@orkestrel/toolbox 0.0.5 → 0.0.7
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/src/core/index.cjs +77 -209
- package/dist/src/core/index.d.cts +34 -94
- package/dist/src/core/index.d.ts +34 -94
- package/dist/src/core/index.js +78 -209
- package/dist/src/server/index.cjs +16 -19
- package/dist/src/server/index.d.cts +0 -34
- package/dist/src/server/index.d.ts +0 -34
- package/dist/src/server/index.js +18 -20
- package/package.json +14 -12
- package/dist/src/core/index.cjs.map +0 -1
- package/dist/src/core/index.js.map +0 -1
- package/dist/src/server/index.cjs.map +0 -1
- package/dist/src/server/index.js.map +0 -1
package/dist/src/core/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { isRelationError } from "@orkestrel/relation";
|
|
|
6
6
|
import { agentResultToJSON } from "@orkestrel/agent";
|
|
7
7
|
import { createWorkspaceManager, isText, rangeOf } from "@orkestrel/workspace";
|
|
8
8
|
import { createTool } from "@orkestrel/tool";
|
|
9
|
+
import { createForm, isFormError, isFormValues, parseForm } from "@orkestrel/form";
|
|
9
10
|
//#region src/core/constants.ts
|
|
10
11
|
/**
|
|
11
12
|
* The name {@link import('./factories.js').createAgentTool} advertises by default — the key a
|
|
@@ -232,23 +233,30 @@ var PROMPT_TOOL_NAME = "ask";
|
|
|
232
233
|
* for the full teaching description; the full text stays retrievable via
|
|
233
234
|
* {@link import('./factories.js').createDescribeTool}.
|
|
234
235
|
*/
|
|
235
|
-
var PROMPT_TOOL_SUMMARY = "Ask another terminal a
|
|
236
|
-
/** The full
|
|
236
|
+
var PROMPT_TOOL_SUMMARY = "Ask another terminal a multi-field form and BLOCK until it answers; the call resolves with the values record. Call describe('ask') for the schema.";
|
|
237
|
+
/** The full form protocol {@link import('./factories.js').createPromptTool} advertises. */
|
|
237
238
|
var PROMPT_TOOL_DESCRIPTION = [
|
|
238
|
-
"Ask another terminal a
|
|
239
|
+
"Ask another terminal a multi-field form and block until it answers. This call does not return until the addressed terminal answers, or the form expires.",
|
|
239
240
|
"",
|
|
240
241
|
"Required:",
|
|
241
|
-
" to
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"Optional:",
|
|
245
|
-
" options - form-specific options (e.g. choices for \"select\"/\"checkbox\").",
|
|
246
|
-
"A cycle (two terminals asking each other) or an expired prompt fails the call with a typed error.",
|
|
242
|
+
" to - the terminal name to ask.",
|
|
243
|
+
" schema - an @orkestrel/form schema: each field declares `control` and `name`, plus optional `label`, `rule`, `default`, and `choices`; `fields` is an ordered array and each `name` must be unique.",
|
|
244
|
+
"The result is one values object keyed by those field names. A cycle or expired form fails with a typed error.",
|
|
247
245
|
"Example:",
|
|
248
246
|
JSON.stringify({
|
|
249
247
|
to: "reviewer",
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
schema: {
|
|
249
|
+
label: "Release review",
|
|
250
|
+
fields: [{
|
|
251
|
+
control: "confirm",
|
|
252
|
+
name: "approved",
|
|
253
|
+
label: "Approve the release?"
|
|
254
|
+
}, {
|
|
255
|
+
control: "editor",
|
|
256
|
+
name: "notes",
|
|
257
|
+
label: "Review notes"
|
|
258
|
+
}]
|
|
259
|
+
}
|
|
252
260
|
})
|
|
253
261
|
].join("\n");
|
|
254
262
|
/**
|
|
@@ -263,21 +271,24 @@ var ANSWER_TOOL_NAME = "answer";
|
|
|
263
271
|
* for the full teaching description; the full text stays retrievable via
|
|
264
272
|
* {@link import('./factories.js').createDescribeTool}.
|
|
265
273
|
*/
|
|
266
|
-
var ANSWER_TOOL_SUMMARY = "List
|
|
274
|
+
var ANSWER_TOOL_SUMMARY = "List forms addressed to this terminal, or answer one by id with a values record. Call describe('answer') for the required fields.";
|
|
267
275
|
/** The pending/answer protocol {@link import('./factories.js').createAnswerTool} advertises. */
|
|
268
276
|
var ANSWER_TOOL_DESCRIPTION = [
|
|
269
|
-
"List the
|
|
277
|
+
"List the forms currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
270
278
|
"",
|
|
271
279
|
"Operations:",
|
|
272
|
-
"- pending { \"operation\": \"pending\" } — list every
|
|
273
|
-
"- answer { \"operation\": \"answer\", \"id\": \"<
|
|
274
|
-
"Example — list pending
|
|
280
|
+
"- pending { \"operation\": \"pending\" } — list every form currently addressed to this terminal (id, from, schema).",
|
|
281
|
+
"- answer { \"operation\": \"answer\", \"id\": \"<form id>\", \"values\": { \"<field name>\": <field value> } } — answer every required field with the value its control accepts.",
|
|
282
|
+
"Example — list pending forms:",
|
|
275
283
|
JSON.stringify({ operation: "pending" }),
|
|
276
284
|
"Example — answer one:",
|
|
277
285
|
JSON.stringify({
|
|
278
286
|
operation: "answer",
|
|
279
287
|
id: "abc123",
|
|
280
|
-
|
|
288
|
+
values: {
|
|
289
|
+
approved: true,
|
|
290
|
+
notes: "Ready"
|
|
291
|
+
}
|
|
281
292
|
})
|
|
282
293
|
].join("\n");
|
|
283
294
|
/**
|
|
@@ -566,104 +577,36 @@ function isToolboxError(value) {
|
|
|
566
577
|
//#endregion
|
|
567
578
|
//#region src/core/shapers.ts
|
|
568
579
|
/**
|
|
569
|
-
* The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to`
|
|
570
|
-
* terminal identity
|
|
571
|
-
* forms to ask), `message`, an optional `timeout` override, and every per-form optional field
|
|
572
|
-
* FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
|
|
573
|
-
* shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
|
|
574
|
-
* which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
|
|
575
|
-
* `message` / `timeout` across six near-identical arms).
|
|
580
|
+
* The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` names the
|
|
581
|
+
* terminal identity and `schema` carries the complete multi-field form document.
|
|
576
582
|
*
|
|
577
583
|
* @remarks
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
|
|
581
|
-
* string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
|
|
582
|
-
* `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
|
|
583
|
-
* (`'input'` / `'password'` / `'confirm'` / `'editor'`).
|
|
584
|
+
* The contract first requires exact JSON. The handler then delegates schema parsing and semantic
|
|
585
|
+
* validation to `@orkestrel/form`'s `parseForm`, so Toolbox does not duplicate the form schema.
|
|
584
586
|
*/
|
|
585
587
|
var promptToolShape = objectShape({
|
|
586
588
|
to: stringShape({
|
|
587
589
|
min: 1,
|
|
588
|
-
description: "The terminal identity to address the
|
|
590
|
+
description: "The terminal identity to address the form to."
|
|
589
591
|
}),
|
|
590
|
-
|
|
591
|
-
"input",
|
|
592
|
-
"password",
|
|
593
|
-
"confirm",
|
|
594
|
-
"select",
|
|
595
|
-
"checkbox",
|
|
596
|
-
"editor"
|
|
597
|
-
], { description: "Which prompt form to ask." }),
|
|
598
|
-
message: stringShape({
|
|
599
|
-
min: 1,
|
|
600
|
-
description: "The prompt's question."
|
|
601
|
-
}),
|
|
602
|
-
default: optionalShape(stringShape({ description: "The default answer if the responder submits blank — 'input' / 'editor' text, 'confirm' 'true'/'false', or a 'select' choice value." })),
|
|
603
|
-
choices: optionalShape(arrayShape(objectShape({
|
|
604
|
-
name: stringShape({
|
|
605
|
-
min: 1,
|
|
606
|
-
description: "The choice label shown to the answering party."
|
|
607
|
-
}),
|
|
608
|
-
value: stringShape({
|
|
609
|
-
min: 1,
|
|
610
|
-
description: "The value submitted when this choice is picked."
|
|
611
|
-
}),
|
|
612
|
-
description: optionalShape(stringShape({ description: "An optional one-line elaboration." }))
|
|
613
|
-
}), { description: "The selectable choices for 'select' / 'checkbox'." })),
|
|
614
|
-
mask: optionalShape(stringShape({
|
|
615
|
-
min: 1,
|
|
616
|
-
description: "The mask character 'password' renders in place of input."
|
|
617
|
-
})),
|
|
618
|
-
min: optionalShape(integerShape({
|
|
619
|
-
min: 0,
|
|
620
|
-
description: "The minimum number of 'checkbox' selections required."
|
|
621
|
-
})),
|
|
622
|
-
max: optionalShape(integerShape({
|
|
623
|
-
min: 0,
|
|
624
|
-
description: "The maximum number of 'checkbox' selections allowed."
|
|
625
|
-
})),
|
|
626
|
-
validate: optionalShape(objectShape({
|
|
627
|
-
required: optionalShape(booleanShape({ description: "Reject an empty (trimmed) input." })),
|
|
628
|
-
minimum: optionalShape(integerShape({
|
|
629
|
-
min: 0,
|
|
630
|
-
description: "Reject an input shorter than this many characters."
|
|
631
|
-
})),
|
|
632
|
-
maximum: optionalShape(integerShape({
|
|
633
|
-
min: 0,
|
|
634
|
-
description: "Reject an input longer than this many characters."
|
|
635
|
-
})),
|
|
636
|
-
pattern: optionalShape(stringShape({ description: "Reject an input that fails this regular-expression source." })),
|
|
637
|
-
email: optionalShape(booleanShape({ description: "Require a valid email-address shape." })),
|
|
638
|
-
url: optionalShape(booleanShape({ description: "Require a valid URL shape." })),
|
|
639
|
-
numeric: optionalShape(booleanShape({ description: "Require a numeric value." })),
|
|
640
|
-
integer: optionalShape(booleanShape({ description: "Require an integer value." })),
|
|
641
|
-
alphanumeric: optionalShape(booleanShape({ description: "Require letters and digits only." }))
|
|
642
|
-
})),
|
|
643
|
-
timeout: optionalShape(integerShape({
|
|
644
|
-
min: 0,
|
|
645
|
-
description: "Milliseconds to wait before the prompt expires."
|
|
646
|
-
}))
|
|
592
|
+
schema: jsonShape({ description: "The @orkestrel/form schema to ask, with form metadata and one or more typed fields." })
|
|
647
593
|
});
|
|
648
594
|
/**
|
|
649
595
|
* The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
|
|
650
|
-
* by `operation`: `'pending'` lists the
|
|
651
|
-
* resolves one by `id` with a `
|
|
596
|
+
* by `operation`: `'pending'` lists the forms addressed to this tool's terminal, while `'answer'`
|
|
597
|
+
* resolves one by `id` with a complete `values` record.
|
|
652
598
|
*
|
|
653
599
|
* @remarks
|
|
654
|
-
* `
|
|
655
|
-
*
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
*/
|
|
660
|
-
var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending"], { description: "List the prompts currently addressed to this terminal." }) }), objectShape({
|
|
661
|
-
operation: literalShape(["answer"], { description: "Answer one pending prompt by id." }),
|
|
600
|
+
* `values` is first bounded to exact JSON. The handler then narrows it with the originating Form
|
|
601
|
+
* package's `isFormValues` guard before the terminal manager applies it to the parked live form.
|
|
602
|
+
*/
|
|
603
|
+
var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending"], { description: "List the forms currently addressed to this terminal." }) }), objectShape({
|
|
604
|
+
operation: literalShape(["answer"], { description: "Answer one pending form by id." }),
|
|
662
605
|
id: stringShape({
|
|
663
606
|
min: 1,
|
|
664
|
-
description: "The id of the pending
|
|
607
|
+
description: "The id of the pending form to answer."
|
|
665
608
|
}),
|
|
666
|
-
|
|
609
|
+
values: jsonShape({ description: "The form answers keyed by field name." })
|
|
667
610
|
}));
|
|
668
611
|
/**
|
|
669
612
|
* The shape of {@link import('./types.js').AgentToolArguments} —
|
|
@@ -1418,48 +1361,6 @@ function expandSteps(flat) {
|
|
|
1418
1361
|
});
|
|
1419
1362
|
}
|
|
1420
1363
|
/**
|
|
1421
|
-
* Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
|
|
1422
|
-
* caller that only ever emits strings can still answer a typed prompt.
|
|
1423
|
-
*
|
|
1424
|
-
* @remarks
|
|
1425
|
-
* `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
|
|
1426
|
-
* `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
|
|
1427
|
-
* `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
|
|
1428
|
-
* (stringifying each entry), a comma-separated string splits + trims into one, and any other
|
|
1429
|
-
* single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
|
|
1430
|
-
* / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
|
|
1431
|
-
* non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
|
|
1432
|
-
* object or array (no lossless string form) falls back to `''` rather than serializing garbage.
|
|
1433
|
-
* Pure and total — never throws.
|
|
1434
|
-
*
|
|
1435
|
-
* @param form - The {@link PromptType} the answer is being coerced FOR
|
|
1436
|
-
* @param value - The raw, LLM-supplied answer value
|
|
1437
|
-
* @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
|
|
1438
|
-
* `string` otherwise
|
|
1439
|
-
*/
|
|
1440
|
-
function coerceAnswer(form, value) {
|
|
1441
|
-
if (form === "confirm") {
|
|
1442
|
-
if (typeof value === "boolean") return value;
|
|
1443
|
-
if (typeof value === "string") {
|
|
1444
|
-
const lower = value.trim().toLowerCase();
|
|
1445
|
-
if (lower === "true") return true;
|
|
1446
|
-
if (lower === "false") return false;
|
|
1447
|
-
}
|
|
1448
|
-
return Boolean(value);
|
|
1449
|
-
}
|
|
1450
|
-
if (form === "checkbox") {
|
|
1451
|
-
if (Array.isArray(value)) return value.map((entry) => String(entry));
|
|
1452
|
-
if (typeof value === "string") {
|
|
1453
|
-
if (value.includes(",")) return value.split(",").map((entry) => entry.trim());
|
|
1454
|
-
return [value];
|
|
1455
|
-
}
|
|
1456
|
-
return [String(value)];
|
|
1457
|
-
}
|
|
1458
|
-
if (typeof value === "string") return value;
|
|
1459
|
-
if (typeof value === "object" && value !== null) return "";
|
|
1460
|
-
return String(value);
|
|
1461
|
-
}
|
|
1462
|
-
/**
|
|
1463
1364
|
* Map a caught error to the {@link ToolboxErrorCode} the terminal-tool factory should throw
|
|
1464
1365
|
* with — the pure classification step of that factory's error handling.
|
|
1465
1366
|
*
|
|
@@ -1468,8 +1369,8 @@ function coerceAnswer(form, value) {
|
|
|
1468
1369
|
* value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
|
|
1469
1370
|
* otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
|
|
1470
1371
|
* to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
|
|
1471
|
-
* (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'`
|
|
1472
|
-
* classifies — the factory performs the actual throw.
|
|
1372
|
+
* (`'TARGET'`, `'LIMIT'`, `'CANCEL'`, `'DRIVER'`, `'DESTROYED'`) maps to the generic `'TOOL'`
|
|
1373
|
+
* code. The mapper only classifies — the factory performs the actual throw.
|
|
1473
1374
|
*
|
|
1474
1375
|
* @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
|
|
1475
1376
|
* @returns The mapped {@link ToolboxErrorCode}, or `undefined` if `error` is not a `TerminalError`
|
|
@@ -2564,19 +2465,18 @@ function createDescribeTool(tools) {
|
|
|
2564
2465
|
});
|
|
2565
2466
|
}
|
|
2566
2467
|
/**
|
|
2567
|
-
* Build an LLM-callable
|
|
2568
|
-
*
|
|
2569
|
-
* returning the resolved answer value.
|
|
2468
|
+
* Build an LLM-callable form tool — the ASK side of the terminal seam. Asks
|
|
2469
|
+
* a multi-field form and BLOCKS until it answers, returning the resolved values record.
|
|
2570
2470
|
*
|
|
2571
2471
|
* @remarks
|
|
2572
2472
|
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2573
|
-
* {@link import('./shapers.js').promptToolShape},
|
|
2574
|
-
*
|
|
2575
|
-
*
|
|
2473
|
+
* {@link import('./shapers.js').promptToolShape}, parses the call's schema through
|
|
2474
|
+
* `@orkestrel/form`, constructs the live form, and passes it to `TerminalManagerInterface.ask`.
|
|
2475
|
+
* `from` is FIXED at construction
|
|
2576
2476
|
* ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
|
|
2577
|
-
* args — so a model cannot spoof which terminal is asking. A
|
|
2477
|
+
* args — so a model cannot spoof which terminal is asking. A form CYCLE rejects with
|
|
2578
2478
|
* `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
|
|
2579
|
-
* {@link import('./errors.js').ToolboxError}; an expired
|
|
2479
|
+
* {@link import('./errors.js').ToolboxError}; an expired form re-surfaces as `EXPIRE`; an
|
|
2580
2480
|
* unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
|
|
2581
2481
|
* plus the known ones (`manager.terminals()`).
|
|
2582
2482
|
*
|
|
@@ -2609,51 +2509,23 @@ function createPromptTool(options) {
|
|
|
2609
2509
|
async execute(args) {
|
|
2610
2510
|
const call = contract.parse(args);
|
|
2611
2511
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed ask call", { args });
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2512
|
+
const schema = parseForm(call.schema);
|
|
2513
|
+
if (schema === void 0) throw new ToolboxError("TOOL", "malformed form schema", { schema: call.schema });
|
|
2514
|
+
for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field '${field.name}' has no enabled choices`, {
|
|
2515
|
+
control: field.control,
|
|
2516
|
+
field: field.name
|
|
2615
2517
|
});
|
|
2616
2518
|
try {
|
|
2617
|
-
|
|
2618
|
-
case "input": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2619
|
-
message: call.message,
|
|
2620
|
-
...call.default === void 0 ? {} : { default: call.default },
|
|
2621
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2622
|
-
});
|
|
2623
|
-
case "editor": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2624
|
-
message: call.message,
|
|
2625
|
-
...call.default === void 0 ? {} : { default: call.default },
|
|
2626
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2627
|
-
});
|
|
2628
|
-
case "password": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2629
|
-
message: call.message,
|
|
2630
|
-
...call.mask === void 0 ? {} : { mask: call.mask },
|
|
2631
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2632
|
-
});
|
|
2633
|
-
case "confirm": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2634
|
-
message: call.message,
|
|
2635
|
-
...call.default === void 0 ? {} : { default: call.default === "true" }
|
|
2636
|
-
});
|
|
2637
|
-
case "select": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2638
|
-
message: call.message,
|
|
2639
|
-
choices: call.choices ?? [],
|
|
2640
|
-
...call.default === void 0 ? {} : { default: call.default }
|
|
2641
|
-
});
|
|
2642
|
-
case "checkbox": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2643
|
-
message: call.message,
|
|
2644
|
-
choices: call.choices ?? [],
|
|
2645
|
-
...call.min === void 0 ? {} : { min: call.min },
|
|
2646
|
-
...call.max === void 0 ? {} : { max: call.max }
|
|
2647
|
-
});
|
|
2648
|
-
}
|
|
2519
|
+
return await options.manager.ask(options.from, call.to, createForm(schema));
|
|
2649
2520
|
} catch (error) {
|
|
2521
|
+
if (isFormError(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2650
2522
|
const code = terminalToolCode(error);
|
|
2651
2523
|
if (code === void 0) throw error;
|
|
2652
|
-
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would
|
|
2524
|
+
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would create a form cycle`, isTerminalError(error) ? error.context : {
|
|
2653
2525
|
from: options.from,
|
|
2654
2526
|
to: call.to
|
|
2655
2527
|
});
|
|
2656
|
-
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `
|
|
2528
|
+
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2657
2529
|
if (isTerminalError(error) && error.code === "TARGET") throw new ToolboxError("TOOL", `unknown terminal '${call.to}'`, {
|
|
2658
2530
|
to: call.to,
|
|
2659
2531
|
known: options.manager.terminals()
|
|
@@ -2664,24 +2536,22 @@ function createPromptTool(options) {
|
|
|
2664
2536
|
});
|
|
2665
2537
|
}
|
|
2666
2538
|
/**
|
|
2667
|
-
* Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the
|
|
2539
|
+
* Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the forms
|
|
2668
2540
|
* currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
|
|
2669
2541
|
* them by id.
|
|
2670
2542
|
*
|
|
2671
2543
|
* @remarks
|
|
2672
2544
|
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2673
2545
|
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
|
|
2674
|
-
* returns a compact list (`{ id, from,
|
|
2675
|
-
* `
|
|
2676
|
-
*
|
|
2677
|
-
* normalizes the model-supplied `value` to the prompt's own form
|
|
2678
|
-
* ({@link import('./helpers.js').coerceAnswer}), and applies it via
|
|
2546
|
+
* returns a compact list (`{ id, from, schema }`) of every form currently addressed to `to`.
|
|
2547
|
+
* `'answer'` looks the form up by `id`, narrows the supplied `values` through `@orkestrel/form`,
|
|
2548
|
+
* and applies it via
|
|
2679
2549
|
* `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
|
|
2680
2550
|
* (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `ToolboxError`; success returns
|
|
2681
2551
|
* `{ answered: id }`. `to` is FIXED at construction
|
|
2682
2552
|
* ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
|
|
2683
2553
|
* so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
|
|
2684
|
-
* endpoint are FIRST-WRITE-WINS — a late answer to an already-settled
|
|
2554
|
+
* endpoint are FIRST-WRITE-WINS — a late answer to an already-settled form returns a typed
|
|
2685
2555
|
* `ANSWER` `ToolboxError` (surfaced as a 422 over HTTP).
|
|
2686
2556
|
*
|
|
2687
2557
|
* @param options - The live manager, the fixed `to` identity, and advertised overrides (see
|
|
@@ -2698,7 +2568,7 @@ function createPromptTool(options) {
|
|
|
2698
2568
|
* manager.add('reviewer')
|
|
2699
2569
|
* const tool = createAnswerTool({ manager, to: 'reviewer' })
|
|
2700
2570
|
* const tools = createToolManager()
|
|
2701
|
-
* tools.add(tool) // the reviewer terminal can now list/answer
|
|
2571
|
+
* tools.add(tool) // the reviewer terminal can now list/answer forms addressed to it
|
|
2702
2572
|
* ```
|
|
2703
2573
|
*/
|
|
2704
2574
|
function createAnswerTool(options) {
|
|
@@ -2712,22 +2582,21 @@ function createAnswerTool(options) {
|
|
|
2712
2582
|
async execute(args) {
|
|
2713
2583
|
const call = contract.parse(args);
|
|
2714
2584
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed answer call", { args });
|
|
2715
|
-
if (call.operation === "pending") return options.manager.pending(options.to).map((
|
|
2716
|
-
id:
|
|
2717
|
-
from:
|
|
2718
|
-
|
|
2719
|
-
message: prompt.message
|
|
2585
|
+
if (call.operation === "pending") return options.manager.pending(options.to).map((form) => ({
|
|
2586
|
+
id: form.id,
|
|
2587
|
+
from: form.from,
|
|
2588
|
+
schema: form.schema
|
|
2720
2589
|
}));
|
|
2721
|
-
|
|
2722
|
-
if (prompt === void 0) throw new ToolboxError("ANSWER", `unknown prompt '${call.id}'`, {
|
|
2590
|
+
if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form '${call.id}'`, {
|
|
2723
2591
|
id: call.id,
|
|
2724
2592
|
reason: "unknown"
|
|
2725
2593
|
});
|
|
2726
|
-
|
|
2727
|
-
const result = options.manager.answer(options.to, call.id,
|
|
2728
|
-
if (!result.success) throw new ToolboxError("ANSWER", `failed to answer
|
|
2594
|
+
if (!isFormValues(call.values)) throw new ToolboxError("TOOL", "malformed form values", { values: call.values });
|
|
2595
|
+
const result = options.manager.answer(options.to, call.id, call.values);
|
|
2596
|
+
if (!result.success) throw new ToolboxError("ANSWER", `failed to answer form '${call.id}': ${result.error.reason}`, {
|
|
2729
2597
|
id: call.id,
|
|
2730
|
-
reason: result.error
|
|
2598
|
+
reason: result.error.reason,
|
|
2599
|
+
...result.error.reason === "rejected" ? { errors: result.error.errors } : {}
|
|
2731
2600
|
});
|
|
2732
2601
|
return { answered: call.id };
|
|
2733
2602
|
}
|
|
@@ -3319,6 +3188,6 @@ function createEndpointTool(definition, options) {
|
|
|
3319
3188
|
});
|
|
3320
3189
|
}
|
|
3321
3190
|
//#endregion
|
|
3322
|
-
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY, MAX_WORKFLOW_DEPTH, MemoryDefinitionStore, PROMPT_TOOL_DESCRIPTION, PROMPT_TOOL_NAME, PROMPT_TOOL_SUMMARY, RELATION_TOOL_DEPTH, RELATION_TOOL_DESCRIPTION, RELATION_TOOL_LIMIT, RELATION_TOOL_NAME, RELATION_TOOL_SUMMARY, ToolboxError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_FLAT_EXAMPLE, WORKFLOW_TOOL_NAME, WORKFLOW_TOOL_NESTED_EXAMPLE, WORKFLOW_TOOL_SUMMARY, WORKSPACE_TOOL_DESCRIPTION, WORKSPACE_TOOL_EXAMPLE, WORKSPACE_TOOL_NAME, WORKSPACE_TOOL_SUMMARY, agentTag, agentToolShape, answerToolShape, clampQuery,
|
|
3191
|
+
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY, MAX_WORKFLOW_DEPTH, MemoryDefinitionStore, PROMPT_TOOL_DESCRIPTION, PROMPT_TOOL_NAME, PROMPT_TOOL_SUMMARY, RELATION_TOOL_DEPTH, RELATION_TOOL_DESCRIPTION, RELATION_TOOL_LIMIT, RELATION_TOOL_NAME, RELATION_TOOL_SUMMARY, ToolboxError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_FLAT_EXAMPLE, WORKFLOW_TOOL_NAME, WORKFLOW_TOOL_NESTED_EXAMPLE, WORKFLOW_TOOL_SUMMARY, WORKSPACE_TOOL_DESCRIPTION, WORKSPACE_TOOL_EXAMPLE, WORKSPACE_TOOL_NAME, WORKSPACE_TOOL_SUMMARY, agentTag, agentToolShape, answerToolShape, clampQuery, columnKindShape, columnShape, columnSpecShape, completeDraft, completePhaseDraft, completeTaskDraft, conditionShape, createAgentFunction, createAgentTool, createAnswerTool, createDatabaseDefinitionStore, createDatabaseTool, createDescribeTool, createEndpointTool, createInferTool, createMemoryDefinitionStore, createPromptTool, createRelationTool, createToolFunction, createWorkflowDraftContract, createWorkflowFunctions, createWorkflowTool, createWorkspaceTool, databaseToolCode, databaseToolShape, deriveWorkflowDepth, describeToolShape, expandInclude, expandSteps, expandTables, extendLineage, includeShape, inferToolShape, isAgentFunction, isColumnKind, isColumnSpec, isDatabaseDefinition, isToolboxError, isWorkflowLineage, keyShape, kindShape, lineageOf, managerShape, orderShape, phaseDraftShape, promptToolShape, queryOf, queryShape, relationKeyShape, relationManagerOf, relationModelOf, relationToolCode, relationToolShape, rowShape, rowsShape, singleKeyShape, stepShape, tableSpecShape, taskDraftShape, terminalToolCode, workflowDraftShape, workflowStepsShape, workflowTag, workflowToolSummary, workspaceToolShape };
|
|
3323
3192
|
|
|
3324
3193
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_terminal = require("@orkestrel/terminal");
|
|
3
|
+
let _orkestrel_form = require("@orkestrel/form");
|
|
4
|
+
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
5
|
let _orkestrel_server = require("@orkestrel/server");
|
|
4
6
|
//#region src/server/constants.ts
|
|
5
7
|
/**
|
|
@@ -19,13 +21,9 @@ var TERMINAL_KEEPALIVE_MS = 15e3;
|
|
|
19
21
|
/**
|
|
20
22
|
* Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
|
|
21
23
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* const connection = new TerminalConnection(manager, name, request, stream, accepts, timer, 15_000)
|
|
27
|
-
* const response = connection.open()
|
|
28
|
-
* ```
|
|
24
|
+
* Internal to this module: `TerminalRoutes` builds the `accepts` predicate from its own token
|
|
25
|
+
* option and opens the stream, so no consumer can construct one. Reach this behaviour through
|
|
26
|
+
* `createTerminalRoutes`.
|
|
29
27
|
*/
|
|
30
28
|
var TerminalConnection = class {
|
|
31
29
|
#manager;
|
|
@@ -44,7 +42,7 @@ var TerminalConnection = class {
|
|
|
44
42
|
/**
|
|
45
43
|
* Create a terminal stream connection.
|
|
46
44
|
*
|
|
47
|
-
* @param manager - Terminal manager supplying pending
|
|
45
|
+
* @param manager - Terminal manager supplying pending forms and lifecycle events
|
|
48
46
|
* @param name - Terminal endpoint streamed by this connection
|
|
49
47
|
* @param request - Request whose abort signal owns the connection lifetime
|
|
50
48
|
* @param stream - Open SSE stream
|
|
@@ -67,7 +65,7 @@ var TerminalConnection = class {
|
|
|
67
65
|
this.#tickHandler = this.#tick.bind(this);
|
|
68
66
|
}
|
|
69
67
|
/**
|
|
70
|
-
* Open the connection by replaying pending
|
|
68
|
+
* Open the connection by replaying pending forms, subscribing, and arming keepalive handling.
|
|
71
69
|
*
|
|
72
70
|
* @returns The SSE response
|
|
73
71
|
*/
|
|
@@ -77,7 +75,7 @@ var TerminalConnection = class {
|
|
|
77
75
|
return this.#stream.response;
|
|
78
76
|
}
|
|
79
77
|
if (this.#cancel !== void 0) return this.#stream.response;
|
|
80
|
-
for (const
|
|
78
|
+
for (const form of this.#manager.pending(this.#name)) this.#write((0, _orkestrel_terminal.serializePending)(form));
|
|
81
79
|
this.#manager.emitter.on("pending", this.#pendingHandler);
|
|
82
80
|
this.#manager.emitter.on("expire", this.#expireHandler);
|
|
83
81
|
this.#cancel = this.#timer(this.#tickHandler, this.#keepalive);
|
|
@@ -91,13 +89,13 @@ var TerminalConnection = class {
|
|
|
91
89
|
...wire.id === void 0 ? {} : { id: wire.id }
|
|
92
90
|
});
|
|
93
91
|
}
|
|
94
|
-
#pending(
|
|
95
|
-
if (
|
|
92
|
+
#pending(form) {
|
|
93
|
+
if (form.to !== this.#name) return;
|
|
96
94
|
if (this.#stream.closed) {
|
|
97
95
|
this.#destroy();
|
|
98
96
|
return;
|
|
99
97
|
}
|
|
100
|
-
this.#write((0, _orkestrel_terminal.serializePending)(
|
|
98
|
+
this.#write((0, _orkestrel_terminal.serializePending)(form));
|
|
101
99
|
}
|
|
102
100
|
#expire(to, id) {
|
|
103
101
|
if (to !== this.#name) return;
|
|
@@ -223,11 +221,11 @@ var TerminalRoutes = class {
|
|
|
223
221
|
} catch {
|
|
224
222
|
return new Response(null, { status: 400 });
|
|
225
223
|
}
|
|
226
|
-
if (!(0,
|
|
227
|
-
const result = this.#manager.answer(name, body.id, body.
|
|
228
|
-
if (result.success) return
|
|
229
|
-
if (result.error === "terminal") return
|
|
230
|
-
return
|
|
224
|
+
if (!(0, _orkestrel_contract.isRecord)(body) || !(0, _orkestrel_contract.isNonEmptyString)(body.id) || !(0, _orkestrel_form.isFormValues)(body.values)) return new Response(null, { status: 422 });
|
|
225
|
+
const result = this.#manager.answer(name, body.id, body.values);
|
|
226
|
+
if (result.success) return Response.json(result);
|
|
227
|
+
if (result.error.reason === "terminal") return Response.json(result, { status: 404 });
|
|
228
|
+
return Response.json(result, { status: 422 });
|
|
231
229
|
}
|
|
232
230
|
};
|
|
233
231
|
//#endregion
|
|
@@ -260,7 +258,6 @@ function createTerminalRoutes(manager, options) {
|
|
|
260
258
|
//#endregion
|
|
261
259
|
exports.TERMINAL_KEEPALIVE_MS = TERMINAL_KEEPALIVE_MS;
|
|
262
260
|
exports.TERMINAL_ROUTES_PATH = TERMINAL_ROUTES_PATH;
|
|
263
|
-
exports.TerminalConnection = TerminalConnection;
|
|
264
261
|
exports.TerminalRoutes = TerminalRoutes;
|
|
265
262
|
exports.createTerminalRoutes = createTerminalRoutes;
|
|
266
263
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { StreamInterface } from '@orkestrel/server';
|
|
2
1
|
import { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
3
2
|
import { TimerHandler } from '@orkestrel/terminal';
|
|
4
3
|
|
|
@@ -43,39 +42,6 @@ export declare const TERMINAL_KEEPALIVE_MS = 15000;
|
|
|
43
42
|
*/
|
|
44
43
|
export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* import { TerminalConnection } from '@orkestrel/toolbox/server'
|
|
52
|
-
*
|
|
53
|
-
* const connection = new TerminalConnection(manager, name, request, stream, accepts, timer, 15_000)
|
|
54
|
-
* const response = connection.open()
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export declare class TerminalConnection {
|
|
58
|
-
#private;
|
|
59
|
-
/**
|
|
60
|
-
* Create a terminal stream connection.
|
|
61
|
-
*
|
|
62
|
-
* @param manager - Terminal manager supplying pending prompts and lifecycle events
|
|
63
|
-
* @param name - Terminal endpoint streamed by this connection
|
|
64
|
-
* @param request - Request whose abort signal owns the connection lifetime
|
|
65
|
-
* @param stream - Open SSE stream
|
|
66
|
-
* @param accepts - Presented-token validator
|
|
67
|
-
* @param timer - Keepalive timer implementation
|
|
68
|
-
* @param keepalive - Keepalive interval in milliseconds
|
|
69
|
-
*/
|
|
70
|
-
constructor(manager: TerminalManagerInterface, name: string, request: Request, stream: StreamInterface, accepts: (presented: string | undefined) => boolean, timer: TimerHandler, keepalive: number);
|
|
71
|
-
/**
|
|
72
|
-
* Open the connection by replaying pending prompts, subscribing, and arming keepalive handling.
|
|
73
|
-
*
|
|
74
|
-
* @returns The SSE response
|
|
75
|
-
*/
|
|
76
|
-
open(): Response;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
45
|
/**
|
|
80
46
|
* One structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
|
|
81
47
|
* plain `{ method, path, handler }` shape carrying NO dependency on `@orkestrel/router`'s own
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { StreamInterface } from '@orkestrel/server';
|
|
2
1
|
import { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
3
2
|
import { TimerHandler } from '@orkestrel/terminal';
|
|
4
3
|
|
|
@@ -43,39 +42,6 @@ export declare const TERMINAL_KEEPALIVE_MS = 15000;
|
|
|
43
42
|
*/
|
|
44
43
|
export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
* Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* import { TerminalConnection } from '@orkestrel/toolbox/server'
|
|
52
|
-
*
|
|
53
|
-
* const connection = new TerminalConnection(manager, name, request, stream, accepts, timer, 15_000)
|
|
54
|
-
* const response = connection.open()
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export declare class TerminalConnection {
|
|
58
|
-
#private;
|
|
59
|
-
/**
|
|
60
|
-
* Create a terminal stream connection.
|
|
61
|
-
*
|
|
62
|
-
* @param manager - Terminal manager supplying pending prompts and lifecycle events
|
|
63
|
-
* @param name - Terminal endpoint streamed by this connection
|
|
64
|
-
* @param request - Request whose abort signal owns the connection lifetime
|
|
65
|
-
* @param stream - Open SSE stream
|
|
66
|
-
* @param accepts - Presented-token validator
|
|
67
|
-
* @param timer - Keepalive timer implementation
|
|
68
|
-
* @param keepalive - Keepalive interval in milliseconds
|
|
69
|
-
*/
|
|
70
|
-
constructor(manager: TerminalManagerInterface, name: string, request: Request, stream: StreamInterface, accepts: (presented: string | undefined) => boolean, timer: TimerHandler, keepalive: number);
|
|
71
|
-
/**
|
|
72
|
-
* Open the connection by replaying pending prompts, subscribing, and arming keepalive handling.
|
|
73
|
-
*
|
|
74
|
-
* @returns The SSE response
|
|
75
|
-
*/
|
|
76
|
-
open(): Response;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
45
|
/**
|
|
80
46
|
* One structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
|
|
81
47
|
* plain `{ method, path, handler }` shape carrying NO dependency on `@orkestrel/router`'s own
|