code-ollama 0.21.0 → 0.21.1
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-BHkHnKUC.js} +129 -73
- package/dist/cli.js +129 -27
- 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 { A as saveClipboardImage, B as PLAN_GENERATION_INSTRUCTION, C as deleteModel, D as loadConfig, E as streamChat, F as LIST$1, G as PLAN, H as CATALOG, I as getTheme, J as REJECT, K as SAFE, L as ASSISTANT, M as withSystemMessage, N as HEADER_PREFIX, O as saveConfig, P as WARNING, R as SYSTEM, S as checkHealth, T as pullModel, U as AUTO, V as BACK, W as LABEL, X as VERSION, Y as NAME, Z as LIST, _ as loadSession, a as normalizeToolCall, b as reset, c as WRITE_TOOLS, d as write, f as appendMessage, g as listSessions, h as deleteSessionIfEmpty, i as formatToolResultContent, j as resetSystemMessage, k as removeClipboardImage, l as tick, m as deleteSession, n as executeTool, o as READ_TOOLS, p as createSession, q as APPROVE, r as executeToolCall, s as TOOLS, t as checkForUpdate, u as color, v as updateSessionModel, w as listModels, x as setClearHandler, y as clear, z as USER } 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";
|
|
@@ -1362,6 +1362,7 @@ function hasExecutablePlan(content) {
|
|
|
1362
1362
|
}
|
|
1363
1363
|
//#endregion
|
|
1364
1364
|
//#region src/components/Chat/Chat.tsx
|
|
1365
|
+
var MAX_TOOL_TURNS = 25;
|
|
1365
1366
|
function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onModeChange, sessionId, theme = getTheme() }) {
|
|
1366
1367
|
const sessionMessages = initialMessages ?? [];
|
|
1367
1368
|
const history = useMemo(() => sessionMessages.flatMap(({ role, content }) => role === "user" && !content.startsWith("/") ? [content] : []), [sessionMessages]);
|
|
@@ -1400,7 +1401,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1400
1401
|
};
|
|
1401
1402
|
return {
|
|
1402
1403
|
role: SYSTEM,
|
|
1403
|
-
content:
|
|
1404
|
+
content: formatToolResultContent(toolName, result)
|
|
1404
1405
|
};
|
|
1405
1406
|
}, []);
|
|
1406
1407
|
const buildPlanModeCorrectionMessage = useCallback((toolName) => ({
|
|
@@ -1425,72 +1426,112 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1425
1426
|
}]);
|
|
1426
1427
|
}, []);
|
|
1427
1428
|
const processStream = useCallback(async (currentMessages, executionMode = mode) => {
|
|
1429
|
+
const modelName = model;
|
|
1428
1430
|
// v8 ignore next
|
|
1429
|
-
if (!
|
|
1431
|
+
if (!modelName) throw new Error("Model is required");
|
|
1430
1432
|
const controller = new AbortController();
|
|
1431
1433
|
abortControllerRef.current = controller;
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
content: ""
|
|
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);
|
|
1434
|
+
let activeMessages = currentMessages;
|
|
1435
|
+
let toolTurns = 0;
|
|
1458
1436
|
try {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1437
|
+
while (!controller.signal.aborted) {
|
|
1438
|
+
const assistantMessage = {
|
|
1439
|
+
role: ASSISTANT,
|
|
1440
|
+
content: ""
|
|
1441
|
+
};
|
|
1442
|
+
let committedMessages = activeMessages;
|
|
1443
|
+
let assistantCommitted = false;
|
|
1444
|
+
const commitAssistantMessage = () => {
|
|
1467
1445
|
// v8 ignore start
|
|
1468
|
-
|
|
1446
|
+
if (assistantCommitted) {
|
|
1447
|
+
if (committedMessages.at(-1)?.role === "assistant") {
|
|
1448
|
+
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
1449
|
+
setMessages(committedMessages);
|
|
1450
|
+
}
|
|
1451
|
+
return committedMessages;
|
|
1452
|
+
}
|
|
1469
1453
|
// v8 ignore stop
|
|
1454
|
+
assistantCommitted = true;
|
|
1455
|
+
setStreamingMessage(null);
|
|
1456
|
+
if (!assistantMessage.content) {
|
|
1457
|
+
setMessages(committedMessages);
|
|
1458
|
+
return committedMessages;
|
|
1459
|
+
}
|
|
1460
|
+
committedMessages = [...committedMessages, { ...assistantMessage }];
|
|
1461
|
+
setMessages(committedMessages);
|
|
1462
|
+
return committedMessages;
|
|
1463
|
+
};
|
|
1464
|
+
setStreamingMessage(assistantMessage);
|
|
1465
|
+
let nextMessages = null;
|
|
1466
|
+
for await (const chunk of streamChat(withSystemMessage(activeMessages), modelName, TOOLS, controller.signal)) {
|
|
1467
|
+
if (chunk.type === "content") {
|
|
1468
|
+
assistantMessage.content += chunk.content;
|
|
1469
|
+
setStreamingMessage({ ...assistantMessage });
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
if (chunk.tool_calls.length === 0) continue;
|
|
1470
1473
|
const updatedMessages = commitAssistantMessage();
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1474
|
+
const toolResultMessages = [];
|
|
1475
|
+
for (const toolCall of chunk.tool_calls) try {
|
|
1476
|
+
const normalized = normalizeToolCall(toolCall);
|
|
1477
|
+
if (executionMode === "safe" && normalized.requiresApproval) {
|
|
1478
|
+
setPendingToolCall({
|
|
1479
|
+
toolCall,
|
|
1480
|
+
messages: [...updatedMessages, ...toolResultMessages],
|
|
1481
|
+
executionMode
|
|
1482
|
+
});
|
|
1483
|
+
setIsLoading(false);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
// v8 ignore next
|
|
1487
|
+
const allowedTools = executionMode === "plan" ? READ_TOOLS : void 0;
|
|
1488
|
+
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools });
|
|
1489
|
+
toolResultMessages.push(buildToolResultMessage(normalized.name, result));
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
toolResultMessages.push(buildToolResultMessage(toolCall.function.name, {
|
|
1492
|
+
content: "",
|
|
1493
|
+
// v8 ignore next
|
|
1494
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1495
|
+
}));
|
|
1475
1496
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1497
|
+
nextMessages = [...updatedMessages, ...toolResultMessages];
|
|
1498
|
+
setMessages(nextMessages);
|
|
1499
|
+
break;
|
|
1500
|
+
}
|
|
1501
|
+
if (!nextMessages) {
|
|
1502
|
+
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1503
|
+
commitAssistantMessage();
|
|
1481
1504
|
return;
|
|
1482
1505
|
}
|
|
1506
|
+
toolTurns += 1;
|
|
1507
|
+
/* v8 ignore start */
|
|
1508
|
+
if (toolTurns >= MAX_TOOL_TURNS) {
|
|
1509
|
+
setMessages([...nextMessages, {
|
|
1510
|
+
role: SYSTEM,
|
|
1511
|
+
content: [
|
|
1512
|
+
"Tool execution stopped because the maximum tool turn limit was reached",
|
|
1513
|
+
ACTION_NOT_PERFORMED,
|
|
1514
|
+
"Summarize completed work and explain what remains without calling more tools."
|
|
1515
|
+
].join("\n")
|
|
1516
|
+
}]);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
/* v8 ignore stop */
|
|
1520
|
+
activeMessages = nextMessages;
|
|
1483
1521
|
}
|
|
1484
|
-
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1485
|
-
commitAssistantMessage();
|
|
1486
1522
|
} catch (error) {
|
|
1487
1523
|
// v8 ignore next
|
|
1488
1524
|
if (!controller.signal.aborted) {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1525
|
+
const errorMessage = {
|
|
1526
|
+
role: ASSISTANT,
|
|
1527
|
+
content: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1528
|
+
};
|
|
1529
|
+
await prewarmCodeBlocks(errorMessage.content, theme);
|
|
1530
|
+
setStreamingMessage(null);
|
|
1531
|
+
setMessages([...activeMessages, errorMessage]);
|
|
1492
1532
|
}
|
|
1493
1533
|
} finally {
|
|
1534
|
+
// v8 ignore next
|
|
1494
1535
|
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
1495
1536
|
setIsLoading(false);
|
|
1496
1537
|
}
|
|
@@ -1542,15 +1583,29 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1542
1583
|
setStreamingMessage({ ...assistantMessage });
|
|
1543
1584
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
1544
1585
|
const updatedMessages = commitAssistantMessage();
|
|
1545
|
-
|
|
1546
|
-
|
|
1586
|
+
let normalized;
|
|
1587
|
+
try {
|
|
1588
|
+
normalized = normalizeToolCall(toolCall);
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
/* v8 ignore start */
|
|
1591
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, {
|
|
1592
|
+
content: "",
|
|
1593
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1594
|
+
});
|
|
1595
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1596
|
+
setMessages(newMessages);
|
|
1597
|
+
await processStreamReadOnly(newMessages);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (!READ_TOOLS.has(normalized.name)) {
|
|
1601
|
+
const correctionMessage = buildPlanModeCorrectionMessage(normalized.name);
|
|
1547
1602
|
const newMessages = [...updatedMessages, correctionMessage];
|
|
1548
1603
|
setMessages(newMessages);
|
|
1549
1604
|
await processStreamReadOnly(newMessages);
|
|
1550
1605
|
return;
|
|
1551
1606
|
}
|
|
1552
|
-
const result = await executeTool(
|
|
1553
|
-
const toolResultMessage = buildToolResultMessage(
|
|
1607
|
+
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools: READ_TOOLS });
|
|
1608
|
+
const toolResultMessage = buildToolResultMessage(normalized.name, result);
|
|
1554
1609
|
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1555
1610
|
setMessages(newMessages);
|
|
1556
1611
|
await processStreamReadOnly(newMessages);
|
|
@@ -1641,35 +1696,36 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1641
1696
|
const handleToolApproval = useCallback(async (decision) => {
|
|
1642
1697
|
// v8 ignore next
|
|
1643
1698
|
if (!pendingToolCall) return;
|
|
1644
|
-
const toolCall = pendingToolCall;
|
|
1699
|
+
const { executionMode, messages: approvedMessages, toolCall } = pendingToolCall;
|
|
1645
1700
|
setPendingToolCall(null);
|
|
1646
1701
|
setIsLoading(true);
|
|
1647
1702
|
switch (decision) {
|
|
1648
1703
|
case APPROVE: {
|
|
1649
|
-
const result = await
|
|
1704
|
+
const result = await executeToolCall(toolCall);
|
|
1650
1705
|
const toolResultMessage = {
|
|
1651
1706
|
role: SYSTEM,
|
|
1652
|
-
content:
|
|
1707
|
+
content: formatToolResultContent(toolCall.function.name, result)
|
|
1653
1708
|
};
|
|
1654
|
-
const newMessages = [...
|
|
1655
|
-
setMessages(
|
|
1656
|
-
await processStream(newMessages);
|
|
1709
|
+
const newMessages = [...approvedMessages, toolResultMessage];
|
|
1710
|
+
setMessages(newMessages);
|
|
1711
|
+
await processStream(newMessages, executionMode);
|
|
1657
1712
|
break;
|
|
1658
1713
|
}
|
|
1659
|
-
case REJECT:
|
|
1660
|
-
|
|
1661
|
-
role:
|
|
1662
|
-
content:
|
|
1663
|
-
|
|
1714
|
+
case REJECT: {
|
|
1715
|
+
const toolResultMessage = {
|
|
1716
|
+
role: SYSTEM,
|
|
1717
|
+
content: formatToolResultContent(toolCall.function.name, {
|
|
1718
|
+
content: "",
|
|
1719
|
+
error: "Tool call rejected by user"
|
|
1720
|
+
})
|
|
1721
|
+
};
|
|
1722
|
+
setMessages([...approvedMessages, toolResultMessage]);
|
|
1664
1723
|
setIsLoading(false);
|
|
1665
1724
|
setInterruptReason(InterruptReason.Rejected);
|
|
1666
1725
|
break;
|
|
1726
|
+
}
|
|
1667
1727
|
}
|
|
1668
|
-
}, [
|
|
1669
|
-
pendingToolCall,
|
|
1670
|
-
messages,
|
|
1671
|
-
processStream
|
|
1672
|
-
]);
|
|
1728
|
+
}, [pendingToolCall, processStream]);
|
|
1673
1729
|
const handleSubmit = useCallback(async ({ content, images }) => {
|
|
1674
1730
|
setInterruptReason(null);
|
|
1675
1731
|
const userContent = content.trim();
|
|
@@ -1711,7 +1767,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1711
1767
|
theme
|
|
1712
1768
|
}),
|
|
1713
1769
|
!pendingPlan && pendingToolCall && /* @__PURE__ */ jsx(ToolApproval, {
|
|
1714
|
-
toolCall: pendingToolCall,
|
|
1770
|
+
toolCall: pendingToolCall.toolCall,
|
|
1715
1771
|
onDecision: handleToolApproval,
|
|
1716
1772
|
theme
|
|
1717
1773
|
}),
|
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.21.
|
|
53
|
+
var version = "0.21.1";
|
|
42
54
|
//#endregion
|
|
43
55
|
//#region src/constants/package.ts
|
|
44
56
|
var NAME = name;
|
|
@@ -280,6 +292,17 @@ function getTheme(themeId = DEFAULT_THEME_ID) {
|
|
|
280
292
|
}
|
|
281
293
|
//#endregion
|
|
282
294
|
//#region src/constants/tool.ts
|
|
295
|
+
var tool_exports = /* @__PURE__ */ __exportAll({
|
|
296
|
+
EDIT_FILE: () => EDIT_FILE,
|
|
297
|
+
GREP_SEARCH: () => GREP_SEARCH,
|
|
298
|
+
LIST_DIR: () => LIST_DIR,
|
|
299
|
+
READ_FILE: () => READ_FILE,
|
|
300
|
+
RUN_SHELL: () => RUN_SHELL,
|
|
301
|
+
VIEW_RANGE: () => VIEW_RANGE,
|
|
302
|
+
WEB_FETCH: () => WEB_FETCH,
|
|
303
|
+
WEB_SEARCH: () => WEB_SEARCH,
|
|
304
|
+
WRITE_FILE: () => WRITE_FILE
|
|
305
|
+
});
|
|
283
306
|
var READ_FILE = "read_file";
|
|
284
307
|
var WRITE_FILE = "write_file";
|
|
285
308
|
var EDIT_FILE = "edit_file";
|
|
@@ -1170,18 +1193,85 @@ var REQUIRED_STRING_ARGS = {
|
|
|
1170
1193
|
[WEB_SEARCH]: ["query"],
|
|
1171
1194
|
[WEB_FETCH]: ["url"]
|
|
1172
1195
|
};
|
|
1196
|
+
var TOOL_NAMES = new Set(Object.values(tool_exports));
|
|
1197
|
+
function isToolName(name) {
|
|
1198
|
+
return TOOL_NAMES.has(name);
|
|
1199
|
+
}
|
|
1173
1200
|
function validateArgs(name, args) {
|
|
1174
|
-
const required = REQUIRED_STRING_ARGS[name]
|
|
1201
|
+
const required = REQUIRED_STRING_ARGS[name];
|
|
1175
1202
|
const received = Object.keys(args).join(", ") || "none";
|
|
1176
|
-
for (const key of required) if (typeof args[key] !== "string") return {
|
|
1203
|
+
for (const key of required) if (typeof args[key] !== "string" || !args[key]) return {
|
|
1177
1204
|
content: "",
|
|
1178
1205
|
error: `Missing required argument: ${key} (received keys: ${received})`
|
|
1179
1206
|
};
|
|
1207
|
+
if (name === "view_range") {
|
|
1208
|
+
if (!Number.isInteger(args.start) || !Number.isInteger(args.end)) return {
|
|
1209
|
+
content: "",
|
|
1210
|
+
error: `Missing required numeric arguments: start, end (received keys: ${received})`
|
|
1211
|
+
};
|
|
1212
|
+
if (args.start < 1 || args.end < args.start) return {
|
|
1213
|
+
content: "",
|
|
1214
|
+
error: "Invalid line range: start must be >= 1 and end must be >= start"
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
if (name === "web_fetch") try {
|
|
1218
|
+
const url = new URL(args.url);
|
|
1219
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return {
|
|
1220
|
+
content: "",
|
|
1221
|
+
error: "URL must use http or https"
|
|
1222
|
+
};
|
|
1223
|
+
} catch {
|
|
1224
|
+
return {
|
|
1225
|
+
content: "",
|
|
1226
|
+
error: "Invalid URL"
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
function normalizeToolCall(toolCall) {
|
|
1231
|
+
const name = toolCall.function.name;
|
|
1232
|
+
const rawArguments = toolCall.function.arguments;
|
|
1233
|
+
if (!isToolName(name)) throw new Error(`Unknown tool: ${name}`);
|
|
1234
|
+
if (typeof rawArguments !== "object" || rawArguments === null || Array.isArray(rawArguments)) throw new Error(`Invalid arguments for tool: ${name}`);
|
|
1235
|
+
const normalizedArguments = rawArguments;
|
|
1236
|
+
const invalid = validateArgs(name, normalizedArguments);
|
|
1237
|
+
if (invalid?.error) throw new Error(invalid.error);
|
|
1238
|
+
return {
|
|
1239
|
+
name,
|
|
1240
|
+
arguments: normalizedArguments,
|
|
1241
|
+
requiresApproval: WRITE_TOOLS.has(name)
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function formatToolResultContent(toolName, result) {
|
|
1245
|
+
const status = result.error ? "The requested action was NOT performed" : "";
|
|
1246
|
+
const content = result.content ? `\n${result.content}` : "";
|
|
1247
|
+
const error = result.error ? `\nError: ${result.error}` : "";
|
|
1248
|
+
return [
|
|
1249
|
+
`Tool ${toolName} result:`,
|
|
1250
|
+
status,
|
|
1251
|
+
content.trim(),
|
|
1252
|
+
error.trim()
|
|
1253
|
+
].filter(Boolean).join("\n");
|
|
1254
|
+
}
|
|
1255
|
+
async function executeToolCall(toolCall, options) {
|
|
1256
|
+
try {
|
|
1257
|
+
const normalized = normalizeToolCall(toolCall);
|
|
1258
|
+
return await executeTool(normalized.name, normalized.arguments, options);
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
return {
|
|
1261
|
+
content: "",
|
|
1262
|
+
// v8 ignore next
|
|
1263
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1180
1266
|
}
|
|
1181
1267
|
/**
|
|
1182
1268
|
* Execute a tool by name with arguments
|
|
1183
1269
|
*/
|
|
1184
1270
|
async function executeTool(name, args, options) {
|
|
1271
|
+
if (!isToolName(name)) return {
|
|
1272
|
+
content: "",
|
|
1273
|
+
error: `Unknown tool: ${name}`
|
|
1274
|
+
};
|
|
1185
1275
|
if (options?.allowedTools && !options.allowedTools.has(name)) return {
|
|
1186
1276
|
content: "",
|
|
1187
1277
|
error: `Tool not allowed: ${name}`
|
|
@@ -1199,6 +1289,7 @@ async function executeTool(name, args, options) {
|
|
|
1199
1289
|
case VIEW_RANGE: return viewRange(stringArgs.path, args.start, args.end);
|
|
1200
1290
|
case WEB_SEARCH: return await webSearch(stringArgs.query);
|
|
1201
1291
|
case WEB_FETCH: return await webFetch(stringArgs.url);
|
|
1292
|
+
// v8 ignore next 2
|
|
1202
1293
|
default: return {
|
|
1203
1294
|
content: "",
|
|
1204
1295
|
error: `Unknown tool: ${name}`
|
|
@@ -1240,6 +1331,7 @@ async function checkForUpdate() {
|
|
|
1240
1331
|
//#endregion
|
|
1241
1332
|
//#region src/cli.ts
|
|
1242
1333
|
var cli = cac("code-ollama");
|
|
1334
|
+
var MAX_TOOL_TURNS = 25;
|
|
1243
1335
|
cli.version(VERSION);
|
|
1244
1336
|
cli.help();
|
|
1245
1337
|
cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model, prompt) => {
|
|
@@ -1272,30 +1364,40 @@ async function runPrompt(model, prompt) {
|
|
|
1272
1364
|
write("\n");
|
|
1273
1365
|
}
|
|
1274
1366
|
async function processRunStream(messages, model) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1367
|
+
let activeMessages = messages;
|
|
1368
|
+
let toolTurns = 0;
|
|
1369
|
+
while (toolTurns < MAX_TOOL_TURNS) {
|
|
1370
|
+
const assistantMessage = {
|
|
1371
|
+
role: ASSISTANT,
|
|
1372
|
+
content: ""
|
|
1373
|
+
};
|
|
1374
|
+
let nextMessages = null;
|
|
1375
|
+
for await (const chunk of streamChat(activeMessages, model, TOOLS)) {
|
|
1376
|
+
if (chunk.type === "content") {
|
|
1377
|
+
assistantMessage.content += chunk.content;
|
|
1378
|
+
write(chunk.content);
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
// v8 ignore next 3
|
|
1382
|
+
if (chunk.tool_calls.length === 0) continue;
|
|
1383
|
+
const committedMessages = [...activeMessages, assistantMessage];
|
|
1384
|
+
const toolResultMessages = [];
|
|
1385
|
+
for (const toolCall of chunk.tool_calls) {
|
|
1386
|
+
const result = await executeToolCall(toolCall);
|
|
1387
|
+
toolResultMessages.push({
|
|
1388
|
+
role: SYSTEM,
|
|
1389
|
+
content: formatToolResultContent(toolCall.function.name, result)
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
nextMessages = [...committedMessages, ...toolResultMessages];
|
|
1393
|
+
break;
|
|
1297
1394
|
}
|
|
1395
|
+
if (!nextMessages) return;
|
|
1396
|
+
activeMessages = nextMessages;
|
|
1397
|
+
toolTurns += 1;
|
|
1298
1398
|
}
|
|
1399
|
+
// v8 ignore next 3
|
|
1400
|
+
writeError("Tool execution stopped because the maximum tool turn limit was reached\n");
|
|
1299
1401
|
}
|
|
1300
1402
|
async function main(args = process.argv.slice(2)) {
|
|
1301
1403
|
if (args.length) cli.parse([
|
|
@@ -1306,7 +1408,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1306
1408
|
else await launchTui();
|
|
1307
1409
|
}
|
|
1308
1410
|
async function launchTui(sessionId) {
|
|
1309
|
-
const { renderApp } = await import("./assets/tui-
|
|
1411
|
+
const { renderApp } = await import("./assets/tui-BHkHnKUC.js");
|
|
1310
1412
|
reset();
|
|
1311
1413
|
renderApp(sessionId);
|
|
1312
1414
|
}
|
|
@@ -1322,4 +1424,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1322
1424
|
if (isEntrypoint()) main();
|
|
1323
1425
|
// v8 ignore stop
|
|
1324
1426
|
//#endregion
|
|
1325
|
-
export {
|
|
1427
|
+
export { saveClipboardImage as A, PLAN_GENERATION_INSTRUCTION as B, deleteModel as C, loadConfig as D, streamChat as E, LIST as F, PLAN as G, CATALOG as H, getTheme as I, REJECT as J, SAFE as K, ASSISTANT as L, withSystemMessage as M, HEADER_PREFIX as N, saveConfig as O, WARNING as P, SYSTEM as R, checkHealth as S, pullModel as T, AUTO as U, BACK as V, LABEL as W, VERSION as X, NAME as Y, LIST$1 as Z, loadSession as _, normalizeToolCall as a, reset as b, WRITE_TOOLS as c, write as d, appendMessage as f, listSessions as g, deleteSessionIfEmpty as h, formatToolResultContent as i, resetSystemMessage as j, removeClipboardImage as k, tick as l, deleteSession as m, main, executeTool as n, READ_TOOLS as o, createSession as p, APPROVE as q, executeToolCall as r, TOOLS as s, checkForUpdate as t, color as u, updateSessionModel as v, listModels as w, setClearHandler as x, clear as y, USER as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
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/"
|