hyperbrane 0.1.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.
- package/README.md +12 -2
- package/dist/agent/agentService.d.ts +1 -0
- package/dist/agent/agentService.js +16 -1
- package/dist/agent/agentStorage.d.ts +2 -0
- package/dist/agent/agentStorage.js +4 -2
- package/dist/agent/api.d.ts +51 -27
- package/dist/agent/client.d.ts +3 -0
- package/dist/agent/client.js +5 -0
- package/dist/agent/hostTranscript.d.ts +13 -0
- package/dist/agent/hostTranscript.js +190 -0
- package/dist/agent/session/backend.d.ts +80 -0
- package/dist/agent/session/backend.js +1 -0
- package/dist/agent/session/claude/home.d.ts +2 -0
- package/dist/agent/session/claude/home.js +4 -0
- package/dist/agent/session/claude/transcript.d.ts +13 -0
- package/dist/agent/session/claude/transcript.js +29 -0
- package/dist/agent/session/client.d.ts +5 -1
- package/dist/agent/session/codex/home.d.ts +2 -0
- package/dist/agent/session/codex/home.js +4 -0
- package/dist/agent/session/codex/messages.d.ts +30 -0
- package/dist/agent/session/codex/messages.js +135 -0
- package/dist/agent/session/codex/transcript.d.ts +12 -0
- package/dist/agent/session/codex/transcript.js +22 -0
- package/dist/agent/session/fileTranscript.d.ts +92 -0
- package/dist/agent/session/fileTranscript.js +454 -0
- package/dist/agent/session/sdkMessages.d.ts +15 -0
- package/dist/agent/session/sdkMessages.js +28 -0
- package/dist/agent/session/sessionStorage.d.ts +4 -0
- package/dist/agent/session/sessionStorage.js +13 -2
- package/dist/agent/session/sharedConnection.d.ts +1 -0
- package/dist/agent/session/storedTranscripts.d.ts +13 -0
- package/dist/agent/session/storedTranscripts.js +8 -0
- package/dist/agent/session/worker/protocol.d.ts +1 -0
- package/dist/agent/session/worker/workerParams.d.ts +11 -0
- package/dist/agent/session/worker/workerParams.js +162 -0
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +10 -0
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +22 -0
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +11 -0
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +23 -0
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/SleepItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +14 -0
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +3 -0
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +19 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +10 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +4 -0
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +8 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +7 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +3 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +5 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +6 -0
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +8 -0
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +1 -0
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +11 -0
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +215 -0
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +35 -0
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +2 -0
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +14 -0
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +2 -0
- package/dist/agent/workerBundle.generated.js +1 -1
- package/dist/cli/agent.js +17 -4
- package/dist/cli/services.d.ts +6 -5
- package/dist/dimension/api.d.ts +5 -4
- package/dist/dimension/paths.d.ts +4 -0
- package/dist/dimension/paths.js +10 -2
- package/dist/dimension/service.d.ts +1 -0
- package/dist/dimension/storage.js +3 -2
- package/dist/index.d.ts +1 -3
- package/dist/index.js +4 -5
- package/dist/lib/docker.d.ts +1 -0
- package/dist/portal-gateway/api.d.ts +6 -5
- package/dist/portal-gateway/gatewayService.d.ts +1 -0
- package/dist/portal-gateway/portalService.d.ts +2 -0
- package/dist/sandbox/api.d.ts +49 -21
- package/dist/sandbox/boatService.js +11 -3
- package/dist/sandbox/containerService.js +10 -3
- package/dist/sandbox/localService.js +20 -2
- package/dist/sandbox/sshService.js +37 -4
- package/package.json +25 -8
package/README.md
CHANGED
|
@@ -30,12 +30,22 @@ await agents.createAgent({ name: "dev", dimensionName: "work" });
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
The root module exports the services, the sandboxes (`createLocalSandbox`, `createSshSandbox`,
|
|
33
|
-
`createContainerSandbox`, `createBoatSandbox`), the endpoint auths and the agent client.
|
|
34
|
-
|
|
33
|
+
`createContainerSandbox`, `createBoatSandbox`), the endpoint auths and the agent client. For a
|
|
34
|
+
browser, which can't take the server side along, the package also exports each part's API
|
|
35
|
+
(`hyperbrane/sandbox/api`, `hyperbrane/dimension/api`, `hyperbrane/portal-gateway/api`,
|
|
36
|
+
`hyperbrane/agent/api`) and the agent's worker client (`hyperbrane/agent/client`). Nothing else is
|
|
37
|
+
importable.
|
|
38
|
+
|
|
39
|
+
What a client may rely on, as the types say: a `readonly` field is set by the package, for the client
|
|
40
|
+
to read; a type marked `@sealed` (the services, sandboxes, clients) is made by the package, for the
|
|
41
|
+
client to use, not implement. Both may gain members in any release; anything else gains only optional
|
|
42
|
+
ones, until a breaking release (`bun run api:diff --breaking` shows what would break).
|
|
35
43
|
|
|
36
44
|
## Building
|
|
37
45
|
|
|
38
46
|
```sh
|
|
39
47
|
bun run build # dist/
|
|
40
48
|
bun run check:package # packs it, installs the tarball elsewhere and uses it under Node and Bun
|
|
49
|
+
bun run api:diff [REF] # how the public interface changed since REF (HEAD): git diff of just that
|
|
50
|
+
bun run api:diff --breaking [REF] # only its breaking changes: removed, renamed, newly required, retyped
|
|
41
51
|
```
|
|
@@ -3,6 +3,7 @@ import type { Sandbox, SandboxAccessibleEndpoint } from "../sandbox/api.ts";
|
|
|
3
3
|
import type { AgentStorage } from "./agentStorage.ts";
|
|
4
4
|
import type { GatewaySignIns } from "./gatewaySignIns.ts";
|
|
5
5
|
import { type AgentClient, type AgentEndpoint, type AgentProvision, type AgentSessionCapabilities } from "./api.ts";
|
|
6
|
+
/** @sealed */
|
|
6
7
|
export interface AgentService extends AgentClient, AgentProvision, AgentSessionCapabilities, AgentEndpoint {
|
|
7
8
|
/** Starts the workers that a restart took down (their dimension runs again); returns their agents' names. */
|
|
8
9
|
restoreAgents(): Promise<string[]>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dimensionStateDirectory } from "../dimension/paths.js";
|
|
1
2
|
import { RUN_PATH, STATE_PATH, containerName as dimensionContainerName } from "../dimension/service.js";
|
|
2
3
|
import { createContainerSandbox } from "../sandbox/containerService.js";
|
|
3
4
|
import { InvalidNameError, checkName } from "../lib/jsonDirectory.js";
|
|
@@ -6,7 +7,9 @@ import { OS_CA_BUNDLE } from "../portal-gateway/portalService.js";
|
|
|
6
7
|
import { AgentConflictError, AgentInvalidError, AgentNotFoundError, } from "./api.js";
|
|
7
8
|
import { createWorkerClient } from "./session/client.js";
|
|
8
9
|
import { workerSocketConnector } from "./session/workerConnection.js";
|
|
10
|
+
import { readHostTranscript } from "./hostTranscript.js";
|
|
9
11
|
import { workerSocketPath } from "./session/sessionStorage.js";
|
|
12
|
+
import { WORKER_PARAMS } from "./session/worker/workerParams.js";
|
|
10
13
|
import { workerBundle } from "./workerBundle.js";
|
|
11
14
|
/** In the dimension: where the backends are installed (a package with its node_modules). Shared by agents. */
|
|
12
15
|
export const WORKER_DIR = "/opt/hyperbrane/worker";
|
|
@@ -320,6 +323,18 @@ export function createAgentService(storage, docker, host, gatewaySignIns) {
|
|
|
320
323
|
async function sessionClient({ agentName }) {
|
|
321
324
|
return clientFor(agentName);
|
|
322
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Read on the host from the dimension's state directory (hostTranscript.ts), so nothing has to run,
|
|
328
|
+
* neither the worker nor the dimension; only a session whose backend's home is elsewhere asks its
|
|
329
|
+
* worker.
|
|
330
|
+
*/
|
|
331
|
+
async function getTranscript(ref, request = {}) {
|
|
332
|
+
const { session, limit, before } = WORKER_PARAMS["session/transcript"]({ session: ref.sessionName, ...request });
|
|
333
|
+
const agent = await requireAgent(ref.agentName);
|
|
334
|
+
const stateDirectory = dimensionStateDirectory(await storage.dataDirectory(), agent.dimensionName);
|
|
335
|
+
const page = await readHostTranscript(host, { stateDirectory, agentName: agent.name, sessionName: session }, { limit, before });
|
|
336
|
+
return page ?? (await sessionClient(ref)).call("session/transcript", { session, limit, before });
|
|
337
|
+
}
|
|
323
338
|
// Agents whose dimension or worker is down have no sessions to show right now.
|
|
324
339
|
async function listSessions() {
|
|
325
340
|
const perAgent = await Promise.all((await storage.listAgents()).map(async (agent) => {
|
|
@@ -359,7 +374,7 @@ export function createAgentService(storage, docker, host, gatewaySignIns) {
|
|
|
359
374
|
createSession: async (agentName, session) => (await clientFor(agentName)).call("session/create", { session }),
|
|
360
375
|
updateSession: async (ref, limits) => (await sessionClient(ref)).call("session/update", { session: ref.sessionName, limits }),
|
|
361
376
|
stopSession: async (ref) => void (await (await sessionClient(ref)).call("session/stop", { session: ref.sessionName })),
|
|
362
|
-
getTranscript
|
|
377
|
+
getTranscript,
|
|
363
378
|
cancel: async (ref) => void (await (await sessionClient(ref)).call("session/interrupt", { session: ref.sessionName })),
|
|
364
379
|
sendMessage: async (ref, message) => void (await (await sessionClient(ref)).call("session/send", { session: ref.sessionName, message })),
|
|
365
380
|
getSessionStatus: async (ref) => (await sessionClient(ref)).call("session/status", { session: ref.sessionName }),
|
|
@@ -9,6 +9,8 @@ export interface AgentStorage {
|
|
|
9
9
|
/** No-op if missing. */
|
|
10
10
|
removeAgent(agentName: string): Promise<void>;
|
|
11
11
|
listAgents(): Promise<Agent[]>;
|
|
12
|
+
/** Hyperbrane's data directory these are kept in, with the dimensions' (see dimension/paths.ts). */
|
|
13
|
+
dataDirectory(): Promise<string>;
|
|
12
14
|
/**
|
|
13
15
|
* The agent's accessible endpoints (AgentService.exposeAgent), credentials included, by the
|
|
14
16
|
* accessible host each was exposed for: one per way the machine is reached.
|
|
@@ -4,9 +4,10 @@ import { createJsonDirectory, defaultDataDir } from "../lib/jsonDirectory.js";
|
|
|
4
4
|
* agents/<agent>.json, agent-endpoints/<agent>.json.
|
|
5
5
|
*/
|
|
6
6
|
export function createAgentStorage(sandbox, dir) {
|
|
7
|
-
const
|
|
7
|
+
const dataDirectory = async () => dir ?? (await defaultDataDir(sandbox));
|
|
8
|
+
const agents = createJsonDirectory(sandbox, async () => `${await dataDirectory()}/agents`);
|
|
8
9
|
// A record used to hold one endpoint; it is read as the entry for its URL's host:port.
|
|
9
|
-
const endpoints = createJsonDirectory(sandbox, async () => `${
|
|
10
|
+
const endpoints = createJsonDirectory(sandbox, async () => `${await dataDirectory()}/agent-endpoints`);
|
|
10
11
|
async function readEndpoints(agentName) {
|
|
11
12
|
const record = await endpoints.read(agentName);
|
|
12
13
|
if (!record)
|
|
@@ -44,6 +45,7 @@ export function createAgentStorage(sandbox, dir) {
|
|
|
44
45
|
readAgent,
|
|
45
46
|
removeAgent,
|
|
46
47
|
listAgents,
|
|
48
|
+
dataDirectory,
|
|
47
49
|
readEndpoints,
|
|
48
50
|
saveEndpoint,
|
|
49
51
|
removeEndpoint,
|
package/dist/agent/api.d.ts
CHANGED
|
@@ -30,18 +30,19 @@ export type AgentSessionLimits = Partial<Pick<AgentSession, "memoryLimit" | "cpu
|
|
|
30
30
|
*/
|
|
31
31
|
export type AgentSessionState = "stopped" | "starting" | "idle" | "running" | "waiting" | "error";
|
|
32
32
|
export type AgentSessionInfo = AgentSession & {
|
|
33
|
-
agentName: string;
|
|
34
|
-
status: AgentSessionState;
|
|
33
|
+
readonly agentName: string;
|
|
34
|
+
readonly status: AgentSessionState;
|
|
35
35
|
/** The backend's own id for the conversation (claude's session id, codex's thread id), once known. */
|
|
36
|
-
nativeId: string | null;
|
|
36
|
+
readonly nativeId: string | null;
|
|
37
37
|
/** Chosen through AgentModels; null for the backend's default. */
|
|
38
|
-
model: string | null;
|
|
38
|
+
readonly model: string | null;
|
|
39
39
|
};
|
|
40
40
|
/** Where a session lives: sessions are named per agent. */
|
|
41
41
|
export type SessionRef = {
|
|
42
42
|
agentName: string;
|
|
43
43
|
sessionName: string;
|
|
44
44
|
};
|
|
45
|
+
/** @sealed */
|
|
45
46
|
export interface AgentClient {
|
|
46
47
|
/** PUT /v1/agents/:agentName — starts (or updates) the worker in the dimension. */
|
|
47
48
|
createAgent(agent: Agent): Promise<Agent>;
|
|
@@ -83,13 +84,23 @@ export type AgentTranscriptMessage = Extract<AgentStreamingMessage, {
|
|
|
83
84
|
export type AgentTranscriptRequest = {
|
|
84
85
|
/** How many messages a page holds, about (a tool call and its result share a page); default 50, at most 500. */
|
|
85
86
|
limit?: number;
|
|
86
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* An ISO 8601 time: the page holds the newest messages from before it (a page's `nextBefore`, for
|
|
89
|
+
* the page before that one); omitted for the newest page.
|
|
90
|
+
*/
|
|
91
|
+
before?: string;
|
|
92
|
+
/** @deprecated `before` by its old name: a page's `nextCursor` is its `nextBefore`. */
|
|
87
93
|
cursor?: string;
|
|
88
94
|
};
|
|
89
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Newest pages first; each page's messages oldest first. `nextBefore` is the time of its oldest
|
|
97
|
+
* message, when there are older ones: every message of that time is on this page.
|
|
98
|
+
*/
|
|
90
99
|
export type AgentTranscriptPage = {
|
|
91
|
-
messages: AgentTranscriptMessage[];
|
|
92
|
-
|
|
100
|
+
readonly messages: AgentTranscriptMessage[];
|
|
101
|
+
readonly nextBefore: string | null;
|
|
102
|
+
/** @deprecated `nextBefore` by its old name, for requests that give it as `cursor`. */
|
|
103
|
+
readonly nextCursor: string | null;
|
|
93
104
|
};
|
|
94
105
|
export type UserMessage = SDKUserMessage;
|
|
95
106
|
/**
|
|
@@ -97,24 +108,24 @@ export type UserMessage = SDKUserMessage;
|
|
|
97
108
|
* `can_use_tool` control request) isn't exported, so this mirrors it.
|
|
98
109
|
*/
|
|
99
110
|
export type AgentPermissionRequest = {
|
|
100
|
-
id: string;
|
|
101
|
-
tool_name: string;
|
|
102
|
-
input: Record<string, unknown>;
|
|
103
|
-
tool_use_id: string;
|
|
111
|
+
readonly id: string;
|
|
112
|
+
readonly tool_name: string;
|
|
113
|
+
readonly input: Record<string, unknown>;
|
|
114
|
+
readonly tool_use_id: string;
|
|
104
115
|
/** Why the agent asks, when it says (e.g. codex's reason for wanting network access). */
|
|
105
|
-
decision_reason?: string;
|
|
116
|
+
readonly decision_reason?: string;
|
|
106
117
|
};
|
|
107
118
|
export type AgentPermissionResult = PermissionResult;
|
|
108
119
|
type AskUserQuestion = AskUserQuestionInput["questions"][number];
|
|
109
120
|
/** A question of claude's AskUserQuestion tool; codex's may come with any number of options. */
|
|
110
121
|
export type AgentQuestion = Omit<AskUserQuestion, "options"> & {
|
|
111
|
-
options: AskUserQuestion["options"][number][];
|
|
122
|
+
readonly options: AskUserQuestion["options"][number][];
|
|
112
123
|
};
|
|
113
124
|
/** The agent asking the user something (claude's AskUserQuestion, codex's request_user_input). */
|
|
114
125
|
export type AgentQuestionRequest = {
|
|
115
|
-
id: string;
|
|
116
|
-
tool_use_id: string;
|
|
117
|
-
questions: AgentQuestion[];
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly tool_use_id: string;
|
|
128
|
+
readonly questions: AgentQuestion[];
|
|
118
129
|
};
|
|
119
130
|
/**
|
|
120
131
|
* AskUserQuestion's own answer: question text -> the chosen label (labels joined with ", " for a
|
|
@@ -138,20 +149,22 @@ export type AgentBackgroundTask = SDKBackgroundTasksChangedMessage["tasks"][numb
|
|
|
138
149
|
* the code the page shows, when `needsCode`).
|
|
139
150
|
*/
|
|
140
151
|
export type AgentAuthStatus = {
|
|
141
|
-
state: "signedIn";
|
|
152
|
+
readonly state: "signedIn";
|
|
142
153
|
} | {
|
|
143
|
-
state: "signedOut"; /** Why the last sign-in failed, if one did. */
|
|
144
|
-
error?: string;
|
|
154
|
+
readonly state: "signedOut"; /** Why the last sign-in failed, if one did. */
|
|
155
|
+
readonly error?: string;
|
|
145
156
|
} | {
|
|
146
|
-
state: "pending";
|
|
147
|
-
url: string;
|
|
148
|
-
userCode?: string;
|
|
149
|
-
needsCode: boolean;
|
|
157
|
+
readonly state: "pending";
|
|
158
|
+
readonly url: string;
|
|
159
|
+
readonly userCode?: string;
|
|
160
|
+
readonly needsCode: boolean;
|
|
150
161
|
};
|
|
162
|
+
/** @sealed */
|
|
151
163
|
export interface AgentProvision {
|
|
152
164
|
/** POST /v1/agents/:agentName/backends/:backend/install — installs the backend in the dimension, or updates it. */
|
|
153
165
|
install(agentName: string, backend: AgentBackend): Promise<void>;
|
|
154
166
|
}
|
|
167
|
+
/** @sealed */
|
|
155
168
|
export interface AgentEndpoint {
|
|
156
169
|
/**
|
|
157
170
|
* POST /v1/agents/:agentName/endpoint — the agent's worker, reachable from the client over the
|
|
@@ -163,6 +176,7 @@ export interface AgentEndpoint {
|
|
|
163
176
|
*/
|
|
164
177
|
exposeAgent(agentName: string, scheme: SandboxEndpointScheme, allowedOrigins?: string[]): Promise<SandboxEndpoint>;
|
|
165
178
|
}
|
|
179
|
+
/** @sealed */
|
|
166
180
|
export interface AgentStream {
|
|
167
181
|
/**
|
|
168
182
|
* GET /v1/agents/:agentName/sessions/:sessionName/stream?after_id= — the complete messages after
|
|
@@ -172,18 +186,21 @@ export interface AgentStream {
|
|
|
172
186
|
*/
|
|
173
187
|
getStream(ref: SessionRef, req: AgentStreamingRequest): AsyncGenerator<AgentStreamingMessage>;
|
|
174
188
|
}
|
|
189
|
+
/** @sealed */
|
|
175
190
|
export interface AgentTranscript {
|
|
176
191
|
/**
|
|
177
|
-
* GET /v1/agents/:agentName/sessions/:sessionName/transcript?limit=&
|
|
192
|
+
* GET /v1/agents/:agentName/sessions/:sessionName/transcript?limit=&before= (or &cursor=, its old name) — a page of the
|
|
178
193
|
* conversation, read from the backend's own storage a piece at a time, so a conversation of any
|
|
179
194
|
* length can be paged through.
|
|
180
195
|
*/
|
|
181
196
|
getTranscript(ref: SessionRef, req?: AgentTranscriptRequest): Promise<AgentTranscriptPage>;
|
|
182
197
|
}
|
|
198
|
+
/** @sealed */
|
|
183
199
|
export interface AgentInterrupt {
|
|
184
200
|
/** POST /v1/agents/:agentName/sessions/:sessionName/interrupt — ends the current turn; pending permission requests are denied and questions dropped. */
|
|
185
201
|
cancel(ref: SessionRef): Promise<void>;
|
|
186
202
|
}
|
|
203
|
+
/** @sealed */
|
|
187
204
|
export interface AgentInput {
|
|
188
205
|
/**
|
|
189
206
|
* POST /v1/agents/:agentName/sessions/:sessionName/messages — resolves once the agent confirms it
|
|
@@ -192,10 +209,12 @@ export interface AgentInput {
|
|
|
192
209
|
*/
|
|
193
210
|
sendMessage(ref: SessionRef, message: UserMessage): Promise<void>;
|
|
194
211
|
}
|
|
212
|
+
/** @sealed */
|
|
195
213
|
export interface AgentSessionStatus {
|
|
196
214
|
/** GET /v1/agents/:agentName/sessions/:sessionName/status */
|
|
197
215
|
getSessionStatus(ref: SessionRef): Promise<AgentSessionState>;
|
|
198
216
|
}
|
|
217
|
+
/** @sealed */
|
|
199
218
|
export interface AgentMessageQueue {
|
|
200
219
|
/**
|
|
201
220
|
* GET /v1/agents/:agentName/sessions/:sessionName/queue — from the agent (claude's command
|
|
@@ -205,24 +224,28 @@ export interface AgentMessageQueue {
|
|
|
205
224
|
*/
|
|
206
225
|
getLastUserMessages(ref: SessionRef): Promise<AgentLastUserMessage[]>;
|
|
207
226
|
}
|
|
227
|
+
/** @sealed */
|
|
208
228
|
export interface AgentPermissions {
|
|
209
229
|
/** GET /v1/agents/:agentName/sessions/:sessionName/permission-requests — the ones still waiting. */
|
|
210
230
|
listPermissionRequests(ref: SessionRef): Promise<AgentPermissionRequest[]>;
|
|
211
231
|
/** POST /v1/agents/:agentName/sessions/:sessionName/permission-requests/:requestId */
|
|
212
232
|
respondPermissionRequest(ref: SessionRef, requestId: string, result: AgentPermissionResult): Promise<void>;
|
|
213
233
|
}
|
|
234
|
+
/** @sealed */
|
|
214
235
|
export interface AgentQuestions {
|
|
215
236
|
/** GET /v1/agents/:agentName/sessions/:sessionName/question-requests — the ones still waiting. */
|
|
216
237
|
listQuestionRequests(ref: SessionRef): Promise<AgentQuestionRequest[]>;
|
|
217
238
|
/** POST /v1/agents/:agentName/sessions/:sessionName/question-requests/:requestId */
|
|
218
239
|
respondQuestionRequest(ref: SessionRef, requestId: string, response: AgentQuestionResponse): Promise<void>;
|
|
219
240
|
}
|
|
241
|
+
/** @sealed */
|
|
220
242
|
export interface AgentBackgroundTasks {
|
|
221
243
|
/** GET /v1/agents/:agentName/sessions/:sessionName/background-tasks — those still running; none while the CLI isn't. */
|
|
222
244
|
listBackgroundTasks(ref: SessionRef): Promise<AgentBackgroundTask[]>;
|
|
223
245
|
/** POST /v1/agents/:agentName/sessions/:sessionName/background-tasks/:taskId/stop — ends the task. */
|
|
224
246
|
stopTask(ref: SessionRef, taskId: string): Promise<void>;
|
|
225
247
|
}
|
|
248
|
+
/** @sealed */
|
|
226
249
|
export interface AgentAuth {
|
|
227
250
|
/** GET /v1/agents/:agentName/sessions/:sessionName/auth */
|
|
228
251
|
getAuthStatus(ref: SessionRef): Promise<AgentAuthStatus>;
|
|
@@ -236,6 +259,7 @@ export interface AgentAuth {
|
|
|
236
259
|
/** POST /v1/agents/:agentName/sessions/:sessionName/auth/code — finishes a pending sign-in that needs the page's code. */
|
|
237
260
|
submitAuthCode(ref: SessionRef, code: string): Promise<AgentAuthStatus>;
|
|
238
261
|
}
|
|
262
|
+
/** @sealed */
|
|
239
263
|
export interface AgentModels {
|
|
240
264
|
/** GET /v1/agents/:agentName/sessions/:sessionName/models — starts the CLI if it isn't running. */
|
|
241
265
|
listModels(ref: SessionRef): Promise<AgentModelInfo[]>;
|
|
@@ -246,8 +270,8 @@ export interface AgentModels {
|
|
|
246
270
|
export type AgentSessionCapabilities = AgentStream & AgentTranscript & AgentInterrupt & AgentInput & AgentSessionStatus & AgentMessageQueue & AgentPermissions & AgentQuestions & AgentBackgroundTasks & AgentAuth & AgentModels;
|
|
247
271
|
/** A message sent to a session, and whether its agent still holds it queued (not yet taken into a turn). */
|
|
248
272
|
export type AgentLastUserMessage = {
|
|
249
|
-
message: UserMessage;
|
|
250
|
-
queued: boolean;
|
|
273
|
+
readonly message: UserMessage;
|
|
274
|
+
readonly queued: boolean;
|
|
251
275
|
};
|
|
252
276
|
/** The request can't be satisfied as sent (bad name, unknown backend, unknown after_id, ...). */
|
|
253
277
|
export declare class AgentInvalidError extends Error {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createWorkerClient, type WorkerClient } from "./session/client.ts";
|
|
2
|
+
export { sharedConnector, type SharedConnector } from "./session/sharedConnection.ts";
|
|
3
|
+
export { EndpointLoginRequiredError, exeDevLoginUrl, workerSseConnector, type WorkerEndpoint } from "./session/workerSseConnection.ts";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// An agent's worker, reached through its endpoint (AgentService.exposeAgent): what a browser can
|
|
2
|
+
// import (hyperbrane/agent/client), since the root module brings the server side with it.
|
|
3
|
+
export { createWorkerClient } from "./session/client.js";
|
|
4
|
+
export { sharedConnector } from "./session/sharedConnection.js";
|
|
5
|
+
export { EndpointLoginRequiredError, exeDevLoginUrl, workerSseConnector } from "./session/workerSseConnection.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Sandbox } from "../sandbox/api.ts";
|
|
2
|
+
import { type AgentTranscriptPage, type AgentTranscriptRequest } from "./api.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A page of the session's transcript, read on `host` from the dimension's state directory (its host
|
|
5
|
+
* side) by the backend's own layout and parsing, so nothing has to run: one command, plus a read
|
|
6
|
+
* for each further chunk the rare page that needs more takes. Null for a session whose env moves the backend's home
|
|
7
|
+
* elsewhere (into the container, where only its worker reaches it).
|
|
8
|
+
*/
|
|
9
|
+
export declare function readHostTranscript(host: Sandbox, { stateDirectory, agentName, sessionName }: {
|
|
10
|
+
stateDirectory: string;
|
|
11
|
+
agentName: string;
|
|
12
|
+
sessionName: string;
|
|
13
|
+
}, { limit, before, cursor }: AgentTranscriptRequest): Promise<AgentTranscriptPage | null>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { AgentNotFoundError } from "./api.js";
|
|
2
|
+
import { CHUNK_BYTES, PROBE_BYTES, PROBE_LINES, TIME_SLACK_MS, createFileTranscript, prefetchedFiles, sandboxFiles, transcriptPage, transcriptTime, } from "./session/fileTranscript.js";
|
|
3
|
+
import { backendHomePath, sessionRecordPath } from "./session/sessionStorage.js";
|
|
4
|
+
import { STORED_TRANSCRIPTS } from "./session/storedTranscripts.js";
|
|
5
|
+
/**
|
|
6
|
+
* In one round trip: the session's record, its backend's file (found as the backend lays them out),
|
|
7
|
+
* the stretches written again in it, where the page starts (for a time, by bisecting the rest of the
|
|
8
|
+
* file as FileTranscript.page does) and what comes before that (PREFETCH_BYTES of it). The same
|
|
9
|
+
* searches as fileTranscript.ts, here where the file is. Args: a JSON object, see readHostTranscript.
|
|
10
|
+
*/
|
|
11
|
+
const READ_SCRIPT = String.raw `
|
|
12
|
+
import base64, datetime, fnmatch, glob, json, mmap, os, sys
|
|
13
|
+
args = json.loads(sys.argv[1])
|
|
14
|
+
CHUNK, SLACK, PROBE_LINES, PROBE_BYTES = args["chunk"], args["slack"], args["probeLines"], args["probeBytes"]
|
|
15
|
+
try:
|
|
16
|
+
with open(args["record"], "rb") as f:
|
|
17
|
+
record = json.load(f)
|
|
18
|
+
except FileNotFoundError:
|
|
19
|
+
print("{}")
|
|
20
|
+
sys.exit(0)
|
|
21
|
+
out = {"record": record}
|
|
22
|
+
backend = record.get("spec", {}).get("backend")
|
|
23
|
+
native_id = record.get("nativeId")
|
|
24
|
+
layout = args["layouts"].get(backend)
|
|
25
|
+
|
|
26
|
+
def entries(path):
|
|
27
|
+
try:
|
|
28
|
+
return sorted(os.listdir(path), reverse=True)
|
|
29
|
+
except OSError:
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
def line_time(raw):
|
|
33
|
+
try:
|
|
34
|
+
value = json.loads(raw).get(layout["time"])
|
|
35
|
+
return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() * 1000
|
|
36
|
+
except Exception:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def kept(start, end, skips):
|
|
40
|
+
parts = []
|
|
41
|
+
for a, b in sorted(skips):
|
|
42
|
+
if b <= start or a >= end:
|
|
43
|
+
continue
|
|
44
|
+
if a > start:
|
|
45
|
+
parts.append((start, a))
|
|
46
|
+
start = max(start, b)
|
|
47
|
+
if start < end:
|
|
48
|
+
parts.append((start, end))
|
|
49
|
+
return parts
|
|
50
|
+
|
|
51
|
+
def forward(m, start, end, skips):
|
|
52
|
+
for a, b in kept(start, end, skips):
|
|
53
|
+
position = a
|
|
54
|
+
for raw in m[a:b].split(b"\n"):
|
|
55
|
+
if raw:
|
|
56
|
+
yield position, raw
|
|
57
|
+
position += len(raw) + 1
|
|
58
|
+
|
|
59
|
+
def probe(m, start, limit, skips):
|
|
60
|
+
first, times = None, []
|
|
61
|
+
for line_start, raw in forward(m, start, min(limit, start + PROBE_BYTES), skips):
|
|
62
|
+
if line_start == start and start > 0:
|
|
63
|
+
continue
|
|
64
|
+
if line_start >= limit:
|
|
65
|
+
break
|
|
66
|
+
if first is None:
|
|
67
|
+
first = line_start
|
|
68
|
+
found = line_time(raw)
|
|
69
|
+
if found is None:
|
|
70
|
+
continue
|
|
71
|
+
times.append(found)
|
|
72
|
+
if len(times) == PROBE_LINES:
|
|
73
|
+
break
|
|
74
|
+
return None if first is None or not times else (first, min(times))
|
|
75
|
+
|
|
76
|
+
def seek(m, low, high, target, skips):
|
|
77
|
+
return narrow(m, low, high, target, skips)[1]
|
|
78
|
+
|
|
79
|
+
def narrow(m, low, high, target, skips):
|
|
80
|
+
while high - low > CHUNK:
|
|
81
|
+
middle = (low + high) // 2
|
|
82
|
+
inside = next((s for s in skips if s[0] <= middle < s[1]), None)
|
|
83
|
+
if inside:
|
|
84
|
+
if inside[1] >= high:
|
|
85
|
+
high = max(low, inside[0])
|
|
86
|
+
continue
|
|
87
|
+
middle = inside[1]
|
|
88
|
+
found = probe(m, middle, high, skips)
|
|
89
|
+
if not found:
|
|
90
|
+
break
|
|
91
|
+
if found[1] >= target:
|
|
92
|
+
high = found[0]
|
|
93
|
+
else:
|
|
94
|
+
low = found[0]
|
|
95
|
+
return low, high
|
|
96
|
+
|
|
97
|
+
def copies_of(m, size, marker):
|
|
98
|
+
copies, found, position = [], [], 0
|
|
99
|
+
while True:
|
|
100
|
+
at = m.find(marker.encode(), position)
|
|
101
|
+
if at < 0:
|
|
102
|
+
break
|
|
103
|
+
found.append(at)
|
|
104
|
+
position = at + 1
|
|
105
|
+
for index, at in enumerate(found):
|
|
106
|
+
start = m.rfind(b"\n", 0, at) + 1
|
|
107
|
+
if any(a <= start < b for a, b in copies):
|
|
108
|
+
continue
|
|
109
|
+
earlier, end = None, start - 1
|
|
110
|
+
while end > 0 and earlier is None:
|
|
111
|
+
begin = m.rfind(b"\n", 0, end) + 1
|
|
112
|
+
earlier = line_time(m[begin:end])
|
|
113
|
+
end = begin - 1
|
|
114
|
+
line_end = m.find(b"\n", start)
|
|
115
|
+
time = line_time(m[start : line_end if line_end >= 0 else size])
|
|
116
|
+
if earlier is None or time is None or time >= earlier:
|
|
117
|
+
continue
|
|
118
|
+
high = m.rfind(b"\n", 0, found[index + 1]) + 1 if index + 1 < len(found) else size
|
|
119
|
+
low, high = narrow(m, start + 1, high, earlier + 1, [])
|
|
120
|
+
end = next((s for s, raw in forward(m, low, high, []) if s > start and (line_time(raw) or -1) > earlier), high)
|
|
121
|
+
copies.append((start, end))
|
|
122
|
+
return copies
|
|
123
|
+
|
|
124
|
+
if native_id and layout:
|
|
125
|
+
name = layout["name"].replace("{id}", glob.escape(native_id))
|
|
126
|
+
dirs = [os.path.join(args["homes"][backend], layout["dir"])]
|
|
127
|
+
for _ in range(layout["depth"] - 1):
|
|
128
|
+
dirs = [os.path.join(d, e) for d in dirs for e in entries(d) if os.path.isdir(os.path.join(d, e))]
|
|
129
|
+
path = next((os.path.join(d, e) for d in dirs for e in entries(d) if fnmatch.fnmatchcase(e, name)), None)
|
|
130
|
+
size = os.path.getsize(path) if path else 0
|
|
131
|
+
if path and size == 0:
|
|
132
|
+
out.update(path=path, size=0, end=0, after=None, copies=[], pieces=[])
|
|
133
|
+
elif path:
|
|
134
|
+
with open(path, "rb") as f:
|
|
135
|
+
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
|
|
136
|
+
copies = copies_of(m, size, layout["copies"]) if layout.get("copies") else []
|
|
137
|
+
end = size if args["before"] is None else seek(m, 0, size, args["before"] + SLACK, copies)
|
|
138
|
+
after = next((t for t in (line_time(raw) for _, raw in forward(m, end, min(size, end + PROBE_BYTES), copies)) if t is not None), None)
|
|
139
|
+
pieces, left = [], args["prefetch"]
|
|
140
|
+
for a, b in reversed(kept(0, end, copies)):
|
|
141
|
+
if left <= 0:
|
|
142
|
+
break
|
|
143
|
+
start = max(a, b - left)
|
|
144
|
+
pieces.append({"start": start, "bytes": base64.b64encode(m[start:b]).decode()})
|
|
145
|
+
left -= b - start
|
|
146
|
+
out.update(path=path, size=size, end=end, after=after, copies=[[a, b] for a, b in copies], pieces=pieces)
|
|
147
|
+
print(json.dumps(out))
|
|
148
|
+
`;
|
|
149
|
+
/** What's sent of the file with the record: a page and the slack around it, mostly, so it takes no more reads. */
|
|
150
|
+
const PREFETCH_BYTES = 8 * CHUNK_BYTES;
|
|
151
|
+
/**
|
|
152
|
+
* A page of the session's transcript, read on `host` from the dimension's state directory (its host
|
|
153
|
+
* side) by the backend's own layout and parsing, so nothing has to run: one command, plus a read
|
|
154
|
+
* for each further chunk the rare page that needs more takes. Null for a session whose env moves the backend's home
|
|
155
|
+
* elsewhere (into the container, where only its worker reaches it).
|
|
156
|
+
*/
|
|
157
|
+
export async function readHostTranscript(host, { stateDirectory, agentName, sessionName }, { limit, before, cursor }) {
|
|
158
|
+
before ??= cursor; // its old name
|
|
159
|
+
const args = {
|
|
160
|
+
record: sessionRecordPath(stateDirectory, agentName, sessionName),
|
|
161
|
+
homes: Object.fromEntries(Object.keys(STORED_TRANSCRIPTS).map((backend) => [backend, backendHomePath(stateDirectory, backend)])),
|
|
162
|
+
layouts: Object.fromEntries(Object.entries(STORED_TRANSCRIPTS).map(([backend, stored]) => [backend, stored.layout])),
|
|
163
|
+
before: transcriptTime(before),
|
|
164
|
+
chunk: CHUNK_BYTES,
|
|
165
|
+
prefetch: PREFETCH_BYTES,
|
|
166
|
+
slack: TIME_SLACK_MS,
|
|
167
|
+
probeLines: PROBE_LINES,
|
|
168
|
+
probeBytes: PROBE_BYTES,
|
|
169
|
+
};
|
|
170
|
+
const result = await host.runCommand(["python3", "-c", READ_SCRIPT, JSON.stringify(args)]);
|
|
171
|
+
if (result.exitCode !== 0)
|
|
172
|
+
throw new Error(`reading session ${sessionName}'s transcript failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
|
|
173
|
+
const { record, path, size, end, after, copies, pieces } = JSON.parse(result.stdout);
|
|
174
|
+
if (!record)
|
|
175
|
+
throw new AgentNotFoundError(`session ${sessionName} does not exist`);
|
|
176
|
+
const stored = STORED_TRANSCRIPTS[record.spec.backend];
|
|
177
|
+
if (Object.keys(stored.homeEnvironment("")).some((name) => record.spec.env?.[name] !== undefined))
|
|
178
|
+
return null;
|
|
179
|
+
if (!record.nativeId)
|
|
180
|
+
return transcriptPage(null, { limit, before });
|
|
181
|
+
let files = sandboxFiles(host);
|
|
182
|
+
let found;
|
|
183
|
+
if (path !== undefined && size !== undefined && end !== undefined) {
|
|
184
|
+
const fetched = (pieces ?? []).map((piece) => ({ start: piece.start, bytes: new Uint8Array(Buffer.from(piece.bytes, "base64")) }));
|
|
185
|
+
files = prefetchedFiles(files, path, size, fetched);
|
|
186
|
+
found = { end, after: after ?? null, copies: (copies ?? []).map(([start, stop]) => ({ start, end: stop })) };
|
|
187
|
+
}
|
|
188
|
+
const transcript = createFileTranscript(files, async () => path ?? null, stored.lineMessages(record.nativeId), stored.layout);
|
|
189
|
+
return transcriptPage(transcript, { limit, before }, found);
|
|
190
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AgentBackgroundTasks, AgentInput, AgentInterrupt, AgentMessageQueue, AgentModels, AgentPermissionRequest, AgentPermissionResult, AgentQuestionRequest, AgentQuestionResponse, AgentSession, AgentSessionStatus, AgentStreamingMessage, SessionRef } from "../api.ts";
|
|
2
|
+
import type { FileTranscript, TranscriptFiles } from "./fileTranscript.ts";
|
|
3
|
+
/** A capability's calls for one session: those of api.ts, with the session already chosen. */
|
|
4
|
+
export type ForSession<Capability> = {
|
|
5
|
+
[K in keyof Capability]: Capability[K] extends (ref: SessionRef, ...args: infer Args) => infer Result ? (...args: Args) => Result : never;
|
|
6
|
+
};
|
|
7
|
+
/** What one session's CLI runs with. */
|
|
8
|
+
export type SessionLaunch = {
|
|
9
|
+
spec: AgentSession;
|
|
10
|
+
/** The backend's id for the conversation, from an earlier run, to resume it; null for a new one. */
|
|
11
|
+
nativeId: string | null;
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** The model chosen through AgentModels; null for the backend's default. */
|
|
14
|
+
model: string | null;
|
|
15
|
+
/** Where backends are installed (see SessionBackend.install). */
|
|
16
|
+
installDir: string;
|
|
17
|
+
/** The worker's own environment, the CLI pointed at its home, and the session's. */
|
|
18
|
+
env: Record<string, string>;
|
|
19
|
+
};
|
|
20
|
+
/** What the CLI reports to, and asks of, the worker. */
|
|
21
|
+
export type SessionCallbacks = {
|
|
22
|
+
/** Appends to the session's stream. */
|
|
23
|
+
emit(message: AgentStreamingMessage): void;
|
|
24
|
+
/** Records the backend's conversation id, so a later run can resume it. */
|
|
25
|
+
setNativeId(id: string): void;
|
|
26
|
+
/** The decision on a tool call: someone's answer, or a denial once the turn is interrupted, the session stops, or `signal` aborts. */
|
|
27
|
+
requestPermission(request: Omit<AgentPermissionRequest, "id">, signal?: AbortSignal): Promise<AgentPermissionResult>;
|
|
28
|
+
/** The user's answers, or null once the turn is interrupted, the session stops, or `signal` aborts. */
|
|
29
|
+
askQuestions(request: Omit<AgentQuestionRequest, "id">, signal?: AbortSignal): Promise<AgentQuestionResponse | null>;
|
|
30
|
+
/** The CLI is gone (crashed, or closed); the next message starts it again. */
|
|
31
|
+
exited(error?: Error): void;
|
|
32
|
+
};
|
|
33
|
+
export type SessionContext = SessionLaunch & SessionCallbacks;
|
|
34
|
+
/** One session's running CLI: the capabilities it serves itself. */
|
|
35
|
+
export type RunningSession = ForSession<AgentInput & AgentInterrupt & AgentSessionStatus & AgentMessageQueue & AgentModels & AgentBackgroundTasks> & {
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
/** What a CLI's sign-in commands run with. */
|
|
39
|
+
export type AuthEnvironment = {
|
|
40
|
+
installDir: string;
|
|
41
|
+
env: Record<string, string>;
|
|
42
|
+
cwd: string;
|
|
43
|
+
};
|
|
44
|
+
/** A sign-in in progress: the CLI's own login command. */
|
|
45
|
+
export interface LoginProcess {
|
|
46
|
+
/** Resolves once the CLI has shown where to sign in. */
|
|
47
|
+
prompt: Promise<{
|
|
48
|
+
url: string;
|
|
49
|
+
userCode?: string;
|
|
50
|
+
needsCode: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
/** Resolves when the CLI signed in; rejects with why it didn't. */
|
|
53
|
+
done: Promise<void>;
|
|
54
|
+
/** Gives the CLI the code the sign-in page showed. */
|
|
55
|
+
submitCode(code: string): void;
|
|
56
|
+
cancel(): void;
|
|
57
|
+
}
|
|
58
|
+
/** A CLI's sign-in, through its own commands. The credentials it saves are fakes when a gateway holds the real ones. */
|
|
59
|
+
export type BackendAuth = {
|
|
60
|
+
signedIn(environment: AuthEnvironment): Promise<boolean>;
|
|
61
|
+
/** With `gatewaySignedIn` (the dimension's gateway holds the upstream's credential) it completes by itself: the gateway answers. */
|
|
62
|
+
login(environment: AuthEnvironment, gatewaySignedIn: boolean): LoginProcess;
|
|
63
|
+
};
|
|
64
|
+
/** What finds a session's conversation in its backend's storage, read through `files`. */
|
|
65
|
+
export type TranscriptSource = {
|
|
66
|
+
nativeId: string;
|
|
67
|
+
env: Record<string, string>;
|
|
68
|
+
files: TranscriptFiles;
|
|
69
|
+
};
|
|
70
|
+
/** An agent CLI, wired in agent/session/<backend>/: one file per capability. */
|
|
71
|
+
export type SessionBackend = {
|
|
72
|
+
/** Installs the backend under `installDir`, or updates it to the pinned version. Runs in the dimension. */
|
|
73
|
+
install(installDir: string): Promise<void>;
|
|
74
|
+
start(context: SessionContext): Promise<RunningSession>;
|
|
75
|
+
auth: BackendAuth;
|
|
76
|
+
/** The conversation `nativeId` in the backend's own storage, which needs no CLI running. */
|
|
77
|
+
transcript(source: TranscriptSource): FileTranscript;
|
|
78
|
+
/** The environment that points the CLI at `home` for everything it keeps (see SessionStorage.backendHome). */
|
|
79
|
+
homeEnvironment(home: string): Record<string, string>;
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentTranscriptMessage } from "../../api.ts";
|
|
2
|
+
import type { TranscriptSource } from "../backend.ts";
|
|
3
|
+
import { type FileTranscript, type TranscriptLayout } from "../fileTranscript.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The conversation from claude's own session file, <config>/projects/<cwd>/<session>.jsonl. Its user
|
|
6
|
+
* and assistant entries are the messages the stream carried, with their uuids (claude keeps the one
|
|
7
|
+
* a prompt was sent with); the rest (attachments, queue bookkeeping, a subagent's sidechain, meta
|
|
8
|
+
* notes) isn't the conversation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function claudeTranscript({ nativeId, env, files }: TranscriptSource): FileTranscript;
|
|
11
|
+
/** projects/<cwd>/<session>.jsonl in claude's config directory. */
|
|
12
|
+
export declare const CLAUDE_TRANSCRIPT_LAYOUT: TranscriptLayout;
|
|
13
|
+
export declare function claudeLineMessages(line: unknown): AgentTranscriptMessage[];
|