miii-agent 0.1.22 → 0.1.24
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 +36 -255
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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;
|
|
@@ -214,8 +190,7 @@ async function* chat(entry, model, messages, tools, opts) {
|
|
|
214
190
|
keep_alive: opts?.keep_alive ?? "10m",
|
|
215
191
|
options
|
|
216
192
|
};
|
|
217
|
-
if (
|
|
218
|
-
else if (tools) req.tools = tools;
|
|
193
|
+
if (tools) req.tools = tools;
|
|
219
194
|
try {
|
|
220
195
|
stream = await client.chat(
|
|
221
196
|
req
|
|
@@ -508,9 +483,6 @@ var init_openai = __esm({
|
|
|
508
483
|
function active() {
|
|
509
484
|
return resolveProvider();
|
|
510
485
|
}
|
|
511
|
-
function providerName() {
|
|
512
|
-
return active().name;
|
|
513
|
-
}
|
|
514
486
|
function isAvailable3() {
|
|
515
487
|
const { entry } = active();
|
|
516
488
|
return entry.type === "ollama" ? isAvailable(entry) : isAvailable2(entry);
|
|
@@ -527,16 +499,6 @@ async function modelContext3(model) {
|
|
|
527
499
|
const { entry } = active();
|
|
528
500
|
return entry.type === "ollama" ? modelContext(entry, model) : modelContext2(entry, model);
|
|
529
501
|
}
|
|
530
|
-
async function modelParamCountB(model) {
|
|
531
|
-
const { entry } = active();
|
|
532
|
-
if (entry.type !== "ollama") return null;
|
|
533
|
-
const key = `${entry.baseUrl}:${model}`;
|
|
534
|
-
const cached = paramCountCache.get(key);
|
|
535
|
-
if (cached !== void 0) return cached;
|
|
536
|
-
const params = await paramCountB(entry, model);
|
|
537
|
-
paramCountCache.set(key, params);
|
|
538
|
-
return params;
|
|
539
|
-
}
|
|
540
502
|
async function* chat3(model, messages, tools, opts) {
|
|
541
503
|
const { entry } = active();
|
|
542
504
|
if (entry.type === "ollama") {
|
|
@@ -545,70 +507,12 @@ async function* chat3(model, messages, tools, opts) {
|
|
|
545
507
|
yield* chat2(entry, model, messages, tools, opts);
|
|
546
508
|
}
|
|
547
509
|
}
|
|
548
|
-
var paramCountCache;
|
|
549
510
|
var init_client = __esm({
|
|
550
511
|
"src/llm/client.ts"() {
|
|
551
512
|
"use strict";
|
|
552
513
|
init_config();
|
|
553
514
|
init_ollama();
|
|
554
515
|
init_openai();
|
|
555
|
-
paramCountCache = /* @__PURE__ */ new Map();
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
// src/llm/grammar.ts
|
|
560
|
-
function argProperties(props) {
|
|
561
|
-
const out = {};
|
|
562
|
-
for (const [key, spec] of Object.entries(props)) {
|
|
563
|
-
const node = { type: spec.type };
|
|
564
|
-
if (spec.enum && spec.enum.length) node.enum = spec.enum;
|
|
565
|
-
out[key] = node;
|
|
566
|
-
}
|
|
567
|
-
return out;
|
|
568
|
-
}
|
|
569
|
-
function toolBranch(tool) {
|
|
570
|
-
const args2 = {
|
|
571
|
-
type: "object",
|
|
572
|
-
additionalProperties: false,
|
|
573
|
-
properties: argProperties(tool.input_schema.properties)
|
|
574
|
-
};
|
|
575
|
-
if (tool.input_schema.required && tool.input_schema.required.length) {
|
|
576
|
-
args2.required = tool.input_schema.required;
|
|
577
|
-
}
|
|
578
|
-
return {
|
|
579
|
-
type: "object",
|
|
580
|
-
additionalProperties: false,
|
|
581
|
-
required: ["name", "arguments"],
|
|
582
|
-
properties: {
|
|
583
|
-
name: { const: tool.name },
|
|
584
|
-
arguments: args2
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
function respondBranch() {
|
|
589
|
-
return {
|
|
590
|
-
type: "object",
|
|
591
|
-
additionalProperties: false,
|
|
592
|
-
required: ["name", "arguments"],
|
|
593
|
-
properties: {
|
|
594
|
-
name: { const: RESPOND_ACTION },
|
|
595
|
-
arguments: {
|
|
596
|
-
type: "object",
|
|
597
|
-
additionalProperties: false,
|
|
598
|
-
required: ["message"],
|
|
599
|
-
properties: { message: { type: "string" } }
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
function buildToolGrammar(tools) {
|
|
605
|
-
return { oneOf: [...tools.map(toolBranch), respondBranch()] };
|
|
606
|
-
}
|
|
607
|
-
var RESPOND_ACTION;
|
|
608
|
-
var init_grammar = __esm({
|
|
609
|
-
"src/llm/grammar.ts"() {
|
|
610
|
-
"use strict";
|
|
611
|
-
RESPOND_ACTION = "respond";
|
|
612
516
|
}
|
|
613
517
|
});
|
|
614
518
|
|
|
@@ -1304,15 +1208,8 @@ var init_context = __esm({
|
|
|
1304
1208
|
});
|
|
1305
1209
|
|
|
1306
1210
|
// src/prompt/system.ts
|
|
1307
|
-
function buildSystemPrompt(tools, cwd, project
|
|
1211
|
+
function buildSystemPrompt(tools, cwd, project) {
|
|
1308
1212
|
const toolLines = tools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
|
|
1309
|
-
const actionProtocol = grammarMode ? `
|
|
1310
|
-
# Action protocol (strict)
|
|
1311
|
-
Every reply is exactly ONE JSON action object, nothing else \u2014 no prose outside it, no markdown, no fences. Decoding is grammar-constrained, so malformed output is impossible; your only job is to choose the right action.
|
|
1312
|
-
To use a tool: {"name": "<tool_name>", "arguments": { ...that tool's args }}
|
|
1313
|
-
To give your final answer to the user: {"name": "respond", "arguments": {"message": "<your full answer here>"}}
|
|
1314
|
-
Call tools until the GOAL is met, then emit a single "respond" action with the complete answer. The "respond" action is the ONLY way to end the turn and talk to the user \u2014 never put your final answer in a tool call.
|
|
1315
|
-
` : "";
|
|
1316
1213
|
const projectSection = project && project.content.trim() ? `
|
|
1317
1214
|
# ${CONTEXT_FILENAME} \u2014 project instructions (authoritative, read first)
|
|
1318
1215
|
The user maintains ${CONTEXT_FILENAME} at ${project.source} to steer how you work in this project: conventions, commands, architecture, do's and don'ts. Treat it as direct instruction from the user, higher priority than your defaults. When it conflicts with a default rule below, ${CONTEXT_FILENAME} wins (except permissions and safety, which you never override).${project.truncated ? `
|
|
@@ -1397,7 +1294,7 @@ Ask in a numbered list. One round of questions per turn. Then wait.
|
|
|
1397
1294
|
# Tools
|
|
1398
1295
|
You have access to the following tools. Call them via the function-calling interface.
|
|
1399
1296
|
${toolLines}
|
|
1400
|
-
|
|
1297
|
+
|
|
1401
1298
|
# Loop semantics
|
|
1402
1299
|
- When you need to act on the filesystem or run a command, emit a tool call.
|
|
1403
1300
|
- After each tool result, decide: more tool calls, or a final plain-text answer.
|
|
@@ -1627,89 +1524,6 @@ function extractFirstJsonObject(s) {
|
|
|
1627
1524
|
}
|
|
1628
1525
|
return null;
|
|
1629
1526
|
}
|
|
1630
|
-
function parseGrammarAction(content, knownToolNames) {
|
|
1631
|
-
if (!content) return null;
|
|
1632
|
-
let raw = content.trim();
|
|
1633
|
-
if (!raw.startsWith("{")) {
|
|
1634
|
-
const found = extractFirstJsonObject(raw);
|
|
1635
|
-
if (!found) return null;
|
|
1636
|
-
raw = found.json;
|
|
1637
|
-
}
|
|
1638
|
-
let obj;
|
|
1639
|
-
try {
|
|
1640
|
-
obj = JSON.parse(raw);
|
|
1641
|
-
} catch {
|
|
1642
|
-
const found = extractFirstJsonObject(raw);
|
|
1643
|
-
if (!found) return null;
|
|
1644
|
-
try {
|
|
1645
|
-
obj = JSON.parse(found.json);
|
|
1646
|
-
} catch {
|
|
1647
|
-
return null;
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
1651
|
-
if (!name) return null;
|
|
1652
|
-
let args2;
|
|
1653
|
-
const wrapped = obj.arguments ?? obj.parameters ?? obj.input ?? obj.args;
|
|
1654
|
-
if (typeof wrapped === "string") {
|
|
1655
|
-
try {
|
|
1656
|
-
const parsed = JSON.parse(wrapped);
|
|
1657
|
-
args2 = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1658
|
-
} catch {
|
|
1659
|
-
args2 = {};
|
|
1660
|
-
}
|
|
1661
|
-
} else if (wrapped && typeof wrapped === "object" && !Array.isArray(wrapped)) {
|
|
1662
|
-
args2 = wrapped;
|
|
1663
|
-
} else {
|
|
1664
|
-
const { name: _n, ...rest } = obj;
|
|
1665
|
-
args2 = rest;
|
|
1666
|
-
}
|
|
1667
|
-
if (name === "respond") {
|
|
1668
|
-
const message = typeof args2.message === "string" ? args2.message : "";
|
|
1669
|
-
return { kind: "respond", message };
|
|
1670
|
-
}
|
|
1671
|
-
if (!knownToolNames.includes(name)) return null;
|
|
1672
|
-
return { kind: "tool", name, arguments: args2 };
|
|
1673
|
-
}
|
|
1674
|
-
function streamRespondMessage(text) {
|
|
1675
|
-
if (!/"name"\s*:\s*"respond"/.test(text)) return null;
|
|
1676
|
-
const m = text.match(/"message"\s*:\s*"/);
|
|
1677
|
-
if (!m || m.index == null) return null;
|
|
1678
|
-
const start = m.index + m[0].length;
|
|
1679
|
-
const escapes = {
|
|
1680
|
-
n: "\n",
|
|
1681
|
-
t: " ",
|
|
1682
|
-
r: "\r",
|
|
1683
|
-
b: "\b",
|
|
1684
|
-
f: "\f",
|
|
1685
|
-
'"': '"',
|
|
1686
|
-
"\\": "\\",
|
|
1687
|
-
"/": "/"
|
|
1688
|
-
};
|
|
1689
|
-
let out = "";
|
|
1690
|
-
let i = start;
|
|
1691
|
-
while (i < text.length) {
|
|
1692
|
-
const ch = text[i];
|
|
1693
|
-
if (ch === '"') return { message: out, complete: true };
|
|
1694
|
-
if (ch === "\\") {
|
|
1695
|
-
const nx = text[i + 1];
|
|
1696
|
-
if (nx === void 0) break;
|
|
1697
|
-
if (nx === "u") {
|
|
1698
|
-
const hex = text.slice(i + 2, i + 6);
|
|
1699
|
-
if (hex.length < 4) break;
|
|
1700
|
-
out += String.fromCharCode(parseInt(hex, 16));
|
|
1701
|
-
i += 6;
|
|
1702
|
-
continue;
|
|
1703
|
-
}
|
|
1704
|
-
out += escapes[nx] ?? nx;
|
|
1705
|
-
i += 2;
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
out += ch;
|
|
1709
|
-
i++;
|
|
1710
|
-
}
|
|
1711
|
-
return { message: out, complete: false };
|
|
1712
|
-
}
|
|
1713
1527
|
function blocksFromOllama(text, tool_calls, knownToolNames = []) {
|
|
1714
1528
|
const blocks = [];
|
|
1715
1529
|
let finalText = text;
|
|
@@ -1791,13 +1605,7 @@ function markSeen(name, input, seen) {
|
|
|
1791
1605
|
async function* runAgent(opts) {
|
|
1792
1606
|
const { model, cwd, permissions, hooks, signal, num_ctx } = opts;
|
|
1793
1607
|
const startTime = Date.now();
|
|
1794
|
-
|
|
1795
|
-
if (providerName() === "ollama") {
|
|
1796
|
-
const params = await modelParamCountB(model);
|
|
1797
|
-
useGrammar = params == null || params <= GRAMMAR_MAX_PARAMS_B;
|
|
1798
|
-
}
|
|
1799
|
-
const system = buildSystemPrompt(TOOLS, cwd, loadProjectContext(cwd), useGrammar);
|
|
1800
|
-
const grammar = useGrammar ? buildToolGrammar(TOOLS) : void 0;
|
|
1608
|
+
const system = buildSystemPrompt(TOOLS, cwd, loadProjectContext(cwd));
|
|
1801
1609
|
const ollamaTools = toOllamaTools(TOOLS);
|
|
1802
1610
|
const toolNames = TOOLS.map((t) => t.name);
|
|
1803
1611
|
const effort = EFFORT_OPTIONS[loadConfig().effort ?? "medium"];
|
|
@@ -1813,8 +1621,6 @@ async function* runAgent(opts) {
|
|
|
1813
1621
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
1814
1622
|
let text = "";
|
|
1815
1623
|
let tool_calls;
|
|
1816
|
-
let respondEmitted = 0;
|
|
1817
|
-
let streamedRespond = false;
|
|
1818
1624
|
let emittedText = false;
|
|
1819
1625
|
let lastTail = "";
|
|
1820
1626
|
let tailRepeats = 0;
|
|
@@ -1824,24 +1630,12 @@ async function* runAgent(opts) {
|
|
|
1824
1630
|
const composedSignal = signal ? AbortSignal.any ? AbortSignal.any([signal, ac.signal]) : ac.signal : ac.signal;
|
|
1825
1631
|
if (signal) signal.addEventListener("abort", () => ac.abort(), { once: true });
|
|
1826
1632
|
try {
|
|
1827
|
-
for await (const chunk of chat3(model, toOllamaMessages(history, system),
|
|
1633
|
+
for await (const chunk of chat3(model, toOllamaMessages(history, system), ollamaTools, { signal: composedSignal, num_ctx, num_predict: effort.num_predict, temperature: effort.temperature })) {
|
|
1828
1634
|
if (signal?.aborted) break;
|
|
1829
1635
|
if (chunk.content) {
|
|
1830
1636
|
text += chunk.content;
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
yield { type: "text-delta", text: chunk.content };
|
|
1834
|
-
} else {
|
|
1835
|
-
const r = streamRespondMessage(text);
|
|
1836
|
-
if (r) {
|
|
1837
|
-
streamedRespond = true;
|
|
1838
|
-
if (r.message.length > respondEmitted) {
|
|
1839
|
-
emittedText = true;
|
|
1840
|
-
yield { type: "text-delta", text: r.message.slice(respondEmitted) };
|
|
1841
|
-
respondEmitted = r.message.length;
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1637
|
+
emittedText = true;
|
|
1638
|
+
yield { type: "text-delta", text: chunk.content };
|
|
1845
1639
|
if (text.length >= REPEAT_TAIL) {
|
|
1846
1640
|
const tail = text.slice(-REPEAT_TAIL);
|
|
1847
1641
|
if (tail === lastTail) {
|
|
@@ -1890,19 +1684,7 @@ async function* runAgent(opts) {
|
|
|
1890
1684
|
};
|
|
1891
1685
|
return history;
|
|
1892
1686
|
}
|
|
1893
|
-
|
|
1894
|
-
if (useGrammar) {
|
|
1895
|
-
const action = parseGrammarAction(text, toolNames);
|
|
1896
|
-
if (action?.kind === "tool") {
|
|
1897
|
-
blocks = [{ type: "tool_use", id: mintToolUseId(), name: action.name, input: action.arguments }];
|
|
1898
|
-
} else {
|
|
1899
|
-
const message = action?.kind === "respond" ? action.message : text.trim();
|
|
1900
|
-
if (message && !streamedRespond) yield { type: "text-delta", text: message };
|
|
1901
|
-
blocks = message ? [{ type: "text", text: message }] : [];
|
|
1902
|
-
}
|
|
1903
|
-
} else {
|
|
1904
|
-
blocks = blocksFromOllama(text, tool_calls, toolNames);
|
|
1905
|
-
}
|
|
1687
|
+
const blocks = blocksFromOllama(text, tool_calls, toolNames);
|
|
1906
1688
|
const tool_uses = blocks.filter((b) => b.type === "tool_use");
|
|
1907
1689
|
history.push({ role: "assistant", content: blocks });
|
|
1908
1690
|
if (truncated && tool_uses.length > 0) {
|
|
@@ -2026,12 +1808,11 @@ async function* runAgent(opts) {
|
|
|
2026
1808
|
yield { type: "done", prompt_tokens: promptTokens, eval_tokens: evalTokens };
|
|
2027
1809
|
return history;
|
|
2028
1810
|
}
|
|
2029
|
-
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL,
|
|
1811
|
+
var MAX_TURNS, REPEAT_TAIL, REPEAT_KILL, BIG_WRITE_TOOLS;
|
|
2030
1812
|
var init_loop = __esm({
|
|
2031
1813
|
"src/agent/loop.ts"() {
|
|
2032
1814
|
"use strict";
|
|
2033
1815
|
init_client();
|
|
2034
|
-
init_grammar();
|
|
2035
1816
|
init_paths();
|
|
2036
1817
|
init_registry();
|
|
2037
1818
|
init_validate();
|
|
@@ -2043,7 +1824,6 @@ var init_loop = __esm({
|
|
|
2043
1824
|
MAX_TURNS = 25;
|
|
2044
1825
|
REPEAT_TAIL = 120;
|
|
2045
1826
|
REPEAT_KILL = 4;
|
|
2046
|
-
GRAMMAR_MAX_PARAMS_B = 14;
|
|
2047
1827
|
BIG_WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
|
|
2048
1828
|
}
|
|
2049
1829
|
});
|
|
@@ -2283,32 +2063,33 @@ import { Box, Text } from "ink";
|
|
|
2283
2063
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2284
2064
|
function WelcomeBlock({ model, activeCtx, effort, cwd, updateAvailable }) {
|
|
2285
2065
|
const ctxLabel = activeCtx != null ? `${Math.round(activeCtx / 1024)}k ctx` : "\u2014 ctx";
|
|
2286
|
-
return /* @__PURE__ */ jsxs(
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2066
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2067
|
+
/* @__PURE__ */ jsxs(
|
|
2068
|
+
Box,
|
|
2069
|
+
{
|
|
2070
|
+
flexDirection: "column",
|
|
2071
|
+
borderStyle: "round",
|
|
2072
|
+
borderColor: "gray",
|
|
2073
|
+
paddingX: 2,
|
|
2074
|
+
children: [
|
|
2075
|
+
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
2076
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "blue", children: "MIII CLI" }),
|
|
2077
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
2078
|
+
/* @__PURE__ */ jsx(Text, { children: model ?? "/models" }),
|
|
2079
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
2080
|
+
/* @__PURE__ */ jsx(Text, { children: ctxLabel }),
|
|
2081
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
|
|
2082
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2083
|
+
effort,
|
|
2084
|
+
" effort"
|
|
2085
|
+
] })
|
|
2086
|
+
] }),
|
|
2087
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: cwd })
|
|
2088
|
+
]
|
|
2089
|
+
}
|
|
2090
|
+
),
|
|
2091
|
+
updateAvailable && /* @__PURE__ */ jsx(Text, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: miii --update` })
|
|
2092
|
+
] });
|
|
2312
2093
|
}
|
|
2313
2094
|
|
|
2314
2095
|
// src/ui/InputBar.tsx
|
package/package.json
CHANGED