@bitkyc08/opencodex 2.53.0 → 2.54.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-BlO4Yl6q.js → index-CkvITofZ.js} +2 -2
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/devin.ts +39 -7
- package/src/cli/capabilities.ts +4 -2
- package/src/cli/catalog.ts +39 -10
- package/src/cli/dispatch.ts +19 -64
- package/src/cli/doctor.ts +1 -1
- package/src/cli/internal-command.ts +44 -0
- package/src/cli/registry.ts +9 -7
- package/src/cli/restart-scope.ts +184 -0
- package/src/codex/app-server-processes.ts +20 -5
- package/src/codex/app-server-restart-service.ts +29 -0
- package/src/codex/catalog/provider-fetch.ts +13 -5
- package/src/codex/catalog/sync.ts +12 -3
- package/src/codex/desktop-app/darwin.ts +268 -0
- package/src/codex/desktop-app/handoff.ts +303 -0
- package/src/codex/desktop-app/linux.ts +388 -0
- package/src/codex/desktop-app/lock.ts +226 -0
- package/src/codex/desktop-app/types.ts +141 -0
- package/src/codex/desktop-app/windows.ts +239 -0
- package/src/codex/desktop-app-restart.ts +264 -279
- package/src/codex/inject.ts +83 -21
- package/src/codex/sync.ts +16 -22
- package/src/generated/compatibility-version.json +58 -22
- package/src/lib/codex-restart-contract.ts +31 -0
- package/src/providers/registry.ts +13 -12
- package/src/server/responses/core.ts +62 -4
- package/src/server/responses/encrypted-payload.ts +161 -0
- package/src/server/responses.ts +1 -1
- package/src/types/provider.ts +6 -5
- package/src/web-search/index.ts +14 -67
- package/src/web-search/passthrough-bridge.ts +256 -32
- package/src/web-search/sidecar-providers.ts +76 -0
|
@@ -22,11 +22,17 @@
|
|
|
22
22
|
* explicit error. Answering both would need the raw mixed-tool continuation contract the
|
|
23
23
|
* 2.47 track deferred (devlog/_plan/260907_track2_protocol/040_hosted_search_disposition.md),
|
|
24
24
|
* and silently half-doing it would drop the client's own tool call.
|
|
25
|
+
* - Assistant text is never treated as a search instruction. The bridge intercepts structured
|
|
26
|
+
* function_call / custom_tool_call items named web_search, not XML-like prose.
|
|
27
|
+
* - Non-Ollama backends reuse the sidecar executors and those executors' own credentials.
|
|
28
|
+
* The passthrough provider's API key is sent only to an ollama search endpoint the operator
|
|
29
|
+
* authorized. A backend whose credential is missing stays disarmed rather than falling
|
|
30
|
+
* through to a different paid search.
|
|
25
31
|
* - Continuation legs use a direct send rather than the core recovery ladder: the first leg
|
|
26
32
|
* still goes through it, and a KEY-auth destination has no OAuth refresh path to replay.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
* The caller's outbound body ceiling is re-applied to every continuation body.
|
|
34
|
+
* - The client stream is renumbered (sequence_number and output_index) because events are both
|
|
35
|
+
* dropped and injected; a plain relay cannot preserve upstream numbering through that.
|
|
30
36
|
*
|
|
31
37
|
* The stream this module produces is ordinary Responses SSE and is handed back to the core relay,
|
|
32
38
|
* so the undeclared-tool guard, the provider payload rewrites, terminal-outcome recording, and the
|
|
@@ -35,11 +41,28 @@
|
|
|
35
41
|
*/
|
|
36
42
|
import { nextSseBlock, sseDataPayload } from "../server/sse-payload-rewrite";
|
|
37
43
|
import { toolChoiceToolPredicate } from "../types";
|
|
38
|
-
import type {
|
|
39
|
-
|
|
44
|
+
import type {
|
|
45
|
+
OcxConfig,
|
|
46
|
+
OcxParsedRequest,
|
|
47
|
+
OcxProviderConfig,
|
|
48
|
+
OcxWebSearchSidecarConfig,
|
|
49
|
+
ProviderWebSearchBridgeBackend,
|
|
50
|
+
} from "../types";
|
|
51
|
+
import type { ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
|
|
52
|
+
import { runWebSearch, type SidecarOutcome, type SidecarSettings } from "./executor";
|
|
40
53
|
import { buildWebSearchTool, WEB_SEARCH_TOOL_NAME } from "./synthetic-tool";
|
|
41
54
|
import { safeWebSearchSources } from "./sources";
|
|
42
55
|
import { runOllamaWebSearch } from "./ollama-executor";
|
|
56
|
+
import { runAnthropicWebSearch } from "./anthropic-executor";
|
|
57
|
+
import { runXaiWebSearch, validateXaiSearchOptions } from "./xai-executor";
|
|
58
|
+
import { runGeminiWebSearch } from "./gemini-executor";
|
|
59
|
+
import { runExaWebSearch } from "./exa-executor";
|
|
60
|
+
import {
|
|
61
|
+
findAnthropicSidecarProvider,
|
|
62
|
+
findGeminiSidecarProvider,
|
|
63
|
+
findXaiSidecarProvider,
|
|
64
|
+
xaiSearchOptionsFromConfig,
|
|
65
|
+
} from "./sidecar-providers";
|
|
43
66
|
|
|
44
67
|
/** Canonical Ollama Cloud origin. The only origin the "ollama" backend derives on its own. */
|
|
45
68
|
export const OLLAMA_CLOUD_ORIGIN = "https://ollama.com";
|
|
@@ -67,10 +90,10 @@ const CLIENT_EXECUTED_ITEM_TYPES = new Set([
|
|
|
67
90
|
]);
|
|
68
91
|
|
|
69
92
|
export interface PassthroughWebSearchBridgePlan {
|
|
70
|
-
/** Resolved executor id.
|
|
93
|
+
/** Resolved executor id. Absent credential for that backend leaves the bridge disarmed. */
|
|
71
94
|
backend: ProviderWebSearchBridgeBackend;
|
|
72
|
-
/** Absolute search-API URL the
|
|
73
|
-
endpoint
|
|
95
|
+
/** Absolute search-API URL for the ollama backend. Other backends ignore this. */
|
|
96
|
+
endpoint?: string;
|
|
74
97
|
/** Searches actually executed per turn before further calls are refused. */
|
|
75
98
|
maxSearches: number;
|
|
76
99
|
/** Per-search deadline in milliseconds. */
|
|
@@ -112,6 +135,66 @@ export function resolveOllamaWebSearchEndpoint(
|
|
|
112
135
|
: undefined;
|
|
113
136
|
}
|
|
114
137
|
|
|
138
|
+
/** Credentials that may run a non-Ollama passthrough-bridge search. The key never rides the plan. */
|
|
139
|
+
export interface PassthroughWebSearchBridgeAuth {
|
|
140
|
+
openAiSidecar?: ResolvedOpenAiForwardSidecar;
|
|
141
|
+
anthropic?: { providerName: string; provider: OcxProviderConfig };
|
|
142
|
+
xai?: { providerName: string; provider: OcxProviderConfig };
|
|
143
|
+
gemini?: { providerName: string; provider: OcxProviderConfig };
|
|
144
|
+
exaApiKey?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the credential handle for one explicit bridge backend. Only that backend is inspected,
|
|
149
|
+
* so naming `exa` cannot spend a ChatGPT or Grok login, and naming `openai` cannot spend Exa.
|
|
150
|
+
*/
|
|
151
|
+
export function resolvePassthroughWebSearchBridgeAuth(
|
|
152
|
+
backend: ProviderWebSearchBridgeBackend | undefined,
|
|
153
|
+
config: OcxConfig,
|
|
154
|
+
openAiSidecar?: ResolvedOpenAiForwardSidecar,
|
|
155
|
+
): PassthroughWebSearchBridgeAuth {
|
|
156
|
+
switch (backend) {
|
|
157
|
+
case "openai":
|
|
158
|
+
return openAiSidecar ? { openAiSidecar } : {};
|
|
159
|
+
case "anthropic": {
|
|
160
|
+
const anthropic = findAnthropicSidecarProvider(config);
|
|
161
|
+
return anthropic ? { anthropic } : {};
|
|
162
|
+
}
|
|
163
|
+
case "xai": {
|
|
164
|
+
const xai = findXaiSidecarProvider(config);
|
|
165
|
+
if (!xai) return {};
|
|
166
|
+
if (validateXaiSearchOptions(xaiSearchOptionsFromConfig(config.webSearchSidecar ?? {}))) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
return { xai };
|
|
170
|
+
}
|
|
171
|
+
case "gemini": {
|
|
172
|
+
const gemini = findGeminiSidecarProvider(config);
|
|
173
|
+
return gemini ? { gemini } : {};
|
|
174
|
+
}
|
|
175
|
+
case "exa": {
|
|
176
|
+
const exaApiKey = config.webSearchSidecar?.exaApiKey;
|
|
177
|
+
return typeof exaApiKey === "string" && exaApiKey.length > 0 ? { exaApiKey } : {};
|
|
178
|
+
}
|
|
179
|
+
default:
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** True when this passthrough turn may need the ChatGPT sidecar for an openai-backed bridge. */
|
|
185
|
+
export function shouldResolveOpenAiPassthroughWebSearchBridge(
|
|
186
|
+
provider: OcxProviderConfig,
|
|
187
|
+
parsed: OcxParsedRequest,
|
|
188
|
+
isPassthrough: boolean,
|
|
189
|
+
): boolean {
|
|
190
|
+
if (!isPassthrough || parsed.stream !== true || !parsed._webSearch) return false;
|
|
191
|
+
if (provider.authMode !== "key") return false;
|
|
192
|
+
if (provider.webSearchBridge?.enabled !== true || provider.webSearchBridge.backend !== "openai") {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return toolChoiceToolPredicate(parsed.options.toolChoice)(buildWebSearchTool());
|
|
196
|
+
}
|
|
197
|
+
|
|
115
198
|
/**
|
|
116
199
|
* Decide whether this passthrough turn may run the web-search bridge.
|
|
117
200
|
*
|
|
@@ -126,7 +209,11 @@ export function resolveOllamaWebSearchEndpoint(
|
|
|
126
209
|
export function planPassthroughWebSearchBridge(
|
|
127
210
|
parsed: OcxParsedRequest,
|
|
128
211
|
provider: OcxProviderConfig,
|
|
129
|
-
options: {
|
|
212
|
+
options: {
|
|
213
|
+
isPassthrough: boolean;
|
|
214
|
+
stream: boolean;
|
|
215
|
+
auth?: PassthroughWebSearchBridgeAuth;
|
|
216
|
+
},
|
|
130
217
|
): PassthroughWebSearchBridgePlan | undefined {
|
|
131
218
|
if (!options.isPassthrough || !options.stream) return undefined;
|
|
132
219
|
if (!parsed._webSearch) return undefined;
|
|
@@ -137,10 +224,9 @@ export function planPassthroughWebSearchBridge(
|
|
|
137
224
|
if (!bridge || bridge.enabled !== true) return undefined;
|
|
138
225
|
// A tool_choice that excludes web search excludes the bridge too; the model may not search.
|
|
139
226
|
if (!toolChoiceToolPredicate(parsed.options.toolChoice)(buildWebSearchTool())) return undefined;
|
|
140
|
-
// Explicit-only
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!endpoint) return undefined;
|
|
227
|
+
// Explicit-only: an omitted backend never defaults to a paid sidecar search.
|
|
228
|
+
const backend = bridge.backend;
|
|
229
|
+
if (!backend) return undefined;
|
|
144
230
|
const maxSearches = Number.isInteger(bridge.maxSearches)
|
|
145
231
|
&& bridge.maxSearches! >= 1
|
|
146
232
|
&& bridge.maxSearches! <= 10
|
|
@@ -151,7 +237,18 @@ export function planPassthroughWebSearchBridge(
|
|
|
151
237
|
&& bridge.timeoutMs! <= 600_000
|
|
152
238
|
? bridge.timeoutMs!
|
|
153
239
|
: DEFAULT_BRIDGE_TIMEOUT_MS;
|
|
154
|
-
|
|
240
|
+
if (backend === "ollama") {
|
|
241
|
+
const endpoint = resolveOllamaWebSearchEndpoint(provider);
|
|
242
|
+
if (!endpoint) return undefined;
|
|
243
|
+
return { backend, endpoint, maxSearches, timeoutMs };
|
|
244
|
+
}
|
|
245
|
+
const auth = options.auth;
|
|
246
|
+
if (backend === "openai" && auth?.openAiSidecar) return { backend, maxSearches, timeoutMs };
|
|
247
|
+
if (backend === "anthropic" && auth?.anthropic) return { backend, maxSearches, timeoutMs };
|
|
248
|
+
if (backend === "xai" && auth?.xai) return { backend, maxSearches, timeoutMs };
|
|
249
|
+
if (backend === "gemini" && auth?.gemini) return { backend, maxSearches, timeoutMs };
|
|
250
|
+
if (backend === "exa" && auth?.exaApiKey) return { backend, maxSearches, timeoutMs };
|
|
251
|
+
return undefined;
|
|
155
252
|
}
|
|
156
253
|
|
|
157
254
|
/** One intercepted search call, carried from the upstream stream into the next request body. */
|
|
@@ -571,27 +668,154 @@ export function createOllamaBridgeExecutor(
|
|
|
571
668
|
plan: PassthroughWebSearchBridgePlan,
|
|
572
669
|
apiKey: string,
|
|
573
670
|
): PassthroughWebSearchBridgeExecutor {
|
|
574
|
-
return
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
671
|
+
return createPassthroughWebSearchBridgeExecutor(plan, { providerApiKey: apiKey });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Per-search credentials and sidecar settings. Secrets stay off the plan object. */
|
|
675
|
+
export interface PassthroughWebSearchBridgeExecutorContext {
|
|
676
|
+
providerApiKey?: string;
|
|
677
|
+
auth?: PassthroughWebSearchBridgeAuth;
|
|
678
|
+
hostedTool?: Record<string, unknown>;
|
|
679
|
+
describeImages?: boolean;
|
|
680
|
+
sidecar?: Pick<OcxWebSearchSidecarConfig, "model" | "reasoning" | "xSearch">;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const DEFAULT_OPENAI_BRIDGE_MODEL = "gpt-5.6-luna";
|
|
684
|
+
const DEFAULT_ANTHROPIC_BRIDGE_MODEL = "claude-sonnet-5";
|
|
685
|
+
const DEFAULT_XAI_BRIDGE_MODEL = "grok-4.6";
|
|
686
|
+
const DEFAULT_GEMINI_BRIDGE_MODEL = "gemini-3.8-flash";
|
|
687
|
+
const DEFAULT_BRIDGE_REASONING = "low";
|
|
688
|
+
|
|
689
|
+
function sidecarSettingsForBridge(
|
|
690
|
+
backend: ProviderWebSearchBridgeBackend,
|
|
691
|
+
plan: PassthroughWebSearchBridgePlan,
|
|
692
|
+
context: PassthroughWebSearchBridgeExecutorContext,
|
|
693
|
+
): SidecarSettings {
|
|
694
|
+
const sidecar = context.sidecar ?? {};
|
|
695
|
+
const model = backend === "anthropic" ? sidecar.model ?? DEFAULT_ANTHROPIC_BRIDGE_MODEL
|
|
696
|
+
: backend === "xai" ? sidecar.model ?? DEFAULT_XAI_BRIDGE_MODEL
|
|
697
|
+
: backend === "gemini" ? sidecar.model ?? DEFAULT_GEMINI_BRIDGE_MODEL
|
|
698
|
+
: sidecar.model ?? DEFAULT_OPENAI_BRIDGE_MODEL;
|
|
699
|
+
return {
|
|
700
|
+
model,
|
|
701
|
+
reasoning: sidecar.reasoning ?? DEFAULT_BRIDGE_REASONING,
|
|
702
|
+
timeoutMs: plan.timeoutMs,
|
|
703
|
+
describeImages: context.describeImages === true,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function executeBridgeQueries(
|
|
708
|
+
queries: string[],
|
|
709
|
+
runOne: (query: string, signal?: AbortSignal) => Promise<SidecarOutcome>,
|
|
710
|
+
signal?: AbortSignal,
|
|
711
|
+
): Promise<SidecarOutcome> {
|
|
712
|
+
const texts: string[] = [];
|
|
713
|
+
const sources: SidecarOutcome["sources"] = [];
|
|
714
|
+
const errors: string[] = [];
|
|
715
|
+
for (const query of queries) {
|
|
716
|
+
if (signal?.aborted) break;
|
|
717
|
+
const outcome = await runOne(query, signal);
|
|
718
|
+
if (outcome.error) {
|
|
719
|
+
errors.push(outcome.error);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
texts.push(queries.length > 1 ? "Results for \"" + query + "\":\n" + outcome.text : outcome.text);
|
|
723
|
+
for (const source of outcome.sources) {
|
|
724
|
+
if (!sources.some(existing => existing.url === source.url)) sources.push(source);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (texts.length === 0) {
|
|
728
|
+
return { text: "", sources: [], error: errors[0] ?? "web search produced no results" };
|
|
729
|
+
}
|
|
730
|
+
return { text: texts.join("\n\n"), sources };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Bind the executor for a planned backend. Ollama spends this provider's API key on the planned
|
|
735
|
+
* endpoint; every other backend spends the sidecar credential that armed the plan.
|
|
736
|
+
*/
|
|
737
|
+
export function createPassthroughWebSearchBridgeExecutor(
|
|
738
|
+
plan: PassthroughWebSearchBridgePlan,
|
|
739
|
+
context: PassthroughWebSearchBridgeExecutorContext,
|
|
740
|
+
): PassthroughWebSearchBridgeExecutor {
|
|
741
|
+
const settings = sidecarSettingsForBridge(plan.backend, plan, context);
|
|
742
|
+
return (queries, signal) => executeBridgeQueries(queries, async (query, querySignal) => {
|
|
743
|
+
switch (plan.backend) {
|
|
744
|
+
case "ollama":
|
|
745
|
+
if (!plan.endpoint) {
|
|
746
|
+
return { text: "", sources: [], error: "ollama web-search backend selected without an endpoint" };
|
|
747
|
+
}
|
|
748
|
+
return runOllamaWebSearch(
|
|
749
|
+
query,
|
|
750
|
+
context.providerApiKey ?? "",
|
|
751
|
+
plan.endpoint,
|
|
752
|
+
plan.timeoutMs,
|
|
753
|
+
querySignal,
|
|
754
|
+
);
|
|
755
|
+
case "openai": {
|
|
756
|
+
const sidecar = context.auth?.openAiSidecar;
|
|
757
|
+
if (!sidecar) {
|
|
758
|
+
return { text: "", sources: [], error: "openai web-search bridge selected without a ChatGPT sidecar" };
|
|
759
|
+
}
|
|
760
|
+
return runWebSearch(
|
|
761
|
+
query,
|
|
762
|
+
context.hostedTool ?? { type: "web_search" },
|
|
763
|
+
sidecar.provider,
|
|
764
|
+
sidecar.headers,
|
|
765
|
+
settings,
|
|
766
|
+
querySignal,
|
|
767
|
+
sidecar.recordOutcome,
|
|
768
|
+
);
|
|
584
769
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if (!
|
|
770
|
+
case "anthropic": {
|
|
771
|
+
const anthropic = context.auth?.anthropic;
|
|
772
|
+
if (!anthropic) {
|
|
773
|
+
return { text: "", sources: [], error: "anthropic web-search bridge selected without stored Anthropic OAuth" };
|
|
774
|
+
}
|
|
775
|
+
return runAnthropicWebSearch(
|
|
776
|
+
query,
|
|
777
|
+
anthropic.providerName,
|
|
778
|
+
anthropic.provider,
|
|
779
|
+
settings,
|
|
780
|
+
querySignal,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
case "xai": {
|
|
784
|
+
const xai = context.auth?.xai;
|
|
785
|
+
if (!xai) {
|
|
786
|
+
return { text: "", sources: [], error: "xai web-search bridge selected without stored Grok OAuth" };
|
|
787
|
+
}
|
|
788
|
+
return runXaiWebSearch(
|
|
789
|
+
query,
|
|
790
|
+
xai.providerName,
|
|
791
|
+
xai.provider,
|
|
792
|
+
settings,
|
|
793
|
+
xaiSearchOptionsFromConfig(context.sidecar ?? {}),
|
|
794
|
+
querySignal,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
case "gemini": {
|
|
798
|
+
const gemini = context.auth?.gemini;
|
|
799
|
+
if (!gemini) {
|
|
800
|
+
return { text: "", sources: [], error: "gemini web-search bridge selected without stored Antigravity OAuth" };
|
|
801
|
+
}
|
|
802
|
+
return runGeminiWebSearch(
|
|
803
|
+
query,
|
|
804
|
+
gemini.providerName,
|
|
805
|
+
gemini.provider,
|
|
806
|
+
settings,
|
|
807
|
+
querySignal,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
case "exa": {
|
|
811
|
+
const exaApiKey = context.auth?.exaApiKey;
|
|
812
|
+
if (!exaApiKey) {
|
|
813
|
+
return { text: "", sources: [], error: "exa web-search bridge selected without an exaApiKey" };
|
|
814
|
+
}
|
|
815
|
+
return runExaWebSearch(query, exaApiKey, settings, querySignal);
|
|
588
816
|
}
|
|
589
817
|
}
|
|
590
|
-
|
|
591
|
-
return { text: "", sources: [], error: errors[0] ?? "web search produced no results" };
|
|
592
|
-
}
|
|
593
|
-
return { text: texts.join("\n\n"), sources };
|
|
594
|
-
};
|
|
818
|
+
}, signal);
|
|
595
819
|
}
|
|
596
820
|
|
|
597
821
|
/**
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidecar credential locators shared by the web-search loop and the key-auth
|
|
3
|
+
* passthrough bridge. Kept out of `index.ts` so the bridge can resolve a backend
|
|
4
|
+
* without importing the barrel (a cycle: core loads both, and the barrel is still
|
|
5
|
+
* evaluating when the bridge asks for these names).
|
|
6
|
+
*/
|
|
7
|
+
import type { OcxConfig, OcxProviderConfig, OcxWebSearchSidecarConfig } from "../types";
|
|
8
|
+
import { resolveSidecarAuth } from "../sidecar/auth";
|
|
9
|
+
import { getAccountSet } from "../oauth/store";
|
|
10
|
+
import type { XaiSearchOptions } from "./xai-executor";
|
|
11
|
+
|
|
12
|
+
/** A configured anthropic-adapter OAuth provider whose ACTIVE stored account is usable (not needs-reauth). */
|
|
13
|
+
export interface AnthropicSidecarProvider {
|
|
14
|
+
providerName: string;
|
|
15
|
+
provider: OcxProviderConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* First enabled anthropic-adapter OAuth provider whose ACTIVE account holds a usable credential — the
|
|
20
|
+
* only path that can run web_search_20250305 without a ChatGPT forward provider. Presence is decided by
|
|
21
|
+
* getAccountSet + the active account's `needsReauth` marker (audit F1: getCredential alone can pick a
|
|
22
|
+
* terminally-invalid account); token refresh happens later at executor time.
|
|
23
|
+
* Delegates to the shared sidecar auth module (#2188) so web-search and vision
|
|
24
|
+
* cannot drift on what "Anthropic auth present" means.
|
|
25
|
+
*/
|
|
26
|
+
export function findAnthropicSidecarProvider(config: OcxConfig): AnthropicSidecarProvider | undefined {
|
|
27
|
+
const auth = resolveSidecarAuth(config);
|
|
28
|
+
if (!auth.isAnthropicAuth || !auth.anthropicProviderName || !auth.anthropicProvider) return undefined;
|
|
29
|
+
return { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* First enabled provider whose stored Grok OAuth account is active and not marked for
|
|
34
|
+
* reauth — the only credential the xai web-search executor may spend. Same account-set
|
|
35
|
+
* predicate the shared sidecar auth module applies to Anthropic.
|
|
36
|
+
*/
|
|
37
|
+
export function findXaiSidecarProvider(config: OcxConfig): { providerName: string; provider: OcxProviderConfig } | undefined {
|
|
38
|
+
// The stored Grok credential lives under the provider named "xai" (registry id);
|
|
39
|
+
// OAuth account sets are keyed by provider name, so the name IS the credential key.
|
|
40
|
+
const provider = config.providers["xai"];
|
|
41
|
+
if (!provider || provider.disabled === true || provider.authMode !== "oauth") return undefined;
|
|
42
|
+
const set = getAccountSet("xai");
|
|
43
|
+
const active = set?.accounts.find(account => account.id === set.activeAccountId);
|
|
44
|
+
if (active && active.needsReauth !== true) return { providerName: "xai", provider };
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* First usable Antigravity credential holder: the "google-antigravity" provider
|
|
50
|
+
* (registry id = OAuth store key, same narrowing as findXaiSidecarProvider) whose
|
|
51
|
+
* active stored account is healthy AND carries a discovered CCA projectId — the
|
|
52
|
+
* executor cannot form the envelope without it.
|
|
53
|
+
*/
|
|
54
|
+
export function findGeminiSidecarProvider(config: OcxConfig): { providerName: string; provider: OcxProviderConfig } | undefined {
|
|
55
|
+
const provider = config.providers["google-antigravity"];
|
|
56
|
+
if (!provider || provider.disabled === true || provider.authMode !== "oauth") return undefined;
|
|
57
|
+
const set = getAccountSet("google-antigravity");
|
|
58
|
+
const active = set?.accounts.find(account => account.id === set.activeAccountId);
|
|
59
|
+
if (!active || active.needsReauth === true) return undefined;
|
|
60
|
+
const projectId = (active.credential as { projectId?: string } | undefined)?.projectId;
|
|
61
|
+
if (!projectId) return undefined;
|
|
62
|
+
return { providerName: "google-antigravity", provider };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Lift the persisted xSearch config block into executor options (absent block = web_search only). */
|
|
66
|
+
export function xaiSearchOptionsFromConfig(cfg: Pick<OcxWebSearchSidecarConfig, "xSearch">): XaiSearchOptions {
|
|
67
|
+
const x = cfg.xSearch;
|
|
68
|
+
if (!x || x.enabled !== true) return {};
|
|
69
|
+
return {
|
|
70
|
+
xSearch: true,
|
|
71
|
+
...(x.allowedXHandles ? { allowedXHandles: x.allowedXHandles } : {}),
|
|
72
|
+
...(x.excludedXHandles ? { excludedXHandles: x.excludedXHandles } : {}),
|
|
73
|
+
...(x.fromDate ? { fromDate: x.fromDate } : {}),
|
|
74
|
+
...(x.toDate ? { toDate: x.toDate } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|