@tangle-network/agent-provider-tangle 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -2
- package/dist/tangle-create-options.d.ts +2 -2
- package/dist/tangle-create-options.js +7 -2
- package/dist/tangle-interactive.js +7 -14
- package/dist/tangle-provider.js +44 -20
- package/dist/tangle-types.d.ts +8 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @tangle-network/agent-provider-tangle
|
|
2
2
|
|
|
3
3
|
Wraps `@tangle-network/sandbox` as an `AgentEnvironmentProvider`.
|
|
4
|
-
The peer range is `>=0.34.6 <1.0.0`, and this package is developed and tested against 0.
|
|
4
|
+
The peer range is `>=0.34.6 <1.0.0`, and this package is developed and tested against 0.37.0.
|
|
5
5
|
The floor is 0.34.6 because exact interactive attachment needs the host receiver, and workspace branching needs keyed snapshots, durable restores, inventory recovery, and cleanup.
|
|
6
6
|
The provider fails closed when the configured backend or its catalog entry cannot be read.
|
|
7
7
|
Newer SDKs may also provide `getBackend()` as a lookup over the same catalog.
|
|
@@ -36,7 +36,9 @@ const provider = createTangleProvider({
|
|
|
36
36
|
readyTimeoutMs: 180_000,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
const environment = await provider.create({
|
|
39
|
+
const environment = await provider.create({
|
|
40
|
+
profile: { name: "worker", harness: "codex" },
|
|
41
|
+
});
|
|
40
42
|
for await (const event of environment.stream({ prompt: "run the task" })) {
|
|
41
43
|
// The sandbox is running. No caller-side wait, poll, or retry stands here.
|
|
42
44
|
}
|
|
@@ -50,6 +52,14 @@ A client that offers neither cannot prove readiness, so the sandbox it returned
|
|
|
50
52
|
The adapter reads the platform's answer back rather than trusting the wait to have resolved for the right reason.
|
|
51
53
|
A create call returns only a sandbox that reports `running`: `failed`, `stopped`, and `expired` are refused with the status named, and so is a wait that resolves while the sandbox still reports `provisioning`.
|
|
52
54
|
|
|
55
|
+
The default create mapping selects `create()`'s `backend`, then the provider's `defaultBackend`, then the inline profile's `harness`.
|
|
56
|
+
An inline profile therefore carries its own backend unless the caller records an execution override.
|
|
57
|
+
Profiles without a harness retain the provider's legacy OpenCode fallback.
|
|
58
|
+
A configured `mapCreateInput` owns the Sandbox options and may select another backend or profile.
|
|
59
|
+
The created environment's capabilities use that mapping's backend; provider capabilities describe the configured default.
|
|
60
|
+
`get(id)` reads the running sandbox's backend status.
|
|
61
|
+
If that status is unavailable, the reconstructed environment advertises no backend-specific capabilities.
|
|
62
|
+
|
|
53
63
|
## Per-turn backend options
|
|
54
64
|
|
|
55
65
|
`AgentTurnInput.providerOptions.backend` reaches `PromptOptions.backend` on the Sandbox prompt call.
|
|
@@ -119,6 +129,7 @@ Each deployment flag this adapter reads gates the claims it backs, and no flag i
|
|
|
119
129
|
| `runs.eventReplay` | `streaming.replay`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
120
130
|
| `runs.executionScopedStatus` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
121
131
|
| `interactions.responseDedupe` | `interactions`, `environment.respondToInteraction`, `session.respondToInteraction` |
|
|
132
|
+
| `interactiveAgent.*` | `interactiveAgent`, exact control, and generation-fenced terminal input and resize |
|
|
122
133
|
|
|
123
134
|
Detached dispatch carries the caller's exact reference and refuses a receipt that does not name the execution back, so it needs both `dispatch` flags and a session handle to reach the run through.
|
|
124
135
|
`sessions.continue`, `retainedControl`, and `session.cancelRun` need every flag in the table, because the capability schema refuses a partial retained-control block and each identity rests on its own flag.
|
|
@@ -333,6 +344,11 @@ Every reference states an expiry one detach window past the newest activity the
|
|
|
333
344
|
`close()` reports `closed` only when the socket delivered an exit; Sandbox exposes no terminal delete, so an unproven close reports `unknown` rather than claiming a termination that did not happen.
|
|
334
345
|
An attach whose runtime metadata lacks a name, shell, working directory, geometry, timestamps, running state, or detach window fails closed with `unknown`, because a reference cannot state facts the runtime did not report.
|
|
335
346
|
|
|
347
|
+
The exact interactive adapter validates control before attach, prompt, and stop operations.
|
|
348
|
+
An attached terminal sends input and resize operations through its authenticated terminal connection.
|
|
349
|
+
The sidecar authorizes those mutations, so the provider does not add a second REST request before each operation.
|
|
350
|
+
Terminal close only detaches the caller's socket, so it stays available after control moves to a newer coordinator.
|
|
351
|
+
|
|
336
352
|
Pass `exactProcess: {}` only when the Sandbox deployment supports `agent: false` creates and reports `metadata.runtimeMode: "control"`.
|
|
337
353
|
The optional capability creates an ephemeral sandbox with an authenticated control service but no managed agent workload or agent credentials, explicit resources, exact blocked/domain egress, bounded binary file reads, shell-free launch, and recoverable process output plus terminal reason.
|
|
338
354
|
Set `teamId` inside `exactProcess` to scope create, lookup, and recovery to one team.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BackendType, type CreateSandboxOptions } from "@tangle-network/sandbox";
|
|
2
2
|
import type { CreateAgentEnvironmentInput, WorkspaceRequest } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
-
export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend
|
|
3
|
+
export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend?: BackendType, parsedWorkspace?: WorkspaceRequest): CreateSandboxOptions;
|
|
4
4
|
/** Reject value-bearing secret maps before any custom mapper can drop them. */
|
|
5
5
|
export declare function assertNoInlineSecretValues(input: CreateAgentEnvironmentInput, parsedWorkspace?: WorkspaceRequest): void;
|
|
6
6
|
/** A custom mapper must not smuggle a value map into the Sandbox request. */
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { parseBackendType, } from "@tangle-network/sandbox";
|
|
1
2
|
import { AgentEnvironmentEgressPolicySchema, WorkspaceRequestSchema, workspaceCwdPathForBase, } from "@tangle-network/agent-interface/environment-provider";
|
|
2
3
|
import { assertBoundedJson, boundedIdentifier, boundedString, MAX_ARRAY_LENGTH, MAX_MAP_ENTRIES, } from "./tangle-contract-safety.js";
|
|
3
4
|
import { sandboxResourcesFromResourceRequest } from "./tangle-resources.js";
|
|
4
5
|
export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorkspace) {
|
|
5
6
|
const workspace = assertCreateInputShape(input, parsedWorkspace) ?? {};
|
|
7
|
+
const profile = inlineAgentProfile(input.profile);
|
|
8
|
+
const backend = input.backend === undefined
|
|
9
|
+
? parseBackendType(defaultBackend ?? profile.harness ?? "opencode")
|
|
10
|
+
: input.backend;
|
|
6
11
|
assertNoInlineSecretValues(input, workspace);
|
|
7
12
|
if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
|
|
8
13
|
throw new Error("Tangle create providerOptions are not supported");
|
|
@@ -63,8 +68,8 @@ export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorks
|
|
|
63
68
|
...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }),
|
|
64
69
|
backend: {
|
|
65
70
|
...(base.backend ?? {}),
|
|
66
|
-
type:
|
|
67
|
-
profile
|
|
71
|
+
type: backend,
|
|
72
|
+
profile,
|
|
68
73
|
},
|
|
69
74
|
};
|
|
70
75
|
return mapped;
|
|
@@ -186,7 +186,7 @@ export function createTangleInteractiveAgentRegistry(box, providerName, environm
|
|
|
186
186
|
await terminal.detach().catch(() => undefined);
|
|
187
187
|
throw error;
|
|
188
188
|
}
|
|
189
|
-
return validatedTerminal(terminal, exactRequest.control,
|
|
189
|
+
return validatedTerminal(terminal, exactRequest.control, ref);
|
|
190
190
|
},
|
|
191
191
|
async sendPrompt(command, options) {
|
|
192
192
|
assertOptionKeys(options, ["signal"], "Tangle interactive agent prompt");
|
|
@@ -246,7 +246,7 @@ function isSandboxInteractiveSession(value) {
|
|
|
246
246
|
"stop",
|
|
247
247
|
].every((key) => typeof candidate[key] === "function");
|
|
248
248
|
}
|
|
249
|
-
function validatedTerminal(terminal, control,
|
|
249
|
+
function validatedTerminal(terminal, control, expectedRef) {
|
|
250
250
|
const boundControl = Object.freeze({ ...control });
|
|
251
251
|
return {
|
|
252
252
|
get ref() {
|
|
@@ -261,19 +261,12 @@ function validatedTerminal(terminal, control, validate, expectedRef) {
|
|
|
261
261
|
return TerminalReplayWindowSchema.parse(terminal.cursors);
|
|
262
262
|
},
|
|
263
263
|
control: boundControl,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
async resize(resize, options) {
|
|
269
|
-
await validate(boundControl, options);
|
|
270
|
-
await terminal.resize(resize, options);
|
|
271
|
-
},
|
|
264
|
+
// The authenticated terminal transport authorizes input and resize.
|
|
265
|
+
// A REST preflight adds latency and can race the authoritative operation.
|
|
266
|
+
input: (input, options) => terminal.input(input, options),
|
|
267
|
+
resize: (resize, options) => terminal.resize(resize, options),
|
|
272
268
|
detach: (options) => terminal.detach(options),
|
|
273
|
-
|
|
274
|
-
await validate(boundControl, options);
|
|
275
|
-
return terminal.close(options);
|
|
276
|
-
},
|
|
269
|
+
close: (options) => terminal.close(options),
|
|
277
270
|
events: (options) => terminal.events(options),
|
|
278
271
|
};
|
|
279
272
|
}
|
package/dist/tangle-provider.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentEnvironmentCapabilitiesSchema, createAgentEnvironmentWithIdempotency, } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import { deepFreeze, harnessTypeSchema } from "@tangle-network/agent-interface";
|
|
2
3
|
import { createTangleExactProcessProvider, } from "./exact-process.js";
|
|
3
4
|
import { capabilitiesForClient, defaultTangleSandboxCapabilities, narrowTangleCapabilitiesToBackend, } from "./tangle-capabilities.js";
|
|
4
5
|
import { sandboxInstanceAsEnvironment } from "./tangle-environment.js";
|
|
@@ -23,18 +24,19 @@ export function createTangleProvider(options) {
|
|
|
23
24
|
readyTimeoutMs,
|
|
24
25
|
})
|
|
25
26
|
: undefined;
|
|
26
|
-
const resolveDeclaredCapabilities = async () => {
|
|
27
|
+
const resolveDeclaredCapabilities = async (backendType) => {
|
|
28
|
+
const harness = harnessTypeSchema.safeParse(backendType);
|
|
27
29
|
const configured = options.capabilities
|
|
28
30
|
? typeof options.capabilities === "function"
|
|
29
31
|
? await options.capabilities()
|
|
30
32
|
: options.capabilities
|
|
31
|
-
: defaultTangleSandboxCapabilities();
|
|
33
|
+
: defaultTangleSandboxCapabilities(harness.success ? harness.data : undefined);
|
|
32
34
|
if (!exactProcess && configured.exactProcess) {
|
|
33
35
|
throw new Error("Tangle capabilities cannot advertise exactProcess without exactProcess configuration");
|
|
34
36
|
}
|
|
35
37
|
const backend = configured.interactions === undefined
|
|
36
38
|
? undefined
|
|
37
|
-
: await
|
|
39
|
+
: await resolveBackend(options.client, backendType);
|
|
38
40
|
const narrowed = narrowTangleCapabilitiesToBackend(configured, backend);
|
|
39
41
|
return exactProcess
|
|
40
42
|
? {
|
|
@@ -46,7 +48,7 @@ export function createTangleProvider(options) {
|
|
|
46
48
|
// Provider-boundary document: client-stage facts only. It also validates
|
|
47
49
|
// the configured document, so create() and get() call it before any effect.
|
|
48
50
|
const narrowedProviderCapabilities = (declared) => AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForClient(declared, options.client, confidentialVerifierOption(options.confidentialAttestationVerifier)));
|
|
49
|
-
const resolveCapabilities = async () => narrowedProviderCapabilities(await resolveDeclaredCapabilities());
|
|
51
|
+
const resolveCapabilities = async () => narrowedProviderCapabilities(await resolveDeclaredCapabilities(options.defaultBackend));
|
|
50
52
|
const createRecords = new Map();
|
|
51
53
|
const createEnvironment = async (input) => {
|
|
52
54
|
const parsedWorkspace = assertCreateInputShape(input);
|
|
@@ -55,19 +57,19 @@ export function createTangleProvider(options) {
|
|
|
55
57
|
if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
|
|
56
58
|
throw new Error("Tangle create providerOptions are not supported");
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
narrowedProviderCapabilities(declaredCapabilities);
|
|
63
|
-
const createOptions = options.mapCreateInput?.(input) ??
|
|
64
|
-
sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode", parsedWorkspace);
|
|
65
|
-
assertMappedCreateOptions(createOptions);
|
|
60
|
+
const mappedOptions = options.mapCreateInput?.(input) ??
|
|
61
|
+
sandboxOptionsFromCreateInput(input, options.defaultBackend, parsedWorkspace);
|
|
62
|
+
assertMappedCreateOptions(mappedOptions);
|
|
63
|
+
const createOptions = deepFreeze(structuredClone(mappedOptions));
|
|
66
64
|
if (input.idempotencyKey !== undefined &&
|
|
67
65
|
createOptions.idempotencyKey !== input.idempotencyKey) {
|
|
68
66
|
throw new Error("Tangle mapped create options must preserve input idempotencyKey");
|
|
69
67
|
}
|
|
70
68
|
assertMappedSecretNames(createOptions);
|
|
69
|
+
// Backend selection belongs to the create projection. The provider-wide
|
|
70
|
+
// default cannot describe a sandbox that this projection routes elsewhere.
|
|
71
|
+
const declaredCapabilities = await resolveDeclaredCapabilities(createOptions.backend?.type);
|
|
72
|
+
narrowedProviderCapabilities(declaredCapabilities);
|
|
71
73
|
input.signal?.throwIfAborted();
|
|
72
74
|
const createPromise = options.client.create(createOptions, input.signal ? { signal: input.signal } : undefined);
|
|
73
75
|
let box;
|
|
@@ -155,21 +157,43 @@ export function createTangleProvider(options) {
|
|
|
155
157
|
...(workspaceBranching === undefined ? {} : { workspaceBranching }),
|
|
156
158
|
capabilities: resolveCapabilities,
|
|
157
159
|
...(options.validateProfile ? { validateProfile: options.validateProfile } : {}),
|
|
158
|
-
create(input) {
|
|
159
|
-
|
|
160
|
+
async create(input) {
|
|
161
|
+
const { signal, ...material } = input;
|
|
162
|
+
signal?.throwIfAborted();
|
|
163
|
+
assertCreateInputShape(input);
|
|
164
|
+
assertNoInlineSecretValues(input);
|
|
165
|
+
for (const value of Object.values(material)) {
|
|
166
|
+
if (value !== undefined)
|
|
167
|
+
assertBoundedJson(value);
|
|
168
|
+
}
|
|
169
|
+
const acceptedInput = Object.freeze({
|
|
170
|
+
...deepFreeze(structuredClone(material)),
|
|
171
|
+
...(signal === undefined ? {} : { signal }),
|
|
172
|
+
});
|
|
173
|
+
return createAgentEnvironmentWithIdempotency(createRecords, acceptedInput, createEnvironment);
|
|
160
174
|
},
|
|
161
175
|
...(options.client.get
|
|
162
176
|
? {
|
|
163
177
|
async get(id, operation) {
|
|
164
178
|
assertProviderOperationOptions(operation, "Tangle get");
|
|
165
179
|
boundedIdentifier(id, "Tangle environment id");
|
|
166
|
-
|
|
167
|
-
narrowedProviderCapabilities(declaredCapabilities);
|
|
180
|
+
await resolveCapabilities();
|
|
168
181
|
operation?.signal?.throwIfAborted();
|
|
169
182
|
const box = await awaitWithSignal(options.client.get?.(id, operation), operation?.signal);
|
|
170
183
|
operation?.signal?.throwIfAborted();
|
|
171
184
|
if (!box || boundedIdentifier(box.id, "Tangle environment id") !== id)
|
|
172
185
|
return null;
|
|
186
|
+
let backendType;
|
|
187
|
+
if (box.status === "running" && box.backend) {
|
|
188
|
+
try {
|
|
189
|
+
const status = await awaitWithSignal(box.backend.status(), operation?.signal);
|
|
190
|
+
backendType = boundedIdentifier(status.type, "Tangle current backend");
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
operation?.signal?.throwIfAborted();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const declaredCapabilities = await resolveDeclaredCapabilities(backendType);
|
|
173
197
|
return await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, operation?.signal ? { signal: operation.signal } : undefined, confidentialVerifierOption(options.confidentialAttestationVerifier));
|
|
174
198
|
},
|
|
175
199
|
}
|
|
@@ -246,16 +270,16 @@ export function createTangleProvider(options) {
|
|
|
246
270
|
: {}),
|
|
247
271
|
};
|
|
248
272
|
}
|
|
249
|
-
async function
|
|
250
|
-
if (
|
|
273
|
+
async function resolveBackend(client, backendType) {
|
|
274
|
+
if (backendType === undefined)
|
|
251
275
|
return undefined;
|
|
252
276
|
try {
|
|
253
277
|
if (client.getBackend)
|
|
254
|
-
return await client.getBackend(
|
|
278
|
+
return await client.getBackend(backendType);
|
|
255
279
|
if (!client.listBackends)
|
|
256
280
|
return undefined;
|
|
257
281
|
const catalog = await client.listBackends();
|
|
258
|
-
return catalog.backends.find((backend) => backend.type ===
|
|
282
|
+
return catalog.backends.find((backend) => backend.type === backendType);
|
|
259
283
|
}
|
|
260
284
|
catch {
|
|
261
285
|
return undefined;
|
package/dist/tangle-types.d.ts
CHANGED
|
@@ -391,6 +391,12 @@ export interface SandboxInstanceLike {
|
|
|
391
391
|
name?: string;
|
|
392
392
|
status?: unknown;
|
|
393
393
|
metadata?: Record<string, unknown>;
|
|
394
|
+
/** Current backend reported by this sandbox's runtime. */
|
|
395
|
+
backend?: {
|
|
396
|
+
status(): Promise<{
|
|
397
|
+
type: string;
|
|
398
|
+
}>;
|
|
399
|
+
};
|
|
394
400
|
/** Network location of the sandbox runtime, without its bearer. */
|
|
395
401
|
connection?: SandboxConnectionLike;
|
|
396
402
|
/** When the platform retires this sandbox, when it set a lifetime. */
|
|
@@ -578,9 +584,11 @@ export interface SandboxInteractionCommandResultLike {
|
|
|
578
584
|
export interface TangleProviderOptions {
|
|
579
585
|
client: SandboxClientLike;
|
|
580
586
|
name?: string;
|
|
587
|
+
/** Overrides an inline profile's harness when create() names no backend. */
|
|
581
588
|
defaultBackend?: BackendType;
|
|
582
589
|
capabilities?: AgentEnvironmentCapabilities | (() => AgentEnvironmentCapabilities | Promise<AgentEnvironmentCapabilities>);
|
|
583
590
|
validateProfile?: AgentEnvironmentProvider["validateProfile"];
|
|
591
|
+
/** Replaces the default mapping, including its backend and profile selection. */
|
|
584
592
|
mapCreateInput?: (input: CreateAgentEnvironmentInput) => CreateSandboxOptions;
|
|
585
593
|
exactProcess?: TangleExactProcessOptions;
|
|
586
594
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"LICENSE"
|
|
92
92
|
],
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@tangle-network/agent-interface": "^2.3.
|
|
94
|
+
"@tangle-network/agent-interface": "^2.3.1"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
97
|
"@tangle-network/sandbox": ">=0.34.6 <1.0.0"
|
|
@@ -102,13 +102,13 @@
|
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
-
"@tangle-network/agent-eval": "0.173.
|
|
106
|
-
"@tangle-network/agent-runtime": "0.
|
|
107
|
-
"@tangle-network/sandbox": "0.
|
|
108
|
-
"@types/node": "26.4.
|
|
105
|
+
"@tangle-network/agent-eval": "0.173.1",
|
|
106
|
+
"@tangle-network/agent-runtime": "0.192.2",
|
|
107
|
+
"@tangle-network/sandbox": "0.37.0",
|
|
108
|
+
"@types/node": "26.4.1",
|
|
109
109
|
"typescript": "7.0.2",
|
|
110
110
|
"vitest": "4.1.11",
|
|
111
|
-
"@tangle-network/agent-provider-testkit": "0.9.
|
|
111
|
+
"@tangle-network/agent-provider-testkit": "0.9.2"
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
114
|
"build": "tsc -p tsconfig.json",
|