@pipelex/sdk 0.33.0 → 0.34.1
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/README.md +40 -2
- package/dist/cli/args.d.ts +41 -0
- package/dist/cli/args.js +96 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bundle.d.ts +45 -0
- package/dist/cli/bundle.js +148 -0
- package/dist/cli/bundle.js.map +1 -0
- package/dist/cli/environment.d.ts +36 -0
- package/dist/cli/environment.js +72 -0
- package/dist/cli/environment.js.map +1 -0
- package/dist/cli/help.d.ts +4 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inputs.d.ts +13 -0
- package/dist/cli/inputs.js +70 -0
- package/dist/cli/inputs.js.map +1 -0
- package/dist/cli/io.d.ts +79 -0
- package/dist/cli/io.js +96 -0
- package/dist/cli/io.js.map +1 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +58 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/method.d.ts +56 -0
- package/dist/cli/method.js +108 -0
- package/dist/cli/method.js.map +1 -0
- package/dist/cli/present.d.ts +43 -0
- package/dist/cli/present.js +191 -0
- package/dist/cli/present.js.map +1 -0
- package/dist/cli/run.d.ts +17 -0
- package/dist/cli/run.js +290 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/script.d.ts +23 -0
- package/dist/cli/script.js +212 -0
- package/dist/cli/script.js.map +1 -0
- package/dist/cli/source.d.ts +49 -0
- package/dist/cli/source.js +71 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +70 -8
- package/dist/client.js +185 -26
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +42 -4
- package/dist/errors.js +43 -4
- package/dist/errors.js.map +1 -1
- package/dist/hooks/validate-client.d.ts +3 -3
- package/dist/hooks/validate-client.js +3 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +118 -3
- package/dist/models.js +4 -3
- package/dist/models.js.map +1 -1
- package/dist/prepare-inputs.d.ts +6 -0
- package/dist/prepare-inputs.js +21 -10
- package/dist/prepare-inputs.js.map +1 -1
- package/dist/runs.d.ts +28 -1
- package/dist/runs.js +2 -1
- package/dist/runs.js.map +1 -1
- package/dist/upload.d.ts +6 -0
- package/dist/upload.js +9 -4
- package/dist/upload.js.map +1 -1
- package/dist/user-agent.d.ts +3 -2
- package/dist/user-agent.js +3 -2
- package/dist/user-agent.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +12 -4
- package/docs/cli.md +123 -0
- package/docs/client-identification.md +1 -1
- package/docs/errors.md +5 -3
- package/docs/input-preparation.md +6 -2
- package/docs/run-results.md +20 -0
- package/package.json +5 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pipelex-sdk script`: write the per-method command, a short shell script that runs one method
|
|
3
|
+
* through this SDK's `run`, its version pinned.
|
|
4
|
+
*
|
|
5
|
+
* The checks that need no request come first, in this order: the flags, `--pipe`'s form, a
|
|
6
|
+
* control character in any value the script would carry, the method's form (a local bundle is
|
|
7
|
+
* refused), `--name`, `--dir`, the target file when its name is already known, and the key. Then
|
|
8
|
+
* one pipe I/O call checks the method and the pipe, which spends no inference; a catalog id with
|
|
9
|
+
* no `--name` is named from its catalog entry; and the file is written, never over an existing
|
|
10
|
+
* one, with the permissions of an executable, an interrupt meanwhile waiting until it is whole.
|
|
11
|
+
*/
|
|
12
|
+
import { lstat, open, stat, unlink } from "node:fs/promises";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { SDK_VERSION } from "../index.js";
|
|
15
|
+
import { parseFlags } from "./args.js";
|
|
16
|
+
import { systemReason } from "./bundle.js";
|
|
17
|
+
import { makeClient } from "./environment.js";
|
|
18
|
+
import { SCRIPT_HELP } from "./help.js";
|
|
19
|
+
import { CommandError, EXIT_INTERRUPTED, EXIT_OK, Interrupted, untilInterrupted, usageError, } from "./io.js";
|
|
20
|
+
import { addressName, addressTag, checkPipeRef, classifyMethod, hasControlCharacter, kebabCase, scriptNameProblem, shellQuote, } from "./method.js";
|
|
21
|
+
import { describePipe } from "./source.js";
|
|
22
|
+
const SCRIPT_FLAGS = {
|
|
23
|
+
method: { type: "string" },
|
|
24
|
+
pipe: { type: "string" },
|
|
25
|
+
name: { type: "string" },
|
|
26
|
+
dir: { type: "string" },
|
|
27
|
+
};
|
|
28
|
+
/** The package a written script runs, and what its runner needs. */
|
|
29
|
+
export const SCRIPT_RUNNER = `npx --yes @pipelex/sdk@${SDK_VERSION}`;
|
|
30
|
+
export const SCRIPT_WRITER = "@pipelex/sdk";
|
|
31
|
+
export const SCRIPT_NEEDS = "Node 22.12 or later";
|
|
32
|
+
/** Run `pipelex-sdk script` and return its exit code. */
|
|
33
|
+
export async function runCommandScript(args, io) {
|
|
34
|
+
const flags = parseFlags("script", args, SCRIPT_FLAGS);
|
|
35
|
+
if (flags.help) {
|
|
36
|
+
io.writeStdout(SCRIPT_HELP);
|
|
37
|
+
return EXIT_OK;
|
|
38
|
+
}
|
|
39
|
+
const method = flags.strings.get("method");
|
|
40
|
+
if (method === undefined) {
|
|
41
|
+
throw usageError("--method is required.", [
|
|
42
|
+
"Run 'pipelex-sdk script --help' to see its flags.",
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
const pipe = flags.strings.get("pipe");
|
|
46
|
+
if (pipe !== undefined)
|
|
47
|
+
checkPipeRef(pipe);
|
|
48
|
+
const givenName = flags.strings.get("name");
|
|
49
|
+
for (const [flag, value] of [
|
|
50
|
+
["--method", method],
|
|
51
|
+
["--pipe", pipe],
|
|
52
|
+
["--name", givenName],
|
|
53
|
+
]) {
|
|
54
|
+
if (value !== undefined && hasControlCharacter(value)) {
|
|
55
|
+
throw usageError(`${flag} holds a control character, which no line of a script may carry.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const selector = classifyMethod(method);
|
|
59
|
+
if (selector.kind === "path") {
|
|
60
|
+
throw usageError(`script takes a method address or a catalog id, and "${method}" is neither: a script naming a local bundle breaks as soon as the script or the bundle moves.`, [
|
|
61
|
+
"Publish the method in a git repository and pass its address: github.com/<owner>/<repo>[/<package>]@<tag>",
|
|
62
|
+
"Or save it to your organization's catalog and pass its id: mt_...",
|
|
63
|
+
"To run a local bundle, use: pipelex-sdk run --method <path>",
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
const source = selector.kind === "address"
|
|
67
|
+
? { kind: "address", methodRef: selector.methodRef }
|
|
68
|
+
: { kind: "catalog", methodId: selector.methodId };
|
|
69
|
+
if (givenName !== undefined) {
|
|
70
|
+
const problem = scriptNameProblem(givenName);
|
|
71
|
+
if (problem !== undefined)
|
|
72
|
+
throw usageError(`--name ${problem}.`);
|
|
73
|
+
}
|
|
74
|
+
const dir = flags.strings.get("dir");
|
|
75
|
+
const shownDir = dir === undefined ? "." : dir.replace(/\/+$/, "");
|
|
76
|
+
await checkDirectory(dir ?? ".");
|
|
77
|
+
let name = givenName;
|
|
78
|
+
if (name === undefined && source.kind === "address") {
|
|
79
|
+
name = defaultName(addressName(source.methodRef), method);
|
|
80
|
+
}
|
|
81
|
+
if (name !== undefined)
|
|
82
|
+
await checkFree(shownDir, name);
|
|
83
|
+
const client = makeClient(io);
|
|
84
|
+
try {
|
|
85
|
+
await describePipe(client, source, pipe, io.interrupt);
|
|
86
|
+
if (name === undefined && source.kind === "catalog") {
|
|
87
|
+
name = defaultName(await catalogName(client, source.methodId, io), method);
|
|
88
|
+
await checkFree(shownDir, name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (!(error instanceof Interrupted))
|
|
93
|
+
throw error;
|
|
94
|
+
throw new CommandError("Interrupted. Nothing was written.", {
|
|
95
|
+
exitCode: EXIT_INTERRUPTED,
|
|
96
|
+
bare: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (name === undefined)
|
|
100
|
+
throw new Error("a script's name is known once the method is checked");
|
|
101
|
+
if (source.kind === "address" && addressTag(source.methodRef) === undefined) {
|
|
102
|
+
io.writeStderr(`Warning: ${source.methodRef} has no tag, so the script runs whatever its repository's default branch holds each time it runs. Add @<tag> to pin a release.\n`);
|
|
103
|
+
}
|
|
104
|
+
const target = `${shownDir}/${name}`;
|
|
105
|
+
// The last check before the file exists: an interrupt that landed during the checks above
|
|
106
|
+
// writes nothing.
|
|
107
|
+
if (io.interrupt.aborted) {
|
|
108
|
+
throw new CommandError("Interrupted. Nothing was written.", {
|
|
109
|
+
exitCode: EXIT_INTERRUPTED,
|
|
110
|
+
bare: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// The write is not raced with the interrupt: one that lands meanwhile waits for the file to be
|
|
114
|
+
// whole, then says it was written, so that the message is true wherever it lands.
|
|
115
|
+
await writeScript(target, scriptBody(name, method, pipe));
|
|
116
|
+
if (io.interrupt.aborted) {
|
|
117
|
+
throw new CommandError(`Interrupted. ${target} was written.`, {
|
|
118
|
+
exitCode: EXIT_INTERRUPTED,
|
|
119
|
+
bare: true,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
io.writeStdout(`${target}\n`);
|
|
123
|
+
io.writeStderr(`Wrote ${target}. Run it with: ${target} --inputs inputs.json\n`);
|
|
124
|
+
return EXIT_OK;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create the file, executable, refusing one that exists, a dangling link included (`wx`). A write
|
|
128
|
+
* that fails once the file exists removes it, so that a script is either whole or absent: a
|
|
129
|
+
* truncated one would run half a command, and would make the next attempt say it already exists.
|
|
130
|
+
* The one exception is a second Ctrl-C, which ends the process at once (`cli.ts`): landing between
|
|
131
|
+
* the file's creation and the end of its write, it leaves that file behind, empty or partial, as
|
|
132
|
+
* `docs/cli.md` says. On a local disk that takes no time, but a write can block on a stalled mount.
|
|
133
|
+
*/
|
|
134
|
+
async function writeScript(target, body) {
|
|
135
|
+
const path = resolve(target);
|
|
136
|
+
let handle;
|
|
137
|
+
try {
|
|
138
|
+
handle = await open(path, "wx", 0o755);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (error.code === "EEXIST")
|
|
142
|
+
throw alreadyThere(target);
|
|
143
|
+
throw usageError(`cannot write "${target}".`, [`Reason: ${systemReason(error)}`]);
|
|
144
|
+
}
|
|
145
|
+
// The write's own failure is the one reported, a failed close the next.
|
|
146
|
+
const failures = [];
|
|
147
|
+
await handle.writeFile(body).catch((error) => failures.push(error));
|
|
148
|
+
await handle.close().catch((error) => failures.push(error));
|
|
149
|
+
if (failures.length > 0) {
|
|
150
|
+
await unlink(path).catch(() => undefined);
|
|
151
|
+
throw usageError(`cannot write "${target}".`, [`Reason: ${systemReason(failures[0])}`]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The script's text: the shebang, a header saying what it runs, which SDK wrote it, what it needs
|
|
156
|
+
* and how to use it, and the one line that runs it, every value single-quoted.
|
|
157
|
+
*/
|
|
158
|
+
export function scriptBody(name, method, pipe) {
|
|
159
|
+
const pipeArgument = pipe === undefined ? "" : ` --pipe ${shellQuote(pipe)}`;
|
|
160
|
+
return [
|
|
161
|
+
"#!/bin/sh",
|
|
162
|
+
`# ${name}: runs ${method} on the Pipelex API.`,
|
|
163
|
+
`# Written by ${SCRIPT_WRITER} ${SDK_VERSION}. Needs ${SCRIPT_NEEDS}, and PIPELEX_API_KEY in the environment.`,
|
|
164
|
+
`# Usage: ./${name} --inputs inputs.json, or ./${name} --inputs-template to see what to fill in.`,
|
|
165
|
+
`exec ${SCRIPT_RUNNER} run --method ${shellQuote(method)}${pipeArgument} "$@"`,
|
|
166
|
+
"",
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
169
|
+
/** A default name, held to the rules of a file name, or the usage error asking for `--name`. */
|
|
170
|
+
function defaultName(candidate, method) {
|
|
171
|
+
if (scriptNameProblem(candidate) !== undefined) {
|
|
172
|
+
throw usageError(`no script name can be made from "${method}"; give one with --name.`);
|
|
173
|
+
}
|
|
174
|
+
return candidate;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A catalog method's name, kebab-cased, read from its catalog entry. An entry whose name is
|
|
178
|
+
* missing or not a string gives an empty one, which asks for `--name` as an unusable name does.
|
|
179
|
+
*/
|
|
180
|
+
async function catalogName(client, methodId, io) {
|
|
181
|
+
const entry = await untilInterrupted(() => client.getMethod(methodId), io.interrupt);
|
|
182
|
+
const name = entry.name;
|
|
183
|
+
return typeof name === "string" ? kebabCase(name) : "";
|
|
184
|
+
}
|
|
185
|
+
async function checkDirectory(dir) {
|
|
186
|
+
let found;
|
|
187
|
+
try {
|
|
188
|
+
found = await stat(resolve(dir));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw usageError(`--dir "${dir}" does not exist.`, [`Reason: ${systemReason(error)}`]);
|
|
192
|
+
}
|
|
193
|
+
if (!found.isDirectory())
|
|
194
|
+
throw usageError(`--dir "${dir}" is not a directory.`);
|
|
195
|
+
}
|
|
196
|
+
/** Refuse a target that exists, whatever it is, a link that leads nowhere included. */
|
|
197
|
+
async function checkFree(shownDir, name) {
|
|
198
|
+
const target = `${shownDir}/${name}`;
|
|
199
|
+
try {
|
|
200
|
+
await lstat(resolve(target));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
throw alreadyThere(target);
|
|
206
|
+
}
|
|
207
|
+
function alreadyThere(target) {
|
|
208
|
+
return usageError(`"${target}" already exists, and script never overwrites a file.`, [
|
|
209
|
+
"Remove it, or choose another name with --name or another directory with --dir.",
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script.js","sourceRoot":"","sources":["../../src/cli/script.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The method as each route names it, and the one pipe I/O call that checks it.
|
|
3
|
+
*
|
|
4
|
+
* A bundle read from disk travels to the pipe I/O route as `files`, each file under its name in the
|
|
5
|
+
* bundle, and to the run as `mthds_contents`, in the same order. An address travels as
|
|
6
|
+
* `method_ref` and a catalog id as `method_id`, both resolved by the server.
|
|
7
|
+
*/
|
|
8
|
+
import type { PipeInputFormDescriptor, PipelexApiClient, PipeIOValidReport, PipelexStartOptions } from "../index.js";
|
|
9
|
+
import type { BundleFile } from "./bundle.js";
|
|
10
|
+
/** A method the command can send: a bundle read from disk, an address or a catalog id. */
|
|
11
|
+
export type MethodSource = {
|
|
12
|
+
readonly kind: "bundle";
|
|
13
|
+
readonly files: readonly BundleFile[];
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "address";
|
|
16
|
+
readonly methodRef: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "catalog";
|
|
19
|
+
readonly methodId: string;
|
|
20
|
+
};
|
|
21
|
+
/** The method as the crate routes and the input preparation name it. */
|
|
22
|
+
export type CrateSelector = {
|
|
23
|
+
files: Array<{
|
|
24
|
+
content: string;
|
|
25
|
+
source: string;
|
|
26
|
+
}>;
|
|
27
|
+
} | {
|
|
28
|
+
method_ref: string;
|
|
29
|
+
} | {
|
|
30
|
+
method_id: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function crateSelector(source: MethodSource): CrateSelector;
|
|
33
|
+
/** The method as the run routes name it. */
|
|
34
|
+
export declare function runSelector(source: MethodSource): PipelexStartOptions;
|
|
35
|
+
/** A pipe I/O answer's valid arm, and the input form of the pipe it selected. */
|
|
36
|
+
export interface DescribedPipe {
|
|
37
|
+
readonly report: PipeIOValidReport;
|
|
38
|
+
readonly pipeRef: string;
|
|
39
|
+
readonly descriptor: PipeInputFormDescriptor;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ask `POST /v1/pipe-io` for the pipe the method and `--pipe` select, which spends no inference:
|
|
43
|
+
* the same selection the run's input preparation makes.
|
|
44
|
+
*
|
|
45
|
+
* @throws {CommandError} When the method does not load, or the answer does not describe the pipe
|
|
46
|
+
* it selected. A refusal of the request itself propagates as the SDK's `ApiResponseError`.
|
|
47
|
+
* @throws {Interrupted} When the person interrupts the wait.
|
|
48
|
+
*/
|
|
49
|
+
export declare function describePipe(client: PipelexApiClient, source: MethodSource, pipe: string | undefined, interrupt: AbortSignal): Promise<DescribedPipe>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The method as each route names it, and the one pipe I/O call that checks it.
|
|
3
|
+
*
|
|
4
|
+
* A bundle read from disk travels to the pipe I/O route as `files`, each file under its name in the
|
|
5
|
+
* bundle, and to the run as `mthds_contents`, in the same order. An address travels as
|
|
6
|
+
* `method_ref` and a catalog id as `method_id`, both resolved by the server.
|
|
7
|
+
*/
|
|
8
|
+
import { CommandError, EXIT_FAILED, untilInterrupted } from "./io.js";
|
|
9
|
+
import { loadFailureLines, METHOD_DOES_NOT_LOAD_SENTENCE } from "./present.js";
|
|
10
|
+
export function crateSelector(source) {
|
|
11
|
+
switch (source.kind) {
|
|
12
|
+
case "bundle":
|
|
13
|
+
return { files: source.files.map((file) => ({ content: file.content, source: file.name })) };
|
|
14
|
+
case "address":
|
|
15
|
+
return { method_ref: source.methodRef };
|
|
16
|
+
case "catalog":
|
|
17
|
+
return { method_id: source.methodId };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** The method as the run routes name it. */
|
|
21
|
+
export function runSelector(source) {
|
|
22
|
+
switch (source.kind) {
|
|
23
|
+
case "bundle":
|
|
24
|
+
return { mthds_contents: source.files.map((file) => file.content) };
|
|
25
|
+
case "address":
|
|
26
|
+
return { method_ref: source.methodRef };
|
|
27
|
+
case "catalog":
|
|
28
|
+
return { method_id: source.methodId };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ask `POST /v1/pipe-io` for the pipe the method and `--pipe` select, which spends no inference:
|
|
33
|
+
* the same selection the run's input preparation makes.
|
|
34
|
+
*
|
|
35
|
+
* @throws {CommandError} When the method does not load, or the answer does not describe the pipe
|
|
36
|
+
* it selected. A refusal of the request itself propagates as the SDK's `ApiResponseError`.
|
|
37
|
+
* @throws {Interrupted} When the person interrupts the wait.
|
|
38
|
+
*/
|
|
39
|
+
export async function describePipe(client, source, pipe, interrupt) {
|
|
40
|
+
const request = {
|
|
41
|
+
...crateSelector(source),
|
|
42
|
+
...(pipe === undefined ? {} : { pipe_ref: pipe }),
|
|
43
|
+
};
|
|
44
|
+
const answer = await untilInterrupted(() => client.pipeIo(request), interrupt);
|
|
45
|
+
if (!isPlainObject(answer) || typeof answer.is_valid !== "boolean") {
|
|
46
|
+
throw new CommandError("the API's pipe I/O answer says neither that the method loads nor why it does not.", {
|
|
47
|
+
exitCode: EXIT_FAILED,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (!answer.is_valid) {
|
|
51
|
+
throw new CommandError(METHOD_DOES_NOT_LOAD_SENTENCE, {
|
|
52
|
+
exitCode: EXIT_FAILED,
|
|
53
|
+
details: loadFailureLines(answer.validation_errors, answer.message),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const report = answer;
|
|
57
|
+
const pipeRef = report.pipe_ref;
|
|
58
|
+
const inputForm = report.input_form;
|
|
59
|
+
if (typeof pipeRef !== "string" ||
|
|
60
|
+
!isPlainObject(inputForm) ||
|
|
61
|
+
!isPlainObject(inputForm[pipeRef])) {
|
|
62
|
+
throw new CommandError("the API's pipe I/O answer does not describe the pipe it selected.", {
|
|
63
|
+
exitCode: EXIT_FAILED,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return { report, pipeRef, descriptor: inputForm[pipeRef] };
|
|
67
|
+
}
|
|
68
|
+
function isPlainObject(value) {
|
|
69
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/cli/source.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `pipelex-sdk` executable, which `package.json` declares as the package's one `bin`, so that
|
|
4
|
+
* `npx @pipelex/sdk run …` runs it. It hands the command the real process — its arguments, its
|
|
5
|
+
* environment, its streams and Ctrl-C — and exits with the command's code (`docs/cli.md`).
|
|
6
|
+
*
|
|
7
|
+
* It uses no top-level `await`, as no module of `dist/` does, so that the package stays loadable
|
|
8
|
+
* through `require(ESM)` (`docs/architecture.md`, "Module format & packaging").
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `pipelex-sdk` executable, which `package.json` declares as the package's one `bin`, so that
|
|
4
|
+
* `npx @pipelex/sdk run …` runs it. It hands the command the real process — its arguments, its
|
|
5
|
+
* environment, its streams and Ctrl-C — and exits with the command's code (`docs/cli.md`).
|
|
6
|
+
*
|
|
7
|
+
* It uses no top-level `await`, as no module of `dist/` does, so that the package stays loadable
|
|
8
|
+
* through `require(ESM)` (`docs/architecture.md`, "Module format & packaging").
|
|
9
|
+
*/
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
import { EXIT_INTERRUPTED, ignoreClosedPipe } from "./cli/io.js";
|
|
12
|
+
import { runCommand } from "./cli/main.js";
|
|
13
|
+
const interrupt = new AbortController();
|
|
14
|
+
// Once: a second Ctrl-C finds no handler and ends the process the default way.
|
|
15
|
+
process.once("SIGINT", () => interrupt.abort());
|
|
16
|
+
// A reader that stops early (`| head`, `2>&1 | head -1`) closes the pipe on either stream.
|
|
17
|
+
ignoreClosedPipe(process.stdout);
|
|
18
|
+
ignoreClosedPipe(process.stderr);
|
|
19
|
+
void runCommand(process.argv.slice(2), {
|
|
20
|
+
env: process.env,
|
|
21
|
+
readStdin: async () => {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
for await (const chunk of process.stdin)
|
|
24
|
+
chunks.push(chunk);
|
|
25
|
+
return Buffer.concat(chunks);
|
|
26
|
+
},
|
|
27
|
+
writeStdout: (text) => {
|
|
28
|
+
process.stdout.write(text);
|
|
29
|
+
},
|
|
30
|
+
writeStderr: (text) => {
|
|
31
|
+
process.stderr.write(text);
|
|
32
|
+
},
|
|
33
|
+
interrupt: interrupt.signal,
|
|
34
|
+
}).then(exitWhenFlushed);
|
|
35
|
+
/**
|
|
36
|
+
* Exit once both streams have flushed, rather than when the event loop empties: a request the
|
|
37
|
+
* command stopped waiting for on Ctrl-C, such as the start of a run, would otherwise hold the
|
|
38
|
+
* process open until the API answers it.
|
|
39
|
+
*
|
|
40
|
+
* After Ctrl-C, the process ends by the signal itself rather than by `process.exit(130)`, which a
|
|
41
|
+
* POSIX shell reports as the same status, 130: Node's exit waits for its worker threads, and one
|
|
42
|
+
* can be blocked for good in a read the command stopped waiting for, such as `--inputs` naming a
|
|
43
|
+
* named pipe no one writes to, or `/dev/stdin` on a terminal. Ending by the signal also tells a
|
|
44
|
+
* calling shell loop that the person interrupted. Windows has no such signal to end by, and exits
|
|
45
|
+
* with 130.
|
|
46
|
+
*/
|
|
47
|
+
async function exitWhenFlushed(code) {
|
|
48
|
+
await flush(process.stdout);
|
|
49
|
+
await flush(process.stderr);
|
|
50
|
+
if (code === EXIT_INTERRUPTED && interrupt.signal.aborted && process.platform !== "win32") {
|
|
51
|
+
// The once-only handler is gone, so SIGINT has its default action again: ending the process.
|
|
52
|
+
process.kill(process.pid, "SIGINT");
|
|
53
|
+
}
|
|
54
|
+
process.exit(code);
|
|
55
|
+
}
|
|
56
|
+
function flush(stream) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
if (stream.destroyed || !stream.writable) {
|
|
59
|
+
resolve();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
stream.write("", () => resolve());
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,OAAO,MAAM,cAAc,CAAC;AAGnC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,SAAS,GAAG,IAAI,eAAe,EAAE,CAAC;AACxC,+EAA+E;AAC/E,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;AAEhD,2FAA2F;AAC3F,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACjC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAEjC,KAAK,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACrC,GAAG,EAAE,OAAO,CAAC,GAAG;IAChB,SAAS,EAAE,KAAK,IAAI,EAAE;QACpB,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;QACtE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;QACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;QACpB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,SAAS,EAAE,SAAS,CAAC,MAAM;CAC5B,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AAEzB;;;;;;;;;;;GAWG;AACH,KAAK,UAAU,eAAe,CAAC,IAAY;IACzC,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,IAAI,KAAK,gBAAgB,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1F,6FAA6F;QAC7F,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,KAAK,CAAC,MAAgB;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACzC,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MTHDSProtocol, ModelCategory, ModelDeck, RunOptions, StartOptions, VersionInfo } from "mthds/protocol";
|
|
2
|
-
import type { CodegenRequest, CodegenResponse, DictPipeOutput, FormatResponse, LintResponse, MthdsFileItem, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
|
|
3
|
-
import { type GetRunResultOptions, type RunRead, type RunResults, type RunResultState, type WaitForResultOptions } from "./runs.js";
|
|
2
|
+
import type { CodegenRequest, CodegenResponse, DictPipeOutput, FormatResponse, LintResponse, ModelCheckCategory, ModelReferenceVerdict, MthdsFileItem, PipeIORequest, PipeIOResponse, PipelexRunResultStart, PipelexValidationResult, ResolveRequest, ResolveResponse, ValidateMethodSelector } from "./models.js";
|
|
3
|
+
import { type GetRunResultOptions, type RunRead, type RunResults, type RunResultState, type StartAndWaitForResultOptions, type WaitForResultOptions } from "./runs.js";
|
|
4
4
|
import type { BillingPortalResponse, ChangePlanResponse, CheckoutResponse, InvoiceView, Membership, MembershipsResponse, ListMethodsQuery, MethodData, MethodDeletionAccepted, MethodPage, MethodSummary, MethodWriteInput, OnboardingSubmission, PipelexApiKeyCreated, PipelexApiKeyList, ListRunsQuery, RunHistoryItem, RunDetail, RunPage, PlanView, ResolvedStorageUrl, SubscriptionResponse, UpdateRunInput, UploadGrant, UploadGrantInput, UploadInput, UploadedFile, UserProfile } from "./product-models.js";
|
|
5
5
|
import type { AppInfo } from "./user-agent.js";
|
|
6
6
|
import type { UploadableAsset, UploadFileOptions, UploadRecord } from "./upload.js";
|
|
@@ -120,7 +120,8 @@ export interface PipelexApiClientOptions {
|
|
|
120
120
|
* `{ name: "acme-invoicer", version: "1.4.0" }` →
|
|
121
121
|
* `acme-invoicer/1.4.0 pipelex-sdk-js/<v> node/<v> (<os>; <arch>)`. Validated at
|
|
122
122
|
* construction: a field that is not an RFC 9110 token throws a `TypeError`. See
|
|
123
|
-
* the
|
|
123
|
+
* the spec `conformance/specs/client-identification.md`, in the `conformance` repository
|
|
124
|
+
* beside the specs' tests.
|
|
124
125
|
*/
|
|
125
126
|
appInfo?: AppInfo;
|
|
126
127
|
}
|
|
@@ -199,8 +200,9 @@ export declare class PipelexApiClient implements MTHDSProtocol<DictPipeOutput> {
|
|
|
199
200
|
private readObjectAnswer;
|
|
200
201
|
/**
|
|
201
202
|
* The JSON body of a 2xx answer, naming the route by its `path` from the origin as a refusal
|
|
202
|
-
* does. A body that is not JSON, an empty one included, is an answer the SDK
|
|
203
|
-
* throws the `ApiResponseError` `unreadableAnswer` builds, with the parse
|
|
203
|
+
* does. A body that is not UTF-8, or not JSON, an empty one included, is an answer the SDK
|
|
204
|
+
* cannot read, so it throws the `ApiResponseError` `unreadableAnswer` builds, with the parse
|
|
205
|
+
* failure as `cause`.
|
|
204
206
|
* The one place a success body is parsed.
|
|
205
207
|
*/
|
|
206
208
|
private readAnswerAt;
|
|
@@ -419,6 +421,26 @@ export declare class PipelexApiClient implements MTHDSProtocol<DictPipeOutput> {
|
|
|
419
421
|
private requestExtension;
|
|
420
422
|
/** The model deck the runner can route to — `GET /v1/models[?type=]`. */
|
|
421
423
|
models(category?: ModelCategory): Promise<ModelDeck>;
|
|
424
|
+
/**
|
|
425
|
+
* Check one model reference — `GET /v1/models/check?reference=<ref>[&type=<category>]`, a
|
|
426
|
+
* Pipelex API extension served by any `pipelex-api` runner from pipelex 0.78.0 and on the
|
|
427
|
+
* hosted API.
|
|
428
|
+
*
|
|
429
|
+
* Answers whether `reference` resolves on the runner, as what kind and to which model, from
|
|
430
|
+
* the parser and the deck lookups a validation runs. `reference` is written as a method's
|
|
431
|
+
* `model` field writes it — `$preset`, `@alias`, `~waterfall`, a bare handle, or a
|
|
432
|
+
* spelled-out namespace (`handle:gpt-4o`) — and is sent percent-encoded. `category` checks
|
|
433
|
+
* in that category alone; without it, the check covers every one.
|
|
434
|
+
*
|
|
435
|
+
* Returns a **200 verdict** whatever the resolution: a reference that resolves nowhere is
|
|
436
|
+
* `resolution: "not_found"` with the names it may have meant, never a thrown error. Narrow
|
|
437
|
+
* on `kind` to read the shape of `matches`. A request that cannot produce a verdict throws
|
|
438
|
+
* the typed `ApiResponseError`, a `422` whose `errorType` says why: `InvalidModelReference`
|
|
439
|
+
* (a blank reference, a sigil or a namespace alone, or one past the runner's length limit),
|
|
440
|
+
* `InvalidModelCategory` (an unknown `type`) or `ValidationError`. None of that is checked
|
|
441
|
+
* here, so the runner's rule is the only one.
|
|
442
|
+
*/
|
|
443
|
+
checkModelReference(reference: string, category?: ModelCheckCategory): Promise<ModelReferenceVerdict>;
|
|
422
444
|
/**
|
|
423
445
|
* Protocol + implementation versions — `GET /v1/version` (always public).
|
|
424
446
|
* The handshake for feature detection (hosted extensions or not).
|
|
@@ -538,8 +560,18 @@ export declare class PipelexApiClient implements MTHDSProtocol<DictPipeOutput> {
|
|
|
538
560
|
* Whether the configured server serves the durable run lifecycle, decided
|
|
539
561
|
* via the `GET /v1/version` handshake and cached for the client's lifetime. A
|
|
540
562
|
* bare `pipelex-api` runner has no run store; anything else is assumed hosted.
|
|
541
|
-
*
|
|
542
|
-
*
|
|
563
|
+
*
|
|
564
|
+
* The rule, the one the Python SDK follows: an answer that is not a usable
|
|
565
|
+
* version means assume hosted (the SDK default), and let the start surface the
|
|
566
|
+
* real error; no answer at all propagates, uncached. An answer is anything the
|
|
567
|
+
* server sent back: a non-2xx status, a body that is not JSON, not UTF-8 or no
|
|
568
|
+
* version, and a body that arrived but could not be decoded, such as a broken
|
|
569
|
+
* gzip stream, which `requestRaw` reports as an `ApiUnreachableError` carrying
|
|
570
|
+
* the decompressor's code (see `isUndecodableBody`).
|
|
571
|
+
*
|
|
572
|
+
* @throws {ApiUnreachableError} The handshake got no answer. Nothing is cached,
|
|
573
|
+
* so the next call asks again, and no start is sent to a host that did not
|
|
574
|
+
* answer: sending it would wait a second time for the same silence.
|
|
543
575
|
*/
|
|
544
576
|
private supportsRunLifecycle;
|
|
545
577
|
/**
|
|
@@ -549,8 +581,19 @@ export declare class PipelexApiClient implements MTHDSProtocol<DictPipeOutput> {
|
|
|
549
581
|
* path that survives the gateway's ~30s synchronous ceiling.
|
|
550
582
|
* - **Bare runner** (no run store): the blocking `POST /v1/execute`, which
|
|
551
583
|
* has no gateway cap off-platform and returns the native `pipe_output`.
|
|
584
|
+
*
|
|
585
|
+
* `pollOptions` are the wait's options, plus `onStarted`, called once with the
|
|
586
|
+
* start acknowledgement as soon as the durable run exists, so the caller holds
|
|
587
|
+
* the run's id while it waits; never on the blocking path, which has none to
|
|
588
|
+
* give; and `onStarting`, called right before each request that may create a
|
|
589
|
+
* run, so the caller knows until then that none exists (see
|
|
590
|
+
* `StartAndWaitForResultOptions`).
|
|
591
|
+
*
|
|
592
|
+
* `pollOptions.signal` is also read before the run is created: a caller that aborts while the
|
|
593
|
+
* version handshake is in flight gets its abort, and neither the start nor the blocking execute
|
|
594
|
+
* is sent. Once one of them is sent, the abort stops only the wait.
|
|
552
595
|
*/
|
|
553
|
-
startAndWaitForResult(options: PipelexStartOptions, pollOptions?:
|
|
596
|
+
startAndWaitForResult(options: PipelexStartOptions, pollOptions?: StartAndWaitForResultOptions): Promise<RunResults>;
|
|
554
597
|
/** The authenticated user's profile — `GET /v1/me`. */
|
|
555
598
|
getMe(): Promise<UserProfile>;
|
|
556
599
|
/**
|
|
@@ -855,3 +898,22 @@ export declare class PipelexApiClient implements MTHDSProtocol<DictPipeOutput> {
|
|
|
855
898
|
*/
|
|
856
899
|
private executeBlocking;
|
|
857
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Whether a request that failed `elapsedMs` after it was sent was cut off by the hosted gateway's
|
|
903
|
+
* ~30-second limit on a request it waits on, rather than refused.
|
|
904
|
+
*
|
|
905
|
+
* It is when, after at least ~28 seconds, the failure is a `503` or `504` answer, or the client's
|
|
906
|
+
* own time limit (`ApiUnreachableError` with the code `ABORT_TIMEOUT`). A fast `503` is the API
|
|
907
|
+
* saying the request was not handled, and any other unreachable host is never the gateway's
|
|
908
|
+
* cut-off, however long it took. The fetch API cannot say whether `ABORT_TIMEOUT` ran out before
|
|
909
|
+
* the connection was made, so it is read as the gateway's; with Node's own dispatcher it never
|
|
910
|
+
* does past the threshold, since undici gives up a connection that does not come after ten
|
|
911
|
+
* seconds (`UND_ERR_CONNECT_TIMEOUT`), the TLS handshake included. The Python SDK's
|
|
912
|
+
* `is_gateway_cut_off` reads the same failures, its `ABORT_TIMEOUT` being httpx's read or write
|
|
913
|
+
* timeout.
|
|
914
|
+
*
|
|
915
|
+
* The blocking `execute` turns such a failure into a `PipelineExecuteTimeoutError`. A caller timing
|
|
916
|
+
* a request that may create a run, such as `start` from `startAndWaitForResult`'s `onStarting`,
|
|
917
|
+
* reads it to know the server may still be handling the request the gateway gave up on.
|
|
918
|
+
*/
|
|
919
|
+
export declare function isGatewayCutOff(error: unknown, elapsedMs: number): boolean;
|