@mupt-ai/dari-gateway 0.1.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/README.md +8 -1
- 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 +22 -5
- package/src/bun.ts +4 -0
- 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 +17 -17
- 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 +10 -9
- 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 +10 -10
- 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/interaction/types.ts +3 -0
- 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 +36 -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 +115 -41
- package/src/providers/pi_context.ts +6 -6
- package/src/providers/pi_options.ts +283 -453
- package/src/providers/pi_output.ts +6 -5
- package/src/providers/provider_attempt_runner.ts +1 -1
- package/src/providers/provider_call.ts +37 -18
- package/src/providers/provider_reported_cost.ts +138 -0
- package/src/providers/reasoning.ts +2 -2
- package/src/providers/stream_finalization.ts +10 -10
- 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 +31 -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 +46 -10
- package/src/runtime/provider_stream_lifecycle.ts +1 -1
- package/src/runtime/provider_stream_outcome.ts +2 -2
- package/src/runtime/request_tags.ts +34 -0
- package/src/runtime/routed_completion.ts +13 -11
- package/src/runtime/routed_protocol.ts +2 -2
- package/src/runtime/routed_runtime.ts +38 -20
- package/src/runtime/routing_lease.ts +7 -7
- package/src/runtime/runtime_reporter.ts +3 -3
- package/src/self_host/main.ts +19 -14
- package/src/self_host/platform.ts +5 -5
- package/src/self_host/prefix_store.ts +2 -2
- package/src/self_host/resolve.ts +11 -11
- 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 +11 -11
- 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 +30 -25
- 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 +9 -5
- package/src/types.ts +15 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { posix, win32 } from "node:path";
|
|
2
|
+
const MAX_COMMAND_TOKENS = 4_096;
|
|
3
|
+
const SHELL_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/u;
|
|
4
|
+
const POSIX_SHELL_WHITESPACE = /[\t\n\v\f\r ]/u;
|
|
5
|
+
const POSIX_CONTROL_COMMANDS = new Set([
|
|
6
|
+
"!",
|
|
7
|
+
"[[",
|
|
8
|
+
"case",
|
|
9
|
+
"do",
|
|
10
|
+
"done",
|
|
11
|
+
"elif",
|
|
12
|
+
"else",
|
|
13
|
+
"esac",
|
|
14
|
+
"fi",
|
|
15
|
+
"for",
|
|
16
|
+
"function",
|
|
17
|
+
"if",
|
|
18
|
+
"select",
|
|
19
|
+
"then",
|
|
20
|
+
"time",
|
|
21
|
+
"until",
|
|
22
|
+
"while",
|
|
23
|
+
"{",
|
|
24
|
+
"}",
|
|
25
|
+
]);
|
|
26
|
+
const TRUSTED_POSIX_EXECUTABLE_DIRECTORIES = new Set([
|
|
27
|
+
"/bin",
|
|
28
|
+
"/opt/homebrew/bin",
|
|
29
|
+
"/usr/bin",
|
|
30
|
+
"/usr/local/bin",
|
|
31
|
+
]);
|
|
32
|
+
export function executableCommand(words) {
|
|
33
|
+
let index = 0;
|
|
34
|
+
while (index < words.length && SHELL_ASSIGNMENT.test(words[index]))
|
|
35
|
+
index += 1;
|
|
36
|
+
if (words[index] === "env") {
|
|
37
|
+
index += 1;
|
|
38
|
+
while (index < words.length
|
|
39
|
+
&& (words[index].startsWith("-") || SHELL_ASSIGNMENT.test(words[index])))
|
|
40
|
+
index += 1;
|
|
41
|
+
}
|
|
42
|
+
if (words[index] === "command" || words[index] === "builtin") {
|
|
43
|
+
index += 1;
|
|
44
|
+
while (index < words.length && words[index].startsWith("-"))
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
const executable = words[index];
|
|
48
|
+
return executable === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: { executable, args: words.slice(index + 1) };
|
|
51
|
+
}
|
|
52
|
+
// This lexer recognizes only the shell structure needed to identify direct
|
|
53
|
+
// read commands. Unsupported substitutions and control flow fail closed.
|
|
54
|
+
export function restrictedShellSegments(command, platform) {
|
|
55
|
+
const powershell = platform === "windows";
|
|
56
|
+
if ((!powershell && (command.includes("$(") || command.includes("`"))) || command.includes("\u0000")) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const segments = [];
|
|
60
|
+
let words = [];
|
|
61
|
+
let word = "";
|
|
62
|
+
let wordStarted = false;
|
|
63
|
+
let quote = null;
|
|
64
|
+
let tokenCount = 0;
|
|
65
|
+
const flushWord = () => {
|
|
66
|
+
if (!wordStarted)
|
|
67
|
+
return;
|
|
68
|
+
words.push(word);
|
|
69
|
+
tokenCount += 1;
|
|
70
|
+
word = "";
|
|
71
|
+
wordStarted = false;
|
|
72
|
+
};
|
|
73
|
+
const flushSegment = (next) => {
|
|
74
|
+
flushWord();
|
|
75
|
+
if (words.length > 0)
|
|
76
|
+
segments.push({ words, next });
|
|
77
|
+
words = [];
|
|
78
|
+
};
|
|
79
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
80
|
+
const character = command[index];
|
|
81
|
+
if (quote !== null) {
|
|
82
|
+
if (character === quote) {
|
|
83
|
+
quote = null;
|
|
84
|
+
}
|
|
85
|
+
else if (powershell && character === "`") {
|
|
86
|
+
index += 1;
|
|
87
|
+
if (index >= command.length)
|
|
88
|
+
return undefined;
|
|
89
|
+
word += command[index];
|
|
90
|
+
}
|
|
91
|
+
else if (!powershell && character === "\\" && quote === '"') {
|
|
92
|
+
const escaped = command[index + 1];
|
|
93
|
+
if (escaped === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (["$", "`", '"', "\\", "\n"].includes(escaped)) {
|
|
96
|
+
index += 1;
|
|
97
|
+
if (escaped !== "\n")
|
|
98
|
+
word += escaped;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
word += character;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
word += character;
|
|
106
|
+
}
|
|
107
|
+
wordStarted = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (character === "'" || character === '"') {
|
|
111
|
+
quote = character;
|
|
112
|
+
wordStarted = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if ((!powershell && character === "\\") || (powershell && character === "`")) {
|
|
116
|
+
index += 1;
|
|
117
|
+
if (index >= command.length)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (command[index] !== "\n") {
|
|
120
|
+
word += command[index];
|
|
121
|
+
wordStarted = true;
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (POSIX_SHELL_WHITESPACE.test(character)) {
|
|
126
|
+
flushWord();
|
|
127
|
+
if (character === "\n")
|
|
128
|
+
flushSegment("\n");
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (character === "#" && !wordStarted) {
|
|
132
|
+
flushWord();
|
|
133
|
+
while (index + 1 < command.length && command[index + 1] !== "\n")
|
|
134
|
+
index += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (character === ";" || character === "|" || character === "&") {
|
|
138
|
+
const doubled = command[index + 1] === character;
|
|
139
|
+
const operator = (doubled ? character + character : character);
|
|
140
|
+
if ((character === "|" || character === "&") && doubled)
|
|
141
|
+
index += 1;
|
|
142
|
+
flushSegment(operator);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (character === "("
|
|
146
|
+
|| character === ")"
|
|
147
|
+
|| character === "<"
|
|
148
|
+
|| character === ">")
|
|
149
|
+
return undefined;
|
|
150
|
+
word += character;
|
|
151
|
+
wordStarted = true;
|
|
152
|
+
}
|
|
153
|
+
if (quote !== null)
|
|
154
|
+
return undefined;
|
|
155
|
+
flushWord();
|
|
156
|
+
if (words.length > 0)
|
|
157
|
+
segments.push({ words, next: null });
|
|
158
|
+
if (tokenCount > MAX_COMMAND_TOKENS
|
|
159
|
+
|| segments.length > MAX_COMMAND_TOKENS
|
|
160
|
+
|| (platform === "posix" && segments.some((segment) => {
|
|
161
|
+
const parsed = executableCommand(segment.words);
|
|
162
|
+
return parsed !== undefined
|
|
163
|
+
&& POSIX_CONTROL_COMMANDS.has(parsed.executable);
|
|
164
|
+
})))
|
|
165
|
+
return undefined;
|
|
166
|
+
return segments;
|
|
167
|
+
}
|
|
168
|
+
export function trustedExecutableName(command, platform) {
|
|
169
|
+
if (!command.includes("/") && !command.includes("\\")) {
|
|
170
|
+
return command.toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
if (platform === "windows") {
|
|
173
|
+
if (!win32.isAbsolute(command))
|
|
174
|
+
return undefined;
|
|
175
|
+
const normalized = win32.normalize(command);
|
|
176
|
+
const directory = win32.dirname(normalized).toLowerCase();
|
|
177
|
+
if (directory !== "c:\\windows\\system32"
|
|
178
|
+
&& directory !== "c:\\program files\\powershell\\7")
|
|
179
|
+
return undefined;
|
|
180
|
+
return win32.basename(normalized).replace(/\.exe$/iu, "").toLowerCase();
|
|
181
|
+
}
|
|
182
|
+
if (!posix.isAbsolute(command))
|
|
183
|
+
return undefined;
|
|
184
|
+
const normalized = posix.normalize(command);
|
|
185
|
+
if (!TRUSTED_POSIX_EXECUTABLE_DIRECTORIES.has(posix.dirname(normalized))) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return posix.basename(normalized).toLowerCase();
|
|
189
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { isRecord } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
export function interactionToolInput(item) {
|
|
3
|
+
if (isRecord(item.input))
|
|
4
|
+
return item.input;
|
|
5
|
+
if (typeof item.input !== "string")
|
|
6
|
+
return undefined;
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(item.input);
|
|
9
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { InteractionOutput, InteractionRequest } from "../../interaction/types.js";
|
|
2
|
+
import type { HarnessConversationIdentity } from "../../runtime/harness_identity/index.js";
|
|
3
|
+
export type ToolSkillEventReport = {
|
|
4
|
+
event_type: "tool_call" | "skill_use" | "skill_load";
|
|
5
|
+
tool_call_id: string | null;
|
|
6
|
+
exchange_context_hash: string | null;
|
|
7
|
+
capability_id: string;
|
|
8
|
+
observed_name: string;
|
|
9
|
+
skill_version: null;
|
|
10
|
+
evidence_source: "model_tool_call" | "skill_content_injection" | "skill_command_expansion" | "skill_tool_activation" | "skill_file_read";
|
|
11
|
+
ordinal: number;
|
|
12
|
+
};
|
|
13
|
+
export type ToolResultObservationReport = {
|
|
14
|
+
tool_call_id: string;
|
|
15
|
+
exchange_context_hash: string | null;
|
|
16
|
+
ordinal: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function toolSkillEvents(args: {
|
|
19
|
+
routerId: string;
|
|
20
|
+
request: InteractionRequest;
|
|
21
|
+
output: InteractionOutput | undefined;
|
|
22
|
+
harnessConversationIdentity?: HarnessConversationIdentity;
|
|
23
|
+
zdr: boolean;
|
|
24
|
+
}): ToolSkillEventReport[];
|
|
25
|
+
export declare function toolResultObservations(args: {
|
|
26
|
+
routerId: string;
|
|
27
|
+
request: InteractionRequest;
|
|
28
|
+
zdr: boolean;
|
|
29
|
+
}): ToolResultObservationReport[];
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { isRecord, stableStringify } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { interactionPrefixChain } from "../../context/interaction_fingerprint.js";
|
|
3
|
+
import { isProtocolToolCallId } from "../../interaction/tool_identity.js";
|
|
4
|
+
import { anthropicMessagesToInteraction } from "../../protocol/input/anthropic_messages.js";
|
|
5
|
+
import { internalChatCompletionsToInteraction } from "../../protocol/input/chat_completions.js";
|
|
6
|
+
import { anthropicContent } from "../../protocol/output/anthropic_messages_output.js";
|
|
7
|
+
import { assistantMessagePayload } from "../../protocol/output/chat_completions.js";
|
|
8
|
+
import { inputSkillObservations, outputSkillObservations, } from "../skills/observations.js";
|
|
9
|
+
const MAX_TOOL_SKILL_EVENTS = 1000;
|
|
10
|
+
export function toolSkillEvents(args) {
|
|
11
|
+
if (args.zdr)
|
|
12
|
+
return [];
|
|
13
|
+
const outputEvents = (args.output?.items ?? [])
|
|
14
|
+
.flatMap((item, ordinal) => item.type === "tool_call"
|
|
15
|
+
&& isProtocolToolCallId(item.callId)
|
|
16
|
+
? [{ item, ordinal }]
|
|
17
|
+
: [])
|
|
18
|
+
.slice(0, MAX_TOOL_SKILL_EVENTS);
|
|
19
|
+
const explicitSkills = inputSkillObservations({
|
|
20
|
+
...args,
|
|
21
|
+
limit: MAX_TOOL_SKILL_EVENTS - outputEvents.length,
|
|
22
|
+
});
|
|
23
|
+
const outputCallIds = new Set(outputEvents.map(({ item }) => item.callId));
|
|
24
|
+
const automaticSkills = args.output === undefined
|
|
25
|
+
? []
|
|
26
|
+
: outputSkillObservations({
|
|
27
|
+
request: args.request,
|
|
28
|
+
output: args.output,
|
|
29
|
+
harnessConversationIdentity: args.harnessConversationIdentity,
|
|
30
|
+
limit: MAX_TOOL_SKILL_EVENTS,
|
|
31
|
+
}).filter((skill) => skill.toolCallId !== null
|
|
32
|
+
&& outputCallIds.has(skill.toolCallId)).slice(0, MAX_TOOL_SKILL_EVENTS - outputEvents.length - explicitSkills.length);
|
|
33
|
+
if (explicitSkills.length === 0
|
|
34
|
+
&& automaticSkills.length === 0
|
|
35
|
+
&& outputEvents.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
const exchangeContextHash = outputEvents.length === 0 || args.output === undefined
|
|
38
|
+
? null
|
|
39
|
+
: outputExchangeContextHash(args.routerId, args.request, args.output);
|
|
40
|
+
const reportedExplicitSkills = explicitSkills.map((skill, ordinal) => ({
|
|
41
|
+
event_type: skill.eventType,
|
|
42
|
+
tool_call_id: skill.toolCallId,
|
|
43
|
+
exchange_context_hash: null,
|
|
44
|
+
capability_id: skill.capabilityId,
|
|
45
|
+
observed_name: skill.observedName,
|
|
46
|
+
skill_version: null,
|
|
47
|
+
evidence_source: skill.evidenceSource,
|
|
48
|
+
ordinal,
|
|
49
|
+
}));
|
|
50
|
+
const reportedAutomaticSkills = automaticSkills.map((skill, ordinal) => ({
|
|
51
|
+
event_type: skill.eventType,
|
|
52
|
+
tool_call_id: skill.toolCallId,
|
|
53
|
+
exchange_context_hash: exchangeContextHash,
|
|
54
|
+
capability_id: skill.capabilityId,
|
|
55
|
+
observed_name: skill.observedName,
|
|
56
|
+
skill_version: null,
|
|
57
|
+
evidence_source: skill.evidenceSource,
|
|
58
|
+
ordinal: explicitSkills.length + ordinal,
|
|
59
|
+
}));
|
|
60
|
+
const skillCount = explicitSkills.length + automaticSkills.length;
|
|
61
|
+
const reportedOutputEvents = outputEvents.map(({ item, ordinal }) => {
|
|
62
|
+
const observedName = item.namespace
|
|
63
|
+
? `${item.namespace}.${item.name}`
|
|
64
|
+
: item.name;
|
|
65
|
+
return {
|
|
66
|
+
event_type: "tool_call",
|
|
67
|
+
tool_call_id: item.callId,
|
|
68
|
+
exchange_context_hash: exchangeContextHash,
|
|
69
|
+
capability_id: observedName,
|
|
70
|
+
observed_name: observedName,
|
|
71
|
+
skill_version: null,
|
|
72
|
+
evidence_source: "model_tool_call",
|
|
73
|
+
ordinal: skillCount + ordinal,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
return [
|
|
77
|
+
...reportedExplicitSkills,
|
|
78
|
+
...reportedAutomaticSkills,
|
|
79
|
+
...reportedOutputEvents,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
export function toolResultObservations(args) {
|
|
83
|
+
if (args.zdr)
|
|
84
|
+
return [];
|
|
85
|
+
const submitted = newlySubmittedToolResults(args.request);
|
|
86
|
+
if (submitted.results.length === 0)
|
|
87
|
+
return [];
|
|
88
|
+
const requestBeforeResults = {
|
|
89
|
+
...args.request,
|
|
90
|
+
items: args.request.items.slice(0, submitted.contextEnd),
|
|
91
|
+
};
|
|
92
|
+
const exchangeContextHash = interactionPrefixChain(args.routerId, normalizeExchangeRequest(requestBeforeResults), null).at(-1) ??
|
|
93
|
+
null;
|
|
94
|
+
return submitted.results.slice(0, 1000).map(({ item, ordinal }) => ({
|
|
95
|
+
tool_call_id: item.callId,
|
|
96
|
+
exchange_context_hash: exchangeContextHash,
|
|
97
|
+
ordinal,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
function newlySubmittedToolResults(request) {
|
|
101
|
+
if (request.sourceProtocol === "anthropic_messages") {
|
|
102
|
+
return currentAnthropicUserTurnResults(request.items);
|
|
103
|
+
}
|
|
104
|
+
const results = [];
|
|
105
|
+
for (let ordinal = request.items.length - 1; ordinal >= 0; ordinal -= 1) {
|
|
106
|
+
const item = request.items[ordinal];
|
|
107
|
+
if (item.type !== "tool_result")
|
|
108
|
+
break;
|
|
109
|
+
if (isProtocolToolCallId(item.callId))
|
|
110
|
+
results.push({ item, ordinal });
|
|
111
|
+
}
|
|
112
|
+
const ordered = results.reverse();
|
|
113
|
+
return {
|
|
114
|
+
results: ordered,
|
|
115
|
+
contextEnd: ordered[0]?.ordinal ?? request.items.length,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function currentAnthropicUserTurnResults(items) {
|
|
119
|
+
const results = [];
|
|
120
|
+
let contextEnd = 0;
|
|
121
|
+
for (let ordinal = items.length - 1; ordinal >= 0; ordinal -= 1) {
|
|
122
|
+
const item = items[ordinal];
|
|
123
|
+
if (isAssistantOutputItem(item)) {
|
|
124
|
+
contextEnd = ordinal + 1;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
if (item.type === "tool_result" && isProtocolToolCallId(item.callId)) {
|
|
128
|
+
results.push({ item, ordinal });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { results: results.reverse(), contextEnd };
|
|
132
|
+
}
|
|
133
|
+
function isAssistantOutputItem(item) {
|
|
134
|
+
return item.type === "reasoning"
|
|
135
|
+
|| item.type === "tool_call"
|
|
136
|
+
|| item.type === "hosted_tool_call"
|
|
137
|
+
|| (item.type === "message" && item.role === "assistant");
|
|
138
|
+
}
|
|
139
|
+
function outputExchangeContextHash(routerId, request, output) {
|
|
140
|
+
const outputItems = clientVisibleOutputItems(request.sourceProtocol, output);
|
|
141
|
+
const completedRequest = normalizeExchangeRequest({
|
|
142
|
+
...request,
|
|
143
|
+
items: [...request.items, ...outputItems],
|
|
144
|
+
});
|
|
145
|
+
return interactionPrefixChain(routerId, completedRequest, null).at(-1) ?? null;
|
|
146
|
+
}
|
|
147
|
+
function clientVisibleOutputItems(sourceProtocol, output) {
|
|
148
|
+
if (sourceProtocol === "openai_chat") {
|
|
149
|
+
return internalChatCompletionsToInteraction({
|
|
150
|
+
model: "dari/routing",
|
|
151
|
+
messages: [assistantMessagePayload(output)],
|
|
152
|
+
}).items;
|
|
153
|
+
}
|
|
154
|
+
if (sourceProtocol === "anthropic_messages") {
|
|
155
|
+
return anthropicMessagesToInteraction({
|
|
156
|
+
model: "dari/routing",
|
|
157
|
+
max_tokens: 1,
|
|
158
|
+
messages: [{ role: "assistant", content: anthropicContent(output) }],
|
|
159
|
+
}).items;
|
|
160
|
+
}
|
|
161
|
+
return output.items;
|
|
162
|
+
}
|
|
163
|
+
function normalizeExchangeRequest(request) {
|
|
164
|
+
// Claude Code may omit thinking blocks when it submits tool results even
|
|
165
|
+
// though those blocks were present in the preceding Anthropic response.
|
|
166
|
+
const items = request.sourceProtocol === "anthropic_messages"
|
|
167
|
+
? request.items.filter((item) => item.type !== "reasoning")
|
|
168
|
+
: request.items;
|
|
169
|
+
return {
|
|
170
|
+
...request,
|
|
171
|
+
instructions: undefined,
|
|
172
|
+
tools: [],
|
|
173
|
+
items: items.map(normalizeExchangeItem),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// Correlation follows semantic tool history, not wire-only item IDs, message
|
|
177
|
+
// phases, content container shapes, or JSON object insertion order.
|
|
178
|
+
function normalizeExchangeItem(item) {
|
|
179
|
+
if (item.type === "reasoning") {
|
|
180
|
+
const { id: _id, ...rest } = item;
|
|
181
|
+
return rest;
|
|
182
|
+
}
|
|
183
|
+
if (item.type === "message") {
|
|
184
|
+
const { id: _id, phase: _phase, ...rest } = item;
|
|
185
|
+
return {
|
|
186
|
+
...rest,
|
|
187
|
+
...normalizeContent(item.content, item.contentShape, item.role === "assistant"),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (item.type === "tool_call") {
|
|
191
|
+
const { id: _id, ...rest } = item;
|
|
192
|
+
return {
|
|
193
|
+
...rest,
|
|
194
|
+
input: normalizeFunctionInput(item.kind, item.input),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (item.type === "tool_result") {
|
|
198
|
+
const { id: _id, ...rest } = item;
|
|
199
|
+
if (!Array.isArray(item.output))
|
|
200
|
+
return rest;
|
|
201
|
+
const normalized = normalizeContent(item.output, item.outputShape);
|
|
202
|
+
return { ...rest, output: normalized.content, outputShape: normalized.contentShape };
|
|
203
|
+
}
|
|
204
|
+
if (item.type === "hosted_tool_call") {
|
|
205
|
+
const { id: _id, source: _source, payload: { id: _payloadId, status: _payloadStatus, ...payload }, ...rest } = item;
|
|
206
|
+
return {
|
|
207
|
+
...rest,
|
|
208
|
+
id: "",
|
|
209
|
+
payload: item.status ? { ...payload, status: item.status } : payload,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return item;
|
|
213
|
+
}
|
|
214
|
+
function normalizeContent(content, contentShape, collapseEmpty = false) {
|
|
215
|
+
if (collapseEmpty
|
|
216
|
+
&& content.every((part) => part.type === "text" && part.text.length === 0 && !part.annotations?.length)) {
|
|
217
|
+
return { content: [], contentShape: "null" };
|
|
218
|
+
}
|
|
219
|
+
if (contentShape === "null" || contentShape === "absent") {
|
|
220
|
+
return { content, contentShape };
|
|
221
|
+
}
|
|
222
|
+
if (content.every((part) => part.type === "text" && !part.annotations?.length)) {
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text",
|
|
226
|
+
text: content
|
|
227
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
228
|
+
.join(""),
|
|
229
|
+
}],
|
|
230
|
+
contentShape: "string",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return { content, contentShape: "parts" };
|
|
234
|
+
}
|
|
235
|
+
function normalizeFunctionInput(kind, input) {
|
|
236
|
+
if (kind === "custom" || input === undefined)
|
|
237
|
+
return input;
|
|
238
|
+
if (isRecord(input))
|
|
239
|
+
return stableStringify(input);
|
|
240
|
+
try {
|
|
241
|
+
const parsed = JSON.parse(input);
|
|
242
|
+
return isRecord(parsed) ? stableStringify(parsed) : input;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return input;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Provider } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { HttpError } from "../http/errors.js";
|
|
3
|
+
import { type RequestTags } from "../platform/platform.js";
|
|
4
|
+
import type { ProviderKeySource, ResolvedRouter } from "../types.js";
|
|
5
|
+
export type RoutingUsageContext = {
|
|
6
|
+
organizationId: string;
|
|
7
|
+
routerId: string;
|
|
8
|
+
provider: Provider;
|
|
9
|
+
model: string;
|
|
10
|
+
tags: RequestTags;
|
|
11
|
+
startedAt: Date;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Reject requests that need model selection when the organization is not on
|
|
15
|
+
* the Autorouting plan. Without it, Gateway serves only routers with a single
|
|
16
|
+
* model and thinking level, which never invoke the selector.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertAutoroutingAllowed(credentials: ResolvedRouter, selects: boolean): void;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the router offers more than one model/thinking-level pair. A model
|
|
21
|
+
* without listed levels serves every level it supports, so it counts as
|
|
22
|
+
* selecting, matching candidate preparation and the CLI.
|
|
23
|
+
*/
|
|
24
|
+
export declare function routerSelectsModels(credentials: ResolvedRouter): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Reject billable requests before the routing decision once the org's
|
|
27
|
+
* credits are exhausted, unless a personal subscription serves one of the
|
|
28
|
+
* router's models: those owe no credits, so they keep serving and routing
|
|
29
|
+
* narrows to them. Managed and own-key requests stay gated (own-key requests
|
|
30
|
+
* owe the router fee), and refusing before selection keeps an exhausted org
|
|
31
|
+
* from burning selector-model spend.
|
|
32
|
+
*/
|
|
33
|
+
export declare function assertCreditAvailableBeforeRouting(billable: boolean, credentials: ResolvedRouter): void;
|
|
34
|
+
/**
|
|
35
|
+
* Decide whether this routed call bills org credits: returns the usage
|
|
36
|
+
* context for billable managed-key selections and undefined otherwise.
|
|
37
|
+
* Throws 402 when the selection needs a managed key while the org's
|
|
38
|
+
* credits are exhausted.
|
|
39
|
+
*/
|
|
40
|
+
export declare function usageContextFor(args: {
|
|
41
|
+
billable: boolean;
|
|
42
|
+
credentials: ResolvedRouter;
|
|
43
|
+
routerId: string;
|
|
44
|
+
selectedModel: string;
|
|
45
|
+
keySource: ProviderKeySource;
|
|
46
|
+
tags: RequestTags;
|
|
47
|
+
}): RoutingUsageContext | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Bill one managed-key routed call to the org's credits through the
|
|
50
|
+
* platform. Stream billing calls this from the protocol stream's onFinal hook; the
|
|
51
|
+
* non-stream path awaits it before responding. Never throws: the provider
|
|
52
|
+
* spend already happened, so a recording failure must not fail an
|
|
53
|
+
* otherwise-successful completion — it is logged and reported operationally
|
|
54
|
+
* instead.
|
|
55
|
+
*/
|
|
56
|
+
export declare function reportRoutingUsage(context: RoutingUsageContext, message: Record<string, unknown>): Promise<void>;
|
|
57
|
+
export declare function creditExhaustedError(): HttpError;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { HttpError } from "../http/errors.js";
|
|
3
|
+
import { executionProviderForModel, keySourceForModel } from "../models/execution_provider.js";
|
|
4
|
+
import { captureOperationalError } from "../observability/errors.js";
|
|
5
|
+
import { gatewayPlatform } from "../platform/platform.js";
|
|
6
|
+
/**
|
|
7
|
+
* Reject requests that need model selection when the organization is not on
|
|
8
|
+
* the Autorouting plan. Without it, Gateway serves only routers with a single
|
|
9
|
+
* model and thinking level, which never invoke the selector.
|
|
10
|
+
*/
|
|
11
|
+
export function assertAutoroutingAllowed(credentials, selects) {
|
|
12
|
+
if (selects && credentials.autorouting_enabled === false) {
|
|
13
|
+
throw new HttpError(402, "Autorouting requires the Autorouting plan. Subscribe at https://app.dari.dev/usage, or keep one model and thinking level on this router.", "autorouting_plan_required");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether the router offers more than one model/thinking-level pair. A model
|
|
18
|
+
* without listed levels serves every level it supports, so it counts as
|
|
19
|
+
* selecting, matching candidate preparation and the CLI.
|
|
20
|
+
*/
|
|
21
|
+
export function routerSelectsModels(credentials) {
|
|
22
|
+
let pairs = 0;
|
|
23
|
+
for (const model of credentials.enabled_models) {
|
|
24
|
+
const levels = credentials.model_thinking_levels?.[model]?.length ?? 0;
|
|
25
|
+
if (levels === 0)
|
|
26
|
+
return true;
|
|
27
|
+
pairs += levels;
|
|
28
|
+
}
|
|
29
|
+
return pairs > 1;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reject billable requests before the routing decision once the org's
|
|
33
|
+
* credits are exhausted, unless a personal subscription serves one of the
|
|
34
|
+
* router's models: those owe no credits, so they keep serving and routing
|
|
35
|
+
* narrows to them. Managed and own-key requests stay gated (own-key requests
|
|
36
|
+
* owe the router fee), and refusing before selection keeps an exhausted org
|
|
37
|
+
* from burning selector-model spend.
|
|
38
|
+
*/
|
|
39
|
+
export function assertCreditAvailableBeforeRouting(billable, credentials) {
|
|
40
|
+
if (billable &&
|
|
41
|
+
credentials.credit_exhausted &&
|
|
42
|
+
!credentials.enabled_models.some((model) => keySourceForModel(credentials, model) === "subscription")) {
|
|
43
|
+
throw creditExhaustedError();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether this routed call bills org credits: returns the usage
|
|
48
|
+
* context for billable managed-key selections and undefined otherwise.
|
|
49
|
+
* Throws 402 when the selection needs a managed key while the org's
|
|
50
|
+
* credits are exhausted.
|
|
51
|
+
*/
|
|
52
|
+
export function usageContextFor(args) {
|
|
53
|
+
if (!args.billable || args.keySource !== "managed")
|
|
54
|
+
return undefined;
|
|
55
|
+
if (args.credentials.credit_exhausted)
|
|
56
|
+
throw creditExhaustedError();
|
|
57
|
+
return {
|
|
58
|
+
organizationId: args.credentials.organization_id,
|
|
59
|
+
routerId: args.routerId,
|
|
60
|
+
provider: executionProviderForModel(args.credentials, args.selectedModel),
|
|
61
|
+
model: args.selectedModel,
|
|
62
|
+
tags: args.tags,
|
|
63
|
+
startedAt: new Date(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Bill one managed-key routed call to the org's credits through the
|
|
68
|
+
* platform. Stream billing calls this from the protocol stream's onFinal hook; the
|
|
69
|
+
* non-stream path awaits it before responding. Never throws: the provider
|
|
70
|
+
* spend already happened, so a recording failure must not fail an
|
|
71
|
+
* otherwise-successful completion — it is logged and reported operationally
|
|
72
|
+
* instead.
|
|
73
|
+
*/
|
|
74
|
+
export async function reportRoutingUsage(context, message) {
|
|
75
|
+
const payload = usagePayload(context, message);
|
|
76
|
+
if (payload.total_cost_usd === "0") {
|
|
77
|
+
console.error("Routing usage billed at zero cost; model pricing may be missing.", usageLogFields(payload));
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await gatewayPlatform().recordUsage(payload);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
reportUsageFailure(error, payload);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function reportUsageFailure(error, payload) {
|
|
87
|
+
console.error("Routing usage not recorded.", usageLogFields(payload));
|
|
88
|
+
captureOperationalError(error, {
|
|
89
|
+
code: "billing_usage_report_failed",
|
|
90
|
+
routerId: typeof payload.router_id === "string" ? payload.router_id : undefined,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function creditExhaustedError() {
|
|
94
|
+
return new HttpError(402, "Organization credits are exhausted.", "credit_exhausted");
|
|
95
|
+
}
|
|
96
|
+
function usagePayload(context, message) {
|
|
97
|
+
const usage = isRecord(message.usage) ? message.usage : {};
|
|
98
|
+
const cost = isRecord(usage.cost) ? usage.cost : {};
|
|
99
|
+
const responseId = typeof message.responseId === "string" && message.responseId ? message.responseId : undefined;
|
|
100
|
+
return {
|
|
101
|
+
organization_id: context.organizationId,
|
|
102
|
+
router_id: context.routerId,
|
|
103
|
+
provider: context.provider,
|
|
104
|
+
model: context.model,
|
|
105
|
+
provider_request_id: responseId ?? `routing-${randomUUID()}`,
|
|
106
|
+
input_tokens: tokens(usage.input),
|
|
107
|
+
cached_input_tokens: tokens(usage.cacheRead),
|
|
108
|
+
output_tokens: tokens(usage.output),
|
|
109
|
+
usage_estimated: usage.estimated === true,
|
|
110
|
+
input_cost_usd: usd(cost.input),
|
|
111
|
+
cached_input_cost_usd: usd(cost.cacheRead),
|
|
112
|
+
output_cost_usd: usd(cost.output),
|
|
113
|
+
total_cost_usd: usd(cost.total) ?? "0",
|
|
114
|
+
started_at: context.startedAt.toISOString(),
|
|
115
|
+
ended_at: new Date().toISOString(),
|
|
116
|
+
tags: context.tags,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function usageLogFields(payload) {
|
|
120
|
+
return {
|
|
121
|
+
organization_id: payload.organization_id,
|
|
122
|
+
router_id: payload.router_id,
|
|
123
|
+
provider_request_id: payload.provider_request_id,
|
|
124
|
+
model: payload.model,
|
|
125
|
+
total_cost_usd: payload.total_cost_usd,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function tokens(value) {
|
|
129
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
130
|
+
}
|
|
131
|
+
function usd(value) {
|
|
132
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? String(value) : null;
|
|
133
|
+
}
|
|
134
|
+
function isRecord(value) {
|
|
135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
|
+
}
|