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.
Files changed (270) hide show
  1. package/README.md +131 -25
  2. package/bin/scoutline.js +0 -0
  3. package/dist/capabilities/map.d.ts +3 -3
  4. package/dist/capabilities/map.js +3 -3
  5. package/dist/capabilities/quota.d.ts +13 -6
  6. package/dist/capabilities/quota.d.ts.map +1 -1
  7. package/dist/capabilities/quota.js +15 -0
  8. package/dist/capabilities/quota.js.map +1 -1
  9. package/dist/capabilities/science.d.ts +193 -0
  10. package/dist/capabilities/science.d.ts.map +1 -0
  11. package/dist/capabilities/science.js +110 -0
  12. package/dist/capabilities/science.js.map +1 -0
  13. package/dist/command-invocation.d.ts +13 -1
  14. package/dist/command-invocation.d.ts.map +1 -1
  15. package/dist/command-invocation.js +44 -2
  16. package/dist/command-invocation.js.map +1 -1
  17. package/dist/commands/archive.d.ts +178 -0
  18. package/dist/commands/archive.d.ts.map +1 -0
  19. package/dist/commands/archive.js +871 -0
  20. package/dist/commands/archive.js.map +1 -0
  21. package/dist/commands/config.d.ts.map +1 -1
  22. package/dist/commands/config.js +12 -3
  23. package/dist/commands/config.js.map +1 -1
  24. package/dist/commands/crawl.d.ts +15 -2
  25. package/dist/commands/crawl.d.ts.map +1 -1
  26. package/dist/commands/crawl.js +93 -34
  27. package/dist/commands/crawl.js.map +1 -1
  28. package/dist/commands/doctor.d.ts +8 -0
  29. package/dist/commands/doctor.d.ts.map +1 -1
  30. package/dist/commands/doctor.js +40 -5
  31. package/dist/commands/doctor.js.map +1 -1
  32. package/dist/commands/fetch.d.ts +75 -0
  33. package/dist/commands/fetch.d.ts.map +1 -0
  34. package/dist/commands/fetch.js +596 -0
  35. package/dist/commands/fetch.js.map +1 -0
  36. package/dist/commands/history.d.ts +165 -8
  37. package/dist/commands/history.d.ts.map +1 -1
  38. package/dist/commands/history.js +591 -20
  39. package/dist/commands/history.js.map +1 -1
  40. package/dist/commands/init.d.ts +13 -0
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +411 -43
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/map.d.ts.map +1 -1
  45. package/dist/commands/map.js +5 -0
  46. package/dist/commands/map.js.map +1 -1
  47. package/dist/commands/quota.d.ts.map +1 -1
  48. package/dist/commands/quota.js +2 -0
  49. package/dist/commands/quota.js.map +1 -1
  50. package/dist/commands/read.d.ts +28 -6
  51. package/dist/commands/read.d.ts.map +1 -1
  52. package/dist/commands/read.js +328 -31
  53. package/dist/commands/read.js.map +1 -1
  54. package/dist/commands/repo.d.ts +12 -10
  55. package/dist/commands/repo.d.ts.map +1 -1
  56. package/dist/commands/repo.js +189 -21
  57. package/dist/commands/repo.js.map +1 -1
  58. package/dist/commands/repository-explorer.d.ts +15 -7
  59. package/dist/commands/repository-explorer.d.ts.map +1 -1
  60. package/dist/commands/repository-explorer.js +117 -97
  61. package/dist/commands/repository-explorer.js.map +1 -1
  62. package/dist/commands/research.d.ts +21 -4
  63. package/dist/commands/research.d.ts.map +1 -1
  64. package/dist/commands/research.js +83 -12
  65. package/dist/commands/research.js.map +1 -1
  66. package/dist/commands/science.d.ts +54 -0
  67. package/dist/commands/science.d.ts.map +1 -0
  68. package/dist/commands/science.js +999 -0
  69. package/dist/commands/science.js.map +1 -0
  70. package/dist/commands/search.d.ts +13 -2
  71. package/dist/commands/search.d.ts.map +1 -1
  72. package/dist/commands/search.js +106 -7
  73. package/dist/commands/search.js.map +1 -1
  74. package/dist/commands/watch.d.ts +94 -0
  75. package/dist/commands/watch.d.ts.map +1 -0
  76. package/dist/commands/watch.js +900 -0
  77. package/dist/commands/watch.js.map +1 -0
  78. package/dist/index.d.ts +168 -4
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +1679 -232
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/agent-registration/deploy.d.ts +69 -0
  83. package/dist/lib/agent-registration/deploy.d.ts.map +1 -0
  84. package/dist/lib/agent-registration/deploy.js +279 -0
  85. package/dist/lib/agent-registration/deploy.js.map +1 -0
  86. package/dist/lib/agent-registration/engines.d.ts +77 -0
  87. package/dist/lib/agent-registration/engines.d.ts.map +1 -0
  88. package/dist/lib/agent-registration/engines.js +544 -0
  89. package/dist/lib/agent-registration/engines.js.map +1 -0
  90. package/dist/lib/agent-registration/registry.d.ts +32 -0
  91. package/dist/lib/agent-registration/registry.d.ts.map +1 -0
  92. package/dist/lib/agent-registration/registry.js +94 -0
  93. package/dist/lib/agent-registration/registry.js.map +1 -0
  94. package/dist/lib/api-client.d.ts +2 -3
  95. package/dist/lib/api-client.d.ts.map +1 -1
  96. package/dist/lib/api-client.js +18 -0
  97. package/dist/lib/api-client.js.map +1 -1
  98. package/dist/lib/artifacts.d.ts +83 -3
  99. package/dist/lib/artifacts.d.ts.map +1 -1
  100. package/dist/lib/artifacts.js +246 -13
  101. package/dist/lib/artifacts.js.map +1 -1
  102. package/dist/lib/async-file-lock.d.ts +19 -9
  103. package/dist/lib/async-file-lock.d.ts.map +1 -1
  104. package/dist/lib/async-file-lock.js +25 -11
  105. package/dist/lib/async-file-lock.js.map +1 -1
  106. package/dist/lib/batch-manifest.d.ts +1 -0
  107. package/dist/lib/batch-manifest.d.ts.map +1 -1
  108. package/dist/lib/batch-manifest.js +12 -5
  109. package/dist/lib/batch-manifest.js.map +1 -1
  110. package/dist/lib/batch-runner.d.ts.map +1 -1
  111. package/dist/lib/batch-runner.js +25 -0
  112. package/dist/lib/batch-runner.js.map +1 -1
  113. package/dist/lib/code-mode.d.ts +19 -0
  114. package/dist/lib/code-mode.d.ts.map +1 -1
  115. package/dist/lib/code-mode.js +170 -14
  116. package/dist/lib/code-mode.js.map +1 -1
  117. package/dist/lib/config-store.d.ts +48 -1
  118. package/dist/lib/config-store.d.ts.map +1 -1
  119. package/dist/lib/config-store.js +92 -5
  120. package/dist/lib/config-store.js.map +1 -1
  121. package/dist/lib/config.js +3 -3
  122. package/dist/lib/config.js.map +1 -1
  123. package/dist/lib/errors.d.ts +20 -0
  124. package/dist/lib/errors.d.ts.map +1 -1
  125. package/dist/lib/errors.js +26 -0
  126. package/dist/lib/errors.js.map +1 -1
  127. package/dist/lib/journal.d.ts +258 -0
  128. package/dist/lib/journal.d.ts.map +1 -0
  129. package/dist/lib/journal.js +582 -0
  130. package/dist/lib/journal.js.map +1 -0
  131. package/dist/lib/mcp-client.d.ts +18 -0
  132. package/dist/lib/mcp-client.d.ts.map +1 -1
  133. package/dist/lib/mcp-client.js +145 -17
  134. package/dist/lib/mcp-client.js.map +1 -1
  135. package/dist/lib/output-budget-persistence.d.ts +76 -0
  136. package/dist/lib/output-budget-persistence.d.ts.map +1 -0
  137. package/dist/lib/output-budget-persistence.js +76 -0
  138. package/dist/lib/output-budget-persistence.js.map +1 -0
  139. package/dist/lib/output-budget.d.ts +99 -0
  140. package/dist/lib/output-budget.d.ts.map +1 -0
  141. package/dist/lib/output-budget.js +175 -0
  142. package/dist/lib/output-budget.js.map +1 -0
  143. package/dist/lib/pdf.d.ts +22 -0
  144. package/dist/lib/pdf.d.ts.map +1 -0
  145. package/dist/lib/pdf.js +1213 -0
  146. package/dist/lib/pdf.js.map +1 -0
  147. package/dist/lib/provider-fallback.d.ts.map +1 -1
  148. package/dist/lib/provider-fallback.js +3 -0
  149. package/dist/lib/provider-fallback.js.map +1 -1
  150. package/dist/lib/quota-mapping.d.ts.map +1 -1
  151. package/dist/lib/quota-mapping.js +31 -0
  152. package/dist/lib/quota-mapping.js.map +1 -1
  153. package/dist/lib/quota-store.d.ts +11 -1
  154. package/dist/lib/quota-store.d.ts.map +1 -1
  155. package/dist/lib/quota-store.js +11 -2
  156. package/dist/lib/quota-store.js.map +1 -1
  157. package/dist/lib/redact.d.ts.map +1 -1
  158. package/dist/lib/redact.js +18 -8
  159. package/dist/lib/redact.js.map +1 -1
  160. package/dist/lib/save-artifacts.d.ts +38 -0
  161. package/dist/lib/save-artifacts.d.ts.map +1 -0
  162. package/dist/lib/save-artifacts.js +146 -0
  163. package/dist/lib/save-artifacts.js.map +1 -0
  164. package/dist/lib/section-diff.d.ts +90 -0
  165. package/dist/lib/section-diff.d.ts.map +1 -0
  166. package/dist/lib/section-diff.js +395 -0
  167. package/dist/lib/section-diff.js.map +1 -0
  168. package/dist/lib/skill-source.d.ts +11 -0
  169. package/dist/lib/skill-source.d.ts.map +1 -0
  170. package/dist/lib/skill-source.js +22 -0
  171. package/dist/lib/skill-source.js.map +1 -0
  172. package/dist/lib/tty.d.ts +1 -1
  173. package/dist/lib/tty.d.ts.map +1 -1
  174. package/dist/lib/tty.js +10 -1
  175. package/dist/lib/tty.js.map +1 -1
  176. package/dist/lib/watch-store.d.ts +203 -0
  177. package/dist/lib/watch-store.d.ts.map +1 -0
  178. package/dist/lib/watch-store.js +628 -0
  179. package/dist/lib/watch-store.js.map +1 -0
  180. package/dist/providers/arxiv/adapter.d.ts +88 -0
  181. package/dist/providers/arxiv/adapter.d.ts.map +1 -0
  182. package/dist/providers/arxiv/adapter.js +316 -0
  183. package/dist/providers/arxiv/adapter.js.map +1 -0
  184. package/dist/providers/arxiv/client.d.ts +33 -0
  185. package/dist/providers/arxiv/client.d.ts.map +1 -0
  186. package/dist/providers/arxiv/client.js +130 -0
  187. package/dist/providers/arxiv/client.js.map +1 -0
  188. package/dist/providers/arxiv/diagnostics.d.ts +28 -0
  189. package/dist/providers/arxiv/diagnostics.d.ts.map +1 -0
  190. package/dist/providers/arxiv/diagnostics.js +34 -0
  191. package/dist/providers/arxiv/diagnostics.js.map +1 -0
  192. package/dist/providers/crossref/adapter.d.ts +97 -0
  193. package/dist/providers/crossref/adapter.d.ts.map +1 -0
  194. package/dist/providers/crossref/adapter.js +249 -0
  195. package/dist/providers/crossref/adapter.js.map +1 -0
  196. package/dist/providers/crossref/client.d.ts +10 -0
  197. package/dist/providers/crossref/client.d.ts.map +1 -0
  198. package/dist/providers/crossref/client.js +143 -0
  199. package/dist/providers/crossref/client.js.map +1 -0
  200. package/dist/providers/crossref/diagnostics.d.ts +18 -0
  201. package/dist/providers/crossref/diagnostics.d.ts.map +1 -0
  202. package/dist/providers/crossref/diagnostics.js +23 -0
  203. package/dist/providers/crossref/diagnostics.js.map +1 -0
  204. package/dist/providers/europepmc/adapter.d.ts +94 -0
  205. package/dist/providers/europepmc/adapter.d.ts.map +1 -0
  206. package/dist/providers/europepmc/adapter.js +264 -0
  207. package/dist/providers/europepmc/adapter.js.map +1 -0
  208. package/dist/providers/europepmc/client.d.ts +18 -0
  209. package/dist/providers/europepmc/client.d.ts.map +1 -0
  210. package/dist/providers/europepmc/client.js +128 -0
  211. package/dist/providers/europepmc/client.js.map +1 -0
  212. package/dist/providers/europepmc/diagnostics.d.ts +18 -0
  213. package/dist/providers/europepmc/diagnostics.d.ts.map +1 -0
  214. package/dist/providers/europepmc/diagnostics.js +23 -0
  215. package/dist/providers/europepmc/diagnostics.js.map +1 -0
  216. package/dist/providers/openalex/adapter.d.ts +53 -0
  217. package/dist/providers/openalex/adapter.d.ts.map +1 -0
  218. package/dist/providers/openalex/adapter.js +313 -0
  219. package/dist/providers/openalex/adapter.js.map +1 -0
  220. package/dist/providers/openalex/client.d.ts +18 -0
  221. package/dist/providers/openalex/client.d.ts.map +1 -0
  222. package/dist/providers/openalex/client.js +159 -0
  223. package/dist/providers/openalex/client.js.map +1 -0
  224. package/dist/providers/openalex/diagnostics.d.ts +19 -0
  225. package/dist/providers/openalex/diagnostics.d.ts.map +1 -0
  226. package/dist/providers/openalex/diagnostics.js +25 -0
  227. package/dist/providers/openalex/diagnostics.js.map +1 -0
  228. package/dist/providers/perplexity/adapter.d.ts +10 -8
  229. package/dist/providers/perplexity/adapter.d.ts.map +1 -1
  230. package/dist/providers/perplexity/adapter.js +68 -41
  231. package/dist/providers/perplexity/adapter.js.map +1 -1
  232. package/dist/providers/perplexity/client.d.ts +22 -12
  233. package/dist/providers/perplexity/client.d.ts.map +1 -1
  234. package/dist/providers/perplexity/client.js +18 -17
  235. package/dist/providers/perplexity/client.js.map +1 -1
  236. package/dist/providers/pubmed/adapter.d.ts +56 -0
  237. package/dist/providers/pubmed/adapter.d.ts.map +1 -0
  238. package/dist/providers/pubmed/adapter.js +425 -0
  239. package/dist/providers/pubmed/adapter.js.map +1 -0
  240. package/dist/providers/pubmed/client.d.ts +29 -0
  241. package/dist/providers/pubmed/client.d.ts.map +1 -0
  242. package/dist/providers/pubmed/client.js +153 -0
  243. package/dist/providers/pubmed/client.js.map +1 -0
  244. package/dist/providers/pubmed/diagnostics.d.ts +18 -0
  245. package/dist/providers/pubmed/diagnostics.d.ts.map +1 -0
  246. package/dist/providers/pubmed/diagnostics.js +23 -0
  247. package/dist/providers/pubmed/diagnostics.js.map +1 -0
  248. package/dist/providers/registry.d.ts.map +1 -1
  249. package/dist/providers/registry.js +16 -0
  250. package/dist/providers/registry.js.map +1 -1
  251. package/dist/providers/selection.d.ts +1 -1
  252. package/dist/providers/selection.d.ts.map +1 -1
  253. package/dist/providers/selection.js +5 -2
  254. package/dist/providers/selection.js.map +1 -1
  255. package/dist/providers/types.d.ts +28 -7
  256. package/dist/providers/types.d.ts.map +1 -1
  257. package/dist/providers/types.js +94 -6
  258. package/dist/providers/types.js.map +1 -1
  259. package/dist/providers/zai/quota.d.ts.map +1 -1
  260. package/dist/providers/zai/quota.js +29 -8
  261. package/dist/providers/zai/quota.js.map +1 -1
  262. package/dist/providers/zai/reader.d.ts.map +1 -1
  263. package/dist/providers/zai/reader.js +5 -3
  264. package/dist/providers/zai/reader.js.map +1 -1
  265. package/dist/providers/zai/repository.d.ts.map +1 -1
  266. package/dist/providers/zai/repository.js +5 -3
  267. package/dist/providers/zai/repository.js.map +1 -1
  268. package/package.json +3 -2
  269. package/skills/scoutline/SKILL.md +787 -0
  270. 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 Sonar Chat Completions endpoint with the
12
- * `sonar-deep-research` model, which is designed for comprehensive
13
- * report synthesis. The response includes `search_results[]` with
14
- * structured source data (title, url, date, snippet).
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 (/chat/completions):
26
- * choices[0].message.content -> report
27
- * search_results[].title/url -> sources[]
28
- * citations[] (fallback) -> sources[]
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;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,EAIL,KAAK,uBAAuB,EAC7B,MAAM,aAAa,CAAC;AAiGrB,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,uBAAuB,CAAC;CAC9C;AAED,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,EAgJzC;CACF;AAED,wBAAgB,0BAA0B,IAAI,kBAAkB,CAU/D"}
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"}