@gajae-code/coding-agent 0.14.2 → 0.15.0
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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import {
|
|
@@ -88,6 +89,7 @@ import {
|
|
|
88
89
|
createProviderSelectionPolicy,
|
|
89
90
|
type EffectiveProviderAuth,
|
|
90
91
|
type ProviderSelectionPolicy,
|
|
92
|
+
projectCatalogProviderOrder,
|
|
91
93
|
} from "./provider-selection-policy";
|
|
92
94
|
import { type Settings, settings } from "./settings";
|
|
93
95
|
|
|
@@ -683,6 +685,78 @@ interface DiscoveryProviderConfig {
|
|
|
683
685
|
optional?: boolean;
|
|
684
686
|
}
|
|
685
687
|
|
|
688
|
+
const REUSABLE_DISCOVERY_HEADER_NAMES = new Set([
|
|
689
|
+
"accept",
|
|
690
|
+
"content-type",
|
|
691
|
+
"openai-organization",
|
|
692
|
+
"openai-project",
|
|
693
|
+
"x-organization-id",
|
|
694
|
+
"x-project-id",
|
|
695
|
+
"x-routing-id",
|
|
696
|
+
"x-tenant-id",
|
|
697
|
+
"x-workspace-id",
|
|
698
|
+
]);
|
|
699
|
+
|
|
700
|
+
function isSensitiveDiscoveryHeader(name: string): boolean {
|
|
701
|
+
// Persist values only for a small, audited set of request-shaping headers.
|
|
702
|
+
// Unknown headers fail closed because their semantics cannot be inferred from
|
|
703
|
+
// their names (for example, CF-Access-Jwt-Assertion carries a credential).
|
|
704
|
+
return !REUSABLE_DISCOVERY_HEADER_NAMES.has(name.toLowerCase());
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Credential-safe fingerprint of the effective configured-discovery request
|
|
709
|
+
* identity: non-secret credential evidence, normalized endpoint, effective
|
|
710
|
+
* request headers (config plus runtime overrides, lowercased and sorted), and
|
|
711
|
+
* the semantic request-shape fields (discovery type, provider api, per-prefix
|
|
712
|
+
* api routing, models.dev catalog key). A
|
|
713
|
+
* published cache row is only trusted while this fingerprint is unchanged, so
|
|
714
|
+
* a tenant/project header change (or any other request-shape change)
|
|
715
|
+
* invalidates the authoritative catalog instead of silently serving the old
|
|
716
|
+
* context's models. Configurations containing secret-bearing headers are not
|
|
717
|
+
* fingerprinted or persisted at all because an unsalted digest would permit
|
|
718
|
+
* offline guessing of low-entropy values.
|
|
719
|
+
*/
|
|
720
|
+
function fingerprintConfiguredDiscoveryRequestShape(
|
|
721
|
+
providerConfig: DiscoveryProviderConfig,
|
|
722
|
+
authEvidence: string,
|
|
723
|
+
endpoint: string,
|
|
724
|
+
): string | undefined {
|
|
725
|
+
const sortEntries = (record: Record<string, string> | undefined) =>
|
|
726
|
+
Object.entries(record ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
727
|
+
try {
|
|
728
|
+
const parsed = new URL(providerConfig.baseUrl ?? "");
|
|
729
|
+
if (parsed.username || parsed.password || parsed.search) return undefined;
|
|
730
|
+
} catch {
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
if (Object.keys(providerConfig.headers ?? {}).some(isSensitiveDiscoveryHeader)) return undefined;
|
|
734
|
+
const context = JSON.stringify({
|
|
735
|
+
authEvidence,
|
|
736
|
+
endpoint,
|
|
737
|
+
headers: sortEntries(providerConfig.headers).map(([name, value]) => [name.toLowerCase(), value]),
|
|
738
|
+
discoveryType: providerConfig.discovery.type,
|
|
739
|
+
api: providerConfig.api,
|
|
740
|
+
apiByModelPrefix: sortEntries(providerConfig.discovery.apiByModelPrefix),
|
|
741
|
+
modelsDevProvider: providerConfig.discovery.modelsDevProvider ?? "",
|
|
742
|
+
});
|
|
743
|
+
return crypto.createHash("sha256").update("gajae:model-discovery-provenance\0").update(context).digest("hex");
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function fingerprintDescriptorDiscoveryProvenance(authEvidence: string, endpoint: string): string | undefined {
|
|
747
|
+
try {
|
|
748
|
+
const parsed = new URL(endpoint);
|
|
749
|
+
if (parsed.username || parsed.password || parsed.search) return undefined;
|
|
750
|
+
} catch {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
return crypto
|
|
754
|
+
.createHash("sha256")
|
|
755
|
+
.update("gajae:model-discovery-provenance\0")
|
|
756
|
+
.update(JSON.stringify({ authEvidence, endpoint }))
|
|
757
|
+
.digest("hex");
|
|
758
|
+
}
|
|
759
|
+
|
|
686
760
|
export interface CanonicalModelQueryOptions {
|
|
687
761
|
availableOnly?: boolean;
|
|
688
762
|
candidates?: readonly Model<Api>[];
|
|
@@ -692,6 +766,12 @@ export interface CanonicalModelQueryOptions {
|
|
|
692
766
|
credentialSessionId?: string;
|
|
693
767
|
}
|
|
694
768
|
|
|
769
|
+
/** One canonical record with its winning variant resolved, from a batch query. */
|
|
770
|
+
export interface CanonicalModelSelection {
|
|
771
|
+
record: CanonicalModelRecord;
|
|
772
|
+
model: Model<Api> | undefined;
|
|
773
|
+
}
|
|
774
|
+
|
|
695
775
|
/** Result of loading custom models from models.json */
|
|
696
776
|
interface CustomModelsResult {
|
|
697
777
|
models?: CustomModelOverlay[];
|
|
@@ -1207,17 +1287,17 @@ function normalizeSuppressedSelector(selector: string): string {
|
|
|
1207
1287
|
return `${parsed.provider}/${parsed.id}`;
|
|
1208
1288
|
}
|
|
1209
1289
|
|
|
1210
|
-
function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
1290
|
+
function getDisabledProviderIdsFromSettings(settingsReader: Pick<Settings, "get"> = settings): Set<string> {
|
|
1211
1291
|
try {
|
|
1212
|
-
return new Set(
|
|
1292
|
+
return new Set(settingsReader.get("disabledProviders"));
|
|
1213
1293
|
} catch {
|
|
1214
1294
|
return new Set();
|
|
1215
1295
|
}
|
|
1216
1296
|
}
|
|
1217
1297
|
|
|
1218
|
-
function getConfiguredProviderOrderFromSettings(): string[] {
|
|
1298
|
+
function getConfiguredProviderOrderFromSettings(settingsReader: Pick<Settings, "getGlobal"> = settings): string[] {
|
|
1219
1299
|
try {
|
|
1220
|
-
const configured =
|
|
1300
|
+
const configured = settingsReader.getGlobal("modelProviderOrder");
|
|
1221
1301
|
return Array.isArray(configured) ? configured.filter((value): value is string => typeof value === "string") : [];
|
|
1222
1302
|
} catch {
|
|
1223
1303
|
return [];
|
|
@@ -1240,6 +1320,18 @@ interface ModelManagerDiscoveryOptions {
|
|
|
1240
1320
|
authGeneration: string;
|
|
1241
1321
|
apiKey: string | undefined;
|
|
1242
1322
|
endpoint: string;
|
|
1323
|
+
endpointContainsUserinfo: boolean;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
interface ConfiguredDiscoveryResult {
|
|
1327
|
+
provider: string;
|
|
1328
|
+
current: boolean;
|
|
1329
|
+
models: Model<Api>[];
|
|
1330
|
+
authGeneration: string;
|
|
1331
|
+
configurationGeneration: number;
|
|
1332
|
+
endpoint: string;
|
|
1333
|
+
fetched: boolean;
|
|
1334
|
+
clearPublishedModelIds?: readonly string[];
|
|
1243
1335
|
}
|
|
1244
1336
|
|
|
1245
1337
|
/**
|
|
@@ -1295,6 +1387,7 @@ export class ModelRegistry {
|
|
|
1295
1387
|
#modelProfiles: Map<string, ModelProfileDefinition> = mergeModelProfiles();
|
|
1296
1388
|
#configError: ConfigError | undefined = undefined;
|
|
1297
1389
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
1390
|
+
#settings: Pick<Settings, "get" | "getGlobal">;
|
|
1298
1391
|
#lastStaticLoadMtime: number | null = null;
|
|
1299
1392
|
#lastStaticLoadEnvironmentFingerprint: string | undefined;
|
|
1300
1393
|
#staticModelsLoaded = false;
|
|
@@ -1322,7 +1415,9 @@ export class ModelRegistry {
|
|
|
1322
1415
|
constructor(
|
|
1323
1416
|
readonly authStorage: AuthStorage,
|
|
1324
1417
|
modelsPath?: string,
|
|
1418
|
+
registrySettings?: Pick<Settings, "get" | "getGlobal">,
|
|
1325
1419
|
) {
|
|
1420
|
+
this.#settings = registrySettings ?? settings;
|
|
1326
1421
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
1327
1422
|
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
1328
1423
|
// Set up fallback resolver for custom provider API keys
|
|
@@ -1342,6 +1437,11 @@ export class ModelRegistry {
|
|
|
1342
1437
|
};
|
|
1343
1438
|
}
|
|
1344
1439
|
|
|
1440
|
+
/** Replace the read-only settings snapshot used by profile-scoped resolution. */
|
|
1441
|
+
setScopedSettings(settingsReader: Pick<Settings, "get" | "getGlobal">): void {
|
|
1442
|
+
this.#settings = settingsReader;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1345
1445
|
/**
|
|
1346
1446
|
* Reload models from disk (built-in + custom from models.json).
|
|
1347
1447
|
*/
|
|
@@ -1412,7 +1512,7 @@ export class ModelRegistry {
|
|
|
1412
1512
|
|
|
1413
1513
|
#reloadStaticModels(): void {
|
|
1414
1514
|
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1415
|
-
const disabledProviderKey = [...getDisabledProviderIdsFromSettings()].sort().join("\u0000");
|
|
1515
|
+
const disabledProviderKey = [...getDisabledProviderIdsFromSettings(this.#settings)].sort().join("\u0000");
|
|
1416
1516
|
const environmentFingerprint = this.#getStaticLoadEnvironmentFingerprint();
|
|
1417
1517
|
if (
|
|
1418
1518
|
this.#staticModelsLoaded &&
|
|
@@ -1658,7 +1758,16 @@ export class ModelRegistry {
|
|
|
1658
1758
|
continue;
|
|
1659
1759
|
}
|
|
1660
1760
|
const cache = readModelCache<Api>(descriptor.providerId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1661
|
-
|
|
1761
|
+
const expectedProvenance = fingerprintDescriptorDiscoveryProvenance(
|
|
1762
|
+
this.#getProviderEvidenceGeneration(descriptor.providerId),
|
|
1763
|
+
this.#normalizeDiscoveryEvidenceEndpoint(this.#getProviderBaseUrlForDiscovery(descriptor.providerId) ?? ""),
|
|
1764
|
+
);
|
|
1765
|
+
if (
|
|
1766
|
+
expectedProvenance === undefined ||
|
|
1767
|
+
!cache ||
|
|
1768
|
+
cache.dynamicModelIds === undefined ||
|
|
1769
|
+
cache.dynamicModelProvenance !== expectedProvenance
|
|
1770
|
+
) {
|
|
1662
1771
|
continue;
|
|
1663
1772
|
}
|
|
1664
1773
|
const models = cache.models.map(model =>
|
|
@@ -1678,12 +1787,33 @@ export class ModelRegistry {
|
|
|
1678
1787
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
1679
1788
|
const cachedModels: Model<Api>[] = [];
|
|
1680
1789
|
for (const providerConfig of this.#discoveryManager.providers) {
|
|
1681
|
-
|
|
1790
|
+
let expectedProvenance: string | undefined;
|
|
1791
|
+
try {
|
|
1792
|
+
const effectiveConfig = this.#effectiveDiscoveryProviderConfig(providerConfig);
|
|
1793
|
+
expectedProvenance = fingerprintConfiguredDiscoveryRequestShape(
|
|
1794
|
+
effectiveConfig,
|
|
1795
|
+
this.#getProviderEvidenceGeneration(effectiveConfig.provider),
|
|
1796
|
+
this.#normalizeDiscoveryEvidenceEndpoint(effectiveConfig.baseUrl ?? ""),
|
|
1797
|
+
);
|
|
1798
|
+
} catch {
|
|
1799
|
+
// A context that cannot be fully derived cannot vouch for a cache row.
|
|
1800
|
+
expectedProvenance = undefined;
|
|
1801
|
+
}
|
|
1802
|
+
const models = this.#discoveryManager.loadCached(providerConfig, this.#cacheDbPath, expectedProvenance);
|
|
1803
|
+
// Cache rows persist sanitized transport metadata (no headers), so a
|
|
1804
|
+
// rebooted registry re-derives the provider transport override from the
|
|
1805
|
+
// same source the live publish path uses — mirroring the cached
|
|
1806
|
+
// descriptor path — instead of serving header-less models until the
|
|
1807
|
+
// next successful online discovery.
|
|
1808
|
+
const providerOverride = this.#resolveProviderOverride(providerConfig.provider);
|
|
1809
|
+
const withTransport = providerOverride
|
|
1810
|
+
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
1811
|
+
: models;
|
|
1682
1812
|
const normalized = this.#applyProviderModelOverrides(
|
|
1683
1813
|
providerConfig.provider,
|
|
1684
1814
|
this.#normalizeDiscoverableModels(
|
|
1685
1815
|
providerConfig,
|
|
1686
|
-
this.#applyProviderCompat(providerConfig.compat, [...
|
|
1816
|
+
this.#applyProviderCompat(providerConfig.compat, [...withTransport]),
|
|
1687
1817
|
),
|
|
1688
1818
|
);
|
|
1689
1819
|
applyGeneratedModelPolicies(normalized);
|
|
@@ -1724,26 +1854,52 @@ export class ModelRegistry {
|
|
|
1724
1854
|
};
|
|
1725
1855
|
});
|
|
1726
1856
|
}
|
|
1727
|
-
#sanitizeDiscoverableModelsForCache(
|
|
1728
|
-
return
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
: models;
|
|
1857
|
+
#sanitizeDiscoverableModelsForCache(_providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
1858
|
+
return this.#stripModelBaseUrlQueries(models).map(model => ({
|
|
1859
|
+
...model,
|
|
1860
|
+
headers: undefined,
|
|
1861
|
+
}));
|
|
1733
1862
|
}
|
|
1734
1863
|
#stripModelBaseUrlQueries(models: readonly Model<Api>[]): Model<Api>[] {
|
|
1735
|
-
return models.map(model =>
|
|
1864
|
+
return models.map(model => {
|
|
1865
|
+
if (!model.baseUrl) return model;
|
|
1866
|
+
try {
|
|
1867
|
+
const parsed = new URL(model.baseUrl);
|
|
1868
|
+
parsed.username = "";
|
|
1869
|
+
parsed.password = "";
|
|
1870
|
+
parsed.search = "";
|
|
1871
|
+
parsed.hash = "";
|
|
1872
|
+
return { ...model, baseUrl: parsed.toString().replace(/\/$/, "") };
|
|
1873
|
+
} catch {
|
|
1874
|
+
const { baseUrl: _baseUrl, ...withoutBaseUrl } = model;
|
|
1875
|
+
return withoutBaseUrl as Model<Api>;
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
#stripUrlUserinfo(url: string | undefined): string | undefined {
|
|
1880
|
+
if (!url) return url;
|
|
1881
|
+
try {
|
|
1882
|
+
const parsed = new URL(url);
|
|
1883
|
+
if (!parsed.username && !parsed.password) return url;
|
|
1884
|
+
parsed.username = "";
|
|
1885
|
+
parsed.password = "";
|
|
1886
|
+
return parsed.toString();
|
|
1887
|
+
} catch {
|
|
1888
|
+
return undefined;
|
|
1889
|
+
}
|
|
1736
1890
|
}
|
|
1737
1891
|
#restoreLiveDiscoveryBaseUrl(modelBaseUrl: string | undefined, liveBaseUrl: string | undefined): string | undefined {
|
|
1738
|
-
if (!modelBaseUrl || !liveBaseUrl
|
|
1739
|
-
|
|
1892
|
+
if (!modelBaseUrl || !liveBaseUrl) return this.#stripUrlUserinfo(modelBaseUrl);
|
|
1893
|
+
const restored =
|
|
1894
|
+
this.#normalizeDiscoveryEvidenceEndpoint(stripUrlQuery(modelBaseUrl)) !==
|
|
1740
1895
|
this.#normalizeDiscoveryEvidenceEndpoint(stripUrlQuery(liveBaseUrl))
|
|
1741
|
-
|
|
1742
|
-
|
|
1896
|
+
? modelBaseUrl
|
|
1897
|
+
: liveBaseUrl;
|
|
1898
|
+
return this.#stripUrlUserinfo(restored);
|
|
1743
1899
|
}
|
|
1744
1900
|
|
|
1745
1901
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
1746
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1902
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
1747
1903
|
if (!configuredProviders.has("ollama") && !disabledProviders.has("ollama")) {
|
|
1748
1904
|
this.#discoveryManager.addProvider({
|
|
1749
1905
|
provider: "ollama",
|
|
@@ -2125,7 +2281,7 @@ export class ModelRegistry {
|
|
|
2125
2281
|
strategy: ModelRefreshStrategy,
|
|
2126
2282
|
providerFilter?: ReadonlySet<string>,
|
|
2127
2283
|
): Promise<void> {
|
|
2128
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2284
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
2129
2285
|
const selectedDiscoverableProviders = (
|
|
2130
2286
|
providerFilter
|
|
2131
2287
|
? this.#discoveryManager.providers.filter(provider => providerFilter.has(provider.provider))
|
|
@@ -2133,17 +2289,7 @@ export class ModelRegistry {
|
|
|
2133
2289
|
).filter(provider => !disabledProviders.has(provider.provider));
|
|
2134
2290
|
const configuredDiscoveriesPromise =
|
|
2135
2291
|
selectedDiscoverableProviders.length === 0
|
|
2136
|
-
? Promise.resolve(
|
|
2137
|
-
[] as Array<{
|
|
2138
|
-
provider: string;
|
|
2139
|
-
current: boolean;
|
|
2140
|
-
models: Model<Api>[];
|
|
2141
|
-
authGeneration: string;
|
|
2142
|
-
configurationGeneration: number;
|
|
2143
|
-
endpoint: string;
|
|
2144
|
-
fetched: boolean;
|
|
2145
|
-
}>,
|
|
2146
|
-
)
|
|
2292
|
+
? Promise.resolve([] as ConfiguredDiscoveryResult[])
|
|
2147
2293
|
: Promise.all(
|
|
2148
2294
|
selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
|
|
2149
2295
|
);
|
|
@@ -2242,6 +2388,11 @@ export class ModelRegistry {
|
|
|
2242
2388
|
const configuredDiscoveries = new Map(currentConfiguredDiscoveryResults.map(result => [result.provider, result]));
|
|
2243
2389
|
const configuredDiscovered = currentConfiguredDiscoveryResults.flatMap(result => result.models);
|
|
2244
2390
|
const discovered = [...configuredDiscovered, ...currentBuiltInDiscovered];
|
|
2391
|
+
const clearPublishedModelIds = new Map(
|
|
2392
|
+
currentConfiguredDiscoveryResults
|
|
2393
|
+
.filter(result => result.current && result.clearPublishedModelIds !== undefined)
|
|
2394
|
+
.map(result => [result.provider, new Set(result.clearPublishedModelIds ?? [])]),
|
|
2395
|
+
);
|
|
2245
2396
|
for (const provider of selectedDiscoverableProviders) {
|
|
2246
2397
|
const evidence = configuredDiscoveryEvidence.get(provider.provider);
|
|
2247
2398
|
const discovery = configuredDiscoveries.get(provider.provider);
|
|
@@ -2272,7 +2423,19 @@ export class ModelRegistry {
|
|
|
2272
2423
|
}
|
|
2273
2424
|
}
|
|
2274
2425
|
this.#rebuildProviderActivity();
|
|
2426
|
+
if (clearPublishedModelIds.size > 0) {
|
|
2427
|
+
this.#models = this.#models.filter(model => {
|
|
2428
|
+
const ids = clearPublishedModelIds.get(model.provider);
|
|
2429
|
+
return (
|
|
2430
|
+
!ids?.has(model.id) ||
|
|
2431
|
+
(this.#providerActivity.get(model.provider)?.staticModelIds.has(model.id) ?? false)
|
|
2432
|
+
);
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2275
2435
|
if (discovered.length === 0) {
|
|
2436
|
+
if (clearPublishedModelIds.size > 0) {
|
|
2437
|
+
this.#rebuildCanonicalIndex();
|
|
2438
|
+
}
|
|
2276
2439
|
return;
|
|
2277
2440
|
}
|
|
2278
2441
|
const discoveredModels = this.#applyHardcodedModelPolicies(
|
|
@@ -2300,15 +2463,7 @@ export class ModelRegistry {
|
|
|
2300
2463
|
async #discoverProviderModels(
|
|
2301
2464
|
providerConfig: DiscoveryProviderConfig,
|
|
2302
2465
|
strategy: ModelRefreshStrategy,
|
|
2303
|
-
): Promise<{
|
|
2304
|
-
provider: string;
|
|
2305
|
-
current: boolean;
|
|
2306
|
-
models: Model<Api>[];
|
|
2307
|
-
authGeneration: string;
|
|
2308
|
-
configurationGeneration: number;
|
|
2309
|
-
endpoint: string;
|
|
2310
|
-
fetched: boolean;
|
|
2311
|
-
}> {
|
|
2466
|
+
): Promise<ConfiguredDiscoveryResult> {
|
|
2312
2467
|
const provider = providerConfig.provider;
|
|
2313
2468
|
const preflightEpoch = this.#optionalAuthPreflightEpoch;
|
|
2314
2469
|
const preflightGeneration = (this.#optionalAuthPreflightGenerations.get(provider) ?? 0) + 1;
|
|
@@ -2394,10 +2549,32 @@ export class ModelRegistry {
|
|
|
2394
2549
|
endpoint ===
|
|
2395
2550
|
this.#normalizeDiscoveryEvidenceEndpoint(this.#effectiveDiscoveryProviderConfig(providerConfig).baseUrl ?? "");
|
|
2396
2551
|
const evidence = this.#configuredDiscoveryEvidence.get(provider);
|
|
2552
|
+
const cacheDynamicModelProvenance = fingerprintConfiguredDiscoveryRequestShape(
|
|
2553
|
+
effectiveProviderConfig,
|
|
2554
|
+
authGenerationBeforeDiscovery,
|
|
2555
|
+
endpoint,
|
|
2556
|
+
);
|
|
2557
|
+
if (cacheDynamicModelProvenance === undefined && strategy === "offline") {
|
|
2558
|
+
const previouslyPublishedModelIds = [...(evidence?.modelIds ?? [])];
|
|
2559
|
+
this.#configuredDiscoveryEvidence.delete(provider);
|
|
2560
|
+
return {
|
|
2561
|
+
provider: effectiveProviderConfig.provider,
|
|
2562
|
+
current: true,
|
|
2563
|
+
models: [],
|
|
2564
|
+
authGeneration: authGenerationBeforeDiscovery,
|
|
2565
|
+
configurationGeneration: preflightAuthConfigurationGeneration,
|
|
2566
|
+
endpoint,
|
|
2567
|
+
fetched: false,
|
|
2568
|
+
clearPublishedModelIds: previouslyPublishedModelIds,
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
const cacheLookupProvenance =
|
|
2572
|
+
cacheDynamicModelProvenance ?? `gajae:non-cacheable-configured:${crypto.randomUUID()}`;
|
|
2397
2573
|
const refreshStrategy =
|
|
2398
2574
|
strategy === "online-if-uncached" &&
|
|
2399
|
-
|
|
2400
|
-
|
|
2575
|
+
(cacheDynamicModelProvenance === undefined ||
|
|
2576
|
+
(evidence !== undefined &&
|
|
2577
|
+
(evidence.authGeneration !== authGenerationBeforeDiscovery || evidence.endpoint !== endpoint)))
|
|
2401
2578
|
? "online"
|
|
2402
2579
|
: strategy;
|
|
2403
2580
|
const mergeInput = await this.#discoveryManager.discover(effectiveProviderConfig, refreshStrategy, {
|
|
@@ -2418,7 +2595,8 @@ export class ModelRegistry {
|
|
|
2418
2595
|
await this.#discoverModelsByProviderType(provider, apiKey),
|
|
2419
2596
|
),
|
|
2420
2597
|
getEvidenceGeneration: provider => this.#getProviderEvidenceGeneration(provider.provider, preflightApiKey),
|
|
2421
|
-
|
|
2598
|
+
cacheDynamicModelProvenance: cacheLookupProvenance,
|
|
2599
|
+
canPublishCache: isCurrentEndpoint,
|
|
2422
2600
|
});
|
|
2423
2601
|
const authGeneration =
|
|
2424
2602
|
mergeInput.authGeneration ??
|
|
@@ -2555,7 +2733,7 @@ export class ModelRegistry {
|
|
|
2555
2733
|
},
|
|
2556
2734
|
},
|
|
2557
2735
|
];
|
|
2558
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2736
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
2559
2737
|
const standardProviderDescriptors = PROVIDER_DESCRIPTORS.filter(
|
|
2560
2738
|
descriptor => !disabledProviders.has(descriptor.providerId) && !excludedProviderIds.has(descriptor.providerId),
|
|
2561
2739
|
);
|
|
@@ -2603,6 +2781,7 @@ export class ModelRegistry {
|
|
|
2603
2781
|
authGeneration,
|
|
2604
2782
|
apiKey,
|
|
2605
2783
|
endpoint: this.#normalizeDiscoveryEvidenceEndpoint(baseUrl ?? ""),
|
|
2784
|
+
endpointContainsUserinfo: this.#urlContainsUserinfo(baseUrl ?? ""),
|
|
2606
2785
|
});
|
|
2607
2786
|
}
|
|
2608
2787
|
}
|
|
@@ -2617,13 +2796,13 @@ export class ModelRegistry {
|
|
|
2617
2796
|
isAuthenticated(key)
|
|
2618
2797
|
) {
|
|
2619
2798
|
const managerOptions = descriptor.createOptions(key);
|
|
2799
|
+
const baseUrl = this.#getProviderBaseUrlForDiscovery(descriptor.providerId) ?? "";
|
|
2620
2800
|
options.push({
|
|
2621
2801
|
options: managerOptions,
|
|
2622
2802
|
authGeneration,
|
|
2623
2803
|
apiKey: apiKeyValue,
|
|
2624
|
-
endpoint: this.#normalizeDiscoveryEvidenceEndpoint(
|
|
2625
|
-
|
|
2626
|
-
),
|
|
2804
|
+
endpoint: this.#normalizeDiscoveryEvidenceEndpoint(baseUrl),
|
|
2805
|
+
endpointContainsUserinfo: this.#urlContainsUserinfo(baseUrl),
|
|
2627
2806
|
});
|
|
2628
2807
|
}
|
|
2629
2808
|
}
|
|
@@ -2631,7 +2810,7 @@ export class ModelRegistry {
|
|
|
2631
2810
|
}
|
|
2632
2811
|
|
|
2633
2812
|
async #discoverWithModelManager(
|
|
2634
|
-
{ options, authGeneration, apiKey, endpoint }: ModelManagerDiscoveryOptions,
|
|
2813
|
+
{ options, authGeneration, apiKey, endpoint, endpointContainsUserinfo }: ModelManagerDiscoveryOptions,
|
|
2635
2814
|
strategy: ModelRefreshStrategy,
|
|
2636
2815
|
): Promise<Model<Api>[]> {
|
|
2637
2816
|
const generation = (this.#descriptorDiscoveryGenerations.get(options.providerId) ?? 0) + 1;
|
|
@@ -2643,6 +2822,16 @@ export class ModelRegistry {
|
|
|
2643
2822
|
this.#providerOverrides.get("xiaomi")?.baseUrl === undefined &&
|
|
2644
2823
|
resolveProviderBaseUrlFromEnv("xiaomi") === undefined;
|
|
2645
2824
|
let credentialDerivedEndpoint: string | undefined;
|
|
2825
|
+
const reusableCacheProvenance = endpointContainsUserinfo
|
|
2826
|
+
? undefined
|
|
2827
|
+
: fingerprintDescriptorDiscoveryProvenance(authGeneration, endpoint);
|
|
2828
|
+
if (reusableCacheProvenance === undefined && strategy === "offline") return [];
|
|
2829
|
+
// Signed/userinfo endpoints may still publish a sanitized best-effort row,
|
|
2830
|
+
// but each refresh gets a new opaque nonce. The nonce contains no endpoint
|
|
2831
|
+
// secret and can never match a previous invocation, so constructor loads,
|
|
2832
|
+
// stale fallback, and online-if-uncached reuse all fail closed.
|
|
2833
|
+
const cacheDynamicModelProvenance =
|
|
2834
|
+
reusableCacheProvenance ?? `gajae:non-cacheable-endpoint:${crypto.randomUUID()}`;
|
|
2646
2835
|
const isCurrentDiscovery = () =>
|
|
2647
2836
|
(this.#descriptorDiscoveryGenerations.get(options.providerId) ?? 0) === generation &&
|
|
2648
2837
|
this.#getProviderEvidenceGeneration(options.providerId, apiKey) === authGeneration &&
|
|
@@ -2653,7 +2842,7 @@ export class ModelRegistry {
|
|
|
2653
2842
|
const manager = createModelManager({
|
|
2654
2843
|
...options,
|
|
2655
2844
|
cacheDbPath: this.#cacheDbPath,
|
|
2656
|
-
cacheDynamicModelProvenance
|
|
2845
|
+
cacheDynamicModelProvenance,
|
|
2657
2846
|
canPublishCache: isCurrentDiscovery,
|
|
2658
2847
|
...(options.fetchDynamicModels
|
|
2659
2848
|
? {
|
|
@@ -3136,6 +3325,14 @@ export class ModelRegistry {
|
|
|
3136
3325
|
return endpoint.replace(/\/+$/g, "");
|
|
3137
3326
|
}
|
|
3138
3327
|
}
|
|
3328
|
+
#urlContainsUserinfo(endpoint: string): boolean {
|
|
3329
|
+
try {
|
|
3330
|
+
const parsed = new URL(endpoint);
|
|
3331
|
+
return Boolean(parsed.username || parsed.password);
|
|
3332
|
+
} catch {
|
|
3333
|
+
return false;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3139
3336
|
#isCredentiallessProvider(provider: string): boolean {
|
|
3140
3337
|
let fallbackMatchesCurrentEvidence = false;
|
|
3141
3338
|
const fallbackEvidenceGeneration = this.#credentiallessAuthFallbackProviders.get(provider);
|
|
@@ -3171,7 +3368,8 @@ export class ModelRegistry {
|
|
|
3171
3368
|
const parsed = new URL(raw);
|
|
3172
3369
|
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
3173
3370
|
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
|
|
3174
|
-
|
|
3371
|
+
parsed.hash = "";
|
|
3372
|
+
return parsed.toString().replace(/\/$/, "");
|
|
3175
3373
|
} catch {
|
|
3176
3374
|
return raw;
|
|
3177
3375
|
}
|
|
@@ -3278,10 +3476,15 @@ export class ModelRegistry {
|
|
|
3278
3476
|
}
|
|
3279
3477
|
#applyRuntimeProviderOverride(model: Model<Api>, override: ProviderOverride): Model<Api> {
|
|
3280
3478
|
const withTransportOverride = this.#applyProviderTransportOverride(model, override);
|
|
3479
|
+
const sanitizedBaseUrl = this.#stripUrlUserinfo(withTransportOverride.baseUrl);
|
|
3480
|
+
const sanitizedTransport: Model<Api> =
|
|
3481
|
+
sanitizedBaseUrl === withTransportOverride.baseUrl
|
|
3482
|
+
? withTransportOverride
|
|
3483
|
+
: { ...withTransportOverride, ...(sanitizedBaseUrl === undefined ? {} : { baseUrl: sanitizedBaseUrl }) };
|
|
3281
3484
|
const modelCompat = this.#modelOverrides.get(model.provider.toLowerCase())?.get(model.id.toLowerCase())?.compat;
|
|
3282
3485
|
return modelCompat
|
|
3283
|
-
? { ...
|
|
3284
|
-
:
|
|
3486
|
+
? { ...sanitizedTransport, compat: mergeCompat(sanitizedTransport.compat, modelCompat) }
|
|
3487
|
+
: sanitizedTransport;
|
|
3285
3488
|
}
|
|
3286
3489
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
3287
3490
|
if (this.#runtimeProviderOverrides.size === 0) return models;
|
|
@@ -3459,21 +3662,43 @@ export class ModelRegistry {
|
|
|
3459
3662
|
return [...this.#configuredProviderIds];
|
|
3460
3663
|
}
|
|
3461
3664
|
|
|
3462
|
-
#isModelAvailable(model: Model<Api>, disabledProviders
|
|
3665
|
+
#isModelAvailable(model: Model<Api>, disabledProviders?: ReadonlySet<string>): boolean {
|
|
3666
|
+
const disabled = disabledProviders ?? getDisabledProviderIdsFromSettings(this.#settings);
|
|
3463
3667
|
return (
|
|
3464
|
-
!
|
|
3668
|
+
!disabled.has(model.provider) &&
|
|
3465
3669
|
(this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider))
|
|
3466
3670
|
);
|
|
3467
3671
|
}
|
|
3672
|
+
/** Per-query precomputed variant filter inputs; plan fields are authoritative when provided. */
|
|
3673
|
+
#buildCanonicalVariantFilterPlan(options: CanonicalModelQueryOptions | undefined): {
|
|
3674
|
+
candidateKeys: Set<string> | undefined;
|
|
3675
|
+
disabledProviders: ReadonlySet<string> | undefined;
|
|
3676
|
+
} {
|
|
3677
|
+
return {
|
|
3678
|
+
candidateKeys: options?.candidates
|
|
3679
|
+
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
3680
|
+
: undefined,
|
|
3681
|
+
disabledProviders: options?.availableOnly ? getDisabledProviderIdsFromSettings(this.#settings) : undefined,
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3468
3684
|
|
|
3469
3685
|
#filterCanonicalVariants(
|
|
3470
3686
|
record: CanonicalModelRecord,
|
|
3471
3687
|
options: CanonicalModelQueryOptions | undefined,
|
|
3688
|
+
plan?: { candidateKeys: Set<string> | undefined; disabledProviders: ReadonlySet<string> | undefined },
|
|
3472
3689
|
): CanonicalModelVariant[] {
|
|
3473
|
-
const candidateKeys =
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3690
|
+
const candidateKeys =
|
|
3691
|
+
plan !== undefined
|
|
3692
|
+
? plan.candidateKeys
|
|
3693
|
+
: options?.candidates
|
|
3694
|
+
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
3695
|
+
: undefined;
|
|
3696
|
+
const disabledProviders =
|
|
3697
|
+
plan !== undefined
|
|
3698
|
+
? plan.disabledProviders
|
|
3699
|
+
: options?.availableOnly
|
|
3700
|
+
? getDisabledProviderIdsFromSettings(this.#settings)
|
|
3701
|
+
: undefined;
|
|
3477
3702
|
return record.variants.filter(variant => {
|
|
3478
3703
|
if (candidateKeys && !candidateKeys.has(variant.selector)) return false;
|
|
3479
3704
|
if (options?.availableOnly && !this.#isModelAvailable(variant.model, disabledProviders)) return false;
|
|
@@ -3520,13 +3745,30 @@ export class ModelRegistry {
|
|
|
3520
3745
|
effectiveAuth.set(providerKey, this.#effectiveProviderAuth(model.provider, sessionId));
|
|
3521
3746
|
}
|
|
3522
3747
|
return createProviderSelectionPolicy({
|
|
3523
|
-
explicitProviderOrder: getConfiguredProviderOrderFromSettings(),
|
|
3748
|
+
explicitProviderOrder: getConfiguredProviderOrderFromSettings(this.#settings),
|
|
3524
3749
|
effectiveAuth,
|
|
3525
3750
|
catalogProviders,
|
|
3526
3751
|
catalogModels,
|
|
3527
3752
|
});
|
|
3528
3753
|
}
|
|
3529
3754
|
|
|
3755
|
+
/**
|
|
3756
|
+
* Deterministic provider priority for autorouting tier generation: configured
|
|
3757
|
+
* `modelProviderOrder` first, then first-wins catalog order.
|
|
3758
|
+
*
|
|
3759
|
+
* Deliberately takes no session and never touches `authStorage`. It bypasses
|
|
3760
|
+
* `#buildProviderSelectionPolicy` entirely so no `effectiveAuth` map is even
|
|
3761
|
+
* assembled — auth-independence is structural here, not a convention. Ranking
|
|
3762
|
+
* that *is* auth-aware stays private to the policy.
|
|
3763
|
+
*
|
|
3764
|
+
* Providers absent from the catalog are dropped so a dead declaration cannot
|
|
3765
|
+
* pollute the generated setup's `declarationFingerprint`. Returned ids use the
|
|
3766
|
+
* catalog's original spelling because the generator matches provider prefixes
|
|
3767
|
+
* with case-sensitive exact strings.
|
|
3768
|
+
*/
|
|
3769
|
+
autoroutingProviderOrder(): readonly string[] {
|
|
3770
|
+
return projectCatalogProviderOrder(getConfiguredProviderOrderFromSettings(), this.#models);
|
|
3771
|
+
}
|
|
3530
3772
|
#providerRankMap(policy: ProviderSelectionPolicy): Map<string, number> {
|
|
3531
3773
|
const providerRank = new Map<string, number>();
|
|
3532
3774
|
for (const provider of policy.orderedProviders()) {
|
|
@@ -3572,7 +3814,13 @@ export class ModelRegistry {
|
|
|
3572
3814
|
#resolveCanonicalVariant(
|
|
3573
3815
|
variants: readonly CanonicalModelVariant[],
|
|
3574
3816
|
sessionId?: string,
|
|
3575
|
-
options: {
|
|
3817
|
+
options: {
|
|
3818
|
+
providerRankFirst?: boolean;
|
|
3819
|
+
exactnessKey?: string;
|
|
3820
|
+
credentialSessionId?: string;
|
|
3821
|
+
providerRank?: Map<string, number>;
|
|
3822
|
+
modelOrder?: Map<string, number>;
|
|
3823
|
+
} = {},
|
|
3576
3824
|
): CanonicalModelVariant | undefined {
|
|
3577
3825
|
if (variants.length === 0) return undefined;
|
|
3578
3826
|
const normalizedSessionId = sessionId?.trim();
|
|
@@ -3585,9 +3833,10 @@ export class ModelRegistry {
|
|
|
3585
3833
|
return stickyVariant;
|
|
3586
3834
|
}
|
|
3587
3835
|
if (normalizedSessionId && stickySelector) this.#sessionCanonicalVariants.delete(normalizedSessionId);
|
|
3588
|
-
const
|
|
3589
|
-
|
|
3590
|
-
|
|
3836
|
+
const providerRank =
|
|
3837
|
+
options.providerRank ??
|
|
3838
|
+
this.#providerRankMap(this.#buildProviderSelectionPolicy(options.credentialSessionId ?? normalizedSessionId));
|
|
3839
|
+
const modelOrder = options.modelOrder ?? this.#catalogModelOrder();
|
|
3591
3840
|
const sourceRank: Record<CanonicalModelVariant["source"], number> = {
|
|
3592
3841
|
override: 1,
|
|
3593
3842
|
bundled: 1,
|
|
@@ -3609,9 +3858,10 @@ export class ModelRegistry {
|
|
|
3609
3858
|
}
|
|
3610
3859
|
|
|
3611
3860
|
getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
|
|
3861
|
+
const filterPlan = this.#buildCanonicalVariantFilterPlan(options);
|
|
3612
3862
|
const records: CanonicalModelRecord[] = [];
|
|
3613
3863
|
for (const record of this.#canonicalIndex.records) {
|
|
3614
|
-
const variants = this.#filterCanonicalVariants(record, options);
|
|
3864
|
+
const variants = this.#filterCanonicalVariants(record, options, filterPlan);
|
|
3615
3865
|
if (variants.length === 0) {
|
|
3616
3866
|
continue;
|
|
3617
3867
|
}
|
|
@@ -3624,6 +3874,36 @@ export class ModelRegistry {
|
|
|
3624
3874
|
return records;
|
|
3625
3875
|
}
|
|
3626
3876
|
|
|
3877
|
+
/**
|
|
3878
|
+
* Batch form of {@link resolveCanonicalModel} over every canonical record:
|
|
3879
|
+
* one candidate-key set, one provider policy, and one catalog order for the
|
|
3880
|
+
* whole query instead of per record. `model` is `undefined` only when a
|
|
3881
|
+
* record has surviving variants but none can win resolution.
|
|
3882
|
+
*/
|
|
3883
|
+
getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[] {
|
|
3884
|
+
const filterPlan = this.#buildCanonicalVariantFilterPlan(options);
|
|
3885
|
+
const providerRank = this.#providerRankMap(
|
|
3886
|
+
this.#buildProviderSelectionPolicy(options?.credentialSessionId ?? options?.sessionId?.trim()),
|
|
3887
|
+
);
|
|
3888
|
+
const modelOrder = this.#catalogModelOrder();
|
|
3889
|
+
const selections: CanonicalModelSelection[] = [];
|
|
3890
|
+
for (const record of this.#canonicalIndex.records) {
|
|
3891
|
+
const variants = this.#filterCanonicalVariants(record, options, filterPlan);
|
|
3892
|
+
if (variants.length === 0) {
|
|
3893
|
+
continue;
|
|
3894
|
+
}
|
|
3895
|
+
const resolved = this.#resolveCanonicalVariant(variants, options?.sessionId, {
|
|
3896
|
+
providerRankFirst: true,
|
|
3897
|
+
credentialSessionId: options?.credentialSessionId,
|
|
3898
|
+
providerRank,
|
|
3899
|
+
modelOrder,
|
|
3900
|
+
});
|
|
3901
|
+
if (resolved && options?.sessionId) this.#rememberCanonicalVariant(options.sessionId, resolved.selector);
|
|
3902
|
+
selections.push({ record: { id: record.id, name: record.name, variants }, model: resolved?.model });
|
|
3903
|
+
}
|
|
3904
|
+
return selections;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3627
3907
|
getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
|
|
3628
3908
|
const record = this.#canonicalIndex.byId.get(canonicalId.trim().toLowerCase());
|
|
3629
3909
|
if (!record) {
|
|
@@ -3790,7 +4070,7 @@ export class ModelRegistry {
|
|
|
3790
4070
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
3791
4071
|
*/
|
|
3792
4072
|
getAvailable(): Model<Api>[] {
|
|
3793
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
4073
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
3794
4074
|
const disabledProviderKey = [...disabledProviders].sort().join("\u0000");
|
|
3795
4075
|
const envFingerprint = envAvailabilityFingerprint();
|
|
3796
4076
|
if (
|
|
@@ -3926,7 +4206,7 @@ export class ModelRegistry {
|
|
|
3926
4206
|
getActiveProviders(): ActiveProviderDescriptor[] {
|
|
3927
4207
|
try {
|
|
3928
4208
|
const descriptors: ActiveProviderDescriptor[] = [];
|
|
3929
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
4209
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
3930
4210
|
const available = this.#models.filter(model => this.#isModelAvailable(model, disabledProviders));
|
|
3931
4211
|
for (const model of available) {
|
|
3932
4212
|
const connectionKind = this.#activeConnectionKind(model);
|
|
@@ -3963,7 +4243,7 @@ export class ModelRegistry {
|
|
|
3963
4243
|
}
|
|
3964
4244
|
|
|
3965
4245
|
getDiscoverableProviders(): string[] {
|
|
3966
|
-
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
4246
|
+
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
3967
4247
|
return this.#discoveryManager.providers
|
|
3968
4248
|
.filter(provider => !disabledProviders.has(provider.provider))
|
|
3969
4249
|
.map(provider => provider.provider);
|