@codehz/ai 0.8.2 → 0.9.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 +8 -2
- package/dist/index.d.mts +191 -28
- package/dist/index.mjs +265 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* KnownAdapterKind 覆盖内置 adapter;
|
|
6
6
|
* AdapterKind 额外接受任意 string,便于自定义 backend 扩展(无需改库联合类型)。
|
|
7
7
|
*/
|
|
8
|
+
/** 内置 adapter kind 列表。 */
|
|
8
9
|
const KNOWN_ADAPTER_KINDS = [
|
|
9
10
|
"chat-completions",
|
|
11
|
+
"delta-completions",
|
|
10
12
|
"messages",
|
|
11
13
|
"responses",
|
|
12
14
|
"ollama",
|
|
@@ -34,6 +36,7 @@ const REASONING_LEVEL_SET = new Set(REASONING_LEVELS);
|
|
|
34
36
|
*
|
|
35
37
|
* runtime.errors 与 events 均从此导出,避免手工双表漂移。
|
|
36
38
|
*/
|
|
39
|
+
/** 标准 warning 代码常量;事件和响应均可携带这些代码。 */
|
|
37
40
|
const WarningCode = {
|
|
38
41
|
/** replay fidelity 低于预期 */
|
|
39
42
|
REPLAY_FIDELITY_LOW: "REPLAY_FIDELITY_LOW",
|
|
@@ -87,7 +90,7 @@ function streamWarningKey(warning) {
|
|
|
87
90
|
}
|
|
88
91
|
//#endregion
|
|
89
92
|
//#region src/types/compress.ts
|
|
90
|
-
/** 探测 adapter
|
|
93
|
+
/** 探测 adapter 是否实现上下文压缩能力。 */
|
|
91
94
|
function supportsContextCompress(adapter) {
|
|
92
95
|
return typeof adapter.compress === "function";
|
|
93
96
|
}
|
|
@@ -99,11 +102,7 @@ const DEFAULT_INCLUDE = {
|
|
|
99
102
|
providerMetadata: "best_effort"
|
|
100
103
|
};
|
|
101
104
|
/**
|
|
102
|
-
*
|
|
103
|
-
* 1. 合并 defaults
|
|
104
|
-
* 2. 填充 include 默认值
|
|
105
|
-
* 3. 生成 requestId
|
|
106
|
-
* 4. 返回 provider 映射所需的归一化请求
|
|
105
|
+
* 归一化请求:合并 defaults、填充 include 默认值并生成 requestId。
|
|
107
106
|
*/
|
|
108
107
|
function normalizeRequest(request, options) {
|
|
109
108
|
const { model, defaults } = options;
|
|
@@ -121,6 +120,7 @@ function normalizeRequest(request, options) {
|
|
|
121
120
|
}
|
|
122
121
|
//#endregion
|
|
123
122
|
//#region src/runtime/client.ts
|
|
123
|
+
/** 创建统一 AI 客户端;请求级字段覆盖 defaults,signal 可在客户端级设置。 */
|
|
124
124
|
function createAIClient(options) {
|
|
125
125
|
const { adapter, model, defaults, signal: defaultSignal } = options;
|
|
126
126
|
return { stream(request) {
|
|
@@ -147,6 +147,7 @@ function mergeAbortSignals(...signals) {
|
|
|
147
147
|
}
|
|
148
148
|
//#endregion
|
|
149
149
|
//#region src/runtime/errors.ts
|
|
150
|
+
/** 所有运行时错误的基类。 */
|
|
150
151
|
var AIError = class extends Error {
|
|
151
152
|
code;
|
|
152
153
|
name;
|
|
@@ -220,24 +221,28 @@ var AIRecoverableError = class extends AIError {
|
|
|
220
221
|
};
|
|
221
222
|
//#endregion
|
|
222
223
|
//#region src/canonical/content.ts
|
|
224
|
+
/** 创建文本内容块。 */
|
|
223
225
|
function textBlock(text) {
|
|
224
226
|
return {
|
|
225
227
|
type: "text",
|
|
226
228
|
text
|
|
227
229
|
};
|
|
228
230
|
}
|
|
231
|
+
/** 创建 JSON 内容块。 */
|
|
229
232
|
function jsonBlock(json) {
|
|
230
233
|
return {
|
|
231
234
|
type: "json",
|
|
232
235
|
json
|
|
233
236
|
};
|
|
234
237
|
}
|
|
238
|
+
/** 创建图片内容块;imageUrl 可为 URL 或 data URL。 */
|
|
235
239
|
function imageBlock(imageUrl) {
|
|
236
240
|
return {
|
|
237
241
|
type: "image",
|
|
238
242
|
imageUrl
|
|
239
243
|
};
|
|
240
244
|
}
|
|
245
|
+
/** 创建 provider 专有 opaque 内容块。 */
|
|
241
246
|
function opaqueBlock(payload) {
|
|
242
247
|
return {
|
|
243
248
|
type: "opaque",
|
|
@@ -283,6 +288,7 @@ function coalesceContentBlocks(blocks) {
|
|
|
283
288
|
* 简单场景下 replay 与 output 一致。
|
|
284
289
|
* 复杂场景(需要 opaque continuation)由 adapter 自行扩展。
|
|
285
290
|
*/
|
|
291
|
+
/** 从 output items 构建 replay;opaque continuation 可由 adapter 额外扩展。 */
|
|
286
292
|
function replayFromOutput(output) {
|
|
287
293
|
return output.map((item) => {
|
|
288
294
|
switch (item.type) {
|
|
@@ -299,6 +305,7 @@ function replayFromOutput(output) {
|
|
|
299
305
|
/**
|
|
300
306
|
* 从 OutputItem 数组中提取所有 message 类型 item 的文本内容。
|
|
301
307
|
*/
|
|
308
|
+
/** 从 output 中提取所有消息 item 的文本内容。 */
|
|
302
309
|
function extractText(output) {
|
|
303
310
|
return output.filter((item) => item.type === "message").flatMap((m) => m.content).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
304
311
|
}
|
|
@@ -637,6 +644,7 @@ function streamProtocolError(message) {
|
|
|
637
644
|
}
|
|
638
645
|
//#endregion
|
|
639
646
|
//#region src/stream/collect-stream.ts
|
|
647
|
+
/** 消费完整个事件流并聚合为 AIResponse;适用于不需要逐事件处理的调用方。 */
|
|
640
648
|
async function collectStream(stream) {
|
|
641
649
|
const state = createAggregatorState();
|
|
642
650
|
for await (const event of stream) aggregateEvent(state, event);
|
|
@@ -669,9 +677,11 @@ const STOP_REASON_MAP = {
|
|
|
669
677
|
MISSING_THOUGHT_SIGNATURE: "error",
|
|
670
678
|
error: "error"
|
|
671
679
|
};
|
|
680
|
+
/** 将 provider stop/finish reason 映射为规范化 StopReason。 */
|
|
672
681
|
function mapStopReason(providerReason) {
|
|
673
682
|
return STOP_REASON_MAP[providerReason] ?? "unknown";
|
|
674
683
|
}
|
|
684
|
+
/** 根据 thinking 和 redacted 标记推断 reasoning 可见性。 */
|
|
675
685
|
function mapReasoningVisibility(hasThinking, hasRedacted) {
|
|
676
686
|
if (hasRedacted) return "redacted";
|
|
677
687
|
if (hasThinking) return "full";
|
|
@@ -679,6 +689,7 @@ function mapReasoningVisibility(hasThinking, hasRedacted) {
|
|
|
679
689
|
}
|
|
680
690
|
//#endregion
|
|
681
691
|
//#region src/canonical/items.ts
|
|
692
|
+
/** 创建消息 item;可用 overrides 补充 id、role 和 citations。 */
|
|
682
693
|
function messageItem(content, overrides) {
|
|
683
694
|
return {
|
|
684
695
|
type: "message",
|
|
@@ -687,6 +698,7 @@ function messageItem(content, overrides) {
|
|
|
687
698
|
content
|
|
688
699
|
};
|
|
689
700
|
}
|
|
701
|
+
/** 创建 reasoning item。 */
|
|
690
702
|
function reasoningItem(content, visibility = "full", id) {
|
|
691
703
|
return {
|
|
692
704
|
type: "reasoning",
|
|
@@ -695,6 +707,7 @@ function reasoningItem(content, visibility = "full", id) {
|
|
|
695
707
|
content
|
|
696
708
|
};
|
|
697
709
|
}
|
|
710
|
+
/** 创建客户端工具调用 item。 */
|
|
698
711
|
function toolCallItem(id, name, argumentsText) {
|
|
699
712
|
return {
|
|
700
713
|
type: "tool_call",
|
|
@@ -703,6 +716,7 @@ function toolCallItem(id, name, argumentsText) {
|
|
|
703
716
|
argumentsText
|
|
704
717
|
};
|
|
705
718
|
}
|
|
719
|
+
/** 创建客户端工具结果 item。 */
|
|
706
720
|
function toolResultItem(callId, toolName, outcome, content) {
|
|
707
721
|
return {
|
|
708
722
|
type: "tool_result",
|
|
@@ -712,6 +726,7 @@ function toolResultItem(callId, toolName, outcome, content) {
|
|
|
712
726
|
content
|
|
713
727
|
};
|
|
714
728
|
}
|
|
729
|
+
/** 创建 opaque item,通常用于 provider replay。 */
|
|
715
730
|
function opaqueItem(source, purpose, payload, id) {
|
|
716
731
|
return {
|
|
717
732
|
type: "opaque",
|
|
@@ -721,6 +736,7 @@ function opaqueItem(source, purpose, payload, id) {
|
|
|
721
736
|
payload
|
|
722
737
|
};
|
|
723
738
|
}
|
|
739
|
+
/** 创建 provider 托管工具调用 item。 */
|
|
724
740
|
function serverToolCallItem(id, tool, overrides) {
|
|
725
741
|
return {
|
|
726
742
|
type: "server_tool_call",
|
|
@@ -729,6 +745,7 @@ function serverToolCallItem(id, tool, overrides) {
|
|
|
729
745
|
...overrides
|
|
730
746
|
};
|
|
731
747
|
}
|
|
748
|
+
/** 创建 provider 托管工具结果 item。 */
|
|
732
749
|
function serverToolResultItem(callId, tool, outcome, content, overrides) {
|
|
733
750
|
return {
|
|
734
751
|
type: "server_tool_result",
|
|
@@ -739,6 +756,7 @@ function serverToolResultItem(callId, tool, outcome, content, overrides) {
|
|
|
739
756
|
...overrides
|
|
740
757
|
};
|
|
741
758
|
}
|
|
759
|
+
/** 创建 MCP 等 provider 托管工具发现 item。 */
|
|
742
760
|
function serverToolDiscoveryItem(id, serverLabel, tools, overrides) {
|
|
743
761
|
return {
|
|
744
762
|
type: "server_tool_discovery",
|
|
@@ -1091,11 +1109,11 @@ var AdapterAuxiliaryState = class {
|
|
|
1091
1109
|
billing: built.billing,
|
|
1092
1110
|
auxiliary: built.auxiliary
|
|
1093
1111
|
}));
|
|
1094
|
-
if (this.request.include?.usage
|
|
1095
|
-
if (this.request.include?.billing
|
|
1112
|
+
if (this.request.include?.usage === "required" && !built.usage) events.push(factory.responseWarning("Usage information was not provided by the provider", WarningCode.USAGE_MISSING));
|
|
1113
|
+
if (this.request.include?.billing === "required") {
|
|
1096
1114
|
if (!built.billing) events.push(factory.responseWarning("Billing information was not provided by the provider", WarningCode.BILLING_MISSING));
|
|
1097
1115
|
else if (built.billing.isEstimated) events.push(factory.responseWarning("Billing amount is an estimate", WarningCode.BILLING_ESTIMATED));
|
|
1098
|
-
}
|
|
1116
|
+
} else if (built.billing?.isEstimated) events.push(factory.responseWarning("Billing amount is an estimate", WarningCode.BILLING_ESTIMATED));
|
|
1099
1117
|
return {
|
|
1100
1118
|
events,
|
|
1101
1119
|
usage: built.usage,
|
|
@@ -1585,7 +1603,7 @@ function createProviderJsonStreamSession(host) {
|
|
|
1585
1603
|
/**
|
|
1586
1604
|
* HTTP adapter 共享基类与构造选项
|
|
1587
1605
|
*
|
|
1588
|
-
*
|
|
1606
|
+
* 收敛真实 HTTP adapter 的:
|
|
1589
1607
|
* - apiKey / baseUrl / fetch / headers / extraBody 字段赋值
|
|
1590
1608
|
* - 默认 baseUrl 解析
|
|
1591
1609
|
* - beginJsonStream:open → batches → complete session
|
|
@@ -1923,6 +1941,7 @@ async function* finalizeStreamTurn(session, items, options = {}) {
|
|
|
1923
1941
|
*/
|
|
1924
1942
|
const OPAQUE_SOURCE = {
|
|
1925
1943
|
CHAT_COMPLETIONS: "chat.completions",
|
|
1944
|
+
DELTA_COMPLETIONS: "delta.completions",
|
|
1926
1945
|
MESSAGES: "messages",
|
|
1927
1946
|
OLLAMA: "ollama",
|
|
1928
1947
|
GEMINI: "gemini",
|
|
@@ -2169,7 +2188,7 @@ function mapServerTools(serverTools) {
|
|
|
2169
2188
|
*
|
|
2170
2189
|
* 同时服务 stream(/responses)与 compress(/responses/compact)。
|
|
2171
2190
|
*/
|
|
2172
|
-
const mapper$
|
|
2191
|
+
const mapper$8 = new NormalizedRequestMapper("responses");
|
|
2173
2192
|
/** compact replay opaque:整份 wire output window 保真回传 */
|
|
2174
2193
|
const RESPONSES_COMPACTED_WINDOW_KIND = "compacted_window";
|
|
2175
2194
|
function isReplayCanonicalInput(item) {
|
|
@@ -2189,13 +2208,13 @@ function readNonEmptyString(value, maxLen = 256) {
|
|
|
2189
2208
|
* - user 纯 text/json:仍发 string,避免无谓 shape churn
|
|
2190
2209
|
*/
|
|
2191
2210
|
function mapResponsesMessageContent(role, blocks, field) {
|
|
2192
|
-
if (role !== "user") return mapper$
|
|
2193
|
-
mapper$
|
|
2211
|
+
if (role !== "user") return mapper$8.textFromBlocks(blocks, field);
|
|
2212
|
+
mapper$8.ensureBlocks(blocks, field, [
|
|
2194
2213
|
"text",
|
|
2195
2214
|
"json",
|
|
2196
2215
|
"image"
|
|
2197
2216
|
], "only text/json/image blocks are supported");
|
|
2198
|
-
if (!blocks.some((block) => block.type === "image")) return mapper$
|
|
2217
|
+
if (!blocks.some((block) => block.type === "image")) return mapper$8.textFromBlocks(blocks, field);
|
|
2199
2218
|
return blocks.map((block) => {
|
|
2200
2219
|
if (block.type === "text") return {
|
|
2201
2220
|
type: "input_text",
|
|
@@ -2209,16 +2228,16 @@ function mapResponsesMessageContent(role, blocks, field) {
|
|
|
2209
2228
|
type: "input_image",
|
|
2210
2229
|
image_url: block.imageUrl
|
|
2211
2230
|
};
|
|
2212
|
-
throw new AIRequestError(`${mapper$
|
|
2231
|
+
throw new AIRequestError(`${mapper$8.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
2213
2232
|
});
|
|
2214
2233
|
}
|
|
2215
2234
|
function mapResponsesToolResultOutput(blocks, field) {
|
|
2216
|
-
mapper$
|
|
2235
|
+
mapper$8.ensureBlocks(blocks, field, [
|
|
2217
2236
|
"text",
|
|
2218
2237
|
"json",
|
|
2219
2238
|
"image"
|
|
2220
2239
|
], "only text/json/image blocks are supported");
|
|
2221
|
-
if (!blocks.some((block) => block.type === "image")) return mapper$
|
|
2240
|
+
if (!blocks.some((block) => block.type === "image")) return mapper$8.textFromBlocks(blocks, field);
|
|
2222
2241
|
return blocks.map((block) => {
|
|
2223
2242
|
if (block.type === "text") return {
|
|
2224
2243
|
type: "input_text",
|
|
@@ -2232,11 +2251,11 @@ function mapResponsesToolResultOutput(blocks, field) {
|
|
|
2232
2251
|
type: "input_image",
|
|
2233
2252
|
image_url: block.imageUrl
|
|
2234
2253
|
};
|
|
2235
|
-
throw new AIRequestError(`${mapper$
|
|
2254
|
+
throw new AIRequestError(`${mapper$8.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
2236
2255
|
});
|
|
2237
2256
|
}
|
|
2238
2257
|
function mapReasoningInput(item, index) {
|
|
2239
|
-
const text = mapper$
|
|
2258
|
+
const text = mapper$8.textFromBlocks(mapper$8.ensureReasoningBlocks(item.content, "reasoning content"), "reasoning content");
|
|
2240
2259
|
const id = item.id && item.id.length > 0 ? item.id : `reasoning_replay_${index}`;
|
|
2241
2260
|
if (item.visibility === "full") return {
|
|
2242
2261
|
type: "reasoning",
|
|
@@ -2346,7 +2365,7 @@ function mapResponsesCore(request, options) {
|
|
|
2346
2365
|
usedCompactedWindow
|
|
2347
2366
|
};
|
|
2348
2367
|
if (previousResponseId && !usedCompactedWindow) mapped.previousResponseId = previousResponseId;
|
|
2349
|
-
if (request.instructions) mapped.instructions = mapper$
|
|
2368
|
+
if (request.instructions) mapped.instructions = mapper$8.mapInstructions(request.instructions);
|
|
2350
2369
|
return mapped;
|
|
2351
2370
|
}
|
|
2352
2371
|
/** 构建 Responses 流式请求体;调用方再 `withExtraBody` 合并构造期扩展字段。 */
|
|
@@ -2359,7 +2378,7 @@ function buildResponsesRequest(request, options) {
|
|
|
2359
2378
|
};
|
|
2360
2379
|
if (core.previousResponseId) body.previous_response_id = core.previousResponseId;
|
|
2361
2380
|
if (core.instructions) body.instructions = core.instructions;
|
|
2362
|
-
const functionTools = mapper$
|
|
2381
|
+
const functionTools = mapper$8.mapToolsIfPresent(request.tools, (t) => ({
|
|
2363
2382
|
type: "function",
|
|
2364
2383
|
name: t.name,
|
|
2365
2384
|
description: t.description,
|
|
@@ -2368,7 +2387,7 @@ function buildResponsesRequest(request, options) {
|
|
|
2368
2387
|
const serverTools = mapServerTools(request.serverTools);
|
|
2369
2388
|
const tools = [...functionTools, ...serverTools];
|
|
2370
2389
|
if (tools.length > 0) body.tools = tools;
|
|
2371
|
-
body.tool_choice = mapper$
|
|
2390
|
+
body.tool_choice = mapper$8.mapToolChoice(request.toolChoice, {
|
|
2372
2391
|
auto: "auto",
|
|
2373
2392
|
none: "none",
|
|
2374
2393
|
tool: (name) => ({
|
|
@@ -3135,6 +3154,7 @@ function createResponsesSseProcessor(factory) {
|
|
|
3135
3154
|
* 编排层:buildRequest + runStream(open → SSE 处理 → complete)。
|
|
3136
3155
|
* 可选能力:compress → POST /responses/compact(ContextCompressCapable)。
|
|
3137
3156
|
*/
|
|
3157
|
+
/** OpenAI Responses API 适配器;支持原生流式响应和上下文压缩。 */
|
|
3138
3158
|
var ResponsesAdapter = class extends HttpAdapterBase {
|
|
3139
3159
|
kind = "responses";
|
|
3140
3160
|
isSyntheticStream = false;
|
|
@@ -3258,7 +3278,7 @@ function parseImageDataUrl(imageUrl) {
|
|
|
3258
3278
|
/**
|
|
3259
3279
|
* MessagesAdapter — request 映射
|
|
3260
3280
|
*/
|
|
3261
|
-
const mapper$
|
|
3281
|
+
const mapper$7 = new NormalizedRequestMapper("messages");
|
|
3262
3282
|
function isMessagesReplayContentBlock(value) {
|
|
3263
3283
|
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
3264
3284
|
const block = value;
|
|
@@ -3320,33 +3340,33 @@ function canonicalToMessagesBlock(b, field = "canonical mapping") {
|
|
|
3320
3340
|
throw new AIRequestError(`messages does not support content block type "${b.type}" in ${field}`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
3321
3341
|
}
|
|
3322
3342
|
function mapMessagesUserContent(blocks, field) {
|
|
3323
|
-
mapper$
|
|
3343
|
+
mapper$7.ensureBlocks(blocks, field, [
|
|
3324
3344
|
"text",
|
|
3325
3345
|
"json",
|
|
3326
3346
|
"image"
|
|
3327
3347
|
], "only text/json/image blocks are supported");
|
|
3328
3348
|
if (!blocks.some((block) => block.type === "image")) {
|
|
3329
|
-
const supportedContent = mapper$
|
|
3349
|
+
const supportedContent = mapper$7.ensureTextBlocks(blocks, field);
|
|
3330
3350
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") return supportedContent[0].text;
|
|
3331
3351
|
return supportedContent.map((block) => canonicalToMessagesBlock(block, field));
|
|
3332
3352
|
}
|
|
3333
3353
|
return blocks.map((block) => canonicalToMessagesBlock(block, field));
|
|
3334
3354
|
}
|
|
3335
3355
|
function mapMessagesToolResultContent(blocks, field) {
|
|
3336
|
-
mapper$
|
|
3356
|
+
mapper$7.ensureBlocks(blocks, field, [
|
|
3337
3357
|
"text",
|
|
3338
3358
|
"json",
|
|
3339
3359
|
"image"
|
|
3340
3360
|
], "only text/json/image blocks are supported");
|
|
3341
|
-
if (!blocks.some((block) => block.type === "image")) return mapper$
|
|
3361
|
+
if (!blocks.some((block) => block.type === "image")) return mapper$7.textFromBlocks(blocks, field);
|
|
3342
3362
|
return blocks.map((block) => canonicalToMessagesBlock(block, field));
|
|
3343
3363
|
}
|
|
3344
3364
|
function buildMessagesRequest(request, options) {
|
|
3345
|
-
mapper$
|
|
3365
|
+
mapper$7.assertNoServerTools(request.serverTools);
|
|
3346
3366
|
const messages = [];
|
|
3347
3367
|
let systemPrompt;
|
|
3348
3368
|
let pendingToolResultMessage;
|
|
3349
|
-
if (request.instructions) systemPrompt = mapper$
|
|
3369
|
+
if (request.instructions) systemPrompt = mapper$7.mapInstructions(request.instructions);
|
|
3350
3370
|
for (const item of request.input) {
|
|
3351
3371
|
if (item.type !== "tool_result") pendingToolResultMessage = void 0;
|
|
3352
3372
|
switch (item.type) {
|
|
@@ -3359,7 +3379,7 @@ function buildMessagesRequest(request, options) {
|
|
|
3359
3379
|
});
|
|
3360
3380
|
break;
|
|
3361
3381
|
}
|
|
3362
|
-
const supportedContent = mapper$
|
|
3382
|
+
const supportedContent = mapper$7.ensureTextBlocks(item.content, field);
|
|
3363
3383
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") messages.push({
|
|
3364
3384
|
role: "assistant",
|
|
3365
3385
|
content: supportedContent[0].text
|
|
@@ -3376,7 +3396,7 @@ function buildMessagesRequest(request, options) {
|
|
|
3376
3396
|
type: "tool_use",
|
|
3377
3397
|
id: item.id,
|
|
3378
3398
|
name: item.name,
|
|
3379
|
-
input: mapper$
|
|
3399
|
+
input: mapper$7.parseToolArguments(item)
|
|
3380
3400
|
};
|
|
3381
3401
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") lastMsg.content.push(toolBlock);
|
|
3382
3402
|
else messages.push({
|
|
@@ -3406,7 +3426,7 @@ function buildMessagesRequest(request, options) {
|
|
|
3406
3426
|
case "reasoning": {
|
|
3407
3427
|
const block = {
|
|
3408
3428
|
type: "thinking",
|
|
3409
|
-
thinking: contentBlocksToText(mapper$
|
|
3429
|
+
thinking: contentBlocksToText(mapper$7.ensureReasoningBlocks(item.content, "reasoning content"))
|
|
3410
3430
|
};
|
|
3411
3431
|
const lastMsg = messages[messages.length - 1];
|
|
3412
3432
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") lastMsg.content.push(block);
|
|
@@ -3421,7 +3441,7 @@ function buildMessagesRequest(request, options) {
|
|
|
3421
3441
|
if (!payload) break;
|
|
3422
3442
|
if (payload.role === "assistant" && "content" in payload) {
|
|
3423
3443
|
assertMessagesReplayContent(payload.content);
|
|
3424
|
-
mapper$
|
|
3444
|
+
mapper$7.rollbackTrailingAssistantMessages(messages);
|
|
3425
3445
|
messages.push({
|
|
3426
3446
|
role: "assistant",
|
|
3427
3447
|
content: payload.content
|
|
@@ -3438,12 +3458,12 @@ function buildMessagesRequest(request, options) {
|
|
|
3438
3458
|
stream: true
|
|
3439
3459
|
};
|
|
3440
3460
|
if (systemPrompt) body.system = systemPrompt;
|
|
3441
|
-
body.tools = mapper$
|
|
3461
|
+
body.tools = mapper$7.mapToolsIfPresent(request.tools, (t) => ({
|
|
3442
3462
|
name: t.name,
|
|
3443
3463
|
description: t.description,
|
|
3444
3464
|
input_schema: t.inputSchema
|
|
3445
3465
|
}));
|
|
3446
|
-
body.tool_choice = mapper$
|
|
3466
|
+
body.tool_choice = mapper$7.mapToolChoice(request.toolChoice, {
|
|
3447
3467
|
auto: { type: "auto" },
|
|
3448
3468
|
none: { type: "none" },
|
|
3449
3469
|
tool: (name) => ({
|
|
@@ -3705,6 +3725,7 @@ async function* mapMessagesStream(host, providerRequest, factory, request) {
|
|
|
3705
3725
|
*
|
|
3706
3726
|
* 编排层:buildRequest + runStream(委托 map-request / map-stream)。
|
|
3707
3727
|
*/
|
|
3728
|
+
/** Anthropic Messages API 适配器。 */
|
|
3708
3729
|
var MessagesAdapter = class extends HttpAdapterBase {
|
|
3709
3730
|
kind = "messages";
|
|
3710
3731
|
isSyntheticStream = false;
|
|
@@ -3747,7 +3768,7 @@ const REASONING_FIELDS = ["reasoning_content", "reasoning"];
|
|
|
3747
3768
|
/**
|
|
3748
3769
|
* ChatCompletionsAdapter — request 映射
|
|
3749
3770
|
*/
|
|
3750
|
-
const mapper$
|
|
3771
|
+
const mapper$5 = new NormalizedRequestMapper("chat-completions");
|
|
3751
3772
|
function extractReasoningText(value) {
|
|
3752
3773
|
if (typeof value === "string") return value;
|
|
3753
3774
|
if (Array.isArray(value)) return value.map(extractReasoningText).join("");
|
|
@@ -3838,7 +3859,7 @@ function buildAssistantReplayMessage(params) {
|
|
|
3838
3859
|
return replayMessage;
|
|
3839
3860
|
}
|
|
3840
3861
|
function mapChatUserContent(blocks, field) {
|
|
3841
|
-
mapper$
|
|
3862
|
+
mapper$5.ensureBlocks(blocks, field, [
|
|
3842
3863
|
"text",
|
|
3843
3864
|
"json",
|
|
3844
3865
|
"image"
|
|
@@ -3857,16 +3878,16 @@ function mapChatUserContent(blocks, field) {
|
|
|
3857
3878
|
type: "image_url",
|
|
3858
3879
|
image_url: { url: block.imageUrl }
|
|
3859
3880
|
};
|
|
3860
|
-
throw new AIRequestError(`${mapper$
|
|
3881
|
+
throw new AIRequestError(`${mapper$5.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
3861
3882
|
});
|
|
3862
3883
|
}
|
|
3863
3884
|
function mapChatToolResultContent(blocks, field) {
|
|
3864
|
-
mapper$
|
|
3885
|
+
mapper$5.ensureBlocks(blocks, field, [
|
|
3865
3886
|
"text",
|
|
3866
3887
|
"json",
|
|
3867
3888
|
"image"
|
|
3868
3889
|
], "only text/json/image blocks are supported");
|
|
3869
|
-
if (!blocks.some((block) => block.type === "image")) return mapper$
|
|
3890
|
+
if (!blocks.some((block) => block.type === "image")) return mapper$5.textFromBlocks(blocks, field);
|
|
3870
3891
|
return blocks.map((block) => {
|
|
3871
3892
|
if (block.type === "text") return {
|
|
3872
3893
|
type: "text",
|
|
@@ -3880,19 +3901,19 @@ function mapChatToolResultContent(blocks, field) {
|
|
|
3880
3901
|
type: "image_url",
|
|
3881
3902
|
image_url: { url: block.imageUrl }
|
|
3882
3903
|
};
|
|
3883
|
-
throw new AIRequestError(`${mapper$
|
|
3904
|
+
throw new AIRequestError(`${mapper$5.kind} does not support ${field} block of type "${block.type}"; only text/json/image blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
3884
3905
|
});
|
|
3885
3906
|
}
|
|
3886
3907
|
function mapChatMessageContent(role, blocks, field) {
|
|
3887
3908
|
if (role === "user") return mapChatUserContent(blocks, field);
|
|
3888
|
-
return mapper$
|
|
3909
|
+
return mapper$5.textFromBlocks(blocks, field) || null;
|
|
3889
3910
|
}
|
|
3890
3911
|
function buildChatCompletionsRequest(request, options) {
|
|
3891
|
-
mapper$
|
|
3912
|
+
mapper$5.assertNoServerTools(request.serverTools);
|
|
3892
3913
|
const messages = [];
|
|
3893
3914
|
if (request.instructions) messages.push({
|
|
3894
3915
|
role: "system",
|
|
3895
|
-
content: mapper$
|
|
3916
|
+
content: mapper$5.mapInstructions(request.instructions)
|
|
3896
3917
|
});
|
|
3897
3918
|
for (const item of request.input) switch (item.type) {
|
|
3898
3919
|
case "message": {
|
|
@@ -3932,7 +3953,7 @@ function buildChatCompletionsRequest(request, options) {
|
|
|
3932
3953
|
case "reasoning":
|
|
3933
3954
|
messages.push({
|
|
3934
3955
|
role: "assistant",
|
|
3935
|
-
content: mapper$
|
|
3956
|
+
content: mapper$5.textFromBlocks(item.content, "reasoning content")
|
|
3936
3957
|
});
|
|
3937
3958
|
break;
|
|
3938
3959
|
case "opaque": {
|
|
@@ -3940,7 +3961,7 @@ function buildChatCompletionsRequest(request, options) {
|
|
|
3940
3961
|
if (!payload) break;
|
|
3941
3962
|
if ("messages" in payload) {
|
|
3942
3963
|
assertChatReplayMessages(payload.messages, "messages");
|
|
3943
|
-
mapper$
|
|
3964
|
+
mapper$5.rollbackTrailingAssistantMessages(messages);
|
|
3944
3965
|
for (const m of payload.messages) messages.push(m);
|
|
3945
3966
|
}
|
|
3946
3967
|
break;
|
|
@@ -3952,8 +3973,8 @@ function buildChatCompletionsRequest(request, options) {
|
|
|
3952
3973
|
stream: true,
|
|
3953
3974
|
n: 1
|
|
3954
3975
|
};
|
|
3955
|
-
body.tools = mapper$
|
|
3956
|
-
body.tool_choice = mapper$
|
|
3976
|
+
body.tools = mapper$5.mapToolsIfPresent(request.tools, mapOpenAiFunctionTool);
|
|
3977
|
+
body.tool_choice = mapper$5.mapToolChoice(request.toolChoice, {
|
|
3957
3978
|
auto: "auto",
|
|
3958
3979
|
none: "none",
|
|
3959
3980
|
tool: (name) => ({
|
|
@@ -4188,6 +4209,7 @@ async function* mapChatCompletionsStream(host, providerRequest, factory, request
|
|
|
4188
4209
|
*
|
|
4189
4210
|
* 编排层:buildRequest + runStream(委托 map-request / map-stream)。
|
|
4190
4211
|
*/
|
|
4212
|
+
/** OpenAI Chat Completions API 适配器。 */
|
|
4191
4213
|
var ChatCompletionsAdapter = class extends HttpAdapterBase {
|
|
4192
4214
|
kind = "chat-completions";
|
|
4193
4215
|
isSyntheticStream = false;
|
|
@@ -4208,6 +4230,188 @@ var ChatCompletionsAdapter = class extends HttpAdapterBase {
|
|
|
4208
4230
|
}
|
|
4209
4231
|
};
|
|
4210
4232
|
//#endregion
|
|
4233
|
+
//#region src/adapters/delta-completions/map-request.ts
|
|
4234
|
+
/**
|
|
4235
|
+
* DeltaCompletionsAdapter — request 映射
|
|
4236
|
+
*/
|
|
4237
|
+
const mapper$4 = new NormalizedRequestMapper("delta-completions");
|
|
4238
|
+
function isDeltaReplayMessage(value) {
|
|
4239
|
+
if (!value || typeof value !== "object") return false;
|
|
4240
|
+
const msg = value;
|
|
4241
|
+
const role = msg.role;
|
|
4242
|
+
return (role === "system" || role === "user" || role === "assistant") && typeof msg.content === "string";
|
|
4243
|
+
}
|
|
4244
|
+
function assertDeltaReplayMessages(messages, field) {
|
|
4245
|
+
if (!Array.isArray(messages)) throw new AIRequestError(`Invalid opaque replay payload: ${field} must be an array`, "INVALID_OPAQUE_REPLAY");
|
|
4246
|
+
for (let i = 0; i < messages.length; i++) if (!isDeltaReplayMessage(messages[i])) throw new AIRequestError(`Invalid opaque replay payload: ${field}[${i}] is not a valid delta-completions text message`, "INVALID_OPAQUE_REPLAY");
|
|
4247
|
+
}
|
|
4248
|
+
function assertTextOnlyRequest(request) {
|
|
4249
|
+
mapper$4.assertNoServerTools(request.serverTools);
|
|
4250
|
+
if (request.tools && request.tools.length > 0) throw new AIRequestError("delta-completions does not support tools; use ChatCompletionsAdapter", "UNSUPPORTED_TOOL");
|
|
4251
|
+
if (request.toolChoice) throw new AIRequestError("delta-completions does not support toolChoice; use ChatCompletionsAdapter", "UNSUPPORTED_TOOL");
|
|
4252
|
+
}
|
|
4253
|
+
function buildDeltaCompletionsRequest(request, options) {
|
|
4254
|
+
assertTextOnlyRequest(request);
|
|
4255
|
+
const messages = [];
|
|
4256
|
+
if (request.instructions) messages.push({
|
|
4257
|
+
role: "system",
|
|
4258
|
+
content: mapper$4.mapInstructions(request.instructions)
|
|
4259
|
+
});
|
|
4260
|
+
for (const item of request.input) switch (item.type) {
|
|
4261
|
+
case "message":
|
|
4262
|
+
messages.push({
|
|
4263
|
+
role: item.role,
|
|
4264
|
+
content: mapper$4.textFromBlocks(item.content, `input message (${item.role}) content`)
|
|
4265
|
+
});
|
|
4266
|
+
break;
|
|
4267
|
+
case "reasoning":
|
|
4268
|
+
messages.push({
|
|
4269
|
+
role: "assistant",
|
|
4270
|
+
content: mapper$4.textFromBlocks(item.content, "reasoning content")
|
|
4271
|
+
});
|
|
4272
|
+
break;
|
|
4273
|
+
case "opaque": {
|
|
4274
|
+
const payload = acceptOpaqueReplay(item, OPAQUE_SOURCE.DELTA_COMPLETIONS, { maxBytes: options?.maxOpaquePayloadBytes });
|
|
4275
|
+
if (!payload) break;
|
|
4276
|
+
if ("messages" in payload) {
|
|
4277
|
+
assertDeltaReplayMessages(payload.messages, "messages");
|
|
4278
|
+
mapper$4.rollbackTrailingAssistantMessages(messages);
|
|
4279
|
+
for (const m of payload.messages) messages.push({
|
|
4280
|
+
role: m.role,
|
|
4281
|
+
content: m.content
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
break;
|
|
4285
|
+
}
|
|
4286
|
+
case "tool_call":
|
|
4287
|
+
case "tool_result":
|
|
4288
|
+
case "server_tool_call":
|
|
4289
|
+
case "server_tool_result":
|
|
4290
|
+
case "server_tool_discovery": throw new AIRequestError(`delta-completions does not support input item type "${item.type}"; use ChatCompletionsAdapter`, "UNSUPPORTED_TOOL");
|
|
4291
|
+
}
|
|
4292
|
+
const body = {
|
|
4293
|
+
model: request.model,
|
|
4294
|
+
messages,
|
|
4295
|
+
stream: true
|
|
4296
|
+
};
|
|
4297
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
4298
|
+
if (request.maxOutputTokens !== void 0) body.max_tokens = request.maxOutputTokens;
|
|
4299
|
+
return body;
|
|
4300
|
+
}
|
|
4301
|
+
//#endregion
|
|
4302
|
+
//#region src/adapters/delta-completions/map-stream.ts
|
|
4303
|
+
/**
|
|
4304
|
+
* DeltaCompletionsAdapter — stream 映射
|
|
4305
|
+
*
|
|
4306
|
+
* 完成信号是 SSE 结束,不依赖 finish_reason / index / id。
|
|
4307
|
+
*/
|
|
4308
|
+
async function* mapDeltaCompletionsStream(host, providerRequest, factory, request) {
|
|
4309
|
+
const session = host.beginJsonStream(factory, request);
|
|
4310
|
+
const { gate } = session;
|
|
4311
|
+
const items = createStreamingItemSession(factory);
|
|
4312
|
+
if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Delta Completions adapter", WarningCode.UNSUPPORTED_METADATA);
|
|
4313
|
+
if (request.reasoningLevel !== void 0) yield factory.responseWarning("Request reasoningLevel is not supported by the Delta Completions adapter", WarningCode.CAPABILITY_DOWNGRADE);
|
|
4314
|
+
const headers = { "Content-Type": "application/json" };
|
|
4315
|
+
if (host.apiKey) headers.Authorization = `Bearer ${host.apiKey}`;
|
|
4316
|
+
await session.open({
|
|
4317
|
+
url: `${host.baseUrl}/chat/completions`,
|
|
4318
|
+
headers: host.mergeHeaders(headers),
|
|
4319
|
+
body: providerRequest
|
|
4320
|
+
});
|
|
4321
|
+
const parser = createDataLineSseParser();
|
|
4322
|
+
let responseId;
|
|
4323
|
+
let accumulatedContent = "";
|
|
4324
|
+
let currentMessageId = "";
|
|
4325
|
+
let hasMessageStarted = false;
|
|
4326
|
+
let lastFinishReason;
|
|
4327
|
+
let warnedExtraChoices = false;
|
|
4328
|
+
let warnedIgnoredDelta = false;
|
|
4329
|
+
const ensureMessageStarted = function* (idSeed) {
|
|
4330
|
+
if (!currentMessageId) currentMessageId = `msg-${idSeed}`;
|
|
4331
|
+
const started = items.ensureMessageStarted(currentMessageId);
|
|
4332
|
+
if (started) {
|
|
4333
|
+
hasMessageStarted = true;
|
|
4334
|
+
yield started;
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
for await (const batch of session.batches({
|
|
4338
|
+
parser,
|
|
4339
|
+
providerLabel: "Delta Completions",
|
|
4340
|
+
transportLabel: "SSE event(s)",
|
|
4341
|
+
incompleteMessage: "Stream ended with an incomplete Delta Completions SSE frame"
|
|
4342
|
+
})) {
|
|
4343
|
+
for (const warning of batch.warnings) yield warning;
|
|
4344
|
+
for (const chunk of batch.items) {
|
|
4345
|
+
if (chunk === null || typeof chunk !== "object") continue;
|
|
4346
|
+
if ("id" in chunk && typeof chunk.id === "string" && chunk.id) responseId = chunk.id;
|
|
4347
|
+
if (!("choices" in chunk) || !Array.isArray(chunk.choices) || chunk.choices.length === 0) continue;
|
|
4348
|
+
if (chunk.choices.length > 1 && !warnedExtraChoices) {
|
|
4349
|
+
yield factory.responseWarning("Delta Completions returned multiple choices; only the first array element is read. This adapter ignores choice.index.", WarningCode.MULTIPLE_CHOICES_IGNORED);
|
|
4350
|
+
warnedExtraChoices = true;
|
|
4351
|
+
}
|
|
4352
|
+
const choice = chunk.choices[0];
|
|
4353
|
+
if (choice === null || typeof choice !== "object") continue;
|
|
4354
|
+
if ("finish_reason" in choice && typeof choice.finish_reason === "string" && choice.finish_reason) lastFinishReason = choice.finish_reason;
|
|
4355
|
+
if (!("delta" in choice) || choice.delta === null || typeof choice.delta !== "object") continue;
|
|
4356
|
+
const delta = choice.delta;
|
|
4357
|
+
if (!warnedIgnoredDelta && ("tool_calls" in delta || "function_call" in delta || "reasoning" in delta || "reasoning_content" in delta)) {
|
|
4358
|
+
yield factory.responseWarning("Delta Completions ignored non-content delta fields (tool_calls / function_call / reasoning); use ChatCompletionsAdapter for those", WarningCode.CAPABILITY_DOWNGRADE);
|
|
4359
|
+
warnedIgnoredDelta = true;
|
|
4360
|
+
}
|
|
4361
|
+
if (!("content" in delta) || typeof delta.content !== "string" || !delta.content) continue;
|
|
4362
|
+
yield* ensureMessageStarted(responseId ?? request.requestId);
|
|
4363
|
+
accumulatedContent += delta.content;
|
|
4364
|
+
yield items.deltaMessage(currentMessageId, textBlock(delta.content));
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
if (gate.completed) return;
|
|
4368
|
+
if (hasMessageStarted && items.isActive(currentMessageId)) yield items.completeMessage(currentMessageId);
|
|
4369
|
+
const stopReason = lastFinishReason ? mapStopReason(lastFinishReason) : "end_turn";
|
|
4370
|
+
const assistantReplayMessage = accumulatedContent.length > 0 ? {
|
|
4371
|
+
role: "assistant",
|
|
4372
|
+
content: accumulatedContent
|
|
4373
|
+
} : null;
|
|
4374
|
+
yield* finalizeStreamTurn(session, items, {
|
|
4375
|
+
stopReason,
|
|
4376
|
+
rawResponseId: responseId ?? request.requestId,
|
|
4377
|
+
factory,
|
|
4378
|
+
maxOpaquePayloadBytes: host.maxOpaquePayloadBytes,
|
|
4379
|
+
opaque: assistantReplayMessage ? opaqueItem(OPAQUE_SOURCE.DELTA_COMPLETIONS, "replay", {
|
|
4380
|
+
replaceCanonical: true,
|
|
4381
|
+
messages: [assistantReplayMessage]
|
|
4382
|
+
}) : null
|
|
4383
|
+
});
|
|
4384
|
+
}
|
|
4385
|
+
//#endregion
|
|
4386
|
+
//#region src/adapters/delta-completions/adapter.ts
|
|
4387
|
+
/**
|
|
4388
|
+
* DeltaCompletionsAdapter
|
|
4389
|
+
*
|
|
4390
|
+
* 残缺 OpenAI 兼容 SSE:只读 choices[0].delta.content,以流结束为完成。
|
|
4391
|
+
* 不要用本 adapter 对接完整 chat.completions。
|
|
4392
|
+
*/
|
|
4393
|
+
/** 残缺 chat/completions SSE 适配器。 */
|
|
4394
|
+
var DeltaCompletionsAdapter = class extends HttpAdapterBase {
|
|
4395
|
+
kind = "delta-completions";
|
|
4396
|
+
isSyntheticStream = false;
|
|
4397
|
+
constructor(options) {
|
|
4398
|
+
if (!options.baseUrl) throw new AIRequestError("DeltaCompletionsAdapter requires baseUrl; it does not default to the OpenAI API", "ADAPTER_OPTIONS_INVALID");
|
|
4399
|
+
super(options, { baseUrl: options.baseUrl });
|
|
4400
|
+
}
|
|
4401
|
+
buildRequest(request) {
|
|
4402
|
+
return this.withExtraBody(buildDeltaCompletionsRequest(request, { maxOpaquePayloadBytes: this.maxOpaquePayloadBytes }));
|
|
4403
|
+
}
|
|
4404
|
+
async *runStream(providerRequest, factory, request) {
|
|
4405
|
+
yield* mapDeltaCompletionsStream({
|
|
4406
|
+
beginJsonStream: this.beginJsonStream.bind(this),
|
|
4407
|
+
baseUrl: this.baseUrl,
|
|
4408
|
+
apiKey: this.apiKey,
|
|
4409
|
+
mergeHeaders: this.mergeHeaders.bind(this),
|
|
4410
|
+
maxOpaquePayloadBytes: this.maxOpaquePayloadBytes
|
|
4411
|
+
}, providerRequest, factory, request);
|
|
4412
|
+
}
|
|
4413
|
+
};
|
|
4414
|
+
//#endregion
|
|
4211
4415
|
//#region src/adapters/ollama/map-request.ts
|
|
4212
4416
|
/**
|
|
4213
4417
|
* OllamaAdapter — request 映射
|
|
@@ -4485,6 +4689,7 @@ async function* mapOllamaStream(host, providerRequest, factory, request) {
|
|
|
4485
4689
|
*
|
|
4486
4690
|
* 编排层:buildRequest + runStream(委托 map-request / map-stream)。
|
|
4487
4691
|
*/
|
|
4692
|
+
/** Ollama Chat API 适配器。 */
|
|
4488
4693
|
var OllamaAdapter = class extends HttpAdapterBase {
|
|
4489
4694
|
kind = "ollama";
|
|
4490
4695
|
isSyntheticStream = false;
|
|
@@ -4826,6 +5031,7 @@ async function* mapGeminiStream(host, providerRequest, factory, request) {
|
|
|
4826
5031
|
*
|
|
4827
5032
|
* 编排层:buildRequest + runStream(委托 map-request / map-stream)。
|
|
4828
5033
|
*/
|
|
5034
|
+
/** Google Gemini generateContent API 适配器。 */
|
|
4829
5035
|
var GeminiAdapter = class extends HttpAdapterBase {
|
|
4830
5036
|
kind = "gemini";
|
|
4831
5037
|
isSyntheticStream = false;
|
|
@@ -4850,6 +5056,7 @@ var GeminiAdapter = class extends HttpAdapterBase {
|
|
|
4850
5056
|
/**
|
|
4851
5057
|
* Mock 流式包装与事件发射
|
|
4852
5058
|
*/
|
|
5059
|
+
/** 为静态 Mock handler 添加可配置的文本/参数流式切分。 */
|
|
4853
5060
|
function withMockStreaming(handler, options) {
|
|
4854
5061
|
const defaults = resolveMockTextStreamOptions(options, "mock stream wrapper");
|
|
4855
5062
|
if (!defaults) throw new AIRequestError("mock stream wrapper requires streaming options", "MOCK_STREAM_CONFIG_INVALID");
|
|
@@ -5045,6 +5252,7 @@ async function sleep(ms) {
|
|
|
5045
5252
|
*
|
|
5046
5253
|
* 这不是通用“假模型”,而是测试工具调用编排与错误路径的测试夹具。
|
|
5047
5254
|
*/
|
|
5255
|
+
/** 用于测试和本地场景的确定性模拟适配器。 */
|
|
5048
5256
|
var MockAdapter = class extends AdapterBase {
|
|
5049
5257
|
kind = "mock";
|
|
5050
5258
|
isSyntheticStream = true;
|
|
@@ -5226,6 +5434,7 @@ function cloneItem(item) {
|
|
|
5226
5434
|
/**
|
|
5227
5435
|
* Mock 请求期望校验
|
|
5228
5436
|
*/
|
|
5437
|
+
/** 按 MockRequestExpectation 校验规范化请求;失败时抛出断言错误。 */
|
|
5229
5438
|
function assertMockRequest(request, expectation, context) {
|
|
5230
5439
|
const prefix = `MockAdapter turn ${context.turnIndex + 1} expectation failed`;
|
|
5231
5440
|
if (expectation.minItems !== void 0 && request.input.length < expectation.minItems) throw new AIRequestError(`${prefix}: expected at least ${expectation.minItems} input item(s)`, "MOCK_EXPECTATION_FAILED");
|
|
@@ -5244,6 +5453,7 @@ function assertMockRequest(request, expectation, context) {
|
|
|
5244
5453
|
if (expectation.items && expectation.items.length > 0) if (expectation.ordered) assertOrderedItems(request.input, expectation.items, prefix);
|
|
5245
5454
|
else assertUnorderedItems(request.input, expectation.items, prefix);
|
|
5246
5455
|
}
|
|
5456
|
+
/** 断言上一轮 replay 按顺序包含在当前输入中。 */
|
|
5247
5457
|
function assertReplayIncluded(input, replay, prefix) {
|
|
5248
5458
|
const fingerprints = input.map(fingerprintItem);
|
|
5249
5459
|
let cursor = 0;
|
|
@@ -5254,6 +5464,7 @@ function assertReplayIncluded(input, replay, prefix) {
|
|
|
5254
5464
|
cursor = foundIndex + 1;
|
|
5255
5465
|
}
|
|
5256
5466
|
}
|
|
5467
|
+
/** 按顺序匹配输入 item。 */
|
|
5257
5468
|
function assertOrderedItems(input, expectations, prefix) {
|
|
5258
5469
|
let cursor = 0;
|
|
5259
5470
|
for (const expected of expectations) {
|
|
@@ -5270,9 +5481,11 @@ function assertOrderedItems(input, expectations, prefix) {
|
|
|
5270
5481
|
if (!matched) throw new AIRequestError(`${prefix}: missing ordered input item ${describeExpectation(expected)}`, "MOCK_EXPECTATION_FAILED");
|
|
5271
5482
|
}
|
|
5272
5483
|
}
|
|
5484
|
+
/** 无序匹配输入 item。 */
|
|
5273
5485
|
function assertUnorderedItems(input, expectations, prefix) {
|
|
5274
5486
|
for (const expected of expectations) if (!input.some((item) => matchesItemExpectation(item, expected))) throw new AIRequestError(`${prefix}: missing input item ${describeExpectation(expected)}`, "MOCK_EXPECTATION_FAILED");
|
|
5275
5487
|
}
|
|
5488
|
+
/** 判断输入 item 是否满足指定条件。 */
|
|
5276
5489
|
function matchesItemExpectation(item, expected) {
|
|
5277
5490
|
if (item.type !== expected.type) return false;
|
|
5278
5491
|
if (expected.id !== void 0 && "id" in item && item.id !== expected.id) return false;
|
|
@@ -5287,6 +5500,7 @@ function matchesItemExpectation(item, expected) {
|
|
|
5287
5500
|
case "opaque": return (expected.source === void 0 || item.source === expected.source) && (expected.purpose === void 0 || item.purpose === expected.purpose);
|
|
5288
5501
|
}
|
|
5289
5502
|
}
|
|
5503
|
+
/** 判断内容块文本是否包含指定片段。 */
|
|
5290
5504
|
function matchesText(blocks, textIncludes) {
|
|
5291
5505
|
if (textIncludes === void 0) return true;
|
|
5292
5506
|
return blocks.some((block) => {
|
|
@@ -5295,9 +5509,11 @@ function matchesText(blocks, textIncludes) {
|
|
|
5295
5509
|
return false;
|
|
5296
5510
|
});
|
|
5297
5511
|
}
|
|
5512
|
+
/** 为 item 生成稳定的 JSON 指纹。 */
|
|
5298
5513
|
function fingerprintItem(item) {
|
|
5299
5514
|
return JSON.stringify(item);
|
|
5300
5515
|
}
|
|
5516
|
+
/** 将 Mock 断言条件格式化为可读文本。 */
|
|
5301
5517
|
function describeExpectation(expectation) {
|
|
5302
5518
|
const parts = [`type=${expectation.type}`];
|
|
5303
5519
|
if (expectation.role) parts.push(`role=${expectation.role}`);
|
|
@@ -5308,6 +5524,6 @@ function describeExpectation(expectation) {
|
|
|
5308
5524
|
return `{ ${parts.join(", ")} }`;
|
|
5309
5525
|
}
|
|
5310
5526
|
//#endregion
|
|
5311
|
-
export { AIError, AIMappingError, AIProviderError, AIRecoverableError, AIRequestError, AIStreamError, ChatCompletionsAdapter, GeminiAdapter, KNOWN_ADAPTER_KINDS, MessagesAdapter, MockAdapter, OllamaAdapter, REASONING_LEVELS, REASONING_LEVEL_SET, ResponsesAdapter, WarningCode, assertMockRequest, blockToText, coalesceContentBlocks, collectStream, contentBlocksToText, createAIClient, extractText, imageBlock, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, opaqueBlock, opaqueItem, reasoningItem, replayFromOutput, serverToolCallItem, serverToolDiscoveryItem, serverToolResultItem, streamWarningKey, supportsContextCompress, textBlock, toolCallItem, toolResultItem, withMockStreaming };
|
|
5527
|
+
export { AIError, AIMappingError, AIProviderError, AIRecoverableError, AIRequestError, AIStreamError, ChatCompletionsAdapter, DeltaCompletionsAdapter, GeminiAdapter, KNOWN_ADAPTER_KINDS, MessagesAdapter, MockAdapter, OllamaAdapter, REASONING_LEVELS, REASONING_LEVEL_SET, ResponsesAdapter, WarningCode, assertMockRequest, blockToText, coalesceContentBlocks, collectStream, contentBlocksToText, createAIClient, extractText, imageBlock, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, opaqueBlock, opaqueItem, reasoningItem, replayFromOutput, serverToolCallItem, serverToolDiscoveryItem, serverToolResultItem, streamWarningKey, supportsContextCompress, textBlock, toolCallItem, toolResultItem, withMockStreaming };
|
|
5312
5528
|
|
|
5313
5529
|
//# sourceMappingURL=index.mjs.map
|