@gajae-code/coding-agent 0.12.21 → 0.13.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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -24,6 +24,8 @@ import {
24
24
  import * as z from "zod/v4";
25
25
  import packageJson from "../../package.json" with { type: "json" };
26
26
  import { isAuthenticated, type ModelRegistry } from "../config/model-registry";
27
+ import { resolveModelRoleValue } from "../config/model-resolver";
28
+ import type { Settings } from "../config/settings";
27
29
  import type { CustomTool } from "../extensibility/custom-tools/types";
28
30
  import imageGenDescription from "../prompts/tools/image-gen.md" with { type: "text" };
29
31
  import { isPrivateOrSpecialAddress, validatePublicHttpUrl } from "../web/insane/url-guard";
@@ -46,7 +48,7 @@ const ALIBABA_IMAGE_GENERATION_URL = `${ALIBABA_TOKEN_PLAN_HOST}/api/v1/services
46
48
  const IMAGE_SYSTEM_INSTRUCTION =
47
49
  "You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request.";
48
50
 
49
- type ImageProvider = "alibaba" | "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter";
51
+ type ImageProvider = "alibaba" | "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | (string & {});
50
52
  interface ImageApiKey {
51
53
  provider: ImageProvider;
52
54
  apiKey: string;
@@ -651,15 +653,7 @@ function extractOpenRouterImageUrls(message: OpenRouterMessage | undefined): str
651
653
  return urls;
652
654
  }
653
655
 
654
- /** Preferred provider set via settings (default: auto) */
655
- let preferredImageProvider: ImageProvider | "auto" = "auto";
656
-
657
- /** Set the preferred image provider from settings */
658
- export function setPreferredImageProvider(provider: ImageProvider | "auto"): void {
659
- preferredImageProvider = provider;
660
- }
661
-
662
- /** Provider → default image model mapping for auto-binding */
656
+ /** Provider → default image model mapping for fallbacks */
663
657
  export const IMAGE_PROVIDER_DEFAULTS: Record<string, string> = {
664
658
  openai: "gpt-image-2",
665
659
  alibaba: "wan2.7-image",
@@ -669,32 +663,26 @@ export const IMAGE_PROVIDER_DEFAULTS: Record<string, string> = {
669
663
  openrouter: "google/gemini-3-pro-image-preview",
670
664
  };
671
665
 
672
- /** Resolved image generation configuration from settings */
673
- export interface ImageProviderConfig {
674
- provider: ImageProvider | "auto" | "custom";
675
- model: string | null;
676
- customUrl?: string;
677
- customKey?: string;
678
- customKeyEnv?: string;
679
- }
680
-
681
- /** Module-level configured image model state (set from settings at session init) */
682
- let configuredImageConfig: ImageProviderConfig | null = null;
683
-
684
- /** Set the configured image provider + model from settings */
685
- export function setConfiguredImageModel(config: ImageProviderConfig | null): void {
686
- configuredImageConfig = config;
687
- // Keep preferredImageProvider in sync for backward compat
688
- if (config && config.provider !== "auto" && config.provider !== "custom") {
689
- preferredImageProvider = config.provider;
690
- } else if (!config || config.provider === "auto") {
691
- preferredImageProvider = "auto";
692
- }
693
- }
694
-
695
- /** Get the current configured image model (for UI display) */
696
- export function getConfiguredImageModel(): ImageProviderConfig | null {
697
- return configuredImageConfig;
666
+ /**
667
+ * Resolve the image-generation model from the `modelRoles.image` settings entry.
668
+ * Returns the resolved Model (with provider identity) or undefined when no
669
+ * image role is configured / no matching model is available.
670
+ */
671
+ export function resolveImageRoleModel(
672
+ settings: Settings,
673
+ modelRegistry: ModelRegistry,
674
+ options?: { sessionId?: string; credentialSessionId?: string },
675
+ ): Model | undefined {
676
+ const roleValue = settings.getModelRole("image");
677
+ if (!roleValue) return undefined;
678
+ const availableModels = modelRegistry.getAvailable();
679
+ const resolved = resolveModelRoleValue(roleValue, availableModels, {
680
+ settings,
681
+ modelRegistry,
682
+ sessionId: options?.sessionId,
683
+ credentialSessionId: options?.credentialSessionId,
684
+ });
685
+ return resolved.model;
698
686
  }
699
687
 
700
688
  /** Resolve the effective image model for a configured provider */
@@ -724,22 +712,6 @@ function parseAntigravityCredentials(raw: string): ParsedAntigravityCredentials
724
712
  return rawToken.length > 0 ? { accessToken: rawToken } : null;
725
713
  }
726
714
 
727
- function createCustomImageModel(baseUrl: string, id: string): Model<"openai-responses"> {
728
- return {
729
- id,
730
- name: id,
731
- api: "openai-responses",
732
- provider: "openai",
733
- baseUrl: baseUrl.replace(/\/+$/, ""),
734
- reasoning: false,
735
- input: ["text"],
736
- output: ["text", "image"],
737
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
738
- contextWindow: 128_000,
739
- maxTokens: 16_384,
740
- };
741
- }
742
-
743
715
  async function findAntigravityCredentials(
744
716
  modelRegistry: ModelRegistry,
745
717
  sessionId?: string,
@@ -766,22 +738,6 @@ async function findAntigravityCredentials(
766
738
  };
767
739
  }
768
740
 
769
- async function findOpenAIHostedImageCredentials(
770
- modelRegistry: ModelRegistry | undefined,
771
- activeModel: Model | undefined,
772
- sessionId?: string,
773
- ): Promise<ImageApiKey | null> {
774
- if (!modelRegistry || !isOpenAIHostedImageModel(activeModel)) return null;
775
- const apiKey = await modelRegistry.getApiKey(activeModel, sessionId);
776
- if (!isAuthenticated(apiKey)) return null;
777
- return {
778
- provider: getOpenAIHostedImageProvider(activeModel),
779
- apiKey,
780
- model: activeModel,
781
- authCredentialType: modelRegistry.getSessionCredentialType?.(activeModel.provider, sessionId),
782
- };
783
- }
784
-
785
741
  async function findAlibabaImageCredentials(
786
742
  modelRegistry: ModelRegistry | undefined,
787
743
  sessionId?: string,
@@ -811,93 +767,77 @@ export function googleImageApiKeyFromEnvForTest(): string | undefined {
811
767
  return googleImageApiKeyFromEnv();
812
768
  }
813
769
 
770
+ /**
771
+ * Resolve image API credentials from the `modelRoles.image` model.
772
+ *
773
+ * The resolved model's provider identity determines which credential path
774
+ * is used. Returns null when no image role model is configured or the
775
+ * corresponding credentials are unavailable.
776
+ */
814
777
  async function findImageApiKey(
815
- modelRegistry?: ModelRegistry,
816
- activeModel?: Model,
778
+ modelRegistry: ModelRegistry,
779
+ settings: Settings,
817
780
  sessionId?: string,
781
+ credentialSessionId?: string,
818
782
  ): Promise<ImageApiKey | null> {
819
- // Config-driven routing: if an explicit provider+model is configured, use it.
820
- if (configuredImageConfig && configuredImageConfig.provider !== "auto") {
821
- const config = configuredImageConfig;
822
- if (config.provider === "custom") {
823
- const baseUrl = config.customUrl?.trim();
824
- const apiKey = config.customKey ?? (config.customKeyEnv ? Bun.env[config.customKeyEnv] : undefined);
825
- if (baseUrl && apiKey) {
826
- return {
827
- provider: "openai",
828
- apiKey,
829
- model: createCustomImageModel(baseUrl, config.model ?? IMAGE_PROVIDER_DEFAULTS.openai),
830
- };
831
- }
832
- return null;
833
- }
834
- // For configured providers, resolve credentials through the existing paths.
835
- if (config.provider === "openai" || config.provider === "openai-codex") {
836
- const openAI = await findOpenAIHostedImageCredentials(modelRegistry, activeModel, sessionId);
837
- if (openAI) return openAI;
838
- return null;
839
- }
840
- if (config.provider === "antigravity") {
841
- if (!modelRegistry) return null;
842
- return await findAntigravityCredentials(modelRegistry, sessionId);
843
- }
844
- if (config.provider === "gemini") {
845
- const geminiKey = getEnvApiKey("google");
846
- if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
847
- const googleKey = googleImageApiKeyFromEnv();
848
- return googleKey ? { provider: "gemini", apiKey: googleKey } : null;
849
- }
850
- if (config.provider === "openrouter") {
851
- const openRouterKey = getEnvApiKey("openrouter");
852
- return openRouterKey ? { provider: "openrouter", apiKey: openRouterKey } : null;
853
- }
854
- if (config.provider === "alibaba") {
855
- return await findAlibabaImageCredentials(modelRegistry, sessionId);
856
- }
783
+ const imageModel = resolveImageRoleModel(settings, modelRegistry, { sessionId, credentialSessionId });
784
+ if (!imageModel) return null;
785
+ return resolveCredentialsForImageModel(imageModel, modelRegistry, sessionId);
786
+ }
787
+
788
+ /**
789
+ * Map a resolved image model to its provider credentials. Each provider
790
+ * branch mirrors the original credential-resolution path.
791
+ */
792
+ async function resolveCredentialsForImageModel(
793
+ model: Model,
794
+ modelRegistry: ModelRegistry,
795
+ sessionId?: string,
796
+ ): Promise<ImageApiKey | null> {
797
+ const provider: string = model.provider;
798
+
799
+ // OpenAI-hosted image generation (gpt-image via Responses API, including
800
+ // OpenAI-compatible proxies whose models declare output:image).
801
+ if (isOpenAIHostedImageModel(model)) {
802
+ const apiKey = await modelRegistry.getApiKey(model, sessionId);
803
+ if (!isAuthenticated(apiKey)) return null;
804
+ return {
805
+ provider: getOpenAIHostedImageProvider(model),
806
+ apiKey,
807
+ model,
808
+ authCredentialType: modelRegistry.getSessionCredentialType?.(model.provider, sessionId),
809
+ };
857
810
  }
858
811
 
859
- // If a specific provider is preferred (legacy path), try it first.
860
- if (preferredImageProvider === "openai") {
861
- const openAI = await findOpenAIHostedImageCredentials(modelRegistry, activeModel, sessionId);
862
- if (openAI) return openAI;
863
- // Fall through to auto-detect if preferred provider key not found.
864
- } else if (preferredImageProvider === "antigravity") {
865
- if (!modelRegistry) return null;
812
+ if (provider === "antigravity" || provider === "google-antigravity") {
866
813
  return await findAntigravityCredentials(modelRegistry, sessionId);
867
- } else if (preferredImageProvider === "gemini") {
868
- const geminiKey = getEnvApiKey("google");
869
- if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
870
- const googleKey = googleImageApiKeyFromEnv();
871
- return googleKey ? { provider: "gemini", apiKey: googleKey } : null;
872
- } else if (preferredImageProvider === "openrouter") {
873
- const openRouterKey = getEnvApiKey("openrouter");
874
- return openRouterKey ? { provider: "openrouter", apiKey: openRouterKey } : null;
875
- } else if (preferredImageProvider === "alibaba") {
814
+ }
815
+ if (provider === "alibaba" || provider === "alibaba-token-plan") {
876
816
  return await findAlibabaImageCredentials(modelRegistry, sessionId);
877
817
  }
878
-
879
- // Auto-detect: GPT hosted image generation, then Antigravity, OpenRouter, Gemini, Alibaba.
880
- const openAI = await findOpenAIHostedImageCredentials(modelRegistry, activeModel, sessionId);
881
- if (openAI) return openAI;
882
-
883
- if (modelRegistry) {
884
- const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
885
- if (antigravity) return antigravity;
818
+ if (provider === "openrouter") {
819
+ const openRouterKey = await modelRegistry.getApiKey(model, sessionId);
820
+ if (isAuthenticated(openRouterKey)) return { provider: "openrouter", apiKey: openRouterKey };
821
+ const envKey = getEnvApiKey("openrouter");
822
+ return envKey ? { provider: "openrouter", apiKey: envKey } : null;
823
+ }
824
+ if (provider === "gemini" || provider === "google") {
825
+ const googleKey = await modelRegistry.getApiKey(model, sessionId);
826
+ if (isAuthenticated(googleKey)) return { provider: "gemini", apiKey: googleKey };
827
+ const envKey = getEnvApiKey("google");
828
+ if (envKey) return { provider: "gemini", apiKey: envKey };
829
+ const fallbackKey = googleImageApiKeyFromEnv();
830
+ return fallbackKey ? { provider: "gemini", apiKey: fallbackKey } : null;
886
831
  }
887
832
 
888
- const openRouterKey = getEnvApiKey("openrouter");
889
- if (openRouterKey) return { provider: "openrouter", apiKey: openRouterKey };
890
-
891
- const geminiKey = getEnvApiKey("google");
892
- if (geminiKey) return { provider: "gemini", apiKey: geminiKey };
893
-
894
- const googleKey = googleImageApiKeyFromEnv();
895
- if (googleKey) return { provider: "gemini", apiKey: googleKey };
896
-
897
- const alibaba = await findAlibabaImageCredentials(modelRegistry, sessionId);
898
- if (alibaba) return alibaba;
899
-
900
- return null;
833
+ // Fallback: try the model's own API key via the registry.
834
+ const apiKey = await modelRegistry.getApiKey(model, sessionId);
835
+ if (!isAuthenticated(apiKey)) return null;
836
+ return {
837
+ provider: provider as ImageProvider,
838
+ apiKey,
839
+ model,
840
+ };
901
841
  }
902
842
 
903
843
  async function loadImageFromPath(imagePath: string, cwd: string): Promise<InlineImageData> {
@@ -1336,27 +1276,29 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
1336
1276
  parameters: imageGenSchema,
1337
1277
  async execute(_toolCallId, params, _onUpdate, ctx, signal) {
1338
1278
  return untilAborted(signal, async () => {
1339
- const sessionId = ctx.sessionManager.getSessionId();
1340
- const apiKey = await findImageApiKey(ctx.modelRegistry, ctx.model, sessionId);
1279
+ const sessionId = ctx.credentialSessionId ?? ctx.sessionManager.getSessionId();
1280
+ const settings = ctx.settings;
1281
+ if (!settings) {
1282
+ throw new Error("Image generation requires session settings to resolve the image model role.");
1283
+ }
1284
+ const apiKey = await findImageApiKey(ctx.modelRegistry, settings, sessionId, ctx.credentialSessionId);
1341
1285
  if (!apiKey) {
1342
1286
  throw new Error(
1343
- "No image API credentials found. Use a GPT Responses/Codex model with OpenAI credentials, login with google-antigravity, or set OPENROUTER_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, or ALIBABA_TOKEN_PLAN_API_KEY.",
1287
+ "No image model configured. Set an image-capable model via /model (image role) to enable image generation.",
1344
1288
  );
1345
1289
  }
1346
1290
 
1347
1291
  const provider = apiKey.provider;
1348
- const model =
1349
- configuredImageConfig && configuredImageConfig.provider !== "auto" && configuredImageConfig.model
1350
- ? configuredImageConfig.model
1351
- : provider === "openai" || provider === "openai-codex"
1352
- ? (apiKey.model?.id ?? resolveImageModel(provider, null))
1353
- : provider === "antigravity"
1354
- ? resolveImageModel("antigravity", null)
1355
- : provider === "alibaba"
1356
- ? resolveImageModel("alibaba", null)
1357
- : provider === "openrouter"
1358
- ? resolveImageModel("openrouter", null)
1359
- : resolveImageModel("gemini", null);
1292
+ const imageModel = apiKey.model;
1293
+ const model = imageModel
1294
+ ? imageModel.id
1295
+ : provider === "antigravity"
1296
+ ? resolveImageModel("antigravity", null)
1297
+ : provider === "alibaba"
1298
+ ? resolveImageModel("alibaba", null)
1299
+ : provider === "openrouter"
1300
+ ? resolveImageModel("openrouter", null)
1301
+ : resolveImageModel("gemini", null);
1360
1302
  const resolvedModel = provider === "openrouter" ? resolveOpenRouterModel(model) : model;
1361
1303
  const cwd = ctx.sessionManager.getCwd();
1362
1304
 
@@ -1743,18 +1685,20 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
1743
1685
 
1744
1686
  export async function getImageGenTools(
1745
1687
  modelRegistry?: ModelRegistry,
1746
- activeModel?: Model,
1688
+ settings?: Settings,
1747
1689
  ): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>> {
1748
- const apiKey = await findImageApiKey(modelRegistry, activeModel);
1749
- if (!apiKey) return [];
1690
+ // The tool is available when an image role model is configured and resolvable.
1691
+ if (!modelRegistry || !settings) return [];
1692
+ const imageModel = resolveImageRoleModel(settings, modelRegistry);
1693
+ if (!imageModel) return [];
1750
1694
  return [imageGenTool];
1751
1695
  }
1752
1696
 
1753
1697
  export async function getImageGenToolsWithRegistry(
1754
1698
  modelRegistry: ModelRegistry,
1755
- activeModel?: Model,
1699
+ settings: Settings,
1756
1700
  ): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>> {
1757
- const apiKey = await findImageApiKey(modelRegistry, activeModel);
1758
- if (!apiKey) return [];
1701
+ const imageModel = resolveImageRoleModel(settings, modelRegistry);
1702
+ if (!imageModel) return [];
1759
1703
  return [imageGenTool];
1760
1704
  }
@@ -219,6 +219,8 @@ export interface ToolSession {
219
219
  requestForegroundBashBackground?: () => boolean;
220
220
  /** Get session ID */
221
221
  getSessionId?: () => string | null;
222
+ /** Get credential-selection session identity. */
223
+ getCredentialSessionId?: () => string | null;
222
224
  /** Scope-held MCP facade for mcp:// resolution. */
223
225
  getMcpManager?: () => import("../runtime-mcp/manager").MCPManager | undefined;
224
226
  /** Whether local:// must use external managed scratch instead of artifacts/local. */
package/src/tools/read.ts CHANGED
@@ -2409,7 +2409,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
2409
2409
  if (countTextLines(code) > MAX_SUMMARY_LINES) return null;
2410
2410
 
2411
2411
  const summarizeCode = (await nativeRead()).summarizeCode;
2412
- return summarizeCode({
2412
+ return await summarizeCode({
2413
2413
  code,
2414
2414
  path: absolutePath,
2415
2415
  minBodyLines: this.session.settings.get("read.summarize.minBodyLines"),
@@ -101,12 +101,17 @@ export interface ResourceGcDeps {
101
101
  ) => Promise<void>;
102
102
  }
103
103
 
104
+ /** Request collection without synchronously stopping the main event loop. */
105
+ export function requestMemoryPressureGc(): void {
106
+ Bun.gc(false);
107
+ }
108
+
104
109
  const defaultDeps: ResourceGcDeps = {
105
110
  now: () => Date.now(),
106
111
  monotonicNow: () => performance.now(),
107
112
  rssBytes: () => process.memoryUsage().rss,
108
113
  memorySnapshot: () => sampleMemoryPressure(),
109
- runGc: () => Bun.gc(true),
114
+ runGc: requestMemoryPressureGc,
110
115
  logWarn: (msg, meta) => logger.warn(msg, meta),
111
116
  listTabs: () => listTabsForGc(),
112
117
  releaseTab: (name, policy) => releaseTabIfGcEligible(name, policy),
@@ -1073,7 +1073,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
1073
1073
  "browser": {
1074
1074
  "name": "browser",
1075
1075
  "label": "Browser",
1076
- "description": "Drives a real Chromium tab with full puppeteer access via JS execution.\n\n<instruction>\n- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL. Use this tool only when you need JS execution, authentication, or interactive actions.\n- Four actions:\n - `open` — acquire (or reuse) a named tab. `name` defaults to `\"main\"`. Optional `url`, `viewport`, and `dialogs: \"accept\" | \"dismiss\"` (auto-handles `alert`/`confirm`/`beforeunload`). The `app` field selects the browser kind (spawned binary, saved Chrome profile, or existing CDP endpoint); omitted means headless Chromium with stealth patches.\n - `close` — release a tab by `name`, or every tab with `all: true`. `kill: true` also terminates a spawned-app process tree.\n - `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns per-step results.\n - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The return value is JSON-stringified into the tool result; `display(value)` calls accumulate text/images. Use `run` only when an `act` verb does not cover what you need.\n- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.\n- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: \"chrome\"` profile mode instead. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.\n- Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.\n- Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`.\n- Full reference — helpers, browser kinds, CDP/security details, and more examples — read `gjc://tools/browser.md`.\n</instruction>\n\n<critical>\n- You MUST call `open` before `run` or `act`. Neither implicitly creates a tab.\n- You MUST observe before taking a screenshot to understand page state; screenshot only when visual appearance matters.\n- After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.\n- `code` runs with full Node access. Treat it as your code, not sandboxed code.\n</critical>\n\n<examples>\n# Open a tab and read structured page data\n`{\"action\":\"open\",\"name\":\"docs\",\"url\":\"https://example.com\"}`\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"observe\"}]}`\n\n# Click an observed element, then fill and submit a form\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"click\",\"id\":12},{\"verb\":\"fill\",\"selector\":\"input[name=email]\",\"value\":\"me@example.com\"},{\"verb\":\"click\",\"selector\":\"text/Continue\"}]}`\n\n# Use `run` only when `act` has no suitable verb\n`{\"action\":\"run\",\"name\":\"docs\",\"code\":\"const count = await page.locator('canvas').count(); return { count };\"}`\n</examples>\n\n<output>\n- Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.\n</output>",
1076
+ "description": "Drives a real Chromium tab with full puppeteer access via JS execution.\n\n<instruction>\n- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL. Use this tool only when you need JS execution, authentication, or interactive actions.\n- Four actions:\n - `open` — acquire (or reuse) a named tab. `name` defaults to `\"main\"`. Optional `url`, `viewport`, and `dialogs: \"accept\" | \"dismiss\"` (auto-handles `alert`/`confirm`/`beforeunload`). The `app` field selects the browser kind (spawned binary, saved Chrome profile, or existing CDP endpoint); omitted means headless Chromium with stealth patches.\n - `close` — release a tab by `name`, or every tab with `all: true`. `kill: true` also terminates a spawned-app process tree.\n - `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns per-step results.\n - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The return value is JSON-stringified into the tool result; `display(value)` calls accumulate text/images. Use `run` only when an `act` verb does not cover what you need.\n- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.\n- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: \"chrome\"` profile mode instead. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.\n- Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.\n- Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`.\n- Runtime diagnostics are opt-in: pass `diagnostics: true` to `open` to subscribe the tab to page `Runtime.exceptionThrown` and `console.error` events. The next successful `act`/`run` response then includes at most 20 `runtimeDiagnostics` entries plus `runtimeDiagnosticsDropped`, then drains them. Entries contain only kind, time, origin-only URL, line/column, and a built-in error class from a fixed allowlist — never path segments, query strings, messages, console arguments, values, or stacks. Output is byte-bounded and marks truncation explicitly.\n- Full reference — helpers, browser kinds, CDP/security details, and more examples — read `gjc://tools/browser.md`.\n</instruction>\n\n<critical>\n- You MUST call `open` before `run` or `act`. Neither implicitly creates a tab.\n- You MUST observe before taking a screenshot to understand page state; screenshot only when visual appearance matters.\n- After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.\n- `code` runs with full Node access. Treat it as your code, not sandboxed code.\n</critical>\n\n<examples>\n# Open a tab and read structured page data\n`{\"action\":\"open\",\"name\":\"docs\",\"url\":\"https://example.com\"}`\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"observe\"}]}`\n\n# Click an observed element, then fill and submit a form\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"click\",\"id\":12},{\"verb\":\"fill\",\"selector\":\"input[name=email]\",\"value\":\"me@example.com\"},{\"verb\":\"click\",\"selector\":\"text/Continue\"}]}`\n\n# Use `run` only when `act` has no suitable verb\n`{\"action\":\"run\",\"name\":\"docs\",\"code\":\"const count = await page.locator('canvas').count(); return { count };\"}`\n</examples>\n\n<output>\n- Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.\n</output>",
1077
1077
  "parameters": {
1078
1078
  "type": "object",
1079
1079
  "properties": {
@@ -1185,6 +1185,10 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
1185
1185
  ],
1186
1186
  "description": "auto-handle dialogs"
1187
1187
  },
1188
+ "diagnostics": {
1189
+ "type": "boolean",
1190
+ "description": "opt-in: capture bounded page runtime diagnostics (page exceptions and console.error metadata) in the next successful run/act response"
1191
+ },
1188
1192
  "code": {
1189
1193
  "type": "string",
1190
1194
  "description": "js body to run in tab"
@@ -316,3 +316,96 @@ export function parsePastedImagePaths(
316
316
  export function formatPastedImageReference(placeholder: string, imagePath: string): string {
317
317
  return `${placeholder} source=${JSON.stringify(imagePath)}`;
318
318
  }
319
+ export interface PastedImageReferenceRange {
320
+ imageIndex: number;
321
+ startCol: number;
322
+ endCol: number;
323
+ }
324
+
325
+ function findJsonStringEnd(text: string, startCol: number): number | null {
326
+ if (text[startCol] !== '"') return null;
327
+
328
+ let escaped = false;
329
+ for (let index = startCol + 1; index < text.length; index += 1) {
330
+ const character = text[index];
331
+ if (escaped) {
332
+ escaped = false;
333
+ continue;
334
+ }
335
+ if (character === "\\") {
336
+ escaped = true;
337
+ continue;
338
+ }
339
+ if (character !== '"') continue;
340
+
341
+ const endCol = index + 1;
342
+ try {
343
+ if (typeof JSON.parse(text.slice(startCol, endCol)) !== "string") return null;
344
+ } catch {
345
+ return null;
346
+ }
347
+ return endCol;
348
+ }
349
+ return null;
350
+ }
351
+
352
+ /**
353
+ * Collects every validated `source=<JSON string>` value span on the line.
354
+ *
355
+ * A placeholder-looking token inside one of these spans is part of a file path,
356
+ * not a composer placeholder, so deleting it would corrupt the path. Spans are
357
+ * collected independently of any preceding placeholder because an orphaned
358
+ * `source="…"` can outlive the placeholder that produced it.
359
+ */
360
+ function findSourceValueSpans(line: string): Array<{ start: number; end: number }> {
361
+ const spans: Array<{ start: number; end: number }> = [];
362
+ const sourcePattern = /source=/g;
363
+ while (true) {
364
+ const match = sourcePattern.exec(line);
365
+ if (!match) return spans;
366
+ const valueStart = match.index + match[0].length;
367
+ const valueEnd = findJsonStringEnd(line, valueStart);
368
+ if (valueEnd === null) continue;
369
+ spans.push({ start: valueStart, end: valueEnd });
370
+ sourcePattern.lastIndex = valueEnd;
371
+ }
372
+ }
373
+
374
+ export function locatePastedImageReferenceAroundCursor(
375
+ line: string,
376
+ cursorCol: number,
377
+ ): PastedImageReferenceRange | null {
378
+ if (!Number.isInteger(cursorCol) || cursorCol < 0 || cursorCol > line.length) return null;
379
+
380
+ const sourceValueSpans = findSourceValueSpans(line);
381
+ const placeholderPattern = /\[image ([1-9]\d*)\]/g;
382
+ while (true) {
383
+ const match = placeholderPattern.exec(line);
384
+ if (!match) return null;
385
+
386
+ const startCol = match.index;
387
+ const placeholder = match[0];
388
+ const imageIndex = Number.parseInt(match[1] ?? "", 10);
389
+ if (!Number.isSafeInteger(imageIndex)) continue;
390
+
391
+ const enclosingSpan = sourceValueSpans.find(span => startCol >= span.start && startCol < span.end);
392
+ if (enclosingSpan) {
393
+ placeholderPattern.lastIndex = enclosingSpan.end;
394
+ continue;
395
+ }
396
+
397
+ const placeholderEnd = startCol + placeholder.length;
398
+ const sourcePrefix = " source=";
399
+ if (line.startsWith(sourcePrefix, placeholderEnd)) {
400
+ const fullEnd = findJsonStringEnd(line, placeholderEnd + sourcePrefix.length);
401
+ if (fullEnd === null) return null;
402
+ if (cursorCol === placeholderEnd || cursorCol === fullEnd) {
403
+ return { imageIndex, startCol, endCol: fullEnd };
404
+ }
405
+ placeholderPattern.lastIndex = fullEnd;
406
+ continue;
407
+ }
408
+
409
+ if (cursorCol === placeholderEnd) return { imageIndex, startCol, endCol: placeholderEnd };
410
+ }
411
+ }
@@ -339,7 +339,7 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
339
339
  : undefined;
340
340
  prewarmSearchProviders({
341
341
  authStorage,
342
- sessionId: session.getSessionId?.() ?? undefined,
342
+ sessionId: session.getCredentialSessionId?.() ?? session.getSessionId?.() ?? undefined,
343
343
  activeModelContext,
344
344
  });
345
345
  } catch {
@@ -356,7 +356,7 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
356
356
  _context?: AgentToolContext,
357
357
  ): Promise<AgentToolResult<SearchRenderDetails>> {
358
358
  const authStorage = this.#session.authStorage ?? (await discoverAuthStorage());
359
- const sessionId = this.#session.getSessionId?.() ?? undefined;
359
+ const sessionId = this.#session.getCredentialSessionId?.() ?? this.#session.getSessionId?.() ?? undefined;
360
360
  const activeModelContext = this.#session.model
361
361
  ? this.#session.modelRegistry?.getActiveSearchModelContext(this.#session.model)
362
362
  : undefined;
@@ -379,7 +379,7 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
379
379
  signal?: AbortSignal,
380
380
  ) {
381
381
  const authStorage = ctx.modelRegistry?.authStorage ?? (await discoverAuthStorage());
382
- const sessionId = ctx.sessionManager.getSessionId();
382
+ const sessionId = ctx.credentialSessionId ?? ctx.sessionManager.getSessionId();
383
383
  return executeSearch(toolCallId, params, {
384
384
  authStorage,
385
385
  sessionId,