@codehz/ai 0.1.2 → 0.1.4
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/.oxlintrc.json +1 -16
- package/AGENTS.md +37 -0
- package/README.md +102 -73
- package/dist/index.d.mts +44 -142
- package/dist/index.mjs +194 -219
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/adapters/chat-completions.ts +7 -29
- package/src/adapters/index.ts +6 -3
- package/src/adapters/messages.ts +21 -12
- package/src/adapters/mock.ts +332 -153
- package/src/adapters/ollama.ts +32 -33
- package/src/adapters/responses.ts +5 -8
- package/src/core/validation.ts +21 -16
- package/src/helpers/adapter-auxiliary.ts +4 -8
- package/src/helpers/adapter-base.ts +9 -10
- package/src/helpers/index.ts +1 -5
- package/src/types/adapter.ts +1 -82
- package/src/types/index.ts +1 -11
package/dist/index.mjs
CHANGED
|
@@ -1,67 +1,3 @@
|
|
|
1
|
-
//#region src/types/adapter.ts
|
|
2
|
-
const CAPABILITY_MATRIX = {
|
|
3
|
-
responses: {
|
|
4
|
-
nativeStreaming: true,
|
|
5
|
-
messageStreaming: true,
|
|
6
|
-
reasoningStreaming: true,
|
|
7
|
-
toolCallStreaming: true,
|
|
8
|
-
hiddenReasoningReplay: "full",
|
|
9
|
-
replayFidelity: "high",
|
|
10
|
-
tools: true,
|
|
11
|
-
usage: "full",
|
|
12
|
-
billing: "lookup",
|
|
13
|
-
providerMetadata: true
|
|
14
|
-
},
|
|
15
|
-
messages: {
|
|
16
|
-
nativeStreaming: true,
|
|
17
|
-
messageStreaming: true,
|
|
18
|
-
reasoningStreaming: false,
|
|
19
|
-
toolCallStreaming: true,
|
|
20
|
-
hiddenReasoningReplay: "partial",
|
|
21
|
-
replayFidelity: "medium",
|
|
22
|
-
tools: true,
|
|
23
|
-
usage: "full",
|
|
24
|
-
billing: "lookup",
|
|
25
|
-
providerMetadata: true
|
|
26
|
-
},
|
|
27
|
-
"chat.completions": {
|
|
28
|
-
nativeStreaming: true,
|
|
29
|
-
messageStreaming: true,
|
|
30
|
-
reasoningStreaming: false,
|
|
31
|
-
toolCallStreaming: false,
|
|
32
|
-
hiddenReasoningReplay: "none",
|
|
33
|
-
replayFidelity: "low",
|
|
34
|
-
tools: true,
|
|
35
|
-
usage: "full",
|
|
36
|
-
billing: "derived",
|
|
37
|
-
providerMetadata: false
|
|
38
|
-
},
|
|
39
|
-
ollama: {
|
|
40
|
-
nativeStreaming: true,
|
|
41
|
-
messageStreaming: true,
|
|
42
|
-
reasoningStreaming: false,
|
|
43
|
-
toolCallStreaming: false,
|
|
44
|
-
hiddenReasoningReplay: "none",
|
|
45
|
-
replayFidelity: "low",
|
|
46
|
-
tools: true,
|
|
47
|
-
usage: "partial",
|
|
48
|
-
billing: "none",
|
|
49
|
-
providerMetadata: false
|
|
50
|
-
},
|
|
51
|
-
mock: {
|
|
52
|
-
nativeStreaming: false,
|
|
53
|
-
messageStreaming: true,
|
|
54
|
-
reasoningStreaming: false,
|
|
55
|
-
toolCallStreaming: true,
|
|
56
|
-
hiddenReasoningReplay: "none",
|
|
57
|
-
replayFidelity: "high",
|
|
58
|
-
tools: true,
|
|
59
|
-
usage: "none",
|
|
60
|
-
billing: "none",
|
|
61
|
-
providerMetadata: true
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
//#endregion
|
|
65
1
|
//#region src/core/errors.ts
|
|
66
2
|
var AIError = class extends Error {
|
|
67
3
|
code;
|
|
@@ -901,12 +837,10 @@ function withTimeout(promise, ms) {
|
|
|
901
837
|
//#region src/helpers/adapter-auxiliary.ts
|
|
902
838
|
var AdapterAuxiliaryState = class {
|
|
903
839
|
request;
|
|
904
|
-
capabilities;
|
|
905
840
|
collector = new AuxiliaryCollector();
|
|
906
841
|
metadataSources = /* @__PURE__ */ new Set();
|
|
907
|
-
constructor(request
|
|
842
|
+
constructor(request) {
|
|
908
843
|
this.request = request;
|
|
909
|
-
this.capabilities = capabilities;
|
|
910
844
|
}
|
|
911
845
|
recordUsage(usage, source, raw) {
|
|
912
846
|
if (this.request.include?.usage === "off" || isEmptyRecord(usage)) return;
|
|
@@ -930,8 +864,7 @@ var AdapterAuxiliaryState = class {
|
|
|
930
864
|
request: this.request,
|
|
931
865
|
usage: snapshot.usage,
|
|
932
866
|
billing: snapshot.billing,
|
|
933
|
-
auxiliary: snapshot.auxiliary
|
|
934
|
-
capabilities: this.capabilities
|
|
867
|
+
auxiliary: snapshot.auxiliary
|
|
935
868
|
});
|
|
936
869
|
if (derived && !isEmptyRecord(derived)) this.collector.recordBilling({
|
|
937
870
|
...derived,
|
|
@@ -996,7 +929,7 @@ var AdapterBase = class {
|
|
|
996
929
|
responseId: request.requestId,
|
|
997
930
|
backend: {
|
|
998
931
|
kind: this.kind,
|
|
999
|
-
isSynthetic: !this.
|
|
932
|
+
isSynthetic: !this.nativeStreaming
|
|
1000
933
|
}
|
|
1001
934
|
});
|
|
1002
935
|
yield factory.responseStarted(request.model);
|
|
@@ -1035,7 +968,7 @@ var AdapterBase = class {
|
|
|
1035
968
|
requestId: request.requestId,
|
|
1036
969
|
rawResponseId: result.rawResponseId,
|
|
1037
970
|
adapter: this.kind,
|
|
1038
|
-
isSyntheticStream: !this.
|
|
971
|
+
isSyntheticStream: !this.nativeStreaming,
|
|
1039
972
|
metadataSources: result.metadataSources,
|
|
1040
973
|
warnings
|
|
1041
974
|
}
|
|
@@ -1046,7 +979,7 @@ var AdapterBase = class {
|
|
|
1046
979
|
return extractText(output);
|
|
1047
980
|
}
|
|
1048
981
|
createAuxiliaryState(request) {
|
|
1049
|
-
return new AdapterAuxiliaryState(request
|
|
982
|
+
return new AdapterAuxiliaryState(request);
|
|
1050
983
|
}
|
|
1051
984
|
};
|
|
1052
985
|
function mergeAuxiliary(base, patch) {
|
|
@@ -1187,7 +1120,7 @@ function canonicalToResponsesBlock(b) {
|
|
|
1187
1120
|
}
|
|
1188
1121
|
var ResponsesAdapter = class extends AdapterBase {
|
|
1189
1122
|
kind = "responses";
|
|
1190
|
-
|
|
1123
|
+
nativeStreaming = true;
|
|
1191
1124
|
apiKey;
|
|
1192
1125
|
baseUrl;
|
|
1193
1126
|
fetchFn;
|
|
@@ -1491,7 +1424,7 @@ function buildStreamMetadata(options) {
|
|
|
1491
1424
|
}
|
|
1492
1425
|
var MessagesAdapter = class extends AdapterBase {
|
|
1493
1426
|
kind = "messages";
|
|
1494
|
-
|
|
1427
|
+
nativeStreaming = true;
|
|
1495
1428
|
apiKey;
|
|
1496
1429
|
apiVersion;
|
|
1497
1430
|
baseUrl;
|
|
@@ -1945,26 +1878,10 @@ function buildAssistantReplayMessage(params) {
|
|
|
1945
1878
|
}
|
|
1946
1879
|
var ChatCompletionsAdapter = class extends AdapterBase {
|
|
1947
1880
|
kind = "chat-completions";
|
|
1948
|
-
|
|
1949
|
-
nativeStreaming: true,
|
|
1950
|
-
messageStreaming: true,
|
|
1951
|
-
reasoningStreaming: false,
|
|
1952
|
-
toolCallStreaming: false,
|
|
1953
|
-
hiddenReasoningReplay: "none",
|
|
1954
|
-
replayFidelity: "low",
|
|
1955
|
-
tools: true,
|
|
1956
|
-
usage: "full",
|
|
1957
|
-
billing: "derived",
|
|
1958
|
-
providerMetadata: false
|
|
1959
|
-
};
|
|
1881
|
+
nativeStreaming = true;
|
|
1960
1882
|
apiKey;
|
|
1961
1883
|
baseUrl;
|
|
1962
1884
|
fetchFn;
|
|
1963
|
-
markReasoningCompatibility() {
|
|
1964
|
-
this.capabilities.reasoningStreaming = true;
|
|
1965
|
-
this.capabilities.hiddenReasoningReplay = "partial";
|
|
1966
|
-
this.capabilities.replayFidelity = "medium";
|
|
1967
|
-
}
|
|
1968
1885
|
constructor(options) {
|
|
1969
1886
|
super();
|
|
1970
1887
|
this.apiKey = options.apiKey;
|
|
@@ -2089,7 +2006,6 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2089
2006
|
let currentReasoningId = "";
|
|
2090
2007
|
let hasMessageStarted = false;
|
|
2091
2008
|
let hasReasoningStarted = false;
|
|
2092
|
-
let hasStreamedReasoning = false;
|
|
2093
2009
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
2094
2010
|
const reasoningByField = /* @__PURE__ */ new Map();
|
|
2095
2011
|
const finalizePendingTurn = () => {
|
|
@@ -2164,7 +2080,6 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2164
2080
|
if (!hasReasoningStarted) {
|
|
2165
2081
|
currentReasoningId = `reason-${chunk.id}`;
|
|
2166
2082
|
hasReasoningStarted = true;
|
|
2167
|
-
hasStreamedReasoning = true;
|
|
2168
2083
|
accumulatedReasoning = "";
|
|
2169
2084
|
yield factory.reasoningStarted(currentReasoningId, "full");
|
|
2170
2085
|
}
|
|
@@ -2222,7 +2137,6 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2222
2137
|
if (finishReason && finishReason !== null) {
|
|
2223
2138
|
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
2224
2139
|
for (const event of events) yield event;
|
|
2225
|
-
if (hasStreamedReasoning) this.markReasoningCompatibility();
|
|
2226
2140
|
const stopReason = mapStopReason(finishReason);
|
|
2227
2141
|
const replay = [...replayFromOutput(output)];
|
|
2228
2142
|
if (assistantReplayMessage) replay.push(opaqueItem("chat.completions", "replay", {
|
|
@@ -2252,7 +2166,6 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2252
2166
|
if (buffer.trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Chat Completions SSE frame", "STREAM_ERROR");
|
|
2253
2167
|
if (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0) {
|
|
2254
2168
|
yield factory.responseWarning("Stream ended without a finish_reason", "INCOMPLETE_STREAM");
|
|
2255
|
-
if (hasStreamedReasoning) this.markReasoningCompatibility();
|
|
2256
2169
|
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
2257
2170
|
for (const event of events) yield event;
|
|
2258
2171
|
const replay = [...replayFromOutput(output)];
|
|
@@ -2357,18 +2270,7 @@ function isOllamaToolCalls(value) {
|
|
|
2357
2270
|
}
|
|
2358
2271
|
var OllamaAdapter = class extends AdapterBase {
|
|
2359
2272
|
kind = "ollama";
|
|
2360
|
-
|
|
2361
|
-
nativeStreaming: true,
|
|
2362
|
-
messageStreaming: true,
|
|
2363
|
-
reasoningStreaming: false,
|
|
2364
|
-
toolCallStreaming: false,
|
|
2365
|
-
hiddenReasoningReplay: "none",
|
|
2366
|
-
replayFidelity: "low",
|
|
2367
|
-
tools: true,
|
|
2368
|
-
usage: "partial",
|
|
2369
|
-
billing: "none",
|
|
2370
|
-
providerMetadata: false
|
|
2371
|
-
};
|
|
2273
|
+
nativeStreaming = true;
|
|
2372
2274
|
baseUrl;
|
|
2373
2275
|
apiKey;
|
|
2374
2276
|
fetchFn;
|
|
@@ -2609,18 +2511,34 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2609
2511
|
/**
|
|
2610
2512
|
* Mock Adapter
|
|
2611
2513
|
*
|
|
2612
|
-
*
|
|
2613
|
-
* -
|
|
2514
|
+
* 面向测试的回调驱动 adapter:
|
|
2515
|
+
* - 每次请求执行用户提供的 handler
|
|
2516
|
+
* - 验证调用方是否正确续接 replay / tool_result
|
|
2614
2517
|
* - 发出可控的 message / reasoning / tool_call 流
|
|
2615
2518
|
* - 注入 warning / auxiliary / content_filter / 中断 / provider error
|
|
2616
2519
|
*
|
|
2617
2520
|
* 这不是通用“假模型”,而是测试工具调用编排与错误路径的测试夹具。
|
|
2618
2521
|
*/
|
|
2522
|
+
function assertMockRequest(request, expectation, context) {
|
|
2523
|
+
const prefix = `MockAdapter turn ${context.turnIndex + 1} expectation failed`;
|
|
2524
|
+
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");
|
|
2525
|
+
if (expectation.maxItems !== void 0 && request.input.length > expectation.maxItems) throw new AIRequestError(`${prefix}: expected at most ${expectation.maxItems} input item(s)`, "MOCK_EXPECTATION_FAILED");
|
|
2526
|
+
if (expectation.tools === "present" && (!request.tools || request.tools.length === 0)) throw new AIRequestError(`${prefix}: expected tools to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2527
|
+
if (expectation.tools === "absent" && request.tools && request.tools.length > 0) throw new AIRequestError(`${prefix}: expected tools to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2528
|
+
if (expectation.toolChoice === "present" && request.toolChoice === void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2529
|
+
if (expectation.toolChoice === "absent" && request.toolChoice !== void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2530
|
+
if (expectation.requireReplayFromPreviousTurn && context.previousReplay.length > 0) assertReplayIncluded(request.input, context.previousReplay, prefix);
|
|
2531
|
+
if (expectation.requireToolResultsForPendingCalls && context.pendingToolCalls.length > 0) {
|
|
2532
|
+
const toolResultIds = new Set(request.input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2533
|
+
for (const call of context.pendingToolCalls) if (!toolResultIds.has(call.id)) throw new AIRequestError(`${prefix}: expected tool_result for pending tool call "${call.id}"`, "MOCK_EXPECTATION_FAILED");
|
|
2534
|
+
}
|
|
2535
|
+
if (expectation.items && expectation.items.length > 0) if (expectation.ordered) assertOrderedItems(request.input, expectation.items, prefix);
|
|
2536
|
+
else assertUnorderedItems(request.input, expectation.items, prefix);
|
|
2537
|
+
}
|
|
2619
2538
|
var MockAdapter = class extends AdapterBase {
|
|
2620
2539
|
kind = "mock";
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
onExhausted;
|
|
2540
|
+
nativeStreaming = false;
|
|
2541
|
+
handler;
|
|
2624
2542
|
providerMetadata;
|
|
2625
2543
|
cursor = 0;
|
|
2626
2544
|
previousReplay = [];
|
|
@@ -2629,24 +2547,19 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2629
2547
|
activeStream = false;
|
|
2630
2548
|
constructor(options) {
|
|
2631
2549
|
super();
|
|
2632
|
-
this.
|
|
2633
|
-
this.onExhausted = options.onExhausted ?? "throw";
|
|
2550
|
+
this.handler = options.handler;
|
|
2634
2551
|
this.providerMetadata = options.providerMetadata;
|
|
2635
2552
|
}
|
|
2636
2553
|
async buildRequest(request) {
|
|
2637
2554
|
const turnIndex = this.cursor;
|
|
2638
|
-
const
|
|
2639
|
-
const turnName = turn.name;
|
|
2640
|
-
const context = this.buildTurnContext(turnIndex);
|
|
2641
|
-
if (turn.expect) if (typeof turn.expect === "function") await turn.expect(request, context);
|
|
2642
|
-
else assertRequestMatchesExpectation(request, turn.expect, context);
|
|
2555
|
+
const context = this.buildHandlerContext(turnIndex);
|
|
2643
2556
|
const remainingPendingToolCalls = consumePendingToolCalls(this.pendingToolCalls, request.input);
|
|
2557
|
+
const handlerResult = this.handler(request, context);
|
|
2644
2558
|
this.cursor += 1;
|
|
2645
2559
|
return {
|
|
2646
2560
|
request,
|
|
2647
|
-
|
|
2561
|
+
handlerResult,
|
|
2648
2562
|
turnIndex,
|
|
2649
|
-
turnName,
|
|
2650
2563
|
remainingPendingToolCalls
|
|
2651
2564
|
};
|
|
2652
2565
|
}
|
|
@@ -2656,76 +2569,79 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2656
2569
|
try {
|
|
2657
2570
|
const mockRequest = providerRequest;
|
|
2658
2571
|
const output = [];
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2572
|
+
let stepCount = 0;
|
|
2573
|
+
for await (const step of mockRequest.handlerResult) {
|
|
2574
|
+
stepCount += 1;
|
|
2575
|
+
switch (step.type) {
|
|
2576
|
+
case "warning":
|
|
2577
|
+
yield factory.responseWarning(step.message, step.code);
|
|
2578
|
+
break;
|
|
2579
|
+
case "auxiliary":
|
|
2580
|
+
yield factory.responseAuxiliary({
|
|
2581
|
+
usage: step.usage,
|
|
2582
|
+
billing: step.billing,
|
|
2583
|
+
auxiliary: step.auxiliary
|
|
2584
|
+
});
|
|
2585
|
+
break;
|
|
2586
|
+
case "message": {
|
|
2587
|
+
const item = createMessageFromStep(step, request, mockRequest.turnIndex, stepCount - 1);
|
|
2588
|
+
yield* emitMessage(factory, item, resolveStepStreamOptions(void 0, step.stream, "message"));
|
|
2589
|
+
output.push(item);
|
|
2590
|
+
break;
|
|
2591
|
+
}
|
|
2592
|
+
case "reasoning": {
|
|
2593
|
+
const item = createReasoningFromStep(step, request, mockRequest.turnIndex, stepCount - 1);
|
|
2594
|
+
yield* emitReasoning(factory, item, resolveStepStreamOptions(void 0, step.stream, "reasoning"));
|
|
2595
|
+
output.push(item);
|
|
2596
|
+
break;
|
|
2597
|
+
}
|
|
2598
|
+
case "tool_call": {
|
|
2599
|
+
const item = createToolCallFromStep(step);
|
|
2600
|
+
yield* emitToolCall(factory, item, step.streamArguments ?? true, resolveStepStreamOptions(void 0, step.stream, "tool_call"));
|
|
2601
|
+
output.push(item);
|
|
2602
|
+
break;
|
|
2603
|
+
}
|
|
2604
|
+
case "output": {
|
|
2605
|
+
assertSupportedOutputItem(step.item);
|
|
2606
|
+
const item = attachSyntheticId(step.item, request, mockRequest.turnIndex, stepCount - 1);
|
|
2607
|
+
yield* emitOutputItem(factory, item, resolveStepStreamOptions(void 0, step.stream, "output"));
|
|
2608
|
+
output.push(item);
|
|
2609
|
+
break;
|
|
2610
|
+
}
|
|
2611
|
+
case "complete": {
|
|
2612
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, step, stepCount);
|
|
2613
|
+
yield factory.responseCompleted(response);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
case "error": {
|
|
2617
|
+
yield factory.responseWarning(step.message, step.code);
|
|
2618
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, {
|
|
2619
|
+
type: "complete",
|
|
2620
|
+
stopReason: step.stopReason ?? "error",
|
|
2621
|
+
providerMetadata: step.providerMetadata
|
|
2622
|
+
}, stepCount);
|
|
2623
|
+
yield factory.responseCompleted(response);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
case "interrupt":
|
|
2627
|
+
this.pendingToolCalls = mockRequest.remainingPendingToolCalls;
|
|
2628
|
+
return;
|
|
2629
|
+
case "throw": throw typeof step.error === "string" ? new Error(step.error) : step.error;
|
|
2709
2630
|
}
|
|
2710
|
-
case "interrupt":
|
|
2711
|
-
this.pendingToolCalls = mockRequest.remainingPendingToolCalls;
|
|
2712
|
-
return;
|
|
2713
|
-
case "throw": throw typeof step.error === "string" ? new Error(step.error) : step.error;
|
|
2714
2631
|
}
|
|
2715
|
-
const response = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" });
|
|
2632
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" }, stepCount);
|
|
2716
2633
|
yield factory.responseCompleted(response);
|
|
2717
2634
|
} finally {
|
|
2718
2635
|
this.activeStream = false;
|
|
2719
2636
|
}
|
|
2720
2637
|
}
|
|
2721
|
-
finalizeTurn(request, factory, mockRequest, output, completion) {
|
|
2638
|
+
finalizeTurn(request, factory, mockRequest, output, completion, stepCount) {
|
|
2722
2639
|
const replay = completion.replay ?? replayFromOutput(output);
|
|
2723
2640
|
const toolCalls = output.filter((item) => item.type === "tool_call");
|
|
2724
2641
|
this.previousReplay = replay;
|
|
2725
2642
|
this.pendingToolCalls = [...mockRequest.remainingPendingToolCalls, ...toolCalls];
|
|
2726
2643
|
this.history.push({
|
|
2727
2644
|
turnIndex: mockRequest.turnIndex,
|
|
2728
|
-
turnName: mockRequest.turnName,
|
|
2729
2645
|
requestId: request.requestId,
|
|
2730
2646
|
replay,
|
|
2731
2647
|
toolCalls
|
|
@@ -2739,8 +2655,7 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2739
2655
|
auxiliary: completion.auxiliary,
|
|
2740
2656
|
providerMetadata: {
|
|
2741
2657
|
turnIndex: mockRequest.turnIndex,
|
|
2742
|
-
|
|
2743
|
-
scriptedSteps: mockRequest.turn.steps.length,
|
|
2658
|
+
stepCount,
|
|
2744
2659
|
pendingToolCallIds: this.pendingToolCalls.map((item) => item.id),
|
|
2745
2660
|
historyLength: this.history.length,
|
|
2746
2661
|
...this.providerMetadata,
|
|
@@ -2751,18 +2666,7 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2751
2666
|
rawResponseId: completion.rawResponseId
|
|
2752
2667
|
}, factory);
|
|
2753
2668
|
}
|
|
2754
|
-
|
|
2755
|
-
const turn = this.turns[turnIndex];
|
|
2756
|
-
if (turn !== void 0) return turn;
|
|
2757
|
-
const lastTurn = this.turns.at(-1);
|
|
2758
|
-
if (this.onExhausted === "repeat-last" && lastTurn !== void 0) return lastTurn;
|
|
2759
|
-
if (this.onExhausted === "complete-empty") return {
|
|
2760
|
-
name: "exhausted",
|
|
2761
|
-
steps: []
|
|
2762
|
-
};
|
|
2763
|
-
throw new AIRequestError(`MockAdapter turn ${turnIndex + 1} requested, but only ${this.turns.length} turn(s) were scripted`, "MOCK_TURN_EXHAUSTED");
|
|
2764
|
-
}
|
|
2765
|
-
buildTurnContext(turnIndex) {
|
|
2669
|
+
buildHandlerContext(turnIndex) {
|
|
2766
2670
|
return {
|
|
2767
2671
|
turnIndex,
|
|
2768
2672
|
previousReplay: this.previousReplay.map(cloneItem),
|
|
@@ -2775,6 +2679,32 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2775
2679
|
};
|
|
2776
2680
|
}
|
|
2777
2681
|
};
|
|
2682
|
+
function withMockStreaming(handler, options) {
|
|
2683
|
+
const defaults = resolveMockTextStreamOptions(options, "mock stream wrapper");
|
|
2684
|
+
if (!defaults) throw new AIRequestError("mock stream wrapper requires streaming options", "MOCK_STREAM_CONFIG_INVALID");
|
|
2685
|
+
return async function* streamWrappedHandler(request, context) {
|
|
2686
|
+
const source = await handler(request, context);
|
|
2687
|
+
for await (const step of source) yield applyDefaultStreaming(step, defaults);
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2690
|
+
function applyDefaultStreaming(step, defaults) {
|
|
2691
|
+
switch (step.type) {
|
|
2692
|
+
case "message":
|
|
2693
|
+
case "reasoning":
|
|
2694
|
+
case "tool_call":
|
|
2695
|
+
case "output":
|
|
2696
|
+
if (step.stream !== void 0) return step;
|
|
2697
|
+
return {
|
|
2698
|
+
...step,
|
|
2699
|
+
stream: {
|
|
2700
|
+
charsPerSecond: defaults.charsPerSecond,
|
|
2701
|
+
chunkSize: defaults.chunkSize,
|
|
2702
|
+
initialDelayMs: defaults.initialDelayMs
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
default: return step;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2778
2708
|
function createMessageFromStep(step, request, turnIndex, stepIndex) {
|
|
2779
2709
|
return {
|
|
2780
2710
|
...messageItem(normalizeBlocks(step.content), { id: step.id ?? `mock-msg-${request.requestId}-${turnIndex}-${stepIndex}` }),
|
|
@@ -2811,34 +2741,95 @@ function attachSyntheticId(item, request, turnIndex, stepIndex) {
|
|
|
2811
2741
|
};
|
|
2812
2742
|
return item;
|
|
2813
2743
|
}
|
|
2814
|
-
async function* emitOutputItem(factory, item) {
|
|
2744
|
+
async function* emitOutputItem(factory, item, stream) {
|
|
2815
2745
|
if (item.type === "message") {
|
|
2816
|
-
yield* emitMessage(factory, item);
|
|
2746
|
+
yield* emitMessage(factory, item, stream);
|
|
2817
2747
|
return;
|
|
2818
2748
|
}
|
|
2819
2749
|
if (item.type === "reasoning") {
|
|
2820
|
-
yield* emitReasoning(factory, item);
|
|
2750
|
+
yield* emitReasoning(factory, item, stream);
|
|
2821
2751
|
return;
|
|
2822
2752
|
}
|
|
2823
|
-
yield* emitToolCall(factory, item, true);
|
|
2753
|
+
yield* emitToolCall(factory, item, true, stream);
|
|
2824
2754
|
}
|
|
2825
|
-
async function* emitMessage(factory, item) {
|
|
2755
|
+
async function* emitMessage(factory, item, stream) {
|
|
2826
2756
|
if (!item.id) throw new AIRequestError("Mock message output requires an id after normalization", "MOCK_MESSAGE_ID_MISSING");
|
|
2827
2757
|
yield factory.messageStarted(item.id);
|
|
2828
|
-
|
|
2758
|
+
let chunkIndex = 0;
|
|
2759
|
+
for (const block of item.content) if (block.type === "text") for (const chunk of chunkText(block.text, stream)) {
|
|
2760
|
+
await delayForChunk(stream, chunkIndex, chunk.length);
|
|
2761
|
+
yield factory.messageDelta(item.id, chunk);
|
|
2762
|
+
chunkIndex += 1;
|
|
2763
|
+
}
|
|
2829
2764
|
yield factory.messageCompleted(item);
|
|
2830
2765
|
}
|
|
2831
|
-
async function* emitReasoning(factory, item) {
|
|
2766
|
+
async function* emitReasoning(factory, item, stream) {
|
|
2832
2767
|
if (!item.id) throw new AIRequestError("Mock reasoning output requires an id after normalization", "MOCK_REASONING_ID_MISSING");
|
|
2833
2768
|
yield factory.reasoningStarted(item.id, item.visibility);
|
|
2834
|
-
|
|
2769
|
+
let chunkIndex = 0;
|
|
2770
|
+
for (const block of item.content) {
|
|
2771
|
+
if (block.type !== "text") {
|
|
2772
|
+
yield factory.reasoningDelta(item.id, block);
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
for (const chunk of chunkText(block.text, stream)) {
|
|
2776
|
+
await delayForChunk(stream, chunkIndex, chunk.length);
|
|
2777
|
+
yield factory.reasoningDelta(item.id, textBlock(chunk));
|
|
2778
|
+
chunkIndex += 1;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2835
2781
|
yield factory.reasoningCompleted(item);
|
|
2836
2782
|
}
|
|
2837
|
-
async function* emitToolCall(factory, item, streamArguments) {
|
|
2783
|
+
async function* emitToolCall(factory, item, streamArguments, stream) {
|
|
2838
2784
|
yield factory.toolCallStarted(item.id, item.name);
|
|
2839
|
-
if (streamArguments && item.argumentsText)
|
|
2785
|
+
if (streamArguments && item.argumentsText) {
|
|
2786
|
+
let chunkIndex = 0;
|
|
2787
|
+
for (const chunk of chunkText(item.argumentsText, stream)) {
|
|
2788
|
+
await delayForChunk(stream, chunkIndex, chunk.length);
|
|
2789
|
+
yield factory.toolCallDelta(item.id, { argumentsText: chunk });
|
|
2790
|
+
chunkIndex += 1;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2840
2793
|
yield factory.toolCallCompleted(item);
|
|
2841
2794
|
}
|
|
2795
|
+
function resolveStepStreamOptions(defaults, override, label) {
|
|
2796
|
+
if (override === false) return;
|
|
2797
|
+
return resolveMockTextStreamOptions(override, `${label} stream`, defaults);
|
|
2798
|
+
}
|
|
2799
|
+
function resolveMockTextStreamOptions(options, label, defaults) {
|
|
2800
|
+
if (options === void 0) return defaults;
|
|
2801
|
+
const chunkSize = options.chunkSize ?? defaults?.chunkSize ?? 1;
|
|
2802
|
+
const initialDelayMs = options.initialDelayMs ?? defaults?.initialDelayMs ?? 0;
|
|
2803
|
+
const charsPerSecond = options.charsPerSecond ?? defaults?.charsPerSecond;
|
|
2804
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1) throw new AIRequestError(`${label}: chunkSize must be a positive integer`, "MOCK_STREAM_CONFIG_INVALID");
|
|
2805
|
+
if (!Number.isFinite(initialDelayMs) || initialDelayMs < 0) throw new AIRequestError(`${label}: initialDelayMs must be a non-negative number`, "MOCK_STREAM_CONFIG_INVALID");
|
|
2806
|
+
if (charsPerSecond !== void 0 && (!Number.isFinite(charsPerSecond) || charsPerSecond <= 0)) throw new AIRequestError(`${label}: charsPerSecond must be a positive number`, "MOCK_STREAM_CONFIG_INVALID");
|
|
2807
|
+
return {
|
|
2808
|
+
chunkSize,
|
|
2809
|
+
initialDelayMs,
|
|
2810
|
+
charsPerSecond
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
function chunkText(text, stream) {
|
|
2814
|
+
if (!text) return [];
|
|
2815
|
+
if (!stream) return [text];
|
|
2816
|
+
const chars = Array.from(text);
|
|
2817
|
+
const chunks = [];
|
|
2818
|
+
for (let index = 0; index < chars.length; index += stream.chunkSize) chunks.push(chars.slice(index, index + stream.chunkSize).join(""));
|
|
2819
|
+
return chunks;
|
|
2820
|
+
}
|
|
2821
|
+
async function delayForChunk(stream, chunkIndex, chunkLength) {
|
|
2822
|
+
if (!stream) return;
|
|
2823
|
+
if (chunkIndex === 0 && stream.initialDelayMs > 0) {
|
|
2824
|
+
await sleep(stream.initialDelayMs);
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
if (chunkIndex > 0 && stream.charsPerSecond !== void 0) await sleep(chunkLength / stream.charsPerSecond * 1e3);
|
|
2828
|
+
}
|
|
2829
|
+
async function sleep(ms) {
|
|
2830
|
+
if (ms <= 0) return;
|
|
2831
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2832
|
+
}
|
|
2842
2833
|
function resolveStopReason(output) {
|
|
2843
2834
|
return output.some((item) => item.type === "tool_call") ? "tool_call" : "end_turn";
|
|
2844
2835
|
}
|
|
@@ -2846,22 +2837,6 @@ function consumePendingToolCalls(pending, input) {
|
|
|
2846
2837
|
const fulfilledIds = new Set(input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2847
2838
|
return pending.filter((item) => !fulfilledIds.has(item.id)).map(cloneItem);
|
|
2848
2839
|
}
|
|
2849
|
-
function assertRequestMatchesExpectation(request, expectation, context) {
|
|
2850
|
-
const prefix = `MockAdapter turn ${context.turnIndex + 1} expectation failed`;
|
|
2851
|
-
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");
|
|
2852
|
-
if (expectation.maxItems !== void 0 && request.input.length > expectation.maxItems) throw new AIRequestError(`${prefix}: expected at most ${expectation.maxItems} input item(s)`, "MOCK_EXPECTATION_FAILED");
|
|
2853
|
-
if (expectation.tools === "present" && (!request.tools || request.tools.length === 0)) throw new AIRequestError(`${prefix}: expected tools to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2854
|
-
if (expectation.tools === "absent" && request.tools && request.tools.length > 0) throw new AIRequestError(`${prefix}: expected tools to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2855
|
-
if (expectation.toolChoice === "present" && request.toolChoice === void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2856
|
-
if (expectation.toolChoice === "absent" && request.toolChoice !== void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2857
|
-
if (expectation.requireReplayFromPreviousTurn && context.previousReplay.length > 0) assertReplayIncluded(request.input, context.previousReplay, prefix);
|
|
2858
|
-
if (expectation.requireToolResultsForPendingCalls && context.pendingToolCalls.length > 0) {
|
|
2859
|
-
const toolResultIds = new Set(request.input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2860
|
-
for (const call of context.pendingToolCalls) if (!toolResultIds.has(call.id)) throw new AIRequestError(`${prefix}: expected tool_result for pending tool call "${call.id}"`, "MOCK_EXPECTATION_FAILED");
|
|
2861
|
-
}
|
|
2862
|
-
if (expectation.items && expectation.items.length > 0) if (expectation.ordered) assertOrderedItems(request.input, expectation.items, prefix);
|
|
2863
|
-
else assertUnorderedItems(request.input, expectation.items, prefix);
|
|
2864
|
-
}
|
|
2865
2840
|
function assertReplayIncluded(input, replay, prefix) {
|
|
2866
2841
|
const fingerprints = input.map(fingerprintItem);
|
|
2867
2842
|
let cursor = 0;
|
|
@@ -3028,6 +3003,6 @@ function* emitToolCallEvents(item, factory) {
|
|
|
3028
3003
|
yield factory.toolCallCompleted(item);
|
|
3029
3004
|
}
|
|
3030
3005
|
//#endregion
|
|
3031
|
-
export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector,
|
|
3006
|
+
export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, MessagesAdapter, MockAdapter, OllamaAdapter, ResponsesAdapter, WarningCode, aggregateEvents, assertMockRequest, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, reasoningItem, replayFromOutput, syntheticStream, textBlock, toolCallItem, toolResultItem, validateRequest, withMockStreaming };
|
|
3032
3007
|
|
|
3033
3008
|
//# sourceMappingURL=index.mjs.map
|