@hue-run/sdk 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2
+ /** Scenarios listed while resolving a name; bounds the registry reads of one selection. */
3
+ const MAX_LISTED = 200;
4
+ /** Lists Scenarios of the project; requires a Read and write key. */
5
+ export function listScenarios(client, page) {
6
+ return client.listCaseConversions(page);
7
+ }
8
+ /** Reads one Scenario with its publication pins; requires a Read and write key. */
9
+ export function getScenario(client, id) {
10
+ return client.getCaseConversion(id);
11
+ }
12
+ /**
13
+ * Interprets a selector as a UUID, a Hue URL containing `/<segment>/<uuid>` (for example
14
+ * `/scenarios/<uuid>`, query parameters ignored) or a display name.
15
+ *
16
+ * @throws TypeError for an empty selector or a URL without the expected segment.
17
+ */
18
+ export function parseScenarioSelector(selector, segments = ["scenarios"]) {
19
+ const value = selector.trim();
20
+ if (!value)
21
+ throw new TypeError("A Scenario selector is required");
22
+ if (UUID.test(value))
23
+ return { kind: "id", id: value.toLowerCase() };
24
+ if (/^https?:\/\//i.test(value)) {
25
+ let url;
26
+ try {
27
+ url = new URL(value);
28
+ }
29
+ catch {
30
+ throw new TypeError("The selector is not a valid URL");
31
+ }
32
+ const parts = url.pathname.split("/");
33
+ for (let index = 0; index < parts.length - 1; index++) {
34
+ const next = parts[index + 1];
35
+ if (segments.includes(parts[index]) && UUID.test(next))
36
+ return { kind: "id", id: next.toLowerCase() };
37
+ }
38
+ throw new TypeError(`The URL does not contain /${segments.join("|")}/<id>; paste the page URL or the ID`);
39
+ }
40
+ return { kind: "name", name: value };
41
+ }
42
+ /** Case-insensitive exact matches first, then unique prefix/substring matches. */
43
+ export function matchByName(candidates, name) {
44
+ const wanted = name.trim().toLowerCase();
45
+ const exact = candidates.filter((candidate) => candidate.name.trim().toLowerCase() === wanted);
46
+ if (exact.length)
47
+ return { matches: exact, exact: true };
48
+ const prefix = candidates.filter((candidate) => candidate.name.trim().toLowerCase().startsWith(wanted));
49
+ if (prefix.length)
50
+ return { matches: prefix, exact: false };
51
+ return {
52
+ matches: candidates.filter((candidate) => candidate.name.toLowerCase().includes(wanted)),
53
+ exact: false,
54
+ };
55
+ }
56
+ async function listPublishedScenarios(client) {
57
+ const items = [];
58
+ let after;
59
+ for (;;) {
60
+ const page = await client.listCaseConversions({ after, limit: 100 });
61
+ items.push(...page.items.filter((item) => item.status === "published"));
62
+ if (!page.nextCursor || items.length >= MAX_LISTED)
63
+ return items.slice(0, MAX_LISTED);
64
+ after = page.nextCursor;
65
+ }
66
+ }
67
+ const describe = (candidates) => candidates.map((candidate) => `${candidate.name} (${candidate.id})`).join(", ");
68
+ async function pinsFromScenario(client, scenario, dataset) {
69
+ if (!scenario.publication)
70
+ throw new Error(`Scenario ${scenario.id} is a draft without published pins; publish it in Hue first`);
71
+ const version = await client.getDatasetVersion(scenario.publication.datasetVersionId);
72
+ return {
73
+ scenarioId: scenario.id,
74
+ name: dataset.name,
75
+ datasetId: scenario.publication.datasetId,
76
+ datasetVersionId: version.id,
77
+ scorerVersionIds: [scenario.publication.scorerVersionId],
78
+ environmentVersionId: scenario.publication.environmentVersionId,
79
+ saved: version.frozenAt !== null,
80
+ revision: version.revision,
81
+ };
82
+ }
83
+ /**
84
+ * Resolves a published Scenario's immutable pins from its ID, its Hue URL or its name. A name
85
+ * matches the dataset name of published Scenarios case-insensitively: exact matches first, then
86
+ * a unique prefix or substring.
87
+ *
88
+ * @throws Error when no Scenario matches, several match, or the Scenario is an unpublished draft.
89
+ */
90
+ export async function resolveScenarioPins(client, selector) {
91
+ const parsed = parseScenarioSelector(selector);
92
+ if (parsed.kind === "id") {
93
+ const scenario = await client.getCaseConversion(parsed.id);
94
+ if (!scenario.publication)
95
+ throw new Error(`Scenario ${scenario.id} is a draft without published pins; publish it in Hue first`);
96
+ return pinsFromScenario(client, scenario, await client.getDataset(scenario.publication.datasetId));
97
+ }
98
+ const published = await listPublishedScenarios(client);
99
+ const datasets = new Map();
100
+ const candidates = [];
101
+ for (const summary of published) {
102
+ const scenario = await client.getCaseConversion(summary.id);
103
+ if (!scenario.publication)
104
+ continue;
105
+ let dataset = datasets.get(scenario.publication.datasetId);
106
+ if (!dataset) {
107
+ dataset = await client.getDataset(scenario.publication.datasetId);
108
+ datasets.set(dataset.id, dataset);
109
+ }
110
+ candidates.push({ name: dataset.name, id: scenario.id, scenario, dataset });
111
+ }
112
+ const { matches } = matchByName(candidates, parsed.name);
113
+ if (matches.length === 1)
114
+ return pinsFromScenario(client, matches[0].scenario, matches[0].dataset);
115
+ if (matches.length > 1)
116
+ throw new Error(`Several published Scenarios match "${parsed.name}"; pass an ID or URL instead: ${describe(matches)}`);
117
+ throw new Error(candidates.length
118
+ ? `No published Scenario matches "${parsed.name}". Published Scenarios: ${describe(candidates)}`
119
+ : `No published Scenario matches "${parsed.name}"; publish one in Hue first`);
120
+ }
121
+ /**
122
+ * Resolves an eval set (dataset) by ID, Hue URL or name to its latest saved version. When the
123
+ * set has no saved version, the latest draft is returned with `saved: false` so a caller can
124
+ * freeze it explicitly. Scorer versions are not pinned by a set; supply them separately.
125
+ *
126
+ * @throws Error when no set matches, several match, or the set has no versions.
127
+ */
128
+ export async function resolveEvalSetPins(client, selector, options = {}) {
129
+ const parsed = parseScenarioSelector(selector, ["datasets", "eval-sets", "evalsets", "sets"]);
130
+ let dataset;
131
+ if (parsed.kind === "id")
132
+ dataset = await client.getDataset(parsed.id);
133
+ else {
134
+ const candidates = [];
135
+ let after;
136
+ for (;;) {
137
+ const page = await client.listDatasets({ after, limit: 100 });
138
+ candidates.push(...page.items.filter((item) => !item.archivedAt));
139
+ if (!page.nextCursor || candidates.length >= MAX_LISTED)
140
+ break;
141
+ after = page.nextCursor;
142
+ }
143
+ // An exact slug is the stable handle scripts and agents pass; names are matched after it.
144
+ const wanted = parsed.name.trim().toLowerCase();
145
+ const bySlug = candidates.filter((item) => item.slug?.toLowerCase() === wanted);
146
+ const { matches } = bySlug.length ? { matches: bySlug } : matchByName(candidates, parsed.name);
147
+ if (matches.length > 1)
148
+ throw new Error(`Several eval sets match "${parsed.name}"; pass an ID or URL instead: ${describe(matches)}`);
149
+ if (!matches.length)
150
+ throw new Error(candidates.length
151
+ ? `No eval set matches "${parsed.name}". Eval sets: ${describe(candidates)}`
152
+ : `No eval set matches "${parsed.name}"`);
153
+ dataset = await client.getDataset(matches[0].id);
154
+ }
155
+ const latest = (versions) => versions.reduce((best, version) => (best === undefined || version.version > best.version ? version : best), undefined);
156
+ const version = latest(dataset.versions.filter((item) => item.frozenAt !== null)) ?? latest(dataset.versions);
157
+ if (!version)
158
+ throw new Error(`Eval set "${dataset.name}" has no versions`);
159
+ return {
160
+ scenarioId: null,
161
+ name: dataset.name,
162
+ datasetId: dataset.id,
163
+ datasetVersionId: version.id,
164
+ scorerVersionIds: [...(options.scorerVersionIds ?? [])],
165
+ environmentVersionId: null,
166
+ saved: version.frozenAt !== null,
167
+ revision: version.revision,
168
+ };
169
+ }
@@ -16,7 +16,20 @@ export declare function defineLocalScorer(options: {
16
16
  score: LocalScorer["score"];
17
17
  }): LocalScorer;
18
18
  /** Check local callbacks before target invocation; never execute downloaded source code. */
19
+ export declare function isBoundLocally(definition: ScorerDefinition, scorers?: LocalScorer[]): boolean;
19
20
  export declare function validateScorerBindings(versions: ScorerVersion[], scorers?: LocalScorer[]): void;
21
+ /**
22
+ * Whether this process produces the result for a pinned version. Built-ins always run here; a
23
+ * `local_code` pin runs here when bound, and with `deferUnboundLocalScorers` an unbound pin is
24
+ * left to the executor that owns its source (for example Hue's grading worker) instead of
25
+ * failing the run.
26
+ */
27
+ export declare function executableHere(definition: ScorerDefinition, options: {
28
+ scorers?: LocalScorer[];
29
+ deferUnboundLocalScorers?: boolean;
30
+ }): definition is Extract<ScorerDefinition, {
31
+ kind: "builtin" | "local_code";
32
+ }>;
20
33
  /** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
21
34
  export declare function isLocallyExecutable(definition: {
22
35
  kind: string;
@@ -92,13 +92,28 @@ function skip(explanation) {
92
92
  return { state: "skipped", explanation };
93
93
  }
94
94
  /** Check local callbacks before target invocation; never execute downloaded source code. */
95
+ export function isBoundLocally(definition, scorers = []) {
96
+ return scorers.some((local) => digest(local.definition) === digest(definition));
97
+ }
95
98
  export function validateScorerBindings(versions, scorers = []) {
96
99
  for (const { definition } of versions) {
97
- if (definition.kind === "local_code" &&
98
- !scorers.some((local) => digest(local.definition) === digest(definition)))
100
+ if (definition.kind === "local_code" && !isBoundLocally(definition, scorers))
99
101
  throw new Error("A pinned local scorer has no matching language/source/entrypoint/metric binding");
100
102
  }
101
103
  }
104
+ /**
105
+ * Whether this process produces the result for a pinned version. Built-ins always run here; a
106
+ * `local_code` pin runs here when bound, and with `deferUnboundLocalScorers` an unbound pin is
107
+ * left to the executor that owns its source (for example Hue's grading worker) instead of
108
+ * failing the run.
109
+ */
110
+ export function executableHere(definition, options) {
111
+ if (!isLocallyExecutable(definition))
112
+ return false;
113
+ if (definition.kind === "local_code" && options.deferUnboundLocalScorers)
114
+ return isBoundLocally(definition, options.scorers);
115
+ return true;
116
+ }
102
117
  /** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
103
118
  export function isLocallyExecutable(definition) {
104
119
  return (definition.kind === "local_code" ||
@@ -127,7 +142,10 @@ export async function scoreLocally(version, context, options = {}) {
127
142
  return { state: "error", error: { type: "LocalScorerError" } };
128
143
  }
129
144
  }
130
- if (!context.hasOutput && !(definition.kind === "local_code" && context.environment))
145
+ // A code evaluator can grade a sealed world or generated files without a JSON output.
146
+ const generatedFiles = context.files?.some((file) => file.role === "output") ?? false;
147
+ if (!context.hasOutput &&
148
+ !(definition.kind === "local_code" && (context.environment || generatedFiles)))
131
149
  return skip("Output evidence is unavailable");
132
150
  if (context.hasOutput && context.output === undefined)
133
151
  throw new TypeError("hasOutput requires a present JSON output");
@@ -22,12 +22,23 @@ export interface RepositorySimulationCase {
22
22
  /** Optional caller-owned case metadata. */
23
23
  metadata?: Record<string, JsonValue>;
24
24
  }
25
- /** Immutable app-authored experiment reference or repository-authored simulation definition. */
25
+ /** Immutable app-authored experiment reference, published pins or repository-authored definition. */
26
26
  export type SimulationDefinition = {
27
27
  /** Select an existing app-authored immutable experiment template. */
28
28
  kind: "experiment";
29
29
  /** Experiment to clone into a fresh attempt. */
30
30
  experimentId: string;
31
+ } | {
32
+ /** Run already published immutable pins, such as a Scenario's frozen case and outcome checks. */
33
+ kind: "pins";
34
+ /** Frozen dataset version whose cases pin their simulated-world versions. */
35
+ datasetVersionId: string;
36
+ /** Immutable scorer versions to pin; Hue-executed pins need no local callback. */
37
+ scorerVersionIds: string[];
38
+ /** JSON configuration passed to every target callback; defaults to `{}`. */
39
+ config?: JsonValue;
40
+ /** Experiment display name; defaults to the dataset name, then `Simulation`. */
41
+ name?: string;
31
42
  } | {
32
43
  /** Publish and resolve the repository-authored definition. */
33
44
  kind: "repository";
@@ -49,6 +49,14 @@ function normalizedEnvironmentDefinition(definition) {
49
49
  function definitionIdentity(definition) {
50
50
  if (definition.kind === "experiment")
51
51
  return definition;
52
+ // The display name is cosmetic; the pins and configuration define the immutable selection.
53
+ if (definition.kind === "pins")
54
+ return json({
55
+ kind: definition.kind,
56
+ datasetVersionId: definition.datasetVersionId,
57
+ scorerVersionIds: [...definition.scorerVersionIds].sort(),
58
+ config: definition.config ?? {},
59
+ });
52
60
  return json({
53
61
  kind: definition.kind,
54
62
  name: definition.name,
@@ -272,6 +280,16 @@ async function resolveDataset(client, scenario, environmentVersionId) {
272
280
  }, "Dataset freeze acknowledgement is unavailable")).id;
273
281
  }
274
282
  }
283
+ /** Display name of the dataset owning a version, or undefined when it cannot be read. */
284
+ async function datasetName(client, versionId) {
285
+ try {
286
+ const version = await client.getDatasetVersion(versionId);
287
+ return (await client.getDataset(version.datasetId)).name;
288
+ }
289
+ catch {
290
+ return undefined;
291
+ }
292
+ }
275
293
  async function allCases(client, versionId) {
276
294
  const items = [];
277
295
  let after;
@@ -295,6 +313,22 @@ async function resolveExperiment(options, definition, idempotencyKey) {
295
313
  });
296
314
  return { experimentId: created.id, bindings: options.localScorers ?? [] };
297
315
  }
316
+ if (definition.kind === "pins") {
317
+ const { datasetVersionId, scorerVersionIds } = definition;
318
+ if (!scorerVersionIds.length)
319
+ throw new TypeError("Pinned scenarios require a scorer version");
320
+ const created = await options.client.createExperiment({
321
+ idempotencyKey,
322
+ name: options.runName ??
323
+ definition.name ??
324
+ (await datasetName(options.client, datasetVersionId)) ??
325
+ "Simulation",
326
+ datasetVersionId,
327
+ scorerVersionIds: [...scorerVersionIds],
328
+ config: definition.config ?? {},
329
+ });
330
+ return { experimentId: created.id, bindings: options.localScorers ?? [] };
331
+ }
298
332
  const environmentVersionId = await resolveEnvironment(options.environmentClient, definition.environment);
299
333
  const datasetVersionId = await resolveDataset(options.client, definition, environmentVersionId);
300
334
  const scorers = await resolveScorers(options.client, definition.scorers);
@@ -320,6 +354,8 @@ export async function runSimulation(options) {
320
354
  throw new TypeError("runSimulation requires a definition");
321
355
  if (options.definition && options.scenario && options.definition !== options.scenario)
322
356
  throw new TypeError("Pass either definition or scenario, not both");
357
+ if (definition.kind === "pins" && !definition.scorerVersionIds.length)
358
+ throw new TypeError("Pinned scenarios require a scorer version");
323
359
  if (!options.definition && !scenarioDeprecationWarned) {
324
360
  scenarioDeprecationWarned = true;
325
361
  process.emitWarning("runSimulation option scenario is deprecated; use definition", "DeprecationWarning");
@@ -76,10 +76,32 @@ export interface DatasetCase {
76
76
  /** Immutable input-file manifest identity, when files are attached. */
77
77
  artifactManifestId?: string | null;
78
78
  }
79
+ /** One pinned input file of a case or subject, as recorded in Hue's immutable manifest. */
80
+ export interface CaseFile {
81
+ /** Hue artifact identity of the pinned bytes. */
82
+ artifactId: string;
83
+ /** How the file relates to the case; `org_template` is evaluator-only. */
84
+ role: "source" | "attached_template" | "attached_reference" | "original" | "org_template" | "evaluator_reference";
85
+ /** Declared file name. */
86
+ filename: string;
87
+ /** Declared content type. */
88
+ contentType: string;
89
+ /** Verified size in bytes. */
90
+ byteSize: number;
91
+ /** Verified SHA-256, hex encoded. */
92
+ sha256: string;
93
+ }
94
+ /** A frozen manifest entry of a subject: the case inputs plus the outputs the target produced. */
95
+ export type SubjectFile = Omit<CaseFile, "role"> & {
96
+ /** Input role, or `output` for a file the target generated. */
97
+ role: CaseFile["role"] | "output";
98
+ };
79
99
  /** A frozen case as an experiment sees it. */
80
100
  export interface ExperimentCase extends DatasetCase {
81
101
  /** Whether a reference output is stored; JSON `null` counts as present. */
82
102
  hasExpected: boolean;
103
+ /** Pinned input files, present on current servers when the case has a manifest. */
104
+ inputFiles?: CaseFile[];
83
105
  }
84
106
  /** Input for {@link EvaluationClient.addCase}. */
85
107
  export interface CaseWrite {
@@ -372,6 +394,17 @@ export interface EvaluationRun {
372
394
  pending: number;
373
395
  };
374
396
  }
397
+ /** One row of the project's evaluation-run listing; `getEvaluationRun` reads the pins and score counts. */
398
+ export interface EvaluationRunSummary {
399
+ /** Run ID. */
400
+ id: string;
401
+ /** Display name. */
402
+ name: string;
403
+ /** Experiment whose default run this is, or `null` for a standalone scoring run. */
404
+ experimentId: string | null;
405
+ /** Creation time. */
406
+ createdAt: string;
407
+ }
375
408
  /** An experiment: a frozen dataset version, a configuration and pinned scorers. */
376
409
  export interface Experiment {
377
410
  /** Experiment ID. */
@@ -432,6 +465,10 @@ export interface CompleteExecution {
432
465
  state: TerminalState;
433
466
  /** Target output; omit when unavailable. */
434
467
  output?: JsonValue;
468
+ /** Verified artifacts the target generated; the manifest freezes them with the case inputs. */
469
+ artifactIds?: string[];
470
+ /** The declared primary generated artifact, one of `artifactIds`. */
471
+ primaryArtifactId?: string;
435
472
  /** Sanitized failure for `state: "error"`. */
436
473
  error?: TypedError;
437
474
  /** Trace revision the stored snapshot must have reached. */
@@ -505,6 +542,12 @@ export interface Subject {
505
542
  traceExternalId: string | null;
506
543
  /** Reason trace evidence was omitted, or `null`. */
507
544
  omissionReason: string | null;
545
+ /** Present on current servers: the case's pinned world, or null for an ordinary case. */
546
+ environmentVersionId?: string | null;
547
+ /** Present on current servers: the frozen input and output files of this subject. */
548
+ files?: SubjectFile[];
549
+ /** The target's declared primary generated artifact, or `null`. */
550
+ primaryArtifactId?: string | null;
508
551
  }
509
552
  /** A reported metric value. */
510
553
  export interface Metric {
@@ -563,7 +606,62 @@ export interface ScoreContext {
563
606
  executionState: TerminalState;
564
607
  /** Authoritative sealed world and complete journal, when required by the runner. */
565
608
  environment?: EnvironmentEvidence;
566
- }
609
+ /** Pinned input files and the target's generated files, verified and saved on this machine.
610
+ * Present only when the runner handled files for this execution. */
611
+ files?: LocalFile[];
612
+ }
613
+ /** A verified copy of a case input or generated output on the runner's disk. */
614
+ export interface LocalFile {
615
+ /** Hue artifact identity; generated files receive theirs after upload. */
616
+ artifactId: string;
617
+ /** Input role, or `output` for a file the target generated. */
618
+ role: CaseFile["role"] | "output";
619
+ /** File name. */
620
+ filename: string;
621
+ /** Content type. */
622
+ contentType: string;
623
+ /** Verified size in bytes. */
624
+ byteSize: number;
625
+ /** Verified SHA-256, hex encoded. */
626
+ sha256: string;
627
+ /** Absolute path of the verified bytes on this machine. */
628
+ path: string;
629
+ /** Whether this is the execution's primary generated document. */
630
+ primary?: boolean;
631
+ }
632
+ /** A file the target generated for one case. Bytes are read from `path` or taken from `bytes`. */
633
+ export type OutputFile = {
634
+ /** File name Hue stores; sanitized to one path segment. */
635
+ filename: string;
636
+ /** One of the accepted generated content types. */
637
+ contentType: string;
638
+ /** The declared primary document; at most one per execution. */
639
+ primary?: boolean;
640
+ } & ({
641
+ /** Path of the generated file on this machine. */
642
+ path: string;
643
+ /** Not used when `path` is given. */
644
+ bytes?: undefined;
645
+ } | {
646
+ /** Generated bytes held in memory. */
647
+ bytes: Uint8Array;
648
+ /** Not used when `bytes` is given. */
649
+ path?: undefined;
650
+ });
651
+ /** A target's saved outcome when it produced files. Create it with `withFiles`. */
652
+ export declare class TargetResult {
653
+ /** JSON output of the target, or `undefined` when it produced only files. */
654
+ readonly output: JsonValue | undefined;
655
+ /** Generated files to save with the execution. */
656
+ readonly files: OutputFile[];
657
+ constructor(
658
+ /** JSON output of the target, or `undefined` when it produced only files. */
659
+ output: JsonValue | undefined,
660
+ /** Generated files to save with the execution. */
661
+ files: OutputFile[]);
662
+ }
663
+ /** Return this from a target to save generated files with the execution. */
664
+ export declare function withFiles(output: JsonValue | undefined, files: OutputFile[]): TargetResult;
567
665
  /** Sealed environment evidence resolved through one target execution. */
568
666
  export interface EnvironmentEvidenceSnapshot extends EnvironmentCoverage {
569
667
  /** Environment-run identity. */
@@ -601,6 +699,40 @@ export interface LocalScorer {
601
699
  /** Trusted local code. There is no callback timeout or side-effect cancellation. */
602
700
  score(context: ScoreContext): Score | Promise<Score>;
603
701
  }
702
+ /** An artifact reservation as Hue reports it through the reserve, upload and complete steps. */
703
+ export interface ArtifactReservation {
704
+ /** Artifact ID. */
705
+ id: string;
706
+ /** Declared file name. */
707
+ filename: string;
708
+ /** Declared content type. */
709
+ declaredContentType: string;
710
+ /** Declared size in bytes. */
711
+ declaredBytes: number;
712
+ /** Declared SHA-256, hex encoded. */
713
+ declaredSha256: string;
714
+ /** Lifecycle state; only `ready` artifacts are verified and downloadable. */
715
+ state: "reserved" | "verifying" | "ready" | "rejected" | "cancelled" | "abandoned";
716
+ /** Progress of the byte copy into Hue storage. */
717
+ copyState: "none" | "started" | "acknowledged";
718
+ /** Verified size once ready, otherwise `null`. */
719
+ verifiedBytes: number | null;
720
+ /** Verified SHA-256 once ready, otherwise `null`. */
721
+ verifiedSha256: string | null;
722
+ /** Why verification failed, or `null`. */
723
+ failureCode: string | null;
724
+ }
725
+ /** A short-lived storage capability for staging one artifact's bytes. */
726
+ export interface ArtifactUpload {
727
+ /** Storage URL that accepts the bytes; the Hue key is never sent there. */
728
+ uploadUrl: string;
729
+ /** HTTP method the capability accepts. */
730
+ method: "PUT";
731
+ /** Headers to send with the bytes; omitted or null means the file content-type only. */
732
+ headers?: Record<string, string> | null;
733
+ /** Capability expiry as an ISO timestamp. */
734
+ expiresAt: string;
735
+ }
604
736
  /** Short-lived execution-scoped MCP connection for one simulated world. */
605
737
  export interface SimulationMcpCapability {
606
738
  /** HTTPS MCP endpoint. */
@@ -641,3 +773,44 @@ export interface LocalAgentClaim {
641
773
  /** Pinned experiment to execute. */
642
774
  experimentId: string;
643
775
  }
776
+ /** A Scenario as listed by {@link EvaluationClient.listCaseConversions}. Extra server fields are ignored. */
777
+ export interface CaseConversionSummary {
778
+ /** Scenario ID. */
779
+ id: string;
780
+ /** Scenario domain label. */
781
+ domain: string;
782
+ /** Whether the Scenario has immutable published pins. */
783
+ status: "draft" | "published";
784
+ /** Optimistic-concurrency revision of the Scenario. */
785
+ revision: number;
786
+ /** Creation timestamp. */
787
+ createdAt: string;
788
+ /** Source trace the Scenario was converted from. */
789
+ traceId: string;
790
+ }
791
+ /** Immutable pins created when a Scenario is published. */
792
+ export interface CaseConversionPublication {
793
+ /** Published dataset case. */
794
+ caseId: string;
795
+ /** Dataset holding the published case. */
796
+ datasetId: string;
797
+ /** Frozen dataset version holding the published case. */
798
+ datasetVersionId: string;
799
+ /** Environment identity of the simulated world. */
800
+ environmentId: string;
801
+ /** Immutable environment version the case pins. */
802
+ environmentVersionId: string;
803
+ /** Scorer identity of the published outcome checks. */
804
+ scorerId: string;
805
+ /** Immutable scorer version pinned by the Scenario. */
806
+ scorerVersionId: string;
807
+ }
808
+ /** A Scenario read by {@link EvaluationClient.getCaseConversion}. Extra server fields are ignored. */
809
+ export interface CaseConversion extends Partial<Omit<CaseConversionSummary, "id" | "status">> {
810
+ /** Scenario ID. */
811
+ id: string;
812
+ /** Whether the Scenario has immutable published pins. */
813
+ status: "draft" | "published";
814
+ /** Published pins, or `null` while the Scenario is a draft. */
815
+ publication: CaseConversionPublication | null;
816
+ }
@@ -1 +1,17 @@
1
- export {};
1
+ /** A target's saved outcome when it produced files. Create it with `withFiles`. */
2
+ export class TargetResult {
3
+ output;
4
+ files;
5
+ constructor(
6
+ /** JSON output of the target, or `undefined` when it produced only files. */
7
+ output,
8
+ /** Generated files to save with the execution. */
9
+ files) {
10
+ this.output = output;
11
+ this.files = files;
12
+ }
13
+ }
14
+ /** Return this from a target to save generated files with the execution. */
15
+ export function withFiles(output, files) {
16
+ return new TargetResult(output, files);
17
+ }