@typeship-ax/mcp 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +15 -11
- package/README.md +22 -53
- package/api.json +9998 -10118
- package/api.md +8983 -9120
- package/dist/arguments.d.ts +54 -0
- package/dist/arguments.d.ts.map +1 -0
- package/dist/arguments.js +265 -0
- package/dist/core/http.d.ts +162 -19
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +381 -48
- package/dist/core/pagination.d.ts +42 -6
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +111 -17
- package/dist/credential-storage.d.ts +10 -3
- package/dist/credential-storage.d.ts.map +1 -1
- package/dist/credential-storage.js +15 -6
- package/dist/dates.d.ts +1 -1
- package/dist/dates.js +1 -1
- package/dist/errors.d.ts +20 -84
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -108
- package/dist/fields.d.ts +36 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +187 -0
- package/dist/index.d.ts +28 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -25
- package/dist/mcp-authorization.d.ts.map +1 -1
- package/dist/mcp-authorization.js +34 -10
- package/dist/mcp-protocol.d.ts +108 -44
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +780 -484
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +129 -29
- package/dist/named-credentials.d.ts +19 -0
- package/dist/named-credentials.d.ts.map +1 -1
- package/dist/named-credentials.js +81 -1
- package/dist/oauth-request.d.ts +7 -1
- package/dist/oauth-request.d.ts.map +1 -1
- package/dist/oauth-request.js +26 -4
- package/dist/oauth-session.d.ts +13 -1
- package/dist/oauth-session.d.ts.map +1 -1
- package/dist/oauth-session.js +34 -18
- package/dist/ops.d.ts +58 -5
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +110 -41
- package/dist/resources/api-keys.d.ts +10 -7
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +10 -31
- package/dist/resources/deliveries.d.ts +89 -5
- package/dist/resources/deliveries.d.ts.map +1 -1
- package/dist/resources/deliveries.js +96 -19
- package/dist/resources/drafts.d.ts +16 -16
- package/dist/resources/drafts.d.ts.map +1 -1
- package/dist/resources/drafts.js +12 -65
- package/dist/resources/files.d.ts +4 -4
- package/dist/resources/files.d.ts.map +1 -1
- package/dist/resources/files.js +3 -12
- package/dist/resources/generations.d.ts +16 -16
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +23 -47
- package/dist/resources/organization.d.ts +4 -4
- package/dist/resources/organization.d.ts.map +1 -1
- package/dist/resources/organization.js +3 -10
- package/dist/resources/{generate.d.ts → packages.d.ts} +16 -16
- package/dist/resources/packages.d.ts.map +1 -0
- package/dist/resources/{generate.js → packages.js} +13 -29
- package/dist/resources/projects.d.ts +50 -50
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +60 -116
- package/dist/resources/releases.d.ts +22 -17
- package/dist/resources/releases.d.ts.map +1 -1
- package/dist/resources/releases.js +19 -40
- package/dist/resources/spec-revisions.d.ts +16 -7
- package/dist/resources/spec-revisions.d.ts.map +1 -1
- package/dist/resources/spec-revisions.js +7 -29
- package/dist/resources/specs.d.ts +7 -7
- package/dist/resources/specs.d.ts.map +1 -1
- package/dist/resources/specs.js +6 -34
- package/dist/resources/targets.d.ts +49 -49
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +59 -115
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +78 -76
- package/dist/search.d.ts +54 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +421 -0
- package/dist/type-docs.d.ts +61 -0
- package/dist/type-docs.d.ts.map +1 -0
- package/dist/type-docs.js +174 -0
- package/dist/types.d.ts +499 -339
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +18 -18
- package/dist/worker.js +2 -2
- package/package.json +5 -2
- package/server.json +5 -5
- package/src/arguments.ts +254 -0
- package/src/core/http.ts +457 -58
- package/src/core/pagination.ts +129 -18
- package/src/credential-storage.ts +16 -6
- package/src/dates.ts +1 -1
- package/src/errors.ts +46 -115
- package/src/fields.ts +167 -0
- package/src/index.ts +45 -28
- package/src/mcp-authorization.ts +29 -9
- package/src/mcp-protocol.ts +808 -435
- package/src/mcp.ts +115 -27
- package/src/named-credentials.ts +66 -1
- package/src/oauth-request.ts +32 -6
- package/src/oauth-session.ts +37 -19
- package/src/ops.ts +146 -45
- package/src/resources/api-keys.ts +34 -48
- package/src/resources/deliveries.ts +213 -32
- package/src/resources/drafts.ts +62 -109
- package/src/resources/files.ts +19 -20
- package/src/resources/generations.ts +61 -79
- package/src/resources/organization.ts +11 -16
- package/src/resources/{generate.ts → packages.ts} +43 -51
- package/src/resources/projects.ts +145 -200
- package/src/resources/releases.ts +50 -67
- package/src/resources/spec-revisions.ts +40 -49
- package/src/resources/specs.ts +39 -59
- package/src/resources/targets.ts +144 -194
- package/src/schemas.ts +78 -76
- package/src/search.ts +434 -0
- package/src/type-docs.ts +205 -0
- package/src/types.ts +538 -357
- package/src/worker.ts +2 -2
- package/dist/resources/generate.d.ts.map +0 -1
- package/dist/resources/publications.d.ts +0 -47
- package/dist/resources/publications.d.ts.map +0 -1
- package/dist/resources/publications.js +0 -70
- package/src/resources/publications.ts +0 -140
package/dist/search.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation search shared by the generated CLI (`docs search`) and MCP
|
|
3
|
+
* server (`search_docs`). Generated by Typeship — https://typeship.dev
|
|
4
|
+
*
|
|
5
|
+
* Agents describe a task ("assign issue", "list calls"), not an operation
|
|
6
|
+
* name, so ranking works on stems, weighs rare words over common ones
|
|
7
|
+
* (IDF), and reads the verb of the query against what each operation does:
|
|
8
|
+
* its HTTP method and whether its path names one item or a collection, or
|
|
9
|
+
* its GraphQL root field. GraphQL mutations are also indexed by their input
|
|
10
|
+
* fields, so "assign issue" finds issueUpdate through assigneeId.
|
|
11
|
+
* Deterministic and dependency-free.
|
|
12
|
+
*/
|
|
13
|
+
/** Reference matches per search page. */
|
|
14
|
+
export const SEARCH_PAGE_SIZE = 10;
|
|
15
|
+
/** Query verbs and the operation kinds they ask for, best first. */
|
|
16
|
+
const VERBS = {
|
|
17
|
+
list: ["list"], all: ["list"], browse: ["list"], enumerate: ["list"],
|
|
18
|
+
search: ["search", "list"], find: ["search", "list", "get"], query: ["search", "list"], lookup: ["get", "search"],
|
|
19
|
+
get: ["get", "list"], release: ["delete"], fetch: ["get", "list"], retrieve: ["get", "list"], read: ["get", "list"], show: ["get", "list"],
|
|
20
|
+
view: ["get", "list"], check: ["get"], describe: ["get"], inspect: ["get"], status: ["get"],
|
|
21
|
+
create: ["create"], add: ["create", "update"], new: ["create"], make: ["create"], send: ["create"], post: ["create"],
|
|
22
|
+
submit: ["create"], open: ["create"], start: ["create", "update"], upload: ["create", "update"], generate: ["create"],
|
|
23
|
+
buy: ["create"], purchase: ["create"], place: ["create"], invite: ["create"], write: ["create", "update"],
|
|
24
|
+
publish: ["create", "update"], register: ["create"],
|
|
25
|
+
update: ["update"], change: ["update"], edit: ["update"], modify: ["update"], set: ["update", "create"],
|
|
26
|
+
assign: ["update", "create"], rename: ["update"], move: ["update"], close: ["update", "delete"], reopen: ["update"],
|
|
27
|
+
mark: ["update"], archive: ["delete", "update"], unarchive: ["update"], enable: ["update"], disable: ["update"],
|
|
28
|
+
pause: ["update"], resume: ["update"], replace: ["update"], patch: ["update"], merge: ["update", "create"],
|
|
29
|
+
save: ["create", "update"], follow: ["create", "update"], unfollow: ["delete", "update"], comment: ["create"], reply: ["create"], request: ["create"], hang: ["update", "delete"],
|
|
30
|
+
delete: ["delete"], remove: ["delete", "update"], destroy: ["delete"], erase: ["delete"], drop: ["delete"],
|
|
31
|
+
revoke: ["delete"], cancel: ["delete", "update"], unassign: ["update", "delete"], clear: ["delete"],
|
|
32
|
+
};
|
|
33
|
+
/** Words that name the caller: "who am I", "my issues", "me". */
|
|
34
|
+
const IDENTITY_WORDS = new Set(["who", "whoami", "me", "my", "mine", "myself", "self"]);
|
|
35
|
+
const IDENTITY_TERMS = ["me", "self", "whoami", "authenticated"];
|
|
36
|
+
/** An operation about the caller: GET /user, /me/…, a viewer or me root
|
|
37
|
+
* field, users_get_authenticated. */
|
|
38
|
+
function isIdentityOperation(op) {
|
|
39
|
+
if (op.graphql?.field)
|
|
40
|
+
return /^(viewer|me|currentUser|whoami)$/.test(op.graphql.field);
|
|
41
|
+
const nameWords = words([op.tool, op.method ?? ""].join(" "));
|
|
42
|
+
if (nameWords.some((w) => w === "me" || w === "whoami" || w === "authenticated" || w === "self"))
|
|
43
|
+
return true;
|
|
44
|
+
return /^(\/v\d+)?\/(me|user|self|whoami)(\/|$)/i.test(op.path) || /\/(users\/)?me(\/|$)/i.test(op.path);
|
|
45
|
+
}
|
|
46
|
+
/** Words that narrow a request rather than name the operation. */
|
|
47
|
+
const QUALIFIERS = new Set(["yesterday", "today", "tomorrow", "recent", "recently", "latest", "last", "week", "month", "year", "daily"]);
|
|
48
|
+
/** A few everyday words for what APIs call something else. */
|
|
49
|
+
const SYNONYMS = {
|
|
50
|
+
now: ["current"], sms: ["message"], mms: ["message"], text: ["message"], document: ["file"], kyc: ["identity", "verification"],
|
|
51
|
+
};
|
|
52
|
+
const STOPWORDS = new Set([
|
|
53
|
+
"a", "an", "the", "to", "of", "for", "in", "on", "at", "from", "by", "with", "and", "or", "is", "are", "am", "be",
|
|
54
|
+
"do", "does", "it", "its", "this", "that", "these", "those", "into", "onto", "up", "how", "what", "which", "can",
|
|
55
|
+
"please", "via", "using", "some", "any", "there", "i", "you", "your", "we", "our",
|
|
56
|
+
]);
|
|
57
|
+
/** Field weights: a word in the operation's name says most about it. */
|
|
58
|
+
const W_NAME = 10, W_SUMMARY = 6, W_PATH = 4, W_PARAM = 3, W_ENUM = 2, W_DESCRIPTION = 1.5, W_OUTPUT = 1;
|
|
59
|
+
/** A light English stemmer: plurals, -ing/-ed, a few derivational
|
|
60
|
+
* suffixes, a trailing e. It only has to map a word and its common
|
|
61
|
+
* variants to one stem ("assignee", "assigned", "assigns" to "assign";
|
|
62
|
+
* "moderate", "moderations" to "moder"). */
|
|
63
|
+
export function stem(word) {
|
|
64
|
+
let w = word.toLowerCase();
|
|
65
|
+
if (w.length <= 3)
|
|
66
|
+
return w;
|
|
67
|
+
if (w.endsWith("ies") && w.length > 4)
|
|
68
|
+
w = w.slice(0, -3) + "y";
|
|
69
|
+
else if (/(sses|xes|ches|shes|zes)$/.test(w))
|
|
70
|
+
w = w.slice(0, -2);
|
|
71
|
+
else if (w.endsWith("s") && !/(ss|us|is)$/.test(w))
|
|
72
|
+
w = w.slice(0, -1);
|
|
73
|
+
let stripped = false;
|
|
74
|
+
if (w.endsWith("ly") && w.length >= 6)
|
|
75
|
+
w = w.slice(0, -2);
|
|
76
|
+
for (const [suffix, min] of [["ing", 4], ["ed", 3]]) {
|
|
77
|
+
if (w.endsWith(suffix) && w.length - suffix.length >= min) {
|
|
78
|
+
w = w.slice(0, -suffix.length);
|
|
79
|
+
stripped = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (stripped) {
|
|
84
|
+
if (/(at|bl|iz)$/.test(w))
|
|
85
|
+
w += "e";
|
|
86
|
+
else if (/([^aeioulsz])\1$/.test(w))
|
|
87
|
+
w = w.slice(0, -1);
|
|
88
|
+
}
|
|
89
|
+
for (const [suffix, min] of [["ations", 4], ["ation", 4], ["ments", 4], ["ment", 4], ["ption", 4], ["ions", 4], ["ion", 4], ["ers", 4], ["er", 4], ["ee", 4], ["ate", 4]]) {
|
|
90
|
+
if (w.endsWith(suffix) && w.length - suffix.length >= min) {
|
|
91
|
+
w = w.slice(0, -suffix.length);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (w.endsWith("e") && w.length >= 5)
|
|
96
|
+
w = w.slice(0, -1);
|
|
97
|
+
return w;
|
|
98
|
+
}
|
|
99
|
+
/** Lowercase words of two or more characters, with snake, kebab, camel and
|
|
100
|
+
* letter-digit seams split so "createAccount" and "accounts_create" share
|
|
101
|
+
* words. */
|
|
102
|
+
function words(text) {
|
|
103
|
+
return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([a-zA-Z])([0-9])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 2);
|
|
104
|
+
}
|
|
105
|
+
function stems(text) {
|
|
106
|
+
return words(text).filter((w) => !STOPWORDS.has(w)).map(stem);
|
|
107
|
+
}
|
|
108
|
+
// A server keeps one operation list, and a hosted one filters the same
|
|
109
|
+
// operation objects per request: cache by both.
|
|
110
|
+
const INDEXES = new WeakMap();
|
|
111
|
+
const INDEXED = new WeakMap();
|
|
112
|
+
const READ_ACTIONS = ["list", "get", "search"];
|
|
113
|
+
const WRITE_ACTIONS = ["create", "update", "delete"];
|
|
114
|
+
/** The first of `words` (in order) that names an allowed action. */
|
|
115
|
+
function actionIn(candidates, allowed) {
|
|
116
|
+
for (const word of candidates) {
|
|
117
|
+
const action = VERBS[word]?.[0];
|
|
118
|
+
if (action && allowed.includes(action))
|
|
119
|
+
return { action, word };
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
/** What an operation does, and the word of its name that says so: its
|
|
124
|
+
* name's verb when it has one, else its HTTP method and whether the path
|
|
125
|
+
* ends at one item or a collection. A read never creates, updates or
|
|
126
|
+
* deletes, whatever its name ("query comment" is a read of a comment). */
|
|
127
|
+
function operationAction(op) {
|
|
128
|
+
const collection = () => {
|
|
129
|
+
const segments = op.path.replace(/\.[a-z]+$/i, "").split("/").filter(Boolean);
|
|
130
|
+
const last = segments.at(-1) ?? "";
|
|
131
|
+
if (last.startsWith("{") || last.startsWith(":"))
|
|
132
|
+
return "get";
|
|
133
|
+
return op.paginated ? "list" : "get";
|
|
134
|
+
};
|
|
135
|
+
if (op.graphql?.field) {
|
|
136
|
+
// issueUpdate, createIssue: the verb may sit at either end.
|
|
137
|
+
const fieldWords = words(op.graphql.field);
|
|
138
|
+
const read = op.graphql.kind !== "mutation";
|
|
139
|
+
const found = actionIn([...fieldWords].reverse(), read ? READ_ACTIONS : WRITE_ACTIONS);
|
|
140
|
+
if (found)
|
|
141
|
+
return found;
|
|
142
|
+
return { action: read ? (op.paginated || /s$/.test(op.graphql.field) ? "list" : "get") : "update", word: null };
|
|
143
|
+
}
|
|
144
|
+
const verb = op.httpMethod.toUpperCase();
|
|
145
|
+
const read = verb === "GET" || verb === "HEAD";
|
|
146
|
+
const found = actionIn(words(op.method ?? ""), read ? READ_ACTIONS : [...WRITE_ACTIONS, "search"]);
|
|
147
|
+
if (found)
|
|
148
|
+
return found;
|
|
149
|
+
if (verb === "DELETE")
|
|
150
|
+
return { action: "delete", word: null };
|
|
151
|
+
if (verb === "PUT" || verb === "PATCH")
|
|
152
|
+
return { action: "update", word: null };
|
|
153
|
+
if (verb === "POST")
|
|
154
|
+
return { action: "create", word: null };
|
|
155
|
+
return { action: collection(), word: null };
|
|
156
|
+
}
|
|
157
|
+
/** Literal path words: no parameters, versions or date segments. */
|
|
158
|
+
function pathWords(path) {
|
|
159
|
+
return path.replace(/\.[a-z]+$/i, "").split("/").filter((s) => s && !/^[{:]/.test(s) && !/^v\d+$/i.test(s) && !/^\d/.test(s)).join(" ");
|
|
160
|
+
}
|
|
161
|
+
/** Property names and enum values of a schema, one level into objects,
|
|
162
|
+
* and one more for object-typed properties (a GraphQL input object). */
|
|
163
|
+
function schemaWords(schema, depth, names, enums) {
|
|
164
|
+
if (!schema || typeof schema !== "object" || depth < 0)
|
|
165
|
+
return;
|
|
166
|
+
const node = schema;
|
|
167
|
+
if (Array.isArray(node.enum))
|
|
168
|
+
for (const value of node.enum)
|
|
169
|
+
if (typeof value === "string")
|
|
170
|
+
enums.push(value);
|
|
171
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
172
|
+
if (Array.isArray(node[key]))
|
|
173
|
+
for (const part of node[key])
|
|
174
|
+
schemaWords(part, depth, names, enums);
|
|
175
|
+
}
|
|
176
|
+
if (node.items)
|
|
177
|
+
schemaWords(node.items, depth, names, enums);
|
|
178
|
+
const properties = node.properties;
|
|
179
|
+
if (properties && typeof properties === "object") {
|
|
180
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
181
|
+
names.push(name);
|
|
182
|
+
schemaWords(child, depth - 1, names, enums);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function outputWords(schema, depth, out) {
|
|
187
|
+
if (!schema || typeof schema !== "object" || depth < 0)
|
|
188
|
+
return;
|
|
189
|
+
const node = schema;
|
|
190
|
+
if (typeof node.description === "string")
|
|
191
|
+
out.push(node.description.slice(0, 200));
|
|
192
|
+
if (node.items)
|
|
193
|
+
outputWords(node.items, depth, out);
|
|
194
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
195
|
+
if (Array.isArray(node[key]))
|
|
196
|
+
for (const part of node[key])
|
|
197
|
+
outputWords(part, depth, out);
|
|
198
|
+
}
|
|
199
|
+
const properties = node.properties;
|
|
200
|
+
if (properties && typeof properties === "object") {
|
|
201
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
202
|
+
out.push(name);
|
|
203
|
+
outputWords(child, depth - 1, out);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function firstSentence(text) {
|
|
208
|
+
return /^(.{1,200}?[.!?])(\s|$)/s.exec(text.trim())?.[1] ?? "";
|
|
209
|
+
}
|
|
210
|
+
function indexOperation(op) {
|
|
211
|
+
const weights = new Map();
|
|
212
|
+
const add = (text, weight) => {
|
|
213
|
+
for (const s of new Set(stems(text))) {
|
|
214
|
+
const entry = weights.get(s);
|
|
215
|
+
if (!entry)
|
|
216
|
+
weights.set(s, { best: weight, rest: 0 });
|
|
217
|
+
else if (weight > entry.best) {
|
|
218
|
+
entry.rest += entry.best;
|
|
219
|
+
entry.best = weight;
|
|
220
|
+
}
|
|
221
|
+
else
|
|
222
|
+
entry.rest += weight;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
// GraphQL tools are named query_<field> / mutation_<field>; the prefix is
|
|
226
|
+
// the kind, not a word a task would use.
|
|
227
|
+
const name = op.graphql?.field
|
|
228
|
+
? [op.graphql.field]
|
|
229
|
+
: [op.tool, op.resource ?? "", op.method ?? ""];
|
|
230
|
+
add(name.join(" "), W_NAME);
|
|
231
|
+
// Without a summary, the description's first sentence plays its part.
|
|
232
|
+
const summary = op.summary?.trim() || firstSentence(op.description ?? "");
|
|
233
|
+
add(summary, W_SUMMARY);
|
|
234
|
+
add(pathWords(op.path), W_PATH);
|
|
235
|
+
const paramNames = [];
|
|
236
|
+
const enumValues = [];
|
|
237
|
+
const paramText = [];
|
|
238
|
+
for (const param of op.params) {
|
|
239
|
+
paramNames.push(param.name);
|
|
240
|
+
if (param.enum)
|
|
241
|
+
enumValues.push(...param.enum);
|
|
242
|
+
if (param.description)
|
|
243
|
+
paramText.push(param.description.split("\n")[0].slice(0, 200));
|
|
244
|
+
}
|
|
245
|
+
const properties = (op.inputSchema?.properties ?? {});
|
|
246
|
+
for (const child of Object.values(properties))
|
|
247
|
+
schemaWords(child, 1, paramNames, enumValues);
|
|
248
|
+
add(paramNames.join(" "), W_PARAM);
|
|
249
|
+
add(enumValues.join(" "), W_ENUM);
|
|
250
|
+
add(op.description ?? "", W_DESCRIPTION);
|
|
251
|
+
add(paramText.join(" "), W_OUTPUT);
|
|
252
|
+
const output = [];
|
|
253
|
+
outputWords(op.outputSchema, 2, output);
|
|
254
|
+
add(output.join(" "), W_OUTPUT);
|
|
255
|
+
const { action, word } = operationAction(op);
|
|
256
|
+
const actionStem = word === null ? null : stem(word);
|
|
257
|
+
const nameStems = [...new Set(stems(name.join(" ")))].filter((s) => s !== actionStem);
|
|
258
|
+
return { weights, nameStems, action, actionStem, identity: isIdentityOperation(op), summary: stems(summary).join(" "), deprecated: op.deprecated === true || /^deprecated\b/i.test(op.summary ?? "") };
|
|
259
|
+
}
|
|
260
|
+
function indexFor(ops) {
|
|
261
|
+
const cached = INDEXES.get(ops);
|
|
262
|
+
if (cached)
|
|
263
|
+
return cached;
|
|
264
|
+
const docs = ops.map((op) => {
|
|
265
|
+
let doc = INDEXED.get(op);
|
|
266
|
+
if (!doc)
|
|
267
|
+
INDEXED.set(op, doc = indexOperation(op));
|
|
268
|
+
return doc;
|
|
269
|
+
});
|
|
270
|
+
const df = new Map();
|
|
271
|
+
for (const doc of docs)
|
|
272
|
+
for (const s of doc.weights.keys())
|
|
273
|
+
df.set(s, (df.get(s) ?? 0) + 1);
|
|
274
|
+
const index = { docs, df, vocabulary: [...df.keys()] };
|
|
275
|
+
INDEXES.set(ops, index);
|
|
276
|
+
return index;
|
|
277
|
+
}
|
|
278
|
+
/** A query stem's matches in the vocabulary: itself, and longer or
|
|
279
|
+
* shorter forms sharing a long prefix ("transcrib" and "transcript"),
|
|
280
|
+
* which count for less. */
|
|
281
|
+
function variants(term, index) {
|
|
282
|
+
const out = new Map();
|
|
283
|
+
if (index.df.has(term))
|
|
284
|
+
out.set(term, 1);
|
|
285
|
+
if (term.length < 5)
|
|
286
|
+
return out;
|
|
287
|
+
for (const word of index.vocabulary) {
|
|
288
|
+
if (word === term || word.length < 5)
|
|
289
|
+
continue;
|
|
290
|
+
let common = 0;
|
|
291
|
+
while (common < word.length && common < term.length && word[common] === term[common])
|
|
292
|
+
common++;
|
|
293
|
+
if (common >= 6 && Math.max(word.length, term.length) - common <= 3)
|
|
294
|
+
out.set(word, 0.6);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function parseQuery(query, index) {
|
|
299
|
+
const raw = words(query);
|
|
300
|
+
let verb = null;
|
|
301
|
+
let identity = false;
|
|
302
|
+
const terms = [];
|
|
303
|
+
const seen = new Set();
|
|
304
|
+
for (const word of raw) {
|
|
305
|
+
if (IDENTITY_WORDS.has(word)) {
|
|
306
|
+
identity = true;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (STOPWORDS.has(word))
|
|
310
|
+
continue;
|
|
311
|
+
const s = stem(word);
|
|
312
|
+
if (seen.has(s))
|
|
313
|
+
continue;
|
|
314
|
+
seen.add(s);
|
|
315
|
+
const actions = VERBS[word] ?? VERBS[s] ?? null;
|
|
316
|
+
const isVerb = actions !== null && actions.length > 0 && verb === null;
|
|
317
|
+
if (isVerb)
|
|
318
|
+
verb = actions;
|
|
319
|
+
const matches = variants(s, index);
|
|
320
|
+
for (const synonym of SYNONYMS[word] ?? [])
|
|
321
|
+
for (const [v, strength] of variants(stem(synonym), index))
|
|
322
|
+
matches.set(v, Math.max(matches.get(v) ?? 0, strength * 0.8));
|
|
323
|
+
// The verb is scored against what the operation does, so its word
|
|
324
|
+
// counts for little; the first noun is usually the object of the task.
|
|
325
|
+
const qualifier = QUALIFIERS.has(word);
|
|
326
|
+
const primary = !isVerb && !qualifier && !terms.some((t) => t.primary);
|
|
327
|
+
const weight = isVerb || qualifier ? 0.3 : primary ? 1.3 : 1;
|
|
328
|
+
terms.push({ matches, weight, verb: isVerb ? actions : null, primary, stem: s });
|
|
329
|
+
}
|
|
330
|
+
if (identity) {
|
|
331
|
+
const matches = new Map();
|
|
332
|
+
for (const t of IDENTITY_TERMS)
|
|
333
|
+
for (const [s, strength] of variants(stem(t), index))
|
|
334
|
+
matches.set(s, Math.max(matches.get(s) ?? 0, strength));
|
|
335
|
+
terms.push({ matches, weight: 0.8, verb: null, primary: false, stem: "@me" });
|
|
336
|
+
}
|
|
337
|
+
return { terms, verb };
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Operations for a free-text query, best first. Weak matches (under a
|
|
341
|
+
* fifth of the best score, or matching only the query's verb) are left
|
|
342
|
+
* out, and deprecated operations follow every current one.
|
|
343
|
+
*/
|
|
344
|
+
export function rankOperations(ops, query) {
|
|
345
|
+
const index = indexFor(ops);
|
|
346
|
+
const { terms, verb } = parseQuery(query, index);
|
|
347
|
+
if (terms.length === 0)
|
|
348
|
+
return [];
|
|
349
|
+
const idf = (s) => Math.log(1 + index.docs.length / (index.df.get(s) ?? index.docs.length));
|
|
350
|
+
const nonVerbTerms = terms.filter((t) => t.verb === null).length;
|
|
351
|
+
const primary = terms.find((t) => t.primary);
|
|
352
|
+
const verbStem = terms.find((t) => t.verb !== null)?.stem ?? null;
|
|
353
|
+
const identityTerm = terms.find((t) => t.stem === "@me");
|
|
354
|
+
const identityIdf = Math.log(1 + index.docs.length / Math.max(1, index.docs.filter((d) => d.identity).length));
|
|
355
|
+
const scored = [];
|
|
356
|
+
const phrase = words(query).filter((w) => !STOPWORDS.has(w)).map(stem).join(" ");
|
|
357
|
+
index.docs.forEach((doc, position) => {
|
|
358
|
+
let score = 0;
|
|
359
|
+
let matchedNonVerb = 0;
|
|
360
|
+
let primaryInName = false;
|
|
361
|
+
let verbInName = false;
|
|
362
|
+
const covered = new Set();
|
|
363
|
+
for (const term of terms) {
|
|
364
|
+
let best = term === identityTerm && doc.identity ? W_NAME * identityIdf : 0;
|
|
365
|
+
for (const [s, strength] of term.matches) {
|
|
366
|
+
const entry = doc.weights.get(s);
|
|
367
|
+
if (!entry)
|
|
368
|
+
continue;
|
|
369
|
+
const value = (entry.best + 0.1 * Math.min(entry.rest, 10)) * strength * idf(s);
|
|
370
|
+
if (value > best)
|
|
371
|
+
best = value;
|
|
372
|
+
if (doc.nameStems.includes(s)) {
|
|
373
|
+
covered.add(s);
|
|
374
|
+
if (term === primary)
|
|
375
|
+
primaryInName = true;
|
|
376
|
+
// The query's verb as a noun of the name: upload_url for "upload".
|
|
377
|
+
if (term.verb !== null)
|
|
378
|
+
verbInName = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (best > 0 && term.verb === null)
|
|
382
|
+
matchedNonVerb++;
|
|
383
|
+
score += best * term.weight;
|
|
384
|
+
}
|
|
385
|
+
if (score === 0)
|
|
386
|
+
return;
|
|
387
|
+
if (nonVerbTerms > 0 && matchedNonVerb === 0)
|
|
388
|
+
return;
|
|
389
|
+
// How much of the operation's name the query accounts for: call_list
|
|
390
|
+
// for "list calls" over sip_auth_calls_credential_list_mapping_delete.
|
|
391
|
+
const coverage = doc.nameStems.length === 0 ? 1 : covered.size / doc.nameStems.length;
|
|
392
|
+
score *= 0.6 + 0.8 * coverage;
|
|
393
|
+
// The query's verb against what the operation does.
|
|
394
|
+
if (verb) {
|
|
395
|
+
const rank = verb.indexOf(doc.action);
|
|
396
|
+
score *= rank === 0 ? 1.5 : rank > 0 ? 1.2 : doc.action === "search" && verb.includes("list") ? 1.2 : verbInName ? 1 : 0.55;
|
|
397
|
+
// The very word: issue_add_label for "add", over a generic create.
|
|
398
|
+
if (verbStem !== null && doc.actionStem === verbStem)
|
|
399
|
+
score *= 1.2;
|
|
400
|
+
}
|
|
401
|
+
// Every query word matched: a stronger signal than any single word.
|
|
402
|
+
if (matchedNonVerb === nonVerbTerms && nonVerbTerms > 1)
|
|
403
|
+
score *= 1.2;
|
|
404
|
+
if (phrase.includes(" ") && doc.nameStems.join(" ").includes(phrase))
|
|
405
|
+
score *= 1.2;
|
|
406
|
+
if (phrase.includes(" ") && doc.summary.includes(phrase))
|
|
407
|
+
score *= 1.3;
|
|
408
|
+
// The operation is named for the task's object: issue_update, not
|
|
409
|
+
// workflow_state_update, for "change issue state".
|
|
410
|
+
if (primaryInName)
|
|
411
|
+
score *= 1.3;
|
|
412
|
+
scored.push({ op: ops[position], score, deprecated: doc.deprecated });
|
|
413
|
+
});
|
|
414
|
+
if (scored.length === 0)
|
|
415
|
+
return [];
|
|
416
|
+
const top = Math.max(...scored.map((s) => s.score));
|
|
417
|
+
return scored
|
|
418
|
+
.filter((s) => s.score >= top * 0.2)
|
|
419
|
+
.sort((a, b) => Number(a.deprecated) - Number(b.deprecated) || b.score - a.score || a.op.tool.localeCompare(b.op.tool))
|
|
420
|
+
.map(({ op, score }) => ({ op, score: Math.round(score * 100) / 100 }));
|
|
421
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named input types for the docs surfaces (MCP read_docs, the CLI's docs
|
|
3
|
+
* command): a type's reference by name, and an argument path such as
|
|
4
|
+
* "filter.team.key" resolved to its type. Each type is described once with
|
|
5
|
+
* its fields typed by name, so a lookup is bounded and every type it
|
|
6
|
+
* mentions can be looked up the same way.
|
|
7
|
+
*/
|
|
8
|
+
export interface InputTypeField {
|
|
9
|
+
/** As an agent writes it: a type name, string[], "a"|"b", object. */
|
|
10
|
+
type: string;
|
|
11
|
+
required?: true;
|
|
12
|
+
description?: string;
|
|
13
|
+
default?: unknown;
|
|
14
|
+
deprecated?: true;
|
|
15
|
+
}
|
|
16
|
+
export interface InputTypeDoc {
|
|
17
|
+
description?: string;
|
|
18
|
+
/** An input object's fields. */
|
|
19
|
+
fields?: Record<string, InputTypeField>;
|
|
20
|
+
/** An enum's values. */
|
|
21
|
+
values?: unknown[];
|
|
22
|
+
/** A union's member types. */
|
|
23
|
+
variants?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface InputTypes {
|
|
26
|
+
types: Record<string, InputTypeDoc>;
|
|
27
|
+
/** Per tool, each argument whose type is (or contains) a named type. */
|
|
28
|
+
args: Record<string, Record<string, string>>;
|
|
29
|
+
}
|
|
30
|
+
/** The table's type names an expression mentions, in order. */
|
|
31
|
+
export declare function namedTypesIn(table: InputTypes | undefined, expression: string | undefined): string[];
|
|
32
|
+
/** A type by exact name, else by a case-insensitive match. */
|
|
33
|
+
export declare function findInputType(table: InputTypes | undefined, name: string): string | undefined;
|
|
34
|
+
/** How to read another type, in the caller's own syntax. */
|
|
35
|
+
export type TypeLookupHint = (name: string) => string;
|
|
36
|
+
/** A named type's reference: description, then its fields, values or
|
|
37
|
+
* members, then how to read the named types it mentions. */
|
|
38
|
+
export declare function inputTypeText(table: InputTypes, name: string, lookup: TypeLookupHint): string;
|
|
39
|
+
export type PathLookup = {
|
|
40
|
+
ok: true;
|
|
41
|
+
text: string;
|
|
42
|
+
} | {
|
|
43
|
+
ok: false;
|
|
44
|
+
message: string;
|
|
45
|
+
available: string[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* What one argument path means: the field's type, whether it is required,
|
|
49
|
+
* its description, and, when its type is named, that type's reference in
|
|
50
|
+
* full (a comparator's operators, an enum's values). `root` is an
|
|
51
|
+
* operation (its tool name, inline input schema, and how the caller names
|
|
52
|
+
* it) or a named type.
|
|
53
|
+
*/
|
|
54
|
+
export declare function argumentPathText(table: InputTypes | undefined, root: {
|
|
55
|
+
tool: string;
|
|
56
|
+
inputSchema: Record<string, unknown>;
|
|
57
|
+
label?: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: string;
|
|
60
|
+
}, path: string, lookup: TypeLookupHint): PathLookup;
|
|
61
|
+
//# sourceMappingURL=type-docs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-docs.d.ts","sourceRoot":"","sources":["../src/type-docs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACxC,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC9C;AAUD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAGpG;AASD,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK7F;AAYD,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAEtD;4DAC4D;AAC5D,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAyB7F;AA6BD,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC/F,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,GACrB,UAAU,CAyDZ"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named input types for the docs surfaces (MCP read_docs, the CLI's docs
|
|
3
|
+
* command): a type's reference by name, and an argument path such as
|
|
4
|
+
* "filter.team.key" resolved to its type. Each type is described once with
|
|
5
|
+
* its fields typed by name, so a lookup is bounded and every type it
|
|
6
|
+
* mentions can be looked up the same way.
|
|
7
|
+
*/
|
|
8
|
+
/** Enum values a type page lists before a count. */
|
|
9
|
+
const TYPE_ENUM_VALUES = 200;
|
|
10
|
+
/** One line of prose: links reduced to their text. */
|
|
11
|
+
function prose(text) {
|
|
12
|
+
return (text ?? "").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
/** The table's type names an expression mentions, in order. */
|
|
15
|
+
export function namedTypesIn(table, expression) {
|
|
16
|
+
if (!table || !expression)
|
|
17
|
+
return [];
|
|
18
|
+
return [...new Set(expression.split(/[|()[\]\s]+/).filter((part) => Object.hasOwn(table.types, part)))];
|
|
19
|
+
}
|
|
20
|
+
/** The one input object an expression names (IssueFilter, IssueFilter[]),
|
|
21
|
+
* whose fields a path continues into. */
|
|
22
|
+
function objectTypeOf(table, expression) {
|
|
23
|
+
const objects = namedTypesIn(table, expression).filter((name) => table.types[name].fields);
|
|
24
|
+
return objects.length === 1 ? objects[0] : undefined;
|
|
25
|
+
}
|
|
26
|
+
/** A type by exact name, else by a case-insensitive match. */
|
|
27
|
+
export function findInputType(table, name) {
|
|
28
|
+
if (!table)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (Object.hasOwn(table.types, name))
|
|
31
|
+
return name;
|
|
32
|
+
const lower = name.toLowerCase();
|
|
33
|
+
return Object.keys(table.types).find((candidate) => candidate.toLowerCase() === lower);
|
|
34
|
+
}
|
|
35
|
+
function fieldLine(name, field) {
|
|
36
|
+
const extras = [
|
|
37
|
+
...(field.required ? ["required"] : []),
|
|
38
|
+
...(field.default !== undefined ? ["default " + JSON.stringify(field.default)] : []),
|
|
39
|
+
...(field.deprecated ? ["deprecated"] : []),
|
|
40
|
+
];
|
|
41
|
+
const description = prose(field.description);
|
|
42
|
+
return " " + name + " (" + field.type + (extras.length ? ", " + extras.join(", ") : "") + ")" + (description ? ": " + description : "");
|
|
43
|
+
}
|
|
44
|
+
/** A named type's reference: description, then its fields, values or
|
|
45
|
+
* members, then how to read the named types it mentions. */
|
|
46
|
+
export function inputTypeText(table, name, lookup) {
|
|
47
|
+
const doc = table.types[name];
|
|
48
|
+
const kind = doc.fields ? "input object" : doc.values ? "enum" : "union";
|
|
49
|
+
const lines = [name + " (" + kind + ")"];
|
|
50
|
+
if (doc.description)
|
|
51
|
+
lines.push(prose(doc.description));
|
|
52
|
+
const mentioned = [];
|
|
53
|
+
if (doc.fields) {
|
|
54
|
+
const entries = Object.entries(doc.fields);
|
|
55
|
+
lines.push("", entries.length === 0 ? "No fields." : "Fields (" + entries.length + "):");
|
|
56
|
+
for (const [field, spec] of entries) {
|
|
57
|
+
lines.push(fieldLine(field, spec));
|
|
58
|
+
mentioned.push(...namedTypesIn(table, spec.type));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (doc.values) {
|
|
62
|
+
const values = doc.values.slice(0, TYPE_ENUM_VALUES).map((value) => JSON.stringify(value));
|
|
63
|
+
lines.push("", "Values: " + values.join(", ") + (doc.values.length > TYPE_ENUM_VALUES ? ", … " + (doc.values.length - TYPE_ENUM_VALUES) + " more" : ""));
|
|
64
|
+
}
|
|
65
|
+
else if (doc.variants) {
|
|
66
|
+
lines.push("", "One of: " + doc.variants.join(" | "));
|
|
67
|
+
for (const variant of doc.variants)
|
|
68
|
+
mentioned.push(...namedTypesIn(table, variant));
|
|
69
|
+
}
|
|
70
|
+
const others = [...new Set(mentioned)].filter((other) => other !== name);
|
|
71
|
+
if (others.length > 0) {
|
|
72
|
+
lines.push("", "Types used above (" + others.length + "): " + others.join(", ") + ". Read one with " + lookup("<type>") + ".");
|
|
73
|
+
}
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
function inlineObject(schema) {
|
|
77
|
+
if (!schema || typeof schema !== "object")
|
|
78
|
+
return undefined;
|
|
79
|
+
if (schema.properties && typeof schema.properties === "object")
|
|
80
|
+
return schema;
|
|
81
|
+
if (schema.items)
|
|
82
|
+
return inlineObject(schema.items);
|
|
83
|
+
const variants = schema.anyOf ?? schema.oneOf;
|
|
84
|
+
if (Array.isArray(variants)) {
|
|
85
|
+
const objects = variants.map(inlineObject).filter((variant) => variant !== undefined);
|
|
86
|
+
if (objects.length === 1)
|
|
87
|
+
return objects[0];
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
function inlineType(schema) {
|
|
92
|
+
if (Array.isArray(schema.enum))
|
|
93
|
+
return schema.enum.filter((value) => value !== null).map((value) => JSON.stringify(value)).join("|");
|
|
94
|
+
const variants = schema.anyOf ?? schema.oneOf;
|
|
95
|
+
if (Array.isArray(variants))
|
|
96
|
+
return [...new Set(variants.map(inlineType))].filter((t) => t !== "null").join("|") || "null";
|
|
97
|
+
const types = (Array.isArray(schema.type) ? schema.type : typeof schema.type === "string" ? [schema.type] : []).filter((t) => t !== "null");
|
|
98
|
+
if (types.includes("array")) {
|
|
99
|
+
const inner = schema.items ? inlineType(schema.items) : "any";
|
|
100
|
+
return (/[| ]/.test(inner) ? "(" + inner + ")" : inner) + "[]";
|
|
101
|
+
}
|
|
102
|
+
return types.join("|") || (schema.properties ? "object" : "any");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* What one argument path means: the field's type, whether it is required,
|
|
106
|
+
* its description, and, when its type is named, that type's reference in
|
|
107
|
+
* full (a comparator's operators, an enum's values). `root` is an
|
|
108
|
+
* operation (its tool name, inline input schema, and how the caller names
|
|
109
|
+
* it) or a named type.
|
|
110
|
+
*/
|
|
111
|
+
export function argumentPathText(table, root, path, lookup) {
|
|
112
|
+
const segments = path.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
113
|
+
if (segments.length === 0)
|
|
114
|
+
return { ok: false, message: "The path is empty.", available: [] };
|
|
115
|
+
// Walk the named-type table where the path has a type name, else the
|
|
116
|
+
// operation's inline schema.
|
|
117
|
+
let typeName = "type" in root ? root.type : undefined;
|
|
118
|
+
let inline = "type" in root ? undefined : root.inputSchema;
|
|
119
|
+
let expression;
|
|
120
|
+
let field;
|
|
121
|
+
const label = "type" in root ? root.type : root.label ?? root.tool;
|
|
122
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
123
|
+
const segment = segments[index];
|
|
124
|
+
const at = index === 0 ? label : label + " " + segments.slice(0, index).join(".");
|
|
125
|
+
if (typeName && table) {
|
|
126
|
+
const fields = table.types[typeName].fields ?? {};
|
|
127
|
+
if (!Object.hasOwn(fields, segment))
|
|
128
|
+
return { ok: false, message: at + " (" + typeName + ") has no field \"" + segment + "\".", available: Object.keys(fields) };
|
|
129
|
+
field = fields[segment];
|
|
130
|
+
expression = field.type;
|
|
131
|
+
inline = undefined;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const object = inlineObject(inline);
|
|
135
|
+
const properties = object?.properties ?? {};
|
|
136
|
+
if (!object || !Object.hasOwn(properties, segment)) {
|
|
137
|
+
return { ok: false, message: object ? at + " has no field \"" + segment + "\"." : at + " is not an object; the path cannot continue past it.", available: Object.keys(properties) };
|
|
138
|
+
}
|
|
139
|
+
const child = properties[segment];
|
|
140
|
+
const topLevel = index === 0 && !("type" in root) ? table?.args[root.tool]?.[segment] : undefined;
|
|
141
|
+
expression = topLevel ?? inlineType(child);
|
|
142
|
+
field = {
|
|
143
|
+
type: expression,
|
|
144
|
+
...(Array.isArray(object.required) && object.required.includes(segment) ? { required: true } : {}),
|
|
145
|
+
...(typeof child.description === "string" ? { description: child.description } : {}),
|
|
146
|
+
};
|
|
147
|
+
inline = child;
|
|
148
|
+
}
|
|
149
|
+
typeName = table ? objectTypeOf(table, expression) : undefined;
|
|
150
|
+
if (!typeName && index < segments.length - 1 && !inlineObject(inline)) {
|
|
151
|
+
return { ok: false, message: label + " " + segments.slice(0, index + 1).join(".") + " is " + expression + ", not an object; the path cannot continue past it.", available: [] };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const lines = [label + " " + segments.join(".") + ": " + field.type + (field.required ? " (required)" : "")];
|
|
155
|
+
const description = prose(field.description);
|
|
156
|
+
if (description)
|
|
157
|
+
lines.push(description);
|
|
158
|
+
const named = namedTypesIn(table, expression);
|
|
159
|
+
if (named.length > 0 && table) {
|
|
160
|
+
for (const name of named)
|
|
161
|
+
lines.push("", inputTypeText(table, name, lookup));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const object = inlineObject(inline);
|
|
165
|
+
if (object) {
|
|
166
|
+
const required = new Set(object.required ?? []);
|
|
167
|
+
lines.push("", "Fields:");
|
|
168
|
+
for (const [name, child] of Object.entries(object.properties ?? {})) {
|
|
169
|
+
lines.push(fieldLine(name, { type: inlineType(child), ...(required.has(name) ? { required: true } : {}), ...(child.description ? { description: child.description } : {}) }));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { ok: true, text: lines.join("\n") };
|
|
174
|
+
}
|