@2kw/ai 6.2.0-dev.14 → 6.2.0-dev.25
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 +2 -2
- package/dist/agent-config/export.d.ts +31 -0
- package/dist/agent-config/export.js +87 -0
- package/dist/agent-config/file.d.ts +20 -0
- package/dist/agent-config/file.js +137 -0
- package/dist/agent-config/plan.d.ts +33 -0
- package/dist/agent-config/plan.js +100 -0
- package/dist/agent-config/schema.d.ts +420 -0
- package/dist/agent-config/schema.js +222 -0
- package/dist/agent-config/template.d.ts +6 -0
- package/dist/agent-config/template.js +59 -0
- package/dist/agent-config/validate.d.ts +29 -0
- package/dist/agent-config/validate.js +123 -0
- package/dist/commands/agent-apply.d.ts +9 -0
- package/dist/commands/agent-apply.js +176 -0
- package/dist/commands/agent-export.d.ts +3 -0
- package/dist/commands/agent-export.js +100 -0
- package/dist/commands/agent-init.d.ts +4 -0
- package/dist/commands/agent-init.js +54 -0
- package/dist/commands/agent-run.d.ts +5 -0
- package/dist/commands/agent-run.js +187 -0
- package/dist/commands/agent-versions.js +12 -7
- package/dist/commands/agents.js +24 -9
- package/dist/commands/ai.d.ts +5 -37
- package/dist/commands/ai.js +29 -136
- package/dist/lib/agent-decide.d.ts +40 -0
- package/dist/lib/agent-decide.js +128 -0
- package/dist/lib/agent-lookup.d.ts +14 -0
- package/dist/lib/agent-lookup.js +70 -0
- package/dist/lib/agent-models.d.ts +28 -0
- package/dist/lib/agent-models.js +53 -0
- package/dist/lib/agent-run.d.ts +83 -0
- package/dist/lib/agent-run.js +170 -0
- package/dist/lib/approval-prompt.d.ts +18 -0
- package/dist/lib/approval-prompt.js +84 -0
- package/dist/lib/client.d.ts +2 -0
- package/dist/lib/client.js +13 -2
- package/dist/lib/errors.d.ts +21 -2
- package/dist/lib/errors.js +46 -2
- package/package.json +5 -2
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
+
import { isJsonOutput } from "../lib/config.js";
|
|
5
|
+
import { BackboneApiError, CliUsageError } from "../lib/errors.js";
|
|
6
|
+
import { withSpinner } from "../lib/output.js";
|
|
7
|
+
import { resolveAgent, splitAgentRef, suggestAgents } from "../lib/agent-lookup.js";
|
|
8
|
+
import { buildRunEnvelope, EXIT_CODES, printRunText, stripControl } from "../lib/agent-run.js";
|
|
9
|
+
import { continueWithDecisions, decisionsFromAnswers, fetchPendingApprovals, planDecisions, } from "../lib/agent-decide.js";
|
|
10
|
+
import { PromptAbortedError, promptApprovals, readlineAsk } from "../lib/approval-prompt.js";
|
|
11
|
+
import { withModelOverride } from "../lib/agent-models.js";
|
|
12
|
+
import { readStdinInput } from "./ai.js";
|
|
13
|
+
export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
14
|
+
if (arg)
|
|
15
|
+
return arg;
|
|
16
|
+
if (inputFile) {
|
|
17
|
+
let text;
|
|
18
|
+
try {
|
|
19
|
+
text = readFileSync(inputFile, "utf-8");
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const code = err.code;
|
|
23
|
+
throw new CliUsageError(`${inputFile}: cannot read file (${code ?? err.message})`);
|
|
24
|
+
}
|
|
25
|
+
// A byte order mark (U+FEFF) from Windows editors is not part of the task.
|
|
26
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
27
|
+
text = text.slice(1);
|
|
28
|
+
if (!text.trim())
|
|
29
|
+
throw new CliUsageError(`Input file ${inputFile} is empty.`);
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
const piped = readStdin();
|
|
33
|
+
if (piped)
|
|
34
|
+
return piped;
|
|
35
|
+
throw new CliUsageError("Input is required: pass it as an argument, with --input-file, or via stdin.");
|
|
36
|
+
}
|
|
37
|
+
async function withAgentSuggestions(client, agentRef, err) {
|
|
38
|
+
if (!(err instanceof BackboneApiError) || err.status !== 404 || !err.message.startsWith("Agent not found")) {
|
|
39
|
+
return err;
|
|
40
|
+
}
|
|
41
|
+
const names = await suggestAgents(client, splitAgentRef(agentRef).name);
|
|
42
|
+
if (names.length === 0)
|
|
43
|
+
return err;
|
|
44
|
+
return new BackboneApiError({
|
|
45
|
+
status: err.status,
|
|
46
|
+
error: err.errorType,
|
|
47
|
+
message: `${err.message}. Did you mean: ${names.join(", ")}`,
|
|
48
|
+
timestamp: err.timestamp,
|
|
49
|
+
code: err.code,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Prints a run result and sets the exit code. In interactive mode an approval pause
|
|
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.
|
|
56
|
+
*/
|
|
57
|
+
async function finishRun(command, client, target, first, opts) {
|
|
58
|
+
const agentRef = target.label ? `${target.name}@${target.label}` : target.name;
|
|
59
|
+
let result = first;
|
|
60
|
+
let env = buildRunEnvelope(result, agentRef);
|
|
61
|
+
if (opts.interactive && env.status === "requires_approval") {
|
|
62
|
+
try {
|
|
63
|
+
const agent = await resolveAgent(client, target.name);
|
|
64
|
+
while (env.status === "requires_approval" && env.responseId) {
|
|
65
|
+
const responseId = env.responseId;
|
|
66
|
+
// Show what the agent said this round before asking; only the final round's text reaches stdout.
|
|
67
|
+
if (env.text)
|
|
68
|
+
console.error(stripControl(env.text));
|
|
69
|
+
let answers;
|
|
70
|
+
// One readline per round: the continuation spinner (ora) pauses stdin when it stops,
|
|
71
|
+
// and only a fresh readline resumes it for the next round's questions.
|
|
72
|
+
const { ask, close } = readlineAsk();
|
|
73
|
+
try {
|
|
74
|
+
answers = await promptApprovals(env.pendingApprovals, ask);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
// Ctrl+C / Ctrl+D / closed stdin: nothing was decided, so the run is still paused (exit 3 below).
|
|
78
|
+
if (err instanceof PromptAbortedError) {
|
|
79
|
+
console.error("Approval prompt closed; nothing was decided.");
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
close();
|
|
86
|
+
}
|
|
87
|
+
const rows = await fetchPendingApprovals(client, String(agent.id), responseId);
|
|
88
|
+
// Same local check as `decide`: never send a continuation with no decisions.
|
|
89
|
+
if (rows.length === 0) {
|
|
90
|
+
throw new CliUsageError(`No pending approvals for ${responseId} (already decided or superseded).`);
|
|
91
|
+
}
|
|
92
|
+
const decisions = decisionsFromAnswers(rows, answers);
|
|
93
|
+
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label));
|
|
94
|
+
env = buildRunEnvelope(result, agentRef);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
// Whatever failed, the paused response id is what the user needs to resume by hand.
|
|
99
|
+
if (env.responseId) {
|
|
100
|
+
console.error(stripControl(`Run is still paused: response ${env.responseId}. Resume with: ${env.next}`));
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (opts.raw) {
|
|
106
|
+
console.log(JSON.stringify(result, null, 2));
|
|
107
|
+
}
|
|
108
|
+
else if (isJsonOutput(command)) {
|
|
109
|
+
console.log(JSON.stringify(env, null, 2));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
printRunText(env);
|
|
113
|
+
}
|
|
114
|
+
process.exitCode = EXIT_CODES[env.status];
|
|
115
|
+
}
|
|
116
|
+
export function makeAgentRunCommand() {
|
|
117
|
+
return new Command("run")
|
|
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")
|
|
120
|
+
.argument("[input]", "Task text (or use --input-file, or pipe it on stdin)")
|
|
121
|
+
.option("--input-file <path>", "Read the task text from a file")
|
|
122
|
+
.option("--conversation <id>", "Continue a conversation")
|
|
123
|
+
.option("--continue <responseId>", "Continue from a previous response")
|
|
124
|
+
.option("--model <model>", "Run this request on another model from the agent's list (sends <agent>#<model>)")
|
|
125
|
+
.option("--no-input", "Never prompt for approvals, even on a terminal")
|
|
126
|
+
.option("--raw", "Print the untouched API response")
|
|
127
|
+
.action(async (agentArg, input, opts, command) => {
|
|
128
|
+
await runAction(command, async () => {
|
|
129
|
+
const agentRef = withModelOverride(agentArg, opts.model);
|
|
130
|
+
const text = resolveRunInput(input, opts.inputFile);
|
|
131
|
+
const client = getClient(command);
|
|
132
|
+
const body = { model: `agent/${agentRef}`, input: text, stream: false };
|
|
133
|
+
if (opts.conversation)
|
|
134
|
+
body.conversation = opts.conversation;
|
|
135
|
+
if (opts.continue)
|
|
136
|
+
body.previous_response_id = opts.continue;
|
|
137
|
+
let data;
|
|
138
|
+
try {
|
|
139
|
+
// The generated body type does not model a bare-string input, hence the cast.
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
|
+
({ data } = await withSpinner("Running agent...", () => client.POST("/v1/responses", { body })));
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
throw await withAgentSuggestions(client, agentRef, err);
|
|
145
|
+
}
|
|
146
|
+
// The prompt reads stdin and writes to stderr, so those two decide; stdout may be redirected.
|
|
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, {
|
|
150
|
+
raw: !!opts.raw,
|
|
151
|
+
interactive,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export function makeAgentDecideCommand() {
|
|
157
|
+
return new Command("decide")
|
|
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)")
|
|
160
|
+
.requiredOption("--response <responseId>", "The paused response id (responseId in the run envelope)")
|
|
161
|
+
.option("--approve <ids...>", "Approval ids to approve")
|
|
162
|
+
.option("--reject <ids...>", "Approval ids to reject")
|
|
163
|
+
.option("--approve-all", "Approve every pending approval of the response")
|
|
164
|
+
.option("--reject-all", "Reject every pending approval of the response")
|
|
165
|
+
.option("--reason <text>", "Reason recorded on each decision (the model sees it on rejects)")
|
|
166
|
+
.option("--remember", "Also approve later calls of the same tool in this conversation")
|
|
167
|
+
.option("--raw", "Print the untouched API response")
|
|
168
|
+
.action(async (agentRef, opts, command) => {
|
|
169
|
+
await runAction(command, async () => {
|
|
170
|
+
const client = getClient(command);
|
|
171
|
+
const { name, label } = splitAgentRef(agentRef);
|
|
172
|
+
const agent = await resolveAgent(client, name);
|
|
173
|
+
const pending = await fetchPendingApprovals(client, String(agent.id), opts.response);
|
|
174
|
+
const decisions = planDecisions(pending, {
|
|
175
|
+
approve: opts.approve,
|
|
176
|
+
reject: opts.reject,
|
|
177
|
+
approveAll: opts.approveAll,
|
|
178
|
+
rejectAll: opts.rejectAll,
|
|
179
|
+
reason: opts.reason,
|
|
180
|
+
remember: opts.remember,
|
|
181
|
+
}, 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 });
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=agent-run.js.map
|
|
@@ -2,6 +2,8 @@ import { Command } from "commander";
|
|
|
2
2
|
import { getClient, runAction } from "../lib/client.js";
|
|
3
3
|
import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
|
|
4
4
|
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
5
|
+
import { maskSecrets } from "../agent-config/file.js";
|
|
6
|
+
import { collectModel, MODEL_HELP, MODELS_HELP, modelFieldsFromOptions } from "../lib/agent-models.js";
|
|
5
7
|
export function makeAgentVersionsCommand() {
|
|
6
8
|
const cmd = new Command("versions").description("Manage agent versions");
|
|
7
9
|
const list = new Command("list").description("List versions of an agent");
|
|
@@ -16,7 +18,8 @@ export function makeAgentVersionsCommand() {
|
|
|
16
18
|
query: paginationParams(opts),
|
|
17
19
|
},
|
|
18
20
|
});
|
|
19
|
-
|
|
21
|
+
// Version reads return webhook secrets in plaintext (#669); only `agents export --include-secrets` prints them.
|
|
22
|
+
formatPage(maskSecrets(data, []), command, ["id", "versionNumber", "models", "changeDescription", "createdAt"]);
|
|
20
23
|
});
|
|
21
24
|
});
|
|
22
25
|
cmd.addCommand(list);
|
|
@@ -29,7 +32,7 @@ export function makeAgentVersionsCommand() {
|
|
|
29
32
|
await runAction(command, async () => {
|
|
30
33
|
const client = getClient(command);
|
|
31
34
|
const { data } = await client.GET("/v1/agents/{agentId}/versions/{versionId}", { params: { path: { agentId: opts.agent, versionId } } });
|
|
32
|
-
formatDetail(data, command);
|
|
35
|
+
formatDetail(maskSecrets(data, []), command);
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
cmd
|
|
@@ -40,14 +43,15 @@ export function makeAgentVersionsCommand() {
|
|
|
40
43
|
await runAction(command, async () => {
|
|
41
44
|
const client = getClient(command);
|
|
42
45
|
const { data } = await client.GET("/v1/agents/{agentId}/versions/latest", { params: { path: { agentId: opts.agent } } });
|
|
43
|
-
formatDetail(data, command);
|
|
46
|
+
formatDetail(maskSecrets(data, []), command);
|
|
44
47
|
});
|
|
45
48
|
});
|
|
46
49
|
cmd
|
|
47
50
|
.command("create")
|
|
48
51
|
.description("Create a new agent version")
|
|
49
52
|
.requiredOption("--agent <id>", "Agent ID")
|
|
50
|
-
.
|
|
53
|
+
.option("-m, --model <model>", MODEL_HELP, collectModel)
|
|
54
|
+
.option("--models <list>", MODELS_HELP)
|
|
51
55
|
.option("-i, --instructions <text>", "System instructions")
|
|
52
56
|
.option("--options <json>", "Model options as JSON")
|
|
53
57
|
.option("--tools <json>", "Tool definitions as JSON")
|
|
@@ -56,11 +60,12 @@ export function makeAgentVersionsCommand() {
|
|
|
56
60
|
.option("-c, --change-description <text>", "Change description")
|
|
57
61
|
.action(async (opts, command) => {
|
|
58
62
|
await runAction(command, async () => {
|
|
63
|
+
const models = modelFieldsFromOptions(opts, true);
|
|
59
64
|
const client = getClient(command);
|
|
60
65
|
const { data } = await client.POST("/v1/agents/{agentId}/versions", {
|
|
61
66
|
params: { path: { agentId: opts.agent } },
|
|
62
67
|
body: {
|
|
63
|
-
|
|
68
|
+
...models,
|
|
64
69
|
instructions: opts.instructions,
|
|
65
70
|
changeDescription: opts.changeDescription,
|
|
66
71
|
options: opts.options ? JSON.parse(opts.options) : undefined,
|
|
@@ -69,7 +74,7 @@ export function makeAgentVersionsCommand() {
|
|
|
69
74
|
hitlPolicy: opts.hitlPolicy ? JSON.parse(opts.hitlPolicy) : undefined,
|
|
70
75
|
},
|
|
71
76
|
});
|
|
72
|
-
formatDetail(data, command);
|
|
77
|
+
formatDetail(maskSecrets(data, []), command);
|
|
73
78
|
});
|
|
74
79
|
});
|
|
75
80
|
cmd
|
|
@@ -81,7 +86,7 @@ export function makeAgentVersionsCommand() {
|
|
|
81
86
|
await runAction(command, async () => {
|
|
82
87
|
const client = getClient(command);
|
|
83
88
|
const { data } = await client.PUT("/v1/agents/{agentId}/versions/{versionId}/activate", { params: { path: { agentId: opts.agent, versionId } } });
|
|
84
|
-
formatDetail(data, command);
|
|
89
|
+
formatDetail(maskSecrets(data, []), command);
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
cmd
|
package/dist/commands/agents.js
CHANGED
|
@@ -4,6 +4,12 @@ import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
|
|
|
4
4
|
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
5
5
|
import { makeAgentVersionsCommand } from "./agent-versions.js";
|
|
6
6
|
import { makeAgentLabelsCommand } from "./agent-labels.js";
|
|
7
|
+
import { makeAgentDecideCommand, makeAgentRunCommand } from "./agent-run.js";
|
|
8
|
+
import { makeAgentApplyCommand } from "./agent-apply.js";
|
|
9
|
+
import { makeAgentExportCommand } from "./agent-export.js";
|
|
10
|
+
import { makeAgentInitCommand, makeAgentSchemaCommand } from "./agent-init.js";
|
|
11
|
+
import { maskSecrets } from "../agent-config/file.js";
|
|
12
|
+
import { collectModel, MODEL_HELP, MODELS_HELP, modelFieldsFromOptions } from "../lib/agent-models.js";
|
|
7
13
|
export function makeAgentsCommand() {
|
|
8
14
|
const cmd = new Command("agents").description("Manage agents");
|
|
9
15
|
const list = new Command("list").description("List agents");
|
|
@@ -15,7 +21,8 @@ export function makeAgentsCommand() {
|
|
|
15
21
|
const { data } = await client.GET("/v1/agents", {
|
|
16
22
|
params: { query: { search: opts.search, ...paginationParams(opts) } },
|
|
17
23
|
});
|
|
18
|
-
|
|
24
|
+
// Agent reads return webhook secrets in plaintext (#669); only `export --include-secrets` prints them.
|
|
25
|
+
formatPage(maskSecrets(data, []), command, ["id", "name", "models", "latestVersionId", "createdAt"]);
|
|
19
26
|
});
|
|
20
27
|
});
|
|
21
28
|
cmd.addCommand(list);
|
|
@@ -29,14 +36,15 @@ export function makeAgentsCommand() {
|
|
|
29
36
|
const { data } = await client.GET("/v1/agents/{id}", {
|
|
30
37
|
params: { path: { id } },
|
|
31
38
|
});
|
|
32
|
-
formatDetail(data, command);
|
|
39
|
+
formatDetail(maskSecrets(data, []), command);
|
|
33
40
|
});
|
|
34
41
|
});
|
|
35
42
|
cmd
|
|
36
43
|
.command("create")
|
|
37
44
|
.description("Create a new agent")
|
|
38
45
|
.requiredOption("-n, --name <name>", "Agent name")
|
|
39
|
-
.
|
|
46
|
+
.option("-m, --model <model>", MODEL_HELP, collectModel)
|
|
47
|
+
.option("--models <list>", MODELS_HELP)
|
|
40
48
|
.option("-d, --description <text>", "Agent description")
|
|
41
49
|
.option("-i, --instructions <text>", "System instructions")
|
|
42
50
|
.option("--options <json>", "Model options as JSON")
|
|
@@ -45,11 +53,12 @@ export function makeAgentsCommand() {
|
|
|
45
53
|
.option("--hitl-policy <json>", "Human-in-the-loop policy as JSON")
|
|
46
54
|
.action(async (opts, command) => {
|
|
47
55
|
await runAction(command, async () => {
|
|
56
|
+
const models = modelFieldsFromOptions(opts, true);
|
|
48
57
|
const client = getClient(command);
|
|
49
58
|
const { data } = await client.POST("/v1/agents", {
|
|
50
59
|
body: {
|
|
51
60
|
name: opts.name,
|
|
52
|
-
|
|
61
|
+
...models,
|
|
53
62
|
description: opts.description,
|
|
54
63
|
instructions: opts.instructions,
|
|
55
64
|
options: opts.options ? JSON.parse(opts.options) : undefined,
|
|
@@ -58,7 +67,7 @@ export function makeAgentsCommand() {
|
|
|
58
67
|
hitlPolicy: opts.hitlPolicy ? JSON.parse(opts.hitlPolicy) : undefined,
|
|
59
68
|
},
|
|
60
69
|
});
|
|
61
|
-
formatDetail(data, command);
|
|
70
|
+
formatDetail(maskSecrets(data, []), command);
|
|
62
71
|
});
|
|
63
72
|
});
|
|
64
73
|
cmd
|
|
@@ -66,7 +75,8 @@ export function makeAgentsCommand() {
|
|
|
66
75
|
.description("Update an agent's metadata and configuration")
|
|
67
76
|
.argument("<id>", "Agent ID")
|
|
68
77
|
.option("-n, --name <name>", "New name")
|
|
69
|
-
.option("-m, --model <model>",
|
|
78
|
+
.option("-m, --model <model>", `${MODEL_HELP}; replaces the stored list`, collectModel)
|
|
79
|
+
.option("--models <list>", `${MODELS_HELP}; replaces the stored list`)
|
|
70
80
|
.option("-d, --description <text>", "New description")
|
|
71
81
|
.option("-i, --instructions <text>", "New system instructions")
|
|
72
82
|
.option("--options <json>", "New model options as JSON")
|
|
@@ -79,8 +89,7 @@ export function makeAgentsCommand() {
|
|
|
79
89
|
const body = {};
|
|
80
90
|
if (opts.name)
|
|
81
91
|
body.name = opts.name;
|
|
82
|
-
|
|
83
|
-
body.model = opts.model;
|
|
92
|
+
Object.assign(body, modelFieldsFromOptions(opts, false));
|
|
84
93
|
if (opts.description !== undefined)
|
|
85
94
|
body.description = opts.description;
|
|
86
95
|
if (opts.instructions !== undefined)
|
|
@@ -97,7 +106,7 @@ export function makeAgentsCommand() {
|
|
|
97
106
|
params: { path: { id } },
|
|
98
107
|
body: body,
|
|
99
108
|
});
|
|
100
|
-
formatDetail(data, command);
|
|
109
|
+
formatDetail(maskSecrets(data, []), command);
|
|
101
110
|
});
|
|
102
111
|
});
|
|
103
112
|
cmd
|
|
@@ -147,6 +156,12 @@ export function makeAgentsCommand() {
|
|
|
147
156
|
});
|
|
148
157
|
});
|
|
149
158
|
cmd.addCommand(toolCatalogs);
|
|
159
|
+
cmd.addCommand(makeAgentRunCommand());
|
|
160
|
+
cmd.addCommand(makeAgentDecideCommand());
|
|
161
|
+
cmd.addCommand(makeAgentApplyCommand());
|
|
162
|
+
cmd.addCommand(makeAgentExportCommand());
|
|
163
|
+
cmd.addCommand(makeAgentInitCommand());
|
|
164
|
+
cmd.addCommand(makeAgentSchemaCommand());
|
|
150
165
|
cmd.addCommand(makeAgentVersionsCommand());
|
|
151
166
|
cmd.addCommand(makeAgentLabelsCommand());
|
|
152
167
|
return cmd;
|
package/dist/commands/ai.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { extractResponseText } from "../lib/agent-run.js";
|
|
3
|
+
export { extractResponseText };
|
|
2
4
|
/**
|
|
3
5
|
* Reads a single text input from stdin, or `undefined` when stdin is a TTY
|
|
4
6
|
* (nothing was piped in) or empty. Mirrors the `-` stdin convention used by
|
|
@@ -11,47 +13,13 @@ export declare function readStdinInput(): string | undefined;
|
|
|
11
13
|
* `--agent` / `--model` options, per {@code ModelReferenceResolver}'s
|
|
12
14
|
* grammar: a stored agent is addressed as `agent/{ref}` (an `--agent`
|
|
13
15
|
* value of `ref@label` naturally composes into `agent/ref@label`), a
|
|
14
|
-
* direct gateway call is a plain `provider/model` string.
|
|
16
|
+
* direct gateway call is a plain `provider/model` string. With both, `--model`
|
|
17
|
+
* switches the agent to another entry of its `models` list for this request:
|
|
18
|
+
* `agent/{ref}#{model}` (#591, #626).
|
|
15
19
|
*/
|
|
16
20
|
export declare function resolveModel(opts: {
|
|
17
21
|
agent?: string;
|
|
18
22
|
model?: string;
|
|
19
23
|
}): string;
|
|
20
|
-
interface ResponseContentPart {
|
|
21
|
-
type?: string;
|
|
22
|
-
text?: string;
|
|
23
|
-
}
|
|
24
|
-
interface ResponseOutputItem {
|
|
25
|
-
type?: string;
|
|
26
|
-
role?: string;
|
|
27
|
-
content?: ResponseContentPart[];
|
|
28
|
-
}
|
|
29
|
-
interface ResponseUsage {
|
|
30
|
-
input_tokens?: number;
|
|
31
|
-
output_tokens?: number;
|
|
32
|
-
total_tokens?: number;
|
|
33
|
-
}
|
|
34
|
-
interface ResponseResult {
|
|
35
|
-
id?: string;
|
|
36
|
-
status?: string;
|
|
37
|
-
model?: string;
|
|
38
|
-
output?: ResponseOutputItem[];
|
|
39
|
-
usage?: ResponseUsage;
|
|
40
|
-
conversation?: {
|
|
41
|
-
id?: string;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Extracts the assistant's text from a Responses API result: the
|
|
46
|
-
* `output_text` content parts of every `message` output item, in order.
|
|
47
|
-
*/
|
|
48
|
-
export declare function extractResponseText(result: ResponseResult | undefined): string;
|
|
49
|
-
/**
|
|
50
|
-
* Extracts the text delta from one line of an SSE stream, or `undefined`
|
|
51
|
-
* when the line carries nothing to print (not a `data:` line, `[DONE]`, an
|
|
52
|
-
* event with no textual delta, or unparsable JSON).
|
|
53
|
-
*/
|
|
54
|
-
export declare function extractSseDelta(line: string): string | undefined;
|
|
55
24
|
export declare function makeAiCommand(): Command;
|
|
56
|
-
export {};
|
|
57
25
|
//# sourceMappingURL=ai.d.ts.map
|
package/dist/commands/ai.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { getClient,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
+
import { isJsonOutput } from "../lib/config.js";
|
|
5
|
+
import { CliUsageError } from "../lib/errors.js";
|
|
6
|
+
import { withModelOverride } from "../lib/agent-models.js";
|
|
6
7
|
import { formatList, withSpinner } from "../lib/output.js";
|
|
8
|
+
import { buildRunEnvelope, extractResponseText, printRunText, } from "../lib/agent-run.js";
|
|
7
9
|
import chalk from "chalk";
|
|
10
|
+
export { extractResponseText };
|
|
8
11
|
/**
|
|
9
12
|
* Reads a single text input from stdin, or `undefined` when stdin is a TTY
|
|
10
13
|
* (nothing was piped in) or empty. Mirrors the `-` stdin convention used by
|
|
@@ -27,134 +30,26 @@ export function readStdinInput() {
|
|
|
27
30
|
* `--agent` / `--model` options, per {@code ModelReferenceResolver}'s
|
|
28
31
|
* grammar: a stored agent is addressed as `agent/{ref}` (an `--agent`
|
|
29
32
|
* value of `ref@label` naturally composes into `agent/ref@label`), a
|
|
30
|
-
* direct gateway call is a plain `provider/model` string.
|
|
33
|
+
* direct gateway call is a plain `provider/model` string. With both, `--model`
|
|
34
|
+
* switches the agent to another entry of its `models` list for this request:
|
|
35
|
+
* `agent/{ref}#{model}` (#591, #626).
|
|
31
36
|
*/
|
|
32
37
|
export function resolveModel(opts) {
|
|
33
|
-
if (opts.agent && opts.model) {
|
|
34
|
-
throw new Error("Use either --agent or --model, not both.");
|
|
35
|
-
}
|
|
36
38
|
if (opts.agent)
|
|
37
|
-
return `agent/${opts.agent}`;
|
|
39
|
+
return `agent/${withModelOverride(opts.agent, opts.model)}`;
|
|
38
40
|
if (opts.model)
|
|
39
41
|
return opts.model;
|
|
40
42
|
throw new Error("Either --agent or --model is required.");
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
* Extracts the assistant's text from a Responses API result: the
|
|
44
|
-
* `output_text` content parts of every `message` output item, in order.
|
|
45
|
-
*/
|
|
46
|
-
export function extractResponseText(result) {
|
|
47
|
-
const parts = [];
|
|
48
|
-
for (const item of result?.output ?? []) {
|
|
49
|
-
if (item.type !== "message")
|
|
50
|
-
continue;
|
|
51
|
-
for (const part of item.content ?? []) {
|
|
52
|
-
if (part.type === "output_text" && part.text) {
|
|
53
|
-
parts.push(part.text);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return parts.join("\n");
|
|
58
|
-
}
|
|
59
|
-
function printResponseResult(result, command) {
|
|
44
|
+
function printResponseResult(result, command, agentRef) {
|
|
60
45
|
if (isJsonOutput(command)) {
|
|
61
46
|
console.log(JSON.stringify(result, null, 2));
|
|
62
47
|
return;
|
|
63
48
|
}
|
|
64
|
-
|
|
65
|
-
if (text) {
|
|
66
|
-
console.log(text);
|
|
67
|
-
}
|
|
68
|
-
if (result?.status && result.status !== "completed") {
|
|
69
|
-
console.log(chalk.yellow(`\n[status: ${result.status}]`));
|
|
70
|
-
}
|
|
49
|
+
printRunText(buildRunEnvelope(result, agentRef));
|
|
71
50
|
if (result?.usage) {
|
|
72
51
|
const usage = result.usage;
|
|
73
|
-
console.log(chalk.dim(
|
|
74
|
-
}
|
|
75
|
-
if (result?.conversation?.id) {
|
|
76
|
-
console.log(chalk.dim(`[conversation: ${result.conversation.id}]`));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Extracts the text delta from one line of an SSE stream, or `undefined`
|
|
81
|
-
* when the line carries nothing to print (not a `data:` line, `[DONE]`, an
|
|
82
|
-
* event with no textual delta, or unparsable JSON).
|
|
83
|
-
*/
|
|
84
|
-
export function extractSseDelta(line) {
|
|
85
|
-
const trimmed = line.trim();
|
|
86
|
-
if (!trimmed.startsWith("data:"))
|
|
87
|
-
return undefined;
|
|
88
|
-
const payload = trimmed.slice(5).trim();
|
|
89
|
-
if (!payload || payload === "[DONE]")
|
|
90
|
-
return undefined;
|
|
91
|
-
try {
|
|
92
|
-
const event = JSON.parse(payload);
|
|
93
|
-
const delta = event.delta;
|
|
94
|
-
return typeof delta === "string" ? delta : undefined;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function parseErrorResponse(res) {
|
|
101
|
-
let body = {};
|
|
102
|
-
try {
|
|
103
|
-
body = await res.json();
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// Fall through to the defaults below.
|
|
107
|
-
}
|
|
108
|
-
return new BackboneApiError({
|
|
109
|
-
status: body.status ?? res.status,
|
|
110
|
-
error: body.error ?? res.statusText,
|
|
111
|
-
message: body.message ?? `HTTP ${res.status}: ${res.statusText}`,
|
|
112
|
-
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Streams `POST /v1/responses` over SSE, printing text deltas as they
|
|
117
|
-
* arrive. Bypasses the typed openapi-fetch client (like the multipart raw
|
|
118
|
-
* `fetch` calls in convert.ts/transcribe.ts) because the response body must
|
|
119
|
-
* be read incrementally rather than parsed whole.
|
|
120
|
-
*/
|
|
121
|
-
async function streamResponse(command, body) {
|
|
122
|
-
const config = resolveConfig(command);
|
|
123
|
-
const authHeader = await resolveAuthHeader(config);
|
|
124
|
-
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
125
|
-
const res = await withSpinner("Connecting...", () => fetch(`${baseUrl}/v1/responses`, {
|
|
126
|
-
method: "POST",
|
|
127
|
-
headers: {
|
|
128
|
-
Authorization: authHeader,
|
|
129
|
-
"Content-Type": "application/json",
|
|
130
|
-
Accept: "text/event-stream",
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify(body),
|
|
133
|
-
}));
|
|
134
|
-
if (!res.ok || !res.body) {
|
|
135
|
-
throw await parseErrorResponse(res);
|
|
136
|
-
}
|
|
137
|
-
const reader = res.body.getReader();
|
|
138
|
-
const decoder = new TextDecoder();
|
|
139
|
-
let buffer = "";
|
|
140
|
-
let wroteAny = false;
|
|
141
|
-
for (;;) {
|
|
142
|
-
const { done, value } = await reader.read();
|
|
143
|
-
if (done)
|
|
144
|
-
break;
|
|
145
|
-
buffer += decoder.decode(value, { stream: true });
|
|
146
|
-
const lines = buffer.split("\n");
|
|
147
|
-
buffer = lines.pop() ?? "";
|
|
148
|
-
for (const line of lines) {
|
|
149
|
-
const delta = extractSseDelta(line);
|
|
150
|
-
if (delta) {
|
|
151
|
-
process.stdout.write(delta);
|
|
152
|
-
wroteAny = true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (wroteAny) {
|
|
157
|
-
process.stdout.write("\n");
|
|
52
|
+
console.log(chalk.dim(`[${result.model} | ${usage.input_tokens} in / ${usage.output_tokens} out / ${usage.total_tokens} total tokens]`));
|
|
158
53
|
}
|
|
159
54
|
}
|
|
160
55
|
export function makeAiCommand() {
|
|
@@ -208,12 +103,15 @@ export function makeAiCommand() {
|
|
|
208
103
|
.command("respond")
|
|
209
104
|
.description("Send a request to the OpenAI-compatible Responses endpoint")
|
|
210
105
|
.argument("[input]", "Input text (reads stdin if omitted)")
|
|
211
|
-
.option("--agent <id>", "Invoke a stored agent by id or name
|
|
212
|
-
.option("--model <id>", "Model in provider/model format
|
|
106
|
+
.option("--agent <id>", "Invoke a stored agent by id or name, optionally with @label")
|
|
107
|
+
.option("--model <id>", "Model in provider/model format; with --agent, one of the agent's models to run this request on")
|
|
213
108
|
.option("--conversation <id>", "Conversation ID this response belongs to")
|
|
214
|
-
.option("--stream", "Stream the response
|
|
109
|
+
.option("--stream", "Stream the response (not supported by the server yet, #148)")
|
|
215
110
|
.action(async (input, opts, command) => {
|
|
216
111
|
await runAction(command, async () => {
|
|
112
|
+
if (opts.stream) {
|
|
113
|
+
throw new CliUsageError("Streaming is not supported by the server yet (#148). Run without --stream.");
|
|
114
|
+
}
|
|
217
115
|
const model = resolveModel(opts);
|
|
218
116
|
const inputText = input ?? readStdinInput();
|
|
219
117
|
if (!inputText) {
|
|
@@ -222,24 +120,19 @@ export function makeAiCommand() {
|
|
|
222
120
|
const body = {
|
|
223
121
|
model,
|
|
224
122
|
input: inputText,
|
|
225
|
-
stream:
|
|
123
|
+
stream: false,
|
|
226
124
|
};
|
|
227
125
|
if (opts.conversation)
|
|
228
126
|
body.conversation = opts.conversation;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// untyped convert.ts source-conversion calls.
|
|
239
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
-
client.POST("/v1/responses", { body }));
|
|
241
|
-
printResponseResult(data, command);
|
|
242
|
-
}
|
|
127
|
+
const client = getClient(command);
|
|
128
|
+
const { data } = await withSpinner("Generating response...", () =>
|
|
129
|
+
// The wire `input` field also accepts a bare string (shorthand for
|
|
130
|
+
// a single user message; see ResponseItemInputDeserializer), which
|
|
131
|
+
// the generated type does not model — hence the cast, same as the
|
|
132
|
+
// untyped convert.ts source-conversion calls.
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
+
client.POST("/v1/responses", { body }));
|
|
135
|
+
printResponseResult(data, command, opts.agent);
|
|
243
136
|
});
|
|
244
137
|
});
|
|
245
138
|
cmd
|