@codehz/ai 0.1.1 → 0.1.2
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 +2 -0
- package/dist/index.d.mts +174 -7
- package/dist/index.mjs +382 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -47,6 +47,18 @@ const CAPABILITY_MATRIX = {
|
|
|
47
47
|
usage: "partial",
|
|
48
48
|
billing: "none",
|
|
49
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
|
|
50
62
|
}
|
|
51
63
|
};
|
|
52
64
|
//#endregion
|
|
@@ -491,17 +503,14 @@ function createEventFactory(state) {
|
|
|
491
503
|
}
|
|
492
504
|
//#endregion
|
|
493
505
|
//#region src/core/aggregator.ts
|
|
494
|
-
function
|
|
506
|
+
function createAggregatorState() {
|
|
495
507
|
return {
|
|
496
|
-
pendingMessages: /* @__PURE__ */ new Map(),
|
|
497
|
-
pendingReasonings: /* @__PURE__ */ new Map(),
|
|
498
|
-
pendingToolCalls: /* @__PURE__ */ new Map(),
|
|
499
|
-
outputOrder: [],
|
|
500
|
-
completedMessages: /* @__PURE__ */ new Map(),
|
|
501
|
-
completedReasonings: /* @__PURE__ */ new Map(),
|
|
502
|
-
completedToolCalls: /* @__PURE__ */ new Map(),
|
|
503
508
|
auxiliary: {},
|
|
504
|
-
warnings: []
|
|
509
|
+
warnings: [],
|
|
510
|
+
warningSet: /* @__PURE__ */ new Set(),
|
|
511
|
+
output: [],
|
|
512
|
+
textParts: [],
|
|
513
|
+
toolCalls: []
|
|
505
514
|
};
|
|
506
515
|
}
|
|
507
516
|
function handleResponseStarted(state, event) {
|
|
@@ -523,55 +532,16 @@ function handleResponseAuxiliary(state, event) {
|
|
|
523
532
|
};
|
|
524
533
|
if (event.auxiliary) state.auxiliary = mergeAuxiliary$1(state.auxiliary, event.auxiliary);
|
|
525
534
|
}
|
|
526
|
-
function handleMessageStarted(state, event) {
|
|
527
|
-
state.pendingMessages.set(event.item.id, {
|
|
528
|
-
role: event.item.role,
|
|
529
|
-
texts: []
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
function handleMessageDelta(state, event) {
|
|
533
|
-
const pending = state.pendingMessages.get(event.itemId);
|
|
534
|
-
if (pending) pending.texts.push(event.delta.text);
|
|
535
|
-
}
|
|
536
535
|
function handleMessageCompleted(state, event) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
state.completedMessages.set(itemId, item);
|
|
540
|
-
state.outputOrder.push(itemId);
|
|
541
|
-
state.pendingMessages.delete(itemId);
|
|
542
|
-
}
|
|
543
|
-
function handleReasoningStarted(state, event) {
|
|
544
|
-
state.pendingReasonings.set(event.item.id, {
|
|
545
|
-
visibility: event.item.visibility,
|
|
546
|
-
blocks: []
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
function handleReasoningDelta(state, event) {
|
|
550
|
-
const pending = state.pendingReasonings.get(event.itemId);
|
|
551
|
-
if (pending) pending.blocks.push(event.delta);
|
|
536
|
+
state.output.push(event.item);
|
|
537
|
+
pushMessageText(state, event.item);
|
|
552
538
|
}
|
|
553
539
|
function handleReasoningCompleted(state, event) {
|
|
554
|
-
|
|
555
|
-
const stableId = item.id ?? `reason-${state.outputOrder.length}-${Date.now()}`;
|
|
556
|
-
state.completedReasonings.set(stableId, item);
|
|
557
|
-
state.outputOrder.push(stableId);
|
|
558
|
-
state.pendingReasonings.delete(item.id ?? "");
|
|
559
|
-
}
|
|
560
|
-
function handleToolCallStarted(state, event) {
|
|
561
|
-
state.pendingToolCalls.set(event.item.id, {
|
|
562
|
-
name: event.item.name,
|
|
563
|
-
argsParts: []
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
function handleToolCallDelta(state, event) {
|
|
567
|
-
const pending = state.pendingToolCalls.get(event.itemId);
|
|
568
|
-
if (pending && event.delta.argumentsText) pending.argsParts.push(event.delta.argumentsText);
|
|
540
|
+
state.output.push(event.item);
|
|
569
541
|
}
|
|
570
542
|
function handleToolCallCompleted(state, event) {
|
|
571
|
-
|
|
572
|
-
state.
|
|
573
|
-
state.outputOrder.push(item.id);
|
|
574
|
-
state.pendingToolCalls.delete(item.id);
|
|
543
|
+
state.output.push(event.item);
|
|
544
|
+
state.toolCalls.push(event.item);
|
|
575
545
|
}
|
|
576
546
|
function handleResponseCompleted(state, event) {
|
|
577
547
|
state.replayFromAdapter = event.response.replay;
|
|
@@ -590,26 +560,6 @@ function handleResponseCompleted(state, event) {
|
|
|
590
560
|
if (event.response.warnings) pushWarnings(state, event.response.warnings);
|
|
591
561
|
}
|
|
592
562
|
function buildResponse(state) {
|
|
593
|
-
const output = [];
|
|
594
|
-
for (const id of state.outputOrder) {
|
|
595
|
-
const msg = state.completedMessages.get(id);
|
|
596
|
-
if (msg) {
|
|
597
|
-
output.push(msg);
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
const reason = state.completedReasonings.get(id);
|
|
601
|
-
if (reason) {
|
|
602
|
-
output.push(reason);
|
|
603
|
-
continue;
|
|
604
|
-
}
|
|
605
|
-
const tc = state.completedToolCalls.get(id);
|
|
606
|
-
if (tc) {
|
|
607
|
-
output.push(tc);
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
const text = output.filter((item) => item.type === "message").flatMap((m) => m.content).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
612
|
-
const toolCalls = output.filter((item) => item.type === "tool_call");
|
|
613
563
|
const backendFromResponse = state.backendFromAdapter;
|
|
614
564
|
const backend = {
|
|
615
565
|
adapter: backendFromResponse?.adapter ?? state.backendInfo?.kind ?? "unknown",
|
|
@@ -621,10 +571,10 @@ function buildResponse(state) {
|
|
|
621
571
|
};
|
|
622
572
|
return {
|
|
623
573
|
id: state.responseIdFromAdapter ?? state.responseId,
|
|
624
|
-
output,
|
|
574
|
+
output: state.output,
|
|
625
575
|
replay: state.replayFromAdapter ?? [],
|
|
626
|
-
text,
|
|
627
|
-
toolCalls,
|
|
576
|
+
text: state.textParts.join(""),
|
|
577
|
+
toolCalls: state.toolCalls,
|
|
628
578
|
stopReason: state.stopReasonFromAdapter,
|
|
629
579
|
usage: state.usage,
|
|
630
580
|
billing: state.billing,
|
|
@@ -638,8 +588,13 @@ function buildResponse(state) {
|
|
|
638
588
|
* 适用于测试和离线处理场景。
|
|
639
589
|
*/
|
|
640
590
|
function aggregateEvents(events) {
|
|
641
|
-
const state =
|
|
642
|
-
for (const event of events)
|
|
591
|
+
const state = createAggregatorState();
|
|
592
|
+
for (const event of events) aggregateEvent(state, event);
|
|
593
|
+
return finalizeAggregation(state);
|
|
594
|
+
}
|
|
595
|
+
function aggregateEvent(state, event) {
|
|
596
|
+
state.lastEventType = event.type;
|
|
597
|
+
switch (event.type) {
|
|
643
598
|
case "response.started":
|
|
644
599
|
handleResponseStarted(state, event);
|
|
645
600
|
break;
|
|
@@ -650,29 +605,17 @@ function aggregateEvents(events) {
|
|
|
650
605
|
handleResponseAuxiliary(state, event);
|
|
651
606
|
break;
|
|
652
607
|
case "message.started":
|
|
653
|
-
handleMessageStarted(state, event);
|
|
654
|
-
break;
|
|
655
608
|
case "message.delta":
|
|
656
|
-
handleMessageDelta(state, event);
|
|
657
|
-
break;
|
|
658
|
-
case "message.completed":
|
|
659
|
-
handleMessageCompleted(state, event);
|
|
660
|
-
break;
|
|
661
609
|
case "reasoning.started":
|
|
662
|
-
handleReasoningStarted(state, event);
|
|
663
|
-
break;
|
|
664
610
|
case "reasoning.delta":
|
|
665
|
-
|
|
611
|
+
case "tool_call.started":
|
|
612
|
+
case "tool_call.delta": break;
|
|
613
|
+
case "message.completed":
|
|
614
|
+
handleMessageCompleted(state, event);
|
|
666
615
|
break;
|
|
667
616
|
case "reasoning.completed":
|
|
668
617
|
handleReasoningCompleted(state, event);
|
|
669
618
|
break;
|
|
670
|
-
case "tool_call.started":
|
|
671
|
-
handleToolCallStarted(state, event);
|
|
672
|
-
break;
|
|
673
|
-
case "tool_call.delta":
|
|
674
|
-
handleToolCallDelta(state, event);
|
|
675
|
-
break;
|
|
676
619
|
case "tool_call.completed":
|
|
677
620
|
handleToolCallCompleted(state, event);
|
|
678
621
|
break;
|
|
@@ -680,8 +623,9 @@ function aggregateEvents(events) {
|
|
|
680
623
|
handleResponseCompleted(state, event);
|
|
681
624
|
break;
|
|
682
625
|
}
|
|
683
|
-
|
|
684
|
-
|
|
626
|
+
}
|
|
627
|
+
function finalizeAggregation(state) {
|
|
628
|
+
if (state.lastEventType !== "response.completed") throw new Error("Stream must end with response.completed event to produce a valid AIResponse");
|
|
685
629
|
return buildResponse(state);
|
|
686
630
|
}
|
|
687
631
|
function mergeAuxiliary$1(base, patch) {
|
|
@@ -690,20 +634,26 @@ function mergeAuxiliary$1(base, patch) {
|
|
|
690
634
|
...patch
|
|
691
635
|
};
|
|
692
636
|
if (base.providerMetadata || patch.providerMetadata) merged.providerMetadata = {
|
|
693
|
-
...base.providerMetadata
|
|
694
|
-
...patch.providerMetadata
|
|
637
|
+
...base.providerMetadata,
|
|
638
|
+
...patch.providerMetadata
|
|
695
639
|
};
|
|
696
640
|
return merged;
|
|
697
641
|
}
|
|
698
642
|
function pushWarnings(state, warnings) {
|
|
699
|
-
for (const warning of warnings) if (!state.
|
|
643
|
+
for (const warning of warnings) if (!state.warningSet.has(warning)) {
|
|
644
|
+
state.warningSet.add(warning);
|
|
645
|
+
state.warnings.push(warning);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function pushMessageText(state, item) {
|
|
649
|
+
for (const block of item.content) if (block.type === "text") state.textParts.push(block.text);
|
|
700
650
|
}
|
|
701
651
|
//#endregion
|
|
702
652
|
//#region src/core/collect-stream.ts
|
|
703
653
|
async function collectStream(stream) {
|
|
704
|
-
const
|
|
705
|
-
for await (const event of stream)
|
|
706
|
-
return
|
|
654
|
+
const state = createAggregatorState();
|
|
655
|
+
for await (const event of stream) aggregateEvent(state, event);
|
|
656
|
+
return finalizeAggregation(state);
|
|
707
657
|
}
|
|
708
658
|
//#endregion
|
|
709
659
|
//#region src/helpers/mapping.ts
|
|
@@ -1102,12 +1052,12 @@ var AdapterBase = class {
|
|
|
1102
1052
|
function mergeAuxiliary(base, patch) {
|
|
1103
1053
|
if (!base && !patch) return void 0;
|
|
1104
1054
|
const merged = {
|
|
1105
|
-
...base
|
|
1106
|
-
...patch
|
|
1055
|
+
...base,
|
|
1056
|
+
...patch
|
|
1107
1057
|
};
|
|
1108
1058
|
if (base?.providerMetadata || patch?.providerMetadata) merged.providerMetadata = {
|
|
1109
|
-
...base?.providerMetadata
|
|
1110
|
-
...patch?.providerMetadata
|
|
1059
|
+
...base?.providerMetadata,
|
|
1060
|
+
...patch?.providerMetadata
|
|
1111
1061
|
};
|
|
1112
1062
|
return merged;
|
|
1113
1063
|
}
|
|
@@ -1189,6 +1139,7 @@ function parseSSEEvents(chunk) {
|
|
|
1189
1139
|
function ensureResponsesTextBlocks(blocks, field) {
|
|
1190
1140
|
for (let i = 0; i < blocks.length; i++) {
|
|
1191
1141
|
const block = blocks[i];
|
|
1142
|
+
if (!block) continue;
|
|
1192
1143
|
if (block.type !== "text" && block.type !== "json") throw new AIRequestError(`responses does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1193
1144
|
}
|
|
1194
1145
|
return blocks;
|
|
@@ -1468,6 +1419,7 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1468
1419
|
function ensureMessagesTextBlocks(blocks, field) {
|
|
1469
1420
|
for (let i = 0; i < blocks.length; i++) {
|
|
1470
1421
|
const block = blocks[i];
|
|
1422
|
+
if (!block) continue;
|
|
1471
1423
|
if (block.type !== "text" && block.type !== "json") throw new AIRequestError(`messages does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1472
1424
|
}
|
|
1473
1425
|
return blocks;
|
|
@@ -1932,6 +1884,7 @@ function parseChatSSE(buffer) {
|
|
|
1932
1884
|
function ensureTextCompatibleBlocks(blocks, field) {
|
|
1933
1885
|
for (let i = 0; i < blocks.length; i++) {
|
|
1934
1886
|
const block = blocks[i];
|
|
1887
|
+
if (!block) continue;
|
|
1935
1888
|
if (block.type !== "text" && block.type !== "json") throw new AIRequestError(`chat-completions does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1936
1889
|
}
|
|
1937
1890
|
return blocks;
|
|
@@ -2343,6 +2296,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2343
2296
|
function ensureOllamaTextBlocks(blocks, field) {
|
|
2344
2297
|
for (let i = 0; i < blocks.length; i++) {
|
|
2345
2298
|
const block = blocks[i];
|
|
2299
|
+
if (!block) continue;
|
|
2346
2300
|
if (block.type !== "text" && block.type !== "json") throw new AIRequestError(`ollama does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
2347
2301
|
}
|
|
2348
2302
|
return blocks;
|
|
@@ -2651,6 +2605,327 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2651
2605
|
}
|
|
2652
2606
|
};
|
|
2653
2607
|
//#endregion
|
|
2608
|
+
//#region src/adapters/mock.ts
|
|
2609
|
+
/**
|
|
2610
|
+
* Mock Adapter
|
|
2611
|
+
*
|
|
2612
|
+
* 面向测试的脚本化 adapter:
|
|
2613
|
+
* - 按 turn 顺序消费请求,验证调用方是否正确续接 replay / tool_result
|
|
2614
|
+
* - 发出可控的 message / reasoning / tool_call 流
|
|
2615
|
+
* - 注入 warning / auxiliary / content_filter / 中断 / provider error
|
|
2616
|
+
*
|
|
2617
|
+
* 这不是通用“假模型”,而是测试工具调用编排与错误路径的测试夹具。
|
|
2618
|
+
*/
|
|
2619
|
+
var MockAdapter = class extends AdapterBase {
|
|
2620
|
+
kind = "mock";
|
|
2621
|
+
capabilities = CAPABILITY_MATRIX.mock;
|
|
2622
|
+
turns;
|
|
2623
|
+
onExhausted;
|
|
2624
|
+
providerMetadata;
|
|
2625
|
+
cursor = 0;
|
|
2626
|
+
previousReplay = [];
|
|
2627
|
+
pendingToolCalls = [];
|
|
2628
|
+
history = [];
|
|
2629
|
+
activeStream = false;
|
|
2630
|
+
constructor(options) {
|
|
2631
|
+
super();
|
|
2632
|
+
this.turns = options.turns;
|
|
2633
|
+
this.onExhausted = options.onExhausted ?? "throw";
|
|
2634
|
+
this.providerMetadata = options.providerMetadata;
|
|
2635
|
+
}
|
|
2636
|
+
async buildRequest(request) {
|
|
2637
|
+
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);
|
|
2643
|
+
const remainingPendingToolCalls = consumePendingToolCalls(this.pendingToolCalls, request.input);
|
|
2644
|
+
this.cursor += 1;
|
|
2645
|
+
return {
|
|
2646
|
+
request,
|
|
2647
|
+
turn,
|
|
2648
|
+
turnIndex,
|
|
2649
|
+
turnName,
|
|
2650
|
+
remainingPendingToolCalls
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
async *runStream(providerRequest, factory, request) {
|
|
2654
|
+
if (this.activeStream) throw new AIRequestError("MockAdapter does not support concurrent streams", "MOCK_CONCURRENT_STREAM");
|
|
2655
|
+
this.activeStream = true;
|
|
2656
|
+
try {
|
|
2657
|
+
const mockRequest = providerRequest;
|
|
2658
|
+
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;
|
|
2709
|
+
}
|
|
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
|
+
}
|
|
2715
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" });
|
|
2716
|
+
yield factory.responseCompleted(response);
|
|
2717
|
+
} finally {
|
|
2718
|
+
this.activeStream = false;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
finalizeTurn(request, factory, mockRequest, output, completion) {
|
|
2722
|
+
const replay = completion.replay ?? replayFromOutput(output);
|
|
2723
|
+
const toolCalls = output.filter((item) => item.type === "tool_call");
|
|
2724
|
+
this.previousReplay = replay;
|
|
2725
|
+
this.pendingToolCalls = [...mockRequest.remainingPendingToolCalls, ...toolCalls];
|
|
2726
|
+
this.history.push({
|
|
2727
|
+
turnIndex: mockRequest.turnIndex,
|
|
2728
|
+
turnName: mockRequest.turnName,
|
|
2729
|
+
requestId: request.requestId,
|
|
2730
|
+
replay,
|
|
2731
|
+
toolCalls
|
|
2732
|
+
});
|
|
2733
|
+
return this.buildResponse(request, {
|
|
2734
|
+
output,
|
|
2735
|
+
replay,
|
|
2736
|
+
stopReason: completion.stopReason ?? resolveStopReason(output),
|
|
2737
|
+
usage: completion.usage,
|
|
2738
|
+
billing: completion.billing,
|
|
2739
|
+
auxiliary: completion.auxiliary,
|
|
2740
|
+
providerMetadata: {
|
|
2741
|
+
turnIndex: mockRequest.turnIndex,
|
|
2742
|
+
turnName: mockRequest.turnName,
|
|
2743
|
+
scriptedSteps: mockRequest.turn.steps.length,
|
|
2744
|
+
pendingToolCallIds: this.pendingToolCalls.map((item) => item.id),
|
|
2745
|
+
historyLength: this.history.length,
|
|
2746
|
+
...this.providerMetadata,
|
|
2747
|
+
...completion.providerMetadata
|
|
2748
|
+
},
|
|
2749
|
+
warnings: completion.warnings,
|
|
2750
|
+
metadataSources: ["mock"],
|
|
2751
|
+
rawResponseId: completion.rawResponseId
|
|
2752
|
+
}, factory);
|
|
2753
|
+
}
|
|
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) {
|
|
2766
|
+
return {
|
|
2767
|
+
turnIndex,
|
|
2768
|
+
previousReplay: this.previousReplay.map(cloneItem),
|
|
2769
|
+
pendingToolCalls: this.pendingToolCalls.map(cloneItem),
|
|
2770
|
+
history: this.history.map((record) => ({
|
|
2771
|
+
...record,
|
|
2772
|
+
replay: record.replay.map(cloneItem),
|
|
2773
|
+
toolCalls: record.toolCalls.map(cloneItem)
|
|
2774
|
+
}))
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
function createMessageFromStep(step, request, turnIndex, stepIndex) {
|
|
2779
|
+
return {
|
|
2780
|
+
...messageItem(normalizeBlocks(step.content), { id: step.id ?? `mock-msg-${request.requestId}-${turnIndex}-${stepIndex}` }),
|
|
2781
|
+
role: "assistant"
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
function createReasoningFromStep(step, request, turnIndex, stepIndex) {
|
|
2785
|
+
return reasoningItem(normalizeBlocks(step.content), step.visibility ?? "full", step.id ?? `mock-reason-${request.requestId}-${turnIndex}-${stepIndex}`);
|
|
2786
|
+
}
|
|
2787
|
+
function createToolCallFromStep(step) {
|
|
2788
|
+
return {
|
|
2789
|
+
type: "tool_call",
|
|
2790
|
+
id: step.id,
|
|
2791
|
+
name: step.name,
|
|
2792
|
+
argumentsText: step.argumentsText,
|
|
2793
|
+
argumentsJson: step.argumentsJson
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
function normalizeBlocks(content) {
|
|
2797
|
+
return typeof content === "string" ? [textBlock(content)] : content;
|
|
2798
|
+
}
|
|
2799
|
+
function assertSupportedOutputItem(item) {
|
|
2800
|
+
if (item.type === "opaque") throw new AIRequestError("MockAdapter does not stream opaque output items; use complete.replay if needed", "MOCK_OPAQUE_OUTPUT");
|
|
2801
|
+
}
|
|
2802
|
+
function attachSyntheticId(item, request, turnIndex, stepIndex) {
|
|
2803
|
+
if (item.type === "message") return {
|
|
2804
|
+
...item,
|
|
2805
|
+
id: item.id ?? `mock-msg-${request.requestId}-${turnIndex}-${stepIndex}`,
|
|
2806
|
+
role: "assistant"
|
|
2807
|
+
};
|
|
2808
|
+
if (item.type === "reasoning") return {
|
|
2809
|
+
...item,
|
|
2810
|
+
id: item.id ?? `mock-reason-${request.requestId}-${turnIndex}-${stepIndex}`
|
|
2811
|
+
};
|
|
2812
|
+
return item;
|
|
2813
|
+
}
|
|
2814
|
+
async function* emitOutputItem(factory, item) {
|
|
2815
|
+
if (item.type === "message") {
|
|
2816
|
+
yield* emitMessage(factory, item);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (item.type === "reasoning") {
|
|
2820
|
+
yield* emitReasoning(factory, item);
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
yield* emitToolCall(factory, item, true);
|
|
2824
|
+
}
|
|
2825
|
+
async function* emitMessage(factory, item) {
|
|
2826
|
+
if (!item.id) throw new AIRequestError("Mock message output requires an id after normalization", "MOCK_MESSAGE_ID_MISSING");
|
|
2827
|
+
yield factory.messageStarted(item.id);
|
|
2828
|
+
for (const block of item.content) if (block.type === "text") yield factory.messageDelta(item.id, block.text);
|
|
2829
|
+
yield factory.messageCompleted(item);
|
|
2830
|
+
}
|
|
2831
|
+
async function* emitReasoning(factory, item) {
|
|
2832
|
+
if (!item.id) throw new AIRequestError("Mock reasoning output requires an id after normalization", "MOCK_REASONING_ID_MISSING");
|
|
2833
|
+
yield factory.reasoningStarted(item.id, item.visibility);
|
|
2834
|
+
for (const block of item.content) yield factory.reasoningDelta(item.id, block);
|
|
2835
|
+
yield factory.reasoningCompleted(item);
|
|
2836
|
+
}
|
|
2837
|
+
async function* emitToolCall(factory, item, streamArguments) {
|
|
2838
|
+
yield factory.toolCallStarted(item.id, item.name);
|
|
2839
|
+
if (streamArguments && item.argumentsText) yield factory.toolCallDelta(item.id, { argumentsText: item.argumentsText });
|
|
2840
|
+
yield factory.toolCallCompleted(item);
|
|
2841
|
+
}
|
|
2842
|
+
function resolveStopReason(output) {
|
|
2843
|
+
return output.some((item) => item.type === "tool_call") ? "tool_call" : "end_turn";
|
|
2844
|
+
}
|
|
2845
|
+
function consumePendingToolCalls(pending, input) {
|
|
2846
|
+
const fulfilledIds = new Set(input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2847
|
+
return pending.filter((item) => !fulfilledIds.has(item.id)).map(cloneItem);
|
|
2848
|
+
}
|
|
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
|
+
function assertReplayIncluded(input, replay, prefix) {
|
|
2866
|
+
const fingerprints = input.map(fingerprintItem);
|
|
2867
|
+
let cursor = 0;
|
|
2868
|
+
for (const replayItem of replay) {
|
|
2869
|
+
const target = fingerprintItem(replayItem);
|
|
2870
|
+
const foundIndex = fingerprints.indexOf(target, cursor);
|
|
2871
|
+
if (foundIndex === -1) throw new AIRequestError(`${prefix}: previous replay item was not carried into the next request`, "MOCK_EXPECTATION_FAILED");
|
|
2872
|
+
cursor = foundIndex + 1;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function assertOrderedItems(input, expectations, prefix) {
|
|
2876
|
+
let cursor = 0;
|
|
2877
|
+
for (const expected of expectations) {
|
|
2878
|
+
let matched = false;
|
|
2879
|
+
while (cursor < input.length) {
|
|
2880
|
+
const item = input[cursor];
|
|
2881
|
+
if (item !== void 0 && matchesItemExpectation(item, expected)) {
|
|
2882
|
+
matched = true;
|
|
2883
|
+
cursor += 1;
|
|
2884
|
+
break;
|
|
2885
|
+
}
|
|
2886
|
+
cursor += 1;
|
|
2887
|
+
}
|
|
2888
|
+
if (!matched) throw new AIRequestError(`${prefix}: missing ordered input item ${describeExpectation(expected)}`, "MOCK_EXPECTATION_FAILED");
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
function assertUnorderedItems(input, expectations, prefix) {
|
|
2892
|
+
for (const expected of expectations) if (!input.some((item) => matchesItemExpectation(item, expected))) throw new AIRequestError(`${prefix}: missing input item ${describeExpectation(expected)}`, "MOCK_EXPECTATION_FAILED");
|
|
2893
|
+
}
|
|
2894
|
+
function matchesItemExpectation(item, expected) {
|
|
2895
|
+
if (item.type !== expected.type) return false;
|
|
2896
|
+
if (expected.id !== void 0 && "id" in item && item.id !== expected.id) return false;
|
|
2897
|
+
switch (item.type) {
|
|
2898
|
+
case "message": return (expected.role === void 0 || item.role === expected.role) && matchesText(item.content, expected.textIncludes);
|
|
2899
|
+
case "reasoning": return (expected.visibility === void 0 || item.visibility === expected.visibility) && matchesText(item.content, expected.textIncludes);
|
|
2900
|
+
case "tool_call": return (expected.name === void 0 || item.name === expected.name) && (expected.textIncludes === void 0 || item.argumentsText.includes(expected.textIncludes));
|
|
2901
|
+
case "tool_result": return (expected.toolName === void 0 || item.toolName === expected.toolName) && (expected.callId === void 0 || item.callId === expected.callId) && (expected.outcome === void 0 || item.outcome === expected.outcome) && matchesText(item.content, expected.textIncludes);
|
|
2902
|
+
case "opaque": return (expected.source === void 0 || item.source === expected.source) && (expected.purpose === void 0 || item.purpose === expected.purpose);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
function matchesText(blocks, textIncludes) {
|
|
2906
|
+
if (textIncludes === void 0) return true;
|
|
2907
|
+
return blocks.some((block) => {
|
|
2908
|
+
if (block.type === "text") return block.text.includes(textIncludes);
|
|
2909
|
+
if (block.type === "json") return JSON.stringify(block.json).includes(textIncludes);
|
|
2910
|
+
return false;
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
function fingerprintItem(item) {
|
|
2914
|
+
return JSON.stringify(item);
|
|
2915
|
+
}
|
|
2916
|
+
function describeExpectation(expectation) {
|
|
2917
|
+
const parts = [`type=${expectation.type}`];
|
|
2918
|
+
if (expectation.role) parts.push(`role=${expectation.role}`);
|
|
2919
|
+
if (expectation.name) parts.push(`name=${expectation.name}`);
|
|
2920
|
+
if (expectation.toolName) parts.push(`toolName=${expectation.toolName}`);
|
|
2921
|
+
if (expectation.callId) parts.push(`callId=${expectation.callId}`);
|
|
2922
|
+
if (expectation.textIncludes) parts.push(`textIncludes=${JSON.stringify(expectation.textIncludes)}`);
|
|
2923
|
+
return `{ ${parts.join(", ")} }`;
|
|
2924
|
+
}
|
|
2925
|
+
function cloneItem(item) {
|
|
2926
|
+
return structuredClone(item);
|
|
2927
|
+
}
|
|
2928
|
+
//#endregion
|
|
2654
2929
|
//#region src/helpers/synthetic-stream.ts
|
|
2655
2930
|
/**
|
|
2656
2931
|
* 模拟流式 (Synthetic Streaming)
|
|
@@ -2753,6 +3028,6 @@ function* emitToolCallEvents(item, factory) {
|
|
|
2753
3028
|
yield factory.toolCallCompleted(item);
|
|
2754
3029
|
}
|
|
2755
3030
|
//#endregion
|
|
2756
|
-
export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, CAPABILITY_MATRIX, ChatCompletionsAdapter, MessagesAdapter, 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 };
|
|
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 };
|
|
2757
3032
|
|
|
2758
3033
|
//# sourceMappingURL=index.mjs.map
|