@procedures/client 0.7.0 → 0.8.1
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/CHANGELOG.md +107 -0
- package/README.md +115 -11
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts +4 -0
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +11 -1
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/client/types.d.ts +19 -0
- package/dist/adapters/http/client/types.d.ts.map +1 -1
- package/dist/adapters/http/server/cors.d.ts +64 -0
- package/dist/adapters/http/server/cors.d.ts.map +1 -0
- package/dist/adapters/http/server/cors.js +113 -0
- package/dist/adapters/http/server/cors.js.map +1 -0
- package/dist/adapters/http/server/handler.d.ts +136 -0
- package/dist/adapters/http/server/handler.d.ts.map +1 -0
- package/dist/adapters/http/server/handler.js +495 -0
- package/dist/adapters/http/server/handler.js.map +1 -0
- package/dist/adapters/http/server/strategies.d.ts +14 -5
- package/dist/adapters/http/server/strategies.d.ts.map +1 -1
- package/dist/adapters/http/server/strategies.js.map +1 -1
- package/dist/adapters/http/server/transport.d.ts +36 -46
- package/dist/adapters/http/server/transport.d.ts.map +1 -1
- package/dist/adapters/http/server/transport.js +58 -433
- package/dist/adapters/http/server/transport.js.map +1 -1
- package/dist/adapters/http/server/types.d.ts +13 -0
- package/dist/adapters/http/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.d.ts +7 -3
- package/dist/adapters/websocket/client/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/client/transport.js +9 -3
- package/dist/adapters/websocket/client/transport.js.map +1 -1
- package/dist/adapters/websocket/server/transport.d.ts +56 -1
- package/dist/adapters/websocket/server/transport.d.ts.map +1 -1
- package/dist/adapters/websocket/server/transport.js +174 -17
- package/dist/adapters/websocket/server/transport.js.map +1 -1
- package/dist/adapters/websocket/server/types.d.ts +10 -3
- package/dist/adapters/websocket/server/types.d.ts.map +1 -1
- package/dist/adapters/websocket/server/upgrade-router.d.ts +28 -0
- package/dist/adapters/websocket/server/upgrade-router.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade-router.js +62 -0
- package/dist/adapters/websocket/server/upgrade-router.js.map +1 -0
- package/dist/adapters/websocket/server/upgrade.d.ts +68 -0
- package/dist/adapters/websocket/server/upgrade.d.ts.map +1 -0
- package/dist/adapters/websocket/server/upgrade.js +49 -0
- package/dist/adapters/websocket/server/upgrade.js.map +1 -0
- package/dist/adapters/websocket/shared/rpc.d.ts +2 -0
- package/dist/adapters/websocket/shared/rpc.d.ts.map +1 -1
- package/dist/adapters/websocket/shared/rpc.js +8 -2
- package/dist/adapters/websocket/shared/rpc.js.map +1 -1
- package/dist/browser.d.ts +14 -6
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +12 -3
- package/dist/browser.js.map +1 -1
- package/dist/client/client.d.ts +43 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +69 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +1 -0
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/middleware/retry.d.ts +4 -0
- package/dist/client/middleware/retry.d.ts.map +1 -1
- package/dist/client/middleware/retry.js +86 -78
- package/dist/client/middleware/retry.js.map +1 -1
- package/dist/events/bus.d.ts +32 -14
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +119 -65
- package/dist/events/bus.js.map +1 -1
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/events/publisher.d.ts +14 -0
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +62 -10
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/remote.d.ts +80 -18
- package/dist/events/remote.d.ts.map +1 -1
- package/dist/events/remote.js +448 -58
- package/dist/events/remote.js.map +1 -1
- package/dist/events/rows.d.ts +31 -0
- package/dist/events/rows.d.ts.map +1 -0
- package/dist/events/rows.js +39 -0
- package/dist/events/rows.js.map +1 -0
- package/dist/events/types.d.ts +13 -2
- package/dist/events/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +14 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +9 -0
- package/dist/node.js.map +1 -1
- package/dist/procedures/caller.d.ts +33 -0
- package/dist/procedures/caller.d.ts.map +1 -0
- package/dist/procedures/caller.js +27 -0
- package/dist/procedures/caller.js.map +1 -0
- package/dist/procedures/capability.d.ts +6 -0
- package/dist/procedures/capability.d.ts.map +1 -1
- package/dist/procedures/capability.js +25 -0
- package/dist/procedures/capability.js.map +1 -1
- package/dist/procedures/collection/changes.d.ts +94 -0
- package/dist/procedures/collection/changes.d.ts.map +1 -0
- package/dist/procedures/collection/changes.js +182 -0
- package/dist/procedures/collection/changes.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +8 -3
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +6 -1
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/procedures.d.ts +39 -2
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +131 -8
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/query.d.ts +246 -0
- package/dist/procedures/collection/query.d.ts.map +1 -0
- package/dist/procedures/collection/query.js +854 -0
- package/dist/procedures/collection/query.js.map +1 -0
- package/dist/procedures/collection/schemas.d.ts +59 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +172 -6
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +64 -6
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/collection/storage.js +23 -0
- package/dist/procedures/collection/storage.js.map +1 -1
- package/dist/procedures/elicit.d.ts +190 -0
- package/dist/procedures/elicit.d.ts.map +1 -0
- package/dist/procedures/elicit.js +127 -0
- package/dist/procedures/elicit.js.map +1 -0
- package/dist/procedures/hints.d.ts +43 -0
- package/dist/procedures/hints.d.ts.map +1 -0
- package/dist/procedures/hints.js +74 -0
- package/dist/procedures/hints.js.map +1 -0
- package/dist/procedures/index.d.ts +12 -3
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +7 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/procedures/invoke.d.ts +20 -0
- package/dist/procedures/invoke.d.ts.map +1 -1
- package/dist/procedures/invoke.js +16 -0
- package/dist/procedures/invoke.js.map +1 -1
- package/dist/procedures/progress.d.ts +24 -0
- package/dist/procedures/progress.d.ts.map +1 -0
- package/dist/procedures/progress.js +11 -0
- package/dist/procedures/progress.js.map +1 -0
- package/dist/procedures/registry.d.ts.map +1 -1
- package/dist/procedures/registry.js +5 -0
- package/dist/procedures/registry.js.map +1 -1
- package/dist/procedures/types.d.ts +92 -0
- package/dist/procedures/types.d.ts.map +1 -1
- package/dist/procedures/types.js.map +1 -1
- package/dist/procedures/untrusted.d.ts +28 -0
- package/dist/procedures/untrusted.d.ts.map +1 -0
- package/dist/procedures/untrusted.js +32 -0
- package/dist/procedures/untrusted.js.map +1 -0
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -0
- package/dist/protocol.js.map +1 -1
- package/dist/server/browser-guard.d.ts +12 -2
- package/dist/server/browser-guard.d.ts.map +1 -1
- package/dist/server/browser-guard.js +43 -6
- package/dist/server/browser-guard.js.map +1 -1
- package/dist/server/host-policy.d.ts +10 -1
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +5 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/procedure-server.d.ts +75 -9
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +151 -17
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +51 -4
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +116 -5
- package/dist/server/request-auth.js.map +1 -1
- package/dist/server/types.d.ts +73 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/dist/timers.d.ts +5 -0
- package/dist/timers.d.ts.map +1 -1
- package/dist/timers.js +3 -1
- package/dist/timers.js.map +1 -1
- package/package.json +5 -1
- package/src/adapters/http/client/transport.ts +11 -1
- package/src/adapters/http/client/types.ts +22 -0
- package/src/adapters/http/server/cors.ts +144 -0
- package/src/adapters/http/server/handler.ts +624 -0
- package/src/adapters/http/server/strategies.ts +16 -6
- package/src/adapters/http/server/transport.ts +66 -479
- package/src/adapters/http/server/types.ts +15 -0
- package/src/adapters/websocket/client/transport.ts +9 -3
- package/src/adapters/websocket/server/transport.ts +189 -18
- package/src/adapters/websocket/server/types.ts +11 -3
- package/src/adapters/websocket/server/upgrade-router.ts +77 -0
- package/src/adapters/websocket/server/upgrade.ts +88 -0
- package/src/adapters/websocket/shared/rpc.ts +9 -2
- package/src/browser.ts +85 -2
- package/src/client/client.ts +83 -1
- package/src/client/errors/registry.ts +1 -0
- package/src/client/middleware/retry.ts +89 -78
- package/src/events/bus.ts +141 -73
- package/src/events/index.ts +3 -0
- package/src/events/publisher.ts +71 -10
- package/src/events/remote.ts +575 -73
- package/src/events/rows.ts +48 -0
- package/src/events/types.ts +15 -2
- package/src/index.ts +1 -1
- package/src/node.ts +21 -0
- package/src/procedures/caller.ts +45 -0
- package/src/procedures/capability.ts +21 -0
- package/src/procedures/collection/changes.ts +245 -0
- package/src/procedures/collection/index.ts +56 -1
- package/src/procedures/collection/procedures.ts +185 -7
- package/src/procedures/collection/query.ts +1009 -0
- package/src/procedures/collection/schemas.ts +233 -7
- package/src/procedures/collection/storage.ts +84 -6
- package/src/procedures/elicit.ts +320 -0
- package/src/procedures/hints.ts +91 -0
- package/src/procedures/index.ts +82 -2
- package/src/procedures/invoke.ts +31 -0
- package/src/procedures/progress.ts +25 -0
- package/src/procedures/registry.ts +4 -0
- package/src/procedures/types.ts +102 -0
- package/src/procedures/untrusted.ts +50 -0
- package/src/protocol.ts +3 -1
- package/src/server/browser-guard.ts +48 -6
- package/src/server/host-policy.ts +13 -1
- package/src/server/index.ts +3 -0
- package/src/server/procedure-server.ts +185 -20
- package/src/server/request-auth.ts +128 -6
- package/src/server/types.ts +82 -5
- package/src/timers.ts +8 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The questions of a procedure to the person at the caller (CLI-12).
|
|
3
|
+
*
|
|
4
|
+
* A face of a host (MCP, a terminal) can ask the person at the caller for input or for a
|
|
5
|
+
* confirmation during a call. The face puts an `Elicit` function on the request, and the
|
|
6
|
+
* procedure gets it as `ctx.elicit`. A face that cannot ask gives no function. Then the helpers
|
|
7
|
+
* `elicitInput` and `elicitConfirmation` give the action `unavailable` with a reason, so a
|
|
8
|
+
* procedure always gets a clear answer.
|
|
9
|
+
*
|
|
10
|
+
* The operator can answer some questions before the calls (`ElicitationPolicy.operatorConsent`):
|
|
11
|
+
* a consent that the operator gives when the server starts. The person comes first: the operator
|
|
12
|
+
* answers only when the face cannot ask the person. When the face can ask, and the question
|
|
13
|
+
* fails (a timeout, a lost connection), the answer is `unavailable`: the silence of a person is
|
|
14
|
+
* not a consent.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Duration } from "../duration.js";
|
|
18
|
+
import type { ProcedureContext } from "./types.js";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// The fields of a form
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
/** The texts of a field that the face shows to the person. */
|
|
25
|
+
export interface ElicitFieldBase {
|
|
26
|
+
/** The short name of the field. */
|
|
27
|
+
readonly title?: string | undefined;
|
|
28
|
+
/** The text that tells what the field is for. */
|
|
29
|
+
readonly description?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A field of free text. */
|
|
33
|
+
export interface ElicitTextField extends ElicitFieldBase {
|
|
34
|
+
readonly type: "string";
|
|
35
|
+
readonly enum?: undefined;
|
|
36
|
+
readonly minLength?: number | undefined;
|
|
37
|
+
readonly maxLength?: number | undefined;
|
|
38
|
+
readonly format?: "email" | "uri" | "date" | "date-time" | undefined;
|
|
39
|
+
readonly default?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A field of a number. */
|
|
43
|
+
export interface ElicitNumberField extends ElicitFieldBase {
|
|
44
|
+
readonly type: "number" | "integer";
|
|
45
|
+
readonly minimum?: number | undefined;
|
|
46
|
+
readonly maximum?: number | undefined;
|
|
47
|
+
readonly default?: number | undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A field of yes or no. */
|
|
51
|
+
export interface ElicitBooleanField extends ElicitFieldBase {
|
|
52
|
+
readonly type: "boolean";
|
|
53
|
+
readonly default?: boolean | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A field of one choice from a list. `enumNames` gives the text of each choice. */
|
|
57
|
+
export interface ElicitChoiceField extends ElicitFieldBase {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly enum: readonly string[];
|
|
60
|
+
readonly enumNames?: readonly string[] | undefined;
|
|
61
|
+
readonly default?: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A field of some choices from a list. */
|
|
65
|
+
export interface ElicitChoicesField extends ElicitFieldBase {
|
|
66
|
+
readonly type: "array";
|
|
67
|
+
readonly items: { readonly type: "string"; readonly enum: readonly string[] };
|
|
68
|
+
readonly minItems?: number | undefined;
|
|
69
|
+
readonly maxItems?: number | undefined;
|
|
70
|
+
readonly default?: readonly string[] | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A field of a form: the primitive fields of MCP elicitation. A form has no nested fields. */
|
|
74
|
+
export type ElicitField = ElicitTextField | ElicitNumberField | ElicitBooleanField | ElicitChoiceField | ElicitChoicesField;
|
|
75
|
+
|
|
76
|
+
/** The value of a field in an answer. */
|
|
77
|
+
export type ElicitValue = string | number | boolean | readonly string[];
|
|
78
|
+
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Questions and answers
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
/** A question with a form: the person fills in the fields. */
|
|
84
|
+
export interface ElicitFormRequest {
|
|
85
|
+
readonly mode?: "form" | undefined;
|
|
86
|
+
/** The question, as the face shows it to the person. */
|
|
87
|
+
readonly message: string;
|
|
88
|
+
/** The fields of the answer, by name. */
|
|
89
|
+
readonly fields: Readonly<Record<string, ElicitField>>;
|
|
90
|
+
/** The names of the fields that an accepted answer must have. */
|
|
91
|
+
readonly required?: readonly string[] | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* The key of the consent that the question asks for, for example `"user-provided"`. With a
|
|
94
|
+
* key, the operator can give the consent before the calls (`ElicitationPolicy.operatorConsent`).
|
|
95
|
+
* Without a key, only the person can answer.
|
|
96
|
+
*/
|
|
97
|
+
readonly consent?: string | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A question with a URL: the person opens the URL and completes a step outside the face (a
|
|
102
|
+
* sign-in, a payment). The answer tells only if the person agreed to open it.
|
|
103
|
+
*/
|
|
104
|
+
export interface ElicitUrlRequest {
|
|
105
|
+
readonly mode: "url";
|
|
106
|
+
readonly message: string;
|
|
107
|
+
readonly url: string;
|
|
108
|
+
/** The id of the question. The default is a random id. */
|
|
109
|
+
readonly id?: string | undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A question to the person at the caller. */
|
|
113
|
+
export type ElicitRequest = ElicitFormRequest | ElicitUrlRequest;
|
|
114
|
+
|
|
115
|
+
/** The kind of a question: a form, or a URL. */
|
|
116
|
+
export type ElicitMode = "form" | "url";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* What the answer of a question is:
|
|
120
|
+
* - `accept`: the person, or the operator, gave the answer.
|
|
121
|
+
* - `decline`: the person said no.
|
|
122
|
+
* - `cancel`: the person closed the question without a choice.
|
|
123
|
+
* - `unavailable`: no one can answer. The face cannot ask the person, and no consent of the
|
|
124
|
+
* operator applies. `reason` tells why.
|
|
125
|
+
*/
|
|
126
|
+
export type ElicitAction = "accept" | "decline" | "cancel" | "unavailable";
|
|
127
|
+
|
|
128
|
+
/** The answer of a question. */
|
|
129
|
+
export interface ElicitResult {
|
|
130
|
+
readonly action: ElicitAction;
|
|
131
|
+
/** The values of the fields, for `accept`. */
|
|
132
|
+
readonly content?: Readonly<Record<string, ElicitValue>> | undefined;
|
|
133
|
+
/** Who answered: the person, or the operator (a consent of the start). Absent for `unavailable`. */
|
|
134
|
+
readonly by?: "person" | "operator" | undefined;
|
|
135
|
+
/** Why no one answered, for `unavailable`. */
|
|
136
|
+
readonly reason?: string | undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The options of one question. */
|
|
140
|
+
export interface ElicitOptions {
|
|
141
|
+
/** When the signal aborts, the question stops. The default is the signal of the call. */
|
|
142
|
+
readonly signal?: AbortSignal | undefined;
|
|
143
|
+
/** The time that the face waits for the answer. The face has a default (MCP: 5 minutes). */
|
|
144
|
+
readonly timeout?: Duration | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The function that asks the person at the caller (`ctx.elicit`). `modes` tells which kinds of
|
|
149
|
+
* question the face can ask the person. An empty list: only the operator answers.
|
|
150
|
+
*/
|
|
151
|
+
export interface Elicit {
|
|
152
|
+
(request: ElicitRequest, options?: ElicitOptions): Promise<ElicitResult>;
|
|
153
|
+
readonly modes: readonly ElicitMode[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** This function makes an `Elicit` function from the modes of a face and its ask function. */
|
|
157
|
+
export function createElicit(
|
|
158
|
+
modes: readonly ElicitMode[],
|
|
159
|
+
ask: (request: ElicitRequest, options: ElicitOptions) => Promise<ElicitResult>,
|
|
160
|
+
): Elicit {
|
|
161
|
+
const elicit = (request: ElicitRequest, options?: ElicitOptions): Promise<ElicitResult> => ask(request, options ?? {});
|
|
162
|
+
return Object.assign(elicit, { modes: Object.freeze([...modes]) });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// =============================================================================
|
|
166
|
+
// The policy of the host
|
|
167
|
+
// =============================================================================
|
|
168
|
+
|
|
169
|
+
/** The rules of a host for the questions of its procedures. */
|
|
170
|
+
export interface ElicitationPolicy {
|
|
171
|
+
/** False: the face does not ask the person, also when it can. The default is true. */
|
|
172
|
+
readonly ask?: boolean | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* The consents that the operator gives when the server starts: `true` for each question with a
|
|
175
|
+
* consent key, or a list of consent keys. The operator answers only when the face cannot ask
|
|
176
|
+
* the person, not when a question of the face fails. The default is no consent. Example:
|
|
177
|
+
* `["user-provided"]` for the flag `--allow-user-provided` of polite-crawler.
|
|
178
|
+
*/
|
|
179
|
+
readonly operatorConsent?: boolean | readonly string[] | undefined;
|
|
180
|
+
/** The time that the face waits for an answer, when the question gives no time. */
|
|
181
|
+
readonly timeout?: Duration | undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function messageOf(error: unknown): string {
|
|
185
|
+
return error instanceof Error ? error.message : String(error);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** True when an error is the abort of a signal, or the signal aborted. */
|
|
189
|
+
function isAbort(error: unknown, signal: AbortSignal | undefined): boolean {
|
|
190
|
+
return signal?.aborted === true || (error instanceof Error && error.name === "AbortError");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The answer when a question of the face fails: `cancel` for an abort, else `unavailable`. The
|
|
195
|
+
* operator does not answer here: the person can see the question.
|
|
196
|
+
*/
|
|
197
|
+
function failedAnswer(error: unknown, signal: AbortSignal | undefined): ElicitResult {
|
|
198
|
+
const reason = `The face could not get the answer of the person: ${messageOf(error)}`;
|
|
199
|
+
return { action: isAbort(error, signal) ? "cancel" : "unavailable", reason };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The answer of the operator to a question, or undefined. The operator answers a form question
|
|
204
|
+
* with a consent key that the operator gave. Each field of yes or no gets `true`, and another
|
|
205
|
+
* field gets its default. The operator gives no answer when a necessary field has no default.
|
|
206
|
+
*/
|
|
207
|
+
export function operatorAnswer(
|
|
208
|
+
request: ElicitRequest,
|
|
209
|
+
consent: boolean | readonly string[] | undefined,
|
|
210
|
+
): ElicitResult | undefined {
|
|
211
|
+
if (consent === undefined || consent === false || request.mode === "url" || request.consent === undefined) return undefined;
|
|
212
|
+
if (consent !== true && !consent.includes(request.consent)) return undefined;
|
|
213
|
+
const content: Record<string, ElicitValue> = {};
|
|
214
|
+
for (const [name, field] of Object.entries(request.fields)) {
|
|
215
|
+
if (field.type === "boolean") content[name] = true;
|
|
216
|
+
else if (field.default !== undefined) content[name] = field.default;
|
|
217
|
+
else if (request.required?.includes(name)) return undefined;
|
|
218
|
+
}
|
|
219
|
+
return { action: "accept", content, by: "operator" };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* This function applies the policy of a host to the `Elicit` function of a face. The result
|
|
224
|
+
* asks the person when the face can. A failed question gives `unavailable` (`cancel` for an
|
|
225
|
+
* abort). When the face cannot ask, the consent of the operator answers, or the answer is
|
|
226
|
+
* `unavailable`. The function gives undefined when no one can answer: no face and no consent.
|
|
227
|
+
*/
|
|
228
|
+
export function elicitWithPolicy(face: Elicit | undefined, policy: ElicitationPolicy = {}): Elicit | undefined {
|
|
229
|
+
const asker = policy.ask === false ? undefined : face;
|
|
230
|
+
const consent = policy.operatorConsent;
|
|
231
|
+
const operator = consent === true || (Array.isArray(consent) && consent.length > 0);
|
|
232
|
+
if (!asker && !operator) return undefined;
|
|
233
|
+
return createElicit(asker?.modes ?? [], async (request, options) => {
|
|
234
|
+
const timed: ElicitOptions =
|
|
235
|
+
options.timeout === undefined && policy.timeout !== undefined ? { ...options, timeout: policy.timeout } : options;
|
|
236
|
+
const mode: ElicitMode = request.mode ?? "form";
|
|
237
|
+
let reason: string;
|
|
238
|
+
if (asker && asker.modes.includes(mode)) {
|
|
239
|
+
// The person can see the question: a timeout or a lost connection is not a consent
|
|
240
|
+
try {
|
|
241
|
+
return await asker(request, timed);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return failedAnswer(error, options.signal);
|
|
244
|
+
}
|
|
245
|
+
} else if (asker) {
|
|
246
|
+
reason = `The face cannot ask a question of the mode "${mode}"`;
|
|
247
|
+
} else {
|
|
248
|
+
reason = policy.ask === false ? "The host does not ask the person" : "The face of the call cannot ask the person";
|
|
249
|
+
}
|
|
250
|
+
return operatorAnswer(request, consent) ?? { action: "unavailable", reason };
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// The helpers of a procedure
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
/** The reason of an answer when the context has no `elicit` function. */
|
|
259
|
+
export const NO_ELICITATION_REASON =
|
|
260
|
+
"The face of the call cannot ask the person, and the operator gave no consent for this question";
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* This function asks the person at the caller through `ctx.elicit`. It always gives an answer,
|
|
264
|
+
* and it does not throw. The answer is `unavailable` with a reason when the face cannot ask, or
|
|
265
|
+
* when the question fails. An abort of the call gives `cancel`.
|
|
266
|
+
*/
|
|
267
|
+
export async function elicitInput(
|
|
268
|
+
ctx: Pick<ProcedureContext, "elicit" | "signal">,
|
|
269
|
+
request: ElicitRequest,
|
|
270
|
+
options: ElicitOptions = {},
|
|
271
|
+
): Promise<ElicitResult> {
|
|
272
|
+
if (!ctx.elicit) return { action: "unavailable", reason: NO_ELICITATION_REASON };
|
|
273
|
+
const signal = options.signal ?? ctx.signal;
|
|
274
|
+
try {
|
|
275
|
+
return await ctx.elicit(request, signal ? { ...options, signal } : options);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return failedAnswer(error, signal);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** The options of `elicitConfirmation`. */
|
|
282
|
+
export interface ConfirmationOptions extends ElicitOptions {
|
|
283
|
+
/** The key of the consent, so the operator can give it before the calls. */
|
|
284
|
+
readonly consent?: string | undefined;
|
|
285
|
+
/** The name of the field of yes or no. The default is `"confirm"`. */
|
|
286
|
+
readonly field?: string | undefined;
|
|
287
|
+
/** The text of the field. The default is `"Yes"`. */
|
|
288
|
+
readonly title?: string | undefined;
|
|
289
|
+
/** The text that tells what a yes does. */
|
|
290
|
+
readonly description?: string | undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** The answer of a confirmation. `confirmed` is true only for an accepted answer of yes. */
|
|
294
|
+
export interface ConfirmationResult extends ElicitResult {
|
|
295
|
+
readonly confirmed: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* This function asks the person at the caller a question of yes or no. `confirmed` is true only
|
|
300
|
+
* when the person, or the consent of the operator, says yes.
|
|
301
|
+
*/
|
|
302
|
+
export async function elicitConfirmation(
|
|
303
|
+
ctx: Pick<ProcedureContext, "elicit" | "signal">,
|
|
304
|
+
question: string,
|
|
305
|
+
options: ConfirmationOptions = {},
|
|
306
|
+
): Promise<ConfirmationResult> {
|
|
307
|
+
const { consent, field: fieldOption, title, description, ...elicitOptions } = options;
|
|
308
|
+
const field = fieldOption ?? "confirm";
|
|
309
|
+
const result = await elicitInput(
|
|
310
|
+
ctx,
|
|
311
|
+
{
|
|
312
|
+
message: question,
|
|
313
|
+
fields: { [field]: { type: "boolean", title: title ?? "Yes", ...(description !== undefined ? { description } : {}) } },
|
|
314
|
+
required: [field],
|
|
315
|
+
...(consent !== undefined ? { consent } : {}),
|
|
316
|
+
},
|
|
317
|
+
elicitOptions,
|
|
318
|
+
);
|
|
319
|
+
return { ...result, confirmed: result.action === "accept" && result.content?.[field] === true };
|
|
320
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool hints of a procedure with its override (CLI-10).
|
|
3
|
+
*
|
|
4
|
+
* The declared effects give the hints of a tool (`toolHints()`). A procedure can make its tool
|
|
5
|
+
* look more dangerous with `metadata.toolHints`, for example `openWorldHint: true` for a store
|
|
6
|
+
* that other people can read. An override cannot make a tool look safer than its effects:
|
|
7
|
+
* the effects stay the truth, and the host enforces the effects, not the hints.
|
|
8
|
+
*
|
|
9
|
+
* The cautious value of each hint:
|
|
10
|
+
* - `readOnlyHint: false`: the tool can change something.
|
|
11
|
+
* - `destructiveHint: true`: the tool can delete or overwrite data.
|
|
12
|
+
* - `idempotentHint: false`: a second call can change more.
|
|
13
|
+
* - `openWorldHint: true`: the tool can reach systems outside the host.
|
|
14
|
+
*
|
|
15
|
+
* An override cannot give the other value, also when the effects give it. The registry refuses
|
|
16
|
+
* such an override when the procedure registers.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { AnyProcedure } from "./types.js";
|
|
20
|
+
import { toolHints, type ToolHints } from "./capability.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The override of the hints of a tool. Each hint has only its cautious value, so the type refuses
|
|
24
|
+
* the other value before the registry does.
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolHintOverride {
|
|
27
|
+
readonly readOnlyHint?: false | undefined;
|
|
28
|
+
readonly destructiveHint?: true | undefined;
|
|
29
|
+
readonly idempotentHint?: false | undefined;
|
|
30
|
+
readonly openWorldHint?: true | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type HintName = keyof ToolHints;
|
|
34
|
+
|
|
35
|
+
/** The cautious value of each hint: the only value that an override can give. */
|
|
36
|
+
const CAUTIOUS: Readonly<Record<HintName, boolean>> = {
|
|
37
|
+
readOnlyHint: false,
|
|
38
|
+
destructiveHint: true,
|
|
39
|
+
idempotentHint: false,
|
|
40
|
+
openWorldHint: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const HINT_NAMES = Object.keys(CAUTIOUS) as HintName[];
|
|
44
|
+
|
|
45
|
+
/** The override of a procedure (`metadata.toolHints`), or undefined. */
|
|
46
|
+
export function toolHintOverrideOf(procedure: AnyProcedure): ToolHintOverride | undefined {
|
|
47
|
+
const value = (procedure.metadata as { toolHints?: unknown } | undefined)?.toolHints;
|
|
48
|
+
return value !== null && typeof value === "object" ? (value as ToolHintOverride) : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The refusal of the override of a procedure, or undefined when the override is correct. An
|
|
53
|
+
* override can give only the cautious value of a hint.
|
|
54
|
+
*/
|
|
55
|
+
export function toolHintOverrideRefusal(procedure: AnyProcedure): string | undefined {
|
|
56
|
+
const value = (procedure.metadata as { toolHints?: unknown } | undefined)?.toolHints;
|
|
57
|
+
if (value === undefined) return undefined;
|
|
58
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
59
|
+
return "metadata.toolHints must be an object of hints";
|
|
60
|
+
}
|
|
61
|
+
for (const [name, hint] of Object.entries(value as Record<string, unknown>)) {
|
|
62
|
+
if (hint === undefined) continue;
|
|
63
|
+
if (!HINT_NAMES.includes(name as HintName)) {
|
|
64
|
+
return `metadata.toolHints has an unknown hint "${name}". The hints are ${HINT_NAMES.join(", ")}`;
|
|
65
|
+
}
|
|
66
|
+
const cautious = CAUTIOUS[name as HintName];
|
|
67
|
+
if (hint !== cautious) {
|
|
68
|
+
return (
|
|
69
|
+
`metadata.toolHints.${name} can only be ${String(cautious)}: an override can only make a tool look more ` +
|
|
70
|
+
`dangerous than its effects. The declared effects give the other hints`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The hints of the tool of a procedure: the hints of its effects, with the cautious values of
|
|
79
|
+
* its override. A value of the override that is not cautious has no effect (the registry refuses
|
|
80
|
+
* it at registration).
|
|
81
|
+
*/
|
|
82
|
+
export function toolHintsOf(procedure: AnyProcedure): ToolHints {
|
|
83
|
+
const derived = toolHints(procedure);
|
|
84
|
+
const override = toolHintOverrideOf(procedure);
|
|
85
|
+
if (!override) return derived;
|
|
86
|
+
const hints: { -readonly [name in HintName]?: boolean } = { ...derived };
|
|
87
|
+
for (const name of HINT_NAMES) {
|
|
88
|
+
if (override[name] === CAUTIOUS[name]) hints[name] = CAUTIOUS[name];
|
|
89
|
+
}
|
|
90
|
+
return hints;
|
|
91
|
+
}
|
package/src/procedures/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type {
|
|
|
25
25
|
RegistrationOptions,
|
|
26
26
|
ProcedureRegistryLike,
|
|
27
27
|
Effects,
|
|
28
|
+
FsRootRules,
|
|
28
29
|
PermittedEffects,
|
|
29
30
|
Register,
|
|
30
31
|
ClearanceOf,
|
|
@@ -70,6 +71,41 @@ export {
|
|
|
70
71
|
newIdempotencyKey,
|
|
71
72
|
} from "./idempotency.js";
|
|
72
73
|
export type { IdempotencyOptions } from "./idempotency.js";
|
|
74
|
+
// What the face of a call offers the procedure (CLI-10, CLI-12)
|
|
75
|
+
export {
|
|
76
|
+
createElicit,
|
|
77
|
+
elicitWithPolicy,
|
|
78
|
+
operatorAnswer,
|
|
79
|
+
elicitInput,
|
|
80
|
+
elicitConfirmation,
|
|
81
|
+
NO_ELICITATION_REASON,
|
|
82
|
+
} from "./elicit.js";
|
|
83
|
+
export type {
|
|
84
|
+
Elicit,
|
|
85
|
+
ElicitRequest,
|
|
86
|
+
ElicitFormRequest,
|
|
87
|
+
ElicitUrlRequest,
|
|
88
|
+
ElicitMode,
|
|
89
|
+
ElicitAction,
|
|
90
|
+
ElicitResult,
|
|
91
|
+
ElicitOptions,
|
|
92
|
+
ElicitField,
|
|
93
|
+
ElicitFieldBase,
|
|
94
|
+
ElicitTextField,
|
|
95
|
+
ElicitNumberField,
|
|
96
|
+
ElicitBooleanField,
|
|
97
|
+
ElicitChoiceField,
|
|
98
|
+
ElicitChoicesField,
|
|
99
|
+
ElicitValue,
|
|
100
|
+
ElicitationPolicy,
|
|
101
|
+
ConfirmationOptions,
|
|
102
|
+
ConfirmationResult,
|
|
103
|
+
} from "./elicit.js";
|
|
104
|
+
export type { ProgressUpdate, ReportProgress } from "./progress.js";
|
|
105
|
+
export { toolHintsOf, toolHintOverrideOf, toolHintOverrideRefusal } from "./hints.js";
|
|
106
|
+
export type { ToolHintOverride } from "./hints.js";
|
|
107
|
+
export { untrustedText, untrustedFieldsOf, untrustedTextsOf } from "./untrusted.js";
|
|
108
|
+
export type { UntrustedText, UntrustedSource, UntrustedValue } from "./untrusted.js";
|
|
73
109
|
export type { ToolHints, CapabilityRefusal, CapabilityRefusalReason, InvocationExplanation, EffectsViolation } from "./capability.js";
|
|
74
110
|
|
|
75
111
|
// Registry
|
|
@@ -87,8 +123,49 @@ export {
|
|
|
87
123
|
export type { ProcedureDefinition, ProcedureStub } from "./define.js";
|
|
88
124
|
|
|
89
125
|
// Collection procedures
|
|
90
|
-
export {
|
|
91
|
-
|
|
126
|
+
export {
|
|
127
|
+
createCollectionProcedures,
|
|
128
|
+
collectionChangeTopic,
|
|
129
|
+
collectionTopicReads,
|
|
130
|
+
DEFAULT_FIND_LIMIT,
|
|
131
|
+
DEFAULT_QUERY_OPERATORS,
|
|
132
|
+
DEFAULT_QUERY_POLICY,
|
|
133
|
+
QUERY_OPERATORS,
|
|
134
|
+
applyPatch,
|
|
135
|
+
compareStorageValues,
|
|
136
|
+
compileSelector,
|
|
137
|
+
isPatchOperations,
|
|
138
|
+
matchesSelector,
|
|
139
|
+
mergePatch,
|
|
140
|
+
patchProblems,
|
|
141
|
+
queryEntries,
|
|
142
|
+
queryOptionsProblems,
|
|
143
|
+
selectorProblems,
|
|
144
|
+
} from "./collection/index.js";
|
|
145
|
+
export type {
|
|
146
|
+
CollectionProceduresOptions,
|
|
147
|
+
CollectionChangeEventsOptions,
|
|
148
|
+
CollectionChangeEvent,
|
|
149
|
+
StoragePatchOperations,
|
|
150
|
+
StorageQueryOperator,
|
|
151
|
+
StorageQueryPolicy,
|
|
152
|
+
CollectionStorage,
|
|
153
|
+
StorageChanges,
|
|
154
|
+
StorageMetadata,
|
|
155
|
+
StorageWriteOptions,
|
|
156
|
+
StorageSetChange,
|
|
157
|
+
StorageDeleteChange,
|
|
158
|
+
StorageClearChange,
|
|
159
|
+
StorageChangeEvent,
|
|
160
|
+
StorageChangeBatch,
|
|
161
|
+
StorageObserver,
|
|
162
|
+
StorageFieldOperators,
|
|
163
|
+
StoragePatch,
|
|
164
|
+
StorageQueryOptions,
|
|
165
|
+
StorageSelector,
|
|
166
|
+
StorageSort,
|
|
167
|
+
StorageSortDirection,
|
|
168
|
+
} from "./collection/index.js";
|
|
92
169
|
|
|
93
170
|
// Manual registration helpers
|
|
94
171
|
export {
|
|
@@ -107,6 +184,9 @@ export {
|
|
|
107
184
|
registerClientProcedures,
|
|
108
185
|
} from "./module.js";
|
|
109
186
|
export type { ModuleInput } from "./module.js";
|
|
187
|
+
// One call for a registry of some modules, `invokePath` and `outputValue`
|
|
188
|
+
export { callerOf } from "./caller.js";
|
|
189
|
+
export type { Caller, CallerOptions } from "./caller.js";
|
|
110
190
|
|
|
111
191
|
// Procedure reference system (procedure-as-data)
|
|
112
192
|
export {
|
package/src/procedures/invoke.ts
CHANGED
|
@@ -40,7 +40,10 @@ import { nestedCallRefusal } from "./policy.js";
|
|
|
40
40
|
import { scopeOf, withScope, type HydrationEvent } from "./ref.js";
|
|
41
41
|
import { UNLIMITED_CAPABILITY, capabilityRefusal, examinesNestedCall, narrowCapability, type CapabilityRefusalReason } from "./capability.js";
|
|
42
42
|
import { timeoutAfterMs } from "./interceptors.js";
|
|
43
|
+
import type { Elicit } from "./elicit.js";
|
|
44
|
+
import type { ReportProgress } from "./progress.js";
|
|
43
45
|
import type { EventBus } from "../events/types.js";
|
|
46
|
+
import type { Principal } from "../server/types.js";
|
|
44
47
|
import { getGlobalEventBus, withEventBusSignal } from "../events/bus.js";
|
|
45
48
|
|
|
46
49
|
// =============================================================================
|
|
@@ -120,6 +123,16 @@ export interface InvokeOptions {
|
|
|
120
123
|
repository?: RepositoryProvider | undefined;
|
|
121
124
|
/** The bus of `ctx.bus`. The default is the global bus of `getGlobalEventBus()`. */
|
|
122
125
|
bus?: EventBus | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* The function that asks the person at the caller (`ctx.elicit`, CLI-12). A face of the host
|
|
128
|
+
* gives it. The nested calls get the same function.
|
|
129
|
+
*/
|
|
130
|
+
elicit?: Elicit | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* The function that tells the caller the progress of the call (`ctx.progress`). Only the call
|
|
133
|
+
* of the host gets it in its context, not its nested calls.
|
|
134
|
+
*/
|
|
135
|
+
progress?: ReportProgress | undefined;
|
|
123
136
|
/** The input is valid already (for example, the route resolver validated it). */
|
|
124
137
|
inputValidated?: boolean | undefined;
|
|
125
138
|
/**
|
|
@@ -154,6 +167,11 @@ export interface InvokeOptions {
|
|
|
154
167
|
* It drops a refused stream item. A refused value gives `FORBIDDEN` (reason `clearance`).
|
|
155
168
|
*/
|
|
156
169
|
admitItem?: AdmitItem | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* The principal of the call, from the authentication of the host. It goes to `ctx.principal`,
|
|
172
|
+
* to the invocation and to the nested calls.
|
|
173
|
+
*/
|
|
174
|
+
principal?: Principal | undefined;
|
|
157
175
|
}
|
|
158
176
|
|
|
159
177
|
/**
|
|
@@ -184,6 +202,8 @@ export interface Invocation {
|
|
|
184
202
|
readonly depth: number;
|
|
185
203
|
/** The capability of the invocation (frozen). */
|
|
186
204
|
readonly capability: Capability;
|
|
205
|
+
/** The principal of the call: who sends it. A call from this process has none. */
|
|
206
|
+
readonly principal?: Principal | undefined;
|
|
187
207
|
}
|
|
188
208
|
|
|
189
209
|
/** The changes that an interceptor gives to the next step: the handler and the nested calls get them. */
|
|
@@ -313,6 +333,7 @@ export async function invokeProcedure<TOutput = unknown>(
|
|
|
313
333
|
parent: options.parent,
|
|
314
334
|
depth: options.parent ? options.parent.depth + 1 : 0,
|
|
315
335
|
capability: options.capability ?? UNLIMITED_CAPABILITY,
|
|
336
|
+
...(options.principal !== undefined ? { principal: options.principal } : {}),
|
|
316
337
|
};
|
|
317
338
|
// A deadline that the parent does not have (from a host, a request or a narrowed nested call)
|
|
318
339
|
// ends the invocation at that time, as the timeout interceptor does
|
|
@@ -486,11 +507,21 @@ export function createProcedureContext(
|
|
|
486
507
|
if (options.expose) context.expose = options.expose;
|
|
487
508
|
if (options.repository) context.repository = options.repository;
|
|
488
509
|
if (options.onRef) context.onRef = options.onRef;
|
|
510
|
+
if (options.principal !== undefined) context.principal = options.principal;
|
|
511
|
+
// The procedure of the parent invocation made this call (roadmap R33: the root of the files)
|
|
512
|
+
if (invocation?.parent) context.caller = invocation.parent.procedure;
|
|
513
|
+
// The output filter of the host, for the data that a handler sends later (roadmap CLI-15)
|
|
514
|
+
const admitItem = options.admitItem;
|
|
515
|
+
if (invocation && invocation.depth === 0 && admitItem) context.admit = async (item) => admitItem(item, invocation);
|
|
489
516
|
// Every context has a bus. With a signal, the waits of the handler on the bus end when the
|
|
490
517
|
// invocation aborts, so a stream handler that waits on ctx.bus.stream() can finish. (Before,
|
|
491
518
|
// no host set ctx.bus: deep dive CORE-15.)
|
|
492
519
|
const bus = options.bus ?? getGlobalEventBus();
|
|
493
520
|
context.bus = options.signal ? withEventBusSignal(bus, options.signal) : bus;
|
|
521
|
+
// What the face of the call offers (CLI-12): the nested calls ask the same person, and only
|
|
522
|
+
// the call of the host reports its progress, so the values of one call stay in order
|
|
523
|
+
if (options.elicit) context.elicit = options.elicit;
|
|
524
|
+
if (options.progress && !invocation?.parent && !options.parent) context.progress = options.progress;
|
|
494
525
|
return context;
|
|
495
526
|
}
|
|
496
527
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The progress of a call, from the procedure to the caller (CLI-10).
|
|
3
|
+
*
|
|
4
|
+
* A face of a host (MCP) can show the progress of a long call while it operates. The face puts a
|
|
5
|
+
* `ReportProgress` function on the request when the caller asked for progress, and the procedure
|
|
6
|
+
* gets it as `ctx.progress`. A stream does not need it: a face that collects a stream shows each
|
|
7
|
+
* item as progress. Only the call of the host gets the function, not its nested calls, so the
|
|
8
|
+
* values of one call stay in order.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** One step of the progress of a call. */
|
|
12
|
+
export interface ProgressUpdate {
|
|
13
|
+
/** The work that is done. It must increase with each update of a call. */
|
|
14
|
+
readonly progress: number;
|
|
15
|
+
/** The work of the whole call, when the procedure knows it. */
|
|
16
|
+
readonly total?: number | undefined;
|
|
17
|
+
/** A short text about the step, for example the URL that a crawler fetched. */
|
|
18
|
+
readonly message?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The function that tells the caller the progress of the call (`ctx.progress`). It does not
|
|
23
|
+
* reject: a caller that is gone does not stop the call.
|
|
24
|
+
*/
|
|
25
|
+
export type ReportProgress = (update: ProgressUpdate) => Promise<void>;
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
} from "./types.js";
|
|
16
16
|
import { pathToKey } from "./types.js";
|
|
17
17
|
import { validateCalls } from "./policy.js";
|
|
18
|
+
import { toolHintOverrideRefusal } from "./hints.js";
|
|
18
19
|
|
|
19
20
|
// =============================================================================
|
|
20
21
|
// Registry Errors
|
|
@@ -135,6 +136,9 @@ export class ProcedureRegistry {
|
|
|
135
136
|
} catch (error) {
|
|
136
137
|
throw new RegistryError((error as Error).message, path);
|
|
137
138
|
}
|
|
139
|
+
// An override of the tool hints can only make the tool look more dangerous than its effects
|
|
140
|
+
const hintRefusal = toolHintOverrideRefusal(procedure);
|
|
141
|
+
if (hintRefusal) throw new RegistryError(`${hintRefusal} (${key})`, path);
|
|
138
142
|
|
|
139
143
|
const replaced = this.procedures.get(key);
|
|
140
144
|
// The key joins the segments with dots: ["a.b", "c"] and ["a", "b.c"] have one key
|