@2kw/ai 7.3.0-dev.9 → 7.3.0

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.
@@ -5,7 +5,10 @@
5
5
  * Kept as a TS constant: package.json `files` publishes only dist JS/d.ts.
6
6
  */
7
7
  export declare const AGENT_SCHEMA_ID = "https://docs.2kw.ai/schemas/agent.v1.json";
8
- /** Types the backend injects or cannot execute; authoring them has no effect or breaks the run. */
8
+ /**
9
+ * Types the backend injects or cannot execute; authoring them has no effect or breaks the run.
10
+ * The backend refuses the same map on save (NonAuthorableTools, #1263); its test fails if they differ.
11
+ */
9
12
  export declare const NON_AUTHORABLE_TOOL_TYPES: Record<string, string>;
10
13
  export declare const agentSchema: {
11
14
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
@@ -209,6 +212,35 @@ export declare const agentSchema: {
209
212
  };
210
213
  };
211
214
  };
215
+ readonly webFetchTool: {
216
+ readonly type: "object";
217
+ readonly additionalProperties: false;
218
+ readonly required: readonly ["type"];
219
+ readonly properties: {
220
+ readonly type: {
221
+ readonly const: "web_fetch";
222
+ };
223
+ readonly config: {
224
+ readonly type: "object";
225
+ readonly properties: {
226
+ readonly source: {
227
+ readonly enum: readonly ["proxy", "native"];
228
+ };
229
+ readonly allowedDomains: {
230
+ readonly type: "array";
231
+ readonly items: {
232
+ readonly type: "string";
233
+ 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}])?$";
234
+ };
235
+ readonly maxItems: 10;
236
+ };
237
+ };
238
+ };
239
+ readonly requiresConfirmation: {
240
+ readonly type: "boolean";
241
+ };
242
+ };
243
+ };
212
244
  readonly extractionTool: {
213
245
  readonly type: "object";
214
246
  readonly additionalProperties: false;
@@ -389,6 +421,18 @@ export declare const agentSchema: {
389
421
  readonly then: {
390
422
  readonly $ref: "#/$defs/webSearchTool";
391
423
  };
424
+ }, {
425
+ readonly if: {
426
+ required: string[];
427
+ properties: {
428
+ type: {
429
+ const: string;
430
+ };
431
+ };
432
+ };
433
+ readonly then: {
434
+ readonly $ref: "#/$defs/webFetchTool";
435
+ };
392
436
  }, {
393
437
  readonly if: {
394
438
  required: string[];
@@ -20,9 +20,13 @@ const MODEL_REF = {
20
20
  // inner hyphens, Unicode or ASCII. Mirrors WebToolEntryValidator.HOST; needs a Unicode-aware regex engine.
21
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}])?$";
22
22
  const oneOfLowercase = (...values) => ({ type: "string", enum: values });
23
- /** Types the backend injects or cannot execute; authoring them has no effect or breaks the run. */
23
+ /**
24
+ * Types the backend injects or cannot execute; authoring them has no effect or breaks the run.
25
+ * The backend refuses the same map on save (NonAuthorableTools, #1263); its test fails if they differ.
26
+ */
24
27
  export const NON_AUTHORABLE_TOOL_TYPES = {
25
- "backbone.tool_search": "the planner adds it automatically",
28
+ "backbone.tool_search": "it is added automatically when tool discovery is on",
29
+ "backbone.tool_call": "it is added automatically when tool discovery is on",
26
30
  "backbone.skill_read": "it is added at run time when skills are pinned",
27
31
  // #944: SkillsetPinner injects it for eligible callers and strips any stored copy (#779).
28
32
  "backbone.skills_apply": "it is added at run time for callers allowed to author skills",
@@ -34,6 +38,7 @@ export const NON_AUTHORABLE_TOOL_TYPES = {
34
38
  const BUILTIN_TYPES = [
35
39
  "file_search",
36
40
  "web_search",
41
+ "web_fetch",
37
42
  "backbone.extraction",
38
43
  "backbone.document_convert",
39
44
  "backbone.todo",
@@ -150,6 +155,24 @@ export const agentSchema = {
150
155
  requiresConfirmation: { type: "boolean" },
151
156
  },
152
157
  },
158
+ // Backbone's own fetcher reads a page the user or an earlier search named. A missing config, `config: {}`
159
+ // or a missing source is the proxy; `native` is accepted and answers provider_unavailable until it ships.
160
+ webFetchTool: {
161
+ type: "object",
162
+ additionalProperties: false,
163
+ required: ["type"],
164
+ properties: {
165
+ type: { const: "web_fetch" },
166
+ config: {
167
+ type: "object",
168
+ properties: {
169
+ source: { enum: ["proxy", "native"] },
170
+ allowedDomains: { type: "array", items: { type: "string", pattern: HOSTNAME }, maxItems: 10 },
171
+ },
172
+ },
173
+ requiresConfirmation: { type: "boolean" },
174
+ },
175
+ },
153
176
  extractionTool: {
154
177
  type: "object",
155
178
  additionalProperties: false,
@@ -237,6 +260,7 @@ export const agentSchema = {
237
260
  allOf: [
238
261
  { if: typeIs("file_search"), then: { $ref: "#/$defs/fileSearchTool" } },
239
262
  { if: typeIs("web_search"), then: { $ref: "#/$defs/webSearchTool" } },
263
+ { if: typeIs("web_fetch"), then: { $ref: "#/$defs/webFetchTool" } },
240
264
  { if: typeIs("backbone.extraction"), then: { $ref: "#/$defs/extractionTool" } },
241
265
  { if: typeIs("backbone.document_convert"), then: { $ref: "#/$defs/documentConvertTool" } },
242
266
  { if: typeIs("backbone.todo"), then: { $ref: "#/$defs/todoTool" } },
@@ -29,6 +29,9 @@ tools: []
29
29
  # - type: web_search
30
30
  # config:
31
31
  # allowedDomains: [example.com]
32
+ # - type: web_fetch
33
+ # config:
34
+ # allowedDomains: [example.com]
32
35
  # - type: backbone.extraction
33
36
  # config:
34
37
  # schemaIds: [schema_id]
@@ -8,6 +8,8 @@ const NULLABLE_KEYS = ["description", "models", "instructions", "tools", "hitlPo
8
8
  const TOOL_RULE_CLASSES = new Set(["read", "write", "destructive"]);
9
9
  /** web_search config keys whose explicit `null` the backend reads as absent. */
10
10
  const WEB_SEARCH_NULLABLE_KEYS = ["source", "fallback", "searchProviderId", "allowedDomains"];
11
+ /** web_fetch config keys whose explicit `null` the backend reads as absent. */
12
+ const WEB_FETCH_NULLABLE_KEYS = ["source", "allowedDomains"];
11
13
  const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
12
14
  /**
13
15
  * A deep copy of `doc` in the form the backend actually checks: explicit nulls it treats as absent
@@ -34,7 +36,9 @@ export function normalizeForValidation(doc) {
34
36
  if (isPlainObject(tool) && tool.annotations === null)
35
37
  delete tool.annotations;
36
38
  if (isPlainObject(tool) && tool.type === "web_search")
37
- normalizeWebSearch(tool);
39
+ normalizeWebTool(tool, WEB_SEARCH_NULLABLE_KEYS);
40
+ if (isPlainObject(tool) && tool.type === "web_fetch")
41
+ normalizeWebTool(tool, WEB_FETCH_NULLABLE_KEYS);
38
42
  }
39
43
  }
40
44
  const policy = copy.hitlPolicy;
@@ -55,13 +59,13 @@ export function normalizeForValidation(doc) {
55
59
  return copy;
56
60
  }
57
61
  /** WebToolEntryValidator reads a null config and null config fields as absent, and trims each domain. */
58
- function normalizeWebSearch(tool) {
62
+ function normalizeWebTool(tool, nullableKeys) {
59
63
  if (tool.config === null)
60
64
  delete tool.config;
61
65
  const config = tool.config;
62
66
  if (!isPlainObject(config))
63
67
  return;
64
- for (const key of WEB_SEARCH_NULLABLE_KEYS) {
68
+ for (const key of nullableKeys) {
65
69
  if (config[key] === null)
66
70
  delete config[key];
67
71
  }
@@ -125,6 +129,7 @@ export function validateAgentConfig(raw) {
125
129
  }
126
130
  if (Array.isArray(record.tools)) {
127
131
  let webSearchSeen = false;
132
+ let webFetchSeen = false;
128
133
  record.tools.forEach((tool, i) => {
129
134
  const type = tool?.type;
130
135
  if (typeof type === "string" && Object.hasOwn(NON_AUTHORABLE_TOOL_TYPES, type)) {
@@ -136,6 +141,11 @@ export function validateAgentConfig(raw) {
136
141
  issues.push({ pointer: `/tools/${i}/type`, message: "an agent may carry only one web_search tool" });
137
142
  webSearchSeen = true;
138
143
  }
144
+ if (type === "web_fetch") {
145
+ if (webFetchSeen)
146
+ issues.push({ pointer: `/tools/${i}/type`, message: "an agent may carry only one web_fetch tool" });
147
+ webFetchSeen = true;
148
+ }
139
149
  });
140
150
  }
141
151
  if (Array.isArray(record.skills)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "7.3.0-dev.9",
3
+ "version": "7.3.0",
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",