@codehz/ai 0.4.6 → 0.7.1

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.
Files changed (46) hide show
  1. package/README.md +221 -75
  2. package/dist/index.d.mts +670 -523
  3. package/dist/index.mjs +3677 -2207
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +19 -8
  6. package/.github/workflows/publish.yml +0 -56
  7. package/.oxfmtrc.json +0 -12
  8. package/.oxlintrc.json +0 -34
  9. package/AGENTS.md +0 -37
  10. package/src/adapters/chat-completions.ts +0 -624
  11. package/src/adapters/index.ts +0 -44
  12. package/src/adapters/messages.ts +0 -635
  13. package/src/adapters/mock.ts +0 -934
  14. package/src/adapters/ollama.ts +0 -526
  15. package/src/adapters/responses.ts +0 -818
  16. package/src/core/aggregator.ts +0 -428
  17. package/src/core/client.ts +0 -36
  18. package/src/core/collect-stream.ts +0 -19
  19. package/src/core/errors.ts +0 -105
  20. package/src/core/event-factory.ts +0 -151
  21. package/src/core/index.ts +0 -18
  22. package/src/core/merge-auxiliary.ts +0 -22
  23. package/src/core/normalize.ts +0 -65
  24. package/src/core/validation.ts +0 -404
  25. package/src/helpers/adapter-auxiliary.ts +0 -155
  26. package/src/helpers/adapter-base.ts +0 -218
  27. package/src/helpers/adapter-security.ts +0 -126
  28. package/src/helpers/auxiliary-collector.ts +0 -166
  29. package/src/helpers/incremental-stream-parser.ts +0 -142
  30. package/src/helpers/index.ts +0 -87
  31. package/src/helpers/mapping.ts +0 -192
  32. package/src/helpers/provider-request-options.ts +0 -25
  33. package/src/helpers/provider-stream.ts +0 -147
  34. package/src/helpers/reasoning-level.ts +0 -86
  35. package/src/helpers/request-mapper.ts +0 -94
  36. package/src/helpers/synthetic-stream.ts +0 -188
  37. package/src/helpers/usage-mapping.ts +0 -110
  38. package/src/index.ts +0 -17
  39. package/src/types/adapter.ts +0 -42
  40. package/src/types/content.ts +0 -15
  41. package/src/types/events.ts +0 -138
  42. package/src/types/index.ts +0 -49
  43. package/src/types/items.ts +0 -57
  44. package/src/types/request.ts +0 -52
  45. package/src/types/response.ts +0 -68
  46. package/tsdown.config.ts +0 -10
package/README.md CHANGED
@@ -1,6 +1,48 @@
1
1
  # @codehz/ai
2
2
 
3
- 统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的回调驱动 `MockAdapter`(`responses` / `messages` / `chat-completions` / `ollama` / `mock`)。
3
+ 统一流式 AI 客户端,提供一套 canonical API,对接真实模型后端与面向测试的回调驱动 `MockAdapter`(`responses` / `messages` / `chat-completions` / `ollama` / `gemini` / `mock`)。
4
+
5
+ ## 0.6.0 迁移说明
6
+
7
+ 小 breaking:warnings 形状、tool_call 校验与 Chat Completions opaque 入站。
8
+
9
+ ### `warnings` 结构化
10
+
11
+ `AIResponse.warnings` / `response.warning` / `response.completed.warnings` 由 `string[]` 改为:
12
+
13
+ ```ts
14
+ type StreamWarning = { message: string; code?: WarningCode };
15
+ // AIResponse.warnings?: StreamWarning[]
16
+ ```
17
+
18
+ 迁移:原先 `for (const w of response.warnings ?? []) console.log(w)` 改为读取 `w.message`(及可选 `w.code`)。
19
+
20
+ ### `tool_call.argumentsText` 校验
21
+
22
+ 运行时 `validateRequest` **不再**对 `tool_call.argumentsText` 做 `JSON.parse`;只校验为 `string`。object-wire adapter(ollama / messages / gemini)在入站映射(`parseToolArguments` / `parseJsonStrictObject`)时若参数不是合法 JSON object,抛 `AIRecoverableError`(`TOOL_CALL_ARGUMENTS_INVALID`):`AdapterBase` soft-complete 为 `response.warning` + `response.completed`(`stopReason: "error"`),不向调用方抛错、也不发 provider HTTP。chat-completions / responses 仍按字符串透传。
23
+
24
+ ### Chat Completions opaque
25
+
26
+ 入站 opaque 仅接受 `messages` 形(`{ messages: ChatMessage[] }`)。单条 `{ role, content }` 已 **deprecate**(有效 envelope 下未识别 shape 会被跳过,不再当完整 assistant turn 还原)。出站仍写 `messages: [assistantReplayMessage]`。
27
+
28
+ ### 其余行为(0.6 一并落地,多为兼容增强)
29
+
30
+ - HTTP adapter 错误码与 incomplete 流完成路径收敛;Chat Completions 支持 arguments-before-id 与 `function_call`/`tool_calls` 互斥 warning。
31
+ - Aggregator 在 finalize 时从 `output` 派生 `text` / `toolCalls` / `serverTool*`;`argumentsText` 分块累积。
32
+ - 四家厚 adapter 拆为 `map-request` / `map-stream`(对齐 `responses`);opaque 恒尾置。
33
+
34
+ ## 0.5.0 迁移说明(摘要)
35
+
36
+ `0.5.0` 收紧了根入口公开面:`AdapterBase`、`createEventFactory`、`aggregateEvents`、`normalizeRequest`、transport / `syntheticStream` 等**不再**从 `@codehz/ai` 根导出(内部模块)。
37
+
38
+ **仍从根导出:** `createAIClient`、`collectStream`、错误类型与 `WarningCode`、全部 adapters 与 Mock 夹具、canonical 构造、`REASONING_LEVELS`,以及 canonical 类型。自定义 adapter 请实现 `BackendAdapter`。
39
+
40
+ ### 错误通道语义
41
+
42
+ - `AIRequestError` / `AIProviderError` / `AIStreamError`:致命,同步或在异步迭代中抛出,不伪造 `response.completed`。
43
+ - `AIMappingError`:由内部 `AdapterBase` 捕获后降级为 `response.warning`(`WarningCode.MAPPING_ERROR`)+ 空 output 的 `response.completed`(无 `stopReason`);生产 adapter 原则上不抛。
44
+ - `AIRecoverableError`:由内部 `AdapterBase` 捕获后 soft-complete 为 `response.warning`(code 取自错误)+ 空 replay 的 `response.completed`(`stopReason` 默认 `"error"`)。用于可清理历史后重试的回合失败(如 object-wire 入站非法 `tool_call.argumentsText`)。
45
+ - 非致命差异走 `response.warning` 与 `WarningCode`(0.6 起为结构化对象)。
4
46
 
5
47
  ## 安装
6
48
 
@@ -41,8 +83,9 @@ for await (const event of stream) {
41
83
  type AIRequest = {
42
84
  instructions?: string | InstructionBlock[]; // 系统级指令
43
85
  input: InputItem[]; // 输入 items
44
- tools?: ToolDefinition[]; // 工具声明
45
- toolChoice?: ToolChoice; // 工具选择策略
86
+ tools?: ToolDefinition[]; // 客户端函数工具(由调用方执行)
87
+ serverTools?: ServerToolDefinition[]; // Provider 托管工具(web_search / code_execution / mcp)
88
+ toolChoice?: ToolChoice; // 客户端工具选择策略
46
89
  temperature?: number; // 温度 (0–2)
47
90
  maxOutputTokens?: number; // 最大输出 token
48
91
  reasoningLevel?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; // 可移植思考力度
@@ -50,25 +93,31 @@ type AIRequest = {
50
93
  };
51
94
  ```
52
95
 
96
+ `tools` 与 `serverTools` 可在同一请求中共存。客户端 `tool_call` / 手动 tool-loop 语义不变;服务端工具由 provider 在请求内执行,**不会**把 `stopReason` 设为 `tool_call`。
97
+
53
98
  `reasoningLevel` 是 portable 枚举,由各 adapter 映射到 provider 原生字段;未设置时不写相关 wire 字段。adapter 无法映射的 level(如 Ollama 的 `minimal` / `xhigh` / `max`)会抛 `AIRequestError`(`UNSUPPORTED_REASONING_LEVEL`)。需要 budget / summary 等特化参数时,仍可用构造期 `extraBody` 覆盖同名顶层键。
54
99
 
55
- | Adapter | 映射 |
56
- | --- | --- |
57
- | `ResponsesAdapter` | `reasoning: { effort }` |
58
- | `ChatCompletionsAdapter` | 顶层 `reasoning_effort` |
59
- | `MessagesAdapter` | `thinking: { type: "disabled" }` 或 `{ type: "enabled", budget_tokens }`(由 `maxOutputTokens` 按比例推导,默认 4096) |
60
- | `OllamaAdapter` | `think: false \| "low" \| "medium" \| "high"` |
61
- | `MockAdapter` | 透传到 `MockHandlerContext.reasoningLevel` |
100
+ | Adapter | 映射 |
101
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
102
+ | `ResponsesAdapter` | `reasoning: { effort }` |
103
+ | `ChatCompletionsAdapter` | 顶层 `reasoning_effort` |
104
+ | `MessagesAdapter` | `thinking: { type: "disabled" }` 或 `{ type: "enabled", budget_tokens }`(由 `maxOutputTokens` 按比例推导,默认 4096) |
105
+ | `OllamaAdapter` | `think: false \| "low" \| "medium" \| "high"` |
106
+ | `GeminiAdapter` | `generationConfig.thinkingConfig`(`none` 关闭 thoughts;`minimal`/`low`/`medium`/`high` → `thinkingLevel`;`xhigh`/`max` 不支持) |
107
+ | `MockAdapter` | 透传到 `MockHandlerContext.reasoningLevel` |
62
108
 
63
109
  `input` 是 item 数组,每个 item 可以是:
64
110
 
65
- | Item 类型 | 用途 |
66
- | ------------- | ----------------------------------- |
67
- | `message` | 用户 / 助手消息 |
68
- | `reasoning` | 思维链(输入侧 replay) |
69
- | `tool_call` | 模型发起的工具调用(输入侧 replay) |
70
- | `tool_result` | 工具执行结果 |
71
- | `opaque` | Provider 私有续接材料 |
111
+ | Item 类型 | 用途 |
112
+ | ----------------------- | ----------------------------------- |
113
+ | `message` | 用户 / 助手消息(可带 `citations`) |
114
+ | `reasoning` | 思维链(输入侧 replay) |
115
+ | `tool_call` | 客户端工具调用(输入侧 replay) |
116
+ | `tool_result` | 客户端工具执行结果 |
117
+ | `server_tool_call` | Provider 托管工具调用 |
118
+ | `server_tool_result` | Provider 托管工具结果 |
119
+ | `server_tool_discovery` | MCP 等远端工具发现列表 |
120
+ | `opaque` | Provider 私有续接材料 |
72
121
 
73
122
  ### 统一事件流
74
123
 
@@ -80,18 +129,24 @@ response.started → (item.started → item.delta* → item.completed)* → resp
80
129
 
81
130
  事件类型:
82
131
 
83
- | 事件 | 含义 |
84
- | ------------------------------------- | ------------------------------------------- |
85
- | `response.started` | 响应开始 |
86
- | `message.{started,delta,completed}` | 消息输出 |
87
- | `reasoning.{started,delta,completed}` | 思维链 |
88
- | `tool_call.{started,delta,completed}` | 工具调用 |
89
- | `response.warning` | 非致命警告 |
90
- | `response.auxiliary` | usage / billing 辅助信息 |
91
- | `response.completed` | 响应结束,携带 replay、终止原因及最终元数据 |
132
+ | 事件 | 含义 |
133
+ | --------------------------------------- | ------------------------------------------- |
134
+ | `response.started` | 响应开始 |
135
+ | `message.{started,delta,completed}` | 消息输出(`completed` 可带 `citations`) |
136
+ | `reasoning.{started,delta,completed}` | 思维链 |
137
+ | `tool_call.{started,delta,completed}` | 客户端工具调用 |
138
+ | `server_tool.{started,delta,completed}` | 服务端工具调用 |
139
+ | `server_tool_result.completed` | 服务端工具结果(原子) |
140
+ | `server_tool_discovery.completed` | MCP 工具发现(原子) |
141
+ | `response.warning` | 非致命警告 |
142
+ | `response.auxiliary` | usage / billing 辅助信息 |
143
+ | `response.completed` | 响应结束,携带 replay、终止原因及最终元数据 |
92
144
 
93
145
  ### 统一终结结果
94
146
 
147
+ **`AIResponse` 的完整形态以 `collectStream()` / 事件聚合器为唯一真相源。**
148
+ 流式消费者读 `AIStreamEvent`;`response.completed` 只携带 `replay`、终止原因、usage/billing 等元数据,**不**携带完整 `output`/`text`/`toolCalls` 账本。
149
+
95
150
  流结束后可通过 `collectStream()` 聚合为 `AIResponse`:
96
151
 
97
152
  ```ts
@@ -106,18 +161,20 @@ console.log(response.replay); // 续接材料
106
161
 
107
162
  `AIResponse` 包含:
108
163
 
109
- | 字段 | 类型 | 说明 |
110
- | ------------ | ---------------- | ------------------------- |
111
- | `output` | `OutputItem[]` | 当前轮输出 |
112
- | `replay` | `ReplayItem[]` | 续接材料(下次请求带回) |
113
- | `text` | `string` | 全部文本拼接 |
114
- | `toolCalls` | `ToolCallItem[]` | 工具调用 |
115
- | `stopReason` | `StopReason?` | 终止原因(可选) |
116
- | `usage` | `Usage?` | token 统计(可选) |
117
- | `billing` | `BillingInfo?` | 计费信息(可选) |
118
- | `auxiliary` | `AuxiliaryInfo?` | Provider 辅助信息(可选) |
119
- | `warnings` | `string[]?` | 非致命警告(可选) |
120
- | `backend` | `BackendTrace` | 调用链路元数据 |
164
+ | 字段 | 类型 | 说明 |
165
+ | ------------------- | ------------------------ | ---------------------------------- |
166
+ | `output` | `OutputItem[]` | 当前轮输出 |
167
+ | `replay` | `ReplayItem[]` | 续接材料(下次请求带回) |
168
+ | `text` | `string` | 全部文本拼接 |
169
+ | `toolCalls` | `ToolCallItem[]` | 客户端工具调用 |
170
+ | `serverToolCalls` | `ServerToolCallItem[]` | 服务端工具调用 |
171
+ | `serverToolResults` | `ServerToolResultItem[]` | 服务端工具结果 |
172
+ | `stopReason` | `StopReason?` | 终止原因(可选) |
173
+ | `usage` | `Usage?` | token 统计(可选) |
174
+ | `billing` | `BillingInfo?` | 计费信息(可选) |
175
+ | `auxiliary` | `AuxiliaryInfo?` | Provider 辅助信息(可选) |
176
+ | `warnings` | `StreamWarning[]?` | 非致命警告(`{ message; code? }`) |
177
+ | `backend` | `BackendTrace` | 调用链路元数据 |
121
178
 
122
179
  流式 `message.delta` / `reasoning.delta` 保持后端分片粒度;完成态 `output` 中的
123
180
  `message` / `reasoning` 会合并相邻 `text` content blocks(直接拼接且不添加分隔符),
@@ -125,13 +182,14 @@ console.log(response.replay); // 续接材料
125
182
 
126
183
  ## 后端 Adapter
127
184
 
128
- | Adapter | 类 | 说明 |
129
- | ----------------------- | ------------------------ | ----------------------- |
130
- | OpenAI Responses API | `ResponsesAdapter` | OpenAI Responses 端点 |
131
- | Anthropic Messages API | `MessagesAdapter` | Anthropic Messages 端点 |
132
- | OpenAI Chat Completions | `ChatCompletionsAdapter` | Chat Completions 端点 |
133
- | Ollama Chat API | `OllamaAdapter` | 本地或自托管 Ollama |
134
- | Scripted Test Backend | `MockAdapter` | 脚本化测试夹具 |
185
+ | Adapter | 类 | 说明 |
186
+ | ----------------------- | ------------------------ | ------------------------------ |
187
+ | OpenAI Responses API | `ResponsesAdapter` | OpenAI Responses 端点 |
188
+ | Anthropic Messages API | `MessagesAdapter` | Anthropic Messages 端点 |
189
+ | OpenAI Chat Completions | `ChatCompletionsAdapter` | Chat Completions 端点 |
190
+ | Ollama Chat API | `OllamaAdapter` | 本地或自托管 Ollama |
191
+ | Google Gemini API | `GeminiAdapter` | Gemini `streamGenerateContent` |
192
+ | Scripted Test Backend | `MockAdapter` | 脚本化测试夹具 |
135
193
 
136
194
  ```ts
137
195
  import {
@@ -139,6 +197,7 @@ import {
139
197
  MessagesAdapter,
140
198
  ChatCompletionsAdapter,
141
199
  OllamaAdapter,
200
+ GeminiAdapter,
142
201
  MockAdapter,
143
202
  withMockStreaming,
144
203
  } from "@codehz/ai";
@@ -172,6 +231,15 @@ const ollama = new OllamaAdapter({
172
231
  extraBody: { keep_alive: "10m" },
173
232
  });
174
233
 
234
+ // Google Gemini Developer API(原生 generateContent 流,非 OpenAI 兼容层)
235
+ const gemini = new GeminiAdapter({
236
+ apiKey: process.env.GEMINI_API_KEY!,
237
+ // 可选:代理 / Vertex 兼容端点
238
+ // baseUrl: "https://generativelanguage.googleapis.com/v1beta",
239
+ headers: { "X-Custom": "..." },
240
+ extraBody: { safetySettings: [] },
241
+ });
242
+
175
243
  // 面向测试的回调驱动 mock backend
176
244
  const mock = new MockAdapter({
177
245
  handler: withMockStreaming(
@@ -202,6 +270,49 @@ adapter.isSyntheticStream;
202
270
  响应级 `backend.isSyntheticStream` 使用同一标记;具体响应内容仍应从
203
271
  本次事件流、warning 和 `replay` 判断。
204
272
 
273
+ ### 上下文压缩(可选能力)
274
+
275
+ 部分 adapter 原生支持上下文压缩,通过独立接口暴露,**不**挂在 `BackendAdapter` / `AIClient` 上:
276
+
277
+ | Adapter | `supportsContextCompress` | 说明 |
278
+ | ------------------ | ------------------------- | ----------------------------------------------------------------------- |
279
+ | `ResponsesAdapter` | 是 | `POST /responses/compact`;结果为 opaque `compacted_window` |
280
+ | `MockAdapter` | 是 | 构造期可选 `compressHandler`;未配置则抛 `MOCK_COMPRESS_NOT_CONFIGURED` |
281
+ | 其余内置 adapter | 否 | 本期无客户端摘要 fallback |
282
+
283
+ ```ts
284
+ import { ResponsesAdapter, supportsContextCompress, createAIClient, collectStream } from "@codehz/ai";
285
+ import type { InputItem } from "@codehz/ai";
286
+
287
+ const adapter = new ResponsesAdapter({ apiKey: process.env.OPENAI_API_KEY! });
288
+ const client = createAIClient({ adapter, model: "gpt-5.1" });
289
+
290
+ let transcript: InputItem[] = [/* 多轮累积 */];
291
+
292
+ if (supportsContextCompress(adapter)) {
293
+ // 用 replay 替换旧 transcript(不要 append 全文)
294
+ const { replay } = await adapter.compress({
295
+ model: "gpt-5.1",
296
+ input: transcript,
297
+ });
298
+ transcript = [...replay];
299
+ }
300
+
301
+ transcript.push({
302
+ type: "message",
303
+ role: "user",
304
+ content: [{ type: "text", text: "继续上一任务" }],
305
+ });
306
+
307
+ const response = await collectStream(client.stream({ input: transcript }));
308
+ ```
309
+
310
+ 边界:
311
+
312
+ - 本期仅覆盖 **独立** `compress()`;不支持在 `stream` 请求里自动 `context_management` / `compact_threshold`。
313
+ - Anthropic Messages 的请求内 compaction 未接入(无独立端点,另期)。
314
+ - 调用方负责何时压缩与 transcript 替换;库不托管会话状态。
315
+
205
316
  ## Mock 后端
206
317
 
207
318
  `MockAdapter` 是一个面向测试的回调驱动 adapter,用来验证长流程工具调用、`replay` 续接和异常路径。
@@ -227,9 +338,10 @@ const handler = withMockStreaming(
227
338
 
228
339
  - handler 会拿到 `request` 和 `context`
229
340
  - `context` 内建 `previousReplay`、`pendingToolCalls`、`history`
230
- - handler 可脚本化发出 `message` / `reasoning` / `tool_call`
341
+ - handler 可脚本化发出 `message` / `reasoning` / `tool_call` / `server_tool_*`
342
+ - message step 可附带 `citations`
231
343
  - 可注入 `warning`、`content_filter`、transport interruption、provider-style error
232
- - 可用 `assertMockRequest()` 验证调用方是否把上一轮 `replay` 和当前 `tool_result` 正确带回
344
+ - 可用 `assertMockRequest()` 验证 `replay` / `tool_result` / `serverTools` 等期望
233
345
 
234
346
  ```ts
235
347
  import { assertMockRequest, createAIClient, MockAdapter } from "@codehz/ai";
@@ -300,7 +412,7 @@ yield { type: "interrupt" }; // 不发 response.completed,collectStream() 应
300
412
  ```
301
413
 
302
414
  ```ts
303
- yield { type: "warning", message: "content filtered by policy", code: "CONTENT_FILTERED" };
415
+ yield { type: "warning", message: "content filtered by policy", code: "CONTENT_FILTER" };
304
416
  yield { type: "complete", stopReason: "content_filter" };
305
417
  ```
306
418
 
@@ -320,6 +432,64 @@ const r2 = await collectStream(client.stream({ input: transcript }));
320
432
 
321
433
  详细示例见 [examples/multi-turn.ts](./examples/multi-turn.ts)。
322
434
 
435
+ ## 服务端工具(`serverTools`)
436
+
437
+ Provider 托管工具(不进客户端 tool loop)。首版由 `ResponsesAdapter` 落地:
438
+
439
+ | Canonical `serverTools` | Responses wire | 说明 |
440
+ | ----------------------- | -------------------------- | ---------------------------------------------- |
441
+ | `web_search` | `type: "web_search"` | 域名过滤、`userLocation`、`searchContextSize` |
442
+ | `code_execution` | `type: "code_interpreter"` | 仅 auto container(`memoryLimit` / `fileIds`) |
443
+ | `mcp` | `type: "mcp"` | 远程 MCP;**仅** `requireApproval: "never"` |
444
+
445
+ ```ts
446
+ const stream = client.stream({
447
+ input: [{ type: "message", role: "user", content: [{ type: "text", text: "杭州今天天气?" }] }],
448
+ serverTools: [
449
+ {
450
+ type: "web_search",
451
+ allowedDomains: ["example.com"],
452
+ searchContextSize: "low",
453
+ },
454
+ {
455
+ type: "code_execution",
456
+ container: { type: "auto", memoryLimit: "4g" },
457
+ },
458
+ {
459
+ type: "mcp",
460
+ serverLabel: "dmcp",
461
+ serverUrl: "https://dmcp-server.example/mcp",
462
+ requireApproval: "never",
463
+ // authorization 每请求由调用方重传;不会写入 opaque 回放
464
+ authorization: process.env.MCP_TOKEN,
465
+ },
466
+ ],
467
+ });
468
+
469
+ const result = await collectStream(stream);
470
+ console.log(result.serverToolCalls);
471
+ console.log(result.serverToolResults);
472
+ // 消息 citations(url / container_file)挂在 MessageItem.citations
473
+ ```
474
+
475
+ 支持矩阵:
476
+
477
+ | Adapter | `serverTools` |
478
+ | ---------------------------------------------------- | ---------------------------------------------------------------------- |
479
+ | `ResponsesAdapter` | 请求映射 + SSE 解析 |
480
+ | `MockAdapter` | 可脚本化产出 `server_tool_*` 事件与 citations |
481
+ | `ChatCompletions` / `Messages` / `Ollama` / `Gemini` | 传入非空 `serverTools` → `AIRequestError`(`UNSUPPORTED_SERVER_TOOL`) |
482
+
483
+ 范围说明(刻意不做):
484
+
485
+ - 客户端自动 tool-loop(仍由调用方编排)
486
+ - computer_use / shell 托管
487
+ - Chat Completions 搜索专用模型
488
+ - MCP approval 交互回路(出现 `mcp_approval_request` 会 `response.warning`)
489
+ - Containers REST 管理 API
490
+
491
+ 多轮续写推荐用 Responses 的 `previous_response_id` opaque replay,无需把 server tool result 当客户端 `tool_result` 回传。Mock 演示见 [examples/server-tools.ts](./examples/server-tools.ts)。
492
+
323
493
  ## 手动工具循环
324
494
 
325
495
  模型返回 `tool_call` → 调用方执行工具 → 下一轮带入 `tool_result`:
@@ -346,37 +516,13 @@ const r2 = await collectStream(client.stream({ input, tools }));
346
516
 
347
517
  ## 模拟流式
348
518
 
349
- 非流式后端可通过 `syntheticStream()` 包装为规范事件流:
350
-
351
- ```ts
352
- import { syntheticStream } from "@codehz/ai";
353
-
354
- const events = syntheticStream({
355
- model: "gpt-4o",
356
- responseId: "req-1",
357
- backend: { kind: "chat-completions" },
358
- output: [messageItem([textBlock("Hello")])],
359
- stopReason: "end_turn",
360
- });
519
+ 真实 adapter 在原生流不可用时,库内部会用 synthetic 路径包装为规范事件流。应用层一般只需消费 `client.stream()` / `collectStream()`;`0.5.0` 起 `syntheticStream` 不再从根入口导出(test-first helper,不展开 `server_tool_*`)。
361
520
 
362
- for await (const event of events) {
363
- // 消费规范事件
364
- }
365
- ```
521
+ 若只需前端逐字预览效果,请优先使用 `MockAdapter` + `withMockStreaming()`(见上文 Mock 后端)。
366
522
 
367
523
  ## 辅助信息采集
368
524
 
369
- `AuxiliaryCollector` 提供分层 best-effort 采集(流事件 headers lookup derived):
370
-
371
- ```ts
372
- import { AuxiliaryCollector } from "@codehz/ai";
373
-
374
- const collector = new AuxiliaryCollector();
375
- collector.recordUsage({ inputTokens: 10, outputTokens: 5 }, "stream");
376
- collector.recordBilling({ amount: 0.002, currency: "USD", isEstimated: false, source: "provider" }, "final");
377
-
378
- const { usage, billing, auxiliary, warnings } = collector.build();
379
- ```
525
+ usage / billing / providerMetadata 由 adapter 在流结束时经 `response.auxiliary` `AIResponse` 字段交付。`AuxiliaryCollector` provider 内部实现细节,`0.5.0` 起不再从根入口导出。`lookup` / `postprocessBilling` 为 experimental 扩展点,库内 HTTP adapter 默认未接线。
380
526
 
381
527
  ## 开发命令
382
528