@2kw/ai 8.0.0-dev.5 → 8.0.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.
@@ -301,6 +301,32 @@ export declare const agentSchema: {
301
301
  };
302
302
  };
303
303
  };
304
+ readonly showTableTool: {
305
+ readonly type: "object";
306
+ readonly additionalProperties: false;
307
+ readonly required: readonly ["type"];
308
+ readonly properties: {
309
+ readonly type: {
310
+ readonly const: "backbone.show_table";
311
+ };
312
+ readonly requiresConfirmation: {
313
+ readonly type: "boolean";
314
+ };
315
+ };
316
+ };
317
+ readonly showChartTool: {
318
+ readonly type: "object";
319
+ readonly additionalProperties: false;
320
+ readonly required: readonly ["type"];
321
+ readonly properties: {
322
+ readonly type: {
323
+ readonly const: "backbone.show_chart";
324
+ };
325
+ readonly requiresConfirmation: {
326
+ readonly type: "boolean";
327
+ };
328
+ };
329
+ };
304
330
  readonly skillTool: {
305
331
  readonly type: "object";
306
332
  readonly additionalProperties: false;
@@ -493,6 +519,30 @@ export declare const agentSchema: {
493
519
  readonly then: {
494
520
  readonly $ref: "#/$defs/bashTool";
495
521
  };
522
+ }, {
523
+ readonly if: {
524
+ required: string[];
525
+ properties: {
526
+ type: {
527
+ const: string;
528
+ };
529
+ };
530
+ };
531
+ readonly then: {
532
+ readonly $ref: "#/$defs/showTableTool";
533
+ };
534
+ }, {
535
+ readonly if: {
536
+ required: string[];
537
+ properties: {
538
+ type: {
539
+ const: string;
540
+ };
541
+ };
542
+ };
543
+ readonly then: {
544
+ readonly $ref: "#/$defs/showChartTool";
545
+ };
496
546
  }, {
497
547
  readonly if: {
498
548
  required: string[];
@@ -44,6 +44,8 @@ const BUILTIN_TYPES = [
44
44
  "backbone.todo",
45
45
  "backbone.skill",
46
46
  "backbone.bash",
47
+ "backbone.show_table",
48
+ "backbone.show_chart",
47
49
  "function",
48
50
  ];
49
51
  const typeIs = (t) => ({ required: ["type"], properties: { type: { const: t } } });
@@ -207,6 +209,19 @@ export const agentSchema = {
207
209
  required: ["type"],
208
210
  properties: { type: { const: "backbone.todo" }, requiresConfirmation: { type: "boolean" } },
209
211
  },
212
+ // #1419: no options; requiresConfirmation as todoTool, since an author may tighten a read tool.
213
+ showTableTool: {
214
+ type: "object",
215
+ additionalProperties: false,
216
+ required: ["type"],
217
+ properties: { type: { const: "backbone.show_table" }, requiresConfirmation: { type: "boolean" } },
218
+ },
219
+ showChartTool: {
220
+ type: "object",
221
+ additionalProperties: false,
222
+ required: ["type"],
223
+ properties: { type: { const: "backbone.show_chart" }, requiresConfirmation: { type: "boolean" } },
224
+ },
210
225
  skillTool: {
211
226
  type: "object",
212
227
  additionalProperties: false,
@@ -266,6 +281,8 @@ export const agentSchema = {
266
281
  { if: typeIs("backbone.todo"), then: { $ref: "#/$defs/todoTool" } },
267
282
  { if: typeIs("backbone.skill"), then: { $ref: "#/$defs/skillTool" } },
268
283
  { if: typeIs("backbone.bash"), then: { $ref: "#/$defs/bashTool" } },
284
+ { if: typeIs("backbone.show_table"), then: { $ref: "#/$defs/showTableTool" } },
285
+ { if: typeIs("backbone.show_chart"), then: { $ref: "#/$defs/showChartTool" } },
269
286
  { if: typeIs("function"), then: { $ref: "#/$defs/functionTool" } },
270
287
  {
271
288
  // The validator reports these with the reason from NON_AUTHORABLE_TOOL_TYPES; this keeps the published schema honest.
@@ -41,6 +41,8 @@ tools: []
41
41
  # skills: [skill-name@latest]
42
42
  # - type: backbone.bash
43
43
  # approval: sandbox
44
+ # - type: backbone.show_table
45
+ # - type: backbone.show_chart
44
46
  # - type: function
45
47
  # name: lookup_order
46
48
  # description: Look up an order by number
@@ -53,6 +53,18 @@ export interface PendingInput {
53
53
  mode: InputMode;
54
54
  answerUrl: string;
55
55
  }
56
+ /**
57
+ * A table or chart the agent showed the user (#1419): a `backbone:ui` item, its validated `props` as the
58
+ * server sent them. A client renders from `props`, never from the call's arguments.
59
+ */
60
+ export interface ShownComponent {
61
+ id: string;
62
+ callId: string;
63
+ /** `table` or `chart`; another value is a component this build does not know. */
64
+ component: string;
65
+ schemaVersion: number;
66
+ props: unknown;
67
+ }
56
68
  /** The end user's conversation mode (epic &59); the server matches these three values exactly. */
57
69
  export type ConversationMode = "plan" | "ask" | "auto";
58
70
  export declare const CONVERSATION_MODES: readonly ConversationMode[];
@@ -87,6 +99,7 @@ export interface RunEnvelope {
87
99
  pendingToolCalls: PendingToolCall[];
88
100
  pendingConnections: PendingConnection[];
89
101
  pendingInputs: PendingInput[];
102
+ components: ShownComponent[];
90
103
  incompleteReason: string | null;
91
104
  usage: {
92
105
  inputTokens: number;
@@ -1,4 +1,5 @@
1
1
  import chalk from "chalk";
2
+ import Table from "cli-table3";
2
3
  import { DEFAULT_CHAT_URL } from "./config.js";
3
4
  import { answerUrlFor, connectCallIds, connectionsPhrase, inputLabelsPhrase, pendingConnectionsOf, pendingInputsOf, } from "./connect-pause.js";
4
5
  import { CliUsageError } from "./errors.js";
@@ -186,6 +187,8 @@ const DECODED_ITEM_TYPES = new Set([
186
187
  "backbone:connector_auth_request",
187
188
  "backbone:input_request",
188
189
  "backbone:tool_image",
190
+ // A table or chart a finished ShowTable or ShowChart call showed (#1419): it never holds a run either.
191
+ "backbone:ui",
189
192
  "mcp_approval_request",
190
193
  "mcp_call",
191
194
  "mcp_list_tools",
@@ -337,6 +340,15 @@ export function buildRunEnvelope(result, agentRef, chatUrl = DEFAULT_CHAT_URL) {
337
340
  pendingToolCalls,
338
341
  pendingConnections,
339
342
  pendingInputs,
343
+ components: output
344
+ .filter((i) => i.type === "backbone:ui")
345
+ .map((i) => ({
346
+ id: String(i.id),
347
+ callId: String(i.call_id),
348
+ component: String(i.component),
349
+ schemaVersion: Number(i.schema_version),
350
+ props: i.props ?? null,
351
+ })),
340
352
  incompleteReason: result?.incomplete_details?.reason ?? null,
341
353
  usage: result?.usage
342
354
  ? { inputTokens: result.usage.input_tokens ?? 0, outputTokens: result.usage.output_tokens ?? 0 }
@@ -351,11 +363,64 @@ const INPUT_MODE_WORDS = {
351
363
  mixed: "form and link",
352
364
  unknown: "question",
353
365
  };
366
+ /** The `schema_version` of `backbone:ui` this build prints (#1419 D8). */
367
+ const UI_SCHEMA_VERSION = 1;
368
+ /** A cell as plain text: empty for null, a percent fraction as `25%`, a currency amount with its code. */
369
+ function cellText(value, column = {}) {
370
+ if (value === null || value === undefined)
371
+ return "";
372
+ if (typeof value === "number" && column.format === "percent")
373
+ return `${Number((value * 100).toPrecision(12))}%`;
374
+ if (typeof value === "number" && column.format === "currency")
375
+ return `${value} ${String(column.currency ?? "")}`.trimEnd();
376
+ return stripControl(String(value));
377
+ }
378
+ function plainTable(head, colAligns) {
379
+ return new Table({
380
+ head: head.map((h) => stripControl(h)),
381
+ colAligns,
382
+ style: { head: [], border: [] },
383
+ // No rule between rows: a table may have 200.
384
+ chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" },
385
+ });
386
+ }
387
+ /**
388
+ * A shown table as aligned columns under its title, a chart as its data table: labels down, one column per
389
+ * series. Null for an unknown component, a newer schema version or props of another shape.
390
+ */
391
+ function componentTable(c) {
392
+ const p = (c.props ?? {});
393
+ if (c.schemaVersion > UI_SCHEMA_VERSION)
394
+ return null;
395
+ if (c.component === "table" && Array.isArray(p.columns) && Array.isArray(p.rows)) {
396
+ const columns = p.columns;
397
+ const table = plainTable(columns.map((col) => String(col.label)), columns.map((col) => (col.align === "right" ? "right" : "left")));
398
+ for (const row of p.rows)
399
+ table.push(columns.map((col) => cellText(row?.[col.key], col)));
400
+ return { title: String(p.title ?? "Table"), table };
401
+ }
402
+ if (c.component === "chart" && Array.isArray(p.labels) && Array.isArray(p.series)) {
403
+ const series = p.series;
404
+ const table = plainTable(["", ...series.map((x) => String(x.name))], ["left", ...series.map(() => "right")]);
405
+ p.labels.forEach((label, i) => table.push([cellText(label), ...series.map((x) => cellText(Array.isArray(x.values) ? x.values[i] : null))]));
406
+ return { title: `${String(p.title ?? "Chart")}${p.unit ? ` (${String(p.unit)})` : ""}`, table };
407
+ }
408
+ return null;
409
+ }
354
410
  /** Human-readable rendering of an envelope on stdout. Every value printed is passed through {@link stripControl}. */
355
411
  export function printRunText(env) {
356
412
  const s = stripControl;
357
413
  if (env.text)
358
414
  console.log(s(env.text));
415
+ for (const component of env.components) {
416
+ const shown = componentTable(component);
417
+ if (!shown) {
418
+ console.log(chalk.dim("\nThis content needs a newer version of the CLI."));
419
+ continue;
420
+ }
421
+ console.log(chalk.bold(`\n${s(shown.title)}`));
422
+ console.log(shown.table.toString());
423
+ }
359
424
  const head = [
360
425
  env.agent ? `${env.agent}${env.version !== null ? `@${env.version}` : ""}` : null,
361
426
  env.responseId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "8.0.0-dev.5",
3
+ "version": "8.0.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",