@cyanheads/openalex-mcp-server 0.7.16 → 0.9.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 +48 -33
- package/CLAUDE.md +48 -33
- package/Dockerfile +98 -33
- package/README.md +23 -16
- package/changelog/0.8.x/0.8.0.md +38 -0
- package/changelog/0.9.x/0.9.0.md +48 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
- package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +28 -3
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +57 -10
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +62 -5
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +100 -52
- 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 +34 -4
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +38 -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 +176 -41
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +75 -4
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +169 -73
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/mcp-server/tools/render-budget.d.ts +4 -2
- package/dist/mcp-server/tools/render-budget.d.ts.map +1 -1
- package/dist/mcp-server/tools/render-budget.js +4 -2
- package/dist/mcp-server/tools/render-budget.js.map +1 -1
- package/dist/mcp-server/tools/render-entity-record.d.ts +12 -1
- package/dist/mcp-server/tools/render-entity-record.d.ts.map +1 -1
- package/dist/mcp-server/tools/render-entity-record.js +23 -8
- package/dist/mcp-server/tools/render-entity-record.js.map +1 -1
- package/dist/mcp-server/tools/response-budget.d.ts +160 -0
- package/dist/mcp-server/tools/response-budget.d.ts.map +1 -0
- package/dist/mcp-server/tools/response-budget.js +489 -0
- package/dist/mcp-server/tools/response-budget.js.map +1 -0
- package/dist/services/openalex/budget.d.ts +3 -1
- package/dist/services/openalex/budget.d.ts.map +1 -1
- package/dist/services/openalex/budget.js +3 -1
- package/dist/services/openalex/budget.js.map +1 -1
- package/dist/services/openalex/field-catalog.json +50 -1
- package/dist/services/openalex/filter-values.d.ts +46 -0
- package/dist/services/openalex/filter-values.d.ts.map +1 -0
- package/dist/services/openalex/filter-values.js +294 -0
- package/dist/services/openalex/filter-values.js.map +1 -0
- package/dist/services/openalex/iso-codes.json +1565 -0
- package/dist/services/openalex/openalex-service.d.ts +181 -16
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +593 -141
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/provider-text.d.ts +13 -5
- package/dist/services/openalex/provider-text.d.ts.map +1 -1
- package/dist/services/openalex/provider-text.js +799 -51
- package/dist/services/openalex/provider-text.js.map +1 -1
- package/dist/services/openalex/types.d.ts +1 -1
- package/dist/services/openalex/types.js +1 -1
- package/package.json +16 -13
- package/server.json +10 -24
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Response-size budget for the tools that return OpenAlex entity records
|
|
3
|
+
* (`openalex_search_entities`, `openalex_get_citation_graph`). Holds each surface — serialized
|
|
4
|
+
* `structuredContent` and the summed `content[]` text, enrichment trailer included — to
|
|
5
|
+
* 64,000 bytes: a list page loses whole records from its end, a record too large on its own has
|
|
6
|
+
* its biggest arrays windowed, and every cut is disclosed with the call that continues it. The
|
|
7
|
+
* same pass flags list records carrying exactly 100 authorships (or the 100 `author_list` entries
|
|
8
|
+
* derived from them), OpenAlex's list-response cap.
|
|
9
|
+
* @module mcp-server/tools/response-budget
|
|
10
|
+
*/
|
|
11
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
12
|
+
import { renderEntityRecord, } from './render-entity-record.js';
|
|
13
|
+
/** Per-surface ceiling: serialized `structuredContent`, and summed `content[]` text. */
|
|
14
|
+
export const RESPONSE_BUDGET_BYTES = 64_000;
|
|
15
|
+
/**
|
|
16
|
+
* The most authorships OpenAlex returns on a record in a list response; the record's `id`
|
|
17
|
+
* lookup returns them all. Exactly this many on a list record may or may not be the whole list.
|
|
18
|
+
*/
|
|
19
|
+
const LIST_AUTHORSHIPS_CAP = 100;
|
|
20
|
+
/** Record arrays the cap applies to: `authorships`, and the `author_list` derived from it. */
|
|
21
|
+
const CAPPED_AUTHOR_FIELDS = ['authorships', 'author_list'];
|
|
22
|
+
/**
|
|
23
|
+
* Bytes reserved on each surface for the `budget` enrichment field. The service writes it from
|
|
24
|
+
* OpenAlex's rate-limit headers through `ctx.enrich`, so its value is not in the handler's hands
|
|
25
|
+
* when the page is measured. The allowance covers its widest realistic form — every number at
|
|
26
|
+
* full double precision, with a prepaid balance — measured at 176 bytes as a JSON member and 108
|
|
27
|
+
* as a trailer line with its separator.
|
|
28
|
+
*/
|
|
29
|
+
export const BUDGET_FIELD_ALLOWANCE_BYTES = 256;
|
|
30
|
+
const BUDGET_LABEL = `${RESPONSE_BUDGET_BYTES.toLocaleString('en-US')}-byte`;
|
|
31
|
+
const CONTINUATION_TOOL = 'openalex_search_entities';
|
|
32
|
+
const OPENALEX_ID_PREFIX = 'https://openalex.org/';
|
|
33
|
+
const NATIVE_OPENALEX_ID = /^[A-Z]\d+$/;
|
|
34
|
+
/**
|
|
35
|
+
* The call that continues a cut, shared by `omitted.next` and every `windows[].next`. Its
|
|
36
|
+
* arguments are data to pass back unchanged, so the schema leaves them loosely typed rather than
|
|
37
|
+
* spelling out the search tool's input a second time.
|
|
38
|
+
*/
|
|
39
|
+
const continuationCallSchema = z.object({
|
|
40
|
+
tool: z.literal(CONTINUATION_TOOL).describe('Tool to call.'),
|
|
41
|
+
arguments: z.object({}).loose().describe('Arguments to pass as-is.'),
|
|
42
|
+
});
|
|
43
|
+
const arrayWindowSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
id: z.string().describe('Bare ID of the record.'),
|
|
46
|
+
field: z.string().describe('Array field.'),
|
|
47
|
+
offset: z.number().describe('Index of the first element shown.'),
|
|
48
|
+
shown: z.number().describe('Elements shown.'),
|
|
49
|
+
total: z.number().describe('Array length as returned to this call.'),
|
|
50
|
+
possibly_capped: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.describe("True for exactly 100 `authorships` or `author_list` entries on a list record (OpenAlex's cap): the list may be longer."),
|
|
53
|
+
next: continuationCallSchema
|
|
54
|
+
.nullable()
|
|
55
|
+
.describe('`id` + `slice` call for the next elements; null at the array end.'),
|
|
56
|
+
})
|
|
57
|
+
.describe('An array returned in part.');
|
|
58
|
+
/** Output field: whole records the budget cut from a list page, and the call returning them. */
|
|
59
|
+
export const omittedOutputSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
ids: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.describe('Bare IDs of the records left out, in page order (a keyword keeps its URL).'),
|
|
64
|
+
next: continuationCallSchema.describe("One call returning those records as a set, in OpenAlex's order, not page order. Budgeted too, so it may carry its own `omitted`."),
|
|
65
|
+
})
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Present when the response budget cut whole records from the page end; pagination continues after the full upstream page.');
|
|
68
|
+
/** Output field: arrays returned in part, or possibly in part, each with its continuation. */
|
|
69
|
+
export const windowsOutputSchema = z
|
|
70
|
+
.array(arrayWindowSchema)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Arrays returned in part — windowed to fit the budget, paged by `slice`, or a possibly capped 100-entry `authorships` or `author_list` — each with its continuation. A `slice` call always carries its window, even for a whole array; otherwise absent when every array is whole.');
|
|
73
|
+
/** Output field: the least this call can return does not fit the budget on its own. */
|
|
74
|
+
export const overBudgetOutputSchema = z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("True when even the least this call can return — the first record's non-array fields (its arrays windowed to empty), or one `slice` element — exceeds the budget.");
|
|
78
|
+
const utf8Bytes = (text) => Buffer.byteLength(text, 'utf8');
|
|
79
|
+
/** `https://openalex.org/W123` → `W123`. Any other form (a keyword URL) passes through whole. */
|
|
80
|
+
export function bareId(id) {
|
|
81
|
+
const tail = id.startsWith(OPENALEX_ID_PREFIX) ? id.slice(OPENALEX_ID_PREFIX.length) : id;
|
|
82
|
+
return NATIVE_OPENALEX_ID.test(tail) ? tail : id;
|
|
83
|
+
}
|
|
84
|
+
/** Join notice sentences, dropping empty ones; undefined when none remain. */
|
|
85
|
+
export function joinNotices(parts) {
|
|
86
|
+
return parts.filter(Boolean).join(' ') || undefined;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Rendering — shared by both tools' format() and by the budget's own measurement
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
const renderCall = (call) => `\`${call.tool}\` \`${JSON.stringify(call.arguments)}\``;
|
|
92
|
+
function renderWindow(window) {
|
|
93
|
+
const capped = window.possibly_capped
|
|
94
|
+
? ` — possibly capped: OpenAlex returns at most ${LIST_AUTHORSHIPS_CAP} authorships on a list record`
|
|
95
|
+
: '';
|
|
96
|
+
const next = window.next ? `next: ${renderCall(window.next)}` : 'end of array';
|
|
97
|
+
return `**Window:** ${window.id} \`${window.field}\` — offset ${window.offset}, ${window.shown} shown of ${window.total}${capped} — ${next}`;
|
|
98
|
+
}
|
|
99
|
+
/** Markdown lines disclosing what the budget cut; empty when the page carries no disclosure. */
|
|
100
|
+
function renderBudgetDisclosure(disclosure) {
|
|
101
|
+
const lines = [];
|
|
102
|
+
if (disclosure.over_budget) {
|
|
103
|
+
lines.push('', `**Over budget:** the least this call can return — the first record's non-array fields, or one \`slice\` element — exceeds the ${BUDGET_LABEL} response budget on its own.`);
|
|
104
|
+
}
|
|
105
|
+
if (disclosure.windows?.length)
|
|
106
|
+
lines.push('', ...disclosure.windows.map(renderWindow));
|
|
107
|
+
if (disclosure.omitted) {
|
|
108
|
+
const { ids, next } = disclosure.omitted;
|
|
109
|
+
lines.push('', `**Omitted by the ${BUDGET_LABEL} response budget (${ids.length}):** ${ids.join(', ')}`, `**Omitted records call:** ${renderCall(next)}`);
|
|
110
|
+
}
|
|
111
|
+
return lines;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Each windowed array's place in its full array, per record ID, so a window numbers its items
|
|
115
|
+
* at their real index and its line says how much of the array it shows.
|
|
116
|
+
*/
|
|
117
|
+
function windowViewsByRecord(windows) {
|
|
118
|
+
const views = new Map();
|
|
119
|
+
for (const window of windows ?? []) {
|
|
120
|
+
const fields = views.get(window.id) ?? new Map();
|
|
121
|
+
fields.set(window.field, { offset: window.offset, total: window.total });
|
|
122
|
+
views.set(window.id, fields);
|
|
123
|
+
}
|
|
124
|
+
return views;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `format()` for a record page: a count header, each record, then the budget disclosure.
|
|
128
|
+
* `noun` names what `meta.count` counts ("result(s)", "edge(s)").
|
|
129
|
+
*/
|
|
130
|
+
export function formatRecordPage(page, noun) {
|
|
131
|
+
const countLabel = `${page.meta.count} ${noun} — ${page.meta.per_page} per page`;
|
|
132
|
+
const lines = [
|
|
133
|
+
page.meta.next_cursor
|
|
134
|
+
? `**${countLabel}** — next cursor: \`${page.meta.next_cursor}\``
|
|
135
|
+
: `**${countLabel}**`,
|
|
136
|
+
];
|
|
137
|
+
const views = windowViewsByRecord(page.windows);
|
|
138
|
+
for (const record of page.results) {
|
|
139
|
+
lines.push(...renderEntityRecord(record, views.get(bareId(record.id))));
|
|
140
|
+
}
|
|
141
|
+
lines.push(...renderBudgetDisclosure(page));
|
|
142
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
143
|
+
}
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Measurement
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
/**
|
|
148
|
+
* Byte-exact copy of the framework's trailer join (`joinTrailerFields` in mcp-ts-core's tool
|
|
149
|
+
* handler factory, which the package does not export — cyanheads/mcp-ts-core#538). A line opening
|
|
150
|
+
* a block quote or list item is followed by a blank line instead of a single newline. The
|
|
151
|
+
* response-budget test compares the modelled trailer with the one `runToolContract` renders.
|
|
152
|
+
*/
|
|
153
|
+
const OPENS_LAZY_CONTAINER = /^ {0,3}(?:>|(?:[-+*]|\d{1,9}[.)])(?: |$))/;
|
|
154
|
+
function joinTrailerFields(fields) {
|
|
155
|
+
let text = '';
|
|
156
|
+
for (const field of fields) {
|
|
157
|
+
if (text.length > 0) {
|
|
158
|
+
text += OPENS_LAZY_CONTAINER.test(text.slice(text.lastIndexOf('\n') + 1)) ? '\n\n' : '\n';
|
|
159
|
+
}
|
|
160
|
+
text += field;
|
|
161
|
+
}
|
|
162
|
+
return text;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The enrichment trailer's bytes, minus the service-written `budget` line. Fields render as the
|
|
166
|
+
* framework renders them for these tools: `echo` under its `Query` label, `totalCount` under
|
|
167
|
+
* `totalLabel`, and `notice` as a block quote, joined after a leading blank line.
|
|
168
|
+
*/
|
|
169
|
+
export function trailerTextBytes(enrichment) {
|
|
170
|
+
const fields = [
|
|
171
|
+
`**Query:** ${enrichment.echo}`,
|
|
172
|
+
`**${enrichment.totalLabel}:** ${enrichment.totalCount}`,
|
|
173
|
+
...(enrichment.notice === undefined ? [] : [`> ${enrichment.notice}`]),
|
|
174
|
+
];
|
|
175
|
+
return utf8Bytes(`\n\n${joinTrailerFields(fields)}`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Both surfaces' bytes for a page frame — the page with no records — plus the enrichment the
|
|
179
|
+
* handler writes and the allowance for the service's `budget` field.
|
|
180
|
+
*/
|
|
181
|
+
export function measurePageFrame(frame, noun, enrichment) {
|
|
182
|
+
const { echo, notice, totalCount } = enrichment;
|
|
183
|
+
const structured = utf8Bytes(JSON.stringify({ ...frame, echo, totalCount, ...(notice !== undefined && { notice }) }));
|
|
184
|
+
const text = formatRecordPage(frame, noun).reduce((sum, block) => sum + utf8Bytes(block.text), 0);
|
|
185
|
+
return {
|
|
186
|
+
structured: structured + BUDGET_FIELD_ALLOWANCE_BYTES,
|
|
187
|
+
text: text + trailerTextBytes(enrichment) + BUDGET_FIELD_ALLOWANCE_BYTES,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* What one record adds to the busier surface: its JSON array member or its rendered lines,
|
|
192
|
+
* each plus the one separator byte (comma, newline) joining it to its neighbour. Rendered with
|
|
193
|
+
* the renderer `format()` uses, with the window views `format()` will pass.
|
|
194
|
+
*/
|
|
195
|
+
function recordCharge(record, views) {
|
|
196
|
+
const json = utf8Bytes(JSON.stringify(record));
|
|
197
|
+
const text = utf8Bytes(renderEntityRecord(record, views).join('\n'));
|
|
198
|
+
return Math.max(json, text) + 1;
|
|
199
|
+
}
|
|
200
|
+
const frameBytes = (bytes) => Math.max(bytes.structured, bytes.text);
|
|
201
|
+
/**
|
|
202
|
+
* Largest n in [0, limit] with `fits(n)`, given `fits(0)` holds and `fits` stays false once
|
|
203
|
+
* it turns false. Gallops up from 0 before bisecting, so each probe renders about as many
|
|
204
|
+
* elements as the answer holds rather than half the array.
|
|
205
|
+
*/
|
|
206
|
+
function largestFitting(limit, fits) {
|
|
207
|
+
let low = 0;
|
|
208
|
+
let high = 1;
|
|
209
|
+
while (high <= limit && fits(high)) {
|
|
210
|
+
low = high;
|
|
211
|
+
high *= 2;
|
|
212
|
+
}
|
|
213
|
+
high = Math.min(high, limit + 1);
|
|
214
|
+
while (high - low > 1) {
|
|
215
|
+
const mid = Math.floor((low + high) / 2);
|
|
216
|
+
if (fits(mid))
|
|
217
|
+
low = mid;
|
|
218
|
+
else
|
|
219
|
+
high = mid;
|
|
220
|
+
}
|
|
221
|
+
return low;
|
|
222
|
+
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Disclosure construction
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
function arrayWindow(entityType, record, field, view) {
|
|
227
|
+
const id = bareId(record.id);
|
|
228
|
+
const end = Math.min(view.offset, view.total) + view.shown;
|
|
229
|
+
return {
|
|
230
|
+
id,
|
|
231
|
+
field,
|
|
232
|
+
offset: view.offset,
|
|
233
|
+
shown: view.shown,
|
|
234
|
+
total: view.total,
|
|
235
|
+
possibly_capped: view.possiblyCapped,
|
|
236
|
+
next: end < view.total || view.possiblyCapped
|
|
237
|
+
? {
|
|
238
|
+
tool: CONTINUATION_TOOL,
|
|
239
|
+
arguments: { entity_type: entityType, id, slice: { field, offset: end } },
|
|
240
|
+
}
|
|
241
|
+
: null,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** The author arrays of a list record holding exactly the cap; none when `capCheck` is off. */
|
|
245
|
+
function cappedListFields(record, capCheck) {
|
|
246
|
+
if (!capCheck)
|
|
247
|
+
return [];
|
|
248
|
+
return CAPPED_AUTHOR_FIELDS.filter((field) => {
|
|
249
|
+
const items = record[field];
|
|
250
|
+
return Array.isArray(items) && items.length === LIST_AUTHORSHIPS_CAP;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function capWindow(entityType, record, field) {
|
|
254
|
+
return arrayWindow(entityType, record, field, {
|
|
255
|
+
offset: 0,
|
|
256
|
+
shown: LIST_AUTHORSHIPS_CAP,
|
|
257
|
+
total: LIST_AUTHORSHIPS_CAP,
|
|
258
|
+
possiblyCapped: true,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function budgetNotice(parts) {
|
|
262
|
+
const { cappedCount, kept, omitted, overBudget, windowedCount } = parts;
|
|
263
|
+
return joinNotices([
|
|
264
|
+
omitted.length > 0
|
|
265
|
+
? `The ${BUDGET_LABEL} response budget held ${kept.length} of ${kept.length + omitted.length} records; \`omitted\` names the other ${omitted.length} and the openalex_search_entities call that returns them.`
|
|
266
|
+
: undefined,
|
|
267
|
+
windowedCount > 0
|
|
268
|
+
? `${windowedCount} array field(s) were windowed to fit the budget; each \`windows\` entry gives the \`id\` + \`slice\` call that continues it.`
|
|
269
|
+
: undefined,
|
|
270
|
+
overBudget
|
|
271
|
+
? "The first record's non-array fields alone exceed the budget, so this response runs over it."
|
|
272
|
+
: undefined,
|
|
273
|
+
cappedCount > 0
|
|
274
|
+
? `${cappedCount} record(s) carry exactly ${LIST_AUTHORSHIPS_CAP} authorships or author_list entries, the most OpenAlex returns on a list record, so the author list may be longer; each such array's \`windows\` entry gives the \`id\` + \`slice\` call for the rest.`
|
|
275
|
+
: undefined,
|
|
276
|
+
]);
|
|
277
|
+
}
|
|
278
|
+
function assemblePage(parts, entityType, select) {
|
|
279
|
+
const disclosure = {};
|
|
280
|
+
if (parts.omitted.length > 0) {
|
|
281
|
+
const ids = parts.omitted.map((record) => bareId(record.id));
|
|
282
|
+
// Keywords have no `openalex` filter upstream (400); their `id` filter takes the keyword URLs.
|
|
283
|
+
const idFilter = entityType === 'keywords' ? 'id' : 'openalex';
|
|
284
|
+
disclosure.omitted = {
|
|
285
|
+
ids,
|
|
286
|
+
next: {
|
|
287
|
+
tool: CONTINUATION_TOOL,
|
|
288
|
+
arguments: {
|
|
289
|
+
entity_type: entityType,
|
|
290
|
+
filters: { [idFilter]: ids.join('|') },
|
|
291
|
+
per_page: ids.length,
|
|
292
|
+
...(select !== undefined && { select: [...select] }),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (parts.windows.length > 0)
|
|
298
|
+
disclosure.windows = [...parts.windows];
|
|
299
|
+
if (parts.overBudget)
|
|
300
|
+
disclosure.over_budget = true;
|
|
301
|
+
return { results: [...parts.kept], disclosure, notice: budgetNotice(parts) };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Fit a page of records to the budget. Keeps the longest prefix whose records, charged
|
|
305
|
+
* `max(JSON bytes, rendered bytes)` each, fit beside the page frame; the rest become `omitted`.
|
|
306
|
+
* When even the first record does not fit alone, its largest arrays are windowed instead.
|
|
307
|
+
* Returns new arrays and, for a windowed record, a new record — never the caller's objects
|
|
308
|
+
* mutated.
|
|
309
|
+
*/
|
|
310
|
+
export function fitRecordsToBudget(request) {
|
|
311
|
+
const { entityType, measureFrame, path, records, select } = request;
|
|
312
|
+
const capCheck = path === 'list' && entityType === 'works';
|
|
313
|
+
const charges = records.map((record) => recordCharge(record));
|
|
314
|
+
let recordBytes = charges.reduce((sum, charge) => sum + charge, 0);
|
|
315
|
+
for (let kept = records.length; kept >= 1; kept--) {
|
|
316
|
+
const keptRecords = records.slice(0, kept);
|
|
317
|
+
const capped = keptRecords
|
|
318
|
+
.map((record) => ({ record, fields: cappedListFields(record, capCheck) }))
|
|
319
|
+
.filter(({ fields }) => fields.length > 0);
|
|
320
|
+
const page = assemblePage({
|
|
321
|
+
kept: keptRecords,
|
|
322
|
+
omitted: records.slice(kept),
|
|
323
|
+
windows: capped.flatMap(({ record, fields }) => fields.map((field) => capWindow(entityType, record, field))),
|
|
324
|
+
windowedCount: 0,
|
|
325
|
+
cappedCount: capped.length,
|
|
326
|
+
overBudget: false,
|
|
327
|
+
}, entityType, select);
|
|
328
|
+
if (frameBytes(measureFrame(page.disclosure, page.notice)) + recordBytes <=
|
|
329
|
+
RESPONSE_BUDGET_BYTES) {
|
|
330
|
+
return page;
|
|
331
|
+
}
|
|
332
|
+
recordBytes -= charges[kept - 1] ?? 0;
|
|
333
|
+
}
|
|
334
|
+
const [first, ...rest] = records;
|
|
335
|
+
if (!first)
|
|
336
|
+
return { results: [], disclosure: {}, notice: undefined };
|
|
337
|
+
return windowRecord(first, rest, cappedListFields(first, capCheck), request);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Fill windowed arrays round-robin: each array in turn takes its next leading element while it
|
|
341
|
+
* fits, so every array shows elements for as long as any of its elements would fit. A record's
|
|
342
|
+
* charge only grows as elements are added, so an array whose next element does not fit never
|
|
343
|
+
* fits again and drops out. Whole rounds are found by bisection and the round that ends a run
|
|
344
|
+
* is played one array at a time, so the result is exactly the one-element-at-a-time fill at a
|
|
345
|
+
* cost of a few renders per array. Returns the count shown per field.
|
|
346
|
+
*/
|
|
347
|
+
function fillRoundRobin(base, arrays, fits) {
|
|
348
|
+
const counts = new Map(arrays.map((array) => [array.field, 0]));
|
|
349
|
+
const shownOf = (array) => counts.get(array.field) ?? 0;
|
|
350
|
+
const build = (extra) => {
|
|
351
|
+
const record = { ...base };
|
|
352
|
+
for (const array of arrays) {
|
|
353
|
+
record[array.field] = array.items.slice(0, shownOf(array) + extra(array));
|
|
354
|
+
}
|
|
355
|
+
return record;
|
|
356
|
+
};
|
|
357
|
+
let active = [...arrays];
|
|
358
|
+
while (active.length > 0) {
|
|
359
|
+
const inPlay = new Set(active);
|
|
360
|
+
const raise = (rounds) => (array) => inPlay.has(array) ? Math.min(rounds, array.items.length - shownOf(array)) : 0;
|
|
361
|
+
const headroom = Math.max(...active.map((array) => array.items.length - shownOf(array)));
|
|
362
|
+
const rounds = largestFitting(headroom, (n) => fits(build(raise(n))));
|
|
363
|
+
for (const array of active)
|
|
364
|
+
counts.set(array.field, shownOf(array) + raise(rounds)(array));
|
|
365
|
+
active = active.filter((array) => {
|
|
366
|
+
if (shownOf(array) >= array.items.length)
|
|
367
|
+
return false;
|
|
368
|
+
const grown = build((other) => (other === array ? 1 : 0));
|
|
369
|
+
if (!fits(grown))
|
|
370
|
+
return false;
|
|
371
|
+
counts.set(array.field, shownOf(array) + 1);
|
|
372
|
+
return true;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return counts;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Window the arrays of a record too large to fit whole. Tries windowing the largest array
|
|
379
|
+
* alone, then the two largest, and so on, until the record with those arrays emptied fits;
|
|
380
|
+
* then fills the windowed arrays round-robin. The disclosure is sized at its upper bound while
|
|
381
|
+
* fitting — every count at its array length — so the final, smaller disclosure can only fit too.
|
|
382
|
+
*/
|
|
383
|
+
function windowRecord(record, rest, capped, request) {
|
|
384
|
+
const { entityType, measureFrame, select } = request;
|
|
385
|
+
const arrays = Object.entries(record)
|
|
386
|
+
.filter((entry) => Array.isArray(entry[1]) && entry[1].length > 0)
|
|
387
|
+
.map(([field, items]) => ({ field, items, bytes: utf8Bytes(JSON.stringify(items)) }))
|
|
388
|
+
.sort((a, b) => b.bytes - a.bytes);
|
|
389
|
+
/** Window entries for `shown` counts per windowed field, plus the cap entries that apply. */
|
|
390
|
+
const windowsFor = (shown) => {
|
|
391
|
+
const entries = [];
|
|
392
|
+
for (const [field, items] of Object.entries(record)) {
|
|
393
|
+
if (!Array.isArray(items))
|
|
394
|
+
continue;
|
|
395
|
+
const count = shown.get(field);
|
|
396
|
+
const cappedField = capped.includes(field);
|
|
397
|
+
if (count !== undefined && count < items.length) {
|
|
398
|
+
entries.push(arrayWindow(entityType, record, field, {
|
|
399
|
+
offset: 0,
|
|
400
|
+
shown: count,
|
|
401
|
+
total: items.length,
|
|
402
|
+
possiblyCapped: cappedField,
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
else if (cappedField) {
|
|
406
|
+
entries.push(capWindow(entityType, record, field));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return entries;
|
|
410
|
+
};
|
|
411
|
+
const partsFor = (kept, windows, overBudget) => ({
|
|
412
|
+
kept: [kept],
|
|
413
|
+
omitted: rest,
|
|
414
|
+
windows,
|
|
415
|
+
windowedCount: windows.filter((w) => w.shown < w.total).length,
|
|
416
|
+
cappedCount: capped.length > 0 ? 1 : 0,
|
|
417
|
+
overBudget,
|
|
418
|
+
});
|
|
419
|
+
for (let count = 1; count <= arrays.length; count++) {
|
|
420
|
+
const windowed = arrays.slice(0, count);
|
|
421
|
+
// Upper bound: every windowed count at its full length, each still continuing.
|
|
422
|
+
const bound = windowed.map((array) => arrayWindow(entityType, record, array.field, {
|
|
423
|
+
offset: 0,
|
|
424
|
+
shown: array.items.length,
|
|
425
|
+
total: array.items.length + 1,
|
|
426
|
+
possiblyCapped: capped.includes(array.field),
|
|
427
|
+
}));
|
|
428
|
+
const boundWindows = [
|
|
429
|
+
...bound,
|
|
430
|
+
...capped
|
|
431
|
+
.filter((field) => !windowed.some((array) => array.field === field))
|
|
432
|
+
.map((field) => capWindow(entityType, record, field)),
|
|
433
|
+
];
|
|
434
|
+
const boundPage = assemblePage(partsFor(record, boundWindows, false), entityType, select);
|
|
435
|
+
const available = RESPONSE_BUDGET_BYTES - frameBytes(measureFrame(boundPage.disclosure, boundPage.notice));
|
|
436
|
+
// Charged with the views `format()` passes, so each window's note is paid for.
|
|
437
|
+
const views = new Map(windowed.map((array) => [array.field, { offset: 0, total: array.items.length }]));
|
|
438
|
+
const fits = (candidate) => recordCharge(candidate, views) <= available;
|
|
439
|
+
const emptied = { ...record };
|
|
440
|
+
for (const array of windowed)
|
|
441
|
+
emptied[array.field] = [];
|
|
442
|
+
if (!fits(emptied))
|
|
443
|
+
continue;
|
|
444
|
+
const shown = fillRoundRobin(record, windowed, fits);
|
|
445
|
+
const kept = { ...record };
|
|
446
|
+
for (const array of windowed) {
|
|
447
|
+
kept[array.field] = array.items.slice(0, shown.get(array.field));
|
|
448
|
+
}
|
|
449
|
+
return assemblePage(partsFor(kept, windowsFor(shown), false), entityType, select);
|
|
450
|
+
}
|
|
451
|
+
// The record's non-array fields alone exceed the budget: return them whole, arrays empty.
|
|
452
|
+
const emptied = { ...record };
|
|
453
|
+
for (const array of arrays)
|
|
454
|
+
emptied[array.field] = [];
|
|
455
|
+
const shown = new Map(arrays.map((array) => [array.field, 0]));
|
|
456
|
+
return assemblePage(partsFor(emptied, windowsFor(shown), true), entityType, select);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Fit one array window of an `id` lookup to the budget: the elements of `field` from `offset`,
|
|
460
|
+
* as many as fit. A window holds at least one element whenever any remain, so a caller walking
|
|
461
|
+
* the array always advances; when that one element alone does not fit, the response runs over
|
|
462
|
+
* the budget and is flagged `over_budget`.
|
|
463
|
+
*/
|
|
464
|
+
export function fitSliceToBudget(request) {
|
|
465
|
+
const { entityType, field, measureFrame, offset, record } = request;
|
|
466
|
+
const items = record[field];
|
|
467
|
+
const total = items.length;
|
|
468
|
+
const start = Math.min(offset, total);
|
|
469
|
+
const remaining = total - start;
|
|
470
|
+
const views = new Map([[field, { offset, total }]]);
|
|
471
|
+
const view = (shown) => ({ offset, shown, total, possiblyCapped: false });
|
|
472
|
+
// Upper bound: the whole remainder shown, with a continuation call.
|
|
473
|
+
const bound = arrayWindow(entityType, record, field, { ...view(remaining), total: total + 1 });
|
|
474
|
+
const available = RESPONSE_BUDGET_BYTES - frameBytes(measureFrame({ windows: [bound] }, undefined));
|
|
475
|
+
const fitted = largestFitting(remaining, (n) => recordCharge({ ...record, [field]: items.slice(start, start + n) }, views) <= available);
|
|
476
|
+
const overBudget = remaining > 0 && fitted === 0;
|
|
477
|
+
const shown = overBudget ? 1 : fitted;
|
|
478
|
+
return {
|
|
479
|
+
results: [{ ...record, [field]: items.slice(start, start + shown) }],
|
|
480
|
+
disclosure: {
|
|
481
|
+
windows: [arrayWindow(entityType, record, field, view(shown))],
|
|
482
|
+
...(overBudget && { over_budget: true }),
|
|
483
|
+
},
|
|
484
|
+
notice: overBudget
|
|
485
|
+
? `The element at offset ${start} of \`${field}\` alone exceeds the ${BUDGET_LABEL} response budget, so this response runs over it; ${start + 1 < total ? "the `windows` entry's `next` call continues after it" : 'it is the last element'}.`
|
|
486
|
+
: undefined,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=response-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-budget.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/response-budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,7 +36,9 @@ export interface UpstreamBudget {
|
|
|
36
36
|
* and a singleton `/works/{id}` lookup alike. Singleton lookups are the reason this reads
|
|
37
37
|
* headers rather than the parsed body: they carry no `meta` wrapper at all, so the body's
|
|
38
38
|
* `cost_usd` (present and identical on the other shapes) is unavailable exactly where
|
|
39
|
-
* `costUsd: 0` is the most interesting signal — fetching by ID
|
|
39
|
+
* `costUsd: 0` is the most interesting signal — fetching by an OpenAlex ID, DOI, or other
|
|
40
|
+
* external identifier is free. (A keyword fetched by its slug, or an ID with characters past it
|
|
41
|
+
* such as `W2741809807#x`, bills like a list request.)
|
|
40
42
|
*
|
|
41
43
|
* Returns `undefined` unless the full set parses. A partial read would report a cost with no
|
|
42
44
|
* budget to weigh it against, which is worse than reporting nothing. The prepaid balance sits
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;;;;;;;GAeG;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"}
|
|
@@ -17,7 +17,9 @@ function readNumericHeader(headers, name) {
|
|
|
17
17
|
* and a singleton `/works/{id}` lookup alike. Singleton lookups are the reason this reads
|
|
18
18
|
* headers rather than the parsed body: they carry no `meta` wrapper at all, so the body's
|
|
19
19
|
* `cost_usd` (present and identical on the other shapes) is unavailable exactly where
|
|
20
|
-
* `costUsd: 0` is the most interesting signal — fetching by ID
|
|
20
|
+
* `costUsd: 0` is the most interesting signal — fetching by an OpenAlex ID, DOI, or other
|
|
21
|
+
* external identifier is free. (A keyword fetched by its slug, or an ID with characters past it
|
|
22
|
+
* such as `W2741809807#x`, bills like a list request.)
|
|
21
23
|
*
|
|
22
24
|
* Returns `undefined` unless the full set parses. A partial read would report a cost with no
|
|
23
25
|
* budget to weigh it against, which is worse than reporting nothing. The prepaid balance sits
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;;;;;;;;GAeG;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"}
|