@oh-my-pi/pi-coding-agent 0.1.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 +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model registry - manages built-in and custom models, provides API key resolution.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import {
|
|
7
|
+
type Api,
|
|
8
|
+
getGitHubCopilotBaseUrl,
|
|
9
|
+
getModels,
|
|
10
|
+
getProviders,
|
|
11
|
+
type KnownProvider,
|
|
12
|
+
type Model,
|
|
13
|
+
normalizeDomain,
|
|
14
|
+
} from "@oh-my-pi/pi-ai";
|
|
15
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
16
|
+
import AjvModule from "ajv";
|
|
17
|
+
import type { AuthStorage } from "./auth-storage";
|
|
18
|
+
import { logger } from "./logger";
|
|
19
|
+
|
|
20
|
+
const Ajv = (AjvModule as any).default || AjvModule;
|
|
21
|
+
|
|
22
|
+
// Schema for OpenAI compatibility settings
|
|
23
|
+
const OpenAICompatSchema = Type.Object({
|
|
24
|
+
supportsStore: Type.Optional(Type.Boolean()),
|
|
25
|
+
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
26
|
+
supportsReasoningEffort: Type.Optional(Type.Boolean()),
|
|
27
|
+
maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Schema for custom model definition
|
|
31
|
+
const ModelDefinitionSchema = Type.Object({
|
|
32
|
+
id: Type.String({ minLength: 1 }),
|
|
33
|
+
name: Type.String({ minLength: 1 }),
|
|
34
|
+
api: Type.Optional(
|
|
35
|
+
Type.Union([
|
|
36
|
+
Type.Literal("openai-completions"),
|
|
37
|
+
Type.Literal("openai-responses"),
|
|
38
|
+
Type.Literal("anthropic-messages"),
|
|
39
|
+
Type.Literal("google-generative-ai"),
|
|
40
|
+
]),
|
|
41
|
+
),
|
|
42
|
+
reasoning: Type.Boolean(),
|
|
43
|
+
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
|
44
|
+
cost: Type.Object({
|
|
45
|
+
input: Type.Number(),
|
|
46
|
+
output: Type.Number(),
|
|
47
|
+
cacheRead: Type.Number(),
|
|
48
|
+
cacheWrite: Type.Number(),
|
|
49
|
+
}),
|
|
50
|
+
contextWindow: Type.Number(),
|
|
51
|
+
maxTokens: Type.Number(),
|
|
52
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
53
|
+
compat: Type.Optional(OpenAICompatSchema),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const ProviderConfigSchema = Type.Object({
|
|
57
|
+
baseUrl: Type.String({ minLength: 1 }),
|
|
58
|
+
apiKey: Type.String({ minLength: 1 }),
|
|
59
|
+
api: Type.Optional(
|
|
60
|
+
Type.Union([
|
|
61
|
+
Type.Literal("openai-completions"),
|
|
62
|
+
Type.Literal("openai-responses"),
|
|
63
|
+
Type.Literal("anthropic-messages"),
|
|
64
|
+
Type.Literal("google-generative-ai"),
|
|
65
|
+
]),
|
|
66
|
+
),
|
|
67
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
68
|
+
authHeader: Type.Optional(Type.Boolean()),
|
|
69
|
+
models: Type.Array(ModelDefinitionSchema),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const ModelsConfigSchema = Type.Object({
|
|
73
|
+
providers: Type.Record(Type.String(), ProviderConfigSchema),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
type ModelsConfig = Static<typeof ModelsConfigSchema>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve an API key config value to an actual key.
|
|
80
|
+
* Checks environment variable first, then treats as literal.
|
|
81
|
+
*/
|
|
82
|
+
function resolveApiKeyConfig(keyConfig: string): string | undefined {
|
|
83
|
+
const envValue = process.env[keyConfig];
|
|
84
|
+
if (envValue) return envValue;
|
|
85
|
+
return keyConfig;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
90
|
+
*/
|
|
91
|
+
export class ModelRegistry {
|
|
92
|
+
private models: Model<Api>[] = [];
|
|
93
|
+
private customProviderApiKeys: Map<string, string> = new Map();
|
|
94
|
+
private loadError: string | undefined = undefined;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param authStorage - Auth storage for API key resolution
|
|
98
|
+
* @param modelsJsonPath - Primary path for models.json
|
|
99
|
+
* @param fallbackPaths - Additional paths to check (legacy support)
|
|
100
|
+
*/
|
|
101
|
+
constructor(
|
|
102
|
+
readonly authStorage: AuthStorage,
|
|
103
|
+
private modelsJsonPath: string | undefined = undefined,
|
|
104
|
+
private fallbackPaths: string[] = [],
|
|
105
|
+
) {
|
|
106
|
+
// Set up fallback resolver for custom provider API keys
|
|
107
|
+
this.authStorage.setFallbackResolver((provider) => {
|
|
108
|
+
const keyConfig = this.customProviderApiKeys.get(provider);
|
|
109
|
+
if (keyConfig) {
|
|
110
|
+
return resolveApiKeyConfig(keyConfig);
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Load models
|
|
116
|
+
this.loadModels();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reload models from disk (built-in + custom from models.json).
|
|
121
|
+
*/
|
|
122
|
+
refresh(): void {
|
|
123
|
+
this.customProviderApiKeys.clear();
|
|
124
|
+
this.loadError = undefined;
|
|
125
|
+
this.loadModels();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get any error from loading models.json (undefined if no error).
|
|
130
|
+
*/
|
|
131
|
+
getError(): string | undefined {
|
|
132
|
+
return this.loadError;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private loadModels(): void {
|
|
136
|
+
// Load built-in models
|
|
137
|
+
const builtInModels: Model<Api>[] = [];
|
|
138
|
+
for (const provider of getProviders()) {
|
|
139
|
+
const providerModels = getModels(provider as KnownProvider);
|
|
140
|
+
builtInModels.push(...(providerModels as Model<Api>[]));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Load custom models from models.json (check primary path, then fallbacks)
|
|
144
|
+
let customModels: Model<Api>[] = [];
|
|
145
|
+
const pathsToCheck = this.modelsJsonPath ? [this.modelsJsonPath, ...this.fallbackPaths] : this.fallbackPaths;
|
|
146
|
+
|
|
147
|
+
if (pathsToCheck.length > 0) {
|
|
148
|
+
logger.debug("ModelRegistry.loadModels checking paths", { paths: pathsToCheck });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const modelsPath of pathsToCheck) {
|
|
152
|
+
if (existsSync(modelsPath)) {
|
|
153
|
+
logger.debug("ModelRegistry.loadModels loading", { path: modelsPath });
|
|
154
|
+
const result = this.loadCustomModels(modelsPath);
|
|
155
|
+
if (result.error) {
|
|
156
|
+
this.loadError = result.error;
|
|
157
|
+
// Keep built-in models even if custom models failed to load
|
|
158
|
+
} else {
|
|
159
|
+
customModels = result.models;
|
|
160
|
+
}
|
|
161
|
+
break; // Use first existing file
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const combined = [...builtInModels, ...customModels];
|
|
166
|
+
|
|
167
|
+
// Update github-copilot base URL based on OAuth credentials
|
|
168
|
+
const copilotCred = this.authStorage.get("github-copilot");
|
|
169
|
+
if (copilotCred?.type === "oauth") {
|
|
170
|
+
const domain = copilotCred.enterpriseUrl
|
|
171
|
+
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
|
172
|
+
: undefined;
|
|
173
|
+
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
|
174
|
+
this.models = combined.map((m) => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
175
|
+
} else {
|
|
176
|
+
this.models = combined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private loadCustomModels(modelsJsonPath: string): { models: Model<Api>[]; error: string | undefined } {
|
|
181
|
+
if (!existsSync(modelsJsonPath)) {
|
|
182
|
+
return { models: [], error: undefined };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const content = readFileSync(modelsJsonPath, "utf-8");
|
|
187
|
+
const config: ModelsConfig = JSON.parse(content);
|
|
188
|
+
|
|
189
|
+
// Validate schema
|
|
190
|
+
const ajv = new Ajv();
|
|
191
|
+
const validate = ajv.compile(ModelsConfigSchema);
|
|
192
|
+
if (!validate(config)) {
|
|
193
|
+
const errors =
|
|
194
|
+
validate.errors?.map((e: any) => ` - ${e.instancePath || "root"}: ${e.message}`).join("\n") ||
|
|
195
|
+
"Unknown schema error";
|
|
196
|
+
return {
|
|
197
|
+
models: [],
|
|
198
|
+
error: `Invalid models.json schema:\n${errors}\n\nFile: ${modelsJsonPath}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Additional validation
|
|
203
|
+
this.validateConfig(config);
|
|
204
|
+
|
|
205
|
+
// Parse models
|
|
206
|
+
return { models: this.parseModels(config), error: undefined };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof SyntaxError) {
|
|
209
|
+
return {
|
|
210
|
+
models: [],
|
|
211
|
+
error: `Failed to parse models.json: ${error.message}\n\nFile: ${modelsJsonPath}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
models: [],
|
|
216
|
+
error: `Failed to load models.json: ${
|
|
217
|
+
error instanceof Error ? error.message : error
|
|
218
|
+
}\n\nFile: ${modelsJsonPath}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private validateConfig(config: ModelsConfig): void {
|
|
224
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
225
|
+
const hasProviderApi = !!providerConfig.api;
|
|
226
|
+
|
|
227
|
+
for (const modelDef of providerConfig.models) {
|
|
228
|
+
const hasModelApi = !!modelDef.api;
|
|
229
|
+
|
|
230
|
+
if (!hasProviderApi && !hasModelApi) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!modelDef.id) throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
237
|
+
if (!modelDef.name) throw new Error(`Provider ${providerName}: model missing "name"`);
|
|
238
|
+
if (modelDef.contextWindow <= 0)
|
|
239
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
240
|
+
if (modelDef.maxTokens <= 0)
|
|
241
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private parseModels(config: ModelsConfig): Model<Api>[] {
|
|
247
|
+
const models: Model<Api>[] = [];
|
|
248
|
+
|
|
249
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
250
|
+
// Store API key config for fallback resolver
|
|
251
|
+
this.customProviderApiKeys.set(providerName, providerConfig.apiKey);
|
|
252
|
+
|
|
253
|
+
for (const modelDef of providerConfig.models) {
|
|
254
|
+
const api = modelDef.api || providerConfig.api;
|
|
255
|
+
if (!api) continue;
|
|
256
|
+
|
|
257
|
+
// Merge headers: provider headers are base, model headers override
|
|
258
|
+
let headers =
|
|
259
|
+
providerConfig.headers || modelDef.headers
|
|
260
|
+
? { ...providerConfig.headers, ...modelDef.headers }
|
|
261
|
+
: undefined;
|
|
262
|
+
|
|
263
|
+
// If authHeader is true, add Authorization header with resolved API key
|
|
264
|
+
if (providerConfig.authHeader) {
|
|
265
|
+
const resolvedKey = resolveApiKeyConfig(providerConfig.apiKey);
|
|
266
|
+
if (resolvedKey) {
|
|
267
|
+
headers = { ...headers, Authorization: `Bearer ${resolvedKey}` };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
models.push({
|
|
272
|
+
id: modelDef.id,
|
|
273
|
+
name: modelDef.name,
|
|
274
|
+
api: api as Api,
|
|
275
|
+
provider: providerName,
|
|
276
|
+
baseUrl: providerConfig.baseUrl,
|
|
277
|
+
reasoning: modelDef.reasoning,
|
|
278
|
+
input: modelDef.input as ("text" | "image")[],
|
|
279
|
+
cost: modelDef.cost,
|
|
280
|
+
contextWindow: modelDef.contextWindow,
|
|
281
|
+
maxTokens: modelDef.maxTokens,
|
|
282
|
+
headers,
|
|
283
|
+
compat: modelDef.compat,
|
|
284
|
+
} as Model<Api>);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return models;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get all models (built-in + custom).
|
|
293
|
+
* If models.json had errors, returns only built-in models.
|
|
294
|
+
*/
|
|
295
|
+
getAll(): Model<Api>[] {
|
|
296
|
+
return this.models;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get only models that have valid API keys available.
|
|
301
|
+
*/
|
|
302
|
+
async getAvailable(): Promise<Model<Api>[]> {
|
|
303
|
+
const available: Model<Api>[] = [];
|
|
304
|
+
for (const model of this.models) {
|
|
305
|
+
const apiKey = await this.authStorage.getApiKey(model.provider);
|
|
306
|
+
if (apiKey) {
|
|
307
|
+
available.push(model);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return available;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Find a model by provider and ID.
|
|
315
|
+
*/
|
|
316
|
+
find(provider: string, modelId: string): Model<Api> | undefined {
|
|
317
|
+
return this.models.find((m) => m.provider === provider && m.id === modelId) ?? undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get API key for a model.
|
|
322
|
+
*/
|
|
323
|
+
async getApiKey(model: Model<Api>): Promise<string | undefined> {
|
|
324
|
+
return this.authStorage.getApiKey(model.provider);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Check if a model is using OAuth credentials (subscription).
|
|
329
|
+
*/
|
|
330
|
+
isUsingOAuth(model: Model<Api>): boolean {
|
|
331
|
+
const cred = this.authStorage.get(model.provider);
|
|
332
|
+
return cred?.type === "oauth";
|
|
333
|
+
}
|
|
334
|
+
}
|