@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,280 @@
|
|
|
1
|
+
import { CHARS_PER_TOKEN, RouterCoreError, buildSizedSelectorRequest, isRecord, parseSelectorDecision, sameRoutingCandidate, selectorSafeMessages, } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { HttpError } from "../http/errors.js";
|
|
3
|
+
import { executionProviderForModel } from "../models/execution_provider.js";
|
|
4
|
+
import { providerLabel } from "../models/provider_defaults.js";
|
|
5
|
+
import { internalChatCompletionsToInteraction } from "../protocol/input/chat_completions.js";
|
|
6
|
+
import { gatewayPlatform } from "../platform/platform.js";
|
|
7
|
+
import { isBedrockApi } from "../providers/model_capabilities.js";
|
|
8
|
+
import { completeInteractionModel, modelContextWindow } from "../providers/pi.js";
|
|
9
|
+
import { deriveFallbackDecision } from "./fallback_decision.js";
|
|
10
|
+
export function resolveRouter(args) {
|
|
11
|
+
return gatewayPlatform().resolveRouter(args);
|
|
12
|
+
}
|
|
13
|
+
export function createLlmSelectorBackend(config) {
|
|
14
|
+
const model = requiredSelectorConfig(config.model, "LLM selector model");
|
|
15
|
+
const apiKey = requiredSelectorConfig(config.apiKey, "LLM selector API key");
|
|
16
|
+
return {
|
|
17
|
+
selectorModel: model,
|
|
18
|
+
contextWindowChars: () => selectorContextWindowChars(model),
|
|
19
|
+
select: (args) => callLlmSelector(args, apiKey, config.reasoningEffort),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export const SELECTOR_OUTPUT_ATTEMPTS = 2;
|
|
23
|
+
// Exported so other selector backends can attach the raw policy completion
|
|
24
|
+
// to failures the same way the LLM selector does. Without this wrapping,
|
|
25
|
+
// selectorTraceFromError() returns null for their failures and the malformed
|
|
26
|
+
// completion is never reported.
|
|
27
|
+
export class SelectorOutputError extends HttpError {
|
|
28
|
+
selectorTrace;
|
|
29
|
+
constructor(error, selectorTrace) {
|
|
30
|
+
super(error.status, error.message, error.code, error.param);
|
|
31
|
+
this.selectorTrace = selectorTrace;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function selectorTraceFromError(error) {
|
|
35
|
+
return error instanceof SelectorOutputError ? error.selectorTrace : null;
|
|
36
|
+
}
|
|
37
|
+
export async function selectModel(args) {
|
|
38
|
+
const { selectorBackend } = args;
|
|
39
|
+
const selectorModel = selectorBackend.selectorModel;
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
const contextWindowChars = await selectorBackend.contextWindowChars();
|
|
42
|
+
const safeMessages = selectorSafeMessages(args.messages);
|
|
43
|
+
const leaseContextEnabled = args.leaseContextEnabled !== false;
|
|
44
|
+
const { selectorInput, selectorRequest } = buildSizedSelectorRequest({
|
|
45
|
+
candidates: args.candidates,
|
|
46
|
+
evals: args.evals,
|
|
47
|
+
previousDecision: args.previousDecision,
|
|
48
|
+
costEstimates: args.costEstimates,
|
|
49
|
+
customRules: args.customRules,
|
|
50
|
+
defaultTarget: args.defaultTarget,
|
|
51
|
+
selectorModel,
|
|
52
|
+
contextWindowChars,
|
|
53
|
+
messages: selectorBackend.prepareMessages?.(safeMessages) ?? safeMessages,
|
|
54
|
+
...(leaseContextEnabled ? { leaseHistory: args.leaseHistory ?? [] } : {}),
|
|
55
|
+
modelFallbackEnabled: args.modelFallbackEnabled,
|
|
56
|
+
fallbackRequiresDifferentProvider: args.fallbackRequiresDifferentProvider,
|
|
57
|
+
imputeEvalScores: args.imputeEvalScores,
|
|
58
|
+
imputationReferenceEvals: args.imputationReferenceEvals,
|
|
59
|
+
});
|
|
60
|
+
const runArgs = {
|
|
61
|
+
...args,
|
|
62
|
+
selectorModel,
|
|
63
|
+
selectorInput,
|
|
64
|
+
selectorRequest,
|
|
65
|
+
startedAt,
|
|
66
|
+
};
|
|
67
|
+
const run = await selectorBackend.select(runArgs);
|
|
68
|
+
return validateSelectorRun(run, args);
|
|
69
|
+
}
|
|
70
|
+
async function selectorContextWindowChars(selectorModel) {
|
|
71
|
+
const contextWindow = modelContextWindow(selectorModel);
|
|
72
|
+
if (contextWindow === null) {
|
|
73
|
+
throw new HttpError(500, `No context window is configured for selector model ${selectorModel}.`, "configuration_error");
|
|
74
|
+
}
|
|
75
|
+
return contextWindow * CHARS_PER_TOKEN;
|
|
76
|
+
}
|
|
77
|
+
async function callLlmSelector(args, selectorApiKey, reasoningEffort) {
|
|
78
|
+
const rejectedOutputTexts = [];
|
|
79
|
+
const usages = [];
|
|
80
|
+
for (let attempt = 1; attempt <= SELECTOR_OUTPUT_ATTEMPTS; attempt += 1) {
|
|
81
|
+
let message;
|
|
82
|
+
try {
|
|
83
|
+
const completion = await completeInteractionModel({
|
|
84
|
+
model: args.selectorModel,
|
|
85
|
+
request: internalChatCompletionsToInteraction(args.selectorRequest),
|
|
86
|
+
apiKey: selectorApiKey,
|
|
87
|
+
sessionId: args.sessionId,
|
|
88
|
+
...(reasoningEffort ? { selectedReasoningEffort: reasoningEffort } : {}),
|
|
89
|
+
});
|
|
90
|
+
message = completion.message;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw selectorExecutionError(args, rejectedOutputTexts, usages);
|
|
94
|
+
}
|
|
95
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
96
|
+
throw selectorExecutionError(args, rejectedOutputTexts, usages);
|
|
97
|
+
}
|
|
98
|
+
const text = assistantText(message);
|
|
99
|
+
usages.push(isRecord(message.usage) ? message.usage : null);
|
|
100
|
+
try {
|
|
101
|
+
// The managed LLM selector never negotiates leases, so drop any
|
|
102
|
+
// lease_turns_remaining a non-strict provider stamps on the output.
|
|
103
|
+
const { leaseTurnsRemaining: _leaseTurnsRemaining, ...decision } = parseSelectorDecision(text).decision;
|
|
104
|
+
return {
|
|
105
|
+
decision,
|
|
106
|
+
selectorModel: args.selectorModel,
|
|
107
|
+
candidates: args.candidates,
|
|
108
|
+
selectorInput: args.selectorInput,
|
|
109
|
+
selectorRequest: args.selectorRequest,
|
|
110
|
+
outputText: text,
|
|
111
|
+
...(rejectedOutputTexts.length > 0
|
|
112
|
+
? { rejectedOutputTexts: [...rejectedOutputTexts] }
|
|
113
|
+
: {}),
|
|
114
|
+
usage: aggregateSelectorUsage(usages),
|
|
115
|
+
latencyMs: Date.now() - args.startedAt,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const mapped = selectorOutputHttpError(error);
|
|
120
|
+
if (!mapped || !isMalformedSelectorOutput(mapped))
|
|
121
|
+
throw error;
|
|
122
|
+
if (attempt < SELECTOR_OUTPUT_ATTEMPTS) {
|
|
123
|
+
rejectedOutputTexts.push(text);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
throw new SelectorOutputError(mapped, {
|
|
127
|
+
selectorModel: args.selectorModel,
|
|
128
|
+
candidates: args.candidates,
|
|
129
|
+
selectorInput: args.selectorInput,
|
|
130
|
+
selectorRequest: args.selectorRequest,
|
|
131
|
+
outputText: text,
|
|
132
|
+
...(rejectedOutputTexts.length > 0
|
|
133
|
+
? { rejectedOutputTexts: [...rejectedOutputTexts] }
|
|
134
|
+
: {}),
|
|
135
|
+
usage: aggregateSelectorUsage(usages),
|
|
136
|
+
latencyMs: Date.now() - args.startedAt,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
throw new HttpError(502, "Selector request failed.", "selector_execution_failed");
|
|
141
|
+
}
|
|
142
|
+
function validateSelectorRun(run, args) {
|
|
143
|
+
const selected = {
|
|
144
|
+
model: run.decision.selectedModel,
|
|
145
|
+
reasoningEffort: run.decision.reasoningEffort,
|
|
146
|
+
};
|
|
147
|
+
if (!args.candidates.some((candidate) => sameRoutingCandidate(candidate, selected))) {
|
|
148
|
+
throw new SelectorOutputError(new HttpError(502, "Selector returned a model/thinking-level pair outside this router's candidates.", "selector_invalid_candidate"), run);
|
|
149
|
+
}
|
|
150
|
+
const { fallbackDecision: _selectorFallback, ...primaryRun } = run;
|
|
151
|
+
const fallbackDecision = deriveFallbackDecision({
|
|
152
|
+
candidates: args.candidates,
|
|
153
|
+
selected,
|
|
154
|
+
enabled: args.modelFallbackEnabled,
|
|
155
|
+
requiresDifferentProvider: args.fallbackRequiresDifferentProvider,
|
|
156
|
+
providerForModel: args.executionProviderForModel,
|
|
157
|
+
});
|
|
158
|
+
return fallbackDecision
|
|
159
|
+
? { ...primaryRun, fallbackDecision }
|
|
160
|
+
: primaryRun;
|
|
161
|
+
}
|
|
162
|
+
function requiredSelectorConfig(value, label) {
|
|
163
|
+
const normalized = value.trim();
|
|
164
|
+
if (!normalized) {
|
|
165
|
+
throw new HttpError(500, `${label} is not configured.`, "configuration_error");
|
|
166
|
+
}
|
|
167
|
+
return normalized;
|
|
168
|
+
}
|
|
169
|
+
export function providerAuthForSelectedModel(args) {
|
|
170
|
+
const provider = executionProviderForModel(args.credentials, args.selectedModel);
|
|
171
|
+
const config = args.credentials.model_configs?.[args.selectedModel];
|
|
172
|
+
const structured = args.credentials.provider_auth?.[provider];
|
|
173
|
+
const key = args.credentials.provider_keys[provider]?.trim();
|
|
174
|
+
if (structured !== undefined) {
|
|
175
|
+
if (key
|
|
176
|
+
|| (provider !== "amazon-bedrock" && !isBedrockApi(config?.api))
|
|
177
|
+
|| !(isAwsSigV4ProviderAuth(structured) || isAwsAssumeRoleProviderAuth(structured))) {
|
|
178
|
+
throw new HttpError(500, `Invalid ${providerLabel(provider)} provider authentication is configured for this router.`, "provider_configuration_error");
|
|
179
|
+
}
|
|
180
|
+
return structured;
|
|
181
|
+
}
|
|
182
|
+
if (key)
|
|
183
|
+
return { type: "bearer", apiKey: key };
|
|
184
|
+
const source = args.credentials.provider_key_sources?.[provider] ?? "user";
|
|
185
|
+
const prefix = source === "managed" ? "Dari-managed" : "stored";
|
|
186
|
+
throw new HttpError(401, `No ${prefix} ${providerLabel(provider)} provider credential is configured for this router.`, "missing_provider_key");
|
|
187
|
+
}
|
|
188
|
+
function isAwsAssumeRoleProviderAuth(value) {
|
|
189
|
+
return (isRecord(value)
|
|
190
|
+
&& value.type === "aws_assume_role"
|
|
191
|
+
&& typeof value.role_arn === "string"
|
|
192
|
+
&& Boolean(value.role_arn.trim())
|
|
193
|
+
&& typeof value.external_id === "string"
|
|
194
|
+
&& Boolean(value.external_id.trim())
|
|
195
|
+
&& typeof value.region === "string"
|
|
196
|
+
&& Boolean(value.region.trim()));
|
|
197
|
+
}
|
|
198
|
+
function isAwsSigV4ProviderAuth(value) {
|
|
199
|
+
return (isRecord(value)
|
|
200
|
+
&& value.type === "aws_sigv4"
|
|
201
|
+
&& typeof value.access_key_id === "string"
|
|
202
|
+
&& Boolean(value.access_key_id.trim())
|
|
203
|
+
&& typeof value.secret_access_key === "string"
|
|
204
|
+
&& Boolean(value.secret_access_key.trim())
|
|
205
|
+
&& typeof value.region === "string"
|
|
206
|
+
&& Boolean(value.region.trim())
|
|
207
|
+
&& (value.session_token == null
|
|
208
|
+
|| (typeof value.session_token === "string"
|
|
209
|
+
&& Boolean(value.session_token.trim()))));
|
|
210
|
+
}
|
|
211
|
+
function assistantText(message) {
|
|
212
|
+
const content = message.content;
|
|
213
|
+
if (!Array.isArray(content))
|
|
214
|
+
return "";
|
|
215
|
+
return content
|
|
216
|
+
.filter(isRecord)
|
|
217
|
+
.filter((item) => item.type === "text" && typeof item.text === "string")
|
|
218
|
+
.map((item) => String(item.text))
|
|
219
|
+
.join("");
|
|
220
|
+
}
|
|
221
|
+
function selectorOutputHttpError(error) {
|
|
222
|
+
if (error instanceof HttpError)
|
|
223
|
+
return error;
|
|
224
|
+
if (!(error instanceof RouterCoreError) || error.kind !== "selector_output")
|
|
225
|
+
return null;
|
|
226
|
+
return new HttpError(502, error.message, error.code, error.param);
|
|
227
|
+
}
|
|
228
|
+
function isMalformedSelectorOutput(error) {
|
|
229
|
+
return error.code === "selector_invalid_json" || error.code === "selector_invalid_response";
|
|
230
|
+
}
|
|
231
|
+
function selectorExecutionError(args, rejectedOutputTexts, usages) {
|
|
232
|
+
const error = new HttpError(502, "Selector request failed.", "selector_execution_failed");
|
|
233
|
+
const lastOutput = rejectedOutputTexts.at(-1);
|
|
234
|
+
if (lastOutput === undefined)
|
|
235
|
+
return error;
|
|
236
|
+
return new SelectorOutputError(error, {
|
|
237
|
+
selectorModel: args.selectorModel,
|
|
238
|
+
candidates: args.candidates,
|
|
239
|
+
selectorInput: args.selectorInput,
|
|
240
|
+
selectorRequest: args.selectorRequest,
|
|
241
|
+
outputText: lastOutput,
|
|
242
|
+
...(rejectedOutputTexts.length > 1
|
|
243
|
+
? { rejectedOutputTexts: rejectedOutputTexts.slice(0, -1) }
|
|
244
|
+
: {}),
|
|
245
|
+
usage: aggregateSelectorUsage(usages),
|
|
246
|
+
latencyMs: Date.now() - args.startedAt,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function aggregateSelectorUsage(usages) {
|
|
250
|
+
const records = usages.filter((usage) => usage !== null);
|
|
251
|
+
if (records.length === 0)
|
|
252
|
+
return null;
|
|
253
|
+
if (records.length === 1)
|
|
254
|
+
return records[0];
|
|
255
|
+
const aggregate = { ...records.at(-1) };
|
|
256
|
+
for (const key of ["input", "cacheRead", "cacheWrite", "output", "totalTokens"]) {
|
|
257
|
+
const total = sumFiniteField(records, key);
|
|
258
|
+
if (total !== null)
|
|
259
|
+
aggregate[key] = total;
|
|
260
|
+
}
|
|
261
|
+
const costs = records.flatMap((usage) => isRecord(usage.cost) ? [usage.cost] : []);
|
|
262
|
+
if (costs.length > 0) {
|
|
263
|
+
const aggregateCost = {
|
|
264
|
+
...(isRecord(aggregate.cost) ? aggregate.cost : {}),
|
|
265
|
+
};
|
|
266
|
+
for (const key of ["input", "cacheRead", "cacheWrite", "output", "total"]) {
|
|
267
|
+
const total = sumFiniteField(costs, key);
|
|
268
|
+
if (total !== null)
|
|
269
|
+
aggregateCost[key] = total;
|
|
270
|
+
}
|
|
271
|
+
aggregate.cost = aggregateCost;
|
|
272
|
+
}
|
|
273
|
+
return aggregate;
|
|
274
|
+
}
|
|
275
|
+
function sumFiniteField(records, key) {
|
|
276
|
+
const values = records.flatMap((record) => typeof record[key] === "number" && Number.isFinite(record[key])
|
|
277
|
+
? [record[key]]
|
|
278
|
+
: []);
|
|
279
|
+
return values.length > 0 ? values.reduce((sum, value) => sum + value, 0) : null;
|
|
280
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChatMessage } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import type { InteractionRequest } from "../interaction/types.js";
|
|
3
|
+
export declare const IMAGE_OMITTED = "<image omitted>";
|
|
4
|
+
export declare const SELECTOR_IMAGE_PLACEHOLDERS: RegExp;
|
|
5
|
+
export type SelectorImage = {
|
|
6
|
+
index: number;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
export type SelectorProjection = {
|
|
10
|
+
messages: ChatMessage[];
|
|
11
|
+
images: SelectorImage[];
|
|
12
|
+
};
|
|
13
|
+
export declare function selectorMessagesForInteraction(interaction: InteractionRequest, options?: {
|
|
14
|
+
imageInput?: boolean;
|
|
15
|
+
}): SelectorProjection;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { toolWireName } from "../interaction/tool_identity.js";
|
|
2
|
+
export const IMAGE_OMITTED = "<image omitted>";
|
|
3
|
+
// Matches every numbered placeholder in a rendered selector prompt.
|
|
4
|
+
export const SELECTOR_IMAGE_PLACEHOLDERS = /<image (\d+)>/g;
|
|
5
|
+
const OPAQUE_REASONING_OMITTED = "<opaque reasoning omitted>";
|
|
6
|
+
// The selector needs readable task history, not provider replay state. This
|
|
7
|
+
// projection intentionally keeps text and tool exchanges while omitting
|
|
8
|
+
// opaque continuation payloads, source item ids, phases, and image bytes.
|
|
9
|
+
// With `imageInput`, image parts become numbered `<image N>` placeholders and
|
|
10
|
+
// the data URLs are returned separately so the policy adapter can attach them
|
|
11
|
+
// as ordered image parts. Without it the projection stays byte-identical to
|
|
12
|
+
// the pre-image-input shape, which the deployed policy was trained on.
|
|
13
|
+
export function selectorMessagesForInteraction(interaction, options = {}) {
|
|
14
|
+
const messages = [];
|
|
15
|
+
const images = [];
|
|
16
|
+
const imagePart = (url, detail) => {
|
|
17
|
+
let placeholder = IMAGE_OMITTED;
|
|
18
|
+
if (options.imageInput === true) {
|
|
19
|
+
images.push({ index: images.length + 1, url });
|
|
20
|
+
placeholder = `<image ${images.length}>`;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
type: "image_url",
|
|
24
|
+
image_url: {
|
|
25
|
+
url: placeholder,
|
|
26
|
+
...(detail !== undefined ? { detail } : {}),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
// Hosted calls are complete provider output groups. Harness calls can be
|
|
31
|
+
// persisted before reasoning that belongs to the same assistant turn.
|
|
32
|
+
const hostedToolCarriers = new WeakSet();
|
|
33
|
+
if (interaction.instructions) {
|
|
34
|
+
messages.push({ role: "system", content: interaction.instructions });
|
|
35
|
+
}
|
|
36
|
+
for (const item of interaction.items) {
|
|
37
|
+
if (item.type === "reasoning") {
|
|
38
|
+
const readable = [...item.summary, ...item.content].join("\n");
|
|
39
|
+
let assistant = messages.at(-1);
|
|
40
|
+
if (!assistant
|
|
41
|
+
|| assistant.role !== "assistant"
|
|
42
|
+
|| hasAssistantContent(assistant)
|
|
43
|
+
|| hostedToolCarriers.has(assistant)) {
|
|
44
|
+
assistant = { role: "assistant", content: null };
|
|
45
|
+
messages.push(assistant);
|
|
46
|
+
}
|
|
47
|
+
const selectorReasoning = readable || OPAQUE_REASONING_OMITTED;
|
|
48
|
+
assistant.reasoning_content = assistant.reasoning_content
|
|
49
|
+
? `${assistant.reasoning_content}\n${selectorReasoning}`
|
|
50
|
+
: selectorReasoning;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (item.type === "reasoning_configuration") {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (item.type === "tool_result") {
|
|
57
|
+
messages.push({
|
|
58
|
+
role: "tool",
|
|
59
|
+
tool_call_id: item.callId,
|
|
60
|
+
...selectorContentField(item.output, item.outputShape, imagePart),
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (item.type === "tool_call") {
|
|
65
|
+
appendToolCall(messages, item);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (item.type === "hosted_tool_call") {
|
|
69
|
+
const assistant = appendToolCall(messages, {
|
|
70
|
+
type: "tool_call",
|
|
71
|
+
id: item.id,
|
|
72
|
+
callId: item.id,
|
|
73
|
+
kind: "function",
|
|
74
|
+
name: item.tool,
|
|
75
|
+
input: item.payload,
|
|
76
|
+
});
|
|
77
|
+
hostedToolCarriers.add(assistant);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const content = selectorContentField(item.content, item.contentShape, imagePart);
|
|
81
|
+
const previous = messages.at(-1);
|
|
82
|
+
if (item.role === "assistant"
|
|
83
|
+
&& previous?.role === "assistant"
|
|
84
|
+
&& isReasoningOnly(previous)) {
|
|
85
|
+
if ("content" in content)
|
|
86
|
+
previous.content = content.content;
|
|
87
|
+
else
|
|
88
|
+
delete previous.content;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
messages.push({ role: item.role, ...content });
|
|
92
|
+
}
|
|
93
|
+
return { messages, images };
|
|
94
|
+
}
|
|
95
|
+
function appendToolCall(messages, item) {
|
|
96
|
+
let assistant = messages.at(-1);
|
|
97
|
+
if (!assistant || assistant.role !== "assistant") {
|
|
98
|
+
assistant = { role: "assistant", content: null };
|
|
99
|
+
messages.push(assistant);
|
|
100
|
+
}
|
|
101
|
+
assistant.tool_calls = [
|
|
102
|
+
...(assistant.tool_calls ?? []),
|
|
103
|
+
{
|
|
104
|
+
id: item.callId,
|
|
105
|
+
type: "function",
|
|
106
|
+
function: {
|
|
107
|
+
name: toolWireName(item.namespace, item.name),
|
|
108
|
+
...(item.input !== undefined
|
|
109
|
+
? {
|
|
110
|
+
arguments: item.kind === "custom"
|
|
111
|
+
? JSON.stringify({ input: String(item.input) })
|
|
112
|
+
: item.input,
|
|
113
|
+
}
|
|
114
|
+
: {}),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
return assistant;
|
|
119
|
+
}
|
|
120
|
+
function selectorContentField(content, shape, imagePart) {
|
|
121
|
+
if (shape === "absent")
|
|
122
|
+
return {};
|
|
123
|
+
if (shape === "null")
|
|
124
|
+
return { content: null };
|
|
125
|
+
if (shape === "string") {
|
|
126
|
+
const text = Array.isArray(content)
|
|
127
|
+
? content
|
|
128
|
+
.filter((part) => part.type === "text")
|
|
129
|
+
.map((part) => part.text)
|
|
130
|
+
.join("")
|
|
131
|
+
: content;
|
|
132
|
+
return { content: text ?? "" };
|
|
133
|
+
}
|
|
134
|
+
if (!Array.isArray(content)) {
|
|
135
|
+
return {
|
|
136
|
+
content: content === null
|
|
137
|
+
? []
|
|
138
|
+
: [{ type: "text", text: content }],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
content: content.map((part) => {
|
|
143
|
+
if (part.type === "text")
|
|
144
|
+
return { type: "text", text: part.text };
|
|
145
|
+
if (part.type === "tool_reference") {
|
|
146
|
+
return { type: "text", text: `Referenced tool: ${part.toolName}` };
|
|
147
|
+
}
|
|
148
|
+
return imagePart(part.url, part.detail);
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function hasAssistantContent(message) {
|
|
153
|
+
if (typeof message.content === "string")
|
|
154
|
+
return message.content.length > 0;
|
|
155
|
+
return Array.isArray(message.content) && message.content.length > 0;
|
|
156
|
+
}
|
|
157
|
+
function isReasoningOnly(message) {
|
|
158
|
+
return !message.tool_calls?.length
|
|
159
|
+
&& !hasAssistantContent(message)
|
|
160
|
+
&& message.reasoning_content !== undefined;
|
|
161
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function trackBackgroundTelemetry(task: Promise<unknown>): void;
|
|
2
|
+
export declare function drainBackgroundTelemetry(): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Drain until every tracked task settles or the timeout passes. Tasks tracked
|
|
5
|
+
* while draining are included. Returns how many tasks were still pending.
|
|
6
|
+
*/
|
|
7
|
+
export declare function drainBackgroundTelemetryFor(timeoutMs: number): Promise<number>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const pendingTelemetry = new Set();
|
|
2
|
+
export function trackBackgroundTelemetry(task) {
|
|
3
|
+
pendingTelemetry.add(task);
|
|
4
|
+
void task.then(() => pendingTelemetry.delete(task), () => pendingTelemetry.delete(task));
|
|
5
|
+
}
|
|
6
|
+
export async function drainBackgroundTelemetry() {
|
|
7
|
+
while (pendingTelemetry.size > 0) {
|
|
8
|
+
await Promise.allSettled([...pendingTelemetry]);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Drain until every tracked task settles or the timeout passes. Tasks tracked
|
|
13
|
+
* while draining are included. Returns how many tasks were still pending.
|
|
14
|
+
*/
|
|
15
|
+
export async function drainBackgroundTelemetryFor(timeoutMs) {
|
|
16
|
+
let expired = false;
|
|
17
|
+
let timer;
|
|
18
|
+
const deadline = new Promise((resolve) => {
|
|
19
|
+
timer = setTimeout(() => {
|
|
20
|
+
expired = true;
|
|
21
|
+
resolve();
|
|
22
|
+
}, Math.max(0, timeoutMs));
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
while (pendingTelemetry.size > 0 && !expired) {
|
|
26
|
+
await Promise.race([Promise.allSettled([...pendingTelemetry]), deadline]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
return pendingTelemetry.size;
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PrefixHit, type RouterModelPrice } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
export declare const BENCHMARK_REPLAY_ESTIMATOR_VERSION = "sticky-cache-v2";
|
|
3
|
+
export type BenchmarkReplayPriceSnapshot = {
|
|
4
|
+
input: number;
|
|
5
|
+
output: number;
|
|
6
|
+
cached_input: number | null;
|
|
7
|
+
cache_write: number | null;
|
|
8
|
+
};
|
|
9
|
+
export type BenchmarkReplayEstimate = {
|
|
10
|
+
costUsd: number | null;
|
|
11
|
+
rawCostUsd: number | null;
|
|
12
|
+
model: string | null;
|
|
13
|
+
price: BenchmarkReplayPriceSnapshot | null;
|
|
14
|
+
estimatorVersion: typeof BENCHMARK_REPLAY_ESTIMATOR_VERSION;
|
|
15
|
+
depth: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Price one turn as if the router had stayed on its most expensive enabled
|
|
19
|
+
* model for the whole conversation.
|
|
20
|
+
*
|
|
21
|
+
* The prior depth comes from synchronized prefix state, so this runs once
|
|
22
|
+
* while the request is being reported instead of replaying conversation rows
|
|
23
|
+
* during every Activity read.
|
|
24
|
+
*/
|
|
25
|
+
export declare function estimateBenchmarkReplay(args: {
|
|
26
|
+
enabledModels: readonly string[];
|
|
27
|
+
modelPrices: Readonly<Record<string, RouterModelPrice>>;
|
|
28
|
+
/** Serving provider per canonical model; owner-namespace ids (deepseek-ai/…)
|
|
29
|
+
* cannot self-identify their provider, so the resolved router supplies it. */
|
|
30
|
+
executionProvider: (model: string) => string;
|
|
31
|
+
priorHit?: PrefixHit;
|
|
32
|
+
receivedAtMs: number;
|
|
33
|
+
promptTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
selectedModel?: string;
|
|
36
|
+
selectedCostUsd?: number;
|
|
37
|
+
}): BenchmarkReplayEstimate;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { PREFIX_WARMTH_WINDOW_MS, providerMinCacheTokens, } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
const TOKENS_PER_MTOK = 1_000_000;
|
|
3
|
+
export const BENCHMARK_REPLAY_ESTIMATOR_VERSION = "sticky-cache-v2";
|
|
4
|
+
/**
|
|
5
|
+
* Price one turn as if the router had stayed on its most expensive enabled
|
|
6
|
+
* model for the whole conversation.
|
|
7
|
+
*
|
|
8
|
+
* The prior depth comes from synchronized prefix state, so this runs once
|
|
9
|
+
* while the request is being reported instead of replaying conversation rows
|
|
10
|
+
* during every Activity read.
|
|
11
|
+
*/
|
|
12
|
+
export function estimateBenchmarkReplay(args) {
|
|
13
|
+
const promptTokens = nonnegativeInteger(args.promptTokens);
|
|
14
|
+
const outputTokens = nonnegativeInteger(args.outputTokens);
|
|
15
|
+
const priorDepth = priorBenchmarkDepth(args.priorHit);
|
|
16
|
+
const depth = Math.max(priorDepth, promptTokens);
|
|
17
|
+
const effectivePrices = Object.fromEntries(Object.entries(args.modelPrices).map(([model, base]) => {
|
|
18
|
+
const tier = base.tiers?.filter((entry) => promptTokens > entry.input_tokens_above)
|
|
19
|
+
.sort((left, right) => right.input_tokens_above - left.input_tokens_above)[0];
|
|
20
|
+
return [model, tier ?? base];
|
|
21
|
+
}));
|
|
22
|
+
const benchmark = benchmarkPrice(args.enabledModels, effectivePrices);
|
|
23
|
+
if (benchmark === null) {
|
|
24
|
+
return {
|
|
25
|
+
costUsd: null,
|
|
26
|
+
rawCostUsd: null,
|
|
27
|
+
model: null,
|
|
28
|
+
price: null,
|
|
29
|
+
estimatorVersion: BENCHMARK_REPLAY_ESTIMATOR_VERSION,
|
|
30
|
+
depth,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const { model, price } = benchmark;
|
|
34
|
+
const updatedAtMs = args.priorHit
|
|
35
|
+
? Date.parse(args.priorHit.entry.updated_at)
|
|
36
|
+
: Number.NaN;
|
|
37
|
+
const isWarm = Number.isFinite(updatedAtMs) &&
|
|
38
|
+
updatedAtMs >= args.receivedAtMs - PREFIX_WARMTH_WINDOW_MS;
|
|
39
|
+
const warmTokens = isWarm ? Math.min(priorDepth, promptTokens) : 0;
|
|
40
|
+
const freshTokens = promptTokens - warmTokens;
|
|
41
|
+
const cachedRate = validOptionalRate(price.cached_input) ?? price.input;
|
|
42
|
+
const cacheWriteRate = validOptionalRate(price.cache_write);
|
|
43
|
+
const freshRate = cacheWriteRate !== null &&
|
|
44
|
+
cacheWriteRate > 0 &&
|
|
45
|
+
promptTokens >= providerMinCacheTokens(model, args.executionProvider(model))
|
|
46
|
+
? cacheWriteRate
|
|
47
|
+
: price.input;
|
|
48
|
+
const replayedCost = (warmTokens * cachedRate +
|
|
49
|
+
freshTokens * freshRate +
|
|
50
|
+
outputTokens * price.output) /
|
|
51
|
+
TOKENS_PER_MTOK;
|
|
52
|
+
const selectedCost = validOptionalRate(args.selectedCostUsd);
|
|
53
|
+
return {
|
|
54
|
+
costUsd: args.selectedModel === model && selectedCost !== null
|
|
55
|
+
? Math.min(replayedCost, selectedCost)
|
|
56
|
+
: replayedCost,
|
|
57
|
+
rawCostUsd: replayedCost,
|
|
58
|
+
model,
|
|
59
|
+
price: {
|
|
60
|
+
input: price.input,
|
|
61
|
+
output: price.output,
|
|
62
|
+
cached_input: validOptionalRate(price.cached_input),
|
|
63
|
+
cache_write: validOptionalRate(price.cache_write),
|
|
64
|
+
},
|
|
65
|
+
estimatorVersion: BENCHMARK_REPLAY_ESTIMATOR_VERSION,
|
|
66
|
+
depth,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function benchmarkPrice(enabledModels, modelPrices) {
|
|
70
|
+
let benchmark = null;
|
|
71
|
+
for (const model of enabledModels) {
|
|
72
|
+
const price = modelPrices[model];
|
|
73
|
+
if (!validPrice(price))
|
|
74
|
+
continue;
|
|
75
|
+
if (benchmark === null ||
|
|
76
|
+
price.output > benchmark.price.output ||
|
|
77
|
+
(price.output === benchmark.price.output && price.input > benchmark.price.input) ||
|
|
78
|
+
(price.output === benchmark.price.output &&
|
|
79
|
+
price.input === benchmark.price.input &&
|
|
80
|
+
model > benchmark.model)) {
|
|
81
|
+
benchmark = { model, price };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return benchmark;
|
|
85
|
+
}
|
|
86
|
+
function priorBenchmarkDepth(hit) {
|
|
87
|
+
if (!hit)
|
|
88
|
+
return 0;
|
|
89
|
+
const stored = hit.entry.benchmark_replay_depth;
|
|
90
|
+
if (typeof stored === "number" && Number.isFinite(stored) && stored >= 0) {
|
|
91
|
+
return Math.trunc(stored);
|
|
92
|
+
}
|
|
93
|
+
// Prefix entries written before benchmark depth existed still provide a
|
|
94
|
+
// safe bootstrap point. The next entry carries the running maximum.
|
|
95
|
+
return nonnegativeInteger(hit.entry.prompt_tokens);
|
|
96
|
+
}
|
|
97
|
+
function validPrice(price) {
|
|
98
|
+
return price !== undefined && validRate(price.input) && validRate(price.output);
|
|
99
|
+
}
|
|
100
|
+
function validRate(value) {
|
|
101
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
102
|
+
}
|
|
103
|
+
function validOptionalRate(value) {
|
|
104
|
+
return validRate(value) ? value : null;
|
|
105
|
+
}
|
|
106
|
+
function nonnegativeInteger(value) {
|
|
107
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
108
|
+
}
|