@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.
- package/README.md +221 -75
- package/dist/index.d.mts +670 -523
- package/dist/index.mjs +3677 -2207
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -8
- package/.github/workflows/publish.yml +0 -56
- package/.oxfmtrc.json +0 -12
- package/.oxlintrc.json +0 -34
- package/AGENTS.md +0 -37
- package/src/adapters/chat-completions.ts +0 -624
- package/src/adapters/index.ts +0 -44
- package/src/adapters/messages.ts +0 -635
- package/src/adapters/mock.ts +0 -934
- package/src/adapters/ollama.ts +0 -526
- package/src/adapters/responses.ts +0 -818
- package/src/core/aggregator.ts +0 -428
- package/src/core/client.ts +0 -36
- package/src/core/collect-stream.ts +0 -19
- package/src/core/errors.ts +0 -105
- package/src/core/event-factory.ts +0 -151
- package/src/core/index.ts +0 -18
- package/src/core/merge-auxiliary.ts +0 -22
- package/src/core/normalize.ts +0 -65
- package/src/core/validation.ts +0 -404
- package/src/helpers/adapter-auxiliary.ts +0 -155
- package/src/helpers/adapter-base.ts +0 -218
- package/src/helpers/adapter-security.ts +0 -126
- package/src/helpers/auxiliary-collector.ts +0 -166
- package/src/helpers/incremental-stream-parser.ts +0 -142
- package/src/helpers/index.ts +0 -87
- package/src/helpers/mapping.ts +0 -192
- package/src/helpers/provider-request-options.ts +0 -25
- package/src/helpers/provider-stream.ts +0 -147
- package/src/helpers/reasoning-level.ts +0 -86
- package/src/helpers/request-mapper.ts +0 -94
- package/src/helpers/synthetic-stream.ts +0 -188
- package/src/helpers/usage-mapping.ts +0 -110
- package/src/index.ts +0 -17
- package/src/types/adapter.ts +0 -42
- package/src/types/content.ts +0 -15
- package/src/types/events.ts +0 -138
- package/src/types/index.ts +0 -49
- package/src/types/items.ts +0 -57
- package/src/types/request.ts +0 -52
- package/src/types/response.ts +0 -68
- 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
|
-
|
|
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`
|
|
58
|
-
| `ChatCompletionsAdapter` | 顶层 `reasoning_effort`
|
|
59
|
-
| `MessagesAdapter`
|
|
60
|
-
| `OllamaAdapter`
|
|
61
|
-
| `
|
|
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`
|
|
69
|
-
| `tool_call`
|
|
70
|
-
| `tool_result`
|
|
71
|
-
| `
|
|
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
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
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`
|
|
112
|
-
| `replay`
|
|
113
|
-
| `text`
|
|
114
|
-
| `toolCalls`
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
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
|
-
|
|
|
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()`
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
// 消费规范事件
|
|
364
|
-
}
|
|
365
|
-
```
|
|
521
|
+
若只需前端逐字预览效果,请优先使用 `MockAdapter` + `withMockStreaming()`(见上文 Mock 后端)。
|
|
366
522
|
|
|
367
523
|
## 辅助信息采集
|
|
368
524
|
|
|
369
|
-
`
|
|
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
|
|