@aryee337/aery-ai 0.2.28 → 0.2.29
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 +2914 -0
- package/README.md +614 -813
- package/package.json +140 -105
- package/src/api-registry.ts +96 -0
- package/src/auth-broker/client.ts +358 -0
- package/src/auth-broker/index.ts +5 -0
- package/src/auth-broker/refresher.ts +117 -0
- package/src/auth-broker/remote-store.ts +623 -0
- package/src/auth-broker/server.ts +644 -0
- package/src/auth-broker/types.ts +127 -0
- package/src/auth-broker/wire-schemas.ts +200 -0
- package/src/auth-gateway/http.ts +194 -0
- package/src/auth-gateway/index.ts +3 -0
- package/src/auth-gateway/server.ts +818 -0
- package/src/auth-gateway/types.ts +143 -0
- package/src/auth-storage.ts +4422 -0
- package/src/index.ts +54 -0
- package/src/model-cache.ts +129 -0
- package/src/model-manager.ts +469 -0
- package/src/model-thinking.ts +782 -0
- package/src/models.json +83530 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +90 -0
- package/src/provider-models/bundled-references.ts +38 -0
- package/src/provider-models/descriptors.ts +355 -0
- package/src/provider-models/google.ts +88 -0
- package/src/provider-models/index.ts +5 -0
- package/src/provider-models/ollama.ts +153 -0
- package/src/provider-models/openai-compat.ts +2817 -0
- package/src/provider-models/special.ts +67 -0
- package/src/providers/aery-native-client.ts +228 -0
- package/src/providers/aery-native-server.ts +212 -0
- package/src/providers/amazon-bedrock.ts +873 -0
- package/src/providers/anthropic-client.ts +318 -0
- package/src/providers/anthropic-messages-server-schema.ts +243 -0
- package/src/providers/anthropic-messages-server.ts +683 -0
- package/src/providers/anthropic-wire.ts +268 -0
- package/src/providers/anthropic.ts +3094 -0
- package/src/providers/aws-credentials.ts +501 -0
- package/src/providers/aws-eventstream.ts +185 -0
- package/src/providers/aws-sigv4.ts +218 -0
- package/src/providers/azure-openai-responses.ts +361 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2621 -0
- package/src/providers/error-message.ts +21 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +372 -0
- package/src/providers/google-auth.ts +252 -0
- package/src/providers/google-gemini-cli.ts +809 -0
- package/src/providers/google-gemini-headers.ts +41 -0
- package/src/providers/google-shared.ts +917 -0
- package/src/providers/google-types.ts +167 -0
- package/src/providers/google-vertex.ts +91 -0
- package/src/providers/google.ts +41 -0
- package/src/providers/grammar.ts +70 -0
- package/src/providers/kimi.ts +52 -0
- package/src/providers/mock.ts +496 -0
- package/src/providers/ollama.ts +644 -0
- package/src/providers/openai-anthropic-shim.ts +138 -0
- package/src/providers/openai-chat-server-schema.ts +252 -0
- package/src/providers/openai-chat-server.ts +647 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +161 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +3018 -0
- package/src/providers/openai-completions-compat.ts +300 -0
- package/src/providers/openai-completions.ts +1979 -0
- package/src/providers/openai-responses-server-schema.ts +290 -0
- package/src/providers/openai-responses-server.ts +1183 -0
- package/src/providers/openai-responses-shared.ts +873 -0
- package/src/providers/openai-responses.ts +679 -0
- package/src/providers/register-builtins.ts +436 -0
- package/src/providers/synthetic.ts +50 -0
- package/src/providers/transform-messages.ts +382 -0
- package/src/providers/vision-guard.ts +31 -0
- package/src/providers/xai-responses.ts +82 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +1065 -0
- package/src/types.ts +944 -0
- package/src/usage/claude.ts +482 -0
- package/src/usage/gemini.ts +250 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +201 -0
- package/src/usage/kimi.ts +271 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +503 -0
- package/src/usage/shared.ts +10 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +185 -0
- package/src/utils/abort.ts +51 -0
- package/src/utils/abortable-iterator.ts +69 -0
- package/src/utils/anthropic-auth.ts +93 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +4 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +142 -0
- package/src/utils/fireworks-model-id.ts +30 -0
- package/src/utils/foundry.ts +8 -0
- package/src/utils/http-inspector.ts +176 -0
- package/src/utils/idle-iterator.ts +267 -0
- package/src/utils/json-parse.ts +182 -0
- package/src/utils/oauth/__tests__/xai-oauth.test.ts +107 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +273 -0
- package/src/utils/oauth/api-key-login.ts +87 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +16 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/deepseek.ts +53 -0
- package/src/utils/oauth/firepass.ts +24 -0
- package/src/utils/oauth/fireworks.ts +15 -0
- package/src/utils/oauth/github-copilot.ts +362 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +200 -0
- package/src/utils/oauth/google-gemini-cli.ts +256 -0
- package/src/utils/oauth/google-oauth-shared.ts +110 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +484 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +254 -0
- package/src/utils/oauth/litellm.ts +47 -0
- package/src/utils/oauth/lm-studio.ts +38 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +23 -0
- package/src/utils/oauth/nanogpt.ts +15 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +203 -0
- package/src/utils/oauth/ollama-cloud.ts +28 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +299 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/openrouter.ts +20 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +206 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +15 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +16 -0
- package/src/utils/oauth/types.ts +99 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/wafer.ts +50 -0
- package/src/utils/oauth/xai-oauth.ts +342 -0
- package/src/utils/oauth/xiaomi.ts +139 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +15 -0
- package/src/utils/oauth/zhipu.ts +60 -0
- package/src/utils/overflow.ts +137 -0
- package/src/utils/parse-bind.ts +54 -0
- package/src/utils/provider-response.ts +30 -0
- package/src/utils/request-debug.ts +336 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +54 -0
- package/src/utils/schema/CONSTRAINTS.md +164 -0
- package/src/utils/schema/adapt.ts +36 -0
- package/src/utils/schema/compatibility.ts +435 -0
- package/src/utils/schema/dereference.ts +98 -0
- package/src/utils/schema/draft.ts +341 -0
- package/src/utils/schema/equality.ts +97 -0
- package/src/utils/schema/fields.ts +191 -0
- package/src/utils/schema/index.ts +13 -0
- package/src/utils/schema/json-schema-validator.ts +577 -0
- package/src/utils/schema/meta-validator.ts +167 -0
- package/src/utils/schema/normalize.ts +1588 -0
- package/src/utils/schema/spill.ts +43 -0
- package/src/utils/schema/stamps.ts +97 -0
- package/src/utils/schema/types.ts +10 -0
- package/src/utils/schema/wire.ts +293 -0
- package/src/utils/schema/zod-decontaminate.ts +331 -0
- package/src/utils/sdk-stream-timeout.ts +43 -0
- package/src/utils/sse-debug.ts +289 -0
- package/src/utils/stream-markup-healing.ts +612 -0
- package/src/utils/tool-choice.ts +99 -0
- package/src/utils/validation.ts +1024 -0
- package/src/utils.ts +166 -0
- package/dist/api-registry.d.ts +0 -20
- package/dist/api-registry.d.ts.map +0 -1
- package/dist/api-registry.js +0 -44
- package/dist/api-registry.js.map +0 -1
- package/dist/bedrock-provider.d.ts +0 -5
- package/dist/bedrock-provider.d.ts.map +0 -1
- package/dist/bedrock-provider.js +0 -6
- package/dist/bedrock-provider.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -130
- package/dist/cli.js.map +0 -1
- package/dist/env-api-keys.d.ts +0 -18
- package/dist/env-api-keys.d.ts.map +0 -1
- package/dist/env-api-keys.js +0 -178
- package/dist/env-api-keys.js.map +0 -1
- package/dist/image-models.d.ts +0 -10
- package/dist/image-models.d.ts.map +0 -1
- package/dist/image-models.generated.d.ts +0 -440
- package/dist/image-models.generated.d.ts.map +0 -1
- package/dist/image-models.generated.js +0 -442
- package/dist/image-models.generated.js.map +0 -1
- package/dist/image-models.js +0 -23
- package/dist/image-models.js.map +0 -1
- package/dist/images-api-registry.d.ts +0 -14
- package/dist/images-api-registry.d.ts.map +0 -1
- package/dist/images-api-registry.js +0 -22
- package/dist/images-api-registry.js.map +0 -1
- package/dist/images.d.ts +0 -4
- package/dist/images.d.ts.map +0 -1
- package/dist/images.js +0 -14
- package/dist/images.js.map +0 -1
- package/dist/index.d.ts +0 -32
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/models.d.ts +0 -18
- package/dist/models.d.ts.map +0 -1
- package/dist/models.generated.d.ts +0 -17707
- package/dist/models.generated.d.ts.map +0 -1
- package/dist/models.generated.js +0 -16561
- package/dist/models.generated.js.map +0 -1
- package/dist/models.js +0 -71
- package/dist/models.js.map +0 -1
- package/dist/oauth.d.ts +0 -2
- package/dist/oauth.d.ts.map +0 -1
- package/dist/oauth.js +0 -2
- package/dist/oauth.js.map +0 -1
- package/dist/providers/aery-error-formatting.d.ts +0 -13
- package/dist/providers/aery-error-formatting.d.ts.map +0 -1
- package/dist/providers/aery-error-formatting.js +0 -112
- package/dist/providers/aery-error-formatting.js.map +0 -1
- package/dist/providers/amazon-bedrock.d.ts +0 -38
- package/dist/providers/amazon-bedrock.d.ts.map +0 -1
- package/dist/providers/amazon-bedrock.js +0 -763
- package/dist/providers/amazon-bedrock.js.map +0 -1
- package/dist/providers/anthropic.d.ts +0 -71
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js +0 -949
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/azure-openai-responses.d.ts +0 -15
- package/dist/providers/azure-openai-responses.d.ts.map +0 -1
- package/dist/providers/azure-openai-responses.js +0 -225
- package/dist/providers/azure-openai-responses.js.map +0 -1
- package/dist/providers/cloudflare.d.ts +0 -13
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js +0 -26
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/faux.d.ts +0 -56
- package/dist/providers/faux.d.ts.map +0 -1
- package/dist/providers/faux.js +0 -368
- package/dist/providers/faux.js.map +0 -1
- package/dist/providers/github-copilot-headers.d.ts +0 -8
- package/dist/providers/github-copilot-headers.d.ts.map +0 -1
- package/dist/providers/github-copilot-headers.js +0 -29
- package/dist/providers/github-copilot-headers.js.map +0 -1
- package/dist/providers/google-gemini-cli.d.ts +0 -74
- package/dist/providers/google-gemini-cli.d.ts.map +0 -1
- package/dist/providers/google-gemini-cli.js +0 -779
- package/dist/providers/google-gemini-cli.js.map +0 -1
- package/dist/providers/google-shared.d.ts +0 -70
- package/dist/providers/google-shared.d.ts.map +0 -1
- package/dist/providers/google-shared.js +0 -329
- package/dist/providers/google-shared.js.map +0 -1
- package/dist/providers/google-vertex.d.ts +0 -15
- package/dist/providers/google-vertex.d.ts.map +0 -1
- package/dist/providers/google-vertex.js +0 -442
- package/dist/providers/google-vertex.js.map +0 -1
- package/dist/providers/google.d.ts +0 -13
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js +0 -400
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/images/openrouter.d.ts +0 -3
- package/dist/providers/images/openrouter.d.ts.map +0 -1
- package/dist/providers/images/openrouter.js +0 -129
- package/dist/providers/images/openrouter.js.map +0 -1
- package/dist/providers/images/register-builtins.d.ts +0 -4
- package/dist/providers/images/register-builtins.d.ts.map +0 -1
- package/dist/providers/images/register-builtins.js +0 -34
- package/dist/providers/images/register-builtins.js.map +0 -1
- package/dist/providers/mistral.d.ts +0 -25
- package/dist/providers/mistral.d.ts.map +0 -1
- package/dist/providers/mistral.js +0 -535
- package/dist/providers/mistral.js.map +0 -1
- package/dist/providers/openai-codex-responses.d.ts +0 -30
- package/dist/providers/openai-codex-responses.d.ts.map +0 -1
- package/dist/providers/openai-codex-responses.js +0 -1090
- package/dist/providers/openai-codex-responses.js.map +0 -1
- package/dist/providers/openai-completions.d.ts +0 -19
- package/dist/providers/openai-completions.d.ts.map +0 -1
- package/dist/providers/openai-completions.js +0 -950
- package/dist/providers/openai-completions.js.map +0 -1
- package/dist/providers/openai-prompt-cache.d.ts +0 -3
- package/dist/providers/openai-prompt-cache.d.ts.map +0 -1
- package/dist/providers/openai-prompt-cache.js +0 -10
- package/dist/providers/openai-prompt-cache.js.map +0 -1
- package/dist/providers/openai-responses-shared.d.ts +0 -18
- package/dist/providers/openai-responses-shared.d.ts.map +0 -1
- package/dist/providers/openai-responses-shared.js +0 -492
- package/dist/providers/openai-responses-shared.js.map +0 -1
- package/dist/providers/openai-responses.d.ts +0 -13
- package/dist/providers/openai-responses.d.ts.map +0 -1
- package/dist/providers/openai-responses.js +0 -237
- package/dist/providers/openai-responses.js.map +0 -1
- package/dist/providers/register-builtins.d.ts +0 -38
- package/dist/providers/register-builtins.d.ts.map +0 -1
- package/dist/providers/register-builtins.js +0 -278
- package/dist/providers/register-builtins.js.map +0 -1
- package/dist/providers/simple-options.d.ts +0 -8
- package/dist/providers/simple-options.d.ts.map +0 -1
- package/dist/providers/simple-options.js +0 -41
- package/dist/providers/simple-options.js.map +0 -1
- package/dist/providers/transform-messages.d.ts +0 -8
- package/dist/providers/transform-messages.d.ts.map +0 -1
- package/dist/providers/transform-messages.js +0 -184
- package/dist/providers/transform-messages.js.map +0 -1
- package/dist/session-resources.d.ts +0 -4
- package/dist/session-resources.d.ts.map +0 -1
- package/dist/session-resources.js +0 -22
- package/dist/session-resources.js.map +0 -1
- package/dist/stream.d.ts +0 -8
- package/dist/stream.d.ts.map +0 -1
- package/dist/stream.js +0 -27
- package/dist/stream.js.map +0 -1
- package/dist/types.d.ts +0 -498
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/diagnostics.d.ts +0 -19
- package/dist/utils/diagnostics.d.ts.map +0 -1
- package/dist/utils/diagnostics.js +0 -25
- package/dist/utils/diagnostics.js.map +0 -1
- package/dist/utils/event-stream.d.ts +0 -21
- package/dist/utils/event-stream.d.ts.map +0 -1
- package/dist/utils/event-stream.js +0 -81
- package/dist/utils/event-stream.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -3
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -14
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/headers.d.ts +0 -2
- package/dist/utils/headers.d.ts.map +0 -1
- package/dist/utils/headers.js +0 -8
- package/dist/utils/headers.js.map +0 -1
- package/dist/utils/json-parse.d.ts +0 -16
- package/dist/utils/json-parse.d.ts.map +0 -1
- package/dist/utils/json-parse.js +0 -113
- package/dist/utils/json-parse.js.map +0 -1
- package/dist/utils/node-http-proxy.d.ts +0 -10
- package/dist/utils/node-http-proxy.d.ts.map +0 -1
- package/dist/utils/node-http-proxy.js +0 -97
- package/dist/utils/node-http-proxy.js.map +0 -1
- package/dist/utils/oauth/anthropic.d.ts +0 -25
- package/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/dist/utils/oauth/anthropic.js +0 -335
- package/dist/utils/oauth/anthropic.js.map +0 -1
- package/dist/utils/oauth/device-code.d.ts +0 -19
- package/dist/utils/oauth/device-code.d.ts.map +0 -1
- package/dist/utils/oauth/device-code.js +0 -55
- package/dist/utils/oauth/device-code.js.map +0 -1
- package/dist/utils/oauth/github-copilot.d.ts +0 -30
- package/dist/utils/oauth/github-copilot.d.ts.map +0 -1
- package/dist/utils/oauth/github-copilot.js +0 -268
- package/dist/utils/oauth/github-copilot.js.map +0 -1
- package/dist/utils/oauth/google-antigravity.d.ts +0 -26
- package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
- package/dist/utils/oauth/google-antigravity.js +0 -377
- package/dist/utils/oauth/google-antigravity.js.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
- package/dist/utils/oauth/google-gemini-cli.js +0 -482
- package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
- package/dist/utils/oauth/index.d.ts +0 -63
- package/dist/utils/oauth/index.d.ts.map +0 -1
- package/dist/utils/oauth/index.js +0 -131
- package/dist/utils/oauth/index.js.map +0 -1
- package/dist/utils/oauth/oauth-page.d.ts +0 -3
- package/dist/utils/oauth/oauth-page.d.ts.map +0 -1
- package/dist/utils/oauth/oauth-page.js +0 -105
- package/dist/utils/oauth/oauth-page.js.map +0 -1
- package/dist/utils/oauth/openai-codex.d.ts +0 -34
- package/dist/utils/oauth/openai-codex.d.ts.map +0 -1
- package/dist/utils/oauth/openai-codex.js +0 -385
- package/dist/utils/oauth/openai-codex.js.map +0 -1
- package/dist/utils/oauth/pkce.d.ts +0 -13
- package/dist/utils/oauth/pkce.d.ts.map +0 -1
- package/dist/utils/oauth/pkce.js +0 -31
- package/dist/utils/oauth/pkce.js.map +0 -1
- package/dist/utils/oauth/types.d.ts +0 -64
- package/dist/utils/oauth/types.d.ts.map +0 -1
- package/dist/utils/oauth/types.js +0 -2
- package/dist/utils/oauth/types.js.map +0 -1
- package/dist/utils/overflow.d.ts +0 -56
- package/dist/utils/overflow.d.ts.map +0 -1
- package/dist/utils/overflow.js +0 -151
- package/dist/utils/overflow.js.map +0 -1
- package/dist/utils/sanitize-unicode.d.ts +0 -22
- package/dist/utils/sanitize-unicode.d.ts.map +0 -1
- package/dist/utils/sanitize-unicode.js +0 -26
- package/dist/utils/sanitize-unicode.js.map +0 -1
- package/dist/utils/typebox-helpers.d.ts +0 -17
- package/dist/utils/typebox-helpers.d.ts.map +0 -1
- package/dist/utils/typebox-helpers.js +0 -21
- package/dist/utils/typebox-helpers.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -18
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -281
- package/dist/utils/validation.js.map +0 -1
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export { type ZodType, z } from "zod/v4";
|
|
2
|
+
export * from "./api-registry";
|
|
3
|
+
export * from "./auth-broker";
|
|
4
|
+
export { type AuthGatewayBootOptions, type ModelResolver, startAuthGateway } from "./auth-gateway/server";
|
|
5
|
+
export * from "./auth-gateway/types";
|
|
6
|
+
export * from "./auth-storage";
|
|
7
|
+
export * from "./model-cache";
|
|
8
|
+
export * from "./model-manager";
|
|
9
|
+
export * from "./model-thinking";
|
|
10
|
+
export * from "./models";
|
|
11
|
+
export * from "./provider-details";
|
|
12
|
+
export * from "./provider-models";
|
|
13
|
+
export * from "./providers/anthropic";
|
|
14
|
+
export * from "./providers/anthropic-client";
|
|
15
|
+
export * from "./providers/azure-openai-responses";
|
|
16
|
+
export type * from "./providers/cursor";
|
|
17
|
+
export * from "./providers/gitlab-duo";
|
|
18
|
+
export type * from "./providers/google";
|
|
19
|
+
export type * from "./providers/google-gemini-cli";
|
|
20
|
+
export * from "./providers/google-gemini-headers";
|
|
21
|
+
export type * from "./providers/google-vertex";
|
|
22
|
+
export * from "./providers/kimi";
|
|
23
|
+
export * from "./providers/mock";
|
|
24
|
+
export * from "./providers/ollama";
|
|
25
|
+
export * from "./providers/openai-codex-responses";
|
|
26
|
+
export * from "./providers/openai-completions";
|
|
27
|
+
export * from "./providers/openai-responses";
|
|
28
|
+
export * from "./providers/synthetic";
|
|
29
|
+
export * from "./rate-limit-utils";
|
|
30
|
+
export * from "./stream";
|
|
31
|
+
export * from "./types";
|
|
32
|
+
export * from "./usage";
|
|
33
|
+
export * from "./usage/claude";
|
|
34
|
+
export * from "./usage/gemini";
|
|
35
|
+
export * from "./usage/github-copilot";
|
|
36
|
+
export * from "./usage/google-antigravity";
|
|
37
|
+
export * from "./usage/kimi";
|
|
38
|
+
export * from "./usage/minimax-code";
|
|
39
|
+
export * from "./usage/openai-codex";
|
|
40
|
+
export * from "./usage/zai";
|
|
41
|
+
export * from "./utils/anthropic-auth";
|
|
42
|
+
export * from "./utils/discovery";
|
|
43
|
+
export * from "./utils/event-stream";
|
|
44
|
+
export * from "./utils/oauth";
|
|
45
|
+
export type {
|
|
46
|
+
OAuthCredentials,
|
|
47
|
+
OAuthProvider,
|
|
48
|
+
OAuthProviderId,
|
|
49
|
+
OAuthProviderInfo,
|
|
50
|
+
} from "./utils/oauth/types";
|
|
51
|
+
export * from "./utils/overflow";
|
|
52
|
+
export * from "./utils/retry";
|
|
53
|
+
export * from "./utils/schema";
|
|
54
|
+
export * from "./utils/validation";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed model cache for atomic cross-process access.
|
|
3
|
+
* Replaces per-provider JSON files with a single cache.db.
|
|
4
|
+
*/
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import { getModelDbPath } from "@aryee337/aery-utils";
|
|
7
|
+
import type { Api, Model } from "./types";
|
|
8
|
+
|
|
9
|
+
const CACHE_SCHEMA_VERSION = 3;
|
|
10
|
+
|
|
11
|
+
interface CacheRow {
|
|
12
|
+
provider_id: string;
|
|
13
|
+
version: number;
|
|
14
|
+
updated_at: number;
|
|
15
|
+
authoritative: number;
|
|
16
|
+
static_fingerprint: string;
|
|
17
|
+
models: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TableInfoRow {
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CacheEntry<TApi extends Api = Api> {
|
|
25
|
+
models: Model<TApi>[];
|
|
26
|
+
fresh: boolean;
|
|
27
|
+
authoritative: boolean;
|
|
28
|
+
updatedAt: number;
|
|
29
|
+
/**
|
|
30
|
+
* Hash of the static catalog slice that was merged into `models` when this
|
|
31
|
+
* row was written. `resolveProviderModels` compares against the current
|
|
32
|
+
* static fingerprint and bypasses the static+cache re-merge when they
|
|
33
|
+
* match — the cache already incorporates the same static state.
|
|
34
|
+
*/
|
|
35
|
+
staticFingerprint: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let sharedDb: Database | null = null;
|
|
39
|
+
let sharedDbPath: string | null = null;
|
|
40
|
+
|
|
41
|
+
function getDb(dbPath?: string): Database {
|
|
42
|
+
const resolvedPath = dbPath ?? getModelDbPath();
|
|
43
|
+
if (sharedDb && sharedDbPath === resolvedPath) {
|
|
44
|
+
return sharedDb;
|
|
45
|
+
}
|
|
46
|
+
if (sharedDb) {
|
|
47
|
+
sharedDb.close();
|
|
48
|
+
}
|
|
49
|
+
const db = new Database(resolvedPath, { create: true });
|
|
50
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
51
|
+
db.run("PRAGMA busy_timeout = 3000");
|
|
52
|
+
db.run(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS model_cache (
|
|
54
|
+
provider_id TEXT PRIMARY KEY,
|
|
55
|
+
version INTEGER NOT NULL,
|
|
56
|
+
updated_at INTEGER NOT NULL,
|
|
57
|
+
authoritative INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
static_fingerprint TEXT NOT NULL DEFAULT '',
|
|
59
|
+
models TEXT NOT NULL
|
|
60
|
+
)
|
|
61
|
+
`);
|
|
62
|
+
migrateCacheSchema(db);
|
|
63
|
+
|
|
64
|
+
sharedDb = db;
|
|
65
|
+
sharedDbPath = resolvedPath;
|
|
66
|
+
return db;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function migrateCacheSchema(db: Database): void {
|
|
70
|
+
const columns = db.prepare("PRAGMA table_info(model_cache)").all() as TableInfoRow[];
|
|
71
|
+
if (!columns.some(column => column.name === "static_fingerprint")) {
|
|
72
|
+
db.run("ALTER TABLE model_cache ADD COLUMN static_fingerprint TEXT NOT NULL DEFAULT ''");
|
|
73
|
+
}
|
|
74
|
+
db.run("UPDATE model_cache SET version = ? WHERE version = 2", [CACHE_SCHEMA_VERSION]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function readModelCache<TApi extends Api>(
|
|
78
|
+
providerId: string,
|
|
79
|
+
ttlMs: number,
|
|
80
|
+
now: () => number,
|
|
81
|
+
dbPath?: string,
|
|
82
|
+
): CacheEntry<TApi> | null {
|
|
83
|
+
try {
|
|
84
|
+
const db = getDb(dbPath);
|
|
85
|
+
const row = db.query<CacheRow, [string]>("SELECT * FROM model_cache WHERE provider_id = ?").get(providerId);
|
|
86
|
+
if (!row || row.version !== CACHE_SCHEMA_VERSION) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const models = JSON.parse(row.models) as Model<TApi>[];
|
|
90
|
+
const ageMs = now() - row.updated_at;
|
|
91
|
+
const fresh = Number.isFinite(ageMs) && ageMs >= 0 && ageMs <= ttlMs;
|
|
92
|
+
return {
|
|
93
|
+
models,
|
|
94
|
+
fresh,
|
|
95
|
+
authoritative: row.authoritative === 1,
|
|
96
|
+
updatedAt: row.updated_at,
|
|
97
|
+
staticFingerprint: row.static_fingerprint ?? "",
|
|
98
|
+
};
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function writeModelCache<TApi extends Api>(
|
|
105
|
+
providerId: string,
|
|
106
|
+
updatedAt: number,
|
|
107
|
+
models: Model<TApi>[],
|
|
108
|
+
authoritative: boolean,
|
|
109
|
+
staticFingerprint: string,
|
|
110
|
+
dbPath?: string,
|
|
111
|
+
): void {
|
|
112
|
+
try {
|
|
113
|
+
const db = getDb(dbPath);
|
|
114
|
+
db.run(
|
|
115
|
+
`INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, models)
|
|
116
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
117
|
+
[
|
|
118
|
+
providerId,
|
|
119
|
+
CACHE_SCHEMA_VERSION,
|
|
120
|
+
updatedAt,
|
|
121
|
+
authoritative ? 1 : 0,
|
|
122
|
+
staticFingerprint,
|
|
123
|
+
JSON.stringify(models),
|
|
124
|
+
],
|
|
125
|
+
);
|
|
126
|
+
} catch {
|
|
127
|
+
// Cache writes are best-effort; failures should not break model resolution.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { readModelCache, writeModelCache } from "./model-cache";
|
|
2
|
+
import { enrichModelThinking } from "./model-thinking";
|
|
3
|
+
import { type GeneratedProvider, getBundledModels } from "./models";
|
|
4
|
+
import type { Api, Model, Provider } from "./types";
|
|
5
|
+
import { isRecord } from "./utils";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CACHE_TTL_MS = 2 * 60 * 60 * 1000;
|
|
8
|
+
const NON_AUTHORITATIVE_RETRY_MS = 5 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Controls when dynamic endpoint models should be fetched.
|
|
12
|
+
*/
|
|
13
|
+
export type ModelRefreshStrategy = "online" | "offline" | "online-if-uncached";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook for loading and mapping models.dev fallback data into canonical model objects.
|
|
17
|
+
*/
|
|
18
|
+
export interface ModelsDevFallback<TApi extends Api = Api, TPayload = unknown> {
|
|
19
|
+
/** Fetches raw fallback payload (for example from models.dev). */
|
|
20
|
+
fetch(): Promise<TPayload>;
|
|
21
|
+
/** Maps payload into provider models. */
|
|
22
|
+
map(payload: TPayload, providerId: Provider): readonly Model<TApi>[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for provider model resolution.
|
|
27
|
+
*/
|
|
28
|
+
export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload = unknown> {
|
|
29
|
+
/** Provider id used for static lookup and cache namespacing. */
|
|
30
|
+
providerId: Provider;
|
|
31
|
+
/** Optional static list override. When omitted, bundled models.json is used. */
|
|
32
|
+
staticModels?: readonly Model<TApi>[];
|
|
33
|
+
/** Optional override for the cache database path. Default: <agent-dir>/models.db. */
|
|
34
|
+
cacheDbPath?: string;
|
|
35
|
+
/** Maximum cache age in milliseconds before considered stale. Default: 24h. */
|
|
36
|
+
cacheTtlMs?: number;
|
|
37
|
+
/** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */
|
|
38
|
+
dynamicModelsAuthoritative?: boolean;
|
|
39
|
+
/** Optional dynamic endpoint fetcher. */
|
|
40
|
+
fetchDynamicModels?: () => Promise<readonly Model<TApi>[] | null>;
|
|
41
|
+
/** Optional models.dev fallback hook. */
|
|
42
|
+
modelsDev?: ModelsDevFallback<TApi, TModelsDevPayload>;
|
|
43
|
+
/** Clock override for deterministic tests. */
|
|
44
|
+
now?: () => number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolution result.
|
|
49
|
+
*
|
|
50
|
+
* `stale` is false when the resolved catalog is authoritative for the selected provider:
|
|
51
|
+
* - dynamic endpoint data was fetched in this call,
|
|
52
|
+
* - a still-fresh authoritative cache was reused in `online-if-uncached` mode, or
|
|
53
|
+
* - the provider has no dynamic fetcher configured.
|
|
54
|
+
*/
|
|
55
|
+
export interface ModelResolutionResult<TApi extends Api = Api> {
|
|
56
|
+
models: Model<TApi>[];
|
|
57
|
+
stale: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stateful facade over provider model resolution.
|
|
62
|
+
*/
|
|
63
|
+
export interface ModelManager<TApi extends Api = Api> {
|
|
64
|
+
refresh(strategy?: ModelRefreshStrategy): Promise<ModelResolutionResult<TApi>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a reusable provider model manager.
|
|
69
|
+
*/
|
|
70
|
+
export function createModelManager<TApi extends Api = Api, TModelsDevPayload = unknown>(
|
|
71
|
+
options: ModelManagerOptions<TApi, TModelsDevPayload>,
|
|
72
|
+
): ModelManager<TApi> {
|
|
73
|
+
return {
|
|
74
|
+
refresh(strategy: ModelRefreshStrategy = "online-if-uncached") {
|
|
75
|
+
return resolveProviderModels(options, strategy);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cheap fast path for trusted model sources (bundled literals, our own cache rows).
|
|
82
|
+
* Skips per-field validation; only guards against catastrophically corrupt rows.
|
|
83
|
+
*/
|
|
84
|
+
function passModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
|
|
85
|
+
if (!Array.isArray(value)) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const out: Model<TApi>[] = [];
|
|
89
|
+
for (const item of value) {
|
|
90
|
+
if (item === null || typeof item !== "object" || typeof (item as { id: unknown }).id !== "string") {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
out.push(enrichModelThinking(item as Model<TApi>));
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolves provider models with source precedence:
|
|
100
|
+
* static -> models.dev -> cache -> dynamic.
|
|
101
|
+
*
|
|
102
|
+
* Later sources override earlier ones by model id.
|
|
103
|
+
*/
|
|
104
|
+
export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPayload = unknown>(
|
|
105
|
+
options: ModelManagerOptions<TApi, TModelsDevPayload>,
|
|
106
|
+
strategy: ModelRefreshStrategy = "online-if-uncached",
|
|
107
|
+
): Promise<ModelResolutionResult<TApi>> {
|
|
108
|
+
const now = options.now ?? Date.now;
|
|
109
|
+
const ttlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
110
|
+
const dbPath = options.cacheDbPath;
|
|
111
|
+
const staticModels = passModelList<TApi>(
|
|
112
|
+
options.staticModels ?? getBundledModels(options.providerId as GeneratedProvider),
|
|
113
|
+
);
|
|
114
|
+
const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
|
|
115
|
+
const dynamicModelsAuthoritative = options.dynamicModelsAuthoritative ?? false;
|
|
116
|
+
const staticFingerprint = fingerprintStatic(staticModels, dynamicModelsAuthoritative);
|
|
117
|
+
const cacheFingerprintMatches = cache?.staticFingerprint === staticFingerprint && staticFingerprint.length > 0;
|
|
118
|
+
const hasUsableFreshCache = (cache?.fresh ?? false) && (!dynamicModelsAuthoritative || cacheFingerprintMatches);
|
|
119
|
+
const dynamicFetcher = options.fetchDynamicModels;
|
|
120
|
+
const hasDynamicFetcher = typeof dynamicFetcher === "function";
|
|
121
|
+
const hasAuthoritativeCache = ((cache?.authoritative ?? false) && hasUsableFreshCache) || !hasDynamicFetcher;
|
|
122
|
+
const cacheAgeMs = cache ? now() - cache.updatedAt : Number.POSITIVE_INFINITY;
|
|
123
|
+
const shouldFetchFromNetwork = shouldFetchRemoteSources(
|
|
124
|
+
strategy,
|
|
125
|
+
hasUsableFreshCache,
|
|
126
|
+
hasAuthoritativeCache,
|
|
127
|
+
cacheAgeMs,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Cold-start fast path: when a fresh, authoritative cache exists, the network
|
|
131
|
+
// fetch is skipped, AND the static catalog slice is byte-identical to what
|
|
132
|
+
// was merged in last time, the cache row IS the authoritative merge result.
|
|
133
|
+
// Re-running `mergeDynamicModels(static, cache)` would just rebuild the same
|
|
134
|
+
// objects (~800ms in the steady-state cold-start profile for `aery -p hi`).
|
|
135
|
+
if (!shouldFetchFromNetwork && cache?.fresh && hasAuthoritativeCache && cacheFingerprintMatches) {
|
|
136
|
+
return { models: passModelList<TApi>(cache.models), stale: false };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const [fetchedModelsDevModels, fetchedDynamicModels] = shouldFetchFromNetwork
|
|
140
|
+
? await Promise.all([fetchModelsDev(options), dynamicFetcher ? fetchDynamicModels(dynamicFetcher) : null])
|
|
141
|
+
: [null, null];
|
|
142
|
+
const modelsDevModels = normalizeModelList<TApi>(fetchedModelsDevModels ?? []);
|
|
143
|
+
const shouldUseFreshCacheAsAuthoritative =
|
|
144
|
+
strategy === "online-if-uncached" && hasUsableFreshCache && hasAuthoritativeCache;
|
|
145
|
+
const dynamicFetchSucceeded = fetchedDynamicModels !== null;
|
|
146
|
+
const cacheModels = dynamicFetchSucceeded ? [] : normalizeModelList<TApi>(cache?.models ?? []);
|
|
147
|
+
const dynamicModels = fetchedDynamicModels ?? [];
|
|
148
|
+
const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
|
|
149
|
+
const mergedModels = mergeDynamicModels(mergedWithCache, dynamicModels);
|
|
150
|
+
const models =
|
|
151
|
+
dynamicModelsAuthoritative && dynamicFetchSucceeded ? retainModelIds(mergedModels, dynamicModels) : mergedModels;
|
|
152
|
+
const dynamicAuthoritative = !hasDynamicFetcher || dynamicFetchSucceeded || shouldUseFreshCacheAsAuthoritative;
|
|
153
|
+
if (shouldFetchFromNetwork) {
|
|
154
|
+
if (dynamicFetchSucceeded) {
|
|
155
|
+
const mergedSnapshot = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), dynamicModels);
|
|
156
|
+
const snapshotModels = dynamicModelsAuthoritative
|
|
157
|
+
? retainModelIds(mergedSnapshot, dynamicModels)
|
|
158
|
+
: mergedSnapshot;
|
|
159
|
+
writeModelCache(options.providerId, now(), snapshotModels, true, staticFingerprint, dbPath);
|
|
160
|
+
} else {
|
|
161
|
+
// Dynamic fetch failed — update cache with a non-authoritative snapshot so
|
|
162
|
+
// stale state remains visible while retry backoff still applies.
|
|
163
|
+
const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
|
|
164
|
+
writeModelCache(
|
|
165
|
+
options.providerId,
|
|
166
|
+
now(),
|
|
167
|
+
mergeDynamicModels(
|
|
168
|
+
mergeModelSources(staticModels, modelsDevModels),
|
|
169
|
+
normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
|
|
170
|
+
),
|
|
171
|
+
false,
|
|
172
|
+
staticFingerprint,
|
|
173
|
+
dbPath,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
models,
|
|
179
|
+
stale: !dynamicAuthoritative,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function fetchModelsDev<TApi extends Api, TModelsDevPayload>(
|
|
184
|
+
options: ModelManagerOptions<TApi, TModelsDevPayload>,
|
|
185
|
+
): Promise<Model<TApi>[] | null> {
|
|
186
|
+
if (!options.modelsDev) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const payload = await options.modelsDev.fetch();
|
|
192
|
+
return normalizeModelList<TApi>(options.modelsDev.map(payload, options.providerId));
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function fetchDynamicModels<TApi extends Api>(
|
|
199
|
+
fetcher: () => Promise<readonly Model<TApi>[] | null>,
|
|
200
|
+
): Promise<Model<TApi>[] | null> {
|
|
201
|
+
try {
|
|
202
|
+
const models = await fetcher();
|
|
203
|
+
if (models === null) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return normalizeModelList<TApi>(models);
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function shouldFetchRemoteSources(
|
|
213
|
+
strategy: ModelRefreshStrategy,
|
|
214
|
+
hasFreshCache: boolean,
|
|
215
|
+
hasAuthoritativeCache: boolean,
|
|
216
|
+
cacheAgeMs: number,
|
|
217
|
+
): boolean {
|
|
218
|
+
if (strategy === "offline") {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (strategy === "online") {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
// online-if-uncached: skip fetch if cache is fresh.
|
|
225
|
+
// For non-authoritative caches (dynamic fetch previously failed),
|
|
226
|
+
// use a shorter retry interval instead of retrying every startup.
|
|
227
|
+
if (!hasFreshCache) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (!hasAuthoritativeCache) {
|
|
231
|
+
return cacheAgeMs >= NON_AUTHORITATIVE_RETRY_MS;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function mergeModelSources<TApi extends Api>(...sources: readonly (readonly Model<TApi>[])[]): Model<TApi>[] {
|
|
237
|
+
// Strip out empty/missing sources up front. The hot path is `(static, [])`
|
|
238
|
+
// (modelsDev disabled / failed) — a single non-empty source means we can
|
|
239
|
+
// skip the Map churn entirely and just hand back the array.
|
|
240
|
+
const nonEmpty = sources.filter(source => source.length > 0);
|
|
241
|
+
if (nonEmpty.length === 0) return [];
|
|
242
|
+
if (nonEmpty.length === 1) return [...nonEmpty[0]];
|
|
243
|
+
const merged = new Map<string, Model<TApi>>();
|
|
244
|
+
for (const source of nonEmpty) {
|
|
245
|
+
for (const model of source) {
|
|
246
|
+
if (!model?.id) continue;
|
|
247
|
+
merged.set(model.id, model);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return Array.from(merged.values());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function mergeDynamicModels<TApi extends Api>(
|
|
254
|
+
baseModels: readonly Model<TApi>[],
|
|
255
|
+
dynamicModels: readonly Model<TApi>[],
|
|
256
|
+
): Model<TApi>[] {
|
|
257
|
+
// Empty-side fast paths: `mergeDynamicModels(base, [])` is the common shape
|
|
258
|
+
// after we've already merged the first pair, and `(...)` with no base
|
|
259
|
+
// happens for providers without static catalogs.
|
|
260
|
+
if (dynamicModels.length === 0) return baseModels.length === 0 ? [] : [...baseModels];
|
|
261
|
+
if (baseModels.length === 0) return [...dynamicModels];
|
|
262
|
+
const merged = new Map<string, Model<TApi>>(baseModels.map(model => [model.id, model]));
|
|
263
|
+
for (const dynamicModel of dynamicModels) {
|
|
264
|
+
if (!dynamicModel?.id) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const existingModel = merged.get(dynamicModel.id);
|
|
268
|
+
if (!existingModel) {
|
|
269
|
+
merged.set(dynamicModel.id, dynamicModel);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
merged.set(dynamicModel.id, mergeDynamicModel(existingModel, dynamicModel));
|
|
273
|
+
}
|
|
274
|
+
return Array.from(merged.values());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function retainModelIds<TApi extends Api>(
|
|
278
|
+
models: readonly Model<TApi>[],
|
|
279
|
+
retainedModels: readonly Model<TApi>[],
|
|
280
|
+
): Model<TApi>[] {
|
|
281
|
+
if (retainedModels.length === 0 || models.length === 0) return [];
|
|
282
|
+
const retainedIds = new Set(retainedModels.map(model => model.id));
|
|
283
|
+
return models.filter(model => retainedIds.has(model.id));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Stable, low-collision fingerprint of a static catalog slice. Cached by
|
|
288
|
+
* reference so repeat calls in the same process (e.g. multiple cold-start
|
|
289
|
+
* arms calling `resolveProviderModels` with the same `staticModels` array)
|
|
290
|
+
* skip the JSON+hash work after the first call.
|
|
291
|
+
*/
|
|
292
|
+
const MODEL_CACHE_FINGERPRINT_VERSION = "merge-v2";
|
|
293
|
+
const kStaticFingerprint = Symbol("model-manager.staticFingerprint");
|
|
294
|
+
type ModelArrayWithFingerprint = readonly Model<Api>[] & { [kStaticFingerprint]?: string };
|
|
295
|
+
function fingerprintStatic<TApi extends Api>(
|
|
296
|
+
models: readonly Model<TApi>[],
|
|
297
|
+
dynamicModelsAuthoritative = false,
|
|
298
|
+
): string {
|
|
299
|
+
if (models.length === 0) return `${MODEL_CACHE_FINGERPRINT_VERSION}:empty`;
|
|
300
|
+
if (dynamicModelsAuthoritative)
|
|
301
|
+
return `${MODEL_CACHE_FINGERPRINT_VERSION}:authoritative:${fingerprintStatic(models)}`;
|
|
302
|
+
const tagged = models as ModelArrayWithFingerprint;
|
|
303
|
+
const cached = tagged[kStaticFingerprint];
|
|
304
|
+
if (cached !== undefined) return cached;
|
|
305
|
+
// `Bun.hash` returns a `bigint`; base36 keeps the string short for the
|
|
306
|
+
// SQLite column without sacrificing distinguishability.
|
|
307
|
+
const fingerprint = `${MODEL_CACHE_FINGERPRINT_VERSION}:${Bun.hash(JSON.stringify(models)).toString(36)}`;
|
|
308
|
+
tagged[kStaticFingerprint] = fingerprint;
|
|
309
|
+
return fingerprint;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamicModel: Model<TApi>): Model<TApi> {
|
|
313
|
+
const supportsImage = existingModel.input.includes("image") || dynamicModel.input.includes("image");
|
|
314
|
+
return enrichModelThinking({
|
|
315
|
+
...existingModel,
|
|
316
|
+
...dynamicModel,
|
|
317
|
+
name: preferDiscoveryName(dynamicModel.name, existingModel.name, dynamicModel.id),
|
|
318
|
+
reasoning: existingModel.reasoning || dynamicModel.reasoning,
|
|
319
|
+
input: supportsImage ? ["text", "image"] : ["text"],
|
|
320
|
+
cost: {
|
|
321
|
+
input: preferDiscoveryCost(dynamicModel.cost.input, existingModel.cost.input),
|
|
322
|
+
output: preferDiscoveryCost(dynamicModel.cost.output, existingModel.cost.output),
|
|
323
|
+
cacheRead: preferDiscoveryCost(dynamicModel.cost.cacheRead, existingModel.cost.cacheRead),
|
|
324
|
+
cacheWrite: preferDiscoveryCost(dynamicModel.cost.cacheWrite, existingModel.cost.cacheWrite),
|
|
325
|
+
},
|
|
326
|
+
contextWindow: preferDiscoveryLimit(dynamicModel.contextWindow, existingModel.contextWindow),
|
|
327
|
+
maxTokens: preferDiscoveryLimit(dynamicModel.maxTokens, existingModel.maxTokens),
|
|
328
|
+
headers: dynamicModel.headers ? { ...existingModel.headers, ...dynamicModel.headers } : existingModel.headers,
|
|
329
|
+
compat: dynamicModel.compat ?? existingModel.compat,
|
|
330
|
+
contextPromotionTarget: dynamicModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function preferDiscoveryCost(discoveryCost: number, fallbackCost: number): number {
|
|
335
|
+
if (Number.isFinite(discoveryCost) && discoveryCost > 0) {
|
|
336
|
+
return discoveryCost;
|
|
337
|
+
}
|
|
338
|
+
return fallbackCost;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function preferDiscoveryName(discoveryName: string, fallbackName: string, modelId: string): string {
|
|
342
|
+
const normalizedDiscoveryName = discoveryName.trim();
|
|
343
|
+
if (normalizedDiscoveryName.length === 0) {
|
|
344
|
+
return fallbackName;
|
|
345
|
+
}
|
|
346
|
+
if (normalizedDiscoveryName === modelId && fallbackName !== modelId) {
|
|
347
|
+
return fallbackName;
|
|
348
|
+
}
|
|
349
|
+
return normalizedDiscoveryName;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function preferDiscoveryLimit(discoveryLimit: number, fallbackLimit: number): number {
|
|
353
|
+
if (!Number.isFinite(discoveryLimit) || discoveryLimit <= 0) {
|
|
354
|
+
return fallbackLimit;
|
|
355
|
+
}
|
|
356
|
+
if (discoveryLimit === 4096 && fallbackLimit > discoveryLimit) {
|
|
357
|
+
return fallbackLimit;
|
|
358
|
+
}
|
|
359
|
+
return discoveryLimit;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function normalizeModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
|
|
363
|
+
if (!Array.isArray(value)) {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
const models: Model<TApi>[] = [];
|
|
367
|
+
for (const item of value) {
|
|
368
|
+
if (isModelLike(item)) {
|
|
369
|
+
models.push(enrichModelThinking(item as Model<TApi>));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return models;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isModelLike(value: unknown): value is Model<Api> {
|
|
376
|
+
if (!isRecord(value)) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
const v = value as {
|
|
380
|
+
id?: unknown;
|
|
381
|
+
name?: unknown;
|
|
382
|
+
api?: unknown;
|
|
383
|
+
provider?: unknown;
|
|
384
|
+
baseUrl?: unknown;
|
|
385
|
+
reasoning?: unknown;
|
|
386
|
+
input?: unknown;
|
|
387
|
+
cost?: unknown;
|
|
388
|
+
contextWindow?: unknown;
|
|
389
|
+
maxTokens?: unknown;
|
|
390
|
+
};
|
|
391
|
+
if (typeof v.id !== "string" || v.id.length === 0) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
if (typeof v.name !== "string" || v.name.length === 0) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
if (typeof v.api !== "string" || v.api.length === 0) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
if (typeof v.provider !== "string" || v.provider.length === 0) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
if (typeof v.baseUrl !== "string" || v.baseUrl.length === 0) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
if (typeof v.reasoning !== "boolean") {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
if (!isModelInputArray(v.input)) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
if (!isModelCost(v.cost)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
// Finite positive: NaN > 0 is false, +Infinity < Infinity is false.
|
|
416
|
+
const cw = v.contextWindow;
|
|
417
|
+
if (typeof cw !== "number" || !(cw > 0 && cw < Infinity)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
const mt = v.maxTokens;
|
|
421
|
+
if (typeof mt !== "number" || !(mt > 0 && mt < Infinity)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isModelInputArray(value: unknown): value is ("text" | "image")[] {
|
|
428
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
for (let i = 0; i < value.length; i++) {
|
|
432
|
+
const item = value[i];
|
|
433
|
+
if (item !== "text" && item !== "image") {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function isModelCost(value: unknown): value is Model<Api>["cost"] {
|
|
441
|
+
if (!isRecord(value)) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const c = value as {
|
|
445
|
+
input?: unknown;
|
|
446
|
+
output?: unknown;
|
|
447
|
+
cacheRead?: unknown;
|
|
448
|
+
cacheWrite?: unknown;
|
|
449
|
+
};
|
|
450
|
+
// Finite (NaN-safe): -Infinity < x < Infinity rejects NaN and both infinities.
|
|
451
|
+
// Preserves original behavior: 0 and negatives remain valid.
|
|
452
|
+
const ci = c.input;
|
|
453
|
+
if (typeof ci !== "number" || !(ci > -Infinity && ci < Infinity)) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
const co = c.output;
|
|
457
|
+
if (typeof co !== "number" || !(co > -Infinity && co < Infinity)) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const cr = c.cacheRead;
|
|
461
|
+
if (typeof cr !== "number" || !(cr > -Infinity && cr < Infinity)) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const cw = c.cacheWrite;
|
|
465
|
+
if (typeof cw !== "number" || !(cw > -Infinity && cw < Infinity)) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|