Define the core contracts: domain model
Details (original message): Define the core contracts: domain model, stream events, HITL, profiles, tools, plugins, UI vocabulary, ingest IR, retrieval and memory; golden-sample conformance tests; TS mirror.
This commit is contained in:
@@ -1,5 +1,25 @@
|
||||
# Agent-System-Core
|
||||
|
||||
系统核心契约仓:领域模型、流式事件协议、HITL 卡片协议、插件 API、设置与 UI 扩展词汇、文档解析/检索/记忆契约。Go 模块路径 agent-system-core;同时发布 JSON Schema 与 TS 类型。不含实现。
|
||||
系统核心契约:领域模型(Project/Conversation/Message/Run/Artifact/Plan)、流式事件信封、
|
||||
HITL 卡片协议、插件 API 与 manifest、AgentProfile 画像、设置 schema、UI 扩展词汇、
|
||||
文档解析(ParsedDocument IR)与检索/记忆契约。Go 模块路径 `agent-system-core`。
|
||||
|
||||
契约与架构见 Agent-System-Documents 设计方案与架构文档;开发规范见 development-guidelines(Dev 仓 `development/guidelines`)。本仓为 Agent-System 多仓组合的组成部分。
|
||||
## 三态一致
|
||||
|
||||
同一契约以三种形态发布,变更须保持一致:
|
||||
|
||||
1. **Go 类型**(`*.go`,camelCase JSON 标签)——后端与代码插件使用。
|
||||
2. **黄金样本**(`testdata/golden/*.json`)——线格式权威,`go test ./...` 强校验;变更显式重生成(`go test ./... -update`)。
|
||||
3. **TS 类型**(`ts/types.ts`)——前端镜像,前端测试按黄金样本做形状断言。
|
||||
|
||||
## 原则
|
||||
|
||||
- 只含契约与纯函数,不含实现、不含 I/O、不依赖生态仓。
|
||||
- 前向兼容:字段只增不删;消费方对未知 `type`/词汇静默忽略(`v` 字段协商)。
|
||||
- 摄取 IR(ParsedDocument)是解析器与下游的隔离层;检索/记忆契约先行、实现后置。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 黄金样本一致性:Go 序列化(线格式契约)与 testdata/golden/*.json 逐字节一致。
|
||||
// ts/types.ts 镜像同一契约;前端测试按样本做形状断言。变更契约须显式更新样本。
|
||||
var update = flag.Bool("update", false, "rewrite golden files")
|
||||
|
||||
func golden(t *testing.T, name string, v any) {
|
||||
t.Helper()
|
||||
data, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join("testdata", "golden", name+".json")
|
||||
if *update {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, append(data, '\n'), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
want, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("golden missing (run go test ./... -update): %v", err)
|
||||
}
|
||||
if string(want) != string(append(data, '\n')) {
|
||||
t.Errorf("%s: golden mismatch:\n got: %s\nwant: %s", name, data, want)
|
||||
}
|
||||
}
|
||||
|
||||
func sampleMessage() Message {
|
||||
return Message{
|
||||
ID: "msg_abc123def456", ConversationID: "cnv_abc123def456", RunID: "run_abc123def456",
|
||||
Role: RoleAssistant,
|
||||
Parts: []Part{
|
||||
{Type: PartThinking, Text: "考虑先检索…", Process: true},
|
||||
{Type: PartToolCall, Process: true, ToolCall: &ToolCallPart{ID: "call_1", Name: "history_search", Arguments: `{"q":"部署"}`, Duration: 12.5}},
|
||||
{Type: PartToolResult, Process: true, ToolResult: &ToolResultPart{CallID: "call_1", Status: "ok", Summary: "3 条命中"}},
|
||||
{Type: PartText, Text: "根据历史记录……"},
|
||||
{Type: PartArtifactRef, Artifact: &ArtifactRef{ArtifactID: "art_abc123def456", Name: "报告.md", Kind: "document"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sampleRun() Run {
|
||||
return Run{
|
||||
ID: "run_abc123def456", ConversationID: "cnv_abc123def456", ProjectID: "prj_abc123def456",
|
||||
Kind: RunConversation, ProfileID: "default", Status: RunCompleted, Phase: PhaseDone,
|
||||
Usage: &Usage{PromptTokens: 1200, CompletionTokens: 300, TotalTokens: 1500, DurationMs: 4200.5, ToolCalls: 2},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenDomain(t *testing.T) {
|
||||
golden(t, "message", sampleMessage())
|
||||
golden(t, "run", sampleRun())
|
||||
golden(t, "project", Project{ID: "prj_abc123def456", Name: "示例项目"})
|
||||
golden(t, "conversation", Conversation{ID: "cnv_abc123def456", ProjectID: "prj_abc123def456", Title: "新会话"})
|
||||
golden(t, "artifact", Artifact{ID: "art_abc123def456", ProjectID: "prj_abc123def456", Name: "报告.md", Kind: "document", MIME: "text/markdown", Size: 128})
|
||||
}
|
||||
|
||||
func TestGoldenEvents(t *testing.T) {
|
||||
golden(t, "event_message_delta", StreamEvent{Seq: 42, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-29T10:00:00Z", V: 1, Type: EvMessageDelta, Payload: json.RawMessage(`{"index":0,"text":"你"}`)})
|
||||
golden(t, "event_agent_spawn", StreamEvent{Seq: 43, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-29T10:00:01Z", V: 1, Type: EvAgentSpawn, Payload: json.RawMessage(`{"agentRun":"run_sub1","name":"检索员","task":"查法规"}`)})
|
||||
golden(t, "event_run_completed", StreamEvent{Seq: 44, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-29T10:00:02Z", V: 1, Type: EvRunCompleted, Payload: json.RawMessage(`{"usage":{"promptTokens":100,"completionTokens":20,"totalTokens":120,"durationMs":1500,"toolCalls":1}}`)})
|
||||
}
|
||||
|
||||
func TestGoldenHITL(t *testing.T) {
|
||||
golden(t, "hitl_card", HITLCard{
|
||||
ID: "hitl_abc123def456", RunID: "run_1", ConversationID: "cnv_1",
|
||||
Question: "采用哪种输出格式?", Kind: HITLKindChoice,
|
||||
Choices: []HITLChoice{
|
||||
{ID: "md", Label: "Markdown 文档", Description: "可版本管理的轻量文档", Impact: "写入 artifacts,可在右栏查看", Recommended: true},
|
||||
{ID: "docx", Label: "Word 文档", Description: "正式交付格式", Impact: "生成 docx 工件"},
|
||||
},
|
||||
AllowFreeInput: true, FreeInputHint: "也可以直接输入其他格式要求",
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenProfileAndTools(t *testing.T) {
|
||||
maxT := 40
|
||||
golden(t, "profile", AgentProfile{
|
||||
ID: "default", Name: "通用助手", Builtin: true, Source: "builtin",
|
||||
SystemPrompt: "你是一个通用智能体助手。",
|
||||
Model: ModelRoute{Chat: "gpt-default", Lightweight: "gpt-lite"},
|
||||
Tools: ToolPolicy{Allow: []string{"*"}, Deny: []string{"http.admin."}, Confirm: []string{"http."}},
|
||||
Context: ContextPolicy{MaxHistoryMessages: 40, MaxToolResultChars: 20000},
|
||||
HITL: HITLPolicy{AskOnAmbiguity: true},
|
||||
Budget: RunBudget{MaxToolCalls: &maxT},
|
||||
})
|
||||
golden(t, "tool_http", ToolDesc{
|
||||
Name: "http_translate", Description: "调用翻译 API", Source: SourceUser,
|
||||
Parameters: json.RawMessage(`{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}`),
|
||||
HTTP: &HTTPToolSpec{Method: "POST", URL: "https://api.example.com/translate", BodyTemplate: `{"q":"{{param.text}}"}`, TimeoutMs: 10000, ResultPath: "data.0"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenIngest(t *testing.T) {
|
||||
golden(t, "parsed_document", ParsedDocument{
|
||||
Meta: DocMeta{Filename: "报告.docx", MIME: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Parser: "builtin:docx", Confidence: 0.92},
|
||||
Blocks: []DocBlock{
|
||||
{Type: BlockHeading, Level: 1, Text: "概述"},
|
||||
{Type: BlockPara, Text: "本文档说明……"},
|
||||
{Type: BlockList, Items: []string{"要点一", "要点二"}},
|
||||
{Type: BlockTable, Rows: [][]string{{"项", "值"}, {"版本", "1.0"}}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenPlugin(t *testing.T) {
|
||||
golden(t, "plugin_manifest", PluginManifest{
|
||||
ID: "example", Name: "示例插件", Version: "0.1.0", APIVersion: PluginAPIVersion,
|
||||
Requires: PluginRequires{Network: []string{"api.example.com"}},
|
||||
Provides: PluginProvides{Tools: []string{"http_echo"}, Intents: []string{"demo"}, Panels: []string{"demo"}, Cards: []string{"note"}, Settings: []string{"greeting"}},
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 纯函数行为 ----
|
||||
|
||||
func TestToolPolicy(t *testing.T) {
|
||||
p := ToolPolicy{Allow: []string{"*"}, Deny: []string{"http.admin."}, Confirm: []string{"http."}}
|
||||
if !p.ToolAllowed("history_search") {
|
||||
t.Error("allow * should permit builtin tools")
|
||||
}
|
||||
if p.ToolAllowed("http.admin.reset") {
|
||||
t.Error("deny prefix should win over allow *")
|
||||
}
|
||||
if !p.ToolAllowed("http.fetch") || !p.NeedsConfirm("http.fetch") {
|
||||
t.Error("http prefix tools allowed but need confirm")
|
||||
}
|
||||
if p.NeedsConfirm("history_search") {
|
||||
t.Error("builtin tool should not need confirm")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlainText(t *testing.T) {
|
||||
d := ParsedDocument{Blocks: []DocBlock{
|
||||
{Type: BlockHeading, Level: 2, Text: "标题"},
|
||||
{Type: BlockPara, Text: "段落"},
|
||||
{Type: BlockTable, Rows: [][]string{{"a", "b"}}},
|
||||
}}
|
||||
got := d.PlainText()
|
||||
for _, want := range []string{"## 标题", "段落", "a\tb"} {
|
||||
if !contains(got, want) {
|
||||
t.Errorf("PlainText missing %q in %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsMergeAndPath(t *testing.T) {
|
||||
base := map[string]any{"models": map[string]any{"chat": "a", "temperature": 0.7}, "ui": map[string]any{"theme": "light"}}
|
||||
over := map[string]any{"models": map[string]any{"chat": "b"}, "ui": map[string]any{"theme": "dark"}}
|
||||
eff := EffectiveSettings(MergeSettings(base, over))
|
||||
if eff.GetString("models.chat", "") != "b" || eff.GetNumber("models.temperature", 0) != 0.7 {
|
||||
t.Error("deep merge failed")
|
||||
}
|
||||
if eff.GetString("models.missing", "def") != "def" {
|
||||
t.Error("missing path should return default")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, sub string) bool {
|
||||
return len(s) >= len(sub) && (func() bool {
|
||||
for i := 0; i+len(sub) <= len(s); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})()
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
// ---- 文档摄取与解析(4.11)----
|
||||
|
||||
// ParsedDocument 是解析中间表示:所有下游(附件上下文/工件预览/检索索引/全文检索)
|
||||
// 只消费 IR,不关心解析器——解析器可替换、可组合、可交叉验证。
|
||||
type ParsedDocument struct {
|
||||
Meta DocMeta `json:"meta"`
|
||||
Blocks []DocBlock `json:"blocks"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type DocMeta struct {
|
||||
Filename string `json:"filename"`
|
||||
MIME string `json:"mime,omitempty"`
|
||||
Parser string `json:"parser"` // builtin:md / builtin:docx / mineru / agent
|
||||
Confidence float64 `json:"confidence,omitempty"` // 0–1;交叉验证差异块拉低
|
||||
CrossCheck *CrossCheck `json:"crossCheck,omitempty"`
|
||||
ParsedAt time.Time `json:"parsedAt"`
|
||||
}
|
||||
|
||||
type CrossCheck struct {
|
||||
With string `json:"with"` // 交叉验证的另一解析器
|
||||
DiffBlocks int `json:"diffBlocks"`
|
||||
Verdict string `json:"verdict"` // agree | differ
|
||||
}
|
||||
|
||||
type DocBlockType string
|
||||
|
||||
const (
|
||||
BlockHeading DocBlockType = "heading"
|
||||
BlockPara DocBlockType = "para"
|
||||
BlockList DocBlockType = "list"
|
||||
BlockTable DocBlockType = "table"
|
||||
BlockCode DocBlockType = "code"
|
||||
BlockImageRef DocBlockType = "image-ref"
|
||||
)
|
||||
|
||||
type DocBlock struct {
|
||||
Type DocBlockType `json:"type"`
|
||||
Level int `json:"level,omitempty"` // heading
|
||||
Text string `json:"text,omitempty"` // para/heading/list 项拼接/code
|
||||
Items []string `json:"items,omitempty"` // list
|
||||
Rows [][]string `json:"rows,omitempty"` // table(首行为表头)
|
||||
Ref string `json:"ref,omitempty"` // image-ref 的对象键
|
||||
Confidence float64 `json:"confidence,omitempty"`
|
||||
}
|
||||
|
||||
// PlainText 把文档压成纯文本(附件注入与全文索引用);表格按行拼接。
|
||||
func (d ParsedDocument) PlainText() string {
|
||||
out := make([]byte, 0, 1024)
|
||||
for _, b := range d.Blocks {
|
||||
switch b.Type {
|
||||
case BlockHeading:
|
||||
out = append(out, '\n')
|
||||
for i := 0; i < b.Level; i++ {
|
||||
out = append(out, '#')
|
||||
}
|
||||
out = append(out, ' ')
|
||||
out = append(out, b.Text...)
|
||||
out = append(out, '\n')
|
||||
case BlockPara, BlockCode:
|
||||
out = append(out, b.Text...)
|
||||
out = append(out, '\n')
|
||||
case BlockList:
|
||||
for _, it := range b.Items {
|
||||
out = append(out, "- "...)
|
||||
out = append(out, it...)
|
||||
out = append(out, '\n')
|
||||
}
|
||||
case BlockTable:
|
||||
for _, row := range b.Rows {
|
||||
for i, cell := range row {
|
||||
if i > 0 {
|
||||
out = append(out, '\t')
|
||||
}
|
||||
out = append(out, cell...)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
}
|
||||
}
|
||||
out = append(out, '\n')
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// ParserDesc 注册表条目(来源标记:builtin/plugin/...;一切可禁用)。
|
||||
type ParserDesc struct {
|
||||
ID string `json:"id"`
|
||||
Formats []string `json:"formats"` // 支持的扩展名
|
||||
Source SourceKind `json:"source"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// ---- 检索(4.12)----
|
||||
|
||||
// KnowledgeDoc 是检索语料的一条文档(来自 ingest 或插件知识库,带来源标记)。
|
||||
type KnowledgeDoc struct {
|
||||
ID ID `json:"id"`
|
||||
ProjectID ID `json:"projectId,omitempty"` // 空 = 插件级知识库
|
||||
Source string `json:"source"` // upload | artifact | plugin:<id>
|
||||
Title string `json:"title"`
|
||||
Ref string `json:"ref,omitempty"` // 工件/文件引用
|
||||
Chunk string `json:"chunk"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type RetrievalHit struct {
|
||||
Doc KnowledgeDoc `json:"doc"`
|
||||
Score float64 `json:"score"`
|
||||
Kind string `json:"kind"` // fulltext | vector | plugin
|
||||
}
|
||||
|
||||
type RetrievalQuery struct {
|
||||
ProjectID ID `json:"projectId,omitempty"`
|
||||
Text string `json:"text"`
|
||||
TopK int `json:"topK,omitempty"`
|
||||
Sources []string `json:"sources,omitempty"` // 过滤来源
|
||||
}
|
||||
|
||||
// ---- 记忆(4.13 契约;实现远期)----
|
||||
|
||||
type MemoryEntry struct {
|
||||
ID string `json:"id"`
|
||||
Scope string `json:"scope"` // user | project
|
||||
ProjectID ID `json:"projectId,omitempty"`
|
||||
Type string `json:"type"` // fact | preference | decision | episode | procedure
|
||||
Content string `json:"content"`
|
||||
Source string `json:"source"` // run:<id> | manual
|
||||
Confidence float64 `json:"confidence,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
LastAccessed time.Time `json:"lastAccessed"`
|
||||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
// ---- 上传文件 ----
|
||||
|
||||
type UploadInfo struct {
|
||||
ArtifactID ID `json:"artifactId"`
|
||||
Filename string `json:"filename"`
|
||||
MIME string `json:"mime,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
Parsed *ParsedDocument `json:"parsed,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// 事件信封:后端 → 前端的唯一增量通道(SSE),持久化后转发,Seq 在会话内单调。
|
||||
// 消费方对未知 Type 静默忽略(原则 6 前向兼容)。
|
||||
type StreamEvent struct {
|
||||
Seq int64 `json:"seq"`
|
||||
RunID ID `json:"runId,omitempty"`
|
||||
ConversationID ID `json:"conversationId"`
|
||||
TS string `json:"ts"` // RFC3339
|
||||
V int `json:"v"`
|
||||
Type StreamEventType `json:"type"`
|
||||
Payload json.RawMessage `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
type StreamEventType string
|
||||
|
||||
const (
|
||||
EvMessageDelta StreamEventType = "message.delta"
|
||||
EvThinkingDelta StreamEventType = "thinking.delta"
|
||||
EvToolCall StreamEventType = "tool.call"
|
||||
EvToolResult StreamEventType = "tool.result"
|
||||
EvRunPhase StreamEventType = "run.phase"
|
||||
EvHITLRequest StreamEventType = "hitl.request"
|
||||
EvHITLResolved StreamEventType = "hitl.resolved"
|
||||
EvPlanUpdate StreamEventType = "plan.update"
|
||||
EvTodoUpdate StreamEventType = "todo.update"
|
||||
EvAgentSpawn StreamEventType = "agent.spawn"
|
||||
EvAgentProgress StreamEventType = "agent.progress"
|
||||
EvAgentResult StreamEventType = "agent.result"
|
||||
EvArtifactCreated StreamEventType = "artifact.created"
|
||||
EvArtifactUpdated StreamEventType = "artifact.updated"
|
||||
EvRunCompleted StreamEventType = "run.completed"
|
||||
EvRunFailed StreamEventType = "run.failed"
|
||||
EvError StreamEventType = "error"
|
||||
)
|
||||
|
||||
// ProtocolVersion 是事件信封与 payload 词汇的当前版本。
|
||||
const ProtocolVersion = 1
|
||||
|
||||
// ---- 各事件 payload(新增字段只增不删;消费方忽略未知字段)----
|
||||
|
||||
// MessageDeltaPayload:正文增量。Index 标识目标 Part 序号(流式聚合用)。
|
||||
type MessageDeltaPayload struct {
|
||||
Index int `json:"index"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type ThinkingDeltaPayload struct {
|
||||
Index int `json:"index"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type ToolCallPayload struct {
|
||||
CallID string `json:"callId"`
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type ToolResultPayload struct {
|
||||
CallID string `json:"callId"`
|
||||
Status string `json:"status"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Truncated bool `json:"truncated,omitempty"`
|
||||
}
|
||||
|
||||
type RunPhasePayload struct {
|
||||
Phase RunPhase `json:"phase"`
|
||||
}
|
||||
|
||||
type HITLResolvedPayload struct {
|
||||
CardID ID `json:"cardId"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
Kind string `json:"kind"` // choice | free | timeout | cancelled
|
||||
}
|
||||
|
||||
type PlanUpdatePayload struct {
|
||||
Plan PlanSnapshot `json:"plan"`
|
||||
}
|
||||
|
||||
type TodoUpdatePayload struct {
|
||||
List TodoList `json:"list"`
|
||||
}
|
||||
|
||||
// AgentSpawnPayload:子代理派生;AgentRun 即子 Run(父子关系即任务树)。
|
||||
type AgentSpawnPayload struct {
|
||||
AgentRun ID `json:"agentRun"`
|
||||
Name string `json:"name"`
|
||||
Task string `json:"task"`
|
||||
}
|
||||
|
||||
type AgentProgressPayload struct {
|
||||
AgentRun ID `json:"agentRun"`
|
||||
Note string `json:"note,omitempty"`
|
||||
Phase RunPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
type AgentResultPayload struct {
|
||||
AgentRun ID `json:"agentRun"`
|
||||
Status RunStatus `json:"status"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
type ArtifactPayload struct {
|
||||
Artifact Artifact `json:"artifact"`
|
||||
}
|
||||
|
||||
type RunCompletedPayload struct {
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
|
||||
type RunFailedPayload struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ErrorPayload struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
RunID ID `json:"runId,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// HITL 卡片协议(4.5):choice | input | confirm | form 四种;
|
||||
// 发起方必须给 Choices 提供 description 与 impact——用户在决策点需要上下文。
|
||||
type HITLCard struct {
|
||||
ID ID `json:"id"`
|
||||
RunID ID `json:"runId"`
|
||||
ConversationID ID `json:"conversationId"`
|
||||
Question string `json:"question"`
|
||||
Kind HITLKind `json:"kind"`
|
||||
Choices []HITLChoice `json:"choices,omitempty"`
|
||||
AllowFreeInput bool `json:"allowFreeInput"`
|
||||
FreeInputHint string `json:"freeInputHint,omitempty"`
|
||||
TimeoutSec *int `json:"timeoutSec,omitempty"`
|
||||
Form []UINode `json:"form,omitempty"` // form kind 的字段(UI 词汇)
|
||||
CreatedAt string `json:"createdAt"`
|
||||
Resolved bool `json:"resolved"`
|
||||
}
|
||||
|
||||
type HITLKind string
|
||||
|
||||
const (
|
||||
HITLKindChoice HITLKind = "choice"
|
||||
HITLKindInput HITLKind = "input"
|
||||
HITLKindConfirm HITLKind = "confirm"
|
||||
HITLKindForm HITLKind = "form"
|
||||
)
|
||||
|
||||
type HITLChoice struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description"`
|
||||
Impact string `json:"impact"` // 该选择可能造成的影响(必填)
|
||||
Recommended bool `json:"recommended,omitempty"`
|
||||
}
|
||||
|
||||
// HITLResponse 是用户对卡片的响应;自由输入时 ChoiceID 为空。
|
||||
type HITLResponse struct {
|
||||
CardID ID `json:"cardId"`
|
||||
ChoiceID string `json:"choiceId,omitempty"`
|
||||
FreeText string `json:"freeText,omitempty"`
|
||||
FormData json.RawMessage `json:"formData,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// Package core 定义 Agent-System 的核心契约:领域模型、流式事件、HITL 卡片、
|
||||
// 插件 API、设置与 UI 词汇。本仓只含契约与少量纯函数,不含实现、不含 I/O。
|
||||
//
|
||||
// JSON 标签(camelCase)是前后端与插件共同的线格式契约;TS 类型镜像见 ts/types.ts,
|
||||
// 黄金样本见 testdata/golden/(conformance 测试保证 Go 序列化与样本一致)。
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
// ---- 标识 ----
|
||||
|
||||
// ID 形态为 <kind>_<hex12>;生成由后端负责,契约只约束非空与唯一。
|
||||
type ID = string
|
||||
|
||||
// ---- 项目与会话 ----
|
||||
|
||||
type Project struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Settings map[string]any `json:"settings,omitempty"` // 项目层设置覆盖(4.9 覆盖链)
|
||||
}
|
||||
|
||||
type Conversation struct {
|
||||
ID ID `json:"id"`
|
||||
ProjectID ID `json:"projectId"`
|
||||
Title string `json:"title"`
|
||||
ProfileID ID `json:"profileId,omitempty"` // 空为默认画像
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// ---- 消息与部件 ----
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleUser Role = "user"
|
||||
RoleAssistant Role = "assistant"
|
||||
RoleSystem Role = "system"
|
||||
)
|
||||
|
||||
// PartType 区分消息成分;前端按类型查渲染器,未知类型静默忽略(原则 6)。
|
||||
type PartType string
|
||||
|
||||
const (
|
||||
PartText PartType = "text"
|
||||
PartThinking PartType = "thinking"
|
||||
PartToolCall PartType = "tool_call"
|
||||
PartToolResult PartType = "tool_result"
|
||||
PartArtifactRef PartType = "artifact_ref"
|
||||
PartHITL PartType = "hitl"
|
||||
PartPlan PartType = "plan"
|
||||
PartError PartType = "error"
|
||||
PartCard PartType = "card" // 插件自定义卡片(Data 为 UI schema)
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ID ID `json:"id"`
|
||||
ConversationID ID `json:"conversationId"`
|
||||
RunID ID `json:"runId,omitempty"`
|
||||
Role Role `json:"role"`
|
||||
Parts []Part `json:"parts"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// Part 是判别联合:Type 决定哪些字段有意义;未用字段为零值。
|
||||
type Part struct {
|
||||
Type PartType `json:"type"`
|
||||
|
||||
Text string `json:"text,omitempty"` // text / thinking(流式增量聚合后)
|
||||
|
||||
ToolCall *ToolCallPart `json:"toolCall,omitempty"`
|
||||
ToolResult *ToolResultPart `json:"toolResult,omitempty"`
|
||||
Artifact *ArtifactRef `json:"artifact,omitempty"`
|
||||
Hitl *HITLSummary `json:"hitl,omitempty"`
|
||||
Plan *PlanSnapshot `json:"plan,omitempty"`
|
||||
Card *CardPart `json:"card,omitempty"`
|
||||
Err *ErrorPart `json:"error,omitempty"`
|
||||
|
||||
// 过程标记:true 的部件在 run 终态后由前端折叠为摘要行(4.2)。
|
||||
Process bool `json:"process,omitempty"`
|
||||
}
|
||||
|
||||
type ToolCallPart struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments,omitempty"` // 原始 JSON
|
||||
Duration float64 `json:"durationMs,omitempty"`
|
||||
}
|
||||
|
||||
type ToolResultPart struct {
|
||||
CallID string `json:"callId"`
|
||||
Status string `json:"status"` // ok | error
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Truncated bool `json:"truncated,omitempty"` // 超长截断并转工件引用(4.10)
|
||||
}
|
||||
|
||||
type ArtifactRef struct {
|
||||
ArtifactID ID `json:"artifactId"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind,omitempty"` // document | image | data | ...
|
||||
}
|
||||
|
||||
type HITLSummary struct {
|
||||
CardID ID `json:"cardId"`
|
||||
Question string `json:"question"`
|
||||
Resolved bool `json:"resolved"`
|
||||
Answer string `json:"answer,omitempty"` // 选中项 label 或回填文本
|
||||
}
|
||||
|
||||
type ErrorPart struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// CardPart.Data 是 UI schema 节点(4.6 有限词汇)。
|
||||
type CardPart struct {
|
||||
Kind string `json:"kind"`
|
||||
Data []UINode `json:"data"`
|
||||
}
|
||||
|
||||
// ---- Run(一切运行皆 Run)----
|
||||
|
||||
type RunKind string
|
||||
|
||||
const (
|
||||
RunConversation RunKind = "conversation"
|
||||
RunSubagent RunKind = "subagent"
|
||||
RunTool RunKind = "tool"
|
||||
)
|
||||
|
||||
type RunStatus string
|
||||
|
||||
const (
|
||||
RunPending RunStatus = "pending"
|
||||
RunRunning RunStatus = "running"
|
||||
RunWaitingHITL RunStatus = "waiting_hitl"
|
||||
RunCompleted RunStatus = "completed"
|
||||
RunFailed RunStatus = "failed"
|
||||
RunCancelled RunStatus = "cancelled"
|
||||
RunInterrupted RunStatus = "interrupted"
|
||||
)
|
||||
|
||||
type RunPhase string
|
||||
|
||||
const (
|
||||
PhaseThinking RunPhase = "thinking"
|
||||
PhaseActing RunPhase = "acting"
|
||||
PhaseResponding RunPhase = "responding"
|
||||
PhaseDone RunPhase = "done"
|
||||
)
|
||||
|
||||
type Run struct {
|
||||
ID ID `json:"id"`
|
||||
ParentRunID ID `json:"parentRunId,omitempty"`
|
||||
ConversationID ID `json:"conversationId"`
|
||||
ProjectID ID `json:"projectId"`
|
||||
Kind RunKind `json:"kind"`
|
||||
ProfileID ID `json:"profileId,omitempty"`
|
||||
Status RunStatus `json:"status"`
|
||||
Phase RunPhase `json:"phase,omitempty"`
|
||||
AgentName string `json:"agentName,omitempty"` // subagent 显示名
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
EndedAt *time.Time `json:"endedAt,omitempty"`
|
||||
Usage *Usage `json:"usage,omitempty"`
|
||||
Error *ErrorPart `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"promptTokens"`
|
||||
CompletionTokens int `json:"completionTokens"`
|
||||
TotalTokens int `json:"totalTokens"`
|
||||
DurationMs float64 `json:"durationMs"`
|
||||
ToolCalls int `json:"toolCalls"`
|
||||
}
|
||||
|
||||
// ---- 工件 ----
|
||||
|
||||
type Artifact struct {
|
||||
ID ID `json:"id"`
|
||||
ProjectID ID `json:"projectId"`
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
MIME string `json:"mime,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
SourceRun ID `json:"sourceRun,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// ---- 计划与待办(右栏 Plan / Todos)----
|
||||
|
||||
type PlanSnapshot struct {
|
||||
PlanID ID `json:"planId"`
|
||||
Title string `json:"title"`
|
||||
Steps []PlanStep `json:"steps"`
|
||||
}
|
||||
|
||||
type PlanStep struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Status string `json:"status"` // pending | in_progress | done | skipped
|
||||
AgentRun ID `json:"agentRun,omitempty"`
|
||||
}
|
||||
|
||||
type TodoList struct {
|
||||
ConversationID ID `json:"conversationId"`
|
||||
Items []TodoItem `json:"items"`
|
||||
}
|
||||
|
||||
type TodoItem struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Status string `json:"status"` // pending | in_progress | done
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// 插件 manifest(4.6):插件 = manifest + 声明资产 + 可选代码。
|
||||
// 校验失败整批拒绝(fail-closed);卸载注销注册项,在途引用按定义缺失稳定报错。
|
||||
|
||||
const PluginAPIVersion = "1.0"
|
||||
|
||||
type PluginManifest struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Requires PluginRequires `json:"requires,omitempty"`
|
||||
Provides PluginProvides `json:"provides"`
|
||||
}
|
||||
|
||||
type PluginRequires struct {
|
||||
Network []string `json:"network,omitempty"` // 允许访问的域名(展示用,v1 自装自审)
|
||||
}
|
||||
|
||||
type PluginProvides struct {
|
||||
Tools []string `json:"tools,omitempty"` // tools/ 目录下的资产名
|
||||
Workflows []string `json:"workflows,omitempty"`
|
||||
Intents []string `json:"intents,omitempty"`
|
||||
Prompts []string `json:"prompts,omitempty"`
|
||||
Panels []string `json:"panels,omitempty"`
|
||||
Cards []string `json:"cards,omitempty"`
|
||||
Settings []string `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
// PluginPanel:右栏面板声明(UI schema 静态骨架 + 可选数据提供者)。
|
||||
type PluginPanel struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Body []UINode `json:"body"`
|
||||
// Tool 绑定:面板挂载时调用该工具刷新 Body 中的动态区(v1 简化为整体刷新)。
|
||||
RefreshTool string `json:"refreshTool,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowDef:工作流即数据(4.6)。Step 可产 HITL 卡片、调工具、产出工件。
|
||||
type WorkflowDef struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
IntentID string `json:"intentId,omitempty"` // 绑定意图(命中即启动)
|
||||
Steps []WorkflowStep `json:"steps"`
|
||||
}
|
||||
|
||||
type WorkflowStep struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Kind string `json:"kind"` // prompt | tool | hitl | artifact
|
||||
Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板
|
||||
Tool string `json:"tool,omitempty"` // tool: 工具名
|
||||
Args json.RawMessage `json:"args,omitempty"` // tool: 参数模板
|
||||
Hitl *HITLCardTemplate `json:"hitl,omitempty"` // hitl: 卡片模板
|
||||
Artifact string `json:"artifact,omitempty"` // artifact: 产出工件名模板
|
||||
}
|
||||
|
||||
type HITLCardTemplate struct {
|
||||
Question string `json:"question"`
|
||||
Kind HITLKind `json:"kind"`
|
||||
Choices []HITLChoice `json:"choices,omitempty"`
|
||||
AllowFreeInput bool `json:"allowFreeInput"`
|
||||
FreeInputHint string `json:"freeInputHint,omitempty"`
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// 智能体画像(4.7):核心定制机制,全部数据化、可导入导出。
|
||||
type AgentProfile struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Builtin bool `json:"builtin,omitempty"`
|
||||
Source string `json:"source,omitempty"` // builtin | plugin | user
|
||||
SystemPrompt string `json:"systemPrompt"`
|
||||
Model ModelRoute `json:"model"`
|
||||
Tools ToolPolicy `json:"tools"`
|
||||
Context ContextPolicy `json:"context"`
|
||||
HITL HITLPolicy `json:"hitl"`
|
||||
Budget RunBudget `json:"budget"`
|
||||
}
|
||||
|
||||
// ModelRoute 按用途选择模型(4.7 kind 路由):空串回落到 chat 默认。
|
||||
type ModelRoute struct {
|
||||
Chat string `json:"chat,omitempty"`
|
||||
Lightweight string `json:"lightweight,omitempty"`
|
||||
Embedding string `json:"embedding,omitempty"`
|
||||
Rerank string `json:"rerank,omitempty"`
|
||||
}
|
||||
|
||||
// ToolPolicy 三清单:allow/deny 决定可见工具,confirm 强制执行前 HITL 确认。
|
||||
// 匹配语义:精确名或前缀通配("http.*" 表示 http 前缀);deny 优先于 allow。
|
||||
type ToolPolicy struct {
|
||||
Allow []string `json:"allow"` // ["*"] 全部
|
||||
Deny []string `json:"deny,omitempty"`
|
||||
Confirm []string `json:"confirm,omitempty"`
|
||||
}
|
||||
|
||||
// ContextPolicy(4.10):ctxasm 的输入;逐版本增加策略项不动组装器接口。
|
||||
type ContextPolicy struct {
|
||||
MaxHistoryMessages int `json:"maxHistoryMessages"` // 0 = 默认 40
|
||||
SummarizeBeyond bool `json:"summarizeBeyond,omitempty"` // 远期;先存标志
|
||||
MemoryBudgetPct int `json:"memoryBudgetPct,omitempty"` // 注入槽份额上限
|
||||
KnowledgeBudgetPct int `json:"knowledgeBudgetPct,omitempty"`
|
||||
MaxToolResultChars int `json:"maxToolResultChars,omitempty"` // 0 = 默认 20000
|
||||
}
|
||||
|
||||
type HITLPolicy struct {
|
||||
AskOnAmbiguity bool `json:"askOnAmbiguity"`
|
||||
TimeoutSec *int `json:"timeoutSec,omitempty"`
|
||||
}
|
||||
|
||||
type RunBudget struct {
|
||||
MaxTokens *int `json:"maxTokens,omitempty"`
|
||||
MaxToolCalls *int `json:"maxToolCalls,omitempty"`
|
||||
MaxTurns *int `json:"maxTurns,omitempty"` // LLM 循环轮上限
|
||||
}
|
||||
|
||||
// ToolAllows 判定:deny 优先;allow 含 "*" 或精确/前缀命中。
|
||||
func (p ToolPolicy) ToolAllowed(name string) bool {
|
||||
if matchAny(p.Deny, name) {
|
||||
return false
|
||||
}
|
||||
if len(p.Allow) == 0 {
|
||||
return true
|
||||
}
|
||||
return matchAny(p.Allow, name)
|
||||
}
|
||||
|
||||
func (p ToolPolicy) NeedsConfirm(name string) bool {
|
||||
return matchAny(p.Confirm, name)
|
||||
}
|
||||
|
||||
// matchAny 语义:pattern 精确等于 name,或以 "xxx." 开头表示前缀族(http. → http.*)。
|
||||
func matchAny(patterns []string, name string) bool {
|
||||
for _, p := range patterns {
|
||||
if p == "*" || p == name {
|
||||
return true
|
||||
}
|
||||
if n := len(p); n > 1 && p[n-1] == '.' {
|
||||
if len(name) > n-1 && name[:n-1] == p[:n-1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---- 设置(4.9)----
|
||||
|
||||
type SettingType string
|
||||
|
||||
const (
|
||||
SettingString SettingType = "string"
|
||||
SettingNumber SettingType = "number"
|
||||
SettingBool SettingType = "boolean"
|
||||
SettingEnum SettingType = "enum"
|
||||
SettingSecret SettingType = "secret"
|
||||
)
|
||||
|
||||
type SettingEffect string
|
||||
|
||||
const (
|
||||
EffectHot SettingEffect = "hot" // 立即生效
|
||||
EffectNewRun SettingEffect = "new-run" // 仅影响新 run
|
||||
EffectRestart SettingEffect = "restart" // 需重启
|
||||
)
|
||||
|
||||
// SettingDesc 描述一个设置项;设置页按此渲染并标注生效时机(schema 驱动)。
|
||||
type SettingDesc struct {
|
||||
Key string `json:"key"`
|
||||
Group string `json:"group"` // 分区:models | profiles | tools | knowledge | ui | plugin:<id>
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Type SettingType `json:"type"`
|
||||
Default any `json:"default,omitempty"`
|
||||
Options []string `json:"options,omitempty"` // enum
|
||||
Effect SettingEffect `json:"effect"`
|
||||
Secret bool `json:"secret,omitempty"`
|
||||
}
|
||||
|
||||
// EffectiveSettings 是覆盖链(system → user → project → conversation)深合并结果。
|
||||
type EffectiveSettings map[string]any
|
||||
|
||||
// MergeSettings 深合并:后者覆盖前者;map 递归合并,其它类型直接覆盖(4.9 数组显式覆盖)。
|
||||
func MergeSettings(base, override map[string]any) map[string]any {
|
||||
out := make(map[string]any, len(base)+len(override))
|
||||
for k, v := range base {
|
||||
out[k] = v
|
||||
}
|
||||
for k, v := range override {
|
||||
if bm, ok := base[k].(map[string]any); ok {
|
||||
if vm, ok2 := v.(map[string]any); ok2 {
|
||||
out[k] = MergeSettings(bm, vm)
|
||||
continue
|
||||
}
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// GetString/GetBool/GetNumber 读取点路径("models.chat");缺省返回 def。
|
||||
func (s EffectiveSettings) GetString(path, def string) string {
|
||||
v, ok := lookupPath(s, path)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
if str, ok2 := v.(string); ok2 {
|
||||
return str
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (s EffectiveSettings) GetBool(path string, def bool) bool {
|
||||
v, ok := lookupPath(s, path)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
if b, ok2 := v.(bool); ok2 {
|
||||
return b
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (s EffectiveSettings) GetNumber(path string, def float64) float64 {
|
||||
v, ok := lookupPath(s, path)
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
if f, ok2 := v.(float64); ok2 {
|
||||
return f
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func lookupPath(m map[string]any, path string) (any, bool) {
|
||||
parts := splitPath(path)
|
||||
var cur any = m
|
||||
for _, p := range parts {
|
||||
cm, ok := cur.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
cur, ok = cm[p]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return cur, true
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
var out []string
|
||||
cur := ""
|
||||
for i := 0; i < len(path); i++ {
|
||||
switch path[i] {
|
||||
case '.':
|
||||
out = append(out, cur)
|
||||
cur = ""
|
||||
default:
|
||||
cur += string(path[i])
|
||||
}
|
||||
}
|
||||
return append(out, cur)
|
||||
}
|
||||
|
||||
var _ = json.Marshal // 保持 encoding/json 引用(RawMessage 一致性)
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "art_abc123def456",
|
||||
"projectId": "prj_abc123def456",
|
||||
"name": "报告.md",
|
||||
"kind": "document",
|
||||
"mime": "text/markdown",
|
||||
"size": 128,
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"updatedAt": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "cnv_abc123def456",
|
||||
"projectId": "prj_abc123def456",
|
||||
"title": "新会话",
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"updatedAt": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"seq": 43,
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"ts": "2026-09-29T10:00:01Z",
|
||||
"v": 1,
|
||||
"type": "agent.spawn",
|
||||
"payload": {
|
||||
"agentRun": "run_sub1",
|
||||
"name": "检索员",
|
||||
"task": "查法规"
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"seq": 42,
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"ts": "2026-09-29T10:00:00Z",
|
||||
"v": 1,
|
||||
"type": "message.delta",
|
||||
"payload": {
|
||||
"index": 0,
|
||||
"text": "你"
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"seq": 44,
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"ts": "2026-09-29T10:00:02Z",
|
||||
"v": 1,
|
||||
"type": "run.completed",
|
||||
"payload": {
|
||||
"usage": {
|
||||
"promptTokens": 100,
|
||||
"completionTokens": 20,
|
||||
"totalTokens": 120,
|
||||
"durationMs": 1500,
|
||||
"toolCalls": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "hitl_abc123def456",
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"question": "采用哪种输出格式?",
|
||||
"kind": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"id": "md",
|
||||
"label": "Markdown 文档",
|
||||
"description": "可版本管理的轻量文档",
|
||||
"impact": "写入 artifacts,可在右栏查看",
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"id": "docx",
|
||||
"label": "Word 文档",
|
||||
"description": "正式交付格式",
|
||||
"impact": "生成 docx 工件"
|
||||
}
|
||||
],
|
||||
"allowFreeInput": true,
|
||||
"freeInputHint": "也可以直接输入其他格式要求",
|
||||
"createdAt": "",
|
||||
"resolved": false
|
||||
}
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"id": "msg_abc123def456",
|
||||
"conversationId": "cnv_abc123def456",
|
||||
"runId": "run_abc123def456",
|
||||
"role": "assistant",
|
||||
"parts": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"text": "考虑先检索…",
|
||||
"process": true
|
||||
},
|
||||
{
|
||||
"type": "tool_call",
|
||||
"toolCall": {
|
||||
"id": "call_1",
|
||||
"name": "history_search",
|
||||
"arguments": "{\"q\":\"部署\"}",
|
||||
"durationMs": 12.5
|
||||
},
|
||||
"process": true
|
||||
},
|
||||
{
|
||||
"type": "tool_result",
|
||||
"toolResult": {
|
||||
"callId": "call_1",
|
||||
"status": "ok",
|
||||
"summary": "3 条命中"
|
||||
},
|
||||
"process": true
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "根据历史记录……"
|
||||
},
|
||||
{
|
||||
"type": "artifact_ref",
|
||||
"artifact": {
|
||||
"artifactId": "art_abc123def456",
|
||||
"name": "报告.md",
|
||||
"kind": "document"
|
||||
}
|
||||
}
|
||||
],
|
||||
"createdAt": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"meta": {
|
||||
"filename": "报告.docx",
|
||||
"mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"parser": "builtin:docx",
|
||||
"confidence": 0.92,
|
||||
"parsedAt": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"blocks": [
|
||||
{
|
||||
"type": "heading",
|
||||
"level": 1,
|
||||
"text": "概述"
|
||||
},
|
||||
{
|
||||
"type": "para",
|
||||
"text": "本文档说明……"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"items": [
|
||||
"要点一",
|
||||
"要点二"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"rows": [
|
||||
[
|
||||
"项",
|
||||
"值"
|
||||
],
|
||||
[
|
||||
"版本",
|
||||
"1.0"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "example",
|
||||
"name": "示例插件",
|
||||
"version": "0.1.0",
|
||||
"apiVersion": "1.0",
|
||||
"requires": {
|
||||
"network": [
|
||||
"api.example.com"
|
||||
]
|
||||
},
|
||||
"provides": {
|
||||
"tools": [
|
||||
"http_echo"
|
||||
],
|
||||
"intents": [
|
||||
"demo"
|
||||
],
|
||||
"panels": [
|
||||
"demo"
|
||||
],
|
||||
"cards": [
|
||||
"note"
|
||||
],
|
||||
"settings": [
|
||||
"greeting"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "default",
|
||||
"name": "通用助手",
|
||||
"builtin": true,
|
||||
"source": "builtin",
|
||||
"systemPrompt": "你是一个通用智能体助手。",
|
||||
"model": {
|
||||
"chat": "gpt-default",
|
||||
"lightweight": "gpt-lite"
|
||||
},
|
||||
"tools": {
|
||||
"allow": [
|
||||
"*"
|
||||
],
|
||||
"deny": [
|
||||
"http.admin."
|
||||
],
|
||||
"confirm": [
|
||||
"http."
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"maxHistoryMessages": 40,
|
||||
"maxToolResultChars": 20000
|
||||
},
|
||||
"hitl": {
|
||||
"askOnAmbiguity": true
|
||||
},
|
||||
"budget": {
|
||||
"maxToolCalls": 40
|
||||
}
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "prj_abc123def456",
|
||||
"name": "示例项目",
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"updatedAt": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "run_abc123def456",
|
||||
"conversationId": "cnv_abc123def456",
|
||||
"projectId": "prj_abc123def456",
|
||||
"kind": "conversation",
|
||||
"profileId": "default",
|
||||
"status": "completed",
|
||||
"phase": "done",
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"usage": {
|
||||
"promptTokens": 1200,
|
||||
"completionTokens": 300,
|
||||
"totalTokens": 1500,
|
||||
"durationMs": 4200.5,
|
||||
"toolCalls": 2
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "http_translate",
|
||||
"description": "调用翻译 API",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"source": "user",
|
||||
"http": {
|
||||
"method": "POST",
|
||||
"url": "https://api.example.com/translate",
|
||||
"bodyTemplate": "{\"q\":\"{{param.text}}\"}",
|
||||
"timeoutMs": 10000,
|
||||
"resultPath": "data.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// 工具契约(4.8):内置、声明式 HTTP、插件工具统一走 ToolProvider 形态。
|
||||
// Parameters 是 JSON Schema(draft-07 子集即可),调用参数按其校验。
|
||||
|
||||
type SourceKind string
|
||||
|
||||
const (
|
||||
SourceBuiltin SourceKind = "builtin"
|
||||
SourcePlugin SourceKind = "plugin"
|
||||
SourceUser SourceKind = "user"
|
||||
)
|
||||
|
||||
type ToolDesc struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Parameters json.RawMessage `json:"parameters,omitempty"`
|
||||
Source SourceKind `json:"source"`
|
||||
PluginID string `json:"pluginId,omitempty"`
|
||||
HTTP *HTTPToolSpec `json:"http,omitempty"` // 声明式 HTTP 工具(无代码)
|
||||
}
|
||||
|
||||
// HTTPToolSpec:无代码自定义工具(4.8)。模板变量 {{param.x}} 从调用参数注入。
|
||||
type HTTPToolSpec struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
BodyTemplate string `json:"bodyTemplate,omitempty"` // JSON 模板
|
||||
AuthRef string `json:"authRef,omitempty"` // 设置中的密钥引用(settings key)
|
||||
TimeoutMs int `json:"timeoutMs,omitempty"`
|
||||
// ResultPath 从响应 JSON 中裁剪(jq 点路径);空取整个响应。
|
||||
ResultPath string `json:"resultPath,omitempty"`
|
||||
}
|
||||
|
||||
type ToolCallRequest struct {
|
||||
CallID string `json:"callId"`
|
||||
Name string `json:"name"`
|
||||
Arguments json.RawMessage `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type ToolCallResult struct {
|
||||
CallID string `json:"callId"`
|
||||
Status string `json:"status"` // ok | error
|
||||
Output string `json:"output,omitempty"`
|
||||
Truncated bool `json:"truncated,omitempty"`
|
||||
Artifact *Artifact `json:"artifact,omitempty"` // 超长转工件
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
// Agent-System-Core 契约的 TypeScript 镜像。
|
||||
// 线格式契约以 Go 类型(camelCase JSON 标签)与 testdata/golden/*.json 黄金样本为准;
|
||||
// 本文件手工镜像同一契约,前端按未知 type/字段静默忽略(前向兼容原则)。
|
||||
|
||||
export type ID = string;
|
||||
|
||||
// ---- 领域模型 ----
|
||||
|
||||
export interface Project {
|
||||
id: ID; name: string; createdAt: string; updatedAt: string;
|
||||
settings?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: ID; projectId: ID; title: string; profileId?: ID;
|
||||
createdAt: string; updatedAt: string;
|
||||
}
|
||||
|
||||
export type Role = 'user' | 'assistant' | 'system';
|
||||
|
||||
export type PartType =
|
||||
| 'text' | 'thinking' | 'tool_call' | 'tool_result' | 'artifact_ref'
|
||||
| 'hitl' | 'plan' | 'error' | 'card';
|
||||
|
||||
export interface Part {
|
||||
type: PartType;
|
||||
text?: string;
|
||||
toolCall?: { id: string; name: string; arguments?: string; durationMs?: number };
|
||||
toolResult?: { callId: string; status: 'ok' | 'error'; summary?: string; truncated?: boolean };
|
||||
artifact?: { artifactId: ID; name: string; kind?: string };
|
||||
hitl?: { cardId: ID; question: string; resolved: boolean; answer?: string };
|
||||
plan?: PlanSnapshot;
|
||||
card?: { kind: string; data: UINode[] };
|
||||
error?: { code: string; message: string };
|
||||
process?: boolean; // run 终态后折叠为摘要行
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: ID; conversationId: ID; runId?: ID; role: Role;
|
||||
parts: Part[]; createdAt: string;
|
||||
}
|
||||
|
||||
export type RunKind = 'conversation' | 'subagent' | 'tool';
|
||||
export type RunStatus =
|
||||
| 'pending' | 'running' | 'waiting_hitl' | 'completed'
|
||||
| 'failed' | 'cancelled' | 'interrupted';
|
||||
export type RunPhase = 'thinking' | 'acting' | 'responding' | 'done';
|
||||
|
||||
export interface Usage {
|
||||
promptTokens: number; completionTokens: number; totalTokens: number;
|
||||
durationMs: number; toolCalls: number;
|
||||
}
|
||||
|
||||
export interface Run {
|
||||
id: ID; parentRunId?: ID; conversationId: ID; projectId: ID;
|
||||
kind: RunKind; profileId?: ID; status: RunStatus; phase?: RunPhase;
|
||||
agentName?: string; createdAt: string; endedAt?: string;
|
||||
usage?: Usage; error?: { code: string; message: string };
|
||||
}
|
||||
|
||||
export interface Artifact {
|
||||
id: ID; projectId: ID; name: string; kind: string; mime?: string;
|
||||
size: number; sourceRun?: ID; createdAt: string; updatedAt: string;
|
||||
}
|
||||
|
||||
export interface PlanSnapshot {
|
||||
planId: ID; title: string;
|
||||
steps: { id: string; text: string; status: 'pending' | 'in_progress' | 'done' | 'skipped'; agentRun?: ID }[];
|
||||
}
|
||||
|
||||
export interface TodoList {
|
||||
conversationId: ID;
|
||||
items: { id: string; text: string; status: 'pending' | 'in_progress' | 'done' }[];
|
||||
}
|
||||
|
||||
// ---- 流式事件 ----
|
||||
|
||||
export type StreamEventType =
|
||||
| 'message.delta' | 'thinking.delta' | 'tool.call' | 'tool.result'
|
||||
| 'run.phase' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update'
|
||||
| 'agent.spawn' | 'agent.progress' | 'agent.result'
|
||||
| 'artifact.created' | 'artifact.updated'
|
||||
| 'run.completed' | 'run.failed' | 'error';
|
||||
|
||||
export interface StreamEvent<P = unknown> {
|
||||
seq: number; runId?: ID; conversationId: ID; ts: string; v: number;
|
||||
type: StreamEventType; payload?: P;
|
||||
}
|
||||
|
||||
// payload 形状(按 type 判别;未知字段忽略)
|
||||
export interface MessageDeltaPayload { index: number; text: string }
|
||||
export interface AgentSpawnPayload { agentRun: ID; name: string; task: string }
|
||||
export interface AgentProgressPayload { agentRun: ID; note?: string; phase?: RunPhase }
|
||||
export interface AgentResultPayload { agentRun: ID; status: RunStatus; summary?: string }
|
||||
export interface HITLResolvedPayload { cardId: ID; answer?: string; kind: 'choice' | 'free' | 'timeout' | 'cancelled' }
|
||||
export interface RunCompletedPayload { usage: Usage }
|
||||
export interface RunFailedPayload { code: string; message: string }
|
||||
|
||||
// ---- HITL ----
|
||||
|
||||
export type HITLKind = 'choice' | 'input' | 'confirm' | 'form';
|
||||
|
||||
export interface HITLChoice {
|
||||
id: string; label: string; description: string; impact: string; recommended?: boolean;
|
||||
}
|
||||
|
||||
export interface HITLCard {
|
||||
id: ID; runId: ID; conversationId: ID; question: string; kind: HITLKind;
|
||||
choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string;
|
||||
timeoutSec?: number | null; form?: UINode[]; createdAt: string; resolved: boolean;
|
||||
}
|
||||
|
||||
export interface HITLResponse {
|
||||
cardId: ID; choiceId?: string; freeText?: string; formData?: unknown;
|
||||
}
|
||||
|
||||
// ---- UI 词汇 ----
|
||||
|
||||
export type UINodeType =
|
||||
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite';
|
||||
|
||||
export interface UINode {
|
||||
type: UINodeType;
|
||||
title?: string;
|
||||
items?: string[];
|
||||
kv?: [string, string][];
|
||||
fields?: UIField[];
|
||||
markdown?: string;
|
||||
docId?: ID;
|
||||
actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger' }[];
|
||||
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
|
||||
children?: UINode[];
|
||||
}
|
||||
|
||||
export interface UIField {
|
||||
key: string; label: string;
|
||||
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select';
|
||||
options?: string[]; required?: boolean; placeholder?: string; help?: string; default?: string;
|
||||
}
|
||||
|
||||
// ---- 画像 / 设置 ----
|
||||
|
||||
export interface AgentProfile {
|
||||
id: string; name: string; description?: string; builtin?: boolean; source?: string;
|
||||
systemPrompt: string;
|
||||
model: { chat?: string; lightweight?: string; embedding?: string; rerank?: string };
|
||||
tools: { allow: string[]; deny?: string[]; confirm?: string[] };
|
||||
context: {
|
||||
maxHistoryMessages: number; summarizeBeyond?: boolean;
|
||||
memoryBudgetPct?: number; knowledgeBudgetPct?: number; maxToolResultChars?: number;
|
||||
};
|
||||
hitl: { askOnAmbiguity: boolean; timeoutSec?: number | null };
|
||||
budget: { maxTokens?: number | null; maxToolCalls?: number | null; maxTurns?: number | null };
|
||||
}
|
||||
|
||||
export interface SettingDesc {
|
||||
key: string; group: string; label: string; description?: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'enum' | 'secret';
|
||||
default?: unknown; options?: string[];
|
||||
effect: 'hot' | 'new-run' | 'restart';
|
||||
secret?: boolean;
|
||||
}
|
||||
|
||||
// ---- 插件 ----
|
||||
|
||||
export interface PluginManifest {
|
||||
id: string; name: string; version: string; apiVersion: string;
|
||||
requires?: { network?: string[] };
|
||||
provides: {
|
||||
tools?: string[]; workflows?: string[]; intents?: string[];
|
||||
prompts?: string[]; panels?: string[]; cards?: string[]; settings?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginPanel {
|
||||
id: string; title: string; icon?: string; body: UINode[]; refreshTool?: string;
|
||||
}
|
||||
|
||||
// ---- 摄取 / 检索 ----
|
||||
|
||||
export interface ParsedDocument {
|
||||
meta: {
|
||||
filename: string; mime?: string; parser: string; confidence?: number;
|
||||
crossCheck?: { with: string; diffBlocks: number; verdict: 'agree' | 'differ' };
|
||||
parsedAt: string;
|
||||
};
|
||||
blocks: {
|
||||
type: 'heading' | 'para' | 'list' | 'table' | 'code' | 'image-ref';
|
||||
level?: number; text?: string; items?: string[];
|
||||
rows?: string[][]; ref?: string; confidence?: number;
|
||||
}[];
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
export interface RetrievalHit {
|
||||
doc: { id: ID; projectId?: ID; source: string; title: string; ref?: string; chunk: string; createdAt: string };
|
||||
score: number; kind: 'fulltext' | 'vector' | 'plugin';
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package core
|
||||
|
||||
// UI schema 词汇(4.6 原则 3):设置界面、插件面板、HITL 表单、插件卡片共用
|
||||
// 这套有限词汇,由前端通用渲染器渲染。插件不注入 React 组件;表达不了的高级
|
||||
// 交互走 Core 契约扩展流程新增词汇,而不是开洞。
|
||||
|
||||
type UINodeType string
|
||||
|
||||
const (
|
||||
UISection UINodeType = "section" // 标题 + 子节点
|
||||
UIList UINodeType = "list" // 字符串列表
|
||||
UIKV UINodeType = "kv" // 键值对列表
|
||||
UIForm UINodeType = "form" // 字段表单(HITL form / 设置分区复用)
|
||||
UIMarkdown UINodeType = "markdown"
|
||||
UIDocRef UINodeType = "doc-ref" // 工件引用(右栏查看器打开)
|
||||
UIActions UINodeType = "actions" // 可点动作(发命令/发消息)
|
||||
UIChartLite UINodeType = "chart-lite"
|
||||
)
|
||||
|
||||
type UINode struct {
|
||||
Type UINodeType `json:"type"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Items []string `json:"items,omitempty"` // list
|
||||
KV [][2]string `json:"kv,omitempty"` // kv
|
||||
Fields []UIField `json:"fields,omitempty"` // form
|
||||
Markdown string `json:"markdown,omitempty"`
|
||||
DocID ID `json:"docId,omitempty"` // doc-ref
|
||||
Actions []UIAction `json:"actions,omitempty"`
|
||||
Chart *UIChart `json:"chart,omitempty"`
|
||||
Children []UINode `json:"children,omitempty"` // section
|
||||
}
|
||||
|
||||
type UIField struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"` // text | textarea | number | boolean | select
|
||||
Options []string `json:"options,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Help string `json:"help,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type UIAction struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Style string `json:"style,omitempty"` // primary | default | danger
|
||||
}
|
||||
|
||||
type UIChart struct {
|
||||
Kind string `json:"kind"` // bar | line
|
||||
Labels []string `json:"labels"`
|
||||
Series []float64 `json:"series"`
|
||||
}
|
||||
Reference in New Issue
Block a user