mixdog 0.9.151 → 0.9.152
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 +110 -68
- package/package.json +3 -2
- package/scripts/tool-stress.mjs +0 -2
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/runtime/agent/orchestrator/config.mjs +6 -66
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +4 -7
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +235 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +24 -1
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +161 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +31 -18
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +556 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +457 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +105 -186
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/browser-bridge/client.mjs +103 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +42 -0
- package/src/runtime/computer-bridge/client.mjs +101 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +53 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-surface.mjs +18 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/goal-runtime.mjs +721 -0
- package/src/session-runtime/goal-runtime.test.mjs +108 -0
- package/src/session-runtime/lifecycle-api.mjs +18 -4
- package/src/session-runtime/lifecycle-api.test.mjs +21 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/resource-api-global.test.mjs +69 -0
- package/src/session-runtime/resource-api.mjs +173 -58
- package/src/session-runtime/runtime-core.mjs +148 -20
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-turn-api.mjs +22 -11
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +4 -13
- package/src/session-runtime/tool-policy-surface.test.mjs +33 -11
- package/src/session-runtime/tool-surface.mjs +12 -0
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/notify.mjs +6 -8
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/daemon.mjs +31 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +696 -58
- package/src/standalone/session-runtime-inline-host.mjs +351 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +83 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +569 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +476 -18
- package/src/standalone/session-service.mjs +81 -6
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +163 -44
- package/src/tui/session/context-state.mjs +1 -0
- package/src/tui/session/goal-continuation.mjs +110 -0
- package/src/tui/session/goal-continuation.test.mjs +84 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +108 -0
- package/src/tui/session/session-api-ext.mjs +39 -8
- package/src/tui/session/session-api.mjs +58 -0
- package/src/tui/session/session-flow.mjs +6 -0
- package/src/tui/session/turn.mjs +1 -0
- package/src/tui/session-local.mjs +15 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -23,8 +23,14 @@ import {
|
|
|
23
23
|
NATIVE_SERVER_TOOL_CALL_BLOCK_TYPES,
|
|
24
24
|
NATIVE_SERVER_TOOL_RESULT_BLOCK_TYPES,
|
|
25
25
|
} from './lib/anthropic-native-blocks.mjs';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
frameProviderSseChunk,
|
|
28
|
+
releaseProviderSseStream,
|
|
29
|
+
retainProviderSseStream,
|
|
30
|
+
} from './stream-json-pool.mjs';
|
|
31
|
+
import { splitSseRegion } from './lib/sse-framing.mjs';
|
|
27
32
|
import { stampStreamOutcome, STREAM_TRANSPORTS, STREAM_OUTCOME_VERSION } from './lib/stream-outcome.mjs';
|
|
33
|
+
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
28
34
|
import {
|
|
29
35
|
anthropicFallbackProviderMetadata,
|
|
30
36
|
parseAnthropicFallbackBlock,
|
|
@@ -46,6 +52,11 @@ const SSE_MIDSTREAM_POLICY = {
|
|
|
46
52
|
|
|
47
53
|
// --- SSE parser ---
|
|
48
54
|
|
|
55
|
+
// Per-stream identity for the shared framing pool: every chunk of one stream
|
|
56
|
+
// settles in submission order (and prefers one worker) regardless of whether
|
|
57
|
+
// it was framed inline or offloaded.
|
|
58
|
+
let _sseStreamSequence = 0;
|
|
59
|
+
|
|
49
60
|
function _captureMidstreamAbort(state, reason) {
|
|
50
61
|
if (!state) return;
|
|
51
62
|
const reasonName = reason?.name || '';
|
|
@@ -142,6 +153,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
142
153
|
// do not mistake transport heartbeats for model output.
|
|
143
154
|
const reader = response.body.getReader();
|
|
144
155
|
const decoder = new TextDecoder();
|
|
156
|
+
const streamKey = `anthropic-sse#${++_sseStreamSequence}`;
|
|
145
157
|
// Transport-idle window. The legacy semanticIdleTimeoutMs seam remains as
|
|
146
158
|
// a fallback for existing tests/callers, but production uses the shared
|
|
147
159
|
// byte/event inactivity policy.
|
|
@@ -199,15 +211,20 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
199
211
|
.filter(([index]) => !pendingNativeToolInputs.has(index));
|
|
200
212
|
const _orderedAssistantBlocks = () => {
|
|
201
213
|
const nativeBlocks = _completedNativeBlocks();
|
|
202
|
-
|
|
203
|
-
return [
|
|
214
|
+
const entries = [
|
|
204
215
|
...[...thinkingBlocks.entries()],
|
|
205
216
|
...[...textBlocks.entries()]
|
|
206
217
|
.filter(([, text]) => typeof text === 'string' && text.length > 0)
|
|
207
218
|
.map(([index, text]) => [index, { type: 'text', text }]),
|
|
208
219
|
...nativeBlocks,
|
|
209
220
|
...[...clientToolUseBlocks.entries()],
|
|
210
|
-
]
|
|
221
|
+
];
|
|
222
|
+
if (!entries.length) return undefined;
|
|
223
|
+
// A leaked plain-text tool call has no provider content_block index.
|
|
224
|
+
// Falling back to the legacy flattened projection is safer than
|
|
225
|
+
// claiming an exact replay while silently omitting that synthetic call.
|
|
226
|
+
if (toolCalls.length > 0 && clientToolUseBlocks.size !== toolCalls.length) return undefined;
|
|
227
|
+
return entries
|
|
211
228
|
.sort((a, b) => a[0] - b[0])
|
|
212
229
|
.map(([, block]) => block);
|
|
213
230
|
};
|
|
@@ -430,6 +447,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
430
447
|
if (_c && typeof _c.catch === 'function') _c.catch(() => {});
|
|
431
448
|
} catch {}
|
|
432
449
|
};
|
|
450
|
+
retainProviderSseStream(streamKey);
|
|
433
451
|
try {
|
|
434
452
|
// Reader ownership begins at getReader() above, so even a signal that
|
|
435
453
|
// was already aborted must pass through this try/finally cleanup path.
|
|
@@ -478,36 +496,35 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
478
496
|
if (state?.sawMessageStart) resetIdleTimer();
|
|
479
497
|
try { onStreamDelta?.('transport'); } catch {}
|
|
480
498
|
buffer += decoder.decode(value, { stream: true });
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const data = line.slice(6).trim();
|
|
502
|
-
if (!data) continue;
|
|
499
|
+
// ONE unit of work per network chunk. The previous loop split the
|
|
500
|
+
// chunk into a line array and awaited a JSON parse per SSE event,
|
|
501
|
+
// so a single readable chunk cost O(events) allocations and
|
|
502
|
+
// O(events) microtask hops on the shared daemon event loop. The
|
|
503
|
+
// pool now frames + parses the whole chunk (in a worker once it is
|
|
504
|
+
// worth the round-trip, otherwise inline and synchronously).
|
|
505
|
+
// Framing rules — `:` keepalive comments, blank record separators,
|
|
506
|
+
// sticky `event:` names, trimmed `data:` payloads — are unchanged;
|
|
507
|
+
// `region` holds only complete records and the trailing partial
|
|
508
|
+
// line stays in `buffer`, exactly as split()/pop() left it.
|
|
509
|
+
const { region, rest } = splitSseRegion(buffer);
|
|
510
|
+
buffer = rest;
|
|
511
|
+
if (!region) continue;
|
|
512
|
+
// A completed reader.read() owns these frames even if abort races
|
|
513
|
+
// immediately afterward, so framing is deliberately NOT signal
|
|
514
|
+
// canceled: the next read observes the cancellation after the
|
|
515
|
+
// frames of this chunk are relayed.
|
|
516
|
+
const framed = frameProviderSseChunk(region, { currentEvent, streamKey });
|
|
517
|
+
const framedChunk = typeof framed?.then === 'function' ? await framed : framed;
|
|
518
|
+
currentEvent = framedChunk.currentEvent;
|
|
503
519
|
|
|
520
|
+
for (const framedEvent of framedChunk.events) {
|
|
521
|
+
// A malformed record is skipped exactly like the former
|
|
522
|
+
// per-event JSON.parse throw — and, unlike a whole-batch
|
|
523
|
+
// rejection, the well-formed records beside it still run.
|
|
524
|
+
if (framedEvent.error) continue;
|
|
525
|
+
const event = framedEvent.value;
|
|
504
526
|
try {
|
|
505
|
-
|
|
506
|
-
// races immediately afterward; the next read observes the
|
|
507
|
-
// cancellation after this frame is relayed.
|
|
508
|
-
const [event] = await parseProviderJsonBatch([data]);
|
|
509
|
-
|
|
510
|
-
if (currentEvent === 'error' || event?.type === 'error' || event?.error) {
|
|
527
|
+
if (framedEvent.name === 'error' || event?.type === 'error' || event?.error) {
|
|
511
528
|
throw _anthropicSseError(event);
|
|
512
529
|
}
|
|
513
530
|
|
|
@@ -897,6 +914,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
897
914
|
err.partialHasThinking = hasThinkingContent;
|
|
898
915
|
err.partialThinkingBlocks = _orderedThinkingBlocks();
|
|
899
916
|
err.partialAssistantBlocks = _orderedAssistantBlocks();
|
|
917
|
+
err.partialProviderReplay = createProviderReplay('anthropic', err.partialAssistantBlocks);
|
|
900
918
|
err.partialStopReason = stopReason || undefined;
|
|
901
919
|
} catch { /* best-effort enrichment */ }
|
|
902
920
|
// Truncation is a continuation, never a terminal turn. A pending
|
|
@@ -921,6 +939,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
921
939
|
throw err;
|
|
922
940
|
}
|
|
923
941
|
|
|
942
|
+
const assistantBlocks = _orderedAssistantBlocks();
|
|
924
943
|
return {
|
|
925
944
|
content,
|
|
926
945
|
model,
|
|
@@ -941,12 +960,14 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
941
960
|
// replays this list verbatim (see toAnthropicMessages'
|
|
942
961
|
// assistantBlocks branch). Absent for every ordinary turn, which
|
|
943
962
|
// keeps the existing text/thinking/tool_use lowering untouched.
|
|
944
|
-
assistantBlocks
|
|
963
|
+
assistantBlocks,
|
|
964
|
+
providerReplay: createProviderReplay('anthropic', assistantBlocks),
|
|
945
965
|
providerMetadata: anthropicFallbackProviderMetadata(fallbackEvents),
|
|
946
966
|
};
|
|
947
967
|
} finally {
|
|
948
968
|
if (idleTimer) clearTimeout(idleTimer);
|
|
949
969
|
clearFirstMessageTimer();
|
|
970
|
+
try { releaseProviderSseStream(streamKey); } catch {}
|
|
950
971
|
if (signal) signal.removeEventListener('abort', onAbort);
|
|
951
972
|
// message_stop deliberately exits before EOF because Anthropic may keep
|
|
952
973
|
// sending pings. Cancel the reader so the successful response body and
|
|
@@ -318,6 +318,7 @@ export class AnthropicProvider {
|
|
|
318
318
|
assistantBlocks: Array.isArray(parseResult.assistantBlocks) && parseResult.assistantBlocks.length
|
|
319
319
|
? parseResult.assistantBlocks
|
|
320
320
|
: undefined,
|
|
321
|
+
...(parseResult.providerReplay ? { providerReplay: parseResult.providerReplay } : {}),
|
|
321
322
|
...(parseResult.providerMetadata && typeof parseResult.providerMetadata === 'object'
|
|
322
323
|
? { providerMetadata: parseResult.providerMetadata }
|
|
323
324
|
: {}),
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { randomUUID, createHash } from 'crypto';
|
|
15
15
|
import { withRetry } from './retry-classifier.mjs';
|
|
16
16
|
import { traceAgentUsage } from '../agent-trace.mjs';
|
|
17
|
+
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
17
18
|
import {
|
|
18
19
|
createPassthroughSignal,
|
|
19
20
|
createTimeoutSignal,
|
|
@@ -353,6 +354,10 @@ export class AntigravityOAuthProvider {
|
|
|
353
354
|
? textLeakGuard.scrubAssistantText(rawContent)
|
|
354
355
|
: rawContent;
|
|
355
356
|
const leakedToolCalls = textLeakGuard?.getLeakedToolCalls() ?? [];
|
|
357
|
+
const providerReplay = createProviderReplay(
|
|
358
|
+
'antigravity',
|
|
359
|
+
leakedToolCalls.length ? [] : responseParts,
|
|
360
|
+
);
|
|
356
361
|
let nativeToolCalls = parseToolCalls(responseParts);
|
|
357
362
|
if (textLeakGuard?.enabled) nativeToolCalls = textLeakGuard.filterNativeToolCalls(nativeToolCalls);
|
|
358
363
|
let toolCalls = nativeToolCalls;
|
|
@@ -373,6 +378,7 @@ export class AntigravityOAuthProvider {
|
|
|
373
378
|
finishReason,
|
|
374
379
|
partialContent: content,
|
|
375
380
|
partialToolCalls: toolCalls,
|
|
381
|
+
partialProviderReplay: providerReplay,
|
|
376
382
|
providerMetadata,
|
|
377
383
|
model: useModel,
|
|
378
384
|
rawUsage: response.usageMetadata || null,
|
|
@@ -407,6 +413,7 @@ export class AntigravityOAuthProvider {
|
|
|
407
413
|
model: useModel,
|
|
408
414
|
toolCalls,
|
|
409
415
|
citations: citations.length ? citations : undefined,
|
|
416
|
+
providerReplay,
|
|
410
417
|
providerMetadata,
|
|
411
418
|
providerState: opts.providerState,
|
|
412
419
|
usage,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { SchemaType } from '@google/generative-ai';
|
|
12
12
|
import { traceHash, stableTraceStringify } from './trace-utils.mjs';
|
|
13
13
|
import { normalizeContentForGeminiParts, splitToolContentForGemini } from './media-normalization.mjs';
|
|
14
|
+
import { providerReplayItems } from './lib/provider-replay.mjs';
|
|
14
15
|
|
|
15
16
|
function explicitGeminiMediaPart(part) {
|
|
16
17
|
if (!part || typeof part !== 'object') return null;
|
|
@@ -617,6 +618,12 @@ function toGeminiToolContent(message, toolNameByCallId, capabilities) {
|
|
|
617
618
|
|
|
618
619
|
function toGeminiContent(message, toolNameByCallId, capabilities) {
|
|
619
620
|
if (!message || message.role === 'system') return null;
|
|
621
|
+
const orderedReplay = message.role === 'assistant'
|
|
622
|
+
? providerReplayItems(message, ['gemini', 'antigravity'])
|
|
623
|
+
: undefined;
|
|
624
|
+
if (orderedReplay?.length) {
|
|
625
|
+
return { role: 'model', parts: orderedReplay };
|
|
626
|
+
}
|
|
620
627
|
if (message.role === 'assistant' && message.toolCalls?.length) {
|
|
621
628
|
const parts = geminiThoughtPartsFromMetadata(message);
|
|
622
629
|
if (message.content) {
|
|
@@ -198,14 +198,9 @@ export function aggregateGeminiStreamChunks(responses) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
for (const part of candidate.content.parts) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (part.functionCall) newPart.functionCall = part.functionCall;
|
|
205
|
-
if (part.thoughtSignature) newPart.thoughtSignature = part.thoughtSignature;
|
|
206
|
-
if (part.thought_signature) newPart.thought_signature = part.thought_signature;
|
|
207
|
-
if (part.executableCode) newPart.executableCode = part.executableCode;
|
|
208
|
-
if (part.codeExecutionResult) newPart.codeExecutionResult = part.codeExecutionResult;
|
|
201
|
+
let newPart;
|
|
202
|
+
try { newPart = structuredClone(part); }
|
|
203
|
+
catch { newPart = { ...part }; }
|
|
209
204
|
if (Object.keys(newPart).length === 0) newPart.text = '';
|
|
210
205
|
aggCand.content.parts.push(newPart);
|
|
211
206
|
}
|
|
@@ -2,6 +2,7 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
|
2
2
|
import { getAgentApiKey } from '../../../shared/provider-api-key.mjs';
|
|
3
3
|
import { canFallbackNonStreaming, withRetry } from './retry-classifier.mjs';
|
|
4
4
|
import { traceAgentUsage, appendAgentTrace } from '../agent-trace.mjs';
|
|
5
|
+
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
5
6
|
import {
|
|
6
7
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
7
8
|
PROVIDER_MAX_BEFORE_WARN_MS,
|
|
@@ -907,6 +908,10 @@ export class GeminiProvider {
|
|
|
907
908
|
? textLeakGuard.scrubAssistantText(rawContent)
|
|
908
909
|
: rawContent;
|
|
909
910
|
const leakedToolCalls = textLeakGuard?.getLeakedToolCalls() ?? [];
|
|
911
|
+
const providerReplay = createProviderReplay(
|
|
912
|
+
'gemini',
|
|
913
|
+
leakedToolCalls.length ? [] : responseParts,
|
|
914
|
+
);
|
|
910
915
|
let nativeToolCalls = parseToolCalls(candidate?.content?.parts ?? []);
|
|
911
916
|
if (textLeakGuard?.enabled) {
|
|
912
917
|
nativeToolCalls = textLeakGuard.filterNativeToolCalls(nativeToolCalls);
|
|
@@ -943,6 +948,7 @@ export class GeminiProvider {
|
|
|
943
948
|
finishReason,
|
|
944
949
|
partialContent: content,
|
|
945
950
|
partialToolCalls: toolCalls,
|
|
951
|
+
partialProviderReplay: providerReplay,
|
|
946
952
|
providerMetadata,
|
|
947
953
|
model: useModel,
|
|
948
954
|
rawUsage: response.usageMetadata || null,
|
|
@@ -1015,6 +1021,7 @@ export class GeminiProvider {
|
|
|
1015
1021
|
model: useModel,
|
|
1016
1022
|
toolCalls,
|
|
1017
1023
|
citations: citations.length ? citations : undefined,
|
|
1024
|
+
providerReplay,
|
|
1018
1025
|
providerMetadata,
|
|
1019
1026
|
providerState: opts.providerState,
|
|
1020
1027
|
// Use the same normalized usage object traceAgentUsage recorded,
|
|
@@ -2,6 +2,7 @@ import { providerNativeToolPrefixCount } from '../../../../../session-runtime/pr
|
|
|
2
2
|
import { isNativeServerToolBlockType } from './anthropic-native-blocks.mjs';
|
|
3
3
|
import { sanitizeAnthropicContentPairs, foldUserTextIntoToolResultTail } from '../../session/context-utils.mjs';
|
|
4
4
|
import { normalizeContentForAnthropic } from '../media-normalization.mjs';
|
|
5
|
+
import { createProviderReplay, providerReplayItems } from './provider-replay.mjs';
|
|
5
6
|
import {
|
|
6
7
|
anthropicFallbackProviderMetadata,
|
|
7
8
|
parseAnthropicFallbackBlock,
|
|
@@ -93,9 +94,14 @@ export function toAnthropicMessages(messages) {
|
|
|
93
94
|
for (let idx = 0; idx < messages.length; idx++) {
|
|
94
95
|
const m = messages[idx];
|
|
95
96
|
if (m.role === 'system') continue;
|
|
96
|
-
|
|
97
|
+
const orderedReplay = m.role === 'assistant'
|
|
98
|
+
? providerReplayItems(m, 'anthropic')
|
|
99
|
+
: undefined;
|
|
100
|
+
if (m.role === 'assistant' && (orderedReplay?.length || m.toolCalls?.length || m.assistantBlocks?.length || m.thinkingBlocks?.length)) {
|
|
97
101
|
let content;
|
|
98
|
-
if (
|
|
102
|
+
if (orderedReplay?.length) {
|
|
103
|
+
content = orderedReplay;
|
|
104
|
+
} else if (m.assistantBlocks?.length) {
|
|
99
105
|
content = m.assistantBlocks.slice();
|
|
100
106
|
} else {
|
|
101
107
|
content = [];
|
|
@@ -211,6 +217,7 @@ export function normalizeAnthropicNonStreamingResponse(message, fallbackModel =
|
|
|
211
217
|
|| (block.type === 'text' && typeof block.text === 'string' && block.text.length > 0)
|
|
212
218
|
))
|
|
213
219
|
: [];
|
|
220
|
+
const replayableBlocks = blocks.filter((block) => block && typeof block === 'object');
|
|
214
221
|
const usage = message?.usage || {};
|
|
215
222
|
const input = Number(usage.input_tokens) || 0;
|
|
216
223
|
const cacheRead = Number(usage.cache_read_input_tokens) || 0;
|
|
@@ -224,6 +231,7 @@ export function normalizeAnthropicNonStreamingResponse(message, fallbackModel =
|
|
|
224
231
|
contentBlockTypes: blocks.map((block) => block?.type).filter(Boolean),
|
|
225
232
|
thinkingBlocks: thinkingBlocks.length ? thinkingBlocks : undefined,
|
|
226
233
|
assistantBlocks: nativeAssistantBlocks.length ? nativeAssistantBlocks : undefined,
|
|
234
|
+
providerReplay: createProviderReplay('anthropic', replayableBlocks),
|
|
227
235
|
providerMetadata: anthropicFallbackProviderMetadata(fallbackEvents),
|
|
228
236
|
usage: {
|
|
229
237
|
inputTokens: input,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-owned assistant output that must be replayed in its original item
|
|
3
|
+
* order. Flattened content/tool/reasoning projections remain available to the
|
|
4
|
+
* agent loop, but provider adapters prefer this envelope when rebuilding wire
|
|
5
|
+
* history for the same provider family.
|
|
6
|
+
*/
|
|
7
|
+
export const PROVIDER_REPLAY_VERSION = 1;
|
|
8
|
+
|
|
9
|
+
function cloneReplayValue(value) {
|
|
10
|
+
try { return structuredClone(value); }
|
|
11
|
+
catch {
|
|
12
|
+
try { return JSON.parse(JSON.stringify(value)); }
|
|
13
|
+
catch { return value; }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createProviderReplay(provider, items) {
|
|
18
|
+
const id = typeof provider === 'string' ? provider.trim() : '';
|
|
19
|
+
if (!id || !Array.isArray(items) || items.length === 0) return undefined;
|
|
20
|
+
return {
|
|
21
|
+
version: PROVIDER_REPLAY_VERSION,
|
|
22
|
+
provider: id,
|
|
23
|
+
items: cloneReplayValue(items),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function cloneProviderReplay(replay) {
|
|
28
|
+
if (!replay || typeof replay !== 'object'
|
|
29
|
+
|| replay.version !== PROVIDER_REPLAY_VERSION
|
|
30
|
+
|| typeof replay.provider !== 'string' || !replay.provider
|
|
31
|
+
|| !Array.isArray(replay.items) || replay.items.length === 0) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return createProviderReplay(replay.provider, replay.items);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function providerReplayItems(messageOrReplay, acceptedProviders) {
|
|
38
|
+
const replay = messageOrReplay?.providerReplay ?? messageOrReplay;
|
|
39
|
+
const cloned = cloneProviderReplay(replay);
|
|
40
|
+
if (!cloned) return undefined;
|
|
41
|
+
const accepted = Array.isArray(acceptedProviders)
|
|
42
|
+
? new Set(acceptedProviders)
|
|
43
|
+
: new Set([acceptedProviders]);
|
|
44
|
+
if (!accepted.has(cloned.provider)) return undefined;
|
|
45
|
+
return cloned.items;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hasProviderReplay(message) {
|
|
49
|
+
return cloneProviderReplay(message?.providerReplay) !== undefined;
|
|
50
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sse-framing.mjs — shared SSE record framing for provider streams.
|
|
3
|
+
*
|
|
4
|
+
* One network chunk carries many SSE records. Splitting it into an array of
|
|
5
|
+
* lines and then awaiting one JSON parse per record turned a single readable
|
|
6
|
+
* chunk into O(records) allocations plus O(records) microtask hops on the
|
|
7
|
+
* shared daemon event loop. These helpers frame a chunk in a single scan with
|
|
8
|
+
* no per-line array, and they are pure so the exact same code runs inline on
|
|
9
|
+
* the event loop and inside the provider stream worker (a worker failure can
|
|
10
|
+
* therefore always be re-run inline with identical results).
|
|
11
|
+
*
|
|
12
|
+
* Framing rules are byte-identical to the previous inline Anthropic loop:
|
|
13
|
+
* - `:`-prefixed lines are comment/keepalive frames and are dropped.
|
|
14
|
+
* - blank lines are record separators and are dropped.
|
|
15
|
+
* - `event: ` sets the current event name, which persists (across chunks)
|
|
16
|
+
* until the next `event: ` line.
|
|
17
|
+
* - `data: ` payloads are trimmed; empty payloads are dropped.
|
|
18
|
+
* - any other line is ignored.
|
|
19
|
+
* CRLF transports behave as before: the trailing `\r` is removed by trim() on
|
|
20
|
+
* the value lines, and a bare `\r` line matches no prefix and is ignored.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const COLON_CODE = 58; // ':'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Split a decoded buffer into the complete-record region and the residual
|
|
27
|
+
* partial line. Equivalent to `const lines = buffer.split('\n'); buffer = lines.pop()`
|
|
28
|
+
* without materializing the line array.
|
|
29
|
+
*/
|
|
30
|
+
export function splitSseRegion(buffer) {
|
|
31
|
+
const cut = buffer.lastIndexOf('\n');
|
|
32
|
+
if (cut < 0) return { region: '', rest: buffer };
|
|
33
|
+
return { region: buffer.slice(0, cut + 1), rest: buffer.slice(cut + 1) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Frame a complete-record region into ordered `{ name, data }` records.
|
|
38
|
+
* `currentEvent` carries the last `event:` name into and out of the region so
|
|
39
|
+
* a record split across network chunks keeps its event name.
|
|
40
|
+
*/
|
|
41
|
+
export function frameSseRegion(text, currentEvent = '') {
|
|
42
|
+
const frames = [];
|
|
43
|
+
let name = String(currentEvent || '');
|
|
44
|
+
const length = text.length;
|
|
45
|
+
let index = 0;
|
|
46
|
+
while (index < length) {
|
|
47
|
+
let end = text.indexOf('\n', index);
|
|
48
|
+
if (end < 0) end = length;
|
|
49
|
+
const start = index;
|
|
50
|
+
index = end + 1;
|
|
51
|
+
if (end === start) continue; // record separator
|
|
52
|
+
if (text.charCodeAt(start) === COLON_CODE) continue; // comment / ping keepalive
|
|
53
|
+
if (text.startsWith('event: ', start)) {
|
|
54
|
+
name = text.slice(start + 7, end).trim();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!text.startsWith('data: ', start)) continue;
|
|
58
|
+
const data = text.slice(start + 6, end).trim();
|
|
59
|
+
if (!data) continue;
|
|
60
|
+
frames.push({ name, data });
|
|
61
|
+
}
|
|
62
|
+
return { frames, currentEvent: name };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse framed records with per-record error isolation. A malformed record
|
|
67
|
+
* must never discard the well-formed records batched beside it: the consumer
|
|
68
|
+
* skips entries that carry `error` exactly like the previous per-event
|
|
69
|
+
* try/catch did.
|
|
70
|
+
*/
|
|
71
|
+
export function parseSseFrames(frames) {
|
|
72
|
+
const events = [];
|
|
73
|
+
for (const frame of frames) {
|
|
74
|
+
try {
|
|
75
|
+
events.push({ name: frame.name, value: JSON.parse(frame.data) });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
events.push({
|
|
78
|
+
name: frame.name,
|
|
79
|
+
error: {
|
|
80
|
+
name: String(error?.name || 'SyntaxError'),
|
|
81
|
+
message: String(error?.message || error || 'invalid JSON'),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return events;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Frame + parse in one pass (the unit of work posted to the stream worker). */
|
|
90
|
+
export function frameAndParseSse(text, currentEvent = '') {
|
|
91
|
+
const framed = frameSseRegion(text, currentEvent);
|
|
92
|
+
return { events: parseSseFrames(framed.frames), currentEvent: framed.currentEvent };
|
|
93
|
+
}
|
|
@@ -25,7 +25,7 @@ const CODING_UNFIT_ID_RE = /-search(-preview)?(\b|-|$)|-(audio|realtime)-preview
|
|
|
25
25
|
const HOSTED_PROVIDERS = new Set([
|
|
26
26
|
'openai', 'openai-oauth', 'openai-ws', 'anthropic', 'anthropic-oauth',
|
|
27
27
|
'gemini', 'google', 'xai', 'grok-oauth', 'deepseek', 'groq', 'mistral',
|
|
28
|
-
'opencode-go', 'azure',
|
|
28
|
+
'opencode-go', 'openrouter', 'azure',
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
31
|
// (a) Non-chat modality ids. Matched as whole word-ish tokens on the id so we
|
|
@@ -166,9 +166,9 @@ function _stalenessFamily(row, id) {
|
|
|
166
166
|
|
|
167
167
|
function _staleMonths() {
|
|
168
168
|
const raw = process.env.MIXDOG_MODEL_STALE_MONTHS;
|
|
169
|
-
if (raw == null || raw === '') return
|
|
169
|
+
if (raw == null || raw === '') return 9;
|
|
170
170
|
const n = Number(raw);
|
|
171
|
-
return Number.isFinite(n) ? n :
|
|
171
|
+
return Number.isFinite(n) ? n : 9;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Apply catalog-driven staleness to the already-filtered `kept` rows.
|
|
@@ -14,6 +14,14 @@ export const OPENAI_COMPAT_PRESETS = {
|
|
|
14
14
|
baseURL: 'https://opencode.ai/zen/go/v1',
|
|
15
15
|
defaultModel: 'glm-5.2',
|
|
16
16
|
},
|
|
17
|
+
openrouter: {
|
|
18
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
19
|
+
defaultModel: 'openrouter/auto',
|
|
20
|
+
extraHeaders: {
|
|
21
|
+
'HTTP-Referer': 'https://github.com/tribgames/mixdog',
|
|
22
|
+
'X-OpenRouter-Title': 'mixdog',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
17
25
|
ollama: {
|
|
18
26
|
baseURL: 'http://localhost:11434/v1',
|
|
19
27
|
defaultModel: 'llama3.3:latest',
|
|
@@ -296,6 +296,7 @@ export async function consumeCompatChatCompletionStream(stream, {
|
|
|
296
296
|
let sawFirstEvent = false;
|
|
297
297
|
let content = '';
|
|
298
298
|
let reasoningContent = '';
|
|
299
|
+
const reasoningDetails = [];
|
|
299
300
|
// Invariant flag for the gateway live-text relay: set once a non-empty
|
|
300
301
|
// text chunk has been forwarded to the client. A failure after this point
|
|
301
302
|
// must be treated as permanent — the rendered text cannot be withdrawn and
|
|
@@ -435,6 +436,10 @@ export async function consumeCompatChatCompletionStream(stream, {
|
|
|
435
436
|
// use reasoning, and some local compatibility shims expose
|
|
436
437
|
// thinking. They are aliases, never concatenate multiple aliases
|
|
437
438
|
// from the same chunk.
|
|
439
|
+
if (Array.isArray(choice?.delta?.reasoning_details) && choice.delta.reasoning_details.length) {
|
|
440
|
+
reasoningDetails.push(...choice.delta.reasoning_details);
|
|
441
|
+
reportProgress('reasoning');
|
|
442
|
+
}
|
|
438
443
|
const reasoningDelta = typeof choice?.delta?.reasoning_content === 'string'
|
|
439
444
|
? choice.delta.reasoning_content
|
|
440
445
|
: typeof choice?.delta?.reasoning === 'string'
|
|
@@ -513,6 +518,7 @@ export async function consumeCompatChatCompletionStream(stream, {
|
|
|
513
518
|
const message = {
|
|
514
519
|
content: content || null,
|
|
515
520
|
...(reasoningContent ? { reasoning_content: reasoningContent } : {}),
|
|
521
|
+
...(reasoningDetails.length ? { reasoning_details: reasoningDetails } : {}),
|
|
516
522
|
};
|
|
517
523
|
const rawToolCalls = [...toolAcc.values()]
|
|
518
524
|
.sort((a, b) => a._order - b._order)
|
|
@@ -557,6 +563,7 @@ export async function consumeCompatChatCompletionStream(stream, {
|
|
|
557
563
|
toolCalls,
|
|
558
564
|
stopReason,
|
|
559
565
|
reasoningContent: reasoningContent || null,
|
|
566
|
+
reasoningDetails: reasoningDetails.length ? reasoningDetails : null,
|
|
560
567
|
rawUsage,
|
|
561
568
|
};
|
|
562
569
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import {
|
|
21
21
|
customToolCallFromResponseItem,
|
|
22
22
|
} from './custom-tool-wire.mjs';
|
|
23
|
+
import { providerReplayItems } from './lib/provider-replay.mjs';
|
|
23
24
|
|
|
24
25
|
export function positiveTokenInt(value) {
|
|
25
26
|
const n = Number(value);
|
|
@@ -56,6 +57,12 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
|
|
|
56
57
|
|| providerName === 'xai';
|
|
57
58
|
const out = [];
|
|
58
59
|
const pendingToolMedia = [];
|
|
60
|
+
const openRouterReasoningDetails = (message) => {
|
|
61
|
+
if (providerName !== 'openrouter') return null;
|
|
62
|
+
const metadata = message?.providerMetadata?.openrouter;
|
|
63
|
+
const details = metadata?.reasoning_details ?? metadata?.reasoningDetails;
|
|
64
|
+
return Array.isArray(details) && details.length ? details : null;
|
|
65
|
+
};
|
|
59
66
|
const flushToolMedia = () => {
|
|
60
67
|
if (!pendingToolMedia.length) return;
|
|
61
68
|
out.push({ role: 'user', content: pendingToolMedia.splice(0) });
|
|
@@ -83,11 +90,20 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
|
|
|
83
90
|
})),
|
|
84
91
|
};
|
|
85
92
|
if (replaysReasoningContent && m.reasoningContent) msg.reasoning_content = m.reasoningContent;
|
|
93
|
+
const reasoningDetails = openRouterReasoningDetails(m);
|
|
94
|
+
if (reasoningDetails) msg.reasoning_details = reasoningDetails;
|
|
86
95
|
out.push(msg);
|
|
87
96
|
continue;
|
|
88
97
|
}
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
const reasoningDetails = m.role === 'assistant' ? openRouterReasoningDetails(m) : null;
|
|
99
|
+
if (m.role === 'assistant' && ((replaysReasoningContent && m.reasoningContent) || reasoningDetails)) {
|
|
100
|
+
const msg = {
|
|
101
|
+
role: m.role,
|
|
102
|
+
content: normalizeContentForOpenAIChat(m.content, { role: 'assistant' }),
|
|
103
|
+
};
|
|
104
|
+
if (replaysReasoningContent && m.reasoningContent) msg.reasoning_content = m.reasoningContent;
|
|
105
|
+
if (reasoningDetails) msg.reasoning_details = reasoningDetails;
|
|
106
|
+
out.push(msg);
|
|
91
107
|
continue;
|
|
92
108
|
}
|
|
93
109
|
out.push({ role: m.role, content: normalizeContentForOpenAIChat(m.content, { role: m.role }) });
|
|
@@ -392,6 +408,13 @@ export function toXaiResponsesInput(messages, providerState, options = {}) {
|
|
|
392
408
|
const m = messages[messageIndex];
|
|
393
409
|
if (!includeSystem && m.role === 'system') continue;
|
|
394
410
|
if (m.role !== 'tool') flushToolMedia();
|
|
411
|
+
const orderedReplay = m.role === 'assistant'
|
|
412
|
+
? providerReplayItems(m, 'xai-responses')
|
|
413
|
+
: undefined;
|
|
414
|
+
if (orderedReplay?.length) {
|
|
415
|
+
input.push(...orderedReplay);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
395
418
|
const reasoningItems = reasoningByMessageIndex.get(messageIndex);
|
|
396
419
|
if (reasoningItems) input.push(...reasoningItems);
|
|
397
420
|
if (dropToolHistory && m.role === 'tool') continue;
|