@jini-ai/mcp 0.4.2 → 0.4.4

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -0
  3. package/dist/agent-install/chrome-devtools.d.ts +4 -0
  4. package/dist/agent-install/chrome-devtools.js +7 -0
  5. package/dist/agent-install/index.d.ts +1 -0
  6. package/dist/agent-install/index.js +1 -0
  7. package/dist/agent-install/install.js +102 -194
  8. package/dist/bin/serve.d.ts +4 -1
  9. package/dist/bin/serve.js +120 -45
  10. package/dist/client/client.js +71 -58
  11. package/dist/core/config.js +113 -149
  12. package/dist/core/tokens.js +29 -53
  13. package/dist/federation/adapter.http.js +3 -2
  14. package/dist/federation/approvals/call-confirmation.js +5 -70
  15. package/dist/federation/approvals/remembered-approval.d.ts +39 -0
  16. package/dist/federation/approvals/remembered-approval.js +98 -0
  17. package/dist/federation/approvals/revocation-reason.d.ts +9 -0
  18. package/dist/federation/approvals/revocation-reason.js +11 -0
  19. package/dist/federation/approvals/revocation.js +19 -13
  20. package/dist/federation/approvals/scope-option.d.ts +6 -0
  21. package/dist/federation/approvals/scope-option.js +6 -0
  22. package/dist/federation/bootstrap.d.ts +18 -0
  23. package/dist/federation/bootstrap.js +4 -4
  24. package/dist/federation/presets.d.ts +16 -17
  25. package/dist/federation/presets.js +35 -28
  26. package/dist/federation/stdio/adapter.stdio.js +14 -6
  27. package/dist/federation/stdio/default-connect.js +24 -27
  28. package/dist/federation/stdio/stdio-launch-resolver.d.ts +1 -1
  29. package/dist/federation/stdio/stdio-launch-resolver.js +13 -9
  30. package/dist/federation/tool-approvals.js +3 -1
  31. package/dist/federation/trust.d.ts +23 -2
  32. package/dist/federation/trust.js +69 -35
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +2 -2
  35. package/dist/server/daemon-client.d.ts +2 -0
  36. package/dist/server/daemon-client.js +86 -69
  37. package/dist/server/index.d.ts +1 -0
  38. package/dist/server/index.js +1 -0
  39. package/dist/server/resources/site-guide-resource.d.ts +15 -0
  40. package/dist/server/resources/site-guide-resource.js +25 -0
  41. package/dist/server/tool-arguments.d.ts +46 -0
  42. package/dist/server/tool-arguments.js +111 -0
  43. package/dist/server/tool-protocol.d.ts +62 -7
  44. package/dist/server/tool-protocol.js +30 -40
  45. package/dist/server/tool-server.d.ts +19 -0
  46. package/dist/server/tool-server.js +91 -19
  47. package/dist/server/tools/delegated-tool.d.ts +47 -0
  48. package/dist/server/tools/delegated-tool.js +35 -2
  49. package/dist/server/tools/tool-catalog-tools.js +30 -8
  50. package/dist/tools/ask-choice/index.d.ts +213 -14
  51. package/dist/tools/ask-choice/index.js +90 -46
  52. package/dist/tools/ask-choice/question.d.ts +5 -1
  53. package/dist/tools/ask-choice/question.js +36 -2
  54. package/dist/tools/ask-choice/schema.d.ts +196 -10
  55. package/dist/tools/ask-choice/schema.js +33 -12
  56. package/dist/tools/ask-choice/types.d.ts +8 -0
  57. package/package.json +6 -5
  58. package/dist/core/oauth.d.ts +0 -274
  59. package/dist/core/oauth.js +0 -667
  60. package/dist/core/secure-write.d.ts +0 -17
  61. package/dist/core/secure-write.js +0 -72
@@ -1,3 +1,39 @@
1
+ /**
2
+ * @module @jini-ai/mcp/server/tools/delegated-tool
3
+ *
4
+ * `execute_delegated_tool` and its read-only companion `execute_readonly_delegated_tool` — the
5
+ * MCP-callback half of gap 3's continuation transport (see
6
+ * `packages/daemon/archived provenance ledger`'s "run/chat orchestration gap 3, part 1" addition and this
7
+ * package's own dated section in `archived provenance ledger` for the rest of the spike). The swarm-consensus
8
+ * Final Recommendation asked for exactly this: "inject the already-shipped MCP host into one
9
+ * MCP-capable CLI's launch config, prove a tool round-trip through the existing
10
+ * `delegated-tool-bridge.ts`." Where `../tools/run-tools.js`'s tools proxy generic
11
+ * run-lifecycle HTTP endpoints, this tool proxies the new `POST /api/delegated-tool-calls`
12
+ * daemon route (`packages/http/src/delegated-tools.ts`), which is the one and only thing that
13
+ * calls into `createDelegatedToolBridge` (`packages/daemon/src/delegated-tool-bridge.ts`) — the
14
+ * same `ToolExecutor` deny-by-default gate every other tool-execution path in this codebase
15
+ * already uses. No parallel authorization mechanism is introduced anywhere on this path.
16
+ *
17
+ * Unlike every other tool this package ships (`run-tools.ts`'s five tools, plain static
18
+ * objects), this one is a **factory**. The MCP server subprocess a `claude` run spawns via
19
+ * `.mcp.json` (see `../../bin/serve.js`) is scoped to exactly one `runId` for its entire
20
+ * lifetime — the daemon injected that run id into the subprocess's own environment at spawn
21
+ * time, before the model ever said a word. `runId` is therefore closed over at construction
22
+ * time, not accepted as a per-call tool argument: a model has no legitimate way to pick, or
23
+ * need to know, which run it is currently inside, and letting it supply one would open a
24
+ * confused-deputy path (one run's MCP subprocess executing a tool call "as" a different run).
25
+ * `toolUseId` is likewise generated per call (`randomUUID` by default), not model-supplied — a
26
+ * model has no legitimate reason to choose its own correlation id either.
27
+ *
28
+ * TWO defs, ONE code path. `createDelegatedGateway` builds both; the factories differ only in the
29
+ * name/title/description they present and in whether the request carries `requireReadOnly: true`.
30
+ * The read-only companion exists because MCP annotations are static per tool declaration — a
31
+ * surface that promises "this only reads" can only be a second declaration, never a per-call mode —
32
+ * and it ENFORCES that promise daemon-side rather than asserting it, because a `readOnlyHint: true`
33
+ * tool that could still reach writes would launder a write past its caller's own safety gate. See
34
+ * {@link createExecuteReadonlyDelegatedToolTool} for the failure that motivated it.
35
+ */
36
+ import { type ExposedToolDescriptor } from '@jini-ai/core';
1
37
  import { type McpToolDef } from '../tool-protocol.js';
2
38
  /**
3
39
  * Request deadline for one delegated tool call, overriding `daemon-client.ts`'s 15 s default.
@@ -87,4 +123,15 @@ export declare function createExecuteDelegatedToolTool(requiredArgs: CreateExecu
87
123
  * every caller, including the ones the annotation exists to protect.
88
124
  */
89
125
  export declare function createExecuteReadonlyDelegatedToolTool(requiredArgs: CreateExecuteDelegatedToolToolRequiredArgs, options?: CreateExecuteDelegatedToolToolOptions): McpToolDef;
126
+ /**
127
+ * Project a pinned registry descriptor as one concrete MCP tool, reusing the existing gateway.
128
+ * @param requiredArgs - Trusted run scope and F0's collision-checked, exposed descriptor.
129
+ * @param options - The same correlation source and deadline used by the legacy wrappers.
130
+ * @returns A schema-preserving definition; model arguments become only the delegated input.
131
+ * @throws Error when the descriptor has no MCP object input schema.
132
+ * @complexity O(s) schema parsing time/space for s schema fields; execution is one daemon call.
133
+ */
134
+ export declare function createTypedDelegatedToolTool({ runId, tool }: CreateExecuteDelegatedToolToolRequiredArgs & {
135
+ tool: ExposedToolDescriptor;
136
+ }, options?: CreateExecuteDelegatedToolToolOptions): McpToolDef;
90
137
  //# sourceMappingURL=delegated-tool.d.ts.map
@@ -33,6 +33,8 @@
33
33
  * tool that could still reach writes would launder a write past its caller's own safety gate. See
34
34
  * {@link createExecuteReadonlyDelegatedToolTool} for the failure that motivated it.
35
35
  */
36
+ import { batchOutcomeFor, isReadOnlyTool } from '@jini-ai/core';
37
+ import { ToolSchema } from '@modelcontextprotocol/sdk/types.js';
36
38
  import { randomUUID } from 'node:crypto';
37
39
  import { postDaemonJson } from '../daemon-client.js';
38
40
  import { daemonCallOptions, requireString } from '../tool-protocol.js';
@@ -42,6 +44,7 @@ function isRecord(value) {
42
44
  /**
43
45
  * Unwraps a completed delegated call's `{executionId, status, output, ...}` envelope down to just
44
46
  * `output` when — and only when — `output` is itself an MCP content envelope (`{content: [...]}`).
47
+ * Batches expose partial_failure/error and isError while retaining every item.
45
48
  * Every other shape (a non-completed status, a plain JSON `output` like a string or a list, no
46
49
  * `output` at all) is returned byte-identical to `result` — this is an additive unwrap, not a new
47
50
  * general response shape, so the existing "surfaces the result envelope unchanged" contract for an
@@ -57,9 +60,14 @@ function isRecord(value) {
57
60
  * block the bridge already preserved.
58
61
  */
59
62
  function unwrapMcpContentEnvelope(result) {
60
- if (!isRecord(result) || result['status'] !== 'completed')
63
+ if (!isRecord(result))
61
64
  return result;
62
65
  const output = result['output'];
66
+ const outcome = batchOutcomeFor({ output });
67
+ if (outcome === 'partial_failure' || outcome === 'error')
68
+ return { ...result, status: outcome, outcome, isError: true };
69
+ if (result['status'] !== 'completed')
70
+ return result;
63
71
  if (!isRecord(output) || !Array.isArray(output['content']))
64
72
  return result;
65
73
  return output;
@@ -109,7 +117,8 @@ function createDelegatedGateway(variant, { runId }, options) {
109
117
  : DEFAULT_DELEGATED_TOOL_TIMEOUT_MS;
110
118
  return {
111
119
  name: variant.name,
112
- description: variant.description,
120
+ description: `${variant.description} Put the tool's own arguments inside input; non-conflicting top-level tool arguments are moved there automatically.`,
121
+ argumentNormalization: { foldIntoInput: true },
113
122
  inputSchema: {
114
123
  type: 'object',
115
124
  properties: {
@@ -223,4 +232,28 @@ export function createExecuteReadonlyDelegatedToolTool(requiredArgs, options = {
223
232
  requireReadOnly: true,
224
233
  }, requiredArgs, options);
225
234
  }
235
+ /**
236
+ * Project a pinned registry descriptor as one concrete MCP tool, reusing the existing gateway.
237
+ * @param requiredArgs - Trusted run scope and F0's collision-checked, exposed descriptor.
238
+ * @param options - The same correlation source and deadline used by the legacy wrappers.
239
+ * @returns A schema-preserving definition; model arguments become only the delegated input.
240
+ * @throws Error when the descriptor has no MCP object input schema.
241
+ * @complexity O(s) schema parsing time/space for s schema fields; execution is one daemon call.
242
+ */
243
+ export function createTypedDelegatedToolTool({ runId, tool }, options = {}) {
244
+ const { descriptor, wireName } = tool;
245
+ const inputSchema = ToolSchema.shape.inputSchema.parse(descriptor.inputSchema);
246
+ const readOnly = isReadOnlyTool({ descriptor });
247
+ const gateway = readOnly
248
+ ? createExecuteReadonlyDelegatedToolTool({ runId }, options)
249
+ : createExecuteDelegatedToolTool({ runId }, options);
250
+ return {
251
+ name: wireName,
252
+ description: descriptor.description ?? descriptor.id,
253
+ // Parse to check the MCP boundary, but publish the original schema, including extensions.
254
+ inputSchema: descriptor.inputSchema,
255
+ annotations: { ...gateway.annotations, title: descriptor.id },
256
+ handler: ({ args, ctx }) => gateway.handler({ args: { toolId: descriptor.id, input: args }, ctx }),
257
+ };
258
+ }
226
259
  //# sourceMappingURL=delegated-tool.js.map
@@ -4,24 +4,40 @@
4
4
  * `search_tools` / `describe_tool` — the discovery half of `ai-control-plane.md` §29 /
5
5
  * `PROP-tool-catalog-discovery-2026-07-26.md` §6.1, proxying `@jini-ai/http-kit`'s
6
6
  * `GET /api/tools/search` / `GET /api/tools/:id` exactly as `run-tools.ts`'s tools proxy
7
- * `runs.ts` — no separate authorization mechanism, no caching, no state.
7
+ * `runs.ts` — no separate authorization mechanism or catalog cache.
8
8
  *
9
9
  * v0 scope: no principal scoping, no versioning, no schema narrowing. Execution is unchanged —
10
- * a caller still runs a discovered id through `execute_delegated_tool`
11
- * (`../tools/delegated-tool.js`), which is the only thing that ever reaches `ToolExecutor`.
10
+ * a caller runs a discovered id through the delegated gateway (`../tools/delegated-tool.js`),
11
+ * either with its legacy wrapper or a concrete definition published after a successful describe.
12
+ * Both reach the same `ToolExecutor`.
12
13
  * Finding a tool id here grants nothing (`PROP` §6.3).
13
14
  */
14
15
  import { getDaemonJson } from '../daemon-client.js';
16
+ import { isRecord } from '../tool-arguments.js';
15
17
  import { daemonCallOptions, requireString } from '../tool-protocol.js';
16
18
  const READ_ANNOTATIONS = {
17
19
  readOnlyHint: true,
18
20
  idempotentHint: true,
19
21
  openWorldHint: false,
20
22
  };
23
+ /** Validate the daemon's identity before publishing; discovery cannot substitute another tool.
24
+ * Unknown risk remains unclassified and therefore cannot claim a read-only annotation.
25
+ * @complexity O(1); no effects. The schema is checked by the publication boundary.
26
+ */
27
+ function describedDescriptor({ id, data }) {
28
+ if (!isRecord(data) || data.id !== id)
29
+ throw new Error('daemon returned a mismatched tool descriptor');
30
+ return {
31
+ id,
32
+ ...(typeof data.description === 'string' ? { description: data.description } : {}),
33
+ ...(typeof data.readOnly === 'boolean' ? { readOnly: data.readOnly } : {}),
34
+ ...(data.inputSchema === undefined ? {} : { inputSchema: data.inputSchema }),
35
+ };
36
+ }
21
37
  /** `search_tools` -> `GET /api/tools/search` (`packages/http/src/tool-catalog.ts`'s `toolCatalogSearchRoute`). */
22
38
  export const searchToolsTool = {
23
39
  name: 'search_tools',
24
- description: 'Search the durable tool catalog. Returns ranked {id, description, source, score} candidates only — no input schemas, so this stays cheap to call broadly. Call describe_tool on the 1-3 candidates that look right before calling execute_delegated_tool. If nothing in the results fits, re-search with a higher limit or different phrasing rather than assuming the tool does not exist.',
40
+ description: 'Search the durable tool catalog. Returns ranked {id, description, source, score, readOnly, sideEffects} candidates only — no input schemas, so this stays cheap to call broadly. Call describe_tool on the 1-3 candidates that look right before calling execute_delegated_tool. If nothing in the results fits, re-search with a higher limit or different phrasing rather than assuming the tool does not exist.',
25
41
  inputSchema: {
26
42
  type: 'object',
27
43
  properties: {
@@ -80,19 +96,25 @@ export const searchToolsTool = {
80
96
  /** `describe_tool` -> `GET /api/tools/:id` (`packages/http/src/tool-catalog.ts`'s `toolCatalogDescribeRoute`). */
81
97
  export const describeToolTool = {
82
98
  name: 'describe_tool',
83
- description: 'Get the full descriptor for one tool id found via search_tools — description and input schema. Paid for only on the candidates that survive search, per the tool catalog\'s staged-discovery design.',
99
+ description: 'Get the full descriptor for one tool id found via search_tools — description, input schema, readOnly and sideEffects. When dynamic publication is enabled, this also exposes that operation as a concrete tool in tools/list; call it with its own arguments. Legacy fallback: use execute_readonly_delegated_tool only when readOnly is true. Supply id (aliases: toolId or name). Paid for only on the candidates that survive search, per the tool catalog\'s staged-discovery design.',
84
100
  inputSchema: {
85
101
  type: 'object',
86
102
  properties: {
87
- id: { type: 'string', description: 'Tool id returned by search_tools. Required.' },
103
+ id: { type: 'string', description: 'Tool id returned by search_tools. Supply id, toolId, or name.' },
104
+ toolId: { type: 'string', description: 'Alias of id, matching the delegated-execute wrappers.' },
105
+ name: { type: 'string', description: 'Alias of id.' },
88
106
  },
89
- required: ['id'],
107
+ anyOf: [{ required: ['id'] }, { required: ['toolId'] }, { required: ['name'] }],
90
108
  additionalProperties: false,
91
109
  },
110
+ argumentNormalization: { aliases: { toolId: 'id', name: 'id' } },
92
111
  annotations: { ...READ_ANNOTATIONS, title: 'Describe a tool' },
93
112
  handler: async ({ args, ctx }) => {
94
113
  const id = requireString({ value: args.id, name: 'id' });
95
- return getDaemonJson({ baseUrl: ctx.baseUrl, route: `/api/tools/${encodeURIComponent(id)}` }, daemonCallOptions({ ctx }));
114
+ const data = await getDaemonJson({ baseUrl: ctx.baseUrl, route: `/api/tools/${encodeURIComponent(id)}` }, daemonCallOptions({ ctx }));
115
+ if (ctx.publishTool)
116
+ await ctx.publishTool({ descriptor: describedDescriptor({ id, data }) });
117
+ return data;
96
118
  },
97
119
  };
98
120
  /** Both tool-catalog discovery tools, ready to pass as `createMcpToolServer`'s `tools` option. */
@@ -1,9 +1,23 @@
1
- import type { AskChoiceCall, AskChoiceExchangeStore, AskChoiceMessages, AskChoicePendingStore, AskChoicePolicy, AskChoicePresentation } from './types.js';
1
+ import type { AskChoiceCall, AskChoiceExchange, AskChoiceExchangeStore, AskChoiceMessages, AskChoicePendingStore, AskChoicePolicy, AskChoicePresentation, AskChoiceQuestion } from './types.js';
2
2
  export type * from './types.js';
3
3
  export { ASK_CHOICE_INPUT_SCHEMA } from './schema.js';
4
4
  export { createAskChoiceAnswerTicketStore } from './pending.js';
5
5
  /** Existing callback wire keys; the tool's identity is supplied by the host. */
6
6
  export declare const ASK_CHOICE_ANSWER_TICKET_PARAM = "__askChoiceAnswerTicket";
7
+ /** Run a trusted host question through the same abort, delivery, validation and cleanup lifecycle
8
+ * as the model-facing tool. The host owns presentation and action policy, never exchange mechanics.
9
+ * @returns Classified human answer, including an offered action when configured.
10
+ * @complexity O(n) offered options and selections.
11
+ */
12
+ export declare function askChoice({ exchange, question, toolId, principalId, presentation, signal, messages }: {
13
+ exchange: AskChoiceExchange;
14
+ question: AskChoiceQuestion;
15
+ toolId: string;
16
+ principalId: string;
17
+ presentation: Pick<AskChoicePresentation, 'render'>;
18
+ signal: AbortSignal;
19
+ messages: AskChoiceMessages;
20
+ }, _options?: {}): Promise<Record<string, unknown>>;
7
21
  /** Creates a choice tool without a CMS, renderer, permission system, clock, or global store.
8
22
  * The host composes this descriptor/handler into its registry and mounts callbacks against the
9
23
  * same exchange and pending stores. Policy and presentation are required, with no product defaults.
@@ -29,15 +43,50 @@ export declare function createAskChoiceTool({ toolId, description, permission, p
29
43
  inputSchema: {
30
44
  readonly type: "object";
31
45
  readonly additionalProperties: false;
32
- readonly required: readonly ["title"];
33
46
  readonly anyOf: readonly [{
34
- readonly required: readonly ["singleSelect"];
47
+ readonly required: readonly ["question", "options"];
35
48
  }, {
36
- readonly required: readonly ["multiSelect"];
49
+ readonly required: readonly ["title", "singleSelect"];
50
+ }, {
51
+ readonly required: readonly ["title", "multiSelect"];
37
52
  }];
38
53
  readonly properties: {
54
+ readonly question: {
55
+ readonly type: "string";
56
+ readonly minLength: 1;
57
+ readonly description: "The question to ask the human.";
58
+ };
59
+ readonly options: {
60
+ readonly type: "array";
61
+ readonly minItems: 1;
62
+ readonly items: {
63
+ readonly type: "object";
64
+ readonly additionalProperties: false;
65
+ readonly required: readonly ["label"];
66
+ readonly properties: {
67
+ readonly label: {
68
+ readonly type: "string";
69
+ readonly minLength: 1;
70
+ };
71
+ readonly value: {
72
+ readonly type: "string";
73
+ readonly minLength: 1;
74
+ readonly description: "Defaults to label when omitted.";
75
+ };
76
+ readonly description: {
77
+ readonly type: "string";
78
+ };
79
+ };
80
+ };
81
+ };
82
+ readonly multiple: {
83
+ readonly type: "boolean";
84
+ readonly description: "Allow several selections. Defaults to false.";
85
+ };
39
86
  readonly title: {
40
87
  readonly type: "string";
88
+ readonly minLength: 1;
89
+ readonly description: "Legacy alias: title with singleSelect or multiSelect.";
41
90
  };
42
91
  readonly description: {
43
92
  readonly type: "string";
@@ -46,25 +95,33 @@ export declare function createAskChoiceTool({ toolId, description, permission, p
46
95
  readonly type: "string";
47
96
  };
48
97
  readonly singleSelect: {
98
+ readonly description: "Legacy nested single-choice alias.";
49
99
  readonly type: "object";
50
100
  readonly additionalProperties: false;
51
101
  readonly required: readonly ["label", "options"];
52
102
  readonly properties: {
53
103
  readonly label: {
54
104
  readonly type: "string";
105
+ readonly minLength: 1;
55
106
  };
56
107
  readonly options: {
57
108
  readonly type: "array";
58
109
  readonly minItems: 1;
59
110
  readonly items: {
111
+ readonly required: readonly ["value", "label"];
60
112
  readonly type: "object";
61
113
  readonly additionalProperties: false;
62
- readonly required: readonly ["value", "label"];
63
114
  readonly properties: {
115
+ readonly label: {
116
+ readonly type: "string";
117
+ readonly minLength: 1;
118
+ };
64
119
  readonly value: {
65
120
  readonly type: "string";
121
+ readonly minLength: 1;
122
+ readonly description: "Defaults to label when omitted.";
66
123
  };
67
- readonly label: {
124
+ readonly description: {
68
125
  readonly type: "string";
69
126
  };
70
127
  };
@@ -73,25 +130,33 @@ export declare function createAskChoiceTool({ toolId, description, permission, p
73
130
  };
74
131
  };
75
132
  readonly multiSelect: {
133
+ readonly description: "Legacy nested multiple-choice alias.";
76
134
  readonly properties: {
77
135
  readonly hint: {
78
136
  readonly type: "string";
79
137
  };
80
138
  readonly label: {
81
139
  readonly type: "string";
140
+ readonly minLength: 1;
82
141
  };
83
142
  readonly options: {
84
143
  readonly type: "array";
85
144
  readonly minItems: 1;
86
145
  readonly items: {
146
+ readonly required: readonly ["value", "label"];
87
147
  readonly type: "object";
88
148
  readonly additionalProperties: false;
89
- readonly required: readonly ["value", "label"];
90
149
  readonly properties: {
150
+ readonly label: {
151
+ readonly type: "string";
152
+ readonly minLength: 1;
153
+ };
91
154
  readonly value: {
92
155
  readonly type: "string";
156
+ readonly minLength: 1;
157
+ readonly description: "Defaults to label when omitted.";
93
158
  };
94
- readonly label: {
159
+ readonly description: {
95
160
  readonly type: "string";
96
161
  };
97
162
  };
@@ -102,19 +167,153 @@ export declare function createAskChoiceTool({ toolId, description, permission, p
102
167
  readonly additionalProperties: false;
103
168
  readonly required: readonly ["label", "options"];
104
169
  };
105
- readonly choice: {
170
+ };
171
+ };
172
+ };
173
+ invocationSchema: {
174
+ readonly anyOf: readonly [{
175
+ readonly required: readonly ["question", "options"];
176
+ }, {
177
+ readonly required: readonly ["title", "singleSelect"];
178
+ }, {
179
+ readonly required: readonly ["title", "multiSelect"];
180
+ }, {
181
+ readonly required: readonly ["choice", "__askChoiceAnswerTicket"];
182
+ }, {
183
+ readonly required: readonly ["selections", "__askChoiceAnswerTicket"];
184
+ }];
185
+ readonly properties: {
186
+ readonly choice: {
187
+ readonly type: "string";
188
+ };
189
+ readonly selections: {
190
+ readonly type: "array";
191
+ readonly items: {
106
192
  readonly type: "string";
107
- readonly description: "Set by the rendered form; never by the model.";
108
193
  };
109
- readonly selections: {
110
- readonly type: "array";
111
- readonly items: {
194
+ };
195
+ readonly __askChoiceAnswerTicket: {
196
+ readonly type: "string";
197
+ };
198
+ readonly question: {
199
+ readonly type: "string";
200
+ readonly minLength: 1;
201
+ readonly description: "The question to ask the human.";
202
+ };
203
+ readonly options: {
204
+ readonly type: "array";
205
+ readonly minItems: 1;
206
+ readonly items: {
207
+ readonly type: "object";
208
+ readonly additionalProperties: false;
209
+ readonly required: readonly ["label"];
210
+ readonly properties: {
211
+ readonly label: {
212
+ readonly type: "string";
213
+ readonly minLength: 1;
214
+ };
215
+ readonly value: {
216
+ readonly type: "string";
217
+ readonly minLength: 1;
218
+ readonly description: "Defaults to label when omitted.";
219
+ };
220
+ readonly description: {
221
+ readonly type: "string";
222
+ };
223
+ };
224
+ };
225
+ };
226
+ readonly multiple: {
227
+ readonly type: "boolean";
228
+ readonly description: "Allow several selections. Defaults to false.";
229
+ };
230
+ readonly title: {
231
+ readonly type: "string";
232
+ readonly minLength: 1;
233
+ readonly description: "Legacy alias: title with singleSelect or multiSelect.";
234
+ };
235
+ readonly description: {
236
+ readonly type: "string";
237
+ };
238
+ readonly submitLabel: {
239
+ readonly type: "string";
240
+ };
241
+ readonly singleSelect: {
242
+ readonly description: "Legacy nested single-choice alias.";
243
+ readonly type: "object";
244
+ readonly additionalProperties: false;
245
+ readonly required: readonly ["label", "options"];
246
+ readonly properties: {
247
+ readonly label: {
112
248
  readonly type: "string";
249
+ readonly minLength: 1;
250
+ };
251
+ readonly options: {
252
+ readonly type: "array";
253
+ readonly minItems: 1;
254
+ readonly items: {
255
+ readonly required: readonly ["value", "label"];
256
+ readonly type: "object";
257
+ readonly additionalProperties: false;
258
+ readonly properties: {
259
+ readonly label: {
260
+ readonly type: "string";
261
+ readonly minLength: 1;
262
+ };
263
+ readonly value: {
264
+ readonly type: "string";
265
+ readonly minLength: 1;
266
+ readonly description: "Defaults to label when omitted.";
267
+ };
268
+ readonly description: {
269
+ readonly type: "string";
270
+ };
271
+ };
272
+ };
273
+ };
274
+ };
275
+ };
276
+ readonly multiSelect: {
277
+ readonly description: "Legacy nested multiple-choice alias.";
278
+ readonly properties: {
279
+ readonly hint: {
280
+ readonly type: "string";
281
+ };
282
+ readonly label: {
283
+ readonly type: "string";
284
+ readonly minLength: 1;
285
+ };
286
+ readonly options: {
287
+ readonly type: "array";
288
+ readonly minItems: 1;
289
+ readonly items: {
290
+ readonly required: readonly ["value", "label"];
291
+ readonly type: "object";
292
+ readonly additionalProperties: false;
293
+ readonly properties: {
294
+ readonly label: {
295
+ readonly type: "string";
296
+ readonly minLength: 1;
297
+ };
298
+ readonly value: {
299
+ readonly type: "string";
300
+ readonly minLength: 1;
301
+ readonly description: "Defaults to label when omitted.";
302
+ };
303
+ readonly description: {
304
+ readonly type: "string";
305
+ };
306
+ };
307
+ };
113
308
  };
114
- readonly description: "Set by the rendered form; never by the model.";
115
309
  };
310
+ readonly type: "object";
311
+ readonly additionalProperties: false;
312
+ readonly required: readonly ["label", "options"];
116
313
  };
117
314
  };
315
+ readonly type: "object";
316
+ readonly additionalProperties: false;
118
317
  };
119
318
  handler({ ctx }: {
120
319
  ctx: AskChoiceCall;