scoutline 0.19.7 → 0.21.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/README.md +131 -25
- package/bin/scoutline.js +0 -0
- package/dist/capabilities/map.d.ts +3 -3
- package/dist/capabilities/map.js +3 -3
- package/dist/capabilities/quota.d.ts +13 -6
- package/dist/capabilities/quota.d.ts.map +1 -1
- package/dist/capabilities/quota.js +15 -0
- package/dist/capabilities/quota.js.map +1 -1
- package/dist/capabilities/science.d.ts +193 -0
- package/dist/capabilities/science.d.ts.map +1 -0
- package/dist/capabilities/science.js +110 -0
- package/dist/capabilities/science.js.map +1 -0
- package/dist/command-invocation.d.ts +13 -1
- package/dist/command-invocation.d.ts.map +1 -1
- package/dist/command-invocation.js +44 -2
- package/dist/command-invocation.js.map +1 -1
- package/dist/commands/archive.d.ts +178 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +871 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +12 -3
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/crawl.d.ts +15 -2
- package/dist/commands/crawl.d.ts.map +1 -1
- package/dist/commands/crawl.js +93 -34
- package/dist/commands/crawl.js.map +1 -1
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +40 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/fetch.d.ts +75 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +596 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/history.d.ts +165 -8
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +591 -20
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +411 -43
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/map.d.ts.map +1 -1
- package/dist/commands/map.js +5 -0
- package/dist/commands/map.js.map +1 -1
- package/dist/commands/quota.d.ts.map +1 -1
- package/dist/commands/quota.js +2 -0
- package/dist/commands/quota.js.map +1 -1
- package/dist/commands/read.d.ts +28 -6
- package/dist/commands/read.d.ts.map +1 -1
- package/dist/commands/read.js +328 -31
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/repo.d.ts +12 -10
- package/dist/commands/repo.d.ts.map +1 -1
- package/dist/commands/repo.js +189 -21
- package/dist/commands/repo.js.map +1 -1
- package/dist/commands/repository-explorer.d.ts +15 -7
- package/dist/commands/repository-explorer.d.ts.map +1 -1
- package/dist/commands/repository-explorer.js +117 -97
- package/dist/commands/repository-explorer.js.map +1 -1
- package/dist/commands/research.d.ts +21 -4
- package/dist/commands/research.d.ts.map +1 -1
- package/dist/commands/research.js +83 -12
- package/dist/commands/research.js.map +1 -1
- package/dist/commands/science.d.ts +54 -0
- package/dist/commands/science.d.ts.map +1 -0
- package/dist/commands/science.js +999 -0
- package/dist/commands/science.js.map +1 -0
- package/dist/commands/search.d.ts +13 -2
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +106 -7
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/watch.d.ts +94 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +900 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/index.d.ts +168 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1679 -232
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-registration/deploy.d.ts +69 -0
- package/dist/lib/agent-registration/deploy.d.ts.map +1 -0
- package/dist/lib/agent-registration/deploy.js +279 -0
- package/dist/lib/agent-registration/deploy.js.map +1 -0
- package/dist/lib/agent-registration/engines.d.ts +77 -0
- package/dist/lib/agent-registration/engines.d.ts.map +1 -0
- package/dist/lib/agent-registration/engines.js +544 -0
- package/dist/lib/agent-registration/engines.js.map +1 -0
- package/dist/lib/agent-registration/registry.d.ts +32 -0
- package/dist/lib/agent-registration/registry.d.ts.map +1 -0
- package/dist/lib/agent-registration/registry.js +94 -0
- package/dist/lib/agent-registration/registry.js.map +1 -0
- package/dist/lib/api-client.d.ts +2 -3
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +18 -0
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/artifacts.d.ts +83 -3
- package/dist/lib/artifacts.d.ts.map +1 -1
- package/dist/lib/artifacts.js +246 -13
- package/dist/lib/artifacts.js.map +1 -1
- package/dist/lib/async-file-lock.d.ts +19 -9
- package/dist/lib/async-file-lock.d.ts.map +1 -1
- package/dist/lib/async-file-lock.js +25 -11
- package/dist/lib/async-file-lock.js.map +1 -1
- package/dist/lib/batch-manifest.d.ts +1 -0
- package/dist/lib/batch-manifest.d.ts.map +1 -1
- package/dist/lib/batch-manifest.js +12 -5
- package/dist/lib/batch-manifest.js.map +1 -1
- package/dist/lib/batch-runner.d.ts.map +1 -1
- package/dist/lib/batch-runner.js +25 -0
- package/dist/lib/batch-runner.js.map +1 -1
- package/dist/lib/code-mode.d.ts +19 -0
- package/dist/lib/code-mode.d.ts.map +1 -1
- package/dist/lib/code-mode.js +170 -14
- package/dist/lib/code-mode.js.map +1 -1
- package/dist/lib/config-store.d.ts +48 -1
- package/dist/lib/config-store.d.ts.map +1 -1
- package/dist/lib/config-store.js +92 -5
- package/dist/lib/config-store.js.map +1 -1
- package/dist/lib/config.js +3 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +20 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +26 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/journal.d.ts +258 -0
- package/dist/lib/journal.d.ts.map +1 -0
- package/dist/lib/journal.js +582 -0
- package/dist/lib/journal.js.map +1 -0
- package/dist/lib/mcp-client.d.ts +18 -0
- package/dist/lib/mcp-client.d.ts.map +1 -1
- package/dist/lib/mcp-client.js +145 -17
- package/dist/lib/mcp-client.js.map +1 -1
- package/dist/lib/output-budget-persistence.d.ts +76 -0
- package/dist/lib/output-budget-persistence.d.ts.map +1 -0
- package/dist/lib/output-budget-persistence.js +76 -0
- package/dist/lib/output-budget-persistence.js.map +1 -0
- package/dist/lib/output-budget.d.ts +99 -0
- package/dist/lib/output-budget.d.ts.map +1 -0
- package/dist/lib/output-budget.js +175 -0
- package/dist/lib/output-budget.js.map +1 -0
- package/dist/lib/pdf.d.ts +22 -0
- package/dist/lib/pdf.d.ts.map +1 -0
- package/dist/lib/pdf.js +1213 -0
- package/dist/lib/pdf.js.map +1 -0
- package/dist/lib/provider-fallback.d.ts.map +1 -1
- package/dist/lib/provider-fallback.js +3 -0
- package/dist/lib/provider-fallback.js.map +1 -1
- package/dist/lib/quota-mapping.d.ts.map +1 -1
- package/dist/lib/quota-mapping.js +31 -0
- package/dist/lib/quota-mapping.js.map +1 -1
- package/dist/lib/quota-store.d.ts +11 -1
- package/dist/lib/quota-store.d.ts.map +1 -1
- package/dist/lib/quota-store.js +11 -2
- package/dist/lib/quota-store.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +18 -8
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/save-artifacts.d.ts +38 -0
- package/dist/lib/save-artifacts.d.ts.map +1 -0
- package/dist/lib/save-artifacts.js +146 -0
- package/dist/lib/save-artifacts.js.map +1 -0
- package/dist/lib/section-diff.d.ts +90 -0
- package/dist/lib/section-diff.d.ts.map +1 -0
- package/dist/lib/section-diff.js +395 -0
- package/dist/lib/section-diff.js.map +1 -0
- package/dist/lib/skill-source.d.ts +11 -0
- package/dist/lib/skill-source.d.ts.map +1 -0
- package/dist/lib/skill-source.js +22 -0
- package/dist/lib/skill-source.js.map +1 -0
- package/dist/lib/tty.d.ts +1 -1
- package/dist/lib/tty.d.ts.map +1 -1
- package/dist/lib/tty.js +10 -1
- package/dist/lib/tty.js.map +1 -1
- package/dist/lib/watch-store.d.ts +203 -0
- package/dist/lib/watch-store.d.ts.map +1 -0
- package/dist/lib/watch-store.js +628 -0
- package/dist/lib/watch-store.js.map +1 -0
- package/dist/providers/arxiv/adapter.d.ts +88 -0
- package/dist/providers/arxiv/adapter.d.ts.map +1 -0
- package/dist/providers/arxiv/adapter.js +316 -0
- package/dist/providers/arxiv/adapter.js.map +1 -0
- package/dist/providers/arxiv/client.d.ts +33 -0
- package/dist/providers/arxiv/client.d.ts.map +1 -0
- package/dist/providers/arxiv/client.js +130 -0
- package/dist/providers/arxiv/client.js.map +1 -0
- package/dist/providers/arxiv/diagnostics.d.ts +28 -0
- package/dist/providers/arxiv/diagnostics.d.ts.map +1 -0
- package/dist/providers/arxiv/diagnostics.js +34 -0
- package/dist/providers/arxiv/diagnostics.js.map +1 -0
- package/dist/providers/crossref/adapter.d.ts +97 -0
- package/dist/providers/crossref/adapter.d.ts.map +1 -0
- package/dist/providers/crossref/adapter.js +249 -0
- package/dist/providers/crossref/adapter.js.map +1 -0
- package/dist/providers/crossref/client.d.ts +10 -0
- package/dist/providers/crossref/client.d.ts.map +1 -0
- package/dist/providers/crossref/client.js +143 -0
- package/dist/providers/crossref/client.js.map +1 -0
- package/dist/providers/crossref/diagnostics.d.ts +18 -0
- package/dist/providers/crossref/diagnostics.d.ts.map +1 -0
- package/dist/providers/crossref/diagnostics.js +23 -0
- package/dist/providers/crossref/diagnostics.js.map +1 -0
- package/dist/providers/europepmc/adapter.d.ts +94 -0
- package/dist/providers/europepmc/adapter.d.ts.map +1 -0
- package/dist/providers/europepmc/adapter.js +264 -0
- package/dist/providers/europepmc/adapter.js.map +1 -0
- package/dist/providers/europepmc/client.d.ts +18 -0
- package/dist/providers/europepmc/client.d.ts.map +1 -0
- package/dist/providers/europepmc/client.js +128 -0
- package/dist/providers/europepmc/client.js.map +1 -0
- package/dist/providers/europepmc/diagnostics.d.ts +18 -0
- package/dist/providers/europepmc/diagnostics.d.ts.map +1 -0
- package/dist/providers/europepmc/diagnostics.js +23 -0
- package/dist/providers/europepmc/diagnostics.js.map +1 -0
- package/dist/providers/openalex/adapter.d.ts +53 -0
- package/dist/providers/openalex/adapter.d.ts.map +1 -0
- package/dist/providers/openalex/adapter.js +313 -0
- package/dist/providers/openalex/adapter.js.map +1 -0
- package/dist/providers/openalex/client.d.ts +18 -0
- package/dist/providers/openalex/client.d.ts.map +1 -0
- package/dist/providers/openalex/client.js +159 -0
- package/dist/providers/openalex/client.js.map +1 -0
- package/dist/providers/openalex/diagnostics.d.ts +19 -0
- package/dist/providers/openalex/diagnostics.d.ts.map +1 -0
- package/dist/providers/openalex/diagnostics.js +25 -0
- package/dist/providers/openalex/diagnostics.js.map +1 -0
- package/dist/providers/perplexity/adapter.d.ts +10 -8
- package/dist/providers/perplexity/adapter.d.ts.map +1 -1
- package/dist/providers/perplexity/adapter.js +68 -41
- package/dist/providers/perplexity/adapter.js.map +1 -1
- package/dist/providers/perplexity/client.d.ts +22 -12
- package/dist/providers/perplexity/client.d.ts.map +1 -1
- package/dist/providers/perplexity/client.js +18 -17
- package/dist/providers/perplexity/client.js.map +1 -1
- package/dist/providers/pubmed/adapter.d.ts +56 -0
- package/dist/providers/pubmed/adapter.d.ts.map +1 -0
- package/dist/providers/pubmed/adapter.js +425 -0
- package/dist/providers/pubmed/adapter.js.map +1 -0
- package/dist/providers/pubmed/client.d.ts +29 -0
- package/dist/providers/pubmed/client.d.ts.map +1 -0
- package/dist/providers/pubmed/client.js +153 -0
- package/dist/providers/pubmed/client.js.map +1 -0
- package/dist/providers/pubmed/diagnostics.d.ts +18 -0
- package/dist/providers/pubmed/diagnostics.d.ts.map +1 -0
- package/dist/providers/pubmed/diagnostics.js +23 -0
- package/dist/providers/pubmed/diagnostics.js.map +1 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +16 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/selection.d.ts +1 -1
- package/dist/providers/selection.d.ts.map +1 -1
- package/dist/providers/selection.js +5 -2
- package/dist/providers/selection.js.map +1 -1
- package/dist/providers/types.d.ts +28 -7
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +94 -6
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/zai/quota.d.ts.map +1 -1
- package/dist/providers/zai/quota.js +29 -8
- package/dist/providers/zai/quota.js.map +1 -1
- package/dist/providers/zai/reader.d.ts.map +1 -1
- package/dist/providers/zai/reader.js +5 -3
- package/dist/providers/zai/reader.js.map +1 -1
- package/dist/providers/zai/repository.d.ts.map +1 -1
- package/dist/providers/zai/repository.js +5 -3
- package/dist/providers/zai/repository.js.map +1 -1
- package/package.json +3 -2
- package/skills/scoutline/SKILL.md +787 -0
- package/skills/scoutline/references/advanced.md +469 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAlex Provider Adapter — science search + get via the works JSON API.
|
|
3
|
+
*
|
|
4
|
+
* Credential model (DESIGN D2 supplier table + D4b note): keyless by
|
|
5
|
+
* default (1000 credits/day), with the optional `OPENALEX_API_KEY`
|
|
6
|
+
* upgrade. `credentialEnvVars` is `["OPENALEX_API_KEY"]`; the cache
|
|
7
|
+
* fingerprint is `""` when keyless (keyless responses are
|
|
8
|
+
* user-independent) and the SHA-256 hex of the key otherwise (house
|
|
9
|
+
* method, brave/linkup precedent) — keyed upgrades re-partition
|
|
10
|
+
* (PRD AC-6b). The env flows through `create({ env })`, never the
|
|
11
|
+
* transport seam.
|
|
12
|
+
*
|
|
13
|
+
* Politeness (DESIGN D2 politeness bullet): the house
|
|
14
|
+
* `scoutline/${VERSION}` User-Agent rides every request; the
|
|
15
|
+
* `mailto=` query param rides whenever no api_key is present (the
|
|
16
|
+
* diagnostics probe included — D2 round-3 posture applies to the
|
|
17
|
+
* minimal wire call).
|
|
18
|
+
*
|
|
19
|
+
* Controls (DESIGN D7 table openalex column): `author`, `year`, and
|
|
20
|
+
* `type` are wire-consumed together on ONE works call
|
|
21
|
+
* (`filter=raw_author_name.search:`, `from/to_publication_date`,
|
|
22
|
+
* `type:`). `venue` is REJECTED at validate — probe-closed: OpenAlex
|
|
23
|
+
* has no name-based venue filter (`display_name.search` 400s) — honest
|
|
24
|
+
* UNSUPPORTED_OPTION, never accept-and-drop (PRD AC-3). The `type`
|
|
25
|
+
* VALUE is rewritten per the D7 round-2 translation table (chapter →
|
|
26
|
+
* book-chapter etc.); `conference-paper` is rejected v1 (D10
|
|
27
|
+
* type-vocabulary probe: the OpenAlex vocabulary carries no such
|
|
28
|
+
* value).
|
|
29
|
+
*
|
|
30
|
+
* Parsing (DESIGN D2/D3, PRD AC-7c/AC-7d): deterministic JSON field
|
|
31
|
+
* mapping with abstract_inverted_index reconstruction — one word per
|
|
32
|
+
* POSITION (a repeated word occupies each of its positions), so the
|
|
33
|
+
* output is token-entry-order independent. Absent supplier fields
|
|
34
|
+
* stay absent, never undefined-valued and never fabricated.
|
|
35
|
+
*
|
|
36
|
+
* Identifiers (DESIGN D10 ruling 3): bare DOI → `works/doi:<doi>`
|
|
37
|
+
* entity route; numeric PMID → `filter=ids.pmid:` search. The URL
|
|
38
|
+
* forms OpenAlex returns (`https://doi.org/…`, the pubmed URL form of
|
|
39
|
+
* ids.pmid) normalize to the bare house forms.
|
|
40
|
+
*/
|
|
41
|
+
import { createHash } from "node:crypto";
|
|
42
|
+
import { parseScienceIdentifier, validateScienceGetRequest, validateScienceSearchRequest, } from "../../capabilities/science.js";
|
|
43
|
+
import { ApiError, UnsupportedOptionError } from "../../lib/errors.js";
|
|
44
|
+
import { fetchOpenalexJson, resolveOpenalexCredentials } from "./client.js";
|
|
45
|
+
import { createOpenalexDiagnosticsCapability } from "./diagnostics.js";
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Wire mapping (DESIGN D7 table openalex column)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* D7 round-2 translation table, openalex column: union `type` values
|
|
51
|
+
* map to OpenAlex wire literals. `conference-paper` is absent — the
|
|
52
|
+
* OpenAlex vocabulary carries no such value (D10 type-vocabulary
|
|
53
|
+
* probe), so it is rejected at validate, never silently narrowed.
|
|
54
|
+
*/
|
|
55
|
+
const TYPE_WIRE_LITERALS = Object.freeze({
|
|
56
|
+
article: "article",
|
|
57
|
+
preprint: "preprint",
|
|
58
|
+
chapter: "book-chapter",
|
|
59
|
+
dataset: "dataset",
|
|
60
|
+
review: "review",
|
|
61
|
+
other: "other",
|
|
62
|
+
});
|
|
63
|
+
/** Year control "2020" | "2018:2022" → from/to publication-date pair. */
|
|
64
|
+
function yearFilterPair(year) {
|
|
65
|
+
const [from, to] = year.split(":");
|
|
66
|
+
return { from: `${from}-01-01`, to: `${to ?? from}-12-31` };
|
|
67
|
+
}
|
|
68
|
+
/** Search filters (D7 table) as one `filter=` param string. */
|
|
69
|
+
function buildSearchFilter(query) {
|
|
70
|
+
const controls = query.controls ?? {};
|
|
71
|
+
const parts = [];
|
|
72
|
+
if (controls.author !== undefined) {
|
|
73
|
+
parts.push(`raw_author_name.search:${controls.author}`);
|
|
74
|
+
}
|
|
75
|
+
if (controls.year !== undefined) {
|
|
76
|
+
const { from, to } = yearFilterPair(controls.year);
|
|
77
|
+
parts.push(`from_publication_date:${from}`, `to_publication_date:${to}`);
|
|
78
|
+
}
|
|
79
|
+
if (controls.type !== undefined) {
|
|
80
|
+
const literal = TYPE_WIRE_LITERALS[controls.type];
|
|
81
|
+
if (literal !== undefined)
|
|
82
|
+
parts.push(`type:${literal}`);
|
|
83
|
+
}
|
|
84
|
+
return parts.join(",");
|
|
85
|
+
}
|
|
86
|
+
function isRecord(value) {
|
|
87
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
function toOpenalexWork(value) {
|
|
90
|
+
return isRecord(value) ? value : undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Deterministic abstract reconstruction (PRD AC-7c): one word per
|
|
94
|
+
* POSITION, token-entry-order independent. A repeated word occupying
|
|
95
|
+
* positions [0, 4] fills both slots; two parses of the same index are
|
|
96
|
+
* identical.
|
|
97
|
+
*/
|
|
98
|
+
function reconstructAbstract(index) {
|
|
99
|
+
if (index === null || index === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
// Positions are collected, never used as array indexes (review):
|
|
102
|
+
// a hostile/huge position (e.g. 1e9) would allocate and scan a
|
|
103
|
+
// sparse array of that length, pinning the process.
|
|
104
|
+
const slots = new Map();
|
|
105
|
+
for (const [word, positions] of Object.entries(index)) {
|
|
106
|
+
for (const position of positions) {
|
|
107
|
+
if (Number.isSafeInteger(position) && position >= 0)
|
|
108
|
+
slots.set(position, word);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const text = [...slots.entries()]
|
|
112
|
+
.sort(([a], [b]) => a - b)
|
|
113
|
+
.map(([, word]) => word)
|
|
114
|
+
.join(" ");
|
|
115
|
+
// An EMPTY inverted index reconstructs to "" — that is an absent
|
|
116
|
+
// abstract (AC-7c), not an empty-string summary.
|
|
117
|
+
return text === "" ? undefined : text;
|
|
118
|
+
}
|
|
119
|
+
/** `https://doi.org/10.1038/...` → `10.1038/...`; null stays absent. */
|
|
120
|
+
function bareDoi(doi) {
|
|
121
|
+
if (doi === null || doi === undefined || doi === "")
|
|
122
|
+
return undefined;
|
|
123
|
+
return doi.replace(/^https?:\/\/doi\.org\//i, "");
|
|
124
|
+
}
|
|
125
|
+
/** Pubmed URL form `https://pubmed.ncbi.nlm.nih.gov/23903748/` → `23903748`. */
|
|
126
|
+
function barePmid(pmid) {
|
|
127
|
+
if (pmid === null || pmid === undefined || pmid === "")
|
|
128
|
+
return undefined;
|
|
129
|
+
const m = /(\d+)\/?$/.exec(pmid);
|
|
130
|
+
return m?.[1];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Map one OpenAlex work record to a `ScienceWork`. Absent supplier
|
|
134
|
+
* fields stay absent — keys are omitted, never set to undefined (the
|
|
135
|
+
* AC-7c honesty teeth: `Object.hasOwn(w, k) === false`), never
|
|
136
|
+
* fabricated.
|
|
137
|
+
*/
|
|
138
|
+
function mapWork(work) {
|
|
139
|
+
const identifiers = {};
|
|
140
|
+
const doi = bareDoi(work.doi ?? work.ids?.doi ?? null);
|
|
141
|
+
const pmid = barePmid(work.ids?.pmid ?? null);
|
|
142
|
+
if (doi !== undefined)
|
|
143
|
+
identifiers.doi = doi;
|
|
144
|
+
if (pmid !== undefined)
|
|
145
|
+
identifiers.pmid = pmid;
|
|
146
|
+
const location = work.primary_location ?? null;
|
|
147
|
+
const authors = (work.authorships ?? [])
|
|
148
|
+
.map((a) => a?.author?.display_name)
|
|
149
|
+
.filter((n) => typeof n === "string" && n !== "");
|
|
150
|
+
const summary = reconstructAbstract(work.abstract_inverted_index);
|
|
151
|
+
const pdfUrl = location?.pdf_url ?? undefined;
|
|
152
|
+
const venue = location?.source?.display_name ?? undefined;
|
|
153
|
+
const year = work.publication_year ?? undefined;
|
|
154
|
+
const citationCount = work.cited_by_count ?? undefined;
|
|
155
|
+
const openAccess = work.open_access?.is_oa ?? undefined;
|
|
156
|
+
const out = {
|
|
157
|
+
title: work.title ?? "",
|
|
158
|
+
url: location?.landing_page_url ?? work.id ?? "",
|
|
159
|
+
};
|
|
160
|
+
if (Object.keys(identifiers).length > 0)
|
|
161
|
+
out.identifiers = identifiers;
|
|
162
|
+
if (authors.length > 0)
|
|
163
|
+
out.authors = authors;
|
|
164
|
+
if (year !== undefined)
|
|
165
|
+
out.year = year;
|
|
166
|
+
if (venue !== undefined && venue !== "")
|
|
167
|
+
out.venue = venue;
|
|
168
|
+
if (summary !== undefined)
|
|
169
|
+
out.summary = summary;
|
|
170
|
+
if (citationCount !== undefined)
|
|
171
|
+
out.citationCount = citationCount;
|
|
172
|
+
if (pdfUrl !== undefined && pdfUrl !== "")
|
|
173
|
+
out.pdfUrl = pdfUrl;
|
|
174
|
+
if (openAccess !== undefined)
|
|
175
|
+
out.openAccess = openAccess;
|
|
176
|
+
if (work.type !== undefined && work.type !== null)
|
|
177
|
+
out.type = work.type;
|
|
178
|
+
if (work.language !== undefined && work.language !== null)
|
|
179
|
+
out.language = work.language;
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/** Extract the `results` array (search) or accept a single work (get). */
|
|
183
|
+
function openalexResults(doc) {
|
|
184
|
+
if (!isRecord(doc))
|
|
185
|
+
return [];
|
|
186
|
+
if (Array.isArray(doc["results"]))
|
|
187
|
+
return doc["results"];
|
|
188
|
+
return [doc];
|
|
189
|
+
}
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Validation (shared validator + adapter-level control rulings)
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
function validateOpenalexSearchRequest(request) {
|
|
194
|
+
validateScienceSearchRequest(request);
|
|
195
|
+
const controls = request.controls ?? {};
|
|
196
|
+
if (controls.venue !== undefined) {
|
|
197
|
+
throw new UnsupportedOptionError("openalex", "science.search", "venue");
|
|
198
|
+
}
|
|
199
|
+
if (controls.type !== undefined && TYPE_WIRE_LITERALS[controls.type] === undefined) {
|
|
200
|
+
throw new UnsupportedOptionError("openalex", "science.search", "type");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function validateOpenalexGetRequest(request) {
|
|
204
|
+
// Shared grammar first: out-of-bare-grammar identifiers (prefixed
|
|
205
|
+
// `doi:…`, free text) throw ValidationError (DESIGN D1).
|
|
206
|
+
validateScienceGetRequest(request);
|
|
207
|
+
// D10 ruling 3: openalex serves DOI and PMID gets (arXiv ids route to
|
|
208
|
+
// the arxiv adapter instead).
|
|
209
|
+
if (parseScienceIdentifier(request.identifier) === "arxiv") {
|
|
210
|
+
throw new UnsupportedOptionError("openalex", "science.get", "identifier");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Science capability
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
/**
|
|
217
|
+
* Cache identity (DESIGN D4b note + PRD AC-6b): keyless `""`
|
|
218
|
+
* (user-independent responses) or the SHA-256 hex of the active
|
|
219
|
+
* `OPENALEX_API_KEY` — keyed upgrades re-partition.
|
|
220
|
+
*/
|
|
221
|
+
function openalexCacheIdentity(env, capability, request) {
|
|
222
|
+
const { apiKey } = resolveOpenalexCredentials(env);
|
|
223
|
+
return {
|
|
224
|
+
supplier: "openalex",
|
|
225
|
+
capability,
|
|
226
|
+
credentialFingerprint: apiKey === undefined ? "" : createHash("sha256").update(apiKey).digest("hex"),
|
|
227
|
+
request,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function createOpenalexScienceCapability(options) {
|
|
231
|
+
const { env, transport } = options;
|
|
232
|
+
const deps = { ...transport, env };
|
|
233
|
+
const search = {
|
|
234
|
+
validate: validateOpenalexSearchRequest,
|
|
235
|
+
cacheIdentity(request) {
|
|
236
|
+
return openalexCacheIdentity(env, "science.search", request);
|
|
237
|
+
},
|
|
238
|
+
async invoke(request, signal) {
|
|
239
|
+
search.validate(request);
|
|
240
|
+
const filter = buildSearchFilter(request);
|
|
241
|
+
const params = { search: request.query.trim() };
|
|
242
|
+
if (filter !== "")
|
|
243
|
+
params["filter"] = filter;
|
|
244
|
+
const doc = await fetchOpenalexJson(params, deps, signal);
|
|
245
|
+
return openalexResults(doc).map((r) => mapWork(toOpenalexWork(r) ?? {}));
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
const get = {
|
|
249
|
+
validate: validateOpenalexGetRequest,
|
|
250
|
+
cacheIdentity(request) {
|
|
251
|
+
return openalexCacheIdentity(env, "science.get", request);
|
|
252
|
+
},
|
|
253
|
+
async invoke(request, signal) {
|
|
254
|
+
get.validate(request);
|
|
255
|
+
const kind = parseScienceIdentifier(request.identifier);
|
|
256
|
+
const doc = kind === "doi"
|
|
257
|
+
? await fetchOpenalexJson({}, deps, signal, `doi:${request.identifier}`)
|
|
258
|
+
: await fetchOpenalexJson({ filter: `ids.pmid:${request.identifier}` }, deps, signal);
|
|
259
|
+
const results = openalexResults(doc);
|
|
260
|
+
const first = results[0];
|
|
261
|
+
if (kind !== "doi" || first === undefined || !isRecord(first)) {
|
|
262
|
+
if (first === undefined || !isRecord(first)) {
|
|
263
|
+
throw new ApiError(`OpenAlex returned no work for ${request.identifier}`, 404);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return mapWork(first);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
return { search, get };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* OpenAlex capability set — the science trio plus diagnostics (D2
|
|
273
|
+
* round-3). `quota` never appears (quota dashboard filter exclusion),
|
|
274
|
+
* with or without the optional key.
|
|
275
|
+
*/
|
|
276
|
+
const OPENALEX_CAPABILITIES = new Set([
|
|
277
|
+
"science.search",
|
|
278
|
+
"science.get",
|
|
279
|
+
"diagnostics",
|
|
280
|
+
]);
|
|
281
|
+
/**
|
|
282
|
+
* Build the OpenAlex Provider Descriptor. Keyless-by-default with an
|
|
283
|
+
* optional upgrade key: `isConfigured` is true for the no-capability
|
|
284
|
+
* form (doctor) and the science trio + diagnostics — never `quota` or
|
|
285
|
+
* non-science capabilities. `create()` is side-effect-free; transport
|
|
286
|
+
* and credential resolution run per capability call.
|
|
287
|
+
*/
|
|
288
|
+
export function createOpenalexDescriptor(dependencies) {
|
|
289
|
+
const transport = dependencies?.transport;
|
|
290
|
+
return {
|
|
291
|
+
id: "openalex",
|
|
292
|
+
isConfigured(_env, capabilityId) {
|
|
293
|
+
if (capabilityId === undefined)
|
|
294
|
+
return true;
|
|
295
|
+
return OPENALEX_CAPABILITIES.has(capabilityId);
|
|
296
|
+
},
|
|
297
|
+
capabilities() {
|
|
298
|
+
return OPENALEX_CAPABILITIES;
|
|
299
|
+
},
|
|
300
|
+
create(context) {
|
|
301
|
+
return {
|
|
302
|
+
id: "openalex",
|
|
303
|
+
science: createOpenalexScienceCapability({ env: context.env, transport }),
|
|
304
|
+
diagnostics: createOpenalexDiagnosticsCapability({
|
|
305
|
+
transport,
|
|
306
|
+
env: context.env,
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
credentialEnvVars: ["OPENALEX_API_KEY"],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/providers/openalex/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProviderQuotaFetch } from "../types.js";
|
|
2
|
+
export declare const OPENALEX_WORKS_URL = "https://api.openalex.org/works";
|
|
3
|
+
/** Injectable transport dependencies (fetch, timers). */
|
|
4
|
+
export interface OpenalexTransportDeps {
|
|
5
|
+
readonly fetch?: ProviderQuotaFetch;
|
|
6
|
+
readonly setTimeout?: typeof setTimeout;
|
|
7
|
+
readonly clearTimeout?: typeof clearTimeout;
|
|
8
|
+
}
|
|
9
|
+
/** Credential resolved per invocation from the injected env. */
|
|
10
|
+
export interface OpenalexCredentials {
|
|
11
|
+
/** `OPENALEX_API_KEY` when present; keyless otherwise. */
|
|
12
|
+
readonly apiKey?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveOpenalexCredentials(env: NodeJS.ProcessEnv): OpenalexCredentials;
|
|
15
|
+
export declare function fetchOpenalexJson(params: Record<string, string>, deps?: OpenalexTransportDeps & {
|
|
16
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
17
|
+
}, signal?: AbortSignal, path?: string): Promise<unknown>;
|
|
18
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/openalex/client.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKtD,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AAMnE,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;CAC7C;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,mBAAmB,CAGtF;AAwED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,IAAI,GAAE,qBAAqB,GAAG;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,EACvE,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,SAAK,GACR,OAAO,CAAC,OAAO,CAAC,CAkElB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAlex direct HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* GETs against `https://api.openalex.org/works` (DESIGN D2 supplier
|
|
5
|
+
* table) consuming JSON. OpenAlex is keyless-by-default with an
|
|
6
|
+
* optional `OPENALEX_API_KEY` upgrade: with no key the request carries
|
|
7
|
+
* the politeness `mailto=` query param (house contact address, D2
|
|
8
|
+
* politeness bullet); with a key the `api_key=` query param replaces
|
|
9
|
+
* it (OpenAlex consumes the key as a query param, not a header). The
|
|
10
|
+
* house `USER_AGENT` rides every request either way.
|
|
11
|
+
*
|
|
12
|
+
* Boundary rules (ARCHITECTURE.md §2), cloning the arXiv client shape:
|
|
13
|
+
* - May import normalized errors and the provider fetch seam.
|
|
14
|
+
* - Must NOT perform response field normalization — the Adapter owns
|
|
15
|
+
* parsing. This module returns the parsed JSON document as unknown.
|
|
16
|
+
*/
|
|
17
|
+
import pkg from "../../../package.json" with { type: "json" };
|
|
18
|
+
import { ApiError, AuthError, NetworkError, QuotaError, TimeoutError } from "../../lib/errors.js";
|
|
19
|
+
import { getGlobalFetch } from "../types.js";
|
|
20
|
+
const { version: VERSION } = pkg;
|
|
21
|
+
export const OPENALEX_WORKS_URL = "https://api.openalex.org/works";
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
23
|
+
const USER_AGENT = `scoutline/${VERSION}`;
|
|
24
|
+
/** House contact address for the OpenAlex polite pool (D2 politeness). */
|
|
25
|
+
const OPENALEX_MAILTO = "scoutline@localhost";
|
|
26
|
+
export function resolveOpenalexCredentials(env) {
|
|
27
|
+
const apiKey = env["OPENALEX_API_KEY"];
|
|
28
|
+
return apiKey === undefined || apiKey === "" ? {} : { apiKey };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Layer 1 — HTTP-status mapping, before the body is consumed. 401/403
|
|
32
|
+
* → AuthError; 408/504 → TimeoutError; other 4xx/5xx → ApiError with
|
|
33
|
+
* the real status preserved for the shared retry classifier.
|
|
34
|
+
*/
|
|
35
|
+
function mapStatusError(status, timeoutMs) {
|
|
36
|
+
if (status === 401 || status === 403) {
|
|
37
|
+
return new AuthError("OpenAlex rejected the request");
|
|
38
|
+
}
|
|
39
|
+
if (status === 408 || status === 504) {
|
|
40
|
+
return new TimeoutError(timeoutMs);
|
|
41
|
+
}
|
|
42
|
+
if (status === 429) {
|
|
43
|
+
return new QuotaError("OpenAlex rate-limited — keyless budget; a free key raises the limit (see `scoutline init`)");
|
|
44
|
+
}
|
|
45
|
+
if (status === 503) {
|
|
46
|
+
// Remedy (owner ruling): anonymous search may be paused under load —
|
|
47
|
+
// the free key via `scoutline init` restores it. The class stays
|
|
48
|
+
// ApiError (the mapping pin) — only the message carries the remedy.
|
|
49
|
+
return new ApiError("OpenAlex request failed (anonymous search may be paused under load — a free API key via `scoutline init` restores it)", status);
|
|
50
|
+
}
|
|
51
|
+
return new ApiError("OpenAlex request failed", status);
|
|
52
|
+
}
|
|
53
|
+
/** Same transport-error normalization contract as the arXiv client. */
|
|
54
|
+
function normalizeTransportError(error, timeoutMs) {
|
|
55
|
+
if (error instanceof AuthError ||
|
|
56
|
+
error instanceof ApiError ||
|
|
57
|
+
error instanceof QuotaError ||
|
|
58
|
+
error instanceof TimeoutError) {
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
62
|
+
return new TimeoutError(timeoutMs);
|
|
63
|
+
}
|
|
64
|
+
return new NetworkError("OpenAlex network error");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Core GET. `params` carries OpenAlex-native query parameters (search,
|
|
68
|
+
* filter, per-page — the Adapter maps Provider-neutral science
|
|
69
|
+
* requests into these); `path` addresses an entity route for
|
|
70
|
+
* identifier-addressed gets (e.g. `doi:10.1038/...`). Returns the
|
|
71
|
+
* parsed JSON document. No internal retry — shared execution owns
|
|
72
|
+
* retry policy.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Percent-encode one entity-route path segment, preserving `/` at the
|
|
76
|
+
* caller. Encodes the URL-delimiter characters (`?`, `#`) and a stray
|
|
77
|
+
* `%` (keeps the segment unambiguous); leaves the `doi:` prefix colon
|
|
78
|
+
* and DOI-legal punctuation readable.
|
|
79
|
+
*/
|
|
80
|
+
function encodePathSegment(segment) {
|
|
81
|
+
// Dot-only segments are double-encoded (review): `new URL`
|
|
82
|
+
// normalizes an unencoded `..` away (`/works/../x` → `/works/x`),
|
|
83
|
+
// silently addressing the WRONG entity. %252E survives URL parsing
|
|
84
|
+
// as the literal segment `..`.
|
|
85
|
+
if (segment === "." || segment === "..") {
|
|
86
|
+
return segment.replaceAll(".", "%252E");
|
|
87
|
+
}
|
|
88
|
+
return segment.replace(/[?#%]/g, (c) => encodeURIComponent(c));
|
|
89
|
+
}
|
|
90
|
+
export async function fetchOpenalexJson(params, deps = {}, signal, path = "") {
|
|
91
|
+
const f = deps.fetch ?? getGlobalFetch();
|
|
92
|
+
const setT = deps.setTimeout ?? setTimeout;
|
|
93
|
+
const clearT = deps.clearTimeout ?? clearTimeout;
|
|
94
|
+
const { apiKey } = resolveOpenalexCredentials(deps.env ?? {});
|
|
95
|
+
// Entity-route path segments are percent-encoded (review): a DOI
|
|
96
|
+
// suffix containing `?` or `#` would otherwise be truncated into the
|
|
97
|
+
// query/fragment by `new URL`. `/` separators are preserved, and the
|
|
98
|
+
// `doi:` prefix colon stays readable.
|
|
99
|
+
const url = new URL(`${OPENALEX_WORKS_URL}${path ? `/${path.replace(/^\/+/, "").split("/").map(encodePathSegment).join("/")}` : ""}`);
|
|
100
|
+
for (const [key, value] of Object.entries(params)) {
|
|
101
|
+
url.searchParams.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
// Politeness (D2): mailto whenever keyless; api_key replaces it when
|
|
104
|
+
// the upgrade key is present (never both).
|
|
105
|
+
if (apiKey !== undefined) {
|
|
106
|
+
url.searchParams.set("api_key", apiKey);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
url.searchParams.set("mailto", OPENALEX_MAILTO);
|
|
110
|
+
}
|
|
111
|
+
// A pre-aborted caller signal must not reach the transport (review):
|
|
112
|
+
// reject before the fetch is invoked at all.
|
|
113
|
+
if (signal?.aborted) {
|
|
114
|
+
throw new TimeoutError(DEFAULT_TIMEOUT_MS);
|
|
115
|
+
}
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
const timeoutId = setT(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
118
|
+
const abortWithExternal = () => controller.abort();
|
|
119
|
+
if (signal !== undefined) {
|
|
120
|
+
if (signal.aborted) {
|
|
121
|
+
controller.abort();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
signal.addEventListener("abort", abortWithExternal, { once: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const res = await f(url, {
|
|
129
|
+
method: "GET",
|
|
130
|
+
headers: { "User-Agent": USER_AGENT },
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
await res.text().catch(() => { });
|
|
135
|
+
throw mapStatusError(res.status, DEFAULT_TIMEOUT_MS);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return await res.json();
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
// The timeout stays armed through body consumption; an abort here
|
|
142
|
+
// is the injected timeout firing, not a malformed payload.
|
|
143
|
+
if (controller.signal.aborted)
|
|
144
|
+
throw err;
|
|
145
|
+
throw new ApiError("OpenAlex returned a malformed response", 500);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
throw normalizeTransportError(err, DEFAULT_TIMEOUT_MS);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
if (signal !== undefined) {
|
|
153
|
+
signal.removeEventListener("abort", abortWithExternal);
|
|
154
|
+
}
|
|
155
|
+
clearT(timeoutId);
|
|
156
|
+
controller.abort();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/providers/openalex/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAlex Diagnostics Capability.
|
|
3
|
+
*
|
|
4
|
+
* Keyless bounded probe (DESIGN D2 round-3 ruling): doctor probes
|
|
5
|
+
* every always-configured science supplier, so the OpenAlex probe is
|
|
6
|
+
* ONE minimal keyless wire call on the works endpoint (`per-page=1`)
|
|
7
|
+
* — never a full search. The politeness posture applies to it too:
|
|
8
|
+
* house UA plus the `mailto=` param whenever no api_key is present.
|
|
9
|
+
* When `diagOptions.probe` is false, `invoke` resolves immediately
|
|
10
|
+
* without touching the network.
|
|
11
|
+
*/
|
|
12
|
+
import type { DiagnosticsCapability } from "../../capabilities/diagnostics.js";
|
|
13
|
+
import { type OpenalexTransportDeps } from "./client.js";
|
|
14
|
+
export interface OpenalexDiagnosticsCapabilityOptions {
|
|
15
|
+
readonly env: NodeJS.ProcessEnv;
|
|
16
|
+
readonly transport?: OpenalexTransportDeps;
|
|
17
|
+
}
|
|
18
|
+
export declare function createOpenalexDiagnosticsCapability(options: OpenalexDiagnosticsCapabilityOptions): DiagnosticsCapability;
|
|
19
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/providers/openalex/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAqB,MAAM,mCAAmC,CAAC;AAElG,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAO5E,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,qBAAqB,CAevB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createProbeErrorNormalizer, STANDARD_PROBE_PASS_THROUGH } from "../../lib/probe-errors.js";
|
|
2
|
+
import { fetchOpenalexJson } from "./client.js";
|
|
3
|
+
const normalizeProbeError = createProbeErrorNormalizer({
|
|
4
|
+
passThrough: STANDARD_PROBE_PASS_THROUGH,
|
|
5
|
+
fallbackMessage: "OpenAlex diagnostics probe failed",
|
|
6
|
+
});
|
|
7
|
+
export function createOpenalexDiagnosticsCapability(options) {
|
|
8
|
+
const { env, transport } = options;
|
|
9
|
+
return {
|
|
10
|
+
async invoke(diagOptions) {
|
|
11
|
+
if (!diagOptions.probe)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
// One minimal keyless wire call on the works endpoint. The
|
|
15
|
+
// request parameter is `per-page` (review) — `per_page` is only
|
|
16
|
+
// response metadata the server ignores.
|
|
17
|
+
await fetchOpenalexJson({ "per-page": "1" }, { ...transport, env });
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw normalizeProbeError(error);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../../src/providers/openalex/diagnostics.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAA8B,MAAM,aAAa,CAAC;AAE5E,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;IACrD,WAAW,EAAE,2BAA2B;IACxC,eAAe,EAAE,mCAAmC;CACrD,CAAC,CAAC;AAOH,MAAM,UAAU,mCAAmC,CACjD,OAA6C;IAE7C,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IACnC,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,WAA8B;YACzC,IAAI,CAAC,WAAW,CAAC,KAAK;gBAAE,OAAO;YAC/B,IAAI,CAAC;gBACH,2DAA2D;gBAC3D,gEAAgE;gBAChE,wCAAwC;gBACxC,MAAM,iBAAiB,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YACtE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,mBAAmB,CAAC,KAAK,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* structured results with titles, URLs, snippets, and dates — far richer
|
|
9
9
|
* than mapping chat completions to citations.
|
|
10
10
|
*
|
|
11
|
-
* Research uses the
|
|
12
|
-
* `sonar-deep-research`
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Research uses the Agent API (POST /v1/agent) with the `high`
|
|
12
|
+
* preset — the official successor to `sonar-deep-research` on the
|
|
13
|
+
* Sonar chat-completions endpoint (sunset 2026-09-27, #107). The
|
|
14
|
+
* response carries an `output[]` trace: `message` items hold the
|
|
15
|
+
* report text, `search_results` items hold structured sources
|
|
16
|
+
* (title, url, date, snippet) — one item per search round.
|
|
15
17
|
*
|
|
16
18
|
* Reader is intentionally omitted: Perplexity does not offer a
|
|
17
19
|
* dedicated webpage extraction API.
|
|
@@ -22,10 +24,10 @@
|
|
|
22
24
|
* results[].url -> url
|
|
23
25
|
* results[].snippet -> summary
|
|
24
26
|
* results[].date -> date
|
|
25
|
-
* Research (/
|
|
26
|
-
*
|
|
27
|
-
* search_results[]
|
|
28
|
-
*
|
|
27
|
+
* Research (/v1/agent, preset "high"):
|
|
28
|
+
* output[].message.content[].text -> report
|
|
29
|
+
* output[].search_results.results[] -> sources[] (union across
|
|
30
|
+
* search rounds, deduped by URL, first occurrence wins)
|
|
29
31
|
*
|
|
30
32
|
* Control mapping (SearchControls → Search API params):
|
|
31
33
|
* domain -> search_domain_filter: [domain]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/perplexity/adapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/perplexity/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EACf,kBAAkB,EAClB,UAAU,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAIV,gBAAgB,EAGjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,kBAAkB,EAGnB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAa/E,OAAO,EAKL,KAAK,uBAAuB,EAC7B,MAAM,aAAa,CAAC;AAmGrB,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,uBAAuB,CAAC;CAC9C;AA0CD,qBAAa,iBAAkB,YAAW,eAAe;IAOrD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAgB;IACvC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC;IAE5C,YACmB,OAAO,EAAE,eAAe,EACzC,IAAI,GAAE,6BAAkC,EAwHzC;CACF;AAED,wBAAgB,0BAA0B,IAAI,kBAAkB,CAU/D"}
|