@2kw/ai 6.3.0-dev.91 → 6.3.0-dev.96
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/dist/agent-config/schema.d.ts +3 -3
- package/dist/agent-config/schema.js +6 -3
- package/dist/commands/agents.js +18 -5
- package/dist/commands/datasets.js +17 -15
- package/dist/commands/experiments.js +34 -24
- package/dist/commands/prompts.js +9 -8
- package/dist/commands/schemas.js +15 -6
- package/dist/commands/skills.js +2 -1
- package/dist/lib/overlay.d.ts +10 -0
- package/dist/lib/overlay.js +20 -0
- package/package.json +1 -1
|
@@ -83,8 +83,8 @@ export declare const agentSchema: {
|
|
|
83
83
|
readonly properties: {
|
|
84
84
|
readonly name: {
|
|
85
85
|
readonly type: "string";
|
|
86
|
-
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)
|
|
87
|
-
readonly maxLength:
|
|
86
|
+
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$";
|
|
87
|
+
readonly maxLength: 201;
|
|
88
88
|
};
|
|
89
89
|
readonly ref: {
|
|
90
90
|
readonly type: "string";
|
|
@@ -216,7 +216,7 @@ export declare const agentSchema: {
|
|
|
216
216
|
readonly uniqueItems: true;
|
|
217
217
|
readonly items: {
|
|
218
218
|
readonly type: "string";
|
|
219
|
-
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(@[A-Za-z0-9._-]+)?$";
|
|
219
|
+
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?(@[A-Za-z0-9._-]+)?$";
|
|
220
220
|
};
|
|
221
221
|
};
|
|
222
222
|
};
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Kept as a TS constant: package.json `files` publishes only dist JS/d.ts.
|
|
6
6
|
*/
|
|
7
7
|
export const AGENT_SCHEMA_ID = "https://docs.2kw.ai/schemas/agent.v1.json";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// #788: a skill name is a slug, or <plugin>:<skill> for a skill a plugin import created.
|
|
9
|
+
// Mirrors ValidationLimits.SKILL_NAME_PATTERN; each part is at most 100 characters, 201 in all.
|
|
10
|
+
const SKILL_NAME = "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$";
|
|
11
|
+
const SKILL_REF = "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?(@[A-Za-z0-9._-]+)?$";
|
|
12
|
+
const SKILL_NAME_MAX = 201;
|
|
10
13
|
const MODEL_REF = {
|
|
11
14
|
type: "string",
|
|
12
15
|
minLength: 1,
|
|
@@ -74,7 +77,7 @@ export const agentSchema = {
|
|
|
74
77
|
additionalProperties: false,
|
|
75
78
|
required: ["name"],
|
|
76
79
|
properties: {
|
|
77
|
-
name: { type: "string", pattern: SKILL_NAME, maxLength:
|
|
80
|
+
name: { type: "string", pattern: SKILL_NAME, maxLength: SKILL_NAME_MAX },
|
|
78
81
|
ref: { type: "string", pattern: "\\S", description: "Label name or version number; default latest." },
|
|
79
82
|
},
|
|
80
83
|
},
|
package/dist/commands/agents.js
CHANGED
|
@@ -11,6 +11,7 @@ import { makeAgentPolicyCommand } from "./agent-policy.js";
|
|
|
11
11
|
import { makeAgentInitCommand, makeAgentSchemaCommand } from "./agent-init.js";
|
|
12
12
|
import { maskSecrets } from "../agent-config/file.js";
|
|
13
13
|
import { collectModel, MODEL_HELP, MODELS_HELP, modelFieldsFromOptions } from "../lib/agent-models.js";
|
|
14
|
+
import { assertNameNotBlank } from "../lib/overlay.js";
|
|
14
15
|
export function makeAgentsCommand() {
|
|
15
16
|
const cmd = new Command("agents").description("Manage agents");
|
|
16
17
|
const list = new Command("list").description("List agents");
|
|
@@ -101,13 +102,25 @@ export function makeAgentsCommand() {
|
|
|
101
102
|
.option("--hitl-policy <json>", "New human-in-the-loop policy as JSON")
|
|
102
103
|
.action(async (id, opts, command) => {
|
|
103
104
|
await runAction(command, async () => {
|
|
105
|
+
let name = opts.name;
|
|
106
|
+
let description = opts.description;
|
|
107
|
+
assertNameNotBlank(name);
|
|
104
108
|
const client = getClient(command);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
// PUT /v1/agents/{id} keeps the configuration fields a body omits, but name is required and
|
|
110
|
+
// description is written as sent, so an omitted description would be cleared (#1195).
|
|
111
|
+
if (name === undefined || description === undefined) {
|
|
112
|
+
const { data: current } = await client.GET("/v1/agents/{id}", {
|
|
113
|
+
params: { path: { id } },
|
|
114
|
+
});
|
|
115
|
+
if (!current)
|
|
116
|
+
throw new Error(`Agent ${id} could not be read.`);
|
|
117
|
+
name ??= current.name;
|
|
118
|
+
description ??= current.description;
|
|
119
|
+
}
|
|
120
|
+
const body = { name };
|
|
108
121
|
Object.assign(body, modelFieldsFromOptions(opts, false));
|
|
109
|
-
if (
|
|
110
|
-
body.description =
|
|
122
|
+
if (description !== undefined)
|
|
123
|
+
body.description = description;
|
|
111
124
|
if (opts.instructions !== undefined)
|
|
112
125
|
body.instructions = opts.instructions;
|
|
113
126
|
if (opts.options)
|
|
@@ -3,6 +3,7 @@ import { getClient, runAction } from "../lib/client.js";
|
|
|
3
3
|
import { resolveLatestVersionId } from "../lib/datasets.js";
|
|
4
4
|
import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
|
|
5
5
|
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
6
|
+
import { assertNameNotBlank, overlay } from "../lib/overlay.js";
|
|
6
7
|
import { makeDatasetVersionsCommand } from "./dataset-versions.js";
|
|
7
8
|
export function makeDatasetsCommand() {
|
|
8
9
|
const cmd = new Command("datasets").description("Manage datasets");
|
|
@@ -77,24 +78,25 @@ export function makeDatasetsCommand() {
|
|
|
77
78
|
.option("--metadata <json>", "Metadata (JSON)")
|
|
78
79
|
.action(async (id, opts, command) => {
|
|
79
80
|
await runAction(command, async () => {
|
|
81
|
+
assertNameNotBlank(opts.name);
|
|
82
|
+
const changes = {
|
|
83
|
+
name: opts.name,
|
|
84
|
+
description: opts.description,
|
|
85
|
+
type: opts.type,
|
|
86
|
+
inputSchema: opts.inputSchema ? JSON.parse(opts.inputSchema) : undefined,
|
|
87
|
+
expectedOutputSchema: opts.expectedOutputSchema ? JSON.parse(opts.expectedOutputSchema) : undefined,
|
|
88
|
+
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
89
|
+
};
|
|
80
90
|
const client = getClient(command);
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
if (opts.type !== undefined)
|
|
88
|
-
body.type = opts.type;
|
|
89
|
-
if (opts.inputSchema)
|
|
90
|
-
body.inputSchema = JSON.parse(opts.inputSchema);
|
|
91
|
-
if (opts.expectedOutputSchema)
|
|
92
|
-
body.expectedOutputSchema = JSON.parse(opts.expectedOutputSchema);
|
|
93
|
-
if (opts.metadata)
|
|
94
|
-
body.metadata = JSON.parse(opts.metadata);
|
|
91
|
+
// PUT /v1/datasets/{id} replaces the whole dataset, so an omitted field would be stored as null.
|
|
92
|
+
const { data: current } = await client.GET("/v1/datasets/{id}", {
|
|
93
|
+
params: { path: { id } },
|
|
94
|
+
});
|
|
95
|
+
if (!current)
|
|
96
|
+
throw new Error(`Dataset ${id} could not be read.`);
|
|
95
97
|
const { data } = await client.PUT("/v1/datasets/{id}", {
|
|
96
98
|
params: { path: { id } },
|
|
97
|
-
body:
|
|
99
|
+
body: overlay(current, changes),
|
|
98
100
|
});
|
|
99
101
|
formatDetail(data, command);
|
|
100
102
|
});
|
|
@@ -5,6 +5,7 @@ import Table from "cli-table3";
|
|
|
5
5
|
import { getClient, runAction } from "../lib/client.js";
|
|
6
6
|
import { formatPage, formatDetail, formatList, formatSuccess, withSpinner } from "../lib/output.js";
|
|
7
7
|
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
8
|
+
import { assertNameNotBlank, overlay } from "../lib/overlay.js";
|
|
8
9
|
import { isJsonOutput } from "../lib/config.js";
|
|
9
10
|
const TERMINAL_STATUSES = new Set(["COMPLETED", "FAILED"]);
|
|
10
11
|
/** Summarize a single cell of the comparison matrix (a variant's result for one item). */
|
|
@@ -92,21 +93,24 @@ export function makeExperimentsCommand() {
|
|
|
92
93
|
.option("--metadata <json>", "Metadata (JSON)")
|
|
93
94
|
.action(async (id, opts, command) => {
|
|
94
95
|
await runAction(command, async () => {
|
|
96
|
+
assertNameNotBlank(opts.name);
|
|
97
|
+
const changes = {
|
|
98
|
+
name: opts.name,
|
|
99
|
+
description: opts.description,
|
|
100
|
+
type: opts.type,
|
|
101
|
+
datasetVersionId: opts.datasetVersion,
|
|
102
|
+
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
103
|
+
};
|
|
95
104
|
const client = getClient(command);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
body.type = opts.type;
|
|
103
|
-
if (opts.datasetVersion !== undefined)
|
|
104
|
-
body.datasetVersionId = opts.datasetVersion;
|
|
105
|
-
if (opts.metadata)
|
|
106
|
-
body.metadata = JSON.parse(opts.metadata);
|
|
105
|
+
// PUT /v1/experiments/{id} replaces the whole experiment, so an omitted field would be stored as null.
|
|
106
|
+
const { data: current } = await client.GET("/v1/experiments/{id}", {
|
|
107
|
+
params: { path: { id } },
|
|
108
|
+
});
|
|
109
|
+
if (!current)
|
|
110
|
+
throw new Error(`Experiment ${id} could not be read.`);
|
|
107
111
|
const { data } = await client.PUT("/v1/experiments/{id}", {
|
|
108
112
|
params: { path: { id } },
|
|
109
|
-
body:
|
|
113
|
+
body: overlay(current, changes),
|
|
110
114
|
});
|
|
111
115
|
formatDetail(data, command);
|
|
112
116
|
});
|
|
@@ -185,21 +189,27 @@ export function makeExperimentsCommand() {
|
|
|
185
189
|
.option("--sort-order <n>", "Sort order", parseInt)
|
|
186
190
|
.action(async (variantId, opts, command) => {
|
|
187
191
|
await runAction(command, async () => {
|
|
192
|
+
assertNameNotBlank(opts.name);
|
|
193
|
+
const changes = {
|
|
194
|
+
name: opts.name,
|
|
195
|
+
taskType: opts.taskType,
|
|
196
|
+
configuration: opts.config ? JSON.parse(opts.config) : undefined,
|
|
197
|
+
description: opts.description,
|
|
198
|
+
sortOrder: opts.sortOrder,
|
|
199
|
+
};
|
|
188
200
|
const client = getClient(command);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
if (opts.sortOrder !== undefined)
|
|
199
|
-
body.sortOrder = opts.sortOrder;
|
|
201
|
+
// The variant PUT writes name, description and configuration as sent, so an omitted one would
|
|
202
|
+
// be cleared. There is no single-variant GET: read it from the list. The read's version goes
|
|
203
|
+
// back with the body, so a change made since is refused with 409.
|
|
204
|
+
const { data: variants } = await client.GET("/v1/experiments/{id}/variants", {
|
|
205
|
+
params: { path: { id: opts.experiment } },
|
|
206
|
+
});
|
|
207
|
+
const current = variants?.find((v) => v.id === variantId);
|
|
208
|
+
if (!current)
|
|
209
|
+
throw new Error(`Variant ${variantId} is not a variant of experiment ${opts.experiment}.`);
|
|
200
210
|
const { data } = await client.PUT("/v1/experiments/{id}/variants/{variantId}", {
|
|
201
211
|
params: { path: { id: opts.experiment, variantId } },
|
|
202
|
-
body:
|
|
212
|
+
body: overlay(current, changes),
|
|
203
213
|
});
|
|
204
214
|
formatDetail(data, command);
|
|
205
215
|
});
|
package/dist/commands/prompts.js
CHANGED
|
@@ -2,6 +2,7 @@ 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 { assertNameNotBlank, overlay } from "../lib/overlay.js";
|
|
5
6
|
import { makePromptVersionsCommand } from "./prompt-versions.js";
|
|
6
7
|
import { makePromptLabelsCommand } from "./prompt-labels.js";
|
|
7
8
|
export function makePromptsCommand() {
|
|
@@ -59,17 +60,17 @@ export function makePromptsCommand() {
|
|
|
59
60
|
.option("--type <type>", "New type")
|
|
60
61
|
.action(async (id, opts, command) => {
|
|
61
62
|
await runAction(command, async () => {
|
|
63
|
+
assertNameNotBlank(opts.name);
|
|
62
64
|
const client = getClient(command);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
body.type = opts.type;
|
|
65
|
+
// PUT /v1/prompts/{id} replaces the whole prompt, so an omitted field would be stored as null.
|
|
66
|
+
const { data: current } = await client.GET("/v1/prompts/{id}", {
|
|
67
|
+
params: { path: { id } },
|
|
68
|
+
});
|
|
69
|
+
if (!current)
|
|
70
|
+
throw new Error(`Prompt ${id} could not be read.`);
|
|
70
71
|
const { data } = await client.PUT("/v1/prompts/{id}", {
|
|
71
72
|
params: { path: { id } },
|
|
72
|
-
body:
|
|
73
|
+
body: overlay(current, { name: opts.name, description: opts.description, type: opts.type }),
|
|
73
74
|
});
|
|
74
75
|
formatDetail(data, command);
|
|
75
76
|
});
|
package/dist/commands/schemas.js
CHANGED
|
@@ -58,15 +58,24 @@ export function makeSchemasCommand() {
|
|
|
58
58
|
.option("-d, --description <text>", "New description")
|
|
59
59
|
.action(async (id, opts, command) => {
|
|
60
60
|
await runAction(command, async () => {
|
|
61
|
+
let name = opts.name;
|
|
62
|
+
let description = opts.description;
|
|
63
|
+
if (name !== undefined && !name.trim())
|
|
64
|
+
throw new Error("--name must not be empty.");
|
|
61
65
|
const client = getClient(command);
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
// PUT /v1/schemas/{id} replaces the whole schema, so an omitted field would be stored as null.
|
|
67
|
+
if (name === undefined || description === undefined) {
|
|
68
|
+
const { data: current } = await client.GET("/v1/schemas/{id}", {
|
|
69
|
+
params: { path: { id } },
|
|
70
|
+
});
|
|
71
|
+
if (!current)
|
|
72
|
+
throw new Error(`Schema ${id} could not be read.`);
|
|
73
|
+
name ??= current.name;
|
|
74
|
+
description ??= current.description;
|
|
75
|
+
}
|
|
67
76
|
const { data } = await client.PUT("/v1/schemas/{id}", {
|
|
68
77
|
params: { path: { id } },
|
|
69
|
-
body:
|
|
78
|
+
body: { name, description },
|
|
70
79
|
});
|
|
71
80
|
formatDetail(data, command);
|
|
72
81
|
});
|
package/dist/commands/skills.js
CHANGED
|
@@ -186,7 +186,8 @@ export function makeSkillsCommand() {
|
|
|
186
186
|
const { data } = await client.GET("/v1/skills/{id}", { params: { path: { id } } });
|
|
187
187
|
if (!data?.name)
|
|
188
188
|
throw new Error("Skill name missing; specify an output path with -o.");
|
|
189
|
-
|
|
189
|
+
// #788: ":" (a plugin skill) becomes "." before basename, which on Windows reads "a:" as a drive.
|
|
190
|
+
outputPath = `${basename(data.name.replace(/:/g, "."))}-v${versionNumber}.zip`;
|
|
190
191
|
}
|
|
191
192
|
const config = resolveConfig(command);
|
|
192
193
|
const authHeader = await resolveAuthHeader(config);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body for a PUT that replaces the whole row: the stored resource as its GET returned it, with
|
|
3
|
+
* every change the caller gave written over it. A change that is `undefined` was not given and keeps
|
|
4
|
+
* the stored value; the backend would store it as null otherwise (#1195). Server-managed fields in
|
|
5
|
+
* `current` (a dataset's or prompt's latestVersionId) go back as read, which is what keeps them.
|
|
6
|
+
*/
|
|
7
|
+
export declare function overlay<T extends object>(current: T, changes: Record<string, unknown>): T;
|
|
8
|
+
/** Refuses a --name that is present but blank, which a full-replace PUT would otherwise store. */
|
|
9
|
+
export declare function assertNameNotBlank(name: string | undefined): void;
|
|
10
|
+
//# sourceMappingURL=overlay.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body for a PUT that replaces the whole row: the stored resource as its GET returned it, with
|
|
3
|
+
* every change the caller gave written over it. A change that is `undefined` was not given and keeps
|
|
4
|
+
* the stored value; the backend would store it as null otherwise (#1195). Server-managed fields in
|
|
5
|
+
* `current` (a dataset's or prompt's latestVersionId) go back as read, which is what keeps them.
|
|
6
|
+
*/
|
|
7
|
+
export function overlay(current, changes) {
|
|
8
|
+
const body = { ...current };
|
|
9
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
10
|
+
if (value !== undefined)
|
|
11
|
+
body[key] = value;
|
|
12
|
+
}
|
|
13
|
+
return body;
|
|
14
|
+
}
|
|
15
|
+
/** Refuses a --name that is present but blank, which a full-replace PUT would otherwise store. */
|
|
16
|
+
export function assertNameNotBlank(name) {
|
|
17
|
+
if (name !== undefined && !name.trim())
|
|
18
|
+
throw new Error("--name must not be empty.");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=overlay.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "6.3.0-dev.
|
|
3
|
+
"version": "6.3.0-dev.96",
|
|
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",
|