@lqc123qwe/car-runtime 1.9.0 → 1.11.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/README.md +17 -1
- package/dist/SHA256SUMS +1 -0
- package/dist/authz/authz.d.ts +46 -0
- package/dist/authz/authz.js +76 -0
- package/dist/car-runtime-1.11.0.tgz +0 -0
- package/dist/car-runtime-1.11.0.tgz.sig.bundle +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +825 -0
- package/dist/dx/doctor.d.ts +62 -0
- package/dist/dx/doctor.js +127 -0
- package/dist/governance/dump.d.ts +47 -0
- package/dist/governance/dump.js +17 -0
- package/dist/host/facade.d.ts +13 -0
- package/dist/host/facade.js +154 -0
- package/dist/host/hostGateway.d.ts +117 -0
- package/dist/host/hostGateway.js +65 -0
- package/dist/host/mappings.d.ts +27 -0
- package/dist/host/mappings.js +52 -0
- package/dist/host/stdio.d.ts +14 -0
- package/dist/host/stdio.js +83 -0
- package/dist/kernel/context.d.ts +76 -0
- package/dist/kernel/context.js +203 -0
- package/dist/kernel/events.d.ts +37 -0
- package/dist/kernel/events.js +82 -0
- package/dist/load/config.d.ts +79 -0
- package/dist/load/config.js +285 -0
- package/dist/load/loader.d.ts +72 -0
- package/dist/load/loader.js +162 -0
- package/dist/load/registry.d.ts +44 -0
- package/dist/load/registry.js +58 -0
- package/dist/load/report.d.ts +98 -0
- package/dist/load/report.js +301 -0
- package/dist/load/sigGate.d.ts +22 -0
- package/dist/load/sigGate.js +68 -0
- package/dist/load/sign.d.ts +25 -0
- package/dist/load/sign.js +47 -0
- package/dist/load/verifier.d.ts +35 -0
- package/dist/load/verifier.js +56 -0
- package/dist/loop/goal.d.ts +31 -0
- package/dist/loop/goal.js +34 -0
- package/dist/loop/recover.d.ts +17 -0
- package/dist/loop/recover.js +39 -0
- package/dist/loop/stop.d.ts +82 -0
- package/dist/loop/stop.js +184 -0
- package/dist/mcp/gateway.d.ts +96 -0
- package/dist/mcp/gateway.js +69 -0
- package/dist/mcp/httpClient.d.ts +27 -0
- package/dist/mcp/httpClient.js +196 -0
- package/dist/mcp/stdioClient.d.ts +8 -0
- package/dist/mcp/stdioClient.js +73 -0
- package/dist/ptc/budget.d.ts +21 -0
- package/dist/ptc/budget.js +36 -0
- package/dist/ptc/erasable.d.ts +14 -0
- package/dist/ptc/erasable.js +27 -0
- package/dist/ptc/runCode.d.ts +65 -0
- package/dist/ptc/runCode.js +140 -0
- package/dist/ptc/schemaRender.d.ts +15 -0
- package/dist/ptc/schemaRender.js +90 -0
- package/dist/ptc/sdk.d.ts +27 -0
- package/dist/ptc/sdk.js +23 -0
- package/dist/ptc/worker-entry.d.ts +1 -0
- package/dist/ptc/worker-entry.js +71 -0
- package/dist/release-audit.jsonl +4 -0
- package/dist/runtime-core/chatStep.d.ts +43 -0
- package/dist/runtime-core/chatStep.js +121 -0
- package/dist/runtime-core/credentials.d.ts +49 -0
- package/dist/runtime-core/credentials.js +118 -0
- package/dist/runtime-core/errors.d.ts +30 -0
- package/dist/runtime-core/errors.js +36 -0
- package/dist/runtime-core/llm.d.ts +76 -0
- package/dist/runtime-core/llm.js +638 -0
- package/dist/runtime-core/redaction.d.ts +39 -0
- package/dist/runtime-core/redaction.js +79 -0
- package/dist/runtime-core/telemetry.d.ts +88 -0
- package/dist/runtime-core/telemetry.js +332 -0
- package/dist/runtime-core/tools.d.ts +15 -0
- package/dist/runtime-core/tools.js +12 -0
- package/dist/runtime-core/trace.d.ts +55 -0
- package/dist/runtime-core/trace.js +137 -0
- package/dist/runtime-core/types.d.ts +104 -0
- package/dist/runtime-core/types.js +13 -0
- package/dist/sandbox/sandbox.d.ts +68 -0
- package/dist/sandbox/sandbox.js +153 -0
- package/dist/sbom.json +263 -0
- package/dist/seam.d.ts +24 -0
- package/dist/seam.js +23 -0
- package/dist/security/secrets-baseline.d.ts +43 -0
- package/dist/security/secrets-baseline.js +176 -0
- package/dist/security/secrets.d.ts +45 -0
- package/dist/security/secrets.js +138 -0
- package/dist/session/export.d.ts +55 -0
- package/dist/session/export.js +81 -0
- package/dist/session/fork.d.ts +51 -0
- package/dist/session/fork.js +63 -0
- package/dist/session/format.d.ts +21 -0
- package/dist/session/format.js +49 -0
- package/dist/session/indexStore.d.ts +44 -0
- package/dist/session/indexStore.js +265 -0
- package/dist/session/log.d.ts +79 -0
- package/dist/session/log.js +157 -0
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.js +37 -0
- package/dist/telemetry/metrics.d.ts +25 -0
- package/dist/telemetry/metrics.js +26 -0
- package/package.json +17 -1
- package/src/authz/authz.ts +12 -0
- package/src/loop/stop.ts +43 -10
- package/src/runtime-core/chatStep.ts +22 -2
- package/src/runtime-core/llm.ts +96 -13
- package/src/runtime-core/types.ts +22 -1
- package/src/seam.ts +24 -0
- package/src/session/log.ts +1 -1
- package/.gitattributes +0 -11
- package/.github/workflows/ci.yml +0 -293
- package/.github/workflows/release.yml +0 -54
- package/adr/ADR-001.md +0 -4
- package/adr/ADR-002.md +0 -5
- package/adr/ADR-003.md +0 -35
- package/adr/host-onboarding.md +0 -68
- package/adr/mlps-audit-checklist.md +0 -41
- package/adr/win32-spike.md +0 -45
- package/docs/plugin-authoring.md +0 -82
- package/native/landlock-run/Makefile +0 -19
- package/native/landlock-run/landlock-run.c +0 -162
- package/scripts/contract-check.ts +0 -56
- package/scripts/invariant-n1.ts +0 -96
- package/scripts/package-check.ts +0 -29
- package/scripts/ptc-baseline/run-baseline.ts +0 -118
- package/scripts/release-artifacts.ts +0 -111
- package/scripts/release-pipeline.ts +0 -245
- package/scripts/secrets-baseline.ts +0 -33
- package/scripts/secrets-scan.ts +0 -56
- package/scripts/win32-ffi-debug.ts +0 -143
- package/scripts/win32-koffi-spike.ts +0 -537
- package/test/contract/eventmap.golden.json +0 -7
- package/test/fixtures/hello-plugin.ts +0 -5
- package/test/kernel.spec.ts +0 -136
- package/test/registry-e2e/run-e2e.ts +0 -213
- package/test/s11.spec.ts +0 -129
- package/test/s12.spec.ts +0 -135
- package/test/s13.spec.ts +0 -117
- package/test/s14.spec.ts +0 -117
- package/test/s15.spec.ts +0 -80
- package/test/s16.spec.ts +0 -133
- package/test/s17.spec.ts +0 -102
- package/test/s18.spec.ts +0 -81
- package/test/s19.spec.ts +0 -138
- package/test/s2.spec.ts +0 -245
- package/test/s20.spec.ts +0 -241
- package/test/s21.spec.ts +0 -231
- package/test/s22-m7-store-format.spec.ts +0 -135
- package/test/s23-m7-export-index.spec.ts +0 -189
- package/test/s24-signature-gate.spec.ts +0 -185
- package/test/s25-m8-runtime-core.spec.ts +0 -600
- package/test/s26-signature-closeout.spec.ts +0 -349
- package/test/s27-host-load-wiring.spec.ts +0 -134
- package/test/s28-session-index-followup.spec.ts +0 -236
- package/test/s29-otel-producer.spec.ts +0 -252
- package/test/s3.spec.ts +0 -179
- package/test/s30-release-artifacts.spec.ts +0 -127
- package/test/s31-real-model-wiring.spec.ts +0 -329
- package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
- package/test/s33-anthropic-adapter.spec.ts +0 -354
- package/test/s34-mcp-remote-transport.spec.ts +0 -250
- package/test/s35-config-ts-zod.spec.ts +0 -145
- package/test/s36-host-tool-surface.spec.ts +0 -250
- package/test/s37-projection-dedupe.spec.ts +0 -161
- package/test/s38-config-jiti.spec.ts +0 -116
- package/test/s39-otlp-span-semantics.spec.ts +0 -100
- package/test/s4.spec.ts +0 -169
- package/test/s40-metrics-cumulative.spec.ts +0 -67
- package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
- package/test/s42-traceparent-propagation.spec.ts +0 -415
- package/test/s43-remote-http-traceparent.spec.ts +0 -262
- package/test/s5.spec.ts +0 -95
- package/test/s6.spec.ts +0 -169
- package/test/s7.spec.ts +0 -136
- package/test/s7b-baseline.spec.ts +0 -20
- package/test/s9.spec.ts +0 -89
- package/test/sandbox-escape/j17-orchestrate.sh +0 -30
- package/test/sandbox-escape/run-matrix.sh +0 -98
- package/tsconfig.json +0 -18
package/src/loop/stop.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* turn 内任一 step 撞限 → turn/end 保留 max-tokens,不被后续成功 step 冲销
|
|
11
11
|
* - 工具收口:concludesTurn=OR(任一 true 即收口);terminate=AND(整批 finalized 全 true 才中断)
|
|
12
12
|
* - 取消:未派发调用补记成对事件(toolCall + 合成错误 toolResult),日志无缺口
|
|
13
|
+
* - 1.11-S2(D-33 / 计量设计 §3.3):turnEnd meta.usage = 本 turn usage 事件四分量求和
|
|
14
|
+
* (聚合在内核消费侧单点 closeTurn——适配器无状态 ACL;无 usage 事件 → meta 不带 usage 键,
|
|
15
|
+
* 缺省=未采集向后兼容;撞限/异常 turn 照常聚合,截断前已完成请求的 usage 事实不吞)
|
|
13
16
|
*/
|
|
14
17
|
import type { SessionLog } from '../session/log.ts'
|
|
15
18
|
import { CarM8Error } from '../runtime-core/errors.ts'
|
|
@@ -58,6 +61,29 @@ export class TurnAborted extends Error {
|
|
|
58
61
|
constructor() { super('CAR-ABORTED: turn cancelled by user') }
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 1.11-S2(D-33 / 计量设计 §3.3):turn 级 usage 聚合——扫描本 turn 的 'usage' 事件(事实本体
|
|
66
|
+
* 在 JSONL,聚合由事实行求和而非运行时状态;字段非数值容错跳过不吞计数)。无 usage 事件
|
|
67
|
+
* 返回 undefined → turnEnd meta 不带 usage 键(缺省=未采集,旧格式向后兼容)。
|
|
68
|
+
*/
|
|
69
|
+
function turnUsageFrom(log: SessionLog, turnId: string): { input: number; output: number; cacheRead: number; cacheCreation: number; requests: number } | undefined {
|
|
70
|
+
let input = 0
|
|
71
|
+
let output = 0
|
|
72
|
+
let cacheRead = 0
|
|
73
|
+
let cacheCreation = 0
|
|
74
|
+
let requests = 0
|
|
75
|
+
for (const e of log.events) {
|
|
76
|
+
if (e.kind !== 'usage' || e.turnId !== turnId) continue
|
|
77
|
+
const p = (e.payload ?? {}) as Record<string, unknown>
|
|
78
|
+
if (typeof p.input === 'number') input += p.input
|
|
79
|
+
if (typeof p.output === 'number') output += p.output
|
|
80
|
+
if (typeof p.cacheRead === 'number') cacheRead += p.cacheRead
|
|
81
|
+
if (typeof p.cacheCreation === 'number') cacheCreation += p.cacheCreation
|
|
82
|
+
requests++
|
|
83
|
+
}
|
|
84
|
+
return requests ? { input, output, cacheRead, cacheCreation, requests } : undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
export async function runTurn(opts: {
|
|
62
88
|
log: SessionLog
|
|
63
89
|
turnId: string
|
|
@@ -72,6 +98,13 @@ export async function runTurn(opts: {
|
|
|
72
98
|
let sawMaxTokens = false
|
|
73
99
|
let steps = 0
|
|
74
100
|
|
|
101
|
+
// 1.11-S2(D-33):turnEnd 落链单点包装——聚合本 turn usage 事件(无 usage 不带键,
|
|
102
|
+
// 缺省=未采集向后兼容);reason 保持 meta 首键,usage 恒为加法尾键
|
|
103
|
+
const closeTurn = (meta: Record<string, unknown>) => {
|
|
104
|
+
const usage = turnUsageFrom(log, turnId)
|
|
105
|
+
return log.append('runtime', 'turnEnd', turnId, null, { ...meta, ...(usage ? { usage } : {}) })
|
|
106
|
+
}
|
|
107
|
+
|
|
75
108
|
const executeBatch = async (calls: ToolCall[]): Promise<{ concludesTurn: boolean; concludesAll: boolean; terminateVotes: number; terminateAll: boolean; finalized: number; denies: number }> => {
|
|
76
109
|
let concludesTurn = false
|
|
77
110
|
let concludesVotes = 0
|
|
@@ -122,7 +155,7 @@ export async function runTurn(opts: {
|
|
|
122
155
|
try {
|
|
123
156
|
while (steps < maxSteps) {
|
|
124
157
|
if (signal?.aborted) {
|
|
125
|
-
const seq =
|
|
158
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
126
159
|
trail.push({ seq, reason: 'aborted' })
|
|
127
160
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
128
161
|
}
|
|
@@ -131,7 +164,7 @@ export async function runTurn(opts: {
|
|
|
131
164
|
const step = await model()
|
|
132
165
|
|
|
133
166
|
if (step.stopReason === 'error') {
|
|
134
|
-
const seq =
|
|
167
|
+
const seq = closeTurn({ reason: 'error' }).seq
|
|
135
168
|
trail.push({ seq, reason: 'error' })
|
|
136
169
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
137
170
|
}
|
|
@@ -151,7 +184,7 @@ export async function runTurn(opts: {
|
|
|
151
184
|
continue
|
|
152
185
|
}
|
|
153
186
|
// 默认收口:截断响应不解析、不执行任何工具调用
|
|
154
|
-
const seq =
|
|
187
|
+
const seq = closeTurn({ reason: 'max-tokens' }).seq
|
|
155
188
|
trail.push({ seq, reason: 'max-tokens' })
|
|
156
189
|
return { reason: 'max-tokens', steps, endReasonTrail: trail }
|
|
157
190
|
}
|
|
@@ -160,21 +193,21 @@ export async function runTurn(opts: {
|
|
|
160
193
|
const batch = await executeBatch(step.toolCalls)
|
|
161
194
|
// F13:授权拒绝即收口(blockOnDeny 产出点——blocked 仅新增产出点,枚举不增)
|
|
162
195
|
if (preset.blockOnDeny && batch.denies > 0) {
|
|
163
|
-
const seq =
|
|
196
|
+
const seq = closeTurn({ reason: 'blocked', denies: batch.denies }).seq
|
|
164
197
|
trail.push({ seq, reason: 'blocked' })
|
|
165
198
|
return { reason: 'blocked', steps, endReasonTrail: trail }
|
|
166
199
|
}
|
|
167
200
|
const agg = preset.aggregate ?? {}
|
|
168
201
|
const abortedNow = (agg.terminate ?? 'all') === 'any' ? batch.terminateVotes > 0 : batch.terminateAll
|
|
169
202
|
if (abortedNow) { // terminate 收口:默认 AND(整批),可配置 any
|
|
170
|
-
const seq =
|
|
203
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
171
204
|
trail.push({ seq, reason: 'aborted' })
|
|
172
205
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
173
206
|
}
|
|
174
207
|
const concludedNow = (agg.concludesTurn ?? 'any') === 'any' ? batch.concludesTurn : batch.concludesAll && batch.finalized > 0
|
|
175
208
|
if (concludedNow) { // concludesTurn 收口:默认 OR,可配置 all
|
|
176
209
|
const reason: TurnEndReason = sawMaxTokens ? 'max-tokens' : 'completed'
|
|
177
|
-
const seq =
|
|
210
|
+
const seq = closeTurn({ reason }).seq
|
|
178
211
|
trail.push({ seq, reason })
|
|
179
212
|
return { reason, steps, endReasonTrail: trail }
|
|
180
213
|
}
|
|
@@ -184,16 +217,16 @@ export async function runTurn(opts: {
|
|
|
184
217
|
|
|
185
218
|
// stopReason === 'stop':模型自然完成
|
|
186
219
|
const reason: TurnEndReason = sawMaxTokens ? 'max-tokens' : 'completed'
|
|
187
|
-
const seq =
|
|
220
|
+
const seq = closeTurn({ reason }).seq
|
|
188
221
|
trail.push({ seq, reason })
|
|
189
222
|
return { reason, steps, endReasonTrail: trail }
|
|
190
223
|
}
|
|
191
|
-
const seq =
|
|
224
|
+
const seq = closeTurn({ reason: 'error', detail: 'max-steps' }).seq
|
|
192
225
|
trail.push({ seq, reason: 'error' })
|
|
193
226
|
return { reason: 'error', steps, endReasonTrail: trail }
|
|
194
227
|
} catch (e) {
|
|
195
228
|
if (e instanceof TurnAborted) {
|
|
196
|
-
const seq =
|
|
229
|
+
const seq = closeTurn({ reason: 'aborted' }).seq
|
|
197
230
|
trail.push({ seq, reason: 'aborted' })
|
|
198
231
|
return { reason: 'aborted', steps, endReasonTrail: trail }
|
|
199
232
|
}
|
|
@@ -201,7 +234,7 @@ export async function runTurn(opts: {
|
|
|
201
234
|
// car doctor」可达用户;非 CarM8Error 维持 String(e) 口径不变)
|
|
202
235
|
const m8 = e instanceof CarM8Error
|
|
203
236
|
const detail = m8 ? `${e.slug}: ${e.message}` : String(e)
|
|
204
|
-
const seq =
|
|
237
|
+
const seq = closeTurn({
|
|
205
238
|
reason: 'error', detail,
|
|
206
239
|
...(m8 ? { code: e.code, userHint: e.userHint, retryable: e.retryable } : {}),
|
|
207
240
|
}).seq
|
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
* (半截 JSON 不解析——ADR-001 同纪律,length 时只交 truncatedTools);
|
|
13
13
|
* 5. assistant 文本事件落 M7(脱敏后 + secretsRedacted 计数留痕 meta);
|
|
14
14
|
* 6. finishReason 不可变透传为 ModelStep.stopReason(error/aborted → 'error',BD-04 收口
|
|
15
|
-
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch
|
|
15
|
+
* 由 runTurn 落 turnEnd);工具调用事件由 runTurn executeBatch 落(本层不重复);
|
|
16
|
+
* 7. 1.11-S2(D-33 / 计量设计 §3.3):流尾 usage chunk → 'runtime'/'usage' 请求级事实落链
|
|
17
|
+
* (缺失 = provider 未回/流中断/畸形,禁补造;actor='runtime'、model_visible=0 不进模型
|
|
18
|
+
* 消息流)——turn 级聚合在 stop.ts 收口单点,本层不做跨请求状态。
|
|
16
19
|
*/
|
|
17
20
|
import type { SessionLog } from '../session/log.ts'
|
|
18
21
|
import { TurnAborted, type ModelStep, type ToolCall } from '../loop/stop.ts'
|
|
19
22
|
import type { RuntimeCore } from './llm.ts'
|
|
20
|
-
import type { ToolDefinition } from './types.ts'
|
|
23
|
+
import type { LlmUsage, ToolDefinition } from './types.ts'
|
|
21
24
|
import { StreamRedactor } from './redaction.ts'
|
|
22
25
|
import { scanSecrets } from '../security/secrets.ts'
|
|
23
26
|
import { providerUnreachable } from './errors.ts'
|
|
@@ -67,6 +70,7 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
67
70
|
const calls = new Map<number, { id?: string; name?: string; argsBuf: string }>()
|
|
68
71
|
let stopReason: ModelStep['stopReason'] | undefined
|
|
69
72
|
let errorDetail: string | undefined
|
|
73
|
+
let usage: LlmUsage | undefined
|
|
70
74
|
|
|
71
75
|
const stream = core.chat({
|
|
72
76
|
model,
|
|
@@ -90,6 +94,7 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
90
94
|
calls.set(idx, cur)
|
|
91
95
|
}
|
|
92
96
|
if (chunk.error) errorDetail = chunk.error.message
|
|
97
|
+
if (chunk.usage) usage = chunk.usage // D-33:附着于最后一个 chunk——后到覆盖
|
|
93
98
|
if (chunk.finishReason !== undefined) stopReason = chunk.finishReason === 'aborted' ? 'error' : chunk.finishReason
|
|
94
99
|
}
|
|
95
100
|
// 1.4-S4(D-19):finish 检查前先查取消(适配器取消路径静默收口无 finishReason——
|
|
@@ -101,6 +106,21 @@ export async function chatStep(opts: ChatStepOptions): Promise<ChatStepResult> {
|
|
|
101
106
|
}
|
|
102
107
|
text += redactor.flush()
|
|
103
108
|
|
|
109
|
+
// 1.11-S2(D-33 / 计量设计 §3.3):请求级 usage 事实落链(拿到 usage 的最后一个 chunk 即请求
|
|
110
|
+
// 收尾;撞限/异常 turn 照常落——截断前已完成请求的 usage 事实不吞)。缺失 = provider 未回,
|
|
111
|
+
// 禁补造;actor='runtime'、不进模型消息流(deriveMessages 不投影 usage,§2.5 红线)
|
|
112
|
+
if (usage) {
|
|
113
|
+
log.append('runtime', 'usage', turnId, {
|
|
114
|
+
model: usage.model,
|
|
115
|
+
adapterId: usage.adapterId,
|
|
116
|
+
input: usage.inputTokens,
|
|
117
|
+
output: usage.outputTokens,
|
|
118
|
+
cacheRead: usage.cacheReadTokens,
|
|
119
|
+
cacheCreation: usage.cacheCreationTokens,
|
|
120
|
+
providerRaw: usage.providerRaw,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
// 5. assistant 文本落 M7(脱敏后 + 计数留痕;空文本不产事件)
|
|
105
125
|
// flush 后兜底复扫只计数不复写:流式遮蔽已生效,>0 即流式边界逃逸的审计信号
|
|
106
126
|
let secretsRedacted = 0
|
package/src/runtime-core/llm.ts
CHANGED
|
@@ -20,10 +20,15 @@
|
|
|
20
20
|
* 0xC0000409)——car run 真路径 ≥3 次模型请求 + PTC worker/MCP 子进程并存即确定性复现
|
|
21
21
|
* (1.4 彼时仅修遥测出站,LLM 出站同为 undici 默认——潜伏至 1.8 E2E 三请求形态暴露);
|
|
22
22
|
* 流式契约 body = AsyncIterable<Uint8Array> 不变;测试注入缝 fetchImpl 保留。
|
|
23
|
+
* - 1.11-S1(D-33 / 计量设计 §3.2):usage 采集——openai 请求体加 stream_options.include_usage
|
|
24
|
+
* (默认开启,第三方网关不兼容时回退 = baseUrl 白名单,登记可回退项;缺失走缺失口径禁估算),
|
|
25
|
+
* 解析「先摘 evt.usage 再判 choice」(空 choices 尾包不再被吞);anthropic message_start(input 侧)
|
|
26
|
+
* + message_delta(output 侧)两包合并(message_delta 缺席 = output 不完整,整条不落);
|
|
27
|
+
* 归一口径 §1.1(openai 减法 / anthropic 透传 + cache_creation),畸形/缺失整条不落(禁补造)。
|
|
23
28
|
*/
|
|
24
29
|
import { CarM8Error, providerUnreachable } from './errors.ts'
|
|
25
30
|
import { CredentialService } from './credentials.ts'
|
|
26
|
-
import type { Disposable, LlmAdapter, LlmChunk, LlmRequest, ToolCallDelta } from './types.ts'
|
|
31
|
+
import type { Disposable, LlmAdapter, LlmChunk, LlmRequest, LlmUsage, ToolCallDelta } from './types.ts'
|
|
27
32
|
import type { PluginContext } from '../kernel/context.ts'
|
|
28
33
|
import { request as httpRequest } from 'node:http'
|
|
29
34
|
import { request as httpsRequest } from 'node:https'
|
|
@@ -279,8 +284,30 @@ const FINISH_MAP: Record<string, LlmChunk['finishReason']> = {
|
|
|
279
284
|
|
|
280
285
|
class Retryable extends Error {}
|
|
281
286
|
|
|
282
|
-
/**
|
|
283
|
-
|
|
287
|
+
/**
|
|
288
|
+
* 1.11-S1(D-33 / 计量设计 §1.1):openai usage 归一——prompt_tokens 含 cached,inputTokens
|
|
289
|
+
* 必须做减法(prompt − prompt_tokens_details.cached_tokens);cache_creation 恒 0(无对应字段,
|
|
290
|
+
* 登记不估算)。usage 对象缺席/畸形(两侧主字段任一非数值)= 整条不落(缺失口径,禁补造)。
|
|
291
|
+
*/
|
|
292
|
+
function normalizeOpenAIUsage(u: unknown, model: string, adapterId: string): LlmUsage | undefined {
|
|
293
|
+
if (!u || typeof u !== 'object') return undefined
|
|
294
|
+
const r = u as Record<string, unknown>
|
|
295
|
+
if (typeof r.prompt_tokens !== 'number' || typeof r.completion_tokens !== 'number') return undefined
|
|
296
|
+
const details = (r.prompt_tokens_details ?? null) as Record<string, unknown> | null
|
|
297
|
+
const cached = details && typeof details.cached_tokens === 'number' ? details.cached_tokens : 0
|
|
298
|
+
return {
|
|
299
|
+
inputTokens: r.prompt_tokens - cached,
|
|
300
|
+
outputTokens: r.completion_tokens,
|
|
301
|
+
cacheReadTokens: cached,
|
|
302
|
+
cacheCreationTokens: 0,
|
|
303
|
+
model,
|
|
304
|
+
adapterId,
|
|
305
|
+
providerRaw: u,
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** 明文 SSE → LlmChunk 流解析(增量容错:半行驻留到下一 chunk;1.11-S1:model/adapterId 供 usage 归一) */
|
|
310
|
+
async function* parseSseStream(body: AsyncIterable<Uint8Array>, model: string, adapterId: string): AsyncIterable<LlmChunk> {
|
|
284
311
|
const decoder = new TextDecoder()
|
|
285
312
|
let buf = ''
|
|
286
313
|
for await (const bytes of body) {
|
|
@@ -295,8 +322,15 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>): AsyncIterable<L
|
|
|
295
322
|
if (!data) continue
|
|
296
323
|
let evt: any
|
|
297
324
|
try { evt = JSON.parse(data) } catch { continue } // 心跳/注释行容错(显式跳过非 JSON data)
|
|
325
|
+
// 1.11-S1(D-33 / 计量设计 §3.2 必核行):先摘 usage 再判 choice——stream_options.include_usage
|
|
326
|
+
// 的尾包 choices 为空,下方 `if (!choice) continue` 原样会把它吞掉
|
|
327
|
+
const usage = evt.usage !== undefined ? normalizeOpenAIUsage(evt.usage, model, adapterId) : undefined
|
|
298
328
|
const choice = evt.choices?.[0]
|
|
299
|
-
if (!choice)
|
|
329
|
+
if (!choice) {
|
|
330
|
+
// 独立 usage 尾包(官方形态:finish_reason chunk 之后、[DONE] 之前)——请求最后一个 chunk
|
|
331
|
+
if (usage) yield { usage }
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
300
334
|
const delta = choice.delta ?? {}
|
|
301
335
|
const raw = choice.finish_reason
|
|
302
336
|
const finish: LlmChunk['finishReason'] | undefined = raw == null ? undefined : (() => {
|
|
@@ -315,26 +349,34 @@ async function* parseSseStream(body: AsyncIterable<Uint8Array>): AsyncIterable<L
|
|
|
315
349
|
: []
|
|
316
350
|
const text = typeof delta.content === 'string' && delta.content.length ? delta.content : undefined
|
|
317
351
|
// LlmChunk 单 toolCallDelta 口径:同事件多条 tool_calls 顺序展开为多 chunk
|
|
318
|
-
if (!text && !toolCallDeltas.length && finish === undefined)
|
|
352
|
+
if (!text && !toolCallDeltas.length && finish === undefined) {
|
|
353
|
+
// 空事件但携 usage(部分 compat 网关形态):usage 不随空事件丢失
|
|
354
|
+
if (usage) yield { usage }
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
const chunks: LlmChunk[] = []
|
|
319
358
|
if (toolCallDeltas.length === 0) {
|
|
320
|
-
|
|
359
|
+
chunks.push({
|
|
321
360
|
...(text !== undefined ? { delta: text } : {}),
|
|
322
361
|
...(finish !== undefined
|
|
323
362
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
324
363
|
: {}),
|
|
325
|
-
}
|
|
364
|
+
})
|
|
326
365
|
} else {
|
|
327
366
|
for (let i = 0; i < toolCallDeltas.length; i++) {
|
|
328
367
|
const last = i === toolCallDeltas.length - 1
|
|
329
|
-
|
|
368
|
+
chunks.push({
|
|
330
369
|
...(i === 0 && text !== undefined ? { delta: text } : {}),
|
|
331
370
|
toolCallDelta: toolCallDeltas[i],
|
|
332
371
|
...(last && finish !== undefined
|
|
333
372
|
? { finishReason: finish, ...(finish === 'error' ? { error: { code: 'B080001', message: `未映射的 finish_reason "${raw}"` } } : {}) }
|
|
334
373
|
: {}),
|
|
335
|
-
}
|
|
374
|
+
})
|
|
336
375
|
}
|
|
337
376
|
}
|
|
377
|
+
// 1.11-S1(D-33):usage 与事件内容同 chunk 到达(网关共存形态)→ 附着于本事件最后一个 chunk
|
|
378
|
+
if (usage) chunks[chunks.length - 1]!.usage = usage
|
|
379
|
+
for (const c of chunks) yield c
|
|
338
380
|
}
|
|
339
381
|
}
|
|
340
382
|
}
|
|
@@ -364,6 +406,9 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
364
406
|
body: JSON.stringify({
|
|
365
407
|
model: req.model,
|
|
366
408
|
stream: true,
|
|
409
|
+
// 1.11-S1(D-33 / 计量设计 §9-2 推荐A):usage 采集默认开启;第三方网关不兼容时的
|
|
410
|
+
// 回退方案(baseUrl 白名单)登记可回退项——不支持网关走缺失口径(禁估算补造)
|
|
411
|
+
stream_options: { include_usage: true },
|
|
367
412
|
...(req.maxTokens != null ? { max_tokens: req.maxTokens } : {}),
|
|
368
413
|
messages: req.messages.map(m => {
|
|
369
414
|
if (m.role === 'toolResult') {
|
|
@@ -402,7 +447,7 @@ export function createOpenAICompatAdapter(opts: OpenAICompatOptions): LlmAdapter
|
|
|
402
447
|
fetchImpl,
|
|
403
448
|
signal: req.signal,
|
|
404
449
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
405
|
-
}, parseSseStream)
|
|
450
|
+
}, (body) => parseSseStream(body, req.model, id))
|
|
406
451
|
},
|
|
407
452
|
}
|
|
408
453
|
}
|
|
@@ -436,6 +481,31 @@ const ANTHROPIC_STOP_REASON_MAP: Record<string, LlmChunk['finishReason']> = {
|
|
|
436
481
|
refusal: 'aborted',
|
|
437
482
|
}
|
|
438
483
|
|
|
484
|
+
/**
|
|
485
|
+
* 1.11-S1(D-33 / 计量设计 §3.2):anthropic usage 两包合并——message_start(input 侧:
|
|
486
|
+
* input_tokens 天然不含 cache + cache_read/cache_creation)+ message_delta(output 侧:
|
|
487
|
+
* output_tokens)。任一包缺席或主字段非数值 = 侧不完整,整条不落(缺失口径,禁补造半条事实);
|
|
488
|
+
* providerRaw 保留两原始片段供审计对拍。
|
|
489
|
+
*/
|
|
490
|
+
function mergeAnthropicUsage(
|
|
491
|
+
start: Record<string, unknown> | null,
|
|
492
|
+
delta: Record<string, unknown> | null,
|
|
493
|
+
model: string,
|
|
494
|
+
adapterId: string,
|
|
495
|
+
): LlmUsage | undefined {
|
|
496
|
+
if (!start || !delta) return undefined
|
|
497
|
+
if (typeof start.input_tokens !== 'number' || typeof delta.output_tokens !== 'number') return undefined
|
|
498
|
+
return {
|
|
499
|
+
inputTokens: start.input_tokens,
|
|
500
|
+
outputTokens: delta.output_tokens,
|
|
501
|
+
cacheReadTokens: typeof start.cache_read_input_tokens === 'number' ? start.cache_read_input_tokens : 0,
|
|
502
|
+
cacheCreationTokens: typeof start.cache_creation_input_tokens === 'number' ? start.cache_creation_input_tokens : 0,
|
|
503
|
+
model,
|
|
504
|
+
adapterId,
|
|
505
|
+
providerRaw: { messageStart: start, messageDelta: delta },
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
439
509
|
/**
|
|
440
510
|
* Anthropic Messages API 请求体(D-20):system 消息提升为顶层 `system`(多条 \n\n 连接);
|
|
441
511
|
* toolResult 收敛进 user 消息 content(连续多条合并为单 user 多 tool_result 块——Anthropic
|
|
@@ -499,10 +569,13 @@ function toAnthropic(req: LlmRequest, apiKey: string) {
|
|
|
499
569
|
* Anthropic SSE → LlmChunk 流解析(data-only:data JSON 自带 `type` 字段,`event:` 行不依赖)。
|
|
500
570
|
* content_block_start(tool_use) → toolCallDelta{id,name};input_json_delta → toolCallDelta{argumentsDelta};
|
|
501
571
|
* message_delta.stop_reason → finishReason(终态信号,单次);ping/message_stop/未知事件容错跳过。
|
|
572
|
+
* 1.11-S1(D-33):message_start.usage 采集 input 侧(原解析整体跳过该事件)+ message_delta.usage
|
|
573
|
+
* 合并 output 侧附着于终态 chunk;流中断 delta 未到 = output 侧不完整,整条不落(R-2)。
|
|
502
574
|
*/
|
|
503
|
-
async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array
|
|
575
|
+
async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>, model: string, adapterId: string): AsyncIterable<LlmChunk> {
|
|
504
576
|
const decoder = new TextDecoder()
|
|
505
577
|
let buf = ''
|
|
578
|
+
let startUsage: Record<string, unknown> | null = null
|
|
506
579
|
for await (const bytes of body) {
|
|
507
580
|
buf += decoder.decode(bytes, { stream: true })
|
|
508
581
|
let nl: number
|
|
@@ -528,13 +601,23 @@ async function* parseAnthropicSseStream(body: AsyncIterable<Uint8Array>): AsyncI
|
|
|
528
601
|
yield { toolCallDelta: { index: evt.index, argumentsDelta: d.partial_json } }
|
|
529
602
|
}
|
|
530
603
|
// thinking_delta / citations_delta 等非 CAR 消费面:容错跳过
|
|
604
|
+
} else if (type === 'message_start') {
|
|
605
|
+
// 1.11-S1(D-33):input 侧采集(usage 嵌套在 message 下)
|
|
606
|
+
const u = (evt.message as Record<string, unknown> | undefined)?.usage
|
|
607
|
+
if (u && typeof u === 'object') startUsage = u as Record<string, unknown>
|
|
531
608
|
} else if (type === 'message_delta') {
|
|
532
609
|
const raw = evt.delta?.stop_reason
|
|
610
|
+
// 1.11-S1(D-33):output 侧(delta.usage.output_tokens)与 input 侧合并——两包齐才落
|
|
611
|
+
const usage = mergeAnthropicUsage(startUsage, (evt.usage ?? null) as Record<string, unknown> | null, model, adapterId)
|
|
533
612
|
if (raw != null) {
|
|
534
613
|
const finish = ANTHROPIC_STOP_REASON_MAP[String(raw)]
|
|
535
|
-
|
|
614
|
+
const chunk: LlmChunk = finish
|
|
536
615
|
? { finishReason: finish }
|
|
537
616
|
: { finishReason: 'error', error: { code: 'B080001', message: `未映射的 stop_reason "${raw}"` } }
|
|
617
|
+
yield usage ? { ...chunk, usage } : chunk
|
|
618
|
+
} else if (usage) {
|
|
619
|
+
// delta 未携 stop_reason 但携 usage(网关形态):usage 不随缺失终态信号丢失
|
|
620
|
+
yield { usage }
|
|
538
621
|
}
|
|
539
622
|
} else if (type === 'error') {
|
|
540
623
|
// 流内 error 事件(overloaded_error 等)→ error chunk 收口(后续 message_delta 由守卫拦冲突)
|
|
@@ -575,7 +658,7 @@ export function createAnthropicAdapter(opts: AnthropicAdapterOptions): LlmAdapte
|
|
|
575
658
|
fetchImpl,
|
|
576
659
|
signal: req.signal,
|
|
577
660
|
timeoutMs, firstByteMs, retries, backoffMs, sleep,
|
|
578
|
-
}, parseAnthropicSseStream)
|
|
661
|
+
}, (body) => parseAnthropicSseStream(body, req.model, id))
|
|
579
662
|
},
|
|
580
663
|
}
|
|
581
664
|
}
|
|
@@ -36,14 +36,35 @@ export interface ToolCallDelta {
|
|
|
36
36
|
argumentsDelta?: string
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 1.11-S1(D-33 / 计量设计 §3.1):token 四分量归一化用量——跨 provider 可加总的库内口径
|
|
41
|
+
* (§1.1:openai prompt_tokens 含 cached 必须减法归一;anthropic input_tokens 天然不含 cache)。
|
|
42
|
+
* provider 原始 usage 整体存 providerRaw 供审计对拍(对拍恒以 provider_raw 为准)。
|
|
43
|
+
*/
|
|
44
|
+
export interface LlmUsage {
|
|
45
|
+
/** 非缓存输入 token(归一化后) */
|
|
46
|
+
inputTokens: number
|
|
47
|
+
outputTokens: number
|
|
48
|
+
cacheReadTokens: number
|
|
49
|
+
/** openai 系无对应字段,恒 0(登记不估算) */
|
|
50
|
+
cacheCreationTokens: number
|
|
51
|
+
/** 实际请求模型(req.model) */
|
|
52
|
+
model: string
|
|
53
|
+
adapterId: string
|
|
54
|
+
/** provider 原始 usage 对象(anthropic 为 messageStart/messageDelta 两原始片段) */
|
|
55
|
+
providerRaw: unknown
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
/** 流式块(finishReason 一经产生不可被任何中间层改写) */
|
|
40
59
|
export interface LlmChunk {
|
|
41
60
|
delta?: string
|
|
42
61
|
toolCallDelta?: ToolCallDelta
|
|
43
62
|
finishReason?: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'
|
|
44
|
-
// —— 规格外加法扩展(登记:M8 收口文档 §4)——
|
|
63
|
+
// —— 规格外加法扩展(登记:M8 收口文档 §4 / 计量设计 §3.1,1.11.0)——
|
|
45
64
|
/** finishReason='error' 时的结构化原因(消费方落 turnEnd meta,不进模型消息流) */
|
|
46
65
|
error?: { code: string; message: string }
|
|
66
|
+
/** 1.11-S1(D-33):每次请求最后一个 chunk 附带归一化用量;缺失 = provider 未回,禁补造 */
|
|
67
|
+
usage?: LlmUsage
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
/** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
|
package/src/seam.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KernelSeam 公开导出面(1.10.0 加法——D16 T-6 冻结面口径:本文件即内核库形态的 semver 承诺面)
|
|
3
|
+
*
|
|
4
|
+
* 消费方:CAR 平台层(KernelSeam 适配器);约定:
|
|
5
|
+
* - 仅暴露进程内挂接所需的最窄面(装配/会话/turn/遥测),禁止透出内核内部模块路径语义;
|
|
6
|
+
* - 破坏性变更须内核 major;加法演进走 minor 并在本文件头登记;
|
|
7
|
+
* - 语义不变:runTurn/chatStep/SessionLog 即 CLI 与测试承载的同一实现(非转发壳)。
|
|
8
|
+
*
|
|
9
|
+
* 加法登记:1.11.0 —— AuthzService/checkCapability 出面(平台确认桥幂等决策 + 会话策略直判承载;F4 US-3 语义同源)。
|
|
10
|
+
*/
|
|
11
|
+
export { SessionLog, type SessionEvent, type EventKind, type Actor } from './session/log.ts'
|
|
12
|
+
export { SessionFileStore } from './session/store.ts'
|
|
13
|
+
export { runTurn, type TurnEndReason, type TurnPreset, type TurnResult } from './loop/stop.ts'
|
|
14
|
+
export { chatStep } from './runtime-core/chatStep.ts'
|
|
15
|
+
export { RuntimeCore, createOpenAICompatAdapter, createAnthropicAdapter } from './runtime-core/llm.ts'
|
|
16
|
+
export { CredentialService } from './runtime-core/credentials.ts'
|
|
17
|
+
export type { LlmAdapter, LlmRequest, LlmChunk } from './runtime-core/types.ts'
|
|
18
|
+
export { mountPlugin } from './load/loader.ts'
|
|
19
|
+
export { Context } from './kernel/context.ts'
|
|
20
|
+
export { loadCarConfig, mergeLlmConfig, mergeSignatureGate, warmCarConfig, type CarConfig, type LlmConfig } from './load/config.ts'
|
|
21
|
+
export { probeCapabilities, SandboxExecutor } from './sandbox/sandbox.ts'
|
|
22
|
+
export { AuthzService, checkCapability, type Decision, type AuthzRequest, type AuthzDecision, type DeclaredCapabilities } from './authz/authz.ts'
|
|
23
|
+
export { createTelemetryFacade, telemetryConfigFromEnv } from './runtime-core/telemetry.ts'
|
|
24
|
+
export { TurnTracer, type TurnSpanHandle } from './runtime-core/trace.ts'
|
package/src/session/log.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto'
|
|
|
11
11
|
import { readFileSync } from 'node:fs'
|
|
12
12
|
import { decodeLogBuffer, splitJsonlLines } from './format.ts'
|
|
13
13
|
|
|
14
|
-
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 =
|
|
14
|
+
export type EventKind = 'user' | 'assistant' | 'toolCall' | 'toolResult' | 'turnEnd' | 'goalUpdate' | 'hostRaw' | 'fork' | 'system' | 'usage' // M3-S11 加法扩展:多宿主归一化降级通道(pattern 未命中留痕,零静默);M5-DEC4 加法扩展:跨宿主 fork 标记事件;1.4-S2 加法扩展:system prompt 落链(§3.5.5:新模型可见输入 = 新事件类型);1.11-S2 加法扩展:usage 计量事实(D-33,actor='runtime'、model_visible=0 不进模型消息流,deriveMessages 不投影);六值 TurnEndReason 不变
|
|
15
15
|
export type Actor = 'user' | 'model' | 'plugin' | 'runtime'
|
|
16
16
|
export interface SessionEvent {
|
|
17
17
|
seq: number
|
package/.gitattributes
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# 行尾纪律:Windows runner checkout 默认 autocrlf=true 会把文本文件打成 CRLF,
|
|
2
|
-
# WSL 内 bash 脚本遇 \r 即炸(run 35314482866:set: pipefail\r invalid option)。
|
|
3
|
-
* text=auto
|
|
4
|
-
*.sh text eol=lf
|
|
5
|
-
*.c text eol=lf
|
|
6
|
-
*.h text eol=lf
|
|
7
|
-
Makefile text eol=lf
|
|
8
|
-
*.md text eol=lf
|
|
9
|
-
*.yml text eol=lf
|
|
10
|
-
*.json text eol=lf
|
|
11
|
-
*.ts text eol=lf
|