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
|
# 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