@ct-agents/worker 0.1.8 → 0.2.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.
@@ -0,0 +1,85 @@
1
+ export const DEFAULT_WORK_LEASE_RENEW_INTERVAL_MS = 60_000;
2
+
3
+ export type LeasedItemRenewal = {
4
+ intervalMs?: number;
5
+ renew(): Promise<boolean>;
6
+ onError?: (error: unknown) => void | Promise<void>;
7
+ };
8
+
9
+ export type RunLeasedItemInput<TExecution> = {
10
+ renewal?: LeasedItemRenewal;
11
+ execute(): Promise<TExecution>;
12
+ complete(execution: TExecution): Promise<boolean>;
13
+ completeRejectedError(execution: TExecution): Error;
14
+ };
15
+
16
+ function normalizeRenewInterval(value: number | undefined) {
17
+ return typeof value === 'number' && Number.isInteger(value) && value > 0
18
+ ? value
19
+ : DEFAULT_WORK_LEASE_RENEW_INTERVAL_MS;
20
+ }
21
+
22
+ function delay(ms: number, signal: AbortSignal): Promise<void> {
23
+ if (signal.aborted) {
24
+ return Promise.resolve();
25
+ }
26
+ return new Promise((resolve) => {
27
+ const finish = () => {
28
+ clearTimeout(timer);
29
+ signal.removeEventListener('abort', finish);
30
+ resolve();
31
+ };
32
+ const timer = setTimeout(finish, ms);
33
+ signal.addEventListener('abort', finish, { once: true });
34
+ if (signal.aborted) {
35
+ finish();
36
+ }
37
+ });
38
+ }
39
+
40
+ async function reportRenewError(
41
+ onError: LeasedItemRenewal['onError'],
42
+ error: unknown,
43
+ ) {
44
+ try {
45
+ await onError?.(error);
46
+ } catch {
47
+ // 观测回调不能中断仍持有 lease 的业务执行;下一周期继续尝试 renew。
48
+ }
49
+ }
50
+
51
+ async function renewUntilStopped(renewal: LeasedItemRenewal, signal: AbortSignal) {
52
+ const intervalMs = normalizeRenewInterval(renewal.intervalMs);
53
+ while (!signal.aborted) {
54
+ await delay(intervalMs, signal);
55
+ if (signal.aborted) {
56
+ return;
57
+ }
58
+ try {
59
+ if (!await renewal.renew()) {
60
+ return;
61
+ }
62
+ } catch (error) {
63
+ await reportRenewError(renewal.onError, error);
64
+ }
65
+ }
66
+ }
67
+
68
+ /** 组合单个 leased item 的续租、执行、完成和续租协程收口。 */
69
+ export async function runLeasedItem<TExecution>(
70
+ input: RunLeasedItemInput<TExecution>,
71
+ ): Promise<void> {
72
+ const renewController = new AbortController();
73
+ const renewal = input.renewal
74
+ ? renewUntilStopped(input.renewal, renewController.signal)
75
+ : Promise.resolve();
76
+ try {
77
+ const execution = await input.execute();
78
+ if (!await input.complete(execution)) {
79
+ throw input.completeRejectedError(execution);
80
+ }
81
+ } finally {
82
+ renewController.abort();
83
+ await renewal;
84
+ }
85
+ }
@@ -0,0 +1,5 @@
1
+ export const PLATFORM_API_BASE_PATH = '/api/v1';
2
+
3
+ export function platformApiPath(path: '' | `/${string}`): string {
4
+ return `${PLATFORM_API_BASE_PATH}${path}`;
5
+ }
@@ -1,8 +1,6 @@
1
1
  import { z } from 'zod';
2
- import type {
3
- Database,
4
- ResourceDefinition,
5
- } from '@ct-agents/protocol';
2
+ import type { Database } from '@ct-agents/protocol';
3
+ import type { EnvironmentWorkerResourceImplementation } from '../../index.js';
6
4
 
7
5
  export type PostgresQueryResult = {
8
6
  rows: unknown[];
@@ -38,46 +36,37 @@ export type PostgresDatabaseOptions = {
38
36
  maxRows?: number;
39
37
  };
40
38
 
41
- export type CreatePostgresDatabaseResourceDefinitionInput = {
39
+ export type CreatePostgresDatabaseResourceImplementationInput = {
42
40
  createConnection: PostgresConnectionFactory;
43
41
  };
44
42
 
45
43
  export const postgresDatabaseOptionsSchema = z.object({
46
- connectionString: z.string().trim().min(1, 'connectionString 为必填字段'),
44
+ connectionString: z.string().trim().min(1, 'connectionString 为必填字段').meta({ secret: true }),
47
45
  allowWrite: z.boolean().optional(),
48
46
  maxRows: z.number().int().positive().optional(),
49
47
  }).strict();
50
48
 
51
- export function createPostgresDatabaseResourceDefinition(
52
- input: CreatePostgresDatabaseResourceDefinitionInput,
53
- ): ResourceDefinition<Database, PostgresDatabaseOptions> {
49
+ export function createPostgresDatabaseResourceImplementation(
50
+ input: CreatePostgresDatabaseResourceImplementationInput,
51
+ ): EnvironmentWorkerResourceImplementation<Database, PostgresDatabaseOptions> {
54
52
  return {
55
- id: 'database',
56
- title: '数据库',
57
- description: 'worker 侧 PostgreSQL database resource。',
58
- implementations: [
59
- {
60
- id: 'postgres',
61
- title: 'PostgreSQL',
62
- description: '通过 worker 本地注入的 PostgreSQL 查询函数访问数据库。',
63
- optionsSchema: postgresDatabaseOptionsSchema,
64
- optionsJsonSchema: z.toJSONSchema(postgresDatabaseOptionsSchema),
65
- factory: (options) => {
66
- const parsed = postgresDatabaseOptionsSchema.parse(options);
67
- const connection = input.createConnection(parsed.connectionString, {
68
- allowWrite: parsed.allowWrite ?? false,
69
- maxRows: parsed.maxRows,
70
- });
71
- if (!connection.withConnection) {
72
- throw new Error('PostgreSQL database resource 需要提供 withConnection,不能使用无状态 pool.query');
73
- }
74
- return createPostgresDatabase({
75
- ...parsed,
76
- connection,
77
- });
78
- },
79
- },
80
- ],
53
+ title: 'PostgreSQL',
54
+ description: '通过 worker 本地注入的 PostgreSQL 查询函数访问数据库。',
55
+ options: postgresDatabaseOptionsSchema,
56
+ factory: (options) => {
57
+ const parsed = postgresDatabaseOptionsSchema.parse(options);
58
+ const connection = input.createConnection(parsed.connectionString, {
59
+ allowWrite: parsed.allowWrite ?? false,
60
+ maxRows: parsed.maxRows,
61
+ });
62
+ if (!connection.withConnection) {
63
+ throw new Error('PostgreSQL database resource 需要提供 withConnection,不能使用无状态 pool.query');
64
+ }
65
+ return createPostgresDatabase({
66
+ ...parsed,
67
+ connection,
68
+ });
69
+ },
81
70
  };
82
71
  }
83
72
 
@@ -95,6 +84,10 @@ export function createPostgresDatabase(input: PostgresDatabaseOptions & {
95
84
  query: async (sql, params, options) => {
96
85
  const query = input.connection?.query ?? input.query;
97
86
  const withConnection = input.connection?.withConnection ?? input.withConnection;
87
+ const maxRows = resolveMaxRows(input.maxRows, options?.maxRows);
88
+ const effectiveOptions = options || maxRows !== undefined
89
+ ? { ...options, ...(maxRows !== undefined ? { maxRows } : {}) }
90
+ : undefined;
98
91
  if (!query) {
99
92
  throw new Error('PostgreSQL database resource 缺少 query 实现');
100
93
  }
@@ -102,17 +95,23 @@ export function createPostgresDatabase(input: PostgresDatabaseOptions & {
102
95
  if (input.allowWrite !== true) {
103
96
  throw new Error('当前 database resource 未允许写 SQL');
104
97
  }
105
- return query(sql, params, options);
98
+ return query(sql, params, effectiveOptions);
106
99
  }
107
100
 
108
101
  if (!withConnection) {
109
102
  throw new Error('readOnly 查询需要同连接 withConnection 实现,不能使用无状态 pool.query');
110
103
  }
111
- return withConnection(async (connection) => executeReadOnlyQuery(connection, sql, params, options.maxRows));
104
+ return withConnection(async (connection) => executeReadOnlyQuery(connection, sql, params, maxRows));
112
105
  },
113
106
  };
114
107
  }
115
108
 
109
+ function resolveMaxRows(configured: number | undefined, requested: number | undefined) {
110
+ if (configured === undefined) return requested;
111
+ if (requested === undefined) return configured;
112
+ return Math.min(configured, requested);
113
+ }
114
+
116
115
  async function executeReadOnlyQuery(
117
116
  connection: PostgresConnectionExecutor,
118
117
  sql: string,
@@ -1,4 +1,4 @@
1
- import type { Database } from '@ct-agents/protocol';
1
+ import { platformApiPath } from '../platform-api-path.js';
2
2
 
3
3
  export type PlatformResourceProxyInput = {
4
4
  baseUrl: string;
@@ -17,10 +17,6 @@ export type PlatformResourceProxyInput = {
17
17
  fetchImpl?: typeof fetch;
18
18
  };
19
19
 
20
- export type PlatformDatabaseProxyResourceInput = Omit<PlatformResourceProxyInput, 'slot'> & {
21
- slot?: string;
22
- };
23
-
24
20
  export type InvokePlatformResourceProxyInput = PlatformResourceProxyInput & {
25
21
  method: string;
26
22
  args?: unknown[] | Record<string, unknown>;
@@ -92,7 +88,10 @@ export async function invokePlatformResourceProxy(input: InvokePlatformResourceP
92
88
  const requestTimeoutMs = input.requestTimeoutMs ?? (input.timeoutMs ? input.timeoutMs + 5_000 : 35_000);
93
89
  const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
94
90
  try {
95
- const response = await fetchImpl(`${baseUrl}/v1/environments/${encodeURIComponent(input.environmentId)}/resources/${encodeURIComponent(input.slot)}/invoke`, {
91
+ const environmentId = encodeURIComponent(input.environmentId);
92
+ const slot = encodeURIComponent(input.slot);
93
+ const path = platformApiPath(`/environments/${environmentId}/resources/${slot}/invoke`);
94
+ const response = await fetchImpl(`${baseUrl}${path}`, {
96
95
  method: 'POST',
97
96
  headers,
98
97
  body: JSON.stringify(body),
@@ -101,7 +100,7 @@ export async function invokePlatformResourceProxy(input: InvokePlatformResourceP
101
100
  return await parseProxyEnvelope(response);
102
101
  } catch (error) {
103
102
  if (isAbortError(error)) {
104
- throw new Error('平台 resource proxy 请求超时');
103
+ throw new Error('平台 resource proxy 请求超时', { cause: error });
105
104
  }
106
105
  throw error;
107
106
  } finally {
@@ -138,32 +137,6 @@ export function createPlatformResourceProxy<TResource extends object>(
138
137
  }) as TResource;
139
138
  }
140
139
 
141
- /**
142
- * 创建通过平台 Resource proxy 访问的 database resource。
143
- *
144
- * worker 只持有 environment key,不持有平台内置 resource 的原始密钥。
145
- */
146
- export function createPlatformDatabaseProxyResource(input: PlatformDatabaseProxyResourceInput): Database {
147
- const slot = input.slot?.trim() || 'database';
148
-
149
- return {
150
- async query(sql, params, options) {
151
- const timeoutMs = options?.timeoutMs ?? input.timeoutMs;
152
- return invokePlatformResourceProxy({
153
- ...input,
154
- slot,
155
- method: 'query',
156
- args: {
157
- sql,
158
- ...(params !== undefined ? { params: Array.from(params) } : {}),
159
- ...(options !== undefined ? { options } : {}),
160
- },
161
- ...(timeoutMs !== undefined ? { timeoutMs } : {}),
162
- });
163
- },
164
- };
165
- }
166
-
167
140
  function isAbortError(error: unknown) {
168
141
  return error instanceof DOMException && error.name === 'AbortError';
169
142
  }
@@ -7,6 +7,7 @@ import type {
7
7
  TextGenerationResource,
8
8
  TextGenerationResult,
9
9
  } from '@ct-agents/protocol';
10
+ import { ModelOutputError, ModelProviderError } from '@ct-agents/harness';
10
11
  import { runAnthropic } from '@ct-agents/harness/llm/anthropic-provider.js';
11
12
  import type {
12
13
  NormalizedModelOutput,
@@ -115,6 +116,28 @@ function classifyProviderError(error: unknown): {
115
116
  message: string;
116
117
  retryable: boolean;
117
118
  } {
119
+ if (error instanceof ModelOutputError) {
120
+ const isCompletedEmpty = error.diagnostic.stage === 'response_completion'
121
+ && [
122
+ 'completed_empty',
123
+ 'end_turn',
124
+ 'stop_sequence',
125
+ 'tool_use',
126
+ 'refusal',
127
+ 'missing',
128
+ ].includes(error.diagnostic.completionReason);
129
+ return isCompletedEmpty
130
+ ? {
131
+ code: 'TEXT_GENERATION_EMPTY_RESPONSE',
132
+ message: '模型服务返回了空文本',
133
+ retryable: false,
134
+ }
135
+ : {
136
+ code: 'TEXT_GENERATION_PROVIDER_ERROR',
137
+ message: '模型服务返回了非预期错误',
138
+ retryable: true,
139
+ };
140
+ }
118
141
  const status = readHttpStatus(error);
119
142
  if (status === 400 || status === 404 || status === 422) {
120
143
  return {
@@ -147,7 +170,7 @@ function classifyProviderError(error: unknown): {
147
170
  return {
148
171
  code: 'TEXT_GENERATION_PROVIDER_ERROR',
149
172
  message: '模型服务返回了非预期错误',
150
- retryable: status === undefined,
173
+ retryable: status === undefined && !(error instanceof ModelProviderError),
151
174
  };
152
175
  }
153
176
 
@@ -350,7 +373,7 @@ export function createTextGenerationResourceImplementation(
350
373
 
351
374
  export function createTextGenerationResourceDefinition(
352
375
  dependencies: TextGenerationResourceDependencies = {},
353
- ): ResourceDefinition<TextGenerationResource, TextGenerationOptions> {
376
+ ): ResourceDefinition<TextGenerationResource> {
354
377
  return {
355
378
  id: 'textGeneration',
356
379
  title: '模型文本生成',
@@ -391,7 +391,7 @@ export function createTavilyWebSearchResourceImplementation(
391
391
 
392
392
  export function createTavilyWebSearchResourceDefinition(
393
393
  dependencies: TavilyWebSearchDependencies = {},
394
- ): ResourceDefinition<WebSearchResource, TavilyWebSearchOptions> {
394
+ ): ResourceDefinition<WebSearchResource> {
395
395
  return {
396
396
  id: 'webSearch',
397
397
  title: '互联网搜索',
@@ -6,84 +6,13 @@ import type {
6
6
  Sandbox,
7
7
  SandboxHandle,
8
8
  } from '@ct-agents/protocol';
9
-
10
- type DockerContainer = {
11
- id?: string;
12
- start(): Promise<unknown>;
13
- exec(options: DockerExecCreateOptions): Promise<DockerExec>;
14
- remove(options?: { force?: boolean; v?: boolean }): Promise<unknown>;
15
- };
16
-
17
- type DockerExec = {
18
- start(options?: { hijack?: boolean; stdin?: boolean }): Promise<DockerReadableStream>;
19
- inspect(): Promise<{ ExitCode?: number | null; Running?: boolean }>;
20
- };
21
-
22
- type DockerReadableStream = NodeJS.ReadableStream & {
23
- destroy?: () => void;
24
- };
25
-
26
- type DockerClient = {
27
- createContainer(options: DockerCreateContainerOptions): Promise<DockerContainer>;
28
- listContainers?(options?: { all?: boolean; filters?: Record<string, string[]> }): Promise<Array<{ Id?: string; id?: string }>>;
29
- getContainer?(id: string): Pick<DockerContainer, 'remove'>;
30
- };
31
-
32
- type DockerExecCreateOptions = {
33
- Cmd: string[];
34
- AttachStdout: boolean;
35
- AttachStderr: boolean;
36
- Tty: boolean;
37
- User?: string;
38
- WorkingDir?: string;
39
- };
40
-
41
- type DockerCreateContainerOptions = {
42
- Image: string;
43
- name?: string;
44
- User: string;
45
- WorkingDir: string;
46
- Cmd: string[];
47
- Tty: boolean;
48
- OpenStdin: boolean;
49
- NetworkDisabled: boolean;
50
- Labels: Record<string, string>;
51
- HostConfig: {
52
- Runtime?: string;
53
- CapDrop: string[];
54
- SecurityOpt: string[];
55
- ReadonlyRootfs: boolean;
56
- NetworkMode: 'none';
57
- AutoRemove: boolean;
58
- CpuQuota?: number;
59
- Memory?: number;
60
- PidsLimit?: number;
61
- Tmpfs: Record<string, string>;
62
- };
63
- };
64
-
65
- export type DockerSandboxRuntime = 'runsc' | 'runc';
66
-
67
- export type DockerSandboxOptions = {
68
- docker: DockerClient;
69
- image: string;
70
- runtime?: DockerSandboxRuntime;
71
- ownerId?: string;
72
- nodeEnv?: string;
73
- user?: string;
74
- workspaceDir?: string;
75
- tmpSize?: string;
76
- workspaceSize?: string;
77
- cpuQuota?: number;
78
- memoryBytes?: number;
79
- pidsLimit?: number;
80
- defaultMaxOutputBytes?: number;
81
- defaultFileTimeoutMs?: number;
82
- };
83
-
84
- export type CreateDockerSandboxOptions = Omit<DockerSandboxOptions, 'runtime'> & {
85
- useGvisor?: boolean;
86
- };
9
+ import type {
10
+ CreateDockerSandboxOptions,
11
+ DockerClient,
12
+ DockerContainer,
13
+ DockerSandboxOptions,
14
+ DockerSandboxRuntime,
15
+ } from './types.js';
87
16
 
88
17
  export class DockerSandbox implements Sandbox {
89
18
  readonly kind = 'docker' as const;
@@ -3,3 +3,4 @@ export * from './local-process.js';
3
3
  export * from './manager.js';
4
4
  export * from './resources-def.js';
5
5
  export * from './resources.js';
6
+ export type * from './types.js';
@@ -8,12 +8,7 @@ import type {
8
8
  Sandbox,
9
9
  SandboxHandle,
10
10
  } from '@ct-agents/protocol';
11
-
12
- export type LocalProcessSandboxOptions = {
13
- rootDir?: string;
14
- nodeEnv?: string;
15
- defaultMaxOutputBytes?: number;
16
- };
11
+ import type { LocalProcessSandboxOptions } from './types.js';
17
12
 
18
13
  const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
19
14
 
@@ -1,13 +1,7 @@
1
1
  import type {
2
- Sandbox,
3
2
  SandboxHandle,
4
3
  } from '@ct-agents/protocol';
5
-
6
- export type SandboxManagerOptions = {
7
- sandbox: Sandbox & { reapOrphanedContainers?: () => Promise<number> };
8
- now?: () => number;
9
- releaseTimeoutMs?: number;
10
- };
4
+ import type { SandboxManagerOptions } from './types.js';
11
5
 
12
6
  type SandboxEntry = {
13
7
  handlePromise: Promise<SandboxHandle>;
@@ -3,25 +3,6 @@ import type {
3
3
  ResourceDefinition,
4
4
  Sandbox,
5
5
  } from '@ct-agents/protocol';
6
-
7
- export type SandboxLifecycle = 'session' | 'work-item';
8
-
9
- export type SandboxResourceOptions = {
10
- allowedCommands?: string[];
11
- deniedCommands?: string[];
12
- requireApproval?: boolean;
13
- timeoutMs?: number;
14
- sandboxLifecycle?: SandboxLifecycle;
15
- workspaceRoot?: string;
16
- readPaths?: string[];
17
- writePaths?: string[];
18
- limits?: {
19
- cpu?: number;
20
- memoryMb?: number;
21
- processes?: number;
22
- };
23
- };
24
-
25
6
  export const sandboxResourceOptionsSchema = z.object({
26
7
  allowedCommands: z.array(z.string().trim().min(1)).optional(),
27
8
  deniedCommands: z.array(z.string().trim().min(1)).optional(),
@@ -38,7 +19,7 @@ export const sandboxResourceOptionsSchema = z.object({
38
19
  }).strict().optional(),
39
20
  }).strict();
40
21
 
41
- export function createSandboxResourceDefinition(): ResourceDefinition<Sandbox, SandboxResourceOptions> {
22
+ export function createSandboxResourceDefinition(): ResourceDefinition<Sandbox> {
42
23
  return {
43
24
  id: 'sandbox',
44
25
  title: 'Sandbox',
@@ -1,21 +1,9 @@
1
1
  import type {
2
- Database,
3
- MemoryResource,
4
2
  ResourceSlots,
5
- SandboxHandle,
6
- SkillResource,
7
3
  ToolWorkItem,
8
4
  } from '@ct-agents/protocol';
9
5
  import type { ResolveResources } from '../index.js';
10
-
11
- export type CreateSandboxResolveResourcesInput = {
12
- manager: {
13
- forSession(input: { sessionId: string }): Promise<SandboxHandle>;
14
- };
15
- database?: Database;
16
- skills?: SkillResource;
17
- memory?: MemoryResource;
18
- };
6
+ import type { CreateSandboxResolveResourcesInput } from './types.js';
19
7
 
20
8
  export function createSandboxResolveResources(input: CreateSandboxResolveResourcesInput): ResolveResources {
21
9
  return async (item: ToolWorkItem): Promise<ResourceSlots> => {
@@ -0,0 +1,124 @@
1
+ import type {
2
+ Database,
3
+ MemoryResource,
4
+ Sandbox,
5
+ SandboxHandle,
6
+ SkillResource,
7
+ } from '@ct-agents/protocol';
8
+
9
+ export type SandboxManagerOptions = {
10
+ sandbox: Sandbox & { reapOrphanedContainers?: () => Promise<number> };
11
+ now?: () => number;
12
+ releaseTimeoutMs?: number;
13
+ };
14
+
15
+ export type LocalProcessSandboxOptions = {
16
+ rootDir?: string;
17
+ nodeEnv?: string;
18
+ defaultMaxOutputBytes?: number;
19
+ };
20
+
21
+ export type DockerContainer = {
22
+ id?: string;
23
+ start(): Promise<unknown>;
24
+ exec(options: DockerExecCreateOptions): Promise<DockerExec>;
25
+ remove(options?: { force?: boolean; v?: boolean }): Promise<unknown>;
26
+ };
27
+
28
+ export type DockerExec = {
29
+ start(options?: { hijack?: boolean; stdin?: boolean }): Promise<DockerReadableStream>;
30
+ inspect(): Promise<{ ExitCode?: number | null; Running?: boolean }>;
31
+ };
32
+
33
+ export type DockerReadableStream = NodeJS.ReadableStream & {
34
+ destroy?: () => void;
35
+ };
36
+
37
+ export type DockerClient = {
38
+ createContainer(options: DockerCreateContainerOptions): Promise<DockerContainer>;
39
+ listContainers?(options?: { all?: boolean; filters?: Record<string, string[]> }): Promise<Array<{ Id?: string; id?: string }>>;
40
+ getContainer?(id: string): Pick<DockerContainer, 'remove'>;
41
+ };
42
+
43
+ export type DockerExecCreateOptions = {
44
+ Cmd: string[];
45
+ AttachStdout: boolean;
46
+ AttachStderr: boolean;
47
+ Tty: boolean;
48
+ User?: string;
49
+ WorkingDir?: string;
50
+ };
51
+
52
+ export type DockerCreateContainerOptions = {
53
+ Image: string;
54
+ name?: string;
55
+ User: string;
56
+ WorkingDir: string;
57
+ Cmd: string[];
58
+ Tty: boolean;
59
+ OpenStdin: boolean;
60
+ NetworkDisabled: boolean;
61
+ Labels: Record<string, string>;
62
+ HostConfig: {
63
+ Runtime?: string;
64
+ CapDrop: string[];
65
+ SecurityOpt: string[];
66
+ ReadonlyRootfs: boolean;
67
+ NetworkMode: 'none';
68
+ AutoRemove: boolean;
69
+ CpuQuota?: number;
70
+ Memory?: number;
71
+ PidsLimit?: number;
72
+ Tmpfs: Record<string, string>;
73
+ };
74
+ };
75
+
76
+ export type DockerSandboxRuntime = 'runsc' | 'runc';
77
+
78
+ export type DockerSandboxOptions = {
79
+ docker: DockerClient;
80
+ image: string;
81
+ runtime?: DockerSandboxRuntime;
82
+ ownerId?: string;
83
+ nodeEnv?: string;
84
+ user?: string;
85
+ workspaceDir?: string;
86
+ tmpSize?: string;
87
+ workspaceSize?: string;
88
+ cpuQuota?: number;
89
+ memoryBytes?: number;
90
+ pidsLimit?: number;
91
+ defaultMaxOutputBytes?: number;
92
+ defaultFileTimeoutMs?: number;
93
+ };
94
+
95
+ export type CreateDockerSandboxOptions = Omit<DockerSandboxOptions, 'runtime'> & {
96
+ useGvisor?: boolean;
97
+ };
98
+
99
+ export type SandboxLifecycle = 'session' | 'work-item';
100
+
101
+ export type SandboxResourceOptions = {
102
+ allowedCommands?: string[];
103
+ deniedCommands?: string[];
104
+ requireApproval?: boolean;
105
+ timeoutMs?: number;
106
+ sandboxLifecycle?: SandboxLifecycle;
107
+ workspaceRoot?: string;
108
+ readPaths?: string[];
109
+ writePaths?: string[];
110
+ limits?: {
111
+ cpu?: number;
112
+ memoryMb?: number;
113
+ processes?: number;
114
+ };
115
+ };
116
+
117
+ export type CreateSandboxResolveResourcesInput = {
118
+ manager: {
119
+ forSession(input: { sessionId: string }): Promise<SandboxHandle>;
120
+ };
121
+ database?: Database;
122
+ skills?: SkillResource;
123
+ memory?: MemoryResource;
124
+ };