@meistrari/agent-core 0.0.0 → 0.1.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.
Files changed (85) hide show
  1. package/README.md +36 -3
  2. package/bin/supervisor.ts +116 -0
  3. package/package.json +42 -3
  4. package/scripts/build-supervisor-executable.ts +32 -0
  5. package/src/agents/agent-error-serializer.ts +23 -0
  6. package/src/agents/agent-event-stream.ts +81 -0
  7. package/src/agents/agent-id.ts +17 -0
  8. package/src/agents/agent-operation.ts +11 -0
  9. package/src/agents/agent-provider.ts +37 -0
  10. package/src/agents/agent-run.ts +32 -0
  11. package/src/agents/agent-runtime-error.ts +161 -0
  12. package/src/agents/agent-session-events.ts +32 -0
  13. package/src/agents/agent-tool-runner.ts +83 -0
  14. package/src/agents/agent-tool.ts +111 -0
  15. package/src/agents/author-context.ts +36 -0
  16. package/src/agents/claude/claude-command-mapper.ts +234 -0
  17. package/src/agents/claude/claude-event-mapper.ts +748 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +468 -0
  20. package/src/agents/claude/claude-tool-mapper.ts +186 -0
  21. package/src/agents/claude/index.ts +1 -0
  22. package/src/agents/codex/codex-auth.ts +34 -0
  23. package/src/agents/codex/codex-command-mapper.ts +78 -0
  24. package/src/agents/codex/codex-event-mapper.ts +721 -0
  25. package/src/agents/codex/codex-json-rpc-client.ts +326 -0
  26. package/src/agents/codex/codex-protocol.ts +36 -0
  27. package/src/agents/codex/codex-provider.ts +1050 -0
  28. package/src/agents/codex/codex-run.ts +404 -0
  29. package/src/agents/codex/codex-skill-catalog.ts +158 -0
  30. package/src/agents/codex/codex-skill-roots.ts +19 -0
  31. package/src/agents/codex/codex-tool-mapper.ts +55 -0
  32. package/src/agents/codex/codex.errors.ts +68 -0
  33. package/src/agents/codex/generated/meta.gen.ts +606 -0
  34. package/src/agents/codex/generated/namespaces.gen.ts +311 -0
  35. package/src/agents/codex/generated/schema.gen.ts +34883 -0
  36. package/src/agents/codex/index.ts +2 -0
  37. package/src/agents/index.ts +14 -0
  38. package/src/agents/input-attachment-preparation.ts +86 -0
  39. package/src/agents/input-attachment.errors.ts +16 -0
  40. package/src/agents/instructions.ts +8 -0
  41. package/src/agents/materialized-input-attachment.ts +26 -0
  42. package/src/agents/message-id.ts +23 -0
  43. package/src/agents/normalize.ts +8 -0
  44. package/src/agents/sandbox-environment.ts +1 -0
  45. package/src/agents/tools/ping.tool.ts +13 -0
  46. package/src/agents/user-input-request.ts +470 -0
  47. package/src/protocol/agent-event.ts +2 -1
  48. package/src/protocol/agent-usage.ts +14 -0
  49. package/src/provenance.gen.ts +3 -3
  50. package/src/supervisor/agent-provider-factory.ts +189 -0
  51. package/src/supervisor/bootstrap-binder.ts +133 -0
  52. package/src/supervisor/config.ts +49 -0
  53. package/src/supervisor/control-authority-verifier.ts +135 -0
  54. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  55. package/src/supervisor/errors.ts +24 -0
  56. package/src/supervisor/index.ts +34 -0
  57. package/src/supervisor/persistence/json.ts +21 -0
  58. package/src/supervisor/persistence/state-discovery.ts +56 -0
  59. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  60. package/src/supervisor/ports/index.ts +109 -0
  61. package/src/supervisor/provider-factory.ts +37 -0
  62. package/src/supervisor/resident.ts +143 -0
  63. package/src/supervisor/rpc-client.ts +120 -0
  64. package/src/supervisor/runtime-handler.ts +309 -0
  65. package/src/supervisor/websocket-server.ts +435 -0
  66. package/src/supervisor-protocol/bootstrap.ts +1 -1
  67. package/src/template-onboarding.ts +47 -0
  68. package/src/testing/es256-test-keys.ts +73 -0
  69. package/src/testing/in-memory-runtime-control-plane.ts +206 -0
  70. package/src/testing/index.ts +6 -0
  71. package/src/testing/loopback-supervisor-connection.ts +71 -0
  72. package/src/testing/scripted-provider.ts +64 -0
  73. package/src/worker-runtime-client/command-pump.ts +127 -0
  74. package/src/worker-runtime-client/connection-attempt.ts +349 -0
  75. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  76. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  77. package/src/worker-runtime-client/frame-processor.ts +178 -0
  78. package/src/worker-runtime-client/index.ts +27 -0
  79. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  80. package/src/worker-runtime-client/ports.ts +139 -0
  81. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  82. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  83. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  84. package/src/worker-runtime-client/sandbox-connection-runtime.ts +301 -0
  85. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,349 @@
1
+ import type { Logger } from '../logger'
2
+ import type { CommandAckEnvelope } from '../supervisor-protocol/envelopes/supervisor-to-control-plane'
3
+ import type {
4
+ BootstrapProvider,
5
+ CommandSource,
6
+ ConnectionAuthorityStore,
7
+ ConnectionClaim,
8
+ ControlAuthoritySigner,
9
+ DurableEventSink,
10
+ EphemeralEventSink,
11
+ RpcDispatcher,
12
+ SandboxConnectionRuntimeTimings,
13
+ SessionFailureSink,
14
+ SupervisorConnection,
15
+ SupervisorConnectionOpener,
16
+ } from './ports'
17
+ import { randomUUID } from 'node:crypto'
18
+ import { wireCommandBodySchema } from '../supervisor-protocol/command-body'
19
+ import { encodeWireFrame } from '../supervisor-protocol/wire-codec'
20
+ import { SessionCommandPump } from './command-pump'
21
+ import { BootstrapRejectedAckError, SupervisorFrameProcessor } from './frame-processor'
22
+ import { decryptToken } from './token-crypto'
23
+
24
+ export class SessionSandboxConnectionAttempt {
25
+ private readonly controller = new AbortController()
26
+ private commandPump: SessionCommandPump | undefined
27
+ private processor: SupervisorFrameProcessor | undefined
28
+ private connection: SupervisorConnection | undefined
29
+ private ready = false
30
+ private readonly parentAbort: () => void
31
+
32
+ constructor(private readonly dependencies: {
33
+ claim: ConnectionClaim
34
+ establishmentDeadline: number
35
+ replicaId: string
36
+ trafficTokenEncryptionKey: string
37
+ signControlAuthority: ControlAuthoritySigner
38
+ openSupervisorConnection: SupervisorConnectionOpener
39
+ authorityStore: ConnectionAuthorityStore
40
+ commands: CommandSource
41
+ durableEvents: DurableEventSink
42
+ ephemeralEvents: EphemeralEventSink
43
+ rpc: RpcDispatcher
44
+ bootstrap: BootstrapProvider
45
+ failures: SessionFailureSink
46
+ timings: SandboxConnectionRuntimeTimings
47
+ logger: Logger
48
+ parentSignal: AbortSignal
49
+ }) {
50
+ this.parentAbort = () => this.stop(dependencies.parentSignal.reason)
51
+ dependencies.parentSignal.addEventListener('abort', this.parentAbort, { once: true })
52
+ }
53
+
54
+ async run(): Promise<never> {
55
+ const { claim } = this.dependencies
56
+ const renewalFailure = Promise.withResolvers<unknown>()
57
+ void this.renewLease().catch((error) => {
58
+ renewalFailure.resolve(error)
59
+ this.stop(error)
60
+ })
61
+ const runtimeConnectionAttemptId = randomUUID()
62
+ const eventAckFloor = await this.dependencies.durableEvents.getAckFloor({
63
+ sessionSandboxId: claim.sessionSandboxId,
64
+ })
65
+ const initialCommandPromise = this.dependencies.commands.initial({ sessionId: claim.sessionId })
66
+ const bootstrapBodyPromise = this.dependencies.bootstrap.loadBootstrapBody({ sessionId: claim.sessionId })
67
+ const secretsPromise = this.dependencies.bootstrap.issueEphemeralSecrets({
68
+ sessionId: claim.sessionId,
69
+ eventAckFloor,
70
+ signal: this.controller.signal,
71
+ })
72
+ const authorityAssertionPromise = this.dependencies.signControlAuthority({
73
+ providerSandboxId: claim.providerSandboxId,
74
+ sessionId: claim.sessionId,
75
+ sessionSandboxId: claim.sessionSandboxId,
76
+ connectionGeneration: claim.runtimeConnectionGeneration,
77
+ runtimeConnectionAttemptId,
78
+ eventAckFloor,
79
+ })
80
+ const trafficAccessToken = decryptToken({
81
+ encryptedToken: claim.trafficAccessTokenEncrypted,
82
+ secret: this.dependencies.trafficTokenEncryptionKey,
83
+ })
84
+
85
+ const queuedFrames: Array<string | ArrayBuffer | Uint8Array> = []
86
+ const closed = Promise.withResolvers<{ code: number, reason: string }>()
87
+ const authorityAssertion = await authorityAssertionPromise
88
+ const connection = await beforeDeadline(this.dependencies.openSupervisorConnection({
89
+ providerSandboxId: claim.providerSandboxId,
90
+ trafficAccessToken,
91
+ sessionId: claim.sessionId,
92
+ sessionSandboxId: claim.sessionSandboxId,
93
+ runtimeConnectionAttemptId,
94
+ authorityAssertion,
95
+ connectionGeneration: claim.runtimeConnectionGeneration,
96
+ eventAckFloor,
97
+ signal: this.controller.signal,
98
+ onMessage: frame => this.processor ? this.processor.receive(frame) : queuedFrames.push(frame),
99
+ onClose: close => closed.resolve(close),
100
+ }), Math.min(this.dependencies.establishmentDeadline, performance.now() + this.dependencies.timings.dialTimeoutMs), this.controller.signal, () => new ConnectionClosedError({
101
+ ready: false,
102
+ detail: 'Supervisor connection dial timed out.',
103
+ }))
104
+ this.connection = connection
105
+ let bootstrapAck: CommandAckEnvelope | undefined
106
+ const processor = new SupervisorFrameProcessor({
107
+ ref: {
108
+ sessionId: claim.sessionId,
109
+ sessionSandboxId: claim.sessionSandboxId,
110
+ providerSandboxId: claim.providerSandboxId,
111
+ },
112
+ generation: claim.runtimeConnectionGeneration,
113
+ runtimeConnectionId: claim.runtimeConnectionId,
114
+ runtimeConnectionAttemptId,
115
+ connection,
116
+ authorityStore: this.dependencies.authorityStore,
117
+ durableEvents: this.dependencies.durableEvents,
118
+ ephemeralEvents: this.dependencies.ephemeralEvents,
119
+ rpcDispatcher: this.dependencies.rpc,
120
+ failures: this.dependencies.failures,
121
+ logger: this.dependencies.logger,
122
+ onCommandAck: (ack) => {
123
+ if (this.commandPump)
124
+ this.commandPump.acknowledge(ack)
125
+ else
126
+ bootstrapAck = ack
127
+ },
128
+ onFatal: error => this.stop(error),
129
+ })
130
+ this.processor = processor
131
+ for (const frame of queuedFrames)
132
+ processor.receive(frame)
133
+
134
+ const [initialCommand, body, secrets] = await Promise.all([
135
+ initialCommandPromise,
136
+ bootstrapBodyPromise,
137
+ secretsPromise,
138
+ ])
139
+ if (!initialCommand || initialCommand.commandSeq !== 1)
140
+ throw new DeterministicEstablishmentError('agent-core.initial-command-invalid', 'Session command 1 is missing or invalid.')
141
+ const parsedInitialBody = wireCommandBodySchema.safeParse(initialCommand.body)
142
+ if (!parsedInitialBody.success) {
143
+ throw new DeterministicEstablishmentError(
144
+ 'agent-core.initial-command-invalid',
145
+ 'Session command 1 has an invalid body.',
146
+ )
147
+ }
148
+ const initialBody = parsedInitialBody.data
149
+ await connection.send(encodeWireFrame({
150
+ kind: 'session.bootstrap',
151
+ connectionGeneration: claim.runtimeConnectionGeneration,
152
+ body: {
153
+ ...body,
154
+ sessionId: claim.sessionId,
155
+ initialCommand: {
156
+ commandId: initialCommand.commandId,
157
+ commandSeq: initialCommand.commandSeq,
158
+ body: initialBody,
159
+ },
160
+ },
161
+ ...secrets,
162
+ }))
163
+
164
+ const readiness = await beforeDeadline(Promise.race([
165
+ processor.waitForReadiness({ signal: this.controller.signal }),
166
+ closed.promise.then((close) => {
167
+ throw new ConnectionClosedError({ ready: false, detail: `${close.code}:${close.reason}` })
168
+ }),
169
+ ]), Math.min(this.dependencies.establishmentDeadline, performance.now() + 10_000), this.controller.signal, () => new ConnectionClosedError({
170
+ ready: false,
171
+ detail: 'Supervisor bootstrap timed out.',
172
+ })).catch(async (error: unknown) => {
173
+ if (error instanceof BootstrapRejectedAckError) {
174
+ await processor.acknowledgeBootstrapRejection(error.ack)
175
+ throw new DeterministicEstablishmentError(error.ack.errorCode, error.ack.detail, true)
176
+ }
177
+ throw error
178
+ })
179
+ const markedReady = await this.dependencies.authorityStore.markConnectionReady({
180
+ sessionSandboxId: claim.sessionSandboxId,
181
+ generation: claim.runtimeConnectionGeneration,
182
+ runtimeConnectionId: claim.runtimeConnectionId,
183
+ lastReceivedCommandSeq: readiness.lastReceivedCommandSeq,
184
+ agentRunSnapshot: readiness.agentRun,
185
+ })
186
+ if (!markedReady)
187
+ throw new ConnectionAuthorityLostError()
188
+ this.ready = true
189
+
190
+ const pump = new SessionCommandPump({
191
+ sessionId: claim.sessionId,
192
+ sessionSandboxId: claim.sessionSandboxId,
193
+ runtimeConnectionId: claim.runtimeConnectionId,
194
+ afterSequence: readiness.lastReceivedCommandSeq,
195
+ commands: this.dependencies.commands,
196
+ authorityStore: this.dependencies.authorityStore,
197
+ connection,
198
+ logger: this.dependencies.logger,
199
+ onRejected: async (ack) => {
200
+ await this.dependencies.failures.markSessionFailed({
201
+ sessionId: claim.sessionId,
202
+ sessionSandboxId: claim.sessionSandboxId,
203
+ providerSandboxId: claim.providerSandboxId,
204
+ code: ack.errorCode,
205
+ detail: ack.detail,
206
+ })
207
+ connection.close(1008, 'Supervisor command was rejected.')
208
+ },
209
+ })
210
+ this.commandPump = pump
211
+ if (bootstrapAck)
212
+ pump.acknowledge(bootstrapAck)
213
+ pump.start()
214
+ const close = await Promise.race([
215
+ closed.promise,
216
+ aborted(this.controller.signal),
217
+ renewalFailure.promise.then((error) => { throw error }),
218
+ ])
219
+ throw new ConnectionClosedError({ ready: this.ready, detail: 'code' in close ? `${close.code}:${close.reason}` : undefined })
220
+ }
221
+
222
+ wakeCommands(): void {
223
+ this.commandPump?.wake()
224
+ }
225
+
226
+ stop(reason?: unknown): void {
227
+ this.dependencies.parentSignal.removeEventListener('abort', this.parentAbort)
228
+ if (!this.controller.signal.aborted)
229
+ this.controller.abort(reason ?? new DOMException('Connection attempt stopped.', 'AbortError'))
230
+ this.commandPump?.stop()
231
+ this.processor?.stop(reason)
232
+ this.connection?.close(1001, 'Connection attempt stopped.')
233
+ }
234
+
235
+ async drain(): Promise<void> {
236
+ await this.processor?.drain()
237
+ }
238
+
239
+ private async renewLease(): Promise<never> {
240
+ const { claim, timings } = this.dependencies
241
+ let leaseSafetyDeadline = monotonicDeadline(claim.leaseExpiresAt)
242
+ while (true) {
243
+ const untilSafetyDeadline = leaseSafetyDeadline - performance.now()
244
+ if (untilSafetyDeadline <= 0)
245
+ throw new ConnectionAuthorityLostError()
246
+ await delay(Math.min(timings.leaseRenewIntervalMs, untilSafetyDeadline), this.controller.signal)
247
+ if (performance.now() >= leaseSafetyDeadline)
248
+ throw new ConnectionAuthorityLostError()
249
+ const result = await beforeDeadline(this.dependencies.authorityStore.renewConnectionLease({
250
+ sessionSandboxId: claim.sessionSandboxId,
251
+ replicaId: this.dependencies.replicaId,
252
+ generation: claim.runtimeConnectionGeneration,
253
+ leaseDurationMs: timings.leaseDurationMs,
254
+ supervisorLivenessObserved: this.processor?.takeSupervisorLivenessObservation() ?? false,
255
+ }), leaseSafetyDeadline, this.controller.signal, () => new ConnectionAuthorityLostError())
256
+ if (!result.renewed)
257
+ throw new ConnectionAuthorityLostError()
258
+ leaseSafetyDeadline = monotonicDeadline(
259
+ result.leaseExpiresAt ?? new Date(Date.now() + timings.leaseDurationMs),
260
+ )
261
+ if (result.hasOutstandingCommand)
262
+ this.commandPump?.wake()
263
+ }
264
+ }
265
+ }
266
+
267
+ export class DeterministicEstablishmentError extends Error {
268
+ override readonly name = 'DeterministicEstablishmentError'
269
+ readonly code: string
270
+ readonly detail: string | undefined
271
+ readonly reported: boolean
272
+
273
+ constructor(code: string, detail?: string, reported = false) {
274
+ super(detail ?? code)
275
+ this.code = code
276
+ this.detail = detail
277
+ this.reported = reported
278
+ }
279
+ }
280
+
281
+ export class ConnectionAuthorityLostError extends Error {
282
+ override readonly name = 'ConnectionAuthorityLostError'
283
+ }
284
+
285
+ export class ConnectionClosedError extends Error {
286
+ override readonly name = 'ConnectionClosedError'
287
+ readonly ready: boolean
288
+
289
+ constructor(input: { ready: boolean, detail?: string }) {
290
+ super(input.detail ?? 'Supervisor connection closed.')
291
+ this.ready = input.ready
292
+ }
293
+ }
294
+
295
+ async function aborted(signal: AbortSignal): Promise<never> {
296
+ if (signal.aborted)
297
+ throw signal.reason
298
+ return await new Promise<never>((_resolve, reject) => {
299
+ signal.addEventListener('abort', () => reject(signal.reason), { once: true })
300
+ })
301
+ }
302
+
303
+ async function delay(milliseconds: number, signal: AbortSignal): Promise<void> {
304
+ if (signal.aborted)
305
+ throw signal.reason
306
+ await new Promise<void>((resolve, reject) => {
307
+ let timeout: ReturnType<typeof setTimeout>
308
+ const abort = () => {
309
+ clearTimeout(timeout)
310
+ signal.removeEventListener('abort', abort)
311
+ reject(signal.reason)
312
+ }
313
+ timeout = setTimeout(() => {
314
+ signal.removeEventListener('abort', abort)
315
+ resolve()
316
+ }, milliseconds)
317
+ signal.addEventListener('abort', abort, { once: true })
318
+ })
319
+ }
320
+
321
+ function monotonicDeadline(expiresAt: Date): number {
322
+ return performance.now() + Math.max(0, expiresAt.getTime() - Date.now())
323
+ }
324
+
325
+ async function beforeDeadline<T>(
326
+ task: Promise<T>,
327
+ deadline: number,
328
+ signal: AbortSignal,
329
+ timeoutError: () => Error,
330
+ ): Promise<T> {
331
+ signal.throwIfAborted()
332
+ const remaining = deadline - performance.now()
333
+ if (remaining <= 0)
334
+ throw timeoutError()
335
+ let timer: ReturnType<typeof setTimeout>
336
+ let abort: () => void
337
+ const guard = new Promise<never>((_resolve, reject) => {
338
+ abort = () => reject(signal.reason)
339
+ timer = setTimeout(() => reject(timeoutError()), remaining)
340
+ signal.addEventListener('abort', abort, { once: true })
341
+ })
342
+ try {
343
+ return await Promise.race([task, guard])
344
+ }
345
+ finally {
346
+ clearTimeout(timer!)
347
+ signal.removeEventListener('abort', abort!)
348
+ }
349
+ }
@@ -0,0 +1,100 @@
1
+ import type { SandboxControlAuthorityClaims, SandboxControlAuthorityPublicJwks } from '../supervisor-protocol/control-authority'
2
+ import type { ControlAuthoritySigner } from './ports'
3
+ import { SignJWT } from 'jose/jwt/sign'
4
+ import { importJWK } from 'jose/key/import'
5
+ import { z } from 'zod'
6
+ import { InfrastructureError } from '../errors'
7
+ import {
8
+ sandboxControlAuthorityAlgorithm,
9
+ sandboxControlAuthorityAssertionLifetimeSeconds,
10
+ sandboxControlAuthorityAudience,
11
+ sandboxControlAuthorityClaimsSchema,
12
+ sandboxControlAuthorityPublicJwksSchema,
13
+ } from '../supervisor-protocol/control-authority'
14
+
15
+ const base64UrlCoordinateSchema = z.string().regex(/^[\w-]+$/u)
16
+
17
+ const privateJwkSchema = z.object({
18
+ kty: z.literal('EC'),
19
+ crv: z.literal('P-256'),
20
+ x: base64UrlCoordinateSchema,
21
+ y: base64UrlCoordinateSchema,
22
+ d: base64UrlCoordinateSchema,
23
+ kid: z.string().regex(/^[\w.-]{1,128}$/u),
24
+ alg: z.literal(sandboxControlAuthorityAlgorithm),
25
+ use: z.literal('sig'),
26
+ key_ops: z.tuple([z.literal('sign')]),
27
+ }).strict()
28
+
29
+ export class ControlAuthorityConfigurationError extends InfrastructureError {
30
+ constructor(input: { message: string, cause?: unknown }) {
31
+ super({
32
+ code: 'agent-core.control-authority-configuration-invalid',
33
+ message: input.message,
34
+ publicMessage: 'Sandbox control authority is not configured.',
35
+ retryable: false,
36
+ cause: input.cause,
37
+ })
38
+ }
39
+ }
40
+
41
+ export async function createControlAuthoritySigner(input: {
42
+ issuer: string
43
+ activeKeyId: string
44
+ signingJwk: string | Record<string, unknown>
45
+ publicJwks: string | SandboxControlAuthorityPublicJwks
46
+ }): Promise<{ sign: ControlAuthoritySigner, publicJwks: SandboxControlAuthorityPublicJwks }> {
47
+ try {
48
+ const rawPrivateJwk = typeof input.signingJwk === 'string' ? JSON.parse(input.signingJwk) : input.signingJwk
49
+ const rawPublicJwks = typeof input.publicJwks === 'string' ? JSON.parse(input.publicJwks) : input.publicJwks
50
+ const privateJwk = privateJwkSchema.parse(rawPrivateJwk)
51
+ const publicJwks = sandboxControlAuthorityPublicJwksSchema.parse(rawPublicJwks)
52
+ if (privateJwk.kid !== input.activeKeyId)
53
+ configurationFailure('The active key ID does not match the signing key.')
54
+ const activePublicKey = publicJwks.keys.find(key => key.kid === input.activeKeyId)
55
+ if (!activePublicKey)
56
+ configurationFailure('The active signing key is absent from the verification keyset.')
57
+ if (activePublicKey.x !== privateJwk.x || activePublicKey.y !== privateJwk.y)
58
+ configurationFailure('The active signing key does not match its verification key.')
59
+ const signingKey = await importJWK(privateJwk, sandboxControlAuthorityAlgorithm)
60
+ if (!(signingKey instanceof CryptoKey))
61
+ configurationFailure('The control-authority signing key is not asymmetric.')
62
+
63
+ const sign: ControlAuthoritySigner = async (assertionInput) => {
64
+ const now = Math.floor(Date.now() / 1_000)
65
+ const claims = sandboxControlAuthorityClaimsSchema.parse({
66
+ iss: input.issuer,
67
+ aud: sandboxControlAuthorityAudience,
68
+ jti: assertionInput.runtimeConnectionAttemptId,
69
+ iat: now,
70
+ nbf: now,
71
+ exp: now + sandboxControlAuthorityAssertionLifetimeSeconds,
72
+ ...assertionInput,
73
+ })
74
+ return await signClaims({ claims, keyId: input.activeKeyId, signingKey })
75
+ }
76
+ return { sign, publicJwks }
77
+ }
78
+ catch (cause) {
79
+ if (cause instanceof ControlAuthorityConfigurationError)
80
+ throw cause
81
+ throw new ControlAuthorityConfigurationError({
82
+ message: 'Sandbox control-authority signing configuration is invalid.',
83
+ cause,
84
+ })
85
+ }
86
+ }
87
+
88
+ function configurationFailure(message: string): never {
89
+ throw new ControlAuthorityConfigurationError({ message })
90
+ }
91
+
92
+ async function signClaims(input: {
93
+ claims: SandboxControlAuthorityClaims
94
+ keyId: string
95
+ signingKey: CryptoKey
96
+ }): Promise<string> {
97
+ return await new SignJWT(input.claims)
98
+ .setProtectedHeader({ alg: sandboxControlAuthorityAlgorithm, kid: input.keyId, typ: 'JWT' })
99
+ .sign(input.signingKey)
100
+ }
@@ -0,0 +1,102 @@
1
+ import type { SupervisorConnection, SupervisorConnectionOpener } from './ports'
2
+ import { ProviderError } from '../errors'
3
+ import { sandboxControlAuthorityHeaderName } from '../supervisor-protocol/control-authority'
4
+
5
+ const defaultIngressUrl = 'wss://sandbox.e2b.app/control/ws'
6
+ const defaultSupervisorPort = 8080
7
+
8
+ export function createE2bSupervisorConnectionOpener(input: {
9
+ ingressUrl?: string
10
+ supervisorPort?: number
11
+ dialTimeoutMs?: number
12
+ } = {}): SupervisorConnectionOpener {
13
+ const ingressUrl = input.ingressUrl ?? defaultIngressUrl
14
+ const supervisorPort = input.supervisorPort ?? defaultSupervisorPort
15
+ const dialTimeoutMs = input.dialTimeoutMs ?? 10_000
16
+
17
+ return async (request) => {
18
+ request.signal.throwIfAborted()
19
+ const socket = new WebSocket(ingressUrl, {
20
+ headers: {
21
+ 'E2B-Traffic-Access-Token': request.trafficAccessToken,
22
+ 'E2b-Sandbox-Id': request.providerSandboxId,
23
+ 'E2b-Sandbox-Port': String(supervisorPort),
24
+ 'X-Coding-Agent-Session-Id': request.sessionId,
25
+ 'X-Coding-Agent-Session-Sandbox-Id': request.sessionSandboxId,
26
+ 'X-Coding-Agent-Runtime-Connection-Attempt-Id': request.runtimeConnectionAttemptId,
27
+ [sandboxControlAuthorityHeaderName]: request.authorityAssertion,
28
+ 'X-Coding-Agent-Connection-Generation': String(request.connectionGeneration),
29
+ 'X-Coding-Agent-Event-Ack': String(request.eventAckFloor),
30
+ },
31
+ })
32
+ const connection: SupervisorConnection = {
33
+ send(frame) {
34
+ if (socket.readyState !== WebSocket.OPEN) {
35
+ throw new E2bSupervisorConnectionError({
36
+ message: 'E2B supervisor connection is not open.',
37
+ })
38
+ }
39
+ socket.send(frame)
40
+ },
41
+ close: (code, reason) => socket.close(code, reason),
42
+ }
43
+ socket.binaryType = 'arraybuffer'
44
+ socket.onmessage = (event) => {
45
+ if (typeof event.data === 'string')
46
+ request.onMessage(event.data)
47
+ else if (event.data instanceof ArrayBuffer)
48
+ request.onMessage(event.data)
49
+ }
50
+
51
+ await new Promise<void>((resolve, reject) => {
52
+ let timeout: ReturnType<typeof setTimeout>
53
+ const abort = () => {
54
+ clearTimeout(timeout)
55
+ request.signal.removeEventListener('abort', abort)
56
+ socket.close()
57
+ reject(request.signal.reason)
58
+ }
59
+ const cleanup = () => {
60
+ clearTimeout(timeout)
61
+ request.signal.removeEventListener('abort', abort)
62
+ }
63
+ timeout = setTimeout(() => {
64
+ cleanup()
65
+ socket.close()
66
+ reject(new E2bSupervisorConnectionError({ message: 'Timed out opening the E2B supervisor connection.' }))
67
+ }, dialTimeoutMs)
68
+ request.signal.addEventListener('abort', abort, { once: true })
69
+ socket.onopen = () => {
70
+ cleanup()
71
+ socket.onclose = event => request.onClose({ code: event.code, reason: event.reason })
72
+ socket.onerror = () => {
73
+ if (socket.readyState === WebSocket.OPEN)
74
+ socket.close()
75
+ }
76
+ resolve()
77
+ }
78
+ socket.onerror = () => undefined
79
+ socket.onclose = (event) => {
80
+ cleanup()
81
+ reject(new E2bSupervisorConnectionError({
82
+ message: `E2B supervisor connection closed during upgrade (${event.code}).`,
83
+ }))
84
+ }
85
+ })
86
+ return connection
87
+ }
88
+ }
89
+
90
+ export const openE2bSupervisorConnection = createE2bSupervisorConnectionOpener()
91
+
92
+ class E2bSupervisorConnectionError extends ProviderError {
93
+ constructor(input: { message: string, cause?: unknown }) {
94
+ super({
95
+ code: 'agent-core.e2b-supervisor-connection-failed',
96
+ message: input.message,
97
+ publicMessage: 'The sandbox control connection is unavailable.',
98
+ retryable: true,
99
+ cause: input.cause,
100
+ })
101
+ }
102
+ }