cartethyia 2.4.5 → 2.4.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 +4 -1
- package/README.md +18 -0
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/{BannedPage-DUrXpp-X.js → BannedPage-QhvbJB9o.js} +1 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-Bcy150Jo.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-DyNXefph.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +1 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-D6jEU7nL.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-BJlMVN4U.js} +1 -1
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js +1 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-C3NIzsDx.js → OverviewPage-CQxXbo2g.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-BHW4Zt06.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js +1 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +1 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js +31 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-Djes-Epy.js → UsagePage-BAFaSDQV.js} +14 -14
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js +1 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +2 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/{app-BDJWMThu.js → app-Cjq7nrwu.js} +3 -3
- package/dist/dashboard/assets/app-Cjq7nrwu.js.gz +0 -0
- package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
- package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
- package/dist/dashboard/assets/{assert-Bwh6DJTK.js → assert-7J6_LVD3.js} +1 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-BTyGk_DL.js} +1 -1
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-bujTWYM4.js} +1 -1
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
- package/dist/dashboard/assets/{card-BTdbRFFg.js → card-B0Bnx5CA.js} +1 -1
- package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
- package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
- package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js +1 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js +1 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BWIP_EvW.js} +1 -1
- package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
- package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-BKcgVugF.js} +1 -1
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +4 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-CcWkGR4a.js +1 -0
- package/dist/dashboard/assets/entry-CcWkGR4a.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js +1 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
- package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
- package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js +1 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
- package/dist/dashboard/assets/{index-C4hXfAMs.js → index-D70pChfn.js} +2 -2
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1TCqM2D.js} +1 -1
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/{input-Bn4_kNav.js → input-B9oyU93y.js} +1 -1
- package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
- package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
- package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
- package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BArzXirQ.js} +1 -1
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-BVZzaoHF.js} +1 -1
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/{live-YlifPYox.js → live-EmuIayqp.js} +1 -1
- package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
- package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
- package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CVvHZ-qz.js → model-bans-CSPQ9xB0.js} +1 -1
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
- package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
- package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
- package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
- package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
- package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
- package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
- package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
- package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
- package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
- package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
- package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
- package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
- package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
- package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
- package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
- package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
- package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
- package/dist/dashboard/assets/{routing-jntw9-Ui.js → routing-BExgxxBU.js} +1 -1
- package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
- package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
- package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
- package/dist/dashboard/assets/{select-CGUuTQmj.js → select-C9mMoEpJ.js} +1 -1
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +1 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-Ca5HxI5i.js} +1 -1
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHKdg-pm.js → state-CzUPfLXo.js} +1 -1
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-DOUIky_i.js} +1 -1
- package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js +2 -0
- package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
- package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
- package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-D87FZwH0.js} +1 -1
- package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
- package/dist/dashboard/assets/wrench-B6E1oEuY.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 +123 -0
- package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
- package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
- package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
- package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
- package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
- package/package.json +25 -30
- package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
- package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
- package/scripts/cli/command/admin.ts +65 -0
- package/scripts/cli/command/console-menu.ts +227 -0
- package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
- package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
- package/scripts/cli/command/start.ts +13 -0
- package/scripts/cli/command/tray.ts +165 -0
- package/scripts/cli/command/update.ts +23 -0
- package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
- package/scripts/cli/gateway/spawn.ts +101 -0
- package/scripts/cli/gateway/stop.ts +51 -0
- package/scripts/cli/main.ts +94 -0
- package/scripts/cli/support/bun.ts +27 -0
- package/scripts/cli/support/env.ts +99 -0
- package/scripts/cli/support/platform.ts +64 -0
- package/scripts/cli/support/services.ts +88 -0
- package/scripts/cli/support/startup-signal.ts +23 -0
- package/scripts/cli/{version.ts → support/version.ts} +1 -1
- package/scripts/cli/tray/hidden-window.vbs +28 -0
- package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
- package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
- package/scripts/cli/ui/background.ts +49 -0
- package/src/boot/app.ts +3 -1
- package/src/boot/dependencies.ts +36 -13
- package/src/config/providers.ts +27 -1
- package/src/config/spec.ts +11 -0
- package/src/console/backup/routes.ts +8 -0
- package/src/console/cli-tools/contracts.ts +6 -6
- package/src/console/console-router.ts +5 -3
- package/src/console/dashboard-assets.ts +81 -11
- package/src/console/observability/contracts.ts +54 -22
- package/src/console/observability/routes.ts +31 -6
- package/src/console/observability/store.ts +68 -28
- package/src/console/providers/catalog/public-model-store.ts +91 -60
- package/src/console/providers/catalog/store.ts +5 -1
- package/src/console/quota/account-quota-tenant.ts +9 -6
- package/src/console/response-compression.ts +69 -2
- package/src/console/routing/pools/probe-verdict.ts +72 -0
- package/src/console/share/share-router.ts +7 -4
- package/src/console/share/share-stats.ts +12 -5
- package/src/console/shared/read-through-cache.ts +133 -7
- package/src/console/updates/repository-updates.ts +39 -34
- package/src/network/outbound-fetch.ts +113 -25
- package/src/network/tls/bun-client-hello.ts +112 -0
- package/src/observability/event-log.ts +29 -8
- package/src/observability/metrics.ts +40 -13
- package/src/observability/runtime-metrics.ts +7 -0
- package/src/persistence/schema.ts +40 -2
- package/src/protocol/response/codex.ts +9 -75
- package/src/protocol/tool-identity.ts +43 -9
- package/src/providers/authentication/jwt-validator.ts +130 -187
- package/src/providers/authentication/oauth-flow-store.ts +16 -9
- package/src/providers/default-registry.ts +4 -0
- package/src/providers/discovery/catalog/catalog-format.ts +20 -0
- package/src/providers/discovery/models-dev-catalog.ts +39 -22
- package/src/providers/discovery/probe-wire.ts +29 -5
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +3 -2
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +4 -0
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
- package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
- package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +106 -73
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
- package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
- package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
- package/src/providers/integrations/codex/codex.ts +5 -8
- package/src/providers/integrations/factory/factory-oauth.ts +7 -11
- package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
- package/src/providers/integrations/opencode/provider.ts +42 -3
- package/src/providers/integrations/qoder/qoder-oauth.ts +3 -9
- package/src/providers/integrations/shared/install-id.ts +29 -2
- package/src/providers/operations/client-versions.ts +11 -1
- package/src/providers/operations/model-catalog-cache.ts +18 -3
- package/src/providers/operations/provider-catalog-service.ts +76 -0
- package/src/transport/dispatch/attempt-finalize.ts +7 -1
- package/src/transport/dispatch/attempt-loop.ts +9 -7
- package/src/transport/dispatch/empty-stream.ts +17 -26
- package/src/transport/failure-policy.ts +20 -0
- package/src/transport/routing/route-catalog.ts +42 -3
- package/src/transport/surface/chat/encode.ts +18 -31
- package/src/transport/surface/messages/stream.ts +3 -2
- package/src/transport/translation/tool-repair.ts +37 -16
- package/src/workers/account-identity-sweep.ts +14 -12
- package/src/workers/daily-checkin.ts +62 -0
- package/src/workers/quota-refresh-sweep.ts +27 -2
- package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
- package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
- package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
- package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
- package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
- package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
- package/dist/dashboard/assets/SortableList-Bol1ASmI.js +0 -1
- package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
- package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
- package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
- package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
- package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
- package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
- package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
- package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
- package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
- package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
- package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
- package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
- package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
- package/scripts/cli/cli.ts +0 -771
- package/scripts/operations/probe-egress.ts +0 -335
- package/scripts/operations/restart.ts +0 -120
- package/scripts/shared/env.ts +0 -269
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
- /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
- /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
|
@@ -73,8 +73,9 @@ const BUDDY_IMAGE_MODEL_IDS: readonly { id: string; endpoint: string; ctx: numbe
|
|
|
73
73
|
* `seedance-2.5` is the text-to-video model the buddy facade exposes. It is a
|
|
74
74
|
* distinct service kind (`videoGeneration`), not a chat or image row, so it is
|
|
75
75
|
* built by {@link makeBuddyVideoModels} and reached through
|
|
76
|
-
* `/
|
|
77
|
-
*
|
|
76
|
+
* `/videos/generations` (relative to each variant's own base, which is why the
|
|
77
|
+
* WorkBuddy rows carry the `/v2` segment themselves) rather than a chat wire.
|
|
78
|
+
* CodeBuddy CN is deliberately absent — it publishes a different roster.
|
|
78
79
|
*/
|
|
79
80
|
const BUDDY_VIDEO_MODEL_IDS: readonly { id: string; endpoint: string; ctx: number; out: number }[] = [
|
|
80
81
|
{ id: "seedance-2.5", endpoint: "/videos/generations", ctx: 1_000_000, out: 128_000 },
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header family only the CLI identity sends.
|
|
3
|
+
*
|
|
4
|
+
* Everything here comes from the two reference gateways, which agree: the
|
|
5
|
+
* desktop product sends conversation/tracing headers and browser transport,
|
|
6
|
+
* while the CLI sends the agent envelope plus the OpenAI Node SDK fingerprint
|
|
7
|
+
* and no browser headers at all. A request carrying both halves is a hybrid no
|
|
8
|
+
* real client produces, which is what risk control keys on.
|
|
9
|
+
*
|
|
10
|
+
* Two references, two fingerprints: the hub derives OS/arch and a Node version
|
|
11
|
+
* from its own host, and codebuddy2api pins a fixed Linux/x64/v24.21.0 set.
|
|
12
|
+
* Neither can be right for every deployment, so the values here follow the
|
|
13
|
+
* host this gateway actually runs on — they are a fingerprint of the process,
|
|
14
|
+
* not a claim about a remote machine.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** OpenAI Node SDK 6.25.0 `x-stainless-*` fingerprint; both references agree. */
|
|
18
|
+
const STAINLESS_PACKAGE_VERSION = "6.25.0";
|
|
19
|
+
|
|
20
|
+
function hostOs(): string {
|
|
21
|
+
return process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "MacOS" : "Linux";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hostArch(): string {
|
|
25
|
+
return process.arch === "arm64" || process.arch === "ia32" ? process.arch : "x64";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The SDK fingerprint headers.
|
|
30
|
+
*
|
|
31
|
+
* `x-stainless-timeout` is in the hub only and `x-stainless-retry-count` is in
|
|
32
|
+
* both; the SDK sends both, so both go out.
|
|
33
|
+
*/
|
|
34
|
+
export function buddyStainlessHeaders(): Record<string, string> {
|
|
35
|
+
return {
|
|
36
|
+
"x-stainless-lang": "js",
|
|
37
|
+
"x-stainless-package-version": STAINLESS_PACKAGE_VERSION,
|
|
38
|
+
"x-stainless-os": hostOs(),
|
|
39
|
+
"x-stainless-arch": hostArch(),
|
|
40
|
+
"x-stainless-runtime": "node",
|
|
41
|
+
"x-stainless-runtime-version": process.version,
|
|
42
|
+
"x-stainless-retry-count": "0",
|
|
43
|
+
"x-stainless-timeout": "1200",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The trace family the CLI carries as a child span.
|
|
49
|
+
*
|
|
50
|
+
* The desktop product omits the parent (`with_parent=False` in the reference);
|
|
51
|
+
* the CLI conversation request is a child span, so it also sends `b3`'s fourth
|
|
52
|
+
* field and `X-B3-ParentSpanId`. `buddyConversationHeaders` already produces
|
|
53
|
+
* `traceparent`, `b3`, and the `X-B3-*` set, so only the parent is added here.
|
|
54
|
+
*/
|
|
55
|
+
export function buddyCliTraceHeaders(
|
|
56
|
+
conversation: Readonly<Record<string, string>>,
|
|
57
|
+
): Record<string, string> {
|
|
58
|
+
const traceId = conversation["x-trace-id"] ?? conversation["x-b3-traceid"];
|
|
59
|
+
const spanId = conversation["x-b3-spanid"];
|
|
60
|
+
if (traceId === undefined) return {};
|
|
61
|
+
const b3 = `${traceId}-${spanId ?? traceId.slice(0, 16)}-1-${traceId.slice(0, 16)}`;
|
|
62
|
+
return { ...(spanId === undefined ? {} : { "X-B3-ParentSpanId": spanId }), b3 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The full CLI identity envelope, minus what each caller already supplies.
|
|
67
|
+
*
|
|
68
|
+
* The caller keeps ownership of `User-Agent`, `X-IDE-*`, `X-Domain`, and the
|
|
69
|
+
* conversation family, because those vary by provider and realm: only the
|
|
70
|
+
* headers that are the same for every CLI dispatch are built here.
|
|
71
|
+
*/
|
|
72
|
+
export function buddyCliHeaders(args: { readonly userId: string }): Record<string, string> {
|
|
73
|
+
return {
|
|
74
|
+
"X-Agent-Intent": "craft",
|
|
75
|
+
"X-Agent-Type": "main",
|
|
76
|
+
// The reference enum is local-agent / cloud-agent / agent-agent, and its
|
|
77
|
+
// parser returns "other" for anything unknown or absent. A gateway is none
|
|
78
|
+
// of the three, so "other" is the honest value rather than a guess.
|
|
79
|
+
"X-Agent-Runtime": "other",
|
|
80
|
+
"X-User-Id": args.userId,
|
|
81
|
+
// Personal accounts are not an enterprise and the reference sends the flag
|
|
82
|
+
// either way (`true` only for an enterprise id), so the non-enterprise
|
|
83
|
+
// default goes out here. Step 3 replaces this with the account's real
|
|
84
|
+
// enterprise id once discovery carries one.
|
|
85
|
+
"X-Private-Data": "false",
|
|
86
|
+
...buddyStainlessHeaders(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -81,6 +81,10 @@ export function buddyConversationHeaders(
|
|
|
81
81
|
"x-request-id": messageId,
|
|
82
82
|
"x-root-request-id": conversationRequestId,
|
|
83
83
|
"x-trace-id": traceId,
|
|
84
|
+
// W3C trace context alongside the B3 set below: the upstream reads both,
|
|
85
|
+
// and every reference request carries `traceparent`, so sending only the
|
|
86
|
+
// B3 half leaves a header the channel check expects absent.
|
|
87
|
+
traceparent: `00-${b3TraceId}-${messageId.slice(0, 16)}-01`,
|
|
84
88
|
"x-b3-traceid": b3TraceId,
|
|
85
89
|
"x-b3-spanid": messageId.slice(0, 16),
|
|
86
90
|
"x-b3-sampled": "1",
|
|
@@ -37,6 +37,18 @@ export const BUDDY_CN_MODELS_PATH = "/console/enterprises/personal/models" as co
|
|
|
37
37
|
*/
|
|
38
38
|
export const BUDDY_INTL_MODELS_PATH = "/v2/enterprises/personal/models" as const;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Remote config path, answered on every realm's chat host.
|
|
42
|
+
*
|
|
43
|
+
* Both reference gateways treat this as the primary model catalog and the
|
|
44
|
+
* personal-models paths above as the narrower fallback: `/v3/config` carries
|
|
45
|
+
* the same `{ code, msg, data: { models, agents } }` envelope plus the
|
|
46
|
+
* per-product rosters the personal path does not always publish. It is tried
|
|
47
|
+
* first, and a failure falls through to the path the caller already passes, so
|
|
48
|
+
* a host that does not answer it behaves exactly as it does today.
|
|
49
|
+
*/
|
|
50
|
+
export const BUDDY_CONFIG_PATH = "/v3/config" as const;
|
|
51
|
+
|
|
40
52
|
/** Envelope code the console directory answers on success. */
|
|
41
53
|
const BUDDY_OK_CODE = 0;
|
|
42
54
|
|
|
@@ -92,13 +104,33 @@ export async function fetchBuddyDirectoryModels(args: {
|
|
|
92
104
|
readonly fetcher?: typeof fetch | undefined;
|
|
93
105
|
}): Promise<readonly ModelDefinition[] | null> {
|
|
94
106
|
const fetcher = args.fetcher ?? globalThis.fetch;
|
|
107
|
+
const primary = await fetchBuddyDirectoryOnce(args, fetcher, BUDDY_CONFIG_PATH);
|
|
108
|
+
if (primary !== null) return primary;
|
|
109
|
+
// Both references rank `/v3/config` above the personal-models path, but this
|
|
110
|
+
// repo has only ever called the latter, so the fallback keeps today's
|
|
111
|
+
// behaviour intact for a host that answers one and not the other.
|
|
112
|
+
return await fetchBuddyDirectoryOnce(args, fetcher, args.modelsPath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Reads one directory path; `null` on any transport, status, or shape miss. */
|
|
116
|
+
async function fetchBuddyDirectoryOnce(
|
|
117
|
+
args: {
|
|
118
|
+
readonly siteUrl: string;
|
|
119
|
+
readonly providerId: string;
|
|
120
|
+
readonly credential: string;
|
|
121
|
+
readonly endpoint: string | undefined;
|
|
122
|
+
readonly signal?: AbortSignal | undefined;
|
|
123
|
+
},
|
|
124
|
+
fetcher: typeof fetch,
|
|
125
|
+
path: string,
|
|
126
|
+
): Promise<readonly ModelDefinition[] | null> {
|
|
95
127
|
// The directory scores by account: an access token's `sub` is the uid. An
|
|
96
128
|
// opaque API key carries no identity, and the endpoint still answers for it,
|
|
97
129
|
// so the header is declared absent rather than guessed.
|
|
98
130
|
const uid = buddyAccountUid(args.credential);
|
|
99
131
|
let response: Response;
|
|
100
132
|
try {
|
|
101
|
-
response = await fetcher(`${siteOrigin(args.siteUrl)}${
|
|
133
|
+
response = await fetcher(`${siteOrigin(args.siteUrl)}${path}`, {
|
|
102
134
|
method: "GET",
|
|
103
135
|
headers: {
|
|
104
136
|
accept: "application/json",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProviderDispatchContext } from "../../provider-contracts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which upstream client a buddy provider presents itself as.
|
|
5
|
+
*
|
|
6
|
+
* `desktop` is what every buddy provider sends today and stays the default for
|
|
7
|
+
* all four. `cli` is the identity the reference gateways use for CLI traffic: a
|
|
8
|
+
* different header set, a different User-Agent formula, and a different CN
|
|
9
|
+
* upstream.
|
|
10
|
+
*
|
|
11
|
+
* Nothing flips to `cli` implicitly. Being wrong here is what gets an account
|
|
12
|
+
* blocked upstream, so a provider changes identity only when an operator or an
|
|
13
|
+
* account says so.
|
|
14
|
+
*/
|
|
15
|
+
export type BuddyIdentity = "desktop" | "cli";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the identity one buddy dispatch presents.
|
|
19
|
+
*
|
|
20
|
+
* Precedence is account then provider: an account authorized as a CLI keeps
|
|
21
|
+
* that identity even when the provider default changes, because the upstream
|
|
22
|
+
* recorded it at login. The provider-level flag is
|
|
23
|
+
* `compatibilityProfile.cli_identity`, already declared, validated, and
|
|
24
|
+
* defaulted — it just never reached these adapters before.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveBuddyIdentity(
|
|
27
|
+
context: ProviderDispatchContext | undefined,
|
|
28
|
+
profileIdentity?: boolean | undefined,
|
|
29
|
+
): BuddyIdentity {
|
|
30
|
+
const accountRaw = context?.credential.auth_state?.["cliIdentity"];
|
|
31
|
+
if (accountRaw === true) return "cli";
|
|
32
|
+
if (accountRaw === false) return "desktop";
|
|
33
|
+
return profileIdentity === true ? "cli" : "desktop";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The upstream's `X-User-Id` for a buddy dispatch.
|
|
38
|
+
*
|
|
39
|
+
* The reference CLI sends the literal `anonymous` when it has no uid rather
|
|
40
|
+
* than omitting the header, so an unauthenticated dispatch still looks like a
|
|
41
|
+
* client rather than like a caller that forgot a header.
|
|
42
|
+
*/
|
|
43
|
+
export function buddyUserId(context?: ProviderDispatchContext): string {
|
|
44
|
+
const accountId = context?.credential.account_id;
|
|
45
|
+
return accountId !== undefined && accountId.length > 0 ? accountId : "anonymous";
|
|
46
|
+
}
|
|
@@ -280,7 +280,43 @@ export class BuddyOAuthClient extends OAuthDeviceFlow {
|
|
|
280
280
|
reason: `${this.#variant.providerId} token response omitted accessToken`,
|
|
281
281
|
};
|
|
282
282
|
}
|
|
283
|
-
|
|
283
|
+
// The profile nickname is a second, authorized request the reference makes
|
|
284
|
+
// once the token lands — the JWT carries the email but not always a
|
|
285
|
+
// display name. Best-effort by design: a login must not fail because the
|
|
286
|
+
// profile endpoint was unreachable, so every miss falls back to the label
|
|
287
|
+
// the token itself yields.
|
|
288
|
+
const nickname = await this.fetchAccountNickname(deviceAuthId, access);
|
|
289
|
+
if (nickname === undefined) return { status: "complete", result: tokenResult(this.#variant, payload) };
|
|
290
|
+
const result = tokenResult(this.#variant, payload);
|
|
291
|
+
return {
|
|
292
|
+
status: "complete",
|
|
293
|
+
result: { ...result, accountLabel: result.accountLabel ?? nickname },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Reads the account profile nickname, or `undefined` on any miss. */
|
|
298
|
+
private async fetchAccountNickname(
|
|
299
|
+
deviceAuthId: string,
|
|
300
|
+
accessToken: string,
|
|
301
|
+
): Promise<string | undefined> {
|
|
302
|
+
try {
|
|
303
|
+
// Both references call `/v2/plugin/login/account` with the token after
|
|
304
|
+
// the poll succeeds; the poll URL is the same versioned plugin root, so
|
|
305
|
+
// the profile path is derived from it rather than declared per variant.
|
|
306
|
+
const url = `${this.#variant.devicePollUrl.replace("/plugin/auth/token", "/plugin/login/account")}?state=${encodeURIComponent(deviceAuthId)}`;
|
|
307
|
+
const response = await this.fetchFn(url, {
|
|
308
|
+
method: "GET",
|
|
309
|
+
headers: {
|
|
310
|
+
...(await devicePollHeaders(this.#variant)),
|
|
311
|
+
authorization: `Bearer ${accessToken}`,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
if (!response.ok) return undefined;
|
|
315
|
+
const profile = buddyResponseData((await response.json()) as unknown);
|
|
316
|
+
return nonEmptyTrimmedString(profile?.nickname);
|
|
317
|
+
} catch {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
284
320
|
}
|
|
285
321
|
|
|
286
322
|
override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Deliberately not a request-path store: this is written by a maintenance pass
|
|
11
11
|
* and read by the console. Nothing here can fail a dispatch.
|
|
12
12
|
*/
|
|
13
|
-
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
|
13
|
+
import { and, desc, eq, gte, inArray, sql } from "drizzle-orm";
|
|
14
14
|
import type { CartethyiaDatabase } from "../../../persistence/db-handle";
|
|
15
15
|
import { buddyUsageSnapshots } from "../../../persistence/schema";
|
|
16
16
|
import type { BuddyUsageRow } from "./buddy-usage-shared";
|
|
@@ -23,6 +23,11 @@ export interface BuddyUsageSnapshotRow extends BuddyUsageRow {
|
|
|
23
23
|
/** Days of usage retained. The upstream's own window is 30. */
|
|
24
24
|
const RETENTION_DAYS = 30;
|
|
25
25
|
|
|
26
|
+
/** Oldest day a stored bucket can hold, as the `YYYY-MM-DD` key the table uses. */
|
|
27
|
+
function retentionCutoff(now: Date = new Date()): string {
|
|
28
|
+
return new Date(now.getTime() - RETENTION_DAYS * 86_400_000).toISOString().slice(0, 10);
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* Upserts one account's usage buckets.
|
|
28
33
|
*
|
|
@@ -72,7 +77,31 @@ export async function saveBuddyUsageSnapshots(
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
/**
|
|
75
|
-
*
|
|
80
|
+
* Most (day, model) buckets one day can hold, used only to bound the read.
|
|
81
|
+
*
|
|
82
|
+
* The renderer keeps {@link DEFAULT_LIMIT_PER_ACCOUNT} entries per account plus
|
|
83
|
+
* every bucket of the newest day, so this is a ceiling on a pathological table
|
|
84
|
+
* rather than a number the page depends on. Set well above any real model count
|
|
85
|
+
* so the cap can only ever trigger on a table that grew without pruning.
|
|
86
|
+
*/
|
|
87
|
+
const MODELS_PER_DAY_CEILING = 64;
|
|
88
|
+
|
|
89
|
+
/** Buckets per account the console renders. */
|
|
90
|
+
const DEFAULT_LIMIT_PER_ACCOUNT = 30;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reads each account's stored usage, newest day first.
|
|
94
|
+
*
|
|
95
|
+
* Bounded on both axes, because it used to be bounded on neither: with no day
|
|
96
|
+
* filter and no `LIMIT`, an account fetched every bucket it had ever stored
|
|
97
|
+
* (~1500 rows for a 30-day window) to render 30 of them, on the route the
|
|
98
|
+
* dashboard polls.
|
|
99
|
+
*
|
|
100
|
+
* - The day window is semantic, not a truncation: {@link pruneBuddyUsageSnapshots}
|
|
101
|
+
* already drops everything older, so no row it excludes could have been
|
|
102
|
+
* rendered anyway.
|
|
103
|
+
* - The row ceiling is the backstop. It is sized per account, so one large
|
|
104
|
+
* account cannot starve the others in a shared `LIMIT`.
|
|
76
105
|
*
|
|
77
106
|
* `numeric` comes back from the driver as a string, so the credit is parsed
|
|
78
107
|
* rather than trusted; an unparseable row is reported as 0 instead of being
|
|
@@ -81,7 +110,7 @@ export async function saveBuddyUsageSnapshots(
|
|
|
81
110
|
export async function readBuddyUsageSnapshots(
|
|
82
111
|
db: CartethyiaDatabase,
|
|
83
112
|
accountIds: readonly string[],
|
|
84
|
-
limitPerAccount =
|
|
113
|
+
limitPerAccount = DEFAULT_LIMIT_PER_ACCOUNT,
|
|
85
114
|
): Promise<Map<string, readonly BuddyUsageSnapshotRow[]>> {
|
|
86
115
|
const result = new Map<string, BuddyUsageSnapshotRow[]>();
|
|
87
116
|
if (accountIds.length === 0) return result;
|
|
@@ -102,8 +131,14 @@ export async function readBuddyUsageSnapshots(
|
|
|
102
131
|
syncedAt: buddyUsageSnapshots.syncedAt,
|
|
103
132
|
})
|
|
104
133
|
.from(buddyUsageSnapshots)
|
|
105
|
-
.where(
|
|
106
|
-
|
|
134
|
+
.where(
|
|
135
|
+
and(
|
|
136
|
+
inArray(buddyUsageSnapshots.accountId, [...accountIds]),
|
|
137
|
+
gte(buddyUsageSnapshots.dayKey, retentionCutoff()),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
.orderBy(desc(buddyUsageSnapshots.dayKey))
|
|
141
|
+
.limit(accountIds.length * limitPerAccount * MODELS_PER_DAY_CEILING);
|
|
107
142
|
} catch {
|
|
108
143
|
return result;
|
|
109
144
|
}
|
|
@@ -109,8 +109,8 @@ export const CODEBUDDY_IMAGE_MODELS: readonly ModelDefinition[] = makeBuddyImage
|
|
|
109
109
|
/**
|
|
110
110
|
* CodeBuddy's video-generation rows (`seedance-2.5`).
|
|
111
111
|
*
|
|
112
|
-
* Reached through `/
|
|
113
|
-
* video path rather than a chat-completions path.
|
|
112
|
+
* Reached through `/videos/generations` on the `/v2` base, so the endpoint is
|
|
113
|
+
* the upstream video path rather than a chat-completions path.
|
|
114
114
|
*/
|
|
115
115
|
export const CODEBUDDY_VIDEO_MODELS: readonly ModelDefinition[] = makeBuddyVideoModels(
|
|
116
116
|
"cb",
|
|
@@ -1,73 +1,106 @@
|
|
|
1
|
-
import { withBearerAuthentication, type OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
|
|
2
|
-
import type { CanonicalRequest } from "../../../../protocol/canonical-model";
|
|
3
|
-
import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
|
|
4
|
-
import { resolveInboundSessionId } from "../../../operations/session-resolution";
|
|
5
|
-
import { buddyConversationHeaders } from "../buddy-conversation-headers";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
import { withBearerAuthentication, type OpenAICompatibleAdapterConfig } from "../../../adapters/compatible-adapter";
|
|
2
|
+
import type { CanonicalRequest } from "../../../../protocol/canonical-model";
|
|
3
|
+
import type { ProviderDispatchContext, ProviderDispatchTarget, ProviderId } from "../../../provider-contracts";
|
|
4
|
+
import { resolveInboundSessionId } from "../../../operations/session-resolution";
|
|
5
|
+
import { buddyConversationHeaders } from "../buddy-conversation-headers";
|
|
6
|
+
import { buddyUserId, resolveBuddyIdentity } from "../buddy-identity";
|
|
7
|
+
import { buildCodeBuddyUserAgent, getCodeBuddyCnVersion, getCodeBuddyVersion } from "../../../operations/client-versions";
|
|
8
|
+
import { readBuddyDeviceToken } from "../workbuddy/shared";
|
|
9
|
+
import { buddyCliHeaders, buddyCliTraceHeaders } from "../buddy-cli-headers";
|
|
10
|
+
|
|
11
|
+
/** Client identity variant behind the per-request CodeBuddy headers. */
|
|
12
|
+
export type CodeBuddyVariant = "IDE" | "CLI";
|
|
13
|
+
|
|
14
|
+
export function codebuddyDomain(variant: CodeBuddyVariant): string {
|
|
15
|
+
return variant === "IDE" ? "www.codebuddy.ai" : "copilot.tencent.com";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Per-request CodeBuddy headers — exact provider contract shared by the
|
|
21
|
+
* international (IDE identity) and CN (CLI identity). The stable conversation
|
|
22
|
+
* header is preserved from the client when available; request IDs rotate.
|
|
23
|
+
*
|
|
24
|
+
* `cliIdentity` selects the real CLI contract rather than today's hybrid: the
|
|
25
|
+
* reference CLI is issued by the OpenAI Node SDK, so it carries no browser
|
|
26
|
+
* headers and no device token, and it adds the agent envelope. Left unset it
|
|
27
|
+
* keeps the current behaviour for every provider.
|
|
28
|
+
*/
|
|
29
|
+
export async function codebuddyHeaders(
|
|
30
|
+
variant: CodeBuddyVariant,
|
|
31
|
+
context?: ProviderDispatchContext,
|
|
32
|
+
request?: CanonicalRequest,
|
|
33
|
+
cliIdentity?: boolean,
|
|
34
|
+
): Promise<Record<string, string>> {
|
|
35
|
+
const identity = variant === "IDE" ? "IDE" : "CLI";
|
|
36
|
+
const ua = buildCodeBuddyUserAgent(identity);
|
|
37
|
+
const deviceToken = readBuddyDeviceToken(context);
|
|
38
|
+
const conversationId = resolveInboundSessionId(context, request);
|
|
39
|
+
const conversation = buddyConversationHeaders({
|
|
40
|
+
...(conversationId === undefined ? {} : { conversationId }),
|
|
41
|
+
});
|
|
42
|
+
if (resolveBuddyIdentity(context, cliIdentity) === "cli") {
|
|
43
|
+
return {
|
|
44
|
+
// The reference is explicit: the official CLI sends `Accept:
|
|
45
|
+
// application/json` and no browser header at all — no
|
|
46
|
+
// `X-Requested-With`, no `x-codebuddy-request`, no device token. A
|
|
47
|
+
// request carrying both halves is a hybrid no real client produces.
|
|
48
|
+
accept: "application/json",
|
|
49
|
+
"User-Agent": `CLI/${getCodeBuddyVersion()} CodeBuddy/${getCodeBuddyVersion()}`,
|
|
50
|
+
"X-Product": "SaaS",
|
|
51
|
+
"X-IDE-Type": "CLI",
|
|
52
|
+
"X-IDE-Name": "CLI",
|
|
53
|
+
"X-IDE-Version": getCodeBuddyVersion(),
|
|
54
|
+
"X-Domain": codebuddyDomain(variant),
|
|
55
|
+
...conversation,
|
|
56
|
+
...buddyCliTraceHeaders(conversation),
|
|
57
|
+
...buddyCliHeaders({ userId: buddyUserId(context) }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
accept: "text/event-stream",
|
|
62
|
+
"User-Agent": ua,
|
|
63
|
+
"X-Product": "SaaS",
|
|
64
|
+
"X-IDE-Type": identity,
|
|
65
|
+
"X-IDE-Name": identity,
|
|
66
|
+
// Every reference branch sends this, in both the desktop and the CLI
|
|
67
|
+
// product; its absence here is what made a CodeBuddy dispatch the only
|
|
68
|
+
// buddy caller with no client version on the wire.
|
|
69
|
+
"X-IDE-Version": identity === "CLI" ? getCodeBuddyCnVersion() : getCodeBuddyVersion(),
|
|
70
|
+
"X-Domain": codebuddyDomain(variant),
|
|
71
|
+
"x-requested-with": "XMLHttpRequest",
|
|
72
|
+
"x-codebuddy-request": "1",
|
|
73
|
+
// Same family WorkBuddy sends: the upstream's channel check reads the
|
|
74
|
+
// turn-level aggregation key, and a chat request without it is refused with
|
|
75
|
+
// `400 · 11-128 "conversation id missing"`.
|
|
76
|
+
...conversation,
|
|
77
|
+
...(deviceToken === undefined ? {} : { "X-Device-Token": deviceToken }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Shared OpenAI-compatible chat-adapter config for both CodeBuddy variants:
|
|
83
|
+
* bearer auth, chat-only wire contract, per-request identity headers, and the
|
|
84
|
+
* variant's payload hook.
|
|
85
|
+
*/
|
|
86
|
+
export function codebuddyAdapterConfig(args: {
|
|
87
|
+
providerId: ProviderId;
|
|
88
|
+
baseUrl: string;
|
|
89
|
+
variant: CodeBuddyVariant;
|
|
90
|
+
prePayload: (
|
|
91
|
+
payload: Record<string, unknown>,
|
|
92
|
+
request: CanonicalRequest,
|
|
93
|
+
candidate: ProviderDispatchTarget,
|
|
94
|
+
) => void;
|
|
95
|
+
fetchImpl?: typeof fetch;
|
|
96
|
+
cliIdentity?: boolean;
|
|
97
|
+
}): OpenAICompatibleAdapterConfig {
|
|
98
|
+
return withBearerAuthentication({
|
|
99
|
+
provider_id: args.providerId,
|
|
100
|
+
base_url: args.baseUrl,
|
|
101
|
+
buildExtraHeaders: (context, request) =>
|
|
102
|
+
codebuddyHeaders(args.variant, context, request, args.cliIdentity),
|
|
103
|
+
prePayload: args.prePayload,
|
|
104
|
+
...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { DiscoveryInput } from "../../../discovery/discovery-types";
|
|
|
5
5
|
import type { ModelDefinition } from "../../../provider-model-definition";
|
|
6
6
|
import { providerBaseUrl } from "../../../provider-metadata";
|
|
7
7
|
import { getWorkBuddyCnClientVersion } from "../../../operations/client-versions";
|
|
8
|
-
import {
|
|
8
|
+
import { BUDDY_CN_MODELS_PATH, fetchBuddyDirectoryModels } from "../buddy-discovery-shared";
|
|
9
9
|
import { CODEBUDDY_CN_MODELS } from "../codebuddy/cn-provider";
|
|
10
10
|
import { workbuddyPrePayload, WORKBUDDY_SYSTEM_PROMPT } from "./provider";
|
|
11
11
|
import { workbuddyAdapterConfig, workbuddyHeaders, WORKBUDDY_CHAT_PATH } from "./shared";
|
|
@@ -28,7 +28,10 @@ export async function discoverWorkBuddyCnModels(
|
|
|
28
28
|
siteUrl: WORKBUDDY_CN_BASE_URL,
|
|
29
29
|
providerId: WORKBUDDY_CN_PROVIDER_ID,
|
|
30
30
|
credential: input.credential,
|
|
31
|
-
|
|
31
|
+
// The CN host answers the `/console/…` directory, exactly like the CodeBuddy
|
|
32
|
+
// CN host does; the international path was inherited from the intl provider
|
|
33
|
+
// and the CN edge answers it with a 500 rather than a 404.
|
|
34
|
+
modelsPath: BUDDY_CN_MODELS_PATH,
|
|
32
35
|
endpoint: WORKBUDDY_CHAT_PATH,
|
|
33
36
|
...(input.signal ? { signal: input.signal } : {}),
|
|
34
37
|
...(input.fetcher ? { fetcher: input.fetcher } : {}),
|