@cybernetyx1/atlasflow-runtime 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.
@@ -0,0 +1,347 @@
1
+ // src/sandbox.ts
2
+ function sandboxHeaderFromEnv(env, options = {}) {
3
+ if (!env || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(env)) throw new Error(`Invalid sandbox credential env name: ${env}`);
4
+ return { env, prefix: options.prefix, required: options.required };
5
+ }
6
+ function resolveSandboxNetworkPolicy(policy, env = {}) {
7
+ if (!policy) return void 0;
8
+ const allowedUrlPrefixes = policy.allowedUrlPrefixes?.map((entry) => {
9
+ if (typeof entry === "string") return entry;
10
+ const headers = resolveSandboxHeaders(entry.headers, env);
11
+ return Object.keys(headers).length ? { url: entry.url, transform: [{ headers }] } : { url: entry.url };
12
+ });
13
+ return {
14
+ ...policy,
15
+ allowedUrlPrefixes
16
+ };
17
+ }
18
+ function resolveSandboxHeaders(headers, env) {
19
+ const out = {};
20
+ for (const [name, value] of Object.entries(headers ?? {})) {
21
+ const resolved = resolveSandboxHeaderValue(value, env);
22
+ if (resolved !== void 0) out[name] = resolved;
23
+ }
24
+ return out;
25
+ }
26
+ function resolveSandboxHeaderValue(value, env) {
27
+ if (typeof value === "string") return value;
28
+ const raw = env[value.env];
29
+ if (typeof raw === "string" && raw.length > 0) return `${value.prefix ?? ""}${raw}`;
30
+ if (value.required === false) return void 0;
31
+ throw new Error(`Sandbox network credential "${value.env}" is not configured.`);
32
+ }
33
+ function defineSandbox(input) {
34
+ if (isSandboxFactory(input)) return input;
35
+ let bootstrapped;
36
+ return {
37
+ async createSessionEnv(options) {
38
+ const ctx = { id: options.id, cwd: options.cwd, env: options.env };
39
+ if (!bootstrapped) bootstrapped = Promise.resolve(input.bootstrap?.(ctx)).then(() => void 0);
40
+ await bootstrapped;
41
+ const factory = typeof input.factory === "function" ? input.factory(ctx) : input.factory;
42
+ if (!isSandboxFactory(factory)) throw new Error("defineSandbox factory must return a SandboxFactory.");
43
+ const env = await factory.createSessionEnv(options);
44
+ await input.onSession?.(env, ctx);
45
+ if (!input.cleanup) return env;
46
+ const wrapped = {
47
+ cwd: env.cwd,
48
+ resolvePath: (path) => env.resolvePath(path),
49
+ exec: (command, execOptions) => env.exec(command, execOptions),
50
+ scope: env.scope ? (scopeOptions) => env.scope(scopeOptions) : void 0,
51
+ readFile: (path) => env.readFile(path),
52
+ readFileBuffer: (path) => env.readFileBuffer(path),
53
+ writeFile: (path, data) => env.writeFile(path, data),
54
+ stat: (path) => env.stat(path),
55
+ readdir: (path) => env.readdir(path),
56
+ exists: (path) => env.exists(path),
57
+ mkdir: (path, mkdirOptions) => env.mkdir(path, mkdirOptions),
58
+ rm: (path, rmOptions) => env.rm(path, rmOptions),
59
+ dispose: async () => {
60
+ try {
61
+ await env.dispose?.();
62
+ } finally {
63
+ await input.cleanup?.(ctx);
64
+ }
65
+ }
66
+ };
67
+ return wrapped;
68
+ }
69
+ };
70
+ }
71
+ function sandboxCascade(entries) {
72
+ if (!entries.length) throw new Error("sandboxCascade requires at least one provider.");
73
+ return {
74
+ async createSessionEnv(options) {
75
+ const ctx = { id: options.id, cwd: options.cwd, env: options.env };
76
+ const skipped = [];
77
+ const failures = [];
78
+ for (const [index, entry] of entries.entries()) {
79
+ const name = sandboxProviderName(entry, index);
80
+ try {
81
+ const provider = normalizeCascadeEntry(entry);
82
+ if (provider.when && !await provider.when(ctx)) {
83
+ skipped.push(name);
84
+ continue;
85
+ }
86
+ const factory = typeof provider.factory === "function" ? await provider.factory(ctx) : provider.factory;
87
+ if (!factory) {
88
+ skipped.push(name);
89
+ continue;
90
+ }
91
+ if (!isSandboxFactory(factory)) throw new Error("provider did not return a SandboxFactory.");
92
+ return await factory.createSessionEnv(options);
93
+ } catch (err) {
94
+ failures.push(`${name}: ${err instanceof Error ? err.message : String(err)}`);
95
+ }
96
+ }
97
+ const detail = [
98
+ failures.length ? `failed [${failures.join("; ")}]` : void 0,
99
+ skipped.length ? `skipped [${skipped.join(", ")}]` : void 0
100
+ ].filter(Boolean);
101
+ throw new Error(`No sandbox provider could create a session${detail.length ? `: ${detail.join("; ")}` : "."}`);
102
+ }
103
+ };
104
+ }
105
+ function normalizeCascadeEntry(entry) {
106
+ return isSandboxFactory(entry) ? { name: "sandbox", factory: entry } : entry;
107
+ }
108
+ function sandboxProviderName(entry, index) {
109
+ if (isSandboxFactory(entry)) return `provider-${index + 1}`;
110
+ return entry.name || `provider-${index + 1}`;
111
+ }
112
+ function isSandboxFactory(value) {
113
+ return typeof value === "object" && value !== null && typeof value.createSessionEnv === "function";
114
+ }
115
+ function makeFs(env) {
116
+ return {
117
+ readFile: (p) => env.readFile(p),
118
+ readFileBuffer: (p) => env.readFileBuffer(p),
119
+ writeFile: (p, d) => env.writeFile(p, d),
120
+ stat: (p) => env.stat(p),
121
+ readdir: (p) => env.readdir(p),
122
+ exists: (p) => env.exists(p),
123
+ mkdir: (p, o) => env.mkdir(p, o),
124
+ rm: (p, o) => env.rm(p, o)
125
+ };
126
+ }
127
+ async function writeFileCreatingParents(write, mkdirParent) {
128
+ try {
129
+ await write();
130
+ return;
131
+ } catch {
132
+ }
133
+ try {
134
+ await mkdirParent();
135
+ } catch {
136
+ }
137
+ await write();
138
+ }
139
+ function createCwdSessionEnv(parentEnv, cwd) {
140
+ const scopedCwd = normalizePath(cwd);
141
+ const resolvePath = makeResolvePath(scopedCwd);
142
+ return {
143
+ cwd: scopedCwd,
144
+ resolvePath,
145
+ exec(command, options) {
146
+ return parentEnv.exec(command, {
147
+ cwd: options?.cwd !== void 0 ? resolvePath(options.cwd) : scopedCwd,
148
+ env: options?.env,
149
+ timeoutMs: options?.timeoutMs,
150
+ signal: options?.signal
151
+ });
152
+ },
153
+ scope: async (options) => {
154
+ const commands = options?.commands ?? [];
155
+ if (commands.length && !parentEnv.scope) {
156
+ throw new Error(
157
+ "Cannot use commands: this sandbox does not support scoped command execution. Use the default virtual sandbox or a connector that implements SessionEnv.scope()."
158
+ );
159
+ }
160
+ const scopedParent = parentEnv.scope ? await parentEnv.scope({ commands }) : parentEnv;
161
+ return createCwdSessionEnv(scopedParent, scopedCwd);
162
+ },
163
+ readFile: (p) => parentEnv.readFile(resolvePath(p)),
164
+ readFileBuffer: (p) => parentEnv.readFileBuffer(resolvePath(p)),
165
+ writeFile: (p, data) => parentEnv.writeFile(resolvePath(p), data),
166
+ stat: (p) => parentEnv.stat(resolvePath(p)),
167
+ readdir: (p) => parentEnv.readdir(resolvePath(p)),
168
+ exists: (p) => parentEnv.exists(resolvePath(p)),
169
+ mkdir: (p, options) => parentEnv.mkdir(resolvePath(p), options),
170
+ rm: (p, options) => parentEnv.rm(resolvePath(p), options),
171
+ dispose: () => parentEnv.dispose?.() ?? Promise.resolve()
172
+ };
173
+ }
174
+ function makeResolvePath(cwd) {
175
+ return (path) => {
176
+ if (path.startsWith("/")) return normalizePath(path);
177
+ if (cwd === "/") return normalizePath(`/${path}`);
178
+ return normalizePath(`${cwd}/${path}`);
179
+ };
180
+ }
181
+ function normalizePath(path) {
182
+ const out = [];
183
+ for (const part of path.split("/")) {
184
+ if (!part || part === ".") continue;
185
+ if (part === "..") out.pop();
186
+ else out.push(part);
187
+ }
188
+ return `/${out.join("/")}`;
189
+ }
190
+ var BaseSandboxEnv = class {
191
+ async exists(path) {
192
+ try {
193
+ await this.stat(path);
194
+ return true;
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+ resolvePath(path) {
200
+ if (path.startsWith("/")) return path;
201
+ return `${this.cwd.replace(/\/$/, "")}/${path}`;
202
+ }
203
+ };
204
+
205
+ // src/virtual-sandbox.ts
206
+ import { Bash, InMemoryFs } from "just-bash";
207
+ var DEFAULT_VIRTUAL_NETWORK = {
208
+ dangerouslyAllowFullInternetAccess: true,
209
+ denyPrivateRanges: true
210
+ };
211
+ function bash(factory) {
212
+ return {
213
+ async createSessionEnv(options) {
214
+ const env = await bashFactoryToSessionEnv(factory);
215
+ return options.cwd ? createCwdSessionEnv(env, options.cwd) : env;
216
+ }
217
+ };
218
+ }
219
+ async function bashFactoryToSessionEnv(factory) {
220
+ const seen = /* @__PURE__ */ new WeakSet();
221
+ async function createBash() {
222
+ const bash2 = await factory();
223
+ assertBashLike(bash2);
224
+ if (seen.has(bash2)) {
225
+ throw new Error("BashFactory must return a fresh Bash-like object for each scoped operation.");
226
+ }
227
+ seen.add(bash2);
228
+ return bash2;
229
+ }
230
+ async function createScope(commands) {
231
+ const scoped = await createBash();
232
+ registerCommands(scoped, commands);
233
+ return createBashSessionEnv(scoped, createScope);
234
+ }
235
+ return createBashSessionEnv(await createBash(), createScope);
236
+ }
237
+ function createBashSessionEnv(bash2, createScope) {
238
+ const fs = bash2.fs;
239
+ const cwd = bash2.getCwd();
240
+ const resolvePath = (p) => p.startsWith("/") ? p : fs.resolvePath(cwd, p);
241
+ return {
242
+ cwd,
243
+ resolvePath,
244
+ scope: (options) => createScope(options?.commands ?? []),
245
+ async exec(command, options) {
246
+ let timeout;
247
+ let timedOut = false;
248
+ const signals = [];
249
+ if (options?.signal) signals.push(options.signal);
250
+ if (options?.timeoutMs) {
251
+ const controller = new AbortController();
252
+ timeout = setTimeout(() => {
253
+ timedOut = true;
254
+ controller.abort(new Error(`command timed out after ${options.timeoutMs}ms`));
255
+ }, options.timeoutMs);
256
+ signals.push(controller.signal);
257
+ }
258
+ const signal = signals.length === 0 ? void 0 : signals.length === 1 ? signals[0] : AbortSignal.any(signals);
259
+ try {
260
+ const result = await bash2.exec(command, { cwd: options?.cwd, env: options?.env, signal });
261
+ if (timedOut) return { ...result, stderr: `${result.stderr}
262
+ [command timed out after ${options?.timeoutMs}ms]`.trim(), exitCode: result.exitCode || 124 };
263
+ return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode };
264
+ } catch (err) {
265
+ if (timedOut) return { stdout: "", stderr: `[command timed out after ${options?.timeoutMs}ms]`, exitCode: 124 };
266
+ throw err;
267
+ } finally {
268
+ if (timeout) clearTimeout(timeout);
269
+ }
270
+ },
271
+ readFile: (p) => fs.readFile(resolvePath(p)),
272
+ readFileBuffer: (p) => fs.readFileBuffer(resolvePath(p)),
273
+ writeFile(p, data) {
274
+ const resolved = resolvePath(p);
275
+ const dir = dirnameOf(resolved);
276
+ return writeFileCreatingParents(
277
+ () => fs.writeFile(resolved, data),
278
+ () => dir ? fs.mkdir(dir, { recursive: true }) : Promise.resolve()
279
+ );
280
+ },
281
+ async stat(p) {
282
+ const s = await fs.stat(resolvePath(p));
283
+ return { isFile: s.isFile, isDirectory: s.isDirectory, isSymbolicLink: s.isSymbolicLink, size: s.size, mtimeMs: s.mtime.getTime() };
284
+ },
285
+ readdir: (p) => fs.readdir(resolvePath(p)),
286
+ exists: (p) => fs.exists(resolvePath(p)),
287
+ mkdir: (p, options) => fs.mkdir(resolvePath(p), options),
288
+ rm: (p, options) => fs.rm(resolvePath(p), options),
289
+ dispose: async () => {
290
+ }
291
+ };
292
+ }
293
+ function registerCommands(bash2, commands) {
294
+ if (!commands.length) return;
295
+ if (typeof bash2.registerCommand !== "function") throw new Error("Cannot use commands: this Bash-like sandbox does not support command registration.");
296
+ for (const command of commands) {
297
+ bash2.registerCommand({
298
+ name: command.name,
299
+ trusted: true,
300
+ execute: (args, ctx) => command.execute(args, ctx?.signal)
301
+ });
302
+ }
303
+ }
304
+ function virtualSandbox(options = {}) {
305
+ return {
306
+ async createSessionEnv(createOptions = { id: "virtual" }) {
307
+ const fs = new InMemoryFs();
308
+ const network = resolveSandboxNetworkPolicy(options.network, createOptions.env) ?? options.bash?.network ?? DEFAULT_VIRTUAL_NETWORK;
309
+ const env = await bashFactoryToSessionEnv(
310
+ () => new Bash({
311
+ fs,
312
+ cwd: options.cwd,
313
+ env: options.env,
314
+ ...options.bash,
315
+ network
316
+ })
317
+ );
318
+ return createOptions.cwd ? createCwdSessionEnv(env, createOptions.cwd) : env;
319
+ }
320
+ };
321
+ }
322
+ function assertBashLike(value) {
323
+ if (typeof value !== "object" || value === null || typeof value.exec !== "function" || typeof value.getCwd !== "function" || typeof value.fs !== "object" || value.fs === null) {
324
+ throw new Error("BashFactory must return a fresh Bash-like object.");
325
+ }
326
+ }
327
+ function dirnameOf(path) {
328
+ const normalized = path.replace(/\/+$/, "");
329
+ const idx = normalized.lastIndexOf("/");
330
+ if (idx <= 0) return idx === 0 ? "/" : void 0;
331
+ return normalized.slice(0, idx);
332
+ }
333
+
334
+ export {
335
+ sandboxHeaderFromEnv,
336
+ resolveSandboxNetworkPolicy,
337
+ defineSandbox,
338
+ sandboxCascade,
339
+ isSandboxFactory,
340
+ makeFs,
341
+ writeFileCreatingParents,
342
+ createCwdSessionEnv,
343
+ BaseSandboxEnv,
344
+ bash,
345
+ bashFactoryToSessionEnv,
346
+ virtualSandbox
347
+ };
@@ -0,0 +1,85 @@
1
+ // src/providers.ts
2
+ import {
3
+ getApiProvider,
4
+ getApiProviders,
5
+ getModel,
6
+ getModels,
7
+ registerApiProvider,
8
+ unregisterApiProviders
9
+ } from "@earendil-works/pi-ai";
10
+ var providersById = /* @__PURE__ */ new Map();
11
+ var ProviderRegistrationError = class extends Error {
12
+ constructor(providerId) {
13
+ super(`Provider "${providerId}" is not in the pi-ai catalog; registerProvider() must include api and baseUrl.`);
14
+ this.name = "ProviderRegistrationError";
15
+ }
16
+ };
17
+ function registerProvider(providerId, registration) {
18
+ const known = getModels(providerId).length > 0;
19
+ if (!known && (registration.api === void 0 || registration.baseUrl === void 0)) {
20
+ throw new ProviderRegistrationError(providerId);
21
+ }
22
+ providersById.set(providerId, { ...registration });
23
+ }
24
+ function resetProvidersForTests() {
25
+ providersById.clear();
26
+ }
27
+ function hasRegisteredProvider(providerId) {
28
+ return providersById.has(providerId);
29
+ }
30
+ function getRegisteredApiKey(providerId) {
31
+ return providersById.get(providerId)?.apiKey;
32
+ }
33
+ function getRegisteredStoreResponses(providerId) {
34
+ return providersById.get(providerId)?.storeResponses === true;
35
+ }
36
+ function resolveRegisteredModel(providerId, modelId) {
37
+ const registration = providersById.get(providerId);
38
+ if (!registration) return void 0;
39
+ const catalog = getModel(providerId, modelId);
40
+ const providerDefaults = catalog ?? getModels(providerId)[0];
41
+ const api = registration.api ?? providerDefaults?.api;
42
+ const baseUrl = registration.baseUrl ?? providerDefaults?.baseUrl;
43
+ if (api === void 0 || baseUrl === void 0) throw new ProviderRegistrationError(providerId);
44
+ const base = catalog ?? zeroMetadataModel(providerId, modelId, api, baseUrl);
45
+ const modelOverride = registration.models?.[modelId];
46
+ return {
47
+ ...base,
48
+ id: modelId,
49
+ name: modelOverride?.name ?? base.name ?? modelId,
50
+ api,
51
+ provider: providerId,
52
+ baseUrl,
53
+ headers: base.headers || registration.headers ? { ...base.headers ?? {}, ...registration.headers ?? {} } : void 0,
54
+ contextWindow: modelOverride?.contextWindow ?? registration.contextWindow ?? base.contextWindow,
55
+ maxTokens: modelOverride?.maxTokens ?? registration.maxTokens ?? base.maxTokens
56
+ };
57
+ }
58
+ function zeroMetadataModel(providerId, modelId, api, baseUrl) {
59
+ return {
60
+ id: modelId,
61
+ name: modelId,
62
+ api,
63
+ provider: providerId,
64
+ baseUrl,
65
+ reasoning: false,
66
+ input: ["text"],
67
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
68
+ contextWindow: 0,
69
+ maxTokens: 0
70
+ };
71
+ }
72
+
73
+ export {
74
+ getApiProvider,
75
+ getApiProviders,
76
+ registerApiProvider,
77
+ unregisterApiProviders,
78
+ ProviderRegistrationError,
79
+ registerProvider,
80
+ resetProvidersForTests,
81
+ hasRegisteredProvider,
82
+ getRegisteredApiKey,
83
+ getRegisteredStoreResponses,
84
+ resolveRegisteredModel
85
+ };