@gajae-code/coding-agent 0.15.5 → 0.16.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.
Files changed (272) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  5. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  6. package/dist/types/cli/root-flags.d.ts +14 -0
  7. package/dist/types/cli/update-cli.d.ts +15 -0
  8. package/dist/types/cli.d.ts +7 -0
  9. package/dist/types/commands/auth-gateway.d.ts +3 -0
  10. package/dist/types/commands/gc.d.ts +13 -0
  11. package/dist/types/commands/launch.d.ts +10 -0
  12. package/dist/types/commands/model-presets.d.ts +21 -0
  13. package/dist/types/config/autorouting-tier-map.d.ts +120 -0
  14. package/dist/types/config/file-lock.d.ts +54 -2
  15. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  16. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  17. package/dist/types/config/keybindings.d.ts +3 -2
  18. package/dist/types/config/model-preset-registry.d.ts +368 -0
  19. package/dist/types/config/model-profile-activation.d.ts +39 -0
  20. package/dist/types/config/model-profiles.d.ts +5 -3
  21. package/dist/types/config/model-registry.d.ts +17 -5
  22. package/dist/types/config/settings-schema.d.ts +19 -1
  23. package/dist/types/config/settings.d.ts +2 -0
  24. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  25. package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
  26. package/dist/types/crash/record-loader.d.ts +15 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  28. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  29. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  31. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  33. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/main.d.ts +3 -1
  38. package/dist/types/master-mode/context.d.ts +21 -0
  39. package/dist/types/master-mode/first-request.d.ts +34 -0
  40. package/dist/types/master-mode/snapshot.d.ts +32 -0
  41. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  43. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  44. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  45. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  46. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  47. package/dist/types/modes/jobs-observer.d.ts +36 -0
  48. package/dist/types/modes/print-mode.d.ts +17 -0
  49. package/dist/types/modes/ui-language.d.ts +7 -1
  50. package/dist/types/sdk/broker/broker.d.ts +80 -1
  51. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  52. package/dist/types/sdk/broker/index.d.ts +3 -0
  53. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  54. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  56. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  57. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  58. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  59. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  60. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  61. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  62. package/dist/types/sdk/bus/index.d.ts +6 -0
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  65. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  66. package/dist/types/sdk/cli/index.d.ts +1 -0
  67. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  68. package/dist/types/sdk/cli/rows.d.ts +4 -3
  69. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  70. package/dist/types/sdk/client/client.d.ts +3 -2
  71. package/dist/types/sdk/client/discovery.d.ts +1 -0
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/host.d.ts +10 -0
  77. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  78. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +67 -1
  80. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  81. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  82. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  83. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  84. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  85. package/dist/types/sdk/router/session-router.d.ts +11 -0
  86. package/dist/types/sdk/session.d.ts +16 -0
  87. package/dist/types/sdk/startup-capability.d.ts +2 -1
  88. package/dist/types/session/account-inventory.d.ts +1 -0
  89. package/dist/types/session/agent-session.d.ts +24 -7
  90. package/dist/types/session/credential-store-errors.d.ts +1 -0
  91. package/dist/types/session/fold-coordinator.d.ts +201 -0
  92. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  93. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  94. package/dist/types/session/session-manager.d.ts +65 -0
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/telemetry/control.d.ts +11 -0
  98. package/dist/types/telemetry/events.d.ts +20 -0
  99. package/dist/types/telemetry/index.d.ts +3 -0
  100. package/dist/types/telemetry/transport.d.ts +22 -0
  101. package/dist/types/tools/bash-interactive.d.ts +23 -0
  102. package/dist/types/tools/bash.d.ts +4 -0
  103. package/dist/types/tools/gh.d.ts +8 -0
  104. package/dist/types/tools/index.d.ts +10 -5
  105. package/dist/types/web/search/providers/base.d.ts +9 -4
  106. package/dist/types/web/search/providers/xai.d.ts +2 -1
  107. package/dist/types/web/search/types.d.ts +14 -0
  108. package/package.json +10 -7
  109. package/scripts/generate-hotkeys-docs.ts +4 -0
  110. package/scripts/generate-sdk-operation-inventory.ts +9 -3
  111. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  112. package/src/async/job-manager.ts +671 -39
  113. package/src/cli/accounts-cli.ts +9 -5
  114. package/src/cli/args.ts +17 -0
  115. package/src/cli/auth-broker-cli.ts +40 -12
  116. package/src/cli/auth-gateway-cli.ts +273 -97
  117. package/src/cli/config-cli.ts +2 -2
  118. package/src/cli/fast-help.ts +4 -0
  119. package/src/cli/model-presets-cli.ts +100 -0
  120. package/src/cli/root-flags.ts +5 -0
  121. package/src/cli/update-cli.ts +117 -16
  122. package/src/cli.ts +6 -2
  123. package/src/commands/auth-gateway.ts +12 -8
  124. package/src/commands/gc.ts +19 -0
  125. package/src/commands/launch.ts +86 -18
  126. package/src/commands/model-presets.ts +24 -0
  127. package/src/commands/sdk.ts +113 -21
  128. package/src/config/autorouting-tier-map.ts +44 -0
  129. package/src/config/file-lock-gc.ts +9 -4
  130. package/src/config/file-lock.ts +890 -99
  131. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  132. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  133. package/src/config/keybindings.ts +6 -2
  134. package/src/config/model-preset-registry.ts +2795 -0
  135. package/src/config/model-profile-activation.ts +115 -28
  136. package/src/config/model-profile-contract.ts +4 -1
  137. package/src/config/model-profiles.ts +56 -14
  138. package/src/config/model-registry.ts +1044 -122
  139. package/src/config/model-resolver.ts +5 -0
  140. package/src/config/settings-schema.ts +19 -1
  141. package/src/config/settings.ts +11 -1
  142. package/src/coordinator-mcp/model-preset.ts +32 -17
  143. package/src/coordinator-mcp/projection-scan.ts +592 -0
  144. package/src/coordinator-mcp/question-state.ts +9 -11
  145. package/src/coordinator-mcp/server.ts +111 -42
  146. package/src/coordinator-mcp/session-reaper.ts +9 -3
  147. package/src/crash/index-store.ts +2 -0
  148. package/src/crash/record-loader.ts +10 -0
  149. package/src/extensibility/extensions/loader.ts +0 -1
  150. package/src/extensibility/extensions/types.ts +2 -3
  151. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  152. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  153. package/src/gjc-runtime/gc-render.ts +29 -0
  154. package/src/gjc-runtime/gc-runtime.ts +174 -11
  155. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  156. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  157. package/src/gjc-runtime/session-state-lock.ts +1321 -147
  158. package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
  159. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  160. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  161. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  162. package/src/hooks/skill-state.ts +6 -0
  163. package/src/index.ts +1 -0
  164. package/src/internal-urls/docs-index.generated.ts +14 -13
  165. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  166. package/src/lsp/config.ts +4 -3
  167. package/src/lsp/path-trust.ts +12 -6
  168. package/src/main.ts +66 -4
  169. package/src/master-mode/context.ts +50 -0
  170. package/src/master-mode/first-request.ts +65 -0
  171. package/src/master-mode/snapshot.ts +205 -0
  172. package/src/modes/acp/acp-agent.ts +616 -161
  173. package/src/modes/components/footer.ts +4 -2
  174. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  175. package/src/modes/components/jobs-overlay-model.ts +99 -16
  176. package/src/modes/components/model-selector.ts +158 -18
  177. package/src/modes/components/oauth-selector.ts +6 -1
  178. package/src/modes/components/settings-selector.ts +1 -0
  179. package/src/modes/components/status-line/model-name.ts +53 -0
  180. package/src/modes/components/status-line/priority-row.ts +107 -0
  181. package/src/modes/components/status-line/segments.ts +29 -10
  182. package/src/modes/components/tool-status-header.ts +151 -15
  183. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  184. package/src/modes/controllers/input-controller.ts +51 -6
  185. package/src/modes/controllers/selector-controller.ts +191 -96
  186. package/src/modes/interactive-mode.ts +14 -1
  187. package/src/modes/jobs-observer.ts +149 -16
  188. package/src/modes/print-mode.ts +64 -0
  189. package/src/modes/runtime-init.ts +1 -1
  190. package/src/modes/ui-language.ts +49 -2
  191. package/src/modes/utils/injected-user-submission.ts +15 -1
  192. package/src/prompts/system/master-mode.md +28 -0
  193. package/src/prompts/tools/bash.md +2 -2
  194. package/src/prompts/tools/fold-receipt.md +3 -0
  195. package/src/prompts/tools/job.md +1 -1
  196. package/src/sdk/acp/adapter.ts +17 -2
  197. package/src/sdk/broker/broker.ts +1671 -34
  198. package/src/sdk/broker/endpoint-authority.ts +86 -0
  199. package/src/sdk/broker/ensure.ts +5 -0
  200. package/src/sdk/broker/index.ts +3 -0
  201. package/src/sdk/broker/lifecycle.ts +217 -46
  202. package/src/sdk/broker/master-capability.ts +232 -0
  203. package/src/sdk/broker/session-index.ts +121 -17
  204. package/src/sdk/broker/session-scope.ts +272 -0
  205. package/src/sdk/broker/spawn-authority.ts +1031 -0
  206. package/src/sdk/broker/spawn-substrate.ts +548 -0
  207. package/src/sdk/broker/state-version.ts +26 -14
  208. package/src/sdk/broker/transport.ts +1 -0
  209. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  210. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  211. package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
  212. package/src/sdk/bus/index.ts +51 -4
  213. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  214. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  215. package/src/sdk/bus/slack-conversation.ts +5 -1
  216. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  217. package/src/sdk/cli/index.ts +1 -0
  218. package/src/sdk/cli/master-cli.ts +259 -0
  219. package/src/sdk/cli/rows.ts +11 -8
  220. package/src/sdk/cli/session-cli.ts +276 -6
  221. package/src/sdk/client/client.ts +14 -8
  222. package/src/sdk/client/discovery.ts +3 -1
  223. package/src/sdk/host/control/dispatch.ts +101 -37
  224. package/src/sdk/host/control/index.ts +2 -0
  225. package/src/sdk/host/control/operations.ts +2 -0
  226. package/src/sdk/host/control/runtime-gate.ts +203 -11
  227. package/src/sdk/host/host.ts +27 -2
  228. package/src/sdk/host/model-pin.ts +185 -25
  229. package/src/sdk/host/sdk-run-capability.ts +1 -0
  230. package/src/sdk/host/session-runtime.test.ts +581 -43
  231. package/src/sdk/host/session-runtime.ts +655 -101
  232. package/src/sdk/lifecycle/broker-client.ts +28 -0
  233. package/src/sdk/lifecycle/service.ts +279 -20
  234. package/src/sdk/lifecycle-session.ts +10 -2
  235. package/src/sdk/prompt-deadline-manager.ts +16 -1
  236. package/src/sdk/protocol/adapter-validation.ts +27 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +53 -2
  238. package/src/sdk/protocol/operation-registry.ts +17 -4
  239. package/src/sdk/router/session-router.ts +294 -154
  240. package/src/sdk/session.ts +440 -167
  241. package/src/sdk/startup-capability.ts +1 -0
  242. package/src/session/account-inventory.ts +27 -11
  243. package/src/session/agent-session.ts +1292 -180
  244. package/src/session/blob-store.ts +15 -5
  245. package/src/session/credential-store-errors.ts +2 -0
  246. package/src/session/fold-coordinator.ts +433 -0
  247. package/src/session/internal/managed-session-storage.ts +153 -78
  248. package/src/session/sdk-run-capability-internal.ts +15 -0
  249. package/src/session/sdk-run-capability.ts +2 -0
  250. package/src/session/session-manager.ts +342 -28
  251. package/src/session/yield-queue.ts +24 -10
  252. package/src/setup/frictionless-onboarding.ts +2 -0
  253. package/src/setup/provider-presets.json +1 -1
  254. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  255. package/src/task/executor.ts +19 -3
  256. package/src/telemetry/control.ts +26 -0
  257. package/src/telemetry/events.ts +1268 -0
  258. package/src/telemetry/index.ts +3 -0
  259. package/src/telemetry/transport.ts +87 -0
  260. package/src/tools/bash-interactive.ts +182 -85
  261. package/src/tools/bash.ts +1028 -109
  262. package/src/tools/gh.ts +104 -48
  263. package/src/tools/github-cache.ts +2 -2
  264. package/src/tools/index.ts +11 -5
  265. package/src/tools/tool-catalog.generated.ts +1 -1
  266. package/src/web/search/provider.ts +120 -11
  267. package/src/web/search/providers/anthropic.ts +89 -17
  268. package/src/web/search/providers/base.ts +9 -4
  269. package/src/web/search/providers/gemini.ts +87 -15
  270. package/src/web/search/providers/openai-compatible.ts +23 -8
  271. package/src/web/search/providers/xai.ts +98 -12
  272. package/src/web/search/types.ts +16 -0
@@ -20,7 +20,10 @@ export const SEARXNG_BEARER_CREDENTIAL_PREFIX = "gjc-searxng-bearer:";
20
20
  /**
21
21
  * Shared web search parameters passed to providers.
22
22
  *
23
- * `authStorage` is the **only** credential source providers may consult.
23
+ * `authStorage` is the credential source for canonical providers. Native
24
+ * active-model search may instead use `activeModelContext.resolveCredentials`,
25
+ * which is supplied by ModelRegistry so rotating config credentials and
26
+ * generated headers are refreshed before the request.
24
27
  * Opening a sibling SQLite handle or calling provider-direct refresh helpers
25
28
  * (e.g. `refreshOpenAIOpenAI code backendToken`, `refreshGoogleCloudToken`) is prohibited:
26
29
  * it races the broker's per-credential refresh and POSTs the broker sentinel
@@ -75,8 +78,8 @@ export interface SearchParams {
75
78
  codeExecution?: Record<string, unknown>;
76
79
  urlContext?: Record<string, unknown>;
77
80
  /**
78
- * The single source of truth for credentials. Providers MUST consult this
79
- * handle exclusively (`getApiKey` for bearer-style auth, `getOAuthAccess`
81
+ * The canonical source of truth for credentials. Providers MUST consult this
82
+ * handle (`getApiKey` for bearer-style auth, `getOAuthAccess`
80
83
  * when identity metadata is required). Do not open `AgentStorage` or any
81
84
  * `AuthCredentialStore` directly — that bypasses the broker pipeline and
82
85
  * the per-credential single-flight refresh.
@@ -108,7 +111,9 @@ export abstract class SearchProvider {
108
111
  abstract isAvailable(authStorage: AuthStorage, settings?: SearchProviderSettings): Promise<boolean> | boolean;
109
112
 
110
113
  /**
111
- * Execute a search. Credentials MUST be resolved through `params.authStorage`.
114
+ * Execute a search. Canonical credentials MUST be resolved through
115
+ * `params.authStorage`; active-model credentials MUST use the injected
116
+ * resolver when one is present.
112
117
  */
113
118
  abstract search(params: SearchParams): Promise<SearchResponse>;
114
119
  }
@@ -16,7 +16,13 @@ import {
16
16
  } from "@gajae-code/ai/providers/google-gemini-headers";
17
17
  import { fetchWithRetry } from "@gajae-code/utils";
18
18
 
19
- import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
19
+ import type {
20
+ ActiveSearchModelContext,
21
+ ActiveSearchModelCredentials,
22
+ SearchCitation,
23
+ SearchResponse,
24
+ SearchSource,
25
+ } from "../../../web/search/types";
20
26
  import { SearchProviderError } from "../../../web/search/types";
21
27
  import type { SearchParams } from "./base";
22
28
  import { SearchProvider } from "./base";
@@ -38,6 +44,49 @@ const RATE_LIMIT_BUDGET_MS = 30 * 1000;
38
44
  const GEMINI_PROVIDERS = ["google-gemini-cli", "google-antigravity"] as const;
39
45
  type GeminiProviderId = (typeof GEMINI_PROVIDERS)[number];
40
46
 
47
+ function isGenerativeLanguageWire(api: string): boolean {
48
+ return api === "google-generative-ai" || api === "google-generative-language" || api === "gemini-wire";
49
+ }
50
+
51
+ function isGoogleWire(api: string): boolean {
52
+ return isGenerativeLanguageWire(api) || api === "google-gemini-cli" || api === "google-vertex";
53
+ }
54
+
55
+ const OFFICIAL_GEMINI_HOSTS = new Set([
56
+ "cloudcode-pa.googleapis.com",
57
+ "daily-cloudcode-pa.googleapis.com",
58
+ "daily-cloudcode-pa.sandbox.googleapis.com",
59
+ "generativelanguage.googleapis.com",
60
+ ]);
61
+ const CANONICAL_GEMINI_PROVIDERS = new Set(["google", "gemini", "google-gemini-cli", "google-antigravity"]);
62
+
63
+ function isOfficialGeminiBaseUrl(baseUrl: string | undefined): boolean {
64
+ if (!baseUrl?.trim()) return true;
65
+ try {
66
+ const host = new URL(baseUrl).hostname.toLowerCase().replace(/^\[/, "").replace(/\]$/, "").replace(/\.$/, "");
67
+ return OFFICIAL_GEMINI_HOSTS.has(host);
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ function isCustomGeminiContext(ctx: ActiveSearchModelContext | undefined): boolean {
74
+ if (!ctx || !isGoogleWire(ctx.api)) return false;
75
+ if (ctx.ownerAuthOverride) return true;
76
+ if (!isOfficialGeminiBaseUrl(ctx.baseUrl)) return true;
77
+ const provider = ctx.provider.toLowerCase();
78
+ return Boolean(ctx.resolveCredentials && !CANONICAL_GEMINI_PROVIDERS.has(provider));
79
+ }
80
+
81
+ function isCustomGenerativeLanguageContext(ctx: ActiveSearchModelContext | undefined): boolean {
82
+ return isGenerativeLanguageWire(ctx?.api ?? "") && isCustomGeminiContext(ctx);
83
+ }
84
+
85
+ function hasHeader(headers: Record<string, string>, name: string): boolean {
86
+ const normalized = name.toLowerCase();
87
+ return Object.keys(headers).some(key => key.toLowerCase() === normalized);
88
+ }
89
+
41
90
  interface GeminiToolParams {
42
91
  google_search?: Record<string, unknown>;
43
92
  code_execution?: Record<string, unknown>;
@@ -433,19 +482,28 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
433
482
  * Native Gemini web search over the public Generative Language REST API
434
483
  * (`{baseUrl}/v1beta/models/{model}:generateContent`), reusing the ACTIVE
435
484
  * model's own API key + baseUrl. This is the "native search over a proxy" path
436
- * for `google-generative-ai` wire models whose canonical gemini-cli/antigravity
437
- * OAuth is absent. Distinct from {@link searchGemini}, which speaks the Cloud
438
- * Code Assist API with OAuth.
485
+ * for Generative Language wire models. Distinct from {@link searchGemini},
486
+ * which speaks the Cloud Code Assist API with OAuth.
439
487
  */
440
488
  async function searchGeminiViaGenerativeLanguage(params: SearchParams): Promise<SearchResponse> {
441
489
  const ctx = params.activeModelContext;
442
490
  if (!ctx) throw new SearchProviderError("gemini", "Gemini web search requires active model context", 400);
443
- const apiKey = await params.authStorage.getApiKey(ctx.provider, params.sessionId, {
444
- baseUrl: ctx.baseUrl,
445
- modelId: ctx.modelId,
446
- signal: params.signal,
447
- });
448
- if (!apiKey) throw new SearchProviderError("gemini", `No credentials for ${ctx.provider}`, 401);
491
+ const activeCredentials: ActiveSearchModelCredentials = ctx.resolveCredentials
492
+ ? await ctx.resolveCredentials({ sessionId: params.sessionId, signal: params.signal })
493
+ : {
494
+ apiKey: await params.authStorage.getApiKey(ctx.provider, params.sessionId, {
495
+ baseUrl: ctx.baseUrl,
496
+ modelId: ctx.modelId,
497
+ signal: params.signal,
498
+ }),
499
+ headers: ctx.headers,
500
+ };
501
+ const apiKey = activeCredentials.apiKey;
502
+ const headers = { ...(activeCredentials.headers ?? ctx.headers ?? {}) };
503
+ if (apiKey && !hasHeader(headers, "x-goog-api-key")) headers["x-goog-api-key"] = apiKey;
504
+ if (!apiKey && !hasHeader(headers, "x-goog-api-key")) {
505
+ throw new SearchProviderError("gemini", `No credentials for ${ctx.provider}`, 401);
506
+ }
449
507
 
450
508
  const model = ctx.wireModelId ?? ctx.modelId;
451
509
  const base = (ctx.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
@@ -468,7 +526,10 @@ async function searchGeminiViaGenerativeLanguage(params: SearchParams): Promise<
468
526
 
469
527
  const response = await fetch(url, {
470
528
  method: "POST",
471
- headers: { ...(ctx.headers ?? {}), "x-goog-api-key": apiKey, "Content-Type": "application/json" },
529
+ headers: {
530
+ ...headers,
531
+ ...(hasHeader(headers, "content-type") ? {} : { "Content-Type": "application/json" }),
532
+ },
472
533
  body: JSON.stringify(body),
473
534
  signal: withHardTimeout(params.signal, "llm"),
474
535
  });
@@ -534,11 +595,22 @@ export class GeminiProvider extends SearchProvider {
534
595
  }
535
596
 
536
597
  search(params: SearchParams): Promise<SearchResponse> {
537
- // Native-over-proxy: when canonical gemini-cli/antigravity OAuth is
538
- // absent but the active model speaks the Generative Language wire, reuse
539
- // its own API key + baseUrl instead of failing closed.
540
598
  const ctx = params.activeModelContext;
541
- if (!hasGeminiOAuth(params.authStorage) && ctx?.api === "google-generative-ai") {
599
+ // Native-over-proxy: a custom model's owner-bound resolver must win over
600
+ // canonical Gemini OAuth. Otherwise the request would silently leave the
601
+ // proxy and hit Cloud Code with another registry's identity.
602
+ if (isCustomGeminiContext(ctx)) {
603
+ if (isCustomGenerativeLanguageContext(ctx)) return searchGeminiViaGenerativeLanguage(params);
604
+ return Promise.reject(
605
+ new SearchProviderError(
606
+ "gemini",
607
+ `Gemini native search does not support custom ${ctx?.api ?? ""} transports`,
608
+ 400,
609
+ ),
610
+ );
611
+ }
612
+ // Preserve the existing active-key fallback when canonical OAuth is absent.
613
+ if (!hasGeminiOAuth(params.authStorage) && ctx && isGenerativeLanguageWire(ctx.api)) {
542
614
  return searchGeminiViaGenerativeLanguage(params);
543
615
  }
544
616
  return searchGemini({
@@ -1,4 +1,4 @@
1
- import type { SearchCitation, SearchResponse, SearchSource } from "../types";
1
+ import type { ActiveSearchModelCredentials, SearchCitation, SearchResponse, SearchSource } from "../types";
2
2
  import { SearchProviderError } from "../types";
3
3
  import type { SearchParams } from "./base";
4
4
  import { SearchProvider } from "./base";
@@ -7,6 +7,11 @@ import { classifyProviderHttpError, withHardTimeout } from "./utils";
7
7
 
8
8
  type JsonObject = Record<string, unknown>;
9
9
 
10
+ function hasHeader(headers: Record<string, string>, name: string): boolean {
11
+ const normalized = name.toLowerCase();
12
+ return Object.keys(headers).some(key => key.toLowerCase() === normalized);
13
+ }
14
+
10
15
  function isJsonObject(value: unknown): value is JsonObject {
11
16
  return typeof value === "object" && value !== null && !Array.isArray(value);
12
17
  }
@@ -154,15 +159,25 @@ export class OpenAICompatibleSearchProvider extends SearchProvider {
154
159
  if (ctx.api !== "openai-responses" && ctx.api !== "openai-completions") {
155
160
  throw new SearchProviderError(this.id, `OpenAI-compatible web search does not support ${ctx.api}`, 400);
156
161
  }
157
- const apiKey = await params.authStorage.getApiKey(ctx.provider, params.sessionId, {
158
- baseUrl: ctx.baseUrl,
159
- modelId: ctx.modelId,
160
- signal: params.signal,
161
- });
162
- if (!apiKey) throw new SearchProviderError(this.id, `No credentials for ${ctx.provider}`, 401);
162
+ const activeCredentials: ActiveSearchModelCredentials = ctx.resolveCredentials
163
+ ? await ctx.resolveCredentials({ sessionId: params.sessionId, signal: params.signal })
164
+ : {
165
+ apiKey: await params.authStorage.getApiKey(ctx.provider, params.sessionId, {
166
+ baseUrl: ctx.baseUrl,
167
+ modelId: ctx.modelId,
168
+ signal: params.signal,
169
+ }),
170
+ headers: ctx.headers,
171
+ };
172
+ const apiKey = activeCredentials.apiKey;
173
+ const headers = { ...(activeCredentials.headers ?? ctx.headers ?? {}) };
174
+ if (apiKey && !hasHeader(headers, "authorization")) headers.Authorization = `Bearer ${apiKey}`;
175
+ if (!hasHeader(headers, "authorization")) {
176
+ throw new SearchProviderError(this.id, `No credentials for ${ctx.provider}`, 401);
177
+ }
178
+ if (!hasHeader(headers, "content-type")) headers["Content-Type"] = "application/json";
163
179
  const model = ctx.wireModelId ?? ctx.modelId;
164
180
  const baseUrl = ctx.baseUrl ?? "";
165
- const headers = { ...(ctx.headers ?? {}), Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" };
166
181
  const messages = [
167
182
  { role: "system", content: params.systemPrompt },
168
183
  { role: "user", content: params.query },
@@ -6,7 +6,14 @@
6
6
  */
7
7
  import type { AuthStorage } from "@gajae-code/ai/core";
8
8
  import { $credentialEnv, $env } from "@gajae-code/utils";
9
- import type { SearchCitation, SearchResponse, SearchSource, SearchUsage } from "../../../web/search/types";
9
+ import type {
10
+ ActiveSearchModelContext,
11
+ ActiveSearchModelCredentials,
12
+ SearchCitation,
13
+ SearchResponse,
14
+ SearchSource,
15
+ SearchUsage,
16
+ } from "../../../web/search/types";
10
17
  import { SearchProviderError } from "../../../web/search/types";
11
18
  import type { SearchParams } from "./base";
12
19
  import { SearchProvider } from "./base";
@@ -49,11 +56,13 @@ export interface XaiSearchParams {
49
56
  signal?: AbortSignal;
50
57
  authStorage: AuthStorage;
51
58
  sessionId?: string;
59
+ activeModelContext?: ActiveSearchModelContext;
52
60
  }
53
61
 
54
62
  interface XaiAuth {
55
63
  bearer: string;
56
64
  mode: "api_key" | "oauth";
65
+ headers?: Record<string, string>;
57
66
  }
58
67
 
59
68
  interface PreparedXaiTools {
@@ -67,7 +76,53 @@ function asTrimmed(value: string | undefined): string | undefined {
67
76
  return trimmed.length > 0 ? trimmed : undefined;
68
77
  }
69
78
 
70
- function getModel(): string {
79
+ function isOpenAICompatWire(api: string): boolean {
80
+ return api === "openai-responses" || api === "openai-completions";
81
+ }
82
+
83
+ function looksXaiModelId(modelId: string | undefined): boolean {
84
+ if (!modelId) return false;
85
+ const id = modelId.toLowerCase();
86
+ return id.startsWith("grok-") || id.startsWith("x-ai/grok-") || id.startsWith("xai/grok-");
87
+ }
88
+
89
+ function isOfficialXaiBaseUrl(baseUrl: string | undefined): boolean {
90
+ if (!baseUrl?.trim()) return true;
91
+ try {
92
+ return new URL(baseUrl).hostname.toLowerCase().replace(/\.$/, "") === "api.x.ai";
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ function isCustomXaiContext(ctx: ActiveSearchModelContext | undefined): ctx is ActiveSearchModelContext {
99
+ if (!ctx || !isOpenAICompatWire(ctx.api)) return false;
100
+ if (ctx.ownerAuthOverride) return true;
101
+ const provider = ctx.provider.toLowerCase();
102
+ if (provider === "xai" && !isOfficialXaiBaseUrl(ctx.baseUrl)) return true;
103
+ if (!looksXaiModelId(ctx.wireModelId) && !looksXaiModelId(ctx.modelId)) return false;
104
+ if (!isOfficialXaiBaseUrl(ctx.baseUrl)) return true;
105
+ return provider !== "xai";
106
+ }
107
+
108
+ function hasHeader(headers: Record<string, string>, name: string): boolean {
109
+ const normalized = name.toLowerCase();
110
+ return Object.keys(headers).some(key => key.toLowerCase() === normalized);
111
+ }
112
+
113
+ function explicitCredentialHeader(headers: Record<string, string> | undefined): string | undefined {
114
+ for (const [key, value] of Object.entries(headers ?? {})) {
115
+ const normalized = key.toLowerCase();
116
+ if (normalized === "authorization" || normalized === "x-api-key") return value;
117
+ }
118
+ return undefined;
119
+ }
120
+
121
+ function getModel(activeModelContext?: ActiveSearchModelContext): string {
122
+ const ctx = activeModelContext;
123
+ if (isCustomXaiContext(ctx)) {
124
+ return ctx.wireModelId ?? ctx.modelId;
125
+ }
71
126
  return asTrimmed($env.PI_XAI_WEB_SEARCH_MODEL) ?? asTrimmed($env.XAI_WEB_SEARCH_MODEL) ?? DEFAULT_MODEL;
72
127
  }
73
128
 
@@ -76,12 +131,14 @@ export function resolveXaiSearchBaseUrlForTest(): string {
76
131
  return getBaseUrl();
77
132
  }
78
133
 
79
- function getBaseUrl(): string {
134
+ function getBaseUrl(activeModelContext?: ActiveSearchModelContext): string {
135
+ const ctx = activeModelContext;
136
+ if (isCustomXaiContext(ctx) && ctx.baseUrl) return ctx.baseUrl;
80
137
  return asTrimmed($credentialEnv("XAI_SEARCH_BASE_URL")) ?? DEFAULT_BASE_URL;
81
138
  }
82
139
 
83
- function responsesEndpoint(): string {
84
- return `${getBaseUrl().replace(/\/+$/, "")}/responses`;
140
+ function responsesEndpoint(activeModelContext?: ActiveSearchModelContext): string {
141
+ return `${getBaseUrl(activeModelContext).replace(/\/+$/, "")}/responses`;
85
142
  }
86
143
 
87
144
  async function resolveXaiAuth(
@@ -89,7 +146,26 @@ async function resolveXaiAuth(
89
146
  sessionId: string | undefined,
90
147
  model: string,
91
148
  signal: AbortSignal | undefined,
149
+ activeModelContext?: ActiveSearchModelContext,
92
150
  ): Promise<XaiAuth | null> {
151
+ const ctx = activeModelContext;
152
+ if (isCustomXaiContext(ctx)) {
153
+ const activeCredentials: ActiveSearchModelCredentials = ctx.resolveCredentials
154
+ ? await ctx.resolveCredentials({ sessionId, signal })
155
+ : {
156
+ apiKey: await authStorage.getApiKey(ctx.provider, sessionId, {
157
+ baseUrl: ctx.baseUrl,
158
+ modelId: ctx.modelId,
159
+ signal,
160
+ }),
161
+ headers: ctx.headers,
162
+ };
163
+ const headers = { ...(activeCredentials.headers ?? ctx.headers ?? {}) };
164
+ const bearer = activeCredentials.apiKey ?? explicitCredentialHeader(headers);
165
+ if (!bearer) return null;
166
+ return { bearer, mode: "api_key", headers };
167
+ }
168
+
93
169
  const credentialSessionId = sessionId ?? `xai-search:${crypto.randomUUID()}`;
94
170
  const bearer = await authStorage.getApiKey("xai", credentialSessionId, {
95
171
  baseUrl: getBaseUrl(),
@@ -409,8 +485,14 @@ function parseUsage(json: any): SearchUsage | undefined {
409
485
 
410
486
  /** Execute xAI web/X search through the Responses API search tools. */
411
487
  export async function searchXai(params: XaiSearchParams): Promise<SearchResponse> {
412
- const model = getModel();
413
- const auth = await resolveXaiAuth(params.authStorage, params.sessionId, model, params.signal);
488
+ const model = getModel(params.activeModelContext);
489
+ const auth = await resolveXaiAuth(
490
+ params.authStorage,
491
+ params.sessionId,
492
+ model,
493
+ params.signal,
494
+ params.activeModelContext,
495
+ );
414
496
  if (!auth) {
415
497
  throw new SearchProviderError(
416
498
  "xai",
@@ -419,12 +501,15 @@ export async function searchXai(params: XaiSearchParams): Promise<SearchResponse
419
501
  );
420
502
  }
421
503
 
422
- const response = await fetch(responsesEndpoint(), {
504
+ const headers = { ...(auth.headers ?? {}) };
505
+ if (!hasHeader(headers, "authorization") && !hasHeader(headers, "x-api-key")) {
506
+ headers.Authorization = `Bearer ${auth.bearer}`;
507
+ }
508
+ if (!hasHeader(headers, "content-type")) headers["Content-Type"] = "application/json";
509
+
510
+ const response = await fetch(responsesEndpoint(params.activeModelContext), {
423
511
  method: "POST",
424
- headers: {
425
- Authorization: `Bearer ${auth.bearer}`,
426
- "Content-Type": "application/json",
427
- },
512
+ headers,
428
513
  body: JSON.stringify(
429
514
  buildXaiRequestBody({
430
515
  query: params.query,
@@ -511,6 +596,7 @@ export class XaiProvider extends SearchProvider {
511
596
  signal: params.signal,
512
597
  authStorage: params.authStorage,
513
598
  sessionId: params.sessionId,
599
+ activeModelContext: params.activeModelContext,
514
600
  });
515
601
  }
516
602
  }
@@ -27,6 +27,18 @@ export type SearchProviderId =
27
27
 
28
28
  export type WebSearchMode = "on" | "off" | "auto";
29
29
 
30
+ /** Credentials and request headers resolved for the active model. */
31
+ export interface ActiveSearchModelCredentials {
32
+ apiKey?: string;
33
+ headers?: Record<string, string>;
34
+ }
35
+
36
+ /** Refresh-aware resolver supplied by the model registry for native search. */
37
+ export type ActiveSearchModelCredentialResolver = (options: {
38
+ sessionId?: string;
39
+ signal?: AbortSignal;
40
+ }) => Promise<ActiveSearchModelCredentials>;
41
+
30
42
  export interface ActiveSearchModelContext {
31
43
  provider: string;
32
44
  modelId: string;
@@ -34,6 +46,10 @@ export interface ActiveSearchModelContext {
34
46
  api: string;
35
47
  baseUrl?: string;
36
48
  headers?: Record<string, string>;
49
+ /** Resolve the active model key and current headers before each native search. */
50
+ resolveCredentials?: ActiveSearchModelCredentialResolver;
51
+ /** True when registry-owned auth or transport overrides this active model. */
52
+ ownerAuthOverride?: boolean;
37
53
  webSearch?: WebSearchMode;
38
54
  }
39
55