mthds 0.9.0 → 0.11.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/README.md +43 -21
- package/dist/agent/binaries.js +2 -2
- package/dist/agent/commands/api-commands.d.ts +8 -0
- package/dist/agent/commands/api-commands.js +310 -93
- package/dist/agent/commands/api-commands.js.map +1 -1
- package/dist/agent/commands/codex-hook.d.ts +20 -0
- package/dist/agent/commands/codex-hook.js +25 -9
- package/dist/agent/commands/codex-hook.js.map +1 -1
- package/dist/agent/commands/config.js +2 -2
- package/dist/agent/commands/config.js.map +1 -1
- package/dist/agent/output.d.ts +4 -0
- package/dist/agent/output.js +7 -0
- package/dist/agent/output.js.map +1 -1
- package/dist/agent/plugin-version.d.ts +1 -1
- package/dist/agent/plugin-version.js +1 -1
- package/dist/agent-cli.js +5 -5
- package/dist/agent-cli.js.map +1 -1
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/install.js +29 -37
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/run.js +82 -69
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.js +22 -23
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/utils.d.ts +9 -1
- package/dist/cli/commands/utils.js +9 -0
- package/dist/cli/commands/utils.js.map +1 -1
- package/dist/cli/commands/validate.js +29 -13
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/config/config.d.ts +14 -1
- package/dist/config/config.js +31 -6
- package/dist/config/config.js.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol/concept.d.ts +14 -0
- package/dist/protocol/concept.js +10 -0
- package/dist/protocol/concept.js.map +1 -0
- package/dist/protocol/exceptions.d.ts +10 -0
- package/dist/protocol/exceptions.js +12 -0
- package/dist/protocol/exceptions.js.map +1 -0
- package/dist/protocol/models.d.ts +150 -0
- package/dist/protocol/models.js +24 -0
- package/dist/protocol/models.js.map +1 -0
- package/dist/protocol/options.d.ts +60 -0
- package/dist/protocol/options.js +11 -0
- package/dist/protocol/options.js.map +1 -0
- package/dist/protocol/pipe_output.d.ts +11 -0
- package/dist/protocol/pipe_output.js +7 -0
- package/dist/protocol/pipe_output.js.map +1 -0
- package/dist/protocol/pipeline_inputs.d.ts +8 -0
- package/dist/protocol/pipeline_inputs.js +7 -0
- package/dist/protocol/pipeline_inputs.js.map +1 -0
- package/dist/protocol/protocol.d.ts +55 -0
- package/dist/{client → protocol}/protocol.js.map +1 -1
- package/dist/protocol/stuff.d.ts +16 -0
- package/dist/protocol/stuff.js +8 -0
- package/dist/{client/models → protocol}/stuff.js.map +1 -1
- package/dist/protocol/working_memory.d.ts +10 -0
- package/dist/protocol/working_memory.js +7 -0
- package/dist/protocol/working_memory.js.map +1 -0
- package/dist/runners/api/client.d.ts +181 -0
- package/dist/runners/api/client.js +679 -0
- package/dist/runners/api/client.js.map +1 -0
- package/dist/runners/api/exceptions.d.ts +121 -0
- package/dist/runners/api/exceptions.js +156 -0
- package/dist/runners/api/exceptions.js.map +1 -0
- package/dist/runners/api/models.d.ts +131 -0
- package/dist/runners/api/models.js +13 -0
- package/dist/runners/api/models.js.map +1 -0
- package/dist/runners/api/runs.d.ts +130 -0
- package/dist/runners/api/runs.js +93 -0
- package/dist/runners/api/runs.js.map +1 -0
- package/dist/runners/base-runner.d.ts +27 -0
- package/dist/runners/base-runner.js +25 -0
- package/dist/runners/base-runner.js.map +1 -0
- package/dist/runners/pipelex/runner.d.ts +38 -0
- package/dist/runners/{pipelex-runner.js → pipelex/runner.js} +171 -83
- package/dist/runners/pipelex/runner.js.map +1 -0
- package/dist/runners/registry.js +10 -4
- package/dist/runners/registry.js.map +1 -1
- package/dist/runners/types.d.ts +13 -71
- package/dist/runners/types.js.map +1 -1
- package/package.json +6 -3
- package/dist/agent/commands/validate.d.ts +0 -18
- package/dist/agent/commands/validate.js +0 -126
- package/dist/agent/commands/validate.js.map +0 -1
- package/dist/client/client.d.ts +0 -15
- package/dist/client/client.js +0 -127
- package/dist/client/client.js.map +0 -1
- package/dist/client/exceptions.d.ts +0 -46
- package/dist/client/exceptions.js +0 -61
- package/dist/client/exceptions.js.map +0 -1
- package/dist/client/index.d.ts +0 -5
- package/dist/client/index.js +0 -3
- package/dist/client/index.js.map +0 -1
- package/dist/client/models/index.d.ts +0 -4
- package/dist/client/models/index.js +0 -2
- package/dist/client/models/index.js.map +0 -1
- package/dist/client/models/pipe_output.d.ts +0 -2
- package/dist/client/models/pipe_output.js +0 -2
- package/dist/client/models/pipe_output.js.map +0 -1
- package/dist/client/models/pipeline_inputs.d.ts +0 -3
- package/dist/client/models/pipeline_inputs.js +0 -2
- package/dist/client/models/pipeline_inputs.js.map +0 -1
- package/dist/client/models/stuff.d.ts +0 -1
- package/dist/client/models/stuff.js +0 -2
- package/dist/client/models/working_memory.d.ts +0 -1
- package/dist/client/models/working_memory.js +0 -2
- package/dist/client/models/working_memory.js.map +0 -1
- package/dist/client/pipeline.d.ts +0 -36
- package/dist/client/pipeline.js +0 -2
- package/dist/client/pipeline.js.map +0 -1
- package/dist/client/protocol.d.ts +0 -5
- package/dist/runners/api-runner.d.ts +0 -24
- package/dist/runners/api-runner.js +0 -91
- package/dist/runners/api-runner.js.map +0 -1
- package/dist/runners/pipelex-runner.d.ts +0 -30
- package/dist/runners/pipelex-runner.js.map +0 -1
- /package/dist/{client → protocol}/protocol.js +0 -0
package/README.md
CHANGED
|
@@ -136,24 +136,31 @@ npx mthds setup runner pipelex
|
|
|
136
136
|
|
|
137
137
|
### Configure the API runner
|
|
138
138
|
|
|
139
|
-
The
|
|
139
|
+
The local pipelex runner is the default; to use the hosted (or self-hosted) API instead, set up the API runner interactively:
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
142
|
mthds setup runner api
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
This prompts for the API URL and API key (masked input) and saves them to `~/.mthds/config`.
|
|
145
|
+
This prompts for the API base URL and an API key (masked input), and saves them to `~/.mthds/config`.
|
|
146
|
+
|
|
147
|
+
There is ONE base URL — the host only, with no version prefix. The SDK composes every endpoint as `{base}/v1/{endpoint}`:
|
|
148
|
+
|
|
149
|
+
- **`base-url`** — hosted: `https://api.pipelex.com` (the default). Self-hosted: `http://localhost:8081` (a bare [pipelex-api](https://github.com/Pipelex/pipelex-api) runner).
|
|
146
150
|
|
|
147
151
|
You can also set values directly:
|
|
148
152
|
|
|
149
153
|
```bash
|
|
150
154
|
mthds config set api-key YOUR_KEY
|
|
151
|
-
|
|
155
|
+
# Hosted (default):
|
|
156
|
+
mthds config set base-url https://api.pipelex.com
|
|
157
|
+
# Self-hosted bare runner:
|
|
158
|
+
mthds config set base-url http://localhost:8081
|
|
152
159
|
```
|
|
153
160
|
|
|
154
161
|
Configuration is stored in `~/.mthds/config` and shared between mthds-js and mthds-python.
|
|
155
162
|
|
|
156
|
-
You can also use environment variables (`
|
|
163
|
+
You can also use environment variables (`MTHDS_API_KEY`, `MTHDS_API_URL`) which take precedence over the config file.
|
|
157
164
|
|
|
158
165
|
See the [SDK Usage](#sdk-usage) section below to connect to a Pipelex API instance programmatically.
|
|
159
166
|
|
|
@@ -171,11 +178,11 @@ npm install mthds
|
|
|
171
178
|
import { MthdsApiClient } from "mthds";
|
|
172
179
|
|
|
173
180
|
const client = new MthdsApiClient({
|
|
174
|
-
|
|
181
|
+
baseUrl: "https://api.pipelex.com",
|
|
175
182
|
apiToken: "your-api-key",
|
|
176
183
|
});
|
|
177
184
|
|
|
178
|
-
const result = await client.
|
|
185
|
+
const result = await client.execute({
|
|
179
186
|
pipe_code: "my-pipeline",
|
|
180
187
|
inputs: {
|
|
181
188
|
topic: "quantum computing",
|
|
@@ -185,50 +192,65 @@ const result = await client.executePipeline({
|
|
|
185
192
|
console.log(result.pipe_output);
|
|
186
193
|
```
|
|
187
194
|
|
|
195
|
+
The base URL is the host only — every endpoint composes as `{baseUrl}/v1/{endpoint}` (e.g. `https://api.pipelex.com/v1/execute`); `/health` resolves to the origin root.
|
|
196
|
+
|
|
188
197
|
### Self-hosted API
|
|
189
198
|
|
|
190
|
-
Point the client
|
|
199
|
+
Point the client at your own [pipelex-api](https://github.com/Pipelex/pipelex-api) instance — the same `MTHDSProtocol` surface, same paths:
|
|
191
200
|
|
|
192
201
|
```typescript
|
|
193
202
|
const client = new MthdsApiClient({
|
|
194
|
-
|
|
203
|
+
baseUrl: "http://localhost:8081",
|
|
195
204
|
apiToken: "your-api-key",
|
|
196
205
|
});
|
|
197
206
|
```
|
|
198
207
|
|
|
208
|
+
The bare open-source runner has no run store, so the durable run-lifecycle methods (`getRunStatus`/`getRunResult`/`waitForResult`) throw a clear `RunLifecycleUnavailableError` against it — use `execute` (blocking) or `start` instead (completion delivery is implementation-defined — see your runner's API documentation). The `GET /v1/version` handshake tells the SDK which deployment it is talking to.
|
|
209
|
+
|
|
210
|
+
> Note: the bare-runner blocking path returns the runner's native `pipe_output`, whereas the hosted durable path returns `main_stuff` + `graph_spec`. Cross-shape normalization is a v1 TODO.
|
|
211
|
+
|
|
199
212
|
### Environment variables
|
|
200
213
|
|
|
201
214
|
Instead of passing options to the constructor, you can set environment variables:
|
|
202
215
|
|
|
203
216
|
| Variable | Description |
|
|
204
217
|
|----------|-------------|
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
218
|
+
| `MTHDS_API_URL` | API base URL — host only, no version prefix (default `https://api.pipelex.com`) |
|
|
219
|
+
| `MTHDS_API_KEY` | API authentication token |
|
|
207
220
|
|
|
208
221
|
```typescript
|
|
209
|
-
// Reads
|
|
222
|
+
// Reads MTHDS_API_URL and MTHDS_API_KEY from the environment
|
|
210
223
|
const client = new MthdsApiClient();
|
|
211
224
|
```
|
|
212
225
|
|
|
213
226
|
### Methods
|
|
214
227
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
|
218
|
-
|
|
228
|
+
The client implements the MTHDS Protocol plus the hosted run-lifecycle extension:
|
|
229
|
+
|
|
230
|
+
| Method | Route | Description |
|
|
231
|
+
|--------|-------|-------------|
|
|
232
|
+
| `execute(options)` | `POST /v1/execute` | Execute a method and wait for the result — returns a `RunResultExecute` (throws `RunStillRunningError` on the protocol's optional 202 degrade) |
|
|
233
|
+
| `start(options)` | `POST /v1/start` | Start a method asynchronously — returns a `RunResultStart` with the authoritative `pipeline_run_id` |
|
|
234
|
+
| `validate(mthdsContents, allowSignatures?, mthdsSources?)` | `POST /v1/validate` | Parse, validate, and dry-run a bundle — returns a typed `PipelexValidationResult` discriminated on `is_valid`; an invalid bundle is a produced verdict (`is_valid: false` with `validation_errors`), not a throw. Only a no-verdict non-2xx (malformed request, auth, server fault) throws `ApiResponseError`. `mthdsSources` (optional, parallel to `mthdsContents`) names each submitted file so cross-file diagnostics resolve the owning file |
|
|
235
|
+
| `models(category?)` | `GET /v1/models` | The model deck the runner routes to |
|
|
236
|
+
| `version()` | `GET /v1/version` | Protocol + implementation versions (the feature-detection handshake) |
|
|
237
|
+
| `getRunStatus(runId)` | `GET /v1/runs/{id}/status` | Hosted extension — self-healing status read |
|
|
238
|
+
| `getRunResult(runId)` | `GET /v1/runs/{id}/results` | Hosted extension — single-shot result lookup |
|
|
239
|
+
| `waitForResult(runId, options?)` | — | Hosted extension — poll to a terminal state |
|
|
219
240
|
|
|
220
|
-
###
|
|
241
|
+
### Run options
|
|
221
242
|
|
|
222
243
|
| Option | Type | Description |
|
|
223
244
|
|--------|------|-------------|
|
|
224
|
-
| `pipe_code` | `string` |
|
|
225
|
-
| `
|
|
226
|
-
| `inputs` | `Record<string, string \| string[] \| object>` |
|
|
245
|
+
| `pipe_code` | `string` | Pipe code to execute |
|
|
246
|
+
| `mthds_contents` | `string[]` | Raw bundle contents (alternative to `pipe_code`) |
|
|
247
|
+
| `inputs` | `Record<string, string \| string[] \| object>` | Method input variables |
|
|
227
248
|
| `output_name` | `string` | Name of the output to return |
|
|
228
249
|
| `output_multiplicity` | `boolean \| number` | Expected output multiplicity |
|
|
229
|
-
| `
|
|
250
|
+
| `dynamic_output_concept_ref` | `string` | Dynamic output concept reference |
|
|
251
|
+
| `extra` | `Record<string, unknown>` | Server-specific extension args, forwarded verbatim into the request body (e.g. a stored-method id) |
|
|
230
252
|
|
|
231
|
-
Either `pipe_code` or `
|
|
253
|
+
Either `pipe_code` or `mthds_contents` must be provided (or a server-specific extension arg via `extra`). Anything beyond the protocol's basic args is server-specific and rides the generic `extra` option, merged into the request body — the server you call defines and handles its own extension args (a client-supplied run id, where a server supports one, is such an extension; the request side never names it). `startAndWaitForResult()` runs the whole start + poll lifecycle in one call.
|
|
232
254
|
|
|
233
255
|
## Telemetry
|
|
234
256
|
|
package/dist/agent/binaries.js
CHANGED
|
@@ -22,7 +22,7 @@ const VERSION_RE = /^[\w-]+\s+(\d+\.\d+\.\d+)/;
|
|
|
22
22
|
const PIPELEX_PKG = {
|
|
23
23
|
package: "pipelex",
|
|
24
24
|
uv_package: "pipelex",
|
|
25
|
-
version_constraint: ">=0.
|
|
25
|
+
version_constraint: ">=0.32.1",
|
|
26
26
|
version_extract: VERSION_RE,
|
|
27
27
|
install_url: "https://pypi.org/project/pipelex/",
|
|
28
28
|
auto_installable: true,
|
|
@@ -31,7 +31,7 @@ const PIPELEX_PKG = {
|
|
|
31
31
|
const PIPELEX_TOOLS_PKG = {
|
|
32
32
|
package: "pipelex-tools",
|
|
33
33
|
uv_package: "pipelex-tools",
|
|
34
|
-
version_constraint: ">=0.
|
|
34
|
+
version_constraint: ">=0.6.0",
|
|
35
35
|
version_extract: VERSION_RE,
|
|
36
36
|
install_url: "https://pypi.org/project/pipelex-tools/",
|
|
37
37
|
auto_installable: true,
|
|
@@ -11,3 +11,11 @@ import type { Runner } from "../../runners/types.js";
|
|
|
11
11
|
* Only called when --runner=api.
|
|
12
12
|
*/
|
|
13
13
|
export declare function registerApiRunnerCommands(program: Command, makeRunner: () => Runner): void;
|
|
14
|
+
/**
|
|
15
|
+
* Run the protocol validate (`POST /v1/validate`) and emit the agent envelope.
|
|
16
|
+
* `/validate` is a diagnostic endpoint: a valid bundle returns the structural
|
|
17
|
+
* artifacts (`is_valid: true`); an invalid bundle is a produced verdict (a 200
|
|
18
|
+
* `is_valid: false` body), surfaced as a structured ValidateBundleError envelope —
|
|
19
|
+
* not a thrown error. A non-2xx (a no-verdict condition) still throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runProtocolValidate(runner: Runner, mthdsContents: string[], allowSignatures: boolean, mthdsSources?: string[]): Promise<void>;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { agentError, agentSuccess, AGENT_ERROR_DOMAINS } from "../output.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
10
|
+
import { isApiRunner } from "../../cli/commands/utils.js";
|
|
11
|
+
import { MODEL_CATEGORIES } from "../../protocol/models.js";
|
|
12
|
+
import { ApiResponseError, RunFailedError, RunTimeoutError } from "../../runners/api/exceptions.js";
|
|
13
13
|
/**
|
|
14
14
|
* Register all API-runner commands on the program.
|
|
15
15
|
* Only called when --runner=api.
|
|
@@ -114,7 +114,7 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
114
114
|
validateGroup
|
|
115
115
|
.command("bundle")
|
|
116
116
|
.argument("[target]", "Bundle file (.mthds) or directory")
|
|
117
|
-
.option("--
|
|
117
|
+
.option("--allow-signatures", "Tolerate unimplemented pipe signatures")
|
|
118
118
|
.option("--content <mthds>", "Bundle content as a string")
|
|
119
119
|
.description("Validate a bundle file or content")
|
|
120
120
|
.allowUnknownOption()
|
|
@@ -123,62 +123,26 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
123
123
|
.action(async (target, options) => {
|
|
124
124
|
const runner = safeCreateRunner(makeRunner);
|
|
125
125
|
const mthdsContent = resolveContent(target, options.content);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
pipe_code: options.pipe,
|
|
130
|
-
});
|
|
131
|
-
if (result.success) {
|
|
132
|
-
agentSuccess({ ...result });
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
agentError(result.message, "ValidationError", {
|
|
136
|
-
error_domain: AGENT_ERROR_DOMAINS.VALIDATION,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
agentError(err.message, "RunnerError", {
|
|
142
|
-
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
126
|
+
// A real file path (not inline --content) names the source for diagnostics.
|
|
127
|
+
const mthdsSources = !options.content && target ? [target] : undefined;
|
|
128
|
+
await runProtocolValidate(runner, [mthdsContent], options.allowSignatures ?? false, mthdsSources);
|
|
145
129
|
});
|
|
146
130
|
validateGroup
|
|
147
131
|
.command("pipe")
|
|
148
|
-
.argument("<target>", "
|
|
149
|
-
.option("--
|
|
150
|
-
.description("Validate a
|
|
132
|
+
.argument("<target>", ".mthds bundle file")
|
|
133
|
+
.option("--allow-signatures", "Tolerate unimplemented pipe signatures")
|
|
134
|
+
.description("Validate a bundle file (protocol validate covers every pipe in it)")
|
|
151
135
|
.allowUnknownOption()
|
|
152
136
|
.allowExcessArguments(true)
|
|
153
137
|
.exitOverride()
|
|
154
138
|
.action(async (target, options) => {
|
|
155
139
|
const runner = safeCreateRunner(makeRunner);
|
|
156
|
-
if (target.endsWith(".mthds")) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const result = await runner.validate({
|
|
160
|
-
mthds_contents: [mthdsContent],
|
|
161
|
-
pipe_code: options.pipe,
|
|
162
|
-
});
|
|
163
|
-
handleValidateResult(result);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
agentError(err.message, "RunnerError", {
|
|
167
|
-
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
try {
|
|
173
|
-
const result = await runner.validate({ pipe_code: target });
|
|
174
|
-
handleValidateResult(result);
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
agentError(err.message, "RunnerError", {
|
|
178
|
-
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
140
|
+
if (!target.endsWith(".mthds")) {
|
|
141
|
+
agentError("Validating a bare pipe code is not supported via the API runner — the protocol validate takes bundle contents. Pass a .mthds file.", "ArgumentError", { error_domain: AGENT_ERROR_DOMAINS.ARGUMENT });
|
|
142
|
+
return;
|
|
181
143
|
}
|
|
144
|
+
const mthdsContent = readFileOrError(target);
|
|
145
|
+
await runProtocolValidate(runner, [mthdsContent], options.allowSignatures ?? false, [target]);
|
|
182
146
|
});
|
|
183
147
|
validateGroup
|
|
184
148
|
.command("method")
|
|
@@ -188,20 +152,8 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
188
152
|
.allowUnknownOption()
|
|
189
153
|
.allowExcessArguments(true)
|
|
190
154
|
.exitOverride()
|
|
191
|
-
.action(async (
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const result = await runner.validate({
|
|
195
|
-
method_url: target,
|
|
196
|
-
pipe_code: options.pipe,
|
|
197
|
-
});
|
|
198
|
-
handleValidateResult(result);
|
|
199
|
-
}
|
|
200
|
-
catch (err) {
|
|
201
|
-
agentError(err.message, "RunnerError", {
|
|
202
|
-
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
155
|
+
.action(async () => {
|
|
156
|
+
agentError("'validate method' is not supported via the API runner — the protocol validate takes bundle contents, not a method URL. Pass a .mthds file to 'validate bundle', or use --runner pipelex.", "UnsupportedError", { error_domain: AGENT_ERROR_DOMAINS.RUNNER });
|
|
205
157
|
});
|
|
206
158
|
// ── inputs ──
|
|
207
159
|
const inputsGroup = program
|
|
@@ -310,12 +262,17 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
310
262
|
inputs = parseJsonOrError(raw, "inputs file");
|
|
311
263
|
}
|
|
312
264
|
try {
|
|
313
|
-
const result = await runner.
|
|
265
|
+
const result = await runner.startAndWaitForResult({
|
|
314
266
|
mthds_contents: [mthdsContent],
|
|
315
267
|
pipe_code: pipeCode,
|
|
316
268
|
inputs,
|
|
317
269
|
});
|
|
318
|
-
agentSuccess({
|
|
270
|
+
agentSuccess({
|
|
271
|
+
state: "completed",
|
|
272
|
+
pipeline_run_id: result.pipeline_run_id,
|
|
273
|
+
main_stuff: result.main_stuff ?? result.pipe_output ?? null,
|
|
274
|
+
graph_spec: result.graph_spec ?? null,
|
|
275
|
+
});
|
|
319
276
|
}
|
|
320
277
|
catch (err) {
|
|
321
278
|
agentError(err.message, "RunnerError", {
|
|
@@ -349,20 +306,31 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
349
306
|
.allowExcessArguments(true)
|
|
350
307
|
.exitOverride()
|
|
351
308
|
.action(runAction);
|
|
352
|
-
// ──
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
309
|
+
// ── run start ──
|
|
310
|
+
// Submit a run and return its id immediately. All run state lives behind the
|
|
311
|
+
// returned `pipeline_run_id` (DB + Temporal), so an agent can submit here,
|
|
312
|
+
// disconnect, and later resume with `run status` / `run result` / `run poll`.
|
|
313
|
+
runGroup
|
|
314
|
+
.command("start")
|
|
315
|
+
.argument("[target]", "Bundle file (.mthds) or directory")
|
|
316
|
+
.option("--pipe <code>", "Pipe code to run")
|
|
317
|
+
.option("-i, --inputs <file>", "Path to JSON inputs file")
|
|
318
|
+
.option("--content <mthds>", "Bundle content as a string")
|
|
319
|
+
.option("--inputs-json <json>", "Inputs as a JSON string")
|
|
320
|
+
.option("--extra <json>", "Server-specific extension args as a JSON object (e.g. a stored-method run) — forwarded to the runner verbatim")
|
|
321
|
+
.option("--output-name <name>", "Name of the output slot to write to")
|
|
322
|
+
.option("--output-multiplicity <value>", "Output multiplicity: 'false', 'true', or an exact count")
|
|
323
|
+
.option("--dynamic-output <concept_ref>", "Override for the dynamic output concept ref")
|
|
324
|
+
.description("Start a run and return its id without waiting")
|
|
357
325
|
.allowUnknownOption()
|
|
358
326
|
.allowExcessArguments(true)
|
|
359
327
|
.exitOverride()
|
|
360
|
-
.action(async (options) => {
|
|
328
|
+
.action(async (target, options) => {
|
|
361
329
|
const runner = safeCreateRunner(makeRunner);
|
|
330
|
+
const startOptions = resolveStartOptions(target, options);
|
|
362
331
|
try {
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
agentSuccess({ ...result });
|
|
332
|
+
const ack = await runner.start(startOptions);
|
|
333
|
+
agentSuccess({ ...ack });
|
|
366
334
|
}
|
|
367
335
|
catch (err) {
|
|
368
336
|
agentError(err.message, "RunnerError", {
|
|
@@ -370,24 +338,151 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
370
338
|
});
|
|
371
339
|
}
|
|
372
340
|
});
|
|
373
|
-
// ──
|
|
374
|
-
|
|
375
|
-
.command("
|
|
376
|
-
.
|
|
377
|
-
.
|
|
378
|
-
.
|
|
379
|
-
.
|
|
341
|
+
// ── run status ──
|
|
342
|
+
runGroup
|
|
343
|
+
.command("status")
|
|
344
|
+
.argument("<pipeline_run_id>", "Run id")
|
|
345
|
+
.description("Fetch a run's current status by id (self-healing)")
|
|
346
|
+
.allowUnknownOption()
|
|
347
|
+
.allowExcessArguments(true)
|
|
348
|
+
.exitOverride()
|
|
349
|
+
.action(async (runId) => {
|
|
350
|
+
const runner = safeCreateRunner(makeRunner);
|
|
351
|
+
try {
|
|
352
|
+
const run = await runner.getRunStatus(runId);
|
|
353
|
+
agentSuccess({ ...run });
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
agentError(err.message, "RunnerError", {
|
|
357
|
+
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
// ── run result ──
|
|
362
|
+
// Single-shot result lookup: 202 → still running, 200 → result, 409 → failed.
|
|
363
|
+
runGroup
|
|
364
|
+
.command("result")
|
|
365
|
+
.argument("<pipeline_run_id>", "Run id")
|
|
366
|
+
.description("Fetch a run's result by id, once (does not wait)")
|
|
380
367
|
.allowUnknownOption()
|
|
381
368
|
.allowExcessArguments(true)
|
|
382
369
|
.exitOverride()
|
|
383
|
-
.action(async (
|
|
384
|
-
|
|
385
|
-
|
|
370
|
+
.action(async (runId) => {
|
|
371
|
+
const runner = safeCreateRunner(makeRunner);
|
|
372
|
+
let state;
|
|
373
|
+
try {
|
|
374
|
+
state = await runner.getRunResult(runId);
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
agentError(err.message, "RunnerError", {
|
|
378
|
+
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
379
|
+
});
|
|
386
380
|
return;
|
|
387
381
|
}
|
|
382
|
+
switch (state.state) {
|
|
383
|
+
case "running":
|
|
384
|
+
agentSuccess({
|
|
385
|
+
state: "running",
|
|
386
|
+
pipeline_run_id: state.pipeline_run_id,
|
|
387
|
+
retry_after_seconds: state.retry_after_seconds,
|
|
388
|
+
hint: `Run is still in progress. Poll with: mthds-agent run poll ${runId}`,
|
|
389
|
+
});
|
|
390
|
+
break;
|
|
391
|
+
case "completed":
|
|
392
|
+
agentSuccess({
|
|
393
|
+
state: "completed",
|
|
394
|
+
pipeline_run_id: state.pipeline_run_id,
|
|
395
|
+
main_stuff: state.result.main_stuff ?? null,
|
|
396
|
+
graph_spec: state.result.graph_spec ?? null,
|
|
397
|
+
});
|
|
398
|
+
break;
|
|
399
|
+
case "failed":
|
|
400
|
+
agentError(state.message, "RunFailedError", {
|
|
401
|
+
error_domain: AGENT_ERROR_DOMAINS.PIPELINE,
|
|
402
|
+
retryable: false,
|
|
403
|
+
});
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
// ── run poll ──
|
|
408
|
+
// Block until the run reaches a terminal state. Ctrl-C (or any SIGINT) stops
|
|
409
|
+
// waiting WITHOUT cancelling the run — the run keeps executing server-side
|
|
410
|
+
// and can be resumed by id.
|
|
411
|
+
runGroup
|
|
412
|
+
.command("poll")
|
|
413
|
+
.argument("<pipeline_run_id>", "Run id")
|
|
414
|
+
.option("--interval <seconds>", "Base poll interval in seconds (default 2)")
|
|
415
|
+
.option("--timeout <seconds>", "Max seconds to wait before giving up (default 1200)")
|
|
416
|
+
.description("Poll a run to completion, then return its result")
|
|
417
|
+
.allowUnknownOption()
|
|
418
|
+
.allowExcessArguments(true)
|
|
419
|
+
.exitOverride()
|
|
420
|
+
.action(async (runId, options) => {
|
|
421
|
+
const runner = safeCreateRunner(makeRunner);
|
|
422
|
+
const intervalMs = parsePositiveSeconds(options.interval, "--interval");
|
|
423
|
+
const timeoutMs = parsePositiveSeconds(options.timeout, "--timeout");
|
|
424
|
+
const controller = new AbortController();
|
|
425
|
+
const onSigint = () => controller.abort();
|
|
426
|
+
process.once("SIGINT", onSigint);
|
|
427
|
+
try {
|
|
428
|
+
const result = await runner.waitForResult(runId, {
|
|
429
|
+
intervalMs,
|
|
430
|
+
timeoutMs,
|
|
431
|
+
signal: controller.signal,
|
|
432
|
+
});
|
|
433
|
+
agentSuccess({
|
|
434
|
+
state: "completed",
|
|
435
|
+
pipeline_run_id: runId,
|
|
436
|
+
main_stuff: result.main_stuff ?? null,
|
|
437
|
+
graph_spec: result.graph_spec ?? null,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
if (controller.signal.aborted) {
|
|
442
|
+
// Walk-away: not an error. Report the run as still resumable by id.
|
|
443
|
+
agentSuccess({
|
|
444
|
+
state: "running",
|
|
445
|
+
pipeline_run_id: runId,
|
|
446
|
+
resumable: true,
|
|
447
|
+
hint: `Stopped waiting; the run continues. Resume with: mthds-agent run poll ${runId}`,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else if (err instanceof RunFailedError) {
|
|
451
|
+
agentError(err.message, "RunFailedError", {
|
|
452
|
+
error_domain: AGENT_ERROR_DOMAINS.PIPELINE,
|
|
453
|
+
retryable: false,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else if (err instanceof RunTimeoutError) {
|
|
457
|
+
agentError(err.message, "RunTimeoutError", {
|
|
458
|
+
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
459
|
+
retryable: true,
|
|
460
|
+
hint: `The run is still executing. Resume with: mthds-agent run poll ${runId}`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
agentError(err.message, "RunnerError", {
|
|
465
|
+
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
process.removeListener("SIGINT", onSigint);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
// ── models ──
|
|
474
|
+
program
|
|
475
|
+
.command("models")
|
|
476
|
+
.description("List the model deck (models, aliases, waterfalls)")
|
|
477
|
+
.option("--type <type>", "Filter by model category (llm, extract, img_gen, search)")
|
|
478
|
+
.allowUnknownOption()
|
|
479
|
+
.allowExcessArguments(true)
|
|
480
|
+
.exitOverride()
|
|
481
|
+
.action(async (options) => {
|
|
388
482
|
const runner = safeCreateRunner(makeRunner);
|
|
483
|
+
const category = parseModelCategory(options.type);
|
|
389
484
|
try {
|
|
390
|
-
const result = await runner.
|
|
485
|
+
const result = await runner.models(category);
|
|
391
486
|
agentSuccess({ ...result });
|
|
392
487
|
}
|
|
393
488
|
catch (err) {
|
|
@@ -396,6 +491,21 @@ export function registerApiRunnerCommands(program, makeRunner) {
|
|
|
396
491
|
});
|
|
397
492
|
}
|
|
398
493
|
});
|
|
494
|
+
// ── check-model ──
|
|
495
|
+
// check-model is a LOCAL CLI capability (pipelex runner) only — the MTHDS
|
|
496
|
+
// API has no check-model route. Registered here so the API runner errors
|
|
497
|
+
// cleanly instead of failing with an opaque 404.
|
|
498
|
+
program
|
|
499
|
+
.command("check-model")
|
|
500
|
+
.description("Validate a model reference with fuzzy suggestions (pipelex runner only)")
|
|
501
|
+
.argument("<reference>", "Model reference to check")
|
|
502
|
+
.option("--type <type>", "Model category (llm, extract, img_gen, search)")
|
|
503
|
+
.allowUnknownOption()
|
|
504
|
+
.allowExcessArguments(true)
|
|
505
|
+
.exitOverride()
|
|
506
|
+
.action(async () => {
|
|
507
|
+
agentError("check-model is not available on the API runner — the MTHDS API has no check-model route. It is a local capability of the pipelex runner: re-run with --runner pipelex. To list what the API can route to, use: mthds-agent models", "UnsupportedError", { error_domain: AGENT_ERROR_DOMAINS.RUNNER });
|
|
508
|
+
});
|
|
399
509
|
}
|
|
400
510
|
// ── Helpers ──
|
|
401
511
|
function safeCreateRunner(makeRunner) {
|
|
@@ -485,13 +595,120 @@ function resolvePipeCode(mthdsContent, pipeCodeOption) {
|
|
|
485
595
|
});
|
|
486
596
|
throw new Error("unreachable");
|
|
487
597
|
}
|
|
488
|
-
function
|
|
489
|
-
if (
|
|
490
|
-
|
|
598
|
+
function resolveRunInputs(options) {
|
|
599
|
+
if (options.inputsJson)
|
|
600
|
+
return parseJsonOrError(options.inputsJson, "--inputs-json");
|
|
601
|
+
if (options.inputs) {
|
|
602
|
+
const raw = readFileOrError(options.inputs);
|
|
603
|
+
return parseJsonOrError(raw, "inputs file");
|
|
604
|
+
}
|
|
605
|
+
return undefined;
|
|
606
|
+
}
|
|
607
|
+
function resolveStartOptions(target, options) {
|
|
608
|
+
const outputs = {
|
|
609
|
+
output_name: options.outputName,
|
|
610
|
+
output_multiplicity: parseMultiplicity(options.outputMultiplicity),
|
|
611
|
+
dynamic_output_concept_ref: options.dynamicOutput,
|
|
612
|
+
};
|
|
613
|
+
const extra = parseExtraOption(options.extra);
|
|
614
|
+
// No inline bundle → an extension-only start: the run is identified entirely by
|
|
615
|
+
// server-specific args passed through `--extra` (e.g. a stored-method run). The
|
|
616
|
+
// runner is the source of truth for what `extra` it accepts — the SDK and CLI
|
|
617
|
+
// never name those args.
|
|
618
|
+
if (!target && !options.content) {
|
|
619
|
+
return { pipe_code: options.pipe, inputs: resolveRunInputs(options), ...outputs, extra };
|
|
620
|
+
}
|
|
621
|
+
// resolveContentForRun may set options.inputs (directory auto-discovery), so
|
|
622
|
+
// resolve the bundle before reading inputs.
|
|
623
|
+
const mthdsContent = resolveContentForRun(target, options);
|
|
624
|
+
const pipeCode = resolvePipeCode(mthdsContent, options.pipe);
|
|
625
|
+
return { pipe_code: pipeCode, mthds_contents: [mthdsContent], inputs: resolveRunInputs(options), ...outputs, extra };
|
|
626
|
+
}
|
|
627
|
+
/** Parse `--extra <json>` into the generic extension passthrough — a JSON object of server-defined args. */
|
|
628
|
+
function parseExtraOption(raw) {
|
|
629
|
+
if (!raw)
|
|
630
|
+
return undefined;
|
|
631
|
+
const parsed = parseJsonOrError(raw, "--extra");
|
|
632
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
633
|
+
agentError("--extra must be a JSON object of server-specific args.", "ArgumentError", {
|
|
634
|
+
error_domain: AGENT_ERROR_DOMAINS.ARGUMENT,
|
|
635
|
+
});
|
|
636
|
+
throw new Error("unreachable");
|
|
637
|
+
}
|
|
638
|
+
return parsed;
|
|
639
|
+
}
|
|
640
|
+
/** Parse `--output-multiplicity`: "false"/"true" → boolean, a positive integer → count. */
|
|
641
|
+
function parseMultiplicity(raw) {
|
|
642
|
+
if (raw === undefined)
|
|
643
|
+
return undefined;
|
|
644
|
+
if (raw === "true")
|
|
645
|
+
return true;
|
|
646
|
+
if (raw === "false")
|
|
647
|
+
return false;
|
|
648
|
+
const count = Number(raw);
|
|
649
|
+
if (Number.isInteger(count) && count > 0)
|
|
650
|
+
return count;
|
|
651
|
+
agentError("--output-multiplicity must be 'true', 'false', or a positive integer.", "ArgumentError", { error_domain: AGENT_ERROR_DOMAINS.ARGUMENT });
|
|
652
|
+
throw new Error("unreachable");
|
|
653
|
+
}
|
|
654
|
+
function parsePositiveSeconds(raw, label) {
|
|
655
|
+
if (raw === undefined)
|
|
656
|
+
return undefined;
|
|
657
|
+
const seconds = Number(raw);
|
|
658
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
659
|
+
agentError(`${label} must be a positive number of seconds.`, "ArgumentError", {
|
|
660
|
+
error_domain: AGENT_ERROR_DOMAINS.ARGUMENT,
|
|
661
|
+
});
|
|
662
|
+
throw new Error("unreachable");
|
|
663
|
+
}
|
|
664
|
+
return seconds * 1000;
|
|
665
|
+
}
|
|
666
|
+
/** Parse the `--type` model-category filter, erroring on unknown values. */
|
|
667
|
+
function parseModelCategory(raw) {
|
|
668
|
+
if (raw === undefined)
|
|
669
|
+
return undefined;
|
|
670
|
+
if (MODEL_CATEGORIES.includes(raw)) {
|
|
671
|
+
return raw;
|
|
672
|
+
}
|
|
673
|
+
agentError(`--type must be one of: ${MODEL_CATEGORIES.join(", ")}.`, "ArgumentError", { error_domain: AGENT_ERROR_DOMAINS.ARGUMENT });
|
|
674
|
+
throw new Error("unreachable");
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Run the protocol validate (`POST /v1/validate`) and emit the agent envelope.
|
|
678
|
+
* `/validate` is a diagnostic endpoint: a valid bundle returns the structural
|
|
679
|
+
* artifacts (`is_valid: true`); an invalid bundle is a produced verdict (a 200
|
|
680
|
+
* `is_valid: false` body), surfaced as a structured ValidateBundleError envelope —
|
|
681
|
+
* not a thrown error. A non-2xx (a no-verdict condition) still throws.
|
|
682
|
+
*/
|
|
683
|
+
export async function runProtocolValidate(runner, mthdsContents, allowSignatures, mthdsSources) {
|
|
684
|
+
try {
|
|
685
|
+
// `mthds_sources` is a Pipelex-API extension (not the pure protocol), so it
|
|
686
|
+
// rides only the concrete client — reach it via `isApiRunner`. The server
|
|
687
|
+
// threads each source onto `validation_errors[].source`, so agent consumers
|
|
688
|
+
// can resolve the owning file from the structured envelope.
|
|
689
|
+
const report = mthdsSources !== undefined && isApiRunner(runner)
|
|
690
|
+
? await runner.validate(mthdsContents, allowSignatures, mthdsSources)
|
|
691
|
+
: await runner.validate(mthdsContents, allowSignatures);
|
|
692
|
+
if (report.is_valid === false) {
|
|
693
|
+
agentError(report.message, "ValidateBundleError", {
|
|
694
|
+
error_domain: AGENT_ERROR_DOMAINS.VALIDATION,
|
|
695
|
+
is_valid: false,
|
|
696
|
+
validation_errors: report.validation_errors,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
agentSuccess({ success: true, ...report });
|
|
491
700
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
701
|
+
catch (err) {
|
|
702
|
+
// Only no-verdict conditions reach here now: a request-shape 422 (malformed
|
|
703
|
+
// body / mthds_sources mismatch), auth, or a server fault.
|
|
704
|
+
if (err instanceof ApiResponseError && err.status === 422) {
|
|
705
|
+
agentError(err.serverMessage ?? err.message, "ValidationError", {
|
|
706
|
+
error_domain: AGENT_ERROR_DOMAINS.VALIDATION,
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
agentError(err.message, "RunnerError", {
|
|
711
|
+
error_domain: AGENT_ERROR_DOMAINS.RUNNER,
|
|
495
712
|
});
|
|
496
713
|
}
|
|
497
714
|
}
|