code-ollama 0.3.0 → 0.4.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-CccSOcSC.js → tui-BLJeczya.js} +230 -152
- package/dist/cli.js +18 -12
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as NAME, a as setClearHandler, b as NAMES, c as loadConfig, d as withSystemMessage, f as HEADER_PREFIX, g as LABEL, h as VERSION, i as executeTool, l as saveConfig, m as PLAN_GENERATION_INSTRUCTION, n as READ_ONLY_TOOLS, o as listModels, p as ROLE, r as TOOLS, s as streamChat, t as DANGEROUS_TOOLS, u as resetSystemMessage, v as APPROVE, y as REJECT } from "../cli.js";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { Box, Text, render, useInput } from "ink";
|
|
4
|
-
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { memo, useCallback, useEffect, useState } from "react";
|
|
5
5
|
import { Select, Spinner, TextInput } from "@inkjs/ui";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/Messages.tsx
|
|
@@ -13,21 +13,39 @@ function getMessageColor(role) {
|
|
|
13
13
|
default: return;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
var MessageRow = memo(function MessageRow({ message }) {
|
|
17
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
18
|
+
marginBottom: 1,
|
|
19
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
20
|
+
color: getMessageColor(message.role),
|
|
21
|
+
dimColor: message.role === ROLE.SYSTEM,
|
|
22
|
+
children: [message.role === ROLE.USER ? "> " : "", message.content]
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
function Messages({ messages, isLoading, streamingMessage }) {
|
|
17
27
|
return /* @__PURE__ */ jsxs(Box, {
|
|
18
28
|
flexDirection: "column",
|
|
19
|
-
children: [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
children: [
|
|
30
|
+
messages.map((message, index) => /* @__PURE__ */ jsx(MessageRow, { message }, `${String(index)}-${message.role}-${message.content.slice(0, 16)}`)),
|
|
31
|
+
streamingMessage && /* @__PURE__ */ jsx(MessageRow, { message: streamingMessage }),
|
|
32
|
+
isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
|
|
33
|
+
marginTop: -1,
|
|
34
|
+
marginBottom: 1,
|
|
35
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
|
|
25
36
|
})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/components/SelectPrompt.tsx
|
|
42
|
+
function SelectPrompt({ children, onEscape, ...selectProps }) {
|
|
43
|
+
useInput((_, key) => {
|
|
44
|
+
if (key.escape) onEscape?.();
|
|
45
|
+
});
|
|
46
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
47
|
+
flexDirection: "column",
|
|
48
|
+
children: [children, /* @__PURE__ */ jsx(Select, { ...selectProps })]
|
|
31
49
|
});
|
|
32
50
|
}
|
|
33
51
|
//#endregion
|
|
@@ -47,34 +65,33 @@ var options$1 = [
|
|
|
47
65
|
}
|
|
48
66
|
];
|
|
49
67
|
function PlanApproval({ planContent, onModeChange }) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
onModeChange
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
]
|
|
68
|
+
return /* @__PURE__ */ jsx(SelectPrompt, {
|
|
69
|
+
options: options$1,
|
|
70
|
+
onChange: useCallback((value) => {
|
|
71
|
+
onModeChange(value);
|
|
72
|
+
}, [onModeChange]),
|
|
73
|
+
onEscape: useCallback(() => {
|
|
74
|
+
onModeChange(NAME.PLAN);
|
|
75
|
+
}, [onModeChange]),
|
|
76
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
77
|
+
flexDirection: "column",
|
|
78
|
+
marginTop: 1,
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx(Text, {
|
|
81
|
+
bold: true,
|
|
82
|
+
color: "magenta",
|
|
83
|
+
children: "Plan Generated - Choose execution mode:"
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ jsx(Box, {
|
|
86
|
+
marginY: 1,
|
|
87
|
+
children: /* @__PURE__ */ jsx(Text, { children: planContent })
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsx(Text, {
|
|
90
|
+
dimColor: true,
|
|
91
|
+
children: "Select execution mode (↑↓ + Enter to confirm, Esc to cancel)"
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
})
|
|
78
95
|
});
|
|
79
96
|
}
|
|
80
97
|
//#endregion
|
|
@@ -87,15 +104,17 @@ var options = [{
|
|
|
87
104
|
value: REJECT
|
|
88
105
|
}];
|
|
89
106
|
function ToolApproval({ toolCall, onDecision }) {
|
|
90
|
-
useInput((_, key) => {
|
|
91
|
-
if (key.escape) onDecision(REJECT);
|
|
92
|
-
});
|
|
93
107
|
const handleChange = useCallback((value) => {
|
|
94
108
|
onDecision(value);
|
|
95
109
|
}, [onDecision]);
|
|
110
|
+
const handleEscape = useCallback(() => {
|
|
111
|
+
onDecision(REJECT);
|
|
112
|
+
}, [onDecision]);
|
|
96
113
|
const args = JSON.stringify(toolCall.function.arguments, null, 2);
|
|
97
|
-
return /* @__PURE__ */ jsxs(
|
|
98
|
-
|
|
114
|
+
return /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
115
|
+
options,
|
|
116
|
+
onChange: handleChange,
|
|
117
|
+
onEscape: handleEscape,
|
|
99
118
|
children: [
|
|
100
119
|
/* @__PURE__ */ jsx(Text, {
|
|
101
120
|
color: "yellow",
|
|
@@ -124,10 +143,6 @@ function ToolApproval({ toolCall, onDecision }) {
|
|
|
124
143
|
/* @__PURE__ */ jsx(Text, {
|
|
125
144
|
dimColor: true,
|
|
126
145
|
children: "Select approval action (↑↓ + Enter to confirm, Esc to reject)"
|
|
127
|
-
}),
|
|
128
|
-
/* @__PURE__ */ jsx(Select, {
|
|
129
|
-
options,
|
|
130
|
-
onChange: handleChange
|
|
131
146
|
})
|
|
132
147
|
]
|
|
133
148
|
});
|
|
@@ -163,11 +178,19 @@ function hasExecutablePlan(content) {
|
|
|
163
178
|
}
|
|
164
179
|
//#endregion
|
|
165
180
|
//#region src/components/Chat/Chat.tsx
|
|
166
|
-
function Chat({ model, onCommand, mode, onModeChange }) {
|
|
167
|
-
const [messages, setMessages] = useState([
|
|
181
|
+
function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
182
|
+
const [messages, setMessages] = useState([]);
|
|
183
|
+
const [streamingMessage, setStreamingMessage] = useState(null);
|
|
168
184
|
const [isLoading, setIsLoading] = useState(false);
|
|
169
185
|
const [pendingToolCall, setPendingToolCall] = useState(null);
|
|
170
186
|
const [pendingPlan, setPendingPlan] = useState(null);
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
setMessages([]);
|
|
189
|
+
setStreamingMessage(null);
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
setPendingToolCall(null);
|
|
192
|
+
setPendingPlan(null);
|
|
193
|
+
}, [sessionId]);
|
|
171
194
|
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
172
195
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
173
196
|
role: ROLE.SYSTEM,
|
|
@@ -198,20 +221,38 @@ function Chat({ model, onCommand, mode, onModeChange }) {
|
|
|
198
221
|
role: ROLE.ASSISTANT,
|
|
199
222
|
content: ""
|
|
200
223
|
};
|
|
201
|
-
|
|
224
|
+
let committedMessages = currentMessages;
|
|
225
|
+
let assistantCommitted = false;
|
|
226
|
+
const commitAssistantMessage = () => {
|
|
227
|
+
if (assistantCommitted) {
|
|
228
|
+
// v8 ignore next
|
|
229
|
+
if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
|
|
230
|
+
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
231
|
+
setMessages(committedMessages);
|
|
232
|
+
}
|
|
233
|
+
return committedMessages;
|
|
234
|
+
}
|
|
235
|
+
assistantCommitted = true;
|
|
236
|
+
setStreamingMessage(null);
|
|
237
|
+
if (!assistantMessage.content) {
|
|
238
|
+
setMessages(committedMessages);
|
|
239
|
+
return committedMessages;
|
|
240
|
+
}
|
|
241
|
+
committedMessages = [...committedMessages, { ...assistantMessage }];
|
|
242
|
+
setMessages(committedMessages);
|
|
243
|
+
return committedMessages;
|
|
244
|
+
};
|
|
245
|
+
setStreamingMessage(assistantMessage);
|
|
202
246
|
try {
|
|
203
|
-
for await (const chunk of streamChat(currentMessages, model, TOOLS)) if (chunk.type === "content") {
|
|
247
|
+
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, TOOLS)) if (chunk.type === "content") {
|
|
204
248
|
assistantMessage.content += chunk.content;
|
|
205
|
-
|
|
206
|
-
const newMessages = [...previousMessages];
|
|
207
|
-
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
208
|
-
return newMessages;
|
|
209
|
-
});
|
|
249
|
+
setStreamingMessage({ ...assistantMessage });
|
|
210
250
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
211
251
|
const requiresApproval = DANGEROUS_TOOLS.has(toolCall.function.name);
|
|
212
252
|
// v8 ignore start
|
|
213
253
|
const allowedTools = executionMode === NAME.PLAN ? READ_ONLY_TOOLS : void 0;
|
|
214
254
|
// v8 ignore stop
|
|
255
|
+
const updatedMessages = commitAssistantMessage();
|
|
215
256
|
if (executionMode === NAME.SAFE && requiresApproval) {
|
|
216
257
|
setPendingToolCall(toolCall);
|
|
217
258
|
setIsLoading(false);
|
|
@@ -219,22 +260,15 @@ function Chat({ model, onCommand, mode, onModeChange }) {
|
|
|
219
260
|
}
|
|
220
261
|
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
|
|
221
262
|
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
222
|
-
const newMessages = [
|
|
223
|
-
|
|
224
|
-
assistantMessage,
|
|
225
|
-
toolResultMessage
|
|
226
|
-
];
|
|
227
|
-
setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
|
|
263
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
264
|
+
setMessages(newMessages);
|
|
228
265
|
await processStream(newMessages, executionMode);
|
|
229
266
|
return;
|
|
230
267
|
}
|
|
268
|
+
commitAssistantMessage();
|
|
231
269
|
} catch (error) {
|
|
232
270
|
assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
233
|
-
|
|
234
|
-
const newMessages = [...previousMessages];
|
|
235
|
-
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
236
|
-
return newMessages;
|
|
237
|
-
});
|
|
271
|
+
commitAssistantMessage();
|
|
238
272
|
} finally {
|
|
239
273
|
setIsLoading(false);
|
|
240
274
|
}
|
|
@@ -248,73 +282,83 @@ function Chat({ model, onCommand, mode, onModeChange }) {
|
|
|
248
282
|
role: ROLE.ASSISTANT,
|
|
249
283
|
content: ""
|
|
250
284
|
};
|
|
251
|
-
|
|
285
|
+
let committedMessages = currentMessages;
|
|
286
|
+
let assistantCommitted = false;
|
|
287
|
+
const commitAssistantMessage = () => {
|
|
288
|
+
if (assistantCommitted) {
|
|
289
|
+
// v8 ignore next
|
|
290
|
+
if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
|
|
291
|
+
committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
|
|
292
|
+
setMessages(committedMessages);
|
|
293
|
+
}
|
|
294
|
+
return committedMessages;
|
|
295
|
+
}
|
|
296
|
+
assistantCommitted = true;
|
|
297
|
+
setStreamingMessage(null);
|
|
298
|
+
if (!assistantMessage.content) {
|
|
299
|
+
setMessages(committedMessages);
|
|
300
|
+
return committedMessages;
|
|
301
|
+
}
|
|
302
|
+
committedMessages = [...committedMessages, { ...assistantMessage }];
|
|
303
|
+
setMessages(committedMessages);
|
|
304
|
+
return committedMessages;
|
|
305
|
+
};
|
|
306
|
+
setStreamingMessage(assistantMessage);
|
|
252
307
|
try {
|
|
253
308
|
const readOnlyTools = TOOLS.filter((tool) => READ_ONLY_TOOLS.has(tool.function.name));
|
|
254
|
-
for await (const chunk of streamChat(currentMessages, model, readOnlyTools)) if (chunk.type === "content") {
|
|
309
|
+
for await (const chunk of streamChat(withSystemMessage(currentMessages), model, readOnlyTools)) if (chunk.type === "content") {
|
|
255
310
|
assistantMessage.content += chunk.content;
|
|
256
|
-
|
|
257
|
-
const newMessages = [...previousMessages];
|
|
258
|
-
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
259
|
-
return newMessages;
|
|
260
|
-
});
|
|
311
|
+
setStreamingMessage({ ...assistantMessage });
|
|
261
312
|
} else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
|
|
313
|
+
const updatedMessages = commitAssistantMessage();
|
|
262
314
|
if (!READ_ONLY_TOOLS.has(toolCall.function.name)) {
|
|
263
315
|
const correctionMessage = buildPlanModeCorrectionMessage(toolCall.function.name);
|
|
264
|
-
const newMessages = [
|
|
265
|
-
|
|
266
|
-
assistantMessage,
|
|
267
|
-
correctionMessage
|
|
268
|
-
];
|
|
269
|
-
setMessages((previousMessages) => [...previousMessages, correctionMessage]);
|
|
316
|
+
const newMessages = [...updatedMessages, correctionMessage];
|
|
317
|
+
setMessages(newMessages);
|
|
270
318
|
await processStreamReadOnly(newMessages);
|
|
271
319
|
return;
|
|
272
320
|
}
|
|
273
321
|
const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools: READ_ONLY_TOOLS });
|
|
274
322
|
const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
|
|
275
|
-
const newMessages = [
|
|
276
|
-
|
|
277
|
-
assistantMessage,
|
|
278
|
-
toolResultMessage
|
|
279
|
-
];
|
|
280
|
-
setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
|
|
323
|
+
const newMessages = [...updatedMessages, toolResultMessage];
|
|
324
|
+
setMessages(newMessages);
|
|
281
325
|
await processStreamReadOnly(newMessages);
|
|
282
326
|
return;
|
|
283
327
|
}
|
|
328
|
+
const researchMessages = commitAssistantMessage();
|
|
284
329
|
const planInstruction = {
|
|
285
330
|
role: ROLE.SYSTEM,
|
|
286
331
|
content: PLAN_GENERATION_INSTRUCTION
|
|
287
332
|
};
|
|
288
|
-
const planMessages = [
|
|
289
|
-
...currentMessages,
|
|
290
|
-
assistantMessage,
|
|
291
|
-
planInstruction
|
|
292
|
-
];
|
|
333
|
+
const planMessages = [...researchMessages, planInstruction];
|
|
293
334
|
const planAssistantMessage = {
|
|
294
335
|
role: ROLE.ASSISTANT,
|
|
295
336
|
content: ""
|
|
296
337
|
};
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
338
|
+
setStreamingMessage(planAssistantMessage);
|
|
339
|
+
try {
|
|
340
|
+
for await (const chunk of streamChat(withSystemMessage(planMessages), model, [])) if (chunk.type === "content") {
|
|
341
|
+
planAssistantMessage.content += chunk.content;
|
|
342
|
+
setStreamingMessage({ ...planAssistantMessage });
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
planAssistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
346
|
+
setMessages([...planMessages, { ...planAssistantMessage }]);
|
|
347
|
+
setStreamingMessage(null);
|
|
348
|
+
setIsLoading(false);
|
|
349
|
+
return;
|
|
305
350
|
}
|
|
351
|
+
const finalPlanMessages = [...planMessages, { ...planAssistantMessage }];
|
|
352
|
+
setMessages(finalPlanMessages);
|
|
353
|
+
setStreamingMessage(null);
|
|
306
354
|
if (hasExecutablePlan(planAssistantMessage.content)) setPendingPlan({
|
|
307
355
|
planContent: planAssistantMessage.content,
|
|
308
|
-
messages:
|
|
356
|
+
messages: finalPlanMessages
|
|
309
357
|
});
|
|
310
358
|
setIsLoading(false);
|
|
311
359
|
} catch (error) {
|
|
312
360
|
assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
313
|
-
|
|
314
|
-
const newMessages = [...previousMessages];
|
|
315
|
-
newMessages[newMessages.length - 1] = { ...assistantMessage };
|
|
316
|
-
return newMessages;
|
|
317
|
-
});
|
|
361
|
+
commitAssistantMessage();
|
|
318
362
|
} finally {
|
|
319
363
|
setIsLoading(false);
|
|
320
364
|
}
|
|
@@ -396,8 +440,8 @@ function Chat({ model, onCommand, mode, onModeChange }) {
|
|
|
396
440
|
role: ROLE.USER,
|
|
397
441
|
content: userContent
|
|
398
442
|
};
|
|
399
|
-
setMessages((previousMessages) => [...previousMessages, userMessage]);
|
|
400
443
|
const updatedMessages = [...messages, userMessage];
|
|
444
|
+
setMessages(updatedMessages);
|
|
401
445
|
if (mode === NAME.PLAN) await processStreamReadOnly(updatedMessages);
|
|
402
446
|
else await processStream(updatedMessages);
|
|
403
447
|
}, [
|
|
@@ -411,8 +455,9 @@ function Chat({ model, onCommand, mode, onModeChange }) {
|
|
|
411
455
|
flexDirection: "column",
|
|
412
456
|
children: [
|
|
413
457
|
/* @__PURE__ */ jsx(Messages, {
|
|
414
|
-
messages
|
|
415
|
-
isLoading
|
|
458
|
+
messages,
|
|
459
|
+
isLoading,
|
|
460
|
+
streamingMessage
|
|
416
461
|
}),
|
|
417
462
|
pendingPlan && /* @__PURE__ */ jsx(PlanApproval, {
|
|
418
463
|
planContent: pendingPlan.planContent,
|
|
@@ -512,40 +557,44 @@ function Header({ model }) {
|
|
|
512
557
|
}
|
|
513
558
|
//#endregion
|
|
514
559
|
//#region src/components/ModelPicker.tsx
|
|
515
|
-
function ModelPicker({ currentModel, onSelect,
|
|
560
|
+
function ModelPicker({ currentModel, onSelect, onClose }) {
|
|
516
561
|
const [options, setOptions] = useState([]);
|
|
517
562
|
const [error, setError] = useState(null);
|
|
563
|
+
useInput((_, key) => {
|
|
564
|
+
if (options.length && key.return) setTimeout(onClose);
|
|
565
|
+
});
|
|
518
566
|
useEffect(() => {
|
|
519
567
|
async function load() {
|
|
520
568
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
569
|
+
const models = await listModels();
|
|
570
|
+
if (models.includes(currentModel)) {
|
|
571
|
+
models.splice(models.indexOf(currentModel), 1);
|
|
572
|
+
models.unshift(currentModel);
|
|
573
|
+
}
|
|
574
|
+
setOptions(models.map((model) => ({
|
|
575
|
+
label: model,
|
|
576
|
+
value: model
|
|
524
577
|
})));
|
|
525
|
-
} catch (
|
|
526
|
-
setError(
|
|
578
|
+
} catch (error) {
|
|
579
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
527
580
|
}
|
|
528
581
|
}
|
|
529
582
|
load();
|
|
530
|
-
}, []);
|
|
531
|
-
useInput((_, key) => {
|
|
532
|
-
if (key.escape) onCancel();
|
|
533
|
-
});
|
|
583
|
+
}, [currentModel]);
|
|
534
584
|
if (error) return /* @__PURE__ */ jsxs(Text, {
|
|
535
585
|
color: "red",
|
|
536
586
|
children: ["Error loading models: ", error]
|
|
537
587
|
});
|
|
538
588
|
if (!options.length) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
|
|
539
|
-
return /* @__PURE__ */
|
|
540
|
-
|
|
541
|
-
|
|
589
|
+
return /* @__PURE__ */ jsx(SelectPrompt, {
|
|
590
|
+
options,
|
|
591
|
+
defaultValue: currentModel,
|
|
592
|
+
onChange: onSelect,
|
|
593
|
+
onEscape: onClose,
|
|
594
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
542
595
|
dimColor: true,
|
|
543
596
|
children: "Select a model (↑↓ + Enter to confirm, Esc to cancel)"
|
|
544
|
-
})
|
|
545
|
-
options,
|
|
546
|
-
defaultValue: currentModel,
|
|
547
|
-
onChange: onSelect
|
|
548
|
-
})]
|
|
597
|
+
})
|
|
549
598
|
});
|
|
550
599
|
}
|
|
551
600
|
//#endregion
|
|
@@ -554,43 +603,64 @@ function App() {
|
|
|
554
603
|
const [model, setModel] = useState(() => loadConfig().model);
|
|
555
604
|
const [picking, setPicking] = useState(false);
|
|
556
605
|
const [mode, setMode] = useState(NAME.SAFE);
|
|
606
|
+
const [sessionId, setSessionId] = useState(0);
|
|
557
607
|
const handleCommand = useCallback((command) => {
|
|
558
|
-
|
|
608
|
+
switch (command) {
|
|
609
|
+
case "/model":
|
|
610
|
+
setPicking(true);
|
|
611
|
+
break;
|
|
612
|
+
case "/clear":
|
|
613
|
+
resetSystemMessage();
|
|
614
|
+
setPicking(false);
|
|
615
|
+
setSessionId((sessionId) => sessionId + 1);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
559
618
|
}, []);
|
|
560
619
|
const handleSelect = useCallback((selected) => {
|
|
561
620
|
setModel(selected);
|
|
562
621
|
saveConfig({ model: selected });
|
|
563
622
|
setPicking(false);
|
|
564
623
|
}, []);
|
|
565
|
-
const
|
|
624
|
+
const handleClose = useCallback(() => {
|
|
566
625
|
setPicking(false);
|
|
567
626
|
}, []);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
627
|
+
const handleToggleMode = useCallback(() => {
|
|
628
|
+
setMode((mode) => {
|
|
629
|
+
switch (mode) {
|
|
630
|
+
case NAME.SAFE: return NAME.AUTO;
|
|
631
|
+
case NAME.AUTO: return NAME.PLAN;
|
|
632
|
+
case NAME.PLAN:
|
|
633
|
+
default: return NAME.SAFE;
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}, []);
|
|
637
|
+
let body;
|
|
638
|
+
switch (true) {
|
|
639
|
+
case picking:
|
|
640
|
+
body = /* @__PURE__ */ jsx(ModelPicker, {
|
|
573
641
|
currentModel: model,
|
|
574
642
|
onSelect: handleSelect,
|
|
575
|
-
|
|
576
|
-
})
|
|
643
|
+
onClose: handleClose
|
|
644
|
+
});
|
|
645
|
+
break;
|
|
646
|
+
default:
|
|
647
|
+
body = /* @__PURE__ */ jsx(Chat, {
|
|
577
648
|
model,
|
|
578
649
|
onCommand: handleCommand,
|
|
579
650
|
mode,
|
|
580
|
-
onModeChange: setMode
|
|
581
|
-
|
|
651
|
+
onModeChange: setMode,
|
|
652
|
+
sessionId
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
657
|
+
flexDirection: "column",
|
|
658
|
+
children: [
|
|
659
|
+
/* @__PURE__ */ jsx(Header, { model }),
|
|
660
|
+
body,
|
|
582
661
|
/* @__PURE__ */ jsx(Footer, {
|
|
583
662
|
mode,
|
|
584
|
-
onToggleMode:
|
|
585
|
-
setMode((mode) => {
|
|
586
|
-
switch (mode) {
|
|
587
|
-
case NAME.SAFE: return NAME.AUTO;
|
|
588
|
-
case NAME.AUTO: return NAME.PLAN;
|
|
589
|
-
case NAME.PLAN:
|
|
590
|
-
default: return NAME.SAFE;
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
663
|
+
onToggleMode: handleToggleMode
|
|
594
664
|
})
|
|
595
665
|
]
|
|
596
666
|
});
|
|
@@ -598,7 +668,15 @@ function App() {
|
|
|
598
668
|
//#endregion
|
|
599
669
|
//#region src/tui.tsx
|
|
600
670
|
function renderApp() {
|
|
601
|
-
|
|
671
|
+
const tree = /* @__PURE__ */ jsx(App, {});
|
|
672
|
+
const app = render(tree, {
|
|
673
|
+
incrementalRendering: true,
|
|
674
|
+
maxFps: 60
|
|
675
|
+
});
|
|
676
|
+
setClearHandler(() => {
|
|
677
|
+
app.clear();
|
|
678
|
+
app.rerender(tree);
|
|
679
|
+
});
|
|
602
680
|
}
|
|
603
681
|
//#endregion
|
|
604
682
|
export { renderApp };
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,9 @@ import { Ollama } from "ollama";
|
|
|
7
7
|
import { exec } from "node:child_process";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
var NAMES = [{
|
|
10
|
+
name: "/clear",
|
|
11
|
+
description: "clear the current session"
|
|
12
|
+
}, {
|
|
10
13
|
name: "/model",
|
|
11
14
|
description: "switch the model"
|
|
12
15
|
}].map(({ name }) => name);
|
|
@@ -28,7 +31,7 @@ var LABEL = {
|
|
|
28
31
|
};
|
|
29
32
|
//#endregion
|
|
30
33
|
//#region src/constants/package.ts
|
|
31
|
-
var VERSION = "0.
|
|
34
|
+
var VERSION = "0.4.0";
|
|
32
35
|
//#endregion
|
|
33
36
|
//#region src/constants/prompt.ts
|
|
34
37
|
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
|
|
@@ -108,12 +111,20 @@ function buildSystemPrompt() {
|
|
|
108
111
|
parts.push("\n\n", TOOL_INSTRUCTIONS);
|
|
109
112
|
return parts.join("");
|
|
110
113
|
}
|
|
114
|
+
var systemMessage = null;
|
|
111
115
|
function createSystemMessage() {
|
|
112
116
|
return {
|
|
113
117
|
role: ROLE.SYSTEM,
|
|
114
118
|
content: buildSystemPrompt()
|
|
115
119
|
};
|
|
116
120
|
}
|
|
121
|
+
function resetSystemMessage() {
|
|
122
|
+
systemMessage = null;
|
|
123
|
+
}
|
|
124
|
+
function withSystemMessage(messages) {
|
|
125
|
+
systemMessage ??= createSystemMessage();
|
|
126
|
+
return [systemMessage, ...messages];
|
|
127
|
+
}
|
|
117
128
|
//#endregion
|
|
118
129
|
//#region src/utils/config.ts
|
|
119
130
|
var CONFIG_DIR = join(homedir(), ".code-ollama");
|
|
@@ -171,12 +182,7 @@ async function listModels() {
|
|
|
171
182
|
const { models } = await client.list();
|
|
172
183
|
return models.map(({ name }) => name);
|
|
173
184
|
}
|
|
174
|
-
|
|
175
|
-
//#region src/utils/screen.ts
|
|
176
|
-
var CLEAR = "\x1Bc";
|
|
177
|
-
function clear() {
|
|
178
|
-
process.stdout.write(CLEAR);
|
|
179
|
-
}
|
|
185
|
+
function setClearHandler(handler) {}
|
|
180
186
|
//#endregion
|
|
181
187
|
//#region src/utils/tools.ts
|
|
182
188
|
var execAsync = promisify(exec);
|
|
@@ -519,8 +525,8 @@ async function processRunStream(messages, model) {
|
|
|
519
525
|
}
|
|
520
526
|
async function main(args = process.argv.slice(2)) {
|
|
521
527
|
if (!args.length) {
|
|
522
|
-
const { renderApp } = await import("./assets/tui-
|
|
523
|
-
|
|
528
|
+
const { renderApp } = await import("./assets/tui-BLJeczya.js");
|
|
529
|
+
process.stdout.write("\x1Bc");
|
|
524
530
|
renderApp();
|
|
525
531
|
return;
|
|
526
532
|
}
|
|
@@ -530,7 +536,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
530
536
|
...args
|
|
531
537
|
]);
|
|
532
538
|
}
|
|
533
|
-
|
|
539
|
+
// v8 ignore start
|
|
534
540
|
function isEntrypoint(argv1 = process.argv[1]) {
|
|
535
541
|
if (!argv1) return false;
|
|
536
542
|
try {
|
|
@@ -540,6 +546,6 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
540
546
|
}
|
|
541
547
|
}
|
|
542
548
|
if (isEntrypoint()) main();
|
|
543
|
-
|
|
549
|
+
// v8 ignore stop
|
|
544
550
|
//#endregion
|
|
545
|
-
export {
|
|
551
|
+
export { NAME$1 as _, setClearHandler as a, NAMES as b, loadConfig as c, withSystemMessage as d, HEADER_PREFIX as f, LABEL as g, VERSION as h, executeTool as i, saveConfig as l, PLAN_GENERATION_INSTRUCTION as m, main, READ_ONLY_TOOLS as n, listModels as o, ROLE as p, TOOLS as r, streamChat as s, DANGEROUS_TOOLS as t, resetSystemMessage as u, APPROVE as v, REJECT as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Ollama coding agent that runs in your terminal",
|
|
5
5
|
"author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
|
|
6
6
|
"type": "module",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"cac": "7.0.0",
|
|
44
44
|
"ink": "7.0.2",
|
|
45
45
|
"ollama": "0.6.3",
|
|
46
|
-
"react": "19.2.
|
|
46
|
+
"react": "19.2.6"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@commitlint/cli": "20.5.3",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"globals": "17.6.0",
|
|
60
60
|
"husky": "9.1.7",
|
|
61
61
|
"ink-testing-library": "4.0.0",
|
|
62
|
-
"lint-staged": "
|
|
62
|
+
"lint-staged": "17.0.2",
|
|
63
63
|
"prettier": "3.8.3",
|
|
64
64
|
"publint": "0.3.19",
|
|
65
65
|
"tsx": "4.21.0",
|