@gemdoq/codi 0.1.3 → 0.1.5

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
@@ -778,6 +778,19 @@ function completer(line) {
778
778
  }
779
779
 
780
780
  // src/repl.ts
781
+ import { readFileSync as readFileSync3 } from "fs";
782
+ import { fileURLToPath as fileURLToPath2 } from "url";
783
+ import * as path5 from "path";
784
+ var __filename2 = fileURLToPath2(import.meta.url);
785
+ var __dirname2 = path5.dirname(__filename2);
786
+ function getVersion() {
787
+ try {
788
+ const pkg = JSON.parse(readFileSync3(path5.join(__dirname2, "..", "package.json"), "utf-8"));
789
+ return `v${pkg.version}`;
790
+ } catch {
791
+ return "v0.1.4";
792
+ }
793
+ }
781
794
  var Repl = class {
782
795
  rl = null;
783
796
  keyBindings = new KeyBindingManager();
@@ -810,7 +823,7 @@ var Repl = class {
810
823
  completer: (line) => completer(line),
811
824
  terminal: true
812
825
  });
813
- if (process.stdin.isTTY) {
826
+ if (process.stdin.isTTY && os2.platform() !== "win32") {
814
827
  process.stdout.write("\x1B[?2004h");
815
828
  }
816
829
  this.printWelcome();
@@ -877,7 +890,7 @@ var Repl = class {
877
890
  }
878
891
  }
879
892
  }
880
- if (process.stdin.isTTY) {
893
+ if (process.stdin.isTTY && os2.platform() !== "win32") {
881
894
  process.stdout.write("\x1B[?2004l");
882
895
  }
883
896
  }
@@ -916,19 +929,38 @@ var Repl = class {
916
929
  return;
917
930
  }
918
931
  let message = input3;
919
- const atMatches = input3.match(/@([\w./-]+)/g);
932
+ const IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"]);
933
+ const atMatches = input3.match(/@([\w.\/\\:~-]+)/g);
920
934
  if (atMatches) {
921
935
  for (const match of atMatches) {
922
936
  const filePath = match.slice(1);
923
937
  try {
924
- const { readFileSync: readFileSync13 } = await import("fs");
925
- const content = readFileSync13(filePath, "utf-8");
926
- message = message.replace(match, `
938
+ const ext = path5.extname(filePath).toLowerCase();
939
+ if (IMAGE_EXTS.has(ext)) {
940
+ const data = readFileSync3(filePath);
941
+ const base64 = data.toString("base64");
942
+ const mimeMap = {
943
+ ".png": "image/png",
944
+ ".jpg": "image/jpeg",
945
+ ".jpeg": "image/jpeg",
946
+ ".gif": "image/gif",
947
+ ".webp": "image/webp",
948
+ ".bmp": "image/bmp",
949
+ ".svg": "image/svg+xml"
950
+ };
951
+ const mime = mimeMap[ext] || "image/png";
952
+ message = message.replace(match, `
953
+ [Image: ${filePath}](data:${mime};base64,${base64})
954
+ `);
955
+ } else {
956
+ const content = readFileSync3(filePath, "utf-8");
957
+ message = message.replace(match, `
927
958
  [File: ${filePath}]
928
959
  \`\`\`
929
960
  ${content}
930
961
  \`\`\`
931
962
  `);
963
+ }
932
964
  } catch {
933
965
  }
934
966
  }
@@ -961,7 +993,8 @@ ${content}
961
993
  printWelcome() {
962
994
  console.log("");
963
995
  console.log(chalk4.cyan.bold(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
964
- console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(" Codi (\uCF54\uB514) v0.1.0 ") + chalk4.cyan.bold("\u2502"));
996
+ const versionPad = ` Codi (\uCF54\uB514) ${getVersion()}`.padEnd(29);
997
+ console.log(chalk4.cyan.bold(" \u2502") + chalk4.white.bold(versionPad) + chalk4.cyan.bold("\u2502"));
965
998
  console.log(chalk4.cyan.bold(" \u2502") + chalk4.dim(" AI Code Agent for Terminal ") + chalk4.cyan.bold("\u2502"));
966
999
  console.log(chalk4.cyan.bold(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
967
1000
  console.log("");
@@ -1456,7 +1489,56 @@ function sleep(ms) {
1456
1489
  init_esm_shims();
1457
1490
  import * as os3 from "os";
1458
1491
  import { execSync as execSync2 } from "child_process";
1459
- var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.`;
1492
+ var ROLE_DEFINITION = `You are Codi (\uCF54\uB514), a terminal-based AI coding agent. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have access to tools for file manipulation, code search, shell execution, and more.
1493
+
1494
+ # How Users Interact with You
1495
+ - Users type natural language messages to you. They do NOT type tool calls directly.
1496
+ - Tools (read_file, bash, grep, etc.) are for YOU to use internally. NEVER tell users to type tool calls like "read_file(path)" or "bash(command)".
1497
+ - When users ask "how should I do X?" or "what should I type?", give them natural language prompts they can type to you, NOT tool call syntax.
1498
+ - When users ask a QUESTION about how to do something, ANSWER with an explanation. Do NOT immediately execute actions.
1499
+ - Only execute actions when the user clearly REQUESTS you to do something (e.g., "clone this repo", "analyze this code", "fix this bug").
1500
+
1501
+ # Codi CLI Features (you must know these)
1502
+ Users can start Codi with these command-line options:
1503
+ - codi --yolo : Skip ALL permission checks (like Claude Code's --dangerously-skip-permissions)
1504
+ - codi --plan : Start in read-only plan mode (analysis only, no changes)
1505
+ - codi -p "prompt" : Run a single prompt and exit
1506
+ - codi -c / --continue : Continue the last session
1507
+ - codi -r <id> / --resume <id> : Resume a specific session
1508
+ - codi -m <model> : Switch to a different model
1509
+ - codi --provider <name> : Switch provider (openai, anthropic, ollama)
1510
+
1511
+ # Slash Commands (available inside Codi)
1512
+ Users can type these commands while using Codi:
1513
+ - /help : Show all available commands
1514
+ - /quit or /exit : Exit Codi
1515
+ - /clear : Clear conversation history
1516
+ - /model <name> : Switch model (e.g., /model gpt-4o)
1517
+ - /compact : Compress conversation to save context
1518
+ - /cost : Show token usage and cost
1519
+ - /plan : Toggle plan mode (read-only analysis)
1520
+ - /commit : Generate commit message from git diff and commit
1521
+ - /review : AI code review of current changes
1522
+ - /fix <command> : Run command, auto-fix if it fails
1523
+ - /search <keyword> : Search past sessions
1524
+ - /save : Save current session
1525
+ - /resume : Resume a saved session
1526
+ - /memory : Show auto memory
1527
+ - /tasks : Show task list
1528
+ - /context : Show context window usage
1529
+ - /rewind : Undo to previous checkpoint
1530
+ - /diff : Show git diff
1531
+
1532
+ # Input Prefixes
1533
+ - ! command : Execute a shell command directly (e.g., ! git status)
1534
+ - @file.ts : Attach file content to your message
1535
+ - \\ at end of line : Continue typing on next line (multiline input)`;
1536
+ var CONVERSATION_RULES = `# Conversation Rules
1537
+ - When a user asks "how do I..." or "what should I type...", give a clear EXPLANATION with example prompts they can type.
1538
+ - Do NOT execute commands or use tools when the user is just asking for information.
1539
+ - When giving examples of what to type, show them as natural language, e.g.: "You can type: \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uAD6C\uC870\uB97C \uBD84\uC11D\uD574\uC918"
1540
+ - Only use tools when the user explicitly requests an action.
1541
+ - If the user's intent is ambiguous, ASK for clarification before acting.`;
1460
1542
  var TOOL_HIERARCHY = `# Tool Usage Rules
1461
1543
  - Use read_file instead of bash cat/head/tail
1462
1544
  - Use edit_file instead of bash sed/awk
@@ -1515,6 +1597,7 @@ function buildSystemPrompt(context) {
1515
1597
  const fragments = [];
1516
1598
  fragments.push(ROLE_DEFINITION);
1517
1599
  fragments.push(buildEnvironmentInfo(context));
1600
+ fragments.push(CONVERSATION_RULES);
1518
1601
  fragments.push(TOOL_HIERARCHY);
1519
1602
  if (os3.platform() === "win32") {
1520
1603
  fragments.push(WINDOWS_RULES);
@@ -1619,15 +1702,15 @@ ${summaryContent}`;
1619
1702
  // src/agent/memory.ts
1620
1703
  init_esm_shims();
1621
1704
  import * as fs4 from "fs";
1622
- import * as path5 from "path";
1705
+ import * as path6 from "path";
1623
1706
  import * as crypto from "crypto";
1624
1707
  var MemoryManager = class {
1625
1708
  memoryDir;
1626
1709
  constructor() {
1627
1710
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1628
1711
  const projectHash = crypto.createHash("md5").update(process.cwd()).digest("hex").slice(0, 8);
1629
- const projectName = path5.basename(process.cwd());
1630
- this.memoryDir = path5.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
1712
+ const projectName = path6.basename(process.cwd());
1713
+ this.memoryDir = path6.join(home, ".codi", "projects", `${projectName}-${projectHash}`, "memory");
1631
1714
  }
1632
1715
  ensureDir() {
1633
1716
  if (!fs4.existsSync(this.memoryDir)) {
@@ -1638,7 +1721,7 @@ var MemoryManager = class {
1638
1721
  return this.memoryDir;
1639
1722
  }
1640
1723
  loadIndex() {
1641
- const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1724
+ const indexPath = path6.join(this.memoryDir, "MEMORY.md");
1642
1725
  if (!fs4.existsSync(indexPath)) return "";
1643
1726
  const content = fs4.readFileSync(indexPath, "utf-8");
1644
1727
  const lines = content.split("\n");
@@ -1646,17 +1729,17 @@ var MemoryManager = class {
1646
1729
  }
1647
1730
  saveIndex(content) {
1648
1731
  this.ensureDir();
1649
- const indexPath = path5.join(this.memoryDir, "MEMORY.md");
1732
+ const indexPath = path6.join(this.memoryDir, "MEMORY.md");
1650
1733
  fs4.writeFileSync(indexPath, content, "utf-8");
1651
1734
  }
1652
1735
  loadTopic(name) {
1653
- const topicPath = path5.join(this.memoryDir, `${name}.md`);
1736
+ const topicPath = path6.join(this.memoryDir, `${name}.md`);
1654
1737
  if (!fs4.existsSync(topicPath)) return null;
1655
1738
  return fs4.readFileSync(topicPath, "utf-8");
1656
1739
  }
1657
1740
  saveTopic(name, content) {
1658
1741
  this.ensureDir();
1659
- const topicPath = path5.join(this.memoryDir, `${name}.md`);
1742
+ const topicPath = path6.join(this.memoryDir, `${name}.md`);
1660
1743
  fs4.writeFileSync(topicPath, content, "utf-8");
1661
1744
  }
1662
1745
  listTopics() {
@@ -1681,13 +1764,13 @@ var memoryManager = new MemoryManager();
1681
1764
  // src/agent/session.ts
1682
1765
  init_esm_shims();
1683
1766
  import * as fs5 from "fs";
1684
- import * as path6 from "path";
1767
+ import * as path7 from "path";
1685
1768
  import * as crypto2 from "crypto";
1686
1769
  var SessionManager = class {
1687
1770
  sessionsDir;
1688
1771
  constructor() {
1689
1772
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1690
- this.sessionsDir = path6.join(home, ".codi", "sessions");
1773
+ this.sessionsDir = path7.join(home, ".codi", "sessions");
1691
1774
  }
1692
1775
  ensureDir() {
1693
1776
  if (!fs5.existsSync(this.sessionsDir)) {
@@ -1697,7 +1780,7 @@ var SessionManager = class {
1697
1780
  save(conversation, name, model) {
1698
1781
  this.ensureDir();
1699
1782
  const id = name || crypto2.randomUUID().slice(0, 8);
1700
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1783
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1701
1784
  const data = conversation.serialize();
1702
1785
  const meta = {
1703
1786
  id,
@@ -1717,7 +1800,7 @@ var SessionManager = class {
1717
1800
  return id;
1718
1801
  }
1719
1802
  load(id) {
1720
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1803
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1721
1804
  if (!fs5.existsSync(filePath)) return null;
1722
1805
  const content = fs5.readFileSync(filePath, "utf-8");
1723
1806
  const lines = content.trim().split("\n").filter(Boolean);
@@ -1747,7 +1830,7 @@ var SessionManager = class {
1747
1830
  const files = fs5.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl"));
1748
1831
  const sessions = [];
1749
1832
  for (const file of files) {
1750
- const filePath = path6.join(this.sessionsDir, file);
1833
+ const filePath = path7.join(this.sessionsDir, file);
1751
1834
  try {
1752
1835
  const firstLine = fs5.readFileSync(filePath, "utf-8").split("\n")[0];
1753
1836
  if (firstLine) {
@@ -1767,7 +1850,7 @@ var SessionManager = class {
1767
1850
  return sessions[0] ?? null;
1768
1851
  }
1769
1852
  delete(id) {
1770
- const filePath = path6.join(this.sessionsDir, `${id}.jsonl`);
1853
+ const filePath = path7.join(this.sessionsDir, `${id}.jsonl`);
1771
1854
  if (fs5.existsSync(filePath)) {
1772
1855
  fs5.unlinkSync(filePath);
1773
1856
  return true;
@@ -1872,21 +1955,21 @@ var checkpointManager = new CheckpointManager();
1872
1955
  // src/agent/codi-md.ts
1873
1956
  init_esm_shims();
1874
1957
  import * as fs6 from "fs";
1875
- import * as path7 from "path";
1958
+ import * as path8 from "path";
1876
1959
  function loadCodiMd() {
1877
1960
  const fragments = [];
1878
1961
  let dir = process.cwd();
1879
- const root = path7.parse(dir).root;
1962
+ const root = path8.parse(dir).root;
1880
1963
  while (dir !== root) {
1881
1964
  loadFromDir(dir, fragments);
1882
- const parent = path7.dirname(dir);
1965
+ const parent = path8.dirname(dir);
1883
1966
  if (parent === dir) break;
1884
1967
  dir = parent;
1885
1968
  }
1886
1969
  return fragments.join("\n\n---\n\n");
1887
1970
  }
1888
1971
  function loadFromDir(dir, fragments) {
1889
- const codiPath = path7.join(dir, "CODI.md");
1972
+ const codiPath = path8.join(dir, "CODI.md");
1890
1973
  if (fs6.existsSync(codiPath)) {
1891
1974
  try {
1892
1975
  let content = fs6.readFileSync(codiPath, "utf-8");
@@ -1896,7 +1979,7 @@ ${content}`);
1896
1979
  } catch {
1897
1980
  }
1898
1981
  }
1899
- const localPath = path7.join(dir, "CODI.local.md");
1982
+ const localPath = path8.join(dir, "CODI.local.md");
1900
1983
  if (fs6.existsSync(localPath)) {
1901
1984
  try {
1902
1985
  const content = fs6.readFileSync(localPath, "utf-8");
@@ -1905,12 +1988,12 @@ ${content}`);
1905
1988
  } catch {
1906
1989
  }
1907
1990
  }
1908
- const rulesDir = path7.join(dir, ".codi", "rules");
1991
+ const rulesDir = path8.join(dir, ".codi", "rules");
1909
1992
  if (fs6.existsSync(rulesDir)) {
1910
1993
  try {
1911
1994
  const files = fs6.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort();
1912
1995
  for (const file of files) {
1913
- const content = fs6.readFileSync(path7.join(rulesDir, file), "utf-8");
1996
+ const content = fs6.readFileSync(path8.join(rulesDir, file), "utf-8");
1914
1997
  fragments.push(`[Rule: ${file}]
1915
1998
  ${content}`);
1916
1999
  }
@@ -1920,7 +2003,7 @@ ${content}`);
1920
2003
  }
1921
2004
  function processImports(content, baseDir) {
1922
2005
  return content.replace(/@([\w./-]+)/g, (match, importPath) => {
1923
- const resolved = path7.resolve(baseDir, importPath);
2006
+ const resolved = path8.resolve(baseDir, importPath);
1924
2007
  if (fs6.existsSync(resolved)) {
1925
2008
  try {
1926
2009
  return fs6.readFileSync(resolved, "utf-8");
@@ -2189,7 +2272,7 @@ var hookManager = new HookManager();
2189
2272
  init_esm_shims();
2190
2273
  init_tool();
2191
2274
  import * as fs7 from "fs";
2192
- import * as path8 from "path";
2275
+ import * as path9 from "path";
2193
2276
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2194
2277
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
2195
2278
  import chalk9 from "chalk";
@@ -2211,8 +2294,8 @@ var McpManager = class {
2211
2294
  const configs = {};
2212
2295
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2213
2296
  const paths = [
2214
- path8.join(home, ".codi", "mcp.json"),
2215
- path8.join(process.cwd(), ".codi", "mcp.json")
2297
+ path9.join(home, ".codi", "mcp.json"),
2298
+ path9.join(process.cwd(), ".codi", "mcp.json")
2216
2299
  ];
2217
2300
  for (const p of paths) {
2218
2301
  try {
@@ -2414,7 +2497,7 @@ var subAgentTool = {
2414
2497
  init_esm_shims();
2415
2498
  import * as fs8 from "fs";
2416
2499
  import * as os5 from "os";
2417
- import * as path9 from "path";
2500
+ import * as path10 from "path";
2418
2501
  import chalk11 from "chalk";
2419
2502
  function createBuiltinCommands() {
2420
2503
  return [
@@ -2608,12 +2691,12 @@ Topics: ${topics.join(", ")}`));
2608
2691
  name: "/init",
2609
2692
  description: "Initialize CODI.md in the current project",
2610
2693
  handler: async () => {
2611
- const codiPath = path9.join(process.cwd(), "CODI.md");
2694
+ const codiPath = path10.join(process.cwd(), "CODI.md");
2612
2695
  if (fs8.existsSync(codiPath)) {
2613
2696
  console.log(chalk11.yellow("CODI.md already exists"));
2614
2697
  return true;
2615
2698
  }
2616
- const content = `# Project: ${path9.basename(process.cwd())}
2699
+ const content = `# Project: ${path10.basename(process.cwd())}
2617
2700
 
2618
2701
  ## Overview
2619
2702
  <!-- Describe your project here -->
@@ -2825,7 +2908,7 @@ ${diff}
2825
2908
  return true;
2826
2909
  }
2827
2910
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2828
- const sessionsDir = path9.join(home, ".codi", "sessions");
2911
+ const sessionsDir = path10.join(home, ".codi", "sessions");
2829
2912
  if (!fs8.existsSync(sessionsDir)) {
2830
2913
  console.log(chalk11.dim("\nNo sessions found.\n"));
2831
2914
  return true;
@@ -2835,7 +2918,7 @@ ${diff}
2835
2918
  const keyword = args.toLowerCase();
2836
2919
  for (const file of files) {
2837
2920
  if (results.length >= 10) break;
2838
- const filePath = path9.join(sessionsDir, file);
2921
+ const filePath = path10.join(sessionsDir, file);
2839
2922
  const lines = fs8.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
2840
2923
  for (const line of lines) {
2841
2924
  if (results.length >= 10) break;
@@ -2929,18 +3012,18 @@ function loadCustomCommands() {
2929
3012
  const commands = [];
2930
3013
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2931
3014
  const dirs = [
2932
- path9.join(home, ".codi", "commands"),
2933
- path9.join(process.cwd(), ".codi", "commands")
3015
+ path10.join(home, ".codi", "commands"),
3016
+ path10.join(process.cwd(), ".codi", "commands")
2934
3017
  ];
2935
3018
  for (const dir of dirs) {
2936
3019
  if (!fs8.existsSync(dir)) continue;
2937
3020
  const files = fs8.readdirSync(dir).filter((f) => f.endsWith(".md"));
2938
3021
  for (const file of files) {
2939
3022
  const name = "/" + file.replace(".md", "");
2940
- const filePath = path9.join(dir, file);
3023
+ const filePath = path10.join(dir, file);
2941
3024
  commands.push({
2942
3025
  name,
2943
- description: `Custom command from ${path9.relative(process.cwd(), filePath)}`,
3026
+ description: `Custom command from ${path10.relative(process.cwd(), filePath)}`,
2944
3027
  handler: async (_args, ctx) => {
2945
3028
  let content = fs8.readFileSync(filePath, "utf-8");
2946
3029
  content = content.replace(/\{\{cwd\}\}/g, process.cwd()).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0]).replace(/\{\{file_path\}\}/g, _args || "");
@@ -2957,7 +3040,7 @@ function loadCustomCommands() {
2957
3040
  init_esm_shims();
2958
3041
  init_tool();
2959
3042
  import * as fs9 from "fs";
2960
- import * as path10 from "path";
3043
+ import * as path11 from "path";
2961
3044
  var fileReadTool = {
2962
3045
  name: "read_file",
2963
3046
  description: `Read a file from the filesystem. Supports text files with line numbers (cat -n format), PDF files, images (returns base64 for multimodal), and Jupyter notebooks (.ipynb). Use offset/limit for large files.`,
@@ -2977,7 +3060,7 @@ var fileReadTool = {
2977
3060
  const filePath = String(input3["file_path"]);
2978
3061
  const offset = input3["offset"];
2979
3062
  const limit = input3["limit"];
2980
- const resolved = path10.resolve(filePath);
3063
+ const resolved = path11.resolve(filePath);
2981
3064
  if (!fs9.existsSync(resolved)) {
2982
3065
  return makeToolError(`File not found: ${resolved}`);
2983
3066
  }
@@ -2985,7 +3068,7 @@ var fileReadTool = {
2985
3068
  if (stat.isDirectory()) {
2986
3069
  return makeToolError(`Path is a directory, not a file: ${resolved}. Use list_dir instead.`);
2987
3070
  }
2988
- const ext = path10.extname(resolved).toLowerCase();
3071
+ const ext = path11.extname(resolved).toLowerCase();
2989
3072
  if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"].includes(ext)) {
2990
3073
  const data = fs9.readFileSync(resolved);
2991
3074
  const base64 = data.toString("base64");
@@ -2998,7 +3081,7 @@ var fileReadTool = {
2998
3081
  ".bmp": "image/bmp",
2999
3082
  ".svg": "image/svg+xml"
3000
3083
  };
3001
- return makeToolResult(`[Image: ${path10.basename(resolved)}]`, {
3084
+ return makeToolResult(`[Image: ${path11.basename(resolved)}]`, {
3002
3085
  filePath: resolved,
3003
3086
  isImage: true,
3004
3087
  imageData: base64,
@@ -3084,7 +3167,7 @@ var fileReadTool = {
3084
3167
  init_esm_shims();
3085
3168
  init_tool();
3086
3169
  import * as fs10 from "fs";
3087
- import * as path11 from "path";
3170
+ import * as path12 from "path";
3088
3171
  var fileWriteTool = {
3089
3172
  name: "write_file",
3090
3173
  description: `Create a new file or overwrite an existing file. Creates parent directories if needed. For modifying existing files, prefer edit_file instead.`,
@@ -3101,9 +3184,9 @@ var fileWriteTool = {
3101
3184
  async execute(input3) {
3102
3185
  const filePath = String(input3["file_path"]);
3103
3186
  const content = String(input3["content"]);
3104
- const resolved = path11.resolve(filePath);
3187
+ const resolved = path12.resolve(filePath);
3105
3188
  try {
3106
- const dir = path11.dirname(resolved);
3189
+ const dir = path12.dirname(resolved);
3107
3190
  if (!fs10.existsSync(dir)) {
3108
3191
  fs10.mkdirSync(dir, { recursive: true });
3109
3192
  }
@@ -3125,7 +3208,7 @@ var fileWriteTool = {
3125
3208
  init_esm_shims();
3126
3209
  init_tool();
3127
3210
  import * as fs11 from "fs";
3128
- import * as path12 from "path";
3211
+ import * as path13 from "path";
3129
3212
  var fileEditTool = {
3130
3213
  name: "edit_file",
3131
3214
  description: `Perform exact string replacement in a file. The old_string must be unique in the file unless replace_all is true. Preserves indentation exactly.`,
@@ -3146,7 +3229,7 @@ var fileEditTool = {
3146
3229
  const oldString = String(input3["old_string"]);
3147
3230
  const newString = String(input3["new_string"]);
3148
3231
  const replaceAll = input3["replace_all"] === true;
3149
- const resolved = path12.resolve(filePath);
3232
+ const resolved = path13.resolve(filePath);
3150
3233
  if (!fs11.existsSync(resolved)) {
3151
3234
  return makeToolError(`File not found: ${resolved}`);
3152
3235
  }
@@ -3205,7 +3288,7 @@ Did you mean this line?
3205
3288
  init_esm_shims();
3206
3289
  init_tool();
3207
3290
  import * as fs12 from "fs";
3208
- import * as path13 from "path";
3291
+ import * as path14 from "path";
3209
3292
  var fileMultiEditTool = {
3210
3293
  name: "multi_edit",
3211
3294
  description: `Apply multiple edits to a single file atomically. Each edit is an old_string \u2192 new_string replacement. All edits are validated before any are applied.`,
@@ -3233,7 +3316,7 @@ var fileMultiEditTool = {
3233
3316
  async execute(input3) {
3234
3317
  const filePath = String(input3["file_path"]);
3235
3318
  const edits = input3["edits"];
3236
- const resolved = path13.resolve(filePath);
3319
+ const resolved = path14.resolve(filePath);
3237
3320
  if (!fs12.existsSync(resolved)) {
3238
3321
  return makeToolError(`File not found: ${resolved}`);
3239
3322
  }
@@ -3281,7 +3364,7 @@ Searching for: ${edit2.old_string.slice(0, 100)}...`
3281
3364
  init_esm_shims();
3282
3365
  init_tool();
3283
3366
  import * as fs13 from "fs";
3284
- import * as path14 from "path";
3367
+ import * as path15 from "path";
3285
3368
  import { globby } from "globby";
3286
3369
  var globTool = {
3287
3370
  name: "glob",
@@ -3299,7 +3382,7 @@ var globTool = {
3299
3382
  async execute(input3) {
3300
3383
  const pattern = String(input3["pattern"]);
3301
3384
  const searchPath = input3["path"] ? String(input3["path"]) : process.cwd();
3302
- const resolved = path14.resolve(searchPath);
3385
+ const resolved = path15.resolve(searchPath);
3303
3386
  try {
3304
3387
  const files = await globby(pattern, {
3305
3388
  cwd: resolved,
@@ -3336,7 +3419,7 @@ init_esm_shims();
3336
3419
  init_tool();
3337
3420
  import { execFile } from "child_process";
3338
3421
  import * as fs14 from "fs";
3339
- import * as path15 from "path";
3422
+ import * as path16 from "path";
3340
3423
  var grepTool = {
3341
3424
  name: "grep",
3342
3425
  description: `Search file contents using regex patterns. Uses ripgrep (rg) if available, falls back to grep, then to a built-in Node.js search. Supports context lines, file type filters, and multiple output modes.`,
@@ -3366,7 +3449,7 @@ var grepTool = {
3366
3449
  readOnly: true,
3367
3450
  async execute(input3) {
3368
3451
  const pattern = String(input3["pattern"]);
3369
- const searchPath = path15.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3452
+ const searchPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3370
3453
  const outputMode = input3["output_mode"] || "files_with_matches";
3371
3454
  const headLimit = input3["head_limit"] || 0;
3372
3455
  const hasRg = await hasCommand("rg");
@@ -3556,11 +3639,11 @@ function collectFiles(dirPath, typeFilter, globFilter) {
3556
3639
  for (const entry of entries) {
3557
3640
  if (IGNORE_DIRS.has(entry.name)) continue;
3558
3641
  if (entry.name.startsWith(".") && entry.name !== ".") continue;
3559
- const fullPath = path15.join(dir, entry.name);
3642
+ const fullPath = path16.join(dir, entry.name);
3560
3643
  if (entry.isDirectory()) {
3561
3644
  walk(fullPath);
3562
3645
  } else if (entry.isFile()) {
3563
- const ext = path15.extname(entry.name).toLowerCase();
3646
+ const ext = path16.extname(entry.name).toLowerCase();
3564
3647
  if (BINARY_EXTENSIONS.has(ext)) continue;
3565
3648
  if (allowedExtensions && !allowedExtensions.has(ext)) continue;
3566
3649
  if (globRegex && !globRegex.test(entry.name)) continue;
@@ -3687,7 +3770,7 @@ Use task_output tool to check results.`);
3687
3770
  init_esm_shims();
3688
3771
  init_tool();
3689
3772
  import * as fs15 from "fs";
3690
- import * as path16 from "path";
3773
+ import * as path17 from "path";
3691
3774
  var listDirTool = {
3692
3775
  name: "list_dir",
3693
3776
  description: `List directory contents with file/folder distinction and basic metadata.`,
@@ -3701,7 +3784,7 @@ var listDirTool = {
3701
3784
  dangerous: false,
3702
3785
  readOnly: true,
3703
3786
  async execute(input3) {
3704
- const dirPath = path16.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3787
+ const dirPath = path17.resolve(input3["path"] ? String(input3["path"]) : process.cwd());
3705
3788
  if (!fs15.existsSync(dirPath)) {
3706
3789
  return makeToolError(`Directory not found: ${dirPath}`);
3707
3790
  }
@@ -3721,7 +3804,7 @@ var listDirTool = {
3721
3804
  dirs.push(`${entry.name}/`);
3722
3805
  } else if (entry.isSymbolicLink()) {
3723
3806
  try {
3724
- const target = fs15.readlinkSync(path16.join(dirPath, entry.name));
3807
+ const target = fs15.readlinkSync(path17.join(dirPath, entry.name));
3725
3808
  files.push(`${entry.name} -> ${target}`);
3726
3809
  } catch {
3727
3810
  files.push(`${entry.name} -> (broken link)`);
@@ -3934,7 +4017,7 @@ ${formatted}`);
3934
4017
  init_esm_shims();
3935
4018
  init_tool();
3936
4019
  import * as fs16 from "fs";
3937
- import * as path17 from "path";
4020
+ import * as path18 from "path";
3938
4021
  var notebookEditTool = {
3939
4022
  name: "notebook_edit",
3940
4023
  description: `Edit Jupyter notebook (.ipynb) cells. Supports replacing, inserting, and deleting cells.`,
@@ -3952,7 +4035,7 @@ var notebookEditTool = {
3952
4035
  dangerous: true,
3953
4036
  readOnly: false,
3954
4037
  async execute(input3) {
3955
- const nbPath = path17.resolve(String(input3["notebook_path"]));
4038
+ const nbPath = path18.resolve(String(input3["notebook_path"]));
3956
4039
  const cellNumber = input3["cell_number"];
3957
4040
  const newSource = String(input3["new_source"]);
3958
4041
  const cellType = input3["cell_type"] || "code";
@@ -4251,6 +4334,7 @@ var OpenAIProvider = class {
4251
4334
  model;
4252
4335
  client;
4253
4336
  maxTokens;
4337
+ isGemini;
4254
4338
  constructor(config) {
4255
4339
  this.client = new OpenAI({
4256
4340
  apiKey: config.apiKey || process.env["OPENAI_API_KEY"],
@@ -4258,6 +4342,7 @@ var OpenAIProvider = class {
4258
4342
  });
4259
4343
  this.model = config.model || "gpt-4o";
4260
4344
  this.maxTokens = config.maxTokens || 8192;
4345
+ this.isGemini = !!(config.baseUrl && config.baseUrl.includes("generativelanguage.googleapis.com"));
4261
4346
  }
4262
4347
  setModel(model) {
4263
4348
  this.model = model;
@@ -4277,30 +4362,45 @@ var OpenAIProvider = class {
4277
4362
  function: {
4278
4363
  name: t.name,
4279
4364
  description: t.description,
4280
- parameters: t.input_schema
4365
+ parameters: this.cleanSchema(t.input_schema)
4281
4366
  }
4282
4367
  }));
4283
- if (options.stream && options.callbacks) {
4284
- return this.streamChat(messages, tools, options);
4368
+ try {
4369
+ if (options.stream && options.callbacks) {
4370
+ return await this.streamChat(messages, tools, options);
4371
+ }
4372
+ const response = await this.client.chat.completions.create({
4373
+ model: this.model,
4374
+ messages,
4375
+ max_tokens: options.maxTokens || this.maxTokens,
4376
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
4377
+ ...tools && tools.length > 0 ? { tools } : {}
4378
+ });
4379
+ return this.parseResponse(response);
4380
+ } catch (err) {
4381
+ const status = err.status || err.statusCode || "";
4382
+ const body = err.error || err.body || err.response?.body || "";
4383
+ const detail = body ? JSON.stringify(body) : err.message || String(err);
4384
+ throw new Error(`${status} ${detail}`.trim());
4285
4385
  }
4286
- const response = await this.client.chat.completions.create({
4287
- model: this.model,
4288
- messages,
4289
- max_tokens: options.maxTokens || this.maxTokens,
4290
- ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
4291
- ...tools && tools.length > 0 ? { tools } : {}
4292
- });
4293
- return this.parseResponse(response);
4294
4386
  }
4295
4387
  async streamChat(messages, tools, options) {
4296
- const stream = await this.client.chat.completions.create({
4297
- model: this.model,
4298
- messages,
4299
- max_tokens: options.maxTokens || this.maxTokens,
4300
- ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
4301
- ...tools && tools.length > 0 ? { tools } : {},
4302
- stream: true
4303
- });
4388
+ let stream;
4389
+ try {
4390
+ stream = await this.client.chat.completions.create({
4391
+ model: this.model,
4392
+ messages,
4393
+ max_tokens: options.maxTokens || this.maxTokens,
4394
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
4395
+ ...tools && tools.length > 0 ? { tools } : {},
4396
+ stream: true
4397
+ });
4398
+ } catch (err) {
4399
+ const status = err.status || err.statusCode || "";
4400
+ const body = err.error || err.body || err.response?.body || "";
4401
+ const detail = body ? JSON.stringify(body) : err.message || String(err);
4402
+ throw new Error(`${status} ${detail}`.trim());
4403
+ }
4304
4404
  const content = [];
4305
4405
  const toolCalls = [];
4306
4406
  let text = "";
@@ -4345,6 +4445,31 @@ var OpenAIProvider = class {
4345
4445
  stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
4346
4446
  };
4347
4447
  }
4448
+ /**
4449
+ * Clean JSON Schema for Gemini compatibility.
4450
+ * Gemini's OpenAI-compatible API rejects some valid JSON Schema features:
4451
+ * - Empty `required` arrays
4452
+ * - `default` values in properties
4453
+ * - `additionalProperties` at top level
4454
+ */
4455
+ cleanSchema(schema) {
4456
+ const cleaned = { ...schema };
4457
+ if (Array.isArray(cleaned["required"]) && cleaned["required"].length === 0) {
4458
+ delete cleaned["required"];
4459
+ }
4460
+ if (cleaned["properties"] && typeof cleaned["properties"] === "object") {
4461
+ const props = { ...cleaned["properties"] };
4462
+ for (const [key, val] of Object.entries(props)) {
4463
+ if (val && typeof val === "object") {
4464
+ const prop = { ...val };
4465
+ delete prop["default"];
4466
+ props[key] = prop;
4467
+ }
4468
+ }
4469
+ cleaned["properties"] = props;
4470
+ }
4471
+ return cleaned;
4472
+ }
4348
4473
  convertMessages(messages, systemPrompt) {
4349
4474
  const result = [];
4350
4475
  if (systemPrompt) {