hyperbrane 0.1.0 → 0.1.2
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 +41 -3
- package/dist/agent/agentService.d.ts +11 -3
- package/dist/agent/agentService.js +63 -19
- package/dist/agent/agentStorage.d.ts +2 -0
- package/dist/agent/agentStorage.js +4 -2
- package/dist/agent/api.d.ts +83 -32
- package/dist/agent/api.js +7 -0
- 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 +82 -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 +5 -1
- package/dist/agent/session/worker/protocol.js +6 -1
- package/dist/agent/session/worker/workerParams.d.ts +11 -0
- package/dist/agent/session/worker/workerParams.js +163 -0
- package/dist/agent/session/workerSseConnection.d.ts +20 -4
- package/dist/agent/session/workerSseConnection.js +57 -7
- 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 +30 -4
- package/dist/cli/services.d.ts +6 -5
- package/dist/cli/services.js +4 -3
- 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 +5 -1
- package/dist/dimension/service.js +5 -1
- package/dist/dimension/storage.js +3 -2
- package/dist/index.d.ts +2 -3
- package/dist/index.js +7 -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 +5 -1
- package/dist/portal-gateway/gatewayService.js +25 -13
- package/dist/portal-gateway/image.d.ts +1 -1
- package/dist/portal-gateway/image.js +1 -1
- package/dist/portal-gateway/imageFiles.generated.js +1 -1
- package/dist/portal-gateway/portalService.d.ts +2 -0
- package/dist/portal-gateway/portalService.js +61 -29
- package/dist/portal-gateway/upstream.d.ts +22 -11
- package/dist/sandbox/api.d.ts +60 -23
- package/dist/sandbox/boatService.js +11 -3
- package/dist/sandbox/containerService.js +10 -3
- package/dist/sandbox/endpointProxy.js +16 -1
- package/dist/sandbox/localService.js +20 -2
- package/dist/sandbox/sshService.js +37 -4
- package/package.json +25 -8
|
@@ -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,82 @@
|
|
|
1
|
+
import type { AgentBackendInfo, 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
|
+
/** What's installed under `installDir`, and whether it's what install leaves: this release's version, complete. */
|
|
75
|
+
installed(installDir: string): Promise<Pick<AgentBackendInfo, "version" | "ready">>;
|
|
76
|
+
start(context: SessionContext): Promise<RunningSession>;
|
|
77
|
+
auth: BackendAuth;
|
|
78
|
+
/** The conversation `nativeId` in the backend's own storage, which needs no CLI running. */
|
|
79
|
+
transcript(source: TranscriptSource): FileTranscript;
|
|
80
|
+
/** The environment that points the CLI at `home` for everything it keeps (see SessionStorage.backendHome). */
|
|
81
|
+
homeEnvironment(home: string): Record<string, string>;
|
|
82
|
+
};
|
|
@@ -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[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFileTranscript, locateTranscript } from "../fileTranscript.js";
|
|
2
|
+
/**
|
|
3
|
+
* The conversation from claude's own session file, <config>/projects/<cwd>/<session>.jsonl. Its user
|
|
4
|
+
* and assistant entries are the messages the stream carried, with their uuids (claude keeps the one
|
|
5
|
+
* a prompt was sent with); the rest (attachments, queue bookkeeping, a subagent's sidechain, meta
|
|
6
|
+
* notes) isn't the conversation.
|
|
7
|
+
*/
|
|
8
|
+
export function claudeTranscript({ nativeId, env, files }) {
|
|
9
|
+
const home = env.CLAUDE_CONFIG_DIR ?? `${env.HOME}/.claude`;
|
|
10
|
+
return createFileTranscript(files, () => locateTranscript(files, home, CLAUDE_TRANSCRIPT_LAYOUT, nativeId), claudeLineMessages, CLAUDE_TRANSCRIPT_LAYOUT);
|
|
11
|
+
}
|
|
12
|
+
/** projects/<cwd>/<session>.jsonl in claude's config directory. */
|
|
13
|
+
export const CLAUDE_TRANSCRIPT_LAYOUT = {
|
|
14
|
+
dir: "projects",
|
|
15
|
+
depth: 2,
|
|
16
|
+
name: "{id}.jsonl",
|
|
17
|
+
time: "timestamp",
|
|
18
|
+
// Resuming a session, claude writes what it said since its last compaction again, from that compaction's line.
|
|
19
|
+
copies: '"subtype":"compact_boundary"',
|
|
20
|
+
};
|
|
21
|
+
export function claudeLineMessages(line) {
|
|
22
|
+
const entry = line;
|
|
23
|
+
if (entry.type !== "user" && entry.type !== "assistant")
|
|
24
|
+
return [];
|
|
25
|
+
if (entry.isSidechain || entry.isMeta || !entry.uuid || !entry.message)
|
|
26
|
+
return [];
|
|
27
|
+
// type-bypass: claude stores the message the stream carried; its type is the SDK's.
|
|
28
|
+
return [{ type: entry.type, message: entry.message, parent_tool_use_id: null, uuid: entry.uuid, session_id: entry.sessionId ?? "" }];
|
|
29
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { AgentStreamingMessage } from "../api.ts";
|
|
2
2
|
import type { WorkerMethod, WorkerRequests } from "./worker/protocol.ts";
|
|
3
3
|
import type { ConnectWorker } from "./workerConnection.ts";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The worker's protocol (protocol.ts). Errors come back as the Agent*Error they were.
|
|
6
|
+
*
|
|
7
|
+
* @sealed
|
|
8
|
+
*/
|
|
5
9
|
export type WorkerClient = {
|
|
6
10
|
call<M extends WorkerMethod>(method: M, params: WorkerRequests[M][0]): Promise<WorkerRequests[M][1]>;
|
|
7
11
|
/**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ThreadItem } from "../../vendor-types/codex/app-server/v2/ThreadItem.ts";
|
|
2
|
+
import type { AgentTranscriptMessage as TranscriptMessage } from "../../api.ts";
|
|
3
|
+
import { type AssistantContentBlock } from "../sdkMessages.ts";
|
|
4
|
+
/** The uuid of the result message of tool item `itemId`; the call's is the item id itself. */
|
|
5
|
+
export declare function toolResultId(itemId: string): string;
|
|
6
|
+
export declare function assistantMessage(uuid: string, content: AssistantContentBlock[], threadId: string, model: string): TranscriptMessage;
|
|
7
|
+
export declare function toolResultMessage(itemId: string, content: string, isError: boolean, threadId: string): TranscriptMessage;
|
|
8
|
+
/**
|
|
9
|
+
* The messages a finished item stands for, as the transcript shows them. A user message keeps the
|
|
10
|
+
* id it was sent with (codex records it as the item's clientId).
|
|
11
|
+
*/
|
|
12
|
+
export declare function itemMessages(item: ThreadItem, threadId: string, model: string): TranscriptMessage[];
|
|
13
|
+
/**
|
|
14
|
+
* The messages a rollout item stands for, the same (ids included) as the stream carried live. Items
|
|
15
|
+
* other than messages and commands (file changes, MCP calls, ...) aren't mapped from the rollout yet.
|
|
16
|
+
*/
|
|
17
|
+
export declare function rolloutItemMessages(stored: unknown, threadId: string): TranscriptMessage[];
|
|
18
|
+
/** The claude-style tool call a codex item stands for, if it is one. */
|
|
19
|
+
export declare function toolUseOf(item: ThreadItem): {
|
|
20
|
+
type: "tool_use";
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
input: unknown;
|
|
24
|
+
} | null;
|
|
25
|
+
export declare function toolResultOf(item: ThreadItem): {
|
|
26
|
+
type: "tool_result";
|
|
27
|
+
tool_use_id: string;
|
|
28
|
+
content: string;
|
|
29
|
+
is_error: boolean;
|
|
30
|
+
} | null;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { resultUsage } from "../sdkMessages.js";
|
|
2
|
+
// codex's thread items as the stream's SDK messages, the same live (stream.ts) and read back from
|
|
3
|
+
// codex's storage (transcript.ts), ids included: a message's uuid is its item's id, which is what
|
|
4
|
+
// after_id finds again. One tool item is two messages, the call and its result.
|
|
5
|
+
/** An id codex chose, in the SDK's uuid slot. type-bypass: codex's ids ("msg_…") aren't UUID-shaped; the SDK types the slot as one, but only ever compares it. */
|
|
6
|
+
function backendId(id) {
|
|
7
|
+
return id;
|
|
8
|
+
}
|
|
9
|
+
/** The uuid of the result message of tool item `itemId`; the call's is the item id itself. */
|
|
10
|
+
export function toolResultId(itemId) {
|
|
11
|
+
return `${itemId}:result`;
|
|
12
|
+
}
|
|
13
|
+
export function assistantMessage(uuid, content, threadId, model) {
|
|
14
|
+
const usage = { ...resultUsage(), cache_creation: null, server_tool_use: null, service_tier: null };
|
|
15
|
+
return {
|
|
16
|
+
type: "assistant",
|
|
17
|
+
message: {
|
|
18
|
+
id: uuid,
|
|
19
|
+
type: "message",
|
|
20
|
+
role: "assistant",
|
|
21
|
+
model,
|
|
22
|
+
content,
|
|
23
|
+
container: null,
|
|
24
|
+
context_management: null,
|
|
25
|
+
stop_reason: null,
|
|
26
|
+
stop_sequence: null,
|
|
27
|
+
usage,
|
|
28
|
+
},
|
|
29
|
+
parent_tool_use_id: null,
|
|
30
|
+
uuid: backendId(uuid),
|
|
31
|
+
session_id: threadId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function toolResultMessage(itemId, content, isError, threadId) {
|
|
35
|
+
return {
|
|
36
|
+
type: "user",
|
|
37
|
+
message: { role: "user", content: [{ type: "tool_result", tool_use_id: itemId, content, is_error: isError }] },
|
|
38
|
+
parent_tool_use_id: null,
|
|
39
|
+
uuid: backendId(toolResultId(itemId)),
|
|
40
|
+
session_id: threadId,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The messages a finished item stands for, as the transcript shows them. A user message keeps the
|
|
45
|
+
* id it was sent with (codex records it as the item's clientId).
|
|
46
|
+
*/
|
|
47
|
+
export function itemMessages(item, threadId, model) {
|
|
48
|
+
if (item.type === "userMessage") {
|
|
49
|
+
const text = item.content.map((input) => (input.type === "text" ? input.text : "")).join("");
|
|
50
|
+
return [{ type: "user", message: { role: "user", content: text }, parent_tool_use_id: null, uuid: backendId(item.clientId ?? item.id), session_id: threadId }];
|
|
51
|
+
}
|
|
52
|
+
if (item.type === "agentMessage")
|
|
53
|
+
return [assistantMessage(item.id, [{ type: "text", text: item.text, citations: null }], threadId, model)];
|
|
54
|
+
const toolUse = toolUseOf(item);
|
|
55
|
+
const toolResult = toolResultOf(item);
|
|
56
|
+
if (!toolUse)
|
|
57
|
+
return [];
|
|
58
|
+
const messages = [assistantMessage(item.id, [toolUse], threadId, model)];
|
|
59
|
+
if (toolResult)
|
|
60
|
+
messages.push(toolResultMessage(item.id, toolResult.content, toolResult.is_error, threadId));
|
|
61
|
+
return messages;
|
|
62
|
+
}
|
|
63
|
+
/** How codex shows a command it ran through a shell: `/bin/bash -lc '<script>'`. */
|
|
64
|
+
function shownCommand(argv) {
|
|
65
|
+
return argv.map((arg) => (/^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", "'\\''")}'`)).join(" ");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The messages a rollout item stands for, the same (ids included) as the stream carried live. Items
|
|
69
|
+
* other than messages and commands (file changes, MCP calls, ...) aren't mapped from the rollout yet.
|
|
70
|
+
*/
|
|
71
|
+
export function rolloutItemMessages(stored, threadId) {
|
|
72
|
+
const item = stored;
|
|
73
|
+
const text = (content) => (content ?? []).map((part) => part.text ?? "").join("");
|
|
74
|
+
if (item.type === "UserMessage" && "content" in item) {
|
|
75
|
+
const user = item;
|
|
76
|
+
return [{ type: "user", message: { role: "user", content: text(user.content) }, parent_tool_use_id: null, uuid: backendId(user.client_id ?? user.id), session_id: threadId }];
|
|
77
|
+
}
|
|
78
|
+
if (item.type === "AgentMessage" && item.id) {
|
|
79
|
+
const agent = item;
|
|
80
|
+
return [assistantMessage(agent.id, [{ type: "text", text: text(agent.content), citations: null }], threadId, "")];
|
|
81
|
+
}
|
|
82
|
+
if (item.type === "CommandExecution" && item.id) {
|
|
83
|
+
const command = item;
|
|
84
|
+
const input = { command: shownCommand(command.command ?? []), cwd: (command.cwd ?? "").replace(/^file:\/\//, "") };
|
|
85
|
+
return [
|
|
86
|
+
assistantMessage(command.id, [{ type: "tool_use", id: command.id, name: "Bash", input }], threadId, ""),
|
|
87
|
+
toolResultMessage(command.id, command.aggregated_output ?? "", command.status !== "completed" || (command.exit_code ?? 0) !== 0, threadId),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
/** The claude-style tool call a codex item stands for, if it is one. */
|
|
93
|
+
export function toolUseOf(item) {
|
|
94
|
+
switch (item.type) {
|
|
95
|
+
case "commandExecution":
|
|
96
|
+
return { type: "tool_use", id: item.id, name: "Bash", input: { command: item.command, cwd: item.cwd } };
|
|
97
|
+
case "fileChange":
|
|
98
|
+
return { type: "tool_use", id: item.id, name: "ApplyPatch", input: { changes: item.changes } };
|
|
99
|
+
case "mcpToolCall":
|
|
100
|
+
return { type: "tool_use", id: item.id, name: `mcp__${item.server}__${item.tool}`, input: item.arguments };
|
|
101
|
+
case "dynamicToolCall":
|
|
102
|
+
return { type: "tool_use", id: item.id, name: item.tool, input: item.arguments };
|
|
103
|
+
default:
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function toolResultOf(item) {
|
|
108
|
+
switch (item.type) {
|
|
109
|
+
case "commandExecution":
|
|
110
|
+
return {
|
|
111
|
+
type: "tool_result",
|
|
112
|
+
tool_use_id: item.id,
|
|
113
|
+
content: item.aggregatedOutput ?? "",
|
|
114
|
+
is_error: item.status !== "completed" || (item.exitCode ?? 0) !== 0,
|
|
115
|
+
};
|
|
116
|
+
case "fileChange":
|
|
117
|
+
return { type: "tool_result", tool_use_id: item.id, content: item.status, is_error: item.status !== "completed" };
|
|
118
|
+
case "mcpToolCall":
|
|
119
|
+
return {
|
|
120
|
+
type: "tool_result",
|
|
121
|
+
tool_use_id: item.id,
|
|
122
|
+
content: JSON.stringify(item.error ?? item.result?.content ?? null),
|
|
123
|
+
is_error: item.status !== "completed",
|
|
124
|
+
};
|
|
125
|
+
case "dynamicToolCall":
|
|
126
|
+
return {
|
|
127
|
+
type: "tool_result",
|
|
128
|
+
tool_use_id: item.id,
|
|
129
|
+
content: JSON.stringify(item.contentItems ?? null),
|
|
130
|
+
is_error: item.success === false,
|
|
131
|
+
};
|
|
132
|
+
default:
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TranscriptSource } from "../backend.ts";
|
|
2
|
+
import { type FileTranscript, type LineMessages, type TranscriptLayout } from "../fileTranscript.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The conversation from codex's own rollout, <codex home>/sessions/<date>/rollout-<time>-<thread>.jsonl:
|
|
5
|
+
* each finished item is an item_completed event there, as the messages the stream carried for it.
|
|
6
|
+
* Read directly: the app-server's thread/read loads the whole thread, and its paged
|
|
7
|
+
* thread/turns/list isn't implemented in codex 0.157.
|
|
8
|
+
*/
|
|
9
|
+
export declare function codexTranscript({ nativeId, env, files }: TranscriptSource): FileTranscript;
|
|
10
|
+
/** sessions/YYYY/MM/DD/rollout-<time>-<thread>.jsonl in codex's home. */
|
|
11
|
+
export declare const CODEX_TRANSCRIPT_LAYOUT: TranscriptLayout;
|
|
12
|
+
export declare function codexLineMessages(threadId: string): LineMessages;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createFileTranscript, locateTranscript } from "../fileTranscript.js";
|
|
2
|
+
import { rolloutItemMessages } from "./messages.js";
|
|
3
|
+
/**
|
|
4
|
+
* The conversation from codex's own rollout, <codex home>/sessions/<date>/rollout-<time>-<thread>.jsonl:
|
|
5
|
+
* each finished item is an item_completed event there, as the messages the stream carried for it.
|
|
6
|
+
* Read directly: the app-server's thread/read loads the whole thread, and its paged
|
|
7
|
+
* thread/turns/list isn't implemented in codex 0.157.
|
|
8
|
+
*/
|
|
9
|
+
export function codexTranscript({ nativeId, env, files }) {
|
|
10
|
+
const home = env.CODEX_HOME ?? `${env.HOME}/.codex`;
|
|
11
|
+
return createFileTranscript(files, () => locateTranscript(files, home, CODEX_TRANSCRIPT_LAYOUT, nativeId), codexLineMessages(nativeId), CODEX_TRANSCRIPT_LAYOUT);
|
|
12
|
+
}
|
|
13
|
+
/** sessions/YYYY/MM/DD/rollout-<time>-<thread>.jsonl in codex's home. */
|
|
14
|
+
export const CODEX_TRANSCRIPT_LAYOUT = { dir: "sessions", depth: 4, name: "rollout-*-{id}.jsonl", time: "timestamp" };
|
|
15
|
+
export function codexLineMessages(threadId) {
|
|
16
|
+
return (line) => {
|
|
17
|
+
const { type, payload } = line;
|
|
18
|
+
if (type !== "event_msg" || payload?.type !== "item_completed" || !payload.item)
|
|
19
|
+
return [];
|
|
20
|
+
return rolloutItemMessages(payload.item, threadId);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type SandboxFileOperations } from "../../sandbox/api.ts";
|
|
2
|
+
import { type AgentTranscriptMessage as TranscriptMessage, type AgentTranscriptPage, type AgentTranscriptRequest } from "../api.ts";
|
|
3
|
+
/** One page of a transcript: its messages oldest first, and the time the next (older) page is before. */
|
|
4
|
+
export type TranscriptPage = {
|
|
5
|
+
messages: TranscriptMessage[];
|
|
6
|
+
nextBefore: string | null;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A backend's transcript, read from its own JSONL file a chunk at a time, so a long conversation (a
|
|
10
|
+
* file of hundreds of MB) never has to be in memory: a page holds its messages and a few chunks, and
|
|
11
|
+
* a replay streams. Pages go by the time on each line: the file is in conversation order, its times
|
|
12
|
+
* nearly so (a line is written when its message completes, so parallel branches land seconds out of
|
|
13
|
+
* order), which lets a time be found by bisecting the file.
|
|
14
|
+
*/
|
|
15
|
+
export interface FileTranscript {
|
|
16
|
+
/**
|
|
17
|
+
* The newest `limit` or so messages from before `before` (epoch ms; null for the newest), by their
|
|
18
|
+
* times, with every other message of the oldest one's time, so the page before it is exactly those
|
|
19
|
+
* before that time. `found`: what a search of the file found beforehand.
|
|
20
|
+
*/
|
|
21
|
+
page(before: number | null, limit: number, found?: TranscriptFound): Promise<TranscriptPage>;
|
|
22
|
+
/**
|
|
23
|
+
* Hands `emit` every message after the one whose uuid is `afterId` (all of them when undefined), in
|
|
24
|
+
* order, as far as the file went when the replay began. False when `afterId` isn't in the file.
|
|
25
|
+
*/
|
|
26
|
+
replay(afterId: string | undefined, emit: (message: TranscriptMessage) => void): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
/** What a transcript is read through: the worker's own files (sandboxFiles), or what the host fetched of them. */
|
|
29
|
+
export type TranscriptFiles = {
|
|
30
|
+
size(path: string): Promise<number>;
|
|
31
|
+
readFileRange(path: string, start: number, end: number): Promise<Uint8Array>;
|
|
32
|
+
listDirectory(path: string): Promise<string[]>;
|
|
33
|
+
};
|
|
34
|
+
export declare function sandboxFiles(sandbox: SandboxFileOperations): TranscriptFiles;
|
|
35
|
+
/**
|
|
36
|
+
* Where a backend keeps its conversations, under its home: in `dir`, `depth` levels down (1: in
|
|
37
|
+
* `dir` itself), a file named `name`, where `*` is any characters and `{id}` the conversation's id.
|
|
38
|
+
* Each line of it is a JSON object with its ISO 8601 time in the field `time`. `copies`: what's in
|
|
39
|
+
* the line that starts a stretch the backend writes again (claude, resuming a session, writes what
|
|
40
|
+
* it said since its last compaction again, under the same ids and times), as it's written.
|
|
41
|
+
*/
|
|
42
|
+
export type TranscriptLayout = {
|
|
43
|
+
dir: string;
|
|
44
|
+
depth: number;
|
|
45
|
+
name: string;
|
|
46
|
+
time: string;
|
|
47
|
+
copies?: string;
|
|
48
|
+
};
|
|
49
|
+
/** A stretch of the file a reader passes over, from one line's start to another's. */
|
|
50
|
+
export type Skip = {
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
};
|
|
54
|
+
/** What a search of the file found beforehand (the host's, in the same round trip as the record). */
|
|
55
|
+
export type TranscriptFound = {
|
|
56
|
+
/** Where the page starts: FileTranscript.page's `before`, found. */
|
|
57
|
+
end: number;
|
|
58
|
+
/** The time of the first timed line from `end` on, if any. */
|
|
59
|
+
after: number | null;
|
|
60
|
+
/** The stretches written again. */
|
|
61
|
+
copies: Skip[];
|
|
62
|
+
};
|
|
63
|
+
/** The file of conversation `nativeId` laid out as `layout` under `home`; null when there's none yet. */
|
|
64
|
+
export declare function locateTranscript(files: TranscriptFiles, home: string, layout: TranscriptLayout, nativeId: string): Promise<string | null>;
|
|
65
|
+
/** The messages one line of the file stands for, oldest first (none for bookkeeping lines). */
|
|
66
|
+
export type LineMessages = (entry: unknown) => TranscriptMessage[];
|
|
67
|
+
/** How much of the file is read at a time. */
|
|
68
|
+
export declare const CHUNK_BYTES: number;
|
|
69
|
+
/**
|
|
70
|
+
* How far out of time order lines get, nearly always (parallel branches completing; in long claude
|
|
71
|
+
* sessions half are within 2s, 99% within a minute): searches allow for this much.
|
|
72
|
+
*/
|
|
73
|
+
export declare const TIME_SLACK_MS = 60000;
|
|
74
|
+
/** How many timed lines a probe of the bisection looks at: the earliest wins, so one odd line can't mislead it. */
|
|
75
|
+
export declare const PROBE_LINES = 3;
|
|
76
|
+
/** How much a probe reads, at most, before it gives up on finding a timed line (huge tool outputs). */
|
|
77
|
+
export declare const PROBE_BYTES: number;
|
|
78
|
+
/** `locate` finds the file; null when there's none yet (nothing stored). */
|
|
79
|
+
export declare function createFileTranscript(files: TranscriptFiles, locate: () => Promise<string | null>, lineMessages: LineMessages, layout: Pick<TranscriptLayout, "time" | "copies">): FileTranscript;
|
|
80
|
+
/** A page of the API's transcript (AgentTranscript.getTranscript); none stored for a session that never ran. */
|
|
81
|
+
export declare function transcriptPage(transcript: FileTranscript | null, { limit, before, cursor }?: AgentTranscriptRequest, found?: TranscriptFound): Promise<AgentTranscriptPage>;
|
|
82
|
+
/** AgentTranscriptRequest.before as epoch ms; null for none. */
|
|
83
|
+
export declare function transcriptTime(before: string | undefined): number | null;
|
|
84
|
+
/**
|
|
85
|
+
* `files` with what was already fetched of the file at `path` in the same round trip: its size, and
|
|
86
|
+
* pieces of it (what a page reads, mostly). Reading back beyond them (a page across huge tool
|
|
87
|
+
* outputs) fetches twice as much as the time before, so it takes a few more reads, not one per chunk.
|
|
88
|
+
*/
|
|
89
|
+
export declare function prefetchedFiles(files: TranscriptFiles, path: string, size: number, pieces: {
|
|
90
|
+
start: number;
|
|
91
|
+
bytes: Uint8Array;
|
|
92
|
+
}[]): TranscriptFiles;
|