708 lines
25 KiB
Go
708 lines
25 KiB
Go
package openai
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const defaultArtifactFrontierCapacity = 1024
|
|
|
|
type artifactFrontierPhase string
|
|
|
|
const (
|
|
artifactPhasePinned artifactFrontierPhase = "pinned"
|
|
artifactPhasePreparePending artifactFrontierPhase = "prepare_pending"
|
|
artifactPhasePairReady artifactFrontierPhase = "pair_ready"
|
|
artifactPhasePairPending artifactFrontierPhase = "pair_pending"
|
|
artifactPhaseLocalEligible artifactFrontierPhase = "local_eligible"
|
|
)
|
|
|
|
type artifactDispositionKind string
|
|
|
|
const (
|
|
artifactDispositionResumeSelector artifactDispositionKind = "resume_selector"
|
|
artifactDispositionLocalEligible artifactDispositionKind = "local_eligible"
|
|
)
|
|
|
|
type artifactDisposition struct {
|
|
Kind artifactDispositionKind
|
|
SelectorStageID string
|
|
PrimaryError *hotPathEndpointError
|
|
}
|
|
|
|
// presetIngressResult carries a control decision that the public handler must
|
|
// consume before it can construct or submit another provider-pool request.
|
|
// It deliberately keeps the artifact disposition out of caller-controlled
|
|
// metadata, which is only a transport for trusted logical request IDs.
|
|
type presetIngressResult struct {
|
|
Artifact artifactDisposition
|
|
Light hotPathLightDisposition
|
|
Cleanup *hotPathCleanupTurn
|
|
Terminal *hotPathTerminalIntent
|
|
}
|
|
|
|
func (r presetIngressResult) localStageEligible() bool {
|
|
return r.Artifact.Kind == artifactDispositionLocalEligible
|
|
}
|
|
|
|
func (r presetIngressResult) lightStageContinuation() bool {
|
|
return r.Light.RequestID != "" && r.Light.Terminal == nil
|
|
}
|
|
|
|
func (r presetIngressResult) cleanupIssued() bool {
|
|
return r.Cleanup != nil
|
|
}
|
|
|
|
func (r presetIngressResult) terminalReady() bool {
|
|
return r.Terminal != nil
|
|
}
|
|
|
|
type artifactFrontierRecord struct {
|
|
requestID string
|
|
ownerEdgeID string
|
|
principalRef string
|
|
protocol string
|
|
selectorStageID string
|
|
lineage logicalRequestLineage
|
|
binding *workspaceBinding
|
|
phase artifactFrontierPhase
|
|
pending map[string]*workspaceEncodedPayload
|
|
pendingHash string
|
|
consumedHashes map[string]struct{}
|
|
consumedIDs map[string]struct{}
|
|
}
|
|
|
|
// artifactFrontierStore owns the request-local workspace binding and the sole
|
|
// prepare/pair receipt frontier. Its fixed capacity prevents abandoned caller
|
|
// continuations from growing Edge-local state without bound.
|
|
type artifactFrontierStore struct {
|
|
mu sync.Mutex
|
|
capacity int
|
|
records map[string]*artifactFrontierRecord
|
|
}
|
|
|
|
func newArtifactFrontierStore(capacity int) *artifactFrontierStore {
|
|
if capacity <= 0 {
|
|
capacity = defaultArtifactFrontierCapacity
|
|
}
|
|
return &artifactFrontierStore{capacity: capacity, records: make(map[string]*artifactFrontierRecord)}
|
|
}
|
|
|
|
func (s *artifactFrontierStore) pin(
|
|
requestID, ownerEdgeID, principalRef, protocol, selectorStageID string,
|
|
lineage logicalRequestLineage,
|
|
binding *workspaceBinding,
|
|
) error {
|
|
if s == nil || binding == nil {
|
|
return fmt.Errorf("artifact frontier binding is unavailable")
|
|
}
|
|
if !validLogicalRequestID(requestID) || !validLogicalRequestID(selectorStageID) {
|
|
return fmt.Errorf("artifact frontier identity is invalid")
|
|
}
|
|
if strings.TrimSpace(ownerEdgeID) == "" || strings.TrimSpace(principalRef) == "" {
|
|
return fmt.Errorf("artifact frontier owner and principal are required")
|
|
}
|
|
if !artifactProtocolMatchesLineage(protocol, lineage) {
|
|
return fmt.Errorf("artifact frontier protocol does not match request lineage")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, exists := s.records[requestID]; exists {
|
|
return fmt.Errorf("artifact frontier already exists")
|
|
}
|
|
if len(s.records) >= s.capacity {
|
|
return fmt.Errorf("artifact frontier capacity reached")
|
|
}
|
|
s.records[requestID] = &artifactFrontierRecord{
|
|
requestID: requestID, ownerEdgeID: ownerEdgeID, principalRef: principalRef,
|
|
protocol: protocol, selectorStageID: selectorStageID, lineage: lineage,
|
|
binding: binding, phase: artifactPhasePinned,
|
|
consumedHashes: make(map[string]struct{}), consumedIDs: make(map[string]struct{}),
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func artifactProtocolMatchesLineage(protocol string, lineage logicalRequestLineage) bool {
|
|
switch protocol {
|
|
case "openai":
|
|
return lineage.Endpoint == logicalRequestEndpointChat
|
|
case "anthropic":
|
|
return lineage.Endpoint == logicalRequestEndpointAnthropic
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (s *artifactFrontierStore) remove(requestID, ownerEdgeID string) {
|
|
if s == nil || requestID == "" {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if record := s.records[requestID]; record != nil && record.ownerEdgeID == ownerEdgeID {
|
|
delete(s.records, requestID)
|
|
}
|
|
}
|
|
|
|
func (s *artifactFrontierStore) has(requestID, ownerEdgeID string) bool {
|
|
if s == nil || requestID == "" {
|
|
return false
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
record := s.records[requestID]
|
|
return record != nil && record.ownerEdgeID == ownerEdgeID
|
|
}
|
|
|
|
// pairRequired reports whether the retained selector may only author the
|
|
// exact Plan/Review pair. The store owns the phase and keeps this observation
|
|
// lock-safe so a handler cannot infer it from untrusted request metadata.
|
|
func (s *artifactFrontierStore) pairRequired(requestID, ownerEdgeID string) bool {
|
|
if s == nil || requestID == "" {
|
|
return false
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
record := s.records[requestID]
|
|
return record != nil && record.ownerEdgeID == ownerEdgeID && record.phase == artifactPhasePairReady
|
|
}
|
|
|
|
func (s *artifactFrontierStore) issue(
|
|
turn *hotPathTurn,
|
|
output normalizedStageOutput,
|
|
coordinator *logicalRequestCoordinator,
|
|
) (normalizedStageOutput, error) {
|
|
if s == nil || coordinator == nil || turn == nil {
|
|
return normalizedStageOutput{}, fmt.Errorf("artifact frontier is unavailable")
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
record := s.records[turn.RequestID]
|
|
if record == nil {
|
|
return normalizedStageOutput{}, fmt.Errorf("artifact frontier is not pinned")
|
|
}
|
|
if record.ownerEdgeID != turn.OwnerEdgeID || record.principalRef != turn.PrincipalRef {
|
|
return normalizedStageOutput{}, fmt.Errorf("artifact frontier owner or principal mismatch")
|
|
}
|
|
if record.protocol != turn.Protocol || record.selectorStageID != turn.StageID {
|
|
return normalizedStageOutput{}, fmt.Errorf("artifact frontier selector stage mismatch")
|
|
}
|
|
|
|
wantPrepare := false
|
|
switch record.phase {
|
|
case artifactPhasePinned:
|
|
wantPrepare = !record.binding.createsParents()
|
|
case artifactPhasePairReady:
|
|
wantPrepare = false
|
|
default:
|
|
return normalizedStageOutput{}, fmt.Errorf("artifact frontier already has a pending or consumed turn")
|
|
}
|
|
|
|
mapped, payloads, err := mapArtifactOutput(record, output, wantPrepare, coordinator)
|
|
if err != nil {
|
|
return normalizedStageOutput{}, err
|
|
}
|
|
if turn.Protocol == "anthropic" {
|
|
mapped.TerminalReason = "tool_use"
|
|
}
|
|
if turn.OuterTurn != nil {
|
|
ctx := context.Background()
|
|
if turn.Request != nil {
|
|
ctx = turn.Request.Context()
|
|
}
|
|
if !output.ProgressivelyReleased {
|
|
if err := runHotPathCollectedStage(ctx, turn.OuterTurn, turn.StageID, mapped); err != nil {
|
|
return normalizedStageOutput{}, fmt.Errorf("collect artifact outer turn: %w", err)
|
|
}
|
|
}
|
|
visible := hotPathCompatibilityOutput(turn.OuterTurn, mapped, turn.Protocol)
|
|
if len(visible.ToolCalls) == 0 && turn.OuterTurn.outputBudget().Exhausted {
|
|
turn.OuterTurn.commitLengthTerminal()
|
|
return hotPathCompatibilityOutput(turn.OuterTurn, mapped, turn.Protocol), nil
|
|
}
|
|
if err := turn.OuterTurn.projectToolIdentities(mapped.ToolCalls); err != nil {
|
|
return normalizedStageOutput{}, err
|
|
}
|
|
mapped = hotPathCompatibilityOutput(turn.OuterTurn, mapped, turn.Protocol)
|
|
}
|
|
issuedHash, err := directIssuedCallHash(turn.Protocol, mapped)
|
|
if err != nil {
|
|
return normalizedStageOutput{}, fmt.Errorf("fingerprint artifact calls: %w", err)
|
|
}
|
|
expected := make([]logicalRequestExpectedTool, 0, len(mapped.ToolCalls))
|
|
for _, call := range mapped.ToolCalls {
|
|
expected = append(expected, logicalRequestExpectedTool{
|
|
PublicCallID: call.ID, ProviderCallID: call.ProviderCallID,
|
|
})
|
|
}
|
|
if _, err := coordinator.awaitToolResults(
|
|
turn.RequestID, turn.OwnerEdgeID, turn.StageID, expected, issuedHash,
|
|
); err != nil {
|
|
return normalizedStageOutput{}, fmt.Errorf("await artifact results: %w", err)
|
|
}
|
|
|
|
record.pending = payloads
|
|
record.pendingHash = issuedHash
|
|
if wantPrepare {
|
|
record.phase = artifactPhasePreparePending
|
|
} else {
|
|
record.phase = artifactPhasePairPending
|
|
}
|
|
return mapped, nil
|
|
}
|
|
|
|
func mapArtifactOutput(
|
|
record *artifactFrontierRecord,
|
|
output normalizedStageOutput,
|
|
wantPrepare bool,
|
|
coordinator *logicalRequestCoordinator,
|
|
) (normalizedStageOutput, map[string]*workspaceEncodedPayload, error) {
|
|
issued := newReservedPaths(record.requestID)
|
|
calls := append([]normalizedToolCall(nil), output.ToolCalls...)
|
|
if wantPrepare {
|
|
if len(calls) != 1 {
|
|
return normalizedStageOutput{}, nil, fmt.Errorf("artifact prepare turn must contain exactly one call")
|
|
}
|
|
mapped, payload, err := mapArtifactCall(record.binding, calls[0], opKindPrepare, issued.JobDir, coordinator)
|
|
if err != nil {
|
|
return normalizedStageOutput{}, nil, err
|
|
}
|
|
return artifactResponseOutput(output, []normalizedToolCall{mapped}), map[string]*workspaceEncodedPayload{mapped.ID: payload}, nil
|
|
}
|
|
|
|
if len(calls) != 2 {
|
|
return normalizedStageOutput{}, nil, fmt.Errorf("artifact pair turn must contain exactly two calls")
|
|
}
|
|
byPath := make(map[string]normalizedToolCall, len(calls))
|
|
for _, call := range calls {
|
|
paths := reservedPathsFromToolCall(call)
|
|
if len(paths) != 1 {
|
|
return normalizedStageOutput{}, nil, fmt.Errorf("artifact pair call has an ambiguous reserved path")
|
|
}
|
|
clean := cleanRelativePath(paths[0])
|
|
if _, duplicate := byPath[clean]; duplicate {
|
|
return normalizedStageOutput{}, nil, fmt.Errorf("artifact pair contains a duplicate path")
|
|
}
|
|
byPath[clean] = call
|
|
}
|
|
|
|
orderedPaths := []string{issued.PlanPath, issued.ReviewPath}
|
|
mappedCalls := make([]normalizedToolCall, 0, 2)
|
|
payloads := make(map[string]*workspaceEncodedPayload, 2)
|
|
for _, requiredPath := range orderedPaths {
|
|
call, ok := byPath[cleanRelativePath(requiredPath)]
|
|
if !ok {
|
|
return normalizedStageOutput{}, nil, fmt.Errorf("artifact pair is missing reserved path %q", requiredPath)
|
|
}
|
|
mapped, payload, err := mapArtifactCall(record.binding, call, opKindWrite, requiredPath, coordinator)
|
|
if err != nil {
|
|
return normalizedStageOutput{}, nil, err
|
|
}
|
|
mappedCalls = append(mappedCalls, mapped)
|
|
payloads[mapped.ID] = payload
|
|
}
|
|
return artifactResponseOutput(output, mappedCalls), payloads, nil
|
|
}
|
|
|
|
func mapArtifactCall(
|
|
binding *workspaceBinding,
|
|
providerCall normalizedToolCall,
|
|
operation workspaceOperationKind,
|
|
requiredPath string,
|
|
coordinator *logicalRequestCoordinator,
|
|
) (normalizedToolCall, *workspaceEncodedPayload, error) {
|
|
providerID := strings.TrimSpace(providerCall.ProviderCallID)
|
|
if providerID == "" {
|
|
providerID = strings.TrimSpace(providerCall.ID)
|
|
}
|
|
if !validLogicalRequestID(providerID) {
|
|
return normalizedToolCall{}, nil, fmt.Errorf("artifact provider tool id is invalid")
|
|
}
|
|
publicID, err := coordinator.newCallID()
|
|
if err != nil {
|
|
return normalizedToolCall{}, nil, fmt.Errorf("allocate artifact public tool id: %w", err)
|
|
}
|
|
providerCall.ID = publicID
|
|
providerCall.ProviderCallID = providerID
|
|
payload, err := encodeWorkspaceCall(binding, operation, providerCall)
|
|
if err != nil {
|
|
return normalizedToolCall{}, nil, fmt.Errorf("encode artifact %s call: %w", operation, err)
|
|
}
|
|
if payload.safePath != cleanRelativePath(requiredPath) {
|
|
return normalizedToolCall{}, nil, fmt.Errorf("artifact call targets %q, want %q", payload.safePath, requiredPath)
|
|
}
|
|
rawArgs, err := json.Marshal(payload.structuredArgs)
|
|
if err != nil {
|
|
return normalizedToolCall{}, nil, fmt.Errorf("encode artifact arguments: %w", err)
|
|
}
|
|
mapped := normalizedToolCall{
|
|
ID: publicID, ProviderCallID: providerID, Name: payload.toolName,
|
|
Arguments: cloneAnyMap(payload.structuredArgs), RawArgs: string(rawArgs), Path: payload.safePath,
|
|
}
|
|
return mapped, payload, nil
|
|
}
|
|
|
|
func artifactResponseOutput(source normalizedStageOutput, calls []normalizedToolCall) normalizedStageOutput {
|
|
return normalizedStageOutput{
|
|
ResponseID: source.ResponseID, Created: source.Created, Content: source.Content,
|
|
Reasoning: source.Reasoning, ReasoningSignature: source.ReasoningSignature, ToolCalls: calls,
|
|
TerminalReason: "tool_calls", Usage: cloneRawJSON(source.Usage), OpenAIUsage: source.OpenAIUsage,
|
|
}
|
|
}
|
|
|
|
func (s *Server) runArtifactPairTurn(turn *hotPathTurn, output normalizedStageOutput, gate hotPathSelectorGate) error {
|
|
if turn == nil {
|
|
return fmt.Errorf("artifact turn is unavailable")
|
|
}
|
|
if strings.TrimSpace(turn.PrincipalRef) == "" {
|
|
turn.PrincipalRef = strings.TrimSpace(turn.Dispatch.PrincipalRef)
|
|
if turn.PrincipalRef == "" {
|
|
turn.PrincipalRef = "anonymous"
|
|
}
|
|
}
|
|
mapped, err := s.artifactFrontiers.issue(turn, output, s.requestCoordinator)
|
|
if err != nil {
|
|
s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
|
|
return s.writeDirectError(turn, 400, "invalid_request_error", fmt.Sprintf("artifact turn rejected: %v", err))
|
|
}
|
|
if turn.OuterTurn != nil && len(mapped.ToolCalls) == 0 && turn.OuterTurn.outputBudget().Exhausted {
|
|
s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
|
|
return s.writeDirectResponse(turn, mapped)
|
|
}
|
|
if s.lightFlows.has(turn.RequestID, turn.OwnerEdgeID) {
|
|
if err := s.lightFlows.commitSelector(turn.RequestID, turn.OwnerEdgeID, output, gate); err != nil {
|
|
s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
|
|
return s.writeDirectError(turn, 400, "invalid_request_error", fmt.Sprintf("light selector commit rejected: %v", err))
|
|
}
|
|
}
|
|
if turn.OuterTurn != nil {
|
|
turn.OuterTurn.commitTerminalSuccess(mapped.TerminalReason)
|
|
mapped = hotPathCompatibilityOutput(turn.OuterTurn, mapped, turn.Protocol)
|
|
}
|
|
if err := s.writeDirectResponse(turn, mapped); err != nil {
|
|
s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) applyArtifactDisposition(
|
|
snap logicalRequestSnapshot,
|
|
disposition artifactDisposition,
|
|
metadata map[string]string,
|
|
) error {
|
|
if metadata == nil {
|
|
return fmt.Errorf("artifact continuation metadata is unavailable")
|
|
}
|
|
callID, err := s.requestCoordinator.newCallID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
metadata["iop_logical_request_id"] = snap.ID
|
|
metadata["iop_call_id"] = callID
|
|
metadata["iop_stage_id"] = disposition.SelectorStageID
|
|
return nil
|
|
}
|
|
|
|
func (s *artifactFrontierStore) consumeChat(
|
|
ownerEdgeID, principalRef string,
|
|
rawBody []byte,
|
|
lineage logicalRequestContinuationLineage,
|
|
coordinator *logicalRequestCoordinator,
|
|
lightFlows *hotPathLightStore,
|
|
) (logicalRequestSnapshot, artifactDisposition, bool, error) {
|
|
results, err := decodeChatWorkspaceResults(rawBody)
|
|
if err != nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, err
|
|
}
|
|
return s.consume(ownerEdgeID, principalRef, "openai", lineage, results, coordinator, lightFlows)
|
|
}
|
|
|
|
func (s *artifactFrontierStore) consumeAnthropic(
|
|
ownerEdgeID, principalRef string,
|
|
rawBody []byte,
|
|
lineage logicalRequestContinuationLineage,
|
|
coordinator *logicalRequestCoordinator,
|
|
lightFlows *hotPathLightStore,
|
|
) (logicalRequestSnapshot, artifactDisposition, bool, error) {
|
|
results, err := decodeAnthropicWorkspaceResults(rawBody)
|
|
if err != nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, err
|
|
}
|
|
return s.consume(ownerEdgeID, principalRef, "anthropic", lineage, results, coordinator, lightFlows)
|
|
}
|
|
|
|
func (s *artifactFrontierStore) consume(
|
|
ownerEdgeID, principalRef, protocol string,
|
|
lineage logicalRequestContinuationLineage,
|
|
results []workspaceResult,
|
|
coordinator *logicalRequestCoordinator,
|
|
lightFlows *hotPathLightStore,
|
|
) (logicalRequestSnapshot, artifactDisposition, bool, error) {
|
|
if s == nil || coordinator == nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, false, nil
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
record, matched, err := s.matchRecordLocked(ownerEdgeID, principalRef, protocol, lineage)
|
|
if !matched || err != nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, matched, err
|
|
}
|
|
if record.pending == nil || record.pendingHash == "" {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact frontier has no pending calls")
|
|
}
|
|
if len(results) != len(record.pending) {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact result set size mismatch")
|
|
}
|
|
seen := make(map[string]struct{}, len(results))
|
|
var primaryFailure *hotPathEndpointError
|
|
for _, result := range results {
|
|
payload := record.pending[result.callID]
|
|
if payload == nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact result id is not in the pending frontier")
|
|
}
|
|
if _, duplicate := seen[result.callID]; duplicate {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact result id is duplicated")
|
|
}
|
|
seen[result.callID] = struct{}{}
|
|
receipt := matchResultReceipt(record.binding, payload, result)
|
|
if !receipt.matched {
|
|
// A valid request lineage, pending call, and immutable issue
|
|
// correlation route an exact receipt-matcher failure to primary
|
|
// cleanup without trusting the result as a success. An invalid issue
|
|
// correlation, or an opaque/malformed result that is not an exact
|
|
// caller report, stays an immediate fail-closed rejection.
|
|
if matchResultCorrelation(record.binding, payload, result) != "" || !workspaceResultIsExact(result) {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true,
|
|
fmt.Errorf("artifact receipt rejected: %s", receipt.mismatchReason)
|
|
}
|
|
if primaryFailure == nil {
|
|
primaryFailure = &hotPathEndpointError{
|
|
Status: http.StatusBadRequest, Type: "invalid_request_error",
|
|
Message: "artifact continuation rejected: artifact receipt rejected: " + receipt.mismatchReason,
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
if primaryFailure != nil && (lightFlows == nil || !lightFlows.has(record.requestID, record.ownerEdgeID)) {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact receipt rejected: result contains an explicit error signal")
|
|
}
|
|
|
|
snap, err := coordinator.consumeContinuationByLineage(ownerEdgeID, principalRef, lineage)
|
|
if err != nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, err
|
|
}
|
|
for id := range record.pending {
|
|
record.consumedIDs[id] = struct{}{}
|
|
}
|
|
record.consumedHashes[record.pendingHash] = struct{}{}
|
|
record.pending = nil
|
|
record.pendingHash = ""
|
|
record.lineage = lineage.Committed
|
|
if primaryFailure != nil {
|
|
return snap, artifactDisposition{
|
|
Kind: artifactDispositionLocalEligible, SelectorStageID: record.selectorStageID,
|
|
PrimaryError: primaryFailure,
|
|
}, true, nil
|
|
}
|
|
|
|
switch record.phase {
|
|
case artifactPhasePreparePending:
|
|
snap, err = coordinator.activateStage(record.requestID, record.ownerEdgeID, record.selectorStageID)
|
|
if err != nil {
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("resume artifact selector stage: %w", err)
|
|
}
|
|
record.phase = artifactPhasePairReady
|
|
return snap, artifactDisposition{Kind: artifactDispositionResumeSelector, SelectorStageID: record.selectorStageID}, true, nil
|
|
case artifactPhasePairPending:
|
|
record.phase = artifactPhaseLocalEligible
|
|
return snap, artifactDisposition{Kind: artifactDispositionLocalEligible, SelectorStageID: record.selectorStageID}, true, nil
|
|
default:
|
|
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact frontier phase cannot consume results")
|
|
}
|
|
}
|
|
|
|
func (s *artifactFrontierStore) matchRecordLocked(
|
|
ownerEdgeID, principalRef, protocol string,
|
|
lineage logicalRequestContinuationLineage,
|
|
) (*artifactFrontierRecord, bool, error) {
|
|
var candidates []*artifactFrontierRecord
|
|
for _, record := range s.records {
|
|
pendingRelated := record.pending != nil && (record.pendingHash == lineage.IssuedCallHash || artifactIDsIntersect(record, lineage.ResultIDs) || record.lineage == lineage.Prefix)
|
|
_, consumedHash := record.consumedHashes[lineage.IssuedCallHash]
|
|
if pendingRelated || consumedHash || artifactConsumedIDsIntersect(record, lineage.ResultIDs) {
|
|
candidates = append(candidates, record)
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
return nil, false, nil
|
|
}
|
|
for _, record := range candidates {
|
|
if _, replay := record.consumedHashes[lineage.IssuedCallHash]; replay {
|
|
return nil, true, fmt.Errorf("artifact frontier replay rejected")
|
|
}
|
|
}
|
|
for _, record := range candidates {
|
|
if record.pendingHash != lineage.IssuedCallHash {
|
|
continue
|
|
}
|
|
if record.ownerEdgeID != ownerEdgeID {
|
|
return nil, true, errLogicalRequestOwnerMismatch
|
|
}
|
|
if record.principalRef != principalRef {
|
|
return nil, true, errLogicalRequestPrincipal
|
|
}
|
|
if record.protocol != protocol || record.lineage != lineage.Prefix {
|
|
return nil, true, errLogicalRequestLineage
|
|
}
|
|
return record, true, nil
|
|
}
|
|
for _, record := range candidates {
|
|
if record.ownerEdgeID == ownerEdgeID && record.principalRef == principalRef && record.protocol == protocol && record.lineage == lineage.Prefix {
|
|
return record, true, nil
|
|
}
|
|
}
|
|
return nil, true, errLogicalRequestLineage
|
|
}
|
|
|
|
func artifactIDsIntersect(record *artifactFrontierRecord, ids []string) bool {
|
|
for _, id := range ids {
|
|
if record.pending[id] != nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func artifactConsumedIDsIntersect(record *artifactFrontierRecord, ids []string) bool {
|
|
for _, id := range ids {
|
|
if _, consumed := record.consumedIDs[id]; consumed {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func decodeChatWorkspaceResults(rawBody []byte) ([]workspaceResult, error) {
|
|
var envelope struct {
|
|
Messages []struct {
|
|
Role string `json:"role"`
|
|
ToolCallID string `json:"tool_call_id"`
|
|
Content json.RawMessage `json:"content"`
|
|
} `json:"messages"`
|
|
}
|
|
if err := json.Unmarshal(rawBody, &envelope); err != nil {
|
|
return nil, fmt.Errorf("decode Chat artifact results: %w", err)
|
|
}
|
|
var reversed []workspaceResult
|
|
for i := len(envelope.Messages) - 1; i >= 0; i-- {
|
|
message := envelope.Messages[i]
|
|
if message.Role != "tool" {
|
|
break
|
|
}
|
|
body, err := workspaceResultBody(message.Content)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode Chat tool result %q: %w", message.ToolCallID, err)
|
|
}
|
|
reversed = append(reversed, workspaceResult{callID: message.ToolCallID, status: "success", body: body})
|
|
}
|
|
results := make([]workspaceResult, len(reversed))
|
|
for i := range reversed {
|
|
results[len(reversed)-1-i] = reversed[i]
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, fmt.Errorf("Chat artifact continuation has no tool results")
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func decodeAnthropicWorkspaceResults(rawBody []byte) ([]workspaceResult, error) {
|
|
var envelope struct {
|
|
Messages []struct {
|
|
Role string `json:"role"`
|
|
Content json.RawMessage `json:"content"`
|
|
} `json:"messages"`
|
|
}
|
|
if err := json.Unmarshal(rawBody, &envelope); err != nil {
|
|
return nil, fmt.Errorf("decode Messages artifact results: %w", err)
|
|
}
|
|
if len(envelope.Messages) == 0 || envelope.Messages[len(envelope.Messages)-1].Role != "user" {
|
|
return nil, fmt.Errorf("Messages artifact continuation has no trailing user results")
|
|
}
|
|
var blocks []struct {
|
|
Type string `json:"type"`
|
|
ToolUseID string `json:"tool_use_id"`
|
|
Content json.RawMessage `json:"content"`
|
|
IsError bool `json:"is_error,omitempty"`
|
|
}
|
|
if err := json.Unmarshal(envelope.Messages[len(envelope.Messages)-1].Content, &blocks); err != nil {
|
|
return nil, fmt.Errorf("decode Messages artifact result blocks: %w", err)
|
|
}
|
|
results := make([]workspaceResult, 0, len(blocks))
|
|
for _, block := range blocks {
|
|
if block.Type != "tool_result" {
|
|
return nil, fmt.Errorf("Messages artifact result contains non-tool_result block")
|
|
}
|
|
body, err := workspaceResultBody(block.Content)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode Messages tool result %q: %w", block.ToolUseID, err)
|
|
}
|
|
status := "success"
|
|
if block.IsError {
|
|
status = "error"
|
|
}
|
|
results = append(results, workspaceResult{callID: block.ToolUseID, status: status, body: body})
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, fmt.Errorf("Messages artifact continuation has no tool results")
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func workspaceResultBody(raw json.RawMessage) (json.RawMessage, error) {
|
|
trimmed := strings.TrimSpace(string(raw))
|
|
if trimmed == "" || trimmed == "null" {
|
|
return nil, nil
|
|
}
|
|
var text string
|
|
if err := json.Unmarshal(raw, &text); err == nil {
|
|
return json.RawMessage(strings.TrimSpace(text)), nil
|
|
}
|
|
var value any
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
return append(json.RawMessage(nil), raw...), nil
|
|
}
|
|
|
|
func decodeArtifactTools(protocol string, rawBody []byte) (any, error) {
|
|
switch protocol {
|
|
case "openai":
|
|
var envelope struct {
|
|
Tools []any `json:"tools"`
|
|
}
|
|
decoder := json.NewDecoder(strings.NewReader(string(rawBody)))
|
|
decoder.UseNumber()
|
|
if err := decoder.Decode(&envelope); err != nil {
|
|
return nil, fmt.Errorf("decode Chat workspace tools: %w", err)
|
|
}
|
|
return envelope.Tools, nil
|
|
case "anthropic":
|
|
var envelope struct {
|
|
Tools []anthropicTool `json:"tools"`
|
|
}
|
|
if err := json.Unmarshal(rawBody, &envelope); err != nil {
|
|
return nil, fmt.Errorf("decode Messages workspace tools: %w", err)
|
|
}
|
|
return envelope.Tools, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported artifact protocol %q", protocol)
|
|
}
|
|
}
|