code-ollama 0.21.1 → 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/dist/assets/{tui-BHkHnKUC.js → tui-CzkVRFXf.js} +87 -26
- package/dist/cli.js +155 -27
- package/package.json +1 -1
|
@@ -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(" ");
|
|
@@ -1363,6 +1405,7 @@ function hasExecutablePlan(content) {
|
|
|
1363
1405
|
//#endregion
|
|
1364
1406
|
//#region src/components/Chat/Chat.tsx
|
|
1365
1407
|
var MAX_TOOL_TURNS = 25;
|
|
1408
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1366
1409
|
function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onModeChange, sessionId, theme = getTheme() }) {
|
|
1367
1410
|
const sessionMessages = initialMessages ?? [];
|
|
1368
1411
|
const history = useMemo(() => sessionMessages.flatMap(({ role, content }) => role === "user" && !content.startsWith("/") ? [content] : []), [sessionMessages]);
|
|
@@ -1389,7 +1432,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1389
1432
|
persistedSnapshotRef.current = snapshot;
|
|
1390
1433
|
onMessagesChange?.(messages);
|
|
1391
1434
|
}, [messages, onMessagesChange]);
|
|
1392
|
-
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
1435
|
+
const buildToolResultMessage = useCallback((toolName, result, args) => {
|
|
1393
1436
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
1394
1437
|
role: SYSTEM,
|
|
1395
1438
|
content: [
|
|
@@ -1401,7 +1444,11 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1401
1444
|
};
|
|
1402
1445
|
return {
|
|
1403
1446
|
role: SYSTEM,
|
|
1404
|
-
content: formatToolResultContent(toolName, result)
|
|
1447
|
+
content: formatToolResultContent(toolName, result, args),
|
|
1448
|
+
toolResult: {
|
|
1449
|
+
name: toolName,
|
|
1450
|
+
...result.diff ? { diff: result.diff } : {}
|
|
1451
|
+
}
|
|
1405
1452
|
};
|
|
1406
1453
|
}, []);
|
|
1407
1454
|
const buildPlanModeCorrectionMessage = useCallback((toolName) => ({
|
|
@@ -1433,6 +1480,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1433
1480
|
abortControllerRef.current = controller;
|
|
1434
1481
|
let activeMessages = currentMessages;
|
|
1435
1482
|
let toolTurns = 0;
|
|
1483
|
+
let toolIntentCorrections = 0;
|
|
1436
1484
|
try {
|
|
1437
1485
|
while (!controller.signal.aborted) {
|
|
1438
1486
|
const assistantMessage = {
|
|
@@ -1442,6 +1490,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1442
1490
|
let committedMessages = activeMessages;
|
|
1443
1491
|
let assistantCommitted = false;
|
|
1444
1492
|
const commitAssistantMessage = () => {
|
|
1493
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1445
1494
|
// v8 ignore start
|
|
1446
1495
|
if (assistantCommitted) {
|
|
1447
1496
|
if (committedMessages.at(-1)?.role === "assistant") {
|
|
@@ -1465,7 +1514,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1465
1514
|
let nextMessages = null;
|
|
1466
1515
|
for await (const chunk of streamChat(withSystemMessage(activeMessages), modelName, TOOLS, controller.signal)) {
|
|
1467
1516
|
if (chunk.type === "content") {
|
|
1468
|
-
assistantMessage.content
|
|
1517
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1469
1518
|
setStreamingMessage({ ...assistantMessage });
|
|
1470
1519
|
continue;
|
|
1471
1520
|
}
|
|
@@ -1486,7 +1535,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1486
1535
|
// v8 ignore next
|
|
1487
1536
|
const allowedTools = executionMode === "plan" ? READ_TOOLS : void 0;
|
|
1488
1537
|
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools });
|
|
1489
|
-
toolResultMessages.push(buildToolResultMessage(normalized.name, result));
|
|
1538
|
+
toolResultMessages.push(buildToolResultMessage(normalized.name, result, normalized.arguments));
|
|
1490
1539
|
} catch (error) {
|
|
1491
1540
|
toolResultMessages.push(buildToolResultMessage(toolCall.function.name, {
|
|
1492
1541
|
content: "",
|
|
@@ -1500,10 +1549,20 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1500
1549
|
}
|
|
1501
1550
|
if (!nextMessages) {
|
|
1502
1551
|
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1503
|
-
commitAssistantMessage();
|
|
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;
|
|
1561
|
+
}
|
|
1504
1562
|
return;
|
|
1505
1563
|
}
|
|
1506
1564
|
toolTurns += 1;
|
|
1565
|
+
toolIntentCorrections = 0;
|
|
1507
1566
|
/* v8 ignore start */
|
|
1508
1567
|
if (toolTurns >= MAX_TOOL_TURNS) {
|
|
1509
1568
|
setMessages([...nextMessages, {
|
|
@@ -1554,6 +1613,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1554
1613
|
let committedMessages = currentMessages;
|
|
1555
1614
|
let assistantCommitted = false;
|
|
1556
1615
|
const commitAssistantMessage = () => {
|
|
1616
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1557
1617
|
if (assistantCommitted) {
|
|
1558
1618
|
// v8 ignore next
|
|
1559
1619
|
if (committedMessages.at(-1)?.role === "assistant") {
|
|
@@ -1579,7 +1639,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1579
1639
|
// v8 ignore next 3
|
|
1580
1640
|
if (controller.signal.aborted) return;
|
|
1581
1641
|
if (chunk.type === "content") {
|
|
1582
|
-
assistantMessage.content
|
|
1642
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1583
1643
|
setStreamingMessage({ ...assistantMessage });
|
|
1584
1644
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
1585
1645
|
const updatedMessages = commitAssistantMessage();
|
|
@@ -1605,7 +1665,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1605
1665
|
return;
|
|
1606
1666
|
}
|
|
1607
1667
|
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools: READ_TOOLS });
|
|
1608
|
-
const toolResultMessage = buildToolResultMessage(normalized.name, result);
|
|
1668
|
+
const toolResultMessage = buildToolResultMessage(normalized.name, result, normalized.arguments);
|
|
1609
1669
|
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1610
1670
|
setMessages(newMessages);
|
|
1611
1671
|
await processStreamReadOnly(newMessages);
|
|
@@ -1629,7 +1689,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1629
1689
|
// v8 ignore next 3
|
|
1630
1690
|
if (controller.signal.aborted) return;
|
|
1631
1691
|
if (chunk.type === "content") {
|
|
1632
|
-
planAssistantMessage.content
|
|
1692
|
+
planAssistantMessage.content = sanitizeAssistantContent(planAssistantMessage.content + chunk.content);
|
|
1633
1693
|
setStreamingMessage({ ...planAssistantMessage });
|
|
1634
1694
|
}
|
|
1635
1695
|
}
|
|
@@ -1702,10 +1762,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1702
1762
|
switch (decision) {
|
|
1703
1763
|
case APPROVE: {
|
|
1704
1764
|
const result = await executeToolCall(toolCall);
|
|
1705
|
-
const toolResultMessage =
|
|
1706
|
-
role: SYSTEM,
|
|
1707
|
-
content: formatToolResultContent(toolCall.function.name, result)
|
|
1708
|
-
};
|
|
1765
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result, toolCall.function.arguments);
|
|
1709
1766
|
const newMessages = [...approvedMessages, toolResultMessage];
|
|
1710
1767
|
setMessages(newMessages);
|
|
1711
1768
|
await processStream(newMessages, executionMode);
|
|
@@ -1717,7 +1774,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1717
1774
|
content: formatToolResultContent(toolCall.function.name, {
|
|
1718
1775
|
content: "",
|
|
1719
1776
|
error: "Tool call rejected by user"
|
|
1720
|
-
})
|
|
1777
|
+
}, toolCall.function.arguments)
|
|
1721
1778
|
};
|
|
1722
1779
|
setMessages([...approvedMessages, toolResultMessage]);
|
|
1723
1780
|
setIsLoading(false);
|
|
@@ -1725,7 +1782,11 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1725
1782
|
break;
|
|
1726
1783
|
}
|
|
1727
1784
|
}
|
|
1728
|
-
}, [
|
|
1785
|
+
}, [
|
|
1786
|
+
buildToolResultMessage,
|
|
1787
|
+
pendingToolCall,
|
|
1788
|
+
processStream
|
|
1789
|
+
]);
|
|
1729
1790
|
const handleSubmit = useCallback(async ({ content, images }) => {
|
|
1730
1791
|
setInterruptReason(null);
|
|
1731
1792
|
const userContent = content.trim();
|
package/dist/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ var LIST$1 = [
|
|
|
50
50
|
//#endregion
|
|
51
51
|
//#region package.json
|
|
52
52
|
var name = "code-ollama";
|
|
53
|
-
var version = "0.
|
|
53
|
+
var version = "0.22.0";
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/constants/package.ts
|
|
56
56
|
var NAME = name;
|
|
@@ -109,11 +109,11 @@ var BACK = {
|
|
|
109
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
|
|
110
110
|
|
|
111
111
|
Follow these rules:
|
|
112
|
-
1.
|
|
113
|
-
2.
|
|
114
|
-
3.
|
|
115
|
-
4.
|
|
116
|
-
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
|
|
117
117
|
6. Confirm with the user before destructive operations
|
|
118
118
|
|
|
119
119
|
When tools return results, incorporate them into your response naturally`;
|
|
@@ -131,7 +131,12 @@ Always use tools when you need to:
|
|
|
131
131
|
- Make file changes
|
|
132
132
|
- Explore project structure
|
|
133
133
|
- Search the codebase
|
|
134
|
-
- 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`;
|
|
135
140
|
var PLAN_GENERATION_INSTRUCTION = `Based on the research above, decide whether the user request needs code or shell execution
|
|
136
141
|
|
|
137
142
|
If the request needs changes or commands, respond with a plan checklist only
|
|
@@ -490,6 +495,15 @@ function saveConfig(patch) {
|
|
|
490
495
|
//#region src/utils/ollama.ts
|
|
491
496
|
var { host } = loadConfig();
|
|
492
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
|
+
}
|
|
493
507
|
async function checkHealth() {
|
|
494
508
|
try {
|
|
495
509
|
return (await fetch(host)).ok;
|
|
@@ -498,9 +512,15 @@ async function checkHealth() {
|
|
|
498
512
|
}
|
|
499
513
|
}
|
|
500
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
|
+
}));
|
|
501
521
|
const response = await client.chat({
|
|
502
522
|
model,
|
|
503
|
-
messages,
|
|
523
|
+
messages: providerMessages,
|
|
504
524
|
stream: true,
|
|
505
525
|
tools,
|
|
506
526
|
// v8 ignore next
|
|
@@ -782,10 +802,10 @@ var TOOLS = [
|
|
|
782
802
|
type: "string",
|
|
783
803
|
description: "The path to the directory to list"
|
|
784
804
|
} }, ["path"]),
|
|
785
|
-
defineTool(GREP_SEARCH, "Search
|
|
805
|
+
defineTool(GREP_SEARCH, "Search files within a directory; multi-word queries also match common code identifier forms", {
|
|
786
806
|
pattern: {
|
|
787
807
|
type: "string",
|
|
788
|
-
description: "The regex
|
|
808
|
+
description: "The regex, phrase, or code concept to search for"
|
|
789
809
|
},
|
|
790
810
|
path: {
|
|
791
811
|
type: "string",
|
|
@@ -861,6 +881,76 @@ async function runShell(command) {
|
|
|
861
881
|
}
|
|
862
882
|
//#endregion
|
|
863
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
|
+
}
|
|
864
954
|
/**
|
|
865
955
|
* Read file contents
|
|
866
956
|
*/
|
|
@@ -910,8 +1000,19 @@ function editFile(filePath, oldText, newText) {
|
|
|
910
1000
|
content: "",
|
|
911
1001
|
error: `Exact text matched multiple locations in file: ${filePath}`
|
|
912
1002
|
};
|
|
913
|
-
|
|
914
|
-
|
|
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
|
+
};
|
|
915
1016
|
} catch (error) {
|
|
916
1017
|
return {
|
|
917
1018
|
content: "",
|
|
@@ -966,17 +1067,17 @@ function listDir(dirPath) {
|
|
|
966
1067
|
* Search for pattern in files using ripgrep if available, fallback to Node.js
|
|
967
1068
|
*/
|
|
968
1069
|
async function grepSearch(pattern, dirPath) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
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 };
|
|
973
1074
|
} catch {}
|
|
974
1075
|
try {
|
|
975
1076
|
if (!existsSync(dirPath)) return {
|
|
976
1077
|
content: "",
|
|
977
1078
|
error: `Directory not found: ${dirPath}`
|
|
978
1079
|
};
|
|
979
|
-
const
|
|
1080
|
+
const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
|
|
980
1081
|
const results = [];
|
|
981
1082
|
function searchDirectory(currentPath) {
|
|
982
1083
|
const entries = readdirSync(currentPath, { withFileTypes: true });
|
|
@@ -986,9 +1087,9 @@ async function grepSearch(pattern, dirPath) {
|
|
|
986
1087
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
|
|
987
1088
|
} else if (entry.isFile()) try {
|
|
988
1089
|
const lines = readFileSync(fullPath, "utf8").split("\n");
|
|
989
|
-
for (let i = 0; i < lines.length; i++) {
|
|
990
|
-
|
|
991
|
-
|
|
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;
|
|
992
1093
|
}
|
|
993
1094
|
} catch {}
|
|
994
1095
|
}
|
|
@@ -1241,17 +1342,25 @@ function normalizeToolCall(toolCall) {
|
|
|
1241
1342
|
requiresApproval: WRITE_TOOLS.has(name)
|
|
1242
1343
|
};
|
|
1243
1344
|
}
|
|
1244
|
-
function formatToolResultContent(toolName, result) {
|
|
1345
|
+
function formatToolResultContent(toolName, result, args) {
|
|
1346
|
+
const formattedArgs = args ? `(${formatToolArguments(args)})` : "";
|
|
1245
1347
|
const status = result.error ? "The requested action was NOT performed" : "";
|
|
1246
1348
|
const content = result.content ? `\n${result.content}` : "";
|
|
1247
1349
|
const error = result.error ? `\nError: ${result.error}` : "";
|
|
1248
1350
|
return [
|
|
1249
|
-
`Tool ${toolName} result:`,
|
|
1351
|
+
`Tool ${toolName}${formattedArgs} result:`,
|
|
1250
1352
|
status,
|
|
1251
1353
|
content.trim(),
|
|
1252
1354
|
error.trim()
|
|
1253
1355
|
].filter(Boolean).join("\n");
|
|
1254
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
|
+
}
|
|
1255
1364
|
async function executeToolCall(toolCall, options) {
|
|
1256
1365
|
try {
|
|
1257
1366
|
const normalized = normalizeToolCall(toolCall);
|
|
@@ -1332,6 +1441,7 @@ async function checkForUpdate() {
|
|
|
1332
1441
|
//#region src/cli.ts
|
|
1333
1442
|
var cli = cac("code-ollama");
|
|
1334
1443
|
var MAX_TOOL_TURNS = 25;
|
|
1444
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1335
1445
|
cli.version(VERSION);
|
|
1336
1446
|
cli.help();
|
|
1337
1447
|
cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model, prompt) => {
|
|
@@ -1366,6 +1476,7 @@ async function runPrompt(model, prompt) {
|
|
|
1366
1476
|
async function processRunStream(messages, model) {
|
|
1367
1477
|
let activeMessages = messages;
|
|
1368
1478
|
let toolTurns = 0;
|
|
1479
|
+
let toolIntentCorrections = 0;
|
|
1369
1480
|
while (toolTurns < MAX_TOOL_TURNS) {
|
|
1370
1481
|
const assistantMessage = {
|
|
1371
1482
|
role: ASSISTANT,
|
|
@@ -1374,10 +1485,11 @@ async function processRunStream(messages, model) {
|
|
|
1374
1485
|
let nextMessages = null;
|
|
1375
1486
|
for await (const chunk of streamChat(activeMessages, model, TOOLS)) {
|
|
1376
1487
|
if (chunk.type === "content") {
|
|
1377
|
-
assistantMessage.content
|
|
1488
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1378
1489
|
write(chunk.content);
|
|
1379
1490
|
continue;
|
|
1380
1491
|
}
|
|
1492
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1381
1493
|
// v8 ignore next 3
|
|
1382
1494
|
if (chunk.tool_calls.length === 0) continue;
|
|
1383
1495
|
const committedMessages = [...activeMessages, assistantMessage];
|
|
@@ -1386,15 +1498,31 @@ async function processRunStream(messages, model) {
|
|
|
1386
1498
|
const result = await executeToolCall(toolCall);
|
|
1387
1499
|
toolResultMessages.push({
|
|
1388
1500
|
role: SYSTEM,
|
|
1389
|
-
content: formatToolResultContent(toolCall.function.name, result)
|
|
1501
|
+
content: formatToolResultContent(toolCall.function.name, result, toolCall.function.arguments)
|
|
1390
1502
|
});
|
|
1391
1503
|
}
|
|
1392
1504
|
nextMessages = [...committedMessages, ...toolResultMessages];
|
|
1393
1505
|
break;
|
|
1394
1506
|
}
|
|
1395
|
-
if (!nextMessages)
|
|
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
|
+
}
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1396
1523
|
activeMessages = nextMessages;
|
|
1397
1524
|
toolTurns += 1;
|
|
1525
|
+
toolIntentCorrections = 0;
|
|
1398
1526
|
}
|
|
1399
1527
|
// v8 ignore next 3
|
|
1400
1528
|
writeError("Tool execution stopped because the maximum tool turn limit was reached\n");
|
|
@@ -1408,7 +1536,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1408
1536
|
else await launchTui();
|
|
1409
1537
|
}
|
|
1410
1538
|
async function launchTui(sessionId) {
|
|
1411
|
-
const { renderApp } = await import("./assets/tui-
|
|
1539
|
+
const { renderApp } = await import("./assets/tui-CzkVRFXf.js");
|
|
1412
1540
|
reset();
|
|
1413
1541
|
renderApp(sessionId);
|
|
1414
1542
|
}
|
|
@@ -1424,4 +1552,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1424
1552
|
if (isEntrypoint()) main();
|
|
1425
1553
|
// v8 ignore stop
|
|
1426
1554
|
//#endregion
|
|
1427
|
-
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 };
|