@hue-run/sdk 0.3.2 → 0.4.2

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 (46) hide show
  1. package/CLI.md +270 -47
  2. package/ENVIRONMENTS.md +11 -1
  3. package/README.md +19 -3
  4. package/dist/client.d.ts +5 -5
  5. package/dist/client.js +13 -6
  6. package/dist/environment/tools.d.ts +6 -1
  7. package/dist/environment/tools.js +7 -1
  8. package/dist/environment/types.d.ts +6 -1
  9. package/dist/evals/simulation.d.ts +12 -4
  10. package/dist/evals/simulation.js +34 -24
  11. package/dist/evals.d.ts +1 -1
  12. package/dist/receipt.js +36 -8
  13. package/dist/setup/application.d.ts +74 -0
  14. package/dist/setup/application.js +766 -0
  15. package/dist/setup/backend.d.ts +229 -0
  16. package/dist/setup/backend.js +855 -0
  17. package/dist/setup/checkpoint.js +100 -30
  18. package/dist/setup/cli.js +20 -4
  19. package/dist/setup/configure.d.ts +13 -0
  20. package/dist/setup/configure.js +454 -0
  21. package/dist/setup/credential.d.ts +2 -0
  22. package/dist/setup/credential.js +9 -0
  23. package/dist/setup/detect.js +4 -1
  24. package/dist/setup/installation.d.ts +118 -0
  25. package/dist/setup/installation.js +605 -0
  26. package/dist/setup/lock.d.ts +2 -0
  27. package/dist/setup/lock.js +38 -0
  28. package/dist/setup/machine.d.ts +1 -10
  29. package/dist/setup/machine.js +8 -7
  30. package/dist/setup/render.d.ts +3 -1
  31. package/dist/setup/render.js +209 -6
  32. package/dist/setup/runner.d.ts +26 -76
  33. package/dist/setup/runner.js +320 -45
  34. package/dist/setup/socket.d.ts +7 -0
  35. package/dist/setup/socket.js +144 -0
  36. package/dist/setup/source.d.ts +9 -0
  37. package/dist/setup/source.js +269 -0
  38. package/dist/setup/types.d.ts +16 -9
  39. package/dist/setup/types.js +1 -1
  40. package/dist/setup.d.ts +6 -2
  41. package/dist/setup.js +3 -0
  42. package/dist/types.d.ts +24 -0
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +2 -1
  46. package/setup-events.schema.json +16 -9
@@ -46,24 +46,24 @@ function normalizedEnvironmentDefinition(definition) {
46
46
  metadata: definition.metadata ?? {},
47
47
  }, aggregateBounds(240_000));
48
48
  }
49
- function scenarioIdentity(scenario) {
50
- if (scenario.kind === "experiment")
51
- return scenario;
49
+ function definitionIdentity(definition) {
50
+ if (definition.kind === "experiment")
51
+ return definition;
52
52
  return json({
53
- kind: scenario.kind,
54
- name: scenario.name,
55
- slug: scenario.slug,
56
- description: scenario.description ?? "",
53
+ kind: definition.kind,
54
+ name: definition.name,
55
+ slug: definition.slug,
56
+ description: definition.description ?? "",
57
57
  environment: {
58
- ...scenario.environment,
59
- definition: normalizedEnvironmentDefinition(scenario.environment.definition),
58
+ ...definition.environment,
59
+ definition: normalizedEnvironmentDefinition(definition.environment.definition),
60
60
  },
61
- cases: scenario.cases,
62
- scorers: scenario.scorers.map(({ scorer, ...identity }) => ({
61
+ cases: definition.cases,
62
+ scorers: definition.scorers.map(({ scorer, ...identity }) => ({
63
63
  ...identity,
64
64
  definition: normalizeScorerDefinitionForPublication("definition" in scorer ? scorer.definition : scorer),
65
65
  })),
66
- config: scenario.config ?? {},
66
+ config: definition.config ?? {},
67
67
  }, aggregateBounds(8 * 1024 * 1024));
68
68
  }
69
69
  async function findBySlug(page, slug) {
@@ -283,9 +283,9 @@ async function allCases(client, versionId) {
283
283
  after = page.nextCursor;
284
284
  }
285
285
  }
286
- async function resolveExperiment(options, idempotencyKey) {
287
- if (options.scenario.kind === "experiment") {
288
- const source = await options.client.getExperiment(options.scenario.experimentId);
286
+ async function resolveExperiment(options, definition, idempotencyKey) {
287
+ if (definition.kind === "experiment") {
288
+ const source = await options.client.getExperiment(definition.experimentId);
289
289
  const created = await options.client.createExperiment({
290
290
  idempotencyKey,
291
291
  name: options.runName ?? source.name,
@@ -295,25 +295,35 @@ async function resolveExperiment(options, idempotencyKey) {
295
295
  });
296
296
  return { experimentId: created.id, bindings: options.localScorers ?? [] };
297
297
  }
298
- const environmentVersionId = await resolveEnvironment(options.environmentClient, options.scenario.environment);
299
- const datasetVersionId = await resolveDataset(options.client, options.scenario, environmentVersionId);
300
- const scorers = await resolveScorers(options.client, options.scenario.scorers);
298
+ const environmentVersionId = await resolveEnvironment(options.environmentClient, definition.environment);
299
+ const datasetVersionId = await resolveDataset(options.client, definition, environmentVersionId);
300
+ const scorers = await resolveScorers(options.client, definition.scorers);
301
301
  const created = await options.client.createExperiment({
302
302
  idempotencyKey,
303
- name: options.runName ?? options.scenario.name,
303
+ name: options.runName ?? definition.name,
304
304
  datasetVersionId,
305
305
  scorerVersionIds: scorers.versionIds,
306
- config: options.scenario.config ?? {},
306
+ config: definition.config ?? {},
307
307
  });
308
308
  return {
309
309
  experimentId: created.id,
310
310
  bindings: [...scorers.bindings, ...(options.localScorers ?? [])],
311
311
  };
312
312
  }
313
+ let scenarioDeprecationWarned = false;
313
314
  /** Run an existing agent callback against one fresh hosted world per case. The helper
314
315
  * owns immutable resolution, execution linkage, finalization, scoring and resumable uploads.
315
316
  */
316
317
  export async function runSimulation(options) {
318
+ const definition = options.definition ?? options.scenario;
319
+ if (!definition)
320
+ throw new TypeError("runSimulation requires a definition");
321
+ if (options.definition && options.scenario && options.definition !== options.scenario)
322
+ throw new TypeError("Pass either definition or scenario, not both");
323
+ if (!options.definition && !scenarioDeprecationWarned) {
324
+ scenarioDeprecationWarned = true;
325
+ process.emitWarning("runSimulation option scenario is deprecated; use definition", "DeprecationWarning");
326
+ }
317
327
  const requestedConfiguration = requestedAttemptV2(options);
318
328
  if (options.maxSteps !== undefined &&
319
329
  (!Number.isInteger(options.maxSteps) ||
@@ -332,7 +342,7 @@ export async function runSimulation(options) {
332
342
  baseUrl: options.client.baseUrl,
333
343
  });
334
344
  try {
335
- const scenarioDigest = digest(scenarioIdentity(options.scenario));
345
+ const scenarioDigest = digest(definitionIdentity(definition));
336
346
  let attempt = await store.read("active-attempt");
337
347
  if (attempt && attempt.stage !== "completed" && attempt.scenarioDigest !== scenarioDigest)
338
348
  throw new Error("Recover the unfinished simulation before running a changed scenario");
@@ -346,15 +356,15 @@ export async function runSimulation(options) {
346
356
  }
347
357
  let bindings = options.localScorers ?? [];
348
358
  if (!attempt.experimentId) {
349
- const resolved = await resolveExperiment(options, attempt.idempotencyKey);
359
+ const resolved = await resolveExperiment(options, definition, attempt.idempotencyKey);
350
360
  attempt.experimentId = resolved.experimentId;
351
361
  bindings = resolved.bindings;
352
362
  attempt.stage = "running";
353
363
  await store.write("active-attempt", attempt);
354
364
  }
355
- else if (options.scenario.kind === "repository") {
365
+ else if (definition.kind === "repository") {
356
366
  bindings = [
357
- ...options.scenario.scorers
367
+ ...definition.scorers
358
368
  .filter((item) => "definition" in item.scorer)
359
369
  .map((item) => item.scorer),
360
370
  ...(options.localScorers ?? []),
package/dist/evals.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { EvaluationClientOptions } from "./evals/client.js";
3
3
  export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, } from "./evals/runner.js";
4
4
  export type { RunExperimentOptions, RescoreOptions, RunnerReport } from "./evals/runner.js";
5
5
  export { runSimulation } from "./evals/simulation.js";
6
- export type { RepositorySimulationCase, RepositorySimulationScorer, RunSimulationOptions, SimulationProgress, SimulationReport, SimulationScenario, SimulationTargetContext, } from "./evals/simulation.js";
6
+ export type { RepositorySimulationCase, RepositorySimulationScorer, RunSimulationOptions, SimulationDefinition, SimulationProgress, SimulationReport, SimulationScenario, SimulationTargetContext, } from "./evals/simulation.js";
7
7
  export { actualAgentManifestV2, agentManifestDigestV2, attemptBaselineV2, attemptBindingRead, attemptConnectionBundleV2, attemptIdentityV2, dependencyManifestV2, dependencyProviderV2, expectedAgentManifestV2, executionManifestDigestV2, parityEvidenceV2, preflightFindingV2, preflightReportV2, prepareAttemptInputV2, projectMcpConnectionV2, secretFreeBindingV2, surfaceBindingV2, } from "./evals/attempt.js";
8
8
  export type { ActualAgentManifestInputV2, ActualAgentManifestV2, AttemptBaselineV2, AttemptBindingRead, AttemptConnectionBundleV2, AttemptIdentityV2, DependencyManifestV2, DependencyProviderV2, ExpectedAgentManifestV2, ParityEvidenceV2, PreflightFindingV2, PreflightReportV2, PrepareAttemptIncompleteV2, PrepareAttemptInputV2, PrepareAttemptReadyV2, PrepareAttemptRequestV2, PrepareAttemptResultV2, RefreshAttemptResultV2, RequestedAttemptProviderV2, RevokeAttemptResult, SurfaceBindingV2, } from "./evals/attempt.js";
9
9
  export { builtins, defineLocalScorer, scoreLocally } from "./evals/scorers.js";
package/dist/receipt.js CHANGED
@@ -31,7 +31,7 @@ function validId(value, length) {
31
31
  function record(value) {
32
32
  return value !== null && typeof value === "object" && !Array.isArray(value);
33
33
  }
34
- function parseReceipt(value, traceId, expected, origin) {
34
+ function parseReceipt(value, traceId, expected, origin, setup = false) {
35
35
  if (!record(value) ||
36
36
  value.traceId !== traceId ||
37
37
  !Number.isSafeInteger(value.spanCount) ||
@@ -52,6 +52,24 @@ function parseReceipt(value, traceId, expected, origin) {
52
52
  }
53
53
  if (traceUrl.origin !== origin || traceUrl.username || traceUrl.password)
54
54
  invalidResponse();
55
+ if (setup) {
56
+ const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u;
57
+ if (traceUrl.href !== value.traceUrl ||
58
+ traceUrl.hash ||
59
+ !traceUrl.pathname.startsWith("/traces/") ||
60
+ !uuid.test(traceUrl.pathname.slice(8)))
61
+ invalidResponse();
62
+ const queryNames = [];
63
+ traceUrl.searchParams.forEach((value, name) => {
64
+ if (!["projectId", "organizationId"].includes(name) ||
65
+ queryNames.includes(name) ||
66
+ !uuid.test(value))
67
+ invalidResponse();
68
+ queryNames.push(name);
69
+ });
70
+ if (queryNames.length !== 2)
71
+ invalidResponse();
72
+ }
55
73
  const matched = value.matchedSpanIds, missing = value.missingSpanIds;
56
74
  if (!Array.isArray(matched) ||
57
75
  !Array.isArray(missing) ||
@@ -128,6 +146,13 @@ async function pause(milliseconds, signal) {
128
146
  }
129
147
  /** Observe persisted evidence after the application and its exporter have finished. */
130
148
  export async function verifyTrace(connection, traceId, options = {}) {
149
+ return verifyTraceAtPath(connection, traceId, options, false, fetch);
150
+ }
151
+ /** @internal Dedicated receipt path for setup credentials; ordinary clients stay unchanged. */
152
+ export async function verifySetupTrace(connection, traceId, options, fetcher, signal) {
153
+ return verifyTraceAtPath(connection, traceId, options, true, fetcher, signal);
154
+ }
155
+ async function verifyTraceAtPath(connection, traceId, options, setup, fetcher, signal) {
131
156
  if (!validId(traceId, 32))
132
157
  throw new TypeError("traceId must be a nonzero lowercase 32-character OpenTelemetry trace ID");
133
158
  if (options === null || typeof options !== "object" || Array.isArray(options))
@@ -148,27 +173,28 @@ export async function verifyTrace(connection, traceId, options = {}) {
148
173
  throw new TypeError("timeoutMillis must be greater than zero and at most 60000");
149
174
  // Snapshot caller arrays so concurrent mutation cannot alter the verification criteria.
150
175
  const expectedIds = [...expected], requiredFields = [...required];
151
- const url = new URL(`/api/v1/traces/${traceId}/receipt`, connection.baseUrl);
176
+ const url = new URL(`/api/v1/${setup ? "setup/" : ""}traces/${traceId}/receipt`, connection.baseUrl);
152
177
  for (const id of expectedIds)
153
178
  url.searchParams.append("expectedSpanId", id);
154
179
  const controller = new AbortController();
155
180
  const deadline = performance.now() + timeout;
156
181
  const timer = setTimeout(() => controller.abort(), timeout);
182
+ const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
157
183
  let receipt = null;
158
184
  let delay = 250;
159
185
  try {
160
- while (!controller.signal.aborted && performance.now() < deadline) {
161
- const response = await fetch(url, {
186
+ while (!requestSignal.aborted && performance.now() < deadline) {
187
+ const response = await fetcher(url, {
162
188
  headers: { Authorization: `Bearer ${connection.apiKey}`, Accept: "application/json" },
163
189
  redirect: "manual",
164
190
  credentials: "omit",
165
191
  cache: "no-store",
166
- signal: controller.signal,
192
+ signal: requestSignal,
167
193
  });
168
194
  let retryAfter = 0;
169
195
  if (response.status === 200) {
170
- receipt = parseReceipt(await readJson(response), traceId, expectedIds, url.origin);
171
- if (!controller.signal.aborted &&
196
+ receipt = parseReceipt(await readJson(response), traceId, expectedIds, url.origin, setup);
197
+ if (!requestSignal.aborted &&
172
198
  performance.now() < deadline &&
173
199
  receipt.missingSpanIds.length === 0 &&
174
200
  requiredFields.every((field) => receipt.fields[field]))
@@ -195,7 +221,7 @@ export async function verifyTrace(connection, traceId, options = {}) {
195
221
  if (remaining <= 0)
196
222
  break;
197
223
  const wait = Math.max(delay, retryAfter);
198
- await pause(Math.min(wait, remaining), controller.signal);
224
+ await pause(Math.min(wait, remaining), requestSignal);
199
225
  // A truncated backoff exhausts this call even if a timer wakes just early.
200
226
  if (wait >= remaining)
201
227
  break;
@@ -203,6 +229,8 @@ export async function verifyTrace(connection, traceId, options = {}) {
203
229
  }
204
230
  }
205
231
  catch (error) {
232
+ if (signal?.aborted)
233
+ throw new Error("Setup interrupted");
206
234
  if (!controller.signal.aborted && performance.now() < deadline) {
207
235
  if (error instanceof HueTraceVerificationError)
208
236
  throw error;
@@ -0,0 +1,74 @@
1
+ import type { SetupFileChange } from "./configure.js";
2
+ import { type FileSetupInstallationStore, type SetupInstallationRecord, type SetupStoredApplicationEvidence } from "./installation.js";
3
+ import type { SetupProjectDetection } from "./types.js";
4
+ /** Closed automatic application matrix; other projects require an explicit agent-owned integration. */
5
+ export type SetupApplicationPlan = {
6
+ /** JavaScript or TypeScript application using the shared TypeScript SDK. */
7
+ language: "typescript";
8
+ /** Package manager selected from the project's manifest and lockfile. */
9
+ manager: "bun" | "npm";
10
+ /** Recognized HTTP framework. */
11
+ framework: "express";
12
+ /** Runtime named by the recognized start script; defaults to Node when omitted. */
13
+ runtime?: "node" | "bun";
14
+ /** Whether the start script explicitly enables Node source maps. */
15
+ sourceMaps?: boolean;
16
+ /** Validated project-relative application entrypoint. */
17
+ entrypoint: string;
18
+ /** One literal loopback HTTP pathname selected for the application request. */
19
+ requestPath: string;
20
+ /** SHA-256 of the source inspected during planning. */
21
+ entryDigest: string;
22
+ } | {
23
+ /** Python application using the Python SDK. */
24
+ language: "python";
25
+ /** Environment manager for the supported Python application. */
26
+ manager: "uv";
27
+ /** Recognized HTTP framework. */
28
+ framework: "flask";
29
+ /** Supported project-relative Flask entrypoint. */
30
+ entrypoint: "app.py";
31
+ /** One literal loopback HTTP pathname selected for the application request. */
32
+ requestPath: string;
33
+ /** SHA-256 of the source inspected during planning. */
34
+ entryDigest: string;
35
+ };
36
+ /** Stable reason an automatic application integration is not safe. */
37
+ export declare class SetupApplicationActionRequired extends Error {
38
+ /** Stable reason the caller must resolve before automatic integration continues. */
39
+ readonly code: "ambiguous-project" | "unsupported-manager" | "unsupported-framework" | "ambiguous-entrypoint" | "custom-instrumentation";
40
+ constructor(
41
+ /** Stable reason the caller must resolve before automatic integration continues. */
42
+ code: "ambiguous-project" | "unsupported-manager" | "unsupported-framework" | "ambiguous-entrypoint" | "custom-instrumentation", message: string);
43
+ }
44
+ /** Fixed argv execution boundary used for package managers and application entrypoints. */
45
+ export interface SetupCommand {
46
+ /** Fixed executable name or runtime path; never evaluated by a shell. */
47
+ command: string;
48
+ /** Explicit argument vector supplied to the executable. */
49
+ args: string[];
50
+ /** Selected application's working directory. */
51
+ cwd: string;
52
+ /** Optional child environment; values are never included in command output. */
53
+ env?: NodeJS.ProcessEnv;
54
+ /** Maximum elapsed execution time before termination. */
55
+ timeoutMillis: number;
56
+ }
57
+ /** Executes a fixed argv without a shell and returns only its status. */
58
+ export type SetupCommandRunner = (command: SetupCommand) => Promise<void>;
59
+ /** Statically recognizes the deliberately narrow automatic matrix without executing project code. */
60
+ export declare function planSetupApplication(project: SetupProjectDetection): Promise<SetupApplicationPlan>;
61
+ /** Default command runner: fixed argv, no shell, bounded output and deadline. */
62
+ export declare function runSetupCommand(input: SetupCommand): Promise<void>;
63
+ /** Installs the exact runtime with the detected owner of the project's manifest and lockfile. */
64
+ export declare function installSetupRuntime(project: SetupProjectDetection, plan: SetupApplicationPlan, runner?: SetupCommandRunner): Promise<boolean>;
65
+ /** Adds only two owned wiring blocks around an existing app object; business logic is untouched. */
66
+ export declare function wireSetupApplication(store: FileSetupInstallationStore, record: SetupInstallationRecord, plan: SetupApplicationPlan): Promise<SetupFileChange | undefined>;
67
+ /** Starts the existing entrypoint without a shell and exercises one existing HTTP GET route. */
68
+ export declare function exerciseSetupApplication(store: FileSetupInstallationStore, record: SetupInstallationRecord, plan: SetupApplicationPlan, signal?: AbortSignal, deadlines?: {
69
+ readinessMillis: number;
70
+ requestMillis: number;
71
+ evidenceMillis: number;
72
+ /** Internal loopback test seam; the public CLI always chooses a random port. */
73
+ port?: number;
74
+ }): Promise<SetupStoredApplicationEvidence>;