miii-agent 0.1.20 → 0.1.21
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 +105 -57
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -89,8 +89,8 @@ var init_config = __esm({
|
|
|
89
89
|
"src/config.ts"() {
|
|
90
90
|
"use strict";
|
|
91
91
|
EFFORT_OPTIONS = {
|
|
92
|
-
low: { temperature: 0.2, num_predict:
|
|
93
|
-
medium: { temperature: 0.7, num_predict:
|
|
92
|
+
low: { temperature: 0.2, num_predict: 8192 },
|
|
93
|
+
medium: { temperature: 0.7, num_predict: 16384 },
|
|
94
94
|
high: { temperature: 1, num_predict: -1 }
|
|
95
95
|
};
|
|
96
96
|
CONFIG_DIR = join(homedir(), ".miii");
|
|
@@ -163,30 +163,6 @@ async function modelContext(entry, model) {
|
|
|
163
163
|
throw err;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
-
async function paramCountB(entry, model) {
|
|
167
|
-
try {
|
|
168
|
-
const info = await makeClient(entry).show({ model });
|
|
169
|
-
const details = info.details;
|
|
170
|
-
if (details?.parameter_size) {
|
|
171
|
-
const m = details.parameter_size.match(/([\d.]+)\s*([BM])/i);
|
|
172
|
-
if (m) {
|
|
173
|
-
const n = parseFloat(m[1]);
|
|
174
|
-
if (!isNaN(n)) return m[2].toUpperCase() === "M" ? n / 1e3 : n;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const modelInfo = info.model_info;
|
|
178
|
-
if (modelInfo) {
|
|
179
|
-
const key = Object.keys(modelInfo).find((k) => k.endsWith("parameter_count"));
|
|
180
|
-
if (key) {
|
|
181
|
-
const val = Number(modelInfo[key]);
|
|
182
|
-
if (!isNaN(val) && val > 0) return val / 1e9;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return null;
|
|
186
|
-
} catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
166
|
async function* chat(entry, model, messages, tools, opts) {
|
|
191
167
|
if (opts?.signal?.aborted) return;
|
|
192
168
|
const signal = opts?.signal;
|
|
@@ -249,6 +225,7 @@ async function* chat(entry, model, messages, tools, opts) {
|
|
|
249
225
|
content: stripHarmony(chunk.message.content),
|
|
250
226
|
thinking: stripHarmony(chunk.message.thinking),
|
|
251
227
|
done: chunk.done,
|
|
228
|
+
done_reason: chunk.done_reason,
|
|
252
229
|
tool_calls: chunk.message.tool_calls,
|
|
253
230
|
prompt_eval_count: chunk.prompt_eval_count,
|
|
254
231
|
eval_count: chunk.eval_count
|
|
@@ -384,6 +361,7 @@ async function* chat2(entry, model, messages, tools, opts) {
|
|
|
384
361
|
if (oaTools) body.tools = oaTools;
|
|
385
362
|
if (opts?.num_predict && opts.num_predict > 0) body.max_tokens = opts.num_predict;
|
|
386
363
|
const toolCallAccum = /* @__PURE__ */ new Map();
|
|
364
|
+
let lastFinishReason;
|
|
387
365
|
const TIMEOUT_MS = 18e4;
|
|
388
366
|
const timeoutSignal = AbortSignal.timeout(TIMEOUT_MS);
|
|
389
367
|
const combinedSignal = opts?.signal && typeof AbortSignal.any === "function" ? AbortSignal.any([opts.signal, timeoutSignal]) : opts?.signal ?? timeoutSignal;
|
|
@@ -423,6 +401,7 @@ async function* chat2(entry, model, messages, tools, opts) {
|
|
|
423
401
|
if (!choices || choices.length === 0) continue;
|
|
424
402
|
const delta = choices[0].delta ?? {};
|
|
425
403
|
const finishReason = choices[0].finish_reason;
|
|
404
|
+
if (finishReason) lastFinishReason = finishReason;
|
|
426
405
|
if (delta.content) {
|
|
427
406
|
yield { content: delta.content, done: false };
|
|
428
407
|
}
|
|
@@ -487,6 +466,9 @@ async function* chat2(entry, model, messages, tools, opts) {
|
|
|
487
466
|
yield {
|
|
488
467
|
content: "",
|
|
489
468
|
done: true,
|
|
469
|
+
// OpenAI signals a hit token cap as finish_reason 'length'; normalize to the
|
|
470
|
+
// Ollama spelling so the agent loop can detect truncation uniformly.
|
|
471
|
+
done_reason: lastFinishReason === "length" ? "length" : lastFinishReason ?? void 0,
|
|
490
472
|
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
|
491
473
|
};
|
|
492
474
|
}
|
|
@@ -502,9 +484,6 @@ var init_openai = __esm({
|
|
|
502
484
|
function active() {
|
|
503
485
|
return resolveProvider();
|
|
504
486
|
}
|
|
505
|
-
function providerName() {
|
|
506
|
-
return active().name;
|
|
507
|
-
}
|
|
508
487
|
function isAvailable3() {
|
|
509
488
|
const { entry } = active();
|
|
510
489
|
return entry.type === "ollama" ? isAvailable(entry) : isAvailable2(entry);
|
|
@@ -521,16 +500,6 @@ async function modelContext3(model) {
|
|
|
521
500
|
const { entry } = active();
|
|
522
501
|
return entry.type === "ollama" ? modelContext(entry, model) : modelContext2(entry, model);
|
|
523
502
|
}
|
|
524
|
-
async function modelParamCountB(model) {
|
|
525
|
-
const { entry } = active();
|
|
526
|
-
if (entry.type !== "ollama") return null;
|
|
527
|
-
const key = `${entry.baseUrl}:${model}`;
|
|
528
|
-
const cached = paramCountCache.get(key);
|
|
529
|
-
if (cached !== void 0) return cached;
|
|
530
|
-
const params = await paramCountB(entry, model);
|
|
531
|
-
paramCountCache.set(key, params);
|
|
532
|
-
return params;
|
|
533
|
-
}
|
|
534
503
|
async function* chat3(model, messages, tools, opts) {
|
|
535
504
|
const { entry } = active();
|
|
536
505
|
if (entry.type === "ollama") {
|
|
@@ -539,14 +508,12 @@ async function* chat3(model, messages, tools, opts) {
|
|
|
539
508
|
yield* chat2(entry, model, messages, tools, opts);
|
|
540
509
|
}
|
|
541
510
|
}
|
|
542
|
-
var paramCountCache;
|
|
543
511
|
var init_client = __esm({
|
|
544
512
|
"src/llm/client.ts"() {
|
|
545
513
|
"use strict";
|
|
546
514
|
init_config();
|
|
547
515
|
init_ollama();
|
|
548
516
|
init_openai();
|
|
549
|
-
paramCountCache = /* @__PURE__ */ new Map();
|
|
550
517
|
}
|
|
551
518
|
});
|
|
552
519
|
|
|
@@ -749,7 +716,10 @@ var init_edit_file = __esm({
|
|
|
749
716
|
handler: ({ path, old_str, new_str, replace_all }) => {
|
|
750
717
|
try {
|
|
751
718
|
if (old_str === new_str) {
|
|
752
|
-
return {
|
|
719
|
+
return {
|
|
720
|
+
content: `old_str and new_str are identical \u2014 nothing to change in ${path}. If the file is already correct, do NOT edit again: finish with the respond action and tell the user it is done.`,
|
|
721
|
+
is_error: true
|
|
722
|
+
};
|
|
753
723
|
}
|
|
754
724
|
const abs = confinePath(path);
|
|
755
725
|
const src = readFileSync3(abs, "utf-8");
|
|
@@ -1218,6 +1188,31 @@ function toZod(schema) {
|
|
|
1218
1188
|
}
|
|
1219
1189
|
return z.object(shape).passthrough();
|
|
1220
1190
|
}
|
|
1191
|
+
function exampleValue(spec) {
|
|
1192
|
+
if (spec.enum && spec.enum.length) return spec.enum[0];
|
|
1193
|
+
switch (spec.type) {
|
|
1194
|
+
case "number":
|
|
1195
|
+
case "integer":
|
|
1196
|
+
return 0;
|
|
1197
|
+
case "boolean":
|
|
1198
|
+
return false;
|
|
1199
|
+
case "array":
|
|
1200
|
+
return [];
|
|
1201
|
+
case "object":
|
|
1202
|
+
return {};
|
|
1203
|
+
default:
|
|
1204
|
+
return "...";
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function exampleInput(schema) {
|
|
1208
|
+
const required = schema.required ?? [];
|
|
1209
|
+
const obj = {};
|
|
1210
|
+
for (const key of required) {
|
|
1211
|
+
const spec = schema.properties[key];
|
|
1212
|
+
if (spec) obj[key] = exampleValue(spec);
|
|
1213
|
+
}
|
|
1214
|
+
return JSON.stringify(obj);
|
|
1215
|
+
}
|
|
1221
1216
|
function validateInput(schema, input) {
|
|
1222
1217
|
const result = toZod(schema).safeParse(input ?? {});
|
|
1223
1218
|
if (result.success) return null;
|
|
@@ -1614,8 +1609,22 @@ function parseGrammarAction(content, knownToolNames) {
|
|
|
1614
1609
|
}
|
|
1615
1610
|
}
|
|
1616
1611
|
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
1617
|
-
const args2 = obj.arguments ?? {};
|
|
1618
1612
|
if (!name) return null;
|
|
1613
|
+
let args2;
|
|
1614
|
+
const wrapped = obj.arguments ?? obj.parameters ?? obj.input ?? obj.args;
|
|
1615
|
+
if (typeof wrapped === "string") {
|
|
1616
|
+
try {
|
|
1617
|
+
const parsed = JSON.parse(wrapped);
|
|
1618
|
+
args2 = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1619
|
+
} catch {
|
|
1620
|
+
args2 = {};
|
|
1621
|
+
}
|
|
1622
|
+
} else if (wrapped && typeof wrapped === "object" && !Array.isArray(wrapped)) {
|
|
1623
|
+
args2 = wrapped;
|
|
1624
|
+
} else {
|
|
1625
|
+
const { name: _n, ...rest } = obj;
|
|
1626
|
+
args2 = rest;
|
|
1627
|
+
}
|
|
1619
1628
|
if (name === "respond") {
|
|
1620
1629
|
const message = typeof args2.message === "string" ? args2.message : "";
|
|
1621
1630
|
return { kind: "respond", message };
|
|
@@ -1707,6 +1716,30 @@ function readGuard(name, input, seen) {
|
|
|
1707
1716
|
const verb = name === "edit_file" ? "edit" : "overwrite";
|
|
1708
1717
|
return `Refusing to ${verb} ${p}: you have not read it this turn. Call read_file on ${p} first, then retry the ${name}.`;
|
|
1709
1718
|
}
|
|
1719
|
+
function unwrapEnvelope(name, input) {
|
|
1720
|
+
if (!("arguments" in input)) return input;
|
|
1721
|
+
if ("name" in input && input.name !== name) return input;
|
|
1722
|
+
let args2 = input.arguments;
|
|
1723
|
+
if (typeof args2 === "string") {
|
|
1724
|
+
try {
|
|
1725
|
+
args2 = JSON.parse(args2);
|
|
1726
|
+
} catch {
|
|
1727
|
+
return input;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (args2 && typeof args2 === "object" && !Array.isArray(args2)) return args2;
|
|
1731
|
+
return input;
|
|
1732
|
+
}
|
|
1733
|
+
function splitWriteHint(name, cause) {
|
|
1734
|
+
const lead = cause === "truncated" ? `Your response was cut off at the output token limit, so this ${name} call is incomplete and was NOT run.` : `Your ${name} call arrived with missing or garbled arguments \u2014 usually the response was cut off or mangled while writing a large value. It was NOT run.`;
|
|
1735
|
+
return `${lead} Do not resend the whole file in one call. Instead create the file with write_file containing only the first portion, then append the rest with successive edit_file calls. Keep each call small.`;
|
|
1736
|
+
}
|
|
1737
|
+
function looksTruncatedWrite(name, input) {
|
|
1738
|
+
if (!BIG_WRITE_TOOLS.has(name)) return false;
|
|
1739
|
+
if (typeof input.path !== "string" || !input.path) return true;
|
|
1740
|
+
if (name === "write_file" && typeof input.content !== "string") return true;
|
|
1741
|
+
return false;
|
|
1742
|
+
}
|
|
1710
1743
|
function markSeen(name, input, seen) {
|
|
1711
1744
|
if (name !== "read_file" && name !== "edit_file" && name !== "write_file") return;
|
|
1712
1745
|
const p = input.path;
|
|
@@ -1719,11 +1752,7 @@ function markSeen(name, input, seen) {
|
|
|
1719
1752
|
async function* runAgent(opts) {
|
|
1720
1753
|
const { model, cwd, permissions, hooks, signal, num_ctx } = opts;
|
|
1721
1754
|
const startTime = Date.now();
|
|
1722
|
-
|
|
1723
|
-
if (providerName() === "ollama") {
|
|
1724
|
-
const params = await modelParamCountB(model);
|
|
1725
|
-
useGrammar = params == null || params <= GRAMMAR_MAX_PARAMS_B;
|
|
1726
|
-
}
|
|
1755
|
+
const useGrammar = false;
|
|
1727
1756
|
const system = buildSystemPrompt(TOOLS, cwd, loadProjectContext(cwd), useGrammar);
|
|
1728
1757
|
const grammar = useGrammar ? buildToolGrammar(TOOLS) : void 0;
|
|
1729
1758
|
const ollamaTools = toOllamaTools(TOOLS);
|
|
@@ -1743,9 +1772,11 @@ async function* runAgent(opts) {
|
|
|
1743
1772
|
let tool_calls;
|
|
1744
1773
|
let respondEmitted = 0;
|
|
1745
1774
|
let streamedRespond = false;
|
|
1775
|
+
let emittedText = false;
|
|
1746
1776
|
let lastTail = "";
|
|
1747
1777
|
let tailRepeats = 0;
|
|
1748
1778
|
let streamLooped = false;
|
|
1779
|
+
let truncated = false;
|
|
1749
1780
|
const ac = new AbortController();
|
|
1750
1781
|
const composedSignal = signal ? AbortSignal.any ? AbortSignal.any([signal, ac.signal]) : ac.signal : ac.signal;
|
|
1751
1782
|
if (signal) signal.addEventListener("abort", () => ac.abort(), { once: true });
|
|
@@ -1755,12 +1786,14 @@ async function* runAgent(opts) {
|
|
|
1755
1786
|
if (chunk.content) {
|
|
1756
1787
|
text += chunk.content;
|
|
1757
1788
|
if (!useGrammar) {
|
|
1789
|
+
emittedText = true;
|
|
1758
1790
|
yield { type: "text-delta", text: chunk.content };
|
|
1759
1791
|
} else {
|
|
1760
1792
|
const r = streamRespondMessage(text);
|
|
1761
1793
|
if (r) {
|
|
1762
1794
|
streamedRespond = true;
|
|
1763
1795
|
if (r.message.length > respondEmitted) {
|
|
1796
|
+
emittedText = true;
|
|
1764
1797
|
yield { type: "text-delta", text: r.message.slice(respondEmitted) };
|
|
1765
1798
|
respondEmitted = r.message.length;
|
|
1766
1799
|
}
|
|
@@ -1781,7 +1814,7 @@ async function* runAgent(opts) {
|
|
|
1781
1814
|
}
|
|
1782
1815
|
}
|
|
1783
1816
|
}
|
|
1784
|
-
if (chunk.thinking) {
|
|
1817
|
+
if (chunk.thinking && !emittedText) {
|
|
1785
1818
|
yield { type: "thinking-delta", text: chunk.thinking };
|
|
1786
1819
|
}
|
|
1787
1820
|
if (chunk.tool_calls && chunk.tool_calls.length > 0) {
|
|
@@ -1790,6 +1823,7 @@ async function* runAgent(opts) {
|
|
|
1790
1823
|
if (chunk.done) {
|
|
1791
1824
|
promptTokens += chunk.prompt_eval_count ?? 0;
|
|
1792
1825
|
evalTokens += chunk.eval_count ?? 0;
|
|
1826
|
+
if (chunk.done_reason === "length") truncated = true;
|
|
1793
1827
|
}
|
|
1794
1828
|
}
|
|
1795
1829
|
} catch (err) {
|
|
@@ -1828,6 +1862,19 @@ async function* runAgent(opts) {
|
|
|
1828
1862
|
}
|
|
1829
1863
|
const tool_uses = blocks.filter((b) => b.type === "tool_use");
|
|
1830
1864
|
history.push({ role: "assistant", content: blocks });
|
|
1865
|
+
if (truncated && tool_uses.length > 0) {
|
|
1866
|
+
const results2 = tool_uses.map((use) => ({
|
|
1867
|
+
type: "tool_result",
|
|
1868
|
+
tool_use_id: use.id,
|
|
1869
|
+
content: splitWriteHint(use.name, "truncated"),
|
|
1870
|
+
is_error: true
|
|
1871
|
+
}));
|
|
1872
|
+
for (const u of tool_uses) yield { type: "tool-use", block: u };
|
|
1873
|
+
for (const r of results2) yield { type: "tool-result", block: r };
|
|
1874
|
+
history.push({ role: "user", content: results2 });
|
|
1875
|
+
yield { type: "turn-end", stop_reason: "tool_use" };
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1831
1878
|
if (tool_uses.length === 0) {
|
|
1832
1879
|
yield { type: "turn-end", stop_reason: "end_turn" };
|
|
1833
1880
|
break;
|
|
@@ -1862,12 +1909,14 @@ async function* runAgent(opts) {
|
|
|
1862
1909
|
yield { type: "tool-result", block: r2 };
|
|
1863
1910
|
continue;
|
|
1864
1911
|
}
|
|
1912
|
+
use.input = unwrapEnvelope(use.name, use.input);
|
|
1865
1913
|
const invalid = validateInput(tool.input_schema, use.input);
|
|
1866
1914
|
if (invalid) {
|
|
1915
|
+
const content = looksTruncatedWrite(use.name, use.input) ? splitWriteHint(use.name, "garbled") : `${invalid} for ${use.name}. Pass the arguments directly as the tool input \u2014 do NOT wrap them in {"name":...,"arguments":...}. Correct shape: ${exampleInput(tool.input_schema)}. Retry with all required fields.`;
|
|
1867
1916
|
const r2 = {
|
|
1868
1917
|
type: "tool_result",
|
|
1869
1918
|
tool_use_id: use.id,
|
|
1870
|
-
content
|
|
1919
|
+
content,
|
|
1871
1920
|
is_error: true
|
|
1872
1921
|
};
|
|
1873
1922
|
results.push(r2);
|
|
@@ -1934,7 +1983,7 @@ async function* runAgent(opts) {
|
|
|
1934
1983
|
yield { type: "done", prompt_tokens: promptTokens, eval_tokens: evalTokens };
|
|
1935
1984
|
return history;
|
|
1936
1985
|
}
|
|
1937
|
-
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL,
|
|
1986
|
+
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL, BIG_WRITE_TOOLS;
|
|
1938
1987
|
var init_loop = __esm({
|
|
1939
1988
|
"src/agent/loop.ts"() {
|
|
1940
1989
|
"use strict";
|
|
@@ -1951,7 +2000,7 @@ var init_loop = __esm({
|
|
|
1951
2000
|
MAX_TURNS = 25;
|
|
1952
2001
|
REPEAT_TAIL = 120;
|
|
1953
2002
|
REPEAT_KILL = 4;
|
|
1954
|
-
|
|
2003
|
+
BIG_WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
|
|
1955
2004
|
}
|
|
1956
2005
|
});
|
|
1957
2006
|
|
|
@@ -2670,7 +2719,7 @@ import { Box as Box12, Text as Text12, Static } from "ink";
|
|
|
2670
2719
|
// src/ui/markdown.ts
|
|
2671
2720
|
import { Marked } from "marked";
|
|
2672
2721
|
import { markedTerminal } from "marked-terminal";
|
|
2673
|
-
import { highlight } from "cli-highlight";
|
|
2722
|
+
import { highlight, supportsLanguage } from "cli-highlight";
|
|
2674
2723
|
|
|
2675
2724
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
2676
2725
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -3194,7 +3243,7 @@ var theme = {
|
|
|
3194
3243
|
// faint rule
|
|
3195
3244
|
};
|
|
3196
3245
|
function highlightCode(code, lang) {
|
|
3197
|
-
if (!lang) return code;
|
|
3246
|
+
if (!lang || !supportsLanguage(lang)) return code;
|
|
3198
3247
|
try {
|
|
3199
3248
|
return highlight(code, { language: lang, ignoreIllegals: true });
|
|
3200
3249
|
} catch {
|
|
@@ -3292,7 +3341,7 @@ import { Box as Box10, Text as Text10 } from "ink";
|
|
|
3292
3341
|
|
|
3293
3342
|
// src/ui/ToolBlock.tsx
|
|
3294
3343
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
3295
|
-
import { highlight as highlight2 } from "cli-highlight";
|
|
3344
|
+
import { highlight as highlight2, supportsLanguage as supportsLanguage2 } from "cli-highlight";
|
|
3296
3345
|
|
|
3297
3346
|
// src/ui/toolExpand.ts
|
|
3298
3347
|
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
@@ -3427,7 +3476,7 @@ function langFromPath(path) {
|
|
|
3427
3476
|
return ext ? EXT_LANG[ext] : void 0;
|
|
3428
3477
|
}
|
|
3429
3478
|
function highlightLine(text, lang) {
|
|
3430
|
-
if (!lang) return text;
|
|
3479
|
+
if (!lang || !supportsLanguage2(lang)) return text;
|
|
3431
3480
|
try {
|
|
3432
3481
|
return highlight2(text, { language: lang, ignoreIllegals: true });
|
|
3433
3482
|
} catch {
|
|
@@ -4628,7 +4677,6 @@ function App() {
|
|
|
4628
4677
|
header: /* @__PURE__ */ jsx13(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd })
|
|
4629
4678
|
}
|
|
4630
4679
|
),
|
|
4631
|
-
updateAvailable && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: miii --update` }) }),
|
|
4632
4680
|
input.startsWith("/") && /* @__PURE__ */ jsx13(CommandPalette, { filter: input, cursor: paletteCursor }),
|
|
4633
4681
|
contextWarning !== null && /* @__PURE__ */ jsx13(Box13, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: `\u26A0 context ${contextWarning}% full \u2014 run /clear and start fresh` }) }),
|
|
4634
4682
|
!input.startsWith("/") && (() => {
|
package/package.json
CHANGED