@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,113 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { stableStringify } from "@mupt-ai/dari-router/policy-engine";
|
|
3
|
+
// Bump when canonical fingerprint semantics change so persisted hashes from
|
|
4
|
+
// incompatible contracts remain disjoint.
|
|
5
|
+
const INTERACTION_FINGERPRINT_VERSION = "interaction-v3";
|
|
6
|
+
const OPTION_FINGERPRINT_VERSION = "interaction-options-v1";
|
|
7
|
+
export function interactionFingerprintHeadHash(routerId, request) {
|
|
8
|
+
const systemItems = [];
|
|
9
|
+
for (const item of request.items) {
|
|
10
|
+
if (item.type !== "message"
|
|
11
|
+
|| (item.role !== "system" && item.role !== "developer")) {
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
systemItems.push({
|
|
15
|
+
actor: item.role,
|
|
16
|
+
content: systemFingerprintContent(item.content, item.contentShape),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return sha256(stableStringify({
|
|
20
|
+
version: INTERACTION_FINGERPRINT_VERSION,
|
|
21
|
+
routerId,
|
|
22
|
+
...(request.agentContext === undefined ? {} : { agentContext: {
|
|
23
|
+
requestClass: request.agentContext.requestClass,
|
|
24
|
+
agentId: request.agentContext.agentId ?? null,
|
|
25
|
+
} }),
|
|
26
|
+
instructions: request.instructions ?? null,
|
|
27
|
+
systemItems,
|
|
28
|
+
tools: request.tools,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function systemFingerprintContent(content, shape) {
|
|
32
|
+
const normalized = interactionFingerprintContent(content, shape);
|
|
33
|
+
// Anthropic defines a string system value and one plain text block as
|
|
34
|
+
// equivalent. Preserve that established head identity without flattening
|
|
35
|
+
// structured system content such as images or multiple blocks.
|
|
36
|
+
if (Array.isArray(normalized)
|
|
37
|
+
&& normalized.length === 1
|
|
38
|
+
&& normalized[0]?.type === "text") {
|
|
39
|
+
return normalized[0].text;
|
|
40
|
+
}
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
export function interactionOptionsFingerprints(request) {
|
|
44
|
+
return {
|
|
45
|
+
tool_choice_fp: optionFingerprint("tool-choice", request.toolChoice ?? null),
|
|
46
|
+
response_format_fp: optionFingerprint("response-format", request.responseFormat ?? null),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function canonicalInteractionFingerprintStep(step) {
|
|
50
|
+
return stableStringify({
|
|
51
|
+
actor: step.actor,
|
|
52
|
+
content: step.content,
|
|
53
|
+
calls: step.calls.length > 0 ? step.calls : null,
|
|
54
|
+
resultCallId: step.resultCallId,
|
|
55
|
+
...(step.itemId ? { itemId: step.itemId } : {}),
|
|
56
|
+
...(step.phase ? { phase: step.phase } : {}),
|
|
57
|
+
...(step.reasoning ? { reasoning: step.reasoning } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function interactionFingerprintContent(content, shape) {
|
|
61
|
+
// Preserve the established identity rule that absent and explicit null
|
|
62
|
+
// content are equivalent. Structured parts remain distinct from strings.
|
|
63
|
+
if (shape === "absent" || shape === "null")
|
|
64
|
+
return null;
|
|
65
|
+
if (shape === "string") {
|
|
66
|
+
if (typeof content === "string")
|
|
67
|
+
return content;
|
|
68
|
+
if (!Array.isArray(content))
|
|
69
|
+
return "";
|
|
70
|
+
return content
|
|
71
|
+
.filter((part) => part.type === "text")
|
|
72
|
+
.map((part) => part.text)
|
|
73
|
+
.join("");
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(content))
|
|
76
|
+
return [];
|
|
77
|
+
return content.map((part) => {
|
|
78
|
+
if (part.type === "text")
|
|
79
|
+
return { type: "text", text: part.text };
|
|
80
|
+
if (part.type === "tool_reference") {
|
|
81
|
+
return {
|
|
82
|
+
type: "tool_reference",
|
|
83
|
+
toolName: part.toolName,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
type: "image",
|
|
88
|
+
url: part.url,
|
|
89
|
+
...(part.detail !== undefined ? { detail: part.detail } : {}),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function normalizedAssistantFingerprintContent(content, shape) {
|
|
94
|
+
const normalized = interactionFingerprintContent(content, shape);
|
|
95
|
+
return typeof normalized === "string" && normalized.trim() === ""
|
|
96
|
+
? null
|
|
97
|
+
: normalized;
|
|
98
|
+
}
|
|
99
|
+
export function appendFingerprintContentText(content, text) {
|
|
100
|
+
if (!text)
|
|
101
|
+
return content;
|
|
102
|
+
if (typeof content === "string")
|
|
103
|
+
return content ? `${content}\n${text}` : text;
|
|
104
|
+
if (Array.isArray(content))
|
|
105
|
+
return [...content, { type: "text", text }];
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
108
|
+
function optionFingerprint(kind, value) {
|
|
109
|
+
return sha256(`${OPTION_FINGERPRINT_VERSION}\u0000${kind}\u0000${stableStringify(value)}`);
|
|
110
|
+
}
|
|
111
|
+
function sha256(text) {
|
|
112
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
113
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Monotonic, target-independent position used only to fence asynchronous
|
|
2
|
+
// lease writes. It follows canonical Interaction grouping and deliberately
|
|
3
|
+
// does not serialize selector messages or approximate provider cache blocks.
|
|
4
|
+
export function interactionLeaseFenceDepth(request) {
|
|
5
|
+
const state = {
|
|
6
|
+
depth: 0,
|
|
7
|
+
last: null,
|
|
8
|
+
};
|
|
9
|
+
const startAssistant = (args) => {
|
|
10
|
+
const assistant = {
|
|
11
|
+
kind: "assistant",
|
|
12
|
+
rawBlocks: args.rawBlocks,
|
|
13
|
+
hasContent: args.hasContent ?? false,
|
|
14
|
+
hasReasoning: args.hasReasoning ?? false,
|
|
15
|
+
hasToolCalls: args.hasToolCalls ?? false,
|
|
16
|
+
hostedToolCarrier: args.hostedToolCarrier ?? false,
|
|
17
|
+
};
|
|
18
|
+
state.depth += groupBlockCount(assistant.rawBlocks);
|
|
19
|
+
state.last = assistant;
|
|
20
|
+
return assistant;
|
|
21
|
+
};
|
|
22
|
+
const addAssistantBlock = (assistant) => {
|
|
23
|
+
const before = groupBlockCount(assistant.rawBlocks);
|
|
24
|
+
assistant.rawBlocks += 1;
|
|
25
|
+
state.depth += groupBlockCount(assistant.rawBlocks) - before;
|
|
26
|
+
};
|
|
27
|
+
const addOther = (blocks) => {
|
|
28
|
+
state.depth += groupBlockCount(blocks);
|
|
29
|
+
state.last = { kind: "other" };
|
|
30
|
+
};
|
|
31
|
+
for (const item of request.items) {
|
|
32
|
+
if (item.type === "reasoning") {
|
|
33
|
+
if (state.last?.kind === "assistant"
|
|
34
|
+
&& !state.last.hasContent
|
|
35
|
+
&& !state.last.hostedToolCarrier) {
|
|
36
|
+
state.last.hasReasoning = true;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
startAssistant({ rawBlocks: 0, hasReasoning: true });
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (item.type === "reasoning_configuration") {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (item.type === "tool_result") {
|
|
47
|
+
addOther(1);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (item.type === "tool_call" || item.type === "hosted_tool_call") {
|
|
51
|
+
const assistant = state.last?.kind === "assistant"
|
|
52
|
+
? state.last
|
|
53
|
+
: startAssistant({ rawBlocks: 0 });
|
|
54
|
+
addAssistantBlock(assistant);
|
|
55
|
+
assistant.hasToolCalls = true;
|
|
56
|
+
if (item.type === "hosted_tool_call") {
|
|
57
|
+
assistant.hostedToolCarrier = true;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (item.role === "system" || item.role === "developer") {
|
|
62
|
+
state.last = { kind: "other" };
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const blocks = messageContentBlocks(item);
|
|
66
|
+
const hasContent = blocks > 0;
|
|
67
|
+
if (item.role === "assistant"
|
|
68
|
+
&& state.last?.kind === "assistant"
|
|
69
|
+
&& state.last.hasReasoning
|
|
70
|
+
&& !state.last.hasContent
|
|
71
|
+
&& !state.last.hasToolCalls) {
|
|
72
|
+
const before = groupBlockCount(state.last.rawBlocks);
|
|
73
|
+
state.last.rawBlocks = blocks;
|
|
74
|
+
state.last.hasContent = hasContent;
|
|
75
|
+
state.depth += groupBlockCount(state.last.rawBlocks) - before;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (item.role === "assistant") {
|
|
79
|
+
startAssistant({ rawBlocks: blocks, hasContent });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
addOther(blocks);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return state.depth;
|
|
86
|
+
}
|
|
87
|
+
function messageContentBlocks(message) {
|
|
88
|
+
if (message.contentShape === "absent" || message.contentShape === "null") {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
if (message.contentShape === "string") {
|
|
92
|
+
return stringContent(message.content).length > 0 ? 1 : 0;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(message.content))
|
|
95
|
+
return message.content.length;
|
|
96
|
+
return message.content === null ? 0 : 1;
|
|
97
|
+
}
|
|
98
|
+
function stringContent(content) {
|
|
99
|
+
if (typeof content === "string")
|
|
100
|
+
return content;
|
|
101
|
+
if (!Array.isArray(content))
|
|
102
|
+
return "";
|
|
103
|
+
return content
|
|
104
|
+
.filter((part) => part.type === "text")
|
|
105
|
+
.map((part) => part.text)
|
|
106
|
+
.join("");
|
|
107
|
+
}
|
|
108
|
+
function groupBlockCount(rawBlocks) {
|
|
109
|
+
return Math.max(1, rawBlocks);
|
|
110
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ChatMessage, type RouterPrefixHit, type RoutingCandidate, type SelectorLeaseHistoryEntry } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
export declare function leaseHistoryFromPrefixHits(args: {
|
|
3
|
+
prefixHits: readonly RouterPrefixHit[];
|
|
4
|
+
conversationId: string;
|
|
5
|
+
candidates: readonly RoutingCandidate[];
|
|
6
|
+
messages: readonly ChatMessage[];
|
|
7
|
+
}): SelectorLeaseHistoryEntry[];
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { isReasoningEffort, isRecord, routingCandidateKey, } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
const MAX_HISTORY_LEASES = 3;
|
|
3
|
+
const MAX_OBSERVATION_CHARS = 3_000;
|
|
4
|
+
const TEST_COMMAND = /(?:^|\s)(?:bun|npm|pnpm|yarn|uv|pytest|cargo|go|make|npx|bash)\b[^\n]*(?:test|check|lint|typecheck|vet|build)/i;
|
|
5
|
+
export function leaseHistoryFromPrefixHits(args) {
|
|
6
|
+
const candidateKeys = new Set(args.candidates.map(routingCandidateKey));
|
|
7
|
+
const hits = deduplicatedHits(args.prefixHits)
|
|
8
|
+
.filter((hit) => hit.conversation_id === args.conversationId)
|
|
9
|
+
.sort((left, right) => left.message_depth - right.message_depth);
|
|
10
|
+
const segments = [];
|
|
11
|
+
for (const hit of hits) {
|
|
12
|
+
const remaining = hit.lease_turns_remaining;
|
|
13
|
+
const effort = hit.next_reasoning_effort;
|
|
14
|
+
if (typeof remaining !== "number" ||
|
|
15
|
+
!Number.isInteger(remaining) ||
|
|
16
|
+
remaining < 0 ||
|
|
17
|
+
!hit.next_model ||
|
|
18
|
+
!isReasoningEffort(effort)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const candidate = { model: hit.next_model, reasoningEffort: effort };
|
|
22
|
+
if (!candidateKeys.has(routingCandidateKey(candidate)))
|
|
23
|
+
continue;
|
|
24
|
+
const current = segments.at(-1);
|
|
25
|
+
if (current !== undefined &&
|
|
26
|
+
routingCandidateKey(current.candidate) === routingCandidateKey(candidate) &&
|
|
27
|
+
remaining < current.lastRemaining) {
|
|
28
|
+
current.lastRemaining = remaining;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
segments.push({
|
|
32
|
+
candidate,
|
|
33
|
+
requestedTurns: remaining + 1,
|
|
34
|
+
lastRemaining: remaining,
|
|
35
|
+
rationale: hit.reason ?? "",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const recent = segments.slice(-MAX_HISTORY_LEASES);
|
|
39
|
+
const turns = conversationTurns(args.messages);
|
|
40
|
+
let turnCursor = turns.length;
|
|
41
|
+
const history = [...recent].reverse().map((segment) => {
|
|
42
|
+
const completedTurns = Math.max(0, Math.min(segment.requestedTurns, segment.requestedTurns - segment.lastRemaining));
|
|
43
|
+
const start = Math.max(0, turnCursor - completedTurns);
|
|
44
|
+
const observation = turns.slice(start, turnCursor);
|
|
45
|
+
turnCursor = start;
|
|
46
|
+
return observationForSegment(segment, completedTurns, observation);
|
|
47
|
+
});
|
|
48
|
+
return history.reverse();
|
|
49
|
+
}
|
|
50
|
+
function observationForSegment(segment, completedTurns, turns) {
|
|
51
|
+
const calls = turns.flatMap((turn) => turn.assistant.tool_calls ?? []);
|
|
52
|
+
const results = turns.flatMap((turn) => turn.toolResults);
|
|
53
|
+
const lastCall = calls.at(-1);
|
|
54
|
+
const callId = typeof lastCall?.["id"] === "string" ? lastCall["id"] : null;
|
|
55
|
+
const lastResult = callId === null
|
|
56
|
+
? undefined
|
|
57
|
+
: [...results].reverse().find((result) => result.tool_call_id === callId);
|
|
58
|
+
const testCall = [...calls].reverse().find((call) => {
|
|
59
|
+
const parsed = toolCall(call);
|
|
60
|
+
return parsed?.name === "bash" && TEST_COMMAND.test(command(parsed.arguments));
|
|
61
|
+
});
|
|
62
|
+
const parsedLastCall = lastCall === undefined ? null : toolCall(lastCall);
|
|
63
|
+
const parsedTestCall = testCall === undefined ? null : toolCall(testCall);
|
|
64
|
+
const testCallId = typeof testCall?.["id"] === "string" ? testCall["id"] : null;
|
|
65
|
+
const testResult = testCallId === null
|
|
66
|
+
? undefined
|
|
67
|
+
: [...results].reverse().find((result) => result.tool_call_id === testCallId);
|
|
68
|
+
const lastAssistant = turns.at(-1)?.assistant;
|
|
69
|
+
const thought = lastAssistant?.reasoning_content
|
|
70
|
+
?? lastAssistant?.reasoning_text
|
|
71
|
+
?? lastAssistant?.reasoning
|
|
72
|
+
?? null;
|
|
73
|
+
return {
|
|
74
|
+
candidate: {
|
|
75
|
+
model: segment.candidate.model,
|
|
76
|
+
thinking_level: segment.candidate.reasoningEffort,
|
|
77
|
+
},
|
|
78
|
+
requested_turns: segment.requestedTurns,
|
|
79
|
+
completed_turns: completedTurns,
|
|
80
|
+
rationale: truncate(segment.rationale),
|
|
81
|
+
last_agent_thought: thought === null ? null : truncate(thought),
|
|
82
|
+
last_tool_call: parsedLastCall === null
|
|
83
|
+
? null
|
|
84
|
+
: { name: parsedLastCall.name, arguments: compactJson(parsedLastCall.arguments) },
|
|
85
|
+
last_tool_result: lastResult === undefined ? null : compactJson(lastResult.content ?? null),
|
|
86
|
+
// Generic Chat Completions tool results do not carry a portable error bit
|
|
87
|
+
// or a trustworthy changed-file count.
|
|
88
|
+
tool_errors: null,
|
|
89
|
+
files_changed: null,
|
|
90
|
+
tests: parsedTestCall === null
|
|
91
|
+
? null
|
|
92
|
+
: {
|
|
93
|
+
command: truncate(command(parsedTestCall.arguments)),
|
|
94
|
+
status: "unknown",
|
|
95
|
+
result: testResult === undefined ? null : compactJson(testResult.content ?? null),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function deduplicatedHits(hits) {
|
|
100
|
+
const byState = new Map();
|
|
101
|
+
for (const hit of hits) {
|
|
102
|
+
const key = [
|
|
103
|
+
hit.conversation_id,
|
|
104
|
+
hit.message_depth,
|
|
105
|
+
hit.next_model ?? "",
|
|
106
|
+
hit.next_reasoning_effort ?? "",
|
|
107
|
+
hit.lease_turns_remaining ?? "",
|
|
108
|
+
].join("\u0000");
|
|
109
|
+
const existing = byState.get(key);
|
|
110
|
+
if (existing === undefined || existing.updated_at < hit.updated_at)
|
|
111
|
+
byState.set(key, hit);
|
|
112
|
+
}
|
|
113
|
+
return [...byState.values()];
|
|
114
|
+
}
|
|
115
|
+
function conversationTurns(messages) {
|
|
116
|
+
const turns = [];
|
|
117
|
+
let current;
|
|
118
|
+
for (const message of messages) {
|
|
119
|
+
if (message.role === "assistant") {
|
|
120
|
+
current = { assistant: message, toolResults: [] };
|
|
121
|
+
turns.push(current);
|
|
122
|
+
}
|
|
123
|
+
else if (message.role === "tool" && current !== undefined) {
|
|
124
|
+
current.toolResults.push(message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return turns;
|
|
128
|
+
}
|
|
129
|
+
function toolCall(value) {
|
|
130
|
+
const fn = value["function"];
|
|
131
|
+
if (!isRecord(fn) || typeof fn.name !== "string")
|
|
132
|
+
return null;
|
|
133
|
+
const rawArguments = fn.arguments ?? {};
|
|
134
|
+
if (typeof rawArguments !== "string") {
|
|
135
|
+
return { name: fn.name, arguments: compactJson(rawArguments) };
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return { name: fn.name, arguments: JSON.parse(rawArguments) };
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return { name: fn.name, arguments: rawArguments };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function command(value) {
|
|
145
|
+
return isRecord(value) && typeof value.command === "string" ? value.command : "";
|
|
146
|
+
}
|
|
147
|
+
function compactJson(value) {
|
|
148
|
+
const serialized = JSON.stringify(value) ?? "null";
|
|
149
|
+
if (serialized.length <= MAX_OBSERVATION_CHARS) {
|
|
150
|
+
return JSON.parse(serialized);
|
|
151
|
+
}
|
|
152
|
+
return `${serialized.slice(0, MAX_OBSERVATION_CHARS)}…`;
|
|
153
|
+
}
|
|
154
|
+
function truncate(value) {
|
|
155
|
+
return value.length <= MAX_OBSERVATION_CHARS
|
|
156
|
+
? value
|
|
157
|
+
: `${value.slice(0, MAX_OBSERVATION_CHARS)}…`;
|
|
158
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModelPricing, PricingLookup } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import type { ResolvedRouter } from "../types.js";
|
|
3
|
+
export declare function pricingLookupForRouter(credentials: ResolvedRouter): PricingLookup;
|
|
4
|
+
export declare function modelPricingForRouter(credentials: ResolvedRouter, model: string): ModelPricing | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function pricingLookupForRouter(credentials) {
|
|
2
|
+
return (model) => modelPricingForRouter(credentials, model);
|
|
3
|
+
}
|
|
4
|
+
export function modelPricingForRouter(credentials, model) {
|
|
5
|
+
const price = credentials.model_prices?.[model];
|
|
6
|
+
if (!price || typeof price.input !== "number" || typeof price.output !== "number")
|
|
7
|
+
return null;
|
|
8
|
+
// Null cached rates mean the provider publishes none: bill cached prompt
|
|
9
|
+
// buckets at the input rate, never for free.
|
|
10
|
+
return {
|
|
11
|
+
input: price.input,
|
|
12
|
+
output: price.output,
|
|
13
|
+
cacheRead: price.cached_input ?? price.input,
|
|
14
|
+
cacheWrite: price.cache_write ?? price.input,
|
|
15
|
+
...(price.tiers?.length ? { tiers: price.tiers.map((tier) => ({
|
|
16
|
+
inputTokensAbove: tier.input_tokens_above,
|
|
17
|
+
input: tier.input,
|
|
18
|
+
output: tier.output,
|
|
19
|
+
cacheRead: tier.cached_input ?? tier.input,
|
|
20
|
+
cacheWrite: tier.cache_write ?? tier.input,
|
|
21
|
+
})) } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CandidatePromptAccounting } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import type { InteractionRequest, ProviderIdentity } from "../interaction/types.js";
|
|
3
|
+
import type { InteractionPrefixContext } from "./interaction_fingerprint.js";
|
|
4
|
+
type PromptMetrics = {
|
|
5
|
+
blockDepth: number;
|
|
6
|
+
serializedChars: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function providerPromptAccounting(args: {
|
|
9
|
+
request: InteractionRequest;
|
|
10
|
+
prefix: InteractionPrefixContext;
|
|
11
|
+
targetsByModel: ReadonlyMap<string, ProviderIdentity>;
|
|
12
|
+
}): CandidatePromptAccounting;
|
|
13
|
+
export declare function promptMetrics(request: InteractionRequest, target: ProviderIdentity): PromptMetrics;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { isRecord, stableStringify, } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { toolsByWireName } from "../interaction/tool_identity.js";
|
|
3
|
+
import { buildContext } from "../providers/pi_context.js";
|
|
4
|
+
import { isOpenAIResponsesApi } from "../providers/model_capabilities.js";
|
|
5
|
+
const ACCOUNTING_IMAGE_URL = "data:image/png;base64,AA==";
|
|
6
|
+
const IMAGE_ESTIMATED_TOKENS = {
|
|
7
|
+
low: 512,
|
|
8
|
+
auto: 1024,
|
|
9
|
+
high: 2048,
|
|
10
|
+
original: 2048,
|
|
11
|
+
};
|
|
12
|
+
const CHARS_PER_ESTIMATED_TOKEN = 4;
|
|
13
|
+
// Cost and cache-lookback estimates are candidate-specific because compatible
|
|
14
|
+
// continuation state and provider serialization can change what each model
|
|
15
|
+
// sees.
|
|
16
|
+
export function providerPromptAccounting(args) {
|
|
17
|
+
const fullByModel = new Map();
|
|
18
|
+
const prefixCharsByModelAndItem = new Map();
|
|
19
|
+
const targetFor = (model) => {
|
|
20
|
+
const target = args.targetsByModel.get(model);
|
|
21
|
+
if (!target) {
|
|
22
|
+
throw new Error(`missing provider target for ${model}`);
|
|
23
|
+
}
|
|
24
|
+
return target;
|
|
25
|
+
};
|
|
26
|
+
const fullMetrics = (model) => {
|
|
27
|
+
const cached = fullByModel.get(model);
|
|
28
|
+
if (cached)
|
|
29
|
+
return cached;
|
|
30
|
+
const target = targetFor(model);
|
|
31
|
+
const metrics = promptMetrics(args.request, target);
|
|
32
|
+
fullByModel.set(model, metrics);
|
|
33
|
+
return metrics;
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
anchorSemantics: "stored_input",
|
|
37
|
+
providerBlockDepthFor: (model) => fullMetrics(model).blockDepth,
|
|
38
|
+
promptEstimateFor: (model, anchor) => {
|
|
39
|
+
const full = fullMetrics(model);
|
|
40
|
+
if (!anchor) {
|
|
41
|
+
return {
|
|
42
|
+
chars: full.serializedChars,
|
|
43
|
+
reusesStoredPromptTokens: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const itemIndex = args.prefix.itemIndexByHashByModel
|
|
47
|
+
.get(model)
|
|
48
|
+
?.get(anchor.hash);
|
|
49
|
+
if (itemIndex === undefined) {
|
|
50
|
+
return {
|
|
51
|
+
chars: full.serializedChars,
|
|
52
|
+
reusesStoredPromptTokens: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const key = `${model}\u0000${itemIndex}`;
|
|
56
|
+
let prefixChars = prefixCharsByModelAndItem.get(key);
|
|
57
|
+
if (prefixChars === undefined) {
|
|
58
|
+
prefixChars = promptMetrics({
|
|
59
|
+
...args.request,
|
|
60
|
+
items: args.request.items.slice(0, itemIndex + 1),
|
|
61
|
+
}, targetFor(model)).serializedChars;
|
|
62
|
+
prefixCharsByModelAndItem.set(key, prefixChars);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
chars: Math.max(0, full.serializedChars - prefixChars),
|
|
66
|
+
reusesStoredPromptTokens: true,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function promptMetrics(request, target) {
|
|
72
|
+
const accountingInput = promptAccountingInput(request);
|
|
73
|
+
const context = buildContext(accountingInput.request, target);
|
|
74
|
+
const messages = context.messages.flatMap((message) => {
|
|
75
|
+
const visible = providerVisibleMessage(message);
|
|
76
|
+
return visible ? [visible] : [];
|
|
77
|
+
});
|
|
78
|
+
const tools = providerVisibleTools(request, context.tools, target);
|
|
79
|
+
const prompt = {
|
|
80
|
+
...(context.systemPrompt !== undefined
|
|
81
|
+
? { systemPrompt: context.systemPrompt }
|
|
82
|
+
: {}),
|
|
83
|
+
...(tools.length ? { tools } : {}),
|
|
84
|
+
messages,
|
|
85
|
+
...(request.responseFormat !== undefined
|
|
86
|
+
? { responseFormat: request.responseFormat }
|
|
87
|
+
: {}),
|
|
88
|
+
...(request.toolChoice !== undefined
|
|
89
|
+
? { toolChoice: request.toolChoice }
|
|
90
|
+
: {}),
|
|
91
|
+
...(request.parallelToolCalls !== undefined
|
|
92
|
+
? { parallelToolCalls: request.parallelToolCalls }
|
|
93
|
+
: {}),
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
blockDepth: messages.reduce((total, message) => total + providerVisibleBlockCount(message), 0),
|
|
97
|
+
serializedChars: stableStringify(prompt).length + accountingInput.imageEstimatedChars,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function providerVisibleMessage(value) {
|
|
101
|
+
if (!isRecord(value) || typeof value.role !== "string")
|
|
102
|
+
return null;
|
|
103
|
+
if (value.role === "assistant") {
|
|
104
|
+
return {
|
|
105
|
+
role: value.role,
|
|
106
|
+
content: sanitizeProviderContent(Array.isArray(value.content) ? value.content : []),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (value.role === "toolResult") {
|
|
110
|
+
return {
|
|
111
|
+
role: value.role,
|
|
112
|
+
toolCallId: value.toolCallId ?? null,
|
|
113
|
+
toolName: value.toolName ?? null,
|
|
114
|
+
content: sanitizeProviderContent(Array.isArray(value.content) ? value.content : []),
|
|
115
|
+
isError: value.isError === true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
role: value.role,
|
|
120
|
+
content: sanitizeProviderContent(value.content ?? ""),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function sanitizeProviderContent(value) {
|
|
124
|
+
if (!Array.isArray(value))
|
|
125
|
+
return value;
|
|
126
|
+
return value.map((part) => {
|
|
127
|
+
if (!isRecord(part))
|
|
128
|
+
return part;
|
|
129
|
+
if (part.type === "providerItem" && isRecord(part.item)) {
|
|
130
|
+
return part.item;
|
|
131
|
+
}
|
|
132
|
+
if (part.type === "image"
|
|
133
|
+
&& typeof part.mimeType === "string"
|
|
134
|
+
&& typeof part.data === "string") {
|
|
135
|
+
return { ...part, data: "<image-data>" };
|
|
136
|
+
}
|
|
137
|
+
return part;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function providerVisibleBlockCount(message) {
|
|
141
|
+
if (message.role === "toolResult")
|
|
142
|
+
return 1;
|
|
143
|
+
const content = message.content;
|
|
144
|
+
return Math.max(1, Array.isArray(content) ? content.length : 1);
|
|
145
|
+
}
|
|
146
|
+
function providerVisibleTools(request, contextTools, target) {
|
|
147
|
+
const definitions = toolsByWireName(request.tools);
|
|
148
|
+
const tools = (contextTools ?? []).map((tool) => {
|
|
149
|
+
if (typeof tool.name !== "string")
|
|
150
|
+
return tool;
|
|
151
|
+
const definition = definitions.get(tool.name);
|
|
152
|
+
return definition?.kind !== "function"
|
|
153
|
+
|| definition.strict === undefined
|
|
154
|
+
? tool
|
|
155
|
+
: { ...tool, strict: definition.strict };
|
|
156
|
+
});
|
|
157
|
+
if (!isOpenAIResponsesApi(target.api))
|
|
158
|
+
return tools;
|
|
159
|
+
return [
|
|
160
|
+
...tools,
|
|
161
|
+
...request.tools.flatMap((tool) => tool.kind === "hosted" ? [tool.config] : []),
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
function promptAccountingInput(request) {
|
|
165
|
+
let imageEstimatedChars = 0;
|
|
166
|
+
const sanitizeContent = (parts) => parts.map((part) => {
|
|
167
|
+
if (part.type !== "image")
|
|
168
|
+
return part;
|
|
169
|
+
const detail = part.detail ?? "auto";
|
|
170
|
+
imageEstimatedChars +=
|
|
171
|
+
IMAGE_ESTIMATED_TOKENS[detail] * CHARS_PER_ESTIMATED_TOKEN;
|
|
172
|
+
return { ...part, url: ACCOUNTING_IMAGE_URL };
|
|
173
|
+
});
|
|
174
|
+
const items = request.items.map((item) => {
|
|
175
|
+
if (item.type === "message") {
|
|
176
|
+
return { ...item, content: sanitizeContent(item.content) };
|
|
177
|
+
}
|
|
178
|
+
if (item.type === "tool_result" && Array.isArray(item.output)) {
|
|
179
|
+
return { ...item, output: sanitizeContent(item.output) };
|
|
180
|
+
}
|
|
181
|
+
return item;
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
request: { ...request, items },
|
|
185
|
+
imageEstimatedChars,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ActiveLease, type PendingLease, type CandidateCostEstimate, type ChainHits, type PrefixHit, type ChatMessage, type PreviousDecision, type PricingLookup, type SelectorLeaseHistoryEntry, type StrategyCandidateResolution } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { type InteractionRequest, type ProviderIdentity } from "../interaction/types.js";
|
|
3
|
+
import { type SelectorImage } from "../strategies/selector_projection.js";
|
|
4
|
+
import type { ResolvedRouter } from "../types.js";
|
|
5
|
+
import { type InteractionPrefixContext } from "./interaction_fingerprint.js";
|
|
6
|
+
export type ImageCapabilityPrunedModel = {
|
|
7
|
+
model: string;
|
|
8
|
+
reason: "image_input_unsupported";
|
|
9
|
+
};
|
|
10
|
+
export type RoutingContext = {
|
|
11
|
+
leaseDepth: number;
|
|
12
|
+
hits: ChainHits;
|
|
13
|
+
reportingCostEstimates: CandidateCostEstimate[];
|
|
14
|
+
previousDecision?: PreviousDecision;
|
|
15
|
+
activeLease?: ActiveLease;
|
|
16
|
+
pendingLease?: PendingLease;
|
|
17
|
+
pricing: PricingLookup;
|
|
18
|
+
conversationId: string;
|
|
19
|
+
isNewConversation: boolean;
|
|
20
|
+
identityMatchDepth: number | null;
|
|
21
|
+
identityHit?: PrefixHit;
|
|
22
|
+
zdr: boolean;
|
|
23
|
+
candidateResolution: StrategyCandidateResolution;
|
|
24
|
+
imageCapabilityPrunedModels?: ImageCapabilityPrunedModel[];
|
|
25
|
+
prefix: InteractionPrefixContext;
|
|
26
|
+
targetsByModel: ReadonlyMap<string, ProviderIdentity>;
|
|
27
|
+
selectorMessages: ChatMessage[];
|
|
28
|
+
selectorImages: SelectorImage[];
|
|
29
|
+
leaseHistory?: SelectorLeaseHistoryEntry[];
|
|
30
|
+
providerBlockDepthFor: (model: string) => number;
|
|
31
|
+
};
|
|
32
|
+
export declare function derivePrefixLookupHashes(routerId: string, interaction: InteractionRequest): string[];
|
|
33
|
+
export declare function buildRoutingContext(args: {
|
|
34
|
+
routerId: string;
|
|
35
|
+
interaction: InteractionRequest;
|
|
36
|
+
credentials: ResolvedRouter;
|
|
37
|
+
previousDecision?: PreviousDecision;
|
|
38
|
+
explicitConversationId?: string;
|
|
39
|
+
receivedAt?: Date;
|
|
40
|
+
}): Promise<RoutingContext>;
|