mixdog 0.9.51 → 0.9.53
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/package.json +5 -3
- package/scripts/abort-recovery-test.mjs +17 -1
- package/scripts/agent-model-liveness-test.mjs +79 -2
- package/scripts/anthropic-admission-retry-integration-test.mjs +119 -0
- package/scripts/anthropic-transport-policy-test.mjs +466 -0
- package/scripts/atomic-lock-tryonce-test.mjs +60 -1
- package/scripts/bench-run.mjs +2 -2
- package/scripts/build-tui.mjs +13 -1
- package/scripts/channel-daemon-smoke.mjs +630 -10
- package/scripts/code-graph-aggregate-cwd-test.mjs +41 -37
- package/scripts/code-graph-disk-hit-test.mjs +11 -0
- package/scripts/code-graph-root-federation-test.mjs +273 -0
- package/scripts/compact-pressure-test.mjs +159 -1
- package/scripts/compact-smoke.mjs +80 -0
- package/scripts/context-mcp-metering-test.mjs +1350 -19
- package/scripts/deferred-tool-loading-test.mjs +17 -0
- package/scripts/desktop-session-bridge-test.mjs +704 -0
- package/scripts/freevar-smoke.mjs +7 -4
- package/scripts/gemini-provider-test.mjs +1053 -0
- package/scripts/interrupted-turn-history-test.mjs +371 -0
- package/scripts/lifecycle-api-test.mjs +137 -0
- package/scripts/max-output-recovery-test.mjs +86 -0
- package/scripts/mcp-grace-deferred-test.mjs +89 -13
- package/scripts/memory-core-input-test.mjs +10 -0
- package/scripts/memory-pg-recovery-test.mjs +59 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +387 -6
- package/scripts/openai-ws-early-settle-test.mjs +40 -0
- package/scripts/parent-abort-link-test.mjs +24 -0
- package/scripts/process-lifecycle-test.mjs +447 -0
- package/scripts/provider-admission-scheduler-test.mjs +582 -0
- package/scripts/provider-contract-test.mjs +525 -0
- package/scripts/provider-toolcall-test.mjs +492 -11
- package/scripts/reactive-compact-persist-smoke.mjs +59 -0
- package/scripts/resource-admission-test.mjs +789 -0
- package/scripts/session-orphan-sweep-test.mjs +27 -1
- package/scripts/shell-jobs-windows-hide-test.mjs +1 -1
- package/scripts/steering-drain-buckets-test.mjs +18 -0
- package/scripts/toolcall-args-test.mjs +14 -6
- package/scripts/tui-transcript-perf-test.mjs +5 -7
- package/src/cli.mjs +15 -2
- package/src/lib/keychain-cjs.cjs +36 -23
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +27 -13
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +4 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
- package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +331 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +114 -308
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +136 -286
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +42 -5
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +554 -42
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +67 -18
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +84 -150
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -119
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +54 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +28 -74
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +10 -80
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +136 -20
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +94 -33
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +201 -123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +50 -45
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +78 -12
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +44 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +49 -8
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +229 -111
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +130 -63
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +99 -32
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +37 -224
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +81 -42
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +8 -28
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +42 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +220 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +20 -4
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +17 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +353 -108
- package/src/runtime/agent/orchestrator/stall-policy.mjs +2 -12
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +74 -37
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +223 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +317 -250
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +176 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +108 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +93 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +125 -85
- package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
- package/src/runtime/channels/backends/discord.mjs +6 -6
- package/src/runtime/channels/lib/config.mjs +15 -2
- package/src/runtime/channels/lib/memory-client.mjs +20 -3
- package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
- package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
- package/src/runtime/channels/lib/worker-main.mjs +16 -5
- package/src/runtime/memory/index.mjs +46 -202
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
- package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
- package/src/runtime/memory/lib/pg/adapter.mjs +84 -10
- package/src/runtime/memory/lib/pg/process.mjs +91 -47
- package/src/runtime/memory/lib/pg/supervisor.mjs +50 -17
- package/src/runtime/memory/lib/query-handlers.mjs +2 -122
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
- package/src/runtime/search/lib/http-fetch.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +28 -150
- package/src/runtime/shared/config.mjs +58 -13
- package/src/runtime/shared/llm/cost.mjs +14 -4
- package/src/runtime/shared/memory-snapshot.mjs +236 -0
- package/src/runtime/shared/process-lifecycle.mjs +436 -0
- package/src/runtime/shared/process-shutdown.mjs +33 -4
- package/src/runtime/shared/resource-admission.mjs +364 -0
- package/src/runtime/shared/staged-child-result.mjs +19 -0
- package/src/session-runtime/channel-config-api.mjs +7 -7
- package/src/session-runtime/config-lifecycle.mjs +20 -17
- package/src/session-runtime/context-status.mjs +38 -27
- package/src/session-runtime/cwd-plugins.mjs +9 -7
- package/src/session-runtime/env.mjs +1 -2
- package/src/session-runtime/hitch-profile.mjs +45 -0
- package/src/session-runtime/lifecycle-api.mjs +60 -13
- package/src/session-runtime/mcp-glue.mjs +6 -11
- package/src/session-runtime/provider-init-key.mjs +17 -0
- package/src/session-runtime/provider-request-tools.mjs +72 -0
- package/src/session-runtime/runtime-core.mjs +49 -105
- package/src/session-runtime/runtime-paths.mjs +20 -0
- package/src/session-runtime/runtime-tool-routing.mjs +55 -0
- package/src/session-runtime/session-turn-api.mjs +5 -4
- package/src/session-runtime/tool-catalog-data.mjs +51 -0
- package/src/session-runtime/tool-catalog.mjs +390 -104
- package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
- package/src/standalone/agent-tool/worker-rows.mjs +93 -0
- package/src/standalone/agent-tool.mjs +29 -200
- package/src/standalone/channel-admin.mjs +29 -0
- package/src/standalone/channel-daemon-client.mjs +200 -38
- package/src/standalone/channel-daemon-transport.mjs +136 -9
- package/src/standalone/channel-worker.mjs +79 -12
- package/src/tui/App.jsx +11 -5
- package/src/tui/dist/index.mjs +275 -343
- package/src/tui/engine/session-api-ext.mjs +50 -6
- package/src/tui/engine/session-api.mjs +1 -1
- package/src/tui/engine/turn.mjs +10 -6
- package/src/tui/engine.mjs +13 -4
- package/src/tui/index.jsx +5 -1
- package/src/tui/lib/voice-setup.mjs +21 -5
- package/scripts/_devtools-stub.mjs +0 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -288
|
@@ -43,6 +43,53 @@ export function applyOpenAIDirectFastTier(body, model, opts) {
|
|
|
43
43
|
return body;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function shouldFallbackDirectTransport(err, options) {
|
|
47
|
+
const status = Number(err?.httpStatus || err?.status || 0);
|
|
48
|
+
const unsafeToRetry = err?.liveTextEmitted === true
|
|
49
|
+
|| err?.emittedToolCall === true
|
|
50
|
+
|| err?.unsafeToRetry === true;
|
|
51
|
+
if (unsafeToRetry || options?.signal?.aborted || options?.enabled === false) return false;
|
|
52
|
+
// Public OpenAI can reject the WebSocket upgrade while still supporting
|
|
53
|
+
// the same request over HTTP/SSE. Only the shared transport's explicit
|
|
54
|
+
// handshake marker may turn these application-looking statuses into a
|
|
55
|
+
// one-shot transport fallback.
|
|
56
|
+
if (err?.wsFailurePhase === 'handshake'
|
|
57
|
+
&& err?.wsHttpFallbackEligible === true
|
|
58
|
+
&& [403, 404, 429].includes(status)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// These are application/auth/quota outcomes from the public Responses API,
|
|
62
|
+
// not evidence that WebSocket transport is unhealthy. Reissuing them over
|
|
63
|
+
// HTTP cannot recover and can duplicate an accepted throttled request.
|
|
64
|
+
if (status >= 400 && status < 500) return false;
|
|
65
|
+
return shouldFallbackTransport(err, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function directOpenAiHandshakeErrorPolicy({ status }) {
|
|
69
|
+
if ([403, 404, 429].includes(Number(status))) {
|
|
70
|
+
return { retry: false, httpFallback: true };
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const DIRECT_WS_TEST_SEAM_KEYS = Object.freeze([
|
|
76
|
+
'_acquireWithRetryFn',
|
|
77
|
+
'_streamFn',
|
|
78
|
+
'_sendFrameFn',
|
|
79
|
+
'_sleepFn',
|
|
80
|
+
'_sendSpanTraceFn',
|
|
81
|
+
'_agentTraceFn',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
function directWebSocketTestSeams(value) {
|
|
85
|
+
if (!value || typeof value !== 'object') return {};
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const key of DIRECT_WS_TEST_SEAM_KEYS) {
|
|
88
|
+
if (typeof value[key] === 'function') out[key] = value[key];
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
export class OpenAIDirectProvider {
|
|
47
94
|
// input_tokens INCLUDES cached tokens (OpenAI convention). See registry.mjs.
|
|
48
95
|
static inputExcludesCache = false;
|
|
@@ -56,7 +103,7 @@ export class OpenAIDirectProvider {
|
|
|
56
103
|
if (!k) throw new Error('OPENAI_API_KEY not configured (providers.openai.apiKey)');
|
|
57
104
|
return k;
|
|
58
105
|
}
|
|
59
|
-
// Auth-recovery mirror of openai-compat.reloadApiKey: on a 401
|
|
106
|
+
// Auth-recovery mirror of openai-compat.reloadApiKey: on a 401 the key
|
|
60
107
|
// was likely rotated after this provider instance was built, so re-read
|
|
61
108
|
// only OpenAI's environment/keychain sources before the single retry.
|
|
62
109
|
// Returns the fresh key (or null if none) — no client to rebuild here since
|
|
@@ -140,7 +187,18 @@ export class OpenAIDirectProvider {
|
|
|
140
187
|
iteration,
|
|
141
188
|
useModel,
|
|
142
189
|
};
|
|
143
|
-
|
|
190
|
+
// Keep the same deterministic transport seams as openai-oauth. Besides
|
|
191
|
+
// making the direct path regression-testable without a live request,
|
|
192
|
+
// this lets embedded callers supply their already-instrumented
|
|
193
|
+
// Responses transports without changing the public request envelope.
|
|
194
|
+
const sendWs = typeof opts._sendViaWebSocketFn === 'function'
|
|
195
|
+
? opts._sendViaWebSocketFn
|
|
196
|
+
: sendViaWebSocket;
|
|
197
|
+
const sendHttp = typeof opts._sendViaHttpSseFn === 'function'
|
|
198
|
+
? opts._sendViaHttpSseFn
|
|
199
|
+
: sendViaHttpSse;
|
|
200
|
+
const wsTestSeams = directWebSocketTestSeams(opts._webSocketTestSeams);
|
|
201
|
+
const dispatchWs = (a) => sendWs({
|
|
144
202
|
...common,
|
|
145
203
|
auth: a,
|
|
146
204
|
sendOpts: opts,
|
|
@@ -151,12 +209,18 @@ export class OpenAIDirectProvider {
|
|
|
151
209
|
// the OAuth/Codex handshake headers. Direct API-key auth pins its own
|
|
152
210
|
// provider so it stays on the public (non-Codex) envelope.
|
|
153
211
|
traceProvider: 'openai-direct',
|
|
212
|
+
// Narrow deterministic seams for transport-policy regression tests.
|
|
213
|
+
// Production callers never set this object.
|
|
214
|
+
...wsTestSeams,
|
|
215
|
+
// Mandatory and deliberately applied AFTER sanitized test seams:
|
|
216
|
+
// no caller can disable or invert direct handshake provenance.
|
|
217
|
+
handshakeErrorPolicy: directOpenAiHandshakeErrorPolicy,
|
|
154
218
|
});
|
|
155
219
|
// WS→HTTP/SSE fallback mirrors the openai-oauth wrapper: the shared
|
|
156
220
|
// HTTP transport now accepts auth.type==='openai-direct' (public
|
|
157
221
|
// Responses endpoint + Bearer <apiKey>), so the api-key provider gets
|
|
158
|
-
// the same envelope.
|
|
159
|
-
//
|
|
222
|
+
// the same envelope. The direct gate denies every application 4xx and
|
|
223
|
+
// any liveTextEmitted/emittedToolCall/unsafeToRetry outcome.
|
|
160
224
|
const transportPolicy = resolveOpenAiTransportPolicy();
|
|
161
225
|
const httpFallbackEnabled = transportPolicy.allowHttpFallback
|
|
162
226
|
&& _envFlag('MIXDOG_OPENAI_HTTP_FALLBACK', true);
|
|
@@ -164,7 +228,7 @@ export class OpenAIDirectProvider {
|
|
|
164
228
|
if (!process.env.MIXDOG_QUIET_PROVIDER_LOG) {
|
|
165
229
|
process.stderr.write('[openai-ws] WebSocket unhealthy; falling back to HTTP/SSE\n');
|
|
166
230
|
}
|
|
167
|
-
return
|
|
231
|
+
return sendHttp({ ...common, auth: a, opts, fetchFn: opts._fetchFn });
|
|
168
232
|
};
|
|
169
233
|
// Transport-policy switch (MIXDOG_OAI_TRANSPORT). 'http-sse' forces the
|
|
170
234
|
// HTTP/SSE transport directly — no WS attempt, so skip the fallback log
|
|
@@ -172,7 +236,7 @@ export class OpenAIDirectProvider {
|
|
|
172
236
|
// ('auto'/'ws-full'/'ws-delta') keep the WS-first path below; ws-full vs
|
|
173
237
|
// ws-delta only affects the delta gate inside openai-ws-delta.mjs.
|
|
174
238
|
if (transportPolicy.transport === 'http') {
|
|
175
|
-
return await
|
|
239
|
+
return await sendHttp({ ...common, auth, opts, fetchFn: opts._fetchFn });
|
|
176
240
|
}
|
|
177
241
|
try {
|
|
178
242
|
return await dispatchWs(auth);
|
|
@@ -183,10 +247,12 @@ export class OpenAIDirectProvider {
|
|
|
183
247
|
const unsafeToRetry = err?.liveTextEmitted === true
|
|
184
248
|
|| err?.emittedToolCall === true
|
|
185
249
|
|| err?.unsafeToRetry === true;
|
|
186
|
-
// (1) 401
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
250
|
+
// (1) 401 → reload only OpenAI auth and retry once over WS.
|
|
251
|
+
// Ordinary 403 is a permission/policy decision, not evidence that
|
|
252
|
+
// an API key rotated. Match the OAuth path and keep it terminal.
|
|
253
|
+
// The direct fallback guard denies auth statuses, so this branch
|
|
254
|
+
// owns the single credential-reload replay.
|
|
255
|
+
if (status === 401 && err?.wsFailurePhase !== 'stream' && !unsafeToRetry) {
|
|
190
256
|
process.stderr.write(`[openai-ws] ${status} — reloading apiKey and retrying once\n`);
|
|
191
257
|
const freshKey = this.reloadApiKey();
|
|
192
258
|
if (freshKey) {
|
|
@@ -194,7 +260,7 @@ export class OpenAIDirectProvider {
|
|
|
194
260
|
try {
|
|
195
261
|
return await dispatchWs(retryAuth);
|
|
196
262
|
} catch (retryErr) {
|
|
197
|
-
if (
|
|
263
|
+
if (shouldFallbackDirectTransport(retryErr, { signal: externalSignal, enabled: httpFallbackEnabled })) {
|
|
198
264
|
return await dispatchHttp(retryAuth);
|
|
199
265
|
}
|
|
200
266
|
throw retryErr;
|
|
@@ -204,7 +270,7 @@ export class OpenAIDirectProvider {
|
|
|
204
270
|
}
|
|
205
271
|
// (2) WS transport failure → HTTP/SSE fallback (predicate handles
|
|
206
272
|
// the safety denies).
|
|
207
|
-
if (
|
|
273
|
+
if (shouldFallbackDirectTransport(err, { signal: externalSignal, enabled: httpFallbackEnabled })) {
|
|
208
274
|
return await dispatchHttp(auth);
|
|
209
275
|
}
|
|
210
276
|
throw err;
|
|
@@ -20,11 +20,44 @@ const OPENCODE_GO_CONTEXT_WINDOWS = Object.freeze({
|
|
|
20
20
|
'glm-5': 202752,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
function isAnthropicGoModel(model) {
|
|
23
|
+
export function isAnthropicGoModel(model) {
|
|
24
24
|
const id = String(model || '').toLowerCase();
|
|
25
25
|
return ANTHROPIC_MODEL_PREFIXES.some(prefix => id.startsWith(prefix));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function resolveOpenCodeGoBaseURLs(configuredBaseURL) {
|
|
29
|
+
const presetBase = OPENAI_COMPAT_PRESETS['opencode-go'].baseURL;
|
|
30
|
+
const openai = String(configuredBaseURL || presetBase).replace(/\/+$/, '');
|
|
31
|
+
// Anthropic's SDK appends `/v1/messages` itself. Supplying the OpenAI base
|
|
32
|
+
// (`.../v1`) would therefore produce the invalid `.../v1/v1/messages`.
|
|
33
|
+
const anthropic = openai.replace(/\/v1$/i, '');
|
|
34
|
+
return { openai, anthropic };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function openCodeGoEndpointForModel(model, configuredBaseURL) {
|
|
38
|
+
const bases = resolveOpenCodeGoBaseURLs(configuredBaseURL);
|
|
39
|
+
return isAnthropicGoModel(model)
|
|
40
|
+
? `${bases.anthropic}/v1/messages`
|
|
41
|
+
: `${bases.openai}/chat/completions`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeOpenCodeGoResultUsage(result, anthropicRoute) {
|
|
45
|
+
if (!anthropicRoute || !result?.usage) return result;
|
|
46
|
+
const usage = result.usage;
|
|
47
|
+
const input = Number(usage.inputTokens) || 0;
|
|
48
|
+
const cached = Number(usage.cachedTokens) || 0;
|
|
49
|
+
const cacheWrite = Number(usage.cacheWriteTokens) || 0;
|
|
50
|
+
const inclusiveInput = input + cached + cacheWrite;
|
|
51
|
+
return {
|
|
52
|
+
...result,
|
|
53
|
+
usage: {
|
|
54
|
+
...usage,
|
|
55
|
+
inputTokens: inclusiveInput,
|
|
56
|
+
promptTokens: Math.max(Number(usage.promptTokens) || 0, inclusiveInput),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
28
61
|
function opencodeGoContextWindow(_modelId, current = 0) {
|
|
29
62
|
const native = Number(current);
|
|
30
63
|
if (Number.isFinite(native) && native > 0) return native;
|
|
@@ -46,24 +79,29 @@ function opencodeGoReasoningLevels(model, current = null) {
|
|
|
46
79
|
export class OpenCodeGoProvider {
|
|
47
80
|
static inputExcludesCache = false;
|
|
48
81
|
name = 'opencode-go';
|
|
82
|
+
config;
|
|
49
83
|
openai;
|
|
50
84
|
anthropic;
|
|
51
85
|
|
|
52
86
|
constructor(config = {}) {
|
|
87
|
+
// Retain the outer account config for the common provider-admission
|
|
88
|
+
// lane. The delegated transports are intentionally not independently
|
|
89
|
+
// wrapped, so all model-family routes share this one 64-wide account.
|
|
90
|
+
this.config = config;
|
|
53
91
|
const preset = OPENAI_COMPAT_PRESETS['opencode-go'];
|
|
54
|
-
const
|
|
55
|
-
this.openai = new OpenAICompatProvider('opencode-go', { ...config, baseURL });
|
|
92
|
+
const bases = resolveOpenCodeGoBaseURLs(config.baseURL || preset.baseURL);
|
|
93
|
+
this.openai = new OpenAICompatProvider('opencode-go', { ...config, baseURL: bases.openai });
|
|
56
94
|
this.anthropic = new AnthropicProvider({
|
|
57
95
|
...config,
|
|
58
96
|
name: 'opencode-go',
|
|
59
|
-
baseURL,
|
|
97
|
+
baseURL: bases.anthropic,
|
|
60
98
|
disableBetaHeaders: true,
|
|
61
99
|
});
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
async send(messages, model, tools, sendOpts) {
|
|
65
103
|
if (isAnthropicGoModel(model)) {
|
|
66
|
-
|
|
104
|
+
const result = await this.anthropic.send(messages, model, tools, {
|
|
67
105
|
...(sendOpts || {}),
|
|
68
106
|
cacheStrategy: {
|
|
69
107
|
tools: 'none',
|
|
@@ -72,6 +110,7 @@ export class OpenCodeGoProvider {
|
|
|
72
110
|
messages: 'none',
|
|
73
111
|
},
|
|
74
112
|
});
|
|
113
|
+
return normalizeOpenCodeGoResultUsage(result, true);
|
|
75
114
|
}
|
|
76
115
|
return this.openai.send(messages, model, tools, sendOpts);
|
|
77
116
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
hasGrokOAuthCredentials,
|
|
6
6
|
} from './oauth-credential-probes.mjs';
|
|
7
7
|
import { refreshCatalog as refreshMetadataCatalog, warmModelMetadataCatalogs } from './model-catalog.mjs';
|
|
8
|
+
import { wrapProviderAdmission } from './admission-scheduler.mjs';
|
|
8
9
|
// OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
|
|
9
10
|
// OPENAI_COMPAT_PRESETS. No parallel list maintained here.
|
|
10
11
|
const providers = new Map();
|
|
@@ -15,6 +16,7 @@ const providerModulePromises = new Map();
|
|
|
15
16
|
// byte identical to the live one, so lazy-init misses that re-run initProviders
|
|
16
17
|
// don't churn (tear down + rebuild) every live provider instance on every call.
|
|
17
18
|
const signatures = new Map();
|
|
19
|
+
const KNOWN_INPUT_EXCLUDES_CACHE = new Set(['anthropic', 'anthropic-oauth']);
|
|
18
20
|
|
|
19
21
|
// Module-level init serialization. agent-tool.mjs's ensureProvider() already
|
|
20
22
|
// serializes inits per provider on a chain promise, but its gateOnPrior() lets
|
|
@@ -108,9 +110,9 @@ async function loadProviderCtor(name, signal = null) {
|
|
|
108
110
|
|
|
109
111
|
function instantiateProvider(name, Ctor, cfg) {
|
|
110
112
|
if (Object.prototype.hasOwnProperty.call(OPENAI_COMPAT_PRESETS, name) && name !== 'opencode-go') {
|
|
111
|
-
return new Ctor(name, cfg);
|
|
113
|
+
return wrapProviderAdmission(new Ctor(name, cfg), name);
|
|
112
114
|
}
|
|
113
|
-
return new Ctor(cfg);
|
|
115
|
+
return wrapProviderAdmission(new Ctor(cfg), name);
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
export async function initProviders(config, { signal = null } = {}) {
|
|
@@ -232,21 +234,21 @@ export function getProvider(name) {
|
|
|
232
234
|
if (name === 'anthropic-oauth' && hasAnthropicOAuthCredentials()) {
|
|
233
235
|
const Ctor = providerCtors.get('anthropic-oauth');
|
|
234
236
|
if (!Ctor) return undefined;
|
|
235
|
-
const inst = new Ctor({});
|
|
237
|
+
const inst = wrapProviderAdmission(new Ctor({}), name);
|
|
236
238
|
providers.set(name, inst);
|
|
237
239
|
return inst;
|
|
238
240
|
}
|
|
239
241
|
if (name === 'openai-oauth' && hasOpenAIOAuthCredentials()) {
|
|
240
242
|
const Ctor = providerCtors.get('openai-oauth');
|
|
241
243
|
if (!Ctor) return undefined;
|
|
242
|
-
const inst = new Ctor({});
|
|
244
|
+
const inst = wrapProviderAdmission(new Ctor({}), name);
|
|
243
245
|
providers.set(name, inst);
|
|
244
246
|
return inst;
|
|
245
247
|
}
|
|
246
248
|
if (name === 'grok-oauth' && hasGrokOAuthCredentials()) {
|
|
247
249
|
const Ctor = providerCtors.get('grok-oauth');
|
|
248
250
|
if (!Ctor) return undefined;
|
|
249
|
-
const inst = new Ctor({});
|
|
251
|
+
const inst = wrapProviderAdmission(new Ctor({}), name);
|
|
250
252
|
providers.set(name, inst);
|
|
251
253
|
return inst;
|
|
252
254
|
}
|
|
@@ -261,15 +263,54 @@ export function getProvider(name) {
|
|
|
261
263
|
// provider states its own answer — no central regex to keep in sync. Unknown /
|
|
262
264
|
// unregistered providers default to false (the openai/gemini majority).
|
|
263
265
|
export function providerInputExcludesCache(name) {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
const normalized = String(name || '').toLowerCase();
|
|
267
|
+
// Usage accounting is a pure lookup. In particular, never call
|
|
268
|
+
// getProvider() here: its OAuth miss path probes credentials and may lazily
|
|
269
|
+
// instantiate/register a provider.
|
|
270
|
+
const p = providers.get(normalized);
|
|
271
|
+
if (p?.constructor && typeof p.constructor.inputExcludesCache === 'boolean') {
|
|
272
|
+
return p.constructor.inputExcludesCache;
|
|
273
|
+
}
|
|
274
|
+
// A constructor may already be loaded even when the provider is currently
|
|
275
|
+
// absent during a route/config transition. Preserve its declaration, but
|
|
276
|
+
// never infer usage semantics from an arbitrary provider-name substring.
|
|
277
|
+
const Ctor = providerCtors.get(normalized);
|
|
278
|
+
if (Ctor && typeof Ctor.inputExcludesCache === 'boolean') {
|
|
279
|
+
return Ctor.inputExcludesCache;
|
|
280
|
+
}
|
|
281
|
+
// Built-in usage semantics must also be correct before lazy construction
|
|
282
|
+
// (including disabled providers in a fresh process).
|
|
283
|
+
return KNOWN_INPUT_EXCLUDES_CACHE.has(normalized);
|
|
267
284
|
}
|
|
268
285
|
export function getAllProviders() {
|
|
269
286
|
// Defensive copy — callers must not mutate the live registry or retain
|
|
270
287
|
// stale entries across re-init (initProviders rebuilds the map in place).
|
|
271
288
|
return new Map(providers);
|
|
272
289
|
}
|
|
290
|
+
// Narrow synchronous test seam for the lazy-OAuth boundary. It models a
|
|
291
|
+
// constructor whose module is already loaded while guaranteeing every touched
|
|
292
|
+
// registry entry is restored, even when the assertion callback throws.
|
|
293
|
+
export function _withLoadedProviderCtorForTest(name, Ctor, fn) {
|
|
294
|
+
const hadProvider = providers.has(name);
|
|
295
|
+
const priorProvider = providers.get(name);
|
|
296
|
+
const hadCtor = providerCtors.has(name);
|
|
297
|
+
const priorCtor = providerCtors.get(name);
|
|
298
|
+
const hadSignature = signatures.has(name);
|
|
299
|
+
const priorSignature = signatures.get(name);
|
|
300
|
+
providers.delete(name);
|
|
301
|
+
signatures.delete(name);
|
|
302
|
+
providerCtors.set(name, Ctor);
|
|
303
|
+
try {
|
|
304
|
+
return fn();
|
|
305
|
+
} finally {
|
|
306
|
+
if (hadProvider) providers.set(name, priorProvider);
|
|
307
|
+
else providers.delete(name);
|
|
308
|
+
if (hadCtor) providerCtors.set(name, priorCtor);
|
|
309
|
+
else providerCtors.delete(name);
|
|
310
|
+
if (hadSignature) signatures.set(name, priorSignature);
|
|
311
|
+
else signatures.delete(name);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
273
314
|
// Background catalog warm-up. Each provider's listModels() either hits its
|
|
274
315
|
// own cached model list (no-op) or fires a single HTTP refresh. Called from
|
|
275
316
|
// agent.init() after providers are registered so the first agent dispatch call
|