hyperbrane 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 (123) hide show
  1. package/README.md +41 -0
  2. package/dist/agent/agentService.d.ts +31 -0
  3. package/dist/agent/agentService.js +388 -0
  4. package/dist/agent/agentStorage.d.ts +27 -0
  5. package/dist/agent/agentStorage.js +52 -0
  6. package/dist/agent/api.d.ts +264 -0
  7. package/dist/agent/api.js +14 -0
  8. package/dist/agent/gatewaySignIns.d.ts +5 -0
  9. package/dist/agent/gatewaySignIns.js +11 -0
  10. package/dist/agent/session/client.d.ts +14 -0
  11. package/dist/agent/session/client.js +47 -0
  12. package/dist/agent/session/sessionStorage.d.ts +66 -0
  13. package/dist/agent/session/sessionStorage.js +89 -0
  14. package/dist/agent/session/sharedConnection.d.ts +13 -0
  15. package/dist/agent/session/sharedConnection.js +55 -0
  16. package/dist/agent/session/worker/jsonRpc.d.ts +39 -0
  17. package/dist/agent/session/worker/jsonRpc.js +102 -0
  18. package/dist/agent/session/worker/protocol.d.ts +120 -0
  19. package/dist/agent/session/worker/protocol.js +32 -0
  20. package/dist/agent/session/workerConnection.d.ts +29 -0
  21. package/dist/agent/session/workerConnection.js +44 -0
  22. package/dist/agent/session/workerSseConnection.d.ts +15 -0
  23. package/dist/agent/session/workerSseConnection.js +26 -0
  24. package/dist/agent/workerBundle.d.ts +19 -0
  25. package/dist/agent/workerBundle.generated.d.ts +1 -0
  26. package/dist/agent/workerBundle.generated.js +2 -0
  27. package/dist/agent/workerBundle.js +31 -0
  28. package/dist/cli/agent.d.ts +3 -0
  29. package/dist/cli/agent.js +423 -0
  30. package/dist/cli/command.d.ts +46 -0
  31. package/dist/cli/command.js +98 -0
  32. package/dist/cli/dimension.d.ts +2 -0
  33. package/dist/cli/dimension.js +85 -0
  34. package/dist/cli/gateway.d.ts +6 -0
  35. package/dist/cli/gateway.js +245 -0
  36. package/dist/cli/main.d.ts +2 -0
  37. package/dist/cli/main.js +125 -0
  38. package/dist/cli/portal.d.ts +2 -0
  39. package/dist/cli/portal.js +39 -0
  40. package/dist/cli/services.d.ts +18 -0
  41. package/dist/cli/services.js +33 -0
  42. package/dist/cli/streamPrinter.d.ts +23 -0
  43. package/dist/cli/streamPrinter.js +251 -0
  44. package/dist/cli/table.d.ts +7 -0
  45. package/dist/cli/table.js +54 -0
  46. package/dist/dimension/api.d.ts +67 -0
  47. package/dist/dimension/api.js +81 -0
  48. package/dist/dimension/http.d.ts +7 -0
  49. package/dist/dimension/http.js +77 -0
  50. package/dist/dimension/image.d.ts +13 -0
  51. package/dist/dimension/image.js +43 -0
  52. package/dist/dimension/imageFiles.generated.d.ts +1 -0
  53. package/dist/dimension/imageFiles.generated.js +5 -0
  54. package/dist/dimension/paths.d.ts +5 -0
  55. package/dist/dimension/paths.js +7 -0
  56. package/dist/dimension/service.d.ts +22 -0
  57. package/dist/dimension/service.js +242 -0
  58. package/dist/dimension/storage.d.ts +39 -0
  59. package/dist/dimension/storage.js +78 -0
  60. package/dist/index.d.ts +18 -0
  61. package/dist/index.js +25 -0
  62. package/dist/lib/docker-api.d.ts +13787 -0
  63. package/dist/lib/docker-api.js +5 -0
  64. package/dist/lib/docker.d.ts +77 -0
  65. package/dist/lib/docker.js +214 -0
  66. package/dist/lib/image.d.ts +14 -0
  67. package/dist/lib/image.js +29 -0
  68. package/dist/lib/jsonDirectory.d.ts +28 -0
  69. package/dist/lib/jsonDirectory.js +102 -0
  70. package/dist/lib/sleep.d.ts +2 -0
  71. package/dist/lib/sleep.js +4 -0
  72. package/dist/lib/tar.d.ts +2 -0
  73. package/dist/lib/tar.js +65 -0
  74. package/dist/lib/version.d.ts +1 -0
  75. package/dist/lib/version.js +1 -0
  76. package/dist/portal-gateway/api.d.ts +98 -0
  77. package/dist/portal-gateway/api.js +12 -0
  78. package/dist/portal-gateway/fakes.d.ts +3 -0
  79. package/dist/portal-gateway/fakes.js +11 -0
  80. package/dist/portal-gateway/gatewayService.d.ts +37 -0
  81. package/dist/portal-gateway/gatewayService.js +380 -0
  82. package/dist/portal-gateway/gatewayStorage.d.ts +66 -0
  83. package/dist/portal-gateway/gatewayStorage.js +125 -0
  84. package/dist/portal-gateway/image.d.ts +6 -0
  85. package/dist/portal-gateway/image.js +13 -0
  86. package/dist/portal-gateway/imageFiles.generated.d.ts +1 -0
  87. package/dist/portal-gateway/imageFiles.generated.js +8 -0
  88. package/dist/portal-gateway/portalService.d.ts +48 -0
  89. package/dist/portal-gateway/portalService.js +327 -0
  90. package/dist/portal-gateway/portalStorage.d.ts +34 -0
  91. package/dist/portal-gateway/portalStorage.js +44 -0
  92. package/dist/portal-gateway/upstream.d.ts +157 -0
  93. package/dist/portal-gateway/upstream.js +4 -0
  94. package/dist/sandbox/api.d.ts +184 -0
  95. package/dist/sandbox/api.js +14 -0
  96. package/dist/sandbox/boatService.d.ts +52 -0
  97. package/dist/sandbox/boatService.js +335 -0
  98. package/dist/sandbox/containerService.d.ts +15 -0
  99. package/dist/sandbox/containerService.js +177 -0
  100. package/dist/sandbox/endpointProxy.d.ts +90 -0
  101. package/dist/sandbox/endpointProxy.js +701 -0
  102. package/dist/sandbox/exeDevAuth.d.ts +16 -0
  103. package/dist/sandbox/exeDevAuth.js +49 -0
  104. package/dist/sandbox/localService.d.ts +19 -0
  105. package/dist/sandbox/localService.js +182 -0
  106. package/dist/sandbox/sseConnection.d.ts +41 -0
  107. package/dist/sandbox/sseConnection.js +166 -0
  108. package/dist/sandbox/sshService.d.ts +30 -0
  109. package/dist/sandbox/sshService.js +647 -0
  110. package/dist/upstreams/anthropic.d.ts +7 -0
  111. package/dist/upstreams/anthropic.js +27 -0
  112. package/dist/upstreams/index.d.ts +6 -0
  113. package/dist/upstreams/index.js +7 -0
  114. package/dist/upstreams/openai.d.ts +7 -0
  115. package/dist/upstreams/openai.js +40 -0
  116. package/dist/vendor-types/claude/agent-sdk/agentSdkTypes.d.ts +1 -0
  117. package/dist/vendor-types/claude/agent-sdk/bridge.d.ts +388 -0
  118. package/dist/vendor-types/claude/agent-sdk/browser-sdk.d.ts +184 -0
  119. package/dist/vendor-types/claude/agent-sdk/core.d.ts +20 -0
  120. package/dist/vendor-types/claude/agent-sdk/extractFromBunfs.d.ts +1 -0
  121. package/dist/vendor-types/claude/agent-sdk/sdk-tools.d.ts +4153 -0
  122. package/dist/vendor-types/claude/agent-sdk/sdk.d.ts +9833 -0
  123. package/package.json +55 -0
@@ -0,0 +1,264 @@
1
+ import type { ModelInfo, SDKBackgroundTasksChangedMessage, PermissionResult, SDKAssistantMessage, SDKPartialAssistantMessage, SDKResultMessage, SDKSystemMessage, SDKUserMessage } from "../vendor-types/claude/agent-sdk/sdk.js";
2
+ import type { AskUserQuestionInput } from "../vendor-types/claude/agent-sdk/sdk-tools.js";
3
+ import type { CPULimit, MemoryLimit } from "../dimension/api.ts";
4
+ import type { SandboxEndpoint, SandboxEndpointScheme } from "../sandbox/api.ts";
5
+ /** A session worker running in a dimension; it hosts that agent's sessions. */
6
+ export type Agent = {
7
+ name: string;
8
+ dimensionName: string;
9
+ };
10
+ /** The agent CLIs a session can run (agent/session/<backend>/). */
11
+ export declare const AGENT_BACKENDS: readonly ["claude", "codex"];
12
+ export type AgentBackend = (typeof AGENT_BACKENDS)[number];
13
+ export type AgentSession = {
14
+ name: string;
15
+ backend: AgentBackend;
16
+ memoryLimit: MemoryLimit;
17
+ cpuLimit: CPULimit;
18
+ /** Dimension path the agent works in; defaults to the workspace. */
19
+ cwd?: string;
20
+ /** Extra environment for the agent CLI (e.g. ANTHROPIC_BASE_URL). */
21
+ env?: Record<string, string>;
22
+ };
23
+ /** What `updateSession` can change; omitted limits stay as they are. */
24
+ export type AgentSessionLimits = Partial<Pick<AgentSession, "memoryLimit" | "cpuLimit">>;
25
+ /**
26
+ * Where a session's CLI stands, as the CLI itself reports it (claude's session state, codex's thread
27
+ * status). "stopped": no CLI running (none started yet, or it exited; the next message starts one).
28
+ * "waiting": a turn is blocked on a permission request or a question. "error": the CLI says it can't
29
+ * go on (codex's systemError).
30
+ */
31
+ export type AgentSessionState = "stopped" | "starting" | "idle" | "running" | "waiting" | "error";
32
+ export type AgentSessionInfo = AgentSession & {
33
+ agentName: string;
34
+ status: AgentSessionState;
35
+ /** The backend's own id for the conversation (claude's session id, codex's thread id), once known. */
36
+ nativeId: string | null;
37
+ /** Chosen through AgentModels; null for the backend's default. */
38
+ model: string | null;
39
+ };
40
+ /** Where a session lives: sessions are named per agent. */
41
+ export type SessionRef = {
42
+ agentName: string;
43
+ sessionName: string;
44
+ };
45
+ export interface AgentClient {
46
+ /** PUT /v1/agents/:agentName — starts (or updates) the worker in the dimension. */
47
+ createAgent(agent: Agent): Promise<Agent>;
48
+ /** DELETE /v1/agents/:agentName — stops the worker and its sessions. */
49
+ stopAgent(agentName: string): Promise<void>;
50
+ /** GET /v1/agents */
51
+ listAgents(): Promise<Agent[]>;
52
+ /** POST /v1/agents/:agentName/sessions — idempotent for the same spec. */
53
+ createSession(agentName: string, session: AgentSession): Promise<AgentSessionInfo>;
54
+ /** PATCH /v1/agents/:agentName/sessions/:sessionName — changes its limits. */
55
+ updateSession(ref: SessionRef, limits: AgentSessionLimits): Promise<AgentSessionInfo>;
56
+ /** GET /v1/sessions — every agent's sessions. */
57
+ listSessions(): Promise<AgentSessionInfo[]>;
58
+ /** DELETE /v1/agents/:agentName/sessions/:sessionName — ends the CLI and forgets the session. */
59
+ stopSession(ref: SessionRef): Promise<void>;
60
+ }
61
+ export type AgentStreamingRequest = {
62
+ /**
63
+ * The `uuid` of the last user or assistant message already seen (e.g. the prompt just sent);
64
+ * omitted to read the whole conversation. Those uuids are the backend's own ids for the messages
65
+ * it stores, so a stream resumes from the backend's transcript; partial output and results aren't
66
+ * stored and can't be resumed from.
67
+ */
68
+ after_id?: string;
69
+ };
70
+ /**
71
+ * The SDK messages a session streams. `stream_event` messages are the partial assistant output;
72
+ * `assistant`/`user` the complete messages (tool calls and their results included), each with the
73
+ * uuid its backend stores it under; `result` ends each turn. codex sessions emit no `system` message.
74
+ */
75
+ export type AgentStreamingMessage = SDKSystemMessage | SDKAssistantMessage | SDKPartialAssistantMessage | (SDKUserMessage & {
76
+ uuid: string;
77
+ session_id: string;
78
+ }) | SDKResultMessage;
79
+ /** A complete message of the conversation so far (no partial output), as the stream carried it. */
80
+ export type AgentTranscriptMessage = Extract<AgentStreamingMessage, {
81
+ type: "user" | "assistant";
82
+ }>;
83
+ export type AgentTranscriptRequest = {
84
+ /** How many messages a page holds, about (a tool call and its result share a page); default 50, at most 500. */
85
+ limit?: number;
86
+ /** A page's `nextCursor`, for the page before it; omitted for the newest page. */
87
+ cursor?: string;
88
+ };
89
+ /** Newest pages first; each page's messages oldest first. */
90
+ export type AgentTranscriptPage = {
91
+ messages: AgentTranscriptMessage[];
92
+ nextCursor: string | null;
93
+ };
94
+ export type UserMessage = SDKUserMessage;
95
+ /**
96
+ * A tool call waiting for permission: claude's canUseTool call. The SDK's own permission request (its
97
+ * `can_use_tool` control request) isn't exported, so this mirrors it.
98
+ */
99
+ export type AgentPermissionRequest = {
100
+ id: string;
101
+ tool_name: string;
102
+ input: Record<string, unknown>;
103
+ tool_use_id: string;
104
+ /** Why the agent asks, when it says (e.g. codex's reason for wanting network access). */
105
+ decision_reason?: string;
106
+ };
107
+ export type AgentPermissionResult = PermissionResult;
108
+ type AskUserQuestion = AskUserQuestionInput["questions"][number];
109
+ /** A question of claude's AskUserQuestion tool; codex's may come with any number of options. */
110
+ export type AgentQuestion = Omit<AskUserQuestion, "options"> & {
111
+ options: AskUserQuestion["options"][number][];
112
+ };
113
+ /** The agent asking the user something (claude's AskUserQuestion, codex's request_user_input). */
114
+ export type AgentQuestionRequest = {
115
+ id: string;
116
+ tool_use_id: string;
117
+ questions: AgentQuestion[];
118
+ };
119
+ /**
120
+ * AskUserQuestion's own answer: question text -> the chosen label (labels joined with ", " for a
121
+ * multi-select), or the user's own words.
122
+ */
123
+ export type AgentQuestionResponse = {
124
+ answers: NonNullable<AskUserQuestionInput["answers"]>;
125
+ };
126
+ /** One of the models a session can be switched to. */
127
+ export type AgentModelInfo = ModelInfo;
128
+ /**
129
+ * A task the agent keeps running after the tool call that started it has returned: claude's
130
+ * background commands and agents (its own shape, from background_tasks_changed), codex's background
131
+ * terminals.
132
+ */
133
+ export type AgentBackgroundTask = SDKBackgroundTasksChangedMessage["tasks"][number];
134
+ /**
135
+ * Where a session's CLI stands with its sign-in. The CLI signs in as it normally would, through the
136
+ * dimension's gateway, which keeps the real tokens and gives the CLI fakes. "pending": a sign-in is
137
+ * waiting for the user to open `url` (and, for codex, enter `userCode` there; for claude, send back
138
+ * the code the page shows, when `needsCode`).
139
+ */
140
+ export type AgentAuthStatus = {
141
+ state: "signedIn";
142
+ } | {
143
+ state: "signedOut"; /** Why the last sign-in failed, if one did. */
144
+ error?: string;
145
+ } | {
146
+ state: "pending";
147
+ url: string;
148
+ userCode?: string;
149
+ needsCode: boolean;
150
+ };
151
+ export interface AgentProvision {
152
+ /** POST /v1/agents/:agentName/backends/:backend/install — installs the backend in the dimension, or updates it. */
153
+ install(agentName: string, backend: AgentBackend): Promise<void>;
154
+ }
155
+ export interface AgentEndpoint {
156
+ /**
157
+ * POST /v1/agents/:agentName/endpoint — the agent's worker, reachable from the client over the
158
+ * network (workerSseConnector speaks to it), authenticated by `scheme`, callable from pages of
159
+ * `allowedOrigins`. Call it before each connection: it starts the endpoint's proxy again if that
160
+ * stopped, keeping the same URL and credentials; a new endpoint only for another scheme or origins,
161
+ * or once the old one can't be served (closed, its port taken). Closed with the agent. Needs a host
162
+ * that declares endpoints.
163
+ */
164
+ exposeAgent(agentName: string, scheme: SandboxEndpointScheme, allowedOrigins?: string[]): Promise<SandboxEndpoint>;
165
+ }
166
+ export interface AgentStream {
167
+ /**
168
+ * GET /v1/agents/:agentName/sessions/:sessionName/stream?after_id= — the complete messages after
169
+ * `after_id` from the backend's transcript, then each message as the session produces it, pushed
170
+ * by the worker (session/subscribe) until the caller stops reading. Throws if the worker goes
171
+ * away; resume with the last user or assistant uuid seen.
172
+ */
173
+ getStream(ref: SessionRef, req: AgentStreamingRequest): AsyncGenerator<AgentStreamingMessage>;
174
+ }
175
+ export interface AgentTranscript {
176
+ /**
177
+ * GET /v1/agents/:agentName/sessions/:sessionName/transcript?limit=&cursor= — a page of the
178
+ * conversation, read from the backend's own storage a piece at a time, so a conversation of any
179
+ * length can be paged through.
180
+ */
181
+ getTranscript(ref: SessionRef, req?: AgentTranscriptRequest): Promise<AgentTranscriptPage>;
182
+ }
183
+ export interface AgentInterrupt {
184
+ /** POST /v1/agents/:agentName/sessions/:sessionName/interrupt — ends the current turn; pending permission requests are denied and questions dropped. */
185
+ cancel(ref: SessionRef): Promise<void>;
186
+ }
187
+ export interface AgentInput {
188
+ /**
189
+ * POST /v1/agents/:agentName/sessions/:sessionName/messages — resolves once the agent confirms it
190
+ * received the message (it starts a turn, or waits in the agent's own queue); fails if the agent
191
+ * refuses it or doesn't confirm.
192
+ */
193
+ sendMessage(ref: SessionRef, message: UserMessage): Promise<void>;
194
+ }
195
+ export interface AgentSessionStatus {
196
+ /** GET /v1/agents/:agentName/sessions/:sessionName/status */
197
+ getSessionStatus(ref: SessionRef): Promise<AgentSessionState>;
198
+ }
199
+ export interface AgentMessageQueue {
200
+ /**
201
+ * GET /v1/agents/:agentName/sessions/:sessionName/queue — from the agent (claude's command
202
+ * lifecycle, codex's turn items), oldest first: the last message it took into a turn, then every
203
+ * one it still holds queued. While no CLI runs (nothing can be queued), the last prompt in its
204
+ * transcript. Empty before the first message.
205
+ */
206
+ getLastUserMessages(ref: SessionRef): Promise<AgentLastUserMessage[]>;
207
+ }
208
+ export interface AgentPermissions {
209
+ /** GET /v1/agents/:agentName/sessions/:sessionName/permission-requests — the ones still waiting. */
210
+ listPermissionRequests(ref: SessionRef): Promise<AgentPermissionRequest[]>;
211
+ /** POST /v1/agents/:agentName/sessions/:sessionName/permission-requests/:requestId */
212
+ respondPermissionRequest(ref: SessionRef, requestId: string, result: AgentPermissionResult): Promise<void>;
213
+ }
214
+ export interface AgentQuestions {
215
+ /** GET /v1/agents/:agentName/sessions/:sessionName/question-requests — the ones still waiting. */
216
+ listQuestionRequests(ref: SessionRef): Promise<AgentQuestionRequest[]>;
217
+ /** POST /v1/agents/:agentName/sessions/:sessionName/question-requests/:requestId */
218
+ respondQuestionRequest(ref: SessionRef, requestId: string, response: AgentQuestionResponse): Promise<void>;
219
+ }
220
+ export interface AgentBackgroundTasks {
221
+ /** GET /v1/agents/:agentName/sessions/:sessionName/background-tasks — those still running; none while the CLI isn't. */
222
+ listBackgroundTasks(ref: SessionRef): Promise<AgentBackgroundTask[]>;
223
+ /** POST /v1/agents/:agentName/sessions/:sessionName/background-tasks/:taskId/stop — ends the task. */
224
+ stopTask(ref: SessionRef, taskId: string): Promise<void>;
225
+ }
226
+ export interface AgentAuth {
227
+ /** GET /v1/agents/:agentName/sessions/:sessionName/auth */
228
+ getAuthStatus(ref: SessionRef): Promise<AgentAuthStatus>;
229
+ /**
230
+ * POST /v1/agents/:agentName/sessions/:sessionName/auth/login — signs the session's CLI in with
231
+ * its own login. When the dimension's gateway already holds the upstream's credential, the sign-in
232
+ * completes without anyone (the gateway answers it) and this resolves signed in; otherwise it
233
+ * resolves pending, with the link for the user, and the gateway keeps the tokens once they sign in.
234
+ */
235
+ login(ref: SessionRef): Promise<AgentAuthStatus>;
236
+ /** POST /v1/agents/:agentName/sessions/:sessionName/auth/code — finishes a pending sign-in that needs the page's code. */
237
+ submitAuthCode(ref: SessionRef, code: string): Promise<AgentAuthStatus>;
238
+ }
239
+ export interface AgentModels {
240
+ /** GET /v1/agents/:agentName/sessions/:sessionName/models — starts the CLI if it isn't running. */
241
+ listModels(ref: SessionRef): Promise<AgentModelInfo[]>;
242
+ /** PUT /v1/agents/:agentName/sessions/:sessionName/model — from the next turn on; null for the backend's default. */
243
+ setModel(ref: SessionRef, model: string | null): Promise<void>;
244
+ }
245
+ /** Everything a session can do, each call naming the session. */
246
+ export type AgentSessionCapabilities = AgentStream & AgentTranscript & AgentInterrupt & AgentInput & AgentSessionStatus & AgentMessageQueue & AgentPermissions & AgentQuestions & AgentBackgroundTasks & AgentAuth & AgentModels;
247
+ /** A message sent to a session, and whether its agent still holds it queued (not yet taken into a turn). */
248
+ export type AgentLastUserMessage = {
249
+ message: UserMessage;
250
+ queued: boolean;
251
+ };
252
+ /** The request can't be satisfied as sent (bad name, unknown backend, unknown after_id, ...). */
253
+ export declare class AgentInvalidError extends Error {
254
+ name: string;
255
+ }
256
+ /** No agent, session, permission or question request by that name. */
257
+ export declare class AgentNotFoundError extends Error {
258
+ name: string;
259
+ }
260
+ /** The name is taken with a different spec. */
261
+ export declare class AgentConflictError extends Error {
262
+ name: string;
263
+ }
264
+ export {};
@@ -0,0 +1,14 @@
1
+ /** The agent CLIs a session can run (agent/session/<backend>/). */
2
+ export const AGENT_BACKENDS = ["claude", "codex"];
3
+ /** The request can't be satisfied as sent (bad name, unknown backend, unknown after_id, ...). */
4
+ export class AgentInvalidError extends Error {
5
+ name = "AgentInvalidError";
6
+ }
7
+ /** No agent, session, permission or question request by that name. */
8
+ export class AgentNotFoundError extends Error {
9
+ name = "AgentNotFoundError";
10
+ }
11
+ /** The name is taken with a different spec. */
12
+ export class AgentConflictError extends Error {
13
+ name = "AgentConflictError";
14
+ }
@@ -0,0 +1,5 @@
1
+ import type { GatewayService } from "../portal-gateway/gatewayService.ts";
2
+ import type { PortalService } from "../portal-gateway/portalService.ts";
3
+ /** Whether a gateway behind the dimension's portals holds an OAuth sign-in for the upstream. */
4
+ export type GatewaySignIns = (dimensionName: string, upstreamName: string) => Promise<boolean>;
5
+ export declare function createGatewaySignIns(portals: PortalService, gateways: GatewayService): GatewaySignIns;
@@ -0,0 +1,11 @@
1
+ export function createGatewaySignIns(portals, gateways) {
2
+ return async (dimensionName, upstreamName) => {
3
+ const gatewayNames = new Set((await portals.listPortals()).filter((portal) => portal.dimensionName === dimensionName).map((portal) => portal.gatewayName));
4
+ for (const gatewayName of gatewayNames) {
5
+ const credentials = await gateways.listCredentials(gatewayName);
6
+ if (credentials.some((credential) => credential.name === `${upstreamName}.oauth`))
7
+ return true;
8
+ }
9
+ return false;
10
+ };
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { AgentStreamingMessage } from "../api.ts";
2
+ import type { WorkerMethod, WorkerRequests } from "./worker/protocol.ts";
3
+ import type { ConnectWorker } from "./workerConnection.ts";
4
+ /** The worker's protocol (protocol.ts). Errors come back as the Agent*Error they were. */
5
+ export type WorkerClient = {
6
+ call<M extends WorkerMethod>(method: M, params: WorkerRequests[M][0]): Promise<WorkerRequests[M][1]>;
7
+ /**
8
+ * The session's messages after `afterId`, then each one as the worker produces it, over a
9
+ * connection held open while the caller reads. Throws once the worker goes away.
10
+ */
11
+ stream(sessionName: string, afterId: string | undefined): AsyncGenerator<AgentStreamingMessage>;
12
+ };
13
+ /** Calls the worker over the connections `connect` opens (workerSocketConnector: its unix socket). */
14
+ export declare function createWorkerClient(connect: ConnectWorker): WorkerClient;
@@ -0,0 +1,47 @@
1
+ /** Calls the worker over the connections `connect` opens (workerSocketConnector: its unix socket). */
2
+ export function createWorkerClient(connect) {
3
+ /** One request on a connection of its own. */
4
+ async function call(method, params) {
5
+ const connection = await connect();
6
+ try {
7
+ return await connection.request(method, params);
8
+ }
9
+ finally {
10
+ connection.close();
11
+ }
12
+ }
13
+ async function* stream(session, afterId) {
14
+ const subscription = crypto.randomUUID();
15
+ const queued = [];
16
+ let wake;
17
+ let ended = false;
18
+ const connection = await connect((method, params) => {
19
+ if (method !== "session/message" || params.subscription !== subscription)
20
+ return;
21
+ queued.push(params.message);
22
+ wake?.();
23
+ });
24
+ connection.closed.then(() => {
25
+ ended = true;
26
+ wake?.();
27
+ });
28
+ try {
29
+ await connection.request("session/subscribe", { session, subscription, after_id: afterId });
30
+ for (;;) {
31
+ while (queued.length > 0)
32
+ yield queued.shift();
33
+ if (ended)
34
+ throw new Error(`the worker closed the stream of session ${session}`);
35
+ await new Promise((resolve) => (wake = resolve));
36
+ wake = undefined;
37
+ }
38
+ }
39
+ finally {
40
+ // On a shared connection (sharedConnector) the worker would go on sending to it otherwise.
41
+ if (!ended)
42
+ connection.request("session/unsubscribe", { subscription }).catch(() => { });
43
+ connection.close();
44
+ }
45
+ }
46
+ return { call, stream };
47
+ }
@@ -0,0 +1,66 @@
1
+ import type { Sandbox } from "../../sandbox/api.ts";
2
+ import type { AgentAuthStatus, AgentBackend, AgentPermissionRequest, AgentQuestionRequest, AgentSession } from "../api.ts";
3
+ /** What the worker keeps of a session: never its messages, which are the backend's to store. */
4
+ export type SessionRecord = {
5
+ spec: AgentSession;
6
+ /** The backend's id for the conversation, once known: what a new worker resumes. */
7
+ nativeId: string | null;
8
+ /** Chosen through AgentModels; null for the backend's default. */
9
+ model: string | null;
10
+ };
11
+ /** Where a session's CLI stands with its sign-in, as far as it outlives a request. */
12
+ export type SessionAuthRecord = {
13
+ /** Where the CLI's login in progress asked the user to sign in; null when none is. */
14
+ login: Extract<AgentAuthStatus, {
15
+ state: "pending";
16
+ }> | null;
17
+ /** Why the last login failed, if it did. */
18
+ error: string | null;
19
+ };
20
+ /** Every name must pass checkName; operations throw InvalidNameError otherwise. */
21
+ export interface SessionStorage {
22
+ getSessions(): Promise<SessionRecord[]>;
23
+ /** Stores the record unless one with that name exists; returns whichever record is stored afterwards. */
24
+ insertSession(record: SessionRecord): Promise<SessionRecord>;
25
+ /** Replaces the record. */
26
+ saveSession(record: SessionRecord): Promise<void>;
27
+ /** Removes the record and everything kept for the session. No-op if missing. */
28
+ removeSession(sessionName: string): Promise<void>;
29
+ /** The permission requests the session's CLI is waiting on, oldest first. */
30
+ getPermissionRequests(sessionName: string): Promise<AgentPermissionRequest[]>;
31
+ savePermissionRequest(sessionName: string, request: AgentPermissionRequest): Promise<void>;
32
+ /** No-op if missing. */
33
+ removePermissionRequest(sessionName: string, requestId: string): Promise<void>;
34
+ /** The questions the session's CLI is waiting on, oldest first. */
35
+ getQuestionRequests(sessionName: string): Promise<AgentQuestionRequest[]>;
36
+ saveQuestionRequest(sessionName: string, request: AgentQuestionRequest): Promise<void>;
37
+ /** No-op if missing. */
38
+ removeQuestionRequest(sessionName: string, requestId: string): Promise<void>;
39
+ /** Null until the session's CLI has been asked to sign in. */
40
+ readAuth(sessionName: string): Promise<SessionAuthRecord | null>;
41
+ saveAuth(sessionName: string, record: SessionAuthRecord): Promise<void>;
42
+ /** Sandbox path of the unix socket the agent's worker listens on (see workerSocketPath). */
43
+ socketPath(): Promise<string>;
44
+ /**
45
+ * Sandbox path of the backend CLI's home (claude's config directory, codex's CODEX_HOME): its
46
+ * sign-in, config and conversations, kept with the rest of the dimension's state. Shared by the
47
+ * dimension's agents, like a home would be. Creates it.
48
+ */
49
+ backendHome(backend: AgentBackend): Promise<string>;
50
+ }
51
+ /**
52
+ * The agent's worker socket in a dimension's run directory: RUN_PATH inside the dimension, or the
53
+ * host side of that mount, so the host reaches the worker through it.
54
+ */
55
+ export declare function workerSocketPath(runDirectory: string, agentName: string): string;
56
+ /**
57
+ * `stateDirectory` is a sandbox path: the dimension's state directory (STATE_PATH, mounted from the
58
+ * host's hyperbrane storage, so everything here lives there with the rest). Layout:
59
+ * agents/<agent>/sessions/<session>.json, and agents/<agent>/sessions/<session>/ holding
60
+ * permission-requests/<id>.json, question-requests/<id>.json and auth.json; backends/<backend>/
61
+ * (the CLIs' homes). The socket goes in `runDirectory`, the dimension's run directory.
62
+ */
63
+ export declare function createSessionStorage(sandbox: Sandbox, agentName: string, { stateDirectory, runDirectory }?: {
64
+ stateDirectory?: string;
65
+ runDirectory?: string;
66
+ }): SessionStorage;
@@ -0,0 +1,89 @@
1
+ import { RUN_PATH, STATE_PATH } from "../../dimension/paths.js";
2
+ import { checkName, createJsonDirectory } from "../../lib/jsonDirectory.js";
3
+ /**
4
+ * The agent's worker socket in a dimension's run directory: RUN_PATH inside the dimension, or the
5
+ * host side of that mount, so the host reaches the worker through it.
6
+ */
7
+ export function workerSocketPath(runDirectory, agentName) {
8
+ return `${runDirectory}/${agentName}.sock`;
9
+ }
10
+ /**
11
+ * `stateDirectory` is a sandbox path: the dimension's state directory (STATE_PATH, mounted from the
12
+ * host's hyperbrane storage, so everything here lives there with the rest). Layout:
13
+ * agents/<agent>/sessions/<session>.json, and agents/<agent>/sessions/<session>/ holding
14
+ * permission-requests/<id>.json, question-requests/<id>.json and auth.json; backends/<backend>/
15
+ * (the CLIs' homes). The socket goes in `runDirectory`, the dimension's run directory.
16
+ */
17
+ export function createSessionStorage(sandbox, agentName, { stateDirectory = STATE_PATH, runDirectory = RUN_PATH } = {}) {
18
+ const sessionsDir = `${stateDirectory}/agents/${agentName}/sessions`;
19
+ const sessions = createJsonDirectory(sandbox, async () => sessionsDir);
20
+ const sessionDir = (sessionName) => `${sessionsDir}/${checkName(sessionName)}`;
21
+ const permissionRequests = (sessionName) => createJsonDirectory(sandbox, async () => `${sessionDir(sessionName)}/permission-requests`);
22
+ const questionRequests = (sessionName) => createJsonDirectory(sandbox, async () => `${sessionDir(sessionName)}/question-requests`);
23
+ const sessionFiles = (sessionName) => createJsonDirectory(sandbox, async () => sessionDir(sessionName));
24
+ async function oldestFirst(stored) {
25
+ return (await stored).sort((a, b) => a.at - b.at).map((entry) => entry.request);
26
+ }
27
+ async function getSessions() {
28
+ return sessions.list();
29
+ }
30
+ async function insertSession(record) {
31
+ return sessions.insert(record.spec.name, record);
32
+ }
33
+ async function saveSession(record) {
34
+ await sessions.save(record.spec.name, record);
35
+ }
36
+ // Everything kept for it first: while the record is there, the removal can be retried.
37
+ async function removeSession(sessionName) {
38
+ await sandbox.removeDirectory(sessionDir(sessionName));
39
+ await sessions.remove(sessionName);
40
+ }
41
+ async function getPermissionRequests(sessionName) {
42
+ return oldestFirst(permissionRequests(sessionName).list());
43
+ }
44
+ async function savePermissionRequest(sessionName, request) {
45
+ await permissionRequests(sessionName).save(request.id, { request, at: Date.now() });
46
+ }
47
+ async function removePermissionRequest(sessionName, requestId) {
48
+ await permissionRequests(sessionName).remove(requestId);
49
+ }
50
+ async function getQuestionRequests(sessionName) {
51
+ return oldestFirst(questionRequests(sessionName).list());
52
+ }
53
+ async function saveQuestionRequest(sessionName, request) {
54
+ await questionRequests(sessionName).save(request.id, { request, at: Date.now() });
55
+ }
56
+ async function removeQuestionRequest(sessionName, requestId) {
57
+ await questionRequests(sessionName).remove(requestId);
58
+ }
59
+ async function readAuth(sessionName) {
60
+ return sessionFiles(sessionName).read("auth");
61
+ }
62
+ async function saveAuth(sessionName, record) {
63
+ await sessionFiles(sessionName).save("auth", record);
64
+ }
65
+ async function socketPath() {
66
+ return workerSocketPath(runDirectory, agentName);
67
+ }
68
+ async function backendHome(backend) {
69
+ const path = `${stateDirectory}/backends/${backend}`;
70
+ await sandbox.createDirectory(path);
71
+ return path;
72
+ }
73
+ return {
74
+ getSessions,
75
+ insertSession,
76
+ saveSession,
77
+ removeSession,
78
+ getPermissionRequests,
79
+ savePermissionRequest,
80
+ removePermissionRequest,
81
+ getQuestionRequests,
82
+ saveQuestionRequest,
83
+ removeQuestionRequest,
84
+ readAuth,
85
+ saveAuth,
86
+ socketPath,
87
+ backendHome,
88
+ };
89
+ }
@@ -0,0 +1,13 @@
1
+ import type { ConnectWorker } from "./workerConnection.ts";
2
+ export type SharedConnector = ConnectWorker & {
3
+ /** Closes the connection they share (a later connect opens another). */
4
+ close(): void;
5
+ };
6
+ /**
7
+ * One connection to the worker, shared by everyone `connect`s through it: each gets a view of it
8
+ * (its requests go over it, the worker's notifications reach every view, closing a view only
9
+ * detaches it), so calls cost a round trip each rather than a connection each. Over an endpoint
10
+ * that's what matters (workerSseConnector opens an event stream per connection). The connection is
11
+ * opened on first use, and again after it closes.
12
+ */
13
+ export declare function sharedConnector(connect: ConnectWorker): SharedConnector;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * One connection to the worker, shared by everyone `connect`s through it: each gets a view of it
3
+ * (its requests go over it, the worker's notifications reach every view, closing a view only
4
+ * detaches it), so calls cost a round trip each rather than a connection each. Over an endpoint
5
+ * that's what matters (workerSseConnector opens an event stream per connection). The connection is
6
+ * opened on first use, and again after it closes.
7
+ */
8
+ export function sharedConnector(connect) {
9
+ let current = null;
10
+ function open() {
11
+ if (current)
12
+ return current;
13
+ const handlers = new Set();
14
+ const dispatch = (method, params) => {
15
+ for (const handler of handlers)
16
+ handler(method, params);
17
+ };
18
+ const opening = connect(dispatch).then((connection) => {
19
+ void connection.closed.then(() => {
20
+ if (current === opening)
21
+ current = null;
22
+ });
23
+ return { connection, handlers };
24
+ });
25
+ opening.catch(() => {
26
+ if (current === opening)
27
+ current = null;
28
+ });
29
+ current = opening;
30
+ return opening;
31
+ }
32
+ const shared = (async (onNotification) => {
33
+ const { connection, handlers } = await open();
34
+ if (onNotification)
35
+ handlers.add(onNotification);
36
+ let detach;
37
+ const closed = new Promise((resolve) => (detach = resolve));
38
+ void connection.closed.then(() => detach());
39
+ return {
40
+ request: (method, params) => connection.request(method, params),
41
+ closed,
42
+ close() {
43
+ if (onNotification)
44
+ handlers.delete(onNotification);
45
+ detach();
46
+ },
47
+ };
48
+ });
49
+ shared.close = () => {
50
+ const closing = current;
51
+ current = null;
52
+ void closing?.then(({ connection }) => connection.close(), () => { });
53
+ };
54
+ return shared;
55
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * JSON-RPC 2.0 over a byte stream, one JSON object per line (the framing codex's app-server uses).
3
+ * Either end can send requests and notifications; the worker protocol (protocol.ts) is built on it.
4
+ */
5
+ export type JsonRpcId = string | number;
6
+ export type JsonRpcErrorObject = {
7
+ code: number;
8
+ message: string;
9
+ data?: unknown;
10
+ };
11
+ export type JsonRpcPeerOptions = {
12
+ /** Answers a request; what it throws goes back as an error response, through `encodeError`. */
13
+ onRequest(method: string, params: unknown): Promise<unknown>;
14
+ onNotification?(method: string, params: unknown): void;
15
+ encodeError(error: unknown): JsonRpcErrorObject;
16
+ decodeError(error: JsonRpcErrorObject): Error;
17
+ };
18
+ export interface JsonRpcPeer {
19
+ /** Feeds text read from the connection; complete lines are handled as they arrive. */
20
+ receive(text: string): void;
21
+ request(method: string, params: unknown): Promise<unknown>;
22
+ notify(method: string, params: unknown): void;
23
+ /** Fails every request still waiting for its response. */
24
+ close(error: Error): void;
25
+ }
26
+ export declare function createJsonRpcPeer(write: (line: string) => void, options: JsonRpcPeerOptions): JsonRpcPeer;
27
+ /** The part of a Bun socket a writer needs. */
28
+ type WritableSocket = {
29
+ write(data: Uint8Array): number;
30
+ };
31
+ /**
32
+ * Writes to a Bun socket without losing what it can't take yet: the rest is kept and written on
33
+ * `drain`, which the socket's drain handler must call.
34
+ */
35
+ export declare function createSocketWriter(socket: WritableSocket): {
36
+ write(text: string): void;
37
+ drain(): void;
38
+ };
39
+ export {};