@gajae-code/coding-agent 0.15.6 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +14 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +27 -8
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +79 -2
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +21 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +9 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +12 -5
- package/dist/types/sdk/cli/session-cli.d.ts +20 -3
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +70 -17
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +3 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +6 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +52 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +79 -2
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +13 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +140 -23
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +135 -28
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +178 -23
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +291 -42
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +27 -8
- package/src/config/settings.ts +51 -15
- package/src/coordinator-mcp/projection-scan.ts +27 -6
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +115 -21
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +213 -14
- package/src/gjc-runtime/session-state-sidecar.ts +938 -39
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +61 -3
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +610 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +30 -2
- package/src/modes/controllers/selector-controller.ts +200 -98
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +1646 -27
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +81 -3
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +440 -75
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +591 -0
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +43 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +84 -7
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +41 -15
- package/src/sdk/cli/session-cli.ts +297 -26
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +101 -40
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +29 -2
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +673 -14
- package/src/sdk/host/session-runtime.ts +674 -103
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +17 -4
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +105 -3
- package/src/sdk/protocol/operation-registry.ts +14 -4
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +219 -118
- package/src/sdk/session.ts +416 -195
- package/src/sdk/startup-capability.ts +1 -0
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1628 -318
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +850 -97
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +412 -75
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +10 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -241,11 +241,11 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
241
241
|
architect: ["anthropic/claude-opus-5:xhigh", "anthropic/claude-opus-4-6:xhigh"],
|
|
242
242
|
}),
|
|
243
243
|
profile("claude-fable", ["anthropic"], {
|
|
244
|
-
default: "anthropic/claude-fable-5:xhigh",
|
|
244
|
+
default: "anthropic/claude-fable-5-1:xhigh",
|
|
245
245
|
executor: "anthropic/claude-sonnet-5",
|
|
246
|
-
planner: "anthropic/claude-fable-5:low",
|
|
247
|
-
critic: "anthropic/claude-fable-5:high",
|
|
248
|
-
architect: "anthropic/claude-fable-5:xhigh",
|
|
246
|
+
planner: "anthropic/claude-fable-5-1:low",
|
|
247
|
+
critic: "anthropic/claude-fable-5-1:high",
|
|
248
|
+
architect: "anthropic/claude-fable-5-1:xhigh",
|
|
249
249
|
}),
|
|
250
250
|
profile("glm-eco", ["zai"], {
|
|
251
251
|
default: "zai/glm-5.3-flash:low",
|
|
@@ -482,7 +482,7 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
482
482
|
architect: "openai-codex/gpt-5.6-sol:high",
|
|
483
483
|
}),
|
|
484
484
|
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
485
|
-
default: "anthropic/claude-fable-5:high",
|
|
485
|
+
default: "anthropic/claude-fable-5-1:high",
|
|
486
486
|
executor: "openai-codex/gpt-5.6-terra:medium",
|
|
487
487
|
planner: "anthropic/claude-opus-5:medium",
|
|
488
488
|
critic: "anthropic/claude-opus-5:high",
|
|
@@ -39,7 +39,11 @@ import {
|
|
|
39
39
|
UNK_MAX_TOKENS,
|
|
40
40
|
unregisterCustomApis,
|
|
41
41
|
} from "@gajae-code/ai/core";
|
|
42
|
-
import {
|
|
42
|
+
import { fetchModelsDevPayload } from "@gajae-code/ai/provider-models/openai-compat";
|
|
43
|
+
import {
|
|
44
|
+
detectDiscoveredApiFamily,
|
|
45
|
+
resolveLoopbackOpenAIBaseUrl,
|
|
46
|
+
} from "@gajae-code/ai/utils/discovery/openai-compatible";
|
|
43
47
|
|
|
44
48
|
// Sentinels for local-only OAuth tokens — declared inline to avoid loading provider
|
|
45
49
|
// modules at startup. Must match the provider OAuth modules.
|
|
@@ -66,6 +70,7 @@ import {
|
|
|
66
70
|
} from "../sdk/providers";
|
|
67
71
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
68
72
|
import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types";
|
|
73
|
+
import { type BillingPath, deriveBillingPath } from "./billing-path";
|
|
69
74
|
import { ConfigError, ConfigFile } from "./config-file";
|
|
70
75
|
import { isAuthenticated, kNoAuth } from "./model-auth";
|
|
71
76
|
import { type ConfiguredModelBindings, ModelBindingsApplier } from "./model-bindings-applier";
|
|
@@ -112,6 +117,7 @@ import {
|
|
|
112
117
|
} from "./provider-selection-policy";
|
|
113
118
|
import { type Settings, settings } from "./settings";
|
|
114
119
|
|
|
120
|
+
export type { BillingPath, BillingPathKind } from "./billing-path";
|
|
115
121
|
export type { EffectiveProviderAuth, ProviderSelectionPolicy } from "./provider-selection-policy";
|
|
116
122
|
export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
|
|
117
123
|
|
|
@@ -135,6 +141,69 @@ function redactDiscoveryUrl(value: string | URL): string {
|
|
|
135
141
|
return "(invalid URL)";
|
|
136
142
|
}
|
|
137
143
|
}
|
|
144
|
+
|
|
145
|
+
/** Whether a structured transport code proves that no listener accepted the connection. */
|
|
146
|
+
function isConnectionRefusedDiscoveryCode(value: unknown): boolean {
|
|
147
|
+
if (typeof value === "string") {
|
|
148
|
+
const normalized = value.replace(/[^A-Za-z0-9]/gu, "").toUpperCase();
|
|
149
|
+
return (
|
|
150
|
+
normalized === "ECONNREFUSED" || normalized === "CONNECTIONREFUSED" || normalized === "ERRCONNECTIONREFUSED"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return false;
|
|
154
|
+
// ECONNREFUSED errno values used by the supported runtimes/OSes.
|
|
155
|
+
return value === 61 || value === -61 || value === 111 || value === -111 || value === 10061 || value === -10061;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isConnectionRefusedDiscoveryEvidence(error: unknown): boolean {
|
|
159
|
+
const seen = new Set<unknown>();
|
|
160
|
+
const queue: unknown[] = [error];
|
|
161
|
+
while (queue.length > 0) {
|
|
162
|
+
const current = queue.shift();
|
|
163
|
+
if (current === null || typeof current !== "object" || seen.has(current)) continue;
|
|
164
|
+
seen.add(current);
|
|
165
|
+
for (const field of ["code", "errno", "errorCode", "transportCode"]) {
|
|
166
|
+
let value: unknown;
|
|
167
|
+
try {
|
|
168
|
+
value = Reflect.get(current, field);
|
|
169
|
+
} catch {
|
|
170
|
+
value = undefined;
|
|
171
|
+
}
|
|
172
|
+
if (isConnectionRefusedDiscoveryCode(value)) return true;
|
|
173
|
+
}
|
|
174
|
+
for (const field of ["cause", "error", "transport"]) {
|
|
175
|
+
let nested: unknown;
|
|
176
|
+
try {
|
|
177
|
+
nested = Reflect.get(current, field);
|
|
178
|
+
} catch {
|
|
179
|
+
nested = undefined;
|
|
180
|
+
}
|
|
181
|
+
if (nested !== undefined) queue.push(nested);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Whether `hostname` (as `URL.hostname` reports it) names this machine's loopback. */
|
|
188
|
+
function isLoopbackDiscoveryHost(hostname: string): boolean {
|
|
189
|
+
if (hostname === "localhost" || hostname === "[::1]" || hostname === "::1") return true;
|
|
190
|
+
return /^127(?:\.\d{1,3}){3}$/u.test(hostname);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A loopback discovery endpoint that is proven to have no listener is the normal state of
|
|
195
|
+
* a machine without a local model server. Structured transport evidence is required when
|
|
196
|
+
* available; DNS, routing, proxy, TLS, and HTTP failures remain warnings.
|
|
197
|
+
*/
|
|
198
|
+
export function isQuietLocalDiscoveryFailure(baseUrl: string, _warning: string, evidence?: unknown): boolean {
|
|
199
|
+
let host: string;
|
|
200
|
+
try {
|
|
201
|
+
host = new URL(baseUrl).hostname;
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return isLoopbackDiscoveryHost(host) && evidence !== undefined && isConnectionRefusedDiscoveryEvidence(evidence);
|
|
206
|
+
}
|
|
138
207
|
function stripUrlQuery(value: string): string {
|
|
139
208
|
const queryStart = value.indexOf("?");
|
|
140
209
|
if (queryStart < 0) return value;
|
|
@@ -272,6 +341,44 @@ type ProviderValidationMode = "models-config" | "runtime-register";
|
|
|
272
341
|
|
|
273
342
|
const OPENAI_REQUEST_TRANSFORM_APIS = new Set<Api>(["openai-completions", "openai-responses"]);
|
|
274
343
|
|
|
344
|
+
const OPENAI_FAMILY_APIS = new Set<Api>([
|
|
345
|
+
"openai-completions",
|
|
346
|
+
"openai-responses",
|
|
347
|
+
"openai-codex-responses",
|
|
348
|
+
"azure-openai-responses",
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
/** Whether an api uses an OpenAI-shaped request/response transport. */
|
|
352
|
+
function isOpenAIFamilyApi(api: Api): boolean {
|
|
353
|
+
return OPENAI_FAMILY_APIS.has(api);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The most frequent OpenAI-family api among statically-configured models, used
|
|
358
|
+
* as the default api for auto-enabled `/v1/models` discovery on a custom
|
|
359
|
+
* provider that sets `api` only per model. Returns `undefined` when no
|
|
360
|
+
* configured model uses an OpenAI-family api (e.g. an Anthropic-only base),
|
|
361
|
+
* which suppresses auto-discovery for that provider.
|
|
362
|
+
*/
|
|
363
|
+
function dominantOpenAIFamilyModelApi(models: readonly { api?: string }[] | undefined): Api | undefined {
|
|
364
|
+
if (!models || models.length === 0) return undefined;
|
|
365
|
+
const counts = new Map<Api, number>();
|
|
366
|
+
for (const model of models) {
|
|
367
|
+
const api = model.api as Api | undefined;
|
|
368
|
+
if (api === undefined || !isOpenAIFamilyApi(api)) continue;
|
|
369
|
+
counts.set(api, (counts.get(api) ?? 0) + 1);
|
|
370
|
+
}
|
|
371
|
+
let best: Api | undefined;
|
|
372
|
+
let bestCount = 0;
|
|
373
|
+
for (const [api, count] of counts) {
|
|
374
|
+
if (count > bestCount) {
|
|
375
|
+
best = api;
|
|
376
|
+
bestCount = count;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return best;
|
|
380
|
+
}
|
|
381
|
+
|
|
275
382
|
function getKnownProviderApis(providerName: string): Set<Api> {
|
|
276
383
|
const apis = new Set<Api>();
|
|
277
384
|
for (const model of getBundledModels(providerName as Parameters<typeof getBundledModels>[0])) {
|
|
@@ -1192,6 +1299,7 @@ type CustomModelOverlay = {
|
|
|
1192
1299
|
wireModelId?: string;
|
|
1193
1300
|
requestTransform?: ModelRequestTransform;
|
|
1194
1301
|
cacheRetention?: CacheRetention;
|
|
1302
|
+
transport?: Model<Api>["transport"];
|
|
1195
1303
|
isOAuth?: boolean;
|
|
1196
1304
|
};
|
|
1197
1305
|
|
|
@@ -1303,6 +1411,7 @@ function buildCustomModelOverlay(
|
|
|
1303
1411
|
providerRequestTransform: ModelRequestTransform | undefined,
|
|
1304
1412
|
providerAuth: ProviderAuthMode | undefined,
|
|
1305
1413
|
providerCacheRetention: CacheRetention | undefined,
|
|
1414
|
+
providerTransport: Model<Api>["transport"] | undefined,
|
|
1306
1415
|
modelDef: CustomModelDefinitionLike,
|
|
1307
1416
|
): CustomModelOverlay | undefined {
|
|
1308
1417
|
const api = modelDef.api ?? providerApi;
|
|
@@ -1331,6 +1440,7 @@ function buildCustomModelOverlay(
|
|
|
1331
1440
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
1332
1441
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
1333
1442
|
cacheRetention: modelDef.cacheRetention ?? providerCacheRetention,
|
|
1443
|
+
transport: providerTransport,
|
|
1334
1444
|
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
1335
1445
|
};
|
|
1336
1446
|
}
|
|
@@ -1466,6 +1576,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
1466
1576
|
wireModelId: resolvedModel.wireModelId,
|
|
1467
1577
|
requestTransform: resolvedModel.requestTransform,
|
|
1468
1578
|
cacheRetention: resolvedModel.cacheRetention ?? reference?.cacheRetention,
|
|
1579
|
+
transport: resolvedModel.transport,
|
|
1469
1580
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
1470
1581
|
isOAuth: resolvedModel.isOAuth,
|
|
1471
1582
|
} as Model<Api>);
|
|
@@ -2291,6 +2402,7 @@ export class ModelRegistry {
|
|
|
2291
2402
|
// re-merge bundled transport metadata here.
|
|
2292
2403
|
headers: customModel.headers,
|
|
2293
2404
|
compat: customModel.compat,
|
|
2405
|
+
transport: customModel.transport,
|
|
2294
2406
|
contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
2295
2407
|
wireModelId: customModel.wireModelId,
|
|
2296
2408
|
requestTransform: customModel.requestTransform,
|
|
@@ -2669,17 +2781,48 @@ export class ModelRegistry {
|
|
|
2669
2781
|
keylessProviders.add(providerName);
|
|
2670
2782
|
}
|
|
2671
2783
|
|
|
2672
|
-
|
|
2784
|
+
// Explicit discovery config is authoritative. Otherwise auto-enable
|
|
2785
|
+
// OpenAI-compatible `/v1/models` discovery for any custom provider that
|
|
2786
|
+
// declares an OpenAI-family `api` and a `baseUrl`: a subscription-style
|
|
2787
|
+
// gateway (e.g. CLIProxyAPI) then auto-populates `/model` with its live
|
|
2788
|
+
// catalog and auto-routes each model's wire family, with no manual
|
|
2789
|
+
// `discovery:` block. It stays optional so an endpoint without
|
|
2790
|
+
// `/v1/models` degrades to the configured static models instead of
|
|
2791
|
+
// failing. Providers that route through the local `openaiCompat` proxy
|
|
2792
|
+
// already register their own discovery above, so skip them here.
|
|
2793
|
+
const effectiveDiscoveryBaseUrl = providerConfig.baseUrl ?? resolveProviderBaseUrlFromEnv(providerName);
|
|
2794
|
+
// The default api for discovered models: the provider-level `api` when
|
|
2795
|
+
// set, otherwise the most common OpenAI-family `api` among the
|
|
2796
|
+
// statically-configured models. For withfox-style configs that only set
|
|
2797
|
+
// `api` per model (`openai-responses` for gpt, `anthropic-messages` for
|
|
2798
|
+
// claude), this preserves the intended OpenAI transport for discovered
|
|
2799
|
+
// OpenAI models while `detectDiscoveredApiFamily` still flips claude ids
|
|
2800
|
+
// to Anthropic.
|
|
2801
|
+
const providerApi =
|
|
2802
|
+
(providerConfig.api as Api | undefined) ??
|
|
2803
|
+
dominantOpenAIFamilyModelApi(providerConfig.models as { api?: string }[] | undefined);
|
|
2804
|
+
const autoDiscovery: ProviderDiscovery | undefined =
|
|
2805
|
+
!providerConfig.discovery &&
|
|
2806
|
+
!localOpenAICompat &&
|
|
2807
|
+
providerName !== "vllm" &&
|
|
2808
|
+
providerName !== "sglang" &&
|
|
2809
|
+
providerApi !== undefined &&
|
|
2810
|
+
isOpenAIFamilyApi(providerApi) &&
|
|
2811
|
+
effectiveDiscoveryBaseUrl !== undefined
|
|
2812
|
+
? { type: "openai-models-list" }
|
|
2813
|
+
: undefined;
|
|
2814
|
+
const effectiveDiscovery = providerConfig.discovery ?? autoDiscovery;
|
|
2815
|
+
if (effectiveDiscovery && providerApi) {
|
|
2673
2816
|
discoverableProviders.push({
|
|
2674
2817
|
provider: providerName,
|
|
2675
|
-
api:
|
|
2676
|
-
baseUrl:
|
|
2818
|
+
api: providerApi,
|
|
2819
|
+
baseUrl: effectiveDiscoveryBaseUrl,
|
|
2677
2820
|
headers: providerConfig.headers,
|
|
2678
2821
|
compat: providerConfig.compat,
|
|
2679
2822
|
requestTransform: providerConfig.requestTransform,
|
|
2680
2823
|
cacheRetention: providerConfig.cacheRetention,
|
|
2681
|
-
discovery:
|
|
2682
|
-
optional:
|
|
2824
|
+
discovery: effectiveDiscovery,
|
|
2825
|
+
optional: !providerConfig.discovery,
|
|
2683
2826
|
});
|
|
2684
2827
|
}
|
|
2685
2828
|
|
|
@@ -2957,27 +3100,71 @@ export class ModelRegistry {
|
|
|
2957
3100
|
? { ...result, invalidatesPublishedState }
|
|
2958
3101
|
: { ...result, current: false, models: [], fetched: false, invalidatesPublishedState };
|
|
2959
3102
|
});
|
|
3103
|
+
// Every field below except `model.baseUrl` and `model.id` depends only on
|
|
3104
|
+
// `model.provider`. The built-in catalog carries thousands of models across
|
|
3105
|
+
// only a few dozen providers, and `#getProviderEvidenceGeneration` reaches
|
|
3106
|
+
// into AuthStorage while `#getProviderBaseUrlForDiscovery` parses URLs, so
|
|
3107
|
+
// recomputing per model turned this filter into a per-refresh UI-thread
|
|
3108
|
+
// stall. Memoize the provider-keyed work once per provider.
|
|
3109
|
+
interface BuiltInProviderEvidenceState {
|
|
3110
|
+
evidence:
|
|
3111
|
+
| {
|
|
3112
|
+
fresh: boolean;
|
|
3113
|
+
modelIds: ReadonlySet<string>;
|
|
3114
|
+
profileModelIds?: ReadonlySet<string>;
|
|
3115
|
+
profileFresh?: boolean;
|
|
3116
|
+
profileEndpoint?: string;
|
|
3117
|
+
authGeneration: string;
|
|
3118
|
+
endpoint: string;
|
|
3119
|
+
}
|
|
3120
|
+
| undefined;
|
|
3121
|
+
expectedAuthGeneration: string | undefined;
|
|
3122
|
+
authGenerationThrew: boolean;
|
|
3123
|
+
providerBaseEndpoint: string | undefined;
|
|
3124
|
+
canUseCredentialDerivedXiaomiEndpoint: boolean;
|
|
3125
|
+
}
|
|
3126
|
+
const builtInProviderStates = new Map<string, BuiltInProviderEvidenceState>();
|
|
3127
|
+
const builtInProviderStateFor = (provider: string): BuiltInProviderEvidenceState => {
|
|
3128
|
+
const cached = builtInProviderStates.get(provider);
|
|
3129
|
+
if (cached) return cached;
|
|
3130
|
+
let expectedAuthGeneration: string | undefined;
|
|
3131
|
+
let authGenerationThrew = false;
|
|
3132
|
+
try {
|
|
3133
|
+
expectedAuthGeneration = this.#getProviderEvidenceGeneration(
|
|
3134
|
+
provider,
|
|
3135
|
+
this.#providerEvidenceApiKeys.get(provider),
|
|
3136
|
+
);
|
|
3137
|
+
} catch {
|
|
3138
|
+
authGenerationThrew = true;
|
|
3139
|
+
}
|
|
3140
|
+
const providerBaseUrl = this.#getProviderBaseUrlForDiscovery(provider);
|
|
3141
|
+
const state: BuiltInProviderEvidenceState = {
|
|
3142
|
+
evidence: this.#descriptorDiscoveryEvidence.get(provider),
|
|
3143
|
+
expectedAuthGeneration,
|
|
3144
|
+
authGenerationThrew,
|
|
3145
|
+
providerBaseEndpoint:
|
|
3146
|
+
providerBaseUrl !== undefined ? this.#normalizeDiscoveryEvidenceEndpoint(providerBaseUrl) : undefined,
|
|
3147
|
+
canUseCredentialDerivedXiaomiEndpoint:
|
|
3148
|
+
provider === "xiaomi" &&
|
|
3149
|
+
this.#providerEvidenceApiKeys.get("xiaomi")?.startsWith("tp-") === true &&
|
|
3150
|
+
this.#runtimeProviderOverrides.get("xiaomi")?.baseUrl === undefined &&
|
|
3151
|
+
this.#providerOverrides.get("xiaomi")?.baseUrl === undefined &&
|
|
3152
|
+
resolveProviderBaseUrlFromEnv("xiaomi") === undefined,
|
|
3153
|
+
};
|
|
3154
|
+
builtInProviderStates.set(provider, state);
|
|
3155
|
+
return state;
|
|
3156
|
+
};
|
|
2960
3157
|
const currentBuiltInDiscovered = builtInDiscovered.filter(model => {
|
|
2961
|
-
const
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
);
|
|
2965
|
-
const canUseCredentialDerivedXiaomiEndpoint =
|
|
2966
|
-
model.provider === "xiaomi" &&
|
|
2967
|
-
this.#providerEvidenceApiKeys.get("xiaomi")?.startsWith("tp-") === true &&
|
|
2968
|
-
this.#runtimeProviderOverrides.get("xiaomi")?.baseUrl === undefined &&
|
|
2969
|
-
this.#providerOverrides.get("xiaomi")?.baseUrl === undefined &&
|
|
2970
|
-
resolveProviderBaseUrlFromEnv("xiaomi") === undefined;
|
|
3158
|
+
const state = builtInProviderStateFor(model.provider);
|
|
3159
|
+
const { evidence } = state;
|
|
3160
|
+
if (evidence === undefined || state.authGenerationThrew) return false;
|
|
3161
|
+
if (evidence.authGeneration !== state.expectedAuthGeneration) return false;
|
|
2971
3162
|
try {
|
|
3163
|
+
const currentEndpoint =
|
|
3164
|
+
state.providerBaseEndpoint ?? this.#normalizeDiscoveryEvidenceEndpoint(model.baseUrl ?? "");
|
|
2972
3165
|
return (
|
|
2973
|
-
evidence !== undefined &&
|
|
2974
|
-
evidence.authGeneration ===
|
|
2975
|
-
this.#getProviderEvidenceGeneration(
|
|
2976
|
-
model.provider,
|
|
2977
|
-
this.#providerEvidenceApiKeys.get(model.provider),
|
|
2978
|
-
) &&
|
|
2979
3166
|
(evidence.endpoint === currentEndpoint ||
|
|
2980
|
-
(canUseCredentialDerivedXiaomiEndpoint &&
|
|
3167
|
+
(state.canUseCredentialDerivedXiaomiEndpoint &&
|
|
2981
3168
|
evidence.endpoint === this.#normalizeDiscoveryEvidenceEndpoint(model.baseUrl ?? ""))) &&
|
|
2982
3169
|
evidence.modelIds.has(model.id)
|
|
2983
3170
|
);
|
|
@@ -3222,6 +3409,8 @@ export class ModelRegistry {
|
|
|
3222
3409
|
(evidence.authGeneration !== authGenerationBeforeDiscovery || evidence.endpoint !== endpoint)))
|
|
3223
3410
|
? "online"
|
|
3224
3411
|
: strategy;
|
|
3412
|
+
let discoveryFailureEvidence: unknown;
|
|
3413
|
+
|
|
3225
3414
|
const mergeInput = await this.#discoveryManager.discover(effectiveProviderConfig, refreshStrategy, {
|
|
3226
3415
|
cacheDbPath: this.#cacheDbPath,
|
|
3227
3416
|
requiresAuth: provider =>
|
|
@@ -3234,11 +3423,17 @@ export class ModelRegistry {
|
|
|
3234
3423
|
baseUrl: provider.baseUrl,
|
|
3235
3424
|
}),
|
|
3236
3425
|
isAuthenticated,
|
|
3237
|
-
fetchModels: async (provider, apiKey) =>
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3426
|
+
fetchModels: async (provider, apiKey) => {
|
|
3427
|
+
try {
|
|
3428
|
+
return this.#sanitizeDiscoverableModelsForCache(
|
|
3429
|
+
provider,
|
|
3430
|
+
await this.#discoverModelsByProviderType(provider, apiKey),
|
|
3431
|
+
);
|
|
3432
|
+
} catch (error) {
|
|
3433
|
+
discoveryFailureEvidence = error;
|
|
3434
|
+
throw error;
|
|
3435
|
+
}
|
|
3436
|
+
},
|
|
3242
3437
|
getEvidenceGeneration: provider => this.#getProviderEvidenceGeneration(provider.provider, preflightApiKey),
|
|
3243
3438
|
cacheDynamicModelProvenance: cacheLookupProvenance,
|
|
3244
3439
|
canPublishCache: () => isCurrentEndpoint() && isCurrentProviderRefresh(),
|
|
@@ -3262,11 +3457,15 @@ export class ModelRegistry {
|
|
|
3262
3457
|
};
|
|
3263
3458
|
}
|
|
3264
3459
|
if (mergeInput.warning) {
|
|
3265
|
-
|
|
3460
|
+
const baseUrl = effectiveProviderConfig.baseUrl ?? "";
|
|
3461
|
+
const fields = {
|
|
3266
3462
|
provider: effectiveProviderConfig.provider,
|
|
3267
|
-
url: redactDiscoveryUrl(
|
|
3463
|
+
url: redactDiscoveryUrl(baseUrl),
|
|
3268
3464
|
error: mergeInput.warning,
|
|
3269
|
-
}
|
|
3465
|
+
};
|
|
3466
|
+
if (isQuietLocalDiscoveryFailure(baseUrl, mergeInput.warning, discoveryFailureEvidence))
|
|
3467
|
+
logger.debug("model discovery failed for provider", fields);
|
|
3468
|
+
else logger.warn("model discovery failed for provider", fields);
|
|
3270
3469
|
}
|
|
3271
3470
|
this.#providerEvidenceApiKeys.set(effectiveProviderConfig.provider, preflightApiKey);
|
|
3272
3471
|
return {
|
|
@@ -3826,27 +4025,47 @@ export class ModelRegistry {
|
|
|
3826
4025
|
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
3827
4026
|
}
|
|
3828
4027
|
|
|
3829
|
-
#resolveDiscoveredModelApi(
|
|
3830
|
-
|
|
4028
|
+
#resolveDiscoveredModelApi(
|
|
4029
|
+
providerConfig: DiscoveryProviderConfig,
|
|
4030
|
+
modelId: string,
|
|
4031
|
+
entry?: { id?: unknown; owned_by?: unknown },
|
|
4032
|
+
): Api {
|
|
4033
|
+
// 1. Explicit per-prefix routing from models.yml always wins — the user
|
|
4034
|
+
// stated the transport for these ids, so never second-guess it.
|
|
3831
4035
|
let matchedPrefixLength = -1;
|
|
4036
|
+
let prefixApi: Api | undefined;
|
|
3832
4037
|
for (const [prefix, routedApi] of Object.entries(providerConfig.discovery.apiByModelPrefix ?? {})) {
|
|
3833
4038
|
if (modelId.startsWith(prefix) && prefix.length > matchedPrefixLength) {
|
|
3834
|
-
|
|
4039
|
+
prefixApi = routedApi;
|
|
3835
4040
|
matchedPrefixLength = prefix.length;
|
|
3836
4041
|
}
|
|
3837
4042
|
}
|
|
3838
|
-
return
|
|
4043
|
+
if (prefixApi !== undefined) return prefixApi;
|
|
4044
|
+
// 2. For OpenAI-compatible gateways that can front both OpenAI and
|
|
4045
|
+
// Anthropic upstreams (e.g. CLIProxyAPI), auto-detect the wire family
|
|
4046
|
+
// per model from the `/v1/models` `owned_by` owner and the model id.
|
|
4047
|
+
// A mixed gateway then routes `claude-*` through Anthropic Messages
|
|
4048
|
+
// with zero manual config. OpenAI-family models keep the provider's
|
|
4049
|
+
// configured OpenAI transport (e.g. `openai-responses` vs
|
|
4050
|
+
// `openai-completions`) rather than being forced to chat-completions,
|
|
4051
|
+
// because a `/v1/models` list cannot reveal which OpenAI API the
|
|
4052
|
+
// gateway expects. Only override to `openai-completions` when the
|
|
4053
|
+
// provider default is not itself an OpenAI-family api.
|
|
4054
|
+
if (providerConfig.discovery.type === "openai-models-list") {
|
|
4055
|
+
const detected = detectDiscoveredApiFamily(entry ?? { id: modelId });
|
|
4056
|
+
if (detected === "anthropic-messages") return "anthropic-messages";
|
|
4057
|
+
if (detected === "openai-completions") {
|
|
4058
|
+
return isOpenAIFamilyApi(providerConfig.api) ? providerConfig.api : "openai-completions";
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
// 3. Fall back to the provider-level default api.
|
|
4062
|
+
return providerConfig.api;
|
|
3839
4063
|
}
|
|
3840
4064
|
|
|
3841
4065
|
async #discoverModelsDevProvider(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
3842
4066
|
const baseUrl = providerConfig.baseUrl;
|
|
3843
4067
|
if (!baseUrl) throw new Error(`Provider "${providerConfig.provider}" requires baseUrl for models.dev discovery.`);
|
|
3844
|
-
const
|
|
3845
|
-
headers: { Accept: "application/json" },
|
|
3846
|
-
signal: AbortSignal.timeout(5_000),
|
|
3847
|
-
});
|
|
3848
|
-
if (!response.ok) throw new Error(`HTTP ${response.status} from https://models.dev/api.json`);
|
|
3849
|
-
const payload: unknown = await response.json();
|
|
4068
|
+
const payload: unknown = await fetchModelsDevPayload();
|
|
3850
4069
|
if (!isRecord(payload)) return [];
|
|
3851
4070
|
const catalogProvider = payload[providerConfig.discovery.modelsDevProvider ?? providerConfig.provider];
|
|
3852
4071
|
if (!isRecord(catalogProvider) || !isRecord(catalogProvider.models)) return [];
|
|
@@ -3952,7 +4171,7 @@ export class ModelRegistry {
|
|
|
3952
4171
|
item.max_tokens,
|
|
3953
4172
|
item.max_output_tokens,
|
|
3954
4173
|
);
|
|
3955
|
-
const api = this.#resolveDiscoveredModelApi(providerConfig, id);
|
|
4174
|
+
const api = this.#resolveDiscoveredModelApi(providerConfig, id, item);
|
|
3956
4175
|
discovered.push(
|
|
3957
4176
|
enrichModelThinking({
|
|
3958
4177
|
id,
|
|
@@ -4452,6 +4671,7 @@ export class ModelRegistry {
|
|
|
4452
4671
|
providerConfig.requestTransform,
|
|
4453
4672
|
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
4454
4673
|
providerConfig.cacheRetention,
|
|
4674
|
+
providerConfig.transport,
|
|
4455
4675
|
modelDef as CustomModelDefinitionLike,
|
|
4456
4676
|
);
|
|
4457
4677
|
if (!model) continue;
|
|
@@ -4584,6 +4804,22 @@ export class ModelRegistry {
|
|
|
4584
4804
|
autoroutingProviderOrder(): readonly string[] {
|
|
4585
4805
|
return projectCatalogProviderOrder(getConfiguredProviderOrderFromSettings(), this.#models);
|
|
4586
4806
|
}
|
|
4807
|
+
|
|
4808
|
+
/**
|
|
4809
|
+
* Provider priority for automatic model selection. Explicit provider order
|
|
4810
|
+
* wins first, followed by omitted OAuth providers, then other providers;
|
|
4811
|
+
* catalog order breaks ties within each policy band.
|
|
4812
|
+
*/
|
|
4813
|
+
automaticProviderOrder(sessionId?: string): readonly string[] {
|
|
4814
|
+
const policy = this.#buildProviderSelectionPolicy(sessionId);
|
|
4815
|
+
return [...policy.orderedProviders()].sort((left, right) => {
|
|
4816
|
+
const rankDifference = policy.rank(left) - policy.rank(right);
|
|
4817
|
+
return rankDifference !== 0
|
|
4818
|
+
? rankDifference
|
|
4819
|
+
: policy.providerCatalogIndex(left) - policy.providerCatalogIndex(right);
|
|
4820
|
+
});
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4587
4823
|
#providerRankMap(policy: ProviderSelectionPolicy): Map<string, number> {
|
|
4588
4824
|
const providerRank = new Map<string, number>();
|
|
4589
4825
|
for (const provider of policy.orderedProviders()) {
|
|
@@ -4820,6 +5056,18 @@ export class ModelRegistry {
|
|
|
4820
5056
|
return this.#effectiveProviderAuth(provider, sessionId);
|
|
4821
5057
|
}
|
|
4822
5058
|
|
|
5059
|
+
/**
|
|
5060
|
+
* Billing path for a resolved binding — derived at the point the binding
|
|
5061
|
+
* resolves credentials (model.api + credential provenance). Not a
|
|
5062
|
+
* provider-name allowlist: a custom OpenAI-compatible provider bound
|
|
5063
|
+
* to a metered gateway is "metered-api" exactly like a first-party
|
|
5064
|
+
* key. Only the billing category is ever disclosed.
|
|
5065
|
+
*/
|
|
5066
|
+
getBillingPath(model: Model<Api>, sessionId?: string): BillingPath | undefined {
|
|
5067
|
+
const effectiveAuth = this.#effectiveProviderAuth(model.provider, sessionId);
|
|
5068
|
+
return deriveBillingPath(model.provider, model.api as Api, effectiveAuth);
|
|
5069
|
+
}
|
|
5070
|
+
|
|
4823
5071
|
/**
|
|
4824
5072
|
* Forget a session's remembered canonical variant so the next resolution
|
|
4825
5073
|
* for that session re-ranks from scratch (explicit reselection
|
|
@@ -5608,6 +5856,7 @@ export class ModelRegistry {
|
|
|
5608
5856
|
config.requestTransform,
|
|
5609
5857
|
undefined,
|
|
5610
5858
|
undefined,
|
|
5859
|
+
config.transport,
|
|
5611
5860
|
modelDef as CustomModelDefinitionLike,
|
|
5612
5861
|
);
|
|
5613
5862
|
if (!overlay) {
|
|
@@ -1580,6 +1580,11 @@ export async function findInitialModel(options: {
|
|
|
1580
1580
|
thinkingLevel = resolveThinkingLevelForModel(found, defaultThinkingSelector);
|
|
1581
1581
|
return { model, thinkingLevel, fallbackMessage: undefined };
|
|
1582
1582
|
}
|
|
1583
|
+
return {
|
|
1584
|
+
model: undefined,
|
|
1585
|
+
thinkingLevel: undefined,
|
|
1586
|
+
fallbackMessage: `Model ${defaultProvider}/${defaultModelId} not found`,
|
|
1587
|
+
};
|
|
1583
1588
|
}
|
|
1584
1589
|
|
|
1585
1590
|
// 4. Try first available model with valid API key
|
|
@@ -421,6 +421,12 @@ export const SETTINGS_SCHEMA = {
|
|
|
421
421
|
description: "Hard maximum runtime for an SDK prompt from acceptance, bounding progress-aware renewals.",
|
|
422
422
|
validate: (value: number) => Number.isSafeInteger(value) && value >= 60_000 && value <= 86_400_000,
|
|
423
423
|
},
|
|
424
|
+
"sdk.masterOrphanGraceMs": {
|
|
425
|
+
type: "number",
|
|
426
|
+
default: 120_000,
|
|
427
|
+
description: "Grace period before a master-spawned child orphaned by confirmed master loss is closed.",
|
|
428
|
+
validate: (value: number) => Number.isSafeInteger(value) && value >= 60_000 && value <= 3_600_000,
|
|
429
|
+
},
|
|
424
430
|
|
|
425
431
|
// Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
|
|
426
432
|
"notifications.enabled": { type: "boolean", default: false },
|
|
@@ -745,7 +751,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
745
751
|
// ────────────────────────────────────────────────────────────────────────
|
|
746
752
|
"ui.language": {
|
|
747
753
|
type: "enum",
|
|
748
|
-
values: ["en", "ko", "ja"] as const,
|
|
754
|
+
values: ["en", "ko", "zh", "ja"] as const,
|
|
749
755
|
default: "en",
|
|
750
756
|
ui: {
|
|
751
757
|
tab: "appearance",
|
|
@@ -754,6 +760,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
754
760
|
options: [
|
|
755
761
|
{ value: "en", label: "English" },
|
|
756
762
|
{ value: "ko", label: "Korean (한국어)" },
|
|
763
|
+
{ value: "zh", label: "Chinese (简体中文)" },
|
|
757
764
|
{ value: "ja", label: "Japanese (日本語)" },
|
|
758
765
|
],
|
|
759
766
|
},
|
|
@@ -1572,14 +1579,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
1572
1579
|
},
|
|
1573
1580
|
},
|
|
1574
1581
|
|
|
1575
|
-
|
|
1582
|
+
toolInterruptPolicy: {
|
|
1576
1583
|
type: "enum",
|
|
1577
|
-
values: ["
|
|
1578
|
-
default: "
|
|
1584
|
+
values: ["abort_tools", "finish_tools"] as const,
|
|
1585
|
+
default: "abort_tools",
|
|
1579
1586
|
ui: {
|
|
1580
1587
|
tab: "interaction",
|
|
1581
|
-
label: "Interrupt
|
|
1582
|
-
description:
|
|
1588
|
+
label: "Tool Interrupt Policy",
|
|
1589
|
+
description:
|
|
1590
|
+
"Whether a steering message aborts the tools still running in the current batch (abort_tools) or lets them finish first (finish_tools). Never changes when a steer is consumed: it is always picked up at the next tool/turn boundary either way.",
|
|
1583
1591
|
},
|
|
1584
1592
|
},
|
|
1585
1593
|
|
|
@@ -3036,6 +3044,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
3036
3044
|
description: "Enable the browser tool (Ulixee Hero)",
|
|
3037
3045
|
},
|
|
3038
3046
|
},
|
|
3047
|
+
"browser.backend": {
|
|
3048
|
+
type: "enum",
|
|
3049
|
+
values: ["native", "aside"] as const,
|
|
3050
|
+
default: "native",
|
|
3051
|
+
ui: {
|
|
3052
|
+
tab: "tools",
|
|
3053
|
+
label: "Browser Backend",
|
|
3054
|
+
description:
|
|
3055
|
+
"Browser surface offered to the model. 'native' exposes the built-in Puppeteer browser tool. 'aside' hides it and requires every browser task to run through the Aside CLI in your live logged-in browser (requires the Aside CLI).",
|
|
3056
|
+
},
|
|
3057
|
+
},
|
|
3039
3058
|
|
|
3040
3059
|
"browser.headless": {
|
|
3041
3060
|
type: "boolean",
|
|
@@ -3065,7 +3084,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
3065
3084
|
tab: "tools",
|
|
3066
3085
|
label: "Profile reuse posture",
|
|
3067
3086
|
description:
|
|
3068
|
-
"'auto' (default): when a usable real Chrome profile is available, the browser tool uses an isolated copy of it (cookies/session/cache) for stronger stealth, warns, and falls back to synthetic. 'opt-in': stay synthetic unless a real profile is explicitly requested.",
|
|
3087
|
+
"'auto' (default): when a usable real Chrome profile is available, the browser tool uses an isolated copy of it (cookies/session/cache) for stronger stealth, warns, and falls back to synthetic. 'opt-in': stay synthetic unless a real profile is explicitly requested. On macOS, cookie decryption is brand-specific (Chrome Safe Storage vs Chromium Safe Storage Keychain); a Chrome profile copied into bundled Chromium decrypts to garbage, so auto reuses the profile only when the headless executable can decrypt it — detected system Chrome/Chromium or PUPPETEER_EXECUTABLE_PATH — otherwise it falls back to synthetic with a warning.",
|
|
3069
3088
|
},
|
|
3070
3089
|
},
|
|
3071
3090
|
|
|
@@ -4117,7 +4136,7 @@ export function getEnumValues(path: SettingPath): readonly string[] | undefined
|
|
|
4117
4136
|
return "values" in def ? (def.values as readonly string[]) : undefined;
|
|
4118
4137
|
}
|
|
4119
4138
|
|
|
4120
|
-
export { CONFIG_SCHEMA_VERSION } from "./config-schema-version";
|
|
4139
|
+
export { CONFIG_SCHEMA_VERSION, normalizeConfigSchemaVersion } from "./config-schema-version";
|
|
4121
4140
|
|
|
4122
4141
|
export type SettingsSchemaIssue = {
|
|
4123
4142
|
path: string;
|