anyclaude-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/dist/agent.d.ts +110 -0
  4. package/dist/agent.js +897 -0
  5. package/dist/background/index.d.ts +3 -0
  6. package/dist/background/index.js +9 -0
  7. package/dist/background/manager.d.ts +32 -0
  8. package/dist/background/manager.js +108 -0
  9. package/dist/background/tools.d.ts +5 -0
  10. package/dist/background/tools.js +98 -0
  11. package/dist/background/worker.d.ts +19 -0
  12. package/dist/background/worker.js +30 -0
  13. package/dist/commands/builtins.d.ts +2 -0
  14. package/dist/commands/builtins.js +306 -0
  15. package/dist/commands/index.d.ts +21 -0
  16. package/dist/commands/index.js +56 -0
  17. package/dist/commands/types.d.ts +110 -0
  18. package/dist/commands/types.js +5 -0
  19. package/dist/compact.d.ts +22 -0
  20. package/dist/compact.js +67 -0
  21. package/dist/fs/dexie.d.ts +57 -0
  22. package/dist/fs/dexie.js +243 -0
  23. package/dist/fs/index.d.ts +4 -0
  24. package/dist/fs/index.js +13 -0
  25. package/dist/fs/linuxTree.d.ts +11 -0
  26. package/dist/fs/linuxTree.js +43 -0
  27. package/dist/fs/opfs.d.ts +23 -0
  28. package/dist/fs/opfs.js +112 -0
  29. package/dist/index.d.ts +26 -0
  30. package/dist/index.js +29 -0
  31. package/dist/llm/anthropic.d.ts +24 -0
  32. package/dist/llm/anthropic.js +280 -0
  33. package/dist/llm/index.d.ts +3 -0
  34. package/dist/llm/index.js +3 -0
  35. package/dist/llm/inlineTools.d.ts +11 -0
  36. package/dist/llm/inlineTools.js +72 -0
  37. package/dist/llm/openai.d.ts +29 -0
  38. package/dist/llm/openai.js +224 -0
  39. package/dist/llm/responses.d.ts +18 -0
  40. package/dist/llm/responses.js +256 -0
  41. package/dist/mcp/client.d.ts +20 -0
  42. package/dist/mcp/client.js +156 -0
  43. package/dist/mcp/index.d.ts +24 -0
  44. package/dist/mcp/index.js +157 -0
  45. package/dist/mcp/proxy.d.ts +3 -0
  46. package/dist/mcp/proxy.js +25 -0
  47. package/dist/mcp/sdkServer.d.ts +21 -0
  48. package/dist/mcp/sdkServer.js +28 -0
  49. package/dist/mcp/types.d.ts +92 -0
  50. package/dist/mcp/types.js +5 -0
  51. package/dist/memory/index.d.ts +4 -0
  52. package/dist/memory/index.js +5 -0
  53. package/dist/memory/render.d.ts +7 -0
  54. package/dist/memory/render.js +46 -0
  55. package/dist/memory/store.d.ts +20 -0
  56. package/dist/memory/store.js +79 -0
  57. package/dist/memory/tools.d.ts +5 -0
  58. package/dist/memory/tools.js +95 -0
  59. package/dist/memory/types.d.ts +15 -0
  60. package/dist/memory/types.js +4 -0
  61. package/dist/permissions/dangerous.d.ts +4 -0
  62. package/dist/permissions/dangerous.js +24 -0
  63. package/dist/permissions/gate.d.ts +21 -0
  64. package/dist/permissions/gate.js +66 -0
  65. package/dist/permissions/index.d.ts +5 -0
  66. package/dist/permissions/index.js +6 -0
  67. package/dist/permissions/match.d.ts +19 -0
  68. package/dist/permissions/match.js +104 -0
  69. package/dist/permissions/planMode.d.ts +3 -0
  70. package/dist/permissions/planMode.js +33 -0
  71. package/dist/permissions/types.d.ts +19 -0
  72. package/dist/permissions/types.js +2 -0
  73. package/dist/persist.d.ts +15 -0
  74. package/dist/persist.js +58 -0
  75. package/dist/prompt.d.ts +6 -0
  76. package/dist/prompt.js +34 -0
  77. package/dist/query.d.ts +105 -0
  78. package/dist/query.js +115 -0
  79. package/dist/queue.d.ts +23 -0
  80. package/dist/queue.js +43 -0
  81. package/dist/sandbox/cloudflare.d.ts +48 -0
  82. package/dist/sandbox/cloudflare.js +124 -0
  83. package/dist/sandbox/daytona.d.ts +48 -0
  84. package/dist/sandbox/daytona.js +79 -0
  85. package/dist/sandbox/e2b.d.ts +54 -0
  86. package/dist/sandbox/e2b.js +87 -0
  87. package/dist/sandbox/index.d.ts +8 -0
  88. package/dist/sandbox/index.js +19 -0
  89. package/dist/sandbox/local.d.ts +51 -0
  90. package/dist/sandbox/local.js +155 -0
  91. package/dist/sandbox/types.d.ts +18 -0
  92. package/dist/sandbox/types.js +27 -0
  93. package/dist/sandbox/util.d.ts +15 -0
  94. package/dist/sandbox/util.js +100 -0
  95. package/dist/sandbox/vercel.d.ts +48 -0
  96. package/dist/sandbox/vercel.js +130 -0
  97. package/dist/session/index.d.ts +2 -0
  98. package/dist/session/index.js +6 -0
  99. package/dist/session/store.d.ts +28 -0
  100. package/dist/session/store.js +122 -0
  101. package/dist/session/types.d.ts +22 -0
  102. package/dist/session/types.js +2 -0
  103. package/dist/settings/index.d.ts +3 -0
  104. package/dist/settings/index.js +3 -0
  105. package/dist/settings/load.d.ts +20 -0
  106. package/dist/settings/load.js +36 -0
  107. package/dist/settings/merge.d.ts +13 -0
  108. package/dist/settings/merge.js +65 -0
  109. package/dist/settings/types.d.ts +17 -0
  110. package/dist/settings/types.js +3 -0
  111. package/dist/skills/index.d.ts +4 -0
  112. package/dist/skills/index.js +5 -0
  113. package/dist/skills/load.d.ts +23 -0
  114. package/dist/skills/load.js +54 -0
  115. package/dist/skills/parse.d.ts +7 -0
  116. package/dist/skills/parse.js +40 -0
  117. package/dist/skills/tool.d.ts +2 -0
  118. package/dist/skills/tool.js +39 -0
  119. package/dist/skills/types.d.ts +10 -0
  120. package/dist/skills/types.js +4 -0
  121. package/dist/team/dispatch.d.ts +2 -0
  122. package/dist/team/dispatch.js +41 -0
  123. package/dist/team/index.d.ts +9 -0
  124. package/dist/team/index.js +11 -0
  125. package/dist/team/mailbox.d.ts +24 -0
  126. package/dist/team/mailbox.js +33 -0
  127. package/dist/team/prompt.d.ts +1 -0
  128. package/dist/team/prompt.js +12 -0
  129. package/dist/team/runner.d.ts +20 -0
  130. package/dist/team/runner.js +45 -0
  131. package/dist/team/taskBoard.d.ts +41 -0
  132. package/dist/team/taskBoard.js +73 -0
  133. package/dist/team/tools.d.ts +7 -0
  134. package/dist/team/tools.js +190 -0
  135. package/dist/tools/bash.d.ts +2 -0
  136. package/dist/tools/bash.js +45 -0
  137. package/dist/tools/config.d.ts +2 -0
  138. package/dist/tools/config.js +44 -0
  139. package/dist/tools/define.d.ts +18 -0
  140. package/dist/tools/define.js +21 -0
  141. package/dist/tools/delete_file.d.ts +2 -0
  142. package/dist/tools/delete_file.js +33 -0
  143. package/dist/tools/edit_file.d.ts +2 -0
  144. package/dist/tools/edit_file.js +93 -0
  145. package/dist/tools/fileTypes.d.ts +32 -0
  146. package/dist/tools/fileTypes.js +166 -0
  147. package/dist/tools/glob.d.ts +2 -0
  148. package/dist/tools/glob.js +53 -0
  149. package/dist/tools/grep.d.ts +2 -0
  150. package/dist/tools/grep.js +110 -0
  151. package/dist/tools/imageProcessor.d.ts +15 -0
  152. package/dist/tools/imageProcessor.js +83 -0
  153. package/dist/tools/index.d.ts +28 -0
  154. package/dist/tools/index.js +45 -0
  155. package/dist/tools/list_files.d.ts +2 -0
  156. package/dist/tools/list_files.js +42 -0
  157. package/dist/tools/multi_edit.d.ts +2 -0
  158. package/dist/tools/multi_edit.js +112 -0
  159. package/dist/tools/notebook_edit.d.ts +2 -0
  160. package/dist/tools/notebook_edit.js +118 -0
  161. package/dist/tools/plan_mode.d.ts +4 -0
  162. package/dist/tools/plan_mode.js +44 -0
  163. package/dist/tools/read_file.d.ts +2 -0
  164. package/dist/tools/read_file.js +193 -0
  165. package/dist/tools/task.d.ts +2 -0
  166. package/dist/tools/task.js +77 -0
  167. package/dist/tools/todo_write.d.ts +2 -0
  168. package/dist/tools/todo_write.js +104 -0
  169. package/dist/tools/tool_search.d.ts +2 -0
  170. package/dist/tools/tool_search.js +49 -0
  171. package/dist/tools/types.d.ts +82 -0
  172. package/dist/tools/types.js +1 -0
  173. package/dist/tools/walk.d.ts +29 -0
  174. package/dist/tools/walk.js +82 -0
  175. package/dist/tools/web_fetch.d.ts +2 -0
  176. package/dist/tools/web_fetch.js +76 -0
  177. package/dist/tools/web_search.d.ts +22 -0
  178. package/dist/tools/web_search.js +195 -0
  179. package/dist/tools/write_file.d.ts +2 -0
  180. package/dist/tools/write_file.js +39 -0
  181. package/dist/types/index.d.ts +363 -0
  182. package/dist/types/index.js +9 -0
  183. package/dist/util/ids.d.ts +3 -0
  184. package/dist/util/ids.js +22 -0
  185. package/dist/util/paths.d.ts +16 -0
  186. package/dist/util/paths.js +72 -0
  187. package/dist/util/pricing.d.ts +15 -0
  188. package/dist/util/pricing.js +81 -0
  189. package/dist/workspace/index.d.ts +2 -0
  190. package/dist/workspace/index.js +2 -0
  191. package/dist/workspace/memory.d.ts +28 -0
  192. package/dist/workspace/memory.js +97 -0
  193. package/dist/workspace/webcontainer.d.ts +65 -0
  194. package/dist/workspace/webcontainer.js +156 -0
  195. package/package.json +78 -0
@@ -0,0 +1,155 @@
1
+ // Local real-OS sandbox — runs the agent against the actual filesystem and
2
+ // shell of the host machine (Node.js), like Claude Code does when executed in a
3
+ // working directory. Auto-detects the platform (Windows / macOS / Linux) and
4
+ // picks an appropriate shell.
5
+ //
6
+ // Node-only. All node built-ins are imported lazily inside methods so this file
7
+ // can sit in the browser barrel without breaking browser bundles — the node
8
+ // code only executes when LocalSandbox is actually used.
9
+ /** Detect the host platform from process.platform. */
10
+ export function detectPlatform() {
11
+ const p = globalThis.process?.platform;
12
+ if (p === 'win32')
13
+ return 'windows';
14
+ if (p === 'darwin')
15
+ return 'mac';
16
+ if (p === 'linux')
17
+ return 'linux';
18
+ return 'unknown';
19
+ }
20
+ /**
21
+ * A Sandbox backed by the local OS filesystem and shell. Pair with an Anthropic/
22
+ * OpenAI client and pass to `query()` to run the agent on real files:
23
+ *
24
+ * const workspace = new LocalSandbox({ cwd: '/path/to/project' })
25
+ * query({ prompt, workspace, llm })
26
+ */
27
+ export class LocalSandbox {
28
+ constructor(options = {}) {
29
+ this.opts = options;
30
+ this.platform = detectPlatform();
31
+ // process.cwd() resolved lazily but captured here when available.
32
+ const proc = globalThis.process;
33
+ this.cwd = options.cwd ?? proc?.cwd?.() ?? '/';
34
+ }
35
+ async fs() {
36
+ return (this._fs ??= await import('node:fs/promises'));
37
+ }
38
+ async path() {
39
+ return (this._path ??= await import('node:path'));
40
+ }
41
+ async cp() {
42
+ return (this._cp ??= await import('node:child_process'));
43
+ }
44
+ async resolve(p) {
45
+ const path = await this.path();
46
+ return path.isAbsolute(p) ? p : path.resolve(this.cwd, p);
47
+ }
48
+ // ---- FileSystem ----
49
+ async readFile(p) {
50
+ try {
51
+ const fs = await this.fs();
52
+ return await fs.readFile(await this.resolve(p), 'utf-8');
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ async readBinary(p) {
59
+ try {
60
+ const fs = await this.fs();
61
+ const buf = await fs.readFile(await this.resolve(p));
62
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ async writeFile(p, contents) {
69
+ const fs = await this.fs();
70
+ const path = await this.path();
71
+ const abs = await this.resolve(p);
72
+ await fs.mkdir(path.dirname(abs), { recursive: true });
73
+ await fs.writeFile(abs, contents, 'utf-8');
74
+ }
75
+ async writeBinary(p, data) {
76
+ const fs = await this.fs();
77
+ const path = await this.path();
78
+ const abs = await this.resolve(p);
79
+ await fs.mkdir(path.dirname(abs), { recursive: true });
80
+ await fs.writeFile(abs, data);
81
+ }
82
+ async deleteFile(p) {
83
+ const fs = await this.fs();
84
+ await fs.rm(await this.resolve(p), { recursive: true, force: true });
85
+ }
86
+ async readdir(p) {
87
+ try {
88
+ const fs = await this.fs();
89
+ const entries = await fs.readdir(await this.resolve(p), { withFileTypes: true });
90
+ return entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ async mkdir(p) {
97
+ const fs = await this.fs();
98
+ await fs.mkdir(await this.resolve(p), { recursive: true });
99
+ }
100
+ // ---- CommandExecutor ----
101
+ async exec(command, timeoutMs = 120_000, env) {
102
+ const cp = await this.cp();
103
+ const proc = globalThis.process;
104
+ const { shell, args } = this.shellFor(command);
105
+ return await new Promise((resolve) => {
106
+ let output = '';
107
+ let settled = false;
108
+ const child = cp.spawn(shell, args, {
109
+ cwd: this.cwd,
110
+ env: { ...proc?.env, ...this.opts.env, ...env },
111
+ windowsHide: true,
112
+ });
113
+ const finish = (exitCode) => {
114
+ if (settled)
115
+ return;
116
+ settled = true;
117
+ clearTimeout(timer);
118
+ resolve({ output: output.trimEnd(), exitCode });
119
+ };
120
+ const timer = setTimeout(() => {
121
+ try {
122
+ child.kill('SIGKILL');
123
+ }
124
+ catch {
125
+ /* ignore */
126
+ }
127
+ output += `\n[command timed out after ${timeoutMs}ms]`;
128
+ finish(124);
129
+ }, timeoutMs);
130
+ child.stdout?.on('data', (d) => (output += d.toString()));
131
+ child.stderr?.on('data', (d) => (output += d.toString()));
132
+ child.on('error', (err) => {
133
+ output += `\n[failed to spawn shell: ${err.message}]`;
134
+ finish(127);
135
+ });
136
+ child.on('close', (code) => finish(code ?? 0));
137
+ });
138
+ }
139
+ /** Pick the shell + args for the detected platform (overridable via options). */
140
+ shellFor(command) {
141
+ if (this.opts.shell) {
142
+ return { shell: this.opts.shell, args: [...(this.opts.shellArgs ?? []), command] };
143
+ }
144
+ if (this.platform === 'windows') {
145
+ // cmd.exe is universally present; /d skips AutoRun, /s /c runs the string.
146
+ return { shell: 'cmd.exe', args: ['/d', '/s', '/c', command] };
147
+ }
148
+ const proc = globalThis.process;
149
+ const shell = proc?.env?.SHELL || '/bin/sh';
150
+ return { shell, args: ['-c', command] };
151
+ }
152
+ }
153
+ export function createLocalSandbox(options) {
154
+ return new LocalSandbox(options);
155
+ }
@@ -0,0 +1,18 @@
1
+ import type { CommandExecutor, FileSystem } from '../types/index.js';
2
+ export type ExecResult = {
3
+ output: string;
4
+ exitCode: number;
5
+ };
6
+ /** A runnable workspace: files + commands, optionally rooted at a cwd. */
7
+ export interface Sandbox extends FileSystem, CommandExecutor {
8
+ readonly cwd?: string;
9
+ /** Optional lifecycle hooks (some providers need explicit setup/teardown). */
10
+ init?(): Promise<void>;
11
+ dispose?(): Promise<void>;
12
+ }
13
+ /**
14
+ * Mix any FileSystem with any CommandExecutor into a single Sandbox. Useful for
15
+ * pairing a persistent local FS (Dexie/OPFS) with a remote shell, or a
16
+ * file-only agent (with NoopCommandExecutor).
17
+ */
18
+ export declare function composeWorkspace(fs: FileSystem, exec: CommandExecutor, cwd?: string): Sandbox;
@@ -0,0 +1,27 @@
1
+ // Sandbox provider abstraction. A Sandbox is just a FileSystem plus a
2
+ // CommandExecutor — the same `Workspace` the agent loop consumes. Adapters wrap
3
+ // each provider's client (WebContainer, E2B, Vercel, Daytona, Cloudflare, …)
4
+ // without a hard dependency on their SDKs (structural typing).
5
+ import { resolvePath } from '../util/paths.js';
6
+ /**
7
+ * Mix any FileSystem with any CommandExecutor into a single Sandbox. Useful for
8
+ * pairing a persistent local FS (Dexie/OPFS) with a remote shell, or a
9
+ * file-only agent (with NoopCommandExecutor).
10
+ */
11
+ export function composeWorkspace(fs, exec, cwd) {
12
+ // When a cwd is given it is authoritative: relative paths resolve against it
13
+ // before hitting the underlying FS (whose own cwd may differ). Absolute paths
14
+ // pass through unchanged.
15
+ const r = cwd ? (p) => resolvePath(cwd, p) : (p) => p;
16
+ return {
17
+ cwd,
18
+ readFile: (p) => fs.readFile(r(p)),
19
+ readBinary: (p) => fs.readBinary(r(p)),
20
+ writeFile: (p, c) => fs.writeFile(r(p), c),
21
+ writeBinary: (p, d) => fs.writeBinary(r(p), d),
22
+ deleteFile: (p) => fs.deleteFile(r(p)),
23
+ readdir: (p) => fs.readdir(r(p)),
24
+ mkdir: (p) => fs.mkdir(r(p)),
25
+ exec: (cmd, timeoutMs, env) => exec.exec(cmd, timeoutMs, env),
26
+ };
27
+ }
@@ -0,0 +1,15 @@
1
+ /** Encode bytes to base64 (browser btoa or Node Buffer), chunked for big inputs. */
2
+ export declare function bytesToBase64(bytes: Uint8Array): string;
3
+ /** Decode base64 to bytes (browser atob or Node Buffer). */
4
+ export declare function base64ToBytes(b64: string): Uint8Array;
5
+ type StreamLike = ReadableStream<Uint8Array | string>;
6
+ type ValueLike = string | Uint8Array | ArrayBuffer | StreamLike | null | undefined;
7
+ /** A provider value that may be the data directly or a thunk/promise returning it. */
8
+ export type MaybeAsync<T> = T | (() => T | Promise<T>) | Promise<T>;
9
+ /** Coerce a provider value (possibly async / a stream) into a string. */
10
+ export declare function toText(value: MaybeAsync<ValueLike>): Promise<string>;
11
+ /** Coerce a provider value (possibly async / a stream) into bytes. */
12
+ export declare function toBytes(value: MaybeAsync<ValueLike>): Promise<Uint8Array>;
13
+ /** Shell-quote a single argument for safe interpolation into a command string. */
14
+ export declare function shellQuote(s: string): string;
15
+ export {};
@@ -0,0 +1,100 @@
1
+ // Small browser-safe helpers shared by the sandbox adapters: base64 codec and
2
+ // normalizers that coerce a provider's varied return shapes (string, bytes,
3
+ // ArrayBuffer, ReadableStream, or a thunk returning any of those) into a
4
+ // concrete string / Uint8Array.
5
+ const CHUNK = 0x8000;
6
+ /** Encode bytes to base64 (browser btoa or Node Buffer), chunked for big inputs. */
7
+ export function bytesToBase64(bytes) {
8
+ const g = globalThis;
9
+ if (typeof g.btoa === 'function') {
10
+ let binary = '';
11
+ for (let i = 0; i < bytes.length; i += CHUNK) {
12
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
13
+ }
14
+ return g.btoa(binary);
15
+ }
16
+ const B = globalThis.Buffer;
17
+ if (B)
18
+ return B.from(bytes).toString('base64');
19
+ throw new Error('No base64 encoder available');
20
+ }
21
+ /** Decode base64 to bytes (browser atob or Node Buffer). */
22
+ export function base64ToBytes(b64) {
23
+ const g = globalThis;
24
+ if (typeof g.atob === 'function') {
25
+ const binary = g.atob(b64);
26
+ const out = new Uint8Array(binary.length);
27
+ for (let i = 0; i < binary.length; i++)
28
+ out[i] = binary.charCodeAt(i);
29
+ return out;
30
+ }
31
+ const B = globalThis.Buffer;
32
+ if (B)
33
+ return new Uint8Array(B.from(b64, 'base64'));
34
+ throw new Error('No base64 decoder available');
35
+ }
36
+ const decoder = new TextDecoder();
37
+ const encoder = new TextEncoder();
38
+ async function unwrap(v) {
39
+ const r = typeof v === 'function' ? v() : v;
40
+ return await r;
41
+ }
42
+ async function readStream(stream) {
43
+ const reader = stream.getReader();
44
+ const chunks = [];
45
+ let total = 0;
46
+ for (;;) {
47
+ const { value, done } = await reader.read();
48
+ if (done)
49
+ break;
50
+ const bytes = typeof value === 'string' ? encoder.encode(value) : value;
51
+ if (bytes) {
52
+ chunks.push(bytes);
53
+ total += bytes.length;
54
+ }
55
+ }
56
+ reader.releaseLock?.();
57
+ const out = new Uint8Array(total);
58
+ let off = 0;
59
+ for (const c of chunks) {
60
+ out.set(c, off);
61
+ off += c.length;
62
+ }
63
+ return out;
64
+ }
65
+ /** Coerce a provider value (possibly async / a stream) into a string. */
66
+ export async function toText(value) {
67
+ const v = await unwrap(value);
68
+ if (v == null)
69
+ return '';
70
+ if (typeof v === 'string')
71
+ return v;
72
+ if (v instanceof Uint8Array)
73
+ return decoder.decode(v);
74
+ if (v instanceof ArrayBuffer)
75
+ return decoder.decode(new Uint8Array(v));
76
+ if (typeof v.getReader === 'function') {
77
+ return decoder.decode(await readStream(v));
78
+ }
79
+ return String(v);
80
+ }
81
+ /** Coerce a provider value (possibly async / a stream) into bytes. */
82
+ export async function toBytes(value) {
83
+ const v = await unwrap(value);
84
+ if (v == null)
85
+ return new Uint8Array();
86
+ if (v instanceof Uint8Array)
87
+ return v;
88
+ if (v instanceof ArrayBuffer)
89
+ return new Uint8Array(v);
90
+ if (typeof v === 'string')
91
+ return encoder.encode(v);
92
+ if (typeof v.getReader === 'function') {
93
+ return readStream(v);
94
+ }
95
+ return encoder.encode(String(v));
96
+ }
97
+ /** Shell-quote a single argument for safe interpolation into a command string. */
98
+ export function shellQuote(s) {
99
+ return `'${s.replace(/'/g, `'\\''`)}'`;
100
+ }
@@ -0,0 +1,48 @@
1
+ import type { CommandExecutor, FileSystem } from '../types/index.js';
2
+ import type { Sandbox } from './types.js';
3
+ import { type MaybeAsync } from './util.js';
4
+ interface VercelCommandResult {
5
+ exitCode?: number;
6
+ stdout?: MaybeAsync<string | Uint8Array>;
7
+ stderr?: MaybeAsync<string | Uint8Array>;
8
+ }
9
+ export interface VercelClientLike {
10
+ runCommand(opts: {
11
+ cmd: string;
12
+ args?: string[];
13
+ env?: Record<string, string>;
14
+ cwd?: string;
15
+ }): Promise<VercelCommandResult>;
16
+ readFile?(opts: {
17
+ path: string;
18
+ }): Promise<unknown>;
19
+ writeFiles?(files: Array<{
20
+ path: string;
21
+ content: Uint8Array | string;
22
+ }>): Promise<unknown>;
23
+ mkdir?(path: string): Promise<unknown>;
24
+ }
25
+ export declare class VercelSandbox implements Sandbox, FileSystem, CommandExecutor {
26
+ private readonly client;
27
+ readonly cwd: string;
28
+ constructor(client: VercelClientLike, cwd?: string);
29
+ private r;
30
+ /** Run a shell command string, returning combined output + exit code. */
31
+ private sh;
32
+ readFile(path: string): Promise<string | null>;
33
+ readBinary(path: string): Promise<Uint8Array | null>;
34
+ writeFile(path: string, contents: string): Promise<void>;
35
+ writeBinary(path: string, data: Uint8Array): Promise<void>;
36
+ deleteFile(path: string): Promise<void>;
37
+ readdir(path: string): Promise<Array<{
38
+ name: string;
39
+ isDir: boolean;
40
+ }> | null>;
41
+ mkdir(path: string): Promise<void>;
42
+ exec(command: string, _timeoutMs?: number, env?: Record<string, string>): Promise<{
43
+ output: string;
44
+ exitCode: number;
45
+ }>;
46
+ }
47
+ export declare function createVercelSandbox(client: VercelClientLike, cwd?: string): VercelSandbox;
48
+ export {};
@@ -0,0 +1,130 @@
1
+ // Vercel Sandbox adapter.
2
+ //
3
+ // Wrap a `@vercel/sandbox` `Sandbox`:
4
+ //
5
+ // import { Sandbox } from '@vercel/sandbox'
6
+ // const sbx = await Sandbox.create({ ... })
7
+ // const workspace = new VercelSandbox(sbx)
8
+ //
9
+ // Primary method used: sbx.runCommand({ cmd, args, env, cwd }) -> result with
10
+ // `.exitCode` and `.stdout()` / `.stderr()` (string | stream | thunk).
11
+ //
12
+ // File ops: the adapter prefers native methods when the client exposes them
13
+ // (readFile({path}), writeFiles([{path,content}]), mkdir(path)), and otherwise
14
+ // falls back to POSIX shell commands run via runCommand (cat / base64 / ls /
15
+ // mkdir -p / rm). The shell fallback assumes a Unix image with `base64`.
16
+ import { dirname, resolvePath } from '../util/paths.js';
17
+ import { base64ToBytes, bytesToBase64, shellQuote, toBytes, toText, } from './util.js';
18
+ export class VercelSandbox {
19
+ constructor(client, cwd = '/vercel/sandbox') {
20
+ this.client = client;
21
+ this.cwd = cwd;
22
+ }
23
+ r(p) {
24
+ return resolvePath(this.cwd, p);
25
+ }
26
+ /** Run a shell command string, returning combined output + exit code. */
27
+ async sh(command) {
28
+ const res = await this.client.runCommand({
29
+ cmd: 'sh',
30
+ args: ['-c', command],
31
+ cwd: this.cwd,
32
+ });
33
+ const out = await toText(res.stdout);
34
+ const err = await toText(res.stderr);
35
+ return {
36
+ output: (out + (err ? (out ? '\n' : '') + err : '')).trimEnd(),
37
+ exitCode: res.exitCode ?? 0,
38
+ };
39
+ }
40
+ async readFile(path) {
41
+ const abs = this.r(path);
42
+ if (this.client.readFile) {
43
+ try {
44
+ return await toText((await this.client.readFile({ path: abs })));
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ const { output, exitCode } = await this.sh(`cat ${shellQuote(abs)}`);
51
+ return exitCode === 0 ? output : null;
52
+ }
53
+ async readBinary(path) {
54
+ const abs = this.r(path);
55
+ if (this.client.readFile) {
56
+ try {
57
+ return await toBytes((await this.client.readFile({ path: abs })));
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ const { output, exitCode } = await this.sh(`base64 ${shellQuote(abs)} | tr -d '\\n'`);
64
+ if (exitCode !== 0)
65
+ return null;
66
+ try {
67
+ return base64ToBytes(output.trim());
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ async writeFile(path, contents) {
74
+ await this.writeBinary(path, new TextEncoder().encode(contents));
75
+ }
76
+ async writeBinary(path, data) {
77
+ const abs = this.r(path);
78
+ if (this.client.writeFiles) {
79
+ await this.client.writeFiles([{ path: abs, content: data }]);
80
+ return;
81
+ }
82
+ // Shell fallback: recreate parent dir and pipe base64 through `base64 -d`.
83
+ const b64 = bytesToBase64(data);
84
+ const dir = dirname(abs);
85
+ const { exitCode, output } = await this.sh(`mkdir -p ${shellQuote(dir)} && printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`);
86
+ if (exitCode !== 0)
87
+ throw new Error(`writeFile failed: ${output}`);
88
+ }
89
+ async deleteFile(path) {
90
+ await this.sh(`rm -rf ${shellQuote(this.r(path))}`);
91
+ }
92
+ async readdir(path) {
93
+ // `ls -1Ap` lists one-per-line, all entries, with a trailing '/' on dirs.
94
+ const { output, exitCode } = await this.sh(`ls -1Ap ${shellQuote(this.r(path))}`);
95
+ if (exitCode !== 0)
96
+ return null;
97
+ return output
98
+ .split('\n')
99
+ .map((l) => l.trim())
100
+ .filter(Boolean)
101
+ .map((name) => name.endsWith('/')
102
+ ? { name: name.slice(0, -1), isDir: true }
103
+ : { name, isDir: false });
104
+ }
105
+ async mkdir(path) {
106
+ const abs = this.r(path);
107
+ if (this.client.mkdir) {
108
+ await this.client.mkdir(abs);
109
+ return;
110
+ }
111
+ await this.sh(`mkdir -p ${shellQuote(abs)}`);
112
+ }
113
+ async exec(command, _timeoutMs, env) {
114
+ const res = await this.client.runCommand({
115
+ cmd: 'sh',
116
+ args: ['-c', command],
117
+ env,
118
+ cwd: this.cwd,
119
+ });
120
+ const out = await toText(res.stdout);
121
+ const err = await toText(res.stderr);
122
+ return {
123
+ output: (out + (err ? (out ? '\n' : '') + err : '')).trimEnd(),
124
+ exitCode: res.exitCode ?? 0,
125
+ };
126
+ }
127
+ }
128
+ export function createVercelSandbox(client, cwd) {
129
+ return new VercelSandbox(client, cwd);
130
+ }
@@ -0,0 +1,2 @@
1
+ export { SessionStore } from './store.js';
2
+ export type { SessionMeta, StoredSession, SessionStoreOptions } from './types.js';
@@ -0,0 +1,6 @@
1
+ // Session persistence + resume for browser-claude-sdk.
2
+ //
3
+ // SessionStore persists per-session metadata and the full LLM transcript to
4
+ // IndexedDB (via Dexie), enabling listSessions / resume / fork / rename across
5
+ // reloads.
6
+ export { SessionStore } from './store.js';
@@ -0,0 +1,28 @@
1
+ import type { ChatMsg } from '../types/index.js';
2
+ import type { SessionMeta, SessionStoreOptions, StoredSession } from './types.js';
3
+ export declare class SessionStore {
4
+ private readonly dbName;
5
+ private db;
6
+ private opening;
7
+ constructor(options?: SessionStoreOptions);
8
+ private open;
9
+ /** Upsert a session's transcript + metadata. */
10
+ save(sessionId: string, transcript: ChatMsg[], meta?: {
11
+ title?: string;
12
+ model?: string;
13
+ }): Promise<void>;
14
+ /** Load a session's transcript, or null if not found. */
15
+ load(sessionId: string): Promise<ChatMsg[] | null>;
16
+ /** Load full session (metadata + transcript), or null if not found. */
17
+ get(sessionId: string): Promise<StoredSession | null>;
18
+ /** List all sessions (metadata only), newest first. */
19
+ list(): Promise<SessionMeta[]>;
20
+ /** Set a session's title. */
21
+ rename(sessionId: string, title: string): Promise<void>;
22
+ /** Copy a session's transcript + metadata to a new id. Returns false if source missing. */
23
+ fork(sessionId: string, newSessionId: string): Promise<boolean>;
24
+ /** Delete a session and its transcript. */
25
+ remove(sessionId: string): Promise<void>;
26
+ /** Wipe all sessions. */
27
+ clear(): Promise<void>;
28
+ }
@@ -0,0 +1,122 @@
1
+ // SessionStore — durable session persistence + resume, backed by IndexedDB via
2
+ // Dexie. Stores per-session metadata and the full LLM transcript (ChatMsg[]),
3
+ // enabling listSessions/resume/fork/rename across page reloads.
4
+ //
5
+ // `dexie` is an OPTIONAL peer dependency — imported dynamically so this module
6
+ // loads even when Dexie isn't installed (the error surfaces only on first use).
7
+ export class SessionStore {
8
+ constructor(options = {}) {
9
+ this.db = null;
10
+ this.opening = null;
11
+ this.dbName = options.dbName ?? 'bcs-sessions';
12
+ }
13
+ async open() {
14
+ if (this.db)
15
+ return this.db;
16
+ if (this.opening)
17
+ return this.opening;
18
+ this.opening = (async () => {
19
+ // @ts-ignore optional peer dependency, resolved at runtime
20
+ const mod = await import('dexie');
21
+ const Dexie = mod.default ?? mod;
22
+ const db = new Dexie(this.dbName);
23
+ db.version(1).stores({
24
+ sessions: 'sessionId, updatedAt',
25
+ transcripts: 'sessionId',
26
+ });
27
+ this.db = db;
28
+ return db;
29
+ })();
30
+ return this.opening;
31
+ }
32
+ /** Upsert a session's transcript + metadata. */
33
+ async save(sessionId, transcript, meta = {}) {
34
+ const db = await this.open();
35
+ const now = Date.now();
36
+ const existing = await db.sessions.get(sessionId);
37
+ const row = {
38
+ sessionId,
39
+ title: meta.title ?? existing?.title,
40
+ model: meta.model ?? existing?.model,
41
+ createdAt: existing?.createdAt ?? now,
42
+ updatedAt: now,
43
+ messageCount: transcript.length,
44
+ };
45
+ await db.sessions.put(row);
46
+ await db.transcripts.put({ sessionId, transcript });
47
+ }
48
+ /** Load a session's transcript, or null if not found. */
49
+ async load(sessionId) {
50
+ try {
51
+ const db = await this.open();
52
+ const row = await db.transcripts.get(sessionId);
53
+ return row?.transcript ?? null;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /** Load full session (metadata + transcript), or null if not found. */
60
+ async get(sessionId) {
61
+ try {
62
+ const db = await this.open();
63
+ const meta = await db.sessions.get(sessionId);
64
+ if (!meta)
65
+ return null;
66
+ const t = await db.transcripts.get(sessionId);
67
+ return { ...meta, transcript: t?.transcript ?? [] };
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** List all sessions (metadata only), newest first. */
74
+ async list() {
75
+ try {
76
+ const db = await this.open();
77
+ const rows = await db.sessions.toArray();
78
+ return rows.sort((a, b) => b.updatedAt - a.updatedAt);
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ }
84
+ /** Set a session's title. */
85
+ async rename(sessionId, title) {
86
+ const db = await this.open();
87
+ const meta = await db.sessions.get(sessionId);
88
+ if (!meta)
89
+ return;
90
+ await db.sessions.put({ ...meta, title, updatedAt: Date.now() });
91
+ }
92
+ /** Copy a session's transcript + metadata to a new id. Returns false if source missing. */
93
+ async fork(sessionId, newSessionId) {
94
+ const db = await this.open();
95
+ const meta = await db.sessions.get(sessionId);
96
+ const t = await db.transcripts.get(sessionId);
97
+ if (!meta || !t)
98
+ return false;
99
+ const now = Date.now();
100
+ await db.sessions.put({
101
+ ...meta,
102
+ sessionId: newSessionId,
103
+ title: (meta.title ?? sessionId) + ' (fork)',
104
+ createdAt: now,
105
+ updatedAt: now,
106
+ });
107
+ await db.transcripts.put({ sessionId: newSessionId, transcript: t.transcript });
108
+ return true;
109
+ }
110
+ /** Delete a session and its transcript. */
111
+ async remove(sessionId) {
112
+ const db = await this.open();
113
+ await db.sessions.delete(sessionId);
114
+ await db.transcripts.delete(sessionId);
115
+ }
116
+ /** Wipe all sessions. */
117
+ async clear() {
118
+ const db = await this.open();
119
+ await db.sessions.clear();
120
+ await db.transcripts.clear();
121
+ }
122
+ }
@@ -0,0 +1,22 @@
1
+ import type { ChatMsg } from '../types/index.js';
2
+ /** Lightweight metadata for a stored session (no transcript). */
3
+ export interface SessionMeta {
4
+ sessionId: string;
5
+ title?: string;
6
+ /** Epoch ms when the session was first saved. */
7
+ createdAt: number;
8
+ /** Epoch ms of the last save. */
9
+ updatedAt: number;
10
+ /** Model id last used in the session. */
11
+ model?: string;
12
+ /** Number of transcript messages at last save. */
13
+ messageCount: number;
14
+ }
15
+ /** A full stored session: metadata plus the LLM transcript. */
16
+ export type StoredSession = SessionMeta & {
17
+ transcript: ChatMsg[];
18
+ };
19
+ export interface SessionStoreOptions {
20
+ /** IndexedDB database name. Default: 'bcs-sessions'. */
21
+ dbName?: string;
22
+ }