@srouterhq/server 0.0.0-stage → 0.9.0
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/LICENSE +21 -0
- package/client/dist/assets/am-TQ7Jmdqe.js +1 -0
- package/client/dist/assets/ar-Bf5a7yfC.js +1 -0
- package/client/dist/assets/az-CrosE1xH.js +1 -0
- package/client/dist/assets/bg-BvWnmcUz.js +1 -0
- package/client/dist/assets/bn-ams5TsJp.js +1 -0
- package/client/dist/assets/cs-Dh67CaJ_.js +1 -0
- package/client/dist/assets/da-9WlT8Iq7.js +1 -0
- package/client/dist/assets/de-lWhJJzz7.js +1 -0
- package/client/dist/assets/el-Cu7_GhNq.js +1 -0
- package/client/dist/assets/es-BZuzsBcP.js +1 -0
- package/client/dist/assets/fa-Dwdn-jKS.js +1 -0
- package/client/dist/assets/fi-BFrFyOTy.js +1 -0
- package/client/dist/assets/fr-XhbtmPpj.js +1 -0
- package/client/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/client/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/client/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/client/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/client/dist/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
- package/client/dist/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
- package/client/dist/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
- package/client/dist/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
- package/client/dist/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
- package/client/dist/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
- package/client/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/client/dist/assets/gu-D0uhLUB8.js +1 -0
- package/client/dist/assets/ha-CVlm9pqp.js +1 -0
- package/client/dist/assets/he-CMIFeoWr.js +1 -0
- package/client/dist/assets/hi-ENTwhBpn.js +1 -0
- package/client/dist/assets/hr-fc03vMy_.js +1 -0
- package/client/dist/assets/hu-YBo2BIYt.js +1 -0
- package/client/dist/assets/id-BEWXYX69.js +1 -0
- package/client/dist/assets/ig-BKLZFKka.js +1 -0
- package/client/dist/assets/index-D9I4KtvR.js +145 -0
- package/client/dist/assets/index-txhEmCdb.css +2 -0
- package/client/dist/assets/it-Bzmei1Y2.js +1 -0
- package/client/dist/assets/ja-BfcpuqEl.js +1 -0
- package/client/dist/assets/ka-CnWjRgLo.js +1 -0
- package/client/dist/assets/km-BAjPR3Qj.js +1 -0
- package/client/dist/assets/kn-B15WKU5m.js +1 -0
- package/client/dist/assets/ko-C61fAB-w.js +1 -0
- package/client/dist/assets/lt-CYTr4_YW.js +1 -0
- package/client/dist/assets/ml-CtqxgyH3.js +1 -0
- package/client/dist/assets/mr-X5TMY7aR.js +1 -0
- package/client/dist/assets/ms-DSg8RqPS.js +1 -0
- package/client/dist/assets/my-D7RqR8uE.js +1 -0
- package/client/dist/assets/ne-DaflxkDj.js +1 -0
- package/client/dist/assets/nl-BOqqtsC8.js +1 -0
- package/client/dist/assets/no-BSHp_StB.js +1 -0
- package/client/dist/assets/or-BkLR9Vub.js +1 -0
- package/client/dist/assets/pa-CarJ2XgI.js +1 -0
- package/client/dist/assets/pl-C2n0V-0k.js +1 -0
- package/client/dist/assets/pt-BR-CqJdNhg2.js +1 -0
- package/client/dist/assets/pt-PT-C_HpMyKz.js +1 -0
- package/client/dist/assets/ro-DVRZqSAS.js +1 -0
- package/client/dist/assets/ru-DwAJG7VC.js +1 -0
- package/client/dist/assets/si-BxUezXm0.js +1 -0
- package/client/dist/assets/sk-eI1VcIfY.js +1 -0
- package/client/dist/assets/sr-CJF3auCr.js +1 -0
- package/client/dist/assets/srouter-logo-C6ZfjGIi.svg +14 -0
- package/client/dist/assets/sv-nqvwHAq_.js +1 -0
- package/client/dist/assets/sw-DWynE2pW.js +1 -0
- package/client/dist/assets/ta-BVM12knC.js +1 -0
- package/client/dist/assets/te-oyzEkF7_.js +1 -0
- package/client/dist/assets/th-COVeE0ZQ.js +1 -0
- package/client/dist/assets/tl-DsJANnQz.js +1 -0
- package/client/dist/assets/tr-B-Fl-urO.js +1 -0
- package/client/dist/assets/uk-C5qketAO.js +1 -0
- package/client/dist/assets/ur-QU9KugfV.js +1 -0
- package/client/dist/assets/uz-DL9cG_mY.js +1 -0
- package/client/dist/assets/vi-CQiVy3Pu.js +1 -0
- package/client/dist/assets/yo-DqazMdrZ.js +1 -0
- package/client/dist/assets/zh-CN-DnYL_Kio.js +1 -0
- package/client/dist/assets/zh-TW-5bdbosKj.js +1 -0
- package/client/dist/favicon.svg +14 -0
- package/client/dist/icons.svg +24 -0
- package/client/dist/index.html +37 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +372 -0
- package/dist/app.js.map +1 -0
- package/dist/db/index.d.ts +49 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +203 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate/TEMPLATE.d.ts +4 -0
- package/dist/db/migrate/TEMPLATE.d.ts.map +1 -0
- package/dist/db/migrate/TEMPLATE.js +18 -0
- package/dist/db/migrate/TEMPLATE.js.map +1 -0
- package/dist/db/migrate/cli.d.ts +2 -0
- package/dist/db/migrate/cli.d.ts.map +1 -0
- package/dist/db/migrate/cli.js +177 -0
- package/dist/db/migrate/cli.js.map +1 -0
- package/dist/db/migrate/defaults.d.ts +52 -0
- package/dist/db/migrate/defaults.d.ts.map +1 -0
- package/dist/db/migrate/defaults.js +126 -0
- package/dist/db/migrate/defaults.js.map +1 -0
- package/dist/db/migrate/runner.d.ts +16 -0
- package/dist/db/migrate/runner.d.ts.map +1 -0
- package/dist/db/migrate/runner.js +178 -0
- package/dist/db/migrate/runner.js.map +1 -0
- package/dist/db/migrations/20260101_000000_legacy_baseline.d.ts +4 -0
- package/dist/db/migrations/20260101_000000_legacy_baseline.d.ts.map +1 -0
- package/dist/db/migrations/20260101_000000_legacy_baseline.js +2240 -0
- package/dist/db/migrations/20260101_000000_legacy_baseline.js.map +1 -0
- package/dist/db/migrations/20260627_000001_custom_provider_modalities.d.ts +4 -0
- package/dist/db/migrations/20260627_000001_custom_provider_modalities.d.ts.map +1 -0
- package/dist/db/migrations/20260627_000001_custom_provider_modalities.js +27 -0
- package/dist/db/migrations/20260627_000001_custom_provider_modalities.js.map +1 -0
- package/dist/db/migrations/20260627_000002_catalog_model_state.d.ts +4 -0
- package/dist/db/migrations/20260627_000002_catalog_model_state.d.ts.map +1 -0
- package/dist/db/migrations/20260627_000002_catalog_model_state.js +30 -0
- package/dist/db/migrations/20260627_000002_catalog_model_state.js.map +1 -0
- package/dist/db/migrations/20260628_120000_request_aggregates.d.ts +6 -0
- package/dist/db/migrations/20260628_120000_request_aggregates.d.ts.map +1 -0
- package/dist/db/migrations/20260628_120000_request_aggregates.js +104 -0
- package/dist/db/migrations/20260628_120000_request_aggregates.js.map +1 -0
- package/dist/db/migrations/20260630_000001_github_gpt41_context.d.ts +9 -0
- package/dist/db/migrations/20260630_000001_github_gpt41_context.d.ts.map +1 -0
- package/dist/db/migrations/20260630_000001_github_gpt41_context.js +22 -0
- package/dist/db/migrations/20260630_000001_github_gpt41_context.js.map +1 -0
- package/dist/db/migrations/20260706_000001_request_client_info.d.ts +4 -0
- package/dist/db/migrations/20260706_000001_request_client_info.d.ts.map +1 -0
- package/dist/db/migrations/20260706_000001_request_client_info.js +30 -0
- package/dist/db/migrations/20260706_000001_request_client_info.js.map +1 -0
- package/dist/db/migrations/20260706_000002_custom_model_tool_support.d.ts +17 -0
- package/dist/db/migrations/20260706_000002_custom_model_tool_support.d.ts.map +1 -0
- package/dist/db/migrations/20260706_000002_custom_model_tool_support.js +20 -0
- package/dist/db/migrations/20260706_000002_custom_model_tool_support.js.map +1 -0
- package/dist/db/migrations/20260714_000001_profile_chain_backfill.d.ts +12 -0
- package/dist/db/migrations/20260714_000001_profile_chain_backfill.d.ts.map +1 -0
- package/dist/db/migrations/20260714_000001_profile_chain_backfill.js +45 -0
- package/dist/db/migrations/20260714_000001_profile_chain_backfill.js.map +1 -0
- package/dist/db/migrations/20260720_000001_key_health_error.d.ts +5 -0
- package/dist/db/migrations/20260720_000001_key_health_error.d.ts.map +1 -0
- package/dist/db/migrations/20260720_000001_key_health_error.js +16 -0
- package/dist/db/migrations/20260720_000001_key_health_error.js.map +1 -0
- package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.d.ts +4 -0
- package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.js +38 -0
- package/dist/db/migrations/20260726_000001_cooldown_probe_provenance.js.map +1 -0
- package/dist/db/migrations/20260726_000002_request_attempts.d.ts +4 -0
- package/dist/db/migrations/20260726_000002_request_attempts.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000002_request_attempts.js +43 -0
- package/dist/db/migrations/20260726_000002_request_attempts.js.map +1 -0
- package/dist/db/migrations/20260726_000003_model_source_provenance.d.ts +4 -0
- package/dist/db/migrations/20260726_000003_model_source_provenance.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000003_model_source_provenance.js +80 -0
- package/dist/db/migrations/20260726_000003_model_source_provenance.js.map +1 -0
- package/dist/db/migrations/20260726_000004_media_model_meta.d.ts +4 -0
- package/dist/db/migrations/20260726_000004_media_model_meta.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000004_media_model_meta.js +34 -0
- package/dist/db/migrations/20260726_000004_media_model_meta.js.map +1 -0
- package/dist/db/migrations/20260726_000005_request_served_model.d.ts +4 -0
- package/dist/db/migrations/20260726_000005_request_served_model.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000005_request_served_model.js +31 -0
- package/dist/db/migrations/20260726_000005_request_served_model.js.map +1 -0
- package/dist/db/migrations/20260726_000006_attempt_error_summary.d.ts +4 -0
- package/dist/db/migrations/20260726_000006_attempt_error_summary.d.ts.map +1 -0
- package/dist/db/migrations/20260726_000006_attempt_error_summary.js +29 -0
- package/dist/db/migrations/20260726_000006_attempt_error_summary.js.map +1 -0
- package/dist/db/migrations/20260727_000001_agent_compatibility.d.ts +4 -0
- package/dist/db/migrations/20260727_000001_agent_compatibility.d.ts.map +1 -0
- package/dist/db/migrations/20260727_000001_agent_compatibility.js +41 -0
- package/dist/db/migrations/20260727_000001_agent_compatibility.js.map +1 -0
- package/dist/db/migrations/20260728_000001_tombstone_provenance.d.ts +12 -0
- package/dist/db/migrations/20260728_000001_tombstone_provenance.d.ts.map +1 -0
- package/dist/db/migrations/20260728_000001_tombstone_provenance.js +37 -0
- package/dist/db/migrations/20260728_000001_tombstone_provenance.js.map +1 -0
- package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.d.ts +4 -0
- package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.d.ts.map +1 -0
- package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.js +145 -0
- package/dist/db/migrations/20260729_000001_custom_model_endpoint_identity.js.map +1 -0
- package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.d.ts +6 -0
- package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.d.ts.map +1 -0
- package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.js +52 -0
- package/dist/db/migrations/20260802_000001_custom_endpoint_host_labels.js.map +1 -0
- package/dist/db/migrations/20260805_000001_key_model_scope.d.ts +6 -0
- package/dist/db/migrations/20260805_000001_key_model_scope.d.ts.map +1 -0
- package/dist/db/migrations/20260805_000001_key_model_scope.js +17 -0
- package/dist/db/migrations/20260805_000001_key_model_scope.js.map +1 -0
- package/dist/db/migrations/20260805_000002_client_profiles.d.ts +4 -0
- package/dist/db/migrations/20260805_000002_client_profiles.d.ts.map +1 -0
- package/dist/db/migrations/20260805_000002_client_profiles.js +33 -0
- package/dist/db/migrations/20260805_000002_client_profiles.js.map +1 -0
- package/dist/db/migrations/20260810_000001_api_key_proxy.d.ts +4 -0
- package/dist/db/migrations/20260810_000001_api_key_proxy.d.ts.map +1 -0
- package/dist/db/migrations/20260810_000001_api_key_proxy.js +33 -0
- package/dist/db/migrations/20260810_000001_api_key_proxy.js.map +1 -0
- package/dist/db/migrations/20260819_000001_custom_model_tombstones.d.ts +4 -0
- package/dist/db/migrations/20260819_000001_custom_model_tombstones.d.ts.map +1 -0
- package/dist/db/migrations/20260819_000001_custom_model_tombstones.js +19 -0
- package/dist/db/migrations/20260819_000001_custom_model_tombstones.js.map +1 -0
- package/dist/db/migrations/20260820_000001_playground_conversations.d.ts +4 -0
- package/dist/db/migrations/20260820_000001_playground_conversations.d.ts.map +1 -0
- package/dist/db/migrations/20260820_000001_playground_conversations.js +47 -0
- package/dist/db/migrations/20260820_000001_playground_conversations.js.map +1 -0
- package/dist/db/migrations/20260823_000001_server_logs.d.ts +4 -0
- package/dist/db/migrations/20260823_000001_server_logs.d.ts.map +1 -0
- package/dist/db/migrations/20260823_000001_server_logs.js +53 -0
- package/dist/db/migrations/20260823_000001_server_logs.js.map +1 -0
- package/dist/db/migrations/20260823_000002_backups_table.d.ts +8 -0
- package/dist/db/migrations/20260823_000002_backups_table.d.ts.map +1 -0
- package/dist/db/migrations/20260823_000002_backups_table.js +22 -0
- package/dist/db/migrations/20260823_000002_backups_table.js.map +1 -0
- package/dist/db/migrations/20260823_000003_attempt_key_label.d.ts +19 -0
- package/dist/db/migrations/20260823_000003_attempt_key_label.d.ts.map +1 -0
- package/dist/db/migrations/20260823_000003_attempt_key_label.js +28 -0
- package/dist/db/migrations/20260823_000003_attempt_key_label.js.map +1 -0
- package/dist/db/migrations/20260823_000004_profile_auto_include.d.ts +14 -0
- package/dist/db/migrations/20260823_000004_profile_auto_include.d.ts.map +1 -0
- package/dist/db/migrations/20260823_000004_profile_auto_include.js +25 -0
- package/dist/db/migrations/20260823_000004_profile_auto_include.js.map +1 -0
- package/dist/db/migrations/20260901_000001_idempotency_claims.d.ts +4 -0
- package/dist/db/migrations/20260901_000001_idempotency_claims.d.ts.map +1 -0
- package/dist/db/migrations/20260901_000001_idempotency_claims.js +46 -0
- package/dist/db/migrations/20260901_000001_idempotency_claims.js.map +1 -0
- package/dist/db/migrations/20260901_000002_quota_observation_lookup.d.ts +4 -0
- package/dist/db/migrations/20260901_000002_quota_observation_lookup.d.ts.map +1 -0
- package/dist/db/migrations/20260901_000002_quota_observation_lookup.js +37 -0
- package/dist/db/migrations/20260901_000002_quota_observation_lookup.js.map +1 -0
- package/dist/db/migrations/20260901_000003_request_caller.d.ts +4 -0
- package/dist/db/migrations/20260901_000003_request_caller.d.ts.map +1 -0
- package/dist/db/migrations/20260901_000003_request_caller.js +27 -0
- package/dist/db/migrations/20260901_000003_request_caller.js.map +1 -0
- package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.d.ts +4 -0
- package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.d.ts.map +1 -0
- package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.js +35 -0
- package/dist/db/migrations/20260902_000001_analytics_latency_percentile_index.js.map +1 -0
- package/dist/db/migrations/20260903_000001_mcp_enabled_default.d.ts +4 -0
- package/dist/db/migrations/20260903_000001_mcp_enabled_default.d.ts.map +1 -0
- package/dist/db/migrations/20260903_000001_mcp_enabled_default.js +37 -0
- package/dist/db/migrations/20260903_000001_mcp_enabled_default.js.map +1 -0
- package/dist/db/migrations/20260903_000002_response_cache.d.ts +4 -0
- package/dist/db/migrations/20260903_000002_response_cache.d.ts.map +1 -0
- package/dist/db/migrations/20260903_000002_response_cache.js +53 -0
- package/dist/db/migrations/20260903_000002_response_cache.js.map +1 -0
- package/dist/db/migrations/20260904_000001_key_monthly_budget.d.ts +13 -0
- package/dist/db/migrations/20260904_000001_key_monthly_budget.d.ts.map +1 -0
- package/dist/db/migrations/20260904_000001_key_monthly_budget.js +35 -0
- package/dist/db/migrations/20260904_000001_key_monthly_budget.js.map +1 -0
- package/dist/db/migrations/20260913_000001_request_model_attribution.d.ts +4 -0
- package/dist/db/migrations/20260913_000001_request_model_attribution.d.ts.map +1 -0
- package/dist/db/migrations/20260913_000001_request_model_attribution.js +68 -0
- package/dist/db/migrations/20260913_000001_request_model_attribution.js.map +1 -0
- package/dist/db/migrations/20260914_000001_key_monthly_usage.d.ts +5 -0
- package/dist/db/migrations/20260914_000001_key_monthly_usage.d.ts.map +1 -0
- package/dist/db/migrations/20260914_000001_key_monthly_usage.js +37 -0
- package/dist/db/migrations/20260914_000001_key_monthly_usage.js.map +1 -0
- package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.d.ts +4 -0
- package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.d.ts.map +1 -0
- package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.js +28 -0
- package/dist/db/migrations/20260915_000001_quota_snapshot_freshness.js.map +1 -0
- package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.d.ts +4 -0
- package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.d.ts.map +1 -0
- package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.js +25 -0
- package/dist/db/migrations/20261004_000001_unified_key_sr_prefix.js.map +1 -0
- package/dist/db/migrations/20261005_000001_key_budget_period.d.ts +10 -0
- package/dist/db/migrations/20261005_000001_key_budget_period.d.ts.map +1 -0
- package/dist/db/migrations/20261005_000001_key_budget_period.js +78 -0
- package/dist/db/migrations/20261005_000001_key_budget_period.js.map +1 -0
- package/dist/db/migrations/20261005_000002_key_budget_period_auto.d.ts +21 -0
- package/dist/db/migrations/20261005_000002_key_budget_period_auto.d.ts.map +1 -0
- package/dist/db/migrations/20261005_000002_key_budget_period_auto.js +41 -0
- package/dist/db/migrations/20261005_000002_key_budget_period_auto.js.map +1 -0
- package/dist/db/model-pricing.d.ts +39 -0
- package/dist/db/model-pricing.d.ts.map +1 -0
- package/dist/db/model-pricing.js +274 -0
- package/dist/db/model-pricing.js.map +1 -0
- package/dist/db/node-sqlite.d.ts +9 -0
- package/dist/db/node-sqlite.d.ts.map +1 -0
- package/dist/db/node-sqlite.js +87 -0
- package/dist/db/node-sqlite.js.map +1 -0
- package/dist/db/types.d.ts +22 -0
- package/dist/db/types.d.ts.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/docs/docs-page.d.ts +2 -0
- package/dist/docs/docs-page.d.ts.map +1 -0
- package/dist/docs/docs-page.js +319 -0
- package/dist/docs/docs-page.js.map +1 -0
- package/dist/docs/openapi.d.ts +1896 -0
- package/dist/docs/openapi.d.ts.map +1 -0
- package/dist/docs/openapi.js +1096 -0
- package/dist/docs/openapi.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +9 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/anthropic-documents.d.ts +41 -0
- package/dist/lib/anthropic-documents.d.ts.map +1 -0
- package/dist/lib/anthropic-documents.js +132 -0
- package/dist/lib/anthropic-documents.js.map +1 -0
- package/dist/lib/app-version.d.ts +5 -0
- package/dist/lib/app-version.d.ts.map +1 -0
- package/dist/lib/app-version.js +61 -0
- package/dist/lib/app-version.js.map +1 -0
- package/dist/lib/attempt-trace.d.ts +23 -0
- package/dist/lib/attempt-trace.d.ts.map +1 -0
- package/dist/lib/attempt-trace.js +32 -0
- package/dist/lib/attempt-trace.js.map +1 -0
- package/dist/lib/budget.d.ts +6 -0
- package/dist/lib/budget.d.ts.map +1 -0
- package/dist/lib/budget.js +40 -0
- package/dist/lib/budget.js.map +1 -0
- package/dist/lib/client-classifier.d.ts +10 -0
- package/dist/lib/client-classifier.d.ts.map +1 -0
- package/dist/lib/client-classifier.js +126 -0
- package/dist/lib/client-classifier.js.map +1 -0
- package/dist/lib/client-context.d.ts +10 -0
- package/dist/lib/client-context.d.ts.map +1 -0
- package/dist/lib/client-context.js +39 -0
- package/dist/lib/client-context.js.map +1 -0
- package/dist/lib/config.d.ts +33 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +94 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/content.d.ts +33 -0
- package/dist/lib/content.d.ts.map +1 -0
- package/dist/lib/content.js +201 -0
- package/dist/lib/content.js.map +1 -0
- package/dist/lib/credential.d.ts +11 -0
- package/dist/lib/credential.d.ts.map +1 -0
- package/dist/lib/credential.js +18 -0
- package/dist/lib/credential.js.map +1 -0
- package/dist/lib/crypto.d.ts +31 -0
- package/dist/lib/crypto.d.ts.map +1 -0
- package/dist/lib/crypto.js +206 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/custom-provider-cleanup.d.ts +13 -0
- package/dist/lib/custom-provider-cleanup.d.ts.map +1 -0
- package/dist/lib/custom-provider-cleanup.js +43 -0
- package/dist/lib/custom-provider-cleanup.js.map +1 -0
- package/dist/lib/db-backup.d.ts +31 -0
- package/dist/lib/db-backup.d.ts.map +1 -0
- package/dist/lib/db-backup.js +255 -0
- package/dist/lib/db-backup.js.map +1 -0
- package/dist/lib/endpoint-scope.d.ts +42 -0
- package/dist/lib/endpoint-scope.d.ts.map +1 -0
- package/dist/lib/endpoint-scope.js +95 -0
- package/dist/lib/endpoint-scope.js.map +1 -0
- package/dist/lib/env-drift.d.ts +17 -0
- package/dist/lib/env-drift.d.ts.map +1 -0
- package/dist/lib/env-drift.js +108 -0
- package/dist/lib/env-drift.js.map +1 -0
- package/dist/lib/error-classify.d.ts +40 -0
- package/dist/lib/error-classify.d.ts.map +1 -0
- package/dist/lib/error-classify.js +687 -0
- package/dist/lib/error-classify.js.map +1 -0
- package/dist/lib/error-redaction.d.ts +8 -0
- package/dist/lib/error-redaction.d.ts.map +1 -0
- package/dist/lib/error-redaction.js +45 -0
- package/dist/lib/error-redaction.js.map +1 -0
- package/dist/lib/fallback-loop.d.ts +306 -0
- package/dist/lib/fallback-loop.d.ts.map +1 -0
- package/dist/lib/fallback-loop.js +1336 -0
- package/dist/lib/fallback-loop.js.map +1 -0
- package/dist/lib/file-permissions.d.ts +98 -0
- package/dist/lib/file-permissions.d.ts.map +1 -0
- package/dist/lib/file-permissions.js +159 -0
- package/dist/lib/file-permissions.js.map +1 -0
- package/dist/lib/gemini-wire.d.ts +76 -0
- package/dist/lib/gemini-wire.d.ts.map +1 -0
- package/dist/lib/gemini-wire.js +392 -0
- package/dist/lib/gemini-wire.js.map +1 -0
- package/dist/lib/guardrails.d.ts +37 -0
- package/dist/lib/guardrails.d.ts.map +1 -0
- package/dist/lib/guardrails.js +106 -0
- package/dist/lib/guardrails.js.map +1 -0
- package/dist/lib/header-value.d.ts +8 -0
- package/dist/lib/header-value.d.ts.map +1 -0
- package/dist/lib/header-value.js +55 -0
- package/dist/lib/header-value.js.map +1 -0
- package/dist/lib/image-normalize.d.ts +21 -0
- package/dist/lib/image-normalize.d.ts.map +1 -0
- package/dist/lib/image-normalize.js +218 -0
- package/dist/lib/image-normalize.js.map +1 -0
- package/dist/lib/inbound-chat.d.ts +48 -0
- package/dist/lib/inbound-chat.d.ts.map +1 -0
- package/dist/lib/inbound-chat.js +406 -0
- package/dist/lib/inbound-chat.js.map +1 -0
- package/dist/lib/key-parser.d.ts +79 -0
- package/dist/lib/key-parser.d.ts.map +1 -0
- package/dist/lib/key-parser.js +701 -0
- package/dist/lib/key-parser.js.map +1 -0
- package/dist/lib/key-proxy.d.ts +49 -0
- package/dist/lib/key-proxy.d.ts.map +1 -0
- package/dist/lib/key-proxy.js +92 -0
- package/dist/lib/key-proxy.js.map +1 -0
- package/dist/lib/log-redaction.d.ts +28 -0
- package/dist/lib/log-redaction.d.ts.map +1 -0
- package/dist/lib/log-redaction.js +166 -0
- package/dist/lib/log-redaction.js.map +1 -0
- package/dist/lib/model-scope.d.ts +9 -0
- package/dist/lib/model-scope.d.ts.map +1 -0
- package/dist/lib/model-scope.js +29 -0
- package/dist/lib/model-scope.js.map +1 -0
- package/dist/lib/one-time-code.d.ts +15 -0
- package/dist/lib/one-time-code.d.ts.map +1 -0
- package/dist/lib/one-time-code.js +41 -0
- package/dist/lib/one-time-code.js.map +1 -0
- package/dist/lib/output-cap.d.ts +24 -0
- package/dist/lib/output-cap.d.ts.map +1 -0
- package/dist/lib/output-cap.js +80 -0
- package/dist/lib/output-cap.js.map +1 -0
- package/dist/lib/password.d.ts +3 -0
- package/dist/lib/password.d.ts.map +1 -0
- package/dist/lib/password.js +27 -0
- package/dist/lib/password.js.map +1 -0
- package/dist/lib/process-safety-net.d.ts +32 -0
- package/dist/lib/process-safety-net.d.ts.map +1 -0
- package/dist/lib/process-safety-net.js +123 -0
- package/dist/lib/process-safety-net.js.map +1 -0
- package/dist/lib/provider-identity.d.ts +37 -0
- package/dist/lib/provider-identity.d.ts.map +1 -0
- package/dist/lib/provider-identity.js +110 -0
- package/dist/lib/provider-identity.js.map +1 -0
- package/dist/lib/provider-size-parser.d.ts +6 -0
- package/dist/lib/provider-size-parser.d.ts.map +1 -0
- package/dist/lib/provider-size-parser.js +72 -0
- package/dist/lib/provider-size-parser.js.map +1 -0
- package/dist/lib/provider-timeout.d.ts +17 -0
- package/dist/lib/provider-timeout.d.ts.map +1 -0
- package/dist/lib/provider-timeout.js +70 -0
- package/dist/lib/provider-timeout.js.map +1 -0
- package/dist/lib/proxy.d.ts +180 -0
- package/dist/lib/proxy.d.ts.map +1 -0
- package/dist/lib/proxy.js +1002 -0
- package/dist/lib/proxy.js.map +1 -0
- package/dist/lib/request-log.d.ts +4 -0
- package/dist/lib/request-log.d.ts.map +1 -0
- package/dist/lib/request-log.js +161 -0
- package/dist/lib/request-log.js.map +1 -0
- package/dist/lib/reset-code.d.ts +5 -0
- package/dist/lib/reset-code.d.ts.map +1 -0
- package/dist/lib/reset-code.js +39 -0
- package/dist/lib/reset-code.js.map +1 -0
- package/dist/lib/retry-hint.d.ts +14 -0
- package/dist/lib/retry-hint.d.ts.map +1 -0
- package/dist/lib/retry-hint.js +36 -0
- package/dist/lib/retry-hint.js.map +1 -0
- package/dist/lib/route-live.d.ts +29 -0
- package/dist/lib/route-live.d.ts.map +1 -0
- package/dist/lib/route-live.js +97 -0
- package/dist/lib/route-live.js.map +1 -0
- package/dist/lib/sampling-params.d.ts +183 -0
- package/dist/lib/sampling-params.d.ts.map +1 -0
- package/dist/lib/sampling-params.js +386 -0
- package/dist/lib/sampling-params.js.map +1 -0
- package/dist/lib/scheduler.d.ts +13 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +11 -0
- package/dist/lib/scheduler.js.map +1 -0
- package/dist/lib/served-model.d.ts +16 -0
- package/dist/lib/served-model.d.ts.map +1 -0
- package/dist/lib/served-model.js +0 -0
- package/dist/lib/served-model.js.map +1 -0
- package/dist/lib/server-logs.d.ts +132 -0
- package/dist/lib/server-logs.d.ts.map +1 -0
- package/dist/lib/server-logs.js +473 -0
- package/dist/lib/server-logs.js.map +1 -0
- package/dist/lib/setup-code.d.ts +5 -0
- package/dist/lib/setup-code.d.ts.map +1 -0
- package/dist/lib/setup-code.js +35 -0
- package/dist/lib/setup-code.js.map +1 -0
- package/dist/lib/structured-output.d.ts +9 -0
- package/dist/lib/structured-output.d.ts.map +1 -0
- package/dist/lib/structured-output.js +72 -0
- package/dist/lib/structured-output.js.map +1 -0
- package/dist/lib/system-prompt.d.ts +30 -0
- package/dist/lib/system-prompt.d.ts.map +1 -0
- package/dist/lib/system-prompt.js +66 -0
- package/dist/lib/system-prompt.js.map +1 -0
- package/dist/lib/task-type.d.ts +18 -0
- package/dist/lib/task-type.d.ts.map +1 -0
- package/dist/lib/task-type.js +92 -0
- package/dist/lib/task-type.js.map +1 -0
- package/dist/lib/think-tags.d.ts +37 -0
- package/dist/lib/think-tags.d.ts.map +1 -0
- package/dist/lib/think-tags.js +203 -0
- package/dist/lib/think-tags.js.map +1 -0
- package/dist/lib/tool-args.d.ts +36 -0
- package/dist/lib/tool-args.d.ts.map +1 -0
- package/dist/lib/tool-args.js +190 -0
- package/dist/lib/tool-args.js.map +1 -0
- package/dist/lib/tool-call-rescue.d.ts +62 -0
- package/dist/lib/tool-call-rescue.d.ts.map +1 -0
- package/dist/lib/tool-call-rescue.js +258 -0
- package/dist/lib/tool-call-rescue.js.map +1 -0
- package/dist/lib/tool-capability.d.ts +13 -0
- package/dist/lib/tool-capability.d.ts.map +1 -0
- package/dist/lib/tool-capability.js +70 -0
- package/dist/lib/tool-capability.js.map +1 -0
- package/dist/lib/tool-validate.d.ts +44 -0
- package/dist/lib/tool-validate.d.ts.map +1 -0
- package/dist/lib/tool-validate.js +165 -0
- package/dist/lib/tool-validate.js.map +1 -0
- package/dist/lib/ttfb-budget.d.ts +13 -0
- package/dist/lib/ttfb-budget.d.ts.map +1 -0
- package/dist/lib/ttfb-budget.js +153 -0
- package/dist/lib/ttfb-budget.js.map +1 -0
- package/dist/lib/url-guard.d.ts +47 -0
- package/dist/lib/url-guard.d.ts.map +1 -0
- package/dist/lib/url-guard.js +229 -0
- package/dist/lib/url-guard.js.map +1 -0
- package/dist/lib/wake-detect.d.ts +12 -0
- package/dist/lib/wake-detect.d.ts.map +1 -0
- package/dist/lib/wake-detect.js +99 -0
- package/dist/lib/wake-detect.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +3 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +45 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +4 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/rateLimit.js +125 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/middleware/requireAuth.d.ts +3 -0
- package/dist/middleware/requireAuth.d.ts.map +1 -0
- package/dist/middleware/requireAuth.js +17 -0
- package/dist/middleware/requireAuth.js.map +1 -0
- package/dist/providers/aclide.d.ts +18 -0
- package/dist/providers/aclide.d.ts.map +1 -0
- package/dist/providers/aclide.js +174 -0
- package/dist/providers/aclide.js.map +1 -0
- package/dist/providers/aihorde.d.ts +42 -0
- package/dist/providers/aihorde.d.ts.map +1 -0
- package/dist/providers/aihorde.js +192 -0
- package/dist/providers/aihorde.js.map +1 -0
- package/dist/providers/airforce.d.ts +11 -0
- package/dist/providers/airforce.d.ts.map +1 -0
- package/dist/providers/airforce.js +32 -0
- package/dist/providers/airforce.js.map +1 -0
- package/dist/providers/base.d.ts +177 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +354 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/blaze.d.ts +11 -0
- package/dist/providers/blaze.d.ts.map +1 -0
- package/dist/providers/blaze.js +33 -0
- package/dist/providers/blaze.js.map +1 -0
- package/dist/providers/blockrun.d.ts +14 -0
- package/dist/providers/blockrun.d.ts.map +1 -0
- package/dist/providers/blockrun.js +36 -0
- package/dist/providers/blockrun.js.map +1 -0
- package/dist/providers/clod.d.ts +11 -0
- package/dist/providers/clod.d.ts.map +1 -0
- package/dist/providers/clod.js +39 -0
- package/dist/providers/clod.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +15 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +175 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/cohere.d.ts +11 -0
- package/dist/providers/cohere.d.ts.map +1 -0
- package/dist/providers/cohere.js +117 -0
- package/dist/providers/cohere.js.map +1 -0
- package/dist/providers/dreamprompting.d.ts +11 -0
- package/dist/providers/dreamprompting.d.ts.map +1 -0
- package/dist/providers/dreamprompting.js +41 -0
- package/dist/providers/dreamprompting.js.map +1 -0
- package/dist/providers/electronhub.d.ts +10 -0
- package/dist/providers/electronhub.d.ts.map +1 -0
- package/dist/providers/electronhub.js +56 -0
- package/dist/providers/electronhub.js.map +1 -0
- package/dist/providers/experiential.d.ts +10 -0
- package/dist/providers/experiential.d.ts.map +1 -0
- package/dist/providers/experiential.js +33 -0
- package/dist/providers/experiential.js.map +1 -0
- package/dist/providers/gizmo.d.ts +15 -0
- package/dist/providers/gizmo.d.ts.map +1 -0
- package/dist/providers/gizmo.js +54 -0
- package/dist/providers/gizmo.js.map +1 -0
- package/dist/providers/google.d.ts +30 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +766 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +561 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/llmtr.d.ts +15 -0
- package/dist/providers/llmtr.d.ts.map +1 -0
- package/dist/providers/llmtr.js +51 -0
- package/dist/providers/llmtr.js.map +1 -0
- package/dist/providers/logfare.d.ts +11 -0
- package/dist/providers/logfare.d.ts.map +1 -0
- package/dist/providers/logfare.js +33 -0
- package/dist/providers/logfare.js.map +1 -0
- package/dist/providers/lucidity.d.ts +11 -0
- package/dist/providers/lucidity.d.ts.map +1 -0
- package/dist/providers/lucidity.js +37 -0
- package/dist/providers/lucidity.js.map +1 -0
- package/dist/providers/modelscope.d.ts +50 -0
- package/dist/providers/modelscope.d.ts.map +1 -0
- package/dist/providers/modelscope.js +143 -0
- package/dist/providers/modelscope.js.map +1 -0
- package/dist/providers/moondream.d.ts +17 -0
- package/dist/providers/moondream.d.ts.map +1 -0
- package/dist/providers/moondream.js +140 -0
- package/dist/providers/moondream.js.map +1 -0
- package/dist/providers/openai-compat.d.ts +140 -0
- package/dist/providers/openai-compat.d.ts.map +1 -0
- package/dist/providers/openai-compat.js +537 -0
- package/dist/providers/openai-compat.js.map +1 -0
- package/dist/providers/opencode-free.d.ts +24 -0
- package/dist/providers/opencode-free.d.ts.map +1 -0
- package/dist/providers/opencode-free.js +262 -0
- package/dist/providers/opencode-free.js.map +1 -0
- package/dist/providers/pollinations.d.ts +32 -0
- package/dist/providers/pollinations.d.ts.map +1 -0
- package/dist/providers/pollinations.js +65 -0
- package/dist/providers/pollinations.js.map +1 -0
- package/dist/providers/router9.d.ts +16 -0
- package/dist/providers/router9.d.ts.map +1 -0
- package/dist/providers/router9.js +42 -0
- package/dist/providers/router9.js.map +1 -0
- package/dist/providers/sail.d.ts +44 -0
- package/dist/providers/sail.d.ts.map +1 -0
- package/dist/providers/sail.js +302 -0
- package/dist/providers/sail.js.map +1 -0
- package/dist/providers/septor.d.ts +10 -0
- package/dist/providers/septor.d.ts.map +1 -0
- package/dist/providers/septor.js +36 -0
- package/dist/providers/septor.js.map +1 -0
- package/dist/providers/speechify.d.ts +14 -0
- package/dist/providers/speechify.d.ts.map +1 -0
- package/dist/providers/speechify.js +28 -0
- package/dist/providers/speechify.js.map +1 -0
- package/dist/providers/speka.d.ts +12 -0
- package/dist/providers/speka.d.ts.map +1 -0
- package/dist/providers/speka.js +37 -0
- package/dist/providers/speka.js.map +1 -0
- package/dist/providers/waterfall.d.ts +11 -0
- package/dist/providers/waterfall.d.ts.map +1 -0
- package/dist/providers/waterfall.js +33 -0
- package/dist/providers/waterfall.js.map +1 -0
- package/dist/providers/xkiro.d.ts +71 -0
- package/dist/providers/xkiro.d.ts.map +1 -0
- package/dist/providers/xkiro.js +119 -0
- package/dist/providers/xkiro.js.map +1 -0
- package/dist/providers/zhipu.d.ts +43 -0
- package/dist/providers/zhipu.d.ts.map +1 -0
- package/dist/providers/zhipu.js +95 -0
- package/dist/providers/zhipu.js.map +1 -0
- package/dist/routes/analytics.d.ts +2 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +738 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/anthropic.d.ts +7 -0
- package/dist/routes/anthropic.d.ts.map +1 -0
- package/dist/routes/anthropic.js +1076 -0
- package/dist/routes/anthropic.js.map +1 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +310 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/backups.d.ts +2 -0
- package/dist/routes/backups.d.ts.map +1 -0
- package/dist/routes/backups.js +127 -0
- package/dist/routes/backups.js.map +1 -0
- package/dist/routes/cache.d.ts +2 -0
- package/dist/routes/cache.d.ts.map +1 -0
- package/dist/routes/cache.js +40 -0
- package/dist/routes/cache.js.map +1 -0
- package/dist/routes/client-profiles.d.ts +2 -0
- package/dist/routes/client-profiles.d.ts.map +1 -0
- package/dist/routes/client-profiles.js +128 -0
- package/dist/routes/client-profiles.js.map +1 -0
- package/dist/routes/compression.d.ts +2 -0
- package/dist/routes/compression.d.ts.map +1 -0
- package/dist/routes/compression.js +82 -0
- package/dist/routes/compression.js.map +1 -0
- package/dist/routes/conversations.d.ts +3 -0
- package/dist/routes/conversations.d.ts.map +1 -0
- package/dist/routes/conversations.js +215 -0
- package/dist/routes/conversations.js.map +1 -0
- package/dist/routes/docs.d.ts +2 -0
- package/dist/routes/docs.d.ts.map +1 -0
- package/dist/routes/docs.js +21 -0
- package/dist/routes/docs.js.map +1 -0
- package/dist/routes/embeddings.d.ts +2 -0
- package/dist/routes/embeddings.d.ts.map +1 -0
- package/dist/routes/embeddings.js +255 -0
- package/dist/routes/embeddings.js.map +1 -0
- package/dist/routes/fallback.d.ts +2 -0
- package/dist/routes/fallback.d.ts.map +1 -0
- package/dist/routes/fallback.js +631 -0
- package/dist/routes/fallback.js.map +1 -0
- package/dist/routes/free-tier.d.ts +22 -0
- package/dist/routes/free-tier.d.ts.map +1 -0
- package/dist/routes/free-tier.js +175 -0
- package/dist/routes/free-tier.js.map +1 -0
- package/dist/routes/gemini.d.ts +2 -0
- package/dist/routes/gemini.d.ts.map +1 -0
- package/dist/routes/gemini.js +218 -0
- package/dist/routes/gemini.js.map +1 -0
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +72 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/keys.d.ts +17 -0
- package/dist/routes/keys.d.ts.map +1 -0
- package/dist/routes/keys.js +1787 -0
- package/dist/routes/keys.js.map +1 -0
- package/dist/routes/logs.d.ts +2 -0
- package/dist/routes/logs.d.ts.map +1 -0
- package/dist/routes/logs.js +88 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/routes/mcp.d.ts +4 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +563 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/media.d.ts +2 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +180 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/models.d.ts +2 -0
- package/dist/routes/models.d.ts.map +1 -0
- package/dist/routes/models.js +439 -0
- package/dist/routes/models.js.map +1 -0
- package/dist/routes/ollama.d.ts +7 -0
- package/dist/routes/ollama.d.ts.map +1 -0
- package/dist/routes/ollama.js +595 -0
- package/dist/routes/ollama.js.map +1 -0
- package/dist/routes/profiles.d.ts +3 -0
- package/dist/routes/profiles.d.ts.map +1 -0
- package/dist/routes/profiles.js +431 -0
- package/dist/routes/profiles.js.map +1 -0
- package/dist/routes/proxy.d.ts +32 -0
- package/dist/routes/proxy.d.ts.map +1 -0
- package/dist/routes/proxy.js +2793 -0
- package/dist/routes/proxy.js.map +1 -0
- package/dist/routes/responses.d.ts +1161 -0
- package/dist/routes/responses.d.ts.map +1 -0
- package/dist/routes/responses.js +1521 -0
- package/dist/routes/responses.js.map +1 -0
- package/dist/routes/settings.d.ts +2 -0
- package/dist/routes/settings.d.ts.map +1 -0
- package/dist/routes/settings.js +549 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/status.d.ts +3 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +214 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/update.d.ts +31 -0
- package/dist/routes/update.d.ts.map +1 -0
- package/dist/routes/update.js +536 -0
- package/dist/routes/update.js.map +1 -0
- package/dist/routes/url-tokens.d.ts +2 -0
- package/dist/routes/url-tokens.d.ts.map +1 -0
- package/dist/routes/url-tokens.js +31 -0
- package/dist/routes/url-tokens.js.map +1 -0
- package/dist/scripts/export-catalog.d.ts +2 -0
- package/dist/scripts/export-catalog.d.ts.map +1 -0
- package/dist/scripts/export-catalog.js +114 -0
- package/dist/scripts/export-catalog.js.map +1 -0
- package/dist/scripts/rotate-encryption-key.d.ts +81 -0
- package/dist/scripts/rotate-encryption-key.d.ts.map +1 -0
- package/dist/scripts/rotate-encryption-key.js +232 -0
- package/dist/scripts/rotate-encryption-key.js.map +1 -0
- package/dist/scripts/routing-sim.d.ts +2 -0
- package/dist/scripts/routing-sim.d.ts.map +1 -0
- package/dist/scripts/routing-sim.js +130 -0
- package/dist/scripts/routing-sim.js.map +1 -0
- package/dist/scripts/test-all-models.d.ts +2 -0
- package/dist/scripts/test-all-models.d.ts.map +1 -0
- package/dist/scripts/test-all-models.js +56 -0
- package/dist/scripts/test-all-models.js.map +1 -0
- package/dist/services/anthropic-map.d.ts +39 -0
- package/dist/services/anthropic-map.d.ts.map +1 -0
- package/dist/services/anthropic-map.js +138 -0
- package/dist/services/anthropic-map.js.map +1 -0
- package/dist/services/auth.d.ts +30 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +124 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/services/auto-discover.d.ts +77 -0
- package/dist/services/auto-discover.d.ts.map +1 -0
- package/dist/services/auto-discover.js +1047 -0
- package/dist/services/auto-discover.js.map +1 -0
- package/dist/services/backups.d.ts +67 -0
- package/dist/services/backups.d.ts.map +1 -0
- package/dist/services/backups.js +444 -0
- package/dist/services/backups.js.map +1 -0
- package/dist/services/builtin-model-discovery.d.ts +107 -0
- package/dist/services/builtin-model-discovery.d.ts.map +1 -0
- package/dist/services/builtin-model-discovery.js +296 -0
- package/dist/services/builtin-model-discovery.js.map +1 -0
- package/dist/services/cache.d.ts +190 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +617 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/catalog-sync.d.ts +257 -0
- package/dist/services/catalog-sync.d.ts.map +1 -0
- package/dist/services/catalog-sync.js +1041 -0
- package/dist/services/catalog-sync.js.map +1 -0
- package/dist/services/compression/config.d.ts +33 -0
- package/dist/services/compression/config.d.ts.map +1 -0
- package/dist/services/compression/config.js +158 -0
- package/dist/services/compression/config.js.map +1 -0
- package/dist/services/compression/engines/aging.d.ts +2 -0
- package/dist/services/compression/engines/aging.d.ts.map +1 -0
- package/dist/services/compression/engines/aging.js +53 -0
- package/dist/services/compression/engines/aging.js.map +1 -0
- package/dist/services/compression/engines/custom-filters.d.ts +4 -0
- package/dist/services/compression/engines/custom-filters.d.ts.map +1 -0
- package/dist/services/compression/engines/custom-filters.js +49 -0
- package/dist/services/compression/engines/custom-filters.js.map +1 -0
- package/dist/services/compression/engines/dedup.d.ts +2 -0
- package/dist/services/compression/engines/dedup.d.ts.map +1 -0
- package/dist/services/compression/engines/dedup.js +61 -0
- package/dist/services/compression/engines/dedup.js.map +1 -0
- package/dist/services/compression/engines/filter-definitions.d.ts +75 -0
- package/dist/services/compression/engines/filter-definitions.d.ts.map +1 -0
- package/dist/services/compression/engines/filter-definitions.js +45 -0
- package/dist/services/compression/engines/filter-definitions.js.map +1 -0
- package/dist/services/compression/engines/hard-budget.d.ts +2 -0
- package/dist/services/compression/engines/hard-budget.d.ts.map +1 -0
- package/dist/services/compression/engines/hard-budget.js +52 -0
- package/dist/services/compression/engines/hard-budget.js.map +1 -0
- package/dist/services/compression/engines/index.d.ts +9 -0
- package/dist/services/compression/engines/index.d.ts.map +1 -0
- package/dist/services/compression/engines/index.js +9 -0
- package/dist/services/compression/engines/index.js.map +1 -0
- package/dist/services/compression/engines/jsoncompact.d.ts +3 -0
- package/dist/services/compression/engines/jsoncompact.d.ts.map +1 -0
- package/dist/services/compression/engines/jsoncompact.js +145 -0
- package/dist/services/compression/engines/jsoncompact.js.map +1 -0
- package/dist/services/compression/engines/lite.d.ts +2 -0
- package/dist/services/compression/engines/lite.d.ts.map +1 -0
- package/dist/services/compression/engines/lite.js +34 -0
- package/dist/services/compression/engines/lite.js.map +1 -0
- package/dist/services/compression/engines/read-lifecycle.d.ts +2 -0
- package/dist/services/compression/engines/read-lifecycle.d.ts.map +1 -0
- package/dist/services/compression/engines/read-lifecycle.js +70 -0
- package/dist/services/compression/engines/read-lifecycle.js.map +1 -0
- package/dist/services/compression/engines/relevance.d.ts +2 -0
- package/dist/services/compression/engines/relevance.d.ts.map +1 -0
- package/dist/services/compression/engines/relevance.js +74 -0
- package/dist/services/compression/engines/relevance.js.map +1 -0
- package/dist/services/compression/engines/toolfilter.d.ts +2 -0
- package/dist/services/compression/engines/toolfilter.d.ts.map +1 -0
- package/dist/services/compression/engines/toolfilter.js +195 -0
- package/dist/services/compression/engines/toolfilter.js.map +1 -0
- package/dist/services/compression/fidelity-gate.d.ts +12 -0
- package/dist/services/compression/fidelity-gate.d.ts.map +1 -0
- package/dist/services/compression/fidelity-gate.js +90 -0
- package/dist/services/compression/fidelity-gate.js.map +1 -0
- package/dist/services/compression/helpers.d.ts +9 -0
- package/dist/services/compression/helpers.d.ts.map +1 -0
- package/dist/services/compression/helpers.js +52 -0
- package/dist/services/compression/helpers.js.map +1 -0
- package/dist/services/compression/pipeline.d.ts +7 -0
- package/dist/services/compression/pipeline.d.ts.map +1 -0
- package/dist/services/compression/pipeline.js +245 -0
- package/dist/services/compression/pipeline.js.map +1 -0
- package/dist/services/compression/preservation.d.ts +27 -0
- package/dist/services/compression/preservation.d.ts.map +1 -0
- package/dist/services/compression/preservation.js +109 -0
- package/dist/services/compression/preservation.js.map +1 -0
- package/dist/services/compression/registry.d.ts +6 -0
- package/dist/services/compression/registry.d.ts.map +1 -0
- package/dist/services/compression/registry.js +16 -0
- package/dist/services/compression/registry.js.map +1 -0
- package/dist/services/compression/stats.d.ts +29 -0
- package/dist/services/compression/stats.d.ts.map +1 -0
- package/dist/services/compression/stats.js +55 -0
- package/dist/services/compression/stats.js.map +1 -0
- package/dist/services/compression/types.d.ts +90 -0
- package/dist/services/compression/types.d.ts.map +1 -0
- package/dist/services/compression/types.js +2 -0
- package/dist/services/compression/types.js.map +1 -0
- package/dist/services/context-handoff.d.ts +22 -0
- package/dist/services/context-handoff.d.ts.map +1 -0
- package/dist/services/context-handoff.js +164 -0
- package/dist/services/context-handoff.js.map +1 -0
- package/dist/services/cooldown-probe.d.ts +24 -0
- package/dist/services/cooldown-probe.d.ts.map +1 -0
- package/dist/services/cooldown-probe.js +181 -0
- package/dist/services/cooldown-probe.js.map +1 -0
- package/dist/services/custom-endpoint.d.ts +58 -0
- package/dist/services/custom-endpoint.d.ts.map +1 -0
- package/dist/services/custom-endpoint.js +167 -0
- package/dist/services/custom-endpoint.js.map +1 -0
- package/dist/services/custom-media-register.d.ts +22 -0
- package/dist/services/custom-media-register.d.ts.map +1 -0
- package/dist/services/custom-media-register.js +45 -0
- package/dist/services/custom-media-register.js.map +1 -0
- package/dist/services/custom-model-register.d.ts +50 -0
- package/dist/services/custom-model-register.d.ts.map +1 -0
- package/dist/services/custom-model-register.js +103 -0
- package/dist/services/custom-model-register.js.map +1 -0
- package/dist/services/custom-model-seed.d.ts +18 -0
- package/dist/services/custom-model-seed.d.ts.map +1 -0
- package/dist/services/custom-model-seed.js +42 -0
- package/dist/services/custom-model-seed.js.map +1 -0
- package/dist/services/custom-model-sync.d.ts +37 -0
- package/dist/services/custom-model-sync.d.ts.map +1 -0
- package/dist/services/custom-model-sync.js +118 -0
- package/dist/services/custom-model-sync.js.map +1 -0
- package/dist/services/custom-model-tombstone.d.ts +5 -0
- package/dist/services/custom-model-tombstone.d.ts.map +1 -0
- package/dist/services/custom-model-tombstone.js +30 -0
- package/dist/services/custom-model-tombstone.js.map +1 -0
- package/dist/services/declarative-config.d.ts +356 -0
- package/dist/services/declarative-config.d.ts.map +1 -0
- package/dist/services/declarative-config.js +427 -0
- package/dist/services/declarative-config.js.map +1 -0
- package/dist/services/degradation.d.ts +40 -0
- package/dist/services/degradation.d.ts.map +1 -0
- package/dist/services/degradation.js +119 -0
- package/dist/services/degradation.js.map +1 -0
- package/dist/services/embeddings.d.ts +76 -0
- package/dist/services/embeddings.d.ts.map +1 -0
- package/dist/services/embeddings.js +319 -0
- package/dist/services/embeddings.js.map +1 -0
- package/dist/services/fusion.d.ts +162 -0
- package/dist/services/fusion.d.ts.map +1 -0
- package/dist/services/fusion.js +789 -0
- package/dist/services/fusion.js.map +1 -0
- package/dist/services/gemini-map.d.ts +14 -0
- package/dist/services/gemini-map.d.ts.map +1 -0
- package/dist/services/gemini-map.js +78 -0
- package/dist/services/gemini-map.js.map +1 -0
- package/dist/services/health.d.ts +52 -0
- package/dist/services/health.d.ts.map +1 -0
- package/dist/services/health.js +352 -0
- package/dist/services/health.js.map +1 -0
- package/dist/services/idempotency.d.ts +49 -0
- package/dist/services/idempotency.d.ts.map +1 -0
- package/dist/services/idempotency.js +140 -0
- package/dist/services/idempotency.js.map +1 -0
- package/dist/services/key-budget.d.ts +74 -0
- package/dist/services/key-budget.d.ts.map +1 -0
- package/dist/services/key-budget.js +200 -0
- package/dist/services/key-budget.js.map +1 -0
- package/dist/services/media.d.ts +123 -0
- package/dist/services/media.d.ts.map +1 -0
- package/dist/services/media.js +954 -0
- package/dist/services/media.js.map +1 -0
- package/dist/services/model-discovery.d.ts +126 -0
- package/dist/services/model-discovery.d.ts.map +1 -0
- package/dist/services/model-discovery.js +662 -0
- package/dist/services/model-discovery.js.map +1 -0
- package/dist/services/model-groups.d.ts +149 -0
- package/dist/services/model-groups.d.ts.map +1 -0
- package/dist/services/model-groups.js +314 -0
- package/dist/services/model-groups.js.map +1 -0
- package/dist/services/model-listing.d.ts +18 -0
- package/dist/services/model-listing.d.ts.map +1 -0
- package/dist/services/model-listing.js +94 -0
- package/dist/services/model-listing.js.map +1 -0
- package/dist/services/model-retirement.d.ts +29 -0
- package/dist/services/model-retirement.d.ts.map +1 -0
- package/dist/services/model-retirement.js +86 -0
- package/dist/services/model-retirement.js.map +1 -0
- package/dist/services/model-state.d.ts +90 -0
- package/dist/services/model-state.d.ts.map +1 -0
- package/dist/services/model-state.js +342 -0
- package/dist/services/model-state.js.map +1 -0
- package/dist/services/model-weight-overrides.d.ts +48 -0
- package/dist/services/model-weight-overrides.d.ts.map +1 -0
- package/dist/services/model-weight-overrides.js +127 -0
- package/dist/services/model-weight-overrides.js.map +1 -0
- package/dist/services/notification-emitter.d.ts +50 -0
- package/dist/services/notification-emitter.d.ts.map +1 -0
- package/dist/services/notification-emitter.js +177 -0
- package/dist/services/notification-emitter.js.map +1 -0
- package/dist/services/opencode-free-sync.d.ts +29 -0
- package/dist/services/opencode-free-sync.d.ts.map +1 -0
- package/dist/services/opencode-free-sync.js +206 -0
- package/dist/services/opencode-free-sync.js.map +1 -0
- package/dist/services/penalty-inspector.d.ts +56 -0
- package/dist/services/penalty-inspector.d.ts.map +1 -0
- package/dist/services/penalty-inspector.js +167 -0
- package/dist/services/penalty-inspector.js.map +1 -0
- package/dist/services/profile-models.d.ts +5 -0
- package/dist/services/profile-models.d.ts.map +1 -0
- package/dist/services/profile-models.js +62 -0
- package/dist/services/profile-models.js.map +1 -0
- package/dist/services/provider-credential.d.ts +19 -0
- package/dist/services/provider-credential.d.ts.map +1 -0
- package/dist/services/provider-credential.js +51 -0
- package/dist/services/provider-credential.js.map +1 -0
- package/dist/services/provider-quota.d.ts +64 -0
- package/dist/services/provider-quota.d.ts.map +1 -0
- package/dist/services/provider-quota.js +563 -0
- package/dist/services/provider-quota.js.map +1 -0
- package/dist/services/quirks.d.ts +0 -0
- package/dist/services/quirks.d.ts.map +1 -0
- package/dist/services/quirks.js +0 -0
- package/dist/services/quirks.js.map +1 -0
- package/dist/services/quota-forecast.d.ts +50 -0
- package/dist/services/quota-forecast.d.ts.map +1 -0
- package/dist/services/quota-forecast.js +150 -0
- package/dist/services/quota-forecast.js.map +1 -0
- package/dist/services/quota-outlook.d.ts +4 -0
- package/dist/services/quota-outlook.d.ts.map +1 -0
- package/dist/services/quota-outlook.js +92 -0
- package/dist/services/quota-outlook.js.map +1 -0
- package/dist/services/ratelimit.d.ts +249 -0
- package/dist/services/ratelimit.d.ts.map +1 -0
- package/dist/services/ratelimit.js +1182 -0
- package/dist/services/ratelimit.js.map +1 -0
- package/dist/services/request-retention.d.ts +60 -0
- package/dist/services/request-retention.d.ts.map +1 -0
- package/dist/services/request-retention.js +229 -0
- package/dist/services/request-retention.js.map +1 -0
- package/dist/services/router.d.ts +384 -0
- package/dist/services/router.d.ts.map +1 -0
- package/dist/services/router.js +1986 -0
- package/dist/services/router.js.map +1 -0
- package/dist/services/scoring.d.ts +156 -0
- package/dist/services/scoring.d.ts.map +1 -0
- package/dist/services/scoring.js +435 -0
- package/dist/services/scoring.js.map +1 -0
- package/dist/services/url-tokens.d.ts +15 -0
- package/dist/services/url-tokens.d.ts.map +1 -0
- package/dist/services/url-tokens.js +55 -0
- package/dist/services/url-tokens.js.map +1 -0
- package/package.json +77 -4
- package/README.md +0 -3
|
@@ -0,0 +1,1076 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { routeRequest, resolveModelGroupCandidates, resolveRoutingChain, resolveStickyPreference, routingReserveTokens } from '../services/router.js';
|
|
5
|
+
import { getSetting, getUnifiedApiKey } from '../db/index.js';
|
|
6
|
+
import { contentToString, estimateInputTokens } from '../lib/content.js';
|
|
7
|
+
import { routeOutputBudget } from '../lib/output-cap.js';
|
|
8
|
+
import { resolveTaskType } from '../lib/task-type.js';
|
|
9
|
+
import { repairToolArguments, toolSchemaMap } from '../lib/tool-args.js';
|
|
10
|
+
import { invalidToolArgumentsError, invalidToolCallReasons, isToolArgumentValidationEnabled } from '../lib/tool-validate.js';
|
|
11
|
+
import { rescueInlineToolCalls, startsWithDialectMarker, couldBecomeDialectMarker, containsDialectMarker } from '../lib/tool-call-rescue.js';
|
|
12
|
+
import { sanitizeProviderErrorMessage } from '../lib/error-redaction.js';
|
|
13
|
+
import { convertDocumentBlock, documentRejectionMessage } from '../lib/anthropic-documents.js';
|
|
14
|
+
import { isClientAbortError, newClientAbortError, newHedgeAbortError, isUpstreamClassificationOutput } from '../lib/error-classify.js';
|
|
15
|
+
import { logRequest } from '../lib/request-log.js';
|
|
16
|
+
import { extractApiToken, timingSafeStringEqual, getStickyModel, setStickyModel } from './proxy.js';
|
|
17
|
+
import { runFallbackLoop, newFallbackState, fallbackRoutingTokens, recordUpstreamSuccess, setFallbackHeaders, setExhaustionHeaders } from '../lib/fallback-loop.js';
|
|
18
|
+
import { routedViaValue } from '../lib/header-value.js';
|
|
19
|
+
import { applyTokenBudget, tokenBudgetMessage } from '../lib/guardrails.js';
|
|
20
|
+
import { resolveAnthropicModel, claudeFamilyDiscoveryEntries } from '../services/anthropic-map.js';
|
|
21
|
+
import { isUnifyEnabled, getModelGroups, resolveRequestedIdForDispatch } from '../services/model-groups.js';
|
|
22
|
+
import { buildModelListing } from '../services/model-listing.js';
|
|
23
|
+
import { compressRequest, formatCompressionHeader } from '../services/compression/pipeline.js';
|
|
24
|
+
import { normalizeMessageImages } from '../lib/image-normalize.js';
|
|
25
|
+
// Anthropic-compatible Messages API (`POST /v1/messages`). This is a thin
|
|
26
|
+
// translation layer over the SAME router/fallback/analytics machinery the
|
|
27
|
+
// OpenAI `/v1/chat/completions` route uses — it converts the Anthropic wire
|
|
28
|
+
// format to our internal (OpenAI-shaped) ChatMessage form on the way in, runs
|
|
29
|
+
// the normal routing loop, and converts the result back to Anthropic shape on
|
|
30
|
+
// the way out. The OpenAI route is left untouched.
|
|
31
|
+
//
|
|
32
|
+
// The point is Claude Code (and anything else that speaks the Anthropic SDK):
|
|
33
|
+
// point `ANTHROPIC_BASE_URL` at this server, set the API key to the unified
|
|
34
|
+
// key, and every `claude-*` request transparently routes to whatever free
|
|
35
|
+
// model the chain picks. Auth accepts Anthropic's native `x-api-key` header
|
|
36
|
+
// (already handled by extractApiToken) as well as a bearer token.
|
|
37
|
+
export const anthropicRouter = Router();
|
|
38
|
+
const AUTO_MODEL_ID = 'auto';
|
|
39
|
+
function isAutoModel(modelId) {
|
|
40
|
+
if (!modelId)
|
|
41
|
+
return true;
|
|
42
|
+
const lower = modelId.toLowerCase();
|
|
43
|
+
return lower === AUTO_MODEL_ID || lower.startsWith(`${AUTO_MODEL_ID}:`);
|
|
44
|
+
}
|
|
45
|
+
const MAX_RETRIES = 20;
|
|
46
|
+
// Anthropic requires `max_tokens`; mirror the OpenAI route's routing-budget
|
|
47
|
+
// default when a client somehow omits it.
|
|
48
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
49
|
+
const IMAGE_TOKEN_ESTIMATE = 1000;
|
|
50
|
+
// ── Request schema ──────────────────────────────────────────────────────────
|
|
51
|
+
// Permissive on purpose: the Anthropic content-block vocabulary grows over
|
|
52
|
+
// time (thinking, document, server-tool blocks…) and Claude Code sends blocks
|
|
53
|
+
// with extra fields like `cache_control`. We validate the envelope and handle
|
|
54
|
+
// the block types we understand by `type`, ignoring the rest — same tolerance
|
|
55
|
+
// philosophy as the OpenAI route (#200).
|
|
56
|
+
const contentBlockSchema = z.object({ type: z.string() }).passthrough();
|
|
57
|
+
const anthropicMessageSchema = z.object({
|
|
58
|
+
// Anthropic's own API only allows user/assistant here, but real clients
|
|
59
|
+
// (Claude Code, routers) sometimes inline a `system` turn in the messages
|
|
60
|
+
// array. Accept it and fold it into the system context rather than 400-ing —
|
|
61
|
+
// same tolerance philosophy as the OpenAI route's developer/function roles.
|
|
62
|
+
role: z.enum(['user', 'assistant', 'system']),
|
|
63
|
+
content: z.union([z.string(), z.array(contentBlockSchema)]),
|
|
64
|
+
}).passthrough();
|
|
65
|
+
const anthropicToolSchema = z.object({
|
|
66
|
+
name: z.string().min(1),
|
|
67
|
+
description: z.string().optional(),
|
|
68
|
+
input_schema: z.record(z.string(), z.unknown()).optional(),
|
|
69
|
+
}).passthrough();
|
|
70
|
+
const anthropicToolChoiceSchema = z.object({
|
|
71
|
+
type: z.enum(['auto', 'any', 'tool', 'none']),
|
|
72
|
+
name: z.string().optional(),
|
|
73
|
+
}).passthrough();
|
|
74
|
+
const messagesSchema = z.object({
|
|
75
|
+
model: z.string().optional(),
|
|
76
|
+
// Anthropic mandates max_tokens; accept omission and clamp non-positive
|
|
77
|
+
// values to the default rather than 400-ing (some clients send 0).
|
|
78
|
+
max_tokens: z.number().int().optional(),
|
|
79
|
+
messages: z.array(anthropicMessageSchema).min(1),
|
|
80
|
+
system: z.union([z.string(), z.array(contentBlockSchema)]).optional(),
|
|
81
|
+
temperature: z.number().min(0).max(2).optional(),
|
|
82
|
+
top_p: z.number().min(0).max(1).optional(),
|
|
83
|
+
// Anthropic's native top_k — forwarded to providers that support it via the
|
|
84
|
+
// platform policy in lib/sampling-params.ts.
|
|
85
|
+
top_k: z.number().int().min(1).nullable().optional(),
|
|
86
|
+
stream: z.boolean().optional(),
|
|
87
|
+
stop_sequences: z.array(z.string()).optional(),
|
|
88
|
+
tools: z.array(anthropicToolSchema).optional(),
|
|
89
|
+
tool_choice: anthropicToolChoiceSchema.optional(),
|
|
90
|
+
// Anthropic's native extended-thinking knob. Mapped onto the internal
|
|
91
|
+
// reasoning_effort (see effortFromAnthropicThinking) so providers with
|
|
92
|
+
// request-side reasoning control receive it; platforms without support have
|
|
93
|
+
// it stripped by the policy in lib/sampling-params.ts.
|
|
94
|
+
// `type` is a free string, not the enabled/disabled enum: Anthropic keeps
|
|
95
|
+
// adding modes (Claude Code sends 'adaptive') and validating the enum here
|
|
96
|
+
// turned a knob we only use as a hint into a hard 400 (#632).
|
|
97
|
+
thinking: z.object({
|
|
98
|
+
type: z.string().optional(),
|
|
99
|
+
budget_tokens: z.number().int().optional(),
|
|
100
|
+
}).passthrough().nullable().optional(),
|
|
101
|
+
}).passthrough();
|
|
102
|
+
// Anthropic expresses reasoning control as a token budget; our internal knob
|
|
103
|
+
// is OpenAI's coarse effort scale. Thresholds follow Anthropic's own guidance
|
|
104
|
+
// bands (minimum budget 1024; multi-10k budgets for hard problems).
|
|
105
|
+
export function effortFromAnthropicThinking(thinking) {
|
|
106
|
+
if (!thinking)
|
|
107
|
+
return undefined;
|
|
108
|
+
const type = thinking.type?.trim().toLowerCase();
|
|
109
|
+
if (type === 'disabled' || type === 'off' || type === 'none')
|
|
110
|
+
return 'none';
|
|
111
|
+
const budget = thinking.budget_tokens;
|
|
112
|
+
// Model-managed modes ('adaptive', 'auto') without an explicit budget mean
|
|
113
|
+
// "you decide how much" — forward no knob at all and let the provider
|
|
114
|
+
// default stand, same as a -1 Gemini thinking budget.
|
|
115
|
+
if (budget == null && (type === 'adaptive' || type === 'auto'))
|
|
116
|
+
return undefined;
|
|
117
|
+
if (budget == null)
|
|
118
|
+
return 'medium';
|
|
119
|
+
if (budget < 4096)
|
|
120
|
+
return 'low';
|
|
121
|
+
if (budget < 16384)
|
|
122
|
+
return 'medium';
|
|
123
|
+
return 'high';
|
|
124
|
+
}
|
|
125
|
+
function sendError(res, status, errorType, message) {
|
|
126
|
+
res.status(status).json({ type: 'error', error: { type: errorType, message } });
|
|
127
|
+
}
|
|
128
|
+
// The shared exhaustion body's `type` strings are chosen to be valid on the
|
|
129
|
+
// OpenAI-shaped surfaces; several of them are not Anthropic error types. Remap
|
|
130
|
+
// them onto Anthropic's vocabulary via the body's coarse `kind` so this
|
|
131
|
+
// surface stays wire-correct: auth/upstream failures → 'api_error', pool
|
|
132
|
+
// unavailability → 'overloaded_error', context exhaustion → Anthropic's 413
|
|
133
|
+
// 'request_too_large', model-gone → 'not_found_error'.
|
|
134
|
+
function anthropicErrorType(body) {
|
|
135
|
+
if (body.kind === 'auth' || body.kind === 'upstream')
|
|
136
|
+
return 'api_error';
|
|
137
|
+
if (body.kind === 'unavailable')
|
|
138
|
+
return 'overloaded_error';
|
|
139
|
+
if (body.kind === 'context_too_large')
|
|
140
|
+
return 'request_too_large';
|
|
141
|
+
if (body.kind === 'model_not_found')
|
|
142
|
+
return 'not_found_error';
|
|
143
|
+
return body.type;
|
|
144
|
+
}
|
|
145
|
+
// Render an exhaustion body on the Anthropic wire shape, carrying the
|
|
146
|
+
// machine-readable extras (`code`, `retryAtMs`) alongside Anthropic's standard
|
|
147
|
+
// type/message and stamping the matching Retry-After header. Extra keys on the
|
|
148
|
+
// error object are ignored by Anthropic SDKs, so this stays wire-compatible.
|
|
149
|
+
function sendExhaustion(res, body) {
|
|
150
|
+
setExhaustionHeaders(res, body);
|
|
151
|
+
res.status(body.status).json({
|
|
152
|
+
type: 'error',
|
|
153
|
+
error: {
|
|
154
|
+
type: anthropicErrorType(body),
|
|
155
|
+
message: body.message,
|
|
156
|
+
...(body.code ? { code: body.code } : {}),
|
|
157
|
+
...(body.retryAtMs != null ? { retryAtMs: body.retryAtMs } : {}),
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function newMessageId() {
|
|
162
|
+
return `msg_${crypto.randomBytes(12).toString('hex')}`;
|
|
163
|
+
}
|
|
164
|
+
// ── Auth (shared with the OpenAI route) ─────────────────────────────────────
|
|
165
|
+
function authenticate(req, res) {
|
|
166
|
+
const token = extractApiToken(req);
|
|
167
|
+
const unifiedKey = getUnifiedApiKey();
|
|
168
|
+
if (!token || !timingSafeStringEqual(token, unifiedKey)) {
|
|
169
|
+
sendError(res, 401, 'authentication_error', 'Invalid API key');
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
// ── Request translation: Anthropic → internal (OpenAI-shaped) ───────────────
|
|
175
|
+
function flattenSystem(system) {
|
|
176
|
+
if (!system)
|
|
177
|
+
return '';
|
|
178
|
+
if (typeof system === 'string')
|
|
179
|
+
return system;
|
|
180
|
+
return system
|
|
181
|
+
.map(block => (typeof block.text === 'string' ? block.text : ''))
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
.join('\n');
|
|
184
|
+
}
|
|
185
|
+
// An Anthropic tool_result's content is a string or an array of blocks; flatten
|
|
186
|
+
// to text for the internal `tool` message (we don't forward tool images).
|
|
187
|
+
function flattenToolResult(content) {
|
|
188
|
+
if (content == null)
|
|
189
|
+
return '';
|
|
190
|
+
if (typeof content === 'string')
|
|
191
|
+
return content;
|
|
192
|
+
if (!Array.isArray(content))
|
|
193
|
+
return JSON.stringify(content);
|
|
194
|
+
return content
|
|
195
|
+
.map(block => {
|
|
196
|
+
if (typeof block === 'string')
|
|
197
|
+
return block;
|
|
198
|
+
if (block && typeof block === 'object' && typeof block.text === 'string')
|
|
199
|
+
return block.text;
|
|
200
|
+
return '';
|
|
201
|
+
})
|
|
202
|
+
.filter(Boolean)
|
|
203
|
+
.join('\n');
|
|
204
|
+
}
|
|
205
|
+
// Anthropic image block → OpenAI `image_url` block, so vision models keep
|
|
206
|
+
// working through the same routing path the OpenAI route uses.
|
|
207
|
+
function imageBlockToUrl(block) {
|
|
208
|
+
const src = block?.source;
|
|
209
|
+
if (!src || typeof src !== 'object')
|
|
210
|
+
return null;
|
|
211
|
+
if (src.type === 'base64' && src.media_type && src.data) {
|
|
212
|
+
return `data:${src.media_type};base64,${src.data}`;
|
|
213
|
+
}
|
|
214
|
+
if (src.type === 'url' && typeof src.url === 'string')
|
|
215
|
+
return src.url;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function convertToolChoice(choice) {
|
|
219
|
+
if (!choice)
|
|
220
|
+
return undefined;
|
|
221
|
+
switch (choice.type) {
|
|
222
|
+
case 'auto': return 'auto';
|
|
223
|
+
case 'none': return 'none';
|
|
224
|
+
case 'any': return 'required'; // Anthropic "any" == OpenAI "required"
|
|
225
|
+
case 'tool': return choice.name ? { type: 'function', function: { name: choice.name } } : 'required';
|
|
226
|
+
default: return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function convertRequest(input) {
|
|
230
|
+
const messages = [];
|
|
231
|
+
const documentRejections = [];
|
|
232
|
+
let hasImage = false;
|
|
233
|
+
const system = flattenSystem(input.system);
|
|
234
|
+
if (system)
|
|
235
|
+
messages.push({ role: 'system', content: system });
|
|
236
|
+
for (const message of input.messages) {
|
|
237
|
+
// A `system` turn inlined in the messages array → fold into system context
|
|
238
|
+
// (flatten text blocks). Anthropic clients occasionally send this; the real
|
|
239
|
+
// API rejects it, but we'd rather route the request than 400.
|
|
240
|
+
if (message.role === 'system') {
|
|
241
|
+
const sysText = typeof message.content === 'string'
|
|
242
|
+
? message.content
|
|
243
|
+
: message.content.map(b => (typeof b.text === 'string' ? b.text : '')).filter(Boolean).join('\n');
|
|
244
|
+
if (sysText)
|
|
245
|
+
messages.push({ role: 'system', content: sysText });
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (typeof message.content === 'string') {
|
|
249
|
+
messages.push({ role: message.role, content: message.content });
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const textParts = [];
|
|
253
|
+
const imageBlocks = [];
|
|
254
|
+
const toolCalls = [];
|
|
255
|
+
const toolResults = [];
|
|
256
|
+
for (const block of message.content) {
|
|
257
|
+
const type = block.type;
|
|
258
|
+
if (type === 'text') {
|
|
259
|
+
if (typeof block.text === 'string' && block.text.length > 0) {
|
|
260
|
+
textParts.push(block.text);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (type === 'image') {
|
|
264
|
+
const url = imageBlockToUrl(block);
|
|
265
|
+
if (url) {
|
|
266
|
+
imageBlocks.push({ type: 'image_url', image_url: { url } });
|
|
267
|
+
hasImage = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (type === 'tool_use') {
|
|
271
|
+
const thoughtSignature = block.thought_signature ?? block.thoughtSignature;
|
|
272
|
+
toolCalls.push({
|
|
273
|
+
id: String(block.id ?? ''),
|
|
274
|
+
type: 'function',
|
|
275
|
+
function: { name: String(block.name ?? ''), arguments: JSON.stringify(block.input ?? {}) },
|
|
276
|
+
...(typeof thoughtSignature === 'string' && thoughtSignature.length > 0 ? { thought_signature: thoughtSignature } : {}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else if (type === 'tool_result') {
|
|
280
|
+
toolResults.push({
|
|
281
|
+
role: 'tool',
|
|
282
|
+
tool_call_id: String(block.tool_use_id ?? ''),
|
|
283
|
+
content: flattenToolResult(block.content),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else if (type === 'document') {
|
|
287
|
+
// A document that is already text costs nothing to inline. One that
|
|
288
|
+
// needs decoding cannot be served by any provider here, and dropping
|
|
289
|
+
// it would answer confidently about a document the model never saw.
|
|
290
|
+
const result = convertDocumentBlock(block);
|
|
291
|
+
if (result.ok)
|
|
292
|
+
textParts.push(result.text);
|
|
293
|
+
else
|
|
294
|
+
documentRejections.push(result.reason);
|
|
295
|
+
}
|
|
296
|
+
// Other unknown block types (thinking, …) are intentionally dropped.
|
|
297
|
+
}
|
|
298
|
+
const text = textParts.join('\n');
|
|
299
|
+
if (message.role === 'assistant') {
|
|
300
|
+
messages.push({
|
|
301
|
+
role: 'assistant',
|
|
302
|
+
content: text.length > 0 ? text : null,
|
|
303
|
+
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// User turn. Anthropic carries tool results inside a user message; split
|
|
308
|
+
// them into the `tool` messages OpenAI expects (which must directly
|
|
309
|
+
// follow the assistant's tool_calls), then any fresh user text/images.
|
|
310
|
+
messages.push(...toolResults);
|
|
311
|
+
if (imageBlocks.length > 0) {
|
|
312
|
+
const blocks = [];
|
|
313
|
+
if (text.length > 0)
|
|
314
|
+
blocks.push({ type: 'text', text });
|
|
315
|
+
blocks.push(...imageBlocks);
|
|
316
|
+
messages.push({ role: 'user', content: blocks });
|
|
317
|
+
}
|
|
318
|
+
else if (text.length > 0) {
|
|
319
|
+
messages.push({ role: 'user', content: text });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const tools = input.tools?.map(t => ({
|
|
324
|
+
type: 'function',
|
|
325
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema ?? { type: 'object', properties: {} } },
|
|
326
|
+
}));
|
|
327
|
+
return {
|
|
328
|
+
messages,
|
|
329
|
+
tools,
|
|
330
|
+
tool_choice: convertToolChoice(input.tool_choice),
|
|
331
|
+
hasImage,
|
|
332
|
+
wantsTools: (tools?.length ?? 0) > 0,
|
|
333
|
+
documentRejections,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Model resolution (Claude family → auto | pinned catalog model) lives in
|
|
337
|
+
// services/anthropic-map.ts so the dashboard mapping editor and this route
|
|
338
|
+
// share one source of truth. Claude Code keeps its built-in `claude-*` names;
|
|
339
|
+
// by default every family maps to "auto" (the router picks a free model).
|
|
340
|
+
// A stable created_at for the Anthropic /v1/models entries; clients only use it
|
|
341
|
+
// for display ordering, so a constant avoids needless churn.
|
|
342
|
+
const MODEL_CREATED_AT = '2026-01-01T00:00:00Z';
|
|
343
|
+
// ── Response translation: internal → Anthropic ──────────────────────────────
|
|
344
|
+
function mapStopReason(finishReason, hadToolCalls) {
|
|
345
|
+
if (hadToolCalls || finishReason === 'tool_calls')
|
|
346
|
+
return 'tool_use';
|
|
347
|
+
if (finishReason === 'length')
|
|
348
|
+
return 'max_tokens';
|
|
349
|
+
if (finishReason === 'stop' || finishReason === 'content_filter' || finishReason == null)
|
|
350
|
+
return 'end_turn';
|
|
351
|
+
return 'end_turn';
|
|
352
|
+
}
|
|
353
|
+
function parseToolInput(raw) {
|
|
354
|
+
try {
|
|
355
|
+
return JSON.parse(raw);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function toAnthropicContent(message) {
|
|
362
|
+
const blocks = [];
|
|
363
|
+
// Reasoning output (native provider reasoning_content, or extracted from an
|
|
364
|
+
// inline <think> block by lib/think-tags.ts) → Anthropic thinking block,
|
|
365
|
+
// first per Anthropic convention.
|
|
366
|
+
const reasoning = message?.reasoning_content;
|
|
367
|
+
if (typeof reasoning === 'string' && reasoning.length > 0) {
|
|
368
|
+
blocks.push({ type: 'thinking', thinking: reasoning, signature: '' });
|
|
369
|
+
}
|
|
370
|
+
const text = contentToString(message?.content ?? '');
|
|
371
|
+
if (text.length > 0)
|
|
372
|
+
blocks.push({ type: 'text', text });
|
|
373
|
+
for (const call of message?.tool_calls ?? []) {
|
|
374
|
+
const block = {
|
|
375
|
+
type: 'tool_use',
|
|
376
|
+
id: call.id,
|
|
377
|
+
name: call.function.name,
|
|
378
|
+
input: parseToolInput(call.function.arguments),
|
|
379
|
+
};
|
|
380
|
+
if (call.thought_signature)
|
|
381
|
+
block.thought_signature = call.thought_signature;
|
|
382
|
+
blocks.push(block);
|
|
383
|
+
}
|
|
384
|
+
return blocks;
|
|
385
|
+
}
|
|
386
|
+
// ── SSE helpers ─────────────────────────────────────────────────────────────
|
|
387
|
+
function writeSse(res, event, data) {
|
|
388
|
+
res.write(`event: ${event}\n`);
|
|
389
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
390
|
+
}
|
|
391
|
+
// Convert rescued inline tool calls (parsed out of a model that serialized its
|
|
392
|
+
// tool call as TEXT) into OpenAI-shaped tool_calls, so toAnthropicContent /
|
|
393
|
+
// the streaming tool_use emitter render them as Anthropic tool_use blocks —
|
|
394
|
+
// the same rescue /chat/completions and /v1/responses already apply (#231).
|
|
395
|
+
function rescuedToToolCalls(calls, schemas) {
|
|
396
|
+
return calls.map((c, i) => ({
|
|
397
|
+
id: `toolu_${crypto.randomBytes(8).toString('hex')}_rescued_${i + 1}`,
|
|
398
|
+
type: 'function',
|
|
399
|
+
function: { name: c.name, arguments: repairToolArguments(c.arguments, schemas.get(c.name)) },
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
anthropicRouter.post('/messages', async (req, res) => {
|
|
403
|
+
const start = Date.now();
|
|
404
|
+
if (!authenticate(req, res))
|
|
405
|
+
return;
|
|
406
|
+
const parsed = messagesSchema.safeParse(req.body);
|
|
407
|
+
if (!parsed.success) {
|
|
408
|
+
const detail = parsed.error.errors
|
|
409
|
+
.map(e => (e.path.length ? `${e.path.join('.')}: ${e.message}` : e.message))
|
|
410
|
+
.slice(0, 5).join(', ');
|
|
411
|
+
console.warn(`[anthropic] 400 invalid /v1/messages request: ${detail}`);
|
|
412
|
+
sendError(res, 400, 'invalid_request_error', `Invalid request: ${detail}`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const body = parsed.data;
|
|
416
|
+
const requestedModel = body.model ?? 'auto';
|
|
417
|
+
const routedModel = body.model?.startsWith('claude/')
|
|
418
|
+
? body.model.slice('claude/'.length)
|
|
419
|
+
: body.model;
|
|
420
|
+
// The Anthropic wire format requires max_tokens, but OUR default injection
|
|
421
|
+
// must not change the budget gate's verdict: gating AFTER defaulting made a
|
|
422
|
+
// no-max_tokens request 413 here (input + 1024 over budget) where the chat
|
|
423
|
+
// surface would cap it and serve. Gate on what the client actually sent,
|
|
424
|
+
// then resolve the default against the budget remainder.
|
|
425
|
+
const clientMaxTokens = body.max_tokens != null && body.max_tokens > 0 ? body.max_tokens : undefined;
|
|
426
|
+
const { temperature, top_p, stream } = body;
|
|
427
|
+
const converted = convertRequest(body);
|
|
428
|
+
// Rejected before routing, and before compression: this is our verdict, not
|
|
429
|
+
// a provider's. Entering the failover loop would spend quota on a request
|
|
430
|
+
// every candidate would fail identically, and book it as provider failure.
|
|
431
|
+
if (converted.documentRejections.length > 0) {
|
|
432
|
+
const message = documentRejectionMessage(converted.documentRejections);
|
|
433
|
+
console.warn(`[anthropic] 400 unsupported document block: ${converted.documentRejections.join('; ')}`);
|
|
434
|
+
sendError(res, 400, 'invalid_request_error', message);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
let { messages } = converted;
|
|
438
|
+
const { tools, tool_choice, hasImage, wantsTools } = converted;
|
|
439
|
+
// Downscale over-threshold inline images before compression/estimation so
|
|
440
|
+
// the budget, routing, and upstream transfer all see the shrunk bytes
|
|
441
|
+
// (see lib/image-normalize.ts). The cache-control detection below reads the
|
|
442
|
+
// RAW wire body, and normalization mutates urls in place keeping the blocks
|
|
443
|
+
// (and any cache_control on them) intact — neither is disturbed.
|
|
444
|
+
await normalizeMessageImages(messages);
|
|
445
|
+
const systemHasCacheControl = Array.isArray(body.system)
|
|
446
|
+
&& body.system.some(block => block && typeof block === 'object' && 'cache_control' in block);
|
|
447
|
+
const messageHasCacheControl = body.messages.some(message => Array.isArray(message.content)
|
|
448
|
+
&& message.content.some(block => block && typeof block === 'object' && 'cache_control' in block));
|
|
449
|
+
const compressionResult = compressRequest(messages, {
|
|
450
|
+
header: req.headers['x-freellm-compress'],
|
|
451
|
+
tools,
|
|
452
|
+
// Claude Code normally marks the system prefix. If a later native message
|
|
453
|
+
// carries a breakpoint, conservatively cap the translated prefix at
|
|
454
|
+
// lossless rather than risk invalidating upstream prompt-cache semantics.
|
|
455
|
+
cacheControlPrefixLength: messageHasCacheControl ? messages.length : (systemHasCacheControl ? 1 : 0),
|
|
456
|
+
});
|
|
457
|
+
messages = compressionResult.messages;
|
|
458
|
+
res.setHeader('X-FreeLLM-Compress', formatCompressionHeader(compressionResult));
|
|
459
|
+
const estimatedInputTokens = estimateInputTokens(messages, tools);
|
|
460
|
+
const imageCount = messages.reduce((n, m) => n + (Array.isArray(m.content) ? m.content.filter(b => b?.type === 'image_url').length : 0), 0);
|
|
461
|
+
// Guardrail: per-request token budget (request_max_tokens_budget, default
|
|
462
|
+
// off). A client-set max_tokens can only pass or reject; an omitted one is
|
|
463
|
+
// capped to the budget remainder, mirroring /v1/chat/completions.
|
|
464
|
+
const budgetCheck = applyTokenBudget(estimatedInputTokens + imageCount * IMAGE_TOKEN_ESTIMATE, clientMaxTokens);
|
|
465
|
+
if (budgetCheck.rejection) {
|
|
466
|
+
sendError(res, 413, 'invalid_request_error', tokenBudgetMessage(budgetCheck.rejection));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const max_tokens = clientMaxTokens
|
|
470
|
+
?? (budgetCheck.maxTokens != null ? Math.min(DEFAULT_MAX_TOKENS, budgetCheck.maxTokens) : DEFAULT_MAX_TOKENS);
|
|
471
|
+
const completionOptions = {
|
|
472
|
+
temperature, max_tokens, top_p, top_k: body.top_k ?? undefined, tools, tool_choice,
|
|
473
|
+
// Native `thinking: {type, budget_tokens}` → the same internal knob the
|
|
474
|
+
// OpenAI surface's reasoning_effort feeds; absent when the client sent none.
|
|
475
|
+
reasoning_effort: effortFromAnthropicThinking(body.thinking),
|
|
476
|
+
};
|
|
477
|
+
// Capped output reserve so a large max_tokens can't falsely exclude the model
|
|
478
|
+
// pool (#470); input + images count in full. Threaded to the router
|
|
479
|
+
// separately: it is exact and must not be inflated by the context-window
|
|
480
|
+
// safety margin (#956 review).
|
|
481
|
+
const outputReserve = routingReserveTokens(max_tokens);
|
|
482
|
+
const estimatedTotal = estimatedInputTokens + imageCount * IMAGE_TOKEN_ESTIMATE + outputReserve;
|
|
483
|
+
// Resolve the model through the operator's Claude-family map (opus/sonnet/
|
|
484
|
+
// haiku/default → auto | a pinned catalog model). A concrete catalog id pins
|
|
485
|
+
// directly. `pinned` drives the analytics requested-model label.
|
|
486
|
+
const resolved = resolveAnthropicModel(routedModel);
|
|
487
|
+
const pinnedModelId = resolved.pinned ? (body.model ?? null) : null;
|
|
488
|
+
// Named `auto:<profile>` chains (and plain `auto`) must go through the same
|
|
489
|
+
// resolver as /v1/chat/completions. resolveAnthropicModel treats unknown ids
|
|
490
|
+
// as unpinned auto-route, which used to silently walk the active pool.
|
|
491
|
+
let resolvedChain;
|
|
492
|
+
let strategyKey;
|
|
493
|
+
const autoModelString = isAutoModel(requestedModel)
|
|
494
|
+
? requestedModel
|
|
495
|
+
: (isAutoModel(routedModel) ? routedModel : undefined);
|
|
496
|
+
if (!resolved.pinned && autoModelString !== undefined) {
|
|
497
|
+
try {
|
|
498
|
+
resolvedChain = resolveRoutingChain(autoModelString);
|
|
499
|
+
// Named auto:<profile> / auto:smart get their own sticky bucket. Plain
|
|
500
|
+
// `auto` stays unscoped so Claude Code session affinity is unchanged.
|
|
501
|
+
strategyKey = resolvedChain.strategyKey === 'auto' ? undefined : resolvedChain.strategyKey;
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
if (err?.status === 400) {
|
|
505
|
+
sendError(res, 400, 'invalid_request_error', err.message);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
throw err;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// Session affinity: Claude Code stamps every request in a session with
|
|
512
|
+
// X-Claude-Code-Session-Id. When auto-routing, stick the whole session to one
|
|
513
|
+
// model so it doesn't flap between free providers mid-conversation.
|
|
514
|
+
const rawSession = req.headers['x-claude-code-session-id'] ?? req.headers['x-session-id'];
|
|
515
|
+
const sessionId = Array.isArray(rawSession) ? rawSession[0] : rawSession;
|
|
516
|
+
// Same-model cross-provider failover (#932). A pin resolves to ONE catalog
|
|
517
|
+
// row, and until now that row was the only thing the request preferred: when
|
|
518
|
+
// its provider failed, the loop walked the rest of the catalog and answered
|
|
519
|
+
// with a completely different model. The OpenAI, Responses and inbound-chat
|
|
520
|
+
// surfaces already avoid that by routing a pinned id over its unified model
|
|
521
|
+
// group as a STRICT chain (#335) — every provider serving the same logical
|
|
522
|
+
// model, and nothing else. Build the identical chain here so /v1/messages
|
|
523
|
+
// behaves the same.
|
|
524
|
+
//
|
|
525
|
+
// Only the pinned paths change. A Claude family alias mapped to 'auto' (the
|
|
526
|
+
// default for opus/sonnet/haiku/default) resolves to no model at all and
|
|
527
|
+
// keeps auto-routing over the full chain exactly as before.
|
|
528
|
+
let groupChain;
|
|
529
|
+
// Sticky scope for a group-pinned request: bucket by the pinned catalog id so
|
|
530
|
+
// the session prefers its last successful provider of THIS model without
|
|
531
|
+
// leaking affinity across groups. Undefined for auto (the global scope).
|
|
532
|
+
let stickyScope;
|
|
533
|
+
if (resolved.modelId) {
|
|
534
|
+
const dispatch = isUnifyEnabled() ? resolveRequestedIdForDispatch(resolved.modelId, getModelGroups()) : null;
|
|
535
|
+
const members = dispatch?.memberDbIds ?? null;
|
|
536
|
+
if (members && members.length > 0) {
|
|
537
|
+
const chain = resolveModelGroupCandidates(members, dispatch.demotedDbIds);
|
|
538
|
+
// An empty chain would mean the pinned row is no longer routable at all;
|
|
539
|
+
// this surface is lenient, so leave the legacy single-row pin in place
|
|
540
|
+
// rather than failing the request.
|
|
541
|
+
if (chain.length > 0) {
|
|
542
|
+
groupChain = chain;
|
|
543
|
+
stickyScope = resolved.modelId;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
let preferredModel = resolved.preferredModelDbId;
|
|
548
|
+
if (groupChain) {
|
|
549
|
+
// The strict chain already fixes the model; the only preference left is
|
|
550
|
+
// which member served this session last. Passing a non-member would make
|
|
551
|
+
// routeRequest splice an off-group model into the chain and break the pin.
|
|
552
|
+
const sticky = getStickyModel(messages, sessionId, stickyScope);
|
|
553
|
+
preferredModel = (sticky != null && groupChain.some(r => r.model_db_id === sticky)) ? sticky : undefined;
|
|
554
|
+
}
|
|
555
|
+
if (preferredModel == null && !groupChain) {
|
|
556
|
+
preferredModel = resolveStickyPreference(getStickyModel(messages, sessionId, strategyKey), resolvedChain?.chain);
|
|
557
|
+
}
|
|
558
|
+
// Thin adapter over the shared fallback loop (lib/fallback-loop.ts): the
|
|
559
|
+
// cooldown/skip/penalty/exhaustion machinery is shared, only the Anthropic
|
|
560
|
+
// request/stream translation lives here. This converged three drifts on this
|
|
561
|
+
// surface: it now honors a provider Retry-After and day-benches a 403 (via the
|
|
562
|
+
// shared cooldown), returns a shared exhaustion body (a 400 invalid_request
|
|
563
|
+
// when every provider rejected the request, not always a 429), and applies the
|
|
564
|
+
// inline tool-call dialect rescue that the OpenAI/Responses surfaces carry.
|
|
565
|
+
const state = newFallbackState();
|
|
566
|
+
// Lets the failover loop learn which models reject tool calls (#1230).
|
|
567
|
+
state.wantsTools = wantsTools;
|
|
568
|
+
const attemptLog = [];
|
|
569
|
+
// Client-disconnect fan-out: the flag stops the loop before the NEXT
|
|
570
|
+
// attempt; the AbortController (threaded to the provider as
|
|
571
|
+
// CompletionOptions.signal) additionally cancels the IN-FLIGHT upstream
|
|
572
|
+
// fetch and any body/stream read, so tokens stop burning and the in-flight
|
|
573
|
+
// lease frees immediately. 'close' also fires on normal completion —
|
|
574
|
+
// writableEnded distinguishes a real disconnect.
|
|
575
|
+
let clientGone = false;
|
|
576
|
+
const clientAbort = new AbortController();
|
|
577
|
+
// Fallback-v2 hedging: the loop aborts this controller (via abortInFlight)
|
|
578
|
+
// when the wall-clock retry budget expires mid-attempt, canceling the
|
|
579
|
+
// in-flight upstream instead of waiting for a stalled attempt to time out.
|
|
580
|
+
const hedgeAbort = new AbortController();
|
|
581
|
+
res.on('close', () => {
|
|
582
|
+
if (!res.writableEnded) {
|
|
583
|
+
clientGone = true;
|
|
584
|
+
clientAbort.abort(newClientAbortError());
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
const dispatchOptions = { ...completionOptions, signal: AbortSignal.any([clientAbort.signal, hedgeAbort.signal]) };
|
|
588
|
+
await runFallbackLoop({
|
|
589
|
+
maxRetries: MAX_RETRIES,
|
|
590
|
+
state,
|
|
591
|
+
attemptLog,
|
|
592
|
+
logIdentity: { surface: 'anthropic messages', requestedModel },
|
|
593
|
+
clientGone: () => clientGone,
|
|
594
|
+
abortInFlight: () => hedgeAbort.abort(newHedgeAbortError()),
|
|
595
|
+
route: () => {
|
|
596
|
+
// Task-type routing (#1127): same header/derivation as /chat/completions.
|
|
597
|
+
const taskType = resolveTaskType(req, tools, messages);
|
|
598
|
+
// #507: inflate the routing estimate from any provider-reported REQUESTED
|
|
599
|
+
// size latched onto state so the existing size gates in router.ts skip
|
|
600
|
+
// low-TPM / small-context models on retry.
|
|
601
|
+
const routingTotal = fallbackRoutingTokens(state, estimatedTotal, outputReserve);
|
|
602
|
+
return routeRequest(routingTotal, state.skipKeys.size > 0 ? state.skipKeys : undefined, preferredModel, hasImage, wantsTools, state.skipModels.size > 0 ? state.skipModels : undefined, groupChain ?? resolvedChain?.chain, false, state.skipPlatforms.size > 0 ? state.skipPlatforms : undefined, outputReserve, taskType);
|
|
603
|
+
},
|
|
604
|
+
dispatch: async (route, attempt, dispatchCtx) => {
|
|
605
|
+
const contextBudget = routeOutputBudget(route, estimatedInputTokens);
|
|
606
|
+
const routeOptions = { ...dispatchOptions, contextBudget };
|
|
607
|
+
if (stream) {
|
|
608
|
+
try {
|
|
609
|
+
await streamCompletion(res, route, messages, routeOptions, {
|
|
610
|
+
start, attempt, attemptLog, clientGone: () => clientGone, requestedModel, estimatedInputTokens, tools, pinnedModelId,
|
|
611
|
+
sessionId, pinned: resolved.pinned, stickyScope, strategyKey, disarmHedge: dispatchCtx.disarmHedge, state,
|
|
612
|
+
});
|
|
613
|
+
return 'done';
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
// The stream already committed (message_start sent) and surfaced its
|
|
617
|
+
// own error event; stop without failover or a second response.
|
|
618
|
+
if (err instanceof StreamAlreadyStarted)
|
|
619
|
+
return 'committed';
|
|
620
|
+
throw err;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const result = await route.provider.chatCompletion(route.apiKey, messages, route.modelId, routeOptions);
|
|
624
|
+
const respMsg = result.choices?.[0]?.message;
|
|
625
|
+
const respText = contentToString(respMsg?.content ?? '');
|
|
626
|
+
let respToolCalls = respMsg?.tool_calls ?? [];
|
|
627
|
+
// Empty completion → fail over via the shared loop, exactly like the
|
|
628
|
+
// OpenAI route; finish_reason 'length' (whole output budget spent on
|
|
629
|
+
// hidden reasoning) skips the cooldown/penalty.
|
|
630
|
+
if (!respText && respToolCalls.length === 0) {
|
|
631
|
+
throw Object.assign(new Error(`empty completion from ${route.displayName}`), result.choices?.[0]?.finish_reason === 'length' ? { skipBench: true } : {});
|
|
632
|
+
}
|
|
633
|
+
// #809: bare "safe"/"unsafe" classification output from a relay is an
|
|
634
|
+
// upstream filter, not the requested model — fail over like an empty
|
|
635
|
+
// completion.
|
|
636
|
+
if (isUpstreamClassificationOutput(respText, route.platform) && respToolCalls.length === 0) {
|
|
637
|
+
throw Object.assign(new Error(`empty completion from ${route.displayName} (upstream classification output)`), result.choices?.[0]?.finish_reason === 'length' ? { skipBench: true } : {});
|
|
638
|
+
}
|
|
639
|
+
// Inline tool-call dialect rescue (#231): a tool-bearing request answered
|
|
640
|
+
// with the call serialized as TEXT → re-parse it into structured tool_use
|
|
641
|
+
// so Claude Code's agent loop keeps working; a detected-but-unparseable
|
|
642
|
+
// dialect is a dead turn and fails over like an empty completion. Same
|
|
643
|
+
// rescue /chat/completions and /v1/responses already apply.
|
|
644
|
+
if (wantsTools && respMsg && respToolCalls.length === 0 && respText) {
|
|
645
|
+
const rescue = rescueInlineToolCalls(respText, new Set((tools ?? []).map(t => t.function.name)));
|
|
646
|
+
if (rescue.detected) {
|
|
647
|
+
if (!rescue.calls) {
|
|
648
|
+
throw new Error(`unparseable inline tool-call dialect from ${route.displayName}: ${respText.slice(0, 120)}`);
|
|
649
|
+
}
|
|
650
|
+
const schemas = toolSchemaMap(tools);
|
|
651
|
+
respMsg.tool_calls = rescuedToToolCalls(rescue.calls, schemas);
|
|
652
|
+
respMsg.content = rescue.cleanText.length > 0 ? rescue.cleanText : null;
|
|
653
|
+
if (result.choices?.[0])
|
|
654
|
+
result.choices[0].finish_reason = 'tool_calls';
|
|
655
|
+
respToolCalls = respMsg.tool_calls;
|
|
656
|
+
console.log(`[Anthropic] Rescued ${rescue.calls.length} inline tool call(s) from ${route.displayName} into tool_use`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Repair double-encoded tool arguments against the request schemas, so
|
|
660
|
+
// strict Anthropic clients (Claude Code) get clean JSON inputs.
|
|
661
|
+
if (respToolCalls.length) {
|
|
662
|
+
const schemas = toolSchemaMap(tools);
|
|
663
|
+
for (const tc of respToolCalls) {
|
|
664
|
+
if (tc?.function?.arguments != null) {
|
|
665
|
+
tc.function.arguments = repairToolArguments(tc.function.arguments, schemas.get(tc.function.name));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Opt-in schema verdict on what the repair could not fix. This surface
|
|
669
|
+
// is where the silent failure was worst: parseToolInput turns
|
|
670
|
+
// unparseable arguments into `input: {}`, so the client sees a tool_use
|
|
671
|
+
// block with nothing in it and no indication anything went wrong.
|
|
672
|
+
if (isToolArgumentValidationEnabled()) {
|
|
673
|
+
const invalid = invalidToolCallReasons(respToolCalls, schemas);
|
|
674
|
+
if (invalid.length > 0)
|
|
675
|
+
throw invalidToolArgumentsError(route.displayName, invalid);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const promptTokens = result.usage?.prompt_tokens ?? estimatedInputTokens;
|
|
679
|
+
const completionTokens = result.usage?.completion_tokens ?? Math.ceil((respText.length + respToolCalls.reduce((n, c) => n + c.function.arguments.length, 0)) / 4);
|
|
680
|
+
recordUpstreamSuccess(route, result.usage?.total_tokens ?? promptTokens + completionTokens, state);
|
|
681
|
+
// Remember this model for the rest of the auto-routed session. A pin used
|
|
682
|
+
// to make this a no-op (the pin fixed the model); a group pin still has a
|
|
683
|
+
// provider choice to remember, recorded under the group's own scope.
|
|
684
|
+
if (!resolved.pinned || stickyScope)
|
|
685
|
+
setStickyModel(messages, route.modelDbId, sessionId, stickyScope ?? strategyKey);
|
|
686
|
+
const anthropicResponse = {
|
|
687
|
+
id: newMessageId(),
|
|
688
|
+
type: 'message',
|
|
689
|
+
role: 'assistant',
|
|
690
|
+
model: requestedModel,
|
|
691
|
+
content: toAnthropicContent(respMsg),
|
|
692
|
+
stop_reason: mapStopReason(result.choices?.[0]?.finish_reason ?? null, respToolCalls.length > 0),
|
|
693
|
+
stop_sequence: null,
|
|
694
|
+
usage: { input_tokens: promptTokens, output_tokens: completionTokens },
|
|
695
|
+
};
|
|
696
|
+
res.setHeader('X-Routed-Via', routedViaValue(route.platform, route.modelId));
|
|
697
|
+
setFallbackHeaders(res, attempt, attemptLog);
|
|
698
|
+
logRequest(route.platform, route.modelId, route.keyId, 'success', promptTokens, completionTokens, Date.now() - start, null, null, pinnedModelId, null, 'http');
|
|
699
|
+
res.json(anthropicResponse);
|
|
700
|
+
return 'done';
|
|
701
|
+
},
|
|
702
|
+
logFailure: (route, err) => {
|
|
703
|
+
logRequest(route.platform, route.modelId, route.keyId, 'error', estimatedInputTokens, 0, Date.now() - start, sanitizeProviderErrorMessage(err.message), null, pinnedModelId, null, 'http');
|
|
704
|
+
},
|
|
705
|
+
onFatal: (route, err, attempt) => {
|
|
706
|
+
setFallbackHeaders(res, attempt, attemptLog);
|
|
707
|
+
sendError(res, 502, 'api_error', `Provider error (${route.displayName}): ${sanitizeProviderErrorMessage(err.message)}`);
|
|
708
|
+
},
|
|
709
|
+
onRoutingExhausted: (lastError, routeErr, exhaustion, info) => {
|
|
710
|
+
setFallbackHeaders(res, info.attempts.length, info.attempts);
|
|
711
|
+
sendExhaustion(res, exhaustion);
|
|
712
|
+
},
|
|
713
|
+
onExhausted: (exhaustion, info) => {
|
|
714
|
+
setFallbackHeaders(res, info.attempts.length, info.attempts);
|
|
715
|
+
sendExhaustion(res, exhaustion);
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
// Thrown by streamCompletion once the SSE response is underway, so the outer
|
|
720
|
+
// loop knows the request is finished (honestly errored mid-stream) and must
|
|
721
|
+
// not fail over or send a second response.
|
|
722
|
+
class StreamAlreadyStarted extends Error {
|
|
723
|
+
}
|
|
724
|
+
// Consume the provider's OpenAI-style stream and re-emit it as the Anthropic
|
|
725
|
+
// SSE event sequence Claude Code expects:
|
|
726
|
+
// message_start → (content_block_start → content_block_delta* →
|
|
727
|
+
// content_block_stop)* → message_delta → message_stop
|
|
728
|
+
// Text is streamed incrementally as text_delta; tool calls are buffered across
|
|
729
|
+
// deltas and emitted as tool_use blocks (input_json_delta) once complete — the
|
|
730
|
+
// same buffering the OpenAI route does, since partial tool-call JSON isn't
|
|
731
|
+
// safely forwardable mid-flight.
|
|
732
|
+
async function streamCompletion(res, route, messages, options, ctx) {
|
|
733
|
+
let messageStarted = false; // doubles as "headers + message_start sent" = committed
|
|
734
|
+
let textBlockOpen = false;
|
|
735
|
+
let textBlockIndex = -1;
|
|
736
|
+
let nextIndex = 0;
|
|
737
|
+
let outputChars = 0;
|
|
738
|
+
let upstreamFinish = null;
|
|
739
|
+
const toolAcc = new Map();
|
|
740
|
+
// Inline-dialect hold window (#231): the first text is held until it either
|
|
741
|
+
// matches a tool-call dialect marker (held to the end and rescued into
|
|
742
|
+
// tool_use blocks) or provably cannot (flushed and streamed normally). This is
|
|
743
|
+
// also the commit point convergence — message_start is not sent until the
|
|
744
|
+
// first MEANINGFUL content, so a stream that opens with a dialect marker and
|
|
745
|
+
// then turns out unparseable fails over invisibly (previously this surface
|
|
746
|
+
// committed on the first non-empty text and had no dialect rescue at all).
|
|
747
|
+
let dialectMode = 'undecided';
|
|
748
|
+
let heldText = '';
|
|
749
|
+
const ensureMessageStart = () => {
|
|
750
|
+
if (messageStarted)
|
|
751
|
+
return;
|
|
752
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
753
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
754
|
+
res.setHeader('Connection', 'keep-alive');
|
|
755
|
+
res.setHeader('X-Routed-Via', routedViaValue(route.platform, route.modelId));
|
|
756
|
+
setFallbackHeaders(res, ctx.attempt, ctx.attemptLog);
|
|
757
|
+
// Committed: the answer is on its way, so the retry budget must no longer
|
|
758
|
+
// cancel this attempt (it could not fail over now anyway).
|
|
759
|
+
ctx.disarmHedge();
|
|
760
|
+
writeSse(res, 'message_start', {
|
|
761
|
+
type: 'message_start',
|
|
762
|
+
message: {
|
|
763
|
+
id: newMessageId(), type: 'message', role: 'assistant', model: ctx.requestedModel,
|
|
764
|
+
content: [], stop_reason: null, stop_sequence: null,
|
|
765
|
+
usage: { input_tokens: ctx.estimatedInputTokens, output_tokens: 0 },
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
messageStarted = true;
|
|
769
|
+
};
|
|
770
|
+
// Reasoning output (delta.reasoning_content from providers / the <think>
|
|
771
|
+
// extractor, delta.reasoning from Ollama-style upstreams) is buffered and
|
|
772
|
+
// emitted as one complete thinking block right before the first text/tool
|
|
773
|
+
// block. Buffering (not live thinking_delta streaming) keeps the commit
|
|
774
|
+
// point where it was: a stream that produces ONLY reasoning and dies still
|
|
775
|
+
// fails over invisibly, exactly like the OpenAI surface's preamble hold.
|
|
776
|
+
let reasoningBuf = '';
|
|
777
|
+
const flushThinking = () => {
|
|
778
|
+
if (reasoningBuf.length === 0)
|
|
779
|
+
return;
|
|
780
|
+
const idx = nextIndex++;
|
|
781
|
+
writeSse(res, 'content_block_start', { type: 'content_block_start', index: idx, content_block: { type: 'thinking', thinking: '' } });
|
|
782
|
+
writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: idx, delta: { type: 'thinking_delta', thinking: reasoningBuf } });
|
|
783
|
+
writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: idx });
|
|
784
|
+
outputChars += reasoningBuf.length;
|
|
785
|
+
reasoningBuf = '';
|
|
786
|
+
};
|
|
787
|
+
// Commit (if needed), open the text block (if needed), and stream `text`.
|
|
788
|
+
const emitText = (text) => {
|
|
789
|
+
ensureMessageStart();
|
|
790
|
+
if (!textBlockOpen) {
|
|
791
|
+
flushThinking(); // thinking block precedes the text block
|
|
792
|
+
textBlockIndex = nextIndex++;
|
|
793
|
+
writeSse(res, 'content_block_start', { type: 'content_block_start', index: textBlockIndex, content_block: { type: 'text', text: '' } });
|
|
794
|
+
textBlockOpen = true;
|
|
795
|
+
}
|
|
796
|
+
writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: textBlockIndex, delta: { type: 'text_delta', text } });
|
|
797
|
+
outputChars += text.length;
|
|
798
|
+
};
|
|
799
|
+
try {
|
|
800
|
+
const gen = route.provider.streamChatCompletion(route.apiKey, messages, route.modelId, options);
|
|
801
|
+
for await (const chunk of gen) {
|
|
802
|
+
if (ctx.clientGone())
|
|
803
|
+
break; // client hung up: stop pulling; reader.cancel() aborts upstream
|
|
804
|
+
const anyChunk = chunk;
|
|
805
|
+
// In-band provider error frame (e.g. Groq emits {"error":…} inside a 200
|
|
806
|
+
// stream). Before message_start: retryable → fail over invisibly. After:
|
|
807
|
+
// surface an Anthropic `error` event and stop.
|
|
808
|
+
if (anyChunk.error && !anyChunk.choices) {
|
|
809
|
+
const msg = anyChunk.error.message ?? JSON.stringify(anyChunk.error).slice(0, 200);
|
|
810
|
+
if (!messageStarted)
|
|
811
|
+
throw new Error(`in-band provider error from ${route.displayName}: ${msg}`);
|
|
812
|
+
writeSse(res, 'error', { type: 'error', error: { type: 'api_error', message: `Provider error (${route.displayName}): ${sanitizeProviderErrorMessage(String(msg))}` } });
|
|
813
|
+
res.end();
|
|
814
|
+
logRequest(route.platform, route.modelId, route.keyId, 'error', ctx.estimatedInputTokens, outputChars, Date.now() - ctx.start, `in-band error frame: ${sanitizeProviderErrorMessage(String(msg))}`, null, ctx.pinnedModelId, null, 'http');
|
|
815
|
+
throw new StreamAlreadyStarted();
|
|
816
|
+
}
|
|
817
|
+
const choice = anyChunk.choices?.[0];
|
|
818
|
+
if (!choice)
|
|
819
|
+
continue;
|
|
820
|
+
if (choice.finish_reason)
|
|
821
|
+
upstreamFinish = choice.finish_reason;
|
|
822
|
+
for (const tc of choice.delta?.tool_calls ?? []) {
|
|
823
|
+
const idx = tc.index ?? 0;
|
|
824
|
+
if (!toolAcc.has(idx))
|
|
825
|
+
toolAcc.set(idx, { id: undefined, name: '', args: '' });
|
|
826
|
+
const acc = toolAcc.get(idx);
|
|
827
|
+
if (tc.id && !acc.id)
|
|
828
|
+
acc.id = tc.id;
|
|
829
|
+
if (tc.function?.name)
|
|
830
|
+
acc.name += tc.function.name;
|
|
831
|
+
if (tc.function?.arguments)
|
|
832
|
+
acc.args += tc.function.arguments;
|
|
833
|
+
const thoughtSignature = tc.thought_signature ?? tc.thoughtSignature;
|
|
834
|
+
if (typeof thoughtSignature === 'string' && thoughtSignature.length > 0 && !acc.thought_signature) {
|
|
835
|
+
acc.thought_signature = thoughtSignature;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Reasoning deltas: providers emit reasoning_content (or Ollama-style
|
|
839
|
+
// `reasoning`); the shared <think> extractor emits reasoning_content.
|
|
840
|
+
const reasoningDelta = choice.delta?.reasoning_content ?? choice.delta?.reasoning;
|
|
841
|
+
if (typeof reasoningDelta === 'string' && reasoningDelta.length > 0)
|
|
842
|
+
reasoningBuf += reasoningDelta;
|
|
843
|
+
const text = typeof choice.delta?.content === 'string' ? choice.delta.content : '';
|
|
844
|
+
if (text.length === 0)
|
|
845
|
+
continue;
|
|
846
|
+
if (dialectMode === 'passthrough') {
|
|
847
|
+
emitText(text);
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
heldText += text;
|
|
851
|
+
if (dialectMode === 'undecided') {
|
|
852
|
+
const probe = heldText.trimStart();
|
|
853
|
+
if (startsWithDialectMarker(probe)) {
|
|
854
|
+
dialectMode = 'dialect';
|
|
855
|
+
}
|
|
856
|
+
else if (!couldBecomeDialectMarker(probe) || heldText.length > 256) {
|
|
857
|
+
dialectMode = 'passthrough';
|
|
858
|
+
emitText(heldText);
|
|
859
|
+
heldText = '';
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// else: still a strict prefix of a marker — keep holding.
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// Assemble buffered tool calls: synthesize missing ids, repair args against
|
|
866
|
+
// the request schemas, drop any that still aren't valid JSON.
|
|
867
|
+
const schemas = toolSchemaMap(ctx.tools);
|
|
868
|
+
let synthetic = 0;
|
|
869
|
+
const completedCalls = [...toolAcc.entries()]
|
|
870
|
+
.sort((a, b) => a[0] - b[0])
|
|
871
|
+
.map(([, acc]) => ({
|
|
872
|
+
id: acc.id && acc.id.length > 0 ? acc.id : `toolu_${crypto.randomBytes(8).toString('hex')}_${++synthetic}`,
|
|
873
|
+
name: acc.name,
|
|
874
|
+
arguments: repairToolArguments(acc.args || '{}', schemas.get(acc.name)),
|
|
875
|
+
...(acc.thought_signature ? { thought_signature: acc.thought_signature } : {}),
|
|
876
|
+
}))
|
|
877
|
+
.filter(c => { try {
|
|
878
|
+
JSON.parse(c.arguments);
|
|
879
|
+
return c.name.length > 0;
|
|
880
|
+
}
|
|
881
|
+
catch {
|
|
882
|
+
return false;
|
|
883
|
+
} });
|
|
884
|
+
// Resolve the dialect hold window now the full text is known. Held text was
|
|
885
|
+
// never emitted, so a dead dialect turn can still fail over (nothing has been
|
|
886
|
+
// committed). A rescued dialect becomes tool_use blocks; leftover clean text
|
|
887
|
+
// is emitted as a text block first.
|
|
888
|
+
if (heldText.length > 0) {
|
|
889
|
+
const rescue = ((ctx.tools?.length ?? 0) > 0 && (dialectMode === 'dialect' || containsDialectMarker(heldText)))
|
|
890
|
+
? rescueInlineToolCalls(heldText, new Set((ctx.tools ?? []).map(t => t.function.name)))
|
|
891
|
+
: { detected: false, calls: null, cleanText: heldText };
|
|
892
|
+
if (rescue.detected && !rescue.calls) {
|
|
893
|
+
throw new Error(`unparseable inline tool-call dialect from ${route.displayName}: ${heldText.slice(0, 120)}`);
|
|
894
|
+
}
|
|
895
|
+
if (rescue.detected && rescue.calls) {
|
|
896
|
+
if (rescue.cleanText.length > 0)
|
|
897
|
+
emitText(rescue.cleanText);
|
|
898
|
+
for (const c of rescue.calls) {
|
|
899
|
+
const repaired = repairToolArguments(c.arguments, schemas.get(c.name));
|
|
900
|
+
if (c.name.length === 0)
|
|
901
|
+
continue;
|
|
902
|
+
try {
|
|
903
|
+
JSON.parse(repaired);
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
completedCalls.push({ id: `toolu_${crypto.randomBytes(8).toString('hex')}_rescued_${++synthetic}`, name: c.name, arguments: repaired });
|
|
909
|
+
}
|
|
910
|
+
console.log(`[Anthropic] Rescued ${rescue.calls.length} inline tool call(s) from ${route.displayName} into tool_use`);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
emitText(heldText);
|
|
914
|
+
}
|
|
915
|
+
heldText = '';
|
|
916
|
+
}
|
|
917
|
+
// Opt-in schema verdict, same rule as the non-streaming surface above and
|
|
918
|
+
// as /chat/completions: this is the surface the silent failure hurt most,
|
|
919
|
+
// and Claude Code streams. Placed after the dialect rescue so a rescued
|
|
920
|
+
// call is judged too, and gated on `!messageStarted` — once message_start
|
|
921
|
+
// has gone out there is no failing over, and tearing the SSE stream down
|
|
922
|
+
// would be worse for the client than a tool_use the schema dislikes.
|
|
923
|
+
if (isToolArgumentValidationEnabled() && !messageStarted && completedCalls.length > 0) {
|
|
924
|
+
const invalid = invalidToolCallReasons(completedCalls.map(c => ({ function: { name: c.name, arguments: c.arguments } })), schemas);
|
|
925
|
+
if (invalid.length > 0)
|
|
926
|
+
throw invalidToolArgumentsError(route.displayName, invalid);
|
|
927
|
+
}
|
|
928
|
+
// Nothing usable came out — fail over (message_start was never sent, so the
|
|
929
|
+
// client never saw this attempt).
|
|
930
|
+
if (!messageStarted && completedCalls.length === 0) {
|
|
931
|
+
// Disconnect before the commit point: the pump loop broke with nothing
|
|
932
|
+
// accumulated because the CLIENT left, not because the provider failed —
|
|
933
|
+
// benching a healthy model+key for it was wrong. StreamAlreadyStarted
|
|
934
|
+
// maps to 'committed' in the dispatcher: stop, no failover, no
|
|
935
|
+
// exhaustion render (the socket is gone anyway).
|
|
936
|
+
if (ctx.clientGone()) {
|
|
937
|
+
console.log(`[Anthropic] client disconnected before first token from ${route.displayName} — dropping attempt without benching`);
|
|
938
|
+
throw new StreamAlreadyStarted();
|
|
939
|
+
}
|
|
940
|
+
// finish_reason 'length' = the model spent the whole output budget on
|
|
941
|
+
// hidden reasoning before any visible text: fail over, but skip the
|
|
942
|
+
// cooldown/penalty (not a provider-health signal).
|
|
943
|
+
throw Object.assign(new Error(`empty completion from ${route.displayName} (stream produced no content and no tool calls)`), upstreamFinish === 'length' ? { skipBench: true } : {});
|
|
944
|
+
}
|
|
945
|
+
ensureMessageStart();
|
|
946
|
+
if (textBlockOpen)
|
|
947
|
+
writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: textBlockIndex });
|
|
948
|
+
// Residual reasoning: tool-only turns (no text block ever opened) and
|
|
949
|
+
// reasoning that arrived after the text block was already streaming.
|
|
950
|
+
flushThinking();
|
|
951
|
+
for (const call of completedCalls) {
|
|
952
|
+
const idx = nextIndex++;
|
|
953
|
+
writeSse(res, 'content_block_start', {
|
|
954
|
+
type: 'content_block_start',
|
|
955
|
+
index: idx,
|
|
956
|
+
content_block: {
|
|
957
|
+
type: 'tool_use',
|
|
958
|
+
id: call.id,
|
|
959
|
+
name: call.name,
|
|
960
|
+
input: {},
|
|
961
|
+
...(call.thought_signature ? { thought_signature: call.thought_signature } : {}),
|
|
962
|
+
},
|
|
963
|
+
});
|
|
964
|
+
writeSse(res, 'content_block_delta', { type: 'content_block_delta', index: idx, delta: { type: 'input_json_delta', partial_json: call.arguments } });
|
|
965
|
+
writeSse(res, 'content_block_stop', { type: 'content_block_stop', index: idx });
|
|
966
|
+
outputChars += call.arguments.length;
|
|
967
|
+
}
|
|
968
|
+
const stopReason = mapStopReason(upstreamFinish, completedCalls.length > 0);
|
|
969
|
+
const outputTokens = Math.ceil(outputChars / 4);
|
|
970
|
+
writeSse(res, 'message_delta', { type: 'message_delta', delta: { stop_reason: stopReason, stop_sequence: null }, usage: { output_tokens: outputTokens } });
|
|
971
|
+
writeSse(res, 'message_stop', { type: 'message_stop' });
|
|
972
|
+
res.end();
|
|
973
|
+
recordUpstreamSuccess(route, ctx.estimatedInputTokens + outputTokens, ctx.state);
|
|
974
|
+
if (!ctx.pinned || ctx.stickyScope)
|
|
975
|
+
setStickyModel(messages, route.modelDbId, ctx.sessionId, ctx.stickyScope ?? ctx.strategyKey);
|
|
976
|
+
logRequest(route.platform, route.modelId, route.keyId, 'success', ctx.estimatedInputTokens, outputTokens, Date.now() - ctx.start, null, null, ctx.pinnedModelId, null, 'http');
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
if (err instanceof StreamAlreadyStarted)
|
|
980
|
+
throw err;
|
|
981
|
+
// Client abort mid-stream: the pump's own `if (ctx.clientGone()) break`
|
|
982
|
+
// can lose the race against the fetch-signal rejection, so the abort may
|
|
983
|
+
// surface here instead. Rethrow — the shared loop's client-abort branch
|
|
984
|
+
// stops the ladder without benching or an error log row (the socket is
|
|
985
|
+
// gone; nothing to render).
|
|
986
|
+
if (isClientAbortError(err))
|
|
987
|
+
throw err;
|
|
988
|
+
if (messageStarted) {
|
|
989
|
+
// Real payload already reached the client — finish the SSE response
|
|
990
|
+
// honestly instead of leaving Claude Code hanging, and stop the retry loop.
|
|
991
|
+
writeSse(res, 'error', { type: 'error', error: { type: 'api_error', message: `Provider error (${route.displayName}): stream interrupted` } });
|
|
992
|
+
try {
|
|
993
|
+
res.end();
|
|
994
|
+
}
|
|
995
|
+
catch { /* socket gone */ }
|
|
996
|
+
logRequest(route.platform, route.modelId, route.keyId, 'error', ctx.estimatedInputTokens, outputChars, Date.now() - ctx.start, sanitizeProviderErrorMessage(err.message), null, ctx.pinnedModelId, null, 'http');
|
|
997
|
+
throw new StreamAlreadyStarted();
|
|
998
|
+
}
|
|
999
|
+
// Headers never sent — bubble to the outer loop for failover.
|
|
1000
|
+
throw err;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
// Anthropic token-counting endpoint. Claude Code calls this to size context
|
|
1004
|
+
// windows; we return a heuristic estimate (the proxy doesn't run a tokenizer).
|
|
1005
|
+
anthropicRouter.post('/messages/count_tokens', (req, res) => {
|
|
1006
|
+
if (!authenticate(req, res))
|
|
1007
|
+
return;
|
|
1008
|
+
const parsed = messagesSchema.safeParse(req.body);
|
|
1009
|
+
if (!parsed.success) {
|
|
1010
|
+
sendError(res, 400, 'invalid_request_error', 'Invalid request');
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
const converted = convertRequest(parsed.data);
|
|
1014
|
+
// Same verdict as POST /messages, so a client sizing a context window learns
|
|
1015
|
+
// the document is unusable here rather than getting a count for a prompt we
|
|
1016
|
+
// would go on to refuse.
|
|
1017
|
+
if (converted.documentRejections.length > 0) {
|
|
1018
|
+
sendError(res, 400, 'invalid_request_error', documentRejectionMessage(converted.documentRejections));
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const { messages, tools } = converted;
|
|
1022
|
+
const compressionResult = compressRequest(messages, {
|
|
1023
|
+
header: req.headers['x-freellm-compress'],
|
|
1024
|
+
tools,
|
|
1025
|
+
recordStats: false,
|
|
1026
|
+
});
|
|
1027
|
+
res.setHeader('X-FreeLLM-Compress', formatCompressionHeader(compressionResult));
|
|
1028
|
+
res.json({ input_tokens: estimateInputTokens(compressionResult.messages, tools) });
|
|
1029
|
+
});
|
|
1030
|
+
// Anthropic-compatible GET /v1/models. Content-negotiated: only answers when
|
|
1031
|
+
// the caller speaks Anthropic (sends an `anthropic-version` header, as Claude
|
|
1032
|
+
// Code does) — otherwise it calls next() and the OpenAI-shaped handler in
|
|
1033
|
+
// proxyRouter serves the same path. Lists the SAME catalog as the OpenAI
|
|
1034
|
+
// endpoint (real free models that can serve a request right now, plus "auto").
|
|
1035
|
+
// Nothing here is hosted Claude, and no entry claims to be.
|
|
1036
|
+
//
|
|
1037
|
+
// One entry per Claude family (claude-sonnet-4-5 and friends) rides along so
|
|
1038
|
+
// clients that only accept Claude-shaped ids can discover anything at all; see
|
|
1039
|
+
// CLAUDE_FAMILY_ALIASES for why, and note /messages already routed those ids
|
|
1040
|
+
// long before they were listed here.
|
|
1041
|
+
//
|
|
1042
|
+
// Optional `claude/<real-id>` aliases let Claude Code's gateway picker discover
|
|
1043
|
+
// the full catalog; /messages strips the synthetic prefix before routing.
|
|
1044
|
+
anthropicRouter.get('/models', (req, res, next) => {
|
|
1045
|
+
if (!req.headers['anthropic-version'])
|
|
1046
|
+
return next(); // OpenAI client → proxyRouter
|
|
1047
|
+
if (!authenticate(req, res))
|
|
1048
|
+
return;
|
|
1049
|
+
const { models } = buildModelListing();
|
|
1050
|
+
const available = models.filter(m => m.available === 1);
|
|
1051
|
+
const aliasesEnabled = getSetting('expose_cc_discovery_aliases') === '1';
|
|
1052
|
+
const data = [
|
|
1053
|
+
{ type: 'model', id: 'auto', display_name: 'Auto (router picks the best available model)', created_at: MODEL_CREATED_AT },
|
|
1054
|
+
// Only when something can actually serve them: advertising a Sonnet slot
|
|
1055
|
+
// backed by an empty pool would trade "0 models" for a model that 503s.
|
|
1056
|
+
...(available.length > 0
|
|
1057
|
+
? claudeFamilyDiscoveryEntries().map(a => ({
|
|
1058
|
+
type: 'model',
|
|
1059
|
+
id: a.id,
|
|
1060
|
+
display_name: a.displayName,
|
|
1061
|
+
created_at: MODEL_CREATED_AT,
|
|
1062
|
+
}))
|
|
1063
|
+
: []),
|
|
1064
|
+
...available.map(m => ({ type: 'model', id: m.id, display_name: m.name, created_at: MODEL_CREATED_AT })),
|
|
1065
|
+
...(aliasesEnabled
|
|
1066
|
+
? available.map(m => ({
|
|
1067
|
+
type: 'model',
|
|
1068
|
+
id: `claude/${m.id}`,
|
|
1069
|
+
display_name: `${m.name} (Claude Code)`,
|
|
1070
|
+
created_at: MODEL_CREATED_AT,
|
|
1071
|
+
}))
|
|
1072
|
+
: []),
|
|
1073
|
+
];
|
|
1074
|
+
res.json({ data, has_more: false, first_id: data[0]?.id ?? null, last_id: data[data.length - 1]?.id ?? null });
|
|
1075
|
+
});
|
|
1076
|
+
//# sourceMappingURL=anthropic.js.map
|