@bitkyc08/opencodex 2.49.0 → 2.50.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/AGENTS_INSTALL.md +9 -1
- package/README.md +3 -0
- package/gui/dist/assets/index-C39tnjXO.js +115 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/claude/inbound.ts +17 -5
- package/src/cli/account-api.ts +18 -3
- package/src/cli/account-auth.ts +8 -1
- package/src/cli/account-extended.ts +2 -1
- package/src/cli/account.ts +1 -0
- package/src/cli/capabilities.ts +15 -1
- package/src/cli/index.ts +5 -1
- package/src/cli/models-runtime.ts +8 -3
- package/src/cli/observe.ts +13 -3
- package/src/clients/config-export/zcode.ts +24 -0
- package/src/codex/account-runtime-state.ts +6 -1
- package/src/codex/account-store.ts +72 -9
- package/src/codex/account-usability.ts +3 -2
- package/src/codex/auth-api.ts +107 -23
- package/src/codex/auth-context.ts +21 -0
- package/src/codex/catalog/parsing.ts +23 -0
- package/src/codex/catalog/provider-fetch.ts +71 -2
- package/src/codex/catalog/sync.ts +14 -0
- package/src/codex/inject.ts +3 -2
- package/src/codex/quota-auto-refresh.ts +6 -1
- package/src/codex/quota.ts +54 -8
- package/src/combos/index.ts +2 -0
- package/src/combos/resolve.ts +52 -0
- package/src/config.ts +58 -0
- package/src/generated/compatibility-version.json +72 -60
- package/src/lib/errors.ts +8 -0
- package/src/lib/privacy.ts +25 -0
- package/src/oauth/health.ts +47 -12
- package/src/oauth/index.ts +46 -8
- package/src/oauth/token-guardian.ts +32 -6
- package/src/providers/google-ai-studio-model-discovery.ts +74 -0
- package/src/providers/opencode-zen-rate-limit.ts +75 -0
- package/src/providers/quota.ts +15 -0
- package/src/providers/registry.ts +1 -1
- package/src/server/auth-cors.ts +6 -0
- package/src/server/chat-completions.ts +4 -4
- package/src/server/chat-native.ts +10 -1
- package/src/server/claude-messages.ts +5 -5
- package/src/server/images.ts +2 -2
- package/src/server/index.ts +25 -2
- package/src/server/management/logs-usage-routes.ts +4 -1
- package/src/server/management/model-rows.ts +16 -1
- package/src/server/management/oauth-account-routes.ts +6 -2
- package/src/server/management/provider-routes.ts +9 -2
- package/src/server/management/request-history-routes.ts +4 -2
- package/src/server/management/route-registry.ts +5 -4
- package/src/server/management/shared.ts +66 -3
- package/src/server/management-api.ts +1 -1
- package/src/server/request-decompress.ts +91 -3
- package/src/server/request-log.ts +10 -0
- package/src/server/responses/codex-ws-wire.ts +1 -1
- package/src/server/responses/compact.ts +8 -2
- package/src/server/responses/context-overflow.ts +11 -0
- package/src/server/responses/core.ts +144 -38
- package/src/server/responses/policy-fallback.ts +6 -2
- package/src/server/search.ts +2 -2
- package/src/service.ts +92 -7
- package/src/types/accounts.ts +18 -0
- package/src/types/config.ts +36 -0
- package/src/types/provider.ts +56 -0
- package/src/types.ts +4 -0
- package/src/web-search/ollama-executor.ts +127 -0
- package/src/web-search/passthrough-bridge.ts +761 -0
- package/gui/dist/assets/index-BtyONQrZ.js +0 -115
package/src/types/provider.ts
CHANGED
|
@@ -86,6 +86,57 @@ export interface RateLimitRetryPolicy {
|
|
|
86
86
|
respectRetryAfter?: boolean;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Backend ids admitted by `providers.<name>.webSearchBridge.backend`. Only `"ollama"` has a
|
|
91
|
+
* shipped executor; every other id is explicit-only and inert, the same contract the top-level
|
|
92
|
+
* `webSearchSidecar` uses for backends whose executor has not landed. Naming one of them keeps
|
|
93
|
+
* the bridge disarmed rather than silently falling back to a different search provider — in
|
|
94
|
+
* particular it never auto-selects a paid Luna or Exa search.
|
|
95
|
+
*/
|
|
96
|
+
export const PROVIDER_WEB_SEARCH_BRIDGE_BACKENDS = [
|
|
97
|
+
"ollama",
|
|
98
|
+
"openai",
|
|
99
|
+
"anthropic",
|
|
100
|
+
"xai",
|
|
101
|
+
"gemini",
|
|
102
|
+
"exa",
|
|
103
|
+
] as const;
|
|
104
|
+
|
|
105
|
+
export type ProviderWebSearchBridgeBackend = typeof PROVIDER_WEB_SEARCH_BRIDGE_BACKENDS[number];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Opt-in hosted-web-search bridge for a KEY-auth Responses passthrough provider
|
|
109
|
+
* (`providers.<name>.webSearchBridge`), default OFF (#3761).
|
|
110
|
+
*
|
|
111
|
+
* Codex always declares the hosted `{type:"web_search"}` tool. On the passthrough the proxy
|
|
112
|
+
* treats that as "the destination runs search itself" and relays it unchanged, which is true for
|
|
113
|
+
* the ChatGPT backend and for xAI but false for an OpenAI-shaped key gateway such as Ollama
|
|
114
|
+
* Cloud: the model answers with a `function_call` named `web_search` that nothing executes,
|
|
115
|
+
* and the undeclared-tool guard ends the turn. With this block enabled the proxy intercepts that
|
|
116
|
+
* call, runs the configured search backend itself, feeds the result back upstream, and shows
|
|
117
|
+
* Codex a hosted `web_search_call` cell.
|
|
118
|
+
*
|
|
119
|
+
* Never armed for `authMode: "forward"` (ChatGPT) or for a provider that executes hosted search
|
|
120
|
+
* upstream; see `planPassthroughWebSearchBridge` in `src/web-search/passthrough-bridge.ts`.
|
|
121
|
+
*/
|
|
122
|
+
export interface ProviderWebSearchBridgeConfig {
|
|
123
|
+
/** Master switch. Absent or false keeps today's relay-and-fail behavior exactly. */
|
|
124
|
+
enabled?: boolean;
|
|
125
|
+
/** Which executor runs the search. Absent disarms the bridge; there is no implicit default. */
|
|
126
|
+
backend?: ProviderWebSearchBridgeBackend;
|
|
127
|
+
/** Searches executed per turn before the bridge refuses further ones (1..10, default 3). */
|
|
128
|
+
maxSearches?: number;
|
|
129
|
+
/** Per-search deadline in milliseconds (1000..600000, default 60000). */
|
|
130
|
+
timeoutMs?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Absolute search-API URL. Required to use the `ollama` backend against anything other than
|
|
133
|
+
* the canonical `https://ollama.com` origin, which is the only origin derived automatically.
|
|
134
|
+
* The bridge sends the PROVIDER's own API key to this URL, so an operator setting it is
|
|
135
|
+
* authorizing that key for this destination.
|
|
136
|
+
*/
|
|
137
|
+
endpoint?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
89
140
|
/**
|
|
90
141
|
* User-configured display price for one model (USD per 1M tokens).
|
|
91
142
|
* Mirrors the `Cost4` shape used by the usage cost estimator; structurally
|
|
@@ -550,6 +601,11 @@ export interface OcxProviderConfig {
|
|
|
550
601
|
* SSE/JSON; raw inspection state remains authoritative.
|
|
551
602
|
*/
|
|
552
603
|
responsesSnapshotRepair?: boolean;
|
|
604
|
+
/**
|
|
605
|
+
* Opt-in hosted-web-search bridge for this KEY-auth Responses passthrough provider (#3761).
|
|
606
|
+
* Absent or disabled leaves the passthrough byte-identical to today.
|
|
607
|
+
*/
|
|
608
|
+
webSearchBridge?: ProviderWebSearchBridgeConfig;
|
|
553
609
|
/**
|
|
554
610
|
* Provider-wide mapping from Codex effort labels to upstream `reasoning_effort` values.
|
|
555
611
|
* Map a label to the reserved value `"__omit__"` to send no reasoning field at all for that
|
package/src/types.ts
CHANGED
|
@@ -101,6 +101,8 @@ export type {
|
|
|
101
101
|
ResponsesItemIdRepairConfig,
|
|
102
102
|
RateLimitRetryPolicy,
|
|
103
103
|
TransientRetryPolicy,
|
|
104
|
+
ProviderWebSearchBridgeBackend,
|
|
105
|
+
ProviderWebSearchBridgeConfig,
|
|
104
106
|
ProviderCostOverlay,
|
|
105
107
|
RequestPacingRule,
|
|
106
108
|
ProviderRequestPacingConfig,
|
|
@@ -111,6 +113,8 @@ export type {
|
|
|
111
113
|
OcxProviderConfig,
|
|
112
114
|
} from "./types/provider";
|
|
113
115
|
|
|
116
|
+
export { PROVIDER_WEB_SEARCH_BRIDGE_BACKENDS } from "./types/provider";
|
|
117
|
+
|
|
114
118
|
export type {
|
|
115
119
|
CodexAccount,
|
|
116
120
|
CodexAccountCredentials,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute ONE web search via the Ollama web-search API — the executor behind
|
|
3
|
+
* `providers.<name>.webSearchBridge.backend: "ollama"` (#3761).
|
|
4
|
+
*
|
|
5
|
+
* Documented contract: POST <origin>/api/web_search with a bearer key returns
|
|
6
|
+
* {results: [{title, url, content}]}; `max_results` defaults to 5 and caps at 10
|
|
7
|
+
* (https://docs.ollama.com/web-search). Like Exa, this lane returns ranked results
|
|
8
|
+
* rather than a prose answer, so the outcome text is a digest the routed model
|
|
9
|
+
* synthesizes from.
|
|
10
|
+
*
|
|
11
|
+
* The key is the PROVIDER's own API key: an operator who enables the bridge is reusing
|
|
12
|
+
* their Ollama Cloud route key on a second Ollama endpoint. That is why the planner
|
|
13
|
+
* refuses to derive a non-canonical origin on its own.
|
|
14
|
+
*
|
|
15
|
+
* Never throws; every error string passes redactSecretString and scrubs the literal key.
|
|
16
|
+
*/
|
|
17
|
+
import { applyUpstreamRecoveryInit, fetchWithResetRetry } from "../lib/upstream-retry";
|
|
18
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
19
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
20
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
21
|
+
import { redactSecretString } from "../lib/redact";
|
|
22
|
+
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
23
|
+
import type { SidecarOutcome } from "./executor";
|
|
24
|
+
|
|
25
|
+
/** Documented ceiling for the API's own `max_results`; a larger value is rejected upstream. */
|
|
26
|
+
export const OLLAMA_WEB_SEARCH_MAX_RESULTS = 5;
|
|
27
|
+
const OLLAMA_SNIPPET_CHARS = 1000;
|
|
28
|
+
|
|
29
|
+
function isRec(value: unknown): value is Record<string, unknown> {
|
|
30
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runOllamaWebSearch(
|
|
34
|
+
query: string,
|
|
35
|
+
apiKey: string,
|
|
36
|
+
endpoint: string,
|
|
37
|
+
timeoutMs: number,
|
|
38
|
+
abortSignal?: AbortSignal,
|
|
39
|
+
): Promise<SidecarOutcome> {
|
|
40
|
+
if (!apiKey) {
|
|
41
|
+
return { text: "", sources: [], error: "ollama web-search backend selected without a provider apiKey" };
|
|
42
|
+
}
|
|
43
|
+
// The executor KNOWS the secret, so pattern-based redaction is not enough: scrub the
|
|
44
|
+
// literal value before anything derived from an upstream body is returned.
|
|
45
|
+
const scrub = (value: string) =>
|
|
46
|
+
redactSecretString(value.split(apiKey).join("[redacted-provider-key]"));
|
|
47
|
+
const linkedSignal = signalWithTimeout(timeoutMs, abortSignal);
|
|
48
|
+
const sidecarExit = sidecarEnter("web-search");
|
|
49
|
+
const startedAt = Date.now();
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetchWithResetRetry(
|
|
52
|
+
recovery => fetch(endpoint, applyUpstreamRecoveryInit({
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
55
|
+
body: JSON.stringify({ query, max_results: OLLAMA_WEB_SEARCH_MAX_RESULTS }),
|
|
56
|
+
signal: linkedSignal.signal,
|
|
57
|
+
// Bun forwards custom headers across redirects, so a redirect would leak the key.
|
|
58
|
+
redirect: "manual",
|
|
59
|
+
}, recovery)),
|
|
60
|
+
{ abortSignal: linkedSignal.signal, label: "ollama-web-search-bridge" },
|
|
61
|
+
);
|
|
62
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
63
|
+
try {
|
|
64
|
+
let bounded: Awaited<ReturnType<typeof readBoundedResponseBytes>> | null = null;
|
|
65
|
+
try {
|
|
66
|
+
bounded = await readBoundedResponseBytes(res, {
|
|
67
|
+
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
|
|
68
|
+
signal: linkedSignal.signal,
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
const reason = linkedSignal.signal.reason;
|
|
72
|
+
if (linkedSignal.signal.aborted && reason instanceof Error && reason.name === "TimeoutError") {
|
|
73
|
+
throw reason;
|
|
74
|
+
}
|
|
75
|
+
// A body-read failure degrades to the status-only outcome below.
|
|
76
|
+
}
|
|
77
|
+
if (bounded?.oversized) {
|
|
78
|
+
const prefix = res.ok ? "ollama web-search response" : `ollama web-search HTTP ${res.status} response`;
|
|
79
|
+
return { text: "", sources: [], error: `${prefix} exceeded byte bound` };
|
|
80
|
+
}
|
|
81
|
+
const text = bounded ? new TextDecoder().decode(bounded.bytes) : "";
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
// Scrub BEFORE truncating: slicing first can cut the literal key at the boundary
|
|
84
|
+
// and leave an unscrubbable prefix in the surviving text.
|
|
85
|
+
return { text: "", sources: [], error: `ollama web-search HTTP ${res.status}: ${scrub(text).slice(0, 200)}` };
|
|
86
|
+
}
|
|
87
|
+
let payload: unknown = null;
|
|
88
|
+
try {
|
|
89
|
+
payload = JSON.parse(text);
|
|
90
|
+
} catch {
|
|
91
|
+
// The mapper owns the stable malformed/empty JSON outcome.
|
|
92
|
+
}
|
|
93
|
+
return mapOllamaSearchResponse(payload);
|
|
94
|
+
} finally {
|
|
95
|
+
detachBodyGuard();
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
99
|
+
console.warn(`[web-search] ollama bridge ${kind} (${Date.now() - startedAt}ms)`);
|
|
100
|
+
return { text: "", sources: [], error: scrub(error instanceof Error ? error.message : String(error)) };
|
|
101
|
+
} finally {
|
|
102
|
+
sidecarExit();
|
|
103
|
+
linkedSignal.cleanup();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Map an Ollama /api/web_search payload to a digest the routed model can synthesize from. */
|
|
108
|
+
export function mapOllamaSearchResponse(payload: unknown): SidecarOutcome {
|
|
109
|
+
if (!isRec(payload) || !Array.isArray(payload.results)) {
|
|
110
|
+
return { text: "", sources: [], error: "ollama web-search returned a non-JSON or shapeless body" };
|
|
111
|
+
}
|
|
112
|
+
const sources: WebSearchSource[] = [];
|
|
113
|
+
const lines: string[] = [];
|
|
114
|
+
const seen = new Set<string>();
|
|
115
|
+
for (const result of payload.results) {
|
|
116
|
+
if (!isRec(result) || typeof result.url !== "string" || result.url.length === 0) continue;
|
|
117
|
+
if (seen.has(result.url)) continue;
|
|
118
|
+
seen.add(result.url);
|
|
119
|
+
const title = typeof result.title === "string" && result.title.length > 0 ? result.title : result.url;
|
|
120
|
+
sources.push({ url: result.url, ...(title !== result.url ? { title } : {}) });
|
|
121
|
+
const snippet = typeof result.content === "string" ? result.content.trim().slice(0, OLLAMA_SNIPPET_CHARS) : "";
|
|
122
|
+
lines.push(`- ${title}: ${snippet || "(no excerpt)"} [${result.url}]`);
|
|
123
|
+
}
|
|
124
|
+
if (lines.length === 0) return { text: "", sources: [], error: "ollama web-search returned no results" };
|
|
125
|
+
return { text: `Search results:\n${lines.join("\n")}`, sources };
|
|
126
|
+
}
|
|
127
|
+
|