@codehz/ai 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/index.d.mts +151 -27
- package/dist/index.mjs +1284 -702
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +243 -196
- package/src/adapters/messages.ts +150 -124
- package/src/adapters/mock.ts +44 -11
- package/src/adapters/ollama.ts +219 -191
- package/src/adapters/responses.ts +222 -137
- package/src/core/aggregator.ts +218 -61
- package/src/core/errors.ts +7 -1
- package/src/core/event-factory.ts +24 -14
- package/src/core/merge-auxiliary.ts +22 -0
- package/src/core/normalize.ts +15 -1
- package/src/core/validation.ts +29 -21
- package/src/helpers/adapter-base.ts +23 -25
- package/src/helpers/adapter-security.ts +126 -0
- package/src/helpers/incremental-stream-parser.ts +84 -0
- package/src/helpers/index.ts +19 -0
- package/src/helpers/request-mapper.ts +72 -0
- package/src/helpers/sse-parser.ts +51 -25
- package/src/helpers/synthetic-stream.ts +13 -21
- package/src/helpers/usage-mapping.ts +4 -9
- package/src/types/adapter.ts +12 -1
- package/src/types/events.ts +14 -10
- package/src/types/index.ts +9 -1
- package/src/types/response.ts +0 -1
package/src/adapters/messages.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { AdapterBase } from "../helpers/adapter-base.js";
|
|
14
|
-
import { AIRequestError } from "../core/errors.js";
|
|
14
|
+
import { AIProviderError, AIRequestError, AIStreamError } from "../core/errors.js";
|
|
15
15
|
import {
|
|
16
16
|
textBlock,
|
|
17
17
|
messageItem,
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
contentBlocksToText,
|
|
25
25
|
} from "../helpers/mapping.js";
|
|
26
26
|
import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
|
|
27
|
+
import { assertOpaqueReplayEnvelope, providerHttpError } from "../helpers/adapter-security.js";
|
|
27
28
|
import { usageFromAnthropicMessages } from "../helpers/usage-mapping.js";
|
|
28
|
-
|
|
29
|
-
import {
|
|
29
|
+
import { NormalizedRequestMapper, splitSSEFrames, IncrementalStreamParser } from "../helpers/index.js";
|
|
30
|
+
import type { ProviderProfile } from "../helpers/index.js";
|
|
30
31
|
|
|
31
32
|
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
|
|
32
33
|
|
|
@@ -72,52 +73,68 @@ type MessagesAPITool = {
|
|
|
72
73
|
input_schema: Record<string, unknown>;
|
|
73
74
|
};
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
// ── ProviderProfile & Mapper ────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
const profile: ProviderProfile = {
|
|
79
|
+
kind: "messages",
|
|
80
|
+
instructionsMode: "system_message",
|
|
81
|
+
supportedBlockTypes: ["text", "json"] as const,
|
|
82
|
+
reasoningBlockTypes: ["text"] as const,
|
|
83
|
+
capabilities: {
|
|
84
|
+
textStreaming: "native",
|
|
85
|
+
reasoningStreaming: "native",
|
|
86
|
+
toolCallStreaming: "synthetic",
|
|
87
|
+
replay: "opaque",
|
|
88
|
+
usage: "stream",
|
|
89
|
+
toolResultOutcomes: ["success", "error"],
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const mapper = new NormalizedRequestMapper(profile);
|
|
94
|
+
|
|
95
|
+
function isMessagesReplayContentBlock(value: unknown): value is MessagesAPIContentBlock {
|
|
96
|
+
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
97
|
+
const block = value as Record<string, unknown>;
|
|
98
|
+
switch (block.type) {
|
|
99
|
+
case "text":
|
|
100
|
+
return typeof block.text === "string";
|
|
101
|
+
case "thinking":
|
|
102
|
+
return (
|
|
103
|
+
typeof block.thinking === "string" && (block.signature === undefined || typeof block.signature === "string")
|
|
104
|
+
);
|
|
105
|
+
case "redacted_thinking":
|
|
106
|
+
return typeof block.data === "string";
|
|
107
|
+
case "tool_use":
|
|
108
|
+
return (
|
|
109
|
+
typeof block.id === "string" &&
|
|
110
|
+
typeof block.name === "string" &&
|
|
111
|
+
!!block.input &&
|
|
112
|
+
typeof block.input === "object" &&
|
|
113
|
+
!Array.isArray(block.input)
|
|
86
114
|
);
|
|
115
|
+
case "tool_result": {
|
|
116
|
+
if (typeof block.tool_use_id !== "string") return false;
|
|
117
|
+
if (block.is_error !== undefined && typeof block.is_error !== "boolean") return false;
|
|
118
|
+
if (typeof block.content === "string") return true;
|
|
119
|
+
if (!Array.isArray(block.content)) return false;
|
|
120
|
+
return block.content.every(isMessagesReplayContentBlock);
|
|
87
121
|
}
|
|
122
|
+
default:
|
|
123
|
+
return false;
|
|
88
124
|
}
|
|
89
|
-
|
|
90
|
-
return blocks;
|
|
91
125
|
}
|
|
92
126
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
127
|
+
function assertMessagesReplayContent(content: unknown): asserts content is MessagesAPIContentBlock[] {
|
|
128
|
+
if (!Array.isArray(content)) {
|
|
129
|
+
throw new AIRequestError("Invalid opaque replay payload: content must be an array", "INVALID_OPAQUE_REPLAY");
|
|
130
|
+
}
|
|
131
|
+
for (let i = 0; i < content.length; i++) {
|
|
132
|
+
if (!isMessagesReplayContentBlock(content[i])) {
|
|
99
133
|
throw new AIRequestError(
|
|
100
|
-
`
|
|
101
|
-
"
|
|
134
|
+
`Invalid opaque replay payload: content[${i}] is not a valid Messages content block`,
|
|
135
|
+
"INVALID_OPAQUE_REPLAY",
|
|
102
136
|
);
|
|
103
137
|
}
|
|
104
|
-
|
|
105
|
-
return block;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function instructionsToMessagesText(instructions: string | import("../index.js").InstructionBlock[]): string {
|
|
110
|
-
return typeof instructions === "string"
|
|
111
|
-
? instructions
|
|
112
|
-
: contentBlocksToText(ensureMessagesTextBlocks(instructions, "instructions"));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function assertMessagesToolResultOutcome(outcome: import("../index.js").ToolResultItem["outcome"]): void {
|
|
116
|
-
if (outcome === "rejected") {
|
|
117
|
-
throw new AIRequestError(
|
|
118
|
-
'messages does not preserve tool_result outcome "rejected"; only "success" and "error" are supported',
|
|
119
|
-
"UNSUPPORTED_TOOL_RESULT_OUTCOME",
|
|
120
|
-
);
|
|
121
138
|
}
|
|
122
139
|
}
|
|
123
140
|
|
|
@@ -155,19 +172,6 @@ type MessagesAPIMessageResponse = {
|
|
|
155
172
|
usage: { input_tokens: number; output_tokens: number };
|
|
156
173
|
};
|
|
157
174
|
|
|
158
|
-
// ── SSE 解析 ──────────────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
function parseMessagesSSE(chunk: string): { events: MessagesSSEEvent[]; rest: string; malformedEvents: number } {
|
|
161
|
-
const result = parseSSEEvents(chunk);
|
|
162
|
-
return { events: result.events as MessagesSSEEvent[], rest: result.rest, malformedEvents: result.malformedEvents };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function rollbackTrailingAssistantMessages(messages: MessagesAPIMessage[]): void {
|
|
166
|
-
while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") {
|
|
167
|
-
messages.pop();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
175
|
/** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
|
|
172
176
|
function synthesizeItemId(kind: "msg" | "reason" | "reason-redacted", blockIndex: number, responseId: string): string {
|
|
173
177
|
return `${kind}-${blockIndex}-${responseId}`;
|
|
@@ -247,13 +251,12 @@ function buildStreamMetadata(options: {
|
|
|
247
251
|
|
|
248
252
|
export class MessagesAdapter extends AdapterBase {
|
|
249
253
|
readonly kind = "messages" as const;
|
|
250
|
-
readonly
|
|
254
|
+
readonly capabilities = profile.capabilities;
|
|
251
255
|
|
|
252
256
|
private apiKey: string;
|
|
253
257
|
private apiVersion: string;
|
|
254
258
|
private baseUrl: string;
|
|
255
259
|
private fetchFn: FetchFn;
|
|
256
|
-
private warningAccumulator: string[];
|
|
257
260
|
|
|
258
261
|
constructor(options: MessagesAdapterOptions) {
|
|
259
262
|
super();
|
|
@@ -261,11 +264,6 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
261
264
|
this.apiVersion = options.apiVersion ?? "2023-06-01";
|
|
262
265
|
this.baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
|
|
263
266
|
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
264
|
-
this.warningAccumulator = [];
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
protected warn(message: string, _code?: string): void {
|
|
268
|
-
this.warningAccumulator.push(message);
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
// ── buildRequest ──────────────────────────────────────────
|
|
@@ -277,7 +275,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
277
275
|
|
|
278
276
|
// 处理 instructions → system prompt
|
|
279
277
|
if (request.instructions) {
|
|
280
|
-
systemPrompt =
|
|
278
|
+
systemPrompt = mapper.mapInstructions(request.instructions);
|
|
281
279
|
}
|
|
282
280
|
|
|
283
281
|
// 处理 input items
|
|
@@ -289,7 +287,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
289
287
|
switch (item.type) {
|
|
290
288
|
case "message": {
|
|
291
289
|
const role = item.role === "user" ? "user" : "assistant";
|
|
292
|
-
const supportedContent =
|
|
290
|
+
const supportedContent = mapper.ensureTextBlocks(item.content, `input message (${item.role}) content`);
|
|
293
291
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") {
|
|
294
292
|
messages.push({ role, content: supportedContent[0].text });
|
|
295
293
|
} else {
|
|
@@ -315,8 +313,9 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
315
313
|
break;
|
|
316
314
|
}
|
|
317
315
|
case "tool_result": {
|
|
318
|
-
|
|
319
|
-
const content =
|
|
316
|
+
mapper.assertToolResultOutcome(item.outcome);
|
|
317
|
+
const content = mapper
|
|
318
|
+
.ensureTextBlocks(item.content, `tool_result ${item.callId} content`)
|
|
320
319
|
.map(blockToText)
|
|
321
320
|
.join("\n");
|
|
322
321
|
const block: MessagesAPIContentBlock = {
|
|
@@ -335,7 +334,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
335
334
|
}
|
|
336
335
|
case "reasoning": {
|
|
337
336
|
// 将 reasoning item 转为 thinking block 在 assistant message 中
|
|
338
|
-
const text = contentBlocksToText(
|
|
337
|
+
const text = contentBlocksToText(mapper.ensureReasoningBlocks(item.content, "reasoning content"));
|
|
339
338
|
const block: MessagesAPIContentBlock = { type: "thinking", thinking: text };
|
|
340
339
|
const lastMsg = messages[messages.length - 1];
|
|
341
340
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
|
|
@@ -347,29 +346,16 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
347
346
|
}
|
|
348
347
|
case "opaque": {
|
|
349
348
|
// 尝试从 opaque replay item 中提取 assistant message
|
|
350
|
-
if (item.purpose
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
b.type === "thinking" ||
|
|
361
|
-
b.type === "redacted_thinking" ||
|
|
362
|
-
b.type === "tool_use" ||
|
|
363
|
-
b.type === "tool_result"),
|
|
364
|
-
);
|
|
365
|
-
if (isValidContent) {
|
|
366
|
-
rollbackTrailingAssistantMessages(messages);
|
|
367
|
-
messages.push({
|
|
368
|
-
role: "assistant",
|
|
369
|
-
content: payload.content as MessagesAPIContentBlock[],
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
349
|
+
if (item.purpose !== "replay") break;
|
|
350
|
+
assertOpaqueReplayEnvelope(item.payload);
|
|
351
|
+
const payload = item.payload as Record<string, unknown>;
|
|
352
|
+
if (payload.role === "assistant" && "content" in payload) {
|
|
353
|
+
assertMessagesReplayContent(payload.content);
|
|
354
|
+
mapper.rollbackTrailingAssistantMessages(messages);
|
|
355
|
+
messages.push({
|
|
356
|
+
role: "assistant",
|
|
357
|
+
content: payload.content,
|
|
358
|
+
});
|
|
373
359
|
}
|
|
374
360
|
break;
|
|
375
361
|
}
|
|
@@ -415,8 +401,8 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
415
401
|
factory: EventFactory,
|
|
416
402
|
request: NormalizedRequest,
|
|
417
403
|
): AsyncIterable<AIStreamEvent> {
|
|
418
|
-
this.warningAccumulator = [];
|
|
419
404
|
const auxiliary = this.createAuxiliaryState(request);
|
|
405
|
+
let completedEmitted = false;
|
|
420
406
|
|
|
421
407
|
if (request.metadata) {
|
|
422
408
|
yield factory.responseWarning(
|
|
@@ -425,30 +411,52 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
425
411
|
);
|
|
426
412
|
}
|
|
427
413
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
"
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
414
|
+
let response: Response;
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
response = await this.fetchFn(`${this.baseUrl}/messages`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: {
|
|
420
|
+
"Content-Type": "application/json",
|
|
421
|
+
"x-api-key": this.apiKey,
|
|
422
|
+
"anthropic-version": this.apiVersion,
|
|
423
|
+
},
|
|
424
|
+
body: JSON.stringify(providerRequest),
|
|
425
|
+
});
|
|
426
|
+
} catch (err) {
|
|
427
|
+
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
428
|
+
}
|
|
437
429
|
|
|
438
430
|
if (!response.ok) {
|
|
439
|
-
const
|
|
440
|
-
throw
|
|
431
|
+
const errorBody = await response.text().catch(() => "");
|
|
432
|
+
throw providerHttpError(response.status, errorBody);
|
|
441
433
|
}
|
|
442
434
|
|
|
443
435
|
const reader = response.body?.getReader();
|
|
444
436
|
if (!reader) {
|
|
445
|
-
throw new
|
|
437
|
+
throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
446
438
|
}
|
|
447
439
|
|
|
448
440
|
// 流累积状态
|
|
441
|
+
const parser = new IncrementalStreamParser<MessagesSSEEvent>(splitSSEFrames, (frame: string) => {
|
|
442
|
+
let eventType = "";
|
|
443
|
+
let dataStr = "";
|
|
444
|
+
for (const rawLine of frame.split("\n")) {
|
|
445
|
+
const line = rawLine.trim();
|
|
446
|
+
if (line.startsWith("event: ")) eventType = line.slice(7).trim();
|
|
447
|
+
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
448
|
+
}
|
|
449
|
+
if (!eventType) return { status: "ignored" };
|
|
450
|
+
try {
|
|
451
|
+
const data = JSON.parse(dataStr);
|
|
452
|
+
return { status: "parsed", value: { type: eventType, data } as MessagesSSEEvent };
|
|
453
|
+
} catch {
|
|
454
|
+
return { status: "malformed" };
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
449
458
|
const output: OutputItem[] = [];
|
|
450
|
-
|
|
451
|
-
let buffer = "";
|
|
459
|
+
let streamDone = false;
|
|
452
460
|
let messageResponse: MessagesAPIMessageResponse | undefined;
|
|
453
461
|
let currentContentBlockIndex = -1;
|
|
454
462
|
let currentItemType: "message" | "reasoning" | "tool_call" | null = null;
|
|
@@ -479,12 +487,14 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
479
487
|
|
|
480
488
|
try {
|
|
481
489
|
while (true) {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
490
|
+
const readResult = await reader.read().catch((err: unknown) => {
|
|
491
|
+
throw new AIStreamError(
|
|
492
|
+
`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`,
|
|
493
|
+
"STREAM_ERROR",
|
|
494
|
+
);
|
|
495
|
+
});
|
|
496
|
+
const { done, value } = readResult;
|
|
497
|
+
const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value as Uint8Array);
|
|
488
498
|
|
|
489
499
|
const malformedWarning = emitMalformedStreamWarning(factory, {
|
|
490
500
|
count: malformedEvents,
|
|
@@ -503,7 +513,6 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
503
513
|
case "error": {
|
|
504
514
|
const err = sseEvent.data.error;
|
|
505
515
|
yield factory.responseWarning(err.message, err.type);
|
|
506
|
-
this.warn(err.message, err.type);
|
|
507
516
|
continue;
|
|
508
517
|
}
|
|
509
518
|
|
|
@@ -547,7 +556,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
547
556
|
yield factory.reasoningStarted(currentItemId, "redacted");
|
|
548
557
|
yield factory.reasoningDelta(currentItemId, textBlock(data));
|
|
549
558
|
const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
|
|
550
|
-
yield factory.reasoningCompleted(
|
|
559
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
551
560
|
output.push(redactedItem);
|
|
552
561
|
rawReplayContent.push({ type: "redacted_thinking", data });
|
|
553
562
|
currentItemType = null;
|
|
@@ -575,7 +584,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
575
584
|
if (currentItemType === "message" && currentItemId) {
|
|
576
585
|
const txt = (delta as unknown as { text: string }).text;
|
|
577
586
|
textBuffer += txt;
|
|
578
|
-
yield factory.messageDelta(currentItemId, txt);
|
|
587
|
+
yield factory.messageDelta(currentItemId, textBlock(txt));
|
|
579
588
|
}
|
|
580
589
|
break;
|
|
581
590
|
}
|
|
@@ -601,18 +610,16 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
601
610
|
|
|
602
611
|
case "content_block_stop": {
|
|
603
612
|
if (currentItemType === "message" && currentItemId) {
|
|
604
|
-
yield factory.messageCompleted(
|
|
613
|
+
yield factory.messageCompleted(currentItemId);
|
|
605
614
|
output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
|
|
606
615
|
rawReplayContent.push({ type: "text", text: textBuffer });
|
|
607
616
|
} else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
|
|
608
|
-
yield factory.reasoningCompleted(
|
|
609
|
-
reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId),
|
|
610
|
-
);
|
|
617
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
611
618
|
output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
|
|
612
619
|
rawReplayContent.push({ type: "thinking", thinking: thinkingBuffer });
|
|
613
620
|
} else if (currentItemType === "tool_call" && currentItemId) {
|
|
614
621
|
const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
|
|
615
|
-
yield factory.toolCallCompleted(
|
|
622
|
+
yield factory.toolCallCompleted(currentItemId);
|
|
616
623
|
output.push(tcItem);
|
|
617
624
|
rawReplayContent.push({
|
|
618
625
|
type: "tool_use",
|
|
@@ -643,12 +650,21 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
643
650
|
}
|
|
644
651
|
}
|
|
645
652
|
}
|
|
653
|
+
|
|
654
|
+
if (done) {
|
|
655
|
+
streamDone = true;
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
646
658
|
}
|
|
647
659
|
} finally {
|
|
648
|
-
|
|
660
|
+
try {
|
|
661
|
+
if (!streamDone) await reader.cancel().catch(() => undefined);
|
|
662
|
+
} finally {
|
|
663
|
+
reader.releaseLock();
|
|
664
|
+
}
|
|
649
665
|
}
|
|
650
666
|
|
|
651
|
-
if (
|
|
667
|
+
if (parser.getRemaining().trim().length > 0) {
|
|
652
668
|
yield factory.responseWarning("Stream ended with an incomplete Messages SSE frame", "STREAM_ERROR");
|
|
653
669
|
}
|
|
654
670
|
|
|
@@ -692,8 +708,9 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
692
708
|
yield event;
|
|
693
709
|
}
|
|
694
710
|
|
|
695
|
-
|
|
696
|
-
|
|
711
|
+
if (!completedEmitted) {
|
|
712
|
+
completedEmitted = true;
|
|
713
|
+
const finalResponse = this.buildResponse(
|
|
697
714
|
request,
|
|
698
715
|
{
|
|
699
716
|
output,
|
|
@@ -707,7 +724,16 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
707
724
|
rawResponseId,
|
|
708
725
|
},
|
|
709
726
|
factory,
|
|
710
|
-
)
|
|
711
|
-
|
|
727
|
+
);
|
|
728
|
+
yield factory.responseCompleted({
|
|
729
|
+
replay: finalResponse.replay,
|
|
730
|
+
stopReason: finalResponse.stopReason,
|
|
731
|
+
trace: finalResponse.backend,
|
|
732
|
+
usage: finalResponse.usage,
|
|
733
|
+
billing: finalResponse.billing,
|
|
734
|
+
auxiliary: finalResponse.auxiliary,
|
|
735
|
+
warnings: finalResponse.warnings,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
712
738
|
}
|
|
713
739
|
}
|
package/src/adapters/mock.ts
CHANGED
|
@@ -264,7 +264,14 @@ export function assertMockRequest(
|
|
|
264
264
|
|
|
265
265
|
export class MockAdapter extends AdapterBase {
|
|
266
266
|
readonly kind = "mock" as const;
|
|
267
|
-
readonly
|
|
267
|
+
readonly capabilities = {
|
|
268
|
+
textStreaming: "synthetic",
|
|
269
|
+
reasoningStreaming: "synthetic",
|
|
270
|
+
toolCallStreaming: "synthetic",
|
|
271
|
+
replay: "canonical",
|
|
272
|
+
usage: "final",
|
|
273
|
+
toolResultOutcomes: ["success", "error", "rejected"],
|
|
274
|
+
} as const;
|
|
268
275
|
|
|
269
276
|
private readonly handler: MockHandler;
|
|
270
277
|
private readonly providerMetadata?: Record<string, unknown>;
|
|
@@ -358,13 +365,21 @@ export class MockAdapter extends AdapterBase {
|
|
|
358
365
|
break;
|
|
359
366
|
}
|
|
360
367
|
case "complete": {
|
|
361
|
-
const
|
|
362
|
-
yield factory.responseCompleted(
|
|
368
|
+
const finalResponse = this.finalizeTurn(request, factory, mockRequest, output, step, stepCount);
|
|
369
|
+
yield factory.responseCompleted({
|
|
370
|
+
replay: finalResponse.replay,
|
|
371
|
+
stopReason: finalResponse.stopReason,
|
|
372
|
+
trace: finalResponse.backend,
|
|
373
|
+
usage: finalResponse.usage,
|
|
374
|
+
billing: finalResponse.billing,
|
|
375
|
+
auxiliary: finalResponse.auxiliary,
|
|
376
|
+
warnings: finalResponse.warnings,
|
|
377
|
+
});
|
|
363
378
|
return;
|
|
364
379
|
}
|
|
365
380
|
case "error": {
|
|
366
381
|
yield factory.responseWarning(step.message, step.code);
|
|
367
|
-
const
|
|
382
|
+
const finalResponse = this.finalizeTurn(
|
|
368
383
|
request,
|
|
369
384
|
factory,
|
|
370
385
|
mockRequest,
|
|
@@ -376,7 +391,15 @@ export class MockAdapter extends AdapterBase {
|
|
|
376
391
|
},
|
|
377
392
|
stepCount,
|
|
378
393
|
);
|
|
379
|
-
yield factory.responseCompleted(
|
|
394
|
+
yield factory.responseCompleted({
|
|
395
|
+
replay: finalResponse.replay,
|
|
396
|
+
stopReason: finalResponse.stopReason,
|
|
397
|
+
trace: finalResponse.backend,
|
|
398
|
+
usage: finalResponse.usage,
|
|
399
|
+
billing: finalResponse.billing,
|
|
400
|
+
auxiliary: finalResponse.auxiliary,
|
|
401
|
+
warnings: finalResponse.warnings,
|
|
402
|
+
});
|
|
380
403
|
return;
|
|
381
404
|
}
|
|
382
405
|
case "interrupt":
|
|
@@ -387,7 +410,7 @@ export class MockAdapter extends AdapterBase {
|
|
|
387
410
|
}
|
|
388
411
|
}
|
|
389
412
|
|
|
390
|
-
const
|
|
413
|
+
const finalResponse = this.finalizeTurn(
|
|
391
414
|
request,
|
|
392
415
|
factory,
|
|
393
416
|
mockRequest,
|
|
@@ -397,7 +420,15 @@ export class MockAdapter extends AdapterBase {
|
|
|
397
420
|
},
|
|
398
421
|
stepCount,
|
|
399
422
|
);
|
|
400
|
-
yield factory.responseCompleted(
|
|
423
|
+
yield factory.responseCompleted({
|
|
424
|
+
replay: finalResponse.replay,
|
|
425
|
+
stopReason: finalResponse.stopReason,
|
|
426
|
+
trace: finalResponse.backend,
|
|
427
|
+
usage: finalResponse.usage,
|
|
428
|
+
billing: finalResponse.billing,
|
|
429
|
+
auxiliary: finalResponse.auxiliary,
|
|
430
|
+
warnings: finalResponse.warnings,
|
|
431
|
+
});
|
|
401
432
|
} finally {
|
|
402
433
|
this.activeStream = false;
|
|
403
434
|
}
|
|
@@ -610,13 +641,15 @@ async function* emitMessage(
|
|
|
610
641
|
if (block.type === "text") {
|
|
611
642
|
for (const chunk of chunkText(block.text, stream)) {
|
|
612
643
|
await delayForChunk(stream, chunkIndex, chunk.length);
|
|
613
|
-
yield factory.messageDelta(item.id, chunk);
|
|
644
|
+
yield factory.messageDelta(item.id, textBlock(chunk));
|
|
614
645
|
chunkIndex += 1;
|
|
615
646
|
}
|
|
647
|
+
} else {
|
|
648
|
+
yield factory.messageDelta(item.id, block);
|
|
616
649
|
}
|
|
617
650
|
}
|
|
618
651
|
|
|
619
|
-
yield factory.messageCompleted(item);
|
|
652
|
+
yield factory.messageCompleted(item.id);
|
|
620
653
|
}
|
|
621
654
|
|
|
622
655
|
async function* emitReasoning(
|
|
@@ -644,7 +677,7 @@ async function* emitReasoning(
|
|
|
644
677
|
}
|
|
645
678
|
}
|
|
646
679
|
|
|
647
|
-
yield factory.reasoningCompleted(item);
|
|
680
|
+
yield factory.reasoningCompleted(item.id);
|
|
648
681
|
}
|
|
649
682
|
|
|
650
683
|
async function* emitToolCall(
|
|
@@ -664,7 +697,7 @@ async function* emitToolCall(
|
|
|
664
697
|
}
|
|
665
698
|
}
|
|
666
699
|
|
|
667
|
-
yield factory.toolCallCompleted(item);
|
|
700
|
+
yield factory.toolCallCompleted(item.id);
|
|
668
701
|
}
|
|
669
702
|
|
|
670
703
|
function resolveStepStreamOptions(
|