@bitkyc08/opencodex 2.69.0 → 2.70.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/App-_MrbTrBe.js +51 -0
- package/gui/dist/assets/{Tray-uqKRqfvw.js → Tray-5PuUalET.js} +1 -1
- package/gui/dist/assets/index-DKrO2eF3.js +86 -0
- package/gui/dist/assets/quota-summary-BCLivc0Q.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-model-contract.ts +119 -0
- package/src/adapters/anthropic.ts +14 -83
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +542 -0
- package/src/adapters/command-code-tool-text.ts +99 -67
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +91 -0
- package/src/adapters/devin/cloud-direct/chat.ts +78 -48
- package/src/adapters/devin/cloud-direct/tool-schema.ts +121 -0
- package/src/adapters/devin/context-overflow.ts +75 -0
- package/src/adapters/devin/live-models.ts +174 -13
- package/src/adapters/devin/reasoning-signature.ts +100 -0
- package/src/adapters/devin.ts +310 -117
- package/src/adapters/kiro/payload.ts +10 -10
- package/src/adapters/kiro-images.ts +58 -7
- package/src/adapters/openai-chat/passthrough.ts +3 -1
- package/src/adapters/openai-chat.ts +2 -1
- package/src/adapters/openai-responses/passthrough.ts +2 -1
- package/src/bridge/response-json.ts +15 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/cli/account-extended.ts +31 -1
- package/src/cli/account.ts +3 -0
- package/src/cli/capabilities.ts +13 -0
- package/src/cli/client-start-fence.ts +36 -0
- package/src/cli/dispatch.ts +3 -2
- package/src/cli/index.ts +98 -117
- package/src/cli/provider-runtime.ts +23 -2
- package/src/cli/provider.ts +3 -0
- package/src/cli/resolve.ts +18 -0
- package/src/cli/start-owner-fence.ts +30 -0
- package/src/cli/stop-approval.ts +13 -1
- package/src/cli/stop-restore.ts +73 -0
- package/src/cli/tray-proxy.ts +221 -18
- package/src/cli/version-skew.ts +25 -23
- package/src/client/runtime.ts +8 -4
- package/src/codex/auth-api/account-list.ts +11 -5
- package/src/codex/auth-api/login-flow.ts +5 -3
- package/src/codex/auth-api/main-account-probe.ts +186 -110
- package/src/codex/auth-api/pool-mode-gate.ts +61 -7
- package/src/codex/auth-api/pool-quota-probe.ts +142 -90
- package/src/codex/auth-api/reset-credit-service.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +1 -0
- package/src/codex/catalog/model-hints.ts +3 -3
- package/src/codex/catalog/provider-models.ts +2 -0
- package/src/codex/low-quota-events.ts +28 -0
- package/src/codex/low-quota-observer.ts +23 -0
- package/src/codex/low-quota-protection.ts +272 -0
- package/src/codex/quota-query-backoff.ts +148 -0
- package/src/codex/quota.ts +7 -2
- package/src/config/diagnostics.ts +10 -1
- package/src/config/load-degrade.ts +17 -0
- package/src/config/mutation-lock.ts +3 -4
- package/src/config/process-state.ts +1 -1
- package/src/config/provider-validation.ts +22 -0
- package/src/config/schema/blocked-model-redirects.ts +17 -0
- package/src/config/schema/config-schema.ts +4 -1
- package/src/config/schema/leaf-validators.ts +33 -2
- package/src/config/serving-runtimes.ts +412 -0
- package/src/generated/compatibility-version.json +211 -115
- package/src/generated/model-metadata.ts +3 -3
- package/src/images/loop.ts +3 -0
- package/src/lib/errors.ts +2 -1
- package/src/lib/handled-signal-exit.ts +23 -0
- package/src/lib/request-failure-model.ts +1 -0
- package/src/lib/self-launch-argv.ts +9 -0
- package/src/lib/shadow-call.ts +5 -2
- package/src/lib/strict-semver.ts +26 -0
- package/src/lib/transient-service-path.ts +16 -0
- package/src/lib/windows-service-wrappers.ts +10 -6
- package/src/lib/winsw.ts +3 -1
- package/src/oauth/anthropic-model-routes.ts +105 -0
- package/src/oauth/anthropic-routing.ts +85 -48
- package/src/oauth/devin.ts +135 -5
- package/src/oauth/index.ts +20 -3
- package/src/oauth/kiro-terminal-failover.ts +23 -9
- package/src/oauth/pool-settings-capability.ts +10 -2
- package/src/oauth/store.ts +1 -1
- package/src/protocols/encoders/adapter-events.ts +3 -1
- package/src/providers/github-copilot-context.ts +27 -0
- package/src/providers/kiro-models.ts +4 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/quota/account-cache.ts +8 -3
- package/src/providers/quota/devin.ts +286 -0
- package/src/providers/quota/vendor-probes-oauth.ts +6 -2
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +1 -1
- package/src/providers/registry/model-seeds.ts +5 -2
- package/src/responses/compaction-images.ts +27 -0
- package/src/router.ts +104 -16
- package/src/routing/analytics.ts +1 -0
- package/src/server/auth-cors.ts +9 -1
- package/src/server/background-lifecycle.ts +21 -10
- package/src/server/chat-completions.ts +2 -1
- package/src/server/chat-native.ts +1 -0
- package/src/server/index.ts +4 -8
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/management/context.ts +3 -0
- package/src/server/management/low-quota-routes.ts +14 -0
- package/src/server/management/oauth-account-routes.ts +21 -1
- package/src/server/management/provider-overwrite-carry.ts +79 -6
- package/src/server/management/provider-routes.ts +41 -28
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management-api.ts +7 -0
- package/src/server/responses/adapter-continuation.ts +3 -0
- package/src/server/responses/adapter-delivery.ts +3 -0
- package/src/server/responses/adapter-dispatch.ts +20 -4
- package/src/server/responses/antigravity-validation-refusal.ts +20 -0
- package/src/server/responses/core-auth.ts +1 -0
- package/src/server/responses/core-codex-account.ts +1 -0
- package/src/server/responses/core-normalize.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +8 -1
- package/src/server/responses/request-sidecar-auth.ts +2 -0
- package/src/server/responses/request-transport.ts +27 -10
- package/src/server/responses/response-effects.ts +4 -1
- package/src/server/responses/run-turn-execution.ts +126 -42
- package/src/server/responses/sidecar-execution.ts +4 -1
- package/src/service/diagnostics.ts +3 -1
- package/src/service/guarded-manager-target.ts +39 -12
- package/src/service/launchd.ts +14 -6
- package/src/service/repair.ts +6 -1
- package/src/service/service-child-ownership.ts +168 -0
- package/src/service/state.ts +22 -4
- package/src/service/systemd.ts +2 -2
- package/src/service/windows-guarded-manager.ts +530 -0
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service.ts +2 -0
- package/src/types/config.ts +26 -0
- package/src/types/provider.ts +12 -0
- package/src/types/request.ts +2 -0
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +13 -0
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/vision/anthropic-describe.ts +2 -1
- package/src/web-search/anthropic-executor.ts +2 -1
- package/src/web-search/loop.ts +1 -0
- package/gui/dist/assets/App-DDbgOTh6.js +0 -51
- package/gui/dist/assets/index-CuaBJkG4.js +0 -86
- package/gui/dist/assets/quota-summary-COppQSVq.js +0 -1
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { nonBlankStringArrayConfigError, normalizeNonBlankStringArray } from "../../config/provider-validation";
|
|
2
|
+
import {
|
|
3
|
+
retryOn429PolicyConfigError,
|
|
4
|
+
retryOnResetPolicyConfigError,
|
|
5
|
+
transientRetryOn5xxPolicyConfigError,
|
|
6
|
+
} from "../../config/load-degrade";
|
|
2
7
|
import type { OcxProviderConfig } from "../../types";
|
|
3
8
|
|
|
9
|
+
/**
|
|
10
|
+
* The retry policies. They are the one carried group the PATCH field mask did not know, and they
|
|
11
|
+
* are `"editor"` fields, so before this a save that dropped one left no API path to put it back.
|
|
12
|
+
* Each validator is the same strict check the loader's schema applies, so the mask cannot store a
|
|
13
|
+
* block that a later load would reject.
|
|
14
|
+
*/
|
|
15
|
+
const RETRY_POLICY_FIELDS = ["retryOn429", "transientRetryOn5xx", "retryOnReset"] as const satisfies
|
|
16
|
+
readonly (keyof OcxProviderConfig)[];
|
|
17
|
+
|
|
18
|
+
const RETRY_POLICY_VALIDATORS = {
|
|
19
|
+
retryOn429: retryOn429PolicyConfigError,
|
|
20
|
+
transientRetryOn5xx: transientRetryOn5xxPolicyConfigError,
|
|
21
|
+
retryOnReset: retryOnResetPolicyConfigError,
|
|
22
|
+
} as const satisfies Record<typeof RETRY_POLICY_FIELDS[number], (policy: unknown) => string | null>;
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* What a provider save keeps (#5563).
|
|
6
26
|
*
|
|
@@ -11,13 +31,22 @@ import type { OcxProviderConfig } from "../../types";
|
|
|
11
31
|
* them afterwards: the disk row equals its baseline, so the three-way merge keeps the value the
|
|
12
32
|
* save wrote.
|
|
13
33
|
*
|
|
14
|
-
*
|
|
34
|
+
* `hideRawReasoning` is an operator display policy, so every overwrite of the same provider name
|
|
35
|
+
* carries it when omitted, even when the destination changes. The compatibility settings below
|
|
36
|
+
* record how one upstream behaves. An overwrite that keeps the destination
|
|
15
37
|
* carries each one the request omits, including an explicit `[]` or `false`. An overwrite that
|
|
16
38
|
* moves the provider to another destination carries none of them: they describe the previous
|
|
17
39
|
* upstream, and registry enrichment already fills what is known about the new one. The same
|
|
18
40
|
* destination rule gates the stored `apiKeyPool`, whose keys were issued for the previous
|
|
19
41
|
* destination. A value the request sends always wins, and the rest of the old row is never merged
|
|
20
42
|
* into the candidate.
|
|
43
|
+
*
|
|
44
|
+
* The three retry policies join them on the same terms, and for a sharper reason: they are
|
|
45
|
+
* `"editor"` fields that had no branch in the PATCH field mask, so a save that dropped one left no
|
|
46
|
+
* API path to put it back. The mask branch below restores that path, and validates each policy
|
|
47
|
+
* against the loader's own schema — PATCH never runs the schema, and a malformed block would not
|
|
48
|
+
* degrade to "absent": the load would fail and the default config would take the whole provider
|
|
49
|
+
* table.
|
|
21
50
|
*/
|
|
22
51
|
export const PROVIDER_COMPAT_CARRY_FIELDS = [
|
|
23
52
|
"preserveReasoningContentModels",
|
|
@@ -25,9 +54,13 @@ export const PROVIDER_COMPAT_CARRY_FIELDS = [
|
|
|
25
54
|
"foldDeveloperRoleToSystem",
|
|
26
55
|
"reasoningWireFormat",
|
|
27
56
|
"omitReasoningEffortWithToolsModels",
|
|
57
|
+
...RETRY_POLICY_FIELDS,
|
|
28
58
|
] as const satisfies readonly (keyof OcxProviderConfig)[];
|
|
29
59
|
|
|
60
|
+
export const PROVIDER_DISPLAY_CARRY_FIELDS = ["hideRawReasoning"] as const satisfies readonly (keyof OcxProviderConfig)[];
|
|
61
|
+
|
|
30
62
|
export type ProviderCompatCarryField = typeof PROVIDER_COMPAT_CARRY_FIELDS[number];
|
|
63
|
+
type ProviderDisplayCarryField = typeof PROVIDER_DISPLAY_CARRY_FIELDS[number];
|
|
31
64
|
|
|
32
65
|
/** The only `reasoningWireFormat` value the adapters understand. */
|
|
33
66
|
export const PROVIDER_REASONING_WIRE_FORMATS: readonly NonNullable<OcxProviderConfig["reasoningWireFormat"]>[] = [
|
|
@@ -39,13 +72,13 @@ export const PROVIDER_REASONING_WIRE_FORMATS: readonly NonNullable<OcxProviderCo
|
|
|
39
72
|
* omitted this" and "the registry supplied it" look the same.
|
|
40
73
|
*/
|
|
41
74
|
export interface ProviderOverwriteSample {
|
|
42
|
-
readonly submitted: ReadonlySet<ProviderCompatCarryField>;
|
|
75
|
+
readonly submitted: ReadonlySet<ProviderCompatCarryField | ProviderDisplayCarryField>;
|
|
43
76
|
readonly namesAuthMode: boolean;
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
export function sampleProviderOverwrite(provider: object): ProviderOverwriteSample {
|
|
47
80
|
return {
|
|
48
|
-
submitted: new Set(PROVIDER_COMPAT_CARRY_FIELDS.filter(field => Object.hasOwn(provider, field))),
|
|
81
|
+
submitted: new Set([...PROVIDER_COMPAT_CARRY_FIELDS, ...PROVIDER_DISPLAY_CARRY_FIELDS].filter(field => Object.hasOwn(provider, field))),
|
|
49
82
|
namesAuthMode: Object.hasOwn(provider, "authMode"),
|
|
50
83
|
};
|
|
51
84
|
}
|
|
@@ -81,7 +114,8 @@ export function providerOverwriteKeepsDestination(
|
|
|
81
114
|
}
|
|
82
115
|
|
|
83
116
|
/**
|
|
84
|
-
* Carry the stored
|
|
117
|
+
* Carry the stored display policy on every overwrite, then the omitted compatibility settings
|
|
118
|
+
* only when the destination is unchanged. `live` must be the row read after
|
|
85
119
|
* the route's DNS await, so a PATCH that saved one of them during the wait is kept.
|
|
86
120
|
*/
|
|
87
121
|
export function carryProviderCompatFields(
|
|
@@ -89,13 +123,25 @@ export function carryProviderCompatFields(
|
|
|
89
123
|
live: OcxProviderConfig | undefined,
|
|
90
124
|
sample: ProviderOverwriteSample,
|
|
91
125
|
): void {
|
|
92
|
-
if (!live
|
|
126
|
+
if (!live) return;
|
|
93
127
|
const target = candidate as unknown as Record<string, unknown>;
|
|
128
|
+
for (const field of PROVIDER_DISPLAY_CARRY_FIELDS) {
|
|
129
|
+
if (sample.submitted.has(field)) continue;
|
|
130
|
+
const stored = live[field];
|
|
131
|
+
if (stored !== undefined) target[field] = stored;
|
|
132
|
+
}
|
|
133
|
+
if (!providerOverwriteKeepsDestination(candidate, live, sample)) return;
|
|
94
134
|
for (const field of PROVIDER_COMPAT_CARRY_FIELDS) {
|
|
95
135
|
if (sample.submitted.has(field)) continue;
|
|
96
136
|
const stored = live[field];
|
|
97
137
|
if (stored === undefined) continue;
|
|
98
|
-
|
|
138
|
+
// Arrays and the retry-policy objects are copied rather than aliased, like `requestPacing`
|
|
139
|
+
// in the route: a later edit of one row must not reach the other through a shared value.
|
|
140
|
+
target[field] = Array.isArray(stored)
|
|
141
|
+
? [...stored]
|
|
142
|
+
: stored !== null && typeof stored === "object"
|
|
143
|
+
? structuredClone(stored)
|
|
144
|
+
: stored;
|
|
99
145
|
}
|
|
100
146
|
}
|
|
101
147
|
|
|
@@ -112,6 +158,8 @@ export function providerCompatFieldConfigError(provider: Record<string, unknown>
|
|
|
112
158
|
}
|
|
113
159
|
const fold = provider.foldDeveloperRoleToSystem;
|
|
114
160
|
if (fold !== undefined && typeof fold !== "boolean") return "foldDeveloperRoleToSystem must be a boolean";
|
|
161
|
+
const hideRawReasoning = provider.hideRawReasoning;
|
|
162
|
+
if (hideRawReasoning !== undefined && typeof hideRawReasoning !== "boolean") return "hideRawReasoning must be a boolean";
|
|
115
163
|
const wire = provider.reasoningWireFormat;
|
|
116
164
|
if (wire !== undefined && !PROVIDER_REASONING_WIRE_FORMATS.includes(wire as never)) {
|
|
117
165
|
return `reasoningWireFormat must be one of: ${PROVIDER_REASONING_WIRE_FORMATS.join(", ")}`;
|
|
@@ -149,6 +197,13 @@ export function applyProviderCompatPatchFields(
|
|
|
149
197
|
else return { error: "foldDeveloperRoleToSystem must be a boolean" };
|
|
150
198
|
touched = true;
|
|
151
199
|
}
|
|
200
|
+
if (Object.hasOwn(rawBody, "hideRawReasoning")) {
|
|
201
|
+
const value = rawBody.hideRawReasoning;
|
|
202
|
+
if (value === null) delete next.hideRawReasoning;
|
|
203
|
+
else if (typeof value === "boolean") next.hideRawReasoning = value;
|
|
204
|
+
else return { error: "hideRawReasoning must be a boolean" };
|
|
205
|
+
touched = true;
|
|
206
|
+
}
|
|
152
207
|
if (Object.hasOwn(rawBody, "reasoningWireFormat")) {
|
|
153
208
|
const value = rawBody.reasoningWireFormat;
|
|
154
209
|
if (value === null) {
|
|
@@ -160,5 +215,23 @@ export function applyProviderCompatPatchFields(
|
|
|
160
215
|
}
|
|
161
216
|
touched = true;
|
|
162
217
|
}
|
|
218
|
+
// The retry policies. They are validated here, like `requestPacing` and `upstreamHttpVersion`,
|
|
219
|
+
// because PATCH does not run the config schema: only POST does, through
|
|
220
|
+
// `validateConfigCandidate`. A malformed block that reached disk would fail the load and take
|
|
221
|
+
// the whole provider table to the default config with it, so the mask refuses it rather than
|
|
222
|
+
// storing something the loader will reject. `null` leaves the row entirely: the schema reads an
|
|
223
|
+
// absent block as "off", so deleting the key is what disables one, not storing `{enabled:false}`.
|
|
224
|
+
for (const field of RETRY_POLICY_FIELDS) {
|
|
225
|
+
if (!Object.hasOwn(rawBody, field)) continue;
|
|
226
|
+
const value = rawBody[field];
|
|
227
|
+
if (value === null) {
|
|
228
|
+
delete next[field];
|
|
229
|
+
} else {
|
|
230
|
+
const error = RETRY_POLICY_VALIDATORS[field](value);
|
|
231
|
+
if (error) return { error };
|
|
232
|
+
next[field] = structuredClone(value) as OcxProviderConfig[typeof field];
|
|
233
|
+
}
|
|
234
|
+
touched = true;
|
|
235
|
+
}
|
|
163
236
|
return { touched };
|
|
164
237
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
|
|
1
|
+
import { contextTierRecordConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
|
|
2
2
|
import { DECLARABLE_HOSTED_TOOL_TYPES } from "../../responses/hosted-tool-policy";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
@@ -530,6 +530,13 @@ function applyProviderPatchFields(
|
|
|
530
530
|
else next.modelCapabilities = capabilities;
|
|
531
531
|
touched = true;
|
|
532
532
|
}
|
|
533
|
+
if (Object.hasOwn(rawBody, "modelContextTiers")) {
|
|
534
|
+
const error = contextTierRecordConfigError(rawBody.modelContextTiers, true);
|
|
535
|
+
if (error) return { error };
|
|
536
|
+
if (rawBody.modelContextTiers === null) delete next.modelContextTiers;
|
|
537
|
+
else next.modelContextTiers = Object.assign(Object.create(null), next.modelContextTiers ?? {}, rawBody.modelContextTiers);
|
|
538
|
+
touched = true;
|
|
539
|
+
}
|
|
533
540
|
if (Object.hasOwn(rawBody, "modelContextWindows")) {
|
|
534
541
|
const value = rawBody.modelContextWindows;
|
|
535
542
|
if (value === null) {
|
|
@@ -933,6 +940,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
933
940
|
models: p.models ?? [],
|
|
934
941
|
contextWindow: p.contextWindow,
|
|
935
942
|
modelContextWindows: p.modelContextWindows,
|
|
943
|
+
modelContextTiers: p.modelContextTiers,
|
|
936
944
|
modelCapabilities: p.modelCapabilities,
|
|
937
945
|
pinnedReasoningEffort: p.pinnedReasoningEffort,
|
|
938
946
|
modelPinnedReasoningEfforts: p.modelPinnedReasoningEfforts,
|
|
@@ -1213,6 +1221,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1213
1221
|
// call can never fire.
|
|
1214
1222
|
const submittedContextWindow = Object.hasOwn(prov, "contextWindow");
|
|
1215
1223
|
const submittedModelContextWindows = Object.hasOwn(prov, "modelContextWindows");
|
|
1224
|
+
const submittedModelContextTiers = Object.hasOwn(prov, "modelContextTiers");
|
|
1216
1225
|
const submittedModelAutoCompactTokenLimits = Object.hasOwn(prov, "modelAutoCompactTokenLimits");
|
|
1217
1226
|
const submittedModelDisplayNames = Object.hasOwn(prov, "modelDisplayNames");
|
|
1218
1227
|
const submittedRequestPacing = Object.hasOwn(prov, "requestPacing");
|
|
@@ -1298,7 +1307,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1298
1307
|
if (!submittedFastEnabled && liveFastEnabled !== undefined) prov.fastEnabled = liveFastEnabled;
|
|
1299
1308
|
// The form sends none of the compatibility settings either (#5563). Read the live row rather
|
|
1300
1309
|
// than `existing`, like the alias overlays below: a PATCH that saved one of them while DNS
|
|
1301
|
-
// validation awaited must not be undone.
|
|
1310
|
+
// validation awaited must not be undone. Only the hideRawReasoning display policy follows the
|
|
1311
|
+
// provider to a new destination; the upstream compatibility settings do not.
|
|
1302
1312
|
carryProviderCompatFields(prov, config.providers[name], overwriteSample);
|
|
1303
1313
|
if (existing?.modelContextWindows) {
|
|
1304
1314
|
// When the client did send a map, its keys win and the user's other keys survive. When
|
|
@@ -1342,48 +1352,51 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1342
1352
|
prov.zaiResponsesDefaultVersion = latest.zaiResponsesDefaultVersion;
|
|
1343
1353
|
}
|
|
1344
1354
|
}
|
|
1345
|
-
// Reapply pins
|
|
1346
|
-
// complete draft before adopting any provider/default state.
|
|
1355
|
+
// Reapply pins after DNS/import awaits; validate the complete draft before adoption.
|
|
1347
1356
|
const latest = config.providers[name];
|
|
1348
1357
|
const latestPinError = applyProviderPinFields(prov, body.provider, latest);
|
|
1349
1358
|
if (latestPinError) return jsonResponse({ error: latestPinError }, 400);
|
|
1350
1359
|
const pinsOwned = Object.hasOwn(body.provider, "pinnedReasoningEffort")
|
|
1351
1360
|
|| Object.hasOwn(body.provider, "modelPinnedReasoningEfforts")
|
|
1352
1361
|
|| latest?.pinnedReasoningEffort !== undefined || latest?.modelPinnedReasoningEfforts !== undefined;
|
|
1353
|
-
//
|
|
1354
|
-
// side effects with the registration draft instead of mutating live state
|
|
1355
|
-
// before validation.
|
|
1362
|
+
// Stage new-registration discovery state in a draft until validation passes.
|
|
1356
1363
|
const registrationDraft = !latest ? {
|
|
1357
1364
|
...config,
|
|
1358
1365
|
...(config.modelDiscovery === undefined ? {} : { modelDiscovery: structuredClone(config.modelDiscovery) }),
|
|
1359
1366
|
} : undefined;
|
|
1360
1367
|
initializeProviderModelSelection(name, prov, latest, registrationDraft ?? config);
|
|
1361
1368
|
const candidate = stripRegistryOnlyStaticHeaders(name, prov);
|
|
1362
|
-
const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate },
|
|
1363
|
-
...(body.setDefault === true ? { defaultProvider: name } : {}) };
|
|
1364
|
-
const validation = validateConfigCandidate(draft);
|
|
1365
|
-
if (!validation.ok) return jsonResponse({ error: validation.error }, 400);
|
|
1366
1369
|
const previous = Object.getOwnPropertyDescriptor(config.providers, name);
|
|
1367
1370
|
const rollback = pinsOwned ? captureConfigTopLevelRollback(config, ["defaultProvider", "modelDiscovery", "disabledModels"]) : undefined;
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1371
|
+
let validationError: string | undefined;
|
|
1372
|
+
withConfigMutationLockSync(() => {
|
|
1373
|
+
const liveTiers = config.providers[name]?.modelContextTiers;
|
|
1374
|
+
if (submittedModelContextTiers && liveTiers) candidate.modelContextTiers = Object.assign(Object.create(null), liveTiers, candidate.modelContextTiers ?? {});
|
|
1375
|
+
else if (!submittedModelContextTiers && liveTiers) candidate.modelContextTiers = { ...liveTiers };
|
|
1376
|
+
else if (!submittedModelContextTiers) delete candidate.modelContextTiers;
|
|
1377
|
+
const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate }, ...(body.setDefault === true ? { defaultProvider: name } : {}) };
|
|
1378
|
+
const validation = validateConfigCandidate(draft); if (!validation.ok) { validationError = validation.error; return; }
|
|
1379
|
+
try {
|
|
1380
|
+
if (registrationDraft) {
|
|
1381
|
+
for (const key of ["modelDiscovery", "disabledModels"] as const) {
|
|
1382
|
+
if (Object.hasOwn(registrationDraft, key)) Object.defineProperty(config, key, {
|
|
1383
|
+
value: registrationDraft[key], writable: true, enumerable: true, configurable: true,
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1374
1386
|
}
|
|
1387
|
+
config.providers[name] = candidate;
|
|
1388
|
+
if (body.setDefault === true) config.defaultProvider = name;
|
|
1389
|
+
(deps.saveConfigPreservingClaudeCode ?? save)(config);
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
if (rollback) {
|
|
1392
|
+
if (previous) Object.defineProperty(config.providers, name, previous);
|
|
1393
|
+
else delete config.providers[name];
|
|
1394
|
+
rollback();
|
|
1395
|
+
}
|
|
1396
|
+
throw error;
|
|
1375
1397
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
(deps.saveConfigPreservingClaudeCode ?? save)(config);
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
if (rollback) {
|
|
1381
|
-
if (previous) Object.defineProperty(config.providers, name, previous);
|
|
1382
|
-
else delete config.providers[name];
|
|
1383
|
-
rollback();
|
|
1384
|
-
}
|
|
1385
|
-
throw error;
|
|
1386
|
-
}
|
|
1398
|
+
});
|
|
1399
|
+
if (validationError !== undefined) return jsonResponse({ error: validationError }, 400);
|
|
1387
1400
|
reconcileLiveStateStores();
|
|
1388
1401
|
if (prov.apiKey && prov.apiKeyPool) {
|
|
1389
1402
|
const { addProviderApiKey } = await import("../../providers/api-keys");
|
|
@@ -103,6 +103,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
103
103
|
{ method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api/routes", mutates: false },
|
|
104
104
|
{ method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api/routes", mutates: false },
|
|
105
105
|
{ method: "GET", path: "/api/codex-auth/quota/history", module: "codex/auth-api/routes", mutates: false },
|
|
106
|
+
{ method: "GET", path: "/api/codex-auth/low-quota-events", module: "server/management/low-quota-routes", mutates: false, exempt: { reason: "deferred-verb", why: "The authenticated event history is an operator diagnostic with no CLI verb yet.", owner: "rt5 low-quota", ownerDoc: "structure/gui-and-management-api.md" } },
|
|
106
107
|
{ method: "GET", path: "/api/codex-auth/reset-credits", module: "codex/auth-api/routes", mutates: false },
|
|
107
108
|
{ method: "PATCH", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api/routes", mutates: true },
|
|
108
109
|
{ method: "POST", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: true },
|
|
@@ -149,6 +149,12 @@ async function handleQuotaResetRoutesOnDemand(ctx: ManagementContext): Promise<R
|
|
|
149
149
|
return handleQuotaResetRoutes(ctx);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
async function handleLowQuotaRoutesOnDemand(ctx: ManagementContext): Promise<Response | null> {
|
|
153
|
+
if (!pathInManagementNamespace(ctx.url.pathname, "/api/codex-auth/low-quota-events", false)) return null;
|
|
154
|
+
const { handleLowQuotaRoutes } = await import("./management/low-quota-routes");
|
|
155
|
+
return handleLowQuotaRoutes(ctx);
|
|
156
|
+
}
|
|
157
|
+
|
|
152
158
|
/**
|
|
153
159
|
* Lazy like the Lab and routing-profile handlers, and for the same recorded reason: this file is
|
|
154
160
|
* mounted for every dashboard request, so a static import would put the workflow-budget ledger
|
|
@@ -328,6 +334,7 @@ export async function handleManagementAPI(
|
|
|
328
334
|
?? (await handleLogsUsageRoutes(ctx))
|
|
329
335
|
?? (await handleRequestHistoryRoutes(ctx))
|
|
330
336
|
?? (await handleQuotaResetRoutesOnDemand(ctx))
|
|
337
|
+
?? (await handleLowQuotaRoutesOnDemand(ctx))
|
|
331
338
|
?? (await handleWorkflowBudgetRoutesOnDemand(ctx))
|
|
332
339
|
?? (await handleProtocolRoutesOnDemand(ctx))
|
|
333
340
|
?? (await handleGrokCouponRoutesOnDemand(ctx))
|
|
@@ -83,6 +83,7 @@ export function createAdapterContinuations(
|
|
|
83
83
|
| "oauthDispatch"
|
|
84
84
|
| "invalidateSameTargetRequest"
|
|
85
85
|
| "resolveSelectionAdapter"
|
|
86
|
+
| "anthropicRouteDecision"
|
|
86
87
|
| "anthropicPoolAccountId"
|
|
87
88
|
| "anthropicPoolFailovers"
|
|
88
89
|
| "anthropicSessionKey"
|
|
@@ -178,6 +179,7 @@ export function createAdapterContinuations(
|
|
|
178
179
|
try {
|
|
179
180
|
continuationRequest = await transportState.activeAdapter.buildRequest(nextParsed, {
|
|
180
181
|
headers: requestState.selectedForwardHeaders,
|
|
182
|
+
providerName: route.providerName,
|
|
181
183
|
translatorBudget,
|
|
182
184
|
...(transportState.imageTierBias > 0 ? { imageTierBias: transportState.imageTierBias } : {}),
|
|
183
185
|
});
|
|
@@ -398,6 +400,7 @@ export function createAdapterContinuations(
|
|
|
398
400
|
anthropicSessionKey,
|
|
399
401
|
Date.now(),
|
|
400
402
|
response.headers,
|
|
403
|
+
transportState.anthropicRouteDecision,
|
|
401
404
|
);
|
|
402
405
|
if (nextAccountId) {
|
|
403
406
|
try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
|
|
@@ -153,6 +153,7 @@ export async function deliverAdapterResponse(
|
|
|
153
153
|
fold: {
|
|
154
154
|
replayCacheScope: parsed._reasoningReplayScope,
|
|
155
155
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
156
|
+
hideRawReasoning: parsed.options.hideRawReasoning,
|
|
156
157
|
toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames,
|
|
157
158
|
},
|
|
158
159
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
@@ -175,6 +176,7 @@ export async function deliverAdapterResponse(
|
|
|
175
176
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
176
177
|
localUpstream,
|
|
177
178
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
179
|
+
hideRawReasoning: parsed.options.hideRawReasoning,
|
|
178
180
|
declaredToolNames,
|
|
179
181
|
bareCustomToolNames,
|
|
180
182
|
enforceDeclaredToolNames: options.inboundWire !== "chat" && options.inboundWire !== "anthropic",
|
|
@@ -243,6 +245,7 @@ export async function deliverAdapterResponse(
|
|
|
243
245
|
translatorBudget,
|
|
244
246
|
replayCacheScope: parsed._reasoningReplayScope,
|
|
245
247
|
hideThinkingSummary: parsed.options.hideThinkingSummary,
|
|
248
|
+
hideRawReasoning: parsed.options.hideRawReasoning,
|
|
246
249
|
toolNsMap,
|
|
247
250
|
declaredToolNames,
|
|
248
251
|
bareCustomToolNames,
|
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
import { resolveClientRetryAfter } from "../../lib/retry-after";
|
|
94
94
|
import { cancelBodyOnAbort } from "../../lib/abort";
|
|
95
95
|
import { chargeWorkflowSends } from "../../lib/workflow-budget";
|
|
96
|
+
import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
|
|
96
97
|
|
|
97
98
|
/** One responsibility of the Responses request pipeline; state owners are explicit. */
|
|
98
99
|
export async function prepareAdapterExchange(
|
|
@@ -126,6 +127,7 @@ export async function prepareAdapterExchange(
|
|
|
126
127
|
| "replayOAuthCredentialSnapshot"
|
|
127
128
|
| "invalidateSameTargetRequest"
|
|
128
129
|
| "resolveSelectionAdapter"
|
|
130
|
+
| "anthropicRouteDecision"
|
|
129
131
|
| "anthropicPoolAccountId"
|
|
130
132
|
| "anthropicPoolFailovers"
|
|
131
133
|
| "anthropicSessionKey"
|
|
@@ -268,6 +270,7 @@ export async function prepareAdapterExchange(
|
|
|
268
270
|
try {
|
|
269
271
|
initialRequest = await transportState.activeAdapter.buildRequest(parsed, {
|
|
270
272
|
headers: requestState.selectedForwardHeaders,
|
|
273
|
+
providerName: route.providerName,
|
|
271
274
|
translatorBudget,
|
|
272
275
|
abortSignal: upstream.signal,
|
|
273
276
|
});
|
|
@@ -413,7 +416,7 @@ export async function prepareAdapterExchange(
|
|
|
413
416
|
// moments later; at most one byte-identical replay is allowed per request.
|
|
414
417
|
const consoleGoUploadRetryGuard: { attempted: boolean } = { attempted: false };
|
|
415
418
|
let oauth401ReplayAttempted = false;
|
|
416
|
-
let
|
|
419
|
+
let antigravityAuthRotationAttempted = false;
|
|
417
420
|
// At most one reasoning-effort downgrade per request. This sits outside the recovery loop
|
|
418
421
|
// below for the same reason the two guards above do: a guard declared inside it is reset by
|
|
419
422
|
// every `continue recovery`, which would let one turn walk the whole ladder down.
|
|
@@ -454,6 +457,7 @@ export async function prepareAdapterExchange(
|
|
|
454
457
|
try {
|
|
455
458
|
retryRequest = await transportState.activeAdapter.buildRequest(parsed, {
|
|
456
459
|
headers: requestState.selectedForwardHeaders,
|
|
460
|
+
providerName: route.providerName,
|
|
457
461
|
translatorBudget,
|
|
458
462
|
abortSignal: upstream.signal,
|
|
459
463
|
...(transportState.imageTierBias > 0 ? { imageTierBias: transportState.imageTierBias } : {}),
|
|
@@ -665,7 +669,7 @@ export async function prepareAdapterExchange(
|
|
|
665
669
|
if (route.providerName === "google-antigravity" && err instanceof OAuthLoginRequiredError && failed
|
|
666
670
|
&& getAccountSet(route.providerName)?.accounts.some(row =>
|
|
667
671
|
row.id === failed.accountId && row.needsReauth === true)) {
|
|
668
|
-
|
|
672
|
+
antigravityAuthRotationAttempted = true;
|
|
669
673
|
const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-401");
|
|
670
674
|
if (rotated) { upstreamResponse = rotated; continue recovery; }
|
|
671
675
|
}
|
|
@@ -758,13 +762,24 @@ export async function prepareAdapterExchange(
|
|
|
758
762
|
}
|
|
759
763
|
|
|
760
764
|
if (route.providerName === "google-antigravity" && upstreamResponse.status === 401
|
|
761
|
-
&& oauth401ReplayAttempted && !
|
|
765
|
+
&& oauth401ReplayAttempted && !antigravityAuthRotationAttempted
|
|
762
766
|
&& !isNonReplayableResponse(upstreamResponse)) {
|
|
763
|
-
|
|
767
|
+
antigravityAuthRotationAttempted = true;
|
|
764
768
|
const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-401");
|
|
765
769
|
if (rotated) { upstreamResponse = rotated; continue recovery; }
|
|
766
770
|
}
|
|
767
771
|
|
|
772
|
+
if (route.providerName === "google-antigravity" && upstreamResponse.status === 403
|
|
773
|
+
&& transportState.activeAdapter.name === "google"
|
|
774
|
+
&& antigravityPoolActivated && !antigravityAuthRotationAttempted
|
|
775
|
+
&& !isNonReplayableResponse(upstreamResponse)
|
|
776
|
+
&& transportState.genericFailovers < transportState.genericFailoverLimit
|
|
777
|
+
&& await isAntigravityValidationRefusal(upstreamResponse, options.abortSignal)) {
|
|
778
|
+
antigravityAuthRotationAttempted = true;
|
|
779
|
+
const rotated = await rotateAntigravityAuth(upstreamResponse, "oauth-account-403");
|
|
780
|
+
if (rotated) { upstreamResponse = rotated; continue recovery; }
|
|
781
|
+
}
|
|
782
|
+
|
|
768
783
|
// Static API-key pools can recover a credential-scoped 401 without abandoning the
|
|
769
784
|
// provider: one revoked or mistyped key says nothing about its siblings. OAuth providers
|
|
770
785
|
// refresh above and never enter here — `hasKeyPoolFailover` rejects oauth/forward modes.
|
|
@@ -959,6 +974,7 @@ export async function prepareAdapterExchange(
|
|
|
959
974
|
anthropicSessionKey,
|
|
960
975
|
Date.now(),
|
|
961
976
|
upstreamResponse.headers,
|
|
977
|
+
transportState.anthropicRouteDecision,
|
|
962
978
|
);
|
|
963
979
|
if (!nextAccountId) break;
|
|
964
980
|
try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX } from "../../adapters/google-errors";
|
|
2
|
+
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
3
|
+
|
|
4
|
+
/** Inspect only the Google adapter's normalized, already bounded 403 response. */
|
|
5
|
+
export async function isAntigravityValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
|
|
6
|
+
if (response.status !== 403 || signal?.aborted) return false;
|
|
7
|
+
try {
|
|
8
|
+
const body = await readBoundedResponseBody(response.clone(), {
|
|
9
|
+
maxBytes: 1024,
|
|
10
|
+
totalTimeoutMs: 1000,
|
|
11
|
+
firstByteTimeoutMs: 1000,
|
|
12
|
+
inactivityTimeoutMs: 1000,
|
|
13
|
+
signal,
|
|
14
|
+
});
|
|
15
|
+
return !signal?.aborted && body.displaySafe && !body.truncated && !body.timedOut && !body.oversized
|
|
16
|
+
&& body.text.startsWith(`${ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX}: `);
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -90,6 +90,7 @@ export function codexRouteCredentialDomainHeaders(
|
|
|
90
90
|
// caller credential. Bearer admission is substituted or stripped below.
|
|
91
91
|
const routeMayChangeCredentialDomain = options.comboAttempt === true
|
|
92
92
|
|| route.routeKind === "policy"
|
|
93
|
+
|| route.credentialDomainRewrite === true
|
|
93
94
|
|| credentialDomainWasRewritten;
|
|
94
95
|
if (routeMayChangeCredentialDomain && options.admission?.source !== "bearer") {
|
|
95
96
|
const scoped = new Headers(req.headers);
|
|
@@ -777,6 +777,7 @@ export async function retryCodexPoolOnAlternateAccount(
|
|
|
777
777
|
}
|
|
778
778
|
const request = await retryAdapter.buildRequest(parsed, {
|
|
779
779
|
headers: retryHeaders,
|
|
780
|
+
providerName: route.providerName,
|
|
780
781
|
translatorBudget: options.translatorBudget,
|
|
781
782
|
});
|
|
782
783
|
recordAdapterReasoning(logCtx, request);
|
|
@@ -176,6 +176,10 @@ export async function applyFinalRouteRequestNormalization(args: {
|
|
|
176
176
|
|| (!summary && !hasValidatedActiveReasoningEffort(parsed.options)
|
|
177
177
|
&& route.provider.showThinkingSummary !== true);
|
|
178
178
|
}
|
|
179
|
+
// Provider policy, recomputed per final route like the summary default above so a fallback
|
|
180
|
+
// cannot inherit the previous target's choice. Raw content-channel reasoning is suppressed;
|
|
181
|
+
// provider-authored summaries stay on the summary channel and remain visible.
|
|
182
|
+
parsed.options.hideRawReasoning = route.provider.hideRawReasoning === true;
|
|
179
183
|
if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
|
|
180
184
|
logCtx.model = virtualModel?.selectedModelId ?? route.modelId;
|
|
181
185
|
logCtx.provider = route.providerName;
|
|
@@ -327,7 +327,11 @@ export async function preparePassthroughExchange(
|
|
|
327
327
|
const providerExecutedCallTypes = new Set<ProviderExecutedCallType>();
|
|
328
328
|
let request: Awaited<ReturnType<typeof transportState.adapter.buildRequest>>;
|
|
329
329
|
try {
|
|
330
|
-
request = await transportState.adapter.buildRequest(parsed, {
|
|
330
|
+
request = await transportState.adapter.buildRequest(parsed, {
|
|
331
|
+
headers: requestState.selectedForwardHeaders,
|
|
332
|
+
providerName: route.providerName,
|
|
333
|
+
translatorBudget,
|
|
334
|
+
});
|
|
331
335
|
} catch (error) {
|
|
332
336
|
releaseCodexAuthContextProbeLease(admissionState.authCtx);
|
|
333
337
|
// A tool catalog this proxy cannot lower onto one wire namespace is a client input error, and
|
|
@@ -993,6 +997,7 @@ export async function preparePassthroughExchange(
|
|
|
993
997
|
if (recovery !== "console-go-upload-retry") {
|
|
994
998
|
request = await retryAdapter.buildRequest(parsed, {
|
|
995
999
|
headers: requestState.selectedForwardHeaders,
|
|
1000
|
+
providerName: route.providerName,
|
|
996
1001
|
translatorBudget,
|
|
997
1002
|
});
|
|
998
1003
|
}
|
|
@@ -1130,6 +1135,7 @@ export async function preparePassthroughExchange(
|
|
|
1130
1135
|
try {
|
|
1131
1136
|
request = await replayAdapter.buildRequest(parsed, {
|
|
1132
1137
|
headers: requestState.selectedForwardHeaders,
|
|
1138
|
+
providerName: route.providerName,
|
|
1133
1139
|
translatorBudget,
|
|
1134
1140
|
});
|
|
1135
1141
|
refreshRequestToolAliases(request);
|
|
@@ -1274,6 +1280,7 @@ export async function preparePassthroughExchange(
|
|
|
1274
1280
|
try {
|
|
1275
1281
|
request = await refreshedAdapter.buildRequest(parsed, {
|
|
1276
1282
|
headers: requestState.selectedForwardHeaders,
|
|
1283
|
+
providerName: route.providerName,
|
|
1277
1284
|
translatorBudget,
|
|
1278
1285
|
});
|
|
1279
1286
|
refreshRequestToolAliases(request);
|
|
@@ -3,6 +3,7 @@ import type { PreparedResponsesRequest } from "./request-prepare";
|
|
|
3
3
|
import type { ResponsesTransport } from "./request-transport";
|
|
4
4
|
import type { ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
|
|
5
5
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
6
|
+
import { omitEarlierCompactionImages } from "../../responses/compaction-images";
|
|
6
7
|
import {
|
|
7
8
|
shouldResolveOpenAiVisionSidecar,
|
|
8
9
|
resolveOpenAiVisionModel,
|
|
@@ -51,6 +52,7 @@ export async function prepareResponsesSidecarAuth(
|
|
|
51
52
|
const visionDescribeTerminal = options.visionDescribeTerminal === true;
|
|
52
53
|
const routedCompaction = parsed._compactionRequest === true
|
|
53
54
|
&& (!isCanonicalOpenAiForwardProvider(route.provider) || parsed._portableCompaction === true);
|
|
55
|
+
if (routedCompaction) parsed.context.messages = omitEarlierCompactionImages(parsed.context.messages);
|
|
54
56
|
const needsOpenAiVision = !visionDescribeTerminal
|
|
55
57
|
&& shouldResolveOpenAiVisionSidecar(config, route.provider, route.modelId, parsed, route.providerName);
|
|
56
58
|
// LOCAL PATCH (runturn-websearch): runTurn adapters can run the web-search
|