@codehz/ai 0.2.4 → 0.4.0
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 +3 -8
- package/dist/index.d.mts +79 -89
- package/dist/index.mjs +717 -931
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +153 -266
- package/src/adapters/messages.ts +179 -301
- package/src/adapters/mock.ts +1 -10
- package/src/adapters/ollama.ts +142 -257
- package/src/adapters/responses.ts +141 -251
- package/src/core/validation.ts +19 -0
- package/src/helpers/adapter-auxiliary.ts +1 -23
- package/src/helpers/adapter-base.ts +41 -7
- package/src/helpers/incremental-stream-parser.ts +58 -0
- package/src/helpers/index.ts +20 -8
- package/src/helpers/mapping.ts +1 -10
- package/src/helpers/provider-stream.ts +147 -0
- package/src/helpers/request-mapper.ts +47 -25
- package/src/helpers/usage-mapping.ts +36 -39
- package/src/types/adapter.ts +1 -12
- package/src/types/index.ts +1 -9
- package/src/types/items.ts +0 -1
- package/src/helpers/sse-parser.ts +0 -113
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 {
|
|
14
|
+
import { AIRequestError } from "../core/errors.js";
|
|
15
15
|
import {
|
|
16
16
|
textBlock,
|
|
17
17
|
messageItem,
|
|
@@ -20,14 +20,17 @@ import {
|
|
|
20
20
|
opaqueItem,
|
|
21
21
|
replayFromOutput,
|
|
22
22
|
mapStopReason,
|
|
23
|
-
blockToText,
|
|
24
23
|
contentBlocksToText,
|
|
25
24
|
} from "../helpers/mapping.js";
|
|
26
|
-
import {
|
|
27
|
-
import { assertOpaqueReplayEnvelope, providerHttpError } from "../helpers/adapter-security.js";
|
|
25
|
+
import { assertOpaqueReplayEnvelope } from "../helpers/adapter-security.js";
|
|
28
26
|
import { usageFromAnthropicMessages } from "../helpers/usage-mapping.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
27
|
+
import {
|
|
28
|
+
NormalizedRequestMapper,
|
|
29
|
+
createSseJsonParser,
|
|
30
|
+
openProviderJsonStream,
|
|
31
|
+
iterateProviderStreamBatches,
|
|
32
|
+
createCompletionGate,
|
|
33
|
+
} from "../helpers/index.js";
|
|
31
34
|
|
|
32
35
|
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
|
|
33
36
|
|
|
@@ -73,24 +76,7 @@ type MessagesAPITool = {
|
|
|
73
76
|
input_schema: Record<string, unknown>;
|
|
74
77
|
};
|
|
75
78
|
|
|
76
|
-
|
|
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);
|
|
79
|
+
const mapper = new NormalizedRequestMapper("messages");
|
|
94
80
|
|
|
95
81
|
function isMessagesReplayContentBlock(value: unknown): value is MessagesAPIContentBlock {
|
|
96
82
|
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
@@ -177,10 +163,10 @@ function synthesizeItemId(kind: "msg" | "reason" | "reason-redacted", blockIndex
|
|
|
177
163
|
return `${kind}-${blockIndex}-${responseId}`;
|
|
178
164
|
}
|
|
179
165
|
|
|
180
|
-
function
|
|
166
|
+
function parseProviderToolUseInput(input: string): Record<string, unknown> {
|
|
181
167
|
try {
|
|
182
|
-
const parsed = JSON.parse(input);
|
|
183
|
-
return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
|
|
168
|
+
const parsed: unknown = JSON.parse(input);
|
|
169
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
|
|
184
170
|
} catch {
|
|
185
171
|
return {};
|
|
186
172
|
}
|
|
@@ -251,7 +237,7 @@ function buildStreamMetadata(options: {
|
|
|
251
237
|
|
|
252
238
|
export class MessagesAdapter extends AdapterBase {
|
|
253
239
|
readonly kind = "messages" as const;
|
|
254
|
-
readonly
|
|
240
|
+
readonly isSyntheticStream = false;
|
|
255
241
|
|
|
256
242
|
private apiKey: string;
|
|
257
243
|
private apiVersion: string;
|
|
@@ -302,7 +288,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
302
288
|
type: "tool_use",
|
|
303
289
|
id: item.id,
|
|
304
290
|
name: item.name,
|
|
305
|
-
input:
|
|
291
|
+
input: mapper.parseToolArguments(item),
|
|
306
292
|
};
|
|
307
293
|
|
|
308
294
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
|
|
@@ -313,16 +299,12 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
313
299
|
break;
|
|
314
300
|
}
|
|
315
301
|
case "tool_result": {
|
|
316
|
-
mapper.
|
|
317
|
-
const content = mapper
|
|
318
|
-
.ensureTextBlocks(item.content, `tool_result ${item.callId} content`)
|
|
319
|
-
.map(blockToText)
|
|
320
|
-
.join("\n");
|
|
302
|
+
const content = mapper.textFromBlocks(item.content, `tool_result ${item.callId} content`);
|
|
321
303
|
const block: MessagesAPIContentBlock = {
|
|
322
304
|
type: "tool_result",
|
|
323
305
|
tool_use_id: item.callId,
|
|
324
306
|
content,
|
|
325
|
-
is_error: item.outcome
|
|
307
|
+
is_error: item.outcome !== "success",
|
|
326
308
|
};
|
|
327
309
|
if (pendingToolResultMessage && typeof pendingToolResultMessage.content !== "string") {
|
|
328
310
|
pendingToolResultMessage.content.push(block);
|
|
@@ -346,7 +328,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
346
328
|
}
|
|
347
329
|
case "opaque": {
|
|
348
330
|
// 尝试从 opaque replay item 中提取 assistant message
|
|
349
|
-
if (item.purpose !== "replay") break;
|
|
331
|
+
if (item.source !== "messages" || item.purpose !== "replay") break;
|
|
350
332
|
assertOpaqueReplayEnvelope(item.payload);
|
|
351
333
|
const payload = item.payload as Record<string, unknown>;
|
|
352
334
|
if (payload.role === "assistant" && "content" in payload) {
|
|
@@ -371,23 +353,20 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
371
353
|
|
|
372
354
|
if (systemPrompt) body.system = systemPrompt;
|
|
373
355
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
body.tool_choice = { type: "tool", name: request.toolChoice.name };
|
|
389
|
-
}
|
|
390
|
-
}
|
|
356
|
+
body.tools = mapper.mapToolsIfPresent(
|
|
357
|
+
request.tools,
|
|
358
|
+
(t): MessagesAPITool => ({
|
|
359
|
+
name: t.name,
|
|
360
|
+
description: t.description,
|
|
361
|
+
input_schema: t.inputSchema,
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
body.tool_choice = mapper.mapToolChoice<Exclude<MessagesAPIRequest["tool_choice"], undefined>>(request.toolChoice, {
|
|
366
|
+
auto: { type: "auto" } as const,
|
|
367
|
+
none: { type: "none" } as const,
|
|
368
|
+
tool: (name) => ({ type: "tool" as const, name }),
|
|
369
|
+
});
|
|
391
370
|
|
|
392
371
|
if (request.temperature !== undefined) body.temperature = request.temperature;
|
|
393
372
|
|
|
@@ -402,7 +381,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
402
381
|
request: NormalizedRequest,
|
|
403
382
|
): AsyncIterable<AIStreamEvent> {
|
|
404
383
|
const auxiliary = this.createAuxiliaryState(request);
|
|
405
|
-
|
|
384
|
+
const gate = createCompletionGate();
|
|
406
385
|
|
|
407
386
|
if (request.metadata) {
|
|
408
387
|
yield factory.responseWarning(
|
|
@@ -411,53 +390,20 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
411
390
|
);
|
|
412
391
|
}
|
|
413
392
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
body: JSON.stringify(providerRequest),
|
|
425
|
-
signal: request.signal,
|
|
426
|
-
});
|
|
427
|
-
} catch (err) {
|
|
428
|
-
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (!response.ok) {
|
|
432
|
-
const errorBody = await response.text().catch(() => "");
|
|
433
|
-
throw providerHttpError(response.status, errorBody);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const reader = response.body?.getReader();
|
|
437
|
-
if (!reader) {
|
|
438
|
-
throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// 流累积状态
|
|
442
|
-
const parser = new IncrementalStreamParser<MessagesSSEEvent>(splitSSEFrames, (frame: string) => {
|
|
443
|
-
let eventType = "";
|
|
444
|
-
let dataStr = "";
|
|
445
|
-
for (const rawLine of frame.split("\n")) {
|
|
446
|
-
const line = rawLine.trim();
|
|
447
|
-
if (line.startsWith("event: ")) eventType = line.slice(7).trim();
|
|
448
|
-
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
449
|
-
}
|
|
450
|
-
if (!eventType) return { status: "ignored" };
|
|
451
|
-
try {
|
|
452
|
-
const data = JSON.parse(dataStr);
|
|
453
|
-
return { status: "parsed", value: { type: eventType, data } as MessagesSSEEvent };
|
|
454
|
-
} catch {
|
|
455
|
-
return { status: "malformed" };
|
|
456
|
-
}
|
|
393
|
+
const { reader, headers } = await openProviderJsonStream({
|
|
394
|
+
fetchFn: this.fetchFn,
|
|
395
|
+
url: `${this.baseUrl}/messages`,
|
|
396
|
+
headers: {
|
|
397
|
+
"Content-Type": "application/json",
|
|
398
|
+
"x-api-key": this.apiKey,
|
|
399
|
+
"anthropic-version": this.apiVersion,
|
|
400
|
+
},
|
|
401
|
+
body: providerRequest,
|
|
402
|
+
signal: request.signal,
|
|
457
403
|
});
|
|
458
404
|
|
|
405
|
+
const parser = createSseJsonParser<MessagesSSEEvent>();
|
|
459
406
|
const output: OutputItem[] = [];
|
|
460
|
-
let streamDone = false;
|
|
461
407
|
let messageResponse: MessagesAPIMessageResponse | undefined;
|
|
462
408
|
let currentContentBlockIndex = -1;
|
|
463
409
|
let currentItemType: "message" | "reasoning" | "tool_call" | null = null;
|
|
@@ -465,215 +411,176 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
465
411
|
let currentToolName = "";
|
|
466
412
|
let currentArgsText = "";
|
|
467
413
|
let currentThinkingVisibility: "full" | "redacted" = "full";
|
|
468
|
-
let hasStreamedReasoning = false;
|
|
469
414
|
const rawReplayContent: MessagesAPIContentBlock[] = [];
|
|
470
415
|
|
|
471
|
-
// 内容块累积缓冲
|
|
472
416
|
let textBuffer = "";
|
|
473
417
|
let thinkingBuffer = "";
|
|
474
418
|
let argsBuffer = "";
|
|
475
419
|
|
|
476
|
-
// 完成响应数据
|
|
477
420
|
let stopReason: string | undefined;
|
|
478
421
|
let stopSequence: string | null | undefined;
|
|
479
422
|
let rawResponseId = "";
|
|
480
423
|
|
|
481
424
|
if (request.include?.providerMetadata !== "off") {
|
|
482
|
-
const headerMetadata = pickProviderHeaders(
|
|
425
|
+
const headerMetadata = pickProviderHeaders(headers);
|
|
483
426
|
auxiliary.recordProviderMetadata(
|
|
484
427
|
"header",
|
|
485
428
|
Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : undefined,
|
|
486
429
|
);
|
|
487
430
|
}
|
|
488
431
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
for (const sseEvent of events) {
|
|
510
|
-
switch (sseEvent.type) {
|
|
511
|
-
case "ping":
|
|
512
|
-
continue;
|
|
432
|
+
for await (const batch of iterateProviderStreamBatches({
|
|
433
|
+
reader,
|
|
434
|
+
parser,
|
|
435
|
+
factory,
|
|
436
|
+
providerLabel: "Messages",
|
|
437
|
+
transportLabel: "SSE event(s)",
|
|
438
|
+
incompleteMessage: "Stream ended with an incomplete Messages SSE frame",
|
|
439
|
+
})) {
|
|
440
|
+
for (const warning of batch.warnings) yield warning;
|
|
441
|
+
|
|
442
|
+
for (const sseEvent of batch.items) {
|
|
443
|
+
switch (sseEvent.type) {
|
|
444
|
+
case "ping":
|
|
445
|
+
continue;
|
|
446
|
+
|
|
447
|
+
case "error": {
|
|
448
|
+
const err = sseEvent.data.error;
|
|
449
|
+
yield factory.responseWarning(err.message, err.type);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
513
452
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
453
|
+
case "message_start": {
|
|
454
|
+
messageResponse = sseEvent.data.message;
|
|
455
|
+
rawResponseId = messageResponse.id;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
519
458
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
459
|
+
case "content_block_start": {
|
|
460
|
+
const block = sseEvent.data.content_block;
|
|
461
|
+
currentContentBlockIndex = sseEvent.data.index;
|
|
462
|
+
|
|
463
|
+
switch (block.type) {
|
|
464
|
+
case "text": {
|
|
465
|
+
currentItemType = "message";
|
|
466
|
+
currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
|
|
467
|
+
textBuffer = "";
|
|
468
|
+
yield factory.messageStarted(currentItemId);
|
|
469
|
+
break;
|
|
526
470
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
yield factory.reasoningDelta(currentItemId, textBlock(data));
|
|
559
|
-
const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
|
|
560
|
-
yield factory.reasoningCompleted(currentItemId);
|
|
561
|
-
output.push(redactedItem);
|
|
562
|
-
rawReplayContent.push({ type: "redacted_thinking", data });
|
|
563
|
-
currentItemType = null;
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
case "tool_use": {
|
|
567
|
-
const tuBlock = block as unknown as { id: string; name: string };
|
|
568
|
-
currentItemType = "tool_call";
|
|
569
|
-
currentItemId = tuBlock.id;
|
|
570
|
-
currentToolName = tuBlock.name;
|
|
571
|
-
currentArgsText = "";
|
|
572
|
-
argsBuffer = "";
|
|
573
|
-
yield factory.toolCallStarted(currentItemId, currentToolName);
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
471
|
+
case "thinking": {
|
|
472
|
+
currentItemType = "reasoning";
|
|
473
|
+
currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
|
|
474
|
+
currentThinkingVisibility = "full";
|
|
475
|
+
thinkingBuffer = "";
|
|
476
|
+
yield factory.reasoningStarted(currentItemId, "full");
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "redacted_thinking": {
|
|
480
|
+
currentItemType = "reasoning";
|
|
481
|
+
currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
|
|
482
|
+
currentThinkingVisibility = "redacted";
|
|
483
|
+
const data = (block as unknown as { data: string }).data;
|
|
484
|
+
yield factory.reasoningStarted(currentItemId, "redacted");
|
|
485
|
+
yield factory.reasoningDelta(currentItemId, textBlock(data));
|
|
486
|
+
const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
|
|
487
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
488
|
+
output.push(redactedItem);
|
|
489
|
+
rawReplayContent.push({ type: "redacted_thinking", data });
|
|
490
|
+
currentItemType = null;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case "tool_use": {
|
|
494
|
+
const tuBlock = block as unknown as { id: string; name: string };
|
|
495
|
+
currentItemType = "tool_call";
|
|
496
|
+
currentItemId = tuBlock.id;
|
|
497
|
+
currentToolName = tuBlock.name;
|
|
498
|
+
currentArgsText = "";
|
|
499
|
+
argsBuffer = "";
|
|
500
|
+
yield factory.toolCallStarted(currentItemId, currentToolName);
|
|
501
|
+
break;
|
|
576
502
|
}
|
|
577
|
-
continue;
|
|
578
503
|
}
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
case "content_block_delta": {
|
|
508
|
+
const delta = sseEvent.data.delta;
|
|
579
509
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const txt = (delta as unknown as { text: string }).text;
|
|
587
|
-
textBuffer += txt;
|
|
588
|
-
yield factory.messageDelta(currentItemId, textBlock(txt));
|
|
589
|
-
}
|
|
590
|
-
break;
|
|
510
|
+
switch (delta.type) {
|
|
511
|
+
case "text_delta": {
|
|
512
|
+
if (currentItemType === "message" && currentItemId) {
|
|
513
|
+
const txt = (delta as unknown as { text: string }).text;
|
|
514
|
+
textBuffer += txt;
|
|
515
|
+
yield factory.messageDelta(currentItemId, textBlock(txt));
|
|
591
516
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case "thinking_delta": {
|
|
520
|
+
if (currentItemType === "reasoning" && currentItemId) {
|
|
521
|
+
const txt = (delta as unknown as { thinking: string }).thinking;
|
|
522
|
+
thinkingBuffer += txt;
|
|
523
|
+
yield factory.reasoningDelta(currentItemId, textBlock(txt));
|
|
599
524
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case "input_json_delta": {
|
|
528
|
+
if (currentItemType === "tool_call" && currentItemId) {
|
|
529
|
+
const partial = (delta as unknown as { partial_json: string }).partial_json;
|
|
530
|
+
argsBuffer += partial;
|
|
531
|
+
yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
|
|
607
532
|
}
|
|
533
|
+
break;
|
|
608
534
|
}
|
|
609
|
-
continue;
|
|
610
535
|
}
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
611
538
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
currentItemType = null;
|
|
634
|
-
currentItemId = "";
|
|
635
|
-
continue;
|
|
539
|
+
case "content_block_stop": {
|
|
540
|
+
if (currentItemType === "message" && currentItemId) {
|
|
541
|
+
yield factory.messageCompleted(currentItemId);
|
|
542
|
+
output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
|
|
543
|
+
rawReplayContent.push({ type: "text", text: textBuffer });
|
|
544
|
+
} else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
|
|
545
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
546
|
+
output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
|
|
547
|
+
rawReplayContent.push({ type: "thinking", thinking: thinkingBuffer });
|
|
548
|
+
} else if (currentItemType === "tool_call" && currentItemId) {
|
|
549
|
+
const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
|
|
550
|
+
yield factory.toolCallCompleted(currentItemId);
|
|
551
|
+
output.push(tcItem);
|
|
552
|
+
rawReplayContent.push({
|
|
553
|
+
type: "tool_use",
|
|
554
|
+
id: currentItemId,
|
|
555
|
+
name: currentToolName,
|
|
556
|
+
input: parseProviderToolUseInput(currentArgsText || argsBuffer),
|
|
557
|
+
});
|
|
636
558
|
}
|
|
637
559
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (u) {
|
|
643
|
-
auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
|
|
644
|
-
}
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
560
|
+
currentItemType = null;
|
|
561
|
+
currentItemId = "";
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
647
564
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
565
|
+
case "message_delta": {
|
|
566
|
+
stopReason = sseEvent.data.delta.stop_reason;
|
|
567
|
+
stopSequence = sseEvent.data.delta.stop_sequence;
|
|
568
|
+
const u = sseEvent.data.usage;
|
|
569
|
+
if (u) {
|
|
570
|
+
auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
|
|
651
571
|
}
|
|
572
|
+
continue;
|
|
652
573
|
}
|
|
653
|
-
}
|
|
654
574
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
575
|
+
case "message_stop": {
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
658
578
|
}
|
|
659
579
|
}
|
|
660
|
-
} finally {
|
|
661
|
-
try {
|
|
662
|
-
if (!streamDone) await reader.cancel().catch(() => undefined);
|
|
663
|
-
} finally {
|
|
664
|
-
reader.releaseLock();
|
|
665
|
-
}
|
|
666
580
|
}
|
|
667
581
|
|
|
668
|
-
if (parser.getRemaining().trim().length > 0) {
|
|
669
|
-
yield factory.responseWarning("Stream ended with an incomplete Messages SSE frame", "STREAM_ERROR");
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// 构造 replay
|
|
673
582
|
const replay = [...replayFromOutput(output)];
|
|
674
583
|
|
|
675
|
-
// 附加 opaque replay item 用于续接
|
|
676
|
-
// 保存 provider 原始 block 以实现高保真 replay
|
|
677
584
|
if (messageResponse) {
|
|
678
585
|
const replayContent = rawReplayContent.length > 0 ? rawReplayContent : messageResponse.content;
|
|
679
586
|
replay.push(
|
|
@@ -699,41 +606,12 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
699
606
|
);
|
|
700
607
|
}
|
|
701
608
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
for (const event of auxiliaryResult.events) {
|
|
709
|
-
yield event;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (!completedEmitted) {
|
|
713
|
-
completedEmitted = true;
|
|
714
|
-
const finalResponse = this.buildResponse(
|
|
715
|
-
request,
|
|
716
|
-
{
|
|
717
|
-
output,
|
|
718
|
-
replay,
|
|
719
|
-
stopReason: stopReason ? mapStopReason(stopReason) : undefined,
|
|
720
|
-
usage: auxiliaryResult.usage,
|
|
721
|
-
billing: auxiliaryResult.billing,
|
|
722
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
723
|
-
warnings: auxiliaryResult.warnings,
|
|
724
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
725
|
-
rawResponseId,
|
|
726
|
-
},
|
|
727
|
-
factory,
|
|
728
|
-
);
|
|
729
|
-
yield factory.responseCompleted({
|
|
730
|
-
replay: finalResponse.replay,
|
|
731
|
-
stopReason: finalResponse.stopReason,
|
|
732
|
-
trace: finalResponse.backend,
|
|
733
|
-
usage: finalResponse.usage,
|
|
734
|
-
billing: finalResponse.billing,
|
|
735
|
-
auxiliary: finalResponse.auxiliary,
|
|
736
|
-
warnings: finalResponse.warnings,
|
|
609
|
+
if (gate.tryComplete()) {
|
|
610
|
+
yield* this.emitStreamCompleted(factory, request, auxiliary, {
|
|
611
|
+
output,
|
|
612
|
+
replay,
|
|
613
|
+
stopReason: stopReason ? mapStopReason(stopReason) : undefined,
|
|
614
|
+
rawResponseId,
|
|
737
615
|
});
|
|
738
616
|
}
|
|
739
617
|
}
|
package/src/adapters/mock.ts
CHANGED
|
@@ -120,7 +120,6 @@ export type MockToolCallStep = {
|
|
|
120
120
|
id: string;
|
|
121
121
|
name: string;
|
|
122
122
|
argumentsText: string;
|
|
123
|
-
argumentsJson?: unknown;
|
|
124
123
|
streamArguments?: boolean;
|
|
125
124
|
stream?: MockTextStreamOptions | false;
|
|
126
125
|
};
|
|
@@ -266,14 +265,7 @@ export function assertMockRequest(
|
|
|
266
265
|
|
|
267
266
|
export class MockAdapter extends AdapterBase {
|
|
268
267
|
readonly kind = "mock" as const;
|
|
269
|
-
readonly
|
|
270
|
-
textStreaming: "synthetic",
|
|
271
|
-
reasoningStreaming: "synthetic",
|
|
272
|
-
toolCallStreaming: "synthetic",
|
|
273
|
-
replay: "canonical",
|
|
274
|
-
usage: "final",
|
|
275
|
-
toolResultOutcomes: ["success", "error", "rejected"],
|
|
276
|
-
} as const;
|
|
268
|
+
readonly isSyntheticStream = true;
|
|
277
269
|
|
|
278
270
|
private readonly handler: MockHandler;
|
|
279
271
|
private readonly providerMetadata?: Record<string, unknown>;
|
|
@@ -572,7 +564,6 @@ function createToolCallFromStep(step: MockToolCallStep): ToolCallItem {
|
|
|
572
564
|
id: step.id,
|
|
573
565
|
name: step.name,
|
|
574
566
|
argumentsText: step.argumentsText,
|
|
575
|
-
argumentsJson: step.argumentsJson,
|
|
576
567
|
};
|
|
577
568
|
}
|
|
578
569
|
|