@bitkyc08/opencodex 2.6.7 → 2.6.8

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.
@@ -1,5 +1,6 @@
1
1
  import type { OcxProviderConfig } from "../types";
2
2
  import { KIRO_MODELS, KIRO_MODEL_CONTEXT_WINDOWS, KIRO_MODEL_REASONING_EFFORTS } from "./kiro-models";
3
+ import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS } from "./antigravity-models";
3
4
 
4
5
  export type ProviderAuthKind = "forward" | "oauth" | "key" | "local";
5
6
  export type MetadataModelIdNormalize = "case-insensitive";
@@ -34,6 +35,9 @@ export interface ProviderRegistryEntry {
34
35
  jawcodeBundle?: string;
35
36
  extraMetadataAliases?: string[];
36
37
  metadataModelIdNormalize?: MetadataModelIdNormalize;
38
+ googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
39
+ project?: string;
40
+ location?: string;
37
41
  }
38
42
 
39
43
  export type ProviderConfigSeed = Pick<
@@ -43,6 +47,7 @@ export type ProviderConfigSeed = Pick<
43
47
  | "reasoningEfforts" | "modelReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
44
48
  | "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
45
49
  | "autoToolChoiceOnlyModels" | "preserveReasoningContentModels" | "escapeBuiltinToolNames"
50
+ | "googleMode" | "project" | "location"
46
51
  >;
47
52
 
48
53
 
@@ -260,6 +265,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
260
265
  { id: "openrouter", label: "OpenRouter", adapter: "openai-chat", baseUrl: "https://openrouter.ai/api/v1", authKind: "key", featured: true, dashboardUrl: "https://openrouter.ai/keys", jawcodeBundle: "openrouter" },
261
266
  { id: "groq", label: "Groq", adapter: "openai-chat", baseUrl: "https://api.groq.com/openai/v1", authKind: "key", featured: true, dashboardUrl: "https://console.groq.com/keys" },
262
267
  { id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true, dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3-pro", jawcodeBundle: "google", extraMetadataAliases: ["gemini"] },
268
+ { id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
269
+ { id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.5-flash-low", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
263
270
  { id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
264
271
  { id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", featured: true, note: "Local — key usually blank", reasoningEffortMap: OLLAMA_REASONING_MAP },
265
272
  { id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", featured: true, note: "Local — key usually blank" },
package/src/redact.ts CHANGED
@@ -69,3 +69,29 @@ export function redactUrlForLog(url: string): string {
69
69
  return redactSecretString(url.split("?")[0] ?? url);
70
70
  }
71
71
  }
72
+
73
+ const USER_HOME_PATH_PATTERNS: Array<[RegExp, string]> = [
74
+ // Windows: C:\Users\<name>\... -> C:\Users\[USER]\...
75
+ [/([A-Za-z]:\\Users\\)[^\\/]+/gi, "$1[USER]"],
76
+ // POSIX: /Users/<name>/... (macOS) and /home/<name>/... (Linux)
77
+ [/(\/(?:Users|home)\/)[^/]+/gi, "$1[USER]"],
78
+ ];
79
+
80
+ // Path segments whose name alone looks sensitive. Masked so a configured path
81
+ // cannot surface a secret-flavored substring in diagnostics or logs.
82
+ const SENSITIVE_SEGMENT_PATTERN = /(^|[\\/])([^\\/]*(?:secret|password|passwd|token|api[-_]?key|apikey|credential|email)[^\\/]*)(?=[\\/]|$)/gi;
83
+
84
+ /**
85
+ * Mask the username segment of an absolute home path so diagnostics can print
86
+ * paths without leaking the OS account name, and mask any path segment whose
87
+ * name looks sensitive (token/secret/password/credential/email/...). Path-focused
88
+ * and secret-safe: also runs {@link redactSecretString} for token-shaped values.
89
+ */
90
+ export function redactUserPath(path: string): string {
91
+ let masked = path;
92
+ for (const [pattern, replacement] of USER_HOME_PATH_PATTERNS) {
93
+ masked = masked.replace(pattern, replacement);
94
+ }
95
+ masked = masked.replace(SENSITIVE_SEGMENT_PATTERN, (_m, sep: string) => `${sep}[REDACTED]`);
96
+ return redactSecretString(masked);
97
+ }
package/src/router.ts CHANGED
@@ -94,6 +94,12 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
94
94
  baseUrl: registryEntry.baseUrl,
95
95
  authMode: canonicalAuthMode,
96
96
  apiKey: resolveEnvValue(provider.apiKey),
97
+ // Backfill the Google wire mode + Vertex project/location from the registry when the user
98
+ // config omits them, so a minimal `google-vertex`/`google-antigravity` entry still routes
99
+ // through the correct branch (CCA/Vertex) instead of falling back to AI Studio.
100
+ ...(provider.googleMode === undefined && registryEntry.googleMode !== undefined ? { googleMode: registryEntry.googleMode } : {}),
101
+ ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
102
+ ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
97
103
  ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
98
104
  ...(provider.reasoningEfforts === undefined && registryEntry.reasoningEfforts !== undefined ? { reasoningEfforts: registryEntry.reasoningEfforts } : {}),
99
105
  ...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
package/src/server.ts CHANGED
@@ -35,7 +35,7 @@ import { parseRequest } from "./responses/parser";
35
35
  import { routeModel } from "./router";
36
36
  import { namespacedToolName } from "./types";
37
37
  import {
38
- clearLoginState, getLoginStatus, getValidAccessToken, isOAuthProvider,
38
+ clearLoginState, getLoginStatus, getOAuthCredentialProjectId, getValidAccessToken, isOAuthProvider,
39
39
  listOAuthProviders, reconcileOAuthProviders, startLoginFlow, UnsupportedOAuthProviderError, upsertOAuthProvider,
40
40
  } from "./oauth/index";
41
41
  import type { CatalogModel } from "./codex-catalog";
@@ -48,7 +48,7 @@ import { enrichProviderFromCatalog, listKeyLoginProviders } from "./oauth/key-pr
48
48
  import { deriveProviderPresets } from "./providers/derive";
49
49
  import type { AdapterEvent, OcxConfig, OcxProviderConfig } from "./types";
50
50
  import type { OcxUsage } from "./types";
51
- import { DEFAULT_PROVIDER_CONTEXT_CAP, providerContextCap, providerContextCaps, setProviderContextCap } from "./provider-context-cap";
51
+ import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "./provider-context-cap";
52
52
  import {
53
53
  appendUsageEntry,
54
54
  readUsageEntries,
@@ -423,6 +423,12 @@ async function handleResponses(
423
423
  if (route.provider.authMode === "oauth") {
424
424
  try {
425
425
  route.provider = { ...route.provider, apiKey: await getValidAccessToken(route.providerName) };
426
+ // Antigravity (cloud-code-assist) needs the discovered Cloud Code Assist project id in the
427
+ // CCA envelope; the server injects only the bare token, so pull project from the credential.
428
+ if (route.provider.googleMode === "cloud-code-assist" && !route.provider.project) {
429
+ const projectId = getOAuthCredentialProjectId(route.providerName);
430
+ if (projectId) route.provider = { ...route.provider, project: projectId };
431
+ }
426
432
  } catch (err) {
427
433
  if (err instanceof UnsupportedOAuthProviderError) {
428
434
  return formatErrorResponse(
@@ -449,7 +455,7 @@ async function handleResponses(
449
455
  const recordTerminalOutcomes = options.recordTerminalOutcomes !== false;
450
456
 
451
457
  if ("passthrough" in adapter && adapter.passthrough) {
452
- const request = adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
458
+ const request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
453
459
  // Abort the upstream if the client disconnects. A directly-relayed body does not propagate the
454
460
  // consumer's cancel to a signalled fetch, so we pass the signal and relay through relayWithAbort,
455
461
  // whose cancel() aborts the upstream — preventing leaked connections (RC2, passthrough path).
@@ -589,7 +595,7 @@ async function handleResponses(
589
595
  const cleanupUpstreamAbort = linkAbortSignal(upstream, options.abortSignal);
590
596
  const connectMs = config.connectTimeoutMs ?? 100_000;
591
597
 
592
- const request = adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
598
+ const request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
593
599
  if (typeof request.usageLog?.inputTokens === "number") {
594
600
  logCtx.usageLogInputTokens = request.usageLog.inputTokens;
595
601
  }
@@ -1802,12 +1808,44 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
1802
1808
  }
1803
1809
 
1804
1810
  if (url.pathname === "/api/provider-context-caps" && req.method === "GET") {
1805
- return jsonResponse({ cap: DEFAULT_PROVIDER_CONTEXT_CAP, caps: providerContextCaps(config) });
1811
+ return jsonResponse({ cap: DEFAULT_PROVIDER_CONTEXT_CAP, value: globalContextCapValue(config), caps: providerContextCaps(config) });
1806
1812
  }
1807
1813
 
1808
1814
  if (url.pathname === "/api/provider-context-caps" && req.method === "PUT") {
1809
- let body: { provider?: unknown; enabled?: unknown };
1815
+ let body: { provider?: unknown; enabled?: unknown; value?: unknown; setAll?: unknown };
1810
1816
  try { body = await req.json(); } catch { return jsonResponse({ error: "invalid JSON body" }, 400); }
1817
+ const { saveConfig: save } = await import("./config");
1818
+ const { clearModelCache } = await import("./model-cache");
1819
+ const respond = () => jsonResponse({ ok: true, cap: DEFAULT_PROVIDER_CONTEXT_CAP, value: globalContextCapValue(config), caps: providerContextCaps(config) });
1820
+
1821
+ // Branch 1: set the global cap value and re-point every enabled provider to it.
1822
+ if (body.value !== undefined) {
1823
+ if (typeof body.value !== "number" || !Number.isFinite(body.value) || body.value <= 0) {
1824
+ return jsonResponse({ error: "value must be a positive number" }, 400);
1825
+ }
1826
+ const affected = Object.keys(providerContextCaps(config));
1827
+ setGlobalContextCapValue(config, body.value);
1828
+ save(config);
1829
+ for (const provider of affected) clearModelCache(provider);
1830
+ await refreshCodexCatalogBestEffort();
1831
+ return respond();
1832
+ }
1833
+
1834
+ // Branch 2: enable/clear the cap for every provider at once.
1835
+ if (body.setAll !== undefined) {
1836
+ if (typeof body.setAll !== "boolean") {
1837
+ return jsonResponse({ error: "setAll must be a boolean" }, 400);
1838
+ }
1839
+ const before = Object.keys(providerContextCaps(config));
1840
+ const names = Object.keys(config.providers);
1841
+ setAllProviderContextCaps(config, names, body.setAll);
1842
+ save(config);
1843
+ for (const provider of new Set([...before, ...names])) clearModelCache(provider);
1844
+ await refreshCodexCatalogBestEffort();
1845
+ return respond();
1846
+ }
1847
+
1848
+ // Branch 3: existing per-provider toggle (enable writes the current global value).
1811
1849
  if (typeof body.provider !== "string" || typeof body.enabled !== "boolean") {
1812
1850
  return jsonResponse({ error: "provider string and enabled boolean are required" }, 400);
1813
1851
  }
@@ -1819,12 +1857,10 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
1819
1857
  return jsonResponse({ error: "unknown provider" }, 404);
1820
1858
  }
1821
1859
  setProviderContextCap(config, provider, body.enabled);
1822
- const { saveConfig: save } = await import("./config");
1823
1860
  save(config);
1824
- const { clearModelCache } = await import("./model-cache");
1825
1861
  clearModelCache(provider);
1826
1862
  await refreshCodexCatalogBestEffort();
1827
- return jsonResponse({ ok: true, cap: DEFAULT_PROVIDER_CONTEXT_CAP, caps: providerContextCaps(config) });
1863
+ return respond();
1828
1864
  }
1829
1865
 
1830
1866
  // Enable/disable models: which routed models Codex sees. PUT hides them from the catalog +
package/src/types.ts CHANGED
@@ -207,6 +207,8 @@ export interface OcxConfig {
207
207
  disabledModels?: string[];
208
208
  /** Provider-level Codex-visible context caps. Values only lower known model context windows. */
209
209
  providerContextCaps?: Record<string, number>;
210
+ /** Global Codex-visible context cap value (tokens). Falls back to DEFAULT_PROVIDER_CONTEXT_CAP. */
211
+ contextCapValue?: number;
210
212
  /** Bind hostname. Default "127.0.0.1" (loopback only). Set "0.0.0.0" to expose on all interfaces. */
211
213
  hostname?: string;
212
214
  /** Upstream stall timeout (seconds). After this many seconds of no upstream data, emits response.incomplete. Default 90. Min 1. */
@@ -326,6 +328,16 @@ export interface OcxProviderConfig {
326
328
  * attached images are described by a gpt vision model and replaced with text before the call.
327
329
  */
328
330
  noVisionModels?: string[];
331
+ /**
332
+ * Google adapter mode. "ai-studio" (default) = Generative Language API + x-goog-api-key.
333
+ * "vertex" = Vertex AI project/location endpoints with GCP ADC (or x-goog-api-key).
334
+ * "cloud-code-assist" = Google Antigravity (Cloud Code Assist) OAuth + CCA envelope.
335
+ */
336
+ googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
337
+ /** Vertex AI GCP project id (or GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env). */
338
+ project?: string;
339
+ /** Vertex AI location, e.g. "us-central1" or "global" (or GOOGLE_CLOUD_LOCATION env). */
340
+ location?: string;
329
341
  }
330
342
 
331
343
  export interface CodexAccount {
@@ -22,6 +22,14 @@ export interface UsageDay {
22
22
  requests: number;
23
23
  reportedRequests: number;
24
24
  totalTokens: number;
25
+ models: UsageDayModel[];
26
+ }
27
+
28
+ export interface UsageDayModel {
29
+ model: string;
30
+ provider: string;
31
+ requests: number;
32
+ totalTokens: number;
25
33
  }
26
34
 
27
35
  export interface UsageModel {
@@ -124,24 +132,43 @@ function buildDayGrid(range: UsageRange, since: number | null, now: number, entr
124
132
  const window = rangeWindow(range, now);
125
133
  const days = range === "all" ? dayCountForAllRange(entries, now) : window.days;
126
134
  const grid = new Map<string, UsageDay>();
135
+ // Per-day model breakdown accumulator, keyed by day then provider/model, so the 7d bar chart can
136
+ // render a per-model stacked bar with a hover tooltip without a second pass over the entries.
137
+ const dayModels = new Map<string, Map<string, UsageDayModel>>();
138
+ const bumpDayModel = (dayKey: string, entry: PersistedUsageEntry): void => {
139
+ let models = dayModels.get(dayKey);
140
+ if (!models) { models = new Map(); dayModels.set(dayKey, models); }
141
+ const mKey = `${entry.provider}/${entry.model}`;
142
+ let m = models.get(mKey);
143
+ if (!m) { m = { model: entry.model, provider: entry.provider, requests: 0, totalTokens: 0 }; models.set(mKey, m); }
144
+ m.requests += 1;
145
+ if (typeof entry.totalTokens === "number") m.totalTokens += entry.totalTokens;
146
+ else if (entry.usage) m.totalTokens += entry.usage.inputTokens + entry.usage.outputTokens;
147
+ };
127
148
  for (let i = days - 1; i >= 0; i--) {
128
149
  const key = localDateKey(now - i * DAY_MS);
129
- grid.set(key, { date: key, requests: 0, reportedRequests: 0, totalTokens: 0 });
150
+ grid.set(key, { date: key, requests: 0, reportedRequests: 0, totalTokens: 0, models: [] });
130
151
  }
131
152
  for (const entry of entries) {
132
153
  const key = localDateKey(entry.timestamp);
133
154
  let day = grid.get(key);
134
155
  if (!day) {
135
- day = { date: key, requests: 0, reportedRequests: 0, totalTokens: 0 };
156
+ day = { date: key, requests: 0, reportedRequests: 0, totalTokens: 0, models: [] };
136
157
  grid.set(key, day);
137
158
  }
138
159
  day.requests += 1;
139
160
  if (entry.usageStatus === "reported") day.reportedRequests += 1;
140
161
  if (typeof entry.totalTokens === "number") day.totalTokens += entry.totalTokens;
141
162
  else if (entry.usage) day.totalTokens += entry.usage.inputTokens + entry.usage.outputTokens;
163
+ bumpDayModel(key, entry);
142
164
  }
143
165
  void since;
144
- return [...grid.values()].sort((a, b) => a.date.localeCompare(b.date));
166
+ const out = [...grid.values()].sort((a, b) => a.date.localeCompare(b.date));
167
+ for (const day of out) {
168
+ const models = dayModels.get(day.date);
169
+ if (models) day.models = [...models.values()].sort((a, b) => b.requests - a.requests);
170
+ }
171
+ return out;
145
172
  }
146
173
 
147
174
  function buildModels(entries: PersistedUsageEntry[], totalRequests: number): UsageModel[] {
@@ -130,7 +130,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
130
130
  ...parsed, stream: false,
131
131
  context: { ...parsed.context, messages, tools: forceAnswer ? toolsNoWebSearch : allTools },
132
132
  };
133
- const request = adapter.buildRequest(iterParsed, { headers: selectedForwardHeaders });
133
+ const request = await adapter.buildRequest(iterParsed, { headers: selectedForwardHeaders });
134
134
  let resp: Response;
135
135
  try {
136
136
  resp = adapter.fetchResponse