@hue-run/sdk 0.1.5 → 0.2.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/ENVIRONMENTS.md +182 -0
- package/EVALUATIONS.md +12 -0
- package/README.md +194 -18
- package/dist/ai-sdk.d.ts +9 -1
- package/dist/ai-sdk.js +34 -8
- package/dist/client.d.ts +121 -6
- package/dist/client.js +329 -56
- package/dist/config.d.ts +11 -2
- package/dist/config.js +36 -7
- package/dist/environment/client.d.ts +73 -0
- package/dist/environment/client.js +209 -0
- package/dist/environment/tools.d.ts +30 -0
- package/dist/environment/tools.js +24 -0
- package/dist/environment/types.d.ts +429 -0
- package/dist/environment/types.js +1 -0
- package/dist/environment.d.ts +5 -0
- package/dist/environment.js +2 -0
- package/dist/evals/attempt.d.ts +454 -0
- package/dist/evals/attempt.js +687 -0
- package/dist/evals/client.d.ts +99 -5
- package/dist/evals/client.js +136 -7
- package/dist/evals/environment-evidence.d.ts +6 -0
- package/dist/evals/environment-evidence.js +123 -0
- package/dist/evals/environment-json.d.ts +3 -0
- package/dist/evals/environment-json.js +76 -0
- package/dist/evals/json.d.ts +9 -1
- package/dist/evals/json.js +14 -6
- package/dist/evals/runner.d.ts +61 -2
- package/dist/evals/runner.js +71 -9
- package/dist/evals/scorer-publication.d.ts +2 -0
- package/dist/evals/scorer-publication.js +84 -0
- package/dist/evals/scorers.d.ts +11 -0
- package/dist/evals/scorers.js +56 -5
- package/dist/evals/simulation.d.ts +184 -0
- package/dist/evals/simulation.js +603 -0
- package/dist/evals/types.d.ts +304 -0
- package/dist/evals.d.ts +5 -1
- package/dist/evals.js +3 -1
- package/dist/experimental-telemetry.d.ts +8 -0
- package/dist/experimental-telemetry.js +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/managed.d.ts +51 -1
- package/dist/managed.js +11 -1
- package/dist/privacy.d.ts +2 -0
- package/dist/privacy.js +16 -1
- package/dist/receipt.d.ts +12 -1
- package/dist/receipt.js +10 -1
- package/dist/safety.d.ts +1 -2
- package/dist/snapshot.js +4 -0
- package/dist/transport.d.ts +41 -9
- package/dist/transport.js +80 -22
- package/dist/types.d.ts +144 -8
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/package.json +51 -15
package/dist/evals/client.d.ts
CHANGED
|
@@ -1,41 +1,73 @@
|
|
|
1
1
|
import type { ProjectConnection } from "../types.js";
|
|
2
|
-
import
|
|
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(
|
|
17
|
+
constructor(
|
|
18
|
+
/** HTTP status when Hue answered; absent for network, timeout and parsing failures. */
|
|
19
|
+
status?: number | undefined);
|
|
11
20
|
}
|
|
12
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/evals/client.js
CHANGED
|
@@ -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(
|
|
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
|
-
/**
|
|
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)),
|
|
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.
|
|
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.
|
|
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
|
+
}
|