cartethyia 2.5.4 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-mUfcknqm.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-uPiGVTTK.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-js6TJvdr.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-WUCXorXw.js → select-BYR_5K2c.js} +1 -1
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +54 -54
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +61 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/{command/update.ts → update.ts} +39 -39
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +697 -693
- package/src/boot/dependencies.ts +647 -646
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +20 -30
- package/src/config/telemetry.ts +0 -20
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/data-management/contracts.ts +13 -6
- package/src/console/data-management/operations.ts +23 -18
- package/src/console/data-management/routes.ts +0 -1
- package/src/console/domain-registration.ts +10 -5
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +39 -16
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +4 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/store.ts +1553 -1553
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +58 -2
- package/src/console/settings/store.ts +30 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +80 -78
- package/src/observability/event-day-index.ts +13 -15
- package/src/observability/event-log.ts +4 -4
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +151 -33
- package/src/observability/payload-store.ts +397 -440
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +1406 -1402
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +236 -237
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +138 -4
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/messages.ts +4 -5
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/configured-openai-providers.ts +134 -136
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +132 -132
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
- package/src/providers/integrations/cursor/cursor.ts +11 -163
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +8 -8
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +1528 -1523
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-metadata.ts +4 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +504 -504
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +5 -6
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +2 -2
- package/src/transport/request/state.ts +12 -8
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +8 -1
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
- package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
- package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
- package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
- package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
- package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
- package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
- package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
- package/dist/dashboard/assets/app-COlK95oi.css +0 -2
- package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
- package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
- package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
- package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
- package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
- package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
- package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
- package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
- package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
- package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
- package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
- package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
- package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
- package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
- package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
- package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
- package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
- package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
- package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
- package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
- package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
- package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
- package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
- package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
- package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
- package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
- package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
- package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
package/src/protocol/codecs.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* `src/protocol/`. This is the single home for the generic guards, UTF-16
|
|
4
4
|
* well-formedness coercion, JSON/argument helpers, hash helpers, endpoint
|
|
5
5
|
* URL construction, image-source resolution, and custom-header validation
|
|
6
|
-
* that the per-wire request/response codecs build on
|
|
7
|
-
* across the Claude/Codex protocol modules and `providers/compatible-adapter`.
|
|
6
|
+
* that the per-wire request/response codecs build on.
|
|
8
7
|
*
|
|
9
8
|
* Provider-specific primitives that depend on provider identity (e.g. Codex
|
|
10
9
|
* identity headers) deliberately live with their provider, not here.
|
|
@@ -273,6 +272,46 @@ const SUPPORTED_SCHEMA_KEYS: Readonly<Record<string, true>> = Object.freeze({
|
|
|
273
272
|
maxProperties: true,
|
|
274
273
|
});
|
|
275
274
|
|
|
275
|
+
/**
|
|
276
|
+
* True when a schema string carries a backslash before an underscore.
|
|
277
|
+
*
|
|
278
|
+
* Some clients emit a property or pattern key containing a literal `\_` (an
|
|
279
|
+
* escaped underscore). Upstreams reject it, so the escape has to be stripped —
|
|
280
|
+
* and stripping it can collapse two previously distinct keys onto the same
|
|
281
|
+
* name, which is why {@link mergeCollidingKeys} exists below.
|
|
282
|
+
*/
|
|
283
|
+
function hasUnderscoreEscape(value: string): boolean {
|
|
284
|
+
return /\\+_/.test(value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Merges map entries whose normalized keys collide, rather than overwriting.
|
|
289
|
+
*
|
|
290
|
+
* `allOf` accepts boolean schemas, so neither constraint is lost — a plain
|
|
291
|
+
* overwrite would silently drop one of two rules the caller actually sent.
|
|
292
|
+
*/
|
|
293
|
+
function mergeCollidingKeys(entries: ReadonlyArray<readonly [string, unknown]>): Record<string, unknown> {
|
|
294
|
+
const merged: Record<string, unknown> = {};
|
|
295
|
+
for (const [key, value] of entries) {
|
|
296
|
+
const existing = merged[key];
|
|
297
|
+
if (existing === undefined) {
|
|
298
|
+
merged[key] = value;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
merged[key] = { allOf: [existing, value] };
|
|
302
|
+
}
|
|
303
|
+
return merged;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** One schema map's keys, with `\_` escapes stripped and collisions merged. */
|
|
307
|
+
function normalizeSchemaMapKeys(map: Record<string, unknown>): Record<string, unknown> {
|
|
308
|
+
const entries = Object.entries(map);
|
|
309
|
+
if (!entries.some(([key]) => hasUnderscoreEscape(key))) return map;
|
|
310
|
+
return mergeCollidingKeys(
|
|
311
|
+
entries.map(([key, value]) => [key.replace(/\\+_/g, "_"), value] as const),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
276
315
|
export function sanitizeSchemaForAnthropic(value: unknown): unknown {
|
|
277
316
|
return sanitizeSchemaWalk(value, ROOT_WALK);
|
|
278
317
|
}
|
|
@@ -292,10 +331,21 @@ function sanitizeSchemaWalk(value: unknown, walk: RewriteWalk): unknown {
|
|
|
292
331
|
// preserve them while sanitizing each property's schema recursively.
|
|
293
332
|
if (key === "properties" && isRecord(child)) {
|
|
294
333
|
out.properties = Object.fromEntries(
|
|
295
|
-
|
|
334
|
+
// Escaped-underscore keys are normalized first: two names differing only
|
|
335
|
+
// by an escape would otherwise survive as two properties and be rebuilt
|
|
336
|
+
// into a `required` list the upstream rejects.
|
|
337
|
+
Object.entries(normalizeSchemaMapKeys(child)).map(([name, schema]) => [
|
|
338
|
+
name,
|
|
339
|
+
sanitizeSchemaWalk(schema, inner),
|
|
340
|
+
]),
|
|
296
341
|
);
|
|
297
342
|
continue;
|
|
298
343
|
}
|
|
344
|
+
// `patternProperties` is deliberately NOT re-emitted: it is absent from
|
|
345
|
+
// `SUPPORTED_SCHEMA_KEYS` because the Anthropic wire does not accept it, and
|
|
346
|
+
// writing it here would be undone by the keyword filter a few lines down
|
|
347
|
+
// anyway. Its keys are still walked below, so nested schemas are sanitized
|
|
348
|
+
// and an escaped-underscore key inside one is still normalized.
|
|
299
349
|
// Drop wire-unsupported keywords so a strict upstream never 400s on
|
|
300
350
|
// them — but never invent caller text to say so. The dropped names are
|
|
301
351
|
// debug-logged by the caller-facing builder when that matters.
|
|
@@ -676,12 +726,96 @@ export function canonicalTerminal(input: {
|
|
|
676
726
|
};
|
|
677
727
|
}
|
|
678
728
|
|
|
729
|
+
/**
|
|
730
|
+
* JSON Schema keywords. A parameter root containing any of these is already a
|
|
731
|
+
* schema object, not a legacy property-name → schema map.
|
|
732
|
+
*/
|
|
733
|
+
const SCHEMA_KEYWORDS: ReadonlySet<string> = new Set([
|
|
734
|
+
"$schema", "$id", "$ref", "$anchor", "$dynamicRef", "$dynamicAnchor", "$defs",
|
|
735
|
+
"$vocabulary", "$comment", "definitions", "type", "properties", "patternProperties",
|
|
736
|
+
"additionalProperties", "unevaluatedProperties", "propertyNames", "required",
|
|
737
|
+
"dependentRequired", "dependentSchemas", "dependencies", "minProperties", "maxProperties",
|
|
738
|
+
"allOf", "anyOf", "oneOf", "not", "if", "then", "else", "items", "prefixItems",
|
|
739
|
+
"additionalItems", "contains", "unevaluatedItems", "minItems", "maxItems", "uniqueItems",
|
|
740
|
+
"minContains", "maxContains", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum",
|
|
741
|
+
"multipleOf", "minLength", "maxLength", "pattern", "format", "contentEncoding",
|
|
742
|
+
"contentMediaType", "contentSchema", "enum", "const", "title", "description", "default",
|
|
743
|
+
"examples", "deprecated", "readOnly", "writeOnly",
|
|
744
|
+
]);
|
|
745
|
+
|
|
746
|
+
/** The JSON Schema primitive type names. */
|
|
747
|
+
const SCHEMA_TYPES: ReadonlySet<string> = new Set([
|
|
748
|
+
"string", "number", "integer", "boolean", "object", "array", "null",
|
|
749
|
+
]);
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Rewrites one legacy tool parameter root into an explicit object schema.
|
|
753
|
+
*
|
|
754
|
+
* Two shapes reach here and are both rejected upstream as-is:
|
|
755
|
+
*
|
|
756
|
+
* - **An empty root.** `{}` carries no `type`, and a schema with no type is not
|
|
757
|
+
* a schema the upstream will accept — it answers `invalid_function_parameters`
|
|
758
|
+
* (CodeBuddy `11129`).
|
|
759
|
+
* - **A legacy property-name → schema map.** Every field names a parameter and
|
|
760
|
+
* carries its own boolean `required` flag, with no `{"type":"object",
|
|
761
|
+
* "properties":…}` wrapper around them. Old clients in the wild still emit
|
|
762
|
+
* this; the upstream wants the wrapper and the `required` array.
|
|
763
|
+
*
|
|
764
|
+
* Anything ambiguous is returned **untouched**. A root that already holds schema
|
|
765
|
+
* keywords, or one whose fields do not all look like `{type: <primitive>}`
|
|
766
|
+
* entries, is left alone: guessing there would rewrite a valid schema into a
|
|
767
|
+
* wrong one, which is worse than the refusal it was trying to avoid.
|
|
768
|
+
*/
|
|
769
|
+
export function normalizeToolParameterRoot(value: unknown): unknown {
|
|
770
|
+
if (!isRecord(value)) return value;
|
|
771
|
+
if (Object.keys(value).length === 0) return { type: "object", properties: {} };
|
|
772
|
+
// Schema keywords (or an extension/annotation key) mean this is a real
|
|
773
|
+
// schema object already, not a property map.
|
|
774
|
+
if (SCHEMA_KEYWORDS.has(Object.keys(value)[0] ?? "")) return value;
|
|
775
|
+
if (Object.keys(value).some((name) => SCHEMA_KEYWORDS.has(name))) return value;
|
|
776
|
+
if (Object.keys(value).some((name) => name.startsWith("$") || name.startsWith("x-")))
|
|
777
|
+
return value;
|
|
778
|
+
const entries = Object.entries(value);
|
|
779
|
+
// A legacy map is identified by its per-field boolean `required`. Without at
|
|
780
|
+
// least one, this is not recognisably the legacy shape.
|
|
781
|
+
if (!entries.some(([, field]) => isRecord(field) && typeof field["required"] === "boolean"))
|
|
782
|
+
return value;
|
|
783
|
+
if (
|
|
784
|
+
!entries.every(
|
|
785
|
+
([, field]) =>
|
|
786
|
+
isRecord(field) &&
|
|
787
|
+
typeof field["type"] === "string" &&
|
|
788
|
+
SCHEMA_TYPES.has(field["type"]) &&
|
|
789
|
+
(!("required" in field) || typeof field["required"] === "boolean"),
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
return value;
|
|
793
|
+
|
|
794
|
+
const properties: Record<string, unknown> = {};
|
|
795
|
+
const required: string[] = [];
|
|
796
|
+
for (const [name, field] of entries) {
|
|
797
|
+
const copy = { ...(field as Record<string, unknown>) };
|
|
798
|
+
if (copy["required"] === true) required.push(name);
|
|
799
|
+
delete copy["required"];
|
|
800
|
+
properties[name] = copy;
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
type: "object",
|
|
804
|
+
properties,
|
|
805
|
+
...(required.length > 0 ? { required } : {}),
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
|
|
679
809
|
/**
|
|
680
810
|
* Defaults a tool schema's `required` list to all declared properties while
|
|
681
811
|
* preserving an explicit list, including optional properties.
|
|
812
|
+
*
|
|
813
|
+
* Also normalizes a legacy parameter root first (see
|
|
814
|
+
* {@link normalizeToolParameterRoot}), so a caller sending an empty root or a
|
|
815
|
+
* property-name → schema map still ships a schema the upstream accepts.
|
|
682
816
|
*/
|
|
683
817
|
export function completeRequiredSchema(value: unknown): unknown {
|
|
684
|
-
return completeRequiredWalk(value, ROOT_WALK);
|
|
818
|
+
return completeRequiredWalk(normalizeToolParameterRoot(value), ROOT_WALK);
|
|
685
819
|
}
|
|
686
820
|
|
|
687
821
|
function completeRequiredWalk(value: unknown, walk: RewriteWalk): unknown {
|
|
@@ -12,6 +12,8 @@ export type GatewayErrorCode =
|
|
|
12
12
|
| "accounts_unavailable"
|
|
13
13
|
| "accounts_rate_limited"
|
|
14
14
|
| "capacity_exhausted"
|
|
15
|
+
| "provider_capacity_exhausted"
|
|
16
|
+
| "upstream_queued"
|
|
15
17
|
| "proxy_pool_capacity_exceeded"
|
|
16
18
|
| "proxy_pool_provider_capacity_exceeded"
|
|
17
19
|
| "proxy_pool_blocked"
|
|
@@ -110,11 +112,11 @@ function stripLegacyOriginLabel(message: string): string {
|
|
|
110
112
|
* stamped into it.
|
|
111
113
|
*
|
|
112
114
|
* The code is not lost — it travels in the envelope's `error.code` field, and
|
|
113
|
-
* the HTTP status carries the class.
|
|
115
|
+
* the HTTP status carries the class. Stamping it into the text
|
|
114
116
|
* (`accounts_unavailable: Model 'x' has no available account (52 candidate(s)
|
|
115
|
-
* unusable: credit_floor_reached, model_cooldown)`)
|
|
116
|
-
* names in front of the operator's sentence and
|
|
117
|
-
* while adding nothing the field
|
|
117
|
+
* unusable: credit_floor_reached, model_cooldown)`) would put internal enum
|
|
118
|
+
* names in front of the operator's sentence and make the message unreadable
|
|
119
|
+
* while adding nothing the field does not already carry.
|
|
118
120
|
*
|
|
119
121
|
* Stripping is idempotent, and that is the point: a message that already
|
|
120
122
|
* carries a `code:` prefix — stamped by an older builder, or by a code path
|
|
@@ -353,7 +353,24 @@ export function canonicalToChatPayload(
|
|
|
353
353
|
// `display: "omitted"`, and dropping it stripped the replay the
|
|
354
354
|
// provider demands on the next request. `reasoning_content` is an
|
|
355
355
|
// assistant-only field, so it is emitted here and nowhere else.
|
|
356
|
-
|
|
356
|
+
//
|
|
357
|
+
// Load-bearing requires *content* in the replay: a non-empty summary
|
|
358
|
+
// or a non-empty serialised payload. Pushing `{content:"",
|
|
359
|
+
// reasoning_content:""}` is the opposite of load-bearing — the
|
|
360
|
+
// upstream reads it as "thinking mode with the reasoning stripped"
|
|
361
|
+
// and rejects the whole request (`WorkBuddy/CodeBuddy 11151 "history
|
|
362
|
+
// not found"`). The canonical form that reaches this branch with no
|
|
363
|
+
// trace is an input `reasoning` item whose `summary` was absent and
|
|
364
|
+
// whose `payload` was `null` — a signature-less thinking block
|
|
365
|
+
// emitted under `display: "omitted"` and already stripped by an
|
|
366
|
+
// earlier hop. There is nothing to replay, so the turn is dropped
|
|
367
|
+
// instead of being forwarded as an invalid stub.
|
|
368
|
+
// This non-empty echo is load-bearing for non-buddy
|
|
369
|
+
// OpenAI-compatible providers. Buddy providers deliberately drop
|
|
370
|
+
// the resulting bare stub later in `dropEmptyBuddyMessages`
|
|
371
|
+
// (`providers/integrations/buddy/buddy-chat-shared.ts`) because
|
|
372
|
+
// their upstream rejects that shape with 11151.
|
|
373
|
+
if (m.role === "assistant" && hasReasoning && reasoningContent.length > 0) {
|
|
357
374
|
messages.push({
|
|
358
375
|
role: "assistant",
|
|
359
376
|
content: "",
|
|
@@ -361,6 +378,9 @@ export function canonicalToChatPayload(
|
|
|
361
378
|
});
|
|
362
379
|
continue;
|
|
363
380
|
}
|
|
381
|
+
// No trace to carry: skip the turn entirely rather than emit an
|
|
382
|
+
// empty assistant message the upstream will reject.
|
|
383
|
+
if (m.role === "assistant" && hasReasoning) continue;
|
|
364
384
|
if (m.role !== "assistant" && hasReasoning && reasoningContent.length > 0) {
|
|
365
385
|
const last = messages[messages.length - 1];
|
|
366
386
|
if (last !== undefined && last["role"] === "tool") {
|
|
@@ -374,7 +374,16 @@ export function canonicalToCodexResponsesPayload(
|
|
|
374
374
|
summary: summaryArray,
|
|
375
375
|
encrypted_content: r.encrypted_content,
|
|
376
376
|
};
|
|
377
|
-
|
|
377
|
+
// The upstream binds encrypted reasoning to the item it was issued
|
|
378
|
+
// for and rejects a replay whose id does not match, so the id that
|
|
379
|
+
// came back with the payload is authoritative. A synthesized
|
|
380
|
+
// `rs_<hash>` never matched the real item and failed the whole
|
|
381
|
+
// request with "Encrypted content item_id did not match the target
|
|
382
|
+
// item id"; only fall back to it when no id survived, where a
|
|
383
|
+
// mismatch is unavoidable anyway.
|
|
384
|
+
if (typeof r.item_id === "string" && r.item_id.length > 0) {
|
|
385
|
+
item["id"] = r.item_id;
|
|
386
|
+
} else if (r.summary !== undefined || r.encrypted_content !== undefined) {
|
|
378
387
|
const idSource = `${r.summary ?? ""}|${r.encrypted_content ?? ""}`;
|
|
379
388
|
if (idSource.length > 1) {
|
|
380
389
|
item["id"] = `rs_${hashToBase36(idSource)}`;
|
|
@@ -157,12 +157,11 @@ function applyImageClamp(
|
|
|
157
157
|
* the upstream rejects anything else. A canonical image part is an opaque
|
|
158
158
|
* origin payload, so it can arrive in a vocabulary that is *not* Anthropic's:
|
|
159
159
|
* a Responses part carries `type: "input_image"` with a top-level `image_url`
|
|
160
|
-
* string, and a Chat part carries a nested `image_url` object.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `source.type: "input_image"` — an invalid block the provider refused.
|
|
160
|
+
* string, and a Chat part carries a nested `image_url` object. Forwarding either
|
|
161
|
+
* with its own `type` intact (or with no `source` at all) sends a Claude model
|
|
162
|
+
* `source.type: "input_image"` — an invalid block the provider refuses.
|
|
164
163
|
*
|
|
165
|
-
* Every shape
|
|
164
|
+
* Every shape collapses to a valid source, preferring the cheapest
|
|
166
165
|
* transport: a Files API reference, then a URL, then base64 bytes (a `data:`
|
|
167
166
|
* URL is split rather than embedded, because Anthropic has no data-URL field).
|
|
168
167
|
*/
|
|
@@ -163,7 +163,15 @@ export function canonicalToResponsesPayload(
|
|
|
163
163
|
flushMessage();
|
|
164
164
|
// Replays prior encrypted reasoning so the model retains continuity
|
|
165
165
|
// across turns; never decrypted or reconstructed from summary text.
|
|
166
|
-
|
|
166
|
+
// The upstream binds encrypted reasoning to the item it was issued
|
|
167
|
+
// for and rejects a replay whose id does not match ("Encrypted content
|
|
168
|
+
// item_id did not match the target item id"), so the item carries the
|
|
169
|
+
// id it was parsed with. Without one there is nothing to match and the
|
|
170
|
+
// payload would be rejected anyway — it is dropped rather than sent
|
|
171
|
+
// under an id the upstream cannot verify.
|
|
172
|
+
if (p.item_id !== undefined) {
|
|
173
|
+
input.push({ type: "reasoning", id: p.item_id, encrypted_content: p.encrypted_content });
|
|
174
|
+
}
|
|
167
175
|
} else if (p.kind === "reasoning") {
|
|
168
176
|
// Plain reasoning replayed from another surface (a Messages `thinking`
|
|
169
177
|
// block, or Chat `reasoning_content`). It has no encrypted artifact, so
|
|
@@ -10,7 +10,7 @@ import { mergeResponsesUsage } from "./responses";
|
|
|
10
10
|
import { usageFromProvider, readReasoningText } from "../../providers/provider-usage";
|
|
11
11
|
import { createToolEmitLedger } from "../tool-identity";
|
|
12
12
|
import { MimoThinkSplitter, stripThinkingTags } from "../../providers/integrations/xiaomi-mimo/think-stream";
|
|
13
|
-
import { parseDsmlToolCalls } from "./dsml";
|
|
13
|
+
import { DsmlToolCallParser, parseDsmlToolCalls } from "./dsml";
|
|
14
14
|
import {
|
|
15
15
|
OmpToolCallMarkupParser,
|
|
16
16
|
parseOmpToolCallMarkup,
|
|
@@ -83,7 +83,7 @@ export function parseChatResponseToEvents(
|
|
|
83
83
|
const contentWithoutMarkup = recoveredMarkup?.text ?? content;
|
|
84
84
|
const recovered =
|
|
85
85
|
typeof contentWithoutMarkup === "string"
|
|
86
|
-
? parseDsmlToolCalls(contentWithoutMarkup)
|
|
86
|
+
? parseDsmlToolCalls(contentWithoutMarkup, declaredToolNames)
|
|
87
87
|
: undefined;
|
|
88
88
|
const visible = recovered === undefined ? contentWithoutMarkup : recovered.text;
|
|
89
89
|
if (typeof visible === "string" && visible.includes("<think")) {
|
|
@@ -144,6 +144,13 @@ export function parseChatResponseToEvents(
|
|
|
144
144
|
: undefined;
|
|
145
145
|
return callId && name !== undefined && name.length > 0;
|
|
146
146
|
});
|
|
147
|
+
// A request that declared no tools carries no allowlist to enforce, so a
|
|
148
|
+
// recovered block is honoured as-is exactly as before; a request that did
|
|
149
|
+
// declare them must never execute a name it did not offer.
|
|
150
|
+
const recoveredCalls =
|
|
151
|
+
declaredToolNames.size > 0
|
|
152
|
+
? (recovered?.calls ?? []).filter((call) => callableToolNames.has(call.name))
|
|
153
|
+
: (recovered?.calls ?? []);
|
|
147
154
|
const toolCalls = hasStructuredCalls
|
|
148
155
|
? structuredCalls
|
|
149
156
|
: recoveredMarkup?.calls.some((call) => callableToolNames.has(call.name))
|
|
@@ -153,7 +160,7 @@ export function parseChatResponseToEvents(
|
|
|
153
160
|
id: call.id,
|
|
154
161
|
function: { name: call.name, arguments: call.arguments },
|
|
155
162
|
}))
|
|
156
|
-
:
|
|
163
|
+
: recoveredCalls;
|
|
157
164
|
if (!hasStructuredCalls && recoveredMarkup !== undefined) {
|
|
158
165
|
recoveredToolCalls = recoveredMarkup.calls.filter((call) =>
|
|
159
166
|
callableToolNames.has(call.name),
|
|
@@ -257,6 +264,8 @@ export async function* decodeChatSseStream(
|
|
|
257
264
|
(request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
|
|
258
265
|
const reasoningMarkup =
|
|
259
266
|
(request.tools?.length ?? 0) > 0 ? new OmpToolCallMarkupParser(declaredToolNames) : undefined;
|
|
267
|
+
const dsmlMarkup =
|
|
268
|
+
(request.tools?.length ?? 0) > 0 ? new DsmlToolCallParser(declaredToolNames) : undefined;
|
|
260
269
|
yield {
|
|
261
270
|
type: "response_start",
|
|
262
271
|
sequence_number: seq++,
|
|
@@ -302,7 +311,7 @@ export async function* decodeChatSseStream(
|
|
|
302
311
|
const raw = delta["content"] as string;
|
|
303
312
|
const markup = contentMarkup?.push(raw);
|
|
304
313
|
if (markup !== undefined) recoveredMarkupCalls.push(...markup.calls);
|
|
305
|
-
const recovered =
|
|
314
|
+
const recovered = dsmlMarkup?.push(markup?.text ?? raw);
|
|
306
315
|
const visible = recovered?.text ?? markup?.text ?? raw;
|
|
307
316
|
const split = think.push(visible);
|
|
308
317
|
if (split.reasoning.length > 0) {
|
|
@@ -478,6 +487,27 @@ export async function* decodeChatSseStream(
|
|
|
478
487
|
}
|
|
479
488
|
}
|
|
480
489
|
}
|
|
490
|
+
const dsmlMarkupTail = dsmlMarkup?.flush();
|
|
491
|
+
if (dsmlMarkupTail !== undefined) {
|
|
492
|
+
if (dsmlMarkupTail.text.length > 0) {
|
|
493
|
+
const split = think.push(dsmlMarkupTail.text);
|
|
494
|
+
if (split.reasoning.length > 0) {
|
|
495
|
+
yield {
|
|
496
|
+
type: "content_delta",
|
|
497
|
+
sequence_number: seq++,
|
|
498
|
+
content: { kind: "reasoning", payload: null, summary: split.reasoning },
|
|
499
|
+
} as CanonicalEvent;
|
|
500
|
+
}
|
|
501
|
+
if (split.text.length > 0) {
|
|
502
|
+
yield {
|
|
503
|
+
type: "content_delta",
|
|
504
|
+
sequence_number: seq++,
|
|
505
|
+
content: { kind: "text", text: split.text },
|
|
506
|
+
} as CanonicalEvent;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const call of dsmlMarkupTail.calls) recoveredMarkupCalls.push(call);
|
|
510
|
+
}
|
|
481
511
|
const reasoningMarkupTail = reasoningMarkup?.flush();
|
|
482
512
|
if (reasoningMarkupTail?.text) {
|
|
483
513
|
const reasoningText = stripThinkingTags(reasoningMarkupTail.text);
|
|
@@ -84,7 +84,10 @@ export function reasoningEvent(
|
|
|
84
84
|
payload: null,
|
|
85
85
|
...(summary === undefined ? {} : { summary }),
|
|
86
86
|
...(typeof encrypted === "string"
|
|
87
|
-
? {
|
|
87
|
+
? {
|
|
88
|
+
encrypted_content: encrypted,
|
|
89
|
+
...(typeof item["id"] === "string" ? { item_id: item["id"] } : {}),
|
|
90
|
+
}
|
|
88
91
|
: {}),
|
|
89
92
|
...(summary === undefined && typeof encrypted === "string"
|
|
90
93
|
? { opaque: true as const }
|
|
@@ -235,9 +238,9 @@ export function parseCodexResponsesJsonToEvents(
|
|
|
235
238
|
| undefined);
|
|
236
239
|
// An incomplete response that produced a tool call is always a tool-use
|
|
237
240
|
// stop; every other incomplete response is a length stop. There is no
|
|
238
|
-
// separate executability gate —
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
+
// separate executability gate — scanning tool arguments for parseable JSON
|
|
242
|
+
// returns `tool_use` on both outcomes, so the scan and the
|
|
243
|
+
// `incomplete_details.reason` read could not change the result.
|
|
241
244
|
const stopReason = mapCodexStopReason(status, hasToolCall, endTurn);
|
|
242
245
|
let finalStopReason = stopReason;
|
|
243
246
|
if (endTurn === false && stopReason === "stop") {
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DeepSeek sometimes emits
|
|
3
|
-
*
|
|
4
|
-
* becomes ordinary tool calls; anything incomplete stays text.
|
|
2
|
+
* CodeBuddy-family DeepSeek sometimes emits native tool markup as assistant text.
|
|
3
|
+
* Complete declared calls are recovered; incomplete and undeclared blocks stay text.
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const INVOKE = new RegExp(`<${PIPE}DSML${PIPE}\\s+invoke\\s+name=["']([^"']+)["']>([\\s\\S]*?)</${PIPE}DSML${PIPE}\\s+invoke>`, "gi");
|
|
10
|
-
const PARAMETER = new RegExp(`<${PIPE}DSML${PIPE}\\s+parameter\\s+name=["']([^"']+)["'][^>]*>([\\s\\S]*?)</${PIPE}DSML${PIPE}\\s+parameter>`, "gi");
|
|
5
|
+
const SEPARATORS = ["||", "|", "|"] as const;
|
|
6
|
+
const BLOCK_NAMES = ["calls", "function_calls"] as const;
|
|
7
|
+
const OPENERS = SEPARATORS.flatMap((separator) => BLOCK_NAMES.map((name) => `<${separator}DSML${separator} ${name}>`));
|
|
11
8
|
const TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$/;
|
|
12
9
|
|
|
10
|
+
/** Escapes regex metacharacters so a `|` separator is literal, not alternation. */
|
|
11
|
+
function escapeRegExp(value: string): string {
|
|
12
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13
|
+
}
|
|
14
|
+
|
|
13
15
|
export interface DsmlToolCall {
|
|
14
16
|
readonly id: string;
|
|
15
17
|
readonly name: string;
|
|
@@ -21,27 +23,137 @@ export interface DsmlRecovery {
|
|
|
21
23
|
readonly calls: readonly DsmlToolCall[];
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
export
|
|
26
|
+
export class DsmlToolCallParser {
|
|
27
|
+
readonly #allowedNames: ReadonlySet<string>;
|
|
28
|
+
#pending = "";
|
|
29
|
+
#callSequence = 0;
|
|
30
|
+
|
|
31
|
+
constructor(allowedNames: ReadonlySet<string>) {
|
|
32
|
+
this.#allowedNames = allowedNames;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
push(chunk: string): DsmlRecovery {
|
|
36
|
+
this.#pending += chunk;
|
|
37
|
+
let cursor = 0;
|
|
38
|
+
let visible = "";
|
|
39
|
+
const calls: DsmlToolCall[] = [];
|
|
40
|
+
for (;;) {
|
|
41
|
+
let openIndex = -1;
|
|
42
|
+
let opener = "";
|
|
43
|
+
for (const candidate of OPENERS) {
|
|
44
|
+
const index = this.#pending.indexOf(candidate, cursor);
|
|
45
|
+
if (index >= 0 && (openIndex < 0 || index < openIndex)) {
|
|
46
|
+
openIndex = index;
|
|
47
|
+
opener = candidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (openIndex < 0) {
|
|
51
|
+
const safeEnd = this.#settledEnd(cursor);
|
|
52
|
+
visible += this.#pending.slice(cursor, safeEnd);
|
|
53
|
+
cursor = safeEnd;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
visible += this.#pending.slice(cursor, openIndex);
|
|
57
|
+
const separator = SEPARATORS.find((value) => opener.startsWith(`<${value}DSML`));
|
|
58
|
+
const blockName = BLOCK_NAMES.find((value) => opener.endsWith(` ${value}>`));
|
|
59
|
+
if (separator === undefined || blockName === undefined) {
|
|
60
|
+
visible += "<";
|
|
61
|
+
cursor = openIndex + 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const close = `</${separator}DSML${separator} ${blockName}>`;
|
|
65
|
+
const closeIndex = this.#pending.indexOf(close, openIndex + opener.length);
|
|
66
|
+
if (closeIndex < 0) {
|
|
67
|
+
cursor = openIndex;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const end = closeIndex + close.length;
|
|
71
|
+
const block = this.#pending.slice(openIndex, end);
|
|
72
|
+
const parsed = parseBlock(block, opener, close, separator, this.#allowedNames);
|
|
73
|
+
if (parsed === undefined) visible += block;
|
|
74
|
+
else {
|
|
75
|
+
for (const call of parsed) calls.push({ ...call, id: `call_dsml_${this.#callSequence++}_${call.name}` });
|
|
76
|
+
}
|
|
77
|
+
cursor = end;
|
|
78
|
+
}
|
|
79
|
+
this.#pending = this.#pending.slice(cursor);
|
|
80
|
+
return { text: visible, calls };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
flush(): DsmlRecovery {
|
|
84
|
+
const text = this.#pending;
|
|
85
|
+
this.#pending = "";
|
|
86
|
+
return { text, calls: [] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#settledEnd(start: number): number {
|
|
90
|
+
const remaining = this.#pending.slice(start);
|
|
91
|
+
for (let length = Math.min(OPENERS.reduce((max, opener) => Math.max(max, opener.length - 1), 0), remaining.length); length > 0; length -= 1) {
|
|
92
|
+
const suffix = remaining.slice(-length);
|
|
93
|
+
if (OPENERS.some((opener) => opener.startsWith(suffix))) return this.#pending.length - length;
|
|
94
|
+
}
|
|
95
|
+
return this.#pending.length;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseDsmlToolCalls(
|
|
100
|
+
text: string,
|
|
101
|
+
allowedNames: ReadonlySet<string> = new Set(),
|
|
102
|
+
): DsmlRecovery | undefined {
|
|
25
103
|
if (!text.includes("DSML")) return undefined;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
104
|
+
const parser = new DsmlToolCallParser(allowedNames);
|
|
105
|
+
const first = parser.push(text);
|
|
106
|
+
const last = parser.flush();
|
|
107
|
+
const recovered = [...first.calls, ...last.calls];
|
|
108
|
+
return recovered.length === 0 ? undefined : { text: `${first.text}${last.text}`.trim(), calls: recovered };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseBlock(
|
|
112
|
+
block: string,
|
|
113
|
+
opener: string,
|
|
114
|
+
close: string,
|
|
115
|
+
separator: string,
|
|
116
|
+
allowedNames: ReadonlySet<string>,
|
|
117
|
+
): Omit<DsmlToolCall, "id">[] | undefined {
|
|
118
|
+
const inner = block.slice(opener.length, -close.length);
|
|
119
|
+
const pipe = escapeRegExp(separator);
|
|
120
|
+
const invokePattern = new RegExp(`<${pipe}DSML${pipe}\\s+invoke\\s+name="([^"]*)"\\s*>`, "g");
|
|
121
|
+
const calls: Omit<DsmlToolCall, "id">[] = [];
|
|
122
|
+
// The allowlist is strict only when the caller knows the declared tools: a
|
|
123
|
+
// caller without one (a direct parse) keeps the generic name rule, while a
|
|
124
|
+
// dispatch that declared its tools must never execute a name it did not offer.
|
|
125
|
+
const strict = allowedNames.size > 0;
|
|
126
|
+
let cursor = 0;
|
|
127
|
+
for (const invoke of inner.matchAll(invokePattern)) {
|
|
128
|
+
const name = invoke[1] ?? "";
|
|
129
|
+
if (!TOOL_NAME.test(name) || (strict && !allowedNames.has(name)) || invoke.index === undefined) return undefined;
|
|
130
|
+
if (inner.slice(cursor, invoke.index).trim().length > 0) return undefined;
|
|
131
|
+
const bodyStart = invoke.index + invoke[0].length;
|
|
132
|
+
const invokeClose = `</${separator}DSML${separator} invoke>`;
|
|
133
|
+
const invokeEnd = inner.indexOf(invokeClose, bodyStart);
|
|
134
|
+
if (invokeEnd < 0) return undefined;
|
|
135
|
+
const parameterPattern = new RegExp(`<${pipe}DSML${pipe}\\s+parameter\\s+name="([^"]*)"(?:\\s+string="(true|false)")?\\s*>`, "g");
|
|
136
|
+
const body = inner.slice(bodyStart, invokeEnd);
|
|
137
|
+
const args: Record<string, unknown> = {};
|
|
138
|
+
let parameterCursor = 0;
|
|
139
|
+
for (const parameter of body.matchAll(parameterPattern)) {
|
|
140
|
+
const key = parameter[1] ?? "";
|
|
141
|
+
if (key.length === 0 || parameter.index === undefined || body.slice(parameterCursor, parameter.index).trim().length > 0) return undefined;
|
|
142
|
+
const valueStart = parameter.index + parameter[0].length;
|
|
143
|
+
const parameterClose = `</${separator}DSML${separator} parameter>`;
|
|
144
|
+
const valueEnd = body.indexOf(parameterClose, valueStart);
|
|
145
|
+
if (valueEnd < 0) return undefined;
|
|
146
|
+
const raw = body.slice(valueStart, valueEnd);
|
|
147
|
+
if (parameter[2] === "true") args[key] = raw;
|
|
148
|
+
else {
|
|
149
|
+
try { args[key] = JSON.parse(raw); }
|
|
150
|
+
catch { args[key] = raw; }
|
|
151
|
+
}
|
|
152
|
+
parameterCursor = valueEnd + parameterClose.length;
|
|
37
153
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
arguments: JSON.stringify(args),
|
|
42
|
-
});
|
|
154
|
+
if (body.slice(parameterCursor).trim().length > 0) return undefined;
|
|
155
|
+
calls.push({ name, arguments: JSON.stringify(args) });
|
|
156
|
+
cursor = invokeEnd + invokeClose.length;
|
|
43
157
|
}
|
|
44
|
-
|
|
45
|
-
const visible = `${text.slice(0, block.index)}${text.slice(block.index + block[0].length)}`.trim();
|
|
46
|
-
return { text: visible, calls };
|
|
158
|
+
return calls.length > 0 && inner.slice(cursor).trim().length === 0 ? calls : undefined;
|
|
47
159
|
}
|
|
@@ -182,7 +182,10 @@ export function parseResponsesResponseToEvents(
|
|
|
182
182
|
}
|
|
183
183
|
: { payload: text }),
|
|
184
184
|
...(position === 0 && encryptedContent !== undefined
|
|
185
|
-
? {
|
|
185
|
+
? {
|
|
186
|
+
encrypted_content: encryptedContent,
|
|
187
|
+
...(itemId === undefined ? {} : { item_id: itemId }),
|
|
188
|
+
}
|
|
186
189
|
: {}),
|
|
187
190
|
},
|
|
188
191
|
});
|
|
@@ -342,9 +345,9 @@ export async function* decodeResponsesSseStream(
|
|
|
342
345
|
const eventType = event["type"];
|
|
343
346
|
// An explicit failure inside a 200 OK. Two shapes carry one: a top-level
|
|
344
347
|
// `type: "error"` frame (the error object itself), and a `response.failed`
|
|
345
|
-
// terminal whose `response.error` explains why.
|
|
346
|
-
//
|
|
347
|
-
// `unknown_error`; the Claude and Gemini decoders already
|
|
348
|
+
// terminal whose `response.error` explains why. Recording either only as a
|
|
349
|
+
// `failed` terminal would leave the client with no code and telemetry with
|
|
350
|
+
// `unknown_error`; the Claude and Gemini decoders already raise.
|
|
348
351
|
const failedResponse = eventType === "response.failed" && isRecord(event["response"]) ? event["response"] : undefined;
|
|
349
352
|
const streamError = gatewayErrorFromStreamError(
|
|
350
353
|
eventType === "error" ? event : failedResponse?.["error"],
|
|
@@ -566,7 +569,10 @@ export async function* decodeResponsesSseStream(
|
|
|
566
569
|
? { payload: null, summary: text, summary_index: reasoningPart.index }
|
|
567
570
|
: { payload: text }),
|
|
568
571
|
...(position === 0 && encryptedContent !== undefined
|
|
569
|
-
? {
|
|
572
|
+
? {
|
|
573
|
+
encrypted_content: encryptedContent,
|
|
574
|
+
...(itemId === undefined ? {} : { item_id: itemId }),
|
|
575
|
+
}
|
|
570
576
|
: {}),
|
|
571
577
|
},
|
|
572
578
|
} as CanonicalEvent;
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* OpenAI-family in-stream error frames → typed `GatewayError`.
|
|
3
3
|
*
|
|
4
4
|
* A `200 OK` whose SSE body carries an explicit error envelope is an upstream
|
|
5
|
-
* failure that arrived *after* the status line.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* decoders already threw for the identical event. This module is the one
|
|
5
|
+
* failure that arrived *after* the status line. Recording it only as a terminal
|
|
6
|
+
* `state: "failed"` leaves the client with no `error.code`, no message, and
|
|
7
|
+
* telemetry with the `unknown_error` category — while the Claude (`messages.ts`)
|
|
8
|
+
* and Gemini decoders throw for the identical event. This module is the one
|
|
10
9
|
* classifier those surfaces share.
|
|
11
10
|
*
|
|
12
11
|
* Classification reads **structured identifiers only** (`error.type`,
|