agent-accelerator 0.5.5 → 0.5.7
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 +16 -17
- package/package.json +1 -1
- package/src/agent/agent.ts +16 -18
- package/src/agent/architecture.md +25 -0
- package/src/agent/context.ts +4 -0
- package/src/agent/delegation.ts +18 -84
- package/src/agent/loop.ts +14 -16
- package/src/agent/worker.ts +1 -3
- package/src/architecture.md +28 -0
- package/src/index.ts +6 -73
- package/src/models/architecture.md +14 -0
- package/src/models/catalog-cache.ts +3 -0
- package/src/models/catalog.ts +7 -0
- package/src/providers/architecture.md +24 -0
- package/src/providers/google.ts +26 -39
- package/src/providers/openai-compat.ts +53 -104
- package/src/providers/openai.ts +47 -104
- package/src/providers/openrouter.ts +114 -175
- package/src/providers/registry.ts +1 -0
- package/src/providers/shared.ts +134 -5
- package/src/providers.ts +30 -59
- package/src/scheduler/architecture.md +19 -0
- package/src/scheduler/scheduler.ts +21 -0
- package/src/scheduler/store.ts +6 -0
- package/src/scheduler/time.ts +2 -0
- package/src/session/architecture.md +19 -0
- package/src/session/store.ts +18 -3
- package/src/session/usage.ts +1 -0
- package/src/streaming/architecture.md +13 -0
- package/src/streaming/event-stream.ts +5 -0
- package/src/telemetry.ts +1 -0
- package/src/tools/architecture.md +19 -0
- package/src/tools/executor.ts +13 -0
- package/src/tools/schema.ts +1 -0
- package/src/tools/subagent-result.ts +1 -0
- package/src/types/architecture.md +20 -0
- package/src/types/message.ts +1 -1
- package/src/utils/architecture.md +25 -0
- package/src/utils/base64.ts +2 -0
- package/src/utils/cache.ts +2 -0
- package/src/utils/errors.ts +8 -1
- package/src/utils/headers.ts +4 -1
- package/src/utils/media.ts +11 -1
- package/src/utils/raw-capture.ts +3 -0
- package/src/utils/retry.ts +1 -0
- package/src/utils/safe-io.ts +4 -0
- package/src/utils/session.ts +5 -161
|
@@ -52,7 +52,6 @@ import type { ProviderContext, ContentPart } from "../types/message.ts";
|
|
|
52
52
|
import type { StandardToolDeclaration, ToolCallRecord } from "../types/tool.ts";
|
|
53
53
|
import type { TokenUsage, ThinkingConfig } from "../types/core.ts";
|
|
54
54
|
import { AssistantMessageEventStream } from "../streaming/event-stream.ts";
|
|
55
|
-
import { SSEParser } from "../streaming/sse-parser.ts";
|
|
56
55
|
import { AgentResponse } from "../types/response.ts";
|
|
57
56
|
import { getApiKey, getEnv } from "../utils/env.ts";
|
|
58
57
|
import { buildSessionHeaders, setHeaderCaseInsensitive, shouldEchoSignature } from "../utils/headers.ts";
|
|
@@ -68,7 +67,14 @@ import {
|
|
|
68
67
|
redactedHeaders,
|
|
69
68
|
readErrorPayload,
|
|
70
69
|
incompleteStreamError,
|
|
71
|
-
|
|
70
|
+
hasUnfinishedToolCall,
|
|
71
|
+
linkStreamAbort,
|
|
72
|
+
withAbortTimer,
|
|
73
|
+
readSseData,
|
|
74
|
+
incompleteStreamCheck,
|
|
75
|
+
failStream,
|
|
76
|
+
toolArgumentsJson,
|
|
77
|
+
toolTurnFinishReason,
|
|
72
78
|
combinedSignal,
|
|
73
79
|
timeoutFor,
|
|
74
80
|
hasAuthHeader,
|
|
@@ -186,10 +192,12 @@ interface ChatResponse {
|
|
|
186
192
|
// Request building (canonical -> Chat Completions)
|
|
187
193
|
// ---------------------------------------------------------------------------
|
|
188
194
|
|
|
195
|
+
// Env-variable prefix of a custom provider (`my-llm` becomes `MY_LLM`); in: key and URL lookups, out: the prefix.
|
|
189
196
|
function envPrefixOf(prefix: string): string {
|
|
190
197
|
return prefix.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
191
198
|
}
|
|
192
199
|
|
|
200
|
+
// Endpoint from the option, the provider's configuration or {PREFIX}_BASE_URL; throws ConfigError when none (never another vendor's URL); in: each request, out: the base URL.
|
|
193
201
|
function resolveBaseUrl(
|
|
194
202
|
prefix: string,
|
|
195
203
|
configuredBaseUrl: string | undefined,
|
|
@@ -215,6 +223,7 @@ function resolveBaseUrl(
|
|
|
215
223
|
return resolved.replace(/\/+$/, "");
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
// Key from the option, the provider's configuration or {PREFIX}_API_KEY, or `unused` when custom headers carry the auth; in: each request, out: the key or undefined.
|
|
218
227
|
function resolveApiKey(
|
|
219
228
|
prefix: string,
|
|
220
229
|
configuredApiKey: string | undefined,
|
|
@@ -276,6 +285,7 @@ function cleanModelId(prefix: string, model: string | ModelSpec): string {
|
|
|
276
285
|
return rawId.replace(new RegExp(`^${escaped}/`, "i"), "");
|
|
277
286
|
}
|
|
278
287
|
|
|
288
|
+
// Canonical tool declarations as Chat Completions function tools; in: options.tools, out: request `tools`.
|
|
279
289
|
function toCompatTools(tools?: StandardToolDeclaration[]): Array<Record<string, unknown>> | undefined {
|
|
280
290
|
if (!tools || tools.length === 0) return undefined;
|
|
281
291
|
return tools.map((t) => ({
|
|
@@ -303,6 +313,7 @@ function warnThinkingDropped(prefix: string, thinking: ThinkingConfig | undefine
|
|
|
303
313
|
});
|
|
304
314
|
}
|
|
305
315
|
|
|
316
|
+
// `wav` or `mp3`, the only formats Chat audio input takes here; in: contentPartsToBlocks, out: input_audio.format.
|
|
306
317
|
function audioFormatFor(mimeType?: string): string {
|
|
307
318
|
const mime = (mimeType || "").toLowerCase();
|
|
308
319
|
if (mime.includes("wav")) return "wav";
|
|
@@ -348,6 +359,7 @@ function assertNoVideoOrFileParts(
|
|
|
348
359
|
throw err;
|
|
349
360
|
}
|
|
350
361
|
|
|
362
|
+
// Text, image (link or data URL) and audio parts as Chat content blocks; in: a user message's parts, out: fullHistoryMessages.
|
|
351
363
|
async function contentPartsToBlocks(parts: ContentPart[]): Promise<ChatContentPart[]> {
|
|
352
364
|
const blocks: ChatContentPart[] = [];
|
|
353
365
|
for (const part of parts) {
|
|
@@ -382,6 +394,7 @@ async function contentPartsToBlocks(parts: ContentPart[]): Promise<ChatContentPa
|
|
|
382
394
|
return blocks;
|
|
383
395
|
}
|
|
384
396
|
|
|
397
|
+
// A tool result as the text of a tool message; in: fullHistoryMessages, out: a string.
|
|
385
398
|
function resultToOutput(result: unknown): string {
|
|
386
399
|
if (typeof result === "string") return result;
|
|
387
400
|
return safeStringify(result);
|
|
@@ -450,7 +463,7 @@ async function fullHistoryMessages(context: ProviderContext): Promise<ChatMessag
|
|
|
450
463
|
} = {
|
|
451
464
|
id: clampToolCallId(part.callId || part.id),
|
|
452
465
|
type: "function",
|
|
453
|
-
function: { name: part.name, arguments:
|
|
466
|
+
function: { name: part.name, arguments: toolArgumentsJson(part.arguments) },
|
|
454
467
|
};
|
|
455
468
|
// Echo provider-issued thought signatures verbatim so endpoints
|
|
456
469
|
// that minted them (Gemini behind a compat proxy) keep working.
|
|
@@ -496,6 +509,7 @@ async function fullHistoryMessages(context: ProviderContext): Promise<ChatMessag
|
|
|
496
509
|
// Response mapping (Chat Completions -> canonical)
|
|
497
510
|
// ---------------------------------------------------------------------------
|
|
498
511
|
|
|
512
|
+
// Chat usage as TokenUsage, clamping cache reads to input; in: a reply or the last stream chunk, out: TokenUsage.
|
|
499
513
|
function mapUsage(raw?: ChatResponse["usage"]): TokenUsage {
|
|
500
514
|
const input = raw?.prompt_tokens ?? 0;
|
|
501
515
|
const output = raw?.completion_tokens ?? 0;
|
|
@@ -511,12 +525,14 @@ function mapUsage(raw?: ChatResponse["usage"]): TokenUsage {
|
|
|
511
525
|
cacheWriteTokens: raw?.prompt_tokens_details?.cache_write_tokens ?? 0,
|
|
512
526
|
thinkingTokens: raw?.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
513
527
|
};
|
|
528
|
+
// `cost` is an OpenRouter extension. A generic endpoint that sends it may use 0 for "not computed", so only a positive value counts as known (OpenRouter's adapter accepts an explicit 0).
|
|
514
529
|
if (typeof raw?.cost === "number" && raw.cost > 0) {
|
|
515
530
|
usage.cost = { totalCost: raw.cost };
|
|
516
531
|
}
|
|
517
532
|
return usage;
|
|
518
533
|
}
|
|
519
534
|
|
|
535
|
+
// The Gemini thought signature a proxy put on a tool call, if any; in: parseResponse, out: a string or undefined.
|
|
520
536
|
function extractSignature(rawTc: {
|
|
521
537
|
extra_content?: { google?: { thought_signature?: string } };
|
|
522
538
|
}): string | undefined {
|
|
@@ -530,6 +546,7 @@ function withCompatSignature(sig: string | undefined): { thoughtSignature?: stri
|
|
|
530
546
|
return { thoughtSignature: sig, thoughtSignatureProvider: "google" };
|
|
531
547
|
}
|
|
532
548
|
|
|
549
|
+
// Throws a concise error for an HTTP 200 reply that carries only `error`; in: parseResponse, out: a thrown error.
|
|
533
550
|
function throwResponseError(
|
|
534
551
|
response: ChatResponse,
|
|
535
552
|
prefix: string,
|
|
@@ -544,6 +561,7 @@ function throwResponseError(
|
|
|
544
561
|
throw toConciseProviderError(failure, prefix, modelId);
|
|
545
562
|
}
|
|
546
563
|
|
|
564
|
+
// Turns a Chat Completions JSON reply into text, thinking, tool calls and usage; in: generate, out: ProviderGenerateResult.
|
|
547
565
|
function parseResponse(
|
|
548
566
|
response: ChatResponse,
|
|
549
567
|
prefix: string,
|
|
@@ -658,6 +676,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
658
676
|
this.configuredApiKey = opts?.apiKey;
|
|
659
677
|
}
|
|
660
678
|
|
|
679
|
+
// The catalog spec of a model, else a permissive generic one; in: the registry, out: a ModelSpec.
|
|
661
680
|
getModel(modelId: string): ModelSpec | undefined {
|
|
662
681
|
const clean = cleanModelId(this.prefix, modelId);
|
|
663
682
|
return (
|
|
@@ -668,14 +687,17 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
668
687
|
);
|
|
669
688
|
}
|
|
670
689
|
|
|
690
|
+
// This provider's endpoint for one request; in: generate/stream, out: resolveBaseUrl.
|
|
671
691
|
private baseUrl(options?: ProviderRequestOptions): string {
|
|
672
692
|
return resolveBaseUrl(this.prefix, this.configuredBaseUrl, options);
|
|
673
693
|
}
|
|
674
694
|
|
|
695
|
+
// This provider's key for one request; in: requireApiKey, out: resolveApiKey.
|
|
675
696
|
private apiKey(options?: ProviderRequestOptions): string | undefined {
|
|
676
697
|
return resolveApiKey(this.prefix, this.configuredApiKey, options);
|
|
677
698
|
}
|
|
678
699
|
|
|
700
|
+
// The key, where a missing one only throws for non-local endpoints (loopback and LAN may omit it); in: generate/stream, out: the key or undefined.
|
|
679
701
|
private requireApiKey(baseUrl: string, options?: ProviderRequestOptions): string | undefined {
|
|
680
702
|
const key = this.apiKey(options);
|
|
681
703
|
if (!key && !isLocalEndpoint(baseUrl)) {
|
|
@@ -686,6 +708,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
686
708
|
return key;
|
|
687
709
|
}
|
|
688
710
|
|
|
711
|
+
// Builds the Chat request (messages, tools, tool_choice, generation controls, response_format, stream usage) and warns+drops what a strict endpoint cannot take; in: generate/stream, out: the body.
|
|
689
712
|
private async buildBody(
|
|
690
713
|
modelId: string,
|
|
691
714
|
context: ProviderContext,
|
|
@@ -741,7 +764,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
741
764
|
const compatTools = toCompatTools(tools);
|
|
742
765
|
if (compatTools) body.tools = compatTools;
|
|
743
766
|
// Standard Chat Completions generation fields, only when the caller set them.
|
|
744
|
-
Object.assign(body, mapGenerationParamsToChat(options));
|
|
767
|
+
Object.assign(body, mapGenerationParamsToChat(options, this.prefix, `${this.prefix}/${modelId}`));
|
|
745
768
|
const toolChoice = mapToolChoiceToOpenAI(
|
|
746
769
|
options?.toolChoice as "auto" | "none" | "required" | { type: "function"; function: { name: string } } | undefined
|
|
747
770
|
);
|
|
@@ -782,6 +805,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
782
805
|
return body;
|
|
783
806
|
}
|
|
784
807
|
|
|
808
|
+
// Fetch options with session-affinity headers (unless opted out), JSON content type and the Bearer key; in: doFetch and stream, out: RequestInit.
|
|
785
809
|
private requestInit(
|
|
786
810
|
body: ChatRequestBody,
|
|
787
811
|
options: ProviderRequestOptions | undefined,
|
|
@@ -817,6 +841,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
817
841
|
return { method: "POST", headers, body: JSON.stringify(body), signal };
|
|
818
842
|
}
|
|
819
843
|
|
|
844
|
+
// One HTTP call under the request timeout, returning status, headers and body text; in: generate, out: the raw reply.
|
|
820
845
|
private async doFetch(
|
|
821
846
|
url: string,
|
|
822
847
|
body: ChatRequestBody,
|
|
@@ -839,6 +864,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
839
864
|
}
|
|
840
865
|
}
|
|
841
866
|
|
|
867
|
+
// Throws a concise provider error (status, code, Retry-After, key hint) for a non-2xx reply; in: generate and stream, out: nothing or a thrown error.
|
|
842
868
|
private throwIfError(
|
|
843
869
|
status: number,
|
|
844
870
|
url: string,
|
|
@@ -867,6 +893,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
867
893
|
throw toConciseProviderError(err, this.prefix, modelId);
|
|
868
894
|
}
|
|
869
895
|
|
|
896
|
+
// Non-streaming turn as one retried JSON request; in: runAgentLoop, out: ProviderGenerateResult.
|
|
870
897
|
async generate(
|
|
871
898
|
model: string | ModelSpec,
|
|
872
899
|
context: ProviderContext,
|
|
@@ -939,6 +966,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
939
966
|
}
|
|
940
967
|
}
|
|
941
968
|
|
|
969
|
+
// Streaming turn over SSE: assembles text, reasoning, tool calls and usage, retrying only before the first byte; in: streamAgentLoop, out: an AssistantMessageEventStream.
|
|
942
970
|
stream(
|
|
943
971
|
model: string | ModelSpec,
|
|
944
972
|
context: ProviderContext,
|
|
@@ -949,23 +977,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
949
977
|
const prefix = this.prefix;
|
|
950
978
|
const clean = cleanModelId(this.prefix, model);
|
|
951
979
|
|
|
952
|
-
const linked =
|
|
953
|
-
const forwardUserAbort = () => {
|
|
954
|
-
try {
|
|
955
|
-
linked.abort((options?.signal as { reason?: unknown })?.reason);
|
|
956
|
-
} catch {
|
|
957
|
-
try {
|
|
958
|
-
linked.abort();
|
|
959
|
-
} catch {}
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
if (options?.signal?.aborted) forwardUserAbort();
|
|
963
|
-
else options?.signal?.addEventListener("abort", forwardUserAbort, { once: true });
|
|
964
|
-
const removeStreamCancel = eventStream.onCancel(() => {
|
|
965
|
-
try {
|
|
966
|
-
linked.abort();
|
|
967
|
-
} catch {}
|
|
968
|
-
});
|
|
980
|
+
const { linked, release } = linkStreamAbort(eventStream, options?.signal);
|
|
969
981
|
|
|
970
982
|
(async () => {
|
|
971
983
|
try {
|
|
@@ -994,21 +1006,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
994
1006
|
// Transient failures (429/5xx/network) are retried here, before the
|
|
995
1007
|
// first byte; once the stream is live it is never retried.
|
|
996
1008
|
const res = await openStreamResponse({
|
|
997
|
-
fetchOnce:
|
|
998
|
-
const timer =
|
|
999
|
-
streamTimeoutMs && streamTimeoutMs > 0
|
|
1000
|
-
? setTimeout(() => {
|
|
1001
|
-
try {
|
|
1002
|
-
linked.abort();
|
|
1003
|
-
} catch {}
|
|
1004
|
-
}, streamTimeoutMs)
|
|
1005
|
-
: undefined;
|
|
1006
|
-
try {
|
|
1007
|
-
return await (options?.fetch ?? fetch)(url, this.requestInit(body, options, apiKey, sessionId, linked.signal));
|
|
1008
|
-
} finally {
|
|
1009
|
-
if (timer) clearTimeout(timer);
|
|
1010
|
-
}
|
|
1011
|
-
},
|
|
1009
|
+
fetchOnce: () => withAbortTimer(linked, streamTimeoutMs, () => (options?.fetch ?? fetch)(url, this.requestInit(body, options, apiKey, sessionId, linked.signal))),
|
|
1012
1010
|
onHttpError: (r, text) => this.throwIfError(r.status, url, text, clean, headersToRecord(r.headers)),
|
|
1013
1011
|
noBodyError: () => toConciseProviderError(new Error("OpenAI-compatible streaming response had no body"), prefix, clean),
|
|
1014
1012
|
retry: retryOptionsFrom(options),
|
|
@@ -1022,9 +1020,6 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1022
1020
|
const responseMeta: Record<string, unknown> & { status: number; statusText: string; headers: Record<string, string> } = { status: res.status, statusText: res.statusText, headers: responseHeaders };
|
|
1023
1021
|
eventStream.push({ type: "start", raw: { request: rawRequest } } as never);
|
|
1024
1022
|
|
|
1025
|
-
const parser = new SSEParser();
|
|
1026
|
-
const reader = res.body!.getReader(); // openStreamResponse guarantees a body
|
|
1027
|
-
const decoder = new TextDecoder();
|
|
1028
1023
|
let text = "";
|
|
1029
1024
|
let thinking = "";
|
|
1030
1025
|
// Tool calls keyed by choice index: { id, name, startArgs, deltaArgs }.
|
|
@@ -1040,18 +1035,6 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1040
1035
|
let framesSeen = 0;
|
|
1041
1036
|
let responseId: string | undefined;
|
|
1042
1037
|
let completedBody: unknown = undefined;
|
|
1043
|
-
let aborted = false;
|
|
1044
|
-
linked.signal.addEventListener(
|
|
1045
|
-
"abort",
|
|
1046
|
-
() => {
|
|
1047
|
-
aborted = true;
|
|
1048
|
-
try {
|
|
1049
|
-
void reader.cancel();
|
|
1050
|
-
} catch {}
|
|
1051
|
-
},
|
|
1052
|
-
{ once: true }
|
|
1053
|
-
);
|
|
1054
|
-
|
|
1055
1038
|
const handleMessage = (data: string): void => {
|
|
1056
1039
|
if (!data) return;
|
|
1057
1040
|
if (data === "[DONE]") {
|
|
@@ -1176,26 +1159,7 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1176
1159
|
}
|
|
1177
1160
|
};
|
|
1178
1161
|
|
|
1179
|
-
|
|
1180
|
-
if (linked.signal.aborted || eventStream.isCancelled()) {
|
|
1181
|
-
try {
|
|
1182
|
-
await reader.cancel();
|
|
1183
|
-
} catch {}
|
|
1184
|
-
throw Object.assign(new Error("Stream aborted"), { name: "AbortError" });
|
|
1185
|
-
}
|
|
1186
|
-
const { done, value } = await reader.read();
|
|
1187
|
-
if (done) break;
|
|
1188
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
1189
|
-
for (const m of parser.feed(chunk)) handleMessage(m.data);
|
|
1190
|
-
}
|
|
1191
|
-
for (const m of parser.flush()) handleMessage(m.data);
|
|
1192
|
-
try {
|
|
1193
|
-
reader.releaseLock();
|
|
1194
|
-
} catch {}
|
|
1195
|
-
|
|
1196
|
-
if (linked.signal.aborted || eventStream.isCancelled() || aborted) {
|
|
1197
|
-
throw Object.assign(new Error("Stream aborted"), { name: "AbortError" });
|
|
1198
|
-
}
|
|
1162
|
+
await readSseData(res.body!, linked, eventStream, handleMessage); // openStreamResponse guarantees a body
|
|
1199
1163
|
|
|
1200
1164
|
const toolCalls: ToolCallRecord[] = [];
|
|
1201
1165
|
for (const [index, c] of calls) {
|
|
@@ -1212,9 +1176,11 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1212
1176
|
eventStream.push({ type: "tool_call_complete", toolCall: record });
|
|
1213
1177
|
}
|
|
1214
1178
|
|
|
1179
|
+
// Did the stream really end? Read before the tool-call and refusal overrides below, which also set sawFinishReason.
|
|
1180
|
+
const hasTerminal = completedBody !== undefined || sawFinishReason || sawUsage;
|
|
1215
1181
|
noteProviderTurn(sessionId, prefix);
|
|
1216
1182
|
if (toolCalls.length > 0) {
|
|
1217
|
-
finishReason =
|
|
1183
|
+
finishReason = toolTurnFinishReason(finishReason, toolCalls);
|
|
1218
1184
|
sawFinishReason = true;
|
|
1219
1185
|
} else if (refusalSeen) {
|
|
1220
1186
|
finishReason = "refusal";
|
|
@@ -1224,26 +1190,21 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1224
1190
|
// Q-23: terminal-state guard — fail `incomplete_stream` (retryable)
|
|
1225
1191
|
// instead of resolving empty success. Skipped for non-SSE payloads
|
|
1226
1192
|
// (JSON mocks) where no SSE frames were ever seen.
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
// api/openai-responses-shared.ts:766 rule). Non-SSE mocks preserve legacy.
|
|
1241
|
-
if (!hasTerminal && toolCalls.some((c) => hasTruncatedToolArguments(c.arguments)) && isSSEPayload) {
|
|
1242
|
-
eventStream.fail(incompleteStreamError(prefix, clean));
|
|
1193
|
+
const cleanThinking = thinking.replace(/\n{3,}/g, "\n\n").trim();
|
|
1194
|
+
const incomplete = incompleteStreamCheck({
|
|
1195
|
+
provider: prefix,
|
|
1196
|
+
modelId: clean,
|
|
1197
|
+
hasTerminal,
|
|
1198
|
+
empty: !text && !cleanThinking && toolCalls.length === 0,
|
|
1199
|
+
unfinishedCall: hasUnfinishedToolCall(calls.values(), toolCalls),
|
|
1200
|
+
responseHeaders,
|
|
1201
|
+
framesSeen,
|
|
1202
|
+
badFrames,
|
|
1203
|
+
});
|
|
1204
|
+
if (incomplete) {
|
|
1205
|
+
eventStream.fail(incomplete);
|
|
1243
1206
|
return;
|
|
1244
1207
|
}
|
|
1245
|
-
|
|
1246
|
-
const cleanThinking = cleanThinkingEarly;
|
|
1247
1208
|
const finalThoughtSignature = toolCalls.map((c) => c.thoughtSignature).find(Boolean);
|
|
1248
1209
|
const finalResponse = new AgentResponse({
|
|
1249
1210
|
text,
|
|
@@ -1264,21 +1225,9 @@ export class OpenAICompatibleChatProvider implements Provider {
|
|
|
1264
1225
|
eventStream.push({ type: "done", delta: "", usage, finishReason, responseId });
|
|
1265
1226
|
eventStream.end(finalResponse);
|
|
1266
1227
|
} catch (err: unknown) {
|
|
1267
|
-
|
|
1268
|
-
const isAbort =
|
|
1269
|
-
linked.signal.aborted ||
|
|
1270
|
-
eventStream.isCancelled() ||
|
|
1271
|
-
(raw as { name?: string }).name === "AbortError";
|
|
1272
|
-
eventStream.fail(
|
|
1273
|
-
isAbort ? Object.assign(new Error("Stream aborted"), { name: "AbortError" }) : raw
|
|
1274
|
-
);
|
|
1228
|
+
failStream(eventStream, linked, err);
|
|
1275
1229
|
} finally {
|
|
1276
|
-
|
|
1277
|
-
options?.signal?.removeEventListener("abort", forwardUserAbort);
|
|
1278
|
-
} catch {}
|
|
1279
|
-
try {
|
|
1280
|
-
removeStreamCancel();
|
|
1281
|
-
} catch {}
|
|
1230
|
+
release();
|
|
1282
1231
|
}
|
|
1283
1232
|
})();
|
|
1284
1233
|
|