@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.
- 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-observation.d.ts +3 -3
- package/dist/environment-provider.d.ts +1 -0
- package/dist/environment-provider.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime-control.d.ts +9 -0
- package/dist/runtime-control.js +13 -4
- 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
|
@@ -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
|
+
}
|
|
@@ -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<{
|
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;
|
package/dist/runtime-control.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
|
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 &&
|