@bitkyc08/opencodex 2.19.0 → 2.21.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/gui/dist/assets/index-BOFeam5a.js +76 -0
- package/gui/dist/assets/index-Xq49CY8F.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +2 -28
- package/src/adapters/google.ts +31 -3
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat.ts +392 -40
- package/src/adapters/registry.ts +144 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/bridge.ts +15 -2
- package/src/chat/inbound.ts +13 -7
- package/src/claude/gateway-cache.ts +41 -4
- package/src/cli/claude.ts +3 -2
- package/src/cli/combo.ts +3 -0
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/export-command.ts +2 -2
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +3 -2
- package/src/cli/lab.ts +135 -1
- package/src/cli/minimax.ts +491 -0
- package/src/cli/models-runtime.ts +22 -1
- package/src/cli/models.ts +67 -2
- package/src/cli/opencode.ts +2 -1
- package/src/cli/registry.ts +22 -2
- package/src/clients/config-export.ts +125 -7
- package/src/codex/app-server-processes.ts +57 -2
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/catalog/aggregation.ts +10 -1
- package/src/codex/catalog/effort.ts +15 -3
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +45 -5
- package/src/codex/catalog/sync.ts +74 -4
- package/src/codex/convergence.ts +2 -0
- package/src/combos/index.ts +1 -0
- package/src/combos/request.ts +30 -0
- package/src/combos/types.ts +6 -0
- package/src/config.ts +52 -0
- package/src/generated/compatibility-version.json +241 -85
- package/src/images/loop.ts +26 -6
- package/src/integrations/registry.ts +7 -0
- package/src/lab/conformance/jcs.ts +42 -2
- package/src/lab/conformance/negative-controls.ts +6 -2
- package/src/lab/conformance/runner.ts +16 -5
- package/src/lab/fabric/observe.ts +49 -14
- package/src/lab/index.ts +16 -0
- package/src/lab/ledger/purge.ts +152 -83
- package/src/lab/ledger/store.ts +168 -54
- package/src/lab/observe/from-conformance.ts +8 -6
- package/src/lab/observe/from-live.ts +8 -2
- package/src/lab/paths.ts +23 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +219 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/lab-activation.ts +109 -47
- package/src/lib/lab-live-pinned-sender.ts +16 -5
- package/src/lib/pinned-http.ts +70 -16
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/upstream-reachability.ts +4 -0
- package/src/lib/windows-elevation.ts +10 -1
- package/src/providers/derive.ts +24 -4
- package/src/providers/registry.ts +14 -4
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +143 -0
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/reasoning-effort.ts +27 -1
- package/src/responses/custom-tool-compat.ts +4 -1
- package/src/responses/parser.ts +7 -1
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/schema.ts +6 -0
- package/src/router.ts +23 -6
- package/src/routing/capability.ts +4 -2
- package/src/routing/compatibility/behavior.ts +5 -1
- package/src/server/adapter-resolve.ts +2 -32
- package/src/server/auth-cors.ts +50 -6
- package/src/server/chat-completions.ts +74 -36
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +371 -0
- package/src/server/management/combo-routes.ts +16 -2
- package/src/server/management/config-routes.ts +6 -4
- package/src/server/management/context.ts +17 -0
- package/src/server/management/lab-routes.ts +181 -19
- package/src/server/management/model-routes.ts +76 -2
- package/src/server/management/model-rows.ts +8 -0
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +76 -4
- package/src/server/management/system-restart.ts +4 -2
- package/src/server/management/system-routes.ts +38 -0
- package/src/server/relay.ts +17 -3
- package/src/server/responses/compact.ts +4 -1
- package/src/server/responses/core.ts +257 -46
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/fetch-helpers.ts +35 -4
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +16 -2
- package/src/server/responses/terminal-guard.ts +1 -1
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses.ts +17 -2
- package/src/server/system-env.ts +1 -1
- package/src/types.ts +84 -5
- package/src/update/index.ts +6 -5
- package/src/update/job.ts +5 -6
- package/src/update/notify.ts +5 -3
- package/src/usage/log.ts +11 -1
- package/src/web-search/loop.ts +21 -5
- package/gui/dist/assets/index-CQ7bIKee.css +0 -1
- package/gui/dist/assets/index-D_JUZLEC.js +0 -76
|
@@ -12,7 +12,9 @@ import { identifyRoutedModel } from "./identity";
|
|
|
12
12
|
import { peekReasoningForCall } from "../responses/reasoning-replay-cache";
|
|
13
13
|
import { buildNonOpenAIToolCatalogNudgeForTools, shouldInjectNonOpenAIToolCatalogNudge } from "./tool-catalog-nudge";
|
|
14
14
|
import { openRouterProviderPayload, resolveOpenRouterRouting } from "../providers/openrouter-routing";
|
|
15
|
+
import { canSerializeServiceTierForChatModel } from "../providers/service-tier";
|
|
15
16
|
import { openaiChatCompletionsUrl } from "./openai-chat-url";
|
|
17
|
+
import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema";
|
|
16
18
|
import {
|
|
17
19
|
isTranslatorBudgetExceededError,
|
|
18
20
|
retainTranslatedEventBatch,
|
|
@@ -34,6 +36,122 @@ export function stripBracketedModelSuffix(modelId: string): string {
|
|
|
34
36
|
return suffixStart === -1 ? modelId : modelId.slice(0, suffixStart);
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
const CHAT_PASSTHROUGH_FIELDS = [
|
|
40
|
+
"audio",
|
|
41
|
+
"frequency_penalty",
|
|
42
|
+
"logit_bias",
|
|
43
|
+
"logprobs",
|
|
44
|
+
"max_completion_tokens",
|
|
45
|
+
"max_tokens",
|
|
46
|
+
"metadata",
|
|
47
|
+
"modalities",
|
|
48
|
+
"n",
|
|
49
|
+
"prediction",
|
|
50
|
+
"presence_penalty",
|
|
51
|
+
"reasoning_effort",
|
|
52
|
+
"response_format",
|
|
53
|
+
"seed",
|
|
54
|
+
"stop",
|
|
55
|
+
"store",
|
|
56
|
+
"temperature",
|
|
57
|
+
"tool_choice",
|
|
58
|
+
"tools",
|
|
59
|
+
"top_logprobs",
|
|
60
|
+
"top_p",
|
|
61
|
+
"user",
|
|
62
|
+
"web_search_options",
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
function openAIChatTransport(provider: OcxProviderConfig): {
|
|
66
|
+
url: string;
|
|
67
|
+
headers: Record<string, string>;
|
|
68
|
+
hasCredential: boolean;
|
|
69
|
+
} {
|
|
70
|
+
const hasCredential = typeof provider.apiKey === "string" && provider.apiKey.trim().length > 0;
|
|
71
|
+
if ((provider.authMode === "key" || provider.authMode === "oauth") && !provider.keyOptional && !hasCredential) {
|
|
72
|
+
throw new Error(`${provider.adapter} requires a non-empty credential (authMode: ${provider.authMode})`);
|
|
73
|
+
}
|
|
74
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
75
|
+
if (hasCredential) headers.Authorization = `Bearer ${provider.apiKey}`;
|
|
76
|
+
if (provider.headers) Object.assign(headers, provider.headers);
|
|
77
|
+
return { url: openaiChatCompletionsUrl(provider.baseUrl), headers, hasCredential };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build a provider request from an inbound Chat Completions body without translating it
|
|
82
|
+
* through the Responses contract. This is deliberately a whitelist: Chat-only caller
|
|
83
|
+
* fields retain their exact wire representation, while provider capability gates remain
|
|
84
|
+
* centralized beside the ordinary openai-chat adapter.
|
|
85
|
+
*/
|
|
86
|
+
export function buildOpenAIChatPassthroughRequest(
|
|
87
|
+
provider: OcxProviderConfig,
|
|
88
|
+
rawBody: Record<string, unknown>,
|
|
89
|
+
modelId: string,
|
|
90
|
+
stream: boolean,
|
|
91
|
+
): AdapterRequest {
|
|
92
|
+
const { url, headers, hasCredential } = openAIChatTransport(provider);
|
|
93
|
+
|
|
94
|
+
const body: Record<string, unknown> = {
|
|
95
|
+
model: provider.modelSuffixBracketStrip ? stripBracketedModelSuffix(modelId) : modelId,
|
|
96
|
+
messages: rawBody.messages,
|
|
97
|
+
stream,
|
|
98
|
+
};
|
|
99
|
+
for (const field of CHAT_PASSTHROUGH_FIELDS) {
|
|
100
|
+
if (rawBody[field] !== undefined) body[field] = rawBody[field];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (modelInList(provider.noTemperatureModels, modelId)) delete body.temperature;
|
|
104
|
+
if (modelInList(provider.noTopPModels, modelId)) delete body.top_p;
|
|
105
|
+
if (modelInList(provider.noPenaltyModels, modelId)) {
|
|
106
|
+
delete body.presence_penalty;
|
|
107
|
+
delete body.frequency_penalty;
|
|
108
|
+
}
|
|
109
|
+
if (modelInList(provider.noStructuredOutputModels, modelId)) delete body.response_format;
|
|
110
|
+
|
|
111
|
+
if (provider.chatServiceTier && rawBody.service_tier !== undefined) {
|
|
112
|
+
body.service_tier = rawBody.service_tier;
|
|
113
|
+
}
|
|
114
|
+
if (provider.promptCacheKey && rawBody.prompt_cache_key !== undefined) {
|
|
115
|
+
body.prompt_cache_key = rawBody.prompt_cache_key;
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(rawBody.tools) && rawBody.tools.length > 0) {
|
|
118
|
+
if (provider.parallelToolCalls === true) {
|
|
119
|
+
body.parallel_tool_calls = rawBody.parallel_tool_calls !== false;
|
|
120
|
+
} else if (provider.parallelToolCalls === false
|
|
121
|
+
&& (provider.baseUrl === "https://integrate.api.nvidia.com/v1" || provider.pinParallelToolCallsFalse === true)) {
|
|
122
|
+
body.parallel_tool_calls = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (stream) {
|
|
126
|
+
const callerOptions = rawBody.stream_options !== null
|
|
127
|
+
&& typeof rawBody.stream_options === "object"
|
|
128
|
+
&& !Array.isArray(rawBody.stream_options)
|
|
129
|
+
? rawBody.stream_options as Record<string, unknown>
|
|
130
|
+
: {};
|
|
131
|
+
body.stream_options = { ...callerOptions, include_usage: true };
|
|
132
|
+
} else if (rawBody.stream_options !== undefined) {
|
|
133
|
+
body.stream_options = rawBody.stream_options;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const bodyJson = JSON.stringify(body);
|
|
137
|
+
|
|
138
|
+
if (isDebugEnabled()) {
|
|
139
|
+
let host = "upstream";
|
|
140
|
+
try { host = new URL(url).host; } catch { /* keep fallback */ }
|
|
141
|
+
debugProviderDiagnostic("openai-chat", "passthrough-request", {
|
|
142
|
+
host,
|
|
143
|
+
model: body.model,
|
|
144
|
+
stream,
|
|
145
|
+
messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
|
|
146
|
+
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
147
|
+
hasCredential,
|
|
148
|
+
bodyBytes: new TextEncoder().encode(bodyJson).length,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { url, method: "POST", headers, body: bodyJson };
|
|
153
|
+
}
|
|
154
|
+
|
|
37
155
|
// 260715 (issue #126): surface upstream error detail through the web-search sidecar loop.
|
|
38
156
|
// loop.ts only appends a suffix to "Provider error N" when the adapter exposes
|
|
39
157
|
// formatErrorBody; without it, strict OpenAI-compatible backends (NVIDIA NIM pydantic
|
|
@@ -215,6 +333,16 @@ type InvalidToolCallReason =
|
|
|
215
333
|
| "tool_call_function_name_blank"
|
|
216
334
|
| "tool_call_function_arguments_invalid";
|
|
217
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Streamed string fields are absent when null or undefined (#1731): OpenAI-compatible
|
|
338
|
+
* streamers repeat already-sent `id`/`name`/`arguments` as null on continuation deltas.
|
|
339
|
+
* The accumulator and this diagnostic share this predicate so they cannot disagree about
|
|
340
|
+
* which delta was the invalid one.
|
|
341
|
+
*/
|
|
342
|
+
function isInvalidStreamStringField(value: unknown): boolean {
|
|
343
|
+
return value != null && typeof value !== "string";
|
|
344
|
+
}
|
|
345
|
+
|
|
218
346
|
/**
|
|
219
347
|
* Explain only the rejected wire shape, never its values. This diagnostic exists so provider
|
|
220
348
|
* compatibility can be tightened from evidence without retaining tool arguments or credentials.
|
|
@@ -241,6 +369,10 @@ function diagnoseInvalidToolCalls(
|
|
|
241
369
|
// Blank names are caught later at flush, not here, so they are not diagnosed on this
|
|
242
370
|
// branch. Describe exactly that boundary rather than tightening compatibility in a
|
|
243
371
|
// diagnostic change.
|
|
372
|
+
// #1731: "present" means the same thing here as in the accumulator — null and undefined
|
|
373
|
+
// are both absent, because some OpenAI-compatible streamers repeat already-sent fields
|
|
374
|
+
// as null on continuation deltas. A separate predicate here would diagnose accepted
|
|
375
|
+
// padding as the failure and point compatibility work at the wrong delta.
|
|
244
376
|
const streamFunction = (rawToolCall as { function?: unknown }).function;
|
|
245
377
|
if (streamFunction !== undefined && streamFunction !== null) {
|
|
246
378
|
if (!isRecord(streamFunction)) {
|
|
@@ -250,14 +382,14 @@ function diagnoseInvalidToolCalls(
|
|
|
250
382
|
valueType: Array.isArray(streamFunction) ? "array" : typeof streamFunction,
|
|
251
383
|
};
|
|
252
384
|
}
|
|
253
|
-
if (streamFunction.name
|
|
385
|
+
if (isInvalidStreamStringField(streamFunction.name)) {
|
|
254
386
|
return { reason: "tool_call_function_name_invalid", callIndex, valueType: typeof streamFunction.name };
|
|
255
387
|
}
|
|
256
|
-
if (streamFunction.arguments
|
|
388
|
+
if (isInvalidStreamStringField(streamFunction.arguments)) {
|
|
257
389
|
return { reason: "tool_call_function_arguments_invalid", callIndex, valueType: typeof streamFunction.arguments };
|
|
258
390
|
}
|
|
259
391
|
}
|
|
260
|
-
if (rawToolCall.id
|
|
392
|
+
if (isInvalidStreamStringField(rawToolCall.id)) {
|
|
261
393
|
return { reason: "tool_call_id_invalid", callIndex, valueType: typeof rawToolCall.id };
|
|
262
394
|
}
|
|
263
395
|
continue;
|
|
@@ -658,11 +790,11 @@ function shouldSanitizeZenToolParameters(provider: OcxProviderConfig): boolean {
|
|
|
658
790
|
|| baseUrl === "https://opencode.ai/zen/go/v1";
|
|
659
791
|
}
|
|
660
792
|
|
|
661
|
-
const XAI_SCHEMA_BASE_URLS = new Set(["api.x.ai", "cli-chat-proxy.grok.com"]);
|
|
662
|
-
|
|
663
793
|
function isXaiSchemaTarget(provider: OcxProviderConfig): boolean {
|
|
664
794
|
try {
|
|
665
|
-
|
|
795
|
+
// Public api.x.ai accepts native root object unions. Only the Grok CLI proxy
|
|
796
|
+
// 400s on a root oneOf/anyOf, so flattening/omitting is scoped to that host.
|
|
797
|
+
return new URL(provider.baseUrl).hostname === "cli-chat-proxy.grok.com";
|
|
666
798
|
} catch {
|
|
667
799
|
return false;
|
|
668
800
|
}
|
|
@@ -696,36 +828,262 @@ function ensureRootObjectType(parameters: unknown): Record<string, unknown> {
|
|
|
696
828
|
return { ...obj, type: "object" };
|
|
697
829
|
}
|
|
698
830
|
|
|
831
|
+
function isXaiObjectSchema(value: unknown): value is Record<string, unknown> {
|
|
832
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function stringRequiredFields(value: unknown): string[] {
|
|
836
|
+
return Array.isArray(value)
|
|
837
|
+
? value.filter((item): item is string => typeof item === "string")
|
|
838
|
+
: [];
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/** Variant keys the merger can keep. Anything else is refused, not silently dropped. */
|
|
842
|
+
const XAI_VARIANT_MERGE_KEYS = new Set([
|
|
843
|
+
"type",
|
|
844
|
+
"properties",
|
|
845
|
+
"required",
|
|
846
|
+
"additionalProperties",
|
|
847
|
+
"description",
|
|
848
|
+
"title",
|
|
849
|
+
"$comment",
|
|
850
|
+
"$defs",
|
|
851
|
+
"definitions",
|
|
852
|
+
]);
|
|
853
|
+
|
|
854
|
+
function decodeJsonPointerToken(token: string): string {
|
|
855
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function lookupLocalJsonPointer(root: unknown, ref: string): unknown {
|
|
859
|
+
if (ref === "#" || ref === "#/") return root;
|
|
860
|
+
if (!ref.startsWith("#/")) return undefined;
|
|
861
|
+
let current: unknown = root;
|
|
862
|
+
for (const token of ref.slice(2).split("/").map(decodeJsonPointerToken)) {
|
|
863
|
+
if (!isXaiObjectSchema(current) || !Object.hasOwn(current, token)) return undefined;
|
|
864
|
+
current = current[token];
|
|
865
|
+
}
|
|
866
|
+
return current;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Resolve local `#/` `$ref`s. Unresolvable or cyclic refs return undefined. */
|
|
870
|
+
function resolveXaiSchemaRefs(
|
|
871
|
+
schema: unknown,
|
|
872
|
+
root: Record<string, unknown>,
|
|
873
|
+
stack: Set<string> = new Set(),
|
|
874
|
+
): unknown | undefined {
|
|
875
|
+
if (!isXaiObjectSchema(schema)) return schema;
|
|
876
|
+
if (typeof schema.$ref === "string") {
|
|
877
|
+
const ref = schema.$ref;
|
|
878
|
+
if (stack.has(ref)) return undefined;
|
|
879
|
+
const target = lookupLocalJsonPointer(root, ref);
|
|
880
|
+
if (target === undefined) return undefined;
|
|
881
|
+
stack.add(ref);
|
|
882
|
+
const resolvedTarget = resolveXaiSchemaRefs(target, root, stack);
|
|
883
|
+
stack.delete(ref);
|
|
884
|
+
if (resolvedTarget === undefined) return undefined;
|
|
885
|
+
const rest: Record<string, unknown> = { ...schema };
|
|
886
|
+
delete rest.$ref;
|
|
887
|
+
if (Object.keys(rest).length === 0) return resolvedTarget;
|
|
888
|
+
const resolvedRest = resolveXaiSchemaRefs(rest, root, stack);
|
|
889
|
+
if (resolvedRest === undefined || !isXaiObjectSchema(resolvedTarget) || !isXaiObjectSchema(resolvedRest)) {
|
|
890
|
+
return undefined;
|
|
891
|
+
}
|
|
892
|
+
return composeXaiObjectSchemas(resolvedTarget, resolvedRest);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const resolved: Record<string, unknown> = {};
|
|
896
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
897
|
+
if ((key === "oneOf" || key === "anyOf") && Array.isArray(value)) {
|
|
898
|
+
const items: unknown[] = [];
|
|
899
|
+
for (const item of value) {
|
|
900
|
+
const next = resolveXaiSchemaRefs(item, root, stack);
|
|
901
|
+
if (next === undefined) return undefined;
|
|
902
|
+
items.push(next);
|
|
903
|
+
}
|
|
904
|
+
resolved[key] = items;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (key === "properties" && isXaiObjectSchema(value)) {
|
|
908
|
+
const properties: Record<string, unknown> = {};
|
|
909
|
+
for (const [name, property] of Object.entries(value)) {
|
|
910
|
+
const next = resolveXaiSchemaRefs(property, root, stack);
|
|
911
|
+
if (next === undefined) return undefined;
|
|
912
|
+
properties[name] = next;
|
|
913
|
+
}
|
|
914
|
+
resolved[key] = properties;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
resolved[key] = value;
|
|
918
|
+
}
|
|
919
|
+
return resolved;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function xaiVariantIsConcreteObject(variant: Record<string, unknown>): boolean {
|
|
923
|
+
if (variant.type !== undefined && variant.type !== "object") return false;
|
|
924
|
+
return Object.keys(variant).every(key => XAI_VARIANT_MERGE_KEYS.has(key));
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function variantProperties(variant: Record<string, unknown>): Record<string, unknown> {
|
|
928
|
+
return isXaiObjectSchema(variant.properties) ? variant.properties : {};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Independent per-property anyOf is lossless only when every property name exists
|
|
933
|
+
* on every variant (absence is meaningful under xAI's default additionalProperties:
|
|
934
|
+
* false, and promoting a branch-local key also tightens explicit-true variants)
|
|
935
|
+
* and at most one of those shared properties has a conflicting schema.
|
|
936
|
+
*/
|
|
937
|
+
function xaiPropertyMergeIsLossless(variants: Record<string, unknown>[]): boolean {
|
|
938
|
+
const names = new Set<string>();
|
|
939
|
+
const props = variants.map(variant => {
|
|
940
|
+
const properties = variantProperties(variant);
|
|
941
|
+
for (const name of Object.keys(properties)) names.add(name);
|
|
942
|
+
return properties;
|
|
943
|
+
});
|
|
944
|
+
let schemaConflicts = 0;
|
|
945
|
+
for (const name of names) {
|
|
946
|
+
const values = props.map(property => property[name]);
|
|
947
|
+
if (values.some(value => value === undefined)) return false;
|
|
948
|
+
if (values.some(value => JSON.stringify(value) !== JSON.stringify(values[0]))) schemaConflicts += 1;
|
|
949
|
+
}
|
|
950
|
+
return schemaConflicts <= 1;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function xaiRequiredSetsMatch(variants: Record<string, unknown>[]): boolean {
|
|
954
|
+
const serialized = variants.map(variant => [...stringRequiredFields(variant.required)].sort().join("\0"));
|
|
955
|
+
return serialized.every(value => value === serialized[0]);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function mergeXaiAdditionalProperties(
|
|
959
|
+
variants: Record<string, unknown>[],
|
|
960
|
+
): { ok: true; value?: unknown } | { ok: false } {
|
|
961
|
+
const values = variants.map(variant => variant.additionalProperties);
|
|
962
|
+
const explicit = values.filter(value => value !== undefined);
|
|
963
|
+
if (explicit.length === 0) return { ok: true };
|
|
964
|
+
if (explicit.length !== values.length) return { ok: false };
|
|
965
|
+
const hasFalse = explicit.some(value => value === false);
|
|
966
|
+
const permissive = explicit.filter(value => value !== false);
|
|
967
|
+
if (hasFalse && permissive.length > 0) return { ok: false };
|
|
968
|
+
if (hasFalse) return { ok: true, value: false };
|
|
969
|
+
const unique: unknown[] = [];
|
|
970
|
+
const seen = new Set<string>();
|
|
971
|
+
for (const value of permissive) {
|
|
972
|
+
const key = JSON.stringify(value);
|
|
973
|
+
if (seen.has(key)) continue;
|
|
974
|
+
seen.add(key);
|
|
975
|
+
unique.push(value);
|
|
976
|
+
}
|
|
977
|
+
if (unique.length !== 1) return { ok: false };
|
|
978
|
+
return { ok: true, value: unique[0] };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/** Compose root siblings into a branch so properties/required are not overwritten. */
|
|
982
|
+
function composeXaiObjectSchemas(
|
|
983
|
+
inherited: Record<string, unknown>,
|
|
984
|
+
branch: Record<string, unknown>,
|
|
985
|
+
): Record<string, unknown> {
|
|
986
|
+
const composed: Record<string, unknown> = { ...inherited, ...branch };
|
|
987
|
+
const inheritedProps = isXaiObjectSchema(inherited.properties) ? inherited.properties : undefined;
|
|
988
|
+
const branchProps = isXaiObjectSchema(branch.properties) ? branch.properties : undefined;
|
|
989
|
+
if (inheritedProps || branchProps) {
|
|
990
|
+
const properties: Record<string, unknown> = { ...(inheritedProps ?? {}) };
|
|
991
|
+
for (const [name, value] of Object.entries(branchProps ?? {})) {
|
|
992
|
+
const inheritedValue = inheritedProps?.[name];
|
|
993
|
+
properties[name] = inheritedValue !== undefined && JSON.stringify(inheritedValue) !== JSON.stringify(value)
|
|
994
|
+
? { allOf: [inheritedValue, value] }
|
|
995
|
+
: value;
|
|
996
|
+
}
|
|
997
|
+
composed.properties = properties;
|
|
998
|
+
}
|
|
999
|
+
const required = [...new Set([
|
|
1000
|
+
...stringRequiredFields(inherited.required),
|
|
1001
|
+
...stringRequiredFields(branch.required),
|
|
1002
|
+
])];
|
|
1003
|
+
if (required.length > 0) composed.required = required;
|
|
1004
|
+
else delete composed.required;
|
|
1005
|
+
return composed;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
699
1008
|
function expandXaiRootObjectSchemas(schema: unknown): Record<string, unknown>[] | undefined {
|
|
700
|
-
if (!
|
|
701
|
-
const
|
|
702
|
-
const compositionKey = ["oneOf", "anyOf"].find(key => Array.isArray(obj[key]));
|
|
1009
|
+
if (!isXaiObjectSchema(schema)) return undefined;
|
|
1010
|
+
const compositionKey = ["oneOf", "anyOf"].find(key => Array.isArray(schema[key]));
|
|
703
1011
|
if (!compositionKey) {
|
|
704
|
-
if (
|
|
705
|
-
return [{ ...
|
|
1012
|
+
if (schema.type !== undefined && schema.type !== "object") return undefined;
|
|
1013
|
+
return [{ ...schema, type: "object" }];
|
|
706
1014
|
}
|
|
707
1015
|
|
|
708
|
-
const siblings = Object.fromEntries(Object.entries(
|
|
709
|
-
const branches =
|
|
1016
|
+
const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => key !== compositionKey));
|
|
1017
|
+
const branches = schema[compositionKey];
|
|
710
1018
|
if (!Array.isArray(branches)) return undefined;
|
|
711
1019
|
const expanded: Record<string, unknown>[] = [];
|
|
712
1020
|
for (const branch of branches) {
|
|
713
1021
|
const variants = expandXaiRootObjectSchemas(branch);
|
|
714
1022
|
if (!variants) return undefined;
|
|
715
|
-
for (const variant of variants) expanded.push(
|
|
1023
|
+
for (const variant of variants) expanded.push(composeXaiObjectSchemas(siblings, variant));
|
|
716
1024
|
}
|
|
717
1025
|
return expanded.length > 0 ? expanded : undefined;
|
|
718
1026
|
}
|
|
719
1027
|
|
|
1028
|
+
function mergeXaiPropertySchemas(values: unknown[]): unknown {
|
|
1029
|
+
const unique: unknown[] = [];
|
|
1030
|
+
const serialized = new Set<string>();
|
|
1031
|
+
for (const value of values) {
|
|
1032
|
+
const key = JSON.stringify(value);
|
|
1033
|
+
if (serialized.has(key)) continue;
|
|
1034
|
+
serialized.add(key);
|
|
1035
|
+
unique.push(value);
|
|
1036
|
+
}
|
|
1037
|
+
return unique.length === 1 ? unique[0] : { anyOf: unique };
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* The Grok CLI proxy rejects a function parameter schema whose root remains oneOf/anyOf.
|
|
1042
|
+
* Flatten only when the merge is lossless: local $refs resolve, every variant is a concrete
|
|
1043
|
+
* object whose keys we can preserve, required sets match, additionalProperties does not change
|
|
1044
|
+
* meaning, every property name exists on every variant, and at most one property schema
|
|
1045
|
+
* differs. Otherwise omit the tool rather than emit a weaker schema.
|
|
1046
|
+
*/
|
|
720
1047
|
function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown> | undefined {
|
|
721
|
-
|
|
1048
|
+
if (!isXaiObjectSchema(parameters)) return undefined;
|
|
1049
|
+
const resolved = resolveXaiSchemaRefs(parameters, parameters);
|
|
1050
|
+
if (!isXaiObjectSchema(resolved)) return undefined;
|
|
1051
|
+
const variants = expandXaiRootObjectSchemas(resolved);
|
|
722
1052
|
if (!variants) return undefined;
|
|
723
|
-
if (variants.length === 1)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
1053
|
+
if (variants.length === 1) {
|
|
1054
|
+
return xaiVariantIsConcreteObject(variants[0]) ? variants[0] : undefined;
|
|
1055
|
+
}
|
|
1056
|
+
if (!variants.every(xaiVariantIsConcreteObject) || !xaiRequiredSetsMatch(variants)) return undefined;
|
|
1057
|
+
const additionalProperties = mergeXaiAdditionalProperties(variants);
|
|
1058
|
+
if (!additionalProperties.ok) return undefined;
|
|
1059
|
+
if (!xaiPropertyMergeIsLossless(variants)) return undefined;
|
|
1060
|
+
|
|
1061
|
+
const metadata = Object.fromEntries(Object.entries(resolved).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
|
|
1062
|
+
delete metadata.properties;
|
|
1063
|
+
delete metadata.required;
|
|
1064
|
+
delete metadata.additionalProperties;
|
|
1065
|
+
|
|
1066
|
+
const propertyValues = new Map<string, unknown[]>();
|
|
1067
|
+
for (const variant of variants) {
|
|
1068
|
+
if (!variant.properties || typeof variant.properties !== "object" || Array.isArray(variant.properties)) continue;
|
|
1069
|
+
for (const [name, value] of Object.entries(variant.properties as Record<string, unknown>)) {
|
|
1070
|
+
const values = propertyValues.get(name) ?? [];
|
|
1071
|
+
values.push(value);
|
|
1072
|
+
propertyValues.set(name, values);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const properties = Object.fromEntries(
|
|
1076
|
+
[...propertyValues].map(([name, values]) => [name, mergeXaiPropertySchemas(values)]),
|
|
1077
|
+
);
|
|
1078
|
+
const required = stringRequiredFields(variants[0]?.required);
|
|
1079
|
+
|
|
1080
|
+
return {
|
|
1081
|
+
...metadata,
|
|
1082
|
+
type: "object",
|
|
1083
|
+
properties,
|
|
1084
|
+
...(required.length > 0 ? { required } : {}),
|
|
1085
|
+
...("value" in additionalProperties ? { additionalProperties: additionalProperties.value } : {}),
|
|
1086
|
+
};
|
|
729
1087
|
}
|
|
730
1088
|
|
|
731
1089
|
function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig): unknown[] | undefined {
|
|
@@ -734,9 +1092,9 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
|
|
|
734
1092
|
if (tools.length === 0) return undefined;
|
|
735
1093
|
const xaiTarget = isXaiSchemaTarget(provider);
|
|
736
1094
|
const formatted = tools.flatMap(t => {
|
|
737
|
-
const parameters = xaiTarget
|
|
1095
|
+
const parameters = stripResponsesOnlyEncryptedMarker(xaiTarget
|
|
738
1096
|
? normalizeXaiToolParameters(t.parameters)
|
|
739
|
-
: ensureRootObjectType(t.parameters);
|
|
1097
|
+
: ensureRootObjectType(t.parameters));
|
|
740
1098
|
|
|
741
1099
|
if (parameters === undefined) return [];
|
|
742
1100
|
return [{
|
|
@@ -825,10 +1183,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
825
1183
|
formatErrorBody: formatOpenAIChatErrorBody,
|
|
826
1184
|
|
|
827
1185
|
buildRequest(parsed: OcxParsedRequest) {
|
|
828
|
-
const
|
|
829
|
-
if ((provider.authMode === "key" || provider.authMode === "oauth") && !provider.keyOptional && !hasCredential) {
|
|
830
|
-
throw new Error(`${provider.adapter} requires a non-empty credential (authMode: ${provider.authMode})`);
|
|
831
|
-
}
|
|
1186
|
+
const { url, headers, hasCredential } = openAIChatTransport(provider);
|
|
832
1187
|
|
|
833
1188
|
const messages = messagesToChatFormat(parsed, provider);
|
|
834
1189
|
const tools = toolsToChatFormatForProvider(parsed, provider);
|
|
@@ -844,11 +1199,11 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
844
1199
|
// an explicit value here makes the Responses parser's serviceTier projection ineffective.
|
|
845
1200
|
//
|
|
846
1201
|
// Opt-in, like `prompt_cache_key` directly below: `service_tier` is an OpenAI-specific
|
|
847
|
-
// extension and 66 registry providers share this adapter
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
|
|
851
|
-
|
|
1202
|
+
// extension and 66 registry providers share this adapter. A provider-wide Chat opt-in
|
|
1203
|
+
// authorizes undeclared models; an exact model declaration can authorize or deny one
|
|
1204
|
+
// model. Provider-level false remains fail-closed.
|
|
1205
|
+
if (canSerializeServiceTierForChatModel(provider, parsed.modelId)
|
|
1206
|
+
&& parsed.options.serviceTier !== undefined) {
|
|
852
1207
|
body.service_tier = parsed.options.serviceTier;
|
|
853
1208
|
}
|
|
854
1209
|
if (modelInList(provider.reasoningSplitModels, parsed.modelId)) body.reasoning_split = true;
|
|
@@ -981,11 +1336,6 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
981
1336
|
}
|
|
982
1337
|
if (parsed.stream) body.stream_options = { include_usage: true };
|
|
983
1338
|
|
|
984
|
-
const url = openaiChatCompletionsUrl(provider.baseUrl);
|
|
985
|
-
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
986
|
-
if (hasCredential) headers["Authorization"] = `Bearer ${provider.apiKey}`;
|
|
987
|
-
if (provider.headers) Object.assign(headers, provider.headers);
|
|
988
|
-
|
|
989
1339
|
const bodyJson = JSON.stringify(body);
|
|
990
1340
|
if (isDebugEnabled()) {
|
|
991
1341
|
let host = "upstream";
|
|
@@ -1153,13 +1503,15 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1153
1503
|
}
|
|
1154
1504
|
const rawName = rawFunction.name;
|
|
1155
1505
|
const rawArguments = rawFunction.arguments;
|
|
1156
|
-
|
|
1157
|
-
|
|
1506
|
+
// Some OpenAI-compatible streamers repeat already-sent fields as null on
|
|
1507
|
+
// continuation deltas. Treat only null/undefined as absent; every other
|
|
1508
|
+
// non-string value still fails closed before entering the accumulator.
|
|
1509
|
+
if (isInvalidStreamStringField(rawName) || isInvalidStreamStringField(rawArguments)) {
|
|
1158
1510
|
logInvalidToolCalls("stream", rawToolCalls);
|
|
1159
1511
|
return yield* terminateWithError(invalidToolCallsEvent(pendingUsage));
|
|
1160
1512
|
}
|
|
1161
1513
|
}
|
|
1162
|
-
if (tc.id
|
|
1514
|
+
if (isInvalidStreamStringField(tc.id)) {
|
|
1163
1515
|
logInvalidToolCalls("stream", rawToolCalls);
|
|
1164
1516
|
return yield* terminateWithError(invalidToolCallsEvent(pendingUsage));
|
|
1165
1517
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createAnthropicAdapter } from "./anthropic";
|
|
2
|
+
import { createAzureAdapter } from "./azure";
|
|
3
|
+
import type { ProviderAdapter } from "./base";
|
|
4
|
+
import { createCommandCodeAdapter } from "./command-code";
|
|
5
|
+
import { createCursorAdapter } from "./cursor";
|
|
6
|
+
import { createGoogleAdapter } from "./google";
|
|
7
|
+
import { createKiroAdapter } from "./kiro";
|
|
8
|
+
import { createMimoFreeAdapter } from "./mimo-free";
|
|
9
|
+
import { createOpenAIChatAdapter } from "./openai-chat";
|
|
10
|
+
import { createResponsesPassthroughAdapter } from "./openai-responses";
|
|
11
|
+
import type { OcxProviderConfig } from "../types";
|
|
12
|
+
|
|
13
|
+
export type AdapterCacheRetention = "none" | "short" | "long";
|
|
14
|
+
|
|
15
|
+
export interface AdapterFactoryContext {
|
|
16
|
+
cacheRetention?: AdapterCacheRetention;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type AdapterWire =
|
|
20
|
+
| "command-code"
|
|
21
|
+
| "openai-chat"
|
|
22
|
+
| "anthropic"
|
|
23
|
+
| "openai-responses"
|
|
24
|
+
| "google"
|
|
25
|
+
| "kiro"
|
|
26
|
+
| "cursor";
|
|
27
|
+
|
|
28
|
+
export type AdapterMutationContract =
|
|
29
|
+
| "codex-owned"
|
|
30
|
+
| "codex-owned-with-gated-native-fallback";
|
|
31
|
+
|
|
32
|
+
type AdapterFactory = (
|
|
33
|
+
provider: OcxProviderConfig,
|
|
34
|
+
context: AdapterFactoryContext,
|
|
35
|
+
) => ProviderAdapter;
|
|
36
|
+
|
|
37
|
+
type DirectAdapterDefinition = {
|
|
38
|
+
wire: AdapterWire;
|
|
39
|
+
mutation: AdapterMutationContract;
|
|
40
|
+
create: AdapterFactory;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type InheritedAdapterDefinition = {
|
|
44
|
+
/** Semantic contract inheritance only. Runtime construction remains independent. */
|
|
45
|
+
contractParent: string;
|
|
46
|
+
create: AdapterFactory;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type AdapterDefinition = DirectAdapterDefinition | InheritedAdapterDefinition;
|
|
50
|
+
|
|
51
|
+
export const ADAPTER_REGISTRY = {
|
|
52
|
+
"command-code": {
|
|
53
|
+
wire: "command-code",
|
|
54
|
+
mutation: "codex-owned",
|
|
55
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createCommandCodeAdapter(provider),
|
|
56
|
+
},
|
|
57
|
+
"openai-chat": {
|
|
58
|
+
wire: "openai-chat",
|
|
59
|
+
mutation: "codex-owned",
|
|
60
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createOpenAIChatAdapter(provider),
|
|
61
|
+
},
|
|
62
|
+
anthropic: {
|
|
63
|
+
wire: "anthropic",
|
|
64
|
+
mutation: "codex-owned",
|
|
65
|
+
create: (provider: OcxProviderConfig, context: AdapterFactoryContext) =>
|
|
66
|
+
createAnthropicAdapter(provider, context.cacheRetention),
|
|
67
|
+
},
|
|
68
|
+
"openai-responses": {
|
|
69
|
+
wire: "openai-responses",
|
|
70
|
+
mutation: "codex-owned",
|
|
71
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) =>
|
|
72
|
+
createResponsesPassthroughAdapter(provider),
|
|
73
|
+
},
|
|
74
|
+
google: {
|
|
75
|
+
wire: "google",
|
|
76
|
+
mutation: "codex-owned",
|
|
77
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createGoogleAdapter(provider),
|
|
78
|
+
},
|
|
79
|
+
kiro: {
|
|
80
|
+
wire: "kiro",
|
|
81
|
+
mutation: "codex-owned",
|
|
82
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createKiroAdapter(provider),
|
|
83
|
+
},
|
|
84
|
+
azure: {
|
|
85
|
+
contractParent: "openai-responses",
|
|
86
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createAzureAdapter(provider),
|
|
87
|
+
},
|
|
88
|
+
"azure-openai": {
|
|
89
|
+
contractParent: "openai-responses",
|
|
90
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createAzureAdapter(provider),
|
|
91
|
+
},
|
|
92
|
+
cursor: {
|
|
93
|
+
wire: "cursor",
|
|
94
|
+
mutation: "codex-owned-with-gated-native-fallback",
|
|
95
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createCursorAdapter(provider),
|
|
96
|
+
},
|
|
97
|
+
"mimo-free": {
|
|
98
|
+
contractParent: "openai-chat",
|
|
99
|
+
create: (provider: OcxProviderConfig, _context: AdapterFactoryContext) => createMimoFreeAdapter(provider),
|
|
100
|
+
},
|
|
101
|
+
} as const satisfies Record<string, AdapterDefinition>;
|
|
102
|
+
|
|
103
|
+
export type AdapterId = keyof typeof ADAPTER_REGISTRY;
|
|
104
|
+
export type RegisteredAdapterDefinition = typeof ADAPTER_REGISTRY[AdapterId];
|
|
105
|
+
|
|
106
|
+
export function adapterDefinitions(): Array<[AdapterId, RegisteredAdapterDefinition]> {
|
|
107
|
+
return Object.entries(ADAPTER_REGISTRY) as Array<[AdapterId, RegisteredAdapterDefinition]>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getAdapterDefinition(adapterId: unknown): RegisteredAdapterDefinition | undefined {
|
|
111
|
+
if (typeof adapterId !== "string" || !Object.hasOwn(ADAPTER_REGISTRY, adapterId)) return undefined;
|
|
112
|
+
return ADAPTER_REGISTRY[adapterId as AdapterId];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function effectiveAdapterContract(adapterId: string): Readonly<{
|
|
116
|
+
wire: AdapterWire;
|
|
117
|
+
mutation: AdapterMutationContract;
|
|
118
|
+
}> {
|
|
119
|
+
const visited = new Set<string>();
|
|
120
|
+
let current = adapterId;
|
|
121
|
+
|
|
122
|
+
while (true) {
|
|
123
|
+
if (visited.has(current)) {
|
|
124
|
+
throw new Error(`Adapter contract cycle detected at ${current}`);
|
|
125
|
+
}
|
|
126
|
+
visited.add(current);
|
|
127
|
+
|
|
128
|
+
const definition = getAdapterDefinition(current);
|
|
129
|
+
if (!definition) throw new Error(`Unknown adapter: ${current}`);
|
|
130
|
+
if ("wire" in definition) {
|
|
131
|
+
return { wire: definition.wire, mutation: definition.mutation };
|
|
132
|
+
}
|
|
133
|
+
current = definition.contractParent;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createRegisteredAdapter(
|
|
138
|
+
provider: OcxProviderConfig,
|
|
139
|
+
context: AdapterFactoryContext = {},
|
|
140
|
+
): ProviderAdapter {
|
|
141
|
+
const definition = getAdapterDefinition(provider.adapter);
|
|
142
|
+
if (!definition) throw new Error(`Unknown adapter: ${provider.adapter}`);
|
|
143
|
+
return definition.create(provider, context);
|
|
144
|
+
}
|