@tangle-network/agent-provider-tangle 1.1.6 → 1.1.8
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 +5 -0
- package/dist/tangle-contract-safety.d.ts +3 -0
- package/dist/tangle-contract-safety.js +22 -6
- package/dist/tangle-events.js +5 -59
- package/dist/tangle-prompt.js +7 -2
- package/dist/tangle-result-values.js +5 -1
- package/dist/tangle-workspace-branching.js +8 -844
- package/dist/tangle-workspace-markers.d.ts +27 -0
- package/dist/tangle-workspace-markers.js +277 -0
- package/dist/tangle-workspace-recovery.d.ts +68 -0
- package/dist/tangle-workspace-recovery.js +530 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -102,6 +102,11 @@ Reconstruct an exact session with `environment.session(reference.id, { controlRe
|
|
|
102
102
|
Result, replay, and cancel operations select that exact execution instead of whichever execution most recently changed the shared session.
|
|
103
103
|
Session status with an exact control reference reports a state only when the payload names that execution; a payload bound to a different or unnamed execution reports `unknown`.
|
|
104
104
|
|
|
105
|
+
Stream frames can contain complete tool output and cumulative assistant text.
|
|
106
|
+
The adapter bounds each complete serialized frame to 1 MiB of UTF-8, including keys and JSON escaping.
|
|
107
|
+
It retains accepted content without truncation in the event data and original provider event.
|
|
108
|
+
Identifiers, usage metadata, and structural JSON limits retain their existing bounds.
|
|
109
|
+
|
|
105
110
|
## Two capability documents
|
|
106
111
|
|
|
107
112
|
Capabilities are derived in two stages, and the two stages answer different questions.
|
|
@@ -21,3 +21,6 @@ export declare function validateExactProcessCreateInput(input: CreateAgentExactP
|
|
|
21
21
|
export declare function awaitWithSignal<T>(operation: Promise<T> | T | undefined, signal?: AbortSignal): Promise<T>;
|
|
22
22
|
/** Await an operation and clean its result if abort wins before it resolves. */
|
|
23
23
|
export declare function awaitWithSignalAndCleanup<T>(operation: () => Promise<T> | T, signal: AbortSignal | undefined, cleanup: (value: T) => Promise<void> | void): Promise<T>;
|
|
24
|
+
export declare function cloneJson<T>(value: T): T;
|
|
25
|
+
export declare function safeIdentifier(value: unknown): string | undefined;
|
|
26
|
+
export declare function safeString(value: unknown): string | undefined;
|
|
@@ -11,13 +11,10 @@ export const MAX_JSON_DEPTH = 16;
|
|
|
11
11
|
export const MAX_JSON_NODES = 8_192;
|
|
12
12
|
const IMMUTABLE_TANGLE_IMAGE = /^(?:sha256:[a-f0-9]{64}|\S+@sha256:[a-f0-9]{64})$/i;
|
|
13
13
|
export function boundedIdentifier(value, label) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
value.length > MAX_IDENTIFIER_LENGTH ||
|
|
17
|
-
value.trim() !== value) {
|
|
14
|
+
const identifier = safeIdentifier(value);
|
|
15
|
+
if (identifier === undefined)
|
|
18
16
|
throw new Error(`${label} is invalid`);
|
|
19
|
-
|
|
20
|
-
return value;
|
|
17
|
+
return identifier;
|
|
21
18
|
}
|
|
22
19
|
export function boundedString(value, label) {
|
|
23
20
|
if (typeof value !== "string" || value.length > MAX_STRING_LENGTH) {
|
|
@@ -269,3 +266,22 @@ export async function awaitWithSignalAndCleanup(operation, signal, cleanup) {
|
|
|
269
266
|
signal.removeEventListener("abort", listener);
|
|
270
267
|
}
|
|
271
268
|
}
|
|
269
|
+
export function cloneJson(value) {
|
|
270
|
+
assertBoundedJson(value);
|
|
271
|
+
return structuredClone(value);
|
|
272
|
+
}
|
|
273
|
+
export function safeIdentifier(value) {
|
|
274
|
+
if (typeof value !== "string" ||
|
|
275
|
+
value.length === 0 ||
|
|
276
|
+
value.length > MAX_IDENTIFIER_LENGTH ||
|
|
277
|
+
value.trim() !== value)
|
|
278
|
+
return undefined;
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
export function safeString(value) {
|
|
282
|
+
if (typeof value !== "string" ||
|
|
283
|
+
value.length === 0 ||
|
|
284
|
+
value.length > MAX_STRING_LENGTH)
|
|
285
|
+
return undefined;
|
|
286
|
+
return value;
|
|
287
|
+
}
|
package/dist/tangle-events.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { CanonicalStreamEventSchema, } from "@tangle-network/agent-interface";
|
|
2
|
-
import { assertBoundedJson } from "./tangle-contract-safety.js";
|
|
1
|
+
import { CanonicalStreamEventSchema, boundedEventContentRecordSchema, } from "@tangle-network/agent-interface";
|
|
3
2
|
import { optionalNonEmptyString } from "./tangle-environment-values.js";
|
|
4
3
|
import { tokenUsageFromData } from "./tangle-result-values.js";
|
|
5
4
|
/** The sidecar sends this envelope when an SSE connection is ready. */
|
|
@@ -135,8 +134,7 @@ export function environmentEventFromSandboxEvent(event, expected = {}) {
|
|
|
135
134
|
throw new Error("Tangle Sandbox event contained an invalid event id");
|
|
136
135
|
}
|
|
137
136
|
const data = record.data;
|
|
138
|
-
assertBoundedRecord(
|
|
139
|
-
assertBoundedJson(record);
|
|
137
|
+
assertBoundedRecord(record);
|
|
140
138
|
if (Object.prototype.hasOwnProperty.call(data, "contextTransferReceipt")) {
|
|
141
139
|
throw new Error("Tangle Sandbox emitted an unsolicited context transfer receipt");
|
|
142
140
|
}
|
|
@@ -328,60 +326,8 @@ function detailFromSandboxData(data) {
|
|
|
328
326
|
return detail === undefined ? {} : { detail };
|
|
329
327
|
}
|
|
330
328
|
function assertBoundedRecord(value) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const pending = [
|
|
335
|
-
{ value, depth: 0 },
|
|
336
|
-
];
|
|
337
|
-
const ancestors = new Set();
|
|
338
|
-
let nodes = 0;
|
|
339
|
-
while (pending.length > 0) {
|
|
340
|
-
const item = pending.pop();
|
|
341
|
-
if (!item)
|
|
342
|
-
continue;
|
|
343
|
-
nodes += 1;
|
|
344
|
-
if (nodes > 8_192)
|
|
345
|
-
throw new Error("Tangle Sandbox event data has too many JSON nodes");
|
|
346
|
-
const current = item.value;
|
|
347
|
-
if (item.leave) {
|
|
348
|
-
ancestors.delete(current);
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (current === null || typeof current === "boolean")
|
|
352
|
-
continue;
|
|
353
|
-
if (typeof current === "string" || typeof current === "number") {
|
|
354
|
-
if (typeof current === "string" && current.length > 16_384) {
|
|
355
|
-
throw new Error("Tangle Sandbox event data exceeded its string bound");
|
|
356
|
-
}
|
|
357
|
-
if (typeof current === "number" && !Number.isFinite(current)) {
|
|
358
|
-
throw new Error("Tangle Sandbox event data contained a non-finite number");
|
|
359
|
-
}
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (typeof current !== "object" || item.depth >= 16 || ancestors.has(current)) {
|
|
363
|
-
throw new Error("Tangle Sandbox event data exceeded its JSON bound");
|
|
364
|
-
}
|
|
365
|
-
ancestors.add(current);
|
|
366
|
-
pending.push({ value: current, depth: item.depth, leave: true });
|
|
367
|
-
if (Array.isArray(current)) {
|
|
368
|
-
if (current.length > 1_024) {
|
|
369
|
-
throw new Error("Tangle Sandbox event data has too many array entries");
|
|
370
|
-
}
|
|
371
|
-
for (const entry of current)
|
|
372
|
-
pending.push({ value: entry, depth: item.depth + 1 });
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (Object.getPrototypeOf(current) !== Object.prototype && Object.getPrototypeOf(current) !== null) {
|
|
376
|
-
throw new Error("Tangle Sandbox event data must be plain JSON");
|
|
377
|
-
}
|
|
378
|
-
const keys = Object.keys(current);
|
|
379
|
-
if (keys.length > 256)
|
|
380
|
-
throw new Error("Tangle Sandbox event map is too large");
|
|
381
|
-
for (const key of keys) {
|
|
382
|
-
if (key.length > 512)
|
|
383
|
-
throw new Error("Tangle Sandbox event key is too long");
|
|
384
|
-
pending.push({ value: current[key], depth: item.depth + 1 });
|
|
385
|
-
}
|
|
329
|
+
const parsed = boundedEventContentRecordSchema.safeParse(value);
|
|
330
|
+
if (!parsed.success) {
|
|
331
|
+
throw new Error("Tangle Sandbox event exceeded its JSON content bound", { cause: parsed.error });
|
|
386
332
|
}
|
|
387
333
|
}
|
package/dist/tangle-prompt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agentProfileSchema, AgentExactRunControlRefSchema, AgentTurnInputSchema, ContextTransferReceiptSchema, contextTransferResultMatchesRequest, } from "@tangle-network/agent-interface";
|
|
1
|
+
import { agentProfileSchema, boundedEventContentRecordSchema, AgentExactRunControlRefSchema, AgentTurnInputSchema, ContextTransferReceiptSchema, contextTransferResultMatchesRequest, } from "@tangle-network/agent-interface";
|
|
2
2
|
import { tokenUsageFromData } from "./tangle-result-values.js";
|
|
3
3
|
import { assertBoundedJson, boundedString } from "./tangle-contract-safety.js";
|
|
4
4
|
export function promptFromTurnInput(input) {
|
|
@@ -372,7 +372,12 @@ export function validatedSandboxPromptResult(result) {
|
|
|
372
372
|
// `undefined`. They were absent on the JSON wire and must stay absent in the
|
|
373
373
|
// provider-neutral result before the strict JSON check runs.
|
|
374
374
|
const record = Object.fromEntries(Object.entries(source).filter(([field, value]) => value !== undefined || !SANDBOX_OPTIONAL_RESULT_FIELDS.has(field)));
|
|
375
|
-
|
|
375
|
+
const content = boundedEventContentRecordSchema.safeParse(record);
|
|
376
|
+
if (!content.success) {
|
|
377
|
+
throw new Error("Tangle prompt result exceeded its JSON bound", { cause: content.error });
|
|
378
|
+
}
|
|
379
|
+
// Response text is content; all other fields retain their metadata limits.
|
|
380
|
+
assertBoundedJson(Object.fromEntries(Object.entries(record).filter(([field]) => field !== "response" && field !== "text" && field !== "finalText")));
|
|
376
381
|
if (typeof record.success !== "boolean") {
|
|
377
382
|
throw new Error("Tangle prompt result omitted its success status");
|
|
378
383
|
}
|
|
@@ -19,7 +19,11 @@ export function execResultFromSandboxExecResult(result) {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
export function tokenUsageFromData(data) {
|
|
22
|
-
|
|
22
|
+
// The enclosing event or result has its own content bound.
|
|
23
|
+
// Apply metadata limits only to fields used to compute usage.
|
|
24
|
+
assertBoundedJson(Object.fromEntries(["usage", "tokenUsage", "costUsd", "totalCostUsd"]
|
|
25
|
+
.filter((field) => Object.hasOwn(data, field))
|
|
26
|
+
.map((field) => [field, data[field]])));
|
|
23
27
|
if (data.usage !== undefined &&
|
|
24
28
|
(!data.usage || typeof data.usage !== "object" || Array.isArray(data.usage))) {
|
|
25
29
|
throw new Error("Tangle usage must be an object");
|