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.
- package/dist/api/client.d.ts +81 -0
- package/dist/api/client.js +130 -0
- package/dist/api/client.js.map +1 -0
- package/dist/callback/server.d.ts +11 -0
- package/dist/callback/server.js +53 -0
- package/dist/callback/server.js.map +1 -0
- package/dist/callback/server.test.d.ts +1 -0
- package/dist/callback/server.test.js +79 -0
- package/dist/callback/server.test.js.map +1 -0
- package/dist/commands/exec.d.ts +10 -0
- package/dist/commands/exec.js +94 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/interactive.d.ts +6 -0
- package/dist/commands/interactive.js +137 -0
- package/dist/commands/interactive.js.map +1 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/config.js +21 -0
- package/dist/config/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/setup.d.ts +11 -0
- package/dist/lifecycle/setup.js +49 -0
- package/dist/lifecycle/setup.js.map +1 -0
- package/dist/lifecycle/teardown.d.ts +6 -0
- package/dist/lifecycle/teardown.js +34 -0
- package/dist/lifecycle/teardown.js.map +1 -0
- package/dist/lifecycle/types.d.ts +21 -0
- package/dist/lifecycle/types.js +2 -0
- package/dist/lifecycle/types.js.map +1 -0
- package/dist/lifecycle/workers.d.ts +2 -0
- package/dist/lifecycle/workers.js +22 -0
- package/dist/lifecycle/workers.js.map +1 -0
- package/dist/pi/launcher.d.ts +26 -0
- package/dist/pi/launcher.js +109 -0
- package/dist/pi/launcher.js.map +1 -0
- package/dist/sync/events.d.ts +14 -0
- package/dist/sync/events.js +44 -0
- package/dist/sync/events.js.map +1 -0
- package/dist/sync/heartbeat.d.ts +12 -0
- package/dist/sync/heartbeat.js +33 -0
- package/dist/sync/heartbeat.js.map +1 -0
- package/dist/sync/session-upload.d.ts +12 -0
- package/dist/sync/session-upload.js +69 -0
- package/dist/sync/session-upload.js.map +1 -0
- package/dist/utils/package-root.d.ts +1 -0
- package/dist/utils/package-root.js +13 -0
- package/dist/utils/package-root.js.map +1 -0
- package/extensions/naiad-extension.ts +330 -0
- 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
|
+
}
|