@soat/cli 0.22.1 → 0.23.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.
Files changed (2) hide show
  1. package/dist/index.mjs +145 -82
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -13,7 +13,7 @@ import { load } from "js-yaml";
13
13
  import * as os from "node:os";
14
14
 
15
15
  //#region package.json
16
- var version = "0.22.1";
16
+ var version = "0.23.0";
17
17
 
18
18
  //#endregion
19
19
  //#region src/cli-wrappers/wrappers/formations.ts
@@ -745,19 +745,7 @@ var routes = {
745
745
  "in": "body"
746
746
  }, {
747
747
  "name": "tool_bindings",
748
- "description": "Tools to attach, one binding object per tool — the canonical attachment field. Cannot be combined with the deprecated `tool_ids`/`tools` shorthands (400). See [Tool Bindings](/docs/modules/agents#tool-bindings).",
749
- "required": false,
750
- "type": "array",
751
- "in": "body"
752
- }, {
753
- "name": "tool_ids",
754
- "description": "Deprecated shorthand — each entry becomes a bare `{ \"tool_id\": … }` binding. Use `tool_bindings` instead.",
755
- "required": false,
756
- "type": "array",
757
- "in": "body"
758
- }, {
759
- "name": "tools",
760
- "description": "Deprecated shorthand — each entry becomes a bare `{ \"tool\": … }` binding (an ephemeral definition: no separate Tool resource is created, any `project_id` on an entry is ignored, entries never appear in `GET /tools`, cannot be targeted by `active_tool_ids`/`step_rules`, and cannot be of type `pipeline`). Use `tool_bindings` instead.",
748
+ "description": "Tools to attach, one binding object per tool — the only attachment field. An entry is either a reference (`{ \"tool_id\": … }`) or an inline definition (`{ \"tool\": … }`). See [Tool Bindings](/docs/modules/agents#tool-bindings).",
761
749
  "required": false,
762
750
  "type": "array",
763
751
  "in": "body"
@@ -909,19 +897,7 @@ var routes = {
909
897
  "in": "body"
910
898
  }, {
911
899
  "name": "tool_bindings",
912
- "description": "Tools attached to the agent — the canonical attachment field. Replaces the whole binding list; set to `null` to clear. Cannot be combined with the deprecated `tool_ids`/`tools` shorthands (400). See [Tool Bindings](/docs/modules/agents#tool-bindings).",
913
- "required": false,
914
- "type": "array",
915
- "in": "body"
916
- }, {
917
- "name": "tool_ids",
918
- "description": "Deprecated shorthand — replaces only the reference (`tool_id`) bindings, rewriting them bare. Use `tool_bindings` instead.",
919
- "required": false,
920
- "type": "array",
921
- "in": "body"
922
- }, {
923
- "name": "tools",
924
- "description": "Deprecated shorthand — replaces only the inline (`tool`) bindings (ephemeral definitions: no separate Tool resource is created, any `project_id` on an entry is ignored, entries never appear in `GET /tools`, cannot be targeted by `active_tool_ids`/`step_rules`, and cannot be of type `pipeline`). Set to `null` to clear the inline bindings. Use `tool_bindings` instead.",
900
+ "description": "Tools attached to the agent — the only attachment field. Replaces the whole binding list; set to `null` to clear. See [Tool Bindings](/docs/modules/agents#tool-bindings).",
925
901
  "required": false,
926
902
  "type": "array",
927
903
  "in": "body"
@@ -1057,19 +1033,7 @@ var routes = {
1057
1033
  "in": "body"
1058
1034
  }, {
1059
1035
  "name": "tool_bindings",
1060
- "description": "Tools attached to the agent — the canonical attachment field. Replaces the whole binding list; set to `null` to clear. Cannot be combined with the deprecated `tool_ids`/`tools` shorthands (400). See [Tool Bindings](/docs/modules/agents#tool-bindings).",
1061
- "required": false,
1062
- "type": "array",
1063
- "in": "body"
1064
- }, {
1065
- "name": "tool_ids",
1066
- "description": "Deprecated shorthand — replaces only the reference (`tool_id`) bindings, rewriting them bare. Use `tool_bindings` instead.",
1067
- "required": false,
1068
- "type": "array",
1069
- "in": "body"
1070
- }, {
1071
- "name": "tools",
1072
- "description": "Deprecated shorthand — replaces only the inline (`tool`) bindings (ephemeral definitions: no separate Tool resource is created, any `project_id` on an entry is ignored, entries never appear in `GET /tools`, cannot be targeted by `active_tool_ids`/`step_rules`, and cannot be of type `pipeline`). Set to `null` to clear the inline bindings. Use `tool_bindings` instead.",
1036
+ "description": "Tools attached to the agent — the only attachment field. Replaces the whole binding list; set to `null` to clear. See [Tool Bindings](/docs/modules/agents#tool-bindings).",
1073
1037
  "required": false,
1074
1038
  "type": "array",
1075
1039
  "in": "body"
@@ -2126,7 +2090,7 @@ var routes = {
2126
2090
  "type": "string",
2127
2091
  "in": "body"
2128
2092
  }, {
2129
- "name": "system_message",
2093
+ "name": "instructions",
2130
2094
  "description": "Optional system message applied to all completions on this chat",
2131
2095
  "required": false,
2132
2096
  "type": "string",
@@ -2171,57 +2135,35 @@ var routes = {
2171
2135
  "in": "path"
2172
2136
  }]
2173
2137
  },
2174
- "create-chat-completion-for-chat": {
2138
+ "create-chat-completion": {
2175
2139
  serviceClass: "Chats",
2176
- operationId: "createChatCompletionForChat",
2177
- description: "Runs a completion using the AI provider and settings stored in the chat. Pass `stream: true` for SSE streaming. A system message in `messages` overrides the chat's stored system message for this call only. Messages may use `documentId` instead of `content`.",
2140
+ operationId: "createChatCompletion",
2141
+ description: "OpenAI Chat Completions-compatible endpoint. Mirrors OpenAI's `POST /v1/chat/completions` path so an OpenAI SDK can target it by base URL alone. Names exactly one target. With `ai_provider_id` the completion is stateless: the provider's secret is decrypted and the appropriate Vercel AI SDK provider is called, with no server-side model fallback. With `chat_id` the stored chat supplies the provider (or the project's `default_model_route_id`), model and instructions. System content travels only in `instructions` — a `role: \"system\"` entry in `messages` is refused with `400 SYSTEM_MESSAGE_NOT_ALLOWED`. With `chat_id`, a request `instructions` replaces the chat's stored one for this call only; the stored value applies when the request carries none, and the two are never merged. Messages may use `document_id` instead of `content` with either target. Chats hold no message history — send the full `messages` array every time.",
2178
2142
  moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/chats",
2179
2143
  httpMethod: "post",
2180
- pathParams: ["chat_id"],
2144
+ pathParams: [],
2181
2145
  queryParams: [],
2182
2146
  flags: [{
2183
- "name": "chat_id",
2184
- "description": "",
2185
- "required": true,
2147
+ "name": "ai_provider_id",
2148
+ "description": "Public ID of the AI provider to run the completion against. Mutually exclusive with `chat_id`; exactly one of the two is required.\n",
2149
+ "required": false,
2186
2150
  "type": "string",
2187
- "in": "path"
2188
- }, {
2189
- "name": "messages",
2190
- "description": "",
2191
- "required": true,
2192
- "type": "array",
2193
2151
  "in": "body"
2194
2152
  }, {
2195
- "name": "model",
2196
- "description": "Override the chat's default model for this call",
2153
+ "name": "chat_id",
2154
+ "description": "Public ID of a stored chat supplying the provider, model and instructions. Mutually exclusive with `ai_provider_id`; exactly one of the two is required.\n",
2197
2155
  "required": false,
2198
2156
  "type": "string",
2199
2157
  "in": "body"
2200
2158
  }, {
2201
- "name": "stream",
2202
- "description": "When `true` the response is an SSE stream.",
2159
+ "name": "model",
2160
+ "description": "Model identifier. Overrides the provider's `default_model`, or the chat's `model`, when specified.\n",
2203
2161
  "required": false,
2204
- "type": "boolean",
2205
- "in": "body"
2206
- }]
2207
- },
2208
- "create-chat-completion": {
2209
- serviceClass: "Chats",
2210
- operationId: "createChatCompletion",
2211
- description: "OpenAI Chat Completions-compatible endpoint. Mirrors OpenAI's `POST /v1/chat/completions` path so an OpenAI SDK can target it by base URL alone. Resolves the AI provider from `ai_provider_id`, decrypts its secret, and calls the appropriate Vercel AI SDK provider. `ai_provider_id` is required — there is no server-side model fallback.",
2212
- moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/chats",
2213
- httpMethod: "post",
2214
- pathParams: [],
2215
- queryParams: [],
2216
- flags: [{
2217
- "name": "ai_provider_id",
2218
- "description": "Public ID of the AI provider to use.",
2219
- "required": true,
2220
2162
  "type": "string",
2221
2163
  "in": "body"
2222
2164
  }, {
2223
- "name": "model",
2224
- "description": "Model identifier. Overrides the provider's `default_model` when specified.\n",
2165
+ "name": "instructions",
2166
+ "description": "System instructions for this call. Sent to the provider as its `instructions` argument rather than as a message, which is the only place the AI SDK accepts system content (`allowSystemInMessages` defaults to false). This field is the only channel — a `role: \"system\"` entry in `messages` is refused with `400 SYSTEM_MESSAGE_NOT_ALLOWED`. With `chat_id`, this replaces the chat's stored `instructions` for this call only; the stored value applies when the request carries none, and the two are never merged.\n",
2225
2167
  "required": false,
2226
2168
  "type": "string",
2227
2169
  "in": "body"
@@ -3129,6 +3071,40 @@ var routes = {
3129
3071
  "in": "body"
3130
3072
  }]
3131
3073
  },
3074
+ "create-dataset-item-from-generation": {
3075
+ serviceClass: "Evaluations",
3076
+ operationId: "createDatasetItemFromGeneration",
3077
+ description: "Promotes a real, completed generation into a test case: its input messages become the item's `input`, and its own answer becomes `expected_output` unless you supply one. Use it to build an evaluation set out of production traffic rather than hand-authoring fixtures. The item is a **copy**, not a view. It keeps working after the source generation's content is purged, and `source_generation_id` goes null if that generation is deleted — a purge can never quietly stop a suite from being runnable. Requires both `evaluations:CreateDataset` and `generations:GetGeneration`: the call copies content out of a generation, so a principal that may not read that generation may not curate it either. Only a **completed** generation can be promoted (`409 GENERATION_NOT_COMPLETED`), and only while its content is still available: an agent or project running with `trace_content_mode: none` never stored the input, and a purged or expired generation no longer has it (`409 GENERATION_CONTENT_UNAVAILABLE`). Generations that predate input recording answer the same way.",
3078
+ moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/evaluations",
3079
+ httpMethod: "post",
3080
+ pathParams: ["dataset_id"],
3081
+ queryParams: [],
3082
+ flags: [{
3083
+ "name": "dataset_id",
3084
+ "description": "Dataset ID",
3085
+ "required": true,
3086
+ "type": "string",
3087
+ "in": "path"
3088
+ }, {
3089
+ "name": "generation_id",
3090
+ "description": "The completed generation to promote. Must belong to the same project as the dataset.",
3091
+ "required": true,
3092
+ "type": "string",
3093
+ "in": "body"
3094
+ }, {
3095
+ "name": "expected_output",
3096
+ "description": "Reference answer. Omit to use the generation's own answer; pass `null` to store the item with no reference answer.",
3097
+ "required": false,
3098
+ "type": "string",
3099
+ "in": "body"
3100
+ }, {
3101
+ "name": "metadata",
3102
+ "description": "Free-form tags, opaque to the platform",
3103
+ "required": false,
3104
+ "type": "object",
3105
+ "in": "body"
3106
+ }]
3107
+ },
3132
3108
  "update-dataset-item": {
3133
3109
  serviceClass: "Evaluations",
3134
3110
  operationId: "updateDatasetItem",
@@ -4110,7 +4086,7 @@ var routes = {
4110
4086
  "delete-formation": {
4111
4087
  serviceClass: "Formations",
4112
4088
  operationId: "deleteFormation",
4113
- description: "Deletes the formation stack and all its managed resources in reverse dependency order.",
4089
+ description: "Deletes the formation stack and all its managed resources in reverse dependency order. A resource the platform refuses to delete on its own — most often an agent that has generation or trace history — fails the teardown with `409 FORMATION_DELETE_FAILED`, naming every blocking resource in `error.meta.failures`. Resolve the blockers (for an agent, `DELETE /api/v1/agents/{agent_id}?force=true` also removes its generations and traces, and `deletion_policy: retain` exempts it from teardown entirely) and delete the formation again. A refusal the platform can foresee is found by a pre-flight, before the first delete: nothing is removed, and the formation stays `active` and intact for the retry. An unforeseeable error surfaces mid-teardown instead, where resources deleted before the blocker stay deleted and the formation is left in `delete_failed`. The error message states which happened.",
4114
4090
  moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/formations",
4115
4091
  httpMethod: "delete",
4116
4092
  pathParams: ["formation_id"],
@@ -5564,7 +5540,7 @@ var routes = {
5564
5540
  "start-orchestration-run": {
5565
5541
  serviceClass: "Orchestrations",
5566
5542
  operationId: "startOrchestrationRun",
5567
- description: "Creates a new run for the orchestration named by orchestration_id. By default the run executes durably in the background: the response returns immediately with status \"queued\" (a worker then claims it and moves it to \"running\") and progress is observed via get-orchestration-run or run lifecycle webhook events (orchestration_runs.started/awaiting_input/succeeded/failed). Delay and poll waits park the run as \"sleeping\" and are woken by a background scheduler, surviving restarts. Pass wait=true to block until the run reaches a terminal or awaiting_input state (the legacy synchronous behaviour).",
5543
+ description: "Creates a new run for the orchestration named by orchestration_id. By default the run executes durably in the background: the response returns immediately with status \"queued\" (a worker then claims it and moves it to \"running\") and progress is observed via get-orchestration-run or run lifecycle webhook events (orchestration_runs.started/awaiting_input/succeeded/failed). Delay and poll waits park the run as \"sleeping\" and are woken by a background scheduler, surviving restarts. Pass wait=true to block until the run reaches a terminal or awaiting_input state.",
5568
5544
  moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/orchestrations",
5569
5545
  httpMethod: "post",
5570
5546
  pathParams: [],
@@ -7444,11 +7420,11 @@ var routes = {
7444
7420
  "list-usage-meters": {
7445
7421
  serviceClass: "Usage",
7446
7422
  operationId: "listUsageMeters",
7447
- description: "Returns the raw usage-meter rows the caller can access, most recent first, optionally filtered by agent, generation, trace, actor, or session. Each row is the per-generation token usage as reported by the provider, for audit and reconciliation.",
7423
+ description: "Returns the raw usage-meter rows the caller can access, most recent first, optionally filtered by agent, generation, trace, actor, session, or `source`. Each row is the per-generation token usage as reported by the provider, for audit and reconciliation.",
7448
7424
  moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/usage",
7449
7425
  httpMethod: "get",
7450
7426
  pathParams: [],
7451
- queryParams: ["agent_id", "generation_id", "trace_id", "actor_id", "session_id", "trigger_id", "action_id", "meter_type", "limit", "offset"],
7427
+ queryParams: ["agent_id", "generation_id", "trace_id", "actor_id", "session_id", "trigger_id", "action_id", "meter_type", "source", "limit", "offset"],
7452
7428
  flags: [{
7453
7429
  "name": "agent_id",
7454
7430
  "description": "Filter by agent public ID",
@@ -7497,6 +7473,12 @@ var routes = {
7497
7473
  "required": false,
7498
7474
  "type": "string",
7499
7475
  "in": "query"
7476
+ }, {
7477
+ "name": "source",
7478
+ "description": "Filter by what the spend was incurred for. `eval` is an eval run's item generations and `eval_judge` an `llm_judge` scorer's own completion, so verification spend is `source` in (`eval`, `eval_judge`). Ordinary agent traffic carries no source and is matched by neither.\n",
7479
+ "required": false,
7480
+ "type": "string",
7481
+ "in": "query"
7500
7482
  }, {
7501
7483
  "name": "limit",
7502
7484
  "description": "",
@@ -7527,7 +7509,7 @@ var routes = {
7527
7509
  "in": "query"
7528
7510
  }, {
7529
7511
  "name": "group_by",
7530
- "description": "Dimension to bucket by. `day` buckets on the event's UTC calendar day; the others bucket on the matching column.\n",
7512
+ "description": "Dimension to bucket by. `day` buckets on the event's UTC calendar day; the others bucket on the matching column. `source` buckets by what the spend was incurred for (`eval`, `eval_judge`), which is how verification spend is priced apart from the traffic serving real users; unlabelled traffic collapses into the single `null` bucket.\n",
7531
7513
  "required": true,
7532
7514
  "type": "string",
7533
7515
  "in": "query"
@@ -8314,7 +8296,12 @@ var routes = {
8314
8296
  };
8315
8297
 
8316
8298
  //#endregion
8317
- //#region src/index.ts
8299
+ //#region src/naming.ts
8300
+ /**
8301
+ * Flag-name spellings. The CLI accepts kebab-case (the documented convention),
8302
+ * snake_case (the wire spelling, so a body field can be typed exactly as the
8303
+ * spec names it), and camelCase alike; matching happens on the canonical form.
8304
+ */
8318
8305
  /**
8319
8306
  * Normalize kebab-case, snake_case, or camelCase to camelCase for param matching.
8320
8307
  * e.g. agent-id → agentId, actor_id → actorId, agentId → agentId
@@ -8338,6 +8325,73 @@ var kebabToSnake = s => {
8338
8325
  var toKebab = s => {
8339
8326
  return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
8340
8327
  };
8328
+
8329
+ //#endregion
8330
+ //#region src/validateFlags.ts
8331
+ /** Accepted on any command: Commander globals plus the generic id alias. */
8332
+ var ALWAYS_ALLOWED = /* @__PURE__ */new Set(["profile", "id"]);
8333
+ /**
8334
+ * Every flag name this command accepts, canonicalized. Wrapper-added flags are
8335
+ * absent on purpose — a wrapper deletes the flags it consumed before this runs,
8336
+ * so anything left over really is unrecognized.
8337
+ */
8338
+ var knownFlagsFor = route => {
8339
+ return new Set([...route.flags.map(f => {
8340
+ return f.name;
8341
+ }), ...route.pathParams, ...route.queryParams].map(toCanonical));
8342
+ };
8343
+ /**
8344
+ * Offer the closest known flag, so a typo is one line away from its fix rather
8345
+ * than a trip to `--help`. Prefix matching in either direction covers the real
8346
+ * cases (`limitt`/`limit`, `agent`/`agent_id`) without a full edit-distance pass.
8347
+ */
8348
+ var suggestionFor = args => {
8349
+ const target = toCanonical(args.flag).toLowerCase();
8350
+ const near = [...args.known].find(known => {
8351
+ const candidate = known.toLowerCase();
8352
+ return candidate.startsWith(target) || target.startsWith(candidate);
8353
+ });
8354
+ return near ? ` Did you mean --${toKebab(near)}?` : "";
8355
+ };
8356
+ /**
8357
+ * Reject an unrecognized flag **only where the server cannot catch it** — a flag
8358
+ * the CLI would otherwise append to the query string.
8359
+ *
8360
+ * An undeclared query param never reaches a check: the server ignores what it
8361
+ * does not know, so `list-agents --limitt 1` returned every row instead of one.
8362
+ * The filter failed **open**, with exit 0 and no warning, and the caller acted on
8363
+ * a superset it never asked for. The name does not survive the request, so only
8364
+ * the client can catch it.
8365
+ *
8366
+ * An unrecognized flag on a **write** is deliberately still forwarded. The server
8367
+ * already answers `400 VALIDATION_FAILED` naming the field (`strictFields`), and
8368
+ * that check is the authority on what a body may contain — rejecting locally
8369
+ * would front-run it, hide the real error, and make an older CLI refuse a field a
8370
+ * newer server accepts. It would also make the behavior untestable through the
8371
+ * CLI: `tests/smoke-tests.sh` asserts precisely that the server rejects
8372
+ * `update-agent --reasoning` with a 400, which a client-side refusal turns into a
8373
+ * usage error the assertion cannot read.
8374
+ *
8375
+ * Returns the error lines to print; empty when every flag is recognized.
8376
+ */
8377
+ var findUnknownFlags = args => {
8378
+ if (args.route.httpMethod !== "get") return [];
8379
+ const known = knownFlagsFor(args.route);
8380
+ const unknown = args.flagKeys.filter(flagKey => {
8381
+ if (ALWAYS_ALLOWED.has(flagKey)) return false;
8382
+ return !known.has(toCanonical(flagKey));
8383
+ });
8384
+ if (unknown.length === 0) return [];
8385
+ return [...unknown.map(flag => {
8386
+ return `Unknown flag --${flag} for '${args.commandName}'.${suggestionFor({
8387
+ flag,
8388
+ known
8389
+ })}`;
8390
+ }), `Run "soat ${args.commandName} --help" to see the flags this command accepts.`];
8391
+ };
8392
+
8393
+ //#endregion
8394
+ //#region src/index.ts
8341
8395
  /**
8342
8396
  * Renders a command's payload for stdout.
8343
8397
  *
@@ -8536,6 +8590,15 @@ program.argument("[command]", "API command in kebab-case (e.g. list-actors)").ar
8536
8590
  const flagTypeByCanonical = new Map(route.flags.map(f => {
8537
8591
  return [toCanonical(f.name), f.type];
8538
8592
  }));
8593
+ const unknownFlagErrors = findUnknownFlags({
8594
+ commandName,
8595
+ route,
8596
+ flagKeys: Object.keys(flags)
8597
+ });
8598
+ if (unknownFlagErrors.length > 0) {
8599
+ for (const line of unknownFlagErrors) console.error(line);
8600
+ process.exit(1);
8601
+ }
8539
8602
  const pathArgs = {};
8540
8603
  const queryArgs = {};
8541
8604
  const bodyArgs = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soat/cli",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@inquirer/input": "^5.1.2",
@@ -8,7 +8,7 @@
8
8
  "@ttoss/logger": "^0.8.19",
9
9
  "commander": "^15.0.0",
10
10
  "js-yaml": "^5.2.1",
11
- "@soat/sdk": "0.22.1"
11
+ "@soat/sdk": "0.23.0"
12
12
  },
13
13
  "devDependencies": {
14
14
  "@ttoss/config": "^1.37.17",