@codehz/ai 0.1.8 → 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 +153 -31
- package/dist/index.mjs +1290 -727
- 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 +6 -43
- 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 -4
package/dist/index.mjs
CHANGED
|
@@ -11,8 +11,10 @@ var AIError = class extends Error {
|
|
|
11
11
|
};
|
|
12
12
|
/** 请求构造失败 — 参数校验不通过。在进入 adapter 前同步抛错。 */
|
|
13
13
|
var AIRequestError = class extends AIError {
|
|
14
|
-
|
|
14
|
+
issues;
|
|
15
|
+
constructor(message, code, issues) {
|
|
15
16
|
super(message, code, "AIRequestError");
|
|
17
|
+
this.issues = issues;
|
|
16
18
|
}
|
|
17
19
|
};
|
|
18
20
|
/** Provider 调用失败 — HTTP 非 2xx、网络错误。由 AdapterBase 捕获转为 warning。 */
|
|
@@ -59,7 +61,9 @@ const WarningCode = {
|
|
|
59
61
|
/** 能力降级 */
|
|
60
62
|
CAPABILITY_DOWNGRADE: "CAPABILITY_DOWNGRADE",
|
|
61
63
|
/** 模拟流式 */
|
|
62
|
-
SYNTHETIC_STREAM: "SYNTHETIC_STREAM"
|
|
64
|
+
SYNTHETIC_STREAM: "SYNTHETIC_STREAM",
|
|
65
|
+
/** 工具调用以批量方式到达(非 token 级流式) */
|
|
66
|
+
TOOL_CALL_BATCHED: "TOOL_CALL_BATCHED"
|
|
63
67
|
};
|
|
64
68
|
//#endregion
|
|
65
69
|
//#region src/core/validation.ts
|
|
@@ -194,6 +198,16 @@ function validateToolChoice(toolChoice, issues) {
|
|
|
194
198
|
if (toolChoice === "auto" || toolChoice === "none") return;
|
|
195
199
|
if (!isRecord(toolChoice) || toolChoice.type !== "tool" || typeof toolChoice.name !== "string" || toolChoice.name.length === 0) pushIssue(issues, "toolChoice", "TOOL_CHOICE_INVALID", "toolChoice must be auto, none, or { type: \"tool\", name }");
|
|
196
200
|
}
|
|
201
|
+
/** Validate include settings, appending issues to the given array. */
|
|
202
|
+
function validateInclude(include, issues) {
|
|
203
|
+
if (!isRecord(include)) {
|
|
204
|
+
pushIssue(issues, "include", "INCLUDE_INVALID", "include must be an object");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (include.usage !== void 0 && (typeof include.usage !== "string" || !INCLUDE_MODES.has(include.usage))) pushIssue(issues, "include.usage", "INCLUDE_USAGE_INVALID", "include.usage must be off or best_effort");
|
|
208
|
+
if (include.billing !== void 0 && (typeof include.billing !== "string" || !INCLUDE_MODES.has(include.billing))) pushIssue(issues, "include.billing", "INCLUDE_BILLING_INVALID", "include.billing must be off or best_effort");
|
|
209
|
+
if (include.providerMetadata !== void 0 && (typeof include.providerMetadata !== "string" || !INCLUDE_MODES.has(include.providerMetadata))) pushIssue(issues, "include.providerMetadata", "INCLUDE_PROVIDER_METADATA_INVALID", "include.providerMetadata must be off or best_effort");
|
|
210
|
+
}
|
|
197
211
|
/**
|
|
198
212
|
* 校验 AIRequest,返回校验问题列表。
|
|
199
213
|
* 空数组表示无问题。
|
|
@@ -205,7 +219,7 @@ function validateRequest(request) {
|
|
|
205
219
|
if (!Array.isArray(request.input) || request.input.length === 0) pushIssue(issues, "input", "INPUT_EMPTY", "input must be a non-empty array");
|
|
206
220
|
if (Array.isArray(request.input)) for (let i = 0; i < request.input.length; i++) validateInputItem(request.input[i], `input[${i}]`, issues);
|
|
207
221
|
if (request.temperature !== void 0) {
|
|
208
|
-
if (typeof request.temperature !== "number" ||
|
|
222
|
+
if (typeof request.temperature !== "number" || !Number.isFinite(request.temperature)) issues.push({
|
|
209
223
|
field: "temperature",
|
|
210
224
|
code: "TEMPERATURE_NOT_NUMBER",
|
|
211
225
|
message: "temperature must be a number"
|
|
@@ -217,7 +231,7 @@ function validateRequest(request) {
|
|
|
217
231
|
});
|
|
218
232
|
}
|
|
219
233
|
if (request.maxOutputTokens !== void 0) {
|
|
220
|
-
if (typeof request.maxOutputTokens !== "number" ||
|
|
234
|
+
if (typeof request.maxOutputTokens !== "number" || !Number.isFinite(request.maxOutputTokens)) issues.push({
|
|
221
235
|
field: "maxOutputTokens",
|
|
222
236
|
code: "MAX_OUTPUT_TOKENS_NOT_NUMBER",
|
|
223
237
|
message: "maxOutputTokens must be a number"
|
|
@@ -228,12 +242,7 @@ function validateRequest(request) {
|
|
|
228
242
|
message: "maxOutputTokens must be a positive integer"
|
|
229
243
|
});
|
|
230
244
|
}
|
|
231
|
-
if (request.include !== void 0)
|
|
232
|
-
else {
|
|
233
|
-
if (request.include.usage !== void 0 && !INCLUDE_MODES.has(request.include.usage)) pushIssue(issues, "include.usage", "INCLUDE_USAGE_INVALID", "include.usage must be off or best_effort");
|
|
234
|
-
if (request.include.billing !== void 0 && !INCLUDE_MODES.has(request.include.billing)) pushIssue(issues, "include.billing", "INCLUDE_BILLING_INVALID", "include.billing must be off or best_effort");
|
|
235
|
-
if (request.include.providerMetadata !== void 0 && !INCLUDE_MODES.has(request.include.providerMetadata)) pushIssue(issues, "include.providerMetadata", "INCLUDE_PROVIDER_METADATA_INVALID", "include.providerMetadata must be off or best_effort");
|
|
236
|
-
}
|
|
245
|
+
if (request.include !== void 0) validateInclude(request.include, issues);
|
|
237
246
|
if (request.metadata !== void 0) {
|
|
238
247
|
if (!isRecord(request.metadata)) pushIssue(issues, "metadata", "METADATA_INVALID", "metadata must be an object");
|
|
239
248
|
else for (const [key, value] of Object.entries(request.metadata)) if (typeof value !== "string") pushIssue(issues, `metadata.${key}`, "METADATA_VALUE_INVALID", `metadata.${key} must be a string`);
|
|
@@ -260,8 +269,9 @@ function validateRequest(request) {
|
|
|
260
269
|
* 适用于客户端入口的快速失败检查。
|
|
261
270
|
*/
|
|
262
271
|
function assertValidRequest(request) {
|
|
263
|
-
const
|
|
264
|
-
|
|
272
|
+
const issues = validateRequest(request);
|
|
273
|
+
const first = issues[0];
|
|
274
|
+
if (first) throw new AIRequestError(first.message, first.code, issues);
|
|
265
275
|
}
|
|
266
276
|
//#endregion
|
|
267
277
|
//#region src/core/normalize.ts
|
|
@@ -279,6 +289,11 @@ const DEFAULT_INCLUDE = {
|
|
|
279
289
|
*/
|
|
280
290
|
function normalizeRequest(request, options) {
|
|
281
291
|
const { model, defaults } = options;
|
|
292
|
+
const earlyIncludeIssues = [];
|
|
293
|
+
if (request.include !== void 0) validateInclude(request.include, earlyIncludeIssues);
|
|
294
|
+
if (defaults?.include !== void 0) validateInclude(defaults.include, earlyIncludeIssues);
|
|
295
|
+
const firstIncludeIssue = earlyIncludeIssues[0];
|
|
296
|
+
if (firstIncludeIssue) throw new AIRequestError(firstIncludeIssue.message, firstIncludeIssue.code, earlyIncludeIssues);
|
|
282
297
|
const merged = {
|
|
283
298
|
...defaults,
|
|
284
299
|
...request,
|
|
@@ -350,11 +365,11 @@ function createEventFactory(state) {
|
|
|
350
365
|
...data
|
|
351
366
|
};
|
|
352
367
|
},
|
|
353
|
-
responseCompleted(
|
|
368
|
+
responseCompleted(completion) {
|
|
354
369
|
return {
|
|
355
370
|
...base(),
|
|
356
371
|
type: "response.completed",
|
|
357
|
-
|
|
372
|
+
...completion
|
|
358
373
|
};
|
|
359
374
|
},
|
|
360
375
|
messageStarted(id) {
|
|
@@ -367,22 +382,19 @@ function createEventFactory(state) {
|
|
|
367
382
|
}
|
|
368
383
|
};
|
|
369
384
|
},
|
|
370
|
-
messageDelta(itemId,
|
|
385
|
+
messageDelta(itemId, delta) {
|
|
371
386
|
return {
|
|
372
387
|
...base(),
|
|
373
388
|
type: "message.delta",
|
|
374
389
|
itemId,
|
|
375
|
-
delta
|
|
376
|
-
type: "text",
|
|
377
|
-
text
|
|
378
|
-
}
|
|
390
|
+
delta
|
|
379
391
|
};
|
|
380
392
|
},
|
|
381
|
-
messageCompleted(
|
|
393
|
+
messageCompleted(itemId) {
|
|
382
394
|
return {
|
|
383
395
|
...base(),
|
|
384
396
|
type: "message.completed",
|
|
385
|
-
|
|
397
|
+
itemId
|
|
386
398
|
};
|
|
387
399
|
},
|
|
388
400
|
reasoningStarted(id, visibility) {
|
|
@@ -403,11 +415,11 @@ function createEventFactory(state) {
|
|
|
403
415
|
delta
|
|
404
416
|
};
|
|
405
417
|
},
|
|
406
|
-
reasoningCompleted(
|
|
418
|
+
reasoningCompleted(itemId) {
|
|
407
419
|
return {
|
|
408
420
|
...base(),
|
|
409
421
|
type: "reasoning.completed",
|
|
410
|
-
|
|
422
|
+
itemId
|
|
411
423
|
};
|
|
412
424
|
},
|
|
413
425
|
toolCallStarted(id, name) {
|
|
@@ -428,11 +440,11 @@ function createEventFactory(state) {
|
|
|
428
440
|
delta
|
|
429
441
|
};
|
|
430
442
|
},
|
|
431
|
-
toolCallCompleted(
|
|
443
|
+
toolCallCompleted(itemId) {
|
|
432
444
|
return {
|
|
433
445
|
...base(),
|
|
434
446
|
type: "tool_call.completed",
|
|
435
|
-
|
|
447
|
+
itemId
|
|
436
448
|
};
|
|
437
449
|
},
|
|
438
450
|
/** 返回当前已发出的 sequence 计数(用于断言) */
|
|
@@ -446,6 +458,20 @@ function createEventFactory(state) {
|
|
|
446
458
|
};
|
|
447
459
|
}
|
|
448
460
|
//#endregion
|
|
461
|
+
//#region src/core/merge-auxiliary.ts
|
|
462
|
+
function mergeAuxiliary(base, patch) {
|
|
463
|
+
if (!base && !patch) return void 0;
|
|
464
|
+
const merged = {
|
|
465
|
+
...base,
|
|
466
|
+
...patch
|
|
467
|
+
};
|
|
468
|
+
if (base?.providerMetadata || patch?.providerMetadata) merged.providerMetadata = {
|
|
469
|
+
...base?.providerMetadata,
|
|
470
|
+
...patch?.providerMetadata
|
|
471
|
+
};
|
|
472
|
+
return merged;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
449
475
|
//#region src/core/aggregator.ts
|
|
450
476
|
function createAggregatorState() {
|
|
451
477
|
return {
|
|
@@ -454,10 +480,45 @@ function createAggregatorState() {
|
|
|
454
480
|
warningSet: /* @__PURE__ */ new Set(),
|
|
455
481
|
output: [],
|
|
456
482
|
textParts: [],
|
|
457
|
-
toolCalls: []
|
|
483
|
+
toolCalls: [],
|
|
484
|
+
started: false,
|
|
485
|
+
completed: false,
|
|
486
|
+
activeItems: /* @__PURE__ */ new Map()
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function getActiveItem(state, itemId, expectedType) {
|
|
490
|
+
const item = state.activeItems.get(itemId);
|
|
491
|
+
if (!item) throw streamProtocolError(`Received ${expectedType} delta/completed for unknown item: ${itemId}`);
|
|
492
|
+
if (item.type !== expectedType) throw streamProtocolError(`Item ${itemId} started as ${item.type} but received ${expectedType} event`);
|
|
493
|
+
return item;
|
|
494
|
+
}
|
|
495
|
+
function finalizeMessage(active) {
|
|
496
|
+
return {
|
|
497
|
+
type: "message",
|
|
498
|
+
id: active.id,
|
|
499
|
+
role: active.role,
|
|
500
|
+
content: active.content
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function finalizeReasoning(active) {
|
|
504
|
+
return {
|
|
505
|
+
type: "reasoning",
|
|
506
|
+
id: active.id,
|
|
507
|
+
visibility: active.visibility,
|
|
508
|
+
content: active.content
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function finalizeToolCall(active) {
|
|
512
|
+
return {
|
|
513
|
+
type: "tool_call",
|
|
514
|
+
id: active.id,
|
|
515
|
+
name: active.name,
|
|
516
|
+
argumentsText: active.argumentsText
|
|
458
517
|
};
|
|
459
518
|
}
|
|
460
519
|
function handleResponseStarted(state, event) {
|
|
520
|
+
if (state.started) throw streamProtocolError("Stream must contain exactly one response.started event");
|
|
521
|
+
state.started = true;
|
|
461
522
|
state.responseId = event.responseId;
|
|
462
523
|
state.model = event.model;
|
|
463
524
|
state.backendInfo = event.backend;
|
|
@@ -474,47 +535,97 @@ function handleResponseAuxiliary(state, event) {
|
|
|
474
535
|
...state.billing,
|
|
475
536
|
...event.billing
|
|
476
537
|
};
|
|
477
|
-
if (event.auxiliary) state.auxiliary = mergeAuxiliary
|
|
538
|
+
if (event.auxiliary) state.auxiliary = mergeAuxiliary(state.auxiliary, event.auxiliary) ?? {};
|
|
539
|
+
}
|
|
540
|
+
function handleMessageStarted(state, event) {
|
|
541
|
+
const id = event.item.id;
|
|
542
|
+
if (state.activeItems.has(id)) throw streamProtocolError(`Item with id ${id} is already active`);
|
|
543
|
+
state.activeItems.set(id, {
|
|
544
|
+
type: "message",
|
|
545
|
+
id,
|
|
546
|
+
role: event.item.role,
|
|
547
|
+
content: []
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
function handleMessageDelta(state, event) {
|
|
551
|
+
getActiveItem(state, event.itemId, "message").content.push(event.delta);
|
|
478
552
|
}
|
|
479
553
|
function handleMessageCompleted(state, event) {
|
|
480
|
-
state
|
|
481
|
-
|
|
554
|
+
const active = getActiveItem(state, event.itemId, "message");
|
|
555
|
+
state.activeItems.delete(event.itemId);
|
|
556
|
+
const item = finalizeMessage(active);
|
|
557
|
+
state.output.push(item);
|
|
558
|
+
pushMessageText(state, item);
|
|
559
|
+
}
|
|
560
|
+
function handleReasoningStarted(state, event) {
|
|
561
|
+
const id = event.item.id;
|
|
562
|
+
if (state.activeItems.has(id)) throw streamProtocolError(`Item with id ${id} is already active`);
|
|
563
|
+
state.activeItems.set(id, {
|
|
564
|
+
type: "reasoning",
|
|
565
|
+
id,
|
|
566
|
+
visibility: event.item.visibility,
|
|
567
|
+
content: []
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function handleReasoningDelta(state, event) {
|
|
571
|
+
getActiveItem(state, event.itemId, "reasoning").content.push(event.delta);
|
|
482
572
|
}
|
|
483
573
|
function handleReasoningCompleted(state, event) {
|
|
484
|
-
state
|
|
574
|
+
const active = getActiveItem(state, event.itemId, "reasoning");
|
|
575
|
+
state.activeItems.delete(event.itemId);
|
|
576
|
+
state.output.push(finalizeReasoning(active));
|
|
577
|
+
}
|
|
578
|
+
function handleToolCallStarted(state, event) {
|
|
579
|
+
const id = event.item.id;
|
|
580
|
+
if (state.activeItems.has(id)) throw streamProtocolError(`Item with id ${id} is already active`);
|
|
581
|
+
state.activeItems.set(id, {
|
|
582
|
+
type: "tool_call",
|
|
583
|
+
id,
|
|
584
|
+
name: event.item.name,
|
|
585
|
+
argumentsText: ""
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function handleToolCallDelta(state, event) {
|
|
589
|
+
const active = getActiveItem(state, event.itemId, "tool_call");
|
|
590
|
+
if (event.delta.argumentsText) active.argumentsText += event.delta.argumentsText;
|
|
485
591
|
}
|
|
486
592
|
function handleToolCallCompleted(state, event) {
|
|
487
|
-
state
|
|
488
|
-
state.
|
|
593
|
+
const active = getActiveItem(state, event.itemId, "tool_call");
|
|
594
|
+
state.activeItems.delete(event.itemId);
|
|
595
|
+
const item = finalizeToolCall(active);
|
|
596
|
+
state.output.push(item);
|
|
597
|
+
state.toolCalls.push(item);
|
|
489
598
|
}
|
|
490
599
|
function handleResponseCompleted(state, event) {
|
|
491
|
-
state.
|
|
492
|
-
state.
|
|
493
|
-
state.
|
|
494
|
-
state.
|
|
495
|
-
|
|
600
|
+
if (state.activeItems.size > 0) throw streamProtocolError("response.completed received while active items still pending");
|
|
601
|
+
state.completed = true;
|
|
602
|
+
state.replayFromAdapter = event.replay;
|
|
603
|
+
state.stopReasonFromAdapter = event.stopReason;
|
|
604
|
+
state.backendFromAdapter = event.trace;
|
|
605
|
+
if (event.usage) state.usage = {
|
|
496
606
|
...state.usage,
|
|
497
|
-
...event.
|
|
607
|
+
...event.usage
|
|
498
608
|
};
|
|
499
|
-
if (event.
|
|
609
|
+
if (event.billing) state.billing = {
|
|
500
610
|
...state.billing,
|
|
501
|
-
...event.
|
|
611
|
+
...event.billing
|
|
502
612
|
};
|
|
503
|
-
if (event.
|
|
504
|
-
if (event.
|
|
613
|
+
if (event.auxiliary) state.auxiliary = mergeAuxiliary(state.auxiliary, event.auxiliary) ?? {};
|
|
614
|
+
if (event.warnings) pushWarnings(state, event.warnings);
|
|
615
|
+
if (event.opaqueOutput) state.output.push(...event.opaqueOutput);
|
|
505
616
|
}
|
|
506
617
|
function buildResponse(state) {
|
|
507
|
-
const
|
|
618
|
+
const backendFromCompleted = state.backendFromAdapter;
|
|
508
619
|
const backend = {
|
|
509
|
-
adapter:
|
|
510
|
-
isSyntheticStream:
|
|
511
|
-
requestId:
|
|
512
|
-
rawResponseId:
|
|
513
|
-
metadataSources:
|
|
514
|
-
warnings:
|
|
620
|
+
adapter: backendFromCompleted?.adapter ?? state.backendInfo?.kind ?? "unknown",
|
|
621
|
+
isSyntheticStream: backendFromCompleted?.isSyntheticStream ?? state.backendInfo?.isSynthetic ?? false,
|
|
622
|
+
requestId: backendFromCompleted?.requestId ?? state.responseId,
|
|
623
|
+
rawResponseId: backendFromCompleted?.rawResponseId,
|
|
624
|
+
metadataSources: backendFromCompleted?.metadataSources,
|
|
625
|
+
warnings: backendFromCompleted?.warnings
|
|
515
626
|
};
|
|
516
627
|
return {
|
|
517
|
-
id: state.
|
|
628
|
+
id: state.responseId,
|
|
518
629
|
output: state.output,
|
|
519
630
|
replay: state.replayFromAdapter ?? [],
|
|
520
631
|
text: state.textParts.join(""),
|
|
@@ -537,6 +648,7 @@ function aggregateEvents(events) {
|
|
|
537
648
|
return finalizeAggregation(state);
|
|
538
649
|
}
|
|
539
650
|
function aggregateEvent(state, event) {
|
|
651
|
+
validateEventEnvelope(state, event);
|
|
540
652
|
state.lastEventType = event.type;
|
|
541
653
|
switch (event.type) {
|
|
542
654
|
case "response.started":
|
|
@@ -549,17 +661,29 @@ function aggregateEvent(state, event) {
|
|
|
549
661
|
handleResponseAuxiliary(state, event);
|
|
550
662
|
break;
|
|
551
663
|
case "message.started":
|
|
664
|
+
handleMessageStarted(state, event);
|
|
665
|
+
break;
|
|
552
666
|
case "message.delta":
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
case "tool_call.started":
|
|
556
|
-
case "tool_call.delta": break;
|
|
667
|
+
handleMessageDelta(state, event);
|
|
668
|
+
break;
|
|
557
669
|
case "message.completed":
|
|
558
670
|
handleMessageCompleted(state, event);
|
|
559
671
|
break;
|
|
672
|
+
case "reasoning.started":
|
|
673
|
+
handleReasoningStarted(state, event);
|
|
674
|
+
break;
|
|
675
|
+
case "reasoning.delta":
|
|
676
|
+
handleReasoningDelta(state, event);
|
|
677
|
+
break;
|
|
560
678
|
case "reasoning.completed":
|
|
561
679
|
handleReasoningCompleted(state, event);
|
|
562
680
|
break;
|
|
681
|
+
case "tool_call.started":
|
|
682
|
+
handleToolCallStarted(state, event);
|
|
683
|
+
break;
|
|
684
|
+
case "tool_call.delta":
|
|
685
|
+
handleToolCallDelta(state, event);
|
|
686
|
+
break;
|
|
563
687
|
case "tool_call.completed":
|
|
564
688
|
handleToolCallCompleted(state, event);
|
|
565
689
|
break;
|
|
@@ -569,20 +693,10 @@ function aggregateEvent(state, event) {
|
|
|
569
693
|
}
|
|
570
694
|
}
|
|
571
695
|
function finalizeAggregation(state) {
|
|
572
|
-
if (state.
|
|
696
|
+
if (!state.started) throw streamProtocolError("Stream must start with response.started event");
|
|
697
|
+
if (!state.completed || state.lastEventType !== "response.completed") throw streamProtocolError("Stream must end with response.completed event to produce a valid AIResponse");
|
|
573
698
|
return buildResponse(state);
|
|
574
699
|
}
|
|
575
|
-
function mergeAuxiliary$1(base, patch) {
|
|
576
|
-
const merged = {
|
|
577
|
-
...base,
|
|
578
|
-
...patch
|
|
579
|
-
};
|
|
580
|
-
if (base.providerMetadata || patch.providerMetadata) merged.providerMetadata = {
|
|
581
|
-
...base.providerMetadata,
|
|
582
|
-
...patch.providerMetadata
|
|
583
|
-
};
|
|
584
|
-
return merged;
|
|
585
|
-
}
|
|
586
700
|
function pushWarnings(state, warnings) {
|
|
587
701
|
for (const warning of warnings) if (!state.warningSet.has(warning)) {
|
|
588
702
|
state.warningSet.add(warning);
|
|
@@ -592,6 +706,16 @@ function pushWarnings(state, warnings) {
|
|
|
592
706
|
function pushMessageText(state, item) {
|
|
593
707
|
for (const block of item.content) if (block.type === "text") state.textParts.push(block.text);
|
|
594
708
|
}
|
|
709
|
+
function validateEventEnvelope(state, event) {
|
|
710
|
+
if (state.completed) throw streamProtocolError("response.completed must be the final stream event");
|
|
711
|
+
if (!state.started && event.type !== "response.started") throw streamProtocolError("Stream must start with response.started event");
|
|
712
|
+
if (state.responseId !== void 0 && event.responseId !== state.responseId) throw streamProtocolError("All stream events must use the same responseId");
|
|
713
|
+
if (state.nextSequence !== void 0 && event.sequence !== state.nextSequence) throw streamProtocolError(`Expected event sequence ${state.nextSequence}, received ${event.sequence}`);
|
|
714
|
+
state.nextSequence = event.sequence + 1;
|
|
715
|
+
}
|
|
716
|
+
function streamProtocolError(message) {
|
|
717
|
+
return new AIStreamError(message, "STREAM_PROTOCOL_ERROR");
|
|
718
|
+
}
|
|
595
719
|
//#endregion
|
|
596
720
|
//#region src/core/collect-stream.ts
|
|
597
721
|
async function collectStream(stream) {
|
|
@@ -937,7 +1061,7 @@ var AdapterBase = class {
|
|
|
937
1061
|
responseId: request.requestId,
|
|
938
1062
|
backend: {
|
|
939
1063
|
kind: this.kind,
|
|
940
|
-
isSynthetic:
|
|
1064
|
+
isSynthetic: this.capabilities.textStreaming === "synthetic"
|
|
941
1065
|
}
|
|
942
1066
|
});
|
|
943
1067
|
yield factory.responseStarted(request.model);
|
|
@@ -945,12 +1069,25 @@ var AdapterBase = class {
|
|
|
945
1069
|
const providerRequest = await this.buildRequest(request);
|
|
946
1070
|
yield* this.runStream(providerRequest, factory, request);
|
|
947
1071
|
} catch (err) {
|
|
948
|
-
if (err instanceof AIRequestError || err instanceof
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1072
|
+
if (err instanceof AIRequestError || err instanceof AIProviderError || err instanceof AIStreamError) throw err;
|
|
1073
|
+
if (err instanceof AIMappingError) {
|
|
1074
|
+
yield factory.responseWarning(err.message, "MAPPING_ERROR");
|
|
1075
|
+
const errorResp = this.buildResponse(request, {
|
|
1076
|
+
output: [],
|
|
1077
|
+
replay: []
|
|
1078
|
+
}, factory);
|
|
1079
|
+
yield factory.responseCompleted({
|
|
1080
|
+
replay: errorResp.replay,
|
|
1081
|
+
stopReason: errorResp.stopReason,
|
|
1082
|
+
trace: errorResp.backend,
|
|
1083
|
+
usage: errorResp.usage,
|
|
1084
|
+
billing: errorResp.billing,
|
|
1085
|
+
auxiliary: errorResp.auxiliary,
|
|
1086
|
+
warnings: errorResp.warnings
|
|
1087
|
+
});
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
throw err;
|
|
954
1091
|
}
|
|
955
1092
|
}
|
|
956
1093
|
/**
|
|
@@ -976,7 +1113,7 @@ var AdapterBase = class {
|
|
|
976
1113
|
requestId: request.requestId,
|
|
977
1114
|
rawResponseId: result.rawResponseId,
|
|
978
1115
|
adapter: this.kind,
|
|
979
|
-
isSyntheticStream:
|
|
1116
|
+
isSyntheticStream: this.capabilities.textStreaming === "synthetic",
|
|
980
1117
|
metadataSources: result.metadataSources,
|
|
981
1118
|
warnings
|
|
982
1119
|
}
|
|
@@ -990,18 +1127,6 @@ var AdapterBase = class {
|
|
|
990
1127
|
return new AdapterAuxiliaryState(request);
|
|
991
1128
|
}
|
|
992
1129
|
};
|
|
993
|
-
function mergeAuxiliary(base, patch) {
|
|
994
|
-
if (!base && !patch) return void 0;
|
|
995
|
-
const merged = {
|
|
996
|
-
...base,
|
|
997
|
-
...patch
|
|
998
|
-
};
|
|
999
|
-
if (base?.providerMetadata || patch?.providerMetadata) merged.providerMetadata = {
|
|
1000
|
-
...base?.providerMetadata,
|
|
1001
|
-
...patch?.providerMetadata
|
|
1002
|
-
};
|
|
1003
|
-
return merged;
|
|
1004
|
-
}
|
|
1005
1130
|
function mergeWarnings(...groups) {
|
|
1006
1131
|
const merged = [];
|
|
1007
1132
|
for (const group of groups) {
|
|
@@ -1011,6 +1136,91 @@ function mergeWarnings(...groups) {
|
|
|
1011
1136
|
return merged.length > 0 ? merged : void 0;
|
|
1012
1137
|
}
|
|
1013
1138
|
//#endregion
|
|
1139
|
+
//#region src/helpers/adapter-security.ts
|
|
1140
|
+
/**
|
|
1141
|
+
* Adapter 边界安全辅助
|
|
1142
|
+
*
|
|
1143
|
+
* - opaque replay 入站 envelope(大小 / 深度)
|
|
1144
|
+
* - provider HTTP 错误 body 出站脱敏
|
|
1145
|
+
*/
|
|
1146
|
+
const MAX_OPAQUE_PAYLOAD_BYTES = 65536;
|
|
1147
|
+
const MAX_OPAQUE_JSON_DEPTH = 8;
|
|
1148
|
+
const PROVIDER_ERROR_MESSAGE_MAX_LEN = 500;
|
|
1149
|
+
const PROVIDER_ERROR_RAW_BODY_THRESHOLD = 200;
|
|
1150
|
+
/** 测量 JSON 值嵌套深度(对象/数组);循环引用按已访问节点深度计。 */
|
|
1151
|
+
function measureJsonDepth(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1152
|
+
if (value === null || typeof value !== "object") return 0;
|
|
1153
|
+
if (seen.has(value)) return 0;
|
|
1154
|
+
seen.add(value);
|
|
1155
|
+
let maxChild = 0;
|
|
1156
|
+
if (Array.isArray(value)) for (const item of value) maxChild = Math.max(maxChild, measureJsonDepth(item, seen));
|
|
1157
|
+
else for (const key of Object.keys(value)) maxChild = Math.max(maxChild, measureJsonDepth(value[key], seen));
|
|
1158
|
+
return 1 + maxChild;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Opaque replay 通用 envelope:必须是 object、体积 ≤ 64KB、深度 ≤ 8。
|
|
1162
|
+
* 不校验 adapter 专用字段形状。
|
|
1163
|
+
*/
|
|
1164
|
+
function validateOpaqueReplayEnvelope(payload) {
|
|
1165
|
+
if (typeof payload !== "object" || payload === null) return {
|
|
1166
|
+
ok: false,
|
|
1167
|
+
reason: "payload must be an object"
|
|
1168
|
+
};
|
|
1169
|
+
let raw;
|
|
1170
|
+
try {
|
|
1171
|
+
raw = JSON.stringify(payload);
|
|
1172
|
+
} catch {
|
|
1173
|
+
return {
|
|
1174
|
+
ok: false,
|
|
1175
|
+
reason: "payload is not JSON-serializable"
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
if (raw === void 0) return {
|
|
1179
|
+
ok: false,
|
|
1180
|
+
reason: "payload is not JSON-serializable"
|
|
1181
|
+
};
|
|
1182
|
+
if (raw.length > 65536) return {
|
|
1183
|
+
ok: false,
|
|
1184
|
+
reason: `opaque payload exceeds max size (${raw.length} > ${MAX_OPAQUE_PAYLOAD_BYTES})`
|
|
1185
|
+
};
|
|
1186
|
+
const depth = measureJsonDepth(payload);
|
|
1187
|
+
if (depth > 8) return {
|
|
1188
|
+
ok: false,
|
|
1189
|
+
reason: `opaque payload nesting depth (${depth}) exceeds max (8)`
|
|
1190
|
+
};
|
|
1191
|
+
return { ok: true };
|
|
1192
|
+
}
|
|
1193
|
+
/** envelope 失败时抛 AIRequestError。 */
|
|
1194
|
+
function assertOpaqueReplayEnvelope(payload) {
|
|
1195
|
+
const result = validateOpaqueReplayEnvelope(payload);
|
|
1196
|
+
if (!result.ok) throw new AIRequestError(`Invalid opaque replay payload: ${result.reason}`, "INVALID_OPAQUE_REPLAY");
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* 从 provider HTTP 错误 body 提取可对外暴露的短消息,避免泄漏 HTML / 内部路径等。
|
|
1200
|
+
*/
|
|
1201
|
+
function extractProviderErrorMessage(body, status) {
|
|
1202
|
+
if (!body) return `HTTP ${status}`;
|
|
1203
|
+
try {
|
|
1204
|
+
const parsed = JSON.parse(body);
|
|
1205
|
+
if (parsed && typeof parsed === "object") {
|
|
1206
|
+
const record = parsed;
|
|
1207
|
+
const errorField = record.error;
|
|
1208
|
+
let msg;
|
|
1209
|
+
if (errorField && typeof errorField === "object" && errorField !== null) msg = errorField.message;
|
|
1210
|
+
if (typeof msg !== "string") msg = typeof errorField === "string" ? errorField : record.message;
|
|
1211
|
+
if (typeof msg === "string" && msg.length > 0) return msg.slice(0, 500);
|
|
1212
|
+
}
|
|
1213
|
+
} catch {}
|
|
1214
|
+
const trimmed = body.trimStart();
|
|
1215
|
+
if (trimmed.startsWith("<!") || trimmed.startsWith("<html") || body.length > 200) return `HTTP ${status}. Body omitted (${body.length} bytes)`;
|
|
1216
|
+
return body.slice(0, 500);
|
|
1217
|
+
}
|
|
1218
|
+
/** 统一构造脱敏后的 AIProviderError。 */
|
|
1219
|
+
function providerHttpError(status, body) {
|
|
1220
|
+
const safe = extractProviderErrorMessage(body, status);
|
|
1221
|
+
return new AIProviderError(`Provider returned ${status}: ${safe}`, "PROVIDER_ERROR", status, safe);
|
|
1222
|
+
}
|
|
1223
|
+
//#endregion
|
|
1014
1224
|
//#region src/helpers/usage-mapping.ts
|
|
1015
1225
|
function num(value) {
|
|
1016
1226
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
@@ -1020,16 +1230,6 @@ function record(obj) {
|
|
|
1020
1230
|
for (const [key, value] of Object.entries(obj)) if (value !== void 0) out[key] = value;
|
|
1021
1231
|
return out;
|
|
1022
1232
|
}
|
|
1023
|
-
function billableFromOpenAIStyle(inputTokens, outputTokens, cachedInputTokens, reasoningTokens) {
|
|
1024
|
-
let billableInputTokens;
|
|
1025
|
-
if (inputTokens !== void 0) billableInputTokens = cachedInputTokens !== void 0 ? Math.max(0, inputTokens - cachedInputTokens) : inputTokens;
|
|
1026
|
-
let billableOutputTokens;
|
|
1027
|
-
if (outputTokens !== void 0) billableOutputTokens = reasoningTokens !== void 0 ? Math.max(0, outputTokens - reasoningTokens) : outputTokens;
|
|
1028
|
-
return record({
|
|
1029
|
-
billableInputTokens,
|
|
1030
|
-
billableOutputTokens
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
1233
|
/** OpenAI Chat Completions `usage` */
|
|
1034
1234
|
function usageFromChatCompletions(raw) {
|
|
1035
1235
|
const inputTokens = num(raw.prompt_tokens);
|
|
@@ -1041,8 +1241,7 @@ function usageFromChatCompletions(raw) {
|
|
|
1041
1241
|
outputTokens,
|
|
1042
1242
|
totalTokens: num(raw.total_tokens) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
|
|
1043
1243
|
cachedInputTokens,
|
|
1044
|
-
reasoningTokens
|
|
1045
|
-
...billableFromOpenAIStyle(inputTokens, outputTokens, cachedInputTokens, reasoningTokens)
|
|
1244
|
+
reasoningTokens
|
|
1046
1245
|
});
|
|
1047
1246
|
}
|
|
1048
1247
|
/** OpenAI Responses API `usage` */
|
|
@@ -1056,8 +1255,7 @@ function usageFromOpenAIResponses(raw) {
|
|
|
1056
1255
|
outputTokens,
|
|
1057
1256
|
totalTokens: num(raw.total_tokens) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
|
|
1058
1257
|
cachedInputTokens,
|
|
1059
|
-
reasoningTokens
|
|
1060
|
-
...billableFromOpenAIStyle(inputTokens, outputTokens, cachedInputTokens, reasoningTokens)
|
|
1258
|
+
reasoningTokens
|
|
1061
1259
|
});
|
|
1062
1260
|
}
|
|
1063
1261
|
/** Anthropic Messages `usage`(message_start / message_delta) */
|
|
@@ -1072,29 +1270,22 @@ function usageFromAnthropicMessages(raw) {
|
|
|
1072
1270
|
cachedInputTokens
|
|
1073
1271
|
].filter((n) => n !== void 0);
|
|
1074
1272
|
const inputTokens = inputParts.length > 0 ? inputParts.reduce((sum, n) => sum + n, 0) : void 0;
|
|
1075
|
-
const totalTokens = inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0;
|
|
1076
|
-
let billableInputTokens;
|
|
1077
|
-
if (uncachedInputTokens !== void 0 || cacheWriteInputTokens !== void 0) billableInputTokens = (uncachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
|
|
1078
1273
|
return record({
|
|
1079
1274
|
inputTokens,
|
|
1080
1275
|
outputTokens,
|
|
1081
|
-
totalTokens,
|
|
1276
|
+
totalTokens: inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0,
|
|
1082
1277
|
cachedInputTokens,
|
|
1083
|
-
cacheWriteInputTokens
|
|
1084
|
-
billableInputTokens,
|
|
1085
|
-
billableOutputTokens: outputTokens
|
|
1278
|
+
cacheWriteInputTokens
|
|
1086
1279
|
});
|
|
1087
1280
|
}
|
|
1088
|
-
/** Ollama 流式 chunk
|
|
1281
|
+
/** Ollama 流式 chunk */
|
|
1089
1282
|
function usageFromOllama(raw) {
|
|
1090
1283
|
const inputTokens = num(raw.prompt_eval_count);
|
|
1091
1284
|
const outputTokens = num(raw.eval_count);
|
|
1092
1285
|
return record({
|
|
1093
1286
|
inputTokens,
|
|
1094
1287
|
outputTokens,
|
|
1095
|
-
totalTokens: inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
|
|
1096
|
-
billableInputTokens: inputTokens,
|
|
1097
|
-
billableOutputTokens: outputTokens
|
|
1288
|
+
totalTokens: inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
|
|
1098
1289
|
});
|
|
1099
1290
|
}
|
|
1100
1291
|
//#endregion
|
|
@@ -1109,43 +1300,55 @@ function usageFromOllama(raw) {
|
|
|
1109
1300
|
* - 未完成的行保留在 rest 中,等待下次 chunk 补全
|
|
1110
1301
|
* - 支持跨 chunk 的 event 分片
|
|
1111
1302
|
*/
|
|
1112
|
-
function parseSSEEvents(chunk) {
|
|
1303
|
+
function parseSSEEvents(chunk, options = {}) {
|
|
1113
1304
|
const events = [];
|
|
1114
1305
|
let eventType = "";
|
|
1115
1306
|
let dataLines = [];
|
|
1116
1307
|
let consumedUntil = 0;
|
|
1117
1308
|
let cursor = 0;
|
|
1118
1309
|
let malformedEvents = 0;
|
|
1310
|
+
const emitEvent = (consumedCursor) => {
|
|
1311
|
+
const dataStr = dataLines.join("\n");
|
|
1312
|
+
if (dataStr === "[DONE]") {
|
|
1313
|
+
eventType = "";
|
|
1314
|
+
dataLines = [];
|
|
1315
|
+
consumedUntil = consumedCursor;
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
try {
|
|
1319
|
+
const data = JSON.parse(dataStr);
|
|
1320
|
+
events.push({
|
|
1321
|
+
type: eventType,
|
|
1322
|
+
data
|
|
1323
|
+
});
|
|
1324
|
+
} catch {
|
|
1325
|
+
malformedEvents++;
|
|
1326
|
+
}
|
|
1327
|
+
eventType = "";
|
|
1328
|
+
dataLines = [];
|
|
1329
|
+
consumedUntil = consumedCursor;
|
|
1330
|
+
};
|
|
1331
|
+
const consumeLine = (line, consumedCursor) => {
|
|
1332
|
+
if (line.startsWith("event: ")) eventType = line.slice(7).trim();
|
|
1333
|
+
else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
|
|
1334
|
+
else if (line === "" && eventType && dataLines.length > 0) emitEvent(consumedCursor);
|
|
1335
|
+
else if (line === "" && !eventType && dataLines.length === 0) consumedUntil = consumedCursor;
|
|
1336
|
+
};
|
|
1119
1337
|
while (cursor < chunk.length) {
|
|
1120
1338
|
const lineEnd = chunk.indexOf("\n", cursor);
|
|
1121
1339
|
if (lineEnd === -1) break;
|
|
1122
1340
|
let line = chunk.slice(cursor, lineEnd);
|
|
1123
1341
|
cursor = lineEnd + 1;
|
|
1124
1342
|
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
1125
|
-
|
|
1126
|
-
else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
|
|
1127
|
-
else if (line === "" && eventType && dataLines.length > 0) {
|
|
1128
|
-
const dataStr = dataLines.join("\n");
|
|
1129
|
-
if (dataStr === "[DONE]") {
|
|
1130
|
-
eventType = "";
|
|
1131
|
-
dataLines = [];
|
|
1132
|
-
consumedUntil = cursor;
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1135
|
-
try {
|
|
1136
|
-
const data = JSON.parse(dataStr);
|
|
1137
|
-
events.push({
|
|
1138
|
-
type: eventType,
|
|
1139
|
-
data
|
|
1140
|
-
});
|
|
1141
|
-
} catch {
|
|
1142
|
-
malformedEvents++;
|
|
1143
|
-
}
|
|
1144
|
-
eventType = "";
|
|
1145
|
-
dataLines = [];
|
|
1146
|
-
consumedUntil = cursor;
|
|
1147
|
-
} else if (line === "" && !eventType && dataLines.length === 0) consumedUntil = cursor;
|
|
1343
|
+
consumeLine(line, cursor);
|
|
1148
1344
|
}
|
|
1345
|
+
if (options.allowEOF && cursor < chunk.length) {
|
|
1346
|
+
let line = chunk.slice(cursor);
|
|
1347
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
1348
|
+
consumeLine(line, chunk.length);
|
|
1349
|
+
cursor = chunk.length;
|
|
1350
|
+
}
|
|
1351
|
+
if (options.allowEOF && eventType && dataLines.length > 0) emitEvent(chunk.length);
|
|
1149
1352
|
return {
|
|
1150
1353
|
events,
|
|
1151
1354
|
rest: chunk.slice(consumedUntil),
|
|
@@ -1153,70 +1356,287 @@ function parseSSEEvents(chunk) {
|
|
|
1153
1356
|
};
|
|
1154
1357
|
}
|
|
1155
1358
|
//#endregion
|
|
1156
|
-
//#region src/
|
|
1359
|
+
//#region src/helpers/synthetic-stream.ts
|
|
1157
1360
|
/**
|
|
1158
|
-
*
|
|
1361
|
+
* 模拟流式 (Synthetic Streaming)
|
|
1159
1362
|
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
* 2. runStream — 调用 API、解析 SSE、发射 canonical 事件
|
|
1363
|
+
* 将一组已解析的 canonical OutputItem 包装为规范事件流。
|
|
1364
|
+
* 适用于非原生流式后端:adapter 拿到完整响应后,调用此函数
|
|
1365
|
+
* 即可产出一致的事件序列,无需自己逐事件组装。
|
|
1164
1366
|
*
|
|
1165
|
-
*
|
|
1367
|
+
* 约束:
|
|
1368
|
+
* - 每个 item 只发一块完整 delta(不模拟逐 token)
|
|
1369
|
+
* - 保持 item 边界
|
|
1370
|
+
* - 保持后端原始顺序
|
|
1371
|
+
* - 不发明 reasoning
|
|
1372
|
+
* - 不改写工具参数
|
|
1166
1373
|
*/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1374
|
+
/**
|
|
1375
|
+
* 将已解析的 output items 包装为完整规范事件流。
|
|
1376
|
+
*
|
|
1377
|
+
* 用法示例(在 adapter 的 runStream 中):
|
|
1378
|
+
* ```ts
|
|
1379
|
+
* const result = parseNonStreamingResponse(data);
|
|
1380
|
+
* yield* syntheticStream({
|
|
1381
|
+
* model: request.model,
|
|
1382
|
+
* responseId: request.requestId,
|
|
1383
|
+
* backend: { kind: "chat-completions" },
|
|
1384
|
+
* output: result.output,
|
|
1385
|
+
* stopReason: result.stopReason,
|
|
1386
|
+
* usage: result.usage,
|
|
1387
|
+
* });
|
|
1388
|
+
* ```
|
|
1389
|
+
*/
|
|
1390
|
+
async function* syntheticStream(options) {
|
|
1391
|
+
const { model, responseId, backend, output, replay, stopReason, usage, billing, providerMetadata, rawResponseId, warnings: extraWarnings } = options;
|
|
1392
|
+
const factory = createEventFactory({
|
|
1393
|
+
responseId,
|
|
1394
|
+
backend: {
|
|
1395
|
+
kind: backend.kind,
|
|
1396
|
+
isSynthetic: true
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
yield factory.responseStarted(model);
|
|
1400
|
+
for (const item of output) yield* emitItemEvents(item, factory);
|
|
1401
|
+
if (usage || billing) yield factory.responseAuxiliary({
|
|
1402
|
+
usage,
|
|
1403
|
+
billing
|
|
1404
|
+
});
|
|
1405
|
+
const finalReplay = replay ?? replayFromOutput(output);
|
|
1406
|
+
const allWarnings = [];
|
|
1407
|
+
allWarnings.push("Response is synthetically streamed; delta granularity may differ from native streaming");
|
|
1408
|
+
if (extraWarnings) allWarnings.push(...extraWarnings);
|
|
1409
|
+
yield factory.responseCompleted({
|
|
1410
|
+
replay: finalReplay,
|
|
1411
|
+
stopReason,
|
|
1412
|
+
usage,
|
|
1413
|
+
billing,
|
|
1414
|
+
auxiliary: providerMetadata ? { providerMetadata } : void 0,
|
|
1415
|
+
opaqueOutput: output.filter((item) => item.type === "opaque"),
|
|
1416
|
+
warnings: allWarnings.length > 0 ? allWarnings : void 0,
|
|
1417
|
+
trace: {
|
|
1418
|
+
requestId: responseId,
|
|
1419
|
+
rawResponseId,
|
|
1420
|
+
adapter: backend.kind,
|
|
1421
|
+
isSyntheticStream: true,
|
|
1422
|
+
warnings: allWarnings.length > 0 ? allWarnings : void 0
|
|
1423
|
+
}
|
|
1179
1424
|
});
|
|
1180
1425
|
}
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1426
|
+
function* emitItemEvents(item, factory) {
|
|
1427
|
+
switch (item.type) {
|
|
1428
|
+
case "message":
|
|
1429
|
+
yield* emitMessageEvents(item, factory);
|
|
1430
|
+
break;
|
|
1431
|
+
case "reasoning":
|
|
1432
|
+
yield* emitReasoningEvents(item, factory);
|
|
1433
|
+
break;
|
|
1434
|
+
case "tool_call":
|
|
1435
|
+
yield* emitToolCallEvents(item, factory);
|
|
1436
|
+
break;
|
|
1437
|
+
case "opaque": break;
|
|
1438
|
+
}
|
|
1183
1439
|
}
|
|
1184
|
-
function
|
|
1185
|
-
|
|
1440
|
+
function* emitMessageEvents(item, factory) {
|
|
1441
|
+
const id = item.id ?? `syn-msg-${crypto.randomUUID()}`;
|
|
1442
|
+
yield factory.messageStarted(id);
|
|
1443
|
+
for (const block of item.content) yield factory.messageDelta(id, block);
|
|
1444
|
+
yield factory.messageCompleted(id);
|
|
1186
1445
|
}
|
|
1187
|
-
function
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
malformedEvents: result.malformedEvents
|
|
1193
|
-
};
|
|
1446
|
+
function* emitReasoningEvents(item, factory) {
|
|
1447
|
+
const id = item.id ?? `syn-reason-${crypto.randomUUID()}`;
|
|
1448
|
+
yield factory.reasoningStarted(id, item.visibility);
|
|
1449
|
+
for (const block of item.content) yield factory.reasoningDelta(id, block);
|
|
1450
|
+
yield factory.reasoningCompleted(id);
|
|
1194
1451
|
}
|
|
1195
|
-
function
|
|
1196
|
-
|
|
1452
|
+
function* emitToolCallEvents(item, factory) {
|
|
1453
|
+
yield factory.toolCallStarted(item.id, item.name);
|
|
1454
|
+
if (item.argumentsText) yield factory.toolCallDelta(item.id, { argumentsText: item.argumentsText });
|
|
1455
|
+
yield factory.toolCallCompleted(item.id);
|
|
1197
1456
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1457
|
+
//#endregion
|
|
1458
|
+
//#region src/helpers/incremental-stream-parser.ts
|
|
1459
|
+
var IncrementalStreamParser = class {
|
|
1460
|
+
split;
|
|
1461
|
+
parse;
|
|
1462
|
+
buffer = "";
|
|
1463
|
+
decoder = new TextDecoder();
|
|
1464
|
+
constructor(split, parse) {
|
|
1465
|
+
this.split = split;
|
|
1466
|
+
this.parse = parse;
|
|
1467
|
+
}
|
|
1468
|
+
feed(value) {
|
|
1469
|
+
this.buffer += this.decoder.decode(value, { stream: true });
|
|
1470
|
+
return this.consume(false);
|
|
1471
|
+
}
|
|
1472
|
+
flush() {
|
|
1473
|
+
this.buffer += this.decoder.decode();
|
|
1474
|
+
return this.consume(true);
|
|
1475
|
+
}
|
|
1476
|
+
getRemaining() {
|
|
1477
|
+
return this.buffer;
|
|
1478
|
+
}
|
|
1479
|
+
consume(allowEOF) {
|
|
1480
|
+
const split = this.split(this.buffer, allowEOF);
|
|
1481
|
+
this.buffer = split.rest;
|
|
1482
|
+
const items = [];
|
|
1483
|
+
let malformed = 0;
|
|
1484
|
+
for (const rawItem of split.items) {
|
|
1485
|
+
const result = this.parse(rawItem);
|
|
1486
|
+
if (result.status === "parsed") items.push(result.value);
|
|
1487
|
+
else if (result.status === "malformed") malformed++;
|
|
1488
|
+
}
|
|
1489
|
+
return {
|
|
1490
|
+
items,
|
|
1491
|
+
malformed
|
|
1492
|
+
};
|
|
1203
1493
|
}
|
|
1204
|
-
}
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1494
|
+
};
|
|
1495
|
+
function splitLines(buffer, allowEOF) {
|
|
1496
|
+
const items = [];
|
|
1497
|
+
let cursor = 0;
|
|
1498
|
+
while (true) {
|
|
1499
|
+
const lineEnd = buffer.indexOf("\n", cursor);
|
|
1500
|
+
if (lineEnd === -1) break;
|
|
1501
|
+
items.push(buffer.slice(cursor, lineEnd));
|
|
1502
|
+
cursor = lineEnd + 1;
|
|
1503
|
+
}
|
|
1504
|
+
if (allowEOF && cursor < buffer.length) {
|
|
1505
|
+
items.push(buffer.slice(cursor));
|
|
1506
|
+
cursor = buffer.length;
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
items,
|
|
1510
|
+
rest: buffer.slice(cursor)
|
|
1213
1511
|
};
|
|
1214
|
-
throw new AIRequestError(`responses does not support content block type "${b.type}" in canonical mapping`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1215
1512
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1513
|
+
function splitSSEFrames(buffer, allowEOF) {
|
|
1514
|
+
const normalized = buffer.replaceAll("\r\n", "\n");
|
|
1515
|
+
const items = [];
|
|
1516
|
+
let cursor = 0;
|
|
1517
|
+
while (true) {
|
|
1518
|
+
const frameEnd = normalized.indexOf("\n\n", cursor);
|
|
1519
|
+
if (frameEnd === -1) break;
|
|
1520
|
+
items.push(normalized.slice(cursor, frameEnd));
|
|
1521
|
+
cursor = frameEnd + 2;
|
|
1522
|
+
}
|
|
1523
|
+
if (allowEOF && cursor < normalized.length) {
|
|
1524
|
+
items.push(normalized.slice(cursor));
|
|
1525
|
+
cursor = normalized.length;
|
|
1526
|
+
}
|
|
1527
|
+
return {
|
|
1528
|
+
items,
|
|
1529
|
+
rest: normalized.slice(cursor)
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
//#endregion
|
|
1533
|
+
//#region src/helpers/request-mapper.ts
|
|
1534
|
+
var NormalizedRequestMapper = class {
|
|
1535
|
+
profile;
|
|
1536
|
+
constructor(profile) {
|
|
1537
|
+
this.profile = profile;
|
|
1538
|
+
}
|
|
1539
|
+
mapInstructions(instructions) {
|
|
1540
|
+
return typeof instructions === "string" ? instructions : contentBlocksToText(this.ensureTextBlocks(instructions, "instructions"));
|
|
1541
|
+
}
|
|
1542
|
+
ensureTextBlocks(blocks, field) {
|
|
1543
|
+
return this.ensureBlocks(blocks, field, this.profile.supportedBlockTypes, "only text/json blocks are supported");
|
|
1544
|
+
}
|
|
1545
|
+
ensureReasoningBlocks(blocks, field) {
|
|
1546
|
+
return this.ensureBlocks(blocks, field, this.profile.reasoningBlockTypes, "reasoning only supports text blocks");
|
|
1547
|
+
}
|
|
1548
|
+
assertToolResultOutcome(outcome) {
|
|
1549
|
+
if (this.profile.capabilities.toolResultOutcomes.includes(outcome)) return;
|
|
1550
|
+
const outcomes = this.profile.capabilities.toolResultOutcomes;
|
|
1551
|
+
const supported = outcomes.map((value) => `"${value}"`).join(" and ");
|
|
1552
|
+
const verb = outcomes.length > 1 ? "are" : "is";
|
|
1553
|
+
throw new AIRequestError(`${this.profile.kind} does not preserve tool_result outcome "${outcome}"; only ${supported} ${verb} supported`, "UNSUPPORTED_TOOL_RESULT_OUTCOME");
|
|
1554
|
+
}
|
|
1555
|
+
rollbackTrailingAssistantMessages(messages) {
|
|
1556
|
+
while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") messages.pop();
|
|
1557
|
+
}
|
|
1558
|
+
ensureBlocks(blocks, field, supportedTypes, description) {
|
|
1559
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1560
|
+
const block = blocks[i];
|
|
1561
|
+
if (block && !supportedTypes.includes(block.type)) throw new AIRequestError(`${this.profile.kind} does not support ${field}[${i}] of type "${block.type}"; ${description}`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1562
|
+
}
|
|
1563
|
+
return blocks;
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
//#endregion
|
|
1567
|
+
//#region src/adapters/responses.ts
|
|
1568
|
+
/**
|
|
1569
|
+
* Responses Adapter
|
|
1570
|
+
*
|
|
1571
|
+
* 接入 OpenAI Responses API (responses 端点)。
|
|
1572
|
+
* 职责分层:
|
|
1573
|
+
* 1. buildRequest — 将 NormalizedRequest 转换为 Responses API 请求
|
|
1574
|
+
* 2. runStream — 调用 API、解析 SSE、发射 canonical 事件
|
|
1575
|
+
*
|
|
1576
|
+
* 支持消息流 / reasoning 流 / tool_call 流及高保真 replay。
|
|
1577
|
+
*/
|
|
1578
|
+
const profile$3 = {
|
|
1579
|
+
kind: "responses",
|
|
1580
|
+
instructionsMode: "instructions_field",
|
|
1581
|
+
supportedBlockTypes: ["text", "json"],
|
|
1582
|
+
reasoningBlockTypes: ["text"],
|
|
1583
|
+
capabilities: {
|
|
1584
|
+
textStreaming: "native",
|
|
1585
|
+
reasoningStreaming: "native",
|
|
1586
|
+
toolCallStreaming: "native",
|
|
1587
|
+
replay: "opaque",
|
|
1588
|
+
usage: "final",
|
|
1589
|
+
toolResultOutcomes: ["success"]
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
const mapper$3 = new NormalizedRequestMapper(profile$3);
|
|
1593
|
+
/** 已处理或可安全忽略的 Responses SSE 类型(未知类型会 warning 一次)。 */
|
|
1594
|
+
const KNOWN_RESPONSES_SSE_TYPES = /* @__PURE__ */ new Set([
|
|
1595
|
+
"response.output_item.added",
|
|
1596
|
+
"response.output_item.done",
|
|
1597
|
+
"response.output_text.delta",
|
|
1598
|
+
"response.output_text.done",
|
|
1599
|
+
"response.reasoning.delta",
|
|
1600
|
+
"response.reasoning.done",
|
|
1601
|
+
"response.tool_call.delta",
|
|
1602
|
+
"response.tool_call.done",
|
|
1603
|
+
"response.function_call_arguments.delta",
|
|
1604
|
+
"response.function_call_arguments.done",
|
|
1605
|
+
"response.content_part.added",
|
|
1606
|
+
"response.content_part.done",
|
|
1607
|
+
"response.refusal.delta",
|
|
1608
|
+
"response.refusal.done",
|
|
1609
|
+
"response.in_progress",
|
|
1610
|
+
"response.created",
|
|
1611
|
+
"response.completed",
|
|
1612
|
+
"response.failed",
|
|
1613
|
+
"response.incomplete",
|
|
1614
|
+
"error"
|
|
1615
|
+
]);
|
|
1616
|
+
function isReplayCanonicalInput(item) {
|
|
1617
|
+
return item.type === "message" && item.role === "assistant" || item.type === "reasoning" || item.type === "function_call";
|
|
1618
|
+
}
|
|
1619
|
+
function hasReplayCanonicalInput(input) {
|
|
1620
|
+
return input.some(isReplayCanonicalInput);
|
|
1621
|
+
}
|
|
1622
|
+
function extractFailureMessage(response) {
|
|
1623
|
+
return response.error?.message ?? response.failure?.message ?? "unknown";
|
|
1624
|
+
}
|
|
1625
|
+
function canonicalToResponsesBlock(b) {
|
|
1626
|
+
if (b.type === "text") return {
|
|
1627
|
+
type: "text",
|
|
1628
|
+
text: b.text
|
|
1629
|
+
};
|
|
1630
|
+
if (b.type === "json") return {
|
|
1631
|
+
type: "text",
|
|
1632
|
+
text: JSON.stringify(b.json)
|
|
1633
|
+
};
|
|
1634
|
+
throw new AIRequestError(`responses does not support content block type "${b.type}" in canonical mapping`, "UNSUPPORTED_CONTENT_BLOCK");
|
|
1635
|
+
}
|
|
1636
|
+
var ResponsesAdapter = class extends AdapterBase {
|
|
1637
|
+
kind = "responses";
|
|
1638
|
+
capabilities = profile$3.capabilities;
|
|
1639
|
+
apiKey;
|
|
1220
1640
|
baseUrl;
|
|
1221
1641
|
fetchFn;
|
|
1222
1642
|
constructor(options) {
|
|
@@ -1230,7 +1650,7 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1230
1650
|
for (const item of request.input) switch (item.type) {
|
|
1231
1651
|
case "message":
|
|
1232
1652
|
if (item.role === "assistant") {
|
|
1233
|
-
const blocks =
|
|
1653
|
+
const blocks = mapper$3.ensureTextBlocks(item.content, `assistant message (${item.role}) content`).map(canonicalToResponsesBlock);
|
|
1234
1654
|
input.push({
|
|
1235
1655
|
type: "message",
|
|
1236
1656
|
role: item.role,
|
|
@@ -1239,11 +1659,11 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1239
1659
|
} else input.push({
|
|
1240
1660
|
type: "message",
|
|
1241
1661
|
role: item.role,
|
|
1242
|
-
content: contentBlocksToText(
|
|
1662
|
+
content: contentBlocksToText(mapper$3.ensureTextBlocks(item.content, `input message (${item.role}) content`))
|
|
1243
1663
|
});
|
|
1244
1664
|
break;
|
|
1245
1665
|
case "reasoning": {
|
|
1246
|
-
const blocks =
|
|
1666
|
+
const blocks = mapper$3.ensureReasoningBlocks(item.content, "reasoning content").map((b) => ({
|
|
1247
1667
|
type: "reasoning",
|
|
1248
1668
|
text: b.text
|
|
1249
1669
|
}));
|
|
@@ -1262,8 +1682,8 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1262
1682
|
});
|
|
1263
1683
|
break;
|
|
1264
1684
|
case "tool_result": {
|
|
1265
|
-
|
|
1266
|
-
const output =
|
|
1685
|
+
mapper$3.assertToolResultOutcome(item.outcome);
|
|
1686
|
+
const output = mapper$3.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
|
|
1267
1687
|
input.push({
|
|
1268
1688
|
type: "function_call_output",
|
|
1269
1689
|
call_id: item.callId,
|
|
@@ -1271,25 +1691,26 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1271
1691
|
});
|
|
1272
1692
|
break;
|
|
1273
1693
|
}
|
|
1274
|
-
case "opaque":
|
|
1275
|
-
if (item.source
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
}
|
|
1694
|
+
case "opaque": {
|
|
1695
|
+
if (item.source !== "responses" || item.purpose !== "replay") break;
|
|
1696
|
+
assertOpaqueReplayEnvelope(item.payload);
|
|
1697
|
+
const payload = item.payload;
|
|
1698
|
+
if ("id" in payload) {
|
|
1699
|
+
if (typeof payload.id !== "string" || payload.id.length === 0 || payload.id.length > 256) throw new AIRequestError("Invalid opaque replay payload: id must be a non-empty string (max 256)", "INVALID_OPAQUE_REPLAY");
|
|
1700
|
+
if (!hasReplayCanonicalInput(input)) input.push({
|
|
1701
|
+
type: "item_reference",
|
|
1702
|
+
id: payload.id
|
|
1703
|
+
});
|
|
1284
1704
|
}
|
|
1285
1705
|
break;
|
|
1706
|
+
}
|
|
1286
1707
|
}
|
|
1287
1708
|
const body = {
|
|
1288
1709
|
model: request.model,
|
|
1289
1710
|
input,
|
|
1290
1711
|
stream: true
|
|
1291
1712
|
};
|
|
1292
|
-
if (request.instructions) body.instructions =
|
|
1713
|
+
if (request.instructions) body.instructions = mapper$3.mapInstructions(request.instructions);
|
|
1293
1714
|
if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
|
|
1294
1715
|
type: "function",
|
|
1295
1716
|
name: t.name,
|
|
@@ -1311,31 +1732,59 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1311
1732
|
}
|
|
1312
1733
|
async *runStream(providerRequest, factory, request) {
|
|
1313
1734
|
const auxiliary = this.createAuxiliaryState(request);
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1735
|
+
let response;
|
|
1736
|
+
try {
|
|
1737
|
+
response = await this.fetchFn(`${this.baseUrl}/responses`, {
|
|
1738
|
+
method: "POST",
|
|
1739
|
+
headers: {
|
|
1740
|
+
"Content-Type": "application/json",
|
|
1741
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1742
|
+
},
|
|
1743
|
+
body: JSON.stringify(providerRequest)
|
|
1744
|
+
});
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
1747
|
+
}
|
|
1322
1748
|
if (!response.ok) {
|
|
1323
|
-
const
|
|
1324
|
-
throw
|
|
1749
|
+
const errorBody = await response.text().catch(() => "");
|
|
1750
|
+
throw providerHttpError(response.status, errorBody);
|
|
1325
1751
|
}
|
|
1326
1752
|
const reader = response.body?.getReader();
|
|
1327
|
-
if (!reader) throw new
|
|
1753
|
+
if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
1754
|
+
const parser = new IncrementalStreamParser(splitSSEFrames, (frame) => {
|
|
1755
|
+
let eventType = "";
|
|
1756
|
+
let dataStr = "";
|
|
1757
|
+
for (const rawLine of frame.split("\n")) {
|
|
1758
|
+
const line = rawLine.trim();
|
|
1759
|
+
if (line.startsWith("event: ")) eventType = line.slice(7).trim();
|
|
1760
|
+
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
1761
|
+
}
|
|
1762
|
+
if (!eventType) return { status: "ignored" };
|
|
1763
|
+
try {
|
|
1764
|
+
const data = JSON.parse(dataStr);
|
|
1765
|
+
return {
|
|
1766
|
+
status: "parsed",
|
|
1767
|
+
value: {
|
|
1768
|
+
type: eventType,
|
|
1769
|
+
data
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
} catch {
|
|
1773
|
+
return { status: "malformed" };
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1328
1776
|
const output = [];
|
|
1329
|
-
|
|
1330
|
-
let buffer = "";
|
|
1777
|
+
let streamDone = false;
|
|
1331
1778
|
let completedResponse;
|
|
1779
|
+
let completedEmitted = false;
|
|
1780
|
+
let unknownEventsWarned = false;
|
|
1781
|
+
const messageItemsWithDelta = /* @__PURE__ */ new Set();
|
|
1332
1782
|
try {
|
|
1333
1783
|
while (true) {
|
|
1334
|
-
const { done, value } = await reader.read()
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const { events,
|
|
1338
|
-
buffer = rest;
|
|
1784
|
+
const { done, value } = await reader.read().catch((err) => {
|
|
1785
|
+
throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
|
|
1786
|
+
});
|
|
1787
|
+
const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
|
|
1339
1788
|
const malformedWarning = emitMalformedStreamWarning(factory, {
|
|
1340
1789
|
count: malformedEvents,
|
|
1341
1790
|
providerLabel: "Responses",
|
|
@@ -1344,7 +1793,8 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1344
1793
|
if (malformedWarning) yield malformedWarning;
|
|
1345
1794
|
for (const sseEvent of events) {
|
|
1346
1795
|
if (sseEvent.type === "error") {
|
|
1347
|
-
|
|
1796
|
+
const data = sseEvent.data;
|
|
1797
|
+
yield factory.responseWarning(data.message ?? "Provider error event", data.code);
|
|
1348
1798
|
continue;
|
|
1349
1799
|
}
|
|
1350
1800
|
if (sseEvent.type === "response.output_item.added") {
|
|
@@ -1363,40 +1813,69 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1363
1813
|
continue;
|
|
1364
1814
|
}
|
|
1365
1815
|
if (sseEvent.type === "response.output_text.delta") {
|
|
1366
|
-
|
|
1816
|
+
const data = sseEvent.data;
|
|
1817
|
+
yield factory.messageDelta(data.item_id, textBlock(data.delta));
|
|
1818
|
+
messageItemsWithDelta.add(data.item_id);
|
|
1367
1819
|
continue;
|
|
1368
1820
|
}
|
|
1369
1821
|
if (sseEvent.type === "response.output_text.done") {
|
|
1370
|
-
|
|
1371
|
-
|
|
1822
|
+
const data = sseEvent.data;
|
|
1823
|
+
if (!messageItemsWithDelta.has(data.item_id) && data.text) yield factory.messageDelta(data.item_id, textBlock(data.text));
|
|
1824
|
+
yield factory.messageCompleted(data.item_id);
|
|
1825
|
+
output.push(messageItem([textBlock(data.text)], { id: data.item_id }));
|
|
1372
1826
|
continue;
|
|
1373
1827
|
}
|
|
1374
1828
|
if (sseEvent.type === "response.reasoning.delta") {
|
|
1375
|
-
|
|
1829
|
+
const data = sseEvent.data;
|
|
1830
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
|
|
1376
1831
|
continue;
|
|
1377
1832
|
}
|
|
1378
1833
|
if (sseEvent.type === "response.reasoning.done") {
|
|
1379
|
-
|
|
1380
|
-
|
|
1834
|
+
const data = sseEvent.data;
|
|
1835
|
+
yield factory.reasoningCompleted(data.item_id);
|
|
1836
|
+
output.push(reasoningItem([textBlock(data.text)], "full", data.item_id));
|
|
1381
1837
|
continue;
|
|
1382
1838
|
}
|
|
1383
1839
|
if (sseEvent.type === "response.tool_call.delta") {
|
|
1384
|
-
|
|
1840
|
+
const data = sseEvent.data;
|
|
1841
|
+
if (data.delta.arguments) yield factory.toolCallDelta(data.item_id, { argumentsText: data.delta.arguments });
|
|
1385
1842
|
continue;
|
|
1386
1843
|
}
|
|
1387
1844
|
if (sseEvent.type === "response.tool_call.done") {
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1845
|
+
const data = sseEvent.data;
|
|
1846
|
+
const tcItem = toolCallItem(data.item_id, data.name ?? "unknown", data.arguments ?? "");
|
|
1847
|
+
yield factory.toolCallCompleted(data.item_id);
|
|
1390
1848
|
output.push(tcItem);
|
|
1391
1849
|
continue;
|
|
1392
1850
|
}
|
|
1393
|
-
if (sseEvent.type === "response.completed"
|
|
1851
|
+
if (sseEvent.type === "response.completed" || sseEvent.type === "response.failed" || sseEvent.type === "response.incomplete") {
|
|
1852
|
+
const data = sseEvent.data;
|
|
1853
|
+
if (completedResponse) {
|
|
1854
|
+
yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
completedResponse = data.response;
|
|
1858
|
+
if (sseEvent.type === "response.failed") yield factory.responseWarning(`Response failed: ${extractFailureMessage(data.response)}`, "PROVIDER_FAILURE");
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (!KNOWN_RESPONSES_SSE_TYPES.has(sseEvent.type) && !unknownEventsWarned) {
|
|
1862
|
+
unknownEventsWarned = true;
|
|
1863
|
+
yield factory.responseWarning(`Responses API sent unknown event type "${sseEvent.type}"; this may indicate an incomplete integration`, "UNKNOWN_PROVIDER_EVENT");
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
if (done) {
|
|
1867
|
+
streamDone = true;
|
|
1868
|
+
break;
|
|
1394
1869
|
}
|
|
1395
1870
|
}
|
|
1396
1871
|
} finally {
|
|
1397
|
-
|
|
1872
|
+
try {
|
|
1873
|
+
if (!streamDone) await reader.cancel().catch(() => void 0);
|
|
1874
|
+
} finally {
|
|
1875
|
+
reader.releaseLock();
|
|
1876
|
+
}
|
|
1398
1877
|
}
|
|
1399
|
-
if (
|
|
1878
|
+
if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Responses SSE frame", "STREAM_ERROR");
|
|
1400
1879
|
let rawResponseId;
|
|
1401
1880
|
if (completedResponse) {
|
|
1402
1881
|
rawResponseId = completedResponse.id;
|
|
@@ -1407,23 +1886,44 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1407
1886
|
const stopReason = completedResponse ? this.inferStopReason(completedResponse) : void 0;
|
|
1408
1887
|
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
1409
1888
|
for (const event of auxiliaryResult.events) yield event;
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1889
|
+
if (!completedEmitted) {
|
|
1890
|
+
completedEmitted = true;
|
|
1891
|
+
const finalResponse = this.buildResponse(request, {
|
|
1892
|
+
output,
|
|
1893
|
+
replay,
|
|
1894
|
+
stopReason,
|
|
1895
|
+
usage: auxiliaryResult.usage,
|
|
1896
|
+
billing: auxiliaryResult.billing,
|
|
1897
|
+
auxiliary: auxiliaryResult.auxiliary,
|
|
1898
|
+
warnings: auxiliaryResult.warnings,
|
|
1899
|
+
metadataSources: auxiliaryResult.metadataSources,
|
|
1900
|
+
rawResponseId
|
|
1901
|
+
}, factory);
|
|
1902
|
+
yield factory.responseCompleted({
|
|
1903
|
+
replay: finalResponse.replay,
|
|
1904
|
+
stopReason: finalResponse.stopReason,
|
|
1905
|
+
trace: finalResponse.backend,
|
|
1906
|
+
usage: finalResponse.usage,
|
|
1907
|
+
billing: finalResponse.billing,
|
|
1908
|
+
auxiliary: finalResponse.auxiliary,
|
|
1909
|
+
warnings: finalResponse.warnings
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1421
1912
|
}
|
|
1422
1913
|
inferStopReason(response) {
|
|
1914
|
+
if (response.status === "failed") return "error";
|
|
1915
|
+
if (response.status === "incomplete") {
|
|
1916
|
+
const reason = response.incomplete_details?.reason;
|
|
1917
|
+
if (reason === "content_filter") return "content_filter";
|
|
1918
|
+
if (reason === "max_output_tokens") return "max_output_tokens";
|
|
1919
|
+
return "max_output_tokens";
|
|
1920
|
+
}
|
|
1423
1921
|
const output = response.output;
|
|
1424
|
-
if (!output || output.length === 0) return "unknown";
|
|
1922
|
+
if (!output || output.length === 0) return response.status === "completed" ? "end_turn" : "unknown";
|
|
1425
1923
|
if (output.some((item) => item.type === "function_call")) return "tool_call";
|
|
1426
|
-
|
|
1924
|
+
const lastItem = output[output.length - 1];
|
|
1925
|
+
if (lastItem?.status === "failed") return "error";
|
|
1926
|
+
if (lastItem?.status === "incomplete") return "max_output_tokens";
|
|
1427
1927
|
return "end_turn";
|
|
1428
1928
|
}
|
|
1429
1929
|
};
|
|
@@ -1440,36 +1940,41 @@ var ResponsesAdapter = class extends AdapterBase {
|
|
|
1440
1940
|
* - 高保真 replay(含 opaque continuation)
|
|
1441
1941
|
* - 能力降级 warning
|
|
1442
1942
|
*/
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1943
|
+
const profile$2 = {
|
|
1944
|
+
kind: "messages",
|
|
1945
|
+
instructionsMode: "system_message",
|
|
1946
|
+
supportedBlockTypes: ["text", "json"],
|
|
1947
|
+
reasoningBlockTypes: ["text"],
|
|
1948
|
+
capabilities: {
|
|
1949
|
+
textStreaming: "native",
|
|
1950
|
+
reasoningStreaming: "native",
|
|
1951
|
+
toolCallStreaming: "synthetic",
|
|
1952
|
+
replay: "opaque",
|
|
1953
|
+
usage: "stream",
|
|
1954
|
+
toolResultOutcomes: ["success", "error"]
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
const mapper$2 = new NormalizedRequestMapper(profile$2);
|
|
1958
|
+
function isMessagesReplayContentBlock(value) {
|
|
1959
|
+
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
1960
|
+
const block = value;
|
|
1961
|
+
switch (block.type) {
|
|
1962
|
+
case "text": return typeof block.text === "string";
|
|
1963
|
+
case "thinking": return typeof block.thinking === "string" && (block.signature === void 0 || typeof block.signature === "string");
|
|
1964
|
+
case "redacted_thinking": return typeof block.data === "string";
|
|
1965
|
+
case "tool_use": return typeof block.id === "string" && typeof block.name === "string" && !!block.input && typeof block.input === "object" && !Array.isArray(block.input);
|
|
1966
|
+
case "tool_result":
|
|
1967
|
+
if (typeof block.tool_use_id !== "string") return false;
|
|
1968
|
+
if (block.is_error !== void 0 && typeof block.is_error !== "boolean") return false;
|
|
1969
|
+
if (typeof block.content === "string") return true;
|
|
1970
|
+
if (!Array.isArray(block.content)) return false;
|
|
1971
|
+
return block.content.every(isMessagesReplayContentBlock);
|
|
1972
|
+
default: return false;
|
|
1973
|
+
}
|
|
1470
1974
|
}
|
|
1471
|
-
function
|
|
1472
|
-
|
|
1975
|
+
function assertMessagesReplayContent(content) {
|
|
1976
|
+
if (!Array.isArray(content)) throw new AIRequestError("Invalid opaque replay payload: content must be an array", "INVALID_OPAQUE_REPLAY");
|
|
1977
|
+
for (let i = 0; i < content.length; i++) if (!isMessagesReplayContentBlock(content[i])) throw new AIRequestError(`Invalid opaque replay payload: content[${i}] is not a valid Messages content block`, "INVALID_OPAQUE_REPLAY");
|
|
1473
1978
|
}
|
|
1474
1979
|
/** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
|
|
1475
1980
|
function synthesizeItemId(kind, blockIndex, responseId) {
|
|
@@ -1519,34 +2024,29 @@ function buildStreamMetadata(options) {
|
|
|
1519
2024
|
}
|
|
1520
2025
|
var MessagesAdapter = class extends AdapterBase {
|
|
1521
2026
|
kind = "messages";
|
|
1522
|
-
|
|
2027
|
+
capabilities = profile$2.capabilities;
|
|
1523
2028
|
apiKey;
|
|
1524
2029
|
apiVersion;
|
|
1525
2030
|
baseUrl;
|
|
1526
2031
|
fetchFn;
|
|
1527
|
-
warningAccumulator;
|
|
1528
2032
|
constructor(options) {
|
|
1529
2033
|
super();
|
|
1530
2034
|
this.apiKey = options.apiKey;
|
|
1531
2035
|
this.apiVersion = options.apiVersion ?? "2023-06-01";
|
|
1532
2036
|
this.baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
|
|
1533
2037
|
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
1534
|
-
this.warningAccumulator = [];
|
|
1535
|
-
}
|
|
1536
|
-
warn(message, _code) {
|
|
1537
|
-
this.warningAccumulator.push(message);
|
|
1538
2038
|
}
|
|
1539
2039
|
buildRequest(request) {
|
|
1540
2040
|
const messages = [];
|
|
1541
2041
|
let systemPrompt;
|
|
1542
2042
|
let pendingToolResultMessage;
|
|
1543
|
-
if (request.instructions) systemPrompt =
|
|
2043
|
+
if (request.instructions) systemPrompt = mapper$2.mapInstructions(request.instructions);
|
|
1544
2044
|
for (const item of request.input) {
|
|
1545
2045
|
if (item.type !== "tool_result") pendingToolResultMessage = void 0;
|
|
1546
2046
|
switch (item.type) {
|
|
1547
2047
|
case "message": {
|
|
1548
2048
|
const role = item.role === "user" ? "user" : "assistant";
|
|
1549
|
-
const supportedContent =
|
|
2049
|
+
const supportedContent = mapper$2.ensureTextBlocks(item.content, `input message (${item.role}) content`);
|
|
1550
2050
|
if (supportedContent.length === 1 && supportedContent[0]?.type === "text") messages.push({
|
|
1551
2051
|
role,
|
|
1552
2052
|
content: supportedContent[0].text
|
|
@@ -1573,8 +2073,8 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1573
2073
|
break;
|
|
1574
2074
|
}
|
|
1575
2075
|
case "tool_result": {
|
|
1576
|
-
|
|
1577
|
-
const content =
|
|
2076
|
+
mapper$2.assertToolResultOutcome(item.outcome);
|
|
2077
|
+
const content = mapper$2.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
|
|
1578
2078
|
const block = {
|
|
1579
2079
|
type: "tool_result",
|
|
1580
2080
|
tool_use_id: item.callId,
|
|
@@ -1594,7 +2094,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1594
2094
|
case "reasoning": {
|
|
1595
2095
|
const block = {
|
|
1596
2096
|
type: "thinking",
|
|
1597
|
-
thinking: contentBlocksToText(
|
|
2097
|
+
thinking: contentBlocksToText(mapper$2.ensureReasoningBlocks(item.content, "reasoning content"))
|
|
1598
2098
|
};
|
|
1599
2099
|
const lastMsg = messages[messages.length - 1];
|
|
1600
2100
|
if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") lastMsg.content.push(block);
|
|
@@ -1604,20 +2104,20 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1604
2104
|
});
|
|
1605
2105
|
break;
|
|
1606
2106
|
}
|
|
1607
|
-
case "opaque":
|
|
1608
|
-
if (item.purpose
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
}
|
|
2107
|
+
case "opaque": {
|
|
2108
|
+
if (item.purpose !== "replay") break;
|
|
2109
|
+
assertOpaqueReplayEnvelope(item.payload);
|
|
2110
|
+
const payload = item.payload;
|
|
2111
|
+
if (payload.role === "assistant" && "content" in payload) {
|
|
2112
|
+
assertMessagesReplayContent(payload.content);
|
|
2113
|
+
mapper$2.rollbackTrailingAssistantMessages(messages);
|
|
2114
|
+
messages.push({
|
|
2115
|
+
role: "assistant",
|
|
2116
|
+
content: payload.content
|
|
2117
|
+
});
|
|
1619
2118
|
}
|
|
1620
2119
|
break;
|
|
2120
|
+
}
|
|
1621
2121
|
}
|
|
1622
2122
|
}
|
|
1623
2123
|
const body = {
|
|
@@ -1644,27 +2144,53 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1644
2144
|
return body;
|
|
1645
2145
|
}
|
|
1646
2146
|
async *runStream(providerRequest, factory, request) {
|
|
1647
|
-
this.warningAccumulator = [];
|
|
1648
2147
|
const auxiliary = this.createAuxiliaryState(request);
|
|
2148
|
+
let completedEmitted = false;
|
|
1649
2149
|
if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Messages adapter", "UNSUPPORTED_METADATA");
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2150
|
+
let response;
|
|
2151
|
+
try {
|
|
2152
|
+
response = await this.fetchFn(`${this.baseUrl}/messages`, {
|
|
2153
|
+
method: "POST",
|
|
2154
|
+
headers: {
|
|
2155
|
+
"Content-Type": "application/json",
|
|
2156
|
+
"x-api-key": this.apiKey,
|
|
2157
|
+
"anthropic-version": this.apiVersion
|
|
2158
|
+
},
|
|
2159
|
+
body: JSON.stringify(providerRequest)
|
|
2160
|
+
});
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
2163
|
+
}
|
|
1659
2164
|
if (!response.ok) {
|
|
1660
|
-
const
|
|
1661
|
-
throw
|
|
2165
|
+
const errorBody = await response.text().catch(() => "");
|
|
2166
|
+
throw providerHttpError(response.status, errorBody);
|
|
1662
2167
|
}
|
|
1663
2168
|
const reader = response.body?.getReader();
|
|
1664
|
-
if (!reader) throw new
|
|
2169
|
+
if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
2170
|
+
const parser = new IncrementalStreamParser(splitSSEFrames, (frame) => {
|
|
2171
|
+
let eventType = "";
|
|
2172
|
+
let dataStr = "";
|
|
2173
|
+
for (const rawLine of frame.split("\n")) {
|
|
2174
|
+
const line = rawLine.trim();
|
|
2175
|
+
if (line.startsWith("event: ")) eventType = line.slice(7).trim();
|
|
2176
|
+
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
2177
|
+
}
|
|
2178
|
+
if (!eventType) return { status: "ignored" };
|
|
2179
|
+
try {
|
|
2180
|
+
const data = JSON.parse(dataStr);
|
|
2181
|
+
return {
|
|
2182
|
+
status: "parsed",
|
|
2183
|
+
value: {
|
|
2184
|
+
type: eventType,
|
|
2185
|
+
data
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
} catch {
|
|
2189
|
+
return { status: "malformed" };
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
1665
2192
|
const output = [];
|
|
1666
|
-
|
|
1667
|
-
let buffer = "";
|
|
2193
|
+
let streamDone = false;
|
|
1668
2194
|
let messageResponse;
|
|
1669
2195
|
let currentContentBlockIndex = -1;
|
|
1670
2196
|
let currentItemType = null;
|
|
@@ -1686,11 +2212,10 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1686
2212
|
}
|
|
1687
2213
|
try {
|
|
1688
2214
|
while (true) {
|
|
1689
|
-
const { done, value } = await reader.read()
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const { events,
|
|
1693
|
-
buffer = rest;
|
|
2215
|
+
const { done, value } = await reader.read().catch((err) => {
|
|
2216
|
+
throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
|
|
2217
|
+
});
|
|
2218
|
+
const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
|
|
1694
2219
|
const malformedWarning = emitMalformedStreamWarning(factory, {
|
|
1695
2220
|
count: malformedEvents,
|
|
1696
2221
|
providerLabel: "Messages",
|
|
@@ -1702,7 +2227,6 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1702
2227
|
case "error": {
|
|
1703
2228
|
const err = sseEvent.data.error;
|
|
1704
2229
|
yield factory.responseWarning(err.message, err.type);
|
|
1705
|
-
this.warn(err.message, err.type);
|
|
1706
2230
|
continue;
|
|
1707
2231
|
}
|
|
1708
2232
|
case "message_start":
|
|
@@ -1737,7 +2261,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1737
2261
|
yield factory.reasoningStarted(currentItemId, "redacted");
|
|
1738
2262
|
yield factory.reasoningDelta(currentItemId, textBlock(data));
|
|
1739
2263
|
const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
|
|
1740
|
-
yield factory.reasoningCompleted(
|
|
2264
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
1741
2265
|
output.push(redactedItem);
|
|
1742
2266
|
rawReplayContent.push({
|
|
1743
2267
|
type: "redacted_thinking",
|
|
@@ -1766,7 +2290,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1766
2290
|
if (currentItemType === "message" && currentItemId) {
|
|
1767
2291
|
const txt = delta.text;
|
|
1768
2292
|
textBuffer += txt;
|
|
1769
|
-
yield factory.messageDelta(currentItemId, txt);
|
|
2293
|
+
yield factory.messageDelta(currentItemId, textBlock(txt));
|
|
1770
2294
|
}
|
|
1771
2295
|
break;
|
|
1772
2296
|
case "thinking_delta":
|
|
@@ -1788,14 +2312,14 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1788
2312
|
}
|
|
1789
2313
|
case "content_block_stop":
|
|
1790
2314
|
if (currentItemType === "message" && currentItemId) {
|
|
1791
|
-
yield factory.messageCompleted(
|
|
2315
|
+
yield factory.messageCompleted(currentItemId);
|
|
1792
2316
|
output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
|
|
1793
2317
|
rawReplayContent.push({
|
|
1794
2318
|
type: "text",
|
|
1795
2319
|
text: textBuffer
|
|
1796
2320
|
});
|
|
1797
2321
|
} else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
|
|
1798
|
-
yield factory.reasoningCompleted(
|
|
2322
|
+
yield factory.reasoningCompleted(currentItemId);
|
|
1799
2323
|
output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
|
|
1800
2324
|
rawReplayContent.push({
|
|
1801
2325
|
type: "thinking",
|
|
@@ -1803,7 +2327,7 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1803
2327
|
});
|
|
1804
2328
|
} else if (currentItemType === "tool_call" && currentItemId) {
|
|
1805
2329
|
const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
|
|
1806
|
-
yield factory.toolCallCompleted(
|
|
2330
|
+
yield factory.toolCallCompleted(currentItemId);
|
|
1807
2331
|
output.push(tcItem);
|
|
1808
2332
|
rawReplayContent.push({
|
|
1809
2333
|
type: "tool_use",
|
|
@@ -1824,11 +2348,19 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1824
2348
|
}
|
|
1825
2349
|
case "message_stop": break;
|
|
1826
2350
|
}
|
|
2351
|
+
if (done) {
|
|
2352
|
+
streamDone = true;
|
|
2353
|
+
break;
|
|
2354
|
+
}
|
|
1827
2355
|
}
|
|
1828
2356
|
} finally {
|
|
1829
|
-
|
|
2357
|
+
try {
|
|
2358
|
+
if (!streamDone) await reader.cancel().catch(() => void 0);
|
|
2359
|
+
} finally {
|
|
2360
|
+
reader.releaseLock();
|
|
2361
|
+
}
|
|
1830
2362
|
}
|
|
1831
|
-
if (
|
|
2363
|
+
if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Messages SSE frame", "STREAM_ERROR");
|
|
1832
2364
|
const replay = [...replayFromOutput(output)];
|
|
1833
2365
|
if (messageResponse) {
|
|
1834
2366
|
const replayContent = rawReplayContent.length > 0 ? rawReplayContent : messageResponse.content;
|
|
@@ -1849,17 +2381,29 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1849
2381
|
if (!hasStreamedReasoning) {}
|
|
1850
2382
|
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
1851
2383
|
for (const event of auxiliaryResult.events) yield event;
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2384
|
+
if (!completedEmitted) {
|
|
2385
|
+
completedEmitted = true;
|
|
2386
|
+
const finalResponse = this.buildResponse(request, {
|
|
2387
|
+
output,
|
|
2388
|
+
replay,
|
|
2389
|
+
stopReason: stopReason ? mapStopReason(stopReason) : void 0,
|
|
2390
|
+
usage: auxiliaryResult.usage,
|
|
2391
|
+
billing: auxiliaryResult.billing,
|
|
2392
|
+
auxiliary: auxiliaryResult.auxiliary,
|
|
2393
|
+
warnings: auxiliaryResult.warnings,
|
|
2394
|
+
metadataSources: auxiliaryResult.metadataSources,
|
|
2395
|
+
rawResponseId
|
|
2396
|
+
}, factory);
|
|
2397
|
+
yield factory.responseCompleted({
|
|
2398
|
+
replay: finalResponse.replay,
|
|
2399
|
+
stopReason: finalResponse.stopReason,
|
|
2400
|
+
trace: finalResponse.backend,
|
|
2401
|
+
usage: finalResponse.usage,
|
|
2402
|
+
billing: finalResponse.billing,
|
|
2403
|
+
auxiliary: finalResponse.auxiliary,
|
|
2404
|
+
warnings: finalResponse.warnings
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
1863
2407
|
}
|
|
1864
2408
|
};
|
|
1865
2409
|
//#endregion
|
|
@@ -1874,51 +2418,21 @@ var MessagesAdapter = class extends AdapterBase {
|
|
|
1874
2418
|
* - replay fidelity 依赖 provider 是否暴露可回放的 assistant turn 字段
|
|
1875
2419
|
*/
|
|
1876
2420
|
const REASONING_FIELDS = ["reasoning_content", "reasoning"];
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
let rest = buffer;
|
|
1890
|
-
let malformedEvents = 0;
|
|
1891
|
-
while (true) {
|
|
1892
|
-
const lineEnd = rest.indexOf("\n");
|
|
1893
|
-
if (lineEnd === -1) break;
|
|
1894
|
-
const line = rest.slice(0, lineEnd).trim();
|
|
1895
|
-
rest = rest.slice(lineEnd + 1);
|
|
1896
|
-
if (!line.startsWith("data: ")) continue;
|
|
1897
|
-
const data = line.slice(6).trim();
|
|
1898
|
-
if (data === "[DONE]") continue;
|
|
1899
|
-
try {
|
|
1900
|
-
chunks.push(JSON.parse(data));
|
|
1901
|
-
} catch {
|
|
1902
|
-
malformedEvents++;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
return {
|
|
1906
|
-
chunks,
|
|
1907
|
-
rest,
|
|
1908
|
-
malformedEvents
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
function ensureTextCompatibleBlocks(blocks, field) {
|
|
1912
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
1913
|
-
const block = blocks[i];
|
|
1914
|
-
if (!block) continue;
|
|
1915
|
-
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");
|
|
2421
|
+
const profile$1 = {
|
|
2422
|
+
kind: "chat-completions",
|
|
2423
|
+
instructionsMode: "system_message",
|
|
2424
|
+
supportedBlockTypes: ["text", "json"],
|
|
2425
|
+
reasoningBlockTypes: ["text"],
|
|
2426
|
+
capabilities: {
|
|
2427
|
+
textStreaming: "native",
|
|
2428
|
+
reasoningStreaming: "native",
|
|
2429
|
+
toolCallStreaming: "native",
|
|
2430
|
+
replay: "opaque",
|
|
2431
|
+
usage: "final",
|
|
2432
|
+
toolResultOutcomes: ["success"]
|
|
1916
2433
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
function contentBlocksToChatText(blocks, field) {
|
|
1920
|
-
return contentBlocksToText(ensureTextCompatibleBlocks(blocks, field));
|
|
1921
|
-
}
|
|
2434
|
+
};
|
|
2435
|
+
const mapper$1 = new NormalizedRequestMapper(profile$1);
|
|
1922
2436
|
function extractReasoningText(value) {
|
|
1923
2437
|
if (typeof value === "string") return value;
|
|
1924
2438
|
if (Array.isArray(value)) return value.map(extractReasoningText).join("");
|
|
@@ -1949,8 +2463,31 @@ function extractReasoningDeltas(delta) {
|
|
|
1949
2463
|
}
|
|
1950
2464
|
return deltas;
|
|
1951
2465
|
}
|
|
1952
|
-
function
|
|
1953
|
-
|
|
2466
|
+
function isChatReplayToolCall(value) {
|
|
2467
|
+
if (!value || typeof value !== "object") return false;
|
|
2468
|
+
const entry = value;
|
|
2469
|
+
if (typeof entry.id !== "string" || entry.type !== "function") return false;
|
|
2470
|
+
const fn = entry.function;
|
|
2471
|
+
if (!fn || typeof fn !== "object") return false;
|
|
2472
|
+
const f = fn;
|
|
2473
|
+
return typeof f.name === "string" && typeof f.arguments === "string";
|
|
2474
|
+
}
|
|
2475
|
+
function isChatReplayMessage(value) {
|
|
2476
|
+
if (!value || typeof value !== "object") return false;
|
|
2477
|
+
const msg = value;
|
|
2478
|
+
const role = msg.role;
|
|
2479
|
+
if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") return false;
|
|
2480
|
+
if (!(msg.content === null || typeof msg.content === "string")) return false;
|
|
2481
|
+
if (msg.tool_calls !== void 0) {
|
|
2482
|
+
if (!Array.isArray(msg.tool_calls) || !msg.tool_calls.every(isChatReplayToolCall)) return false;
|
|
2483
|
+
}
|
|
2484
|
+
if (msg.tool_call_id !== void 0 && typeof msg.tool_call_id !== "string") return false;
|
|
2485
|
+
if (msg.name !== void 0 && typeof msg.name !== "string") return false;
|
|
2486
|
+
return true;
|
|
2487
|
+
}
|
|
2488
|
+
function assertChatReplayMessages(messages, field) {
|
|
2489
|
+
if (!Array.isArray(messages)) throw new AIRequestError(`Invalid opaque replay payload: ${field} must be an array`, "INVALID_OPAQUE_REPLAY");
|
|
2490
|
+
for (let i = 0; i < messages.length; i++) if (!isChatReplayMessage(messages[i])) throw new AIRequestError(`Invalid opaque replay payload: ${field}[${i}] is not a valid chat message`, "INVALID_OPAQUE_REPLAY");
|
|
1954
2491
|
}
|
|
1955
2492
|
function buildAssistantReplayMessage(params) {
|
|
1956
2493
|
const { content, reasoningByField, toolCalls } = params;
|
|
@@ -1972,7 +2509,7 @@ function buildAssistantReplayMessage(params) {
|
|
|
1972
2509
|
}
|
|
1973
2510
|
var ChatCompletionsAdapter = class extends AdapterBase {
|
|
1974
2511
|
kind = "chat-completions";
|
|
1975
|
-
|
|
2512
|
+
capabilities = profile$1.capabilities;
|
|
1976
2513
|
apiKey;
|
|
1977
2514
|
baseUrl;
|
|
1978
2515
|
fetchFn;
|
|
@@ -1984,17 +2521,14 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
1984
2521
|
}
|
|
1985
2522
|
buildRequest(request) {
|
|
1986
2523
|
const messages = [];
|
|
1987
|
-
if (request.instructions) {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
content
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
2524
|
+
if (request.instructions) messages.push({
|
|
2525
|
+
role: "system",
|
|
2526
|
+
content: mapper$1.mapInstructions(request.instructions)
|
|
2527
|
+
});
|
|
1994
2528
|
for (const item of request.input) switch (item.type) {
|
|
1995
2529
|
case "message": {
|
|
1996
2530
|
const role = item.role;
|
|
1997
|
-
const text =
|
|
2531
|
+
const text = contentBlocksToText(mapper$1.ensureTextBlocks(item.content, `input message (${item.role}) content`));
|
|
1998
2532
|
messages.push({
|
|
1999
2533
|
role,
|
|
2000
2534
|
content: text || null
|
|
@@ -2020,38 +2554,44 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2020
2554
|
break;
|
|
2021
2555
|
}
|
|
2022
2556
|
case "tool_result":
|
|
2023
|
-
|
|
2557
|
+
mapper$1.assertToolResultOutcome(item.outcome);
|
|
2024
2558
|
messages.push({
|
|
2025
2559
|
role: "tool",
|
|
2026
2560
|
tool_call_id: item.callId,
|
|
2027
2561
|
name: item.toolName,
|
|
2028
|
-
content:
|
|
2562
|
+
content: contentBlocksToText(mapper$1.ensureTextBlocks(item.content, `tool_result ${item.callId} content`))
|
|
2029
2563
|
});
|
|
2030
2564
|
break;
|
|
2031
2565
|
case "reasoning":
|
|
2032
2566
|
messages.push({
|
|
2033
2567
|
role: "assistant",
|
|
2034
|
-
content:
|
|
2568
|
+
content: contentBlocksToText(mapper$1.ensureTextBlocks(item.content, "reasoning content"))
|
|
2035
2569
|
});
|
|
2036
2570
|
break;
|
|
2037
|
-
case "opaque":
|
|
2038
|
-
if (item.purpose
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2571
|
+
case "opaque": {
|
|
2572
|
+
if (item.purpose !== "replay") break;
|
|
2573
|
+
assertOpaqueReplayEnvelope(item.payload);
|
|
2574
|
+
const payload = item.payload;
|
|
2575
|
+
if (payload.role === "assistant" && typeof payload.content === "string") messages.push({
|
|
2576
|
+
role: "assistant",
|
|
2577
|
+
content: payload.content
|
|
2578
|
+
});
|
|
2579
|
+
else if (payload.replaceCanonical === true && "messages" in payload) {
|
|
2580
|
+
assertChatReplayMessages(payload.messages, "messages");
|
|
2581
|
+
mapper$1.rollbackTrailingAssistantMessages(messages);
|
|
2582
|
+
for (const m of payload.messages) messages.push(m);
|
|
2583
|
+
} else if ("messages" in payload) {
|
|
2584
|
+
assertChatReplayMessages(payload.messages, "messages");
|
|
2585
|
+
for (const m of payload.messages) messages.push(m);
|
|
2048
2586
|
}
|
|
2049
2587
|
break;
|
|
2588
|
+
}
|
|
2050
2589
|
}
|
|
2051
2590
|
const body = {
|
|
2052
2591
|
model: request.model,
|
|
2053
2592
|
messages,
|
|
2054
|
-
stream: true
|
|
2593
|
+
stream: true,
|
|
2594
|
+
n: 1
|
|
2055
2595
|
};
|
|
2056
2596
|
if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
|
|
2057
2597
|
type: "function",
|
|
@@ -2076,23 +2616,41 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2076
2616
|
}
|
|
2077
2617
|
async *runStream(providerRequest, factory, request) {
|
|
2078
2618
|
const auxiliary = this.createAuxiliaryState(request);
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2619
|
+
let response;
|
|
2620
|
+
try {
|
|
2621
|
+
response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
2622
|
+
method: "POST",
|
|
2623
|
+
headers: {
|
|
2624
|
+
"Content-Type": "application/json",
|
|
2625
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2626
|
+
},
|
|
2627
|
+
body: JSON.stringify(providerRequest)
|
|
2628
|
+
});
|
|
2629
|
+
} catch (err) {
|
|
2630
|
+
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
2631
|
+
}
|
|
2087
2632
|
if (!response.ok) {
|
|
2088
|
-
const
|
|
2089
|
-
throw
|
|
2633
|
+
const errorBody = await response.text().catch(() => "");
|
|
2634
|
+
throw providerHttpError(response.status, errorBody);
|
|
2090
2635
|
}
|
|
2091
2636
|
const reader = response.body?.getReader();
|
|
2092
|
-
if (!reader) throw new
|
|
2637
|
+
if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
2638
|
+
const parser = new IncrementalStreamParser(splitLines, (item) => {
|
|
2639
|
+
const trimmed = item.trim();
|
|
2640
|
+
if (!trimmed.startsWith("data: ")) return { status: "ignored" };
|
|
2641
|
+
const data = trimmed.slice(6).trim();
|
|
2642
|
+
if (data === "[DONE]") return { status: "ignored" };
|
|
2643
|
+
try {
|
|
2644
|
+
return {
|
|
2645
|
+
status: "parsed",
|
|
2646
|
+
value: JSON.parse(data)
|
|
2647
|
+
};
|
|
2648
|
+
} catch {
|
|
2649
|
+
return { status: "malformed" };
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2093
2652
|
const output = [];
|
|
2094
|
-
|
|
2095
|
-
let buffer = "";
|
|
2653
|
+
let streamDone = false;
|
|
2096
2654
|
let responseId;
|
|
2097
2655
|
let accumulatedContent = "";
|
|
2098
2656
|
let accumulatedReasoning = "";
|
|
@@ -2100,6 +2658,9 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2100
2658
|
let currentReasoningId = "";
|
|
2101
2659
|
let hasMessageStarted = false;
|
|
2102
2660
|
let hasReasoningStarted = false;
|
|
2661
|
+
let completedEmitted = false;
|
|
2662
|
+
let warnedNonZeroChoice = false;
|
|
2663
|
+
const buildResponse = this.buildResponse.bind(this);
|
|
2103
2664
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
2104
2665
|
const reasoningByField = /* @__PURE__ */ new Map();
|
|
2105
2666
|
const finalizePendingTurn = () => {
|
|
@@ -2108,17 +2669,17 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2108
2669
|
const finalizedReasoningByField = new Map(reasoningByField);
|
|
2109
2670
|
if (hasReasoningStarted && accumulatedReasoning) {
|
|
2110
2671
|
const reasoning = reasoningItem([textBlock(accumulatedReasoning)], "full", currentReasoningId);
|
|
2111
|
-
events.push(factory.reasoningCompleted(
|
|
2672
|
+
events.push(factory.reasoningCompleted(currentReasoningId));
|
|
2112
2673
|
output.push(reasoning);
|
|
2113
2674
|
}
|
|
2114
|
-
if (hasMessageStarted
|
|
2675
|
+
if (hasMessageStarted) {
|
|
2115
2676
|
const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
|
|
2116
|
-
events.push(factory.messageCompleted(
|
|
2117
|
-
output.push(message);
|
|
2677
|
+
events.push(factory.messageCompleted(currentMessageId));
|
|
2678
|
+
if (accumulatedContent) output.push(message);
|
|
2118
2679
|
}
|
|
2119
2680
|
for (const pending of finalizedToolCalls) {
|
|
2120
2681
|
const toolCall = toolCallItem(pending.id, pending.name, pending.args);
|
|
2121
|
-
events.push(factory.toolCallCompleted(
|
|
2682
|
+
events.push(factory.toolCallCompleted(pending.id));
|
|
2122
2683
|
output.push(toolCall);
|
|
2123
2684
|
}
|
|
2124
2685
|
const assistantReplayMessage = buildAssistantReplayMessage({
|
|
@@ -2139,13 +2700,46 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2139
2700
|
assistantReplayMessage
|
|
2140
2701
|
};
|
|
2141
2702
|
};
|
|
2703
|
+
const emitCompleted = async function* (stopReason, assistantReplayMessage, rawResponseId) {
|
|
2704
|
+
if (completedEmitted) {
|
|
2705
|
+
yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
completedEmitted = true;
|
|
2709
|
+
const replay = [...replayFromOutput(output)];
|
|
2710
|
+
if (assistantReplayMessage) replay.push(opaqueItem("chat.completions", "replay", {
|
|
2711
|
+
replaceCanonical: true,
|
|
2712
|
+
messages: [assistantReplayMessage]
|
|
2713
|
+
}));
|
|
2714
|
+
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
2715
|
+
for (const event of auxiliaryResult.events) yield event;
|
|
2716
|
+
const finalResponse = buildResponse(request, {
|
|
2717
|
+
output,
|
|
2718
|
+
replay,
|
|
2719
|
+
stopReason,
|
|
2720
|
+
usage: auxiliaryResult.usage,
|
|
2721
|
+
billing: auxiliaryResult.billing,
|
|
2722
|
+
auxiliary: auxiliaryResult.auxiliary,
|
|
2723
|
+
warnings: auxiliaryResult.warnings,
|
|
2724
|
+
metadataSources: auxiliaryResult.metadataSources,
|
|
2725
|
+
rawResponseId
|
|
2726
|
+
}, factory);
|
|
2727
|
+
yield factory.responseCompleted({
|
|
2728
|
+
replay: finalResponse.replay,
|
|
2729
|
+
stopReason: finalResponse.stopReason,
|
|
2730
|
+
trace: finalResponse.backend,
|
|
2731
|
+
usage: finalResponse.usage,
|
|
2732
|
+
billing: finalResponse.billing,
|
|
2733
|
+
auxiliary: finalResponse.auxiliary,
|
|
2734
|
+
warnings: finalResponse.warnings
|
|
2735
|
+
});
|
|
2736
|
+
};
|
|
2142
2737
|
try {
|
|
2143
2738
|
while (true) {
|
|
2144
|
-
const { done, value } = await reader.read()
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
const { chunks,
|
|
2148
|
-
buffer = rest;
|
|
2739
|
+
const { done, value } = await reader.read().catch((err) => {
|
|
2740
|
+
throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
|
|
2741
|
+
});
|
|
2742
|
+
const { items: chunks, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
|
|
2149
2743
|
const malformedWarning = emitMalformedStreamWarning(factory, {
|
|
2150
2744
|
count: malformedEvents,
|
|
2151
2745
|
providerLabel: "Chat Completions",
|
|
@@ -2156,14 +2750,28 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2156
2750
|
responseId = chunk.id;
|
|
2157
2751
|
if (chunk.usage) auxiliary.recordUsage(usageFromChatCompletions(chunk.usage), "final", chunk.usage);
|
|
2158
2752
|
for (const choice of chunk.choices) {
|
|
2159
|
-
if (choice.index !== 0)
|
|
2753
|
+
if (choice.index !== 0) {
|
|
2754
|
+
if (!warnedNonZeroChoice) {
|
|
2755
|
+
yield factory.responseWarning(`Chat Completions returned choice index ${choice.index}; only the first choice (index 0) is supported. This choice is ignored.`, "MULTIPLE_CHOICES_IGNORED");
|
|
2756
|
+
warnedNonZeroChoice = true;
|
|
2757
|
+
}
|
|
2758
|
+
continue;
|
|
2759
|
+
}
|
|
2760
|
+
if (completedEmitted) {
|
|
2761
|
+
if (choice.finish_reason) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
2762
|
+
continue;
|
|
2763
|
+
}
|
|
2160
2764
|
const delta = choice.delta;
|
|
2161
2765
|
const finishReason = choice.finish_reason;
|
|
2162
2766
|
const reasoningDeltas = extractReasoningDeltas(delta);
|
|
2163
|
-
|
|
2767
|
+
const ensureMessageStarted = () => {
|
|
2768
|
+
if (hasMessageStarted) return;
|
|
2164
2769
|
currentMessageId = `msg-${chunk.id}`;
|
|
2165
2770
|
hasMessageStarted = true;
|
|
2166
2771
|
accumulatedContent = "";
|
|
2772
|
+
};
|
|
2773
|
+
if (delta.role === "assistant" && !hasMessageStarted) {
|
|
2774
|
+
ensureMessageStarted();
|
|
2167
2775
|
yield factory.messageStarted(currentMessageId);
|
|
2168
2776
|
}
|
|
2169
2777
|
if (reasoningDeltas.length > 0) {
|
|
@@ -2181,32 +2789,41 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2181
2789
|
}
|
|
2182
2790
|
if (delta.content) {
|
|
2183
2791
|
if (!hasMessageStarted) {
|
|
2184
|
-
|
|
2185
|
-
hasMessageStarted = true;
|
|
2792
|
+
ensureMessageStarted();
|
|
2186
2793
|
yield factory.messageStarted(currentMessageId);
|
|
2187
2794
|
}
|
|
2188
2795
|
accumulatedContent += delta.content;
|
|
2189
|
-
yield factory.messageDelta(currentMessageId, delta.content);
|
|
2796
|
+
yield factory.messageDelta(currentMessageId, textBlock(delta.content));
|
|
2190
2797
|
}
|
|
2191
|
-
if (delta.tool_calls)
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
id: tc.id,
|
|
2196
|
-
name: tc.function?.name ?? "",
|
|
2197
|
-
args: ""
|
|
2198
|
-
});
|
|
2199
|
-
yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
|
|
2798
|
+
if (delta.tool_calls) {
|
|
2799
|
+
if (!hasMessageStarted) {
|
|
2800
|
+
ensureMessageStarted();
|
|
2801
|
+
yield factory.messageStarted(currentMessageId);
|
|
2200
2802
|
}
|
|
2201
|
-
|
|
2202
|
-
const
|
|
2203
|
-
if (
|
|
2204
|
-
|
|
2205
|
-
|
|
2803
|
+
for (const tc of delta.tool_calls) {
|
|
2804
|
+
const idx = tc.index;
|
|
2805
|
+
if (tc.id) {
|
|
2806
|
+
pendingToolCalls.set(idx, {
|
|
2807
|
+
id: tc.id,
|
|
2808
|
+
name: tc.function?.name ?? "",
|
|
2809
|
+
args: ""
|
|
2810
|
+
});
|
|
2811
|
+
yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
|
|
2812
|
+
}
|
|
2813
|
+
if (tc.function?.arguments) {
|
|
2814
|
+
const pending = pendingToolCalls.get(idx);
|
|
2815
|
+
if (pending) {
|
|
2816
|
+
pending.args += tc.function.arguments;
|
|
2817
|
+
yield factory.toolCallDelta(pending.id, { argumentsText: tc.function.arguments });
|
|
2818
|
+
}
|
|
2206
2819
|
}
|
|
2207
2820
|
}
|
|
2208
2821
|
}
|
|
2209
2822
|
if (delta.function_call) {
|
|
2823
|
+
if (!hasMessageStarted) {
|
|
2824
|
+
ensureMessageStarted();
|
|
2825
|
+
yield factory.messageStarted(currentMessageId);
|
|
2826
|
+
}
|
|
2210
2827
|
if (delta.function_call.name) {
|
|
2211
2828
|
const fcId = `fc-${chunk.id}-0`;
|
|
2212
2829
|
pendingToolCalls.set(0, {
|
|
@@ -2227,54 +2844,28 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2227
2844
|
if (finishReason && finishReason !== null) {
|
|
2228
2845
|
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
2229
2846
|
for (const event of events) yield event;
|
|
2230
|
-
|
|
2231
|
-
const replay = [...replayFromOutput(output)];
|
|
2232
|
-
if (assistantReplayMessage) replay.push(opaqueItem("chat.completions", "replay", {
|
|
2233
|
-
replaceCanonical: true,
|
|
2234
|
-
messages: [assistantReplayMessage]
|
|
2235
|
-
}));
|
|
2236
|
-
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
2237
|
-
for (const event of auxiliaryResult.events) yield event;
|
|
2238
|
-
yield factory.responseCompleted(this.buildResponse(request, {
|
|
2239
|
-
output,
|
|
2240
|
-
replay,
|
|
2241
|
-
stopReason,
|
|
2242
|
-
usage: auxiliaryResult.usage,
|
|
2243
|
-
billing: auxiliaryResult.billing,
|
|
2244
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
2245
|
-
warnings: auxiliaryResult.warnings,
|
|
2246
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
2247
|
-
rawResponseId: chunk.id
|
|
2248
|
-
}, factory));
|
|
2847
|
+
yield* emitCompleted(mapStopReason(finishReason), assistantReplayMessage, chunk.id);
|
|
2249
2848
|
}
|
|
2250
2849
|
}
|
|
2251
2850
|
}
|
|
2851
|
+
if (done) {
|
|
2852
|
+
streamDone = true;
|
|
2853
|
+
break;
|
|
2854
|
+
}
|
|
2252
2855
|
}
|
|
2253
2856
|
} finally {
|
|
2254
|
-
|
|
2857
|
+
try {
|
|
2858
|
+
if (!streamDone) await reader.cancel().catch(() => void 0);
|
|
2859
|
+
} finally {
|
|
2860
|
+
reader.releaseLock();
|
|
2861
|
+
}
|
|
2255
2862
|
}
|
|
2256
|
-
if (
|
|
2257
|
-
if (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0) {
|
|
2863
|
+
if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Chat Completions SSE frame", "STREAM_ERROR");
|
|
2864
|
+
if (!completedEmitted && (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0)) {
|
|
2258
2865
|
yield factory.responseWarning("Stream ended without a finish_reason", "INCOMPLETE_STREAM");
|
|
2259
2866
|
const { events, assistantReplayMessage } = finalizePendingTurn();
|
|
2260
2867
|
for (const event of events) yield event;
|
|
2261
|
-
|
|
2262
|
-
if (assistantReplayMessage) replay.push(opaqueItem("chat.completions", "replay", {
|
|
2263
|
-
replaceCanonical: true,
|
|
2264
|
-
messages: [assistantReplayMessage]
|
|
2265
|
-
}));
|
|
2266
|
-
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
2267
|
-
for (const event of auxiliaryResult.events) yield event;
|
|
2268
|
-
yield factory.responseCompleted(this.buildResponse(request, {
|
|
2269
|
-
output,
|
|
2270
|
-
replay,
|
|
2271
|
-
usage: auxiliaryResult.usage,
|
|
2272
|
-
billing: auxiliaryResult.billing,
|
|
2273
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
2274
|
-
warnings: auxiliaryResult.warnings,
|
|
2275
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
2276
|
-
rawResponseId: responseId
|
|
2277
|
-
}, factory));
|
|
2868
|
+
yield* emitCompleted(void 0, assistantReplayMessage, responseId);
|
|
2278
2869
|
}
|
|
2279
2870
|
}
|
|
2280
2871
|
};
|
|
@@ -2296,23 +2887,21 @@ var ChatCompletionsAdapter = class extends AdapterBase {
|
|
|
2296
2887
|
* - tool_call 不支持逐 token 流式
|
|
2297
2888
|
* - replay 保真度低(无 opaque continuation 机制)
|
|
2298
2889
|
*/
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
return typeof instructions === "string" ? instructions : contentBlocksToText(ensureOllamaTextBlocks(instructions, "instructions"));
|
|
2315
|
-
}
|
|
2890
|
+
const profile = {
|
|
2891
|
+
kind: "ollama",
|
|
2892
|
+
instructionsMode: "system_message",
|
|
2893
|
+
supportedBlockTypes: ["text", "json"],
|
|
2894
|
+
reasoningBlockTypes: ["text"],
|
|
2895
|
+
capabilities: {
|
|
2896
|
+
textStreaming: "native",
|
|
2897
|
+
reasoningStreaming: "none",
|
|
2898
|
+
toolCallStreaming: "synthetic",
|
|
2899
|
+
replay: "opaque",
|
|
2900
|
+
usage: "final",
|
|
2901
|
+
toolResultOutcomes: ["success"]
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
const mapper = new NormalizedRequestMapper(profile);
|
|
2316
2905
|
function parseOllamaToolArguments(item) {
|
|
2317
2906
|
if (item.argumentsJson && typeof item.argumentsJson === "object" && item.argumentsJson !== null) return item.argumentsJson;
|
|
2318
2907
|
try {
|
|
@@ -2321,46 +2910,24 @@ function parseOllamaToolArguments(item) {
|
|
|
2321
2910
|
} catch {}
|
|
2322
2911
|
throw new AIRequestError("ollama tool_call argumentsText must be valid JSON object when argumentsJson is absent", "TOOL_CALL_ARGUMENTS_INVALID");
|
|
2323
2912
|
}
|
|
2324
|
-
function
|
|
2325
|
-
if (outcome !== "success") throw new AIRequestError(`ollama does not preserve tool_result outcome "${outcome}"; only "success" is supported`, "UNSUPPORTED_TOOL_RESULT_OUTCOME");
|
|
2326
|
-
}
|
|
2327
|
-
function parseOllamaNDJSON(buffer) {
|
|
2328
|
-
const chunks = [];
|
|
2329
|
-
let rest = buffer;
|
|
2330
|
-
let malformedLines = 0;
|
|
2331
|
-
while (true) {
|
|
2332
|
-
const lineEnd = rest.indexOf("\n");
|
|
2333
|
-
if (lineEnd === -1) break;
|
|
2334
|
-
const line = rest.slice(0, lineEnd).trim();
|
|
2335
|
-
rest = rest.slice(lineEnd + 1);
|
|
2336
|
-
if (!line) continue;
|
|
2337
|
-
try {
|
|
2338
|
-
const parsed = JSON.parse(line);
|
|
2339
|
-
if (parsed && typeof parsed === "object" && "message" in parsed) chunks.push(parsed);
|
|
2340
|
-
else malformedLines++;
|
|
2341
|
-
} catch {
|
|
2342
|
-
malformedLines++;
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
return {
|
|
2346
|
-
chunks,
|
|
2347
|
-
rest,
|
|
2348
|
-
malformedLines
|
|
2349
|
-
};
|
|
2350
|
-
}
|
|
2351
|
-
function rollbackTrailingAssistantMessages(messages) {
|
|
2352
|
-
while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") messages.pop();
|
|
2353
|
-
}
|
|
2354
|
-
function isOllamaToolCalls(value) {
|
|
2913
|
+
function isOllamaReplayToolCalls(value) {
|
|
2355
2914
|
return Array.isArray(value) && value.every((entry) => {
|
|
2356
2915
|
if (!entry || typeof entry !== "object" || !("function" in entry)) return false;
|
|
2357
2916
|
const fn = entry.function;
|
|
2917
|
+
const id = entry.id;
|
|
2918
|
+
if (id !== void 0 && typeof id !== "string") return false;
|
|
2358
2919
|
return !!fn && typeof fn === "object" && "name" in fn && typeof fn.name === "string" && "arguments" in fn && typeof fn.arguments === "object" && fn.arguments !== null;
|
|
2359
2920
|
});
|
|
2360
2921
|
}
|
|
2922
|
+
function toWireOllamaToolCalls(toolCalls) {
|
|
2923
|
+
return toolCalls.map((tc) => ({ function: {
|
|
2924
|
+
name: tc.function.name,
|
|
2925
|
+
arguments: tc.function.arguments
|
|
2926
|
+
} }));
|
|
2927
|
+
}
|
|
2361
2928
|
var OllamaAdapter = class extends AdapterBase {
|
|
2362
2929
|
kind = "ollama";
|
|
2363
|
-
|
|
2930
|
+
capabilities = profile.capabilities;
|
|
2364
2931
|
baseUrl;
|
|
2365
2932
|
apiKey;
|
|
2366
2933
|
fetchFn;
|
|
@@ -2373,16 +2940,18 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2373
2940
|
buildRequest(request) {
|
|
2374
2941
|
if (request.toolChoice && request.toolChoice !== "auto") throw new AIRequestError("ollama does not support explicit toolChoice", "UNSUPPORTED_TOOL_CHOICE");
|
|
2375
2942
|
const messages = [];
|
|
2943
|
+
/** Local-only name → call id queue for best-effort tool_result association (not sent to Ollama). */
|
|
2944
|
+
const callIdsByName = /* @__PURE__ */ new Map();
|
|
2376
2945
|
if (request.instructions) messages.push({
|
|
2377
2946
|
role: "system",
|
|
2378
|
-
content:
|
|
2947
|
+
content: mapper.mapInstructions(request.instructions)
|
|
2379
2948
|
});
|
|
2380
2949
|
for (const item of request.input) switch (item.type) {
|
|
2381
2950
|
case "message": {
|
|
2382
2951
|
const role = item.role;
|
|
2383
2952
|
messages.push({
|
|
2384
2953
|
role,
|
|
2385
|
-
content: contentBlocksToText(
|
|
2954
|
+
content: contentBlocksToText(mapper.ensureTextBlocks(item.content, `input message (${item.role}) content`))
|
|
2386
2955
|
});
|
|
2387
2956
|
break;
|
|
2388
2957
|
}
|
|
@@ -2392,6 +2961,9 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2392
2961
|
name: item.name,
|
|
2393
2962
|
arguments: parseOllamaToolArguments(item)
|
|
2394
2963
|
} };
|
|
2964
|
+
const queue = callIdsByName.get(item.name) ?? [];
|
|
2965
|
+
queue.push(item.id);
|
|
2966
|
+
callIdsByName.set(item.name, queue);
|
|
2395
2967
|
if (lastAssistant) lastAssistant.tool_calls = [...lastAssistant.tool_calls ?? [], tc];
|
|
2396
2968
|
else messages.push({
|
|
2397
2969
|
role: "assistant",
|
|
@@ -2400,32 +2972,48 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2400
2972
|
});
|
|
2401
2973
|
break;
|
|
2402
2974
|
}
|
|
2403
|
-
case "tool_result":
|
|
2404
|
-
|
|
2975
|
+
case "tool_result": {
|
|
2976
|
+
mapper.assertToolResultOutcome(item.outcome);
|
|
2977
|
+
const queue = callIdsByName.get(item.toolName);
|
|
2978
|
+
if (queue && queue.length > 0) queue.shift();
|
|
2405
2979
|
messages.push({
|
|
2406
2980
|
role: "tool",
|
|
2407
|
-
content: contentBlocksToText(
|
|
2981
|
+
content: contentBlocksToText(mapper.ensureTextBlocks(item.content, `tool_result ${item.callId} content`))
|
|
2408
2982
|
});
|
|
2409
2983
|
break;
|
|
2984
|
+
}
|
|
2410
2985
|
case "reasoning":
|
|
2411
2986
|
messages.push({
|
|
2412
2987
|
role: "assistant",
|
|
2413
|
-
content: contentBlocksToText(
|
|
2988
|
+
content: contentBlocksToText(mapper.ensureReasoningBlocks(item.content, "reasoning content"))
|
|
2414
2989
|
});
|
|
2415
2990
|
break;
|
|
2416
|
-
case "opaque":
|
|
2417
|
-
if (item.source
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2991
|
+
case "opaque": {
|
|
2992
|
+
if (item.source !== "ollama" || item.purpose !== "replay") break;
|
|
2993
|
+
assertOpaqueReplayEnvelope(item.payload);
|
|
2994
|
+
const payload = item.payload;
|
|
2995
|
+
if (payload.role === "assistant" && typeof payload.content === "string") {
|
|
2996
|
+
mapper.rollbackTrailingAssistantMessages(messages);
|
|
2997
|
+
let replayToolCalls;
|
|
2998
|
+
if ("tool_calls" in payload && payload.tool_calls !== void 0) {
|
|
2999
|
+
if (!isOllamaReplayToolCalls(payload.tool_calls)) throw new AIRequestError("Invalid opaque replay payload: tool_calls is not a valid ollama tool_calls array", "INVALID_OPAQUE_REPLAY");
|
|
3000
|
+
replayToolCalls = payload.tool_calls;
|
|
3001
|
+
}
|
|
3002
|
+
if (replayToolCalls) {
|
|
3003
|
+
for (const tc of replayToolCalls) if (tc.id) {
|
|
3004
|
+
const queue = callIdsByName.get(tc.function.name) ?? [];
|
|
3005
|
+
queue.push(tc.id);
|
|
3006
|
+
callIdsByName.set(tc.function.name, queue);
|
|
3007
|
+
}
|
|
2426
3008
|
}
|
|
3009
|
+
messages.push({
|
|
3010
|
+
role: "assistant",
|
|
3011
|
+
content: payload.content,
|
|
3012
|
+
tool_calls: replayToolCalls ? toWireOllamaToolCalls(replayToolCalls) : void 0
|
|
3013
|
+
});
|
|
2427
3014
|
}
|
|
2428
3015
|
break;
|
|
3016
|
+
}
|
|
2429
3017
|
}
|
|
2430
3018
|
const body = {
|
|
2431
3019
|
model: request.model,
|
|
@@ -2449,35 +3037,96 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2449
3037
|
}
|
|
2450
3038
|
async *runStream(providerRequest, factory, request) {
|
|
2451
3039
|
const auxiliary = this.createAuxiliaryState(request);
|
|
3040
|
+
let completedEmitted = false;
|
|
2452
3041
|
if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Ollama adapter", "UNSUPPORTED_METADATA");
|
|
2453
3042
|
const headers = { "Content-Type": "application/json" };
|
|
2454
3043
|
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
3044
|
+
let response;
|
|
3045
|
+
try {
|
|
3046
|
+
response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
|
|
3047
|
+
method: "POST",
|
|
3048
|
+
headers,
|
|
3049
|
+
body: JSON.stringify(providerRequest)
|
|
3050
|
+
});
|
|
3051
|
+
} catch (err) {
|
|
3052
|
+
throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
|
|
3053
|
+
}
|
|
2460
3054
|
if (!response.ok) {
|
|
2461
|
-
const
|
|
2462
|
-
throw
|
|
3055
|
+
const errorBody = await response.text().catch(() => "");
|
|
3056
|
+
throw providerHttpError(response.status, errorBody);
|
|
2463
3057
|
}
|
|
2464
3058
|
const reader = response.body?.getReader();
|
|
2465
|
-
if (!reader) throw new
|
|
3059
|
+
if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
|
|
3060
|
+
const parser = new IncrementalStreamParser(splitLines, (item) => {
|
|
3061
|
+
const trimmed = item.trim();
|
|
3062
|
+
if (!trimmed) return { status: "ignored" };
|
|
3063
|
+
try {
|
|
3064
|
+
const parsed = JSON.parse(trimmed);
|
|
3065
|
+
if (parsed && typeof parsed === "object" && "message" in parsed) return {
|
|
3066
|
+
status: "parsed",
|
|
3067
|
+
value: parsed
|
|
3068
|
+
};
|
|
3069
|
+
return { status: "malformed" };
|
|
3070
|
+
} catch {
|
|
3071
|
+
return { status: "malformed" };
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
2466
3074
|
const output = [];
|
|
2467
|
-
|
|
2468
|
-
let buffer = "";
|
|
3075
|
+
let streamDone = false;
|
|
2469
3076
|
let responseId;
|
|
2470
3077
|
let accumulatedContent = "";
|
|
2471
3078
|
let currentMessageId = "";
|
|
2472
3079
|
let hasMessageStarted = false;
|
|
2473
3080
|
let pendingToolCalls = [];
|
|
3081
|
+
let toolCallIndex = 0;
|
|
3082
|
+
const buildResponse = this.buildResponse.bind(this);
|
|
3083
|
+
const emitCompleted = async function* (stopReason, rawResponseId) {
|
|
3084
|
+
if (completedEmitted) {
|
|
3085
|
+
yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
completedEmitted = true;
|
|
3089
|
+
const replay = replayFromOutput(output);
|
|
3090
|
+
if (accumulatedContent || pendingToolCalls.length > 0) replay.push(opaqueItem("ollama", "replay", {
|
|
3091
|
+
role: "assistant",
|
|
3092
|
+
content: accumulatedContent,
|
|
3093
|
+
tool_calls: pendingToolCalls.map((tc) => ({
|
|
3094
|
+
id: tc.id,
|
|
3095
|
+
function: {
|
|
3096
|
+
name: tc.name,
|
|
3097
|
+
arguments: tc.argumentsJson
|
|
3098
|
+
}
|
|
3099
|
+
}))
|
|
3100
|
+
}));
|
|
3101
|
+
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
3102
|
+
for (const event of auxiliaryResult.events) yield event;
|
|
3103
|
+
const finalResponse = buildResponse(request, {
|
|
3104
|
+
output,
|
|
3105
|
+
replay,
|
|
3106
|
+
stopReason,
|
|
3107
|
+
usage: auxiliaryResult.usage,
|
|
3108
|
+
billing: auxiliaryResult.billing,
|
|
3109
|
+
auxiliary: auxiliaryResult.auxiliary,
|
|
3110
|
+
warnings: auxiliaryResult.warnings,
|
|
3111
|
+
metadataSources: auxiliaryResult.metadataSources,
|
|
3112
|
+
rawResponseId
|
|
3113
|
+
}, factory);
|
|
3114
|
+
yield factory.responseCompleted({
|
|
3115
|
+
replay: finalResponse.replay,
|
|
3116
|
+
stopReason: finalResponse.stopReason,
|
|
3117
|
+
trace: finalResponse.backend,
|
|
3118
|
+
usage: finalResponse.usage,
|
|
3119
|
+
billing: finalResponse.billing,
|
|
3120
|
+
auxiliary: finalResponse.auxiliary,
|
|
3121
|
+
warnings: finalResponse.warnings
|
|
3122
|
+
});
|
|
3123
|
+
};
|
|
2474
3124
|
try {
|
|
2475
3125
|
while (true) {
|
|
2476
|
-
const { done, value } = await reader.read()
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
const { chunks,
|
|
2480
|
-
buffer = rest;
|
|
3126
|
+
const { done, value } = await reader.read().catch((err) => {
|
|
3127
|
+
throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
|
|
3128
|
+
});
|
|
3129
|
+
const { items: chunks, malformed: malformedLines } = done ? parser.flush() : parser.feed(value);
|
|
2481
3130
|
const malformedWarning = emitMalformedStreamWarning(factory, {
|
|
2482
3131
|
count: malformedLines,
|
|
2483
3132
|
providerLabel: "Ollama",
|
|
@@ -2486,6 +3135,10 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2486
3135
|
if (malformedWarning) yield malformedWarning;
|
|
2487
3136
|
for (const chunk of chunks) {
|
|
2488
3137
|
responseId = chunk.created_at;
|
|
3138
|
+
if (completedEmitted) {
|
|
3139
|
+
if (chunk.done) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
2489
3142
|
const msg = chunk.message;
|
|
2490
3143
|
if (msg.content) {
|
|
2491
3144
|
if (!hasMessageStarted) {
|
|
@@ -2494,10 +3147,10 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2494
3147
|
yield factory.messageStarted(currentMessageId);
|
|
2495
3148
|
}
|
|
2496
3149
|
accumulatedContent += msg.content;
|
|
2497
|
-
yield factory.messageDelta(currentMessageId, msg.content);
|
|
3150
|
+
yield factory.messageDelta(currentMessageId, textBlock(msg.content));
|
|
2498
3151
|
}
|
|
2499
3152
|
if (msg.tool_calls && msg.tool_calls.length > 0) for (const tc of msg.tool_calls) {
|
|
2500
|
-
const tcId = `tc-${
|
|
3153
|
+
const tcId = `ollama-tc-${request.requestId}-${toolCallIndex++}`;
|
|
2501
3154
|
const argsText = JSON.stringify(tc.function.arguments);
|
|
2502
3155
|
pendingToolCalls.push({
|
|
2503
3156
|
id: tcId,
|
|
@@ -2514,14 +3167,15 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2514
3167
|
}
|
|
2515
3168
|
if (hasMessageStarted) {
|
|
2516
3169
|
const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
|
|
2517
|
-
yield factory.messageCompleted(
|
|
3170
|
+
yield factory.messageCompleted(currentMessageId);
|
|
2518
3171
|
if (accumulatedContent) output.push(message);
|
|
2519
3172
|
}
|
|
3173
|
+
if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
|
|
2520
3174
|
for (const pending of pendingToolCalls) {
|
|
2521
3175
|
const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText, pending.argumentsJson);
|
|
2522
3176
|
yield factory.toolCallStarted(pending.id, pending.name);
|
|
2523
3177
|
yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
|
|
2524
|
-
yield factory.toolCallCompleted(
|
|
3178
|
+
yield factory.toolCallCompleted(pending.id);
|
|
2525
3179
|
output.push(toolCall);
|
|
2526
3180
|
}
|
|
2527
3181
|
if (request.include?.usage !== "off" && (chunk.prompt_eval_count !== void 0 || chunk.eval_count !== void 0)) auxiliary.recordUsage(usageFromOllama({
|
|
@@ -2531,67 +3185,42 @@ var OllamaAdapter = class extends AdapterBase {
|
|
|
2531
3185
|
prompt_eval_count: chunk.prompt_eval_count,
|
|
2532
3186
|
eval_count: chunk.eval_count
|
|
2533
3187
|
});
|
|
2534
|
-
|
|
2535
|
-
const replay = replayFromOutput(output);
|
|
2536
|
-
if (accumulatedContent || pendingToolCalls.length > 0) replay.push(opaqueItem("ollama", "replay", {
|
|
2537
|
-
role: "assistant",
|
|
2538
|
-
content: accumulatedContent,
|
|
2539
|
-
tool_calls: pendingToolCalls.map((tc) => ({ function: {
|
|
2540
|
-
name: tc.name,
|
|
2541
|
-
arguments: tc.argumentsJson
|
|
2542
|
-
} }))
|
|
2543
|
-
}));
|
|
2544
|
-
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
2545
|
-
for (const event of auxiliaryResult.events) yield event;
|
|
2546
|
-
yield factory.responseCompleted(this.buildResponse(request, {
|
|
2547
|
-
output,
|
|
2548
|
-
replay,
|
|
2549
|
-
stopReason,
|
|
2550
|
-
usage: auxiliaryResult.usage,
|
|
2551
|
-
billing: auxiliaryResult.billing,
|
|
2552
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
2553
|
-
warnings: auxiliaryResult.warnings,
|
|
2554
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
2555
|
-
rawResponseId: chunk.created_at
|
|
2556
|
-
}, factory));
|
|
3188
|
+
yield* emitCompleted(chunk.done_reason ? mapStopReason(chunk.done_reason) : void 0, chunk.created_at);
|
|
2557
3189
|
accumulatedContent = "";
|
|
2558
3190
|
currentMessageId = "";
|
|
2559
3191
|
hasMessageStarted = false;
|
|
2560
3192
|
pendingToolCalls = [];
|
|
2561
3193
|
}
|
|
2562
3194
|
}
|
|
3195
|
+
if (done) {
|
|
3196
|
+
streamDone = true;
|
|
3197
|
+
break;
|
|
3198
|
+
}
|
|
2563
3199
|
}
|
|
2564
3200
|
} finally {
|
|
2565
|
-
|
|
3201
|
+
try {
|
|
3202
|
+
if (!streamDone) await reader.cancel().catch(() => void 0);
|
|
3203
|
+
} finally {
|
|
3204
|
+
reader.releaseLock();
|
|
3205
|
+
}
|
|
2566
3206
|
}
|
|
2567
|
-
if (
|
|
2568
|
-
if (hasMessageStarted || pendingToolCalls.length > 0) {
|
|
3207
|
+
if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Ollama NDJSON line", "STREAM_ERROR");
|
|
3208
|
+
if (!completedEmitted && (hasMessageStarted || pendingToolCalls.length > 0)) {
|
|
2569
3209
|
yield factory.responseWarning("Stream ended without a done signal", "INCOMPLETE_STREAM");
|
|
2570
3210
|
if (hasMessageStarted) {
|
|
2571
3211
|
const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
|
|
2572
|
-
yield factory.messageCompleted(
|
|
3212
|
+
yield factory.messageCompleted(currentMessageId);
|
|
2573
3213
|
if (accumulatedContent) output.push(message);
|
|
2574
3214
|
}
|
|
3215
|
+
if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
|
|
2575
3216
|
for (const pending of pendingToolCalls) {
|
|
2576
3217
|
const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText, pending.argumentsJson);
|
|
2577
3218
|
yield factory.toolCallStarted(pending.id, pending.name);
|
|
2578
3219
|
yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
|
|
2579
|
-
yield factory.toolCallCompleted(
|
|
3220
|
+
yield factory.toolCallCompleted(pending.id);
|
|
2580
3221
|
output.push(toolCall);
|
|
2581
3222
|
}
|
|
2582
|
-
|
|
2583
|
-
const auxiliaryResult = await auxiliary.finalize(factory);
|
|
2584
|
-
for (const event of auxiliaryResult.events) yield event;
|
|
2585
|
-
yield factory.responseCompleted(this.buildResponse(request, {
|
|
2586
|
-
output,
|
|
2587
|
-
replay,
|
|
2588
|
-
usage: auxiliaryResult.usage,
|
|
2589
|
-
billing: auxiliaryResult.billing,
|
|
2590
|
-
auxiliary: auxiliaryResult.auxiliary,
|
|
2591
|
-
warnings: auxiliaryResult.warnings,
|
|
2592
|
-
metadataSources: auxiliaryResult.metadataSources,
|
|
2593
|
-
rawResponseId: responseId
|
|
2594
|
-
}, factory));
|
|
3223
|
+
yield* emitCompleted(void 0, responseId);
|
|
2595
3224
|
}
|
|
2596
3225
|
}
|
|
2597
3226
|
};
|
|
@@ -2626,7 +3255,18 @@ function assertMockRequest(request, expectation, context) {
|
|
|
2626
3255
|
}
|
|
2627
3256
|
var MockAdapter = class extends AdapterBase {
|
|
2628
3257
|
kind = "mock";
|
|
2629
|
-
|
|
3258
|
+
capabilities = {
|
|
3259
|
+
textStreaming: "synthetic",
|
|
3260
|
+
reasoningStreaming: "synthetic",
|
|
3261
|
+
toolCallStreaming: "synthetic",
|
|
3262
|
+
replay: "canonical",
|
|
3263
|
+
usage: "final",
|
|
3264
|
+
toolResultOutcomes: [
|
|
3265
|
+
"success",
|
|
3266
|
+
"error",
|
|
3267
|
+
"rejected"
|
|
3268
|
+
]
|
|
3269
|
+
};
|
|
2630
3270
|
handler;
|
|
2631
3271
|
providerMetadata;
|
|
2632
3272
|
cursor = 0;
|
|
@@ -2698,18 +3338,34 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2698
3338
|
break;
|
|
2699
3339
|
}
|
|
2700
3340
|
case "complete": {
|
|
2701
|
-
const
|
|
2702
|
-
yield factory.responseCompleted(
|
|
3341
|
+
const finalResponse = this.finalizeTurn(request, factory, mockRequest, output, step, stepCount);
|
|
3342
|
+
yield factory.responseCompleted({
|
|
3343
|
+
replay: finalResponse.replay,
|
|
3344
|
+
stopReason: finalResponse.stopReason,
|
|
3345
|
+
trace: finalResponse.backend,
|
|
3346
|
+
usage: finalResponse.usage,
|
|
3347
|
+
billing: finalResponse.billing,
|
|
3348
|
+
auxiliary: finalResponse.auxiliary,
|
|
3349
|
+
warnings: finalResponse.warnings
|
|
3350
|
+
});
|
|
2703
3351
|
return;
|
|
2704
3352
|
}
|
|
2705
3353
|
case "error": {
|
|
2706
3354
|
yield factory.responseWarning(step.message, step.code);
|
|
2707
|
-
const
|
|
3355
|
+
const finalResponse = this.finalizeTurn(request, factory, mockRequest, output, {
|
|
2708
3356
|
type: "complete",
|
|
2709
3357
|
stopReason: step.stopReason ?? "error",
|
|
2710
3358
|
providerMetadata: step.providerMetadata
|
|
2711
3359
|
}, stepCount);
|
|
2712
|
-
yield factory.responseCompleted(
|
|
3360
|
+
yield factory.responseCompleted({
|
|
3361
|
+
replay: finalResponse.replay,
|
|
3362
|
+
stopReason: finalResponse.stopReason,
|
|
3363
|
+
trace: finalResponse.backend,
|
|
3364
|
+
usage: finalResponse.usage,
|
|
3365
|
+
billing: finalResponse.billing,
|
|
3366
|
+
auxiliary: finalResponse.auxiliary,
|
|
3367
|
+
warnings: finalResponse.warnings
|
|
3368
|
+
});
|
|
2713
3369
|
return;
|
|
2714
3370
|
}
|
|
2715
3371
|
case "interrupt":
|
|
@@ -2718,8 +3374,16 @@ var MockAdapter = class extends AdapterBase {
|
|
|
2718
3374
|
case "throw": throw typeof step.error === "string" ? new Error(step.error) : step.error;
|
|
2719
3375
|
}
|
|
2720
3376
|
}
|
|
2721
|
-
const
|
|
2722
|
-
yield factory.responseCompleted(
|
|
3377
|
+
const finalResponse = this.finalizeTurn(request, factory, mockRequest, output, { type: "complete" }, stepCount);
|
|
3378
|
+
yield factory.responseCompleted({
|
|
3379
|
+
replay: finalResponse.replay,
|
|
3380
|
+
stopReason: finalResponse.stopReason,
|
|
3381
|
+
trace: finalResponse.backend,
|
|
3382
|
+
usage: finalResponse.usage,
|
|
3383
|
+
billing: finalResponse.billing,
|
|
3384
|
+
auxiliary: finalResponse.auxiliary,
|
|
3385
|
+
warnings: finalResponse.warnings
|
|
3386
|
+
});
|
|
2723
3387
|
} finally {
|
|
2724
3388
|
this.activeStream = false;
|
|
2725
3389
|
}
|
|
@@ -2847,10 +3511,11 @@ async function* emitMessage(factory, item, stream) {
|
|
|
2847
3511
|
let chunkIndex = 0;
|
|
2848
3512
|
for (const block of item.content) if (block.type === "text") for (const chunk of chunkText(block.text, stream)) {
|
|
2849
3513
|
await delayForChunk(stream, chunkIndex, chunk.length);
|
|
2850
|
-
yield factory.messageDelta(item.id, chunk);
|
|
3514
|
+
yield factory.messageDelta(item.id, textBlock(chunk));
|
|
2851
3515
|
chunkIndex += 1;
|
|
2852
3516
|
}
|
|
2853
|
-
yield factory.
|
|
3517
|
+
else yield factory.messageDelta(item.id, block);
|
|
3518
|
+
yield factory.messageCompleted(item.id);
|
|
2854
3519
|
}
|
|
2855
3520
|
async function* emitReasoning(factory, item, stream) {
|
|
2856
3521
|
if (!item.id) throw new AIRequestError("Mock reasoning output requires an id after normalization", "MOCK_REASONING_ID_MISSING");
|
|
@@ -2867,7 +3532,7 @@ async function* emitReasoning(factory, item, stream) {
|
|
|
2867
3532
|
chunkIndex += 1;
|
|
2868
3533
|
}
|
|
2869
3534
|
}
|
|
2870
|
-
yield factory.reasoningCompleted(item);
|
|
3535
|
+
yield factory.reasoningCompleted(item.id);
|
|
2871
3536
|
}
|
|
2872
3537
|
async function* emitToolCall(factory, item, streamArguments, stream) {
|
|
2873
3538
|
yield factory.toolCallStarted(item.id, item.name);
|
|
@@ -2879,7 +3544,7 @@ async function* emitToolCall(factory, item, streamArguments, stream) {
|
|
|
2879
3544
|
chunkIndex += 1;
|
|
2880
3545
|
}
|
|
2881
3546
|
}
|
|
2882
|
-
yield factory.toolCallCompleted(item);
|
|
3547
|
+
yield factory.toolCallCompleted(item.id);
|
|
2883
3548
|
}
|
|
2884
3549
|
function resolveStepStreamOptions(defaults, override, label) {
|
|
2885
3550
|
if (override === false) return;
|
|
@@ -2990,108 +3655,6 @@ function cloneItem(item) {
|
|
|
2990
3655
|
return structuredClone(item);
|
|
2991
3656
|
}
|
|
2992
3657
|
//#endregion
|
|
2993
|
-
|
|
2994
|
-
/**
|
|
2995
|
-
* 模拟流式 (Synthetic Streaming)
|
|
2996
|
-
*
|
|
2997
|
-
* 将一组已解析的 canonical OutputItem 包装为规范事件流。
|
|
2998
|
-
* 适用于非原生流式后端:adapter 拿到完整响应后,调用此函数
|
|
2999
|
-
* 即可产出一致的事件序列,无需自己逐事件组装。
|
|
3000
|
-
*
|
|
3001
|
-
* 约束:
|
|
3002
|
-
* - 每个 item 只发一块完整 delta(不模拟逐 token)
|
|
3003
|
-
* - 保持 item 边界
|
|
3004
|
-
* - 保持后端原始顺序
|
|
3005
|
-
* - 不发明 reasoning
|
|
3006
|
-
* - 不改写工具参数
|
|
3007
|
-
*/
|
|
3008
|
-
/**
|
|
3009
|
-
* 将已解析的 output items 包装为完整规范事件流。
|
|
3010
|
-
*
|
|
3011
|
-
* 用法示例(在 adapter 的 runStream 中):
|
|
3012
|
-
* ```ts
|
|
3013
|
-
* const result = parseNonStreamingResponse(data);
|
|
3014
|
-
* yield* syntheticStream({
|
|
3015
|
-
* model: request.model,
|
|
3016
|
-
* responseId: request.requestId,
|
|
3017
|
-
* backend: { kind: "chat-completions" },
|
|
3018
|
-
* output: result.output,
|
|
3019
|
-
* stopReason: result.stopReason,
|
|
3020
|
-
* usage: result.usage,
|
|
3021
|
-
* });
|
|
3022
|
-
* ```
|
|
3023
|
-
*/
|
|
3024
|
-
async function* syntheticStream(options) {
|
|
3025
|
-
const { model, responseId, backend, output, replay, stopReason, usage, billing, providerMetadata, rawResponseId, warnings: extraWarnings } = options;
|
|
3026
|
-
const factory = createEventFactory({
|
|
3027
|
-
responseId,
|
|
3028
|
-
backend: {
|
|
3029
|
-
kind: backend.kind,
|
|
3030
|
-
isSynthetic: true
|
|
3031
|
-
}
|
|
3032
|
-
});
|
|
3033
|
-
yield factory.responseStarted(model);
|
|
3034
|
-
for (const item of output) yield* emitItemEvents(item, factory);
|
|
3035
|
-
if (usage || billing) yield factory.responseAuxiliary({
|
|
3036
|
-
usage,
|
|
3037
|
-
billing
|
|
3038
|
-
});
|
|
3039
|
-
const finalReplay = replay ?? replayFromOutput(output);
|
|
3040
|
-
const allWarnings = [];
|
|
3041
|
-
allWarnings.push("Response is synthetically streamed; delta granularity may differ from native streaming");
|
|
3042
|
-
if (extraWarnings) allWarnings.push(...extraWarnings);
|
|
3043
|
-
const response = {
|
|
3044
|
-
id: responseId,
|
|
3045
|
-
output,
|
|
3046
|
-
replay: finalReplay,
|
|
3047
|
-
text: extractText(output),
|
|
3048
|
-
toolCalls: output.filter((item) => item.type === "tool_call"),
|
|
3049
|
-
stopReason,
|
|
3050
|
-
usage,
|
|
3051
|
-
billing,
|
|
3052
|
-
auxiliary: providerMetadata ? { providerMetadata } : void 0,
|
|
3053
|
-
warnings: allWarnings.length > 0 ? allWarnings : void 0,
|
|
3054
|
-
backend: {
|
|
3055
|
-
requestId: responseId,
|
|
3056
|
-
rawResponseId,
|
|
3057
|
-
adapter: backend.kind,
|
|
3058
|
-
isSyntheticStream: true
|
|
3059
|
-
}
|
|
3060
|
-
};
|
|
3061
|
-
yield factory.responseCompleted(response);
|
|
3062
|
-
}
|
|
3063
|
-
function* emitItemEvents(item, factory) {
|
|
3064
|
-
switch (item.type) {
|
|
3065
|
-
case "message":
|
|
3066
|
-
yield* emitMessageEvents(item, factory);
|
|
3067
|
-
break;
|
|
3068
|
-
case "reasoning":
|
|
3069
|
-
yield* emitReasoningEvents(item, factory);
|
|
3070
|
-
break;
|
|
3071
|
-
case "tool_call":
|
|
3072
|
-
yield* emitToolCallEvents(item, factory);
|
|
3073
|
-
break;
|
|
3074
|
-
case "opaque": break;
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
function* emitMessageEvents(item, factory) {
|
|
3078
|
-
const id = item.id ?? `syn-msg-${crypto.randomUUID()}`;
|
|
3079
|
-
yield factory.messageStarted(id);
|
|
3080
|
-
for (const block of item.content) if (block.type === "text") yield factory.messageDelta(id, block.text);
|
|
3081
|
-
yield factory.messageCompleted(item);
|
|
3082
|
-
}
|
|
3083
|
-
function* emitReasoningEvents(item, factory) {
|
|
3084
|
-
const id = item.id ?? `syn-reason-${crypto.randomUUID()}`;
|
|
3085
|
-
yield factory.reasoningStarted(id, item.visibility);
|
|
3086
|
-
for (const block of item.content) if (block.type === "text") yield factory.reasoningDelta(id, block);
|
|
3087
|
-
yield factory.reasoningCompleted(item);
|
|
3088
|
-
}
|
|
3089
|
-
function* emitToolCallEvents(item, factory) {
|
|
3090
|
-
yield factory.toolCallStarted(item.id, item.name);
|
|
3091
|
-
if (item.argumentsText) yield factory.toolCallDelta(item.id, { argumentsText: item.argumentsText });
|
|
3092
|
-
yield factory.toolCallCompleted(item);
|
|
3093
|
-
}
|
|
3094
|
-
//#endregion
|
|
3095
|
-
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, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateRequest, withMockStreaming };
|
|
3658
|
+
export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, IncrementalStreamParser, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, MessagesAdapter, MockAdapter, NormalizedRequestMapper, OllamaAdapter, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, ResponsesAdapter, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
|
|
3096
3659
|
|
|
3097
3660
|
//# sourceMappingURL=index.mjs.map
|