@tangle-network/agent-provider-tangle 0.5.1 → 0.6.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.
Files changed (41) hide show
  1. package/dist/exact-process.d.ts +1 -4
  2. package/dist/exact-process.js +123 -206
  3. package/dist/index.d.ts +4 -126
  4. package/dist/index.js +3 -687
  5. package/dist/tangle-capabilities.d.ts +39 -0
  6. package/dist/tangle-capabilities.js +140 -0
  7. package/dist/tangle-contract-safety.d.ts +19 -0
  8. package/dist/tangle-contract-safety.js +240 -0
  9. package/dist/tangle-create-options.d.ts +9 -0
  10. package/dist/tangle-create-options.js +243 -0
  11. package/dist/tangle-environment-control.d.ts +6 -0
  12. package/dist/tangle-environment-control.js +50 -0
  13. package/dist/tangle-environment-dispatch.d.ts +3 -0
  14. package/dist/tangle-environment-dispatch.js +60 -0
  15. package/dist/tangle-environment-session.d.ts +4 -0
  16. package/dist/tangle-environment-session.js +156 -0
  17. package/dist/tangle-environment-validation.d.ts +11 -0
  18. package/dist/tangle-environment-validation.js +63 -0
  19. package/dist/tangle-environment-values.d.ts +8 -0
  20. package/dist/tangle-environment-values.js +84 -0
  21. package/dist/tangle-environment.d.ts +3 -0
  22. package/dist/tangle-environment.js +216 -0
  23. package/dist/tangle-events.d.ts +6 -0
  24. package/dist/tangle-events.js +111 -0
  25. package/dist/tangle-exact-process-environment.d.ts +3 -0
  26. package/dist/tangle-exact-process-environment.js +184 -0
  27. package/dist/tangle-exact-process-runtime.d.ts +5 -0
  28. package/dist/tangle-exact-process-runtime.js +150 -0
  29. package/dist/tangle-exact-process-validation.d.ts +17 -0
  30. package/dist/tangle-exact-process-validation.js +123 -0
  31. package/dist/tangle-prompt.d.ts +24 -0
  32. package/dist/tangle-prompt.js +166 -0
  33. package/dist/tangle-provider.d.ts +3 -0
  34. package/dist/tangle-provider.js +192 -0
  35. package/dist/tangle-result-values.d.ts +5 -0
  36. package/dist/tangle-result-values.js +94 -0
  37. package/dist/tangle-session-control.d.ts +7 -0
  38. package/dist/tangle-session-control.js +89 -0
  39. package/dist/tangle-types.d.ts +141 -0
  40. package/dist/tangle-types.js +1 -0
  41. package/package.json +39 -3
package/dist/index.js CHANGED
@@ -1,687 +1,3 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { AgentEnvironmentCapabilitiesSchema, } from "@tangle-network/agent-interface/environment-provider";
3
- import { AgentRunControlRefSchema, ContextTransferReceiptSchema, harnessSystemPromptIntents, } from "@tangle-network/agent-interface";
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";