@eigenpal/sdk 0.5.3 → 0.5.5
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/CHANGELOG.md +5 -1
- package/package.json +1 -1
- package/src/generated/types.gen.ts +4 -0
- package/src/index.ts +2 -1
- package/src/lib/files.ts +161 -80
- package/src/resources/agents.ts +1 -1
- package/src/resources/executions.ts +23 -17
- package/src/resources/workflows.ts +1 -1
- package/src/telemetry.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# @eigenpal/sdk
|
|
2
2
|
|
|
3
|
-
## 0.5.
|
|
3
|
+
## 0.5.5
|
|
4
4
|
|
|
5
5
|
### Major Changes
|
|
6
6
|
|
|
7
7
|
- c15ce88: Rename agent API calls to `/v1/agents` and scope execution helpers under their owning workflow or agent.
|
|
8
8
|
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- d1d3260: `client.workflows.run` (and agent runs) now accept a Node readable stream (`fs.createReadStream('contract.pdf')`) as a file input, inferring the upload filename from the stream's path. Adds a `toFile(content, filename, mimeType?)` helper for attaching a filename to raw bytes (`Buffer`, `ArrayBuffer`, or `Blob`). Streams are drained to bytes before the request is sent, so a retried request can replay the body.
|
|
12
|
+
|
|
9
13
|
### Patch Changes
|
|
10
14
|
|
|
11
15
|
- 5909b21: Add execution-scoped agent feedback filters and expected artifact management to the API, CLI, and SDKs.
|
package/package.json
CHANGED
|
@@ -185,6 +185,9 @@ export type AgentSummary = {
|
|
|
185
185
|
slug: string;
|
|
186
186
|
name: string;
|
|
187
187
|
description?: string | null;
|
|
188
|
+
sourceIntegrity?: 'healthy' | 'source_missing' | 'unregistered';
|
|
189
|
+
latestVersion?: string | null;
|
|
190
|
+
latestCommit?: string | null;
|
|
188
191
|
config?: {
|
|
189
192
|
[key: string]: unknown;
|
|
190
193
|
};
|
|
@@ -1497,6 +1500,7 @@ export type AgentsListData = {
|
|
|
1497
1500
|
search?: string;
|
|
1498
1501
|
limit?: number;
|
|
1499
1502
|
offset?: number;
|
|
1503
|
+
includeArchived?: boolean;
|
|
1500
1504
|
};
|
|
1501
1505
|
url: '/api/v1/agents';
|
|
1502
1506
|
};
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,8 @@ export {
|
|
|
32
32
|
EigenpalValidationError,
|
|
33
33
|
} from './errors';
|
|
34
34
|
|
|
35
|
-
export
|
|
35
|
+
export { toFile } from './lib/files';
|
|
36
|
+
export type { FileDescriptor, FileInput, NodeReadableStream } from './lib/files';
|
|
36
37
|
export type {
|
|
37
38
|
ListAgentExecutionsOptions,
|
|
38
39
|
ListAgentsOptions,
|
package/src/lib/files.ts
CHANGED
|
@@ -1,25 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File-input helpers for `client.workflows.run()
|
|
2
|
+
* File-input helpers for `client.workflows.run()` (and agent runs).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A workflow input value is treated as a file when it is one of:
|
|
5
|
+
* - a Node readable stream — `fs.createReadStream('contract.pdf')`
|
|
6
|
+
* - a `File` or `Blob`
|
|
7
|
+
* - a `{ content, filename, mimeType? }` descriptor — build one with `toFile()`
|
|
8
|
+
*
|
|
9
|
+
* File values are auto-detected and uploaded as `multipart/form-data`
|
|
10
|
+
* (matching `curl -F`) — no base64 round-trip.
|
|
7
11
|
*/
|
|
8
12
|
|
|
13
|
+
const DEFAULT_MIME = 'application/octet-stream';
|
|
14
|
+
|
|
15
|
+
/** Common file extensions → MIME type, for upload filename inference. */
|
|
16
|
+
const MIME_BY_EXT: Record<string, string> = {
|
|
17
|
+
pdf: 'application/pdf',
|
|
18
|
+
png: 'image/png',
|
|
19
|
+
jpg: 'image/jpeg',
|
|
20
|
+
jpeg: 'image/jpeg',
|
|
21
|
+
gif: 'image/gif',
|
|
22
|
+
webp: 'image/webp',
|
|
23
|
+
txt: 'text/plain',
|
|
24
|
+
csv: 'text/csv',
|
|
25
|
+
json: 'application/json',
|
|
26
|
+
xml: 'application/xml',
|
|
27
|
+
html: 'text/html',
|
|
28
|
+
md: 'text/markdown',
|
|
29
|
+
doc: 'application/msword',
|
|
30
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
31
|
+
xls: 'application/vnd.ms-excel',
|
|
32
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
33
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
34
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Guess a MIME type from a filename extension; falls back to octet-stream. */
|
|
38
|
+
function guessMimeType(filename: string): string {
|
|
39
|
+
const ext = filename.includes('.') ? filename.split('.').pop()!.toLowerCase() : '';
|
|
40
|
+
return MIME_BY_EXT[ext] ?? DEFAULT_MIME;
|
|
41
|
+
}
|
|
42
|
+
|
|
9
43
|
/**
|
|
10
|
-
* Explicit file descriptor — raw bytes plus metadata.
|
|
11
|
-
* `Buffer` / `Uint8Array` / `ArrayBuffer`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* await client.workflows.run('extract-invoice', {
|
|
16
|
-
* contract_document: {
|
|
17
|
-
* content: buffer,
|
|
18
|
-
* filename: 'contract.pdf',
|
|
19
|
-
* mimeType: 'application/pdf',
|
|
20
|
-
* },
|
|
21
|
-
* });
|
|
22
|
-
* ```
|
|
44
|
+
* Explicit file descriptor — raw bytes plus metadata. Build one with
|
|
45
|
+
* `toFile()` when you have a `Buffer` / `Uint8Array` / `ArrayBuffer` / `Blob`
|
|
46
|
+
* in memory rather than a path or a stream.
|
|
23
47
|
*/
|
|
24
48
|
export interface FileDescriptor {
|
|
25
49
|
content: ArrayBuffer | ArrayBufferView | Blob;
|
|
@@ -28,27 +52,63 @@ export interface FileDescriptor {
|
|
|
28
52
|
}
|
|
29
53
|
|
|
30
54
|
/**
|
|
31
|
-
*
|
|
55
|
+
* A Node readable stream — typically `fs.createReadStream('contract.pdf')`.
|
|
56
|
+
* The SDK drains it and infers the upload filename from `path`.
|
|
32
57
|
*/
|
|
33
|
-
export
|
|
58
|
+
export interface NodeReadableStream extends AsyncIterable<unknown> {
|
|
59
|
+
/** Source path; used to infer the upload filename. */
|
|
60
|
+
readonly path?: string;
|
|
61
|
+
}
|
|
34
62
|
|
|
35
|
-
|
|
63
|
+
/** Any value the SDK accepts as a "file" workflow input. */
|
|
64
|
+
export type FileInput = Blob | FileDescriptor | NodeReadableStream;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Attach a filename (and optional MIME type) to raw bytes. The escape hatch
|
|
68
|
+
* for when you have a `Buffer` / `ArrayBuffer` / `Blob` in memory rather than
|
|
69
|
+
* a file path or stream.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* await client.workflows.run('extract-invoice', {
|
|
73
|
+
* contract_document: toFile(buffer, 'contract.pdf'),
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function toFile(
|
|
78
|
+
content: ArrayBuffer | ArrayBufferView | Blob,
|
|
79
|
+
filename: string,
|
|
80
|
+
mimeType?: string
|
|
81
|
+
): FileDescriptor {
|
|
82
|
+
return { content, filename, mimeType: mimeType ?? guessMimeType(filename) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Detect a Node readable stream (`fs.createReadStream`, etc.). */
|
|
86
|
+
function isReadStream(value: unknown): value is NodeReadableStream {
|
|
87
|
+
if (value === null || typeof value !== 'object') return false;
|
|
88
|
+
const v = value as Record<PropertyKey, unknown>;
|
|
89
|
+
return (
|
|
90
|
+
typeof v[Symbol.asyncIterator] === 'function' &&
|
|
91
|
+
(typeof v.pipe === 'function' || typeof v.read === 'function')
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Detect an explicit `{ content, filename }` descriptor. */
|
|
96
|
+
function isFileDescriptor(value: unknown): value is FileDescriptor {
|
|
97
|
+
if (value === null || typeof value !== 'object') return false;
|
|
98
|
+
const v = value as { content?: unknown; filename?: unknown };
|
|
99
|
+
return (
|
|
100
|
+
typeof v.filename === 'string' &&
|
|
101
|
+
v.content != null &&
|
|
102
|
+
(v.content instanceof ArrayBuffer ||
|
|
103
|
+
ArrayBuffer.isView(v.content) ||
|
|
104
|
+
(typeof Blob !== 'undefined' && v.content instanceof Blob))
|
|
105
|
+
);
|
|
106
|
+
}
|
|
36
107
|
|
|
37
108
|
export function isFileInput(value: unknown): value is FileInput {
|
|
38
109
|
if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
|
|
39
|
-
if (value
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
typeof v.filename === 'string' &&
|
|
43
|
-
v.content != null &&
|
|
44
|
-
(v.content instanceof ArrayBuffer ||
|
|
45
|
-
ArrayBuffer.isView(v.content) ||
|
|
46
|
-
(typeof Blob !== 'undefined' && v.content instanceof Blob))
|
|
47
|
-
) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
110
|
+
if (isReadStream(value)) return true;
|
|
111
|
+
return isFileDescriptor(value);
|
|
52
112
|
}
|
|
53
113
|
|
|
54
114
|
export function hasFileInput(input: Record<string, unknown> | undefined): boolean {
|
|
@@ -59,23 +119,45 @@ export function hasFileInput(input: Record<string, unknown> | undefined): boolea
|
|
|
59
119
|
return false;
|
|
60
120
|
}
|
|
61
121
|
|
|
62
|
-
/**
|
|
63
|
-
function
|
|
64
|
-
|
|
122
|
+
/** Last path segment of a (possibly nested) file path. */
|
|
123
|
+
function basename(path: string): string {
|
|
124
|
+
const segments = path.split(/[\\/]/);
|
|
125
|
+
return segments[segments.length - 1] || 'file';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve a `FileInput` to a `{ blob, filename }` pair for `FormData.append`.
|
|
130
|
+
*
|
|
131
|
+
* Streams are drained to bytes *here* — eagerly, before the request is sent —
|
|
132
|
+
* so the body can be replayed if the SDK retries the request. (A consumed
|
|
133
|
+
* stream cannot be re-read; a Blob can.)
|
|
134
|
+
*/
|
|
135
|
+
async function resolveFileBlob(file: FileInput): Promise<{ blob: Blob; filename: string }> {
|
|
136
|
+
// `File` extends `Blob`, so this branch covers both.
|
|
65
137
|
if (typeof Blob !== 'undefined' && file instanceof Blob) {
|
|
66
|
-
|
|
67
|
-
|
|
138
|
+
return { blob: file, filename: (file as File).name || 'file' };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Node readable stream — drain it into a Blob now.
|
|
142
|
+
if (isReadStream(file)) {
|
|
143
|
+
const filename = typeof file.path === 'string' ? basename(file.path) : 'file';
|
|
144
|
+
const parts: BlobPart[] = [];
|
|
145
|
+
for await (const chunk of file) {
|
|
146
|
+
parts.push(chunk as BlobPart);
|
|
147
|
+
}
|
|
148
|
+
return { blob: new Blob(parts, { type: guessMimeType(filename) }), filename };
|
|
68
149
|
}
|
|
150
|
+
|
|
151
|
+
// Explicit descriptor.
|
|
69
152
|
const desc = file as FileDescriptor;
|
|
70
|
-
const content = desc.content;
|
|
71
153
|
const type = desc.mimeType ?? DEFAULT_MIME;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} else {
|
|
76
|
-
// ArrayBuffer or ArrayBufferView — wrap in a Blob.
|
|
77
|
-
blob = new Blob([content as BlobPart], { type });
|
|
154
|
+
if (!(desc.content instanceof Blob)) {
|
|
155
|
+
// ArrayBuffer / ArrayBufferView — wrap the bytes in a typed Blob.
|
|
156
|
+
return { blob: new Blob([desc.content as BlobPart], { type }), filename: desc.filename };
|
|
78
157
|
}
|
|
158
|
+
// Already a Blob — reuse it, re-slicing only to apply a different MIME type.
|
|
159
|
+
const blob =
|
|
160
|
+
desc.content.type === type ? desc.content : desc.content.slice(0, desc.content.size, type);
|
|
79
161
|
return { blob, filename: desc.filename };
|
|
80
162
|
}
|
|
81
163
|
|
|
@@ -86,38 +168,46 @@ export interface MultipartParts {
|
|
|
86
168
|
fileCount: number;
|
|
87
169
|
}
|
|
88
170
|
|
|
171
|
+
/** Drain `input` into a FormData, appending each detected file as a field. */
|
|
172
|
+
async function appendFiles(
|
|
173
|
+
fd: FormData,
|
|
174
|
+
input: Record<string, unknown> | undefined
|
|
175
|
+
): Promise<{ scalars: Record<string, unknown>; fileCount: number }> {
|
|
176
|
+
const scalars: Record<string, unknown> = {};
|
|
177
|
+
let fileCount = 0;
|
|
178
|
+
for (const [key, value] of Object.entries(input ?? {})) {
|
|
179
|
+
if (isFileInput(value)) {
|
|
180
|
+
const { blob, filename } = await resolveFileBlob(value);
|
|
181
|
+
fd.append(key, blob, filename);
|
|
182
|
+
fileCount += 1;
|
|
183
|
+
} else {
|
|
184
|
+
scalars[key] = value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { scalars, fileCount };
|
|
188
|
+
}
|
|
189
|
+
|
|
89
190
|
/**
|
|
90
|
-
* Build a `multipart/form-data` body
|
|
91
|
-
*
|
|
191
|
+
* Build a `multipart/form-data` body matching what `processMultipartRunBody`
|
|
192
|
+
* on the server expects:
|
|
92
193
|
*
|
|
93
194
|
* - Each file in `input` becomes a top-level form field (key = input name).
|
|
94
195
|
* - Non-file inputs + overrides + trigger go in a `_json` text field.
|
|
95
196
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
197
|
+
* Async because stream inputs are drained to bytes here. Only top-level file
|
|
198
|
+
* values are extracted — files nested inside arrays / objects keep their
|
|
199
|
+
* position in the JSON sidecar (the server has no nested-upload path).
|
|
99
200
|
*/
|
|
100
|
-
export function buildMultipart(args: {
|
|
201
|
+
export async function buildMultipart(args: {
|
|
101
202
|
input?: Record<string, unknown>;
|
|
102
203
|
overrides?: { steps?: Record<string, Record<string, unknown>> };
|
|
103
204
|
trigger?: 'api' | 'cli';
|
|
104
|
-
}): MultipartParts {
|
|
205
|
+
}): Promise<MultipartParts> {
|
|
105
206
|
const fd = new FormData();
|
|
106
|
-
const
|
|
107
|
-
let fileCount = 0;
|
|
108
|
-
|
|
109
|
-
for (const [key, value] of Object.entries(args.input ?? {})) {
|
|
110
|
-
if (isFileInput(value)) {
|
|
111
|
-
const { blob, filename } = toBlobAndFilename(value);
|
|
112
|
-
fd.append(key, blob, filename);
|
|
113
|
-
fileCount += 1;
|
|
114
|
-
} else {
|
|
115
|
-
inputScalars[key] = value;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
207
|
+
const { scalars, fileCount } = await appendFiles(fd, args.input);
|
|
118
208
|
|
|
119
209
|
const sidecar: Record<string, unknown> = {};
|
|
120
|
-
if (Object.keys(
|
|
210
|
+
if (Object.keys(scalars).length > 0) sidecar.input = scalars;
|
|
121
211
|
if (args.overrides) sidecar.overrides = args.overrides;
|
|
122
212
|
if (args.trigger) sidecar.trigger = args.trigger;
|
|
123
213
|
if (Object.keys(sidecar).length > 0) {
|
|
@@ -132,23 +222,14 @@ export function buildMultipart(args: {
|
|
|
132
222
|
* input object itself, unlike workflow runs where `_json` is a sidecar with
|
|
133
223
|
* `{ input, overrides, trigger }`.
|
|
134
224
|
*/
|
|
135
|
-
export function buildAgentMultipart(
|
|
225
|
+
export async function buildAgentMultipart(
|
|
226
|
+
input?: Record<string, unknown>
|
|
227
|
+
): Promise<MultipartParts> {
|
|
136
228
|
const fd = new FormData();
|
|
137
|
-
const
|
|
138
|
-
let fileCount = 0;
|
|
139
|
-
|
|
140
|
-
for (const [key, value] of Object.entries(input ?? {})) {
|
|
141
|
-
if (isFileInput(value)) {
|
|
142
|
-
const { blob, filename } = toBlobAndFilename(value);
|
|
143
|
-
fd.append(key, blob, filename);
|
|
144
|
-
fileCount += 1;
|
|
145
|
-
} else {
|
|
146
|
-
inputScalars[key] = value;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
229
|
+
const { scalars, fileCount } = await appendFiles(fd, input);
|
|
149
230
|
|
|
150
|
-
if (Object.keys(
|
|
151
|
-
fd.append('_json', JSON.stringify(
|
|
231
|
+
if (Object.keys(scalars).length > 0) {
|
|
232
|
+
fd.append('_json', JSON.stringify(scalars));
|
|
152
233
|
}
|
|
153
234
|
|
|
154
235
|
return { formData: fd, fileCount };
|
package/src/resources/agents.ts
CHANGED
|
@@ -409,7 +409,7 @@ export class AgentsResource {
|
|
|
409
409
|
: {};
|
|
410
410
|
|
|
411
411
|
if (hasFileInput(input)) {
|
|
412
|
-
const { formData } = buildAgentMultipart(input);
|
|
412
|
+
const { formData } = await buildAgentMultipart(input);
|
|
413
413
|
return this.dispatch<RunAgentResponse>(
|
|
414
414
|
() =>
|
|
415
415
|
this.client.post({
|
|
@@ -136,10 +136,14 @@ export class WorkflowExecutionsResource {
|
|
|
136
136
|
|
|
137
137
|
// Trigger async — we don't ask the server to wait, since we're polling.
|
|
138
138
|
const triggerQuery = options.version ? { version: options.version } : {};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
let runResult: RunWorkflowResponse;
|
|
140
|
+
if (hasFileInput(input)) {
|
|
141
|
+
// Build the multipart body once, up front — `dispatch` may retry the
|
|
142
|
+
// POST, and a drained stream cannot be replayed.
|
|
143
|
+
const { formData } = await buildMultipart({ input, overrides: options.overrides });
|
|
144
|
+
runResult = await this.dispatch<RunWorkflowResponse>(
|
|
145
|
+
() =>
|
|
146
|
+
this.client.post({
|
|
143
147
|
url: '/api/v1/workflows/{id}/run',
|
|
144
148
|
path: { id: workflowId },
|
|
145
149
|
query: triggerQuery,
|
|
@@ -147,20 +151,22 @@ export class WorkflowExecutionsResource {
|
|
|
147
151
|
bodySerializer: null,
|
|
148
152
|
headers: { 'Content-Type': null },
|
|
149
153
|
signal: options.signal,
|
|
150
|
-
}) as Promise<OperationResult<RunWorkflowResponse
|
|
154
|
+
}) as Promise<OperationResult<RunWorkflowResponse>>
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
runResult = await this.dispatch<RunWorkflowResponse>(() =>
|
|
158
|
+
workflowsRun({
|
|
159
|
+
client: this.client,
|
|
160
|
+
path: { id: workflowId },
|
|
161
|
+
query: triggerQuery,
|
|
162
|
+
body: {
|
|
163
|
+
...(input !== undefined ? { input } : {}),
|
|
164
|
+
...(options.overrides ? { overrides: options.overrides } : {}),
|
|
165
|
+
},
|
|
166
|
+
signal: options.signal,
|
|
151
167
|
})
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
client: this.client,
|
|
155
|
-
path: { id: workflowId },
|
|
156
|
-
query: triggerQuery,
|
|
157
|
-
body: {
|
|
158
|
-
...(input !== undefined ? { input } : {}),
|
|
159
|
-
...(options.overrides ? { overrides: options.overrides } : {}),
|
|
160
|
-
},
|
|
161
|
-
signal: options.signal,
|
|
162
|
-
})
|
|
163
|
-
);
|
|
168
|
+
);
|
|
169
|
+
}
|
|
164
170
|
|
|
165
171
|
const { executionId } = runResult;
|
|
166
172
|
|
|
@@ -96,7 +96,7 @@ export class WorkflowsResource {
|
|
|
96
96
|
|
|
97
97
|
// File-bearing input → multipart/form-data (no base64 overhead).
|
|
98
98
|
if (hasFileInput(input)) {
|
|
99
|
-
const { formData } = buildMultipart({ input, overrides: options.overrides });
|
|
99
|
+
const { formData } = await buildMultipart({ input, overrides: options.overrides });
|
|
100
100
|
return this.dispatch<RunWorkflowResponse>(
|
|
101
101
|
() =>
|
|
102
102
|
this.client.post({
|
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.5.
|
|
22
|
+
export const SDK_VERSION = '0.5.5';
|
|
23
23
|
|
|
24
24
|
function detectRuntime(): string {
|
|
25
25
|
const g = globalThis as unknown as {
|