@cyanheads/openalex-mcp-server 0.7.6 → 0.7.8
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 +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +9 -9
- package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +4 -0
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +4 -0
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js +10 -0
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +4 -2
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js +52 -19
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +4 -3
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +76 -34
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/mcp-server/tools/render-budget.d.ts +3 -1
- package/dist/mcp-server/tools/render-budget.d.ts.map +1 -1
- package/dist/mcp-server/tools/render-budget.js +7 -2
- package/dist/mcp-server/tools/render-budget.js.map +1 -1
- package/dist/services/openalex/budget.d.ts +17 -4
- package/dist/services/openalex/budget.d.ts.map +1 -1
- package/dist/services/openalex/budget.js +18 -4
- package/dist/services/openalex/budget.js.map +1 -1
- package/dist/services/openalex/field-catalog.json +10 -0
- package/dist/services/openalex/openalex-service.d.ts +41 -1
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +158 -0
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/types.d.ts +6 -1
- package/dist/services/openalex/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -10,25 +10,45 @@ import { getOpenAlexService } from '../../../services/openalex/openalex-service.
|
|
|
10
10
|
import { ENTITY_TYPES } from '../../../services/openalex/types.js';
|
|
11
11
|
const SEMANTIC_PER_PAGE_CAP = 50;
|
|
12
12
|
const SAMPLE_MAX = 100;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The parameters that shape a *list* query, each paired with its echo rendering. Ground truth
|
|
15
|
+
* is `OpenAlexService.search()`: its `id` branch requests `/{entity_type}/{id}` with `select`
|
|
16
|
+
* alone, so every parameter named here is dropped on that path — by OpenAlex itself, which
|
|
17
|
+
* answers an entity-by-ID URL identically whether or not search params ride along.
|
|
18
|
+
*
|
|
19
|
+
* Pagination (`per_page`, `cursor`) is deliberately absent: a singleton lookup is one record by
|
|
20
|
+
* definition, and `per_page` always carries its schema default, so a caller's intent there
|
|
21
|
+
* cannot be told apart from the absence of one. Naming it would be noise, not a warning.
|
|
22
|
+
*/
|
|
23
|
+
function searchOnlyParams(input) {
|
|
24
|
+
const parts = [];
|
|
17
25
|
if (input.query)
|
|
18
|
-
parts.push(`query="${input.query}"`);
|
|
26
|
+
parts.push({ name: 'query', rendered: `query="${input.query}"` });
|
|
19
27
|
if (input.search_mode && input.search_mode !== 'keyword') {
|
|
20
|
-
parts.push(`search_mode=${input.search_mode}`);
|
|
28
|
+
parts.push({ name: 'search_mode', rendered: `search_mode=${input.search_mode}` });
|
|
21
29
|
}
|
|
22
30
|
if (input.filters && Object.keys(input.filters).length > 0) {
|
|
23
|
-
parts.push(`filters=${JSON.stringify(input.filters)}`);
|
|
31
|
+
parts.push({ name: 'filters', rendered: `filters=${JSON.stringify(input.filters)}` });
|
|
24
32
|
}
|
|
25
33
|
if (input.sort)
|
|
26
|
-
parts.push(`sort=${input.sort}`);
|
|
27
|
-
if (input.sample !== undefined)
|
|
28
|
-
parts.push(`sample=${input.sample}`);
|
|
34
|
+
parts.push({ name: 'sort', rendered: `sort=${input.sort}` });
|
|
35
|
+
if (input.sample !== undefined) {
|
|
36
|
+
parts.push({ name: 'sample', rendered: `sample=${input.sample}` });
|
|
37
|
+
}
|
|
29
38
|
if (input.seed !== undefined)
|
|
30
|
-
parts.push(`seed=${input.seed}`);
|
|
31
|
-
return parts
|
|
39
|
+
parts.push({ name: 'seed', rendered: `seed=${input.seed}` });
|
|
40
|
+
return parts;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Echo the criteria that actually ran. An `id` lookup echoes `entity_type` and `id` only —
|
|
44
|
+
* echoing a filter the singleton path never applied reads as confirmation that it did, which
|
|
45
|
+
* is the one thing this field exists not to do.
|
|
46
|
+
*/
|
|
47
|
+
function buildSearchEcho(input) {
|
|
48
|
+
const parts = [`entity_type=${input.entity_type}`];
|
|
49
|
+
if (input.id)
|
|
50
|
+
return [...parts, `id=${input.id}`].join(' | ');
|
|
51
|
+
return [...parts, ...searchOnlyParams(input).map((part) => part.rendered)].join(' | ');
|
|
32
52
|
}
|
|
33
53
|
export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
34
54
|
description: 'Search, filter, sort, or retrieve by ID. Covers all OpenAlex entity types (works, authors, sources, institutions, topics, keywords, publishers, funders). Pass `id` to retrieve a single entity. Otherwise, use `query` and/or `filters` for discovery. Supports keyword search with boolean operators, exact phrase matching, and AI semantic search. Use openalex_resolve_name to resolve names to IDs before filtering. Searches and ID lookups return a curated set of fields by default; pass `select` to override with specific fields, or `["*"]` for the full record.',
|
|
@@ -38,19 +58,19 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
38
58
|
{
|
|
39
59
|
reason: 'semantic_per_page_cap',
|
|
40
60
|
code: JsonRpcErrorCode.ValidationError,
|
|
41
|
-
when: `per_page
|
|
61
|
+
when: `A search (no \`id\`) set per_page above the semantic-search cap of ${SEMANTIC_PER_PAGE_CAP}.`,
|
|
42
62
|
recovery: `Reduce per_page to ${SEMANTIC_PER_PAGE_CAP} or less, or switch search_mode to keyword.`,
|
|
43
63
|
},
|
|
44
64
|
{
|
|
45
65
|
reason: 'sample_with_cursor',
|
|
46
66
|
code: JsonRpcErrorCode.ValidationError,
|
|
47
|
-
when: '
|
|
67
|
+
when: 'A search (no `id`) provided both `sample` and `cursor`.',
|
|
48
68
|
recovery: 'Sampling returns a single page only; remove `cursor` or remove `sample`.',
|
|
49
69
|
},
|
|
50
70
|
{
|
|
51
71
|
reason: 'seed_without_sample',
|
|
52
72
|
code: JsonRpcErrorCode.ValidationError,
|
|
53
|
-
when: '`
|
|
73
|
+
when: 'A search (no `id`) provided `seed` without `sample`.',
|
|
54
74
|
recovery: 'Pass `sample` to enable random sampling, or remove `seed`.',
|
|
55
75
|
},
|
|
56
76
|
{
|
|
@@ -141,7 +161,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
141
161
|
id: z
|
|
142
162
|
.string()
|
|
143
163
|
.optional()
|
|
144
|
-
.describe('Retrieve a single entity by ID. Supports: OpenAlex ID ("W2741809807"), DOI ("10.1038/nature12373"), ORCID ("0000-0002-1825-0097"), ROR ("https://ror.org/00hx57361"), PMID ("12345678"), PMCID ("PMC1234567"), ISSN ("1234-5678"). When provided,
|
|
164
|
+
.describe('Retrieve a single entity by ID. Supports: OpenAlex ID ("W2741809807"), DOI ("10.1038/nature12373"), ORCID ("0000-0002-1825-0097"), ROR ("https://ror.org/00hx57361"), PMID ("12345678"), PMCID ("PMC1234567"), ISSN ("1234-5678"). When provided, `query`, `search_mode`, `filters`, `sort`, `sample`, and `seed` are not applied — the returned record is the entity at that ID regardless of them, and the response `notice` names any you passed. `select` still applies: the curated per-entity-type default is returned unless you pass `select` (use `["*"]` for the complete record). To filter, drop `id` and search. Use openalex_resolve_name to find the ID if unknown.'),
|
|
145
165
|
query: z
|
|
146
166
|
.string()
|
|
147
167
|
.optional()
|
|
@@ -168,7 +188,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
168
188
|
.min(1)
|
|
169
189
|
.max(100)
|
|
170
190
|
.default(25)
|
|
171
|
-
.describe('Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode="semantic", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream).'),
|
|
191
|
+
.describe('Results per page (1-100). Default 25. Semantic search caps at 50 — when search_mode="semantic", set per_page ≤ 50 (also subject to a 1 req/sec rate limit upstream). The cap applies to searches only; an `id` lookup returns its one record regardless of both.'),
|
|
172
192
|
cursor: z
|
|
173
193
|
.string()
|
|
174
194
|
.optional()
|
|
@@ -183,7 +203,7 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
183
203
|
seed: z
|
|
184
204
|
.string()
|
|
185
205
|
.optional()
|
|
186
|
-
.describe('Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect
|
|
206
|
+
.describe('Deterministic seed for `sample`. Same seed + same filters = same results — pass when reproducibility matters. Has no effect without `sample`, and a search that passes it alone is rejected.'),
|
|
187
207
|
}),
|
|
188
208
|
output: z.object({
|
|
189
209
|
meta: z
|
|
@@ -215,12 +235,12 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
215
235
|
enrichment: {
|
|
216
236
|
echo: z
|
|
217
237
|
.string()
|
|
218
|
-
.describe('Compact echo of the
|
|
238
|
+
.describe('Compact echo of the criteria that actually ran (entity_type, query, filters, sort, search_mode) — surfaces what was searched when results are empty. An `id` lookup echoes entity_type and id alone, because the search criteria are not applied on that path.'),
|
|
219
239
|
totalCount: z.number().describe('Total results matching the query/filters across all pages.'),
|
|
220
240
|
notice: z
|
|
221
241
|
.string()
|
|
222
242
|
.optional()
|
|
223
|
-
.describe('
|
|
243
|
+
.describe('Guidance notice. Set when results are empty (echoes the criteria and suggests how to broaden) or when an `id` lookup was passed search criteria it does not apply (names them). Absent otherwise.'),
|
|
224
244
|
budget: z
|
|
225
245
|
.object({
|
|
226
246
|
costUsd: z
|
|
@@ -230,6 +250,10 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
230
250
|
resetsInSeconds: z
|
|
231
251
|
.number()
|
|
232
252
|
.describe('Seconds until the daily budget refills (midnight UTC).'),
|
|
253
|
+
prepaidRemainingUsd: z
|
|
254
|
+
.number()
|
|
255
|
+
.optional()
|
|
256
|
+
.describe('USD left in the prepaid balance — a separate pool OpenAlex draws on only after the daily allowance runs out, and which the daily reset does not refill. Add it to `remainingUsd` for the full spendable amount. Absent when the account holds no prepaid balance.'),
|
|
233
257
|
})
|
|
234
258
|
.optional()
|
|
235
259
|
.describe('What this call cost against the OpenAlex daily budget and what is left of it. Price a full traversal before committing to it: `totalCount` ÷ `per_page` × `costUsd` against `remainingUsd`. Absent when OpenAlex omitted the accounting headers.'),
|
|
@@ -240,19 +264,29 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
240
264
|
budget: { render: renderBudgetTrailer },
|
|
241
265
|
},
|
|
242
266
|
async handler(input, ctx) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (input.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
267
|
+
/**
|
|
268
|
+
* All three checks below constrain a *list* query. `id` takes the singleton path in
|
|
269
|
+
* `OpenAlexService.search()`, which reads only `entity_type`, `id`, and `select` — so
|
|
270
|
+
* search_mode, per_page, cursor, sample, and seed are unread there, and rejecting an ID
|
|
271
|
+
* lookup over how they relate to each other fails it on constraints it never met. The
|
|
272
|
+
* dropped-parameter notice below reports them instead. The `id` predicate is truthiness,
|
|
273
|
+
* not `!== undefined`, to match the service branch: an empty-string `id` parses and lists.
|
|
274
|
+
*/
|
|
275
|
+
if (!input.id) {
|
|
276
|
+
if (input.search_mode === 'semantic' && input.per_page > SEMANTIC_PER_PAGE_CAP) {
|
|
277
|
+
throw ctx.fail('semantic_per_page_cap', `Semantic search supports at most ${SEMANTIC_PER_PAGE_CAP} results per page. Reduce per_page or switch search_mode.`, {
|
|
278
|
+
...ctx.recoveryFor('semantic_per_page_cap'),
|
|
279
|
+
searchMode: input.search_mode,
|
|
280
|
+
perPage: input.per_page,
|
|
281
|
+
cap: SEMANTIC_PER_PAGE_CAP,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (input.sample !== undefined && input.cursor !== undefined) {
|
|
285
|
+
throw ctx.fail('sample_with_cursor', 'Sampling returns one page only — `sample` cannot be combined with `cursor` pagination.', { ...ctx.recoveryFor('sample_with_cursor'), sample: input.sample, cursor: input.cursor });
|
|
286
|
+
}
|
|
287
|
+
if (input.seed !== undefined && input.sample === undefined) {
|
|
288
|
+
throw ctx.fail('seed_without_sample', '`seed` is only meaningful with `sample` — pass `sample` to enable random sampling.', { ...ctx.recoveryFor('seed_without_sample'), seed: input.seed });
|
|
289
|
+
}
|
|
256
290
|
}
|
|
257
291
|
const service = getOpenAlexService();
|
|
258
292
|
const result = await service.search({
|
|
@@ -277,7 +311,15 @@ export const searchEntitiesTool = tool('openalex_search_entities', {
|
|
|
277
311
|
});
|
|
278
312
|
const echo = buildSearchEcho(input);
|
|
279
313
|
ctx.enrich({ echo, totalCount: result.meta.count });
|
|
280
|
-
|
|
314
|
+
// An `id` lookup always returns its one record or throws, so it never reaches the
|
|
315
|
+
// empty-results notice — the two branches are exclusive, and the dropped-parameter
|
|
316
|
+
// warning is the more useful of the two on that path.
|
|
317
|
+
const ignored = input.id ? searchOnlyParams(input).map((part) => part.name) : [];
|
|
318
|
+
if (ignored.length > 0) {
|
|
319
|
+
const [verb, pronoun] = ignored.length === 1 ? ['was', 'it'] : ['were', 'them'];
|
|
320
|
+
ctx.enrich.notice(`\`id\` takes precedence — ${ignored.join(', ')} ${verb} not applied, and the record below is the entity at that ID regardless of ${pronoun}. Drop \`id\` to run ${pronoun} as a search.`);
|
|
321
|
+
}
|
|
322
|
+
else if (result.results.length === 0) {
|
|
281
323
|
ctx.enrich.notice(`No matches for ${echo}. Try broadening the query, removing filters, or switching search_mode.`);
|
|
282
324
|
}
|
|
283
325
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-entities.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAqB,MAAM,8BAA8B,CAAC;AAE/E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,UAAU,GAAG,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"search-entities.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/search-entities.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,9 @@ import type { UpstreamBudget } from '../../services/openalex/budget.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* One-line budget trailer. `costUsd: 0` renders as "free" rather than `$0.0000` because it
|
|
10
10
|
* carries real signal — it means the call was a singleton ID lookup, which OpenAlex does not
|
|
11
|
-
* bill, so batching by ID beats paging a filtered list.
|
|
11
|
+
* bill, so batching by ID beats paging a filtered list. The prepaid segment appears only for
|
|
12
|
+
* an account holding that balance: without it, "left today" is the whole spendable figure;
|
|
13
|
+
* with it, the daily line alone reads as broke on an account that keeps serving.
|
|
12
14
|
*/
|
|
13
15
|
export declare function renderBudgetTrailer(budget: UpstreamBudget | undefined): string;
|
|
14
16
|
//# sourceMappingURL=render-budget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-budget.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAgBpE
|
|
1
|
+
{"version":3,"file":"render-budget.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAgBpE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAQ9E"}
|
|
@@ -20,12 +20,17 @@ function formatCountdown(seconds) {
|
|
|
20
20
|
/**
|
|
21
21
|
* One-line budget trailer. `costUsd: 0` renders as "free" rather than `$0.0000` because it
|
|
22
22
|
* carries real signal — it means the call was a singleton ID lookup, which OpenAlex does not
|
|
23
|
-
* bill, so batching by ID beats paging a filtered list.
|
|
23
|
+
* bill, so batching by ID beats paging a filtered list. The prepaid segment appears only for
|
|
24
|
+
* an account holding that balance: without it, "left today" is the whole spendable figure;
|
|
25
|
+
* with it, the daily line alone reads as broke on an account that keeps serving.
|
|
24
26
|
*/
|
|
25
27
|
export function renderBudgetTrailer(budget) {
|
|
26
28
|
if (!budget)
|
|
27
29
|
return '';
|
|
28
30
|
const cost = budget.costUsd === 0 ? 'free' : formatUsd(budget.costUsd);
|
|
29
|
-
|
|
31
|
+
const prepaid = budget.prepaidRemainingUsd === undefined
|
|
32
|
+
? ''
|
|
33
|
+
: ` + ${formatUsd(budget.prepaidRemainingUsd)} prepaid`;
|
|
34
|
+
return `**Budget:** ${cost} this call · ${formatUsd(budget.remainingUsd)} left today${prepaid} (resets in ${formatCountdown(budget.resetsInSeconds)})`;
|
|
30
35
|
}
|
|
31
36
|
//# sourceMappingURL=render-budget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-budget.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,kGAAkG;AAClG,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACvC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC;AACzD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"render-budget.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,kGAAkG;AAClG,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,OAAe;IACtC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACvC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAkC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvE,MAAM,OAAO,GACX,MAAM,CAAC,mBAAmB,KAAK,SAAS;QACtC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,MAAM,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC,UAAU,CAAC;IAC5D,OAAO,eAAe,IAAI,gBAAgB,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,cAAc,OAAO,eAAe,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC;AACzJ,CAAC"}
|
|
@@ -10,11 +10,20 @@
|
|
|
10
10
|
* $1/day with a free account key) that refills at midnight UTC. It reports the accounting
|
|
11
11
|
* on every successful response's headers. `X-RateLimit-Remaining` is a *credit* count
|
|
12
12
|
* against a separate 1000-credit ceiling — proportional to the dollar figure and therefore
|
|
13
|
-
* redundant — so only the `-USD` headers are carried here.
|
|
13
|
+
* redundant — so only the `-USD` headers are carried here. `X-RateLimit-Onetime-Remaining`
|
|
14
|
+
* is excluded on the same grounds and one more: it carries no unit, and OpenAlex documents
|
|
15
|
+
* neither the header nor any matching field on its `/rate-limit` endpoint, so there is no
|
|
16
|
+
* way to say what a caller could spend it on.
|
|
14
17
|
*/
|
|
15
18
|
export interface UpstreamBudget {
|
|
16
19
|
/** USD this call spent, summed over every upstream request it issued. */
|
|
17
20
|
costUsd: number;
|
|
21
|
+
/**
|
|
22
|
+
* USD left in the prepaid balance — a separate pool OpenAlex draws on once the daily
|
|
23
|
+
* allowance is spent, and which does not refill at the reset. Omitted when the account
|
|
24
|
+
* holds none, which is every anonymous call.
|
|
25
|
+
*/
|
|
26
|
+
prepaidRemainingUsd?: number;
|
|
18
27
|
/** USD left in today's budget as of the most recent upstream response. */
|
|
19
28
|
remainingUsd: number;
|
|
20
29
|
/** Seconds until the daily budget refills. */
|
|
@@ -30,7 +39,9 @@ export interface UpstreamBudget {
|
|
|
30
39
|
* `costUsd: 0` is the most interesting signal — fetching by ID is free.
|
|
31
40
|
*
|
|
32
41
|
* Returns `undefined` unless the full set parses. A partial read would report a cost with no
|
|
33
|
-
* budget to weigh it against, which is worse than reporting nothing.
|
|
42
|
+
* budget to weigh it against, which is worse than reporting nothing. The prepaid balance sits
|
|
43
|
+
* outside that gate — it is an independent pool most accounts don't hold, so its absence says
|
|
44
|
+
* nothing about whether the daily accounting is readable.
|
|
34
45
|
*/
|
|
35
46
|
export declare function parseUpstreamBudget(headers: Headers): UpstreamBudget | undefined;
|
|
36
47
|
/**
|
|
@@ -38,9 +49,11 @@ export declare function parseUpstreamBudget(headers: Headers): UpstreamBudget |
|
|
|
38
49
|
*
|
|
39
50
|
* Cost adds up — a single tool call can issue several billed requests (a sampled search pairs
|
|
40
51
|
* the sample with a population count; the citation-graph walk gates on a seed lookup), and the
|
|
41
|
-
* caller pays for each. The
|
|
52
|
+
* caller pays for each. The counters run *down*, so the smaller reading is the more recent
|
|
42
53
|
* one; taking the minimum also stays deterministic when requests are issued in parallel and
|
|
43
|
-
* resolve in arbitrary order.
|
|
54
|
+
* resolve in arbitrary order. The prepaid balance follows the same rule with an absent side
|
|
55
|
+
* read as zero — one request seeing the pool empty means it is empty — which lands back on
|
|
56
|
+
* the omit-when-nothing-is-left convention `parseUpstreamBudget` sets.
|
|
44
57
|
*/
|
|
45
58
|
export declare function mergeUpstreamBudget(previous: UpstreamBudget | undefined, next: UpstreamBudget): UpstreamBudget;
|
|
46
59
|
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/budget.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/budget.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;CACzB;AASD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAgBhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,IAAI,EAAE,cAAc,GACnB,cAAc,CAYhB"}
|
|
@@ -20,7 +20,9 @@ function readNumericHeader(headers, name) {
|
|
|
20
20
|
* `costUsd: 0` is the most interesting signal — fetching by ID is free.
|
|
21
21
|
*
|
|
22
22
|
* Returns `undefined` unless the full set parses. A partial read would report a cost with no
|
|
23
|
-
* budget to weigh it against, which is worse than reporting nothing.
|
|
23
|
+
* budget to weigh it against, which is worse than reporting nothing. The prepaid balance sits
|
|
24
|
+
* outside that gate — it is an independent pool most accounts don't hold, so its absence says
|
|
25
|
+
* nothing about whether the daily accounting is readable.
|
|
24
26
|
*/
|
|
25
27
|
export function parseUpstreamBudget(headers) {
|
|
26
28
|
const costUsd = readNumericHeader(headers, 'x-ratelimit-cost-usd');
|
|
@@ -28,24 +30,36 @@ export function parseUpstreamBudget(headers) {
|
|
|
28
30
|
const resetsInSeconds = readNumericHeader(headers, 'x-ratelimit-reset');
|
|
29
31
|
if (costUsd === undefined || remainingUsd === undefined || resetsInSeconds === undefined)
|
|
30
32
|
return;
|
|
31
|
-
|
|
33
|
+
const prepaidRemainingUsd = readNumericHeader(headers, 'x-ratelimit-prepaid-remaining-usd');
|
|
34
|
+
return {
|
|
35
|
+
costUsd,
|
|
36
|
+
remainingUsd,
|
|
37
|
+
resetsInSeconds,
|
|
38
|
+
// A zero reading is the no-prepaid-balance case, indistinguishable from an account that
|
|
39
|
+
// has spent it — either way there is nothing extra to spend, so report nothing extra.
|
|
40
|
+
...(prepaidRemainingUsd !== undefined && prepaidRemainingUsd > 0 && { prepaidRemainingUsd }),
|
|
41
|
+
};
|
|
32
42
|
}
|
|
33
43
|
/**
|
|
34
44
|
* Fold one response's accounting into the running total for a tool call.
|
|
35
45
|
*
|
|
36
46
|
* Cost adds up — a single tool call can issue several billed requests (a sampled search pairs
|
|
37
47
|
* the sample with a population count; the citation-graph walk gates on a seed lookup), and the
|
|
38
|
-
* caller pays for each. The
|
|
48
|
+
* caller pays for each. The counters run *down*, so the smaller reading is the more recent
|
|
39
49
|
* one; taking the minimum also stays deterministic when requests are issued in parallel and
|
|
40
|
-
* resolve in arbitrary order.
|
|
50
|
+
* resolve in arbitrary order. The prepaid balance follows the same rule with an absent side
|
|
51
|
+
* read as zero — one request seeing the pool empty means it is empty — which lands back on
|
|
52
|
+
* the omit-when-nothing-is-left convention `parseUpstreamBudget` sets.
|
|
41
53
|
*/
|
|
42
54
|
export function mergeUpstreamBudget(previous, next) {
|
|
43
55
|
if (previous === undefined)
|
|
44
56
|
return next;
|
|
57
|
+
const prepaidRemainingUsd = Math.min(previous.prepaidRemainingUsd ?? 0, next.prepaidRemainingUsd ?? 0);
|
|
45
58
|
return {
|
|
46
59
|
costUsd: previous.costUsd + next.costUsd,
|
|
47
60
|
remainingUsd: Math.min(previous.remainingUsd, next.remainingUsd),
|
|
48
61
|
resetsInSeconds: Math.min(previous.resetsInSeconds, next.resetsInSeconds),
|
|
62
|
+
...(prepaidRemainingUsd > 0 && { prepaidRemainingUsd }),
|
|
49
63
|
};
|
|
50
64
|
}
|
|
51
65
|
//# sourceMappingURL=budget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../../src/services/openalex/budget.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../../src/services/openalex/budget.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6BH,SAAS,iBAAiB,CAAC,OAAgB,EAAE,IAAY;IACvD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,CAAC,CAAC;IACnE,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IAC7E,MAAM,eAAe,GAAG,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAExE,IAAI,OAAO,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO;IAEjG,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,OAAO,EAAE,mCAAmC,CAAC,CAAC;IAC5F,OAAO;QACL,OAAO;QACP,YAAY;QACZ,eAAe;QACf,wFAAwF;QACxF,sFAAsF;QACtF,GAAG,CAAC,mBAAmB,KAAK,SAAS,IAAI,mBAAmB,GAAG,CAAC,IAAI,EAAE,mBAAmB,EAAE,CAAC;KAC7F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAoC,EACpC,IAAoB;IAEpB,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAClC,QAAQ,CAAC,mBAAmB,IAAI,CAAC,EACjC,IAAI,CAAC,mBAAmB,IAAI,CAAC,CAC9B,CAAC;IACF,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO;QACxC,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC;QAChE,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,eAAe,CAAC;QACzE,GAAG,CAAC,mBAAmB,GAAG,CAAC,IAAI,EAAE,mBAAmB,EAAE,CAAC;KACxD,CAAC;AACJ,CAAC"}
|
|
@@ -288,6 +288,7 @@
|
|
|
288
288
|
"has_orcid",
|
|
289
289
|
"id",
|
|
290
290
|
"ids.openalex",
|
|
291
|
+
"last_known_authorships.institutions.lineage",
|
|
291
292
|
"last_known_institutions.continent",
|
|
292
293
|
"last_known_institutions.country_code",
|
|
293
294
|
"last_known_institutions.id",
|
|
@@ -306,6 +307,7 @@
|
|
|
306
307
|
"summary_stats.2yr_mean_citedness",
|
|
307
308
|
"summary_stats.h_index",
|
|
308
309
|
"summary_stats.i10_index",
|
|
310
|
+
"text.search",
|
|
309
311
|
"to_created_date",
|
|
310
312
|
"to_updated_date",
|
|
311
313
|
"topic_share.id",
|
|
@@ -369,6 +371,7 @@
|
|
|
369
371
|
"is_in_jstage_since_year",
|
|
370
372
|
"is_oa",
|
|
371
373
|
"is_ojs",
|
|
374
|
+
"is_preprint_repository",
|
|
372
375
|
"issn",
|
|
373
376
|
"issn_l",
|
|
374
377
|
"last_publication_year",
|
|
@@ -378,6 +381,7 @@
|
|
|
378
381
|
"summary_stats.2yr_mean_citedness",
|
|
379
382
|
"summary_stats.h_index",
|
|
380
383
|
"summary_stats.i10_index",
|
|
384
|
+
"text.search",
|
|
381
385
|
"to_created_date",
|
|
382
386
|
"to_updated_date",
|
|
383
387
|
"topic_share.id",
|
|
@@ -408,6 +412,7 @@
|
|
|
408
412
|
"is_in_jstage_since_year",
|
|
409
413
|
"is_in_scielo",
|
|
410
414
|
"is_ojs",
|
|
415
|
+
"is_preprint_repository",
|
|
411
416
|
"is_indexed_in_scopus",
|
|
412
417
|
"is_core",
|
|
413
418
|
"oa_flip_year",
|
|
@@ -461,6 +466,7 @@
|
|
|
461
466
|
"summary_stats.2yr_mean_citedness",
|
|
462
467
|
"summary_stats.h_index",
|
|
463
468
|
"summary_stats.i10_index",
|
|
469
|
+
"text.search",
|
|
464
470
|
"topic_share.id",
|
|
465
471
|
"topics.id",
|
|
466
472
|
"type",
|
|
@@ -517,6 +523,7 @@
|
|
|
517
523
|
"keywords.search",
|
|
518
524
|
"openalex",
|
|
519
525
|
"subfield.id",
|
|
526
|
+
"text.search",
|
|
520
527
|
"works_count"
|
|
521
528
|
],
|
|
522
529
|
"select": [
|
|
@@ -546,6 +553,7 @@
|
|
|
546
553
|
"display_name.search",
|
|
547
554
|
"from_created_date",
|
|
548
555
|
"id",
|
|
556
|
+
"text.search",
|
|
549
557
|
"works_count"
|
|
550
558
|
],
|
|
551
559
|
"select": [
|
|
@@ -582,6 +590,7 @@
|
|
|
582
590
|
"summary_stats.2yr_mean_citedness",
|
|
583
591
|
"summary_stats.h_index",
|
|
584
592
|
"summary_stats.i10_index",
|
|
593
|
+
"text.search",
|
|
585
594
|
"wikidata",
|
|
586
595
|
"works_count"
|
|
587
596
|
],
|
|
@@ -633,6 +642,7 @@
|
|
|
633
642
|
"summary_stats.2yr_mean_citedness",
|
|
634
643
|
"summary_stats.h_index",
|
|
635
644
|
"summary_stats.i10_index",
|
|
645
|
+
"text.search",
|
|
636
646
|
"wikidata",
|
|
637
647
|
"works_count"
|
|
638
648
|
],
|
|
@@ -3,17 +3,43 @@
|
|
|
3
3
|
* @module services/openalex/openalex-service
|
|
4
4
|
*/
|
|
5
5
|
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
6
|
-
import { type AnalyzeParams, type AnalyzeResult, type AutocompleteParams, type AutocompleteResult, type EntityType, type SearchParams, type SearchResult } from './types.js';
|
|
6
|
+
import { type AnalyzeParams, type AnalyzeResult, type AutocompleteParams, type AutocompleteRecord, type AutocompleteResult, type EntityRecord, type EntityType, type SearchParams, type SearchResult } from './types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Detect ID format and return the API path segment.
|
|
9
9
|
* "10.1038/nature12373" → "doi:10.1038/nature12373"
|
|
10
10
|
* "https://doi.org/10.1038/nature12373" → "doi:10.1038/nature12373"
|
|
11
11
|
* "0000-0002-1825-0097" → "orcid:0000-0002-1825-0097"
|
|
12
|
+
* "https://orcid.org/0000-0002-1825-0097" → "orcid:https://orcid.org/0000-0002-1825-0097"
|
|
12
13
|
* "https://ror.org/00hx57361" → "ror:https://ror.org/00hx57361"
|
|
14
|
+
* "013meh722" → "ror:013meh722"
|
|
13
15
|
* "PMC1234567" → "pmcid:PMC1234567"
|
|
14
16
|
* "W2741809807" → "W2741809807"
|
|
15
17
|
*/
|
|
16
18
|
export declare function normalizeId(id: string): string;
|
|
19
|
+
/** An identifier the deterministic by-ID path can resolve, with the scheme it was read as. */
|
|
20
|
+
export interface ResolvedIdentifier {
|
|
21
|
+
entityType: EntityType;
|
|
22
|
+
/** The value in the form the OpenAlex path segment expects (`doi:10.1038/…`, `W2741809807`). */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Identifier scheme, for caller-facing messages: `doi`, `orcid`, `openalex`, … */
|
|
25
|
+
scheme: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classify a query as an identifier, or return `undefined` for a name.
|
|
29
|
+
*
|
|
30
|
+
* `normalizeId()` already knows every shape the by-ID lookup accepts, bare and URL-form alike,
|
|
31
|
+
* and stamps external ones with their scheme prefix — so detection reduces to reading back what
|
|
32
|
+
* it produced. Names never survive that: a name with a colon in it yields a prefix that is in no
|
|
33
|
+
* scheme table, and anything else fails the native-ID pattern.
|
|
34
|
+
*/
|
|
35
|
+
export declare function inferIdentifier(query: string): ResolvedIdentifier | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Shape a singleton entity record like an autocomplete match, so both resolution paths of
|
|
38
|
+
* `openalex_resolve_name` return one result type. `entity_type` is singularized to match what
|
|
39
|
+
* the autocomplete endpoint emits (`work`, not `works`), and `works_count` stays null on works —
|
|
40
|
+
* a work has no works of its own, which is the same thing autocomplete reports.
|
|
41
|
+
*/
|
|
42
|
+
export declare function toAutocompleteRecord(entityType: EntityType, record: EntityRecord): AutocompleteRecord;
|
|
17
43
|
declare class OpenAlexService {
|
|
18
44
|
private readonly baseUrl;
|
|
19
45
|
private readonly apiKey;
|
|
@@ -27,6 +53,20 @@ declare class OpenAlexService {
|
|
|
27
53
|
search(params: SearchParams, ctx: Context): Promise<SearchResult>;
|
|
28
54
|
/** Group-by aggregation. */
|
|
29
55
|
analyze(params: AnalyzeParams, ctx: Context): Promise<AnalyzeResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve an identifier to the single entity it addresses, shaped as an autocomplete match.
|
|
58
|
+
*
|
|
59
|
+
* Autocomplete matches on `display_name` and the external-ID URL, so whether an identifier
|
|
60
|
+
* resolves through it depends on scheme, bare-vs-URL form, and entity type — a bare ORCID or
|
|
61
|
+
* a funder's own OpenAlex ID find nothing, while the identical query on works succeeds. This
|
|
62
|
+
* path goes to `/{entity_type}/{id}` instead, where every accepted shape resolves or does not,
|
|
63
|
+
* with no in-between.
|
|
64
|
+
*
|
|
65
|
+
* A miss returns no results rather than throwing. Autocomplete answers an unmatched query with
|
|
66
|
+
* an empty list, and routing identifiers through here is meant to make them behave *more* like
|
|
67
|
+
* the front door, not to hand one class of query a hard failure the rest never sees.
|
|
68
|
+
*/
|
|
69
|
+
resolveIdentifier(identifier: ResolvedIdentifier, ctx: Context): Promise<AutocompleteResult>;
|
|
30
70
|
/** Autocomplete name resolution. */
|
|
31
71
|
autocomplete(params: AutocompleteParams, ctx: Context): Promise<AutocompleteResult>;
|
|
32
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAuBtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAuBtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAGvB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAgEpB;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAuD9C;AAoCD,8FAA8F;AAC9F,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,gGAAgG;IAChG,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAa7E;AAuJD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,GACnB,kBAAkB,CAUpB;AAicD,cAAM,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,cAKC;IAED,yFAAyF;IACzF,OAAO,CAAC,OAAO;IAiDf,OAAO,CAAC,2BAA2B;IAoEnC,OAAO,CAAC,aAAa;IAqCrB,sEAAsE;IAChE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA0FtE;IAED,4BAA4B;IACtB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAkDzE;IAED;;;;;;;;;;;;OAYG;IACG,iBAAiB,CACrB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,kBAAkB,CAAC,CAkB7B;IAED,oCAAoC;IAC9B,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsBxF;CACF;AAID,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAAC,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAE5F"}
|