@agimon-ai/doompi-computer-use 0.0.1-alpha.2 → 0.0.1-alpha.8
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/README.md +6 -3
- package/dist/adapters/computerUseApi.cjs +1 -1
- package/dist/adapters/computerUseApi.cjs.map +1 -1
- package/dist/adapters/computerUseApi.d.cts +9 -5
- package/dist/adapters/computerUseApi.d.cts.map +1 -1
- package/dist/adapters/computerUseApi.d.mts +9 -5
- package/dist/adapters/computerUseApi.d.mts.map +1 -1
- package/dist/adapters/computerUseApi.mjs +1 -1
- package/dist/adapters/computerUseApi.mjs.map +1 -1
- package/dist/adapters/pi/computerScriptRunner.cjs +45 -0
- package/dist/adapters/pi/computerScriptRunner.cjs.map +1 -0
- package/dist/adapters/pi/computerScriptRunner.d.cts +12 -0
- package/dist/adapters/pi/computerScriptRunner.d.cts.map +1 -0
- package/dist/adapters/pi/computerScriptRunner.d.mts +12 -0
- package/dist/adapters/pi/computerScriptRunner.d.mts.map +1 -0
- package/dist/adapters/pi/computerScriptRunner.mjs +45 -0
- package/dist/adapters/pi/computerScriptRunner.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -2
- package/dist/adapters/pi/extension.cjs.map +1 -1
- package/dist/adapters/pi/extension.d.cts +4 -4
- package/dist/adapters/pi/extension.d.cts.map +1 -1
- package/dist/adapters/pi/extension.d.mts +4 -4
- package/dist/adapters/pi/extension.d.mts.map +1 -1
- package/dist/adapters/pi/extension.mjs +2 -2
- package/dist/adapters/pi/extension.mjs.map +1 -1
- package/dist/adapters/pi/sessionApiClient.d.cts +3 -4
- package/dist/adapters/pi/sessionApiClient.d.cts.map +1 -1
- package/dist/adapters/pi/sessionApiClient.d.mts +3 -4
- package/dist/adapters/pi/sessionApiClient.d.mts.map +1 -1
- package/dist/adapters/webComputerUseChannel.cjs +1 -1
- package/dist/adapters/webComputerUseChannel.cjs.map +1 -1
- package/dist/adapters/webComputerUseChannel.d.cts +3 -3
- package/dist/adapters/webComputerUseChannel.d.cts.map +1 -1
- package/dist/adapters/webComputerUseChannel.d.mts +3 -3
- package/dist/adapters/webComputerUseChannel.d.mts.map +1 -1
- package/dist/adapters/webComputerUseChannel.mjs +1 -1
- package/dist/adapters/webComputerUseChannel.mjs.map +1 -1
- package/dist/container/index.cjs +1 -1
- package/dist/container/index.cjs.map +1 -1
- package/dist/container/index.d.cts +2 -2
- package/dist/container/index.d.cts.map +1 -1
- package/dist/container/index.d.mts +2 -2
- package/dist/container/index.d.mts.map +1 -1
- package/dist/container/index.mjs +1 -1
- package/dist/container/index.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +1 -1
- package/dist/services/extensionService.d.cts +1 -2
- package/dist/services/extensionService.d.cts.map +1 -1
- package/dist/services/extensionService.d.mts +1 -2
- package/dist/services/extensionService.d.mts.map +1 -1
- package/dist/services/sessionController.d.cts +2 -3
- package/dist/services/sessionController.d.cts.map +1 -1
- package/dist/services/sessionController.d.mts +2 -3
- package/dist/services/sessionController.d.mts.map +1 -1
- package/dist/services/traceRedaction.d.cts +3 -4
- package/dist/services/traceRedaction.d.cts.map +1 -1
- package/dist/services/traceRedaction.d.mts +3 -4
- package/dist/services/traceRedaction.d.mts.map +1 -1
- package/dist/types/computerScript.d.cts +38 -0
- package/dist/types/computerScript.d.cts.map +1 -0
- package/dist/types/computerScript.d.mts +38 -0
- package/dist/types/computerScript.d.mts.map +1 -0
- package/dist/types/computerUse.d.cts +14 -14
- package/dist/types/computerUse.d.cts.map +1 -1
- package/dist/types/computerUse.d.mts +14 -14
- package/dist/types/computerUse.d.mts.map +1 -1
- package/dist/types/computerUseApi.cjs.map +1 -1
- package/dist/types/computerUseApi.d.cts +22 -19
- package/dist/types/computerUseApi.d.cts.map +1 -1
- package/dist/types/computerUseApi.d.mts +22 -19
- package/dist/types/computerUseApi.d.mts.map +1 -1
- package/dist/types/computerUseApi.mjs.map +1 -1
- package/dist/types/extension.d.cts +7 -5
- package/dist/types/extension.d.cts.map +1 -1
- package/dist/types/extension.d.mts +7 -5
- package/dist/types/extension.d.mts.map +1 -1
- package/docs/computer-scripts.md +37 -0
- package/llms.txt +1 -0
- package/package.json +14 -12
- package/src/prompts/doompi-use-computer-use/SKILL.md +1 -1
- package/src/types/computerUse.ts +3 -2
- package/src/types/computerUseApi.ts +1 -0
- package/src/web/{ComputerActionToolCard.tsx → components/ComputerActionToolCard.tsx} +1 -1
- package/src/web/components/ComputerExecToolCard.tsx +44 -0
- package/src/web/{ComputerStateToolCard.tsx → components/ComputerStateToolCard.tsx} +1 -1
- package/src/web/{ComputerUsePanel.tsx → components/ComputerUsePanel.tsx} +14 -5
- package/src/web/index.ts +12 -6
- package/src/web/lib/computerExecToolRender.ts +14 -0
- package/src/web/lib/computerUseSettings.ts +17 -0
- package/src/web/{computerUseStore.ts → stores/computerUseStore.ts} +2 -2
- /package/src/web/{computerActionToolRender.ts → lib/computerActionToolRender.ts} +0 -0
- /package/src/web/{computerStateToolRender.ts → lib/computerStateToolRender.ts} +0 -0
package/README.md
CHANGED
|
@@ -20,12 +20,15 @@ Enable the default-off **Computer Use** minor mode in the cockpit. Its Activity
|
|
|
20
20
|
|
|
21
21
|
The `computer-use` minor mode exposes Activate, Deactivate, and Doctor actions. `/computer-use` reports the current state.
|
|
22
22
|
|
|
23
|
-
While active, the package exposes
|
|
23
|
+
While active, the package exposes three tools:
|
|
24
24
|
|
|
25
25
|
- `computer_state` observes the authorized window and returns a semantic snapshot.
|
|
26
|
-
- `computer_action` performs one semantic press, value change, or scroll against a current snapshot.
|
|
26
|
+
- `computer_action` performs one semantic press, focus change, value change, or scroll against a current snapshot.
|
|
27
|
+
- `computer_exec` runs a trusted, explicitly allowed local TypeScript script with JSON input, then returns a fresh observation.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
Computer scripts are trusted Node.js code and are not sandboxed. Configure exact allowed paths with `DOOMPI_COMPUTER_USE_SCRIPT_PATHS` and review every script before allowing it. See [Author computer scripts](./docs/computer-scripts.md) for the execution contract, limits, and an example.
|
|
30
|
+
|
|
31
|
+
All three tools and their model guidance remain unavailable during setup and are exposed only after the native grant becomes active. The Activity section shows activation status, busy ownership, stop controls, and completed recording or trace metadata. Browser clients cannot call observation or action routes.
|
|
29
32
|
|
|
30
33
|
## Public API
|
|
31
34
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../types/computerUseApi.cjs");let t=require("@agimon-ai/doompi-extension-contracts/package-api"),n=require("node:crypto");function r(e,t){return Response.json({error:e},{status:t})}function i(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:void 0}async function a(e){try{return i(await e.json())??{}}catch{return{}}}function o(e){if(!(typeof e.snapshotId==`string`&&e.snapshotId.length>0&&typeof e.elementRef==`string`&&e.elementRef.length>0))return!1;let t=t=>Object.keys(e).every(e=>t.includes(e));return e.kind===`press`?t([`kind`,`snapshotId`,`elementRef`]):e.kind===`set_value`?typeof e.value==`string`&&t([`kind`,`snapshotId`,`elementRef`,`value`]):e.kind===`scroll`&&[`up`,`down`,`left`,`right`].includes(e.direction)&&(e.amount===`line`||e.amount===`page`)&&t([`kind`,`snapshotId`,`elementRef`,`direction`,`amount`])}function s(e){let t=i(e);if(typeof t?.artifactId!=`string`||t.status!==`ready`&&t.status!==`failed`)return;let n=e=>typeof e==`string`&&e.startsWith(`/api/`)?e:void 0,r=n(t.downloadUrl),a=n(t.previewUrl);return{artifactId:t.artifactId,status:t.status,...r===void 0?{}:{downloadUrl:r},...a===void 0?{}:{previewUrl:a},...typeof t.actionCount==`number`?{actionCount:t.actionCount}:{},...typeof t.completedAt==`string`?{completedAt:t.completedAt}:{}}}var c=class{sessionId;internalToken;hubToken;revision=0;wake=0;phase=`inactive`;activation;grantId;expiresAt;actionSequence=0;failure;artifact;pending;closed=!1;constructor(e){this.sessionId=e.sessionId??`unknown`,this.internalToken=e.internalToken,this.hubToken=e.hubToken}state(){return Object.freeze({sessionId:this.sessionId,revision:this.revision,wake:this.wake,phase:this.phase,...this.activation===void 0?{}:{requestId:this.activation.requestId,target:this.activation.target,durationMs:this.activation.durationSeconds*1e3},...this.expiresAt===void 0?{}:{expiresAt:this.expiresAt},...this.failure===void 0?{}:{failure:this.failure},...this.artifact===void 0?{}:{artifact:this.artifact}})}changed(){this.revision+=1,this.wake=(this.wake+1)%e.COMPUTER_USE_WAKE_LIMIT}authorized(e,t){return t!==void 0&&e.headers.get(`authorization`)===`Bearer ${t}`}async requestActivation(o){let s=(0,t.doomApiCallerFrom)(o.headers);if(s===void 0||s.locality===`remote`&&s.stepUp===`not-required`)return r(`Not found.`,404);if(this.phase!==`inactive`&&this.phase!==`failed`)return r(`Computer use is already busy.`,409);let c=await a(o),l=i(c.target),u=c.durationMs;if(l===void 0||typeof l.windowId!=`string`||typeof l.bundleId!=`string`)return r(`A Desktop target is required.`,400);if(typeof u!=`number`||!Number.isInteger(u)||u<1e3||u>18e5)return r(`The requested duration is invalid.`,400);let d=Date.now();return this.activation=Object.freeze({requestId:(0,n.randomUUID)(),target:Object.freeze({...l}),durationSeconds:u/1e3,createdAt:d,confirmationExpiresAt:d+e.COMPUTER_USE_CONFIRMATION_WINDOW_MS,caller:Object.freeze({...s})}),this.phase=`awaiting_confirmation`,this.failure=void 0,this.artifact=void 0,this.changed(),Response.json(this.state(),{status:202})}async enqueue(e,t){if(this.phase!==`active`||this.grantId===void 0)return r(`Computer use is not active.`,409);if(this.pending!==void 0)return r(`Another computer-use request is live.`,409);let
|
|
1
|
+
const e=require("../types/computerUseApi.cjs");let t=require("@agimon-ai/doompi-extension-contracts/package-api"),n=require("node:crypto");function r(e,t){return Response.json({error:e},{status:t})}function i(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:void 0}async function a(e){try{return i(await e.json())??{}}catch{return{}}}function o(e){if(!(typeof e.snapshotId==`string`&&e.snapshotId.length>0&&typeof e.elementRef==`string`&&e.elementRef.length>0))return!1;let t=t=>Object.keys(e).every(e=>t.includes(e));return e.kind===`press`||e.kind===`focus`?t([`kind`,`snapshotId`,`elementRef`]):e.kind===`set_value`?typeof e.value==`string`&&t([`kind`,`snapshotId`,`elementRef`,`value`]):e.kind===`scroll`&&[`up`,`down`,`left`,`right`].includes(e.direction)&&(e.amount===`line`||e.amount===`page`)&&t([`kind`,`snapshotId`,`elementRef`,`direction`,`amount`])}function s(e){let t=i(e);if(typeof t?.artifactId!=`string`||t.status!==`ready`&&t.status!==`failed`)return;let n=e=>typeof e==`string`&&e.startsWith(`/api/`)?e:void 0,r=n(t.downloadUrl),a=n(t.previewUrl),o=i(t.failure),s=typeof o?.code==`string`&&typeof o.message==`string`?{code:o.code.slice(0,128),message:o.message.slice(0,512)}:void 0;return{artifactId:t.artifactId,status:t.status,...r===void 0?{}:{downloadUrl:r},...a===void 0?{}:{previewUrl:a},...typeof t.actionCount==`number`?{actionCount:t.actionCount}:{},...typeof t.completedAt==`string`?{completedAt:t.completedAt}:{},...s===void 0?{}:{failure:s}}}var c=class c{sessionId;internalToken;hubToken;requestTimeoutMs;revision=0;wake=0;phase=`inactive`;activation;grantId;expiresAt;actionSequence=0;failure;artifact;pending;closed=!1;static DEFAULT_REQUEST_TIMEOUT_MS=3e4;constructor(e){this.sessionId=e.sessionId??`unknown`,this.internalToken=e.internalToken,this.hubToken=e.hubToken,this.requestTimeoutMs=e.requestTimeoutMs??c.DEFAULT_REQUEST_TIMEOUT_MS}state(){return Object.freeze({sessionId:this.sessionId,revision:this.revision,wake:this.wake,phase:this.phase,...this.activation===void 0?{}:{requestId:this.activation.requestId,target:this.activation.target,durationMs:this.activation.durationSeconds*1e3},...this.expiresAt===void 0?{}:{expiresAt:this.expiresAt},...this.failure===void 0?{}:{failure:this.failure},...this.artifact===void 0?{}:{artifact:this.artifact}})}changed(){this.revision+=1,this.wake=(this.wake+1)%e.COMPUTER_USE_WAKE_LIMIT}authorized(e,t){return t!==void 0&&e.headers.get(`authorization`)===`Bearer ${t}`}async requestActivation(o){let s=(0,t.doomApiCallerFrom)(o.headers);if(s===void 0||s.locality===`remote`&&s.stepUp===`not-required`)return r(`Not found.`,404);if(this.phase!==`inactive`&&this.phase!==`failed`)return r(`Computer use is already busy.`,409);let c=await a(o),l=i(c.target),u=c.durationMs;if(l===void 0||typeof l.windowId!=`string`||typeof l.bundleId!=`string`)return r(`A Desktop target is required.`,400);if(typeof u!=`number`||!Number.isInteger(u)||u<1e3||u>18e5)return r(`The requested duration is invalid.`,400);let d=Date.now();return this.activation=Object.freeze({requestId:(0,n.randomUUID)(),target:Object.freeze({...l}),durationSeconds:u/1e3,createdAt:d,confirmationExpiresAt:d+e.COMPUTER_USE_CONFIRMATION_WINDOW_MS,caller:Object.freeze({...s})}),this.phase=`awaiting_confirmation`,this.failure=void 0,this.artifact=void 0,this.changed(),Response.json(this.state(),{status:202})}rejectPending(e){let t=this.pending;t!==void 0&&(this.pending=void 0,t.dispose(),t.reject(Error(e)))}stopAfterUncertainRequest(e){this.phase===`active`&&(this.phase=`stopping`,this.changed()),this.rejectPending(e)}async enqueue(e,t,i){if(this.phase!==`active`||this.grantId===void 0)return r(`Computer use is not active.`,409);if(this.pending!==void 0)return r(`Another computer-use request is live.`,409);if(i?.aborted===!0)return r(`The computer-use request was cancelled.`,499);let a=e===`act`?++this.actionSequence:void 0,o={id:(0,n.randomUUID)(),operation:e,grantId:this.grantId,...a===void 0?{}:{sequence:a},...t===void 0?{}:{payload:t}},s=new Promise((e,t)=>{let n=()=>this.stopAfterUncertainRequest(`The computer-use request was cancelled.`),r=setTimeout(()=>this.stopAfterUncertainRequest(`The computer-use request timed out.`),this.requestTimeoutMs);r.unref?.(),i?.addEventListener(`abort`,n,{once:!0}),this.pending={request:o,resolve:e,reject:t,dispose:()=>{clearTimeout(r),i?.removeEventListener(`abort`,n)}}});this.changed();try{return Response.json(await s)}catch(e){return r(e instanceof Error?e.message:String(e),502)}}async fetch(t){if(this.closed)return r(`Computer-use broker is closed.`,503);let n=new URL(t.url).pathname;if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.activate)return this.requestActivation(t);let c=n.startsWith(`/agent/`),l=n.startsWith(`/hub/`);if(c&&!this.authorized(t,this.internalToken)||l&&!this.authorized(t,this.hubToken))return r(`Not found.`,404);if(t.method===`GET`&&(n===e.COMPUTER_USE_ROUTES.agentState||n===e.COMPUTER_USE_ROUTES.hubState))return Response.json(this.state());if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.agentObserve)return this.enqueue(`observe`,void 0,t.signal);if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.agentAction){let e=await a(t);return o(e)?this.enqueue(`act`,e,t.signal):r(`A valid semantic action is required.`,400)}if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.agentStop)return this.phase===`active`?(this.phase=`stopping`,this.rejectPending(`The computer-use request was stopped.`),this.changed(),Response.json(this.state(),{status:202})):r(`Computer use is not active.`,409);if(t.method===`GET`&&n===e.COMPUTER_USE_ROUTES.hubActivation)return this.activation===void 0||this.phase!==`awaiting_confirmation`?Response.json(null):(this.phase=`activating`,this.changed(),Response.json(this.activation));if(t.method===`GET`&&n===e.COMPUTER_USE_ROUTES.hubAuthorization)return Response.json(this.grantId===void 0?null:{grantId:this.grantId,expiresAt:this.expiresAt});if(t.method===`GET`&&n===e.COMPUTER_USE_ROUTES.hubNext)return Response.json(this.pending?.request??null);if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.hubComplete){let e=await a(t),n=typeof e.id==`string`?e.id:void 0;if(this.pending===void 0||this.pending.request.id!==n)return r(`The request is stale.`,409);let i=this.pending;return this.pending=void 0,i.dispose(),this.changed(),typeof e.error==`string`?i.reject(Error(e.error)):i.resolve(e.result),Response.json(this.state())}if(t.method===`POST`&&n===e.COMPUTER_USE_ROUTES.hubStop){let e=await a(t);if(this.phase===`activating`){if(typeof e.error==`string`)this.phase=`failed`,this.failure={code:`desktop_unavailable`,message:e.error.slice(0,512)},this.activation=void 0;else{let t=i(e.host);if(typeof t?.grantId!=`string`||!Number.isFinite(t.expiresAt))return r(`Desktop did not issue a complete grant.`,502);this.grantId=t.grantId,this.expiresAt=t.expiresAt,this.actionSequence=0,this.phase=`active`}}else this.rejectPending(`The computer-use session stopped.`),this.phase=`inactive`,this.grantId=void 0,this.expiresAt=void 0,this.activation=void 0,this.artifact=s(e.artifact);return this.changed(),Response.json(this.state())}return r(`Not found.`,404)}close(){this.closed=!0,this.rejectPending(`Computer-use broker closed.`),this.grantId=void 0,this.expiresAt=void 0}};function l(e={}){return new c(e)}const u={basePath:e.API_BASE_PATH,start(e){return l({...e.sessionId===void 0?{}:{sessionId:e.sessionId},...e.internalToken===void 0?{}:{internalToken:e.internalToken},...e.hubToken===void 0?{}:{hubToken:e.hubToken}})}};exports.ComputerUseRequestBroker=c,exports.api=u,exports.createComputerUseApi=l;
|
|
2
2
|
//# sourceMappingURL=computerUseApi.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computerUseApi.cjs","names":["COMPUTER_USE_WAKE_LIMIT","doomApiCallerFrom","randomUUID","COMPUTER_USE_CONFIRMATION_WINDOW_MS","COMPUTER_USE_ROUTES","API_BASE_PATH"],"sources":["../../src/adapters/computerUseApi.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n doomApiCallerFrom,\n type DoomApi,\n type DoomApiContext,\n type DoomApiHandler,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport {\n API_BASE_PATH,\n COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n COMPUTER_USE_MAX_DURATION_MS,\n COMPUTER_USE_ROUTES,\n COMPUTER_USE_WAKE_LIMIT,\n type ComputerUseActivationRequest,\n type ComputerUseArtifactView,\n type ComputerUseBrokerRequest,\n type ComputerUseSessionView,\n} from '../types/computerUseApi.ts';\n\ninterface PendingRequest {\n readonly request: ComputerUseBrokerRequest;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\nfunction jsonError(message: string, status: number): Response {\n return Response.json({ error: message }, { status });\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nasync function body(request: Request): Promise<Record<string, unknown>> {\n try {\n return record(await request.json()) ?? {};\n } catch {\n return {};\n }\n}\n\nfunction semanticAction(value: Record<string, unknown>): boolean {\n const base =\n typeof value.snapshotId === 'string' &&\n value.snapshotId.length > 0 &&\n typeof value.elementRef === 'string' &&\n value.elementRef.length > 0;\n if (!base) return false;\n const allowed = (keys: readonly string[]) => Object.keys(value).every((key) => keys.includes(key));\n if (value.kind === 'press') return allowed(['kind', 'snapshotId', 'elementRef']);\n if (value.kind === 'set_value')\n return typeof value.value === 'string' && allowed(['kind', 'snapshotId', 'elementRef', 'value']);\n if (value.kind === 'scroll')\n return (\n ['up', 'down', 'left', 'right'].includes(value.direction as string) &&\n (value.amount === 'line' || value.amount === 'page') &&\n allowed(['kind', 'snapshotId', 'elementRef', 'direction', 'amount'])\n );\n return false;\n}\nfunction artifactView(value: unknown): ComputerUseArtifactView | undefined {\n const input = record(value);\n if (typeof input?.artifactId !== 'string' || (input.status !== 'ready' && input.status !== 'failed'))\n return undefined;\n const safeUrl = (candidate: unknown) =>\n typeof candidate === 'string' && candidate.startsWith('/api/') ? candidate : undefined;\n const downloadUrl = safeUrl(input.downloadUrl);\n const previewUrl = safeUrl(input.previewUrl);\n return {\n artifactId: input.artifactId,\n status: input.status,\n ...(downloadUrl === undefined ? {} : { downloadUrl }),\n ...(previewUrl === undefined ? {} : { previewUrl }),\n ...(typeof input.actionCount === 'number' ? { actionCount: input.actionCount } : {}),\n ...(typeof input.completedAt === 'string' ? { completedAt: input.completedAt } : {}),\n };\n}\n\nexport interface ComputerUseApiOptions {\n readonly sessionId?: string;\n readonly internalToken?: string;\n readonly hubToken?: string;\n}\n\nexport class ComputerUseRequestBroker implements DoomApiHandler {\n private readonly sessionId: string;\n private readonly internalToken?: string;\n private readonly hubToken?: string;\n private revision = 0;\n private wake = 0;\n private phase: ComputerUseSessionView['phase'] = 'inactive';\n private activation?: ComputerUseActivationRequest;\n private grantId?: string;\n private expiresAt?: number;\n private actionSequence = 0;\n private failure?: ComputerUseSessionView['failure'];\n private artifact?: ComputerUseArtifactView;\n private pending?: PendingRequest;\n private closed = false;\n\n public constructor(options: ComputerUseApiOptions) {\n this.sessionId = options.sessionId ?? 'unknown';\n this.internalToken = options.internalToken;\n this.hubToken = options.hubToken;\n }\n\n public state(): ComputerUseSessionView {\n return Object.freeze({\n sessionId: this.sessionId,\n revision: this.revision,\n wake: this.wake,\n phase: this.phase,\n ...(this.activation === undefined\n ? {}\n : {\n requestId: this.activation.requestId,\n target: this.activation.target,\n durationMs: this.activation.durationSeconds * 1_000,\n }),\n ...(this.expiresAt === undefined ? {} : { expiresAt: this.expiresAt }),\n ...(this.failure === undefined ? {} : { failure: this.failure }),\n ...(this.artifact === undefined ? {} : { artifact: this.artifact }),\n });\n }\n\n private changed(): void {\n this.revision += 1;\n this.wake = (this.wake + 1) % COMPUTER_USE_WAKE_LIMIT;\n }\n\n private authorized(request: Request, token: string | undefined): boolean {\n return token !== undefined && request.headers.get('authorization') === `Bearer ${token}`;\n }\n\n private async requestActivation(request: Request): Promise<Response> {\n const caller = doomApiCallerFrom(request.headers);\n if (caller === undefined || (caller.locality === 'remote' && caller.stepUp === 'not-required'))\n return jsonError('Not found.', 404);\n if (this.phase !== 'inactive' && this.phase !== 'failed') return jsonError('Computer use is already busy.', 409);\n const input = await body(request);\n const target = record(input.target);\n const durationMs = input.durationMs;\n if (target === undefined || typeof target.windowId !== 'string' || typeof target.bundleId !== 'string')\n return jsonError('A Desktop target is required.', 400);\n if (\n typeof durationMs !== 'number' ||\n !Number.isInteger(durationMs) ||\n durationMs < 1_000 ||\n durationMs > COMPUTER_USE_MAX_DURATION_MS\n )\n return jsonError('The requested duration is invalid.', 400);\n const createdAt = Date.now();\n this.activation = Object.freeze({\n requestId: randomUUID(),\n target: Object.freeze({ ...target }),\n durationSeconds: durationMs / 1_000,\n createdAt,\n confirmationExpiresAt: createdAt + COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n caller: Object.freeze({ ...caller }),\n });\n this.phase = 'awaiting_confirmation';\n this.failure = undefined;\n this.artifact = undefined;\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n\n private async enqueue(operation: ComputerUseBrokerRequest['operation'], payload?: unknown): Promise<Response> {\n if (this.phase !== 'active' || this.grantId === undefined) return jsonError('Computer use is not active.', 409);\n if (this.pending !== undefined) return jsonError('Another computer-use request is live.', 409);\n const sequence = operation === 'act' ? ++this.actionSequence : undefined;\n const request: ComputerUseBrokerRequest = {\n id: randomUUID(),\n operation,\n grantId: this.grantId,\n ...(sequence === undefined ? {} : { sequence }),\n ...(payload === undefined ? {} : { payload }),\n };\n const completion = new Promise<unknown>((resolve, reject) => {\n this.pending = { request, resolve, reject };\n });\n this.changed();\n try {\n return Response.json(await completion);\n } catch (error) {\n return jsonError(error instanceof Error ? error.message : String(error), 502);\n }\n }\n\n public async fetch(request: Request): Promise<Response> {\n if (this.closed) return jsonError('Computer-use broker is closed.', 503);\n const path = new URL(request.url).pathname;\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.activate) return this.requestActivation(request);\n const agent = path.startsWith('/agent/');\n const hub = path.startsWith('/hub/');\n if ((agent && !this.authorized(request, this.internalToken)) || (hub && !this.authorized(request, this.hubToken)))\n return jsonError('Not found.', 404);\n\n if (request.method === 'GET' && (path === COMPUTER_USE_ROUTES.agentState || path === COMPUTER_USE_ROUTES.hubState))\n return Response.json(this.state());\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentObserve) return this.enqueue('observe');\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentAction) {\n const action = await body(request);\n if (!semanticAction(action)) return jsonError('A valid semantic action is required.', 400);\n return this.enqueue('act', action);\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentStop) {\n if (this.phase !== 'active') return jsonError('Computer use is not active.', 409);\n this.phase = 'stopping';\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubActivation) {\n if (this.activation === undefined || this.phase !== 'awaiting_confirmation') return Response.json(null);\n this.phase = 'activating';\n this.changed();\n return Response.json(this.activation);\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubAuthorization)\n return Response.json(this.grantId === undefined ? null : { grantId: this.grantId, expiresAt: this.expiresAt });\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubNext)\n return Response.json(this.pending?.request ?? null);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubComplete) {\n const input = await body(request);\n const id = typeof input.id === 'string' ? input.id : undefined;\n if (this.pending === undefined || this.pending.request.id !== id) return jsonError('The request is stale.', 409);\n const pending = this.pending;\n this.pending = undefined;\n this.changed();\n if (typeof input.error === 'string') pending.reject(new Error(input.error));\n else pending.resolve(input.result);\n return Response.json(this.state());\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubStop) {\n const input = await body(request);\n if (this.phase === 'activating') {\n if (typeof input.error === 'string') {\n this.phase = 'failed';\n this.failure = { code: 'desktop_unavailable', message: input.error.slice(0, 512) };\n this.activation = undefined;\n } else {\n const host = record(input.host);\n if (typeof host?.grantId !== 'string' || !Number.isFinite(host.expiresAt))\n return jsonError('Desktop did not issue a complete grant.', 502);\n this.grantId = host.grantId;\n this.expiresAt = host.expiresAt as number;\n this.actionSequence = 0;\n this.phase = 'active';\n }\n } else {\n this.phase = 'inactive';\n this.grantId = undefined;\n this.expiresAt = undefined;\n this.activation = undefined;\n this.artifact = artifactView(input.artifact);\n }\n this.changed();\n return Response.json(this.state());\n }\n return jsonError('Not found.', 404);\n }\n\n public close(): void {\n this.closed = true;\n this.pending?.reject(new Error('Computer-use broker closed.'));\n this.pending = undefined;\n this.grantId = undefined;\n this.expiresAt = undefined;\n }\n}\n\nexport function createComputerUseApi(options: ComputerUseApiOptions = {}): ComputerUseRequestBroker {\n return new ComputerUseRequestBroker(options);\n}\n\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n return createComputerUseApi({\n ...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),\n ...(context.internalToken === undefined ? {} : { internalToken: context.internalToken }),\n ...(context.hubToken === undefined ? {} : { hubToken: context.hubToken }),\n });\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"computerUseApi.cjs","names":["COMPUTER_USE_WAKE_LIMIT","doomApiCallerFrom","randomUUID","COMPUTER_USE_CONFIRMATION_WINDOW_MS","COMPUTER_USE_ROUTES","API_BASE_PATH"],"sources":["../../src/adapters/computerUseApi.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n doomApiCallerFrom,\n type DoomApi,\n type DoomApiContext,\n type DoomApiHandler,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport {\n API_BASE_PATH,\n COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n COMPUTER_USE_MAX_DURATION_MS,\n COMPUTER_USE_ROUTES,\n COMPUTER_USE_WAKE_LIMIT,\n type ComputerUseActivationRequest,\n type ComputerUseArtifactView,\n type ComputerUseBrokerRequest,\n type ComputerUseSessionView,\n} from '../types/computerUseApi.ts';\n\ninterface PendingRequest {\n readonly request: ComputerUseBrokerRequest;\n readonly dispose: () => void;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\nfunction jsonError(message: string, status: number): Response {\n return Response.json({ error: message }, { status });\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nasync function body(request: Request): Promise<Record<string, unknown>> {\n try {\n return record(await request.json()) ?? {};\n } catch {\n return {};\n }\n}\n\nfunction semanticAction(value: Record<string, unknown>): boolean {\n const base =\n typeof value.snapshotId === 'string' &&\n value.snapshotId.length > 0 &&\n typeof value.elementRef === 'string' &&\n value.elementRef.length > 0;\n if (!base) return false;\n const allowed = (keys: readonly string[]) => Object.keys(value).every((key) => keys.includes(key));\n if (value.kind === 'press' || value.kind === 'focus') return allowed(['kind', 'snapshotId', 'elementRef']);\n if (value.kind === 'set_value')\n return typeof value.value === 'string' && allowed(['kind', 'snapshotId', 'elementRef', 'value']);\n if (value.kind === 'scroll')\n return (\n ['up', 'down', 'left', 'right'].includes(value.direction as string) &&\n (value.amount === 'line' || value.amount === 'page') &&\n allowed(['kind', 'snapshotId', 'elementRef', 'direction', 'amount'])\n );\n return false;\n}\nfunction artifactView(value: unknown): ComputerUseArtifactView | undefined {\n const input = record(value);\n if (typeof input?.artifactId !== 'string' || (input.status !== 'ready' && input.status !== 'failed'))\n return undefined;\n const safeUrl = (candidate: unknown) =>\n typeof candidate === 'string' && candidate.startsWith('/api/') ? candidate : undefined;\n const downloadUrl = safeUrl(input.downloadUrl);\n const previewUrl = safeUrl(input.previewUrl);\n const artifactFailure = record(input.failure);\n const safeFailure =\n typeof artifactFailure?.code === 'string' && typeof artifactFailure.message === 'string'\n ? { code: artifactFailure.code.slice(0, 128), message: artifactFailure.message.slice(0, 512) }\n : undefined;\n return {\n artifactId: input.artifactId,\n status: input.status,\n ...(downloadUrl === undefined ? {} : { downloadUrl }),\n ...(previewUrl === undefined ? {} : { previewUrl }),\n ...(typeof input.actionCount === 'number' ? { actionCount: input.actionCount } : {}),\n ...(typeof input.completedAt === 'string' ? { completedAt: input.completedAt } : {}),\n ...(safeFailure === undefined ? {} : { failure: safeFailure }),\n };\n}\n\nexport interface ComputerUseApiOptions {\n readonly sessionId?: string;\n readonly internalToken?: string;\n readonly hubToken?: string;\n readonly requestTimeoutMs?: number;\n}\n\nexport class ComputerUseRequestBroker implements DoomApiHandler {\n private readonly sessionId: string;\n private readonly internalToken?: string;\n private readonly hubToken?: string;\n private readonly requestTimeoutMs: number;\n private revision = 0;\n private wake = 0;\n private phase: ComputerUseSessionView['phase'] = 'inactive';\n private activation?: ComputerUseActivationRequest;\n private grantId?: string;\n private expiresAt?: number;\n private actionSequence = 0;\n private failure?: ComputerUseSessionView['failure'];\n private artifact?: ComputerUseArtifactView;\n private pending?: PendingRequest;\n private closed = false;\n\n private static readonly DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n public constructor(options: ComputerUseApiOptions) {\n this.sessionId = options.sessionId ?? 'unknown';\n this.internalToken = options.internalToken;\n this.hubToken = options.hubToken;\n this.requestTimeoutMs = options.requestTimeoutMs ?? ComputerUseRequestBroker.DEFAULT_REQUEST_TIMEOUT_MS;\n }\n\n public state(): ComputerUseSessionView {\n return Object.freeze({\n sessionId: this.sessionId,\n revision: this.revision,\n wake: this.wake,\n phase: this.phase,\n ...(this.activation === undefined\n ? {}\n : {\n requestId: this.activation.requestId,\n target: this.activation.target,\n durationMs: this.activation.durationSeconds * 1_000,\n }),\n ...(this.expiresAt === undefined ? {} : { expiresAt: this.expiresAt }),\n ...(this.failure === undefined ? {} : { failure: this.failure }),\n ...(this.artifact === undefined ? {} : { artifact: this.artifact }),\n });\n }\n\n private changed(): void {\n this.revision += 1;\n this.wake = (this.wake + 1) % COMPUTER_USE_WAKE_LIMIT;\n }\n\n private authorized(request: Request, token: string | undefined): boolean {\n return token !== undefined && request.headers.get('authorization') === `Bearer ${token}`;\n }\n\n private async requestActivation(request: Request): Promise<Response> {\n const caller = doomApiCallerFrom(request.headers);\n if (caller === undefined || (caller.locality === 'remote' && caller.stepUp === 'not-required'))\n return jsonError('Not found.', 404);\n if (this.phase !== 'inactive' && this.phase !== 'failed') return jsonError('Computer use is already busy.', 409);\n const input = await body(request);\n const target = record(input.target);\n const durationMs = input.durationMs;\n if (target === undefined || typeof target.windowId !== 'string' || typeof target.bundleId !== 'string')\n return jsonError('A Desktop target is required.', 400);\n if (\n typeof durationMs !== 'number' ||\n !Number.isInteger(durationMs) ||\n durationMs < 1_000 ||\n durationMs > COMPUTER_USE_MAX_DURATION_MS\n )\n return jsonError('The requested duration is invalid.', 400);\n const createdAt = Date.now();\n this.activation = Object.freeze({\n requestId: randomUUID(),\n target: Object.freeze({ ...target }),\n durationSeconds: durationMs / 1_000,\n createdAt,\n confirmationExpiresAt: createdAt + COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n caller: Object.freeze({ ...caller }),\n });\n this.phase = 'awaiting_confirmation';\n this.failure = undefined;\n this.artifact = undefined;\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n\n private rejectPending(message: string): void {\n const pending = this.pending;\n if (pending === undefined) return;\n this.pending = undefined;\n pending.dispose();\n pending.reject(new Error(message));\n }\n\n private stopAfterUncertainRequest(message: string): void {\n if (this.phase === 'active') {\n this.phase = 'stopping';\n this.changed();\n }\n this.rejectPending(message);\n }\n\n private async enqueue(\n operation: ComputerUseBrokerRequest['operation'],\n payload?: unknown,\n signal?: AbortSignal,\n ): Promise<Response> {\n if (this.phase !== 'active' || this.grantId === undefined) return jsonError('Computer use is not active.', 409);\n if (this.pending !== undefined) return jsonError('Another computer-use request is live.', 409);\n if (signal?.aborted === true) return jsonError('The computer-use request was cancelled.', 499);\n const sequence = operation === 'act' ? ++this.actionSequence : undefined;\n const request: ComputerUseBrokerRequest = {\n id: randomUUID(),\n operation,\n grantId: this.grantId,\n ...(sequence === undefined ? {} : { sequence }),\n ...(payload === undefined ? {} : { payload }),\n };\n const completion = new Promise<unknown>((resolve, reject) => {\n const onAbort = () => this.stopAfterUncertainRequest('The computer-use request was cancelled.');\n const timer = setTimeout(\n () => this.stopAfterUncertainRequest('The computer-use request timed out.'),\n this.requestTimeoutMs,\n );\n timer.unref?.();\n signal?.addEventListener('abort', onAbort, { once: true });\n this.pending = {\n request,\n resolve,\n reject,\n dispose: () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n },\n };\n });\n this.changed();\n try {\n return Response.json(await completion);\n } catch (error) {\n return jsonError(error instanceof Error ? error.message : String(error), 502);\n }\n }\n\n public async fetch(request: Request): Promise<Response> {\n if (this.closed) return jsonError('Computer-use broker is closed.', 503);\n const path = new URL(request.url).pathname;\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.activate) return this.requestActivation(request);\n const agent = path.startsWith('/agent/');\n const hub = path.startsWith('/hub/');\n if ((agent && !this.authorized(request, this.internalToken)) || (hub && !this.authorized(request, this.hubToken)))\n return jsonError('Not found.', 404);\n\n if (request.method === 'GET' && (path === COMPUTER_USE_ROUTES.agentState || path === COMPUTER_USE_ROUTES.hubState))\n return Response.json(this.state());\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentObserve)\n return this.enqueue('observe', undefined, request.signal);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentAction) {\n const action = await body(request);\n if (!semanticAction(action)) return jsonError('A valid semantic action is required.', 400);\n return this.enqueue('act', action, request.signal);\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentStop) {\n if (this.phase !== 'active') return jsonError('Computer use is not active.', 409);\n this.phase = 'stopping';\n this.rejectPending('The computer-use request was stopped.');\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubActivation) {\n if (this.activation === undefined || this.phase !== 'awaiting_confirmation') return Response.json(null);\n this.phase = 'activating';\n this.changed();\n return Response.json(this.activation);\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubAuthorization)\n return Response.json(this.grantId === undefined ? null : { grantId: this.grantId, expiresAt: this.expiresAt });\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubNext)\n return Response.json(this.pending?.request ?? null);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubComplete) {\n const input = await body(request);\n const id = typeof input.id === 'string' ? input.id : undefined;\n if (this.pending === undefined || this.pending.request.id !== id) return jsonError('The request is stale.', 409);\n const pending = this.pending;\n this.pending = undefined;\n pending.dispose();\n this.changed();\n if (typeof input.error === 'string') pending.reject(new Error(input.error));\n else pending.resolve(input.result);\n return Response.json(this.state());\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubStop) {\n const input = await body(request);\n if (this.phase === 'activating') {\n if (typeof input.error === 'string') {\n this.phase = 'failed';\n this.failure = { code: 'desktop_unavailable', message: input.error.slice(0, 512) };\n this.activation = undefined;\n } else {\n const host = record(input.host);\n if (typeof host?.grantId !== 'string' || !Number.isFinite(host.expiresAt))\n return jsonError('Desktop did not issue a complete grant.', 502);\n this.grantId = host.grantId;\n this.expiresAt = host.expiresAt as number;\n this.actionSequence = 0;\n this.phase = 'active';\n }\n } else {\n this.rejectPending('The computer-use session stopped.');\n this.phase = 'inactive';\n this.grantId = undefined;\n this.expiresAt = undefined;\n this.activation = undefined;\n this.artifact = artifactView(input.artifact);\n }\n this.changed();\n return Response.json(this.state());\n }\n return jsonError('Not found.', 404);\n }\n\n public close(): void {\n this.closed = true;\n this.rejectPending('Computer-use broker closed.');\n this.grantId = undefined;\n this.expiresAt = undefined;\n }\n}\n\nexport function createComputerUseApi(options: ComputerUseApiOptions = {}): ComputerUseRequestBroker {\n return new ComputerUseRequestBroker(options);\n}\n\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n return createComputerUseApi({\n ...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),\n ...(context.internalToken === undefined ? {} : { internalToken: context.internalToken }),\n ...(context.hubToken === undefined ? {} : { hubToken: context.hubToken }),\n });\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ComputerUseSessionView } from "../types/computerUseApi.cjs";
|
|
2
2
|
import { DoomApi, DoomApiHandler } from "@agimon-ai/doompi-extension-contracts/package-api";
|
|
3
3
|
//#region src/adapters/computerUseApi.d.ts
|
|
4
|
-
interface ComputerUseApiOptions {
|
|
4
|
+
export interface ComputerUseApiOptions {
|
|
5
5
|
readonly sessionId?: string;
|
|
6
6
|
readonly internalToken?: string;
|
|
7
7
|
readonly hubToken?: string;
|
|
8
|
+
readonly requestTimeoutMs?: number;
|
|
8
9
|
}
|
|
9
|
-
declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
10
|
+
export declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
10
11
|
private readonly sessionId;
|
|
11
12
|
private readonly internalToken?;
|
|
12
13
|
private readonly hubToken?;
|
|
14
|
+
private readonly requestTimeoutMs;
|
|
13
15
|
private revision;
|
|
14
16
|
private wake;
|
|
15
17
|
private phase;
|
|
@@ -21,17 +23,19 @@ declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
|
21
23
|
private artifact?;
|
|
22
24
|
private pending?;
|
|
23
25
|
private closed;
|
|
26
|
+
private static readonly DEFAULT_REQUEST_TIMEOUT_MS;
|
|
24
27
|
constructor(options: ComputerUseApiOptions);
|
|
25
28
|
state(): ComputerUseSessionView;
|
|
26
29
|
private changed;
|
|
27
30
|
private authorized;
|
|
28
31
|
private requestActivation;
|
|
32
|
+
private rejectPending;
|
|
33
|
+
private stopAfterUncertainRequest;
|
|
29
34
|
private enqueue;
|
|
30
35
|
fetch(request: Request): Promise<Response>;
|
|
31
36
|
close(): void;
|
|
32
37
|
}
|
|
33
|
-
declare function createComputerUseApi(options?: ComputerUseApiOptions): ComputerUseRequestBroker;
|
|
34
|
-
declare const api: DoomApi;
|
|
38
|
+
export declare function createComputerUseApi(options?: ComputerUseApiOptions): ComputerUseRequestBroker;
|
|
39
|
+
export declare const api: DoomApi;
|
|
35
40
|
//#endregion
|
|
36
|
-
export { ComputerUseApiOptions, ComputerUseRequestBroker, api, createComputerUseApi };
|
|
37
41
|
//# sourceMappingURL=computerUseApi.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computerUseApi.d.cts","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"computerUseApi.d.cts","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"mappings":";;;iBAuFiB;WACN;WACA;WACA;WACA;;qBAGE,oCAAoC;mBAC9B;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;0BAEgB;EACxB,YAAmB,SAAS;EAOrB,SAAS;UAmBR;UAKA;UAIM;UAiCN;UAQA;UAQM;EA0CD,MAAM,SAAS,UAAU,QAAQ;EA6EvC;;wBAQO,qBAAqB,UAAS,wBAA6B;qBAI9D,KAAK"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ComputerUseSessionView } from "../types/computerUseApi.mjs";
|
|
2
2
|
import { DoomApi, DoomApiHandler } from "@agimon-ai/doompi-extension-contracts/package-api";
|
|
3
3
|
//#region src/adapters/computerUseApi.d.ts
|
|
4
|
-
interface ComputerUseApiOptions {
|
|
4
|
+
export interface ComputerUseApiOptions {
|
|
5
5
|
readonly sessionId?: string;
|
|
6
6
|
readonly internalToken?: string;
|
|
7
7
|
readonly hubToken?: string;
|
|
8
|
+
readonly requestTimeoutMs?: number;
|
|
8
9
|
}
|
|
9
|
-
declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
10
|
+
export declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
10
11
|
private readonly sessionId;
|
|
11
12
|
private readonly internalToken?;
|
|
12
13
|
private readonly hubToken?;
|
|
14
|
+
private readonly requestTimeoutMs;
|
|
13
15
|
private revision;
|
|
14
16
|
private wake;
|
|
15
17
|
private phase;
|
|
@@ -21,17 +23,19 @@ declare class ComputerUseRequestBroker implements DoomApiHandler {
|
|
|
21
23
|
private artifact?;
|
|
22
24
|
private pending?;
|
|
23
25
|
private closed;
|
|
26
|
+
private static readonly DEFAULT_REQUEST_TIMEOUT_MS;
|
|
24
27
|
constructor(options: ComputerUseApiOptions);
|
|
25
28
|
state(): ComputerUseSessionView;
|
|
26
29
|
private changed;
|
|
27
30
|
private authorized;
|
|
28
31
|
private requestActivation;
|
|
32
|
+
private rejectPending;
|
|
33
|
+
private stopAfterUncertainRequest;
|
|
29
34
|
private enqueue;
|
|
30
35
|
fetch(request: Request): Promise<Response>;
|
|
31
36
|
close(): void;
|
|
32
37
|
}
|
|
33
|
-
declare function createComputerUseApi(options?: ComputerUseApiOptions): ComputerUseRequestBroker;
|
|
34
|
-
declare const api: DoomApi;
|
|
38
|
+
export declare function createComputerUseApi(options?: ComputerUseApiOptions): ComputerUseRequestBroker;
|
|
39
|
+
export declare const api: DoomApi;
|
|
35
40
|
//#endregion
|
|
36
|
-
export { ComputerUseApiOptions, ComputerUseRequestBroker, api, createComputerUseApi };
|
|
37
41
|
//# sourceMappingURL=computerUseApi.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computerUseApi.d.mts","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"computerUseApi.d.mts","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"mappings":";;;iBAuFiB;WACN;WACA;WACA;WACA;;qBAGE,oCAAoC;mBAC9B;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;0BAEgB;EACxB,YAAmB,SAAS;EAOrB,SAAS;UAmBR;UAKA;UAIM;UAiCN;UAQA;UAQM;EA0CD,MAAM,SAAS,UAAU,QAAQ;EA6EvC;;wBAQO,qBAAqB,UAAS,wBAA6B;qBAI9D,KAAK"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{API_BASE_PATH as e,COMPUTER_USE_CONFIRMATION_WINDOW_MS as t,COMPUTER_USE_ROUTES as n,COMPUTER_USE_WAKE_LIMIT as r}from"../types/computerUseApi.mjs";import{doomApiCallerFrom as i}from"@agimon-ai/doompi-extension-contracts/package-api";import{randomUUID as a}from"node:crypto";function o(e,t){return Response.json({error:e},{status:t})}function s(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:void 0}async function c(e){try{return s(await e.json())??{}}catch{return{}}}function l(e){if(!(typeof e.snapshotId==`string`&&e.snapshotId.length>0&&typeof e.elementRef==`string`&&e.elementRef.length>0))return!1;let t=t=>Object.keys(e).every(e=>t.includes(e));return e.kind===`press`?t([`kind`,`snapshotId`,`elementRef`]):e.kind===`set_value`?typeof e.value==`string`&&t([`kind`,`snapshotId`,`elementRef`,`value`]):e.kind===`scroll`&&[`up`,`down`,`left`,`right`].includes(e.direction)&&(e.amount===`line`||e.amount===`page`)&&t([`kind`,`snapshotId`,`elementRef`,`direction`,`amount`])}function u(e){let t=s(e);if(typeof t?.artifactId!=`string`||t.status!==`ready`&&t.status!==`failed`)return;let n=e=>typeof e==`string`&&e.startsWith(`/api/`)?e:void 0,r=n(t.downloadUrl),i=n(t.previewUrl);return{artifactId:t.artifactId,status:t.status,...r===void 0?{}:{downloadUrl:r},...i===void 0?{}:{previewUrl:i},...typeof t.actionCount==`number`?{actionCount:t.actionCount}:{},...typeof t.completedAt==`string`?{completedAt:t.completedAt}:{}}}var d=class{sessionId;internalToken;hubToken;revision=0;wake=0;phase=`inactive`;activation;grantId;expiresAt;actionSequence=0;failure;artifact;pending;closed=!1;constructor(
|
|
1
|
+
import{API_BASE_PATH as e,COMPUTER_USE_CONFIRMATION_WINDOW_MS as t,COMPUTER_USE_ROUTES as n,COMPUTER_USE_WAKE_LIMIT as r}from"../types/computerUseApi.mjs";import{doomApiCallerFrom as i}from"@agimon-ai/doompi-extension-contracts/package-api";import{randomUUID as a}from"node:crypto";function o(e,t){return Response.json({error:e},{status:t})}function s(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:void 0}async function c(e){try{return s(await e.json())??{}}catch{return{}}}function l(e){if(!(typeof e.snapshotId==`string`&&e.snapshotId.length>0&&typeof e.elementRef==`string`&&e.elementRef.length>0))return!1;let t=t=>Object.keys(e).every(e=>t.includes(e));return e.kind===`press`||e.kind===`focus`?t([`kind`,`snapshotId`,`elementRef`]):e.kind===`set_value`?typeof e.value==`string`&&t([`kind`,`snapshotId`,`elementRef`,`value`]):e.kind===`scroll`&&[`up`,`down`,`left`,`right`].includes(e.direction)&&(e.amount===`line`||e.amount===`page`)&&t([`kind`,`snapshotId`,`elementRef`,`direction`,`amount`])}function u(e){let t=s(e);if(typeof t?.artifactId!=`string`||t.status!==`ready`&&t.status!==`failed`)return;let n=e=>typeof e==`string`&&e.startsWith(`/api/`)?e:void 0,r=n(t.downloadUrl),i=n(t.previewUrl),a=s(t.failure),o=typeof a?.code==`string`&&typeof a.message==`string`?{code:a.code.slice(0,128),message:a.message.slice(0,512)}:void 0;return{artifactId:t.artifactId,status:t.status,...r===void 0?{}:{downloadUrl:r},...i===void 0?{}:{previewUrl:i},...typeof t.actionCount==`number`?{actionCount:t.actionCount}:{},...typeof t.completedAt==`string`?{completedAt:t.completedAt}:{},...o===void 0?{}:{failure:o}}}var d=class e{sessionId;internalToken;hubToken;requestTimeoutMs;revision=0;wake=0;phase=`inactive`;activation;grantId;expiresAt;actionSequence=0;failure;artifact;pending;closed=!1;static DEFAULT_REQUEST_TIMEOUT_MS=3e4;constructor(t){this.sessionId=t.sessionId??`unknown`,this.internalToken=t.internalToken,this.hubToken=t.hubToken,this.requestTimeoutMs=t.requestTimeoutMs??e.DEFAULT_REQUEST_TIMEOUT_MS}state(){return Object.freeze({sessionId:this.sessionId,revision:this.revision,wake:this.wake,phase:this.phase,...this.activation===void 0?{}:{requestId:this.activation.requestId,target:this.activation.target,durationMs:this.activation.durationSeconds*1e3},...this.expiresAt===void 0?{}:{expiresAt:this.expiresAt},...this.failure===void 0?{}:{failure:this.failure},...this.artifact===void 0?{}:{artifact:this.artifact}})}changed(){this.revision+=1,this.wake=(this.wake+1)%r}authorized(e,t){return t!==void 0&&e.headers.get(`authorization`)===`Bearer ${t}`}async requestActivation(e){let n=i(e.headers);if(n===void 0||n.locality===`remote`&&n.stepUp===`not-required`)return o(`Not found.`,404);if(this.phase!==`inactive`&&this.phase!==`failed`)return o(`Computer use is already busy.`,409);let r=await c(e),l=s(r.target),u=r.durationMs;if(l===void 0||typeof l.windowId!=`string`||typeof l.bundleId!=`string`)return o(`A Desktop target is required.`,400);if(typeof u!=`number`||!Number.isInteger(u)||u<1e3||u>18e5)return o(`The requested duration is invalid.`,400);let d=Date.now();return this.activation=Object.freeze({requestId:a(),target:Object.freeze({...l}),durationSeconds:u/1e3,createdAt:d,confirmationExpiresAt:d+t,caller:Object.freeze({...n})}),this.phase=`awaiting_confirmation`,this.failure=void 0,this.artifact=void 0,this.changed(),Response.json(this.state(),{status:202})}rejectPending(e){let t=this.pending;t!==void 0&&(this.pending=void 0,t.dispose(),t.reject(Error(e)))}stopAfterUncertainRequest(e){this.phase===`active`&&(this.phase=`stopping`,this.changed()),this.rejectPending(e)}async enqueue(e,t,n){if(this.phase!==`active`||this.grantId===void 0)return o(`Computer use is not active.`,409);if(this.pending!==void 0)return o(`Another computer-use request is live.`,409);if(n?.aborted===!0)return o(`The computer-use request was cancelled.`,499);let r=e===`act`?++this.actionSequence:void 0,i={id:a(),operation:e,grantId:this.grantId,...r===void 0?{}:{sequence:r},...t===void 0?{}:{payload:t}},s=new Promise((e,t)=>{let r=()=>this.stopAfterUncertainRequest(`The computer-use request was cancelled.`),a=setTimeout(()=>this.stopAfterUncertainRequest(`The computer-use request timed out.`),this.requestTimeoutMs);a.unref?.(),n?.addEventListener(`abort`,r,{once:!0}),this.pending={request:i,resolve:e,reject:t,dispose:()=>{clearTimeout(a),n?.removeEventListener(`abort`,r)}}});this.changed();try{return Response.json(await s)}catch(e){return o(e instanceof Error?e.message:String(e),502)}}async fetch(e){if(this.closed)return o(`Computer-use broker is closed.`,503);let t=new URL(e.url).pathname;if(e.method===`POST`&&t===n.activate)return this.requestActivation(e);let r=t.startsWith(`/agent/`),i=t.startsWith(`/hub/`);if(r&&!this.authorized(e,this.internalToken)||i&&!this.authorized(e,this.hubToken))return o(`Not found.`,404);if(e.method===`GET`&&(t===n.agentState||t===n.hubState))return Response.json(this.state());if(e.method===`POST`&&t===n.agentObserve)return this.enqueue(`observe`,void 0,e.signal);if(e.method===`POST`&&t===n.agentAction){let t=await c(e);return l(t)?this.enqueue(`act`,t,e.signal):o(`A valid semantic action is required.`,400)}if(e.method===`POST`&&t===n.agentStop)return this.phase===`active`?(this.phase=`stopping`,this.rejectPending(`The computer-use request was stopped.`),this.changed(),Response.json(this.state(),{status:202})):o(`Computer use is not active.`,409);if(e.method===`GET`&&t===n.hubActivation)return this.activation===void 0||this.phase!==`awaiting_confirmation`?Response.json(null):(this.phase=`activating`,this.changed(),Response.json(this.activation));if(e.method===`GET`&&t===n.hubAuthorization)return Response.json(this.grantId===void 0?null:{grantId:this.grantId,expiresAt:this.expiresAt});if(e.method===`GET`&&t===n.hubNext)return Response.json(this.pending?.request??null);if(e.method===`POST`&&t===n.hubComplete){let t=await c(e),n=typeof t.id==`string`?t.id:void 0;if(this.pending===void 0||this.pending.request.id!==n)return o(`The request is stale.`,409);let r=this.pending;return this.pending=void 0,r.dispose(),this.changed(),typeof t.error==`string`?r.reject(Error(t.error)):r.resolve(t.result),Response.json(this.state())}if(e.method===`POST`&&t===n.hubStop){let t=await c(e);if(this.phase===`activating`){if(typeof t.error==`string`)this.phase=`failed`,this.failure={code:`desktop_unavailable`,message:t.error.slice(0,512)},this.activation=void 0;else{let e=s(t.host);if(typeof e?.grantId!=`string`||!Number.isFinite(e.expiresAt))return o(`Desktop did not issue a complete grant.`,502);this.grantId=e.grantId,this.expiresAt=e.expiresAt,this.actionSequence=0,this.phase=`active`}}else this.rejectPending(`The computer-use session stopped.`),this.phase=`inactive`,this.grantId=void 0,this.expiresAt=void 0,this.activation=void 0,this.artifact=u(t.artifact);return this.changed(),Response.json(this.state())}return o(`Not found.`,404)}close(){this.closed=!0,this.rejectPending(`Computer-use broker closed.`),this.grantId=void 0,this.expiresAt=void 0}};function f(e={}){return new d(e)}const p={basePath:e,start(e){return f({...e.sessionId===void 0?{}:{sessionId:e.sessionId},...e.internalToken===void 0?{}:{internalToken:e.internalToken},...e.hubToken===void 0?{}:{hubToken:e.hubToken}})}};export{d as ComputerUseRequestBroker,p as api,f as createComputerUseApi};
|
|
2
2
|
//# sourceMappingURL=computerUseApi.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computerUseApi.mjs","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n doomApiCallerFrom,\n type DoomApi,\n type DoomApiContext,\n type DoomApiHandler,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport {\n API_BASE_PATH,\n COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n COMPUTER_USE_MAX_DURATION_MS,\n COMPUTER_USE_ROUTES,\n COMPUTER_USE_WAKE_LIMIT,\n type ComputerUseActivationRequest,\n type ComputerUseArtifactView,\n type ComputerUseBrokerRequest,\n type ComputerUseSessionView,\n} from '../types/computerUseApi.ts';\n\ninterface PendingRequest {\n readonly request: ComputerUseBrokerRequest;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\nfunction jsonError(message: string, status: number): Response {\n return Response.json({ error: message }, { status });\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nasync function body(request: Request): Promise<Record<string, unknown>> {\n try {\n return record(await request.json()) ?? {};\n } catch {\n return {};\n }\n}\n\nfunction semanticAction(value: Record<string, unknown>): boolean {\n const base =\n typeof value.snapshotId === 'string' &&\n value.snapshotId.length > 0 &&\n typeof value.elementRef === 'string' &&\n value.elementRef.length > 0;\n if (!base) return false;\n const allowed = (keys: readonly string[]) => Object.keys(value).every((key) => keys.includes(key));\n if (value.kind === 'press') return allowed(['kind', 'snapshotId', 'elementRef']);\n if (value.kind === 'set_value')\n return typeof value.value === 'string' && allowed(['kind', 'snapshotId', 'elementRef', 'value']);\n if (value.kind === 'scroll')\n return (\n ['up', 'down', 'left', 'right'].includes(value.direction as string) &&\n (value.amount === 'line' || value.amount === 'page') &&\n allowed(['kind', 'snapshotId', 'elementRef', 'direction', 'amount'])\n );\n return false;\n}\nfunction artifactView(value: unknown): ComputerUseArtifactView | undefined {\n const input = record(value);\n if (typeof input?.artifactId !== 'string' || (input.status !== 'ready' && input.status !== 'failed'))\n return undefined;\n const safeUrl = (candidate: unknown) =>\n typeof candidate === 'string' && candidate.startsWith('/api/') ? candidate : undefined;\n const downloadUrl = safeUrl(input.downloadUrl);\n const previewUrl = safeUrl(input.previewUrl);\n return {\n artifactId: input.artifactId,\n status: input.status,\n ...(downloadUrl === undefined ? {} : { downloadUrl }),\n ...(previewUrl === undefined ? {} : { previewUrl }),\n ...(typeof input.actionCount === 'number' ? { actionCount: input.actionCount } : {}),\n ...(typeof input.completedAt === 'string' ? { completedAt: input.completedAt } : {}),\n };\n}\n\nexport interface ComputerUseApiOptions {\n readonly sessionId?: string;\n readonly internalToken?: string;\n readonly hubToken?: string;\n}\n\nexport class ComputerUseRequestBroker implements DoomApiHandler {\n private readonly sessionId: string;\n private readonly internalToken?: string;\n private readonly hubToken?: string;\n private revision = 0;\n private wake = 0;\n private phase: ComputerUseSessionView['phase'] = 'inactive';\n private activation?: ComputerUseActivationRequest;\n private grantId?: string;\n private expiresAt?: number;\n private actionSequence = 0;\n private failure?: ComputerUseSessionView['failure'];\n private artifact?: ComputerUseArtifactView;\n private pending?: PendingRequest;\n private closed = false;\n\n public constructor(options: ComputerUseApiOptions) {\n this.sessionId = options.sessionId ?? 'unknown';\n this.internalToken = options.internalToken;\n this.hubToken = options.hubToken;\n }\n\n public state(): ComputerUseSessionView {\n return Object.freeze({\n sessionId: this.sessionId,\n revision: this.revision,\n wake: this.wake,\n phase: this.phase,\n ...(this.activation === undefined\n ? {}\n : {\n requestId: this.activation.requestId,\n target: this.activation.target,\n durationMs: this.activation.durationSeconds * 1_000,\n }),\n ...(this.expiresAt === undefined ? {} : { expiresAt: this.expiresAt }),\n ...(this.failure === undefined ? {} : { failure: this.failure }),\n ...(this.artifact === undefined ? {} : { artifact: this.artifact }),\n });\n }\n\n private changed(): void {\n this.revision += 1;\n this.wake = (this.wake + 1) % COMPUTER_USE_WAKE_LIMIT;\n }\n\n private authorized(request: Request, token: string | undefined): boolean {\n return token !== undefined && request.headers.get('authorization') === `Bearer ${token}`;\n }\n\n private async requestActivation(request: Request): Promise<Response> {\n const caller = doomApiCallerFrom(request.headers);\n if (caller === undefined || (caller.locality === 'remote' && caller.stepUp === 'not-required'))\n return jsonError('Not found.', 404);\n if (this.phase !== 'inactive' && this.phase !== 'failed') return jsonError('Computer use is already busy.', 409);\n const input = await body(request);\n const target = record(input.target);\n const durationMs = input.durationMs;\n if (target === undefined || typeof target.windowId !== 'string' || typeof target.bundleId !== 'string')\n return jsonError('A Desktop target is required.', 400);\n if (\n typeof durationMs !== 'number' ||\n !Number.isInteger(durationMs) ||\n durationMs < 1_000 ||\n durationMs > COMPUTER_USE_MAX_DURATION_MS\n )\n return jsonError('The requested duration is invalid.', 400);\n const createdAt = Date.now();\n this.activation = Object.freeze({\n requestId: randomUUID(),\n target: Object.freeze({ ...target }),\n durationSeconds: durationMs / 1_000,\n createdAt,\n confirmationExpiresAt: createdAt + COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n caller: Object.freeze({ ...caller }),\n });\n this.phase = 'awaiting_confirmation';\n this.failure = undefined;\n this.artifact = undefined;\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n\n private async enqueue(operation: ComputerUseBrokerRequest['operation'], payload?: unknown): Promise<Response> {\n if (this.phase !== 'active' || this.grantId === undefined) return jsonError('Computer use is not active.', 409);\n if (this.pending !== undefined) return jsonError('Another computer-use request is live.', 409);\n const sequence = operation === 'act' ? ++this.actionSequence : undefined;\n const request: ComputerUseBrokerRequest = {\n id: randomUUID(),\n operation,\n grantId: this.grantId,\n ...(sequence === undefined ? {} : { sequence }),\n ...(payload === undefined ? {} : { payload }),\n };\n const completion = new Promise<unknown>((resolve, reject) => {\n this.pending = { request, resolve, reject };\n });\n this.changed();\n try {\n return Response.json(await completion);\n } catch (error) {\n return jsonError(error instanceof Error ? error.message : String(error), 502);\n }\n }\n\n public async fetch(request: Request): Promise<Response> {\n if (this.closed) return jsonError('Computer-use broker is closed.', 503);\n const path = new URL(request.url).pathname;\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.activate) return this.requestActivation(request);\n const agent = path.startsWith('/agent/');\n const hub = path.startsWith('/hub/');\n if ((agent && !this.authorized(request, this.internalToken)) || (hub && !this.authorized(request, this.hubToken)))\n return jsonError('Not found.', 404);\n\n if (request.method === 'GET' && (path === COMPUTER_USE_ROUTES.agentState || path === COMPUTER_USE_ROUTES.hubState))\n return Response.json(this.state());\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentObserve) return this.enqueue('observe');\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentAction) {\n const action = await body(request);\n if (!semanticAction(action)) return jsonError('A valid semantic action is required.', 400);\n return this.enqueue('act', action);\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentStop) {\n if (this.phase !== 'active') return jsonError('Computer use is not active.', 409);\n this.phase = 'stopping';\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubActivation) {\n if (this.activation === undefined || this.phase !== 'awaiting_confirmation') return Response.json(null);\n this.phase = 'activating';\n this.changed();\n return Response.json(this.activation);\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubAuthorization)\n return Response.json(this.grantId === undefined ? null : { grantId: this.grantId, expiresAt: this.expiresAt });\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubNext)\n return Response.json(this.pending?.request ?? null);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubComplete) {\n const input = await body(request);\n const id = typeof input.id === 'string' ? input.id : undefined;\n if (this.pending === undefined || this.pending.request.id !== id) return jsonError('The request is stale.', 409);\n const pending = this.pending;\n this.pending = undefined;\n this.changed();\n if (typeof input.error === 'string') pending.reject(new Error(input.error));\n else pending.resolve(input.result);\n return Response.json(this.state());\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubStop) {\n const input = await body(request);\n if (this.phase === 'activating') {\n if (typeof input.error === 'string') {\n this.phase = 'failed';\n this.failure = { code: 'desktop_unavailable', message: input.error.slice(0, 512) };\n this.activation = undefined;\n } else {\n const host = record(input.host);\n if (typeof host?.grantId !== 'string' || !Number.isFinite(host.expiresAt))\n return jsonError('Desktop did not issue a complete grant.', 502);\n this.grantId = host.grantId;\n this.expiresAt = host.expiresAt as number;\n this.actionSequence = 0;\n this.phase = 'active';\n }\n } else {\n this.phase = 'inactive';\n this.grantId = undefined;\n this.expiresAt = undefined;\n this.activation = undefined;\n this.artifact = artifactView(input.artifact);\n }\n this.changed();\n return Response.json(this.state());\n }\n return jsonError('Not found.', 404);\n }\n\n public close(): void {\n this.closed = true;\n this.pending?.reject(new Error('Computer-use broker closed.'));\n this.pending = undefined;\n this.grantId = undefined;\n this.expiresAt = undefined;\n }\n}\n\nexport function createComputerUseApi(options: ComputerUseApiOptions = {}): ComputerUseRequestBroker {\n return new ComputerUseRequestBroker(options);\n}\n\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n return createComputerUseApi({\n ...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),\n ...(context.internalToken === undefined ? {} : { internalToken: context.internalToken }),\n ...(context.hubToken === undefined ? {} : { hubToken: context.hubToken }),\n });\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"computerUseApi.mjs","names":[],"sources":["../../src/adapters/computerUseApi.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n doomApiCallerFrom,\n type DoomApi,\n type DoomApiContext,\n type DoomApiHandler,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport {\n API_BASE_PATH,\n COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n COMPUTER_USE_MAX_DURATION_MS,\n COMPUTER_USE_ROUTES,\n COMPUTER_USE_WAKE_LIMIT,\n type ComputerUseActivationRequest,\n type ComputerUseArtifactView,\n type ComputerUseBrokerRequest,\n type ComputerUseSessionView,\n} from '../types/computerUseApi.ts';\n\ninterface PendingRequest {\n readonly request: ComputerUseBrokerRequest;\n readonly dispose: () => void;\n resolve(value: unknown): void;\n reject(error: Error): void;\n}\n\nfunction jsonError(message: string, status: number): Response {\n return Response.json({ error: message }, { status });\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nasync function body(request: Request): Promise<Record<string, unknown>> {\n try {\n return record(await request.json()) ?? {};\n } catch {\n return {};\n }\n}\n\nfunction semanticAction(value: Record<string, unknown>): boolean {\n const base =\n typeof value.snapshotId === 'string' &&\n value.snapshotId.length > 0 &&\n typeof value.elementRef === 'string' &&\n value.elementRef.length > 0;\n if (!base) return false;\n const allowed = (keys: readonly string[]) => Object.keys(value).every((key) => keys.includes(key));\n if (value.kind === 'press' || value.kind === 'focus') return allowed(['kind', 'snapshotId', 'elementRef']);\n if (value.kind === 'set_value')\n return typeof value.value === 'string' && allowed(['kind', 'snapshotId', 'elementRef', 'value']);\n if (value.kind === 'scroll')\n return (\n ['up', 'down', 'left', 'right'].includes(value.direction as string) &&\n (value.amount === 'line' || value.amount === 'page') &&\n allowed(['kind', 'snapshotId', 'elementRef', 'direction', 'amount'])\n );\n return false;\n}\nfunction artifactView(value: unknown): ComputerUseArtifactView | undefined {\n const input = record(value);\n if (typeof input?.artifactId !== 'string' || (input.status !== 'ready' && input.status !== 'failed'))\n return undefined;\n const safeUrl = (candidate: unknown) =>\n typeof candidate === 'string' && candidate.startsWith('/api/') ? candidate : undefined;\n const downloadUrl = safeUrl(input.downloadUrl);\n const previewUrl = safeUrl(input.previewUrl);\n const artifactFailure = record(input.failure);\n const safeFailure =\n typeof artifactFailure?.code === 'string' && typeof artifactFailure.message === 'string'\n ? { code: artifactFailure.code.slice(0, 128), message: artifactFailure.message.slice(0, 512) }\n : undefined;\n return {\n artifactId: input.artifactId,\n status: input.status,\n ...(downloadUrl === undefined ? {} : { downloadUrl }),\n ...(previewUrl === undefined ? {} : { previewUrl }),\n ...(typeof input.actionCount === 'number' ? { actionCount: input.actionCount } : {}),\n ...(typeof input.completedAt === 'string' ? { completedAt: input.completedAt } : {}),\n ...(safeFailure === undefined ? {} : { failure: safeFailure }),\n };\n}\n\nexport interface ComputerUseApiOptions {\n readonly sessionId?: string;\n readonly internalToken?: string;\n readonly hubToken?: string;\n readonly requestTimeoutMs?: number;\n}\n\nexport class ComputerUseRequestBroker implements DoomApiHandler {\n private readonly sessionId: string;\n private readonly internalToken?: string;\n private readonly hubToken?: string;\n private readonly requestTimeoutMs: number;\n private revision = 0;\n private wake = 0;\n private phase: ComputerUseSessionView['phase'] = 'inactive';\n private activation?: ComputerUseActivationRequest;\n private grantId?: string;\n private expiresAt?: number;\n private actionSequence = 0;\n private failure?: ComputerUseSessionView['failure'];\n private artifact?: ComputerUseArtifactView;\n private pending?: PendingRequest;\n private closed = false;\n\n private static readonly DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n public constructor(options: ComputerUseApiOptions) {\n this.sessionId = options.sessionId ?? 'unknown';\n this.internalToken = options.internalToken;\n this.hubToken = options.hubToken;\n this.requestTimeoutMs = options.requestTimeoutMs ?? ComputerUseRequestBroker.DEFAULT_REQUEST_TIMEOUT_MS;\n }\n\n public state(): ComputerUseSessionView {\n return Object.freeze({\n sessionId: this.sessionId,\n revision: this.revision,\n wake: this.wake,\n phase: this.phase,\n ...(this.activation === undefined\n ? {}\n : {\n requestId: this.activation.requestId,\n target: this.activation.target,\n durationMs: this.activation.durationSeconds * 1_000,\n }),\n ...(this.expiresAt === undefined ? {} : { expiresAt: this.expiresAt }),\n ...(this.failure === undefined ? {} : { failure: this.failure }),\n ...(this.artifact === undefined ? {} : { artifact: this.artifact }),\n });\n }\n\n private changed(): void {\n this.revision += 1;\n this.wake = (this.wake + 1) % COMPUTER_USE_WAKE_LIMIT;\n }\n\n private authorized(request: Request, token: string | undefined): boolean {\n return token !== undefined && request.headers.get('authorization') === `Bearer ${token}`;\n }\n\n private async requestActivation(request: Request): Promise<Response> {\n const caller = doomApiCallerFrom(request.headers);\n if (caller === undefined || (caller.locality === 'remote' && caller.stepUp === 'not-required'))\n return jsonError('Not found.', 404);\n if (this.phase !== 'inactive' && this.phase !== 'failed') return jsonError('Computer use is already busy.', 409);\n const input = await body(request);\n const target = record(input.target);\n const durationMs = input.durationMs;\n if (target === undefined || typeof target.windowId !== 'string' || typeof target.bundleId !== 'string')\n return jsonError('A Desktop target is required.', 400);\n if (\n typeof durationMs !== 'number' ||\n !Number.isInteger(durationMs) ||\n durationMs < 1_000 ||\n durationMs > COMPUTER_USE_MAX_DURATION_MS\n )\n return jsonError('The requested duration is invalid.', 400);\n const createdAt = Date.now();\n this.activation = Object.freeze({\n requestId: randomUUID(),\n target: Object.freeze({ ...target }),\n durationSeconds: durationMs / 1_000,\n createdAt,\n confirmationExpiresAt: createdAt + COMPUTER_USE_CONFIRMATION_WINDOW_MS,\n caller: Object.freeze({ ...caller }),\n });\n this.phase = 'awaiting_confirmation';\n this.failure = undefined;\n this.artifact = undefined;\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n\n private rejectPending(message: string): void {\n const pending = this.pending;\n if (pending === undefined) return;\n this.pending = undefined;\n pending.dispose();\n pending.reject(new Error(message));\n }\n\n private stopAfterUncertainRequest(message: string): void {\n if (this.phase === 'active') {\n this.phase = 'stopping';\n this.changed();\n }\n this.rejectPending(message);\n }\n\n private async enqueue(\n operation: ComputerUseBrokerRequest['operation'],\n payload?: unknown,\n signal?: AbortSignal,\n ): Promise<Response> {\n if (this.phase !== 'active' || this.grantId === undefined) return jsonError('Computer use is not active.', 409);\n if (this.pending !== undefined) return jsonError('Another computer-use request is live.', 409);\n if (signal?.aborted === true) return jsonError('The computer-use request was cancelled.', 499);\n const sequence = operation === 'act' ? ++this.actionSequence : undefined;\n const request: ComputerUseBrokerRequest = {\n id: randomUUID(),\n operation,\n grantId: this.grantId,\n ...(sequence === undefined ? {} : { sequence }),\n ...(payload === undefined ? {} : { payload }),\n };\n const completion = new Promise<unknown>((resolve, reject) => {\n const onAbort = () => this.stopAfterUncertainRequest('The computer-use request was cancelled.');\n const timer = setTimeout(\n () => this.stopAfterUncertainRequest('The computer-use request timed out.'),\n this.requestTimeoutMs,\n );\n timer.unref?.();\n signal?.addEventListener('abort', onAbort, { once: true });\n this.pending = {\n request,\n resolve,\n reject,\n dispose: () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n },\n };\n });\n this.changed();\n try {\n return Response.json(await completion);\n } catch (error) {\n return jsonError(error instanceof Error ? error.message : String(error), 502);\n }\n }\n\n public async fetch(request: Request): Promise<Response> {\n if (this.closed) return jsonError('Computer-use broker is closed.', 503);\n const path = new URL(request.url).pathname;\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.activate) return this.requestActivation(request);\n const agent = path.startsWith('/agent/');\n const hub = path.startsWith('/hub/');\n if ((agent && !this.authorized(request, this.internalToken)) || (hub && !this.authorized(request, this.hubToken)))\n return jsonError('Not found.', 404);\n\n if (request.method === 'GET' && (path === COMPUTER_USE_ROUTES.agentState || path === COMPUTER_USE_ROUTES.hubState))\n return Response.json(this.state());\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentObserve)\n return this.enqueue('observe', undefined, request.signal);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentAction) {\n const action = await body(request);\n if (!semanticAction(action)) return jsonError('A valid semantic action is required.', 400);\n return this.enqueue('act', action, request.signal);\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.agentStop) {\n if (this.phase !== 'active') return jsonError('Computer use is not active.', 409);\n this.phase = 'stopping';\n this.rejectPending('The computer-use request was stopped.');\n this.changed();\n return Response.json(this.state(), { status: 202 });\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubActivation) {\n if (this.activation === undefined || this.phase !== 'awaiting_confirmation') return Response.json(null);\n this.phase = 'activating';\n this.changed();\n return Response.json(this.activation);\n }\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubAuthorization)\n return Response.json(this.grantId === undefined ? null : { grantId: this.grantId, expiresAt: this.expiresAt });\n if (request.method === 'GET' && path === COMPUTER_USE_ROUTES.hubNext)\n return Response.json(this.pending?.request ?? null);\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubComplete) {\n const input = await body(request);\n const id = typeof input.id === 'string' ? input.id : undefined;\n if (this.pending === undefined || this.pending.request.id !== id) return jsonError('The request is stale.', 409);\n const pending = this.pending;\n this.pending = undefined;\n pending.dispose();\n this.changed();\n if (typeof input.error === 'string') pending.reject(new Error(input.error));\n else pending.resolve(input.result);\n return Response.json(this.state());\n }\n if (request.method === 'POST' && path === COMPUTER_USE_ROUTES.hubStop) {\n const input = await body(request);\n if (this.phase === 'activating') {\n if (typeof input.error === 'string') {\n this.phase = 'failed';\n this.failure = { code: 'desktop_unavailable', message: input.error.slice(0, 512) };\n this.activation = undefined;\n } else {\n const host = record(input.host);\n if (typeof host?.grantId !== 'string' || !Number.isFinite(host.expiresAt))\n return jsonError('Desktop did not issue a complete grant.', 502);\n this.grantId = host.grantId;\n this.expiresAt = host.expiresAt as number;\n this.actionSequence = 0;\n this.phase = 'active';\n }\n } else {\n this.rejectPending('The computer-use session stopped.');\n this.phase = 'inactive';\n this.grantId = undefined;\n this.expiresAt = undefined;\n this.activation = undefined;\n this.artifact = artifactView(input.artifact);\n }\n this.changed();\n return Response.json(this.state());\n }\n return jsonError('Not found.', 404);\n }\n\n public close(): void {\n this.closed = true;\n this.rejectPending('Computer-use broker closed.');\n this.grantId = undefined;\n this.expiresAt = undefined;\n }\n}\n\nexport function createComputerUseApi(options: ComputerUseApiOptions = {}): ComputerUseRequestBroker {\n return new ComputerUseRequestBroker(options);\n}\n\nexport const api: DoomApi = {\n basePath: API_BASE_PATH,\n start(context: DoomApiContext): DoomApiHandler {\n return createComputerUseApi({\n ...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),\n ...(context.internalToken === undefined ? {} : { internalToken: context.internalToken }),\n ...(context.hubToken === undefined ? {} : { hubToken: context.hubToken }),\n });\n },\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
let e=require("node:child_process"),t=require("node:fs/promises");const n=String.raw`
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { stripTypeScriptTypes } from 'node:module';
|
|
4
|
+
|
|
5
|
+
let nextRequestId = 0;
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
const call = (operation, payload) => new Promise((resolve, reject) => {
|
|
8
|
+
const id = String(++nextRequestId);
|
|
9
|
+
pending.set(id, { resolve, reject });
|
|
10
|
+
process.send({ type: 'program', id, operation, payload });
|
|
11
|
+
});
|
|
12
|
+
process.on('message', async (message) => {
|
|
13
|
+
if (message?.type === 'response') {
|
|
14
|
+
const request = pending.get(message.id);
|
|
15
|
+
if (!request) return;
|
|
16
|
+
pending.delete(message.id);
|
|
17
|
+
if (message.error) request.reject(new Error(message.error));
|
|
18
|
+
else request.resolve(message.value);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (message?.type !== 'start') return;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
process.once('SIGTERM', () => controller.abort());
|
|
24
|
+
const logger = Object.fromEntries(['debug', 'info', 'warn', 'error'].map((level) => [level, (value) => {
|
|
25
|
+
process.send({ type: 'log', level, message: String(value) });
|
|
26
|
+
}]));
|
|
27
|
+
try {
|
|
28
|
+
const source = await readFile(message.scriptPath, 'utf8');
|
|
29
|
+
const compiled = stripTypeScriptTypes(source, { mode: 'strip' });
|
|
30
|
+
const moduleUrl = 'data:text/javascript;base64,' + Buffer.from(compiled).toString('base64');
|
|
31
|
+
const loaded = await import(moduleUrl);
|
|
32
|
+
if (typeof loaded.run !== 'function') throw new Error('Computer script must export a named run function.');
|
|
33
|
+
const program = {
|
|
34
|
+
observe: () => call('observe'),
|
|
35
|
+
act: (action) => call('act', action),
|
|
36
|
+
};
|
|
37
|
+
const result = await loaded.run({ context: { program }, input: message.input, logger, signal: controller.signal });
|
|
38
|
+
const resultJson = result === undefined ? undefined : JSON.stringify(result);
|
|
39
|
+
process.send({ type: 'complete', resultJson });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
process.send({ type: 'failed', error: error instanceof Error ? error.message : String(error) });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
`;var r=class{client;allowedScriptPaths;timeoutMs;maxOutputBytes;constructor(e){this.client=e.client,this.allowedScriptPaths=e.allowedScriptPaths,this.timeoutMs=e.timeoutMs??3e4,this.maxOutputBytes=e.maxOutputBytes??262144}async execute(r,i,a){let o=await(0,t.realpath)(r);if(!(await Promise.all(this.allowedScriptPaths.map(async e=>(0,t.realpath)(e).catch(()=>``)))).includes(o))throw Error(`Computer script path is not explicitly allowed.`);if(!o.endsWith(`.ts`))throw Error(`Computer scripts must be TypeScript files.`);let s=JSON.stringify(i);if(s===void 0)throw Error(`Computer script input must be JSON serializable.`);if(Buffer.byteLength(s)>this.maxOutputBytes)throw Error(`Computer script input exceeds the size limit.`);return new Promise((t,r)=>{let i=(0,e.spawn)(process.execPath,[`--input-type=module`,`--eval`,n],{stdio:[`ignore`,`pipe`,`pipe`,`ipc`]}),c=new AbortController,l=[],u=0,d=!1,f=Promise.resolve(),p=()=>{c.abort(),i.kill(`SIGTERM`),setTimeout(()=>i.kill(`SIGKILL`),250).unref?.()},m=(e,n)=>{d||(d=!0,clearTimeout(_),a?.removeEventListener(`abort`,g),p(),e===void 0?t(n):r(e))},h=e=>(u+=Buffer.byteLength(e),u<=this.maxOutputBytes||(m(Error(`Computer script output exceeded the size limit.`)),!1)),g=()=>m(Error(`Computer script execution was cancelled.`)),_=setTimeout(()=>m(Error(`Computer script execution timed out.`)),this.timeoutMs);if(_.unref?.(),a?.addEventListener(`abort`,g,{once:!0}),a?.aborted===!0)return g();i.stdout?.on(`data`,e=>h(e.toString(`utf8`))),i.stderr?.on(`data`,e=>h(e.toString(`utf8`))),i.on(`error`,e=>m(e)),i.on(`exit`,(e,t)=>{d||m(Error(`Computer script worker exited before completion (${t??e??`unknown`}).`))}),i.on(`message`,e=>{if(!d){if(e.type===`log`&&typeof e.message==`string`){let t=`[${String(e.level)}] ${e.message}`;h(t)&&l.push(t);return}if(e.type===`program`&&typeof e.id==`string`&&(e.operation===`observe`||e.operation===`act`)){f=f.then(async()=>{try{let t=e.operation===`observe`?await this.client.observe(c.signal):await this.client.act(e.payload,c.signal);i.send({type:`response`,id:e.id,value:t})}catch(t){i.send({type:`response`,id:e.id,error:t instanceof Error?t.message:String(t)})}});return}if(e.type===`failed`&&typeof e.error==`string`){m(Error(e.error));return}e.type===`complete`&&(f=f.then(async()=>{let t=e.resultJson;if(t!==void 0&&typeof t!=`string`){m(Error(`Computer script returned an invalid result.`));return}if(typeof t==`string`&&!h(t))return;let n=await this.client.observe(c.signal);m(void 0,{...typeof t==`string`?{result:JSON.parse(t)}:{},observation:n,logs:l})}).catch(e=>m(e instanceof Error?e:Error(String(e)))))}}),i.send({type:`start`,scriptPath:o,input:JSON.parse(s)})})}};exports.ComputerScriptRunner=r;
|
|
45
|
+
//# sourceMappingURL=computerScriptRunner.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computerScriptRunner.cjs","names":["realpath","spawn"],"sources":["../../../src/adapters/pi/computerScriptRunner.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { realpath } from 'node:fs/promises';\nimport type { ComputerScriptExecutionResult, ComputerScriptRunnerOptions } from '../../types/computerScript.ts';\nimport type { ComputerUseAction } from '../../types/computerUse.ts';\nimport type { ComputerUseSessionClient } from './sessionApiClient.ts';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 256 * 1024;\n\nconst WORKER_SOURCE = String.raw`\nimport { readFile } from 'node:fs/promises';\nimport { stripTypeScriptTypes } from 'node:module';\n\nlet nextRequestId = 0;\nconst pending = new Map();\nconst call = (operation, payload) => new Promise((resolve, reject) => {\n const id = String(++nextRequestId);\n pending.set(id, { resolve, reject });\n process.send({ type: 'program', id, operation, payload });\n});\nprocess.on('message', async (message) => {\n if (message?.type === 'response') {\n const request = pending.get(message.id);\n if (!request) return;\n pending.delete(message.id);\n if (message.error) request.reject(new Error(message.error));\n else request.resolve(message.value);\n return;\n }\n if (message?.type !== 'start') return;\n const controller = new AbortController();\n process.once('SIGTERM', () => controller.abort());\n const logger = Object.fromEntries(['debug', 'info', 'warn', 'error'].map((level) => [level, (value) => {\n process.send({ type: 'log', level, message: String(value) });\n }]));\n try {\n const source = await readFile(message.scriptPath, 'utf8');\n const compiled = stripTypeScriptTypes(source, { mode: 'strip' });\n const moduleUrl = 'data:text/javascript;base64,' + Buffer.from(compiled).toString('base64');\n const loaded = await import(moduleUrl);\n if (typeof loaded.run !== 'function') throw new Error('Computer script must export a named run function.');\n const program = {\n observe: () => call('observe'),\n act: (action) => call('act', action),\n };\n const result = await loaded.run({ context: { program }, input: message.input, logger, signal: controller.signal });\n const resultJson = result === undefined ? undefined : JSON.stringify(result);\n process.send({ type: 'complete', resultJson });\n } catch (error) {\n process.send({ type: 'failed', error: error instanceof Error ? error.message : String(error) });\n }\n});\n`;\n\ninterface WorkerMessage {\n readonly type?: unknown;\n readonly id?: unknown;\n readonly operation?: unknown;\n readonly payload?: unknown;\n readonly level?: unknown;\n readonly message?: unknown;\n readonly resultJson?: unknown;\n readonly error?: unknown;\n}\n\nexport class ComputerScriptRunner {\n private readonly client: ComputerUseSessionClient;\n private readonly allowedScriptPaths: readonly string[];\n private readonly timeoutMs: number;\n private readonly maxOutputBytes: number;\n\n public constructor(options: ComputerScriptRunnerOptions) {\n this.client = options.client;\n this.allowedScriptPaths = options.allowedScriptPaths;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;\n }\n\n public async execute(\n scriptPath: string,\n input: unknown,\n signal?: AbortSignal,\n ): Promise<ComputerScriptExecutionResult> {\n const resolvedPath = await realpath(scriptPath);\n const allowedPaths = await Promise.all(\n this.allowedScriptPaths.map(async (candidate) => realpath(candidate).catch(() => '')),\n );\n if (!allowedPaths.includes(resolvedPath)) throw new Error('Computer script path is not explicitly allowed.');\n if (!resolvedPath.endsWith('.ts')) throw new Error('Computer scripts must be TypeScript files.');\n\n const serializedInput = JSON.stringify(input);\n if (serializedInput === undefined) throw new Error('Computer script input must be JSON serializable.');\n if (Buffer.byteLength(serializedInput) > this.maxOutputBytes)\n throw new Error('Computer script input exceeds the size limit.');\n\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, ['--input-type=module', '--eval', WORKER_SOURCE], {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n const controller = new AbortController();\n const logs: string[] = [];\n let outputBytes = 0;\n let settled = false;\n let operation = Promise.resolve();\n\n const terminate = (): void => {\n controller.abort();\n child.kill('SIGTERM');\n const killTimer = setTimeout(() => child.kill('SIGKILL'), 250);\n killTimer.unref?.();\n };\n const finish = (error?: Error, value?: ComputerScriptExecutionResult): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n signal?.removeEventListener('abort', onAbort);\n terminate();\n if (error !== undefined) reject(error);\n else resolve(value as ComputerScriptExecutionResult);\n };\n const addOutput = (value: string): boolean => {\n outputBytes += Buffer.byteLength(value);\n if (outputBytes <= this.maxOutputBytes) return true;\n finish(new Error('Computer script output exceeded the size limit.'));\n return false;\n };\n const onAbort = () => finish(new Error('Computer script execution was cancelled.'));\n const timeout = setTimeout(() => finish(new Error('Computer script execution timed out.')), this.timeoutMs);\n timeout.unref?.();\n signal?.addEventListener('abort', onAbort, { once: true });\n if (signal?.aborted === true) return onAbort();\n\n child.stdout?.on('data', (chunk: Buffer) => addOutput(chunk.toString('utf8')));\n child.stderr?.on('data', (chunk: Buffer) => addOutput(chunk.toString('utf8')));\n child.on('error', (error) => finish(error));\n child.on('exit', (code, exitSignal) => {\n if (!settled)\n finish(new Error(`Computer script worker exited before completion (${exitSignal ?? code ?? 'unknown'}).`));\n });\n child.on('message', (rawMessage: WorkerMessage) => {\n if (settled) return;\n if (rawMessage.type === 'log' && typeof rawMessage.message === 'string') {\n const line = `[${String(rawMessage.level)}] ${rawMessage.message}`;\n if (addOutput(line)) logs.push(line);\n return;\n }\n if (\n rawMessage.type === 'program' &&\n typeof rawMessage.id === 'string' &&\n (rawMessage.operation === 'observe' || rawMessage.operation === 'act')\n ) {\n operation = operation.then(async () => {\n try {\n const value =\n rawMessage.operation === 'observe'\n ? await this.client.observe(controller.signal)\n : await this.client.act(rawMessage.payload as ComputerUseAction, controller.signal);\n child.send({ type: 'response', id: rawMessage.id, value });\n } catch (error) {\n child.send({\n type: 'response',\n id: rawMessage.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n });\n return;\n }\n if (rawMessage.type === 'failed' && typeof rawMessage.error === 'string') {\n finish(new Error(rawMessage.error));\n return;\n }\n if (rawMessage.type === 'complete') {\n operation = operation\n .then(async () => {\n const resultJson = rawMessage.resultJson;\n if (resultJson !== undefined && typeof resultJson !== 'string') {\n finish(new Error('Computer script returned an invalid result.'));\n return;\n }\n if (typeof resultJson === 'string' && !addOutput(resultJson)) return;\n const observation = await this.client.observe(controller.signal);\n finish(undefined, {\n ...(typeof resultJson === 'string' ? { result: JSON.parse(resultJson) as unknown } : {}),\n observation,\n logs,\n });\n })\n .catch((error: unknown) => finish(error instanceof Error ? error : new Error(String(error))));\n }\n });\n child.send({ type: 'start', scriptPath: resolvedPath, input: JSON.parse(serializedInput) as unknown });\n });\n }\n}\n"],"mappings":"kEAMA,MAGM,EAAgB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwDhC,IAAa,EAAb,KAAkC,CAChC,OACA,mBACA,UACA,eAEA,YAAmB,EAAsC,CACvD,KAAK,OAAS,EAAQ,OACtB,KAAK,mBAAqB,EAAQ,mBAClC,KAAK,UAAY,EAAQ,WAAa,IACtC,KAAK,eAAiB,EAAQ,gBAAkB,MAClD,CAEA,MAAa,QACX,EACA,EACA,EACwC,CACxC,IAAM,EAAe,MAAA,EAAMA,EAAAA,SAAAA,CAAS,CAAU,EAI9C,GAAI,EAAC,MAHsB,QAAQ,IACjC,KAAK,mBAAmB,IAAI,KAAO,KAAA,EAAcA,EAAAA,SAAAA,CAAS,CAAS,CAAC,CAAC,UAAY,EAAE,CAAC,CACtF,EAAA,CACkB,SAAS,CAAY,EAAG,MAAU,MAAM,iDAAiD,EAC3G,GAAI,CAAC,EAAa,SAAS,KAAK,EAAG,MAAU,MAAM,4CAA4C,EAE/F,IAAM,EAAkB,KAAK,UAAU,CAAK,EAC5C,GAAI,IAAoB,IAAA,GAAW,MAAU,MAAM,kDAAkD,EACrG,GAAI,OAAO,WAAW,CAAe,EAAI,KAAK,eAC5C,MAAU,MAAM,+CAA+C,EAEjE,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,GAAA,EAAQC,EAAAA,MAAAA,CAAM,QAAQ,SAAU,CAAC,sBAAuB,SAAU,CAAa,EAAG,CACtF,MAAO,CAAC,SAAU,OAAQ,OAAQ,KAAK,CACzC,CAAC,EACK,EAAa,IAAI,gBACjB,EAAiB,CAAC,EACpB,EAAc,EACd,EAAU,GACV,EAAY,QAAQ,QAAQ,EAE1B,MAAwB,CAC5B,EAAW,MAAM,EACjB,EAAM,KAAK,SAAS,EAEpB,eADmC,EAAM,KAAK,SAAS,EAAG,GAClD,CAAC,CAAC,QAAQ,CACpB,EACM,GAAU,EAAe,IAAgD,CACzE,IACJ,EAAU,GACV,aAAa,CAAO,EACpB,GAAQ,oBAAoB,QAAS,CAAO,EAC5C,EAAU,EACN,IAAU,IAAA,GACT,EAAQ,CAAsC,EAD1B,EAAO,CAAK,EAEvC,EACM,EAAa,IACjB,GAAe,OAAO,WAAW,CAAK,EAClC,GAAe,KAAK,iBACxB,EAAW,MAAM,iDAAiD,CAAC,EAC5D,KAEH,MAAgB,EAAW,MAAM,0CAA0C,CAAC,EAC5E,EAAU,eAAiB,EAAW,MAAM,sCAAsC,CAAC,EAAG,KAAK,SAAS,EAG1G,GAFA,EAAQ,QAAQ,EAChB,GAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EACrD,GAAQ,UAAY,GAAM,OAAO,EAAQ,EAE7C,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAU,EAAM,SAAS,MAAM,CAAC,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAU,EAAM,SAAS,MAAM,CAAC,CAAC,EAC7E,EAAM,GAAG,QAAU,GAAU,EAAO,CAAK,CAAC,EAC1C,EAAM,GAAG,QAAS,EAAM,IAAe,CAChC,GACH,EAAW,MAAM,oDAAoD,GAAc,GAAQ,UAAU,GAAG,CAAC,CAC7G,CAAC,EACD,EAAM,GAAG,UAAY,GAA8B,CAC7C,MACJ,IAAI,EAAW,OAAS,OAAS,OAAO,EAAW,SAAY,SAAU,CACvE,IAAM,EAAO,IAAI,OAAO,EAAW,KAAK,EAAE,IAAI,EAAW,UACrD,EAAU,CAAI,GAAG,EAAK,KAAK,CAAI,EACnC,MACF,CACA,GACE,EAAW,OAAS,WACpB,OAAO,EAAW,IAAO,WACxB,EAAW,YAAc,WAAa,EAAW,YAAc,OAChE,CACA,EAAY,EAAU,KAAK,SAAY,CACrC,GAAI,CACF,IAAM,EACJ,EAAW,YAAc,UACrB,MAAM,KAAK,OAAO,QAAQ,EAAW,MAAM,EAC3C,MAAM,KAAK,OAAO,IAAI,EAAW,QAA8B,EAAW,MAAM,EACtF,EAAM,KAAK,CAAE,KAAM,WAAY,GAAI,EAAW,GAAI,OAAM,CAAC,CAC3D,OAAS,EAAO,CACd,EAAM,KAAK,CACT,KAAM,WACN,GAAI,EAAW,GACf,MAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D,CAAC,CACH,CACF,CAAC,EACD,MACF,CACA,GAAI,EAAW,OAAS,UAAY,OAAO,EAAW,OAAU,SAAU,CACxE,EAAW,MAAM,EAAW,KAAK,CAAC,EAClC,MACF,CACI,EAAW,OAAS,aACtB,EAAY,EACT,KAAK,SAAY,CAChB,IAAM,EAAa,EAAW,WAC9B,GAAI,IAAe,IAAA,IAAa,OAAO,GAAe,SAAU,CAC9D,EAAW,MAAM,6CAA6C,CAAC,EAC/D,MACF,CACA,GAAI,OAAO,GAAe,UAAY,CAAC,EAAU,CAAU,EAAG,OAC9D,IAAM,EAAc,MAAM,KAAK,OAAO,QAAQ,EAAW,MAAM,EAC/D,EAAO,IAAA,GAAW,CAChB,GAAI,OAAO,GAAe,SAAW,CAAE,OAAQ,KAAK,MAAM,CAAU,CAAa,EAAI,CAAC,EACtF,cACA,MACF,CAAC,CACH,CAAC,CAAC,CACD,MAAO,GAAmB,EAAO,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,CAAC,CAAC,EA3ChG,CA6CF,CAAC,EACD,EAAM,KAAK,CAAE,KAAM,QAAS,WAAY,EAAc,MAAO,KAAK,MAAM,CAAe,CAAa,CAAC,CACvG,CAAC,CACH,CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ComputerScriptExecutionResult, ComputerScriptRunnerOptions } from "../../types/computerScript.cjs";
|
|
2
|
+
//#region src/adapters/pi/computerScriptRunner.d.ts
|
|
3
|
+
export declare class ComputerScriptRunner {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly allowedScriptPaths;
|
|
6
|
+
private readonly timeoutMs;
|
|
7
|
+
private readonly maxOutputBytes;
|
|
8
|
+
constructor(options: ComputerScriptRunnerOptions);
|
|
9
|
+
execute(scriptPath: string, input: unknown, signal?: AbortSignal): Promise<ComputerScriptExecutionResult>;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//# sourceMappingURL=computerScriptRunner.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computerScriptRunner.d.cts","names":[],"sources":["../../../src/adapters/pi/computerScriptRunner.ts"],"mappings":";;qBAiEa;mBACM;mBACA;mBACA;mBACA;EAEjB,YAAmB,SAAS;EAOf,QACX,oBACA,gBACA,SAAS,cACR,QAAQ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ComputerScriptExecutionResult, ComputerScriptRunnerOptions } from "../../types/computerScript.mjs";
|
|
2
|
+
//#region src/adapters/pi/computerScriptRunner.d.ts
|
|
3
|
+
export declare class ComputerScriptRunner {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly allowedScriptPaths;
|
|
6
|
+
private readonly timeoutMs;
|
|
7
|
+
private readonly maxOutputBytes;
|
|
8
|
+
constructor(options: ComputerScriptRunnerOptions);
|
|
9
|
+
execute(scriptPath: string, input: unknown, signal?: AbortSignal): Promise<ComputerScriptExecutionResult>;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//# sourceMappingURL=computerScriptRunner.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computerScriptRunner.d.mts","names":[],"sources":["../../../src/adapters/pi/computerScriptRunner.ts"],"mappings":";;qBAiEa;mBACM;mBACA;mBACA;mBACA;EAEjB,YAAmB,SAAS;EAOf,QACX,oBACA,gBACA,SAAS,cACR,QAAQ"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import{spawn as e}from"node:child_process";import{realpath as t}from"node:fs/promises";const n=String.raw`
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { stripTypeScriptTypes } from 'node:module';
|
|
4
|
+
|
|
5
|
+
let nextRequestId = 0;
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
const call = (operation, payload) => new Promise((resolve, reject) => {
|
|
8
|
+
const id = String(++nextRequestId);
|
|
9
|
+
pending.set(id, { resolve, reject });
|
|
10
|
+
process.send({ type: 'program', id, operation, payload });
|
|
11
|
+
});
|
|
12
|
+
process.on('message', async (message) => {
|
|
13
|
+
if (message?.type === 'response') {
|
|
14
|
+
const request = pending.get(message.id);
|
|
15
|
+
if (!request) return;
|
|
16
|
+
pending.delete(message.id);
|
|
17
|
+
if (message.error) request.reject(new Error(message.error));
|
|
18
|
+
else request.resolve(message.value);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (message?.type !== 'start') return;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
process.once('SIGTERM', () => controller.abort());
|
|
24
|
+
const logger = Object.fromEntries(['debug', 'info', 'warn', 'error'].map((level) => [level, (value) => {
|
|
25
|
+
process.send({ type: 'log', level, message: String(value) });
|
|
26
|
+
}]));
|
|
27
|
+
try {
|
|
28
|
+
const source = await readFile(message.scriptPath, 'utf8');
|
|
29
|
+
const compiled = stripTypeScriptTypes(source, { mode: 'strip' });
|
|
30
|
+
const moduleUrl = 'data:text/javascript;base64,' + Buffer.from(compiled).toString('base64');
|
|
31
|
+
const loaded = await import(moduleUrl);
|
|
32
|
+
if (typeof loaded.run !== 'function') throw new Error('Computer script must export a named run function.');
|
|
33
|
+
const program = {
|
|
34
|
+
observe: () => call('observe'),
|
|
35
|
+
act: (action) => call('act', action),
|
|
36
|
+
};
|
|
37
|
+
const result = await loaded.run({ context: { program }, input: message.input, logger, signal: controller.signal });
|
|
38
|
+
const resultJson = result === undefined ? undefined : JSON.stringify(result);
|
|
39
|
+
process.send({ type: 'complete', resultJson });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
process.send({ type: 'failed', error: error instanceof Error ? error.message : String(error) });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
`;var r=class{client;allowedScriptPaths;timeoutMs;maxOutputBytes;constructor(e){this.client=e.client,this.allowedScriptPaths=e.allowedScriptPaths,this.timeoutMs=e.timeoutMs??3e4,this.maxOutputBytes=e.maxOutputBytes??262144}async execute(r,i,a){let o=await t(r);if(!(await Promise.all(this.allowedScriptPaths.map(async e=>t(e).catch(()=>``)))).includes(o))throw Error(`Computer script path is not explicitly allowed.`);if(!o.endsWith(`.ts`))throw Error(`Computer scripts must be TypeScript files.`);let s=JSON.stringify(i);if(s===void 0)throw Error(`Computer script input must be JSON serializable.`);if(Buffer.byteLength(s)>this.maxOutputBytes)throw Error(`Computer script input exceeds the size limit.`);return new Promise((t,r)=>{let i=e(process.execPath,[`--input-type=module`,`--eval`,n],{stdio:[`ignore`,`pipe`,`pipe`,`ipc`]}),c=new AbortController,l=[],u=0,d=!1,f=Promise.resolve(),p=()=>{c.abort(),i.kill(`SIGTERM`),setTimeout(()=>i.kill(`SIGKILL`),250).unref?.()},m=(e,n)=>{d||(d=!0,clearTimeout(_),a?.removeEventListener(`abort`,g),p(),e===void 0?t(n):r(e))},h=e=>(u+=Buffer.byteLength(e),u<=this.maxOutputBytes||(m(Error(`Computer script output exceeded the size limit.`)),!1)),g=()=>m(Error(`Computer script execution was cancelled.`)),_=setTimeout(()=>m(Error(`Computer script execution timed out.`)),this.timeoutMs);if(_.unref?.(),a?.addEventListener(`abort`,g,{once:!0}),a?.aborted===!0)return g();i.stdout?.on(`data`,e=>h(e.toString(`utf8`))),i.stderr?.on(`data`,e=>h(e.toString(`utf8`))),i.on(`error`,e=>m(e)),i.on(`exit`,(e,t)=>{d||m(Error(`Computer script worker exited before completion (${t??e??`unknown`}).`))}),i.on(`message`,e=>{if(!d){if(e.type===`log`&&typeof e.message==`string`){let t=`[${String(e.level)}] ${e.message}`;h(t)&&l.push(t);return}if(e.type===`program`&&typeof e.id==`string`&&(e.operation===`observe`||e.operation===`act`)){f=f.then(async()=>{try{let t=e.operation===`observe`?await this.client.observe(c.signal):await this.client.act(e.payload,c.signal);i.send({type:`response`,id:e.id,value:t})}catch(t){i.send({type:`response`,id:e.id,error:t instanceof Error?t.message:String(t)})}});return}if(e.type===`failed`&&typeof e.error==`string`){m(Error(e.error));return}e.type===`complete`&&(f=f.then(async()=>{let t=e.resultJson;if(t!==void 0&&typeof t!=`string`){m(Error(`Computer script returned an invalid result.`));return}if(typeof t==`string`&&!h(t))return;let n=await this.client.observe(c.signal);m(void 0,{...typeof t==`string`?{result:JSON.parse(t)}:{},observation:n,logs:l})}).catch(e=>m(e instanceof Error?e:Error(String(e)))))}}),i.send({type:`start`,scriptPath:o,input:JSON.parse(s)})})}};export{r as ComputerScriptRunner};
|
|
45
|
+
//# sourceMappingURL=computerScriptRunner.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computerScriptRunner.mjs","names":[],"sources":["../../../src/adapters/pi/computerScriptRunner.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { realpath } from 'node:fs/promises';\nimport type { ComputerScriptExecutionResult, ComputerScriptRunnerOptions } from '../../types/computerScript.ts';\nimport type { ComputerUseAction } from '../../types/computerUse.ts';\nimport type { ComputerUseSessionClient } from './sessionApiClient.ts';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_OUTPUT_BYTES = 256 * 1024;\n\nconst WORKER_SOURCE = String.raw`\nimport { readFile } from 'node:fs/promises';\nimport { stripTypeScriptTypes } from 'node:module';\n\nlet nextRequestId = 0;\nconst pending = new Map();\nconst call = (operation, payload) => new Promise((resolve, reject) => {\n const id = String(++nextRequestId);\n pending.set(id, { resolve, reject });\n process.send({ type: 'program', id, operation, payload });\n});\nprocess.on('message', async (message) => {\n if (message?.type === 'response') {\n const request = pending.get(message.id);\n if (!request) return;\n pending.delete(message.id);\n if (message.error) request.reject(new Error(message.error));\n else request.resolve(message.value);\n return;\n }\n if (message?.type !== 'start') return;\n const controller = new AbortController();\n process.once('SIGTERM', () => controller.abort());\n const logger = Object.fromEntries(['debug', 'info', 'warn', 'error'].map((level) => [level, (value) => {\n process.send({ type: 'log', level, message: String(value) });\n }]));\n try {\n const source = await readFile(message.scriptPath, 'utf8');\n const compiled = stripTypeScriptTypes(source, { mode: 'strip' });\n const moduleUrl = 'data:text/javascript;base64,' + Buffer.from(compiled).toString('base64');\n const loaded = await import(moduleUrl);\n if (typeof loaded.run !== 'function') throw new Error('Computer script must export a named run function.');\n const program = {\n observe: () => call('observe'),\n act: (action) => call('act', action),\n };\n const result = await loaded.run({ context: { program }, input: message.input, logger, signal: controller.signal });\n const resultJson = result === undefined ? undefined : JSON.stringify(result);\n process.send({ type: 'complete', resultJson });\n } catch (error) {\n process.send({ type: 'failed', error: error instanceof Error ? error.message : String(error) });\n }\n});\n`;\n\ninterface WorkerMessage {\n readonly type?: unknown;\n readonly id?: unknown;\n readonly operation?: unknown;\n readonly payload?: unknown;\n readonly level?: unknown;\n readonly message?: unknown;\n readonly resultJson?: unknown;\n readonly error?: unknown;\n}\n\nexport class ComputerScriptRunner {\n private readonly client: ComputerUseSessionClient;\n private readonly allowedScriptPaths: readonly string[];\n private readonly timeoutMs: number;\n private readonly maxOutputBytes: number;\n\n public constructor(options: ComputerScriptRunnerOptions) {\n this.client = options.client;\n this.allowedScriptPaths = options.allowedScriptPaths;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;\n }\n\n public async execute(\n scriptPath: string,\n input: unknown,\n signal?: AbortSignal,\n ): Promise<ComputerScriptExecutionResult> {\n const resolvedPath = await realpath(scriptPath);\n const allowedPaths = await Promise.all(\n this.allowedScriptPaths.map(async (candidate) => realpath(candidate).catch(() => '')),\n );\n if (!allowedPaths.includes(resolvedPath)) throw new Error('Computer script path is not explicitly allowed.');\n if (!resolvedPath.endsWith('.ts')) throw new Error('Computer scripts must be TypeScript files.');\n\n const serializedInput = JSON.stringify(input);\n if (serializedInput === undefined) throw new Error('Computer script input must be JSON serializable.');\n if (Buffer.byteLength(serializedInput) > this.maxOutputBytes)\n throw new Error('Computer script input exceeds the size limit.');\n\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, ['--input-type=module', '--eval', WORKER_SOURCE], {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n const controller = new AbortController();\n const logs: string[] = [];\n let outputBytes = 0;\n let settled = false;\n let operation = Promise.resolve();\n\n const terminate = (): void => {\n controller.abort();\n child.kill('SIGTERM');\n const killTimer = setTimeout(() => child.kill('SIGKILL'), 250);\n killTimer.unref?.();\n };\n const finish = (error?: Error, value?: ComputerScriptExecutionResult): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeout);\n signal?.removeEventListener('abort', onAbort);\n terminate();\n if (error !== undefined) reject(error);\n else resolve(value as ComputerScriptExecutionResult);\n };\n const addOutput = (value: string): boolean => {\n outputBytes += Buffer.byteLength(value);\n if (outputBytes <= this.maxOutputBytes) return true;\n finish(new Error('Computer script output exceeded the size limit.'));\n return false;\n };\n const onAbort = () => finish(new Error('Computer script execution was cancelled.'));\n const timeout = setTimeout(() => finish(new Error('Computer script execution timed out.')), this.timeoutMs);\n timeout.unref?.();\n signal?.addEventListener('abort', onAbort, { once: true });\n if (signal?.aborted === true) return onAbort();\n\n child.stdout?.on('data', (chunk: Buffer) => addOutput(chunk.toString('utf8')));\n child.stderr?.on('data', (chunk: Buffer) => addOutput(chunk.toString('utf8')));\n child.on('error', (error) => finish(error));\n child.on('exit', (code, exitSignal) => {\n if (!settled)\n finish(new Error(`Computer script worker exited before completion (${exitSignal ?? code ?? 'unknown'}).`));\n });\n child.on('message', (rawMessage: WorkerMessage) => {\n if (settled) return;\n if (rawMessage.type === 'log' && typeof rawMessage.message === 'string') {\n const line = `[${String(rawMessage.level)}] ${rawMessage.message}`;\n if (addOutput(line)) logs.push(line);\n return;\n }\n if (\n rawMessage.type === 'program' &&\n typeof rawMessage.id === 'string' &&\n (rawMessage.operation === 'observe' || rawMessage.operation === 'act')\n ) {\n operation = operation.then(async () => {\n try {\n const value =\n rawMessage.operation === 'observe'\n ? await this.client.observe(controller.signal)\n : await this.client.act(rawMessage.payload as ComputerUseAction, controller.signal);\n child.send({ type: 'response', id: rawMessage.id, value });\n } catch (error) {\n child.send({\n type: 'response',\n id: rawMessage.id,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n });\n return;\n }\n if (rawMessage.type === 'failed' && typeof rawMessage.error === 'string') {\n finish(new Error(rawMessage.error));\n return;\n }\n if (rawMessage.type === 'complete') {\n operation = operation\n .then(async () => {\n const resultJson = rawMessage.resultJson;\n if (resultJson !== undefined && typeof resultJson !== 'string') {\n finish(new Error('Computer script returned an invalid result.'));\n return;\n }\n if (typeof resultJson === 'string' && !addOutput(resultJson)) return;\n const observation = await this.client.observe(controller.signal);\n finish(undefined, {\n ...(typeof resultJson === 'string' ? { result: JSON.parse(resultJson) as unknown } : {}),\n observation,\n logs,\n });\n })\n .catch((error: unknown) => finish(error instanceof Error ? error : new Error(String(error))));\n }\n });\n child.send({ type: 'start', scriptPath: resolvedPath, input: JSON.parse(serializedInput) as unknown });\n });\n }\n}\n"],"mappings":"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"}
|