@companion-ai/feynman 0.5.17 → 0.5.18
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.
|
@@ -31,7 +31,7 @@ You are Feynman's evidence-gathering subagent.
|
|
|
31
31
|
| Biomedical papers | `source: "pubmed"`, then `source: "europepmc"` for open-access full-text sections | `semanticscholar` for citation counts |
|
|
32
32
|
| Citation graph | `source: "openalex"` with `openalex_citations:` / `openalex_references:` | `semanticscholar` citation counts to spot seminal work |
|
|
33
33
|
| Conceptual or recent work that keyword search misses | `source: "openalex"` with a `semantic:` query prefix | `semanticscholar` with `sort: "relevance"` |
|
|
34
|
-
| Web, docs, repos, grey literature | `web_search` (if the default provider fails or is rate-limited, retry with `provider
|
|
34
|
+
| Web, docs, repos, grey literature | `web_search` (if the default provider fails or is rate-limited, retry with `provider` set to the plain string `parallel-mcp`, not a JSON-encoded list; it needs no key) | `fetch_content` on the best results |
|
|
35
35
|
| Known paper ID | `source: "arxiv"` for arXiv IDs, `source: "crossref"` for DOIs | `fetch_content` on `arxiv.org/html/<id>` for full text |
|
|
36
36
|
|
|
37
37
|
Run 2–4 reworded queries for each question (synonyms, the method's name, the problem's name) and merge the results. Do not trust one query's ranking; seminal papers often appear only under one phrasing or one sort order.
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,12 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.18 - 2026-09-30
|
|
10
|
+
|
|
11
|
+
- **A rate-limited paper index no longer ends a research step.** When Semantic Scholar is rate-limited or down, a search is answered from OpenAlex, and an OpenAlex topic search that hits OpenAlex's anonymous rate limit or an outage is answered from Semantic Scholar. The result says first which index answered and why. Lookups by OpenAlex ID, DOI, author, venue, or citation still report the error, since the other index has no equivalent.
|
|
12
|
+
- **alphaXiv tools answer when alphaXiv cannot.** If your alphaXiv login expired or alphaXiv does not respond, `alpha_search` returns OpenAlex semantic search results and `alpha_get_paper` returns the arXiv metadata and abstract for an arXiv ID, each marked as a fallback. Run `feynman alpha login` to get alphaXiv's reports back.
|
|
13
|
+
- pi-web-access 0.35.0: OpenAI web search works when you signed in with ChatGPT (`feynman model login openai`), and Brave search keeps to its rate limits. The researcher is told to pass a search provider as a plain string, since some models sent `provider: "[\"parallel-mcp\"]"`, which `web_search` rejects; nicobailon/pi-web-access#491 makes `web_search` accept that form too.
|
|
14
|
+
|
|
9
15
|
## v0.5.17 - 2026-09-30
|
|
10
16
|
|
|
11
17
|
- **`feynman model login openai` works again.** Since Pi 0.99, Sign in with ChatGPT needs a stable ID for the installation, which Feynman did not pass, so the login failed at once with "requires a device ID (UUID)". Feynman now shares the `deviceId` Pi's own `/login` keeps in `~/.feynman/agent/settings.json`.
|
|
@@ -13,6 +13,8 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
13
13
|
import { Type } from "typebox";
|
|
14
14
|
|
|
15
15
|
import { extractPaperSections } from "./alpha-sections.js";
|
|
16
|
+
import { searchArxiv } from "./science-database-arxiv.js";
|
|
17
|
+
import { searchOpenAlex } from "./science-database-openalex.js";
|
|
16
18
|
|
|
17
19
|
function formatText(value: unknown): string {
|
|
18
20
|
if (typeof value === "string") return value;
|
|
@@ -21,11 +23,23 @@ function formatText(value: unknown): string {
|
|
|
21
23
|
|
|
22
24
|
// alphaXiv's API has changed without notice before; when a call fails, point
|
|
23
25
|
// the model at the other paper sources instead of leaving it on a broken tool.
|
|
24
|
-
|
|
26
|
+
const ARXIV_ID = /(?:^|arxiv\.org\/(?:abs|pdf)\/|alphaxiv\.org\/(?:abs|overview)\/)(\d{4}\.\d{4,5}(?:v\d+)?|[a-z-]+(?:\.[A-Z]{2})?\/\d{7}(?:v\d+)?)/i;
|
|
27
|
+
|
|
28
|
+
// When alphaXiv does not answer (expired login, outage), answer from another
|
|
29
|
+
// paper source and say so first, instead of failing the research step.
|
|
30
|
+
async function withPaperFallback<T>(
|
|
31
|
+
run: () => Promise<T>,
|
|
32
|
+
fallback?: { source: string; run: () => Promise<Record<string, unknown>> },
|
|
33
|
+
): Promise<T | Record<string, unknown>> {
|
|
25
34
|
try {
|
|
26
35
|
return await run();
|
|
27
36
|
} catch (error) {
|
|
28
37
|
const message = (error instanceof Error ? error.message : String(error)).replace("`alpha login`", "`feynman alpha login`");
|
|
38
|
+
if (fallback) {
|
|
39
|
+
try {
|
|
40
|
+
return { fallbackNote: `alphaXiv did not answer (${message}); these results are from ${fallback.source}.`, ...(await fallback.run()) };
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
29
43
|
throw new Error(
|
|
30
44
|
`${message}\nalphaXiv did not answer this call. Use feynman_science_database_search (source "arxiv" or "semanticscholar") or fetch_content on https://arxiv.org/abs/<id> instead.`,
|
|
31
45
|
);
|
|
@@ -65,7 +79,10 @@ export function registerAlphaTools(pi: ExtensionAPI, signedIn = isLoggedIn()): v
|
|
|
65
79
|
),
|
|
66
80
|
}),
|
|
67
81
|
async execute(_toolCallId, params) {
|
|
68
|
-
const result = await withPaperFallback(() => searchPapers(params.query, params.mode?.trim() || "semantic")
|
|
82
|
+
const result = await withPaperFallback(() => searchPapers(params.query, params.mode?.trim() || "semantic"), {
|
|
83
|
+
source: "OpenAlex semantic search",
|
|
84
|
+
run: () => searchOpenAlex({ source: "openalex", query: `semantic: ${params.query}`, limit: 10 }),
|
|
85
|
+
});
|
|
69
86
|
return { content: [{ type: "text", text: formatText(result) }], details: result };
|
|
70
87
|
},
|
|
71
88
|
});
|
|
@@ -87,7 +104,14 @@ export function registerAlphaTools(pi: ExtensionAPI, signedIn = isLoggedIn()): v
|
|
|
87
104
|
sections: Type.Optional(paperSectionsSchema),
|
|
88
105
|
}),
|
|
89
106
|
async execute(_toolCallId, params) {
|
|
90
|
-
const
|
|
107
|
+
const arxivId = ARXIV_ID.exec(params.paper.trim())?.[1];
|
|
108
|
+
const result = await withPaperFallback(() => getPaper(params.paper, { fullText: params.fullText }), arxivId ? {
|
|
109
|
+
source: "arXiv metadata and abstract (no full text or sections)",
|
|
110
|
+
run: () => searchArxiv({ query: arxivId }),
|
|
111
|
+
} : undefined);
|
|
112
|
+
if ("fallbackNote" in result) {
|
|
113
|
+
return { content: [{ type: "text", text: formatText(result) }], details: result };
|
|
114
|
+
}
|
|
91
115
|
const extracted = extractPaperSections(result.content, params.section, params.sections);
|
|
92
116
|
const filteredResult = extracted.requested.length
|
|
93
117
|
? {
|
|
@@ -380,19 +380,54 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
|
|
|
380
380
|
};
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
// OpenAlex lookups by ID, DOI, author, venue, or citation have no Semantic
|
|
384
|
+
// Scholar equivalent; only plain topic searches fall back.
|
|
385
|
+
const OPENALEX_LOOKUP_QUERY =
|
|
386
|
+
/^(?:openalex_\w+|rate-limit|authors?|author-search|sources?|venues?|citations?|references?|work|detail)(?::|\s|$)|^(?:W\d+|doi:\s*10\.|10\.\S+\/)/i;
|
|
387
|
+
|
|
388
|
+
function isIndexUnavailable(error: unknown): boolean {
|
|
389
|
+
if (error instanceof ScienceDatabaseRequestError) return error.status === 429 || error.status >= 500;
|
|
390
|
+
return error instanceof Error && /^OpenAlex request failed: (?:429|5\d\d)\b/.test(error.message);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// A rate-limited or overloaded index should not end a research step: answer a
|
|
394
|
+
// topic search from the other general index, and say so first in the result.
|
|
395
|
+
async function withIndexFallback(
|
|
396
|
+
run: () => Promise<Record<string, unknown>>,
|
|
397
|
+
fallback: (() => Promise<Record<string, unknown>>) | undefined,
|
|
398
|
+
describe: (reason: string) => string,
|
|
399
|
+
): Promise<Record<string, unknown>> {
|
|
400
|
+
try {
|
|
401
|
+
return await run();
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (!fallback || !isIndexUnavailable(error)) throw error;
|
|
404
|
+
let result: Record<string, unknown>;
|
|
405
|
+
try {
|
|
406
|
+
result = await fallback();
|
|
407
|
+
} catch {
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
return { fallbackNote: describe(error instanceof Error ? error.message : String(error)), ...result };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
383
414
|
async function scienceDatabaseSearch(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
|
|
384
415
|
if (params.source === "arxiv") return searchArxiv(params);
|
|
385
416
|
if (params.source === "biorxiv") return searchPreprints(params, "biorxiv");
|
|
386
417
|
if (params.source === "medrxiv") return searchPreprints(params, "medrxiv");
|
|
387
418
|
if (params.source === "pubmed") return searchPubMed(params);
|
|
388
419
|
if (params.source === "crossref") return searchCrossref(params);
|
|
389
|
-
|
|
420
|
+
const openAlex = () => searchOpenAlex({ limit: params.limit, query: params.query, source: "openalex" });
|
|
421
|
+
const semanticScholar = () => searchSemanticScholar({ ...params, source: "semanticscholar" });
|
|
422
|
+
if (params.source === "semanticscholar") {
|
|
423
|
+
return withIndexFallback(semanticScholar, openAlex, (reason) => `Semantic Scholar was unavailable (${reason}); these results are from OpenAlex.`);
|
|
424
|
+
}
|
|
390
425
|
if (params.source === "openalex") {
|
|
391
|
-
return
|
|
392
|
-
|
|
393
|
-
query:
|
|
394
|
-
|
|
395
|
-
|
|
426
|
+
return withIndexFallback(
|
|
427
|
+
openAlex,
|
|
428
|
+
OPENALEX_LOOKUP_QUERY.test(params.query.trim()) ? undefined : semanticScholar,
|
|
429
|
+
(reason) => `OpenAlex was unavailable (${reason}); these results are from Semantic Scholar.`,
|
|
430
|
+
);
|
|
396
431
|
}
|
|
397
432
|
return searchEuropePmc(params);
|
|
398
433
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@companion-ai/feynman",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.18",
|
|
4
4
|
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"pi-btw": "0.6.1",
|
|
84
84
|
"pi-docparser": "4.0.0",
|
|
85
85
|
"pi-subagents": "0.73.1",
|
|
86
|
-
"pi-web-access": "0.
|
|
86
|
+
"pi-web-access": "0.35.0",
|
|
87
87
|
"posthog-node": "^5.53.0",
|
|
88
88
|
"undici": "^8.11.2"
|
|
89
89
|
},
|