open-research 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,7 +2,16 @@
2
2
  import {
3
3
  appendSessionEvent,
4
4
  loadSessionHistory
5
- } from "./chunk-ZUSIRA5S.js";
5
+ } from "./chunk-HRVDYJEC.js";
6
+ import {
7
+ GEMINI_CODE_ASSIST_URL,
8
+ clearStoredAuth,
9
+ loadGeminiAuth,
10
+ loadStoredAuth,
11
+ refreshGeminiAccessToken,
12
+ saveGeminiAuth,
13
+ saveStoredAuth
14
+ } from "./chunk-3GZIDCV2.js";
6
15
  import {
7
16
  ensureOpenResearchConfig,
8
17
  executeFetchUrl,
@@ -15,19 +24,26 @@ import {
15
24
  getOpenAlexApiKey,
16
25
  getSemanticScholarApiKey,
17
26
  loadOpenResearchConfig,
18
- readJsonFile,
19
27
  saveOpenResearchConfig,
20
- themeValues,
28
+ themeValues
29
+ } from "./chunk-3KZN54JZ.js";
30
+ import {
31
+ readJsonFile,
21
32
  writeJsonFile
22
- } from "./chunk-TQSQRNX6.js";
33
+ } from "./chunk-77Q5B5H7.js";
23
34
  import {
24
- getOpenResearchAuthFile,
25
35
  getOpenResearchRoot,
26
36
  getOpenResearchSkillsDir,
27
37
  getWorkspaceMetaDir,
28
38
  getWorkspaceProjectFile,
29
39
  getWorkspaceSessionsDir
30
- } from "./chunk-I5NVYKG7.js";
40
+ } from "./chunk-4HCPHCC2.js";
41
+ import {
42
+ getAvailableModels,
43
+ getDefaultModel,
44
+ getProviderCatalog,
45
+ selectModelForTask
46
+ } from "./chunk-GVEVKDGV.js";
31
47
  import {
32
48
  __require
33
49
  } from "./chunk-3RG5ZIWI.js";
@@ -164,7 +180,7 @@ ${markdown}`;
164
180
  }
165
181
 
166
182
  // src/lib/auth/import-codex.ts
167
- import fs4 from "fs/promises";
183
+ import fs3 from "fs/promises";
168
184
  import path3 from "path";
169
185
 
170
186
  // src/lib/storage/credential-types.ts
@@ -193,30 +209,6 @@ function getBootstrapCredentialValidation() {
193
209
  };
194
210
  }
195
211
 
196
- // src/lib/auth/store.ts
197
- import fs3 from "fs/promises";
198
- var AUTH_FILE_MODE = 384;
199
- async function ensureCliHome(options) {
200
- const root = getOpenResearchRoot(options);
201
- await fs3.mkdir(root, { recursive: true, mode: 448 });
202
- return root;
203
- }
204
- async function saveStoredAuth(auth2, options) {
205
- await ensureCliHome(options);
206
- const authFile = getOpenResearchAuthFile(options);
207
- await writeJsonFile(authFile, auth2, AUTH_FILE_MODE);
208
- await fs3.chmod(authFile, AUTH_FILE_MODE);
209
- return authFile;
210
- }
211
- async function loadStoredAuth(options) {
212
- const authFile = getOpenResearchAuthFile(options);
213
- return readJsonFile(authFile, null);
214
- }
215
- async function clearStoredAuth(options) {
216
- const authFile = getOpenResearchAuthFile(options);
217
- await fs3.rm(authFile, { force: true });
218
- }
219
-
220
212
  // src/lib/auth/openai-oauth.ts
221
213
  import { createHash, randomBytes } from "crypto";
222
214
  var OPENAI_AUTH_URL = "https://auth.openai.com/oauth/authorize";
@@ -309,7 +301,7 @@ async function importCodexAuth(options = {}) {
309
301
  const now = options.now ?? Date.now;
310
302
  const codexAuthPath = options.codexAuthFilePath ?? path3.join(options.homeDir ?? process.env.HOME ?? "", ".codex", "auth.json");
311
303
  const parsed = JSON.parse(
312
- await fs4.readFile(codexAuthPath, "utf8")
304
+ await fs3.readFile(codexAuthPath, "utf8")
313
305
  );
314
306
  if (parsed.auth_mode !== "chatgpt") {
315
307
  throw new Error("Codex is not signed in with OpenAI on this device.");
@@ -480,54 +472,6 @@ var OPENAI_AUTH_ONLY = process.env.OPENAI_AUTH_ONLY !== "false";
480
472
  var CODEX_RESPONSES_URL = process.env.CODEX_RESPONSES_URL ?? "https://chatgpt.com/backend-api/codex/responses";
481
473
  var OPENAI_VALIDATION_STALE_MS = 15 * 60 * 1e3;
482
474
 
483
- // src/lib/llm/provider-catalog.ts
484
- var OPENAI_PROVIDER_MODELS = [
485
- "gpt-5.4",
486
- "gpt-5.4-mini",
487
- "o3",
488
- "o4-mini"
489
- ];
490
- var OPENAI_CATALOG = {
491
- family: "openai",
492
- displayName: "OpenAI",
493
- models: OPENAI_PROVIDER_MODELS,
494
- defaultModel: "gpt-5.4",
495
- backgroundModel: "gpt-5.4-mini"
496
- };
497
- function getProviderCatalog(providerKind) {
498
- switch (providerKind) {
499
- case "openai_auth":
500
- case "openai_api_key":
501
- default:
502
- return OPENAI_CATALOG;
503
- }
504
- }
505
- function getAvailableModels(providerKind) {
506
- return getProviderCatalog(providerKind).models;
507
- }
508
- function isSupportedModel(model, providerKind) {
509
- if (!model) return false;
510
- return getAvailableModels(providerKind).includes(model);
511
- }
512
- function getDefaultModel(providerKind) {
513
- return getProviderCatalog(providerKind).defaultModel;
514
- }
515
- function selectModelForTask(providerKind, requestedModel, task) {
516
- const catalog = getProviderCatalog(providerKind);
517
- const selected = isSupportedModel(requestedModel, providerKind) ? requestedModel : catalog.defaultModel;
518
- switch (task) {
519
- case "conversation":
520
- return selected;
521
- case "compaction":
522
- return selected.includes("5.4") ? catalog.backgroundModel : selected;
523
- case "memory":
524
- case "workspace":
525
- return catalog.backgroundModel;
526
- default:
527
- return selected;
528
- }
529
- }
530
-
531
475
  // src/lib/llm/openai-connection.ts
532
476
  var VALIDATION_INSTRUCTIONS = "You are validating whether this OpenAI Codex connection can execute a minimal request. Reply with the single word ok.";
533
477
  function buildHeaders(token, accountId) {
@@ -645,44 +589,42 @@ function trimCredential(value) {
645
589
  const trimmed = value?.trim();
646
590
  return trimmed ? trimmed : void 0;
647
591
  }
648
- async function resolveConfiguredProvider(options) {
649
- const [config, stored] = await Promise.all([
650
- loadOpenResearchConfig({ homeDir: options?.homeDir }),
651
- loadStoredAuth({ homeDir: options?.homeDir })
652
- ]);
592
+ function resolveOpenAI(stored, config) {
653
593
  if (stored) {
654
- return {
655
- kind: "openai_auth",
656
- source: "stored_auth",
657
- stored
658
- };
594
+ return { kind: "openai_auth", source: "stored_auth", stored };
659
595
  }
660
596
  const envKey = trimCredential(process.env.OPENAI_API_KEY);
661
- if (envKey) {
662
- return {
663
- kind: "openai_api_key",
664
- source: "env",
665
- apiKey: envKey
666
- };
667
- }
597
+ if (envKey) return { kind: "openai_api_key", source: "env", apiKey: envKey };
668
598
  const providerKey = trimCredential(config?.providers?.openai?.apiKey);
669
- if (providerKey) {
670
- return {
671
- kind: "openai_api_key",
672
- source: "providers.openai.apiKey",
673
- apiKey: providerKey
674
- };
675
- }
599
+ if (providerKey) return { kind: "openai_api_key", source: "providers.openai.apiKey", apiKey: providerKey };
676
600
  const legacyKey = trimCredential(config?.apiKeys?.openai);
677
- if (legacyKey) {
678
- return {
679
- kind: "openai_api_key",
680
- source: "apiKeys.openai",
681
- apiKey: legacyKey
682
- };
683
- }
601
+ if (legacyKey) return { kind: "openai_api_key", source: "apiKeys.openai", apiKey: legacyKey };
684
602
  return null;
685
603
  }
604
+ function resolveGemini(stored, config) {
605
+ if (stored) {
606
+ return { kind: "gemini_auth", source: "stored_auth", stored };
607
+ }
608
+ const envKey = trimCredential(process.env.GEMINI_API_KEY);
609
+ if (envKey) return { kind: "gemini_api_key", source: "env", apiKey: envKey };
610
+ const providerKey = trimCredential(config?.providers?.gemini?.apiKey);
611
+ if (providerKey) return { kind: "gemini_api_key", source: "providers.gemini.apiKey", apiKey: providerKey };
612
+ const legacyKey = trimCredential(config?.apiKeys?.gemini);
613
+ if (legacyKey) return { kind: "gemini_api_key", source: "apiKeys.gemini", apiKey: legacyKey };
614
+ return null;
615
+ }
616
+ async function resolveConfiguredProvider(options) {
617
+ const [config, openaiStored, geminiStored] = await Promise.all([
618
+ loadOpenResearchConfig({ homeDir: options?.homeDir }),
619
+ loadStoredAuth({ homeDir: options?.homeDir }),
620
+ loadGeminiAuth({ homeDir: options?.homeDir })
621
+ ]);
622
+ const activeProvider = config?.activeProvider ?? "openai";
623
+ if (activeProvider === "gemini") {
624
+ return resolveGemini(geminiStored, config) ?? resolveOpenAI(openaiStored, config);
625
+ }
626
+ return resolveOpenAI(openaiStored, config) ?? resolveGemini(geminiStored, config);
627
+ }
686
628
  async function hasConfiguredProvider(options) {
687
629
  return await resolveConfiguredProvider(options) !== null;
688
630
  }
@@ -691,24 +633,22 @@ async function getConfiguredProviderSummary(options) {
691
633
  if (!resolved) {
692
634
  return {
693
635
  connected: false,
694
- message: "No OpenAI credentials configured. Set OPENAI_API_KEY, run /config apikey <key>, or run /auth."
636
+ message: "No credentials configured. Run /auth (OpenAI), /auth-gemini (Google), or /config apikey <key>."
695
637
  };
696
638
  }
697
639
  if (resolved.kind === "openai_auth") {
698
- return {
699
- connected: true,
700
- kind: resolved.kind,
701
- source: resolved.source,
702
- message: "OpenAI account connected."
703
- };
640
+ return { connected: true, kind: resolved.kind, source: resolved.source, message: "OpenAI account connected." };
704
641
  }
705
- const sourceMessage = resolved.source === "env" ? "OPENAI_API_KEY" : resolved.source === "providers.openai.apiKey" ? "providers.openai.apiKey" : "apiKeys.openai";
706
- return {
707
- connected: true,
708
- kind: resolved.kind,
709
- source: resolved.source,
710
- message: `OpenAI API key configured via ${sourceMessage}.`
711
- };
642
+ if (resolved.kind === "openai_api_key") {
643
+ return { connected: true, kind: resolved.kind, source: resolved.source, message: `OpenAI API key configured via ${resolved.source}.` };
644
+ }
645
+ if (resolved.kind === "gemini_auth") {
646
+ return { connected: true, kind: resolved.kind, source: resolved.source, message: `Google account connected (${resolved.stored.tokens.email}).` };
647
+ }
648
+ if (resolved.kind === "gemini_api_key") {
649
+ return { connected: true, kind: resolved.kind, source: resolved.source, message: `Gemini API key configured via ${resolved.source}.` };
650
+ }
651
+ return { connected: false, message: "Unknown provider state." };
712
652
  }
713
653
 
714
654
  // src/lib/auth/status.ts
@@ -743,7 +683,7 @@ async function getAuthStatus(options) {
743
683
  }
744
684
 
745
685
  // src/lib/skills/registry.ts
746
- import fs5 from "fs/promises";
686
+ import fs4 from "fs/promises";
747
687
  import fsSync from "fs";
748
688
  import path4 from "path";
749
689
  import matter from "gray-matter";
@@ -756,13 +696,13 @@ function normalizeSkillName(name) {
756
696
  }
757
697
  async function ensureUserSkillsDir(options) {
758
698
  const dir = getOpenResearchSkillsDir(options);
759
- await fs5.mkdir(dir, { recursive: true });
699
+ await fs4.mkdir(dir, { recursive: true });
760
700
  return dir;
761
701
  }
762
702
  async function readSkillSummary(skillDir, source2) {
763
703
  const skillFile = path4.join(skillDir, "SKILL.md");
764
704
  try {
765
- const raw = await fs5.readFile(skillFile, "utf8");
705
+ const raw = await fs4.readFile(skillFile, "utf8");
766
706
  const parsed = matter(raw);
767
707
  const name = String(parsed.data.name ?? "").trim();
768
708
  const description = String(parsed.data.description ?? "").trim();
@@ -781,7 +721,7 @@ async function readSkillSummary(skillDir, source2) {
781
721
  }
782
722
  }
783
723
  async function listSkillsInDirectory(rootDir, source2) {
784
- const entries = await fs5.readdir(rootDir, { withFileTypes: true }).catch(() => []);
724
+ const entries = await fs4.readdir(rootDir, { withFileTypes: true }).catch(() => []);
785
725
  const results = await Promise.all(
786
726
  entries.filter((entry) => entry.isDirectory()).map((entry) => readSkillSummary(path4.join(rootDir, entry.name), source2))
787
727
  );
@@ -798,7 +738,7 @@ async function listAvailableSkills(options) {
798
738
  async function validateSkillDirectory(input2) {
799
739
  const errors = [];
800
740
  const skillFile = path4.join(input2.skillDir, "SKILL.md");
801
- const raw = await fs5.readFile(skillFile, "utf8").catch(() => "");
741
+ const raw = await fs4.readFile(skillFile, "utf8").catch(() => "");
802
742
  if (!raw) {
803
743
  return { ok: false, errors: ["SKILL.md is missing."] };
804
744
  }
@@ -829,9 +769,9 @@ async function createSkillScaffold(input2) {
829
769
  const skillsDir = await ensureUserSkillsDir({ homeDir: input2.homeDir });
830
770
  const name = normalizeSkillName(input2.name);
831
771
  const skillDir = path4.join(skillsDir, name);
832
- await fs5.mkdir(path4.join(skillDir, "scripts"), { recursive: true });
833
- await fs5.mkdir(path4.join(skillDir, "references"), { recursive: true });
834
- await fs5.mkdir(path4.join(skillDir, "assets"), { recursive: true });
772
+ await fs4.mkdir(path4.join(skillDir, "scripts"), { recursive: true });
773
+ await fs4.mkdir(path4.join(skillDir, "references"), { recursive: true });
774
+ await fs4.mkdir(path4.join(skillDir, "assets"), { recursive: true });
835
775
  const body = `---
836
776
  name: ${name}
837
777
  description: ${input2.description}
@@ -851,7 +791,7 @@ ${input2.examples.map((example) => `- ${example}`).join("\n")}
851
791
 
852
792
  ${input2.workflow}
853
793
  `;
854
- await fs5.writeFile(path4.join(skillDir, "SKILL.md"), body, "utf8");
794
+ await fs4.writeFile(path4.join(skillDir, "SKILL.md"), body, "utf8");
855
795
  return skillDir;
856
796
  }
857
797
 
@@ -889,7 +829,7 @@ function formatDateTime(value) {
889
829
  }
890
830
 
891
831
  // src/lib/cli/version.ts
892
- var PACKAGE_VERSION = "1.0.0";
832
+ var PACKAGE_VERSION = "1.0.1";
893
833
  function getPackageVersion() {
894
834
  return PACKAGE_VERSION;
895
835
  }
@@ -2355,7 +2295,7 @@ function useTheme() {
2355
2295
  }
2356
2296
 
2357
2297
  // src/lib/workspace/scan.ts
2358
- import fs6 from "fs/promises";
2298
+ import fs5 from "fs/promises";
2359
2299
  import path5 from "path";
2360
2300
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
2361
2301
  ".md",
@@ -2386,7 +2326,7 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
2386
2326
  ".turbo"
2387
2327
  ]);
2388
2328
  async function walkDir(rootDir, currentDir, out) {
2389
- const entries = await fs6.readdir(currentDir, { withFileTypes: true });
2329
+ const entries = await fs5.readdir(currentDir, { withFileTypes: true });
2390
2330
  for (const entry of entries) {
2391
2331
  if (IGNORED_DIRS.has(entry.name)) {
2392
2332
  continue;
@@ -2404,7 +2344,7 @@ async function walkDir(rootDir, currentDir, out) {
2404
2344
  key: `path:${relativePath}`,
2405
2345
  label: relativePath,
2406
2346
  path: relativePath,
2407
- content: await fs6.readFile(fullPath, "utf8")
2347
+ content: await fs5.readFile(fullPath, "utf8")
2408
2348
  });
2409
2349
  }
2410
2350
  }
@@ -2563,14 +2503,14 @@ function createOpenAIAuthProvider(credentials, onTokenRefresh, onValidationChang
2563
2503
  }
2564
2504
  return creds.accessToken;
2565
2505
  }
2566
- const sessionId = crypto.randomUUID();
2506
+ const sessionId2 = crypto.randomUUID();
2567
2507
  function buildHeaders3(token) {
2568
2508
  const headers = {
2569
2509
  Authorization: `Bearer ${token}`,
2570
2510
  "Content-Type": "application/json",
2571
2511
  originator: "open-research",
2572
2512
  "User-Agent": `open-research/${getPackageVersion()} (${process.platform} ${process.arch})`,
2573
- session_id: sessionId
2513
+ session_id: sessionId2
2574
2514
  };
2575
2515
  if (creds.accountId) {
2576
2516
  headers["ChatGPT-Account-Id"] = creds.accountId;
@@ -3085,12 +3025,478 @@ function createOpenAIAPIKeyProvider(apiKey) {
3085
3025
  };
3086
3026
  }
3087
3027
 
3028
+ // src/lib/llm/gemini-format.ts
3029
+ function convertMessagesToGemini(messages) {
3030
+ const systemParts = [];
3031
+ const rawContents = [];
3032
+ const toolCallNames = /* @__PURE__ */ new Map();
3033
+ for (const msg of messages) {
3034
+ if (msg.role === "assistant" && msg.tool_calls) {
3035
+ for (const tc of msg.tool_calls) {
3036
+ toolCallNames.set(tc.id, tc.function.name);
3037
+ }
3038
+ }
3039
+ }
3040
+ for (const msg of messages) {
3041
+ if (msg.role === "system") {
3042
+ const text = typeof msg.content === "string" ? msg.content : "";
3043
+ if (text.trim()) systemParts.push(text);
3044
+ continue;
3045
+ }
3046
+ if (msg.role === "user") {
3047
+ const text = typeof msg.content === "string" ? msg.content : "";
3048
+ if (text.trim()) {
3049
+ rawContents.push({ role: "user", parts: [{ text }] });
3050
+ }
3051
+ continue;
3052
+ }
3053
+ if (msg.role === "assistant") {
3054
+ const parts = [];
3055
+ const text = typeof msg.content === "string" ? msg.content : "";
3056
+ if (text.trim()) {
3057
+ parts.push({ text });
3058
+ }
3059
+ if (msg.tool_calls) {
3060
+ for (const tc of msg.tool_calls) {
3061
+ let args = {};
3062
+ try {
3063
+ args = JSON.parse(tc.function.arguments);
3064
+ } catch {
3065
+ }
3066
+ parts.push({
3067
+ functionCall: { name: tc.function.name, args },
3068
+ thoughtSignature: "skip_thought_signature_validator"
3069
+ });
3070
+ }
3071
+ }
3072
+ if (parts.length > 0) {
3073
+ rawContents.push({ role: "model", parts });
3074
+ }
3075
+ continue;
3076
+ }
3077
+ if (msg.role === "tool") {
3078
+ const name = msg.tool_call_id ? toolCallNames.get(msg.tool_call_id) ?? "unknown" : "unknown";
3079
+ const content = typeof msg.content === "string" ? msg.content : "";
3080
+ rawContents.push({
3081
+ role: "user",
3082
+ parts: [{ functionResponse: { name, response: { content } } }]
3083
+ });
3084
+ continue;
3085
+ }
3086
+ }
3087
+ const merged = [];
3088
+ for (const content of rawContents) {
3089
+ const last = merged[merged.length - 1];
3090
+ if (last && last.role === content.role) {
3091
+ last.parts.push(...content.parts);
3092
+ } else {
3093
+ merged.push({ role: content.role, parts: [...content.parts] });
3094
+ }
3095
+ }
3096
+ return {
3097
+ systemInstruction: systemParts.length > 0 ? { parts: systemParts.map((text) => ({ text })) } : void 0,
3098
+ contents: merged
3099
+ };
3100
+ }
3101
+ function convertToolsToGemini(tools) {
3102
+ if (!tools || tools.length === 0) return void 0;
3103
+ return {
3104
+ functionDeclarations: tools.map((t) => ({
3105
+ name: t.function.name,
3106
+ description: t.function.description,
3107
+ parameters: t.function.parameters
3108
+ }))
3109
+ };
3110
+ }
3111
+ function mapReasoningEffort(effort, model) {
3112
+ if (!effort || effort === "none") return void 0;
3113
+ if (model.includes("gemini-3")) {
3114
+ const levelMap = {
3115
+ low: "THINKING_LEVEL_LOW",
3116
+ medium: "THINKING_LEVEL_MEDIUM",
3117
+ high: "THINKING_LEVEL_HIGH",
3118
+ xhigh: "THINKING_LEVEL_HIGH"
3119
+ };
3120
+ return {
3121
+ thinkingConfig: {
3122
+ thinkingLevel: levelMap[effort] ?? "THINKING_LEVEL_MEDIUM",
3123
+ includeThoughts: true
3124
+ }
3125
+ };
3126
+ }
3127
+ const budgetMap = {
3128
+ low: 1024,
3129
+ medium: 8192,
3130
+ high: 16384,
3131
+ xhigh: 32768
3132
+ };
3133
+ return {
3134
+ thinkingConfig: {
3135
+ thinkingBudget: budgetMap[effort] ?? 8192,
3136
+ includeThoughts: true
3137
+ }
3138
+ };
3139
+ }
3140
+ function mapJsonSchema(schema) {
3141
+ if (!schema) return void 0;
3142
+ return {
3143
+ responseMimeType: "application/json",
3144
+ responseSchema: schema.schema
3145
+ };
3146
+ }
3147
+
3148
+ // src/lib/llm/providers/gemini-auth.ts
3149
+ var REFRESH_BUFFER_MS = 6e4;
3150
+ var GEMINI_CLI_VERSION = "0.30.0";
3151
+ var sessionId = crypto.randomUUID();
3152
+ function createGeminiAuthProvider(credentials, onTokenRefresh) {
3153
+ let currentToken = credentials.accessToken;
3154
+ let currentExpiry = credentials.expiresAt;
3155
+ let refreshing = null;
3156
+ async function ensureValidToken() {
3157
+ if (Date.now() < currentExpiry - REFRESH_BUFFER_MS) {
3158
+ return currentToken;
3159
+ }
3160
+ if (!refreshing) {
3161
+ refreshing = (async () => {
3162
+ const result = await refreshGeminiAccessToken(credentials.refreshToken);
3163
+ currentToken = result.access_token;
3164
+ currentExpiry = Date.now() + result.expires_in * 1e3;
3165
+ if (result.refresh_token) {
3166
+ credentials.refreshToken = result.refresh_token;
3167
+ }
3168
+ await onTokenRefresh({
3169
+ ...credentials,
3170
+ accessToken: currentToken,
3171
+ expiresAt: currentExpiry
3172
+ });
3173
+ })();
3174
+ }
3175
+ try {
3176
+ await refreshing;
3177
+ } finally {
3178
+ refreshing = null;
3179
+ }
3180
+ return currentToken;
3181
+ }
3182
+ function buildHeaders3(token, model) {
3183
+ return {
3184
+ Authorization: `Bearer ${token}`,
3185
+ "Content-Type": "application/json",
3186
+ "User-Agent": `GeminiCLI/${GEMINI_CLI_VERSION}/${model} (${process.platform}; ${process.arch})`,
3187
+ "x-activity-request-id": Math.random().toString(36).slice(2, 10)
3188
+ };
3189
+ }
3190
+ function buildRequestBody(options, model) {
3191
+ const messages = options.messages;
3192
+ const { systemInstruction, contents } = convertMessagesToGemini(messages);
3193
+ const generationConfig = {};
3194
+ if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
3195
+ if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
3196
+ const thinking = mapReasoningEffort(options.reasoningEffort, model);
3197
+ if (thinking) Object.assign(generationConfig, thinking);
3198
+ if ("jsonSchema" in options && options.jsonSchema) {
3199
+ const schema = mapJsonSchema(options.jsonSchema);
3200
+ if (schema) Object.assign(generationConfig, schema);
3201
+ }
3202
+ const request = {
3203
+ contents,
3204
+ generationConfig,
3205
+ session_id: sessionId
3206
+ };
3207
+ if (systemInstruction) request.systemInstruction = systemInstruction;
3208
+ if ("tools" in options && options.tools) {
3209
+ const geminiTools = convertToolsToGemini(options.tools);
3210
+ if (geminiTools) request.tools = [geminiTools];
3211
+ }
3212
+ return {
3213
+ project: credentials.projectId,
3214
+ model,
3215
+ user_prompt_id: crypto.randomUUID(),
3216
+ request
3217
+ };
3218
+ }
3219
+ async function callLLM(options) {
3220
+ const model = options.model ?? "gemini-3.1-pro-preview";
3221
+ const token = await ensureValidToken();
3222
+ const body = buildRequestBody(options, model);
3223
+ const startTime = Date.now();
3224
+ const response = await fetch(
3225
+ `${GEMINI_CODE_ASSIST_URL}/v1internal:generateContent`,
3226
+ {
3227
+ method: "POST",
3228
+ headers: buildHeaders3(token, model),
3229
+ body: JSON.stringify(body)
3230
+ }
3231
+ );
3232
+ if (!response.ok) {
3233
+ const text = await response.text();
3234
+ throw new Error(`Gemini API error: ${response.status} ${text}`);
3235
+ }
3236
+ const data = await response.json();
3237
+ const inner = data.response ?? data;
3238
+ const parts = inner.candidates?.[0]?.content?.parts ?? [];
3239
+ const content = parts.map((p) => p.text ?? "").join("");
3240
+ const usage = inner.usageMetadata ?? {};
3241
+ return {
3242
+ content,
3243
+ model,
3244
+ usage: {
3245
+ promptTokens: usage.promptTokenCount ?? 0,
3246
+ completionTokens: usage.candidatesTokenCount ?? 0,
3247
+ totalTokens: usage.totalTokenCount ?? 0
3248
+ },
3249
+ latencyMs: Date.now() - startTime
3250
+ };
3251
+ }
3252
+ async function* callLLMStreaming(options) {
3253
+ const model = options.model ?? "gemini-3.1-pro-preview";
3254
+ const token = await ensureValidToken();
3255
+ const body = buildRequestBody(options, model);
3256
+ const response = await fetch(
3257
+ `${GEMINI_CODE_ASSIST_URL}/v1internal:streamGenerateContent?alt=sse`,
3258
+ {
3259
+ method: "POST",
3260
+ headers: {
3261
+ ...buildHeaders3(token, model),
3262
+ Accept: "text/event-stream"
3263
+ },
3264
+ body: JSON.stringify(body),
3265
+ signal: options.signal
3266
+ }
3267
+ );
3268
+ if (!response.ok) {
3269
+ const text = await response.text();
3270
+ throw new Error(`Gemini streaming error: ${response.status} ${text}`);
3271
+ }
3272
+ const reader = response.body?.getReader();
3273
+ if (!reader) throw new Error("No response body");
3274
+ const decoder = new TextDecoder();
3275
+ let buffer = "";
3276
+ let fullText = "";
3277
+ const toolCalls = [];
3278
+ let toolCallIndex = 0;
3279
+ let lastUsage;
3280
+ while (true) {
3281
+ const { done, value } = await reader.read();
3282
+ if (done) break;
3283
+ buffer += decoder.decode(value, { stream: true });
3284
+ while (buffer.includes("\n")) {
3285
+ const lineEnd = buffer.indexOf("\n");
3286
+ const line = buffer.slice(0, lineEnd).trim();
3287
+ buffer = buffer.slice(lineEnd + 1);
3288
+ if (!line.startsWith("data: ")) continue;
3289
+ const jsonStr = line.slice(6);
3290
+ if (jsonStr === "[DONE]") continue;
3291
+ let parsed;
3292
+ try {
3293
+ parsed = JSON.parse(jsonStr);
3294
+ } catch {
3295
+ continue;
3296
+ }
3297
+ const inner = parsed.response ?? parsed;
3298
+ const candidates = inner.candidates ?? [];
3299
+ const candidate = candidates[0];
3300
+ if (!candidate) continue;
3301
+ const parts = candidate.content?.parts ?? [];
3302
+ for (const part of parts) {
3303
+ if (typeof part.text === "string" && part.text) {
3304
+ if (part.thought === true) continue;
3305
+ fullText += part.text;
3306
+ yield { type: "text_delta", content: part.text };
3307
+ }
3308
+ if (part.functionCall) {
3309
+ const fc = part.functionCall;
3310
+ const id = `call_${crypto.randomUUID().slice(0, 8)}`;
3311
+ const args = JSON.stringify(fc.args ?? {});
3312
+ toolCalls.push({ id, name: fc.name, arguments: args });
3313
+ yield { type: "tool_call_start", index: toolCallIndex, id, name: fc.name };
3314
+ yield { type: "tool_call_delta", index: toolCallIndex, arguments: args };
3315
+ toolCallIndex++;
3316
+ }
3317
+ }
3318
+ const usage = inner.usageMetadata;
3319
+ if (usage) {
3320
+ lastUsage = {
3321
+ promptTokens: usage.promptTokenCount ?? 0,
3322
+ completionTokens: usage.candidatesTokenCount ?? 0,
3323
+ totalTokens: usage.totalTokenCount ?? 0,
3324
+ cachedTokens: usage.cachedContentTokenCount ?? 0,
3325
+ reasoningTokens: usage.thoughtsTokenCount ?? 0
3326
+ };
3327
+ }
3328
+ }
3329
+ }
3330
+ yield {
3331
+ type: "done",
3332
+ content: fullText,
3333
+ toolCalls,
3334
+ usage: lastUsage
3335
+ };
3336
+ }
3337
+ return {
3338
+ kind: "gemini_auth",
3339
+ callLLM,
3340
+ callLLMStreaming
3341
+ };
3342
+ }
3343
+
3344
+ // src/lib/llm/providers/gemini-api-key.ts
3345
+ var GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
3346
+ function createGeminiAPIKeyProvider(apiKey) {
3347
+ function buildRequestBody(options, model) {
3348
+ const { systemInstruction, contents } = convertMessagesToGemini(options.messages);
3349
+ const generationConfig = {};
3350
+ if (options.temperature !== void 0) generationConfig.temperature = options.temperature;
3351
+ if (options.maxTokens !== void 0) generationConfig.maxOutputTokens = options.maxTokens;
3352
+ const thinking = mapReasoningEffort(options.reasoningEffort, model);
3353
+ if (thinking) Object.assign(generationConfig, thinking);
3354
+ if ("jsonSchema" in options && options.jsonSchema) {
3355
+ const schema = mapJsonSchema(options.jsonSchema);
3356
+ if (schema) Object.assign(generationConfig, schema);
3357
+ }
3358
+ const body = {
3359
+ contents,
3360
+ generationConfig
3361
+ };
3362
+ if (systemInstruction) body.systemInstruction = systemInstruction;
3363
+ if ("tools" in options && options.tools) {
3364
+ const geminiTools = convertToolsToGemini(options.tools);
3365
+ if (geminiTools) body.tools = [geminiTools];
3366
+ }
3367
+ return body;
3368
+ }
3369
+ async function callLLM(options) {
3370
+ const model = options.model ?? "gemini-3-flash-preview";
3371
+ const body = buildRequestBody(options, model);
3372
+ const startTime = Date.now();
3373
+ const response = await fetch(
3374
+ `${GEMINI_API_BASE}/models/${model}:generateContent`,
3375
+ {
3376
+ method: "POST",
3377
+ headers: {
3378
+ "Content-Type": "application/json",
3379
+ "x-goog-api-key": apiKey
3380
+ },
3381
+ body: JSON.stringify(body)
3382
+ }
3383
+ );
3384
+ if (!response.ok) {
3385
+ const text = await response.text();
3386
+ throw new Error(`Gemini API error: ${response.status} ${text}`);
3387
+ }
3388
+ const data = await response.json();
3389
+ const parts = data.candidates?.[0]?.content?.parts ?? [];
3390
+ const content = parts.map((p) => p.text ?? "").join("");
3391
+ const usage = data.usageMetadata ?? {};
3392
+ return {
3393
+ content,
3394
+ model,
3395
+ usage: {
3396
+ promptTokens: usage.promptTokenCount ?? 0,
3397
+ completionTokens: usage.candidatesTokenCount ?? 0,
3398
+ totalTokens: usage.totalTokenCount ?? 0
3399
+ },
3400
+ latencyMs: Date.now() - startTime
3401
+ };
3402
+ }
3403
+ async function* callLLMStreaming(options) {
3404
+ const model = options.model ?? "gemini-3-flash-preview";
3405
+ const body = buildRequestBody(options, model);
3406
+ const response = await fetch(
3407
+ `${GEMINI_API_BASE}/models/${model}:streamGenerateContent?alt=sse`,
3408
+ {
3409
+ method: "POST",
3410
+ headers: {
3411
+ "Content-Type": "application/json",
3412
+ "x-goog-api-key": apiKey,
3413
+ Accept: "text/event-stream"
3414
+ },
3415
+ body: JSON.stringify(body),
3416
+ signal: options.signal
3417
+ }
3418
+ );
3419
+ if (!response.ok) {
3420
+ const text = await response.text();
3421
+ throw new Error(`Gemini streaming error: ${response.status} ${text}`);
3422
+ }
3423
+ const reader = response.body?.getReader();
3424
+ if (!reader) throw new Error("No response body");
3425
+ const decoder = new TextDecoder();
3426
+ let buffer = "";
3427
+ let fullText = "";
3428
+ const toolCalls = [];
3429
+ let toolCallIndex = 0;
3430
+ let lastUsage;
3431
+ while (true) {
3432
+ const { done, value } = await reader.read();
3433
+ if (done) break;
3434
+ buffer += decoder.decode(value, { stream: true });
3435
+ while (buffer.includes("\n")) {
3436
+ const lineEnd = buffer.indexOf("\n");
3437
+ const line = buffer.slice(0, lineEnd).trim();
3438
+ buffer = buffer.slice(lineEnd + 1);
3439
+ if (!line.startsWith("data: ")) continue;
3440
+ const jsonStr = line.slice(6);
3441
+ if (jsonStr === "[DONE]") continue;
3442
+ let parsed;
3443
+ try {
3444
+ parsed = JSON.parse(jsonStr);
3445
+ } catch {
3446
+ continue;
3447
+ }
3448
+ const candidates = parsed.candidates ?? [];
3449
+ const candidate = candidates[0];
3450
+ if (!candidate) continue;
3451
+ const parts = candidate.content?.parts ?? [];
3452
+ for (const part of parts) {
3453
+ if (typeof part.text === "string" && part.text) {
3454
+ if (part.thought === true) continue;
3455
+ fullText += part.text;
3456
+ yield { type: "text_delta", content: part.text };
3457
+ }
3458
+ if (part.functionCall) {
3459
+ const fc = part.functionCall;
3460
+ const id = `call_${crypto.randomUUID().slice(0, 8)}`;
3461
+ const args = JSON.stringify(fc.args ?? {});
3462
+ toolCalls.push({ id, name: fc.name, arguments: args });
3463
+ yield { type: "tool_call_start", index: toolCallIndex, id, name: fc.name };
3464
+ yield { type: "tool_call_delta", index: toolCallIndex, arguments: args };
3465
+ toolCallIndex++;
3466
+ }
3467
+ }
3468
+ const usage = parsed.usageMetadata;
3469
+ if (usage) {
3470
+ lastUsage = {
3471
+ promptTokens: usage.promptTokenCount ?? 0,
3472
+ completionTokens: usage.candidatesTokenCount ?? 0,
3473
+ totalTokens: usage.totalTokenCount ?? 0,
3474
+ cachedTokens: usage.cachedContentTokenCount ?? 0,
3475
+ reasoningTokens: usage.thoughtsTokenCount ?? 0
3476
+ };
3477
+ }
3478
+ }
3479
+ }
3480
+ yield {
3481
+ type: "done",
3482
+ content: fullText,
3483
+ toolCalls,
3484
+ usage: lastUsage
3485
+ };
3486
+ }
3487
+ return {
3488
+ kind: "gemini_api_key",
3489
+ callLLM,
3490
+ callLLMStreaming
3491
+ };
3492
+ }
3493
+
3088
3494
  // src/lib/llm/provider-factory.ts
3089
3495
  async function createProviderFromStoredAuth(options) {
3090
3496
  const resolved = await resolveConfiguredProvider({ homeDir: options?.homeDir });
3091
3497
  if (!resolved) {
3092
3498
  throw new Error(
3093
- "No OpenAI credentials found. Run /auth to connect via OAuth (free), set OPENAI_API_KEY, or run /config apikey <key>."
3499
+ "No credentials found. Run /auth (OpenAI), /auth-gemini (Google), or /config apikey <key>."
3094
3500
  );
3095
3501
  }
3096
3502
  if (resolved.kind === "openai_auth") {
@@ -3117,7 +3523,32 @@ async function createProviderFromStoredAuth(options) {
3117
3523
  }
3118
3524
  );
3119
3525
  }
3120
- return createOpenAIAPIKeyProvider(resolved.apiKey);
3526
+ if (resolved.kind === "openai_api_key") {
3527
+ return createOpenAIAPIKeyProvider(resolved.apiKey);
3528
+ }
3529
+ if (resolved.kind === "gemini_auth") {
3530
+ const stored = resolved.stored;
3531
+ return createGeminiAuthProvider(
3532
+ {
3533
+ accessToken: stored.tokens.access,
3534
+ refreshToken: stored.tokens.refresh,
3535
+ expiresAt: stored.tokens.expires,
3536
+ email: stored.tokens.email,
3537
+ projectId: stored.tokens.projectId
3538
+ },
3539
+ async (newCreds) => {
3540
+ stored.tokens.access = newCreds.accessToken;
3541
+ stored.tokens.refresh = newCreds.refreshToken;
3542
+ stored.tokens.expires = newCreds.expiresAt;
3543
+ stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3544
+ await saveGeminiAuth(stored, { homeDir: options?.homeDir });
3545
+ }
3546
+ );
3547
+ }
3548
+ if (resolved.kind === "gemini_api_key") {
3549
+ return createGeminiAPIKeyProvider(resolved.apiKey);
3550
+ }
3551
+ throw new Error("Unknown provider kind.");
3121
3552
  }
3122
3553
 
3123
3554
  // src/lib/agent/tool-schemas.ts
@@ -3668,7 +4099,7 @@ Root: ${process.cwd()}`,
3668
4099
  }
3669
4100
 
3670
4101
  // src/lib/agent/tools/read-file.ts
3671
- import fs7 from "fs/promises";
4102
+ import fs6 from "fs/promises";
3672
4103
  import { createReadStream } from "fs";
3673
4104
  import path6 from "path";
3674
4105
  import os2 from "os";
@@ -3732,7 +4163,7 @@ function isBinaryByExtension(filePath) {
3732
4163
  return BINARY_EXTENSIONS.has(path6.extname(filePath).toLowerCase());
3733
4164
  }
3734
4165
  async function isBinaryByContent(filePath) {
3735
- const handle = await fs7.open(filePath, "r");
4166
+ const handle = await fs6.open(filePath, "r");
3736
4167
  try {
3737
4168
  const buf = Buffer.alloc(4096);
3738
4169
  const { bytesRead } = await handle.read(buf, 0, 4096, 0);
@@ -3797,7 +4228,7 @@ async function executeReadFile(args, ctx) {
3797
4228
  const resolved = path6.isAbsolute(filePath) ? filePath : path6.resolve(filePath);
3798
4229
  let stat;
3799
4230
  try {
3800
- stat = await fs7.stat(resolved);
4231
+ stat = await fs6.stat(resolved);
3801
4232
  } catch {
3802
4233
  if (filePath in ctx.workspaceFiles) {
3803
4234
  return formatFromString(filePath, ctx.workspaceFiles[filePath], offset, limit);
@@ -3805,7 +4236,7 @@ async function executeReadFile(args, ctx) {
3805
4236
  return `Error: File not found: ${args.file_path}`;
3806
4237
  }
3807
4238
  if (stat.isDirectory()) {
3808
- const entries = await fs7.readdir(resolved, { withFileTypes: true });
4239
+ const entries = await fs6.readdir(resolved, { withFileTypes: true });
3809
4240
  const lines = entries.sort((a, b) => {
3810
4241
  if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
3811
4242
  return a.name.localeCompare(b.name);
@@ -3869,7 +4300,7 @@ ${outputLines.join("\n")}${footer}
3869
4300
  }
3870
4301
 
3871
4302
  // src/lib/agent/tools/list-directory.ts
3872
- import fs8 from "fs/promises";
4303
+ import fs7 from "fs/promises";
3873
4304
  import path7 from "path";
3874
4305
  var MAX_ENTRIES = 200;
3875
4306
  var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
@@ -3887,7 +4318,7 @@ var DEFAULT_IGNORE = /* @__PURE__ */ new Set([
3887
4318
  ".env"
3888
4319
  ]);
3889
4320
  async function listEntries(dirPath, ignore) {
3890
- const entries = await fs8.readdir(dirPath, { withFileTypes: true });
4321
+ const entries = await fs7.readdir(dirPath, { withFileTypes: true });
3891
4322
  const results = [];
3892
4323
  for (const entry of entries) {
3893
4324
  if (ignore.has(entry.name) || entry.name.startsWith(".") && DEFAULT_IGNORE.has(entry.name)) {
@@ -3896,7 +4327,7 @@ async function listEntries(dirPath, ignore) {
3896
4327
  let size = 0;
3897
4328
  if (!entry.isDirectory()) {
3898
4329
  try {
3899
- const stat = await fs8.stat(path7.join(dirPath, entry.name));
4330
+ const stat = await fs7.stat(path7.join(dirPath, entry.name));
3900
4331
  size = stat.size;
3901
4332
  } catch {
3902
4333
  }
@@ -3954,7 +4385,7 @@ async function executeListDirectory(args) {
3954
4385
  const maxDepth = Math.min(args.depth ?? 2, 5);
3955
4386
  const ignore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...args.ignore ?? []]);
3956
4387
  try {
3957
- const stat = await fs8.stat(dirPath);
4388
+ const stat = await fs7.stat(dirPath);
3958
4389
  if (!stat.isDirectory()) {
3959
4390
  return `Error: ${dirPath} is not a directory.`;
3960
4391
  }
@@ -3969,7 +4400,7 @@ async function executeListDirectory(args) {
3969
4400
 
3970
4401
  // src/lib/agent/tools/run-command.ts
3971
4402
  import { spawn } from "child_process";
3972
- import fs9 from "fs/promises";
4403
+ import fs8 from "fs/promises";
3973
4404
  import path8 from "path";
3974
4405
  var DEFAULT_TIMEOUT_MS = 2 * 60 * 1e3;
3975
4406
  var MAX_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -3981,7 +4412,7 @@ async function executeRunCommand(args, signal) {
3981
4412
  }
3982
4413
  const workdir = args.workdir ? path8.isAbsolute(args.workdir) ? args.workdir : path8.resolve(args.workdir) : process.cwd();
3983
4414
  try {
3984
- const stat = await fs9.stat(workdir);
4415
+ const stat = await fs8.stat(workdir);
3985
4416
  if (!stat.isDirectory()) {
3986
4417
  return `Error: workdir is not a directory: ${workdir}`;
3987
4418
  }
@@ -4391,7 +4822,7 @@ function executeCreatePaper(args, ctx) {
4391
4822
 
4392
4823
  // src/lib/agent/tools/read-pdf.ts
4393
4824
  import path9 from "path";
4394
- import fs10 from "fs/promises";
4825
+ import fs9 from "fs/promises";
4395
4826
  var MAX_OUTPUT_BYTES3 = 5e4;
4396
4827
  function parsePages(pages) {
4397
4828
  const range = pages.match(/^(\d+)\s*-\s*(\d+)$/);
@@ -4402,7 +4833,7 @@ function parsePages(pages) {
4402
4833
  }
4403
4834
  async function executeReadPdf(args) {
4404
4835
  const resolved = path9.resolve(args.file_path);
4405
- const stat = await fs10.stat(resolved).catch(() => null);
4836
+ const stat = await fs9.stat(resolved).catch(() => null);
4406
4837
  if (!stat || !stat.isFile()) {
4407
4838
  return `Error: file not found: ${resolved}`;
4408
4839
  }
@@ -5589,7 +6020,7 @@ ${summary}`,
5589
6020
  }
5590
6021
 
5591
6022
  // src/lib/skills/runtime.ts
5592
- import fs11 from "fs/promises";
6023
+ import fs10 from "fs/promises";
5593
6024
  import path10 from "path";
5594
6025
  import matter2 from "gray-matter";
5595
6026
  async function loadRuntimeSkillByName(input2) {
@@ -5598,7 +6029,7 @@ async function loadRuntimeSkillByName(input2) {
5598
6029
  if (!match) {
5599
6030
  return null;
5600
6031
  }
5601
- const raw = await fs11.readFile(path10.join(match.skillDir, "SKILL.md"), "utf8");
6032
+ const raw = await fs10.readFile(path10.join(match.skillDir, "SKILL.md"), "utf8");
5602
6033
  const parsed = matter2(raw);
5603
6034
  return {
5604
6035
  id: match.name,
@@ -5610,14 +6041,15 @@ async function loadRuntimeSkillByName(input2) {
5610
6041
  }
5611
6042
  async function readSkillReferenceFile(skillDir, referencePath) {
5612
6043
  const fullPath = path10.join(skillDir, "references", referencePath);
5613
- return fs11.readFile(fullPath, "utf8");
6044
+ return fs10.readFile(fullPath, "utf8");
5614
6045
  }
5615
6046
 
5616
6047
  // src/lib/agent/subagent/configs.ts
5617
6048
  var exploreConfig = {
5618
6049
  id: "explore",
5619
6050
  name: "Explore",
5620
- model: "gpt-5.4-mini",
6051
+ model: void 0,
6052
+ // Resolved at runtime from provider catalog (backgroundModel)
5621
6053
  reasoningEffort: "high",
5622
6054
  allowedTools: /* @__PURE__ */ new Set([
5623
6055
  "read_file",
@@ -5714,7 +6146,7 @@ ${input2.context}` : input2.goal;
5714
6146
  for await (const chunk of input2.provider.callLLMStreaming({
5715
6147
  messages,
5716
6148
  tools,
5717
- model: config.model,
6149
+ model: config.model ?? getProviderCatalog(input2.provider.kind).backgroundModel,
5718
6150
  reasoningEffort: config.reasoningEffort,
5719
6151
  signal: input2.signal
5720
6152
  })) {
@@ -5775,7 +6207,7 @@ ${input2.context}` : input2.goal;
5775
6207
  let finalText = "";
5776
6208
  for await (const chunk of input2.provider.callLLMStreaming({
5777
6209
  messages,
5778
- model: config.model,
6210
+ model: config.model ?? getProviderCatalog(input2.provider.kind).backgroundModel,
5779
6211
  reasoningEffort: config.reasoningEffort,
5780
6212
  signal: input2.signal
5781
6213
  })) {
@@ -5816,7 +6248,7 @@ function describeSubAgentTool(name, args) {
5816
6248
 
5817
6249
  // src/lib/agent/tools/tasks.ts
5818
6250
  import path11 from "path";
5819
- import fs12 from "fs/promises";
6251
+ import fs11 from "fs/promises";
5820
6252
  var tasks = [];
5821
6253
  var storePath = null;
5822
6254
  function shortId() {
@@ -5826,9 +6258,9 @@ async function persist() {
5826
6258
  if (!storePath) return;
5827
6259
  const live = tasks.filter((t) => t.status !== "deleted");
5828
6260
  const tmpPath = storePath + ".tmp";
5829
- await fs12.mkdir(path11.dirname(storePath), { recursive: true });
5830
- await fs12.writeFile(tmpPath, JSON.stringify({ version: 1, tasks: live }, null, 2));
5831
- await fs12.rename(tmpPath, storePath);
6261
+ await fs11.mkdir(path11.dirname(storePath), { recursive: true });
6262
+ await fs11.writeFile(tmpPath, JSON.stringify({ version: 1, tasks: live }, null, 2));
6263
+ await fs11.rename(tmpPath, storePath);
5832
6264
  }
5833
6265
  async function initTaskStore(workspaceDir) {
5834
6266
  storePath = path11.join(workspaceDir, ".open-research", "tasks.json");
@@ -5960,7 +6392,7 @@ async function executeTool(name, args, ctx, activeSkills, homeDir, signal, provi
5960
6392
  return { result: out.result, searchResults: out.sources };
5961
6393
  }
5962
6394
  case "web_search": {
5963
- const { executeWebSearch } = await import("./web-search-B7D5WMHU.js");
6395
+ const { executeWebSearch } = await import("./web-search-TKBFSU3M.js");
5964
6396
  const out = await executeWebSearch(
5965
6397
  args,
5966
6398
  provider
@@ -6039,7 +6471,7 @@ ${meta}` };
6039
6471
  if (!provider) {
6040
6472
  return { result: "Error: query_ontology requires an LLM provider." };
6041
6473
  }
6042
- const { runQueryAgent } = await import("./query-agent-LRUUJR4F.js");
6474
+ const { runQueryAgent } = await import("./query-agent-WM6UNZ37.js");
6043
6475
  const workspaceDir = ctx.workspaceDir ?? process.cwd();
6044
6476
  const answer = await runQueryAgent({
6045
6477
  query: String(args.query ?? ""),
@@ -6101,7 +6533,9 @@ var MODEL_CONTEXT_WINDOWS = {
6101
6533
  "gpt-4o": 128e3,
6102
6534
  "gpt-5.4-mini": 128e3,
6103
6535
  "o3": 2e5,
6104
- "o4-mini": 2e5
6536
+ "o4-mini": 2e5,
6537
+ "gemini-3.1-pro-preview": 1048576,
6538
+ "gemini-3-flash-preview": 1048576
6105
6539
  };
6106
6540
  var DEFAULT_CONTEXT_WINDOW = 128e3;
6107
6541
  var AUTO_COMPACT_TOKEN_LIMIT = 25e4;
@@ -6315,7 +6749,7 @@ async function manualCompact(messages, model, provider, usage, customInstruction
6315
6749
  }
6316
6750
 
6317
6751
  // src/lib/memory/store.ts
6318
- import fs13 from "fs/promises";
6752
+ import fs12 from "fs/promises";
6319
6753
  import path12 from "path";
6320
6754
  function getGlobalMemoryFile(options) {
6321
6755
  return path12.join(getOpenResearchRoot(options), "memory.json");
@@ -6325,7 +6759,7 @@ function getProjectMemoryFile(workspaceDir) {
6325
6759
  }
6326
6760
  async function loadMemoryFile(filePath) {
6327
6761
  try {
6328
- const raw = await fs13.readFile(filePath, "utf8");
6762
+ const raw = await fs12.readFile(filePath, "utf8");
6329
6763
  const store = JSON.parse(raw);
6330
6764
  return store.memories ?? [];
6331
6765
  } catch {
@@ -6333,9 +6767,9 @@ async function loadMemoryFile(filePath) {
6333
6767
  }
6334
6768
  }
6335
6769
  async function saveMemoryFile(filePath, memories) {
6336
- await fs13.mkdir(path12.dirname(filePath), { recursive: true });
6770
+ await fs12.mkdir(path12.dirname(filePath), { recursive: true });
6337
6771
  const store = { version: 2, memories };
6338
- await fs13.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
6772
+ await fs12.writeFile(filePath, JSON.stringify(store, null, 2), "utf8");
6339
6773
  }
6340
6774
  async function loadGlobalMemories(options) {
6341
6775
  const mems = await loadMemoryFile(getGlobalMemoryFile(options));
@@ -6667,18 +7101,18 @@ async function extractAndStoreMemories(input2) {
6667
7101
  }
6668
7102
 
6669
7103
  // src/lib/workspace/agents-md.ts
6670
- import fs14 from "fs/promises";
7104
+ import fs13 from "fs/promises";
6671
7105
  import path13 from "path";
6672
7106
  var AGENTS_MD_FILENAME = "AGENTS.md";
6673
7107
  async function readAgentsMd(workspaceDir) {
6674
7108
  try {
6675
- return await fs14.readFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
7109
+ return await fs13.readFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
6676
7110
  } catch {
6677
7111
  return "";
6678
7112
  }
6679
7113
  }
6680
7114
  async function writeAgentsMd(workspaceDir, content) {
6681
- await fs14.writeFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
7115
+ await fs13.writeFile(path13.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
6682
7116
  }
6683
7117
  var UPDATE_SYSTEM_PROMPT = `You maintain an AGENTS.md file for a research workspace. This file gives future agent sessions instant context about the project \u2014 what it's about, what's been done, key files, and current direction.
6684
7118
 
@@ -6881,7 +7315,7 @@ async function runAgentTurn(input2) {
6881
7315
  const isPlanning = input2.mode === "planning";
6882
7316
  const tools = isPlanning ? getToolsForMode("planning") : TOOL_SCHEMAS;
6883
7317
  const systemPrompt = isPlanning ? buildPlanningSystemPrompt(input2.workspace, activeSkills) : buildSystemPrompt(input2.workspace, activeSkills);
6884
- const model = input2.model ?? "gpt-5.4";
7318
+ const model = input2.model ?? getProviderCatalog(input2.provider.kind).defaultModel;
6885
7319
  const usage = input2.sessionUsage ?? createSessionUsage();
6886
7320
  const allMemories = await loadAllMemories({
6887
7321
  homeDir: input2.homeDir,
@@ -6895,7 +7329,7 @@ async function runAgentTurn(input2) {
6895
7329
  if (input2.workspace.workspaceDir) {
6896
7330
  try {
6897
7331
  const { loadOntology } = await import("./store-LT5EGDOI.js");
6898
- const { runRelevanceAgent } = await import("./relevance-agent-CCN7JGTM.js");
7332
+ const { runRelevanceAgent } = await import("./relevance-agent-H3U6TROD.js");
6899
7333
  const { buildScaffoldingContext } = await import("./scaffolding-MSAICMWV.js");
6900
7334
  const ontology = await loadOntology(input2.workspace.workspaceDir);
6901
7335
  const relevantIds = await runRelevanceAgent({
@@ -6996,7 +7430,7 @@ ${agentsMd}` : null,
6996
7430
  });
6997
7431
  }
6998
7432
  if (input2.workspace.workspaceDir) {
6999
- import("./manager-queue-F4VVZMTE.js").then(({ enqueueOntologyManager }) => {
7433
+ import("./manager-queue-FBAUCAGI.js").then(({ enqueueOntologyManager }) => {
7000
7434
  enqueueOntologyManager({
7001
7435
  userMessage: input2.message,
7002
7436
  agentResponse: fullText,
@@ -7155,7 +7589,7 @@ function classifyUpdateRisk(update) {
7155
7589
  }
7156
7590
 
7157
7591
  // src/lib/workspace/apply-update.ts
7158
- import fs15 from "fs/promises";
7592
+ import fs14 from "fs/promises";
7159
7593
  import path14 from "path";
7160
7594
  function resolveRelativePath(update) {
7161
7595
  if (update.key.startsWith("path:")) {
@@ -7178,8 +7612,8 @@ function resolveRelativePath(update) {
7178
7612
  async function applyProposedUpdate(workspaceDir, update) {
7179
7613
  const relativePath = resolveRelativePath(update);
7180
7614
  const absolutePath = path14.join(workspaceDir, relativePath);
7181
- await fs15.mkdir(path14.dirname(absolutePath), { recursive: true });
7182
- await fs15.writeFile(absolutePath, update.content, "utf8");
7615
+ await fs14.mkdir(path14.dirname(absolutePath), { recursive: true });
7616
+ await fs14.writeFile(absolutePath, update.content, "utf8");
7183
7617
  return absolutePath;
7184
7618
  }
7185
7619
 
@@ -7397,7 +7831,7 @@ function SessionPicker({ sessions, onSelect, onCancel }) {
7397
7831
  }
7398
7832
 
7399
7833
  // src/lib/cli/update-check.ts
7400
- import fs16 from "fs/promises";
7834
+ import fs15 from "fs/promises";
7401
7835
  import path15 from "path";
7402
7836
  import os3 from "os";
7403
7837
  var PACKAGE_NAME = "open-research";
@@ -7405,7 +7839,7 @@ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
7405
7839
  var STATE_FILE = path15.join(os3.homedir(), ".open-research", "update-check.json");
7406
7840
  async function readState() {
7407
7841
  try {
7408
- const raw = await fs16.readFile(STATE_FILE, "utf8");
7842
+ const raw = await fs15.readFile(STATE_FILE, "utf8");
7409
7843
  const parsed = JSON.parse(raw);
7410
7844
  return {
7411
7845
  lastCheck: typeof parsed.lastCheck === "number" ? parsed.lastCheck : 0,
@@ -7417,8 +7851,8 @@ async function readState() {
7417
7851
  }
7418
7852
  }
7419
7853
  async function writeState(state) {
7420
- await fs16.mkdir(path15.dirname(STATE_FILE), { recursive: true });
7421
- await fs16.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
7854
+ await fs15.mkdir(path15.dirname(STATE_FILE), { recursive: true });
7855
+ await fs15.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
7422
7856
  }
7423
7857
  function getCurrentVersion() {
7424
7858
  return getPackageVersion();
@@ -7473,6 +7907,7 @@ async function checkForUpdate() {
7473
7907
  // src/tui/commands.ts
7474
7908
  var SLASH_COMMANDS = [
7475
7909
  { name: "auth", aliases: ["/connect", "/login"], description: "Connect your OpenAI account via browser OAuth", category: "auth" },
7910
+ { name: "auth-gemini", aliases: ["/login-gemini", "/gemini"], description: "Connect your Google account via browser OAuth (Gemini)", category: "auth" },
7476
7911
  { name: "auth-codex", aliases: ["/import-codex"], description: "Import auth from existing Codex CLI", category: "auth" },
7477
7912
  { name: "auth-status", aliases: [], description: "Check auth connection status", category: "auth" },
7478
7913
  { name: "logout", aliases: [], description: "Clear stored auth", category: "auth" },
@@ -8331,7 +8766,7 @@ function useTerminalWidth() {
8331
8766
  import { startTransition } from "react";
8332
8767
 
8333
8768
  // src/lib/workspace/init-agents-md.ts
8334
- import fs17 from "fs/promises";
8769
+ import fs16 from "fs/promises";
8335
8770
  import path16 from "path";
8336
8771
  var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
8337
8772
  "node_modules",
@@ -8371,7 +8806,7 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
8371
8806
  const results = [];
8372
8807
  if (depth > maxDepth) return results;
8373
8808
  try {
8374
- const entries = await fs17.readdir(dir, { withFileTypes: true });
8809
+ const entries = await fs16.readdir(dir, { withFileTypes: true });
8375
8810
  for (const entry of entries) {
8376
8811
  if (IGNORED_DIRS2.has(entry.name)) continue;
8377
8812
  if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
@@ -8382,7 +8817,7 @@ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
8382
8817
  const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
8383
8818
  results.push(...children);
8384
8819
  } else {
8385
- const stat = await fs17.stat(fullPath).catch(() => null);
8820
+ const stat = await fs16.stat(fullPath).catch(() => null);
8386
8821
  results.push({ path: relativePath, size: stat?.size ?? 0, isDir: false });
8387
8822
  }
8388
8823
  }
@@ -8394,7 +8829,7 @@ async function readKeyFiles(dir) {
8394
8829
  const contents = {};
8395
8830
  for (const name of INTERESTING_FILES) {
8396
8831
  try {
8397
- const content = await fs17.readFile(path16.join(dir, name), "utf8");
8832
+ const content = await fs16.readFile(path16.join(dir, name), "utf8");
8398
8833
  contents[name] = content.slice(0, 2e3);
8399
8834
  } catch {
8400
8835
  }
@@ -8480,7 +8915,7 @@ ${scanData.slice(0, 25e3)}`;
8480
8915
 
8481
8916
  // src/lib/preview/server.ts
8482
8917
  import http2 from "http";
8483
- import fs18 from "fs";
8918
+ import fs17 from "fs";
8484
8919
  import path17 from "path";
8485
8920
 
8486
8921
  // src/lib/preview/latex-to-html.ts
@@ -8752,7 +9187,7 @@ function startPreviewServer(texPath) {
8752
9187
  let currentHash = "";
8753
9188
  function getContentHash() {
8754
9189
  try {
8755
- const content = fs18.readFileSync(resolved, "utf8");
9190
+ const content = fs17.readFileSync(resolved, "utf8");
8756
9191
  return `${content.length}-${content.slice(0, 100)}-${content.slice(-100)}`;
8757
9192
  } catch {
8758
9193
  return "error";
@@ -8760,7 +9195,7 @@ function startPreviewServer(texPath) {
8760
9195
  }
8761
9196
  function renderPage() {
8762
9197
  try {
8763
- const latex = fs18.readFileSync(resolved, "utf8");
9198
+ const latex = fs17.readFileSync(resolved, "utf8");
8764
9199
  const htmlContent = latexToHtml(latex);
8765
9200
  currentHash = getContentHash();
8766
9201
  return HTML_TEMPLATE.replace("{{CONTENT}}", htmlContent);
@@ -8807,7 +9242,7 @@ async function executeSlashCommand(cmd, args, ctx) {
8807
9242
  history,
8808
9243
  skills: skills2,
8809
9244
  workspaceFiles,
8810
- sessionId,
9245
+ sessionId: sessionId2,
8811
9246
  sessionTokens,
8812
9247
  agentMode,
8813
9248
  addSystemMessage,
@@ -8846,6 +9281,23 @@ async function executeSlashCommand(cmd, args, ctx) {
8846
9281
  }
8847
9282
  break;
8848
9283
  }
9284
+ case "auth-gemini": {
9285
+ addSystemMessage("Opening browser for Google login...");
9286
+ addSystemMessage("Note: Uses Gemini CLI credentials. Google may restrict third-party use.");
9287
+ setBusy(true);
9288
+ try {
9289
+ const { loginWithGemini } = await import("./gemini-login-EYY3EFH4.js");
9290
+ const result = await loginWithGemini({ homeDir });
9291
+ setAuthStatus("connected");
9292
+ addSystemMessage(`Connected Google account ${result.tokens.email}`);
9293
+ addSystemMessage("Switch to Gemini: /config provider gemini");
9294
+ } catch (err) {
9295
+ addSystemMessage(`Auth failed: ${err instanceof Error ? err.message : String(err)}`);
9296
+ } finally {
9297
+ setBusy(false);
9298
+ }
9299
+ break;
9300
+ }
8849
9301
  case "auth-codex": {
8850
9302
  addSystemMessage("Importing Codex CLI auth...");
8851
9303
  setBusy(true);
@@ -8934,7 +9386,7 @@ async function executeSlashCommand(cmd, args, ctx) {
8934
9386
  addSystemMessage("No workspace. Run /init first.");
8935
9387
  break;
8936
9388
  }
8937
- const { listSessions: listSessions2 } = await import("./sessions-GRES2MUV.js");
9389
+ const { listSessions: listSessions2 } = await import("./sessions-KL4LUGD7.js");
8938
9390
  const foundSessions = await listSessions2(workspacePath);
8939
9391
  if (foundSessions.length === 0) {
8940
9392
  addSystemMessage("No previous sessions found.");
@@ -9544,7 +9996,7 @@ function App({
9544
9996
  const [screen, setScreen] = useState6("main");
9545
9997
  const [resumeSessions, setResumeSessions] = useState6([]);
9546
9998
  const [ctrlCPending, setCtrlCPending] = useState6(false);
9547
- const sessionId = useMemo3(() => crypto.randomUUID(), []);
9999
+ const sessionId2 = useMemo3(() => crypto.randomUUID(), []);
9548
10000
  const deferredMessages = useDeferredValue(messages);
9549
10001
  const deferredPendingUpdates = useDeferredValue(pendingUpdates);
9550
10002
  const activityFrame = useAnimatedFrame(busy);
@@ -9715,7 +10167,7 @@ function App({
9715
10167
  history,
9716
10168
  skills: skills2,
9717
10169
  workspaceFiles,
9718
- sessionId,
10170
+ sessionId: sessionId2,
9719
10171
  sessionTokens,
9720
10172
  agentMode,
9721
10173
  previewRef,
@@ -9743,7 +10195,7 @@ function App({
9743
10195
  if (pendingUpdates.length === 0 || !workspacePath) return;
9744
10196
  const [next, ...rest] = pendingUpdates;
9745
10197
  await applyProposedUpdate(workspacePath, next);
9746
- await appendSessionEvent(workspacePath, sessionId, {
10198
+ await appendSessionEvent(workspacePath, sessionId2, {
9747
10199
  type: "update.accepted",
9748
10200
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9749
10201
  payload: { key: next.key, summary: next.summary }
@@ -9758,7 +10210,7 @@ function App({
9758
10210
  function rejectNextPendingUpdate() {
9759
10211
  if (pendingUpdates.length === 0 || !workspacePath) return;
9760
10212
  const [next, ...rest] = pendingUpdates;
9761
- void appendSessionEvent(workspacePath, sessionId, {
10213
+ void appendSessionEvent(workspacePath, sessionId2, {
9762
10214
  type: "update.rejected",
9763
10215
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9764
10216
  payload: { key: next.key, summary: next.summary }
@@ -9822,7 +10274,7 @@ function App({
9822
10274
  summary: `Research charter: ${charter.researchQuestion}`
9823
10275
  };
9824
10276
  await applyProposedUpdate(workspacePath, charterUpdate);
9825
- await appendSessionEvent(workspacePath, sessionId, {
10277
+ await appendSessionEvent(workspacePath, sessionId2, {
9826
10278
  type: "charter.approved",
9827
10279
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9828
10280
  payload: { charterId: charter.id }
@@ -9835,7 +10287,7 @@ function App({
9835
10287
  }
9836
10288
  function rejectCharter() {
9837
10289
  if (!planningState.charter || !workspacePath) return;
9838
- void appendSessionEvent(workspacePath, sessionId, {
10290
+ void appendSessionEvent(workspacePath, sessionId2, {
9839
10291
  type: "charter.rejected",
9840
10292
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9841
10293
  payload: { charterId: planningState.charter.id }
@@ -10008,7 +10460,7 @@ function App({
10008
10460
  const workspace = await scanWorkspace(workspacePath);
10009
10461
  const workspaceContext = {
10010
10462
  workspaceDir: workspacePath,
10011
- runId: sessionId,
10463
+ runId: sessionId2,
10012
10464
  workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
10013
10465
  availableKeys: workspace.files.map((f) => f.key),
10014
10466
  fileLabels: Object.fromEntries(workspace.files.map((f) => [f.key, f.label]))
@@ -10104,7 +10556,7 @@ function App({
10104
10556
  if (reviewRequired.length > 0) {
10105
10557
  startTransition2(() => setPendingUpdates((c) => [...c, ...reviewRequired]));
10106
10558
  }
10107
- await appendSessionEvent(workspacePath, sessionId, {
10559
+ await appendSessionEvent(workspacePath, sessionId2, {
10108
10560
  type: "chat.turn",
10109
10561
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10110
10562
  payload: {
@@ -10162,7 +10614,7 @@ ${error.stack}` : String(error)}` }
10162
10614
  const workspace = await scanWorkspace(workspacePath);
10163
10615
  const workspaceContext = {
10164
10616
  workspaceDir: workspacePath,
10165
- runId: sessionId,
10617
+ runId: sessionId2,
10166
10618
  workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
10167
10619
  availableKeys: workspace.files.map((f) => f.key),
10168
10620
  fileLabels: Object.fromEntries(workspace.files.map((f) => [f.key, f.label]))
@@ -10202,7 +10654,7 @@ ${error.stack}` : String(error)}` }
10202
10654
  const charter = parseCharterYaml(result.detectedCharter);
10203
10655
  setPlanningState((prev) => ({ ...prev, status: "charter-review", charter }));
10204
10656
  if (workspacePath) {
10205
- await appendSessionEvent(workspacePath, sessionId, {
10657
+ await appendSessionEvent(workspacePath, sessionId2, {
10206
10658
  type: "charter.generated",
10207
10659
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10208
10660
  payload: { charterId: charter.id, researchQuestion: charter.researchQuestion }
@@ -10541,7 +10993,7 @@ skills.command("validate").argument("[nameOrPath]").description("Validate one us
10541
10993
  await ensureOpenResearchConfig();
10542
10994
  const skillDir = nameOrPath ? path19.isAbsolute(nameOrPath) ? nameOrPath : path19.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
10543
10995
  const stat = await import("fs/promises").then(
10544
- (fs19) => fs19.stat(skillDir).catch(() => null)
10996
+ (fs18) => fs18.stat(skillDir).catch(() => null)
10545
10997
  );
10546
10998
  if (!stat) {
10547
10999
  throw new Error(`Skill path not found: ${skillDir}`);