open-agents-ai 0.172.0 → 0.173.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +84 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25688,7 +25688,32 @@ Respond with your assessment, then take action.`;
25688
25688
 
25689
25689
  TASK: ${task}` : task }
25690
25690
  ];
25691
- const toolDefs = this.buildToolDefinitions();
25691
+ let toolDefs = this.buildToolDefinitions();
25692
+ let textToolModeActive = this.options.textToolMode ?? false;
25693
+ if (textToolModeActive) {
25694
+ const toolDescriptions = Array.from(this.tools.values()).map((t) => `- ${t.name}: ${t.description}`).join("\n");
25695
+ messages[0].content += [
25696
+ "\n\n[TEXT TOOL MODE]",
25697
+ "This model uses text-based tool invocation (no native tool API).",
25698
+ "To use a tool, output a JSON block in your response:",
25699
+ "```json",
25700
+ '{"tool": "tool_name", "args": {"param": "value"}}',
25701
+ "```",
25702
+ "\nAvailable tools:",
25703
+ toolDescriptions,
25704
+ "\nRules:",
25705
+ "- Output EXACTLY ONE tool call per response in the JSON format above",
25706
+ "- After seeing the tool result, continue or call another tool",
25707
+ '- When done: {"tool": "task_complete", "args": {"summary": "what you did"}}',
25708
+ "- You CAN also write ```bash ... ``` blocks which will be auto-executed"
25709
+ ].join("\n");
25710
+ toolDefs = [];
25711
+ this.emit({
25712
+ type: "status",
25713
+ content: "Text tool mode active \u2014 tools described in prompt, parsed from JSON/code blocks",
25714
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
25715
+ });
25716
+ }
25692
25717
  let totalTokens = 0;
25693
25718
  let promptTokens = 0;
25694
25719
  let completionTokens = 0;
@@ -39132,6 +39157,34 @@ import { promisify as promisify6 } from "node:util";
39132
39157
  import { existsSync as existsSync37, writeFileSync as writeFileSync16, readFileSync as readFileSync28, appendFileSync as appendFileSync2, mkdirSync as mkdirSync15 } from "node:fs";
39133
39158
  import { join as join54 } from "node:path";
39134
39159
  import { homedir as homedir12, platform as platform2 } from "node:os";
39160
+ async function checkToolSupport(modelName, backendUrl = "http://localhost:11434") {
39161
+ if (_toolSupportCache.has(modelName))
39162
+ return _toolSupportCache.get(modelName);
39163
+ try {
39164
+ const resp = await fetch(`${backendUrl.replace(/\/+$/, "")}/api/show`, {
39165
+ method: "POST",
39166
+ headers: { "Content-Type": "application/json" },
39167
+ body: JSON.stringify({ name: modelName }),
39168
+ signal: AbortSignal.timeout(5e3)
39169
+ });
39170
+ if (!resp.ok) {
39171
+ _toolSupportCache.set(modelName, false);
39172
+ return false;
39173
+ }
39174
+ const data = await resp.json();
39175
+ const template = data.template ?? "";
39176
+ const hasTools = template.includes("{{.Tools}}") || template.includes("{{ .Tools }}");
39177
+ _toolSupportCache.set(modelName, hasTools);
39178
+ return hasTools;
39179
+ } catch {
39180
+ _toolSupportCache.set(modelName, false);
39181
+ return false;
39182
+ }
39183
+ }
39184
+ async function needsTextToolMode(modelName, backendUrl) {
39185
+ const hasTools = await checkToolSupport(modelName, backendUrl);
39186
+ return !hasTools;
39187
+ }
39135
39188
  function detectSystemSpecs() {
39136
39189
  let totalRamGB = 0;
39137
39190
  let availableRamGB = 0;
@@ -39259,14 +39312,6 @@ function calculateContextWindow(specs, modelSizeGB2, kvBytesPerToken, archMax) {
39259
39312
  const label = numCtx >= 1024 ? `${Math.floor(numCtx / 1024)}K` : String(numCtx);
39260
39313
  return { numCtx, label };
39261
39314
  }
39262
- function modelSupportsToolCalling(modelName) {
39263
- const lower = modelName.toLowerCase();
39264
- for (const known of TOOL_CALLING_MODELS) {
39265
- if (lower.startsWith(known) || lower.includes(known))
39266
- return true;
39267
- }
39268
- return false;
39269
- }
39270
39315
  function ask(rl, question) {
39271
39316
  return new Promise((resolve34) => {
39272
39317
  rl.question(question, (answer) => resolve34(answer.trim()));
@@ -40065,33 +40110,42 @@ async function doSetup(config, rl) {
40065
40110
  process.stdout.write(` ${c2.yellow("\u26A0")} Default model ${c2.bold(config.model)} is not available.
40066
40111
 
40067
40112
  `);
40068
- const toolCallingModels = models.filter((m) => modelSupportsToolCalling(m.name));
40069
- if (toolCallingModels.length > 0) {
40070
- process.stdout.write(` ${c2.cyan("\u25CF")} Found ${toolCallingModels.length} model(s) with tool-calling support:
40113
+ if (models.length > 0) {
40114
+ const backendUrl = config.backendUrl || "http://localhost:11434";
40115
+ const modelChecks = await Promise.all(models.slice(0, 15).map(async (m) => ({
40116
+ ...m,
40117
+ hasTools: await checkToolSupport(m.name, backendUrl)
40118
+ })));
40119
+ process.stdout.write(` ${c2.cyan("\u25CF")} Available models:
40071
40120
 
40072
40121
  `);
40073
- for (let i = 0; i < Math.min(toolCallingModels.length, 10); i++) {
40074
- const m = toolCallingModels[i];
40075
- process.stdout.write(` ${c2.bold(String(i + 1))}. ${m.name} ${c2.dim(`(${m.size})`)}
40122
+ for (let i = 0; i < modelChecks.length; i++) {
40123
+ const m = modelChecks[i];
40124
+ const toolTag = m.hasTools ? c2.green("tools \u2713") : c2.yellow("text mode");
40125
+ process.stdout.write(` ${c2.bold(String(i + 1))}. ${m.name} ${c2.dim(`(${m.size})`)} ${toolTag}
40076
40126
  `);
40077
40127
  }
40078
40128
  process.stdout.write(`
40079
40129
  ${c2.dim("0")}. Pull a new qwen3.5 model instead
40130
+ `);
40131
+ process.stdout.write(`
40132
+ ${c2.dim("Models marked 'text mode' work via instruct-and-parse (no native tool API needed)")}
40080
40133
  `);
40081
40134
  process.stdout.write("\n");
40082
- const choice = await ask(rl, ` ${c2.bold("Select a model")} (1-${Math.min(toolCallingModels.length, 10)}, or 0 to pull new): `);
40135
+ const choice = await ask(rl, ` ${c2.bold("Select a model")} (1-${modelChecks.length}, or 0 to pull new): `);
40083
40136
  const idx = parseInt(choice, 10);
40084
- if (idx > 0 && idx <= toolCallingModels.length) {
40085
- const selected = toolCallingModels[idx - 1];
40137
+ if (idx > 0 && idx <= modelChecks.length) {
40138
+ const selected = modelChecks[idx - 1];
40086
40139
  setConfigValue("model", selected.name);
40140
+ const mode = selected.hasTools ? "native tool calling" : "text mode (instruct-and-parse)";
40087
40141
  process.stdout.write(`
40088
- ${c2.green("\u2714")} Selected ${c2.bold(selected.name)}. Saved to config.
40142
+ ${c2.green("\u2714")} Selected ${c2.bold(selected.name)} \u2014 ${mode}. Saved to config.
40089
40143
 
40090
40144
  `);
40091
40145
  return selected.name;
40092
40146
  }
40093
40147
  } else {
40094
- process.stdout.write(` ${c2.yellow("\u26A0")} No tool-calling capable models found on this system.
40148
+ process.stdout.write(` ${c2.yellow("\u26A0")} No models found on this system.
40095
40149
 
40096
40150
  `);
40097
40151
  }
@@ -40809,7 +40863,7 @@ export PATH="${binDir}:$PATH" # Added by open-agents for nvim
40809
40863
  } catch {
40810
40864
  }
40811
40865
  }
40812
- var execAsync, QWEN_VARIANTS, TOOL_CALLING_MODELS, _cloudflaredInstallPromise;
40866
+ var execAsync, QWEN_VARIANTS, _toolSupportCache, _cloudflaredInstallPromise;
40813
40867
  var init_setup = __esm({
40814
40868
  "packages/cli/dist/tui/setup.js"() {
40815
40869
  "use strict";
@@ -40829,19 +40883,7 @@ var init_setup = __esm({
40829
40883
  { tag: "qwen3.5:cloud", sizeGB: 0, label: "Cloud (Ollama Cloud)", cloud: true },
40830
40884
  { tag: "qwen3.5:397b-cloud", sizeGB: 0, label: "397B Cloud (Ollama Cloud)", cloud: true }
40831
40885
  ];
40832
- TOOL_CALLING_MODELS = /* @__PURE__ */ new Set([
40833
- "qwen3.5",
40834
- "qwen3",
40835
- "qwen2.5",
40836
- "llama3.3",
40837
- "llama3.1",
40838
- "mistral",
40839
- "mixtral",
40840
- "command-r",
40841
- "gemma3",
40842
- "devstral",
40843
- "deepseek"
40844
- ]);
40886
+ _toolSupportCache = /* @__PURE__ */ new Map();
40845
40887
  _cloudflaredInstallPromise = null;
40846
40888
  }
40847
40889
  });
@@ -60073,9 +60115,17 @@ ${lines.join("\n")}
60073
60115
  personalityName: personality ?? void 0,
60074
60116
  identityInjection,
60075
60117
  environmentProvider
60118
+ // Text tool mode is set dynamically after runner creation (async check)
60119
+ // The runner also auto-detects this when Ollama returns HTTP 400 for tools
60076
60120
  });
60077
60121
  runner.setWorkingDirectory(repoRoot);
60078
60122
  _activeRunnerRef = runner;
60123
+ needsTextToolMode(config.model, config.backendUrl).then((needsText) => {
60124
+ if (needsText) {
60125
+ runner.options.textToolMode = true;
60126
+ }
60127
+ }).catch(() => {
60128
+ });
60079
60129
  const tools = buildTools(repoRoot, config, contextWindowSize);
60080
60130
  if (contextWindowSize && contextWindowSize > 0) {
60081
60131
  for (const tool of tools) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.172.0",
3
+ "version": "0.173.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",