@iislee/opencodex 2.31.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
} from "../providers/fastwire";
|
|
27
27
|
import { openaiChatCompletionsUrl } from "./openai-chat-url";
|
|
28
28
|
import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema";
|
|
29
|
+
import {
|
|
30
|
+
isXaiSchemaTarget,
|
|
31
|
+
lookupLocalJsonPointer,
|
|
32
|
+
normalizeXaiToolParameters,
|
|
33
|
+
} from "./xai-tool-schema";
|
|
29
34
|
import {
|
|
30
35
|
isTranslatorBudgetExceededError,
|
|
31
36
|
retainTranslatedEventBatch,
|
|
@@ -922,11 +927,50 @@ function shouldSanitizeZenToolParameters(provider: OcxProviderConfig): boolean {
|
|
|
922
927
|
|| baseUrl === "https://opencode.ai/zen/go/v1";
|
|
923
928
|
}
|
|
924
929
|
|
|
925
|
-
|
|
930
|
+
/** Azure Model Router (and Gemini-in-the-pool) 400s Codex MCP schemas whose root is a union. */
|
|
931
|
+
const AZURE_CHAT_FORBIDDEN_ROOT_KEYS = ["oneOf", "anyOf", "allOf", "enum", "const", "not"] as const;
|
|
932
|
+
|
|
933
|
+
function isAzureOpenAiChatTarget(provider: OcxProviderConfig): boolean {
|
|
934
|
+
try {
|
|
935
|
+
const host = new URL(provider.baseUrl).hostname.toLowerCase();
|
|
936
|
+
return host.endsWith(".openai.azure.com")
|
|
937
|
+
|| host.endsWith(".cognitiveservices.azure.com")
|
|
938
|
+
|| host.endsWith(".services.ai.azure.com")
|
|
939
|
+
|| host.endsWith(".ai.azure.com");
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Azure Foundry Model Router validates every function schema against the strictest model in
|
|
947
|
+
* the pool (Gemini-shaped): root must be {type:"object"} with no oneOf/anyOf/allOf/enum/
|
|
948
|
+
* const/not. Codex App MCP tools such as mcp__codex_app__automation_update ship a root
|
|
949
|
+
* union, which 400s the whole turn. Flatten like Zen, then strip leftover forbidden keys.
|
|
950
|
+
*/
|
|
951
|
+
function sanitizeAzureChatToolParameters(parameters: unknown): Record<string, unknown> {
|
|
952
|
+
const root = ensureZenRootObjectSchema(parameters);
|
|
953
|
+
for (const key of AZURE_CHAT_FORBIDDEN_ROOT_KEYS) delete root[key];
|
|
954
|
+
root.type = "object";
|
|
955
|
+
if (!root.properties || typeof root.properties !== "object" || Array.isArray(root.properties)) {
|
|
956
|
+
root.properties = {};
|
|
957
|
+
}
|
|
958
|
+
return root;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Moonshot validates function schemas against a draft-07 reading of `$ref`, where the
|
|
962
|
+
// keyword stands alone and siblings are ignored. It rejects the whole request rather
|
|
963
|
+
// than ignoring them: "not a valid moonshot flavored json schema ... when using $ref,
|
|
964
|
+
// type should be defined in the referenced schema instead of the parent schema".
|
|
965
|
+
const MOONSHOT_SCHEMA_HOSTNAMES = new Set([
|
|
966
|
+
"api.kimi.com",
|
|
967
|
+
"api.moonshot.ai",
|
|
968
|
+
"api.moonshot.cn",
|
|
969
|
+
]);
|
|
970
|
+
|
|
971
|
+
function isMoonshotSchemaTarget(provider: OcxProviderConfig): boolean {
|
|
926
972
|
try {
|
|
927
|
-
|
|
928
|
-
// 400s on a root oneOf/anyOf, so flattening/omitting is scoped to that host.
|
|
929
|
-
return new URL(provider.baseUrl).hostname === "cli-chat-proxy.grok.com";
|
|
973
|
+
return MOONSHOT_SCHEMA_HOSTNAMES.has(new URL(provider.baseUrl).hostname);
|
|
930
974
|
} catch {
|
|
931
975
|
return false;
|
|
932
976
|
}
|
|
@@ -964,263 +1008,248 @@ function isXaiObjectSchema(value: unknown): value is Record<string, unknown> {
|
|
|
964
1008
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
965
1009
|
}
|
|
966
1010
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1011
|
+
/**
|
|
1012
|
+
* JSON Schema 2020-12 makes `$ref` an in-place applicator: siblings stay in force and are
|
|
1013
|
+
* combined with the referenced schema. Moonshot enforces the older draft-07 reading where
|
|
1014
|
+
* `$ref` must stand alone, and 400s the entire request when a node carries both. Codex's own
|
|
1015
|
+
* deferred tool catalog emits exactly that shape (zod-to-json-schema deduplicates into
|
|
1016
|
+
* `$defs.__schema*` nodes that keep `type`/`minLength`/`format` beside the `$ref`), so the
|
|
1017
|
+
* schema is not something a user can fix from configuration — see issue #2673.
|
|
1018
|
+
*
|
|
1019
|
+
* Inline the referenced schema underneath the node's own keywords, which is what 2020-12 says
|
|
1020
|
+
* the node means, then drop `$ref`. Constraints reach the model instead of being stripped.
|
|
1021
|
+
* The `$defs` bag is preserved: a bare `$ref` (no siblings) is already legal for Moonshot and
|
|
1022
|
+
* is left pointing at its definition rather than expanded, which keeps recursive schemas finite.
|
|
1023
|
+
*/
|
|
1024
|
+
function moonshotRefTargetKeys(node: Record<string, unknown>): string[] {
|
|
1025
|
+
return Object.keys(node).filter(key => key !== "$ref");
|
|
971
1026
|
}
|
|
972
1027
|
|
|
973
|
-
/**
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
"title",
|
|
981
|
-
"$comment",
|
|
982
|
-
"$defs",
|
|
983
|
-
"definitions",
|
|
984
|
-
]);
|
|
1028
|
+
/**
|
|
1029
|
+
* Inlining duplicates the target, so a schema referencing one large definition from many
|
|
1030
|
+
* sibling-carrying nodes can multiply. Bound the total expansions and fall back to a bare
|
|
1031
|
+
* `$ref` once the budget is spent: still valid for Moonshot, just without the node's own
|
|
1032
|
+
* narrowing keywords. Mirrors the node budget in google-tool-schema.ts.
|
|
1033
|
+
*/
|
|
1034
|
+
const MOONSHOT_MAX_REF_EXPANSIONS = 512;
|
|
985
1035
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1036
|
+
/**
|
|
1037
|
+
* Expansion count alone does not bound the walk: a deeply nested ref-free schema, or one
|
|
1038
|
+
* large definition repeated across many nodes, still recurses to exhaustion or amplifies the
|
|
1039
|
+
* emitted output. Depth and node budgets close both, and mirror google-tool-schema.ts.
|
|
1040
|
+
*/
|
|
1041
|
+
const MOONSHOT_MAX_SCHEMA_DEPTH = 64;
|
|
1042
|
+
const MOONSHOT_MAX_SCHEMA_NODES = 4_096;
|
|
989
1043
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1044
|
+
/**
|
|
1045
|
+
* Assertion keywords whose meaning under a `$ref` is CONJUNCTION, not replacement. A node
|
|
1046
|
+
* carrying `required: ["b"]` beside a target requiring `["a"]` means both are required;
|
|
1047
|
+
* letting the sibling win emitted a schema that no longer described the tool.
|
|
1048
|
+
*/
|
|
1049
|
+
function unionRequired(target: unknown, sibling: unknown): unknown {
|
|
1050
|
+
if (!Array.isArray(target) || !Array.isArray(sibling)) return sibling;
|
|
1051
|
+
const seen = new Set<unknown>();
|
|
1052
|
+
const out: unknown[] = [];
|
|
1053
|
+
for (const name of [...target, ...sibling]) {
|
|
1054
|
+
if (seen.has(name)) continue;
|
|
1055
|
+
seen.add(name);
|
|
1056
|
+
out.push(name);
|
|
997
1057
|
}
|
|
998
|
-
return
|
|
1058
|
+
return out;
|
|
999
1059
|
}
|
|
1000
1060
|
|
|
1001
|
-
/**
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const ref = schema.$ref;
|
|
1010
|
-
if (stack.has(ref)) return undefined;
|
|
1011
|
-
const target = lookupLocalJsonPointer(root, ref);
|
|
1012
|
-
if (target === undefined) return undefined;
|
|
1013
|
-
stack.add(ref);
|
|
1014
|
-
const resolvedTarget = resolveXaiSchemaRefs(target, root, stack);
|
|
1015
|
-
stack.delete(ref);
|
|
1016
|
-
if (resolvedTarget === undefined) return undefined;
|
|
1017
|
-
const rest: Record<string, unknown> = { ...schema };
|
|
1018
|
-
delete rest.$ref;
|
|
1019
|
-
if (Object.keys(rest).length === 0) return resolvedTarget;
|
|
1020
|
-
const resolvedRest = resolveXaiSchemaRefs(rest, root, stack);
|
|
1021
|
-
if (resolvedRest === undefined || !isXaiObjectSchema(resolvedTarget) || !isXaiObjectSchema(resolvedRest)) {
|
|
1022
|
-
return undefined;
|
|
1023
|
-
}
|
|
1024
|
-
return composeXaiObjectSchemas(resolvedTarget, resolvedRest);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
const resolved: Record<string, unknown> = {};
|
|
1028
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
1029
|
-
if ((key === "oneOf" || key === "anyOf") && Array.isArray(value)) {
|
|
1030
|
-
const items: unknown[] = [];
|
|
1031
|
-
for (const item of value) {
|
|
1032
|
-
const next = resolveXaiSchemaRefs(item, root, stack);
|
|
1033
|
-
if (next === undefined) return undefined;
|
|
1034
|
-
items.push(next);
|
|
1035
|
-
}
|
|
1036
|
-
resolved[key] = items;
|
|
1037
|
-
continue;
|
|
1038
|
-
}
|
|
1039
|
-
if (key === "properties" && isXaiObjectSchema(value)) {
|
|
1040
|
-
const properties: Record<string, unknown> = {};
|
|
1041
|
-
for (const [name, property] of Object.entries(value)) {
|
|
1042
|
-
const next = resolveXaiSchemaRefs(property, root, stack);
|
|
1043
|
-
if (next === undefined) return undefined;
|
|
1044
|
-
properties[name] = next;
|
|
1045
|
-
}
|
|
1046
|
-
resolved[key] = properties;
|
|
1047
|
-
continue;
|
|
1048
|
-
}
|
|
1049
|
-
resolved[key] = value;
|
|
1050
|
-
}
|
|
1051
|
-
return resolved;
|
|
1052
|
-
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Keywords whose values are DATA, not schemas.
|
|
1063
|
+
*
|
|
1064
|
+
* Recursing into them rewrote user data: an `enum` listing a literal object that happens
|
|
1065
|
+
* to carry a `"$ref"` string had that key stripped as if it were a schema reference, so a
|
|
1066
|
+
* value the tool declared as legal silently changed shape. These are copied through.
|
|
1067
|
+
*/
|
|
1068
|
+
const MOONSHOT_DATA_VALUED_KEYWORDS = new Set(["enum", "const", "default", "examples"]);
|
|
1053
1069
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1070
|
+
/**
|
|
1071
|
+
* Numeric assertions whose intersection is a bound, and which direction tightens.
|
|
1072
|
+
*
|
|
1073
|
+
* `$ref` under 2020-12 is an in-place applicator: the node and its target BOTH apply, so
|
|
1074
|
+
* the emitted schema must be their INTERSECTION. The previous code overwrote the target
|
|
1075
|
+
* with the node and called that "the narrower reading", which holds only when the node
|
|
1076
|
+
* happens to be narrower. A node declaring `minLength: 1` beside a target declaring
|
|
1077
|
+
* `minLength: 5` shipped `minLength: 1` - a contract weaker than either side asked for,
|
|
1078
|
+
* emitted silently, which is the same failure mode the `required` composition fixed for
|
|
1079
|
+
* set-valued keywords.
|
|
1080
|
+
*
|
|
1081
|
+
* "max" means the surviving value is the larger of the two (lower bounds), "min" the
|
|
1082
|
+
* smaller (upper bounds). A keyword absent from this table keeps the overwrite: for
|
|
1083
|
+
* `type`, `format`, `description` and friends there is no ordering to intersect along,
|
|
1084
|
+
* and the node is the more specific statement.
|
|
1085
|
+
*/
|
|
1086
|
+
const MOONSHOT_BOUND_KEYWORDS: Record<string, "max" | "min"> = {
|
|
1087
|
+
minLength: "max",
|
|
1088
|
+
minItems: "max",
|
|
1089
|
+
minProperties: "max",
|
|
1090
|
+
minimum: "max",
|
|
1091
|
+
exclusiveMinimum: "max",
|
|
1092
|
+
maxLength: "min",
|
|
1093
|
+
maxItems: "min",
|
|
1094
|
+
maxProperties: "min",
|
|
1095
|
+
maximum: "min",
|
|
1096
|
+
exclusiveMaximum: "min",
|
|
1097
|
+
};
|
|
1058
1098
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1099
|
+
/**
|
|
1100
|
+
* Intersect one numeric bound. Either side being absent or non-finite yields the other,
|
|
1101
|
+
* because an unstated bound constrains nothing - returning `undefined` there would drop
|
|
1102
|
+
* a constraint the remaining side genuinely made.
|
|
1103
|
+
*/
|
|
1104
|
+
function intersectBound(target: unknown, sibling: unknown, direction: "max" | "min"): unknown {
|
|
1105
|
+
const a = typeof target === "number" && Number.isFinite(target) ? target : null;
|
|
1106
|
+
const b = typeof sibling === "number" && Number.isFinite(sibling) ? sibling : null;
|
|
1107
|
+
if (a === null) return b === null ? sibling : sibling;
|
|
1108
|
+
if (b === null) return target;
|
|
1109
|
+
return direction === "max" ? Math.max(a, b) : Math.min(a, b);
|
|
1061
1110
|
}
|
|
1062
1111
|
|
|
1063
1112
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1113
|
+
* Compose two `properties` maps. A property named in BOTH the referenced target and the
|
|
1114
|
+
* node is the same conjunction problem `required` had: letting the sibling win discards
|
|
1115
|
+
* the target's constraints for that member. Merge the two member schemas so neither side
|
|
1116
|
+
* loses its keywords. Shared member bounds are the same conjunction one level down,
|
|
1117
|
+
* and nested object members recurse through this helper instead of replacing the target.
|
|
1068
1118
|
*/
|
|
1069
|
-
function
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1119
|
+
function composeProperties(
|
|
1120
|
+
target: Record<string, unknown>,
|
|
1121
|
+
sibling: Record<string, unknown>,
|
|
1122
|
+
): Record<string, unknown> {
|
|
1123
|
+
const combined: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
1124
|
+
for (const [name, sub] of Object.entries(target)) combined[name] = sub;
|
|
1125
|
+
for (const [name, sub] of Object.entries(sibling)) {
|
|
1126
|
+
const existing = combined[name];
|
|
1127
|
+
if (isXaiObjectSchema(existing) && isXaiObjectSchema(sub)) {
|
|
1128
|
+
const member: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
1129
|
+
for (const [k, v] of Object.entries(existing)) member[k] = v;
|
|
1130
|
+
for (const [k, v] of Object.entries(sub)) {
|
|
1131
|
+
if (k === "required") {
|
|
1132
|
+
member[k] = unionRequired(member[k], v);
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (k === "properties" && isXaiObjectSchema(member[k]) && isXaiObjectSchema(v)) {
|
|
1136
|
+
member[k] = composeProperties(member[k] as Record<string, unknown>, v);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
const boundDirection = MOONSHOT_BOUND_KEYWORDS[k];
|
|
1140
|
+
if (boundDirection && k in member) {
|
|
1141
|
+
member[k] = intersectBound(member[k], v, boundDirection);
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
member[k] = v;
|
|
1145
|
+
}
|
|
1146
|
+
combined[name] = member;
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
combined[name] = sub;
|
|
1081
1150
|
}
|
|
1082
|
-
return
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
function xaiRequiredSetsMatch(variants: Record<string, unknown>[]): boolean {
|
|
1086
|
-
const serialized = variants.map(variant => [...stringRequiredFields(variant.required)].sort().join("\0"));
|
|
1087
|
-
return serialized.every(value => value === serialized[0]);
|
|
1151
|
+
return combined;
|
|
1088
1152
|
}
|
|
1089
1153
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const explicit = values.filter(value => value !== undefined);
|
|
1095
|
-
if (explicit.length === 0) return { ok: true };
|
|
1096
|
-
if (explicit.length !== values.length) return { ok: false };
|
|
1097
|
-
const hasFalse = explicit.some(value => value === false);
|
|
1098
|
-
const permissive = explicit.filter(value => value !== false);
|
|
1099
|
-
if (hasFalse && permissive.length > 0) return { ok: false };
|
|
1100
|
-
if (hasFalse) return { ok: true, value: false };
|
|
1101
|
-
const unique: unknown[] = [];
|
|
1102
|
-
const seen = new Set<string>();
|
|
1103
|
-
for (const value of permissive) {
|
|
1104
|
-
const key = JSON.stringify(value);
|
|
1105
|
-
if (seen.has(key)) continue;
|
|
1106
|
-
seen.add(key);
|
|
1107
|
-
unique.push(value);
|
|
1108
|
-
}
|
|
1109
|
-
if (unique.length !== 1) return { ok: false };
|
|
1110
|
-
return { ok: true, value: unique[0] };
|
|
1154
|
+
interface MoonshotNormalizeState {
|
|
1155
|
+
activeRefs: Set<string>;
|
|
1156
|
+
remainingExpansions: number;
|
|
1157
|
+
remainingNodes: number;
|
|
1111
1158
|
}
|
|
1112
1159
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const properties: Record<string, unknown> = { ...(inheritedProps ?? {}) };
|
|
1123
|
-
for (const [name, value] of Object.entries(branchProps ?? {})) {
|
|
1124
|
-
const inheritedValue = inheritedProps?.[name];
|
|
1125
|
-
properties[name] = inheritedValue !== undefined && JSON.stringify(inheritedValue) !== JSON.stringify(value)
|
|
1126
|
-
? { allOf: [inheritedValue, value] }
|
|
1127
|
-
: value;
|
|
1128
|
-
}
|
|
1129
|
-
composed.properties = properties;
|
|
1160
|
+
function normalizeMoonshotSchemaNode(
|
|
1161
|
+
node: unknown,
|
|
1162
|
+
root: Record<string, unknown>,
|
|
1163
|
+
state: MoonshotNormalizeState,
|
|
1164
|
+
depth = 0,
|
|
1165
|
+
): unknown {
|
|
1166
|
+
if (Array.isArray(node)) {
|
|
1167
|
+
if (depth >= MOONSHOT_MAX_SCHEMA_DEPTH) return [];
|
|
1168
|
+
return node.map(item => normalizeMoonshotSchemaNode(item, root, state, depth + 1));
|
|
1130
1169
|
}
|
|
1131
|
-
|
|
1132
|
-
...stringRequiredFields(inherited.required),
|
|
1133
|
-
...stringRequiredFields(branch.required),
|
|
1134
|
-
])];
|
|
1135
|
-
if (required.length > 0) composed.required = required;
|
|
1136
|
-
else delete composed.required;
|
|
1137
|
-
return composed;
|
|
1138
|
-
}
|
|
1170
|
+
if (!isXaiObjectSchema(node)) return node;
|
|
1139
1171
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (schema.type !== undefined && schema.type !== "object") return undefined;
|
|
1145
|
-
return [{ ...schema, type: "object" }];
|
|
1146
|
-
}
|
|
1172
|
+
// Fail closed for this node rather than emitting a partially weakened schema: an empty
|
|
1173
|
+
// object is the one shape that asserts nothing it cannot back up.
|
|
1174
|
+
if (depth >= MOONSHOT_MAX_SCHEMA_DEPTH || state.remainingNodes <= 0) return {};
|
|
1175
|
+
state.remainingNodes -= 1;
|
|
1147
1176
|
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1150
|
-
if (!Array.isArray(branches)) return undefined;
|
|
1151
|
-
const expanded: Record<string, unknown>[] = [];
|
|
1152
|
-
for (const branch of branches) {
|
|
1153
|
-
const variants = expandXaiRootObjectSchemas(branch);
|
|
1154
|
-
if (!variants) return undefined;
|
|
1155
|
-
for (const variant of variants) expanded.push(composeXaiObjectSchemas(siblings, variant));
|
|
1156
|
-
}
|
|
1157
|
-
return expanded.length > 0 ? expanded : undefined;
|
|
1158
|
-
}
|
|
1177
|
+
const ref = node.$ref;
|
|
1178
|
+
const hasSiblings = moonshotRefTargetKeys(node).length > 0;
|
|
1159
1179
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const key = JSON.stringify(value);
|
|
1165
|
-
if (serialized.has(key)) continue;
|
|
1166
|
-
serialized.add(key);
|
|
1167
|
-
unique.push(value);
|
|
1168
|
-
}
|
|
1169
|
-
return unique.length === 1 ? unique[0] : { anyOf: unique };
|
|
1170
|
-
}
|
|
1180
|
+
if (typeof ref === "string" && hasSiblings) {
|
|
1181
|
+
// A cycle cannot be inlined. Keeping the bare `$ref` is the lossy-but-valid fallback:
|
|
1182
|
+
// Moonshot accepts it, and the alternative (dropping the ref) would erase the recursion.
|
|
1183
|
+
if (state.activeRefs.has(ref) || state.remainingExpansions <= 0) return { $ref: ref };
|
|
1171
1184
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1185
|
+
const target = lookupLocalJsonPointer(root, ref);
|
|
1186
|
+
if (isXaiObjectSchema(target)) {
|
|
1187
|
+
state.remainingExpansions -= 1;
|
|
1188
|
+
state.activeRefs.add(ref);
|
|
1189
|
+
const resolvedTarget = normalizeMoonshotSchemaNode(target, root, state, depth + 1);
|
|
1190
|
+
state.activeRefs.delete(ref);
|
|
1191
|
+
const merged: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
1192
|
+
if (isXaiObjectSchema(resolvedTarget)) {
|
|
1193
|
+
for (const [key, value] of Object.entries(resolvedTarget)) merged[key] = value;
|
|
1194
|
+
}
|
|
1195
|
+
// "Alongside the target" is conjunction, not replacement. For most keywords the node
|
|
1196
|
+
// narrows the target and overwriting is the narrower reading, but `required` and
|
|
1197
|
+
// `properties` are set-valued: letting the sibling win DROPPED the target's own
|
|
1198
|
+
// members, so a tool requiring `a` beside a node requiring `b` shipped requiring only
|
|
1199
|
+
// `b`. Those two compose; everything else keeps the narrowing overwrite.
|
|
1200
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1201
|
+
if (key === "$ref") continue;
|
|
1202
|
+
if (MOONSHOT_DATA_VALUED_KEYWORDS.has(key)) {
|
|
1203
|
+
merged[key] = value;
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const normalized = normalizeMoonshotSchemaNode(value, root, state, depth + 1);
|
|
1207
|
+
if (key === "required") {
|
|
1208
|
+
merged[key] = unionRequired(merged[key], normalized);
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (key === "properties" && isXaiObjectSchema(merged[key]) && isXaiObjectSchema(normalized)) {
|
|
1212
|
+
merged[key] = composeProperties(merged[key] as Record<string, unknown>, normalized);
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
// Numeric bounds intersect rather than overwrite: both the node and its target
|
|
1216
|
+
// apply, so the surviving bound is the stricter of the two in whichever direction
|
|
1217
|
+
// that keyword tightens.
|
|
1218
|
+
const boundDirection = MOONSHOT_BOUND_KEYWORDS[key];
|
|
1219
|
+
if (boundDirection && key in merged) {
|
|
1220
|
+
merged[key] = intersectBound(merged[key], normalized, boundDirection);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
merged[key] = normalized;
|
|
1224
|
+
}
|
|
1225
|
+
return merged;
|
|
1209
1226
|
}
|
|
1227
|
+
|
|
1228
|
+
// Unresolvable pointer: a remote ref, a malformed path, or a non-object target. Dropping
|
|
1229
|
+
// the ref and keeping the siblings silently discards whatever the reference constrained,
|
|
1230
|
+
// which is the one outcome we cannot detect downstream. A bare `$ref` is lossy in the
|
|
1231
|
+
// other direction - it loses the node's own keywords - but it preserves the identity of
|
|
1232
|
+
// what was asked for, and Moonshot accepts it.
|
|
1233
|
+
return { $ref: ref };
|
|
1210
1234
|
}
|
|
1211
1235
|
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1236
|
+
const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
1237
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1238
|
+
out[key] = key === "$ref" || MOONSHOT_DATA_VALUED_KEYWORDS.has(key)
|
|
1239
|
+
? value
|
|
1240
|
+
: normalizeMoonshotSchemaNode(value, root, state, depth + 1);
|
|
1241
|
+
}
|
|
1242
|
+
return out;
|
|
1243
|
+
}
|
|
1216
1244
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
};
|
|
1245
|
+
function normalizeMoonshotToolParameters(parameters: unknown): Record<string, unknown> {
|
|
1246
|
+
const rooted = ensureRootObjectType(parameters);
|
|
1247
|
+
const normalized = normalizeMoonshotSchemaNode(rooted, rooted, {
|
|
1248
|
+
activeRefs: new Set<string>(),
|
|
1249
|
+
remainingExpansions: MOONSHOT_MAX_REF_EXPANSIONS,
|
|
1250
|
+
remainingNodes: MOONSHOT_MAX_SCHEMA_NODES,
|
|
1251
|
+
});
|
|
1252
|
+
return isXaiObjectSchema(normalized) ? normalized : rooted;
|
|
1224
1253
|
}
|
|
1225
1254
|
|
|
1226
1255
|
function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig): unknown[] | undefined {
|
|
@@ -1228,10 +1257,14 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
|
|
|
1228
1257
|
const tools = parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools));
|
|
1229
1258
|
if (tools.length === 0) return undefined;
|
|
1230
1259
|
const xaiTarget = isXaiSchemaTarget(provider);
|
|
1260
|
+
const moonshotTarget = !xaiTarget && isMoonshotSchemaTarget(provider);
|
|
1231
1261
|
const formatted = tools.flatMap(t => {
|
|
1232
|
-
const
|
|
1262
|
+
const normalized = xaiTarget
|
|
1233
1263
|
? normalizeXaiToolParameters(t.parameters)
|
|
1234
|
-
:
|
|
1264
|
+
: moonshotTarget
|
|
1265
|
+
? normalizeMoonshotToolParameters(t.parameters)
|
|
1266
|
+
: ensureRootObjectType(t.parameters);
|
|
1267
|
+
const parameters = stripResponsesOnlyEncryptedMarker(normalized);
|
|
1235
1268
|
|
|
1236
1269
|
if (parameters === undefined) return [];
|
|
1237
1270
|
return [{
|
|
@@ -1249,17 +1282,22 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
|
|
|
1249
1282
|
|
|
1250
1283
|
function toolsToChatFormatForProvider(parsed: OcxParsedRequest, provider: OcxProviderConfig): unknown[] | undefined {
|
|
1251
1284
|
const base = toolsToChatFormat(parsed, provider);
|
|
1252
|
-
|
|
1285
|
+
const azureChat = isAzureOpenAiChatTarget(provider);
|
|
1286
|
+
const zenChat = shouldSanitizeZenToolParameters(provider);
|
|
1287
|
+
if (!base || (!zenChat && !azureChat)) return base;
|
|
1253
1288
|
return base.map(tool => {
|
|
1254
1289
|
if (!tool || typeof tool !== "object") return tool;
|
|
1255
1290
|
const functionDef = (tool as { function?: Record<string, unknown> }).function;
|
|
1256
1291
|
if (!functionDef || typeof functionDef !== "object") return tool;
|
|
1292
|
+
const parameters = azureChat
|
|
1293
|
+
? sanitizeAzureChatToolParameters(functionDef.parameters ?? {})
|
|
1294
|
+
: ensureZenRootObjectSchema(functionDef.parameters ?? {});
|
|
1295
|
+
const nextFunction: Record<string, unknown> = { ...functionDef, parameters };
|
|
1296
|
+
// strict: true plus a flattened schema is rejected by Gemini-in-the-pool routers.
|
|
1297
|
+
if (azureChat) delete nextFunction.strict;
|
|
1257
1298
|
return {
|
|
1258
1299
|
...tool,
|
|
1259
|
-
function:
|
|
1260
|
-
...functionDef,
|
|
1261
|
-
parameters: ensureZenRootObjectSchema(functionDef.parameters ?? {}),
|
|
1262
|
-
},
|
|
1300
|
+
function: nextFunction,
|
|
1263
1301
|
};
|
|
1264
1302
|
});
|
|
1265
1303
|
}
|
|
@@ -1932,8 +1970,28 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1932
1970
|
const choice = rawChoice;
|
|
1933
1971
|
if (choice.finish_reason === "error") return [upstreamErrorEvent(choice.error, usage)];
|
|
1934
1972
|
if (!choice.message) return [{ type: "error", message: "upstream response contained no choices", ...(usage ? { usage } : {}) }];
|
|
1973
|
+
// `!choice.message` splits this input class on TRUTHINESS, not on shape: `null` and `0` fail
|
|
1974
|
+
// closed here, while `"text"`, `true` and `[{...}]` pass and every property read below yields
|
|
1975
|
+
// `undefined` — so a choice claiming an assistant message completed as a SUCCESSFUL EMPTY
|
|
1976
|
+
// turn, stranding any tool call it claimed. The one line above already validates the choice
|
|
1977
|
+
// container this way; its message was left on a truthiness test.
|
|
1978
|
+
//
|
|
1979
|
+
// Every non-record is rejected, arrays included. The google adapter does carve out `[]` for
|
|
1980
|
+
// `content`, but that carve-out is specific to a protobuf-derived wire where a repeated
|
|
1981
|
+
// field can spell an empty message — and `content` is genuinely an ARRAY of blocks there.
|
|
1982
|
+
// `message` is a record on a plain-JSON wire that already has `{}`, so importing the
|
|
1983
|
+
// exception would be an analogy rather than evidence. `[{"content":"…"}]` is the case that
|
|
1984
|
+
// matters: it discards a complete answer, #2232's `content: [{ parts: [...] }]` one adapter over.
|
|
1985
|
+
//
|
|
1986
|
+
// Read through `unknown` rather than the declared type: `choices` is a cast over wire data,
|
|
1987
|
+
// so its `message?: Record<string, unknown>` is an assertion the upstream never made, and
|
|
1988
|
+
// narrowing against it is what let the missing check look type-safe.
|
|
1989
|
+
const rawMessage: unknown = choice.message;
|
|
1990
|
+
if (!isRecord(rawMessage)) {
|
|
1991
|
+
return [invalidChoicesEvent(usage)];
|
|
1992
|
+
}
|
|
1935
1993
|
|
|
1936
|
-
const msg =
|
|
1994
|
+
const msg = rawMessage as Record<string, unknown>;
|
|
1937
1995
|
const reasoningText = reasoningTextFrom(msg);
|
|
1938
1996
|
if (reasoningText !== undefined) events.push({ type: "reasoning_raw_delta", text: reasoningText });
|
|
1939
1997
|
if (typeof msg.content === "string") events.push({ type: "text_delta", text: msg.content });
|