@lightspeed-ai/agent-client 0.1.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 (47) hide show
  1. package/README.md +63 -0
  2. package/dist/client.d.ts +65 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +194 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors.d.ts +20 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +47 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/generated/methods.d.ts +2497 -0
  11. package/dist/generated/methods.d.ts.map +1 -0
  12. package/dist/generated/methods.js +1751 -0
  13. package/dist/generated/methods.js.map +1 -0
  14. package/dist/generated/types.d.ts +6950 -0
  15. package/dist/generated/types.d.ts.map +1 -0
  16. package/dist/generated/types.js +6 -0
  17. package/dist/generated/types.js.map +1 -0
  18. package/dist/generated/workflow-manifest.d.ts +219 -0
  19. package/dist/generated/workflow-manifest.d.ts.map +1 -0
  20. package/dist/generated/workflow-manifest.js +251 -0
  21. package/dist/generated/workflow-manifest.js.map +1 -0
  22. package/dist/generated/workflow-types.d.ts +451 -0
  23. package/dist/generated/workflow-types.d.ts.map +1 -0
  24. package/dist/generated/workflow-types.js +6 -0
  25. package/dist/generated/workflow-types.js.map +1 -0
  26. package/dist/index.d.ts +8 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +4 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/workflow.d.ts +217 -0
  31. package/dist/workflow.d.ts.map +1 -0
  32. package/dist/workflow.js +338 -0
  33. package/dist/workflow.js.map +1 -0
  34. package/examples/temporal-activity.ts +33 -0
  35. package/package.json +55 -0
  36. package/release.json +4 -0
  37. package/schema/api.schema.json +16643 -0
  38. package/schema/workflow.json +246 -0
  39. package/schema/workflow.schema.json +880 -0
  40. package/src/client.ts +318 -0
  41. package/src/errors.ts +64 -0
  42. package/src/generated/methods.ts +2888 -0
  43. package/src/generated/types.ts +7189 -0
  44. package/src/generated/workflow-manifest.ts +251 -0
  45. package/src/generated/workflow-types.ts +468 -0
  46. package/src/index.ts +31 -0
  47. package/src/workflow.ts +468 -0
@@ -0,0 +1,468 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
+
3
+ import { WORKFLOW_CONTRACT_MANIFEST } from "./generated/workflow-manifest.js";
4
+ import type {
5
+ EmissionEnvelope,
6
+ PromiseResolution,
7
+ RunStatus,
8
+ WorkflowToolInvocation,
9
+ } from "./generated/workflow-types.js";
10
+
11
+ export type * from "./generated/workflow-types.js";
12
+
13
+ export const DELIVER_EMISSION_SIGNAL =
14
+ WORKFLOW_CONTRACT_MANIFEST.signals.deliverEmission;
15
+ export const WORKFLOW_TOOL_RECOVERY_QUERY =
16
+ WORKFLOW_CONTRACT_MANIFEST.queries.workflowToolRecovery;
17
+ export const WORKFLOW_TOOL_EXECUTION_KIND =
18
+ WORKFLOW_CONTRACT_MANIFEST.workflowTools.executionKind;
19
+ export const WORKFLOW_TOOL_RECIPE_FORMAT_V1 =
20
+ WORKFLOW_CONTRACT_MANIFEST.workflowTools.recipeFormatV1;
21
+ export const WORKFLOW_TOOL_RECIPE_FINGERPRINT_PREFIX =
22
+ WORKFLOW_CONTRACT_MANIFEST.workflowTools.recipeFingerprintPrefix;
23
+ export const REPLY_COMPLETION_KEY =
24
+ WORKFLOW_CONTRACT_MANIFEST.workflowTools.replyCompletionKey;
25
+
26
+ /** Activity names a connector host registers on its account task queues. */
27
+ export const CHANNEL_CONNECTOR_ACTIVITIES =
28
+ WORKFLOW_CONTRACT_MANIFEST.channels.connectorActivities;
29
+ export const CHANNEL_CONVERSATION_WORKFLOW_KIND =
30
+ WORKFLOW_CONTRACT_MANIFEST.channels.workflowKind;
31
+ export const CHANNEL_INBOUND_SIGNAL =
32
+ WORKFLOW_CONTRACT_MANIFEST.channels.inboundSignal;
33
+ export const CHANNEL_STATE_QUERY =
34
+ WORKFLOW_CONTRACT_MANIFEST.channels.stateQuery;
35
+ export const CHANNEL_DELIVERY_RECEIPT_SIGNAL =
36
+ WORKFLOW_CONTRACT_MANIFEST.channels.deliveryReceiptSignal;
37
+
38
+ /** Known-answer vectors emitted by Rust and shared by every generated consumer. */
39
+ export const WORKFLOW_CONTRACT_VECTORS = WORKFLOW_CONTRACT_MANIFEST.vectors;
40
+
41
+ const EMISSION_ID = /^(?:emission|wti):sha256:[0-9a-f]{64}$/;
42
+ const INVOCATION_ID = /^wti:sha256:[0-9a-f]{64}$/;
43
+ const BLOB_REF = /^sha256:[0-9a-f]{64}$/;
44
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
45
+ const SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
46
+ const textEncoder = new TextEncoder();
47
+
48
+ export const emissionId = {
49
+ runTerminal(
50
+ universeId: string,
51
+ sessionId: string,
52
+ runId: number,
53
+ token: string,
54
+ ): string {
55
+ requireSessionId(sessionId);
56
+ requireSafeIntegerValue(runId, "runId");
57
+ requireNonEmpty(token, "token");
58
+ return derivedEmissionId("run_terminal", [
59
+ utf8(normalizeUniverseId(universeId)),
60
+ utf8(sessionId),
61
+ u64be(runId),
62
+ utf8(token),
63
+ ]);
64
+ },
65
+
66
+ /**
67
+ * Promise ids are session-scoped counters (`promise_7`), so the holder
68
+ * workflow id is part of the emission identity: one producer resolving
69
+ * `promise_7` for two holders sends two distinct emissions.
70
+ */
71
+ sourceResolution(
72
+ universeId: string,
73
+ producerWorkflowId: string,
74
+ holderWorkflowId: string,
75
+ promiseId: string,
76
+ ): string {
77
+ requireNonEmpty(producerWorkflowId, "producerWorkflowId");
78
+ requireNonEmpty(holderWorkflowId, "holderWorkflowId");
79
+ requireNonEmpty(promiseId, "promiseId");
80
+ return derivedEmissionId("source_resolution", [
81
+ utf8(normalizeUniverseId(universeId)),
82
+ utf8(producerWorkflowId),
83
+ utf8(holderWorkflowId),
84
+ utf8(promiseId),
85
+ ]);
86
+ },
87
+
88
+ toolInvocation(invocationId: string): string {
89
+ requireCanonicalInvocationId(invocationId);
90
+ return invocationId;
91
+ },
92
+
93
+ invocationCancellation(invocationId: string, completionKey: string): string {
94
+ requireCanonicalInvocationId(invocationId);
95
+ requireNonEmpty(completionKey, "completionKey");
96
+ return derivedEmissionId("invocation_cancellation", [
97
+ utf8(invocationId),
98
+ utf8(completionKey),
99
+ ]);
100
+ },
101
+ } as const;
102
+
103
+ export interface SourceResolutionEnvelopeInput {
104
+ universeId: string;
105
+ producerWorkflowId: string;
106
+ /** The Lightspeed session workflow that holds the promise. */
107
+ holderWorkflowId: string;
108
+ promiseId: string;
109
+ resolution: PromiseResolution;
110
+ }
111
+
112
+ /** Build the exact envelope a workflow sends back to a Lightspeed holder. */
113
+ export function sourceResolutionEnvelope(
114
+ input: SourceResolutionEnvelopeInput,
115
+ ): EmissionEnvelope {
116
+ const universeId = normalizeUniverseId(input.universeId);
117
+ requireNonEmpty(input.producerWorkflowId, "producerWorkflowId");
118
+ requireNonEmpty(input.holderWorkflowId, "holderWorkflowId");
119
+ requireNonEmpty(input.promiseId, "promiseId");
120
+ parseResolution(input.resolution);
121
+ return {
122
+ emission_id: emissionId.sourceResolution(
123
+ universeId,
124
+ input.producerWorkflowId,
125
+ input.holderWorkflowId,
126
+ input.promiseId,
127
+ ),
128
+ producer: {
129
+ kind: "workflow",
130
+ universe_id: universeId,
131
+ workflow_id: input.producerWorkflowId,
132
+ },
133
+ body: {
134
+ kind: "source_resolution",
135
+ promise_id: input.promiseId,
136
+ resolution: input.resolution,
137
+ },
138
+ };
139
+ }
140
+
141
+ /** Decode and validate the fixed signal payload at a workflow boundary. */
142
+ export function parseEmissionEnvelope(value: unknown): EmissionEnvelope {
143
+ const envelope = record(value, "emission envelope");
144
+ requirePattern(envelope, "emission_id", EMISSION_ID);
145
+ parseProducer(envelope.producer);
146
+ parseBody(envelope.body);
147
+ return value as EmissionEnvelope;
148
+ }
149
+
150
+ /** Return the single receiver-visible Promise under the reserved `reply` key. */
151
+ export function replyPromiseId(invocation: WorkflowToolInvocation): string {
152
+ const promises = invocation.completion_promises;
153
+ if (promises === undefined || promises === null) {
154
+ throw new TypeError(
155
+ "pushed invocation must carry exactly the reserved `reply` completion promise",
156
+ );
157
+ }
158
+ const keys = Object.keys(promises);
159
+ if (keys.length !== 1 || keys[0] !== REPLY_COMPLETION_KEY) {
160
+ throw new TypeError(
161
+ "pushed invocation must carry exactly the reserved `reply` completion promise",
162
+ );
163
+ }
164
+ const promiseId = promises[REPLY_COMPLETION_KEY];
165
+ requireNonEmptyValue(promiseId, "reply promise");
166
+ return promiseId;
167
+ }
168
+
169
+ /** Compose the stable Temporal workflow id of a Lightspeed session. */
170
+ export function sessionWorkflowId(
171
+ universeId: string,
172
+ sessionId: string,
173
+ ): string {
174
+ requireSessionId(sessionId);
175
+ return `${normalizeUniverseId(universeId)}/${sessionId}`;
176
+ }
177
+
178
+ /** Compose the stable Temporal workflow id of an environment job group. */
179
+ export function environmentJobWorkflowId(
180
+ universeId: string,
181
+ environmentId: string,
182
+ jobGroupId: string,
183
+ ): string {
184
+ requireNonEmpty(environmentId, "environmentId");
185
+ requireNonEmpty(jobGroupId, "jobGroupId");
186
+ return `${normalizeUniverseId(universeId)}/envjob-${environmentId}-${jobGroupId}`;
187
+ }
188
+
189
+ /**
190
+ * Task queue of the connector host serving one channel account:
191
+ * `lightspeed-connector-{provider}-{24 hex}`, the hex being sha256 over the
192
+ * length-prefixed domain, hyphenated lowercase universe id, provider, and
193
+ * account id. The core routes connector activities here; the host polls it.
194
+ */
195
+ export function connectorTaskQueue(
196
+ universeId: string,
197
+ provider: string,
198
+ accountId: string,
199
+ ): string {
200
+ requireNonEmpty(provider, "provider");
201
+ requireNonEmpty(accountId, "accountId");
202
+ const digest = sha256.create();
203
+ for (const part of [
204
+ utf8(WORKFLOW_CONTRACT_MANIFEST.channels.domains.connectorTaskQueue),
205
+ utf8(normalizeUniverseId(universeId)),
206
+ utf8(provider),
207
+ utf8(accountId),
208
+ ]) {
209
+ digest.update(u64be(part.length));
210
+ digest.update(part);
211
+ }
212
+ return `lightspeed-connector-${provider}-${hex(digest.digest()).slice(0, 24)}`;
213
+ }
214
+
215
+ /** Split a canonical session workflow id, returning undefined for other ids. */
216
+ export function splitWorkflowId(
217
+ workflowId: string,
218
+ ): { universeId: string; sessionId: string } | undefined {
219
+ const separator = workflowId.indexOf("/");
220
+ if (separator < 0 || workflowId.indexOf("/", separator + 1) >= 0)
221
+ return undefined;
222
+ const universeId = workflowId.slice(0, separator);
223
+ const sessionId = workflowId.slice(separator + 1);
224
+ try {
225
+ const normalizedUniverseId = normalizeUniverseId(universeId);
226
+ requireSessionId(sessionId);
227
+ return { universeId: normalizedUniverseId, sessionId };
228
+ } catch {
229
+ return undefined;
230
+ }
231
+ }
232
+
233
+ /** Fingerprint the exact raw recipe bytes with the canonical prefix. */
234
+ export function recipeFingerprint(recipe: string | Uint8Array): string {
235
+ const bytes = typeof recipe === "string" ? utf8(recipe) : recipe;
236
+ return `${WORKFLOW_TOOL_RECIPE_FINGERPRINT_PREFIX}${hex(sha256(bytes))}`;
237
+ }
238
+
239
+ function derivedEmissionId(kind: string, parts: Uint8Array[]): string {
240
+ const digest = sha256.create();
241
+ for (const part of [
242
+ utf8(WORKFLOW_CONTRACT_MANIFEST.emissionIds.hashDomain),
243
+ utf8(kind),
244
+ ...parts,
245
+ ]) {
246
+ digest.update(u64be(part.length));
247
+ digest.update(part);
248
+ }
249
+ return `${WORKFLOW_CONTRACT_MANIFEST.emissionIds.prefix}${hex(digest.digest())}`;
250
+ }
251
+
252
+ function parseProducer(value: unknown): void {
253
+ const producer = record(value, "emission producer");
254
+ const kind = requireString(producer, "kind");
255
+ normalizeUniverseId(requireString(producer, "universe_id"));
256
+ if (kind === "session") {
257
+ requireSessionId(requireString(producer, "session_id"));
258
+ requireSafeInteger(producer, "log_seq");
259
+ return;
260
+ }
261
+ if (kind === "workflow") {
262
+ requireNonEmpty(
263
+ requireString(producer, "workflow_id"),
264
+ "producer.workflow_id",
265
+ );
266
+ return;
267
+ }
268
+ throw new TypeError(`unknown emission producer kind: ${kind}`);
269
+ }
270
+
271
+ function parseBody(value: unknown): void {
272
+ const body = record(value, "emission body");
273
+ const kind = requireString(body, "kind");
274
+ switch (kind) {
275
+ case "run_terminal":
276
+ requireNonEmpty(requireString(body, "token"), "run_terminal.token");
277
+ requireSafeInteger(body, "run_id");
278
+ requireRunStatus(body.status);
279
+ requireOptionalBlobRef(body, "output_ref");
280
+ requireOptionalBlobRef(body, "failure_message_ref");
281
+ return;
282
+ case "source_resolution":
283
+ requireNonEmpty(
284
+ requireString(body, "promise_id"),
285
+ "source_resolution.promise_id",
286
+ );
287
+ parseResolution(body.resolution);
288
+ return;
289
+ case "tool_invocation":
290
+ parseInvocation(body.invocation);
291
+ requireNonEmpty(
292
+ requireString(body, "holder_workflow_id"),
293
+ "tool_invocation.holder_workflow_id",
294
+ );
295
+ return;
296
+ case "invocation_cancellation":
297
+ requirePattern(body, "invocation_id", INVOCATION_ID);
298
+ requireNonEmpty(
299
+ requireString(body, "completion_key"),
300
+ "cancellation.completion_key",
301
+ );
302
+ requireNonEmpty(
303
+ requireString(body, "promise_id"),
304
+ "cancellation.promise_id",
305
+ );
306
+ return;
307
+ default:
308
+ throw new TypeError(`unknown emission body kind: ${kind}`);
309
+ }
310
+ }
311
+
312
+ function parseInvocation(value: unknown): void {
313
+ const invocation = record(value, "workflow tool invocation");
314
+ requirePattern(invocation, "invocation_id", INVOCATION_ID);
315
+ for (const key of [
316
+ "tool_id",
317
+ "semantic_type",
318
+ "binding_fingerprint",
319
+ "session_id",
320
+ "tool_call_id",
321
+ ]) {
322
+ requireNonEmpty(requireString(invocation, key), `invocation.${key}`);
323
+ }
324
+ normalizeUniverseId(requireString(invocation, "session_universe_id"));
325
+ requirePattern(invocation, "arguments_ref", BLOB_REF);
326
+ const contextRef = invocation.execution_context_ref;
327
+ if (contextRef !== undefined && contextRef !== null) {
328
+ requirePattern(invocation, "execution_context_ref", BLOB_REF);
329
+ }
330
+ for (const key of ["schema_revision", "run_id", "turn_id", "tool_batch_id"]) {
331
+ requireSafeInteger(invocation, key);
332
+ }
333
+ if (
334
+ invocation.completion_promises !== undefined &&
335
+ invocation.completion_promises !== null
336
+ ) {
337
+ const promises = record(
338
+ invocation.completion_promises,
339
+ "completion promises",
340
+ );
341
+ for (const [key, promiseId] of Object.entries(promises)) {
342
+ requireNonEmpty(key, "completion promise key");
343
+ requireNonEmptyValue(promiseId, `completion promise ${key}`);
344
+ }
345
+ }
346
+ }
347
+
348
+ function parseResolution(value: unknown): void {
349
+ const resolution = record(value, "promise resolution");
350
+ const kind = requireString(resolution, "kind");
351
+ if (kind === "resolved") {
352
+ requireOptionalBlobRef(resolution, "payload_ref");
353
+ return;
354
+ }
355
+ if (kind === "failed") {
356
+ requireOptionalBlobRef(resolution, "error_ref");
357
+ return;
358
+ }
359
+ if (kind !== "cancelled") {
360
+ throw new TypeError(`unknown promise resolution kind: ${kind}`);
361
+ }
362
+ }
363
+
364
+ function requireRunStatus(value: unknown): asserts value is RunStatus {
365
+ if (
366
+ value !== "active" &&
367
+ value !== "parked" &&
368
+ value !== "cancelling" &&
369
+ value !== "cancelling_grace" &&
370
+ value !== "completed" &&
371
+ value !== "failed" &&
372
+ value !== "cancelled"
373
+ ) {
374
+ throw new TypeError(`invalid run status: ${String(value)}`);
375
+ }
376
+ }
377
+
378
+ function requireOptionalBlobRef(
379
+ value: Record<string, unknown>,
380
+ key: string,
381
+ ): void {
382
+ const field = value[key];
383
+ if (field !== undefined && field !== null) {
384
+ requireNonEmptyValue(field, key);
385
+ if (!BLOB_REF.test(field))
386
+ throw new TypeError(`${key} is not a canonical blob ref`);
387
+ }
388
+ }
389
+
390
+ function record(value: unknown, name: string): Record<string, unknown> {
391
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
392
+ throw new TypeError(`${name} must be an object`);
393
+ }
394
+ return value as Record<string, unknown>;
395
+ }
396
+
397
+ function requireString(value: Record<string, unknown>, key: string): string {
398
+ const field = value[key];
399
+ if (typeof field !== "string") throw new TypeError(`${key} must be a string`);
400
+ return field;
401
+ }
402
+
403
+ function requirePattern(
404
+ value: Record<string, unknown>,
405
+ key: string,
406
+ pattern: RegExp,
407
+ ): void {
408
+ const field = requireString(value, key);
409
+ if (!pattern.test(field)) throw new TypeError(`${key} has an invalid format`);
410
+ }
411
+
412
+ function requireSafeInteger(value: Record<string, unknown>, key: string): void {
413
+ requireSafeIntegerValue(value[key], key);
414
+ }
415
+
416
+ function requireSafeIntegerValue(
417
+ value: unknown,
418
+ name: string,
419
+ ): asserts value is number {
420
+ if (!Number.isSafeInteger(value) || (value as number) < 0) {
421
+ throw new TypeError(`${name} must be a non-negative safe integer`);
422
+ }
423
+ }
424
+
425
+ function requireSessionId(value: string): void {
426
+ if (!SESSION_ID.test(value) || utf8(value).length > 128) {
427
+ throw new TypeError("invalid session id");
428
+ }
429
+ }
430
+
431
+ function normalizeUniverseId(value: string): string {
432
+ if (!UUID.test(value)) throw new TypeError("universeId must be a UUID");
433
+ return value.toLowerCase();
434
+ }
435
+
436
+ function requireCanonicalInvocationId(value: string): void {
437
+ if (!INVOCATION_ID.test(value))
438
+ throw new TypeError("invalid workflow tool invocation id");
439
+ }
440
+
441
+ function requireNonEmpty(value: string, name: string): void {
442
+ if (value.length === 0) throw new TypeError(`${name} must not be empty`);
443
+ }
444
+
445
+ function requireNonEmptyValue(
446
+ value: unknown,
447
+ name: string,
448
+ ): asserts value is string {
449
+ if (typeof value !== "string" || value.length === 0) {
450
+ throw new TypeError(`${name} must be a non-empty string`);
451
+ }
452
+ }
453
+
454
+ function utf8(value: string): Uint8Array {
455
+ return textEncoder.encode(value);
456
+ }
457
+
458
+ function u64be(value: number): Uint8Array {
459
+ const bytes = new Uint8Array(8);
460
+ new DataView(bytes.buffer).setBigUint64(0, BigInt(value), false);
461
+ return bytes;
462
+ }
463
+
464
+ function hex(value: Uint8Array): string {
465
+ return Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join(
466
+ "",
467
+ );
468
+ }