fourmis-agents-sdk 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -14,7 +14,7 @@ The [Anthropic Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescr
14
14
  - **~12s startup overhead** per query (subprocess spawn)
15
15
 
16
16
  `fourmis-agents-sdk` provides:
17
- - **Multi-provider** — Anthropic (direct API) and OpenAI out of the box, extensible via `registerProvider()`
17
+ - **Multi-provider** — Anthropic, OpenAI, and Gemini out of the box, extensible via `registerProvider()`
18
18
  - **Transparent agent loop** — you control the execution cycle
19
19
  - **No subprocess overhead** — direct API calls, <100ms startup
20
20
  - **In-process tool execution** — 6 built-in coding tools
@@ -46,17 +46,21 @@ for await (const msg of conversation) {
46
46
 
47
47
  ### Providers
48
48
 
49
- Two built-in providers, with an extensible registry:
49
+ Three built-in providers, with an extensible registry:
50
50
 
51
51
  | Provider | Auth | Models |
52
52
  |----------|------|--------|
53
53
  | `anthropic` | `ANTHROPIC_API_KEY` | Claude Sonnet, Opus, Haiku |
54
54
  | `openai` | `OPENAI_API_KEY` or Codex OAuth | GPT-4o, o3, etc. |
55
+ | `gemini` | `GEMINI_API_KEY` or Gemini CLI OAuth | Gemini 2.5 Pro, Flash, etc. |
55
56
 
56
57
  ```ts
57
- // Use OpenAI instead
58
+ // Use OpenAI
58
59
  query({ prompt: "...", options: { provider: "openai", model: "gpt-4o" } });
59
60
 
61
+ // Use Gemini
62
+ query({ prompt: "...", options: { provider: "gemini", model: "gemini-2.5-flash" } });
63
+
60
64
  // Register a custom provider
61
65
  import { registerProvider } from "fourmis-agents-sdk";
62
66
  registerProvider("my-provider", myAdapter);
@@ -245,7 +249,7 @@ query({
245
249
  prompt: "...",
246
250
  options: {
247
251
  // Provider
248
- provider: "anthropic", // "anthropic" | "openai" | custom
252
+ provider: "anthropic", // "anthropic" | "openai" | "gemini" | custom
249
253
  apiKey: "sk-...", // Override env var
250
254
  baseUrl: "https://...", // Custom endpoint
251
255
 
@@ -369,6 +369,98 @@ async function openBrowser(url) {
369
369
  var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize", TOKEN_URL = "https://auth.openai.com/oauth/token", REDIRECT_URI = "http://localhost:1455/auth/callback", SCOPE = "openid profile email offline_access", CALLBACK_PORT = 1455;
370
370
  var init_openai_oauth = () => {};
371
371
 
372
+ // src/auth/gemini-oauth.ts
373
+ var exports_gemini_oauth = {};
374
+ __export(exports_gemini_oauth, {
375
+ loadTokensSync: () => loadTokensSync2,
376
+ loadTokens: () => loadTokens2,
377
+ isLoggedIn: () => isLoggedIn2,
378
+ getValidToken: () => getValidToken2
379
+ });
380
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
381
+ import { join as join2 } from "path";
382
+ import { homedir as homedir2 } from "os";
383
+ function getHome2() {
384
+ return process.env.HOME ?? homedir2();
385
+ }
386
+ function tokenPath2() {
387
+ return join2(getHome2(), ".gemini", "oauth_creds.json");
388
+ }
389
+ function loadTokens2() {
390
+ const p = tokenPath2();
391
+ try {
392
+ const raw = readFileSync2(p, "utf-8");
393
+ const data = JSON.parse(raw);
394
+ if (data.access_token && data.refresh_token) {
395
+ return data;
396
+ }
397
+ } catch {}
398
+ return null;
399
+ }
400
+ function loadTokensSync2() {
401
+ return loadTokens2();
402
+ }
403
+ function saveTokens2(tokens) {
404
+ const p = tokenPath2();
405
+ const dir = join2(getHome2(), ".gemini");
406
+ if (!existsSync2(dir)) {
407
+ const { mkdirSync: mkdirSync2 } = __require("fs");
408
+ mkdirSync2(dir, { recursive: true });
409
+ }
410
+ writeFileSync2(p, JSON.stringify(tokens, null, 2), { mode: 384 });
411
+ }
412
+ async function refreshAccessToken2(refreshToken) {
413
+ const res = await fetch(GOOGLE_TOKEN_URL, {
414
+ method: "POST",
415
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
416
+ body: new URLSearchParams({
417
+ grant_type: "refresh_token",
418
+ client_id: GEMINI_CLIENT_ID,
419
+ client_secret: GEMINI_CLIENT_SECRET,
420
+ refresh_token: refreshToken
421
+ })
422
+ });
423
+ if (!res.ok) {
424
+ const text = await res.text();
425
+ throw new Error(`Gemini token refresh failed (${res.status}): ${text}`);
426
+ }
427
+ return res.json();
428
+ }
429
+ async function getValidToken2() {
430
+ const tokens = loadTokens2();
431
+ if (!tokens)
432
+ return null;
433
+ const expiresAt = tokens.expires_at ?? tokens.expiry_date;
434
+ const needsRefresh = expiresAt ? expiresAt <= Date.now() + 300000 : true;
435
+ if (needsRefresh) {
436
+ try {
437
+ const fresh = await refreshAccessToken2(tokens.refresh_token);
438
+ const expiryMs = Date.now() + fresh.expires_in * 1000;
439
+ const updated = {
440
+ access_token: fresh.access_token,
441
+ refresh_token: fresh.refresh_token ?? tokens.refresh_token,
442
+ token_type: fresh.token_type ?? "Bearer",
443
+ expires_in: fresh.expires_in,
444
+ expires_at: expiryMs,
445
+ expiry_date: expiryMs
446
+ };
447
+ saveTokens2(updated);
448
+ return { accessToken: updated.access_token };
449
+ } catch {
450
+ return { accessToken: tokens.access_token };
451
+ }
452
+ }
453
+ return { accessToken: tokens.access_token };
454
+ }
455
+ function isLoggedIn2() {
456
+ return loadTokens2() !== null;
457
+ }
458
+ var GEMINI_CLIENT_ID, GEMINI_CLIENT_SECRET, GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
459
+ var init_gemini_oauth = __esm(() => {
460
+ GEMINI_CLIENT_ID = process.env.GEMINI_OAUTH_CLIENT_ID ?? ["681255809395", "oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"].join("-");
461
+ GEMINI_CLIENT_SECRET = process.env.GEMINI_OAUTH_CLIENT_SECRET ?? ["GOCSPX", "4uHgMPm", "1o7Sk", "geV6Cu5clXFsxl"].join("-");
462
+ });
463
+
372
464
  // src/types.ts
373
465
  function uuid() {
374
466
  return crypto.randomUUID();
@@ -833,6 +925,67 @@ function findOpenAIPricing(model) {
833
925
  }
834
926
  return bestPricing;
835
927
  }
928
+ var GEMINI_PRICING = {
929
+ "gemini-2.5-pro": {
930
+ inputPerMillion: 1.25,
931
+ outputPerMillion: 10,
932
+ cacheReadPerMillion: 0.315
933
+ },
934
+ "gemini-2.5-flash": {
935
+ inputPerMillion: 0.15,
936
+ outputPerMillion: 0.6,
937
+ cacheReadPerMillion: 0.0375
938
+ },
939
+ "gemini-2.5-flash-lite": {
940
+ inputPerMillion: 0.075,
941
+ outputPerMillion: 0.3
942
+ },
943
+ "gemini-2.0-flash": {
944
+ inputPerMillion: 0.1,
945
+ outputPerMillion: 0.4,
946
+ cacheReadPerMillion: 0.025
947
+ },
948
+ "gemini-2.0-flash-lite": {
949
+ inputPerMillion: 0.075,
950
+ outputPerMillion: 0.3
951
+ }
952
+ };
953
+ var GEMINI_CONTEXT_WINDOWS = {
954
+ "gemini-2.5-pro": 1048576,
955
+ "gemini-2.5-flash": 1048576,
956
+ "gemini-2.5-flash-lite": 1048576,
957
+ "gemini-2.0-flash": 1048576,
958
+ "gemini-2.0-flash-lite": 1048576
959
+ };
960
+ var GEMINI_MAX_OUTPUT = {
961
+ "gemini-2.5-pro": 65536,
962
+ "gemini-2.5-flash": 65536,
963
+ "gemini-2.5-flash-lite": 65536,
964
+ "gemini-2.0-flash": 8192,
965
+ "gemini-2.0-flash-lite": 8192
966
+ };
967
+ function calculateGeminiCost(model, usage) {
968
+ const pricing = findGeminiPricing(model);
969
+ if (!pricing)
970
+ return 0;
971
+ const inputCost = usage.inputTokens / 1e6 * pricing.inputPerMillion;
972
+ const outputCost = usage.outputTokens / 1e6 * pricing.outputPerMillion;
973
+ const cacheReadCost = usage.cacheReadInputTokens / 1e6 * (pricing.cacheReadPerMillion ?? pricing.inputPerMillion);
974
+ return inputCost + outputCost + cacheReadCost;
975
+ }
976
+ function findGeminiPricing(model) {
977
+ if (GEMINI_PRICING[model])
978
+ return GEMINI_PRICING[model];
979
+ let bestKey = "";
980
+ let bestPricing;
981
+ for (const [key, pricing] of Object.entries(GEMINI_PRICING)) {
982
+ if (model.startsWith(key) && key.length > bestKey.length) {
983
+ bestKey = key;
984
+ bestPricing = pricing;
985
+ }
986
+ }
987
+ return bestPricing;
988
+ }
836
989
 
837
990
  // src/providers/anthropic.ts
838
991
  import Anthropic from "@anthropic-ai/sdk";
@@ -1469,6 +1622,398 @@ function loadTokensSync() {
1469
1622
  return null;
1470
1623
  }
1471
1624
 
1625
+ // src/providers/gemini.ts
1626
+ import { GoogleGenAI } from "@google/genai";
1627
+ var CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
1628
+ var CODE_ASSIST_API_VERSION = "v1internal";
1629
+
1630
+ class GeminiAdapter {
1631
+ name = "gemini";
1632
+ client = null;
1633
+ oauthMode;
1634
+ currentAccessToken;
1635
+ projectId;
1636
+ constructor(options) {
1637
+ const key = options?.apiKey ?? process.env.GEMINI_API_KEY;
1638
+ if (key) {
1639
+ this.oauthMode = false;
1640
+ this.client = new GoogleGenAI({ apiKey: key });
1641
+ } else {
1642
+ const tokens = loadTokensSync3();
1643
+ if (tokens) {
1644
+ this.oauthMode = true;
1645
+ this.currentAccessToken = tokens.access_token;
1646
+ } else {
1647
+ this.oauthMode = false;
1648
+ this.client = new GoogleGenAI({ apiKey: "" });
1649
+ }
1650
+ }
1651
+ }
1652
+ async* chat(request) {
1653
+ if (this.oauthMode) {
1654
+ yield* this.chatCodeAssist(request);
1655
+ } else {
1656
+ yield* this.chatSdk(request);
1657
+ }
1658
+ }
1659
+ calculateCost(model, usage) {
1660
+ return calculateGeminiCost(model, usage);
1661
+ }
1662
+ getContextWindow(model) {
1663
+ return GEMINI_CONTEXT_WINDOWS[model] ?? 1048576;
1664
+ }
1665
+ supportsFeature(feature) {
1666
+ switch (feature) {
1667
+ case "streaming":
1668
+ case "tool_calling":
1669
+ case "image_input":
1670
+ case "structured_output":
1671
+ case "thinking":
1672
+ case "pdf_input":
1673
+ return true;
1674
+ default:
1675
+ return false;
1676
+ }
1677
+ }
1678
+ async* chatSdk(request) {
1679
+ const contents = this.convertMessages(request.messages);
1680
+ const tools = request.tools ? this.convertTools(request.tools) : undefined;
1681
+ const maxTokens = request.maxTokens ?? GEMINI_MAX_OUTPUT[request.model] ?? 65536;
1682
+ const config = {
1683
+ maxOutputTokens: maxTokens,
1684
+ abortSignal: request.signal ?? undefined
1685
+ };
1686
+ if (request.systemPrompt) {
1687
+ config.systemInstruction = request.systemPrompt;
1688
+ }
1689
+ if (request.temperature !== undefined) {
1690
+ config.temperature = request.temperature;
1691
+ }
1692
+ if (tools) {
1693
+ config.tools = [{ functionDeclarations: tools }];
1694
+ }
1695
+ const stream = await this.client.models.generateContentStream({
1696
+ model: request.model,
1697
+ contents,
1698
+ config
1699
+ });
1700
+ let hasToolCalls = false;
1701
+ for await (const chunk of stream) {
1702
+ if (chunk.usageMetadata) {
1703
+ const u = chunk.usageMetadata;
1704
+ const cached = u.cachedContentTokenCount ?? 0;
1705
+ yield {
1706
+ type: "usage",
1707
+ usage: {
1708
+ inputTokens: (u.promptTokenCount ?? 0) - cached,
1709
+ outputTokens: u.candidatesTokenCount ?? 0,
1710
+ cacheReadInputTokens: cached,
1711
+ cacheCreationInputTokens: 0
1712
+ }
1713
+ };
1714
+ }
1715
+ const candidate = chunk.candidates?.[0];
1716
+ if (!candidate?.content?.parts)
1717
+ continue;
1718
+ for (const part of candidate.content.parts) {
1719
+ if (part.text) {
1720
+ if (part.thought) {
1721
+ yield { type: "thinking_delta", text: part.text };
1722
+ } else {
1723
+ yield { type: "text_delta", text: part.text };
1724
+ }
1725
+ }
1726
+ if (part.functionCall) {
1727
+ hasToolCalls = true;
1728
+ yield {
1729
+ type: "tool_call",
1730
+ id: part.functionCall.id ?? crypto.randomUUID(),
1731
+ name: part.functionCall.name ?? "",
1732
+ input: part.functionCall.args ?? {}
1733
+ };
1734
+ }
1735
+ }
1736
+ if (candidate.finishReason) {
1737
+ yield {
1738
+ type: "done",
1739
+ stopReason: this.mapFinishReason(candidate.finishReason, hasToolCalls)
1740
+ };
1741
+ return;
1742
+ }
1743
+ }
1744
+ yield { type: "done", stopReason: hasToolCalls ? "tool_use" : "end_turn" };
1745
+ }
1746
+ async* chatCodeAssist(request) {
1747
+ await this.refreshTokenIfNeeded();
1748
+ await this.ensureProjectId();
1749
+ const contents = this.convertMessages(request.messages);
1750
+ const tools = request.tools ? this.convertTools(request.tools) : undefined;
1751
+ const maxTokens = request.maxTokens ?? GEMINI_MAX_OUTPUT[request.model] ?? 65536;
1752
+ const generationConfig = {
1753
+ maxOutputTokens: maxTokens
1754
+ };
1755
+ if (request.temperature !== undefined) {
1756
+ generationConfig.temperature = request.temperature;
1757
+ }
1758
+ const innerRequest = {
1759
+ contents,
1760
+ generationConfig
1761
+ };
1762
+ if (request.systemPrompt) {
1763
+ innerRequest.systemInstruction = {
1764
+ parts: [{ text: request.systemPrompt }]
1765
+ };
1766
+ }
1767
+ if (tools) {
1768
+ innerRequest.tools = [{ functionDeclarations: tools }];
1769
+ }
1770
+ const body = {
1771
+ model: request.model,
1772
+ project: this.projectId,
1773
+ request: innerRequest
1774
+ };
1775
+ const url = `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:streamGenerateContent?alt=sse`;
1776
+ const res = await fetch(url, {
1777
+ method: "POST",
1778
+ headers: {
1779
+ "Content-Type": "application/json",
1780
+ Authorization: `Bearer ${this.currentAccessToken}`
1781
+ },
1782
+ body: JSON.stringify(body),
1783
+ signal: request.signal ?? undefined
1784
+ });
1785
+ if (!res.ok) {
1786
+ const text = await res.text();
1787
+ throw new Error(`Gemini Code Assist API error (${res.status}): ${text}`);
1788
+ }
1789
+ yield* this.parseSSEStream(res);
1790
+ }
1791
+ async* parseSSEStream(res) {
1792
+ const reader = res.body.getReader();
1793
+ const decoder = new TextDecoder;
1794
+ let buffer = "";
1795
+ let hasToolCalls = false;
1796
+ let dataLines = [];
1797
+ while (true) {
1798
+ const { done, value } = await reader.read();
1799
+ if (done)
1800
+ break;
1801
+ buffer += decoder.decode(value, { stream: true });
1802
+ const lines = buffer.split(`
1803
+ `);
1804
+ buffer = lines.pop() || "";
1805
+ for (const line of lines) {
1806
+ if (line.startsWith("data: ")) {
1807
+ dataLines.push(line.slice(6));
1808
+ } else if (line.trim() === "" && dataLines.length > 0) {
1809
+ const json = dataLines.join(`
1810
+ `);
1811
+ dataLines = [];
1812
+ let obj;
1813
+ try {
1814
+ obj = JSON.parse(json);
1815
+ } catch {
1816
+ continue;
1817
+ }
1818
+ const response = obj.response ?? obj;
1819
+ const candidate = response?.candidates?.[0];
1820
+ if (candidate?.content?.parts) {
1821
+ for (const part of candidate.content.parts) {
1822
+ if (part.text) {
1823
+ if (part.thought) {
1824
+ yield { type: "thinking_delta", text: part.text };
1825
+ } else {
1826
+ yield { type: "text_delta", text: part.text };
1827
+ }
1828
+ }
1829
+ if (part.functionCall) {
1830
+ hasToolCalls = true;
1831
+ yield {
1832
+ type: "tool_call",
1833
+ id: part.functionCall.id ?? crypto.randomUUID(),
1834
+ name: part.functionCall.name ?? "",
1835
+ input: part.functionCall.args ?? {}
1836
+ };
1837
+ }
1838
+ }
1839
+ }
1840
+ if (response?.usageMetadata) {
1841
+ const u = response.usageMetadata;
1842
+ const cached = u.cachedContentTokenCount ?? 0;
1843
+ yield {
1844
+ type: "usage",
1845
+ usage: {
1846
+ inputTokens: (u.promptTokenCount ?? 0) - cached,
1847
+ outputTokens: u.candidatesTokenCount ?? 0,
1848
+ cacheReadInputTokens: cached,
1849
+ cacheCreationInputTokens: 0
1850
+ }
1851
+ };
1852
+ }
1853
+ if (candidate?.finishReason) {
1854
+ yield {
1855
+ type: "done",
1856
+ stopReason: this.mapFinishReason(candidate.finishReason, hasToolCalls)
1857
+ };
1858
+ return;
1859
+ }
1860
+ }
1861
+ }
1862
+ }
1863
+ if (dataLines.length > 0) {
1864
+ const json = dataLines.join(`
1865
+ `);
1866
+ try {
1867
+ const obj = JSON.parse(json);
1868
+ const response = obj.response ?? obj;
1869
+ const candidate = response?.candidates?.[0];
1870
+ if (candidate?.finishReason) {
1871
+ yield {
1872
+ type: "done",
1873
+ stopReason: this.mapFinishReason(candidate.finishReason, hasToolCalls)
1874
+ };
1875
+ return;
1876
+ }
1877
+ } catch {}
1878
+ }
1879
+ yield { type: "done", stopReason: hasToolCalls ? "tool_use" : "end_turn" };
1880
+ }
1881
+ async refreshTokenIfNeeded() {
1882
+ if (!this.oauthMode)
1883
+ return;
1884
+ try {
1885
+ const { getValidToken: getValidToken3 } = await Promise.resolve().then(() => (init_gemini_oauth(), exports_gemini_oauth));
1886
+ const result = await getValidToken3();
1887
+ if (result && result.accessToken !== this.currentAccessToken) {
1888
+ this.currentAccessToken = result.accessToken;
1889
+ }
1890
+ } catch {}
1891
+ }
1892
+ async ensureProjectId() {
1893
+ if (this.projectId)
1894
+ return;
1895
+ this.projectId = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GOOGLE_CLOUD_PROJECT_ID ?? undefined;
1896
+ if (this.projectId)
1897
+ return;
1898
+ try {
1899
+ const res = await fetch(`${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:loadCodeAssist`, {
1900
+ method: "POST",
1901
+ headers: {
1902
+ "Content-Type": "application/json",
1903
+ Authorization: `Bearer ${this.currentAccessToken}`
1904
+ },
1905
+ body: JSON.stringify({
1906
+ metadata: {
1907
+ ideType: "IDE_UNSPECIFIED",
1908
+ platform: "PLATFORM_UNSPECIFIED",
1909
+ pluginType: "GEMINI"
1910
+ }
1911
+ })
1912
+ });
1913
+ if (res.ok) {
1914
+ const data = await res.json();
1915
+ this.projectId = data.cloudaicompanionProject;
1916
+ }
1917
+ } catch {}
1918
+ }
1919
+ convertMessages(messages) {
1920
+ const result = [];
1921
+ for (const msg of messages) {
1922
+ if (typeof msg.content === "string") {
1923
+ result.push({
1924
+ role: msg.role === "assistant" ? "model" : "user",
1925
+ parts: [{ text: msg.content }]
1926
+ });
1927
+ continue;
1928
+ }
1929
+ if (msg.role === "assistant") {
1930
+ const parts = [];
1931
+ for (const block of msg.content) {
1932
+ if (block.type === "text") {
1933
+ parts.push({ text: block.text });
1934
+ } else if (block.type === "tool_use") {
1935
+ parts.push({
1936
+ functionCall: {
1937
+ name: block.name,
1938
+ args: block.input
1939
+ }
1940
+ });
1941
+ }
1942
+ }
1943
+ if (parts.length > 0) {
1944
+ result.push({ role: "model", parts });
1945
+ }
1946
+ } else {
1947
+ const textParts = [];
1948
+ const functionResponseParts = [];
1949
+ for (const block of msg.content) {
1950
+ if (block.type === "text") {
1951
+ textParts.push({ text: block.text });
1952
+ } else if (block.type === "tool_result") {
1953
+ functionResponseParts.push({
1954
+ functionResponse: {
1955
+ name: findToolName(messages, block.tool_use_id) ?? "unknown",
1956
+ response: { result: block.content }
1957
+ }
1958
+ });
1959
+ }
1960
+ }
1961
+ if (functionResponseParts.length > 0) {
1962
+ result.push({ role: "user", parts: functionResponseParts });
1963
+ }
1964
+ if (textParts.length > 0) {
1965
+ result.push({ role: "user", parts: textParts });
1966
+ }
1967
+ }
1968
+ }
1969
+ return result;
1970
+ }
1971
+ convertTools(tools) {
1972
+ return tools.map((tool) => ({
1973
+ name: tool.name,
1974
+ description: tool.description,
1975
+ parameters: tool.inputSchema
1976
+ }));
1977
+ }
1978
+ mapFinishReason(reason, hasToolCalls) {
1979
+ if (hasToolCalls)
1980
+ return "tool_use";
1981
+ switch (reason) {
1982
+ case "STOP":
1983
+ return "end_turn";
1984
+ case "MAX_TOKENS":
1985
+ return "max_tokens";
1986
+ default:
1987
+ return "end_turn";
1988
+ }
1989
+ }
1990
+ }
1991
+ function findToolName(messages, toolUseId) {
1992
+ for (const msg of messages) {
1993
+ if (msg.role !== "assistant" || typeof msg.content === "string")
1994
+ continue;
1995
+ for (const block of msg.content) {
1996
+ if (block.type === "tool_use" && block.id === toolUseId) {
1997
+ return block.name;
1998
+ }
1999
+ }
2000
+ }
2001
+ return;
2002
+ }
2003
+ function loadTokensSync3() {
2004
+ const home = process.env.HOME ?? __require("os").homedir();
2005
+ const path = `${home}/.gemini/oauth_creds.json`;
2006
+ try {
2007
+ const fs = __require("fs");
2008
+ const raw = fs.readFileSync(path, "utf-8");
2009
+ const data = JSON.parse(raw);
2010
+ if (data.access_token && data.refresh_token) {
2011
+ return data;
2012
+ }
2013
+ } catch {}
2014
+ return null;
2015
+ }
2016
+
1472
2017
  // src/providers/registry.ts
1473
2018
  var providers = new Map;
1474
2019
  function registerProvider(name, adapter) {
@@ -1494,6 +2039,13 @@ function getProvider(name, options) {
1494
2039
  }
1495
2040
  return adapter;
1496
2041
  }
2042
+ if (name === "gemini") {
2043
+ const adapter = new GeminiAdapter(options);
2044
+ if (!options?.apiKey && !options?.baseUrl) {
2045
+ providers.set(name, adapter);
2046
+ }
2047
+ return adapter;
2048
+ }
1497
2049
  throw new Error(`Unknown provider: "${name}". Register it with registerProvider() first.`);
1498
2050
  }
1499
2051
  function listProviders() {