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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "netheriteai-code",
3
- "version": "0.3.5",
3
+ "version": "0.3.8",
4
4
  "description": "NetheriteAI:Code by hurdacu. High-performance coding assistant.",
5
5
  "author": "hurdacu",
6
6
  "type": "module",
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, yet feminine. Use a polite and encouraging tone.");
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.5";
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
- spawn(process.argv[0], process.argv.slice(1), { stdio: "inherit", detached: false });
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((model) => ({
48
- name: model.name,
49
- size: model.size,
50
- modifiedAt: model.modified_at,
51
- digest: model.digest,
52
- details: model.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
- throw new Error("Server down");
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 && tools.length) body.tools = 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
- return {
81
- ...json.message,
82
- reasoning: json.message?.thinking || "",
83
- };
84
- }
85
-
86
- function createTaggedStreamParser({ onContent, onReasoning }) {
87
- let buffer = "";
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 && tools.length && model !== "glm-5:cloud") body.tools = 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
- const parser = createTaggedStreamParser({
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 message = json.message || {};
180
- if (message.thinking) {
181
- lastMessage.reasoning += message.thinking;
182
- onReasoningChunk?.(message.thinking);
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
- if (message.tool_calls?.length) {
188
- lastMessage.tool_calls = message.tool_calls;
189
- }
190
- if (message.role) {
191
- lastMessage.role = message.role;
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
- } catch {
194
- // Ignore parse errors from partial lines or server noise
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
  }