code-ollama 0.20.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-DDbMjcMS.js → tui-BHkHnKUC.js} +216 -109
- package/dist/cli.js +161 -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
|
}),
|
|
@@ -1783,12 +1839,9 @@ function abbreviatePath(dir) {
|
|
|
1783
1839
|
const home = homedir();
|
|
1784
1840
|
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
1785
1841
|
}
|
|
1786
|
-
function Header({ model,
|
|
1842
|
+
function Header({ model, theme = getTheme() }) {
|
|
1787
1843
|
const directory = abbreviatePath(process.cwd());
|
|
1788
1844
|
const modelLabel = model || "not configured";
|
|
1789
|
-
useEffect(() => {
|
|
1790
|
-
onLoad();
|
|
1791
|
-
}, []);
|
|
1792
1845
|
return /* @__PURE__ */ jsx(Static, {
|
|
1793
1846
|
items: [0],
|
|
1794
1847
|
children: (key) => /* @__PURE__ */ jsxs(Box, {
|
|
@@ -2781,6 +2834,63 @@ function ThemeSettings({ currentTheme, onClose, onPreview, onSave }) {
|
|
|
2781
2834
|
});
|
|
2782
2835
|
}
|
|
2783
2836
|
//#endregion
|
|
2837
|
+
//#region src/components/UpdateBanner/UpdateBanner.tsx
|
|
2838
|
+
var RELEASES_URL = `https://github.com/ai-action/${NAME}/releases`;
|
|
2839
|
+
function UpdateBanner({ onLoad, theme }) {
|
|
2840
|
+
const [latestVersion, setLatestVersion] = useState();
|
|
2841
|
+
useEffect(() => {
|
|
2842
|
+
checkForUpdate().then(setLatestVersion).finally(async () => {
|
|
2843
|
+
await tick();
|
|
2844
|
+
onLoad();
|
|
2845
|
+
});
|
|
2846
|
+
}, []);
|
|
2847
|
+
if (!latestVersion) return null;
|
|
2848
|
+
return /* @__PURE__ */ jsx(Static, {
|
|
2849
|
+
items: [0],
|
|
2850
|
+
children: (key) => /* @__PURE__ */ jsxs(Box, {
|
|
2851
|
+
borderStyle: "bold",
|
|
2852
|
+
flexDirection: "column",
|
|
2853
|
+
paddingX: 1,
|
|
2854
|
+
children: [
|
|
2855
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2856
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2857
|
+
italic: true,
|
|
2858
|
+
children: "🚀 Update available! "
|
|
2859
|
+
}),
|
|
2860
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2861
|
+
color: theme.colors.secondary,
|
|
2862
|
+
children: VERSION
|
|
2863
|
+
}),
|
|
2864
|
+
" →",
|
|
2865
|
+
" ",
|
|
2866
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2867
|
+
color: theme.colors.secondary,
|
|
2868
|
+
children: latestVersion
|
|
2869
|
+
})
|
|
2870
|
+
] }),
|
|
2871
|
+
/* @__PURE__ */ jsx(Box, {
|
|
2872
|
+
marginBottom: 1,
|
|
2873
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
2874
|
+
"Run to update:",
|
|
2875
|
+
" ",
|
|
2876
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
2877
|
+
color: theme.colors.command,
|
|
2878
|
+
children: ["npm i -g ", NAME]
|
|
2879
|
+
})
|
|
2880
|
+
] })
|
|
2881
|
+
}),
|
|
2882
|
+
/* @__PURE__ */ jsx(Text, { children: "See release notes:" }),
|
|
2883
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2884
|
+
color: theme.colors.accent,
|
|
2885
|
+
dimColor: true,
|
|
2886
|
+
underline: true,
|
|
2887
|
+
children: RELEASES_URL
|
|
2888
|
+
})
|
|
2889
|
+
]
|
|
2890
|
+
}, key)
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
//#endregion
|
|
2784
2894
|
//#region src/components/App/constants.ts
|
|
2785
2895
|
var Screen = /* @__PURE__ */ function(Screen) {
|
|
2786
2896
|
Screen["Chat"] = "chat";
|
|
@@ -3002,7 +3112,7 @@ function ReadinessCheck({ errorMessage, onCommand, setupState, theme = getTheme(
|
|
|
3002
3112
|
function App({ sessionId }) {
|
|
3003
3113
|
const [appConfig, setConfig] = useState(() => loadConfig());
|
|
3004
3114
|
const [mode, setMode] = useState(SAFE);
|
|
3005
|
-
const [
|
|
3115
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
3006
3116
|
const [setupState, setSetupState] = useState(() => appConfig.model ? ReadinessState.Ready : ReadinessState.MissingModelConfig);
|
|
3007
3117
|
const [setupErrorMessage, setSetupErrorMessage] = useState(null);
|
|
3008
3118
|
const { currentScreen, setScreen, handleClose, handleCommand } = useScreenRouter();
|
|
@@ -3066,19 +3176,6 @@ function App({ sessionId }) {
|
|
|
3066
3176
|
onUpdateConfig: handleUpdateConfig,
|
|
3067
3177
|
setScreen
|
|
3068
3178
|
});
|
|
3069
|
-
const handleHeaderLoad = useCallback(() => {
|
|
3070
|
-
setIsHeaderLoaded(true);
|
|
3071
|
-
}, []);
|
|
3072
|
-
const handleToggleMode = useCallback(() => {
|
|
3073
|
-
setMode((mode) => {
|
|
3074
|
-
switch (mode) {
|
|
3075
|
-
case SAFE: return AUTO;
|
|
3076
|
-
case AUTO: return PLAN;
|
|
3077
|
-
case PLAN:
|
|
3078
|
-
default: return SAFE;
|
|
3079
|
-
}
|
|
3080
|
-
});
|
|
3081
|
-
}, []);
|
|
3082
3179
|
const handleChatCommand = useCallback((command) => {
|
|
3083
3180
|
handleCommand(command, {
|
|
3084
3181
|
model: appConfig.model ?? "",
|
|
@@ -3093,18 +3190,6 @@ function App({ sessionId }) {
|
|
|
3093
3190
|
handleCreateSession,
|
|
3094
3191
|
setPreviewThemeId
|
|
3095
3192
|
]);
|
|
3096
|
-
const handleDeleteSessionAndStay = useCallback((sid) => {
|
|
3097
|
-
handleDeleteSession(sid);
|
|
3098
|
-
setScreen(Screen.SessionManager);
|
|
3099
|
-
}, [handleDeleteSession, setScreen]);
|
|
3100
|
-
const handleOpenSessionAndNavigate = useCallback((sid) => {
|
|
3101
|
-
handleOpenSession(sid);
|
|
3102
|
-
setScreen(Screen.Chat);
|
|
3103
|
-
}, [handleOpenSession, setScreen]);
|
|
3104
|
-
const handleCreateSessionAndNavigate = useCallback(() => {
|
|
3105
|
-
handleCreateSession();
|
|
3106
|
-
setScreen(Screen.Chat);
|
|
3107
|
-
}, [handleCreateSession, setScreen]);
|
|
3108
3193
|
let screenContent;
|
|
3109
3194
|
switch (currentScreen) {
|
|
3110
3195
|
case Screen.ModelManager:
|
|
@@ -3127,9 +3212,18 @@ function App({ sessionId }) {
|
|
|
3127
3212
|
screenContent = /* @__PURE__ */ jsx(SessionManager, {
|
|
3128
3213
|
currentSessionId: activeSession.metadata.id,
|
|
3129
3214
|
onClose: handleClose,
|
|
3130
|
-
onDelete:
|
|
3131
|
-
|
|
3132
|
-
|
|
3215
|
+
onDelete: (sessionId) => {
|
|
3216
|
+
handleDeleteSession(sessionId);
|
|
3217
|
+
setScreen(Screen.SessionManager);
|
|
3218
|
+
},
|
|
3219
|
+
onNew: () => {
|
|
3220
|
+
handleCreateSession();
|
|
3221
|
+
setScreen(Screen.Chat);
|
|
3222
|
+
},
|
|
3223
|
+
onOpen: (sessionId) => {
|
|
3224
|
+
handleOpenSession(sessionId);
|
|
3225
|
+
setScreen(Screen.Chat);
|
|
3226
|
+
},
|
|
3133
3227
|
theme: activeTheme
|
|
3134
3228
|
});
|
|
3135
3229
|
break;
|
|
@@ -3164,14 +3258,27 @@ function App({ sessionId }) {
|
|
|
3164
3258
|
children: [
|
|
3165
3259
|
/* @__PURE__ */ jsx(Header, {
|
|
3166
3260
|
model: appConfig.model ?? "",
|
|
3167
|
-
onLoad: handleHeaderLoad,
|
|
3168
3261
|
theme: activeTheme
|
|
3169
3262
|
}),
|
|
3170
|
-
|
|
3263
|
+
/* @__PURE__ */ jsx(UpdateBanner, {
|
|
3264
|
+
onLoad: () => {
|
|
3265
|
+
setIsLoaded(true);
|
|
3266
|
+
},
|
|
3267
|
+
theme: activeTheme
|
|
3268
|
+
}),
|
|
3269
|
+
isLoaded && screenContent,
|
|
3171
3270
|
/* @__PURE__ */ jsx(Footer, {
|
|
3172
3271
|
mode,
|
|
3173
3272
|
model: appConfig.model ?? "",
|
|
3174
|
-
onToggleMode:
|
|
3273
|
+
onToggleMode: () => {
|
|
3274
|
+
setMode((mode) => {
|
|
3275
|
+
switch (mode) {
|
|
3276
|
+
case SAFE: return AUTO;
|
|
3277
|
+
case AUTO: return PLAN;
|
|
3278
|
+
case PLAN: return SAFE;
|
|
3279
|
+
}
|
|
3280
|
+
});
|
|
3281
|
+
},
|
|
3175
3282
|
theme: activeTheme
|
|
3176
3283
|
})
|
|
3177
3284
|
]
|
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.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}`
|
|
@@ -1206,8 +1297,41 @@ async function executeTool(name, args, options) {
|
|
|
1206
1297
|
}
|
|
1207
1298
|
}
|
|
1208
1299
|
//#endregion
|
|
1300
|
+
//#region src/utils/update.ts
|
|
1301
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${NAME}/latest`;
|
|
1302
|
+
function getSemver(version) {
|
|
1303
|
+
return version.split(".").map(Number);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Check if the latest version is newer than the current version
|
|
1307
|
+
* @param current The current version
|
|
1308
|
+
* @param latest The latest version
|
|
1309
|
+
* @returns true if the latest version is newer, false otherwise
|
|
1310
|
+
*/
|
|
1311
|
+
function isVersionNewer(current, latest) {
|
|
1312
|
+
const [currentMajor, currentMinor, currentPatch] = getSemver(current);
|
|
1313
|
+
const [latestMajor, latestMinor, latestPatch] = getSemver(latest);
|
|
1314
|
+
if (latestMajor !== currentMajor) return latestMajor > currentMajor;
|
|
1315
|
+
if (latestMinor !== currentMinor) return latestMinor > currentMinor;
|
|
1316
|
+
return latestPatch > currentPatch;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Check if a newer version is available on npm
|
|
1320
|
+
* @returns The latest version if available, undefined otherwise
|
|
1321
|
+
*/
|
|
1322
|
+
async function checkForUpdate() {
|
|
1323
|
+
try {
|
|
1324
|
+
const response = await fetch(REGISTRY_URL);
|
|
1325
|
+
if (!response.ok) return;
|
|
1326
|
+
const { version: latestVersion } = await response.json();
|
|
1327
|
+
if (!latestVersion) return;
|
|
1328
|
+
if (isVersionNewer(VERSION, latestVersion)) return latestVersion;
|
|
1329
|
+
} catch {}
|
|
1330
|
+
}
|
|
1331
|
+
//#endregion
|
|
1209
1332
|
//#region src/cli.ts
|
|
1210
1333
|
var cli = cac("code-ollama");
|
|
1334
|
+
var MAX_TOOL_TURNS = 25;
|
|
1211
1335
|
cli.version(VERSION);
|
|
1212
1336
|
cli.help();
|
|
1213
1337
|
cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model, prompt) => {
|
|
@@ -1240,30 +1364,40 @@ async function runPrompt(model, prompt) {
|
|
|
1240
1364
|
write("\n");
|
|
1241
1365
|
}
|
|
1242
1366
|
async function processRunStream(messages, model) {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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;
|
|
1265
1394
|
}
|
|
1395
|
+
if (!nextMessages) return;
|
|
1396
|
+
activeMessages = nextMessages;
|
|
1397
|
+
toolTurns += 1;
|
|
1266
1398
|
}
|
|
1399
|
+
// v8 ignore next 3
|
|
1400
|
+
writeError("Tool execution stopped because the maximum tool turn limit was reached\n");
|
|
1267
1401
|
}
|
|
1268
1402
|
async function main(args = process.argv.slice(2)) {
|
|
1269
1403
|
if (args.length) cli.parse([
|
|
@@ -1274,7 +1408,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1274
1408
|
else await launchTui();
|
|
1275
1409
|
}
|
|
1276
1410
|
async function launchTui(sessionId) {
|
|
1277
|
-
const { renderApp } = await import("./assets/tui-
|
|
1411
|
+
const { renderApp } = await import("./assets/tui-BHkHnKUC.js");
|
|
1278
1412
|
reset();
|
|
1279
1413
|
renderApp(sessionId);
|
|
1280
1414
|
}
|
|
@@ -1290,4 +1424,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1290
1424
|
if (isEntrypoint()) main();
|
|
1291
1425
|
// v8 ignore stop
|
|
1292
1426
|
//#endregion
|
|
1293
|
-
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.
|
|
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/"
|