@mupt-ai/dari-gateway 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bun.d.ts +2 -0
- package/dist/bun.js +4 -0
- package/dist/context/eval_catalog.d.ts +3 -0
- package/dist/context/eval_catalog.js +7 -0
- package/dist/context/interaction_fingerprint.d.ts +27 -0
- package/dist/context/interaction_fingerprint.js +519 -0
- package/dist/context/interaction_fingerprint_format.d.ts +42 -0
- package/dist/context/interaction_fingerprint_format.js +113 -0
- package/dist/context/interaction_lease_depth.d.ts +2 -0
- package/dist/context/interaction_lease_depth.js +110 -0
- package/dist/context/lease_history.d.ts +7 -0
- package/dist/context/lease_history.js +158 -0
- package/dist/context/pricing.d.ts +4 -0
- package/dist/context/pricing.js +23 -0
- package/dist/context/provider_prompt_accounting.d.ts +14 -0
- package/dist/context/provider_prompt_accounting.js +187 -0
- package/dist/context/routing_context.d.ts +40 -0
- package/dist/context/routing_context.js +211 -0
- package/dist/http/auth.d.ts +2 -0
- package/dist/http/auth.js +35 -0
- package/dist/http/errors.d.ts +19 -0
- package/dist/http/errors.js +89 -0
- package/dist/http/handler.d.ts +12 -0
- package/dist/http/handler.js +81 -0
- package/dist/http/http2_server.d.ts +6 -0
- package/dist/http/http2_server.js +103 -0
- package/dist/http/serve.d.ts +20 -0
- package/dist/http/serve.js +78 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/ingress/anthropic_messages.d.ts +3 -0
- package/dist/ingress/anthropic_messages.js +122 -0
- package/dist/ingress/anthropic_models.d.ts +1 -0
- package/dist/ingress/anthropic_models.js +23 -0
- package/dist/ingress/chat_completions.d.ts +2 -0
- package/dist/ingress/chat_completions.js +37 -0
- package/dist/ingress/claude_code/request_context.d.ts +2 -0
- package/dist/ingress/claude_code/request_context.js +31 -0
- package/dist/ingress/codex.d.ts +3 -0
- package/dist/ingress/codex.js +65 -0
- package/dist/ingress/codex_models.d.ts +2 -0
- package/dist/ingress/codex_models.js +89 -0
- package/dist/ingress/json.d.ts +1 -0
- package/dist/ingress/json.js +95 -0
- package/dist/ingress/models.d.ts +1 -0
- package/dist/ingress/models.js +53 -0
- package/dist/interaction/capabilities.d.ts +3 -0
- package/dist/interaction/capabilities.js +24 -0
- package/dist/interaction/image.d.ts +4 -0
- package/dist/interaction/image.js +13 -0
- package/dist/interaction/portable_reasoning_state.d.ts +4 -0
- package/dist/interaction/portable_reasoning_state.js +18 -0
- package/dist/interaction/provider_continuation_state.d.ts +6 -0
- package/dist/interaction/provider_continuation_state.js +27 -0
- package/dist/interaction/tool_identity.d.ts +10 -0
- package/dist/interaction/tool_identity.js +41 -0
- package/dist/interaction/types.d.ts +203 -0
- package/dist/interaction/types.js +5 -0
- package/dist/models/context_limits.d.ts +2 -0
- package/dist/models/context_limits.js +7 -0
- package/dist/models/execution_provider.d.ts +8 -0
- package/dist/models/execution_provider.js +13 -0
- package/dist/models/gateways.d.ts +11 -0
- package/dist/models/gateways.js +45 -0
- package/dist/models/ids.d.ts +1 -0
- package/dist/models/ids.js +15 -0
- package/dist/models/provider_defaults.d.ts +4 -0
- package/dist/models/provider_defaults.js +41 -0
- package/dist/models/runtime_overrides.d.ts +4 -0
- package/dist/models/runtime_overrides.js +209 -0
- package/dist/observability/errors.d.ts +8 -0
- package/dist/observability/errors.js +11 -0
- package/dist/platform/platform.d.ts +152 -0
- package/dist/platform/platform.js +11 -0
- package/dist/platform/routing_contract.d.ts +2 -0
- package/dist/platform/routing_contract.js +5 -0
- package/dist/protocol/input/anthropic_messages.d.ts +2 -0
- package/dist/protocol/input/anthropic_messages.js +494 -0
- package/dist/protocol/input/chat_completions.d.ts +4 -0
- package/dist/protocol/input/chat_completions.js +212 -0
- package/dist/protocol/input/chat_completions_validation.d.ts +2 -0
- package/dist/protocol/input/chat_completions_validation.js +257 -0
- package/dist/protocol/input/openai_responses.d.ts +2 -0
- package/dist/protocol/input/openai_responses.js +666 -0
- package/dist/protocol/input/router_model.d.ts +7 -0
- package/dist/protocol/input/router_model.js +43 -0
- package/dist/protocol/output/anthropic_messages.d.ts +10 -0
- package/dist/protocol/output/anthropic_messages.js +15 -0
- package/dist/protocol/output/anthropic_messages_output.d.ts +10 -0
- package/dist/protocol/output/anthropic_messages_output.js +124 -0
- package/dist/protocol/output/anthropic_messages_stream.d.ts +16 -0
- package/dist/protocol/output/anthropic_messages_stream.js +267 -0
- package/dist/protocol/output/chat_completions.d.ts +29 -0
- package/dist/protocol/output/chat_completions.js +406 -0
- package/dist/protocol/output/chat_reasoning_detail.d.ts +2 -0
- package/dist/protocol/output/chat_reasoning_detail.js +13 -0
- package/dist/protocol/output/delivered_stream_snapshot.d.ts +10 -0
- package/dist/protocol/output/delivered_stream_snapshot.js +31 -0
- package/dist/protocol/output/openai_responses.d.ts +10 -0
- package/dist/protocol/output/openai_responses.js +14 -0
- package/dist/protocol/output/openai_responses_output.d.ts +14 -0
- package/dist/protocol/output/openai_responses_output.js +125 -0
- package/dist/protocol/output/openai_responses_stream.d.ts +18 -0
- package/dist/protocol/output/openai_responses_stream.js +546 -0
- package/dist/protocol/output/sse_stream.d.ts +31 -0
- package/dist/protocol/output/sse_stream.js +67 -0
- package/dist/providers/anthropic_safeguards.d.ts +11 -0
- package/dist/providers/anthropic_safeguards.js +81 -0
- package/dist/providers/aws_assume_role.d.ts +17 -0
- package/dist/providers/aws_assume_role.js +107 -0
- package/dist/providers/cancel_estimate.d.ts +43 -0
- package/dist/providers/cancel_estimate.js +89 -0
- package/dist/providers/execution.d.ts +72 -0
- package/dist/providers/execution.js +491 -0
- package/dist/providers/model_capabilities.d.ts +11 -0
- package/dist/providers/model_capabilities.js +29 -0
- package/dist/providers/pi.d.ts +65 -0
- package/dist/providers/pi.js +319 -0
- package/dist/providers/pi_context.d.ts +7 -0
- package/dist/providers/pi_context.js +405 -0
- package/dist/providers/pi_options.d.ts +11 -0
- package/dist/providers/pi_options.js +398 -0
- package/dist/providers/pi_output.d.ts +2 -0
- package/dist/providers/pi_output.js +453 -0
- package/dist/providers/provider_attempt_runner.d.ts +55 -0
- package/dist/providers/provider_attempt_runner.js +90 -0
- package/dist/providers/provider_call.d.ts +77 -0
- package/dist/providers/provider_call.js +292 -0
- package/dist/providers/provider_reported_cost.d.ts +10 -0
- package/dist/providers/provider_reported_cost.js +123 -0
- package/dist/providers/reasoning.d.ts +12 -0
- package/dist/providers/reasoning.js +30 -0
- package/dist/providers/response_capture.d.ts +23 -0
- package/dist/providers/response_capture.js +47 -0
- package/dist/providers/stream_finalization.d.ts +46 -0
- package/dist/providers/stream_finalization.js +165 -0
- package/dist/providers/subscription_limits.d.ts +31 -0
- package/dist/providers/subscription_limits.js +163 -0
- package/dist/providers/subscription_usage.d.ts +10 -0
- package/dist/providers/subscription_usage.js +79 -0
- package/dist/runtime/claude_code/auxiliary_routing.d.ts +3 -0
- package/dist/runtime/claude_code/auxiliary_routing.js +60 -0
- package/dist/runtime/customer_disconnect.d.ts +20 -0
- package/dist/runtime/customer_disconnect.js +49 -0
- package/dist/runtime/explicit_model.d.ts +4 -0
- package/dist/runtime/explicit_model.js +53 -0
- package/dist/runtime/harness_identity/conversation.d.ts +33 -0
- package/dist/runtime/harness_identity/conversation.js +39 -0
- package/dist/runtime/harness_identity/extract.d.ts +8 -0
- package/dist/runtime/harness_identity/extract.js +214 -0
- package/dist/runtime/harness_identity/index.d.ts +2 -0
- package/dist/runtime/harness_identity/index.js +2 -0
- package/dist/runtime/prepare_routed_completion.d.ts +33 -0
- package/dist/runtime/prepare_routed_completion.js +167 -0
- package/dist/runtime/provider_stream_lifecycle.d.ts +31 -0
- package/dist/runtime/provider_stream_lifecycle.js +88 -0
- package/dist/runtime/provider_stream_outcome.d.ts +23 -0
- package/dist/runtime/provider_stream_outcome.js +55 -0
- package/dist/runtime/request_tags.d.ts +7 -0
- package/dist/runtime/request_tags.js +33 -0
- package/dist/runtime/routed_completion.d.ts +17 -0
- package/dist/runtime/routed_completion.js +80 -0
- package/dist/runtime/routed_protocol.d.ts +30 -0
- package/dist/runtime/routed_protocol.js +1 -0
- package/dist/runtime/routed_runtime.d.ts +40 -0
- package/dist/runtime/routed_runtime.js +535 -0
- package/dist/runtime/routing_lease.d.ts +26 -0
- package/dist/runtime/routing_lease.js +210 -0
- package/dist/runtime/runtime_reporter.d.ts +71 -0
- package/dist/runtime/runtime_reporter.js +35 -0
- package/dist/self_host/config.d.ts +46 -0
- package/dist/self_host/config.js +105 -0
- package/dist/self_host/main.d.ts +16 -0
- package/dist/self_host/main.js +95 -0
- package/dist/self_host/platform.d.ts +13 -0
- package/dist/self_host/platform.js +50 -0
- package/dist/self_host/prefix_store.d.ts +46 -0
- package/dist/self_host/prefix_store.js +261 -0
- package/dist/self_host/resolve.d.ts +28 -0
- package/dist/self_host/resolve.js +171 -0
- package/dist/self_host/subscriptions/access.d.ts +10 -0
- package/dist/self_host/subscriptions/access.js +37 -0
- package/dist/self_host/subscriptions/http.d.ts +15 -0
- package/dist/self_host/subscriptions/http.js +187 -0
- package/dist/self_host/subscriptions/index.d.ts +24 -0
- package/dist/self_host/subscriptions/index.js +24 -0
- package/dist/self_host/subscriptions/oauth.d.ts +55 -0
- package/dist/self_host/subscriptions/oauth.js +281 -0
- package/dist/self_host/subscriptions/store.d.ts +78 -0
- package/dist/self_host/subscriptions/store.js +235 -0
- package/dist/strategies/auto_routing_selector.d.ts +12 -0
- package/dist/strategies/auto_routing_selector.js +61 -0
- package/dist/strategies/eval_relevance.d.ts +17 -0
- package/dist/strategies/eval_relevance.js +32 -0
- package/dist/strategies/fallback_decision.d.ts +8 -0
- package/dist/strategies/fallback_decision.js +17 -0
- package/dist/strategies/priority_selector.d.ts +7 -0
- package/dist/strategies/priority_selector.js +37 -0
- package/dist/strategies/route_decision.d.ts +22 -0
- package/dist/strategies/route_decision.js +65 -0
- package/dist/strategies/routing.d.ts +74 -0
- package/dist/strategies/routing.js +280 -0
- package/dist/strategies/selector_projection.d.ts +15 -0
- package/dist/strategies/selector_projection.js +161 -0
- package/dist/telemetry/background.d.ts +7 -0
- package/dist/telemetry/background.js +33 -0
- package/dist/telemetry/benchmark_replay.d.ts +37 -0
- package/dist/telemetry/benchmark_replay.js +108 -0
- package/dist/telemetry/billing_finalization.d.ts +64 -0
- package/dist/telemetry/billing_finalization.js +96 -0
- package/dist/telemetry/ingest_values.d.ts +4 -0
- package/dist/telemetry/ingest_values.js +12 -0
- package/dist/telemetry/model_cost.d.ts +3 -0
- package/dist/telemetry/model_cost.js +15 -0
- package/dist/telemetry/prefix_state.d.ts +47 -0
- package/dist/telemetry/prefix_state.js +116 -0
- package/dist/telemetry/report.d.ts +88 -0
- package/dist/telemetry/report.js +621 -0
- package/dist/telemetry/requests/interaction_observability.d.ts +45 -0
- package/dist/telemetry/requests/interaction_observability.js +180 -0
- package/dist/telemetry/requests/retained_interaction_request.d.ts +8 -0
- package/dist/telemetry/requests/retained_interaction_request.js +10 -0
- package/dist/telemetry/scrub.d.ts +2 -0
- package/dist/telemetry/scrub.js +31 -0
- package/dist/telemetry/skills/claude/catalog.d.ts +6 -0
- package/dist/telemetry/skills/claude/catalog.js +99 -0
- package/dist/telemetry/skills/claude/file_reads.d.ts +4 -0
- package/dist/telemetry/skills/claude/file_reads.js +110 -0
- package/dist/telemetry/skills/claude/observations.d.ts +3 -0
- package/dist/telemetry/skills/claude/observations.js +366 -0
- package/dist/telemetry/skills/codex/catalog.d.ts +11 -0
- package/dist/telemetry/skills/codex/catalog.js +138 -0
- package/dist/telemetry/skills/codex/exec_file_reads.d.ts +9 -0
- package/dist/telemetry/skills/codex/exec_file_reads.js +36 -0
- package/dist/telemetry/skills/codex/explicit.d.ts +3 -0
- package/dist/telemetry/skills/codex/explicit.js +90 -0
- package/dist/telemetry/skills/codex/file_reads.d.ts +4 -0
- package/dist/telemetry/skills/codex/file_reads.js +37 -0
- package/dist/telemetry/skills/codex/identity.d.ts +2 -0
- package/dist/telemetry/skills/codex/identity.js +20 -0
- package/dist/telemetry/skills/observations.d.ts +21 -0
- package/dist/telemetry/skills/observations.js +41 -0
- package/dist/telemetry/skills/paths.d.ts +11 -0
- package/dist/telemetry/skills/paths.js +61 -0
- package/dist/telemetry/skills/pi.d.ts +6 -0
- package/dist/telemetry/skills/pi.js +223 -0
- package/dist/telemetry/skills/shell_file_reads.d.ts +7 -0
- package/dist/telemetry/skills/shell_file_reads.js +226 -0
- package/dist/telemetry/skills/shell_words.d.ts +13 -0
- package/dist/telemetry/skills/shell_words.js +189 -0
- package/dist/telemetry/tools/input.d.ts +2 -0
- package/dist/telemetry/tools/input.js +14 -0
- package/dist/telemetry/tools/observations.d.ts +29 -0
- package/dist/telemetry/tools/observations.js +247 -0
- package/dist/telemetry/usage.d.ts +57 -0
- package/dist/telemetry/usage.js +136 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +1 -0
- package/package.json +21 -6
- package/src/bun.ts +2 -2
- package/src/context/eval_catalog.ts +1 -1
- package/src/context/interaction_fingerprint.ts +9 -9
- package/src/context/interaction_fingerprint_format.ts +1 -1
- package/src/context/interaction_lease_depth.ts +1 -1
- package/src/context/pricing.ts +1 -1
- package/src/context/provider_prompt_accounting.ts +5 -5
- package/src/context/routing_context.ts +16 -16
- package/src/http/auth.ts +1 -1
- package/src/http/handler.ts +8 -8
- package/src/http/serve.ts +3 -3
- package/src/index.ts +6 -6
- package/src/ingress/anthropic_messages.ts +12 -12
- package/src/ingress/anthropic_models.ts +1 -1
- package/src/ingress/chat_completions.ts +9 -9
- package/src/ingress/claude_code/request_context.ts +2 -2
- package/src/ingress/codex.ts +10 -10
- package/src/ingress/codex_models.ts +4 -4
- package/src/ingress/json.ts +1 -1
- package/src/ingress/models.ts +9 -9
- package/src/interaction/capabilities.ts +1 -1
- package/src/interaction/portable_reasoning_state.ts +1 -1
- package/src/interaction/provider_continuation_state.ts +1 -1
- package/src/interaction/tool_identity.ts +2 -2
- package/src/models/context_limits.ts +1 -1
- package/src/models/execution_provider.ts +1 -1
- package/src/models/ids.ts +1 -1
- package/src/models/provider_defaults.ts +1 -1
- package/src/models/runtime_overrides.ts +1 -1
- package/src/observability/errors.ts +2 -2
- package/src/platform/platform.ts +4 -4
- package/src/protocol/input/anthropic_messages.ts +7 -7
- package/src/protocol/input/chat_completions.ts +5 -5
- package/src/protocol/input/chat_completions_validation.ts +4 -4
- package/src/protocol/input/openai_responses.ts +6 -6
- package/src/protocol/output/anthropic_messages.ts +3 -3
- package/src/protocol/output/anthropic_messages_output.ts +6 -4
- package/src/protocol/output/anthropic_messages_stream.ts +7 -7
- package/src/protocol/output/chat_completions.ts +13 -13
- package/src/protocol/output/chat_reasoning_detail.ts +1 -1
- package/src/protocol/output/openai_responses.ts +3 -3
- package/src/protocol/output/openai_responses_output.ts +4 -2
- package/src/protocol/output/openai_responses_stream.ts +6 -6
- package/src/protocol/output/sse_stream.ts +3 -3
- package/src/providers/anthropic_safeguards.ts +2 -2
- package/src/providers/aws_assume_role.ts +2 -2
- package/src/providers/cancel_estimate.ts +1 -1
- package/src/providers/execution.ts +17 -17
- package/src/providers/pi.ts +14 -14
- package/src/providers/pi_context.ts +6 -6
- package/src/providers/pi_options.ts +10 -10
- package/src/providers/pi_output.ts +5 -5
- package/src/providers/provider_attempt_runner.ts +1 -1
- package/src/providers/provider_call.ts +14 -14
- package/src/providers/provider_reported_cost.ts +3 -3
- package/src/providers/reasoning.ts +2 -2
- package/src/providers/stream_finalization.ts +9 -9
- package/src/providers/subscription_limits.ts +3 -3
- package/src/runtime/claude_code/auxiliary_routing.ts +5 -5
- package/src/runtime/customer_disconnect.ts +1 -1
- package/src/runtime/explicit_model.ts +4 -4
- package/src/runtime/harness_identity/conversation.ts +1 -1
- package/src/runtime/harness_identity/extract.ts +3 -3
- package/src/runtime/harness_identity/index.ts +2 -2
- package/src/runtime/prepare_routed_completion.ts +13 -13
- package/src/runtime/provider_stream_lifecycle.ts +1 -1
- package/src/runtime/provider_stream_outcome.ts +2 -2
- package/src/runtime/request_tags.ts +2 -2
- package/src/runtime/routed_completion.ts +11 -11
- package/src/runtime/routed_protocol.ts +2 -2
- package/src/runtime/routed_runtime.ts +20 -19
- package/src/runtime/routing_lease.ts +7 -7
- package/src/runtime/runtime_reporter.ts +3 -3
- package/src/self_host/main.ts +12 -12
- package/src/self_host/platform.ts +5 -5
- package/src/self_host/prefix_store.ts +2 -2
- package/src/self_host/resolve.ts +6 -6
- package/src/self_host/subscriptions/access.ts +2 -2
- package/src/self_host/subscriptions/http.ts +2 -2
- package/src/self_host/subscriptions/index.ts +4 -4
- package/src/self_host/subscriptions/store.ts +1 -1
- package/src/strategies/auto_routing_selector.ts +2 -2
- package/src/strategies/eval_relevance.ts +3 -3
- package/src/strategies/priority_selector.ts +1 -1
- package/src/strategies/route_decision.ts +7 -7
- package/src/strategies/routing.ts +10 -10
- package/src/strategies/selector_projection.ts +2 -2
- package/src/telemetry/billing_finalization.ts +3 -3
- package/src/telemetry/model_cost.ts +1 -1
- package/src/telemetry/prefix_state.ts +4 -4
- package/src/telemetry/report.ts +21 -21
- package/src/telemetry/requests/interaction_observability.ts +1 -1
- package/src/telemetry/requests/retained_interaction_request.ts +1 -1
- package/src/telemetry/skills/claude/catalog.ts +1 -1
- package/src/telemetry/skills/claude/file_reads.ts +6 -6
- package/src/telemetry/skills/claude/observations.ts +3 -3
- package/src/telemetry/skills/codex/catalog.ts +3 -3
- package/src/telemetry/skills/codex/exec_file_reads.ts +4 -4
- package/src/telemetry/skills/codex/explicit.ts +3 -3
- package/src/telemetry/skills/codex/file_reads.ts +5 -5
- package/src/telemetry/skills/observations.ts +7 -7
- package/src/telemetry/skills/pi.ts +3 -3
- package/src/telemetry/skills/shell_file_reads.ts +2 -2
- package/src/telemetry/skills/shell_words.ts +1 -1
- package/src/telemetry/tools/input.ts +1 -1
- package/src/telemetry/tools/observations.ts +9 -9
- package/src/telemetry/usage.ts +5 -5
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// Reporting of served routing requests to the platform: the request record
|
|
2
|
+
// and the prefix-state write the next turn resolves. Most callers
|
|
3
|
+
// fire-and-forget; streams hold their terminal marker on the prefix-state
|
|
4
|
+
// write so the immediate next turn can see next_model. Reporting failures are
|
|
5
|
+
// always logged and swallowed.
|
|
6
|
+
import { selectPrefixHit, } from "@mupt-ai/dari-router/policy-engine";
|
|
7
|
+
import { errorCode } from "../http/errors.js";
|
|
8
|
+
import { executionProviderForModel } from "../models/execution_provider.js";
|
|
9
|
+
import { isBackgroundRequest, } from "../interaction/types.js";
|
|
10
|
+
import { captureOperationalError } from "../observability/errors.js";
|
|
11
|
+
import { gatewayPlatform } from "../platform/platform.js";
|
|
12
|
+
import { selectorTraceFromError, } from "../strategies/routing.js";
|
|
13
|
+
import { trackBackgroundTelemetry } from "./background.js";
|
|
14
|
+
import { modelCost } from "./model_cost.js";
|
|
15
|
+
import { routingBillingProviderAttemptFacts, } from "./billing_finalization.js";
|
|
16
|
+
import { estimateBenchmarkReplay } from "./benchmark_replay.js";
|
|
17
|
+
import { extractInteractionObservability } from "./requests/interaction_observability.js";
|
|
18
|
+
import { costString, finiteNumber, isRecord, tokenCount, } from "./ingest_values.js";
|
|
19
|
+
import { buildCompletedPrefixHit, leaseCountdownForPrefix, prefixEntryForIngest, reportPendingLease, reportPrefixState, } from "./prefix_state.js";
|
|
20
|
+
import { subscriptionUsageLimitFromFailure } from "../providers/subscription_limits.js";
|
|
21
|
+
import { scrubSecrets } from "./scrub.js";
|
|
22
|
+
import { encodeRetainedInteractionRequest } from "./requests/retained_interaction_request.js";
|
|
23
|
+
import { toolResultObservations, toolSkillEvents } from "./tools/observations.js";
|
|
24
|
+
// Log pipelines commonly cap entries at 256 KB and split longer stdout lines
|
|
25
|
+
// into fragments; one guard keeps pathological provider errors
|
|
26
|
+
// (request-payload echoes) from mangling log entries.
|
|
27
|
+
const LOG_TEXT_MAX_CHARS = 64 * 1024;
|
|
28
|
+
// Upper bound on how long a response's terminal marker waits for the
|
|
29
|
+
// prefix-state write. The write is one small intra-region round trip
|
|
30
|
+
// (tens of milliseconds typical); the cap only matters when the platform is
|
|
31
|
+
// degraded, where closing the stream on time and letting the full report
|
|
32
|
+
// carry the entry is the right trade.
|
|
33
|
+
const PREFIX_STATE_WRITE_WAIT_MS = 1500;
|
|
34
|
+
function guardedLogText(text) {
|
|
35
|
+
const scrubbed = scrubSecrets(text);
|
|
36
|
+
return scrubbed.length > LOG_TEXT_MAX_CHARS
|
|
37
|
+
? `${scrubbed.slice(0, LOG_TEXT_MAX_CHARS)}…[truncated]`
|
|
38
|
+
: scrubbed;
|
|
39
|
+
}
|
|
40
|
+
function errorText(error) {
|
|
41
|
+
if (error instanceof Error && error.message)
|
|
42
|
+
return error.message;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export class RequestReporter {
|
|
46
|
+
args;
|
|
47
|
+
selectorRun = null;
|
|
48
|
+
selectorTrace = null;
|
|
49
|
+
adoptedSelectorOutput = null;
|
|
50
|
+
selectorOutputAccepted = false;
|
|
51
|
+
decision = null;
|
|
52
|
+
nextDecision = null;
|
|
53
|
+
fallbackDecision = null;
|
|
54
|
+
providerAttempts = [];
|
|
55
|
+
billingProviderAttempts = [];
|
|
56
|
+
decisionDetails = {};
|
|
57
|
+
reported = false;
|
|
58
|
+
terminal = null;
|
|
59
|
+
prefixStatePromise = null;
|
|
60
|
+
constructor(args) {
|
|
61
|
+
this.args = args;
|
|
62
|
+
}
|
|
63
|
+
attachSelectorRun(run) {
|
|
64
|
+
this.selectorRun = run;
|
|
65
|
+
this.selectorTrace = run;
|
|
66
|
+
this.selectorOutputAccepted = true;
|
|
67
|
+
this.decision = run.decision;
|
|
68
|
+
this.nextDecision = run.decision;
|
|
69
|
+
this.fallbackDecision = run.fallbackDecision ?? null;
|
|
70
|
+
}
|
|
71
|
+
attachSpeculativeSelectorRun(run, servingDecision) {
|
|
72
|
+
this.selectorRun = run;
|
|
73
|
+
this.selectorTrace = run;
|
|
74
|
+
this.selectorOutputAccepted = true;
|
|
75
|
+
this.decision = servingDecision;
|
|
76
|
+
this.nextDecision = run.decision;
|
|
77
|
+
this.fallbackDecision = run.fallbackDecision ?? null;
|
|
78
|
+
}
|
|
79
|
+
// Speculative execution can begin before a selector run; its serving
|
|
80
|
+
// decision drives selected-model attribution, prefix entries, and errors.
|
|
81
|
+
attachDecision(decision) {
|
|
82
|
+
this.decision = decision;
|
|
83
|
+
this.nextDecision = decision;
|
|
84
|
+
}
|
|
85
|
+
attachFallbackDecision(decision) {
|
|
86
|
+
this.fallbackDecision = decision ?? null;
|
|
87
|
+
}
|
|
88
|
+
attachServingDecision(decision) {
|
|
89
|
+
this.decision = decision;
|
|
90
|
+
}
|
|
91
|
+
recordProviderAttempt(attempt) {
|
|
92
|
+
const { execution, message, billingOutcome, ...audit } = attempt;
|
|
93
|
+
this.providerAttempts.push(audit);
|
|
94
|
+
this.billingProviderAttempts.push(this.billingFacts(attempt));
|
|
95
|
+
}
|
|
96
|
+
modelCost(attempt) {
|
|
97
|
+
return modelCost(attempt ? [...this.billingProviderAttempts, this.billingFacts(attempt)] : this.billingProviderAttempts);
|
|
98
|
+
}
|
|
99
|
+
billingFacts({ execution, message, role, attempt, billingOutcome }) {
|
|
100
|
+
return routingBillingProviderAttemptFacts({
|
|
101
|
+
provider: execution.provider, model: execution.model,
|
|
102
|
+
providerKeySource: execution.providerKeySource, role, attempt, outcome: billingOutcome,
|
|
103
|
+
providerMessage: message, usage: isRecord(message.usage) ? message.usage : null,
|
|
104
|
+
providerPricingKnown: this.pricingKnown(execution.model),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Strategy-specific audit data (e.g. custom-rule drops and cache-aware
|
|
108
|
+
// pruning) merged into the logged decision_details next to the strategy.
|
|
109
|
+
attachAdoptedSelectorOutput(adopted) {
|
|
110
|
+
this.adoptedSelectorOutput = adopted;
|
|
111
|
+
}
|
|
112
|
+
attachDecisionDetails(details) {
|
|
113
|
+
this.decisionDetails = { ...this.decisionDetails, ...details };
|
|
114
|
+
}
|
|
115
|
+
// Resolves once this turn's prefix-state write has settled (committed,
|
|
116
|
+
// failed, or timed out). Stream serializers hold the response's terminal
|
|
117
|
+
// marker on this so the immediate next turn always resolves the entry;
|
|
118
|
+
// the cap bounds the worst-case close delay, and the full report remains
|
|
119
|
+
// the fallback writer whenever the fast write loses.
|
|
120
|
+
waitForPrefixEntryWrite() {
|
|
121
|
+
const pending = this.prefixStatePromise;
|
|
122
|
+
if (!pending)
|
|
123
|
+
return Promise.resolve();
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const timer = setTimeout(() => resolve(), PREFIX_STATE_WRITE_WAIT_MS);
|
|
126
|
+
void pending.then(() => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
resolve();
|
|
129
|
+
}, () => {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
freezeProviderTerminal(args) {
|
|
136
|
+
return this.freezeTerminal(reportOutcome(args));
|
|
137
|
+
}
|
|
138
|
+
reportSelectorFailure(error) {
|
|
139
|
+
this.attachFailedSelectorTrace(error);
|
|
140
|
+
}
|
|
141
|
+
// Catch-all for errors that escape the request handler after resolution
|
|
142
|
+
// (missing provider key, thrown provider calls). send() is idempotent, so
|
|
143
|
+
// paths that already reported are unaffected.
|
|
144
|
+
freezeFailure(error) {
|
|
145
|
+
this.attachFailedSelectorTrace(error);
|
|
146
|
+
if (this.terminal !== null)
|
|
147
|
+
return this.terminal;
|
|
148
|
+
const code = errorCode(error, "internal_error");
|
|
149
|
+
return this.freezeTerminal({
|
|
150
|
+
status: this.decision ? "provider_error" : "selector_error",
|
|
151
|
+
errorCode: code,
|
|
152
|
+
errorMessage: errorText(error) ?? undefined,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
attachFailedSelectorTrace(error) {
|
|
156
|
+
const selectorTrace = selectorTraceFromError(error);
|
|
157
|
+
if (!selectorTrace)
|
|
158
|
+
return;
|
|
159
|
+
this.selectorTrace = selectorTrace;
|
|
160
|
+
this.selectorOutputAccepted = false;
|
|
161
|
+
}
|
|
162
|
+
pricingKnown(model) {
|
|
163
|
+
try {
|
|
164
|
+
return this.args.pricing?.(model) != null;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
freezeTerminal(outcome) {
|
|
171
|
+
if (this.terminal !== null)
|
|
172
|
+
return this.terminal;
|
|
173
|
+
const billing = this.registerBilling(outcome);
|
|
174
|
+
const terminal = {
|
|
175
|
+
report: () => this.send(outcome, billing),
|
|
176
|
+
};
|
|
177
|
+
this.terminal = terminal;
|
|
178
|
+
return terminal;
|
|
179
|
+
}
|
|
180
|
+
registerBilling(outcome) {
|
|
181
|
+
const providerMessage = providerMessageForOutcome(outcome);
|
|
182
|
+
const terminalUsage = outcome.status === "completed"
|
|
183
|
+
? outcome.interactionOutput.usage
|
|
184
|
+
: isRecord(providerMessage?.usage)
|
|
185
|
+
? providerMessage.usage
|
|
186
|
+
: null;
|
|
187
|
+
const benchmarkReplay = estimateBenchmarkReplay({
|
|
188
|
+
enabledModels: this.args.credentials.enabled_models,
|
|
189
|
+
modelPrices: this.args.credentials.model_prices ?? {},
|
|
190
|
+
executionProvider: (model) => executionProviderForModel(this.args.credentials, model),
|
|
191
|
+
priorHit: this.args.identityHit,
|
|
192
|
+
receivedAtMs: this.args.receivedAt.getTime(),
|
|
193
|
+
promptTokens: (tokenCount(terminalUsage?.input) ?? 0) +
|
|
194
|
+
(tokenCount(terminalUsage?.cacheRead) ?? 0) +
|
|
195
|
+
(tokenCount(terminalUsage?.cacheWrite) ?? 0),
|
|
196
|
+
outputTokens: tokenCount(terminalUsage?.output) ?? 0,
|
|
197
|
+
});
|
|
198
|
+
const terminal = this.args.billingFinalization.freeze(billingTerminalOutcome(outcome), this.billingProviderAttempts, terminalUsage === null ? null : benchmarkReplay.costUsd);
|
|
199
|
+
return {
|
|
200
|
+
payload: terminal.readPayload(),
|
|
201
|
+
benchmarkReplay,
|
|
202
|
+
terminal,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
send(outcome, billing) {
|
|
206
|
+
if (this.reported)
|
|
207
|
+
return;
|
|
208
|
+
this.reported = true;
|
|
209
|
+
try {
|
|
210
|
+
const a = this.args;
|
|
211
|
+
const scrubbedError = outcome.errorMessage ? guardedLogText(outcome.errorMessage) : null;
|
|
212
|
+
if (outcome.status !== "completed" && outcome.status !== "aborted") {
|
|
213
|
+
// Provider errors can echo request content; logs are outside the
|
|
214
|
+
// ingest boundary's ZDR scrub, so only the code is logged.
|
|
215
|
+
console.error("routing request failed", JSON.stringify({
|
|
216
|
+
request_id: a.requestId,
|
|
217
|
+
router_id: a.routerId,
|
|
218
|
+
status: outcome.status,
|
|
219
|
+
error_code: outcome.errorCode ?? null,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
const run = this.selectorRun;
|
|
223
|
+
const selectorTrace = this.selectorTrace;
|
|
224
|
+
const decision = this.decision;
|
|
225
|
+
const selectedModel = outcome.providerExecution?.model ?? decision?.selectedModel ?? null;
|
|
226
|
+
const reasoningBucket = decision?.reasoningEffort ?? null;
|
|
227
|
+
// A completed fallback attempt means the intended pair failed this
|
|
228
|
+
// turn: the next-turn recommendation and any lease countdown follow the
|
|
229
|
+
// pair that actually served instead of re-pinning the failed one.
|
|
230
|
+
const fallbackServed = this.providerAttempts.some((attempt) => attempt.role === "fallback" && attempt.outcome === "completed");
|
|
231
|
+
const nextModel = fallbackServed
|
|
232
|
+
? selectedModel
|
|
233
|
+
: this.nextDecision?.selectedModel ?? selectedModel;
|
|
234
|
+
const nextReasoningEffort = fallbackServed
|
|
235
|
+
? reasoningBucket
|
|
236
|
+
: this.nextDecision?.reasoningEffort ?? reasoningBucket;
|
|
237
|
+
const selectedProvider = outcome.providerExecution?.provider ?? (selectedModel
|
|
238
|
+
? executionProviderForModel(a.credentials, selectedModel)
|
|
239
|
+
: null);
|
|
240
|
+
const keySource = outcome.providerExecution?.providerKeySource ?? (selectedModel && selectedProvider
|
|
241
|
+
? (a.credentials.provider_key_sources?.[selectedProvider] ?? "user")
|
|
242
|
+
: null);
|
|
243
|
+
const selectedEstimate = a.costEstimates.find((item) => item.model === selectedModel &&
|
|
244
|
+
item.reasoning_effort === reasoningBucket);
|
|
245
|
+
const completedOutput = outcome.status === "completed"
|
|
246
|
+
? outcome.interactionOutput
|
|
247
|
+
: null;
|
|
248
|
+
const responseBody = outcome.status === "completed"
|
|
249
|
+
? outcome.responseBody
|
|
250
|
+
: outcome.status === "aborted"
|
|
251
|
+
? outcome.responseBody ?? null
|
|
252
|
+
: null;
|
|
253
|
+
const providerMessage = providerMessageForOutcome(outcome);
|
|
254
|
+
const usage = completedOutput?.usage ?? (isRecord(providerMessage?.usage) ? providerMessage.usage : null);
|
|
255
|
+
const stopReason = completedOutput?.stopReason ?? (typeof providerMessage?.stopReason === "string"
|
|
256
|
+
? providerMessage.stopReason
|
|
257
|
+
: null);
|
|
258
|
+
const pricingKnown = selectedModel ? this.pricingKnown(selectedModel) : null;
|
|
259
|
+
const costSource = usage?.costSource === "provider"
|
|
260
|
+
? "provider"
|
|
261
|
+
: pricingKnown === true ? "pricing" : null;
|
|
262
|
+
const observability = extractInteractionObservability(a.interaction);
|
|
263
|
+
const inputTokens = tokenCount(usage?.input) ?? 0;
|
|
264
|
+
const retainedClientRequest = encodeRetainedInteractionRequest(a.interaction);
|
|
265
|
+
const cacheReadTokens = tokenCount(usage?.cacheRead) ?? 0;
|
|
266
|
+
const cacheWriteTokens = tokenCount(usage?.cacheWrite) ?? 0;
|
|
267
|
+
const outputTokens = tokenCount(usage?.output) ?? 0;
|
|
268
|
+
const { benchmarkReplay, payload: billingPayload } = billing;
|
|
269
|
+
const selectedChain = selectedModel ? a.chainByModel.get(selectedModel) : undefined;
|
|
270
|
+
const selectedTarget = selectedModel
|
|
271
|
+
? a.targetsByModel.get(selectedModel)
|
|
272
|
+
: undefined;
|
|
273
|
+
// An interrupted turn writes its delivered partial too, so a follow-up
|
|
274
|
+
// that echoes the partial text matches this turn instead of the last.
|
|
275
|
+
const prefixOutput = completedOutput ?? (outcome.status === "aborted" ? outcome.partialOutput ?? null : null);
|
|
276
|
+
const backgroundRequest = isBackgroundRequest(a.interaction);
|
|
277
|
+
const completedPrefix = !backgroundRequest && prefixOutput &&
|
|
278
|
+
selectedModel &&
|
|
279
|
+
selectedChain &&
|
|
280
|
+
selectedTarget
|
|
281
|
+
? buildCompletedPrefixHit({
|
|
282
|
+
request: a.interaction,
|
|
283
|
+
output: prefixOutput,
|
|
284
|
+
model: selectedModel,
|
|
285
|
+
nextModel,
|
|
286
|
+
nextReasoningEffort,
|
|
287
|
+
reasoningBucket,
|
|
288
|
+
// The strategy stamps remaining = turns - 1 assuming the
|
|
289
|
+
// granting turn served the leased pair. A speculative switch
|
|
290
|
+
// serves the previous pair instead, so the commitment has not
|
|
291
|
+
// started: credit the turn back.
|
|
292
|
+
leaseTurnsRemaining: leaseCountdownForPrefix({
|
|
293
|
+
served: decision ?? null,
|
|
294
|
+
next: this.nextDecision,
|
|
295
|
+
fallbackServed,
|
|
296
|
+
}),
|
|
297
|
+
reason: this.nextDecision?.reason ?? decision?.reason ?? null,
|
|
298
|
+
chain: selectedChain,
|
|
299
|
+
looseChain: a.looseChain,
|
|
300
|
+
target: selectedTarget,
|
|
301
|
+
conversationId: a.conversationId,
|
|
302
|
+
leaseDepth: a.leaseDepth,
|
|
303
|
+
providerBlockDepth: a.providerBlockDepthFor(selectedModel),
|
|
304
|
+
fingerprints: a.fingerprints,
|
|
305
|
+
usage: prefixOutput.usage,
|
|
306
|
+
benchmarkReplayDepth: benchmarkReplay.depth,
|
|
307
|
+
})
|
|
308
|
+
: null;
|
|
309
|
+
const prefixEntry = completedPrefix === null
|
|
310
|
+
? null
|
|
311
|
+
: prefixEntryForIngest(completedPrefix);
|
|
312
|
+
// Touch the SELECTED model's hit whose provider cache was just read (on
|
|
313
|
+
// a switch-back it is shallower than the global deepest hit), so its
|
|
314
|
+
// freshness clock restarts. For effort-keyed models that is the entry
|
|
315
|
+
// in the serving reasoning bucket; a cross-effort turn read no existing
|
|
316
|
+
// entry, and refreshing another bucket's entry would keep a partition
|
|
317
|
+
// alive that the provider never touched.
|
|
318
|
+
const touchedHash = !backgroundRequest && outcome.status === "completed" && selectedModel && cacheReadTokens > 0
|
|
319
|
+
? (selectPrefixHit(a.hits, selectedModel, reasoningBucket ?? "off")?.entry.hash ?? null)
|
|
320
|
+
: null;
|
|
321
|
+
// Fast-path prefix-state write, committed before the response's
|
|
322
|
+
// terminal marker is released (waitForPrefixEntryWrite): the next
|
|
323
|
+
// turn — even one arriving milliseconds after stream close — always
|
|
324
|
+
// resolves this turn's conversation id and next-model prediction.
|
|
325
|
+
// The full report below re-applies the same state idempotently, so a
|
|
326
|
+
// lost or timed-out fast write only degrades to the old ordering.
|
|
327
|
+
// An ordinary fallback breaks any lease commitment, prefetched ones
|
|
328
|
+
// included. A lease-aware fallback transfers the active countdown to
|
|
329
|
+
// the serving pair, so only clear pending state when no countdown was
|
|
330
|
+
// transferred. The depth fence orders the clear safely against
|
|
331
|
+
// in-flight prefetch writes.
|
|
332
|
+
if (!backgroundRequest && fallbackServed && decision?.leaseTurnsRemaining === undefined) {
|
|
333
|
+
trackBackgroundTelemetry(reportPendingLease({
|
|
334
|
+
routerId: a.routerId,
|
|
335
|
+
conversationId: a.conversationId,
|
|
336
|
+
decidedAtDepth: a.leaseDepth,
|
|
337
|
+
lease: null,
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
if (prefixEntry !== null || touchedHash !== null) {
|
|
341
|
+
this.prefixStatePromise = reportPrefixState({
|
|
342
|
+
organization_id: a.credentials.organization_id,
|
|
343
|
+
router_id: a.routerId,
|
|
344
|
+
received_at: a.receivedAt.toISOString(),
|
|
345
|
+
zdr: a.zdr,
|
|
346
|
+
prefix_entry: prefixEntry,
|
|
347
|
+
touched_prefix_hash: touchedHash,
|
|
348
|
+
});
|
|
349
|
+
trackBackgroundTelemetry(this.prefixStatePromise);
|
|
350
|
+
}
|
|
351
|
+
const ingestPayload = {
|
|
352
|
+
request_id: this.args.requestId,
|
|
353
|
+
client_request_id: a.clientRequestId,
|
|
354
|
+
organization_id: a.credentials.organization_id,
|
|
355
|
+
router_id: a.routerId,
|
|
356
|
+
api_key_id: a.credentials.api_key_id,
|
|
357
|
+
attributed_user_id: a.credentials.attributed_user_id,
|
|
358
|
+
source_protocol: a.interaction.sourceProtocol,
|
|
359
|
+
request_kind: observability.requestKind,
|
|
360
|
+
received_at: a.receivedAt.toISOString(),
|
|
361
|
+
completed_at: billingPayload.finalized_at,
|
|
362
|
+
billing_terminal_outcome: billingPayload.terminal_outcome,
|
|
363
|
+
billing_provider_attempts: billingPayload.provider_attempts,
|
|
364
|
+
stream: a.interaction.stream,
|
|
365
|
+
zdr: a.zdr,
|
|
366
|
+
status: outcome.status,
|
|
367
|
+
error_code: outcome.errorCode ?? null,
|
|
368
|
+
error_message: scrubbedError,
|
|
369
|
+
conversation_id: a.conversationId,
|
|
370
|
+
agent_session_id: a.agentSessionId ?? null,
|
|
371
|
+
prefix_match_depth: a.identityMatchDepth ?? a.hits.deepest?.depth ?? null,
|
|
372
|
+
prefix_warm_tokens: selectedEstimate?.warm_tokens ?? null,
|
|
373
|
+
message_count: observability.messageCount,
|
|
374
|
+
has_tools: observability.hasTools,
|
|
375
|
+
request_metadata: observability.metadata,
|
|
376
|
+
selected_model: selectedModel,
|
|
377
|
+
selected_provider: selectedProvider,
|
|
378
|
+
provider_key_source: keySource,
|
|
379
|
+
selector_latency_ms: selectorTrace?.latencyMs ?? null,
|
|
380
|
+
provider_latency_ms: outcome.providerLatencyMs ?? null,
|
|
381
|
+
total_latency_ms: Date.now() - a.startedMs,
|
|
382
|
+
client_request_json: retainedClientRequest.json,
|
|
383
|
+
client_request_format: retainedClientRequest.format,
|
|
384
|
+
client_response_json: responseBody ? JSON.stringify(responseBody) : null,
|
|
385
|
+
selector: selectorTrace
|
|
386
|
+
? selectorIngest({
|
|
387
|
+
trace: selectorTrace,
|
|
388
|
+
reason: run?.decision.reason ?? null,
|
|
389
|
+
outputAccepted: this.selectorOutputAccepted,
|
|
390
|
+
pricingKnown: this.pricingKnown(selectorTrace.selectorModel),
|
|
391
|
+
})
|
|
392
|
+
: this.adoptedSelectorOutput
|
|
393
|
+
? {
|
|
394
|
+
selector_model: "grpo-policy",
|
|
395
|
+
candidate_models: this.adoptedSelectorOutput.candidates.map((candidate) => ({
|
|
396
|
+
model: candidate.model,
|
|
397
|
+
thinking_level: candidate.reasoningEffort,
|
|
398
|
+
})),
|
|
399
|
+
reason: this.adoptedSelectorOutput.reason,
|
|
400
|
+
output_text: this.adoptedSelectorOutput.outputText,
|
|
401
|
+
}
|
|
402
|
+
: null,
|
|
403
|
+
decision_details: {
|
|
404
|
+
strategy: a.strategy,
|
|
405
|
+
selected_reasoning_effort: reasoningBucket,
|
|
406
|
+
next_reasoning_effort: nextReasoningEffort,
|
|
407
|
+
...(this.fallbackDecision
|
|
408
|
+
? {
|
|
409
|
+
fallback_model: this.fallbackDecision.selectedModel,
|
|
410
|
+
fallback_reasoning_effort: this.fallbackDecision.reasoningEffort,
|
|
411
|
+
}
|
|
412
|
+
: {}),
|
|
413
|
+
...(this.providerAttempts.length > 0
|
|
414
|
+
? { provider_attempts: this.providerAttempts }
|
|
415
|
+
: {}),
|
|
416
|
+
...this.decisionDetails,
|
|
417
|
+
...(a.pruning ? { candidate_pruning: a.pruning } : {}),
|
|
418
|
+
},
|
|
419
|
+
// Model/provider/key source and latency live at the top level (they
|
|
420
|
+
// apply to failures with no provider call too).
|
|
421
|
+
provider_call: selectedModel && selectedProvider && (completedOutput || providerMessage)
|
|
422
|
+
? {
|
|
423
|
+
stop_reason: stopReason,
|
|
424
|
+
usage: usageIngest(usage, pricingKnown === true),
|
|
425
|
+
}
|
|
426
|
+
: null,
|
|
427
|
+
benchmark_replay_cost_usd: billingPayload.benchmark_replay_cost_usd,
|
|
428
|
+
benchmark_replay_raw_cost_usd: usage === null ? null : costString(benchmarkReplay.rawCostUsd),
|
|
429
|
+
benchmark_model_id: benchmarkReplay.model,
|
|
430
|
+
benchmark_estimator_version: benchmarkReplay.estimatorVersion,
|
|
431
|
+
benchmark_input_rate_usd_per_mtok: costString(benchmarkReplay.price?.input ?? null),
|
|
432
|
+
benchmark_cached_input_rate_usd_per_mtok: costString(benchmarkReplay.price?.cached_input ?? null),
|
|
433
|
+
benchmark_cache_write_rate_usd_per_mtok: costString(benchmarkReplay.price?.cache_write ?? null),
|
|
434
|
+
benchmark_output_rate_usd_per_mtok: costString(benchmarkReplay.price?.output ?? null),
|
|
435
|
+
tool_skill_events: toolSkillEvents({
|
|
436
|
+
routerId: a.routerId,
|
|
437
|
+
request: a.interaction,
|
|
438
|
+
output: completedOutput ?? undefined,
|
|
439
|
+
harnessConversationIdentity: a.harnessConversationIdentity,
|
|
440
|
+
zdr: a.zdr,
|
|
441
|
+
}),
|
|
442
|
+
tool_result_observations: toolResultObservations({
|
|
443
|
+
routerId: a.routerId,
|
|
444
|
+
request: a.interaction,
|
|
445
|
+
zdr: a.zdr,
|
|
446
|
+
}),
|
|
447
|
+
prefix_entry: prefixEntry,
|
|
448
|
+
touched_prefix_hash: touchedHash,
|
|
449
|
+
};
|
|
450
|
+
gatewayPlatform().recordRequest({
|
|
451
|
+
row: ingestPayload,
|
|
452
|
+
summary: {
|
|
453
|
+
request_id: a.requestId,
|
|
454
|
+
organization_id: a.credentials.organization_id,
|
|
455
|
+
router_id: a.routerId,
|
|
456
|
+
api_key_id: a.credentials.api_key_id,
|
|
457
|
+
source_protocol: a.interaction.sourceProtocol,
|
|
458
|
+
request_kind: observability.requestKind,
|
|
459
|
+
status: outcome.status,
|
|
460
|
+
error_code: outcome.errorCode ?? null,
|
|
461
|
+
stream: a.interaction.stream,
|
|
462
|
+
zdr: a.zdr,
|
|
463
|
+
strategy: a.strategy,
|
|
464
|
+
selected_model: selectedModel,
|
|
465
|
+
selected_provider: selectedProvider,
|
|
466
|
+
selected_reasoning_effort: reasoningBucket,
|
|
467
|
+
next_model: nextModel,
|
|
468
|
+
next_reasoning_effort: nextReasoningEffort,
|
|
469
|
+
fallback_model: this.fallbackDecision?.selectedModel ?? null,
|
|
470
|
+
fallback_reasoning_effort: this.fallbackDecision?.reasoningEffort ?? null,
|
|
471
|
+
provider_key_source: keySource,
|
|
472
|
+
message_count: observability.messageCount,
|
|
473
|
+
tool_count: observability.metadata.declared_tool_counts.total,
|
|
474
|
+
candidate_count: a.costEstimates.length,
|
|
475
|
+
provider_attempt_count: this.providerAttempts.length,
|
|
476
|
+
prefix_match_depth: a.identityMatchDepth ?? a.hits.deepest?.depth ?? null,
|
|
477
|
+
prefix_warm_tokens: selectedEstimate?.warm_tokens ?? null,
|
|
478
|
+
selector_latency_ms: selectorTrace?.latencyMs ?? null,
|
|
479
|
+
provider_latency_ms: outcome.providerLatencyMs ?? null,
|
|
480
|
+
total_latency_ms: Date.now() - a.startedMs,
|
|
481
|
+
input_tokens: tokenCount(usage?.input),
|
|
482
|
+
cache_read_tokens: tokenCount(usage?.cacheRead),
|
|
483
|
+
cache_write_tokens: tokenCount(usage?.cacheWrite),
|
|
484
|
+
output_tokens: tokenCount(usage?.output),
|
|
485
|
+
total_tokens: tokenCount(usage?.totalTokens),
|
|
486
|
+
total_cost_usd: costSource === null || !isRecord(usage?.cost)
|
|
487
|
+
? null
|
|
488
|
+
: finiteNumber(usage.cost.total),
|
|
489
|
+
cost_source: costSource,
|
|
490
|
+
pricing_known: pricingKnown,
|
|
491
|
+
},
|
|
492
|
+
distinctId: a.credentials.attributed_user_id ??
|
|
493
|
+
a.credentials.api_key_id ??
|
|
494
|
+
a.credentials.organization_id,
|
|
495
|
+
interaction: a.interaction,
|
|
496
|
+
isNewConversation: a.isNewConversation,
|
|
497
|
+
zdr: a.zdr,
|
|
498
|
+
harnessConversationIdentity: a.harnessConversationIdentity,
|
|
499
|
+
tags: a.tags,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
console.error("routing report assembly failed", error);
|
|
504
|
+
captureOperationalError(error, {
|
|
505
|
+
code: "routing_report_assembly_failed",
|
|
506
|
+
requestId: this.args.requestId,
|
|
507
|
+
routerId: this.args.routerId,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function billingTerminalOutcome(outcome) {
|
|
513
|
+
if (outcome.status === "completed")
|
|
514
|
+
return "completed";
|
|
515
|
+
if (outcome.status === "aborted") {
|
|
516
|
+
return outcome.billingOutcome;
|
|
517
|
+
}
|
|
518
|
+
return "provider_failed";
|
|
519
|
+
}
|
|
520
|
+
function providerMessageForOutcome(outcome) {
|
|
521
|
+
return outcome.status === "selector_error"
|
|
522
|
+
? null
|
|
523
|
+
: outcome.providerMessage ?? null;
|
|
524
|
+
}
|
|
525
|
+
function reportOutcome(args) {
|
|
526
|
+
if (args.status === "customer_canceled") {
|
|
527
|
+
return {
|
|
528
|
+
status: "aborted",
|
|
529
|
+
errorCode: "client_disconnected",
|
|
530
|
+
providerLatencyMs: args.providerLatencyMs,
|
|
531
|
+
providerExecution: args.execution,
|
|
532
|
+
providerMessage: args.message,
|
|
533
|
+
responseBody: args.responseBody,
|
|
534
|
+
billingOutcome: "customer_canceled",
|
|
535
|
+
partialOutput: args.partialOutput ?? null,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (args.status === "provider_error") {
|
|
539
|
+
return {
|
|
540
|
+
status: "provider_error",
|
|
541
|
+
errorCode: subscriptionUsageLimitFromFailure(args.message) === null
|
|
542
|
+
? "provider_execution_failed"
|
|
543
|
+
: "subscription_usage_limit",
|
|
544
|
+
errorMessage: typeof args.message.errorMessage === "string"
|
|
545
|
+
? args.message.errorMessage
|
|
546
|
+
: undefined,
|
|
547
|
+
providerMessage: args.message,
|
|
548
|
+
providerLatencyMs: args.providerLatencyMs,
|
|
549
|
+
providerExecution: args.execution,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
status: "completed",
|
|
554
|
+
interactionOutput: args.output,
|
|
555
|
+
providerMessage: args.message,
|
|
556
|
+
responseBody: args.responseBody,
|
|
557
|
+
providerLatencyMs: args.providerLatencyMs,
|
|
558
|
+
providerExecution: args.execution,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function selectorIngest(args) {
|
|
562
|
+
const { trace } = args;
|
|
563
|
+
return {
|
|
564
|
+
selector_model: trace.selectorModel,
|
|
565
|
+
candidate_models: trace.selectorInput.candidate_pairs.map((candidate) => ({
|
|
566
|
+
model: candidate.model,
|
|
567
|
+
thinking_level: candidate.thinking_level,
|
|
568
|
+
})),
|
|
569
|
+
evals: trace.selectorInput.imported_evals.flatMap((card) => {
|
|
570
|
+
const id = card.id;
|
|
571
|
+
const name = card.name;
|
|
572
|
+
return typeof id === "string" && typeof name === "string" ? [{ id, name }] : [];
|
|
573
|
+
}),
|
|
574
|
+
cost_estimates: trace.selectorInput.cost_estimates,
|
|
575
|
+
previous_model: trace.selectorInput.previous_decision?.model ?? null,
|
|
576
|
+
reason: args.reason,
|
|
577
|
+
prompt_json: JSON.stringify(trace.selectorRequest),
|
|
578
|
+
output_text: selectorOutputText(trace, args.outputAccepted),
|
|
579
|
+
usage: usageIngest(trace.usage, args.pricingKnown),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function selectorOutputText(trace, accepted) {
|
|
583
|
+
const rejected = trace.rejectedOutputTexts ?? [];
|
|
584
|
+
if (rejected.length === 0)
|
|
585
|
+
return trace.outputText;
|
|
586
|
+
return JSON.stringify({
|
|
587
|
+
schema_version: 1,
|
|
588
|
+
kind: "selector_output_attempts",
|
|
589
|
+
attempts: [
|
|
590
|
+
...rejected.map((outputText) => ({
|
|
591
|
+
outcome: "rejected",
|
|
592
|
+
output_text: outputText,
|
|
593
|
+
})),
|
|
594
|
+
{
|
|
595
|
+
outcome: accepted ? "accepted" : "rejected",
|
|
596
|
+
output_text: trace.outputText,
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
// Normalized usage -> RoutingUsageIngest. Costs ride along as strings so the
|
|
602
|
+
// backend's Decimal columns parse them exactly; pricing_known=false marks
|
|
603
|
+
// catalog-missing models whose normalized cost would otherwise read as $0.
|
|
604
|
+
function usageIngest(usage, pricingKnown) {
|
|
605
|
+
if (!usage)
|
|
606
|
+
return null;
|
|
607
|
+
const cost = isRecord(usage.cost) ? usage.cost : {};
|
|
608
|
+
return {
|
|
609
|
+
input_tokens: tokenCount(usage.input),
|
|
610
|
+
cache_read_tokens: tokenCount(usage.cacheRead),
|
|
611
|
+
cache_write_tokens: tokenCount(usage.cacheWrite),
|
|
612
|
+
output_tokens: tokenCount(usage.output),
|
|
613
|
+
total_tokens: tokenCount(usage.totalTokens),
|
|
614
|
+
input_cost_usd: pricingKnown ? costString(cost.input) : null,
|
|
615
|
+
cache_read_cost_usd: pricingKnown ? costString(cost.cacheRead) : null,
|
|
616
|
+
cache_write_cost_usd: pricingKnown ? costString(cost.cacheWrite) : null,
|
|
617
|
+
output_cost_usd: pricingKnown ? costString(cost.output) : null,
|
|
618
|
+
total_cost_usd: pricingKnown ? costString(cost.total) : null,
|
|
619
|
+
pricing_known: pricingKnown,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type AgentRequestContext, type InteractionContent, type InteractionRequest, type ProviderContinuationState } from "../../interaction/types.js";
|
|
2
|
+
declare const SCHEMA_VERSION: 1;
|
|
3
|
+
type CountBy<T extends string> = Record<T, number>;
|
|
4
|
+
type ItemKind = "hosted_tool_call" | "hosted_tool_result" | "message" | "reasoning" | "tool_call" | "tool_result";
|
|
5
|
+
type MessageRole = "assistant" | "developer" | "system" | "user";
|
|
6
|
+
type ToolKind = "custom" | "function" | "hosted";
|
|
7
|
+
type ContentModality = "image" | "text";
|
|
8
|
+
type ContinuationKind = ProviderContinuationState["kind"];
|
|
9
|
+
export type InteractionObservabilityMetadata = {
|
|
10
|
+
schema_version: typeof SCHEMA_VERSION;
|
|
11
|
+
agent_context?: AgentRequestContext;
|
|
12
|
+
item_counts: CountBy<ItemKind> & {
|
|
13
|
+
total: number;
|
|
14
|
+
};
|
|
15
|
+
message_role_counts: CountBy<MessageRole>;
|
|
16
|
+
tool_call_counts: CountBy<ToolKind>;
|
|
17
|
+
declared_tool_counts: CountBy<ToolKind> & {
|
|
18
|
+
total: number;
|
|
19
|
+
};
|
|
20
|
+
item_content_modality_counts: CountBy<ContentModality>;
|
|
21
|
+
continuation_state_counts: CountBy<ContinuationKind> & {
|
|
22
|
+
total: number;
|
|
23
|
+
};
|
|
24
|
+
instructions_present: boolean;
|
|
25
|
+
reasoning_history_present: boolean;
|
|
26
|
+
prompt_cache_key_present: boolean;
|
|
27
|
+
user_present: boolean;
|
|
28
|
+
controls: {
|
|
29
|
+
reasoning_effort?: NonNullable<InteractionRequest["reasoning"]>["effort"];
|
|
30
|
+
reasoning_enabled?: boolean;
|
|
31
|
+
response_format?: NonNullable<InteractionRequest["responseFormat"]>["type"];
|
|
32
|
+
tool_choice?: "any" | "auto" | "custom" | "function" | "hosted" | "none" | "required";
|
|
33
|
+
parallel_tool_calls?: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export type InteractionRequestKind = "other" | "tool_continuation" | "user_message";
|
|
37
|
+
export type InteractionObservability = {
|
|
38
|
+
requestKind: InteractionRequestKind;
|
|
39
|
+
messageCount: number;
|
|
40
|
+
hasTools: boolean;
|
|
41
|
+
metadata: InteractionObservabilityMetadata;
|
|
42
|
+
};
|
|
43
|
+
export declare function extractInteractionObservability(request: InteractionRequest): InteractionObservability;
|
|
44
|
+
export declare function hasMeaningfulUserContent(content: InteractionContent[] | string | null): boolean;
|
|
45
|
+
export {};
|