@orkestrel/tool 0.0.1 → 0.0.2
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 +407 -2
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +270 -3
- package/dist/src/core/index.d.ts +270 -3
- package/dist/src/core/index.js +396 -3
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/constants.d.ts +12 -0
- package/dist/src/server/factories.d.ts +44 -0
- package/dist/src/server/index.cjs +202 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +3 -0
- package/dist/src/server/index.d.ts +3 -0
- package/dist/src/server/index.js +199 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/types.d.ts +60 -0
- package/package.json +28 -13
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
|
+
let _orkestrel_terminal = require("@orkestrel/terminal");
|
|
3
4
|
let _orkestrel_agent = require("@orkestrel/agent");
|
|
4
5
|
let _orkestrel_workflow = require("@orkestrel/workflow");
|
|
5
6
|
//#region src/core/constants.ts
|
|
@@ -237,13 +238,74 @@ var DESCRIBE_TOOL_SUMMARY = "Return the full description of a named registered t
|
|
|
237
238
|
* schema or multi-step protocol to teach.
|
|
238
239
|
*/
|
|
239
240
|
var DESCRIBE_TOOL_DESCRIPTION = "Return the full description of a registered tool by its name. Required: name - the registered tool name (see another tool listing for available names).";
|
|
241
|
+
/**
|
|
242
|
+
* The name {@link import('./factories.js').createPromptTool} advertises by default — the key a
|
|
243
|
+
* model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
|
|
244
|
+
*/
|
|
245
|
+
var PROMPT_TOOL_NAME = "ask";
|
|
246
|
+
/**
|
|
247
|
+
* The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createPromptTool}
|
|
248
|
+
* advertises in place of {@link PROMPT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
249
|
+
* (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
250
|
+
* for the full teaching description; the full text stays retrievable via
|
|
251
|
+
* {@link import('./factories.js').createDescribeTool}.
|
|
252
|
+
*/
|
|
253
|
+
var PROMPT_TOOL_SUMMARY = "Ask another terminal a question and BLOCK until it answers; the call resolves with the answered value. Call describe('ask') for the required fields.";
|
|
254
|
+
var PROMPT_TOOL_DESCRIPTION = [
|
|
255
|
+
"Ask another terminal a question and block until it answers. This call does not return until the addressed terminal answers, or the prompt fails.",
|
|
256
|
+
"",
|
|
257
|
+
"Required:",
|
|
258
|
+
" to - the terminal name to ask.",
|
|
259
|
+
" form - the prompt kind: one of \"input\", \"password\", \"confirm\", \"select\", \"checkbox\", \"editor\".",
|
|
260
|
+
" message - the question shown to the answering terminal.",
|
|
261
|
+
"Optional:",
|
|
262
|
+
" options - form-specific options (e.g. choices for \"select\"/\"checkbox\").",
|
|
263
|
+
"A cycle (two terminals asking each other) or an expired prompt fails the call with a typed error.",
|
|
264
|
+
"Example:",
|
|
265
|
+
JSON.stringify({
|
|
266
|
+
to: "reviewer",
|
|
267
|
+
form: "confirm",
|
|
268
|
+
message: "Approve the release?"
|
|
269
|
+
})
|
|
270
|
+
].join("\n");
|
|
271
|
+
/**
|
|
272
|
+
* The name {@link import('./factories.js').createAnswerTool} advertises by default — the key a
|
|
273
|
+
* model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
|
|
274
|
+
*/
|
|
275
|
+
var ANSWER_TOOL_NAME = "answer";
|
|
276
|
+
/**
|
|
277
|
+
* The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAnswerTool}
|
|
278
|
+
* advertises in place of {@link ANSWER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
279
|
+
* (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
280
|
+
* for the full teaching description; the full text stays retrievable via
|
|
281
|
+
* {@link import('./factories.js').createDescribeTool}.
|
|
282
|
+
*/
|
|
283
|
+
var ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
|
|
284
|
+
var ANSWER_TOOL_DESCRIPTION = [
|
|
285
|
+
"List the prompts currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
286
|
+
"",
|
|
287
|
+
"Operations:",
|
|
288
|
+
"- pending { \"operation\": \"pending\" } — list every prompt currently addressed to this terminal (id, form, message, options, time).",
|
|
289
|
+
"- answer { \"operation\": \"answer\", \"id\": \"<prompt id>\", \"value\": <answer value> } — answer the prompt with that id; \"value\" must match the prompt's form (a string for \"input\"/\"password\"/\"editor\", a boolean for \"confirm\", a choice for \"select\", an array of choices for \"checkbox\").",
|
|
290
|
+
"Example — list pending prompts:",
|
|
291
|
+
JSON.stringify({ operation: "pending" }),
|
|
292
|
+
"Example — answer one:",
|
|
293
|
+
JSON.stringify({
|
|
294
|
+
operation: "answer",
|
|
295
|
+
id: "abc123",
|
|
296
|
+
value: true
|
|
297
|
+
})
|
|
298
|
+
].join("\n");
|
|
240
299
|
//#endregion
|
|
241
300
|
//#region src/core/errors.ts
|
|
242
301
|
/**
|
|
243
302
|
* Thrown by {@link import('./factories.js').createAgentTool}'s and
|
|
244
303
|
* {@link import('./factories.js').createDescribeTool}'s handlers on every failure path — a
|
|
245
|
-
* malformed / unresolvable call or an unknown tool name (`TOOL`),
|
|
246
|
-
* exceed the configured depth bound or re-enter an ancestor (`DEPTH`)
|
|
304
|
+
* malformed / unresolvable call or an unknown tool name (`TOOL`), a delegation that would
|
|
305
|
+
* exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
|
|
306
|
+
* (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
|
|
307
|
+
* failed to apply (`ANSWER`) — the last three thrown by
|
|
308
|
+
* {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
|
|
247
309
|
*
|
|
248
310
|
* @remarks
|
|
249
311
|
* Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
|
|
@@ -295,6 +357,106 @@ function isAgentToolError(value) {
|
|
|
295
357
|
//#endregion
|
|
296
358
|
//#region src/core/shapers.ts
|
|
297
359
|
/**
|
|
360
|
+
* The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` (the
|
|
361
|
+
* terminal identity to address), `form` (which of the six {@link import('@orkestrel/terminal').PromptType}
|
|
362
|
+
* forms to ask), `message`, an optional `timeout` override, and every per-form optional field
|
|
363
|
+
* FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
|
|
364
|
+
* shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
|
|
365
|
+
* which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
|
|
366
|
+
* `message` / `timeout` across six near-identical arms).
|
|
367
|
+
*
|
|
368
|
+
* @remarks
|
|
369
|
+
* `choices` backs `'select'` / `'checkbox'`; `default` backs `'input'` / `'confirm'` / `'select'`
|
|
370
|
+
* (a string for the first two forms' text default, `'true'`/`'false'` string for confirm — the
|
|
371
|
+
* contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
|
|
372
|
+
* string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
|
|
373
|
+
* `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
|
|
374
|
+
* (`'input'` / `'password'` / `'confirm'` / `'editor'`).
|
|
375
|
+
*/
|
|
376
|
+
var promptToolShape = (0, _orkestrel_contract.objectShape)({
|
|
377
|
+
to: (0, _orkestrel_contract.stringShape)({
|
|
378
|
+
min: 1,
|
|
379
|
+
description: "The terminal identity to address the prompt to."
|
|
380
|
+
}),
|
|
381
|
+
form: (0, _orkestrel_contract.literalShape)([
|
|
382
|
+
"input",
|
|
383
|
+
"password",
|
|
384
|
+
"confirm",
|
|
385
|
+
"select",
|
|
386
|
+
"checkbox",
|
|
387
|
+
"editor"
|
|
388
|
+
], { description: "Which prompt form to ask." }),
|
|
389
|
+
message: (0, _orkestrel_contract.stringShape)({
|
|
390
|
+
min: 1,
|
|
391
|
+
description: "The prompt's question."
|
|
392
|
+
}),
|
|
393
|
+
default: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "The default answer if the responder submits blank — 'input' / 'editor' text, 'confirm' 'true'/'false', or a 'select' choice value." })),
|
|
394
|
+
choices: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
|
|
395
|
+
name: (0, _orkestrel_contract.stringShape)({
|
|
396
|
+
min: 1,
|
|
397
|
+
description: "The choice label shown to the answering party."
|
|
398
|
+
}),
|
|
399
|
+
value: (0, _orkestrel_contract.stringShape)({
|
|
400
|
+
min: 1,
|
|
401
|
+
description: "The value submitted when this choice is picked."
|
|
402
|
+
}),
|
|
403
|
+
description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "An optional one-line elaboration." }))
|
|
404
|
+
}), { description: "The selectable choices for 'select' / 'checkbox'." })),
|
|
405
|
+
mask: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
|
|
406
|
+
min: 1,
|
|
407
|
+
description: "The mask character 'password' renders in place of input."
|
|
408
|
+
})),
|
|
409
|
+
min: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
410
|
+
min: 0,
|
|
411
|
+
description: "The minimum number of 'checkbox' selections required."
|
|
412
|
+
})),
|
|
413
|
+
max: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
414
|
+
min: 0,
|
|
415
|
+
description: "The maximum number of 'checkbox' selections allowed."
|
|
416
|
+
})),
|
|
417
|
+
validate: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
|
|
418
|
+
required: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Reject an empty (trimmed) input." })),
|
|
419
|
+
minimum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
420
|
+
min: 0,
|
|
421
|
+
description: "Reject an input shorter than this many characters."
|
|
422
|
+
})),
|
|
423
|
+
maximum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
424
|
+
min: 0,
|
|
425
|
+
description: "Reject an input longer than this many characters."
|
|
426
|
+
})),
|
|
427
|
+
pattern: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Reject an input that fails this regular-expression source." })),
|
|
428
|
+
email: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid email-address shape." })),
|
|
429
|
+
url: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid URL shape." })),
|
|
430
|
+
numeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a numeric value." })),
|
|
431
|
+
integer: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require an integer value." })),
|
|
432
|
+
alphanumeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require letters and digits only." }))
|
|
433
|
+
})),
|
|
434
|
+
timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
435
|
+
min: 0,
|
|
436
|
+
description: "Milliseconds to wait before the prompt expires."
|
|
437
|
+
}))
|
|
438
|
+
});
|
|
439
|
+
/**
|
|
440
|
+
* The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
|
|
441
|
+
* by `operation`: `'pending'` lists the prompts addressed to this tool's terminal, `'answer'`
|
|
442
|
+
* resolves one by `id` with a `value`.
|
|
443
|
+
*
|
|
444
|
+
* @remarks
|
|
445
|
+
* `value`'s type varies by the ORIGINAL prompt's form (`string` for `'input'` / `'password'` /
|
|
446
|
+
* `'select'` / `'editor'`, `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`) —
|
|
447
|
+
* `unionShape(stringShape(), booleanShape(), arrayShape(stringShape()))` expresses that
|
|
448
|
+
* union directly, so `value` is typed as the full `string | boolean | readonly string[]` union
|
|
449
|
+
* here (no lossy string-only fallback needed).
|
|
450
|
+
*/
|
|
451
|
+
var answerToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["pending"], { description: "List the prompts currently addressed to this terminal." }) }), (0, _orkestrel_contract.objectShape)({
|
|
452
|
+
operation: (0, _orkestrel_contract.literalShape)(["answer"], { description: "Answer one pending prompt by id." }),
|
|
453
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
454
|
+
min: 1,
|
|
455
|
+
description: "The id of the pending prompt to answer."
|
|
456
|
+
}),
|
|
457
|
+
value: (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "A text / select / editor answer." }), (0, _orkestrel_contract.booleanShape)({ description: "A confirm answer." }), (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)(), { description: "A checkbox answer — the checked values." }))
|
|
458
|
+
}));
|
|
459
|
+
/**
|
|
298
460
|
* The shape of {@link import('./types.js').AgentToolArguments} —
|
|
299
461
|
* {@link import('./factories.js').createAgentTool}'s advertised `parameters`.
|
|
300
462
|
*
|
|
@@ -656,6 +818,69 @@ function expandSteps(flat) {
|
|
|
656
818
|
phases: flat.steps.map((step) => ({ tasks: [{ run: step.name }] }))
|
|
657
819
|
});
|
|
658
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
|
|
823
|
+
* caller that only ever emits strings can still answer a typed prompt.
|
|
824
|
+
*
|
|
825
|
+
* @remarks
|
|
826
|
+
* `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
|
|
827
|
+
* `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
|
|
828
|
+
* `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
|
|
829
|
+
* (stringifying each entry), a comma-separated string splits + trims into one, and any other
|
|
830
|
+
* single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
|
|
831
|
+
* / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
|
|
832
|
+
* non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
|
|
833
|
+
* object or array (no lossless string form) falls back to `''` rather than serializing garbage.
|
|
834
|
+
* Pure and total — never throws.
|
|
835
|
+
*
|
|
836
|
+
* @param form - The {@link PromptType} the answer is being coerced FOR
|
|
837
|
+
* @param value - The raw, LLM-supplied answer value
|
|
838
|
+
* @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
|
|
839
|
+
* `string` otherwise
|
|
840
|
+
*/
|
|
841
|
+
function coerceAnswer(form, value) {
|
|
842
|
+
if (form === "confirm") {
|
|
843
|
+
if (typeof value === "boolean") return value;
|
|
844
|
+
if (typeof value === "string") {
|
|
845
|
+
const lower = value.trim().toLowerCase();
|
|
846
|
+
if (lower === "true") return true;
|
|
847
|
+
if (lower === "false") return false;
|
|
848
|
+
}
|
|
849
|
+
return Boolean(value);
|
|
850
|
+
}
|
|
851
|
+
if (form === "checkbox") {
|
|
852
|
+
if (Array.isArray(value)) return value.map((entry) => String(entry));
|
|
853
|
+
if (typeof value === "string") {
|
|
854
|
+
if (value.includes(",")) return value.split(",").map((entry) => entry.trim());
|
|
855
|
+
return [value];
|
|
856
|
+
}
|
|
857
|
+
return [String(value)];
|
|
858
|
+
}
|
|
859
|
+
if (typeof value === "string") return value;
|
|
860
|
+
if (typeof value === "object" && value !== null) return "";
|
|
861
|
+
return String(value);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Map a caught error to the {@link AgentToolErrorCode} the terminal-tool factory should throw
|
|
865
|
+
* with — the pure classification step of that factory's error handling.
|
|
866
|
+
*
|
|
867
|
+
* @remarks
|
|
868
|
+
* Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
|
|
869
|
+
* value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
|
|
870
|
+
* otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
|
|
871
|
+
* to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
|
|
872
|
+
* (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'` code. The mapper only
|
|
873
|
+
* classifies — the factory performs the actual throw.
|
|
874
|
+
*
|
|
875
|
+
* @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
|
|
876
|
+
* @returns The mapped {@link AgentToolErrorCode}, or `undefined` if `error` is not a `TerminalError`
|
|
877
|
+
*/
|
|
878
|
+
function terminalToolCode(error) {
|
|
879
|
+
if (!(0, _orkestrel_terminal.isTerminalError)(error)) return void 0;
|
|
880
|
+
if (error.code === "DEADLOCK") return "DEADLOCK";
|
|
881
|
+
if (error.code === "EXPIRE") return "EXPIRE";
|
|
882
|
+
return "TOOL";
|
|
883
|
+
}
|
|
659
884
|
//#endregion
|
|
660
885
|
//#region src/core/factories.ts
|
|
661
886
|
/**
|
|
@@ -1194,16 +1419,190 @@ function createDescribeTool(tools) {
|
|
|
1194
1419
|
}
|
|
1195
1420
|
});
|
|
1196
1421
|
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Build an LLM-callable prompt tool — the ASK side of the terminal seam. Asks
|
|
1424
|
+
* {@link import('./types.js').PromptToolOptions.to} a question and BLOCKS until it answers,
|
|
1425
|
+
* returning the resolved answer value.
|
|
1426
|
+
*
|
|
1427
|
+
* @remarks
|
|
1428
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
1429
|
+
* {@link import('./shapers.js').promptToolShape}, dispatches to the matching
|
|
1430
|
+
* `TerminalManagerInterface.ask` overload (`@orkestrel/terminal`) for the call's `form`, and
|
|
1431
|
+
* RETURNS the resolved answer on success. `from` is FIXED at construction
|
|
1432
|
+
* ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
|
|
1433
|
+
* args — so a model cannot spoof which terminal is asking. A prompt CYCLE rejects with
|
|
1434
|
+
* `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
|
|
1435
|
+
* {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`; an
|
|
1436
|
+
* unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
|
|
1437
|
+
* plus the known ones (`manager.terminals()`).
|
|
1438
|
+
*
|
|
1439
|
+
* @param options - The live manager, the fixed `from` identity, and advertised overrides (see
|
|
1440
|
+
* {@link import('./types.js').PromptToolOptions})
|
|
1441
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').PROMPT_TOOL_NAME} by default)
|
|
1442
|
+
*
|
|
1443
|
+
* @example
|
|
1444
|
+
* ```ts
|
|
1445
|
+
* import { createPromptTool } from '@src/core'
|
|
1446
|
+
* import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
|
|
1447
|
+
*
|
|
1448
|
+
* const manager = createTerminalManager()
|
|
1449
|
+
* manager.add('agent')
|
|
1450
|
+
* manager.add('reviewer')
|
|
1451
|
+
* const tool = createPromptTool({ manager, from: 'agent' })
|
|
1452
|
+
* const tools = createToolManager()
|
|
1453
|
+
* tools.add(tool) // the agent can now ask 'reviewer' and block for the answer
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
function createPromptTool(options) {
|
|
1457
|
+
const contract = (0, _orkestrel_contract.createContract)(promptToolShape);
|
|
1458
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
1459
|
+
return (0, _orkestrel_agent.createTool)({
|
|
1460
|
+
name: options.name ?? "ask",
|
|
1461
|
+
description: options.description ?? PROMPT_TOOL_DESCRIPTION,
|
|
1462
|
+
summary: PROMPT_TOOL_SUMMARY,
|
|
1463
|
+
parameters,
|
|
1464
|
+
execute: async (args) => {
|
|
1465
|
+
const call = contract.parse(args);
|
|
1466
|
+
if (call === void 0) throw new AgentToolError("TOOL", "malformed ask call", { args });
|
|
1467
|
+
if ((call.form === "select" || call.form === "checkbox") && (call.choices ?? []).length === 0) throw new AgentToolError("TOOL", "select/checkbox requires at least one choice", {
|
|
1468
|
+
to: call.to,
|
|
1469
|
+
form: call.form
|
|
1470
|
+
});
|
|
1471
|
+
try {
|
|
1472
|
+
switch (call.form) {
|
|
1473
|
+
case "input": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1474
|
+
message: call.message,
|
|
1475
|
+
...call.default === void 0 ? {} : { default: call.default },
|
|
1476
|
+
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
1477
|
+
});
|
|
1478
|
+
case "editor": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1479
|
+
message: call.message,
|
|
1480
|
+
...call.default === void 0 ? {} : { default: call.default },
|
|
1481
|
+
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
1482
|
+
});
|
|
1483
|
+
case "password": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1484
|
+
message: call.message,
|
|
1485
|
+
...call.mask === void 0 ? {} : { mask: call.mask },
|
|
1486
|
+
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
1487
|
+
});
|
|
1488
|
+
case "confirm": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1489
|
+
message: call.message,
|
|
1490
|
+
...call.default === void 0 ? {} : { default: call.default === "true" }
|
|
1491
|
+
});
|
|
1492
|
+
case "select": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1493
|
+
message: call.message,
|
|
1494
|
+
choices: call.choices ?? [],
|
|
1495
|
+
...call.default === void 0 ? {} : { default: call.default }
|
|
1496
|
+
});
|
|
1497
|
+
case "checkbox": return await options.manager.ask(options.from, call.to, call.form, {
|
|
1498
|
+
message: call.message,
|
|
1499
|
+
choices: call.choices ?? [],
|
|
1500
|
+
...call.min === void 0 ? {} : { min: call.min },
|
|
1501
|
+
...call.max === void 0 ? {} : { max: call.max }
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
const code = terminalToolCode(error);
|
|
1506
|
+
if (code === void 0) throw error;
|
|
1507
|
+
if (code === "DEADLOCK") throw new AgentToolError("DEADLOCK", `asking '${call.to}' would form a prompt cycle`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
|
|
1508
|
+
from: options.from,
|
|
1509
|
+
to: call.to
|
|
1510
|
+
});
|
|
1511
|
+
if (code === "EXPIRE") throw new AgentToolError("EXPIRE", `prompt to '${call.to}' expired before it was answered`, { to: call.to });
|
|
1512
|
+
if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") throw new AgentToolError("TOOL", `unknown terminal '${call.to}'`, {
|
|
1513
|
+
to: call.to,
|
|
1514
|
+
known: options.manager.terminals()
|
|
1515
|
+
});
|
|
1516
|
+
throw new AgentToolError("TOOL", `asking '${call.to}' failed`, { to: call.to });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the prompts
|
|
1523
|
+
* currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
|
|
1524
|
+
* them by id.
|
|
1525
|
+
*
|
|
1526
|
+
* @remarks
|
|
1527
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
1528
|
+
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
|
|
1529
|
+
* returns a compact list (`{ id, from, form, message }`) of every prompt currently addressed to
|
|
1530
|
+
* `to` (`TerminalManagerInterface.pending`, `@orkestrel/terminal`). `'answer'` looks the prompt
|
|
1531
|
+
* up by `id` (an unknown id throws a typed `ANSWER` {@link import('./errors.js').AgentToolError}),
|
|
1532
|
+
* normalizes the model-supplied `value` to the prompt's own form
|
|
1533
|
+
* ({@link import('./helpers.js').coerceAnswer}), and applies it via
|
|
1534
|
+
* `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
|
|
1535
|
+
* (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `AgentToolError`; success returns
|
|
1536
|
+
* `{ answered: id }`. `to` is FIXED at construction
|
|
1537
|
+
* ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
|
|
1538
|
+
* so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
|
|
1539
|
+
* endpoint are FIRST-WRITE-WINS — a late answer to an already-settled prompt returns a typed
|
|
1540
|
+
* `ANSWER` `AgentToolError` (surfaced as a 422 over HTTP).
|
|
1541
|
+
*
|
|
1542
|
+
* @param options - The live manager, the fixed `to` identity, and advertised overrides (see
|
|
1543
|
+
* {@link import('./types.js').AnswerToolOptions})
|
|
1544
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').ANSWER_TOOL_NAME} by default)
|
|
1545
|
+
*
|
|
1546
|
+
* @example
|
|
1547
|
+
* ```ts
|
|
1548
|
+
* import { createAnswerTool } from '@src/core'
|
|
1549
|
+
* import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
|
|
1550
|
+
*
|
|
1551
|
+
* const manager = createTerminalManager()
|
|
1552
|
+
* manager.add('reviewer')
|
|
1553
|
+
* const tool = createAnswerTool({ manager, to: 'reviewer' })
|
|
1554
|
+
* const tools = createToolManager()
|
|
1555
|
+
* tools.add(tool) // the reviewer terminal can now list/answer prompts addressed to it
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
function createAnswerTool(options) {
|
|
1559
|
+
const contract = (0, _orkestrel_contract.createContract)(answerToolShape);
|
|
1560
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
1561
|
+
return (0, _orkestrel_agent.createTool)({
|
|
1562
|
+
name: options.name ?? "answer",
|
|
1563
|
+
description: options.description ?? ANSWER_TOOL_DESCRIPTION,
|
|
1564
|
+
summary: ANSWER_TOOL_SUMMARY,
|
|
1565
|
+
parameters,
|
|
1566
|
+
execute: async (args) => {
|
|
1567
|
+
const call = contract.parse(args);
|
|
1568
|
+
if (call === void 0) throw new AgentToolError("TOOL", "malformed answer call", { args });
|
|
1569
|
+
if (call.operation === "pending") return options.manager.pending(options.to).map((prompt) => ({
|
|
1570
|
+
id: prompt.id,
|
|
1571
|
+
from: prompt.from,
|
|
1572
|
+
form: prompt.form,
|
|
1573
|
+
message: prompt.message
|
|
1574
|
+
}));
|
|
1575
|
+
const prompt = options.manager.pending(options.to).find((entry) => entry.id === call.id);
|
|
1576
|
+
if (prompt === void 0) throw new AgentToolError("ANSWER", `unknown prompt '${call.id}'`, {
|
|
1577
|
+
id: call.id,
|
|
1578
|
+
reason: "unknown"
|
|
1579
|
+
});
|
|
1580
|
+
const coerced = coerceAnswer(prompt.form, call.value);
|
|
1581
|
+
const result = options.manager.answer(options.to, call.id, coerced);
|
|
1582
|
+
if (!result.success) throw new AgentToolError("ANSWER", `failed to answer prompt '${call.id}': ${result.error}`, {
|
|
1583
|
+
id: call.id,
|
|
1584
|
+
reason: result.error
|
|
1585
|
+
});
|
|
1586
|
+
return { answered: call.id };
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1197
1590
|
//#endregion
|
|
1198
1591
|
exports.AGENT_TOOL_DEPTH = AGENT_TOOL_DEPTH;
|
|
1199
1592
|
exports.AGENT_TOOL_DESCRIPTION = AGENT_TOOL_DESCRIPTION;
|
|
1200
1593
|
exports.AGENT_TOOL_NAME = AGENT_TOOL_NAME;
|
|
1201
1594
|
exports.AGENT_TOOL_SUMMARY = AGENT_TOOL_SUMMARY;
|
|
1595
|
+
exports.ANSWER_TOOL_DESCRIPTION = ANSWER_TOOL_DESCRIPTION;
|
|
1596
|
+
exports.ANSWER_TOOL_NAME = ANSWER_TOOL_NAME;
|
|
1597
|
+
exports.ANSWER_TOOL_SUMMARY = ANSWER_TOOL_SUMMARY;
|
|
1202
1598
|
exports.AgentToolError = AgentToolError;
|
|
1203
1599
|
exports.DESCRIBE_TOOL_DESCRIPTION = DESCRIBE_TOOL_DESCRIPTION;
|
|
1204
1600
|
exports.DESCRIBE_TOOL_NAME = DESCRIBE_TOOL_NAME;
|
|
1205
1601
|
exports.DESCRIBE_TOOL_SUMMARY = DESCRIBE_TOOL_SUMMARY;
|
|
1206
1602
|
exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
|
|
1603
|
+
exports.PROMPT_TOOL_DESCRIPTION = PROMPT_TOOL_DESCRIPTION;
|
|
1604
|
+
exports.PROMPT_TOOL_NAME = PROMPT_TOOL_NAME;
|
|
1605
|
+
exports.PROMPT_TOOL_SUMMARY = PROMPT_TOOL_SUMMARY;
|
|
1207
1606
|
exports.WORKFLOW_TOOL_DESCRIPTION = WORKFLOW_TOOL_DESCRIPTION;
|
|
1208
1607
|
exports.WORKFLOW_TOOL_FLAT_EXAMPLE = WORKFLOW_TOOL_FLAT_EXAMPLE;
|
|
1209
1608
|
exports.WORKFLOW_TOOL_NAME = WORKFLOW_TOOL_NAME;
|
|
@@ -1215,12 +1614,16 @@ exports.WORKSPACE_TOOL_NAME = WORKSPACE_TOOL_NAME;
|
|
|
1215
1614
|
exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
|
|
1216
1615
|
exports.agentTag = agentTag;
|
|
1217
1616
|
exports.agentToolShape = agentToolShape;
|
|
1617
|
+
exports.answerToolShape = answerToolShape;
|
|
1618
|
+
exports.coerceAnswer = coerceAnswer;
|
|
1218
1619
|
exports.completeDraft = completeDraft;
|
|
1219
1620
|
exports.completePhaseDraft = completePhaseDraft;
|
|
1220
1621
|
exports.completeTaskDraft = completeTaskDraft;
|
|
1221
1622
|
exports.createAgentFunction = createAgentFunction;
|
|
1222
1623
|
exports.createAgentTool = createAgentTool;
|
|
1624
|
+
exports.createAnswerTool = createAnswerTool;
|
|
1223
1625
|
exports.createDescribeTool = createDescribeTool;
|
|
1626
|
+
exports.createPromptTool = createPromptTool;
|
|
1224
1627
|
exports.createToolFunction = createToolFunction;
|
|
1225
1628
|
exports.createWorkflowDraftContract = createWorkflowDraftContract;
|
|
1226
1629
|
exports.createWorkflowTool = createWorkflowTool;
|
|
@@ -1229,8 +1632,10 @@ exports.describeToolShape = describeToolShape;
|
|
|
1229
1632
|
exports.expandSteps = expandSteps;
|
|
1230
1633
|
exports.isAgentToolError = isAgentToolError;
|
|
1231
1634
|
exports.phaseDraftShape = phaseDraftShape;
|
|
1635
|
+
exports.promptToolShape = promptToolShape;
|
|
1232
1636
|
exports.stepShape = stepShape;
|
|
1233
1637
|
exports.taskDraftShape = taskDraftShape;
|
|
1638
|
+
exports.terminalToolCode = terminalToolCode;
|
|
1234
1639
|
exports.workflowDraftShape = workflowDraftShape;
|
|
1235
1640
|
exports.workflowStepsShape = workflowStepsShape;
|
|
1236
1641
|
exports.workflowTag = workflowTag;
|