code-ollama 0.21.0 → 0.22.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.
- package/README.md +3 -0
- package/dist/assets/{tui-48fT4HLs.js → tui-CzkVRFXf.js} +202 -85
- package/dist/cli.js +274 -44
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
npx code-ollama
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
> [!IMPORTANT]
|
|
25
|
+
> If you see an error that says server/model is unavailable, then follow these [steps](https://github.com/ai-action/code-ollama/wiki/Ollama).
|
|
26
|
+
|
|
24
27
|
## Install
|
|
25
28
|
|
|
26
29
|
Install the [CLI](https://www.npmjs.com/package/code-ollama) globally:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as VERSION, A as loadConfig, B as ASSISTANT, C as checkHealth, D as pullModel, E as listModels, F as withSystemMessage, G as CATALOG, H as USER, I as HEADER_PREFIX, J as PLAN, K as AUTO, L as WARNING, M as removeClipboardImage, N as saveClipboardImage, O as sanitizeAssistantContent, P as resetSystemMessage, Q as NAME, R as LIST$1, S as TOOL_INTENT_CORRECTION, T as hasUncalledToolIntent, U as PLAN_GENERATION_INSTRUCTION, V as SYSTEM, W as BACK, X as APPROVE, Y as SAFE, Z as REJECT, _ as loadSession, a as normalizeToolCall, b as reset, c as WRITE_TOOLS, d as write, et as LIST, f as appendMessage, g as listSessions, h as deleteSessionIfEmpty, i as formatToolResultContent, j as saveConfig, k as streamChat, l as tick, m as deleteSession, n as executeTool, o as READ_TOOLS, p as createSession, q as LABEL, r as executeToolCall, s as TOOLS, t as checkForUpdate, u as color, v as updateSessionModel, w as deleteModel, x as setClearHandler, y as clear, z as getTheme } from "../cli.js";
|
|
2
2
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -11,6 +11,16 @@ import { Marked } from "marked";
|
|
|
11
11
|
import { markedTerminal } from "marked-terminal";
|
|
12
12
|
//#region src/components/CodeBlock/CodeBlock.tsx
|
|
13
13
|
var highlightCache = /* @__PURE__ */ new Map();
|
|
14
|
+
var DIFF_LANGUAGE = "diff";
|
|
15
|
+
function getDiffLineColor(line, isSystem, theme) {
|
|
16
|
+
switch (true) {
|
|
17
|
+
case isSystem: return theme.colors.messageSystem;
|
|
18
|
+
case line.startsWith("+") && !line.startsWith("+++"): return "green";
|
|
19
|
+
case line.startsWith("-") && !line.startsWith("---"): return "red";
|
|
20
|
+
case line.startsWith("@@"): return theme.colors.accent;
|
|
21
|
+
case line.startsWith("---") || line.startsWith("+++"): return theme.colors.secondary;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
14
24
|
var CODE_BLOCK_REGEX = /^(?<indent>[ \t]*)(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\k<indent>\2[ \t]*$/gm;
|
|
15
25
|
function normalizeCodeBlockContent(content, indent = "") {
|
|
16
26
|
if (!indent) return content.trim();
|
|
@@ -48,6 +58,7 @@ async function highlightCode(code, language = "text", codeTheme = getTheme().cod
|
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
var CodeBlock = memo(function CodeBlock({ code, language, role, theme = getTheme() }) {
|
|
61
|
+
const isDiff = language === DIFF_LANGUAGE;
|
|
51
62
|
const cacheKey = `${theme.codeTheme}:${language ?? ""}:${code}`;
|
|
52
63
|
const [highlighted, setHighlighted] = useState(() => highlightCache.get(cacheKey) ?? code);
|
|
53
64
|
useEffect(() => {
|
|
@@ -76,7 +87,13 @@ var CodeBlock = memo(function CodeBlock({ code, language, role, theme = getTheme
|
|
|
76
87
|
borderColor: isSystem ? theme.colors.secondary : theme.colors.codeBorder,
|
|
77
88
|
paddingX: 1,
|
|
78
89
|
marginY: 1,
|
|
79
|
-
children:
|
|
90
|
+
children: isDiff ? code.split("\n").map((line, index) => {
|
|
91
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
92
|
+
color: getDiffLineColor(line, isSystem, theme),
|
|
93
|
+
dimColor: isSystem,
|
|
94
|
+
children: line || " "
|
|
95
|
+
}, index);
|
|
96
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
80
97
|
color: isSystem ? theme.colors.messageSystem : void 0,
|
|
81
98
|
dimColor: isSystem,
|
|
82
99
|
children: highlighted
|
|
@@ -393,6 +410,24 @@ function renderStickyPaddingLines(count) {
|
|
|
393
410
|
index
|
|
394
411
|
));
|
|
395
412
|
}
|
|
413
|
+
function ToolResultMessage({ message, messageColor, theme }) {
|
|
414
|
+
const diffContent = message.toolResult?.diff?.visible;
|
|
415
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
416
|
+
flexDirection: "column",
|
|
417
|
+
marginBottom: 1,
|
|
418
|
+
marginX: 2,
|
|
419
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
420
|
+
color: messageColor,
|
|
421
|
+
dimColor: true,
|
|
422
|
+
children: message.content
|
|
423
|
+
}), diffContent && /* @__PURE__ */ jsx(CodeBlock, {
|
|
424
|
+
code: diffContent,
|
|
425
|
+
language: "diff",
|
|
426
|
+
role: "assistant",
|
|
427
|
+
theme
|
|
428
|
+
})]
|
|
429
|
+
});
|
|
430
|
+
}
|
|
396
431
|
function Message({ message, isStreaming = false, theme }) {
|
|
397
432
|
const messageColor = getMessageColor(message.role, theme);
|
|
398
433
|
const isSystem = message.role === SYSTEM;
|
|
@@ -403,16 +438,23 @@ function Message({ message, isStreaming = false, theme }) {
|
|
|
403
438
|
columns: stdout.columns,
|
|
404
439
|
maxHeight: 0
|
|
405
440
|
});
|
|
406
|
-
if (isSystem)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
441
|
+
if (isSystem) {
|
|
442
|
+
if (message.toolResult?.diff) return /* @__PURE__ */ jsx(ToolResultMessage, {
|
|
443
|
+
message,
|
|
444
|
+
messageColor,
|
|
445
|
+
theme
|
|
446
|
+
});
|
|
447
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
448
|
+
flexDirection: "column",
|
|
449
|
+
marginBottom: 1,
|
|
450
|
+
marginX: 2,
|
|
451
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
452
|
+
color: messageColor,
|
|
453
|
+
dimColor: true,
|
|
454
|
+
children: message.content
|
|
455
|
+
})
|
|
456
|
+
});
|
|
457
|
+
}
|
|
416
458
|
if (isUser) {
|
|
417
459
|
// v8 ignore start
|
|
418
460
|
const attachmentPrefix = (message.images ?? []).map((path) => `[${path.split(/[\\/]/).at(-1) ?? path}]`).join(" ");
|
|
@@ -1362,6 +1404,8 @@ function hasExecutablePlan(content) {
|
|
|
1362
1404
|
}
|
|
1363
1405
|
//#endregion
|
|
1364
1406
|
//#region src/components/Chat/Chat.tsx
|
|
1407
|
+
var MAX_TOOL_TURNS = 25;
|
|
1408
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1365
1409
|
function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onModeChange, sessionId, theme = getTheme() }) {
|
|
1366
1410
|
const sessionMessages = initialMessages ?? [];
|
|
1367
1411
|
const history = useMemo(() => sessionMessages.flatMap(({ role, content }) => role === "user" && !content.startsWith("/") ? [content] : []), [sessionMessages]);
|
|
@@ -1388,7 +1432,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1388
1432
|
persistedSnapshotRef.current = snapshot;
|
|
1389
1433
|
onMessagesChange?.(messages);
|
|
1390
1434
|
}, [messages, onMessagesChange]);
|
|
1391
|
-
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
1435
|
+
const buildToolResultMessage = useCallback((toolName, result, args) => {
|
|
1392
1436
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
1393
1437
|
role: SYSTEM,
|
|
1394
1438
|
content: [
|
|
@@ -1400,7 +1444,11 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1400
1444
|
};
|
|
1401
1445
|
return {
|
|
1402
1446
|
role: SYSTEM,
|
|
1403
|
-
content:
|
|
1447
|
+
content: formatToolResultContent(toolName, result, args),
|
|
1448
|
+
toolResult: {
|
|
1449
|
+
name: toolName,
|
|
1450
|
+
...result.diff ? { diff: result.diff } : {}
|
|
1451
|
+
}
|
|
1404
1452
|
};
|
|
1405
1453
|
}, []);
|
|
1406
1454
|
const buildPlanModeCorrectionMessage = useCallback((toolName) => ({
|
|
@@ -1425,72 +1473,124 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1425
1473
|
}]);
|
|
1426
1474
|
}, []);
|
|
1427
1475
|
const processStream = useCallback(async (currentMessages, executionMode = mode) => {
|
|
1476
|
+
const modelName = model;
|
|
1428
1477
|
// v8 ignore next
|
|
1429
|
-
if (!
|
|
1478
|
+
if (!modelName) throw new Error("Model is required");
|
|
1430
1479
|
const controller = new AbortController();
|
|
1431
1480
|
abortControllerRef.current = controller;
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
};
|
|
1436
|
-
let committedMessages = currentMessages;
|
|
1437
|
-
let assistantCommitted = false;
|
|
1438
|
-
const commitAssistantMessage = () => {
|
|
1439
|
-
if (assistantCommitted) {
|
|
1440
|
-
// v8 ignore next
|
|
1441
|
-
if (committedMessages.at(-1)?.role === "assistant") {
|
|
1442
|
-
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
1443
|
-
setMessages(committedMessages);
|
|
1444
|
-
}
|
|
1445
|
-
return committedMessages;
|
|
1446
|
-
}
|
|
1447
|
-
assistantCommitted = true;
|
|
1448
|
-
setStreamingMessage(null);
|
|
1449
|
-
if (!assistantMessage.content) {
|
|
1450
|
-
setMessages(committedMessages);
|
|
1451
|
-
return committedMessages;
|
|
1452
|
-
}
|
|
1453
|
-
committedMessages = [...committedMessages, { ...assistantMessage }];
|
|
1454
|
-
setMessages(committedMessages);
|
|
1455
|
-
return committedMessages;
|
|
1456
|
-
};
|
|
1457
|
-
setStreamingMessage(assistantMessage);
|
|
1481
|
+
let activeMessages = currentMessages;
|
|
1482
|
+
let toolTurns = 0;
|
|
1483
|
+
let toolIntentCorrections = 0;
|
|
1458
1484
|
try {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1485
|
+
while (!controller.signal.aborted) {
|
|
1486
|
+
const assistantMessage = {
|
|
1487
|
+
role: ASSISTANT,
|
|
1488
|
+
content: ""
|
|
1489
|
+
};
|
|
1490
|
+
let committedMessages = activeMessages;
|
|
1491
|
+
let assistantCommitted = false;
|
|
1492
|
+
const commitAssistantMessage = () => {
|
|
1493
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1467
1494
|
// v8 ignore start
|
|
1468
|
-
|
|
1495
|
+
if (assistantCommitted) {
|
|
1496
|
+
if (committedMessages.at(-1)?.role === "assistant") {
|
|
1497
|
+
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
1498
|
+
setMessages(committedMessages);
|
|
1499
|
+
}
|
|
1500
|
+
return committedMessages;
|
|
1501
|
+
}
|
|
1469
1502
|
// v8 ignore stop
|
|
1503
|
+
assistantCommitted = true;
|
|
1504
|
+
setStreamingMessage(null);
|
|
1505
|
+
if (!assistantMessage.content) {
|
|
1506
|
+
setMessages(committedMessages);
|
|
1507
|
+
return committedMessages;
|
|
1508
|
+
}
|
|
1509
|
+
committedMessages = [...committedMessages, { ...assistantMessage }];
|
|
1510
|
+
setMessages(committedMessages);
|
|
1511
|
+
return committedMessages;
|
|
1512
|
+
};
|
|
1513
|
+
setStreamingMessage(assistantMessage);
|
|
1514
|
+
let nextMessages = null;
|
|
1515
|
+
for await (const chunk of streamChat(withSystemMessage(activeMessages), modelName, TOOLS, controller.signal)) {
|
|
1516
|
+
if (chunk.type === "content") {
|
|
1517
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1518
|
+
setStreamingMessage({ ...assistantMessage });
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (chunk.tool_calls.length === 0) continue;
|
|
1470
1522
|
const updatedMessages = commitAssistantMessage();
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1523
|
+
const toolResultMessages = [];
|
|
1524
|
+
for (const toolCall of chunk.tool_calls) try {
|
|
1525
|
+
const normalized = normalizeToolCall(toolCall);
|
|
1526
|
+
if (executionMode === "safe" && normalized.requiresApproval) {
|
|
1527
|
+
setPendingToolCall({
|
|
1528
|
+
toolCall,
|
|
1529
|
+
messages: [...updatedMessages, ...toolResultMessages],
|
|
1530
|
+
executionMode
|
|
1531
|
+
});
|
|
1532
|
+
setIsLoading(false);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
// v8 ignore next
|
|
1536
|
+
const allowedTools = executionMode === "plan" ? READ_TOOLS : void 0;
|
|
1537
|
+
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools });
|
|
1538
|
+
toolResultMessages.push(buildToolResultMessage(normalized.name, result, normalized.arguments));
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
toolResultMessages.push(buildToolResultMessage(toolCall.function.name, {
|
|
1541
|
+
content: "",
|
|
1542
|
+
// v8 ignore next
|
|
1543
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1544
|
+
}));
|
|
1545
|
+
}
|
|
1546
|
+
nextMessages = [...updatedMessages, ...toolResultMessages];
|
|
1547
|
+
setMessages(nextMessages);
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
if (!nextMessages) {
|
|
1551
|
+
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1552
|
+
const updatedMessages = commitAssistantMessage();
|
|
1553
|
+
if (hasUncalledToolIntent(assistantMessage.content) && toolIntentCorrections < MAX_TOOL_INTENT_CORRECTIONS) {
|
|
1554
|
+
toolIntentCorrections += 1;
|
|
1555
|
+
activeMessages = [...updatedMessages, {
|
|
1556
|
+
role: SYSTEM,
|
|
1557
|
+
content: TOOL_INTENT_CORRECTION
|
|
1558
|
+
}];
|
|
1559
|
+
setMessages(activeMessages);
|
|
1560
|
+
continue;
|
|
1475
1561
|
}
|
|
1476
|
-
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
|
|
1477
|
-
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
1478
|
-
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1479
|
-
setMessages(newMessages);
|
|
1480
|
-
await processStream(newMessages, executionMode);
|
|
1481
1562
|
return;
|
|
1482
1563
|
}
|
|
1564
|
+
toolTurns += 1;
|
|
1565
|
+
toolIntentCorrections = 0;
|
|
1566
|
+
/* v8 ignore start */
|
|
1567
|
+
if (toolTurns >= MAX_TOOL_TURNS) {
|
|
1568
|
+
setMessages([...nextMessages, {
|
|
1569
|
+
role: SYSTEM,
|
|
1570
|
+
content: [
|
|
1571
|
+
"Tool execution stopped because the maximum tool turn limit was reached",
|
|
1572
|
+
ACTION_NOT_PERFORMED,
|
|
1573
|
+
"Summarize completed work and explain what remains without calling more tools."
|
|
1574
|
+
].join("\n")
|
|
1575
|
+
}]);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
/* v8 ignore stop */
|
|
1579
|
+
activeMessages = nextMessages;
|
|
1483
1580
|
}
|
|
1484
|
-
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1485
|
-
commitAssistantMessage();
|
|
1486
1581
|
} catch (error) {
|
|
1487
1582
|
// v8 ignore next
|
|
1488
1583
|
if (!controller.signal.aborted) {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1584
|
+
const errorMessage = {
|
|
1585
|
+
role: ASSISTANT,
|
|
1586
|
+
content: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1587
|
+
};
|
|
1588
|
+
await prewarmCodeBlocks(errorMessage.content, theme);
|
|
1589
|
+
setStreamingMessage(null);
|
|
1590
|
+
setMessages([...activeMessages, errorMessage]);
|
|
1492
1591
|
}
|
|
1493
1592
|
} finally {
|
|
1593
|
+
// v8 ignore next
|
|
1494
1594
|
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
1495
1595
|
setIsLoading(false);
|
|
1496
1596
|
}
|
|
@@ -1513,6 +1613,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1513
1613
|
let committedMessages = currentMessages;
|
|
1514
1614
|
let assistantCommitted = false;
|
|
1515
1615
|
const commitAssistantMessage = () => {
|
|
1616
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1516
1617
|
if (assistantCommitted) {
|
|
1517
1618
|
// v8 ignore next
|
|
1518
1619
|
if (committedMessages.at(-1)?.role === "assistant") {
|
|
@@ -1538,19 +1639,33 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1538
1639
|
// v8 ignore next 3
|
|
1539
1640
|
if (controller.signal.aborted) return;
|
|
1540
1641
|
if (chunk.type === "content") {
|
|
1541
|
-
assistantMessage.content
|
|
1642
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1542
1643
|
setStreamingMessage({ ...assistantMessage });
|
|
1543
1644
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
1544
1645
|
const updatedMessages = commitAssistantMessage();
|
|
1545
|
-
|
|
1546
|
-
|
|
1646
|
+
let normalized;
|
|
1647
|
+
try {
|
|
1648
|
+
normalized = normalizeToolCall(toolCall);
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
/* v8 ignore start */
|
|
1651
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, {
|
|
1652
|
+
content: "",
|
|
1653
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1654
|
+
});
|
|
1655
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1656
|
+
setMessages(newMessages);
|
|
1657
|
+
await processStreamReadOnly(newMessages);
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (!READ_TOOLS.has(normalized.name)) {
|
|
1661
|
+
const correctionMessage = buildPlanModeCorrectionMessage(normalized.name);
|
|
1547
1662
|
const newMessages = [...updatedMessages, correctionMessage];
|
|
1548
1663
|
setMessages(newMessages);
|
|
1549
1664
|
await processStreamReadOnly(newMessages);
|
|
1550
1665
|
return;
|
|
1551
1666
|
}
|
|
1552
|
-
const result = await executeTool(
|
|
1553
|
-
const toolResultMessage = buildToolResultMessage(
|
|
1667
|
+
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools: READ_TOOLS });
|
|
1668
|
+
const toolResultMessage = buildToolResultMessage(normalized.name, result, normalized.arguments);
|
|
1554
1669
|
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1555
1670
|
setMessages(newMessages);
|
|
1556
1671
|
await processStreamReadOnly(newMessages);
|
|
@@ -1574,7 +1689,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1574
1689
|
// v8 ignore next 3
|
|
1575
1690
|
if (controller.signal.aborted) return;
|
|
1576
1691
|
if (chunk.type === "content") {
|
|
1577
|
-
planAssistantMessage.content
|
|
1692
|
+
planAssistantMessage.content = sanitizeAssistantContent(planAssistantMessage.content + chunk.content);
|
|
1578
1693
|
setStreamingMessage({ ...planAssistantMessage });
|
|
1579
1694
|
}
|
|
1580
1695
|
}
|
|
@@ -1641,33 +1756,35 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1641
1756
|
const handleToolApproval = useCallback(async (decision) => {
|
|
1642
1757
|
// v8 ignore next
|
|
1643
1758
|
if (!pendingToolCall) return;
|
|
1644
|
-
const toolCall = pendingToolCall;
|
|
1759
|
+
const { executionMode, messages: approvedMessages, toolCall } = pendingToolCall;
|
|
1645
1760
|
setPendingToolCall(null);
|
|
1646
1761
|
setIsLoading(true);
|
|
1647
1762
|
switch (decision) {
|
|
1648
1763
|
case APPROVE: {
|
|
1649
|
-
const result = await
|
|
1764
|
+
const result = await executeToolCall(toolCall);
|
|
1765
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result, toolCall.function.arguments);
|
|
1766
|
+
const newMessages = [...approvedMessages, toolResultMessage];
|
|
1767
|
+
setMessages(newMessages);
|
|
1768
|
+
await processStream(newMessages, executionMode);
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1771
|
+
case REJECT: {
|
|
1650
1772
|
const toolResultMessage = {
|
|
1651
1773
|
role: SYSTEM,
|
|
1652
|
-
content:
|
|
1774
|
+
content: formatToolResultContent(toolCall.function.name, {
|
|
1775
|
+
content: "",
|
|
1776
|
+
error: "Tool call rejected by user"
|
|
1777
|
+
}, toolCall.function.arguments)
|
|
1653
1778
|
};
|
|
1654
|
-
|
|
1655
|
-
setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
|
|
1656
|
-
await processStream(newMessages);
|
|
1657
|
-
break;
|
|
1658
|
-
}
|
|
1659
|
-
case REJECT:
|
|
1660
|
-
setMessages((previousMessages) => [...previousMessages, {
|
|
1661
|
-
role: USER,
|
|
1662
|
-
content: TURN_ABORTED_MESSAGE
|
|
1663
|
-
}]);
|
|
1779
|
+
setMessages([...approvedMessages, toolResultMessage]);
|
|
1664
1780
|
setIsLoading(false);
|
|
1665
1781
|
setInterruptReason(InterruptReason.Rejected);
|
|
1666
1782
|
break;
|
|
1783
|
+
}
|
|
1667
1784
|
}
|
|
1668
1785
|
}, [
|
|
1786
|
+
buildToolResultMessage,
|
|
1669
1787
|
pendingToolCall,
|
|
1670
|
-
messages,
|
|
1671
1788
|
processStream
|
|
1672
1789
|
]);
|
|
1673
1790
|
const handleSubmit = useCallback(async ({ content, images }) => {
|
|
@@ -1711,7 +1828,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1711
1828
|
theme
|
|
1712
1829
|
}),
|
|
1713
1830
|
!pendingPlan && pendingToolCall && /* @__PURE__ */ jsx(ToolApproval, {
|
|
1714
|
-
toolCall: pendingToolCall,
|
|
1831
|
+
toolCall: pendingToolCall.toolCall,
|
|
1715
1832
|
onDecision: handleToolApproval,
|
|
1716
1833
|
theme
|
|
1717
1834
|
}),
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,18 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import { Ollama } from "ollama";
|
|
9
9
|
import { v7 } from "uuid";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
|
+
//#region \0rolldown/runtime.js
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __exportAll = (all, no_symbols) => {
|
|
14
|
+
let target = {};
|
|
15
|
+
for (var name in all) __defProp(target, name, {
|
|
16
|
+
get: all[name],
|
|
17
|
+
enumerable: true
|
|
18
|
+
});
|
|
19
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
20
|
+
return target;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
11
23
|
//#region src/constants/command.ts
|
|
12
24
|
var LIST$1 = [
|
|
13
25
|
{
|
|
@@ -38,7 +50,7 @@ var LIST$1 = [
|
|
|
38
50
|
//#endregion
|
|
39
51
|
//#region package.json
|
|
40
52
|
var name = "code-ollama";
|
|
41
|
-
var version = "0.
|
|
53
|
+
var version = "0.22.0";
|
|
42
54
|
//#endregion
|
|
43
55
|
//#region src/constants/package.ts
|
|
44
56
|
var NAME = name;
|
|
@@ -97,11 +109,11 @@ var BACK = {
|
|
|
97
109
|
var BASE_SYSTEM_PROMPT = `You are a coding assistant that helps users write, edit, and understand code. You have access to tools for reading files, writing files, running shell commands, searching code, and searching the web
|
|
98
110
|
|
|
99
111
|
Follow these rules:
|
|
100
|
-
1.
|
|
101
|
-
2.
|
|
102
|
-
3.
|
|
103
|
-
4.
|
|
104
|
-
5.
|
|
112
|
+
1. Use available tools rather than guessing file contents, paths, or code behavior
|
|
113
|
+
2. When a tool is needed, call it immediately instead of saying you will call it
|
|
114
|
+
3. Read files before editing them to understand context
|
|
115
|
+
4. Make the smallest exact change that satisfies the request
|
|
116
|
+
5. Explain after tool results are available, unless the user asks for discussion or a plan
|
|
105
117
|
6. Confirm with the user before destructive operations
|
|
106
118
|
|
|
107
119
|
When tools return results, incorporate them into your response naturally`;
|
|
@@ -119,7 +131,12 @@ Always use tools when you need to:
|
|
|
119
131
|
- Make file changes
|
|
120
132
|
- Explore project structure
|
|
121
133
|
- Search the codebase
|
|
122
|
-
- Look up current or external information
|
|
134
|
+
- Look up current or external information
|
|
135
|
+
|
|
136
|
+
Path rules:
|
|
137
|
+
- Paths are relative to the project root unless absolute
|
|
138
|
+
- Preserve parent directories from listings; if list_dir("src") returns [d] utils, use src/utils
|
|
139
|
+
- If a path fails, inspect the parent directory or search before retrying`;
|
|
123
140
|
var PLAN_GENERATION_INSTRUCTION = `Based on the research above, decide whether the user request needs code or shell execution
|
|
124
141
|
|
|
125
142
|
If the request needs changes or commands, respond with a plan checklist only
|
|
@@ -280,6 +297,17 @@ function getTheme(themeId = DEFAULT_THEME_ID) {
|
|
|
280
297
|
}
|
|
281
298
|
//#endregion
|
|
282
299
|
//#region src/constants/tool.ts
|
|
300
|
+
var tool_exports = /* @__PURE__ */ __exportAll({
|
|
301
|
+
EDIT_FILE: () => EDIT_FILE,
|
|
302
|
+
GREP_SEARCH: () => GREP_SEARCH,
|
|
303
|
+
LIST_DIR: () => LIST_DIR,
|
|
304
|
+
READ_FILE: () => READ_FILE,
|
|
305
|
+
RUN_SHELL: () => RUN_SHELL,
|
|
306
|
+
VIEW_RANGE: () => VIEW_RANGE,
|
|
307
|
+
WEB_FETCH: () => WEB_FETCH,
|
|
308
|
+
WEB_SEARCH: () => WEB_SEARCH,
|
|
309
|
+
WRITE_FILE: () => WRITE_FILE
|
|
310
|
+
});
|
|
283
311
|
var READ_FILE = "read_file";
|
|
284
312
|
var WRITE_FILE = "write_file";
|
|
285
313
|
var EDIT_FILE = "edit_file";
|
|
@@ -467,6 +495,15 @@ function saveConfig(patch) {
|
|
|
467
495
|
//#region src/utils/ollama.ts
|
|
468
496
|
var { host } = loadConfig();
|
|
469
497
|
var client = new Ollama({ host });
|
|
498
|
+
var TRAILING_CONTROL_TOKEN_REGEX = /(?:\s*<\|?channel\|?>)+\s*$/;
|
|
499
|
+
var TOOL_INTENT_REGEX = /\b(?:i\s+(?:will|am going to)|next,\s*i\s+will|now\s+i\s+will|first,\s*i\s+will)\b[\s\S]*\b(?:read|inspect|check|list|search|update|edit|write|modify|run)\b/i;
|
|
500
|
+
var TOOL_INTENT_CORRECTION = "You said you would use a tool but did not call one. Continue by calling the appropriate tool now. Do not describe the tool call.";
|
|
501
|
+
function sanitizeAssistantContent(content) {
|
|
502
|
+
return content.replace(TRAILING_CONTROL_TOKEN_REGEX, "");
|
|
503
|
+
}
|
|
504
|
+
function hasUncalledToolIntent(content) {
|
|
505
|
+
return TOOL_INTENT_REGEX.test(content);
|
|
506
|
+
}
|
|
470
507
|
async function checkHealth() {
|
|
471
508
|
try {
|
|
472
509
|
return (await fetch(host)).ok;
|
|
@@ -475,9 +512,15 @@ async function checkHealth() {
|
|
|
475
512
|
}
|
|
476
513
|
}
|
|
477
514
|
async function* streamChat(messages, model, tools, signal) {
|
|
515
|
+
const providerMessages = messages.map(({ role, content, images, tool_calls }) => ({
|
|
516
|
+
role,
|
|
517
|
+
content,
|
|
518
|
+
...images ? { images } : {},
|
|
519
|
+
...tool_calls ? { tool_calls } : {}
|
|
520
|
+
}));
|
|
478
521
|
const response = await client.chat({
|
|
479
522
|
model,
|
|
480
|
-
messages,
|
|
523
|
+
messages: providerMessages,
|
|
481
524
|
stream: true,
|
|
482
525
|
tools,
|
|
483
526
|
// v8 ignore next
|
|
@@ -759,10 +802,10 @@ var TOOLS = [
|
|
|
759
802
|
type: "string",
|
|
760
803
|
description: "The path to the directory to list"
|
|
761
804
|
} }, ["path"]),
|
|
762
|
-
defineTool(GREP_SEARCH, "Search
|
|
805
|
+
defineTool(GREP_SEARCH, "Search files within a directory; multi-word queries also match common code identifier forms", {
|
|
763
806
|
pattern: {
|
|
764
807
|
type: "string",
|
|
765
|
-
description: "The regex
|
|
808
|
+
description: "The regex, phrase, or code concept to search for"
|
|
766
809
|
},
|
|
767
810
|
path: {
|
|
768
811
|
type: "string",
|
|
@@ -838,6 +881,76 @@ async function runShell(command) {
|
|
|
838
881
|
}
|
|
839
882
|
//#endregion
|
|
840
883
|
//#region src/utils/tools/filesystem.ts
|
|
884
|
+
var DIFF_CONTEXT_LINES = 3;
|
|
885
|
+
var DIFF_MAX_LINES = 120;
|
|
886
|
+
var DIFF_MAX_CHARS = 12e3;
|
|
887
|
+
function splitLines(content) {
|
|
888
|
+
return content.split("\n");
|
|
889
|
+
}
|
|
890
|
+
function createUnifiedDiff(filePath, beforeContent, afterContent) {
|
|
891
|
+
const beforeLines = splitLines(beforeContent);
|
|
892
|
+
const afterLines = splitLines(afterContent);
|
|
893
|
+
let commonPrefix = 0;
|
|
894
|
+
while (commonPrefix < beforeLines.length && commonPrefix < afterLines.length && beforeLines[commonPrefix] === afterLines[commonPrefix]) commonPrefix += 1;
|
|
895
|
+
let commonSuffix = 0;
|
|
896
|
+
while (commonSuffix < beforeLines.length - commonPrefix && commonSuffix < afterLines.length - commonPrefix && beforeLines[beforeLines.length - 1 - commonSuffix] === afterLines[afterLines.length - 1 - commonSuffix]) commonSuffix += 1;
|
|
897
|
+
const beforeChangeEnd = beforeLines.length - commonSuffix;
|
|
898
|
+
const afterChangeEnd = afterLines.length - commonSuffix;
|
|
899
|
+
const hunkStart = Math.max(0, commonPrefix - DIFF_CONTEXT_LINES);
|
|
900
|
+
const beforeHunkEnd = Math.min(beforeLines.length, beforeChangeEnd + DIFF_CONTEXT_LINES);
|
|
901
|
+
const afterHunkEnd = Math.min(afterLines.length, afterChangeEnd + DIFF_CONTEXT_LINES);
|
|
902
|
+
const beforeHunkLines = beforeLines.slice(hunkStart, beforeHunkEnd);
|
|
903
|
+
const afterHunkLines = afterLines.slice(hunkStart, afterHunkEnd);
|
|
904
|
+
const beforeChangedLines = beforeLines.slice(commonPrefix, beforeChangeEnd);
|
|
905
|
+
const afterChangedLines = afterLines.slice(commonPrefix, afterChangeEnd);
|
|
906
|
+
const contextBefore = beforeLines.slice(hunkStart, commonPrefix);
|
|
907
|
+
const contextAfter = beforeLines.slice(beforeChangeEnd, beforeHunkEnd);
|
|
908
|
+
return [
|
|
909
|
+
`--- ${filePath}`,
|
|
910
|
+
`+++ ${filePath}`,
|
|
911
|
+
`@@ -${String(hunkStart + 1)},${String(beforeHunkLines.length)} +${String(hunkStart + 1)},${String(afterHunkLines.length)} @@`,
|
|
912
|
+
...contextBefore.map((line) => ` ${line}`),
|
|
913
|
+
...beforeChangedLines.map((line) => `-${line}`),
|
|
914
|
+
...afterChangedLines.map((line) => `+${line}`),
|
|
915
|
+
...contextAfter.map((line) => ` ${line}`)
|
|
916
|
+
].join("\n");
|
|
917
|
+
}
|
|
918
|
+
function truncateDiff(diff) {
|
|
919
|
+
const lines = diff.split("\n");
|
|
920
|
+
let visibleLines = lines.slice(0, DIFF_MAX_LINES);
|
|
921
|
+
let truncated = lines.length > DIFF_MAX_LINES;
|
|
922
|
+
while (visibleLines.join("\n").length > DIFF_MAX_CHARS) {
|
|
923
|
+
visibleLines = visibleLines.slice(0, -1);
|
|
924
|
+
truncated = true;
|
|
925
|
+
}
|
|
926
|
+
if (truncated) visibleLines = [...visibleLines, `[diff truncated: showing ${String(visibleLines.length)} of ${String(lines.length)} lines]`];
|
|
927
|
+
return {
|
|
928
|
+
visible: visibleLines.join("\n"),
|
|
929
|
+
truncated,
|
|
930
|
+
totalLines: lines.length,
|
|
931
|
+
visibleLines: Math.min(visibleLines.length, lines.length)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function buildSearchPatterns(pattern) {
|
|
935
|
+
const words = pattern.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
936
|
+
if (words.length < 2) return [pattern];
|
|
937
|
+
const camelCase = words.map((word, index) => index === 0 ? word.toLowerCase() : capitalize(word.toLowerCase())).join("");
|
|
938
|
+
const pascalCase = words.map((word) => capitalize(word.toLowerCase())).join("");
|
|
939
|
+
const snakeCase = words.map((word) => word.toLowerCase()).join("_");
|
|
940
|
+
const upperSnakeCase = snakeCase.toUpperCase();
|
|
941
|
+
const flexibleWhitespace = words.join(String.raw`\s+`);
|
|
942
|
+
return Array.from(new Set([
|
|
943
|
+
pattern,
|
|
944
|
+
flexibleWhitespace,
|
|
945
|
+
snakeCase,
|
|
946
|
+
upperSnakeCase,
|
|
947
|
+
camelCase,
|
|
948
|
+
pascalCase
|
|
949
|
+
]));
|
|
950
|
+
}
|
|
951
|
+
function capitalize(value) {
|
|
952
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
953
|
+
}
|
|
841
954
|
/**
|
|
842
955
|
* Read file contents
|
|
843
956
|
*/
|
|
@@ -887,8 +1000,19 @@ function editFile(filePath, oldText, newText) {
|
|
|
887
1000
|
content: "",
|
|
888
1001
|
error: `Exact text matched multiple locations in file: ${filePath}`
|
|
889
1002
|
};
|
|
890
|
-
|
|
891
|
-
|
|
1003
|
+
const updatedContent = content.replace(oldText, newText);
|
|
1004
|
+
const truncatedDiff = truncateDiff(createUnifiedDiff(filePath, content, updatedContent));
|
|
1005
|
+
writeFileSync(filePath, updatedContent, "utf8");
|
|
1006
|
+
return {
|
|
1007
|
+
content: `File edited successfully: ${filePath}`,
|
|
1008
|
+
diff: {
|
|
1009
|
+
path: filePath,
|
|
1010
|
+
visible: truncatedDiff.visible,
|
|
1011
|
+
truncated: truncatedDiff.truncated,
|
|
1012
|
+
totalLines: truncatedDiff.totalLines,
|
|
1013
|
+
visibleLines: truncatedDiff.visibleLines
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
892
1016
|
} catch (error) {
|
|
893
1017
|
return {
|
|
894
1018
|
content: "",
|
|
@@ -943,17 +1067,17 @@ function listDir(dirPath) {
|
|
|
943
1067
|
* Search for pattern in files using ripgrep if available, fallback to Node.js
|
|
944
1068
|
*/
|
|
945
1069
|
async function grepSearch(pattern, dirPath) {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
return { content: stdout
|
|
1070
|
+
const patterns = buildSearchPatterns(pattern);
|
|
1071
|
+
for (const searchPattern of patterns) try {
|
|
1072
|
+
const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${searchPattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
|
|
1073
|
+
if (stdout) return { content: stdout };
|
|
950
1074
|
} catch {}
|
|
951
1075
|
try {
|
|
952
1076
|
if (!existsSync(dirPath)) return {
|
|
953
1077
|
content: "",
|
|
954
1078
|
error: `Directory not found: ${dirPath}`
|
|
955
1079
|
};
|
|
956
|
-
const
|
|
1080
|
+
const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
|
|
957
1081
|
const results = [];
|
|
958
1082
|
function searchDirectory(currentPath) {
|
|
959
1083
|
const entries = readdirSync(currentPath, { withFileTypes: true });
|
|
@@ -963,9 +1087,9 @@ async function grepSearch(pattern, dirPath) {
|
|
|
963
1087
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
|
|
964
1088
|
} else if (entry.isFile()) try {
|
|
965
1089
|
const lines = readFileSync(fullPath, "utf8").split("\n");
|
|
966
|
-
for (let i = 0; i < lines.length; i++) {
|
|
967
|
-
|
|
968
|
-
|
|
1090
|
+
for (let i = 0; i < lines.length; i++) for (const regex of regexes) if (regex.test(lines[i])) {
|
|
1091
|
+
results.push(`${fullPath}:${(i + 1).toString()}: ${lines[i].trim()}`);
|
|
1092
|
+
break;
|
|
969
1093
|
}
|
|
970
1094
|
} catch {}
|
|
971
1095
|
}
|
|
@@ -1170,18 +1294,93 @@ var REQUIRED_STRING_ARGS = {
|
|
|
1170
1294
|
[WEB_SEARCH]: ["query"],
|
|
1171
1295
|
[WEB_FETCH]: ["url"]
|
|
1172
1296
|
};
|
|
1297
|
+
var TOOL_NAMES = new Set(Object.values(tool_exports));
|
|
1298
|
+
function isToolName(name) {
|
|
1299
|
+
return TOOL_NAMES.has(name);
|
|
1300
|
+
}
|
|
1173
1301
|
function validateArgs(name, args) {
|
|
1174
|
-
const required = REQUIRED_STRING_ARGS[name]
|
|
1302
|
+
const required = REQUIRED_STRING_ARGS[name];
|
|
1175
1303
|
const received = Object.keys(args).join(", ") || "none";
|
|
1176
|
-
for (const key of required) if (typeof args[key] !== "string") return {
|
|
1304
|
+
for (const key of required) if (typeof args[key] !== "string" || !args[key]) return {
|
|
1177
1305
|
content: "",
|
|
1178
1306
|
error: `Missing required argument: ${key} (received keys: ${received})`
|
|
1179
1307
|
};
|
|
1308
|
+
if (name === "view_range") {
|
|
1309
|
+
if (!Number.isInteger(args.start) || !Number.isInteger(args.end)) return {
|
|
1310
|
+
content: "",
|
|
1311
|
+
error: `Missing required numeric arguments: start, end (received keys: ${received})`
|
|
1312
|
+
};
|
|
1313
|
+
if (args.start < 1 || args.end < args.start) return {
|
|
1314
|
+
content: "",
|
|
1315
|
+
error: "Invalid line range: start must be >= 1 and end must be >= start"
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
if (name === "web_fetch") try {
|
|
1319
|
+
const url = new URL(args.url);
|
|
1320
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return {
|
|
1321
|
+
content: "",
|
|
1322
|
+
error: "URL must use http or https"
|
|
1323
|
+
};
|
|
1324
|
+
} catch {
|
|
1325
|
+
return {
|
|
1326
|
+
content: "",
|
|
1327
|
+
error: "Invalid URL"
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function normalizeToolCall(toolCall) {
|
|
1332
|
+
const name = toolCall.function.name;
|
|
1333
|
+
const rawArguments = toolCall.function.arguments;
|
|
1334
|
+
if (!isToolName(name)) throw new Error(`Unknown tool: ${name}`);
|
|
1335
|
+
if (typeof rawArguments !== "object" || rawArguments === null || Array.isArray(rawArguments)) throw new Error(`Invalid arguments for tool: ${name}`);
|
|
1336
|
+
const normalizedArguments = rawArguments;
|
|
1337
|
+
const invalid = validateArgs(name, normalizedArguments);
|
|
1338
|
+
if (invalid?.error) throw new Error(invalid.error);
|
|
1339
|
+
return {
|
|
1340
|
+
name,
|
|
1341
|
+
arguments: normalizedArguments,
|
|
1342
|
+
requiresApproval: WRITE_TOOLS.has(name)
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function formatToolResultContent(toolName, result, args) {
|
|
1346
|
+
const formattedArgs = args ? `(${formatToolArguments(args)})` : "";
|
|
1347
|
+
const status = result.error ? "The requested action was NOT performed" : "";
|
|
1348
|
+
const content = result.content ? `\n${result.content}` : "";
|
|
1349
|
+
const error = result.error ? `\nError: ${result.error}` : "";
|
|
1350
|
+
return [
|
|
1351
|
+
`Tool ${toolName}${formattedArgs} result:`,
|
|
1352
|
+
status,
|
|
1353
|
+
content.trim(),
|
|
1354
|
+
error.trim()
|
|
1355
|
+
].filter(Boolean).join("\n");
|
|
1356
|
+
}
|
|
1357
|
+
function formatToolArguments(args) {
|
|
1358
|
+
return JSON.stringify(args, (_, value) => {
|
|
1359
|
+
if (typeof value !== "string") return value;
|
|
1360
|
+
if (value.length <= 80 && !value.includes("\n")) return value;
|
|
1361
|
+
return `<${String(value.length)} chars>`;
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
async function executeToolCall(toolCall, options) {
|
|
1365
|
+
try {
|
|
1366
|
+
const normalized = normalizeToolCall(toolCall);
|
|
1367
|
+
return await executeTool(normalized.name, normalized.arguments, options);
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
return {
|
|
1370
|
+
content: "",
|
|
1371
|
+
// v8 ignore next
|
|
1372
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1180
1375
|
}
|
|
1181
1376
|
/**
|
|
1182
1377
|
* Execute a tool by name with arguments
|
|
1183
1378
|
*/
|
|
1184
1379
|
async function executeTool(name, args, options) {
|
|
1380
|
+
if (!isToolName(name)) return {
|
|
1381
|
+
content: "",
|
|
1382
|
+
error: `Unknown tool: ${name}`
|
|
1383
|
+
};
|
|
1185
1384
|
if (options?.allowedTools && !options.allowedTools.has(name)) return {
|
|
1186
1385
|
content: "",
|
|
1187
1386
|
error: `Tool not allowed: ${name}`
|
|
@@ -1199,6 +1398,7 @@ async function executeTool(name, args, options) {
|
|
|
1199
1398
|
case VIEW_RANGE: return viewRange(stringArgs.path, args.start, args.end);
|
|
1200
1399
|
case WEB_SEARCH: return await webSearch(stringArgs.query);
|
|
1201
1400
|
case WEB_FETCH: return await webFetch(stringArgs.url);
|
|
1401
|
+
// v8 ignore next 2
|
|
1202
1402
|
default: return {
|
|
1203
1403
|
content: "",
|
|
1204
1404
|
error: `Unknown tool: ${name}`
|
|
@@ -1240,6 +1440,8 @@ async function checkForUpdate() {
|
|
|
1240
1440
|
//#endregion
|
|
1241
1441
|
//#region src/cli.ts
|
|
1242
1442
|
var cli = cac("code-ollama");
|
|
1443
|
+
var MAX_TOOL_TURNS = 25;
|
|
1444
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1243
1445
|
cli.version(VERSION);
|
|
1244
1446
|
cli.help();
|
|
1245
1447
|
cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model, prompt) => {
|
|
@@ -1272,30 +1474,58 @@ async function runPrompt(model, prompt) {
|
|
|
1272
1474
|
write("\n");
|
|
1273
1475
|
}
|
|
1274
1476
|
async function processRunStream(messages, model) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1477
|
+
let activeMessages = messages;
|
|
1478
|
+
let toolTurns = 0;
|
|
1479
|
+
let toolIntentCorrections = 0;
|
|
1480
|
+
while (toolTurns < MAX_TOOL_TURNS) {
|
|
1481
|
+
const assistantMessage = {
|
|
1482
|
+
role: ASSISTANT,
|
|
1483
|
+
content: ""
|
|
1484
|
+
};
|
|
1485
|
+
let nextMessages = null;
|
|
1486
|
+
for await (const chunk of streamChat(activeMessages, model, TOOLS)) {
|
|
1487
|
+
if (chunk.type === "content") {
|
|
1488
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1489
|
+
write(chunk.content);
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1493
|
+
// v8 ignore next 3
|
|
1494
|
+
if (chunk.tool_calls.length === 0) continue;
|
|
1495
|
+
const committedMessages = [...activeMessages, assistantMessage];
|
|
1496
|
+
const toolResultMessages = [];
|
|
1497
|
+
for (const toolCall of chunk.tool_calls) {
|
|
1498
|
+
const result = await executeToolCall(toolCall);
|
|
1499
|
+
toolResultMessages.push({
|
|
1500
|
+
role: SYSTEM,
|
|
1501
|
+
content: formatToolResultContent(toolCall.function.name, result, toolCall.function.arguments)
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
nextMessages = [...committedMessages, ...toolResultMessages];
|
|
1505
|
+
break;
|
|
1284
1506
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1507
|
+
if (!nextMessages) {
|
|
1508
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1509
|
+
if (hasUncalledToolIntent(assistantMessage.content) && toolIntentCorrections < MAX_TOOL_INTENT_CORRECTIONS) {
|
|
1510
|
+
toolIntentCorrections += 1;
|
|
1511
|
+
activeMessages = [
|
|
1512
|
+
...activeMessages,
|
|
1513
|
+
assistantMessage,
|
|
1514
|
+
{
|
|
1515
|
+
role: SYSTEM,
|
|
1516
|
+
content: TOOL_INTENT_CORRECTION
|
|
1517
|
+
}
|
|
1518
|
+
];
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1296
1521
|
return;
|
|
1297
1522
|
}
|
|
1523
|
+
activeMessages = nextMessages;
|
|
1524
|
+
toolTurns += 1;
|
|
1525
|
+
toolIntentCorrections = 0;
|
|
1298
1526
|
}
|
|
1527
|
+
// v8 ignore next 3
|
|
1528
|
+
writeError("Tool execution stopped because the maximum tool turn limit was reached\n");
|
|
1299
1529
|
}
|
|
1300
1530
|
async function main(args = process.argv.slice(2)) {
|
|
1301
1531
|
if (args.length) cli.parse([
|
|
@@ -1306,7 +1536,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1306
1536
|
else await launchTui();
|
|
1307
1537
|
}
|
|
1308
1538
|
async function launchTui(sessionId) {
|
|
1309
|
-
const { renderApp } = await import("./assets/tui-
|
|
1539
|
+
const { renderApp } = await import("./assets/tui-CzkVRFXf.js");
|
|
1310
1540
|
reset();
|
|
1311
1541
|
renderApp(sessionId);
|
|
1312
1542
|
}
|
|
@@ -1322,4 +1552,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1322
1552
|
if (isEntrypoint()) main();
|
|
1323
1553
|
// v8 ignore stop
|
|
1324
1554
|
//#endregion
|
|
1325
|
-
export {
|
|
1555
|
+
export { VERSION as $, loadConfig as A, ASSISTANT as B, checkHealth as C, pullModel as D, listModels as E, withSystemMessage as F, CATALOG as G, USER as H, HEADER_PREFIX as I, PLAN as J, AUTO as K, WARNING as L, removeClipboardImage as M, saveClipboardImage as N, sanitizeAssistantContent as O, resetSystemMessage as P, NAME as Q, LIST as R, TOOL_INTENT_CORRECTION as S, hasUncalledToolIntent as T, PLAN_GENERATION_INSTRUCTION as U, SYSTEM as V, BACK as W, APPROVE as X, SAFE as Y, REJECT as Z, loadSession as _, normalizeToolCall as a, reset as b, WRITE_TOOLS as c, write as d, LIST$1 as et, appendMessage as f, listSessions as g, deleteSessionIfEmpty as h, formatToolResultContent as i, saveConfig as j, streamChat as k, tick as l, deleteSession as m, main, executeTool as n, READ_TOOLS as o, createSession as p, LABEL as q, executeToolCall as r, TOOLS as s, checkForUpdate as t, color as u, updateSessionModel as v, deleteModel as w, setClearHandler as x, clear as y, getTheme as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Ollama coding agent that runs in your terminal",
|
|
5
5
|
"author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
|
|
6
6
|
"type": "module",
|
|
@@ -42,34 +42,34 @@
|
|
|
42
42
|
"@inkjs/ui": "2.0.0",
|
|
43
43
|
"@shikijs/cli": "4.1.0",
|
|
44
44
|
"cac": "7.0.0",
|
|
45
|
-
"ink": "7.0.
|
|
45
|
+
"ink": "7.0.5",
|
|
46
46
|
"marked": "15.0.12",
|
|
47
47
|
"marked-terminal": "7.3.0",
|
|
48
48
|
"ollama": "0.6.3",
|
|
49
|
-
"react": "19.2.
|
|
49
|
+
"react": "19.2.7",
|
|
50
50
|
"uuid": "14.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@commitlint/cli": "21.0.
|
|
54
|
-
"@commitlint/config-conventional": "21.0.
|
|
53
|
+
"@commitlint/cli": "21.0.2",
|
|
54
|
+
"@commitlint/config-conventional": "21.0.2",
|
|
55
55
|
"@eslint/js": "10.0.1",
|
|
56
56
|
"@types/node": "25.9.1",
|
|
57
|
-
"@types/react": "19.2.
|
|
58
|
-
"@vitest/coverage-v8": "4.1.
|
|
59
|
-
"eslint": "10.4.
|
|
60
|
-
"eslint-plugin-prettier": "5.5.
|
|
57
|
+
"@types/react": "19.2.16",
|
|
58
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
59
|
+
"eslint": "10.4.1",
|
|
60
|
+
"eslint-plugin-prettier": "5.5.6",
|
|
61
61
|
"eslint-plugin-simple-import-sort": "13.0.0",
|
|
62
62
|
"globals": "17.6.0",
|
|
63
63
|
"husky": "9.1.7",
|
|
64
64
|
"ink-testing-library": "4.0.0",
|
|
65
|
-
"lint-staged": "17.0.
|
|
65
|
+
"lint-staged": "17.0.7",
|
|
66
66
|
"prettier": "3.8.3",
|
|
67
67
|
"publint": "0.3.21",
|
|
68
|
-
"tsx": "4.22.
|
|
68
|
+
"tsx": "4.22.4",
|
|
69
69
|
"typescript": "6.0.3",
|
|
70
|
-
"typescript-eslint": "8.
|
|
71
|
-
"vite": "8.0.
|
|
72
|
-
"vitest": "4.1.
|
|
70
|
+
"typescript-eslint": "8.60.1",
|
|
71
|
+
"vite": "8.0.16",
|
|
72
|
+
"vitest": "4.1.8"
|
|
73
73
|
},
|
|
74
74
|
"files": [
|
|
75
75
|
"dist/"
|