@zocomputer/agent-sdk 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +673 -0
  3. package/dist/attachments.js +52 -0
  4. package/dist/gateway-fetch.js +67 -0
  5. package/dist/index.js +4169 -0
  6. package/dist/initiator-auth.js +49 -0
  7. package/dist/platform/agent-sandbox/index.js +691 -0
  8. package/dist/platform/cloud-tools/image.js +498 -0
  9. package/dist/platform/cloud-tools/index.js +507 -0
  10. package/dist/platform/cloud-tools/web-search.js +87 -0
  11. package/dist/platform/runtime-ai/gateway.js +87 -0
  12. package/dist/platform/runtime-ai/index.js +91 -0
  13. package/dist/platform/runtime-ai/register.js +88 -0
  14. package/dist/platform/runtime-ai/session-fetch.js +54 -0
  15. package/dist/platform/runtime-auth/index.js +141 -0
  16. package/dist/state-files.js +287 -0
  17. package/dist/state-sandbox.js +383 -0
  18. package/dist/state.js +29 -0
  19. package/dist/steer-inbox.js +84 -0
  20. package/dist/steer.js +83 -0
  21. package/package.json +143 -0
  22. package/platform/agent-sandbox/api-client.ts +196 -0
  23. package/platform/agent-sandbox/index.ts +27 -0
  24. package/platform/agent-sandbox/pure.ts +83 -0
  25. package/platform/agent-sandbox/sftp.ts +141 -0
  26. package/platform/agent-sandbox/ssh-connection.ts +165 -0
  27. package/platform/agent-sandbox/ssh-exec.ts +98 -0
  28. package/platform/agent-sandbox/ssh-session.ts +487 -0
  29. package/platform/agent-sandbox/zo-backend.ts +88 -0
  30. package/platform/agent-sandbox/zo-sandbox.ts +39 -0
  31. package/platform/cloud-tools/image-path.ts +44 -0
  32. package/platform/cloud-tools/image.ts +225 -0
  33. package/platform/cloud-tools/index.ts +22 -0
  34. package/platform/cloud-tools/state-files.ts +368 -0
  35. package/platform/cloud-tools/tool-meta.ts +32 -0
  36. package/platform/cloud-tools/web-search.ts +15 -0
  37. package/platform/runtime-ai/gateway.ts +76 -0
  38. package/platform/runtime-ai/index.ts +20 -0
  39. package/platform/runtime-ai/register.ts +26 -0
  40. package/platform/runtime-ai/session-fetch.ts +124 -0
  41. package/platform/runtime-auth/index.ts +331 -0
  42. package/src/async-tasks.ts +273 -0
  43. package/src/attachments.ts +109 -0
  44. package/src/backgroundable.ts +88 -0
  45. package/src/bounded-output.ts +159 -0
  46. package/src/dir-conventions.ts +238 -0
  47. package/src/extract/cache.ts +40 -0
  48. package/src/extract/docx.ts +18 -0
  49. package/src/extract/pdf.ts +54 -0
  50. package/src/extract/sheet.ts +56 -0
  51. package/src/file-kind.ts +258 -0
  52. package/src/file-view.ts +80 -0
  53. package/src/gateway-fetch.ts +115 -0
  54. package/src/glob-match.ts +13 -0
  55. package/src/hooks.ts +213 -0
  56. package/src/index.ts +419 -0
  57. package/src/initiator-auth.ts +81 -0
  58. package/src/instructions.ts +224 -0
  59. package/src/list-files.ts +41 -0
  60. package/src/mock-model.ts +572 -0
  61. package/src/park-delivery.ts +247 -0
  62. package/src/path-locks.ts +52 -0
  63. package/src/read-file-content.ts +142 -0
  64. package/src/read-text.ts +40 -0
  65. package/src/redeliver.ts +155 -0
  66. package/src/run.ts +159 -0
  67. package/src/sandbox-io.ts +414 -0
  68. package/src/state-files.ts +460 -0
  69. package/src/state-sandbox.ts +710 -0
  70. package/src/state.ts +96 -0
  71. package/src/steer-inbox.ts +105 -0
  72. package/src/steer-tool.ts +83 -0
  73. package/src/steer.ts +146 -0
  74. package/src/task.ts +320 -0
  75. package/src/tools/bash.ts +143 -0
  76. package/src/tools/edit.ts +58 -0
  77. package/src/tools/glob.ts +56 -0
  78. package/src/tools/grep.ts +188 -0
  79. package/src/tools/read.ts +319 -0
  80. package/src/tools/tasks.ts +241 -0
  81. package/src/tools/webfetch.ts +368 -0
  82. package/src/tools/write.ts +42 -0
  83. package/src/walk.ts +112 -0
  84. package/src/watch-output.ts +104 -0
  85. package/src/web-fetch.ts +324 -0
  86. package/src/web-page.ts +179 -0
  87. package/src/workspace-io.ts +225 -0
  88. package/src/workspace.ts +41 -0
@@ -0,0 +1,460 @@
1
+ /** Runtime client contract for the external-state `files` interface. */
2
+
3
+ export type StateFilesAccess = "r" | "rw";
4
+ export type StateFilesInterface = "files";
5
+ export type StateFilesEngine = "zo-blob-r2";
6
+ export type StateFilesPartition = "none" | "team" | "user" | "session";
7
+
8
+ export const STATE_FILES_HANDLE_PATH = "/state/handles";
9
+ export const ZO_AGENT_TOKEN_HEADER = "x-zo-agent-token";
10
+ export const ZO_EVE_SESSION_HEADER = "x-zo-eve-session";
11
+
12
+ export interface StateFilesCredentials {
13
+ readonly accessKeyId: string;
14
+ readonly secretAccessKey: string;
15
+ readonly sessionToken: string;
16
+ readonly expiresAt: string;
17
+ }
18
+
19
+ /**
20
+ * Bearer-secret handle for direct object-store access.
21
+ *
22
+ * `handleId`, `storeId`, `stateInstanceId`, `bucketName`, and `partition` are safe to log for
23
+ * debugging. Never log the full object: `credentials.secretAccessKey` and
24
+ * `credentials.sessionToken` grant temporary bucket access.
25
+ */
26
+ export interface StateFilesHandle {
27
+ readonly handleId: string;
28
+ readonly declarationName: string;
29
+ readonly interface: StateFilesInterface;
30
+ readonly access: StateFilesAccess;
31
+ readonly engine: StateFilesEngine;
32
+ readonly storeId: string;
33
+ readonly stateInstanceId: string;
34
+ readonly partition: StateFilesPartition;
35
+ readonly bucketName: string;
36
+ readonly endpoint: string;
37
+ readonly credentials: StateFilesCredentials;
38
+ }
39
+
40
+ export interface StateFilesHandleRequest {
41
+ readonly declarationName: string;
42
+ readonly interface: StateFilesInterface;
43
+ readonly access: StateFilesAccess;
44
+ }
45
+
46
+ export interface StateFilesHandleFetch {
47
+ (input: string | URL | Request, init?: RequestInit): Promise<Response>;
48
+ }
49
+
50
+ export type StateFilesHeadersInit =
51
+ | Headers
52
+ | ReadonlyArray<readonly [string, string]>
53
+ | Readonly<Record<string, string>>;
54
+
55
+ export interface RequestStateFilesHandleOptions {
56
+ readonly fetch: StateFilesHandleFetch;
57
+ readonly apiBaseUrl: string | URL;
58
+ readonly declarationName: string;
59
+ readonly access: StateFilesAccess;
60
+ /** Agent bearer token sent to the runtime broker as `x-zo-agent-token`. */
61
+ readonly agentToken?: string;
62
+ /** eve session key sent as `x-zo-eve-session`; the route derives resolver session identity from auth context. */
63
+ readonly eveSessionKey?: string;
64
+ /** Extra headers; cannot override the SDK-managed content type or Zo auth headers. */
65
+ readonly headers?: StateFilesHeadersInit;
66
+ }
67
+
68
+ export class StateFilesHandleError extends Error {
69
+ readonly status: number;
70
+ readonly code: string | null;
71
+
72
+ constructor(message: string, options: { status: number; code?: string | null }) {
73
+ super(message);
74
+ this.name = "StateFilesHandleError";
75
+ this.status = options.status;
76
+ this.code = options.code ?? null;
77
+ }
78
+ }
79
+
80
+ export async function requestStateFilesHandle(
81
+ options: RequestStateFilesHandleOptions,
82
+ ): Promise<StateFilesHandle> {
83
+ const response = await options.fetch(buildStateFilesHandleUrl(options.apiBaseUrl), {
84
+ method: "POST",
85
+ headers: buildStateFilesHandleHeaders(options),
86
+ body: JSON.stringify(buildStateFilesHandleRequest(options)),
87
+ });
88
+ const json: unknown = await response.json().catch(() => null);
89
+ if (!response.ok) {
90
+ const error = parseStateFilesBrokerError(json);
91
+ throw new StateFilesHandleError(error.message, {
92
+ status: response.status,
93
+ code: error.code,
94
+ });
95
+ }
96
+ const handle = parseStateFilesHandle(json);
97
+ if (handle === null) {
98
+ throw new StateFilesHandleError("state files broker returned a malformed handle", {
99
+ status: response.status,
100
+ code: "malformed_handle",
101
+ });
102
+ }
103
+ return handle;
104
+ }
105
+
106
+ export function parseStateFilesHandle(value: unknown): StateFilesHandle | null {
107
+ if (!isRecord(value)) {
108
+ return null;
109
+ }
110
+ const credentials = parseStateFilesCredentials(value.credentials);
111
+ if (credentials === null) {
112
+ return null;
113
+ }
114
+ const access = parseStateFilesAccess(value.access);
115
+ if (access === null) {
116
+ return null;
117
+ }
118
+ if (value.interface !== "files" || value.engine !== "zo-blob-r2") {
119
+ return null;
120
+ }
121
+ const handleId = readString(value, "handleId");
122
+ const declarationName = readString(value, "declarationName");
123
+ const storeId = readString(value, "storeId");
124
+ const stateInstanceId = readString(value, "stateInstanceId");
125
+ const partition = parseStateFilesPartition(value.partition);
126
+ const bucketName = readString(value, "bucketName");
127
+ const endpoint = readString(value, "endpoint");
128
+ if (
129
+ handleId === null ||
130
+ declarationName === null ||
131
+ storeId === null ||
132
+ stateInstanceId === null ||
133
+ partition === null ||
134
+ bucketName === null ||
135
+ endpoint === null
136
+ ) {
137
+ return null;
138
+ }
139
+ return Object.freeze({
140
+ handleId,
141
+ declarationName,
142
+ interface: "files",
143
+ access,
144
+ engine: "zo-blob-r2",
145
+ storeId,
146
+ stateInstanceId,
147
+ partition,
148
+ bucketName,
149
+ endpoint,
150
+ credentials,
151
+ });
152
+ }
153
+
154
+ export interface StateFilesObject {
155
+ readonly key: string;
156
+ readonly size?: number;
157
+ readonly etag?: string;
158
+ readonly lastModified?: Date;
159
+ }
160
+
161
+ export type StateFilesBody = string | Uint8Array | ArrayBuffer | Blob;
162
+
163
+ export interface StateFilesS3ListInput {
164
+ readonly endpoint: string;
165
+ readonly bucketName: string;
166
+ readonly credentials: StateFilesCredentials;
167
+ readonly prefix?: string;
168
+ }
169
+
170
+ export interface StateFilesS3ReadInput {
171
+ readonly endpoint: string;
172
+ readonly bucketName: string;
173
+ readonly credentials: StateFilesCredentials;
174
+ readonly key: string;
175
+ }
176
+
177
+ export interface StateFilesS3WriteInput {
178
+ readonly endpoint: string;
179
+ readonly bucketName: string;
180
+ readonly credentials: StateFilesCredentials;
181
+ readonly key: string;
182
+ readonly body: StateFilesBody;
183
+ readonly contentType?: string;
184
+ }
185
+
186
+ export interface StateFilesS3DeleteInput {
187
+ readonly endpoint: string;
188
+ readonly bucketName: string;
189
+ readonly credentials: StateFilesCredentials;
190
+ readonly key: string;
191
+ }
192
+
193
+ export interface StateFilesS3Client {
194
+ listObjects(input: StateFilesS3ListInput): Promise<readonly StateFilesObject[]>;
195
+ readObject(input: StateFilesS3ReadInput): Promise<Uint8Array>;
196
+ writeObject(input: StateFilesS3WriteInput): Promise<void>;
197
+ deleteObject(input: StateFilesS3DeleteInput): Promise<void>;
198
+ }
199
+
200
+ export interface StateFilesWriteOptions {
201
+ readonly contentType?: string;
202
+ }
203
+
204
+ export interface StateFilesClient {
205
+ list(prefix?: string): Promise<readonly StateFilesObject[]>;
206
+ read(path: string): Promise<Uint8Array>;
207
+ write(path: string, body: StateFilesBody, options?: StateFilesWriteOptions): Promise<void>;
208
+ delete(path: string): Promise<void>;
209
+ }
210
+
211
+ export interface CreateStateFilesClientOptions {
212
+ readonly handle: StateFilesHandle;
213
+ readonly s3: StateFilesS3Client;
214
+ }
215
+
216
+ export function createStateFilesClient(options: CreateStateFilesClientOptions): StateFilesClient {
217
+ return createStateFilesClientFromHandleSource({
218
+ getHandle: async () => options.handle,
219
+ s3: options.s3,
220
+ });
221
+ }
222
+
223
+ export interface CreateRefreshingStateFilesClientOptions {
224
+ readonly loadHandle: () => Promise<StateFilesHandle>;
225
+ readonly s3: StateFilesS3Client;
226
+ readonly now?: () => Date;
227
+ /** Reload when the handle expires within this window. Defaults to 60 seconds. */
228
+ readonly refreshWindowMs?: number;
229
+ }
230
+
231
+ export function createRefreshingStateFilesClient(
232
+ options: CreateRefreshingStateFilesClientOptions,
233
+ ): StateFilesClient {
234
+ let cached: StateFilesHandle | null = null;
235
+ const now = options.now ?? (() => new Date());
236
+ const refreshWindowMs = options.refreshWindowMs ?? 60_000;
237
+ return createStateFilesClientFromHandleSource({
238
+ getHandle: async () => {
239
+ if (cached === null || shouldRefreshStateFilesHandle(cached, now(), refreshWindowMs)) {
240
+ cached = await options.loadHandle();
241
+ }
242
+ return cached;
243
+ },
244
+ s3: options.s3,
245
+ });
246
+ }
247
+
248
+ export function shouldRefreshStateFilesHandle(
249
+ handle: StateFilesHandle,
250
+ now: Date,
251
+ refreshWindowMs = 60_000,
252
+ ): boolean {
253
+ const expiresAtMs = Date.parse(handle.credentials.expiresAt);
254
+ if (!Number.isFinite(expiresAtMs)) {
255
+ return true;
256
+ }
257
+ return expiresAtMs - now.getTime() <= refreshWindowMs;
258
+ }
259
+
260
+ function createStateFilesClientFromHandleSource(options: {
261
+ readonly getHandle: () => Promise<StateFilesHandle>;
262
+ readonly s3: StateFilesS3Client;
263
+ }): StateFilesClient {
264
+ return {
265
+ async list(prefix?: string) {
266
+ const handle = await options.getHandle();
267
+ const normalizedPrefix = normalizeStateFilePrefix(prefix);
268
+ return options.s3.listObjects({
269
+ ...buildS3Context(handle),
270
+ ...(normalizedPrefix === undefined ? {} : { prefix: normalizedPrefix }),
271
+ });
272
+ },
273
+ async read(path: string) {
274
+ const handle = await options.getHandle();
275
+ return options.s3.readObject({
276
+ ...buildS3Context(handle),
277
+ key: normalizeStateFilePath(path),
278
+ });
279
+ },
280
+ async write(path: string, body: StateFilesBody, writeOptions?: StateFilesWriteOptions) {
281
+ const handle = await options.getHandle();
282
+ assertStateFilesWriteAccess(handle);
283
+ return options.s3.writeObject({
284
+ ...buildS3Context(handle),
285
+ key: normalizeStateFilePath(path),
286
+ body,
287
+ ...(writeOptions?.contentType === undefined
288
+ ? {}
289
+ : { contentType: writeOptions.contentType }),
290
+ });
291
+ },
292
+ async delete(path: string) {
293
+ const handle = await options.getHandle();
294
+ assertStateFilesWriteAccess(handle);
295
+ return options.s3.deleteObject({
296
+ ...buildS3Context(handle),
297
+ key: normalizeStateFilePath(path),
298
+ });
299
+ },
300
+ };
301
+ }
302
+
303
+ export function normalizeStateFilePath(path: string): string {
304
+ if (path.length === 0) {
305
+ throw new Error("state file path must not be empty");
306
+ }
307
+ if (path.startsWith("/")) {
308
+ throw new Error(`state file path "${path}" must be relative`);
309
+ }
310
+ const segments = path.split("/");
311
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
312
+ throw new Error(`state file path "${path}" must not contain empty, . or .. segments`);
313
+ }
314
+ return path;
315
+ }
316
+
317
+ export function normalizeStateFilePrefix(prefix: string | undefined): string | undefined {
318
+ if (prefix === undefined || prefix.length === 0) {
319
+ return undefined;
320
+ }
321
+ if (prefix.endsWith("/")) {
322
+ return normalizeStateFilePath(prefix.slice(0, -1)) + "/";
323
+ }
324
+ return normalizeStateFilePath(prefix);
325
+ }
326
+
327
+ function buildStateFilesHandleRequest(
328
+ options: RequestStateFilesHandleOptions,
329
+ ): StateFilesHandleRequest {
330
+ const request: StateFilesHandleRequest = {
331
+ declarationName: options.declarationName,
332
+ interface: "files",
333
+ access: options.access,
334
+ };
335
+ return request;
336
+ }
337
+
338
+ function buildStateFilesHandleUrl(apiBaseUrl: string | URL): string {
339
+ const url = new URL(String(apiBaseUrl));
340
+ url.pathname = joinUrlPath(url.pathname, STATE_FILES_HANDLE_PATH);
341
+ url.search = "";
342
+ url.hash = "";
343
+ return url.toString();
344
+ }
345
+
346
+ function joinUrlPath(basePath: string, childPath: string): string {
347
+ const base = basePath.replace(/\/+$/, "");
348
+ const child = childPath.replace(/^\/+/, "");
349
+ return `${base}/${child}`;
350
+ }
351
+
352
+ function buildStateFilesHandleHeaders(options: RequestStateFilesHandleOptions): Headers {
353
+ const headers = createHeaders(options.headers);
354
+ headers.set("content-type", "application/json");
355
+ if (options.agentToken !== undefined) {
356
+ headers.set(ZO_AGENT_TOKEN_HEADER, options.agentToken);
357
+ }
358
+ if (options.eveSessionKey !== undefined) {
359
+ headers.set(ZO_EVE_SESSION_HEADER, options.eveSessionKey);
360
+ }
361
+ return headers;
362
+ }
363
+
364
+ function createHeaders(init: StateFilesHeadersInit | undefined): Headers {
365
+ const headers = new Headers();
366
+ if (init === undefined) {
367
+ return headers;
368
+ }
369
+ if (init instanceof Headers) {
370
+ init.forEach((value, key) => headers.set(key, value));
371
+ return headers;
372
+ }
373
+ if (Array.isArray(init)) {
374
+ for (const [key, value] of init) {
375
+ headers.set(key, value);
376
+ }
377
+ return headers;
378
+ }
379
+ for (const [key, value] of Object.entries(init)) {
380
+ headers.set(key, value);
381
+ }
382
+ return headers;
383
+ }
384
+
385
+ function parseStateFilesBrokerError(value: unknown): { message: string; code: string | null } {
386
+ if (!isRecord(value)) {
387
+ return { message: "state files broker request failed", code: null };
388
+ }
389
+ const routeError = readString(value, "error");
390
+ if (routeError !== null) {
391
+ return {
392
+ message: readString(value, "message") ?? "state files broker request failed",
393
+ code: routeError,
394
+ };
395
+ }
396
+ const error = isRecord(value.error) ? value.error : value;
397
+ const message = readString(error, "message") ?? "state files broker request failed";
398
+ const code = readString(error, "code") ?? readString(error, "error");
399
+ return { message, code };
400
+ }
401
+
402
+ function parseStateFilesCredentials(value: unknown): StateFilesCredentials | null {
403
+ if (!isRecord(value)) {
404
+ return null;
405
+ }
406
+ const accessKeyId = readString(value, "accessKeyId");
407
+ const secretAccessKey = readString(value, "secretAccessKey");
408
+ const sessionToken = readString(value, "sessionToken");
409
+ const expiresAt = readString(value, "expiresAt");
410
+ if (
411
+ accessKeyId === null ||
412
+ secretAccessKey === null ||
413
+ sessionToken === null ||
414
+ expiresAt === null ||
415
+ !Number.isFinite(Date.parse(expiresAt))
416
+ ) {
417
+ return null;
418
+ }
419
+ return Object.freeze({ accessKeyId, secretAccessKey, sessionToken, expiresAt });
420
+ }
421
+
422
+ function parseStateFilesPartition(value: unknown): StateFilesPartition | null {
423
+ if (value === "none" || value === "team" || value === "user" || value === "session") {
424
+ return value;
425
+ }
426
+ return null;
427
+ }
428
+
429
+ function parseStateFilesAccess(value: unknown): StateFilesAccess | null {
430
+ if (value === "r" || value === "rw") {
431
+ return value;
432
+ }
433
+ return null;
434
+ }
435
+
436
+ function buildS3Context(handle: StateFilesHandle): Pick<
437
+ StateFilesS3ListInput,
438
+ "endpoint" | "bucketName" | "credentials"
439
+ > {
440
+ return {
441
+ endpoint: handle.endpoint,
442
+ bucketName: handle.bucketName,
443
+ credentials: handle.credentials,
444
+ };
445
+ }
446
+
447
+ function assertStateFilesWriteAccess(handle: StateFilesHandle): void {
448
+ if (handle.access !== "rw") {
449
+ throw new Error(`state files handle "${handle.handleId}" is read-only`);
450
+ }
451
+ }
452
+
453
+ function isRecord(value: unknown): value is Record<string, unknown> {
454
+ return typeof value === "object" && value !== null && !Array.isArray(value);
455
+ }
456
+
457
+ function readString(record: Record<string, unknown>, key: string): string | null {
458
+ const value = record[key];
459
+ return typeof value === "string" && value.length > 0 ? value : null;
460
+ }