@tangle-network/agent-provider-tangle 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exact-process.d.ts +1 -4
- package/dist/exact-process.js +123 -206
- package/dist/index.d.ts +4 -126
- package/dist/index.js +3 -687
- package/dist/tangle-capabilities.d.ts +39 -0
- package/dist/tangle-capabilities.js +140 -0
- package/dist/tangle-contract-safety.d.ts +19 -0
- package/dist/tangle-contract-safety.js +240 -0
- package/dist/tangle-create-options.d.ts +9 -0
- package/dist/tangle-create-options.js +243 -0
- package/dist/tangle-environment-control.d.ts +6 -0
- package/dist/tangle-environment-control.js +50 -0
- package/dist/tangle-environment-dispatch.d.ts +3 -0
- package/dist/tangle-environment-dispatch.js +60 -0
- package/dist/tangle-environment-session.d.ts +4 -0
- package/dist/tangle-environment-session.js +156 -0
- package/dist/tangle-environment-validation.d.ts +11 -0
- package/dist/tangle-environment-validation.js +63 -0
- package/dist/tangle-environment-values.d.ts +8 -0
- package/dist/tangle-environment-values.js +84 -0
- package/dist/tangle-environment.d.ts +3 -0
- package/dist/tangle-environment.js +216 -0
- package/dist/tangle-events.d.ts +6 -0
- package/dist/tangle-events.js +111 -0
- package/dist/tangle-exact-process-environment.d.ts +3 -0
- package/dist/tangle-exact-process-environment.js +184 -0
- package/dist/tangle-exact-process-runtime.d.ts +5 -0
- package/dist/tangle-exact-process-runtime.js +150 -0
- package/dist/tangle-exact-process-validation.d.ts +17 -0
- package/dist/tangle-exact-process-validation.js +123 -0
- package/dist/tangle-prompt.d.ts +24 -0
- package/dist/tangle-prompt.js +166 -0
- package/dist/tangle-provider.d.ts +3 -0
- package/dist/tangle-provider.js +192 -0
- package/dist/tangle-result-values.d.ts +5 -0
- package/dist/tangle-result-values.js +94 -0
- package/dist/tangle-session-control.d.ts +7 -0
- package/dist/tangle-session-control.js +89 -0
- package/dist/tangle-types.d.ts +141 -0
- package/dist/tangle-types.js +1 -0
- package/package.json +39 -3
package/dist/index.js
CHANGED
|
@@ -1,687 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { createTangleExactProcessProvider, } from "./exact-process.js";
|
|
5
|
-
export function createTangleProvider(options) {
|
|
6
|
-
const providerName = options.name ?? "tangle-sandbox";
|
|
7
|
-
const exactProcess = options.exactProcess
|
|
8
|
-
? createTangleExactProcessProvider({
|
|
9
|
-
client: options.client,
|
|
10
|
-
options: options.exactProcess,
|
|
11
|
-
providerName,
|
|
12
|
-
})
|
|
13
|
-
: undefined;
|
|
14
|
-
const resolveCapabilities = async () => {
|
|
15
|
-
const configured = options.capabilities
|
|
16
|
-
? typeof options.capabilities === "function"
|
|
17
|
-
? await options.capabilities()
|
|
18
|
-
: options.capabilities
|
|
19
|
-
: defaultTangleSandboxCapabilities();
|
|
20
|
-
if (!exactProcess && configured.exactProcess) {
|
|
21
|
-
throw new Error("Tangle capabilities cannot advertise exactProcess without exactProcess configuration");
|
|
22
|
-
}
|
|
23
|
-
return AgentEnvironmentCapabilitiesSchema.parse(exactProcess
|
|
24
|
-
? {
|
|
25
|
-
...configured,
|
|
26
|
-
exactProcess: { egress: ["blocked", "strict"] },
|
|
27
|
-
}
|
|
28
|
-
: configured);
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
name: providerName,
|
|
32
|
-
...(exactProcess ? { exactProcess } : {}),
|
|
33
|
-
capabilities: resolveCapabilities,
|
|
34
|
-
...(options.validateProfile ? { validateProfile: options.validateProfile } : {}),
|
|
35
|
-
async create(input) {
|
|
36
|
-
const capabilities = await resolveCapabilities();
|
|
37
|
-
const createOptions = options.mapCreateInput?.(input) ??
|
|
38
|
-
sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode");
|
|
39
|
-
const box = await options.client.create(createOptions, input.signal ? { signal: input.signal } : undefined);
|
|
40
|
-
return sandboxInstanceAsEnvironment(box, providerName, options.client, capabilities);
|
|
41
|
-
},
|
|
42
|
-
...(options.client.get
|
|
43
|
-
? {
|
|
44
|
-
async get(id) {
|
|
45
|
-
const box = await options.client.get?.(id);
|
|
46
|
-
return box
|
|
47
|
-
? sandboxInstanceAsEnvironment(box, providerName, options.client, await resolveCapabilities())
|
|
48
|
-
: null;
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
: {}),
|
|
52
|
-
...(options.client.list
|
|
53
|
-
? {
|
|
54
|
-
async list(query) {
|
|
55
|
-
const boxes = await options.client.list?.(query?.providerOptions);
|
|
56
|
-
return (boxes ?? []).map((box) => ({
|
|
57
|
-
id: String(box.id),
|
|
58
|
-
provider: providerName,
|
|
59
|
-
...(box.name ? { name: box.name } : {}),
|
|
60
|
-
status: statusFromUnknown(box.status),
|
|
61
|
-
...(box.metadata ? { metadata: box.metadata } : {}),
|
|
62
|
-
}));
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
: {}),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function sandboxInstanceAsEnvironment(box, providerName, client, capabilities) {
|
|
69
|
-
return {
|
|
70
|
-
id: String(box.id),
|
|
71
|
-
provider: providerName,
|
|
72
|
-
...(box.name ? { name: box.name } : {}),
|
|
73
|
-
async status() {
|
|
74
|
-
await box.refresh?.();
|
|
75
|
-
return statusFromUnknown(box.status);
|
|
76
|
-
},
|
|
77
|
-
async *stream(input) {
|
|
78
|
-
const expectedExecutionId = executionIdFromTurnInput(input);
|
|
79
|
-
const expectedSessionId = input.sessionId ?? input.controlRef?.sessionId;
|
|
80
|
-
for await (const event of box.streamPrompt(promptFromTurnInput(input), promptOptionsFromTurnInput(input, {
|
|
81
|
-
provider: providerName,
|
|
82
|
-
environmentId: String(box.id),
|
|
83
|
-
}))) {
|
|
84
|
-
yield environmentEventFromSandboxEvent(event, {
|
|
85
|
-
executionId: expectedExecutionId,
|
|
86
|
-
sessionId: expectedSessionId,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
...(capabilities.streaming.detach && box.dispatchPrompt
|
|
91
|
-
? {
|
|
92
|
-
async dispatch(input) {
|
|
93
|
-
const dispatched = await box.dispatchPrompt?.(promptFromTurnInput(input), promptOptionsFromTurnInput(input, {
|
|
94
|
-
provider: providerName,
|
|
95
|
-
environmentId: String(box.id),
|
|
96
|
-
}));
|
|
97
|
-
return sessionRefFromSandboxDispatch(dispatched, providerName, String(box.id), executionIdFromTurnInput(input));
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
: {}),
|
|
101
|
-
...((capabilities.sessions.continue ||
|
|
102
|
-
capabilities.streaming.replay ||
|
|
103
|
-
capabilities.streaming.detach) &&
|
|
104
|
-
box.session
|
|
105
|
-
? {
|
|
106
|
-
session(id, options) {
|
|
107
|
-
const session = box.session?.(id);
|
|
108
|
-
if (!session)
|
|
109
|
-
throw new Error("sandbox session(id) returned undefined");
|
|
110
|
-
return sandboxSessionAsAgentSession(session, resolveRetainedSessionControlRef(options?.controlRef, session.id, providerName, String(box.id)), providerName, String(box.id));
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
: {}),
|
|
114
|
-
...(capabilities.workspace.read && box.read
|
|
115
|
-
? { read: box.read.bind(box) }
|
|
116
|
-
: {}),
|
|
117
|
-
...(capabilities.workspace.write && box.write
|
|
118
|
-
? {
|
|
119
|
-
async write(path, content) {
|
|
120
|
-
await box.write?.(path, content);
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
: {}),
|
|
124
|
-
...(capabilities.workspace.exec && box.exec
|
|
125
|
-
? {
|
|
126
|
-
async exec(command, options) {
|
|
127
|
-
return execResultFromSandboxExecResult(await box.exec?.(command, options));
|
|
128
|
-
},
|
|
129
|
-
}
|
|
130
|
-
: {}),
|
|
131
|
-
...(capabilities.branching.checkpoint && box.checkpoint
|
|
132
|
-
? {
|
|
133
|
-
async checkpoint(options) {
|
|
134
|
-
const result = await box.checkpoint?.(options);
|
|
135
|
-
return { id: checkpointIdFromResult(result), provider: providerName };
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
: {}),
|
|
139
|
-
...(capabilities.branching.fork && box.fork
|
|
140
|
-
? {
|
|
141
|
-
async fork(checkpoint, options) {
|
|
142
|
-
const forked = await box.fork?.(checkpoint.id, options);
|
|
143
|
-
if (!forked)
|
|
144
|
-
throw new Error("sandbox fork returned no environment");
|
|
145
|
-
return sandboxInstanceAsEnvironment(forked, providerName, client, capabilities);
|
|
146
|
-
},
|
|
147
|
-
}
|
|
148
|
-
: {}),
|
|
149
|
-
...(capabilities.placement
|
|
150
|
-
? {
|
|
151
|
-
async placement() {
|
|
152
|
-
return placementInfoFromLoopPlacement(client.describePlacement?.(box), box);
|
|
153
|
-
},
|
|
154
|
-
}
|
|
155
|
-
: {}),
|
|
156
|
-
async refresh() {
|
|
157
|
-
await box.refresh?.();
|
|
158
|
-
},
|
|
159
|
-
async destroy() {
|
|
160
|
-
await box.delete?.();
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
function sandboxSessionAsAgentSession(session, controlRef, provider, environmentId) {
|
|
165
|
-
let activeControlRef = controlRef;
|
|
166
|
-
return {
|
|
167
|
-
id: session.id,
|
|
168
|
-
get controlRef() {
|
|
169
|
-
return activeControlRef;
|
|
170
|
-
},
|
|
171
|
-
async status() {
|
|
172
|
-
const status = await session.status();
|
|
173
|
-
if (!status)
|
|
174
|
-
return null;
|
|
175
|
-
return sessionStatusFromUnknown(status.status);
|
|
176
|
-
},
|
|
177
|
-
async *events(options) {
|
|
178
|
-
if (options?.executionId !== undefined &&
|
|
179
|
-
activeControlRef?.executionId !== undefined &&
|
|
180
|
-
options.executionId !== activeControlRef.executionId) {
|
|
181
|
-
throw new Error("Tangle replay executionId conflicts with the control reference");
|
|
182
|
-
}
|
|
183
|
-
const executionId = activeControlRef?.executionId ?? options?.executionId;
|
|
184
|
-
if (options?.since !== undefined && executionId === undefined) {
|
|
185
|
-
throw new Error("Tangle cursor replay requires an exact executionId from its control reference");
|
|
186
|
-
}
|
|
187
|
-
const seenEventIds = new Set();
|
|
188
|
-
for await (const event of session.events({
|
|
189
|
-
...(options?.since !== undefined ? { since: options.since } : {}),
|
|
190
|
-
...(executionId !== undefined ? { executionId } : {}),
|
|
191
|
-
...(options?.signal ? { signal: options.signal } : {}),
|
|
192
|
-
})) {
|
|
193
|
-
if (options?.since !== undefined && event.id === options.since)
|
|
194
|
-
continue;
|
|
195
|
-
const converted = environmentEventFromSandboxEvent(event, {
|
|
196
|
-
executionId,
|
|
197
|
-
sessionId: session.id,
|
|
198
|
-
});
|
|
199
|
-
if (executionId !== undefined && converted.id === undefined) {
|
|
200
|
-
throw new Error("Tangle exact session replay received an event without a stable id");
|
|
201
|
-
}
|
|
202
|
-
if (converted.id !== undefined) {
|
|
203
|
-
if (seenEventIds.has(converted.id)) {
|
|
204
|
-
throw new Error(`Tangle session replay repeated event id ${converted.id}`);
|
|
205
|
-
}
|
|
206
|
-
seenEventIds.add(converted.id);
|
|
207
|
-
}
|
|
208
|
-
yield converted;
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
async result() {
|
|
212
|
-
const expectedExecutionId = activeControlRef?.executionId;
|
|
213
|
-
if (expectedExecutionId === undefined) {
|
|
214
|
-
throw new Error("Tangle session result requires an exact executionId from its control reference");
|
|
215
|
-
}
|
|
216
|
-
const result = await session.result({ executionId: expectedExecutionId });
|
|
217
|
-
const resultRecord = validatedSandboxPromptResult(result);
|
|
218
|
-
if (resultRecord.executionId !== expectedExecutionId) {
|
|
219
|
-
throw new Error("Tangle session result did not confirm its exact executionId");
|
|
220
|
-
}
|
|
221
|
-
return agentTurnResultFromPromptRecord(resultRecord);
|
|
222
|
-
},
|
|
223
|
-
async prompt(input) {
|
|
224
|
-
if (input.sessionId !== undefined && input.sessionId !== session.id) {
|
|
225
|
-
throw new Error("Tangle sessionId conflicts with this session");
|
|
226
|
-
}
|
|
227
|
-
const requestedControlRef = resolveRetainedSessionControlRef(input.controlRef, session.id, provider, environmentId);
|
|
228
|
-
if (activeControlRef !== undefined &&
|
|
229
|
-
requestedControlRef !== undefined &&
|
|
230
|
-
!sameRunControlRef(activeControlRef, requestedControlRef)) {
|
|
231
|
-
throw new Error("Tangle prompt control reference conflicts with this session");
|
|
232
|
-
}
|
|
233
|
-
const sourceControlRef = requestedControlRef ?? activeControlRef;
|
|
234
|
-
const replay = input.lastEventId !== undefined;
|
|
235
|
-
if (replay &&
|
|
236
|
-
sourceControlRef?.executionId !== undefined &&
|
|
237
|
-
input.executionId !== undefined &&
|
|
238
|
-
input.executionId !== sourceControlRef.executionId) {
|
|
239
|
-
throw new Error("Tangle replay executionId conflicts with the control reference");
|
|
240
|
-
}
|
|
241
|
-
const executionId = replay
|
|
242
|
-
? input.executionId ?? sourceControlRef?.executionId
|
|
243
|
-
: input.executionId ??
|
|
244
|
-
sessionPromptExecutionId(provider, environmentId, session.id, input.turnId);
|
|
245
|
-
if (executionId === undefined) {
|
|
246
|
-
throw new Error("Tangle session replay requires the exact executionId from its control reference");
|
|
247
|
-
}
|
|
248
|
-
const result = await session.prompt(promptFromTurnInput(input), promptOptionsFromTurnInput({
|
|
249
|
-
...input,
|
|
250
|
-
sessionId: session.id,
|
|
251
|
-
executionId,
|
|
252
|
-
controlRef: undefined,
|
|
253
|
-
}, {
|
|
254
|
-
provider,
|
|
255
|
-
environmentId,
|
|
256
|
-
sessionId: session.id,
|
|
257
|
-
}));
|
|
258
|
-
const resultRecord = validatedSandboxPromptResult(result);
|
|
259
|
-
if (resultRecord.executionId !== executionId) {
|
|
260
|
-
throw new Error("Tangle session prompt did not confirm its exact executionId");
|
|
261
|
-
}
|
|
262
|
-
if (replay) {
|
|
263
|
-
activeControlRef =
|
|
264
|
-
sourceControlRef ??
|
|
265
|
-
retainedSessionControlRef(session.id, executionId, provider, environmentId);
|
|
266
|
-
return agentTurnResultFromPromptRecord(resultRecord);
|
|
267
|
-
}
|
|
268
|
-
activeControlRef = retainedSessionControlRef(session.id, executionId, provider, environmentId);
|
|
269
|
-
return agentTurnResultFromPromptRecord(resultRecord);
|
|
270
|
-
},
|
|
271
|
-
async cancel() {
|
|
272
|
-
const executionId = activeControlRef?.executionId;
|
|
273
|
-
if (executionId === undefined) {
|
|
274
|
-
throw new Error("Tangle session cancellation requires an exact executionId from its control reference");
|
|
275
|
-
}
|
|
276
|
-
await session.interrupt({ executionId });
|
|
277
|
-
},
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
function sandboxOptionsFromCreateInput(input, defaultBackend) {
|
|
281
|
-
const workspace = input.workspace ?? {};
|
|
282
|
-
if (workspace.environment !== undefined && workspace.image !== undefined) {
|
|
283
|
-
throw new Error("Tangle workspace cannot specify both environment and image");
|
|
284
|
-
}
|
|
285
|
-
const environment = workspace.image ?? workspace.environment;
|
|
286
|
-
const providerOptions = input.providerOptions?.sandboxCreateOptions;
|
|
287
|
-
const base = providerOptions && typeof providerOptions === "object"
|
|
288
|
-
? { ...providerOptions }
|
|
289
|
-
: {};
|
|
290
|
-
return {
|
|
291
|
-
...base,
|
|
292
|
-
...(environment !== undefined ? { environment } : {}),
|
|
293
|
-
...(workspace.repoUrl ? { git: { url: workspace.repoUrl, ref: workspace.gitRef } } : {}),
|
|
294
|
-
...(input.resources ? { resources: input.resources } : {}),
|
|
295
|
-
...(input.env ? { env: input.env } : {}),
|
|
296
|
-
...(Array.isArray(input.secrets) ? { secrets: input.secrets } : {}),
|
|
297
|
-
...(input.metadata ? { metadata: input.metadata } : {}),
|
|
298
|
-
...(input.name ? { name: input.name } : {}),
|
|
299
|
-
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
300
|
-
backend: {
|
|
301
|
-
...(base.backend ?? {}),
|
|
302
|
-
type: (input.backend ?? defaultBackend),
|
|
303
|
-
profile: inlineAgentProfile(input.profile),
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
function inlineAgentProfile(profile) {
|
|
308
|
-
if (typeof profile === "string") {
|
|
309
|
-
throw new Error("Tangle provider requires an inline AgentProfile, not a profile reference");
|
|
310
|
-
}
|
|
311
|
-
return profile;
|
|
312
|
-
}
|
|
313
|
-
function environmentEventFromSandboxEvent(event, expected = {}) {
|
|
314
|
-
if (!event || typeof event !== "object") {
|
|
315
|
-
throw new Error("Tangle Sandbox emitted a non-object event");
|
|
316
|
-
}
|
|
317
|
-
const record = event;
|
|
318
|
-
if (typeof record.type !== "string" || record.type.length === 0) {
|
|
319
|
-
throw new Error("Tangle Sandbox event omitted its type");
|
|
320
|
-
}
|
|
321
|
-
if (!record.data ||
|
|
322
|
-
typeof record.data !== "object" ||
|
|
323
|
-
Array.isArray(record.data)) {
|
|
324
|
-
throw new Error("Tangle Sandbox event omitted its object data");
|
|
325
|
-
}
|
|
326
|
-
if (record.id !== undefined &&
|
|
327
|
-
(typeof record.id !== "string" || record.id.length === 0)) {
|
|
328
|
-
throw new Error("Tangle Sandbox event contained an invalid event id");
|
|
329
|
-
}
|
|
330
|
-
const data = record.data;
|
|
331
|
-
const eventExecutionId = optionalNonEmptyString(data.executionId, "Tangle Sandbox event executionId");
|
|
332
|
-
const eventSessionId = optionalNonEmptyString(data.sessionId, "Tangle Sandbox event sessionId");
|
|
333
|
-
// Sandbox binds the stream with session.events({ executionId }). Individual
|
|
334
|
-
// event variants do not all repeat that selector, so validate IDs when present.
|
|
335
|
-
if (expected.executionId !== undefined &&
|
|
336
|
-
eventExecutionId !== undefined &&
|
|
337
|
-
eventExecutionId !== expected.executionId) {
|
|
338
|
-
throw new Error("Tangle exact session event identified a different executionId");
|
|
339
|
-
}
|
|
340
|
-
if (expected.sessionId !== undefined &&
|
|
341
|
-
eventSessionId !== undefined &&
|
|
342
|
-
eventSessionId !== expected.sessionId) {
|
|
343
|
-
throw new Error("Tangle exact session event identified a different sessionId");
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
type: record.type,
|
|
347
|
-
data,
|
|
348
|
-
...(typeof record.id === "string" ? { id: record.id } : {}),
|
|
349
|
-
usage: tokenUsageFromData(data),
|
|
350
|
-
providerEvent: event,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
function promptFromTurnInput(input) {
|
|
354
|
-
if (input.parts)
|
|
355
|
-
return input.parts;
|
|
356
|
-
return input.prompt ?? "";
|
|
357
|
-
}
|
|
358
|
-
function executionIdFromTurnInput(input) {
|
|
359
|
-
return input.executionId ?? input.controlRef?.executionId;
|
|
360
|
-
}
|
|
361
|
-
function promptOptionsFromTurnInput(input, target) {
|
|
362
|
-
if (input.contextTransfer !== undefined) {
|
|
363
|
-
throw new Error("Tangle provider does not yet support portable context transfer");
|
|
364
|
-
}
|
|
365
|
-
if (input.nativeContinuation !== undefined) {
|
|
366
|
-
throw new Error("Tangle provider does not yet support verified native continuation");
|
|
367
|
-
}
|
|
368
|
-
const controlRef = input.controlRef
|
|
369
|
-
? AgentRunControlRefSchema.parse(input.controlRef)
|
|
370
|
-
: undefined;
|
|
371
|
-
if (controlRef) {
|
|
372
|
-
if (controlRef.provider !== target.provider ||
|
|
373
|
-
controlRef.environmentId !== target.environmentId ||
|
|
374
|
-
(target.sessionId !== undefined &&
|
|
375
|
-
controlRef.sessionId !== target.sessionId)) {
|
|
376
|
-
throw new Error("Tangle control reference does not match this target");
|
|
377
|
-
}
|
|
378
|
-
if (controlRef.sessionId === undefined || controlRef.executionId === undefined) {
|
|
379
|
-
throw new Error("Tangle control reference requires exact sessionId and executionId");
|
|
380
|
-
}
|
|
381
|
-
if (controlRef.runId !== controlRef.executionId) {
|
|
382
|
-
throw new Error("Tangle control reference requires runId to equal executionId");
|
|
383
|
-
}
|
|
384
|
-
if (input.sessionId !== undefined &&
|
|
385
|
-
input.sessionId !== controlRef.sessionId) {
|
|
386
|
-
throw new Error("Tangle sessionId conflicts with the control reference");
|
|
387
|
-
}
|
|
388
|
-
if (input.executionId !== undefined &&
|
|
389
|
-
input.executionId !== controlRef.executionId) {
|
|
390
|
-
throw new Error("Tangle executionId conflicts with the control reference");
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const sessionId = input.sessionId ?? controlRef?.sessionId;
|
|
394
|
-
const executionId = input.executionId ?? controlRef?.executionId;
|
|
395
|
-
return {
|
|
396
|
-
...(sessionId ? { sessionId } : {}),
|
|
397
|
-
...(input.model ? { model: input.model } : {}),
|
|
398
|
-
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
|
|
399
|
-
...(input.context ? { context: input.context } : {}),
|
|
400
|
-
...(input.signal ? { signal: input.signal } : {}),
|
|
401
|
-
...(executionId ? { executionId } : {}),
|
|
402
|
-
...(input.lastEventId ? { lastEventId: input.lastEventId } : {}),
|
|
403
|
-
...(input.turnId ? { turnId: input.turnId } : {}),
|
|
404
|
-
...(input.detach !== undefined ? { detach: input.detach } : {}),
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
function validatedSandboxPromptResult(result) {
|
|
408
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
409
|
-
throw new Error("Tangle prompt returned no result object");
|
|
410
|
-
}
|
|
411
|
-
const record = result;
|
|
412
|
-
if (typeof record.success !== "boolean") {
|
|
413
|
-
throw new Error("Tangle prompt result omitted its success status");
|
|
414
|
-
}
|
|
415
|
-
const statuses = new Set([
|
|
416
|
-
"success",
|
|
417
|
-
"failed",
|
|
418
|
-
"blocked_on_approval",
|
|
419
|
-
"awaiting_question",
|
|
420
|
-
"awaiting_plan_decision",
|
|
421
|
-
]);
|
|
422
|
-
if (typeof record.status !== "string" ||
|
|
423
|
-
!statuses.has(record.status)) {
|
|
424
|
-
throw new Error("Tangle prompt result contained an invalid run status");
|
|
425
|
-
}
|
|
426
|
-
if (record.success !== (record.status === "success")) {
|
|
427
|
-
throw new Error("Tangle prompt result success flag conflicts with its run status");
|
|
428
|
-
}
|
|
429
|
-
if (typeof record.durationMs !== "number" ||
|
|
430
|
-
!Number.isFinite(record.durationMs) ||
|
|
431
|
-
record.durationMs < 0) {
|
|
432
|
-
throw new Error("Tangle prompt result contained an invalid duration");
|
|
433
|
-
}
|
|
434
|
-
for (const field of [
|
|
435
|
-
"executionId",
|
|
436
|
-
"response",
|
|
437
|
-
"text",
|
|
438
|
-
"finalText",
|
|
439
|
-
"error",
|
|
440
|
-
"errorCode",
|
|
441
|
-
"traceId",
|
|
442
|
-
]) {
|
|
443
|
-
if (record[field] !== undefined && typeof record[field] !== "string") {
|
|
444
|
-
throw new Error(`Tangle prompt result contained an invalid ${field}`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (record.executionId === "") {
|
|
448
|
-
throw new Error("Tangle prompt result contained an empty executionId");
|
|
449
|
-
}
|
|
450
|
-
tokenUsageFromData(record);
|
|
451
|
-
return record;
|
|
452
|
-
}
|
|
453
|
-
function agentTurnResultFromPromptRecord(record) {
|
|
454
|
-
const text = typeof record.response === "string"
|
|
455
|
-
? record.response
|
|
456
|
-
: typeof record.text === "string"
|
|
457
|
-
? record.text
|
|
458
|
-
: typeof record.finalText === "string"
|
|
459
|
-
? record.finalText
|
|
460
|
-
: "";
|
|
461
|
-
const contextTransferReceipt = ContextTransferReceiptSchema.safeParse(record.contextTransferReceipt);
|
|
462
|
-
if (record.contextTransferReceipt !== undefined &&
|
|
463
|
-
!contextTransferReceipt.success) {
|
|
464
|
-
throw new Error("Tangle prompt result contained an invalid context receipt");
|
|
465
|
-
}
|
|
466
|
-
return {
|
|
467
|
-
text,
|
|
468
|
-
success: record.success,
|
|
469
|
-
...(typeof record.error === "string" ? { error: record.error } : {}),
|
|
470
|
-
usage: tokenUsageFromData(record),
|
|
471
|
-
...(contextTransferReceipt.success
|
|
472
|
-
? { contextTransferReceipt: contextTransferReceipt.data }
|
|
473
|
-
: {}),
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
function sessionRefFromSandboxDispatch(dispatched, providerName, environmentId, expectedExecutionId) {
|
|
477
|
-
const record = dispatched && typeof dispatched === "object"
|
|
478
|
-
? dispatched
|
|
479
|
-
: undefined;
|
|
480
|
-
const id = record?.sessionId ?? record?.id;
|
|
481
|
-
if (typeof id !== "string" || id.length === 0 || !record) {
|
|
482
|
-
throw new Error("sandbox dispatch returned no session id");
|
|
483
|
-
}
|
|
484
|
-
const executionId = nonEmptyString(record.executionId);
|
|
485
|
-
if (executionId === undefined) {
|
|
486
|
-
throw new Error("sandbox dispatch returned no exact execution id for durable replay");
|
|
487
|
-
}
|
|
488
|
-
if (expectedExecutionId !== undefined &&
|
|
489
|
-
executionId !== expectedExecutionId) {
|
|
490
|
-
throw new Error("sandbox dispatch returned an execution id different from the requested run");
|
|
491
|
-
}
|
|
492
|
-
return {
|
|
493
|
-
id,
|
|
494
|
-
provider: providerName,
|
|
495
|
-
controlRef: retainedSessionControlRef(id, executionId, providerName, environmentId),
|
|
496
|
-
metadata: {
|
|
497
|
-
...(record.status ? { status: record.status } : {}),
|
|
498
|
-
...(record.alreadyExisted !== undefined ? { alreadyExisted: record.alreadyExisted } : {}),
|
|
499
|
-
...(record.dispatched !== undefined ? { dispatched: record.dispatched } : {}),
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
function retainedSessionControlRef(sessionId, executionId, provider, environmentId) {
|
|
504
|
-
return AgentRunControlRefSchema.parse({
|
|
505
|
-
runId: executionId,
|
|
506
|
-
provider,
|
|
507
|
-
environmentId,
|
|
508
|
-
sessionId,
|
|
509
|
-
executionId,
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function sessionPromptExecutionId(provider, environmentId, sessionId, turnId) {
|
|
513
|
-
if (turnId === undefined)
|
|
514
|
-
return randomUUID();
|
|
515
|
-
const digest = createHash("sha256")
|
|
516
|
-
.update(`${provider}\0${environmentId}\0${sessionId}\0${turnId}`)
|
|
517
|
-
.digest("hex");
|
|
518
|
-
return `session-turn-${digest}`;
|
|
519
|
-
}
|
|
520
|
-
function sameRunControlRef(left, right) {
|
|
521
|
-
return (left.runId === right.runId &&
|
|
522
|
-
left.provider === right.provider &&
|
|
523
|
-
left.environmentId === right.environmentId &&
|
|
524
|
-
left.sessionId === right.sessionId &&
|
|
525
|
-
left.executionId === right.executionId);
|
|
526
|
-
}
|
|
527
|
-
function resolveRetainedSessionControlRef(candidate, sessionId, provider, environmentId) {
|
|
528
|
-
if (candidate === undefined)
|
|
529
|
-
return undefined;
|
|
530
|
-
const controlRef = AgentRunControlRefSchema.parse(candidate);
|
|
531
|
-
if (controlRef.provider !== provider ||
|
|
532
|
-
controlRef.environmentId !== environmentId ||
|
|
533
|
-
controlRef.sessionId !== sessionId) {
|
|
534
|
-
throw new Error("Tangle control reference does not match this session");
|
|
535
|
-
}
|
|
536
|
-
if (controlRef.executionId === undefined ||
|
|
537
|
-
controlRef.runId !== controlRef.executionId) {
|
|
538
|
-
throw new Error("Tangle session control reference requires runId to equal executionId");
|
|
539
|
-
}
|
|
540
|
-
return controlRef;
|
|
541
|
-
}
|
|
542
|
-
function nonEmptyString(value) {
|
|
543
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
544
|
-
}
|
|
545
|
-
function optionalNonEmptyString(value, label) {
|
|
546
|
-
if (value === undefined)
|
|
547
|
-
return undefined;
|
|
548
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
549
|
-
throw new Error(`${label} must be a non-empty string`);
|
|
550
|
-
}
|
|
551
|
-
return value;
|
|
552
|
-
}
|
|
553
|
-
function execResultFromSandboxExecResult(result) {
|
|
554
|
-
if (!result || typeof result !== "object") {
|
|
555
|
-
throw new Error("Tangle Sandbox exec returned no result");
|
|
556
|
-
}
|
|
557
|
-
const record = result;
|
|
558
|
-
if (typeof record.exitCode !== "number" ||
|
|
559
|
-
!Number.isSafeInteger(record.exitCode)) {
|
|
560
|
-
throw new Error("Tangle Sandbox exec returned an invalid exit code");
|
|
561
|
-
}
|
|
562
|
-
if (typeof record.stdout !== "string" || typeof record.stderr !== "string") {
|
|
563
|
-
throw new Error("Tangle Sandbox exec returned invalid output streams");
|
|
564
|
-
}
|
|
565
|
-
return {
|
|
566
|
-
exitCode: record.exitCode,
|
|
567
|
-
stdout: record.stdout,
|
|
568
|
-
stderr: record.stderr,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
function checkpointIdFromResult(result) {
|
|
572
|
-
const record = result && typeof result === "object" ? result : {};
|
|
573
|
-
const id = record.checkpointId ?? record.id;
|
|
574
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
575
|
-
throw new Error("sandbox checkpoint returned no checkpoint id");
|
|
576
|
-
}
|
|
577
|
-
return id;
|
|
578
|
-
}
|
|
579
|
-
function placementInfoFromLoopPlacement(placement, box) {
|
|
580
|
-
if (!placement || typeof placement !== "object")
|
|
581
|
-
return { kind: "sandbox", sandboxId: String(box.id) };
|
|
582
|
-
const record = placement;
|
|
583
|
-
return {
|
|
584
|
-
kind: record.kind === "fleet" ? "fleet" : "sandbox",
|
|
585
|
-
sandboxId: typeof record.sandboxId === "string" ? record.sandboxId : String(box.id),
|
|
586
|
-
...(typeof record.fleetId === "string" ? { fleetId: record.fleetId } : {}),
|
|
587
|
-
...(typeof record.machineId === "string" ? { machineId: record.machineId } : {}),
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
function tokenUsageFromData(data) {
|
|
591
|
-
if (data.usage !== undefined &&
|
|
592
|
-
(!data.usage || typeof data.usage !== "object" || Array.isArray(data.usage))) {
|
|
593
|
-
throw new Error("Tangle usage must be an object");
|
|
594
|
-
}
|
|
595
|
-
if (data.tokenUsage !== undefined &&
|
|
596
|
-
(!data.tokenUsage ||
|
|
597
|
-
typeof data.tokenUsage !== "object" ||
|
|
598
|
-
Array.isArray(data.tokenUsage))) {
|
|
599
|
-
throw new Error("Tangle token usage must be an object");
|
|
600
|
-
}
|
|
601
|
-
const usageRecord = data.usage && typeof data.usage === "object"
|
|
602
|
-
? data.usage
|
|
603
|
-
: data.tokenUsage && typeof data.tokenUsage === "object"
|
|
604
|
-
? data.tokenUsage
|
|
605
|
-
: data;
|
|
606
|
-
const inputTokens = firstValidatedNumber(usageRecord, ["inputTokens", "tokensIn", "prompt_tokens"], "input token count", true);
|
|
607
|
-
const outputTokens = firstValidatedNumber(usageRecord, ["outputTokens", "tokensOut", "completion_tokens"], "output token count", true);
|
|
608
|
-
const nestedCost = firstValidatedNumber(usageRecord, ["cost", "costUsd", "totalCostUsd"], "usage cost", false);
|
|
609
|
-
const topLevelCost = firstValidatedNumber(data, ["costUsd", "totalCostUsd"], "result cost", false);
|
|
610
|
-
const cost = nestedCost ?? topLevelCost;
|
|
611
|
-
if (inputTokens === undefined && outputTokens === undefined && cost === undefined)
|
|
612
|
-
return undefined;
|
|
613
|
-
return {
|
|
614
|
-
inputTokens: inputTokens ?? 0,
|
|
615
|
-
outputTokens: outputTokens ?? 0,
|
|
616
|
-
...(cost !== undefined ? { cost } : {}),
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
function firstValidatedNumber(record, fields, label, integer) {
|
|
620
|
-
let selected;
|
|
621
|
-
for (const field of fields) {
|
|
622
|
-
const value = record[field];
|
|
623
|
-
if (value === undefined)
|
|
624
|
-
continue;
|
|
625
|
-
if (typeof value !== "number" ||
|
|
626
|
-
!Number.isFinite(value) ||
|
|
627
|
-
value < 0 ||
|
|
628
|
-
(integer && !Number.isSafeInteger(value))) {
|
|
629
|
-
throw new Error(`Tangle ${label} is invalid`);
|
|
630
|
-
}
|
|
631
|
-
selected ??= value;
|
|
632
|
-
}
|
|
633
|
-
return selected;
|
|
634
|
-
}
|
|
635
|
-
function statusFromUnknown(status) {
|
|
636
|
-
if (status === "pending" || status === "provisioning" || status === "running")
|
|
637
|
-
return status;
|
|
638
|
-
if (status === "stopped" || status === "failed" || status === "expired")
|
|
639
|
-
return status;
|
|
640
|
-
if (status === "completed" || status === "cancelled")
|
|
641
|
-
return "stopped";
|
|
642
|
-
return "unknown";
|
|
643
|
-
}
|
|
644
|
-
function sessionStatusFromUnknown(status) {
|
|
645
|
-
if (status === "completed" || status === "cancelled")
|
|
646
|
-
return status;
|
|
647
|
-
return statusFromUnknown(status);
|
|
648
|
-
}
|
|
649
|
-
/**
|
|
650
|
-
* @param harness The harness the sandbox will materialize the profile with. The prompt intents are
|
|
651
|
-
* that harness's, not this adapter's: forwarding the whole profile on the wire makes both fields
|
|
652
|
-
* *expressible*, but the sandbox's materializer refuses the intent its harness has no control for
|
|
653
|
-
* (opencode has no replacement, codex and gemini no addition). Omit it and both intents declare
|
|
654
|
-
* `false` — an adapter that cannot name its harness cannot promise either one.
|
|
655
|
-
*/
|
|
656
|
-
export function defaultTangleSandboxCapabilities(harness) {
|
|
657
|
-
return {
|
|
658
|
-
profile: {
|
|
659
|
-
namedProfiles: true,
|
|
660
|
-
systemPrompt: harnessSystemPromptIntents(harness),
|
|
661
|
-
instructions: true,
|
|
662
|
-
tools: true,
|
|
663
|
-
permissions: true,
|
|
664
|
-
mcp: true,
|
|
665
|
-
subagents: true,
|
|
666
|
-
resources: {
|
|
667
|
-
files: true,
|
|
668
|
-
instructions: true,
|
|
669
|
-
tools: true,
|
|
670
|
-
skills: true,
|
|
671
|
-
agents: true,
|
|
672
|
-
commands: true,
|
|
673
|
-
},
|
|
674
|
-
hooks: true,
|
|
675
|
-
modes: true,
|
|
676
|
-
runtimeUpdate: true,
|
|
677
|
-
validation: true,
|
|
678
|
-
},
|
|
679
|
-
streaming: { live: true, replay: true, detach: true, turnIdempotency: true },
|
|
680
|
-
sessions: { continue: true, list: true, messages: true },
|
|
681
|
-
workspace: { read: true, write: true, exec: true, git: true, upload: true, download: true },
|
|
682
|
-
branching: { checkpoint: false, fork: false },
|
|
683
|
-
placement: true,
|
|
684
|
-
usage: true,
|
|
685
|
-
confidential: true,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
1
|
+
export * from "./tangle-types.js";
|
|
2
|
+
export { createTangleProvider } from "./tangle-provider.js";
|
|
3
|
+
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|