@eigenpal/sdk 0.6.16 → 0.7.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/src/index.ts CHANGED
@@ -8,26 +8,21 @@
8
8
  * const client = new EigenpalClient({ apiKey: process.env.EIGENPAL_API_KEY! });
9
9
  *
10
10
  * // Async — enqueue and poll later.
11
- * const { runId } = await client.run('workflows.extract-invoice', {
12
- * input: { ... },
13
- * });
11
+ * const { id } = await client.run('workflows.extract-invoice', { ... });
14
12
  *
15
13
  * // Sync (server holds the connection up to 60s).
16
- * const result = await client.run('workflows.extract-invoice', {
17
- * input: { ... },
18
- * waitForCompletion: 60,
19
- * });
14
+ * const result = await client.run('workflows.extract-invoice', { ... }, { waitForCompletion: 60 });
20
15
  *
21
16
  * // Client-side poll (up to 5min by default).
22
- * const final = await client.workflows.executions.runAndWait('wf_abc', { input: { ... } });
17
+ * const final = await client.workflows.executions.runAndWait('wf_abc', { ... });
23
18
  * ```
24
19
  */
25
20
  export {
26
21
  EigenpalClient,
27
22
  type EigenpalOptions,
28
23
  type RerunOptions,
24
+ type RunCallOptions,
29
25
  type RunInput,
30
- type RunOptions,
31
26
  type RunStartResponse,
32
27
  type RunTarget,
33
28
  } from './client';
@@ -46,7 +41,7 @@ export {
46
41
  export { toFile } from './lib/files';
47
42
  export type { FileDescriptor, FileInput, NodeReadableStream } from './lib/files';
48
43
  export type { ListAgentsOptions } from './resources/agents';
49
- export type { ListRunsOptions } from './resources/runs';
44
+ export type { ListRunsOptions, RunExpand, RunExpandSection } from './resources/runs';
50
45
  export type { SourceRawOptions, SourceReleasesOptions } from './resources/source';
51
46
  export type {
52
47
  ListVersionsOptions,
@@ -81,8 +76,8 @@ export type {
81
76
  ListVersionsResponse,
82
77
  ListWorkflowsResponse,
83
78
  RawSourceResponse,
84
- RunSummary,
85
- RunTargetInputBody,
79
+ RunListItem,
80
+ RunStartBody,
86
81
  RunsCancelResponse,
87
82
  RunsExpectedCreateResponse,
88
83
  RunsExpectedFileDeleteResponse,
package/src/lib/files.ts CHANGED
@@ -168,7 +168,7 @@ export interface MultipartParts {
168
168
  fileCount: number;
169
169
  }
170
170
 
171
- /** Drain `input` into a FormData, appending each detected file as a field. */
171
+ /** Drain `input` into a FormData, appending each detected file as `files.<name>`. */
172
172
  async function appendFiles(
173
173
  fd: FormData,
174
174
  input: Record<string, unknown> | undefined
@@ -178,7 +178,7 @@ async function appendFiles(
178
178
  for (const [key, value] of Object.entries(input ?? {})) {
179
179
  if (isFileInput(value)) {
180
180
  const { blob, filename } = await resolveFileBlob(value);
181
- fd.append(key, blob, filename);
181
+ fd.append(`files.${key}`, blob, filename);
182
182
  fileCount += 1;
183
183
  } else {
184
184
  scalars[key] = value;
@@ -188,39 +188,52 @@ async function appendFiles(
188
188
  }
189
189
 
190
190
  /**
191
- * JSON-body counterpart of {@link buildRunMultipart}: per-step overrides ride
192
- * in the reserved `_overrides` body key.
191
+ * JSON-body counterpart of {@link buildRunMultipart}: canonical envelope
192
+ * `{ target, input, overrides?, metadata? }`.
193
193
  */
194
194
  export function buildRunJsonBody(
195
+ target: string,
195
196
  input: Record<string, unknown> | undefined,
196
- overrides?: { steps?: Record<string, Record<string, unknown>> }
197
+ overrides?: { steps?: Record<string, Record<string, unknown>> },
198
+ metadata?: Record<string, unknown>
197
199
  ): Record<string, unknown> {
198
- return overrides ? { ...(input ?? {}), _overrides: overrides } : (input ?? {});
200
+ const body: Record<string, unknown> = {
201
+ target,
202
+ input: input ?? {},
203
+ };
204
+ if (overrides) body.overrides = overrides;
205
+ if (metadata) body.metadata = metadata;
206
+ return body;
199
207
  }
200
208
 
201
209
  /**
202
210
  * Build multipart for the canonical `client.run(...)` endpoint
203
- * (`POST /api/v1/run/{target}`):
211
+ * (`POST /api/v1/runs`):
204
212
  *
205
- * - Each top-level file in `input` becomes a form field (key = input name).
206
- * - Non-file inputs go in the `_json` text field (the input object itself).
207
- * - Per-step overrides go in the reserved `_overrides` text field.
213
+ * - `target` is a required text field.
214
+ * - Scalar inputs go in the `input` JSON text field.
215
+ * - Each top-level file in `input` becomes `files.<fieldName>`.
216
+ * - Per-step overrides and caller metadata use `overrides` / `metadata` JSON parts.
208
217
  *
209
218
  * Async because stream inputs are drained to bytes here. Only top-level file
210
219
  * values are extracted — files nested inside arrays / objects keep their
211
220
  * position in the JSON sidecar (the server has no nested-upload path).
212
221
  */
213
222
  export async function buildRunMultipart(args: {
223
+ target: string;
214
224
  input?: Record<string, unknown>;
215
225
  overrides?: { steps?: Record<string, Record<string, unknown>> };
226
+ metadata?: Record<string, unknown>;
216
227
  }): Promise<MultipartParts> {
217
228
  const fd = new FormData();
229
+ fd.append('target', args.target);
218
230
  const { scalars, fileCount } = await appendFiles(fd, args.input);
219
- if (Object.keys(scalars).length > 0) {
220
- fd.append('_json', JSON.stringify(scalars));
221
- }
231
+ fd.append('input', JSON.stringify(scalars));
222
232
  if (args.overrides) {
223
- fd.append('_overrides', JSON.stringify(args.overrides));
233
+ fd.append('overrides', JSON.stringify(args.overrides));
234
+ }
235
+ if (args.metadata) {
236
+ fd.append('metadata', JSON.stringify(args.metadata));
224
237
  }
225
238
  return { formData: fd, fileCount };
226
239
  }
@@ -23,19 +23,12 @@ export interface RunAndWaitOptions {
23
23
  }
24
24
 
25
25
  export type WorkflowRunAndWaitResponse = {
26
- runId: string;
26
+ id: string;
27
27
  status?: ExecutionStatus;
28
28
  output?: unknown;
29
29
  error?: string;
30
30
  };
31
31
 
32
- const TERMINAL_STATUSES = new Set<ExecutionStatus>([
33
- 'completed',
34
- 'failed',
35
- 'cancelled',
36
- 'rejected',
37
- ]);
38
-
39
32
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
40
33
  const DEFAULT_RUN_AND_WAIT_TIMEOUT_MS = 5 * 60 * 1000;
41
34
 
@@ -55,7 +48,7 @@ export class WorkflowExecutionsResource {
55
48
  /**
56
49
  * Trigger a workflow and poll for completion client-side.
57
50
  *
58
- * Unlike `client.run({ waitForCompletion: 60 })`, this helper polls
51
+ * Unlike `client.run(target, input, { waitForCompletion: 60 })`, this helper polls
59
52
  * indefinitely (up to `timeoutMs`, default 5 min) so it works for runs
60
53
  * that exceed the server-side 60s sync window. Returns the final
61
54
  * response with `status`/`output`/`error` populated.
@@ -70,15 +63,19 @@ export class WorkflowExecutionsResource {
70
63
  const deadline = Date.now() + timeoutMs;
71
64
 
72
65
  const target = `workflows.${workflowId}`;
73
- const runUrl = `/api/v1/run/${encodeURIComponent(target)}`;
66
+ const runUrl = '/api/v1/runs';
74
67
  const runQuery =
75
68
  options.version && options.version !== 'latest' ? { version: options.version } : undefined;
76
- let runResult: { runId: string };
69
+ let runResult: { id: string };
77
70
  if (hasFileInput(input)) {
78
71
  // Build the multipart body once, up front — `dispatch` may retry the
79
72
  // POST, and a drained stream cannot be replayed.
80
- const { formData } = await buildRunMultipart({ input, overrides: options.overrides });
81
- runResult = await this.dispatch<{ runId: string }>(
73
+ const { formData } = await buildRunMultipart({
74
+ target,
75
+ input,
76
+ overrides: options.overrides,
77
+ });
78
+ runResult = await this.dispatch<{ id: string }>(
82
79
  () =>
83
80
  this.client.post({
84
81
  url: runUrl,
@@ -87,22 +84,22 @@ export class WorkflowExecutionsResource {
87
84
  bodySerializer: null,
88
85
  headers: { 'Content-Type': null },
89
86
  signal: options.signal,
90
- }) as Promise<OperationResult<{ runId: string }>>
87
+ }) as Promise<OperationResult<{ id: string }>>
91
88
  );
92
89
  } else {
93
- const body = buildRunJsonBody(input, options.overrides);
94
- runResult = await this.dispatch<{ runId: string }>(
90
+ const body = buildRunJsonBody(target, input, options.overrides);
91
+ runResult = await this.dispatch<{ id: string }>(
95
92
  () =>
96
93
  this.client.post({
97
94
  url: runUrl,
98
95
  query: runQuery,
99
96
  body,
100
97
  signal: options.signal,
101
- }) as Promise<OperationResult<{ runId: string }>>
98
+ }) as Promise<OperationResult<{ id: string }>>
102
99
  );
103
100
  }
104
101
 
105
- const runId = runResult.runId;
102
+ const runId = runResult.id;
106
103
 
107
104
  while (true) {
108
105
  if (options.signal?.aborted) {
@@ -114,27 +111,28 @@ export class WorkflowExecutionsResource {
114
111
  );
115
112
  }
116
113
 
117
- const response = await this.dispatch<RunsGetResponse>(
114
+ const run = (await this.dispatch<RunsGetResponse>(
118
115
  () =>
119
116
  runsGet({
120
117
  client: this.client,
121
118
  path: { id: runId },
122
- query: { include: 'detail' },
119
+ query: { expand: 'execution' },
123
120
  signal: options.signal,
124
121
  }) as Promise<OperationResult<RunsGetResponse>>
125
- );
126
- const status = response.run as {
127
- status?: ExecutionStatus | null;
122
+ )) as {
123
+ finished?: boolean;
128
124
  output?: unknown;
129
125
  error?: string | null;
126
+ execution?: { status?: ExecutionStatus | null } | null;
130
127
  };
131
128
 
132
- if (status.status && TERMINAL_STATUSES.has(status.status)) {
129
+ const status = run.execution?.status;
130
+ if (run.finished) {
133
131
  return {
134
- runId,
135
- status: status.status,
136
- ...(status.output != null ? { output: status.output } : {}),
137
- ...(status.error != null ? { error: status.error } : {}),
132
+ id: runId,
133
+ status: status ?? 'completed',
134
+ ...(run.output != null ? { output: run.output } : {}),
135
+ ...(run.error != null ? { error: run.error } : {}),
138
136
  };
139
137
  }
140
138
 
@@ -2,10 +2,11 @@ import type { OperationResult } from '../client';
2
2
  import { EigenpalError } from '../errors';
3
3
  import type { Client } from '../generated/client';
4
4
  import {
5
- runsArtifactGet,
5
+ runsArtifactsList,
6
6
  runsCancel,
7
7
  runsComparisonGet,
8
8
  runsConnect,
9
+ runsDefinitionGet,
9
10
  runsExpectedCreate,
10
11
  runsExpectedFileDelete,
11
12
  runsExpectedFileGet,
@@ -24,7 +25,8 @@ import {
24
25
  runsTraceGet,
25
26
  } from '../generated/sdk.gen';
26
27
  import type {
27
- RunRerunRequest,
28
+ RunArtifactsResponse,
29
+ RunDefinitionResponse,
28
30
  RunsCancelResponse,
29
31
  RunsComparisonGetResponse,
30
32
  RunsConnectResponse,
@@ -54,6 +56,21 @@ type Dispatch = <T>(call: () => Promise<OperationResult<T>>) => Promise<T>;
54
56
  export type ListRunsOptions = NonNullable<RunsListData['query']> & { signal?: AbortSignal };
55
57
  type SignalOptions = { signal?: AbortSignal };
56
58
 
59
+ /**
60
+ * Expandable sections for `runs.get`. Each token adds one nested object with
61
+ * the same name. Terminal runs expose top-level `output`, `files`, and `error`.
62
+ */
63
+ export type RunExpandSection = 'input' | 'usage' | 'execution' | 'debug';
64
+
65
+ /** Typed section list, or a raw comma-separated string for forward compat. */
66
+ export type RunExpand = readonly RunExpandSection[] | (string & {});
67
+
68
+ function formatExpand(expand: RunExpand | undefined): string | undefined {
69
+ if (expand === undefined) return undefined;
70
+ const joined = typeof expand === 'string' ? expand : expand.join(',');
71
+ return joined.length > 0 ? joined : undefined;
72
+ }
73
+
57
74
  export class RunsResource {
58
75
  public readonly feedback: RunsFeedbackResource;
59
76
  public readonly expected: RunsExpectedResource;
@@ -69,7 +86,7 @@ export class RunsResource {
69
86
  this.feedback = new RunsFeedbackResource(client, dispatch);
70
87
  this.expected = new RunsExpectedResource(client, dispatch);
71
88
  this.files = new RunsFilesResource(client, dispatch);
72
- this.artifacts = new RunsArtifactsResource(client);
89
+ this.artifacts = new RunsArtifactsResource(client, dispatch);
73
90
  this.comparison = new RunsComparisonResource(client, dispatch);
74
91
  this.trace = new RunsTraceResource(client, dispatch);
75
92
  }
@@ -79,16 +96,24 @@ export class RunsResource {
79
96
  return this.dispatch(() => runsList({ client: this.client, query, signal }));
80
97
  }
81
98
 
82
- async get(runId: string, options: { include?: string; signal?: AbortSignal } = {}) {
83
- const response = await this.dispatch<RunsGetResponse>(() =>
99
+ /**
100
+ * Fetch the canonical grouped run object. Terminal runs include top-level
101
+ * `output`, `files`, and `error`. Pass `expand` (for example
102
+ * `['usage', 'execution']`) to add optional nested detail objects.
103
+ */
104
+ async get(
105
+ runId: string,
106
+ options: { expand?: RunExpand; signal?: AbortSignal } = {}
107
+ ): Promise<RunsGetResponse> {
108
+ const expand = formatExpand(options.expand);
109
+ return this.dispatch<RunsGetResponse>(() =>
84
110
  runsGet({
85
111
  client: this.client,
86
112
  path: { id: runId },
87
- query: options.include ? { include: options.include } : {},
113
+ query: expand ? { expand } : {},
88
114
  signal: options.signal,
89
115
  })
90
116
  );
91
- return response.run;
92
117
  }
93
118
 
94
119
  async cancel(runId: string, options: SignalOptions = {}): Promise<RunsCancelResponse> {
@@ -99,11 +124,11 @@ export class RunsResource {
99
124
 
100
125
  async rerun(
101
126
  runId: string,
102
- body: RunRerunRequest = {},
127
+ query: { version?: string; wait_for_completion?: number } = {},
103
128
  options: SignalOptions = {}
104
129
  ): Promise<RunsRerunResponse> {
105
130
  return this.dispatch(() =>
106
- runsRerun({ client: this.client, path: { id: runId }, body, signal: options.signal })
131
+ runsRerun({ client: this.client, path: { id: runId }, query, signal: options.signal })
107
132
  );
108
133
  }
109
134
 
@@ -120,10 +145,10 @@ export class RunsResource {
120
145
  const mode = options.baseline ? 'baseline' : 'expected';
121
146
  const [reference, target] = await Promise.all([
122
147
  this.get(referenceRunId, {
123
- include: mode === 'baseline' ? 'detail,files,output' : 'detail,expected',
148
+ expand: ['execution'],
124
149
  signal: options.signal,
125
150
  }),
126
- this.get(runId, { include: 'detail,files,output', signal: options.signal }),
151
+ this.get(runId, { expand: ['execution'], signal: options.signal }),
127
152
  ]);
128
153
 
129
154
  if (isWorkflowRun(reference) && isWorkflowRun(target)) {
@@ -156,6 +181,12 @@ export class RunsResource {
156
181
  runsConnect({ client: this.client, path: { id: runId }, signal: options.signal })
157
182
  );
158
183
  }
184
+
185
+ async definition(runId: string, options: SignalOptions = {}): Promise<RunDefinitionResponse> {
186
+ return this.dispatch(() =>
187
+ runsDefinitionGet({ client: this.client, path: { id: runId }, signal: options.signal })
188
+ );
189
+ }
159
190
  }
160
191
 
161
192
  type RunRecord = Record<string, unknown>;
@@ -174,19 +205,57 @@ export type RunComparisonReport = {
174
205
  warnings?: string[];
175
206
  };
176
207
 
177
- function isWorkflowRun(run: unknown): run is RunRecord & { stepExecutions: unknown[] } {
178
- return isRecord(run) && Array.isArray(run.stepExecutions);
208
+ function isWorkflowRun(run: unknown): run is RunRecord {
209
+ return isRecord(run) && run.type === 'workflow';
179
210
  }
180
211
 
181
212
  function isRecord(value: unknown): value is RunRecord {
182
213
  return value != null && typeof value === 'object' && !Array.isArray(value);
183
214
  }
184
215
 
216
+ function runExecution(run: RunRecord): RunRecord {
217
+ return isRecord(run.execution) ? run.execution : {};
218
+ }
219
+
220
+ function runResult(run: RunRecord): RunRecord {
221
+ return isRecord(run.result) ? run.result : {};
222
+ }
223
+
224
+ export function runOutput(run: unknown): unknown {
225
+ if (!isRecord(run)) return undefined;
226
+ if (run.output !== undefined) return run.output;
227
+ return runResult(run).output;
228
+ }
229
+
230
+ export function runUsage(run: unknown): unknown {
231
+ return isRecord(run) ? run.usage : undefined;
232
+ }
233
+
234
+ export function runExecutionDetails(run: unknown): unknown {
235
+ return isRecord(run) ? run.execution : undefined;
236
+ }
237
+
238
+ function workflowSteps(run: RunRecord): unknown[] {
239
+ const steps = runExecution(run).steps;
240
+ return Array.isArray(steps) ? steps : [];
241
+ }
242
+
243
+ function agentOutputFiles(run: RunRecord): unknown[] {
244
+ const rawFiles = runExecution(run).files;
245
+ const files: RunRecord = isRecord(rawFiles) ? rawFiles : {};
246
+ return Array.isArray(files.output) ? files.output : [];
247
+ }
248
+
249
+ function agentExpected(run: RunRecord): RunRecord {
250
+ const expected = runExecution(run).expected;
251
+ return isRecord(expected) ? expected : {};
252
+ }
253
+
185
254
  function compareWorkflowRuns(
186
255
  referenceRunId: string,
187
- reference: RunRecord & { stepExecutions: unknown[] },
256
+ reference: RunRecord,
188
257
  runId: string,
189
- target: RunRecord & { stepExecutions: unknown[] },
258
+ target: RunRecord,
190
259
  stepFilter?: string
191
260
  ): RunComparisonReport {
192
261
  const wanted = stepFilter
@@ -194,11 +263,11 @@ function compareWorkflowRuns(
194
263
  .map((step) => step.trim())
195
264
  .filter(Boolean);
196
265
  const targetSteps = new Map(
197
- target.stepExecutions
266
+ workflowSteps(target)
198
267
  .filter(isRecord)
199
268
  .map((step) => [String(step.stepName ?? step.name ?? step.id ?? ''), step])
200
269
  );
201
- const steps = reference.stepExecutions
270
+ const steps = workflowSteps(reference)
202
271
  .filter(isRecord)
203
272
  .filter(
204
273
  (step) => !wanted?.length || wanted.includes(String(step.stepName ?? step.name ?? step.id))
@@ -237,10 +306,13 @@ function compareArtifactRuns(
237
306
  ): RunComparisonReport {
238
307
  const referenceRun = isRecord(reference) ? reference : {};
239
308
  const targetRun = isRecord(target) ? target : {};
240
- const expectedValue = mode === 'baseline' ? referenceRun.output : referenceRun.expected;
309
+ const expectedValue =
310
+ mode === 'baseline' ? runOutput(referenceRun) : agentExpected(referenceRun).output;
241
311
  const expectedFiles =
242
- mode === 'baseline' ? names(referenceRun.resultFiles) : names(referenceRun.expectedFiles);
243
- const outputFiles = names(targetRun.resultFiles);
312
+ mode === 'baseline'
313
+ ? names(agentOutputFiles(referenceRun))
314
+ : names(agentExpected(referenceRun).files);
315
+ const outputFiles = names(agentOutputFiles(targetRun));
244
316
  const missing = expectedFiles.filter(
245
317
  (name) =>
246
318
  !outputFiles.some(
@@ -262,7 +334,7 @@ function compareArtifactRuns(
262
334
  (out) => comparableName(out, normalizeDates) === comparableName(name, normalizeDates)
263
335
  ) ?? name,
264
336
  }));
265
- const jsonDifferences = diffJson(expectedValue, targetRun.output);
337
+ const jsonDifferences = diffJson(expectedValue, runOutput(targetRun));
266
338
  return {
267
339
  status:
268
340
  jsonDifferences.length === 0 && missing.length === 0 && extra.length === 0 ? 'pass' : 'fail',
@@ -479,14 +551,23 @@ export class RunsFilesResource {
479
551
  }
480
552
 
481
553
  export class RunsArtifactsResource {
482
- constructor(private readonly client: Client) {}
554
+ constructor(
555
+ private readonly client: Client,
556
+ private readonly dispatch: Dispatch
557
+ ) {}
558
+
559
+ async list(runId: string, options: SignalOptions = {}): Promise<RunArtifactsResponse> {
560
+ const { signal } = options;
561
+ return this.dispatch(() =>
562
+ runsArtifactsList({ client: this.client, path: { id: runId }, signal })
563
+ );
564
+ }
483
565
 
484
566
  async download(runId: string, path: string, options: SignalOptions = {}): Promise<Blob> {
485
567
  return downloadBlob(
486
568
  () =>
487
- runsArtifactGet({
488
- client: this.client,
489
- path: { id: runId, path },
569
+ this.client.get({
570
+ url: `/api/v1/runs/${encodeURIComponent(runId)}/artifacts/${encodeArtifactPath(path)}`,
490
571
  parseAs: 'blob',
491
572
  signal: options.signal,
492
573
  }) as Promise<OperationResult<Blob>>
@@ -546,3 +627,7 @@ async function downloadBlob(call: () => Promise<OperationResult<Blob>>): Promise
546
627
  status: result.response?.status ?? 0,
547
628
  });
548
629
  }
630
+
631
+ function encodeArtifactPath(path: string): string {
632
+ return path.split('/').map(encodeURIComponent).join('/');
633
+ }
@@ -11,5 +11,5 @@ import type { CreateClientConfig } from './generated/client.gen';
11
11
  */
12
12
  export const createClientConfig: CreateClientConfig = (config) => ({
13
13
  ...config,
14
- baseUrl: config?.baseUrl ?? 'https://app.eigenpal.com',
14
+ baseUrl: config?.baseUrl ?? 'https://studio.eigenpal.com',
15
15
  });
package/src/telemetry.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  export const SDK_LANGUAGE = 'typescript';
20
20
  // Rewritten at publish time by scripts/render-sdk-versions.sh.
21
21
  // Keep this string literal exactly stable — sed matches on it.
22
- export const SDK_VERSION = '0.6.16';
22
+ export const SDK_VERSION = '0.7.0';
23
23
 
24
24
  function detectRuntime(): string {
25
25
  const g = globalThis as unknown as {