cartethyia 2.5.4 → 2.5.6
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 +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-mUfcknqm.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-uPiGVTTK.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-js6TJvdr.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-WUCXorXw.js → select-BYR_5K2c.js} +1 -1
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.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 +54 -54
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +61 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/{command/update.ts → update.ts} +39 -39
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +697 -693
- package/src/boot/dependencies.ts +647 -646
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +20 -30
- package/src/config/telemetry.ts +0 -20
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/data-management/contracts.ts +13 -6
- package/src/console/data-management/operations.ts +23 -18
- package/src/console/data-management/routes.ts +0 -1
- package/src/console/domain-registration.ts +10 -5
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +39 -16
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +4 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/store.ts +1553 -1553
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +58 -2
- package/src/console/settings/store.ts +30 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +80 -78
- package/src/observability/event-day-index.ts +13 -15
- package/src/observability/event-log.ts +4 -4
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +151 -33
- package/src/observability/payload-store.ts +397 -440
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +1406 -1402
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +236 -237
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +138 -4
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/configured-openai-providers.ts +134 -136
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +132 -132
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
- package/src/providers/integrations/cursor/cursor.ts +11 -163
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +8 -8
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +1528 -1523
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-metadata.ts +4 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +504 -504
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +5 -6
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +2 -2
- package/src/transport/request/state.ts +12 -8
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +8 -1
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
- package/dist/dashboard/assets/app-COlK95oi.css +0 -2
- package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
- package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
- package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
- package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
- package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
- package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
- package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
- package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
- package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
- package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
- package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
- package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
- package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
- package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
- package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
- package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
- package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
- package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
- package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
- package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
|
@@ -1,539 +1,537 @@
|
|
|
1
|
-
import { createApiKeyAdapter } from "../../configured-provider";
|
|
2
|
-
import type { ProviderDispatchTarget, ProviderAdapter, ProviderDispatchContext } from "../../provider-contracts";
|
|
3
|
-
import type { ModelDefinition } from "../../provider-model-definition";
|
|
4
|
-
import { unwrapProviderToken } from "../../provider-credential-envelope";
|
|
5
|
-
import { GatewayError } from "../../../protocol/gateway-error";
|
|
6
|
-
import type { CanonicalRequest } from "../../../protocol/canonical-model";
|
|
7
|
-
import { completeRequiredSchema, isRecord } from "../../../protocol/codecs";
|
|
8
|
-
import { providerBaseUrl } from "../../provider-metadata";
|
|
9
|
-
import { getCachedModelDiscovery } from "../../operations/model-discovery-cache";
|
|
10
|
-
import { defineModel } from "../../provider-model-definition";
|
|
11
|
-
import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
|
|
12
|
-
import { clinePlatform, clinePlatformVersion } from "../../operations/cli-platform";
|
|
13
|
-
import {
|
|
14
|
-
getClineCliVersion,
|
|
15
|
-
getClineSdkVersion,
|
|
16
|
-
} from "../../operations/client-versions";
|
|
17
|
-
export const CLINE_BASE_URL = providerBaseUrl("cline");
|
|
18
|
-
export const CLINE_PROVIDER_ID = "cline" as const;
|
|
19
|
-
const CLINE_CHAT_PATH = "/chat/completions" as const;
|
|
20
|
-
const CLINE_RECOMMENDED_MODELS_PATH = "/ai/cline/recommended-models" as const;
|
|
21
|
-
/** The OpenAI-style listing; its `:free` suffix is a second free-tier roster. */
|
|
22
|
-
const CLINE_MODELS_LIST_PATH = "/models" as const;
|
|
23
|
-
/**
|
|
24
|
-
* Cline's own model catalog. Unlike the roster above it states `context_length`
|
|
25
|
-
* and `top_provider.max_completion_tokens`, so it is the authority for limits.
|
|
26
|
-
*/
|
|
27
|
-
const CLINE_MODELS_CATALOG_PATH = "/ai/cline/models" as const;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export function workosToken(token: string): string {
|
|
32
|
-
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Cartethyia expresses this as a per-dispatch buildExtraHeaders hook so every
|
|
36
|
-
// request gets fresh client-version headers.
|
|
37
|
-
// API-key credentials use direct Bearer format (no workos prefix);
|
|
38
|
-
// OAuth and scoped credentials retain the required workos: prefix.
|
|
39
|
-
|
|
40
|
-
async function clineExtraHeaders(context: ProviderDispatchContext): Promise<Record<string, string>> {
|
|
41
|
-
let authOverride: Record<string, string> = {};
|
|
42
|
-
const secret = context.credential.secret;
|
|
43
|
-
if (secret && secret.length > 0) {
|
|
44
|
-
const raw = new TextDecoder().decode(secret).trim();
|
|
45
|
-
if (raw.length > 0) {
|
|
46
|
-
// Bearer-strip delegates to the single token-envelope implementation.
|
|
47
|
-
const normalized = new TextDecoder().decode(unwrapProviderToken(raw, "bearer"));
|
|
48
|
-
const kind = context.credential.credential_kind;
|
|
49
|
-
const token = kind === "api_key" ? normalized : workosToken(normalized);
|
|
50
|
-
authOverride = { authorization: `Bearer ${token}` };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
// The chat surface claims the CLI identity, matching the billing surface in
|
|
54
|
-
// `cline-quota.ts` and the reference client: `cline` (npm, bin `cline`) is a
|
|
55
|
-
// separate release line from the VS Code extension, so the client version
|
|
56
|
-
// must come from the CLI package, not the extension manifest. `@cline/sdk`
|
|
57
|
-
// is a dependency of the CLI, so its version stays the core version.
|
|
58
|
-
const clientVersion = getClineCliVersion();
|
|
59
|
-
const sdkVersion = getClineSdkVersion();
|
|
60
|
-
// Cline's own client sends `X-Task-ID` on inference calls as the stable
|
|
61
|
-
// prompt-cache/session key, and omits it on account and discovery calls
|
|
62
|
-
// rather than inventing a per-request identity. `conversation_affinity` is
|
|
63
|
-
// that same value here: stable across the retries of one conversation, so a
|
|
64
|
-
// retried dispatch keeps its cache entry instead of starting a new one.
|
|
65
|
-
const taskId = context.conversation_affinity;
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
...authOverride,
|
|
69
|
-
"user-agent": `Cline/${clientVersion}`,
|
|
70
|
-
// One spelling only. Header names are case-insensitive, so sending both
|
|
71
|
-
// `http-referer` and `HTTP-Referer` does not hedge against a case-sensitive
|
|
72
|
-
// check — it makes `Headers` merge them into the single value
|
|
73
|
-
// `https://cline.bot, https://cline.bot`.
|
|
74
|
-
"HTTP-Referer": "https://cline.bot",
|
|
75
|
-
"x-platform": clinePlatform(),
|
|
76
|
-
"x-platform-version": clinePlatformVersion(),
|
|
77
|
-
"x-title": "Cline",
|
|
78
|
-
"x-client-type": "cline-cli",
|
|
79
|
-
"x-client-version": clientVersion,
|
|
80
|
-
"x-core-version": sdkVersion,
|
|
81
|
-
"X-Title": "Cline",
|
|
82
|
-
"x-is-multi-root": "false",
|
|
83
|
-
...(taskId ? { "x-task-id": taskId } : {}),
|
|
84
|
-
accept: "text/event-stream, application/json",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// System-prompt injection and materialized-stream handling.
|
|
89
|
-
// Cline requires a system/developer message for compatibility; its upstream
|
|
90
|
-
// free models also reject empty system content, so the fallback is concise.
|
|
91
|
-
|
|
92
|
-
function completeClineToolSchemas(payload: Record<string, unknown>): void {
|
|
93
|
-
if (!Array.isArray(payload.tools)) return;
|
|
94
|
-
payload.tools = payload.tools.map((tool) => {
|
|
95
|
-
if (!isRecord(tool)) return tool;
|
|
96
|
-
const functionValue = tool.function;
|
|
97
|
-
if (isRecord(functionValue) && "parameters" in functionValue) {
|
|
98
|
-
return {
|
|
99
|
-
...tool,
|
|
100
|
-
function: { ...functionValue, parameters: completeRequiredSchema(functionValue.parameters) },
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if ("parameters" in tool) return { ...tool, parameters: completeRequiredSchema(tool.parameters) };
|
|
104
|
-
return tool;
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function clinePrePayload(
|
|
109
|
-
payload: Record<string, unknown>,
|
|
110
|
-
request: CanonicalRequest,
|
|
111
|
-
candidate: ProviderDispatchTarget,
|
|
112
|
-
): void {
|
|
113
|
-
completeClineToolSchemas(payload);
|
|
114
|
-
// 1) Ensure system/developer messages always carry non-empty content.
|
|
115
|
-
const rawMessages = payload["messages"];
|
|
116
|
-
const messages: Record<string, unknown>[] = Array.isArray(rawMessages)
|
|
117
|
-
? rawMessages.filter(isRecord).map((message) => {
|
|
118
|
-
const role = message["role"];
|
|
119
|
-
const content = message["content"];
|
|
120
|
-
const emptyContent =
|
|
121
|
-
content === undefined ||
|
|
122
|
-
content === null ||
|
|
123
|
-
(typeof content === "string" && content.trim().length === 0) ||
|
|
124
|
-
(Array.isArray(content) && content.length === 0);
|
|
125
|
-
return (role === "system" || role === "developer") && emptyContent
|
|
126
|
-
? { ...message, content: "You are a helpful assistant." }
|
|
127
|
-
: message;
|
|
128
|
-
})
|
|
129
|
-
: [];
|
|
130
|
-
const hasSystem = messages.some(
|
|
131
|
-
(message) => message["role"] === "system" || message["role"] === "developer",
|
|
132
|
-
);
|
|
133
|
-
payload["messages"] = hasSystem
|
|
134
|
-
? messages
|
|
135
|
-
: [{ role: "system", content: "You are a helpful assistant." }, ...messages];
|
|
136
|
-
|
|
137
|
-
// 2) Materialize deepseek-v4-flash non-stream quirk
|
|
138
|
-
const model = typeof payload["model"] === "string" ? (payload["model"] as string) : candidate.model_id;
|
|
139
|
-
const needsMaterialize = !request.stream && model.includes("deepseek-v4-flash");
|
|
140
|
-
if (needsMaterialize) {
|
|
141
|
-
payload["stream"] = true;
|
|
142
|
-
// Set reasoning_effort to none for this model to suppress reasoning streaming
|
|
143
|
-
payload["reasoning_effort"] = "none";
|
|
144
|
-
const cur = typeof payload["max_tokens"] === "number" ? (payload["max_tokens"] as number) : 0;
|
|
145
|
-
payload["max_tokens"] = Math.max(cur, 256);
|
|
146
|
-
// keep GatewayError reachable
|
|
147
|
-
if (!model) throw new GatewayError("invalid_request", 400, "Cline model is required");
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// (modelEntries / recommendedModels / fetchRecommendedModels) preserved
|
|
152
|
-
// as Cartethyia discovery helpers. The factory's discovery endpoint is wired
|
|
153
|
-
// to /ai/cline/recommended-models; these helpers can be used by a custom
|
|
154
|
-
// discovery layer or tests. Keeping them guarantees the custom tag→vision
|
|
155
|
-
// logic and clinePass vs free branching are not lost in the port.
|
|
156
|
-
|
|
157
|
-
interface ClineRecommendedModel {
|
|
158
|
-
readonly id?: unknown;
|
|
159
|
-
readonly name?: unknown;
|
|
160
|
-
readonly description?: unknown;
|
|
161
|
-
readonly tags?: unknown;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
interface ClineRecommendedPayload {
|
|
165
|
-
readonly recommended?: unknown;
|
|
166
|
-
readonly free?: unknown;
|
|
167
|
-
readonly clinePass?: unknown;
|
|
168
|
-
readonly clineCloud?: unknown;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function modelEntries(value: unknown): readonly ClineRecommendedModel[] {
|
|
172
|
-
return Array.isArray(value)
|
|
173
|
-
? value.filter((entry): entry is ClineRecommendedModel => typeof entry === "object" && entry !== null && !Array.isArray(entry))
|
|
174
|
-
: [];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function recommendedModels(
|
|
178
|
-
value: unknown,
|
|
179
|
-
pass: boolean,
|
|
180
|
-
upstream: ReadonlyMap<string, ClineUpstreamLimits> = new Map(),
|
|
181
|
-
freeIds: readonly string[] | null = null,
|
|
182
|
-
): readonly ModelDefinition[] {
|
|
183
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
|
|
184
|
-
const payload = value as ClineRecommendedPayload;
|
|
185
|
-
// The endpoint publishes four buckets: `recommended` (the current picks),
|
|
186
|
-
// `free` (the zero-cost tier), `clinePass` (the subscription roster) and
|
|
187
|
-
// `clineCloud` (cloud-routed). A pass-enabled account may use every one of
|
|
188
|
-
// them, so all four are read; a free account sees the free tier only. Reading
|
|
189
|
-
// just `free`/`clinePass` silently dropped `recommended` and `clineCloud`
|
|
190
|
-
// from the catalog entirely.
|
|
191
|
-
//
|
|
192
|
-
// The third element marks the zero-cost tier — the `free` bucket alone, not
|
|
193
|
-
// `recommended`: a pass account receives `recommended` too, so calling it a
|
|
194
|
-
// free-tier row would mislabel a subscription catalog entry. The tier is what
|
|
195
|
-
// separates a "Free models (auto)" row from an ordinary fetched one.
|
|
196
|
-
//
|
|
197
|
-
// Membership of the bucket *is* the rule, deliberately not the id prefix. The
|
|
198
|
-
// `free` bucket's ids are mostly `cline-free/…`, but the prefix does not
|
|
199
|
-
// define the tier in either direction: a free id need not carry it (the
|
|
200
|
-
// catalog serves `deepseek/deepseek-v4-flash` and `z-ai/glm-5.3-flash` on the
|
|
201
|
-
// free tier with no prefix at all), and carrying it proves nothing on its own.
|
|
202
|
-
// A prefix test would therefore both miss served models and admit retired
|
|
203
|
-
// ones, so the bucket the endpoint already sorted them into is the authority.
|
|
204
|
-
const buckets: ReadonlyArray<readonly [unknown, boolean, boolean]> = pass
|
|
205
|
-
? [
|
|
206
|
-
[payload.recommended, false, false],
|
|
207
|
-
[payload.free, false, true],
|
|
208
|
-
[payload.clinePass, true, false],
|
|
209
|
-
[payload.clineCloud, true, false],
|
|
210
|
-
]
|
|
211
|
-
: [
|
|
212
|
-
[payload.recommended, false, false],
|
|
213
|
-
[payload.free, false, true],
|
|
214
|
-
];
|
|
215
|
-
const seen = new Set<string>();
|
|
216
|
-
const out: ModelDefinition[] = [];
|
|
217
|
-
for (const [bucket, subscription, freeTier] of buckets) {
|
|
218
|
-
for (const entry of modelEntries(bucket)) {
|
|
219
|
-
const rawId = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
220
|
-
if (rawId.length === 0) continue;
|
|
221
|
-
// The endpoint already namespaces its own ids (`cline-pass/…`,
|
|
222
|
-
// `cline-cloud/…`, `cline-free/…`), so the bucket decides only how the
|
|
223
|
-
// row is priced and which catalog entry answers for it — never how the id
|
|
224
|
-
// is spelled. Prefixing here double-namespaced every id the endpoint had
|
|
225
|
-
// already prefixed (`cline-pass/cline-pass/mimo-v2.6-flash`), which no
|
|
226
|
-
// upstream route resolves.
|
|
227
|
-
const id = rawId;
|
|
228
|
-
if (seen.has(id)) continue;
|
|
229
|
-
seen.add(id);
|
|
230
|
-
// Limits, in authority order:
|
|
231
|
-
// 1. Cline's own `/ai/cline/models` catalog (`context_length`,
|
|
232
|
-
// `top_provider.max_completion_tokens`) — the serving provider's
|
|
233
|
-
// answer, and the only source that covers the whole roster.
|
|
234
|
-
// 2. `defineModel`'s fallback, which is the base catalog's exact row
|
|
235
|
-
// when models.dev files the provider (`cline-pass`), else the
|
|
236
|
-
// most-agreed row for the bare id, else the documented default.
|
|
237
|
-
// The recommended-models endpoint itself states no limits (only
|
|
238
|
-
// id/name/description/tags), so a fixed pair here was simply invented:
|
|
239
|
-
// the pass entries are 1M-context, and an output cap above the real
|
|
240
|
-
// window is unsatisfiable.
|
|
241
|
-
const providerId = subscription ? "cline-pass" : "cline";
|
|
242
|
-
const limits = upstreamLimitsFor(upstream, id);
|
|
243
|
-
const declaredVision = tagsOf(entry).some(
|
|
244
|
-
(tag) => tag.includes("vision") || tag.includes("multimodal"),
|
|
245
|
-
);
|
|
246
|
-
out.push(
|
|
247
|
-
defineModel({
|
|
248
|
-
id,
|
|
249
|
-
providerId,
|
|
250
|
-
wireFamily: "chat",
|
|
251
|
-
endpoint: CLINE_CHAT_PATH,
|
|
252
|
-
// The upstream catalog's `input_modalities` is authoritative when it
|
|
253
|
-
// lists the id; the roster's own tags are the fallback signal.
|
|
254
|
-
vision: limits?.vision ?? declaredVision,
|
|
255
|
-
...(limits?.contextLimit === undefined ? {} : { ctx: limits.contextLimit }),
|
|
256
|
-
...(limits?.outputLimit === undefined ? {} : { out: limits.outputLimit }),
|
|
257
|
-
reasoning: true,
|
|
258
|
-
// `free` follows the free bucket, not "not a subscription"
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
// `
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
readonly
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
:
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
id
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
):
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
);
|
|
539
|
-
}
|
|
1
|
+
import { createApiKeyAdapter } from "../../configured-provider";
|
|
2
|
+
import type { ProviderDispatchTarget, ProviderAdapter, ProviderDispatchContext } from "../../provider-contracts";
|
|
3
|
+
import type { ModelDefinition } from "../../provider-model-definition";
|
|
4
|
+
import { unwrapProviderToken } from "../../provider-credential-envelope";
|
|
5
|
+
import { GatewayError } from "../../../protocol/gateway-error";
|
|
6
|
+
import type { CanonicalRequest } from "../../../protocol/canonical-model";
|
|
7
|
+
import { completeRequiredSchema, isRecord } from "../../../protocol/codecs";
|
|
8
|
+
import { providerBaseUrl } from "../../provider-metadata";
|
|
9
|
+
import { getCachedModelDiscovery } from "../../operations/model-discovery-cache";
|
|
10
|
+
import { defineModel } from "../../provider-model-definition";
|
|
11
|
+
import { openrouterFreeRoster } from "../../discovery/openrouter-free-roster";
|
|
12
|
+
import { clinePlatform, clinePlatformVersion } from "../../operations/cli-platform";
|
|
13
|
+
import {
|
|
14
|
+
getClineCliVersion,
|
|
15
|
+
getClineSdkVersion,
|
|
16
|
+
} from "../../operations/client-versions";
|
|
17
|
+
export const CLINE_BASE_URL = providerBaseUrl("cline");
|
|
18
|
+
export const CLINE_PROVIDER_ID = "cline" as const;
|
|
19
|
+
const CLINE_CHAT_PATH = "/chat/completions" as const;
|
|
20
|
+
const CLINE_RECOMMENDED_MODELS_PATH = "/ai/cline/recommended-models" as const;
|
|
21
|
+
/** The OpenAI-style listing; its `:free` suffix is a second free-tier roster. */
|
|
22
|
+
const CLINE_MODELS_LIST_PATH = "/models" as const;
|
|
23
|
+
/**
|
|
24
|
+
* Cline's own model catalog. Unlike the roster above it states `context_length`
|
|
25
|
+
* and `top_provider.max_completion_tokens`, so it is the authority for limits.
|
|
26
|
+
*/
|
|
27
|
+
const CLINE_MODELS_CATALOG_PATH = "/ai/cline/models" as const;
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export function workosToken(token: string): string {
|
|
32
|
+
return token.startsWith("workos:") ? token : `workos:${token}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cartethyia expresses this as a per-dispatch buildExtraHeaders hook so every
|
|
36
|
+
// request gets fresh client-version headers.
|
|
37
|
+
// API-key credentials use direct Bearer format (no workos prefix);
|
|
38
|
+
// OAuth and scoped credentials retain the required workos: prefix.
|
|
39
|
+
|
|
40
|
+
async function clineExtraHeaders(context: ProviderDispatchContext): Promise<Record<string, string>> {
|
|
41
|
+
let authOverride: Record<string, string> = {};
|
|
42
|
+
const secret = context.credential.secret;
|
|
43
|
+
if (secret && secret.length > 0) {
|
|
44
|
+
const raw = new TextDecoder().decode(secret).trim();
|
|
45
|
+
if (raw.length > 0) {
|
|
46
|
+
// Bearer-strip delegates to the single token-envelope implementation.
|
|
47
|
+
const normalized = new TextDecoder().decode(unwrapProviderToken(raw, "bearer"));
|
|
48
|
+
const kind = context.credential.credential_kind;
|
|
49
|
+
const token = kind === "api_key" ? normalized : workosToken(normalized);
|
|
50
|
+
authOverride = { authorization: `Bearer ${token}` };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// The chat surface claims the CLI identity, matching the billing surface in
|
|
54
|
+
// `cline-quota.ts` and the reference client: `cline` (npm, bin `cline`) is a
|
|
55
|
+
// separate release line from the VS Code extension, so the client version
|
|
56
|
+
// must come from the CLI package, not the extension manifest. `@cline/sdk`
|
|
57
|
+
// is a dependency of the CLI, so its version stays the core version.
|
|
58
|
+
const clientVersion = getClineCliVersion();
|
|
59
|
+
const sdkVersion = getClineSdkVersion();
|
|
60
|
+
// Cline's own client sends `X-Task-ID` on inference calls as the stable
|
|
61
|
+
// prompt-cache/session key, and omits it on account and discovery calls
|
|
62
|
+
// rather than inventing a per-request identity. `conversation_affinity` is
|
|
63
|
+
// that same value here: stable across the retries of one conversation, so a
|
|
64
|
+
// retried dispatch keeps its cache entry instead of starting a new one.
|
|
65
|
+
const taskId = context.conversation_affinity;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
...authOverride,
|
|
69
|
+
"user-agent": `Cline/${clientVersion}`,
|
|
70
|
+
// One spelling only. Header names are case-insensitive, so sending both
|
|
71
|
+
// `http-referer` and `HTTP-Referer` does not hedge against a case-sensitive
|
|
72
|
+
// check — it makes `Headers` merge them into the single value
|
|
73
|
+
// `https://cline.bot, https://cline.bot`.
|
|
74
|
+
"HTTP-Referer": "https://cline.bot",
|
|
75
|
+
"x-platform": clinePlatform(),
|
|
76
|
+
"x-platform-version": clinePlatformVersion(),
|
|
77
|
+
"x-title": "Cline",
|
|
78
|
+
"x-client-type": "cline-cli",
|
|
79
|
+
"x-client-version": clientVersion,
|
|
80
|
+
"x-core-version": sdkVersion,
|
|
81
|
+
"X-Title": "Cline",
|
|
82
|
+
"x-is-multi-root": "false",
|
|
83
|
+
...(taskId ? { "x-task-id": taskId } : {}),
|
|
84
|
+
accept: "text/event-stream, application/json",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// System-prompt injection and materialized-stream handling.
|
|
89
|
+
// Cline requires a system/developer message for compatibility; its upstream
|
|
90
|
+
// free models also reject empty system content, so the fallback is concise.
|
|
91
|
+
|
|
92
|
+
function completeClineToolSchemas(payload: Record<string, unknown>): void {
|
|
93
|
+
if (!Array.isArray(payload.tools)) return;
|
|
94
|
+
payload.tools = payload.tools.map((tool) => {
|
|
95
|
+
if (!isRecord(tool)) return tool;
|
|
96
|
+
const functionValue = tool.function;
|
|
97
|
+
if (isRecord(functionValue) && "parameters" in functionValue) {
|
|
98
|
+
return {
|
|
99
|
+
...tool,
|
|
100
|
+
function: { ...functionValue, parameters: completeRequiredSchema(functionValue.parameters) },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if ("parameters" in tool) return { ...tool, parameters: completeRequiredSchema(tool.parameters) };
|
|
104
|
+
return tool;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function clinePrePayload(
|
|
109
|
+
payload: Record<string, unknown>,
|
|
110
|
+
request: CanonicalRequest,
|
|
111
|
+
candidate: ProviderDispatchTarget,
|
|
112
|
+
): void {
|
|
113
|
+
completeClineToolSchemas(payload);
|
|
114
|
+
// 1) Ensure system/developer messages always carry non-empty content.
|
|
115
|
+
const rawMessages = payload["messages"];
|
|
116
|
+
const messages: Record<string, unknown>[] = Array.isArray(rawMessages)
|
|
117
|
+
? rawMessages.filter(isRecord).map((message) => {
|
|
118
|
+
const role = message["role"];
|
|
119
|
+
const content = message["content"];
|
|
120
|
+
const emptyContent =
|
|
121
|
+
content === undefined ||
|
|
122
|
+
content === null ||
|
|
123
|
+
(typeof content === "string" && content.trim().length === 0) ||
|
|
124
|
+
(Array.isArray(content) && content.length === 0);
|
|
125
|
+
return (role === "system" || role === "developer") && emptyContent
|
|
126
|
+
? { ...message, content: "You are a helpful assistant." }
|
|
127
|
+
: message;
|
|
128
|
+
})
|
|
129
|
+
: [];
|
|
130
|
+
const hasSystem = messages.some(
|
|
131
|
+
(message) => message["role"] === "system" || message["role"] === "developer",
|
|
132
|
+
);
|
|
133
|
+
payload["messages"] = hasSystem
|
|
134
|
+
? messages
|
|
135
|
+
: [{ role: "system", content: "You are a helpful assistant." }, ...messages];
|
|
136
|
+
|
|
137
|
+
// 2) Materialize deepseek-v4-flash non-stream quirk
|
|
138
|
+
const model = typeof payload["model"] === "string" ? (payload["model"] as string) : candidate.model_id;
|
|
139
|
+
const needsMaterialize = !request.stream && model.includes("deepseek-v4-flash");
|
|
140
|
+
if (needsMaterialize) {
|
|
141
|
+
payload["stream"] = true;
|
|
142
|
+
// Set reasoning_effort to none for this model to suppress reasoning streaming
|
|
143
|
+
payload["reasoning_effort"] = "none";
|
|
144
|
+
const cur = typeof payload["max_tokens"] === "number" ? (payload["max_tokens"] as number) : 0;
|
|
145
|
+
payload["max_tokens"] = Math.max(cur, 256);
|
|
146
|
+
// keep GatewayError reachable
|
|
147
|
+
if (!model) throw new GatewayError("invalid_request", 400, "Cline model is required");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// (modelEntries / recommendedModels / fetchRecommendedModels) preserved
|
|
152
|
+
// as Cartethyia discovery helpers. The factory's discovery endpoint is wired
|
|
153
|
+
// to /ai/cline/recommended-models; these helpers can be used by a custom
|
|
154
|
+
// discovery layer or tests. Keeping them guarantees the custom tag→vision
|
|
155
|
+
// logic and clinePass vs free branching are not lost in the port.
|
|
156
|
+
|
|
157
|
+
interface ClineRecommendedModel {
|
|
158
|
+
readonly id?: unknown;
|
|
159
|
+
readonly name?: unknown;
|
|
160
|
+
readonly description?: unknown;
|
|
161
|
+
readonly tags?: unknown;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface ClineRecommendedPayload {
|
|
165
|
+
readonly recommended?: unknown;
|
|
166
|
+
readonly free?: unknown;
|
|
167
|
+
readonly clinePass?: unknown;
|
|
168
|
+
readonly clineCloud?: unknown;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function modelEntries(value: unknown): readonly ClineRecommendedModel[] {
|
|
172
|
+
return Array.isArray(value)
|
|
173
|
+
? value.filter((entry): entry is ClineRecommendedModel => typeof entry === "object" && entry !== null && !Array.isArray(entry))
|
|
174
|
+
: [];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function recommendedModels(
|
|
178
|
+
value: unknown,
|
|
179
|
+
pass: boolean,
|
|
180
|
+
upstream: ReadonlyMap<string, ClineUpstreamLimits> = new Map(),
|
|
181
|
+
freeIds: readonly string[] | null = null,
|
|
182
|
+
): readonly ModelDefinition[] {
|
|
183
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
|
|
184
|
+
const payload = value as ClineRecommendedPayload;
|
|
185
|
+
// The endpoint publishes four buckets: `recommended` (the current picks),
|
|
186
|
+
// `free` (the zero-cost tier), `clinePass` (the subscription roster) and
|
|
187
|
+
// `clineCloud` (cloud-routed). A pass-enabled account may use every one of
|
|
188
|
+
// them, so all four are read; a free account sees the free tier only. Reading
|
|
189
|
+
// just `free`/`clinePass` silently dropped `recommended` and `clineCloud`
|
|
190
|
+
// from the catalog entirely.
|
|
191
|
+
//
|
|
192
|
+
// The third element marks the zero-cost tier — the `free` bucket alone, not
|
|
193
|
+
// `recommended`: a pass account receives `recommended` too, so calling it a
|
|
194
|
+
// free-tier row would mislabel a subscription catalog entry. The tier is what
|
|
195
|
+
// separates a "Free models (auto)" row from an ordinary fetched one.
|
|
196
|
+
//
|
|
197
|
+
// Membership of the bucket *is* the rule, deliberately not the id prefix. The
|
|
198
|
+
// `free` bucket's ids are mostly `cline-free/…`, but the prefix does not
|
|
199
|
+
// define the tier in either direction: a free id need not carry it (the
|
|
200
|
+
// catalog serves `deepseek/deepseek-v4-flash` and `z-ai/glm-5.3-flash` on the
|
|
201
|
+
// free tier with no prefix at all), and carrying it proves nothing on its own.
|
|
202
|
+
// A prefix test would therefore both miss served models and admit retired
|
|
203
|
+
// ones, so the bucket the endpoint already sorted them into is the authority.
|
|
204
|
+
const buckets: ReadonlyArray<readonly [unknown, boolean, boolean]> = pass
|
|
205
|
+
? [
|
|
206
|
+
[payload.recommended, false, false],
|
|
207
|
+
[payload.free, false, true],
|
|
208
|
+
[payload.clinePass, true, false],
|
|
209
|
+
[payload.clineCloud, true, false],
|
|
210
|
+
]
|
|
211
|
+
: [
|
|
212
|
+
[payload.recommended, false, false],
|
|
213
|
+
[payload.free, false, true],
|
|
214
|
+
];
|
|
215
|
+
const seen = new Set<string>();
|
|
216
|
+
const out: ModelDefinition[] = [];
|
|
217
|
+
for (const [bucket, subscription, freeTier] of buckets) {
|
|
218
|
+
for (const entry of modelEntries(bucket)) {
|
|
219
|
+
const rawId = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
220
|
+
if (rawId.length === 0) continue;
|
|
221
|
+
// The endpoint already namespaces its own ids (`cline-pass/…`,
|
|
222
|
+
// `cline-cloud/…`, `cline-free/…`), so the bucket decides only how the
|
|
223
|
+
// row is priced and which catalog entry answers for it — never how the id
|
|
224
|
+
// is spelled. Prefixing here double-namespaced every id the endpoint had
|
|
225
|
+
// already prefixed (`cline-pass/cline-pass/mimo-v2.6-flash`), which no
|
|
226
|
+
// upstream route resolves.
|
|
227
|
+
const id = rawId;
|
|
228
|
+
if (seen.has(id)) continue;
|
|
229
|
+
seen.add(id);
|
|
230
|
+
// Limits, in authority order:
|
|
231
|
+
// 1. Cline's own `/ai/cline/models` catalog (`context_length`,
|
|
232
|
+
// `top_provider.max_completion_tokens`) — the serving provider's
|
|
233
|
+
// answer, and the only source that covers the whole roster.
|
|
234
|
+
// 2. `defineModel`'s fallback, which is the base catalog's exact row
|
|
235
|
+
// when models.dev files the provider (`cline-pass`), else the
|
|
236
|
+
// most-agreed row for the bare id, else the documented default.
|
|
237
|
+
// The recommended-models endpoint itself states no limits (only
|
|
238
|
+
// id/name/description/tags), so a fixed pair here was simply invented:
|
|
239
|
+
// the pass entries are 1M-context, and an output cap above the real
|
|
240
|
+
// window is unsatisfiable.
|
|
241
|
+
const providerId = subscription ? "cline-pass" : "cline";
|
|
242
|
+
const limits = upstreamLimitsFor(upstream, id);
|
|
243
|
+
const declaredVision = tagsOf(entry).some(
|
|
244
|
+
(tag) => tag.includes("vision") || tag.includes("multimodal"),
|
|
245
|
+
);
|
|
246
|
+
out.push(
|
|
247
|
+
defineModel({
|
|
248
|
+
id,
|
|
249
|
+
providerId,
|
|
250
|
+
wireFamily: "chat",
|
|
251
|
+
endpoint: CLINE_CHAT_PATH,
|
|
252
|
+
// The upstream catalog's `input_modalities` is authoritative when it
|
|
253
|
+
// lists the id; the roster's own tags are the fallback signal.
|
|
254
|
+
vision: limits?.vision ?? declaredVision,
|
|
255
|
+
...(limits?.contextLimit === undefined ? {} : { ctx: limits.contextLimit }),
|
|
256
|
+
...(limits?.outputLimit === undefined ? {} : { out: limits.outputLimit }),
|
|
257
|
+
reasoning: true,
|
|
258
|
+
// `free` follows the free bucket, not "not a subscription": the
|
|
259
|
+
// `recommended` bucket is billed, and the flag is not cosmetic —
|
|
260
|
+
// `defineModel` prices a `free` row at zero, so the cost surface
|
|
261
|
+
// would report paid models as free. The upstream settles it:
|
|
262
|
+
// dispatching `anthropic/claude-sonnet-5.5` from `recommended` answers
|
|
263
|
+
// 402 `insufficient_credits`, while a `free` bucket id answers 200. A
|
|
264
|
+
// subscription row is also not free — it bills a subscription — so
|
|
265
|
+
// only the free bucket carries the flag.
|
|
266
|
+
free: freeTier,
|
|
267
|
+
freeTier,
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// `/models` names free ids the recommended roster does not, so it is read as
|
|
273
|
+
// a second free-tier source rather than a replacement. An id the roster
|
|
274
|
+
// already defined keeps the roster's row: it carries the endpoint's own
|
|
275
|
+
// pricing and subscription classification, which this listing cannot state.
|
|
276
|
+
for (const id of freeIds ?? []) {
|
|
277
|
+
if (seen.has(id)) continue;
|
|
278
|
+
seen.add(id);
|
|
279
|
+
const limits = upstreamLimitsFor(upstream, id);
|
|
280
|
+
out.push(
|
|
281
|
+
defineModel({
|
|
282
|
+
id,
|
|
283
|
+
providerId: "cline",
|
|
284
|
+
wireFamily: "chat",
|
|
285
|
+
endpoint: CLINE_CHAT_PATH,
|
|
286
|
+
vision: limits?.vision ?? false,
|
|
287
|
+
...(limits?.contextLimit === undefined ? {} : { ctx: limits.contextLimit }),
|
|
288
|
+
...(limits?.outputLimit === undefined ? {} : { out: limits.outputLimit }),
|
|
289
|
+
reasoning: true,
|
|
290
|
+
// The suffix is the provider's own free-tier spelling, so both
|
|
291
|
+
// statements hold: zero per token, and a row the model list groups
|
|
292
|
+
// under the free tier.
|
|
293
|
+
free: true,
|
|
294
|
+
freeTier: true,
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Upstream limits for one model id, read from Cline's own catalog.
|
|
303
|
+
*
|
|
304
|
+
* The recommended-models roster states no limits at all, but Cline's
|
|
305
|
+
* `/ai/cline/models` catalog does: `context_length` plus
|
|
306
|
+
* `top_provider.max_completion_tokens`, and `architecture.input_modalities`.
|
|
307
|
+
* That is the serving provider's own answer, so it outranks any guess.
|
|
308
|
+
*
|
|
309
|
+
* The two endpoints do not share an id spelling. The roster names a
|
|
310
|
+
* subscription model `cline-pass/kimi-k3` while the catalog files it as
|
|
311
|
+
* `moonshotai/kimi-k3`, so a lookup tries the id as given and then its bare
|
|
312
|
+
* segment — the same bare-id rule `modelsDevCatalog` uses, and safe here
|
|
313
|
+
* because this is one provider's catalog rather than a shared global index.
|
|
314
|
+
*/
|
|
315
|
+
interface ClineUpstreamLimits {
|
|
316
|
+
readonly contextLimit?: number;
|
|
317
|
+
readonly outputLimit?: number;
|
|
318
|
+
readonly vision?: boolean;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function upstreamCatalogIndex(value: unknown): ReadonlyMap<string, ClineUpstreamLimits> {
|
|
322
|
+
const root = isRecord(value) ? value : undefined;
|
|
323
|
+
const rows = Array.isArray(root?.["data"]) ? root["data"] : Array.isArray(value) ? value : [];
|
|
324
|
+
const byId = new Map<string, ClineUpstreamLimits>();
|
|
325
|
+
for (const row of rows) {
|
|
326
|
+
if (!isRecord(row)) continue;
|
|
327
|
+
const id = typeof row["id"] === "string" ? row["id"].trim().toLowerCase() : "";
|
|
328
|
+
if (id.length === 0) continue;
|
|
329
|
+
const contextLimit = positiveCount(row["context_length"]);
|
|
330
|
+
const topProvider = isRecord(row["top_provider"]) ? row["top_provider"] : undefined;
|
|
331
|
+
const outputLimit = positiveCount(topProvider?.["max_completion_tokens"]);
|
|
332
|
+
const architecture = isRecord(row["architecture"]) ? row["architecture"] : undefined;
|
|
333
|
+
const modalities = Array.isArray(architecture?.["input_modalities"])
|
|
334
|
+
? architecture["input_modalities"]
|
|
335
|
+
: [];
|
|
336
|
+
const limits: ClineUpstreamLimits = {
|
|
337
|
+
...(contextLimit === undefined ? {} : { contextLimit }),
|
|
338
|
+
...(outputLimit === undefined ? {} : { outputLimit }),
|
|
339
|
+
vision: modalities.some((m) => m === "image"),
|
|
340
|
+
};
|
|
341
|
+
byId.set(id, limits);
|
|
342
|
+
// The roster and this catalog spell the same model differently: the roster
|
|
343
|
+
// names a subscription model `cline-pass/kimi-k3` while this catalog files
|
|
344
|
+
// it as `moonshotai/kimi-k3`. Indexing the bare segment too is what lets
|
|
345
|
+
// `upstreamLimitsFor` resolve one to the other. An exact key already
|
|
346
|
+
// recorded stays authoritative, so a provider-prefixed row never shadows a
|
|
347
|
+
// row filed under the bare id itself.
|
|
348
|
+
const slash = id.indexOf("/");
|
|
349
|
+
if (slash >= 0) {
|
|
350
|
+
const bare = id.slice(slash + 1);
|
|
351
|
+
if (!byId.has(bare)) byId.set(bare, limits);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return byId;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function positiveCount(value: unknown): number | undefined {
|
|
358
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
359
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The catalog row for `id`, falling back to its bare segment. */
|
|
363
|
+
function upstreamLimitsFor(
|
|
364
|
+
index: ReadonlyMap<string, ClineUpstreamLimits>,
|
|
365
|
+
id: string,
|
|
366
|
+
): ClineUpstreamLimits | undefined {
|
|
367
|
+
const normalized = id.trim().toLowerCase();
|
|
368
|
+
const exact = index.get(normalized);
|
|
369
|
+
if (exact !== undefined) return exact;
|
|
370
|
+
const slash = normalized.indexOf("/");
|
|
371
|
+
return slash < 0 ? undefined : index.get(normalized.slice(slash + 1));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function tagsOf(entry: ClineRecommendedModel): readonly string[] {
|
|
375
|
+
return Array.isArray(entry.tags)
|
|
376
|
+
? entry.tags
|
|
377
|
+
.filter((tag): tag is string => typeof tag === "string")
|
|
378
|
+
.map((tag) => tag.toLowerCase())
|
|
379
|
+
: [];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export async function fetchClineRecommendedModels(
|
|
383
|
+
signal: AbortSignal | undefined,
|
|
384
|
+
pass: boolean,
|
|
385
|
+
fetcher: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> = globalThis.fetch,
|
|
386
|
+
): Promise<readonly ModelDefinition[] | null> {
|
|
387
|
+
const load = async (): Promise<readonly ModelDefinition[] | null> => {
|
|
388
|
+
const timeout = AbortSignal.timeout(10_000);
|
|
389
|
+
const composed = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
390
|
+
try {
|
|
391
|
+
const url = `${CLINE_BASE_URL}${CLINE_RECOMMENDED_MODELS_PATH}`;
|
|
392
|
+
const res = await fetcher(url, { headers: { accept: "application/json" }, signal: composed });
|
|
393
|
+
if (!res.ok) return null;
|
|
394
|
+
const json = (await res.json()) as unknown;
|
|
395
|
+
// The limits live in the sibling catalog, not in the roster. Fetch it
|
|
396
|
+
// alongside, and treat its absence as "no upstream limits" rather than a
|
|
397
|
+
// failure: the roster is still useful without them, and `defineModel`
|
|
398
|
+
// falls back to the base catalog.
|
|
399
|
+
const upstream = await fetchClineUpstreamCatalog(fetcher, composed);
|
|
400
|
+
// The recommended roster is not the whole free tier: `/models` names
|
|
401
|
+
// free ids it never lists (minimax among them). Union both, with the
|
|
402
|
+
// recommended roster winning on an id it already defined.
|
|
403
|
+
const freeIds = await fetchClineFreeModelIds(fetcher, composed);
|
|
404
|
+
const models = recommendedModels(json, pass, upstream, freeIds);
|
|
405
|
+
return models.length > 0 ? models : null;
|
|
406
|
+
} catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
// The default fetcher is the production path: this roster is public and
|
|
411
|
+
// changes slowly, so a short TTL avoids redundant refreshes. A caller that
|
|
412
|
+
// injects its own fetcher (tests, custom egress) always gets a live call.
|
|
413
|
+
if (fetcher !== globalThis.fetch) return load();
|
|
414
|
+
return getCachedModelDiscovery(`cline-recommended:${pass ? "pass" : "free"}`, load);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Whether the credential belongs to a Cline Pass account.
|
|
419
|
+
*
|
|
420
|
+
* Reads the same `/users/me/plan` endpoint the quota collector uses. A pass
|
|
421
|
+
* account is the only one allowed to see the `clinePass`/`clineCloud` roster;
|
|
422
|
+
* any failure answers `false`, so a free account never has subscription rows
|
|
423
|
+
* written into its catalog.
|
|
424
|
+
*/
|
|
425
|
+
export async function clineAccountHasPass(
|
|
426
|
+
credential: string,
|
|
427
|
+
fetcher: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> = globalThis.fetch,
|
|
428
|
+
): Promise<boolean> {
|
|
429
|
+
try {
|
|
430
|
+
const token = credential.startsWith("workos:") ? credential : `workos:${credential}`;
|
|
431
|
+
const res = await fetcher("https://api.cline.bot/api/v1/users/me/plan", {
|
|
432
|
+
headers: { accept: "application/json", authorization: `Bearer ${token}` },
|
|
433
|
+
signal: AbortSignal.timeout(10_000),
|
|
434
|
+
});
|
|
435
|
+
if (!res.ok) return false;
|
|
436
|
+
const body = (await res.json()) as { plan?: { displayName?: unknown } };
|
|
437
|
+
const name = typeof body.plan?.displayName === "string" ? body.plan.displayName.toLowerCase() : "";
|
|
438
|
+
return name.includes("pass");
|
|
439
|
+
} catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Cline's model catalog, indexed for limit lookup. Empty when unavailable. */
|
|
445
|
+
async function fetchClineUpstreamCatalog(
|
|
446
|
+
fetcher: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
|
|
447
|
+
signal: AbortSignal,
|
|
448
|
+
): Promise<ReadonlyMap<string, ClineUpstreamLimits>> {
|
|
449
|
+
try {
|
|
450
|
+
const res = await fetcher(`${CLINE_BASE_URL}${CLINE_MODELS_CATALOG_PATH}`, {
|
|
451
|
+
headers: { accept: "application/json" },
|
|
452
|
+
signal,
|
|
453
|
+
});
|
|
454
|
+
if (!res.ok) return new Map();
|
|
455
|
+
return upstreamCatalogIndex(await res.json());
|
|
456
|
+
} catch {
|
|
457
|
+
return new Map();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The free tier as `/models` states it, which is not the same roster as
|
|
463
|
+
* `/ai/cline/recommended-models`.
|
|
464
|
+
*
|
|
465
|
+
* The two endpoints disagree, and the disagreement is the whole reason this
|
|
466
|
+
* exists: `recommended-models` files four free ids, all spelled `cline-free/…`,
|
|
467
|
+
* while `/models` publishes seventeen ids carrying a `:free` suffix — including
|
|
468
|
+
* `minimax/minimax-m3.1-flash:free`, which the recommended roster never names.
|
|
469
|
+
* Reading only the recommended roster therefore could not see minimax at all.
|
|
470
|
+
*
|
|
471
|
+
* The suffix is matched exactly rather than as a substring, the same rule
|
|
472
|
+
* OpenRouter's free discovery uses, so an id whose own name contains "free" is
|
|
473
|
+
* not swept in. A non-ok response is `null` — "this endpoint had nothing to
|
|
474
|
+
* say" — rather than an empty list, so the caller can tell a failed fetch from
|
|
475
|
+
* a genuinely empty tier.
|
|
476
|
+
*/
|
|
477
|
+
export async function fetchClineFreeModelIds(
|
|
478
|
+
fetcher: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> = globalThis.fetch,
|
|
479
|
+
signal: AbortSignal = AbortSignal.timeout(10_000),
|
|
480
|
+
): Promise<readonly string[] | null> {
|
|
481
|
+
try {
|
|
482
|
+
const res = await fetcher(`${CLINE_BASE_URL}${CLINE_MODELS_LIST_PATH}`, {
|
|
483
|
+
headers: { accept: "application/json" },
|
|
484
|
+
signal,
|
|
485
|
+
});
|
|
486
|
+
if (!res.ok) return null;
|
|
487
|
+
const body = (await res.json()) as unknown;
|
|
488
|
+
const root = isRecord(body) ? body : undefined;
|
|
489
|
+
const rows = Array.isArray(root?.["data"]) ? root["data"] : Array.isArray(body) ? body : [];
|
|
490
|
+
const ids = new Set<string>();
|
|
491
|
+
for (const row of rows) {
|
|
492
|
+
if (!isRecord(row)) continue;
|
|
493
|
+
const id = typeof row["id"] === "string" ? row["id"].trim() : "";
|
|
494
|
+
if (id.length === 0) continue;
|
|
495
|
+
if (!id.endsWith(":free")) continue;
|
|
496
|
+
ids.add(id);
|
|
497
|
+
}
|
|
498
|
+
return [...ids];
|
|
499
|
+
} catch {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Static seed for Cline's free tier.
|
|
506
|
+
*
|
|
507
|
+
* Every id here is materialized into the `models` table at boot with
|
|
508
|
+
* `source: "builtin"`, and `seedBundledModels` deletes any builtin row this
|
|
509
|
+
* list no longer declares. That makes the list the *owner* of these rows: an id
|
|
510
|
+
* Cline retires from its roster keeps its catalog card — and its route — until
|
|
511
|
+
* it is removed here, because nothing else prunes a builtin row. The seed is
|
|
512
|
+
* therefore kept to ids Cline currently serves on the free tier; a stale entry
|
|
513
|
+
* is a card that can only ever fail a probe.
|
|
514
|
+
*
|
|
515
|
+
* Cline's live roster (`/ai/cline/recommended-models`, read by
|
|
516
|
+
* `fetchClineRecommendedModels`) is the authority for what is routable right
|
|
517
|
+
* now, and "Fetch models" reconciles against it. This list is the offline seed
|
|
518
|
+
* that makes a fresh install usable before the first fetch.
|
|
519
|
+
*/
|
|
520
|
+
export const CLINE_MODELS: readonly ModelDefinition[] = openrouterFreeRoster({
|
|
521
|
+
providerId: CLINE_PROVIDER_ID,
|
|
522
|
+
endpoint: CLINE_CHAT_PATH,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
export function createClineAdapter(fetchImpl?: typeof fetch): ProviderAdapter {
|
|
527
|
+
return createApiKeyAdapter(
|
|
528
|
+
{
|
|
529
|
+
provider_id: CLINE_PROVIDER_ID,
|
|
530
|
+
base_url: CLINE_BASE_URL,
|
|
531
|
+
endpoint_paths_by_wire_family: { chat: CLINE_CHAT_PATH, responses: "/responses" },
|
|
532
|
+
buildExtraHeaders: (ctx) => clineExtraHeaders(ctx),
|
|
533
|
+
prePayload: clinePrePayload,
|
|
534
|
+
},
|
|
535
|
+
fetchImpl,
|
|
536
|
+
);
|
|
537
|
+
}
|