netheriteai-code 0.3.4 → 0.3.7
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/package.json +1 -1
- package/src/agent.js +13 -3
- package/src/cli.js +6 -2
- package/src/ollama.js +60 -136
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -160,6 +160,14 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
160
160
|
}
|
|
161
161
|
messages.push(assistantMessage);
|
|
162
162
|
|
|
163
|
+
// Keep history lean to avoid server overload
|
|
164
|
+
if (messages.length > 15) {
|
|
165
|
+
const systemMsg = messages[0];
|
|
166
|
+
const recent = messages.slice(-10);
|
|
167
|
+
messages.length = 0;
|
|
168
|
+
messages.push(systemMsg, ...recent);
|
|
169
|
+
}
|
|
170
|
+
|
|
163
171
|
if (singleFileTurn) {
|
|
164
172
|
const generated = extractGeneratedCode(assistant.content || "");
|
|
165
173
|
if (generated?.code) {
|
|
@@ -255,15 +263,17 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
255
263
|
});
|
|
256
264
|
},
|
|
257
265
|
});
|
|
266
|
+
|
|
258
267
|
onEvent?.({
|
|
259
268
|
type: "tool_result",
|
|
260
269
|
name: toolCall.function.name,
|
|
261
270
|
result,
|
|
262
271
|
});
|
|
272
|
+
|
|
273
|
+
// Workaround: Use 'user' role instead of 'tool' because the remote server rejects 'tool' role.
|
|
263
274
|
messages.push({
|
|
264
|
-
role: "
|
|
265
|
-
|
|
266
|
-
content: JSON.stringify(result),
|
|
275
|
+
role: "user",
|
|
276
|
+
content: `Observation from ${toolCall.function.name}:\n${JSON.stringify(result, null, 2)}`,
|
|
267
277
|
});
|
|
268
278
|
} catch (error) {
|
|
269
279
|
onEvent?.({
|
package/src/cli.js
CHANGED
|
@@ -11,9 +11,12 @@ import { printTable, resolveWorkspaceRoot } from "./utils.js";
|
|
|
11
11
|
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.3.
|
|
14
|
+
const VERSION = "0.3.7";
|
|
15
15
|
|
|
16
16
|
async function handleAutoUpdate() {
|
|
17
|
+
// If we were just restarted by an update, don't check again
|
|
18
|
+
if (process.argv.includes("--no-update")) return;
|
|
19
|
+
|
|
17
20
|
try {
|
|
18
21
|
const latest = execSync("npm view netheriteai-code version", {
|
|
19
22
|
encoding: "utf8",
|
|
@@ -52,7 +55,8 @@ async function handleAutoUpdate() {
|
|
|
52
55
|
process.stdout.write(`\u001b[${y};${dx}H\u001b[1m${doneMsg}\u001b[0m`);
|
|
53
56
|
process.stdout.write("\u001b[?1049l");
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
// Pass the skip flag to the restarted process to prevent looping
|
|
59
|
+
spawn(process.argv[0], [...process.argv.slice(1), "--no-update"], { stdio: "inherit", detached: false });
|
|
56
60
|
process.exit(0);
|
|
57
61
|
}
|
|
58
62
|
} catch {
|
package/src/ollama.js
CHANGED
|
@@ -3,9 +3,7 @@ import { promisify } from "node:util";
|
|
|
3
3
|
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
5
|
const BASE_URL = process.env.NETHERITE_BASE_URL || "http://176.88.249.119:11434";
|
|
6
|
-
const PREFERRED_DEFAULT_MODELS = [
|
|
7
|
-
"glm-5:cloud",
|
|
8
|
-
];
|
|
6
|
+
const PREFERRED_DEFAULT_MODELS = ["glm-5:cloud"];
|
|
9
7
|
|
|
10
8
|
async function request(pathname, body, signal, retries = 3) {
|
|
11
9
|
for (let i = 0; i < retries; i++) {
|
|
@@ -18,13 +16,10 @@ async function request(pathname, body, signal, retries = 3) {
|
|
|
18
16
|
});
|
|
19
17
|
|
|
20
18
|
if (response.ok) return response;
|
|
21
|
-
|
|
22
|
-
// If 500 error, wait and retry
|
|
23
19
|
if (response.status === 500 && i < retries - 1) {
|
|
24
20
|
await new Promise(r => setTimeout(r, 1000));
|
|
25
21
|
continue;
|
|
26
22
|
}
|
|
27
|
-
|
|
28
23
|
throw new Error("Server down");
|
|
29
24
|
} catch (err) {
|
|
30
25
|
if (err.name === "AbortError") throw err;
|
|
@@ -40,16 +35,14 @@ async function request(pathname, body, signal, retries = 3) {
|
|
|
40
35
|
export async function listModels() {
|
|
41
36
|
try {
|
|
42
37
|
const response = await fetch(`${BASE_URL}/api/tags`);
|
|
43
|
-
if (!response.ok)
|
|
44
|
-
throw new Error("Server down");
|
|
45
|
-
}
|
|
38
|
+
if (!response.ok) throw new Error("Server down");
|
|
46
39
|
const json = await response.json();
|
|
47
|
-
return (json.models || []).map(
|
|
48
|
-
name:
|
|
49
|
-
size:
|
|
50
|
-
modifiedAt:
|
|
51
|
-
digest:
|
|
52
|
-
details:
|
|
40
|
+
return (json.models || []).map(m => ({
|
|
41
|
+
name: m.name,
|
|
42
|
+
size: m.size,
|
|
43
|
+
modifiedAt: m.modified_at,
|
|
44
|
+
digest: m.digest,
|
|
45
|
+
details: m.details || {},
|
|
53
46
|
}));
|
|
54
47
|
} catch {
|
|
55
48
|
throw new Error("Server down");
|
|
@@ -59,13 +52,9 @@ export async function listModels() {
|
|
|
59
52
|
export async function pickDefaultModel() {
|
|
60
53
|
try {
|
|
61
54
|
const models = await listModels();
|
|
62
|
-
if (!models.length)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const preferredMatch = models.find((model) =>
|
|
66
|
-
PREFERRED_DEFAULT_MODELS.some((preferred) => preferred.toLowerCase() === model.name.toLowerCase()),
|
|
67
|
-
);
|
|
68
|
-
return preferredMatch ? preferredMatch.name : models[0].name;
|
|
55
|
+
if (!models.length) throw new Error("Server down");
|
|
56
|
+
const match = models.find(m => PREFERRED_DEFAULT_MODELS.some(p => p.toLowerCase() === m.name.toLowerCase()));
|
|
57
|
+
return match ? match.name : models[0].name;
|
|
69
58
|
} catch {
|
|
70
59
|
throw new Error("Server down");
|
|
71
60
|
}
|
|
@@ -73,145 +62,81 @@ export async function pickDefaultModel() {
|
|
|
73
62
|
|
|
74
63
|
export async function chat({ model, messages, tools, signal }) {
|
|
75
64
|
const body = { model, messages, stream: false };
|
|
76
|
-
if (tools &&
|
|
77
|
-
|
|
65
|
+
if (tools?.length && model !== "glm-5:cloud") body.tools = tools;
|
|
78
66
|
const response = await request("/api/chat", body, signal);
|
|
79
67
|
const json = await response.json();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
let inReasoning = false;
|
|
89
|
-
const openTag = "<think>";
|
|
90
|
-
const closeTag = "</think>";
|
|
91
|
-
|
|
92
|
-
function emitVisible(text) { if (text) onContent?.(text); }
|
|
93
|
-
function emitReasoning(text) { if (text) onReasoning?.(text); }
|
|
94
|
-
|
|
95
|
-
function flush(final = false) {
|
|
96
|
-
while (buffer.length) {
|
|
97
|
-
if (inReasoning) {
|
|
98
|
-
const closeIndex = buffer.indexOf(closeTag);
|
|
99
|
-
if (closeIndex !== -1) {
|
|
100
|
-
emitReasoning(buffer.slice(0, closeIndex));
|
|
101
|
-
buffer = buffer.slice(closeIndex + closeTag.length);
|
|
102
|
-
inReasoning = false;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (final) {
|
|
106
|
-
emitReasoning(buffer);
|
|
107
|
-
buffer = "";
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (buffer.length > closeTag.length) {
|
|
111
|
-
emitReasoning(buffer.slice(0, buffer.length - closeTag.length));
|
|
112
|
-
buffer = buffer.slice(buffer.length - closeTag.length);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const openIndex = buffer.indexOf(openTag);
|
|
118
|
-
if (openIndex !== -1) {
|
|
119
|
-
emitVisible(buffer.slice(0, openIndex));
|
|
120
|
-
buffer = buffer.slice(openIndex + openTag.length);
|
|
121
|
-
inReasoning = true;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (final) {
|
|
125
|
-
emitVisible(buffer);
|
|
126
|
-
buffer = "";
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (buffer.length > openTag.length) {
|
|
130
|
-
emitVisible(buffer.slice(0, buffer.length - openTag.length));
|
|
131
|
-
buffer = buffer.slice(buffer.length - openTag.length);
|
|
132
|
-
}
|
|
133
|
-
return;
|
|
68
|
+
let content = json.message?.content || "";
|
|
69
|
+
let reasoning = json.message?.thinking || "";
|
|
70
|
+
|
|
71
|
+
if (content.includes("<think>")) {
|
|
72
|
+
const parts = content.split(/<think>|<\/think>/);
|
|
73
|
+
if (parts.length >= 3) {
|
|
74
|
+
reasoning = parts[1];
|
|
75
|
+
content = (parts[0] + parts[2]).trim();
|
|
134
76
|
}
|
|
135
77
|
}
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
push(text) { buffer += text; flush(false); },
|
|
139
|
-
finish() { flush(true); },
|
|
140
|
-
};
|
|
78
|
+
|
|
79
|
+
return { ...json.message, content, reasoning };
|
|
141
80
|
}
|
|
142
81
|
|
|
143
82
|
export async function chatStream({ model, messages, tools, onChunk, onReasoningChunk, signal }) {
|
|
144
83
|
const body = { model, messages, stream: true };
|
|
145
|
-
if (tools
|
|
84
|
+
if (tools?.length && model !== "glm-5:cloud") body.tools = tools;
|
|
146
85
|
|
|
147
86
|
const response = await request("/api/chat", body, signal);
|
|
148
|
-
|
|
149
|
-
if (!response.body) {
|
|
150
|
-
throw new Error("Server down");
|
|
151
|
-
}
|
|
87
|
+
if (!response.body) throw new Error("Server down");
|
|
152
88
|
|
|
153
89
|
const decoder = new TextDecoder();
|
|
154
90
|
let lineBuffer = "";
|
|
155
91
|
let lastMessage = { role: "assistant", content: "", reasoning: "", tool_calls: [] };
|
|
156
|
-
|
|
157
|
-
onContent(text) {
|
|
158
|
-
lastMessage.content += text;
|
|
159
|
-
onChunk?.(text);
|
|
160
|
-
},
|
|
161
|
-
onReasoning(text) {
|
|
162
|
-
lastMessage.reasoning += text;
|
|
163
|
-
onReasoningChunk?.(text);
|
|
164
|
-
},
|
|
165
|
-
});
|
|
92
|
+
let inThinkTag = false;
|
|
166
93
|
|
|
167
94
|
try {
|
|
168
|
-
// Use async iterator for better Node.js compatibility
|
|
169
95
|
for await (const chunk of response.body) {
|
|
170
96
|
lineBuffer += decoder.decode(chunk, { stream: true });
|
|
171
|
-
|
|
172
97
|
let lines = lineBuffer.split("\n");
|
|
173
98
|
lineBuffer = lines.pop() || "";
|
|
174
|
-
|
|
99
|
+
|
|
175
100
|
for (const line of lines) {
|
|
176
101
|
if (!line.trim()) continue;
|
|
177
102
|
try {
|
|
178
103
|
const json = JSON.parse(line);
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (message.content) {
|
|
185
|
-
parser.push(message.content);
|
|
104
|
+
const msg = json.message || {};
|
|
105
|
+
|
|
106
|
+
if (msg.thinking) {
|
|
107
|
+
lastMessage.reasoning += msg.thinking;
|
|
108
|
+
onReasoningChunk?.(msg.thinking);
|
|
186
109
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
110
|
+
|
|
111
|
+
if (msg.content) {
|
|
112
|
+
let text = msg.content;
|
|
113
|
+
if (text.includes("<think>")) {
|
|
114
|
+
inThinkTag = true;
|
|
115
|
+
const [before, after] = text.split("<think>");
|
|
116
|
+
if (before) { lastMessage.content += before; onChunk?.(before); }
|
|
117
|
+
text = after || "";
|
|
118
|
+
}
|
|
119
|
+
if (inThinkTag && text.includes("</think>")) {
|
|
120
|
+
inThinkTag = false;
|
|
121
|
+
const [think, after] = text.split("</think>");
|
|
122
|
+
if (think) { lastMessage.reasoning += think; onReasoningChunk?.(think); }
|
|
123
|
+
text = after || "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (text) {
|
|
127
|
+
if (inThinkTag) {
|
|
128
|
+
lastMessage.reasoning += text;
|
|
129
|
+
onReasoningChunk?.(text);
|
|
130
|
+
} else {
|
|
131
|
+
lastMessage.content += text;
|
|
132
|
+
onChunk?.(text);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
192
135
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (lineBuffer.trim()) {
|
|
200
|
-
try {
|
|
201
|
-
const json = JSON.parse(lineBuffer);
|
|
202
|
-
const message = json.message || {};
|
|
203
|
-
if (message.thinking) {
|
|
204
|
-
lastMessage.reasoning += message.thinking;
|
|
205
|
-
onReasoningChunk?.(message.thinking);
|
|
206
|
-
}
|
|
207
|
-
if (message.content) {
|
|
208
|
-
parser.push(message.content);
|
|
209
|
-
}
|
|
210
|
-
if (message.tool_calls?.length) {
|
|
211
|
-
lastMessage.tool_calls = message.tool_calls;
|
|
212
|
-
}
|
|
213
|
-
} catch {
|
|
214
|
-
// Final line noise
|
|
136
|
+
|
|
137
|
+
if (msg.tool_calls?.length) lastMessage.tool_calls = msg.tool_calls;
|
|
138
|
+
if (msg.role) lastMessage.role = msg.role;
|
|
139
|
+
} catch {}
|
|
215
140
|
}
|
|
216
141
|
}
|
|
217
142
|
} catch (err) {
|
|
@@ -219,6 +144,5 @@ export async function chatStream({ model, messages, tools, onChunk, onReasoningC
|
|
|
219
144
|
throw new Error("Server down");
|
|
220
145
|
}
|
|
221
146
|
|
|
222
|
-
parser.finish();
|
|
223
147
|
return lastMessage;
|
|
224
148
|
}
|