@tangle-network/agent-provider-tangle 0.5.1 → 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.
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
@@ -0,0 +1,39 @@
1
+ import type { AgentEnvironmentCapabilities, HarnessType } from "@tangle-network/agent-interface";
2
+ import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
3
+ /**
4
+ * The full capability document this adapter supports when the Sandbox client
5
+ * implements every optional method.
6
+ *
7
+ * This is an upper bound, not a claim. `capabilitiesForSandbox()` narrows it
8
+ * to what a specific client actually exposes, because a capability the client
9
+ * cannot back becomes an action the caller selects and finds missing.
10
+ */
11
+ export declare function defaultTangleSandboxCapabilities(harness?: HarnessType): AgentEnvironmentCapabilities;
12
+ /** Optional methods whose absence must clear the matching declared capability. */
13
+ export interface SandboxCapabilitySupport {
14
+ dispatchPrompt: boolean;
15
+ session: boolean;
16
+ read: boolean;
17
+ write: boolean;
18
+ exec: boolean;
19
+ checkpoint: boolean;
20
+ fork: boolean;
21
+ placement: boolean;
22
+ destroy: boolean;
23
+ }
24
+ export declare function sandboxCapabilitySupport(box: SandboxInstanceLike, client: SandboxClientLike): SandboxCapabilitySupport;
25
+ /**
26
+ * Narrow provider-level claims to capabilities the client can actually back.
27
+ *
28
+ * A client without placement metadata cannot satisfy placement(), and this
29
+ * adapter has no durable branching, interaction, or native-continuation
30
+ * implementation regardless of an overly broad configured document.
31
+ */
32
+ export declare function capabilitiesForClient(declared: AgentEnvironmentCapabilities, client: SandboxClientLike): AgentEnvironmentCapabilities;
33
+ /**
34
+ * Narrow a declared capability document to what this Sandbox instance backs.
35
+ *
36
+ * Braid derives product actions from these flags, so an over-claimed flag is
37
+ * an offered action that throws at the moment the user selects it.
38
+ */
39
+ export declare function capabilitiesForSandbox(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport): AgentEnvironmentCapabilities;
@@ -0,0 +1,140 @@
1
+ import { harnessSystemPromptIntents } from "@tangle-network/agent-interface";
2
+ /**
3
+ * The full capability document this adapter supports when the Sandbox client
4
+ * implements every optional method.
5
+ *
6
+ * This is an upper bound, not a claim. `capabilitiesForSandbox()` narrows it
7
+ * to what a specific client actually exposes, because a capability the client
8
+ * cannot back becomes an action the caller selects and finds missing.
9
+ */
10
+ export function defaultTangleSandboxCapabilities(harness) {
11
+ return {
12
+ profile: {
13
+ namedProfiles: true,
14
+ systemPrompt: harnessSystemPromptIntents(harness),
15
+ instructions: true,
16
+ tools: true,
17
+ permissions: true,
18
+ mcp: true,
19
+ subagents: true,
20
+ resources: {
21
+ files: true,
22
+ instructions: true,
23
+ tools: true,
24
+ skills: true,
25
+ agents: true,
26
+ commands: true,
27
+ },
28
+ hooks: true,
29
+ modes: true,
30
+ runtimeUpdate: true,
31
+ validation: true,
32
+ },
33
+ streaming: { live: true, replay: true, detach: true, turnIdempotency: true },
34
+ // A Sandbox session id is not a Braid context-boundary proof. Native
35
+ // continuation stays unavailable until this adapter can verify one.
36
+ sessions: { continue: false, list: false, messages: false },
37
+ workspace: { read: true, write: true, exec: true, git: false, upload: true, download: true },
38
+ branching: { checkpoint: false, fork: false },
39
+ placement: true,
40
+ usage: false,
41
+ // Confidential execution needs verified attestation evidence, which this
42
+ // adapter does not yet obtain, so it is never declared by default.
43
+ confidential: false,
44
+ };
45
+ }
46
+ export function sandboxCapabilitySupport(box, client) {
47
+ return {
48
+ dispatchPrompt: typeof box.dispatchPrompt === "function",
49
+ session: typeof box.session === "function",
50
+ read: typeof box.read === "function",
51
+ write: typeof box.write === "function",
52
+ exec: typeof box.exec === "function",
53
+ checkpoint: typeof box.checkpoint === "function",
54
+ fork: typeof box.fork === "function",
55
+ placement: typeof client.describePlacement === "function",
56
+ destroy: typeof box.delete === "function",
57
+ };
58
+ }
59
+ /**
60
+ * Narrow provider-level claims to capabilities the client can actually back.
61
+ *
62
+ * A client without placement metadata cannot satisfy placement(), and this
63
+ * adapter has no durable branching, interaction, or native-continuation
64
+ * implementation regardless of an overly broad configured document.
65
+ */
66
+ export function capabilitiesForClient(declared, client) {
67
+ const narrowed = {
68
+ ...declared,
69
+ streaming: {
70
+ ...declared.streaming,
71
+ replay: declared.streaming.replay,
72
+ detach: declared.streaming.detach,
73
+ turnIdempotency: declared.streaming.turnIdempotency,
74
+ },
75
+ sessions: { ...declared.sessions, continue: false, list: false, messages: false },
76
+ workspace: { ...declared.workspace, git: false },
77
+ usage: false,
78
+ branching: {
79
+ ...declared.branching,
80
+ checkpoint: false,
81
+ fork: false,
82
+ ...(declared.branching.retrySafe !== undefined ? { retrySafe: false } : {}),
83
+ ...(declared.branching.lookup !== undefined ? { lookup: false } : {}),
84
+ ...(declared.branching.cleanup !== undefined ? { cleanup: false } : {}),
85
+ },
86
+ placement: declared.placement && typeof client.describePlacement === "function",
87
+ };
88
+ delete narrowed.interactions;
89
+ delete narrowed.retainedControl;
90
+ delete narrowed.nativeContinuation;
91
+ return narrowed;
92
+ }
93
+ /**
94
+ * Narrow a declared capability document to what this Sandbox instance backs.
95
+ *
96
+ * Braid derives product actions from these flags, so an over-claimed flag is
97
+ * an offered action that throws at the moment the user selects it.
98
+ */
99
+ export function capabilitiesForSandbox(declared, support) {
100
+ const narrowed = { ...declared };
101
+ delete narrowed.interactions;
102
+ delete narrowed.retainedControl;
103
+ delete narrowed.nativeContinuation;
104
+ return {
105
+ ...narrowed,
106
+ streaming: {
107
+ ...declared.streaming,
108
+ detach: declared.streaming.detach && support.dispatchPrompt,
109
+ replay: declared.streaming.replay && support.session,
110
+ turnIdempotency: declared.streaming.turnIdempotency && support.session,
111
+ },
112
+ sessions: {
113
+ ...declared.sessions,
114
+ continue: false,
115
+ list: false,
116
+ messages: false,
117
+ },
118
+ workspace: {
119
+ ...declared.workspace,
120
+ read: declared.workspace.read && support.read,
121
+ write: declared.workspace.write && support.write,
122
+ exec: declared.workspace.exec && support.exec,
123
+ git: false,
124
+ upload: declared.workspace.upload && support.write,
125
+ download: declared.workspace.download && support.read,
126
+ },
127
+ branching: {
128
+ ...narrowed.branching,
129
+ checkpoint: false,
130
+ fork: false,
131
+ ...(declared.branching.retrySafe !== undefined
132
+ ? { retrySafe: false }
133
+ : {}),
134
+ ...(declared.branching.lookup !== undefined ? { lookup: false } : {}),
135
+ ...(declared.branching.cleanup !== undefined ? { cleanup: false } : {}),
136
+ },
137
+ placement: narrowed.placement && support.placement,
138
+ usage: false,
139
+ };
140
+ }
@@ -0,0 +1,19 @@
1
+ import type { CreateAgentExactProcessEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
2
+ import type { TangleExactProcessOptions } from "./tangle-types.js";
3
+ export declare const MAX_EXACT_FILE_BYTES: number;
4
+ export declare const MAX_LIST_RESULTS = 100000;
5
+ export declare const MAX_IDENTIFIER_LENGTH = 512;
6
+ export declare const MAX_STRING_LENGTH = 16384;
7
+ export declare const MAX_ARRAY_LENGTH = 1024;
8
+ export declare const MAX_MAP_ENTRIES = 256;
9
+ export declare const MAX_JSON_DEPTH = 16;
10
+ export declare const MAX_JSON_NODES = 8192;
11
+ export declare function boundedIdentifier(value: unknown, label: string): string;
12
+ export declare function boundedString(value: unknown, label: string): string;
13
+ export declare function isBoundedJson(value: unknown): boolean;
14
+ export declare function assertBoundedJson(value: unknown): void;
15
+ /** Keep a late-created provider handle reachable when an abort wins the race. */
16
+ export declare function attachCleanupHandle(error: unknown, handle: unknown, cleanupError?: unknown): void;
17
+ export declare function exactProcessRequestDigest(input: CreateAgentExactProcessEnvironmentInput, providerName: string, options: TangleExactProcessOptions): `sha256:${string}`;
18
+ export declare function validateExactProcessCreateInput(input: CreateAgentExactProcessEnvironmentInput, providerName: string, options: TangleExactProcessOptions): void;
19
+ export declare function awaitWithSignal<T>(operation: Promise<T> | T | undefined, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,240 @@
1
+ import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
2
+ export const MAX_EXACT_FILE_BYTES = 64 * 1024 * 1024;
3
+ export const MAX_LIST_RESULTS = 100_000;
4
+ export const MAX_IDENTIFIER_LENGTH = 512;
5
+ export const MAX_STRING_LENGTH = 16_384;
6
+ export const MAX_ARRAY_LENGTH = 1_024;
7
+ export const MAX_MAP_ENTRIES = 256;
8
+ export const MAX_JSON_DEPTH = 16;
9
+ export const MAX_JSON_NODES = 8_192;
10
+ const IMMUTABLE_TANGLE_IMAGE = /^(?:sha256:[a-f0-9]{64}|\S+@sha256:[a-f0-9]{64})$/i;
11
+ export function boundedIdentifier(value, label) {
12
+ if (typeof value !== "string" ||
13
+ value.length === 0 ||
14
+ value.length > MAX_IDENTIFIER_LENGTH ||
15
+ value.trim() !== value) {
16
+ throw new Error(`${label} is invalid`);
17
+ }
18
+ return value;
19
+ }
20
+ export function boundedString(value, label) {
21
+ if (typeof value !== "string" || value.length > MAX_STRING_LENGTH) {
22
+ throw new Error(`${label} exceeds its bound`);
23
+ }
24
+ return value;
25
+ }
26
+ export function isBoundedJson(value) {
27
+ const pending = [
28
+ { value, depth: 0 },
29
+ ];
30
+ const ancestors = new Set();
31
+ let nodes = 0;
32
+ while (pending.length > 0) {
33
+ const item = pending.pop();
34
+ if (!item)
35
+ continue;
36
+ nodes += 1;
37
+ if (nodes > MAX_JSON_NODES)
38
+ return false;
39
+ const current = item.value;
40
+ if (item.leave) {
41
+ ancestors.delete(current);
42
+ continue;
43
+ }
44
+ if (current === undefined)
45
+ return false;
46
+ if (current === null || typeof current === "boolean")
47
+ continue;
48
+ if (typeof current === "string") {
49
+ if (current.length > MAX_STRING_LENGTH)
50
+ return false;
51
+ continue;
52
+ }
53
+ if (typeof current === "number") {
54
+ if (!Number.isFinite(current))
55
+ return false;
56
+ continue;
57
+ }
58
+ if (typeof current !== "object" ||
59
+ item.depth >= MAX_JSON_DEPTH ||
60
+ ancestors.has(current))
61
+ return false;
62
+ ancestors.add(current);
63
+ pending.push({ value: current, depth: item.depth, leave: true });
64
+ if (Array.isArray(current)) {
65
+ if (current.length > MAX_ARRAY_LENGTH)
66
+ return false;
67
+ for (const entry of current)
68
+ pending.push({ value: entry, depth: item.depth + 1 });
69
+ continue;
70
+ }
71
+ if (Object.getPrototypeOf(current) !== Object.prototype && Object.getPrototypeOf(current) !== null)
72
+ return false;
73
+ const keys = Object.keys(current);
74
+ if (keys.length > MAX_MAP_ENTRIES ||
75
+ keys.some((key) => key.length > MAX_IDENTIFIER_LENGTH))
76
+ return false;
77
+ for (const key of keys)
78
+ pending.push({ value: current[key], depth: item.depth + 1 });
79
+ }
80
+ return true;
81
+ }
82
+ export function assertBoundedJson(value) {
83
+ if (!isBoundedJson(value))
84
+ throw new Error("value exceeds its JSON bound");
85
+ }
86
+ /** Keep a late-created provider handle reachable when an abort wins the race. */
87
+ export function attachCleanupHandle(error, handle, cleanupError) {
88
+ if ((typeof error !== "object" || error === null) &&
89
+ typeof error !== "function")
90
+ return;
91
+ try {
92
+ Object.assign(error, {
93
+ cleanupHandle: handle,
94
+ ...(cleanupError === undefined ? {} : { cleanupError }),
95
+ });
96
+ }
97
+ catch {
98
+ // A non-extensible provider error cannot carry the handle; the operation
99
+ // still remains rejected rather than pretending cleanup completed.
100
+ }
101
+ }
102
+ export function exactProcessRequestDigest(input, providerName, options) {
103
+ validateExactProcessCreateInput(input, providerName, options);
104
+ assertBoundedJson(input.metadata);
105
+ return canonicalCandidateDigest({
106
+ provider: providerName,
107
+ image: input.image,
108
+ egress: input.egress,
109
+ maxLifetimeMs: input.maxLifetimeMs,
110
+ resources: input.resources,
111
+ metadata: input.metadata,
112
+ idempotencyKey: input.idempotencyKey,
113
+ ...(options.teamId === undefined ? {} : { teamId: options.teamId }),
114
+ ...(input.provisionTimeoutMs === undefined
115
+ ? {}
116
+ : { provisionTimeoutMs: input.provisionTimeoutMs }),
117
+ });
118
+ }
119
+ export function validateExactProcessCreateInput(input, providerName, options) {
120
+ const inputKeys = new Set(Object.keys(input));
121
+ for (const key of [
122
+ "image",
123
+ "egress",
124
+ "maxLifetimeMs",
125
+ "provisionTimeoutMs",
126
+ "resources",
127
+ "metadata",
128
+ "idempotencyKey",
129
+ "signal",
130
+ "providerOptions",
131
+ ])
132
+ inputKeys.delete(key);
133
+ if (inputKeys.size > 0)
134
+ throw new Error("exact process input contains unsupported fields");
135
+ boundedIdentifier(providerName, "Tangle exact process provider");
136
+ if (options.teamId !== undefined) {
137
+ boundedIdentifier(options.teamId, "Tangle exact process team id");
138
+ }
139
+ boundedString(input.image, "exact process image");
140
+ if (!input.image.trim() || !IMMUTABLE_TANGLE_IMAGE.test(input.image)) {
141
+ throw new Error("Tangle exact process image must include a sha256 manifest digest");
142
+ }
143
+ boundedIdentifier(input.idempotencyKey, "exact process idempotencyKey");
144
+ if (!input.metadata || typeof input.metadata !== "object" || Array.isArray(input.metadata)) {
145
+ throw new Error("exact process metadata must be a JSON object");
146
+ }
147
+ assertBoundedJson(input.metadata);
148
+ if (input.providerOptions !== undefined) {
149
+ if (!input.providerOptions || typeof input.providerOptions !== "object" || Array.isArray(input.providerOptions)) {
150
+ throw new Error("Tangle exact process providerOptions must be a JSON object");
151
+ }
152
+ assertBoundedJson(input.providerOptions);
153
+ if (Object.keys(input.providerOptions).length > 0) {
154
+ throw new Error("Tangle exact process providerOptions are not supported");
155
+ }
156
+ }
157
+ if (!input.egress || typeof input.egress !== "object" || Array.isArray(input.egress)) {
158
+ throw new Error("exact process egress policy is required");
159
+ }
160
+ if (input.egress.mode !== "blocked" && input.egress.mode !== "strict") {
161
+ throw new Error("exact process egress mode is unsupported");
162
+ }
163
+ const egressKeys = new Set(Object.keys(input.egress));
164
+ egressKeys.delete("mode");
165
+ if (input.egress.mode === "blocked") {
166
+ if (egressKeys.size > 0)
167
+ throw new Error("blocked exact process egress has unsupported fields");
168
+ }
169
+ else {
170
+ egressKeys.delete("allowDomains");
171
+ if (egressKeys.size > 0)
172
+ throw new Error("strict exact process egress has unsupported fields");
173
+ }
174
+ if (input.egress.mode === "strict") {
175
+ if (!Array.isArray(input.egress.allowDomains)) {
176
+ throw new Error("exact process egress allowDomains must be an array");
177
+ }
178
+ if (input.egress.allowDomains.length > MAX_ARRAY_LENGTH) {
179
+ throw new Error("exact process egress allowDomains has too many entries");
180
+ }
181
+ if (input.egress.allowDomains.length === 0) {
182
+ throw new Error("strict exact process egress requires at least one domain");
183
+ }
184
+ for (const domain of input.egress.allowDomains) {
185
+ boundedIdentifier(domain, "exact process egress domain");
186
+ }
187
+ }
188
+ if (!Number.isSafeInteger(input.maxLifetimeMs) ||
189
+ input.maxLifetimeMs < 1 ||
190
+ input.maxLifetimeMs % 1_000 !== 0) {
191
+ throw new Error("exact process maxLifetimeMs must be a positive safe integer");
192
+ }
193
+ if (input.provisionTimeoutMs !== undefined &&
194
+ (!Number.isSafeInteger(input.provisionTimeoutMs) || input.provisionTimeoutMs < 1)) {
195
+ throw new Error("exact process provisionTimeoutMs must be a positive safe integer");
196
+ }
197
+ if (!input.resources || typeof input.resources !== "object" || Array.isArray(input.resources)) {
198
+ throw new Error("exact process resources are required");
199
+ }
200
+ const resourceKeys = new Set(Object.keys(input.resources));
201
+ for (const key of ["cpu", "memoryMb", "diskMb"])
202
+ resourceKeys.delete(key);
203
+ if (resourceKeys.size > 0)
204
+ throw new Error("exact process resources contain unsupported fields");
205
+ if (!Number.isFinite(input.resources.cpu) || input.resources.cpu <= 0) {
206
+ throw new Error("exact process CPU must be positive and finite");
207
+ }
208
+ for (const [name, value] of [
209
+ ["memoryMb", input.resources.memoryMb],
210
+ ["diskMb", input.resources.diskMb],
211
+ ]) {
212
+ if (!Number.isSafeInteger(value) || value < 1) {
213
+ throw new Error(`exact process ${name} must be a positive safe integer`);
214
+ }
215
+ }
216
+ if (input.resources.diskMb % 1_024 !== 0) {
217
+ throw new Error("exact process diskMb must be a whole number of gibibytes");
218
+ }
219
+ }
220
+ export async function awaitWithSignal(operation, signal) {
221
+ signal?.throwIfAborted();
222
+ if (operation === undefined)
223
+ return undefined;
224
+ if (!signal)
225
+ return await operation;
226
+ let listener;
227
+ try {
228
+ return await Promise.race([
229
+ Promise.resolve(operation),
230
+ new Promise((_, reject) => {
231
+ listener = () => reject(new DOMException("The operation was aborted", "AbortError"));
232
+ signal.addEventListener("abort", listener, { once: true });
233
+ }),
234
+ ]);
235
+ }
236
+ finally {
237
+ if (listener)
238
+ signal.removeEventListener("abort", listener);
239
+ }
240
+ }
@@ -0,0 +1,9 @@
1
+ import type { BackendType, CreateSandboxOptions } from "@tangle-network/sandbox";
2
+ import type { CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
3
+ export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend: BackendType): CreateSandboxOptions;
4
+ /** Reject value-bearing secret maps before any custom mapper can drop them. */
5
+ export declare function assertNoInlineSecretValues(input: CreateAgentEnvironmentInput): void;
6
+ /** A custom mapper must not smuggle a value map into the Sandbox request. */
7
+ export declare function assertMappedSecretNames(options: CreateSandboxOptions): void;
8
+ export declare function assertCreateInputShape(input: CreateAgentEnvironmentInput): void;
9
+ export declare function assertMappedCreateOptions(options: CreateSandboxOptions): void;