@yansigit/opencodex 2.31.2 → 2.32.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/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-BG43zwVe.js +102 -0
- package/gui/dist/assets/index-CiSI-jrP.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +6 -8
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code-project-context.ts +377 -0
- package/src/adapters/command-code.ts +7 -4
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/live-transport.ts +21 -0
- package/src/adapters/cursor/native-exec-bridge.ts +141 -0
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- package/src/adapters/cursor/tool-definitions.ts +1 -1
- package/src/adapters/google-http.ts +12 -2
- package/src/adapters/google-wire-compiler.ts +83 -2
- package/src/adapters/google.ts +62 -21
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-responses.ts +3 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +21 -15
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +24 -11
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +11 -4
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +2 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/catalog/aggregation.ts +13 -1
- package/src/codex/catalog/effort.ts +24 -9
- package/src/codex/catalog/model-metadata.ts +566 -0
- package/src/codex/catalog/parsing.ts +33 -0
- package/src/codex/catalog/provider-fetch.ts +123 -32
- package/src/codex/catalog/sync.ts +5 -4
- package/src/codex/desktop-app-restart.ts +342 -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.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config.ts +77 -812
- package/src/generated/compatibility-version.json +162 -94
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/process-control.ts +2 -1
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +3 -0
- package/src/providers/registry.ts +1 -1
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/parser.ts +3 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/auth-cors.ts +9 -7
- package/src/server/index.ts +102 -21
- 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 +1 -1
- package/src/server/management/config-routes.ts +4 -1
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +242 -101
- package/src/server/responses/fetch-helpers.ts +2 -97
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +156 -15
- package/src/service.ts +8 -4
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +12 -1
- package/src/types/tools.ts +87 -11
- package/src/types.ts +1 -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/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/gemini-executor.ts +35 -13
- package/src/web-search/index.ts +85 -1
- package/gui/dist/assets/index-BAMgarF9.js +0 -102
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
retryableGoogleStatus,
|
|
6
6
|
safeGoogleHttpErrorMessage,
|
|
7
7
|
} from "./google-errors";
|
|
8
|
-
import { repairGoogleInvalidRequestBody } from "./google-wire-compiler";
|
|
8
|
+
import { isGoogleMixedBuiltinToolError, repairGoogleInvalidRequestBody, stripGoogleBuiltinToolsFromWireBody } from "./google-wire-compiler";
|
|
9
9
|
import { normalizeUpstreamHttpErrorResponse, readDisplaySafeErrorPayloadText } from "./upstream-http-error";
|
|
10
10
|
import { recordAntigravityCooldown } from "../oauth/antigravity-routing";
|
|
11
11
|
import {
|
|
@@ -378,7 +378,17 @@ async function fetchGoogleWithRetryInternal(
|
|
|
378
378
|
} catch (error) {
|
|
379
379
|
if (ctx.abortSignal?.aborted) throw error;
|
|
380
380
|
}
|
|
381
|
-
|
|
381
|
+
// Mixed built-in + function tools must win over schema repair: those 400s often mention
|
|
382
|
+
// function_declarations, which would otherwise empty parameters and leave google_search attached.
|
|
383
|
+
let repairedBody: string | undefined;
|
|
384
|
+
if (isGoogleMixedBuiltinToolError(payloadText)) {
|
|
385
|
+
repairedBody = stripGoogleBuiltinToolsFromWireBody(activeRequest.body);
|
|
386
|
+
if (repairedBody === undefined) {
|
|
387
|
+
repairedBody = repairGoogleInvalidRequestBody(activeRequest.body, payloadText);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
repairedBody = repairGoogleInvalidRequestBody(activeRequest.body, payloadText);
|
|
391
|
+
}
|
|
382
392
|
if (repairedBody !== undefined) {
|
|
383
393
|
compatibilityReplayUsed = true;
|
|
384
394
|
activeRequest = { ...activeRequest, body: repairedBody };
|
|
@@ -94,10 +94,35 @@ function compileContents(value: unknown, toWireName: (name: string) => string):
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const GOOGLE_BUILTIN_TOOL_KEYS = new Set([
|
|
98
|
+
"googleSearch", "google_search",
|
|
99
|
+
"urlContext", "url_context",
|
|
100
|
+
"codeExecution", "code_execution",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function isGoogleBuiltinToolObject(rawTool: unknown): boolean {
|
|
104
|
+
if (!isObject(rawTool)) return false;
|
|
105
|
+
const keys = Object.keys(rawTool);
|
|
106
|
+
return keys.length > 0 && keys.every(key => GOOGLE_BUILTIN_TOOL_KEYS.has(key));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function passthroughGoogleBuiltinTools(rawTool: unknown): unknown[] {
|
|
110
|
+
if (!isObject(rawTool) || Array.isArray(rawTool.functionDeclarations)) return [];
|
|
111
|
+
if (!isGoogleBuiltinToolObject(rawTool)) return [];
|
|
112
|
+
const out: Record<string, unknown> = {};
|
|
113
|
+
for (const key of Object.keys(rawTool)) {
|
|
114
|
+
if (key === "googleSearch" || key === "google_search") out.google_search = {};
|
|
115
|
+
else if (key === "urlContext" || key === "url_context") out.url_context = {};
|
|
116
|
+
else if (key === "codeExecution" || key === "code_execution") out.code_execution = rawTool[key] ?? {};
|
|
117
|
+
}
|
|
118
|
+
return Object.keys(out).length > 0 ? [out] : [];
|
|
119
|
+
}
|
|
120
|
+
|
|
97
121
|
function compileTools(value: unknown, toWireName: (name: string) => string): unknown[] | undefined {
|
|
98
122
|
if (!Array.isArray(value)) return undefined;
|
|
99
123
|
const tools = value.flatMap(rawTool => {
|
|
100
|
-
|
|
124
|
+
const builtins = passthroughGoogleBuiltinTools(rawTool);
|
|
125
|
+
if (!isObject(rawTool) || !Array.isArray(rawTool.functionDeclarations)) return builtins;
|
|
101
126
|
const functionDeclarations = rawTool.functionDeclarations.flatMap(rawDeclaration => {
|
|
102
127
|
if (!isObject(rawDeclaration) || typeof rawDeclaration.name !== "string") return [];
|
|
103
128
|
return [{
|
|
@@ -106,7 +131,10 @@ function compileTools(value: unknown, toWireName: (name: string) => string): unk
|
|
|
106
131
|
parameters: sanitizeGeminiToolParameters(rawDeclaration.parameters),
|
|
107
132
|
}];
|
|
108
133
|
});
|
|
109
|
-
return
|
|
134
|
+
return [
|
|
135
|
+
...builtins,
|
|
136
|
+
...(functionDeclarations.length > 0 ? [{ functionDeclarations }] : []),
|
|
137
|
+
];
|
|
110
138
|
});
|
|
111
139
|
return tools.length > 0 ? tools : undefined;
|
|
112
140
|
}
|
|
@@ -238,3 +266,56 @@ export function repairGoogleInvalidRequestBody(body: string, errorPayload: strin
|
|
|
238
266
|
}
|
|
239
267
|
return changed ? JSON.stringify(parsed) : undefined;
|
|
240
268
|
}
|
|
269
|
+
|
|
270
|
+
function stripBuiltinToolsFromRoot(root: JsonObject): boolean {
|
|
271
|
+
if (!Array.isArray(root.tools)) return false;
|
|
272
|
+
const before = root.tools.length;
|
|
273
|
+
const filtered = root.tools.filter(rawTool => !isGoogleBuiltinToolObject(rawTool));
|
|
274
|
+
root.tools = filtered;
|
|
275
|
+
return filtered.length !== before;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** One mixed-tool 400 replay: drop known built-in siblings and keep functionDeclarations. */
|
|
279
|
+
export function stripGoogleBuiltinToolsFromWireBody(body: string): string | undefined {
|
|
280
|
+
let parsed: unknown;
|
|
281
|
+
try {
|
|
282
|
+
parsed = JSON.parse(body) as unknown;
|
|
283
|
+
} catch {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
if (!isObject(parsed)) return undefined;
|
|
287
|
+
const root = isObject(parsed.request) ? parsed.request : parsed;
|
|
288
|
+
if (!stripBuiltinToolsFromRoot(root)) return undefined;
|
|
289
|
+
return JSON.stringify(parsed);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function isGoogleMixedBuiltinToolError(errorPayload: string): boolean {
|
|
293
|
+
const mentionsBuiltin = /\b(?:google[_ ]?search|url[_ ]?context|code[_ ]?execution|built[- ]?in(?:\s+tools?)?)\b/i.test(errorPayload);
|
|
294
|
+
if (!mentionsBuiltin) return false;
|
|
295
|
+
|
|
296
|
+
// A mixed-tool error names a builtin AND a function/tool term AND a coexistence verb.
|
|
297
|
+
// Builtin mention is gated above; each pattern below requires a coexistence verb to
|
|
298
|
+
// appear within a bounded window of a tool/function/declaration term (or the phrase
|
|
299
|
+
// itself implies coexistence, e.g. "mutually exclusive"). Bare "coexist"/"alongside"
|
|
300
|
+
// without a nearby tool/function term is NOT enough — a schema error can say fields
|
|
301
|
+
// "coexist" while merely mentioning a builtin.
|
|
302
|
+
const describesIncompatibleCoexistence = [
|
|
303
|
+
/\bmix\w*\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
304
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bmix\w*\b/i,
|
|
305
|
+
/\bcombin\w*\b[^\n.!?]{0,80}\bwith\b/i,
|
|
306
|
+
/\bcoexist\w*\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
307
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bcoexist\w*\b/i,
|
|
308
|
+
/\balongside\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
309
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\balongside\b/i,
|
|
310
|
+
/\buse\w*\b[^\n.!?]{0,80}\btogether\b/i,
|
|
311
|
+
/\btogether\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
312
|
+
/\buse\w*\b[^\n.!?]{0,80}\bwith\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
313
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bwith\b[^\n.!?]{0,80}\buse\w*\b/i,
|
|
314
|
+
/\bnot supported with\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
315
|
+
/\bmutually exclusive\b/i,
|
|
316
|
+
/\bincompatib\w*\b[^\n.!?]{0,80}\b(?:coexist\w*|combin\w*|alongside|used together|mutually exclusive)\b/i,
|
|
317
|
+
/\b(?:coexist\w*|combin\w*|alongside|used together|mutually exclusive)\b[^\n.!?]{0,80}\bincompatib\w*\b/i,
|
|
318
|
+
].some(pattern => pattern.test(errorPayload));
|
|
319
|
+
|
|
320
|
+
return describesIncompatibleCoexistence;
|
|
321
|
+
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type {
|
|
|
14
14
|
OcxToolResultMessage,
|
|
15
15
|
OcxUsage,
|
|
16
16
|
} from "../types";
|
|
17
|
-
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName,
|
|
17
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
18
18
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
19
19
|
import { getVertexAccessToken } from "../lib/gcp-adc";
|
|
20
20
|
import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
|
|
@@ -28,6 +28,12 @@ import { sanitizeGeminiToolParameters } from "./google-tool-schema";
|
|
|
28
28
|
import { identifyRoutedModel } from "./identity";
|
|
29
29
|
import { antigravityUsesReplayCache, applyAntigravityReplay, clearAntigravityReplay, observeAntigravityReplay } from "./google-antigravity-replay";
|
|
30
30
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
31
|
+
import {
|
|
32
|
+
extractCcaGroundingSources,
|
|
33
|
+
formatCcaGroundingSourcesAppendix,
|
|
34
|
+
isCcaSearchSuggestionHtml,
|
|
35
|
+
} from "../web-search/gemini-executor";
|
|
36
|
+
import type { WebSearchSource } from "../web-search/parse";
|
|
31
37
|
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
32
38
|
import { lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
33
39
|
import {
|
|
@@ -335,22 +341,26 @@ function messagesToGeminiFormat(
|
|
|
335
341
|
return { systemInstruction, contents };
|
|
336
342
|
}
|
|
337
343
|
|
|
338
|
-
function toolsToGeminiFormat(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const tools =
|
|
344
|
-
? parsed.context.tools
|
|
345
|
-
: parsed.context.tools;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
344
|
+
function toolsToGeminiFormat(
|
|
345
|
+
parsed: OcxParsedRequest,
|
|
346
|
+
wireModelId: string,
|
|
347
|
+
): unknown[] | undefined {
|
|
348
|
+
const grounding = parsed._ccaInTurnGrounding;
|
|
349
|
+
const tools = isAllowedToolChoice(parsed.options.toolChoice)
|
|
350
|
+
? parsed.context.tools?.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools)) ?? []
|
|
351
|
+
: parsed.context.tools ?? [];
|
|
352
|
+
const functionDeclarations = tools.map(t => ({
|
|
353
|
+
name: namespacedToolName(t.namespace, t.name),
|
|
354
|
+
description: t.description,
|
|
355
|
+
parameters: t.parameters,
|
|
356
|
+
}));
|
|
357
|
+
const wireTools: unknown[] = [];
|
|
358
|
+
if (functionDeclarations.length > 0) wireTools.push({ functionDeclarations });
|
|
359
|
+
if (grounding && !/claude/i.test(wireModelId)) {
|
|
360
|
+
if (grounding.search) wireTools.push({ google_search: {} });
|
|
361
|
+
if (grounding.urlContext) wireTools.push({ url_context: {} });
|
|
362
|
+
}
|
|
363
|
+
return wireTools.length > 0 ? wireTools : undefined;
|
|
354
364
|
}
|
|
355
365
|
|
|
356
366
|
/**
|
|
@@ -509,11 +519,12 @@ function googleToolCallMetadataFromPart(
|
|
|
509
519
|
* Keep that provider visibility bit authoritative here so the streaming and buffered parsers
|
|
510
520
|
* cannot accidentally expose the same hidden reasoning through different event types.
|
|
511
521
|
*/
|
|
512
|
-
function googlePartTextEvent(part: GoogleResponsePart): AdapterEvent | undefined {
|
|
522
|
+
function googlePartTextEvent(part: GoogleResponsePart, filterCcaSearchSuggestionHtml = false): AdapterEvent | undefined {
|
|
513
523
|
// A malformed scalar/object is not text and must not cross the AdapterEvent boundary. Dropping
|
|
514
524
|
// only this optional field preserves the rest of the part without inventing assistant output by
|
|
515
525
|
// coercion; an empty string keeps its existing no-event behavior.
|
|
516
526
|
if (typeof part.text !== "string" || part.text.length === 0) return undefined;
|
|
527
|
+
if (filterCcaSearchSuggestionHtml && isCcaSearchSuggestionHtml(part.text)) return undefined;
|
|
517
528
|
return part.thought === true
|
|
518
529
|
? { type: "reasoning_raw_delta", text: part.text }
|
|
519
530
|
: { type: "text_delta", text: part.text };
|
|
@@ -669,6 +680,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
669
680
|
let vertexReplayModel: string | undefined;
|
|
670
681
|
let vertexReplaySession: string | undefined;
|
|
671
682
|
let restoreGoogleToolName = (name: string): string => name;
|
|
683
|
+
const emitInTurnGroundingSourcesQueue: boolean[] = [];
|
|
672
684
|
return {
|
|
673
685
|
name: "google",
|
|
674
686
|
|
|
@@ -702,7 +714,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
702
714
|
identityModelId,
|
|
703
715
|
provider.googleMode === "cloud-code-assist",
|
|
704
716
|
);
|
|
705
|
-
const tools = toolsToGeminiFormat(parsed);
|
|
717
|
+
const tools = toolsToGeminiFormat(parsed, routedModelId);
|
|
706
718
|
|
|
707
719
|
const body: Record<string, unknown> = { contents };
|
|
708
720
|
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
@@ -836,6 +848,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
836
848
|
if (/claude/i.test(wireModelId)) {
|
|
837
849
|
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
838
850
|
}
|
|
851
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
839
852
|
return { url, method: "POST", headers, body: JSON.stringify(envelope) };
|
|
840
853
|
}
|
|
841
854
|
|
|
@@ -861,6 +874,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
861
874
|
if (apiKey) {
|
|
862
875
|
const url = `https://aiplatform.googleapis.com/v1/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
863
876
|
headers["x-goog-api-key"] = apiKey;
|
|
877
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
864
878
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
865
879
|
}
|
|
866
880
|
const project = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
|
|
@@ -873,6 +887,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
873
887
|
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
874
888
|
const token = await getVertexAccessToken();
|
|
875
889
|
headers["Authorization"] = `Bearer ${token}`;
|
|
890
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
876
891
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
877
892
|
}
|
|
878
893
|
|
|
@@ -884,10 +899,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
884
899
|
|
|
885
900
|
const compiled = compileGoogleWireBody(body);
|
|
886
901
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
902
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
887
903
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
888
904
|
},
|
|
889
905
|
|
|
890
906
|
async *parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
907
|
+
const emitInTurnGroundingSources = emitInTurnGroundingSourcesQueue.shift() ?? false;
|
|
908
|
+
const filterCcaSearchSuggestionHtml =
|
|
909
|
+
provider.googleMode === "cloud-code-assist" && emitInTurnGroundingSources;
|
|
891
910
|
if (!response.body) {
|
|
892
911
|
yield { type: "error", message: "No response body" };
|
|
893
912
|
return;
|
|
@@ -912,6 +931,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
912
931
|
let sawAnyFrame = false;
|
|
913
932
|
let sawTerminalSignal = false;
|
|
914
933
|
let pendingStreamThoughtSig: string | undefined;
|
|
934
|
+
const groundingSources: WebSearchSource[] = [];
|
|
935
|
+
let groundingSourcesEmitted = false;
|
|
936
|
+
|
|
937
|
+
const mergeGroundingSources = (groundingMetadata: unknown): void => {
|
|
938
|
+
for (const source of extractCcaGroundingSources(groundingMetadata)) {
|
|
939
|
+
if (!groundingSources.some(existing => existing.url === source.url)) groundingSources.push(source);
|
|
940
|
+
}
|
|
941
|
+
};
|
|
915
942
|
|
|
916
943
|
const handleDataLine = async function* (line: string): AsyncGenerator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
917
944
|
const payload = line.slice(5).trim();
|
|
@@ -1006,8 +1033,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1006
1033
|
const candidate = rawCandidate as {
|
|
1007
1034
|
content?: unknown;
|
|
1008
1035
|
finishReason?: string;
|
|
1036
|
+
groundingMetadata?: unknown;
|
|
1009
1037
|
};
|
|
1010
1038
|
|
|
1039
|
+
if (emitInTurnGroundingSources && candidate.groundingMetadata) {
|
|
1040
|
+
mergeGroundingSources(candidate.groundingMetadata);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1011
1043
|
if (typeof candidate.finishReason === "string" && candidate.finishReason) {
|
|
1012
1044
|
lastFinishReason = candidate.finishReason;
|
|
1013
1045
|
sawTerminalSignal = true;
|
|
@@ -1042,7 +1074,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1042
1074
|
const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
|
|
1043
1075
|
if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
|
|
1044
1076
|
&& parts && replayModel && replaySession) {
|
|
1045
|
-
|
|
1077
|
+
// Observation may scan the whole frame, so use it only for replay-cache side effects.
|
|
1078
|
+
// The source-order loop below exclusively owns stream carry and cannot pair backwards.
|
|
1079
|
+
observeAntigravityReplay(
|
|
1046
1080
|
replayModel,
|
|
1047
1081
|
replaySession,
|
|
1048
1082
|
parts as unknown[],
|
|
@@ -1055,7 +1089,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1055
1089
|
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
1056
1090
|
pendingStreamThoughtSig = sig;
|
|
1057
1091
|
}
|
|
1058
|
-
const textEvent = googlePartTextEvent(part);
|
|
1092
|
+
const textEvent = googlePartTextEvent(part, filterCcaSearchSuggestionHtml);
|
|
1059
1093
|
if (textEvent) {
|
|
1060
1094
|
emittedContentEvent = true;
|
|
1061
1095
|
yield textEvent;
|
|
@@ -1166,6 +1200,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1166
1200
|
yield { type: "error", message: "upstream stream ended without a terminal signal — possible truncation" };
|
|
1167
1201
|
return;
|
|
1168
1202
|
}
|
|
1203
|
+
if (emitInTurnGroundingSources && !groundingSourcesEmitted && groundingSources.length > 0) {
|
|
1204
|
+
const appendix = formatCcaGroundingSourcesAppendix(groundingSources);
|
|
1205
|
+
if (appendix) {
|
|
1206
|
+
groundingSourcesEmitted = true;
|
|
1207
|
+
yield { type: "text_delta", text: appendix };
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1169
1210
|
const stopReason = lastFinishReason === "MAX_TOKENS"
|
|
1170
1211
|
? "max_tokens"
|
|
1171
1212
|
: ["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII"].includes(lastFinishReason ?? "")
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -318,9 +318,6 @@ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
|
318
318
|
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
319
319
|
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
320
320
|
}
|
|
321
|
-
if (parsed.options.parallelToolCalls === true) {
|
|
322
|
-
throw new Error("Kiro does not support parallel tool calls");
|
|
323
|
-
}
|
|
324
321
|
if (parsed.options.serviceTier !== undefined) {
|
|
325
322
|
throw new Error("Kiro does not support service tiers");
|
|
326
323
|
}
|
|
@@ -1684,6 +1684,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1684
1684
|
|
|
1685
1685
|
const forward = provider.authMode === "forward";
|
|
1686
1686
|
let convertedRoutedCustomToolNames: Set<string> | undefined;
|
|
1687
|
+
let routedCustomToolRepairNames: Set<string> | undefined;
|
|
1687
1688
|
let convertedRoutedToolSearchNames: Set<string> | undefined;
|
|
1688
1689
|
let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string }> | undefined;
|
|
1689
1690
|
const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
|
|
@@ -1741,6 +1742,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1741
1742
|
);
|
|
1742
1743
|
outBody = rewritten.body;
|
|
1743
1744
|
convertedRoutedCustomToolNames = rewritten.names;
|
|
1745
|
+
routedCustomToolRepairNames = rewritten.repairNames;
|
|
1744
1746
|
}
|
|
1745
1747
|
if (!isCanonicalOpenAiForwardProvider(provider)) {
|
|
1746
1748
|
// Run after custom-tool lowering so the search compatibility layer can choose a
|
|
@@ -1811,6 +1813,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1811
1813
|
body,
|
|
1812
1814
|
releaseBodyObservation,
|
|
1813
1815
|
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
|
|
1816
|
+
...(routedCustomToolRepairNames ? { routedCustomToolRepairNames } : {}),
|
|
1814
1817
|
...(convertedRoutedToolSearchNames ? { convertedRoutedToolSearchNames } : {}),
|
|
1815
1818
|
...(convertedRoutedNamespaceToolAliases ? { convertedRoutedNamespaceToolAliases } : {}),
|
|
1816
1819
|
...(tierLog ? { tierLog } : {}),
|
|
@@ -120,7 +120,7 @@ export function buildNonOpenAIToolCatalogNudgeFromNames(
|
|
|
120
120
|
"Call only listed names with their listed argument keys; do not invent, translate, or rename tools.",
|
|
121
121
|
"Names mentioned only in instructions, tool descriptions, argument descriptions, or nested helper APIs are not additional top-level tools.",
|
|
122
122
|
verifiedCodeModeExecName
|
|
123
|
-
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names."
|
|
123
|
+
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names. Nested `tools.apply_patch(input)` is host-executed: the string must begin exactly with `*** Begin Patch` and end with `*** End Patch` (no trailing `***` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated `*** Begin Patch ***` envelope is rejected by Codex before the file is touched."
|
|
124
124
|
: "If a listed tool exposes nested helpers such as a tools.* API, call the listed parent tool and use those helpers only inside that tool's input.",
|
|
125
125
|
unavailableNeighborNames.length > 0
|
|
126
126
|
? "Do not use neighboring-agent tool names " + quoteNames(unavailableNeighborNames) + " unless this turn's catalog lists those exact names."
|
|
@@ -60,10 +60,15 @@ function normalizeToolGroup(tools: unknown[]): ToolGroupRewrite {
|
|
|
60
60
|
: undefined;
|
|
61
61
|
const enableImageSearch = searchContentTypes?.includes("image") === true;
|
|
62
62
|
const next: Record<string, unknown> = { ...tool, type: CODEX_WEB_SEARCH_TOOL };
|
|
63
|
+
// Only the two fields xAI actually refuses are removed. Probed 2026-08-22, one field per
|
|
64
|
+
// request, against BOTH xAI destinations (api.x.ai and cli-chat-proxy.grok.com): they behave
|
|
65
|
+
// identically — `external_web_access` 400s on every value including `true`, and
|
|
66
|
+
// `search_context_size` 400s, while `user_location`, `search_content_types`, `filters` and
|
|
67
|
+
// `enable_image_search` are all accepted. Deleting the accepted ones was a silent capability
|
|
68
|
+
// loss, and it contradicted the sibling layer, whose own probe note already records
|
|
69
|
+
// user_location/filters as accepted (tests/responses-routed-web-search-fields.test.ts).
|
|
63
70
|
delete next.external_web_access;
|
|
64
71
|
delete next.search_context_size;
|
|
65
|
-
delete next.search_content_types;
|
|
66
|
-
delete next.user_location;
|
|
67
72
|
if (enableImageSearch && !Object.hasOwn(next, "enable_image_search")) {
|
|
68
73
|
next.enable_image_search = true;
|
|
69
74
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "./types";
|
|
9
9
|
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
10
10
|
import { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors";
|
|
11
|
+
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
11
12
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
12
13
|
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
13
14
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
@@ -230,12 +231,12 @@ export function bridgeToResponsesSSE(
|
|
|
230
231
|
const replayCacheScope = options?.replayCacheScope;
|
|
231
232
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
232
233
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
233
|
-
// Freeform/custom tools (apply_patch) carry their body in `input`; the
|
|
234
|
-
// function with `{input:string}`, so unwrap it here when relaying back
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
235
|
+
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
236
|
+
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
237
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
238
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
239
|
+
);
|
|
239
240
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
240
241
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
241
242
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -626,6 +627,7 @@ export function bridgeToResponsesSSE(
|
|
|
626
627
|
const argsStr = coerceIntegerToolArguments(
|
|
627
628
|
currentToolCall.args || "{}",
|
|
628
629
|
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
630
|
+
currentToolCall.namespace === undefined ? currentToolCall.name : undefined,
|
|
629
631
|
);
|
|
630
632
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
631
633
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
@@ -636,7 +638,8 @@ export function bridgeToResponsesSSE(
|
|
|
636
638
|
if (currentToolCall.freeform) {
|
|
637
639
|
emit("response.custom_tool_call_input.done", {
|
|
638
640
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
639
|
-
|
|
641
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
642
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace),
|
|
640
643
|
});
|
|
641
644
|
}
|
|
642
645
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
@@ -652,7 +655,8 @@ export function bridgeToResponsesSSE(
|
|
|
652
655
|
? {
|
|
653
656
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
654
657
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
655
|
-
|
|
658
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
659
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "completed",
|
|
656
660
|
}
|
|
657
661
|
: {
|
|
658
662
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -691,7 +695,8 @@ export function bridgeToResponsesSSE(
|
|
|
691
695
|
? {
|
|
692
696
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
693
697
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
694
|
-
|
|
698
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
699
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace), status: "incomplete",
|
|
695
700
|
}
|
|
696
701
|
: {
|
|
697
702
|
type: "function_call", id: currentToolCall.itemId,
|
|
@@ -1064,7 +1069,7 @@ export function bridgeToResponsesSSE(
|
|
|
1064
1069
|
const item = toolSearch
|
|
1065
1070
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
1066
1071
|
: freeform
|
|
1067
|
-
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
1072
|
+
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
1068
1073
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
1069
1074
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1070
1075
|
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
@@ -1567,10 +1572,9 @@ function buildResponseJSONWithBudget(
|
|
|
1567
1572
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1568
1573
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1569
1574
|
|
|
1570
|
-
const freeformInput = (args: string): string =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
};
|
|
1575
|
+
const freeformInput = (args: string, toolName: string, namespace?: string): string => (
|
|
1576
|
+
repairFreeformToolInput(args, toolName, namespace)
|
|
1577
|
+
);
|
|
1574
1578
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1575
1579
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1576
1580
|
};
|
|
@@ -1656,6 +1660,7 @@ function buildResponseJSONWithBudget(
|
|
|
1656
1660
|
const coercedArgs = coerceIntegerToolArguments(
|
|
1657
1661
|
currentToolCallArgs,
|
|
1658
1662
|
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1663
|
+
ns === undefined ? realName : undefined,
|
|
1659
1664
|
);
|
|
1660
1665
|
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1661
1666
|
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
@@ -1670,7 +1675,8 @@ function buildResponseJSONWithBudget(
|
|
|
1670
1675
|
pushOutput({
|
|
1671
1676
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1672
1677
|
call_id: currentToolCallId, name: realName,
|
|
1673
|
-
|
|
1678
|
+
...(ns ? { namespace: ns } : {}),
|
|
1679
|
+
input: freeformInput(currentToolCallArgs, realName, ns), status,
|
|
1674
1680
|
});
|
|
1675
1681
|
} else {
|
|
1676
1682
|
pushOutput({
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -203,7 +203,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
203
203
|
return 0;
|
|
204
204
|
},
|
|
205
205
|
sync: async deps => {
|
|
206
|
-
const
|
|
206
|
+
const syncArgs = deps.args.slice(1);
|
|
207
|
+
const restartCodex = syncArgs.includes("--restart-codex");
|
|
208
|
+
// Separate flag on purpose: --restart-codex promises app-server-only scope,
|
|
209
|
+
// and quitting the desktop app ends live conversations.
|
|
210
|
+
const restartDesktopApp = syncArgs.includes("--restart-desktop-app");
|
|
207
211
|
const live = await deps.findLiveProxy();
|
|
208
212
|
const synced = await syncModelsToCodex(
|
|
209
213
|
live?.port,
|
|
@@ -229,6 +233,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
229
233
|
// exactly when a long-lived app-server is holding the stale list.
|
|
230
234
|
if (synced.catalogWritten || synced.cacheSynced) {
|
|
231
235
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
236
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
232
237
|
}
|
|
233
238
|
// `ocx sync` is a direct CLI path; it does not call the management
|
|
234
239
|
// `/api/sync` route. Refresh the already-connected MCode block here too,
|
|
@@ -259,7 +264,9 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
259
264
|
return await cmdV2(deps.args.slice(1), {}, async () => (await deps.findLiveProxy())?.port);
|
|
260
265
|
},
|
|
261
266
|
"sync-cache": async deps => {
|
|
262
|
-
const
|
|
267
|
+
const cacheArgs = deps.args.slice(1);
|
|
268
|
+
const restartCodex = cacheArgs.includes("--restart-codex");
|
|
269
|
+
const restartDesktopApp = cacheArgs.includes("--restart-desktop-app");
|
|
263
270
|
const { withCatalogWriteSerialization } = await import("../codex/catalog-write-serialization");
|
|
264
271
|
const { invalidateCodexModelsCacheWithPermit } = await import("../codex/catalog/sync");
|
|
265
272
|
const { getCodexHome } = await import("../codex/paths");
|
|
@@ -270,6 +277,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
270
277
|
// Only warn/restart when models_cache was actually rewritten from a readable catalog.
|
|
271
278
|
if (invalidated.kind === "completed" && invalidated.value) {
|
|
272
279
|
afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
|
|
280
|
+
if (restartDesktopApp) await handleDesktopAppRestart(console);
|
|
273
281
|
} else if (desiredDisabled) {
|
|
274
282
|
console.log("Codex integration is OFF; cache sync skipped (no catalog or cache write).");
|
|
275
283
|
}
|
|
@@ -583,3 +591,43 @@ export async function dispatchCommand(head: CliHead, deps: CliDispatchDeps): Pro
|
|
|
583
591
|
}
|
|
584
592
|
return await runner(deps);
|
|
585
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Report the outcome of an opt-in desktop-app restart. Kept next to the two
|
|
597
|
+
* callers so `sync` and `sync-cache` cannot drift in what they tell the user.
|
|
598
|
+
*/
|
|
599
|
+
async function handleDesktopAppRestart(log: Pick<Console, "log" | "error">): Promise<void> {
|
|
600
|
+
const { restartCodexDesktopApp } = await import("../codex/desktop-app-restart");
|
|
601
|
+
const result = restartCodexDesktopApp();
|
|
602
|
+
switch (result.reason) {
|
|
603
|
+
case "windows_only":
|
|
604
|
+
log.error("--restart-desktop-app is supported on Windows only; nothing was stopped.");
|
|
605
|
+
return;
|
|
606
|
+
case "package_discovery_failed":
|
|
607
|
+
log.error(
|
|
608
|
+
"Could not identify the installed Codex desktop package. Quit and relaunch the desktop app "
|
|
609
|
+
+ "manually to refresh the model picker.",
|
|
610
|
+
);
|
|
611
|
+
return;
|
|
612
|
+
case "self_ancestry":
|
|
613
|
+
log.error(
|
|
614
|
+
"Refusing to restart the desktop app because this command is running inside it. "
|
|
615
|
+
+ "Run 'ocx sync --restart-desktop-app' from an external terminal instead.",
|
|
616
|
+
);
|
|
617
|
+
return;
|
|
618
|
+
case "no_targets":
|
|
619
|
+
log.log("Codex desktop app is not running; nothing to restart.");
|
|
620
|
+
return;
|
|
621
|
+
case "targets_survived":
|
|
622
|
+
log.error(
|
|
623
|
+
`Codex desktop app PID(s) ${result.surviving.join(", ")} did not exit, so it was not relaunched. `
|
|
624
|
+
+ "Quit the desktop app manually to refresh the model picker.",
|
|
625
|
+
);
|
|
626
|
+
return;
|
|
627
|
+
default:
|
|
628
|
+
if (result.relaunch === "started") {
|
|
629
|
+
log.log("Codex desktop app restarted; its model picker will re-read the catalog.");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|