@companion-ai/feynman 0.5.8 → 0.5.10
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
CHANGED
|
@@ -10,176 +10,70 @@
|
|
|
10
10
|
<a href="https://deepwiki.com/Companion-Inc/feynman"><img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**macOS / Linux:**
|
|
15
|
+
macOS / Linux:
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
18
|
curl -fsSL https://feynman.is/install | bash
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
Windows (PowerShell):
|
|
24
22
|
|
|
25
23
|
```powershell
|
|
26
24
|
irm https://feynman.is/install.ps1 | iex
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
**npm alternative** (uses your local Node.js `>=22.22.0`):
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install -g @companion-ai/feynman
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
If you installed the interim `@advaitpaliwal/feynman` package, migrate once:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm uninstall -g @advaitpaliwal/feynman
|
|
41
|
-
npm install -g @companion-ai/feynman
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Then run `feynman setup` to sign in to a model provider. To use Feynman in an ACP editor such as Zed, run it through [pi-acp](https://github.com/svkozak/pi-acp): `"agent_servers": { "Feynman": { "command": "npx", "args": ["-y", "pi-acp"], "env": { "PI_ACP_PI_COMMAND": "feynman" } } }`. See the [installation guide](https://feynman.is/docs/getting-started/installation) for uninstalling and the [setup guide](https://feynman.is/docs/getting-started/setup) for local models (LM Studio, LiteLLM, Ollama, vLLM) and Amazon Bedrock.
|
|
45
|
-
|
|
46
|
-
### Skills Only
|
|
47
|
-
|
|
48
|
-
If you want just the research skills without the full terminal app:
|
|
27
|
+
Or with npm and Node.js 22.22 or newer: `npm install -g @companion-ai/feynman`.
|
|
49
28
|
|
|
50
|
-
|
|
29
|
+
Then sign in to a model provider:
|
|
51
30
|
|
|
52
31
|
```bash
|
|
53
|
-
|
|
32
|
+
feynman setup
|
|
54
33
|
```
|
|
55
34
|
|
|
56
|
-
|
|
35
|
+
The [installation guide](https://feynman.is/docs/getting-started/installation) covers pinned versions, uninstalling, and the skills-only bundle for Codex and OpenCode. The [setup guide](https://feynman.is/docs/getting-started/setup) covers local models and editors such as Zed.
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
irm https://feynman.is/install-skills.ps1 | iex
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
That installs the skill library into `~/.codex/skills/feynman` for Codex. For other targets, pass a scope:
|
|
37
|
+
## Use
|
|
63
38
|
|
|
64
39
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
```powershell
|
|
71
|
-
& ([scriptblock]::Create((irm https://feynman.is/install-skills.ps1))) -Scope Codex # or -Scope Repo, -Scope OpenCode
|
|
40
|
+
feynman "what do we know about scaling laws"
|
|
41
|
+
feynman lit "RLHF alternatives"
|
|
42
|
+
feynman deepresearch "mechanistic interpretability"
|
|
72
43
|
```
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
### What you type → what happens
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
$ feynman "what do we know about scaling laws"
|
|
82
|
-
→ Searches papers and the web, answers with cited sources
|
|
83
|
-
|
|
84
|
-
$ feynman deepresearch "mechanistic interpretability"
|
|
85
|
-
→ Plan-first investigation with parallel researchers, synthesis, and citation verification
|
|
86
|
-
|
|
87
|
-
$ feynman lit "RLHF alternatives"
|
|
88
|
-
→ Literature review with consensus, disagreements, open questions, and lab/PI corpus mode when the input names a research group
|
|
89
|
-
|
|
90
|
-
$ feynman audit 2401.12345
|
|
91
|
-
→ Compares paper claims against the public codebase
|
|
92
|
-
|
|
93
|
-
$ feynman replicate "chain-of-thought improves math"
|
|
94
|
-
→ Plans replication checks and runs them only after an explicit environment choice
|
|
95
|
-
|
|
96
|
-
$ feynman recipe "fine-tune a small model for math reasoning"
|
|
97
|
-
→ Finds ranked, implementable ML training recipes from papers, datasets, docs, and code
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
### Workflows
|
|
103
|
-
|
|
104
|
-
Ask naturally or use slash commands as shortcuts.
|
|
105
|
-
|
|
106
|
-
| Command | What it does |
|
|
45
|
+
| Workflow | What it does |
|
|
107
46
|
| --- | --- |
|
|
108
|
-
| `/deepresearch <topic>` |
|
|
109
|
-
| `/lit <topic
|
|
110
|
-
| `/review <
|
|
111
|
-
| `/audit <
|
|
112
|
-
| `/replicate <paper>` |
|
|
113
|
-
| `/recipe <task
|
|
47
|
+
| `/deepresearch <topic>` | Planned investigation with parallel researchers and citation checks |
|
|
48
|
+
| `/lit <topic or lab>` | Literature review: consensus, disagreements, open questions |
|
|
49
|
+
| `/review <paper or draft>` | Critique with severity levels and a revision plan |
|
|
50
|
+
| `/audit <paper>` | Paper claims compared with its public code |
|
|
51
|
+
| `/replicate <paper>` | Replication plan; runs only after you choose an environment |
|
|
52
|
+
| `/recipe <task>` | Ranked ML training recipes with datasets and code |
|
|
114
53
|
| `/compare <topic>` | Source comparison matrix |
|
|
115
54
|
| `/draft <topic>` | Paper-style draft from research findings |
|
|
116
|
-
| `/autoresearch <idea>` | Bounded experiment loop
|
|
117
|
-
| `/summarize <source>` |
|
|
118
|
-
| `/btw <question>` | Side conversation while the main research agent is busy, with optional handoff back into the main thread |
|
|
119
|
-
| `/outputs` | Browse all research artifacts |
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
### Agents
|
|
124
|
-
|
|
125
|
-
Four bundled research agents, invoked by workflow prompts when decomposition helps.
|
|
126
|
-
|
|
127
|
-
- **Researcher** — gather evidence across papers, web, repos, docs
|
|
128
|
-
- **Reviewer** — internal research critique with severity-graded feedback
|
|
129
|
-
- **Writer** — structured drafts from research notes
|
|
130
|
-
- **Verifier** — inline citations, source URL verification, dead link cleanup
|
|
55
|
+
| `/autoresearch <idea>` | Bounded experiment loop against a benchmark |
|
|
56
|
+
| `/summarize <source>` | Summary of a paper, repository, or PDF |
|
|
131
57
|
|
|
132
|
-
|
|
58
|
+
Results are saved to `outputs/` (drafts to `papers/`). `/lit`, `/deepresearch`, and `/recipe` also write a provenance file listing the sources used and what was verified.
|
|
133
59
|
|
|
134
|
-
|
|
60
|
+
## Sources
|
|
135
61
|
|
|
136
|
-
|
|
137
|
-
- **Literature databases** — read-only Semantic Scholar, OpenAlex, arXiv ID lookup, PubMed, Europe PMC full text, bioRxiv/medRxiv, and Crossref, with stable identifiers. Set the free `OPENALEX_API_KEY` ([create one](https://openalex.org/settings/api)) and optionally `SEMANTIC_SCHOLAR_API_KEY` ([request one](https://www.semanticscholar.org/product/api#api-key-form)) to use your own rate limits
|
|
138
|
-
- **[Hugging Face Hub](https://huggingface.co/docs/hub/api)** — dataset metadata, split/schema inspection, and small file reads from model, dataset, and Space repos
|
|
139
|
-
- **Web research** — search, page fetching, and PDF extraction through [pi-web-access](https://github.com/nicobailon/pi-web-access); Exa works without a key, and `feynman search set` configures Perplexity, Exa, or Gemini
|
|
140
|
-
- **Documents** — local PDF and office-document parsing through [pi-docparser](https://github.com/maxedapps/pi-docparser)
|
|
141
|
-
- **Compute** — Docker, plus Modal or RunPod when their CLIs are installed, for replication and experiment runs you explicitly approve
|
|
62
|
+
Feynman searches alphaXiv, Semantic Scholar, OpenAlex, arXiv, PubMed, Europe PMC, bioRxiv, medRxiv, Crossref, the web, and Hugging Face, and reads local PDFs and documents. Free `OPENALEX_API_KEY` and `SEMANTIC_SCHOLAR_API_KEY` keys avoid the shared rate limits. It runs on stock [Pi](https://github.com/earendil-works/pi) with four research agents: researcher, verifier, reviewer, and writer.
|
|
142
63
|
|
|
143
|
-
|
|
64
|
+
## Telemetry
|
|
144
65
|
|
|
145
|
-
|
|
66
|
+
Feynman sends anonymous usage data to PostHog: commands, workflows, tools, models, token counts, and errors with their messages and stack traces (your home folder shown as `~`). It never sends prompts, model output, paper content, or tool arguments. Turn it off with `FEYNMAN_TELEMETRY=off`. [What is sent](https://feynman.is/docs/getting-started/configuration#telemetry)
|
|
146
67
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
### Telemetry
|
|
152
|
-
|
|
153
|
-
Feynman sends anonymous usage telemetry to PostHog by default and prints a one-time notice on first run. It sends commands, workflow names and outcomes, tool names, model and provider names, token counts, latency, and error messages with stack traces (home folder shown as `~`) under a random install ID. It never sends prompts, model output, paper content, or tool arguments. Set `FEYNMAN_TELEMETRY=off` (or `DO_NOT_TRACK=1`) to disable it; `feynman status` shows the current setting. The full event list is in the [configuration docs](https://feynman.is/docs/getting-started/configuration#telemetry).
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
### Star History
|
|
158
|
-
|
|
159
|
-
<a href="https://www.star-history.com/?repos=Companion-Inc%2Ffeynman&type=date&legend=top-left">
|
|
160
|
-
<picture>
|
|
161
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&theme=dark&legend=top-left" />
|
|
162
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&legend=top-left" />
|
|
163
|
-
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&legend=top-left" />
|
|
164
|
-
</picture>
|
|
165
|
-
</a>
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
The bundled Pi packages are pinned and update with Feynman, not through `feynman update`. See the [package stack](https://feynman.is/docs/reference/package-stack) and [release notes](https://feynman.is/docs/reference/releases).
|
|
170
|
-
|
|
171
|
-
### Contributing
|
|
172
|
-
|
|
173
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contributor guide.
|
|
68
|
+
## Contributing
|
|
174
69
|
|
|
175
70
|
```bash
|
|
176
71
|
git clone https://github.com/Companion-Inc/feynman.git
|
|
177
72
|
cd feynman
|
|
178
|
-
nvm use || nvm install
|
|
179
73
|
npm install
|
|
180
74
|
npm test
|
|
181
|
-
npm run typecheck
|
|
182
|
-
npm run build
|
|
183
75
|
```
|
|
184
76
|
|
|
185
|
-
[
|
|
77
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
78
|
+
|
|
79
|
+
[Docs](https://feynman.is/docs) · [Release notes](RELEASES.md) · [MIT License](LICENSE)
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,18 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.10 - 2026-09-27
|
|
10
|
+
|
|
11
|
+
- **Semantic Scholar understands the documented query syntax.** A `semantic:` prefix and `year_from=`/`year_to=` flags, which the model reuses from the OpenAlex examples, were sent to Semantic Scholar as search text and returned unrelated papers or nothing. They now become Semantic Scholar's own year filter.
|
|
12
|
+
- **The startup header lists only Feynman's workflows.** It also listed prompt commands that pi-subagents ships, such as `/parallel-review` and `/review-loop`.
|
|
13
|
+
- **Cleaner README and landing page** with a current screenshot, and a shorter configuration page.
|
|
14
|
+
|
|
15
|
+
## v0.5.9 - 2026-09-27
|
|
16
|
+
|
|
17
|
+
- **Subagents keep their search tools in the foreground.** A `researcher` or `verifier` launched in the foreground failed at once because foreground children never load the parent's extensions, so `web_search`, `fetch_content`, and `feynman_science_database_search` were missing. Feynman now points pi-subagents' `defaultSubagentOnlyExtensions` setting at its research tools and pi-web-access, so every child has them, in the foreground or background.
|
|
18
|
+
- **No alphaXiv account, no alphaXiv search tools.** Without an account, every `alpha_search`, `alpha_get_paper`, `alpha_ask_paper`, and `alpha_read_code` call failed, and research runs retried them. They are now registered only when you are signed in; otherwise papers come from Semantic Scholar, OpenAlex, and the other literature databases. Start a new session after `feynman alpha login`.
|
|
19
|
+
- The Windows install docs now say Feynman needs Bash from Git for Windows for shell commands.
|
|
20
|
+
|
|
9
21
|
## v0.5.8 - 2026-09-26
|
|
10
22
|
|
|
11
23
|
- `FEYNMAN_HOME=~` or `~/path` now means your home folder. Unexpanded `~` (common in `.env` files, Docker `ENV`, and Windows shells) created a literal `~` folder in the current directory.
|
package/dist/pi/settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { BUNDLED_PI_PACKAGES, getFeynmanPackageSources } from "./runtime.js";
|
|
3
|
+
import { BUNDLED_PI_PACKAGES, getFeynmanPackageSources, resolvePackageRoot } from "./runtime.js";
|
|
4
4
|
import { choosePreferredModelRecord, getAvailableModelRecords } from "../model/catalog.js";
|
|
5
5
|
// Written by Feynman <= 0.4.0 next to a subagent extension path it managed.
|
|
6
6
|
const LEGACY_RESEARCHER_EXTENSION_MARKER = "_feynmanResearchToolsExtension";
|
|
@@ -150,6 +150,17 @@ function removeLegacyResearcherExtension(settings) {
|
|
|
150
150
|
else
|
|
151
151
|
delete researcher.subagentOnlyExtensions;
|
|
152
152
|
}
|
|
153
|
+
// Foreground subagents run inside the parent process and never load its
|
|
154
|
+
// extensions, so researcher and verifier lost web and paper search there.
|
|
155
|
+
// pi-subagents loads these paths in every child session; they are rewritten on
|
|
156
|
+
// each launch because the install path changes between versions.
|
|
157
|
+
export function feynmanSubagentExtensions(appRoot) {
|
|
158
|
+
const webAccess = resolvePackageRoot(appRoot, "pi-web-access");
|
|
159
|
+
return [join(appRoot, "extensions", "research-tools.ts"), ...(webAccess ? [webAccess] : [])];
|
|
160
|
+
}
|
|
161
|
+
function isFeynmanSubagentExtension(path) {
|
|
162
|
+
return typeof path === "string" && (/[\\/]extensions[\\/]research-tools\.ts$/.test(path) || /[\\/]pi-web-access$/.test(path));
|
|
163
|
+
}
|
|
153
164
|
export async function ensureFeynmanSettings(settingsPath, bundledSettingsPath, appRoot, defaultThinkingLevel, authPath) {
|
|
154
165
|
// Validate before model discovery or either settings write. Invalid custom
|
|
155
166
|
// configuration must not silently turn into an empty/default configuration.
|
|
@@ -173,6 +184,12 @@ export async function ensureFeynmanSettings(settingsPath, bundledSettingsPath, a
|
|
|
173
184
|
if (isRecord(settings.subagents) && settings.subagents.agentExcludeDirs === undefined) {
|
|
174
185
|
settings.subagents.agentExcludeDirs = ["~/.agents"];
|
|
175
186
|
}
|
|
187
|
+
if (isRecord(settings.subagents)) {
|
|
188
|
+
const current = settings.subagents.defaultSubagentOnlyExtensions;
|
|
189
|
+
if (current === undefined || (Array.isArray(current) && current.every(isFeynmanSubagentExtension))) {
|
|
190
|
+
settings.subagents.defaultSubagentOnlyExtensions = feynmanSubagentExtensions(appRoot);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
176
193
|
if (!settings.defaultProvider || !settings.defaultModel) {
|
|
177
194
|
const preferredModel = choosePreferredModelRecord(await getAvailableModelRecords(authPath));
|
|
178
195
|
if (preferredModel) {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
clearPaperAnnotation,
|
|
5
5
|
disconnect,
|
|
6
6
|
getPaper,
|
|
7
|
+
isLoggedIn,
|
|
7
8
|
listPaperAnnotations,
|
|
8
9
|
readPaperCode,
|
|
9
10
|
searchPapers,
|
|
@@ -41,14 +42,18 @@ const paperSectionsSchema = Type.Unsafe<string[]>({
|
|
|
41
42
|
}),
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
export function registerAlphaTools(pi: ExtensionAPI): void {
|
|
45
|
+
export function registerAlphaTools(pi: ExtensionAPI, signedIn = isLoggedIn()): void {
|
|
45
46
|
// The alphaXiv MCP client keeps a connection open; close it so print and
|
|
46
47
|
// JSON runs can exit once the session ends.
|
|
47
48
|
pi.on("session_shutdown", async () => {
|
|
48
49
|
await disconnect();
|
|
49
50
|
});
|
|
51
|
+
// Search, paper, Q&A, and code tools need an alphaXiv account. Without one,
|
|
52
|
+
// every call failed, so leave them out and let the model use the other
|
|
53
|
+
// paper sources. Annotations are local and stay available.
|
|
54
|
+
const registerAccountTool: ExtensionAPI["registerTool"] = signedIn ? (tool) => pi.registerTool(tool) : () => {};
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
registerAccountTool({
|
|
52
57
|
name: "alpha_search",
|
|
53
58
|
label: "Alpha Search",
|
|
54
59
|
description:
|
|
@@ -65,7 +70,7 @@ export function registerAlphaTools(pi: ExtensionAPI): void {
|
|
|
65
70
|
},
|
|
66
71
|
});
|
|
67
72
|
|
|
68
|
-
|
|
73
|
+
registerAccountTool({
|
|
69
74
|
name: "alpha_get_paper",
|
|
70
75
|
label: "Alpha Get Paper",
|
|
71
76
|
description:
|
|
@@ -96,7 +101,7 @@ export function registerAlphaTools(pi: ExtensionAPI): void {
|
|
|
96
101
|
},
|
|
97
102
|
});
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
registerAccountTool({
|
|
100
105
|
name: "alpha_ask_paper",
|
|
101
106
|
label: "Alpha Ask Paper",
|
|
102
107
|
description: "Ask a targeted question about an arXiv or alphaXiv paper. Uses AI to analyze the PDF and answer. DOI-only papers are not supported; read those with fetch_content on the open-access PDF or Europe PMC full text.",
|
|
@@ -140,7 +145,7 @@ export function registerAlphaTools(pi: ExtensionAPI): void {
|
|
|
140
145
|
},
|
|
141
146
|
});
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
registerAccountTool({
|
|
144
149
|
name: "alpha_read_code",
|
|
145
150
|
label: "Alpha Read Code",
|
|
146
151
|
description: "Read files from a paper's GitHub repository. Use '/' for repo overview.",
|
|
@@ -6,6 +6,8 @@ import { resolve as resolvePath } from "node:path";
|
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
8
|
|
|
9
|
+
import { readPromptSpecs } from "../../metadata/commands.mjs";
|
|
10
|
+
|
|
9
11
|
import {
|
|
10
12
|
APP_ROOT,
|
|
11
13
|
FEYNMAN_AGENT_LOGO,
|
|
@@ -161,9 +163,11 @@ function detectSystemResources(): SystemResources {
|
|
|
161
163
|
|
|
162
164
|
type WorkflowInfo = { name: string; description: string };
|
|
163
165
|
|
|
164
|
-
|
|
166
|
+
// Only Feynman's own workflows; other packages (pi-subagents) also ship prompt commands.
|
|
167
|
+
export function getResearchWorkflows(pi: Pick<ExtensionAPI, "getCommands">, appRoot = APP_ROOT): WorkflowInfo[] {
|
|
168
|
+
const feynmanWorkflows = new Set(readPromptSpecs(appRoot).map((spec) => spec.name));
|
|
165
169
|
return pi.getCommands()
|
|
166
|
-
.filter((cmd) => cmd.source === "prompt")
|
|
170
|
+
.filter((cmd) => cmd.source === "prompt" && feynmanWorkflows.has(cmd.name))
|
|
167
171
|
.map((cmd) => ({ name: `/${cmd.name}`, description: cmd.description ?? "" }))
|
|
168
172
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
169
173
|
}
|
|
@@ -257,12 +257,31 @@ function truncateText(value: string | undefined, maxChars: number): string | und
|
|
|
257
257
|
return `${value.slice(0, maxChars).trimEnd()}…`;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
|
|
260
|
+
// The model reuses the OpenAlex syntax this tool documents (a `semantic:`
|
|
261
|
+
// prefix, year_from=/year_to= flags). Sent as text, it returned junk or zero
|
|
262
|
+
// results; map it to Semantic Scholar's own year filter instead.
|
|
263
|
+
export function parseSemanticScholarQuery(raw: string): { query: string; year?: string } {
|
|
264
|
+
let from = "";
|
|
265
|
+
let to = "";
|
|
266
|
+
const query = raw
|
|
267
|
+
.replace(/^\s*semantic:\s*/i, "")
|
|
268
|
+
.replace(/\byear_(from|to)=(\d{4})\b/gi, (_match, kind: string, year: string) => {
|
|
269
|
+
if (kind.toLowerCase() === "from") from = year;
|
|
270
|
+
else to = year;
|
|
271
|
+
return " ";
|
|
272
|
+
})
|
|
273
|
+
.replace(/\s+/g, " ")
|
|
274
|
+
.trim();
|
|
275
|
+
return from || to ? { query, year: `${from}-${to}` } : { query };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function semanticScholarSearchUrl(query: string, limit: number, sort: string, year?: string): URL {
|
|
261
279
|
const relevance = sort === "relevance";
|
|
262
280
|
const url = new URL(`${SEMANTIC_SCHOLAR_BASE}/paper/search${relevance ? "" : "/bulk"}`);
|
|
263
281
|
url.search = new URLSearchParams({
|
|
264
282
|
query,
|
|
265
283
|
fields: SEMANTIC_SCHOLAR_FIELDS,
|
|
284
|
+
...(year ? { year } : {}),
|
|
266
285
|
...(relevance ? { limit: String(limit) } : { sort }),
|
|
267
286
|
}).toString();
|
|
268
287
|
return url;
|
|
@@ -271,10 +290,10 @@ function semanticScholarSearchUrl(query: string, limit: number, sort: string): U
|
|
|
271
290
|
// Bulk search sorted by citation count surfaces seminal papers that relevance
|
|
272
291
|
// ranking misses; it returns up to 1,000 rows, so only the top `limit` are kept.
|
|
273
292
|
async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
|
|
274
|
-
const query = cleanQuery(params.query);
|
|
293
|
+
const { query, year } = parseSemanticScholarQuery(cleanQuery(params.query));
|
|
275
294
|
const limit = safeLimit(params.limit);
|
|
276
295
|
let sort = params.sort === "relevance" ? "relevance" : params.sort === "pub_date" ? "publicationDate:desc" : "citationCount:desc";
|
|
277
|
-
const endpoints = [semanticScholarSearchUrl(query, limit, sort)];
|
|
296
|
+
const endpoints = [semanticScholarSearchUrl(query, limit, sort, year)];
|
|
278
297
|
let note: string | undefined;
|
|
279
298
|
let payload: Record<string, unknown>;
|
|
280
299
|
try {
|
|
@@ -284,7 +303,7 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
|
|
|
284
303
|
const anonymous = !process.env.SEMANTIC_SCHOLAR_API_KEY?.trim();
|
|
285
304
|
if (sort !== "relevance" || !anonymous || !(error instanceof ScienceDatabaseRequestError) || error.status !== 429) throw error;
|
|
286
305
|
sort = "citationCount:desc";
|
|
287
|
-
endpoints.push(semanticScholarSearchUrl(query, limit, sort));
|
|
306
|
+
endpoints.push(semanticScholarSearchUrl(query, limit, sort, year));
|
|
288
307
|
note = `Relevance search was rate-limited (HTTP 429) on Semantic Scholar's shared anonymous pool, so these are citation-sorted bulk results. A free SEMANTIC_SCHOLAR_API_KEY avoids this: ${SEMANTIC_SCHOLAR_KEY_URL}`;
|
|
289
308
|
payload = recordValue(await fetchSemanticScholar(endpoints[1]!));
|
|
290
309
|
}
|
|
@@ -314,6 +333,7 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
|
|
|
314
333
|
schema: "feynman.scienceDatabaseSearch.v1",
|
|
315
334
|
source: "semanticscholar",
|
|
316
335
|
query,
|
|
336
|
+
...(year ? { year } : {}),
|
|
317
337
|
sort,
|
|
318
338
|
...(note ? { note } : {}),
|
|
319
339
|
totalCount: numberValue(payload.total) ?? results.length,
|