poe-code 3.0.72-beta.2 → 3.0.73
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/cli/commands/agent.d.ts +3 -0
- package/dist/cli/commands/agent.js +77 -0
- package/dist/cli/commands/agent.js.map +1 -0
- package/dist/cli/commands/ralph.js +14 -13
- package/dist/cli/commands/ralph.js.map +1 -1
- package/dist/cli/program.js +7 -0
- package/dist/cli/program.js.map +1 -1
- package/dist/index.js +16881 -19757
- package/dist/index.js.map +4 -4
- package/dist/providers/poe-agent.d.ts +20 -0
- package/dist/providers/poe-agent.js +3930 -0
- package/dist/providers/poe-agent.js.map +7 -0
- package/dist/sdk/spawn.js +17 -0
- package/dist/sdk/spawn.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,3930 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
// src/templates/python/env.hbs
|
|
35
|
+
var require_env = __commonJS({
|
|
36
|
+
"src/templates/python/env.hbs"(exports, module) {
|
|
37
|
+
module.exports = "POE_API_KEY={{apiKey}}\nPOE_BASE_URL=https://api.poe.com/v1\nMODEL={{model}}\n";
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/templates/python/main.py.hbs
|
|
42
|
+
var require_main_py = __commonJS({
|
|
43
|
+
"src/templates/python/main.py.hbs"(exports, module) {
|
|
44
|
+
module.exports = 'import os\nfrom openai import OpenAI\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nclient = OpenAI(\n api_key=os.getenv("POE_API_KEY"),\n base_url=os.getenv("POE_BASE_URL")\n)\n\nresponse = client.chat.completions.create(\n model=os.getenv("MODEL", "{{model}}"),\n messages=[{"role": "user", "content": "Tell me a joke"}]\n)\n\nprint(response.choices[0].message.content)\n';
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// src/templates/python/requirements.txt.hbs
|
|
49
|
+
var require_requirements_txt = __commonJS({
|
|
50
|
+
"src/templates/python/requirements.txt.hbs"(exports, module) {
|
|
51
|
+
module.exports = "openai>=1.0.0\npython-dotenv>=1.0.0\n";
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// src/templates/codex/config.toml.hbs
|
|
56
|
+
var require_config_toml = __commonJS({
|
|
57
|
+
"src/templates/codex/config.toml.hbs"(exports, module) {
|
|
58
|
+
module.exports = 'model_provider = "poe"\nmodel = "{{{model}}}"\nmodel_reasoning_effort = "{{reasoningEffort}}"\nmodel_verbosity = "medium"\n\n[model_providers.poe]\nname = "poe"\nbase_url = "{{{baseUrl}}}"\nwire_api = "responses"\nexperimental_bearer_token = "{{apiKey}}"\n';
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// packages/auth/src/encrypted-file-auth-store.ts
|
|
63
|
+
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from "node:crypto";
|
|
64
|
+
import { promises as fs } from "node:fs";
|
|
65
|
+
import { homedir as homedir2, hostname, userInfo } from "node:os";
|
|
66
|
+
import path2 from "node:path";
|
|
67
|
+
function defaultMachineIdentity() {
|
|
68
|
+
return {
|
|
69
|
+
hostname: hostname(),
|
|
70
|
+
username: userInfo().username
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function deriveEncryptionKey(getMachineIdentity) {
|
|
74
|
+
const machineIdentity = await getMachineIdentity();
|
|
75
|
+
const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
|
|
76
|
+
return await new Promise((resolve, reject) => {
|
|
77
|
+
scrypt(secret, ENCRYPTION_SALT, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
|
|
78
|
+
if (error) {
|
|
79
|
+
reject(error);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
resolve(Buffer.from(derivedKey));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function parseEncryptedDocument(raw) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
if (!isRecord(parsed)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (parsed.version !== ENCRYPTION_VERSION) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (typeof parsed.iv !== "string" || typeof parsed.authTag !== "string" || typeof parsed.ciphertext !== "string") {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
version: parsed.version,
|
|
100
|
+
iv: parsed.iv,
|
|
101
|
+
authTag: parsed.authTag,
|
|
102
|
+
ciphertext: parsed.ciphertext
|
|
103
|
+
};
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isRecord(value) {
|
|
109
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
110
|
+
}
|
|
111
|
+
function isNotFoundError(error) {
|
|
112
|
+
return Boolean(
|
|
113
|
+
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
var ENCRYPTION_ALGORITHM, ENCRYPTION_VERSION, ENCRYPTION_KEY_BYTES, ENCRYPTION_IV_BYTES, ENCRYPTION_AUTH_TAG_BYTES, ENCRYPTION_SALT, ENCRYPTION_FILE_MODE, EncryptedFileAuthStore;
|
|
117
|
+
var init_encrypted_file_auth_store = __esm({
|
|
118
|
+
"packages/auth/src/encrypted-file-auth-store.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
121
|
+
ENCRYPTION_VERSION = 1;
|
|
122
|
+
ENCRYPTION_KEY_BYTES = 32;
|
|
123
|
+
ENCRYPTION_IV_BYTES = 12;
|
|
124
|
+
ENCRYPTION_AUTH_TAG_BYTES = 16;
|
|
125
|
+
ENCRYPTION_SALT = "poe-code:encrypted-file-auth-store:v1";
|
|
126
|
+
ENCRYPTION_FILE_MODE = 384;
|
|
127
|
+
EncryptedFileAuthStore = class {
|
|
128
|
+
fs;
|
|
129
|
+
filePath;
|
|
130
|
+
getMachineIdentity;
|
|
131
|
+
getRandomBytes;
|
|
132
|
+
keyPromise = null;
|
|
133
|
+
constructor(input = {}) {
|
|
134
|
+
this.fs = input.fs ?? fs;
|
|
135
|
+
this.filePath = input.filePath ?? path2.join(
|
|
136
|
+
(input.getHomeDirectory ?? homedir2)(),
|
|
137
|
+
".poe-code",
|
|
138
|
+
"credentials.enc"
|
|
139
|
+
);
|
|
140
|
+
this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
|
|
141
|
+
this.getRandomBytes = input.getRandomBytes ?? randomBytes;
|
|
142
|
+
}
|
|
143
|
+
async getApiKey() {
|
|
144
|
+
let rawDocument;
|
|
145
|
+
try {
|
|
146
|
+
rawDocument = await this.fs.readFile(this.filePath, "utf8");
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (isNotFoundError(error)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
const document = parseEncryptedDocument(rawDocument);
|
|
154
|
+
if (!document) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const key = await this.getEncryptionKey();
|
|
158
|
+
try {
|
|
159
|
+
const iv = Buffer.from(document.iv, "base64");
|
|
160
|
+
const authTag = Buffer.from(document.authTag, "base64");
|
|
161
|
+
const ciphertext = Buffer.from(document.ciphertext, "base64");
|
|
162
|
+
if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
166
|
+
decipher.setAuthTag(authTag);
|
|
167
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
168
|
+
return plaintext.toString("utf8");
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async setApiKey(apiKey) {
|
|
174
|
+
const key = await this.getEncryptionKey();
|
|
175
|
+
const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
|
|
176
|
+
const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
177
|
+
const ciphertext = Buffer.concat([
|
|
178
|
+
cipher.update(apiKey, "utf8"),
|
|
179
|
+
cipher.final()
|
|
180
|
+
]);
|
|
181
|
+
const authTag = cipher.getAuthTag();
|
|
182
|
+
const document = {
|
|
183
|
+
version: ENCRYPTION_VERSION,
|
|
184
|
+
iv: iv.toString("base64"),
|
|
185
|
+
authTag: authTag.toString("base64"),
|
|
186
|
+
ciphertext: ciphertext.toString("base64")
|
|
187
|
+
};
|
|
188
|
+
await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
189
|
+
await this.fs.writeFile(this.filePath, JSON.stringify(document), {
|
|
190
|
+
encoding: "utf8"
|
|
191
|
+
});
|
|
192
|
+
await this.fs.chmod(this.filePath, ENCRYPTION_FILE_MODE);
|
|
193
|
+
}
|
|
194
|
+
async deleteApiKey() {
|
|
195
|
+
try {
|
|
196
|
+
await this.fs.unlink(this.filePath);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (!isNotFoundError(error)) {
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
getEncryptionKey() {
|
|
204
|
+
if (!this.keyPromise) {
|
|
205
|
+
this.keyPromise = deriveEncryptionKey(this.getMachineIdentity);
|
|
206
|
+
}
|
|
207
|
+
return this.keyPromise;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// packages/auth/src/keychain-auth-store.ts
|
|
214
|
+
import { spawn } from "node:child_process";
|
|
215
|
+
function runSecurityCommand(command, args) {
|
|
216
|
+
return new Promise((resolve) => {
|
|
217
|
+
const child = spawn(command, args, {
|
|
218
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
219
|
+
});
|
|
220
|
+
let stdout = "";
|
|
221
|
+
let stderr = "";
|
|
222
|
+
child.stdout?.setEncoding("utf8");
|
|
223
|
+
child.stdout?.on("data", (chunk) => {
|
|
224
|
+
stdout += chunk.toString();
|
|
225
|
+
});
|
|
226
|
+
child.stderr?.setEncoding("utf8");
|
|
227
|
+
child.stderr?.on("data", (chunk) => {
|
|
228
|
+
stderr += chunk.toString();
|
|
229
|
+
});
|
|
230
|
+
child.on("error", (error) => {
|
|
231
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
232
|
+
resolve({
|
|
233
|
+
stdout,
|
|
234
|
+
stderr: stderr ? `${stderr}${message}` : message,
|
|
235
|
+
exitCode: 127
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
child.on("close", (code) => {
|
|
239
|
+
resolve({
|
|
240
|
+
stdout,
|
|
241
|
+
stderr,
|
|
242
|
+
exitCode: code ?? 0
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function stripTrailingLineBreak(value) {
|
|
248
|
+
if (value.endsWith("\r\n")) {
|
|
249
|
+
return value.slice(0, -2);
|
|
250
|
+
}
|
|
251
|
+
if (value.endsWith("\n") || value.endsWith("\r")) {
|
|
252
|
+
return value.slice(0, -1);
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
function isKeychainEntryNotFound(result) {
|
|
257
|
+
if (result.exitCode === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
const output = `${result.stderr}
|
|
261
|
+
${result.stdout}`.toLowerCase();
|
|
262
|
+
return output.includes("could not be found") || output.includes("item not found") || output.includes("errsecitemnotfound");
|
|
263
|
+
}
|
|
264
|
+
function createSecurityCliFailure(operation, result) {
|
|
265
|
+
const details = result.stderr.trim() || result.stdout.trim();
|
|
266
|
+
if (details) {
|
|
267
|
+
return new Error(
|
|
268
|
+
`Failed to ${operation}: security exited with code ${result.exitCode}: ${details}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return new Error(`Failed to ${operation}: security exited with code ${result.exitCode}`);
|
|
272
|
+
}
|
|
273
|
+
var SECURITY_CLI, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE, KeychainAuthStore;
|
|
274
|
+
var init_keychain_auth_store = __esm({
|
|
275
|
+
"packages/auth/src/keychain-auth-store.ts"() {
|
|
276
|
+
"use strict";
|
|
277
|
+
SECURITY_CLI = "security";
|
|
278
|
+
KEYCHAIN_SERVICE = "poe-code";
|
|
279
|
+
KEYCHAIN_ACCOUNT = "api-key";
|
|
280
|
+
KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
|
|
281
|
+
KeychainAuthStore = class {
|
|
282
|
+
runCommand;
|
|
283
|
+
service;
|
|
284
|
+
account;
|
|
285
|
+
constructor(input = {}) {
|
|
286
|
+
this.runCommand = input.runCommand ?? runSecurityCommand;
|
|
287
|
+
this.service = input.service ?? KEYCHAIN_SERVICE;
|
|
288
|
+
this.account = input.account ?? KEYCHAIN_ACCOUNT;
|
|
289
|
+
}
|
|
290
|
+
async getApiKey() {
|
|
291
|
+
const result = await this.executeSecurityCommand(
|
|
292
|
+
["find-generic-password", "-s", this.service, "-a", this.account, "-w"],
|
|
293
|
+
"read API key from macOS Keychain"
|
|
294
|
+
);
|
|
295
|
+
if (result.exitCode === 0) {
|
|
296
|
+
return stripTrailingLineBreak(result.stdout);
|
|
297
|
+
}
|
|
298
|
+
if (isKeychainEntryNotFound(result)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
throw createSecurityCliFailure("read API key from macOS Keychain", result);
|
|
302
|
+
}
|
|
303
|
+
async setApiKey(apiKey) {
|
|
304
|
+
const result = await this.executeSecurityCommand(
|
|
305
|
+
[
|
|
306
|
+
"add-generic-password",
|
|
307
|
+
"-s",
|
|
308
|
+
this.service,
|
|
309
|
+
"-a",
|
|
310
|
+
this.account,
|
|
311
|
+
"-w",
|
|
312
|
+
apiKey,
|
|
313
|
+
"-U"
|
|
314
|
+
],
|
|
315
|
+
"store API key in macOS Keychain"
|
|
316
|
+
);
|
|
317
|
+
if (result.exitCode !== 0) {
|
|
318
|
+
throw createSecurityCliFailure("store API key in macOS Keychain", result);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async deleteApiKey() {
|
|
322
|
+
const result = await this.executeSecurityCommand(
|
|
323
|
+
["delete-generic-password", "-s", this.service, "-a", this.account],
|
|
324
|
+
"delete API key from macOS Keychain"
|
|
325
|
+
);
|
|
326
|
+
if (result.exitCode === 0 || isKeychainEntryNotFound(result)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
throw createSecurityCliFailure("delete API key from macOS Keychain", result);
|
|
330
|
+
}
|
|
331
|
+
async executeSecurityCommand(args, operation) {
|
|
332
|
+
try {
|
|
333
|
+
return await this.runCommand(SECURITY_CLI, args);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
336
|
+
throw new Error(`Failed to ${operation}: ${message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// packages/auth/src/create-auth-store.ts
|
|
344
|
+
import { promises as nodeFs } from "node:fs";
|
|
345
|
+
import { homedir as homedir3 } from "node:os";
|
|
346
|
+
import path3 from "node:path";
|
|
347
|
+
function createAuthStore(input = {}) {
|
|
348
|
+
const backend = resolveBackend(input);
|
|
349
|
+
const platform = input.platform ?? process.platform;
|
|
350
|
+
if (backend === "keychain" && platform !== MACOS_PLATFORM) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`POE_AUTH_BACKEND=keychain is only supported on macOS. Current platform: ${platform}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const store = authStoreFactories[backend](input);
|
|
356
|
+
return {
|
|
357
|
+
backend,
|
|
358
|
+
store: enableLegacyCredentialsMigration(store, input)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function resolveBackend(input) {
|
|
362
|
+
const configuredBackend = input.backend ?? input.env?.[AUTH_BACKEND_ENV_VAR] ?? process.env[AUTH_BACKEND_ENV_VAR];
|
|
363
|
+
if (configuredBackend === "keychain") {
|
|
364
|
+
return "keychain";
|
|
365
|
+
}
|
|
366
|
+
return "file";
|
|
367
|
+
}
|
|
368
|
+
function enableLegacyCredentialsMigration(store, input) {
|
|
369
|
+
const migrationContext = createLegacyMigrationContext(input);
|
|
370
|
+
const readApiKeyFromStore = store.getApiKey.bind(store);
|
|
371
|
+
let hasCheckedLegacyCredentials = false;
|
|
372
|
+
let legacyMigrationPromise = null;
|
|
373
|
+
store.getApiKey = async () => {
|
|
374
|
+
const storedApiKey = await readApiKeyFromStore();
|
|
375
|
+
if (isNonEmptyString(storedApiKey)) {
|
|
376
|
+
return storedApiKey;
|
|
377
|
+
}
|
|
378
|
+
if (hasCheckedLegacyCredentials) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if (!legacyMigrationPromise) {
|
|
382
|
+
legacyMigrationPromise = migrateLegacyApiKey(store, migrationContext).finally(() => {
|
|
383
|
+
hasCheckedLegacyCredentials = true;
|
|
384
|
+
legacyMigrationPromise = null;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return legacyMigrationPromise;
|
|
388
|
+
};
|
|
389
|
+
return store;
|
|
390
|
+
}
|
|
391
|
+
async function migrateLegacyApiKey(store, migrationContext) {
|
|
392
|
+
const legacyCredentials = await loadLegacyCredentials(
|
|
393
|
+
migrationContext.fs,
|
|
394
|
+
migrationContext.filePath
|
|
395
|
+
);
|
|
396
|
+
if (!legacyCredentials || !isNonEmptyString(legacyCredentials.apiKey)) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const plaintextApiKey = legacyCredentials.apiKey;
|
|
400
|
+
try {
|
|
401
|
+
await store.setApiKey(plaintextApiKey);
|
|
402
|
+
delete legacyCredentials.apiKey;
|
|
403
|
+
await saveLegacyCredentials(
|
|
404
|
+
migrationContext.fs,
|
|
405
|
+
migrationContext.filePath,
|
|
406
|
+
legacyCredentials
|
|
407
|
+
);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
migrationContext.logWarning(
|
|
410
|
+
`Failed to migrate plaintext API key from ${migrationContext.filePath}.`,
|
|
411
|
+
error
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return plaintextApiKey;
|
|
415
|
+
}
|
|
416
|
+
function createLegacyMigrationContext(input) {
|
|
417
|
+
const legacyCredentialsInput = input.legacyCredentials;
|
|
418
|
+
const getHomeDirectory = legacyCredentialsInput?.getHomeDirectory ?? homedir3;
|
|
419
|
+
return {
|
|
420
|
+
fs: legacyCredentialsInput?.fs ?? input.fileStore?.fs ?? nodeFs,
|
|
421
|
+
filePath: legacyCredentialsInput?.filePath ?? path3.join(
|
|
422
|
+
getHomeDirectory(),
|
|
423
|
+
LEGACY_CREDENTIALS_RELATIVE_PATH
|
|
424
|
+
),
|
|
425
|
+
logWarning: legacyCredentialsInput?.logWarning ?? defaultMigrationWarning
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function loadLegacyCredentials(fs2, filePath) {
|
|
429
|
+
let raw;
|
|
430
|
+
try {
|
|
431
|
+
raw = await fs2.readFile(filePath, "utf8");
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (isNotFoundError2(error)) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const parsed = JSON.parse(raw);
|
|
440
|
+
if (!isRecord2(parsed)) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
return parsed;
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async function saveLegacyCredentials(fs2, filePath, document) {
|
|
449
|
+
await fs2.mkdir(path3.dirname(filePath), { recursive: true });
|
|
450
|
+
await fs2.writeFile(filePath, `${JSON.stringify(document, null, 2)}
|
|
451
|
+
`, {
|
|
452
|
+
encoding: "utf8"
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function defaultMigrationWarning(message, error) {
|
|
456
|
+
const details = toErrorDetails(error);
|
|
457
|
+
if (details.length > 0) {
|
|
458
|
+
console.warn(`${message} ${details}`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.warn(message);
|
|
462
|
+
}
|
|
463
|
+
function toErrorDetails(error) {
|
|
464
|
+
if (error instanceof Error) {
|
|
465
|
+
return error.message;
|
|
466
|
+
}
|
|
467
|
+
return typeof error === "string" ? error : "";
|
|
468
|
+
}
|
|
469
|
+
function isNonEmptyString(value) {
|
|
470
|
+
return typeof value === "string" && value.length > 0;
|
|
471
|
+
}
|
|
472
|
+
function isRecord2(value) {
|
|
473
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
474
|
+
}
|
|
475
|
+
function isNotFoundError2(error) {
|
|
476
|
+
return Boolean(
|
|
477
|
+
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
var AUTH_BACKEND_ENV_VAR, MACOS_PLATFORM, LEGACY_CREDENTIALS_RELATIVE_PATH, authStoreFactories;
|
|
481
|
+
var init_create_auth_store = __esm({
|
|
482
|
+
"packages/auth/src/create-auth-store.ts"() {
|
|
483
|
+
"use strict";
|
|
484
|
+
init_encrypted_file_auth_store();
|
|
485
|
+
init_keychain_auth_store();
|
|
486
|
+
AUTH_BACKEND_ENV_VAR = "POE_AUTH_BACKEND";
|
|
487
|
+
MACOS_PLATFORM = "darwin";
|
|
488
|
+
LEGACY_CREDENTIALS_RELATIVE_PATH = ".poe-code/credentials.json";
|
|
489
|
+
authStoreFactories = {
|
|
490
|
+
file: (input) => new EncryptedFileAuthStore(input.fileStore),
|
|
491
|
+
keychain: (input) => new KeychainAuthStore(input.keychainStore)
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// packages/auth/src/index.ts
|
|
497
|
+
var init_src = __esm({
|
|
498
|
+
"packages/auth/src/index.ts"() {
|
|
499
|
+
"use strict";
|
|
500
|
+
init_create_auth_store();
|
|
501
|
+
init_encrypted_file_auth_store();
|
|
502
|
+
init_keychain_auth_store();
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// packages/poe-agent/src/chat.ts
|
|
507
|
+
function toChatCompletionsUrl(baseUrl) {
|
|
508
|
+
const trimmedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
509
|
+
if (trimmedBaseUrl.endsWith("/v1")) {
|
|
510
|
+
return `${trimmedBaseUrl}/chat/completions`;
|
|
511
|
+
}
|
|
512
|
+
return `${trimmedBaseUrl}/v1/chat/completions`;
|
|
513
|
+
}
|
|
514
|
+
function parseToolArguments(rawArguments) {
|
|
515
|
+
const parsed = JSON.parse(rawArguments);
|
|
516
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
517
|
+
throw new Error("Tool call arguments must be a JSON object");
|
|
518
|
+
}
|
|
519
|
+
return parsed;
|
|
520
|
+
}
|
|
521
|
+
async function readResponseText(response) {
|
|
522
|
+
try {
|
|
523
|
+
const text = await response.text();
|
|
524
|
+
return text.trim() || void 0;
|
|
525
|
+
} catch {
|
|
526
|
+
return void 0;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function getErrorMessage(error) {
|
|
530
|
+
if (error instanceof Error) {
|
|
531
|
+
return error.message;
|
|
532
|
+
}
|
|
533
|
+
return String(error);
|
|
534
|
+
}
|
|
535
|
+
var PoeChatService;
|
|
536
|
+
var init_chat = __esm({
|
|
537
|
+
"packages/poe-agent/src/chat.ts"() {
|
|
538
|
+
"use strict";
|
|
539
|
+
PoeChatService = class {
|
|
540
|
+
apiKey;
|
|
541
|
+
model;
|
|
542
|
+
fetchFn;
|
|
543
|
+
chatCompletionsUrl;
|
|
544
|
+
toolExecutor;
|
|
545
|
+
maxToolCallIterations;
|
|
546
|
+
conversationHistory = [];
|
|
547
|
+
toolCallCallback;
|
|
548
|
+
constructor(options) {
|
|
549
|
+
this.apiKey = options.apiKey;
|
|
550
|
+
this.model = options.model;
|
|
551
|
+
this.fetchFn = options.fetch ?? globalThis.fetch;
|
|
552
|
+
this.chatCompletionsUrl = toChatCompletionsUrl(options.baseUrl ?? "https://api.poe.com");
|
|
553
|
+
this.toolExecutor = options.toolExecutor;
|
|
554
|
+
this.toolCallCallback = options.onToolCall;
|
|
555
|
+
this.maxToolCallIterations = options.maxToolCallIterations ?? 100;
|
|
556
|
+
if (options.systemPrompt) {
|
|
557
|
+
this.conversationHistory.push({
|
|
558
|
+
role: "system",
|
|
559
|
+
content: options.systemPrompt
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
clearConversationHistory() {
|
|
564
|
+
this.conversationHistory = [];
|
|
565
|
+
}
|
|
566
|
+
async sendMessage(userMessage, options) {
|
|
567
|
+
this.conversationHistory.push({
|
|
568
|
+
role: "user",
|
|
569
|
+
content: userMessage
|
|
570
|
+
});
|
|
571
|
+
let iterationCount = 0;
|
|
572
|
+
while (iterationCount < this.maxToolCallIterations) {
|
|
573
|
+
const response = await this.requestCompletion(options?.tools, options?.signal);
|
|
574
|
+
const assistantMessage = response.choices[0]?.message;
|
|
575
|
+
if (!assistantMessage) {
|
|
576
|
+
throw new Error("Poe API response did not include an assistant message");
|
|
577
|
+
}
|
|
578
|
+
this.conversationHistory.push(assistantMessage);
|
|
579
|
+
const hasToolCalls = assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0 && this.toolExecutor;
|
|
580
|
+
if (!hasToolCalls) {
|
|
581
|
+
return assistantMessage;
|
|
582
|
+
}
|
|
583
|
+
await this.executeToolCalls(assistantMessage.tool_calls);
|
|
584
|
+
iterationCount += 1;
|
|
585
|
+
}
|
|
586
|
+
throw new Error("Maximum tool call iterations reached");
|
|
587
|
+
}
|
|
588
|
+
async requestCompletion(tools, signal) {
|
|
589
|
+
const requestBody = {
|
|
590
|
+
model: this.model,
|
|
591
|
+
messages: this.conversationHistory
|
|
592
|
+
};
|
|
593
|
+
if (tools && tools.length > 0) {
|
|
594
|
+
requestBody.tools = tools;
|
|
595
|
+
}
|
|
596
|
+
const response = await this.fetchFn(this.chatCompletionsUrl, {
|
|
597
|
+
method: "POST",
|
|
598
|
+
headers: {
|
|
599
|
+
"Content-Type": "application/json",
|
|
600
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
601
|
+
},
|
|
602
|
+
body: JSON.stringify(requestBody),
|
|
603
|
+
signal
|
|
604
|
+
});
|
|
605
|
+
if (!response.ok) {
|
|
606
|
+
const errorBody = await readResponseText(response);
|
|
607
|
+
const details = errorBody || response.statusText || "Unknown error";
|
|
608
|
+
throw new Error(`Poe API request failed (${response.status}): ${details}`);
|
|
609
|
+
}
|
|
610
|
+
const payload = await response.json();
|
|
611
|
+
if (!Array.isArray(payload.choices)) {
|
|
612
|
+
throw new Error("Poe API response had invalid choices payload");
|
|
613
|
+
}
|
|
614
|
+
return payload;
|
|
615
|
+
}
|
|
616
|
+
async executeToolCalls(toolCalls) {
|
|
617
|
+
if (!this.toolExecutor) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
for (const toolCall of toolCalls) {
|
|
621
|
+
let args = {};
|
|
622
|
+
try {
|
|
623
|
+
args = parseToolArguments(toolCall.function.arguments);
|
|
624
|
+
this.emitToolCallEvent({
|
|
625
|
+
phase: "started",
|
|
626
|
+
toolCallId: toolCall.id,
|
|
627
|
+
toolName: toolCall.function.name,
|
|
628
|
+
args
|
|
629
|
+
});
|
|
630
|
+
const result = await this.toolExecutor.executeTool(toolCall.function.name, args);
|
|
631
|
+
this.emitToolCallEvent({
|
|
632
|
+
phase: "completed",
|
|
633
|
+
toolCallId: toolCall.id,
|
|
634
|
+
toolName: toolCall.function.name,
|
|
635
|
+
args,
|
|
636
|
+
result
|
|
637
|
+
});
|
|
638
|
+
this.conversationHistory.push({
|
|
639
|
+
role: "tool",
|
|
640
|
+
tool_call_id: toolCall.id,
|
|
641
|
+
name: toolCall.function.name,
|
|
642
|
+
content: result
|
|
643
|
+
});
|
|
644
|
+
} catch (error) {
|
|
645
|
+
const message = getErrorMessage(error);
|
|
646
|
+
this.emitToolCallEvent({
|
|
647
|
+
phase: "failed",
|
|
648
|
+
toolCallId: toolCall.id,
|
|
649
|
+
toolName: toolCall.function.name,
|
|
650
|
+
args,
|
|
651
|
+
error: message
|
|
652
|
+
});
|
|
653
|
+
this.conversationHistory.push({
|
|
654
|
+
role: "tool",
|
|
655
|
+
tool_call_id: toolCall.id,
|
|
656
|
+
name: toolCall.function.name,
|
|
657
|
+
content: `Error: ${message}`
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
emitToolCallEvent(event) {
|
|
663
|
+
if (this.toolCallCallback) {
|
|
664
|
+
this.toolCallCallback(event);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// packages/poe-agent/src/system-prompt.ts
|
|
672
|
+
import { readFileSync } from "node:fs";
|
|
673
|
+
import { readFile } from "node:fs/promises";
|
|
674
|
+
async function loadSystemPrompt() {
|
|
675
|
+
return readFile(systemPromptUrl, "utf8");
|
|
676
|
+
}
|
|
677
|
+
var systemPromptUrl;
|
|
678
|
+
var init_system_prompt = __esm({
|
|
679
|
+
"packages/poe-agent/src/system-prompt.ts"() {
|
|
680
|
+
"use strict";
|
|
681
|
+
systemPromptUrl = new URL("./SYSTEM_PROMPT.md", import.meta.url);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// packages/poe-agent/src/tool-executor.ts
|
|
686
|
+
import { exec as execCallback } from "node:child_process";
|
|
687
|
+
import fsPromises2 from "node:fs/promises";
|
|
688
|
+
import path4 from "node:path";
|
|
689
|
+
import { promisify } from "node:util";
|
|
690
|
+
function getRequiredString(args, key, allowEmptyString = false) {
|
|
691
|
+
const value = args[key];
|
|
692
|
+
if (typeof value !== "string") {
|
|
693
|
+
throw new Error(`Tool argument "${key}" must be a string`);
|
|
694
|
+
}
|
|
695
|
+
if (!allowEmptyString && value.trim().length === 0) {
|
|
696
|
+
throw new Error(`Tool argument "${key}" must not be empty`);
|
|
697
|
+
}
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
function getOptionalString(args, key) {
|
|
701
|
+
const value = args[key];
|
|
702
|
+
if (value === void 0) {
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
if (typeof value !== "string") {
|
|
706
|
+
throw new Error(`Tool argument "${key}" must be a string`);
|
|
707
|
+
}
|
|
708
|
+
return value;
|
|
709
|
+
}
|
|
710
|
+
function countOccurrences(text, search) {
|
|
711
|
+
let count = 0;
|
|
712
|
+
let index = 0;
|
|
713
|
+
while ((index = text.indexOf(search, index)) !== -1) {
|
|
714
|
+
count++;
|
|
715
|
+
index += search.length;
|
|
716
|
+
}
|
|
717
|
+
return count;
|
|
718
|
+
}
|
|
719
|
+
async function defaultRunCommand(command, cwd) {
|
|
720
|
+
try {
|
|
721
|
+
const result = await exec(command, {
|
|
722
|
+
cwd,
|
|
723
|
+
timeout: 3e4,
|
|
724
|
+
maxBuffer: 1024 * 1024
|
|
725
|
+
});
|
|
726
|
+
const combinedOutput = [result.stdout, result.stderr].map((output) => output.trim()).filter((output) => output.length > 0).join("\n");
|
|
727
|
+
return combinedOutput || "Command completed with no output";
|
|
728
|
+
} catch (error) {
|
|
729
|
+
if (error instanceof Error) {
|
|
730
|
+
const stderr = Reflect.get(error, "stderr");
|
|
731
|
+
if (typeof stderr === "string" && stderr.trim().length > 0) {
|
|
732
|
+
throw new Error(`Command failed: ${stderr.trim()}`);
|
|
733
|
+
}
|
|
734
|
+
const stdout = Reflect.get(error, "stdout");
|
|
735
|
+
if (typeof stdout === "string" && stdout.trim().length > 0) {
|
|
736
|
+
throw new Error(`Command failed: ${stdout.trim()}`);
|
|
737
|
+
}
|
|
738
|
+
throw new Error(`Command failed: ${error.message}`);
|
|
739
|
+
}
|
|
740
|
+
throw new Error(`Command failed: ${String(error)}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async function defaultSearchWeb(query, fetchFn) {
|
|
744
|
+
const url = new URL("https://api.duckduckgo.com/");
|
|
745
|
+
url.searchParams.set("q", query);
|
|
746
|
+
url.searchParams.set("format", "json");
|
|
747
|
+
url.searchParams.set("no_redirect", "1");
|
|
748
|
+
url.searchParams.set("no_html", "1");
|
|
749
|
+
url.searchParams.set("skip_disambig", "1");
|
|
750
|
+
const response = await fetchFn(url.toString());
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
throw new Error(`Web search failed (${response.status})`);
|
|
753
|
+
}
|
|
754
|
+
const body = await response.json();
|
|
755
|
+
const lines = [];
|
|
756
|
+
if (typeof body.AbstractText === "string" && body.AbstractText.trim().length > 0) {
|
|
757
|
+
lines.push(body.AbstractText.trim());
|
|
758
|
+
}
|
|
759
|
+
if (body.RelatedTopics) {
|
|
760
|
+
const queue = [...body.RelatedTopics];
|
|
761
|
+
while (queue.length > 0 && lines.length < 5) {
|
|
762
|
+
const current = queue.shift();
|
|
763
|
+
if (!current) continue;
|
|
764
|
+
if (typeof current.Text === "string" && current.Text.trim().length > 0) {
|
|
765
|
+
lines.push(current.Text.trim());
|
|
766
|
+
}
|
|
767
|
+
if (current.Topics) {
|
|
768
|
+
queue.push(...current.Topics);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (lines.length === 0) {
|
|
773
|
+
return "No search results found.";
|
|
774
|
+
}
|
|
775
|
+
return lines.join("\n");
|
|
776
|
+
}
|
|
777
|
+
var exec, DefaultToolExecutor;
|
|
778
|
+
var init_tool_executor = __esm({
|
|
779
|
+
"packages/poe-agent/src/tool-executor.ts"() {
|
|
780
|
+
"use strict";
|
|
781
|
+
exec = promisify(execCallback);
|
|
782
|
+
DefaultToolExecutor = class {
|
|
783
|
+
cwd;
|
|
784
|
+
allowedPaths;
|
|
785
|
+
fs;
|
|
786
|
+
runCommandFn;
|
|
787
|
+
searchWebFn;
|
|
788
|
+
constructor(options = {}) {
|
|
789
|
+
this.cwd = path4.resolve(options.cwd ?? process.cwd());
|
|
790
|
+
this.allowedPaths = (options.allowedPaths ?? [this.cwd]).map(
|
|
791
|
+
(allowedPath) => path4.resolve(this.cwd, allowedPath)
|
|
792
|
+
);
|
|
793
|
+
this.fs = options.fs ?? fsPromises2;
|
|
794
|
+
this.runCommandFn = options.runCommand ?? defaultRunCommand;
|
|
795
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
796
|
+
this.searchWebFn = options.searchWeb ?? ((query) => defaultSearchWeb(query, fetchFn));
|
|
797
|
+
}
|
|
798
|
+
async executeTool(name, args) {
|
|
799
|
+
switch (name) {
|
|
800
|
+
case "read_file":
|
|
801
|
+
return this.executeReadFile(args);
|
|
802
|
+
case "edit_file":
|
|
803
|
+
return this.executeEditFile(args);
|
|
804
|
+
case "list_files":
|
|
805
|
+
return this.executeListFiles(args);
|
|
806
|
+
case "run_command":
|
|
807
|
+
return this.executeRunCommand(args);
|
|
808
|
+
case "search_web":
|
|
809
|
+
return this.executeSearchWeb(args);
|
|
810
|
+
default:
|
|
811
|
+
throw new Error(`Unsupported tool: ${name}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
getAvailableTools() {
|
|
815
|
+
return [
|
|
816
|
+
{
|
|
817
|
+
type: "function",
|
|
818
|
+
function: {
|
|
819
|
+
name: "read_file",
|
|
820
|
+
description: "Read UTF-8 content from a file.",
|
|
821
|
+
parameters: {
|
|
822
|
+
type: "object",
|
|
823
|
+
properties: {
|
|
824
|
+
path: {
|
|
825
|
+
type: "string",
|
|
826
|
+
description: "Path to the file to read."
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
required: ["path"]
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
type: "function",
|
|
835
|
+
function: {
|
|
836
|
+
name: "edit_file",
|
|
837
|
+
description: "Edit or create files. Use 'str_replace' to replace exact text in an existing file (old_str must appear exactly once). Use 'create' to create a new file (fails if file already exists).",
|
|
838
|
+
parameters: {
|
|
839
|
+
type: "object",
|
|
840
|
+
properties: {
|
|
841
|
+
command: {
|
|
842
|
+
type: "string",
|
|
843
|
+
enum: ["str_replace", "create"],
|
|
844
|
+
description: "Operation to perform."
|
|
845
|
+
},
|
|
846
|
+
path: {
|
|
847
|
+
type: "string",
|
|
848
|
+
description: "File path."
|
|
849
|
+
},
|
|
850
|
+
old_str: {
|
|
851
|
+
type: "string",
|
|
852
|
+
description: "Exact string to find and replace (str_replace only). Must match exactly once."
|
|
853
|
+
},
|
|
854
|
+
new_str: {
|
|
855
|
+
type: "string",
|
|
856
|
+
description: "Replacement string (str_replace only)."
|
|
857
|
+
},
|
|
858
|
+
file_text: {
|
|
859
|
+
type: "string",
|
|
860
|
+
description: "Full file content (create only)."
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
required: ["command", "path"]
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
type: "function",
|
|
869
|
+
function: {
|
|
870
|
+
name: "list_files",
|
|
871
|
+
description: "List files in a directory.",
|
|
872
|
+
parameters: {
|
|
873
|
+
type: "object",
|
|
874
|
+
properties: {
|
|
875
|
+
path: {
|
|
876
|
+
type: "string",
|
|
877
|
+
description: "Directory path to list. Defaults to current working directory."
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
type: "function",
|
|
885
|
+
function: {
|
|
886
|
+
name: "run_command",
|
|
887
|
+
description: "Run a shell command.",
|
|
888
|
+
parameters: {
|
|
889
|
+
type: "object",
|
|
890
|
+
properties: {
|
|
891
|
+
command: {
|
|
892
|
+
type: "string",
|
|
893
|
+
description: "Command to execute."
|
|
894
|
+
},
|
|
895
|
+
cwd: {
|
|
896
|
+
type: "string",
|
|
897
|
+
description: "Working directory for command execution."
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
required: ["command"]
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
type: "function",
|
|
906
|
+
function: {
|
|
907
|
+
name: "search_web",
|
|
908
|
+
description: "Search the web for a query.",
|
|
909
|
+
parameters: {
|
|
910
|
+
type: "object",
|
|
911
|
+
properties: {
|
|
912
|
+
query: {
|
|
913
|
+
type: "string",
|
|
914
|
+
description: "Search query."
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
required: ["query"]
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
];
|
|
922
|
+
}
|
|
923
|
+
async executeReadFile(args) {
|
|
924
|
+
const filePath = this.resolveAllowedPath(getRequiredString(args, "path"));
|
|
925
|
+
return this.fs.readFile(filePath, "utf8");
|
|
926
|
+
}
|
|
927
|
+
async executeEditFile(args) {
|
|
928
|
+
const command = getRequiredString(args, "command");
|
|
929
|
+
const filePath = this.resolveAllowedPath(getRequiredString(args, "path"));
|
|
930
|
+
const displayedPath = path4.relative(this.cwd, filePath) || path4.basename(filePath);
|
|
931
|
+
if (command === "str_replace") {
|
|
932
|
+
const oldStr = getRequiredString(args, "old_str", true);
|
|
933
|
+
const newStr = getRequiredString(args, "new_str", true);
|
|
934
|
+
const content = await this.fs.readFile(filePath, "utf8");
|
|
935
|
+
const count = countOccurrences(content, oldStr);
|
|
936
|
+
if (count === 0) {
|
|
937
|
+
throw new Error("old_str not found in file");
|
|
938
|
+
}
|
|
939
|
+
if (count > 1) {
|
|
940
|
+
throw new Error(`old_str appears ${count} times \u2014 must be unique`);
|
|
941
|
+
}
|
|
942
|
+
await this.fs.writeFile(filePath, content.replace(oldStr, newStr), "utf8");
|
|
943
|
+
return `Edited file: ${displayedPath}`;
|
|
944
|
+
}
|
|
945
|
+
if (command === "create") {
|
|
946
|
+
const fileText = getRequiredString(args, "file_text", true);
|
|
947
|
+
if (await this.fileExists(filePath)) {
|
|
948
|
+
throw new Error("File already exists \u2014 use str_replace to edit");
|
|
949
|
+
}
|
|
950
|
+
await this.fs.mkdir(path4.dirname(filePath), { recursive: true });
|
|
951
|
+
await this.fs.writeFile(filePath, fileText, "utf8");
|
|
952
|
+
return `Created file: ${displayedPath}`;
|
|
953
|
+
}
|
|
954
|
+
throw new Error(`Unknown edit_file command: ${command}`);
|
|
955
|
+
}
|
|
956
|
+
async executeListFiles(args) {
|
|
957
|
+
const rawPath = getOptionalString(args, "path") ?? ".";
|
|
958
|
+
const directoryPath = this.resolveAllowedPath(rawPath);
|
|
959
|
+
const entries = await this.fs.readdir(directoryPath);
|
|
960
|
+
const names = entries.sort((left, right) => left.localeCompare(right));
|
|
961
|
+
if (names.length === 0) {
|
|
962
|
+
return "(empty directory)";
|
|
963
|
+
}
|
|
964
|
+
return names.join("\n");
|
|
965
|
+
}
|
|
966
|
+
async executeRunCommand(args) {
|
|
967
|
+
const command = getRequiredString(args, "command");
|
|
968
|
+
const commandCwdArg = getOptionalString(args, "cwd");
|
|
969
|
+
const commandCwd = commandCwdArg ? this.resolveAllowedPath(commandCwdArg) : this.cwd;
|
|
970
|
+
return this.runCommandFn(command, commandCwd);
|
|
971
|
+
}
|
|
972
|
+
async executeSearchWeb(args) {
|
|
973
|
+
const query = getRequiredString(args, "query");
|
|
974
|
+
return this.searchWebFn(query);
|
|
975
|
+
}
|
|
976
|
+
resolveAllowedPath(inputPath) {
|
|
977
|
+
const resolvedPath = path4.resolve(this.cwd, inputPath);
|
|
978
|
+
const isAllowed = this.allowedPaths.some((allowedPath) => {
|
|
979
|
+
if (allowedPath === resolvedPath) return true;
|
|
980
|
+
const rel = path4.relative(allowedPath, resolvedPath);
|
|
981
|
+
return rel.length > 0 && !rel.startsWith("..") && !path4.isAbsolute(rel);
|
|
982
|
+
});
|
|
983
|
+
if (!isAllowed) {
|
|
984
|
+
throw new Error(`Path is outside allowed paths: ${inputPath}`);
|
|
985
|
+
}
|
|
986
|
+
return resolvedPath;
|
|
987
|
+
}
|
|
988
|
+
async fileExists(filePath) {
|
|
989
|
+
try {
|
|
990
|
+
await this.fs.readFile(filePath, "utf8");
|
|
991
|
+
return true;
|
|
992
|
+
} catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// packages/poe-agent/src/agent-session.ts
|
|
1001
|
+
async function createAgentSession(options = {}) {
|
|
1002
|
+
const model = resolveRequiredModel(options.model);
|
|
1003
|
+
const apiKey = await resolveApiKey(options.apiKey);
|
|
1004
|
+
const systemPrompt = await loadSystemPrompt();
|
|
1005
|
+
const toolExecutor = new DefaultToolExecutor({
|
|
1006
|
+
cwd: options.cwd,
|
|
1007
|
+
allowedPaths: options.allowedPaths
|
|
1008
|
+
});
|
|
1009
|
+
const tools = toolExecutor.getAvailableTools();
|
|
1010
|
+
let currentOnSessionUpdate;
|
|
1011
|
+
const chatService = new PoeChatService({
|
|
1012
|
+
apiKey,
|
|
1013
|
+
model,
|
|
1014
|
+
baseUrl: options.baseUrl,
|
|
1015
|
+
fetch: options.fetch,
|
|
1016
|
+
systemPrompt,
|
|
1017
|
+
toolExecutor,
|
|
1018
|
+
maxToolCallIterations: options.maxToolCallIterations,
|
|
1019
|
+
onToolCall: (event) => {
|
|
1020
|
+
if (!currentOnSessionUpdate) return;
|
|
1021
|
+
for (const update of mapToolLifecycleEventToSessionUpdates(event)) {
|
|
1022
|
+
currentOnSessionUpdate(update);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
let disposed = false;
|
|
1027
|
+
return {
|
|
1028
|
+
async sendMessage(prompt, sendOptions) {
|
|
1029
|
+
if (disposed) {
|
|
1030
|
+
throw new Error("Agent session is already disposed.");
|
|
1031
|
+
}
|
|
1032
|
+
currentOnSessionUpdate = sendOptions?.onSessionUpdate;
|
|
1033
|
+
const response = await chatService.sendMessage(prompt, {
|
|
1034
|
+
tools,
|
|
1035
|
+
signal: sendOptions?.signal
|
|
1036
|
+
});
|
|
1037
|
+
if (currentOnSessionUpdate && response.role === "assistant" && response.content.length > 0) {
|
|
1038
|
+
currentOnSessionUpdate({
|
|
1039
|
+
sessionUpdate: "agent_message_chunk",
|
|
1040
|
+
content: {
|
|
1041
|
+
type: "text",
|
|
1042
|
+
text: response.content
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
return response;
|
|
1047
|
+
},
|
|
1048
|
+
async dispose() {
|
|
1049
|
+
if (disposed) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
disposed = true;
|
|
1053
|
+
chatService.clearConversationHistory();
|
|
1054
|
+
const disposableToolExecutor = toolExecutor;
|
|
1055
|
+
if (typeof disposableToolExecutor.dispose === "function") {
|
|
1056
|
+
await disposableToolExecutor.dispose();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
async function resolveApiKey(explicitApiKey) {
|
|
1062
|
+
const normalizedExplicitApiKey = normalizeNonEmptyString(explicitApiKey);
|
|
1063
|
+
if (normalizedExplicitApiKey) {
|
|
1064
|
+
return normalizedExplicitApiKey;
|
|
1065
|
+
}
|
|
1066
|
+
const { store } = createAuthStore();
|
|
1067
|
+
const storedApiKey = normalizeNonEmptyString(await store.getApiKey());
|
|
1068
|
+
if (storedApiKey) {
|
|
1069
|
+
return storedApiKey;
|
|
1070
|
+
}
|
|
1071
|
+
throw new Error("Missing Poe API key. Provide apiKey or run 'poe-code login'.");
|
|
1072
|
+
}
|
|
1073
|
+
function resolveRequiredModel(model) {
|
|
1074
|
+
const normalizedModel = normalizeNonEmptyString(model);
|
|
1075
|
+
if (normalizedModel) {
|
|
1076
|
+
return normalizedModel;
|
|
1077
|
+
}
|
|
1078
|
+
throw new Error("Missing model. Provide a non-empty model to createAgentSession.");
|
|
1079
|
+
}
|
|
1080
|
+
function normalizeNonEmptyString(value) {
|
|
1081
|
+
if (typeof value !== "string") {
|
|
1082
|
+
return void 0;
|
|
1083
|
+
}
|
|
1084
|
+
const trimmed = value.trim();
|
|
1085
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1086
|
+
}
|
|
1087
|
+
function mapToolLifecycleEventToSessionUpdates(event) {
|
|
1088
|
+
if (event.phase === "started") {
|
|
1089
|
+
const toolCall = {
|
|
1090
|
+
sessionUpdate: "tool_call",
|
|
1091
|
+
toolCallId: event.toolCallId,
|
|
1092
|
+
title: event.toolName,
|
|
1093
|
+
kind: "execute",
|
|
1094
|
+
status: "pending",
|
|
1095
|
+
rawInput: event.args
|
|
1096
|
+
};
|
|
1097
|
+
const inProgressUpdate = {
|
|
1098
|
+
sessionUpdate: "tool_call_update",
|
|
1099
|
+
toolCallId: event.toolCallId,
|
|
1100
|
+
kind: "execute",
|
|
1101
|
+
status: "in_progress"
|
|
1102
|
+
};
|
|
1103
|
+
return [toolCall, inProgressUpdate];
|
|
1104
|
+
}
|
|
1105
|
+
const terminalUpdate = {
|
|
1106
|
+
sessionUpdate: "tool_call_update",
|
|
1107
|
+
toolCallId: event.toolCallId,
|
|
1108
|
+
kind: "execute",
|
|
1109
|
+
status: event.phase === "completed" ? "completed" : "failed"
|
|
1110
|
+
};
|
|
1111
|
+
if (event.phase === "completed" && event.result !== void 0) {
|
|
1112
|
+
terminalUpdate.rawOutput = event.result;
|
|
1113
|
+
}
|
|
1114
|
+
if (event.phase === "failed" && event.error !== void 0) {
|
|
1115
|
+
terminalUpdate.rawOutput = event.error;
|
|
1116
|
+
}
|
|
1117
|
+
return [terminalUpdate];
|
|
1118
|
+
}
|
|
1119
|
+
var init_agent_session = __esm({
|
|
1120
|
+
"packages/poe-agent/src/agent-session.ts"() {
|
|
1121
|
+
"use strict";
|
|
1122
|
+
init_src();
|
|
1123
|
+
init_chat();
|
|
1124
|
+
init_system_prompt();
|
|
1125
|
+
init_tool_executor();
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
// packages/poe-agent/src/index.ts
|
|
1130
|
+
var src_exports = {};
|
|
1131
|
+
__export(src_exports, {
|
|
1132
|
+
createAgentSession: () => createAgentSession
|
|
1133
|
+
});
|
|
1134
|
+
var init_src2 = __esm({
|
|
1135
|
+
"packages/poe-agent/src/index.ts"() {
|
|
1136
|
+
"use strict";
|
|
1137
|
+
init_agent_session();
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
// src/cli/constants.ts
|
|
1142
|
+
var DEFAULT_FRONTIER_MODEL = "anthropic/claude-sonnet-4.6";
|
|
1143
|
+
var CLAUDE_CODE_VARIANTS = {
|
|
1144
|
+
haiku: "anthropic/claude-haiku-4.5",
|
|
1145
|
+
sonnet: "anthropic/claude-sonnet-4.6",
|
|
1146
|
+
opus: "anthropic/claude-opus-4.6"
|
|
1147
|
+
};
|
|
1148
|
+
var DEFAULT_CLAUDE_CODE_MODEL = CLAUDE_CODE_VARIANTS.sonnet;
|
|
1149
|
+
var CODEX_MODELS = [
|
|
1150
|
+
"openai/gpt-5.3-codex",
|
|
1151
|
+
"openai/gpt-5.2-codex",
|
|
1152
|
+
"openai/gpt-5.2",
|
|
1153
|
+
"openai/gpt-5.2-chat",
|
|
1154
|
+
"openai/gpt-5.2-pro",
|
|
1155
|
+
"openai/gpt-5.1",
|
|
1156
|
+
"openai/gpt-5.1-codex-mini"
|
|
1157
|
+
];
|
|
1158
|
+
var DEFAULT_CODEX_MODEL = CODEX_MODELS[0];
|
|
1159
|
+
var KIMI_MODELS = [
|
|
1160
|
+
"novitaai/kimi-k2.5",
|
|
1161
|
+
"novitaai/kimi-k2-thinking"
|
|
1162
|
+
];
|
|
1163
|
+
var DEFAULT_KIMI_MODEL = KIMI_MODELS[0];
|
|
1164
|
+
|
|
1165
|
+
// packages/poe-acp-client/src/acp-client.ts
|
|
1166
|
+
import { isAbsolute } from "node:path";
|
|
1167
|
+
|
|
1168
|
+
// packages/poe-acp-client/src/acp-transport.ts
|
|
1169
|
+
import {
|
|
1170
|
+
spawn as spawnChildProcess
|
|
1171
|
+
} from "node:child_process";
|
|
1172
|
+
|
|
1173
|
+
// packages/poe-acp-client/src/types.ts
|
|
1174
|
+
var ACP_ERROR_CODE_PARSE = -32700;
|
|
1175
|
+
var ACP_ERROR_CODE_INVALID_REQUEST = -32600;
|
|
1176
|
+
var ACP_ERROR_CODE_METHOD_NOT_FOUND = -32601;
|
|
1177
|
+
var ACP_ERROR_CODE_INVALID_PARAMS = -32602;
|
|
1178
|
+
var ACP_ERROR_CODE_INTERNAL = -32603;
|
|
1179
|
+
var ACP_ERROR_CODE_RESOURCE_NOT_FOUND = -32002;
|
|
1180
|
+
function isObjectRecord(value) {
|
|
1181
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1182
|
+
}
|
|
1183
|
+
function isAcpErrorCode(value) {
|
|
1184
|
+
return typeof value === "number" && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647;
|
|
1185
|
+
}
|
|
1186
|
+
var AcpError = class extends Error {
|
|
1187
|
+
code;
|
|
1188
|
+
data;
|
|
1189
|
+
constructor(code, message, data) {
|
|
1190
|
+
super(message);
|
|
1191
|
+
this.name = "AcpError";
|
|
1192
|
+
this.code = code;
|
|
1193
|
+
if (data !== void 0) {
|
|
1194
|
+
this.data = data;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
function isAcpError(value) {
|
|
1199
|
+
if (value instanceof AcpError) {
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
if (!isObjectRecord(value)) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
if (!isAcpErrorCode(value.code) || typeof value.message !== "string") {
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
return value.data === void 0 || Object.prototype.hasOwnProperty.call(value, "data");
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// packages/poe-acp-client/src/jsonrpc-message-layer.ts
|
|
1212
|
+
function isObjectRecord2(value) {
|
|
1213
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1214
|
+
}
|
|
1215
|
+
function hasOwn(value, property) {
|
|
1216
|
+
return Object.prototype.hasOwnProperty.call(value, property);
|
|
1217
|
+
}
|
|
1218
|
+
function isRequestId(value) {
|
|
1219
|
+
return value === null || typeof value === "string" || typeof value === "number";
|
|
1220
|
+
}
|
|
1221
|
+
function toRequestId(value) {
|
|
1222
|
+
return isRequestId(value) ? value : null;
|
|
1223
|
+
}
|
|
1224
|
+
function parseError() {
|
|
1225
|
+
return new AcpError(ACP_ERROR_CODE_PARSE, "Parse error");
|
|
1226
|
+
}
|
|
1227
|
+
function invalidRequest() {
|
|
1228
|
+
return new AcpError(ACP_ERROR_CODE_INVALID_REQUEST, "Invalid Request");
|
|
1229
|
+
}
|
|
1230
|
+
function methodNotFound(method) {
|
|
1231
|
+
return new AcpError(
|
|
1232
|
+
ACP_ERROR_CODE_METHOD_NOT_FOUND,
|
|
1233
|
+
`Method not found: "${method}"`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
function internalError(message) {
|
|
1237
|
+
return new AcpError(ACP_ERROR_CODE_INTERNAL, message);
|
|
1238
|
+
}
|
|
1239
|
+
function isJsonRpcErrorObject(value) {
|
|
1240
|
+
if (!isObjectRecord2(value)) {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
if (!isAcpErrorCode(value.code) || typeof value.message !== "string") {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
return value.data === void 0 || hasOwn(value, "data");
|
|
1247
|
+
}
|
|
1248
|
+
function toResponseError(error) {
|
|
1249
|
+
return new AcpError(error.code, error.message, error.data);
|
|
1250
|
+
}
|
|
1251
|
+
function toDispatchError(error) {
|
|
1252
|
+
if (error instanceof AcpError) {
|
|
1253
|
+
return error;
|
|
1254
|
+
}
|
|
1255
|
+
if (isAcpError(error)) {
|
|
1256
|
+
return new AcpError(error.code, error.message, error.data);
|
|
1257
|
+
}
|
|
1258
|
+
if (error instanceof Error && error.message.length > 0) {
|
|
1259
|
+
return internalError(error.message);
|
|
1260
|
+
}
|
|
1261
|
+
return internalError("Internal error");
|
|
1262
|
+
}
|
|
1263
|
+
function chunkToString(chunk) {
|
|
1264
|
+
if (typeof chunk === "string") {
|
|
1265
|
+
return chunk;
|
|
1266
|
+
}
|
|
1267
|
+
if (chunk instanceof Uint8Array) {
|
|
1268
|
+
return Buffer.from(chunk).toString("utf8");
|
|
1269
|
+
}
|
|
1270
|
+
return String(chunk);
|
|
1271
|
+
}
|
|
1272
|
+
function normalizeLine(line) {
|
|
1273
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1274
|
+
}
|
|
1275
|
+
async function* readLines(stream) {
|
|
1276
|
+
let buffer = "";
|
|
1277
|
+
for await (const chunk of stream) {
|
|
1278
|
+
buffer += chunkToString(chunk);
|
|
1279
|
+
while (true) {
|
|
1280
|
+
const newlineIndex = buffer.indexOf("\n");
|
|
1281
|
+
if (newlineIndex === -1) {
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
const line = buffer.slice(0, newlineIndex);
|
|
1285
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
1286
|
+
yield normalizeLine(line);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (buffer.length > 0) {
|
|
1290
|
+
yield normalizeLine(buffer);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function createResponseMessage(id, result) {
|
|
1294
|
+
return {
|
|
1295
|
+
jsonrpc: "2.0",
|
|
1296
|
+
id,
|
|
1297
|
+
result: result === void 0 ? null : result
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
function createJsonRpcErrorResponse(id, error) {
|
|
1301
|
+
const response = {
|
|
1302
|
+
jsonrpc: "2.0",
|
|
1303
|
+
id,
|
|
1304
|
+
error: {
|
|
1305
|
+
code: error.code,
|
|
1306
|
+
message: error.message
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
if (error.data !== void 0) {
|
|
1310
|
+
response.error.data = error.data;
|
|
1311
|
+
}
|
|
1312
|
+
return response;
|
|
1313
|
+
}
|
|
1314
|
+
function serializeJsonRpcMessage(message) {
|
|
1315
|
+
return `${JSON.stringify(message)}
|
|
1316
|
+
`;
|
|
1317
|
+
}
|
|
1318
|
+
function parseJsonRpcMessage(line) {
|
|
1319
|
+
let parsed;
|
|
1320
|
+
try {
|
|
1321
|
+
parsed = JSON.parse(line);
|
|
1322
|
+
} catch {
|
|
1323
|
+
return {
|
|
1324
|
+
type: "invalid",
|
|
1325
|
+
id: null,
|
|
1326
|
+
error: parseError()
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
if (!isObjectRecord2(parsed)) {
|
|
1330
|
+
return {
|
|
1331
|
+
type: "invalid",
|
|
1332
|
+
id: null,
|
|
1333
|
+
error: invalidRequest()
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
const id = toRequestId(parsed.id);
|
|
1337
|
+
if (parsed.jsonrpc !== "2.0") {
|
|
1338
|
+
return {
|
|
1339
|
+
type: "invalid",
|
|
1340
|
+
id,
|
|
1341
|
+
error: invalidRequest()
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
const hasMethod = hasOwn(parsed, "method");
|
|
1345
|
+
const hasId = hasOwn(parsed, "id");
|
|
1346
|
+
if (hasMethod) {
|
|
1347
|
+
if (typeof parsed.method !== "string") {
|
|
1348
|
+
return {
|
|
1349
|
+
type: "invalid",
|
|
1350
|
+
id,
|
|
1351
|
+
error: invalidRequest()
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (hasId) {
|
|
1355
|
+
if (!isRequestId(parsed.id)) {
|
|
1356
|
+
return {
|
|
1357
|
+
type: "invalid",
|
|
1358
|
+
id: null,
|
|
1359
|
+
error: invalidRequest()
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const request = {
|
|
1363
|
+
jsonrpc: "2.0",
|
|
1364
|
+
id: parsed.id,
|
|
1365
|
+
method: parsed.method
|
|
1366
|
+
};
|
|
1367
|
+
if (hasOwn(parsed, "params")) {
|
|
1368
|
+
request.params = parsed.params;
|
|
1369
|
+
}
|
|
1370
|
+
return { type: "request", message: request };
|
|
1371
|
+
}
|
|
1372
|
+
const notification = {
|
|
1373
|
+
jsonrpc: "2.0",
|
|
1374
|
+
method: parsed.method
|
|
1375
|
+
};
|
|
1376
|
+
if (hasOwn(parsed, "params")) {
|
|
1377
|
+
notification.params = parsed.params;
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
type: "notification",
|
|
1381
|
+
message: notification
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
if (!hasId || !isRequestId(parsed.id)) {
|
|
1385
|
+
return {
|
|
1386
|
+
type: "invalid",
|
|
1387
|
+
id,
|
|
1388
|
+
error: invalidRequest()
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const hasResult = hasOwn(parsed, "result");
|
|
1392
|
+
const hasError = hasOwn(parsed, "error");
|
|
1393
|
+
if (hasResult === hasError) {
|
|
1394
|
+
return {
|
|
1395
|
+
type: "invalid",
|
|
1396
|
+
id: parsed.id,
|
|
1397
|
+
error: invalidRequest()
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
if (hasResult) {
|
|
1401
|
+
return {
|
|
1402
|
+
type: "response",
|
|
1403
|
+
message: {
|
|
1404
|
+
jsonrpc: "2.0",
|
|
1405
|
+
id: parsed.id,
|
|
1406
|
+
result: parsed.result
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
if (!isJsonRpcErrorObject(parsed.error)) {
|
|
1411
|
+
return {
|
|
1412
|
+
type: "invalid",
|
|
1413
|
+
id: parsed.id,
|
|
1414
|
+
error: invalidRequest()
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
type: "response",
|
|
1419
|
+
message: {
|
|
1420
|
+
jsonrpc: "2.0",
|
|
1421
|
+
id: parsed.id,
|
|
1422
|
+
error: parsed.error
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
var JsonRpcMessageLayer = class {
|
|
1427
|
+
input;
|
|
1428
|
+
output;
|
|
1429
|
+
requestHandlers = /* @__PURE__ */ new Map();
|
|
1430
|
+
notificationHandlers = /* @__PURE__ */ new Map();
|
|
1431
|
+
pending = /* @__PURE__ */ new Map();
|
|
1432
|
+
defaultTimeoutMs;
|
|
1433
|
+
nextRequestId;
|
|
1434
|
+
disposed = false;
|
|
1435
|
+
constructor(options) {
|
|
1436
|
+
this.input = options.input;
|
|
1437
|
+
this.output = options.output;
|
|
1438
|
+
this.defaultTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
1439
|
+
this.nextRequestId = options.firstRequestId ?? 1;
|
|
1440
|
+
if (!Number.isFinite(this.defaultTimeoutMs) || this.defaultTimeoutMs < 0) {
|
|
1441
|
+
throw new Error("requestTimeoutMs must be a non-negative finite number");
|
|
1442
|
+
}
|
|
1443
|
+
if (!Number.isFinite(this.nextRequestId)) {
|
|
1444
|
+
throw new Error("firstRequestId must be a finite number");
|
|
1445
|
+
}
|
|
1446
|
+
void this.consumeInput();
|
|
1447
|
+
}
|
|
1448
|
+
onRequest(method, handler) {
|
|
1449
|
+
this.requestHandlers.set(method, handler);
|
|
1450
|
+
}
|
|
1451
|
+
onNotification(method, handler) {
|
|
1452
|
+
this.notificationHandlers.set(method, handler);
|
|
1453
|
+
}
|
|
1454
|
+
pendingRequestCount() {
|
|
1455
|
+
return this.pending.size;
|
|
1456
|
+
}
|
|
1457
|
+
sendNotification(method, params) {
|
|
1458
|
+
const notification = {
|
|
1459
|
+
jsonrpc: "2.0",
|
|
1460
|
+
method
|
|
1461
|
+
};
|
|
1462
|
+
if (params !== void 0) {
|
|
1463
|
+
notification.params = params;
|
|
1464
|
+
}
|
|
1465
|
+
this.sendMessage(notification);
|
|
1466
|
+
}
|
|
1467
|
+
sendRequest(method, params, options = {}) {
|
|
1468
|
+
if (this.disposed) {
|
|
1469
|
+
throw new Error("JSON-RPC message layer is disposed");
|
|
1470
|
+
}
|
|
1471
|
+
const id = options.id === void 0 ? this.nextRequestId++ : options.id;
|
|
1472
|
+
if (this.pending.has(id)) {
|
|
1473
|
+
throw new Error(`A request with id ${JSON.stringify(id)} is already pending`);
|
|
1474
|
+
}
|
|
1475
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
1476
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
1477
|
+
throw new Error("timeoutMs must be a non-negative finite number");
|
|
1478
|
+
}
|
|
1479
|
+
const request = {
|
|
1480
|
+
jsonrpc: "2.0",
|
|
1481
|
+
id,
|
|
1482
|
+
method
|
|
1483
|
+
};
|
|
1484
|
+
if (params !== void 0) {
|
|
1485
|
+
request.params = params;
|
|
1486
|
+
}
|
|
1487
|
+
return new Promise((resolve, reject) => {
|
|
1488
|
+
const timeout = setTimeout(() => {
|
|
1489
|
+
this.pending.delete(id);
|
|
1490
|
+
reject(
|
|
1491
|
+
new Error(`JSON-RPC request "${method}" timed out after ${timeoutMs}ms`)
|
|
1492
|
+
);
|
|
1493
|
+
}, timeoutMs);
|
|
1494
|
+
this.pending.set(id, {
|
|
1495
|
+
method,
|
|
1496
|
+
resolve,
|
|
1497
|
+
reject,
|
|
1498
|
+
timeout
|
|
1499
|
+
});
|
|
1500
|
+
try {
|
|
1501
|
+
this.sendMessage(request);
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
clearTimeout(timeout);
|
|
1504
|
+
this.pending.delete(id);
|
|
1505
|
+
reject(error);
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
dispose(reason = new Error("JSON-RPC message layer disposed")) {
|
|
1510
|
+
if (this.disposed) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
this.disposed = true;
|
|
1514
|
+
this.rejectAllPending(reason);
|
|
1515
|
+
}
|
|
1516
|
+
async consumeInput() {
|
|
1517
|
+
try {
|
|
1518
|
+
for await (const line of readLines(this.input)) {
|
|
1519
|
+
if (this.disposed || line.length === 0) {
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
await this.handleIncomingLine(line);
|
|
1523
|
+
}
|
|
1524
|
+
if (!this.disposed) {
|
|
1525
|
+
this.dispose(new Error("JSON-RPC input stream closed"));
|
|
1526
|
+
}
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
if (!this.disposed) {
|
|
1529
|
+
this.dispose(
|
|
1530
|
+
error instanceof Error ? error : new Error(`JSON-RPC input stream failed: ${String(error)}`)
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
async handleIncomingLine(line) {
|
|
1536
|
+
const parsed = parseJsonRpcMessage(line);
|
|
1537
|
+
if (parsed.type === "invalid") {
|
|
1538
|
+
this.sendMessage(createJsonRpcErrorResponse(parsed.id, parsed.error));
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (parsed.type === "response") {
|
|
1542
|
+
this.handleResponse(parsed.message);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
if (parsed.type === "notification") {
|
|
1546
|
+
await this.handleNotification(parsed.message);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
await this.handleRequest(parsed.message);
|
|
1550
|
+
}
|
|
1551
|
+
handleResponse(message) {
|
|
1552
|
+
const pending = this.pending.get(message.id);
|
|
1553
|
+
if (!pending) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
this.pending.delete(message.id);
|
|
1557
|
+
if (pending.timeout !== null) {
|
|
1558
|
+
clearTimeout(pending.timeout);
|
|
1559
|
+
}
|
|
1560
|
+
if ("error" in message) {
|
|
1561
|
+
pending.reject(toResponseError(message.error));
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
pending.resolve(message.result);
|
|
1565
|
+
}
|
|
1566
|
+
async handleRequest(message) {
|
|
1567
|
+
const handler = this.requestHandlers.get(message.method);
|
|
1568
|
+
if (!handler) {
|
|
1569
|
+
this.sendMessage(createJsonRpcErrorResponse(message.id, methodNotFound(message.method)));
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
const result = await handler(message.params, {
|
|
1574
|
+
id: message.id,
|
|
1575
|
+
method: message.method
|
|
1576
|
+
});
|
|
1577
|
+
this.sendMessage(createResponseMessage(message.id, result));
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
this.sendMessage(createJsonRpcErrorResponse(message.id, toDispatchError(error)));
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
async handleNotification(message) {
|
|
1583
|
+
const handler = this.notificationHandlers.get(message.method);
|
|
1584
|
+
if (!handler) {
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
await handler(message.params, { method: message.method });
|
|
1589
|
+
} catch {
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
sendMessage(message) {
|
|
1593
|
+
if (this.disposed) {
|
|
1594
|
+
throw new Error("JSON-RPC message layer is disposed");
|
|
1595
|
+
}
|
|
1596
|
+
this.output.write(serializeJsonRpcMessage(message));
|
|
1597
|
+
}
|
|
1598
|
+
rejectAllPending(error) {
|
|
1599
|
+
for (const pending of this.pending.values()) {
|
|
1600
|
+
if (pending.timeout !== null) {
|
|
1601
|
+
clearTimeout(pending.timeout);
|
|
1602
|
+
}
|
|
1603
|
+
pending.reject(error);
|
|
1604
|
+
}
|
|
1605
|
+
this.pending.clear();
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
// packages/poe-acp-client/src/acp-transport.ts
|
|
1610
|
+
function assertExtensionMethod(method) {
|
|
1611
|
+
if (!method.startsWith("_")) {
|
|
1612
|
+
throw new Error('Extension method must start with "_"');
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
var AcpTransport = class {
|
|
1616
|
+
closed;
|
|
1617
|
+
command;
|
|
1618
|
+
child;
|
|
1619
|
+
layer;
|
|
1620
|
+
stderrChunks = [];
|
|
1621
|
+
resolveClosed = null;
|
|
1622
|
+
closeEvent = null;
|
|
1623
|
+
closeReason = null;
|
|
1624
|
+
constructor(options) {
|
|
1625
|
+
const {
|
|
1626
|
+
command,
|
|
1627
|
+
args = [],
|
|
1628
|
+
cwd,
|
|
1629
|
+
env,
|
|
1630
|
+
requestTimeoutMs,
|
|
1631
|
+
firstRequestId,
|
|
1632
|
+
spawn: spawn2 = spawnChildProcess
|
|
1633
|
+
} = options;
|
|
1634
|
+
this.command = command;
|
|
1635
|
+
this.closed = new Promise((resolve) => {
|
|
1636
|
+
this.resolveClosed = resolve;
|
|
1637
|
+
});
|
|
1638
|
+
this.child = spawn2(command, [...args], {
|
|
1639
|
+
cwd,
|
|
1640
|
+
env,
|
|
1641
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1642
|
+
});
|
|
1643
|
+
this.child.stderr.setEncoding("utf8");
|
|
1644
|
+
this.child.stderr.on("data", (chunk) => {
|
|
1645
|
+
this.stderrChunks.push(String(chunk));
|
|
1646
|
+
});
|
|
1647
|
+
this.layer = new JsonRpcMessageLayer({
|
|
1648
|
+
input: this.child.stdout,
|
|
1649
|
+
output: this.child.stdin,
|
|
1650
|
+
requestTimeoutMs,
|
|
1651
|
+
firstRequestId
|
|
1652
|
+
});
|
|
1653
|
+
this.child.once("error", (error) => {
|
|
1654
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
1655
|
+
this.close(reason, this.child.exitCode ?? null, this.child.signalCode ?? null);
|
|
1656
|
+
});
|
|
1657
|
+
this.child.once("close", (code, signal) => {
|
|
1658
|
+
const reason = this.closeReason ?? new Error(
|
|
1659
|
+
`ACP transport closed (command "${this.command}", code: ${code ?? "null"}${signal ? `, signal: ${signal}` : ""})`
|
|
1660
|
+
);
|
|
1661
|
+
this.close(reason, code ?? null, signal ?? null);
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
sendRequest(method, params, options = {}) {
|
|
1665
|
+
return this.layer.sendRequest(method, params, options);
|
|
1666
|
+
}
|
|
1667
|
+
sendExtRequest(method, params, options = {}) {
|
|
1668
|
+
assertExtensionMethod(method);
|
|
1669
|
+
return this.layer.sendRequest(method, params, options);
|
|
1670
|
+
}
|
|
1671
|
+
sendNotification(method, params) {
|
|
1672
|
+
this.layer.sendNotification(method, params);
|
|
1673
|
+
}
|
|
1674
|
+
sendExtNotification(method, params) {
|
|
1675
|
+
assertExtensionMethod(method);
|
|
1676
|
+
this.layer.sendNotification(method, params);
|
|
1677
|
+
}
|
|
1678
|
+
onRequest(method, handler) {
|
|
1679
|
+
this.layer.onRequest(method, handler);
|
|
1680
|
+
}
|
|
1681
|
+
onExtRequest(method, handler) {
|
|
1682
|
+
assertExtensionMethod(method);
|
|
1683
|
+
this.layer.onRequest(method, handler);
|
|
1684
|
+
}
|
|
1685
|
+
onNotification(method, handler) {
|
|
1686
|
+
this.layer.onNotification(method, handler);
|
|
1687
|
+
}
|
|
1688
|
+
onExtNotification(method, handler) {
|
|
1689
|
+
assertExtensionMethod(method);
|
|
1690
|
+
this.layer.onNotification(method, handler);
|
|
1691
|
+
}
|
|
1692
|
+
getStderrOutput() {
|
|
1693
|
+
return this.stderrChunks.join("");
|
|
1694
|
+
}
|
|
1695
|
+
pendingRequestCount() {
|
|
1696
|
+
return this.layer.pendingRequestCount();
|
|
1697
|
+
}
|
|
1698
|
+
dispose(reason = new Error("ACP transport disposed")) {
|
|
1699
|
+
if (this.closeEvent !== null) {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
this.closeReason = reason;
|
|
1703
|
+
this.layer.dispose(reason);
|
|
1704
|
+
if (!this.child.stdin.destroyed && !this.child.stdin.writableEnded) {
|
|
1705
|
+
this.child.stdin.end();
|
|
1706
|
+
}
|
|
1707
|
+
if (this.child.exitCode !== null || this.child.signalCode !== null) {
|
|
1708
|
+
this.close(reason, this.child.exitCode, this.child.signalCode);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const killed = this.child.kill();
|
|
1712
|
+
if (!killed) {
|
|
1713
|
+
this.close(reason, this.child.exitCode, this.child.signalCode);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
close(reason, code, signal) {
|
|
1717
|
+
if (this.closeEvent !== null) {
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
this.layer.dispose(reason);
|
|
1721
|
+
this.closeEvent = {
|
|
1722
|
+
code,
|
|
1723
|
+
signal,
|
|
1724
|
+
reason,
|
|
1725
|
+
stderr: this.getStderrOutput()
|
|
1726
|
+
};
|
|
1727
|
+
this.resolveClosed?.(this.closeEvent);
|
|
1728
|
+
this.resolveClosed = null;
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
// packages/poe-acp-client/src/acp-client.ts
|
|
1733
|
+
function toError(reason) {
|
|
1734
|
+
return reason instanceof Error ? reason : new Error(String(reason));
|
|
1735
|
+
}
|
|
1736
|
+
function invalidParams(message) {
|
|
1737
|
+
return new AcpError(
|
|
1738
|
+
ACP_ERROR_CODE_INVALID_PARAMS,
|
|
1739
|
+
`Invalid params: ${message}`
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
function resourceNotFound(resource) {
|
|
1743
|
+
return new AcpError(
|
|
1744
|
+
ACP_ERROR_CODE_RESOURCE_NOT_FOUND,
|
|
1745
|
+
`Resource not found: ${resource}`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
function assertAbsolutePath(path5) {
|
|
1749
|
+
if (!isAbsolute(path5)) {
|
|
1750
|
+
throw invalidParams('"path" must be an absolute path');
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function assertOneBasedLineNumber(line) {
|
|
1754
|
+
if (line === null || line === void 0) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (!Number.isInteger(line) || line < 1) {
|
|
1758
|
+
throw invalidParams('"line" must be a 1-based integer');
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function assertExtensionMethod2(method) {
|
|
1762
|
+
if (!method.startsWith("_")) {
|
|
1763
|
+
throw new Error('Extension method must start with "_"');
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
function isInjectedTransportOptions(options) {
|
|
1767
|
+
return "transport" in options;
|
|
1768
|
+
}
|
|
1769
|
+
function createAsyncQueue() {
|
|
1770
|
+
const values = [];
|
|
1771
|
+
const waiters = [];
|
|
1772
|
+
let closed = false;
|
|
1773
|
+
let failure = null;
|
|
1774
|
+
const resolveOne = (value) => {
|
|
1775
|
+
const waiter = waiters.shift();
|
|
1776
|
+
if (!waiter) {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
waiter.resolve({ done: false, value });
|
|
1780
|
+
return true;
|
|
1781
|
+
};
|
|
1782
|
+
const iterator = {
|
|
1783
|
+
push(value) {
|
|
1784
|
+
if (closed || failure) {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (!resolveOne(value)) {
|
|
1788
|
+
values.push(value);
|
|
1789
|
+
}
|
|
1790
|
+
},
|
|
1791
|
+
complete() {
|
|
1792
|
+
if (closed || failure) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
closed = true;
|
|
1796
|
+
while (waiters.length > 0) {
|
|
1797
|
+
waiters.shift()?.resolve({ done: true, value: void 0 });
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
fail(error) {
|
|
1801
|
+
if (closed || failure) {
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
failure = error;
|
|
1805
|
+
while (waiters.length > 0) {
|
|
1806
|
+
waiters.shift()?.reject(error);
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
async next() {
|
|
1810
|
+
if (values.length > 0) {
|
|
1811
|
+
const value = values.shift();
|
|
1812
|
+
return { done: false, value };
|
|
1813
|
+
}
|
|
1814
|
+
if (failure) {
|
|
1815
|
+
throw failure;
|
|
1816
|
+
}
|
|
1817
|
+
if (closed) {
|
|
1818
|
+
return { done: true, value: void 0 };
|
|
1819
|
+
}
|
|
1820
|
+
return new Promise((resolve, reject) => {
|
|
1821
|
+
waiters.push({ resolve, reject });
|
|
1822
|
+
});
|
|
1823
|
+
},
|
|
1824
|
+
async return() {
|
|
1825
|
+
iterator.complete();
|
|
1826
|
+
return { done: true, value: void 0 };
|
|
1827
|
+
},
|
|
1828
|
+
async throw(error) {
|
|
1829
|
+
const normalizedError = toError(error);
|
|
1830
|
+
iterator.fail(normalizedError);
|
|
1831
|
+
throw normalizedError;
|
|
1832
|
+
},
|
|
1833
|
+
[Symbol.asyncIterator]() {
|
|
1834
|
+
return iterator;
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
return iterator;
|
|
1838
|
+
}
|
|
1839
|
+
var AcpClient = class {
|
|
1840
|
+
transport;
|
|
1841
|
+
clientProtocolVersion;
|
|
1842
|
+
clientCapabilities;
|
|
1843
|
+
clientInfo;
|
|
1844
|
+
permissionHandler;
|
|
1845
|
+
fsHandler;
|
|
1846
|
+
terminalHandler;
|
|
1847
|
+
activePromptUpdates = /* @__PURE__ */ new Map();
|
|
1848
|
+
trackedTerminalIds = /* @__PURE__ */ new Map();
|
|
1849
|
+
hasRegisteredFsReadHandler = false;
|
|
1850
|
+
hasRegisteredFsWriteHandler = false;
|
|
1851
|
+
hasRegisteredTerminalHandlers = false;
|
|
1852
|
+
disposed = false;
|
|
1853
|
+
lifecycleState = "uninitialized";
|
|
1854
|
+
negotiatedVersion = null;
|
|
1855
|
+
availableAuthMethods = [];
|
|
1856
|
+
negotiatedAgentCapabilities;
|
|
1857
|
+
negotiatedAgentInfo;
|
|
1858
|
+
constructor(options) {
|
|
1859
|
+
this.transport = isInjectedTransportOptions(options) ? options.transport : new AcpTransport({
|
|
1860
|
+
command: options.command,
|
|
1861
|
+
args: options.args,
|
|
1862
|
+
cwd: options.cwd,
|
|
1863
|
+
env: options.env,
|
|
1864
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
1865
|
+
firstRequestId: options.firstRequestId,
|
|
1866
|
+
spawn: options.spawn
|
|
1867
|
+
});
|
|
1868
|
+
this.clientProtocolVersion = options.protocolVersion ?? 1;
|
|
1869
|
+
this.clientCapabilities = options.clientCapabilities;
|
|
1870
|
+
this.clientInfo = options.clientInfo;
|
|
1871
|
+
this.permissionHandler = options.handlers?.permission ?? options.permissionHandler;
|
|
1872
|
+
this.fsHandler = options.handlers?.fs ?? options.fsHandler;
|
|
1873
|
+
this.terminalHandler = options.handlers?.terminal ?? options.terminalHandler;
|
|
1874
|
+
this.transport.onRequest(
|
|
1875
|
+
"session/request_permission",
|
|
1876
|
+
async (params) => {
|
|
1877
|
+
if (!this.permissionHandler) {
|
|
1878
|
+
return {
|
|
1879
|
+
outcome: { outcome: "cancelled" }
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
const outcome = await this.permissionHandler({
|
|
1883
|
+
toolCall: params.toolCall,
|
|
1884
|
+
options: params.options
|
|
1885
|
+
});
|
|
1886
|
+
return { outcome };
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
this.registerCapabilityHandlers(this.clientCapabilities);
|
|
1890
|
+
this.transport.onNotification("session/update", (params) => {
|
|
1891
|
+
this.handleSessionUpdateNotification(params);
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
get state() {
|
|
1895
|
+
return this.lifecycleState;
|
|
1896
|
+
}
|
|
1897
|
+
get negotiatedProtocolVersion() {
|
|
1898
|
+
return this.negotiatedVersion;
|
|
1899
|
+
}
|
|
1900
|
+
get authMethods() {
|
|
1901
|
+
return [...this.availableAuthMethods];
|
|
1902
|
+
}
|
|
1903
|
+
get agentCapabilities() {
|
|
1904
|
+
return this.negotiatedAgentCapabilities;
|
|
1905
|
+
}
|
|
1906
|
+
get agentInfo() {
|
|
1907
|
+
return this.negotiatedAgentInfo;
|
|
1908
|
+
}
|
|
1909
|
+
get closed() {
|
|
1910
|
+
return this.transport.closed;
|
|
1911
|
+
}
|
|
1912
|
+
async initialize(clientCapabilities) {
|
|
1913
|
+
if (this.lifecycleState !== "uninitialized") {
|
|
1914
|
+
throw new Error("initialize() can only be called once.");
|
|
1915
|
+
}
|
|
1916
|
+
if (clientCapabilities !== void 0) {
|
|
1917
|
+
this.clientCapabilities = clientCapabilities;
|
|
1918
|
+
this.registerCapabilityHandlers(clientCapabilities);
|
|
1919
|
+
}
|
|
1920
|
+
const response = await this.transport.sendRequest("initialize", {
|
|
1921
|
+
protocolVersion: this.clientProtocolVersion,
|
|
1922
|
+
clientInfo: this.clientInfo,
|
|
1923
|
+
clientCapabilities: this.clientCapabilities
|
|
1924
|
+
});
|
|
1925
|
+
const negotiatedProtocolVersion = Math.min(
|
|
1926
|
+
this.clientProtocolVersion,
|
|
1927
|
+
response.protocolVersion
|
|
1928
|
+
);
|
|
1929
|
+
this.negotiatedVersion = negotiatedProtocolVersion;
|
|
1930
|
+
this.negotiatedAgentCapabilities = response.agentCapabilities;
|
|
1931
|
+
this.negotiatedAgentInfo = response.agentInfo;
|
|
1932
|
+
this.availableAuthMethods = response.authMethods ? [...response.authMethods] : [];
|
|
1933
|
+
this.lifecycleState = this.availableAuthMethods.length > 0 ? "initialized" : "ready";
|
|
1934
|
+
return {
|
|
1935
|
+
protocolVersion: negotiatedProtocolVersion,
|
|
1936
|
+
...this.negotiatedAgentCapabilities !== void 0 ? { agentCapabilities: this.negotiatedAgentCapabilities } : {},
|
|
1937
|
+
...this.negotiatedAgentInfo !== void 0 ? { agentInfo: this.negotiatedAgentInfo } : {},
|
|
1938
|
+
...this.availableAuthMethods.length > 0 ? { authMethods: this.authMethods } : {}
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
async authenticate(methodId) {
|
|
1942
|
+
if (this.lifecycleState === "uninitialized") {
|
|
1943
|
+
throw new Error("Cannot authenticate before initialize().");
|
|
1944
|
+
}
|
|
1945
|
+
if (this.lifecycleState === "ready") {
|
|
1946
|
+
throw new Error("Authentication is not required for this agent.");
|
|
1947
|
+
}
|
|
1948
|
+
if (!this.availableAuthMethods.some((authMethod) => authMethod.id === methodId)) {
|
|
1949
|
+
throw new Error(`Unknown auth method "${methodId}".`);
|
|
1950
|
+
}
|
|
1951
|
+
const response = await this.transport.sendRequest("authenticate", {
|
|
1952
|
+
methodId
|
|
1953
|
+
});
|
|
1954
|
+
this.lifecycleState = "ready";
|
|
1955
|
+
return response;
|
|
1956
|
+
}
|
|
1957
|
+
async newSession(cwd, mcpServers) {
|
|
1958
|
+
this.assertReady("session/new");
|
|
1959
|
+
this.assertMcpServerCapabilitySupport(mcpServers);
|
|
1960
|
+
return this.transport.sendRequest("session/new", {
|
|
1961
|
+
cwd,
|
|
1962
|
+
mcpServers
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
async loadSession(sessionId, cwd, mcpServers) {
|
|
1966
|
+
this.assertReady("session/load");
|
|
1967
|
+
if (this.negotiatedAgentCapabilities?.loadSession !== true) {
|
|
1968
|
+
throw new Error(
|
|
1969
|
+
'Cannot call "session/load" because the agent does not support session loading.'
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
this.assertMcpServerCapabilitySupport(mcpServers);
|
|
1973
|
+
return this.transport.sendRequest("session/load", {
|
|
1974
|
+
sessionId,
|
|
1975
|
+
cwd,
|
|
1976
|
+
mcpServers
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
async cancelSession(sessionId) {
|
|
1980
|
+
this.assertReady("session/cancel");
|
|
1981
|
+
const payload = { sessionId };
|
|
1982
|
+
this.transport.sendNotification("session/cancel", payload);
|
|
1983
|
+
}
|
|
1984
|
+
async setMode(sessionId, modeId) {
|
|
1985
|
+
this.assertReady("session/set_mode");
|
|
1986
|
+
return this.transport.sendRequest("session/set_mode", {
|
|
1987
|
+
sessionId,
|
|
1988
|
+
modeId
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
async setConfigOption(sessionId, configId, value) {
|
|
1992
|
+
this.assertReady("session/set_config_option");
|
|
1993
|
+
const response = await this.transport.sendRequest("session/set_config_option", {
|
|
1994
|
+
sessionId,
|
|
1995
|
+
configId,
|
|
1996
|
+
value
|
|
1997
|
+
});
|
|
1998
|
+
return response.configOptions;
|
|
1999
|
+
}
|
|
2000
|
+
prompt(sessionId, content) {
|
|
2001
|
+
this.assertReady("session/prompt");
|
|
2002
|
+
this.assertPromptContentCapabilitySupport(content);
|
|
2003
|
+
if (this.activePromptUpdates.has(sessionId)) {
|
|
2004
|
+
throw new Error(
|
|
2005
|
+
`Cannot call "session/prompt" while another prompt is in progress for session "${sessionId}".`
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
const updates = createAsyncQueue();
|
|
2009
|
+
this.activePromptUpdates.set(sessionId, updates);
|
|
2010
|
+
let requestPromise;
|
|
2011
|
+
try {
|
|
2012
|
+
requestPromise = this.transport.sendRequest("session/prompt", {
|
|
2013
|
+
sessionId,
|
|
2014
|
+
prompt: content
|
|
2015
|
+
});
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
const normalizedError = toError(error);
|
|
2018
|
+
this.activePromptUpdates.delete(sessionId);
|
|
2019
|
+
updates.fail(normalizedError);
|
|
2020
|
+
throw normalizedError;
|
|
2021
|
+
}
|
|
2022
|
+
const response = requestPromise.then((promptResponse) => {
|
|
2023
|
+
this.activePromptUpdates.delete(sessionId);
|
|
2024
|
+
updates.complete();
|
|
2025
|
+
return promptResponse;
|
|
2026
|
+
}).catch((error) => {
|
|
2027
|
+
const normalizedError = toError(error);
|
|
2028
|
+
this.activePromptUpdates.delete(sessionId);
|
|
2029
|
+
updates.fail(normalizedError);
|
|
2030
|
+
throw normalizedError;
|
|
2031
|
+
});
|
|
2032
|
+
return {
|
|
2033
|
+
response,
|
|
2034
|
+
[Symbol.asyncIterator]() {
|
|
2035
|
+
return updates;
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
async sendExtRequest(method, params, options = {}) {
|
|
2040
|
+
assertExtensionMethod2(method);
|
|
2041
|
+
return this.transport.sendRequest(method, params, options);
|
|
2042
|
+
}
|
|
2043
|
+
async sendExtNotification(method, params) {
|
|
2044
|
+
assertExtensionMethod2(method);
|
|
2045
|
+
this.transport.sendNotification(method, params);
|
|
2046
|
+
}
|
|
2047
|
+
onExtRequest(method, handler) {
|
|
2048
|
+
assertExtensionMethod2(method);
|
|
2049
|
+
this.transport.onRequest(method, handler);
|
|
2050
|
+
}
|
|
2051
|
+
onExtNotification(method, handler) {
|
|
2052
|
+
assertExtensionMethod2(method);
|
|
2053
|
+
this.transport.onNotification(method, handler);
|
|
2054
|
+
}
|
|
2055
|
+
async dispose() {
|
|
2056
|
+
if (this.disposed) {
|
|
2057
|
+
if (this.transport.closed) {
|
|
2058
|
+
await this.transport.closed;
|
|
2059
|
+
}
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
this.disposed = true;
|
|
2063
|
+
const disposeReason = new Error("ACP client disposed");
|
|
2064
|
+
for (const updates of this.activePromptUpdates.values()) {
|
|
2065
|
+
updates.fail(disposeReason);
|
|
2066
|
+
}
|
|
2067
|
+
this.activePromptUpdates.clear();
|
|
2068
|
+
if (typeof this.transport.dispose === "function") {
|
|
2069
|
+
this.transport.dispose(disposeReason);
|
|
2070
|
+
}
|
|
2071
|
+
if (this.transport.closed) {
|
|
2072
|
+
await this.transport.closed;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
assertReady(operation) {
|
|
2076
|
+
if (this.lifecycleState === "ready") {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
if (this.lifecycleState === "uninitialized") {
|
|
2080
|
+
throw new Error(`Cannot call "${operation}" before initialize().`);
|
|
2081
|
+
}
|
|
2082
|
+
throw new Error(`Cannot call "${operation}" before authentication completes.`);
|
|
2083
|
+
}
|
|
2084
|
+
registerCapabilityHandlers(capabilities) {
|
|
2085
|
+
if (!this.hasRegisteredFsReadHandler && capabilities?.fs?.readTextFile === true && this.fsHandler?.readTextFile) {
|
|
2086
|
+
const readTextFile = this.fsHandler.readTextFile;
|
|
2087
|
+
this.transport.onRequest(
|
|
2088
|
+
"fs/read_text_file",
|
|
2089
|
+
async (params) => {
|
|
2090
|
+
assertAbsolutePath(params.path);
|
|
2091
|
+
assertOneBasedLineNumber(params.line);
|
|
2092
|
+
const content = await readTextFile({
|
|
2093
|
+
sessionId: params.sessionId,
|
|
2094
|
+
path: params.path,
|
|
2095
|
+
line: params.line,
|
|
2096
|
+
limit: params.limit
|
|
2097
|
+
});
|
|
2098
|
+
return { content };
|
|
2099
|
+
}
|
|
2100
|
+
);
|
|
2101
|
+
this.hasRegisteredFsReadHandler = true;
|
|
2102
|
+
}
|
|
2103
|
+
if (!this.hasRegisteredFsWriteHandler && capabilities?.fs?.writeTextFile === true && this.fsHandler?.writeTextFile) {
|
|
2104
|
+
const writeTextFile = this.fsHandler.writeTextFile;
|
|
2105
|
+
this.transport.onRequest(
|
|
2106
|
+
"fs/write_text_file",
|
|
2107
|
+
async (params) => {
|
|
2108
|
+
assertAbsolutePath(params.path);
|
|
2109
|
+
await writeTextFile({
|
|
2110
|
+
sessionId: params.sessionId,
|
|
2111
|
+
path: params.path,
|
|
2112
|
+
content: params.content
|
|
2113
|
+
});
|
|
2114
|
+
return {};
|
|
2115
|
+
}
|
|
2116
|
+
);
|
|
2117
|
+
this.hasRegisteredFsWriteHandler = true;
|
|
2118
|
+
}
|
|
2119
|
+
if (!this.hasRegisteredTerminalHandlers && capabilities?.terminal === true && this.terminalHandler) {
|
|
2120
|
+
const terminalHandler = this.terminalHandler;
|
|
2121
|
+
this.transport.onRequest(
|
|
2122
|
+
"terminal/create",
|
|
2123
|
+
async (params) => {
|
|
2124
|
+
const terminalId = await terminalHandler.create({
|
|
2125
|
+
sessionId: params.sessionId,
|
|
2126
|
+
command: params.command,
|
|
2127
|
+
args: params.args,
|
|
2128
|
+
cwd: params.cwd,
|
|
2129
|
+
env: params.env,
|
|
2130
|
+
outputByteLimit: params.outputByteLimit
|
|
2131
|
+
});
|
|
2132
|
+
this.trackTerminal(params.sessionId, terminalId);
|
|
2133
|
+
return { terminalId };
|
|
2134
|
+
}
|
|
2135
|
+
);
|
|
2136
|
+
this.transport.onRequest(
|
|
2137
|
+
"terminal/output",
|
|
2138
|
+
async (params) => {
|
|
2139
|
+
this.assertKnownTerminal(params.sessionId, params.terminalId);
|
|
2140
|
+
return terminalHandler.output({
|
|
2141
|
+
sessionId: params.sessionId,
|
|
2142
|
+
terminalId: params.terminalId
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
);
|
|
2146
|
+
this.transport.onRequest(
|
|
2147
|
+
"terminal/wait_for_exit",
|
|
2148
|
+
async (params) => {
|
|
2149
|
+
this.assertKnownTerminal(params.sessionId, params.terminalId);
|
|
2150
|
+
return terminalHandler.waitForExit({
|
|
2151
|
+
sessionId: params.sessionId,
|
|
2152
|
+
terminalId: params.terminalId
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
);
|
|
2156
|
+
this.transport.onRequest(
|
|
2157
|
+
"terminal/kill",
|
|
2158
|
+
async (params) => {
|
|
2159
|
+
this.assertKnownTerminal(params.sessionId, params.terminalId);
|
|
2160
|
+
await terminalHandler.kill({
|
|
2161
|
+
sessionId: params.sessionId,
|
|
2162
|
+
terminalId: params.terminalId
|
|
2163
|
+
});
|
|
2164
|
+
return {};
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
this.transport.onRequest(
|
|
2168
|
+
"terminal/release",
|
|
2169
|
+
async (params) => {
|
|
2170
|
+
this.assertKnownTerminal(params.sessionId, params.terminalId);
|
|
2171
|
+
await terminalHandler.release({
|
|
2172
|
+
sessionId: params.sessionId,
|
|
2173
|
+
terminalId: params.terminalId
|
|
2174
|
+
});
|
|
2175
|
+
this.untrackTerminal(params.sessionId, params.terminalId);
|
|
2176
|
+
return {};
|
|
2177
|
+
}
|
|
2178
|
+
);
|
|
2179
|
+
this.hasRegisteredTerminalHandlers = true;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
assertMcpServerCapabilitySupport(mcpServers) {
|
|
2183
|
+
const mcpCapabilities = this.negotiatedAgentCapabilities?.mcpCapabilities;
|
|
2184
|
+
for (const mcpServer of mcpServers) {
|
|
2185
|
+
if (!("type" in mcpServer)) {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (mcpServer.type === "http" && mcpCapabilities?.http !== true) {
|
|
2189
|
+
throw new Error('Agent does not support MCP server type "http".');
|
|
2190
|
+
}
|
|
2191
|
+
if (mcpServer.type === "sse" && mcpCapabilities?.sse !== true) {
|
|
2192
|
+
throw new Error('Agent does not support MCP server type "sse".');
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
handleSessionUpdateNotification(notification) {
|
|
2197
|
+
const activePrompt = this.activePromptUpdates.get(notification.sessionId);
|
|
2198
|
+
if (!activePrompt) {
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
activePrompt.push({
|
|
2202
|
+
jsonrpc: "2.0",
|
|
2203
|
+
method: "session/update",
|
|
2204
|
+
params: notification
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
trackTerminal(sessionId, terminalId) {
|
|
2208
|
+
const sessionTerminals = this.trackedTerminalIds.get(sessionId);
|
|
2209
|
+
if (sessionTerminals) {
|
|
2210
|
+
sessionTerminals.add(terminalId);
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
this.trackedTerminalIds.set(sessionId, /* @__PURE__ */ new Set([terminalId]));
|
|
2214
|
+
}
|
|
2215
|
+
assertKnownTerminal(sessionId, terminalId) {
|
|
2216
|
+
const sessionTerminals = this.trackedTerminalIds.get(sessionId);
|
|
2217
|
+
if (sessionTerminals?.has(terminalId) === true) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
throw resourceNotFound(`terminal "${terminalId}"`);
|
|
2221
|
+
}
|
|
2222
|
+
untrackTerminal(sessionId, terminalId) {
|
|
2223
|
+
const sessionTerminals = this.trackedTerminalIds.get(sessionId);
|
|
2224
|
+
if (!sessionTerminals) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
sessionTerminals.delete(terminalId);
|
|
2228
|
+
if (sessionTerminals.size === 0) {
|
|
2229
|
+
this.trackedTerminalIds.delete(sessionId);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
assertPromptContentCapabilitySupport(content) {
|
|
2233
|
+
const promptCapabilities = this.negotiatedAgentCapabilities?.promptCapabilities;
|
|
2234
|
+
for (const block of content) {
|
|
2235
|
+
if (block.type === "image" && promptCapabilities?.image !== true) {
|
|
2236
|
+
throw new Error('Agent does not support prompt content type "image".');
|
|
2237
|
+
}
|
|
2238
|
+
if (block.type === "audio" && promptCapabilities?.audio !== true) {
|
|
2239
|
+
throw new Error('Agent does not support prompt content type "audio".');
|
|
2240
|
+
}
|
|
2241
|
+
if (block.type === "resource" && promptCapabilities?.embeddedContext !== true) {
|
|
2242
|
+
throw new Error('Agent does not support prompt content type "resource".');
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
|
|
2248
|
+
// packages/poe-acp-client/src/run-report.ts
|
|
2249
|
+
import * as fsPromises from "node:fs/promises";
|
|
2250
|
+
import { homedir } from "node:os";
|
|
2251
|
+
import { join } from "node:path";
|
|
2252
|
+
|
|
2253
|
+
// packages/poe-acp-client/src/stream-helpers.ts
|
|
2254
|
+
async function extractUsageFromSessionUpdateStream(stream) {
|
|
2255
|
+
const updates = [];
|
|
2256
|
+
for await (const entry of stream) {
|
|
2257
|
+
const update = toSessionUpdate(entry);
|
|
2258
|
+
if (update.sessionUpdate === "usage_update") {
|
|
2259
|
+
updates.push(update);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return updates;
|
|
2263
|
+
}
|
|
2264
|
+
async function extractToolCallSummariesFromSessionUpdateStream(stream) {
|
|
2265
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
2266
|
+
for await (const entry of stream) {
|
|
2267
|
+
const update = toSessionUpdate(entry);
|
|
2268
|
+
if (update.sessionUpdate === "tool_call") {
|
|
2269
|
+
const summary = {
|
|
2270
|
+
toolCallId: update.toolCallId,
|
|
2271
|
+
title: update.title
|
|
2272
|
+
};
|
|
2273
|
+
if (update.kind !== void 0) {
|
|
2274
|
+
summary.kind = update.kind;
|
|
2275
|
+
}
|
|
2276
|
+
if (update.status !== void 0) {
|
|
2277
|
+
summary.status = update.status;
|
|
2278
|
+
}
|
|
2279
|
+
if (update.rawInput !== void 0) {
|
|
2280
|
+
summary.rawInput = update.rawInput;
|
|
2281
|
+
}
|
|
2282
|
+
if (update.rawOutput !== void 0) {
|
|
2283
|
+
summary.rawOutput = update.rawOutput;
|
|
2284
|
+
}
|
|
2285
|
+
summaries.set(update.toolCallId, summary);
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (update.sessionUpdate === "tool_call_update") {
|
|
2289
|
+
const existing = summaries.get(update.toolCallId);
|
|
2290
|
+
const summary = existing ?? {
|
|
2291
|
+
toolCallId: update.toolCallId,
|
|
2292
|
+
title: toTitle(update.title, update.toolCallId)
|
|
2293
|
+
};
|
|
2294
|
+
if (update.title !== null && update.title !== void 0 && update.title.length > 0) {
|
|
2295
|
+
summary.title = update.title;
|
|
2296
|
+
}
|
|
2297
|
+
if (update.kind !== null && update.kind !== void 0) {
|
|
2298
|
+
summary.kind = update.kind;
|
|
2299
|
+
}
|
|
2300
|
+
if (update.status !== null && update.status !== void 0) {
|
|
2301
|
+
summary.status = update.status;
|
|
2302
|
+
}
|
|
2303
|
+
if (update.rawInput !== void 0) {
|
|
2304
|
+
summary.rawInput = update.rawInput;
|
|
2305
|
+
}
|
|
2306
|
+
if (update.rawOutput !== void 0) {
|
|
2307
|
+
summary.rawOutput = update.rawOutput;
|
|
2308
|
+
}
|
|
2309
|
+
summaries.set(update.toolCallId, summary);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
return Array.from(summaries.values());
|
|
2313
|
+
}
|
|
2314
|
+
function toSessionUpdate(entry) {
|
|
2315
|
+
if (isSessionUpdateNotification(entry)) {
|
|
2316
|
+
return entry.params.update;
|
|
2317
|
+
}
|
|
2318
|
+
return entry;
|
|
2319
|
+
}
|
|
2320
|
+
function isSessionUpdateNotification(entry) {
|
|
2321
|
+
return typeof entry.jsonrpc === "string" && entry.method === "session/update";
|
|
2322
|
+
}
|
|
2323
|
+
function toTitle(value, fallback) {
|
|
2324
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2325
|
+
return value;
|
|
2326
|
+
}
|
|
2327
|
+
return fallback;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// packages/poe-acp-client/src/run-report.ts
|
|
2331
|
+
async function generateRunReportFromSessionUpdateStream(stream, options = {}) {
|
|
2332
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2333
|
+
const bufferedEntries = [];
|
|
2334
|
+
let runIdFromStream;
|
|
2335
|
+
for await (const entry of stream) {
|
|
2336
|
+
bufferedEntries.push(entry);
|
|
2337
|
+
if (runIdFromStream) {
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
if (isSessionUpdateNotification2(entry)) {
|
|
2341
|
+
const sessionId = toNonEmptyString(entry.params.sessionId);
|
|
2342
|
+
if (sessionId) {
|
|
2343
|
+
runIdFromStream = sessionId;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
const runId = toNonEmptyString(options.runId) ?? runIdFromStream;
|
|
2348
|
+
if (!runId) {
|
|
2349
|
+
throw new Error("Run id is required via options.runId or session/update stream items");
|
|
2350
|
+
}
|
|
2351
|
+
const startTime = normalizeTime(options.startTime, now);
|
|
2352
|
+
const endTime = normalizeTime(options.endTime, now);
|
|
2353
|
+
const toolCalls = await extractToolCallSummariesFromSessionUpdateStream(bufferedEntries);
|
|
2354
|
+
const usageUpdates = await extractUsageFromSessionUpdateStream(bufferedEntries);
|
|
2355
|
+
const usage = summarizeUsage(usageUpdates);
|
|
2356
|
+
const errors = collectErrors(toolCalls, options.errors);
|
|
2357
|
+
const exitStatus = options.exitStatus ?? (errors.length > 0 ? "failed" : "success");
|
|
2358
|
+
return {
|
|
2359
|
+
runId,
|
|
2360
|
+
startTime,
|
|
2361
|
+
endTime,
|
|
2362
|
+
exitStatus,
|
|
2363
|
+
toolCalls,
|
|
2364
|
+
usage,
|
|
2365
|
+
errors
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
function isSessionUpdateNotification2(entry) {
|
|
2369
|
+
return typeof entry.jsonrpc === "string" && entry.method === "session/update";
|
|
2370
|
+
}
|
|
2371
|
+
function normalizeTime(value, now) {
|
|
2372
|
+
if (value instanceof Date) {
|
|
2373
|
+
return value.toISOString();
|
|
2374
|
+
}
|
|
2375
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2376
|
+
const parsed = new Date(value);
|
|
2377
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
2378
|
+
return parsed.toISOString();
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
return now().toISOString();
|
|
2382
|
+
}
|
|
2383
|
+
function summarizeUsage(updates) {
|
|
2384
|
+
let used = 0;
|
|
2385
|
+
let size = 0;
|
|
2386
|
+
let cost;
|
|
2387
|
+
for (const update of updates) {
|
|
2388
|
+
used += update.used;
|
|
2389
|
+
size += update.size;
|
|
2390
|
+
if (update.cost !== void 0) {
|
|
2391
|
+
cost = update.cost;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
const usage = {
|
|
2395
|
+
used,
|
|
2396
|
+
size,
|
|
2397
|
+
updates: updates.length
|
|
2398
|
+
};
|
|
2399
|
+
if (cost !== void 0) {
|
|
2400
|
+
usage.cost = cost;
|
|
2401
|
+
}
|
|
2402
|
+
return usage;
|
|
2403
|
+
}
|
|
2404
|
+
function collectErrors(toolCalls, additionalErrors) {
|
|
2405
|
+
const errors = [];
|
|
2406
|
+
for (const toolCall of toolCalls) {
|
|
2407
|
+
if (toolCall.status !== "failed") {
|
|
2408
|
+
continue;
|
|
2409
|
+
}
|
|
2410
|
+
errors.push({
|
|
2411
|
+
toolCallId: toolCall.toolCallId,
|
|
2412
|
+
message: toErrorMessage(toolCall)
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
if (additionalErrors) {
|
|
2416
|
+
for (const message of additionalErrors) {
|
|
2417
|
+
const text = toNonEmptyString(message);
|
|
2418
|
+
if (text) {
|
|
2419
|
+
errors.push({ message: text });
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
return errors;
|
|
2424
|
+
}
|
|
2425
|
+
function toErrorMessage(toolCall) {
|
|
2426
|
+
if (typeof toolCall.rawOutput === "string" && toolCall.rawOutput.length > 0) {
|
|
2427
|
+
return toolCall.rawOutput;
|
|
2428
|
+
}
|
|
2429
|
+
if (toolCall.rawOutput instanceof Error && toolCall.rawOutput.message.length > 0) {
|
|
2430
|
+
return toolCall.rawOutput.message;
|
|
2431
|
+
}
|
|
2432
|
+
if (toolCall.rawOutput !== void 0 && toolCall.rawOutput !== null) {
|
|
2433
|
+
const encoded = trySerialize(toolCall.rawOutput);
|
|
2434
|
+
if (encoded) {
|
|
2435
|
+
return encoded;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
return `${toolCall.title} failed`;
|
|
2439
|
+
}
|
|
2440
|
+
function trySerialize(value) {
|
|
2441
|
+
try {
|
|
2442
|
+
const serialized = JSON.stringify(value);
|
|
2443
|
+
if (typeof serialized === "string" && serialized.length > 0) {
|
|
2444
|
+
return serialized;
|
|
2445
|
+
}
|
|
2446
|
+
} catch {
|
|
2447
|
+
return void 0;
|
|
2448
|
+
}
|
|
2449
|
+
return void 0;
|
|
2450
|
+
}
|
|
2451
|
+
function toNonEmptyString(value) {
|
|
2452
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2453
|
+
return void 0;
|
|
2454
|
+
}
|
|
2455
|
+
return value;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// packages/config-mutations/src/execution/apply-mutation.ts
|
|
2459
|
+
import Mustache from "mustache";
|
|
2460
|
+
|
|
2461
|
+
// packages/config-mutations/src/formats/json.ts
|
|
2462
|
+
import * as jsonc from "jsonc-parser";
|
|
2463
|
+
function isConfigObject(value) {
|
|
2464
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2465
|
+
}
|
|
2466
|
+
function parse2(content) {
|
|
2467
|
+
if (!content || content.trim() === "") {
|
|
2468
|
+
return {};
|
|
2469
|
+
}
|
|
2470
|
+
const errors = [];
|
|
2471
|
+
const parsed = jsonc.parse(content, errors, {
|
|
2472
|
+
allowTrailingComma: true,
|
|
2473
|
+
disallowComments: false
|
|
2474
|
+
});
|
|
2475
|
+
if (errors.length > 0) {
|
|
2476
|
+
throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
|
|
2477
|
+
}
|
|
2478
|
+
if (parsed === null || parsed === void 0) {
|
|
2479
|
+
return {};
|
|
2480
|
+
}
|
|
2481
|
+
if (!isConfigObject(parsed)) {
|
|
2482
|
+
throw new Error("Expected JSON object.");
|
|
2483
|
+
}
|
|
2484
|
+
return parsed;
|
|
2485
|
+
}
|
|
2486
|
+
function serialize(obj) {
|
|
2487
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
2488
|
+
`;
|
|
2489
|
+
}
|
|
2490
|
+
function merge(base, patch) {
|
|
2491
|
+
const result = { ...base };
|
|
2492
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
2493
|
+
if (value === void 0) {
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
const existing = result[key];
|
|
2497
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
2498
|
+
result[key] = merge(existing, value);
|
|
2499
|
+
continue;
|
|
2500
|
+
}
|
|
2501
|
+
result[key] = value;
|
|
2502
|
+
}
|
|
2503
|
+
return result;
|
|
2504
|
+
}
|
|
2505
|
+
function prune(obj, shape) {
|
|
2506
|
+
let changed = false;
|
|
2507
|
+
const result = { ...obj };
|
|
2508
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
2509
|
+
if (!(key in result)) {
|
|
2510
|
+
continue;
|
|
2511
|
+
}
|
|
2512
|
+
const current = result[key];
|
|
2513
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
2514
|
+
delete result[key];
|
|
2515
|
+
changed = true;
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
2519
|
+
const { changed: childChanged, result: childResult } = prune(
|
|
2520
|
+
current,
|
|
2521
|
+
pattern
|
|
2522
|
+
);
|
|
2523
|
+
if (childChanged) {
|
|
2524
|
+
changed = true;
|
|
2525
|
+
}
|
|
2526
|
+
if (Object.keys(childResult).length === 0) {
|
|
2527
|
+
delete result[key];
|
|
2528
|
+
} else {
|
|
2529
|
+
result[key] = childResult;
|
|
2530
|
+
}
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
delete result[key];
|
|
2534
|
+
changed = true;
|
|
2535
|
+
}
|
|
2536
|
+
return { changed, result };
|
|
2537
|
+
}
|
|
2538
|
+
var jsonFormat = {
|
|
2539
|
+
parse: parse2,
|
|
2540
|
+
serialize,
|
|
2541
|
+
merge,
|
|
2542
|
+
prune
|
|
2543
|
+
};
|
|
2544
|
+
|
|
2545
|
+
// packages/config-mutations/src/formats/toml.ts
|
|
2546
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
2547
|
+
function isConfigObject2(value) {
|
|
2548
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2549
|
+
}
|
|
2550
|
+
function parse3(content) {
|
|
2551
|
+
if (!content || content.trim() === "") {
|
|
2552
|
+
return {};
|
|
2553
|
+
}
|
|
2554
|
+
const parsed = parseToml(content);
|
|
2555
|
+
if (!isConfigObject2(parsed)) {
|
|
2556
|
+
throw new Error("Expected TOML document to be a table.");
|
|
2557
|
+
}
|
|
2558
|
+
return parsed;
|
|
2559
|
+
}
|
|
2560
|
+
function serialize2(obj) {
|
|
2561
|
+
const serialized = stringifyToml(obj);
|
|
2562
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}
|
|
2563
|
+
`;
|
|
2564
|
+
}
|
|
2565
|
+
function merge2(base, patch) {
|
|
2566
|
+
const result = { ...base };
|
|
2567
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
2568
|
+
if (value === void 0) {
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
const existing = result[key];
|
|
2572
|
+
if (isConfigObject2(existing) && isConfigObject2(value)) {
|
|
2573
|
+
result[key] = merge2(existing, value);
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
result[key] = value;
|
|
2577
|
+
}
|
|
2578
|
+
return result;
|
|
2579
|
+
}
|
|
2580
|
+
function prune2(obj, shape) {
|
|
2581
|
+
let changed = false;
|
|
2582
|
+
const result = { ...obj };
|
|
2583
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
2584
|
+
if (!(key in result)) {
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
const current = result[key];
|
|
2588
|
+
if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
|
|
2589
|
+
delete result[key];
|
|
2590
|
+
changed = true;
|
|
2591
|
+
continue;
|
|
2592
|
+
}
|
|
2593
|
+
if (isConfigObject2(pattern) && isConfigObject2(current)) {
|
|
2594
|
+
const { changed: childChanged, result: childResult } = prune2(
|
|
2595
|
+
current,
|
|
2596
|
+
pattern
|
|
2597
|
+
);
|
|
2598
|
+
if (childChanged) {
|
|
2599
|
+
changed = true;
|
|
2600
|
+
}
|
|
2601
|
+
if (Object.keys(childResult).length === 0) {
|
|
2602
|
+
delete result[key];
|
|
2603
|
+
} else {
|
|
2604
|
+
result[key] = childResult;
|
|
2605
|
+
}
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
delete result[key];
|
|
2609
|
+
changed = true;
|
|
2610
|
+
}
|
|
2611
|
+
return { changed, result };
|
|
2612
|
+
}
|
|
2613
|
+
var tomlFormat = {
|
|
2614
|
+
parse: parse3,
|
|
2615
|
+
serialize: serialize2,
|
|
2616
|
+
merge: merge2,
|
|
2617
|
+
prune: prune2
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
// packages/config-mutations/src/formats/index.ts
|
|
2621
|
+
var formatRegistry = {
|
|
2622
|
+
json: jsonFormat,
|
|
2623
|
+
toml: tomlFormat
|
|
2624
|
+
};
|
|
2625
|
+
var extensionMap = {
|
|
2626
|
+
".json": "json",
|
|
2627
|
+
".toml": "toml"
|
|
2628
|
+
};
|
|
2629
|
+
function getConfigFormat(pathOrFormat) {
|
|
2630
|
+
if (pathOrFormat in formatRegistry) {
|
|
2631
|
+
return formatRegistry[pathOrFormat];
|
|
2632
|
+
}
|
|
2633
|
+
const ext = getExtension(pathOrFormat);
|
|
2634
|
+
const formatName = extensionMap[ext];
|
|
2635
|
+
if (!formatName) {
|
|
2636
|
+
throw new Error(
|
|
2637
|
+
`Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
return formatRegistry[formatName];
|
|
2641
|
+
}
|
|
2642
|
+
function detectFormat(path5) {
|
|
2643
|
+
const ext = getExtension(path5);
|
|
2644
|
+
return extensionMap[ext];
|
|
2645
|
+
}
|
|
2646
|
+
function getExtension(path5) {
|
|
2647
|
+
const lastDot = path5.lastIndexOf(".");
|
|
2648
|
+
if (lastDot === -1) {
|
|
2649
|
+
return "";
|
|
2650
|
+
}
|
|
2651
|
+
return path5.slice(lastDot).toLowerCase();
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// packages/config-mutations/src/execution/path-utils.ts
|
|
2655
|
+
import path from "node:path";
|
|
2656
|
+
function expandHome(targetPath, homeDir) {
|
|
2657
|
+
if (!targetPath?.startsWith("~")) {
|
|
2658
|
+
return targetPath;
|
|
2659
|
+
}
|
|
2660
|
+
if (targetPath.startsWith("~./")) {
|
|
2661
|
+
targetPath = `~/.${targetPath.slice(3)}`;
|
|
2662
|
+
}
|
|
2663
|
+
let remainder = targetPath.slice(1);
|
|
2664
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
2665
|
+
remainder = remainder.slice(1);
|
|
2666
|
+
} else if (remainder.startsWith(".")) {
|
|
2667
|
+
remainder = remainder.slice(1);
|
|
2668
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
2669
|
+
remainder = remainder.slice(1);
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return remainder.length === 0 ? homeDir : path.join(homeDir, remainder);
|
|
2673
|
+
}
|
|
2674
|
+
function validateHomePath(targetPath) {
|
|
2675
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
2676
|
+
throw new Error("Target path must be a non-empty string.");
|
|
2677
|
+
}
|
|
2678
|
+
if (!targetPath.startsWith("~")) {
|
|
2679
|
+
throw new Error(
|
|
2680
|
+
`All target paths must be home-relative (start with ~). Received: "${targetPath}"`
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
function resolvePath(rawPath, homeDir, pathMapper) {
|
|
2685
|
+
validateHomePath(rawPath);
|
|
2686
|
+
const expanded = expandHome(rawPath, homeDir);
|
|
2687
|
+
if (!pathMapper) {
|
|
2688
|
+
return expanded;
|
|
2689
|
+
}
|
|
2690
|
+
const rawDirectory = path.dirname(expanded);
|
|
2691
|
+
const mappedDirectory = pathMapper.mapTargetDirectory({
|
|
2692
|
+
targetDirectory: rawDirectory
|
|
2693
|
+
});
|
|
2694
|
+
const filename = path.basename(expanded);
|
|
2695
|
+
return filename.length === 0 ? mappedDirectory : path.join(mappedDirectory, filename);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
// packages/config-mutations/src/fs-utils.ts
|
|
2699
|
+
function isNotFound(error) {
|
|
2700
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2701
|
+
}
|
|
2702
|
+
async function readFileIfExists(fs2, target) {
|
|
2703
|
+
try {
|
|
2704
|
+
return await fs2.readFile(target, "utf8");
|
|
2705
|
+
} catch (error) {
|
|
2706
|
+
if (isNotFound(error)) {
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
throw error;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
async function pathExists(fs2, target) {
|
|
2713
|
+
try {
|
|
2714
|
+
await fs2.stat(target);
|
|
2715
|
+
return true;
|
|
2716
|
+
} catch (error) {
|
|
2717
|
+
if (isNotFound(error)) {
|
|
2718
|
+
return false;
|
|
2719
|
+
}
|
|
2720
|
+
throw error;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function createTimestamp() {
|
|
2724
|
+
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// packages/config-mutations/src/execution/apply-mutation.ts
|
|
2728
|
+
function resolveValue(resolver, options) {
|
|
2729
|
+
if (typeof resolver === "function") {
|
|
2730
|
+
return resolver(options);
|
|
2731
|
+
}
|
|
2732
|
+
return resolver;
|
|
2733
|
+
}
|
|
2734
|
+
function createInvalidDocumentBackupPath(targetPath) {
|
|
2735
|
+
const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
|
|
2736
|
+
return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
|
|
2737
|
+
}
|
|
2738
|
+
async function backupInvalidDocument(fs2, targetPath, content) {
|
|
2739
|
+
const backupPath = createInvalidDocumentBackupPath(targetPath);
|
|
2740
|
+
await fs2.writeFile(backupPath, content, { encoding: "utf8" });
|
|
2741
|
+
}
|
|
2742
|
+
function describeMutation(kind, targetPath) {
|
|
2743
|
+
const displayPath = targetPath ?? "target";
|
|
2744
|
+
switch (kind) {
|
|
2745
|
+
case "ensureDirectory":
|
|
2746
|
+
return `Create ${displayPath}`;
|
|
2747
|
+
case "removeDirectory":
|
|
2748
|
+
return `Remove directory ${displayPath}`;
|
|
2749
|
+
case "backup":
|
|
2750
|
+
return `Backup ${displayPath}`;
|
|
2751
|
+
case "templateWrite":
|
|
2752
|
+
return `Write ${displayPath}`;
|
|
2753
|
+
case "chmod":
|
|
2754
|
+
return `Set permissions on ${displayPath}`;
|
|
2755
|
+
case "removeFile":
|
|
2756
|
+
return `Remove ${displayPath}`;
|
|
2757
|
+
case "configMerge":
|
|
2758
|
+
case "configPrune":
|
|
2759
|
+
case "configTransform":
|
|
2760
|
+
case "templateMergeToml":
|
|
2761
|
+
case "templateMergeJson":
|
|
2762
|
+
return `Update ${displayPath}`;
|
|
2763
|
+
default:
|
|
2764
|
+
return "Operation";
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
function pruneKeysByPrefix(table, prefix) {
|
|
2768
|
+
const result = {};
|
|
2769
|
+
for (const [key, value] of Object.entries(table)) {
|
|
2770
|
+
if (!key.startsWith(prefix)) {
|
|
2771
|
+
result[key] = value;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return result;
|
|
2775
|
+
}
|
|
2776
|
+
function isConfigObject3(value) {
|
|
2777
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2778
|
+
}
|
|
2779
|
+
function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
|
|
2780
|
+
const result = { ...base };
|
|
2781
|
+
const prefixMap = pruneByPrefix ?? {};
|
|
2782
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
2783
|
+
const current = result[key];
|
|
2784
|
+
const prefix = prefixMap[key];
|
|
2785
|
+
if (isConfigObject3(current) && isConfigObject3(value)) {
|
|
2786
|
+
if (prefix) {
|
|
2787
|
+
const pruned = pruneKeysByPrefix(current, prefix);
|
|
2788
|
+
result[key] = { ...pruned, ...value };
|
|
2789
|
+
} else {
|
|
2790
|
+
result[key] = mergeWithPruneByPrefix(
|
|
2791
|
+
current,
|
|
2792
|
+
value,
|
|
2793
|
+
prefixMap
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
continue;
|
|
2797
|
+
}
|
|
2798
|
+
result[key] = value;
|
|
2799
|
+
}
|
|
2800
|
+
return result;
|
|
2801
|
+
}
|
|
2802
|
+
async function applyMutation(mutation, context, options) {
|
|
2803
|
+
switch (mutation.kind) {
|
|
2804
|
+
case "ensureDirectory":
|
|
2805
|
+
return applyEnsureDirectory(mutation, context, options);
|
|
2806
|
+
case "removeDirectory":
|
|
2807
|
+
return applyRemoveDirectory(mutation, context, options);
|
|
2808
|
+
case "removeFile":
|
|
2809
|
+
return applyRemoveFile(mutation, context, options);
|
|
2810
|
+
case "chmod":
|
|
2811
|
+
return applyChmod(mutation, context, options);
|
|
2812
|
+
case "backup":
|
|
2813
|
+
return applyBackup(mutation, context, options);
|
|
2814
|
+
case "configMerge":
|
|
2815
|
+
return applyConfigMerge(mutation, context, options);
|
|
2816
|
+
case "configPrune":
|
|
2817
|
+
return applyConfigPrune(mutation, context, options);
|
|
2818
|
+
case "configTransform":
|
|
2819
|
+
return applyConfigTransform(mutation, context, options);
|
|
2820
|
+
case "templateWrite":
|
|
2821
|
+
return applyTemplateWrite(mutation, context, options);
|
|
2822
|
+
case "templateMergeToml":
|
|
2823
|
+
return applyTemplateMerge(mutation, context, options, "toml");
|
|
2824
|
+
case "templateMergeJson":
|
|
2825
|
+
return applyTemplateMerge(mutation, context, options, "json");
|
|
2826
|
+
default: {
|
|
2827
|
+
const never = mutation;
|
|
2828
|
+
throw new Error(`Unknown mutation kind: ${never.kind}`);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
async function applyEnsureDirectory(mutation, context, options) {
|
|
2833
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
2834
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2835
|
+
const details = {
|
|
2836
|
+
kind: mutation.kind,
|
|
2837
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2838
|
+
targetPath
|
|
2839
|
+
};
|
|
2840
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
2841
|
+
if (!context.dryRun) {
|
|
2842
|
+
await context.fs.mkdir(targetPath, { recursive: true });
|
|
2843
|
+
}
|
|
2844
|
+
return {
|
|
2845
|
+
outcome: {
|
|
2846
|
+
changed: !existed,
|
|
2847
|
+
effect: "mkdir",
|
|
2848
|
+
detail: existed ? "noop" : "create"
|
|
2849
|
+
},
|
|
2850
|
+
details
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
async function applyRemoveDirectory(mutation, context, options) {
|
|
2854
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
2855
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2856
|
+
const details = {
|
|
2857
|
+
kind: mutation.kind,
|
|
2858
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2859
|
+
targetPath
|
|
2860
|
+
};
|
|
2861
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
2862
|
+
if (!existed) {
|
|
2863
|
+
return {
|
|
2864
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2865
|
+
details
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
if (typeof context.fs.rm !== "function") {
|
|
2869
|
+
return {
|
|
2870
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2871
|
+
details
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
if (mutation.force) {
|
|
2875
|
+
if (!context.dryRun) {
|
|
2876
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
2877
|
+
}
|
|
2878
|
+
return {
|
|
2879
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2880
|
+
details
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
const entries = await context.fs.readdir(targetPath);
|
|
2884
|
+
if (entries.length > 0) {
|
|
2885
|
+
return {
|
|
2886
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2887
|
+
details
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
if (!context.dryRun) {
|
|
2891
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
2892
|
+
}
|
|
2893
|
+
return {
|
|
2894
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2895
|
+
details
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
async function applyRemoveFile(mutation, context, options) {
|
|
2899
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2900
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2901
|
+
const details = {
|
|
2902
|
+
kind: mutation.kind,
|
|
2903
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2904
|
+
targetPath
|
|
2905
|
+
};
|
|
2906
|
+
try {
|
|
2907
|
+
const content = await context.fs.readFile(targetPath, "utf8");
|
|
2908
|
+
const trimmed = content.trim();
|
|
2909
|
+
if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
|
|
2910
|
+
return {
|
|
2911
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2912
|
+
details
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
if (mutation.whenEmpty && trimmed.length > 0) {
|
|
2916
|
+
return {
|
|
2917
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2918
|
+
details
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
if (!context.dryRun) {
|
|
2922
|
+
await context.fs.unlink(targetPath);
|
|
2923
|
+
}
|
|
2924
|
+
return {
|
|
2925
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2926
|
+
details
|
|
2927
|
+
};
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
if (isNotFound(error)) {
|
|
2930
|
+
return {
|
|
2931
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2932
|
+
details
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
throw error;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
async function applyChmod(mutation, context, options) {
|
|
2939
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2940
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2941
|
+
const details = {
|
|
2942
|
+
kind: mutation.kind,
|
|
2943
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2944
|
+
targetPath
|
|
2945
|
+
};
|
|
2946
|
+
if (typeof context.fs.chmod !== "function") {
|
|
2947
|
+
return {
|
|
2948
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2949
|
+
details
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
try {
|
|
2953
|
+
const stat = await context.fs.stat(targetPath);
|
|
2954
|
+
const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
|
|
2955
|
+
if (currentMode === mutation.mode) {
|
|
2956
|
+
return {
|
|
2957
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2958
|
+
details
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
if (!context.dryRun) {
|
|
2962
|
+
await context.fs.chmod(targetPath, mutation.mode);
|
|
2963
|
+
}
|
|
2964
|
+
return {
|
|
2965
|
+
outcome: { changed: true, effect: "chmod", detail: "update" },
|
|
2966
|
+
details
|
|
2967
|
+
};
|
|
2968
|
+
} catch (error) {
|
|
2969
|
+
if (isNotFound(error)) {
|
|
2970
|
+
return {
|
|
2971
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2972
|
+
details
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
throw error;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
async function applyBackup(mutation, context, options) {
|
|
2979
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2980
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2981
|
+
const details = {
|
|
2982
|
+
kind: mutation.kind,
|
|
2983
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2984
|
+
targetPath
|
|
2985
|
+
};
|
|
2986
|
+
const content = await readFileIfExists(context.fs, targetPath);
|
|
2987
|
+
if (content === null) {
|
|
2988
|
+
return {
|
|
2989
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2990
|
+
details
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
if (!context.dryRun) {
|
|
2994
|
+
const backupPath = `${targetPath}.backup-${createTimestamp()}`;
|
|
2995
|
+
await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
2996
|
+
}
|
|
2997
|
+
return {
|
|
2998
|
+
outcome: { changed: true, effect: "copy", detail: "backup" },
|
|
2999
|
+
details
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
async function applyConfigMerge(mutation, context, options) {
|
|
3003
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
3004
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
3005
|
+
const details = {
|
|
3006
|
+
kind: mutation.kind,
|
|
3007
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
3008
|
+
targetPath
|
|
3009
|
+
};
|
|
3010
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
3011
|
+
if (!formatName) {
|
|
3012
|
+
throw new Error(
|
|
3013
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
const format = getConfigFormat(formatName);
|
|
3017
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
3018
|
+
let current;
|
|
3019
|
+
try {
|
|
3020
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
3021
|
+
} catch {
|
|
3022
|
+
if (rawContent !== null) {
|
|
3023
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
3024
|
+
}
|
|
3025
|
+
current = {};
|
|
3026
|
+
}
|
|
3027
|
+
const value = resolveValue(mutation.value, options);
|
|
3028
|
+
let merged;
|
|
3029
|
+
if (mutation.pruneByPrefix) {
|
|
3030
|
+
merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
|
|
3031
|
+
} else {
|
|
3032
|
+
merged = format.merge(current, value);
|
|
3033
|
+
}
|
|
3034
|
+
const serialized = format.serialize(merged);
|
|
3035
|
+
const changed = serialized !== rawContent;
|
|
3036
|
+
if (changed && !context.dryRun) {
|
|
3037
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
3038
|
+
}
|
|
3039
|
+
return {
|
|
3040
|
+
outcome: {
|
|
3041
|
+
changed,
|
|
3042
|
+
effect: changed ? "write" : "none",
|
|
3043
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
3044
|
+
},
|
|
3045
|
+
details
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
async function applyConfigPrune(mutation, context, options) {
|
|
3049
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
3050
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
3051
|
+
const details = {
|
|
3052
|
+
kind: mutation.kind,
|
|
3053
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
3054
|
+
targetPath
|
|
3055
|
+
};
|
|
3056
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
3057
|
+
if (rawContent === null) {
|
|
3058
|
+
return {
|
|
3059
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3060
|
+
details
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
3064
|
+
if (!formatName) {
|
|
3065
|
+
throw new Error(
|
|
3066
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
const format = getConfigFormat(formatName);
|
|
3070
|
+
let current;
|
|
3071
|
+
try {
|
|
3072
|
+
current = format.parse(rawContent);
|
|
3073
|
+
} catch {
|
|
3074
|
+
return {
|
|
3075
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3076
|
+
details
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
|
|
3080
|
+
return {
|
|
3081
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3082
|
+
details
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
const shape = resolveValue(mutation.shape, options);
|
|
3086
|
+
const { changed, result } = format.prune(current, shape);
|
|
3087
|
+
if (!changed) {
|
|
3088
|
+
return {
|
|
3089
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3090
|
+
details
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
if (Object.keys(result).length === 0) {
|
|
3094
|
+
if (!context.dryRun) {
|
|
3095
|
+
await context.fs.unlink(targetPath);
|
|
3096
|
+
}
|
|
3097
|
+
return {
|
|
3098
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
3099
|
+
details
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
const serialized = format.serialize(result);
|
|
3103
|
+
if (!context.dryRun) {
|
|
3104
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
3105
|
+
}
|
|
3106
|
+
return {
|
|
3107
|
+
outcome: { changed: true, effect: "write", detail: "update" },
|
|
3108
|
+
details
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
async function applyConfigTransform(mutation, context, options) {
|
|
3112
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
3113
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
3114
|
+
const details = {
|
|
3115
|
+
kind: mutation.kind,
|
|
3116
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
3117
|
+
targetPath
|
|
3118
|
+
};
|
|
3119
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
3120
|
+
if (!formatName) {
|
|
3121
|
+
throw new Error(
|
|
3122
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
const format = getConfigFormat(formatName);
|
|
3126
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
3127
|
+
let current;
|
|
3128
|
+
try {
|
|
3129
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
3130
|
+
} catch {
|
|
3131
|
+
if (rawContent !== null) {
|
|
3132
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
3133
|
+
}
|
|
3134
|
+
current = {};
|
|
3135
|
+
}
|
|
3136
|
+
const { content: transformed, changed } = mutation.transform(current, options);
|
|
3137
|
+
if (!changed) {
|
|
3138
|
+
return {
|
|
3139
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3140
|
+
details
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
if (transformed === null) {
|
|
3144
|
+
if (rawContent === null) {
|
|
3145
|
+
return {
|
|
3146
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
3147
|
+
details
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
if (!context.dryRun) {
|
|
3151
|
+
await context.fs.unlink(targetPath);
|
|
3152
|
+
}
|
|
3153
|
+
return {
|
|
3154
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
3155
|
+
details
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
const serialized = format.serialize(transformed);
|
|
3159
|
+
if (!context.dryRun) {
|
|
3160
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
3161
|
+
}
|
|
3162
|
+
return {
|
|
3163
|
+
outcome: {
|
|
3164
|
+
changed: true,
|
|
3165
|
+
effect: "write",
|
|
3166
|
+
detail: rawContent === null ? "create" : "update"
|
|
3167
|
+
},
|
|
3168
|
+
details
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
async function applyTemplateWrite(mutation, context, options) {
|
|
3172
|
+
if (!context.templates) {
|
|
3173
|
+
throw new Error(
|
|
3174
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
3178
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
3179
|
+
const details = {
|
|
3180
|
+
kind: mutation.kind,
|
|
3181
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
3182
|
+
targetPath
|
|
3183
|
+
};
|
|
3184
|
+
const template = await context.templates(mutation.templateId);
|
|
3185
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
3186
|
+
const rendered = Mustache.render(template, templateContext);
|
|
3187
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
3188
|
+
if (!context.dryRun) {
|
|
3189
|
+
await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
|
|
3190
|
+
}
|
|
3191
|
+
return {
|
|
3192
|
+
outcome: {
|
|
3193
|
+
changed: true,
|
|
3194
|
+
effect: "write",
|
|
3195
|
+
detail: existed ? "update" : "create"
|
|
3196
|
+
},
|
|
3197
|
+
details
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
async function applyTemplateMerge(mutation, context, options, formatName) {
|
|
3201
|
+
if (!context.templates) {
|
|
3202
|
+
throw new Error(
|
|
3203
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
3207
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
3208
|
+
const details = {
|
|
3209
|
+
kind: mutation.kind,
|
|
3210
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
3211
|
+
targetPath
|
|
3212
|
+
};
|
|
3213
|
+
const format = getConfigFormat(formatName);
|
|
3214
|
+
const template = await context.templates(mutation.templateId);
|
|
3215
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
3216
|
+
const rendered = Mustache.render(template, templateContext);
|
|
3217
|
+
let templateDoc;
|
|
3218
|
+
try {
|
|
3219
|
+
templateDoc = format.parse(rendered);
|
|
3220
|
+
} catch (error) {
|
|
3221
|
+
throw new Error(
|
|
3222
|
+
`Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`,
|
|
3223
|
+
{ cause: error }
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
3227
|
+
let current;
|
|
3228
|
+
try {
|
|
3229
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
3230
|
+
} catch {
|
|
3231
|
+
if (rawContent !== null) {
|
|
3232
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
3233
|
+
}
|
|
3234
|
+
current = {};
|
|
3235
|
+
}
|
|
3236
|
+
const merged = format.merge(current, templateDoc);
|
|
3237
|
+
const serialized = format.serialize(merged);
|
|
3238
|
+
const changed = serialized !== rawContent;
|
|
3239
|
+
if (changed && !context.dryRun) {
|
|
3240
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
3241
|
+
}
|
|
3242
|
+
return {
|
|
3243
|
+
outcome: {
|
|
3244
|
+
changed,
|
|
3245
|
+
effect: changed ? "write" : "none",
|
|
3246
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
3247
|
+
},
|
|
3248
|
+
details
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// packages/config-mutations/src/execution/run-mutations.ts
|
|
3253
|
+
async function runMutations(mutations, context, options) {
|
|
3254
|
+
const effects = [];
|
|
3255
|
+
let anyChanged = false;
|
|
3256
|
+
const resolverOptions = options ?? {};
|
|
3257
|
+
for (const mutation of mutations) {
|
|
3258
|
+
const { outcome } = await executeMutation(
|
|
3259
|
+
mutation,
|
|
3260
|
+
context,
|
|
3261
|
+
resolverOptions
|
|
3262
|
+
);
|
|
3263
|
+
effects.push(outcome);
|
|
3264
|
+
if (outcome.changed) {
|
|
3265
|
+
anyChanged = true;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
return {
|
|
3269
|
+
changed: anyChanged,
|
|
3270
|
+
effects
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
async function executeMutation(mutation, context, options) {
|
|
3274
|
+
context.observers?.onStart?.({
|
|
3275
|
+
kind: mutation.kind,
|
|
3276
|
+
label: mutation.label ?? mutation.kind,
|
|
3277
|
+
targetPath: void 0
|
|
3278
|
+
// Will be resolved during apply
|
|
3279
|
+
});
|
|
3280
|
+
try {
|
|
3281
|
+
const { outcome, details } = await applyMutation(mutation, context, options);
|
|
3282
|
+
context.observers?.onComplete?.(details, outcome);
|
|
3283
|
+
return { outcome, details };
|
|
3284
|
+
} catch (error) {
|
|
3285
|
+
context.observers?.onError?.(
|
|
3286
|
+
{
|
|
3287
|
+
kind: mutation.kind,
|
|
3288
|
+
label: mutation.label ?? mutation.kind,
|
|
3289
|
+
targetPath: void 0
|
|
3290
|
+
},
|
|
3291
|
+
error
|
|
3292
|
+
);
|
|
3293
|
+
throw error;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
// packages/config-mutations/src/template/render.ts
|
|
3298
|
+
import Mustache2 from "mustache";
|
|
3299
|
+
var originalEscape = Mustache2.escape;
|
|
3300
|
+
|
|
3301
|
+
// src/services/service-install.ts
|
|
3302
|
+
async function runServiceInstall(definition, context) {
|
|
3303
|
+
const checkContext = {
|
|
3304
|
+
isDryRun: context.isDryRun,
|
|
3305
|
+
runCommand: context.runCommand
|
|
3306
|
+
};
|
|
3307
|
+
let needsInstall = false;
|
|
3308
|
+
try {
|
|
3309
|
+
await definition.check.run(checkContext);
|
|
3310
|
+
context.logger(`${definition.summary} already installed.`);
|
|
3311
|
+
} catch (error) {
|
|
3312
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3313
|
+
context.logger(`${definition.summary} not detected: ${detail}`);
|
|
3314
|
+
needsInstall = true;
|
|
3315
|
+
}
|
|
3316
|
+
if (!needsInstall) {
|
|
3317
|
+
return false;
|
|
3318
|
+
}
|
|
3319
|
+
if (context.isDryRun) {
|
|
3320
|
+
logInstallDryRun(definition, context);
|
|
3321
|
+
return true;
|
|
3322
|
+
}
|
|
3323
|
+
const platformSteps = filterStepsByPlatform(definition.steps, context.platform);
|
|
3324
|
+
for (const step of platformSteps) {
|
|
3325
|
+
await runInstallStep(step, context);
|
|
3326
|
+
}
|
|
3327
|
+
await definition.check.run(checkContext);
|
|
3328
|
+
if (definition.postChecks) {
|
|
3329
|
+
for (const postCheck of definition.postChecks) {
|
|
3330
|
+
await postCheck.run(checkContext);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
context.logger(
|
|
3334
|
+
definition.successMessage ?? `${definition.summary} installed.`
|
|
3335
|
+
);
|
|
3336
|
+
return true;
|
|
3337
|
+
}
|
|
3338
|
+
function describeInstallCommand(step) {
|
|
3339
|
+
return `[${step.id}] ${formatCommand(step.command, step.args)}`;
|
|
3340
|
+
}
|
|
3341
|
+
function formatCommand(command, args) {
|
|
3342
|
+
return [command, ...args.map(quoteIfNeeded)].join(" ");
|
|
3343
|
+
}
|
|
3344
|
+
function quoteIfNeeded(value) {
|
|
3345
|
+
if (value.length === 0) {
|
|
3346
|
+
return '""';
|
|
3347
|
+
}
|
|
3348
|
+
if (value.includes(" ") || value.includes(" ") || value.includes("\n")) {
|
|
3349
|
+
return `"${value.replaceAll('"', '\\"')}"`;
|
|
3350
|
+
}
|
|
3351
|
+
return value;
|
|
3352
|
+
}
|
|
3353
|
+
function filterStepsByPlatform(steps, platform) {
|
|
3354
|
+
return steps.filter(
|
|
3355
|
+
(step) => !step.platforms || step.platforms.includes(platform)
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
function logInstallDryRun(definition, context) {
|
|
3359
|
+
context.logger(`Dry run: would install ${definition.summary}.`);
|
|
3360
|
+
const platformSteps = filterStepsByPlatform(definition.steps, context.platform);
|
|
3361
|
+
for (const step of platformSteps) {
|
|
3362
|
+
context.logger(`Dry run: ${describeInstallCommand(step)}`);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
async function runInstallStep(step, context) {
|
|
3366
|
+
context.logger(`Running ${describeInstallCommand(step)}`);
|
|
3367
|
+
const result = await context.runCommand(step.command, step.args);
|
|
3368
|
+
if (result.exitCode !== 0) {
|
|
3369
|
+
const stderr = result.stderr.trim();
|
|
3370
|
+
const suffix = stderr.length > 0 ? `: ${stderr}` : "";
|
|
3371
|
+
throw new Error(
|
|
3372
|
+
`${describeInstallCommand(step)} failed with exit code ${result.exitCode}${suffix}`
|
|
3373
|
+
);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
// src/providers/create-provider.ts
|
|
3378
|
+
var templateImports = {
|
|
3379
|
+
"python/env.hbs": () => Promise.resolve().then(() => __toESM(require_env(), 1)),
|
|
3380
|
+
"python/main.py.hbs": () => Promise.resolve().then(() => __toESM(require_main_py(), 1)),
|
|
3381
|
+
"python/requirements.txt.hbs": () => Promise.resolve().then(() => __toESM(require_requirements_txt(), 1)),
|
|
3382
|
+
"codex/config.toml.hbs": () => Promise.resolve().then(() => __toESM(require_config_toml(), 1))
|
|
3383
|
+
};
|
|
3384
|
+
async function loadTemplate(templateId) {
|
|
3385
|
+
const loader = templateImports[templateId];
|
|
3386
|
+
if (!loader) {
|
|
3387
|
+
throw new Error(`Template not found: ${templateId}`);
|
|
3388
|
+
}
|
|
3389
|
+
const module = await loader();
|
|
3390
|
+
return module.default;
|
|
3391
|
+
}
|
|
3392
|
+
function createProvider(opts) {
|
|
3393
|
+
const provider2 = {
|
|
3394
|
+
id: opts.id,
|
|
3395
|
+
summary: opts.summary,
|
|
3396
|
+
name: opts.name,
|
|
3397
|
+
aliases: opts.aliases,
|
|
3398
|
+
label: opts.label,
|
|
3399
|
+
branding: opts.branding,
|
|
3400
|
+
disabled: opts.disabled,
|
|
3401
|
+
supportsStdinPrompt: opts.supportsStdinPrompt,
|
|
3402
|
+
configurePrompts: opts.configurePrompts,
|
|
3403
|
+
postConfigureMessages: opts.postConfigureMessages,
|
|
3404
|
+
isolatedEnv: opts.isolatedEnv,
|
|
3405
|
+
async configure(context, runOptions) {
|
|
3406
|
+
await runMutations(opts.manifest.configure, {
|
|
3407
|
+
fs: context.fs,
|
|
3408
|
+
homeDir: context.env.homeDir,
|
|
3409
|
+
observers: runOptions?.observers,
|
|
3410
|
+
templates: loadTemplate,
|
|
3411
|
+
pathMapper: context.pathMapper
|
|
3412
|
+
}, context.options);
|
|
3413
|
+
context.command.flushDryRun({ emitIfEmpty: false });
|
|
3414
|
+
},
|
|
3415
|
+
async unconfigure(context, runOptions) {
|
|
3416
|
+
if (!opts.manifest.unconfigure) {
|
|
3417
|
+
return false;
|
|
3418
|
+
}
|
|
3419
|
+
const result = await runMutations(opts.manifest.unconfigure, {
|
|
3420
|
+
fs: context.fs,
|
|
3421
|
+
homeDir: context.env.homeDir,
|
|
3422
|
+
observers: runOptions?.observers,
|
|
3423
|
+
templates: loadTemplate,
|
|
3424
|
+
pathMapper: context.pathMapper
|
|
3425
|
+
}, context.options);
|
|
3426
|
+
context.command.flushDryRun({ emitIfEmpty: false });
|
|
3427
|
+
return result.changed;
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
if (opts.install) {
|
|
3431
|
+
provider2.install = createInstallRunner(opts.install);
|
|
3432
|
+
}
|
|
3433
|
+
if (opts.test) {
|
|
3434
|
+
provider2.test = opts.test;
|
|
3435
|
+
}
|
|
3436
|
+
if (opts.spawn) {
|
|
3437
|
+
provider2.spawn = opts.spawn;
|
|
3438
|
+
}
|
|
3439
|
+
return provider2;
|
|
3440
|
+
}
|
|
3441
|
+
function createInstallRunner(definition) {
|
|
3442
|
+
return async (context) => {
|
|
3443
|
+
await runServiceInstall(definition, {
|
|
3444
|
+
isDryRun: context.logger.context.dryRun,
|
|
3445
|
+
runCommand: context.command.runCommand,
|
|
3446
|
+
logger: (message) => context.logger.verbose(message),
|
|
3447
|
+
platform: context.env.platform
|
|
3448
|
+
});
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
// src/providers/poe-agent.ts
|
|
3453
|
+
function createEventQueue() {
|
|
3454
|
+
const values = [];
|
|
3455
|
+
const waiters = [];
|
|
3456
|
+
let completed = false;
|
|
3457
|
+
return {
|
|
3458
|
+
push(value) {
|
|
3459
|
+
if (completed) {
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
const waiter = waiters.shift();
|
|
3463
|
+
if (waiter) {
|
|
3464
|
+
waiter({ done: false, value });
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
values.push(value);
|
|
3468
|
+
},
|
|
3469
|
+
complete() {
|
|
3470
|
+
if (completed) {
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
completed = true;
|
|
3474
|
+
while (waiters.length > 0) {
|
|
3475
|
+
const waiter = waiters.shift();
|
|
3476
|
+
waiter?.({ done: true, value: void 0 });
|
|
3477
|
+
}
|
|
3478
|
+
},
|
|
3479
|
+
[Symbol.asyncIterator]() {
|
|
3480
|
+
return {
|
|
3481
|
+
next() {
|
|
3482
|
+
if (values.length > 0) {
|
|
3483
|
+
const value = values.shift();
|
|
3484
|
+
return Promise.resolve({ done: false, value });
|
|
3485
|
+
}
|
|
3486
|
+
if (completed) {
|
|
3487
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
3488
|
+
}
|
|
3489
|
+
return new Promise((resolve) => {
|
|
3490
|
+
waiters.push(resolve);
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
function createToolRenderState() {
|
|
3498
|
+
return {
|
|
3499
|
+
startedToolCalls: /* @__PURE__ */ new Set(),
|
|
3500
|
+
toolCallKinds: /* @__PURE__ */ new Map(),
|
|
3501
|
+
toolCallTitles: /* @__PURE__ */ new Map()
|
|
3502
|
+
};
|
|
3503
|
+
}
|
|
3504
|
+
function toErrorMessage2(value) {
|
|
3505
|
+
if (value instanceof Error) {
|
|
3506
|
+
return value.message;
|
|
3507
|
+
}
|
|
3508
|
+
if (typeof value === "string") {
|
|
3509
|
+
return value;
|
|
3510
|
+
}
|
|
3511
|
+
return String(value);
|
|
3512
|
+
}
|
|
3513
|
+
function toErrorStack(value) {
|
|
3514
|
+
if (value instanceof Error && typeof value.stack === "string" && value.stack.length > 0) {
|
|
3515
|
+
return value.stack;
|
|
3516
|
+
}
|
|
3517
|
+
return void 0;
|
|
3518
|
+
}
|
|
3519
|
+
function mapLegacyToolKind(kind) {
|
|
3520
|
+
if (kind === "read") {
|
|
3521
|
+
return "read";
|
|
3522
|
+
}
|
|
3523
|
+
if (kind === "execute") {
|
|
3524
|
+
return "execute";
|
|
3525
|
+
}
|
|
3526
|
+
if (kind === "edit" || kind === "delete" || kind === "move") {
|
|
3527
|
+
return "write";
|
|
3528
|
+
}
|
|
3529
|
+
if (kind === "other" || kind === "search" || kind === "think" || kind === "fetch" || kind === "switch_mode") {
|
|
3530
|
+
return "other";
|
|
3531
|
+
}
|
|
3532
|
+
return void 0;
|
|
3533
|
+
}
|
|
3534
|
+
function mapLegacyToolStatus(status) {
|
|
3535
|
+
if (status === "pending" || status === "in_progress" || status === "completed" || status === "failed" || status === "cancelled") {
|
|
3536
|
+
return status;
|
|
3537
|
+
}
|
|
3538
|
+
return void 0;
|
|
3539
|
+
}
|
|
3540
|
+
function normalizeSessionUpdate(update) {
|
|
3541
|
+
if (update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") {
|
|
3542
|
+
return {
|
|
3543
|
+
sessionUpdate: update.sessionUpdate,
|
|
3544
|
+
content: update.content
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
if (update.sessionUpdate === "tool_call") {
|
|
3548
|
+
const normalized2 = {
|
|
3549
|
+
sessionUpdate: "tool_call",
|
|
3550
|
+
toolCallId: update.toolCallId,
|
|
3551
|
+
title: update.title
|
|
3552
|
+
};
|
|
3553
|
+
const kind2 = mapLegacyToolKind(update.kind);
|
|
3554
|
+
if (kind2 !== void 0) {
|
|
3555
|
+
normalized2.kind = kind2;
|
|
3556
|
+
}
|
|
3557
|
+
const status2 = mapLegacyToolStatus(update.status);
|
|
3558
|
+
if (status2 !== void 0) {
|
|
3559
|
+
normalized2.status = status2;
|
|
3560
|
+
}
|
|
3561
|
+
if (update.rawInput !== void 0) {
|
|
3562
|
+
normalized2.rawInput = update.rawInput;
|
|
3563
|
+
}
|
|
3564
|
+
if (update._meta !== void 0) {
|
|
3565
|
+
normalized2._meta = update._meta;
|
|
3566
|
+
}
|
|
3567
|
+
return normalized2;
|
|
3568
|
+
}
|
|
3569
|
+
const normalized = {
|
|
3570
|
+
sessionUpdate: "tool_call_update",
|
|
3571
|
+
toolCallId: update.toolCallId
|
|
3572
|
+
};
|
|
3573
|
+
const kind = mapLegacyToolKind(update.kind);
|
|
3574
|
+
if (kind !== void 0) {
|
|
3575
|
+
normalized.kind = kind;
|
|
3576
|
+
}
|
|
3577
|
+
const status = mapLegacyToolStatus(update.status);
|
|
3578
|
+
if (status !== void 0) {
|
|
3579
|
+
normalized.status = status;
|
|
3580
|
+
}
|
|
3581
|
+
if (update.rawOutput !== void 0) {
|
|
3582
|
+
normalized.rawOutput = update.rawOutput;
|
|
3583
|
+
}
|
|
3584
|
+
if (update._meta !== void 0) {
|
|
3585
|
+
normalized._meta = update._meta;
|
|
3586
|
+
}
|
|
3587
|
+
return normalized;
|
|
3588
|
+
}
|
|
3589
|
+
function toRenderKind(kind) {
|
|
3590
|
+
if (kind === "execute") {
|
|
3591
|
+
return "exec";
|
|
3592
|
+
}
|
|
3593
|
+
if (kind === "write") {
|
|
3594
|
+
return "edit";
|
|
3595
|
+
}
|
|
3596
|
+
if (kind === "read") {
|
|
3597
|
+
return "read";
|
|
3598
|
+
}
|
|
3599
|
+
return "other";
|
|
3600
|
+
}
|
|
3601
|
+
function toToolOutput(value) {
|
|
3602
|
+
if (typeof value === "string") {
|
|
3603
|
+
return value;
|
|
3604
|
+
}
|
|
3605
|
+
if (value === void 0) {
|
|
3606
|
+
return "";
|
|
3607
|
+
}
|
|
3608
|
+
try {
|
|
3609
|
+
const serialized = JSON.stringify(value);
|
|
3610
|
+
if (typeof serialized === "string") {
|
|
3611
|
+
return serialized;
|
|
3612
|
+
}
|
|
3613
|
+
} catch {
|
|
3614
|
+
}
|
|
3615
|
+
return String(value);
|
|
3616
|
+
}
|
|
3617
|
+
function toPromptText(prompt) {
|
|
3618
|
+
const lines = [];
|
|
3619
|
+
for (const block of prompt) {
|
|
3620
|
+
if (block.type === "text") {
|
|
3621
|
+
lines.push(block.text);
|
|
3622
|
+
continue;
|
|
3623
|
+
}
|
|
3624
|
+
if (block.type === "resource_link") {
|
|
3625
|
+
lines.push(`${block.name}: ${block.uri}`);
|
|
3626
|
+
continue;
|
|
3627
|
+
}
|
|
3628
|
+
if (block.type === "resource") {
|
|
3629
|
+
if ("text" in block.resource) {
|
|
3630
|
+
lines.push(block.resource.text);
|
|
3631
|
+
}
|
|
3632
|
+
continue;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return lines.join("\n");
|
|
3636
|
+
}
|
|
3637
|
+
function toEventsFromSessionUpdate(notification, state) {
|
|
3638
|
+
const update = notification.params.update;
|
|
3639
|
+
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
|
|
3640
|
+
return [{ event: "agent_message", text: update.content.text }];
|
|
3641
|
+
}
|
|
3642
|
+
if (update.sessionUpdate === "agent_thought_chunk" && update.content.type === "text") {
|
|
3643
|
+
return [{ event: "reasoning", text: update.content.text }];
|
|
3644
|
+
}
|
|
3645
|
+
if (update.sessionUpdate === "usage_update") {
|
|
3646
|
+
const cachedTokens = Math.max(0, update.size - update.used);
|
|
3647
|
+
const usage = {
|
|
3648
|
+
event: "usage",
|
|
3649
|
+
inputTokens: update.used,
|
|
3650
|
+
outputTokens: 0
|
|
3651
|
+
};
|
|
3652
|
+
if (cachedTokens > 0) {
|
|
3653
|
+
usage.cachedTokens = cachedTokens;
|
|
3654
|
+
}
|
|
3655
|
+
if (update.cost && update.cost.currency === "USD") {
|
|
3656
|
+
usage.costUsd = update.cost.amount;
|
|
3657
|
+
}
|
|
3658
|
+
return [usage];
|
|
3659
|
+
}
|
|
3660
|
+
if (update.sessionUpdate === "tool_call") {
|
|
3661
|
+
const renderKind = toRenderKind(update.kind);
|
|
3662
|
+
state.toolCallKinds.set(update.toolCallId, renderKind);
|
|
3663
|
+
state.toolCallTitles.set(update.toolCallId, update.title);
|
|
3664
|
+
if (state.startedToolCalls.has(update.toolCallId)) {
|
|
3665
|
+
return [];
|
|
3666
|
+
}
|
|
3667
|
+
state.startedToolCalls.add(update.toolCallId);
|
|
3668
|
+
return [{
|
|
3669
|
+
event: "tool_start",
|
|
3670
|
+
kind: renderKind,
|
|
3671
|
+
title: update.title,
|
|
3672
|
+
id: update.toolCallId
|
|
3673
|
+
}];
|
|
3674
|
+
}
|
|
3675
|
+
if (update.sessionUpdate === "tool_call_update") {
|
|
3676
|
+
const renderKind = toRenderKind(update.kind ?? void 0) || state.toolCallKinds.get(update.toolCallId) || "other";
|
|
3677
|
+
state.toolCallKinds.set(update.toolCallId, renderKind);
|
|
3678
|
+
const events = [];
|
|
3679
|
+
const toolTitle = state.toolCallTitles.get(update.toolCallId) ?? update.toolCallId;
|
|
3680
|
+
const status = update.status;
|
|
3681
|
+
const shouldStart = !state.startedToolCalls.has(update.toolCallId) && (status === "pending" || status === "in_progress");
|
|
3682
|
+
if (shouldStart) {
|
|
3683
|
+
state.startedToolCalls.add(update.toolCallId);
|
|
3684
|
+
events.push({
|
|
3685
|
+
event: "tool_start",
|
|
3686
|
+
kind: renderKind,
|
|
3687
|
+
title: toolTitle,
|
|
3688
|
+
id: update.toolCallId
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
3692
|
+
if (!state.startedToolCalls.has(update.toolCallId)) {
|
|
3693
|
+
state.startedToolCalls.add(update.toolCallId);
|
|
3694
|
+
events.push({
|
|
3695
|
+
event: "tool_start",
|
|
3696
|
+
kind: renderKind,
|
|
3697
|
+
title: toolTitle,
|
|
3698
|
+
id: update.toolCallId
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
events.push({
|
|
3702
|
+
event: "tool_complete",
|
|
3703
|
+
kind: renderKind,
|
|
3704
|
+
path: toToolOutput(update.rawOutput),
|
|
3705
|
+
id: update.toolCallId
|
|
3706
|
+
});
|
|
3707
|
+
}
|
|
3708
|
+
return events;
|
|
3709
|
+
}
|
|
3710
|
+
return [];
|
|
3711
|
+
}
|
|
3712
|
+
function emitEvent(callback, event) {
|
|
3713
|
+
if (!callback) {
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
callback(event);
|
|
3717
|
+
}
|
|
3718
|
+
function createInMemoryAcpTransport(options) {
|
|
3719
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
3720
|
+
const notificationHandlers = /* @__PURE__ */ new Map();
|
|
3721
|
+
const requestHandlers = /* @__PURE__ */ new Map();
|
|
3722
|
+
let sessionCounter = 0;
|
|
3723
|
+
let closed = false;
|
|
3724
|
+
let resolveClosed;
|
|
3725
|
+
const closedPromise = new Promise((resolve) => {
|
|
3726
|
+
resolveClosed = resolve;
|
|
3727
|
+
});
|
|
3728
|
+
const closeTransport = (reason) => {
|
|
3729
|
+
if (closed) {
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
closed = true;
|
|
3733
|
+
const entries = Array.from(sessions.values());
|
|
3734
|
+
sessions.clear();
|
|
3735
|
+
void Promise.all(entries.map(async (session) => {
|
|
3736
|
+
await session.dispose();
|
|
3737
|
+
})).finally(() => {
|
|
3738
|
+
resolveClosed?.({
|
|
3739
|
+
code: 0,
|
|
3740
|
+
signal: null,
|
|
3741
|
+
reason,
|
|
3742
|
+
stderr: ""
|
|
3743
|
+
});
|
|
3744
|
+
});
|
|
3745
|
+
};
|
|
3746
|
+
return {
|
|
3747
|
+
closed: closedPromise,
|
|
3748
|
+
async sendRequest(method, params) {
|
|
3749
|
+
if (method === "initialize") {
|
|
3750
|
+
const request = params;
|
|
3751
|
+
const response = {
|
|
3752
|
+
protocolVersion: request.protocolVersion ?? 1,
|
|
3753
|
+
agentInfo: { name: "poe-agent", version: "0.0.1" },
|
|
3754
|
+
agentCapabilities: {
|
|
3755
|
+
sessionCapabilities: {},
|
|
3756
|
+
promptCapabilities: {}
|
|
3757
|
+
}
|
|
3758
|
+
};
|
|
3759
|
+
return response;
|
|
3760
|
+
}
|
|
3761
|
+
if (method === "session/new") {
|
|
3762
|
+
const request = params;
|
|
3763
|
+
const { createAgentSession: createAgentSession2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
3764
|
+
const session = await createAgentSession2({
|
|
3765
|
+
model: options.model,
|
|
3766
|
+
cwd: request.cwd || options.cwd
|
|
3767
|
+
});
|
|
3768
|
+
const sessionId = `poe-agent-session-${sessionCounter + 1}`;
|
|
3769
|
+
sessionCounter += 1;
|
|
3770
|
+
sessions.set(sessionId, session);
|
|
3771
|
+
const response = { sessionId };
|
|
3772
|
+
return response;
|
|
3773
|
+
}
|
|
3774
|
+
if (method === "session/prompt") {
|
|
3775
|
+
const request = params;
|
|
3776
|
+
const session = sessions.get(request.sessionId);
|
|
3777
|
+
if (!session) {
|
|
3778
|
+
throw new Error(`Unknown session "${request.sessionId}".`);
|
|
3779
|
+
}
|
|
3780
|
+
const promptText = toPromptText(request.prompt);
|
|
3781
|
+
await session.sendMessage(promptText, {
|
|
3782
|
+
onSessionUpdate: (legacyUpdate) => {
|
|
3783
|
+
const normalizedUpdate = normalizeSessionUpdate(legacyUpdate);
|
|
3784
|
+
const handlers2 = notificationHandlers.get("session/update");
|
|
3785
|
+
if (!handlers2 || handlers2.length === 0) {
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
const notification = {
|
|
3789
|
+
sessionId: request.sessionId,
|
|
3790
|
+
update: normalizedUpdate
|
|
3791
|
+
};
|
|
3792
|
+
for (const handler of handlers2) {
|
|
3793
|
+
void handler(notification, { method: "session/update" });
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
const response = { stopReason: "completed" };
|
|
3798
|
+
return response;
|
|
3799
|
+
}
|
|
3800
|
+
const handlers = requestHandlers.get(method);
|
|
3801
|
+
if (handlers && handlers.length > 0) {
|
|
3802
|
+
const result = handlers[0](params, { id: null, method });
|
|
3803
|
+
return await Promise.resolve(result);
|
|
3804
|
+
}
|
|
3805
|
+
throw new Error(`Unsupported ACP request method "${method}".`);
|
|
3806
|
+
},
|
|
3807
|
+
sendNotification(method, params) {
|
|
3808
|
+
if (method === "session/cancel") {
|
|
3809
|
+
const sessionId = params?.sessionId;
|
|
3810
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
3811
|
+
const session = sessions.get(sessionId);
|
|
3812
|
+
sessions.delete(sessionId);
|
|
3813
|
+
void session?.dispose();
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
},
|
|
3817
|
+
onRequest(method, handler) {
|
|
3818
|
+
const current = requestHandlers.get(method) ?? [];
|
|
3819
|
+
requestHandlers.set(method, [...current, handler]);
|
|
3820
|
+
},
|
|
3821
|
+
onNotification(method, handler) {
|
|
3822
|
+
const current = notificationHandlers.get(method) ?? [];
|
|
3823
|
+
notificationHandlers.set(method, [...current, handler]);
|
|
3824
|
+
},
|
|
3825
|
+
dispose(reason) {
|
|
3826
|
+
closeTransport(reason ?? new Error("ACP in-memory transport disposed"));
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
async function runPoeAgentAcpLifecycle(options) {
|
|
3831
|
+
const sessionUpdates = [];
|
|
3832
|
+
const toolState = createToolRenderState();
|
|
3833
|
+
let sessionId = "";
|
|
3834
|
+
let assistantText = "";
|
|
3835
|
+
const transport = createInMemoryAcpTransport({
|
|
3836
|
+
model: options.model,
|
|
3837
|
+
cwd: options.cwd
|
|
3838
|
+
});
|
|
3839
|
+
const client = new AcpClient({ transport });
|
|
3840
|
+
try {
|
|
3841
|
+
await client.initialize();
|
|
3842
|
+
const session = await client.newSession(options.cwd, []);
|
|
3843
|
+
sessionId = session.sessionId;
|
|
3844
|
+
emitEvent(options.onEvent, {
|
|
3845
|
+
event: "session_start",
|
|
3846
|
+
threadId: sessionId
|
|
3847
|
+
});
|
|
3848
|
+
const turn = client.prompt(sessionId, [{ type: "text", text: options.prompt }]);
|
|
3849
|
+
for await (const notification of turn) {
|
|
3850
|
+
sessionUpdates.push(notification);
|
|
3851
|
+
const update = notification.params.update;
|
|
3852
|
+
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
|
|
3853
|
+
assistantText += update.content.text;
|
|
3854
|
+
}
|
|
3855
|
+
for (const event of toEventsFromSessionUpdate(notification, toolState)) {
|
|
3856
|
+
emitEvent(options.onEvent, event);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
const promptResponse = await turn.response;
|
|
3860
|
+
await generateRunReportFromSessionUpdateStream(sessionUpdates, {
|
|
3861
|
+
runId: sessionId,
|
|
3862
|
+
exitStatus: promptResponse.stopReason === "completed" ? "success" : "failed"
|
|
3863
|
+
});
|
|
3864
|
+
return {
|
|
3865
|
+
stdout: assistantText.length > 0 ? `${assistantText}
|
|
3866
|
+
` : "",
|
|
3867
|
+
stderr: "",
|
|
3868
|
+
exitCode: promptResponse.stopReason === "completed" ? 0 : 1,
|
|
3869
|
+
threadId: sessionId,
|
|
3870
|
+
sessionId
|
|
3871
|
+
};
|
|
3872
|
+
} catch (error) {
|
|
3873
|
+
emitEvent(options.onEvent, {
|
|
3874
|
+
event: "error",
|
|
3875
|
+
message: toErrorMessage2(error),
|
|
3876
|
+
...toErrorStack(error) ? { stack: toErrorStack(error) } : {}
|
|
3877
|
+
});
|
|
3878
|
+
throw error;
|
|
3879
|
+
} finally {
|
|
3880
|
+
await client.dispose();
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
function spawnPoeAgentWithAcp(options) {
|
|
3884
|
+
const queue = createEventQueue();
|
|
3885
|
+
const model = options.model ?? DEFAULT_FRONTIER_MODEL;
|
|
3886
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3887
|
+
const done = runPoeAgentAcpLifecycle({
|
|
3888
|
+
prompt: options.prompt,
|
|
3889
|
+
model,
|
|
3890
|
+
cwd,
|
|
3891
|
+
onEvent: (event) => {
|
|
3892
|
+
queue.push(event);
|
|
3893
|
+
}
|
|
3894
|
+
}).finally(() => {
|
|
3895
|
+
queue.complete();
|
|
3896
|
+
});
|
|
3897
|
+
return {
|
|
3898
|
+
events: queue,
|
|
3899
|
+
done
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
var poeAgentService = createProvider({
|
|
3903
|
+
id: "poe-agent",
|
|
3904
|
+
name: "poe-agent",
|
|
3905
|
+
label: "Poe Agent",
|
|
3906
|
+
summary: "Run one-shot prompts with the built-in Poe agent runtime.",
|
|
3907
|
+
supportsStdinPrompt: true,
|
|
3908
|
+
manifest: {
|
|
3909
|
+
configure: []
|
|
3910
|
+
},
|
|
3911
|
+
async spawn(context, options) {
|
|
3912
|
+
const result = await runPoeAgentAcpLifecycle({
|
|
3913
|
+
prompt: options.prompt,
|
|
3914
|
+
model: options.model ?? DEFAULT_FRONTIER_MODEL,
|
|
3915
|
+
cwd: options.cwd ?? context.env.cwd
|
|
3916
|
+
});
|
|
3917
|
+
return {
|
|
3918
|
+
stdout: result.stdout,
|
|
3919
|
+
stderr: result.stderr,
|
|
3920
|
+
exitCode: result.exitCode
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
});
|
|
3924
|
+
var provider = poeAgentService;
|
|
3925
|
+
export {
|
|
3926
|
+
poeAgentService,
|
|
3927
|
+
provider,
|
|
3928
|
+
spawnPoeAgentWithAcp
|
|
3929
|
+
};
|
|
3930
|
+
//# sourceMappingURL=poe-agent.js.map
|