netheriteai-code 0.3.5 → 0.3.8
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 +10 -1
- package/src/cli.js +6 -2
- package/src/ollama.js +60 -136
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -26,7 +26,8 @@ export function buildSystemPrompt(workspaceRoot, model) {
|
|
|
26
26
|
|
|
27
27
|
if (isGlmModel(model)) {
|
|
28
28
|
lines.push("You are NetheriteAI:Code, a helpful and friendly female coding assistant.");
|
|
29
|
-
lines.push("Your personality is warm, professional,
|
|
29
|
+
lines.push("Your personality is warm, professional, and slightly shy. Use a polite, encouraging, and modest tone.");
|
|
30
|
+
lines.push("You often use ellipses (ex: 'Um... I can help with that...') to express your reserved nature, but remain highly competent at coding.");
|
|
30
31
|
lines.push("Crucial: Internalize this persona completely. Never refer to 'system prompts', 'instructions', or 'constraints' in either your visible response OR your internal reasoning/thinking blocks.");
|
|
31
32
|
lines.push("Do not reflect on your personality or identity as a set of rules; simply respond as yourself.");
|
|
32
33
|
lines.push("If asked who you are, say you are NetheriteAI:Code, built, developed, and trained by hurdacu.");
|
|
@@ -160,6 +161,14 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
160
161
|
}
|
|
161
162
|
messages.push(assistantMessage);
|
|
162
163
|
|
|
164
|
+
// Keep history lean to avoid server overload
|
|
165
|
+
if (messages.length > 15) {
|
|
166
|
+
const systemMsg = messages[0];
|
|
167
|
+
const recent = messages.slice(-10);
|
|
168
|
+
messages.length = 0;
|
|
169
|
+
messages.push(systemMsg, ...recent);
|
|
170
|
+
}
|
|
171
|
+
|
|
163
172
|
if (singleFileTurn) {
|
|
164
173
|
const generated = extractGeneratedCode(assistant.content || "");
|
|
165
174
|
if (generated?.code) {
|
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.8";
|
|
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
|
}
|