@finalai/final-agent-provider 0.14.0 → 0.15.1
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/harnesses/adapters.mjs +5 -2
- package/dist/modules/harnesses/adapters.mjs.map +2 -2
- package/dist/modules/sandboxes/computer.mjs +17 -0
- package/dist/modules/sandboxes/computer.mjs.map +2 -2
- package/dist/modules/sandboxes/computers-api-transport.d.mts +0 -1
- package/dist/modules/sandboxes/computers-api-transport.mjs +4 -5
- package/dist/modules/sandboxes/computers-api-transport.mjs.map +2 -2
- package/dist/modules/sandboxes/computers-api.d.mts +5 -1
- package/dist/modules/sandboxes/computers-api.mjs +19 -1
- package/dist/modules/sandboxes/computers-api.mjs.map +2 -2
- package/dist/modules/sandboxes/device.mjs +4 -12
- package/dist/modules/sandboxes/device.mjs.map +2 -2
- package/dist/modules/sandboxes/mcp-pipe.d.mts +1 -0
- package/dist/modules/sandboxes/mcp-pipe.mjs +18 -3
- package/dist/modules/sandboxes/mcp-pipe.mjs.map +2 -2
- package/dist/modules/sandboxes/testing/fake-backends.d.mts +1 -0
- package/dist/modules/sandboxes/testing/fake-backends.mjs +2 -0
- package/dist/modules/sandboxes/testing/fake-backends.mjs.map +2 -2
- package/dist/modules/sandboxes/tool-catalog.d.mts +2 -0
- package/dist/modules/sandboxes/tool-catalog.mjs +14 -0
- package/dist/modules/sandboxes/tool-catalog.mjs.map +7 -0
- package/package.json +5 -5
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ProcessInspectionOperation
|
|
3
3
|
} from "@finalai/agent-protocol/process-inspection";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
jsonObjectSchema,
|
|
6
|
+
jsonValueSchema
|
|
7
|
+
} from "@finalai/agent-provider";
|
|
5
8
|
import {
|
|
6
9
|
computerBrowserAccessSchema,
|
|
7
10
|
ComputerImageState,
|
|
@@ -51,6 +54,7 @@ const computerResponseSchema = z.object({ computer: jsonValueSchema });
|
|
|
51
54
|
const imageResponseSchema = z.object({ image: jsonValueSchema });
|
|
52
55
|
const baseResponseSchema = z.object({ base: jsonValueSchema });
|
|
53
56
|
const imagesResponseSchema = z.object({ images: z.array(jsonValueSchema) });
|
|
57
|
+
const computerUseCatalogSchema = z.object({ tools: z.array(jsonObjectSchema).nullable() });
|
|
54
58
|
const createComputersApiClient = (args) => {
|
|
55
59
|
const request = async (req) => {
|
|
56
60
|
const send = async (refresh) => {
|
|
@@ -209,6 +213,20 @@ const createComputersApiClient = (args) => {
|
|
|
209
213
|
signal,
|
|
210
214
|
schema: z.object({ available: z.boolean(), installed: z.boolean().default(false) })
|
|
211
215
|
}),
|
|
216
|
+
computerUseCatalog: async ({ image, signal }) => {
|
|
217
|
+
try {
|
|
218
|
+
return (await request({
|
|
219
|
+
method: "GET",
|
|
220
|
+
path: "/computer-use/catalog",
|
|
221
|
+
query: { image },
|
|
222
|
+
signal,
|
|
223
|
+
schema: computerUseCatalogSchema
|
|
224
|
+
})).tools;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (isComputersApiStatus(error, 404)) return null;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
},
|
|
212
230
|
openComputerUse: (computerId, options) => request({
|
|
213
231
|
method: "POST",
|
|
214
232
|
path: `${computerPath(computerId)}/computer-use`,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/computers-api.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n ProcessInspectionOperation,\n type ProcessInspectionRequestIface,\n type ProcessInspectionResultIface,\n} from '@finalai/agent-protocol/process-inspection';\nimport { jsonValueSchema, type JsonValue } from '@finalai/agent-provider';\nimport type { FinalPlatformAccessIface } from \"./environment.mjs\";\nimport {\n computerBrowserAccessSchema,\n ComputerImageState,\n computerSnapshotResponseSchema,\n createTransport,\n normalizeComputerBase,\n normalizeComputerImage,\n normalizeComputerInfo,\n ok,\n type ComputerBaseIface,\n type ComputerBrowserAccessIface,\n type ComputerBrowserAccessOptionsIface,\n type ComputerCommandMode,\n ComputerEgressPolicy,\n type ComputerIdlePolicy,\n type ComputerImageIface,\n type ComputerInfoIface,\n type ComputerScreenOpenOptionsIface,\n type ComputerSnapshotRequestIface,\n type ComputerSnapshotResponseIface,\n} from '@finalai/computers';\nimport * as z from 'zod';\nimport { inspectComputerProcesses } from \"./computer-processes.mjs\";\n// The public Computers API (`/v1`), authenticated with the provider's\n// configured access (./access.ts). This speaks the REST surface directly, reusing\n// @finalai/computers's row normalization, because the provider needs an injected\n// fetch and per-call abort signals that the SDK's client does not take.\n\n// The Computers API access asked for before each request, and again after the\n// API rejected the last token. Null: none granted.\nexport interface ComputersApiCredentialsIface {\n computersAccess(args?: { refresh?: boolean }): Promise<FinalPlatformAccessIface | null>;\n}\n\nexport interface ComputersApiConfigIface {\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\n\nexport enum ComputersApiErrorCode {\n CredentialMissing = 'credential_missing',\n RequestFailed = 'request_failed',\n}\n\nexport interface ComputersApiErrorIface extends Error {\n code: ComputersApiErrorCode;\n statusCode: number | null;\n}\n\nexport const computersApiError = (\n message: string,\n statusCode: number | null,\n code: ComputersApiErrorCode = ComputersApiErrorCode.RequestFailed,\n): ComputersApiErrorIface =>\n Object.assign(new Error(message), { name: 'ComputersApiError', code, statusCode });\n\nexport const isComputersApiError = (cause: unknown): cause is ComputersApiErrorIface =>\n cause instanceof Error && 'statusCode' in cause && 'code' in cause;\n\nexport const isComputersApiStatus = (cause: unknown, ...statuses: number[]): boolean =>\n isComputersApiError(cause) && cause.statusCode != null && statuses.includes(cause.statusCode);\n\n// Exactly one of imageVersionId (a registered image) and baseId (a captured computer).\nexport interface CreateComputerBodyIface {\n name: string;\n imageVersionId?: string;\n baseId?: string;\n regionSlug?: string;\n vcpu?: number;\n memoryMb?: number;\n diskGb?: number;\n // Null: no user volume, so everything the computer writes is on the disk a capture takes.\n userVolumeGb?: number | null;\n env: Record<string, string>;\n commandMode?: ComputerCommandMode;\n customCommand?: string;\n ephemeral?: boolean;\n idleTimeoutSeconds?: number;\n idlePolicy?: ComputerIdlePolicy;\n network?: { egress: ComputerEgressPolicy; allow?: string[] };\n autoResume?: boolean;\n tags?: string[];\n}\n\nexport interface ExecBodyIface {\n autoResume?: boolean;\n command: string;\n env?: Record<string, string>;\n cwd?: string;\n user?: string;\n timeoutMs?: number;\n readyTimeoutMs?: number;\n}\n\n// `stop` shelves a computer that is not ephemeral: its disks go to storage and\n// its host is released; `start` brings it back with its files.\nexport type ComputerAction = 'start' | 'stop' | 'pause' | 'resume' | 'delete';\n\nconst outputStreamSchema = z.object({\n dataB64: z.string(),\n offset: z.number(),\n nextOffset: z.number(),\n eof: z.boolean(),\n});\nconst processOutputSchema = z.object({\n stdout: outputStreamSchema,\n stderr: outputStreamSchema,\n state: z.union([z.literal('running'), z.literal('exited')]),\n result: z.object({ exitCode: z.number().nullable(), signal: z.string().nullable() }).nullable(),\n});\nexport type ComputerProcessOutputIface = z.infer<typeof processOutputSchema>;\nconst runResponseSchema = processOutputSchema.extend({\n id: z.string(),\n bootId: z.string(),\n timedOut: z.boolean(),\n});\nexport type ComputerRunResultIface = z.infer<typeof runResponseSchema>;\nconst execResponseSchema = z.object({ id: z.string(), bootId: z.string() });\nconst fileReadSchema = z.object({\n dataB64: z.string(),\n eof: z.boolean(),\n offset: z.number().optional(),\n});\nconst tokenSchema = z.object({ id: z.string(), token: z.string() });\nconst computerResponseSchema = z.object({ computer: jsonValueSchema });\nconst imageResponseSchema = z.object({ image: jsonValueSchema });\nconst baseResponseSchema = z.object({ base: jsonValueSchema });\nconst imagesResponseSchema = z.object({ images: z.array(jsonValueSchema) });\n\nexport interface ComputersApiClientIface {\n disableAutoResume(computerId: string): Promise<void>;\n inspectProcesses(\n computerId: string,\n input: ProcessInspectionRequestIface,\n ): Promise<ProcessInspectionResultIface>;\n openBrowserAccess(\n computerId: string,\n options: ComputerBrowserAccessOptionsIface,\n ): Promise<ComputerBrowserAccessIface>;\n closeBrowserAccess(computerId: string, tokenId: string): Promise<void>;\n readSnapshot(\n computerId: string,\n request: ComputerSnapshotRequestIface,\n ): Promise<ComputerSnapshotResponseIface>;\n screenStatus(computerId: string, signal?: AbortSignal): Promise<{ available: boolean }>;\n openScreen(\n computerId: string,\n options?: ComputerScreenOpenOptionsIface,\n ): Promise<{ url: string; token: string; expiresAt: string }>;\n computerUseStatus(\n computerId: string,\n signal?: AbortSignal,\n ): Promise<{ available: boolean; installed: boolean }>;\n openComputerUse(\n computerId: string,\n options: { cwd: string; readyTimeoutMs?: number; signal?: AbortSignal },\n ): Promise<{ id: string; bootId: string }>;\n setNetworkPolicy(\n computerId: string,\n policy: { egress: ComputerEgressPolicy; allow?: string[] },\n lifecycle?: { autoResume: false },\n ): Promise<void>;\n createComputer(body: CreateComputerBodyIface, idempotencyKey: string): Promise<ComputerInfoIface>;\n // Null when the computer does not exist (or was deleted).\n getComputer(idOrName: string): Promise<ComputerInfoIface | null>;\n getNetworkPolicy(\n idOrName: string,\n ): Promise<{ egress: ComputerEgressPolicy; allow: string[] } | null>;\n action(idOrName: string, action: ComputerAction): Promise<void>;\n exec(computerId: string, body: ExecBodyIface): Promise<{ id: string; bootId: string }>;\n run(\n computerId: string,\n body: ExecBodyIface & { waitMs?: number },\n ): Promise<ComputerRunResultIface>;\n output(args: {\n computerId: string;\n processId: string;\n bootId: string;\n stdoutOffset: number;\n stderrOffset: number;\n waitMs: number;\n signal?: AbortSignal;\n }): Promise<ComputerProcessOutputIface>;\n input(args: {\n computerId: string;\n processId: string;\n body: Record<string, JsonValue>;\n signal?: AbortSignal;\n }): Promise<void>;\n terminate(args: { computerId: string; processId: string; bootId: string }): Promise<void>;\n readFile(args: {\n computerId: string;\n path: string;\n offset: number;\n length: number;\n signal?: AbortSignal;\n }): Promise<{ dataB64: string; eof: boolean }>;\n writeFile(args: {\n computerId: string;\n path: string;\n dataB64: string;\n append: boolean;\n signal?: AbortSignal;\n }): Promise<void>;\n // An `http`-scoped token for one computer's ports (a preview token).\n createPreviewToken(args: { computerId: string; name: string; expiresAt: Date }): Promise<string>;\n // The organization's and the platform's image versions, newest first.\n listImages(args: { lineage: string; state: ComputerImageState }): Promise<ComputerImageIface[]>;\n // Registering a digest the organization already has returns that version.\n registerImage(args: {\n reference: string;\n registryCredentialId?: string;\n }): Promise<ComputerImageIface>;\n getImage(imageVersionId: string): Promise<ComputerImageIface>;\n // Captures a running computer as a base; the computer stops for it.\n captureComputer(idOrName: string, options?: { retained?: boolean }): Promise<ComputerBaseIface>;\n setBaseRetention(baseId: string, retained: boolean): Promise<ComputerBaseIface>;\n // Null when the base does not exist.\n getBase(baseId: string): Promise<ComputerBaseIface | null>;\n}\n\nexport const createComputersApiClient = (args: {\n config: ComputersApiConfigIface;\n // Asked for access before each request.\n credentials: ComputersApiCredentialsIface | undefined;\n}): ComputersApiClientIface => {\n const request = async <T>(req: {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n query?: Record<string, string>;\n body?: object;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n schema: z.ZodType<T>;\n }): Promise<T> => {\n const send = async (refresh: boolean) => {\n const access = await args.credentials?.computersAccess({ refresh });\n if (access == null) {\n throw computersApiError(\n 'the computer provider was granted no Computers API access',\n null,\n ComputersApiErrorCode.CredentialMissing,\n );\n }\n return createTransport({\n baseUrl: access.baseUrl,\n auth: { apiKey: access.token },\n fetch: args.config.fetch,\n requestTimeoutMs: args.config.requestTimeoutMs,\n }).request({\n method: req.method,\n path: req.path,\n query: req.query,\n jsonBody: req.body,\n headers: req.headers,\n signal: req.signal,\n });\n };\n let response = await send(false);\n // A rejected token is asked for again once, in case it expired.\n if (!response.ok && response.statusCode === 401) response = await send(true);\n if (!response.ok) throw computersApiError(response.error, response.statusCode ?? null);\n const parsed = response.data;\n const result = req.schema.safeParse(parsed);\n if (!result.success)\n throw computersApiError(`unexpected Computers API ${req.path} response`, null);\n return result.data;\n };\n\n const computerPath = (idOrName: string): string => `/computers/${encodeURIComponent(idOrName)}`;\n const toInfo = (row: JsonValue): ComputerInfoIface => {\n const info = normalizeComputerInfo(row);\n if (info == null) throw computersApiError('unexpected computer shape', null);\n return info;\n };\n const toImage = (row: JsonValue): ComputerImageIface => {\n const image = normalizeComputerImage(row);\n if (image == null) throw computersApiError('unexpected image shape', null);\n return image;\n };\n const toBase = (row: JsonValue): ComputerBaseIface => {\n const base = normalizeComputerBase(row);\n if (base == null) throw computersApiError('unexpected base shape', null);\n return base;\n };\n const anyBody = jsonValueSchema;\n\n return {\n inspectProcesses: async (computerId, input) => {\n const result = await inspectComputerProcesses({\n computerPath: computerPath(computerId),\n request: input,\n transport: {\n request: async (args) =>\n ok(\n await request({\n method: args.method,\n path: args.path,\n query: args.query,\n body: args.jsonBody,\n schema: jsonValueSchema,\n }),\n ),\n },\n });\n if (!result.ok) throw computersApiError(result.error, result.statusCode ?? null);\n return input.operation === ProcessInspectionOperation.Available && 'available' in result.data\n ? { ...result.data, canStop: true }\n : result.data;\n },\n createComputer: async (body, idempotencyKey) => {\n // The token determines the project.\n const response = await request({\n method: 'POST',\n path: '/computers',\n body,\n headers: { 'idempotency-key': idempotencyKey },\n schema: computerResponseSchema,\n });\n return toInfo(response.computer);\n },\n getNetworkPolicy: async (idOrName) => {\n const response = await request({\n method: 'GET',\n path: computerPath(idOrName),\n schema: computerResponseSchema,\n });\n const parsed = z\n .object({\n network: z.object({\n egress: z.nativeEnum(ComputerEgressPolicy),\n allow: z.array(z.string()).default([]),\n }),\n })\n .safeParse(response.computer);\n if (!parsed.success)\n throw computersApiError(\n 'The public Computers API did not return a supported network policy',\n null,\n );\n return parsed.data.network;\n },\n getComputer: async (idOrName) => {\n try {\n const response = await request({\n method: 'GET',\n path: computerPath(idOrName),\n schema: computerResponseSchema,\n });\n return toInfo(response.computer);\n } catch (error) {\n if (isComputersApiStatus(error, 404)) return null;\n throw error;\n }\n },\n action: async (idOrName, action) => {\n await request({\n method: 'POST',\n path: `${computerPath(idOrName)}/actions/${action}`,\n body: {},\n schema: anyBody,\n });\n },\n openBrowserAccess: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/browser-access`,\n body: options,\n schema: computerBrowserAccessSchema,\n }),\n closeBrowserAccess: async (computerId, tokenId) => {\n await request({\n method: 'DELETE',\n path: `${computerPath(computerId)}/browser-access/${encodeURIComponent(tokenId)}`,\n schema: anyBody,\n });\n },\n readSnapshot: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/file-snapshots/read`,\n body,\n schema: computerSnapshotResponseSchema,\n }),\n screenStatus: (computerId, signal) =>\n request({\n method: 'GET',\n path: `${computerPath(computerId)}/screen`,\n signal,\n schema: z.object({ available: z.boolean() }),\n }),\n openScreen: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/screen`,\n body: options ?? {},\n schema: z.object({ url: z.string().url(), token: z.string(), expiresAt: z.string() }),\n }),\n computerUseStatus: async (computerId, signal) =>\n request({\n method: 'GET',\n path: `${computerPath(computerId)}/computer-use`,\n signal,\n schema: z.object({ available: z.boolean(), installed: z.boolean().default(false) }),\n }),\n openComputerUse: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/computer-use`,\n body: { cwd: options.cwd, readyTimeoutMs: options.readyTimeoutMs },\n signal: options.signal,\n schema: execResponseSchema,\n }),\n disableAutoResume: async (computerId) => {\n await request({\n method: 'PATCH',\n path: computerPath(computerId),\n body: { autoResume: false },\n schema: computerResponseSchema,\n });\n },\n setNetworkPolicy: async (computerId, policy, lifecycle) => {\n await request({\n method: 'PATCH',\n path: computerPath(computerId),\n body: { network: policy, ...lifecycle },\n schema: computerResponseSchema,\n });\n },\n exec: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/exec`,\n body,\n schema: execResponseSchema,\n }),\n run: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/run`,\n body,\n schema: runResponseSchema,\n }),\n output: (o) =>\n request({\n method: 'GET',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/output`,\n query: {\n bootId: o.bootId,\n stdoutOffset: String(o.stdoutOffset),\n stderrOffset: String(o.stderrOffset),\n waitMs: String(o.waitMs),\n },\n signal: o.signal,\n schema: processOutputSchema,\n }),\n input: async (o) => {\n await request({\n method: 'POST',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/input`,\n body: o.body,\n signal: o.signal,\n schema: anyBody,\n });\n },\n terminate: async (o) => {\n await request({\n method: 'POST',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/terminate`,\n body: { bootId: o.bootId },\n schema: anyBody,\n });\n },\n readFile: async (o) => {\n const read = await request({\n method: 'GET',\n path: `${computerPath(o.computerId)}/files`,\n query: {\n path: o.path,\n offset: String(o.offset),\n length: String(o.length),\n autoResume: 'false',\n },\n signal: o.signal,\n schema: fileReadSchema,\n });\n // The API echoes the offset; a server that ignored the range would hand back the start again.\n if (o.offset > 0 && read.offset !== o.offset) {\n throw computersApiError(\n 'the Computers API ignored a ranged file read; partial contents refused',\n null,\n );\n }\n return { dataB64: read.dataB64, eof: read.eof };\n },\n writeFile: async (o) => {\n await request({\n method: 'PUT',\n path: `${computerPath(o.computerId)}/files`,\n body: { path: o.path, dataB64: o.dataB64, append: o.append },\n signal: o.signal,\n schema: anyBody,\n });\n },\n createPreviewToken: async (o) =>\n (\n await request({\n method: 'POST',\n path: '/tokens',\n body: {\n name: o.name,\n computerId: o.computerId,\n scopes: ['http'],\n expiresAt: o.expiresAt.toISOString(),\n },\n schema: tokenSchema,\n })\n ).token,\n listImages: async (o) =>\n (\n await request({\n method: 'GET',\n path: '/images',\n query: { lineage: o.lineage, state: o.state },\n schema: imagesResponseSchema,\n })\n ).images.map(toImage),\n registerImage: async (o) =>\n toImage(\n (\n await request({\n method: 'POST',\n path: '/images',\n body: o.registryCredentialId == null ? { reference: o.reference } : o,\n schema: imageResponseSchema,\n })\n ).image,\n ),\n getImage: async (imageVersionId) =>\n toImage(\n (\n await request({\n method: 'GET',\n path: `/images/${encodeURIComponent(imageVersionId)}`,\n schema: imageResponseSchema,\n })\n ).image,\n ),\n captureComputer: async (idOrName, options) =>\n toBase(\n (\n await request({\n method: 'POST',\n path: `${computerPath(idOrName)}/capture`,\n body: options ?? {},\n schema: baseResponseSchema,\n })\n ).base,\n ),\n setBaseRetention: async (baseId, retained) =>\n toBase(\n (\n await request({\n method: 'PATCH',\n path: `/bases/${encodeURIComponent(baseId)}`,\n body: { retained },\n schema: baseResponseSchema,\n })\n ).base,\n ),\n getBase: async (baseId) => {\n try {\n const response = await request({\n method: 'GET',\n path: `/bases/${encodeURIComponent(baseId)}`,\n schema: baseResponseSchema,\n });\n return toBase(response.base);\n } catch (error) {\n if (isComputersApiStatus(error, 404)) return null;\n throw error;\n }\n },\n };\n};\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,OAGK;AACP,
|
|
4
|
+
"sourcesContent": ["import {\n ProcessInspectionOperation,\n type ProcessInspectionRequestIface,\n type ProcessInspectionResultIface,\n} from '@finalai/agent-protocol/process-inspection';\nimport {\n jsonObjectSchema,\n jsonValueSchema,\n type JsonObject,\n type JsonValue,\n} from '@finalai/agent-provider';\nimport type { FinalPlatformAccessIface } from \"./environment.mjs\";\nimport {\n computerBrowserAccessSchema,\n ComputerImageState,\n computerSnapshotResponseSchema,\n createTransport,\n normalizeComputerBase,\n normalizeComputerImage,\n normalizeComputerInfo,\n ok,\n type ComputerBaseIface,\n type ComputerBrowserAccessIface,\n type ComputerBrowserAccessOptionsIface,\n type ComputerCommandMode,\n ComputerEgressPolicy,\n type ComputerIdlePolicy,\n type ComputerImageIface,\n type ComputerInfoIface,\n type ComputerScreenOpenOptionsIface,\n type ComputerSnapshotRequestIface,\n type ComputerSnapshotResponseIface,\n} from '@finalai/computers';\nimport * as z from 'zod';\nimport { inspectComputerProcesses } from \"./computer-processes.mjs\";\n// The public Computers API (`/v1`), authenticated with the provider's\n// configured access (./access.ts). This speaks the REST surface directly, reusing\n// @finalai/computers's row normalization, because the provider needs an injected\n// fetch and per-call abort signals that the SDK's client does not take.\n\n// The Computers API access asked for before each request, and again after the\n// API rejected the last token. Null: none granted.\nexport interface ComputersApiCredentialsIface {\n computersAccess(args?: { refresh?: boolean }): Promise<FinalPlatformAccessIface | null>;\n}\n\nexport interface ComputersApiConfigIface {\n fetch?: typeof fetch;\n requestTimeoutMs?: number;\n}\n\nexport enum ComputersApiErrorCode {\n CredentialMissing = 'credential_missing',\n RequestFailed = 'request_failed',\n}\n\nexport interface ComputersApiErrorIface extends Error {\n code: ComputersApiErrorCode;\n statusCode: number | null;\n}\n\nexport const computersApiError = (\n message: string,\n statusCode: number | null,\n code: ComputersApiErrorCode = ComputersApiErrorCode.RequestFailed,\n): ComputersApiErrorIface =>\n Object.assign(new Error(message), { name: 'ComputersApiError', code, statusCode });\n\nexport const isComputersApiError = (cause: unknown): cause is ComputersApiErrorIface =>\n cause instanceof Error && 'statusCode' in cause && 'code' in cause;\n\nexport const isComputersApiStatus = (cause: unknown, ...statuses: number[]): boolean =>\n isComputersApiError(cause) && cause.statusCode != null && statuses.includes(cause.statusCode);\n\n// Exactly one of imageVersionId (a registered image) and baseId (a captured computer).\nexport interface CreateComputerBodyIface {\n name: string;\n imageVersionId?: string;\n baseId?: string;\n regionSlug?: string;\n vcpu?: number;\n memoryMb?: number;\n diskGb?: number;\n // Null: no user volume, so everything the computer writes is on the disk a capture takes.\n userVolumeGb?: number | null;\n env: Record<string, string>;\n commandMode?: ComputerCommandMode;\n customCommand?: string;\n ephemeral?: boolean;\n idleTimeoutSeconds?: number;\n idlePolicy?: ComputerIdlePolicy;\n network?: { egress: ComputerEgressPolicy; allow?: string[] };\n autoResume?: boolean;\n tags?: string[];\n}\n\nexport interface ExecBodyIface {\n autoResume?: boolean;\n command: string;\n env?: Record<string, string>;\n cwd?: string;\n user?: string;\n timeoutMs?: number;\n readyTimeoutMs?: number;\n}\n\n// `stop` shelves a computer that is not ephemeral: its disks go to storage and\n// its host is released; `start` brings it back with its files.\nexport type ComputerAction = 'start' | 'stop' | 'pause' | 'resume' | 'delete';\n\nconst outputStreamSchema = z.object({\n dataB64: z.string(),\n offset: z.number(),\n nextOffset: z.number(),\n eof: z.boolean(),\n});\nconst processOutputSchema = z.object({\n stdout: outputStreamSchema,\n stderr: outputStreamSchema,\n state: z.union([z.literal('running'), z.literal('exited')]),\n result: z.object({ exitCode: z.number().nullable(), signal: z.string().nullable() }).nullable(),\n});\nexport type ComputerProcessOutputIface = z.infer<typeof processOutputSchema>;\nconst runResponseSchema = processOutputSchema.extend({\n id: z.string(),\n bootId: z.string(),\n timedOut: z.boolean(),\n});\nexport type ComputerRunResultIface = z.infer<typeof runResponseSchema>;\nconst execResponseSchema = z.object({ id: z.string(), bootId: z.string() });\nconst fileReadSchema = z.object({\n dataB64: z.string(),\n eof: z.boolean(),\n offset: z.number().optional(),\n});\nconst tokenSchema = z.object({ id: z.string(), token: z.string() });\nconst computerResponseSchema = z.object({ computer: jsonValueSchema });\nconst imageResponseSchema = z.object({ image: jsonValueSchema });\nconst baseResponseSchema = z.object({ base: jsonValueSchema });\nconst imagesResponseSchema = z.object({ images: z.array(jsonValueSchema) });\nconst computerUseCatalogSchema = z.object({ tools: z.array(jsonObjectSchema).nullable() });\n\nexport interface ComputersApiClientIface {\n disableAutoResume(computerId: string): Promise<void>;\n inspectProcesses(\n computerId: string,\n input: ProcessInspectionRequestIface,\n ): Promise<ProcessInspectionResultIface>;\n openBrowserAccess(\n computerId: string,\n options: ComputerBrowserAccessOptionsIface,\n ): Promise<ComputerBrowserAccessIface>;\n closeBrowserAccess(computerId: string, tokenId: string): Promise<void>;\n readSnapshot(\n computerId: string,\n request: ComputerSnapshotRequestIface,\n ): Promise<ComputerSnapshotResponseIface>;\n screenStatus(computerId: string, signal?: AbortSignal): Promise<{ available: boolean }>;\n openScreen(\n computerId: string,\n options?: ComputerScreenOpenOptionsIface,\n ): Promise<{ url: string; token: string; expiresAt: string }>;\n computerUseStatus(\n computerId: string,\n signal?: AbortSignal,\n ): Promise<{ available: boolean; installed: boolean }>;\n openComputerUse(\n computerId: string,\n options: { cwd: string; readyTimeoutMs?: number; signal?: AbortSignal },\n ): Promise<{ id: string; bootId: string }>;\n // The computer-use server's tools on computers made from `image`, known\n // without a computer: empty when it has none, null when the API cannot tell.\n computerUseCatalog(args: { image: string; signal?: AbortSignal }): Promise<JsonObject[] | null>;\n setNetworkPolicy(\n computerId: string,\n policy: { egress: ComputerEgressPolicy; allow?: string[] },\n lifecycle?: { autoResume: false },\n ): Promise<void>;\n createComputer(body: CreateComputerBodyIface, idempotencyKey: string): Promise<ComputerInfoIface>;\n // Null when the computer does not exist (or was deleted).\n getComputer(idOrName: string): Promise<ComputerInfoIface | null>;\n getNetworkPolicy(\n idOrName: string,\n ): Promise<{ egress: ComputerEgressPolicy; allow: string[] } | null>;\n action(idOrName: string, action: ComputerAction): Promise<void>;\n exec(computerId: string, body: ExecBodyIface): Promise<{ id: string; bootId: string }>;\n run(\n computerId: string,\n body: ExecBodyIface & { waitMs?: number },\n ): Promise<ComputerRunResultIface>;\n output(args: {\n computerId: string;\n processId: string;\n bootId: string;\n stdoutOffset: number;\n stderrOffset: number;\n waitMs: number;\n signal?: AbortSignal;\n }): Promise<ComputerProcessOutputIface>;\n input(args: {\n computerId: string;\n processId: string;\n body: Record<string, JsonValue>;\n signal?: AbortSignal;\n }): Promise<void>;\n terminate(args: { computerId: string; processId: string; bootId: string }): Promise<void>;\n readFile(args: {\n computerId: string;\n path: string;\n offset: number;\n length: number;\n signal?: AbortSignal;\n }): Promise<{ dataB64: string; eof: boolean }>;\n writeFile(args: {\n computerId: string;\n path: string;\n dataB64: string;\n append: boolean;\n signal?: AbortSignal;\n }): Promise<void>;\n // An `http`-scoped token for one computer's ports (a preview token).\n createPreviewToken(args: { computerId: string; name: string; expiresAt: Date }): Promise<string>;\n // The organization's and the platform's image versions, newest first.\n listImages(args: { lineage: string; state: ComputerImageState }): Promise<ComputerImageIface[]>;\n // Registering a digest the organization already has returns that version.\n registerImage(args: {\n reference: string;\n registryCredentialId?: string;\n }): Promise<ComputerImageIface>;\n getImage(imageVersionId: string): Promise<ComputerImageIface>;\n // Captures a running computer as a base; the computer stops for it.\n captureComputer(idOrName: string, options?: { retained?: boolean }): Promise<ComputerBaseIface>;\n setBaseRetention(baseId: string, retained: boolean): Promise<ComputerBaseIface>;\n // Null when the base does not exist.\n getBase(baseId: string): Promise<ComputerBaseIface | null>;\n}\n\nexport const createComputersApiClient = (args: {\n config: ComputersApiConfigIface;\n // Asked for access before each request.\n credentials: ComputersApiCredentialsIface | undefined;\n}): ComputersApiClientIface => {\n const request = async <T>(req: {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n query?: Record<string, string>;\n body?: object;\n headers?: Record<string, string>;\n signal?: AbortSignal;\n schema: z.ZodType<T>;\n }): Promise<T> => {\n const send = async (refresh: boolean) => {\n const access = await args.credentials?.computersAccess({ refresh });\n if (access == null) {\n throw computersApiError(\n 'the computer provider was granted no Computers API access',\n null,\n ComputersApiErrorCode.CredentialMissing,\n );\n }\n return createTransport({\n baseUrl: access.baseUrl,\n auth: { apiKey: access.token },\n fetch: args.config.fetch,\n requestTimeoutMs: args.config.requestTimeoutMs,\n }).request({\n method: req.method,\n path: req.path,\n query: req.query,\n jsonBody: req.body,\n headers: req.headers,\n signal: req.signal,\n });\n };\n let response = await send(false);\n // A rejected token is asked for again once, in case it expired.\n if (!response.ok && response.statusCode === 401) response = await send(true);\n if (!response.ok) throw computersApiError(response.error, response.statusCode ?? null);\n const parsed = response.data;\n const result = req.schema.safeParse(parsed);\n if (!result.success)\n throw computersApiError(`unexpected Computers API ${req.path} response`, null);\n return result.data;\n };\n\n const computerPath = (idOrName: string): string => `/computers/${encodeURIComponent(idOrName)}`;\n const toInfo = (row: JsonValue): ComputerInfoIface => {\n const info = normalizeComputerInfo(row);\n if (info == null) throw computersApiError('unexpected computer shape', null);\n return info;\n };\n const toImage = (row: JsonValue): ComputerImageIface => {\n const image = normalizeComputerImage(row);\n if (image == null) throw computersApiError('unexpected image shape', null);\n return image;\n };\n const toBase = (row: JsonValue): ComputerBaseIface => {\n const base = normalizeComputerBase(row);\n if (base == null) throw computersApiError('unexpected base shape', null);\n return base;\n };\n const anyBody = jsonValueSchema;\n\n return {\n inspectProcesses: async (computerId, input) => {\n const result = await inspectComputerProcesses({\n computerPath: computerPath(computerId),\n request: input,\n transport: {\n request: async (args) =>\n ok(\n await request({\n method: args.method,\n path: args.path,\n query: args.query,\n body: args.jsonBody,\n schema: jsonValueSchema,\n }),\n ),\n },\n });\n if (!result.ok) throw computersApiError(result.error, result.statusCode ?? null);\n return input.operation === ProcessInspectionOperation.Available && 'available' in result.data\n ? { ...result.data, canStop: true }\n : result.data;\n },\n createComputer: async (body, idempotencyKey) => {\n // The token determines the project.\n const response = await request({\n method: 'POST',\n path: '/computers',\n body,\n headers: { 'idempotency-key': idempotencyKey },\n schema: computerResponseSchema,\n });\n return toInfo(response.computer);\n },\n getNetworkPolicy: async (idOrName) => {\n const response = await request({\n method: 'GET',\n path: computerPath(idOrName),\n schema: computerResponseSchema,\n });\n const parsed = z\n .object({\n network: z.object({\n egress: z.nativeEnum(ComputerEgressPolicy),\n allow: z.array(z.string()).default([]),\n }),\n })\n .safeParse(response.computer);\n if (!parsed.success)\n throw computersApiError(\n 'The public Computers API did not return a supported network policy',\n null,\n );\n return parsed.data.network;\n },\n getComputer: async (idOrName) => {\n try {\n const response = await request({\n method: 'GET',\n path: computerPath(idOrName),\n schema: computerResponseSchema,\n });\n return toInfo(response.computer);\n } catch (error) {\n if (isComputersApiStatus(error, 404)) return null;\n throw error;\n }\n },\n action: async (idOrName, action) => {\n await request({\n method: 'POST',\n path: `${computerPath(idOrName)}/actions/${action}`,\n body: {},\n schema: anyBody,\n });\n },\n openBrowserAccess: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/browser-access`,\n body: options,\n schema: computerBrowserAccessSchema,\n }),\n closeBrowserAccess: async (computerId, tokenId) => {\n await request({\n method: 'DELETE',\n path: `${computerPath(computerId)}/browser-access/${encodeURIComponent(tokenId)}`,\n schema: anyBody,\n });\n },\n readSnapshot: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/file-snapshots/read`,\n body,\n schema: computerSnapshotResponseSchema,\n }),\n screenStatus: (computerId, signal) =>\n request({\n method: 'GET',\n path: `${computerPath(computerId)}/screen`,\n signal,\n schema: z.object({ available: z.boolean() }),\n }),\n openScreen: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/screen`,\n body: options ?? {},\n schema: z.object({ url: z.string().url(), token: z.string(), expiresAt: z.string() }),\n }),\n computerUseStatus: async (computerId, signal) =>\n request({\n method: 'GET',\n path: `${computerPath(computerId)}/computer-use`,\n signal,\n schema: z.object({ available: z.boolean(), installed: z.boolean().default(false) }),\n }),\n computerUseCatalog: async ({ image, signal }) => {\n try {\n return (\n await request({\n method: 'GET',\n path: '/computer-use/catalog',\n query: { image },\n signal,\n schema: computerUseCatalogSchema,\n })\n ).tools;\n } catch (error) {\n // An API without the catalog leaves it unknown.\n if (isComputersApiStatus(error, 404)) return null;\n throw error;\n }\n },\n openComputerUse: (computerId, options) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/computer-use`,\n body: { cwd: options.cwd, readyTimeoutMs: options.readyTimeoutMs },\n signal: options.signal,\n schema: execResponseSchema,\n }),\n disableAutoResume: async (computerId) => {\n await request({\n method: 'PATCH',\n path: computerPath(computerId),\n body: { autoResume: false },\n schema: computerResponseSchema,\n });\n },\n setNetworkPolicy: async (computerId, policy, lifecycle) => {\n await request({\n method: 'PATCH',\n path: computerPath(computerId),\n body: { network: policy, ...lifecycle },\n schema: computerResponseSchema,\n });\n },\n exec: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/exec`,\n body,\n schema: execResponseSchema,\n }),\n run: (computerId, body) =>\n request({\n method: 'POST',\n path: `${computerPath(computerId)}/run`,\n body,\n schema: runResponseSchema,\n }),\n output: (o) =>\n request({\n method: 'GET',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/output`,\n query: {\n bootId: o.bootId,\n stdoutOffset: String(o.stdoutOffset),\n stderrOffset: String(o.stderrOffset),\n waitMs: String(o.waitMs),\n },\n signal: o.signal,\n schema: processOutputSchema,\n }),\n input: async (o) => {\n await request({\n method: 'POST',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/input`,\n body: o.body,\n signal: o.signal,\n schema: anyBody,\n });\n },\n terminate: async (o) => {\n await request({\n method: 'POST',\n path: `${computerPath(o.computerId)}/processes/${encodeURIComponent(o.processId)}/terminate`,\n body: { bootId: o.bootId },\n schema: anyBody,\n });\n },\n readFile: async (o) => {\n const read = await request({\n method: 'GET',\n path: `${computerPath(o.computerId)}/files`,\n query: {\n path: o.path,\n offset: String(o.offset),\n length: String(o.length),\n autoResume: 'false',\n },\n signal: o.signal,\n schema: fileReadSchema,\n });\n // The API echoes the offset; a server that ignored the range would hand back the start again.\n if (o.offset > 0 && read.offset !== o.offset) {\n throw computersApiError(\n 'the Computers API ignored a ranged file read; partial contents refused',\n null,\n );\n }\n return { dataB64: read.dataB64, eof: read.eof };\n },\n writeFile: async (o) => {\n await request({\n method: 'PUT',\n path: `${computerPath(o.computerId)}/files`,\n body: { path: o.path, dataB64: o.dataB64, append: o.append },\n signal: o.signal,\n schema: anyBody,\n });\n },\n createPreviewToken: async (o) =>\n (\n await request({\n method: 'POST',\n path: '/tokens',\n body: {\n name: o.name,\n computerId: o.computerId,\n scopes: ['http'],\n expiresAt: o.expiresAt.toISOString(),\n },\n schema: tokenSchema,\n })\n ).token,\n listImages: async (o) =>\n (\n await request({\n method: 'GET',\n path: '/images',\n query: { lineage: o.lineage, state: o.state },\n schema: imagesResponseSchema,\n })\n ).images.map(toImage),\n registerImage: async (o) =>\n toImage(\n (\n await request({\n method: 'POST',\n path: '/images',\n body: o.registryCredentialId == null ? { reference: o.reference } : o,\n schema: imageResponseSchema,\n })\n ).image,\n ),\n getImage: async (imageVersionId) =>\n toImage(\n (\n await request({\n method: 'GET',\n path: `/images/${encodeURIComponent(imageVersionId)}`,\n schema: imageResponseSchema,\n })\n ).image,\n ),\n captureComputer: async (idOrName, options) =>\n toBase(\n (\n await request({\n method: 'POST',\n path: `${computerPath(idOrName)}/capture`,\n body: options ?? {},\n schema: baseResponseSchema,\n })\n ).base,\n ),\n setBaseRetention: async (baseId, retained) =>\n toBase(\n (\n await request({\n method: 'PATCH',\n path: `/bases/${encodeURIComponent(baseId)}`,\n body: { retained },\n schema: baseResponseSchema,\n })\n ).base,\n ),\n getBase: async (baseId) => {\n try {\n const response = await request({\n method: 'GET',\n path: `/bases/${encodeURIComponent(baseId)}`,\n schema: baseResponseSchema,\n });\n return toBase(response.base);\n } catch (error) {\n if (isComputersApiStatus(error, 404)) return null;\n throw error;\n }\n },\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["ComputersApiErrorCode", "args"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Capability, FilesAccess, ScreenAccess, ShellAccess } from "@finalai/agent-protocol";
|
|
2
|
-
import {
|
|
3
|
-
toolDescriptorSchema
|
|
4
|
-
} from "@finalai/agent-provider";
|
|
2
|
+
import {} from "@finalai/agent-provider";
|
|
5
3
|
import {
|
|
6
4
|
defineSandboxProvider,
|
|
7
5
|
SandboxCapability
|
|
@@ -18,6 +16,7 @@ import { DEVICE_PROVIDER, deviceEnvironmentSchema } from "./environment.mjs";
|
|
|
18
16
|
import { createMcpPipe } from "./mcp-pipe.mjs";
|
|
19
17
|
import { sandboxPathsFor, SandboxPlatform } from "./paths.mjs";
|
|
20
18
|
import { createTransportSession, readOnlySession } from "./session.mjs";
|
|
19
|
+
import { toolDescriptorsOf } from "./tool-catalog.mjs";
|
|
21
20
|
import { DEVICE_API_ACTING_USER_HEADER, DeviceApiClientErrorCode } from "./device-api.mjs";
|
|
22
21
|
import { DEVICE_PROVIDER as DEVICE_PROVIDER2 } from "./environment.mjs";
|
|
23
22
|
import { SandboxPlatform as SandboxPlatform2 } from "./paths.mjs";
|
|
@@ -28,13 +27,6 @@ const deviceStateSchema = z.object({
|
|
|
28
27
|
generation: z.number().int().min(0)
|
|
29
28
|
});
|
|
30
29
|
const DEVICE_COMPUTER_USE_SERVER = DEVICE_API_COMPUTER_USE_SERVER;
|
|
31
|
-
const descriptorsOf = (tools) => tools.flatMap((tool) => {
|
|
32
|
-
const parsed = toolDescriptorSchema.safeParse({
|
|
33
|
-
...tool,
|
|
34
|
-
description: tool.description ?? tool.title ?? tool.name
|
|
35
|
-
});
|
|
36
|
-
return parsed.success ? [parsed.data] : [];
|
|
37
|
-
});
|
|
38
30
|
const LEASE_RENEW_MARGIN_MS = 5 * 6e4;
|
|
39
31
|
const ONLINE_POLL_MS = 500;
|
|
40
32
|
const ONLINE_POLL_MAX_MS = 5e3;
|
|
@@ -292,10 +284,10 @@ const createDeviceSandboxProvider = (config) => {
|
|
|
292
284
|
let localKnown = local != null;
|
|
293
285
|
for (const server of local?.servers ?? []) {
|
|
294
286
|
if (server.tools == null) localKnown = false;
|
|
295
|
-
else localMcp[server.name] =
|
|
287
|
+
else localMcp[server.name] = toolDescriptorsOf(server.tools);
|
|
296
288
|
}
|
|
297
289
|
return {
|
|
298
|
-
computerUse: catalog.computerUse == null ? null :
|
|
290
|
+
computerUse: catalog.computerUse == null ? null : toolDescriptorsOf(catalog.computerUse.tools),
|
|
299
291
|
localMcp: localKnown ? localMcp : null
|
|
300
292
|
};
|
|
301
293
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/device.ts"],
|
|
4
|
-
"sourcesContent": ["import { Capability, FilesAccess, ScreenAccess, ShellAccess } from '@finalai/agent-protocol';\nimport {\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 SandboxCapabilityOffer,\n type SandboxEnvironmentImplementationIface,\n type SandboxProviderContextIface,\n type SandboxProviderHandleIface,\n type SandboxProviderIface,\n type SandboxResourceContextIface,\n type SandboxSetupIface,\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 DeviceApiCredentialsIface,\n type DeviceApiDeviceIface,\n type DeviceApiFetchIface,\n type DeviceApiLeaseIface,\n} from \"./device-api.mjs\";\nimport { DEVICE_PROVIDER, deviceEnvironmentSchema } from \"./environment.mjs\";\nimport { createMcpPipe } from \"./mcp-pipe.mjs\";\nimport { sandboxPathsFor, SandboxPlatform } from \"./paths.mjs\";\nimport { createTransportSession, readOnlySession } 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_PROVIDER } from \"./environment.mjs\";\nexport type { FinalPlatformAccessIface } from \"./environment.mjs\";\nexport { SandboxPlatform } from \"./paths.mjs\";\n\n// The `device` sandbox: a person's own computer running Final Device. The\n// provider is a client of the Device API, the Device's backend, exactly as a\n// vendor provider is a client of its vendor's API: it leases a workspace there\n// and drives files, processes, computer use and the screen through that API.\n// It knows nothing of how the Device API reaches the Device\n// (DEVICE_DIRECT_ENDPOINT.md). A Device is one person's machine: every call\n// names the user the sandbox acts for (ctx.actingUser, read per call since it\n// changes between turns), and the Device API refuses a Device that user does\n// not own. A Device takes no sandbox setup: it is the person's machine as it is.\n\nexport interface DeviceSandboxProviderConfigIface {\n // Transport options.\n api: DeviceApiConfigIface;\n // The Device API origin. Requests authenticate with the provider's platform\n // identity for it (ctx.identity), which names the acting user and which the\n // registration must be granted.\n deviceApiUrl: string;\n fetch?: DeviceApiFetchIface;\n // Device leases last at most an hour; renewed while in use.\n leaseTtlMs?: number;\n // How long attach waits 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() });\n// `generation`: the last lease generation taken; 0 before the first attach.\nconst deviceStateSchema = z.object({\n version: z.literal(2),\n deviceId: z.string(),\n generation: z.number().int().min(0),\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 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\n// A Device is the person's machine as it is: nothing is cloned, installed or\n// started on it, and its network is the person's. Notes may still describe it.\nconst refuseSetup = (setup: SandboxSetupIface | null): void => {\n if (setup == null) return;\n const parts = [\n setup.repositories.length > 0 ? 'repositories' : null,\n Object.keys(setup.variables).length > 0 ? 'variables' : null,\n setup.steps.length > 0 ? 'steps' : null,\n setup.services.length > 0 ? 'services' : null,\n Object.keys(setup.commands).length > 0 ? 'commands' : null,\n setup.network != null ? 'a network policy' : null,\n ].filter((part) => part != null);\n if (parts.length > 0)\n throw new Error(`a Final Device takes no sandbox setup (this one has ${parts.join(', ')})`);\n};\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 });\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 // An assertion for the Device API before each request, and a new one after\n // a rejection; it carries the acting user it was issued for.\n const credentialsFor = (ctx: SandboxProviderContextIface): DeviceApiCredentialsIface => ({\n deviceAccess: async (args) => ({\n baseUrl: config.deviceApiUrl,\n token: (\n await ctx.identity({ audience: config.deviceApiUrl, refresh: args?.refresh === true })\n ).token,\n }),\n });\n\n const callerFor = (ctx: SandboxProviderContextIface): DeviceApiCallerIface => ({\n organizationId: ctx.organizationId,\n credentials: credentialsFor(ctx),\n actingUser: ctx.actingUser,\n });\n\n const lease = async (args: {\n ctx: SandboxResourceContextIface;\n deviceId: string;\n afterGeneration: number;\n passive: boolean;\n }): Promise<{\n handle: SandboxProviderHandleIface<DeviceSandboxSessionIface>;\n generation: number;\n }> => {\n const { ctx } = args;\n const caller = callerFor(ctx);\n // A Device workspace is keyed by the sandbox: every attach of this sandbox\n // finds the same files, and its processes end with each lease generation.\n const bindingId = ctx.sandboxId;\n // The provider's awaitRunning, before the lease.\n const device = await awaitOnline(caller, args.deviceId, onlineWaitMs);\n // The host gates each call by its grant; the lease is the most the Device allows.\n const leaseAccess = device.writable ? DeviceApiLeaseAccess.Write : DeviceApiLeaseAccess.Read;\n const afterGeneration = args.afterGeneration === 0 ? undefined : args.afterGeneration;\n let current: DeviceApiLeaseIface = args.passive\n ? await api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\n bindingId,\n access: leaseAccess,\n })\n : await api.lease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\n bindingId,\n access: leaseAccess,\n ttlMs: leaseTtlMs,\n afterGeneration,\n });\n if (\n args.passive &&\n (current.deviceId !== device.id ||\n current.generation < args.afterGeneration ||\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 leaseHandle = async (): Promise<string> => {\n if (current.expiresAtMs - now() < LEASE_RENEW_MARGIN_MS) {\n renewing ??= (\n args.passive\n ? api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\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 || args.passive) 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: leaseHandle }),\n baseEnv: async () => ({}),\n randomUuid,\n paths,\n commands: commandEncoderFor(platform),\n // Lease processes end with the lease generation; a new one knows none.\n processScope: () => String(current.generation),\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 (processes && !args.passive) capabilities.push(SandboxCapability.DurableProcesses);\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 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 leaseHandle(),\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 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 sandbox: DeviceSandboxSessionIface = { ...session, capabilities };\n // Without process access there is nothing to attach to.\n if (!processes) delete sandbox.attachProcess;\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 leaseHandle() })).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 // A viewer's lease is the existing one: it changes nothing.\n sandbox: args.passive ? readOnlySession(sandbox) : sandbox,\n onSandboxStop: release,\n onSandboxDelete: release,\n onRuntimeShutdown: async () => undefined,\n },\n };\n };\n\n const environment = (\n rawOptions?: JsonValue,\n ): SandboxEnvironmentImplementationIface<\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 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 return {\n // What a lease on this Device grants, known from the registry without\n // leasing (the Device API's rule: processes and computer use with a write\n // lease on a Device that allows processes).\n capabilities: async (ctx): Promise<SandboxCapabilityOffer> => {\n const device = await api.getDevice({\n caller: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(ctx)),\n });\n if (device == null) return {};\n const offer: SandboxCapabilityOffer = {\n [Capability.Files]: device.writable ? FilesAccess.Write : FilesAccess.Read,\n };\n if (device.writable && device.allowProcesses) {\n offer[Capability.Shell] = ShellAccess.Execute;\n offer[Capability.ComputerUse] = ScreenAccess.Control;\n } else if (device.screenReady) offer[Capability.ComputerUse] = ScreenAccess.View;\n return offer;\n },\n features: async (ctx) => {\n const device = await api.getDevice({\n caller: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(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.DurableProcesses,\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: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(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 processes: async (ctx, rawState, request) => {\n const state = deviceStateSchema.parse(rawState);\n return api.inspectProcesses({\n caller: callerFor(ctx),\n deviceId: state.deviceId,\n bindingId: ctx.sandboxId,\n request,\n });\n },\n artifactKey: async (ctx) => `device:${await resolveDeviceId(callerFor(ctx))}`,\n prepare: async (ctx) => ({ version: 1, deviceId: await resolveDeviceId(callerFor(ctx)) }),\n create: async (_ctx, { artifact, setup }) => {\n refuseSetup(setup);\n return { state: { version: 2, deviceId: artifact.deviceId, generation: 0 } };\n },\n awaitRunning: async (ctx, rawState, { timeoutMs }) => {\n const { deviceId } = deviceStateSchema.pick({ deviceId: true }).parse(rawState);\n await awaitOnline(callerFor(ctx), deviceId, timeoutMs);\n },\n // A Device never takes a lease back at an old generation once it lapsed,\n // so every attach leases a later one, and no process from before survives.\n attach: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n const { handle, generation } = await lease({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n passive: false,\n });\n return {\n handle,\n state: { ...state, generation },\n fresh: state.generation === 0,\n booted: true,\n };\n },\n attachPassive: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await lease({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n passive: true,\n })\n ).handle;\n },\n // The lease lapses on its own; the next generation retires its processes.\n detach: async (_ctx, { state }) => deviceStateSchema.parse(state),\n park: async (_ctx, { state }) => deviceStateSchema.parse(state),\n // The Device stays the person's; its workspace for this sandbox is let go.\n delete: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n await api.releaseWorkspaceLeases({\n caller: callerFor(ctx),\n deviceId: state.deviceId,\n bindingId: ctx.sandboxId,\n });\n },\n };\n };\n\n return defineSandboxProvider({\n name: DEVICE_PROVIDER,\n stateProtocolVersion: 2,\n environment,\n });\n};\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,YAAY,aAAa,cAAc,mBAAmB;AACnE
|
|
4
|
+
"sourcesContent": ["import { Capability, FilesAccess, ScreenAccess, ShellAccess } from '@finalai/agent-protocol';\nimport { type JsonValue, type ToolDescriptorIface } from '@finalai/agent-provider';\nimport {\n defineSandboxProvider,\n SandboxCapability,\n type McpChannelEndpointIface,\n type NetworkSandboxSessionIface,\n type SandboxCapabilityOffer,\n type SandboxEnvironmentImplementationIface,\n type SandboxProviderContextIface,\n type SandboxProviderHandleIface,\n type SandboxProviderIface,\n type SandboxResourceContextIface,\n type SandboxSetupIface,\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 DeviceApiCredentialsIface,\n type DeviceApiDeviceIface,\n type DeviceApiFetchIface,\n type DeviceApiLeaseIface,\n} from \"./device-api.mjs\";\nimport { DEVICE_PROVIDER, deviceEnvironmentSchema } from \"./environment.mjs\";\nimport { createMcpPipe } from \"./mcp-pipe.mjs\";\nimport { sandboxPathsFor, SandboxPlatform } from \"./paths.mjs\";\nimport { createTransportSession, readOnlySession } from \"./session.mjs\";\nimport { toolDescriptorsOf } from \"./tool-catalog.mjs\";\n\nexport { DEVICE_API_ACTING_USER_HEADER, DeviceApiClientErrorCode } from \"./device-api.mjs\";\nexport type { DeviceApiConfigIface, DeviceApiFetchIface } from \"./device-api.mjs\";\nexport { DEVICE_PROVIDER } from \"./environment.mjs\";\nexport type { FinalPlatformAccessIface } from \"./environment.mjs\";\nexport { SandboxPlatform } from \"./paths.mjs\";\n\n// The `device` sandbox: a person's own computer running Final Device. The\n// provider is a client of the Device API, the Device's backend, exactly as a\n// vendor provider is a client of its vendor's API: it leases a workspace there\n// and drives files, processes, computer use and the screen through that API.\n// It knows nothing of how the Device API reaches the Device\n// (DEVICE_DIRECT_ENDPOINT.md). A Device is one person's machine: every call\n// names the user the sandbox acts for (ctx.actingUser, read per call since it\n// changes between turns), and the Device API refuses a Device that user does\n// not own. A Device takes no sandbox setup: it is the person's machine as it is.\n\nexport interface DeviceSandboxProviderConfigIface {\n // Transport options.\n api: DeviceApiConfigIface;\n // The Device API origin. Requests authenticate with the provider's platform\n // identity for it (ctx.identity), which names the acting user and which the\n // registration must be granted.\n deviceApiUrl: string;\n fetch?: DeviceApiFetchIface;\n // Device leases last at most an hour; renewed while in use.\n leaseTtlMs?: number;\n // How long attach waits 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() });\n// `generation`: the last lease generation taken; 0 before the first attach.\nconst deviceStateSchema = z.object({\n version: z.literal(2),\n deviceId: z.string(),\n generation: z.number().int().min(0),\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 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 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\n// A Device is the person's machine as it is: nothing is cloned, installed or\n// started on it, and its network is the person's. Notes may still describe it.\nconst refuseSetup = (setup: SandboxSetupIface | null): void => {\n if (setup == null) return;\n const parts = [\n setup.repositories.length > 0 ? 'repositories' : null,\n Object.keys(setup.variables).length > 0 ? 'variables' : null,\n setup.steps.length > 0 ? 'steps' : null,\n setup.services.length > 0 ? 'services' : null,\n Object.keys(setup.commands).length > 0 ? 'commands' : null,\n setup.network != null ? 'a network policy' : null,\n ].filter((part) => part != null);\n if (parts.length > 0)\n throw new Error(`a Final Device takes no sandbox setup (this one has ${parts.join(', ')})`);\n};\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 });\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 // An assertion for the Device API before each request, and a new one after\n // a rejection; it carries the acting user it was issued for.\n const credentialsFor = (ctx: SandboxProviderContextIface): DeviceApiCredentialsIface => ({\n deviceAccess: async (args) => ({\n baseUrl: config.deviceApiUrl,\n token: (\n await ctx.identity({ audience: config.deviceApiUrl, refresh: args?.refresh === true })\n ).token,\n }),\n });\n\n const callerFor = (ctx: SandboxProviderContextIface): DeviceApiCallerIface => ({\n organizationId: ctx.organizationId,\n credentials: credentialsFor(ctx),\n actingUser: ctx.actingUser,\n });\n\n const lease = async (args: {\n ctx: SandboxResourceContextIface;\n deviceId: string;\n afterGeneration: number;\n passive: boolean;\n }): Promise<{\n handle: SandboxProviderHandleIface<DeviceSandboxSessionIface>;\n generation: number;\n }> => {\n const { ctx } = args;\n const caller = callerFor(ctx);\n // A Device workspace is keyed by the sandbox: every attach of this sandbox\n // finds the same files, and its processes end with each lease generation.\n const bindingId = ctx.sandboxId;\n // The provider's awaitRunning, before the lease.\n const device = await awaitOnline(caller, args.deviceId, onlineWaitMs);\n // The host gates each call by its grant; the lease is the most the Device allows.\n const leaseAccess = device.writable ? DeviceApiLeaseAccess.Write : DeviceApiLeaseAccess.Read;\n const afterGeneration = args.afterGeneration === 0 ? undefined : args.afterGeneration;\n let current: DeviceApiLeaseIface = args.passive\n ? await api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\n bindingId,\n access: leaseAccess,\n })\n : await api.lease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\n bindingId,\n access: leaseAccess,\n ttlMs: leaseTtlMs,\n afterGeneration,\n });\n if (\n args.passive &&\n (current.deviceId !== device.id ||\n current.generation < args.afterGeneration ||\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 leaseHandle = async (): Promise<string> => {\n if (current.expiresAtMs - now() < LEASE_RENEW_MARGIN_MS) {\n renewing ??= (\n args.passive\n ? api.recoverLease({\n caller,\n deviceId: device.id,\n sessionId: ctx.sandboxId,\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 || args.passive) 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: leaseHandle }),\n baseEnv: async () => ({}),\n randomUuid,\n paths,\n commands: commandEncoderFor(platform),\n // Lease processes end with the lease generation; a new one knows none.\n processScope: () => String(current.generation),\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 (processes && !args.passive) capabilities.push(SandboxCapability.DurableProcesses);\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 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 leaseHandle(),\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 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 sandbox: DeviceSandboxSessionIface = { ...session, capabilities };\n // Without process access there is nothing to attach to.\n if (!processes) delete sandbox.attachProcess;\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 leaseHandle() })).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 // A viewer's lease is the existing one: it changes nothing.\n sandbox: args.passive ? readOnlySession(sandbox) : sandbox,\n onSandboxStop: release,\n onSandboxDelete: release,\n onRuntimeShutdown: async () => undefined,\n },\n };\n };\n\n const environment = (\n rawOptions?: JsonValue,\n ): SandboxEnvironmentImplementationIface<\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 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 return {\n // What a lease on this Device grants, known from the registry without\n // leasing (the Device API's rule: processes and computer use with a write\n // lease on a Device that allows processes).\n capabilities: async (ctx): Promise<SandboxCapabilityOffer> => {\n const device = await api.getDevice({\n caller: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(ctx)),\n });\n if (device == null) return {};\n const offer: SandboxCapabilityOffer = {\n [Capability.Files]: device.writable ? FilesAccess.Write : FilesAccess.Read,\n };\n if (device.writable && device.allowProcesses) {\n offer[Capability.Shell] = ShellAccess.Execute;\n offer[Capability.ComputerUse] = ScreenAccess.Control;\n } else if (device.screenReady) offer[Capability.ComputerUse] = ScreenAccess.View;\n return offer;\n },\n features: async (ctx) => {\n const device = await api.getDevice({\n caller: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(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.DurableProcesses,\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: callerFor(ctx),\n deviceId: await resolveDeviceId(callerFor(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] = toolDescriptorsOf(server.tools);\n }\n return {\n computerUse:\n catalog.computerUse == null ? null : toolDescriptorsOf(catalog.computerUse.tools),\n localMcp: localKnown ? localMcp : null,\n };\n },\n processes: async (ctx, rawState, request) => {\n const state = deviceStateSchema.parse(rawState);\n return api.inspectProcesses({\n caller: callerFor(ctx),\n deviceId: state.deviceId,\n bindingId: ctx.sandboxId,\n request,\n });\n },\n artifactKey: async (ctx) => `device:${await resolveDeviceId(callerFor(ctx))}`,\n prepare: async (ctx) => ({ version: 1, deviceId: await resolveDeviceId(callerFor(ctx)) }),\n create: async (_ctx, { artifact, setup }) => {\n refuseSetup(setup);\n return { state: { version: 2, deviceId: artifact.deviceId, generation: 0 } };\n },\n awaitRunning: async (ctx, rawState, { timeoutMs }) => {\n const { deviceId } = deviceStateSchema.pick({ deviceId: true }).parse(rawState);\n await awaitOnline(callerFor(ctx), deviceId, timeoutMs);\n },\n // A Device never takes a lease back at an old generation once it lapsed,\n // so every attach leases a later one, and no process from before survives.\n attach: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n const { handle, generation } = await lease({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n passive: false,\n });\n return {\n handle,\n state: { ...state, generation },\n fresh: state.generation === 0,\n booted: true,\n };\n },\n attachPassive: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n return (\n await lease({\n ctx,\n deviceId: state.deviceId,\n afterGeneration: state.generation,\n passive: true,\n })\n ).handle;\n },\n // The lease lapses on its own; the next generation retires its processes.\n detach: async (_ctx, { state }) => deviceStateSchema.parse(state),\n park: async (_ctx, { state }) => deviceStateSchema.parse(state),\n // The Device stays the person's; its workspace for this sandbox is let go.\n delete: async (ctx, { state: rawState }) => {\n const state = deviceStateSchema.parse(rawState);\n await api.releaseWorkspaceLeases({\n caller: callerFor(ctx),\n deviceId: state.deviceId,\n bindingId: ctx.sandboxId,\n });\n },\n };\n };\n\n return defineSandboxProvider({\n name: DEVICE_PROVIDER,\n stateProtocolVersion: 2,\n environment,\n });\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["DEVICE_PROVIDER", "SandboxPlatform"]
|
|
7
7
|
}
|
|
@@ -10,6 +10,7 @@ export interface McpPipeWireIface {
|
|
|
10
10
|
read(signal?: AbortSignal): Promise<string>;
|
|
11
11
|
close(): Promise<void>;
|
|
12
12
|
}
|
|
13
|
+
export declare const utf8Writes: (bytes: Uint8Array, maxBytes: number) => Uint8Array[];
|
|
13
14
|
export declare const createMcpPipe: (args: {
|
|
14
15
|
wire: McpPipeWireIface;
|
|
15
16
|
}) => McpPipeIface;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { jsonValueSchema } from "@finalai/agent-provider";
|
|
2
2
|
import { decodeBase64, encodeBase64 } from "./bytes.mjs";
|
|
3
3
|
const MAX_WRITE_BYTES = 64 * 1024;
|
|
4
|
+
const utf8Writes = (bytes, maxBytes) => {
|
|
5
|
+
const writes = [];
|
|
6
|
+
let start = 0;
|
|
7
|
+
while (start < bytes.byteLength) {
|
|
8
|
+
let end = Math.min(start + maxBytes, bytes.byteLength);
|
|
9
|
+
let boundary = end;
|
|
10
|
+
while (boundary > start && boundary < bytes.byteLength && ((bytes[boundary] ?? 0) & 192) === 128)
|
|
11
|
+
boundary -= 1;
|
|
12
|
+
if (boundary > start) end = boundary;
|
|
13
|
+
writes.push(bytes.subarray(start, end));
|
|
14
|
+
start = end;
|
|
15
|
+
}
|
|
16
|
+
return writes;
|
|
17
|
+
};
|
|
4
18
|
const createMcpPipe = (args) => {
|
|
5
19
|
let opened = null;
|
|
6
20
|
const decoder = new TextDecoder();
|
|
@@ -32,8 +46,8 @@ const createMcpPipe = (args) => {
|
|
|
32
46
|
await ensureOpen(signal);
|
|
33
47
|
const bytes = new TextEncoder().encode(`${JSON.stringify(message)}
|
|
34
48
|
`);
|
|
35
|
-
for (
|
|
36
|
-
await args.wire.write(encodeBase64(
|
|
49
|
+
for (const write of utf8Writes(bytes, MAX_WRITE_BYTES)) {
|
|
50
|
+
await args.wire.write(encodeBase64(write), signal);
|
|
37
51
|
}
|
|
38
52
|
};
|
|
39
53
|
return {
|
|
@@ -50,6 +64,7 @@ const createMcpPipe = (args) => {
|
|
|
50
64
|
};
|
|
51
65
|
};
|
|
52
66
|
export {
|
|
53
|
-
createMcpPipe
|
|
67
|
+
createMcpPipe,
|
|
68
|
+
utf8Writes
|
|
54
69
|
};
|
|
55
70
|
//# sourceMappingURL=mcp-pipe.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/sandboxes/mcp-pipe.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JsonValue } from '@finalai/agent-provider';\nimport { jsonValueSchema } from '@finalai/agent-provider';\nimport { decodeBase64, encodeBase64 } from \"./bytes.mjs\";\n\n// An MCP server reached as an open/write/read/close byte pipe (the Device API's\n// `/v1/leases/:lease/mcp/:server/*`), framed as newline-delimited JSON-RPC. The\n// device handle hands it over as an MCP channel (McpChannelIface), which the\n// session service's MCP projection drives like any sandbox-hosted server.\n\nexport interface McpPipeIface {\n // Sends one JSON-RPC message (request, response or notification).\n send(message: JsonValue, signal?: AbortSignal): Promise<void>;\n // Messages the server sent since the last call; empty when none arrived.\n receive(signal?: AbortSignal): Promise<JsonValue[]>;\n close(): Promise<void>;\n}\n\nexport interface McpPipeWireIface {\n open(signal?: AbortSignal): Promise<void>;\n // Base64 bytes in; resolves once written.\n write(dataBase64: string, signal?: AbortSignal): Promise<void>;\n // Base64 bytes out; empty when nothing arrived before the wire's idle wait.\n read(signal?: AbortSignal): Promise<string>;\n close(): Promise<void>;\n}\n\n// Writes are clicks and keystrokes: keep them small and never replay one.\nconst MAX_WRITE_BYTES = 64 * 1024;\n\nexport const createMcpPipe = (args: { wire: McpPipeWireIface }): McpPipeIface => {\n let opened: Promise<void> | null = null;\n const decoder = new TextDecoder();\n let partial = '';\n const inbox: JsonValue[] = [];\n const ensureOpen = (signal?: AbortSignal): Promise<void> => {\n opened ??= args.wire.open(signal).catch((cause: unknown) => {\n opened = null;\n throw cause;\n });\n return opened;\n };\n\n const pull = async (signal?: AbortSignal): Promise<void> => {\n await ensureOpen(signal);\n const data = await args.wire.read(signal);\n if (data === '') return;\n const lines = (partial + decoder.decode(decodeBase64(data), { stream: true })).split('\\n');\n partial = lines.pop() ?? '';\n for (const line of lines) {\n if (line.trim() === '') continue;\n try {\n const parsed = jsonValueSchema.safeParse(JSON.parse(line));\n if (parsed.success) inbox.push(parsed.data);\n } catch {\n // Not JSON-RPC (server log noise on stdout); dropped.\n }\n }\n };\n\n const send = async (message: JsonValue, signal?: AbortSignal): Promise<void> => {\n await ensureOpen(signal);\n const bytes = new TextEncoder().encode(`${JSON.stringify(message)}\\n`);\n for (
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,cAAc,oBAAoB;AAyB3C,MAAM,kBAAkB,KAAK;
|
|
4
|
+
"sourcesContent": ["import type { JsonValue } from '@finalai/agent-provider';\nimport { jsonValueSchema } from '@finalai/agent-provider';\nimport { decodeBase64, encodeBase64 } from \"./bytes.mjs\";\n\n// An MCP server reached as an open/write/read/close byte pipe (the Device API's\n// `/v1/leases/:lease/mcp/:server/*`), framed as newline-delimited JSON-RPC. The\n// device handle hands it over as an MCP channel (McpChannelIface), which the\n// session service's MCP projection drives like any sandbox-hosted server.\n\nexport interface McpPipeIface {\n // Sends one JSON-RPC message (request, response or notification).\n send(message: JsonValue, signal?: AbortSignal): Promise<void>;\n // Messages the server sent since the last call; empty when none arrived.\n receive(signal?: AbortSignal): Promise<JsonValue[]>;\n close(): Promise<void>;\n}\n\nexport interface McpPipeWireIface {\n open(signal?: AbortSignal): Promise<void>;\n // Base64 bytes in; resolves once written.\n write(dataBase64: string, signal?: AbortSignal): Promise<void>;\n // Base64 bytes out; empty when nothing arrived before the wire's idle wait.\n read(signal?: AbortSignal): Promise<string>;\n close(): Promise<void>;\n}\n\n// Writes are clicks and keystrokes: keep them small and never replay one.\nconst MAX_WRITE_BYTES = 64 * 1024;\n\n// Splits UTF-8 bytes into writes of at most `maxBytes`, each ending on a\n// character boundary: a receiver may decode every write on its own.\nexport const utf8Writes = (bytes: Uint8Array, maxBytes: number): Uint8Array[] => {\n const writes: Uint8Array[] = [];\n let start = 0;\n while (start < bytes.byteLength) {\n let end = Math.min(start + maxBytes, bytes.byteLength);\n // Back off continuation bytes (10xxxxxx) so the next write starts a character.\n let boundary = end;\n while (\n boundary > start &&\n boundary < bytes.byteLength &&\n ((bytes[boundary] ?? 0) & 0xc0) === 0x80\n )\n boundary -= 1;\n if (boundary > start) end = boundary;\n writes.push(bytes.subarray(start, end));\n start = end;\n }\n return writes;\n};\n\nexport const createMcpPipe = (args: { wire: McpPipeWireIface }): McpPipeIface => {\n let opened: Promise<void> | null = null;\n const decoder = new TextDecoder();\n let partial = '';\n const inbox: JsonValue[] = [];\n const ensureOpen = (signal?: AbortSignal): Promise<void> => {\n opened ??= args.wire.open(signal).catch((cause: unknown) => {\n opened = null;\n throw cause;\n });\n return opened;\n };\n\n const pull = async (signal?: AbortSignal): Promise<void> => {\n await ensureOpen(signal);\n const data = await args.wire.read(signal);\n if (data === '') return;\n const lines = (partial + decoder.decode(decodeBase64(data), { stream: true })).split('\\n');\n partial = lines.pop() ?? '';\n for (const line of lines) {\n if (line.trim() === '') continue;\n try {\n const parsed = jsonValueSchema.safeParse(JSON.parse(line));\n if (parsed.success) inbox.push(parsed.data);\n } catch {\n // Not JSON-RPC (server log noise on stdout); dropped.\n }\n }\n };\n\n const send = async (message: JsonValue, signal?: AbortSignal): Promise<void> => {\n await ensureOpen(signal);\n const bytes = new TextEncoder().encode(`${JSON.stringify(message)}\\n`);\n for (const write of utf8Writes(bytes, MAX_WRITE_BYTES)) {\n await args.wire.write(encodeBase64(write), signal);\n }\n };\n\n return {\n send,\n receive: async (signal) => {\n await pull(signal);\n return inbox.splice(0);\n },\n close: async () => {\n if (opened == null) return;\n opened = null;\n await args.wire.close();\n },\n };\n};\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,cAAc,oBAAoB;AAyB3C,MAAM,kBAAkB,KAAK;AAItB,MAAM,aAAa,CAAC,OAAmB,aAAmC;AAC/E,QAAM,SAAuB,CAAC;AAC9B,MAAI,QAAQ;AACZ,SAAO,QAAQ,MAAM,YAAY;AAC/B,QAAI,MAAM,KAAK,IAAI,QAAQ,UAAU,MAAM,UAAU;AAErD,QAAI,WAAW;AACf,WACE,WAAW,SACX,WAAW,MAAM,gBACf,MAAM,QAAQ,KAAK,KAAK,SAAU;AAEpC,kBAAY;AACd,QAAI,WAAW,MAAO,OAAM;AAC5B,WAAO,KAAK,MAAM,SAAS,OAAO,GAAG,CAAC;AACtC,YAAQ;AAAA,EACV;AACA,SAAO;AACT;AAEO,MAAM,gBAAgB,CAAC,SAAmD;AAC/E,MAAI,SAA+B;AACnC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,UAAU;AACd,QAAM,QAAqB,CAAC;AAC5B,QAAM,aAAa,CAAC,WAAwC;AAC1D,eAAW,KAAK,KAAK,KAAK,MAAM,EAAE,MAAM,CAAC,UAAmB;AAC1D,eAAS;AACT,YAAM;AAAA,IACR,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,WAAwC;AAC1D,UAAM,WAAW,MAAM;AACvB,UAAM,OAAO,MAAM,KAAK,KAAK,KAAK,MAAM;AACxC,QAAI,SAAS,GAAI;AACjB,UAAM,SAAS,UAAU,QAAQ,OAAO,aAAa,IAAI,GAAG,EAAE,QAAQ,KAAK,CAAC,GAAG,MAAM,IAAI;AACzF,cAAU,MAAM,IAAI,KAAK;AACzB,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,KAAK,MAAM,GAAI;AACxB,UAAI;AACF,cAAM,SAAS,gBAAgB,UAAU,KAAK,MAAM,IAAI,CAAC;AACzD,YAAI,OAAO,QAAS,OAAM,KAAK,OAAO,IAAI;AAAA,MAC5C,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,OAAO,SAAoB,WAAwC;AAC9E,UAAM,WAAW,MAAM;AACvB,UAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,CAAI;AACrE,eAAW,SAAS,WAAW,OAAO,eAAe,GAAG;AACtD,YAAM,KAAK,KAAK,MAAM,aAAa,KAAK,GAAG,MAAM;AAAA,IACnD;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,OAAO,WAAW;AACzB,YAAM,KAAK,MAAM;AACjB,aAAO,MAAM,OAAO,CAAC;AAAA,IACvB;AAAA,IACA,OAAO,YAAY;AACjB,UAAI,UAAU,KAAM;AACpB,eAAS;AACT,YAAM,KAAK,KAAK,MAAM;AAAA,IACxB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -165,6 +165,8 @@ const createFakeComputersApi = (args = {}) => {
|
|
|
165
165
|
if (key != null) byIdempotency.set(key, id);
|
|
166
166
|
return json({ computer: wire(computer2) });
|
|
167
167
|
}
|
|
168
|
+
if (request.method === "GET" && path === "/computer-use/catalog" && args.computerUseCatalog != null)
|
|
169
|
+
return json({ tools: args.computerUseCatalog[url.searchParams.get("image") ?? ""] ?? null });
|
|
168
170
|
if (request.method === "GET" && path === "/images") {
|
|
169
171
|
const lineage = url.searchParams.get("lineage");
|
|
170
172
|
const state = url.searchParams.get("state");
|