cartethyia 2.5.10 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/dist/dashboard/assets/{BannedPage-Cd30Z0b9.js → BannedPage-wr370U7a.js} +1 -1
- package/dist/dashboard/assets/BannedPage-wr370U7a.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-wf5u-ww6.js → CliToolDetailPage-D1sCezWa.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-D1sCezWa.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DykPvuck.js → CliToolsPage-DVWbJPEQ.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DVWbJPEQ.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DwvSD3vs.js → CombosPage-CZc7fyMe.js} +1 -1
- package/dist/dashboard/assets/CombosPage-CZc7fyMe.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CIr7KDex.js → ConsoleLogPage-OPN3qRCa.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-OPN3qRCa.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-82X0DDdH.js → CustomizationPage-DGTSqmwm.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-DGTSqmwm.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BujG2QP4.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BujG2QP4.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-CS5mPuVF.js → LoginPage-C_Jd_KgP.js} +1 -1
- package/dist/dashboard/assets/LoginPage-C_Jd_KgP.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C0UCUsVJ.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Bpbu-VeU.js +1 -0
- package/dist/dashboard/assets/OverviewPage-Bpbu-VeU.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BEhXOO4L.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-BEhXOO4L.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-vdja5ZPK.js → ProvidersPage-DbQnexQd.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-DbQnexQd.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BB4CNm4-.js → ProxyPage-ByprzADo.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-ByprzADo.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CvSfv6tP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CvSfv6tP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-_NzLq3C-.js +1 -0
- package/dist/dashboard/assets/SettingsPage-_NzLq3C-.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-FadUOl9S.js → SetupPage-Bg9DcG-G.js} +1 -1
- package/dist/dashboard/assets/SetupPage-Bg9DcG-G.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Cfy6_Khv.js → SortableList-C2PWPzJm.js} +1 -1
- package/dist/dashboard/assets/SortableList-C2PWPzJm.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CM92fgJf.js → StudioPage-Dky1zZMt.js} +1 -1
- package/dist/dashboard/assets/StudioPage-Dky1zZMt.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-BT4MyQeT.js → UsagePage-C5Vdpd_c.js} +2 -2
- package/dist/dashboard/assets/UsagePage-C5Vdpd_c.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-B7pVRhJ1.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-B7pVRhJ1.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Bvyr7D0u.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DIvKAZih.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-D5_gYIS_.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-B6vDBtbm.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-CpjrgdwJ.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BWmt3b5y.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/entry-BNFjUjQ0.js +4 -0
- package/dist/dashboard/assets/entry-BNFjUjQ0.js.gz +0 -0
- package/dist/dashboard/assets/{entry-C_oakV3z.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-RRHHWGxh.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-Bhey_rg_.js → index-DyllNVCY.js} +2 -2
- package/dist/dashboard/assets/index-DyllNVCY.js.gz +0 -0
- package/dist/dashboard/assets/{inline-C2_EQ5Uf.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-BMRlQ450.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-XR5xP8ew.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Cw07dioc.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DrdA9oSM.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-CxVVB__N.js → select-DAhoTabv.js} +1 -1
- package/dist/dashboard/assets/select-DAhoTabv.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-uPOwM9Hs.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-D1Yz79iL.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-sevsRrRo.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -95
- package/scripts/cli/admin.ts +126 -128
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +123 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +177 -167
- package/scripts/cli/console-menu.ts +45 -30
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -260
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +195 -167
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +217 -79
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -85
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/hidden-window.vbs +16 -2
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +29 -4
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +10 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Cd30Z0b9.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-wf5u-ww6.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DykPvuck.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DwvSD3vs.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CIr7KDex.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-82X0DDdH.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CS5mPuVF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C0UCUsVJ.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-vdja5ZPK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BB4CNm4-.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js +0 -1
- package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js +0 -1
- package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-FadUOl9S.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Cfy6_Khv.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CM92fgJf.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BT4MyQeT.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-Bvyr7D0u.js.gz +0 -0
- package/dist/dashboard/assets/banner-DIvKAZih.js.gz +0 -0
- package/dist/dashboard/assets/card-D5_gYIS_.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-B6vDBtbm.js.gz +0 -0
- package/dist/dashboard/assets/dialog-CpjrgdwJ.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BWmt3b5y.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-gvimrg.js +0 -1
- package/dist/dashboard/assets/entry-B-gvimrg.js.gz +0 -0
- package/dist/dashboard/assets/entry-C_oakV3z.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dj0cHfEc.js +0 -4
- package/dist/dashboard/assets/entry-Dj0cHfEc.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-RRHHWGxh.js.gz +0 -0
- package/dist/dashboard/assets/index-Bhey_rg_.js.gz +0 -0
- package/dist/dashboard/assets/inline-C2_EQ5Uf.js.gz +0 -0
- package/dist/dashboard/assets/input-BMRlQ450.js.gz +0 -0
- package/dist/dashboard/assets/layout-XR5xP8ew.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Cw07dioc.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DrdA9oSM.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-CxVVB__N.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-uPOwM9Hs.js.gz +0 -0
- package/dist/dashboard/assets/state-D1Yz79iL.js.gz +0 -0
- package/dist/dashboard/assets/switch-sevsRrRo.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -439
|
@@ -1,373 +1,373 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical model-definition helpers.
|
|
3
|
-
*
|
|
4
|
-
* Every static provider catalog describes the same `ModelDefinition` shape with
|
|
5
|
-
* only a handful of per-provider differences (wire family, endpoint path,
|
|
6
|
-
* vision/reasoning/tool flags, pricing). `defineModel` is the single place that
|
|
7
|
-
* shape is constructed. Catalogs pass only the fields that differ, so a catalog
|
|
8
|
-
* edit cannot silently drift from the shared defaults. Limits and pricing fall
|
|
9
|
-
* back to the prebuilt base catalog; capability does not — see `defineModel`.
|
|
10
|
-
*/
|
|
11
|
-
import type { ServiceKind, WireFamily } from "../protocol/canonical-model";
|
|
12
|
-
import { modelsDevCatalog } from "./discovery/models-dev-catalog";
|
|
13
|
-
|
|
14
|
-
/** Billing metadata for one provider model. */
|
|
15
|
-
export interface ModelCostDefinition {
|
|
16
|
-
readonly input: number | null;
|
|
17
|
-
readonly output: number | null;
|
|
18
|
-
readonly cache_read?: number;
|
|
19
|
-
readonly cache_write?: number;
|
|
20
|
-
readonly pricing_model?: "pay-per-use" | "subscription" | "unknown";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Per-token cost for a plan whose tier is free. Zero is a tier fact owned by
|
|
25
|
-
* the provider (Cline's `free` bucket, mirroring the upstream client's
|
|
26
|
-
* deliberate cost zeroing), not a price copied from the base catalog: the base
|
|
27
|
-
* catalog prices the *underlying* model, which the free tier does not bill.
|
|
28
|
-
*/
|
|
29
|
-
export const FREE_TIER_COST: ModelCostDefinition = {
|
|
30
|
-
input: 0,
|
|
31
|
-
output: 0,
|
|
32
|
-
pricing_model: "pay-per-use",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** Canonical static metadata for one provider model. */
|
|
36
|
-
export interface ModelDefinition {
|
|
37
|
-
readonly modelId: string;
|
|
38
|
-
readonly wireFamily: WireFamily;
|
|
39
|
-
/**
|
|
40
|
-
* Which protocol shape this model speaks. Absent means `"llm"` (the canonical
|
|
41
|
-
* chat pipeline). A non-`llm` row is served by its native passthrough route,
|
|
42
|
-
* not by the surface codecs, and `wireFamily`/`endpointPath` describe that
|
|
43
|
-
* native route's transport rather than a chat wire.
|
|
44
|
-
*
|
|
45
|
-
* Optional so the hand-built catalog literals that predate this dimension
|
|
46
|
-
* stay valid unchanged; read it as `definition.serviceKind ?? "llm"`.
|
|
47
|
-
*/
|
|
48
|
-
readonly serviceKind?: ServiceKind;
|
|
49
|
-
readonly endpointPath: string;
|
|
50
|
-
readonly contextLimit: number | null;
|
|
51
|
-
readonly outputLimit: number | null;
|
|
52
|
-
readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
|
|
53
|
-
readonly reasoning: boolean;
|
|
54
|
-
/** Explicitly supported reasoning effort scale (e.g. `["minimal", "low", "medium", "high", "xhigh"]`). */
|
|
55
|
-
readonly reasoningEfforts?: readonly string[];
|
|
56
|
-
readonly toolCall: boolean;
|
|
57
|
-
readonly cost: ModelCostDefinition;
|
|
58
|
-
/**
|
|
59
|
-
* Whether the provider serves this row on a free plan tier.
|
|
60
|
-
*
|
|
61
|
-
* Deliberately not the same thing as `free` on the input row. `free` is a
|
|
62
|
-
* *billing* statement — bill zero per token — and a subscription roster also
|
|
63
|
-
* bills zero per token. This is a *catalog* statement: the row belongs to the
|
|
64
|
-
* tier the provider publishes as free. Discovery has to tell them apart
|
|
65
|
-
* because the free tier is a distinct group in the model list, and `cost`
|
|
66
|
-
* cannot answer it — a reseller may legitimately price a model at `$0.00`
|
|
67
|
-
* without it being the provider's free tier.
|
|
68
|
-
*
|
|
69
|
-
* Optional, and absence is the answer "not a free-tier row": the field is a
|
|
70
|
-
* marker only a discovery module that reads a provider's free tier can set,
|
|
71
|
-
* so a hand-built definition correctly says nothing. Read it as `=== true`.
|
|
72
|
-
*/
|
|
73
|
-
readonly freeTier?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Whether the provider refuses this row on a free plan.
|
|
76
|
-
*
|
|
77
|
-
* The opposite statement to `freeTier`, and not its absence: `freeTier` says
|
|
78
|
-
* "served on the free tier", while this says "refused there". A row neither
|
|
79
|
-
* marked is simply one whose tier nobody established — which is most rows,
|
|
80
|
-
* and must not be rendered as "free".
|
|
81
|
-
*
|
|
82
|
-
* CodeBuddy is why both exist. Its console directory publishes no field that
|
|
83
|
-
* predicts access: `credits` and `vendor` were both measured against real
|
|
84
|
-
* dispatches and neither correlates — `kimi-k3` bills `x1.62` and is served
|
|
85
|
-
* on a free account while `gpt-5.6-luna` bills `x0.14` and is refused. The
|
|
86
|
-
* only signal is the refusal itself: `11158 "model [X] is not allowed for
|
|
87
|
-
* user type [personal_free_user]"`. See
|
|
88
|
-
* `.skills/cartethyia-reverse-engineering/references/codebuddy-dispatch-headers.md`.
|
|
89
|
-
*
|
|
90
|
-
* Optional, and absence is "tier unknown" rather than "served on free". Read
|
|
91
|
-
* it as `=== true`; a UI that shows a tier must treat undefined as unknown,
|
|
92
|
-
* not as the permissive answer.
|
|
93
|
-
*/
|
|
94
|
-
readonly proOnly?: boolean;
|
|
95
|
-
/**
|
|
96
|
-
* True when neither the row nor the catalog could state this model's limits,
|
|
97
|
-
* so `contextLimit`/`outputLimit` are the shared fallback rather than
|
|
98
|
-
* anything measured.
|
|
99
|
-
*
|
|
100
|
-
* A model nobody has filed — a brand-new id the catalog has not caught up
|
|
101
|
-
* with — must still be routable, and a hard number is what routing needs to
|
|
102
|
-
* size a request. But publishing that number as if it were the model's own
|
|
103
|
-
* turns a gap in our catalog into a claim about someone else's model.
|
|
104
|
-
*/
|
|
105
|
-
readonly limitsEstimated?: boolean;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Limits assumed when neither the catalog nor the operator supplies any.
|
|
110
|
-
*
|
|
111
|
-
* Deliberately generous: a context ceiling that is too *low* truncates a
|
|
112
|
-
* working request, while one that is too high only means the upstream rejects
|
|
113
|
-
* the call on its own terms. Chosen as a round number above every current
|
|
114
|
-
* frontier model so the fallback is never the binding constraint.
|
|
115
|
-
*
|
|
116
|
-
* A row carrying these because nothing filed the model is flagged as estimated,
|
|
117
|
-
* so the UI can say the number is a guess rather than presenting it as a fact
|
|
118
|
-
* about the model.
|
|
119
|
-
*/
|
|
120
|
-
export const FALLBACK_CONTEXT_LIMIT = 512_000;
|
|
121
|
-
export const FALLBACK_OUTPUT_LIMIT = 131_072;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Limits assumed for a model the catalog has never filed.
|
|
125
|
-
*
|
|
126
|
-
* Fit comfortably rather than tightly: a fallback that is too small silently
|
|
127
|
-
* truncates a request that would have been fine, while one that is too large
|
|
128
|
-
* only costs an admission estimate no upstream enforces. The two spellings above
|
|
129
|
-
* are one number, so a row rendered in the console and a row routed through
|
|
130
|
-
* discovery cannot disagree about what "unfiled" means.
|
|
131
|
-
*/
|
|
132
|
-
const UNFILED_CONTEXT_LIMIT = FALLBACK_CONTEXT_LIMIT;
|
|
133
|
-
const UNFILED_OUTPUT_LIMIT = FALLBACK_OUTPUT_LIMIT;
|
|
134
|
-
|
|
135
|
-
/** Fields accepted by `defineModel`; every field except `id` falls back to the shared default. */
|
|
136
|
-
export interface ModelHelperRow {
|
|
137
|
-
readonly id: string;
|
|
138
|
-
/**
|
|
139
|
-
* Canonical provider id this row is served by. Optional only so shared
|
|
140
|
-
* helper rows can omit it; supplying it is what makes base-catalog limits
|
|
141
|
-
* and pricing resolve to the right provider's entry.
|
|
142
|
-
*/
|
|
143
|
-
readonly providerId?: string;
|
|
144
|
-
/** Defaults to `"chat"`. */
|
|
145
|
-
readonly wireFamily?: ModelDefinition["wireFamily"];
|
|
146
|
-
/** Defaults to `"llm"`. A non-`llm` row is served by its native route. */
|
|
147
|
-
readonly serviceKind?: ServiceKind;
|
|
148
|
-
/** Defaults to the family's conventional path (`/chat/completions`, `/responses`, `/messages`). */
|
|
149
|
-
readonly endpoint?: string;
|
|
150
|
-
/** Defaults to 128k. */
|
|
151
|
-
readonly ctx?: number | null;
|
|
152
|
-
/** Defaults to 8k. */
|
|
153
|
-
readonly out?: number | null;
|
|
154
|
-
readonly vision?: boolean;
|
|
155
|
-
/** Accepts PDF/plain-file input parts (`document`/`file` canonical kinds). */
|
|
156
|
-
readonly document?: boolean;
|
|
157
|
-
/** Accepts audio input parts. */
|
|
158
|
-
readonly audio?: boolean;
|
|
159
|
-
readonly reasoning?: boolean;
|
|
160
|
-
/**
|
|
161
|
-
* Output modalities this row produces. Defaults to `["text"]`.
|
|
162
|
-
*
|
|
163
|
-
* A generation row sets `["image"]` or `["video"]`: the capability profile's
|
|
164
|
-
* `mediaGeneration` flag reads `output.includes("image")`, and before this
|
|
165
|
-
* field existed every catalog row was hardcoded to text, so no static row
|
|
166
|
-
* could ever declare that it returns an image.
|
|
167
|
-
*/
|
|
168
|
-
readonly output?: readonly ModelDefinition["modalities"]["output"][number][];
|
|
169
|
-
/** Explicitly supported reasoning effort scale. */
|
|
170
|
-
readonly reasoningEfforts?: readonly string[];
|
|
171
|
-
readonly toolCall?: boolean;
|
|
172
|
-
/**
|
|
173
|
-
* Marks a row served by a free plan tier. Such rows bill zero per token
|
|
174
|
-
* regardless of what the base catalog charges for the underlying model.
|
|
175
|
-
*/
|
|
176
|
-
readonly free?: boolean;
|
|
177
|
-
/**
|
|
178
|
-
* Marks a row that belongs to the tier the provider publishes as free.
|
|
179
|
-
*
|
|
180
|
-
* Separate from `free` because the two answer different questions: a
|
|
181
|
-
* subscription roster bills zero per token (`free`) without being the free
|
|
182
|
-
* tier. This one is what the model list groups on, so a provider's free-tier
|
|
183
|
-
* discovery can be labelled as such instead of mixed into ordinary fetched
|
|
184
|
-
* rows. Defaults to false — a row nobody marked is not a free-tier row.
|
|
185
|
-
*/
|
|
186
|
-
readonly freeTier?: boolean;
|
|
187
|
-
/** Whether the provider refuses this row on a free plan. See `ModelDefinition.proOnly`. */
|
|
188
|
-
readonly proOnly?: boolean;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function defaultEndpoint(wireFamily: ModelDefinition["wireFamily"]): string {
|
|
192
|
-
if (wireFamily === "chat") return "/chat/completions";
|
|
193
|
-
if (wireFamily === "responses") return "/responses";
|
|
194
|
-
return "/messages";
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Model ids whose families speak native OpenAI Responses on every gateway that
|
|
199
|
-
* lists them (GPT-5/6, o3/o4). Verified live: `gpt-5.6-terra` answers on
|
|
200
|
-
* `/v1/responses` in ~2s with its gateway routing prefix intact, and the
|
|
201
|
-
* codex/openai static catalogs already carry these families as responses-wire.
|
|
202
|
-
*
|
|
203
|
-
* Both the static inferhub catalog and the tolerant `/models` discovery path
|
|
204
|
-
* classify wire family from the raw id, so the rule is stated once here: a
|
|
205
|
-
* gateway that lists one of these families must not pin it to the chat wire.
|
|
206
|
-
*/
|
|
207
|
-
export function isResponsesNativeModelId(modelId: string): boolean {
|
|
208
|
-
const id = modelId.toLowerCase();
|
|
209
|
-
return id.includes("gpt-5") || id.includes("gpt-6") || id.includes("/o3") || id.includes("/o4");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Builds one static catalog row. No empty-id guard: the Anthropic and Claude
|
|
214
|
-
* catalogs intentionally ship empty-id rows, so validation here would break
|
|
215
|
-
* module load.
|
|
216
|
-
*
|
|
217
|
-
* Capability is declared per row and never read from the base catalog. The base
|
|
218
|
-
* catalog is keyed by bare model id, and a bare id maps to many provider rows
|
|
219
|
-
* where only the first survives (`models-dev-catalog.ts`), so a capability read
|
|
220
|
-
* from it can describe a different provider's serving of the same model. What a
|
|
221
|
-
* row does not declare is left to the routing ladder's filter → degrade path
|
|
222
|
-
* rather than guessed from a stranger's metadata.
|
|
223
|
-
*/
|
|
224
|
-
export function defineModel(row: ModelHelperRow): ModelDefinition {
|
|
225
|
-
const wireFamily = row.wireFamily ?? "chat";
|
|
226
|
-
// `row.providerId` names the provider whose catalog this row belongs to, so
|
|
227
|
-
// limits and cost come from that provider's own base-catalog entry. Without
|
|
228
|
-
// it the lookup falls back to the bare id, which returns `undefined` when the
|
|
229
|
-
// catalog disagrees about that id (428 bare keys disagree on context alone).
|
|
230
|
-
const fallback = row.id ? modelsDevCatalog.resolve(row.providerId ?? "", row.id) : undefined;
|
|
231
|
-
// A provider the catalog does not file under our id (or files nowhere at
|
|
232
|
-
// all) leaves `resolve` undefined, which would fall through to the fixed
|
|
233
|
-
// default below. The majority vote is the honest middle tier: the model is
|
|
234
|
-
// known, the catalog merely disagrees about it, and the most-agreed row
|
|
235
|
-
// describes the model itself rather than one reseller's serving. Only the
|
|
236
|
-
// field the vote actually answered is taken; a model nobody records stays
|
|
237
|
-
// at the default.
|
|
238
|
-
const majority = fallback === undefined && row.id ? modelsDevCatalog.majorityFor(row.id) : undefined;
|
|
239
|
-
|
|
240
|
-
// Nothing filed this model: not the row, not the provider's catalog entry,
|
|
241
|
-
// not a majority across the catalog's other resellers. The fallback keeps it
|
|
242
|
-
// routable, and the marker keeps the number honest.
|
|
243
|
-
const limitsEstimated = row.ctx === undefined && fallback?.contextLimit === undefined && majority?.contextLimit === undefined;
|
|
244
|
-
|
|
245
|
-
const contextLimit =
|
|
246
|
-
row.ctx !== undefined
|
|
247
|
-
? row.ctx
|
|
248
|
-
: (fallback?.contextLimit ?? majority?.contextLimit ?? UNFILED_CONTEXT_LIMIT);
|
|
249
|
-
|
|
250
|
-
const declaredOutput =
|
|
251
|
-
row.out !== undefined
|
|
252
|
-
? row.out
|
|
253
|
-
: (fallback?.outputLimit ?? majority?.outputLimit ?? UNFILED_OUTPUT_LIMIT);
|
|
254
|
-
|
|
255
|
-
// A maximum output above the context window is unsatisfiable: no request can
|
|
256
|
-
// reserve more output tokens than the model accepts as input. The base
|
|
257
|
-
// catalog carries 1084 rows that state output === context and 69 that state
|
|
258
|
-
// output > context (a Kimi K2.6 row is filed as 262144/262144), so an
|
|
259
|
-
// unclamped cap would publish a limit the provider rejects and make the
|
|
260
|
-
// admission estimate reserve the whole window as output headroom.
|
|
261
|
-
const outputLimit =
|
|
262
|
-
contextLimit === null || declaredOutput === null
|
|
263
|
-
? declaredOutput
|
|
264
|
-
: Math.min(declaredOutput, contextLimit);
|
|
265
|
-
|
|
266
|
-
const cost = row.free ? FREE_TIER_COST : modelsDevCatalog.costFor(row.providerId ?? "", row.id);
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
modelId: row.id,
|
|
270
|
-
wireFamily,
|
|
271
|
-
serviceKind: row.serviceKind ?? "llm",
|
|
272
|
-
endpointPath: row.endpoint ?? defaultEndpoint(wireFamily),
|
|
273
|
-
contextLimit,
|
|
274
|
-
outputLimit,
|
|
275
|
-
modalities: {
|
|
276
|
-
input: [
|
|
277
|
-
"text",
|
|
278
|
-
...(row.vision ? ["image"] : []),
|
|
279
|
-
...(row.document ? ["document"] : []),
|
|
280
|
-
...(row.audio ? ["audio"] : []),
|
|
281
|
-
],
|
|
282
|
-
output: row.output ?? ["text"],
|
|
283
|
-
},
|
|
284
|
-
reasoning: row.reasoning ?? false,
|
|
285
|
-
...(row.reasoningEfforts !== undefined ? { reasoningEfforts: row.reasoningEfforts } : {}),
|
|
286
|
-
toolCall: row.toolCall ?? true,
|
|
287
|
-
cost,
|
|
288
|
-
...(row.freeTier === undefined ? {} : { freeTier: row.freeTier }),
|
|
289
|
-
...(row.proOnly === undefined ? {} : { proOnly: row.proOnly }),
|
|
290
|
-
...(limitsEstimated ? { limitsEstimated: true } : {}),
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** Capability metadata persisted for one manually registered model id. */
|
|
295
|
-
export interface ManualModelMetadata {
|
|
296
|
-
readonly contextLimit: number | null;
|
|
297
|
-
readonly outputLimit: number | null;
|
|
298
|
-
readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
|
|
299
|
-
readonly reasoning: boolean;
|
|
300
|
-
readonly toolCall: boolean;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Resolves capability metadata for a manually registered model id.
|
|
305
|
-
*
|
|
306
|
-
* A manual add performs no upstream probe and has no static catalog row. Its
|
|
307
|
-
* capabilities are therefore declared up front rather than inferred from a
|
|
308
|
-
* `defineModel(...)` entry: tools, vision, and reasoning are always enabled. A
|
|
309
|
-
* false negative here is not cosmetic — capability preflight reads `toolCall`
|
|
310
|
-
* and silently strips `tools` from the request, so a manually added model
|
|
311
|
-
* would appear unable to call tools at all. The prebuilt base catalog still
|
|
312
|
-
* supplies the non-capability facts (context/output limits, cost) when it
|
|
313
|
-
* knows the id.
|
|
314
|
-
*/
|
|
315
|
-
export function resolveManualModelMetadata(
|
|
316
|
-
providerId: string,
|
|
317
|
-
modelId: string,
|
|
318
|
-
): ManualModelMetadata {
|
|
319
|
-
const meta = modelsDevCatalog.resolve(providerId, modelId);
|
|
320
|
-
const catalogInput = meta?.modalities.input ?? [];
|
|
321
|
-
return {
|
|
322
|
-
contextLimit: meta?.contextLimit ?? null,
|
|
323
|
-
outputLimit: meta?.outputLimit ?? null,
|
|
324
|
-
modalities: {
|
|
325
|
-
input: ["text", "image", ...catalogInput.filter((m) => m !== "text" && m !== "image")],
|
|
326
|
-
output: meta?.modalities.output ?? ["text"],
|
|
327
|
-
},
|
|
328
|
-
reasoning: true,
|
|
329
|
-
toolCall: true,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Folds operator-supplied metadata over a resolved model's metadata.
|
|
335
|
-
*
|
|
336
|
-
* `passthrough` wins over the individual flags: it grants everything, so a
|
|
337
|
-
* caller that cannot know which capabilities a model has does not have to
|
|
338
|
-
* guess. When it is set the per-flag fields are ignored rather than merged —
|
|
339
|
-
* merging would let a stale `image: false` silently revoke what passthrough
|
|
340
|
-
* just granted.
|
|
341
|
-
*/
|
|
342
|
-
export function applyModelMetadataOverride(
|
|
343
|
-
base: ManualModelMetadata,
|
|
344
|
-
override: {
|
|
345
|
-
readonly contextLimit?: number | null;
|
|
346
|
-
readonly outputLimit?: number | null;
|
|
347
|
-
readonly passthrough?: boolean;
|
|
348
|
-
readonly image?: boolean;
|
|
349
|
-
readonly reasoning?: boolean;
|
|
350
|
-
readonly toolCall?: boolean;
|
|
351
|
-
} | undefined,
|
|
352
|
-
): ManualModelMetadata {
|
|
353
|
-
if (override === undefined) return base;
|
|
354
|
-
const passthrough = override.passthrough === true;
|
|
355
|
-
const image = passthrough ? true : override.image;
|
|
356
|
-
const reasoning = passthrough ? true : override.reasoning;
|
|
357
|
-
const toolCall = passthrough ? true : override.toolCall;
|
|
358
|
-
return {
|
|
359
|
-
contextLimit: override.contextLimit ?? base.contextLimit,
|
|
360
|
-
outputLimit: override.outputLimit ?? base.outputLimit,
|
|
361
|
-
modalities: {
|
|
362
|
-
...base.modalities,
|
|
363
|
-
input:
|
|
364
|
-
image === undefined
|
|
365
|
-
? base.modalities.input
|
|
366
|
-
: image
|
|
367
|
-
? Array.from(new Set([...base.modalities.input, "image"]))
|
|
368
|
-
: base.modalities.input.filter((m) => m !== "image"),
|
|
369
|
-
},
|
|
370
|
-
reasoning: reasoning ?? base.reasoning,
|
|
371
|
-
toolCall: toolCall ?? base.toolCall,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Canonical model-definition helpers.
|
|
3
|
+
*
|
|
4
|
+
* Every static provider catalog describes the same `ModelDefinition` shape with
|
|
5
|
+
* only a handful of per-provider differences (wire family, endpoint path,
|
|
6
|
+
* vision/reasoning/tool flags, pricing). `defineModel` is the single place that
|
|
7
|
+
* shape is constructed. Catalogs pass only the fields that differ, so a catalog
|
|
8
|
+
* edit cannot silently drift from the shared defaults. Limits and pricing fall
|
|
9
|
+
* back to the prebuilt base catalog; capability does not — see `defineModel`.
|
|
10
|
+
*/
|
|
11
|
+
import type { ServiceKind, WireFamily } from "../protocol/canonical-model";
|
|
12
|
+
import { modelsDevCatalog } from "./discovery/models-dev-catalog";
|
|
13
|
+
|
|
14
|
+
/** Billing metadata for one provider model. */
|
|
15
|
+
export interface ModelCostDefinition {
|
|
16
|
+
readonly input: number | null;
|
|
17
|
+
readonly output: number | null;
|
|
18
|
+
readonly cache_read?: number;
|
|
19
|
+
readonly cache_write?: number;
|
|
20
|
+
readonly pricing_model?: "pay-per-use" | "subscription" | "unknown";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Per-token cost for a plan whose tier is free. Zero is a tier fact owned by
|
|
25
|
+
* the provider (Cline's `free` bucket, mirroring the upstream client's
|
|
26
|
+
* deliberate cost zeroing), not a price copied from the base catalog: the base
|
|
27
|
+
* catalog prices the *underlying* model, which the free tier does not bill.
|
|
28
|
+
*/
|
|
29
|
+
export const FREE_TIER_COST: ModelCostDefinition = {
|
|
30
|
+
input: 0,
|
|
31
|
+
output: 0,
|
|
32
|
+
pricing_model: "pay-per-use",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Canonical static metadata for one provider model. */
|
|
36
|
+
export interface ModelDefinition {
|
|
37
|
+
readonly modelId: string;
|
|
38
|
+
readonly wireFamily: WireFamily;
|
|
39
|
+
/**
|
|
40
|
+
* Which protocol shape this model speaks. Absent means `"llm"` (the canonical
|
|
41
|
+
* chat pipeline). A non-`llm` row is served by its native passthrough route,
|
|
42
|
+
* not by the surface codecs, and `wireFamily`/`endpointPath` describe that
|
|
43
|
+
* native route's transport rather than a chat wire.
|
|
44
|
+
*
|
|
45
|
+
* Optional so the hand-built catalog literals that predate this dimension
|
|
46
|
+
* stay valid unchanged; read it as `definition.serviceKind ?? "llm"`.
|
|
47
|
+
*/
|
|
48
|
+
readonly serviceKind?: ServiceKind;
|
|
49
|
+
readonly endpointPath: string;
|
|
50
|
+
readonly contextLimit: number | null;
|
|
51
|
+
readonly outputLimit: number | null;
|
|
52
|
+
readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
|
|
53
|
+
readonly reasoning: boolean;
|
|
54
|
+
/** Explicitly supported reasoning effort scale (e.g. `["minimal", "low", "medium", "high", "xhigh"]`). */
|
|
55
|
+
readonly reasoningEfforts?: readonly string[];
|
|
56
|
+
readonly toolCall: boolean;
|
|
57
|
+
readonly cost: ModelCostDefinition;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the provider serves this row on a free plan tier.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately not the same thing as `free` on the input row. `free` is a
|
|
62
|
+
* *billing* statement — bill zero per token — and a subscription roster also
|
|
63
|
+
* bills zero per token. This is a *catalog* statement: the row belongs to the
|
|
64
|
+
* tier the provider publishes as free. Discovery has to tell them apart
|
|
65
|
+
* because the free tier is a distinct group in the model list, and `cost`
|
|
66
|
+
* cannot answer it — a reseller may legitimately price a model at `$0.00`
|
|
67
|
+
* without it being the provider's free tier.
|
|
68
|
+
*
|
|
69
|
+
* Optional, and absence is the answer "not a free-tier row": the field is a
|
|
70
|
+
* marker only a discovery module that reads a provider's free tier can set,
|
|
71
|
+
* so a hand-built definition correctly says nothing. Read it as `=== true`.
|
|
72
|
+
*/
|
|
73
|
+
readonly freeTier?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the provider refuses this row on a free plan.
|
|
76
|
+
*
|
|
77
|
+
* The opposite statement to `freeTier`, and not its absence: `freeTier` says
|
|
78
|
+
* "served on the free tier", while this says "refused there". A row neither
|
|
79
|
+
* marked is simply one whose tier nobody established — which is most rows,
|
|
80
|
+
* and must not be rendered as "free".
|
|
81
|
+
*
|
|
82
|
+
* CodeBuddy is why both exist. Its console directory publishes no field that
|
|
83
|
+
* predicts access: `credits` and `vendor` were both measured against real
|
|
84
|
+
* dispatches and neither correlates — `kimi-k3` bills `x1.62` and is served
|
|
85
|
+
* on a free account while `gpt-5.6-luna` bills `x0.14` and is refused. The
|
|
86
|
+
* only signal is the refusal itself: `11158 "model [X] is not allowed for
|
|
87
|
+
* user type [personal_free_user]"`. See
|
|
88
|
+
* `.skills/cartethyia-reverse-engineering/references/codebuddy-dispatch-headers.md`.
|
|
89
|
+
*
|
|
90
|
+
* Optional, and absence is "tier unknown" rather than "served on free". Read
|
|
91
|
+
* it as `=== true`; a UI that shows a tier must treat undefined as unknown,
|
|
92
|
+
* not as the permissive answer.
|
|
93
|
+
*/
|
|
94
|
+
readonly proOnly?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* True when neither the row nor the catalog could state this model's limits,
|
|
97
|
+
* so `contextLimit`/`outputLimit` are the shared fallback rather than
|
|
98
|
+
* anything measured.
|
|
99
|
+
*
|
|
100
|
+
* A model nobody has filed — a brand-new id the catalog has not caught up
|
|
101
|
+
* with — must still be routable, and a hard number is what routing needs to
|
|
102
|
+
* size a request. But publishing that number as if it were the model's own
|
|
103
|
+
* turns a gap in our catalog into a claim about someone else's model.
|
|
104
|
+
*/
|
|
105
|
+
readonly limitsEstimated?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Limits assumed when neither the catalog nor the operator supplies any.
|
|
110
|
+
*
|
|
111
|
+
* Deliberately generous: a context ceiling that is too *low* truncates a
|
|
112
|
+
* working request, while one that is too high only means the upstream rejects
|
|
113
|
+
* the call on its own terms. Chosen as a round number above every current
|
|
114
|
+
* frontier model so the fallback is never the binding constraint.
|
|
115
|
+
*
|
|
116
|
+
* A row carrying these because nothing filed the model is flagged as estimated,
|
|
117
|
+
* so the UI can say the number is a guess rather than presenting it as a fact
|
|
118
|
+
* about the model.
|
|
119
|
+
*/
|
|
120
|
+
export const FALLBACK_CONTEXT_LIMIT = 512_000;
|
|
121
|
+
export const FALLBACK_OUTPUT_LIMIT = 131_072;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Limits assumed for a model the catalog has never filed.
|
|
125
|
+
*
|
|
126
|
+
* Fit comfortably rather than tightly: a fallback that is too small silently
|
|
127
|
+
* truncates a request that would have been fine, while one that is too large
|
|
128
|
+
* only costs an admission estimate no upstream enforces. The two spellings above
|
|
129
|
+
* are one number, so a row rendered in the console and a row routed through
|
|
130
|
+
* discovery cannot disagree about what "unfiled" means.
|
|
131
|
+
*/
|
|
132
|
+
const UNFILED_CONTEXT_LIMIT = FALLBACK_CONTEXT_LIMIT;
|
|
133
|
+
const UNFILED_OUTPUT_LIMIT = FALLBACK_OUTPUT_LIMIT;
|
|
134
|
+
|
|
135
|
+
/** Fields accepted by `defineModel`; every field except `id` falls back to the shared default. */
|
|
136
|
+
export interface ModelHelperRow {
|
|
137
|
+
readonly id: string;
|
|
138
|
+
/**
|
|
139
|
+
* Canonical provider id this row is served by. Optional only so shared
|
|
140
|
+
* helper rows can omit it; supplying it is what makes base-catalog limits
|
|
141
|
+
* and pricing resolve to the right provider's entry.
|
|
142
|
+
*/
|
|
143
|
+
readonly providerId?: string;
|
|
144
|
+
/** Defaults to `"chat"`. */
|
|
145
|
+
readonly wireFamily?: ModelDefinition["wireFamily"];
|
|
146
|
+
/** Defaults to `"llm"`. A non-`llm` row is served by its native route. */
|
|
147
|
+
readonly serviceKind?: ServiceKind;
|
|
148
|
+
/** Defaults to the family's conventional path (`/chat/completions`, `/responses`, `/messages`). */
|
|
149
|
+
readonly endpoint?: string;
|
|
150
|
+
/** Defaults to 128k. */
|
|
151
|
+
readonly ctx?: number | null;
|
|
152
|
+
/** Defaults to 8k. */
|
|
153
|
+
readonly out?: number | null;
|
|
154
|
+
readonly vision?: boolean;
|
|
155
|
+
/** Accepts PDF/plain-file input parts (`document`/`file` canonical kinds). */
|
|
156
|
+
readonly document?: boolean;
|
|
157
|
+
/** Accepts audio input parts. */
|
|
158
|
+
readonly audio?: boolean;
|
|
159
|
+
readonly reasoning?: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Output modalities this row produces. Defaults to `["text"]`.
|
|
162
|
+
*
|
|
163
|
+
* A generation row sets `["image"]` or `["video"]`: the capability profile's
|
|
164
|
+
* `mediaGeneration` flag reads `output.includes("image")`, and before this
|
|
165
|
+
* field existed every catalog row was hardcoded to text, so no static row
|
|
166
|
+
* could ever declare that it returns an image.
|
|
167
|
+
*/
|
|
168
|
+
readonly output?: readonly ModelDefinition["modalities"]["output"][number][];
|
|
169
|
+
/** Explicitly supported reasoning effort scale. */
|
|
170
|
+
readonly reasoningEfforts?: readonly string[];
|
|
171
|
+
readonly toolCall?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Marks a row served by a free plan tier. Such rows bill zero per token
|
|
174
|
+
* regardless of what the base catalog charges for the underlying model.
|
|
175
|
+
*/
|
|
176
|
+
readonly free?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Marks a row that belongs to the tier the provider publishes as free.
|
|
179
|
+
*
|
|
180
|
+
* Separate from `free` because the two answer different questions: a
|
|
181
|
+
* subscription roster bills zero per token (`free`) without being the free
|
|
182
|
+
* tier. This one is what the model list groups on, so a provider's free-tier
|
|
183
|
+
* discovery can be labelled as such instead of mixed into ordinary fetched
|
|
184
|
+
* rows. Defaults to false — a row nobody marked is not a free-tier row.
|
|
185
|
+
*/
|
|
186
|
+
readonly freeTier?: boolean;
|
|
187
|
+
/** Whether the provider refuses this row on a free plan. See `ModelDefinition.proOnly`. */
|
|
188
|
+
readonly proOnly?: boolean;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function defaultEndpoint(wireFamily: ModelDefinition["wireFamily"]): string {
|
|
192
|
+
if (wireFamily === "chat") return "/chat/completions";
|
|
193
|
+
if (wireFamily === "responses") return "/responses";
|
|
194
|
+
return "/messages";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Model ids whose families speak native OpenAI Responses on every gateway that
|
|
199
|
+
* lists them (GPT-5/6, o3/o4). Verified live: `gpt-5.6-terra` answers on
|
|
200
|
+
* `/v1/responses` in ~2s with its gateway routing prefix intact, and the
|
|
201
|
+
* codex/openai static catalogs already carry these families as responses-wire.
|
|
202
|
+
*
|
|
203
|
+
* Both the static inferhub catalog and the tolerant `/models` discovery path
|
|
204
|
+
* classify wire family from the raw id, so the rule is stated once here: a
|
|
205
|
+
* gateway that lists one of these families must not pin it to the chat wire.
|
|
206
|
+
*/
|
|
207
|
+
export function isResponsesNativeModelId(modelId: string): boolean {
|
|
208
|
+
const id = modelId.toLowerCase();
|
|
209
|
+
return id.includes("gpt-5") || id.includes("gpt-6") || id.includes("/o3") || id.includes("/o4");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Builds one static catalog row. No empty-id guard: the Anthropic and Claude
|
|
214
|
+
* catalogs intentionally ship empty-id rows, so validation here would break
|
|
215
|
+
* module load.
|
|
216
|
+
*
|
|
217
|
+
* Capability is declared per row and never read from the base catalog. The base
|
|
218
|
+
* catalog is keyed by bare model id, and a bare id maps to many provider rows
|
|
219
|
+
* where only the first survives (`models-dev-catalog.ts`), so a capability read
|
|
220
|
+
* from it can describe a different provider's serving of the same model. What a
|
|
221
|
+
* row does not declare is left to the routing ladder's filter → degrade path
|
|
222
|
+
* rather than guessed from a stranger's metadata.
|
|
223
|
+
*/
|
|
224
|
+
export function defineModel(row: ModelHelperRow): ModelDefinition {
|
|
225
|
+
const wireFamily = row.wireFamily ?? "chat";
|
|
226
|
+
// `row.providerId` names the provider whose catalog this row belongs to, so
|
|
227
|
+
// limits and cost come from that provider's own base-catalog entry. Without
|
|
228
|
+
// it the lookup falls back to the bare id, which returns `undefined` when the
|
|
229
|
+
// catalog disagrees about that id (428 bare keys disagree on context alone).
|
|
230
|
+
const fallback = row.id ? modelsDevCatalog.resolve(row.providerId ?? "", row.id) : undefined;
|
|
231
|
+
// A provider the catalog does not file under our id (or files nowhere at
|
|
232
|
+
// all) leaves `resolve` undefined, which would fall through to the fixed
|
|
233
|
+
// default below. The majority vote is the honest middle tier: the model is
|
|
234
|
+
// known, the catalog merely disagrees about it, and the most-agreed row
|
|
235
|
+
// describes the model itself rather than one reseller's serving. Only the
|
|
236
|
+
// field the vote actually answered is taken; a model nobody records stays
|
|
237
|
+
// at the default.
|
|
238
|
+
const majority = fallback === undefined && row.id ? modelsDevCatalog.majorityFor(row.id) : undefined;
|
|
239
|
+
|
|
240
|
+
// Nothing filed this model: not the row, not the provider's catalog entry,
|
|
241
|
+
// not a majority across the catalog's other resellers. The fallback keeps it
|
|
242
|
+
// routable, and the marker keeps the number honest.
|
|
243
|
+
const limitsEstimated = row.ctx === undefined && fallback?.contextLimit === undefined && majority?.contextLimit === undefined;
|
|
244
|
+
|
|
245
|
+
const contextLimit =
|
|
246
|
+
row.ctx !== undefined
|
|
247
|
+
? row.ctx
|
|
248
|
+
: (fallback?.contextLimit ?? majority?.contextLimit ?? UNFILED_CONTEXT_LIMIT);
|
|
249
|
+
|
|
250
|
+
const declaredOutput =
|
|
251
|
+
row.out !== undefined
|
|
252
|
+
? row.out
|
|
253
|
+
: (fallback?.outputLimit ?? majority?.outputLimit ?? UNFILED_OUTPUT_LIMIT);
|
|
254
|
+
|
|
255
|
+
// A maximum output above the context window is unsatisfiable: no request can
|
|
256
|
+
// reserve more output tokens than the model accepts as input. The base
|
|
257
|
+
// catalog carries 1084 rows that state output === context and 69 that state
|
|
258
|
+
// output > context (a Kimi K2.6 row is filed as 262144/262144), so an
|
|
259
|
+
// unclamped cap would publish a limit the provider rejects and make the
|
|
260
|
+
// admission estimate reserve the whole window as output headroom.
|
|
261
|
+
const outputLimit =
|
|
262
|
+
contextLimit === null || declaredOutput === null
|
|
263
|
+
? declaredOutput
|
|
264
|
+
: Math.min(declaredOutput, contextLimit);
|
|
265
|
+
|
|
266
|
+
const cost = row.free ? FREE_TIER_COST : modelsDevCatalog.costFor(row.providerId ?? "", row.id);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
modelId: row.id,
|
|
270
|
+
wireFamily,
|
|
271
|
+
serviceKind: row.serviceKind ?? "llm",
|
|
272
|
+
endpointPath: row.endpoint ?? defaultEndpoint(wireFamily),
|
|
273
|
+
contextLimit,
|
|
274
|
+
outputLimit,
|
|
275
|
+
modalities: {
|
|
276
|
+
input: [
|
|
277
|
+
"text",
|
|
278
|
+
...(row.vision ? ["image"] : []),
|
|
279
|
+
...(row.document ? ["document"] : []),
|
|
280
|
+
...(row.audio ? ["audio"] : []),
|
|
281
|
+
],
|
|
282
|
+
output: row.output ?? ["text"],
|
|
283
|
+
},
|
|
284
|
+
reasoning: row.reasoning ?? false,
|
|
285
|
+
...(row.reasoningEfforts !== undefined ? { reasoningEfforts: row.reasoningEfforts } : {}),
|
|
286
|
+
toolCall: row.toolCall ?? true,
|
|
287
|
+
cost,
|
|
288
|
+
...(row.freeTier === undefined ? {} : { freeTier: row.freeTier }),
|
|
289
|
+
...(row.proOnly === undefined ? {} : { proOnly: row.proOnly }),
|
|
290
|
+
...(limitsEstimated ? { limitsEstimated: true } : {}),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Capability metadata persisted for one manually registered model id. */
|
|
295
|
+
export interface ManualModelMetadata {
|
|
296
|
+
readonly contextLimit: number | null;
|
|
297
|
+
readonly outputLimit: number | null;
|
|
298
|
+
readonly modalities: { readonly input: readonly string[]; readonly output: readonly string[] };
|
|
299
|
+
readonly reasoning: boolean;
|
|
300
|
+
readonly toolCall: boolean;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Resolves capability metadata for a manually registered model id.
|
|
305
|
+
*
|
|
306
|
+
* A manual add performs no upstream probe and has no static catalog row. Its
|
|
307
|
+
* capabilities are therefore declared up front rather than inferred from a
|
|
308
|
+
* `defineModel(...)` entry: tools, vision, and reasoning are always enabled. A
|
|
309
|
+
* false negative here is not cosmetic — capability preflight reads `toolCall`
|
|
310
|
+
* and silently strips `tools` from the request, so a manually added model
|
|
311
|
+
* would appear unable to call tools at all. The prebuilt base catalog still
|
|
312
|
+
* supplies the non-capability facts (context/output limits, cost) when it
|
|
313
|
+
* knows the id.
|
|
314
|
+
*/
|
|
315
|
+
export function resolveManualModelMetadata(
|
|
316
|
+
providerId: string,
|
|
317
|
+
modelId: string,
|
|
318
|
+
): ManualModelMetadata {
|
|
319
|
+
const meta = modelsDevCatalog.resolve(providerId, modelId);
|
|
320
|
+
const catalogInput = meta?.modalities.input ?? [];
|
|
321
|
+
return {
|
|
322
|
+
contextLimit: meta?.contextLimit ?? null,
|
|
323
|
+
outputLimit: meta?.outputLimit ?? null,
|
|
324
|
+
modalities: {
|
|
325
|
+
input: ["text", "image", ...catalogInput.filter((m) => m !== "text" && m !== "image")],
|
|
326
|
+
output: meta?.modalities.output ?? ["text"],
|
|
327
|
+
},
|
|
328
|
+
reasoning: true,
|
|
329
|
+
toolCall: true,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Folds operator-supplied metadata over a resolved model's metadata.
|
|
335
|
+
*
|
|
336
|
+
* `passthrough` wins over the individual flags: it grants everything, so a
|
|
337
|
+
* caller that cannot know which capabilities a model has does not have to
|
|
338
|
+
* guess. When it is set the per-flag fields are ignored rather than merged —
|
|
339
|
+
* merging would let a stale `image: false` silently revoke what passthrough
|
|
340
|
+
* just granted.
|
|
341
|
+
*/
|
|
342
|
+
export function applyModelMetadataOverride(
|
|
343
|
+
base: ManualModelMetadata,
|
|
344
|
+
override: {
|
|
345
|
+
readonly contextLimit?: number | null;
|
|
346
|
+
readonly outputLimit?: number | null;
|
|
347
|
+
readonly passthrough?: boolean;
|
|
348
|
+
readonly image?: boolean;
|
|
349
|
+
readonly reasoning?: boolean;
|
|
350
|
+
readonly toolCall?: boolean;
|
|
351
|
+
} | undefined,
|
|
352
|
+
): ManualModelMetadata {
|
|
353
|
+
if (override === undefined) return base;
|
|
354
|
+
const passthrough = override.passthrough === true;
|
|
355
|
+
const image = passthrough ? true : override.image;
|
|
356
|
+
const reasoning = passthrough ? true : override.reasoning;
|
|
357
|
+
const toolCall = passthrough ? true : override.toolCall;
|
|
358
|
+
return {
|
|
359
|
+
contextLimit: override.contextLimit ?? base.contextLimit,
|
|
360
|
+
outputLimit: override.outputLimit ?? base.outputLimit,
|
|
361
|
+
modalities: {
|
|
362
|
+
...base.modalities,
|
|
363
|
+
input:
|
|
364
|
+
image === undefined
|
|
365
|
+
? base.modalities.input
|
|
366
|
+
: image
|
|
367
|
+
? Array.from(new Set([...base.modalities.input, "image"]))
|
|
368
|
+
: base.modalities.input.filter((m) => m !== "image"),
|
|
369
|
+
},
|
|
370
|
+
reasoning: reasoning ?? base.reasoning,
|
|
371
|
+
toolCall: toolCall ?? base.toolCall,
|
|
372
|
+
};
|
|
373
|
+
}
|