@tangle-network/agent-interface 1.3.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.
- package/README.md +6 -0
- package/dist/agent-candidate-evidence-schema.d.ts +35 -0
- package/dist/agent-candidate-evidence-schema.js +39 -0
- package/dist/agent-candidate-execution-plan-schema.js +2 -24
- package/dist/agent-candidate-outcome-schema.js +5 -30
- package/dist/agent-profile-snapshot.js +1 -10
- package/dist/certified-context.js +1 -9
- package/dist/deep-freeze.d.ts +13 -0
- package/dist/deep-freeze.js +22 -0
- package/dist/environment-command-turn.d.ts +48 -0
- package/dist/environment-command-turn.js +128 -0
- package/dist/environment-provider.d.ts +1 -0
- package/dist/environment-provider.js +1 -0
- package/dist/environment-runtime.d.ts +41 -1
- package/dist/environment-runtime.js +27 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/runtime-control.d.ts +9 -0
- package/dist/runtime-control.js +79 -4
- package/dist/stream-events.d.ts +59 -1
- package/dist/workspace-branching-shared.d.ts +40 -2
- package/dist/workspace-branching-shared.js +39 -0
- package/dist/workspace-checkpoint.js +18 -40
- package/dist/workspace-fork.js +18 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,9 +33,15 @@ The public `AgentInstanceRecord` contains a credential-free profile identity, no
|
|
|
33
33
|
|
|
34
34
|
`AgentRunControlRef` identifies a retained run without depending on a live JavaScript object and may carry the provider's admission digest so reconstruction can reject changed-input reuse.
|
|
35
35
|
`RuntimeEventEnvelope` adds stable run, event, sequence, cursor, and timestamp fields around the existing `StreamEvent` union, and its runtime schema validates every canonical event variant.
|
|
36
|
+
The `child-task` event reports one update of a provider-native child task (a subagent, worker, or delegated task) with a stable `childId`, an optional `parentChildId`, a lifecycle status, start and update times, and the runner, model, usage, and terminal reason when the provider reports them.
|
|
37
|
+
Its `sourceEventId` identifies the exact update, so a consumer applies the first event with a given `sourceEventId` and ignores later copies during replay or reconnect.
|
|
38
|
+
Identity never depends on the bounded `raw` payload, and a provider that cannot report a stable `childId` emits no `child-task` event.
|
|
36
39
|
The canonical `cancelled` status identifies caller cancellation and remains distinct from `failed`.
|
|
37
40
|
Providers advertise `retainedControl` only when exact run, result, event, cancellation, replay, detach, turn, and session identity are all implemented together.
|
|
38
41
|
`AgentEnvironment.metadata` is the detached snapshot returned by create or get, so recovery can check persisted annotations without listing environments.
|
|
42
|
+
`AgentEnvironment.creation` reports what the create call that returned the object did: `created` when the call provisioned the environment, `replayed` when an existing environment matched the idempotency key.
|
|
43
|
+
It is a per-call fact, so a same-key replay returns a view of the same environment with `creation: "replayed"`, and the value is absent when the provider cannot prove either outcome.
|
|
44
|
+
A consumer never destroys an environment whose creation it cannot prove, because another caller can hold it.
|
|
39
45
|
Metadata can include caller-authored values and does not prove authorization or authorship.
|
|
40
46
|
`AgentSession.cancelRun()` accepts a canonical request digest bound to one operation and `AgentExactRunControlRef`, so a caller can safely repeat the same cancellation after losing the first acknowledgement.
|
|
41
47
|
Its acknowledgement repeats the operation, digest, and run coordinates and distinguishes a known cancellation effect from conflict or unknown state.
|
|
@@ -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
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
+
}
|
|
@@ -624,10 +624,34 @@ export interface AgentSession {
|
|
|
624
624
|
signal?: AbortSignal;
|
|
625
625
|
}): Promise<void>;
|
|
626
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* What one {@link AgentEnvironmentProvider.create} call did for the
|
|
629
|
+
* environment it returned.
|
|
630
|
+
*
|
|
631
|
+
* - `created`: this call provisioned the environment.
|
|
632
|
+
* - `replayed`: an existing environment that matched the idempotency key was
|
|
633
|
+
* returned. This call provisioned nothing.
|
|
634
|
+
*
|
|
635
|
+
* Absent when the provider cannot distinguish the two. A consumer treats an
|
|
636
|
+
* absent value as unknown and fails closed: it never destroys an environment
|
|
637
|
+
* whose creation it cannot prove, because another caller can hold it.
|
|
638
|
+
*/
|
|
639
|
+
export type AgentEnvironmentCreation = "created" | "replayed";
|
|
640
|
+
export declare const AgentEnvironmentCreationSchema: z.ZodEnum<{
|
|
641
|
+
replayed: "replayed";
|
|
642
|
+
created: "created";
|
|
643
|
+
}>;
|
|
627
644
|
export interface AgentEnvironment {
|
|
628
645
|
readonly id: string;
|
|
629
646
|
readonly provider: string;
|
|
630
647
|
readonly name?: string;
|
|
648
|
+
/**
|
|
649
|
+
* The verdict of the create call that returned this object. It is a
|
|
650
|
+
* per-call fact: a same-key replay returns a view of the same environment
|
|
651
|
+
* with `creation: "replayed"`. Absent on `get()` results and when the
|
|
652
|
+
* provider cannot prove which outcome happened.
|
|
653
|
+
*/
|
|
654
|
+
readonly creation?: AgentEnvironmentCreation;
|
|
631
655
|
/**
|
|
632
656
|
* Detached metadata returned by the provider.
|
|
633
657
|
* It can contain caller-authored annotations and is not authorization evidence.
|
|
@@ -945,15 +969,31 @@ export interface AgentEnvironmentCreateIdempotencyRecord<T> {
|
|
|
945
969
|
readonly pending: Promise<T>;
|
|
946
970
|
environment?: T;
|
|
947
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Return the per-call view of an environment that a same-key create replayed.
|
|
974
|
+
*
|
|
975
|
+
* The view shares every member of the environment, so operations act on the
|
|
976
|
+
* one environment, and it states `creation: "replayed"` because this call
|
|
977
|
+
* provisioned nothing. The copy is shallow, so the environment must be a plain
|
|
978
|
+
* object whose members do not read `this`; a class instance loses its
|
|
979
|
+
* prototype members in a copy and is rejected.
|
|
980
|
+
* @internal
|
|
981
|
+
*/
|
|
982
|
+
export declare function replayedAgentEnvironmentView<T extends object>(environment: T): T;
|
|
948
983
|
/**
|
|
949
984
|
* Apply the generic create contract to one provider adapter's keyed requests.
|
|
950
985
|
*
|
|
951
986
|
* The provider's backing service remains responsible for retaining the key
|
|
952
987
|
* across adapter reconstruction. This helper coalesces concurrent retries and
|
|
953
988
|
* rejects collisions before the provider performs another create effect.
|
|
989
|
+
*
|
|
990
|
+
* The call that runs `create` receives the environment the provider built,
|
|
991
|
+
* with the creation verdict the provider could prove. Every same-key call
|
|
992
|
+
* after it, including one that awaited the same pending create, receives
|
|
993
|
+
* {@link replayedAgentEnvironmentView} of that environment.
|
|
954
994
|
* @internal
|
|
955
995
|
*/
|
|
956
|
-
export declare function createAgentEnvironmentWithIdempotency<T>(records: Map<string, AgentEnvironmentCreateIdempotencyRecord<T>>, input: CreateAgentEnvironmentInput, create: () => Promise<T>): Promise<T>;
|
|
996
|
+
export declare function createAgentEnvironmentWithIdempotency<T extends object>(records: Map<string, AgentEnvironmentCreateIdempotencyRecord<T>>, input: CreateAgentEnvironmentInput, create: () => Promise<T>): Promise<T>;
|
|
957
997
|
export interface AgentEnvironmentProvider {
|
|
958
998
|
readonly name: string;
|
|
959
999
|
readonly exactProcess?: AgentExactProcessProvider;
|
|
@@ -113,6 +113,10 @@ export function agentNativeContextContinuationResultMatchesRequest(request, outc
|
|
|
113
113
|
(exactOutcome.result.sessionId === undefined ||
|
|
114
114
|
exactOutcome.result.sessionId === current.sessionId));
|
|
115
115
|
}
|
|
116
|
+
export const AgentEnvironmentCreationSchema = z.enum([
|
|
117
|
+
"created",
|
|
118
|
+
"replayed",
|
|
119
|
+
]);
|
|
116
120
|
/** Strict runtime validator for provider capability negotiation. */
|
|
117
121
|
export const AgentEnvironmentCapabilitiesSchema = z
|
|
118
122
|
.strictObject({
|
|
@@ -325,12 +329,34 @@ export function agentEnvironmentCreateInputDigest(input) {
|
|
|
325
329
|
input: material,
|
|
326
330
|
});
|
|
327
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Return the per-call view of an environment that a same-key create replayed.
|
|
334
|
+
*
|
|
335
|
+
* The view shares every member of the environment, so operations act on the
|
|
336
|
+
* one environment, and it states `creation: "replayed"` because this call
|
|
337
|
+
* provisioned nothing. The copy is shallow, so the environment must be a plain
|
|
338
|
+
* object whose members do not read `this`; a class instance loses its
|
|
339
|
+
* prototype members in a copy and is rejected.
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
export function replayedAgentEnvironmentView(environment) {
|
|
343
|
+
const prototype = Object.getPrototypeOf(environment);
|
|
344
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
345
|
+
throw new Error("a replayed agent environment view requires a plain object environment");
|
|
346
|
+
}
|
|
347
|
+
return { ...environment, creation: "replayed" };
|
|
348
|
+
}
|
|
328
349
|
/**
|
|
329
350
|
* Apply the generic create contract to one provider adapter's keyed requests.
|
|
330
351
|
*
|
|
331
352
|
* The provider's backing service remains responsible for retaining the key
|
|
332
353
|
* across adapter reconstruction. This helper coalesces concurrent retries and
|
|
333
354
|
* rejects collisions before the provider performs another create effect.
|
|
355
|
+
*
|
|
356
|
+
* The call that runs `create` receives the environment the provider built,
|
|
357
|
+
* with the creation verdict the provider could prove. Every same-key call
|
|
358
|
+
* after it, including one that awaited the same pending create, receives
|
|
359
|
+
* {@link replayedAgentEnvironmentView} of that environment.
|
|
334
360
|
* @internal
|
|
335
361
|
*/
|
|
336
362
|
export async function createAgentEnvironmentWithIdempotency(records, input, create) {
|
|
@@ -344,7 +370,7 @@ export async function createAgentEnvironmentWithIdempotency(records, input, crea
|
|
|
344
370
|
if (existing.digest !== digest) {
|
|
345
371
|
throw new Error("agent environment create idempotency key conflicts with a different create input");
|
|
346
372
|
}
|
|
347
|
-
return existing.environment ?? existing.pending;
|
|
373
|
+
return replayedAgentEnvironmentView(existing.environment ?? (await existing.pending));
|
|
348
374
|
}
|
|
349
375
|
const pending = Promise.resolve().then(create);
|
|
350
376
|
const record = {
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export * from "./host-services.js";
|
|
|
8
8
|
export * from "./mcp.js";
|
|
9
9
|
export * from "./provider-adapter.js";
|
|
10
10
|
export type * from "./environment-provider.js";
|
|
11
|
-
export { AgentEnvironmentCapabilitiesSchema, AgentNativeContextContinuationResultSchema, AgentTurnInputSchema, AgentTurnResultSchema, agentEnvironmentCreateInputDigest, agentNativeContextContinuationResultMatchesRequest, AccountUsageSchema, AgentEnvironmentObservationSchema, AgentEnvironmentStatusSchema, ComputeBillingSchema, EnvironmentLifecycleSchema, ModelUsageSchema, ObservationProvenanceSchema, ObservationStateSchema, PlacementDescriptorSchema, ProviderIdentitySchema, ResourceProfileSchema, ResourceUseSampleSchema, SafeEndpointSchema, agentEnvironmentObservationIdentityMatches, assertObservationCredentialFree, observationContainsCredential, observationOf, AgentInteractiveSessionAttachSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimRequestSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionPromptCommandSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStartSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentInteractiveSessionStopCommandSchema, AgentInteractiveSessionStatusSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimRequestDigest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionControlClaimIsNewer, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionPromptRequestDigest, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentInteractiveSessionStopRequestDigest, agentInteractiveSessionRequestDigest, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, exactAgentInteractiveSessionStart, createAgentEnvironmentWithIdempotency, TerminalAttachRequestSchema, TerminalAttachResultSchema, TerminalDetachAckSchema, TerminalInputSchema, TerminalOutputEventSchema, TerminalReplayWindowSchema, TerminalResizeSchema, TerminalSessionRefSchema, terminalAttachResultMatchesRequest, terminalSessionUsable, } from "./environment-provider.js";
|
|
11
|
+
export { AgentEnvironmentCapabilitiesSchema, AgentNativeContextContinuationResultSchema, AgentTurnInputSchema, AgentTurnResultSchema, agentEnvironmentCreateInputDigest, agentNativeContextContinuationResultMatchesRequest, AccountUsageSchema, AgentEnvironmentObservationSchema, AgentEnvironmentStatusSchema, ComputeBillingSchema, EnvironmentLifecycleSchema, ModelUsageSchema, ObservationProvenanceSchema, ObservationStateSchema, PlacementDescriptorSchema, ProviderIdentitySchema, ResourceProfileSchema, ResourceUseSampleSchema, SafeEndpointSchema, agentEnvironmentObservationIdentityMatches, assertObservationCredentialFree, observationContainsCredential, observationOf, AgentInteractiveSessionAttachSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimRequestSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionPromptCommandSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStartSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentInteractiveSessionStopCommandSchema, AgentInteractiveSessionStatusSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimRequestDigest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionControlClaimIsNewer, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionPromptRequestDigest, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentInteractiveSessionStopRequestDigest, agentInteractiveSessionRequestDigest, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, exactAgentInteractiveSessionStart, AgentEnvironmentCreationSchema, createAgentEnvironmentWithIdempotency, replayedAgentEnvironmentView, TerminalAttachRequestSchema, TerminalAttachResultSchema, TerminalDetachAckSchema, TerminalInputSchema, TerminalOutputEventSchema, TerminalReplayWindowSchema, TerminalResizeSchema, TerminalSessionRefSchema, terminalAttachResultMatchesRequest, terminalSessionUsable, } from "./environment-provider.js";
|
|
12
12
|
export * from "./plan.js";
|
|
13
13
|
export * from "./runtime-control.js";
|
|
14
14
|
export * from "./portable-context.js";
|
|
@@ -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
|
@@ -7,7 +7,7 @@ export * from "./provider-config.js";
|
|
|
7
7
|
export * from "./host-services.js";
|
|
8
8
|
export * from "./mcp.js";
|
|
9
9
|
export * from "./provider-adapter.js";
|
|
10
|
-
export { AgentEnvironmentCapabilitiesSchema, AgentNativeContextContinuationResultSchema, AgentTurnInputSchema, AgentTurnResultSchema, agentEnvironmentCreateInputDigest, agentNativeContextContinuationResultMatchesRequest, AccountUsageSchema, AgentEnvironmentObservationSchema, AgentEnvironmentStatusSchema, ComputeBillingSchema, EnvironmentLifecycleSchema, ModelUsageSchema, ObservationProvenanceSchema, ObservationStateSchema, PlacementDescriptorSchema, ProviderIdentitySchema, ResourceProfileSchema, ResourceUseSampleSchema, SafeEndpointSchema, agentEnvironmentObservationIdentityMatches, assertObservationCredentialFree, observationContainsCredential, observationOf, AgentInteractiveSessionAttachSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimRequestSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionPromptCommandSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStartSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentInteractiveSessionStopCommandSchema, AgentInteractiveSessionStatusSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimRequestDigest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionControlClaimIsNewer, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionPromptRequestDigest, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentInteractiveSessionStopRequestDigest, agentInteractiveSessionRequestDigest, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, exactAgentInteractiveSessionStart, createAgentEnvironmentWithIdempotency, TerminalAttachRequestSchema, TerminalAttachResultSchema, TerminalDetachAckSchema, TerminalInputSchema, TerminalOutputEventSchema, TerminalReplayWindowSchema, TerminalResizeSchema, TerminalSessionRefSchema, terminalAttachResultMatchesRequest, terminalSessionUsable, } from "./environment-provider.js";
|
|
10
|
+
export { AgentEnvironmentCapabilitiesSchema, AgentNativeContextContinuationResultSchema, AgentTurnInputSchema, AgentTurnResultSchema, agentEnvironmentCreateInputDigest, agentNativeContextContinuationResultMatchesRequest, AccountUsageSchema, AgentEnvironmentObservationSchema, AgentEnvironmentStatusSchema, ComputeBillingSchema, EnvironmentLifecycleSchema, ModelUsageSchema, ObservationProvenanceSchema, ObservationStateSchema, PlacementDescriptorSchema, ProviderIdentitySchema, ResourceProfileSchema, ResourceUseSampleSchema, SafeEndpointSchema, agentEnvironmentObservationIdentityMatches, assertObservationCredentialFree, observationContainsCredential, observationOf, AgentInteractiveSessionAttachSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimRequestSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionPromptCommandSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStartSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentInteractiveSessionStopCommandSchema, AgentInteractiveSessionStatusSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimRequestDigest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionControlClaimIsNewer, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionPromptRequestDigest, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentInteractiveSessionStopRequestDigest, agentInteractiveSessionRequestDigest, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, exactAgentInteractiveSessionStart, AgentEnvironmentCreationSchema, createAgentEnvironmentWithIdempotency, replayedAgentEnvironmentView, TerminalAttachRequestSchema, TerminalAttachResultSchema, TerminalDetachAckSchema, TerminalInputSchema, TerminalOutputEventSchema, TerminalReplayWindowSchema, TerminalResizeSchema, TerminalSessionRefSchema, terminalAttachResultMatchesRequest, terminalSessionUsable, } from "./environment-provider.js";
|
|
11
11
|
export * from "./plan.js";
|
|
12
12
|
export * from "./runtime-control.js";
|
|
13
13
|
export * from "./portable-context.js";
|
|
@@ -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;
|
package/dist/runtime-control.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { canonicalCandidateDigest, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
|
|
3
3
|
import { boundedIdentifierSchema, boundedJsonRecordSchema, boundedJsonSchema, boundedStringSchema, } from "./contract-limits.js";
|
|
4
|
+
import { ModelUsageSchema } from "./environment-observation.js";
|
|
4
5
|
import { InteractionRequestSchema } from "./interaction.js";
|
|
5
6
|
import { DurablePlanSchema } from "./plan.js";
|
|
6
7
|
const stableIdSchema = boundedIdentifierSchema;
|
|
@@ -17,6 +18,17 @@ export const AgentExactRunControlRefSchema = AgentRunControlRefSchema.extend({
|
|
|
17
18
|
executionId: stableIdSchema,
|
|
18
19
|
requestDigest: sha256DigestSchema,
|
|
19
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
|
+
}
|
|
20
32
|
export function agentRunCancellationRequestDigest(request) {
|
|
21
33
|
const parsed = AgentRunCancellationRequestMaterialSchema.parse(request);
|
|
22
34
|
return canonicalCandidateDigest({
|
|
@@ -110,8 +122,7 @@ export function agentRunCancellationAcknowledgementMatchesRequest(request, ackno
|
|
|
110
122
|
return false;
|
|
111
123
|
return (exactAcknowledgement.data.operationId === exactRequest.data.operationId &&
|
|
112
124
|
exactAcknowledgement.data.requestDigest === exactRequest.data.requestDigest &&
|
|
113
|
-
|
|
114
|
-
canonicalCandidateDigest(exactRequest.data.run));
|
|
125
|
+
sameAgentRunControlRef(exactAcknowledgement.data.run, exactRequest.data.run));
|
|
115
126
|
}
|
|
116
127
|
export function agentRunControlRequestDigest(request) {
|
|
117
128
|
const parsed = AgentRunControlRequestMaterialSchema.parse(request);
|
|
@@ -187,8 +198,7 @@ export function agentRunControlAcknowledgementMatchesRequest(request, acknowledg
|
|
|
187
198
|
return false;
|
|
188
199
|
return (exactAcknowledgement.data.operationId === exactRequest.data.operationId &&
|
|
189
200
|
exactAcknowledgement.data.requestDigest === exactRequest.data.requestDigest &&
|
|
190
|
-
|
|
191
|
-
canonicalCandidateDigest(exactRequest.data.run));
|
|
201
|
+
sameAgentRunControlRef(exactAcknowledgement.data.run, exactRequest.data.run));
|
|
192
202
|
}
|
|
193
203
|
const unknownRecordSchema = boundedJsonRecordSchema;
|
|
194
204
|
const partBase = {
|
|
@@ -263,6 +273,70 @@ const partSchema = z.discriminatedUnion("type", [
|
|
|
263
273
|
agent: stableIdSchema,
|
|
264
274
|
}),
|
|
265
275
|
]);
|
|
276
|
+
const TERMINAL_CHILD_TASK_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
277
|
+
const epochMillisecondsSchema = z.number().finite().nonnegative();
|
|
278
|
+
/**
|
|
279
|
+
* Provider-native child task lifecycle. Identity comes only from `childId`,
|
|
280
|
+
* `parentChildId`, and `sourceEventId`; `raw` is opaque and bounded. A provider
|
|
281
|
+
* without a stable `childId` emits no `child-task` event.
|
|
282
|
+
*/
|
|
283
|
+
const ChildTaskEventSchema = z
|
|
284
|
+
.strictObject({
|
|
285
|
+
type: z.literal("child-task"),
|
|
286
|
+
childId: stableIdSchema,
|
|
287
|
+
parentChildId: stableIdSchema.optional(),
|
|
288
|
+
status: z.enum(["started", "running", "completed", "failed", "cancelled"]),
|
|
289
|
+
title: boundedStringSchema.optional(),
|
|
290
|
+
time: z.strictObject({
|
|
291
|
+
started: epochMillisecondsSchema,
|
|
292
|
+
updated: epochMillisecondsSchema,
|
|
293
|
+
ended: epochMillisecondsSchema.optional(),
|
|
294
|
+
}),
|
|
295
|
+
runner: stableIdSchema.optional(),
|
|
296
|
+
model: stableIdSchema.optional(),
|
|
297
|
+
usage: ModelUsageSchema.optional(),
|
|
298
|
+
terminalReason: boundedStringSchema.optional(),
|
|
299
|
+
sourceEventId: stableIdSchema,
|
|
300
|
+
raw: boundedJsonRecordSchema.optional(),
|
|
301
|
+
})
|
|
302
|
+
.superRefine((event, refinement) => {
|
|
303
|
+
const terminal = TERMINAL_CHILD_TASK_STATUSES.has(event.status);
|
|
304
|
+
if (!terminal && event.time.ended !== undefined) {
|
|
305
|
+
refinement.addIssue({
|
|
306
|
+
code: "custom",
|
|
307
|
+
path: ["time", "ended"],
|
|
308
|
+
message: "only a terminal child task status may carry an end time",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (!terminal && event.terminalReason !== undefined) {
|
|
312
|
+
refinement.addIssue({
|
|
313
|
+
code: "custom",
|
|
314
|
+
path: ["terminalReason"],
|
|
315
|
+
message: "only a terminal child task status may carry a terminal reason",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (event.time.updated < event.time.started) {
|
|
319
|
+
refinement.addIssue({
|
|
320
|
+
code: "custom",
|
|
321
|
+
path: ["time", "updated"],
|
|
322
|
+
message: "a child task update time cannot precede its start time",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (event.time.ended !== undefined && event.time.ended < event.time.started) {
|
|
326
|
+
refinement.addIssue({
|
|
327
|
+
code: "custom",
|
|
328
|
+
path: ["time", "ended"],
|
|
329
|
+
message: "a child task end time cannot precede its start time",
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (event.parentChildId === event.childId) {
|
|
333
|
+
refinement.addIssue({
|
|
334
|
+
code: "custom",
|
|
335
|
+
path: ["parentChildId"],
|
|
336
|
+
message: "a child task cannot be its own parent",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
});
|
|
266
340
|
/** Runtime validator for every member of the existing canonical event union. */
|
|
267
341
|
export const CanonicalStreamEventSchema = z.discriminatedUnion("type", [
|
|
268
342
|
z.strictObject({
|
|
@@ -332,6 +406,7 @@ export const CanonicalStreamEventSchema = z.discriminatedUnion("type", [
|
|
|
332
406
|
type: z.literal("plan.submitted"),
|
|
333
407
|
plan: DurablePlanSchema,
|
|
334
408
|
}),
|
|
409
|
+
ChildTaskEventSchema,
|
|
335
410
|
]);
|
|
336
411
|
export const RuntimeEventEnvelopeSchema = z.strictObject({
|
|
337
412
|
runId: stableIdSchema,
|
package/dist/stream-events.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TokenUsage } from "./execution-types.js";
|
|
1
2
|
import type { InteractionRequest } from "./interaction.js";
|
|
2
3
|
import type { DurablePlan } from "./plan.js";
|
|
3
4
|
import type { Part } from "./parts.js";
|
|
@@ -7,6 +8,63 @@ export type MessagePartUpdatedEvent = {
|
|
|
7
8
|
delta?: string;
|
|
8
9
|
};
|
|
9
10
|
export type StreamStatus = "started" | "processing" | "completed" | "failed" | "cancelled";
|
|
11
|
+
export type ChildTaskStatus = "started" | "running" | "completed" | "failed" | "cancelled";
|
|
12
|
+
/**
|
|
13
|
+
* One observed update of a provider-native child task: a subagent, worker, or
|
|
14
|
+
* delegated task that the runner started inside the same run.
|
|
15
|
+
*
|
|
16
|
+
* Identity rules:
|
|
17
|
+
* - `childId` is the provider's stable identifier for the child task. Every
|
|
18
|
+
* update of one child repeats the same `childId`. A provider that cannot
|
|
19
|
+
* report a stable `childId` emits no `child-task` event.
|
|
20
|
+
* - `parentChildId` names the parent child task. It is absent when the parent
|
|
21
|
+
* is the run itself.
|
|
22
|
+
* - `sourceEventId` is the provider's identifier for this exact update. Two
|
|
23
|
+
* events with the same `sourceEventId` are the same update, so a consumer
|
|
24
|
+
* applies the first and ignores the rest during replay or reconnect.
|
|
25
|
+
* - Identity never depends on `raw`. `raw` is an opaque, bounded copy of
|
|
26
|
+
* provider fields that have no canonical position.
|
|
27
|
+
*
|
|
28
|
+
* Certainty rules:
|
|
29
|
+
* - `time.ended` and `terminalReason` are present only with a terminal status
|
|
30
|
+
* (`completed`, `failed`, `cancelled`).
|
|
31
|
+
* - `time.updated` and `time.ended` are never earlier than `time.started`.
|
|
32
|
+
*
|
|
33
|
+
* Dedupe example for a consumer that rebuilds the child tree from a replayed
|
|
34
|
+
* stream. Live and replayed streams produce the same tree because identity
|
|
35
|
+
* comes only from `childId`, `parentChildId`, and `sourceEventId`:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const applied = new Set<string>();
|
|
39
|
+
* const children = new Map<string, ChildTaskEvent>();
|
|
40
|
+
* for (const event of events) {
|
|
41
|
+
* if (event.type !== "child-task") continue;
|
|
42
|
+
* if (applied.has(event.sourceEventId)) continue;
|
|
43
|
+
* applied.add(event.sourceEventId);
|
|
44
|
+
* children.set(event.childId, event);
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export type ChildTaskEvent = {
|
|
49
|
+
type: "child-task";
|
|
50
|
+
childId: string;
|
|
51
|
+
parentChildId?: string;
|
|
52
|
+
status: ChildTaskStatus;
|
|
53
|
+
title?: string;
|
|
54
|
+
/** Epoch milliseconds reported by the provider. */
|
|
55
|
+
time: {
|
|
56
|
+
started: number;
|
|
57
|
+
updated: number;
|
|
58
|
+
ended?: number;
|
|
59
|
+
};
|
|
60
|
+
/** Runner that executes the child, for example `claude-code`. */
|
|
61
|
+
runner?: string;
|
|
62
|
+
model?: string;
|
|
63
|
+
usage?: TokenUsage;
|
|
64
|
+
terminalReason?: string;
|
|
65
|
+
sourceEventId: string;
|
|
66
|
+
raw?: Record<string, unknown>;
|
|
67
|
+
};
|
|
10
68
|
export type StreamEvent = MessagePartUpdatedEvent | {
|
|
11
69
|
type: "tool-heartbeat";
|
|
12
70
|
toolName: string;
|
|
@@ -53,4 +111,4 @@ export type StreamEvent = MessagePartUpdatedEvent | {
|
|
|
53
111
|
} | {
|
|
54
112
|
type: "plan.submitted";
|
|
55
113
|
plan: DurablePlan;
|
|
56
|
-
};
|
|
114
|
+
} | ChildTaskEvent;
|
|
@@ -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:
|
|
5
|
-
export declare const jsonRecordSchema:
|
|
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
|
-
...
|
|
62
|
+
...operationIdentityShape,
|
|
67
63
|
checkpoint: WorkspaceCheckpointRefSchema,
|
|
68
64
|
}),
|
|
69
65
|
z.strictObject({
|
|
70
66
|
status: z.literal("conflict"),
|
|
71
|
-
...
|
|
67
|
+
...operationIdentityShape,
|
|
72
68
|
existingRequestDigest: sha256DigestSchema,
|
|
73
69
|
}),
|
|
74
70
|
z.strictObject({
|
|
75
71
|
status: z.literal("unknown"),
|
|
76
|
-
...
|
|
72
|
+
...operationIdentityShape,
|
|
77
73
|
message: boundedStringSchema.min(1),
|
|
78
74
|
retryable: z.boolean(),
|
|
79
75
|
}),
|
|
80
|
-
]).superRefine((result,
|
|
76
|
+
]).superRefine((result, ctx) => {
|
|
81
77
|
if ((result.status === "created" || result.status === "replayed") &&
|
|
82
|
-
(result
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
94
|
+
...operationIdentityShape,
|
|
107
95
|
checkpoint: WorkspaceCheckpointRefSchema,
|
|
108
96
|
}),
|
|
109
97
|
z.strictObject({
|
|
110
98
|
status: z.literal("not_found"),
|
|
111
|
-
...
|
|
99
|
+
...operationIdentityShape,
|
|
112
100
|
}),
|
|
113
101
|
z.strictObject({
|
|
114
102
|
status: z.literal("conflict"),
|
|
115
|
-
...
|
|
103
|
+
...operationIdentityShape,
|
|
116
104
|
existingRequestDigest: sha256DigestSchema,
|
|
117
105
|
}),
|
|
118
106
|
z.strictObject({
|
|
119
107
|
status: z.literal("unknown"),
|
|
120
|
-
...
|
|
108
|
+
...operationIdentityShape,
|
|
121
109
|
message: boundedStringSchema.min(1),
|
|
122
110
|
retryable: z.boolean(),
|
|
123
111
|
}),
|
|
124
|
-
]).superRefine((result,
|
|
112
|
+
]).superRefine((result, ctx) => {
|
|
125
113
|
if (result.status === "found" &&
|
|
126
|
-
(result
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
163
|
-
result.
|
|
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);
|
package/dist/workspace-fork.js
CHANGED
|
@@ -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
|
-
...
|
|
161
|
+
...operationIdentityShape,
|
|
166
162
|
environment: ForkedEnvironmentRefSchema,
|
|
167
163
|
}),
|
|
168
164
|
z.strictObject({
|
|
169
165
|
status: z.literal("conflict"),
|
|
170
|
-
...
|
|
166
|
+
...operationIdentityShape,
|
|
171
167
|
existingRequestDigest: sha256DigestSchema,
|
|
172
168
|
}),
|
|
173
169
|
z.strictObject({
|
|
174
170
|
status: z.literal("unknown"),
|
|
175
|
-
...
|
|
171
|
+
...operationIdentityShape,
|
|
176
172
|
message: boundedStringSchema.min(1),
|
|
177
173
|
retryable: z.boolean(),
|
|
178
174
|
}),
|
|
179
|
-
]).superRefine((result,
|
|
175
|
+
]).superRefine((result, ctx) => {
|
|
180
176
|
if ((result.status === "created" || result.status === "replayed") &&
|
|
181
|
-
(result
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
189
|
+
...operationIdentityShape,
|
|
202
190
|
environment: ForkedEnvironmentRefSchema,
|
|
203
191
|
}),
|
|
204
|
-
z.strictObject({ status: z.literal("not_found"), ...
|
|
192
|
+
z.strictObject({ status: z.literal("not_found"), ...operationIdentityShape }),
|
|
205
193
|
z.strictObject({
|
|
206
194
|
status: z.literal("conflict"),
|
|
207
|
-
...
|
|
195
|
+
...operationIdentityShape,
|
|
208
196
|
existingRequestDigest: sha256DigestSchema,
|
|
209
197
|
}),
|
|
210
198
|
z.strictObject({
|
|
211
199
|
status: z.literal("unknown"),
|
|
212
|
-
...
|
|
200
|
+
...operationIdentityShape,
|
|
213
201
|
message: boundedStringSchema.min(1),
|
|
214
202
|
retryable: z.boolean(),
|
|
215
203
|
}),
|
|
216
|
-
]).superRefine((result,
|
|
204
|
+
]).superRefine((result, ctx) => {
|
|
217
205
|
if (result.status === "found" &&
|
|
218
|
-
(result
|
|
219
|
-
|
|
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
|
-
|
|
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
|
|
255
|
-
result.
|
|
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 &&
|