cartethyia 2.6.7 → 2.7.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 +1 -7
- package/README.md +25 -2
- package/dist/dashboard/assets/{BannedPage-Dskhyp0l.js → BannedPage-D-J75hBM.js} +1 -1
- package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage--SJhk7qQ.js → CliToolDetailPage-CVe6RaBH.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-C203VT-c.js → CliToolsPage-Dd8ZSamp.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BcS7kGG1.js → CombosPage-QoJ6foxo.js} +1 -1
- package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-uujbBYwC.js → CustomizationPage-CKUFTcW4.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js +1 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-CA1kLs45.js → ModelPicker-BH8Tehdi.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +9 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BueSWX5D.js → QuotaPage-ATpaxrbl.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CETophpx.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js +1 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DrKeMcGN.js → SortableList-CAK-EN9z.js} +1 -1
- package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-qjuYtccc.js → StudioPage-B7W2_RhT.js} +3 -3
- package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js +56 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-C23S9sgp.js → api-keys-CUi6UXsR.js} +1 -1
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +2 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/{assert-DU6qrMA_.js → assert-67xXH5rf.js} +1 -1
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Bkz_-RrM.js → badge-DC2L43Mj.js} +1 -1
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/{banner-C-7y3VKV.js → banner-BBVcb0J3.js} +1 -1
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/{card-BOPBuBzD.js → card-Cca1XC6G.js} +1 -1
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-BaFUuFFF.js → cli-tools-KvsWa1Jl.js} +1 -1
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-e-Mlk0FH.js → clipboard-button-DUzEbynq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-Iec_ywbD.js → dialog-DFvWhwTa.js} +1 -1
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bs8MIIHc.js → drawer-CFM2sMX1.js} +1 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DOddwoc0.js → entry-BLBLxX0h.js} +3 -3
- package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DcobaKFu.js → entry-Bqy8RFvM.js} +1 -1
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BRszm4_Z.js → entry-_xHo5x6r.js} +1 -1
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js +1 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BuDsrsPn.js → github-badge-C9e8Ts7e.js} +1 -1
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/{index-BeLRhsmt.js → index-D6QzEC_n.js} +2 -2
- package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DToMICwl.js → inline-BFUcvllj.js} +1 -1
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/{input-WlyHWeG2.js → input-C_ILAvBb.js} +1 -1
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DaJ5baJr.js → layout-CBIyT6Rt.js} +1 -1
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-B3oSK13z.js → list-row-ebJ_-Nsb.js} +1 -1
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/{live-ta6GMb-8.js → live-D0mm--ME.js} +1 -1
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +1 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-g_fduiNw.js → mutation-sMD965oO.js} +1 -1
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Dulqgys5.js → numbers-BO9dKaFr.js} +1 -1
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/providers/inception.svg +5 -0
- package/dist/dashboard/assets/providers/inception.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-UETqfytp.js → query-keys-CfL_Zdoh.js} +1 -1
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BPUo-W8d.js → routing-BnIpi1IJ.js} +1 -1
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/{select-BfZ3SCTM.js → select-Cj5Nw43g.js} +1 -1
- package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js +1 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/{stack-B1w5BM0u.js → stack-EMrOMt4I.js} +1 -1
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/{state-DhZBfWgx.js → state-CWoo7ty5.js} +1 -1
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/{switch-yD7D99Lg.js → switch-CCjT6IkF.js} +1 -1
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-EDFRkn1i.js → useMutation-DjkLHUcE.js} +1 -1
- package/dist/dashboard/assets/useMutation-DjkLHUcE.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 +55 -54
- package/migrations/full-postgres/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/full-postgres/0025_add_pool_random_strategy.sql +3 -0
- package/migrations/lite-sqlite3/0024_add_pool_provider_cooldowns.sql +12 -0
- package/migrations/lite-sqlite3/0025_add_pool_random_strategy.sql +20 -0
- package/package.json +1 -1
- package/scripts/cli/console-menu.ts +5 -2
- package/scripts/cli/tray.ts +21 -6
- package/src/boot/app.ts +24 -62
- package/src/boot/dependencies.ts +732 -750
- package/src/config/cooldowns.ts +16 -0
- package/src/config/providers.ts +13 -1
- package/src/config/resources.ts +13 -80
- package/src/config/security.ts +2 -38
- package/src/config/spec.ts +16 -23
- package/src/console/console-router.ts +7 -7
- package/src/console/domain-registration.ts +19 -12
- package/src/console/providers/catalog/catalog-projections.ts +2 -6
- package/src/console/providers/catalog/contracts.ts +19 -0
- package/src/console/providers/catalog/provider-operations.ts +38 -0
- package/src/console/providers/catalog/routes.ts +14 -0
- package/src/console/providers/catalog/store-helpers.ts +326 -0
- package/src/console/providers/catalog/store.ts +1292 -1563
- package/src/console/quota/account-quota-global.ts +2 -2
- package/src/console/quota/account-quota-shared.ts +1 -1
- package/src/console/quota/account-quota-tenant.ts +35 -12
- package/src/console/quota/quota-view.ts +2 -2
- package/src/console/remote-support/contracts.ts +3 -1
- package/src/console/remote-support/service.ts +115 -6
- package/src/console/routing/pools/contracts.ts +41 -1
- package/src/console/routing/pools/operations.ts +183 -4
- package/src/console/routing/pools/relay-deploy.ts +17 -4
- package/src/console/routing/pools/routes.ts +30 -0
- package/src/console/routing/pools/store.ts +98 -8
- package/src/main.ts +16 -2
- package/src/network/outbound-fetch.ts +226 -365
- package/src/network/pool/agent.ts +8 -0
- package/src/network/pool/pool-state.ts +7 -0
- package/src/network/pool/selector.ts +110 -2
- package/src/network/pool-cooldown-store.ts +252 -0
- package/src/network/pool-health-machine.ts +5 -3
- package/src/network/pool-health.ts +36 -9
- package/src/observability/event-query.ts +57 -0
- package/src/observability/metrics.ts +15 -5
- package/src/observability/runtime-metrics.ts +30 -15
- package/src/persistence/health-event-flush.ts +2 -0
- package/src/persistence/schema.ts +19 -0
- package/src/persistence/usage-flush.ts +8 -3
- package/src/protocol/canonical-model.ts +1 -1
- package/src/protocol/dsml.ts +377 -0
- package/src/protocol/gateway-error.ts +0 -1
- package/src/{providers/provider-reasoning.ts → protocol/reasoning.ts} +10 -9
- package/src/protocol/request/chat.ts +7 -7
- package/src/protocol/request/codex.ts +2 -2
- package/src/protocol/request/gemini.ts +1 -1
- package/src/protocol/request/messages.ts +6 -6
- package/src/protocol/request/responses.ts +7 -7
- package/src/protocol/response/dsml.ts +12 -133
- package/src/{transport → protocol}/translation/attachment-integrity.ts +2 -2
- package/src/{transport → protocol}/translation/cache-controls.ts +1 -1
- package/src/{transport → protocol}/translation/capabilities.ts +2 -2
- package/src/{transport → protocol}/translation/normalize.ts +1 -1
- package/src/{transport → protocol}/translation/quirks.ts +1 -1
- package/src/{transport → protocol}/translation/search.ts +1 -1
- package/src/{transport → protocol}/translation/thinking.ts +23 -1
- package/src/{transport → protocol}/translation/tool-id.ts +1 -1
- package/src/{transport → protocol}/translation/tool-repair.ts +2 -2
- package/src/protocol/upstream-parsing.ts +176 -0
- package/src/providers/adapters/compatible-adapter.ts +11 -2
- package/src/providers/authentication/oauth-refresh-service.ts +9 -5
- package/src/providers/default-registry.ts +21 -5
- package/src/providers/discovery/probe-phases.ts +16 -3
- package/src/providers/discovery/probing-service.ts +4 -1
- package/src/providers/integrations/anthropic.ts +1 -1
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -4
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +141 -8
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -1
- package/src/providers/integrations/buddy/buddy-growth-shared.ts +0 -82
- package/src/providers/integrations/buddy/buddy-identity.ts +25 -0
- package/src/providers/integrations/buddy/buddy-server-tools.ts +240 -0
- package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +36 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +6 -31
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/codebuddy/shared.ts +19 -4
- package/src/providers/integrations/buddy/turing-device-token.ts +262 -0
- package/src/providers/integrations/buddy/workbuddy/provider.ts +2 -0
- package/src/providers/integrations/buddy/workbuddy/shared.ts +56 -3
- package/src/providers/integrations/claude/claude.ts +1 -1
- package/src/providers/integrations/cloudflareai/cloudflareai.ts +1 -1
- package/src/providers/integrations/codex/codex-errors.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +1 -1
- package/src/providers/integrations/commandcode/commandcode.ts +134 -104
- package/src/providers/integrations/commandcode/discovery.ts +6 -5
- package/src/providers/integrations/factory/factory.ts +1 -1
- package/src/providers/integrations/github/github.ts +1 -1
- package/src/providers/integrations/grok/grok.ts +1 -1
- package/src/providers/integrations/inception/provider.ts +91 -0
- package/src/providers/integrations/inferhub.ts +1 -1
- package/src/providers/integrations/kiro/kiro-request.ts +1 -1
- package/src/providers/integrations/mistral.ts +193 -19
- package/src/providers/integrations/opencode/fingerprint.ts +19 -2
- package/src/providers/integrations/opencode/prompt.ts +111 -0
- package/src/providers/integrations/opencode/provider.ts +101 -23
- package/src/providers/integrations/opencode/tool-definitions.ts +4 -4
- package/src/providers/integrations/qoder/qoder-checkin.ts +303 -0
- package/src/providers/integrations/qoder/qoder.ts +1641 -884
- package/src/providers/integrations/shared/install-id.ts +2 -1
- package/src/providers/integrations/trae/trae-cache.ts +110 -0
- package/src/providers/integrations/trae/trae-oauth.ts +29 -4
- package/src/providers/integrations/trae/trae-protocol.ts +2 -1
- package/src/providers/integrations/trae/trae.ts +106 -23
- package/src/providers/operations/account-error-classification.ts +1660 -0
- package/src/providers/operations/account-health-service.ts +621 -2097
- package/src/providers/operations/account-reset-service.ts +2 -0
- package/src/providers/provider-contracts.ts +0 -2
- package/src/providers/provider-metadata.ts +25 -4
- package/src/providers/provider-probe.ts +2 -2
- package/src/{console → providers}/quota/cache.ts +1 -1
- package/src/{console → providers}/quota/refresh.ts +5 -5
- package/src/security/abuse.ts +17 -0
- package/src/transport/dispatch/attempt-finalize.ts +114 -12
- package/src/transport/dispatch/attempt-loop.ts +3 -0
- package/src/transport/dispatch/empty-stream.ts +224 -217
- package/src/transport/dispatch/generation.ts +37 -55
- package/src/transport/dispatch/handler-deps.ts +32 -0
- package/src/transport/dispatch/leases.ts +9 -4
- package/src/transport/dispatch/proxy-request.ts +6 -30
- package/src/transport/dispatch/responses-compact.ts +22 -43
- package/src/transport/dispatch/streaming/finalize.ts +3 -0
- package/src/transport/dispatch/streaming/stream.ts +0 -8
- package/src/transport/dispatch/systemone.ts +22 -42
- package/src/transport/dispatch/token-count.ts +23 -39
- package/src/transport/dispatch/upstream.ts +1 -3
- package/src/transport/dispatch/websearch.ts +22 -41
- package/src/transport/failure-policy.ts +59 -178
- package/src/transport/guards/abuse-guard.ts +10 -0
- package/src/transport/guards/auth-guard.ts +0 -18
- package/src/transport/guards/error-lifecycle.ts +1 -1
- package/src/transport/guards/pipeline.ts +0 -6
- package/src/transport/guards/request-context.ts +18 -91
- package/src/transport/request/degradation.ts +20 -1
- package/src/transport/request/inflight.ts +4 -95
- package/src/transport/request/preparer.ts +6 -6
- package/src/transport/request/state.ts +66 -38
- package/src/transport/request/tenant-preferences.ts +1 -1
- package/src/transport/routing/route-catalog.ts +41 -31
- package/src/transport/routing/router.ts +2 -2
- package/src/transport/surface/messages/parse.ts +1 -1
- package/src/workers/buddy-growth-tasks.ts +346 -425
- package/src/workers/daily-checkin.ts +17 -6
- package/src/workers/quota-refresh-sweep.ts +2 -2
- package/src/workers/scheduler.ts +17 -1
- package/dist/dashboard/assets/BannedPage-Dskhyp0l.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage--SJhk7qQ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-C203VT-c.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BcS7kGG1.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-uujbBYwC.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoOUthbG.js +0 -1
- package/dist/dashboard/assets/LoginPage-DoOUthbG.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CA1kLs45.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-B9mBTweh.js +0 -7
- package/dist/dashboard/assets/ProxyPage-B9mBTweh.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BueSWX5D.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js +0 -1
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js +0 -1
- package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DrKeMcGN.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-qjuYtccc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js +0 -56
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/app-BUSiYL6F.css +0 -2
- package/dist/dashboard/assets/app-BUSiYL6F.css.gz +0 -0
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/badge-Bkz_-RrM.js.gz +0 -0
- package/dist/dashboard/assets/banner-C-7y3VKV.js.gz +0 -0
- package/dist/dashboard/assets/card-BOPBuBzD.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-e-Mlk0FH.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js +0 -1
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/dialog-Iec_ywbD.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bs8MIIHc.js.gz +0 -0
- package/dist/dashboard/assets/entry-BRszm4_Z.js.gz +0 -0
- package/dist/dashboard/assets/entry-DOddwoc0.js.gz +0 -0
- package/dist/dashboard/assets/entry-DcobaKFu.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BuDsrsPn.js.gz +0 -0
- package/dist/dashboard/assets/index-BeLRhsmt.js.gz +0 -0
- package/dist/dashboard/assets/inline-DToMICwl.js.gz +0 -0
- package/dist/dashboard/assets/input-WlyHWeG2.js.gz +0 -0
- package/dist/dashboard/assets/layout-DaJ5baJr.js.gz +0 -0
- package/dist/dashboard/assets/list-row-B3oSK13z.js.gz +0 -0
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js +0 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Dulqgys5.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/select-BfZ3SCTM.js.gz +0 -0
- package/dist/dashboard/assets/settings--3hxSyry.js +0 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/stack-B1w5BM0u.js.gz +0 -0
- package/dist/dashboard/assets/state-DhZBfWgx.js.gz +0 -0
- package/dist/dashboard/assets/switch-yD7D99Lg.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/src/console/admin/model-abuse/contracts.ts +0 -38
- package/src/console/admin/model-abuse/routes.ts +0 -97
- package/src/security/model-abuse.ts +0 -704
- package/src/transport/guards/concurrency-guard.ts +0 -125
- /package/dist/dashboard/assets/{app-MdVfwraB.js → app-BaWd0puM.js} +0 -0
- /package/dist/dashboard/assets/{app-MdVfwraB.js.gz → app-BaWd0puM.js.gz} +0 -0
|
@@ -9,6 +9,7 @@ import type { ApiKeyProviderSpec } from "../../configured-provider";
|
|
|
9
9
|
import { resolveOpenCodeUnavailableFree } from "../../../config/providers";
|
|
10
10
|
import { buildOpenCodeHeaders } from "./fingerprint";
|
|
11
11
|
import { OPENCODE_TOOL_DEFINITIONS } from "./tool-definitions";
|
|
12
|
+
import { injectOpenCodePrompt } from "./prompt";
|
|
12
13
|
/**
|
|
13
14
|
* OpenCode's three API-key tiers, all served from `opencode.ai`:
|
|
14
15
|
*
|
|
@@ -19,8 +20,10 @@ import { OPENCODE_TOOL_DEFINITIONS } from "./tool-definitions";
|
|
|
19
20
|
* - `opencode-go` (OpenCode Go): billed API-key route on the separate
|
|
20
21
|
* `/zen/go/v1` base — a bundled, higher-tier model set.
|
|
21
22
|
*
|
|
22
|
-
* Free and Zen dispatch on `/inference
|
|
23
|
-
*
|
|
23
|
+
* Free and Zen dispatch on the `/inference` origin using the 2.1 family
|
|
24
|
+
* segment: `/inference/openai/v1/...` for chat/responses and
|
|
25
|
+
* `/inference/anthropic/v1/messages?beta=true` for Anthropic. The catalog is
|
|
26
|
+
* the other way round: `/zen/v1/models` publishes the listing while
|
|
24
27
|
* `/inference/v1/models` answers 404. A replay that hit `/inference/v1` directly
|
|
25
28
|
* returned 200 while the gateway kept failing, because every model row carried
|
|
26
29
|
* an explicit `/zen/v1` endpoint — which outranks the per-provider wire-family
|
|
@@ -30,8 +33,28 @@ import { OPENCODE_TOOL_DEFINITIONS } from "./tool-definitions";
|
|
|
30
33
|
* (`x-opencode-*` correlation IDs, `user-agent: opencode`). Go is
|
|
31
34
|
* intentionally API-key-only.
|
|
32
35
|
*/
|
|
33
|
-
/**
|
|
34
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The v2 upstream path for a wire family on the `/inference` origin, matching
|
|
38
|
+
* the reference implementation's `v2EndpointPath`: the 2.1 client
|
|
39
|
+
* groups `/inference` routes by provider family, so chat/responses sit under
|
|
40
|
+
* `/openai/v1/...` and Anthropic under `/anthropic/v1/messages?beta=true`.
|
|
41
|
+
* Go keeps the flat `/zen/go/v1/...` shape.
|
|
42
|
+
*/
|
|
43
|
+
function zenEndpoint(wireFamily: string): string {
|
|
44
|
+
if (wireFamily === "messages") return "/inference/anthropic/v1/messages?beta=true";
|
|
45
|
+
const protocol = wireFamily === "responses" ? "responses" : "chat/completions";
|
|
46
|
+
return `/inference/openai/v1/${protocol}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The System One decision endpoint on the `/inference` origin. Verified live:
|
|
51
|
+
* System One does NOT take the v2 family segment — `/inference/openai/v1/systemone`
|
|
52
|
+
* answers 404 while the flat `/inference/v1/systemone` answers 200 with a valid
|
|
53
|
+
* `{state, questions}` body.
|
|
54
|
+
*/
|
|
55
|
+
function zenSystemoneEndpoint(): string {
|
|
56
|
+
return "/inference/v1/systemone";
|
|
57
|
+
}
|
|
35
58
|
const GO_PATH_PREFIX = "/zen/go/v1";
|
|
36
59
|
|
|
37
60
|
/**
|
|
@@ -121,7 +144,10 @@ export function isFreeTierZenModel(modelId: string): boolean {
|
|
|
121
144
|
// id rather than the prefixed one.
|
|
122
145
|
const bare = modelId.slice(modelId.indexOf("/") + 1);
|
|
123
146
|
if (unavailableFreeZenIds().includes(bare)) return false;
|
|
124
|
-
|
|
147
|
+
// The reference gateway's name rule admits any id containing "free" (any
|
|
148
|
+
// case) in addition to the suffix convention; the explicit lists keep the
|
|
149
|
+
// measured exclusions and the one free id that carries no marker.
|
|
150
|
+
return bare.endsWith("-free") || KNOWN_FREE_ZEN_IDS.includes(bare) || bare.toLowerCase().includes("free");
|
|
125
151
|
}
|
|
126
152
|
|
|
127
153
|
/**
|
|
@@ -156,6 +182,7 @@ function opencodeDesktopHeaders(
|
|
|
156
182
|
undefined,
|
|
157
183
|
context.conversation_affinity,
|
|
158
184
|
context.request_headers?.["x-opencode-project"],
|
|
185
|
+
context.request_headers?.["x-opencode-session-id"],
|
|
159
186
|
),
|
|
160
187
|
// The gate reads the anonymous sentinel: a free dispatch with no
|
|
161
188
|
// Authorization is refused regardless of headers, tools, or session shape.
|
|
@@ -327,6 +354,34 @@ function clampOpenCodeTools(payload: Record<string, unknown>, request: Canonical
|
|
|
327
354
|
if (kept.length > OPENCODE_MAX_TOOLS) return;
|
|
328
355
|
payload["tools"] = [...kept, ...rest.slice(0, OPENCODE_MAX_TOOLS - kept.length)];
|
|
329
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* The completion ceiling a 2.x client puts on every v2 chat body, matching the
|
|
359
|
+
* reference gateway's `v2CompletionLimit`. The upstream's free gate expects the
|
|
360
|
+
* same ceiling; an explicit `max_tokens`/`max_completion_tokens` from the
|
|
361
|
+
* caller always wins.
|
|
362
|
+
*/
|
|
363
|
+
const OPENCODE_V2_COMPLETION_LIMIT = 32_000;
|
|
364
|
+
|
|
365
|
+
/** The key order a 2.x client's JSON.stringify emits for a chat body. */
|
|
366
|
+
const V2_CHAT_KEY_ORDER = ["model", "messages", "tools", "stream", "stream_options", "max_completion_tokens"] as const;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Reorders a chat payload's keys to the 2.x client's byte layout: the skeleton
|
|
370
|
+
* keys first in the client's order, then every remaining key sorted. Values are
|
|
371
|
+
* untouched; JSON.stringify preserves this insertion order, so the serialized
|
|
372
|
+
* body matches the reference gateway's `marshalV2ChatBody`.
|
|
373
|
+
*/
|
|
374
|
+
function reorderV2ChatKeys(payload: Record<string, unknown>): void {
|
|
375
|
+
const keys = [
|
|
376
|
+
...V2_CHAT_KEY_ORDER.filter((key) => key in payload),
|
|
377
|
+
...Object.keys(payload).filter((key) => !(V2_CHAT_KEY_ORDER as readonly string[]).includes(key)).sort(),
|
|
378
|
+
];
|
|
379
|
+
const next: Record<string, unknown> = {};
|
|
380
|
+
for (const key of keys) next[key] = payload[key];
|
|
381
|
+
for (const key of Object.keys(payload)) delete payload[key];
|
|
382
|
+
Object.assign(payload, next);
|
|
383
|
+
}
|
|
384
|
+
|
|
330
385
|
function opencodeSpec(providerId: "opencode" | "opencode-zen" | "opencode-go"): ApiKeyProviderSpec {
|
|
331
386
|
// Free-tier gate facts (verified live 2026-10-14 against opencode.ai/zen/v1):
|
|
332
387
|
// a request for a free model with stream:false is ALWAYS refused with 403
|
|
@@ -352,19 +407,42 @@ function opencodeSpec(providerId: "opencode" | "opencode-zen" | "opencode-go"):
|
|
|
352
407
|
if (candidate.wire_family === "responses") {
|
|
353
408
|
payload.store = false;
|
|
354
409
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
include_usage: true,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
// Free model: force the upstream stream flag regardless of what the
|
|
364
|
-
// caller asked for. The gate refuses stream:false on free models with
|
|
365
|
-
// 403 FreeTierError; here it only patches the wire body, never the
|
|
410
|
+
// Free model: match the reference gateway's free-tier shaping 1:1 —
|
|
411
|
+
// the official CLI system prompt, the v2 completion ceiling, the
|
|
412
|
+
// include_usage flag, and the forced upstream stream. The gate refuses
|
|
413
|
+
// tool-less, non-streaming, or non-agent-shaped free dispatches with 403
|
|
414
|
+
// FreeTierError; here it only patches the wire body, never the
|
|
366
415
|
// client-visible stream contract.
|
|
367
|
-
if (isFreeTierZenModel(candidate.model_id))
|
|
416
|
+
if (isFreeTierZenModel(candidate.model_id)) {
|
|
417
|
+
if (candidate.wire_family === "chat") {
|
|
418
|
+
// The wire encoder injects the v2 ceiling (32000) as the default
|
|
419
|
+
// max_completion_tokens when the caller set no limit. Treat that
|
|
420
|
+
// sentinel as "unset" so the model's own declared output ceiling can
|
|
421
|
+
// take its place; an explicit user limit is never touched.
|
|
422
|
+
const isV2Default =
|
|
423
|
+
payload["max_completion_tokens"] === OPENCODE_V2_COMPLETION_LIMIT &&
|
|
424
|
+
payload["max_tokens"] === undefined;
|
|
425
|
+
if (isV2Default || (payload["max_completion_tokens"] === undefined && payload["max_tokens"] === undefined)) {
|
|
426
|
+
const ceiling =
|
|
427
|
+
typeof candidate.output_limit === "number" && candidate.output_limit > 0
|
|
428
|
+
? candidate.output_limit
|
|
429
|
+
: OPENCODE_V2_COMPLETION_LIMIT;
|
|
430
|
+
payload["max_completion_tokens"] = ceiling;
|
|
431
|
+
}
|
|
432
|
+
payload.stream_options = {
|
|
433
|
+
...(typeof payload.stream_options === "object" && payload.stream_options !== null
|
|
434
|
+
? (payload.stream_options as Record<string, unknown>)
|
|
435
|
+
: {}),
|
|
436
|
+
include_usage: true,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const family = candidate.wire_family;
|
|
440
|
+
if (family === "chat" || family === "responses" || family === "messages") {
|
|
441
|
+
injectOpenCodePrompt(payload, family);
|
|
442
|
+
}
|
|
443
|
+
payload.stream = true;
|
|
444
|
+
if (candidate.wire_family === "chat") reorderV2ChatKeys(payload);
|
|
445
|
+
}
|
|
368
446
|
},
|
|
369
447
|
// OpenCode Free is a genuinely public, unauthenticated endpoint: no
|
|
370
448
|
// credential is ever forwarded, whatever the account configuration says.
|
|
@@ -424,7 +502,7 @@ export async function discoverOpenCodeFreeModels(options: {
|
|
|
424
502
|
...systemoneLimits(),
|
|
425
503
|
freeTier: true,
|
|
426
504
|
serviceKind: "systemone" as const,
|
|
427
|
-
endpointPath:
|
|
505
|
+
endpointPath: zenSystemoneEndpoint(),
|
|
428
506
|
};
|
|
429
507
|
});
|
|
430
508
|
}
|
|
@@ -436,12 +514,12 @@ export async function discoverOpenCodeFreeModels(options: {
|
|
|
436
514
|
// OpenCode row understates this model's 1M context limit, so keep the corrected
|
|
437
515
|
// limit explicit for both tiers that serve it.
|
|
438
516
|
const sharedZenChat: readonly ModelDefinition[] = [
|
|
439
|
-
defineModel({ id: "big-pickle", providerId: "opencode", wireFamily: "chat", endpoint:
|
|
517
|
+
defineModel({ id: "big-pickle", providerId: "opencode", wireFamily: "chat", endpoint: zenEndpoint("chat"), vision: true, reasoning: true }),
|
|
440
518
|
defineModel({
|
|
441
519
|
id: "mimo-v2.6-flash-free",
|
|
442
520
|
providerId: "opencode",
|
|
443
521
|
wireFamily: "chat",
|
|
444
|
-
endpoint:
|
|
522
|
+
endpoint: zenEndpoint("chat"),
|
|
445
523
|
ctx: 1_000_000,
|
|
446
524
|
out: 32_000,
|
|
447
525
|
vision: true,
|
|
@@ -478,7 +556,7 @@ const systemoneZen = (id: string, free: boolean): ModelDefinition =>
|
|
|
478
556
|
providerId: "opencode",
|
|
479
557
|
serviceKind: "systemone",
|
|
480
558
|
wireFamily: "chat",
|
|
481
|
-
endpoint:
|
|
559
|
+
endpoint: zenSystemoneEndpoint(),
|
|
482
560
|
ctx: SYSTEMONE_LIMITS.context,
|
|
483
561
|
out: SYSTEMONE_LIMITS.output,
|
|
484
562
|
toolCall: false,
|
|
@@ -507,8 +585,8 @@ export const OPENCODE_FREE_MODELS: readonly ModelDefinition[] = [
|
|
|
507
585
|
|
|
508
586
|
export const OPENCODE_ZEN_MODELS: readonly ModelDefinition[] = [
|
|
509
587
|
...sharedZenChat,
|
|
510
|
-
defineModel({ id: "muse-spark-1.2", providerId: "opencode", wireFamily: "responses", endpoint:
|
|
511
|
-
defineModel({ id: "muse-spark-1.3", providerId: "opencode", wireFamily: "responses", endpoint:
|
|
588
|
+
defineModel({ id: "muse-spark-1.2", providerId: "opencode", wireFamily: "responses", endpoint: zenEndpoint("responses"), vision: true, reasoning: true, reasoningEfforts: MUSE_SPARK_REASONING_EFFORTS }),
|
|
589
|
+
defineModel({ id: "muse-spark-1.3", providerId: "opencode", wireFamily: "responses", endpoint: zenEndpoint("responses"), vision: true, reasoning: true, reasoningEfforts: MUSE_SPARK_REASONING_EFFORTS }),
|
|
512
590
|
systemoneZen("jev-1.13", false),
|
|
513
591
|
];
|
|
514
592
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* even one carrying the right name — is refused with `403 FreeTierError`.
|
|
7
7
|
* That is why these are verbatim rather than minimal.
|
|
8
8
|
*
|
|
9
|
-
* Source: the client's own tool registry as embedded by the
|
|
10
|
-
* gateway
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Source: the client's own tool registry as embedded by the reference
|
|
10
|
+
* gateway's tool asset, captured from a real dispatch. `patch` is absent from
|
|
11
|
+
* that capture; its description and single `patchText` parameter come from the
|
|
12
|
+
* 2.0.32 CLI binary's own registration.
|
|
13
13
|
*
|
|
14
14
|
* Thirteen entries, matching the CLI's internal tool-name set.
|
|
15
15
|
*/
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qoder daily check-in (streak / 100 Credits per day), ported from the
|
|
3
|
+
* reference bridge (`qoder_accounts.py` in `qoder2api-hub`).
|
|
4
|
+
*
|
|
5
|
+
* Two upstream surfaces, tried in order:
|
|
6
|
+
*
|
|
7
|
+
* 1. Legacy sash: `GET /sash/api/v1/me/daily-check-in/status` then
|
|
8
|
+
* `POST /sash/api/v1/me/daily-check-in/claim`. The status exposes the
|
|
9
|
+
* streak (`currentStreakDays`) and the reward (`rewardCredits`, 100).
|
|
10
|
+
* 2. Activity-platform campaign: when the legacy surface is absent (INTL often
|
|
11
|
+
* answers 404), `GET /sash/api/v1/me/campaigns` lists daily rewards and the
|
|
12
|
+
* CREDITS-kind claimable one is claimed via
|
|
13
|
+
* `POST /sash/api/v1/me/campaigns/{id}/claim`.
|
|
14
|
+
*
|
|
15
|
+
* The result shape is the same `DailyCheckinResult` the buddy family uses, so
|
|
16
|
+
* the shared sweep slot ledger (`daily_checkin_ledger`), the log phrasing, and
|
|
17
|
+
* the console surface treat Qoder like any other daily-grant provider.
|
|
18
|
+
*/
|
|
19
|
+
import { QODER_REALMS, qoderRealmForProvider } from "./qoder-realm";
|
|
20
|
+
|
|
21
|
+
/** Structural fetch bound; Bun's own fetch type carries extra host props. */
|
|
22
|
+
type Fetcher = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
23
|
+
import type { DailyCheckinResult, DailyCheckinState } from "../buddy/buddy-checkin";
|
|
24
|
+
|
|
25
|
+
export const QODER_CHECKIN_PROVIDER_IDS: readonly string[] = ["qoder", "qodercn"];
|
|
26
|
+
|
|
27
|
+
/** True when this provider id runs the Qoder check-in surface. */
|
|
28
|
+
export function isQoderCheckinProvider(providerId: string): boolean {
|
|
29
|
+
return (QODER_CHECKIN_PROVIDER_IDS as readonly string[]).includes(providerId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Abort budget for one upstream check-in request. */
|
|
33
|
+
const TIMEOUT_MS = 15_000;
|
|
34
|
+
|
|
35
|
+
const CHECKIN_STATUS_PATH = "/sash/api/v1/me/daily-check-in/status";
|
|
36
|
+
const CHECKIN_CLAIM_PATH = "/sash/api/v1/me/daily-check-in/claim";
|
|
37
|
+
const CAMPAIGNS_PATH = "/sash/api/v1/me/campaigns";
|
|
38
|
+
const CAMPAIGN_CLAIM_PATH = "/sash/api/v1/me/campaigns/%s/claim";
|
|
39
|
+
|
|
40
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
41
|
+
if (typeof value !== "object" || value === null) return null;
|
|
42
|
+
return value as Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function asNumber(value: unknown): number | null {
|
|
46
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
47
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
48
|
+
const parsed = Number(value);
|
|
49
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function safeParse(raw: string): unknown {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function baseOutcome(
|
|
63
|
+
providerId: string,
|
|
64
|
+
accountId: string,
|
|
65
|
+
): { providerId: string; accountId: string; credit: null; streakDays: null; error: null } {
|
|
66
|
+
return { providerId, accountId, credit: null, streakDays: null, error: null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function asState(state: string, today: string, lastClaimedAt: unknown): { active: boolean; todayCheckedIn: boolean } {
|
|
70
|
+
const status = state.toUpperCase();
|
|
71
|
+
const active = status === "CLAIMABLE" || status === "CLAIMED";
|
|
72
|
+
let claimedToday = false;
|
|
73
|
+
if (status === "CLAIMED") {
|
|
74
|
+
const stamp = asNumber(lastClaimedAt);
|
|
75
|
+
if (stamp !== null) {
|
|
76
|
+
const day = new Date(stamp * 1000).toISOString().slice(0, 10);
|
|
77
|
+
claimedToday = day === today;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { active, todayCheckedIn: claimedToday };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** GET campaign list; returns `null` when the surface is unreachable/unauthorized. */
|
|
84
|
+
async function fetchCampaigns(
|
|
85
|
+
base: string,
|
|
86
|
+
credential: string,
|
|
87
|
+
fetcher: Fetcher,
|
|
88
|
+
): Promise<Record<string, unknown>[] | null> {
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetcher(`${base}${CAMPAIGNS_PATH}`, {
|
|
91
|
+
method: "GET",
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${credential}`,
|
|
94
|
+
Accept: "application/json",
|
|
95
|
+
},
|
|
96
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
97
|
+
});
|
|
98
|
+
if (response.status === 401 || response.status === 403) return null;
|
|
99
|
+
if (!response.ok) return null;
|
|
100
|
+
const payload: unknown = await response.json().catch(() => null);
|
|
101
|
+
const rec = asRecord(payload);
|
|
102
|
+
const list = Array.isArray(rec?.["campaigns"]) ? rec!["campaigns"] : Array.isArray(payload) ? payload : null;
|
|
103
|
+
return Array.isArray(list) ? (list as Record<string, unknown>[]) : null;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Claims one campaign and returns the resulting state. Mirrors the donor's
|
|
111
|
+
* `claim_campaign` idempotence handling: a 409 or `ALREADY_CLAIMED` body is
|
|
112
|
+
* an already-claimed grant, `SAME_PERSON_ALREADY_CLAIMED`/`BLOCKED` is a
|
|
113
|
+
* same-person block (not a failure of this account's day), and anything else
|
|
114
|
+
* is an error.
|
|
115
|
+
*/
|
|
116
|
+
async function claimCampaign(
|
|
117
|
+
base: string,
|
|
118
|
+
campaignId: string,
|
|
119
|
+
credential: string,
|
|
120
|
+
fetcher: Fetcher,
|
|
121
|
+
): Promise<{ state: DailyCheckinState; amount: number }> {
|
|
122
|
+
const url = base + CAMPAIGN_CLAIM_PATH.replace("%s", encodeURIComponent(campaignId));
|
|
123
|
+
let response: Response;
|
|
124
|
+
try {
|
|
125
|
+
response = await fetcher(url, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `Bearer ${credential}`,
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
Accept: "application/json",
|
|
131
|
+
},
|
|
132
|
+
body: "{}",
|
|
133
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
134
|
+
});
|
|
135
|
+
} catch {
|
|
136
|
+
return { state: "error", amount: 0 };
|
|
137
|
+
}
|
|
138
|
+
const raw = await response.text().catch(() => "");
|
|
139
|
+
const payload: unknown = raw ? safeParse(raw) : null;
|
|
140
|
+
const rec = asRecord(payload);
|
|
141
|
+
const status = String(rec?.["status"] ?? "").toUpperCase();
|
|
142
|
+
const failure = String(rec?.["failureCode"] ?? "").toUpperCase();
|
|
143
|
+
const success = rec?.["success"] !== false;
|
|
144
|
+
const benefit = asRecord(rec?.["benefit"]);
|
|
145
|
+
const amount = asNumber(rec?.["amount"] ?? benefit?.["amount"]) ?? 0;
|
|
146
|
+
if (response.status === 409 || failure === "ALREADY_CLAIMED" || status === "CLAIMED" && failure === "REPLAYED") {
|
|
147
|
+
return { state: "already_claimed", amount };
|
|
148
|
+
}
|
|
149
|
+
if (failure === "SAME_PERSON_ALREADY_CLAIMED" || status === "BLOCKED") {
|
|
150
|
+
return { state: "not_eligible", amount: 0 };
|
|
151
|
+
}
|
|
152
|
+
if (response.ok && success && (status === "CLAIMED" || status === "GRANTED" || status === "SUCCESS")) {
|
|
153
|
+
return { state: "claimed", amount };
|
|
154
|
+
}
|
|
155
|
+
return { state: "error", amount: 0 };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Legacy sash status + claim. Returns `null` when the surface is absent (404/405/410). */
|
|
159
|
+
async function legacySashCheckin(args: {
|
|
160
|
+
readonly base: string;
|
|
161
|
+
readonly credential: string;
|
|
162
|
+
readonly fetcher: Fetcher;
|
|
163
|
+
readonly now?: () => Date;
|
|
164
|
+
}): Promise<{ state: DailyCheckinState; credit: number; streakDays: number } | null> {
|
|
165
|
+
const { base, credential, fetcher } = args;
|
|
166
|
+
const today = (args.now?.() ?? new Date()).toISOString().slice(0, 10);
|
|
167
|
+
let statusResponse: Response;
|
|
168
|
+
try {
|
|
169
|
+
statusResponse = await fetcher(`${base}${CHECKIN_STATUS_PATH}`, {
|
|
170
|
+
method: "GET",
|
|
171
|
+
headers: {
|
|
172
|
+
Authorization: `Bearer ${credential}`,
|
|
173
|
+
Accept: "application/json",
|
|
174
|
+
},
|
|
175
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
176
|
+
});
|
|
177
|
+
} catch {
|
|
178
|
+
return { state: "error", credit: 0, streakDays: 0 };
|
|
179
|
+
}
|
|
180
|
+
if (statusResponse.status === 401 || statusResponse.status === 403) {
|
|
181
|
+
return { state: "error", credit: 0, streakDays: 0 };
|
|
182
|
+
}
|
|
183
|
+
if (statusResponse.status === 404 || statusResponse.status === 405 || statusResponse.status === 410) {
|
|
184
|
+
return null; // surface absent — caller falls back to campaign
|
|
185
|
+
}
|
|
186
|
+
if (!statusResponse.ok) {
|
|
187
|
+
return { state: "error", credit: 0, streakDays: 0 };
|
|
188
|
+
}
|
|
189
|
+
const payload: unknown = await statusResponse.json().catch(() => null);
|
|
190
|
+
const rec = asRecord(payload);
|
|
191
|
+
if (!rec) return { state: "error", credit: 0, streakDays: 0 };
|
|
192
|
+
const { active, todayCheckedIn } = asState(String(rec["status"] ?? ""), today, rec["lastClaimedAt"]);
|
|
193
|
+
const streakDays = asNumber(rec["currentStreakDays"]) ?? 0;
|
|
194
|
+
const credit = asNumber(rec["rewardCredits"]) ?? 100;
|
|
195
|
+
if (todayCheckedIn) return { state: "already_claimed", credit: 0, streakDays };
|
|
196
|
+
if (!active) return { state: "not_eligible", credit: 0, streakDays };
|
|
197
|
+
// Claim.
|
|
198
|
+
let claimResponse: Response;
|
|
199
|
+
try {
|
|
200
|
+
claimResponse = await fetcher(`${base}${CHECKIN_CLAIM_PATH}`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: {
|
|
203
|
+
Authorization: `Bearer ${credential}`,
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
Accept: "application/json",
|
|
206
|
+
},
|
|
207
|
+
body: "{}",
|
|
208
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
return { state: "error", credit: 0, streakDays };
|
|
212
|
+
}
|
|
213
|
+
const raw = await claimResponse.text().catch(() => "");
|
|
214
|
+
const lower = raw.toLowerCase();
|
|
215
|
+
if (claimResponse.status === 409 || lower.includes("already_claimed") || lower.includes("已签到")) {
|
|
216
|
+
return { state: "already_claimed", credit: 0, streakDays };
|
|
217
|
+
}
|
|
218
|
+
const body: unknown = raw ? safeParse(raw) : null;
|
|
219
|
+
const bodyRec = asRecord(body);
|
|
220
|
+
const ok = claimResponse.ok && bodyRec?.["success"] !== false;
|
|
221
|
+
if (!ok) return { state: "error", credit: 0, streakDays };
|
|
222
|
+
return { state: "claimed", credit, streakDays };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Performs one account's Qoder daily check-in: legacy sash first, campaign
|
|
227
|
+
* fallback when the sash surface is absent. Returns the same
|
|
228
|
+
* `DailyCheckinResult` shape the shared sweep consumes.
|
|
229
|
+
*/
|
|
230
|
+
export async function fetchQoderDailyCheckin(args: {
|
|
231
|
+
readonly providerId: string;
|
|
232
|
+
readonly accountId: string;
|
|
233
|
+
readonly credential: string;
|
|
234
|
+
readonly fetcher: Fetcher;
|
|
235
|
+
readonly now?: () => Date;
|
|
236
|
+
}): Promise<DailyCheckinResult> {
|
|
237
|
+
const { providerId, accountId, credential, fetcher } = args;
|
|
238
|
+
const baseResult = baseOutcome(providerId, accountId);
|
|
239
|
+
if (!credential) {
|
|
240
|
+
return { ...baseResult, state: "error", error: "Account has no stored credential." };
|
|
241
|
+
}
|
|
242
|
+
const realm = qoderRealmForProvider(providerId);
|
|
243
|
+
const base = QODER_REALMS[realm].openapi;
|
|
244
|
+
|
|
245
|
+
const sash = await legacySashCheckin({
|
|
246
|
+
base,
|
|
247
|
+
credential,
|
|
248
|
+
fetcher,
|
|
249
|
+
...(args.now === undefined ? {} : { now: args.now }),
|
|
250
|
+
});
|
|
251
|
+
if (sash !== null) {
|
|
252
|
+
return {
|
|
253
|
+
...baseResult,
|
|
254
|
+
state: sash.state,
|
|
255
|
+
credit: sash.state === "claimed" ? sash.credit : null,
|
|
256
|
+
streakDays: sash.streakDays,
|
|
257
|
+
...(sash.state === "error" ? { error: "Upstream daily check-in failed." } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Legacy surface absent: campaign fallback.
|
|
262
|
+
const campaigns = await fetchCampaigns(base, credential, fetcher);
|
|
263
|
+
if (campaigns === null) {
|
|
264
|
+
return { ...baseResult, state: "error", error: "Check-in surface unavailable (legacy 404 and campaign list failed)." };
|
|
265
|
+
}
|
|
266
|
+
let alreadySeen = false;
|
|
267
|
+
for (const campaign of campaigns) {
|
|
268
|
+
const rec = asRecord(campaign);
|
|
269
|
+
if (!rec) continue;
|
|
270
|
+
const benefit = asRecord(rec["benefit"]);
|
|
271
|
+
const kind = String(benefit?.["kind"] ?? "").toUpperCase();
|
|
272
|
+
if (kind !== "CREDITS" && kind !== "") continue;
|
|
273
|
+
const actionType = String(rec["action_type"] ?? "");
|
|
274
|
+
if (actionType !== "" && actionType !== "CLAIM_BENEFIT") continue;
|
|
275
|
+
const claimStatus = String(rec["claim_status"] ?? "").toUpperCase();
|
|
276
|
+
if (claimStatus === "CLAIMED") {
|
|
277
|
+
alreadySeen = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (claimStatus !== "CLAIMABLE") continue;
|
|
281
|
+
const campaignId = String(rec["campaign_id"] ?? "");
|
|
282
|
+
if (!campaignId) continue;
|
|
283
|
+
const result = await claimCampaign(base, campaignId, credential, fetcher);
|
|
284
|
+
if (result.state === "claimed") {
|
|
285
|
+
const amount = asNumber(benefit?.["amount"]) ?? result.amount;
|
|
286
|
+
return { ...baseResult, state: "claimed", credit: amount, streakDays: null };
|
|
287
|
+
}
|
|
288
|
+
if (result.state === "already_claimed") {
|
|
289
|
+
alreadySeen = true;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (result.state === "error") {
|
|
293
|
+
return { ...baseResult, state: "error", error: "Campaign claim failed." };
|
|
294
|
+
}
|
|
295
|
+
// not_eligible (same-person block): stop trying further campaigns.
|
|
296
|
+
return { ...baseResult, state: "not_eligible", error: "Same-person claim already used today." };
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
...baseResult,
|
|
300
|
+
state: alreadySeen ? "already_claimed" : "unavailable",
|
|
301
|
+
error: alreadySeen ? null : "No daily CREDITS campaign claimable.",
|
|
302
|
+
};
|
|
303
|
+
}
|