@finalai/final-agent-provider 0.2.0 → 0.2.2
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/modules/sandboxes/computer.d.mts +2 -0
- package/dist/modules/sandboxes/computer.mjs +70 -18
- package/dist/modules/sandboxes/computer.mjs.map +3 -3
- package/dist/modules/sandboxes/device.d.mts +2 -0
- package/dist/modules/sandboxes/device.mjs +27 -4
- package/dist/modules/sandboxes/device.mjs.map +2 -2
- package/dist/modules/sandboxes/session.d.mts +1 -1
- package/dist/modules/sandboxes/session.mjs +1 -1
- package/dist/modules/sandboxes/session.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-backends.d.mts +3 -0
- package/dist/modules/sandboxes/testing/fake-backends.mjs +35 -3
- package/dist/modules/sandboxes/testing/fake-backends.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-machine.mjs +8 -0
- package/dist/modules/sandboxes/testing/fake-machine.mjs.map +2 -2
- package/package.json +3 -3
|
@@ -38,6 +38,8 @@ const descriptorsOf = (tools) => tools.flatMap((tool) => {
|
|
|
38
38
|
return parsed.success ? [parsed.data] : [];
|
|
39
39
|
});
|
|
40
40
|
const LEASE_RENEW_MARGIN_MS = 5 * 6e4;
|
|
41
|
+
const ONLINE_POLL_MS = 500;
|
|
42
|
+
const ONLINE_POLL_MAX_MS = 5e3;
|
|
41
43
|
const MAX_LEASE_TTL_MS = 60 * 6e4;
|
|
42
44
|
const MIN_LEASE_TTL_MS = 6e4;
|
|
43
45
|
const platformOf = (platform) => platform === "windows" ? SandboxPlatform.Windows : SandboxPlatform.Posix;
|
|
@@ -49,7 +51,22 @@ const createDeviceSandboxProvider = (config) => {
|
|
|
49
51
|
MIN_LEASE_TTL_MS,
|
|
50
52
|
Math.min(config.leaseTtlMs ?? 15 * 6e4, MAX_LEASE_TTL_MS)
|
|
51
53
|
);
|
|
54
|
+
const sleep = config.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
55
|
+
const onlineWaitMs = config.onlineWaitMs ?? 0;
|
|
52
56
|
const api = createDeviceApiClient({ api: config.api, fetch: config.fetch, now });
|
|
57
|
+
const awaitOnline = async (caller, deviceId, timeoutMs) => {
|
|
58
|
+
const deadline = now() + timeoutMs;
|
|
59
|
+
let pollMs = ONLINE_POLL_MS;
|
|
60
|
+
for (; ; ) {
|
|
61
|
+
const device = await api.getDevice({ caller, deviceId });
|
|
62
|
+
if (device == null) throw new Error(`device ${deviceId} was not found`);
|
|
63
|
+
if (device.online) return device;
|
|
64
|
+
const left = deadline - now();
|
|
65
|
+
if (left <= 0) throw new Error(`${device.displayName} is not connected`);
|
|
66
|
+
await sleep(Math.min(pollMs, left));
|
|
67
|
+
pollMs = Math.min(pollMs * 2, ONLINE_POLL_MAX_MS);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
53
70
|
const attach = async (args) => {
|
|
54
71
|
const { ctx, access } = args;
|
|
55
72
|
const caller = {
|
|
@@ -57,9 +74,7 @@ const createDeviceSandboxProvider = (config) => {
|
|
|
57
74
|
actingUser: ctx.actingUser
|
|
58
75
|
};
|
|
59
76
|
const bindingId = ctx.session.bindingId;
|
|
60
|
-
const device = await
|
|
61
|
-
if (device == null) throw new Error(`device ${args.deviceId} was not found`);
|
|
62
|
-
if (!device.online) throw new Error(`${device.displayName} is not connected`);
|
|
77
|
+
const device = await awaitOnline(caller, args.deviceId, onlineWaitMs);
|
|
63
78
|
if (access !== SandboxAccess.Read && !device.writable) {
|
|
64
79
|
throw new Error(`${device.displayName} does not permit writes`);
|
|
65
80
|
}
|
|
@@ -110,7 +125,7 @@ const createDeviceSandboxProvider = (config) => {
|
|
|
110
125
|
description: `${device.displayName} (Final Device)`,
|
|
111
126
|
workspaceRoot: current.workspaceRoot,
|
|
112
127
|
transport: api.transport({ caller, lease }),
|
|
113
|
-
baseEnv: () => ({}),
|
|
128
|
+
baseEnv: async () => ({}),
|
|
114
129
|
randomUuid,
|
|
115
130
|
paths,
|
|
116
131
|
commands: commandEncoderFor(platform),
|
|
@@ -269,6 +284,14 @@ const createDeviceSandboxProvider = (config) => {
|
|
|
269
284
|
localMcp: localKnown ? localMcp : null
|
|
270
285
|
};
|
|
271
286
|
},
|
|
287
|
+
awaitRunning: async (ctx, rawState, { timeoutMs }) => {
|
|
288
|
+
const { deviceId } = deviceStateSchema.pick({ deviceId: true }).parse(rawState);
|
|
289
|
+
await awaitOnline(
|
|
290
|
+
{ organizationId: ctx.session.organizationId, actingUser: ctx.actingUser },
|
|
291
|
+
deviceId,
|
|
292
|
+
timeoutMs
|
|
293
|
+
);
|
|
294
|
+
},
|
|
272
295
|
artifactKey: async (ctx) => `device:${await resolveDeviceId(ctx)}`,
|
|
273
296
|
prepare: async (ctx) => ({ version: 1, deviceId: await resolveDeviceId(ctx) }),
|
|
274
297
|
start: async (ctx, openOptions, rawArtifact) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/device.ts"],
|
|
4
|
-
"sourcesContent": ["import { ProcessInspectionOperation } from '@finalai/agent-protocol/process-inspection';\nimport {\n SandboxAccess,\n toolDescriptorSchema,\n type JsonObject,\n type JsonValue,\n type ToolDescriptorIface,\n} from '@finalai/agent-provider';\nimport {\n defineSandboxProvider,\n SandboxCapability,\n type McpChannelEndpointIface,\n type NetworkSandboxSessionIface,\n type SandboxProviderHandleIface,\n type SandboxProviderIface,\n type SandboxProviderImplementationIface,\n type SandboxStartContextIface,\n} from '@finalai/agent-provider/sandbox';\nimport * as z from 'zod';\nimport { commandEncoderFor } from \"./commands.mjs\";\nimport {\n createDeviceApiClient,\n DEVICE_API_COMPUTER_USE_SERVER,\n DeviceApiCapability,\n DeviceApiLeaseAccess,\n type DeviceApiCallerIface,\n type DeviceApiConfigIface,\n type DeviceApiFetchIface,\n type DeviceApiLeaseIface,\n} from \"./device-api.mjs\";\nimport { computerOpenOptionsSchema, deviceEnvironmentSchema } from \"./environment.mjs\";\nimport { createMcpPipe } from \"./mcp-pipe.mjs\";\nimport { sandboxPathsFor, SandboxPlatform } from \"./paths.mjs\";\nimport { createTransportSession, sessionForAccess } from \"./session.mjs\";\n\nexport { DEVICE_API_ACTING_USER_HEADER, DeviceApiClientErrorCode } from \"./device-api.mjs\";\nexport type { DeviceApiConfigIface, DeviceApiFetchIface } from \"./device-api.mjs\";\nexport { DEVICE_API_KEY_SECRET, DEVICE_SANDBOX_SPECIFIER } from \"./environment.mjs\";\nexport { SandboxPlatform } from \"./paths.mjs\";\n\n// The `device` sandbox: a user's own computer running Final Device. The\n// provider is a client of the Device API (device-api-service), the Device's\n// backend, exactly as a vendor provider is a client of its vendor's API: it\n// leases a workspace there and drives files, processes, computer use and the\n// screen through that API with the organization's own API key. It knows nothing of how\n// the Device API reaches the Device (DEVICE_DIRECT_ENDPOINT.md). A Device is one\n// person's machine: every call names the user the session acts for\n// (ctx.actingUser, read per call since it changes between turns), and the Device\n// API refuses a Device that user does not own.\n\nexport interface DeviceSandboxProviderConfigIface {\n // The Device API origin, and each organization's own API key for it.\n api: DeviceApiConfigIface;\n fetch?: DeviceApiFetchIface;\n // Device leases last at most an hour; renewed while in use.\n leaseTtlMs?: number;\n log?: (message: string, fields?: Record<string, string | number | boolean | null>) => void;\n randomUuid?: () => string;\n now?: () => number;\n}\n\nconst deviceArtifactSchema = z.object({ version: z.literal(1), deviceId: z.string() });\nconst deviceStateSchema = z.object({\n version: z.literal(1),\n deviceId: z.string(),\n generation: z.number().int().min(1),\n access: z.nativeEnum(SandboxAccess),\n});\nexport type DeviceSandboxArtifact = z.infer<typeof deviceArtifactSchema>;\nexport type DeviceSandboxState = z.infer<typeof deviceStateSchema>;\n\nexport const DEVICE_COMPUTER_USE_SERVER = DEVICE_API_COMPUTER_USE_SERVER;\n\nexport interface DeviceSandboxSessionIface extends NetworkSandboxSessionIface {\n readonly capabilities: readonly SandboxCapability[];\n}\n\nexport type DeviceSandboxProviderIface = SandboxProviderIface<\n JsonValue,\n JsonValue,\n DeviceSandboxArtifact,\n DeviceSandboxState,\n DeviceSandboxSessionIface\n>;\n\n// The Device API's recorded tools as tool descriptors; one that does not fit\n// the contract is left out.\nconst descriptorsOf = (tools: readonly JsonObject[]): ToolDescriptorIface[] =>\n tools.flatMap((tool) => {\n const parsed = toolDescriptorSchema.safeParse({\n ...tool,\n description: tool.description ?? tool.title ?? tool.name,\n });\n return parsed.success ? [parsed.data] : [];\n });\n\nconst LEASE_RENEW_MARGIN_MS = 5 * 60_000;\nconst MAX_LEASE_TTL_MS = 60 * 60_000;\nconst MIN_LEASE_TTL_MS = 60_000;\n\nconst platformOf = (platform: string): SandboxPlatform =>\n platform === 'windows' ? SandboxPlatform.Windows : SandboxPlatform.Posix;\n\nexport const createDeviceSandboxProvider = (\n config: DeviceSandboxProviderConfigIface,\n): DeviceSandboxProviderIface => {\n const randomUuid = config.randomUuid ?? (() => crypto.randomUUID());\n const now = config.now ?? (() => Date.now());\n const log = config.log ?? (() => undefined);\n const leaseTtlMs = Math.max(\n MIN_LEASE_TTL_MS,\n Math.min(config.leaseTtlMs ?? 15 * 60_000, MAX_LEASE_TTL_MS),\n );\n const api = createDeviceApiClient({ api: config.api, fetch: config.fetch, now });\n\n const attach = async (args: {\n ctx: SandboxStartContextIface;\n deviceId: string;\n afterGeneration: number | null;\n passive?: boolean;\n access: SandboxAccess;\n }): Promise<{\n handle: SandboxProviderHandleIface<DeviceSandboxSessionIface>;\n generation: number;\n }> => {\n const { ctx, access } = args;\n const caller: DeviceApiCallerIface = {\n organizationId: ctx.session.organizationId,\n actingUser: ctx.actingUser,\n };\n const bindingId = ctx.session.bindingId;\n const device = await api.getDevice({ caller, deviceId: args.deviceId });\n if (device == null) throw new Error(`device ${args.deviceId} was not found`);\n if (!device.online) throw new Error(`${device.displayName} is not connected`);\n // Processes need the Device's write lease, so execute access does too.\n if (access !== SandboxAccess.Read && !device.writable) {\n throw new Error(`${device.displayName} does not permit writes`);\n }\n const leaseAccess =\n access === SandboxAccess.Read ? DeviceApiLeaseAccess.Read : DeviceApiLeaseAccess.Write;\n let current: DeviceApiLeaseIface =\n args.passive === true\n ? await api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n })\n : await api.lease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n ttlMs: leaseTtlMs,\n afterGeneration: args.afterGeneration ?? undefined,\n });\n if (\n args.passive === true &&\n (current.deviceId !== device.id ||\n current.access !== leaseAccess ||\n current.generation < (args.afterGeneration ?? 0) ||\n current.expiresAtMs <= now())\n )\n throw new Error('The existing Device lease is unavailable');\n let renewing: Promise<DeviceApiLeaseIface> | null = null;\n // The current lease handle, renewed shortly before it lapses.\n const lease = async (): Promise<string> => {\n if (current.expiresAtMs - now() < LEASE_RENEW_MARGIN_MS) {\n renewing ??= (\n args.passive === true\n ? api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n })\n : api.renew({ caller, lease: current.lease, ttlMs: leaseTtlMs })\n ).finally(() => {\n renewing = null;\n });\n current = await renewing;\n }\n return current.lease;\n };\n let released = false;\n const release = async (): Promise<void> => {\n if (released) return;\n released = true;\n await api.release({ caller, lease: current.lease });\n };\n\n const platform = platformOf(current.platform);\n const paths = sandboxPathsFor(platform);\n const session = createTransportSession({\n id: `${device.id}:${bindingId}`,\n description: `${device.displayName} (Final Device)`,\n workspaceRoot: current.workspaceRoot,\n transport: api.transport({ caller, lease }),\n baseEnv: () => ({}),\n randomUuid,\n paths,\n commands: commandEncoderFor(platform),\n network: {\n ports: [],\n getPortEndpoint: async () => {\n throw new Error('Final Device sandboxes expose no ports');\n },\n stop: release,\n destroy: release,\n },\n });\n\n const offered = new Set(current.capabilities);\n const processes = offered.has(DeviceApiCapability.Process);\n const localMcp = offered.has(DeviceApiCapability.LocalMcp);\n const computerUse = offered.has(DeviceApiCapability.ComputerUse);\n const capabilities = [SandboxCapability.Files];\n if (processes) capabilities.push(SandboxCapability.Process);\n if (localMcp) capabilities.push(SandboxCapability.LocalMcp);\n if (computerUse) capabilities.push(SandboxCapability.ComputerUse);\n if (device.screenReady) capabilities.push(SandboxCapability.Screen);\n\n // The Device's computer-use server, or one of its local MCP servers, is a byte\n // pipe in the Device API (`/v1/leases/:lease/mcp/:server/{open,write,read,close}`):\n // a channel endpoint whoever runs this handle opens in process.\n const channelEndpoint = (server: string): McpChannelEndpointIface => ({\n name: server,\n connect: async () => {\n const pipe = openMcpPipe(server);\n return {\n send: (message, options) => pipe.send(message, options?.abortSignal),\n receive: (options) => pipe.receive(options?.abortSignal),\n close: () => pipe.close(),\n };\n },\n });\n const openMcpPipe = (server: string) => {\n const clientId = randomUuid();\n const request = async (\n action: 'open' | 'write' | 'read' | 'close',\n dataBase64?: string,\n signal?: AbortSignal,\n ): Promise<string> =>\n api.pipe({\n caller,\n lease: await lease(),\n server,\n action,\n clientId,\n dataBase64,\n signal,\n });\n return createMcpPipe({\n wire: {\n open: async (signal) => {\n await request('open', undefined, signal);\n },\n write: async (data, signal) => {\n await request('write', data, signal);\n },\n read: (signal) => request('read', undefined, signal),\n close: async () => {\n await request('close').catch(() => undefined);\n },\n },\n });\n };\n const sandbox: DeviceSandboxSessionIface = { ...session, capabilities };\n if (computerUse) {\n sandbox.computerUse = async () => channelEndpoint(DEVICE_COMPUTER_USE_SERVER);\n }\n if (localMcp) {\n sandbox.localMcp = async () =>\n (await api.localMcpServers({ caller, lease: await lease() })).map(channelEndpoint);\n }\n if (device.screenReady) {\n sandbox.screen = (options) =>\n api.screen({\n caller,\n deviceId: device.id,\n sessionId: randomUuid(),\n viewOnly: options?.viewOnly ?? true,\n });\n }\n log('device_sandbox.attached', { deviceId: device.id, generation: current.generation });\n return {\n generation: current.generation,\n handle: {\n // The Device enforces the read lease too; the view refuses before a round trip.\n sandbox: sessionForAccess(sandbox, access),\n onSandboxStop: release,\n onSandboxDelete: release,\n onRuntimeShutdown: async () => undefined,\n },\n };\n };\n\n const environment = (\n rawOptions?: JsonValue,\n ): SandboxProviderImplementationIface<\n JsonValue,\n DeviceSandboxArtifact,\n DeviceSandboxState,\n DeviceSandboxSessionIface\n > => {\n const parsed = deviceEnvironmentSchema.safeParse(rawOptions ?? {});\n if (!parsed.success) throw new Error(`invalid device environment: ${parsed.error.message}`);\n const options = parsed.data;\n // The Device the environment names; the Device API checks on every call\n // that the acting user owns it.\n const resolveDeviceId = async (caller: DeviceApiCallerIface): Promise<string> => {\n if ('deviceId' in options) return options.deviceId;\n if ('pairing' in options) {\n const device = await api.resolvePairing({ caller, code: options.pairing });\n if (device == null) throw new Error('the Final Device pairing code is unknown or expired');\n return device.id;\n }\n // An unnamed binding uses the only Device of the user the session acts\n // for (the API lists only that user's Devices), never another member's.\n const devices = await api.listDevices({ caller });\n const [only] = devices;\n if (devices.length !== 1 || only == null) {\n throw new Error(\n devices.length === 0\n ? 'the user this session acts for has no Final Device'\n : 'the user this session acts for has several Final Devices; name one by id or pairing code',\n );\n }\n return only.id;\n };\n return {\n processes: async (ctx, rawState, request) => {\n const state = deviceStateSchema.parse(rawState);\n if (\n request.operation === ProcessInspectionOperation.Stop &&\n state.access === SandboxAccess.Read\n )\n throw new Error('stopping processes requires execute access');\n return api.inspectProcesses({\n caller: ctx,\n deviceId: state.deviceId,\n bindingId: ctx.bindingId,\n request,\n });\n },\n // What a lease on this Device grants, known from the registry without\n // leasing (the Device API's rule: processes, local MCP and computer use with\n // a write lease on a Device that allows processes). The computer-use\n // driver's readiness is checked when leased.\n capabilities: async (ctx) => {\n const device = await api.getDevice({\n caller: ctx,\n deviceId: await resolveDeviceId(ctx),\n });\n if (device == null) return null;\n const offered = [SandboxCapability.Files];\n if (device.writable && device.allowProcesses) {\n offered.push(\n SandboxCapability.Process,\n SandboxCapability.LocalMcp,\n SandboxCapability.ComputerUse,\n );\n }\n if (device.screenReady) offered.push(SandboxCapability.Screen);\n return offered;\n },\n // What the Device's servers last listed through the Device API (it records\n // them from the MCP pipes), so the projection lists them without leasing.\n // A Device the API has never seen list gives unknown; its first lease does.\n mcpCatalog: async (ctx) => {\n const catalog = await api.mcpCatalog({\n caller: ctx,\n deviceId: await resolveDeviceId(ctx),\n });\n const local = catalog.localServers;\n const localMcp: Record<string, ToolDescriptorIface[]> = {};\n let localKnown = local != null;\n for (const server of local?.servers ?? []) {\n if (server.tools == null) localKnown = false;\n else localMcp[server.name] = descriptorsOf(server.tools);\n }\n return {\n computerUse:\n catalog.computerUse == null ? null : descriptorsOf(catalog.computerUse.tools),\n localMcp: localKnown ? localMcp : null,\n };\n },\n artifactKey: async (ctx) => `device:${await resolveDeviceId(ctx)}`,\n prepare: async (ctx) => ({ version: 1, deviceId: await resolveDeviceId(ctx) }),\n start: async (ctx, openOptions, rawArtifact) => {\n const artifact = deviceArtifactSchema.parse(rawArtifact);\n const access = computerOpenOptionsSchema.parse(openOptions ?? {}).access;\n const { handle, generation } = await attach({\n ctx,\n deviceId: artifact.deviceId,\n afterGeneration: null,\n access,\n });\n return { handle, state: { version: 1, deviceId: artifact.deviceId, generation, access } };\n },\n // A Device never takes a lease back at an old generation once it lapsed,\n // so every attach leases a later one.\n resume: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await attach({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n access: state.access,\n })\n ).handle;\n },\n resumePassive: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await attach({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n access: state.access,\n passive: true,\n })\n ).handle;\n },\n onSessionEnd: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n await api.releaseWorkspaceLeases({\n caller: { organizationId: ctx.session.organizationId, actingUser: ctx.actingUser },\n deviceId: state.deviceId,\n bindingId: ctx.session.bindingId,\n });\n },\n // The lease lapses on its own; the next generation retires its processes.\n park: async (_ctx, _artifact, rawState) => deviceStateSchema.parse(rawState),\n };\n };\n\n return defineSandboxProvider({\n name: 'device',\n stateProtocolVersion: 1,\n environment,\n });\n};\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAOK;AACP,YAAY,OAAO;AACnB,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { ProcessInspectionOperation } from '@finalai/agent-protocol/process-inspection';\nimport {\n SandboxAccess,\n toolDescriptorSchema,\n type JsonObject,\n type JsonValue,\n type ToolDescriptorIface,\n} from '@finalai/agent-provider';\nimport {\n defineSandboxProvider,\n SandboxCapability,\n type McpChannelEndpointIface,\n type NetworkSandboxSessionIface,\n type SandboxProviderHandleIface,\n type SandboxProviderIface,\n type SandboxProviderImplementationIface,\n type SandboxStartContextIface,\n} from '@finalai/agent-provider/sandbox';\nimport * as z from 'zod';\nimport { commandEncoderFor } from \"./commands.mjs\";\nimport {\n createDeviceApiClient,\n DEVICE_API_COMPUTER_USE_SERVER,\n DeviceApiCapability,\n DeviceApiLeaseAccess,\n type DeviceApiCallerIface,\n type DeviceApiConfigIface,\n type DeviceApiDeviceIface,\n type DeviceApiFetchIface,\n type DeviceApiLeaseIface,\n} from \"./device-api.mjs\";\nimport { computerOpenOptionsSchema, deviceEnvironmentSchema } from \"./environment.mjs\";\nimport { createMcpPipe } from \"./mcp-pipe.mjs\";\nimport { sandboxPathsFor, SandboxPlatform } from \"./paths.mjs\";\nimport { createTransportSession, sessionForAccess } from \"./session.mjs\";\n\nexport { DEVICE_API_ACTING_USER_HEADER, DeviceApiClientErrorCode } from \"./device-api.mjs\";\nexport type { DeviceApiConfigIface, DeviceApiFetchIface } from \"./device-api.mjs\";\nexport { DEVICE_API_KEY_SECRET, DEVICE_SANDBOX_SPECIFIER } from \"./environment.mjs\";\nexport { SandboxPlatform } from \"./paths.mjs\";\n\n// The `device` sandbox: a user's own computer running Final Device. The\n// provider is a client of the Device API (device-api-service), the Device's\n// backend, exactly as a vendor provider is a client of its vendor's API: it\n// leases a workspace there and drives files, processes, computer use and the\n// screen through that API with the organization's own API key. It knows nothing of how\n// the Device API reaches the Device (DEVICE_DIRECT_ENDPOINT.md). A Device is one\n// person's machine: every call names the user the session acts for\n// (ctx.actingUser, read per call since it changes between turns), and the Device\n// API refuses a Device that user does not own.\n\nexport interface DeviceSandboxProviderConfigIface {\n // The Device API origin, and each organization's own API key for it.\n api: DeviceApiConfigIface;\n fetch?: DeviceApiFetchIface;\n // Device leases last at most an hour; renewed while in use.\n leaseTtlMs?: number;\n // How long start and resume wait for an offline Device to connect (default 0:\n // an offline Device fails at once); polled from 500 ms, doubling to 5 s.\n onlineWaitMs?: number;\n sleep?: (ms: number) => Promise<void>;\n log?: (message: string, fields?: Record<string, string | number | boolean | null>) => void;\n randomUuid?: () => string;\n now?: () => number;\n}\n\nconst deviceArtifactSchema = z.object({ version: z.literal(1), deviceId: z.string() });\nconst deviceStateSchema = z.object({\n version: z.literal(1),\n deviceId: z.string(),\n generation: z.number().int().min(1),\n access: z.nativeEnum(SandboxAccess),\n});\nexport type DeviceSandboxArtifact = z.infer<typeof deviceArtifactSchema>;\nexport type DeviceSandboxState = z.infer<typeof deviceStateSchema>;\n\nexport const DEVICE_COMPUTER_USE_SERVER = DEVICE_API_COMPUTER_USE_SERVER;\n\nexport interface DeviceSandboxSessionIface extends NetworkSandboxSessionIface {\n readonly capabilities: readonly SandboxCapability[];\n}\n\nexport type DeviceSandboxProviderIface = SandboxProviderIface<\n JsonValue,\n JsonValue,\n DeviceSandboxArtifact,\n DeviceSandboxState,\n DeviceSandboxSessionIface\n>;\n\n// The Device API's recorded tools as tool descriptors; one that does not fit\n// the contract is left out.\nconst descriptorsOf = (tools: readonly JsonObject[]): ToolDescriptorIface[] =>\n tools.flatMap((tool) => {\n const parsed = toolDescriptorSchema.safeParse({\n ...tool,\n description: tool.description ?? tool.title ?? tool.name,\n });\n return parsed.success ? [parsed.data] : [];\n });\n\nconst LEASE_RENEW_MARGIN_MS = 5 * 60_000;\nconst ONLINE_POLL_MS = 500;\nconst ONLINE_POLL_MAX_MS = 5_000;\nconst MAX_LEASE_TTL_MS = 60 * 60_000;\nconst MIN_LEASE_TTL_MS = 60_000;\n\nconst platformOf = (platform: string): SandboxPlatform =>\n platform === 'windows' ? SandboxPlatform.Windows : SandboxPlatform.Posix;\n\nexport const createDeviceSandboxProvider = (\n config: DeviceSandboxProviderConfigIface,\n): DeviceSandboxProviderIface => {\n const randomUuid = config.randomUuid ?? (() => crypto.randomUUID());\n const now = config.now ?? (() => Date.now());\n const log = config.log ?? (() => undefined);\n const leaseTtlMs = Math.max(\n MIN_LEASE_TTL_MS,\n Math.min(config.leaseTtlMs ?? 15 * 60_000, MAX_LEASE_TTL_MS),\n );\n const sleep =\n config.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));\n const onlineWaitMs = config.onlineWaitMs ?? 0;\n const api = createDeviceApiClient({ api: config.api, fetch: config.fetch, now });\n\n // A Device runs when it is connected to the Device API; nothing is leased before.\n const awaitOnline = async (\n caller: DeviceApiCallerIface,\n deviceId: string,\n timeoutMs: number,\n ): Promise<DeviceApiDeviceIface> => {\n const deadline = now() + timeoutMs;\n let pollMs = ONLINE_POLL_MS;\n for (;;) {\n const device = await api.getDevice({ caller, deviceId });\n if (device == null) throw new Error(`device ${deviceId} was not found`);\n if (device.online) return device;\n const left = deadline - now();\n if (left <= 0) throw new Error(`${device.displayName} is not connected`);\n await sleep(Math.min(pollMs, left));\n pollMs = Math.min(pollMs * 2, ONLINE_POLL_MAX_MS);\n }\n };\n\n const attach = async (args: {\n ctx: SandboxStartContextIface;\n deviceId: string;\n afterGeneration: number | null;\n passive?: boolean;\n access: SandboxAccess;\n }): Promise<{\n handle: SandboxProviderHandleIface<DeviceSandboxSessionIface>;\n generation: number;\n }> => {\n const { ctx, access } = args;\n const caller: DeviceApiCallerIface = {\n organizationId: ctx.session.organizationId,\n actingUser: ctx.actingUser,\n };\n const bindingId = ctx.session.bindingId;\n // The provider's awaitRunning, before the lease.\n const device = await awaitOnline(caller, args.deviceId, onlineWaitMs);\n // Processes need the Device's write lease, so execute access does too.\n if (access !== SandboxAccess.Read && !device.writable) {\n throw new Error(`${device.displayName} does not permit writes`);\n }\n const leaseAccess =\n access === SandboxAccess.Read ? DeviceApiLeaseAccess.Read : DeviceApiLeaseAccess.Write;\n let current: DeviceApiLeaseIface =\n args.passive === true\n ? await api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n })\n : await api.lease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n ttlMs: leaseTtlMs,\n afterGeneration: args.afterGeneration ?? undefined,\n });\n if (\n args.passive === true &&\n (current.deviceId !== device.id ||\n current.access !== leaseAccess ||\n current.generation < (args.afterGeneration ?? 0) ||\n current.expiresAtMs <= now())\n )\n throw new Error('The existing Device lease is unavailable');\n let renewing: Promise<DeviceApiLeaseIface> | null = null;\n // The current lease handle, renewed shortly before it lapses.\n const lease = async (): Promise<string> => {\n if (current.expiresAtMs - now() < LEASE_RENEW_MARGIN_MS) {\n renewing ??= (\n args.passive === true\n ? api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.session.id,\n bindingId,\n access: leaseAccess,\n })\n : api.renew({ caller, lease: current.lease, ttlMs: leaseTtlMs })\n ).finally(() => {\n renewing = null;\n });\n current = await renewing;\n }\n return current.lease;\n };\n let released = false;\n const release = async (): Promise<void> => {\n if (released) return;\n released = true;\n await api.release({ caller, lease: current.lease });\n };\n\n const platform = platformOf(current.platform);\n const paths = sandboxPathsFor(platform);\n const session = createTransportSession({\n id: `${device.id}:${bindingId}`,\n description: `${device.displayName} (Final Device)`,\n workspaceRoot: current.workspaceRoot,\n transport: api.transport({ caller, lease }),\n baseEnv: async () => ({}),\n randomUuid,\n paths,\n commands: commandEncoderFor(platform),\n network: {\n ports: [],\n getPortEndpoint: async () => {\n throw new Error('Final Device sandboxes expose no ports');\n },\n stop: release,\n destroy: release,\n },\n });\n\n const offered = new Set(current.capabilities);\n const processes = offered.has(DeviceApiCapability.Process);\n const localMcp = offered.has(DeviceApiCapability.LocalMcp);\n const computerUse = offered.has(DeviceApiCapability.ComputerUse);\n const capabilities = [SandboxCapability.Files];\n if (processes) capabilities.push(SandboxCapability.Process);\n if (localMcp) capabilities.push(SandboxCapability.LocalMcp);\n if (computerUse) capabilities.push(SandboxCapability.ComputerUse);\n if (device.screenReady) capabilities.push(SandboxCapability.Screen);\n\n // The Device's computer-use server, or one of its local MCP servers, is a byte\n // pipe in the Device API (`/v1/leases/:lease/mcp/:server/{open,write,read,close}`):\n // a channel endpoint whoever runs this handle opens in process.\n const channelEndpoint = (server: string): McpChannelEndpointIface => ({\n name: server,\n connect: async () => {\n const pipe = openMcpPipe(server);\n return {\n send: (message, options) => pipe.send(message, options?.abortSignal),\n receive: (options) => pipe.receive(options?.abortSignal),\n close: () => pipe.close(),\n };\n },\n });\n const openMcpPipe = (server: string) => {\n const clientId = randomUuid();\n const request = async (\n action: 'open' | 'write' | 'read' | 'close',\n dataBase64?: string,\n signal?: AbortSignal,\n ): Promise<string> =>\n api.pipe({\n caller,\n lease: await lease(),\n server,\n action,\n clientId,\n dataBase64,\n signal,\n });\n return createMcpPipe({\n wire: {\n open: async (signal) => {\n await request('open', undefined, signal);\n },\n write: async (data, signal) => {\n await request('write', data, signal);\n },\n read: (signal) => request('read', undefined, signal),\n close: async () => {\n await request('close').catch(() => undefined);\n },\n },\n });\n };\n const sandbox: DeviceSandboxSessionIface = { ...session, capabilities };\n if (computerUse) {\n sandbox.computerUse = async () => channelEndpoint(DEVICE_COMPUTER_USE_SERVER);\n }\n if (localMcp) {\n sandbox.localMcp = async () =>\n (await api.localMcpServers({ caller, lease: await lease() })).map(channelEndpoint);\n }\n if (device.screenReady) {\n sandbox.screen = (options) =>\n api.screen({\n caller,\n deviceId: device.id,\n sessionId: randomUuid(),\n viewOnly: options?.viewOnly ?? true,\n });\n }\n log('device_sandbox.attached', { deviceId: device.id, generation: current.generation });\n return {\n generation: current.generation,\n handle: {\n // The Device enforces the read lease too; the view refuses before a round trip.\n sandbox: sessionForAccess(sandbox, access),\n onSandboxStop: release,\n onSandboxDelete: release,\n onRuntimeShutdown: async () => undefined,\n },\n };\n };\n\n const environment = (\n rawOptions?: JsonValue,\n ): SandboxProviderImplementationIface<\n JsonValue,\n DeviceSandboxArtifact,\n DeviceSandboxState,\n DeviceSandboxSessionIface\n > => {\n const parsed = deviceEnvironmentSchema.safeParse(rawOptions ?? {});\n if (!parsed.success) throw new Error(`invalid device environment: ${parsed.error.message}`);\n const options = parsed.data;\n // The Device the environment names; the Device API checks on every call\n // that the acting user owns it.\n const resolveDeviceId = async (caller: DeviceApiCallerIface): Promise<string> => {\n if ('deviceId' in options) return options.deviceId;\n if ('pairing' in options) {\n const device = await api.resolvePairing({ caller, code: options.pairing });\n if (device == null) throw new Error('the Final Device pairing code is unknown or expired');\n return device.id;\n }\n // An unnamed binding uses the only Device of the user the session acts\n // for (the API lists only that user's Devices), never another member's.\n const devices = await api.listDevices({ caller });\n const [only] = devices;\n if (devices.length !== 1 || only == null) {\n throw new Error(\n devices.length === 0\n ? 'the user this session acts for has no Final Device'\n : 'the user this session acts for has several Final Devices; name one by id or pairing code',\n );\n }\n return only.id;\n };\n return {\n processes: async (ctx, rawState, request) => {\n const state = deviceStateSchema.parse(rawState);\n if (\n request.operation === ProcessInspectionOperation.Stop &&\n state.access === SandboxAccess.Read\n )\n throw new Error('stopping processes requires execute access');\n return api.inspectProcesses({\n caller: ctx,\n deviceId: state.deviceId,\n bindingId: ctx.bindingId,\n request,\n });\n },\n // What a lease on this Device grants, known from the registry without\n // leasing (the Device API's rule: processes, local MCP and computer use with\n // a write lease on a Device that allows processes). The computer-use\n // driver's readiness is checked when leased.\n capabilities: async (ctx) => {\n const device = await api.getDevice({\n caller: ctx,\n deviceId: await resolveDeviceId(ctx),\n });\n if (device == null) return null;\n const offered = [SandboxCapability.Files];\n if (device.writable && device.allowProcesses) {\n offered.push(\n SandboxCapability.Process,\n SandboxCapability.LocalMcp,\n SandboxCapability.ComputerUse,\n );\n }\n if (device.screenReady) offered.push(SandboxCapability.Screen);\n return offered;\n },\n // What the Device's servers last listed through the Device API (it records\n // them from the MCP pipes), so the projection lists them without leasing.\n // A Device the API has never seen list gives unknown; its first lease does.\n mcpCatalog: async (ctx) => {\n const catalog = await api.mcpCatalog({\n caller: ctx,\n deviceId: await resolveDeviceId(ctx),\n });\n const local = catalog.localServers;\n const localMcp: Record<string, ToolDescriptorIface[]> = {};\n let localKnown = local != null;\n for (const server of local?.servers ?? []) {\n if (server.tools == null) localKnown = false;\n else localMcp[server.name] = descriptorsOf(server.tools);\n }\n return {\n computerUse:\n catalog.computerUse == null ? null : descriptorsOf(catalog.computerUse.tools),\n localMcp: localKnown ? localMcp : null,\n };\n },\n awaitRunning: async (ctx, rawState, { timeoutMs }) => {\n const { deviceId } = deviceStateSchema.pick({ deviceId: true }).parse(rawState);\n await awaitOnline(\n { organizationId: ctx.session.organizationId, actingUser: ctx.actingUser },\n deviceId,\n timeoutMs,\n );\n },\n artifactKey: async (ctx) => `device:${await resolveDeviceId(ctx)}`,\n prepare: async (ctx) => ({ version: 1, deviceId: await resolveDeviceId(ctx) }),\n start: async (ctx, openOptions, rawArtifact) => {\n const artifact = deviceArtifactSchema.parse(rawArtifact);\n const access = computerOpenOptionsSchema.parse(openOptions ?? {}).access;\n const { handle, generation } = await attach({\n ctx,\n deviceId: artifact.deviceId,\n afterGeneration: null,\n access,\n });\n return { handle, state: { version: 1, deviceId: artifact.deviceId, generation, access } };\n },\n // A Device never takes a lease back at an old generation once it lapsed,\n // so every attach leases a later one.\n resume: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await attach({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n access: state.access,\n })\n ).handle;\n },\n resumePassive: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await attach({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n access: state.access,\n passive: true,\n })\n ).handle;\n },\n onSessionEnd: async (ctx, _artifact, rawState) => {\n const state = deviceStateSchema.parse(rawState);\n await api.releaseWorkspaceLeases({\n caller: { organizationId: ctx.session.organizationId, actingUser: ctx.actingUser },\n deviceId: state.deviceId,\n bindingId: ctx.session.bindingId,\n });\n },\n // The lease lapses on its own; the next generation retires its processes.\n park: async (_ctx, _artifact, rawState) => deviceStateSchema.parse(rawState),\n };\n };\n\n return defineSandboxProvider({\n name: 'device',\n stateProtocolVersion: 1,\n environment,\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["SandboxPlatform"]
|
|
7
7
|
}
|
|
@@ -20,7 +20,7 @@ export interface CreateTransportSessionArgsIface {
|
|
|
20
20
|
description: string;
|
|
21
21
|
workspaceRoot: string;
|
|
22
22
|
transport: SandboxTransportIface;
|
|
23
|
-
baseEnv: () => Record<string, string
|
|
23
|
+
baseEnv: () => Promise<Record<string, string>>;
|
|
24
24
|
randomUuid: () => string;
|
|
25
25
|
network: TransportSessionNetworkIface;
|
|
26
26
|
paths?: SandboxPathsIface;
|
|
@@ -34,7 +34,7 @@ const createTransportSession = (args) => {
|
|
|
34
34
|
id: args.randomUuid(),
|
|
35
35
|
command: commandLine(options),
|
|
36
36
|
cwd: resolvePath(options.cwd ?? args.workspaceRoot),
|
|
37
|
-
env: { ...args.baseEnv(), ...options.env },
|
|
37
|
+
env: { ...await args.baseEnv(), ...options.env },
|
|
38
38
|
timeoutMs: options.timeoutMs ?? null,
|
|
39
39
|
signal: options.abortSignal
|
|
40
40
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/session.ts"],
|
|
4
|
-
"sourcesContent": ["import { SandboxAccess } from '@finalai/agent-provider';\nimport type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport { exitStatus, type SandboxTransportIface, type TransportProcessIface } from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env.\n baseEnv: () => Record<string, string>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n}\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...args.baseEnv(), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n const { stream: stdout } = stdoutPush;\n const { stream: stderr } = stderrPush;\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n\n const kill = async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n const onAbort = (): void => {\n void kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n\n void (async () => {\n let stdoutOffset = 0;\n let stderrOffset = 0;\n let waitMs = transport.outputWaitMs;\n try {\n for (;;) {\n const output = await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n });\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n } finally {\n options.abortSignal?.removeEventListener('abort', onAbort);\n }\n })();\n\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n for await (const chunk of rechunk(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n stdout,\n stderr,\n write,\n wait: () => exit,\n kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess(options, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding is read-only');\n};\n\n// A read binding's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the lease the provider took.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n\nconst fileWriteRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding runs processes but does not write files');\n};\n\n// An execute binding's view of a handle: reads and processes pass through; file\n// writes and lifecycle are refused.\nexport const executeOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => ({\n ...session,\n writeFile: fileWriteRefusal,\n writeBinaryFile: fileWriteRefusal,\n writeTextFile: fileWriteRefusal,\n removePath: fileWriteRefusal,\n stop: fileWriteRefusal,\n destroy: fileWriteRefusal,\n});\n\n// The view of a handle that a binding's access allows.\nexport const sessionForAccess = <S extends NetworkSandboxSessionIface>(\n session: S,\n access: SandboxAccess,\n): S =>\n access === SandboxAccess.Read\n ? readOnlySession(session)\n : access === SandboxAccess.Execute\n ? executeOnlySession(session)\n : session;\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,qBAAqB;AAU9B,SAAS,0BAA0B,wBAAwB;AAC3D,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AACnC,SAAS,eAAe,cAAc,mBAAmB,eAAe;AACxE,SAAS,wBAAyD;AAClE,SAAS,mBAA2C;AACpD,SAAS,kBAA0E;
|
|
4
|
+
"sourcesContent": ["import { SandboxAccess } from '@finalai/agent-provider';\nimport type {\n DirEntryIface,\n NetworkSandboxSessionIface,\n PortProtocol,\n SandboxProcessIface,\n SandboxProcessOptionsIface,\n SandboxRunResultIface,\n StatIface,\n} from '@finalai/agent-provider/sandbox';\nimport { createConfinedFileReader, SandboxEntryType } from '@finalai/agent-provider/sandbox';\nimport { encodeSandboxText } from '@finalai/agent-provider/sandbox/bytes';\nimport { readTextFileStream } from '@finalai/agent-provider/sandbox/convenience';\nimport { collectStream, errorMessage, isNotFoundMessage, rechunk } from \"./bytes.mjs\";\nimport { posixCommandLine, type SandboxCommandEncoderIface } from \"./commands.mjs\";\nimport { POSIX_PATHS, type SandboxPathsIface } from \"./paths.mjs\";\nimport { exitStatus, type SandboxTransportIface, type TransportProcessIface } from \"./transport.mjs\";\n\n// NetworkSandboxSessionIface over any SandboxTransportIface. Provider specifics\n// (lifecycle, ports, extra capabilities) come in through `network`.\n\nexport interface TransportSessionNetworkIface {\n ports: readonly number[];\n getPortEndpoint(options: {\n port: number;\n protocol?: PortProtocol;\n }): Promise<{ url: string; headers?: Record<string, string> }>;\n stop(): Promise<void>;\n destroy(): Promise<void>;\n}\n\nexport interface CreateTransportSessionArgsIface {\n id: string;\n description: string;\n workspaceRoot: string;\n transport: SandboxTransportIface;\n // Environment every process gets (injected variables), under per-call env;\n // read again for every process.\n baseEnv: () => Promise<Record<string, string>>;\n randomUuid: () => string;\n network: TransportSessionNetworkIface;\n // The sandbox's path flavour and command encoding; POSIX unless a Device says otherwise.\n paths?: SandboxPathsIface;\n commands?: SandboxCommandEncoderIface;\n}\n\nconst textDecoder = (): TextDecoder => new TextDecoder('utf-8');\n\ninterface PushStreamIface {\n stream: ReadableStream<Uint8Array>;\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\ninterface ControllerHolderIface {\n controller?: ReadableStreamDefaultController<Uint8Array>;\n}\n\n// A stream the output pump pushes into; unread output stays queued in memory.\nconst pushStream = (): PushStreamIface => {\n const holder: ControllerHolderIface = {};\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n holder.controller = controller;\n },\n });\n return { stream, controller: holder.controller };\n};\n\nconst toStatEntry = (\n path: string,\n stat: { type: SandboxEntryType; size: number; modifiedAtMs: number | null; mode: number | null },\n): StatIface => ({\n path,\n type: stat.type,\n size: stat.size,\n modifiedAt: stat.modifiedAtMs == null ? null : new Date(stat.modifiedAtMs).toISOString(),\n mode: stat.mode,\n});\n\nexport const createTransportSession = (\n args: CreateTransportSessionArgsIface,\n): NetworkSandboxSessionIface => {\n const { transport } = args;\n const paths = args.paths ?? POSIX_PATHS;\n const commandLine = args.commands ?? posixCommandLine;\n const resolvePath = (path: string): string => paths.resolve(args.workspaceRoot, path);\n\n const spawnProcess = async (\n options: SandboxProcessOptionsIface,\n closeStdin: boolean,\n ): Promise<SandboxProcessIface> => {\n options.abortSignal?.throwIfAborted();\n const process: TransportProcessIface = await transport.exec({\n id: args.randomUuid(),\n command: commandLine(options),\n cwd: resolvePath(options.cwd ?? args.workspaceRoot),\n env: { ...(await args.baseEnv()), ...options.env },\n timeoutMs: options.timeoutMs ?? null,\n signal: options.abortSignal,\n });\n const stdoutPush = pushStream();\n const stderrPush = pushStream();\n const { stream: stdout } = stdoutPush;\n const { stream: stderr } = stderrPush;\n let settleExit: (value: { exitCode: number }) => void = () => undefined;\n let failExit: (error: Error) => void = () => undefined;\n const exit = new Promise<{ exitCode: number }>((resolve, reject) => {\n settleExit = resolve;\n failExit = reject;\n });\n // Avoid an unhandled rejection when the caller only reads the streams.\n exit.catch(() => undefined);\n\n const kill = async (signal?: string): Promise<void> => {\n await transport.input(\n signal == null || signal === 'SIGKILL' ? { process, terminate: true } : { process, signal },\n );\n };\n const onAbort = (): void => {\n void kill().catch(() => undefined);\n };\n options.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (options.abortSignal?.aborted === true) onAbort();\n\n void (async () => {\n let stdoutOffset = 0;\n let stderrOffset = 0;\n let waitMs = transport.outputWaitMs;\n try {\n for (;;) {\n const output = await transport.output({\n process,\n stdoutOffset,\n stderrOffset,\n waitMs,\n });\n if (output.stdout.data.byteLength > 0) stdoutPush.controller?.enqueue(output.stdout.data);\n if (output.stderr.data.byteLength > 0) stderrPush.controller?.enqueue(output.stderr.data);\n const advanced =\n output.stdout.nextOffset !== stdoutOffset || output.stderr.nextOffset !== stderrOffset;\n stdoutOffset = output.stdout.nextOffset;\n stderrOffset = output.stderr.nextOffset;\n if (output.exited && output.stdout.eof && output.stderr.eof) {\n stdoutPush.controller?.close();\n stderrPush.controller?.close();\n settleExit({ exitCode: exitStatus(output.exitCode, output.exitSignal) });\n return;\n }\n // Exited with unread output: drain it without waiting.\n waitMs = output.exited && advanced ? 0 : transport.outputWaitMs;\n }\n } catch (cause) {\n const error = cause instanceof Error ? cause : new Error(String(cause));\n stdoutPush.controller?.error(error);\n stderrPush.controller?.error(error);\n failExit(error);\n } finally {\n options.abortSignal?.removeEventListener('abort', onAbort);\n }\n })();\n\n const write = async (data: Uint8Array | string, writeOptions?: { end?: boolean }) => {\n const bytes = data instanceof Uint8Array ? data : new TextEncoder().encode(data);\n if (bytes.byteLength > 0) await transport.input({ process, data: bytes });\n if (writeOptions?.end === true) await transport.input({ process, eof: true });\n };\n\n const input = options.stdin;\n if (input instanceof ReadableStream) {\n void (async () => {\n for await (const chunk of rechunk(input, 64 * 1024)) await write(chunk);\n await write(new Uint8Array(), { end: true });\n })().catch(() => undefined);\n } else if (input != null) {\n await write(input, { end: true });\n } else if (closeStdin) {\n await transport.input({ process, eof: true });\n }\n\n return {\n pid: process.id,\n stdout,\n stderr,\n write,\n wait: () => exit,\n kill,\n };\n };\n\n const readStream = async (\n path: string,\n signal: AbortSignal | undefined,\n ): Promise<ReadableStream<Uint8Array> | null> => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal });\n if (stat == null) return null;\n if (stat.type === SandboxEntryType.Directory) throw new Error(`${resolved} is a directory`);\n // Read to end of file rather than to the stat size: a symlink's stat is the\n // link's own length, not its target's.\n let offset = 0;\n let done = false;\n return new ReadableStream<Uint8Array>({\n pull: async (controller) => {\n if (done) {\n controller.close();\n return;\n }\n const chunk = await transport.readChunk({\n path: resolved,\n offset,\n length: transport.fileChunkBytes,\n signal,\n });\n done = chunk.eof || chunk.bytes.byteLength === 0;\n offset += chunk.bytes.byteLength;\n if (chunk.bytes.byteLength > 0) controller.enqueue(chunk.bytes);\n if (done) controller.close();\n },\n });\n };\n\n const writeStream = async (\n path: string,\n content: ReadableStream<Uint8Array>,\n signal: AbortSignal | undefined,\n ): Promise<void> => {\n const resolved = resolvePath(path);\n await transport.createDirectory({ path: paths.dirname(resolved), signal });\n let offset = 0;\n for await (const chunk of rechunk(content, transport.fileChunkBytes)) {\n await transport.writeChunk({ path: resolved, offset, bytes: chunk, signal });\n offset += chunk.byteLength;\n }\n if (offset === 0)\n await transport.writeChunk({ path: resolved, offset: 0, bytes: new Uint8Array(), signal });\n };\n\n const bytesStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start: (controller) => {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\n const session: NetworkSandboxSessionIface = {\n id: args.id,\n description: args.description,\n defaultWorkingDirectory: args.workspaceRoot,\n ports: args.network.ports,\n resolvePath,\n readFile: async ({ path, abortSignal }) => readStream(path, abortSignal),\n readBinaryFile: async ({ path, abortSignal }) => {\n const stream = await readStream(path, abortSignal);\n return stream == null ? null : collectStream(stream);\n },\n readTextFile: async (options) => {\n const stream = await readStream(options.path, options.abortSignal);\n return stream == null ? null : readTextFileStream(stream, options);\n },\n writeFile: async ({ path, content, abortSignal }) => writeStream(path, content, abortSignal),\n writeBinaryFile: async ({ path, content, abortSignal }) =>\n writeStream(path, bytesStream(content), abortSignal),\n writeTextFile: async ({ path, content, encoding, abortSignal }) =>\n writeStream(path, bytesStream(encodeSandboxText(content, encoding)), abortSignal),\n spawn: (options) => spawnProcess(options, false),\n run: async (options): Promise<SandboxRunResultIface> => {\n const process = await spawnProcess(options, true);\n const [stdout, stderr, exit] = await Promise.all([\n collectStream(process.stdout),\n collectStream(process.stderr),\n process.wait(),\n ]);\n return {\n exitCode: exit.exitCode,\n stdout: textDecoder().decode(stdout),\n stderr: textDecoder().decode(stderr),\n };\n },\n stat: async ({ path, abortSignal }) => {\n const resolved = resolvePath(path);\n const stat = await transport.stat({ path: resolved, signal: abortSignal });\n return stat == null ? null : toStatEntry(resolved, stat);\n },\n readDir: async ({ path, abortSignal }): Promise<DirEntryIface[]> => {\n const resolved = resolvePath(path);\n const entries = await transport.readDir({ path: resolved, signal: abortSignal });\n return entries.map((entry) => ({\n name: entry.name,\n path: paths.join(resolved, entry.name),\n type: entry.type,\n size: entry.size,\n }));\n },\n removePath: async ({ path, recursive, force, abortSignal }) => {\n const resolved = resolvePath(path);\n try {\n await transport.remove({\n path: resolved,\n recursive: recursive === true,\n force: force === true,\n signal: abortSignal,\n });\n } catch (error) {\n if (force === true && isNotFoundMessage(errorMessage(error))) return;\n throw error;\n }\n },\n getPortEndpoint: (options) => args.network.getPortEndpoint(options),\n getPortUrl: async (options) => (await args.network.getPortEndpoint(options)).url,\n stop: () => args.network.stop(),\n destroy: () => args.network.destroy(),\n };\n return { ...session, confinedFileReader: createConfinedFileReader(session) };\n};\n\nconst readOnlyRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding is read-only');\n};\n\n// A read binding's view of a handle: reads pass through; writes, processes and\n// lifecycle are refused here as well as by the lease the provider took.\nexport const readOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => {\n const restricted: S = {\n ...session,\n writeFile: readOnlyRefusal,\n writeBinaryFile: readOnlyRefusal,\n writeTextFile: readOnlyRefusal,\n spawn: readOnlyRefusal,\n run: readOnlyRefusal,\n removePath: readOnlyRefusal,\n stop: readOnlyRefusal,\n destroy: readOnlyRefusal,\n };\n if (session.setNetworkPolicy != null) restricted.setNetworkPolicy = readOnlyRefusal;\n const screen = session.screen;\n if (screen != null)\n restricted.screen = async (options) => {\n if (options?.viewOnly === false) return readOnlyRefusal();\n return screen.call(session, options);\n };\n return restricted;\n};\n\nconst fileWriteRefusal = async (): Promise<never> => {\n throw new Error('this sandbox binding runs processes but does not write files');\n};\n\n// An execute binding's view of a handle: reads and processes pass through; file\n// writes and lifecycle are refused.\nexport const executeOnlySession = <S extends NetworkSandboxSessionIface>(session: S): S => ({\n ...session,\n writeFile: fileWriteRefusal,\n writeBinaryFile: fileWriteRefusal,\n writeTextFile: fileWriteRefusal,\n removePath: fileWriteRefusal,\n stop: fileWriteRefusal,\n destroy: fileWriteRefusal,\n});\n\n// The view of a handle that a binding's access allows.\nexport const sessionForAccess = <S extends NetworkSandboxSessionIface>(\n session: S,\n access: SandboxAccess,\n): S =>\n access === SandboxAccess.Read\n ? readOnlySession(session)\n : access === SandboxAccess.Execute\n ? executeOnlySession(session)\n : session;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -50,6 +50,9 @@ export interface FakeComputersApiIface {
|
|
|
50
50
|
imageBuildReads: {
|
|
51
51
|
count: number;
|
|
52
52
|
};
|
|
53
|
+
bootReads: {
|
|
54
|
+
count: number;
|
|
55
|
+
};
|
|
53
56
|
computerByName(name: string): FakeComputerIface | undefined;
|
|
54
57
|
}
|
|
55
58
|
export declare const FAKE_COMPUTERS_API_KEY = "fk_org_api_key";
|
|
@@ -7,7 +7,16 @@ import { posix } from "node:path";
|
|
|
7
7
|
import * as z from "zod";
|
|
8
8
|
import { concatBytes, decodeBase64, encodeBase64 } from "../bytes.mjs";
|
|
9
9
|
import { createFakeMachine, readFakeOutput } from "./fake-machine.mjs";
|
|
10
|
-
const STATE = {
|
|
10
|
+
const STATE = {
|
|
11
|
+
Pending: 1,
|
|
12
|
+
Running: 3,
|
|
13
|
+
Paused: 4,
|
|
14
|
+
Stopped: 5,
|
|
15
|
+
Resuming: 6,
|
|
16
|
+
Deleted: 9,
|
|
17
|
+
Shelved: 10
|
|
18
|
+
};
|
|
19
|
+
const NOT_RUNNING = "Computer is not running; resume it explicitly before this operation";
|
|
11
20
|
const FAKE_COMPUTERS_API_KEY = "fk_org_api_key";
|
|
12
21
|
const FAKE_PLATFORM_SANDBOX_IMAGE = "cimv_0190e0000000700080000000000001";
|
|
13
22
|
const json = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
@@ -59,6 +68,16 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
59
68
|
const shelveReads = { count: 0 };
|
|
60
69
|
const shelving = /* @__PURE__ */ new Map();
|
|
61
70
|
const imageBuildReads = { count: 1 };
|
|
71
|
+
const bootReads = { count: 1 };
|
|
72
|
+
const booting = /* @__PURE__ */ new Map();
|
|
73
|
+
const boot = (computer, state) => {
|
|
74
|
+
if (bootReads.count <= 0) {
|
|
75
|
+
computer.state = STATE.Running;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
computer.state = state;
|
|
79
|
+
booting.set(computer.id, bootReads.count);
|
|
80
|
+
};
|
|
62
81
|
const imageReads = /* @__PURE__ */ new Map();
|
|
63
82
|
const bases = /* @__PURE__ */ new Map();
|
|
64
83
|
const baseCaptureReads = { count: 1 };
|
|
@@ -134,10 +153,11 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
134
153
|
const computer2 = {
|
|
135
154
|
id,
|
|
136
155
|
name,
|
|
137
|
-
state: STATE.
|
|
156
|
+
state: STATE.Pending,
|
|
138
157
|
body: jsonObjectSchema.parse(body),
|
|
139
158
|
machine: args.machineFor?.({ id, name }) ?? createFakeMachine()
|
|
140
159
|
};
|
|
160
|
+
boot(computer2, STATE.Pending);
|
|
141
161
|
computers.set(id, computer2);
|
|
142
162
|
if (key != null) byIdempotency.set(key, id);
|
|
143
163
|
return json({ computer: wire(computer2) });
|
|
@@ -236,6 +256,13 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
236
256
|
installed: args.computerUseAvailable ?? false
|
|
237
257
|
});
|
|
238
258
|
if (request.method === "GET" && rest === "") {
|
|
259
|
+
const starting = booting.get(computer.id);
|
|
260
|
+
if (starting != null) {
|
|
261
|
+
if (starting <= 0) {
|
|
262
|
+
booting.delete(computer.id);
|
|
263
|
+
computer.state = STATE.Running;
|
|
264
|
+
} else booting.set(computer.id, starting - 1);
|
|
265
|
+
}
|
|
239
266
|
const left = shelving.get(computer.id);
|
|
240
267
|
if (left != null) {
|
|
241
268
|
if (left <= 0) {
|
|
@@ -268,8 +295,10 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
268
295
|
actions.push({ computerId: computer.id, action: name });
|
|
269
296
|
if (actionConflicts.has(name))
|
|
270
297
|
return json({ error: "a lifecycle transition is committing" }, 409);
|
|
298
|
+
if (name !== "resume" && name !== "start") booting.delete(computer.id);
|
|
271
299
|
if (name === "pause") computer.state = STATE.Paused;
|
|
272
|
-
if (name === "resume"
|
|
300
|
+
if (name === "resume") boot(computer, STATE.Resuming);
|
|
301
|
+
if (name === "start") boot(computer, STATE.Pending);
|
|
273
302
|
if (name === "stop") {
|
|
274
303
|
computer.state = shelveReads.count > 0 ? STATE.Stopped : STATE.Shelved;
|
|
275
304
|
if (shelveReads.count > 0) shelving.set(computer.id, shelveReads.count);
|
|
@@ -277,6 +306,8 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
277
306
|
if (name === "delete") computer.state = STATE.Deleted;
|
|
278
307
|
return json({ computer: wire(computer) });
|
|
279
308
|
}
|
|
309
|
+
if (computer.state !== STATE.Running && (rest === "/exec" || rest === "/run" || rest === "/files" || rest.startsWith("/processes/")))
|
|
310
|
+
return json({ error: NOT_RUNNING }, 409);
|
|
280
311
|
if (request.method === "POST" && (rest === "/exec" || rest === "/run")) {
|
|
281
312
|
const parsed = z.object({
|
|
282
313
|
command: z.string(),
|
|
@@ -365,6 +396,7 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
365
396
|
actionConflicts,
|
|
366
397
|
shelveReads,
|
|
367
398
|
imageBuildReads,
|
|
399
|
+
bootReads,
|
|
368
400
|
computerByName: (name) => [...computers.values()].find((computer) => computer.name === name)
|
|
369
401
|
};
|
|
370
402
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/sandboxes/testing/fake-backends.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n jsonObjectSchema,\n jsonValueSchema,\n type JsonObject,\n type JsonValue,\n} from '@finalai/agent-provider';\nimport { ComputerEgressPolicy } from '@finalai/computers';\nimport { posix } from 'node:path';\nimport * as z from 'zod';\nimport { concatBytes, decodeBase64, encodeBase64 } from \"../bytes.mjs\";\nimport { createFakeMachine, readFakeOutput, type FakeMachineIface } from \"./fake-machine.mjs\";\n\n// A fake public Computers API (vm-service `/v1` over fetch, API-key bearer),\n// backed by in-memory machines. The `device` provider's fakes live with the\n// Device API (device-api-service/src/testing), which it is tested against.\n\nexport interface FakeComputerIface {\n id: string;\n name: string;\n state: number;\n body: JsonObject;\n machine: FakeMachineIface;\n}\n\nexport interface FakeImageIface {\n id: string;\n source: 'platform' | 'customer';\n reference: string;\n lineage: string;\n state: 'pending' | 'building' | 'ready' | 'failed' | 'expired';\n error: string | null;\n}\n\nexport interface FakeBaseIface {\n id: string;\n computerId: string;\n state: 'capturing' | 'ready' | 'failed' | 'expired';\n error: string | null;\n retained: boolean;\n}\n\nexport interface FakeComputersApiIface {\n fetch: typeof fetch;\n computers: Map<string, FakeComputerIface>;\n images: Map<string, FakeImageIface>;\n bases: Map<string, FakeBaseIface>;\n // Base reads a fresh capture answers `capturing` before it settles.\n baseCaptureReads: { count: number };\n // Set: the next captures settle `failed` with this error instead of `ready`.\n captureFailure: { error: string | null };\n requests: { method: string; path: string; body: JsonValue; headers: Headers }[];\n actions: { computerId: string; action: string }[];\n // Actions answered 409 (a conflicting transition) without changing the computer.\n actionConflicts: Set<string>;\n // Computer reads a stopped computer answers `stopped` before it is shelved.\n shelveReads: { count: number };\n // Image reads a fresh registration answers `building` before it is ready.\n imageBuildReads: { count: number };\n computerByName(name: string): FakeComputerIface | undefined;\n}\n\nconst STATE = { Running: 3, Paused: 4, Stopped: 5, Deleted: 9, Shelved: 10 } as const;\nexport const FAKE_COMPUTERS_API_KEY = 'fk_org_api_key';\nexport const FAKE_PLATFORM_SANDBOX_IMAGE = 'cimv_0190e0000000700080000000000001';\n\nconst json = (body: JsonValue, status = 200): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n\n// What POST /v1/computers accepts (vm-service/src/routes/v1.ts); anything else,\n// such as the internal surface's image, lineage, projectArtifact or the\n// platform-only policies, is refused so a test fails when the provider asks for it.\nconst createComputerSchema = z.strictObject({\n name: z.string().optional(),\n regionSlug: z.string().optional(),\n imageVersionId: z.string().startsWith('cimv_').optional(),\n baseId: z.string().startsWith('part_').optional(),\n idlePolicy: z.enum(['shelve', 'pause', 'delete']).optional(),\n network: z\n .strictObject({\n egress: z.nativeEnum(ComputerEgressPolicy),\n allow: z.array(z.string()).optional(),\n })\n .optional(),\n teamId: z.string().startsWith('team_').optional(),\n vcpu: z.number().optional(),\n memoryMb: z.number().optional(),\n diskGb: z.number().optional(),\n userVolumeGb: z.number().nullable().optional(),\n env: z.record(z.string(), z.string()).optional(),\n setupScript: z.string().optional(),\n commandMode: z.number().optional(),\n customCommand: z.string().optional(),\n httpPort: z.number().optional(),\n httpPublic: z.boolean().optional(),\n ephemeral: z.boolean().optional(),\n idleTimeoutSeconds: z.number().optional(),\n autoResume: z.boolean().optional(),\n tags: z.array(z.string()).optional(),\n comment: z.string().optional(),\n});\n\nexport const createFakeComputersApi = (\n args: {\n machineFor?: (computer: { id: string; name: string }) => FakeMachineIface;\n computerUseAvailable?: boolean;\n screenAvailable?: boolean;\n } = {},\n): FakeComputersApiIface => {\n const computers = new Map<string, FakeComputerIface>();\n const images = new Map<string, FakeImageIface>([\n [\n FAKE_PLATFORM_SANDBOX_IMAGE,\n {\n id: FAKE_PLATFORM_SANDBOX_IMAGE,\n source: 'platform',\n reference: 'ghcr.io/final/sandbox:release',\n lineage: 'sandbox',\n state: 'ready',\n error: null,\n },\n ],\n ]);\n const requests: FakeComputersApiIface['requests'] = [];\n const actions: FakeComputersApiIface['actions'] = [];\n const actionConflicts = new Set<string>();\n const shelveReads = { count: 0 };\n const shelving = new Map<string, number>();\n const imageBuildReads = { count: 1 };\n const imageReads = new Map<string, number>();\n const bases = new Map<string, FakeBaseIface>();\n const baseCaptureReads = { count: 1 };\n const captureFailure: FakeComputersApiIface['captureFailure'] = { error: null };\n const captureErrors = new Map<string, string>();\n const baseReads = new Map<string, number>();\n let computerCount = 0;\n const byIdempotency = new Map<string, string>();\n\n const wire = (computer: FakeComputerIface) => ({\n id: computer.id,\n name: computer.name,\n state: computer.state,\n imageVersionId: z\n .string()\n .nullable()\n .catch(null)\n .parse(computer.body.imageVersionId ?? null),\n teamId: z\n .string()\n .nullable()\n .catch(null)\n .parse(computer.body.teamId ?? null),\n hostname: `${computer.name}-k3p9x7qd.virtual-machine.lax-2.example`,\n tags: [],\n });\n const imageWire = (image: FakeImageIface) => ({\n ...image,\n digest: `sha256:${image.id}`,\n sizes: ['standard'],\n registryCredentialId: null,\n createdAt: null,\n readyAt: null,\n lastUsedAt: null,\n });\n const baseWire = (base: FakeBaseIface) => ({\n ...base,\n image: 'ghcr.io/final/sandbox:release',\n digest: `sha256:${'b'.repeat(64)}`,\n diskGb: 20,\n createdAt: null,\n readyAt: null,\n lastUsedAt: null,\n });\n const find = (idOrName: string): FakeComputerIface | undefined =>\n computers.get(idOrName) ??\n [...computers.values()].find(\n (computer) => computer.name === idOrName && computer.state !== STATE.Deleted,\n );\n\n const handle = async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n const text = request.method === 'GET' ? '' : await request.text();\n const body = text === '' ? null : jsonValueSchema.parse(JSON.parse(text));\n requests.push({ method: request.method, path: url.pathname, body, headers: request.headers });\n if (!url.pathname.startsWith('/v1/')) return json({ error: 'not found' }, 404);\n if (request.headers.get('authorization') !== `Bearer ${FAKE_COMPUTERS_API_KEY}`) {\n return json({ error: 'unauthorized' }, 401);\n }\n const path = url.pathname.slice('/v1'.length);\n if (request.method === 'POST' && path === '/computers') {\n const parsed = createComputerSchema.safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n if ((parsed.data.imageVersionId == null) === (parsed.data.baseId == null)) {\n return json({ error: 'pass exactly one of imageVersionId and baseId' }, 400);\n }\n if (parsed.data.imageVersionId != null) {\n const image = images.get(parsed.data.imageVersionId);\n if (image == null) return json({ error: 'image version not found' }, 404);\n if (image.state !== 'ready') return json({ error: `image version is ${image.state}` }, 409);\n }\n if (parsed.data.baseId != null) {\n const base = bases.get(parsed.data.baseId);\n if (base == null) return json({ error: 'base not found' }, 404);\n if (base.state !== 'ready') return json({ error: `base is ${base.state}` }, 409);\n }\n const key = request.headers.get('idempotency-key');\n if (parsed.data.ephemeral === true && parsed.data.idlePolicy === 'pause')\n return json({ error: 'ephemeral VMs are always deleted when idle' }, 400);\n const existing = key == null ? undefined : computers.get(byIdempotency.get(key) ?? '');\n if (existing != null) return json({ computer: wire(existing) });\n computerCount += 1;\n const id = `comp_0190a0000000700080000000${String(computerCount).padStart(4, '0')}`;\n const name = parsed.data.name ?? `computer-${computerCount}`;\n const computer: FakeComputerIface = {\n id,\n name,\n state: STATE.Running,\n body: jsonObjectSchema.parse(body),\n machine: args.machineFor?.({ id, name }) ?? createFakeMachine(),\n };\n computers.set(id, computer);\n if (key != null) byIdempotency.set(key, id);\n return json({ computer: wire(computer) });\n }\n if (request.method === 'GET' && path === '/images') {\n const lineage = url.searchParams.get('lineage');\n const state = url.searchParams.get('state');\n const matching = [...images.values()]\n .filter((image) => lineage == null || image.lineage === lineage)\n .filter((image) => state == null || image.state === state)\n .reverse();\n return json({ images: matching.map(imageWire) });\n }\n if (request.method === 'POST' && path === '/images') {\n const parsed = z\n .strictObject({\n reference: z.string(),\n registryCredentialId: z.string().optional(),\n sizes: z.array(z.string()).optional(),\n })\n .safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n const known = [...images.values()].find(\n (image) =>\n image.reference === parsed.data.reference &&\n (image.state === 'pending' || image.state === 'building' || image.state === 'ready'),\n );\n if (known != null) return json({ image: imageWire(known) });\n const id = `cimv_0190f0000000700080000000${String(images.size + 1).padStart(4, '0')}`;\n const image: FakeImageIface = {\n id,\n source: 'customer',\n reference: parsed.data.reference,\n lineage: parsed.data.reference,\n state: 'building',\n error: null,\n };\n images.set(id, image);\n return json({ image: imageWire(image) }, 201);\n }\n const imageMatch = /^\\/images\\/([^/]+)$/.exec(path);\n if (request.method === 'GET' && imageMatch != null) {\n const image = images.get(decodeURIComponent(imageMatch[1] ?? ''));\n if (image == null) return json({ error: 'image not found' }, 404);\n const reads = (imageReads.get(image.id) ?? 0) + 1;\n imageReads.set(image.id, reads);\n if (image.state === 'building' && reads > imageBuildReads.count) image.state = 'ready';\n return json({ image: imageWire(image) });\n }\n if (request.method === 'POST' && path === '/tokens') {\n const parsed = z\n .strictObject({\n name: z.string(),\n computerId: z.string().startsWith('comp_').optional(),\n scopes: z.array(z.string()).optional(),\n expiresAt: z.string().optional(),\n })\n .safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n return json({ id: 'ctok_1', token: 'fvm1.http-token' });\n }\n const baseMatch = /^\\/bases\\/([^/]+)$/.exec(path);\n if (request.method === 'PATCH' && baseMatch != null) {\n const base = bases.get(decodeURIComponent(baseMatch[1] ?? ''));\n if (base == null) return json({ error: 'base not found' }, 404);\n const retained = z.object({ retained: z.boolean() }).parse(body);\n base.retained = retained.retained;\n return json({ base: baseWire(base) });\n }\n if (request.method === 'GET' && baseMatch != null) {\n const base = bases.get(decodeURIComponent(baseMatch[1] ?? ''));\n if (base == null) return json({ error: 'base not found' }, 404);\n const reads = (baseReads.get(base.id) ?? 0) + 1;\n baseReads.set(base.id, reads);\n if (base.state === 'capturing' && reads > baseCaptureReads.count) {\n const error = captureErrors.get(base.id) ?? null;\n base.state = error == null ? 'ready' : 'failed';\n base.error = error;\n }\n return json({ base: baseWire(base) });\n }\n const computerMatch = /^\\/computers\\/([^/]+)(\\/.*)?$/.exec(path);\n if (computerMatch == null) return json({ error: `no route ${path}` }, 404);\n const computer = find(decodeURIComponent(computerMatch[1] ?? ''));\n if (computer == null || computer.state === STATE.Deleted)\n return json({ error: 'computer not found' }, 404);\n const rest = computerMatch[2] ?? '';\n const machine = computer.machine;\n if (request.method === 'GET' && rest === '/screen')\n return json({ available: args.screenAvailable ?? false });\n if (request.method === 'POST' && rest === '/screen')\n return json({\n url: `https://computer-api.example/v1/computers/${computer.id}/screen/view#screen-token`,\n token: 'screen-token',\n expiresAt: new Date(Date.now() + 900_000).toISOString(),\n });\n if (request.method === 'PATCH' && rest === '') {\n const patch = createComputerSchema.pick({ network: true }).parse(body);\n if (patch.network != null) computer.body.network = patch.network;\n return json({ computer: wire(computer) });\n }\n if (request.method === 'GET' && rest === '/computer-use')\n return json({\n available: args.computerUseAvailable ?? false,\n installed: args.computerUseAvailable ?? false,\n });\n if (request.method === 'GET' && rest === '') {\n const left = shelving.get(computer.id);\n if (left != null) {\n if (left <= 0) {\n shelving.delete(computer.id);\n computer.state = STATE.Shelved;\n } else shelving.set(computer.id, left - 1);\n }\n return json({ computer: wire(computer) });\n }\n if (request.method === 'POST' && rest === '/capture') {\n if (computer.state !== STATE.Running) return json({ error: 'not running' }, 409);\n if (computer.body.userVolumeGb !== null) return json({ error: 'has a user volume' }, 409);\n const id = `part_0190b0000000700080000000${String(bases.size + 1).padStart(4, '0')}`;\n const capture = z.object({ retained: z.boolean().default(false) }).parse(body ?? {});\n const base: FakeBaseIface = {\n id,\n computerId: computer.id,\n state: 'capturing',\n error: null,\n retained: capture.retained,\n };\n bases.set(id, base);\n if (captureFailure.error != null) captureErrors.set(id, captureFailure.error);\n computer.state = STATE.Stopped;\n return json({ base: baseWire(base) }, 202);\n }\n const action = /^\\/actions\\/(.+)$/.exec(rest);\n if (request.method === 'POST' && action != null) {\n const name = action[1] ?? '';\n actions.push({ computerId: computer.id, action: name });\n if (actionConflicts.has(name))\n return json({ error: 'a lifecycle transition is committing' }, 409);\n if (name === 'pause') computer.state = STATE.Paused;\n if (name === 'resume' || name === 'start') computer.state = STATE.Running;\n // A computer that is not ephemeral shelves on stop (vm-service vm-ops).\n if (name === 'stop') {\n computer.state = shelveReads.count > 0 ? STATE.Stopped : STATE.Shelved;\n if (shelveReads.count > 0) shelving.set(computer.id, shelveReads.count);\n }\n if (name === 'delete') computer.state = STATE.Deleted;\n return json({ computer: wire(computer) });\n }\n if (request.method === 'POST' && (rest === '/exec' || rest === '/run')) {\n const parsed = z\n .object({\n command: z.string(),\n cwd: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .parse(body);\n const process = machine.exec({\n id: `proc-${machine.execs.length + 1}`,\n command: parsed.command,\n cwd: parsed.cwd ?? '/',\n env: parsed.env ?? {},\n });\n if (rest === '/exec')\n return json({ id: process.id, bootId: process.bootId, pid: 1, state: 'running' });\n const output = await readFakeOutput(process, { stdout: 0, stderr: 0, waitMs: 50 });\n return json({\n id: process.id,\n bootId: process.bootId,\n pid: 1,\n timedOut: false,\n elapsedMs: 1,\n ...outputBody(output, process, 0, 0),\n });\n }\n const processMatch = /^\\/processes\\/([^/]+)\\/(output|input|terminate)$/.exec(rest);\n if (processMatch != null) {\n const process = machine.processes.get(processMatch[1] ?? '');\n if (process == null) return json({ error: 'process not found' }, 404);\n if (processMatch[2] === 'output') {\n const stdoutOffset = Number(url.searchParams.get('stdoutOffset') ?? 0);\n const stderrOffset = Number(url.searchParams.get('stderrOffset') ?? 0);\n const output = await readFakeOutput(process, {\n stdout: stdoutOffset,\n stderr: stderrOffset,\n waitMs: Number(url.searchParams.get('waitMs') ?? 0),\n });\n return json(outputBody(output, process, stdoutOffset, stderrOffset));\n }\n if (processMatch[2] === 'terminate') {\n process.terminate();\n return json({ ok: true });\n }\n const input = z\n .object({\n dataB64: z.string().optional(),\n eof: z.boolean().optional(),\n signal: z.string().optional(),\n })\n .parse(body);\n if (input.dataB64 != null) process.write(decodeBase64(input.dataB64));\n if (input.eof === true) process.eof();\n if (input.signal != null) process.terminate(input.signal);\n return json({ ok: true });\n }\n if (rest === '/files' && request.method === 'GET') {\n const path = url.searchParams.get('path') ?? '';\n // Reads follow symlinks.\n const content = machine.files.get(machine.links.get(path) ?? path);\n if (content == null) return json({ error: `${path}: No such file or directory` }, 502);\n const offset = Number(url.searchParams.get('offset') ?? 0);\n const length = Number(url.searchParams.get('length') ?? content.byteLength);\n const slice = content.subarray(offset, offset + length);\n return json({\n dataB64: encodeBase64(slice),\n eof: offset + slice.byteLength >= content.byteLength,\n offset,\n });\n }\n if (rest === '/files' && request.method === 'PUT') {\n const parsed = z\n .object({ path: z.string(), dataB64: z.string(), append: z.boolean().optional() })\n .parse(body);\n if (!machine.dirs.has(posix.dirname(parsed.path)))\n return json({ error: 'No such file or directory' }, 502);\n const data = decodeBase64(parsed.dataB64);\n const previous =\n parsed.append === true\n ? (machine.files.get(parsed.path) ?? new Uint8Array())\n : new Uint8Array();\n machine.files.set(parsed.path, concatBytes([previous, data]));\n return json({ bytesWritten: data.byteLength });\n }\n return json({ error: `no route ${request.method} ${path}` }, 404);\n };\n\n return {\n fetch: async (input, init) => handle(new Request(input, init)),\n computers,\n images,\n bases,\n baseCaptureReads,\n captureFailure,\n requests,\n actions,\n actionConflicts,\n shelveReads,\n imageBuildReads,\n computerByName: (name) => [...computers.values()].find((computer) => computer.name === name),\n };\n};\n\nconst outputBody = (\n output: { stdout: Uint8Array; stderr: Uint8Array; exited: boolean },\n process: {\n exitCode: number | null;\n signal: string | null;\n stdout: Uint8Array;\n stderr: Uint8Array;\n },\n stdoutOffset: number,\n stderrOffset: number,\n) => ({\n stdout: {\n dataB64: encodeBase64(output.stdout),\n offset: stdoutOffset,\n nextOffset: stdoutOffset + output.stdout.byteLength,\n eof: output.exited && stdoutOffset + output.stdout.byteLength >= process.stdout.byteLength,\n },\n stderr: {\n dataB64: encodeBase64(output.stderr),\n offset: stderrOffset,\n nextOffset: stderrOffset + output.stderr.byteLength,\n eof: output.exited && stderrOffset + output.stderr.byteLength >= process.stderr.byteLength,\n },\n state: output.exited ? 'exited' : 'running',\n result: output.exited ? { exitCode: process.exitCode, signal: process.signal } : null,\n});\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,aAAa;AACtB,YAAY,OAAO;AACnB,SAAS,aAAa,cAAc,oBAAoB;AACxD,SAAS,mBAAmB,sBAA6C;AAmDzE,MAAM,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;AACpE,MAAM,yBAAyB;AAC/B,MAAM,8BAA8B;AAE3C,MAAM,OAAO,CAAC,MAAiB,SAAS,QACtC,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG,EAAE,QAAQ,SAAS,EAAE,gBAAgB,mBAAmB,EAAE,CAAC;AAKhG,MAAM,uBAAuB,EAAE,aAAa;AAAA,EAC1C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,gBAAgB,EAAE,OAAO,EAAE,WAAW,OAAO,EAAE,SAAS;AAAA,EACxD,QAAQ,EAAE,OAAO,EAAE,WAAW,OAAO,EAAE,SAAS;AAAA,EAChD,YAAY,EAAE,KAAK,CAAC,UAAU,SAAS,QAAQ,CAAC,EAAE,SAAS;AAAA,EAC3D,SAAS,EACN,aAAa;AAAA,IACZ,QAAQ,EAAE,WAAW,oBAAoB;AAAA,IACzC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACtC,CAAC,EACA,SAAS;AAAA,EACZ,QAAQ,EAAE,OAAO,EAAE,WAAW,OAAO,EAAE,SAAS;AAAA,EAChD,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EAC7C,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAC/C,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,YAAY,EAAE,QAAQ,EAAE,SAAS;AAAA,EACjC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,EAChC,oBAAoB,EAAE,OAAO,EAAE,SAAS;AAAA,EACxC,YAAY,EAAE,QAAQ,EAAE,SAAS;AAAA,EACjC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACnC,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAEM,MAAM,yBAAyB,CACpC,OAII,CAAC,MACqB;AAC1B,QAAM,YAAY,oBAAI,IAA+B;AACrD,QAAM,SAAS,oBAAI,IAA4B;AAAA,IAC7C;AAAA,MACE;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AACD,QAAM,WAA8C,CAAC;AACrD,QAAM,UAA4C,CAAC;AACnD,QAAM,kBAAkB,oBAAI,IAAY;AACxC,QAAM,cAAc,EAAE,OAAO,EAAE;AAC/B,QAAM,WAAW,oBAAI,IAAoB;AACzC,QAAM,kBAAkB,EAAE,OAAO,EAAE;AACnC,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,QAAQ,oBAAI,IAA2B;AAC7C,QAAM,mBAAmB,EAAE,OAAO,EAAE;AACpC,QAAM,iBAA0D,EAAE,OAAO,KAAK;AAC9E,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,YAAY,oBAAI,IAAoB;AAC1C,MAAI,gBAAgB;AACpB,QAAM,gBAAgB,oBAAI,IAAoB;AAE9C,QAAM,OAAO,CAAC,cAAiC;AAAA,IAC7C,IAAI,SAAS;AAAA,IACb,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,gBAAgB,EACb,OAAO,EACP,SAAS,EACT,MAAM,IAAI,EACV,MAAM,SAAS,KAAK,kBAAkB,IAAI;AAAA,IAC7C,QAAQ,EACL,OAAO,EACP,SAAS,EACT,MAAM,IAAI,EACV,MAAM,SAAS,KAAK,UAAU,IAAI;AAAA,IACrC,UAAU,GAAG,SAAS,IAAI;AAAA,IAC1B,MAAM,CAAC;AAAA,EACT;AACA,QAAM,YAAY,CAAC,WAA2B;AAAA,IAC5C,GAAG;AAAA,IACH,QAAQ,UAAU,MAAM,EAAE;AAAA,IAC1B,OAAO,CAAC,UAAU;AAAA,IAClB,sBAAsB;AAAA,IACtB,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AACA,QAAM,WAAW,CAAC,UAAyB;AAAA,IACzC,GAAG;AAAA,IACH,OAAO;AAAA,IACP,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC;AAAA,IAChC,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AACA,QAAM,OAAO,CAAC,aACZ,UAAU,IAAI,QAAQ,KACtB,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE;AAAA,IACtB,CAAC,aAAa,SAAS,SAAS,YAAY,SAAS,UAAU,MAAM;AAAA,EACvE;AAEF,QAAM,SAAS,OAAO,YAAwC;AAC5D,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,QAAQ,WAAW,QAAQ,KAAK,MAAM,QAAQ,KAAK;AAChE,UAAM,OAAO,SAAS,KAAK,OAAO,gBAAgB,MAAM,KAAK,MAAM,IAAI,CAAC;AACxE,aAAS,KAAK,EAAE,QAAQ,QAAQ,QAAQ,MAAM,IAAI,UAAU,MAAM,SAAS,QAAQ,QAAQ,CAAC;AAC5F,QAAI,CAAC,IAAI,SAAS,WAAW,MAAM,EAAG,QAAO,KAAK,EAAE,OAAO,YAAY,GAAG,GAAG;AAC7E,QAAI,QAAQ,QAAQ,IAAI,eAAe,MAAM,UAAU,sBAAsB,IAAI;AAC/E,aAAO,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAAA,IAC5C;AACA,UAAM,OAAO,IAAI,SAAS,MAAM,MAAM,MAAM;AAC5C,QAAI,QAAQ,WAAW,UAAU,SAAS,cAAc;AACtD,YAAM,SAAS,qBAAqB,UAAU,IAAI;AAClD,UAAI,CAAC,OAAO,QAAS,QAAO,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAC/D,UAAK,OAAO,KAAK,kBAAkB,UAAW,OAAO,KAAK,UAAU,OAAO;AACzE,eAAO,KAAK,EAAE,OAAO,gDAAgD,GAAG,GAAG;AAAA,MAC7E;AACA,UAAI,OAAO,KAAK,kBAAkB,MAAM;AACtC,cAAM,QAAQ,OAAO,IAAI,OAAO,KAAK,cAAc;AACnD,YAAI,SAAS,KAAM,QAAO,KAAK,EAAE,OAAO,0BAA0B,GAAG,GAAG;AACxE,YAAI,MAAM,UAAU,QAAS,QAAO,KAAK,EAAE,OAAO,oBAAoB,MAAM,KAAK,GAAG,GAAG,GAAG;AAAA,MAC5F;AACA,UAAI,OAAO,KAAK,UAAU,MAAM;AAC9B,cAAM,OAAO,MAAM,IAAI,OAAO,KAAK,MAAM;AACzC,YAAI,QAAQ,KAAM,QAAO,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAC9D,YAAI,KAAK,UAAU,QAAS,QAAO,KAAK,EAAE,OAAO,WAAW,KAAK,KAAK,GAAG,GAAG,GAAG;AAAA,MACjF;AACA,YAAM,MAAM,QAAQ,QAAQ,IAAI,iBAAiB;AACjD,UAAI,OAAO,KAAK,cAAc,QAAQ,OAAO,KAAK,eAAe;AAC/D,eAAO,KAAK,EAAE,OAAO,6CAA6C,GAAG,GAAG;AAC1E,YAAM,WAAW,OAAO,OAAO,SAAY,UAAU,IAAI,cAAc,IAAI,GAAG,KAAK,EAAE;AACrF,UAAI,YAAY,KAAM,QAAO,KAAK,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC;AAC9D,uBAAiB;AACjB,YAAM,KAAK,gCAAgC,OAAO,aAAa,EAAE,SAAS,GAAG,GAAG,CAAC;AACjF,YAAM,OAAO,OAAO,KAAK,QAAQ,YAAY,aAAa;AAC1D,YAAMA,YAA8B;AAAA,QAClC;AAAA,QACA;AAAA,QACA,OAAO,MAAM;AAAA,QACb,MAAM,iBAAiB,MAAM,IAAI;AAAA,QACjC,SAAS,KAAK,aAAa,EAAE,IAAI,KAAK,CAAC,KAAK,kBAAkB;AAAA,MAChE;AACA,gBAAU,IAAI,IAAIA,SAAQ;AAC1B,UAAI,OAAO,KAAM,eAAc,IAAI,KAAK,EAAE;AAC1C,aAAO,KAAK,EAAE,UAAU,KAAKA,SAAQ,EAAE,CAAC;AAAA,IAC1C;AACA,QAAI,QAAQ,WAAW,SAAS,SAAS,WAAW;AAClD,YAAM,UAAU,IAAI,aAAa,IAAI,SAAS;AAC9C,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,YAAM,WAAW,CAAC,GAAG,OAAO,OAAO,CAAC,EACjC,OAAO,CAAC,UAAU,WAAW,QAAQ,MAAM,YAAY,OAAO,EAC9D,OAAO,CAAC,UAAU,SAAS,QAAQ,MAAM,UAAU,KAAK,EACxD,QAAQ;AACX,aAAO,KAAK,EAAE,QAAQ,SAAS,IAAI,SAAS,EAAE,CAAC;AAAA,IACjD;AACA,QAAI,QAAQ,WAAW,UAAU,SAAS,WAAW;AACnD,YAAM,SAAS,EACZ,aAAa;AAAA,QACZ,WAAW,EAAE,OAAO;AAAA,QACpB,sBAAsB,EAAE,OAAO,EAAE,SAAS;AAAA,QAC1C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,MACtC,CAAC,EACA,UAAU,IAAI;AACjB,UAAI,CAAC,OAAO,QAAS,QAAO,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAC/D,YAAM,QAAQ,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE;AAAA,QACjC,CAACC,WACCA,OAAM,cAAc,OAAO,KAAK,cAC/BA,OAAM,UAAU,aAAaA,OAAM,UAAU,cAAcA,OAAM,UAAU;AAAA,MAChF;AACA,UAAI,SAAS,KAAM,QAAO,KAAK,EAAE,OAAO,UAAU,KAAK,EAAE,CAAC;AAC1D,YAAM,KAAK,gCAAgC,OAAO,OAAO,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACnF,YAAM,QAAwB;AAAA,QAC5B;AAAA,QACA,QAAQ;AAAA,QACR,WAAW,OAAO,KAAK;AAAA,QACvB,SAAS,OAAO,KAAK;AAAA,QACrB,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AACA,aAAO,IAAI,IAAI,KAAK;AACpB,aAAO,KAAK,EAAE,OAAO,UAAU,KAAK,EAAE,GAAG,GAAG;AAAA,IAC9C;AACA,UAAM,aAAa,sBAAsB,KAAK,IAAI;AAClD,QAAI,QAAQ,WAAW,SAAS,cAAc,MAAM;AAClD,YAAM,QAAQ,OAAO,IAAI,mBAAmB,WAAW,CAAC,KAAK,EAAE,CAAC;AAChE,UAAI,SAAS,KAAM,QAAO,KAAK,EAAE,OAAO,kBAAkB,GAAG,GAAG;AAChE,YAAM,SAAS,WAAW,IAAI,MAAM,EAAE,KAAK,KAAK;AAChD,iBAAW,IAAI,MAAM,IAAI,KAAK;AAC9B,UAAI,MAAM,UAAU,cAAc,QAAQ,gBAAgB,MAAO,OAAM,QAAQ;AAC/E,aAAO,KAAK,EAAE,OAAO,UAAU,KAAK,EAAE,CAAC;AAAA,IACzC;AACA,QAAI,QAAQ,WAAW,UAAU,SAAS,WAAW;AACnD,YAAM,SAAS,EACZ,aAAa;AAAA,QACZ,MAAM,EAAE,OAAO;AAAA,QACf,YAAY,EAAE,OAAO,EAAE,WAAW,OAAO,EAAE,SAAS;AAAA,QACpD,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,QACrC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,MACjC,CAAC,EACA,UAAU,IAAI;AACjB,UAAI,CAAC,OAAO,QAAS,QAAO,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAC/D,aAAO,KAAK,EAAE,IAAI,UAAU,OAAO,kBAAkB,CAAC;AAAA,IACxD;AACA,UAAM,YAAY,qBAAqB,KAAK,IAAI;AAChD,QAAI,QAAQ,WAAW,WAAW,aAAa,MAAM;AACnD,YAAM,OAAO,MAAM,IAAI,mBAAmB,UAAU,CAAC,KAAK,EAAE,CAAC;AAC7D,UAAI,QAAQ,KAAM,QAAO,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAC9D,YAAM,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,IAAI;AAC/D,WAAK,WAAW,SAAS;AACzB,aAAO,KAAK,EAAE,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IACtC;AACA,QAAI,QAAQ,WAAW,SAAS,aAAa,MAAM;AACjD,YAAM,OAAO,MAAM,IAAI,mBAAmB,UAAU,CAAC,KAAK,EAAE,CAAC;AAC7D,UAAI,QAAQ,KAAM,QAAO,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAC9D,YAAM,SAAS,UAAU,IAAI,KAAK,EAAE,KAAK,KAAK;AAC9C,gBAAU,IAAI,KAAK,IAAI,KAAK;AAC5B,UAAI,KAAK,UAAU,eAAe,QAAQ,iBAAiB,OAAO;AAChE,cAAM,QAAQ,cAAc,IAAI,KAAK,EAAE,KAAK;AAC5C,aAAK,QAAQ,SAAS,OAAO,UAAU;AACvC,aAAK,QAAQ;AAAA,MACf;AACA,aAAO,KAAK,EAAE,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,IACtC;AACA,UAAM,gBAAgB,gCAAgC,KAAK,IAAI;AAC/D,QAAI,iBAAiB,KAAM,QAAO,KAAK,EAAE,OAAO,YAAY,IAAI,GAAG,GAAG,GAAG;AACzE,UAAM,WAAW,KAAK,mBAAmB,cAAc,CAAC,KAAK,EAAE,CAAC;AAChE,QAAI,YAAY,QAAQ,SAAS,UAAU,MAAM;AAC/C,aAAO,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;AAClD,UAAM,OAAO,cAAc,CAAC,KAAK;AACjC,UAAM,UAAU,SAAS;AACzB,QAAI,QAAQ,WAAW,SAAS,SAAS;AACvC,aAAO,KAAK,EAAE,WAAW,KAAK,mBAAmB,MAAM,CAAC;AAC1D,QAAI,QAAQ,WAAW,UAAU,SAAS;AACxC,aAAO,KAAK;AAAA,QACV,KAAK,6CAA6C,SAAS,EAAE;AAAA,QAC7D,OAAO;AAAA,QACP,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,GAAO,EAAE,YAAY;AAAA,MACxD,CAAC;AACH,QAAI,QAAQ,WAAW,WAAW,SAAS,IAAI;AAC7C,YAAM,QAAQ,qBAAqB,KAAK,EAAE,SAAS,KAAK,CAAC,EAAE,MAAM,IAAI;AACrE,UAAI,MAAM,WAAW,KAAM,UAAS,KAAK,UAAU,MAAM;AACzD,aAAO,KAAK,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC;AAAA,IAC1C;AACA,QAAI,QAAQ,WAAW,SAAS,SAAS;AACvC,aAAO,KAAK;AAAA,QACV,WAAW,KAAK,wBAAwB;AAAA,QACxC,WAAW,KAAK,wBAAwB;AAAA,MAC1C,CAAC;AACH,QAAI,QAAQ,WAAW,SAAS,SAAS,IAAI;AAC3C,YAAM,OAAO,SAAS,IAAI,SAAS,EAAE;AACrC,UAAI,QAAQ,MAAM;AAChB,YAAI,QAAQ,GAAG;AACb,mBAAS,OAAO,SAAS,EAAE;AAC3B,mBAAS,QAAQ,MAAM;AAAA,QACzB,MAAO,UAAS,IAAI,SAAS,IAAI,OAAO,CAAC;AAAA,MAC3C;AACA,aAAO,KAAK,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC;AAAA,IAC1C;AACA,QAAI,QAAQ,WAAW,UAAU,SAAS,YAAY;AACpD,UAAI,SAAS,UAAU,MAAM,QAAS,QAAO,KAAK,EAAE,OAAO,cAAc,GAAG,GAAG;AAC/E,UAAI,SAAS,KAAK,iBAAiB,KAAM,QAAO,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;AACxF,YAAM,KAAK,gCAAgC,OAAO,MAAM,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAClF,YAAM,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC;AACnF,YAAM,OAAsB;AAAA,QAC1B;AAAA,QACA,YAAY,SAAS;AAAA,QACrB,OAAO;AAAA,QACP,OAAO;AAAA,QACP,UAAU,QAAQ;AAAA,MACpB;AACA,YAAM,IAAI,IAAI,IAAI;AAClB,UAAI,eAAe,SAAS,KAAM,eAAc,IAAI,IAAI,eAAe,KAAK;AAC5E,eAAS,QAAQ,MAAM;AACvB,aAAO,KAAK,EAAE,MAAM,SAAS,IAAI,EAAE,GAAG,GAAG;AAAA,IAC3C;AACA,UAAM,SAAS,oBAAoB,KAAK,IAAI;AAC5C,QAAI,QAAQ,WAAW,UAAU,UAAU,MAAM;AAC/C,YAAM,OAAO,OAAO,CAAC,KAAK;AAC1B,cAAQ,KAAK,EAAE,YAAY,SAAS,IAAI,QAAQ,KAAK,CAAC;AACtD,UAAI,gBAAgB,IAAI,IAAI;AAC1B,eAAO,KAAK,EAAE,OAAO,uCAAuC,GAAG,GAAG;AACpE,UAAI,SAAS,QAAS,UAAS,QAAQ,MAAM;AAC7C,UAAI,SAAS,YAAY,SAAS,QAAS,UAAS,QAAQ,MAAM;AAElE,UAAI,SAAS,QAAQ;AACnB,iBAAS,QAAQ,YAAY,QAAQ,IAAI,MAAM,UAAU,MAAM;AAC/D,YAAI,YAAY,QAAQ,EAAG,UAAS,IAAI,SAAS,IAAI,YAAY,KAAK;AAAA,MACxE;AACA,UAAI,SAAS,SAAU,UAAS,QAAQ,MAAM;AAC9C,aAAO,KAAK,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC;AAAA,IAC1C;AACA,QAAI,QAAQ,WAAW,WAAW,SAAS,WAAW,SAAS,SAAS;AACtE,YAAM,SAAS,EACZ,OAAO;AAAA,QACN,SAAS,EAAE,OAAO;AAAA,QAClB,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,QACzB,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,MACjD,CAAC,EACA,MAAM,IAAI;AACb,YAAM,UAAU,QAAQ,KAAK;AAAA,QAC3B,IAAI,QAAQ,QAAQ,MAAM,SAAS,CAAC;AAAA,QACpC,SAAS,OAAO;AAAA,QAChB,KAAK,OAAO,OAAO;AAAA,QACnB,KAAK,OAAO,OAAO,CAAC;AAAA,MACtB,CAAC;AACD,UAAI,SAAS;AACX,eAAO,KAAK,EAAE,IAAI,QAAQ,IAAI,QAAQ,QAAQ,QAAQ,KAAK,GAAG,OAAO,UAAU,CAAC;AAClF,YAAM,SAAS,MAAM,eAAe,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC;AACjF,aAAO,KAAK;AAAA,QACV,IAAI,QAAQ;AAAA,QACZ,QAAQ,QAAQ;AAAA,QAChB,KAAK;AAAA,QACL,UAAU;AAAA,QACV,WAAW;AAAA,QACX,GAAG,WAAW,QAAQ,SAAS,GAAG,CAAC;AAAA,MACrC,CAAC;AAAA,IACH;AACA,UAAM,eAAe,mDAAmD,KAAK,IAAI;AACjF,QAAI,gBAAgB,MAAM;AACxB,YAAM,UAAU,QAAQ,UAAU,IAAI,aAAa,CAAC,KAAK,EAAE;AAC3D,UAAI,WAAW,KAAM,QAAO,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;AACpE,UAAI,aAAa,CAAC,MAAM,UAAU;AAChC,cAAM,eAAe,OAAO,IAAI,aAAa,IAAI,cAAc,KAAK,CAAC;AACrE,cAAM,eAAe,OAAO,IAAI,aAAa,IAAI,cAAc,KAAK,CAAC;AACrE,cAAM,SAAS,MAAM,eAAe,SAAS;AAAA,UAC3C,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,QAAQ,OAAO,IAAI,aAAa,IAAI,QAAQ,KAAK,CAAC;AAAA,QACpD,CAAC;AACD,eAAO,KAAK,WAAW,QAAQ,SAAS,cAAc,YAAY,CAAC;AAAA,MACrE;AACA,UAAI,aAAa,CAAC,MAAM,aAAa;AACnC,gBAAQ,UAAU;AAClB,eAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,MAC1B;AACA,YAAM,QAAQ,EACX,OAAO;AAAA,QACN,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,QAC7B,KAAK,EAAE,QAAQ,EAAE,SAAS;AAAA,QAC1B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,MAC9B,CAAC,EACA,MAAM,IAAI;AACb,UAAI,MAAM,WAAW,KAAM,SAAQ,MAAM,aAAa,MAAM,OAAO,CAAC;AACpE,UAAI,MAAM,QAAQ,KAAM,SAAQ,IAAI;AACpC,UAAI,MAAM,UAAU,KAAM,SAAQ,UAAU,MAAM,MAAM;AACxD,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B;AACA,QAAI,SAAS,YAAY,QAAQ,WAAW,OAAO;AACjD,YAAMC,QAAO,IAAI,aAAa,IAAI,MAAM,KAAK;AAE7C,YAAM,UAAU,QAAQ,MAAM,IAAI,QAAQ,MAAM,IAAIA,KAAI,KAAKA,KAAI;AACjE,UAAI,WAAW,KAAM,QAAO,KAAK,EAAE,OAAO,GAAGA,KAAI,8BAA8B,GAAG,GAAG;AACrF,YAAM,SAAS,OAAO,IAAI,aAAa,IAAI,QAAQ,KAAK,CAAC;AACzD,YAAM,SAAS,OAAO,IAAI,aAAa,IAAI,QAAQ,KAAK,QAAQ,UAAU;AAC1E,YAAM,QAAQ,QAAQ,SAAS,QAAQ,SAAS,MAAM;AACtD,aAAO,KAAK;AAAA,QACV,SAAS,aAAa,KAAK;AAAA,QAC3B,KAAK,SAAS,MAAM,cAAc,QAAQ;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AACA,QAAI,SAAS,YAAY,QAAQ,WAAW,OAAO;AACjD,YAAM,SAAS,EACZ,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,GAAG,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAChF,MAAM,IAAI;AACb,UAAI,CAAC,QAAQ,KAAK,IAAI,MAAM,QAAQ,OAAO,IAAI,CAAC;AAC9C,eAAO,KAAK,EAAE,OAAO,4BAA4B,GAAG,GAAG;AACzD,YAAM,OAAO,aAAa,OAAO,OAAO;AACxC,YAAM,WACJ,OAAO,WAAW,OACb,QAAQ,MAAM,IAAI,OAAO,IAAI,KAAK,IAAI,WAAW,IAClD,IAAI,WAAW;AACrB,cAAQ,MAAM,IAAI,OAAO,MAAM,YAAY,CAAC,UAAU,IAAI,CAAC,CAAC;AAC5D,aAAO,KAAK,EAAE,cAAc,KAAK,WAAW,CAAC;AAAA,IAC/C;AACA,WAAO,KAAK,EAAE,OAAO,YAAY,QAAQ,MAAM,IAAI,IAAI,GAAG,GAAG,GAAG;AAAA,EAClE;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,OAAO,SAAS,OAAO,IAAI,QAAQ,OAAO,IAAI,CAAC;AAAA,IAC7D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,CAAC,SAAS,CAAC,GAAG,UAAU,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,SAAS,SAAS,IAAI;AAAA,EAC7F;AACF;AAEA,MAAM,aAAa,CACjB,QACA,SAMA,cACA,kBACI;AAAA,EACJ,QAAQ;AAAA,IACN,SAAS,aAAa,OAAO,MAAM;AAAA,IACnC,QAAQ;AAAA,IACR,YAAY,eAAe,OAAO,OAAO;AAAA,IACzC,KAAK,OAAO,UAAU,eAAe,OAAO,OAAO,cAAc,QAAQ,OAAO;AAAA,EAClF;AAAA,EACA,QAAQ;AAAA,IACN,SAAS,aAAa,OAAO,MAAM;AAAA,IACnC,QAAQ;AAAA,IACR,YAAY,eAAe,OAAO,OAAO;AAAA,IACzC,KAAK,OAAO,UAAU,eAAe,OAAO,OAAO,cAAc,QAAQ,OAAO;AAAA,EAClF;AAAA,EACA,OAAO,OAAO,SAAS,WAAW;AAAA,EAClC,QAAQ,OAAO,SAAS,EAAE,UAAU,QAAQ,UAAU,QAAQ,QAAQ,OAAO,IAAI;AACnF;",
|
|
4
|
+
"sourcesContent": ["import {\n jsonObjectSchema,\n jsonValueSchema,\n type JsonObject,\n type JsonValue,\n} from '@finalai/agent-provider';\nimport { ComputerEgressPolicy } from '@finalai/computers';\nimport { posix } from 'node:path';\nimport * as z from 'zod';\nimport { concatBytes, decodeBase64, encodeBase64 } from \"../bytes.mjs\";\nimport { createFakeMachine, readFakeOutput, type FakeMachineIface } from \"./fake-machine.mjs\";\n\n// A fake public Computers API (vm-service `/v1` over fetch, API-key bearer),\n// backed by in-memory machines. The `device` provider's fakes live with the\n// Device API (device-api-service/src/testing), which it is tested against.\n\nexport interface FakeComputerIface {\n id: string;\n name: string;\n state: number;\n body: JsonObject;\n machine: FakeMachineIface;\n}\n\nexport interface FakeImageIface {\n id: string;\n source: 'platform' | 'customer';\n reference: string;\n lineage: string;\n state: 'pending' | 'building' | 'ready' | 'failed' | 'expired';\n error: string | null;\n}\n\nexport interface FakeBaseIface {\n id: string;\n computerId: string;\n state: 'capturing' | 'ready' | 'failed' | 'expired';\n error: string | null;\n retained: boolean;\n}\n\nexport interface FakeComputersApiIface {\n fetch: typeof fetch;\n computers: Map<string, FakeComputerIface>;\n images: Map<string, FakeImageIface>;\n bases: Map<string, FakeBaseIface>;\n // Base reads a fresh capture answers `capturing` before it settles.\n baseCaptureReads: { count: number };\n // Set: the next captures settle `failed` with this error instead of `ready`.\n captureFailure: { error: string | null };\n requests: { method: string; path: string; body: JsonValue; headers: Headers }[];\n actions: { computerId: string; action: string }[];\n // Actions answered 409 (a conflicting transition) without changing the computer.\n actionConflicts: Set<string>;\n // Computer reads a stopped computer answers `stopped` before it is shelved.\n shelveReads: { count: number };\n // Image reads a fresh registration answers `building` before it is ready.\n imageBuildReads: { count: number };\n // Computer reads a created or started computer answers `pending` (a resumed\n // one `resuming`) before it runs; until then commands and files answer 409.\n bootReads: { count: number };\n computerByName(name: string): FakeComputerIface | undefined;\n}\n\nconst STATE = {\n Pending: 1,\n Running: 3,\n Paused: 4,\n Stopped: 5,\n Resuming: 6,\n Deleted: 9,\n Shelved: 10,\n} as const;\nconst NOT_RUNNING = 'Computer is not running; resume it explicitly before this operation';\nexport const FAKE_COMPUTERS_API_KEY = 'fk_org_api_key';\nexport const FAKE_PLATFORM_SANDBOX_IMAGE = 'cimv_0190e0000000700080000000000001';\n\nconst json = (body: JsonValue, status = 200): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });\n\n// What POST /v1/computers accepts (vm-service/src/routes/v1.ts); anything else,\n// such as the internal surface's image, lineage, projectArtifact or the\n// platform-only policies, is refused so a test fails when the provider asks for it.\nconst createComputerSchema = z.strictObject({\n name: z.string().optional(),\n regionSlug: z.string().optional(),\n imageVersionId: z.string().startsWith('cimv_').optional(),\n baseId: z.string().startsWith('part_').optional(),\n idlePolicy: z.enum(['shelve', 'pause', 'delete']).optional(),\n network: z\n .strictObject({\n egress: z.nativeEnum(ComputerEgressPolicy),\n allow: z.array(z.string()).optional(),\n })\n .optional(),\n teamId: z.string().startsWith('team_').optional(),\n vcpu: z.number().optional(),\n memoryMb: z.number().optional(),\n diskGb: z.number().optional(),\n userVolumeGb: z.number().nullable().optional(),\n env: z.record(z.string(), z.string()).optional(),\n setupScript: z.string().optional(),\n commandMode: z.number().optional(),\n customCommand: z.string().optional(),\n httpPort: z.number().optional(),\n httpPublic: z.boolean().optional(),\n ephemeral: z.boolean().optional(),\n idleTimeoutSeconds: z.number().optional(),\n autoResume: z.boolean().optional(),\n tags: z.array(z.string()).optional(),\n comment: z.string().optional(),\n});\n\nexport const createFakeComputersApi = (\n args: {\n machineFor?: (computer: { id: string; name: string }) => FakeMachineIface;\n computerUseAvailable?: boolean;\n screenAvailable?: boolean;\n } = {},\n): FakeComputersApiIface => {\n const computers = new Map<string, FakeComputerIface>();\n const images = new Map<string, FakeImageIface>([\n [\n FAKE_PLATFORM_SANDBOX_IMAGE,\n {\n id: FAKE_PLATFORM_SANDBOX_IMAGE,\n source: 'platform',\n reference: 'ghcr.io/final/sandbox:release',\n lineage: 'sandbox',\n state: 'ready',\n error: null,\n },\n ],\n ]);\n const requests: FakeComputersApiIface['requests'] = [];\n const actions: FakeComputersApiIface['actions'] = [];\n const actionConflicts = new Set<string>();\n const shelveReads = { count: 0 };\n const shelving = new Map<string, number>();\n const imageBuildReads = { count: 1 };\n const bootReads = { count: 1 };\n const booting = new Map<string, number>();\n const boot = (computer: FakeComputerIface, state: number) => {\n if (bootReads.count <= 0) {\n computer.state = STATE.Running;\n return;\n }\n computer.state = state;\n booting.set(computer.id, bootReads.count);\n };\n const imageReads = new Map<string, number>();\n const bases = new Map<string, FakeBaseIface>();\n const baseCaptureReads = { count: 1 };\n const captureFailure: FakeComputersApiIface['captureFailure'] = { error: null };\n const captureErrors = new Map<string, string>();\n const baseReads = new Map<string, number>();\n let computerCount = 0;\n const byIdempotency = new Map<string, string>();\n\n const wire = (computer: FakeComputerIface) => ({\n id: computer.id,\n name: computer.name,\n state: computer.state,\n imageVersionId: z\n .string()\n .nullable()\n .catch(null)\n .parse(computer.body.imageVersionId ?? null),\n teamId: z\n .string()\n .nullable()\n .catch(null)\n .parse(computer.body.teamId ?? null),\n hostname: `${computer.name}-k3p9x7qd.virtual-machine.lax-2.example`,\n tags: [],\n });\n const imageWire = (image: FakeImageIface) => ({\n ...image,\n digest: `sha256:${image.id}`,\n sizes: ['standard'],\n registryCredentialId: null,\n createdAt: null,\n readyAt: null,\n lastUsedAt: null,\n });\n const baseWire = (base: FakeBaseIface) => ({\n ...base,\n image: 'ghcr.io/final/sandbox:release',\n digest: `sha256:${'b'.repeat(64)}`,\n diskGb: 20,\n createdAt: null,\n readyAt: null,\n lastUsedAt: null,\n });\n const find = (idOrName: string): FakeComputerIface | undefined =>\n computers.get(idOrName) ??\n [...computers.values()].find(\n (computer) => computer.name === idOrName && computer.state !== STATE.Deleted,\n );\n\n const handle = async (request: Request): Promise<Response> => {\n const url = new URL(request.url);\n const text = request.method === 'GET' ? '' : await request.text();\n const body = text === '' ? null : jsonValueSchema.parse(JSON.parse(text));\n requests.push({ method: request.method, path: url.pathname, body, headers: request.headers });\n if (!url.pathname.startsWith('/v1/')) return json({ error: 'not found' }, 404);\n if (request.headers.get('authorization') !== `Bearer ${FAKE_COMPUTERS_API_KEY}`) {\n return json({ error: 'unauthorized' }, 401);\n }\n const path = url.pathname.slice('/v1'.length);\n if (request.method === 'POST' && path === '/computers') {\n const parsed = createComputerSchema.safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n if ((parsed.data.imageVersionId == null) === (parsed.data.baseId == null)) {\n return json({ error: 'pass exactly one of imageVersionId and baseId' }, 400);\n }\n if (parsed.data.imageVersionId != null) {\n const image = images.get(parsed.data.imageVersionId);\n if (image == null) return json({ error: 'image version not found' }, 404);\n if (image.state !== 'ready') return json({ error: `image version is ${image.state}` }, 409);\n }\n if (parsed.data.baseId != null) {\n const base = bases.get(parsed.data.baseId);\n if (base == null) return json({ error: 'base not found' }, 404);\n if (base.state !== 'ready') return json({ error: `base is ${base.state}` }, 409);\n }\n const key = request.headers.get('idempotency-key');\n if (parsed.data.ephemeral === true && parsed.data.idlePolicy === 'pause')\n return json({ error: 'ephemeral VMs are always deleted when idle' }, 400);\n const existing = key == null ? undefined : computers.get(byIdempotency.get(key) ?? '');\n if (existing != null) return json({ computer: wire(existing) });\n computerCount += 1;\n const id = `comp_0190a0000000700080000000${String(computerCount).padStart(4, '0')}`;\n const name = parsed.data.name ?? `computer-${computerCount}`;\n const computer: FakeComputerIface = {\n id,\n name,\n state: STATE.Pending,\n body: jsonObjectSchema.parse(body),\n machine: args.machineFor?.({ id, name }) ?? createFakeMachine(),\n };\n boot(computer, STATE.Pending);\n computers.set(id, computer);\n if (key != null) byIdempotency.set(key, id);\n return json({ computer: wire(computer) });\n }\n if (request.method === 'GET' && path === '/images') {\n const lineage = url.searchParams.get('lineage');\n const state = url.searchParams.get('state');\n const matching = [...images.values()]\n .filter((image) => lineage == null || image.lineage === lineage)\n .filter((image) => state == null || image.state === state)\n .reverse();\n return json({ images: matching.map(imageWire) });\n }\n if (request.method === 'POST' && path === '/images') {\n const parsed = z\n .strictObject({\n reference: z.string(),\n registryCredentialId: z.string().optional(),\n sizes: z.array(z.string()).optional(),\n })\n .safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n const known = [...images.values()].find(\n (image) =>\n image.reference === parsed.data.reference &&\n (image.state === 'pending' || image.state === 'building' || image.state === 'ready'),\n );\n if (known != null) return json({ image: imageWire(known) });\n const id = `cimv_0190f0000000700080000000${String(images.size + 1).padStart(4, '0')}`;\n const image: FakeImageIface = {\n id,\n source: 'customer',\n reference: parsed.data.reference,\n lineage: parsed.data.reference,\n state: 'building',\n error: null,\n };\n images.set(id, image);\n return json({ image: imageWire(image) }, 201);\n }\n const imageMatch = /^\\/images\\/([^/]+)$/.exec(path);\n if (request.method === 'GET' && imageMatch != null) {\n const image = images.get(decodeURIComponent(imageMatch[1] ?? ''));\n if (image == null) return json({ error: 'image not found' }, 404);\n const reads = (imageReads.get(image.id) ?? 0) + 1;\n imageReads.set(image.id, reads);\n if (image.state === 'building' && reads > imageBuildReads.count) image.state = 'ready';\n return json({ image: imageWire(image) });\n }\n if (request.method === 'POST' && path === '/tokens') {\n const parsed = z\n .strictObject({\n name: z.string(),\n computerId: z.string().startsWith('comp_').optional(),\n scopes: z.array(z.string()).optional(),\n expiresAt: z.string().optional(),\n })\n .safeParse(body);\n if (!parsed.success) return json({ error: 'invalid body' }, 400);\n return json({ id: 'ctok_1', token: 'fvm1.http-token' });\n }\n const baseMatch = /^\\/bases\\/([^/]+)$/.exec(path);\n if (request.method === 'PATCH' && baseMatch != null) {\n const base = bases.get(decodeURIComponent(baseMatch[1] ?? ''));\n if (base == null) return json({ error: 'base not found' }, 404);\n const retained = z.object({ retained: z.boolean() }).parse(body);\n base.retained = retained.retained;\n return json({ base: baseWire(base) });\n }\n if (request.method === 'GET' && baseMatch != null) {\n const base = bases.get(decodeURIComponent(baseMatch[1] ?? ''));\n if (base == null) return json({ error: 'base not found' }, 404);\n const reads = (baseReads.get(base.id) ?? 0) + 1;\n baseReads.set(base.id, reads);\n if (base.state === 'capturing' && reads > baseCaptureReads.count) {\n const error = captureErrors.get(base.id) ?? null;\n base.state = error == null ? 'ready' : 'failed';\n base.error = error;\n }\n return json({ base: baseWire(base) });\n }\n const computerMatch = /^\\/computers\\/([^/]+)(\\/.*)?$/.exec(path);\n if (computerMatch == null) return json({ error: `no route ${path}` }, 404);\n const computer = find(decodeURIComponent(computerMatch[1] ?? ''));\n if (computer == null || computer.state === STATE.Deleted)\n return json({ error: 'computer not found' }, 404);\n const rest = computerMatch[2] ?? '';\n const machine = computer.machine;\n if (request.method === 'GET' && rest === '/screen')\n return json({ available: args.screenAvailable ?? false });\n if (request.method === 'POST' && rest === '/screen')\n return json({\n url: `https://computer-api.example/v1/computers/${computer.id}/screen/view#screen-token`,\n token: 'screen-token',\n expiresAt: new Date(Date.now() + 900_000).toISOString(),\n });\n if (request.method === 'PATCH' && rest === '') {\n const patch = createComputerSchema.pick({ network: true }).parse(body);\n if (patch.network != null) computer.body.network = patch.network;\n return json({ computer: wire(computer) });\n }\n if (request.method === 'GET' && rest === '/computer-use')\n return json({\n available: args.computerUseAvailable ?? false,\n installed: args.computerUseAvailable ?? false,\n });\n if (request.method === 'GET' && rest === '') {\n const starting = booting.get(computer.id);\n if (starting != null) {\n if (starting <= 0) {\n booting.delete(computer.id);\n computer.state = STATE.Running;\n } else booting.set(computer.id, starting - 1);\n }\n const left = shelving.get(computer.id);\n if (left != null) {\n if (left <= 0) {\n shelving.delete(computer.id);\n computer.state = STATE.Shelved;\n } else shelving.set(computer.id, left - 1);\n }\n return json({ computer: wire(computer) });\n }\n if (request.method === 'POST' && rest === '/capture') {\n if (computer.state !== STATE.Running) return json({ error: 'not running' }, 409);\n if (computer.body.userVolumeGb !== null) return json({ error: 'has a user volume' }, 409);\n const id = `part_0190b0000000700080000000${String(bases.size + 1).padStart(4, '0')}`;\n const capture = z.object({ retained: z.boolean().default(false) }).parse(body ?? {});\n const base: FakeBaseIface = {\n id,\n computerId: computer.id,\n state: 'capturing',\n error: null,\n retained: capture.retained,\n };\n bases.set(id, base);\n if (captureFailure.error != null) captureErrors.set(id, captureFailure.error);\n computer.state = STATE.Stopped;\n return json({ base: baseWire(base) }, 202);\n }\n const action = /^\\/actions\\/(.+)$/.exec(rest);\n if (request.method === 'POST' && action != null) {\n const name = action[1] ?? '';\n actions.push({ computerId: computer.id, action: name });\n if (actionConflicts.has(name))\n return json({ error: 'a lifecycle transition is committing' }, 409);\n if (name !== 'resume' && name !== 'start') booting.delete(computer.id);\n if (name === 'pause') computer.state = STATE.Paused;\n if (name === 'resume') boot(computer, STATE.Resuming);\n if (name === 'start') boot(computer, STATE.Pending);\n // A computer that is not ephemeral shelves on stop (vm-service vm-ops).\n if (name === 'stop') {\n computer.state = shelveReads.count > 0 ? STATE.Stopped : STATE.Shelved;\n if (shelveReads.count > 0) shelving.set(computer.id, shelveReads.count);\n }\n if (name === 'delete') computer.state = STATE.Deleted;\n return json({ computer: wire(computer) });\n }\n if (\n computer.state !== STATE.Running &&\n (rest === '/exec' || rest === '/run' || rest === '/files' || rest.startsWith('/processes/'))\n )\n return json({ error: NOT_RUNNING }, 409);\n if (request.method === 'POST' && (rest === '/exec' || rest === '/run')) {\n const parsed = z\n .object({\n command: z.string(),\n cwd: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .parse(body);\n const process = machine.exec({\n id: `proc-${machine.execs.length + 1}`,\n command: parsed.command,\n cwd: parsed.cwd ?? '/',\n env: parsed.env ?? {},\n });\n if (rest === '/exec')\n return json({ id: process.id, bootId: process.bootId, pid: 1, state: 'running' });\n const output = await readFakeOutput(process, { stdout: 0, stderr: 0, waitMs: 50 });\n return json({\n id: process.id,\n bootId: process.bootId,\n pid: 1,\n timedOut: false,\n elapsedMs: 1,\n ...outputBody(output, process, 0, 0),\n });\n }\n const processMatch = /^\\/processes\\/([^/]+)\\/(output|input|terminate)$/.exec(rest);\n if (processMatch != null) {\n const process = machine.processes.get(processMatch[1] ?? '');\n if (process == null) return json({ error: 'process not found' }, 404);\n if (processMatch[2] === 'output') {\n const stdoutOffset = Number(url.searchParams.get('stdoutOffset') ?? 0);\n const stderrOffset = Number(url.searchParams.get('stderrOffset') ?? 0);\n const output = await readFakeOutput(process, {\n stdout: stdoutOffset,\n stderr: stderrOffset,\n waitMs: Number(url.searchParams.get('waitMs') ?? 0),\n });\n return json(outputBody(output, process, stdoutOffset, stderrOffset));\n }\n if (processMatch[2] === 'terminate') {\n process.terminate();\n return json({ ok: true });\n }\n const input = z\n .object({\n dataB64: z.string().optional(),\n eof: z.boolean().optional(),\n signal: z.string().optional(),\n })\n .parse(body);\n if (input.dataB64 != null) process.write(decodeBase64(input.dataB64));\n if (input.eof === true) process.eof();\n if (input.signal != null) process.terminate(input.signal);\n return json({ ok: true });\n }\n if (rest === '/files' && request.method === 'GET') {\n const path = url.searchParams.get('path') ?? '';\n // Reads follow symlinks.\n const content = machine.files.get(machine.links.get(path) ?? path);\n if (content == null) return json({ error: `${path}: No such file or directory` }, 502);\n const offset = Number(url.searchParams.get('offset') ?? 0);\n const length = Number(url.searchParams.get('length') ?? content.byteLength);\n const slice = content.subarray(offset, offset + length);\n return json({\n dataB64: encodeBase64(slice),\n eof: offset + slice.byteLength >= content.byteLength,\n offset,\n });\n }\n if (rest === '/files' && request.method === 'PUT') {\n const parsed = z\n .object({ path: z.string(), dataB64: z.string(), append: z.boolean().optional() })\n .parse(body);\n if (!machine.dirs.has(posix.dirname(parsed.path)))\n return json({ error: 'No such file or directory' }, 502);\n const data = decodeBase64(parsed.dataB64);\n const previous =\n parsed.append === true\n ? (machine.files.get(parsed.path) ?? new Uint8Array())\n : new Uint8Array();\n machine.files.set(parsed.path, concatBytes([previous, data]));\n return json({ bytesWritten: data.byteLength });\n }\n return json({ error: `no route ${request.method} ${path}` }, 404);\n };\n\n return {\n fetch: async (input, init) => handle(new Request(input, init)),\n computers,\n images,\n bases,\n baseCaptureReads,\n captureFailure,\n requests,\n actions,\n actionConflicts,\n shelveReads,\n imageBuildReads,\n bootReads,\n computerByName: (name) => [...computers.values()].find((computer) => computer.name === name),\n };\n};\n\nconst outputBody = (\n output: { stdout: Uint8Array; stderr: Uint8Array; exited: boolean },\n process: {\n exitCode: number | null;\n signal: string | null;\n stdout: Uint8Array;\n stderr: Uint8Array;\n },\n stdoutOffset: number,\n stderrOffset: number,\n) => ({\n stdout: {\n dataB64: encodeBase64(output.stdout),\n offset: stdoutOffset,\n nextOffset: stdoutOffset + output.stdout.byteLength,\n eof: output.exited && stdoutOffset + output.stdout.byteLength >= process.stdout.byteLength,\n },\n stderr: {\n dataB64: encodeBase64(output.stderr),\n offset: stderrOffset,\n nextOffset: stderrOffset + output.stderr.byteLength,\n eof: output.exited && stderrOffset + output.stderr.byteLength >= process.stderr.byteLength,\n },\n state: output.exited ? 'exited' : 'running',\n result: output.exited ? { exitCode: process.exitCode, signal: process.signal } : null,\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["computer", "image", "path"]
|
|
7
7
|
}
|