























@@ -0,0 +1,194 @@
1+import type { OpenClawConfig } from "../config/types.openclaw.js";
2+import { isRecord } from "../utils.js";
3+import { listProfilesForProvider } from "./auth-profiles/profile-list.js";
4+import { ensureAuthProfileStore } from "./auth-profiles/store.js";
5+import {
6+type CodexNativeSearchMode,
7+resolveCodexNativeWebSearchConfig,
8+} from "./codex-native-web-search.shared.js";
9+10+export type CodexNativeSearchActivation = {
11+globalWebSearchEnabled: boolean;
12+codexNativeEnabled: boolean;
13+codexMode: CodexNativeSearchMode;
14+nativeEligible: boolean;
15+hasRequiredAuth: boolean;
16+state: "managed_only" | "native_active";
17+inactiveReason?:
18+| "globally_disabled"
19+| "codex_not_enabled"
20+| "model_not_eligible"
21+| "codex_auth_missing";
22+};
23+24+export type CodexNativeSearchPayloadPatchResult = {
25+status: "payload_not_object" | "native_tool_already_present" | "injected";
26+};
27+28+export function isCodexNativeSearchEligibleModel(params: {
29+modelProvider?: string;
30+modelApi?: string;
31+}): boolean {
32+return params.modelProvider === "openai-codex" || params.modelApi === "openai-codex-responses";
33+}
34+35+export function hasCodexNativeWebSearchTool(tools: unknown): boolean {
36+if (!Array.isArray(tools)) {
37+return false;
38+}
39+return tools.some(
40+(tool) => isRecord(tool) && typeof tool.type === "string" && tool.type === "web_search",
41+);
42+}
43+44+export function hasAvailableCodexAuth(params: {
45+config?: OpenClawConfig;
46+agentDir?: string;
47+}): boolean {
48+if (
49+Object.values(params.config?.auth?.profiles ?? {}).some(
50+(profile) => isRecord(profile) && profile.provider === "openai-codex",
51+)
52+) {
53+return true;
54+}
55+56+if (params.agentDir) {
57+try {
58+if (
59+listProfilesForProvider(ensureAuthProfileStore(params.agentDir), "openai-codex").length > 0
60+) {
61+return true;
62+}
63+} catch {
64+// Fall back to config-based detection below.
65+}
66+}
67+return false;
68+}
69+70+export function resolveCodexNativeSearchActivation(params: {
71+config?: OpenClawConfig;
72+modelProvider?: string;
73+modelApi?: string;
74+agentDir?: string;
75+}): CodexNativeSearchActivation {
76+const globalWebSearchEnabled = params.config?.tools?.web?.search?.enabled !== false;
77+const codexConfig = resolveCodexNativeWebSearchConfig(params.config);
78+const nativeEligible = isCodexNativeSearchEligibleModel(params);
79+const hasRequiredAuth = params.modelProvider !== "openai-codex" || hasAvailableCodexAuth(params);
80+81+if (!globalWebSearchEnabled) {
82+return {
83+ globalWebSearchEnabled,
84+codexNativeEnabled: codexConfig.enabled,
85+codexMode: codexConfig.mode,
86+ nativeEligible,
87+ hasRequiredAuth,
88+state: "managed_only",
89+inactiveReason: "globally_disabled",
90+};
91+}
92+93+if (!codexConfig.enabled) {
94+return {
95+ globalWebSearchEnabled,
96+codexNativeEnabled: false,
97+codexMode: codexConfig.mode,
98+ nativeEligible,
99+ hasRequiredAuth,
100+state: "managed_only",
101+inactiveReason: "codex_not_enabled",
102+};
103+}
104+105+if (!nativeEligible) {
106+return {
107+ globalWebSearchEnabled,
108+codexNativeEnabled: true,
109+codexMode: codexConfig.mode,
110+nativeEligible: false,
111+ hasRequiredAuth,
112+state: "managed_only",
113+inactiveReason: "model_not_eligible",
114+};
115+}
116+117+if (!hasRequiredAuth) {
118+return {
119+ globalWebSearchEnabled,
120+codexNativeEnabled: true,
121+codexMode: codexConfig.mode,
122+nativeEligible: true,
123+hasRequiredAuth: false,
124+state: "managed_only",
125+inactiveReason: "codex_auth_missing",
126+};
127+}
128+129+return {
130+ globalWebSearchEnabled,
131+codexNativeEnabled: true,
132+codexMode: codexConfig.mode,
133+nativeEligible: true,
134+hasRequiredAuth: true,
135+state: "native_active",
136+};
137+}
138+139+export function buildCodexNativeWebSearchTool(
140+config: OpenClawConfig | undefined,
141+): Record<string, unknown> {
142+const nativeConfig = resolveCodexNativeWebSearchConfig(config);
143+const tool: Record<string, unknown> = {
144+type: "web_search",
145+external_web_access: nativeConfig.mode === "live",
146+};
147+148+if (nativeConfig.allowedDomains) {
149+tool.filters = {
150+allowed_domains: nativeConfig.allowedDomains,
151+};
152+}
153+154+if (nativeConfig.contextSize) {
155+tool.search_context_size = nativeConfig.contextSize;
156+}
157+158+if (nativeConfig.userLocation) {
159+tool.user_location = {
160+type: "approximate",
161+ ...nativeConfig.userLocation,
162+};
163+}
164+165+return tool;
166+}
167+168+export function patchCodexNativeWebSearchPayload(params: {
169+payload: unknown;
170+config?: OpenClawConfig;
171+}): CodexNativeSearchPayloadPatchResult {
172+if (!isRecord(params.payload)) {
173+return { status: "payload_not_object" };
174+}
175+176+const payload = params.payload;
177+if (hasCodexNativeWebSearchTool(payload.tools)) {
178+return { status: "native_tool_already_present" };
179+}
180+181+const tools = Array.isArray(payload.tools) ? [...payload.tools] : [];
182+tools.push(buildCodexNativeWebSearchTool(params.config));
183+payload.tools = tools;
184+return { status: "injected" };
185+}
186+187+export function shouldSuppressManagedWebSearchTool(params: {
188+config?: OpenClawConfig;
189+modelProvider?: string;
190+modelApi?: string;
191+agentDir?: string;
192+}): boolean {
193+return resolveCodexNativeSearchActivation(params).state === "native_active";
194+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。