@pipelex/sdk 0.3.1 → 0.5.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.
@@ -0,0 +1,178 @@
1
+ /**
2
+ * `prepareInputs` — signature-driven input preparation. Resolves the target
3
+ * pipe's declared inputs via the explicit inputs template, interprets the
4
+ * caller's compact inputs top-down against it, uploads the file-bearing values,
5
+ * and returns rewritten inputs (canonical content carrying `pipelex-storage://`
6
+ * in `url`) plus one upload record per prepared asset.
7
+ *
8
+ * The classification mirrors the runtime: `pipelex`'s `input_normalizer` walks
9
+ * Image/Document contents (recognized by their `url`-bearing shape, incl. nested
10
+ * in structured content) and `resolve_uri` decides upload vs pass-through. The
11
+ * declared signature comes from the explicit template (`buildInputs`,
12
+ * `explicit: true`), whose canonical content shape is the classifier — the file
13
+ * signal is a value that is a dict containing a `url` key. See the shared
14
+ * behavior matrix (`wip/upload/behavior-matrix.md`) and `docs/input-preparation.md`.
15
+ */
16
+ import { InputPreparationError } from "./errors.js";
17
+ import { uploadFile } from "./upload.js";
18
+ const PIPELEX_STORAGE_SCHEME = "pipelex-storage://";
19
+ const HTTP_URL_RE = /^https?:\/\//i;
20
+ /** Strict plain-object test — excludes arrays, `Uint8Array`, `Blob`, and other exotics. */
21
+ function isPlainObject(value) {
22
+ return Object.prototype.toString.call(value) === "[object Object]";
23
+ }
24
+ /** A canonical Image/Document content is a plain object carrying a `url` key. */
25
+ function isFileContent(node) {
26
+ return isPlainObject(node) && "url" in node;
27
+ }
28
+ /** Decode a `data:` URL into bytes plus its MIME type. */
29
+ function decodeDataUrl(dataUrl) {
30
+ const comma = dataUrl.indexOf(",");
31
+ if (comma < 0) {
32
+ throw new InputPreparationError(`Malformed data URL (no comma separator): ${dataUrl.slice(0, 32)}…`);
33
+ }
34
+ const header = dataUrl.slice(5, comma); // strip "data:"
35
+ const payload = dataUrl.slice(comma + 1);
36
+ const isBase64 = /;base64/i.test(header);
37
+ const contentType = header.split(";")[0] || "application/octet-stream";
38
+ // Decoding can throw on a malformed payload — a URIError from percent-decoding,
39
+ // or an InvalidCharacterError from `atob` on bad base64. Surface those as a typed
40
+ // InputPreparationError so a bad data URL stays within the preparation contract.
41
+ try {
42
+ if (isBase64) {
43
+ // Decode via atob in every runtime. atob rejects malformed base64 with an
44
+ // InvalidCharacterError — Buffer.from(payload, "base64") does NOT: in Node it
45
+ // silently drops invalid characters and returns truncated/empty bytes, which would
46
+ // upload corrupt content instead of failing the preparation contract. atob is a
47
+ // global in every supported runtime (engines.node >= 22.12).
48
+ return { bytes: base64ToBytes(payload), contentType };
49
+ }
50
+ const text = decodeURIComponent(payload);
51
+ return { bytes: new TextEncoder().encode(text), contentType };
52
+ }
53
+ catch (cause) {
54
+ throw new InputPreparationError(`Malformed data URL payload (${isBase64 ? "invalid base64" : "invalid percent-encoding"}): ${dataUrl.slice(0, 32)}…`, { cause });
55
+ }
56
+ }
57
+ /**
58
+ * Strict cross-runtime base64 decode via `atob` — the single decoder for data-URL
59
+ * payloads in every runtime. `atob` throws an `InvalidCharacterError` on malformed
60
+ * base64, unlike the lenient `Buffer.from(payload, "base64")`, so a bad payload
61
+ * fails the preparation contract instead of yielding truncated/empty bytes.
62
+ */
63
+ function base64ToBytes(base64) {
64
+ const binary = atob(base64);
65
+ const bytes = new Uint8Array(binary.length);
66
+ for (let index = 0; index < binary.length; index += 1)
67
+ bytes[index] = binary.charCodeAt(index);
68
+ return bytes;
69
+ }
70
+ /** Resolve one source (string reference or bytes) to the URL/URI to write, deduped by identity. */
71
+ function resolveSource(ctx, source) {
72
+ const cached = ctx.dedup.get(source);
73
+ if (cached !== undefined)
74
+ return cached;
75
+ const pending = doResolveSource(ctx, source);
76
+ ctx.dedup.set(source, pending);
77
+ return pending;
78
+ }
79
+ async function doResolveSource(ctx, source) {
80
+ if (typeof source === "string") {
81
+ if (source.startsWith(PIPELEX_STORAGE_SCHEME))
82
+ return source; // already prepared
83
+ if (HTTP_URL_RE.test(source))
84
+ return source; // reachable URL — pass through
85
+ if (source.startsWith("data:")) {
86
+ const { bytes, contentType } = decodeDataUrl(source);
87
+ const record = await uploadFile(ctx.client, bytes, { contentType });
88
+ ctx.uploads.push(record);
89
+ return record.uri;
90
+ }
91
+ // Anything else is a local filesystem path — Node only (uploadFile enforces it).
92
+ const record = await uploadFile(ctx.client, source);
93
+ ctx.uploads.push(record);
94
+ return record.uri;
95
+ }
96
+ if (source instanceof Blob || source instanceof ArrayBuffer || source instanceof Uint8Array) {
97
+ const record = await uploadFile(ctx.client, source);
98
+ ctx.uploads.push(record);
99
+ return record.uri;
100
+ }
101
+ // An unrecognized value sits at a file-bearing position (neither a source string,
102
+ // bytes, nor a canonical `{url}` content dict). Fail with a typed error rather than
103
+ // letting a raw TypeError escape from the byte-extraction path.
104
+ throw new InputPreparationError(`Unsupported value at a file input: expected a path string, bytes (Blob/File/ArrayBuffer/Uint8Array), ` +
105
+ `a data URL, an http(s)/pipelex-storage:// URL, or canonical {url} content; got ${typeof source}.`);
106
+ }
107
+ /** Resolve a value known to sit at a file position into canonical content with a rewritten `url`. */
108
+ async function resolveFilePosition(ctx, callerValue) {
109
+ if (isFileContent(callerValue)) {
110
+ const resolved = await resolveSource(ctx, callerValue.url);
111
+ return { ...callerValue, url: resolved };
112
+ }
113
+ const resolved = await resolveSource(ctx, callerValue);
114
+ return { url: resolved };
115
+ }
116
+ /** Template-guided walk: a template node that is canonical file content marks a file position. */
117
+ async function resolveNode(ctx, templateNode, callerValue) {
118
+ if (isFileContent(templateNode)) {
119
+ return resolveFilePosition(ctx, callerValue);
120
+ }
121
+ if (Array.isArray(templateNode) && templateNode.length > 0) {
122
+ const elementTemplate = templateNode[0];
123
+ if (Array.isArray(callerValue)) {
124
+ return Promise.all(callerValue.map((item) => resolveNode(ctx, elementTemplate, item)));
125
+ }
126
+ return callerValue; // shape mismatch — leave it for the run to reject
127
+ }
128
+ if (isPlainObject(templateNode) && isPlainObject(callerValue)) {
129
+ const result = { ...callerValue };
130
+ for (const key of Object.keys(templateNode)) {
131
+ if (key in callerValue) {
132
+ result[key] = await resolveNode(ctx, templateNode[key], callerValue[key]);
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ return callerValue; // scalar (text/number/…) or shape mismatch — pass through
138
+ }
139
+ /**
140
+ * Prepare a pipe's inputs: upload local/byte/data-URL assets at the signature's
141
+ * file-bearing positions and return copy-on-write rewritten inputs plus upload
142
+ * records. HTTP(S) URLs and existing `pipelex-storage://` URIs pass through
143
+ * unchanged. All failures are raised before any run is created.
144
+ *
145
+ * The declared signature is resolved from the closure; a closure that does not
146
+ * resolve throws {@link InputPreparationError}. No-verdict conditions from the
147
+ * signature route (unknown `pipe_ref`, auth, server fault) surface as the build
148
+ * route's `ApiResponseError`.
149
+ */
150
+ export async function prepareInputs(client, request) {
151
+ const report = await client.buildInputs({
152
+ files: request.files,
153
+ ...(request.pipe_ref === undefined ? {} : { pipe_ref: request.pipe_ref }),
154
+ format: "json",
155
+ explicit: true,
156
+ });
157
+ if (!report.is_valid) {
158
+ const first = report.validation_errors[0]?.message ?? report.message;
159
+ throw new InputPreparationError(`Cannot prepare inputs: the method signature did not resolve — ${first}`);
160
+ }
161
+ // We requested `format: "json"`, so the valid arm carries the envelope in `inputs`.
162
+ if (report.format !== "json") {
163
+ throw new InputPreparationError(`Cannot prepare inputs: expected a JSON inputs template, got "${report.format}".`);
164
+ }
165
+ const template = report.inputs;
166
+ const ctx = { client, uploads: [], dedup: new Map() };
167
+ const rewritten = { ...request.inputs };
168
+ for (const [name, callerValue] of Object.entries(request.inputs)) {
169
+ const entry = template[name];
170
+ if (!isPlainObject(entry) || !("content" in entry)) {
171
+ // Not a declared input (or an unexpected envelope) — pass through untouched.
172
+ continue;
173
+ }
174
+ rewritten[name] = await resolveNode(ctx, entry.content, callerValue);
175
+ }
176
+ return { inputs: rewritten, uploads: ctx.uploads };
177
+ }
178
+ //# sourceMappingURL=prepare-inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-inputs.js","sourceRoot":"","sources":["../src/prepare-inputs.ts"],"names":[],"mappings":"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"}
package/dist/runs.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { DictPipeOutput } from "./models.js";
1
2
  /**
2
3
  * Run-lifecycle types + polling for the hosted polling surface (`/v1/runs/*`).
3
4
  *
@@ -55,6 +56,61 @@ export interface RunRead extends RunPublic {
55
56
  degraded: boolean;
56
57
  retry_after_seconds?: number | null;
57
58
  }
59
+ /**
60
+ * One inference call's token usage — the client-facing wire record.
61
+ *
62
+ * Mirrors the runtime's `TokensUsageRecord`, specified in
63
+ * `docs/specs/pipelex-mthds-protocol.md#tokensusage-records-on-run-artifacts`. The same
64
+ * shape rides both surfaces: the durable `tokens_usages.json` artifact that the hosted
65
+ * results route relays, and the blocking execute response's `pipe_output.tokens_usages`.
66
+ *
67
+ * Every field is optional and the index signature is open **on purpose**. A record the
68
+ * current runtime emits always carries the full key set (a field with no value is an
69
+ * explicit `null`, never an omitted key), so callers may read any field directly. But
70
+ * durable artifacts written before the contract shipped are relayed verbatim and never
71
+ * migrated: such a record arrives with no `cost` and no `pipe_code`, and keeps its legacy
72
+ * `job_metadata` / `unit_costs` — reachable through the index signature, never contract
73
+ * fields.
74
+ *
75
+ * The enum-ish fields are open sets on the wire and stay `string` here — never frozen
76
+ * unions — so runtime enum churn is non-breaking for consumers.
77
+ */
78
+ export interface TokensUsageRecord {
79
+ /** Kind of inference. Known values: `llm`, `img_gen`, `extract`, `search`. */
80
+ model_type?: string | null;
81
+ /** Human model name (e.g. `gpt-4o`). */
82
+ inference_model_name?: string | null;
83
+ /** Provider/platform model id (e.g. `gpt-4o-2024-11-20`). */
84
+ inference_model_id?: string | null;
85
+ /** The pipe that made the call — what makes per-pipe cost attribution possible. */
86
+ pipe_code?: string | null;
87
+ /** Known values: `llm_job`, `img_gen_job`, `extract_job`, `search_job`, `jinja2_job`, `mock_job`. */
88
+ job_category?: string | null;
89
+ /**
90
+ * Known values: `llm_gen_text`, `llm_gen_object`, `img_gen_text_to_image`,
91
+ * `extract_pages`, `search_sourced_answer`, `search_structured`.
92
+ */
93
+ unit_job_id?: string | null;
94
+ /**
95
+ * Raw provider-reported token counts, keyed by token category (`input`, `input_cached`,
96
+ * `output`, `output_reasoning`, …). `input` is the joined total and `input_cached` a
97
+ * subset of it — the categories are NOT additive, so summing them double-counts.
98
+ */
99
+ nb_tokens_by_category?: Record<string, number> | null;
100
+ /**
101
+ * Computed USD cost of this call. Null when the model has no rate table at all (own-GPU,
102
+ * mock, dry run); `0` means a rate table existed and priced the call at zero. The
103
+ * underlying rate table never crosses the wire and there is no run-level aggregate — sum
104
+ * the records.
105
+ */
106
+ cost?: number | null;
107
+ /** ISO 8601 start of the call. */
108
+ started_at?: string | null;
109
+ /** ISO 8601 end of the call. Duration is derivable from the pair and deliberately not shipped. */
110
+ completed_at?: string | null;
111
+ /** Legacy fields on a pre-contract artifact relayed verbatim (`job_metadata`, `unit_costs`). */
112
+ [extension: string]: unknown;
113
+ }
58
114
  /**
59
115
  * Result artifacts for a completed run — `GET /v1/runs/{pipeline_run_id}/results`.
60
116
  *
@@ -81,7 +137,22 @@ export interface RunResults {
81
137
  * blocking-execute path only; null on the hosted path. Supplementary to `main_stuff`,
82
138
  * which is already resolved out of it; kept for consumers that need the whole working memory.
83
139
  */
84
- pipe_output?: Record<string, unknown> | null;
140
+ pipe_output?: DictPipeOutput | null;
141
+ /**
142
+ * Per-call usage records — token counts by category, computed `cost` in USD, model id —
143
+ * for LLM and img-gen/extract/search calls alike. On the hosted path this is the
144
+ * `tokens_usages.json` artifact's record list relayed verbatim; on the blocking path it is
145
+ * the execute response's `pipe_output.tokens_usages`. Null whenever assembly produced no
146
+ * list — it was off, it broke (see `usage_assembly_error`), or (hosted) the run was
147
+ * delivered before the artifact existed; `[]` when assembly ran and no inference happened.
148
+ */
149
+ tokens_usages?: TokensUsageRecord[] | null;
150
+ /**
151
+ * Non-null when the runner's usage assembly failed for the run. The ONLY field that
152
+ * separates "usage broke" from "usage was off" / "pre-artifact run" — all three leave
153
+ * `tokens_usages` null, so a caller that cares must branch on this, not on the list.
154
+ */
155
+ usage_assembly_error?: string | null;
85
156
  }
86
157
  /**
87
158
  * Single-shot result lookup outcome, discriminated on `state`:
package/dist/runs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../src/runs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `uploadFile` — the single-asset upload convenience over the raw `upload()` wire
3
+ * call. Accepts web byte types in every runtime (`Blob`/`File`/`ArrayBuffer`/
4
+ * `Uint8Array`) and, in Node only, a filesystem path string; returns an
5
+ * {@link UploadRecord} assembled client-side. See `docs/input-preparation.md`.
6
+ *
7
+ * The MIME type and size are known client-side at upload time, so the record is
8
+ * built without extending the `/v1/upload` response.
9
+ */
10
+ /** A local asset `uploadFile` accepts. A path string is Node-only (see {@link readLocalPath}). */
11
+ export type UploadableAsset = Blob | ArrayBuffer | Uint8Array | string;
12
+ /**
13
+ * The record `uploadFile` returns for a prepared asset. Beyond the source identity
14
+ * it guarantees the resulting `uri`, the MIME `contentType`, the `size` in bytes,
15
+ * and the `filename`. A content checksum is deliberately not included — it is
16
+ * best-effort at most, and within-preparation dedup keys on source identity.
17
+ */
18
+ export interface UploadRecord {
19
+ /** The `pipelex-storage://` reference for the uploaded asset. */
20
+ uri: string;
21
+ filename: string;
22
+ /** MIME type, known client-side. Best-effort for raw bytes (falls back to `application/octet-stream`). */
23
+ contentType: string;
24
+ /** Size of the uploaded bytes. */
25
+ size: number;
26
+ }
27
+ /** Per-call options for {@link uploadFile}. Fill in a filename/MIME for nameless byte sources. */
28
+ export interface UploadFileOptions {
29
+ filename?: string;
30
+ contentType?: string;
31
+ }
32
+ /** The subset of the client `uploadFile` needs — the raw base64 `upload` wire call. */
33
+ export interface UploadCapableClient {
34
+ upload(input: {
35
+ filename: string;
36
+ data: string;
37
+ content_type: string;
38
+ }): Promise<{
39
+ uri: string;
40
+ filename: string;
41
+ }>;
42
+ }
43
+ /** True when running under Node (has `process.versions.node`). */
44
+ export declare function isNodeRuntime(): boolean;
45
+ /** MIME guess from a filename extension; `application/octet-stream` when unknown. */
46
+ export declare function guessContentType(filename: string): string;
47
+ /**
48
+ * Read a filesystem path into bytes — Node only. A path string in a non-Node
49
+ * runtime cannot be read, so it fails as an invalid local source rather than
50
+ * being silently misread as text or a URL. `node:fs/promises` is imported
51
+ * dynamically and guarded by `isNodeRuntime()`, so it is only *loaded* in Node.
52
+ * Note: the dynamic import defers execution, not bundling — a browser-targeting
53
+ * bundler still statically resolves this literal specifier, so browser consumers
54
+ * must currently mark `node:*` external. Making the byte-only path bundle for the
55
+ * browser out of the box is an open packaging decision (see wip/pr-16-review-notes.md).
56
+ */
57
+ export declare function readLocalPath(path: string): Promise<Uint8Array>;
58
+ /** Base64-encode bytes, cross-runtime (Buffer when present, else chunked btoa). */
59
+ export declare function bytesToBase64(bytes: Uint8Array): string;
60
+ /**
61
+ * Upload one local asset and return its {@link UploadRecord}. Maps the raw
62
+ * `upload()` transport errors onto the semantic input-preparation errors: a
63
+ * `413` is a rejected asset, `401`/`403` an auth failure, `404` an unsupported
64
+ * upload capability, an unreachable host a transport failure.
65
+ */
66
+ export declare function uploadFile(client: UploadCapableClient, asset: UploadableAsset, options?: UploadFileOptions): Promise<UploadRecord>;
package/dist/upload.js ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * `uploadFile` — the single-asset upload convenience over the raw `upload()` wire
3
+ * call. Accepts web byte types in every runtime (`Blob`/`File`/`ArrayBuffer`/
4
+ * `Uint8Array`) and, in Node only, a filesystem path string; returns an
5
+ * {@link UploadRecord} assembled client-side. See `docs/input-preparation.md`.
6
+ *
7
+ * The MIME type and size are known client-side at upload time, so the record is
8
+ * built without extending the `/v1/upload` response.
9
+ */
10
+ import { InvalidLocalSourceError, RejectedAssetError, UploadAuthenticationError, UploadTransportError, UnsupportedUploadCapabilityError, } from "./errors.js";
11
+ import { ApiResponseError, ApiUnreachableError } from "./errors.js";
12
+ const DEFAULT_CONTENT_TYPE = "application/octet-stream";
13
+ const DEFAULT_FILENAME = "upload.bin";
14
+ const EXTENSION_MIME = {
15
+ png: "image/png",
16
+ jpg: "image/jpeg",
17
+ jpeg: "image/jpeg",
18
+ gif: "image/gif",
19
+ webp: "image/webp",
20
+ svg: "image/svg+xml",
21
+ bmp: "image/bmp",
22
+ tif: "image/tiff",
23
+ tiff: "image/tiff",
24
+ heic: "image/heic",
25
+ pdf: "application/pdf",
26
+ txt: "text/plain",
27
+ csv: "text/csv",
28
+ json: "application/json",
29
+ };
30
+ /** True when running under Node (has `process.versions.node`). */
31
+ export function isNodeRuntime() {
32
+ return (typeof process !== "undefined" && process.versions != null && process.versions.node != null);
33
+ }
34
+ /** Filename tail of a path, splitting on both separators (no `node:path` import needed). */
35
+ function basename(path) {
36
+ const parts = path.split(/[\\/]/);
37
+ return parts[parts.length - 1] || path;
38
+ }
39
+ /** MIME guess from a filename extension; `application/octet-stream` when unknown. */
40
+ export function guessContentType(filename) {
41
+ const dot = filename.lastIndexOf(".");
42
+ if (dot < 0 || dot === filename.length - 1)
43
+ return DEFAULT_CONTENT_TYPE;
44
+ const ext = filename.slice(dot + 1).toLowerCase();
45
+ return EXTENSION_MIME[ext] ?? DEFAULT_CONTENT_TYPE;
46
+ }
47
+ /**
48
+ * Read a filesystem path into bytes — Node only. A path string in a non-Node
49
+ * runtime cannot be read, so it fails as an invalid local source rather than
50
+ * being silently misread as text or a URL. `node:fs/promises` is imported
51
+ * dynamically and guarded by `isNodeRuntime()`, so it is only *loaded* in Node.
52
+ * Note: the dynamic import defers execution, not bundling — a browser-targeting
53
+ * bundler still statically resolves this literal specifier, so browser consumers
54
+ * must currently mark `node:*` external. Making the byte-only path bundle for the
55
+ * browser out of the box is an open packaging decision (see wip/pr-16-review-notes.md).
56
+ */
57
+ export async function readLocalPath(path) {
58
+ if (!isNodeRuntime()) {
59
+ throw new InvalidLocalSourceError(`Cannot read the local path "${path}" outside Node — path strings are Node-only. ` +
60
+ "In the browser or an edge runtime, pass the file as bytes (Blob/File/ArrayBuffer/Uint8Array).", path);
61
+ }
62
+ try {
63
+ const { readFile } = await import("node:fs/promises");
64
+ return await readFile(path);
65
+ }
66
+ catch (cause) {
67
+ throw new InvalidLocalSourceError(`Local file cannot be read: "${path}" (${cause.code ?? "read error"}).`, path, { cause });
68
+ }
69
+ }
70
+ /** Base64-encode bytes, cross-runtime (Buffer when present, else chunked btoa). */
71
+ export function bytesToBase64(bytes) {
72
+ if (typeof Buffer !== "undefined") {
73
+ return Buffer.from(bytes).toString("base64");
74
+ }
75
+ let binary = "";
76
+ const chunk = 0x8000; // stay under the String.fromCharCode argument cap
77
+ for (let index = 0; index < bytes.length; index += chunk) {
78
+ binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
79
+ }
80
+ return btoa(binary);
81
+ }
82
+ /** Normalize any accepted asset form into bytes plus a filename and MIME. */
83
+ async function toAssetBytes(asset, options) {
84
+ if (typeof asset === "string") {
85
+ const bytes = await readLocalPath(asset);
86
+ const filename = options.filename ?? basename(asset);
87
+ return { bytes, filename, contentType: options.contentType ?? guessContentType(filename) };
88
+ }
89
+ if (asset instanceof Uint8Array) {
90
+ return assetBytesFromBuffer(asset, options);
91
+ }
92
+ if (asset instanceof ArrayBuffer) {
93
+ return assetBytesFromBuffer(new Uint8Array(asset), options);
94
+ }
95
+ // Blob / File — File carries `.name`; both carry `.type`.
96
+ const bytes = new Uint8Array(await asset.arrayBuffer());
97
+ const name = options.filename ?? asset.name;
98
+ const filename = name && name.length > 0 ? name : DEFAULT_FILENAME;
99
+ const contentType = options.contentType ?? (asset.type || guessContentType(filename));
100
+ return { bytes, filename, contentType };
101
+ }
102
+ function assetBytesFromBuffer(bytes, options) {
103
+ const filename = options.filename ?? DEFAULT_FILENAME;
104
+ return { bytes, filename, contentType: options.contentType ?? guessContentType(filename) };
105
+ }
106
+ /**
107
+ * Upload one local asset and return its {@link UploadRecord}. Maps the raw
108
+ * `upload()` transport errors onto the semantic input-preparation errors: a
109
+ * `413` is a rejected asset, `401`/`403` an auth failure, `404` an unsupported
110
+ * upload capability, an unreachable host a transport failure.
111
+ */
112
+ export async function uploadFile(client, asset, options = {}) {
113
+ const { bytes, filename, contentType } = await toAssetBytes(asset, options);
114
+ const data = bytesToBase64(bytes);
115
+ let uploaded;
116
+ try {
117
+ uploaded = await client.upload({ filename, data, content_type: contentType });
118
+ }
119
+ catch (error) {
120
+ throw mapUploadError(error, filename);
121
+ }
122
+ return { uri: uploaded.uri, filename: uploaded.filename, contentType, size: bytes.length };
123
+ }
124
+ /** Translate a raw `upload()` transport error into the matching preparation error. */
125
+ function mapUploadError(error, filename) {
126
+ if (error instanceof ApiResponseError) {
127
+ switch (error.status) {
128
+ case 413:
129
+ return new RejectedAssetError(`The server rejected "${filename}": ${error.serverMessage ?? "asset exceeds the service size limit"}.`, filename, error.status, { cause: error });
130
+ case 401:
131
+ case 403:
132
+ return new UploadAuthenticationError(`Upload of "${filename}" was not authorized (${error.status}). Check the configured Pipelex API key.`, error.status, { cause: error });
133
+ case 404:
134
+ return new UnsupportedUploadCapabilityError("The configured Pipelex deployment does not support file upload (no /v1/upload route). " +
135
+ "Upload is a hosted Pipelex capability.", { cause: error });
136
+ default:
137
+ return new UploadTransportError(`Upload of "${filename}" failed (${error.status}): ${error.serverMessage ?? error.statusText}.`, { cause: error });
138
+ }
139
+ }
140
+ if (error instanceof ApiUnreachableError) {
141
+ return new UploadTransportError(`Upload of "${filename}" could not reach the Pipelex API (${error.code ?? "unreachable"}).`, { cause: error });
142
+ }
143
+ // Only errors thrown by the `client.upload()` call reach here — the local-source
144
+ // and asset-type failures are raised in `toAssetBytes`, before this try block. Any
145
+ // error that is neither of the two mapped types (a malformed 2xx body surfacing as a
146
+ // SyntaxError, a custom client throwing a plain Error) is wrapped so every upload
147
+ // failure stays catchable as an InputPreparationError, never a raw escape.
148
+ const detail = error instanceof Error ? error.message : String(error);
149
+ return new UploadTransportError(`Upload of "${filename}" failed unexpectedly: ${detail}.`, {
150
+ cause: error,
151
+ });
152
+ }
153
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for the Pipelex hosted API — execute methods, manage runs, and call the product surface.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,8 +19,10 @@
19
19
  },
20
20
  "scripts": {
21
21
  "build": "tsc",
22
+ "build:hook": "node scripts/build-hook.mjs",
22
23
  "test": "vitest run",
23
24
  "test:coverage": "vitest run --coverage",
25
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
24
26
  "typecheck": "tsc --noEmit",
25
27
  "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
26
28
  "lint": "eslint src/ tests/",
@@ -41,9 +43,11 @@
41
43
  },
42
44
  "devDependencies": {
43
45
  "@eslint/js": "^9.39.4",
46
+ "@pipelex/tools-wasm": "^0.1.0",
44
47
  "@types/node": "^22.12.0",
45
48
  "@vitest/coverage-v8": "^4.1.9",
46
49
  "dependency-cruiser": "^17.4.3",
50
+ "esbuild": "^0.25.12",
47
51
  "eslint": "^9.39.4",
48
52
  "globals": "^16.5.0",
49
53
  "prettier": "^3.8.4",
@@ -52,6 +56,6 @@
52
56
  "vitest": "^4.0.18"
53
57
  },
54
58
  "dependencies": {
55
- "mthds": "^0.18.0"
59
+ "mthds": "^0.19.0"
56
60
  }
57
61
  }