@hue-run/sdk 0.1.5 → 0.2.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.
Files changed (56) hide show
  1. package/ENVIRONMENTS.md +182 -0
  2. package/EVALUATIONS.md +12 -0
  3. package/README.md +192 -18
  4. package/dist/ai-sdk.d.ts +9 -1
  5. package/dist/ai-sdk.js +34 -8
  6. package/dist/client.d.ts +121 -6
  7. package/dist/client.js +329 -56
  8. package/dist/config.d.ts +11 -2
  9. package/dist/config.js +36 -7
  10. package/dist/environment/client.d.ts +73 -0
  11. package/dist/environment/client.js +209 -0
  12. package/dist/environment/tools.d.ts +30 -0
  13. package/dist/environment/tools.js +24 -0
  14. package/dist/environment/types.d.ts +429 -0
  15. package/dist/environment/types.js +1 -0
  16. package/dist/environment.d.ts +5 -0
  17. package/dist/environment.js +2 -0
  18. package/dist/evals/attempt.d.ts +454 -0
  19. package/dist/evals/attempt.js +687 -0
  20. package/dist/evals/client.d.ts +99 -5
  21. package/dist/evals/client.js +136 -7
  22. package/dist/evals/environment-evidence.d.ts +6 -0
  23. package/dist/evals/environment-evidence.js +123 -0
  24. package/dist/evals/environment-json.d.ts +3 -0
  25. package/dist/evals/environment-json.js +76 -0
  26. package/dist/evals/json.d.ts +9 -1
  27. package/dist/evals/json.js +14 -6
  28. package/dist/evals/runner.d.ts +61 -2
  29. package/dist/evals/runner.js +71 -9
  30. package/dist/evals/scorer-publication.d.ts +2 -0
  31. package/dist/evals/scorer-publication.js +84 -0
  32. package/dist/evals/scorers.d.ts +11 -0
  33. package/dist/evals/scorers.js +56 -5
  34. package/dist/evals/simulation.d.ts +184 -0
  35. package/dist/evals/simulation.js +603 -0
  36. package/dist/evals/types.d.ts +304 -0
  37. package/dist/evals.d.ts +5 -1
  38. package/dist/evals.js +3 -1
  39. package/dist/experimental-telemetry.d.ts +8 -0
  40. package/dist/experimental-telemetry.js +13 -0
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.js +2 -0
  43. package/dist/managed.d.ts +51 -1
  44. package/dist/managed.js +11 -1
  45. package/dist/privacy.d.ts +2 -0
  46. package/dist/privacy.js +16 -1
  47. package/dist/receipt.d.ts +12 -1
  48. package/dist/receipt.js +10 -1
  49. package/dist/safety.d.ts +1 -2
  50. package/dist/snapshot.js +4 -0
  51. package/dist/transport.d.ts +41 -9
  52. package/dist/transport.js +80 -22
  53. package/dist/types.d.ts +144 -8
  54. package/dist/version.d.ts +2 -0
  55. package/dist/version.js +3 -0
  56. package/package.json +51 -15
@@ -1,41 +1,73 @@
1
1
  import type { ProjectConnection } from "../types.js";
2
- import type { CaseWrite, CompleteExecution, Completion, Dataset, DatasetCase, DatasetVersion, EvaluationItem, EvaluationRun, Execution, Experiment, ExperimentCase, ExperimentItem, Identity, JsonValue, JudgeBudget, JudgeJob, Page, PageOptions, Result, ResultSummary, Scorer, ScorerDefinition, ScorerVersion, StartExecution, Subject, StoredResult } from "./types.js";
2
+ import { type AttemptConnectionBundleV2, type PrepareAttemptRequestV2 } from "./attempt.js";
3
+ import type { CaseWrite, CompleteExecution, Completion, Dataset, DatasetCase, DatasetVersion, EvaluationItem, EvaluationRun, EnvironmentEvidenceSnapshot, Execution, Experiment, ExperimentCase, ExperimentItem, Identity, JsonValue, JudgeBudget, JudgeJob, Page, PageOptions, RegistryPageOptions, Result, ResultSummary, Scorer, ScorerDefinition, ScorerVersion, SimulationMcpCapability, StartExecution, Subject, StoredResult } from "./types.js";
4
+ /** Connection options for {@link createEvaluationClient}. */
3
5
  export interface EvaluationClientOptions {
6
+ /** Project service key sent as a Bearer token; server side only. */
4
7
  apiKey: string;
8
+ /** Hue origin, `https://app.hue.run` by default; HTTPS except for loopback. */
5
9
  baseUrl?: string;
10
+ /** Per-request budget in milliseconds, 100–60000. Default 10000. */
6
11
  timeoutMillis?: number;
7
12
  }
13
+ /** Thrown for a failed evaluation API request; the message is fixed and never includes response text. */
8
14
  export declare class HueApiError extends Error {
15
+ /** HTTP status when Hue answered; absent for network, timeout and parsing failures. */
9
16
  readonly status?: number | undefined;
10
- constructor(status?: number | undefined);
17
+ constructor(
18
+ /** HTTP status when Hue answered; absent for network, timeout and parsing failures. */
19
+ status?: number | undefined);
11
20
  }
12
- /** No implicit mutation retry: callers retain stable idempotency keys for experiments/results. */
21
+ /**
22
+ * Typed client for Hue's evaluation REST API: datasets, scorers, experiments, executions, runs,
23
+ * results and hosted judge jobs. No implicit mutation retry: callers retain stable idempotency keys
24
+ * for experiments and results. Responses are bounded to 4 MiB.
25
+ */
13
26
  export declare class EvaluationClient {
27
+ /** Validated Hue origin. */
14
28
  readonly baseUrl: string;
15
29
  private readonly apiKey;
16
30
  private readonly timeoutMillis;
17
31
  constructor(options: EvaluationClientOptions);
18
32
  private request;
19
33
  private page;
34
+ private registryPage;
35
+ /** Reads the current project to confirm the key and origin. */
20
36
  checkConnection(): Promise<ProjectConnection>;
37
+ /** Creates a dataset with an initial draft version. */
21
38
  createDataset(input: Identity): Promise<Dataset>;
39
+ /** Reads a dataset and its versions. */
22
40
  getDataset(id: string): Promise<Dataset>;
23
- listDatasets(page?: PageOptions): Promise<Page<Omit<Dataset, "versions">>>;
41
+ /** Lists datasets without their versions. */
42
+ listDatasets(page?: RegistryPageOptions): Promise<Page<Omit<Dataset, "versions">>>;
43
+ /** Creates a new draft version, optionally copying cases from an existing version. */
24
44
  createDatasetVersion(id: string, input?: {
25
45
  fromVersionId?: string;
26
46
  }): Promise<DatasetVersion>;
47
+ /** Reads a dataset version. */
27
48
  getDatasetVersion(id: string): Promise<DatasetVersion>;
49
+ /** Lists the full cases of a dataset version; use small page limits for large values. */
28
50
  listCases(id: string, page?: PageOptions): Promise<Page<DatasetCase>>;
51
+ /** Adds a case to a draft version using optimistic concurrency on `expectedRevision`. */
29
52
  addCase(id: string, input: CaseWrite): Promise<{
53
+ /** The stored case. */
30
54
  item: DatasetCase;
55
+ /** The version with its new revision. */
31
56
  version: DatasetVersion;
32
57
  }>;
58
+ /** Freezes a draft version at the given revision; frozen versions are immutable. */
33
59
  freezeDatasetVersion(id: string, expectedRevision: number): Promise<DatasetVersion>;
60
+ /** Creates a scorer identity; publish definitions with {@link publishScorerVersion}. */
34
61
  createScorer(input: Identity): Promise<Scorer>;
62
+ /** Reads a scorer and its published versions. */
35
63
  getScorer(id: string): Promise<Scorer>;
36
- listScorers(page?: PageOptions): Promise<Page<Scorer>>;
64
+ /** Lists scorers. */
65
+ listScorers(page?: RegistryPageOptions): Promise<Page<Scorer>>;
66
+ /** Publishes an immutable scorer version; the server validates the pinned definition. */
37
67
  publishScorerVersion(id: string, definition: ScorerDefinition): Promise<ScorerVersion>;
68
+ /** Reads a published scorer version. */
38
69
  getScorerVersion(id: string): Promise<ScorerVersion>;
70
+ /** Creates an experiment over a frozen dataset version with pinned scorer versions and a configuration. */
39
71
  createExperiment(input: {
40
72
  idempotencyKey: string;
41
73
  name: string;
@@ -43,38 +75,82 @@ export declare class EvaluationClient {
43
75
  scorerVersionIds: string[];
44
76
  config: JsonValue;
45
77
  }): Promise<{
78
+ /** Experiment ID. */
46
79
  id: string;
80
+ /** ID of the experiment's evaluation run. */
47
81
  evaluationRunId: string;
48
82
  }>;
83
+ /** Reads an experiment with its evaluation run and execution counts. */
49
84
  getExperiment(id: string): Promise<Experiment>;
85
+ /** Lists an experiment's cases with their latest executions. */
50
86
  listExperimentItems(id: string, page?: PageOptions): Promise<Page<ExperimentItem>>;
87
+ /** Reads one frozen case of an experiment. */
51
88
  getExperimentCase(id: string, caseId: string): Promise<ExperimentCase>;
89
+ /** Starts (or, with the same key, replays) a target execution for a case. */
52
90
  startExecution(id: string, caseId: string, input: StartExecution): Promise<Execution>;
91
+ /** Reads an execution. */
53
92
  getExecution(id: string): Promise<Execution>;
93
+ /** Reads the sealed environment evidence linked to an execution. */
94
+ getEnvironmentEvidence(executionId: string): Promise<EnvironmentEvidenceSnapshot>;
95
+ /** Pages the sealed environment journal linked to an execution. */
96
+ getEnvironmentSteps(executionId: string, page?: {
97
+ after?: number;
98
+ limit?: number;
99
+ }): Promise<import("../environment/types.js").StepPage>;
100
+ /**
101
+ * Prepares the immutable provider-profile binding for one execution. The route identity is
102
+ * removed from the JSON body, and credential-bearing responses are validated against the
103
+ * request before being returned.
104
+ */
105
+ prepareAttempt(input: PrepareAttemptRequestV2): Promise<import("./attempt.js").PrepareAttemptResultV2>;
106
+ /** Reads coupled, secret-free V1 or V2 binding evidence; it never reacquires credentials. */
107
+ getAttemptBinding(bindingId: string): Promise<import("./attempt.js").AttemptBindingRead>;
108
+ /** Rotates an unexpired V2 connection while preserving its immutable binding evidence. */
109
+ refreshAttemptConnection(previous: AttemptConnectionBundleV2, input: {
110
+ idempotencyKey: string;
111
+ }): Promise<import("./attempt.js").PrepareAttemptReadyV2>;
112
+ /** Revokes an attempt binding. A revoked connection must not be reused or refreshed. */
113
+ revokeAttemptConnection(input: {
114
+ bindingId: string;
115
+ }): Promise<import("./attempt.js").RevokeAttemptResult>;
116
+ /** Saves an execution's outcome and creates its immutable subject. */
54
117
  completeExecution(id: string, input: CompleteExecution): Promise<Completion>;
118
+ /** Marks an experiment finished. */
55
119
  finishExperiment(id: string, idempotencyKey: string): Promise<{
120
+ /** Experiment ID. */
56
121
  id: string;
122
+ /** When it was finished. */
57
123
  finishedAt: string;
58
124
  }>;
125
+ /** Creates a historical evaluation run that rescores existing subjects with pinned scorer versions. */
59
126
  createEvaluationRun(input: {
60
127
  idempotencyKey: string;
61
128
  name: string;
62
129
  subjectIds: string[];
63
130
  scorerVersionIds: string[];
64
131
  }): Promise<{
132
+ /** Evaluation run ID. */
65
133
  id: string;
66
134
  }>;
135
+ /** Reads an evaluation run and its scoring progress. */
67
136
  getEvaluationRun(id: string): Promise<EvaluationRun>;
137
+ /** Lists the subjects of an evaluation run. */
68
138
  listEvaluationItems(id: string, page?: PageOptions): Promise<Page<EvaluationItem>>;
139
+ /** Reads an immutable subject, including output and reference when available. */
69
140
  getSubject(id: string): Promise<Subject>;
141
+ /** Uploads scorer results for an evaluation run; a replayed key returns the same IDs. */
70
142
  submitResults(id: string, input: {
71
143
  idempotencyKey: string;
72
144
  results: Result[];
73
145
  }): Promise<{
146
+ /** Stored result IDs, in input order. */
74
147
  ids: string[];
75
148
  }>;
149
+ /** Lists result summaries of an evaluation run. */
76
150
  listResults(id: string, page?: PageOptions): Promise<Page<ResultSummary>>;
151
+ /** Reads a full stored result. */
77
152
  getResult(id: string): Promise<StoredResult>;
153
+ /** Dispatches hosted judge jobs for `llm_judge` pins; check {@link getJudgeBudget} first. */
78
154
  createJudgeJobs(id: string, input: {
79
155
  idempotencyKey: string;
80
156
  jobs: {
@@ -82,15 +158,33 @@ export declare class EvaluationClient {
82
158
  scorerVersionId: string;
83
159
  }[];
84
160
  }): Promise<{
161
+ /** Created job IDs, in input order. */
85
162
  ids: string[];
86
163
  }>;
164
+ /** Lists hosted judge jobs of an evaluation run. */
87
165
  listJudgeJobs(id: string, page?: PageOptions): Promise<Page<JudgeJob>>;
166
+ /** Reads a hosted judge job, including its charge accounting. */
88
167
  getJudgeJob(id: string): Promise<JudgeJob>;
168
+ /** Requests cancellation of a hosted judge job. */
89
169
  cancelJudgeJob(id: string, reason: string): Promise<{
170
+ /** Job ID. */
90
171
  id: string;
172
+ /** Job state after the request. */
91
173
  state: JudgeJob["state"];
174
+ /** Whether a cancellation request was recorded. */
92
175
  cancellationRequested?: boolean;
93
176
  }>;
177
+ /** Reads the project's hosted judge budget and admission controls. */
94
178
  getJudgeBudget(): Promise<JudgeBudget>;
179
+ /** Creates the legacy execution-scoped generic MCP capability for one world. */
180
+ createSimulationMcpCapability(input: {
181
+ runId: string;
182
+ executionId: string;
183
+ }): Promise<SimulationMcpCapability>;
95
184
  }
185
+ /**
186
+ * Creates an {@link EvaluationClient}.
187
+ *
188
+ * @throws TypeError for an invalid key, origin or budget.
189
+ */
96
190
  export declare function createEvaluationClient(options: EvaluationClientOptions): EvaluationClient;
@@ -1,15 +1,24 @@
1
1
  import { validateOptions } from "../config.js";
2
- import { json, uuid } from "./json.js";
2
+ import { json, uuid, valueBounds } from "./json.js";
3
+ import { attemptBindingRead, parsePrepareAttemptResultV2, parseRefreshedAttemptResultV2, parseRevocationResult, prepareAttemptInputV2, validateAttemptConnectionBundleV2, } from "./attempt.js";
4
+ /** Thrown for a failed evaluation API request; the message is fixed and never includes response text. */
3
5
  export class HueApiError extends Error {
4
6
  status;
5
- constructor(status) {
7
+ constructor(
8
+ /** HTTP status when Hue answered; absent for network, timeout and parsing failures. */
9
+ status) {
6
10
  super(status ? `Hue API request failed (HTTP ${status})` : "Hue API connection or response failed");
7
11
  this.status = status;
8
12
  this.name = "HueApiError";
9
13
  }
10
14
  }
11
- /** No implicit mutation retry: callers retain stable idempotency keys for experiments/results. */
15
+ /**
16
+ * Typed client for Hue's evaluation REST API: datasets, scorers, experiments, executions, runs,
17
+ * results and hosted judge jobs. No implicit mutation retry: callers retain stable idempotency keys
18
+ * for experiments and results. Responses are bounded to 4 MiB.
19
+ */
12
20
  export class EvaluationClient {
21
+ /** Validated Hue origin. */
13
22
  baseUrl;
14
23
  apiKey;
15
24
  timeoutMillis;
@@ -23,11 +32,11 @@ export class EvaluationClient {
23
32
  this.apiKey = validated.apiKey;
24
33
  this.timeoutMillis = validated.timeoutMillis;
25
34
  }
26
- async request(method, path, body) {
35
+ async request(method, path, body, bounds = { ...valueBounds, bytes: 1024 * 1024 }) {
27
36
  // Optional top-level fields are omitted intentionally; nested undefined remains invalid.
28
37
  const payload = body === undefined
29
38
  ? undefined
30
- : JSON.stringify(json(Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined)), 1024 * 1024));
39
+ : JSON.stringify(json(Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined)), bounds));
31
40
  let response;
32
41
  try {
33
42
  response = await fetch(`${this.baseUrl}/api/v1${path}`, {
@@ -85,111 +94,231 @@ export class EvaluationClient {
85
94
  }
86
95
  return query.size ? `?${query}` : "";
87
96
  }
97
+ registryPage(options = {}) {
98
+ const query = new URLSearchParams(this.page(options).slice(1));
99
+ if (options.includeArchived !== undefined)
100
+ query.set("includeArchived", String(options.includeArchived));
101
+ return query.size ? `?${query}` : "";
102
+ }
103
+ /** Reads the current project to confirm the key and origin. */
88
104
  checkConnection() {
89
105
  return this.request("GET", "/projects/current");
90
106
  }
107
+ /** Creates a dataset with an initial draft version. */
91
108
  createDataset(input) {
92
109
  return this.request("POST", "/datasets", input);
93
110
  }
111
+ /** Reads a dataset and its versions. */
94
112
  getDataset(id) {
95
113
  return this.request("GET", `/datasets/${uuid(id)}`);
96
114
  }
115
+ /** Lists datasets without their versions. */
97
116
  listDatasets(page) {
98
- return this.request("GET", `/datasets${this.page(page)}`);
117
+ return this.request("GET", `/datasets${this.registryPage(page)}`);
99
118
  }
119
+ /** Creates a new draft version, optionally copying cases from an existing version. */
100
120
  createDatasetVersion(id, input = {}) {
101
121
  return this.request("POST", `/datasets/${uuid(id)}/versions`, input);
102
122
  }
123
+ /** Reads a dataset version. */
103
124
  getDatasetVersion(id) {
104
125
  return this.request("GET", `/dataset-versions/${uuid(id)}`);
105
126
  }
127
+ /** Lists the full cases of a dataset version; use small page limits for large values. */
106
128
  listCases(id, page) {
107
129
  return this.request("GET", `/dataset-versions/${uuid(id)}/cases${this.page(page)}`);
108
130
  }
131
+ /** Adds a case to a draft version using optimistic concurrency on `expectedRevision`. */
109
132
  addCase(id, input) {
110
133
  return this.request("POST", `/dataset-versions/${uuid(id)}/cases`, input);
111
134
  }
135
+ /** Freezes a draft version at the given revision; frozen versions are immutable. */
112
136
  freezeDatasetVersion(id, expectedRevision) {
113
137
  return this.request("POST", `/dataset-versions/${uuid(id)}/freeze`, {
114
138
  expectedRevision,
115
139
  });
116
140
  }
141
+ /** Creates a scorer identity; publish definitions with {@link publishScorerVersion}. */
117
142
  createScorer(input) {
118
143
  return this.request("POST", "/scorers", input);
119
144
  }
145
+ /** Reads a scorer and its published versions. */
120
146
  getScorer(id) {
121
147
  return this.request("GET", `/scorers/${uuid(id)}`);
122
148
  }
149
+ /** Lists scorers. */
123
150
  listScorers(page) {
124
- return this.request("GET", `/scorers${this.page(page)}`);
151
+ return this.request("GET", `/scorers${this.registryPage(page)}`);
125
152
  }
153
+ /** Publishes an immutable scorer version; the server validates the pinned definition. */
126
154
  publishScorerVersion(id, definition) {
127
155
  return this.request("POST", `/scorers/${uuid(id)}/versions`, { definition });
128
156
  }
157
+ /** Reads a published scorer version. */
129
158
  getScorerVersion(id) {
130
159
  return this.request("GET", `/scorer-versions/${uuid(id)}`);
131
160
  }
161
+ /** Creates an experiment over a frozen dataset version with pinned scorer versions and a configuration. */
132
162
  createExperiment(input) {
133
163
  return this.request("POST", "/experiments", input);
134
164
  }
165
+ /** Reads an experiment with its evaluation run and execution counts. */
135
166
  getExperiment(id) {
136
167
  return this.request("GET", `/experiments/${uuid(id)}`);
137
168
  }
169
+ /** Lists an experiment's cases with their latest executions. */
138
170
  listExperimentItems(id, page) {
139
171
  return this.request("GET", `/experiments/${uuid(id)}/items${this.page(page)}`);
140
172
  }
173
+ /** Reads one frozen case of an experiment. */
141
174
  getExperimentCase(id, caseId) {
142
175
  return this.request("GET", `/experiments/${uuid(id)}/items/${uuid(caseId)}`);
143
176
  }
177
+ /** Starts (or, with the same key, replays) a target execution for a case. */
144
178
  startExecution(id, caseId, input) {
145
179
  return this.request("POST", `/experiments/${uuid(id)}/items/${uuid(caseId)}/start`, input);
146
180
  }
181
+ /** Reads an execution. */
147
182
  getExecution(id) {
148
183
  return this.request("GET", `/experiment-executions/${uuid(id)}`);
149
184
  }
185
+ /** Reads the sealed environment evidence linked to an execution. */
186
+ getEnvironmentEvidence(executionId) {
187
+ return this.request("GET", `/experiment-executions/${uuid(executionId)}/environment`);
188
+ }
189
+ /** Pages the sealed environment journal linked to an execution. */
190
+ getEnvironmentSteps(executionId, page = {}) {
191
+ if (page.after !== undefined && (!Number.isInteger(page.after) || page.after < -1))
192
+ throw new RangeError("Step cursor must be an integer at least -1");
193
+ if (page.limit !== undefined &&
194
+ (!Number.isInteger(page.limit) || page.limit < 1 || page.limit > 100))
195
+ throw new RangeError("Step page size must be 1–100");
196
+ const query = new URLSearchParams();
197
+ if (page.after !== undefined)
198
+ query.set("after", String(page.after));
199
+ if (page.limit !== undefined)
200
+ query.set("limit", String(page.limit));
201
+ return this.request("GET", `/experiment-executions/${uuid(executionId)}/environment/steps${query.size ? `?${query}` : ""}`);
202
+ }
203
+ /**
204
+ * Prepares the immutable provider-profile binding for one execution. The route identity is
205
+ * removed from the JSON body, and credential-bearing responses are validated against the
206
+ * request before being returned.
207
+ */
208
+ async prepareAttempt(input) {
209
+ const request = prepareAttemptInputV2.parse(input);
210
+ const { executionId, ...body } = request;
211
+ const response = await this.request("POST", `/experiment-executions/${executionId}/prepare-attempt`, body, { ...valueBounds, bytes: 128_000 });
212
+ try {
213
+ return parsePrepareAttemptResultV2(response, request);
214
+ }
215
+ catch {
216
+ // A malformed success response may follow a committed decision. Never expose
217
+ // credential-bearing response details or imply that replay is automatically safe.
218
+ throw new HueApiError();
219
+ }
220
+ }
221
+ /** Reads coupled, secret-free V1 or V2 binding evidence; it never reacquires credentials. */
222
+ async getAttemptBinding(bindingId) {
223
+ const response = await this.request("GET", `/attempt-bindings/${uuid(bindingId)}`);
224
+ try {
225
+ return attemptBindingRead.parse(response);
226
+ }
227
+ catch {
228
+ throw new HueApiError();
229
+ }
230
+ }
231
+ /** Rotates an unexpired V2 connection while preserving its immutable binding evidence. */
232
+ async refreshAttemptConnection(previous, input) {
233
+ const source = validateAttemptConnectionBundleV2(previous);
234
+ const response = await this.request("POST", `/attempt-bindings/${source.bindingId}/refresh`, {
235
+ idempotencyKey: uuid(input.idempotencyKey),
236
+ expectedGeneration: source.credentialGeneration,
237
+ }, { ...valueBounds, bytes: 128_000 });
238
+ try {
239
+ return parseRefreshedAttemptResultV2(response, source);
240
+ }
241
+ catch {
242
+ throw new HueApiError();
243
+ }
244
+ }
245
+ /** Revokes an attempt binding. A revoked connection must not be reused or refreshed. */
246
+ async revokeAttemptConnection(input) {
247
+ const bindingId = uuid(input.bindingId);
248
+ const response = await this.request("POST", `/attempt-bindings/${bindingId}/revoke`, {}, { ...valueBounds, bytes: 128_000 });
249
+ try {
250
+ return parseRevocationResult(response, bindingId);
251
+ }
252
+ catch {
253
+ throw new HueApiError();
254
+ }
255
+ }
256
+ /** Saves an execution's outcome and creates its immutable subject. */
150
257
  completeExecution(id, input) {
151
258
  return this.request("POST", `/experiment-executions/${uuid(id)}/complete`, input);
152
259
  }
260
+ /** Marks an experiment finished. */
153
261
  finishExperiment(id, idempotencyKey) {
154
262
  return this.request("POST", `/experiments/${uuid(id)}/finish`, { idempotencyKey });
155
263
  }
264
+ /** Creates a historical evaluation run that rescores existing subjects with pinned scorer versions. */
156
265
  createEvaluationRun(input) {
157
266
  return this.request("POST", "/evaluation-runs", input);
158
267
  }
268
+ /** Reads an evaluation run and its scoring progress. */
159
269
  getEvaluationRun(id) {
160
270
  return this.request("GET", `/evaluation-runs/${uuid(id)}`);
161
271
  }
272
+ /** Lists the subjects of an evaluation run. */
162
273
  listEvaluationItems(id, page) {
163
274
  return this.request("GET", `/evaluation-runs/${uuid(id)}/items${this.page(page)}`);
164
275
  }
276
+ /** Reads an immutable subject, including output and reference when available. */
165
277
  getSubject(id) {
166
278
  return this.request("GET", `/evaluation-subjects/${uuid(id)}`);
167
279
  }
280
+ /** Uploads scorer results for an evaluation run; a replayed key returns the same IDs. */
168
281
  submitResults(id, input) {
169
282
  return this.request("POST", `/evaluation-runs/${uuid(id)}/results`, input);
170
283
  }
284
+ /** Lists result summaries of an evaluation run. */
171
285
  listResults(id, page) {
172
286
  return this.request("GET", `/evaluation-runs/${uuid(id)}/results${this.page(page)}`);
173
287
  }
288
+ /** Reads a full stored result. */
174
289
  getResult(id) {
175
290
  return this.request("GET", `/evaluation-results/${uuid(id)}`);
176
291
  }
292
+ /** Dispatches hosted judge jobs for `llm_judge` pins; check {@link getJudgeBudget} first. */
177
293
  createJudgeJobs(id, input) {
178
294
  return this.request("POST", `/evaluation-runs/${uuid(id)}/judge-jobs`, input);
179
295
  }
296
+ /** Lists hosted judge jobs of an evaluation run. */
180
297
  listJudgeJobs(id, page) {
181
298
  return this.request("GET", `/evaluation-runs/${uuid(id)}/judge-jobs${this.page(page)}`);
182
299
  }
300
+ /** Reads a hosted judge job, including its charge accounting. */
183
301
  getJudgeJob(id) {
184
302
  return this.request("GET", `/judge-jobs/${uuid(id)}`);
185
303
  }
304
+ /** Requests cancellation of a hosted judge job. */
186
305
  cancelJudgeJob(id, reason) {
187
306
  return this.request("POST", `/judge-jobs/${uuid(id)}/cancel`, { reason });
188
307
  }
308
+ /** Reads the project's hosted judge budget and admission controls. */
189
309
  getJudgeBudget() {
190
310
  return this.request("GET", "/judge-budget");
191
311
  }
312
+ /** Creates the legacy execution-scoped generic MCP capability for one world. */
313
+ createSimulationMcpCapability(input) {
314
+ return this.request("POST", "/local-agent-worker/mcp-capability", input);
315
+ }
192
316
  }
317
+ /**
318
+ * Creates an {@link EvaluationClient}.
319
+ *
320
+ * @throws TypeError for an invalid key, origin or budget.
321
+ */
193
322
  export function createEvaluationClient(options) {
194
323
  return new EvaluationClient(options);
195
324
  }
@@ -0,0 +1,6 @@
1
+ import type { EvaluationClient } from "./client.js";
2
+ import type { EnvironmentEvidence } from "./types.js";
3
+ export declare const MAX_ENVIRONMENT_STEPS = 500;
4
+ export declare const environmentIncompleteReason = "Environment incomplete: provider behavior is not implemented.";
5
+ export declare function validateEnvironmentEvidence(evidence: EnvironmentEvidence): void;
6
+ export declare function loadEnvironmentEvidence(client: EvaluationClient, executionId: string): Promise<EnvironmentEvidence>;
@@ -0,0 +1,123 @@
1
+ import { setTimeout as wait } from "node:timers/promises";
2
+ import { HueApiError } from "./client.js";
3
+ import { json, uuid, valueBounds } from "./json.js";
4
+ import { environmentJson, validateEvidenceArguments, validateEvidenceWorld, } from "./environment-json.js";
5
+ const maxBytes = 8 * 1024 * 1024;
6
+ const hexDigest = /^[a-f0-9]{64}$/;
7
+ export const MAX_ENVIRONMENT_STEPS = 500;
8
+ async function readEvidence(operation) {
9
+ for (let attempt = 1;; attempt++) {
10
+ try {
11
+ return await operation();
12
+ }
13
+ catch (error) {
14
+ const retryable = error instanceof HueApiError &&
15
+ (error.status === undefined ||
16
+ error.status === 408 ||
17
+ error.status === 429 ||
18
+ error.status >= 500);
19
+ if (!retryable || attempt >= 4)
20
+ throw error;
21
+ await wait(25 * 2 ** (attempt - 1));
22
+ }
23
+ }
24
+ }
25
+ export const environmentIncompleteReason = "Environment incomplete: provider behavior is not implemented.";
26
+ function validateCoverage(evidence) {
27
+ const validity = evidence.validity === undefined ? "not_assessed" : evidence.validity;
28
+ const gap = evidence.coverageGap ?? null;
29
+ if (validity === "not_assessed" && gap === null)
30
+ return;
31
+ if (validity !== "environment_incomplete" || !gap || typeof gap !== "object")
32
+ throw new TypeError("Invalid environment coverage evidence");
33
+ const { args, ...metadata } = gap;
34
+ json(metadata);
35
+ json(args, { ...valueBounds, bytes: 16_000 });
36
+ if (!args ||
37
+ typeof args !== "object" ||
38
+ Array.isArray(args) ||
39
+ Object.keys(gap).sort().join(",") !==
40
+ "args,code,description,operation,provider,reportedAt,reportedBy" ||
41
+ [
42
+ [gap.provider, 128],
43
+ [gap.operation, 256],
44
+ [gap.code, 128],
45
+ [gap.description, 2000],
46
+ ].some(([value, maximum]) => typeof value !== "string" || !value.length || value.length > Number(maximum)) ||
47
+ typeof gap.reportedAt !== "string" ||
48
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(gap.reportedAt) ||
49
+ !Number.isFinite(Date.parse(gap.reportedAt)) ||
50
+ !gap.reportedBy ||
51
+ !["project_key", "user"].includes(gap.reportedBy.kind) ||
52
+ Object.keys(gap.reportedBy).sort().join(",") !== "id,kind")
53
+ throw new TypeError("Invalid environment coverage gap");
54
+ uuid(gap.reportedBy.id);
55
+ }
56
+ export function validateEnvironmentEvidence(evidence) {
57
+ validateCoverage(evidence);
58
+ uuid(evidence.runId);
59
+ uuid(evidence.executionId);
60
+ uuid(evidence.environmentVersionId);
61
+ if (!hexDigest.test(evidence.definitionDigest) ||
62
+ !hexDigest.test(evidence.stateDigest) ||
63
+ !/^[a-f0-9]{32}$/.test(evidence.seed) ||
64
+ !["completed", "abandoned", "expired"].includes(evidence.status) ||
65
+ !Number.isInteger(evidence.stepCount) ||
66
+ evidence.stepCount < 0 ||
67
+ evidence.stepCount > MAX_ENVIRONMENT_STEPS ||
68
+ !Array.isArray(evidence.steps) ||
69
+ evidence.steps.length !== evidence.stepCount)
70
+ throw new TypeError("Invalid sealed environment evidence");
71
+ validateEvidenceWorld(evidence.initialState);
72
+ validateEvidenceWorld(evidence.finalState);
73
+ for (const [ordinal, step] of evidence.steps.entries()) {
74
+ const { args, observation, effects, ...header } = step;
75
+ json(header);
76
+ validateEvidenceArguments(args);
77
+ environmentJson(observation, 256 * 1024, 35, 256 * 1024);
78
+ environmentJson(effects, 256 * 1024, 35, 256 * 1024);
79
+ if (step.ordinal !== ordinal || !hexDigest.test(step.stateDigest))
80
+ throw new TypeError("Environment history is incomplete or unordered");
81
+ }
82
+ if (evidence.steps.length && evidence.steps.at(-1).stateDigest !== evidence.stateDigest)
83
+ throw new TypeError("Environment history does not end at the sealed state");
84
+ if (Buffer.byteLength(JSON.stringify(evidence)) > maxBytes)
85
+ throw new RangeError("Environment evidence exceeds 8 MiB; no history was truncated");
86
+ }
87
+ export async function loadEnvironmentEvidence(client, executionId) {
88
+ const snapshot = await readEvidence(() => client.getEnvironmentEvidence(executionId));
89
+ if (snapshot.executionId !== executionId)
90
+ throw new TypeError("Environment execution differs");
91
+ if (!Number.isInteger(snapshot.stepCount) ||
92
+ snapshot.stepCount < 0 ||
93
+ snapshot.stepCount > MAX_ENVIRONMENT_STEPS)
94
+ throw new TypeError("Invalid environment step count");
95
+ const evidence = {
96
+ validity: "not_assessed",
97
+ coverageGap: null,
98
+ ...snapshot,
99
+ steps: [],
100
+ };
101
+ let size = Buffer.byteLength(JSON.stringify(evidence));
102
+ let after;
103
+ for (;;) {
104
+ const page = await readEvidence(() => client.getEnvironmentSteps(executionId, { after, limit: 5 }));
105
+ if (!Array.isArray(page.items))
106
+ throw new TypeError("Invalid environment history page");
107
+ for (const step of page.items) {
108
+ if (step.ordinal !== evidence.steps.length || evidence.steps.length >= snapshot.stepCount)
109
+ throw new TypeError("Environment history is incomplete or unordered");
110
+ size += Buffer.byteLength(JSON.stringify(step));
111
+ if (size > maxBytes)
112
+ throw new RangeError("Environment evidence exceeds 8 MiB");
113
+ evidence.steps.push(step);
114
+ }
115
+ if (page.nextCursor === null)
116
+ break;
117
+ if (!page.items.length || page.nextCursor !== evidence.steps.length - 1)
118
+ throw new TypeError("Environment history repeated or skipped a cursor");
119
+ after = page.nextCursor;
120
+ }
121
+ validateEnvironmentEvidence(evidence);
122
+ return evidence;
123
+ }
@@ -0,0 +1,3 @@
1
+ export declare function environmentJson(value: unknown, maxBytes?: number, maxDepth?: number, maxNodes?: number): void;
2
+ export declare function validateEvidenceWorld(value: unknown): void;
3
+ export declare function validateEvidenceArguments(value: unknown): void;