260 lines
11 KiB
Go
260 lines
11 KiB
Go
// Package contract 定义 Agent-Native 的公共执行契约。
|
||
//
|
||
// 本包是助手(Core)与小队(Squad)共用的唯一执行接口层:Core 注入具体
|
||
// Backend 实现,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 contract
|
||
|
||
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"
|
||
)
|
||
|
||
// ToolProfile 是预定义工具策略名;与现有产品配置兼容。
|
||
const (
|
||
ToolProfileWorkspace = "workspace" // 默认:文档类工具受限集合
|
||
ToolProfileReasoning = "reasoning" // 无工具,仅推理
|
||
)
|
||
|
||
// 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"`
|
||
}
|
||
|
||
// Budget 约束一次执行(含共享 ExecutionID 的重试)。
|
||
type Budget 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"`
|
||
}
|
||
|
||
// Request 是一次授权执行的统一输入。字段为设计契约;现有 file-only 流程
|
||
// (Prompt+Instructions+Targets→reply.md)是其子集 facade。
|
||
type Request 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"`
|
||
// Budget 为本次执行预算。
|
||
Budget Budget `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 = "canceled"
|
||
ErrDeadlineExceeded ErrorCode = "deadline_exceeded"
|
||
ErrBudgetExceeded ErrorCode = "budget_exceeded"
|
||
ErrProviderUnavailable ErrorCode = "provider_unavailable"
|
||
ErrProviderRejected ErrorCode = "provider_rejected"
|
||
ErrToolRejected ErrorCode = "tool_rejected"
|
||
ErrPathOutsideWorkspace ErrorCode = "path_outside_workspace"
|
||
ErrMissingCapability ErrorCode = "missing_capability"
|
||
ErrMissingDeliverable ErrorCode = "missing_deliverable"
|
||
ErrEmptyOutput ErrorCode = "empty_output"
|
||
ErrUnknownOutcome ErrorCode = "unknown_outcome"
|
||
ErrInternal ErrorCode = "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 { return string(e.Code) + ": " + e.Reason }
|
||
|
||
// Usage 报告 token 用量;实现报告不了时保持零值,不猜测。
|
||
type Usage struct {
|
||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||
}
|
||
|
||
// Result 是执行终态。Text 与 Files 依据任务可为空;Outcome 决定解释。
|
||
type Result struct {
|
||
Outcome Outcome `json:"outcome"`
|
||
// Text 为文本结果(CapText 任务必填)。
|
||
Text string `json:"text,omitempty"`
|
||
// Files 为文件候选产物(内容字节),由调用方决定正式落盘与提交。
|
||
Files map[string][]byte `json:"files,omitempty"`
|
||
// Logs 为执行日志/过程摘要(兼容现有 Result.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
|
||
}
|
||
|
||
// Backend 是统一执行接口。实现必须:
|
||
// - 在 ctx 取消时尽快返回(Canceled 或 Unknown);
|
||
// - 不持有跨执行的可变会话状态(连接池等无状态资源可复用);
|
||
// - 不自行扩大工具范围或读取 Workspace 之外的路径。
|
||
type Backend interface {
|
||
Descriptor() BackendDescriptor
|
||
// Execute 执行一次授权调用。返回的 Result 永不为 nil;ctx 错误映射为
|
||
// Outcome=Canceled/Unknown,不返回 Go error 表达业务终态。
|
||
Execute(ctx context.Context, req Request) (*Result, error)
|
||
}
|
||
|
||
// Resolver 按名称与必需能力解析后端。未知名必须返回错误,生产路径不得
|
||
// 回落到 Stub。
|
||
type Resolver interface {
|
||
Resolve(name string, required []Capability) (Backend, error)
|
||
}
|