@meistrari/agent-core 0.0.0 → 0.1.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 (83) hide show
  1. package/README.md +24 -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 +736 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +464 -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 +708 -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/provenance.gen.ts +3 -3
  48. package/src/supervisor/agent-provider-factory.ts +189 -0
  49. package/src/supervisor/bootstrap-binder.ts +125 -0
  50. package/src/supervisor/config.ts +49 -0
  51. package/src/supervisor/control-authority-verifier.ts +135 -0
  52. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  53. package/src/supervisor/errors.ts +24 -0
  54. package/src/supervisor/index.ts +34 -0
  55. package/src/supervisor/persistence/json.ts +21 -0
  56. package/src/supervisor/persistence/state-discovery.ts +56 -0
  57. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  58. package/src/supervisor/ports/index.ts +109 -0
  59. package/src/supervisor/provider-factory.ts +37 -0
  60. package/src/supervisor/resident.ts +143 -0
  61. package/src/supervisor/rpc-client.ts +120 -0
  62. package/src/supervisor/runtime-handler.ts +309 -0
  63. package/src/supervisor/websocket-server.ts +434 -0
  64. package/src/supervisor-protocol/bootstrap.ts +1 -1
  65. package/src/template-onboarding.ts +47 -0
  66. package/src/testing/es256-test-keys.ts +73 -0
  67. package/src/testing/in-memory-runtime-control-plane.ts +205 -0
  68. package/src/testing/index.ts +6 -0
  69. package/src/testing/loopback-supervisor-connection.ts +71 -0
  70. package/src/testing/scripted-provider.ts +64 -0
  71. package/src/worker-runtime-client/command-pump.ts +132 -0
  72. package/src/worker-runtime-client/connection-attempt.ts +340 -0
  73. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  74. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  75. package/src/worker-runtime-client/frame-processor.ts +178 -0
  76. package/src/worker-runtime-client/index.ts +27 -0
  77. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  78. package/src/worker-runtime-client/ports.ts +137 -0
  79. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  80. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  81. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  82. package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
  83. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,340 @@
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
+ replicaId: string
35
+ trafficTokenEncryptionKey: string
36
+ signControlAuthority: ControlAuthoritySigner
37
+ openSupervisorConnection: SupervisorConnectionOpener
38
+ authorityStore: ConnectionAuthorityStore
39
+ commands: CommandSource
40
+ durableEvents: DurableEventSink
41
+ ephemeralEvents: EphemeralEventSink
42
+ rpc: RpcDispatcher
43
+ bootstrap: BootstrapProvider
44
+ failures: SessionFailureSink
45
+ timings: SandboxConnectionRuntimeTimings
46
+ logger: Logger
47
+ parentSignal: AbortSignal
48
+ }) {
49
+ this.parentAbort = () => this.stop(dependencies.parentSignal.reason)
50
+ dependencies.parentSignal.addEventListener('abort', this.parentAbort, { once: true })
51
+ }
52
+
53
+ async run(): Promise<never> {
54
+ const { claim } = this.dependencies
55
+ const renewalFailure = Promise.withResolvers<unknown>()
56
+ void this.renewLease().catch((error) => {
57
+ renewalFailure.resolve(error)
58
+ this.stop(error)
59
+ })
60
+ const runtimeConnectionAttemptId = randomUUID()
61
+ const eventAckFloor = await this.dependencies.durableEvents.getAckFloor({
62
+ sessionSandboxId: claim.sessionSandboxId,
63
+ })
64
+ const initialCommandPromise = this.dependencies.commands.initial({ sessionId: claim.sessionId })
65
+ const bootstrapBodyPromise = this.dependencies.bootstrap.loadBootstrapBody({ sessionId: claim.sessionId })
66
+ const secretsPromise = this.dependencies.bootstrap.issueEphemeralSecrets({
67
+ sessionId: claim.sessionId,
68
+ eventAckFloor,
69
+ signal: this.controller.signal,
70
+ })
71
+ const authorityAssertionPromise = this.dependencies.signControlAuthority({
72
+ providerSandboxId: claim.providerSandboxId,
73
+ sessionId: claim.sessionId,
74
+ sessionSandboxId: claim.sessionSandboxId,
75
+ connectionGeneration: claim.runtimeConnectionGeneration,
76
+ runtimeConnectionAttemptId,
77
+ eventAckFloor,
78
+ })
79
+ const trafficAccessToken = decryptToken({
80
+ encryptedToken: claim.trafficAccessTokenEncrypted,
81
+ secret: this.dependencies.trafficTokenEncryptionKey,
82
+ })
83
+
84
+ const queuedFrames: Array<string | ArrayBuffer | Uint8Array> = []
85
+ const closed = Promise.withResolvers<{ code: number, reason: string }>()
86
+ const authorityAssertion = await authorityAssertionPromise
87
+ const connection = await beforeDeadline(this.dependencies.openSupervisorConnection({
88
+ providerSandboxId: claim.providerSandboxId,
89
+ trafficAccessToken,
90
+ sessionId: claim.sessionId,
91
+ sessionSandboxId: claim.sessionSandboxId,
92
+ runtimeConnectionAttemptId,
93
+ authorityAssertion,
94
+ connectionGeneration: claim.runtimeConnectionGeneration,
95
+ eventAckFloor,
96
+ signal: this.controller.signal,
97
+ onMessage: frame => this.processor ? this.processor.receive(frame) : queuedFrames.push(frame),
98
+ onClose: close => closed.resolve(close),
99
+ }), performance.now() + this.dependencies.timings.dialTimeoutMs, this.controller.signal, () => new ConnectionClosedError({
100
+ ready: false,
101
+ detail: 'Supervisor connection dial timed out.',
102
+ }))
103
+ this.connection = connection
104
+ let bootstrapAck: CommandAckEnvelope | undefined
105
+ const processor = new SupervisorFrameProcessor({
106
+ ref: {
107
+ sessionId: claim.sessionId,
108
+ sessionSandboxId: claim.sessionSandboxId,
109
+ providerSandboxId: claim.providerSandboxId,
110
+ },
111
+ generation: claim.runtimeConnectionGeneration,
112
+ runtimeConnectionId: claim.runtimeConnectionId,
113
+ runtimeConnectionAttemptId,
114
+ connection,
115
+ authorityStore: this.dependencies.authorityStore,
116
+ durableEvents: this.dependencies.durableEvents,
117
+ ephemeralEvents: this.dependencies.ephemeralEvents,
118
+ rpcDispatcher: this.dependencies.rpc,
119
+ failures: this.dependencies.failures,
120
+ logger: this.dependencies.logger,
121
+ onCommandAck: (ack) => {
122
+ if (this.commandPump)
123
+ this.commandPump.acknowledge(ack)
124
+ else
125
+ bootstrapAck = ack
126
+ },
127
+ onFatal: error => this.stop(error),
128
+ })
129
+ this.processor = processor
130
+ for (const frame of queuedFrames)
131
+ processor.receive(frame)
132
+
133
+ const [initialCommand, body, secrets] = await Promise.all([
134
+ initialCommandPromise,
135
+ bootstrapBodyPromise,
136
+ secretsPromise,
137
+ ])
138
+ if (!initialCommand || initialCommand.commandSeq !== 1)
139
+ throw new DeterministicEstablishmentError('agent-core.initial-command-invalid', 'Session command 1 is missing or invalid.')
140
+ const parsedInitialBody = wireCommandBodySchema.safeParse(initialCommand.body)
141
+ if (!parsedInitialBody.success) {
142
+ throw new DeterministicEstablishmentError(
143
+ 'agent-core.initial-command-invalid',
144
+ 'Session command 1 has an invalid body.',
145
+ )
146
+ }
147
+ const initialBody = parsedInitialBody.data
148
+ await connection.send(encodeWireFrame({
149
+ kind: 'session.bootstrap',
150
+ connectionGeneration: claim.runtimeConnectionGeneration,
151
+ body: {
152
+ ...body,
153
+ sessionId: claim.sessionId,
154
+ initialCommand: {
155
+ commandId: initialCommand.commandId,
156
+ commandSeq: initialCommand.commandSeq,
157
+ body: initialBody,
158
+ },
159
+ },
160
+ ...secrets,
161
+ }))
162
+
163
+ const readiness = await processor.waitForReadiness({ signal: this.controller.signal }).catch(async (error: unknown) => {
164
+ if (error instanceof BootstrapRejectedAckError) {
165
+ await processor.acknowledgeBootstrapRejection(error.ack)
166
+ throw new DeterministicEstablishmentError(error.ack.errorCode, error.ack.detail, true)
167
+ }
168
+ throw error
169
+ })
170
+ const markedReady = await this.dependencies.authorityStore.markConnectionReady({
171
+ sessionSandboxId: claim.sessionSandboxId,
172
+ generation: claim.runtimeConnectionGeneration,
173
+ runtimeConnectionId: claim.runtimeConnectionId,
174
+ lastReceivedCommandSeq: readiness.lastReceivedCommandSeq,
175
+ agentRunSnapshot: readiness.agentRun,
176
+ })
177
+ if (!markedReady)
178
+ throw new ConnectionAuthorityLostError()
179
+ this.ready = true
180
+
181
+ const pump = new SessionCommandPump({
182
+ sessionId: claim.sessionId,
183
+ sessionSandboxId: claim.sessionSandboxId,
184
+ runtimeConnectionId: claim.runtimeConnectionId,
185
+ afterSequence: readiness.lastReceivedCommandSeq,
186
+ commands: this.dependencies.commands,
187
+ authorityStore: this.dependencies.authorityStore,
188
+ connection,
189
+ logger: this.dependencies.logger,
190
+ onRejected: async (ack) => {
191
+ await this.dependencies.failures.markSessionFailed({
192
+ sessionId: claim.sessionId,
193
+ sessionSandboxId: claim.sessionSandboxId,
194
+ providerSandboxId: claim.providerSandboxId,
195
+ code: ack.errorCode,
196
+ detail: ack.detail,
197
+ })
198
+ connection.close(1008, 'Supervisor command was rejected.')
199
+ },
200
+ })
201
+ this.commandPump = pump
202
+ if (bootstrapAck)
203
+ pump.acknowledge(bootstrapAck)
204
+ pump.start()
205
+ const close = await Promise.race([
206
+ closed.promise,
207
+ aborted(this.controller.signal),
208
+ renewalFailure.promise.then((error) => { throw error }),
209
+ ])
210
+ throw new ConnectionClosedError({ ready: this.ready, detail: 'code' in close ? `${close.code}:${close.reason}` : undefined })
211
+ }
212
+
213
+ wakeCommands(): void {
214
+ this.commandPump?.wake()
215
+ }
216
+
217
+ stop(reason?: unknown): void {
218
+ this.dependencies.parentSignal.removeEventListener('abort', this.parentAbort)
219
+ if (!this.controller.signal.aborted)
220
+ this.controller.abort(reason ?? new DOMException('Connection attempt stopped.', 'AbortError'))
221
+ this.commandPump?.stop()
222
+ this.processor?.stop(reason)
223
+ this.connection?.close(1001, 'Connection attempt stopped.')
224
+ }
225
+
226
+ async drain(): Promise<void> {
227
+ await this.processor?.drain()
228
+ }
229
+
230
+ private async renewLease(): Promise<never> {
231
+ const { claim, timings } = this.dependencies
232
+ let leaseSafetyDeadline = monotonicDeadline(claim.leaseExpiresAt)
233
+ while (true) {
234
+ const untilSafetyDeadline = leaseSafetyDeadline - performance.now()
235
+ if (untilSafetyDeadline <= 0)
236
+ throw new ConnectionAuthorityLostError()
237
+ await delay(Math.min(timings.leaseRenewIntervalMs, untilSafetyDeadline), this.controller.signal)
238
+ if (performance.now() >= leaseSafetyDeadline)
239
+ throw new ConnectionAuthorityLostError()
240
+ const result = await beforeDeadline(this.dependencies.authorityStore.renewConnectionLease({
241
+ sessionSandboxId: claim.sessionSandboxId,
242
+ replicaId: this.dependencies.replicaId,
243
+ generation: claim.runtimeConnectionGeneration,
244
+ leaseDurationMs: timings.leaseDurationMs,
245
+ supervisorLivenessObserved: this.processor?.takeSupervisorLivenessObservation() ?? false,
246
+ }), leaseSafetyDeadline, this.controller.signal, () => new ConnectionAuthorityLostError())
247
+ if (!result.renewed)
248
+ throw new ConnectionAuthorityLostError()
249
+ leaseSafetyDeadline = monotonicDeadline(
250
+ result.leaseExpiresAt ?? new Date(Date.now() + timings.leaseDurationMs),
251
+ )
252
+ if (result.hasOutstandingCommand)
253
+ this.commandPump?.wake()
254
+ }
255
+ }
256
+ }
257
+
258
+ export class DeterministicEstablishmentError extends Error {
259
+ override readonly name = 'DeterministicEstablishmentError'
260
+ readonly code: string
261
+ readonly detail: string | undefined
262
+ readonly reported: boolean
263
+
264
+ constructor(code: string, detail?: string, reported = false) {
265
+ super(detail ?? code)
266
+ this.code = code
267
+ this.detail = detail
268
+ this.reported = reported
269
+ }
270
+ }
271
+
272
+ export class ConnectionAuthorityLostError extends Error {
273
+ override readonly name = 'ConnectionAuthorityLostError'
274
+ }
275
+
276
+ export class ConnectionClosedError extends Error {
277
+ override readonly name = 'ConnectionClosedError'
278
+ readonly ready: boolean
279
+
280
+ constructor(input: { ready: boolean, detail?: string }) {
281
+ super(input.detail ?? 'Supervisor connection closed.')
282
+ this.ready = input.ready
283
+ }
284
+ }
285
+
286
+ async function aborted(signal: AbortSignal): Promise<never> {
287
+ if (signal.aborted)
288
+ throw signal.reason
289
+ return await new Promise<never>((_resolve, reject) => {
290
+ signal.addEventListener('abort', () => reject(signal.reason), { once: true })
291
+ })
292
+ }
293
+
294
+ async function delay(milliseconds: number, signal: AbortSignal): Promise<void> {
295
+ if (signal.aborted)
296
+ throw signal.reason
297
+ await new Promise<void>((resolve, reject) => {
298
+ let timeout: ReturnType<typeof setTimeout>
299
+ const abort = () => {
300
+ clearTimeout(timeout)
301
+ signal.removeEventListener('abort', abort)
302
+ reject(signal.reason)
303
+ }
304
+ timeout = setTimeout(() => {
305
+ signal.removeEventListener('abort', abort)
306
+ resolve()
307
+ }, milliseconds)
308
+ signal.addEventListener('abort', abort, { once: true })
309
+ })
310
+ }
311
+
312
+ function monotonicDeadline(expiresAt: Date): number {
313
+ return performance.now() + Math.max(0, expiresAt.getTime() - Date.now())
314
+ }
315
+
316
+ async function beforeDeadline<T>(
317
+ task: Promise<T>,
318
+ deadline: number,
319
+ signal: AbortSignal,
320
+ timeoutError: () => Error,
321
+ ): Promise<T> {
322
+ signal.throwIfAborted()
323
+ const remaining = deadline - performance.now()
324
+ if (remaining <= 0)
325
+ throw timeoutError()
326
+ let timer: ReturnType<typeof setTimeout>
327
+ let abort: () => void
328
+ const guard = new Promise<never>((_resolve, reject) => {
329
+ abort = () => reject(signal.reason)
330
+ timer = setTimeout(() => reject(timeoutError()), remaining)
331
+ signal.addEventListener('abort', abort, { once: true })
332
+ })
333
+ try {
334
+ return await Promise.race([task, guard])
335
+ }
336
+ finally {
337
+ clearTimeout(timer!)
338
+ signal.removeEventListener('abort', abort!)
339
+ }
340
+ }
@@ -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
+ }