@2kw/ai 6.2.0-dev.25 → 6.2.0-dev.30

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.
@@ -52,10 +52,10 @@ async function withAgentSuggestions(client, agentRef, err) {
52
52
  /**
53
53
  * Prints a run result and sets the exit code. In interactive mode an approval pause
54
54
  * is decided at the terminal and continued until the run stops pausing for approval.
55
- * Continuations stay on the run's label, and the envelope's `next` names it.
55
+ * Continuations stay on the run's label and model override, and the envelope's `next` names both.
56
56
  */
57
57
  async function finishRun(command, client, target, first, opts) {
58
- const agentRef = target.label ? `${target.name}@${target.label}` : target.name;
58
+ const agentRef = `${target.name}${target.label ? `@${target.label}` : ""}${target.model ? `#${target.model}` : ""}`;
59
59
  let result = first;
60
60
  let env = buildRunEnvelope(result, agentRef);
61
61
  if (opts.interactive && env.status === "requires_approval") {
@@ -90,7 +90,7 @@ async function finishRun(command, client, target, first, opts) {
90
90
  throw new CliUsageError(`No pending approvals for ${responseId} (already decided or superseded).`);
91
91
  }
92
92
  const decisions = decisionsFromAnswers(rows, answers);
93
- result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label));
93
+ result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model));
94
94
  env = buildRunEnvelope(result, agentRef);
95
95
  }
96
96
  }
@@ -116,7 +116,7 @@ async function finishRun(command, client, target, first, opts) {
116
116
  export function makeAgentRunCommand() {
117
117
  return new Command("run")
118
118
  .description("Run an agent on a task (exit 3 = paused for approval, 4 = needs client tool output, 5 = incomplete)")
119
- .argument("<agent>", "Agent name or id, optionally with @label")
119
+ .argument("<agent>", "Agent name or id, optionally with @label and #model")
120
120
  .argument("[input]", "Task text (or use --input-file, or pipe it on stdin)")
121
121
  .option("--input-file <path>", "Read the task text from a file")
122
122
  .option("--conversation <id>", "Continue a conversation")
@@ -145,8 +145,8 @@ export function makeAgentRunCommand() {
145
145
  }
146
146
  // The prompt reads stdin and writes to stderr, so those two decide; stdout may be redirected.
147
147
  const interactive = !!process.stdin.isTTY && !!process.stderr.isTTY && !isJsonOutput(command) && opts.input !== false && !opts.raw;
148
- const { name, label } = splitAgentRef(agentRef);
149
- await finishRun(command, client, { name, label }, data, {
148
+ const { name, label, model } = splitAgentRef(agentRef);
149
+ await finishRun(command, client, { name, label, model }, data, {
150
150
  raw: !!opts.raw,
151
151
  interactive,
152
152
  });
@@ -156,7 +156,7 @@ export function makeAgentRunCommand() {
156
156
  export function makeAgentDecideCommand() {
157
157
  return new Command("decide")
158
158
  .description("Decide every pending approval of a paused run and continue it")
159
- .argument("<agent>", "Agent name or id, optionally with @label (use the same label the run used)")
159
+ .argument("<agent>", "Agent name or id, optionally with @label and #model (use the same @label and #model the run used)")
160
160
  .requiredOption("--response <responseId>", "The paused response id (responseId in the run envelope)")
161
161
  .option("--approve <ids...>", "Approval ids to approve")
162
162
  .option("--reject <ids...>", "Approval ids to reject")
@@ -168,7 +168,10 @@ export function makeAgentDecideCommand() {
168
168
  .action(async (agentRef, opts, command) => {
169
169
  await runAction(command, async () => {
170
170
  const client = getClient(command);
171
- const { name, label } = splitAgentRef(agentRef);
171
+ const { name, label, model } = splitAgentRef(agentRef);
172
+ // Dropped silently, an empty override would continue the run on the default model.
173
+ if (model === "")
174
+ throw new CliUsageError(`Empty #model in '${agentRef}': use the model the run used, or drop the '#'.`);
172
175
  const agent = await resolveAgent(client, name);
173
176
  const pending = await fetchPendingApprovals(client, String(agent.id), opts.response);
174
177
  const decisions = planDecisions(pending, {
@@ -179,8 +182,8 @@ export function makeAgentDecideCommand() {
179
182
  reason: opts.reason,
180
183
  remember: opts.remember,
181
184
  }, opts.response);
182
- const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label));
183
- await finishRun(command, client, { name, label }, result, { raw: !!opts.raw, interactive: false });
185
+ const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model));
186
+ await finishRun(command, client, { name, label, model }, result, { raw: !!opts.raw, interactive: false });
184
187
  });
185
188
  });
186
189
  }
@@ -35,7 +35,7 @@ export function makeProvidersCommand() {
35
35
  .command("create")
36
36
  .description("Create a new provider")
37
37
  .requiredOption("-n, --name <name>", "Provider name")
38
- .requiredOption("--type <type>", "Provider type (openai, azure_openai, anthropic, xai, mistral, vertex_ai, ollama)")
38
+ .requiredOption("--type <type>", "Provider type (openai, azure_openai, anthropic, xai, mistral, ollama)")
39
39
  .requiredOption("--provider-api-key <key>", "API key for the AI provider")
40
40
  .option("-c, --config <json>", "Provider config as JSON")
41
41
  .action(async (opts, command) => {
@@ -107,7 +107,7 @@ export function makeProvidersCommand() {
107
107
  cmd
108
108
  .command("test")
109
109
  .description("Test connection to an AI provider using given or stored credentials")
110
- .requiredOption("--type <type>", "Provider type (openai, azure_openai, anthropic, xai, mistral, vertex_ai, ollama)")
110
+ .requiredOption("--type <type>", "Provider type (openai, azure_openai, anthropic, xai, mistral, ollama)")
111
111
  .option("--provider-api-key <key>", "API key to test (omit to use --provider-id's stored key)")
112
112
  .option("--provider-id <id>", "Existing provider ID whose stored credentials should be used")
113
113
  .option("-c, --config <json>", "Provider config as JSON")
@@ -32,9 +32,10 @@ export declare function decisionsFromAnswers(pending: ApprovalRow[], answers: Ma
32
32
  /** Same item shape as n8n Decide Approval (#660). */
33
33
  export declare function toApprovalItems(decisions: Decision[]): Record<string, string>[];
34
34
  /**
35
- * Continues on `agent/<agentId>`, or `agent/<agentId>@<label>` when the run used a label: the server
36
- * resolves the approved call's tools from the continuation's version, so the label must match the run's.
37
- * Never the echoed `name@<versionNumber>` (it would 404 as a label) and never a `#model` suffix.
35
+ * Continues on `agent/<agentId>[@<label>][#<model>]` with the label and model override the run used: the
36
+ * server resolves the continuation's tools, instructions and model from this reference, so both must match
37
+ * the run's or the continuation drops to another version or the agent's default model.
38
+ * Never the echoed `name@<versionNumber>` (it would 404 as a label).
38
39
  */
39
- export declare function continueWithDecisions(client: ApiClient, agentId: string, responseId: string, decisions: Decision[], label?: string): Promise<ResponsesResult>;
40
+ export declare function continueWithDecisions(client: ApiClient, agentId: string, responseId: string, decisions: Decision[], label?: string, model?: string): Promise<ResponsesResult>;
40
41
  //# sourceMappingURL=agent-decide.d.ts.map
@@ -109,13 +109,14 @@ export function toApprovalItems(decisions) {
109
109
  }));
110
110
  }
111
111
  /**
112
- * Continues on `agent/<agentId>`, or `agent/<agentId>@<label>` when the run used a label: the server
113
- * resolves the approved call's tools from the continuation's version, so the label must match the run's.
114
- * Never the echoed `name@<versionNumber>` (it would 404 as a label) and never a `#model` suffix.
112
+ * Continues on `agent/<agentId>[@<label>][#<model>]` with the label and model override the run used: the
113
+ * server resolves the continuation's tools, instructions and model from this reference, so both must match
114
+ * the run's or the continuation drops to another version or the agent's default model.
115
+ * Never the echoed `name@<versionNumber>` (it would 404 as a label).
115
116
  */
116
- export async function continueWithDecisions(client, agentId, responseId, decisions, label) {
117
+ export async function continueWithDecisions(client, agentId, responseId, decisions, label, model) {
117
118
  const body = {
118
- model: label ? `agent/${agentId}@${label}` : `agent/${agentId}`,
119
+ model: `agent/${agentId}${label ? `@${label}` : ""}${model ? `#${model}` : ""}`,
119
120
  previous_response_id: responseId,
120
121
  stream: false,
121
122
  input: toApprovalItems(decisions),
@@ -2,6 +2,7 @@ import type { components } from "../generated/openapi.js";
2
2
  import type { getClient } from "./client.js";
3
3
  export type ApiClient = ReturnType<typeof getClient>;
4
4
  export type AgentDTO = components["schemas"]["AgentDTO"];
5
+ /** `name[@label][#model]`, split like the server: the model at the last `#`, then the label at the last `@`. */
5
6
  export declare function splitAgentRef(ref: string): {
6
7
  name: string;
7
8
  label: string | undefined;
@@ -3,8 +3,9 @@ import { paginationParams } from "./pagination.js";
3
3
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
4
  const PAGE_SIZE = 100;
5
5
  const MAX_PAGES = 50;
6
+ /** `name[@label][#model]`, split like the server: the model at the last `#`, then the label at the last `@`. */
6
7
  export function splitAgentRef(ref) {
7
- const hash = ref.indexOf("#");
8
+ const hash = ref.lastIndexOf("#");
8
9
  const model = hash >= 0 ? ref.slice(hash + 1) : undefined;
9
10
  const base = hash >= 0 ? ref.slice(0, hash) : ref;
10
11
  const at = base.lastIndexOf("@");
@@ -24,8 +24,8 @@ export function extractResponseText(result) {
24
24
  * any other `@` suffix stays part of the name.
25
25
  */
26
26
  export function parseAgentModel(model) {
27
- // Strip the `#<model>` override before matching the agent reference.
28
- const hash = (model ?? "").indexOf("#");
27
+ // Strip the `#<model>` override (from the last `#`, as the server splits it) before matching.
28
+ const hash = (model ?? "").lastIndexOf("#");
29
29
  const ref = hash >= 0 ? (model ?? "").slice(0, hash) : model ?? "";
30
30
  const withVersion = /^agent\/(.+)@(\d+)$/.exec(ref);
31
31
  if (withVersion)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "6.2.0-dev.25",
3
+ "version": "6.2.0-dev.30",
4
4
  "description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
5
5
  "keywords": [
6
6
  "cli",