@agentproto/computer-api 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,9 @@ describe. A major bump of this package is where that constant changes.
33
33
  | POST | `/v1/computers/:id/exec` | `execRequestSchema` | `execResponseSchema` |
34
34
  | POST | `/v1/computers/:id/suspend` | - | `suspendResponseSchema` |
35
35
  | POST | `/v1/computers/:id/resume` | - | `resumeResponseSchema` |
36
- | DELETE | `/v1/computers/:id` | - | `suspendResponseSchema` shape (all-optional) |
36
+ | POST | `/v1/computers/:id/attach` | - | `sessionsResponseSchema` |
37
+ | POST | `/v1/computers/:id/detach` | - | `sessionsResponseSchema` |
38
+ | DELETE | `/v1/computers/:id` | - (`?force=true` optional) | `suspendResponseSchema` shape (all-optional) |
37
39
  | any | error body | - | `errorResponseSchema` |
38
40
 
39
41
  ## Contract tests
@@ -45,6 +47,10 @@ same schemas, so the test double cannot drift from the published contract.
45
47
 
46
48
  ## Notes
47
49
 
50
+ - Sessions: `maxSessions` on create sizes the computer for that many
51
+ concurrent sessions. `attach` takes a slot (409 at the cap), `detach`
52
+ releases one (floors at 0), and `DELETE` answers 409 while sessions are
53
+ attached unless `?force=true` is passed.
48
54
  - `resume` MAY rename the computer: the controller can restore the suspend
49
55
  snapshot into a fresh computer under a NEW generated id and answer
50
56
  `{ id: <new>, state: "running" }`. Callers must treat the old id as gone
package/dist/index.d.ts CHANGED
@@ -38,7 +38,9 @@ declare const COMPUTER_STATE_UNKNOWN: ComputerState;
38
38
  * POST /v1/computers/:id/exec synchronous exec of an argv array
39
39
  * POST /v1/computers/:id/suspend full snapshot + stop
40
40
  * POST /v1/computers/:id/resume restore into a FRESH computer
41
- * DELETE /v1/computers/:id rm + snapshot cleanup
41
+ * POST /v1/computers/:id/attach take a session slot
42
+ * POST /v1/computers/:id/detach release a session slot
43
+ * DELETE /v1/computers/:id rm + snapshot cleanup (?force=true skips the session check)
42
44
  *
43
45
  * These are deliberately MINIMAL: only the fields a client actually
44
46
  * consumes are declared, and every schema allows (and preserves) extra
@@ -81,6 +83,7 @@ declare const createComputerRequestSchema: z.ZodObject<{
81
83
  memoryMiB: z.ZodOptional<z.ZodNumber>;
82
84
  rootDiskMiB: z.ZodOptional<z.ZodNumber>;
83
85
  ttl: z.ZodOptional<z.ZodNumber>;
86
+ maxSessions: z.ZodOptional<z.ZodNumber>;
84
87
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
85
88
  egress: z.ZodOptional<z.ZodObject<{
86
89
  allow: z.ZodArray<z.ZodObject<{
@@ -150,5 +153,18 @@ declare const resumeResponseSchema: z.ZodObject<{
150
153
  state: z.ZodOptional<z.ZodString>;
151
154
  }, z.core.$loose>;
152
155
  type ResumeResponse = z.infer<typeof resumeResponseSchema>;
156
+ /**
157
+ * 200 response body of `POST /v1/computers/:id/attach` and `.../detach`:
158
+ * the controller-held session count after the call and the cap the computer
159
+ * was sized for. Neither route takes a body. Attach answers 409 when the
160
+ * computer already hosts `maxSessions` sessions; detach floors at 0.
161
+ * `DELETE /v1/computers/:id` answers 409 while sessions are attached unless
162
+ * the `force=true` query parameter is passed.
163
+ */
164
+ declare const sessionsResponseSchema: z.ZodObject<{
165
+ sessions: z.ZodOptional<z.ZodNumber>;
166
+ maxSessions: z.ZodOptional<z.ZodNumber>;
167
+ }, z.core.$loose>;
168
+ type SessionsResponse = z.infer<typeof sessionsResponseSchema>;
153
169
 
154
- export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, type ComputerState, type ComputerStatus, type CreateComputerRequest, type CreateComputerResponse, type EgressRule, type ErrorResponse, type ExecRequest, type ExecResponse, type PortPublish, type ResumeResponse, type SuspendResponse, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, suspendResponseSchema };
170
+ export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, type ComputerState, type ComputerStatus, type CreateComputerRequest, type CreateComputerResponse, type EgressRule, type ErrorResponse, type ExecRequest, type ExecResponse, type PortPublish, type ResumeResponse, type SessionsResponse, type SuspendResponse, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
package/dist/index.mjs CHANGED
@@ -44,6 +44,8 @@ var createComputerRequestSchema = z.object({
44
44
  rootDiskMiB: z.number().int().optional(),
45
45
  /** Computer TTL seconds. */
46
46
  ttl: z.number().int().optional(),
47
+ /** Concurrent sessions the computer is sized for (controller default 1). */
48
+ maxSessions: z.number().int().optional(),
47
49
  /** Environment variables injected into the computer. */
48
50
  env: z.record(z.string(), z.string()).optional(),
49
51
  /** Deny-by-default egress allowlist; a computer created without one has no DNS. */
@@ -82,7 +84,11 @@ var resumeResponseSchema = z.object({
82
84
  id: z.string().optional(),
83
85
  state: z.string().optional()
84
86
  }).passthrough();
87
+ var sessionsResponseSchema = z.object({
88
+ sessions: z.number().int().optional(),
89
+ maxSessions: z.number().int().optional()
90
+ }).passthrough();
85
91
 
86
- export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, suspendResponseSchema };
92
+ export { COMPUTER_API_VERSION, COMPUTER_STATES, COMPUTER_STATE_UNKNOWN, computerStateSchema, computerStatusSchema, createComputerRequestSchema, createComputerResponseSchema, egressRuleSchema, errorResponseSchema, execRequestSchema, execResponseSchema, portPublishSchema, resumeResponseSchema, sessionsResponseSchema, suspendResponseSchema };
87
93
  //# sourceMappingURL=index.mjs.map
88
94
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts","../src/computer-states.ts","../src/schemas.ts"],"names":["z"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Version of the computer controller HTTP contract described by this\n * package. The controller routes live under this version prefix (for\n * example `POST /v1/computers`), and every payload schema here corresponds\n * to that version.\n *\n * Semver of the PACKAGE tracks this contract: a MAJOR bump of\n * `@agentproto/computer-api` is where this constant changes.\n */\nexport const COMPUTER_API_VERSION = \"v1\"\n","/**\n * Driver-level computer states, as reported by `GET /v1/computers/:id`.\n *\n * The controller itself never reports `suspended`: a suspended computer\n * reads back as `stopped`. The state is kept here as a tolerated alias so\n * a newer controller can report it without breaking an older client.\n */\nexport const COMPUTER_STATES = [\n \"running\",\n \"stopped\",\n \"paused\",\n \"crashed\",\n \"starting\",\n \"unknown\",\n \"missing\",\n \"suspended\",\n] as const\n\nexport type ComputerState = (typeof COMPUTER_STATES)[number]\n\nimport { z } from \"zod\"\n\n/**\n * The state field is READ-tolerant: any unknown future state is bucketed\n * under `\"unknown\"` instead of rejecting, so a status read never throws on\n * controller evolution.\n */\nexport const computerStateSchema = z.string().transform((v): ComputerState => {\n return (COMPUTER_STATES as readonly string[]).includes(v) ? (v as ComputerState) : \"unknown\"\n})\n\nexport const COMPUTER_STATE_UNKNOWN: ComputerState = \"unknown\"\n","/**\n * Zod schemas for the computer controller's HTTP contract constants -\n * the generic, deployment-independent shape of every endpoint:\n *\n * POST /v1/computers create\n * GET /v1/computers/:id status\n * POST /v1/computers/:id/exec synchronous exec of an argv array\n * POST /v1/computers/:id/suspend full snapshot + stop\n * POST /v1/computers/:id/resume restore into a FRESH computer\n * DELETE /v1/computers/:id rm + snapshot cleanup\n *\n * These are deliberately MINIMAL: only the fields a client actually\n * consumes are declared, and every schema allows (and preserves) extra\n * fields so a controller version adding fields never breaks an older\n * client. Template ids are an OPEN string - any registered id is valid.\n *\n * Nothing deployment-specific belongs here: no host names, no IPs, no\n * tenant names, no auth realm details. Auth headers are wired by the\n * client, not the contract.\n */\n\nimport { z } from \"zod\"\nimport { computerStateSchema } from \"./computer-states.js\"\n\n// ── transport pieces ──────────────────────────────────────────────────\n\n/** A published port: the host-side port mapped to the guest-side port. */\nexport const portPublishSchema = z.object({\n host: z.number().int(),\n guest: z.number().int(),\n})\nexport type PortPublish = z.infer<typeof portPublishSchema>\n\n/** Egress allowlist entry in the create body: destination host + port. */\nexport const egressRuleSchema = z.object({\n host: z.string(),\n port: z.number().int(),\n})\nexport type EgressRule = z.infer<typeof egressRuleSchema>\n\n/**\n * Controller error bodies are always `{ error: string }`, sometimes with a\n * `detail` array of validation issues. The `code` strings a controller may\n * emit (for example unknown template, capacity) are contract-OPEN: clients\n * inspect but never switch exhaustively on them.\n */\nexport const errorResponseSchema = z.object({\n error: z.string(),\n detail: z.unknown().optional(),\n}).passthrough()\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>\n\n// ── POST /v1/computers ────────────────────────────────────────────────\n\n/** Request body for `POST /v1/computers`. */\nexport const createComputerRequestSchema = z.object({\n /** Registered template id (OPEN string; a controller rejects unknown ids). */\n template: z.string(),\n /** Tenant the computer is created under (scope marker, not a name). */\n tenantId: z.string(),\n cpus: z.number().int().optional(),\n memoryMiB: z.number().int().optional(),\n rootDiskMiB: z.number().int().optional(),\n /** Computer TTL seconds. */\n ttl: z.number().int().optional(),\n /** Environment variables injected into the computer. */\n env: z.record(z.string(), z.string()).optional(),\n /** Deny-by-default egress allowlist; a computer created without one has no DNS. */\n egress: z.object({ allow: z.array(egressRuleSchema) }).optional(),\n}).passthrough()\nexport type CreateComputerRequest = z.infer<typeof createComputerRequestSchema>\n\n/** 201 response body of `POST /v1/computers`. */\nexport const createComputerResponseSchema = z.object({\n id: z.string(),\n template: z.string(),\n state: z.string(),\n ports: z.array(portPublishSchema).optional(),\n}).passthrough()\nexport type CreateComputerResponse = z.infer<typeof createComputerResponseSchema>\n\n// ── GET /v1/computers/:id ─────────────────────────────────────────────\n\n/**\n * Status response of `GET /v1/computers/:id` (status + published ports and\n * any egress/template metadata; extras preserved). The state is parsed\n * READ-tOLERANTLY: unknown future states bucket under `\"unknown\"`.\n */\nexport const computerStatusSchema = z.object({\n name: z.string(),\n state: computerStateSchema,\n ports: z.array(portPublishSchema).optional(),\n template: z.string().optional(),\n tenantId: z.string().optional(),\n}).passthrough()\nexport type ComputerStatus = z.infer<typeof computerStatusSchema>\n\n// ── POST /v1/computers/:id/exec ───────────────────────────────────────\n\n/** Request body of `POST /v1/computers/:id/exec` - synchronous argv exec. */\nexport const execRequestSchema = z.object({\n argv: z.array(z.string()),\n /** Per-exec timeout; controllers cap this (for example at 600 seconds). */\n timeoutSec: z.number().int(),\n}).passthrough()\nexport type ExecRequest = z.infer<typeof execRequestSchema>\n\n/** 200 response body of `POST /v1/computers/:id/exec`. Controllers may\n * redact registered secret values from the streams and cap their length. */\nexport const execResponseSchema = z.object({\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n}).passthrough()\nexport type ExecResponse = z.infer<typeof execResponseSchema>\n\n// ── POST /v1/computers/:id/suspend ────────────────────────────────────\n\n/** 200 response body of `POST /v1/computers/:id/suspend`. */\nexport const suspendResponseSchema = z.object({\n ok: z.boolean().optional(),\n snapshot: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type SuspendResponse = z.infer<typeof suspendResponseSchema>\n\n// ── POST /v1/computers/:id/resume ─────────────────────────────────────\n\n/**\n * Response of `POST /v1/computers/:id/resume`. MAY rename the computer:\n * the controller can restore the suspend snapshot into a FRESH computer\n * under a NEW generated id and answer `{ id: <new>, state: \"running\" }`.\n * Callers must treat the old id as gone and adopt the returned id.\n */\nexport const resumeResponseSchema = z.object({\n ok: z.boolean().optional(),\n id: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type ResumeResponse = z.infer<typeof resumeResponseSchema>\n"]}
1
+ {"version":3,"sources":["../src/version.ts","../src/computer-states.ts","../src/schemas.ts"],"names":["z"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Version of the computer controller HTTP contract described by this\n * package. The controller routes live under this version prefix (for\n * example `POST /v1/computers`), and every payload schema here corresponds\n * to that version.\n *\n * Semver of the PACKAGE tracks this contract: a MAJOR bump of\n * `@agentproto/computer-api` is where this constant changes.\n */\nexport const COMPUTER_API_VERSION = \"v1\"\n","/**\n * Driver-level computer states, as reported by `GET /v1/computers/:id`.\n *\n * The controller itself never reports `suspended`: a suspended computer\n * reads back as `stopped`. The state is kept here as a tolerated alias so\n * a newer controller can report it without breaking an older client.\n */\nexport const COMPUTER_STATES = [\n \"running\",\n \"stopped\",\n \"paused\",\n \"crashed\",\n \"starting\",\n \"unknown\",\n \"missing\",\n \"suspended\",\n] as const\n\nexport type ComputerState = (typeof COMPUTER_STATES)[number]\n\nimport { z } from \"zod\"\n\n/**\n * The state field is READ-tolerant: any unknown future state is bucketed\n * under `\"unknown\"` instead of rejecting, so a status read never throws on\n * controller evolution.\n */\nexport const computerStateSchema = z.string().transform((v): ComputerState => {\n return (COMPUTER_STATES as readonly string[]).includes(v) ? (v as ComputerState) : \"unknown\"\n})\n\nexport const COMPUTER_STATE_UNKNOWN: ComputerState = \"unknown\"\n","/**\n * Zod schemas for the computer controller's HTTP contract constants -\n * the generic, deployment-independent shape of every endpoint:\n *\n * POST /v1/computers create\n * GET /v1/computers/:id status\n * POST /v1/computers/:id/exec synchronous exec of an argv array\n * POST /v1/computers/:id/suspend full snapshot + stop\n * POST /v1/computers/:id/resume restore into a FRESH computer\n * POST /v1/computers/:id/attach take a session slot\n * POST /v1/computers/:id/detach release a session slot\n * DELETE /v1/computers/:id rm + snapshot cleanup (?force=true skips the session check)\n *\n * These are deliberately MINIMAL: only the fields a client actually\n * consumes are declared, and every schema allows (and preserves) extra\n * fields so a controller version adding fields never breaks an older\n * client. Template ids are an OPEN string - any registered id is valid.\n *\n * Nothing deployment-specific belongs here: no host names, no IPs, no\n * tenant names, no auth realm details. Auth headers are wired by the\n * client, not the contract.\n */\n\nimport { z } from \"zod\"\nimport { computerStateSchema } from \"./computer-states.js\"\n\n// ── transport pieces ──────────────────────────────────────────────────\n\n/** A published port: the host-side port mapped to the guest-side port. */\nexport const portPublishSchema = z.object({\n host: z.number().int(),\n guest: z.number().int(),\n})\nexport type PortPublish = z.infer<typeof portPublishSchema>\n\n/** Egress allowlist entry in the create body: destination host + port. */\nexport const egressRuleSchema = z.object({\n host: z.string(),\n port: z.number().int(),\n})\nexport type EgressRule = z.infer<typeof egressRuleSchema>\n\n/**\n * Controller error bodies are always `{ error: string }`, sometimes with a\n * `detail` array of validation issues. The `code` strings a controller may\n * emit (for example unknown template, capacity) are contract-OPEN: clients\n * inspect but never switch exhaustively on them.\n */\nexport const errorResponseSchema = z.object({\n error: z.string(),\n detail: z.unknown().optional(),\n}).passthrough()\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>\n\n// ── POST /v1/computers ────────────────────────────────────────────────\n\n/** Request body for `POST /v1/computers`. */\nexport const createComputerRequestSchema = z.object({\n /** Registered template id (OPEN string; a controller rejects unknown ids). */\n template: z.string(),\n /** Tenant the computer is created under (scope marker, not a name). */\n tenantId: z.string(),\n cpus: z.number().int().optional(),\n memoryMiB: z.number().int().optional(),\n rootDiskMiB: z.number().int().optional(),\n /** Computer TTL seconds. */\n ttl: z.number().int().optional(),\n /** Concurrent sessions the computer is sized for (controller default 1). */\n maxSessions: z.number().int().optional(),\n /** Environment variables injected into the computer. */\n env: z.record(z.string(), z.string()).optional(),\n /** Deny-by-default egress allowlist; a computer created without one has no DNS. */\n egress: z.object({ allow: z.array(egressRuleSchema) }).optional(),\n}).passthrough()\nexport type CreateComputerRequest = z.infer<typeof createComputerRequestSchema>\n\n/** 201 response body of `POST /v1/computers`. */\nexport const createComputerResponseSchema = z.object({\n id: z.string(),\n template: z.string(),\n state: z.string(),\n ports: z.array(portPublishSchema).optional(),\n}).passthrough()\nexport type CreateComputerResponse = z.infer<typeof createComputerResponseSchema>\n\n// ── GET /v1/computers/:id ─────────────────────────────────────────────\n\n/**\n * Status response of `GET /v1/computers/:id` (status + published ports and\n * any egress/template metadata; extras preserved). The state is parsed\n * READ-tOLERANTLY: unknown future states bucket under `\"unknown\"`.\n */\nexport const computerStatusSchema = z.object({\n name: z.string(),\n state: computerStateSchema,\n ports: z.array(portPublishSchema).optional(),\n template: z.string().optional(),\n tenantId: z.string().optional(),\n}).passthrough()\nexport type ComputerStatus = z.infer<typeof computerStatusSchema>\n\n// ── POST /v1/computers/:id/exec ───────────────────────────────────────\n\n/** Request body of `POST /v1/computers/:id/exec` - synchronous argv exec. */\nexport const execRequestSchema = z.object({\n argv: z.array(z.string()),\n /** Per-exec timeout; controllers cap this (for example at 600 seconds). */\n timeoutSec: z.number().int(),\n}).passthrough()\nexport type ExecRequest = z.infer<typeof execRequestSchema>\n\n/** 200 response body of `POST /v1/computers/:id/exec`. Controllers may\n * redact registered secret values from the streams and cap their length. */\nexport const execResponseSchema = z.object({\n exitCode: z.number().int(),\n stdout: z.string(),\n stderr: z.string(),\n}).passthrough()\nexport type ExecResponse = z.infer<typeof execResponseSchema>\n\n// ── POST /v1/computers/:id/suspend ────────────────────────────────────\n\n/** 200 response body of `POST /v1/computers/:id/suspend`. */\nexport const suspendResponseSchema = z.object({\n ok: z.boolean().optional(),\n snapshot: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type SuspendResponse = z.infer<typeof suspendResponseSchema>\n\n// ── POST /v1/computers/:id/resume ─────────────────────────────────────\n\n/**\n * Response of `POST /v1/computers/:id/resume`. MAY rename the computer:\n * the controller can restore the suspend snapshot into a FRESH computer\n * under a NEW generated id and answer `{ id: <new>, state: \"running\" }`.\n * Callers must treat the old id as gone and adopt the returned id.\n */\nexport const resumeResponseSchema = z.object({\n ok: z.boolean().optional(),\n id: z.string().optional(),\n state: z.string().optional(),\n}).passthrough()\nexport type ResumeResponse = z.infer<typeof resumeResponseSchema>\n\n// ── POST /v1/computers/:id/attach | detach ────────────────────────────\n\n/**\n * 200 response body of `POST /v1/computers/:id/attach` and `.../detach`:\n * the controller-held session count after the call and the cap the computer\n * was sized for. Neither route takes a body. Attach answers 409 when the\n * computer already hosts `maxSessions` sessions; detach floors at 0.\n * `DELETE /v1/computers/:id` answers 409 while sessions are attached unless\n * the `force=true` query parameter is passed.\n */\nexport const sessionsResponseSchema = z.object({\n sessions: z.number().int().optional(),\n maxSessions: z.number().int().optional(),\n}).passthrough()\nexport type SessionsResponse = z.infer<typeof sessionsResponseSchema>\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentproto/computer-api",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "@agentproto/computer-api — versioned public contract for the computer controller HTTP API: zod schemas and inferred types for create/get/exec/suspend/resume/rm of computers, published ports, status and the error envelope.",
5
5
  "keywords": [
6
6
  "agentproto",