package service import ( "context" "errors" "time" iop "iop/proto/gen/iop" ) var ( ErrSingleRequestInternalArtifactUnavailable = errors.New("single-request internal artifact is unavailable") ErrSingleRequestInternalArtifactInvalid = errors.New("single-request internal artifact request is invalid") ErrSingleRequestInternalArtifactBudget = errors.New("single-request internal artifact budget exceeded") ErrSingleRequestInternalArtifactFailed = errors.New("single-request internal artifact operation failed") ErrSingleRequestWorkspaceOpen = errors.New("single-request workspace open failed") ) // SingleRequestArtifactKind is the coordinator-facing closed artifact set. It // deliberately carries no filename or relative path. type SingleRequestArtifactKind string const ( SingleRequestArtifactPlan SingleRequestArtifactKind = "plan" SingleRequestArtifactReview SingleRequestArtifactKind = "review" ) type singleRequestWorkspaceArtifactRuntime interface { workspaceArtifact(context.Context, *SingleRequestWorkspaceBinding, *iop.WorkspaceArtifactRequest, int) (*iop.WorkspaceArtifactResponse, error) } type singleRequestArtifactOperation struct { ctx context.Context cancel context.CancelFunc runtime singleRequestWorkspaceArtifactRuntime openRuntime singleRequestWorkspaceToolRuntime binding *SingleRequestWorkspaceBinding kind iop.WorkspaceArtifactKind maximum int complete func() } func (h *singleRequestHandle) ReadInternalArtifact(ctx context.Context, kind SingleRequestArtifactKind) ([]byte, error) { operation, err := h.prepareSingleRequestArtifact(ctx, kind, 0) if err != nil { h.failSingleRequestArtifact(ctx, err) return nil, err } defer operation.cancel() defer operation.complete() if err := operation.ctx.Err(); err != nil { return nil, h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := h.ensureSingleRequestWorkspaceOpen(operation.ctx, operation.openRuntime, operation.binding); err != nil { return nil, h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := operation.ctx.Err(); err != nil { return nil, h.finishSingleRequestArtifactFailure(operation.ctx, err) } response, err := operation.runtime.workspaceArtifact(operation.ctx, operation.binding, &iop.WorkspaceArtifactRequest{ RequestId: h.req.RequestID, Kind: operation.kind, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ, }, operation.maximum) if err != nil || response.GetStatus() != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { return nil, h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := operation.ctx.Err(); err != nil { return nil, h.finishSingleRequestArtifactFailure(operation.ctx, err) } return append([]byte(nil), response.GetContent()...), nil } func (h *singleRequestHandle) WriteInternalArtifact(ctx context.Context, kind SingleRequestArtifactKind, content []byte) error { operation, err := h.prepareSingleRequestArtifact(ctx, kind, len(content)) if err != nil { h.failSingleRequestArtifact(ctx, err) return err } defer operation.cancel() defer operation.complete() if err := operation.ctx.Err(); err != nil { return h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := h.ensureSingleRequestWorkspaceOpen(operation.ctx, operation.openRuntime, operation.binding); err != nil { return h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := operation.ctx.Err(); err != nil { return h.finishSingleRequestArtifactFailure(operation.ctx, err) } response, err := operation.runtime.workspaceArtifact(operation.ctx, operation.binding, &iop.WorkspaceArtifactRequest{ RequestId: h.req.RequestID, Kind: operation.kind, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: append([]byte(nil), content...), }, operation.maximum) if err != nil || response.GetStatus() != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS { return h.finishSingleRequestArtifactFailure(operation.ctx, err) } if err := operation.ctx.Err(); err != nil { return h.finishSingleRequestArtifactFailure(operation.ctx, err) } return nil } func (h *singleRequestHandle) prepareSingleRequestArtifact(ctx context.Context, kind SingleRequestArtifactKind, contentBytes int) (*singleRequestArtifactOperation, error) { if ctx == nil { return nil, ErrSingleRequestInternalArtifactInvalid } if err := ctx.Err(); err != nil { return nil, err } protoKind, ok := singleRequestArtifactProtoKind(kind) if !ok { return nil, ErrSingleRequestInternalArtifactInvalid } h.mu.Lock() if isTerminalState(h.state) || h.state == SingleRequestStateFinalizing { h.mu.Unlock() return nil, ErrSingleRequestTerminal } if h.state == SingleRequestStateInternalTool || h.activeStageIDLocked() == "" || h.toolLoop.stageDeadline.IsZero() { h.mu.Unlock() return nil, ErrSingleRequestInternalArtifactInvalid } openRuntime := h.toolLoop.runtime runtime, ok := openRuntime.(singleRequestWorkspaceArtifactRuntime) binding := h.binding.Workspace.Clone() deadline := h.toolLoop.stageDeadline if !ok || runtime == nil || binding == nil { h.mu.Unlock() return nil, ErrSingleRequestInternalArtifactUnavailable } maximum := binding.Limits.MaxOutputBytes if maximum < 1 || contentBytes < 0 || contentBytes > maximum { h.mu.Unlock() return nil, ErrSingleRequestInternalArtifactBudget } if !time.Now().Before(deadline) { h.mu.Unlock() return nil, ErrSingleRequestInternalArtifactBudget } h.toolWg.Add(1) h.toolWork++ h.mu.Unlock() operationCtx, cancel := singleRequestArtifactContext(h.execCtx, ctx, deadline) return &singleRequestArtifactOperation{ ctx: operationCtx, cancel: cancel, runtime: runtime, openRuntime: openRuntime, binding: binding, kind: protoKind, maximum: maximum, complete: func() { h.mu.Lock() h.toolWork-- h.mu.Unlock() h.toolWg.Done() }, }, nil } func singleRequestArtifactProtoKind(kind SingleRequestArtifactKind) (iop.WorkspaceArtifactKind, bool) { switch kind { case SingleRequestArtifactPlan: return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, true case SingleRequestArtifactReview: return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW, true default: return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED, false } } func singleRequestArtifactContext(execution, caller context.Context, stageDeadline time.Time) (context.Context, context.CancelFunc) { deadline := stageDeadline if callerDeadline, ok := caller.Deadline(); ok && callerDeadline.Before(deadline) { deadline = callerDeadline } ctx, cancel := context.WithDeadline(execution, deadline) stop := context.AfterFunc(caller, cancel) return ctx, func() { stop() cancel() } } func (h *singleRequestHandle) finishSingleRequestArtifactFailure(ctx context.Context, _ error) error { err := ErrSingleRequestInternalArtifactFailed if ctx != nil && ctx.Err() != nil { err = ctx.Err() } h.failSingleRequestArtifact(ctx, err) return err } func (h *singleRequestHandle) failSingleRequestArtifact(ctx context.Context, err error) { fallback := singleRequestErrorClassFromErr(err) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, ErrSingleRequestInternalArtifactBudget) { fallback = singleRequestErrorClassTimeout } outcome, errorClass := h.classifyChildOperationContext(ctx, fallback) h.mu.Lock() defer h.mu.Unlock() if isTerminalState(h.state) || h.state == SingleRequestStateFinalizing { return } if outcome == singleRequestOutcomeCancel { h.cancelLocked() return } if errorClass == singleRequestErrorClassInternalToolBudget { h.failLockedWithErrorClass(ErrSingleRequestInternalToolBudget, singleRequestErrorClassInternalToolBudget) return } if errorClass == singleRequestErrorClassTimeout { h.failLockedWithErrorClass(ErrSingleRequestInternalArtifactBudget, singleRequestErrorClassTimeout) return } h.failLockedWithErrorClass(err, errorClass) } // ensureSingleRequestWorkspaceOpen serializes the one permitted open attempt // across model-tool and internal-artifact callers. A failed attempt is cached so // no racing caller can silently send a second open on the same request. func (h *singleRequestHandle) ensureSingleRequestWorkspaceOpen(ctx context.Context, runtime singleRequestWorkspaceToolRuntime, binding *SingleRequestWorkspaceBinding) error { for { if err := ctx.Err(); err != nil { return err } h.mu.Lock() if h.toolLoop.opened { h.mu.Unlock() return nil } if h.toolLoop.opening { done := h.toolLoop.openDone h.mu.Unlock() select { case <-done: continue case <-ctx.Done(): return ctx.Err() } } if h.toolLoop.openAttempted { err := h.toolLoop.openErr if err == nil { err = ErrSingleRequestWorkspaceOpen } h.mu.Unlock() return err } if runtime == nil || binding == nil { h.mu.Unlock() return ErrSingleRequestWorkspaceOpen } h.toolLoop.openAttempted = true h.toolLoop.opening = true h.toolLoop.openDone = make(chan struct{}) done := h.toolLoop.openDone h.mu.Unlock() response, err := runtime.workspaceOpen(ctx, binding, &iop.WorkspaceOpenRequest{ RequestId: h.req.RequestID, WorkspaceRef: binding.Ref, TimeoutMs: singleRequestContextRemainingMilliseconds(ctx), }) accepted := err == nil && response.GetStatus() == iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS h.mu.Lock() if accepted { h.toolLoop.opened = true h.toolLoop.openErr = nil } else { h.toolLoop.openErr = ErrSingleRequestWorkspaceOpen } h.toolLoop.opening = false close(done) h.mu.Unlock() if !accepted { if ctx.Err() != nil { return ctx.Err() } return ErrSingleRequestWorkspaceOpen } if ctx.Err() != nil { return ctx.Err() } return nil } } func singleRequestContextRemainingMilliseconds(ctx context.Context) int64 { deadline, ok := ctx.Deadline() if !ok { return 0 } return internalToolRemainingMilliseconds(deadline) }