@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11
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 +117 -0
- package/dist/cli.js +23087 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/async/job-manager.d.ts +18 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +1 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
- package/dist/types/cli/usage-cli.d.ts +72 -0
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/model-discovery.d.ts +55 -0
- package/dist/types/config/model-registry.d.ts +20 -219
- package/dist/types/config/model-resolver.d.ts +16 -10
- package/dist/types/config/model-roles.d.ts +28 -0
- package/dist/types/config/models-config-schema.d.ts +523 -42
- package/dist/types/config/models-config.d.ts +385 -0
- package/dist/types/config/settings-schema.d.ts +12 -16
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/debug/terminal-info.d.ts +0 -1
- package/dist/types/eval/backend.d.ts +0 -2
- package/dist/types/eval/idle-timeout.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -3
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/index.d.ts +2 -2
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +3 -0
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/mnemopi/state.d.ts +11 -1
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +31 -26
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +19 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/github-cache.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +3 -0
- package/dist/types/tools/todo.d.ts +2 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +1 -1
- package/examples/extensions/tools.ts +5 -4
- package/package.json +14 -11
- package/scripts/build-binary.ts +18 -23
- package/scripts/bundle-dist.ts +81 -0
- package/scripts/{dev-launch → omp} +1 -1
- package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
- package/src/async/job-manager.ts +57 -3
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/capability/fs.ts +10 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -3
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +1 -1
- package/src/cli/gallery-fixtures/types.ts +5 -1
- package/src/cli/list-models.ts +7 -12
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +69 -5
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commands/read.ts +6 -3
- package/src/commands/usage.ts +35 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/model-selection.ts +1 -1
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +308 -1025
- package/src/config/model-resolver.ts +113 -156
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +57 -8
- package/src/config/models-config.ts +129 -0
- package/src/config/settings-schema.ts +18 -14
- package/src/config/settings.ts +37 -1
- package/src/dap/client.ts +124 -37
- package/src/dap/session.ts +259 -158
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/raw-sse.ts +1 -1
- package/src/debug/terminal-info.ts +0 -3
- package/src/edit/diff.ts +95 -18
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +8 -2
- package/src/edit/index.ts +16 -1
- package/src/edit/modes/patch.ts +52 -0
- package/src/edit/modes/replace.ts +56 -22
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +36 -10
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/backend.ts +0 -2
- package/src/eval/completion-bridge.ts +2 -1
- package/src/eval/idle-timeout.ts +2 -9
- package/src/eval/js/context-manager.ts +6 -8
- package/src/eval/js/executor.ts +6 -2
- package/src/eval/js/index.ts +0 -2
- package/src/eval/js/shared/helpers.ts +5 -6
- package/src/eval/js/shared/local-module-loader.ts +1 -1
- package/src/eval/js/shared/prelude.txt +62 -1
- package/src/eval/js/shared/rewrite-imports.ts +49 -23
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +0 -2
- package/src/eval/py/kernel.ts +19 -0
- package/src/eval/py/runner.py +107 -3
- package/src/exec/bash-executor.ts +3 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/types.ts +3 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +6 -1
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/issue-pr-protocol.ts +12 -5
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/lib/xai-http.ts +1 -1
- package/src/lsp/client.ts +118 -38
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/index.ts +31 -22
- package/src/lsp/render.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/lsp/utils.ts +28 -10
- package/src/main.ts +165 -17
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +2 -1
- package/src/mnemopi/backend.ts +25 -3
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +19 -13
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/copy-selector.ts +1 -1
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +1 -1
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-selector.ts +66 -54
- package/src/modes/components/plan-review-overlay.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-selector.ts +5 -1
- package/src/modes/components/status-line/component.ts +1 -1
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +373 -141
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +17 -5
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +108 -26
- package/src/modes/controllers/command-controller.ts +10 -3
- package/src/modes/controllers/event-controller.ts +73 -49
- package/src/modes/controllers/input-controller.ts +5 -5
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -5
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/interactive-mode.ts +5 -19
- package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
- package/src/modes/setup-wizard/scenes/providers.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +1 -1
- package/src/modes/setup-wizard/scenes/types.ts +1 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +2 -1
- package/src/prompts/agents/explore.md +2 -2
- package/src/prompts/agents/librarian.md +1 -2
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +5 -5
- package/src/prompts/agents/task.md +5 -5
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/background-tan-dispatch.md +1 -1
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +2 -2
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/system/manual-continue.md +1 -1
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +9 -9
- package/src/prompts/system/plan-mode-active.md +4 -4
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +15 -26
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +8 -10
- package/src/prompts/tools/browser.md +7 -7
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +15 -15
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +3 -4
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task.md +2 -3
- package/src/prompts/tools/todo.md +6 -2
- package/src/sdk.ts +23 -10
- package/src/session/agent-session.ts +44 -10
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +23 -2
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/ssh/connection-manager.ts +27 -0
- package/src/task/commands.ts +2 -1
- package/src/task/discovery.ts +17 -24
- package/src/task/executor.ts +61 -53
- package/src/task/index.ts +137 -60
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +2 -2
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +2 -1
- package/src/tiny/title-client.ts +32 -14
- package/src/tools/archive-reader.ts +30 -2
- package/src/tools/ask.ts +104 -21
- package/src/tools/ast-edit.ts +25 -5
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +27 -7
- package/src/tools/bash.ts +54 -13
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +4 -1
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +55 -16
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +5 -5
- package/src/tools/eval.ts +0 -2
- package/src/tools/fetch.ts +33 -10
- package/src/tools/gh-cache-invalidation.ts +63 -8
- package/src/tools/gh-renderer.ts +1 -1
- package/src/tools/gh.ts +172 -29
- package/src/tools/github-cache.ts +70 -6
- package/src/tools/image-gen.ts +3 -9
- package/src/tools/irc.ts +5 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/read.ts +202 -61
- package/src/tools/render-utils.ts +3 -3
- package/src/tools/resolve.ts +1 -1
- package/src/tools/search.ts +92 -29
- package/src/tools/sqlite-reader.ts +17 -5
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +51 -12
- package/src/tools/write.ts +118 -18
- package/src/tui/output-block.ts +4 -4
- package/src/utils/changelog.ts +27 -1
- package/src/utils/file-mentions.ts +2 -1
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +6 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/providers/anthropic.ts +8 -2
- package/src/web/search/providers/codex.ts +2 -1
- package/src/web/search/providers/gemini.ts +2 -3
- package/src/web/search/render.ts +8 -6
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-id-affixes.d.ts +0 -12
- package/dist/types/config/model-provider-priority.d.ts +0 -1
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/src/config/model-equivalence.ts +0 -875
- package/src/config/model-id-affixes.ts +0 -81
- package/src/config/model-provider-priority.ts +0 -56
- package/src/exec/idle-timeout-watchdog.ts +0 -126
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP discovery protocols for configured and implicit providers — ollama,
|
|
3
|
+
* llama.cpp, lm-studio, openai-models-list, and new-api/one-api-style proxies.
|
|
4
|
+
* `ModelRegistry` owns the orchestration (status, state, caching) and calls
|
|
5
|
+
* `discoverModelsByProviderType` with a `DiscoveryContext`; built-in provider
|
|
6
|
+
* discovery lives in pi-catalog's provider-models.
|
|
7
|
+
*/
|
|
8
|
+
import type { FetchImpl } from "@oh-my-pi/pi-ai";
|
|
9
|
+
import type { Api, Model } from "@oh-my-pi/pi-ai/types";
|
|
10
|
+
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
11
|
+
import {
|
|
12
|
+
getBundledModelReferenceIndex,
|
|
13
|
+
resolveModelReference,
|
|
14
|
+
stripBracketedModelIdAffixes,
|
|
15
|
+
} from "@oh-my-pi/pi-catalog/identity";
|
|
16
|
+
import type { ModelSpec } from "@oh-my-pi/pi-catalog/types";
|
|
17
|
+
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
18
|
+
import type { ProviderDiscovery } from "./models-config-schema";
|
|
19
|
+
|
|
20
|
+
// Default cap on `max_tokens` for auto-discovered models that do not advertise
|
|
21
|
+
// their own output limit (OpenAI-models-list, Ollama, llama.cpp, new-api/
|
|
22
|
+
// one-api proxies). 32K matches the upper end of what mainstream
|
|
23
|
+
// OpenAI-compatible providers (DeepSeek, MiMo, OpenRouter, etc.) actually
|
|
24
|
+
// accept and keeps `min(contextWindow, …)` honoring smaller local windows.
|
|
25
|
+
// Conservative caps below this caused providers to drop the connection
|
|
26
|
+
// mid-stream when models hit the cap on legitimate large tool calls (see
|
|
27
|
+
// issue #1528: `write` payloads >~5KB on deepseek-v4-pro surfaced as
|
|
28
|
+
// "socket connection was closed unexpectedly").
|
|
29
|
+
export const DISCOVERY_DEFAULT_MAX_TOKENS = 32_768;
|
|
30
|
+
|
|
31
|
+
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
32
|
+
const OLLAMA_HOST_DEFAULT_PORT = "11434";
|
|
33
|
+
|
|
34
|
+
function normalizeOllamaHostEnv(value: string | undefined): string | undefined {
|
|
35
|
+
const trimmed = value?.trim();
|
|
36
|
+
if (!trimmed) return undefined;
|
|
37
|
+
const candidate = trimmed.includes("://")
|
|
38
|
+
? trimmed
|
|
39
|
+
: trimmed.startsWith("//")
|
|
40
|
+
? `http:${trimmed}`
|
|
41
|
+
: trimmed.startsWith(":")
|
|
42
|
+
? `http://127.0.0.1${trimmed}`
|
|
43
|
+
: `http://${trimmed}`;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(candidate);
|
|
46
|
+
if (!parsed.hostname || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (!parsed.port && parsed.protocol === "http:") {
|
|
50
|
+
parsed.port = OLLAMA_HOST_DEFAULT_PORT;
|
|
51
|
+
}
|
|
52
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
53
|
+
} catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getImplicitOllamaBaseUrl(): string {
|
|
59
|
+
const baseUrl = Bun.env.OLLAMA_BASE_URL?.trim();
|
|
60
|
+
return baseUrl || normalizeOllamaHostEnv(Bun.env.OLLAMA_HOST) || DEFAULT_OLLAMA_BASE_URL;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getOllamaContextLengthOverride(): number | undefined {
|
|
64
|
+
const value = Bun.env.OLLAMA_CONTEXT_LENGTH?.trim();
|
|
65
|
+
if (!value) return undefined;
|
|
66
|
+
const parsed = Number(value);
|
|
67
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Anthropic-safe variant of the discovery cap. The Anthropic stream converter
|
|
71
|
+
// in `packages/ai/src/providers/anthropic.ts` derives the request limit as
|
|
72
|
+
// `(model.maxTokens / 3) | 0`, so the 32K default would surface as 10,922
|
|
73
|
+
// requested output tokens — above the 8,192 hard cap on classic Claude 3.x
|
|
74
|
+
// Sonnet/Haiku/Opus endpoints. Discovered models routed through
|
|
75
|
+
// `anthropic-messages` (proxy `supported_endpoint_types: ["anthropic"]` or a
|
|
76
|
+
// custom provider with `api: anthropic-messages` + openai-models-list
|
|
77
|
+
// discovery) fall back to this conservative value.
|
|
78
|
+
const DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC = 8_192;
|
|
79
|
+
|
|
80
|
+
/** Routes discovered-model `maxTokens` defaults around Anthropic's 3× output divisor. */
|
|
81
|
+
export function discoveryDefaultMaxTokens(api: Api | undefined): number {
|
|
82
|
+
return api === "anthropic-messages" ? DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC : DISCOVERY_DEFAULT_MAX_TOKENS;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DiscoveryProviderConfig {
|
|
86
|
+
provider: string;
|
|
87
|
+
api: Api;
|
|
88
|
+
baseUrl?: string;
|
|
89
|
+
headers?: Record<string, string>;
|
|
90
|
+
compat?: ModelSpec<Api>["compat"];
|
|
91
|
+
discovery: ProviderDiscovery;
|
|
92
|
+
optional?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Registry-provided capabilities the protocol probes need; never the registry itself. */
|
|
96
|
+
export interface DiscoveryContext {
|
|
97
|
+
/** Injected fetch implementation (tests stub this). */
|
|
98
|
+
fetch: FetchImpl;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a provider's API key for `Authorization: Bearer …`. Returns
|
|
101
|
+
* undefined when no key is stored or it is a local/no-auth sentinel.
|
|
102
|
+
*/
|
|
103
|
+
getBearerApiKey(provider: string): Promise<string | undefined>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type OllamaDiscoveredModelMetadata = {
|
|
107
|
+
reasoning: boolean;
|
|
108
|
+
input: ("text" | "image")[];
|
|
109
|
+
contextWindow?: number;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type LlamaCppDiscoveredServerMetadata = {
|
|
113
|
+
contextWindow?: number;
|
|
114
|
+
input?: ("text" | "image")[];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function toPositiveNumberOrUndefined(value: unknown): number | undefined {
|
|
118
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
if (typeof value === "string" && value.trim()) {
|
|
122
|
+
const parsed = Number(value);
|
|
123
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
124
|
+
return parsed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractOllamaContextWindow(payload: Record<string, unknown>): number | undefined {
|
|
131
|
+
const modelInfo = payload.model_info;
|
|
132
|
+
if (isRecord(modelInfo)) {
|
|
133
|
+
for (const [key, value] of Object.entries(modelInfo)) {
|
|
134
|
+
if (key === "context_length" || key.endsWith(".context_length")) {
|
|
135
|
+
const contextWindow = toPositiveNumberOrUndefined(value);
|
|
136
|
+
if (contextWindow !== undefined) {
|
|
137
|
+
return contextWindow;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const parameters = payload.parameters;
|
|
144
|
+
if (typeof parameters !== "string") {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const match = parameters.match(/(?:^|\n)\s*num_ctx\s+(\d+)\s*(?:$|\n)/m);
|
|
148
|
+
return match ? toPositiveNumberOrUndefined(match[1]) : undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractLlamaCppContextWindow(payload: Record<string, unknown>): number | undefined {
|
|
152
|
+
const generationSettings = payload.default_generation_settings;
|
|
153
|
+
if (isRecord(generationSettings)) {
|
|
154
|
+
const contextWindow = toPositiveNumberOrUndefined(generationSettings.n_ctx);
|
|
155
|
+
if (contextWindow !== undefined) {
|
|
156
|
+
return contextWindow;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return toPositiveNumberOrUndefined(payload.n_ctx);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function extractLlamaCppInputCapabilities(payload: Record<string, unknown>): ("text" | "image")[] | undefined {
|
|
163
|
+
const modalities = payload.modalities;
|
|
164
|
+
if (!isRecord(modalities)) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return modalities.vision === true ? ["text", "image"] : ["text"];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function discoverModelsByProviderType(
|
|
171
|
+
providerConfig: DiscoveryProviderConfig,
|
|
172
|
+
ctx: DiscoveryContext,
|
|
173
|
+
): Promise<Model<Api>[]> {
|
|
174
|
+
switch (providerConfig.discovery.type) {
|
|
175
|
+
case "ollama":
|
|
176
|
+
return discoverOllamaModels(providerConfig, ctx);
|
|
177
|
+
case "llama.cpp":
|
|
178
|
+
return discoverLlamaCppModels(providerConfig, ctx);
|
|
179
|
+
case "lm-studio":
|
|
180
|
+
case "openai-models-list":
|
|
181
|
+
return discoverOpenAIModelsList(providerConfig, ctx);
|
|
182
|
+
case "proxy":
|
|
183
|
+
return discoverProxyModels(providerConfig, ctx);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function discoverOllamaModelMetadata(
|
|
188
|
+
ctx: DiscoveryContext,
|
|
189
|
+
endpoint: string,
|
|
190
|
+
modelId: string,
|
|
191
|
+
headers: Record<string, string> | undefined,
|
|
192
|
+
): Promise<OllamaDiscoveredModelMetadata | null> {
|
|
193
|
+
const showUrl = `${endpoint}/api/show`;
|
|
194
|
+
try {
|
|
195
|
+
const response = await ctx.fetch(showUrl, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { ...(headers ?? {}), "Content-Type": "application/json" },
|
|
198
|
+
body: JSON.stringify({ model: modelId }),
|
|
199
|
+
signal: AbortSignal.timeout(150),
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const payload = (await response.json()) as unknown;
|
|
205
|
+
if (!isRecord(payload)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const contextWindow = extractOllamaContextWindow(payload);
|
|
209
|
+
const capabilities = payload.capabilities;
|
|
210
|
+
if (Array.isArray(capabilities)) {
|
|
211
|
+
const normalized = new Set(
|
|
212
|
+
capabilities.flatMap(capability => (typeof capability === "string" ? [capability.toLowerCase()] : [])),
|
|
213
|
+
);
|
|
214
|
+
const supportsVision = normalized.has("vision") || normalized.has("image");
|
|
215
|
+
return {
|
|
216
|
+
reasoning: normalized.has("thinking"),
|
|
217
|
+
input: supportsVision ? ["text", "image"] : ["text"],
|
|
218
|
+
contextWindow,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (!isRecord(capabilities)) {
|
|
222
|
+
return {
|
|
223
|
+
reasoning: false,
|
|
224
|
+
input: ["text"],
|
|
225
|
+
contextWindow,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const supportsVision = capabilities.vision === true || capabilities.image === true;
|
|
229
|
+
return {
|
|
230
|
+
reasoning: capabilities.thinking === true,
|
|
231
|
+
input: supportsVision ? ["text", "image"] : ["text"],
|
|
232
|
+
contextWindow,
|
|
233
|
+
};
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function discoverOllamaModels(
|
|
240
|
+
providerConfig: DiscoveryProviderConfig,
|
|
241
|
+
ctx: DiscoveryContext,
|
|
242
|
+
): Promise<Model<Api>[]> {
|
|
243
|
+
const endpoint = normalizeOllamaBaseUrl(providerConfig.baseUrl);
|
|
244
|
+
const tagsUrl = `${endpoint}/api/tags`;
|
|
245
|
+
const headers = { ...(providerConfig.headers ?? {}) };
|
|
246
|
+
const response = await ctx.fetch(tagsUrl, {
|
|
247
|
+
headers,
|
|
248
|
+
signal: AbortSignal.timeout(250),
|
|
249
|
+
});
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
throw new Error(`HTTP ${response.status} from ${tagsUrl}`);
|
|
252
|
+
}
|
|
253
|
+
const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
|
|
254
|
+
const entries = (payload.models ?? []).flatMap(item => {
|
|
255
|
+
const id = item.model || item.name;
|
|
256
|
+
return id ? [{ id, name: item.name || id }] : [];
|
|
257
|
+
});
|
|
258
|
+
const metadataById = new Map(
|
|
259
|
+
await Promise.all(
|
|
260
|
+
entries.map(
|
|
261
|
+
async entry => [entry.id, await discoverOllamaModelMetadata(ctx, endpoint, entry.id, headers)] as const,
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
return entries.map(entry => {
|
|
266
|
+
const metadata = metadataById.get(entry.id);
|
|
267
|
+
return buildModel({
|
|
268
|
+
id: entry.id,
|
|
269
|
+
name: entry.name,
|
|
270
|
+
api: providerConfig.api,
|
|
271
|
+
provider: providerConfig.provider,
|
|
272
|
+
baseUrl: `${endpoint}/v1`,
|
|
273
|
+
reasoning: metadata?.reasoning ?? false,
|
|
274
|
+
input: metadata?.input ?? ["text"],
|
|
275
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
276
|
+
contextWindow: metadata?.contextWindow ?? 128000,
|
|
277
|
+
maxTokens: Math.min(metadata?.contextWindow ?? Number.POSITIVE_INFINITY, DISCOVERY_DEFAULT_MAX_TOKENS),
|
|
278
|
+
headers: providerConfig.headers,
|
|
279
|
+
} as ModelSpec<Api>);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function discoverLlamaCppServerMetadata(
|
|
284
|
+
ctx: DiscoveryContext,
|
|
285
|
+
baseUrl: string,
|
|
286
|
+
headers: Record<string, string> | undefined,
|
|
287
|
+
): Promise<LlamaCppDiscoveredServerMetadata | null> {
|
|
288
|
+
const propsUrl = `${toLlamaCppNativeBaseUrl(baseUrl)}/props`;
|
|
289
|
+
try {
|
|
290
|
+
const response = await ctx.fetch(propsUrl, {
|
|
291
|
+
headers,
|
|
292
|
+
signal: AbortSignal.timeout(150),
|
|
293
|
+
});
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const payload = (await response.json()) as unknown;
|
|
298
|
+
if (!isRecord(payload)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
contextWindow: extractLlamaCppContextWindow(payload),
|
|
303
|
+
input: extractLlamaCppInputCapabilities(payload),
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function discoverLlamaCppModels(
|
|
311
|
+
providerConfig: DiscoveryProviderConfig,
|
|
312
|
+
ctx: DiscoveryContext,
|
|
313
|
+
): Promise<Model<Api>[]> {
|
|
314
|
+
const baseUrl = normalizeLlamaCppBaseUrl(providerConfig.baseUrl);
|
|
315
|
+
const modelsUrl = `${baseUrl}/models`;
|
|
316
|
+
|
|
317
|
+
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
318
|
+
const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
|
|
319
|
+
if (apiKey) {
|
|
320
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const [response, serverMetadata] = await Promise.all([
|
|
324
|
+
ctx.fetch(modelsUrl, {
|
|
325
|
+
headers,
|
|
326
|
+
signal: AbortSignal.timeout(250),
|
|
327
|
+
}),
|
|
328
|
+
discoverLlamaCppServerMetadata(ctx, baseUrl, headers),
|
|
329
|
+
]);
|
|
330
|
+
if (!response.ok) {
|
|
331
|
+
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
332
|
+
}
|
|
333
|
+
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
334
|
+
const models = payload.data ?? [];
|
|
335
|
+
const discovered: Model<Api>[] = [];
|
|
336
|
+
for (const item of models) {
|
|
337
|
+
const id = item.id;
|
|
338
|
+
if (!id) continue;
|
|
339
|
+
discovered.push(
|
|
340
|
+
buildModel({
|
|
341
|
+
id,
|
|
342
|
+
name: id,
|
|
343
|
+
api: providerConfig.api,
|
|
344
|
+
provider: providerConfig.provider,
|
|
345
|
+
baseUrl,
|
|
346
|
+
reasoning: false,
|
|
347
|
+
input: serverMetadata?.input ?? ["text"],
|
|
348
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
349
|
+
contextWindow: serverMetadata?.contextWindow ?? 128000,
|
|
350
|
+
maxTokens: Math.min(
|
|
351
|
+
serverMetadata?.contextWindow ?? Number.POSITIVE_INFINITY,
|
|
352
|
+
DISCOVERY_DEFAULT_MAX_TOKENS,
|
|
353
|
+
),
|
|
354
|
+
headers,
|
|
355
|
+
compat: {
|
|
356
|
+
supportsStore: false,
|
|
357
|
+
supportsDeveloperRole: false,
|
|
358
|
+
supportsReasoningEffort: false,
|
|
359
|
+
},
|
|
360
|
+
} as ModelSpec<Api>),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return discovered;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export async function discoverOpenAIModelsList(
|
|
367
|
+
providerConfig: DiscoveryProviderConfig,
|
|
368
|
+
ctx: DiscoveryContext,
|
|
369
|
+
): Promise<Model<Api>[]> {
|
|
370
|
+
const baseUrl = normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
|
|
371
|
+
const modelsUrl = `${baseUrl}/models`;
|
|
372
|
+
|
|
373
|
+
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
374
|
+
const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
|
|
375
|
+
if (apiKey) {
|
|
376
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const response = await ctx.fetch(modelsUrl, {
|
|
380
|
+
headers,
|
|
381
|
+
signal: AbortSignal.timeout(10_000),
|
|
382
|
+
});
|
|
383
|
+
if (!response.ok) {
|
|
384
|
+
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
385
|
+
}
|
|
386
|
+
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
387
|
+
const models = payload.data ?? [];
|
|
388
|
+
const discovered: Model<Api>[] = [];
|
|
389
|
+
for (const item of models) {
|
|
390
|
+
const id = item.id;
|
|
391
|
+
if (!id) continue;
|
|
392
|
+
discovered.push(
|
|
393
|
+
buildModel({
|
|
394
|
+
id,
|
|
395
|
+
name: id,
|
|
396
|
+
api: providerConfig.api,
|
|
397
|
+
provider: providerConfig.provider,
|
|
398
|
+
baseUrl,
|
|
399
|
+
reasoning: false,
|
|
400
|
+
input: ["text"],
|
|
401
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
402
|
+
contextWindow: 128000,
|
|
403
|
+
maxTokens: discoveryDefaultMaxTokens(providerConfig.api),
|
|
404
|
+
headers,
|
|
405
|
+
compat: {
|
|
406
|
+
supportsStore: false,
|
|
407
|
+
supportsDeveloperRole: false,
|
|
408
|
+
supportsReasoningEffort: false,
|
|
409
|
+
},
|
|
410
|
+
} as ModelSpec<Api>),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return discovered;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Discover models from an Anthropic+OpenAI-compatible reseller proxy that
|
|
418
|
+
* exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
|
|
419
|
+
* model's wire capabilities through `supported_endpoint_types` on
|
|
420
|
+
* `GET /v1/models` (new-api / one-api-style proxies).
|
|
421
|
+
*
|
|
422
|
+
* Routing per model:
|
|
423
|
+
* supported_endpoint_types: ["anthropic", ...] -> api: "anthropic-messages"
|
|
424
|
+
* supported_endpoint_types: ["openai"] -> api: "openai-completions"
|
|
425
|
+
* missing / neither -> provider-level api fallback
|
|
426
|
+
*
|
|
427
|
+
* Anthropic models share the same baseUrl; the Anthropic SDK strips a
|
|
428
|
+
* trailing `/v1` itself before appending `/v1/messages`, so the discovery
|
|
429
|
+
* URL (which ends in `/v1`) round-trips correctly.
|
|
430
|
+
*/
|
|
431
|
+
export async function discoverProxyModels(
|
|
432
|
+
providerConfig: DiscoveryProviderConfig,
|
|
433
|
+
ctx: DiscoveryContext,
|
|
434
|
+
): Promise<Model<Api>[]> {
|
|
435
|
+
const baseUrl = normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
|
|
436
|
+
const modelsUrl = `${baseUrl}/models`;
|
|
437
|
+
|
|
438
|
+
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
439
|
+
const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
|
|
440
|
+
if (apiKey) {
|
|
441
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const response = await ctx.fetch(modelsUrl, {
|
|
445
|
+
headers,
|
|
446
|
+
signal: AbortSignal.timeout(10_000),
|
|
447
|
+
});
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
450
|
+
}
|
|
451
|
+
const payload = (await response.json()) as {
|
|
452
|
+
data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
|
|
453
|
+
};
|
|
454
|
+
const items = payload.data ?? [];
|
|
455
|
+
const discovered: Model<Api>[] = [];
|
|
456
|
+
for (const item of items) {
|
|
457
|
+
const id = item.id;
|
|
458
|
+
if (!id) continue;
|
|
459
|
+
const endpoints = item.supported_endpoint_types ?? [];
|
|
460
|
+
const api: Api | undefined = endpoints.includes("anthropic")
|
|
461
|
+
? "anthropic-messages"
|
|
462
|
+
: endpoints.includes("openai")
|
|
463
|
+
? "openai-completions"
|
|
464
|
+
: providerConfig.api;
|
|
465
|
+
if (!api) continue;
|
|
466
|
+
const isAnthropic = api === "anthropic-messages";
|
|
467
|
+
const reference = resolveModelReference(id, getBundledModelReferenceIndex());
|
|
468
|
+
const discoveryName = typeof item.name === "string" ? item.name.trim() : "";
|
|
469
|
+
const displayName =
|
|
470
|
+
reference?.name ??
|
|
471
|
+
(discoveryName && discoveryName !== id ? discoveryName : undefined) ??
|
|
472
|
+
stripBracketedModelIdAffixes(id) ??
|
|
473
|
+
id;
|
|
474
|
+
discovered.push(
|
|
475
|
+
buildModel({
|
|
476
|
+
id,
|
|
477
|
+
name: displayName,
|
|
478
|
+
api,
|
|
479
|
+
provider: providerConfig.provider,
|
|
480
|
+
baseUrl,
|
|
481
|
+
reasoning: reference?.reasoning ?? false,
|
|
482
|
+
thinking: reference?.thinking,
|
|
483
|
+
input: reference?.input ?? ["text"],
|
|
484
|
+
// Proxy pricing is provider-specific and usually does not match
|
|
485
|
+
// upstream bundled catalogs, so keep costs local-unknown even when
|
|
486
|
+
// we successfully recover the upstream model identity.
|
|
487
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
488
|
+
contextWindow: reference?.contextWindow ?? 128000,
|
|
489
|
+
maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
|
|
490
|
+
headers,
|
|
491
|
+
// OpenAI-compat fields are no-ops on anthropic models; the
|
|
492
|
+
// Anthropic SDK ignores them. Provider-level disableStrictTools
|
|
493
|
+
// flows in via #applyProviderCompat for the third-party-Anthropic
|
|
494
|
+
// path. Cross-wire bundled compat is intentionally not copied:
|
|
495
|
+
// request-shaping fields are provider-wire specific.
|
|
496
|
+
compat: isAnthropic
|
|
497
|
+
? undefined
|
|
498
|
+
: {
|
|
499
|
+
supportsStore: false,
|
|
500
|
+
supportsDeveloperRole: false,
|
|
501
|
+
supportsReasoningEffort: false,
|
|
502
|
+
},
|
|
503
|
+
} as ModelSpec<Api>),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return discovered;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
510
|
+
const defaultBaseUrl = "http://127.0.0.1:8080";
|
|
511
|
+
const raw = baseUrl || defaultBaseUrl;
|
|
512
|
+
try {
|
|
513
|
+
const parsed = new URL(raw);
|
|
514
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
515
|
+
return `${parsed.protocol}//${parsed.host}${trimmedPath}`;
|
|
516
|
+
} catch {
|
|
517
|
+
return raw;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function toLlamaCppNativeBaseUrl(baseUrl: string): string {
|
|
522
|
+
try {
|
|
523
|
+
const parsed = new URL(baseUrl);
|
|
524
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
525
|
+
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath.slice(0, -3) || "/" : trimmedPath || "/";
|
|
526
|
+
const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
527
|
+
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
528
|
+
} catch {
|
|
529
|
+
return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
|
|
534
|
+
const defaultBaseUrl = "http://127.0.0.1:1234/v1";
|
|
535
|
+
const raw = baseUrl || defaultBaseUrl;
|
|
536
|
+
try {
|
|
537
|
+
const parsed = new URL(raw);
|
|
538
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
|
|
539
|
+
parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
|
|
540
|
+
return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
541
|
+
} catch {
|
|
542
|
+
return raw;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function normalizeOllamaBaseUrl(baseUrl?: string): string {
|
|
547
|
+
const raw = baseUrl || DEFAULT_OLLAMA_BASE_URL;
|
|
548
|
+
try {
|
|
549
|
+
const parsed = new URL(raw);
|
|
550
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
551
|
+
} catch {
|
|
552
|
+
return DEFAULT_OLLAMA_BASE_URL;
|
|
553
|
+
}
|
|
554
|
+
}
|