naiad-cli 0.1.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.
Files changed (50) hide show
  1. package/dist/api/client.d.ts +81 -0
  2. package/dist/api/client.js +130 -0
  3. package/dist/api/client.js.map +1 -0
  4. package/dist/callback/server.d.ts +11 -0
  5. package/dist/callback/server.js +53 -0
  6. package/dist/callback/server.js.map +1 -0
  7. package/dist/callback/server.test.d.ts +1 -0
  8. package/dist/callback/server.test.js +79 -0
  9. package/dist/callback/server.test.js.map +1 -0
  10. package/dist/commands/exec.d.ts +10 -0
  11. package/dist/commands/exec.js +94 -0
  12. package/dist/commands/exec.js.map +1 -0
  13. package/dist/commands/interactive.d.ts +6 -0
  14. package/dist/commands/interactive.js +137 -0
  15. package/dist/commands/interactive.js.map +1 -0
  16. package/dist/config/config.d.ts +5 -0
  17. package/dist/config/config.js +21 -0
  18. package/dist/config/config.js.map +1 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +137 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/lifecycle/setup.d.ts +11 -0
  23. package/dist/lifecycle/setup.js +49 -0
  24. package/dist/lifecycle/setup.js.map +1 -0
  25. package/dist/lifecycle/teardown.d.ts +6 -0
  26. package/dist/lifecycle/teardown.js +34 -0
  27. package/dist/lifecycle/teardown.js.map +1 -0
  28. package/dist/lifecycle/types.d.ts +21 -0
  29. package/dist/lifecycle/types.js +2 -0
  30. package/dist/lifecycle/types.js.map +1 -0
  31. package/dist/lifecycle/workers.d.ts +2 -0
  32. package/dist/lifecycle/workers.js +22 -0
  33. package/dist/lifecycle/workers.js.map +1 -0
  34. package/dist/pi/launcher.d.ts +26 -0
  35. package/dist/pi/launcher.js +109 -0
  36. package/dist/pi/launcher.js.map +1 -0
  37. package/dist/sync/events.d.ts +14 -0
  38. package/dist/sync/events.js +44 -0
  39. package/dist/sync/events.js.map +1 -0
  40. package/dist/sync/heartbeat.d.ts +12 -0
  41. package/dist/sync/heartbeat.js +33 -0
  42. package/dist/sync/heartbeat.js.map +1 -0
  43. package/dist/sync/session-upload.d.ts +12 -0
  44. package/dist/sync/session-upload.js +69 -0
  45. package/dist/sync/session-upload.js.map +1 -0
  46. package/dist/utils/package-root.d.ts +1 -0
  47. package/dist/utils/package-root.js +13 -0
  48. package/dist/utils/package-root.js.map +1 -0
  49. package/extensions/naiad-extension.ts +330 -0
  50. package/package.json +31 -0
@@ -0,0 +1,69 @@
1
+ import { readFileSync, readdirSync, statSync } from "fs";
2
+ import { join } from "path";
3
+ import { createHash } from "crypto";
4
+ const CHUNK_SIZE = 4 * 1024 * 1024; // 4 MiB
5
+ export class SessionUploader {
6
+ client;
7
+ sessionId;
8
+ sessionDir;
9
+ pollTimer = null;
10
+ constructor(client, sessionId, sessionDir) {
11
+ this.client = client;
12
+ this.sessionId = sessionId;
13
+ this.sessionDir = sessionDir;
14
+ }
15
+ start() {
16
+ this.pollTimer = setInterval(() => this.checkAndUpload(), 10_000);
17
+ }
18
+ async stop() {
19
+ if (this.pollTimer) {
20
+ clearInterval(this.pollTimer);
21
+ this.pollTimer = null;
22
+ }
23
+ await this.checkAndUpload();
24
+ }
25
+ async checkAndUpload() {
26
+ try {
27
+ const files = readdirSync(this.sessionDir).filter((f) => f.endsWith(".jsonl"));
28
+ for (const file of files) {
29
+ await this.uploadFile(join(this.sessionDir, file));
30
+ }
31
+ }
32
+ catch {
33
+ // Session dir may not exist yet
34
+ }
35
+ }
36
+ async uploadFile(filePath) {
37
+ const stat = statSync(filePath);
38
+ if (stat.size === 0)
39
+ return;
40
+ const data = readFileSync(filePath);
41
+ const totalBytes = data.length;
42
+ const chunkCount = Math.ceil(totalBytes / CHUNK_SIZE);
43
+ const fileHash = createHash("sha256").update(data).digest("hex");
44
+ // Build chunks
45
+ const chunks = [];
46
+ for (let i = 0; i < chunkCount; i++) {
47
+ const start = i * CHUNK_SIZE;
48
+ const end = Math.min(start + CHUNK_SIZE, totalBytes);
49
+ const chunkData = data.subarray(start, end);
50
+ const chunkHash = createHash("sha256").update(chunkData).digest("hex");
51
+ chunks.push({ start, end, sha256_hex: chunkHash, data: chunkData });
52
+ }
53
+ // Get presigned URLs
54
+ const { urls } = await this.client.getUploadUrls(this.sessionId, chunks.map((c) => ({ start: c.start, end: c.end, sha256_hex: c.sha256_hex })));
55
+ // Upload chunks
56
+ for (let i = 0; i < urls.length; i++) {
57
+ const res = await fetch(urls[i].url, {
58
+ method: "PUT",
59
+ body: new Uint8Array(chunks[i].data),
60
+ });
61
+ if (!res.ok) {
62
+ throw new Error(`Chunk upload failed: ${res.status}`);
63
+ }
64
+ }
65
+ // Finalize
66
+ await this.client.finalizeSession(this.sessionId, totalBytes, chunkCount, fileHash);
67
+ }
68
+ }
69
+ //# sourceMappingURL=session-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-upload.js","sourceRoot":"","sources":["../../src/sync/session-upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function findPackageRoot(): string;
@@ -0,0 +1,13 @@
1
+ import { dirname, join } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { existsSync } from "fs";
4
+ export function findPackageRoot() {
5
+ let dir = dirname(fileURLToPath(import.meta.url));
6
+ while (dir !== dirname(dir)) {
7
+ if (existsSync(join(dir, "package.json")))
8
+ return dir;
9
+ dir = dirname(dir);
10
+ }
11
+ throw new Error("Cannot find package root");
12
+ }
13
+ //# sourceMappingURL=package-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-root.js","sourceRoot":"","sources":["../../src/utils/package-root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAEhC,MAAM,UAAU,eAAe;IAC7B,IAAI,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,OAAO,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACtD,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,330 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import {
3
+ type AssistantMessage,
4
+ type AssistantMessageEventStream,
5
+ type Context,
6
+ type Model,
7
+ type SimpleStreamOptions,
8
+ type ToolCall,
9
+ createAssistantMessageEventStream,
10
+ } from "@mariozechner/pi-ai";
11
+
12
+ export default function (pi: ExtensionAPI) {
13
+ const inferenceUrl = process.env.NAIAD_INFERENCE_URL;
14
+ const apiKey = process.env.NAIAD_API_KEY ?? "";
15
+ const model = process.env.NAIAD_MODEL ?? "llama-3.1-8b";
16
+ const threadId = process.env.NAIAD_THREAD_ID ?? "";
17
+ const sessionId = process.env.NAIAD_SESSION_ID ?? "";
18
+
19
+ if (!inferenceUrl) {
20
+ console.error("[naiad-extension] NAIAD_INFERENCE_URL not set, skipping provider registration");
21
+ return;
22
+ }
23
+
24
+ // Event forwarding via HTTP callback (for interactive mode)
25
+ const callbackUrl = process.env.NAIAD_CALLBACK_URL;
26
+ if (callbackUrl && typeof (pi as any).on === "function") {
27
+ const postEvent = (type: string, data: unknown) => {
28
+ fetch(callbackUrl, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ type, data }),
32
+ }).catch(() => {
33
+ // Best effort — don't crash the agent if callback fails
34
+ });
35
+ };
36
+
37
+ (pi as any).on("agent_start", (event: unknown) => postEvent("agent_start", event));
38
+ (pi as any).on("agent_end", (event: unknown) => postEvent("agent_end", event));
39
+ (pi as any).on("turn_start", (event: unknown) => postEvent("turn_start", event));
40
+ (pi as any).on("turn_end", (event: unknown) => postEvent("turn_end", event));
41
+ (pi as any).on("message_start", (event: unknown) => postEvent("message_start", event));
42
+ (pi as any).on("message_end", (event: unknown) => postEvent("message_end", event));
43
+ (pi as any).on("message_update", (event: unknown) => postEvent("message_update", event));
44
+ (pi as any).on("tool_execution_start", (event: unknown) => postEvent("tool_execution_start", event));
45
+ (pi as any).on("tool_execution_end", (event: unknown) => postEvent("tool_execution_end", event));
46
+ }
47
+
48
+ pi.registerProvider("naiad-proxy", {
49
+ baseUrl: inferenceUrl,
50
+ apiKey: "NAIAD_API_KEY",
51
+ api: "naiad-openai",
52
+ models: [
53
+ {
54
+ id: model,
55
+ name: `Naiad Model (${model})`,
56
+ input: ["text"],
57
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
58
+ contextWindow: 131072,
59
+ maxTokens: 8192,
60
+ },
61
+ ],
62
+ streamSimple: streamNaiad,
63
+ });
64
+
65
+ function convertMessages(context: Context): any[] {
66
+ const messages: any[] = [];
67
+
68
+ if (context.systemPrompt) {
69
+ messages.push({ role: "system", content: context.systemPrompt });
70
+ }
71
+
72
+ for (const msg of context.messages) {
73
+ if (msg.role === "user") {
74
+ const content =
75
+ typeof msg.content === "string"
76
+ ? msg.content
77
+ : msg.content
78
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
79
+ .map((c) => c.text)
80
+ .join("\n");
81
+ messages.push({ role: "user" as const, content });
82
+ } else if (msg.role === "assistant") {
83
+ const assistantMsg: any = { role: "assistant", content: null };
84
+
85
+ const textBlocks = msg.content.filter((b): b is { type: "text"; text: string } => b.type === "text");
86
+ if (textBlocks.length > 0) {
87
+ assistantMsg.content = textBlocks.map((b) => b.text).join("");
88
+ }
89
+
90
+ const toolCalls = msg.content.filter((b): b is ToolCall => b.type === "toolCall");
91
+ if (toolCalls.length > 0) {
92
+ assistantMsg.tool_calls = toolCalls.map((tc) => ({
93
+ id: tc.id,
94
+ type: "function",
95
+ function: {
96
+ name: tc.name,
97
+ arguments: JSON.stringify(tc.arguments),
98
+ },
99
+ }));
100
+ }
101
+
102
+ // Skip empty assistant messages
103
+ if (assistantMsg.content === null && !assistantMsg.tool_calls) continue;
104
+ messages.push(assistantMsg);
105
+ } else if (msg.role === "toolResult") {
106
+ const textResult = msg.content
107
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
108
+ .map((c) => c.text)
109
+ .join("\n");
110
+ messages.push({
111
+ role: "tool",
112
+ tool_call_id: msg.toolCallId,
113
+ content: textResult || "(no output)",
114
+ });
115
+ }
116
+ }
117
+
118
+ return messages;
119
+ }
120
+
121
+ function convertTools(context: Context): any[] | undefined {
122
+ if (!context.tools || context.tools.length === 0) return undefined;
123
+ return context.tools.map((tool) => ({
124
+ type: "function",
125
+ function: {
126
+ name: tool.name,
127
+ description: tool.description,
128
+ parameters: tool.parameters,
129
+ },
130
+ }));
131
+ }
132
+
133
+ function streamNaiad(
134
+ mdl: Model,
135
+ context: Context,
136
+ options?: SimpleStreamOptions,
137
+ ): AssistantMessageEventStream {
138
+ const stream = createAssistantMessageEventStream();
139
+
140
+ (async () => {
141
+ const output: AssistantMessage = {
142
+ role: "assistant",
143
+ content: [],
144
+ api: mdl.api,
145
+ provider: mdl.provider,
146
+ model: mdl.id,
147
+ usage: {
148
+ input: 0,
149
+ output: 0,
150
+ cacheRead: 0,
151
+ cacheWrite: 0,
152
+ totalTokens: 0,
153
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
154
+ },
155
+ stopReason: "stop",
156
+ timestamp: Date.now(),
157
+ };
158
+
159
+ try {
160
+ const messages = convertMessages(context);
161
+ const tools = convertTools(context);
162
+
163
+ const body: any = {
164
+ model: mdl.id,
165
+ messages,
166
+ stream: true,
167
+ stream_options: { include_usage: true },
168
+ };
169
+ if (tools) {
170
+ body.tools = tools;
171
+ }
172
+
173
+ const res = await fetch(`${inferenceUrl}/v1/chat/completions`, {
174
+ method: "POST",
175
+ headers: {
176
+ "Content-Type": "application/json",
177
+ "Authorization": `Bearer ${apiKey}`,
178
+ "X-Naiad-Thread-Id": threadId,
179
+ "X-Naiad-Session-Id": sessionId,
180
+ },
181
+ body: JSON.stringify(body),
182
+ signal: options?.signal,
183
+ });
184
+
185
+ if (!res.ok) {
186
+ throw new Error(`Inference proxy error: ${res.status} ${await res.text()}`);
187
+ }
188
+
189
+ stream.push({ type: "start", partial: output });
190
+
191
+ const reader = res.body!.getReader();
192
+ const decoder = new TextDecoder();
193
+ let buffer = "";
194
+
195
+ // Track current content block for proper event sequencing
196
+ let currentBlock: any = null;
197
+ const blocks = output.content;
198
+ const blockIndex = () => blocks.length - 1;
199
+
200
+ const finishCurrentBlock = (block: any) => {
201
+ if (!block) return;
202
+ if (block.type === "text") {
203
+ stream.push({
204
+ type: "text_end",
205
+ contentIndex: blockIndex(),
206
+ content: block.text,
207
+ partial: output,
208
+ });
209
+ } else if (block.type === "toolCall") {
210
+ try {
211
+ block.arguments = JSON.parse(block.partialArgs || "{}");
212
+ } catch {
213
+ block.arguments = {};
214
+ }
215
+ delete block.partialArgs;
216
+ stream.push({
217
+ type: "toolcall_end",
218
+ contentIndex: blockIndex(),
219
+ toolCall: block as ToolCall,
220
+ partial: output,
221
+ });
222
+ }
223
+ };
224
+
225
+ while (true) {
226
+ const { done, value } = await reader.read();
227
+ if (done) break;
228
+ buffer += decoder.decode(value, { stream: true });
229
+
230
+ const lines = buffer.split("\n");
231
+ buffer = lines.pop() ?? "";
232
+
233
+ for (const line of lines) {
234
+ if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
235
+ try {
236
+ const chunk = JSON.parse(line.slice(6));
237
+
238
+ if (chunk.usage) {
239
+ output.usage.input = chunk.usage.prompt_tokens ?? 0;
240
+ output.usage.output = chunk.usage.completion_tokens ?? 0;
241
+ output.usage.totalTokens = chunk.usage.total_tokens ?? 0;
242
+ }
243
+
244
+ const choice = chunk.choices?.[0];
245
+ if (!choice) continue;
246
+
247
+ if (choice.finish_reason) {
248
+ if (choice.finish_reason === "tool_calls" || choice.finish_reason === "function_call") {
249
+ output.stopReason = "toolUse";
250
+ } else if (choice.finish_reason === "length") {
251
+ output.stopReason = "length";
252
+ } else {
253
+ output.stopReason = "stop";
254
+ }
255
+ }
256
+
257
+ // Handle text content delta
258
+ const delta = choice.delta?.content;
259
+ if (delta) {
260
+ if (!currentBlock || currentBlock.type !== "text") {
261
+ finishCurrentBlock(currentBlock);
262
+ currentBlock = { type: "text", text: "" };
263
+ output.content.push(currentBlock);
264
+ stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
265
+ }
266
+ currentBlock.text += delta;
267
+ stream.push({ type: "text_delta", contentIndex: blockIndex(), delta, partial: output });
268
+ }
269
+
270
+ // Handle tool call deltas
271
+ if (choice.delta?.tool_calls) {
272
+ for (const toolCall of choice.delta.tool_calls) {
273
+ if (
274
+ !currentBlock ||
275
+ currentBlock.type !== "toolCall" ||
276
+ (toolCall.id && currentBlock.id !== toolCall.id)
277
+ ) {
278
+ finishCurrentBlock(currentBlock);
279
+ currentBlock = {
280
+ type: "toolCall",
281
+ id: toolCall.id || "",
282
+ name: toolCall.function?.name || "",
283
+ arguments: {},
284
+ partialArgs: "",
285
+ };
286
+ output.content.push(currentBlock);
287
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
288
+ }
289
+ if (currentBlock.type === "toolCall") {
290
+ if (toolCall.id) currentBlock.id = toolCall.id;
291
+ if (toolCall.function?.name) currentBlock.name = toolCall.function.name;
292
+ let argDelta = "";
293
+ if (toolCall.function?.arguments) {
294
+ argDelta = toolCall.function.arguments;
295
+ currentBlock.partialArgs += argDelta;
296
+ try {
297
+ currentBlock.arguments = JSON.parse(currentBlock.partialArgs);
298
+ } catch {
299
+ // partial JSON, keep accumulating
300
+ }
301
+ }
302
+ stream.push({
303
+ type: "toolcall_delta",
304
+ contentIndex: blockIndex(),
305
+ delta: argDelta,
306
+ partial: output,
307
+ });
308
+ }
309
+ }
310
+ }
311
+ } catch {
312
+ // skip unparseable lines
313
+ }
314
+ }
315
+ }
316
+
317
+ finishCurrentBlock(currentBlock);
318
+ stream.push({ type: "done", reason: output.stopReason as "stop" | "length" | "toolUse", message: output });
319
+ stream.end();
320
+ } catch (error) {
321
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
322
+ output.errorMessage = error instanceof Error ? error.message : String(error);
323
+ stream.push({ type: "error", reason: output.stopReason, error: output });
324
+ stream.end();
325
+ }
326
+ })();
327
+
328
+ return stream;
329
+ }
330
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "naiad-cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "naiad": "./dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist/",
10
+ "extensions/"
11
+ ],
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsx src/index.ts",
21
+ "test": "tsx --test src/callback/server.test.ts"
22
+ },
23
+ "dependencies": {
24
+ "@mariozechner/pi-coding-agent": "^0.55.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.0.0",
28
+ "tsx": "^4.0.0",
29
+ "typescript": "^5.7.0"
30
+ }
31
+ }