@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,212 @@
|
|
|
1
|
+
import { isRecord, } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { HttpError } from "../../http/errors.js";
|
|
3
|
+
import { toolsByWireName } from "../../interaction/tool_identity.js";
|
|
4
|
+
import { decodeUntrustedProviderContinuationState } from "../../interaction/provider_continuation_state.js";
|
|
5
|
+
import { validateChatCompletionsRequest } from "./chat_completions_validation.js";
|
|
6
|
+
export function chatCompletionsToInteraction(request) {
|
|
7
|
+
validateChatCompletionsRequest(request);
|
|
8
|
+
return internalChatCompletionsToInteraction(request);
|
|
9
|
+
}
|
|
10
|
+
// Internal Chat requests are constructed by Dari and do not use the public
|
|
11
|
+
// dari/routing model constraint enforced at ingress.
|
|
12
|
+
export function internalChatCompletionsToInteraction(request) {
|
|
13
|
+
const items = [];
|
|
14
|
+
const toolNames = new Map();
|
|
15
|
+
for (const [messageIndex, message] of (request.messages ?? []).entries()) {
|
|
16
|
+
if (message.role === "tool") {
|
|
17
|
+
items.push({
|
|
18
|
+
type: "tool_result",
|
|
19
|
+
callId: message.tool_call_id ?? "",
|
|
20
|
+
kind: "function",
|
|
21
|
+
name: toolNames.get(message.tool_call_id ?? ""),
|
|
22
|
+
output: messageContent(message.content),
|
|
23
|
+
outputShape: contentShape(message.content),
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const reasoningItems = chatReasoningItems(message, messageIndex);
|
|
28
|
+
items.push(...reasoningItems);
|
|
29
|
+
items.push({
|
|
30
|
+
type: "message",
|
|
31
|
+
role: message.role,
|
|
32
|
+
content: messageContent(message.content),
|
|
33
|
+
contentShape: contentShape(message.content),
|
|
34
|
+
});
|
|
35
|
+
for (const [toolCallIndex, toolCall] of (message.tool_calls ?? []).entries()) {
|
|
36
|
+
const fn = toolCall.function;
|
|
37
|
+
const callId = String(toolCall.id);
|
|
38
|
+
const name = String(fn.name);
|
|
39
|
+
const hostedToolCall = chatHostedToolCall(callId, name, fn.arguments, reasoningItems, messageIndex, toolCallIndex);
|
|
40
|
+
if (hostedToolCall) {
|
|
41
|
+
items.push(hostedToolCall);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
toolNames.set(callId, name);
|
|
45
|
+
items.push({
|
|
46
|
+
type: "tool_call",
|
|
47
|
+
callId,
|
|
48
|
+
kind: "function",
|
|
49
|
+
name,
|
|
50
|
+
input: typeof fn.arguments === "string" || isRecord(fn.arguments) ? fn.arguments : undefined,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const tools = chatTools(request.tools);
|
|
55
|
+
toolsByWireName(tools);
|
|
56
|
+
const responseFormat = toInteractionResponseFormat(request.response_format);
|
|
57
|
+
const reasoningEffort = request.reasoning_effort;
|
|
58
|
+
return {
|
|
59
|
+
sourceProtocol: request.source_protocol ?? "openai_chat",
|
|
60
|
+
model: request.model,
|
|
61
|
+
items,
|
|
62
|
+
tools,
|
|
63
|
+
toolChoice: chatToolChoice(request.tool_choice),
|
|
64
|
+
parallelToolCalls: request.parallel_tool_calls,
|
|
65
|
+
generation: {
|
|
66
|
+
temperature: request.temperature,
|
|
67
|
+
topP: request.top_p,
|
|
68
|
+
maxOutputTokens: request.max_completion_tokens ?? request.max_tokens,
|
|
69
|
+
stop: request.stop,
|
|
70
|
+
},
|
|
71
|
+
reasoning: reasoningEffort === undefined ? undefined : { effort: reasoningEffort },
|
|
72
|
+
responseFormat,
|
|
73
|
+
promptCacheKey: request.prompt_cache_key,
|
|
74
|
+
stream: request.stream === true,
|
|
75
|
+
metadata: request.metadata,
|
|
76
|
+
user: request.user,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function chatHostedToolCall(callId, name, argumentsValue, reasoningItems, messageIndex, toolCallIndex) {
|
|
80
|
+
if (name !== "web_search")
|
|
81
|
+
return null;
|
|
82
|
+
const payload = chatToolArgumentsRecord(argumentsValue);
|
|
83
|
+
if (!payload || payload.type !== "web_search_call" || payload.id !== callId)
|
|
84
|
+
return null;
|
|
85
|
+
const status = hostedToolCallStatus(payload.status, `messages.${messageIndex}.tool_calls.${toolCallIndex}.function.arguments.status`);
|
|
86
|
+
const source = reasoningItems.flatMap((item) => {
|
|
87
|
+
if (item.type !== "reasoning"
|
|
88
|
+
|| item.continuation?.kind !== "openai_reasoning"
|
|
89
|
+
|| !item.continuation.hostedToolCallIds?.includes(callId)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return [item.continuation.source];
|
|
93
|
+
})[0];
|
|
94
|
+
return {
|
|
95
|
+
type: "hosted_tool_call",
|
|
96
|
+
id: callId,
|
|
97
|
+
tool: "web_search",
|
|
98
|
+
providerType: "web_search_call",
|
|
99
|
+
...(status ? { status } : {}),
|
|
100
|
+
payload: { ...payload },
|
|
101
|
+
...(source ? { source } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function chatToolArgumentsRecord(value) {
|
|
105
|
+
if (isRecord(value))
|
|
106
|
+
return value;
|
|
107
|
+
if (typeof value !== "string")
|
|
108
|
+
return null;
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(value);
|
|
111
|
+
return isRecord(parsed) ? parsed : null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function hostedToolCallStatus(value, param) {
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
if (value === "in_progress"
|
|
121
|
+
|| value === "searching"
|
|
122
|
+
|| value === "completed"
|
|
123
|
+
|| value === "incomplete"
|
|
124
|
+
|| value === "failed") {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
throw new HttpError(400, `${param} is not supported.`, "invalid_request_error", param);
|
|
128
|
+
}
|
|
129
|
+
function chatReasoningItems(message, messageIndex) {
|
|
130
|
+
if (message.role !== "assistant")
|
|
131
|
+
return [];
|
|
132
|
+
const readable = [message.reasoning_content, message.reasoning, message.reasoning_text]
|
|
133
|
+
.find((value) => typeof value === "string" && value.length > 0);
|
|
134
|
+
const continuations = (message.reasoning_details ?? []).map((detail, detailIndex) => {
|
|
135
|
+
if (detail.type !== "reasoning.encrypted" || typeof detail.data !== "string") {
|
|
136
|
+
throw invalidChatContinuation(messageIndex, detailIndex);
|
|
137
|
+
}
|
|
138
|
+
const continuation = decodeUntrustedProviderContinuationState(detail.data);
|
|
139
|
+
if (!continuation)
|
|
140
|
+
throw invalidChatContinuation(messageIndex, detailIndex);
|
|
141
|
+
return continuation;
|
|
142
|
+
});
|
|
143
|
+
if (continuations.length === 0) {
|
|
144
|
+
return readable
|
|
145
|
+
? [{ type: "reasoning", summary: [readable], content: [] }]
|
|
146
|
+
: [];
|
|
147
|
+
}
|
|
148
|
+
return continuations.map((continuation, index) => ({
|
|
149
|
+
type: "reasoning",
|
|
150
|
+
summary: index === 0 && readable ? [readable] : [],
|
|
151
|
+
content: [],
|
|
152
|
+
source: continuation.source,
|
|
153
|
+
continuation,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
function invalidChatContinuation(messageIndex, detailIndex) {
|
|
157
|
+
const param = `messages.${messageIndex}.reasoning_details.${detailIndex}.data`;
|
|
158
|
+
return new HttpError(400, `${param} must be a Dari provider continuation envelope; raw provider state has no recoverable provider/model provenance.`, "invalid_provider_continuation_state", param);
|
|
159
|
+
}
|
|
160
|
+
function contentShape(content) {
|
|
161
|
+
if (content === undefined)
|
|
162
|
+
return "absent";
|
|
163
|
+
if (content === null)
|
|
164
|
+
return "null";
|
|
165
|
+
return typeof content === "string" ? "string" : "parts";
|
|
166
|
+
}
|
|
167
|
+
function messageContent(content) {
|
|
168
|
+
if (typeof content === "string")
|
|
169
|
+
return [{ type: "text", text: content }];
|
|
170
|
+
if (!Array.isArray(content))
|
|
171
|
+
return [];
|
|
172
|
+
return content.map((part) => {
|
|
173
|
+
if (part.type === "text")
|
|
174
|
+
return { type: "text", text: String(part.text) };
|
|
175
|
+
if (part.type === "tool_reference") {
|
|
176
|
+
return { type: "text", text: `Referenced tool: ${String(part.tool_name)}` };
|
|
177
|
+
}
|
|
178
|
+
const imageUrl = part.image_url;
|
|
179
|
+
const detail = imageUrl.detail;
|
|
180
|
+
return {
|
|
181
|
+
type: "image",
|
|
182
|
+
url: String(imageUrl.url),
|
|
183
|
+
...(detail === "auto" || detail === "low" || detail === "high" || detail === "original"
|
|
184
|
+
? { detail }
|
|
185
|
+
: {}),
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function chatTools(tools) {
|
|
190
|
+
return (tools ?? []).map((tool) => {
|
|
191
|
+
const fn = tool.function;
|
|
192
|
+
return {
|
|
193
|
+
kind: "function",
|
|
194
|
+
name: String(fn.name),
|
|
195
|
+
description: typeof fn.description === "string" ? fn.description : undefined,
|
|
196
|
+
parameters: isRecord(fn.parameters) ? fn.parameters : undefined,
|
|
197
|
+
strict: typeof fn.strict === "boolean" ? fn.strict : undefined,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function chatToolChoice(choice) {
|
|
202
|
+
if (typeof choice === "string")
|
|
203
|
+
return choice;
|
|
204
|
+
if (!isRecord(choice) || !isRecord(choice.function))
|
|
205
|
+
return undefined;
|
|
206
|
+
return { kind: "function", name: String(choice.function.name) };
|
|
207
|
+
}
|
|
208
|
+
function toInteractionResponseFormat(format) {
|
|
209
|
+
if (format === undefined || format.type !== "json_schema")
|
|
210
|
+
return format;
|
|
211
|
+
return { type: "json_schema", ...format.json_schema };
|
|
212
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { isReasoningEffort, isRecord } from "@mupt-ai/dari-router/policy-engine";
|
|
2
|
+
import { HttpError } from "../../http/errors.js";
|
|
3
|
+
import { parseDataImageUrl } from "../../interaction/image.js";
|
|
4
|
+
import { CANONICAL_ROUTER_MODEL, explicitModelId, unsupportedModelMessage } from "./router_model.js";
|
|
5
|
+
import { isProtocolToolCallId, TOOL_CALL_ID_MAX_LENGTH, } from "../../interaction/tool_identity.js";
|
|
6
|
+
export function validateChatCompletionsRequest(request) {
|
|
7
|
+
if (request.model !== CANONICAL_ROUTER_MODEL && explicitModelId(request.model) === null) {
|
|
8
|
+
throw new HttpError(400, unsupportedModelMessage(), "unsupported_model", "model");
|
|
9
|
+
}
|
|
10
|
+
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
11
|
+
throw new HttpError(400, "messages must include at least one message.", "invalid_request_error", "messages");
|
|
12
|
+
}
|
|
13
|
+
validateMessages(request.messages);
|
|
14
|
+
validateRequestOptions(request);
|
|
15
|
+
const responsesBridge = request.source_protocol === "openai_responses";
|
|
16
|
+
validateTools(request.tools, responsesBridge);
|
|
17
|
+
validateToolChoice(request.tool_choice, responsesBridge);
|
|
18
|
+
validateResponseFormat(request.response_format);
|
|
19
|
+
validateReasoning(request);
|
|
20
|
+
}
|
|
21
|
+
function validateMessages(messages) {
|
|
22
|
+
const pendingToolCallIds = new Set();
|
|
23
|
+
messages.forEach((message, messageIndex) => {
|
|
24
|
+
if (!isRecord(message)) {
|
|
25
|
+
throw new HttpError(400, `messages[${messageIndex}] must be an object.`, "invalid_request_error", `messages.${messageIndex}`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof message.role !== "string" || !["system", "developer", "user", "assistant", "tool"].includes(message.role)) {
|
|
28
|
+
throw new HttpError(400, `Unsupported message role at messages[${messageIndex}].role.`, "invalid_request_error", `messages.${messageIndex}.role`);
|
|
29
|
+
}
|
|
30
|
+
validateMessageContent(message.content, messageIndex, message.role);
|
|
31
|
+
validateMessageToolCalls(message.tool_calls, messageIndex, message.role);
|
|
32
|
+
validateMessageReasoning(message, messageIndex);
|
|
33
|
+
if (message.role === "tool") {
|
|
34
|
+
if (!isProtocolToolCallId(message.tool_call_id)) {
|
|
35
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_call_id is required for tool messages and must not exceed ${TOOL_CALL_ID_MAX_LENGTH} characters.`, "invalid_request_error", `messages.${messageIndex}.tool_call_id`);
|
|
36
|
+
}
|
|
37
|
+
if (!pendingToolCallIds.delete(message.tool_call_id)) {
|
|
38
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_call_id does not match any tool_call.id in the preceding assistant tool-call exchange.`, "invalid_request_error", `messages.${messageIndex}.tool_call_id`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
pendingToolCallIds.clear();
|
|
43
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
44
|
+
for (const toolCall of message.tool_calls) {
|
|
45
|
+
if (isRecord(toolCall) && isProtocolToolCallId(toolCall.id)) {
|
|
46
|
+
pendingToolCallIds.add(toolCall.id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (message.role !== "tool" && message.tool_call_id !== undefined) {
|
|
52
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_call_id is only supported for tool messages.`, "invalid_request_error", `messages.${messageIndex}.tool_call_id`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function validateMessageReasoning(message, messageIndex) {
|
|
57
|
+
const reasoningFields = ["reasoning_content", "reasoning", "reasoning_text"];
|
|
58
|
+
for (const field of reasoningFields) {
|
|
59
|
+
if (message[field] === undefined)
|
|
60
|
+
continue;
|
|
61
|
+
if (message.role !== "assistant" || typeof message[field] !== "string") {
|
|
62
|
+
throw new HttpError(400, `messages[${messageIndex}].${field} is only supported as a string on assistant messages.`, "invalid_request_error", `messages.${messageIndex}.${field}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (message.reasoning_details === undefined)
|
|
66
|
+
return;
|
|
67
|
+
if (message.role !== "assistant" || !Array.isArray(message.reasoning_details)) {
|
|
68
|
+
throw new HttpError(400, `messages[${messageIndex}].reasoning_details must be an array on an assistant message.`, "invalid_request_error", `messages.${messageIndex}.reasoning_details`);
|
|
69
|
+
}
|
|
70
|
+
for (const [detailIndex, detail] of message.reasoning_details.entries()) {
|
|
71
|
+
if (!isRecord(detail) ||
|
|
72
|
+
detail.type !== "reasoning.encrypted" ||
|
|
73
|
+
typeof detail.id !== "string" ||
|
|
74
|
+
!detail.id ||
|
|
75
|
+
typeof detail.data !== "string" ||
|
|
76
|
+
!detail.data) {
|
|
77
|
+
throw new HttpError(400, `messages[${messageIndex}].reasoning_details[${detailIndex}] is not a supported encrypted reasoning detail.`, "invalid_request_error", `messages.${messageIndex}.reasoning_details.${detailIndex}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function validateMessageContent(content, messageIndex, role) {
|
|
82
|
+
if (content === undefined || content === null || typeof content === "string")
|
|
83
|
+
return;
|
|
84
|
+
if (!Array.isArray(content)) {
|
|
85
|
+
throw new HttpError(400, `messages[${messageIndex}].content must be a string, an array, or null.`, "invalid_request_error", `messages.${messageIndex}.content`);
|
|
86
|
+
}
|
|
87
|
+
content.forEach((part, partIndex) => {
|
|
88
|
+
if (!isRecord(part)) {
|
|
89
|
+
throw new HttpError(400, `Unsupported content part at messages[${messageIndex}].content[${partIndex}].`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}`);
|
|
90
|
+
}
|
|
91
|
+
if (part.type === "text") {
|
|
92
|
+
if (typeof part.text !== "string") {
|
|
93
|
+
throw new HttpError(400, `messages[${messageIndex}].content[${partIndex}].text must be a string.`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.text`);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (part.type === "tool_reference") {
|
|
98
|
+
if (role !== "tool") {
|
|
99
|
+
throw new HttpError(400, `messages[${messageIndex}].content[${partIndex}] tool_reference blocks are only supported on tool messages.`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.type`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof part.tool_name !== "string" || !part.tool_name) {
|
|
102
|
+
throw new HttpError(400, `messages[${messageIndex}].content[${partIndex}].tool_name must be a non-empty string.`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.tool_name`);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (part.type === "image_url") {
|
|
107
|
+
const imageUrl = part.image_url;
|
|
108
|
+
const url = isRecord(imageUrl) && typeof imageUrl.url === "string" ? imageUrl.url : "";
|
|
109
|
+
const detail = isRecord(imageUrl) ? imageUrl.detail : undefined;
|
|
110
|
+
if (detail !== undefined &&
|
|
111
|
+
detail !== null &&
|
|
112
|
+
detail !== "auto" &&
|
|
113
|
+
detail !== "low" &&
|
|
114
|
+
detail !== "high" &&
|
|
115
|
+
detail !== "original") {
|
|
116
|
+
throw new HttpError(400, `messages[${messageIndex}].content[${partIndex}].image_url.detail is not supported.`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.image_url.detail`);
|
|
117
|
+
}
|
|
118
|
+
if (!parseDataImageUrl(url)) {
|
|
119
|
+
throw new HttpError(400, `Unsupported image_url at messages[${messageIndex}].content[${partIndex}]; use a base64 data URL.`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.image_url`);
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
throw new HttpError(400, `Unsupported content part at messages[${messageIndex}].content[${partIndex}].`, "invalid_request_error", `messages.${messageIndex}.content.${partIndex}.type`);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function validateMessageToolCalls(toolCalls, messageIndex, role) {
|
|
127
|
+
if (toolCalls === undefined)
|
|
128
|
+
return;
|
|
129
|
+
if (role !== "assistant") {
|
|
130
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls is only supported for assistant messages.`, "invalid_request_error", `messages.${messageIndex}.tool_calls`);
|
|
131
|
+
}
|
|
132
|
+
if (!Array.isArray(toolCalls)) {
|
|
133
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls must be an array.`, "invalid_request_error", `messages.${messageIndex}.tool_calls`);
|
|
134
|
+
}
|
|
135
|
+
toolCalls.forEach((toolCall, toolCallIndex) => {
|
|
136
|
+
if (!isRecord(toolCall)) {
|
|
137
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls[${toolCallIndex}] must be an object.`, "invalid_request_error", `messages.${messageIndex}.tool_calls.${toolCallIndex}`);
|
|
138
|
+
}
|
|
139
|
+
if (toolCall.type !== undefined && toolCall.type !== "function") {
|
|
140
|
+
throw new HttpError(400, `Unsupported tool call type at messages[${messageIndex}].tool_calls[${toolCallIndex}].type.`, "invalid_request_error", `messages.${messageIndex}.tool_calls.${toolCallIndex}.type`);
|
|
141
|
+
}
|
|
142
|
+
if (!isProtocolToolCallId(toolCall.id)) {
|
|
143
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls[${toolCallIndex}].id must be a non-empty string no longer than ${TOOL_CALL_ID_MAX_LENGTH} characters.`, "invalid_request_error", `messages.${messageIndex}.tool_calls.${toolCallIndex}.id`);
|
|
144
|
+
}
|
|
145
|
+
const fn = toolCall.function;
|
|
146
|
+
if (!isRecord(fn) || typeof fn.name !== "string" || fn.name.trim() === "") {
|
|
147
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls[${toolCallIndex}].function.name must be a non-empty string.`, "invalid_request_error", `messages.${messageIndex}.tool_calls.${toolCallIndex}.function.name`);
|
|
148
|
+
}
|
|
149
|
+
if (fn.arguments !== undefined && typeof fn.arguments !== "string" && !isRecord(fn.arguments)) {
|
|
150
|
+
throw new HttpError(400, `messages[${messageIndex}].tool_calls[${toolCallIndex}].function.arguments must be a JSON string or object.`, "invalid_request_error", `messages.${messageIndex}.tool_calls.${toolCallIndex}.function.arguments`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function validateRequestOptions(request) {
|
|
155
|
+
if (request.stream !== undefined && typeof request.stream !== "boolean") {
|
|
156
|
+
throw new HttpError(400, "stream must be a boolean.", "invalid_request_error", "stream");
|
|
157
|
+
}
|
|
158
|
+
if (request.prompt_cache_key !== undefined && typeof request.prompt_cache_key !== "string") {
|
|
159
|
+
throw new HttpError(400, "prompt_cache_key must be a string.", "invalid_request_error", "prompt_cache_key");
|
|
160
|
+
}
|
|
161
|
+
if (request.parallel_tool_calls !== undefined && typeof request.parallel_tool_calls !== "boolean") {
|
|
162
|
+
throw new HttpError(400, "parallel_tool_calls must be a boolean.", "invalid_request_error", "parallel_tool_calls");
|
|
163
|
+
}
|
|
164
|
+
validateNumberOption(request.temperature, "temperature");
|
|
165
|
+
validateNumberOption(request.top_p, "top_p");
|
|
166
|
+
validateNumberOption(request.max_tokens, "max_tokens");
|
|
167
|
+
validateNumberOption(request.max_completion_tokens, "max_completion_tokens");
|
|
168
|
+
if (request.stop !== undefined && typeof request.stop !== "string" && (!Array.isArray(request.stop) || request.stop.some((item) => typeof item !== "string"))) {
|
|
169
|
+
throw new HttpError(400, "stop must be a string or an array of strings.", "invalid_request_error", "stop");
|
|
170
|
+
}
|
|
171
|
+
if (request.metadata !== undefined && !isRecord(request.metadata)) {
|
|
172
|
+
throw new HttpError(400, "metadata must be an object.", "invalid_request_error", "metadata");
|
|
173
|
+
}
|
|
174
|
+
if (request.user !== undefined && typeof request.user !== "string") {
|
|
175
|
+
throw new HttpError(400, "user must be a string.", "invalid_request_error", "user");
|
|
176
|
+
}
|
|
177
|
+
const raw = request;
|
|
178
|
+
if (raw.n !== undefined && raw.n !== 1) {
|
|
179
|
+
throw new HttpError(400, "Only n=1 is supported.", "invalid_request_error", "n");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function validateReasoning(request) {
|
|
183
|
+
const raw = request;
|
|
184
|
+
if (raw.reasoning !== undefined) {
|
|
185
|
+
throw new HttpError(400, "reasoning is not supported; use reasoning_effort.", "invalid_request_error", "reasoning");
|
|
186
|
+
}
|
|
187
|
+
if (request.reasoning_effort !== undefined && !isReasoningEffort(request.reasoning_effort)) {
|
|
188
|
+
throw new HttpError(400, "reasoning_effort is not supported.", "unsupported_reasoning_effort", "reasoning_effort");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function validateNumberOption(value, param) {
|
|
192
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) {
|
|
193
|
+
throw new HttpError(400, `${param} must be a number.`, "invalid_request_error", param);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function validateTools(tools, allowResponsesHostedTools) {
|
|
197
|
+
if (tools === undefined)
|
|
198
|
+
return;
|
|
199
|
+
if (!Array.isArray(tools)) {
|
|
200
|
+
throw new HttpError(400, "tools must be an array.", "invalid_request_error", "tools");
|
|
201
|
+
}
|
|
202
|
+
tools.forEach((tool, index) => {
|
|
203
|
+
if (allowResponsesHostedTools &&
|
|
204
|
+
isRecord(tool) &&
|
|
205
|
+
(tool.type === "web_search" || tool.type === "web_search_preview"))
|
|
206
|
+
return;
|
|
207
|
+
if (!isRecord(tool) || tool.type !== "function" || !isRecord(tool.function)) {
|
|
208
|
+
throw new HttpError(400, `tools[${index}] must be an OpenAI function tool.`, "invalid_request_error", `tools.${index}`);
|
|
209
|
+
}
|
|
210
|
+
const fn = tool.function;
|
|
211
|
+
if (typeof fn.name !== "string" || fn.name.trim() === "") {
|
|
212
|
+
throw new HttpError(400, `tools[${index}].function.name must be a non-empty string.`, "invalid_request_error", `tools.${index}.function.name`);
|
|
213
|
+
}
|
|
214
|
+
if (fn.description !== undefined && typeof fn.description !== "string") {
|
|
215
|
+
throw new HttpError(400, `tools[${index}].function.description must be a string.`, "invalid_request_error", `tools.${index}.function.description`);
|
|
216
|
+
}
|
|
217
|
+
if (fn.parameters !== undefined && !isRecord(fn.parameters)) {
|
|
218
|
+
throw new HttpError(400, `tools[${index}].function.parameters must be a JSON schema object.`, "invalid_request_error", `tools.${index}.function.parameters`);
|
|
219
|
+
}
|
|
220
|
+
if (fn.strict !== undefined && typeof fn.strict !== "boolean") {
|
|
221
|
+
throw new HttpError(400, `tools[${index}].function.strict must be a boolean.`, "invalid_request_error", `tools.${index}.function.strict`);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function validateToolChoice(toolChoice, allowResponsesHostedTools) {
|
|
226
|
+
if (toolChoice === undefined)
|
|
227
|
+
return;
|
|
228
|
+
if (typeof toolChoice === "string") {
|
|
229
|
+
if (["auto", "none", "required", "any"].includes(toolChoice))
|
|
230
|
+
return;
|
|
231
|
+
throw new HttpError(400, "tool_choice must be 'auto', 'none', 'required', 'any', or a function choice object.", "invalid_request_error", "tool_choice");
|
|
232
|
+
}
|
|
233
|
+
if (isRecord(toolChoice) && toolChoice.type === "function" && isRecord(toolChoice.function) && typeof toolChoice.function.name === "string" && toolChoice.function.name.trim() !== "") {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (allowResponsesHostedTools &&
|
|
237
|
+
isRecord(toolChoice) &&
|
|
238
|
+
(toolChoice.type === "web_search" || toolChoice.type === "web_search_preview"))
|
|
239
|
+
return;
|
|
240
|
+
throw new HttpError(400, "tool_choice must be 'auto', 'none', 'required', 'any', or a function choice object.", "invalid_request_error", "tool_choice");
|
|
241
|
+
}
|
|
242
|
+
function validateResponseFormat(format) {
|
|
243
|
+
if (format === undefined)
|
|
244
|
+
return;
|
|
245
|
+
if (!isRecord(format) || typeof format.type !== "string") {
|
|
246
|
+
throw new HttpError(400, "response_format must be an object with a supported type.", "invalid_request_error", "response_format");
|
|
247
|
+
}
|
|
248
|
+
if (format.type === "text" || format.type === "json_object")
|
|
249
|
+
return;
|
|
250
|
+
if (format.type !== "json_schema") {
|
|
251
|
+
throw new HttpError(400, "response_format.type is not supported.", "invalid_request_error", "response_format.type");
|
|
252
|
+
}
|
|
253
|
+
const jsonSchema = format.json_schema;
|
|
254
|
+
if (!isRecord(jsonSchema) || typeof jsonSchema.name !== "string" || jsonSchema.name.trim() === "" || !isRecord(jsonSchema.schema)) {
|
|
255
|
+
throw new HttpError(400, "response_format.json_schema must include name and schema.", "invalid_request_error", "response_format.json_schema");
|
|
256
|
+
}
|
|
257
|
+
}
|