package native import ( "context" "errors" "strings" "agent-native/exec" ) // contractBackend 把 Backend 适配为 exec.ContractBackend。 // 独立类型的原因:旧门面 Execute(ctx, exec.Request) 与契约的 // Execute(ctx, exec.ContractRequest) 签名冲突,同名方法不能并存; // 包装让现有 squad 消费(旧门面)与契约消费(conformance/Core)互不影响。 type contractBackend struct{ b *Backend } // Contract 返回该后端的契约形态(N1 WP6;旧门面保持不变)。 func (b *Backend) Contract() exec.ContractBackend { return contractBackend{b: b} } func (c contractBackend) Descriptor() exec.BackendDescriptor { return exec.BackendDescriptor{ Name: c.b.Name(), Capabilities: []exec.Capability{exec.CapText, exec.CapStream, exec.CapTools}, } } func (c contractBackend) Execute(ctx context.Context, req exec.ContractRequest) (*exec.ContractResult, error) { var onEvent func(ev exec.ProcessEvent) if req.OnEvent != nil { next := req.OnEvent onEvent = func(ev exec.ProcessEvent) { next(exec.Event{Kind: exec.EventKind(ev.Type), Text: ev.Text, Tool: ev.Tool, Detail: ev.Detail}) } } // 契约语义:空 ToolPolicy.Profile 等同 reasoning(无工具纯文本任务)。 profile := strings.TrimSpace(req.ToolPolicy.Profile) if profile == "" { profile = exec.ToolProfileReasoning } old := exec.Request{ Prompt: req.Prompt, Instructions: req.Instructions, ExecutionID: req.ExecutionID, Targets: append([]string(nil), req.Targets...), ToolProfile: profile, ToolAllowlist: append([]string(nil), req.ToolPolicy.Allowlist...), WorkDir: req.Workspace.Root, OnEvent: onEvent, } if b := req.ContractBudget; b.MaxPromptTokens > 0 || b.MaxCompletionTokens > 0 || !b.Deadline.IsZero() { old.Budget = &exec.Budget{ MaxPromptTokens: b.MaxPromptTokens, MaxCompletionTokens: b.MaxCompletionTokens, Deadline: b.Deadline, } } res, err := c.b.Execute(ctx, old) if err != nil { // 传输/实现级错误:按契约映射为 Unknown,不经 Go error 返回业务终态。 return &exec.ContractResult{ Outcome: exec.OutcomeUnknown, Err: &exec.Error{Code: exec.ErrProviderUnavailable, Reason: err.Error(), SideEffects: exec.SideEffectPossible}, }, nil } out := &exec.ContractResult{ Logs: res.Logs, Usage: exec.Usage{ PromptTokens: res.PromptTokens, CompletionTokens: res.CompletionTokens, }, } switch { case res.Success: out.Outcome = exec.OutcomeSucceeded out.Files = res.Patches case errors.Is(ctx.Err(), context.Canceled): out.Outcome = exec.OutcomeCanceled out.Err = &exec.Error{Code: exec.ErrCanceled, Reason: res.ErrorReason, SideEffects: exec.SideEffectPossible} case errors.Is(ctx.Err(), context.DeadlineExceeded): out.Outcome = exec.OutcomeFailed out.Err = &exec.Error{Code: exec.ErrDeadlineExceeded, Reason: res.ErrorReason, SideEffects: exec.SideEffectPossible} default: out.Outcome = exec.OutcomeFailed out.Err = &exec.Error{Code: exec.ErrProviderRejected, Reason: res.ErrorReason, Retryable: res.Retryable, SideEffects: exec.SideEffectPossible} } return out, nil }