@tangle-network/agent-interface 1.4.0 → 1.5.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.
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Evidence that one captured artifact is the canonical bytes of its material.
4
+ *
5
+ * The artifact's hash must equal the digest the material canonicalizes to, and
6
+ * the artifact must actually carry bytes. Both are integrity claims: a record
7
+ * whose artifact hash names something other than its material is evidence for
8
+ * a different value, and a zero-length artifact is a claim with nothing behind
9
+ * it. `label` names the evidence in the refusal so a reader knows which record
10
+ * failed.
11
+ */
12
+ export declare function agentCandidateEvidenceSchema<TKind extends string, TMaterial>(kind: TKind, material: z.ZodType<TMaterial>, label: string): z.ZodObject<{
13
+ kind: z.ZodLiteral<TKind>;
14
+ digest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
15
+ material: z.ZodType<TMaterial, unknown, z.core.$ZodTypeInternals<TMaterial, unknown>>;
16
+ artifact: z.ZodUnion<readonly [z.ZodObject<{
17
+ locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
18
+ kind: z.ZodLiteral<"s3">;
19
+ bucket: z.ZodString;
20
+ key: z.ZodString;
21
+ region: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strict>, z.ZodObject<{
23
+ kind: z.ZodLiteral<"ipfs">;
24
+ cid: z.ZodString;
25
+ path: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strict>], "kind">;
27
+ sha256: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
28
+ byteLength: z.ZodNumber;
29
+ }, z.core.$strict>, z.ZodObject<{
30
+ encoding: z.ZodLiteral<"base64">;
31
+ content: z.ZodString;
32
+ sha256: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
33
+ byteLength: z.ZodNumber;
34
+ }, z.core.$strict>]>;
35
+ }, z.core.$strict>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { agentCandidateCapturedArtifactSchema } from "./agent-candidate-artifact-schema.js";
3
+ import { sha256DigestSchema } from "./agent-candidate-schema-common.js";
4
+ /**
5
+ * Evidence that one captured artifact is the canonical bytes of its material.
6
+ *
7
+ * The artifact's hash must equal the digest the material canonicalizes to, and
8
+ * the artifact must actually carry bytes. Both are integrity claims: a record
9
+ * whose artifact hash names something other than its material is evidence for
10
+ * a different value, and a zero-length artifact is a claim with nothing behind
11
+ * it. `label` names the evidence in the refusal so a reader knows which record
12
+ * failed.
13
+ */
14
+ export function agentCandidateEvidenceSchema(kind, material, label) {
15
+ return z
16
+ .object({
17
+ kind: z.literal(kind),
18
+ digest: sha256DigestSchema,
19
+ material,
20
+ artifact: agentCandidateCapturedArtifactSchema,
21
+ })
22
+ .strict()
23
+ .superRefine((evidence, ctx) => {
24
+ if (evidence.artifact.sha256 !== evidence.digest) {
25
+ ctx.addIssue({
26
+ code: "custom",
27
+ path: ["artifact", "sha256"],
28
+ message: `${label} artifact hash must equal its canonical material digest`,
29
+ });
30
+ }
31
+ if (evidence.artifact.byteLength === 0) {
32
+ ctx.addIssue({
33
+ code: "custom",
34
+ path: ["artifact", "byteLength"],
35
+ message: `${label} artifact must contain canonical material bytes`,
36
+ });
37
+ }
38
+ });
39
+ }
@@ -1,3 +1,4 @@
1
+ import { agentCandidateEvidenceSchema } from "./agent-candidate-evidence-schema.js";
1
2
  import { z } from "zod";
2
3
  import { agentCandidateArtifactRefSchema, agentCandidateCapturedArtifactSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, } from "./agent-candidate-artifact-schema.js";
3
4
  import { agentCandidateContainerSchema, agentCandidateInstructionDeliverySchema, agentCandidateWorkingDirectorySchema, } from "./agent-candidate-code-schema.js";
@@ -448,30 +449,7 @@ export const agentCandidateExecutionPlanMaterialSchema = z
448
449
  }
449
450
  });
450
451
  function planEvidenceSchema(kind, material) {
451
- return z
452
- .object({
453
- kind: z.literal(kind),
454
- digest: sha256DigestSchema,
455
- material,
456
- artifact: agentCandidateCapturedArtifactSchema,
457
- })
458
- .strict()
459
- .superRefine((evidence, ctx) => {
460
- if (evidence.artifact.sha256 !== evidence.digest) {
461
- ctx.addIssue({
462
- code: "custom",
463
- path: ["artifact", "sha256"],
464
- message: "plan artifact hash must equal its canonical material digest",
465
- });
466
- }
467
- if (evidence.artifact.byteLength === 0) {
468
- ctx.addIssue({
469
- code: "custom",
470
- path: ["artifact", "byteLength"],
471
- message: "plan artifact must contain canonical material bytes",
472
- });
473
- }
474
- });
452
+ return agentCandidateEvidenceSchema(kind, material, "plan");
475
453
  }
476
454
  export const agentCandidateProfilePlanEvidenceSchema = planEvidenceSchema("agent-profile-workspace-plan", agentCandidateProfilePlanMaterialSchema);
477
455
  export const agentCandidateProfileActivationSchema = createAgentProfileActivationEvidenceSchema(agentCandidateProfilePlanEvidenceSchema)
@@ -1,5 +1,6 @@
1
+ import { agentCandidateEvidenceSchema } from "./agent-candidate-evidence-schema.js";
1
2
  import { z } from "zod";
2
- import { agentCandidateArtifactRefSchema, agentCandidateCapturedArtifactSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, } from "./agent-candidate-artifact-schema.js";
3
+ import { agentCandidateArtifactRefSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, } from "./agent-candidate-artifact-schema.js";
3
4
  import { agentCandidateBenchmarkGraderIdentitySchema, agentCandidateResolvedModelSchema, } from "./agent-candidate-execution-plan-schema.js";
4
5
  import { agentCandidateMediaTypeSchema, gitObjectSchema, isCanonicalJsonValue, sameGitObjectFormat, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
5
6
  const safeCountSchema = z
@@ -174,7 +175,7 @@ function refineModelSettlementMaterial(material, ctx) {
174
175
  });
175
176
  }
176
177
  }
177
- export const agentCandidateModelSettlementEvidenceSchema = evidenceSchema("agent-candidate-model-settlement", agentCandidateModelSettlementMaterialSchema, "model settlement");
178
+ export const agentCandidateModelSettlementEvidenceSchema = agentCandidateEvidenceSchema("agent-candidate-model-settlement", agentCandidateModelSettlementMaterialSchema, "model settlement");
178
179
  export const agentCandidateRepositoryStateSchema = z
179
180
  .object({
180
181
  identity: z.string().min(1),
@@ -278,7 +279,7 @@ export const agentCandidateTaskOutcomeMaterialSchema = z
278
279
  });
279
280
  }
280
281
  });
281
- export const agentCandidateTaskOutcomeEvidenceSchema = evidenceSchema("agent-candidate-task-outcome", agentCandidateTaskOutcomeMaterialSchema, "task outcome");
282
+ export const agentCandidateTaskOutcomeEvidenceSchema = agentCandidateEvidenceSchema("agent-candidate-task-outcome", agentCandidateTaskOutcomeMaterialSchema, "task outcome");
282
283
  export const agentCandidateBenchmarkDimensionSchema = z
283
284
  .object({
284
285
  name: normalizedDimensionNameSchema,
@@ -346,7 +347,7 @@ export const agentCandidateBenchmarkResultMaterialSchema = z
346
347
  });
347
348
  }
348
349
  });
349
- export const agentCandidateBenchmarkResultEvidenceSchema = evidenceSchema("agent-candidate-benchmark-result", agentCandidateBenchmarkResultMaterialSchema, "benchmark result");
350
+ export const agentCandidateBenchmarkResultEvidenceSchema = agentCandidateEvidenceSchema("agent-candidate-benchmark-result", agentCandidateBenchmarkResultMaterialSchema, "benchmark result");
350
351
  function sameFixedSpend(left, right) {
351
352
  return (left.inputTokens === right.inputTokens &&
352
353
  left.outputTokens === right.outputTokens &&
@@ -356,29 +357,3 @@ function sameFixedSpend(left, right) {
356
357
  left.costUsdNanos === right.costUsdNanos &&
357
358
  left.costProvenance === right.costProvenance);
358
359
  }
359
- function evidenceSchema(kind, material, label) {
360
- return z
361
- .object({
362
- kind: z.literal(kind),
363
- digest: sha256DigestSchema,
364
- material,
365
- artifact: agentCandidateCapturedArtifactSchema,
366
- })
367
- .strict()
368
- .superRefine((evidence, ctx) => {
369
- if (evidence.artifact.sha256 !== evidence.digest) {
370
- ctx.addIssue({
371
- code: "custom",
372
- path: ["artifact", "sha256"],
373
- message: `${label} artifact hash must equal its canonical material digest`,
374
- });
375
- }
376
- if (evidence.artifact.byteLength === 0) {
377
- ctx.addIssue({
378
- code: "custom",
379
- path: ["artifact", "byteLength"],
380
- message: `${label} artifact must contain canonical material bytes`,
381
- });
382
- }
383
- });
384
- }
@@ -1,4 +1,5 @@
1
1
  import { canonicalAgentProfileValue } from "./agent-profile-canonical.js";
2
+ import { deepFreeze } from "./deep-freeze.js";
2
3
  import { agentProfileSchema } from "./profile-schema.js";
3
4
  /**
4
5
  * Detach, validate, and recursively freeze one AgentProfile at an intake boundary.
@@ -12,13 +13,3 @@ export function snapshotAgentProfile(value) {
12
13
  canonicalAgentProfileValue(parsed);
13
14
  return deepFreeze(parsed);
14
15
  }
15
- function deepFreeze(value, seen = new Set()) {
16
- if (value === null || typeof value !== "object" || seen.has(value)) {
17
- return value;
18
- }
19
- seen.add(value);
20
- for (const child of Object.values(value)) {
21
- deepFreeze(child, seen);
22
- }
23
- return Object.freeze(value);
24
- }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { deepFreeze } from "./deep-freeze.js";
2
3
  import { canonicalCandidateDigest, isSafeRelativePath, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
4
  const MAX_INLINE_CONTEXT_BYTES = 65_536;
4
5
  const MAX_TOTAL_INLINE_CONTEXT_BYTES = 131_072;
@@ -203,15 +204,6 @@ export const certifiedContextSchema = z
203
204
  });
204
205
  }
205
206
  });
206
- function deepFreeze(value) {
207
- if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
208
- for (const child of Object.values(value)) {
209
- deepFreeze(child);
210
- }
211
- Object.freeze(value);
212
- }
213
- return value;
214
- }
215
207
  const _certifiedContextDeliverySchemaMatches = true;
216
208
  const _certifiedContextProvenanceSchemaMatches = true;
217
209
  const _certifiedContextEntrySchemaMatches = true;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Make a validated value immutable before it is handed out.
3
+ *
4
+ * A profile snapshot, a certified context, and a capability document are each
5
+ * evidence: something already checked, that a caller then reads and acts on.
6
+ * If the copy the caller holds can be written to, the evidence can be changed
7
+ * after the check that made it trustworthy, and a mutated capability flag
8
+ * describes a surface the environment does not have.
9
+ *
10
+ * Values already visited are skipped, so a value that refers back to itself is
11
+ * frozen once instead of recursing until the stack runs out.
12
+ */
13
+ export declare function deepFreeze<T>(value: T, seen?: Set<object>): T;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Make a validated value immutable before it is handed out.
3
+ *
4
+ * A profile snapshot, a certified context, and a capability document are each
5
+ * evidence: something already checked, that a caller then reads and acts on.
6
+ * If the copy the caller holds can be written to, the evidence can be changed
7
+ * after the check that made it trustworthy, and a mutated capability flag
8
+ * describes a surface the environment does not have.
9
+ *
10
+ * Values already visited are skipped, so a value that refers back to itself is
11
+ * frozen once instead of recursing until the stack runs out.
12
+ */
13
+ export function deepFreeze(value, seen = new Set()) {
14
+ if (value === null || typeof value !== "object" || seen.has(value)) {
15
+ return value;
16
+ }
17
+ seen.add(value);
18
+ for (const child of Object.values(value)) {
19
+ deepFreeze(child, seen);
20
+ }
21
+ return Object.freeze(value);
22
+ }
@@ -0,0 +1,48 @@
1
+ import type { ExecResult } from "./environment-requests.js";
2
+ import type { AgentEnvironment, AgentEnvironmentCapabilities, AgentEnvironmentEvent, AgentTurnInput } from "./environment-runtime.js";
3
+ /**
4
+ * Normalize the untyped return of a sandbox SDK's command call into
5
+ * {@link ExecResult}.
6
+ *
7
+ * Sandbox SDKs name the same three facts differently: an exit status under
8
+ * `exitCode` or `code`, captured output under `stdout` or `output`, and
9
+ * captured errors under `stderr` or `error`. Reading both names in one place
10
+ * keeps every adapter's exec surface answering with the same shape.
11
+ *
12
+ * A result carrying no exit status is refused. Exit zero is the one value that
13
+ * means the command succeeded, so reading an absent status as zero reports a
14
+ * command nobody watched as a command that worked, and the turn that ran it
15
+ * completes on evidence that was never measured. `source` names the SDK call
16
+ * whose answer could not be read, because that is where the fix belongs.
17
+ */
18
+ export declare function execResultFromUnknown(value: unknown, source: string): ExecResult;
19
+ export interface CommandTurnOptions {
20
+ /** The turn the caller asked this environment to run. */
21
+ input: AgentTurnInput;
22
+ /** The environment whose `exec` runs the command. */
23
+ environment: AgentEnvironment;
24
+ /** The adapter name the refusal names when no command can be resolved. */
25
+ providerLabel: string;
26
+ /** The caller's explicit command for this turn, chosen before any default. */
27
+ turnCommand?: (input: AgentTurnInput, environment: AgentEnvironment) => string | Promise<string>;
28
+ }
29
+ /**
30
+ * Run one turn as a single command in an environment whose only surface is a
31
+ * workspace, and emit the turn's events.
32
+ *
33
+ * The command comes from the caller's `turnCommand`, then
34
+ * `providerOptions.command` or `providerOptions.agentCommand`, then the
35
+ * prompt. An environment that reaches none of them cannot run the turn and
36
+ * the turn is refused rather than executing an empty command.
37
+ */
38
+ export declare function commandTurnEvents(options: CommandTurnOptions): AsyncIterable<AgentEnvironmentEvent>;
39
+ /**
40
+ * The capability document of a provider whose only surface is a workspace.
41
+ *
42
+ * Such an adapter runs commands and moves files in a sandbox. It owns no
43
+ * agent profile, serves no live or replayable stream, keeps no provider
44
+ * session, and branches no environment, so every surface outside `workspace`
45
+ * and `placement` reads false. The caller states its own workspace facts
46
+ * because they are the only ones that differ between these adapters.
47
+ */
48
+ export declare function execOnlyEnvironmentCapabilities(workspace: AgentEnvironmentCapabilities["workspace"]): AgentEnvironmentCapabilities;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Read one non-empty string from an untyped provider-options bag.
3
+ *
4
+ * An absent key and a present key holding a non-string or an empty string are
5
+ * the same answer: the caller named nothing.
6
+ */
7
+ function stringOption(value) {
8
+ return typeof value === "string" && value.length > 0 ? value : undefined;
9
+ }
10
+ function number(value) {
11
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12
+ }
13
+ /**
14
+ * Normalize the untyped return of a sandbox SDK's command call into
15
+ * {@link ExecResult}.
16
+ *
17
+ * Sandbox SDKs name the same three facts differently: an exit status under
18
+ * `exitCode` or `code`, captured output under `stdout` or `output`, and
19
+ * captured errors under `stderr` or `error`. Reading both names in one place
20
+ * keeps every adapter's exec surface answering with the same shape.
21
+ *
22
+ * A result carrying no exit status is refused. Exit zero is the one value that
23
+ * means the command succeeded, so reading an absent status as zero reports a
24
+ * command nobody watched as a command that worked, and the turn that ran it
25
+ * completes on evidence that was never measured. `source` names the SDK call
26
+ * whose answer could not be read, because that is where the fix belongs.
27
+ */
28
+ export function execResultFromUnknown(value, source) {
29
+ const record = value && typeof value === "object" ? value : {};
30
+ const exitCode = number(record.exitCode) ?? number(record.code);
31
+ if (exitCode === undefined) {
32
+ throw new Error(`${source} returned no exit status: a command result must carry a finite exitCode or code`);
33
+ }
34
+ return {
35
+ exitCode,
36
+ stdout: typeof record.stdout === "string"
37
+ ? record.stdout
38
+ : typeof record.output === "string"
39
+ ? record.output
40
+ : "",
41
+ stderr: typeof record.stderr === "string"
42
+ ? record.stderr
43
+ : typeof record.error === "string"
44
+ ? record.error
45
+ : "",
46
+ };
47
+ }
48
+ /**
49
+ * Run one turn as a single command in an environment whose only surface is a
50
+ * workspace, and emit the turn's events.
51
+ *
52
+ * The command comes from the caller's `turnCommand`, then
53
+ * `providerOptions.command` or `providerOptions.agentCommand`, then the
54
+ * prompt. An environment that reaches none of them cannot run the turn and
55
+ * the turn is refused rather than executing an empty command.
56
+ */
57
+ export async function* commandTurnEvents(options) {
58
+ const { input, environment, providerLabel } = options;
59
+ const command = (await options.turnCommand?.(input, environment)) ??
60
+ stringOption(input.providerOptions?.command) ??
61
+ stringOption(input.providerOptions?.agentCommand) ??
62
+ input.prompt;
63
+ if (!command) {
64
+ throw new Error(`${providerLabel} provider requires turnCommand, providerOptions.command, or prompt`);
65
+ }
66
+ const result = await environment.exec?.(command, {
67
+ cwd: stringOption(input.providerOptions?.cwd),
68
+ timeoutMs: input.timeoutMs,
69
+ signal: input.signal,
70
+ });
71
+ const text = result?.stdout ?? "";
72
+ yield { type: "message.part.updated", data: { delta: text } };
73
+ yield {
74
+ type: "result",
75
+ data: {
76
+ finalText: text,
77
+ status: result?.exitCode === 0 ? "completed" : "failed",
78
+ exitCode: result?.exitCode ?? 1,
79
+ stderr: result?.stderr ?? "",
80
+ },
81
+ };
82
+ }
83
+ /**
84
+ * The capability document of a provider whose only surface is a workspace.
85
+ *
86
+ * Such an adapter runs commands and moves files in a sandbox. It owns no
87
+ * agent profile, serves no live or replayable stream, keeps no provider
88
+ * session, and branches no environment, so every surface outside `workspace`
89
+ * and `placement` reads false. The caller states its own workspace facts
90
+ * because they are the only ones that differ between these adapters.
91
+ */
92
+ export function execOnlyEnvironmentCapabilities(workspace) {
93
+ return {
94
+ profile: {
95
+ namedProfiles: false,
96
+ systemPrompt: { replace: false, append: false },
97
+ instructions: false,
98
+ tools: false,
99
+ permissions: false,
100
+ mcp: false,
101
+ subagents: false,
102
+ resources: {
103
+ files: true,
104
+ instructions: false,
105
+ tools: false,
106
+ skills: false,
107
+ agents: false,
108
+ commands: false,
109
+ },
110
+ hooks: false,
111
+ modes: false,
112
+ runtimeUpdate: false,
113
+ validation: false,
114
+ },
115
+ streaming: {
116
+ live: false,
117
+ replay: false,
118
+ detach: false,
119
+ turnIdempotency: false,
120
+ },
121
+ sessions: { continue: false, list: false, messages: false },
122
+ workspace: { ...workspace },
123
+ branching: { checkpoint: false, fork: false },
124
+ placement: true,
125
+ usage: false,
126
+ confidential: false,
127
+ };
128
+ }
@@ -135,8 +135,8 @@ export declare const EnvironmentLifecycleSchema: z.ZodObject<{
135
135
  resumable: z.ZodBoolean;
136
136
  mode: z.ZodOptional<z.ZodEnum<{
137
137
  none: "none";
138
- native: "native";
139
138
  replayed: "replayed";
139
+ native: "native";
140
140
  }>>;
141
141
  }, z.core.$strict>>;
142
142
  persistence: z.ZodOptional<z.ZodObject<{
@@ -460,8 +460,8 @@ export declare const AgentEnvironmentObservationSchema: z.ZodObject<{
460
460
  resumable: z.ZodBoolean;
461
461
  mode: z.ZodOptional<z.ZodEnum<{
462
462
  none: "none";
463
- native: "native";
464
463
  replayed: "replayed";
464
+ native: "native";
465
465
  }>>;
466
466
  }, z.core.$strict>>;
467
467
  persistence: z.ZodOptional<z.ZodObject<{
@@ -508,8 +508,8 @@ export declare const AgentEnvironmentObservationSchema: z.ZodObject<{
508
508
  resumable: z.ZodBoolean;
509
509
  mode: z.ZodOptional<z.ZodEnum<{
510
510
  none: "none";
511
- native: "native";
512
511
  replayed: "replayed";
512
+ native: "native";
513
513
  }>>;
514
514
  }, z.core.$strict>>;
515
515
  persistence: z.ZodOptional<z.ZodObject<{
@@ -4,3 +4,4 @@ export * from "./environment-interactive.js";
4
4
  export * from "./environment-observation.js";
5
5
  export * from "./environment-terminal.js";
6
6
  export * from "./environment-runtime.js";
7
+ export * from "./environment-command-turn.js";
@@ -4,3 +4,4 @@ export * from "./environment-interactive.js";
4
4
  export * from "./environment-observation.js";
5
5
  export * from "./environment-terminal.js";
6
6
  export * from "./environment-runtime.js";
7
+ export * from "./environment-command-turn.js";
package/dist/index.d.ts CHANGED
@@ -33,6 +33,7 @@ export * from "./agent-profile-materialization.js";
33
33
  export * from "./agent-execution-preparation.js";
34
34
  export * from "./agent-workspace-lease.js";
35
35
  export * from "./certified-context.js";
36
+ export * from "./deep-freeze.js";
36
37
  export * from "./profile-diff.js";
37
38
  export * from "./harness.js";
38
39
  export * from "./harness-capabilities.js";
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ export * from "./agent-profile-materialization.js";
31
31
  export * from "./agent-execution-preparation.js";
32
32
  export * from "./agent-workspace-lease.js";
33
33
  export * from "./certified-context.js";
34
+ export * from "./deep-freeze.js";
34
35
  export * from "./profile-diff.js";
35
36
  export * from "./harness.js";
36
37
  export * from "./harness-capabilities.js";
@@ -33,6 +33,15 @@ export declare const AgentExactRunControlRefSchema: z.ZodObject<{
33
33
  executionId: z.ZodString;
34
34
  requestDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
35
35
  }, z.core.$strict>;
36
+ /**
37
+ * Whether two references name the same run.
38
+ *
39
+ * Comparison is over the reference's canonical form rather than a list of
40
+ * field names, so a reference that gains a field is compared on it without a
41
+ * caller having to remember: two runs that differ only in a field nobody
42
+ * enumerated must not read as one run.
43
+ */
44
+ export declare function sameAgentRunControlRef(left: AgentRunControlRef, right: AgentRunControlRef): boolean;
36
45
  export type AgentRunCancellationEffect = "cancel_requested" | "cancelled" | "not_live" | "unknown";
37
46
  export interface AgentRunCancellationRequestMaterial {
38
47
  operationId: string;
@@ -18,6 +18,17 @@ export const AgentExactRunControlRefSchema = AgentRunControlRefSchema.extend({
18
18
  executionId: stableIdSchema,
19
19
  requestDigest: sha256DigestSchema,
20
20
  });
21
+ /**
22
+ * Whether two references name the same run.
23
+ *
24
+ * Comparison is over the reference's canonical form rather than a list of
25
+ * field names, so a reference that gains a field is compared on it without a
26
+ * caller having to remember: two runs that differ only in a field nobody
27
+ * enumerated must not read as one run.
28
+ */
29
+ export function sameAgentRunControlRef(left, right) {
30
+ return canonicalCandidateDigest(left) === canonicalCandidateDigest(right);
31
+ }
21
32
  export function agentRunCancellationRequestDigest(request) {
22
33
  const parsed = AgentRunCancellationRequestMaterialSchema.parse(request);
23
34
  return canonicalCandidateDigest({
@@ -111,8 +122,7 @@ export function agentRunCancellationAcknowledgementMatchesRequest(request, ackno
111
122
  return false;
112
123
  return (exactAcknowledgement.data.operationId === exactRequest.data.operationId &&
113
124
  exactAcknowledgement.data.requestDigest === exactRequest.data.requestDigest &&
114
- canonicalCandidateDigest(exactAcknowledgement.data.run) ===
115
- canonicalCandidateDigest(exactRequest.data.run));
125
+ sameAgentRunControlRef(exactAcknowledgement.data.run, exactRequest.data.run));
116
126
  }
117
127
  export function agentRunControlRequestDigest(request) {
118
128
  const parsed = AgentRunControlRequestMaterialSchema.parse(request);
@@ -188,8 +198,7 @@ export function agentRunControlAcknowledgementMatchesRequest(request, acknowledg
188
198
  return false;
189
199
  return (exactAcknowledgement.data.operationId === exactRequest.data.operationId &&
190
200
  exactAcknowledgement.data.requestDigest === exactRequest.data.requestDigest &&
191
- canonicalCandidateDigest(exactAcknowledgement.data.run) ===
192
- canonicalCandidateDigest(exactRequest.data.run));
201
+ sameAgentRunControlRef(exactAcknowledgement.data.run, exactRequest.data.run));
193
202
  }
194
203
  const unknownRecordSchema = boundedJsonRecordSchema;
195
204
  const partBase = {
@@ -1,7 +1,45 @@
1
+ import { z } from "zod";
1
2
  import type { Sha256Digest } from "./agent-candidate.js";
2
3
  import { sha256DigestSchema } from "./agent-candidate-schema-common.js";
3
4
  export { sha256DigestSchema };
4
- export declare const idSchema: import("zod").ZodString;
5
- export declare const jsonRecordSchema: import("zod").ZodCustom<Record<string, unknown>, Record<string, unknown>>;
5
+ export declare const idSchema: z.ZodString;
6
+ export declare const jsonRecordSchema: z.ZodCustom<Record<string, unknown>, Record<string, unknown>>;
6
7
  export declare function wireDigest(value: unknown): Sha256Digest;
7
8
  export declare function sameOptionalWireValue(left: unknown, right: unknown): boolean;
9
+ /**
10
+ * The two fields that identify one durable workspace operation.
11
+ *
12
+ * Every result and lookup answer carries them, so a caller can bind an answer
13
+ * to the exact request it asked before using the resource it names.
14
+ */
15
+ export declare const operationIdentityShape: {
16
+ idempotencyKey: z.ZodString;
17
+ requestDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
18
+ };
19
+ /**
20
+ * Refuse a conflict answer that names the request it was asked about.
21
+ *
22
+ * A conflict states that the key is already held by a different request. An
23
+ * answer whose `existingRequestDigest` equals the request's own digest states
24
+ * a conflict with itself, which is a replay, so the caller would retry a
25
+ * request the service already accepted.
26
+ */
27
+ export declare function refuseSelfConflict(result: {
28
+ status: string;
29
+ requestDigest: string;
30
+ existingRequestDigest?: string;
31
+ }, ctx: z.RefinementCtx): void;
32
+ /**
33
+ * Whether the resource an answer carries was produced by that same operation.
34
+ *
35
+ * A checkpoint or forked environment repeats the key and digest of the
36
+ * operation that made it. An answer carrying a resource stamped with anything
37
+ * else names a resource the caller did not ask for.
38
+ */
39
+ export declare function operationResourceIdentityMatches(operation: {
40
+ idempotencyKey: string;
41
+ requestDigest: string;
42
+ }, resource: {
43
+ idempotencyKey: string;
44
+ requestDigest: string;
45
+ }): boolean;
@@ -18,3 +18,42 @@ export function sameOptionalWireValue(left, right) {
18
18
  return left === right;
19
19
  return wireDigest(left) === wireDigest(right);
20
20
  }
21
+ /**
22
+ * The two fields that identify one durable workspace operation.
23
+ *
24
+ * Every result and lookup answer carries them, so a caller can bind an answer
25
+ * to the exact request it asked before using the resource it names.
26
+ */
27
+ export const operationIdentityShape = {
28
+ idempotencyKey: idSchema,
29
+ requestDigest: sha256DigestSchema,
30
+ };
31
+ /**
32
+ * Refuse a conflict answer that names the request it was asked about.
33
+ *
34
+ * A conflict states that the key is already held by a different request. An
35
+ * answer whose `existingRequestDigest` equals the request's own digest states
36
+ * a conflict with itself, which is a replay, so the caller would retry a
37
+ * request the service already accepted.
38
+ */
39
+ export function refuseSelfConflict(result, ctx) {
40
+ if (result.status === "conflict" &&
41
+ result.existingRequestDigest === result.requestDigest) {
42
+ ctx.addIssue({
43
+ code: "custom",
44
+ path: ["existingRequestDigest"],
45
+ message: "a conflict must identify a different existing request",
46
+ });
47
+ }
48
+ }
49
+ /**
50
+ * Whether the resource an answer carries was produced by that same operation.
51
+ *
52
+ * A checkpoint or forked environment repeats the key and digest of the
53
+ * operation that made it. An answer carrying a resource stamped with anything
54
+ * else names a resource the caller did not ask for.
55
+ */
56
+ export function operationResourceIdentityMatches(operation, resource) {
57
+ return (resource.idempotencyKey === operation.idempotencyKey &&
58
+ resource.requestDigest === operation.requestDigest);
59
+ }
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AgentExactRunControlRefSchema } from "./runtime-control.js";
3
- import { idSchema, jsonRecordSchema, sameOptionalWireValue, sha256DigestSchema, wireDigest } from "./workspace-branching-shared.js";
3
+ import { idSchema, jsonRecordSchema, operationIdentityShape, operationResourceIdentityMatches, refuseSelfConflict, sameOptionalWireValue, sha256DigestSchema, wireDigest } from "./workspace-branching-shared.js";
4
4
  import { boundedStringSchema } from "./contract-limits.js";
5
5
  const WorkspaceCheckpointMaterialSchema = z.strictObject({
6
6
  source: AgentExactRunControlRefSchema,
@@ -56,45 +56,33 @@ export const WorkspaceCheckpointRefSchema = z
56
56
  });
57
57
  }
58
58
  });
59
- const checkpointOperationBase = {
60
- idempotencyKey: idSchema,
61
- requestDigest: sha256DigestSchema,
62
- };
63
59
  export const WorkspaceCheckpointResultSchema = z.discriminatedUnion("status", [
64
60
  z.strictObject({
65
61
  status: z.enum(["created", "replayed"]),
66
- ...checkpointOperationBase,
62
+ ...operationIdentityShape,
67
63
  checkpoint: WorkspaceCheckpointRefSchema,
68
64
  }),
69
65
  z.strictObject({
70
66
  status: z.literal("conflict"),
71
- ...checkpointOperationBase,
67
+ ...operationIdentityShape,
72
68
  existingRequestDigest: sha256DigestSchema,
73
69
  }),
74
70
  z.strictObject({
75
71
  status: z.literal("unknown"),
76
- ...checkpointOperationBase,
72
+ ...operationIdentityShape,
77
73
  message: boundedStringSchema.min(1),
78
74
  retryable: z.boolean(),
79
75
  }),
80
- ]).superRefine((result, refinement) => {
76
+ ]).superRefine((result, ctx) => {
81
77
  if ((result.status === "created" || result.status === "replayed") &&
82
- (result.checkpoint.idempotencyKey !== result.idempotencyKey ||
83
- result.checkpoint.requestDigest !== result.requestDigest)) {
84
- refinement.addIssue({
78
+ !operationResourceIdentityMatches(result, result.checkpoint)) {
79
+ ctx.addIssue({
85
80
  code: "custom",
86
81
  path: ["checkpoint"],
87
82
  message: "checkpoint identity must match its operation",
88
83
  });
89
84
  }
90
- if (result.status === "conflict" &&
91
- result.existingRequestDigest === result.requestDigest) {
92
- refinement.addIssue({
93
- code: "custom",
94
- path: ["existingRequestDigest"],
95
- message: "a conflict must identify a different existing request",
96
- });
97
- }
85
+ refuseSelfConflict(result, ctx);
98
86
  });
99
87
  export const WorkspaceOperationLookupRequestSchema = z.strictObject({
100
88
  idempotencyKey: idSchema,
@@ -103,42 +91,34 @@ export const WorkspaceOperationLookupRequestSchema = z.strictObject({
103
91
  export const WorkspaceCheckpointLookupResultSchema = z.discriminatedUnion("status", [
104
92
  z.strictObject({
105
93
  status: z.literal("found"),
106
- ...checkpointOperationBase,
94
+ ...operationIdentityShape,
107
95
  checkpoint: WorkspaceCheckpointRefSchema,
108
96
  }),
109
97
  z.strictObject({
110
98
  status: z.literal("not_found"),
111
- ...checkpointOperationBase,
99
+ ...operationIdentityShape,
112
100
  }),
113
101
  z.strictObject({
114
102
  status: z.literal("conflict"),
115
- ...checkpointOperationBase,
103
+ ...operationIdentityShape,
116
104
  existingRequestDigest: sha256DigestSchema,
117
105
  }),
118
106
  z.strictObject({
119
107
  status: z.literal("unknown"),
120
- ...checkpointOperationBase,
108
+ ...operationIdentityShape,
121
109
  message: boundedStringSchema.min(1),
122
110
  retryable: z.boolean(),
123
111
  }),
124
- ]).superRefine((result, refinement) => {
112
+ ]).superRefine((result, ctx) => {
125
113
  if (result.status === "found" &&
126
- (result.checkpoint.idempotencyKey !== result.idempotencyKey ||
127
- result.checkpoint.requestDigest !== result.requestDigest)) {
128
- refinement.addIssue({
114
+ !operationResourceIdentityMatches(result, result.checkpoint)) {
115
+ ctx.addIssue({
129
116
  code: "custom",
130
117
  path: ["checkpoint"],
131
118
  message: "checkpoint identity must match its lookup operation",
132
119
  });
133
120
  }
134
- if (result.status === "conflict" &&
135
- result.existingRequestDigest === result.requestDigest) {
136
- refinement.addIssue({
137
- code: "custom",
138
- path: ["existingRequestDigest"],
139
- message: "a conflict must identify a different existing request",
140
- });
141
- }
121
+ refuseSelfConflict(result, ctx);
142
122
  });
143
123
  /** Bind a checkpoint result to the exact request before using the resource. */
144
124
  export function workspaceCheckpointResultMatchesRequest(request, result) {
@@ -159,10 +139,8 @@ export function workspaceCheckpointResultMatchesRequest(request, result) {
159
139
  return false;
160
140
  }
161
141
  function checkpointResultMatchesParsed(request, result) {
162
- return (result.idempotencyKey === request.idempotencyKey &&
163
- result.requestDigest === request.requestDigest &&
164
- result.checkpoint.idempotencyKey === request.idempotencyKey &&
165
- result.checkpoint.requestDigest === request.requestDigest &&
142
+ return (operationResourceIdentityMatches(request, result) &&
143
+ operationResourceIdentityMatches(request, result.checkpoint) &&
166
144
  wireDigest(result.checkpoint.source) === wireDigest(request.source) &&
167
145
  sameOptionalWireValue(result.checkpoint.metadata, request.metadata) &&
168
146
  result.checkpoint.provider === request.source.provider);
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AgentExactRunControlRefSchema } from "./runtime-control.js";
3
- import { idSchema, jsonRecordSchema, sameOptionalWireValue, sha256DigestSchema, wireDigest } from "./workspace-branching-shared.js";
3
+ import { idSchema, jsonRecordSchema, operationIdentityShape, operationResourceIdentityMatches, refuseSelfConflict, sameOptionalWireValue, sha256DigestSchema, wireDigest } from "./workspace-branching-shared.js";
4
4
  import { WorkspaceCheckpointRefSchema } from "./workspace-checkpoint.js";
5
5
  import { ConfidentialAttestationSchema, ConfidentialExecutionRequestSchema, confidentialExecutionVerified, } from "./workspace-confidentiality.js";
6
6
  import { boundedStringSchema } from "./contract-limits.js";
@@ -155,82 +155,62 @@ export function forkedEnvironmentConfidentialityVerified(request, environment, v
155
155
  ...(verifyProviderAttestation ? { verifyProviderAttestation } : {}),
156
156
  });
157
157
  }
158
- const forkOperationBase = {
159
- idempotencyKey: idSchema,
160
- requestDigest: sha256DigestSchema,
161
- };
162
158
  export const WorkspaceForkResultSchema = z.discriminatedUnion("status", [
163
159
  z.strictObject({
164
160
  status: z.enum(["created", "replayed"]),
165
- ...forkOperationBase,
161
+ ...operationIdentityShape,
166
162
  environment: ForkedEnvironmentRefSchema,
167
163
  }),
168
164
  z.strictObject({
169
165
  status: z.literal("conflict"),
170
- ...forkOperationBase,
166
+ ...operationIdentityShape,
171
167
  existingRequestDigest: sha256DigestSchema,
172
168
  }),
173
169
  z.strictObject({
174
170
  status: z.literal("unknown"),
175
- ...forkOperationBase,
171
+ ...operationIdentityShape,
176
172
  message: boundedStringSchema.min(1),
177
173
  retryable: z.boolean(),
178
174
  }),
179
- ]).superRefine((result, refinement) => {
175
+ ]).superRefine((result, ctx) => {
180
176
  if ((result.status === "created" || result.status === "replayed") &&
181
- (result.environment.idempotencyKey !== result.idempotencyKey ||
182
- result.environment.requestDigest !== result.requestDigest)) {
183
- refinement.addIssue({
177
+ !operationResourceIdentityMatches(result, result.environment)) {
178
+ ctx.addIssue({
184
179
  code: "custom",
185
180
  path: ["environment"],
186
181
  message: "forked environment identity must match its operation",
187
182
  });
188
183
  }
189
- if (result.status === "conflict" &&
190
- result.existingRequestDigest === result.requestDigest) {
191
- refinement.addIssue({
192
- code: "custom",
193
- path: ["existingRequestDigest"],
194
- message: "a conflict must identify a different existing request",
195
- });
196
- }
184
+ refuseSelfConflict(result, ctx);
197
185
  });
198
186
  export const WorkspaceForkLookupResultSchema = z.discriminatedUnion("status", [
199
187
  z.strictObject({
200
188
  status: z.literal("found"),
201
- ...forkOperationBase,
189
+ ...operationIdentityShape,
202
190
  environment: ForkedEnvironmentRefSchema,
203
191
  }),
204
- z.strictObject({ status: z.literal("not_found"), ...forkOperationBase }),
192
+ z.strictObject({ status: z.literal("not_found"), ...operationIdentityShape }),
205
193
  z.strictObject({
206
194
  status: z.literal("conflict"),
207
- ...forkOperationBase,
195
+ ...operationIdentityShape,
208
196
  existingRequestDigest: sha256DigestSchema,
209
197
  }),
210
198
  z.strictObject({
211
199
  status: z.literal("unknown"),
212
- ...forkOperationBase,
200
+ ...operationIdentityShape,
213
201
  message: boundedStringSchema.min(1),
214
202
  retryable: z.boolean(),
215
203
  }),
216
- ]).superRefine((result, refinement) => {
204
+ ]).superRefine((result, ctx) => {
217
205
  if (result.status === "found" &&
218
- (result.environment.idempotencyKey !== result.idempotencyKey ||
219
- result.environment.requestDigest !== result.requestDigest)) {
220
- refinement.addIssue({
206
+ !operationResourceIdentityMatches(result, result.environment)) {
207
+ ctx.addIssue({
221
208
  code: "custom",
222
209
  path: ["environment"],
223
210
  message: "forked environment identity must match its lookup operation",
224
211
  });
225
212
  }
226
- if (result.status === "conflict" &&
227
- result.existingRequestDigest === result.requestDigest) {
228
- refinement.addIssue({
229
- code: "custom",
230
- path: ["existingRequestDigest"],
231
- message: "a conflict must identify a different existing request",
232
- });
233
- }
213
+ refuseSelfConflict(result, ctx);
234
214
  });
235
215
  export function workspaceForkResultMatchesRequest(request, result) {
236
216
  const parsedRequest = WorkspaceForkRequestSchema.safeParse(request);
@@ -251,10 +231,8 @@ export function workspaceForkResultMatchesRequest(request, result) {
251
231
  }
252
232
  function forkResultMatchesParsed(request, result) {
253
233
  const source = request.checkpoint.source;
254
- return (result.idempotencyKey === request.idempotencyKey &&
255
- result.requestDigest === request.requestDigest &&
256
- result.environment.idempotencyKey === request.idempotencyKey &&
257
- result.environment.requestDigest === request.requestDigest &&
234
+ return (operationResourceIdentityMatches(request, result) &&
235
+ operationResourceIdentityMatches(request, result.environment) &&
258
236
  result.environment.sourceCheckpointId === request.checkpoint.checkpointId &&
259
237
  result.environment.provider === request.checkpoint.provider &&
260
238
  result.environment.sourceEnvironmentId === source.environmentId &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",