@agentclientprotocol/claude-agent-acp 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools.js ADDED
@@ -0,0 +1,569 @@
1
+ import path from "node:path";
2
+ /**
3
+ * Convert an absolute file path to a project-relative path for display.
4
+ * Returns the original path if it's outside the project directory or if no cwd is provided.
5
+ */
6
+ export function toDisplayPath(filePath, cwd) {
7
+ if (!cwd)
8
+ return filePath;
9
+ const resolvedCwd = path.resolve(cwd);
10
+ const resolvedFile = path.resolve(filePath);
11
+ if (resolvedFile.startsWith(resolvedCwd + path.sep) || resolvedFile === resolvedCwd) {
12
+ return path.relative(resolvedCwd, resolvedFile);
13
+ }
14
+ return filePath;
15
+ }
16
+ export function toolInfoFromToolUse(toolUse, supportsTerminalOutput = false, cwd) {
17
+ const name = toolUse.name;
18
+ switch (name) {
19
+ case "Agent":
20
+ case "Task": {
21
+ const input = toolUse.input;
22
+ return {
23
+ title: input?.description ? input.description : "Task",
24
+ kind: "think",
25
+ content: input && "prompt" in input
26
+ ? [
27
+ {
28
+ type: "content",
29
+ content: { type: "text", text: input.prompt },
30
+ },
31
+ ]
32
+ : [],
33
+ };
34
+ }
35
+ case "Bash": {
36
+ const input = toolUse.input;
37
+ return {
38
+ title: input?.command ? input.command : "Terminal",
39
+ kind: "execute",
40
+ content: supportsTerminalOutput
41
+ ? [{ type: "terminal", terminalId: toolUse.id }]
42
+ : input && input.description
43
+ ? [
44
+ {
45
+ type: "content",
46
+ content: { type: "text", text: input.description },
47
+ },
48
+ ]
49
+ : [],
50
+ };
51
+ }
52
+ case "Read": {
53
+ const input = toolUse.input;
54
+ let limit = "";
55
+ if (input.limit && input.limit > 0) {
56
+ limit = " (" + (input.offset ?? 1) + " - " + ((input.offset ?? 1) + input.limit - 1) + ")";
57
+ }
58
+ else if (input.offset) {
59
+ limit = " (from line " + input.offset + ")";
60
+ }
61
+ const displayPath = input.file_path ? toDisplayPath(input.file_path, cwd) : "File";
62
+ return {
63
+ title: "Read " + displayPath + limit,
64
+ kind: "read",
65
+ locations: input.file_path
66
+ ? [
67
+ {
68
+ path: input.file_path,
69
+ line: input.offset ?? 1,
70
+ },
71
+ ]
72
+ : [],
73
+ content: [],
74
+ };
75
+ }
76
+ case "Write": {
77
+ const input = toolUse.input;
78
+ let content = [];
79
+ if (input && input.file_path) {
80
+ content = [
81
+ {
82
+ type: "diff",
83
+ path: input.file_path,
84
+ oldText: null,
85
+ newText: input.content,
86
+ },
87
+ ];
88
+ }
89
+ else if (input && input.content) {
90
+ content = [
91
+ {
92
+ type: "content",
93
+ content: { type: "text", text: input.content },
94
+ },
95
+ ];
96
+ }
97
+ const displayPath = input?.file_path ? toDisplayPath(input.file_path, cwd) : undefined;
98
+ return {
99
+ title: displayPath ? `Write ${displayPath}` : "Write",
100
+ kind: "edit",
101
+ content,
102
+ locations: input?.file_path ? [{ path: input.file_path }] : [],
103
+ };
104
+ }
105
+ case "Edit": {
106
+ const input = toolUse.input;
107
+ let content = [];
108
+ if (input && input.file_path && (input.old_string || input.new_string)) {
109
+ content = [
110
+ {
111
+ type: "diff",
112
+ path: input.file_path,
113
+ oldText: input.old_string || null,
114
+ newText: input.new_string ?? "",
115
+ },
116
+ ];
117
+ }
118
+ const displayPath = input?.file_path ? toDisplayPath(input.file_path, cwd) : undefined;
119
+ return {
120
+ title: displayPath ? `Edit ${displayPath}` : "Edit",
121
+ kind: "edit",
122
+ content,
123
+ locations: input?.file_path ? [{ path: input.file_path }] : [],
124
+ };
125
+ }
126
+ case "Glob": {
127
+ const input = toolUse.input;
128
+ let label = "Find";
129
+ if (input.path) {
130
+ label += ` \`${input.path}\``;
131
+ }
132
+ if (input.pattern) {
133
+ label += ` \`${input.pattern}\``;
134
+ }
135
+ return {
136
+ title: label,
137
+ kind: "search",
138
+ content: [],
139
+ locations: input.path ? [{ path: input.path }] : [],
140
+ };
141
+ }
142
+ case "Grep": {
143
+ const input = toolUse.input;
144
+ let label = "grep";
145
+ if (input["-i"]) {
146
+ label += " -i";
147
+ }
148
+ if (input["-n"]) {
149
+ label += " -n";
150
+ }
151
+ if (input["-A"] !== undefined) {
152
+ label += ` -A ${input["-A"]}`;
153
+ }
154
+ if (input["-B"] !== undefined) {
155
+ label += ` -B ${input["-B"]}`;
156
+ }
157
+ if (input["-C"] !== undefined) {
158
+ label += ` -C ${input["-C"]}`;
159
+ }
160
+ if (input.output_mode) {
161
+ switch (input.output_mode) {
162
+ case "files_with_matches":
163
+ label += " -l";
164
+ break;
165
+ case "count":
166
+ label += " -c";
167
+ break;
168
+ case "content":
169
+ default:
170
+ break;
171
+ }
172
+ }
173
+ if (input.head_limit !== undefined) {
174
+ label += ` | head -${input.head_limit}`;
175
+ }
176
+ if (input.glob) {
177
+ label += ` --include="${input.glob}"`;
178
+ }
179
+ if (input.type) {
180
+ label += ` --type=${input.type}`;
181
+ }
182
+ if (input.multiline) {
183
+ label += " -P";
184
+ }
185
+ if (input.pattern) {
186
+ label += ` "${input.pattern}"`;
187
+ }
188
+ if (input.path) {
189
+ label += ` ${input.path}`;
190
+ }
191
+ return {
192
+ title: label,
193
+ kind: "search",
194
+ content: [],
195
+ };
196
+ }
197
+ case "WebFetch": {
198
+ const input = toolUse.input;
199
+ return {
200
+ title: input?.url ? `Fetch ${input.url}` : "Fetch",
201
+ kind: "fetch",
202
+ content: input && input.prompt
203
+ ? [
204
+ {
205
+ type: "content",
206
+ content: { type: "text", text: input.prompt },
207
+ },
208
+ ]
209
+ : [],
210
+ };
211
+ }
212
+ case "WebSearch": {
213
+ const input = toolUse.input;
214
+ let label = `"${input.query}"`;
215
+ if (input.allowed_domains && input.allowed_domains.length > 0) {
216
+ label += ` (allowed: ${input.allowed_domains.join(", ")})`;
217
+ }
218
+ if (input.blocked_domains && input.blocked_domains.length > 0) {
219
+ label += ` (blocked: ${input.blocked_domains.join(", ")})`;
220
+ }
221
+ return {
222
+ title: label,
223
+ kind: "fetch",
224
+ content: [],
225
+ };
226
+ }
227
+ case "TodoWrite": {
228
+ const input = toolUse.input;
229
+ return {
230
+ title: Array.isArray(input?.todos)
231
+ ? `Update TODOs: ${input.todos.map((todo) => todo.content).join(", ")}`
232
+ : "Update TODOs",
233
+ kind: "think",
234
+ content: [],
235
+ };
236
+ }
237
+ case "ExitPlanMode": {
238
+ const planInput = toolUse.input;
239
+ return {
240
+ title: "Ready to code?",
241
+ kind: "switch_mode",
242
+ content: planInput?.plan
243
+ ? [{ type: "content", content: { type: "text", text: planInput.plan } }]
244
+ : [],
245
+ };
246
+ }
247
+ case "Other": {
248
+ const input = toolUse.input;
249
+ let output;
250
+ try {
251
+ output = JSON.stringify(input, null, 2);
252
+ }
253
+ catch {
254
+ output = typeof input === "string" ? input : "{}";
255
+ }
256
+ return {
257
+ title: name || "Unknown Tool",
258
+ kind: "other",
259
+ content: [
260
+ {
261
+ type: "content",
262
+ content: {
263
+ type: "text",
264
+ text: `\`\`\`json\n${output}\`\`\``,
265
+ },
266
+ },
267
+ ],
268
+ };
269
+ }
270
+ default:
271
+ return {
272
+ title: name || "Unknown Tool",
273
+ kind: "other",
274
+ content: [],
275
+ };
276
+ }
277
+ }
278
+ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOutput = false) {
279
+ if ("is_error" in toolResult &&
280
+ toolResult.is_error &&
281
+ toolResult.content &&
282
+ toolResult.content.length > 0) {
283
+ // Only return errors
284
+ return toAcpContentUpdate(toolResult.content, true);
285
+ }
286
+ switch (toolUse?.name) {
287
+ case "Read":
288
+ if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
289
+ return {
290
+ content: toolResult.content.map((content) => ({
291
+ type: "content",
292
+ content: content.type === "text"
293
+ ? {
294
+ type: "text",
295
+ text: markdownEscape(content.text),
296
+ }
297
+ : toAcpContentBlock(content, false),
298
+ })),
299
+ };
300
+ }
301
+ else if (typeof toolResult.content === "string" && toolResult.content.length > 0) {
302
+ return {
303
+ content: [
304
+ {
305
+ type: "content",
306
+ content: {
307
+ type: "text",
308
+ text: markdownEscape(toolResult.content),
309
+ },
310
+ },
311
+ ],
312
+ };
313
+ }
314
+ return {};
315
+ case "Bash": {
316
+ const result = toolResult.content;
317
+ const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
318
+ const isError = "is_error" in toolResult && toolResult.is_error;
319
+ // Extract output and exit code from either format:
320
+ // 1. BetaBashCodeExecutionResultBlock: { type: "bash_code_execution_result", stdout, stderr, return_code }
321
+ // 2. Plain string content from a regular tool_result
322
+ // 3. Array content (e.g. [{ type: "text", text: "..." }])
323
+ let output = "";
324
+ let exitCode = isError ? 1 : 0;
325
+ if (result &&
326
+ typeof result === "object" &&
327
+ "type" in result &&
328
+ result.type === "bash_code_execution_result") {
329
+ const bashResult = result;
330
+ output = [bashResult.stdout, bashResult.stderr].filter(Boolean).join("\n");
331
+ exitCode = bashResult.return_code;
332
+ }
333
+ else if (typeof result === "string") {
334
+ output = result;
335
+ }
336
+ else if (Array.isArray(result) &&
337
+ result.length > 0 &&
338
+ "text" in result[0] &&
339
+ typeof result[0].text === "string") {
340
+ output = result.map((c) => c.text).join("\n");
341
+ }
342
+ if (supportsTerminalOutput) {
343
+ return {
344
+ content: [{ type: "terminal", terminalId }],
345
+ _meta: {
346
+ terminal_info: {
347
+ terminal_id: terminalId,
348
+ },
349
+ terminal_output: {
350
+ terminal_id: terminalId,
351
+ data: output,
352
+ },
353
+ terminal_exit: {
354
+ terminal_id: terminalId,
355
+ exit_code: exitCode,
356
+ signal: null,
357
+ },
358
+ },
359
+ };
360
+ }
361
+ // Fallback: format output as a code block without terminal _meta
362
+ if (output.trim()) {
363
+ return {
364
+ content: [
365
+ {
366
+ type: "content",
367
+ content: {
368
+ type: "text",
369
+ text: `\`\`\`console\n${output.trimEnd()}\n\`\`\``,
370
+ },
371
+ },
372
+ ],
373
+ };
374
+ }
375
+ return {};
376
+ }
377
+ case "Edit": // Edit is handled in hooks
378
+ case "Write": {
379
+ return {};
380
+ }
381
+ case "ExitPlanMode": {
382
+ return { title: "Exited Plan Mode" };
383
+ }
384
+ default: {
385
+ return toAcpContentUpdate(toolResult.content, "is_error" in toolResult ? toolResult.is_error : false);
386
+ }
387
+ }
388
+ }
389
+ function toAcpContentUpdate(content, isError = false) {
390
+ if (Array.isArray(content) && content.length > 0) {
391
+ return {
392
+ content: content.map((c) => ({
393
+ type: "content",
394
+ content: toAcpContentBlock(c, isError),
395
+ })),
396
+ };
397
+ }
398
+ else if (typeof content === "object" && content !== null && "type" in content) {
399
+ return {
400
+ content: [
401
+ {
402
+ type: "content",
403
+ content: toAcpContentBlock(content, isError),
404
+ },
405
+ ],
406
+ };
407
+ }
408
+ else if (typeof content === "string" && content.length > 0) {
409
+ return {
410
+ content: [
411
+ {
412
+ type: "content",
413
+ content: {
414
+ type: "text",
415
+ text: isError ? `\`\`\`\n${content}\n\`\`\`` : content,
416
+ },
417
+ },
418
+ ],
419
+ };
420
+ }
421
+ return {};
422
+ }
423
+ function toAcpContentBlock(content, isError) {
424
+ const wrapText = (text) => ({
425
+ type: "text",
426
+ text: isError ? `\`\`\`\n${text}\n\`\`\`` : text,
427
+ });
428
+ switch (content.type) {
429
+ case "text":
430
+ return {
431
+ type: "text",
432
+ text: isError ? `\`\`\`\n${content.text}\n\`\`\`` : content.text,
433
+ };
434
+ case "image":
435
+ if (content.source.type === "base64") {
436
+ return {
437
+ type: "image",
438
+ data: content.source.data,
439
+ mimeType: content.source.media_type,
440
+ };
441
+ }
442
+ // URL and file-based images can't be converted to ACP format (requires data)
443
+ return wrapText(content.source.type === "url"
444
+ ? `[image: ${content.source.url}]`
445
+ : "[image: file reference]");
446
+ case "tool_reference":
447
+ return wrapText(`Tool: ${content.tool_name}`);
448
+ case "tool_search_tool_search_result":
449
+ return wrapText(`Tools found: ${content.tool_references.map((r) => r.tool_name).join(", ") || "none"}`);
450
+ case "tool_search_tool_result_error":
451
+ return wrapText(`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`);
452
+ case "web_search_result":
453
+ return wrapText(`${content.title} (${content.url})`);
454
+ case "web_search_tool_result_error":
455
+ return wrapText(`Error: ${content.error_code}`);
456
+ case "web_fetch_result":
457
+ return wrapText(`Fetched: ${content.url}`);
458
+ case "web_fetch_tool_result_error":
459
+ return wrapText(`Error: ${content.error_code}`);
460
+ case "code_execution_result":
461
+ return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
462
+ case "bash_code_execution_result":
463
+ return wrapText(`Output: ${content.stdout || content.stderr || ""}`);
464
+ case "code_execution_tool_result_error":
465
+ case "bash_code_execution_tool_result_error":
466
+ return wrapText(`Error: ${content.error_code}`);
467
+ case "text_editor_code_execution_view_result":
468
+ return wrapText(content.content);
469
+ case "text_editor_code_execution_create_result":
470
+ return wrapText(content.is_file_update ? "File updated" : "File created");
471
+ case "text_editor_code_execution_str_replace_result":
472
+ return wrapText(content.lines?.join("\n") || "");
473
+ case "text_editor_code_execution_tool_result_error":
474
+ return wrapText(`Error: ${content.error_code}${content.error_message ? ` - ${content.error_message}` : ""}`);
475
+ default:
476
+ return wrapText(JSON.stringify(content));
477
+ }
478
+ }
479
+ export function planEntries(input) {
480
+ return input.todos.map((input) => ({
481
+ content: input.content,
482
+ status: input.status,
483
+ priority: "medium",
484
+ }));
485
+ }
486
+ export function markdownEscape(text) {
487
+ let escape = "```";
488
+ for (const [m] of text.matchAll(/^```+/gm)) {
489
+ while (m.length >= escape.length) {
490
+ escape += "`";
491
+ }
492
+ }
493
+ return escape + "\n" + text + (text.endsWith("\n") ? "" : "\n") + escape;
494
+ }
495
+ /**
496
+ * Builds diff ToolUpdate content from the structured Edit toolResponse provided
497
+ * by the PostToolUse hook. Unlike parsing the plain unified diff string, this uses
498
+ * the pre-parsed structuredPatch which supports multiple replacement sites (replaceAll)
499
+ * and always includes context lines for better readability.
500
+ */
501
+ export function toolUpdateFromEditToolResponse(toolResponse) {
502
+ if (!toolResponse || typeof toolResponse !== "object")
503
+ return {};
504
+ const response = toolResponse;
505
+ if (!response.filePath || !Array.isArray(response.structuredPatch))
506
+ return {};
507
+ const content = [];
508
+ const locations = [];
509
+ for (const { lines, newStart } of response.structuredPatch) {
510
+ const oldText = [];
511
+ const newText = [];
512
+ for (const line of lines) {
513
+ if (line.startsWith("-")) {
514
+ oldText.push(line.slice(1));
515
+ }
516
+ else if (line.startsWith("+")) {
517
+ newText.push(line.slice(1));
518
+ }
519
+ else {
520
+ oldText.push(line.slice(1));
521
+ newText.push(line.slice(1));
522
+ }
523
+ }
524
+ if (oldText.length > 0 || newText.length > 0) {
525
+ locations.push({ path: response.filePath, line: newStart });
526
+ content.push({
527
+ type: "diff",
528
+ path: response.filePath,
529
+ oldText: oldText.join("\n") || null,
530
+ newText: newText.join("\n"),
531
+ });
532
+ }
533
+ }
534
+ const result = {};
535
+ if (content.length > 0)
536
+ result.content = content;
537
+ if (locations.length > 0)
538
+ result.locations = locations;
539
+ return result;
540
+ }
541
+ /* A global variable to store callbacks that should be executed when receiving hooks from Claude Code */
542
+ const toolUseCallbacks = {};
543
+ /* Setup callbacks that will be called when receiving hooks from Claude Code */
544
+ export const registerHookCallback = (toolUseID, { onPostToolUseHook, }) => {
545
+ toolUseCallbacks[toolUseID] = {
546
+ onPostToolUseHook,
547
+ };
548
+ };
549
+ /* A callback for Claude Code that is called when receiving a PostToolUse hook */
550
+ export const createPostToolUseHook = (logger = console, options) => async (input, toolUseID) => {
551
+ if (input.hook_event_name === "PostToolUse") {
552
+ // Handle EnterPlanMode tool - notify client of mode change after successful execution
553
+ if (input.tool_name === "EnterPlanMode" && options?.onEnterPlanMode) {
554
+ await options.onEnterPlanMode();
555
+ }
556
+ if (toolUseID) {
557
+ const onPostToolUseHook = toolUseCallbacks[toolUseID]?.onPostToolUseHook;
558
+ if (onPostToolUseHook) {
559
+ await onPostToolUseHook(toolUseID, input.tool_input, input.tool_response);
560
+ delete toolUseCallbacks[toolUseID]; // Cleanup after execution
561
+ }
562
+ else {
563
+ logger.error(`No onPostToolUseHook found for tool use ID: ${toolUseID}`);
564
+ delete toolUseCallbacks[toolUseID];
565
+ }
566
+ }
567
+ }
568
+ return { continue: true };
569
+ };
@@ -0,0 +1,19 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import { WritableStream, ReadableStream } from "node:stream/web";
3
+ import { Logger } from "./acp-agent.js";
4
+ import { ClaudeCodeSettings } from "./settings.js";
5
+ export declare class Pushable<T> implements AsyncIterable<T> {
6
+ private queue;
7
+ private resolvers;
8
+ private done;
9
+ push(item: T): void;
10
+ end(): void;
11
+ [Symbol.asyncIterator](): AsyncIterator<T>;
12
+ }
13
+ export declare function nodeToWebWritable(nodeStream: Writable): WritableStream<Uint8Array>;
14
+ export declare function nodeToWebReadable(nodeStream: Readable): ReadableStream<Uint8Array>;
15
+ export declare function unreachable(value: never, logger?: Logger): void;
16
+ export declare function sleep(time: number): Promise<void>;
17
+ export declare function loadManagedSettings(): ClaudeCodeSettings | null;
18
+ export declare function applyEnvironmentSettings(settings: ClaudeCodeSettings): void;
19
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAA0B,MAAM,eAAe,CAAC;AAG3E,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,IAAI,CAAS;IAErB,IAAI,CAAC,IAAI,EAAE,CAAC;IASZ,GAAG;IAQH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAgB3C;AAGD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAclF;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAUlF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,MAAgB,QAQjE;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD;AAED,wBAAgB,mBAAmB,IAAI,kBAAkB,GAAG,IAAI,CAM/D;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAM3E"}
package/dist/utils.js ADDED
@@ -0,0 +1,100 @@
1
+ // A pushable async iterable: allows you to push items and consume them with for-await.
2
+ import { WritableStream, ReadableStream } from "node:stream/web";
3
+ import { readFileSync } from "node:fs";
4
+ import { getManagedSettingsPath } from "./settings.js";
5
+ // Useful for bridging push-based and async-iterator-based code.
6
+ export class Pushable {
7
+ constructor() {
8
+ this.queue = [];
9
+ this.resolvers = [];
10
+ this.done = false;
11
+ }
12
+ push(item) {
13
+ if (this.resolvers.length > 0) {
14
+ const resolve = this.resolvers.shift();
15
+ resolve({ value: item, done: false });
16
+ }
17
+ else {
18
+ this.queue.push(item);
19
+ }
20
+ }
21
+ end() {
22
+ this.done = true;
23
+ while (this.resolvers.length > 0) {
24
+ const resolve = this.resolvers.shift();
25
+ resolve({ value: undefined, done: true });
26
+ }
27
+ }
28
+ [Symbol.asyncIterator]() {
29
+ return {
30
+ next: () => {
31
+ if (this.queue.length > 0) {
32
+ const value = this.queue.shift();
33
+ return Promise.resolve({ value, done: false });
34
+ }
35
+ if (this.done) {
36
+ return Promise.resolve({ value: undefined, done: true });
37
+ }
38
+ return new Promise((resolve) => {
39
+ this.resolvers.push(resolve);
40
+ });
41
+ },
42
+ };
43
+ }
44
+ }
45
+ // Helper to convert Node.js streams to Web Streams
46
+ export function nodeToWebWritable(nodeStream) {
47
+ return new WritableStream({
48
+ write(chunk) {
49
+ return new Promise((resolve, reject) => {
50
+ nodeStream.write(Buffer.from(chunk), (err) => {
51
+ if (err) {
52
+ reject(err);
53
+ }
54
+ else {
55
+ resolve();
56
+ }
57
+ });
58
+ });
59
+ },
60
+ });
61
+ }
62
+ export function nodeToWebReadable(nodeStream) {
63
+ return new ReadableStream({
64
+ start(controller) {
65
+ nodeStream.on("data", (chunk) => {
66
+ controller.enqueue(new Uint8Array(chunk));
67
+ });
68
+ nodeStream.on("end", () => controller.close());
69
+ nodeStream.on("error", (err) => controller.error(err));
70
+ },
71
+ });
72
+ }
73
+ export function unreachable(value, logger = console) {
74
+ let valueAsString;
75
+ try {
76
+ valueAsString = JSON.stringify(value);
77
+ }
78
+ catch {
79
+ valueAsString = value;
80
+ }
81
+ logger.error(`Unexpected case: ${valueAsString}`);
82
+ }
83
+ export function sleep(time) {
84
+ return new Promise((resolve) => setTimeout(resolve, time));
85
+ }
86
+ export function loadManagedSettings() {
87
+ try {
88
+ return JSON.parse(readFileSync(getManagedSettingsPath(), "utf8"));
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ export function applyEnvironmentSettings(settings) {
95
+ if (settings.env) {
96
+ for (const [key, value] of Object.entries(settings.env)) {
97
+ process.env[key] = value;
98
+ }
99
+ }
100
+ }