@2kw/ai 7.1.0-dev.1 → 7.1.0-dev.3
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.
|
@@ -11,8 +11,8 @@ export declare function redactSecrets(tools: Record<string, unknown>[] | undefin
|
|
|
11
11
|
envNames: string[];
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
-
* Tool entries the backend stores but never runs (planner
|
|
15
|
-
* e.g.
|
|
14
|
+
* Tool entries the backend stores but never runs as authored (planner- or runtime-injected,
|
|
15
|
+
* e.g. `backbone.tool_search`). agent.yaml cannot author them, so export drops them.
|
|
16
16
|
*/
|
|
17
17
|
export declare function dropInertTools(tools: Record<string, unknown>[] | undefined): {
|
|
18
18
|
tools: Record<string, unknown>[] | undefined;
|
|
@@ -43,8 +43,8 @@ export function redactSecrets(tools) {
|
|
|
43
43
|
return { tools: redacted, envNames: [...owners.keys()] };
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Tool entries the backend stores but never runs (planner
|
|
47
|
-
* e.g.
|
|
46
|
+
* Tool entries the backend stores but never runs as authored (planner- or runtime-injected,
|
|
47
|
+
* e.g. `backbone.tool_search`). agent.yaml cannot author them, so export drops them.
|
|
48
48
|
*/
|
|
49
49
|
export function dropInertTools(tools) {
|
|
50
50
|
if (!tools)
|
|
@@ -138,6 +138,77 @@ export declare const agentSchema: {
|
|
|
138
138
|
};
|
|
139
139
|
};
|
|
140
140
|
};
|
|
141
|
+
readonly webSearchTool: {
|
|
142
|
+
readonly type: "object";
|
|
143
|
+
readonly additionalProperties: false;
|
|
144
|
+
readonly required: readonly ["type"];
|
|
145
|
+
readonly properties: {
|
|
146
|
+
readonly type: {
|
|
147
|
+
readonly const: "web_search";
|
|
148
|
+
};
|
|
149
|
+
readonly config: {
|
|
150
|
+
readonly type: "object";
|
|
151
|
+
readonly properties: {
|
|
152
|
+
readonly source: {
|
|
153
|
+
readonly enum: readonly ["builtin", "external", "native"];
|
|
154
|
+
};
|
|
155
|
+
readonly searchProviderId: {
|
|
156
|
+
readonly type: readonly ["string", "null"];
|
|
157
|
+
};
|
|
158
|
+
readonly fallback: {
|
|
159
|
+
readonly enum: readonly ["builtin", "external"];
|
|
160
|
+
};
|
|
161
|
+
readonly allowedDomains: {
|
|
162
|
+
readonly type: "array";
|
|
163
|
+
readonly items: {
|
|
164
|
+
readonly type: "string";
|
|
165
|
+
readonly pattern: "^(?=.{1,253}$)([\\p{L}\\p{N}](?:[\\p{L}\\p{N}-]{0,61}[\\p{L}\\p{N}])?\\.)+\\p{L}(?:[\\p{L}\\p{N}-]{0,61}[\\p{L}\\p{N}])?$";
|
|
166
|
+
};
|
|
167
|
+
readonly maxItems: 10;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
readonly if: {
|
|
171
|
+
readonly anyOf: readonly [{
|
|
172
|
+
readonly required: readonly ["source"];
|
|
173
|
+
readonly properties: {
|
|
174
|
+
readonly source: {
|
|
175
|
+
readonly const: "external";
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}, {
|
|
179
|
+
readonly required: readonly ["source", "fallback"];
|
|
180
|
+
readonly properties: {
|
|
181
|
+
readonly source: {
|
|
182
|
+
readonly const: "native";
|
|
183
|
+
};
|
|
184
|
+
readonly fallback: {
|
|
185
|
+
readonly const: "external";
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
}];
|
|
189
|
+
};
|
|
190
|
+
readonly then: {
|
|
191
|
+
readonly required: readonly ["searchProviderId"];
|
|
192
|
+
readonly properties: {
|
|
193
|
+
readonly searchProviderId: {
|
|
194
|
+
readonly type: "string";
|
|
195
|
+
readonly pattern: "\\S";
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
readonly else: {
|
|
200
|
+
readonly properties: {
|
|
201
|
+
readonly searchProviderId: {
|
|
202
|
+
readonly type: "null";
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
readonly requiresConfirmation: {
|
|
208
|
+
readonly type: "boolean";
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
};
|
|
141
212
|
readonly extractionTool: {
|
|
142
213
|
readonly type: "object";
|
|
143
214
|
readonly additionalProperties: false;
|
|
@@ -306,6 +377,18 @@ export declare const agentSchema: {
|
|
|
306
377
|
readonly then: {
|
|
307
378
|
readonly $ref: "#/$defs/fileSearchTool";
|
|
308
379
|
};
|
|
380
|
+
}, {
|
|
381
|
+
readonly if: {
|
|
382
|
+
required: string[];
|
|
383
|
+
properties: {
|
|
384
|
+
type: {
|
|
385
|
+
const: string;
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
readonly then: {
|
|
390
|
+
readonly $ref: "#/$defs/webSearchTool";
|
|
391
|
+
};
|
|
309
392
|
}, {
|
|
310
393
|
readonly if: {
|
|
311
394
|
required: string[];
|
|
@@ -16,6 +16,9 @@ const MODEL_REF = {
|
|
|
16
16
|
maxLength: 255,
|
|
17
17
|
pattern: "^(?:[^/#]+|[A-Za-z0-9-]+/[^#]+)$",
|
|
18
18
|
};
|
|
19
|
+
// A plain hostname (no scheme, path, port or wildcard): dot-separated labels of letters, digits and
|
|
20
|
+
// inner hyphens, Unicode or ASCII. Mirrors WebToolEntryValidator.HOST; needs a Unicode-aware regex engine.
|
|
21
|
+
const HOSTNAME = "^(?=.{1,253}$)([\\p{L}\\p{N}](?:[\\p{L}\\p{N}-]{0,61}[\\p{L}\\p{N}])?\\.)+\\p{L}(?:[\\p{L}\\p{N}-]{0,61}[\\p{L}\\p{N}])?$";
|
|
19
22
|
const oneOfLowercase = (...values) => ({ type: "string", enum: values });
|
|
20
23
|
/** Types the backend injects or cannot execute; authoring them has no effect or breaks the run. */
|
|
21
24
|
export const NON_AUTHORABLE_TOOL_TYPES = {
|
|
@@ -27,10 +30,10 @@ export const NON_AUTHORABLE_TOOL_TYPES = {
|
|
|
27
30
|
"backbone.deliver": "it is added at run time while sandbox outputs are held",
|
|
28
31
|
// #1244: injected wherever backbone.bash is advertised; an authored copy is dropped.
|
|
29
32
|
"backbone.view_image": "it is added at run time wherever the sandboxed shell is",
|
|
30
|
-
web_search: "it has no executor and is skipped",
|
|
31
33
|
};
|
|
32
34
|
const BUILTIN_TYPES = [
|
|
33
35
|
"file_search",
|
|
36
|
+
"web_search",
|
|
34
37
|
"backbone.extraction",
|
|
35
38
|
"backbone.document_convert",
|
|
36
39
|
"backbone.todo",
|
|
@@ -118,6 +121,35 @@ export const agentSchema = {
|
|
|
118
121
|
},
|
|
119
122
|
},
|
|
120
123
|
},
|
|
124
|
+
// A missing config, `config: {}` or a missing source runs the built-in search. Mirrors
|
|
125
|
+
// WebToolEntryValidator; `config` stays open so later sources' fields do not break older CLIs.
|
|
126
|
+
webSearchTool: {
|
|
127
|
+
type: "object",
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
required: ["type"],
|
|
130
|
+
properties: {
|
|
131
|
+
type: { const: "web_search" },
|
|
132
|
+
config: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
source: { enum: ["builtin", "external", "native"] },
|
|
136
|
+
searchProviderId: { type: ["string", "null"] },
|
|
137
|
+
fallback: { enum: ["builtin", "external"] },
|
|
138
|
+
allowedDomains: { type: "array", items: { type: "string", pattern: HOSTNAME }, maxItems: 10 },
|
|
139
|
+
},
|
|
140
|
+
// A provider id is required exactly when an external provider serves the search.
|
|
141
|
+
if: {
|
|
142
|
+
anyOf: [
|
|
143
|
+
{ required: ["source"], properties: { source: { const: "external" } } },
|
|
144
|
+
{ required: ["source", "fallback"], properties: { source: { const: "native" }, fallback: { const: "external" } } },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
then: { required: ["searchProviderId"], properties: { searchProviderId: { type: "string", pattern: "\\S" } } },
|
|
148
|
+
else: { properties: { searchProviderId: { type: "null" } } },
|
|
149
|
+
},
|
|
150
|
+
requiresConfirmation: { type: "boolean" },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
121
153
|
extractionTool: {
|
|
122
154
|
type: "object",
|
|
123
155
|
additionalProperties: false,
|
|
@@ -204,6 +236,7 @@ export const agentSchema = {
|
|
|
204
236
|
type: "object",
|
|
205
237
|
allOf: [
|
|
206
238
|
{ if: typeIs("file_search"), then: { $ref: "#/$defs/fileSearchTool" } },
|
|
239
|
+
{ if: typeIs("web_search"), then: { $ref: "#/$defs/webSearchTool" } },
|
|
207
240
|
{ if: typeIs("backbone.extraction"), then: { $ref: "#/$defs/extractionTool" } },
|
|
208
241
|
{ if: typeIs("backbone.document_convert"), then: { $ref: "#/$defs/documentConvertTool" } },
|
|
209
242
|
{ if: typeIs("backbone.todo"), then: { $ref: "#/$defs/todoTool" } },
|
|
@@ -6,6 +6,8 @@ const validate = ajv.compile(agentSchema);
|
|
|
6
6
|
const NULLABLE_KEYS = ["description", "models", "instructions", "tools", "hitlPolicy", "options", "skills"];
|
|
7
7
|
/** Tool-rule values the backend matches case-insensitively; the actions (approve, auto, deny) are exact. */
|
|
8
8
|
const TOOL_RULE_CLASSES = new Set(["read", "write", "destructive"]);
|
|
9
|
+
/** web_search config keys whose explicit `null` the backend reads as absent. */
|
|
10
|
+
const WEB_SEARCH_NULLABLE_KEYS = ["source", "fallback", "searchProviderId", "allowedDomains"];
|
|
9
11
|
const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10
12
|
/**
|
|
11
13
|
* A deep copy of `doc` in the form the backend actually checks: explicit nulls it treats as absent
|
|
@@ -31,6 +33,8 @@ export function normalizeForValidation(doc) {
|
|
|
31
33
|
for (const tool of copy.tools) {
|
|
32
34
|
if (isPlainObject(tool) && tool.annotations === null)
|
|
33
35
|
delete tool.annotations;
|
|
36
|
+
if (isPlainObject(tool) && tool.type === "web_search")
|
|
37
|
+
normalizeWebSearch(tool);
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
const policy = copy.hitlPolicy;
|
|
@@ -50,6 +54,21 @@ export function normalizeForValidation(doc) {
|
|
|
50
54
|
}
|
|
51
55
|
return copy;
|
|
52
56
|
}
|
|
57
|
+
/** WebToolEntryValidator reads a null config and null config fields as absent, and trims each domain. */
|
|
58
|
+
function normalizeWebSearch(tool) {
|
|
59
|
+
if (tool.config === null)
|
|
60
|
+
delete tool.config;
|
|
61
|
+
const config = tool.config;
|
|
62
|
+
if (!isPlainObject(config))
|
|
63
|
+
return;
|
|
64
|
+
for (const key of WEB_SEARCH_NULLABLE_KEYS) {
|
|
65
|
+
if (config[key] === null)
|
|
66
|
+
delete config[key];
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(config.allowedDomains)) {
|
|
69
|
+
config.allowedDomains = config.allowedDomains.map((d) => (typeof d === "string" ? d.trim() : d));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
53
72
|
function describe(e) {
|
|
54
73
|
// if/then wrappers restate the real error one level up; drop them. `not` only marks
|
|
55
74
|
// non-authorable tool types, which validateAgentConfig reports with the reason.
|
|
@@ -75,6 +94,13 @@ function describe(e) {
|
|
|
75
94
|
if (e.keyword === "const") {
|
|
76
95
|
return { pointer, message: `must be ${JSON.stringify(params.allowedValue)}` };
|
|
77
96
|
}
|
|
97
|
+
// The `else` branch of webSearchTool's provider rule: an id where no external provider serves the search.
|
|
98
|
+
if (e.keyword === "type" && params.type === "null" && pointer.endsWith("/config/searchProviderId")) {
|
|
99
|
+
return {
|
|
100
|
+
pointer,
|
|
101
|
+
message: "only allowed when an external provider serves the search (source external, or native with fallback external)",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
78
104
|
return { pointer, message: e.message ?? e.keyword };
|
|
79
105
|
}
|
|
80
106
|
export function validateAgentConfig(raw) {
|
|
@@ -98,11 +124,18 @@ export function validateAgentConfig(raw) {
|
|
|
98
124
|
}
|
|
99
125
|
}
|
|
100
126
|
if (Array.isArray(record.tools)) {
|
|
127
|
+
let webSearchSeen = false;
|
|
101
128
|
record.tools.forEach((tool, i) => {
|
|
102
129
|
const type = tool?.type;
|
|
103
130
|
if (typeof type === "string" && Object.hasOwn(NON_AUTHORABLE_TOOL_TYPES, type)) {
|
|
104
131
|
issues.push({ pointer: `/tools/${i}/type`, message: `type "${type}" cannot be authored: ${NON_AUTHORABLE_TOOL_TYPES[type]}` });
|
|
105
132
|
}
|
|
133
|
+
// The backend refuses a second envelope (WebToolEntryValidator).
|
|
134
|
+
if (type === "web_search") {
|
|
135
|
+
if (webSearchSeen)
|
|
136
|
+
issues.push({ pointer: `/tools/${i}/type`, message: "an agent may carry only one web_search tool" });
|
|
137
|
+
webSearchSeen = true;
|
|
138
|
+
}
|
|
106
139
|
});
|
|
107
140
|
}
|
|
108
141
|
if (Array.isArray(record.skills)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.3",
|
|
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",
|