@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.
- 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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { AgentEnvironmentCapabilitiesSchema } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import { createTangleExactProcessProvider, } from "./exact-process.js";
|
|
3
|
+
import { capabilitiesForClient, defaultTangleSandboxCapabilities, } from "./tangle-capabilities.js";
|
|
4
|
+
import { sandboxInstanceAsEnvironment } from "./tangle-environment.js";
|
|
5
|
+
import { assertCreateInputShape, assertMappedCreateOptions, assertMappedSecretNames, assertNoInlineSecretValues, sandboxOptionsFromCreateInput } from "./tangle-create-options.js";
|
|
6
|
+
import { statusFromUnknown } from "./tangle-environment-values.js";
|
|
7
|
+
import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, MAX_LIST_RESULTS, } from "./tangle-contract-safety.js";
|
|
8
|
+
export function createTangleProvider(options) {
|
|
9
|
+
const providerName = options.name ?? "tangle-sandbox";
|
|
10
|
+
boundedIdentifier(providerName, "Tangle provider name");
|
|
11
|
+
const exactProcess = options.exactProcess
|
|
12
|
+
? createTangleExactProcessProvider({
|
|
13
|
+
client: options.client,
|
|
14
|
+
options: options.exactProcess,
|
|
15
|
+
providerName,
|
|
16
|
+
})
|
|
17
|
+
: undefined;
|
|
18
|
+
const resolveCapabilities = async () => {
|
|
19
|
+
const configured = options.capabilities
|
|
20
|
+
? typeof options.capabilities === "function"
|
|
21
|
+
? await options.capabilities()
|
|
22
|
+
: options.capabilities
|
|
23
|
+
: defaultTangleSandboxCapabilities();
|
|
24
|
+
if (!exactProcess && configured.exactProcess) {
|
|
25
|
+
throw new Error("Tangle capabilities cannot advertise exactProcess without exactProcess configuration");
|
|
26
|
+
}
|
|
27
|
+
const withExactProcess = exactProcess
|
|
28
|
+
? {
|
|
29
|
+
...configured,
|
|
30
|
+
exactProcess: { egress: ["blocked", "strict"] },
|
|
31
|
+
}
|
|
32
|
+
: configured;
|
|
33
|
+
return AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForClient(withExactProcess, options.client));
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
name: providerName,
|
|
37
|
+
...(exactProcess ? { exactProcess } : {}),
|
|
38
|
+
capabilities: resolveCapabilities,
|
|
39
|
+
...(options.validateProfile ? { validateProfile: options.validateProfile } : {}),
|
|
40
|
+
async create(input) {
|
|
41
|
+
assertCreateInputShape(input);
|
|
42
|
+
input.signal?.throwIfAborted();
|
|
43
|
+
assertNoInlineSecretValues(input);
|
|
44
|
+
if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
|
|
45
|
+
throw new Error("Tangle create providerOptions are not supported");
|
|
46
|
+
}
|
|
47
|
+
const capabilities = await resolveCapabilities();
|
|
48
|
+
const createOptions = options.mapCreateInput?.(input) ??
|
|
49
|
+
sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode");
|
|
50
|
+
assertMappedCreateOptions(createOptions);
|
|
51
|
+
assertMappedSecretNames(createOptions);
|
|
52
|
+
input.signal?.throwIfAborted();
|
|
53
|
+
const createPromise = options.client.create(createOptions, input.signal ? { signal: input.signal } : undefined);
|
|
54
|
+
let box;
|
|
55
|
+
try {
|
|
56
|
+
box = await awaitWithSignal(createPromise, input.signal);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (input.signal?.aborted) {
|
|
60
|
+
void createPromise
|
|
61
|
+
.then(async (lateBox) => {
|
|
62
|
+
if (!lateBox.delete) {
|
|
63
|
+
attachCleanupHandle(error, lateBox);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await lateBox.delete();
|
|
68
|
+
}
|
|
69
|
+
catch (cleanupError) {
|
|
70
|
+
attachCleanupHandle(error, lateBox, cleanupError);
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.catch((lateError) => attachCleanupHandle(error, undefined, lateError));
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
input.signal?.throwIfAborted();
|
|
79
|
+
const environment = sandboxInstanceAsEnvironment(box, providerName, options.client, capabilities);
|
|
80
|
+
input.signal?.throwIfAborted();
|
|
81
|
+
return environment;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (!box.delete) {
|
|
85
|
+
const baseError = error instanceof Error ? error : new Error(String(error));
|
|
86
|
+
throw Object.assign(baseError, { cleanupHandle: box });
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await box.delete();
|
|
90
|
+
}
|
|
91
|
+
catch (cleanupError) {
|
|
92
|
+
const combined = new AggregateError([error, cleanupError], "Tangle environment validation and cleanup both failed");
|
|
93
|
+
attachCleanupHandle(combined, box, cleanupError);
|
|
94
|
+
throw combined;
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
...(options.client.get
|
|
100
|
+
? {
|
|
101
|
+
async get(id, operation) {
|
|
102
|
+
assertProviderOperationOptions(operation, "Tangle get");
|
|
103
|
+
boundedIdentifier(id, "Tangle environment id");
|
|
104
|
+
operation?.signal?.throwIfAborted();
|
|
105
|
+
const box = await awaitWithSignal(options.client.get?.(id, operation), operation?.signal);
|
|
106
|
+
operation?.signal?.throwIfAborted();
|
|
107
|
+
if (!box || boundedIdentifier(box.id, "Tangle environment id") !== id)
|
|
108
|
+
return null;
|
|
109
|
+
return box
|
|
110
|
+
? sandboxInstanceAsEnvironment(box, providerName, options.client, await resolveCapabilities())
|
|
111
|
+
: null;
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
: {}),
|
|
115
|
+
...(options.client.list
|
|
116
|
+
? {
|
|
117
|
+
async list(query, operation) {
|
|
118
|
+
assertProviderOperationOptions(operation, "Tangle list");
|
|
119
|
+
assertEnvironmentQuery(query);
|
|
120
|
+
operation?.signal?.throwIfAborted();
|
|
121
|
+
if (query?.name !== undefined)
|
|
122
|
+
boundedString(query.name, "Tangle environment query name");
|
|
123
|
+
if (query?.providerOptions && Object.keys(query.providerOptions).length > 0) {
|
|
124
|
+
throw new Error("Tangle environment list providerOptions are not supported");
|
|
125
|
+
}
|
|
126
|
+
if (query?.providerOptions !== undefined) {
|
|
127
|
+
if (!query.providerOptions || typeof query.providerOptions !== "object" || Array.isArray(query.providerOptions)) {
|
|
128
|
+
throw new Error("Tangle environment list providerOptions must be a JSON object");
|
|
129
|
+
}
|
|
130
|
+
assertBoundedJson(query.providerOptions);
|
|
131
|
+
}
|
|
132
|
+
if (query?.metadata !== undefined) {
|
|
133
|
+
if (!query.metadata || typeof query.metadata !== "object" || Array.isArray(query.metadata)) {
|
|
134
|
+
throw new Error("Tangle environment query metadata must be a JSON object");
|
|
135
|
+
}
|
|
136
|
+
assertBoundedJson(query.metadata);
|
|
137
|
+
}
|
|
138
|
+
const boxes = await awaitWithSignal(options.client.list?.(operation?.signal ? { signal: operation.signal } : undefined), operation?.signal);
|
|
139
|
+
if (!Array.isArray(boxes) || boxes.length > MAX_LIST_RESULTS) {
|
|
140
|
+
throw new Error("Tangle environment list exceeded its result bound");
|
|
141
|
+
}
|
|
142
|
+
const summaries = (boxes ?? []).filter((box) => {
|
|
143
|
+
boundedIdentifier(box.id, "Tangle environment id");
|
|
144
|
+
if (box.name !== undefined)
|
|
145
|
+
boundedString(box.name, "Tangle environment name");
|
|
146
|
+
if (box.metadata !== undefined) {
|
|
147
|
+
if (!box.metadata || typeof box.metadata !== "object" || Array.isArray(box.metadata)) {
|
|
148
|
+
throw new Error("Tangle environment metadata must be a JSON object");
|
|
149
|
+
}
|
|
150
|
+
assertBoundedJson(box.metadata);
|
|
151
|
+
}
|
|
152
|
+
const nameMatches = query?.name === undefined || box.name === query.name;
|
|
153
|
+
const metadataMatches = query?.metadata === undefined ||
|
|
154
|
+
Object.entries(query.metadata).every(([key, value]) => Object.hasOwn(box.metadata ?? {}, key) && JSON.stringify(box.metadata?.[key]) === JSON.stringify(value));
|
|
155
|
+
return nameMatches && metadataMatches;
|
|
156
|
+
}).map((box) => ({
|
|
157
|
+
id: boundedIdentifier(box.id, "Tangle environment id"),
|
|
158
|
+
provider: providerName,
|
|
159
|
+
...(box.name ? { name: box.name } : {}),
|
|
160
|
+
status: statusFromUnknown(box.status),
|
|
161
|
+
...(box.metadata ? { metadata: box.metadata } : {}),
|
|
162
|
+
}));
|
|
163
|
+
operation?.signal?.throwIfAborted();
|
|
164
|
+
return summaries;
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function assertProviderOperationOptions(options, label) {
|
|
171
|
+
if (options === undefined)
|
|
172
|
+
return;
|
|
173
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
174
|
+
throw new Error(`${label} options must be an object`);
|
|
175
|
+
}
|
|
176
|
+
for (const key of Object.keys(options)) {
|
|
177
|
+
if (key !== "signal")
|
|
178
|
+
throw new Error(`${label} options contain unsupported fields`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function assertEnvironmentQuery(query) {
|
|
182
|
+
if (query === undefined)
|
|
183
|
+
return;
|
|
184
|
+
if (!query || typeof query !== "object" || Array.isArray(query)) {
|
|
185
|
+
throw new Error("Tangle environment query must be an object");
|
|
186
|
+
}
|
|
187
|
+
const keys = new Set(Object.keys(query));
|
|
188
|
+
for (const key of ["name", "metadata", "providerOptions"])
|
|
189
|
+
keys.delete(key);
|
|
190
|
+
if (keys.size > 0)
|
|
191
|
+
throw new Error("Tangle environment query contains unsupported fields");
|
|
192
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExecResult as SandboxExecResult } from "@tangle-network/sandbox";
|
|
2
|
+
import type { ExecResult } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
import type { TokenUsage } from "@tangle-network/agent-interface";
|
|
4
|
+
export declare function execResultFromSandboxExecResult(result: SandboxExecResult | undefined): ExecResult;
|
|
5
|
+
export declare function tokenUsageFromData(data: Record<string, unknown>): TokenUsage | undefined;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { assertBoundedJson, boundedString } from "./tangle-contract-safety.js";
|
|
2
|
+
export function execResultFromSandboxExecResult(result) {
|
|
3
|
+
if (!result || typeof result !== "object") {
|
|
4
|
+
throw new Error("Tangle Sandbox exec returned no result");
|
|
5
|
+
}
|
|
6
|
+
const record = result;
|
|
7
|
+
assertBoundedJson(record);
|
|
8
|
+
if (typeof record.exitCode !== "number" ||
|
|
9
|
+
!Number.isSafeInteger(record.exitCode)) {
|
|
10
|
+
throw new Error("Tangle Sandbox exec returned an invalid exit code");
|
|
11
|
+
}
|
|
12
|
+
if (typeof record.stdout !== "string" || typeof record.stderr !== "string") {
|
|
13
|
+
throw new Error("Tangle Sandbox exec returned invalid output streams");
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
exitCode: record.exitCode,
|
|
17
|
+
stdout: boundedString(record.stdout, "Tangle exec stdout"),
|
|
18
|
+
stderr: boundedString(record.stderr, "Tangle exec stderr"),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function tokenUsageFromData(data) {
|
|
22
|
+
assertBoundedJson(data);
|
|
23
|
+
if (data.usage !== undefined &&
|
|
24
|
+
(!data.usage || typeof data.usage !== "object" || Array.isArray(data.usage))) {
|
|
25
|
+
throw new Error("Tangle usage must be an object");
|
|
26
|
+
}
|
|
27
|
+
if (data.tokenUsage !== undefined &&
|
|
28
|
+
(!data.tokenUsage ||
|
|
29
|
+
typeof data.tokenUsage !== "object" ||
|
|
30
|
+
Array.isArray(data.tokenUsage))) {
|
|
31
|
+
throw new Error("Tangle token usage must be an object");
|
|
32
|
+
}
|
|
33
|
+
// Only an explicit usage object counts. Scanning the raw event body turned
|
|
34
|
+
// any field named like a token count into a reported total, including on
|
|
35
|
+
// events that carry no usage at all.
|
|
36
|
+
const usageRecord = data.usage && typeof data.usage === "object"
|
|
37
|
+
? data.usage
|
|
38
|
+
: data.tokenUsage && typeof data.tokenUsage === "object"
|
|
39
|
+
? data.tokenUsage
|
|
40
|
+
: undefined;
|
|
41
|
+
if (usageRecord === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
assertBoundedJson(usageRecord);
|
|
44
|
+
const inputTokens = firstValidatedNumber(usageRecord, ["inputTokens", "tokensIn", "prompt_tokens"], "input token count", true);
|
|
45
|
+
const outputTokens = firstValidatedNumber(usageRecord, ["outputTokens", "tokensOut", "completion_tokens"], "output token count", true);
|
|
46
|
+
const totalTokens = firstValidatedNumber(usageRecord, ["totalTokens", "tokensTotal", "total_tokens"], "total token count", true);
|
|
47
|
+
const cacheReadInputTokens = firstValidatedNumber(usageRecord, ["cacheReadInputTokens", "cacheReadTokens", "cache_read_input_tokens"], "cache-read token count", true);
|
|
48
|
+
const cacheCreationInputTokens = firstValidatedNumber(usageRecord, [
|
|
49
|
+
"cacheCreationInputTokens",
|
|
50
|
+
"cacheWriteInputTokens",
|
|
51
|
+
"cacheCreationTokens",
|
|
52
|
+
"cache_creation_input_tokens",
|
|
53
|
+
], "cache-creation token count", true);
|
|
54
|
+
const reasoningTokens = firstValidatedNumber(usageRecord, ["reasoningTokens", "reasoning_tokens"], "reasoning token count", true);
|
|
55
|
+
const nestedCost = firstValidatedNumber(usageRecord, ["cost", "costUsd", "totalCostUsd"], "usage cost", false);
|
|
56
|
+
const topLevelCost = firstValidatedNumber(data, ["costUsd", "totalCostUsd"], "result cost", false);
|
|
57
|
+
const cost = nestedCost ?? topLevelCost;
|
|
58
|
+
// `TokenUsage` cannot express "not reported", so a usage record is only
|
|
59
|
+
// emitted when both counts were actually measured. Emitting a cost beside
|
|
60
|
+
// two zeroes published an unmeasured total as a measured one.
|
|
61
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
inputTokens,
|
|
65
|
+
outputTokens,
|
|
66
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
67
|
+
...(cacheReadInputTokens !== undefined
|
|
68
|
+
? { cacheReadInputTokens }
|
|
69
|
+
: {}),
|
|
70
|
+
...(cacheCreationInputTokens !== undefined
|
|
71
|
+
? { cacheCreationInputTokens }
|
|
72
|
+
: {}),
|
|
73
|
+
...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
|
|
74
|
+
...(cost !== undefined ? { cost } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function firstValidatedNumber(record, fields, label, integer) {
|
|
78
|
+
let selected;
|
|
79
|
+
for (const field of fields) {
|
|
80
|
+
if (!Object.hasOwn(record, field))
|
|
81
|
+
continue;
|
|
82
|
+
const value = record[field];
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
if (typeof value !== "number" ||
|
|
86
|
+
!Number.isFinite(value) ||
|
|
87
|
+
value < 0 ||
|
|
88
|
+
(integer && !Number.isSafeInteger(value))) {
|
|
89
|
+
throw new Error(`Tangle ${label} is invalid`);
|
|
90
|
+
}
|
|
91
|
+
selected ??= value;
|
|
92
|
+
}
|
|
93
|
+
return selected;
|
|
94
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentRunControlRef } from "@tangle-network/agent-interface";
|
|
2
|
+
import type { AgentSessionRef } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
export declare function sessionRefFromSandboxDispatch(dispatched: unknown, providerName: string, environmentId: string, expectedExecutionId: string | undefined, requestDigest?: `sha256:${string}` | undefined, expectedSessionId?: string | undefined): AgentSessionRef;
|
|
4
|
+
export declare function retainedSessionControlRef(sessionId: string, executionId: string, provider: string, environmentId: string, requestDigest?: `sha256:${string}`): AgentRunControlRef;
|
|
5
|
+
export declare function sessionPromptExecutionId(provider: string, environmentId: string, sessionId: string, turnId: string | undefined): string;
|
|
6
|
+
export declare function sameRunControlRef(left: AgentRunControlRef, right: AgentRunControlRef): boolean;
|
|
7
|
+
export declare function resolveRetainedSessionControlRef(candidate: AgentRunControlRef | undefined, sessionId: string, provider: string, environmentId: string): AgentRunControlRef | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { AgentExactRunControlRefSchema, canonicalCandidateDigest, } from "@tangle-network/agent-interface";
|
|
3
|
+
import { nonEmptyString } from "./tangle-environment-values.js";
|
|
4
|
+
import { assertBoundedJson, boundedIdentifier, } from "./tangle-contract-safety.js";
|
|
5
|
+
export function sessionRefFromSandboxDispatch(dispatched, providerName, environmentId, expectedExecutionId, requestDigest = undefined, expectedSessionId = undefined) {
|
|
6
|
+
const record = dispatched && typeof dispatched === "object" && !Array.isArray(dispatched)
|
|
7
|
+
? dispatched
|
|
8
|
+
: undefined;
|
|
9
|
+
if (record)
|
|
10
|
+
assertBoundedJson(record);
|
|
11
|
+
const id = record?.sessionId ?? record?.id;
|
|
12
|
+
if (!record || typeof id !== "string") {
|
|
13
|
+
throw new Error("sandbox dispatch returned no session id");
|
|
14
|
+
}
|
|
15
|
+
boundedIdentifier(id, "Tangle dispatched session id");
|
|
16
|
+
if (expectedSessionId !== undefined && id !== expectedSessionId) {
|
|
17
|
+
throw new Error("sandbox dispatch returned a session id different from the requested session");
|
|
18
|
+
}
|
|
19
|
+
const executionId = nonEmptyString(record.executionId);
|
|
20
|
+
if (executionId === undefined) {
|
|
21
|
+
throw new Error("sandbox dispatch returned no exact execution id for durable replay");
|
|
22
|
+
}
|
|
23
|
+
if (expectedExecutionId !== undefined &&
|
|
24
|
+
executionId !== expectedExecutionId) {
|
|
25
|
+
throw new Error("sandbox dispatch returned an execution id different from the requested run");
|
|
26
|
+
}
|
|
27
|
+
boundedIdentifier(executionId, "Tangle dispatched execution id");
|
|
28
|
+
if (record.status !== undefined && typeof record.status !== "string") {
|
|
29
|
+
throw new Error("sandbox dispatch returned an invalid status");
|
|
30
|
+
}
|
|
31
|
+
if (record.alreadyExisted !== undefined && typeof record.alreadyExisted !== "boolean") {
|
|
32
|
+
throw new Error("sandbox dispatch returned an invalid alreadyExisted flag");
|
|
33
|
+
}
|
|
34
|
+
if (record.dispatched !== undefined && typeof record.dispatched !== "boolean") {
|
|
35
|
+
throw new Error("sandbox dispatch returned an invalid dispatched flag");
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
provider: providerName,
|
|
40
|
+
controlRef: retainedSessionControlRef(id, executionId, providerName, environmentId, requestDigest ??
|
|
41
|
+
canonicalCandidateDigest({ provider: providerName, environmentId, id, executionId })),
|
|
42
|
+
metadata: {
|
|
43
|
+
...(record.status ? { status: record.status } : {}),
|
|
44
|
+
...(record.alreadyExisted !== undefined ? { alreadyExisted: record.alreadyExisted } : {}),
|
|
45
|
+
...(record.dispatched !== undefined ? { dispatched: record.dispatched } : {}),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function retainedSessionControlRef(sessionId, executionId, provider, environmentId, requestDigest) {
|
|
50
|
+
return AgentExactRunControlRefSchema.parse({
|
|
51
|
+
runId: executionId,
|
|
52
|
+
provider,
|
|
53
|
+
environmentId,
|
|
54
|
+
sessionId,
|
|
55
|
+
executionId,
|
|
56
|
+
requestDigest: requestDigest ??
|
|
57
|
+
canonicalCandidateDigest({ provider, environmentId, sessionId, executionId }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function sessionPromptExecutionId(provider, environmentId, sessionId, turnId) {
|
|
61
|
+
if (turnId === undefined)
|
|
62
|
+
return randomUUID();
|
|
63
|
+
const digest = createHash("sha256")
|
|
64
|
+
.update(`${provider}\0${environmentId}\0${sessionId}\0${turnId}`)
|
|
65
|
+
.digest("hex");
|
|
66
|
+
return `session-turn-${digest}`;
|
|
67
|
+
}
|
|
68
|
+
export function sameRunControlRef(left, right) {
|
|
69
|
+
return (left.runId === right.runId &&
|
|
70
|
+
left.provider === right.provider &&
|
|
71
|
+
left.environmentId === right.environmentId &&
|
|
72
|
+
left.sessionId === right.sessionId &&
|
|
73
|
+
left.executionId === right.executionId &&
|
|
74
|
+
left.requestDigest === right.requestDigest);
|
|
75
|
+
}
|
|
76
|
+
export function resolveRetainedSessionControlRef(candidate, sessionId, provider, environmentId) {
|
|
77
|
+
if (candidate === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const controlRef = AgentExactRunControlRefSchema.parse(candidate);
|
|
80
|
+
if (controlRef.provider !== provider ||
|
|
81
|
+
controlRef.environmentId !== environmentId ||
|
|
82
|
+
controlRef.sessionId !== sessionId) {
|
|
83
|
+
throw new Error("Tangle control reference does not match this session");
|
|
84
|
+
}
|
|
85
|
+
if (controlRef.runId !== controlRef.executionId) {
|
|
86
|
+
throw new Error("Tangle session control reference requires runId to equal executionId");
|
|
87
|
+
}
|
|
88
|
+
return controlRef;
|
|
89
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { BackendType, CreateSandboxOptions, ExecResult as SandboxExecResult, PromptOptions, PromptResult, SandboxEvent } from "@tangle-network/sandbox";
|
|
2
|
+
import type { InputPart } from "@tangle-network/agent-interface";
|
|
3
|
+
import type { AgentEnvironmentCapabilities, AgentEnvironmentProvider, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
4
|
+
export interface TangleExactProcessOptions {
|
|
5
|
+
teamId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SandboxClientLike {
|
|
8
|
+
create(options?: CreateSandboxOptions, requestOptions?: {
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}): Promise<SandboxInstanceLike>;
|
|
12
|
+
get?(id: string, requestOptions?: {
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}): Promise<SandboxInstanceLike | null>;
|
|
15
|
+
list?(options?: {
|
|
16
|
+
scope?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
offset?: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}): Promise<SandboxInstanceLike[]>;
|
|
21
|
+
describePlacement?(box: SandboxInstanceLike): unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface SandboxProcessStatusLike {
|
|
24
|
+
pid: number;
|
|
25
|
+
running: boolean;
|
|
26
|
+
exitCode: number;
|
|
27
|
+
exitSignal?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SandboxProcessLike {
|
|
30
|
+
readonly pid: number;
|
|
31
|
+
status(): Promise<SandboxProcessStatusLike>;
|
|
32
|
+
wait(): Promise<number>;
|
|
33
|
+
kill(signal?: "SIGKILL", options?: {
|
|
34
|
+
tree?: boolean;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
stdout(): AsyncIterable<string>;
|
|
37
|
+
stderr(): AsyncIterable<string>;
|
|
38
|
+
}
|
|
39
|
+
export interface SandboxProcessManagerLike {
|
|
40
|
+
list(): Promise<SandboxProcessStatusLike[]>;
|
|
41
|
+
get(pid: number): Promise<SandboxProcessLike | null>;
|
|
42
|
+
spawnExact(executable: string, args: readonly string[], options?: {
|
|
43
|
+
cwd?: string;
|
|
44
|
+
env?: Record<string, string>;
|
|
45
|
+
inheritEnv?: boolean;
|
|
46
|
+
stdin?: string;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}): Promise<SandboxProcessLike>;
|
|
50
|
+
}
|
|
51
|
+
export interface SandboxInstanceLike {
|
|
52
|
+
id: string;
|
|
53
|
+
name?: string;
|
|
54
|
+
status?: unknown;
|
|
55
|
+
metadata?: Record<string, unknown>;
|
|
56
|
+
streamPrompt(message: string | InputPart[], options?: PromptOptions): AsyncIterable<SandboxEvent>;
|
|
57
|
+
prompt?(message: string | InputPart[], options?: PromptOptions): Promise<PromptResult>;
|
|
58
|
+
dispatchPrompt?(message: string | InputPart[], options?: PromptOptions): Promise<unknown>;
|
|
59
|
+
session?(id: string, options?: {
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}): SandboxSessionLike;
|
|
62
|
+
read?(path: string, options?: {
|
|
63
|
+
sessionId?: string;
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}): Promise<string>;
|
|
66
|
+
write?(path: string, content: string, options?: {
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}): Promise<unknown>;
|
|
70
|
+
exec?(command: string, options?: unknown): Promise<SandboxExecResult>;
|
|
71
|
+
fs?: {
|
|
72
|
+
supportsWriteMode?: true;
|
|
73
|
+
stat(path: string): Promise<{
|
|
74
|
+
size: number;
|
|
75
|
+
isFile: boolean;
|
|
76
|
+
}>;
|
|
77
|
+
readBatch(paths: string[], options?: {
|
|
78
|
+
encoding?: "utf8" | "base64";
|
|
79
|
+
}): Promise<{
|
|
80
|
+
files: Array<{
|
|
81
|
+
path: string;
|
|
82
|
+
content: string;
|
|
83
|
+
encoding: "utf8" | "base64";
|
|
84
|
+
size: number;
|
|
85
|
+
}>;
|
|
86
|
+
errors: Array<{
|
|
87
|
+
path: string;
|
|
88
|
+
error: string;
|
|
89
|
+
code?: string;
|
|
90
|
+
}>;
|
|
91
|
+
}>;
|
|
92
|
+
write(path: string, content: string, options: {
|
|
93
|
+
encoding: "base64";
|
|
94
|
+
mode: number;
|
|
95
|
+
}): Promise<unknown>;
|
|
96
|
+
};
|
|
97
|
+
process?: SandboxProcessManagerLike;
|
|
98
|
+
checkpoint?(options?: {
|
|
99
|
+
signal?: AbortSignal;
|
|
100
|
+
} & Record<string, unknown>): Promise<unknown>;
|
|
101
|
+
fork?(checkpointId: string, options?: {
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
} & Record<string, unknown>): Promise<SandboxInstanceLike>;
|
|
104
|
+
refresh?(options?: {
|
|
105
|
+
signal?: AbortSignal;
|
|
106
|
+
}): Promise<void>;
|
|
107
|
+
delete?(options?: {
|
|
108
|
+
signal?: AbortSignal;
|
|
109
|
+
}): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
export interface SandboxSessionLike {
|
|
112
|
+
readonly id: string;
|
|
113
|
+
status(options?: {
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
}): Promise<unknown | null>;
|
|
116
|
+
events(options?: {
|
|
117
|
+
since?: string;
|
|
118
|
+
executionId?: string;
|
|
119
|
+
signal?: AbortSignal;
|
|
120
|
+
}): AsyncIterable<SandboxEvent>;
|
|
121
|
+
result(options?: {
|
|
122
|
+
executionId?: string;
|
|
123
|
+
signal?: AbortSignal;
|
|
124
|
+
}): Promise<PromptResult>;
|
|
125
|
+
prompt(message: string | InputPart[], options?: PromptOptions): Promise<PromptResult>;
|
|
126
|
+
interrupt(options?: {
|
|
127
|
+
executionId?: string;
|
|
128
|
+
signal?: AbortSignal;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
cancelled: boolean;
|
|
131
|
+
}>;
|
|
132
|
+
}
|
|
133
|
+
export interface TangleProviderOptions {
|
|
134
|
+
client: SandboxClientLike;
|
|
135
|
+
name?: string;
|
|
136
|
+
defaultBackend?: BackendType;
|
|
137
|
+
capabilities?: AgentEnvironmentCapabilities | (() => AgentEnvironmentCapabilities | Promise<AgentEnvironmentCapabilities>);
|
|
138
|
+
validateProfile?: AgentEnvironmentProvider["validateProfile"];
|
|
139
|
+
mapCreateInput?: (input: CreateAgentEnvironmentInput) => CreateSandboxOptions;
|
|
140
|
+
exactProcess?: TangleExactProcessOptions;
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,11 +27,47 @@
|
|
|
27
27
|
"dist/index.js",
|
|
28
28
|
"dist/exact-process.d.ts",
|
|
29
29
|
"dist/exact-process.js",
|
|
30
|
+
"dist/tangle-exact-process-environment.d.ts",
|
|
31
|
+
"dist/tangle-exact-process-environment.js",
|
|
32
|
+
"dist/tangle-contract-safety.d.ts",
|
|
33
|
+
"dist/tangle-contract-safety.js",
|
|
34
|
+
"dist/tangle-exact-process-runtime.d.ts",
|
|
35
|
+
"dist/tangle-exact-process-runtime.js",
|
|
36
|
+
"dist/tangle-exact-process-validation.d.ts",
|
|
37
|
+
"dist/tangle-exact-process-validation.js",
|
|
38
|
+
"dist/tangle-capabilities.d.ts",
|
|
39
|
+
"dist/tangle-capabilities.js",
|
|
40
|
+
"dist/tangle-create-options.d.ts",
|
|
41
|
+
"dist/tangle-create-options.js",
|
|
42
|
+
"dist/tangle-environment-values.d.ts",
|
|
43
|
+
"dist/tangle-environment-values.js",
|
|
44
|
+
"dist/tangle-environment.d.ts",
|
|
45
|
+
"dist/tangle-environment.js",
|
|
46
|
+
"dist/tangle-environment-dispatch.d.ts",
|
|
47
|
+
"dist/tangle-environment-dispatch.js",
|
|
48
|
+
"dist/tangle-environment-session.d.ts",
|
|
49
|
+
"dist/tangle-environment-session.js",
|
|
50
|
+
"dist/tangle-environment-control.d.ts",
|
|
51
|
+
"dist/tangle-environment-control.js",
|
|
52
|
+
"dist/tangle-environment-validation.d.ts",
|
|
53
|
+
"dist/tangle-environment-validation.js",
|
|
54
|
+
"dist/tangle-events.d.ts",
|
|
55
|
+
"dist/tangle-events.js",
|
|
56
|
+
"dist/tangle-prompt.d.ts",
|
|
57
|
+
"dist/tangle-prompt.js",
|
|
58
|
+
"dist/tangle-provider.d.ts",
|
|
59
|
+
"dist/tangle-provider.js",
|
|
60
|
+
"dist/tangle-result-values.d.ts",
|
|
61
|
+
"dist/tangle-result-values.js",
|
|
62
|
+
"dist/tangle-session-control.d.ts",
|
|
63
|
+
"dist/tangle-session-control.js",
|
|
64
|
+
"dist/tangle-types.d.ts",
|
|
65
|
+
"dist/tangle-types.js",
|
|
30
66
|
"README.md",
|
|
31
67
|
"LICENSE"
|
|
32
68
|
],
|
|
33
69
|
"dependencies": {
|
|
34
|
-
"@tangle-network/agent-interface": "0.
|
|
70
|
+
"@tangle-network/agent-interface": "0.46.1"
|
|
35
71
|
},
|
|
36
72
|
"peerDependencies": {
|
|
37
73
|
"@tangle-network/sandbox": ">=0.17.0 <1.0.0"
|
|
@@ -46,7 +82,7 @@
|
|
|
46
82
|
"@types/node": "25.6.0",
|
|
47
83
|
"typescript": "^6.0.3",
|
|
48
84
|
"vitest": "^4.1.5",
|
|
49
|
-
"@tangle-network/agent-provider-testkit": "0.
|
|
85
|
+
"@tangle-network/agent-provider-testkit": "0.6.1"
|
|
50
86
|
},
|
|
51
87
|
"scripts": {
|
|
52
88
|
"build": "tsc -p tsconfig.json",
|