@hue-run/sdk 0.19.1 → 0.20.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.
Files changed (48) hide show
  1. package/CLI.md +27 -10
  2. package/ENVIRONMENTS.md +5 -2
  3. package/EVALUATIONS.md +6 -1
  4. package/README.md +2 -2
  5. package/WORLDS.md +122 -0
  6. package/dist/cli/eval.d.ts +4 -0
  7. package/dist/cli/eval.js +434 -186
  8. package/dist/cli/listen.js +1 -1
  9. package/dist/environment/client.d.ts +2 -95
  10. package/dist/environment/client.js +2 -283
  11. package/dist/environment/tools.d.ts +2 -35
  12. package/dist/environment/tools.js +2 -30
  13. package/dist/environment/types.d.ts +2 -623
  14. package/dist/environment/types.js +2 -1
  15. package/dist/environment/world.d.ts +2 -65
  16. package/dist/environment/world.js +2 -171
  17. package/dist/environment.d.ts +7 -7
  18. package/dist/environment.js +3 -3
  19. package/dist/evals/attempt.d.ts +1 -1
  20. package/dist/evals/client.d.ts +3 -2
  21. package/dist/evals/client.js +2 -1
  22. package/dist/evals/environment-target.d.ts +3 -3
  23. package/dist/evals/environment-target.js +3 -3
  24. package/dist/evals/failure.js +1 -1
  25. package/dist/evals/local-worker.d.ts +8 -3
  26. package/dist/evals/local-worker.js +10 -2
  27. package/dist/evals/simulation.d.ts +6 -3
  28. package/dist/evals/simulation.js +2 -1
  29. package/dist/evals/types.d.ts +2 -2
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/dist/world/client.d.ts +150 -0
  33. package/dist/world/client.js +368 -0
  34. package/dist/world/definitions.d.ts +47 -0
  35. package/dist/world/definitions.js +1 -0
  36. package/dist/world/model-types.d.ts +117 -0
  37. package/dist/world/model-types.js +1 -0
  38. package/dist/world/models.d.ts +40 -0
  39. package/dist/world/models.js +56 -0
  40. package/dist/world/tools.d.ts +35 -0
  41. package/dist/world/tools.js +30 -0
  42. package/dist/world/types.d.ts +625 -0
  43. package/dist/world/types.js +1 -0
  44. package/dist/world/world.d.ts +65 -0
  45. package/dist/world/world.js +175 -0
  46. package/dist/world.d.ts +13 -0
  47. package/dist/world.js +5 -0
  48. package/package.json +8 -2
@@ -0,0 +1,368 @@
1
+ import { validateOptions } from "../config.js";
2
+ import { aggregateBounds, json, uuid, valueBounds } from "../evals/json.js";
3
+ /** Sanitized environment API failure that never includes response text or credentials. */
4
+ export class HueEnvironmentError extends Error {
5
+ status;
6
+ retryAfterMs;
7
+ diagnostic;
8
+ constructor(
9
+ /** HTTP status when Hue answered; absent for transport, timeout or parse failure. */
10
+ status,
11
+ /** Hue's `Retry-After` in milliseconds, bounded, when a 429 or 503 carried one. */
12
+ retryAfterMs,
13
+ /** The server's `X-Hue-Diagnostic` code, when it is a short snake-case value. */
14
+ diagnostic) {
15
+ super(status
16
+ ? `Hue environment request failed (HTTP ${status}${diagnostic ? `, ${diagnostic}` : ""})`
17
+ : "Hue environment connection or response failed");
18
+ this.status = status;
19
+ this.retryAfterMs = retryAfterMs;
20
+ this.diagnostic = diagnostic;
21
+ this.name = "HueEnvironmentError";
22
+ }
23
+ }
24
+ const DIAGNOSTIC = /^[a-z_]{1,64}$/;
25
+ /** The response's diagnostic code, or undefined when absent or invalid. */
26
+ function diagnosticOf(response) {
27
+ const value = response.headers.get("x-hue-diagnostic");
28
+ return value !== null && DIAGNOSTIC.test(value) ? value : undefined;
29
+ }
30
+ const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);
31
+ /** A connection failure or a status the client retries; seal polling continues through these. */
32
+ export function isTransientEnvironmentError(error) {
33
+ return (error instanceof HueEnvironmentError &&
34
+ (error.status === undefined || RETRYABLE.has(error.status)));
35
+ }
36
+ /** A `Retry-After` longer than this waits this long: Hue asks for a second, never minutes. */
37
+ const MAX_RETRY_AFTER_MS = 10_000;
38
+ const WORLD_PREPARATION_WAIT_MS = 120_000;
39
+ const TRACEPARENT = /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/;
40
+ /** Version 00 with real identifiers: an all-zero trace or span ID is invalid, and Hue refuses it. */
41
+ function validTraceparent(value) {
42
+ if (!TRACEPARENT.test(value))
43
+ return false;
44
+ const [, traceId, spanId] = value.split("-");
45
+ return !/^0+$/.test(traceId) && !/^0+$/.test(spanId);
46
+ }
47
+ /** Whole seconds only, as Hue sends them; a date or garbage is ignored. */
48
+ function retryAfterMillis(response) {
49
+ const header = response.headers.get("retry-after");
50
+ if (header === null || !/^\d{1,6}$/.test(header.trim()))
51
+ return undefined;
52
+ return Math.min(Number(header.trim()) * 1000, MAX_RETRY_AFTER_MS);
53
+ }
54
+ const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
55
+ const REQUEST_BOUNDS = { ...valueBounds, bytes: 1024 * 1024 };
56
+ /** The server bounds JSON inside each entity independently, then permits the parsed
57
+ * definition to contain up to 240 KB. The publication wrapper adds one node/depth
58
+ * level and a small fixed byte prefix. Aggregate bounds cover every server-valid
59
+ * definition without relaxing action/run request envelopes.
60
+ */
61
+ const ENVIRONMENT_PUBLICATION_BOUNDS = aggregateBounds(240_000 + 32);
62
+ /** Typed client for authored environments, isolated runs and immutable journals. */
63
+ export class WorldClient {
64
+ /** Validated Hue origin. */
65
+ baseUrl;
66
+ #apiKey;
67
+ timeoutMillis;
68
+ maxAttempts;
69
+ constructor(options) {
70
+ const validated = validateOptions({
71
+ ...options,
72
+ serviceName: "hue-environments",
73
+ captureContent: false,
74
+ });
75
+ this.baseUrl = validated.baseUrl;
76
+ this.#apiKey = validated.apiKey;
77
+ this.timeoutMillis = validated.timeoutMillis;
78
+ const attempts = options.maxAttempts ?? 4;
79
+ if (!Number.isInteger(attempts) || attempts < 1 || attempts > 10)
80
+ throw new RangeError("maxAttempts must be 1–10");
81
+ this.maxAttempts = attempts;
82
+ }
83
+ /** Compatibility clients override only resource routing. */
84
+ resourcePath(path) {
85
+ return path
86
+ .replace(/^\/environments(?=\/|$|\?)/, "/worlds")
87
+ .replace(/^\/environment-versions(?=\/|$|\?)/, "/world-versions")
88
+ .replace(/^\/environment-runs(?=\/|$|\?)/, "/instances");
89
+ }
90
+ async send(method, path, payload, options = {}) {
91
+ let response;
92
+ try {
93
+ const timeout = AbortSignal.timeout(this.timeoutMillis);
94
+ response = await fetch(`${this.baseUrl}/api/v1${this.resourcePath(path)}`, {
95
+ method,
96
+ headers: {
97
+ Authorization: `Bearer ${this.#apiKey}`,
98
+ ...(payload ? { "Content-Type": "application/json" } : {}),
99
+ },
100
+ body: payload,
101
+ redirect: "error",
102
+ signal: options.signal ? AbortSignal.any([options.signal, timeout]) : timeout,
103
+ });
104
+ }
105
+ catch {
106
+ throw new HueEnvironmentError();
107
+ }
108
+ if (!response.ok) {
109
+ await response.body?.cancel();
110
+ throw new HueEnvironmentError(response.status, response.status === 429 || response.status === 503 ? retryAfterMillis(response) : undefined, diagnosticOf(response));
111
+ }
112
+ try {
113
+ const reader = response.body?.getReader();
114
+ if (!reader)
115
+ throw new Error("Missing response");
116
+ const chunks = [];
117
+ let size = 0;
118
+ try {
119
+ for (;;) {
120
+ const { done, value } = await reader.read();
121
+ if (done)
122
+ break;
123
+ size += value.byteLength;
124
+ if (size > MAX_RESPONSE_BYTES)
125
+ throw new Error("Oversized response");
126
+ chunks.push(value);
127
+ }
128
+ }
129
+ finally {
130
+ await reader.cancel();
131
+ }
132
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
133
+ }
134
+ catch {
135
+ throw new HueEnvironmentError();
136
+ }
137
+ }
138
+ /** @internal Shared bounded transport for World clients. */
139
+ async request(method, path, body, options = {}, waitForWorld = false) {
140
+ // Serialize once: a body this client cannot encode is a caller error that no retry fixes.
141
+ const payload = body === undefined
142
+ ? undefined
143
+ : JSON.stringify(json(Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined)), REQUEST_BOUNDS));
144
+ let ordinaryFailures = 0;
145
+ let preparingUntil;
146
+ let preparation;
147
+ let preparationTimer;
148
+ try {
149
+ for (;;) {
150
+ const signal = preparation
151
+ ? options.signal
152
+ ? AbortSignal.any([options.signal, preparation.signal])
153
+ : preparation.signal
154
+ : options.signal;
155
+ if (signal?.aborted ||
156
+ (preparingUntil !== undefined && performance.now() >= preparingUntil))
157
+ throw new HueEnvironmentError();
158
+ try {
159
+ return await this.send(method, path, payload, { signal });
160
+ }
161
+ catch (error) {
162
+ if (!(error instanceof HueEnvironmentError))
163
+ throw error;
164
+ if (signal?.aborted)
165
+ throw error;
166
+ const preparing = waitForWorld && error.status === 503 && error.diagnostic === "world_preparing";
167
+ let wait;
168
+ if (preparing) {
169
+ if (preparingUntil === undefined) {
170
+ preparingUntil = performance.now() + WORLD_PREPARATION_WAIT_MS;
171
+ preparation = new AbortController();
172
+ preparationTimer = setTimeout(() => preparation.abort(), WORLD_PREPARATION_WAIT_MS);
173
+ }
174
+ // A zero Retry-After must not turn preparation into an unbounded busy loop.
175
+ wait = Math.max(100, error.retryAfterMs ?? 1000);
176
+ }
177
+ else {
178
+ ordinaryFailures++;
179
+ if (!isTransientEnvironmentError(error) || ordinaryFailures >= this.maxAttempts)
180
+ throw error;
181
+ const backoff = Math.min(100 * 2 ** (ordinaryFailures - 1), 2000);
182
+ wait = error.retryAfterMs ?? backoff + Math.random() * backoff;
183
+ }
184
+ if (preparingUntil !== undefined) {
185
+ const remaining = preparingUntil - performance.now();
186
+ if (remaining <= 0)
187
+ throw error;
188
+ wait = Math.min(wait, remaining);
189
+ }
190
+ const waiting = preparation
191
+ ? options.signal
192
+ ? AbortSignal.any([options.signal, preparation.signal])
193
+ : preparation.signal
194
+ : options.signal;
195
+ await new Promise((resolve, reject) => {
196
+ const timer = setTimeout(() => {
197
+ waiting?.removeEventListener("abort", abort);
198
+ resolve();
199
+ }, wait);
200
+ const abort = () => {
201
+ clearTimeout(timer);
202
+ waiting?.removeEventListener("abort", abort);
203
+ reject(new HueEnvironmentError());
204
+ };
205
+ if (waiting?.aborted)
206
+ abort();
207
+ else
208
+ waiting?.addEventListener("abort", abort, { once: true });
209
+ });
210
+ }
211
+ }
212
+ }
213
+ finally {
214
+ if (preparationTimer !== undefined)
215
+ clearTimeout(preparationTimer);
216
+ }
217
+ }
218
+ /** @internal Single-attempt transport for non-idempotent compatibility operations. */
219
+ requestOnce(method, path, body, bounds = REQUEST_BOUNDS) {
220
+ const payload = body === undefined
221
+ ? undefined
222
+ : JSON.stringify(json(Object.fromEntries(Object.entries(body).filter(([, value]) => value !== undefined)), bounds));
223
+ return this.send(method, path, payload);
224
+ }
225
+ page(options = {}) {
226
+ const query = new URLSearchParams();
227
+ if (options.after)
228
+ query.set("after", uuid(options.after));
229
+ if (options.limit !== undefined) {
230
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100)
231
+ throw new RangeError("Page limit must be 1–100");
232
+ query.set("limit", String(options.limit));
233
+ }
234
+ return query.size ? `?${query}` : "";
235
+ }
236
+ /** Creates an isolated instance from an immutable World version. */
237
+ createInstance(input, options = {}) {
238
+ const { worldVersionId, ...runInput } = input;
239
+ return this.createRun({ ...runInput, environmentVersionId: worldVersionId }, options);
240
+ }
241
+ /** Reads current or sealed instance state. */
242
+ getInstance(instanceId, options = {}) {
243
+ return this.getRun(instanceId, options);
244
+ }
245
+ /** Completes or abandons one instance and freezes its evidence. */
246
+ finishInstance(instanceId, input) {
247
+ return this.finishRun(instanceId, input);
248
+ }
249
+ /** Creates an environment identity; this non-idempotent registry write is not retried. */
250
+ createEnvironment(input) {
251
+ return this.requestOnce("POST", "/environments", input);
252
+ }
253
+ /** Lists active environment identities. */
254
+ listEnvironments(page) {
255
+ return this.request("GET", `/environments${this.page(page)}`);
256
+ }
257
+ /** Reads one environment and its immutable version summaries. */
258
+ getEnvironment(id) {
259
+ return this.request("GET", `/environments/${uuid(id)}`);
260
+ }
261
+ /** Publishes an immutable definition; this non-idempotent registry write is not retried. */
262
+ publishVersion(environmentId, definition) {
263
+ return this.requestOnce("POST", `/environments/${uuid(environmentId)}/versions`, { definition }, ENVIRONMENT_PUBLICATION_BOUNDS);
264
+ }
265
+ /** Reads a full immutable environment version and generated action catalog. */
266
+ getVersion(id) {
267
+ return this.request("GET", `/environment-versions/${uuid(id)}`);
268
+ }
269
+ /** Creates or recovers one fresh isolated world using a stable idempotency key. */
270
+ createRun(input, options = {}) {
271
+ if (input.maxSteps !== undefined &&
272
+ (!Number.isInteger(input.maxSteps) || input.maxSteps < 1 || input.maxSteps > 500))
273
+ throw new RangeError("maxSteps must be 1–500");
274
+ if (input.ttlSeconds !== undefined &&
275
+ (!Number.isInteger(input.ttlSeconds) || input.ttlSeconds < 1 || input.ttlSeconds > 86_400))
276
+ throw new RangeError("ttlSeconds must be 1–86400");
277
+ if (input.seed !== undefined && !/^[a-f0-9]{32}$/.test(input.seed))
278
+ throw new TypeError("Seed must be 32 lowercase hexadecimal characters");
279
+ if (input.traceparent !== undefined && !validTraceparent(input.traceparent))
280
+ throw new TypeError("traceparent must be a version-00 W3C trace context");
281
+ if (input.agentRevision !== undefined &&
282
+ (typeof input.agentRevision !== "string" ||
283
+ input.agentRevision.length < 1 ||
284
+ input.agentRevision.length > 256))
285
+ throw new RangeError("agentRevision must be 1–256 characters");
286
+ return this.request("POST", "/environment-runs", {
287
+ ...input,
288
+ environmentVersionId: uuid(input.environmentVersionId),
289
+ ...(input.executionId === undefined ? {} : { executionId: uuid(input.executionId) }),
290
+ }, options, true);
291
+ }
292
+ /** Reads authoritative current or sealed world state. */
293
+ async getRun(runId, options = {}) {
294
+ const run = await this.request("GET", `/environment-runs/${uuid(runId)}`, undefined, options);
295
+ return { validity: "not_assessed", coverageGap: null, ...run };
296
+ }
297
+ /** Record a known coverage gap with durable identity; retries reuse the exact request. */
298
+ recordCoverageGap(runId, input) {
299
+ uuid(input.idempotencyKey);
300
+ if (!input.args || typeof input.args !== "object" || Array.isArray(input.args))
301
+ throw new TypeError("Coverage gap arguments must be a JSON object");
302
+ json(input.args, { ...valueBounds, bytes: 16_000 });
303
+ return this.request("POST", `/environment-runs/${uuid(runId)}/coverage-gap`, input);
304
+ }
305
+ /** Invokes an action; repeating an invocation identity replays its recorded result. */
306
+ act(runId, input) {
307
+ return this.request("POST", `/environment-runs/${uuid(runId)}/actions`, {
308
+ ...input,
309
+ args: input.args ?? {},
310
+ });
311
+ }
312
+ /** Pages the immutable journal by step ordinal. */
313
+ listSteps(runId, page = {}) {
314
+ const query = new URLSearchParams();
315
+ if (page.after !== undefined) {
316
+ if (!Number.isInteger(page.after) || page.after < -1)
317
+ throw new RangeError("Step cursor must be an ordinal");
318
+ query.set("after", String(page.after));
319
+ }
320
+ if (page.limit !== undefined) {
321
+ if (!Number.isInteger(page.limit) || page.limit < 1 || page.limit > 100)
322
+ throw new RangeError("Page limit must be 1–100");
323
+ query.set("limit", String(page.limit));
324
+ }
325
+ return this.request("GET", `/environment-runs/${uuid(runId)}/steps${query.size ? `?${query}` : ""}`);
326
+ }
327
+ /** Seals a world as completed or abandoned and freezes its evidence. */
328
+ finishRun(runId, input) {
329
+ return this.request("POST", `/environment-runs/${uuid(runId)}/finish`, input);
330
+ }
331
+ /** Reads a sealed world's evaluator-only evidence with the project key; a world token never
332
+ * can. An open world answers 409 until it is sealed. */
333
+ getEvidence(runId, options = {}) {
334
+ const query = new URLSearchParams();
335
+ if (options.section !== undefined) {
336
+ if (!["all", "start", "end", "diff", "ledger"].includes(options.section))
337
+ throw new TypeError("Evidence section must be all, start, end, diff or ledger");
338
+ query.set("section", options.section);
339
+ }
340
+ if (options.bodies !== undefined)
341
+ query.set("bodies", options.bodies ? "true" : "false");
342
+ return this.request("GET", `/environment-runs/${uuid(runId)}/evidence${query.size ? `?${query}` : ""}`);
343
+ }
344
+ }
345
+ /** Creates a typed simulated-environment client. */
346
+ /** Compatibility client using the original REST resource names. */
347
+ export class EnvironmentClient extends WorldClient {
348
+ /** Publishes the definition shapes supported by the compatibility client. */
349
+ publishVersion(environmentId, definition) {
350
+ return super.publishVersion(environmentId, definition);
351
+ }
352
+ /** Reads the definition using the compatibility client's original return type. */
353
+ getVersion(id) {
354
+ return super.getVersion(id);
355
+ }
356
+ resourcePath(path) {
357
+ return path;
358
+ }
359
+ }
360
+ /** Creates the compatibility client using the original REST resource names. */
361
+ export function createEnvironmentClient(options) {
362
+ return new EnvironmentClient(options);
363
+ }
364
+ /** Creates a World API client using canonical World and instance routes. */
365
+ export function createWorldClient(options) {
366
+ return new WorldClient(options);
367
+ }
368
+ export { HueEnvironmentError as HueWorldError, isTransientEnvironmentError as isTransientWorldError, };
@@ -0,0 +1,47 @@
1
+ import type { EnvironmentDefinitionV1, EnvironmentVersion, JsonValue, PublishableEnvironmentDefinition } from "./types.js";
2
+ /** One account binding for any app registered by the Hue deployment. */
3
+ export interface WorldProviderInstance {
4
+ /** Account-specific key used to select this binding. */
5
+ providerInstanceKey: string;
6
+ /** Registered app identifier. The server validates support. */
7
+ providerId: string;
8
+ /** Stable synthetic principal for this account connection. */
9
+ syntheticPrincipalId: string;
10
+ /** App-owned JSON configuration, validated by that app's simulation package. */
11
+ configuration: Record<string, JsonValue>;
12
+ /** Optional registered protocol dialects pinned for this binding. */
13
+ dialects?: string[];
14
+ }
15
+ /** V1 authored starting-state definition. */
16
+ export type WorldDefinitionV1 = EnvironmentDefinitionV1;
17
+ /** Compatible V1 starting-state definition. */
18
+ export type WorldDefinition = WorldDefinitionV1;
19
+ /** Recorded app credential refusal, optionally restricted to one account binding. */
20
+ export interface WorldCredentialFailure {
21
+ /** App identity. */
22
+ provider: string;
23
+ /** Protocol surface on which the refusal was observed. */
24
+ surface: string;
25
+ /** Recorded operation. */
26
+ operation: string;
27
+ /** Account binding affected by the refusal. */
28
+ instance?: string;
29
+ /** Optional recorded wire result. */
30
+ result?: Record<string, JsonValue>;
31
+ }
32
+ /** Authored World with arbitrary registered apps and independently identified accounts. */
33
+ export interface WorldDefinitionV2 extends Omit<WorldDefinitionV1, "schemaVersion"> {
34
+ /** Definition schema discriminator. */
35
+ schemaVersion: 2;
36
+ /** App/account bindings. Several entries may use the same provider. */
37
+ providerInstances: WorldProviderInstance[];
38
+ /** Recorded credential refusals, independently scoped to app accounts. */
39
+ credentialFailures?: WorldCredentialFailure[];
40
+ }
41
+ /** Definitions accepted by canonical World publication, including legacy typed carriers. */
42
+ export type PublishableWorldDefinition = WorldDefinitionV1 | WorldDefinitionV2 | PublishableEnvironmentDefinition;
43
+ /** Immutable published World version with an app-agnostic starting-state definition. */
44
+ export interface WorldVersion extends Omit<EnvironmentVersion, "definition"> {
45
+ /** App/account-aware immutable starting-state definition. */
46
+ definition: PublishableWorldDefinition;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,117 @@
1
+ /** Project-scoped identity of the user or entity represented by a World model. */
2
+ export interface WorldEntity {
3
+ /** Identifier namespace, such as the application's user namespace. */
4
+ namespace: string;
5
+ /** Full external identifier. */
6
+ id: string;
7
+ }
8
+ /** Continuously enriched history belonging to one entity in one project. */
9
+ export interface WorldModel {
10
+ /** Stable identity of this record. */
11
+ id: string;
12
+ /** Project owning this World model. */
13
+ projectId: string;
14
+ /** Full external identity represented by the model. */
15
+ entity: WorldEntity;
16
+ /** Display name. */
17
+ name: string;
18
+ /** Creation timestamp in RFC 3339 format. */
19
+ createdAt: string;
20
+ /** Last successful instance start, or null when never used. */
21
+ lastUsedAt: string | null;
22
+ /** Committed evidence sequence used for reconstruction. */
23
+ evidenceCutoff: number;
24
+ /** Publication or erasure state. */
25
+ status: "ready" | "preparing" | "erased";
26
+ }
27
+ /** One app account bound to a World model. */
28
+ export interface WorldModelAccount {
29
+ /** Stable identity of this record. */
30
+ id: string;
31
+ /** App simulation identifier. */
32
+ providerId: string;
33
+ /** Account-specific binding key used by the simulation gateway. */
34
+ providerInstanceKey: string;
35
+ /** External account identity. */
36
+ accountId: string;
37
+ /** Connection generation that separates account rebinding. */
38
+ generation: number;
39
+ /** Optional tenant or workspace scope within the external account. */
40
+ scope?: string;
41
+ /** Stable simulation principal for this account binding. */
42
+ syntheticPrincipalId: string;
43
+ }
44
+ /** Immutable reconstructed starting state at a represented time and evidence cutoff. */
45
+ export interface StartingWorld {
46
+ /** Stable identity of this record. */
47
+ id: string;
48
+ /** World model from which this state was reconstructed. */
49
+ modelId: string;
50
+ /** Represented time as an ISO 8601 timestamp with a timezone. */
51
+ at: string;
52
+ /** Committed evidence sequence used for reconstruction. */
53
+ evidenceCutoff: number;
54
+ /** Immutable starting-state digest, or null while preparation is incomplete. */
55
+ digest: string | null;
56
+ /** Publication or erasure state. */
57
+ status: "ready" | "preparing";
58
+ }
59
+ /** Bounded pagination over World models or their accounts. */
60
+ export interface WorldModelPageOptions {
61
+ /** Opaque cursor returned by the previous page. */
62
+ after?: string;
63
+ /** Maximum entries in the page, from 1 to 100. */
64
+ limit?: number;
65
+ }
66
+ /** One page of World models. */
67
+ export interface WorldModelPage {
68
+ /** Entries in this page. */
69
+ items: WorldModel[];
70
+ /** Cursor for the next page, omitted when no further entries remain. */
71
+ nextCursor?: string;
72
+ }
73
+ /** One page of account bindings. */
74
+ export interface WorldModelAccountPage {
75
+ /** Entries in this page. */
76
+ items: WorldModelAccount[];
77
+ /** Cursor for the next page, omitted when no further entries remain. */
78
+ nextCursor?: string;
79
+ }
80
+ /** Creates or retrieves the World model for an entity. */
81
+ export interface CreateWorldModelInput {
82
+ /** Full external identity represented by the model. */
83
+ entity: WorldEntity;
84
+ /** Display name. */
85
+ name?: string;
86
+ }
87
+ /** Starts an isolated instance using the latest completed reconstruction of a chosen time. */
88
+ export interface StartWorldInstanceInput {
89
+ /** Caller-owned identity reused for retries of this exact operation. */
90
+ idempotencyKey: string;
91
+ /** Represented time as an ISO 8601 timestamp with a timezone. */
92
+ at: string;
93
+ /** Requested instance lifetime in seconds. */
94
+ ttlSeconds?: number;
95
+ }
96
+ /** Reproduces an earlier starting World under a durable request identity. */
97
+ export interface ReproduceWorldInstanceInput {
98
+ /** Caller-owned identity reused for retries of this exact operation. */
99
+ idempotencyKey: string;
100
+ /** Requested instance lifetime in seconds. */
101
+ ttlSeconds?: number;
102
+ }
103
+ /** One World model returned by create or get. */
104
+ export interface WorldModelResult {
105
+ /** Entity history and its current committed evidence sequence. */
106
+ worldModel: WorldModel;
107
+ }
108
+ /** One reconstructed starting World without a running instance. */
109
+ export interface StartingWorldResult {
110
+ /** Immutable starting state, or an explicit preparing state. */
111
+ world: StartingWorld;
112
+ }
113
+ /** Acknowledgement that the World model has been erased. */
114
+ export interface ErasedWorldModel {
115
+ /** Successful erasure acknowledgement. */
116
+ erased: true;
117
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import { WorldClient, type WorldClientOptions } from "./client.js";
2
+ import type { CreateWorldModelInput, WorldModelResult, StartingWorldResult, ErasedWorldModel, ReproduceWorldInstanceInput, StartingWorld, StartWorldInstanceInput, WorldModelAccountPage, WorldModelPage, WorldModelPageOptions } from "./model-types.js";
3
+ import type { WorldHandoff } from "./types.js";
4
+ /** Reconstructed starting World and its isolated runtime connection. */
5
+ export interface WorldInstanceResult {
6
+ /** Immutable starting state used by the new instance. */
7
+ world: StartingWorld;
8
+ /** Fresh, instance-scoped connection and credentials. */
9
+ instance: WorldHandoff;
10
+ }
11
+ /** Project-key client for entity histories, reconstructed Worlds and isolated instances. */
12
+ export declare class WorldModelClient extends WorldClient {
13
+ /** Creates the client without starting a World or instance. */
14
+ constructor(options: WorldClientOptions);
15
+ /** Creates or retrieves the model for one full project-scoped entity identity. */
16
+ createModel(input: CreateWorldModelInput): Promise<WorldModelResult>;
17
+ /** Lists World models without loading their evidence or runtime state. */
18
+ listModels(options?: WorldModelPageOptions): Promise<WorldModelPage>;
19
+ /** Reads a model's identity and committed evidence cutoff. */
20
+ getModel(modelId: string): Promise<WorldModelResult>;
21
+ /** Lists account bindings, including multiple accounts of the same app. */
22
+ listAccounts(modelId: string, options?: WorldModelPageOptions): Promise<WorldModelAccountPage>;
23
+ /** Reconstructs the chosen time with the latest committed evidence, without starting an instance. */
24
+ materialize(modelId: string, input: {
25
+ at: string;
26
+ }, options?: {
27
+ signal?: AbortSignal;
28
+ }): Promise<StartingWorldResult>;
29
+ /** Starts one isolated instance, replaying the same request identity after transport failures. */
30
+ startInstance(modelId: string, input: StartWorldInstanceInput, options?: {
31
+ signal?: AbortSignal;
32
+ }): Promise<WorldInstanceResult>;
33
+ /** Starts a fresh instance from the original immutable starting World of an earlier instance. */
34
+ reproduceInstance(instanceId: string, input: ReproduceWorldInstanceInput, options?: {
35
+ signal?: AbortSignal;
36
+ }): Promise<WorldInstanceResult>;
37
+ /** Revokes and erases a World model. Erased starting Worlds cannot be reproduced. */
38
+ eraseModel(modelId: string): Promise<ErasedWorldModel>;
39
+ private modelPage;
40
+ }
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { uuid } from "../evals/json.js";
3
+ import { WorldClient } from "./client.js";
4
+ /** Project-key client for entity histories, reconstructed Worlds and isolated instances. */
5
+ export class WorldModelClient extends WorldClient {
6
+ /** Creates the client without starting a World or instance. */
7
+ constructor(options) {
8
+ super(options);
9
+ }
10
+ /** Creates or retrieves the model for one full project-scoped entity identity. */
11
+ createModel(input) {
12
+ return this.request("POST", "/world-models", input);
13
+ }
14
+ /** Lists World models without loading their evidence or runtime state. */
15
+ listModels(options = {}) {
16
+ return this.request("GET", `/world-models${this.modelPage(options)}`);
17
+ }
18
+ /** Reads a model's identity and committed evidence cutoff. */
19
+ getModel(modelId) {
20
+ return this.request("GET", `/world-models/${uuid(modelId)}`);
21
+ }
22
+ /** Lists account bindings, including multiple accounts of the same app. */
23
+ listAccounts(modelId, options = {}) {
24
+ return this.request("GET", `/world-models/${uuid(modelId)}/accounts${this.modelPage(options)}`);
25
+ }
26
+ /** Reconstructs the chosen time with the latest committed evidence, without starting an instance. */
27
+ materialize(modelId, input, options = {}) {
28
+ return this.request("POST", `/world-models/${uuid(modelId)}/materializations`, { ...input, idempotencyKey: randomUUID() }, options, true);
29
+ }
30
+ /** Starts one isolated instance, replaying the same request identity after transport failures. */
31
+ startInstance(modelId, input, options = {}) {
32
+ return this.request("POST", `/world-models/${uuid(modelId)}/instances`, input, options, true);
33
+ }
34
+ /** Starts a fresh instance from the original immutable starting World of an earlier instance. */
35
+ reproduceInstance(instanceId, input, options = {}) {
36
+ return this.request("POST", `/instances/${uuid(instanceId)}/reproduce`, input, options, true);
37
+ }
38
+ /** Revokes and erases a World model. Erased starting Worlds cannot be reproduced. */
39
+ eraseModel(modelId) {
40
+ return this.request("DELETE", `/world-models/${uuid(modelId)}`);
41
+ }
42
+ modelPage(options) {
43
+ const query = new URLSearchParams();
44
+ if (options.after !== undefined) {
45
+ if (typeof options.after !== "string" || !options.after)
46
+ throw new TypeError("Page cursor must be a nonempty string");
47
+ query.set("after", options.after);
48
+ }
49
+ if (options.limit !== undefined) {
50
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 100)
51
+ throw new RangeError("Page limit must be 1–100");
52
+ query.set("limit", String(options.limit));
53
+ }
54
+ return query.size ? `?${query}` : "";
55
+ }
56
+ }
@@ -0,0 +1,35 @@
1
+ import type { Context } from "@opentelemetry/api";
2
+ import type { HueClient } from "../client.js";
3
+ import type { EnvironmentClient } from "./client.js";
4
+ import type { ActionSchema, EnvironmentRun, JsonValue, Observation } from "./types.js";
5
+ /** Framework-neutral callable generated from one environment action. */
6
+ export interface EnvironmentTool {
7
+ /** Tool name. */
8
+ name: string;
9
+ /** Agent-visible description. */
10
+ description?: string;
11
+ /** JSON Schema input contract. */
12
+ inputSchema: ActionSchema;
13
+ /** Executes the action and resolves with the world's observation. */
14
+ execute(args?: Record<string, JsonValue>): Promise<Observation>;
15
+ }
16
+ /** Dependencies and tracing context for {@link bindEnvironmentTools}. */
17
+ export interface BindEnvironmentToolsOptions {
18
+ /** Hue client used to record each action as tool telemetry. */
19
+ hue: HueClient;
20
+ /** Environment client bound to the same Hue origin. */
21
+ client: Pick<EnvironmentClient, "act">;
22
+ /** Fresh environment run whose closed catalog becomes callables. */
23
+ run: EnvironmentRun;
24
+ /** Optional parent span context for generated tool spans. */
25
+ parentContext?: Context;
26
+ /** Optional durable invocation-ID factory for caller-owned resume state. */
27
+ invocationId?(action: string): string;
28
+ }
29
+ /**
30
+ * Binds a run's generated catalog to plain local callables without changing the agent
31
+ * framework. Each call is recorded through {@link HueClient.tool}. Catalog entries that
32
+ * include `mcp` stamp `mcp.server.name` / `mcp.server.version` and `hue.mcp.provider` /
33
+ * `hue.mcp.surface` so a generic verb is attributed to that MCP server and Hue surface.
34
+ */
35
+ export declare function bindEnvironmentTools(options: BindEnvironmentToolsOptions): Record<string, EnvironmentTool>;