agent-trellis 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +12 -1
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +87 -2
- package/dist/commands/evolve.d.ts +43 -0
- package/dist/commands/evolve.js +479 -0
- package/dist/commands/mcpGateway.d.ts +28 -0
- package/dist/commands/mcpGateway.js +40 -6
- package/dist/evolve/agentView.d.ts +32 -0
- package/dist/evolve/agentView.js +34 -0
- package/dist/evolve/apply.d.ts +71 -0
- package/dist/evolve/apply.js +162 -0
- package/dist/evolve/archive.d.ts +23 -0
- package/dist/evolve/archive.js +58 -0
- package/dist/evolve/erase.d.ts +17 -0
- package/dist/evolve/erase.js +65 -0
- package/dist/evolve/extension.d.ts +37 -0
- package/dist/evolve/extension.js +63 -0
- package/dist/evolve/gate.d.ts +95 -0
- package/dist/evolve/gate.js +295 -0
- package/dist/evolve/importHistory.d.ts +91 -0
- package/dist/evolve/importHistory.js +239 -0
- package/dist/evolve/jobMain.d.ts +22 -0
- package/dist/evolve/jobMain.js +52 -0
- package/dist/evolve/jobRun.d.ts +94 -0
- package/dist/evolve/jobRun.js +273 -0
- package/dist/evolve/jobShell.d.ts +46 -0
- package/dist/evolve/jobShell.js +75 -0
- package/dist/evolve/l2.d.ts +93 -0
- package/dist/evolve/l2.js +81 -0
- package/dist/evolve/lesson.d.ts +57 -0
- package/dist/evolve/lesson.js +97 -0
- package/dist/evolve/pending.d.ts +69 -0
- package/dist/evolve/pending.js +116 -0
- package/dist/evolve/probeWorkspace.d.ts +57 -0
- package/dist/evolve/probeWorkspace.js +69 -0
- package/dist/evolve/propose.d.ts +73 -0
- package/dist/evolve/propose.js +136 -0
- package/dist/evolve/recorder.d.ts +68 -0
- package/dist/evolve/recorder.js +169 -0
- package/dist/evolve/report.d.ts +26 -0
- package/dist/evolve/report.js +37 -0
- package/dist/evolve/reportView.d.ts +96 -0
- package/dist/evolve/reportView.js +311 -0
- package/dist/evolve/risk.d.ts +106 -0
- package/dist/evolve/risk.js +224 -0
- package/dist/evolve/sources.d.ts +19 -0
- package/dist/evolve/sources.js +29 -0
- package/dist/evolve/stateDir.d.ts +21 -0
- package/dist/evolve/stateDir.js +56 -0
- package/dist/evolve/surface.d.ts +34 -0
- package/dist/evolve/surface.js +72 -0
- package/dist/evolve/trajectory.d.ts +25 -0
- package/dist/evolve/trajectory.js +88 -0
- package/dist/evolve/treeToCanonical.d.ts +28 -0
- package/dist/evolve/treeToCanonical.js +79 -0
- package/dist/evolve/trellisSubstrate.d.ts +65 -0
- package/dist/evolve/trellisSubstrate.js +154 -0
- package/dist/evolve/trigger.d.ts +177 -0
- package/dist/evolve/trigger.js +400 -0
- package/dist/evolve/types.d.ts +126 -0
- package/dist/evolve/types.js +15 -0
- package/dist/evolve/validate.d.ts +139 -0
- package/dist/evolve/validate.js +315 -0
- package/dist/host/checks.d.ts +57 -0
- package/dist/host/checks.js +126 -0
- package/dist/host/delivery.d.ts +42 -0
- package/dist/host/delivery.js +59 -0
- package/dist/host/edits.d.ts +87 -0
- package/dist/host/edits.js +102 -0
- package/dist/host/hostEvents.d.ts +74 -0
- package/dist/host/hostEvents.js +45 -0
- package/dist/host/index.d.ts +22 -0
- package/dist/host/index.js +17 -0
- package/dist/host/levels.d.ts +106 -0
- package/dist/host/levels.js +206 -0
- package/dist/host/staging.d.ts +56 -0
- package/dist/host/staging.js +106 -0
- package/dist/host/surface.d.ts +36 -0
- package/dist/host/surface.js +20 -0
- package/dist/lib/backup.js +12 -3
- package/dist/lib/fsAtomic.d.ts +7 -0
- package/dist/lib/fsAtomic.js +14 -0
- package/dist/lib/hostEventBus.d.ts +47 -0
- package/dist/lib/hostEventBus.js +94 -0
- package/dist/lib/hostExtensions.d.ts +75 -0
- package/dist/lib/hostExtensions.js +196 -0
- package/dist/lib/mcpRuntime.d.ts +12 -1
- package/dist/lib/mcpRuntime.js +34 -2
- package/dist/lib/mcpToolRegistry.d.ts +18 -2
- package/dist/lib/mcpToolRegistry.js +29 -9
- package/dist/pi-bridge/bundle.js +16 -6
- package/docs/magpie-gateway-research.md +422 -0
- package/docs/roadmap.md +22 -12
- package/docs/runtime-infra.md +41 -22
- package/docs/self-evolution.md +138 -44
- package/package.json +7 -2
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# Magpie 网关核心实现调研
|
|
2
|
+
|
|
3
|
+
> **调研快照,2026-10-23。** 对象:开源本地 LLM API 网关
|
|
4
|
+
> [yetone/magpie](https://github.com/yetone/magpie)(Go,MIT)。源码以研究副本形式
|
|
5
|
+
> 浅克隆在 `~/www/morphix-research/sources/magpie`(HEAD 时点 v0.1.161 一线,
|
|
6
|
+
> 197M;Air 生产装的是 v0.1.1332 二进制)。行号以该克隆为准,是快照不是稳定引用。
|
|
7
|
+
>
|
|
8
|
+
> 属于 [runtime-infra.md](runtime-infra.md) 议程第 1 项。目的:吃透**已在用的**
|
|
9
|
+
> 网关的内部机制,服务运维、排障与升级判断。方向上已决定不自研、直接采用
|
|
10
|
+
> 开源 Magpie(2026-10-23,用户裁决);本文不是自研参照。
|
|
11
|
+
|
|
12
|
+
## 0. 总览(一分钟地图)
|
|
13
|
+
|
|
14
|
+
- 入口 `internal/gateway/gateway.go`:`handle()`(:1440)识别入站协议 → `serve()`
|
|
15
|
+
(:1606)构建候选 → `passthrough()`(:4215,同协议直通)或 `translate()`
|
|
16
|
+
(:5853)/`streamTranslated()`(`continuation.go:369`,跨协议翻译)。
|
|
17
|
+
- 四种 wire protocol:`provider.Chat`(OpenAI Chat)/`provider.Responses`/
|
|
18
|
+
`provider.Anthropic`/`provider.Gemini`;翻译枢纽是 `internal/gateway/ir.go` 的
|
|
19
|
+
IR(`Request/Message/Part/Event/Usage`)。
|
|
20
|
+
- 路由与 failover:`internal/gateway/fallback.go`(candidate 展开、plan、
|
|
21
|
+
holdWriter)+ `internal/gateway/routing.go`(失败分类、rest)+ `internal/routes`
|
|
22
|
+
(纯排序策略)。
|
|
23
|
+
- 外设:`internal/netproxy`(出站代理决策)、`internal/netguard`(SSRF 地址判定)、
|
|
24
|
+
`internal/budget`+`internal/gwbudget`(网关 key 限额)、`internal/gwmw`+
|
|
25
|
+
`internal/middleware`(Go/JS 中间件)、`internal/gwtool`(工具 gate)、
|
|
26
|
+
`internal/catalog`(models.dev 模型目录)。
|
|
27
|
+
|
|
28
|
+
## 1. Provider 路由与适配
|
|
29
|
+
|
|
30
|
+
### 核心数据流
|
|
31
|
+
|
|
32
|
+
请求 → 按 path 识别入站协议(`protoOfPath`,`key_limit.go:99`;handle 里也有按
|
|
33
|
+
body 特征的识别)→ `provider.Resolve` 解析出目标 `Provider` → `fallback.go` 把
|
|
34
|
+
该 provider 的全部 key/account(及其 Fallback 链、routing group)摊平成同构
|
|
35
|
+
`candidate` 列表 → `routes.Strategy` 只做排序 → serve 循环按序尝试。
|
|
36
|
+
|
|
37
|
+
### 关键文件与机制
|
|
38
|
+
|
|
39
|
+
- `internal/provider/provider.go:35-429` — `Provider` 结构体同时承载两类上游:
|
|
40
|
+
API key(`Key`/`Keys []KeyAccount`/`KeyProtocol`)与订阅账号
|
|
41
|
+
(`Account *Account`,json:"-" 不落盘);协议端点
|
|
42
|
+
`Chat/Responses/Anthropic/Gemini/Decide` 各自 Base URL;`Fallback []string`
|
|
43
|
+
(provider/model 链);`Routing`(""/smart/order/rotate/usage/pace/weight);
|
|
44
|
+
`Sink`(429 限流沉底);`Affinity`;并发/限流
|
|
45
|
+
(`MaxConcurrency/AccountConcurrency/QueueLimit/QueueWait/MaxRPM`);
|
|
46
|
+
`QuotaRest/QuotaResetAt/RestCap`;per-account 覆盖
|
|
47
|
+
(`AccountProxies/AccountModels/AccountCaps/AccountWindowCaps/AccountNames`)。
|
|
48
|
+
方法:`APIs(model)`(该模型可走哪些协议,用户显式设置优先)、`Speaks()`、
|
|
49
|
+
`Native(model)`、`Ready()`。
|
|
50
|
+
- `internal/provider/account.go:44-118` —
|
|
51
|
+
`Account{Agent, User, Plan, Stream, Home}` + 行为函数闭包
|
|
52
|
+
(`token/sign/renew/body/models/fetch/retry/unusable/explain/plugin/transport/
|
|
53
|
+
clientFor`)。设计要点:**订阅账号不复制凭据**,而是读取对应 agent(Claude
|
|
54
|
+
Code/Codex/Gemini CLI…)自身磁盘/Keychain 里的凭据,`Agent` 字段指向"谁的凭据"。
|
|
55
|
+
- `internal/gateway/fallback.go` — 统一抽象的落点:
|
|
56
|
+
- `restKey()`(:102)= `p.ID@user`(账号)或 `p.ID#KeyID`(key),rest/记账/
|
|
57
|
+
affinity 全用这个 id,两类上游在此同构;`restID()`(:124)=
|
|
58
|
+
`restKey + /model`,支持 per-model rest。
|
|
59
|
+
- `perKeyBarred()`(:175-289):账号侧先 Own 后 AlsoOn,按 LoginRanks 排序,
|
|
60
|
+
`AccountServes` 过滤、capHeld 剔除;key 侧按 `keyFit()`
|
|
61
|
+
(:293,0=协议匹配 1=需翻译 2=别家模型)稳定排序后按 KeyProtocol 分池。
|
|
62
|
+
产出 out/aside/barred 三组 candidate。
|
|
63
|
+
- `plan()`(:341):主 provider + `p.Fallback` 链,seen 去重,`restLast`
|
|
64
|
+
(:831)把在休息的压到最后;`planGroup/planLevel`(:390/:431)把嵌套 group
|
|
65
|
+
作为一个 unit(可 weightedUnits)。
|
|
66
|
+
- `modelFamily()`(:317):claude*→Anthropic、gpt-/codex/o*→Chat,用于推断
|
|
67
|
+
原生协议。
|
|
68
|
+
- `internal/routes/routes.go` — `Seat`(Key/Account/Known/Learns/Used/Soon/
|
|
69
|
+
Pace/Weight/Resting/Tokens/Free/Credits)、`Strategy{Order func(pool string,
|
|
70
|
+
seats []Seat) []int; Exact; ByUse; ByFree}`、`Register/Find`(同路由多注册,
|
|
71
|
+
最后者生效)、`Apply`(:121,recover + 排列校验,**fail-open** 回 identity)。
|
|
72
|
+
包注释明说分工:gateway 自己做 rest/affinity/failover,Strategy 只管顺序
|
|
73
|
+
(这是 P3 插件点)。
|
|
74
|
+
- `internal/routes/strategies.go` — 内置策略:`Smart`(默认:
|
|
75
|
+
tier(share≥UsedShare→2/≥90→1/else 0)分层,fine 层按 Soon 最早截断到小时、
|
|
76
|
+
Learns 未知者排前一次、spent 按 creditsBefore)、`Ordered`、`Rotate`
|
|
77
|
+
(per-pool 轮转)、`LeastUsed`、`Pace`(分带排序,防"十分之一内互等"造成的
|
|
78
|
+
循环不等)、`Weighted`、`FreeFirst`。
|
|
79
|
+
- `internal/routes/round.go` — `Round()`:NGINX 式平滑加权轮询
|
|
80
|
+
(`cur[k]+=w; 选中者 -=total`),resting 者排除出轮;`MoveRound`(:67)在
|
|
81
|
+
affinity 换人时把这次请求记到 kept 名下,保证轮询记账真实。
|
|
82
|
+
- `internal/gateway/routing.go` — `weigh/weighRouted`(:1405/:1434):从
|
|
83
|
+
`provider.KeyAllowance/allowances(agent)` 读各账号用量窗口,组装 `[]Seat`
|
|
84
|
+
交给 `routes.Find(p.Routing)`;`routingOf`(:1429):策略未注册时回落 Ordered。
|
|
85
|
+
- `internal/catalog/catalog.go` — "knows which models exist",读 models.dev
|
|
86
|
+
目录 + Codex 模型缓存 + 内置兜底表;模型选择器的数据源,不参与请求路由。
|
|
87
|
+
- `internal/provider/account_order.go` — **只是 UI 层**账号展示顺序,与请求
|
|
88
|
+
路由顺序无关,勿混淆。
|
|
89
|
+
|
|
90
|
+
### 值得借鉴
|
|
91
|
+
|
|
92
|
+
- **key 与订阅账号统一为 candidate**,用 `restKey` 字符串做全生命周期身份
|
|
93
|
+
(rest、记账、affinity、concurrency 都以它为键)——一套代码处理两类上游。
|
|
94
|
+
- **策略插件点只暴露"排序"**,recover+校验+fail-open 做在框架侧,第三方策略
|
|
95
|
+
写错不致命。
|
|
96
|
+
- 账号凭据"借用"而非"托管"(不存两份),避免凭据同步与撤销问题。
|
|
97
|
+
- `APIs(model)` 让"同一模型可走多种协议"成为一等公民,翻译层据此选最短路径
|
|
98
|
+
(能 passthrough 就不翻译)。
|
|
99
|
+
|
|
100
|
+
### 取舍
|
|
101
|
+
|
|
102
|
+
- 路由状态(轮转计数、rest、affinity、Sink)分散在 gateway 内全局 map
|
|
103
|
+
(`rounds`/`restingUntil`/`routed`),单进程内存态、重启即清,换来实现简单。
|
|
104
|
+
- Smart 策略里大量特例(Learns、credits、auto-use renew)是真实运维问题
|
|
105
|
+
(#576/#718)驱动的,复杂度集中在这一处。
|
|
106
|
+
|
|
107
|
+
## 2. 流式转发与格式双向翻译
|
|
108
|
+
|
|
109
|
+
### 核心数据流
|
|
110
|
+
|
|
111
|
+
同协议:`passthrough` 直接字节转发。跨协议:`streamTranslated`(流式)/
|
|
112
|
+
`translate`(非流式)做 IR 往返:`readSSEAlive` 收上游 SSE →
|
|
113
|
+
`decoderFor(p, actual)` 译成 `Event` → 业务过滤/计费/续传 →
|
|
114
|
+
`encoder(from, …)`(`streamEncoder{event/finish/keepalive}`,gateway.go:6218)
|
|
115
|
+
按客户端协议写出。
|
|
116
|
+
|
|
117
|
+
### 关键文件与机制
|
|
118
|
+
|
|
119
|
+
- `internal/gateway/ir.go` — IR 全集:`Part{Kind; ID/Name/Args/CallID/
|
|
120
|
+
IsError/Images/Loads; Signature; Sealed/SealedBy}`、
|
|
121
|
+
`Event{Kind,Status,Text,ID,Name,MsgID,Model,Stop,Code,RequestID,Usage,Hits,…}`、
|
|
122
|
+
`EventKind`(KStart/KText/KThink/KSig/KToolStart/KToolArgs/KStop/KUsage/
|
|
123
|
+
KError/KSearch/KImage/KThinkStart/KSealed/KTextSig/KAlive)、`Usage`(含
|
|
124
|
+
`Deferred` 处理续传多 try 的合并)。协议四件套分派在
|
|
125
|
+
`gateway.go: parse/build/decoder/encoder`(:6109/:6164/:6200/:6261)。
|
|
126
|
+
- `internal/gateway/sse.go` —
|
|
127
|
+
- `readSSE/readSSEAlive`(:15/:22):bufio.Scanner 64KB 起步、上限 32MB;
|
|
128
|
+
空行 flush 一个 event;**`:` 注释行触发 alive 回调**而不算事件——这是
|
|
129
|
+
"上游还活着"的感知通道。
|
|
130
|
+
- `sseWriter`(:63):`begin()` 设流头 + WriteHeader(200) + flush;
|
|
131
|
+
`comment()` 写 `": text\n"` **不带尾随空行**(openai-go v2 会把空行当
|
|
132
|
+
事件);`event()` 才是 `event:/data:` + 双换行 + flush。
|
|
133
|
+
- `internal/gateway/gateway.go` — `relayEvents`(:6239):
|
|
134
|
+
select{事件 chan, 15s ticker};只有 `sw.quiet() >= keepaliveEvery/2` 才补
|
|
135
|
+
keepalive(避免刷屏),超过 `keepaliveLongest=5m` 无事件则放弃;
|
|
136
|
+
`keepaliveGap=1s`(透传上游心跳的间隔,:6229-6234)。
|
|
137
|
+
- `internal/gateway/fallback.go` — **holdWriter(:1573-2408)是本问题的心脏**,
|
|
138
|
+
backpressure/failover/心跳全部在此:
|
|
139
|
+
- 语义:头与体扣住(内存 buffer),直到 release 或为下一个候选丢弃;流式
|
|
140
|
+
回复**扣到第一个内容事件为止**——之前的错误/拒绝都还能换人重试。
|
|
141
|
+
- `writeHeader`(:1736):hold 时 4xx 只记录不发;SSE→`stream=true`;
|
|
142
|
+
JSON→`whole=true`(整读供 settle 判断拒绝)。
|
|
143
|
+
- `scan()`(:2021):对 held buffer 逐事件扫描:
|
|
144
|
+
`eventError/eventRefusalError` → 记 failure 并 `h.stop()` **主动掐断上游
|
|
145
|
+
请求**(否则上游 429 事件会让客户端干等),`eventThinking` → 延长持有;
|
|
146
|
+
见内容 → `flow()` 放行。持有上限:`holdLongest=15s`、`holdMost=1MB`、
|
|
147
|
+
`holdBuffered=4m`(ChatGPT safety_buffering)、`holdThinking=4m`、
|
|
148
|
+
`holdLead=holdThinking`。
|
|
149
|
+
- `keepAlive()`(:2150):持有超过 keepHeldAfter 时,**先把 try 的响应头拷到
|
|
150
|
+
真 ResponseWriter 并 WriteHeader(200)**,之后每 15s 写 `": keepalive\n"`
|
|
151
|
+
——客户端拿到合法流头但没有内容;此时若失败,failover 的下一个候选
|
|
152
|
+
**在同一条 HTTP 响应里继续写流**(头部只发一次)。Gemini 协议不支持注释
|
|
153
|
+
(#934),直接排除。
|
|
154
|
+
- `keptAlive`(:2114):`{proto, sent, at, told, streams}` 共享于一次请求的
|
|
155
|
+
多个 try;`agentQuietMost=4m`(:2136):**Codex(Responses)只把真事件算进
|
|
156
|
+
idle timeout,注释不算**——`tooQuiet()`(:2140)检测到快超时就强制放行。
|
|
157
|
+
- `watch`/`watchEvery=1s`(:2410)/`keepQueued`(:2427):排队等并发槽期间同样
|
|
158
|
+
先扣住、发心跳、转 hold;慢启动(firstWait)超时以 `errSlowStart` 放弃。
|
|
159
|
+
- `see()/streamEnds`(:1948/:1938):扫各协议流终止 marker
|
|
160
|
+
(`response.completed`/`message_stop`/`data:[DONE]`)——agent 收到终止可能
|
|
161
|
+
立即挂断(#1978)。
|
|
162
|
+
- `settle()`(:2300):try 结束后对整读回复判安全拒绝,`handBack()`(:2364)
|
|
163
|
+
把成形的拒绝作为 vendor 原样 200 交回;`release()`(:2383)无人可换时最终
|
|
164
|
+
放出;`failed()`(:2338)= `!passing && code>=400 && retryable` 即可换人。
|
|
165
|
+
- 附带:`loopGuard`(推理死循环检测 #1359)、`wantWhole`(:1693,压缩摘要类
|
|
166
|
+
回复扣到底,防止中途 429 变成唯一答案)。
|
|
167
|
+
- `internal/gateway/continuation.go` — `streamTranslated`(:369-670):
|
|
168
|
+
- 主循环 `for again`:每次 `forwardTranslated` 发上游;`readSSEAlive` 逐事件
|
|
169
|
+
喂 decoder;`alive` 回调(:458)在 `sw.quiet()>=keepaliveGap` 时
|
|
170
|
+
`enc.keepalive()`,把上游心跳转成对客户端的心跳(#436)。
|
|
171
|
+
- **断流续传**:上游中途断(cut)时,`continuation{mode: prefillHow}` 构造
|
|
172
|
+
`cont.request()`:把已发给客户端的 thinking+text 作为 assistant prefill、
|
|
173
|
+
`r.Resume=true` 向**同一对话**重问,`unecho`(:347)剥掉模型对 prefill 的
|
|
174
|
+
复读;重试间隔 `retryPause<<again` 指数退避。
|
|
175
|
+
- 错误扣发:KError 且 `cont.possible()` 时先记下不发,确认续不上才发给
|
|
176
|
+
客户端——**客户端永远看不到中间失败**。
|
|
177
|
+
- 计费合并:KStart/KUsage 时 `this + before`(各次 try 的 usage 相加)。
|
|
178
|
+
- 空回复重试(:599):`empty && !said` 时先原地重问 `emptyRetries` 次
|
|
179
|
+
(Gemini 长对话偶发只回推理,#667)。
|
|
180
|
+
- 截断判定(:558):Anthropic 流无 stop_reason/message_stop、Responses 无
|
|
181
|
+
response.completed → `errEndedShort` 视为被 relay 截断(#1643)。
|
|
182
|
+
- 上游不吐流(忽略 stream:true)→ `didNotStream` 502 干净失败(:521-531)。
|
|
183
|
+
- `internal/gateway/upstream.go` — `upstreamStop`(:72)、`replyEnds`(:123)、
|
|
184
|
+
`carriedError`(:155)/`streamErrorText`(:181,**永不返回空**)。
|
|
185
|
+
|
|
186
|
+
### 值得借鉴
|
|
187
|
+
|
|
188
|
+
- **holdWriter 模式**:把"还没决定用哪个上游的回复"作为独立
|
|
189
|
+
`http.ResponseWriter` 包装层,统一解决 failover、心跳、慢启动、排队、安全
|
|
190
|
+
拒绝、循环检测——比在每个转发点散落 if 干净得多。
|
|
191
|
+
- 注释行心跳 + "客户端 idle 超时只认真事件"的模型差异(agentQuietMost 只对
|
|
192
|
+
Responses 收紧),是对真实客户端(Codex 300s idle、Alma 240s)的逆向适配。
|
|
193
|
+
- 流头先行 + 同响应续写:failover 对客户端完全透明(不重连、不重发头)。
|
|
194
|
+
- 断流续传用 prefill 恢复对话,配合 `unecho` 处理复读。
|
|
195
|
+
|
|
196
|
+
### 取舍
|
|
197
|
+
|
|
198
|
+
- holdWriter 状态机字段极多(30+),每个都有 issue 号注脚——正确性靠测试与
|
|
199
|
+
issue 追溯。自建 runtime 可从"扣首事件 + 15s 注释心跳 + 4m 上限"最小内核
|
|
200
|
+
起步。
|
|
201
|
+
- 事件逐个翻译意味着跨协议时上游原始帧不可复用,passthrough 是性能逃生门。
|
|
202
|
+
|
|
203
|
+
## 3. 工具调用透传
|
|
204
|
+
|
|
205
|
+
### 核心数据流
|
|
206
|
+
|
|
207
|
+
入站协议的 tools/tool_calls → `parse` 译为 IR `Part{ToolCall/ToolResult}` +
|
|
208
|
+
`Tool` → (可选:gwmw Request/masker 改写 body 前、gwtool gate 过滤)→ `build`
|
|
209
|
+
译为出站协议;回复侧反向:出站 tool_call delta → `decoder` →
|
|
210
|
+
`Event{KToolStart/KToolArgs}` → 入站 encoder 写出。同协议 passthrough 不经手。
|
|
211
|
+
|
|
212
|
+
### 关键文件与机制
|
|
213
|
+
|
|
214
|
+
- `internal/gateway/ir.go` — `Part` 携带 `ID/Name/Args/CallID/IsError/Images/
|
|
215
|
+
Loads(tool_reference 拉起的 deferred 工具)/Signature/Sealed/SealedBy`;
|
|
216
|
+
`Tool{Name,Description,Schema,Strict,Deferred}`;`joinSplitCalls` 合并被拆散
|
|
217
|
+
的连续 assistant 消息。**Sealed 规则**:`Part.Sealed`(Anthropic
|
|
218
|
+
`redacted_thinking` / Responses `encrypted_content`)只由同协议的
|
|
219
|
+
builder/encoder 原样字节透传,跨协议不伪造——`KSealed` 事件同理。
|
|
220
|
+
- reasoning 透传:thinking 文本 + `Signature`(KSig/KTextSig)成对回传,支撑
|
|
221
|
+
interleaved thinking 续接;continuation 的 `emit` 对 resume 状态过滤
|
|
222
|
+
KThink/KSig/KThinkStart/KSealed 不重发(:317-330),thinking 已 journal 化
|
|
223
|
+
用于续传 prefill。
|
|
224
|
+
- `internal/gateway/additional_tools.go` — Responses Lite 特有:Codex 把工具
|
|
225
|
+
放在 input 的 `additional_tools` item
|
|
226
|
+
(`codex_protocol::DEFAULT_FUNCTION_NAMESPACE`)。`liftAdditionalTools`(:25)
|
|
227
|
+
入站时拆进顶层 `tools` 并剥 namespace;`addTool`(:111)合并重复 namespace。
|
|
228
|
+
只有 OpenAI 认识这个 item,xAI 直接 422(#350)——provider 侧有
|
|
229
|
+
`AdditionalTools/CodexAsSent/LoadedInPlace` 开关控制何时保持原样发回。
|
|
230
|
+
- `internal/gwmw/gwmw.go`(Go 侧插件点,JS 经 `internal/middleware` 同接口
|
|
231
|
+
桥接)— `Agent{Begin(Info) Run}`;
|
|
232
|
+
`Run{Request(body) ([]byte, *Reject); Wrap(w) (http.ResponseWriter,
|
|
233
|
+
func()); End()}`——请求改写、直接拒绝、响应逐事件改写三个钩子;
|
|
234
|
+
`Tried(body, to To) []byte` 在**每个 provider 候选发出前**再给一次改 body
|
|
235
|
+
机会。
|
|
236
|
+
`Masker{JSON,Text,Restore,Writer}` + `Teller/Hit`:敏感信息遮蔽;**agent
|
|
237
|
+
中间件 fail-open(`Safely` 捕 panic),Masker fail-closed(失败即
|
|
238
|
+
`ErrUnmasked` 拒绝)**——遮蔽错=泄密,方向相反,安全分级清晰。
|
|
239
|
+
`internal/middleware/run.go`:`Chain/Begin/Run`(:27-:192)为 JS 实现;
|
|
240
|
+
`Wrap` 返回的 `writer`(:260)逐 SSE 事件切分(`eventEnd/splitEvent`
|
|
241
|
+
:431/:446)喂 JS hook,注释行不计;`middleware.go` 管理 JS realm 实例池、
|
|
242
|
+
单 hook 超时、console 限流(linesAMinute=60)。
|
|
243
|
+
`internal/middleware/points.go`:JS 插件还能注册 `routes.Strategy`
|
|
244
|
+
(`mw.strategy` :215,fail-open 回原序)与 usage observer(`Points()` :359)。
|
|
245
|
+
- `internal/gwtool/gwtool.go` — `Gate()/Serve(name)/Served(name)`:网关自答
|
|
246
|
+
工具(`web_search`、`plugin_tools`)的准入闸。
|
|
247
|
+
- 文本形式 tool call:continuation.go:535 的 `textCallNames/textCallSee`——
|
|
248
|
+
某些模型把 tool call 写成正文,由 see 层识别并 hold/release。
|
|
249
|
+
|
|
250
|
+
### 值得借鉴
|
|
251
|
+
|
|
252
|
+
- "additional_tools 降级/还原"策略:为单个客户端方言做的语义扩展,入口 lift、
|
|
253
|
+
出口按 provider 能力还原,而不是让每个 provider 适配器都懂这个方言。
|
|
254
|
+
- Sealed/Signature 这类"不可跨协议翻译的 opaque 块"显式建模,同协议直通、
|
|
255
|
+
跨协议丢弃而非伪造——避免把加密 thinking 假装成普通 thinking。
|
|
256
|
+
- 中间件 fail-open / masker fail-closed 的显式分级;`Tried` 钩子允许按候选
|
|
257
|
+
改写请求。
|
|
258
|
+
|
|
259
|
+
### 取舍
|
|
260
|
+
|
|
261
|
+
- 工具 schema 只做结构搬运,不做 JSON Schema 深度兼容修补——schema 不兼容
|
|
262
|
+
的锅留给 400 → 自适应重建重试。
|
|
263
|
+
- JS 中间件逐事件喂 JS 换来灵活性,代价是每个事件一次跨语言调用 + realm 池。
|
|
264
|
+
|
|
265
|
+
## 4. 错误与重试语义
|
|
266
|
+
|
|
267
|
+
### 核心数据流
|
|
268
|
+
|
|
269
|
+
`failure(status, body)` 分类 → serve 循环分支决定:原地重建重试 / `restAfter`
|
|
270
|
+
给该候选设 rest 并换下一个 / 透传给客户端;回复已开始后的失败走 holdWriter
|
|
271
|
+
(`failed()`)+ streamTranslated 扣发或续传。budget 限流在**更早**的 HTTP
|
|
272
|
+
中间件层(keyLimited),在任何 provider 之前。
|
|
273
|
+
|
|
274
|
+
### 关键文件与机制
|
|
275
|
+
|
|
276
|
+
- `internal/gateway/routing.go` —
|
|
277
|
+
- 18 个 `fail*` 常量(:269-326);`failure(status, body)`(:375-414):
|
|
278
|
+
proxyconnect→failProxy;402+usage 词→quota;credit/insufficient_quota→
|
|
279
|
+
credit;429+resetsWhen→quota;429+rateWords→rate;5xx+quotaCodes 或
|
|
280
|
+
<500+usedUpWords→quota;429→rate;else other。`failureOf`(:420)再叠加:
|
|
281
|
+
登录失效/Revoked→failAuth、restrictedRefused→failRestricted。词表多语言
|
|
282
|
+
(中英文时间、Zhipu 1308 的北京时区日期 :684)。
|
|
283
|
+
- `restAfter/restAfterMarked`(:850-:1080)→ `Rest{Why, Status, Until, By,
|
|
284
|
+
Failures, Key, Model, Link}`(:482),写入全局 `restingUntil`
|
|
285
|
+
(fallback.go:1010)。`By` 取值即策略:credit(30m)/quota(15m)/resets
|
|
286
|
+
(vendor 自述时间,`resetsAt` :655 解析 Claude Code/ChatGPT/中文/Zhipu)/
|
|
287
|
+
window(allowance 读数)/retry-after/cooldown(1m)/backoff(指数
|
|
288
|
+
`fallbackCooldown<<n`,封顶 RestCapOr(10m))/restricted(2h)/verify/
|
|
289
|
+
"off"(provider.RestCapOff:不明错误不 rest)。
|
|
290
|
+
- **rest 粒度决策**(:1038-:1066):模型不被该 key 计划服务(modelRefused
|
|
291
|
+
#1235)、pooled quota(#2036)、Cline 免费模型日限(#1398)、"付费模型
|
|
292
|
+
credit 用尽但同账号有免费模型"(PaidBesideFree)→ 只 rest `restID`
|
|
293
|
+
(模型级);OpenRouter 共享池、远程 magpie(#1849/#2077,
|
|
294
|
+
X-Magpie-Limit-Reset 头)同理;其余 rest 整个账号/key。
|
|
295
|
+
- `rateRest`(:1166):优先 Retry-After/reset 头/resets_at;刚放出来又 429 →
|
|
296
|
+
指数翻倍(rateForget 窗口),封顶 longestRateRest(30m);仍有 quota 的
|
|
297
|
+
rate-limited → `sink` 沉底(provider.Sink)。
|
|
298
|
+
- `restSlow`(:1116):慢启动按模型计,连续 slowRests(2) 次起 rest。
|
|
299
|
+
- `internal/gateway/fallback.go` —
|
|
300
|
+
- `retryable(status, body)`(:1275):**overflow(上下文超长)→ false**
|
|
301
|
+
(透传给 agent 让它 compact,换谁都一样);401/402/403/404/408/429/5xx →
|
|
302
|
+
true;400/422 仅当 quota/unserved/refused/shape/unsaid/region/
|
|
303
|
+
wrongEndpoint 词;EdgeBlocked → true。
|
|
304
|
+
- `tooLongUnsaid`(:1317):什么都不说的 400/502,若请求 token 估算超窗口
|
|
305
|
+
1.2 倍 → 改写为"prompt too long"让 agent compact。
|
|
306
|
+
- `lateRests`(:1304):回复已开始后断掉的错误是否 rest(超长/拒绝/防护/空
|
|
307
|
+
回复不 rest)。
|
|
308
|
+
- `internal/gateway/gateway.go` serve 循环失败分支(:2960-3199):failUpdate
|
|
309
|
+
(重发原样)、failFloor(tokenFloor 抬 max_tokens)、failRoom(按窗口余量改
|
|
310
|
+
max_tokens 重试同账号)、failRefused(安全拒绝:不 rest、`refusalAsksAgain`
|
|
311
|
+
限次换人)、failAuth(登录失效:直接跳过,**不 rest**——会话级问题)、
|
|
312
|
+
failProxy(不 rest 任何人,代理是用户侧问题)、effortRefused(换 matesFirst
|
|
313
|
+
或 400)、413/overflow(`withRoom` 过滤已知太小的成员)。
|
|
314
|
+
- **Budget/限流的介入层**:
|
|
315
|
+
- `internal/gateway/key_limit.go` — `keyLimited`(:36)HTTP 中间件:仅 POST、
|
|
316
|
+
仅带 Limits 的 gateway key;拒绝时 429 + Retry-After +
|
|
317
|
+
`X-Magpie-Limit-Reset`(自身限额与 provider 429 区分,#2077)+ **
|
|
318
|
+
`X-Should-Retry: false`**(否则 Anthropic/OpenAI SDK 会自动重试);有
|
|
319
|
+
limit 但 limiter 未注册 → **503 fail-closed**。`reserveFor`(:83) →
|
|
320
|
+
`gwbudget.Limiting`(fail-closed)→ `Limiter.Reserve`。
|
|
321
|
+
- `internal/gwbudget/gwbudget.go` — `Limiter{Reserve(who, body, model, now)
|
|
322
|
+
(release func(), refused Refusal)}`、`Refusal{RetryAfter/ResetAt}`;
|
|
323
|
+
key 有 Limits 而无限额器 → 先触发一次 refresh(≤1s 节流)再取。
|
|
324
|
+
- `internal/budget/budget.go` — `Reserve`(:341):每 key 多 limit(#2227,
|
|
325
|
+
如 5h+week 各自窗口);用量首读自 `usage.jsonl`,之后 `Append`(:155)原子
|
|
326
|
+
地"写文件+入账";**预留 = body字节/4 + 该窗口平均 output**,
|
|
327
|
+
`used + reserved < cap` 才放行(并行请求大约只超一个 call 的量);
|
|
328
|
+
`Binding`(:257)取最紧的 limit。
|
|
329
|
+
- `internal/steady/steady.go` — **与请求重试无关**:Windows 文件 rename 竞争
|
|
330
|
+
重试与 `WriteFile`(temp+fsync+rename 防掉电零字节 #1505)。
|
|
331
|
+
|
|
332
|
+
### 值得借鉴
|
|
333
|
+
|
|
334
|
+
- **错误分类 → rest 策略 → 候选选择三段解耦**:正则词表分类(可独立测试)、
|
|
335
|
+
`Rest{By}` 记录策略来源、serve 循环只看 `resting()`。给每个 rest 记
|
|
336
|
+
`By` 与 `Failures`,UI 可解释、可手动解除。
|
|
337
|
+
- 两个实用判断:overflow 不重试(透传促使 agent compact,`tooLongUnsaid` 用
|
|
338
|
+
token 估算兜底);failAuth 不 rest(重登前 rest 8 天毫无意义)。
|
|
339
|
+
- budget 的预留制(平均 output + body/4)比纯事后计数更抗并发超支;
|
|
340
|
+
`X-Should-Retry: false` 防客户端 SDK 二次重试打穿预算。
|
|
341
|
+
- rest 粒度可细到"模型级",并允许 rest 后异步收窄(narrowWhenRead):先保守
|
|
342
|
+
rest 整账号,读到 allowance 后缩小到模型——避免一次误判冻结整个账号。
|
|
343
|
+
|
|
344
|
+
### 取舍
|
|
345
|
+
|
|
346
|
+
- 分类靠词表正则,需持续维护多语言/多厂商变体(代码里大量 issue 号是证据);
|
|
347
|
+
好处是零成本、离线、可测。
|
|
348
|
+
- rest 全内存,多实例不共享(magpie 是单机桌面场景)。
|
|
349
|
+
|
|
350
|
+
## 5. netproxy 与 netguard(调研第 2 项"代理"的直接参照)
|
|
351
|
+
|
|
352
|
+
### netproxy = 出站代理决策层(不是代理服务器)
|
|
353
|
+
|
|
354
|
+
`internal/netproxy/netproxy.go`,包注释开宗明义:决定"magpie 自己发往 vendor
|
|
355
|
+
的请求走哪个代理"。GUI(Dock/Start menu 启动)没有 `HTTPS_PROXY` 环境变量,
|
|
356
|
+
没有它所有请求直连会挂死。
|
|
357
|
+
|
|
358
|
+
- `Install()`(:30):把 `http.DefaultTransport.Proxy` 设为 `Func`,
|
|
359
|
+
`http.DefaultClient.Transport` 设为 `Dispatch(t)`。
|
|
360
|
+
- 全局代理选择 `For(u)`(:244),优先级:settings 里的 Proxy(`"direct"`=强制
|
|
361
|
+
直连)→ 环境变量(含 Go 原生不读的 `ALL_PROXY`,`NO_PROXY` 按 curl 语义
|
|
362
|
+
匹配 :422)→ 系统代理(macOS scutil / Windows Internet Options,`System()`
|
|
363
|
+
:472 缓存 15s/无代理 2s,#1518)。
|
|
364
|
+
- **per-provider 代理(自带到上游的代理层:是,出站方向)**:`With(ctx, choice)`
|
|
365
|
+
(:111)在请求 ctx 上标注该 provider 的选择(`""`全局、`"direct"`直连、
|
|
366
|
+
`http(s)://`、`socks5://`、裸 host:port=HTTP);provider 配置项即
|
|
367
|
+
`provider.Proxy/AccountProxies`。
|
|
368
|
+
- `Dispatch`(:158):RoundTripper 按 `route{choice, h1, unchecked}` 克隆 base
|
|
369
|
+
transport(≤32 个,超了全清重建)——**连接池按代理隔离**,防止 HTTP/2 连接
|
|
370
|
+
"从代理 A 拨号却被代理 B 的请求复用"。附加:`HTTP1()`(:146/:224,ChatGPT
|
|
371
|
+
backend 强制 HTTP/1.1 且清 ALPN,仿 Codex)、`uncheckTLS`(per-host 关证书
|
|
372
|
+
校验,配合 provider.InsecureTLS)、`v4Fallback`(IPv6 死路回退 IPv4)、
|
|
373
|
+
`HTTP2Health`(:53,20s 无帧 ping、10s 无 pong 断链,治静默死连接 #2843)。
|
|
374
|
+
- `Hold(ctx, err)`(:85):构造"禁止出网"ctx(#1518)。
|
|
375
|
+
- `EnvWith(choice, env)`(:274):为 magpie 代启动的 CLI 合成
|
|
376
|
+
`*_PROXY/NO_PROXY`——解决 GUI 父进程无代理环境变量、子进程 CLI 只认环境
|
|
377
|
+
变量的问题。
|
|
378
|
+
- `lan.go`:`onLAN` —— 除非该 provider 自带代理,局域网 host 永远直连;
|
|
379
|
+
loopback 永不代理。
|
|
380
|
+
|
|
381
|
+
### netguard = SSRF 地址判定库(纯函数,无 IO)
|
|
382
|
+
|
|
383
|
+
`internal/netguard/netguard.go`:头注"只判地址,不拨号"。`Public(a netip.Addr)
|
|
384
|
+
bool` 排除 loopback/RFC1918/`fc00::/7`/link-local(含 169.254.169.254 元数据
|
|
385
|
+
端点)/CGNAT 100.64/10/6/文档段/fake-ip 198.18.0.0/15/NAT64/6to4;另有
|
|
386
|
+
`FakeIP`(Clash/Surge fake-ip 段识别)。用于插件清单、更新源等主动下载校验。
|
|
387
|
+
|
|
388
|
+
### 分工与借鉴
|
|
389
|
+
|
|
390
|
+
- 一句话:**netproxy 管出站"走哪条路",netguard 管"哪些目的地不许去"**。
|
|
391
|
+
- 借鉴:`Dispatch` 的"按(代理,协议,校验策略)三元组克隆 transport"是多租户
|
|
392
|
+
出站连接隔离的干净做法;`EnvWith` 解决 GUI 父进程 → CLI 子进程的代理传递;
|
|
393
|
+
系统代理短 TTL 缓存(有 15s/无 2s)兼顾启动时序与变更响应。
|
|
394
|
+
|
|
395
|
+
## 对 Trellis runtime 的意义(2026-10-23 决策后)
|
|
396
|
+
|
|
397
|
+
**决策:不自研网关,直接采用开源 Magpie**(见 runtime-infra.md)。本调研的
|
|
398
|
+
价值是运维与排障能力:
|
|
399
|
+
|
|
400
|
+
1. **排障地图**:上游失败时的行为可解释——rest 分类(`Rest{Why,By}`)、候选
|
|
401
|
+
顺序(plan/perKeyBarred)、holdWriter 的心跳与持有上限,出问题时能定位到
|
|
402
|
+
具体机制;必要时按行号向上游提 issue 或打补丁。
|
|
403
|
+
2. **升级判断**:Air 上 0.1.1332 → 上游 0.1.161 提级时,用本文对照行为变化
|
|
404
|
+
面(错误分类词表、rest 策略、心跳/持有上限都属于易变处)。
|
|
405
|
+
3. **代理层的现成参照**(议程第 2 项,已后置):per-provider 出站代理、连接池
|
|
406
|
+
按代理隔离、IPv6 回退、HTTP/2 健康探测、GUI→CLI 代理传递,magpie 都已
|
|
407
|
+
踩过并给出实现;若将来需要独立代理组件,以此为基线。
|
|
408
|
+
4. **与 Trellis MCP 网关的镜像关系**:magpie 的 rest/backoff 与我们刚发布的
|
|
409
|
+
session recovery(v0.19.0)是同一族问题(上游失效→隔离→恢复);它的
|
|
410
|
+
`Rest{By}` 可解释 rest 记录值得 MCP 网关后续借鉴。
|
|
411
|
+
|
|
412
|
+
## 附:引用时的更正(防误读)
|
|
413
|
+
|
|
414
|
+
- `internal/gateway/draw.go` 是图片生成,非候选挑选(挑选在 fallback.go 的
|
|
415
|
+
plan/perKeyBarred)。
|
|
416
|
+
- 不存在 `keepalive.go`;心跳在 `relayEvents`(gateway.go:6239)、
|
|
417
|
+
`sseWriter.comment`(sse.go:109)、`holdWriter.keepAlive/keepQueued`
|
|
418
|
+
(fallback.go:2150/:2427)三处。
|
|
419
|
+
- `internal/gateway/classify.go` 是路由意图分类器;错误分类在
|
|
420
|
+
`routing.go failure()`。
|
|
421
|
+
- `internal/provider/account_order.go` 是 UI 顺序;`internal/steady` 是
|
|
422
|
+
Windows 文件安全写,均与请求路由/重试无关。
|
package/docs/roadmap.md
CHANGED
|
@@ -993,19 +993,29 @@ coordination, evaluated only after the simpler pieces are proven). See
|
|
|
993
993
|
and `daytonaio/daytona` were both evaluated and not adopted (preview-only
|
|
994
994
|
API instability, and unmaintained-as-of-June-2026, respectively).
|
|
995
995
|
|
|
996
|
-
**Self-evolution —
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
byte-identical.
|
|
1003
|
-
|
|
1004
|
-
|
|
996
|
+
**Self-evolution — phase one delivered 2026-10-26, default off.** Trellis
|
|
997
|
+
is two decoupled subsystems: hosting (delivery and consistency of
|
|
998
|
+
canonical content) and self-evolution (content that improves with use,
|
|
999
|
+
running ambiently on the runtime substrate, opt-in and graded off →
|
|
1000
|
+
observe → propose → auto-A). Dependency runs one way, evolution → hosting,
|
|
1001
|
+
through a narrow port; uninstalling evolution leaves hosting
|
|
1002
|
+
byte-identical. Engine ported from google-research/harness2 (no usable SDK
|
|
1003
|
+
exists) into the workspace-internal package `packages/evolve-engine`
|
|
1004
|
+
(stage one: not published to npm; stage two — publishing it — is the same
|
|
1005
|
+
move as agent-framework integration and waits for a second real consumer).
|
|
1006
|
+
Phase one covers the engine port, generic hosting extension points, the
|
|
1007
|
+
TrellisSubstrate adapter, observe/trigger, L4 governance, and a fixture
|
|
1008
|
+
end-to-end run; the production wiring closed on 2026-10-26 (extension
|
|
1009
|
+
manifest sync, `activate` subscribers, and the job pipeline). The job
|
|
1010
|
+
body runs a rule-based governance pipeline — the ported engine's
|
|
1011
|
+
schedule/recursion/proposer/selection/judge loop is golden-anchored but
|
|
1012
|
+
not yet consumed in production, awaiting the L2 probe task set.
|
|
1013
|
+
Evolution visibility (timeline, before/after
|
|
1014
|
+
measurements, honest trends — no synthetic scores) landed as
|
|
1015
|
+
`trellis-evolve-visualization`. See [`self-evolution.md`](self-evolution.md) for the full
|
|
1005
1016
|
idea, what is verified versus assumed, and the open decisions; the
|
|
1006
|
-
verifiable contract is
|
|
1007
|
-
`
|
|
1008
|
-
`host-extension-points` and `self-evolution`; unimplemented).
|
|
1017
|
+
verifiable contract is at `openspec/changes/trellis-evolve/` (one change,
|
|
1018
|
+
two capabilities: `host-extension-points` and `self-evolution`).
|
|
1009
1019
|
|
|
1010
1020
|
No dates. This is scoped by verification milestones, not calendar time.
|
|
1011
1021
|
Execution order for P11–P16: P11 → P12 → P13 → P14 → P15 → P16 — CRUD
|
package/docs/runtime-infra.md
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
# Runtime 基建调研议程
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
3
|
+
> **状态:方向已定(2026-10-23)。** 网关层采用开源 Magpie 且 Trellis 零投入
|
|
4
|
+
> (不内置、不托管、不代管升级);自我进化(第 4 项)是实施主线;网络两线
|
|
5
|
+
> (代理、多机互通)按用户决定后置。调研 1 成果沉淀在
|
|
6
|
+
> [magpie-gateway-research.md](magpie-gateway-research.md)。自我进化已有
|
|
7
|
+
> 完整草案,见 `docs/self-evolution.md` 与 `openspec/changes/trellis-evolve/`。
|
|
8
|
+
|
|
9
|
+
## 顺序:网关线已收口 → 自我进化 tasks → 网络(2 → 3)最后
|
|
10
|
+
|
|
11
|
+
网关层的结论(2026-10-23,用户两步裁决):**采用开源 Magpie,且 Trellis 不做
|
|
12
|
+
任何 magpie 的事**——不内置、不托管接入、不动其生命周期与版本;magpie 完全
|
|
13
|
+
外部,用法由 skills 文档化(`$local-model-gateway`、`$mac-air-usage`、
|
|
14
|
+
`$ai-workstation-setup`)。进化按既有 tasks 推进,是当前主线;网络调研在主线
|
|
15
|
+
稳定后再做。
|
|
16
|
+
|
|
17
|
+
## 1. 大模型网关(Magpie)——✅ 已收口:采用开源 + Trellis 零投入
|
|
18
|
+
|
|
19
|
+
状态(2026-10-23):
|
|
20
|
+
- Magpie 已在 Air 两层实测运行(macOS 与 OrbStack worker 各自 `127.0.0.1:3425/v1`;
|
|
21
|
+
用法见 `$local-model-gateway`、`$mac-air-usage`);部署迁移走 `$ai-workstation-setup`。
|
|
22
|
+
- Magpie 源码已定位:上游 [github.com/yetone/magpie](https://github.com/yetone/magpie)
|
|
23
|
+
(Go,MIT,活跃);本机以研究副本形态浅克隆在 `~/www/morphix-research/sources/magpie`
|
|
24
|
+
(v0.1.161 时点;Air 生产装 v0.1.1332)。
|
|
25
|
+
- **决策 1:不自研网关**,模型接入层直接用开源 Magpie。
|
|
26
|
+
- **决策 2(同日):Trellis 不做 magpie 的事情。** 曾评估过托管接入(探测/状态
|
|
27
|
+
并入/清单声明),用户裁决不做——magpie 的配置域、生命周期、版本升级全部归
|
|
28
|
+
magpie 自己;Trellis 侧不写代码、不改其配置。排障知识(调研文档)仅作参考,
|
|
29
|
+
不维护联动。
|
|
30
|
+
- 五问深挖成果见 [magpie-gateway-research.md](magpie-gateway-research.md)
|
|
31
|
+
(运维视角快照,行号随时点漂移,不持续维护)。
|
|
32
|
+
|
|
33
|
+
本线无待办。Air 上 0.1.1332 → 上游 0.1.161 的提级**搁置**(属"magpie 的事",
|
|
34
|
+
用户裁决不做;将来需要时由用户在 magpie 自己的更新机制里完成)。
|
|
35
|
+
|
|
36
|
+
## 2. 代理——稳定访问层(**后置**,网关采用开源后此线降级为运维议题)
|
|
37
|
+
|
|
38
|
+
magpie 侧已有现成参照(调研文档第 5 节):`internal/netproxy` 是**出站代理
|
|
39
|
+
决策层**(per-provider `With(ctx, choice)`、transport 按{代理,协议,校验}
|
|
40
|
+
三元组克隆隔离连接池、IPv4 回退、HTTP/2 健康探测、GUI→CLI 用 `EnvWith` 合成
|
|
41
|
+
`*_PROXY`);`internal/netguard` 是 SSRF 地址判定纯函数。我们的代理层调研应
|
|
42
|
+
以此为基线,再论证是否需要独立组件。
|
|
24
43
|
|
|
25
44
|
调研问题:
|
|
26
45
|
- Magpie 到各上游 provider 的代理路由(per-provider 配置、failover 策略);
|
|
@@ -31,7 +50,7 @@
|
|
|
31
50
|
已有数据点:2026-10-22 npm publish 上传连接 ECONNRESET,IPv4-first + 单 socket 解决;
|
|
32
51
|
GET/ping 正常、仅上传路径异常。
|
|
33
52
|
|
|
34
|
-
## 3. 多机互通——相互委派任务的网络底座
|
|
53
|
+
## 3. 多机互通——相互委派任务的网络底座(**后置**)
|
|
35
54
|
|
|
36
55
|
已知锚点:
|
|
37
56
|
- Pro↔Air 私网由 Nore 设备互联提供(`ssh morphix-air-private` → `100.127.171.120`);
|