@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,84 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { stripControl } from "./agent-run.js";
|
|
4
|
+
/** Raised when the prompt's input closes (EOF, Ctrl+C, Ctrl+D) before an answer arrives. */
|
|
5
|
+
export class PromptAbortedError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Approval prompt closed");
|
|
8
|
+
this.name = "PromptAbortedError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Prompts go to stderr so stdout stays the agent's answer. A closed input never
|
|
13
|
+
* leaves a question hanging: every pending and later `ask` rejects with
|
|
14
|
+
* {@link PromptAbortedError}.
|
|
15
|
+
*/
|
|
16
|
+
export function readlineAsk(input = process.stdin, output = process.stderr) {
|
|
17
|
+
const rl = createInterface({ input, output, terminal: Boolean(input.isTTY) });
|
|
18
|
+
let closed = false;
|
|
19
|
+
let abortPending;
|
|
20
|
+
rl.once("close", () => {
|
|
21
|
+
closed = true;
|
|
22
|
+
abortPending?.();
|
|
23
|
+
});
|
|
24
|
+
const ask = (question) => new Promise((resolve, reject) => {
|
|
25
|
+
if (closed) {
|
|
26
|
+
reject(new PromptAbortedError());
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let settled = false;
|
|
30
|
+
const abort = () => settle(() => reject(new PromptAbortedError()));
|
|
31
|
+
const settle = (finish) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
if (abortPending === abort)
|
|
36
|
+
abortPending = undefined;
|
|
37
|
+
finish();
|
|
38
|
+
};
|
|
39
|
+
abortPending = abort;
|
|
40
|
+
rl.question(question).then((answer) => settle(() => resolve(answer)), (err) => settle(() => reject(err?.name === "AbortError" ? new PromptAbortedError() : err)));
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
ask,
|
|
44
|
+
close: () => {
|
|
45
|
+
if (!closed)
|
|
46
|
+
rl.close();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function promptApprovals(pending, ask, write = (line) => console.error(line)) {
|
|
51
|
+
const answers = new Map();
|
|
52
|
+
const s = stripControl;
|
|
53
|
+
for (const [index, a] of pending.entries()) {
|
|
54
|
+
const destructive = a.policyClass.toLowerCase() === "destructive";
|
|
55
|
+
write(chalk.yellow(`\nApproval ${index + 1}/${pending.length}: ${s(a.tool)} [${s(a.policyClass)}]`));
|
|
56
|
+
// JSON.stringify escapes C0 controls but leaves DEL and C1 raw.
|
|
57
|
+
write(chalk.dim(s(JSON.stringify(a.arguments, null, 2))));
|
|
58
|
+
if (a.reason)
|
|
59
|
+
write(chalk.dim(`reason: ${s(a.reason)}`));
|
|
60
|
+
const question = destructive ? "[a]pprove / [r]eject? " : "[a]pprove / [r]eject / approve and [R]emember? ";
|
|
61
|
+
const hint = destructive
|
|
62
|
+
? "Answer a (approve), r (reject)"
|
|
63
|
+
: "Answer a (approve), r (reject), or R (approve and remember)";
|
|
64
|
+
for (;;) {
|
|
65
|
+
const reply = (await ask(question)).trim();
|
|
66
|
+
if (reply === "a") {
|
|
67
|
+
answers.set(a.approvalId, { decision: "approve" });
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (reply === "R" && !destructive) {
|
|
71
|
+
answers.set(a.approvalId, { decision: "approve", remember: true });
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (reply === "r") {
|
|
75
|
+
const reason = (await ask("Reason (optional): ")).trim();
|
|
76
|
+
answers.set(a.approvalId, reason ? { decision: "reject", reason } : { decision: "reject" });
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
write(hint);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return answers;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=approval-prompt.js.map
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SessionAuthDeps {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Error-handling middleware: intercepts non-ok responses and throws BackboneApiError.
|
|
26
|
+
* The api-key filter now returns the OpenAI envelope (#658); the surface
|
|
27
|
+
* filter still sends the flat shape, so both are normalized here.
|
|
26
28
|
*/
|
|
27
29
|
export declare const errorMiddleware: Middleware;
|
|
28
30
|
/**
|
package/dist/lib/client.js
CHANGED
|
@@ -9,6 +9,8 @@ import { BackboneApiError, handleError } from "./errors.js";
|
|
|
9
9
|
export const RETRY_MARKER = "x-2kw-auth-retried";
|
|
10
10
|
/**
|
|
11
11
|
* Error-handling middleware: intercepts non-ok responses and throws BackboneApiError.
|
|
12
|
+
* The api-key filter now returns the OpenAI envelope (#658); the surface
|
|
13
|
+
* filter still sends the flat shape, so both are normalized here.
|
|
12
14
|
*/
|
|
13
15
|
export const errorMiddleware = {
|
|
14
16
|
async onResponse({ response }) {
|
|
@@ -21,11 +23,20 @@ export const errorMiddleware = {
|
|
|
21
23
|
catch {
|
|
22
24
|
body = {};
|
|
23
25
|
}
|
|
26
|
+
// Gateway endpoints and the api-key filter answer in the OpenAI shape
|
|
27
|
+
// {error: {message, type, param, code}}; its code is also kept on its own.
|
|
28
|
+
const nested = body && typeof body.error === "object" && body.error !== null ? body.error : undefined;
|
|
24
29
|
throw new BackboneApiError({
|
|
25
30
|
status: body.status ?? response.status,
|
|
26
|
-
error:
|
|
27
|
-
|
|
31
|
+
error: typeof body.error === "string"
|
|
32
|
+
? body.error
|
|
33
|
+
: (nested?.code ?? nested?.type ?? body.title ?? response.statusText),
|
|
34
|
+
message: body.detail ??
|
|
35
|
+
body.message ??
|
|
36
|
+
nested?.message ??
|
|
37
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
28
38
|
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
39
|
+
code: typeof nested?.code === "string" ? nested.code : undefined,
|
|
29
40
|
});
|
|
30
41
|
},
|
|
31
42
|
};
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
export interface ApiErrorBody {
|
|
2
2
|
status: number;
|
|
3
|
-
error
|
|
3
|
+
error?: string | {
|
|
4
|
+
message?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
param?: string | null;
|
|
7
|
+
code?: string | null;
|
|
8
|
+
};
|
|
4
9
|
message: string;
|
|
5
10
|
timestamp: string;
|
|
11
|
+
/** Machine-readable code from an OpenAI-style gateway error, e.g. `approval_hmac_mismatch`. */
|
|
12
|
+
code?: string;
|
|
6
13
|
}
|
|
7
14
|
export declare class BackboneApiError extends Error {
|
|
8
15
|
readonly status: number;
|
|
9
16
|
readonly errorType: string;
|
|
10
17
|
readonly timestamp: string;
|
|
11
|
-
|
|
18
|
+
readonly code?: string;
|
|
19
|
+
constructor(body: Omit<ApiErrorBody, "error"> & {
|
|
20
|
+
error: string;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Invalid usage, invalid config, or a local check that failed before any request.
|
|
25
|
+
* `handleError` maps it to exit code 2 so scripted callers can tell it from an API failure.
|
|
26
|
+
*/
|
|
27
|
+
export declare class CliUsageError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
12
29
|
}
|
|
13
30
|
/**
|
|
14
31
|
* Discriminated cause of a failed credential check. Lets callers give a
|
|
@@ -28,5 +45,7 @@ export type AuthFailureKind = "UNAUTHORIZED" | "FORBIDDEN" | "UNREACHABLE" | "UN
|
|
|
28
45
|
* - Everything else → UNKNOWN.
|
|
29
46
|
*/
|
|
30
47
|
export declare function classifyAuthFailure(err: unknown): AuthFailureKind;
|
|
48
|
+
/** The most specific hint for an API error: gateway code, then known messages, then the status table. */
|
|
49
|
+
export declare function hintFor(err: BackboneApiError): string | undefined;
|
|
31
50
|
export declare function handleError(err: unknown, json: boolean): void;
|
|
32
51
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/lib/errors.js
CHANGED
|
@@ -6,12 +6,24 @@ export class BackboneApiError extends Error {
|
|
|
6
6
|
status;
|
|
7
7
|
errorType;
|
|
8
8
|
timestamp;
|
|
9
|
+
code;
|
|
9
10
|
constructor(body) {
|
|
10
11
|
super(body.message);
|
|
11
12
|
this.name = "BackboneApiError";
|
|
12
13
|
this.status = body.status;
|
|
13
14
|
this.errorType = body.error;
|
|
14
15
|
this.timestamp = body.timestamp;
|
|
16
|
+
this.code = body.code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Invalid usage, invalid config, or a local check that failed before any request.
|
|
21
|
+
* `handleError` maps it to exit code 2 so scripted callers can tell it from an API failure.
|
|
22
|
+
*/
|
|
23
|
+
export class CliUsageError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "CliUsageError";
|
|
15
27
|
}
|
|
16
28
|
}
|
|
17
29
|
// Node/undici surface network faults through the error's `code`. A DNS or
|
|
@@ -77,6 +89,37 @@ const HINTS = {
|
|
|
77
89
|
422: "Validation error. Check the input values.",
|
|
78
90
|
429: "Rate limit exceeded. Wait a moment and try again.",
|
|
79
91
|
};
|
|
92
|
+
const PENDING_APPROVALS_HINT = "The approval was already decided or its response was superseded. See what is still pending: 2kw agents approvals --agent <agent-id> --status pending";
|
|
93
|
+
const CODE_HINTS = {
|
|
94
|
+
approval_hmac_mismatch: PENDING_APPROVALS_HINT,
|
|
95
|
+
unknown_approval_id: PENDING_APPROVALS_HINT,
|
|
96
|
+
incomplete_tool_outputs: "Every pending approval of a paused response must be decided in one call. Use --approve-all or --reject-all, or name every id.",
|
|
97
|
+
conversation_agent_mismatch: "This conversation belongs to another agent. Start a new conversation or run the agent that owns it.",
|
|
98
|
+
};
|
|
99
|
+
/** The most specific hint for an API error: gateway code, then known messages, then the status table. */
|
|
100
|
+
export function hintFor(err) {
|
|
101
|
+
if (err.code === "invalid_approval_decision" && /decided automatically/.test(err.message)) {
|
|
102
|
+
return "This request was already decided by the automatic approver; nothing to decide.";
|
|
103
|
+
}
|
|
104
|
+
if (err.code === "invalid_approval_decision" && /decided by a conversation grant/.test(err.message)) {
|
|
105
|
+
return "This request was already approved by a conversation grant; nothing to decide.";
|
|
106
|
+
}
|
|
107
|
+
if (err.code && CODE_HINTS[err.code])
|
|
108
|
+
return CODE_HINTS[err.code];
|
|
109
|
+
// Anchored on the agent service's wording: prompts and schemas send the same
|
|
110
|
+
// "Label '<x>' not found" prefix, and must keep the generic 404 hint.
|
|
111
|
+
if (err.status === 404 && /^Label 'latest' not found (on agent|for this agent)/.test(err.message)) {
|
|
112
|
+
return "The agent has no published version. Run: 2kw agents apply -f agent.yaml (or 2kw agents versions create).";
|
|
113
|
+
}
|
|
114
|
+
if (err.status === 404 && /^Label '.+' not found (on agent|for this agent)/.test(err.message)) {
|
|
115
|
+
return "That label does not exist on the agent. List labels: 2kw agents labels list --agent <agent-id>";
|
|
116
|
+
}
|
|
117
|
+
// Only a skill-binding resolution failure, not any 422 that mentions a skill.
|
|
118
|
+
if (err.status === 422 && /^Skill '.+' (cannot be resolved|is archived)/.test(err.message)) {
|
|
119
|
+
return "Check the skill exists, is active, and has that label or version: 2kw skills list";
|
|
120
|
+
}
|
|
121
|
+
return HINTS[err.status];
|
|
122
|
+
}
|
|
80
123
|
/**
|
|
81
124
|
* Drop the cached org JWT of the active session context.
|
|
82
125
|
*
|
|
@@ -114,13 +157,14 @@ export function handleError(err, json) {
|
|
|
114
157
|
console.error(JSON.stringify({
|
|
115
158
|
error: err.errorType,
|
|
116
159
|
status: err.status,
|
|
160
|
+
...(err.code ? { code: err.code } : {}),
|
|
117
161
|
message: err.message,
|
|
118
162
|
timestamp: err.timestamp,
|
|
119
163
|
}));
|
|
120
164
|
}
|
|
121
165
|
else {
|
|
122
166
|
console.error(chalk.red(`Error ${err.status}: ${err.message}`));
|
|
123
|
-
const hint =
|
|
167
|
+
const hint = hintFor(err);
|
|
124
168
|
if (hint) {
|
|
125
169
|
console.error(chalk.yellow(`Hint: ${hint}`));
|
|
126
170
|
}
|
|
@@ -151,6 +195,6 @@ export function handleError(err, json) {
|
|
|
151
195
|
else {
|
|
152
196
|
console.error(chalk.red(`Unknown error: ${String(err)}`));
|
|
153
197
|
}
|
|
154
|
-
process.exitCode = 1;
|
|
198
|
+
process.exitCode = err instanceof CliUsageError ? 2 : 1;
|
|
155
199
|
}
|
|
156
200
|
//# sourceMappingURL=errors.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "6.2.0-dev.
|
|
3
|
+
"version": "6.2.0-dev.25",
|
|
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",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"check:generated": "tsx openapi/scripts/check-generated.ts",
|
|
39
39
|
"fix:generated": "tsx openapi/scripts/check-generated.ts --write",
|
|
40
40
|
"check:coverage": "tsx openapi/scripts/check-coverage.ts",
|
|
41
|
+
"sync:agent-schema": "tsx scripts/sync-agent-schema.ts",
|
|
41
42
|
"typecheck": "tsc --noEmit",
|
|
42
43
|
"test": "vitest",
|
|
43
44
|
"test:run": "vitest run",
|
|
@@ -45,13 +46,15 @@
|
|
|
45
46
|
"test:contract": "vitest run tests/contract"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
49
|
+
"ajv": "^8.20.0",
|
|
48
50
|
"chalk": "^5.6.2",
|
|
49
51
|
"cli-table3": "^0.6.5",
|
|
50
52
|
"commander": "^13.1.0",
|
|
51
53
|
"conf": "^13.1.0",
|
|
52
54
|
"open": "^10.2.0",
|
|
53
55
|
"openapi-fetch": "^0.13.5",
|
|
54
|
-
"ora": "^8.2.0"
|
|
56
|
+
"ora": "^8.2.0",
|
|
57
|
+
"yaml": "^2.9.1"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
57
60
|
"@types/node": "^22.20.1",
|