code-ollama 0.7.0 → 0.8.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-CDaKDOEJ.js → tui-BjeMfZFh.js} +123 -59
- package/dist/cli.js +23 -13
- package/package.json +1 -1
|
@@ -40,9 +40,16 @@ var LABEL = {
|
|
|
40
40
|
};
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/constants/ui.ts
|
|
43
|
-
var HEADER_PREFIX = "🦙";
|
|
43
|
+
var HEADER_PREFIX = "🦙 ";
|
|
44
44
|
//#endregion
|
|
45
|
-
//#region src/components/Messages.
|
|
45
|
+
//#region src/components/Messages/constants.ts
|
|
46
|
+
var TURN_ABORTED_MESSAGE = [
|
|
47
|
+
"<turn_aborted>",
|
|
48
|
+
"The user interrupted the previous turn on purpose. Any running commands may still be running in the background. If any tools were aborted, they may have partially executed.",
|
|
49
|
+
"</turn_aborted>"
|
|
50
|
+
].join("\n");
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/components/Messages/Messages.tsx
|
|
46
53
|
function getMessageColor(role) {
|
|
47
54
|
switch (role) {
|
|
48
55
|
case ROLE.USER: return "black";
|
|
@@ -51,13 +58,13 @@ function getMessageColor(role) {
|
|
|
51
58
|
default: return;
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
|
-
var
|
|
61
|
+
var Message = memo(function Message({ message }) {
|
|
55
62
|
return /* @__PURE__ */ jsx(Box, {
|
|
56
63
|
marginBottom: 1,
|
|
57
64
|
children: /* @__PURE__ */ jsxs(Text, {
|
|
58
65
|
color: getMessageColor(message.role),
|
|
59
66
|
dimColor: message.role === ROLE.SYSTEM,
|
|
60
|
-
children: [message.role === ROLE.USER
|
|
67
|
+
children: [message.role === ROLE.USER && "> ", message.content]
|
|
61
68
|
})
|
|
62
69
|
});
|
|
63
70
|
});
|
|
@@ -65,8 +72,8 @@ function Messages({ messages, isLoading, streamingMessage }) {
|
|
|
65
72
|
return /* @__PURE__ */ jsxs(Box, {
|
|
66
73
|
flexDirection: "column",
|
|
67
74
|
children: [
|
|
68
|
-
messages.map((message, index) => /* @__PURE__ */ jsx(
|
|
69
|
-
streamingMessage && /* @__PURE__ */ jsx(
|
|
75
|
+
messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE).map((message, index) => /* @__PURE__ */ jsx(Message, { message }, index)),
|
|
76
|
+
streamingMessage && /* @__PURE__ */ jsx(Message, { message: streamingMessage }),
|
|
70
77
|
isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
|
|
71
78
|
marginTop: -1,
|
|
72
79
|
marginBottom: 1,
|
|
@@ -226,6 +233,11 @@ function ToolApproval({ toolCall, onDecision }) {
|
|
|
226
233
|
var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
|
|
227
234
|
var PLAN_CHECKLIST_REMINDER = "Then display the execution plan as an unchecked Markdown checklist only";
|
|
228
235
|
var PLAN_EXECUTION_REMINDER = "Do not claim success and do not call write_file or run_shell until the user approves execution";
|
|
236
|
+
var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
|
|
237
|
+
INTERRUPT_REASON["INTERRUPTED"] = "interrupted";
|
|
238
|
+
INTERRUPT_REASON["REJECTED"] = "rejected";
|
|
239
|
+
return INTERRUPT_REASON;
|
|
240
|
+
}({});
|
|
229
241
|
//#endregion
|
|
230
242
|
//#region src/components/Chat/CommandMenu.tsx
|
|
231
243
|
function getMatchingCommands(input) {
|
|
@@ -376,7 +388,7 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
|
|
|
376
388
|
function hasFileSuggestionQuery(input) {
|
|
377
389
|
return /(^|\s)@\S+$/.test(input);
|
|
378
390
|
}
|
|
379
|
-
function Input({ isDisabled = false, onSubmit }) {
|
|
391
|
+
function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
380
392
|
const { exit } = useApp();
|
|
381
393
|
const [input, setInput] = useState("");
|
|
382
394
|
const [inputKey, setInputKey] = useState(0);
|
|
@@ -414,10 +426,19 @@ function Input({ isDisabled = false, onSubmit }) {
|
|
|
414
426
|
if (LIST.find(({ name }) => name === input)) submitAndReset(input);
|
|
415
427
|
}, [submitAndReset]);
|
|
416
428
|
useInput((_input, key) => {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
429
|
+
const isCtrlC = key.ctrl && _input === "c";
|
|
430
|
+
if (isDisabled) {
|
|
431
|
+
if (key.escape || isCtrlC) onInterrupt?.();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (isCtrlC) {
|
|
435
|
+
if (input) {
|
|
436
|
+
setInput("");
|
|
437
|
+
remountTextInput();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
exit();
|
|
441
|
+
}
|
|
421
442
|
});
|
|
422
443
|
return /* @__PURE__ */ jsxs(Box, {
|
|
423
444
|
flexDirection: "column",
|
|
@@ -458,12 +479,15 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
458
479
|
const [isLoading, setIsLoading] = useState(false);
|
|
459
480
|
const [pendingToolCall, setPendingToolCall] = useState(null);
|
|
460
481
|
const [pendingPlan, setPendingPlan] = useState(null);
|
|
482
|
+
const [interruptReason, setInterruptReason] = useState(null);
|
|
483
|
+
const abortControllerRef = useRef(null);
|
|
461
484
|
useEffect(() => {
|
|
462
485
|
setMessages([]);
|
|
463
486
|
setStreamingMessage(null);
|
|
464
487
|
setIsLoading(false);
|
|
465
488
|
setPendingToolCall(null);
|
|
466
489
|
setPendingPlan(null);
|
|
490
|
+
setInterruptReason(null);
|
|
467
491
|
}, [sessionId]);
|
|
468
492
|
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
469
493
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
@@ -490,7 +514,20 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
490
514
|
PLAN_EXECUTION_REMINDER
|
|
491
515
|
].join("\n")
|
|
492
516
|
}), []);
|
|
517
|
+
const handleInterrupt = useCallback(() => {
|
|
518
|
+
abortControllerRef.current?.abort();
|
|
519
|
+
abortControllerRef.current = null;
|
|
520
|
+
setIsLoading(false);
|
|
521
|
+
setStreamingMessage(null);
|
|
522
|
+
setInterruptReason(INTERRUPT_REASON.INTERRUPTED);
|
|
523
|
+
setMessages((prev) => [...prev, {
|
|
524
|
+
role: ROLE.USER,
|
|
525
|
+
content: TURN_ABORTED_MESSAGE
|
|
526
|
+
}]);
|
|
527
|
+
}, []);
|
|
493
528
|
const processStream = useCallback(async (currentMessages, executionMode = mode) => {
|
|
529
|
+
const controller = new AbortController();
|
|
530
|
+
abortControllerRef.current = controller;
|
|
494
531
|
const assistantMessage = {
|
|
495
532
|
role: ROLE.ASSISTANT,
|
|
496
533
|
content: ""
|
|
@@ -518,33 +555,40 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
518
555
|
};
|
|
519
556
|
setStreamingMessage(assistantMessage);
|
|
520
557
|
try {
|
|
521
|
-
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, TOOLS
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
558
|
+
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, TOOLS, controller.signal)) {
|
|
559
|
+
// v8 ignore next 3
|
|
560
|
+
if (controller.signal.aborted) return;
|
|
561
|
+
if (chunk.type === "content") {
|
|
562
|
+
assistantMessage.content += chunk.content;
|
|
563
|
+
setStreamingMessage({ ...assistantMessage });
|
|
564
|
+
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
565
|
+
const requiresApproval = WRITE_TOOLS.has(toolCall.function.name);
|
|
566
|
+
// v8 ignore start
|
|
567
|
+
const allowedTools = executionMode === NAME.PLAN ? READ_TOOLS : void 0;
|
|
568
|
+
// v8 ignore stop
|
|
569
|
+
const updatedMessages = commitAssistantMessage();
|
|
570
|
+
if (executionMode === NAME.SAFE && requiresApproval) {
|
|
571
|
+
setPendingToolCall(toolCall);
|
|
572
|
+
setIsLoading(false);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
|
|
576
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
577
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
578
|
+
setMessages(newMessages);
|
|
579
|
+
await processStream(newMessages, executionMode);
|
|
533
580
|
return;
|
|
534
581
|
}
|
|
535
|
-
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
|
|
536
|
-
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
537
|
-
const newMessages = [...updatedMessages, toolResultMessage];
|
|
538
|
-
setMessages(newMessages);
|
|
539
|
-
await processStream(newMessages, executionMode);
|
|
540
|
-
return;
|
|
541
582
|
}
|
|
542
583
|
commitAssistantMessage();
|
|
543
584
|
} catch (error) {
|
|
544
585
|
// v8 ignore next
|
|
545
|
-
|
|
546
|
-
|
|
586
|
+
if (!controller.signal.aborted) {
|
|
587
|
+
assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
588
|
+
commitAssistantMessage();
|
|
589
|
+
}
|
|
547
590
|
} finally {
|
|
591
|
+
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
548
592
|
setIsLoading(false);
|
|
549
593
|
}
|
|
550
594
|
}, [
|
|
@@ -553,6 +597,8 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
553
597
|
mode
|
|
554
598
|
]);
|
|
555
599
|
const processStreamReadOnly = useCallback(async (currentMessages) => {
|
|
600
|
+
const controller = new AbortController();
|
|
601
|
+
abortControllerRef.current = controller;
|
|
556
602
|
const assistantMessage = {
|
|
557
603
|
role: ROLE.ASSISTANT,
|
|
558
604
|
content: ""
|
|
@@ -581,24 +627,28 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
581
627
|
setStreamingMessage(assistantMessage);
|
|
582
628
|
try {
|
|
583
629
|
const readOnlyTools = TOOLS.filter((tool) => READ_TOOLS.has(tool.function.name));
|
|
584
|
-
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, readOnlyTools
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const
|
|
630
|
+
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, readOnlyTools, controller.signal)) {
|
|
631
|
+
// v8 ignore next 3
|
|
632
|
+
if (controller.signal.aborted) return;
|
|
633
|
+
if (chunk.type === "content") {
|
|
634
|
+
assistantMessage.content += chunk.content;
|
|
635
|
+
setStreamingMessage({ ...assistantMessage });
|
|
636
|
+
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
637
|
+
const updatedMessages = commitAssistantMessage();
|
|
638
|
+
if (!READ_TOOLS.has(toolCall.function.name)) {
|
|
639
|
+
const correctionMessage = buildPlanModeCorrectionMessage(toolCall.function.name);
|
|
640
|
+
const newMessages = [...updatedMessages, correctionMessage];
|
|
641
|
+
setMessages(newMessages);
|
|
642
|
+
await processStreamReadOnly(newMessages);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools: READ_TOOLS });
|
|
646
|
+
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
647
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
592
648
|
setMessages(newMessages);
|
|
593
649
|
await processStreamReadOnly(newMessages);
|
|
594
650
|
return;
|
|
595
651
|
}
|
|
596
|
-
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools: READ_TOOLS });
|
|
597
|
-
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
598
|
-
const newMessages = [...updatedMessages, toolResultMessage];
|
|
599
|
-
setMessages(newMessages);
|
|
600
|
-
await processStreamReadOnly(newMessages);
|
|
601
|
-
return;
|
|
602
652
|
}
|
|
603
653
|
const researchMessages = commitAssistantMessage();
|
|
604
654
|
const planInstruction = {
|
|
@@ -612,9 +662,13 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
612
662
|
};
|
|
613
663
|
setStreamingMessage(planAssistantMessage);
|
|
614
664
|
try {
|
|
615
|
-
for await (const chunk of streamChat(withSystemMessage(planMessages), model, []
|
|
616
|
-
|
|
617
|
-
|
|
665
|
+
for await (const chunk of streamChat(withSystemMessage(planMessages), model, [], controller.signal)) {
|
|
666
|
+
// v8 ignore next 3
|
|
667
|
+
if (controller.signal.aborted) return;
|
|
668
|
+
if (chunk.type === "content") {
|
|
669
|
+
planAssistantMessage.content += chunk.content;
|
|
670
|
+
setStreamingMessage({ ...planAssistantMessage });
|
|
671
|
+
}
|
|
618
672
|
}
|
|
619
673
|
} catch (error) {
|
|
620
674
|
// v8 ignore next
|
|
@@ -634,9 +688,12 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
634
688
|
setIsLoading(false);
|
|
635
689
|
} catch (error) {
|
|
636
690
|
// v8 ignore next
|
|
637
|
-
|
|
638
|
-
|
|
691
|
+
if (!controller.signal.aborted) {
|
|
692
|
+
assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
693
|
+
commitAssistantMessage();
|
|
694
|
+
}
|
|
639
695
|
} finally {
|
|
696
|
+
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
640
697
|
setIsLoading(false);
|
|
641
698
|
}
|
|
642
699
|
}, [
|
|
@@ -689,16 +746,14 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
689
746
|
await processStream(newMessages);
|
|
690
747
|
break;
|
|
691
748
|
}
|
|
692
|
-
case REJECT:
|
|
693
|
-
|
|
694
|
-
role: ROLE.
|
|
695
|
-
content:
|
|
696
|
-
};
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
await processStream(newMessages);
|
|
749
|
+
case REJECT:
|
|
750
|
+
setMessages((previousMessages) => [...previousMessages, {
|
|
751
|
+
role: ROLE.USER,
|
|
752
|
+
content: TURN_ABORTED_MESSAGE
|
|
753
|
+
}]);
|
|
754
|
+
setIsLoading(false);
|
|
755
|
+
setInterruptReason(INTERRUPT_REASON.REJECTED);
|
|
700
756
|
break;
|
|
701
|
-
}
|
|
702
757
|
}
|
|
703
758
|
}, [
|
|
704
759
|
pendingToolCall,
|
|
@@ -706,6 +761,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
706
761
|
processStream
|
|
707
762
|
]);
|
|
708
763
|
const handleSubmit = useCallback(async (value) => {
|
|
764
|
+
setInterruptReason(null);
|
|
709
765
|
const userContent = value.trim();
|
|
710
766
|
if (!userContent) return;
|
|
711
767
|
if (userContent.startsWith("/")) {
|
|
@@ -744,8 +800,16 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
|
744
800
|
toolCall: pendingToolCall,
|
|
745
801
|
onDecision: handleToolApproval
|
|
746
802
|
}),
|
|
803
|
+
interruptReason && !isLoading && /* @__PURE__ */ jsx(Box, {
|
|
804
|
+
marginBottom: 1,
|
|
805
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
806
|
+
color: "red",
|
|
807
|
+
children: interruptReason === INTERRUPT_REASON.REJECTED ? "❗ Tool call rejected." : "❗ Execution interrupted."
|
|
808
|
+
})
|
|
809
|
+
}),
|
|
747
810
|
!pendingPlan && !pendingToolCall && /* @__PURE__ */ jsx(Input, {
|
|
748
811
|
isDisabled: isLoading,
|
|
812
|
+
onInterrupt: handleInterrupt,
|
|
749
813
|
onSubmit: handleSubmit
|
|
750
814
|
})
|
|
751
815
|
]
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { exec } from "node:child_process";
|
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/constants/package.ts
|
|
11
|
-
var VERSION = "0.
|
|
11
|
+
var VERSION = "0.8.0";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/constants/prompt.ts
|
|
14
14
|
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, and searching code
|
|
@@ -134,22 +134,32 @@ function saveConfig(patch) {
|
|
|
134
134
|
//#region src/utils/ollama.ts
|
|
135
135
|
var { host, model: DEFAULT_MODEL } = loadConfig();
|
|
136
136
|
var client = new Ollama({ host });
|
|
137
|
-
async function* streamChat(messages, model = DEFAULT_MODEL, tools) {
|
|
137
|
+
async function* streamChat(messages, model = DEFAULT_MODEL, tools, signal) {
|
|
138
138
|
const response = await client.chat({
|
|
139
139
|
model,
|
|
140
140
|
messages,
|
|
141
141
|
stream: true,
|
|
142
|
-
tools
|
|
142
|
+
tools,
|
|
143
|
+
// v8 ignore next
|
|
144
|
+
...signal ? { signal } : {}
|
|
143
145
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
try {
|
|
147
|
+
for await (const chunk of response) {
|
|
148
|
+
// v8 ignore next 3
|
|
149
|
+
if (signal?.aborted) return;
|
|
150
|
+
if (chunk.message.content) yield {
|
|
151
|
+
type: "content",
|
|
152
|
+
content: chunk.message.content
|
|
153
|
+
};
|
|
154
|
+
if (chunk.message.tool_calls) yield {
|
|
155
|
+
type: "tool_calls",
|
|
156
|
+
tool_calls: chunk.message.tool_calls
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// v8 ignore start
|
|
161
|
+
if (error instanceof Error && (error.name === "AbortError" || signal?.aborted)) return;
|
|
162
|
+
throw error;
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
165
|
async function listModels() {
|
|
@@ -502,7 +512,7 @@ async function processRunStream(messages, model) {
|
|
|
502
512
|
}
|
|
503
513
|
async function main(args = process.argv.slice(2)) {
|
|
504
514
|
if (!args.length) {
|
|
505
|
-
const { renderApp } = await import("./assets/tui-
|
|
515
|
+
const { renderApp } = await import("./assets/tui-BjeMfZFh.js");
|
|
506
516
|
process.stdout.write("\x1Bc");
|
|
507
517
|
renderApp();
|
|
508
518
|
return;
|