@codehz/ai 0.1.3 → 0.1.5
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 +86 -81
- package/dist/index.d.mts +29 -34
- package/dist/index.mjs +140 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/adapters/chat-completions.ts +5 -11
- package/src/adapters/index.ts +5 -3
- package/src/adapters/messages.ts +27 -22
- package/src/adapters/mock.ts +190 -160
- package/src/adapters/ollama.ts +32 -27
- package/src/adapters/responses.ts +6 -8
- package/src/core/validation.ts +41 -18
- package/src/helpers/adapter-auxiliary.ts +3 -1
- package/src/helpers/adapter-base.ts +5 -5
- package/src/helpers/index.ts +1 -5
- package/src/helpers/mapping.ts +3 -2
- package/src/types/content.ts +5 -2
- package/src/types/index.ts +2 -8
- package/src/types/items.ts +1 -1
- package/src/types/request.ts +2 -2
- package/bun.lock +0 -231
package/dist/index.mjs
CHANGED
|
@@ -63,12 +63,7 @@ const WarningCode = {
|
|
|
63
63
|
};
|
|
64
64
|
//#endregion
|
|
65
65
|
//#region src/core/validation.ts
|
|
66
|
-
const MESSAGE_ROLES = /* @__PURE__ */ new Set([
|
|
67
|
-
"user",
|
|
68
|
-
"assistant",
|
|
69
|
-
"system",
|
|
70
|
-
"developer"
|
|
71
|
-
]);
|
|
66
|
+
const MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
72
67
|
const REASONING_VISIBILITIES = /* @__PURE__ */ new Set([
|
|
73
68
|
"full",
|
|
74
69
|
"summary",
|
|
@@ -122,6 +117,19 @@ function validateContentArray(content, field, issues, code) {
|
|
|
122
117
|
}
|
|
123
118
|
for (let i = 0; i < content.length; i++) validateContentBlock(content[i], `${field}[${i}]`, issues);
|
|
124
119
|
}
|
|
120
|
+
function validateInstructionArray(content, field, issues) {
|
|
121
|
+
if (!Array.isArray(content)) {
|
|
122
|
+
pushIssue(issues, field, "INSTRUCTIONS_INVALID", `${field} must be an InstructionBlock[]`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (let i = 0; i < content.length; i++) {
|
|
126
|
+
const block = content[i];
|
|
127
|
+
const blockField = `${field}[${i}]`;
|
|
128
|
+
validateContentBlock(block, blockField, issues);
|
|
129
|
+
if (!isRecord(block) || typeof block.type !== "string") continue;
|
|
130
|
+
if (block.type !== "text" && block.type !== "json") pushIssue(issues, blockField, "INSTRUCTIONS_INVALID", `${blockField} only supports text/json blocks`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
function validateInputItem(item, field, issues) {
|
|
126
134
|
if (!isRecord(item)) {
|
|
127
135
|
pushIssue(issues, field, "INPUT_INVALID_ITEM", `${field} must be a valid InputItem`);
|
|
@@ -192,8 +200,8 @@ function validateToolChoice(toolChoice, issues) {
|
|
|
192
200
|
*/
|
|
193
201
|
function validateRequest(request) {
|
|
194
202
|
const issues = [];
|
|
195
|
-
if (request.instructions !== void 0) if (typeof request.instructions === "string") {} else if (Array.isArray(request.instructions))
|
|
196
|
-
else pushIssue(issues, "instructions", "INSTRUCTIONS_INVALID", "instructions must be a string or
|
|
203
|
+
if (request.instructions !== void 0) if (typeof request.instructions === "string") {} else if (Array.isArray(request.instructions)) validateInstructionArray(request.instructions, "instructions", issues);
|
|
204
|
+
else pushIssue(issues, "instructions", "INSTRUCTIONS_INVALID", "instructions must be a string or InstructionBlock[]");
|
|
197
205
|
if (!Array.isArray(request.input) || request.input.length === 0) pushIssue(issues, "input", "INPUT_EMPTY", "input must be a non-empty array");
|
|
198
206
|
if (Array.isArray(request.input)) for (let i = 0; i < request.input.length; i++) validateInputItem(request.input[i], `input[${i}]`, issues);
|
|
199
207
|
if (request.temperature !== void 0) {
|
|
@@ -713,7 +721,7 @@ function contentBlocksToText(blocks) {
|
|
|
713
721
|
return blocks.map(blockToText).join("\n");
|
|
714
722
|
}
|
|
715
723
|
/**
|
|
716
|
-
* 将 instructions(string |
|
|
724
|
+
* 将 instructions(string | InstructionBlock[])归一化为纯文本。
|
|
717
725
|
*/
|
|
718
726
|
function instructionsToText(instructions) {
|
|
719
727
|
return typeof instructions === "string" ? instructions : contentBlocksToText(instructions);
|
|
@@ -1380,6 +1388,10 @@ function parseMessagesSSE(chunk) {
|
|
|
1380
1388
|
function rollbackTrailingAssistantMessages$2(messages) {
|
|
1381
1389
|
while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") messages.pop();
|
|
1382
1390
|
}
|
|
1391
|
+
/** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
|
|
1392
|
+
function synthesizeItemId(kind, blockIndex, responseId) {
|
|
1393
|
+
return `${kind}-${blockIndex}-${responseId}`;
|
|
1394
|
+
}
|
|
1383
1395
|
function parseToolUseInput(input) {
|
|
1384
1396
|
try {
|
|
1385
1397
|
const parsed = JSON.parse(input);
|
|
@@ -1447,11 +1459,6 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1447
1459
|
if (request.instructions) systemPrompt = instructionsToMessagesText(request.instructions);
|
|
1448
1460
|
for (const item of request.input) switch (item.type) {
|
|
1449
1461
|
case "message": {
|
|
1450
|
-
if (item.role === "system" || item.role === "developer") {
|
|
1451
|
-
const text = contentBlocksToText(ensureMessagesTextBlocks(item.content, `input message (${item.role}) content`));
|
|
1452
|
-
systemPrompt = systemPrompt ? `${systemPrompt}\n${text}` : text;
|
|
1453
|
-
break;
|
|
1454
|
-
}
|
|
1455
1462
|
const role = item.role === "user" ? "user" : "assistant";
|
|
1456
1463
|
const supportedContent = ensureMessagesTextBlocks(item.content, `input message (${item.role}) content`);
|
|
1457
1464
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") messages.push({
|
|
@@ -1581,7 +1588,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1581
1588
|
let argsBuffer = "";
|
|
1582
1589
|
let stopReason;
|
|
1583
1590
|
let stopSequence;
|
|
1584
|
-
let rawResponseId;
|
|
1591
|
+
let rawResponseId = "";
|
|
1585
1592
|
if (request.include?.providerMetadata !== "off") {
|
|
1586
1593
|
const headerMetadata = pickProviderHeaders(response.headers);
|
|
1587
1594
|
auxiliary.recordProviderMetadata("header", Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : void 0);
|
|
@@ -1618,14 +1625,14 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1618
1625
|
switch (block.type) {
|
|
1619
1626
|
case "text":
|
|
1620
1627
|
currentItemType = "message";
|
|
1621
|
-
currentItemId =
|
|
1628
|
+
currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
|
|
1622
1629
|
textBuffer = "";
|
|
1623
1630
|
yield factory.messageStarted(currentItemId);
|
|
1624
1631
|
break;
|
|
1625
1632
|
case "thinking":
|
|
1626
1633
|
hasStreamedReasoning = true;
|
|
1627
1634
|
currentItemType = "reasoning";
|
|
1628
|
-
currentItemId =
|
|
1635
|
+
currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
|
|
1629
1636
|
currentThinkingVisibility = "full";
|
|
1630
1637
|
thinkingBuffer = "";
|
|
1631
1638
|
yield factory.reasoningStarted(currentItemId, "full");
|
|
@@ -1633,7 +1640,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1633
1640
|
case "redacted_thinking": {
|
|
1634
1641
|
hasStreamedReasoning = true;
|
|
1635
1642
|
currentItemType = "reasoning";
|
|
1636
|
-
currentItemId =
|
|
1643
|
+
currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
|
|
1637
1644
|
currentThinkingVisibility = "redacted";
|
|
1638
1645
|
const data = block.data;
|
|
1639
1646
|
yield factory.reasoningStarted(currentItemId, "redacted");
|
|
@@ -1899,7 +1906,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
1899
1906
|
}
|
|
1900
1907
|
for (const item of request.input) switch (item.type) {
|
|
1901
1908
|
case "message": {
|
|
1902
|
-
const role = item.role
|
|
1909
|
+
const role = item.role;
|
|
1903
1910
|
const text = contentBlocksToChatText(item.content, `input message (${item.role}) content`);
|
|
1904
1911
|
messages.push({
|
|
1905
1912
|
role,
|
|
@@ -2289,7 +2296,7 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2289
2296
|
});
|
|
2290
2297
|
for (const item of request.input) switch (item.type) {
|
|
2291
2298
|
case "message": {
|
|
2292
|
-
const role = item.role
|
|
2299
|
+
const role = item.role;
|
|
2293
2300
|
messages.push({
|
|
2294
2301
|
role,
|
|
2295
2302
|
content: contentBlocksToText(ensureOllamaTextBlocks(item.content, `input message (${item.role}) content`))
|
|
@@ -2511,20 +2518,35 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2511
2518
|
/**
|
|
2512
2519
|
* Mock Adapter
|
|
2513
2520
|
*
|
|
2514
|
-
*
|
|
2515
|
-
* -
|
|
2521
|
+
* 面向测试的回调驱动 adapter:
|
|
2522
|
+
* - 每次请求执行用户提供的 handler
|
|
2523
|
+
* - 验证调用方是否正确续接 replay / tool_result
|
|
2516
2524
|
* - 发出可控的 message / reasoning / tool_call 流
|
|
2517
2525
|
* - 注入 warning / auxiliary / content_filter / 中断 / provider error
|
|
2518
2526
|
*
|
|
2519
2527
|
* 这不是通用“假模型”,而是测试工具调用编排与错误路径的测试夹具。
|
|
2520
2528
|
*/
|
|
2529
|
+
function assertMockRequest(request, expectation, context) {
|
|
2530
|
+
const prefix = `MockAdapter turn ${context.turnIndex + 1} expectation failed`;
|
|
2531
|
+
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");
|
|
2532
|
+
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");
|
|
2533
|
+
if (expectation.tools === "present" && (!request.tools || request.tools.length === 0)) throw new AIRequestError(`${prefix}: expected tools to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2534
|
+
if (expectation.tools === "absent" && request.tools && request.tools.length > 0) throw new AIRequestError(`${prefix}: expected tools to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2535
|
+
if (expectation.toolChoice === "present" && request.toolChoice === void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2536
|
+
if (expectation.toolChoice === "absent" && request.toolChoice !== void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2537
|
+
if (expectation.requireReplayFromPreviousTurn && context.previousReplay.length > 0) assertReplayIncluded(request.input, context.previousReplay, prefix);
|
|
2538
|
+
if (expectation.requireToolResultsForPendingCalls && context.pendingToolCalls.length > 0) {
|
|
2539
|
+
const toolResultIds = new Set(request.input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2540
|
+
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");
|
|
2541
|
+
}
|
|
2542
|
+
if (expectation.items && expectation.items.length > 0) if (expectation.ordered) assertOrderedItems(request.input, expectation.items, prefix);
|
|
2543
|
+
else assertUnorderedItems(request.input, expectation.items, prefix);
|
|
2544
|
+
}
|
|
2521
2545
|
var MockAdapter = class extends AdapterBase {
|
|
2522
2546
|
kind = "mock";
|
|
2523
2547
|
nativeStreaming = false;
|
|
2524
|
-
|
|
2525
|
-
onExhausted;
|
|
2548
|
+
handler;
|
|
2526
2549
|
providerMetadata;
|
|
2527
|
-
defaultStream;
|
|
2528
2550
|
cursor = 0;
|
|
2529
2551
|
previousReplay = [];
|
|
2530
2552
|
pendingToolCalls = [];
|
|
@@ -2532,25 +2554,19 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2532
2554
|
activeStream = false;
|
|
2533
2555
|
constructor(options) {
|
|
2534
2556
|
super();
|
|
2535
|
-
this.
|
|
2536
|
-
this.onExhausted = options.onExhausted ?? "throw";
|
|
2557
|
+
this.handler = options.handler;
|
|
2537
2558
|
this.providerMetadata = options.providerMetadata;
|
|
2538
|
-
this.defaultStream = resolveMockTextStreamOptions(options.stream, "adapter stream");
|
|
2539
2559
|
}
|
|
2540
2560
|
async buildRequest(request) {
|
|
2541
2561
|
const turnIndex = this.cursor;
|
|
2542
|
-
const
|
|
2543
|
-
const turnName = turn.name;
|
|
2544
|
-
const context = this.buildTurnContext(turnIndex);
|
|
2545
|
-
if (turn.expect) if (typeof turn.expect === "function") await turn.expect(request, context);
|
|
2546
|
-
else assertRequestMatchesExpectation(request, turn.expect, context);
|
|
2562
|
+
const context = this.buildHandlerContext(turnIndex);
|
|
2547
2563
|
const remainingPendingToolCalls = consumePendingToolCalls(this.pendingToolCalls, request.input);
|
|
2564
|
+
const handlerResult = this.handler(request, context);
|
|
2548
2565
|
this.cursor += 1;
|
|
2549
2566
|
return {
|
|
2550
2567
|
request,
|
|
2551
|
-
|
|
2568
|
+
handlerResult,
|
|
2552
2569
|
turnIndex,
|
|
2553
|
-
turnName,
|
|
2554
2570
|
remainingPendingToolCalls
|
|
2555
2571
|
};
|
|
2556
2572
|
}
|
|
@@ -2560,76 +2576,79 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2560
2576
|
try {
|
|
2561
2577
|
const mockRequest = providerRequest;
|
|
2562
2578
|
const output = [];
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2579
|
+
let stepCount = 0;
|
|
2580
|
+
for await (const step of mockRequest.handlerResult) {
|
|
2581
|
+
stepCount += 1;
|
|
2582
|
+
switch (step.type) {
|
|
2583
|
+
case "warning":
|
|
2584
|
+
yield factory.responseWarning(step.message, step.code);
|
|
2585
|
+
break;
|
|
2586
|
+
case "auxiliary":
|
|
2587
|
+
yield factory.responseAuxiliary({
|
|
2588
|
+
usage: step.usage,
|
|
2589
|
+
billing: step.billing,
|
|
2590
|
+
auxiliary: step.auxiliary
|
|
2591
|
+
});
|
|
2592
|
+
break;
|
|
2593
|
+
case "message": {
|
|
2594
|
+
const item = createMessageFromStep(step, request, mockRequest.turnIndex, stepCount - 1);
|
|
2595
|
+
yield* emitMessage(factory, item, resolveStepStreamOptions(void 0, step.stream, "message"));
|
|
2596
|
+
output.push(item);
|
|
2597
|
+
break;
|
|
2598
|
+
}
|
|
2599
|
+
case "reasoning": {
|
|
2600
|
+
const item = createReasoningFromStep(step, request, mockRequest.turnIndex, stepCount - 1);
|
|
2601
|
+
yield* emitReasoning(factory, item, resolveStepStreamOptions(void 0, step.stream, "reasoning"));
|
|
2602
|
+
output.push(item);
|
|
2603
|
+
break;
|
|
2604
|
+
}
|
|
2605
|
+
case "tool_call": {
|
|
2606
|
+
const item = createToolCallFromStep(step);
|
|
2607
|
+
yield* emitToolCall(factory, item, step.streamArguments ?? true, resolveStepStreamOptions(void 0, step.stream, "tool_call"));
|
|
2608
|
+
output.push(item);
|
|
2609
|
+
break;
|
|
2610
|
+
}
|
|
2611
|
+
case "output": {
|
|
2612
|
+
assertSupportedOutputItem(step.item);
|
|
2613
|
+
const item = attachSyntheticId(step.item, request, mockRequest.turnIndex, stepCount - 1);
|
|
2614
|
+
yield* emitOutputItem(factory, item, resolveStepStreamOptions(void 0, step.stream, "output"));
|
|
2615
|
+
output.push(item);
|
|
2616
|
+
break;
|
|
2617
|
+
}
|
|
2618
|
+
case "complete": {
|
|
2619
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, step, stepCount);
|
|
2620
|
+
yield factory.responseCompleted(response);
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
case "error": {
|
|
2624
|
+
yield factory.responseWarning(step.message, step.code);
|
|
2625
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, {
|
|
2626
|
+
type: "complete",
|
|
2627
|
+
stopReason: step.stopReason ?? "error",
|
|
2628
|
+
providerMetadata: step.providerMetadata
|
|
2629
|
+
}, stepCount);
|
|
2630
|
+
yield factory.responseCompleted(response);
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
case "interrupt":
|
|
2634
|
+
this.pendingToolCalls = mockRequest.remainingPendingToolCalls;
|
|
2635
|
+
return;
|
|
2636
|
+
case "throw": throw typeof step.error === "string" ? new Error(step.error) : step.error;
|
|
2613
2637
|
}
|
|
2614
|
-
case "interrupt":
|
|
2615
|
-
this.pendingToolCalls = mockRequest.remainingPendingToolCalls;
|
|
2616
|
-
return;
|
|
2617
|
-
case "throw": throw typeof step.error === "string" ? new Error(step.error) : step.error;
|
|
2618
2638
|
}
|
|
2619
|
-
const response = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" });
|
|
2639
|
+
const response = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" }, stepCount);
|
|
2620
2640
|
yield factory.responseCompleted(response);
|
|
2621
2641
|
} finally {
|
|
2622
2642
|
this.activeStream = false;
|
|
2623
2643
|
}
|
|
2624
2644
|
}
|
|
2625
|
-
finalizeTurn(request, factory, mockRequest, output, completion) {
|
|
2645
|
+
finalizeTurn(request, factory, mockRequest, output, completion, stepCount) {
|
|
2626
2646
|
const replay = completion.replay ?? replayFromOutput(output);
|
|
2627
2647
|
const toolCalls = output.filter((item) => item.type === "tool_call");
|
|
2628
2648
|
this.previousReplay = replay;
|
|
2629
2649
|
this.pendingToolCalls = [...mockRequest.remainingPendingToolCalls, ...toolCalls];
|
|
2630
2650
|
this.history.push({
|
|
2631
2651
|
turnIndex: mockRequest.turnIndex,
|
|
2632
|
-
turnName: mockRequest.turnName,
|
|
2633
2652
|
requestId: request.requestId,
|
|
2634
2653
|
replay,
|
|
2635
2654
|
toolCalls
|
|
@@ -2643,8 +2662,7 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2643
2662
|
auxiliary: completion.auxiliary,
|
|
2644
2663
|
providerMetadata: {
|
|
2645
2664
|
turnIndex: mockRequest.turnIndex,
|
|
2646
|
-
|
|
2647
|
-
scriptedSteps: mockRequest.turn.steps.length,
|
|
2665
|
+
stepCount,
|
|
2648
2666
|
pendingToolCallIds: this.pendingToolCalls.map((item) => item.id),
|
|
2649
2667
|
historyLength: this.history.length,
|
|
2650
2668
|
...this.providerMetadata,
|
|
@@ -2655,18 +2673,7 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2655
2673
|
rawResponseId: completion.rawResponseId
|
|
2656
2674
|
}, factory);
|
|
2657
2675
|
}
|
|
2658
|
-
|
|
2659
|
-
const turn = this.turns[turnIndex];
|
|
2660
|
-
if (turn !== void 0) return turn;
|
|
2661
|
-
const lastTurn = this.turns.at(-1);
|
|
2662
|
-
if (this.onExhausted === "repeat-last" && lastTurn !== void 0) return lastTurn;
|
|
2663
|
-
if (this.onExhausted === "complete-empty") return {
|
|
2664
|
-
name: "exhausted",
|
|
2665
|
-
steps: []
|
|
2666
|
-
};
|
|
2667
|
-
throw new AIRequestError(`MockAdapter turn ${turnIndex + 1} requested, but only ${this.turns.length} turn(s) were scripted`, "MOCK_TURN_EXHAUSTED");
|
|
2668
|
-
}
|
|
2669
|
-
buildTurnContext(turnIndex) {
|
|
2676
|
+
buildHandlerContext(turnIndex) {
|
|
2670
2677
|
return {
|
|
2671
2678
|
turnIndex,
|
|
2672
2679
|
previousReplay: this.previousReplay.map(cloneItem),
|
|
@@ -2679,6 +2686,32 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2679
2686
|
};
|
|
2680
2687
|
}
|
|
2681
2688
|
};
|
|
2689
|
+
function withMockStreaming(handler, options) {
|
|
2690
|
+
const defaults = resolveMockTextStreamOptions(options, "mock stream wrapper");
|
|
2691
|
+
if (!defaults) throw new AIRequestError("mock stream wrapper requires streaming options", "MOCK_STREAM_CONFIG_INVALID");
|
|
2692
|
+
return async function* streamWrappedHandler(request, context) {
|
|
2693
|
+
const source = await handler(request, context);
|
|
2694
|
+
for await (const step of source) yield applyDefaultStreaming(step, defaults);
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
function applyDefaultStreaming(step, defaults) {
|
|
2698
|
+
switch (step.type) {
|
|
2699
|
+
case "message":
|
|
2700
|
+
case "reasoning":
|
|
2701
|
+
case "tool_call":
|
|
2702
|
+
case "output":
|
|
2703
|
+
if (step.stream !== void 0) return step;
|
|
2704
|
+
return {
|
|
2705
|
+
...step,
|
|
2706
|
+
stream: {
|
|
2707
|
+
charsPerSecond: defaults.charsPerSecond,
|
|
2708
|
+
chunkSize: defaults.chunkSize,
|
|
2709
|
+
initialDelayMs: defaults.initialDelayMs
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
default: return step;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2682
2715
|
function createMessageFromStep(step, request, turnIndex, stepIndex) {
|
|
2683
2716
|
return {
|
|
2684
2717
|
...messageItem(normalizeBlocks(step.content), { id: step.id ?? `mock-msg-${request.requestId}-${turnIndex}-${stepIndex}` }),
|
|
@@ -2811,22 +2844,6 @@ function consumePendingToolCalls(pending, input) {
|
|
|
2811
2844
|
const fulfilledIds = new Set(input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2812
2845
|
return pending.filter((item) => !fulfilledIds.has(item.id)).map(cloneItem);
|
|
2813
2846
|
}
|
|
2814
|
-
function assertRequestMatchesExpectation(request, expectation, context) {
|
|
2815
|
-
const prefix = `MockAdapter turn ${context.turnIndex + 1} expectation failed`;
|
|
2816
|
-
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");
|
|
2817
|
-
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");
|
|
2818
|
-
if (expectation.tools === "present" && (!request.tools || request.tools.length === 0)) throw new AIRequestError(`${prefix}: expected tools to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2819
|
-
if (expectation.tools === "absent" && request.tools && request.tools.length > 0) throw new AIRequestError(`${prefix}: expected tools to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2820
|
-
if (expectation.toolChoice === "present" && request.toolChoice === void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be present`, "MOCK_EXPECTATION_FAILED");
|
|
2821
|
-
if (expectation.toolChoice === "absent" && request.toolChoice !== void 0) throw new AIRequestError(`${prefix}: expected toolChoice to be absent`, "MOCK_EXPECTATION_FAILED");
|
|
2822
|
-
if (expectation.requireReplayFromPreviousTurn && context.previousReplay.length > 0) assertReplayIncluded(request.input, context.previousReplay, prefix);
|
|
2823
|
-
if (expectation.requireToolResultsForPendingCalls && context.pendingToolCalls.length > 0) {
|
|
2824
|
-
const toolResultIds = new Set(request.input.filter((item) => item.type === "tool_result").map((item) => item.callId));
|
|
2825
|
-
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");
|
|
2826
|
-
}
|
|
2827
|
-
if (expectation.items && expectation.items.length > 0) if (expectation.ordered) assertOrderedItems(request.input, expectation.items, prefix);
|
|
2828
|
-
else assertUnorderedItems(request.input, expectation.items, prefix);
|
|
2829
|
-
}
|
|
2830
2847
|
function assertReplayIncluded(input, replay, prefix) {
|
|
2831
2848
|
const fingerprints = input.map(fingerprintItem);
|
|
2832
2849
|
let cursor = 0;
|
|
@@ -2993,6 +3010,6 @@ function* emitToolCallEvents(item, factory) {
|
|
|
2993
3010
|
yield factory.toolCallCompleted(item);
|
|
2994
3011
|
}
|
|
2995
3012
|
//#endregion
|
|
2996
|
-
export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, 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 };
|
|
3013
|
+
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 };
|
|
2997
3014
|
|
|
2998
3015
|
//# sourceMappingURL=index.mjs.map
|