code-ollama 0.21.1 → 0.23.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-iewVFcZW.js} +161 -76
- package/dist/cli.js +241 -71
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as NAME, A as loadConfig, B as ASSISTANT, C as checkHealth, D as pullModel, E as listModels, F as withSystemMessage, G as BACK, H as USER, I as HEADER_PREFIX, J as LABEL, K as CATALOG, L as WARNING, M as removeClipboardImage, N as saveClipboardImage, O as sanitizeAssistantContent, P as resetSystemMessage, Q as REJECT, R as LIST$1, S as TOOL_INTENT_CORRECTION, T as hasUncalledToolIntent, U as PLAN_GENERATION_INSTRUCTION, V as SYSTEM, W as PLAN_INSTRUCTION, X as SAFE, Y as PLAN, Z as APPROVE, _ as loadSession, a as normalizeToolCall, b as reset, c as WRITE_TOOLS, d as write, et as VERSION, 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 AUTO, r as executeToolCall, s as TOOLS, t as checkForUpdate, tt as LIST, 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(" ");
|
|
@@ -491,29 +533,26 @@ function Message({ message, isStreaming = false, theme }) {
|
|
|
491
533
|
//#endregion
|
|
492
534
|
//#region src/components/Messages/Messages.tsx
|
|
493
535
|
function Messages({ messages, isLoading, sessionId, streamingMessage, theme = getTheme() }) {
|
|
494
|
-
return /* @__PURE__ */ jsxs(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/* @__PURE__ */ jsx(
|
|
498
|
-
|
|
499
|
-
children: (message, index) => /* @__PURE__ */ jsx(Message, {
|
|
500
|
-
message,
|
|
501
|
-
theme
|
|
502
|
-
}, index)
|
|
503
|
-
}, sessionId),
|
|
504
|
-
streamingMessage && /* @__PURE__ */ jsx(Message, {
|
|
505
|
-
isStreaming: true,
|
|
506
|
-
message: streamingMessage,
|
|
536
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
537
|
+
/* @__PURE__ */ jsx(Static, {
|
|
538
|
+
items: messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE),
|
|
539
|
+
children: (message, index) => /* @__PURE__ */ jsx(Message, {
|
|
540
|
+
message,
|
|
507
541
|
theme
|
|
508
|
-
})
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
542
|
+
}, index)
|
|
543
|
+
}, sessionId),
|
|
544
|
+
streamingMessage && /* @__PURE__ */ jsx(Message, {
|
|
545
|
+
isStreaming: true,
|
|
546
|
+
message: streamingMessage,
|
|
547
|
+
theme
|
|
548
|
+
}),
|
|
549
|
+
isLoading && streamingMessage && !streamingMessage.content && /* @__PURE__ */ jsx(Box, {
|
|
550
|
+
marginTop: -1,
|
|
551
|
+
marginBottom: 1,
|
|
552
|
+
marginX: 2,
|
|
553
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
|
|
554
|
+
})
|
|
555
|
+
] });
|
|
517
556
|
}
|
|
518
557
|
//#endregion
|
|
519
558
|
//#region src/components/SelectPrompt/SelectPrompt.tsx
|
|
@@ -595,22 +634,22 @@ function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" })
|
|
|
595
634
|
});
|
|
596
635
|
}
|
|
597
636
|
//#endregion
|
|
598
|
-
//#region src/components/
|
|
637
|
+
//#region src/components/PlanReview/PlanReview.tsx
|
|
599
638
|
var options$1 = [
|
|
600
639
|
{
|
|
601
|
-
label: "
|
|
640
|
+
label: "Approve in auto mode",
|
|
602
641
|
value: AUTO
|
|
603
642
|
},
|
|
604
643
|
{
|
|
605
|
-
label: "
|
|
644
|
+
label: "Approve in safe mode",
|
|
606
645
|
value: SAFE
|
|
607
646
|
},
|
|
608
647
|
{
|
|
609
|
-
label: "
|
|
648
|
+
label: "Continue planning",
|
|
610
649
|
value: PLAN
|
|
611
650
|
}
|
|
612
651
|
];
|
|
613
|
-
function
|
|
652
|
+
function PlanReview({ planContent, onModeChange, theme = getTheme() }) {
|
|
614
653
|
return /* @__PURE__ */ jsx(Box, {
|
|
615
654
|
marginX: 2,
|
|
616
655
|
children: /* @__PURE__ */ jsx(SelectPrompt, {
|
|
@@ -628,13 +667,16 @@ function PlanApproval({ planContent, onModeChange, theme = getTheme() }) {
|
|
|
628
667
|
/* @__PURE__ */ jsx(Text, {
|
|
629
668
|
bold: true,
|
|
630
669
|
color: theme.colors.accent,
|
|
631
|
-
children: "Plan
|
|
670
|
+
children: "Plan Review - Choose next step:"
|
|
632
671
|
}),
|
|
633
672
|
/* @__PURE__ */ jsx(Box, {
|
|
634
673
|
marginY: 1,
|
|
635
|
-
children: /* @__PURE__ */ jsx(
|
|
674
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
675
|
+
content: planContent,
|
|
676
|
+
theme
|
|
677
|
+
})
|
|
636
678
|
}),
|
|
637
|
-
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Select
|
|
679
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Select review action" })
|
|
638
680
|
]
|
|
639
681
|
})
|
|
640
682
|
})
|
|
@@ -1345,7 +1387,7 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
|
|
|
1345
1387
|
//#endregion
|
|
1346
1388
|
//#region src/components/Chat/constants.ts
|
|
1347
1389
|
var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
|
|
1348
|
-
var PLAN_CHECKLIST_REMINDER = "Then display the
|
|
1390
|
+
var PLAN_CHECKLIST_REMINDER = "Then display the plan using either the Plan Needs Input or Proposed Plan Markdown template";
|
|
1349
1391
|
var PLAN_EXECUTION_REMINDER = `Do not claim success and do not call ${Array.from(WRITE_TOOLS).join(", ")} until the user approves execution`;
|
|
1350
1392
|
var InterruptReason = /* @__PURE__ */ function(InterruptReason) {
|
|
1351
1393
|
InterruptReason["Interrupted"] = "interrupted";
|
|
@@ -1355,14 +1397,18 @@ var InterruptReason = /* @__PURE__ */ function(InterruptReason) {
|
|
|
1355
1397
|
//#endregion
|
|
1356
1398
|
//#region src/components/Chat/plan.ts
|
|
1357
1399
|
function hasExecutablePlan(content) {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1400
|
+
const lines = content.split("\n");
|
|
1401
|
+
const proposedPlanIndex = lines.findIndex((line) => line.trim().toLowerCase() === "## proposed plan");
|
|
1402
|
+
if (proposedPlanIndex === -1) return false;
|
|
1403
|
+
const executionStepsIndex = lines.findIndex((line, index) => index > proposedPlanIndex && line.trim().toLowerCase() === "### execution steps");
|
|
1404
|
+
if (executionStepsIndex === -1) return false;
|
|
1405
|
+
const nextSectionIndex = lines.findIndex((line, index) => index > executionStepsIndex && /^#{1,6}\s+\S/.test(line.trim()));
|
|
1406
|
+
return lines.slice(executionStepsIndex + 1, nextSectionIndex === -1 ? void 0 : nextSectionIndex).some((line) => /^(?:[-*]|\d+[.)])\s+\S/.test(line.trim()));
|
|
1362
1407
|
}
|
|
1363
1408
|
//#endregion
|
|
1364
1409
|
//#region src/components/Chat/Chat.tsx
|
|
1365
1410
|
var MAX_TOOL_TURNS = 25;
|
|
1411
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1366
1412
|
function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onModeChange, sessionId, theme = getTheme() }) {
|
|
1367
1413
|
const sessionMessages = initialMessages ?? [];
|
|
1368
1414
|
const history = useMemo(() => sessionMessages.flatMap(({ role, content }) => role === "user" && !content.startsWith("/") ? [content] : []), [sessionMessages]);
|
|
@@ -1389,7 +1435,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1389
1435
|
persistedSnapshotRef.current = snapshot;
|
|
1390
1436
|
onMessagesChange?.(messages);
|
|
1391
1437
|
}, [messages, onMessagesChange]);
|
|
1392
|
-
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
1438
|
+
const buildToolResultMessage = useCallback((toolName, result, args) => {
|
|
1393
1439
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
1394
1440
|
role: SYSTEM,
|
|
1395
1441
|
content: [
|
|
@@ -1401,7 +1447,11 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1401
1447
|
};
|
|
1402
1448
|
return {
|
|
1403
1449
|
role: SYSTEM,
|
|
1404
|
-
content: formatToolResultContent(toolName, result)
|
|
1450
|
+
content: formatToolResultContent(toolName, result, args),
|
|
1451
|
+
toolResult: {
|
|
1452
|
+
name: toolName,
|
|
1453
|
+
...result.diff ? { diff: result.diff } : {}
|
|
1454
|
+
}
|
|
1405
1455
|
};
|
|
1406
1456
|
}, []);
|
|
1407
1457
|
const buildPlanModeCorrectionMessage = useCallback((toolName) => ({
|
|
@@ -1433,6 +1483,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1433
1483
|
abortControllerRef.current = controller;
|
|
1434
1484
|
let activeMessages = currentMessages;
|
|
1435
1485
|
let toolTurns = 0;
|
|
1486
|
+
let toolIntentCorrections = 0;
|
|
1436
1487
|
try {
|
|
1437
1488
|
while (!controller.signal.aborted) {
|
|
1438
1489
|
const assistantMessage = {
|
|
@@ -1442,6 +1493,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1442
1493
|
let committedMessages = activeMessages;
|
|
1443
1494
|
let assistantCommitted = false;
|
|
1444
1495
|
const commitAssistantMessage = () => {
|
|
1496
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1445
1497
|
// v8 ignore start
|
|
1446
1498
|
if (assistantCommitted) {
|
|
1447
1499
|
if (committedMessages.at(-1)?.role === "assistant") {
|
|
@@ -1465,7 +1517,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1465
1517
|
let nextMessages = null;
|
|
1466
1518
|
for await (const chunk of streamChat(withSystemMessage(activeMessages), modelName, TOOLS, controller.signal)) {
|
|
1467
1519
|
if (chunk.type === "content") {
|
|
1468
|
-
assistantMessage.content
|
|
1520
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1469
1521
|
setStreamingMessage({ ...assistantMessage });
|
|
1470
1522
|
continue;
|
|
1471
1523
|
}
|
|
@@ -1486,7 +1538,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1486
1538
|
// v8 ignore next
|
|
1487
1539
|
const allowedTools = executionMode === "plan" ? READ_TOOLS : void 0;
|
|
1488
1540
|
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools });
|
|
1489
|
-
toolResultMessages.push(buildToolResultMessage(normalized.name, result));
|
|
1541
|
+
toolResultMessages.push(buildToolResultMessage(normalized.name, result, normalized.arguments));
|
|
1490
1542
|
} catch (error) {
|
|
1491
1543
|
toolResultMessages.push(buildToolResultMessage(toolCall.function.name, {
|
|
1492
1544
|
content: "",
|
|
@@ -1500,10 +1552,20 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1500
1552
|
}
|
|
1501
1553
|
if (!nextMessages) {
|
|
1502
1554
|
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1503
|
-
commitAssistantMessage();
|
|
1555
|
+
const updatedMessages = commitAssistantMessage();
|
|
1556
|
+
if (hasUncalledToolIntent(assistantMessage.content) && toolIntentCorrections < MAX_TOOL_INTENT_CORRECTIONS) {
|
|
1557
|
+
toolIntentCorrections += 1;
|
|
1558
|
+
activeMessages = [...updatedMessages, {
|
|
1559
|
+
role: SYSTEM,
|
|
1560
|
+
content: TOOL_INTENT_CORRECTION
|
|
1561
|
+
}];
|
|
1562
|
+
setMessages(activeMessages);
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1504
1565
|
return;
|
|
1505
1566
|
}
|
|
1506
1567
|
toolTurns += 1;
|
|
1568
|
+
toolIntentCorrections = 0;
|
|
1507
1569
|
/* v8 ignore start */
|
|
1508
1570
|
if (toolTurns >= MAX_TOOL_TURNS) {
|
|
1509
1571
|
setMessages([...nextMessages, {
|
|
@@ -1551,17 +1613,23 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1551
1613
|
role: ASSISTANT,
|
|
1552
1614
|
content: ""
|
|
1553
1615
|
};
|
|
1616
|
+
const emptyAssistantMessage = {
|
|
1617
|
+
role: ASSISTANT,
|
|
1618
|
+
content: ""
|
|
1619
|
+
};
|
|
1554
1620
|
let committedMessages = currentMessages;
|
|
1555
1621
|
let assistantCommitted = false;
|
|
1556
1622
|
const commitAssistantMessage = () => {
|
|
1623
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1624
|
+
/* v8 ignore start */
|
|
1557
1625
|
if (assistantCommitted) {
|
|
1558
|
-
// v8 ignore next
|
|
1559
1626
|
if (committedMessages.at(-1)?.role === "assistant") {
|
|
1560
1627
|
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
1561
1628
|
setMessages(committedMessages);
|
|
1562
1629
|
}
|
|
1563
1630
|
return committedMessages;
|
|
1564
1631
|
}
|
|
1632
|
+
/* v8 ignore stop */
|
|
1565
1633
|
assistantCommitted = true;
|
|
1566
1634
|
setStreamingMessage(null);
|
|
1567
1635
|
if (!assistantMessage.content) {
|
|
@@ -1572,17 +1640,32 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1572
1640
|
setMessages(committedMessages);
|
|
1573
1641
|
return committedMessages;
|
|
1574
1642
|
};
|
|
1575
|
-
setStreamingMessage(
|
|
1643
|
+
setStreamingMessage(emptyAssistantMessage);
|
|
1576
1644
|
try {
|
|
1577
1645
|
const readOnlyTools = TOOLS.filter((tool) => READ_TOOLS.has(tool.function.name));
|
|
1578
|
-
|
|
1646
|
+
const planResearchMessages = [...currentMessages, {
|
|
1647
|
+
role: SYSTEM,
|
|
1648
|
+
content: PLAN_INSTRUCTION
|
|
1649
|
+
}];
|
|
1650
|
+
for await (const chunk of streamChat(withSystemMessage(planResearchMessages), modelName, readOnlyTools, controller.signal)) {
|
|
1579
1651
|
// v8 ignore next 3
|
|
1580
1652
|
if (controller.signal.aborted) return;
|
|
1581
1653
|
if (chunk.type === "content") {
|
|
1582
|
-
assistantMessage.content
|
|
1654
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1583
1655
|
setStreamingMessage({ ...assistantMessage });
|
|
1584
1656
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
1585
|
-
const
|
|
1657
|
+
const toolName = toolCall.function.name;
|
|
1658
|
+
if (!READ_TOOLS.has(toolName)) {
|
|
1659
|
+
const correctionMessage = buildPlanModeCorrectionMessage(toolName);
|
|
1660
|
+
setStreamingMessage(null);
|
|
1661
|
+
const newMessages = [...committedMessages, correctionMessage];
|
|
1662
|
+
setMessages(newMessages);
|
|
1663
|
+
await processStreamReadOnly(newMessages);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
setStreamingMessage(emptyAssistantMessage);
|
|
1667
|
+
assistantMessage.content = "";
|
|
1668
|
+
const updatedMessages = committedMessages;
|
|
1586
1669
|
let normalized;
|
|
1587
1670
|
try {
|
|
1588
1671
|
normalized = normalizeToolCall(toolCall);
|
|
@@ -1597,15 +1680,8 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1597
1680
|
await processStreamReadOnly(newMessages);
|
|
1598
1681
|
return;
|
|
1599
1682
|
}
|
|
1600
|
-
if (!READ_TOOLS.has(normalized.name)) {
|
|
1601
|
-
const correctionMessage = buildPlanModeCorrectionMessage(normalized.name);
|
|
1602
|
-
const newMessages = [...updatedMessages, correctionMessage];
|
|
1603
|
-
setMessages(newMessages);
|
|
1604
|
-
await processStreamReadOnly(newMessages);
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
1683
|
const result = await executeTool(normalized.name, normalized.arguments, { allowedTools: READ_TOOLS });
|
|
1608
|
-
const toolResultMessage = buildToolResultMessage(normalized.name, result);
|
|
1684
|
+
const toolResultMessage = buildToolResultMessage(normalized.name, result, normalized.arguments);
|
|
1609
1685
|
const newMessages = [...updatedMessages, toolResultMessage];
|
|
1610
1686
|
setMessages(newMessages);
|
|
1611
1687
|
await processStreamReadOnly(newMessages);
|
|
@@ -1614,6 +1690,14 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1614
1690
|
}
|
|
1615
1691
|
await prewarmCodeBlocks(assistantMessage.content, theme);
|
|
1616
1692
|
const researchMessages = commitAssistantMessage();
|
|
1693
|
+
if (hasExecutablePlan(assistantMessage.content)) {
|
|
1694
|
+
setPendingPlan({
|
|
1695
|
+
planContent: assistantMessage.content,
|
|
1696
|
+
messages: researchMessages
|
|
1697
|
+
});
|
|
1698
|
+
setIsLoading(false);
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1617
1701
|
const planInstruction = {
|
|
1618
1702
|
role: SYSTEM,
|
|
1619
1703
|
content: PLAN_GENERATION_INSTRUCTION
|
|
@@ -1623,13 +1707,13 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1623
1707
|
role: ASSISTANT,
|
|
1624
1708
|
content: ""
|
|
1625
1709
|
};
|
|
1626
|
-
setStreamingMessage(
|
|
1710
|
+
setStreamingMessage(emptyAssistantMessage);
|
|
1627
1711
|
try {
|
|
1628
1712
|
for await (const chunk of streamChat(withSystemMessage(planMessages), modelName, [], controller.signal)) {
|
|
1629
1713
|
// v8 ignore next 3
|
|
1630
1714
|
if (controller.signal.aborted) return;
|
|
1631
1715
|
if (chunk.type === "content") {
|
|
1632
|
-
planAssistantMessage.content
|
|
1716
|
+
planAssistantMessage.content = sanitizeAssistantContent(planAssistantMessage.content + chunk.content);
|
|
1633
1717
|
setStreamingMessage({ ...planAssistantMessage });
|
|
1634
1718
|
}
|
|
1635
1719
|
}
|
|
@@ -1666,7 +1750,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1666
1750
|
model,
|
|
1667
1751
|
theme
|
|
1668
1752
|
]);
|
|
1669
|
-
const
|
|
1753
|
+
const handlePlanReview = useCallback(async (mode) => {
|
|
1670
1754
|
// v8 ignore next
|
|
1671
1755
|
if (!pendingPlan) return;
|
|
1672
1756
|
const { messages: planMessages } = pendingPlan;
|
|
@@ -1702,10 +1786,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1702
1786
|
switch (decision) {
|
|
1703
1787
|
case APPROVE: {
|
|
1704
1788
|
const result = await executeToolCall(toolCall);
|
|
1705
|
-
const toolResultMessage =
|
|
1706
|
-
role: SYSTEM,
|
|
1707
|
-
content: formatToolResultContent(toolCall.function.name, result)
|
|
1708
|
-
};
|
|
1789
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result, toolCall.function.arguments);
|
|
1709
1790
|
const newMessages = [...approvedMessages, toolResultMessage];
|
|
1710
1791
|
setMessages(newMessages);
|
|
1711
1792
|
await processStream(newMessages, executionMode);
|
|
@@ -1717,7 +1798,7 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1717
1798
|
content: formatToolResultContent(toolCall.function.name, {
|
|
1718
1799
|
content: "",
|
|
1719
1800
|
error: "Tool call rejected by user"
|
|
1720
|
-
})
|
|
1801
|
+
}, toolCall.function.arguments)
|
|
1721
1802
|
};
|
|
1722
1803
|
setMessages([...approvedMessages, toolResultMessage]);
|
|
1723
1804
|
setIsLoading(false);
|
|
@@ -1725,7 +1806,11 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1725
1806
|
break;
|
|
1726
1807
|
}
|
|
1727
1808
|
}
|
|
1728
|
-
}, [
|
|
1809
|
+
}, [
|
|
1810
|
+
buildToolResultMessage,
|
|
1811
|
+
pendingToolCall,
|
|
1812
|
+
processStream
|
|
1813
|
+
]);
|
|
1729
1814
|
const handleSubmit = useCallback(async ({ content, images }) => {
|
|
1730
1815
|
setInterruptReason(null);
|
|
1731
1816
|
const userContent = content.trim();
|
|
@@ -1761,9 +1846,9 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
|
|
|
1761
1846
|
streamingMessage,
|
|
1762
1847
|
theme
|
|
1763
1848
|
}),
|
|
1764
|
-
pendingPlan && /* @__PURE__ */ jsx(
|
|
1849
|
+
pendingPlan && /* @__PURE__ */ jsx(PlanReview, {
|
|
1765
1850
|
planContent: pendingPlan.planContent,
|
|
1766
|
-
onModeChange:
|
|
1851
|
+
onModeChange: handlePlanReview,
|
|
1767
1852
|
theme
|
|
1768
1853
|
}),
|
|
1769
1854
|
!pendingPlan && pendingToolCall && /* @__PURE__ */ jsx(ToolApproval, {
|
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.23.0";
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/constants/package.ts
|
|
56
56
|
var NAME = name;
|
|
@@ -105,15 +105,54 @@ var BACK = {
|
|
|
105
105
|
value: "back"
|
|
106
106
|
};
|
|
107
107
|
//#endregion
|
|
108
|
+
//#region src/constants/tool.ts
|
|
109
|
+
var tool_exports = /* @__PURE__ */ __exportAll({
|
|
110
|
+
EDIT_FILE: () => EDIT_FILE,
|
|
111
|
+
GREP_SEARCH: () => GREP_SEARCH,
|
|
112
|
+
LIST_DIR: () => LIST_DIR,
|
|
113
|
+
READ_FILE: () => READ_FILE,
|
|
114
|
+
READ_TOOL_NAMES: () => READ_TOOL_NAMES,
|
|
115
|
+
RUN_SHELL: () => RUN_SHELL,
|
|
116
|
+
VIEW_RANGE: () => VIEW_RANGE,
|
|
117
|
+
WEB_FETCH: () => WEB_FETCH,
|
|
118
|
+
WEB_SEARCH: () => WEB_SEARCH,
|
|
119
|
+
WRITE_FILE: () => WRITE_FILE,
|
|
120
|
+
WRITE_TOOL_NAMES: () => WRITE_TOOL_NAMES
|
|
121
|
+
});
|
|
122
|
+
var READ_FILE = "read_file";
|
|
123
|
+
var WRITE_FILE = "write_file";
|
|
124
|
+
var EDIT_FILE = "edit_file";
|
|
125
|
+
var RUN_SHELL = "run_shell";
|
|
126
|
+
var LIST_DIR = "list_dir";
|
|
127
|
+
var GREP_SEARCH = "grep_search";
|
|
128
|
+
var VIEW_RANGE = "view_range";
|
|
129
|
+
var WEB_SEARCH = "web_search";
|
|
130
|
+
var WEB_FETCH = "web_fetch";
|
|
131
|
+
var READ_TOOL_NAMES = [
|
|
132
|
+
READ_FILE,
|
|
133
|
+
LIST_DIR,
|
|
134
|
+
GREP_SEARCH,
|
|
135
|
+
VIEW_RANGE,
|
|
136
|
+
WEB_SEARCH,
|
|
137
|
+
WEB_FETCH
|
|
138
|
+
];
|
|
139
|
+
var WRITE_TOOL_NAMES = [
|
|
140
|
+
WRITE_FILE,
|
|
141
|
+
EDIT_FILE,
|
|
142
|
+
RUN_SHELL
|
|
143
|
+
];
|
|
144
|
+
//#endregion
|
|
108
145
|
//#region src/constants/prompt.ts
|
|
146
|
+
var PLAN_READ_TOOLS = READ_TOOL_NAMES.join(", ");
|
|
147
|
+
var PLAN_WRITE_TOOLS = WRITE_TOOL_NAMES.join(", ");
|
|
109
148
|
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
149
|
|
|
111
150
|
Follow these rules:
|
|
112
|
-
1.
|
|
113
|
-
2.
|
|
114
|
-
3.
|
|
115
|
-
4.
|
|
116
|
-
5.
|
|
151
|
+
1. Use available tools rather than guessing file contents, paths, or code behavior
|
|
152
|
+
2. When a tool is needed, call it immediately instead of saying you will call it
|
|
153
|
+
3. Read files before editing them to understand context
|
|
154
|
+
4. Make the smallest exact change that satisfies the request
|
|
155
|
+
5. Explain after tool results are available, unless the user asks for discussion or a plan
|
|
117
156
|
6. Confirm with the user before destructive operations
|
|
118
157
|
|
|
119
158
|
When tools return results, incorporate them into your response naturally`;
|
|
@@ -131,20 +170,61 @@ Always use tools when you need to:
|
|
|
131
170
|
- Make file changes
|
|
132
171
|
- Explore project structure
|
|
133
172
|
- Search the codebase
|
|
134
|
-
- Look up current or external information
|
|
135
|
-
|
|
173
|
+
- Look up current or external information
|
|
174
|
+
|
|
175
|
+
Path rules:
|
|
176
|
+
- Paths are relative to the project root unless absolute
|
|
177
|
+
- Preserve parent directories from listings; if list_dir("src") returns [d] utils, use src/utils
|
|
178
|
+
- If a path fails, inspect the parent directory or search before retrying`;
|
|
179
|
+
var PLAN_RESPONSE_TEMPLATE = `If important product, implementation, or safety details are missing, respond with this Markdown template:
|
|
180
|
+
|
|
181
|
+
## Plan Needs Input
|
|
182
|
+
|
|
183
|
+
### Questions
|
|
184
|
+
- ...
|
|
185
|
+
|
|
186
|
+
### What I Found
|
|
187
|
+
- ...
|
|
188
|
+
|
|
189
|
+
### Draft Plan
|
|
190
|
+
- ...
|
|
191
|
+
|
|
192
|
+
If the request is ready for execution, respond with this Markdown template:
|
|
193
|
+
|
|
194
|
+
## Proposed Plan
|
|
195
|
+
|
|
196
|
+
### Summary
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
### Changes
|
|
200
|
+
- ...
|
|
201
|
+
|
|
202
|
+
### Test Plan
|
|
203
|
+
- ...
|
|
204
|
+
|
|
205
|
+
### Execution Steps
|
|
206
|
+
- ...
|
|
207
|
+
|
|
208
|
+
Keep Execution Steps as human-readable bullets for mutating work that needs approval, not preliminary read-only research
|
|
209
|
+
Do not add extra wrapper text before or after the template
|
|
210
|
+
If no execution is needed, answer normally`;
|
|
211
|
+
var PLAN_GENERATION_INSTRUCTION = `Based on the research above, decide whether the user request is ready for execution
|
|
136
212
|
|
|
137
|
-
If the request needs changes or commands, respond with a plan checklist only
|
|
138
213
|
Do not execute any tools
|
|
139
214
|
Do not claim any action was performed
|
|
140
|
-
|
|
215
|
+
Use the exact headings shown below
|
|
141
216
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- [ ] run_shell("command") - Brief description
|
|
217
|
+
${PLAN_RESPONSE_TEMPLATE}`;
|
|
218
|
+
var PLAN_INSTRUCTION = `Plan mode is active
|
|
145
219
|
|
|
146
|
-
Only
|
|
147
|
-
|
|
220
|
+
Only use read-only tools: ${PLAN_READ_TOOLS}
|
|
221
|
+
Do not call ${PLAN_WRITE_TOOLS} during Plan mode
|
|
222
|
+
Use read-only tools to resolve discoverable facts before asking questions
|
|
223
|
+
If the user asks to search, inspect, find, read, or locate something, use read-only tools immediately
|
|
224
|
+
Only ask questions for user preferences or product decisions that cannot be discovered from available tools
|
|
225
|
+
When enough context is available, stop calling tools and produce either Plan Needs Input or Proposed Plan using the required template
|
|
226
|
+
|
|
227
|
+
${PLAN_RESPONSE_TEMPLATE}`;
|
|
148
228
|
//#endregion
|
|
149
229
|
//#region src/constants/role.ts
|
|
150
230
|
var USER = "user";
|
|
@@ -291,28 +371,6 @@ function getTheme(themeId = DEFAULT_THEME_ID) {
|
|
|
291
371
|
return LIST.find(({ id }) => id === themeId) ?? LIST[0];
|
|
292
372
|
}
|
|
293
373
|
//#endregion
|
|
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
|
-
});
|
|
306
|
-
var READ_FILE = "read_file";
|
|
307
|
-
var WRITE_FILE = "write_file";
|
|
308
|
-
var EDIT_FILE = "edit_file";
|
|
309
|
-
var RUN_SHELL = "run_shell";
|
|
310
|
-
var LIST_DIR = "list_dir";
|
|
311
|
-
var GREP_SEARCH = "grep_search";
|
|
312
|
-
var VIEW_RANGE = "view_range";
|
|
313
|
-
var WEB_SEARCH = "web_search";
|
|
314
|
-
var WEB_FETCH = "web_fetch";
|
|
315
|
-
//#endregion
|
|
316
374
|
//#region src/constants/ui.ts
|
|
317
375
|
var HEADER_PREFIX = "🦙 ";
|
|
318
376
|
var WARNING = "⚠️";
|
|
@@ -490,6 +548,15 @@ function saveConfig(patch) {
|
|
|
490
548
|
//#region src/utils/ollama.ts
|
|
491
549
|
var { host } = loadConfig();
|
|
492
550
|
var client = new Ollama({ host });
|
|
551
|
+
var TRAILING_CONTROL_TOKEN_REGEX = /(?:\s*<\|?channel\|?>)+\s*$/;
|
|
552
|
+
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;
|
|
553
|
+
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.";
|
|
554
|
+
function sanitizeAssistantContent(content) {
|
|
555
|
+
return content.replace(TRAILING_CONTROL_TOKEN_REGEX, "");
|
|
556
|
+
}
|
|
557
|
+
function hasUncalledToolIntent(content) {
|
|
558
|
+
return TOOL_INTENT_REGEX.test(content);
|
|
559
|
+
}
|
|
493
560
|
async function checkHealth() {
|
|
494
561
|
try {
|
|
495
562
|
return (await fetch(host)).ok;
|
|
@@ -498,9 +565,15 @@ async function checkHealth() {
|
|
|
498
565
|
}
|
|
499
566
|
}
|
|
500
567
|
async function* streamChat(messages, model, tools, signal) {
|
|
568
|
+
const providerMessages = messages.map(({ role, content, images, tool_calls }) => ({
|
|
569
|
+
role,
|
|
570
|
+
content,
|
|
571
|
+
...images ? { images } : {},
|
|
572
|
+
...tool_calls ? { tool_calls } : {}
|
|
573
|
+
}));
|
|
501
574
|
const response = await client.chat({
|
|
502
575
|
model,
|
|
503
|
-
messages,
|
|
576
|
+
messages: providerMessages,
|
|
504
577
|
stream: true,
|
|
505
578
|
tools,
|
|
506
579
|
// v8 ignore next
|
|
@@ -782,10 +855,10 @@ var TOOLS = [
|
|
|
782
855
|
type: "string",
|
|
783
856
|
description: "The path to the directory to list"
|
|
784
857
|
} }, ["path"]),
|
|
785
|
-
defineTool(GREP_SEARCH, "Search
|
|
858
|
+
defineTool(GREP_SEARCH, "Search files within a directory; multi-word queries also match common code identifier forms", {
|
|
786
859
|
pattern: {
|
|
787
860
|
type: "string",
|
|
788
|
-
description: "The regex
|
|
861
|
+
description: "The regex, phrase, or code concept to search for"
|
|
789
862
|
},
|
|
790
863
|
path: {
|
|
791
864
|
type: "string",
|
|
@@ -819,19 +892,8 @@ var TOOLS = [
|
|
|
819
892
|
description: "The full URL of the page to fetch"
|
|
820
893
|
} }, ["url"])
|
|
821
894
|
];
|
|
822
|
-
var READ_TOOLS = new Set(
|
|
823
|
-
|
|
824
|
-
LIST_DIR,
|
|
825
|
-
GREP_SEARCH,
|
|
826
|
-
VIEW_RANGE,
|
|
827
|
-
WEB_SEARCH,
|
|
828
|
-
WEB_FETCH
|
|
829
|
-
]);
|
|
830
|
-
var WRITE_TOOLS = new Set([
|
|
831
|
-
WRITE_FILE,
|
|
832
|
-
EDIT_FILE,
|
|
833
|
-
RUN_SHELL
|
|
834
|
-
]);
|
|
895
|
+
var READ_TOOLS = new Set(READ_TOOL_NAMES);
|
|
896
|
+
var WRITE_TOOLS = new Set(WRITE_TOOL_NAMES);
|
|
835
897
|
//#endregion
|
|
836
898
|
//#region src/utils/tools/shell.ts
|
|
837
899
|
var execAsync = promisify(exec);
|
|
@@ -861,6 +923,76 @@ async function runShell(command) {
|
|
|
861
923
|
}
|
|
862
924
|
//#endregion
|
|
863
925
|
//#region src/utils/tools/filesystem.ts
|
|
926
|
+
var DIFF_CONTEXT_LINES = 3;
|
|
927
|
+
var DIFF_MAX_LINES = 120;
|
|
928
|
+
var DIFF_MAX_CHARS = 12e3;
|
|
929
|
+
function splitLines(content) {
|
|
930
|
+
return content.split("\n");
|
|
931
|
+
}
|
|
932
|
+
function createUnifiedDiff(filePath, beforeContent, afterContent) {
|
|
933
|
+
const beforeLines = splitLines(beforeContent);
|
|
934
|
+
const afterLines = splitLines(afterContent);
|
|
935
|
+
let commonPrefix = 0;
|
|
936
|
+
while (commonPrefix < beforeLines.length && commonPrefix < afterLines.length && beforeLines[commonPrefix] === afterLines[commonPrefix]) commonPrefix += 1;
|
|
937
|
+
let commonSuffix = 0;
|
|
938
|
+
while (commonSuffix < beforeLines.length - commonPrefix && commonSuffix < afterLines.length - commonPrefix && beforeLines[beforeLines.length - 1 - commonSuffix] === afterLines[afterLines.length - 1 - commonSuffix]) commonSuffix += 1;
|
|
939
|
+
const beforeChangeEnd = beforeLines.length - commonSuffix;
|
|
940
|
+
const afterChangeEnd = afterLines.length - commonSuffix;
|
|
941
|
+
const hunkStart = Math.max(0, commonPrefix - DIFF_CONTEXT_LINES);
|
|
942
|
+
const beforeHunkEnd = Math.min(beforeLines.length, beforeChangeEnd + DIFF_CONTEXT_LINES);
|
|
943
|
+
const afterHunkEnd = Math.min(afterLines.length, afterChangeEnd + DIFF_CONTEXT_LINES);
|
|
944
|
+
const beforeHunkLines = beforeLines.slice(hunkStart, beforeHunkEnd);
|
|
945
|
+
const afterHunkLines = afterLines.slice(hunkStart, afterHunkEnd);
|
|
946
|
+
const beforeChangedLines = beforeLines.slice(commonPrefix, beforeChangeEnd);
|
|
947
|
+
const afterChangedLines = afterLines.slice(commonPrefix, afterChangeEnd);
|
|
948
|
+
const contextBefore = beforeLines.slice(hunkStart, commonPrefix);
|
|
949
|
+
const contextAfter = beforeLines.slice(beforeChangeEnd, beforeHunkEnd);
|
|
950
|
+
return [
|
|
951
|
+
`--- ${filePath}`,
|
|
952
|
+
`+++ ${filePath}`,
|
|
953
|
+
`@@ -${String(hunkStart + 1)},${String(beforeHunkLines.length)} +${String(hunkStart + 1)},${String(afterHunkLines.length)} @@`,
|
|
954
|
+
...contextBefore.map((line) => ` ${line}`),
|
|
955
|
+
...beforeChangedLines.map((line) => `-${line}`),
|
|
956
|
+
...afterChangedLines.map((line) => `+${line}`),
|
|
957
|
+
...contextAfter.map((line) => ` ${line}`)
|
|
958
|
+
].join("\n");
|
|
959
|
+
}
|
|
960
|
+
function truncateDiff(diff) {
|
|
961
|
+
const lines = diff.split("\n");
|
|
962
|
+
let visibleLines = lines.slice(0, DIFF_MAX_LINES);
|
|
963
|
+
let truncated = lines.length > DIFF_MAX_LINES;
|
|
964
|
+
while (visibleLines.join("\n").length > DIFF_MAX_CHARS) {
|
|
965
|
+
visibleLines = visibleLines.slice(0, -1);
|
|
966
|
+
truncated = true;
|
|
967
|
+
}
|
|
968
|
+
if (truncated) visibleLines = [...visibleLines, `[diff truncated: showing ${String(visibleLines.length)} of ${String(lines.length)} lines]`];
|
|
969
|
+
return {
|
|
970
|
+
visible: visibleLines.join("\n"),
|
|
971
|
+
truncated,
|
|
972
|
+
totalLines: lines.length,
|
|
973
|
+
visibleLines: Math.min(visibleLines.length, lines.length)
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function buildSearchPatterns(pattern) {
|
|
977
|
+
const words = pattern.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
978
|
+
if (words.length < 2) return [pattern];
|
|
979
|
+
const camelCase = words.map((word, index) => index === 0 ? word.toLowerCase() : capitalize(word.toLowerCase())).join("");
|
|
980
|
+
const pascalCase = words.map((word) => capitalize(word.toLowerCase())).join("");
|
|
981
|
+
const snakeCase = words.map((word) => word.toLowerCase()).join("_");
|
|
982
|
+
const upperSnakeCase = snakeCase.toUpperCase();
|
|
983
|
+
const flexibleWhitespace = words.join(String.raw`\s+`);
|
|
984
|
+
return Array.from(new Set([
|
|
985
|
+
pattern,
|
|
986
|
+
flexibleWhitespace,
|
|
987
|
+
snakeCase,
|
|
988
|
+
upperSnakeCase,
|
|
989
|
+
camelCase,
|
|
990
|
+
pascalCase
|
|
991
|
+
]));
|
|
992
|
+
}
|
|
993
|
+
function capitalize(value) {
|
|
994
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
995
|
+
}
|
|
864
996
|
/**
|
|
865
997
|
* Read file contents
|
|
866
998
|
*/
|
|
@@ -910,8 +1042,19 @@ function editFile(filePath, oldText, newText) {
|
|
|
910
1042
|
content: "",
|
|
911
1043
|
error: `Exact text matched multiple locations in file: ${filePath}`
|
|
912
1044
|
};
|
|
913
|
-
|
|
914
|
-
|
|
1045
|
+
const updatedContent = content.replace(oldText, newText);
|
|
1046
|
+
const truncatedDiff = truncateDiff(createUnifiedDiff(filePath, content, updatedContent));
|
|
1047
|
+
writeFileSync(filePath, updatedContent, "utf8");
|
|
1048
|
+
return {
|
|
1049
|
+
content: `File edited successfully: ${filePath}`,
|
|
1050
|
+
diff: {
|
|
1051
|
+
path: filePath,
|
|
1052
|
+
visible: truncatedDiff.visible,
|
|
1053
|
+
truncated: truncatedDiff.truncated,
|
|
1054
|
+
totalLines: truncatedDiff.totalLines,
|
|
1055
|
+
visibleLines: truncatedDiff.visibleLines
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
915
1058
|
} catch (error) {
|
|
916
1059
|
return {
|
|
917
1060
|
content: "",
|
|
@@ -966,17 +1109,17 @@ function listDir(dirPath) {
|
|
|
966
1109
|
* Search for pattern in files using ripgrep if available, fallback to Node.js
|
|
967
1110
|
*/
|
|
968
1111
|
async function grepSearch(pattern, dirPath) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
return { content: stdout
|
|
1112
|
+
const patterns = buildSearchPatterns(pattern);
|
|
1113
|
+
for (const searchPattern of patterns) try {
|
|
1114
|
+
const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${searchPattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
|
|
1115
|
+
if (stdout) return { content: stdout };
|
|
973
1116
|
} catch {}
|
|
974
1117
|
try {
|
|
975
1118
|
if (!existsSync(dirPath)) return {
|
|
976
1119
|
content: "",
|
|
977
1120
|
error: `Directory not found: ${dirPath}`
|
|
978
1121
|
};
|
|
979
|
-
const
|
|
1122
|
+
const regexes = patterns.map((searchPattern) => new RegExp(searchPattern));
|
|
980
1123
|
const results = [];
|
|
981
1124
|
function searchDirectory(currentPath) {
|
|
982
1125
|
const entries = readdirSync(currentPath, { withFileTypes: true });
|
|
@@ -986,9 +1129,9 @@ async function grepSearch(pattern, dirPath) {
|
|
|
986
1129
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") searchDirectory(fullPath);
|
|
987
1130
|
} else if (entry.isFile()) try {
|
|
988
1131
|
const lines = readFileSync(fullPath, "utf8").split("\n");
|
|
989
|
-
for (let i = 0; i < lines.length; i++) {
|
|
990
|
-
|
|
991
|
-
|
|
1132
|
+
for (let i = 0; i < lines.length; i++) for (const regex of regexes) if (regex.test(lines[i])) {
|
|
1133
|
+
results.push(`${fullPath}:${(i + 1).toString()}: ${lines[i].trim()}`);
|
|
1134
|
+
break;
|
|
992
1135
|
}
|
|
993
1136
|
} catch {}
|
|
994
1137
|
}
|
|
@@ -1193,7 +1336,7 @@ var REQUIRED_STRING_ARGS = {
|
|
|
1193
1336
|
[WEB_SEARCH]: ["query"],
|
|
1194
1337
|
[WEB_FETCH]: ["url"]
|
|
1195
1338
|
};
|
|
1196
|
-
var TOOL_NAMES = new Set(Object.values(tool_exports));
|
|
1339
|
+
var TOOL_NAMES = new Set(Object.values(tool_exports).filter((value) => typeof value === "string"));
|
|
1197
1340
|
function isToolName(name) {
|
|
1198
1341
|
return TOOL_NAMES.has(name);
|
|
1199
1342
|
}
|
|
@@ -1241,17 +1384,25 @@ function normalizeToolCall(toolCall) {
|
|
|
1241
1384
|
requiresApproval: WRITE_TOOLS.has(name)
|
|
1242
1385
|
};
|
|
1243
1386
|
}
|
|
1244
|
-
function formatToolResultContent(toolName, result) {
|
|
1387
|
+
function formatToolResultContent(toolName, result, args) {
|
|
1388
|
+
const formattedArgs = args ? `(${formatToolArguments(args)})` : "";
|
|
1245
1389
|
const status = result.error ? "The requested action was NOT performed" : "";
|
|
1246
1390
|
const content = result.content ? `\n${result.content}` : "";
|
|
1247
1391
|
const error = result.error ? `\nError: ${result.error}` : "";
|
|
1248
1392
|
return [
|
|
1249
|
-
`Tool ${toolName} result:`,
|
|
1393
|
+
`Tool ${toolName}${formattedArgs} result:`,
|
|
1250
1394
|
status,
|
|
1251
1395
|
content.trim(),
|
|
1252
1396
|
error.trim()
|
|
1253
1397
|
].filter(Boolean).join("\n");
|
|
1254
1398
|
}
|
|
1399
|
+
function formatToolArguments(args) {
|
|
1400
|
+
return JSON.stringify(args, (_, value) => {
|
|
1401
|
+
if (typeof value !== "string") return value;
|
|
1402
|
+
if (value.length <= 80 && !value.includes("\n")) return value;
|
|
1403
|
+
return `<${String(value.length)} chars>`;
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1255
1406
|
async function executeToolCall(toolCall, options) {
|
|
1256
1407
|
try {
|
|
1257
1408
|
const normalized = normalizeToolCall(toolCall);
|
|
@@ -1332,6 +1483,7 @@ async function checkForUpdate() {
|
|
|
1332
1483
|
//#region src/cli.ts
|
|
1333
1484
|
var cli = cac("code-ollama");
|
|
1334
1485
|
var MAX_TOOL_TURNS = 25;
|
|
1486
|
+
var MAX_TOOL_INTENT_CORRECTIONS = 2;
|
|
1335
1487
|
cli.version(VERSION);
|
|
1336
1488
|
cli.help();
|
|
1337
1489
|
cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model, prompt) => {
|
|
@@ -1366,6 +1518,7 @@ async function runPrompt(model, prompt) {
|
|
|
1366
1518
|
async function processRunStream(messages, model) {
|
|
1367
1519
|
let activeMessages = messages;
|
|
1368
1520
|
let toolTurns = 0;
|
|
1521
|
+
let toolIntentCorrections = 0;
|
|
1369
1522
|
while (toolTurns < MAX_TOOL_TURNS) {
|
|
1370
1523
|
const assistantMessage = {
|
|
1371
1524
|
role: ASSISTANT,
|
|
@@ -1374,10 +1527,11 @@ async function processRunStream(messages, model) {
|
|
|
1374
1527
|
let nextMessages = null;
|
|
1375
1528
|
for await (const chunk of streamChat(activeMessages, model, TOOLS)) {
|
|
1376
1529
|
if (chunk.type === "content") {
|
|
1377
|
-
assistantMessage.content
|
|
1530
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content + chunk.content);
|
|
1378
1531
|
write(chunk.content);
|
|
1379
1532
|
continue;
|
|
1380
1533
|
}
|
|
1534
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1381
1535
|
// v8 ignore next 3
|
|
1382
1536
|
if (chunk.tool_calls.length === 0) continue;
|
|
1383
1537
|
const committedMessages = [...activeMessages, assistantMessage];
|
|
@@ -1386,15 +1540,31 @@ async function processRunStream(messages, model) {
|
|
|
1386
1540
|
const result = await executeToolCall(toolCall);
|
|
1387
1541
|
toolResultMessages.push({
|
|
1388
1542
|
role: SYSTEM,
|
|
1389
|
-
content: formatToolResultContent(toolCall.function.name, result)
|
|
1543
|
+
content: formatToolResultContent(toolCall.function.name, result, toolCall.function.arguments)
|
|
1390
1544
|
});
|
|
1391
1545
|
}
|
|
1392
1546
|
nextMessages = [...committedMessages, ...toolResultMessages];
|
|
1393
1547
|
break;
|
|
1394
1548
|
}
|
|
1395
|
-
if (!nextMessages)
|
|
1549
|
+
if (!nextMessages) {
|
|
1550
|
+
assistantMessage.content = sanitizeAssistantContent(assistantMessage.content);
|
|
1551
|
+
if (hasUncalledToolIntent(assistantMessage.content) && toolIntentCorrections < MAX_TOOL_INTENT_CORRECTIONS) {
|
|
1552
|
+
toolIntentCorrections += 1;
|
|
1553
|
+
activeMessages = [
|
|
1554
|
+
...activeMessages,
|
|
1555
|
+
assistantMessage,
|
|
1556
|
+
{
|
|
1557
|
+
role: SYSTEM,
|
|
1558
|
+
content: TOOL_INTENT_CORRECTION
|
|
1559
|
+
}
|
|
1560
|
+
];
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1396
1565
|
activeMessages = nextMessages;
|
|
1397
1566
|
toolTurns += 1;
|
|
1567
|
+
toolIntentCorrections = 0;
|
|
1398
1568
|
}
|
|
1399
1569
|
// v8 ignore next 3
|
|
1400
1570
|
writeError("Tool execution stopped because the maximum tool turn limit was reached\n");
|
|
@@ -1408,7 +1578,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
1408
1578
|
else await launchTui();
|
|
1409
1579
|
}
|
|
1410
1580
|
async function launchTui(sessionId) {
|
|
1411
|
-
const { renderApp } = await import("./assets/tui-
|
|
1581
|
+
const { renderApp } = await import("./assets/tui-iewVFcZW.js");
|
|
1412
1582
|
reset();
|
|
1413
1583
|
renderApp(sessionId);
|
|
1414
1584
|
}
|
|
@@ -1424,4 +1594,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
1424
1594
|
if (isEntrypoint()) main();
|
|
1425
1595
|
// v8 ignore stop
|
|
1426
1596
|
//#endregion
|
|
1427
|
-
export {
|
|
1597
|
+
export { NAME as $, loadConfig as A, ASSISTANT as B, checkHealth as C, pullModel as D, listModels as E, withSystemMessage as F, BACK as G, USER as H, HEADER_PREFIX as I, LABEL as J, CATALOG as K, WARNING as L, removeClipboardImage as M, saveClipboardImage as N, sanitizeAssistantContent as O, resetSystemMessage as P, REJECT as Q, LIST as R, TOOL_INTENT_CORRECTION as S, hasUncalledToolIntent as T, PLAN_GENERATION_INSTRUCTION as U, SYSTEM as V, PLAN_INSTRUCTION as W, SAFE as X, PLAN as Y, APPROVE as Z, loadSession as _, normalizeToolCall as a, reset as b, WRITE_TOOLS as c, write as d, VERSION 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, AUTO as q, executeToolCall as r, TOOLS as s, checkForUpdate as t, LIST$1 as tt, color as u, updateSessionModel as v, deleteModel as w, setClearHandler as x, clear as y, getTheme as z };
|