mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createKeyedSingleflight() {
|
|
2
|
+
const pending = new Map();
|
|
3
|
+
return {
|
|
4
|
+
run(key, task) {
|
|
5
|
+
const lane = String(key || '');
|
|
6
|
+
const existing = pending.get(lane);
|
|
7
|
+
if (existing) return existing;
|
|
8
|
+
let tracked;
|
|
9
|
+
const promise = Promise.resolve().then(task);
|
|
10
|
+
tracked = promise.finally(() => {
|
|
11
|
+
if (pending.get(lane) === tracked) pending.delete(lane);
|
|
12
|
+
});
|
|
13
|
+
pending.set(lane, tracked);
|
|
14
|
+
return tracked;
|
|
15
|
+
},
|
|
16
|
+
has(key) {
|
|
17
|
+
return pending.has(String(key || ''));
|
|
18
|
+
},
|
|
19
|
+
get size() {
|
|
20
|
+
return pending.size;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// the adaptive halving/recovery below is inert.
|
|
4
|
+
// Normal provider traffic is not concurrency-gated. Independent sessions and
|
|
5
|
+
// accounts start immediately; a finite limit exists only when an operator
|
|
6
|
+
// explicitly configures one, or after that account reports a real 429.
|
|
8
7
|
export const PROVIDER_ACCOUNT_CONCURRENCY = Infinity;
|
|
9
8
|
export const PROVIDER_ACCOUNT_MAX_QUEUE = 1024;
|
|
10
9
|
// A cooldown longer than this is a quota-window block (subscription limit),
|
|
@@ -13,6 +12,21 @@ export const PROVIDER_ACCOUNT_MAX_QUEUE = 1024;
|
|
|
13
12
|
export const PROVIDER_COOLDOWN_FAIL_FAST_MS = 60_000;
|
|
14
13
|
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
15
14
|
const currentAdmission = new AsyncLocalStorage();
|
|
15
|
+
const PRIORITY_RANK = Object.freeze({
|
|
16
|
+
'user-blocking': 0,
|
|
17
|
+
'user-visible': 1,
|
|
18
|
+
'best-effort': 2,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function normalizePriority(value) {
|
|
22
|
+
const key = String(value || 'user-visible').toLowerCase();
|
|
23
|
+
return Object.hasOwn(PRIORITY_RANK, key) ? key : 'user-visible';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function currentProviderAdmissionOwner() {
|
|
27
|
+
const active = currentAdmission.getStore();
|
|
28
|
+
return active?.ownerKey ? String(active.ownerKey) : null;
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
function abortError(signal, fallback = 'provider request canceled') {
|
|
18
32
|
return signal?.reason instanceof Error
|
|
@@ -110,10 +124,10 @@ export class ProviderAdmissionScheduler {
|
|
|
110
124
|
setTimer = setTimeout,
|
|
111
125
|
clearTimer = clearTimeout,
|
|
112
126
|
} = {}) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
const parsedConcurrency = Math.floor(Number(concurrency));
|
|
128
|
+
this.concurrency = Number.isFinite(parsedConcurrency) && parsedConcurrency >= 1
|
|
129
|
+
? parsedConcurrency
|
|
130
|
+
: Infinity;
|
|
117
131
|
this.maxQueue = configuredQueueBound(maxQueue);
|
|
118
132
|
this.now = now;
|
|
119
133
|
this.setTimer = setTimer;
|
|
@@ -160,7 +174,9 @@ export class ProviderAdmissionScheduler {
|
|
|
160
174
|
limit: this.concurrency,
|
|
161
175
|
cooldownUntil: 0,
|
|
162
176
|
recoverySuccesses: 0,
|
|
177
|
+
recoveryTarget: null,
|
|
163
178
|
cooldownTimer: null,
|
|
179
|
+
fairCursor: null,
|
|
164
180
|
};
|
|
165
181
|
if (!lane.adaptive || until <= lane.cooldownUntil) return false;
|
|
166
182
|
this.lanes.set(laneKey, lane);
|
|
@@ -175,8 +191,15 @@ export class ProviderAdmissionScheduler {
|
|
|
175
191
|
return true;
|
|
176
192
|
}
|
|
177
193
|
|
|
178
|
-
run(key, task, {
|
|
194
|
+
run(key, task, {
|
|
195
|
+
signal = null,
|
|
196
|
+
onCooldownWait = null,
|
|
197
|
+
ownerKey = null,
|
|
198
|
+
priority = 'user-visible',
|
|
199
|
+
} = {}) {
|
|
179
200
|
const laneKey = String(key || 'provider:default');
|
|
201
|
+
const owner = String(ownerKey || 'anonymous').trim().slice(0, 240) || 'anonymous';
|
|
202
|
+
const taskPriority = normalizePriority(priority);
|
|
180
203
|
// Provider-local recovery may recursively call this.send(). It already
|
|
181
204
|
// owns a slot, so reacquiring the same lane could deadlock a full wave.
|
|
182
205
|
const current = this.context.getStore();
|
|
@@ -193,7 +216,9 @@ export class ProviderAdmissionScheduler {
|
|
|
193
216
|
limit: this.concurrency,
|
|
194
217
|
cooldownUntil: 0,
|
|
195
218
|
recoverySuccesses: 0,
|
|
219
|
+
recoveryTarget: null,
|
|
196
220
|
cooldownTimer: null,
|
|
221
|
+
fairCursor: null,
|
|
197
222
|
};
|
|
198
223
|
this.lanes.set(laneKey, lane);
|
|
199
224
|
// Long cooldown = quota window. Fail fast with a visible error; short
|
|
@@ -206,7 +231,17 @@ export class ProviderAdmissionScheduler {
|
|
|
206
231
|
return Promise.reject(new ProviderAdmissionQueueOverflowError(laneKey, this.maxQueue));
|
|
207
232
|
}
|
|
208
233
|
return new Promise((resolve, reject) => {
|
|
209
|
-
const item = {
|
|
234
|
+
const item = {
|
|
235
|
+
task,
|
|
236
|
+
signal,
|
|
237
|
+
resolve,
|
|
238
|
+
reject,
|
|
239
|
+
canceled: false,
|
|
240
|
+
onAbort: null,
|
|
241
|
+
ownerKey: owner,
|
|
242
|
+
priority: taskPriority,
|
|
243
|
+
queuedAt: this.now(),
|
|
244
|
+
};
|
|
210
245
|
if (signal) {
|
|
211
246
|
item.onAbort = () => {
|
|
212
247
|
if (item.started || item.canceled) return;
|
|
@@ -236,6 +271,28 @@ export class ProviderAdmissionScheduler {
|
|
|
236
271
|
});
|
|
237
272
|
}
|
|
238
273
|
|
|
274
|
+
_nextQueueIndex(lane) {
|
|
275
|
+
if (!lane.queue.length) return -1;
|
|
276
|
+
const bestRank = Math.min(...lane.queue.map((item) =>
|
|
277
|
+
PRIORITY_RANK[normalizePriority(item.priority)]));
|
|
278
|
+
const eligible = lane.queue
|
|
279
|
+
.map((item, index) => ({ item, index }))
|
|
280
|
+
.filter(({ item }) => PRIORITY_RANK[normalizePriority(item.priority)] === bestRank);
|
|
281
|
+
const owners = [];
|
|
282
|
+
for (const { item } of eligible) {
|
|
283
|
+
const owner = item.ownerKey || 'anonymous';
|
|
284
|
+
if (!owners.includes(owner)) owners.push(owner);
|
|
285
|
+
}
|
|
286
|
+
let selected = owners[0] || 'anonymous';
|
|
287
|
+
if (owners.length > 1) {
|
|
288
|
+
const prior = owners.indexOf(lane.fairCursor);
|
|
289
|
+
selected = owners[(prior + 1 + owners.length) % owners.length];
|
|
290
|
+
}
|
|
291
|
+
lane.fairCursor = selected;
|
|
292
|
+
return eligible.find(({ item }) => (item.ownerKey || 'anonymous') === selected)?.index
|
|
293
|
+
?? eligible[0].index;
|
|
294
|
+
}
|
|
295
|
+
|
|
239
296
|
_drain(key, lane) {
|
|
240
297
|
if (lane.adaptive && lane.cooldownUntil > this.now()) {
|
|
241
298
|
if (lane.cooldownUntil - this.now() > PROVIDER_COOLDOWN_FAIL_FAST_MS) {
|
|
@@ -245,7 +302,9 @@ export class ProviderAdmissionScheduler {
|
|
|
245
302
|
return;
|
|
246
303
|
}
|
|
247
304
|
while (lane.active < lane.limit && lane.queue.length) {
|
|
248
|
-
const
|
|
305
|
+
const index = this._nextQueueIndex(lane);
|
|
306
|
+
if (index < 0) break;
|
|
307
|
+
const [item] = lane.queue.splice(index, 1);
|
|
249
308
|
if (item.canceled) {
|
|
250
309
|
this._detach(item);
|
|
251
310
|
continue;
|
|
@@ -268,7 +327,14 @@ export class ProviderAdmissionScheduler {
|
|
|
268
327
|
item.signal.addEventListener('abort', running.parentAbort, { once: true });
|
|
269
328
|
}
|
|
270
329
|
this.running.add(running);
|
|
271
|
-
const admission = {
|
|
330
|
+
const admission = {
|
|
331
|
+
scheduler: this,
|
|
332
|
+
key,
|
|
333
|
+
lane,
|
|
334
|
+
ownerKey: item.ownerKey,
|
|
335
|
+
priority: item.priority,
|
|
336
|
+
rateLimited: false,
|
|
337
|
+
};
|
|
272
338
|
this.context.run(admission, () => Promise.resolve().then(() => item.task(controller.signal)))
|
|
273
339
|
.then((value) => {
|
|
274
340
|
// A retry that eventually succeeds is not evidence that
|
|
@@ -292,6 +358,25 @@ export class ProviderAdmissionScheduler {
|
|
|
292
358
|
if (this._canDeleteLane(lane)) this._deleteLane(key, lane);
|
|
293
359
|
}
|
|
294
360
|
|
|
361
|
+
snapshot() {
|
|
362
|
+
const now = this.now();
|
|
363
|
+
return {
|
|
364
|
+
concurrency: Number.isFinite(this.concurrency) ? this.concurrency : null,
|
|
365
|
+
lanes: [...this.lanes.entries()].map(([key, lane]) => ({
|
|
366
|
+
key,
|
|
367
|
+
active: lane.active,
|
|
368
|
+
queued: lane.queue.length,
|
|
369
|
+
owners: new Set(lane.queue.map((item) => item.ownerKey || 'anonymous')).size,
|
|
370
|
+
limit: Number.isFinite(lane.limit) ? lane.limit : null,
|
|
371
|
+
cooldownMs: Math.max(0, lane.cooldownUntil - now),
|
|
372
|
+
oldestWaitMs: lane.queue.reduce(
|
|
373
|
+
(oldest, item) => Math.max(oldest, Math.max(0, now - (item.queuedAt || now))),
|
|
374
|
+
0,
|
|
375
|
+
),
|
|
376
|
+
})),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
295
380
|
_recordFailure(key, lane, error) {
|
|
296
381
|
if (!lane.adaptive) return false;
|
|
297
382
|
const status = Number(error?.httpStatus || error?.status || error?.response?.status || 0);
|
|
@@ -300,7 +385,14 @@ export class ProviderAdmissionScheduler {
|
|
|
300
385
|
if (this.reportedRateLimits.has(error)) return false;
|
|
301
386
|
this.reportedRateLimits.add(error);
|
|
302
387
|
}
|
|
303
|
-
|
|
388
|
+
const observed = Number.isFinite(lane.limit)
|
|
389
|
+
? lane.limit
|
|
390
|
+
: Math.max(1, lane.active);
|
|
391
|
+
lane.recoveryTarget = Math.max(
|
|
392
|
+
Number(lane.recoveryTarget) || 0,
|
|
393
|
+
observed,
|
|
394
|
+
);
|
|
395
|
+
lane.limit = Math.max(1, Math.floor(observed / 2));
|
|
304
396
|
lane.recoverySuccesses = 0;
|
|
305
397
|
const now = this.now();
|
|
306
398
|
lane.cooldownUntil = Math.max(
|
|
@@ -348,6 +440,7 @@ export class ProviderAdmissionScheduler {
|
|
|
348
440
|
lane.cooldownUntil = 0;
|
|
349
441
|
lane.limit = this.concurrency;
|
|
350
442
|
lane.recoverySuccesses = 0;
|
|
443
|
+
lane.recoveryTarget = null;
|
|
351
444
|
if (lane.cooldownTimer) {
|
|
352
445
|
this.clearTimer(lane.cooldownTimer);
|
|
353
446
|
lane.cooldownTimer = null;
|
|
@@ -364,11 +457,15 @@ export class ProviderAdmissionScheduler {
|
|
|
364
457
|
}
|
|
365
458
|
|
|
366
459
|
_recordSuccess(lane) {
|
|
367
|
-
if (!lane.adaptive || lane.limit
|
|
460
|
+
if (!lane.adaptive || !Number.isFinite(lane.limit)) return;
|
|
368
461
|
lane.recoverySuccesses += 1;
|
|
369
462
|
if (lane.recoverySuccesses >= lane.limit) {
|
|
370
463
|
lane.limit += 1;
|
|
371
464
|
lane.recoverySuccesses = 0;
|
|
465
|
+
if (lane.recoveryTarget && lane.limit >= lane.recoveryTarget) {
|
|
466
|
+
lane.limit = this.concurrency;
|
|
467
|
+
lane.recoveryTarget = null;
|
|
468
|
+
}
|
|
372
469
|
}
|
|
373
470
|
}
|
|
374
471
|
|
|
@@ -505,6 +602,8 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
|
|
|
505
602
|
});
|
|
506
603
|
}, {
|
|
507
604
|
signal,
|
|
605
|
+
ownerKey: opts.admissionOwner || opts.sessionId || null,
|
|
606
|
+
priority: opts.admissionPriority || 'user-visible',
|
|
508
607
|
onCooldownWait: (waitMs) => {
|
|
509
608
|
// Display-only: the TUI/desktop 'reconnecting' stage already
|
|
510
609
|
// renders a custom verb, so no new stage vocabulary is needed.
|
|
@@ -23,6 +23,7 @@ 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 { parseProviderJsonBatch } from './stream-json-pool.mjs';
|
|
26
27
|
import { stampStreamOutcome, STREAM_TRANSPORTS, STREAM_OUTCOME_VERSION } from './lib/stream-outcome.mjs';
|
|
27
28
|
|
|
28
29
|
/** Bounded mid-stream SSE retries (transient stream loss); shared with anthropic.mjs.
|
|
@@ -486,7 +487,10 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
486
487
|
if (!data) continue;
|
|
487
488
|
|
|
488
489
|
try {
|
|
489
|
-
|
|
490
|
+
// A completed reader.read() owns this frame even if abort
|
|
491
|
+
// races immediately afterward; the next read observes the
|
|
492
|
+
// cancellation after this frame is relayed.
|
|
493
|
+
const [event] = await parseProviderJsonBatch([data]);
|
|
490
494
|
|
|
491
495
|
// SEMANTIC idle reset (Part A): reset the idle timer ONLY for
|
|
492
496
|
// real progress events, NOT for Anthropic keepalives. Anthropic
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
collectGeminiGroundingSources,
|
|
24
24
|
parseGeminiTextPartMetadata,
|
|
25
25
|
} from './gemini-schema.mjs';
|
|
26
|
+
import { parseProviderJsonBatch } from './stream-json-pool.mjs';
|
|
26
27
|
|
|
27
28
|
export const GEMINI_FIRST_BYTE_TIMEOUT_MS = resolveTimeoutMs(
|
|
28
29
|
'MIXDOG_GEMINI_FIRST_BYTE_TIMEOUT_MS',
|
|
@@ -131,6 +132,19 @@ function geminiChunkHasFunctionCall(chunk) {
|
|
|
131
132
|
return parts.some((p) => p && p.functionCall);
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
export function geminiChunkProgressKind(chunk) {
|
|
136
|
+
const parts = chunk?.candidates?.[0]?.content?.parts;
|
|
137
|
+
if (!Array.isArray(parts)) return 'transport';
|
|
138
|
+
if (parts.some((part) => part?.functionCall)) return 'tool';
|
|
139
|
+
if (parts.some((part) => part?.thought === true && typeof part.text === 'string' && part.text)) {
|
|
140
|
+
return 'reasoning';
|
|
141
|
+
}
|
|
142
|
+
if (parts.some((part) => part?.thought !== true && typeof part?.text === 'string' && part.text)) {
|
|
143
|
+
return 'text';
|
|
144
|
+
}
|
|
145
|
+
return 'transport';
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
/**
|
|
135
149
|
* Aggregate streamed GenerateContentResponse chunks into one response object
|
|
136
150
|
* (same shape as a non-streaming generateContent JSON body).
|
|
@@ -256,7 +270,7 @@ export function createGeminiTextLeakGuard({ knownToolNames, onTextDelta, onToolC
|
|
|
256
270
|
};
|
|
257
271
|
leakedCalls.push(call);
|
|
258
272
|
try { onToolCall?.(call); } catch {}
|
|
259
|
-
try { onStreamDelta?.(); } catch {}
|
|
273
|
+
try { onStreamDelta?.('tool'); } catch {}
|
|
260
274
|
};
|
|
261
275
|
|
|
262
276
|
const pumpLeakBuffer = (final) => {
|
|
@@ -407,6 +421,7 @@ export async function consumeGeminiRestStreamResponse(response, { signal, onStre
|
|
|
407
421
|
if (done) break;
|
|
408
422
|
resetIdleTimer();
|
|
409
423
|
buffer += decoder.decode(value, { stream: true });
|
|
424
|
+
const payloads = [];
|
|
410
425
|
let lineEnd;
|
|
411
426
|
while ((lineEnd = buffer.indexOf('\n')) >= 0) {
|
|
412
427
|
let line = buffer.slice(0, lineEnd);
|
|
@@ -415,18 +430,24 @@ export async function consumeGeminiRestStreamResponse(response, { signal, onStre
|
|
|
415
430
|
if (!line.startsWith('data: ')) continue;
|
|
416
431
|
const data = line.slice(6).trim();
|
|
417
432
|
if (!data || data === '[DONE]') continue;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
433
|
+
payloads.push(data);
|
|
434
|
+
}
|
|
435
|
+
let parsedChunks;
|
|
436
|
+
try {
|
|
437
|
+
// Preserve the established delivery boundary: once read()
|
|
438
|
+
// returned the bytes, parse and relay them before the next
|
|
439
|
+
// iteration observes cancellation.
|
|
440
|
+
parsedChunks = await parseProviderJsonBatch(payloads);
|
|
441
|
+
} catch (cause) {
|
|
442
|
+
throw geminiStreamCorruptionError(`${label} corrupt SSE JSON`, cause);
|
|
443
|
+
}
|
|
444
|
+
for (const parsed of parsedChunks) {
|
|
424
445
|
if (!sawStreamChunk) {
|
|
425
446
|
sawStreamChunk = true;
|
|
426
447
|
clearFirstByteTimer();
|
|
427
448
|
}
|
|
428
449
|
allChunks.push(parsed);
|
|
429
|
-
try { onStreamDelta?.(); } catch {}
|
|
450
|
+
try { onStreamDelta?.(geminiChunkProgressKind(parsed)); } catch {}
|
|
430
451
|
if (!sawFunctionCall && geminiChunkHasFunctionCall(parsed)) sawFunctionCall = true;
|
|
431
452
|
if (onTextDelta || textLeakGuard) {
|
|
432
453
|
const t = geminiChunkText(parsed);
|
|
@@ -440,13 +461,13 @@ export async function consumeGeminiRestStreamResponse(response, { signal, onStre
|
|
|
440
461
|
const data = line.slice(6).trim();
|
|
441
462
|
if (data && data !== '[DONE]') {
|
|
442
463
|
try {
|
|
443
|
-
const parsed =
|
|
464
|
+
const [parsed] = await parseProviderJsonBatch([data]);
|
|
444
465
|
if (!sawStreamChunk) {
|
|
445
466
|
sawStreamChunk = true;
|
|
446
467
|
clearFirstByteTimer();
|
|
447
468
|
}
|
|
448
469
|
allChunks.push(parsed);
|
|
449
|
-
try { onStreamDelta?.(); } catch {}
|
|
470
|
+
try { onStreamDelta?.(geminiChunkProgressKind(parsed)); } catch {}
|
|
450
471
|
if (!sawFunctionCall && geminiChunkHasFunctionCall(parsed)) sawFunctionCall = true;
|
|
451
472
|
if (onTextDelta || textLeakGuard) {
|
|
452
473
|
const t = geminiChunkText(parsed);
|
|
@@ -677,7 +698,7 @@ export async function consumeGeminiSdkStream(streamResult, {
|
|
|
677
698
|
}
|
|
678
699
|
resetIdleTimer();
|
|
679
700
|
if (step.value) collectedChunks.push(step.value);
|
|
680
|
-
try { onStreamDelta?.(); } catch {}
|
|
701
|
+
try { onStreamDelta?.(geminiChunkProgressKind(step.value)); } catch {}
|
|
681
702
|
if (!sawFunctionCall && geminiChunkHasFunctionCall(step.value)) sawFunctionCall = true;
|
|
682
703
|
if (onTextDelta || textLeakGuard) {
|
|
683
704
|
const t = geminiChunkText(step.value);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
2
|
import { getAgentApiKey } from '../../../shared/provider-api-key.mjs';
|
|
3
|
-
import { withRetry } from './retry-classifier.mjs';
|
|
3
|
+
import { canFallbackNonStreaming, withRetry } from './retry-classifier.mjs';
|
|
4
4
|
import { traceAgentUsage, appendAgentTrace } from '../agent-trace.mjs';
|
|
5
5
|
import {
|
|
6
6
|
PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
@@ -158,6 +158,53 @@ export class GeminiProvider {
|
|
|
158
158
|
return this.config?.apiKey || process.env.GEMINI_API_KEY || '';
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* cc parity (claude.ts:2504): re-issue a dead stream ONCE as a
|
|
163
|
+
* non-streaming generateContent call instead of failing the turn.
|
|
164
|
+
* Narrower than cc by design — canFallbackNonStreaming() clears only a
|
|
165
|
+
* stream that exposed nothing, so rendered text is never duplicated and a
|
|
166
|
+
* dispatched tool can never run twice. Returns the aggregated response, or
|
|
167
|
+
* null when the failure is ineligible or the fallback itself fails.
|
|
168
|
+
*/
|
|
169
|
+
async _recoverGeminiNonStreaming({ streamErr, signal, opts, model, generate }) {
|
|
170
|
+
if (!canFallbackNonStreaming(streamErr, { signal })) return null;
|
|
171
|
+
let aggregated;
|
|
172
|
+
try {
|
|
173
|
+
try { opts?.onStageChange?.('requesting'); } catch { /* heartbeat best-effort */ }
|
|
174
|
+
aggregated = await generate(signal || undefined);
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (!aggregated || !Array.isArray(aggregated.candidates) || aggregated.candidates.length === 0) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`[gemini] stream failed (${streamErr?.code || streamErr?.message || 'unknown'}); `
|
|
184
|
+
+ `recovered via non-streaming generateContent\n`,
|
|
185
|
+
);
|
|
186
|
+
} catch { /* best-effort */ }
|
|
187
|
+
try {
|
|
188
|
+
appendAgentTrace({
|
|
189
|
+
sessionId: opts?.sessionId || opts?.session?.id || null,
|
|
190
|
+
iteration: Number.isFinite(Number(opts?.iteration)) ? Number(opts.iteration) : null,
|
|
191
|
+
kind: 'transport_fallback',
|
|
192
|
+
provider: 'gemini',
|
|
193
|
+
model,
|
|
194
|
+
transport: 'non-streaming',
|
|
195
|
+
payload: {
|
|
196
|
+
from: 'stream',
|
|
197
|
+
to: 'non-streaming',
|
|
198
|
+
reason: streamErr?.retryClassifier || streamErr?.code || streamErr?.message || 'stream_failed',
|
|
199
|
+
error_code: streamErr?.code || null,
|
|
200
|
+
error_http_status: Number(streamErr?.httpStatus || streamErr?.status || 0) || null,
|
|
201
|
+
error_classifier: streamErr?.retryClassifier || streamErr?.midstreamClassifier || null,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
} catch { /* best-effort */ }
|
|
205
|
+
return aggregated;
|
|
206
|
+
}
|
|
207
|
+
|
|
161
208
|
// Explicit cachedContents API. The implicit cache layer on Gemini 3.x
|
|
162
209
|
// does not surface cachedContentTokenCount in usageMetadata, so the only
|
|
163
210
|
// way to obtain measurable + billable cache savings is to register the
|
|
@@ -655,7 +702,30 @@ export class GeminiProvider {
|
|
|
655
702
|
}
|
|
656
703
|
return await this._doSend(messages, model, tools, opts, { skipExplicitCache: true });
|
|
657
704
|
}
|
|
658
|
-
|
|
705
|
+
const recovered = await this._recoverGeminiNonStreaming({
|
|
706
|
+
streamErr: err,
|
|
707
|
+
signal: totalSignal,
|
|
708
|
+
opts,
|
|
709
|
+
model: useModel,
|
|
710
|
+
generate: async (fallbackSignal) => {
|
|
711
|
+
const nonStreamUrl = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(useModel)}:generateContent?key=${encodeURIComponent(apiKey)}`;
|
|
712
|
+
const res = await this._fetch(nonStreamUrl, {
|
|
713
|
+
method: 'POST',
|
|
714
|
+
headers: { 'Content-Type': 'application/json' },
|
|
715
|
+
body: JSON.stringify(body),
|
|
716
|
+
signal: fallbackSignal,
|
|
717
|
+
dispatcher: getLlmDispatcher(),
|
|
718
|
+
});
|
|
719
|
+
if (!res.ok) {
|
|
720
|
+
const text = await res.text().catch(() => '');
|
|
721
|
+
throw geminiRestError(res, text, 'Gemini REST generateContent');
|
|
722
|
+
}
|
|
723
|
+
return await res.json();
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
if (!recovered) throw err;
|
|
727
|
+
textLeakGuard = null;
|
|
728
|
+
response = recovered;
|
|
659
729
|
} finally {
|
|
660
730
|
restPassthrough.cleanup();
|
|
661
731
|
}
|
|
@@ -749,6 +819,23 @@ export class GeminiProvider {
|
|
|
749
819
|
},
|
|
750
820
|
},
|
|
751
821
|
);
|
|
822
|
+
} catch (err) {
|
|
823
|
+
const recovered = await this._recoverGeminiNonStreaming({
|
|
824
|
+
streamErr: err,
|
|
825
|
+
signal: totalSignal,
|
|
826
|
+
opts,
|
|
827
|
+
model: useModel,
|
|
828
|
+
generate: async (fallbackSignal) => {
|
|
829
|
+
const result = await genModel.generateContent(
|
|
830
|
+
{ contents },
|
|
831
|
+
{ signal: fallbackSignal },
|
|
832
|
+
);
|
|
833
|
+
return result?.response ?? null;
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
if (!recovered) throw err;
|
|
837
|
+
textLeakGuard = null;
|
|
838
|
+
response = recovered;
|
|
752
839
|
} finally {
|
|
753
840
|
sdkPassthrough.cleanup();
|
|
754
841
|
}
|
|
@@ -23,7 +23,7 @@ import { boundProviderAuthPath } from '../../../shared/provider-auth-binding.mjs
|
|
|
23
23
|
import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
|
|
24
24
|
import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
25
25
|
import { makeModelCache } from './model-cache.mjs';
|
|
26
|
-
import { OpenAICompatProvider } from './openai-compat.mjs';
|
|
26
|
+
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
27
27
|
import { createTimeoutSignal } from '../stall-policy.mjs';
|
|
28
28
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
29
29
|
import { normalizeGrokToolSchemas } from './lib/grok-tool-schema.mjs';
|
|
@@ -205,6 +205,17 @@ export class GrokOAuthProvider {
|
|
|
205
205
|
constructor(config) {
|
|
206
206
|
this.config = config || {};
|
|
207
207
|
this.tokens = loadTokens();
|
|
208
|
+
// The credential/header-bound inner provider is created lazily, but its
|
|
209
|
+
// heavy SDK + undici graph and proxy socket are not request-specific.
|
|
210
|
+
// Pay both during the daemon's process-wide provider warmup so first
|
|
211
|
+
// Grok send only builds the cheap inner client.
|
|
212
|
+
try { preloadOpenAICompatRuntime(); } catch { /* authoritative send surfaces load failures */ }
|
|
213
|
+
if (this.config?.preconnect !== false) {
|
|
214
|
+
const warm = typeof this.config?.preconnectFn === 'function'
|
|
215
|
+
? this.config.preconnectFn
|
|
216
|
+
: preconnect;
|
|
217
|
+
try { warm(PROXY_BASE_URL); } catch { /* best-effort */ }
|
|
218
|
+
}
|
|
208
219
|
}
|
|
209
220
|
|
|
210
221
|
async ensureAuth({ forceRefresh = false } = {}) {
|
|
@@ -59,6 +59,22 @@ export function xaiResponsesCacheRouting(opts, params, rawTools, model) {
|
|
|
59
59
|
const scope = String(opts?.xaiResponsesCacheScope || process.env.MIXDOG_XAI_RESPONSES_CACHE_SCOPE || 'prefix')
|
|
60
60
|
.trim()
|
|
61
61
|
.toLowerCase();
|
|
62
|
+
// 'none' omits prompt_cache_key entirely, matching xAI's own reference
|
|
63
|
+
// sampler (grok-build sends prompt_cache_key: None) and zed's xAI provider
|
|
64
|
+
// (supports_prompt_cache_key() == false): the service falls back to
|
|
65
|
+
// automatic prompt-prefix caching with no client-supplied lane. Callers
|
|
66
|
+
// must treat a null key as "do not send the field".
|
|
67
|
+
if (scope === 'none' || scope === 'off' || scope === 'omit') {
|
|
68
|
+
const sessionId = String(opts?.sessionId || opts?.session?.id || '').trim();
|
|
69
|
+
const prefixHash = traceHash(xaiPrefixSeed({ opts, params, rawTools, model }));
|
|
70
|
+
return {
|
|
71
|
+
key: null,
|
|
72
|
+
mode: 'none',
|
|
73
|
+
seedHash: null,
|
|
74
|
+
prefixHash,
|
|
75
|
+
ownerSessionHash: sessionId ? traceHash(sessionId) : null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
62
78
|
if (scope !== 'prefix') {
|
|
63
79
|
return xaiCacheRouting(opts, params, rawTools, model);
|
|
64
80
|
}
|
|
@@ -183,7 +199,9 @@ export function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseI
|
|
|
183
199
|
if (['0', 'false', 'off', 'none', 'disabled'].includes(mode)) return false;
|
|
184
200
|
if (['1', 'true', 'on', 'always', 'force'].includes(mode)) return true;
|
|
185
201
|
}
|
|
186
|
-
|
|
202
|
+
// A generate:false response can still carry provider usage/quota. Keep it
|
|
203
|
+
// strictly opt-in; transport-only HTTP/WS preconnect remains cost-free.
|
|
204
|
+
return false;
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
// Codex-aligned default: no compat cache lane/serialization. Keep the override
|