@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/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, capabilities) {
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.capabilities.nativeStreaming
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.capabilities.nativeStreaming,
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, this.capabilities);
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
- capabilities = CAPABILITY_MATRIX.responses;
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
- capabilities = CAPABILITY_MATRIX.messages;
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
- capabilities = {
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
- capabilities = {
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
- * 面向测试的脚本化 adapter:
2613
- * - turn 顺序消费请求,验证调用方是否正确续接 replay / tool_result
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
- capabilities = CAPABILITY_MATRIX.mock;
2622
- turns;
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.turns = options.turns;
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 turn = this.resolveTurn(turnIndex);
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
- turn,
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
- for (const [stepIndex, step] of mockRequest.turn.steps.entries()) switch (step.type) {
2660
- case "warning":
2661
- yield factory.responseWarning(step.message, step.code);
2662
- break;
2663
- case "auxiliary":
2664
- yield factory.responseAuxiliary({
2665
- usage: step.usage,
2666
- billing: step.billing,
2667
- auxiliary: step.auxiliary
2668
- });
2669
- break;
2670
- case "message": {
2671
- const item = createMessageFromStep(step, request, mockRequest.turnIndex, stepIndex);
2672
- yield* emitMessage(factory, item);
2673
- output.push(item);
2674
- break;
2675
- }
2676
- case "reasoning": {
2677
- const item = createReasoningFromStep(step, request, mockRequest.turnIndex, stepIndex);
2678
- yield* emitReasoning(factory, item);
2679
- output.push(item);
2680
- break;
2681
- }
2682
- case "tool_call": {
2683
- const item = createToolCallFromStep(step);
2684
- yield* emitToolCall(factory, item, step.streamArguments ?? true);
2685
- output.push(item);
2686
- break;
2687
- }
2688
- case "output": {
2689
- assertSupportedOutputItem(step.item);
2690
- const item = attachSyntheticId(step.item, request, mockRequest.turnIndex, stepIndex);
2691
- yield* emitOutputItem(factory, item);
2692
- output.push(item);
2693
- break;
2694
- }
2695
- case "complete": {
2696
- const response = this.finalizeTurn(request, factory, mockRequest, output, step);
2697
- yield factory.responseCompleted(response);
2698
- return;
2699
- }
2700
- case "error": {
2701
- yield factory.responseWarning(step.message, step.code);
2702
- const response = this.finalizeTurn(request, factory, mockRequest, output, {
2703
- type: "complete",
2704
- stopReason: step.stopReason ?? "error",
2705
- providerMetadata: step.providerMetadata
2706
- });
2707
- yield factory.responseCompleted(response);
2708
- return;
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
- turnName: mockRequest.turnName,
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
- resolveTurn(turnIndex) {
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
- for (const block of item.content) if (block.type === "text") yield factory.messageDelta(item.id, block.text);
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
- for (const block of item.content) yield factory.reasoningDelta(item.id, block);
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) yield factory.toolCallDelta(item.id, { argumentsText: 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, CAPABILITY_MATRIX, ChatCompletionsAdapter, MessagesAdapter, MockAdapter, OllamaAdapter, ResponsesAdapter, WarningCode, aggregateEvents, 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 };
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