Files
Agent-Native/exec/contract_spec.go
weichenandCursor 4a10818535 Drop the intent adapter and fold contract into exec
Why: native must compile without agent-intent, and the two execution packages were the same contract under two names.
Evidence: GOWORK=off go test -count=1 ./... passed, including schema.TestRequestAndResultRoundTrip and the native adapter tests after Options was split into embedded sub-structs.
Impact: adapters/intentgate is gone, go.mod no longer requires agent-intent, and the nested gitlink stays for the release task. Callers of Options composite literals name the sub-struct.
Refs: D1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 13:31:48 +00:00

285 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 原 contract 包并入 exec。与本文件同名的执行门面仍是 Request、Result、Backend;
// 设计契约里冲突的四个名字加 Contract 前缀。
//
// 本包是助手(Core)与小队(Squad)共用的唯一执行接口层:Core 注入具体
// ContractBackend 实现,Squad 只表达需求并使用已注入的后端。契约不依赖任何具体
// 内核、产品类型、数据库或配置包。
//
// 语义不变量(实现方与消费方都必须遵守):
//
// 1. ExecutionID 在一次逻辑执行内唯一;步骤重试与受控 fallback 必须复用
// 同一个 ExecutionID,并共享预算与截止时间。
// 2. Outcome 取值 Succeeded/Failed/Canceled/Unknown 四态之一;Unknown 表示
// 结果不确定(可能已有副作用),绝不静默降级为 Succeeded 或 Failed。
// 3. 重试只有一个责任层:步骤级重试归调用方(Squad),传输级有界重试归
// adapter,循环 turn 上限归 runtime;不得多层重试产生重复写入。
// 4. 取消必须传播到模型请求、工具调用与子进程;不支持可靠取消的实现必须
// 报告 cancel_requested 并保持 Unknown,不得伪报 Canceled。
// 5. 原生执行失败且已有文件或外部动作、或结果 Unknown 时,禁止无条件换
// 后端重放;是否可重放由调用方依据 Error.SideEffects 判定。
// 6. 执行 transcript 与产品聊天历史不是同一账本;SessionScope 仅用于隔离
// 上下文,不授予跨会话可见性。
// 7. Workspace 的路径约束不是 OS 沙箱;执行不可信第三方工具需要调用方
// 提供的进程/容器隔离,不能由本契约推导。
package exec
import (
"context"
"time"
)
// Capability 声明后端可验证的能力。解析器按任务需求选择实现;必须能力
// 缺失时拒绝执行,而不是静默降级到 Stub。
type Capability string
const (
// CapText 表示能返回非空文本结果(对话、摘要等纯文本任务)。
CapText Capability = "text"
// CapFiles 表示能按 Targets 交付文件候选产物(reply.md/output 流程)。
CapFiles Capability = "files"
// CapStream 表示支持过程事件流(OnEvent 回调有实际内容)。
CapStream Capability = "stream"
// CapTools 表示支持受控工具调用(受 ToolPolicy 约束)。
CapTools Capability = "tools"
// CapCancel 表示支持可靠取消(取消传播到模型/工具/子进程)。
CapCancel Capability = "cancel"
// CapResume 表示支持跨进程恢复执行(配合 ExecutionJournal 端口)。
CapResume Capability = "resume"
// CapUsage 表示能报告 token 用量。
CapUsage Capability = "usage"
// CapStructuredOutput 表示支持结构化(非纯文本)输出。
CapStructuredOutput Capability = "structured-output"
)
// ToolPolicy 描述执行允许的工具范围。实现不得自行扩展;未知工具一律拒绝。
type ToolPolicy struct {
// Profile 为策略名(workspace/reasoning/或产品注册的自定义名)。
Profile string `json:"profile"`
// Allowlist 显式列出额外允许的工具名;与 Profile 取交集语义由实现声明,
// 未声明时视为仅 Allowlist。
Allowlist []string `json:"allowlist,omitempty"`
}
// WorkspaceHandle 是调用方已授权的工作区句柄。Root 之外的路径访问必须失败。
// 它表达路径授权,不构成操作系统级隔离。
type WorkspaceHandle struct {
// Root 为授权根目录的绝对路径(已归一化)。
Root string `json:"root"`
// Writable 声明允许写入的相对路径集合;空表示由产品规则决定(兼容现状)。
Writable []string `json:"writable,omitempty"`
}
// SessionScope 隔离执行上下文:不同 tenant/project/session 的会话、transcript、
// 授权与预算互不可见。字段为产品侧标识,契约层只要求非空时保持隔离。
type SessionScope struct {
Tenant string `json:"tenant,omitempty"`
Project string `json:"project,omitempty"`
Session string `json:"session,omitempty"`
}
// ContractBudget 约束一次执行(含共享 ExecutionID 的重试)。
type ContractBudget struct {
// MaxPromptTokens/MaxCompletionTokens 为 0 时不限制。
MaxPromptTokens int `json:"max_prompt_tokens,omitempty"`
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
// Deadline 为截止时间;零值表示无外部截止(仍受 ctx 控制)。
Deadline time.Time `json:"deadline,omitempty"`
}
// Message 是可选的历史上下文(多轮对话/延续任务)。实现可以忽略,但不得
// 跨 SessionScope 混入。
type Message struct {
Role string `json:"role"` // user | assistant | tool | system
Content string `json:"content"`
}
// ContractRequest 是一次授权执行的统一输入。字段为设计契约;现有 file-only 流程
// (Prompt+Instructions+Targets→reply.md)是其子集 facade。
type ContractRequest struct {
// ExecutionID 由调用方生成,重试与受控 fallback 复用同值。
ExecutionID string `json:"execution_id"`
// SessionScope 为隔离身份;可为零值(一次性任务)。
SessionScope SessionScope `json:"session_scope"`
Prompt string `json:"prompt"`
Instructions string `json:"instructions,omitempty"`
// Context 为可选历史消息;实现不得将其与 Prompt 之外的数据混合。
Context []Message `json:"context,omitempty"`
// Targets 为期望交付的文件相对路径(文件任务);纯文本任务可空。
Targets []string `json:"targets,omitempty"`
// Workspace 为授权工作区;文件任务的读写都必须限制在 Root 内。
Workspace WorkspaceHandle `json:"workspace"`
// ToolPolicy 为工具范围;空 Profile 等同 reasoning。
ToolPolicy ToolPolicy `json:"tool_policy"`
// ContractBudget 为本次执行预算。
ContractBudget ContractBudget `json:"budget"`
// Required 为必须满足的能力;解析器据此选择实现,缺失即拒绝。
Required []Capability `json:"required,omitempty"`
// OnEvent 为可选过程事件回调(nil = 调用方不需要过程流)。
// 回调必须在 Execute 返回前完成;回调内不得阻塞。
OnEvent func(Event) `json:"-"`
}
// Outcome 是执行终态。Unknown 优先于乐观判定:凡无法确认结果(含进程崩溃、
// 传输中断后未收到终态)一律 Unknown,由调用方恢复。
type Outcome string
const (
OutcomeSucceeded Outcome = "succeeded"
OutcomeFailed Outcome = "failed"
OutcomeCanceled Outcome = "canceled"
OutcomeUnknown Outcome = "unknown"
)
// EventKind 过程事件类型(与现有 ProcessEvent 投影兼容)。
type EventKind string
const (
EventTextDelta EventKind = "text_delta"
EventReasoningDelta EventKind = "reasoning_delta"
EventToolCallDelta EventKind = "tool_call_delta"
EventToolExecStart EventKind = "tool_execution_start"
EventToolExecEnd EventKind = "tool_execution_end"
EventMessageEnd EventKind = "message_end"
)
// Event 是过程事件帧;消费方(Core)负责映射为 SSE/持久化事件。
type Event struct {
Kind EventKind `json:"kind"`
Text string `json:"text,omitempty"` // *_delta 文本
Tool string `json:"tool,omitempty"` // tool_* 工具名
Detail string `json:"detail,omitempty"` // 工具参数/结果摘要
}
// SideEffects 描述失败时的副作用状态,决定是否允许重试/fallback 重放。
type SideEffects string
const (
// SideEffectNone:确认未产生任何外部副作用(未派发或纯读取后失败)。
SideEffectNone SideEffects = "none"
// SideEffectPossible:可能已有副作用(文件已写、外部工具已调用、
// 终态未知)。禁止无条件重放;恢复判断归调用方。
SideEffectPossible SideEffects = "possible"
)
// ErrorCode 稳定错误分类。未知代码按 Unknown 处理。
type ErrorCode string
const (
ErrCanceled ErrorCode = "agent.canceled"
ErrDeadlineExceeded ErrorCode = "agent.deadline_exceeded"
ErrBudgetExceeded ErrorCode = "agent.budget_exceeded"
ErrProviderUnavailable ErrorCode = "agent.provider_unavailable"
ErrProviderRejected ErrorCode = "agent.provider_rejected"
ErrToolRejected ErrorCode = "agent.tool_rejected"
ErrPathOutsideWorkspace ErrorCode = "agent.path_outside_workspace"
ErrMissingCapability ErrorCode = "agent.missing_capability"
ErrMissingDeliverable ErrorCode = "agent.missing_deliverable"
ErrEmptyOutput ErrorCode = "agent.empty_output"
ErrUnknownOutcome ErrorCode = "agent.unknown_outcome"
ErrInternal ErrorCode = "agent.internal"
)
// Error 是标准化执行错误。Retryable 仅表示传输级瞬时故障可由 adapter 有界
// 重试或调用方作为新步骤重试;与 SideEffects 独立判定。
type Error struct {
Code ErrorCode `json:"code"`
Reason string `json:"reason,omitempty"`
Retryable bool `json:"retryable,omitempty"`
SideEffects SideEffects `json:"side_effects"`
}
func (e *Error) Error() string {
if e == nil {
return ""
}
return string(e.Code) + ": " + e.Reason
}
// ErrorCode 返回规范码字符串。日志库用这个方法识别错误,因此 contract 不导入错误码模块。
func (e *Error) ErrorCode() string {
if e == nil {
return ""
}
return string(e.Code)
}
// ErrorFields 返回可写入日志的字段。执行标识由调用方随日志字段另传,不放进本结构。
func (e *Error) ErrorFields() map[string]any {
if e == nil {
return nil
}
fields := map[string]any{
"retryable": e.Retryable,
}
if e.Reason != "" {
fields["reason"] = e.Reason
}
if e.SideEffects != "" {
fields["side_effects"] = string(e.SideEffects)
}
return fields
}
// Usage 报告 token 用量;实现报告不了时保持零值,不猜测。
type Usage struct {
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
}
// ContractResult 是执行终态。Text 与 Files 依据任务可为空;Outcome 决定解释。
type ContractResult struct {
Outcome Outcome `json:"outcome"`
// Text 为文本结果(CapText 任务必填)。
Text string `json:"text,omitempty"`
// Files 为文件候选产物(内容字节),由调用方决定正式落盘与提交。
Files map[string][]byte `json:"files,omitempty"`
// Logs 为执行日志/过程摘要(兼容现有 ContractResult.Logs)。
Logs string `json:"logs,omitempty"`
// Err 为 Failed/Canceled/Unknown 时的标准化错误;Succeeded 时为 nil。
Err *Error `json:"err,omitempty"`
// Usage 为 token 用量。
Usage Usage `json:"usage"`
}
// BackendDescriptor 声明实现身份与可验证能力;前端只展示经授权的描述。
type BackendDescriptor struct {
// Name 为规范后端名(如 agent-native、pi-agent)。规范名 agent-native;
// pi-native 为兼容别名,注册表负责别名解析与弃用周期。
Name string `json:"name"`
// Capabilities 为该实现声明且经验证的能力集合。
Capabilities []Capability `json:"capabilities"`
// AdapterVersion 标识实现版本(内核或适配器),用于收据与回退审计。
AdapterVersion string `json:"adapter_version,omitempty"`
}
// Has 报告描述是否声明指定能力。
func (d BackendDescriptor) Has(c Capability) bool {
for _, x := range d.Capabilities {
if x == c {
return true
}
}
return false
}
// ContractBackend 是统一执行接口。实现必须:
// - 在 ctx 取消时尽快返回(Canceled 或 Unknown);
// - 不持有跨执行的可变会话状态(连接池等无状态资源可复用);
// - 不自行扩大工具范围或读取 Workspace 之外的路径。
type ContractBackend interface {
Descriptor() BackendDescriptor
// Execute 执行一次授权调用。返回的 ContractResult 永不为 nil;ctx 错误映射为
// Outcome=Canceled/Unknown,不返回 Go error 表达业务终态。
Execute(ctx context.Context, req ContractRequest) (*ContractResult, error)
}
// Resolver 按名称与必需能力解析后端。未知名必须返回错误,生产路径不得
// 回落到 Stub。
type Resolver interface {
Resolve(name string, required []Capability) (ContractBackend, error)
}