@companion-ai/feynman 0.5.45 → 0.5.46

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/RELEASES.md CHANGED
@@ -6,6 +6,11 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.5.46 - 2026-10-13
10
+
11
+ - **Recent arXiv papers resolve by DOI.** OpenAlex adds new arXiv preprints days late, so looking up an arXiv DOI (`10.48550/arXiv.<id>`) it doesn't have yet failed. It is now looked up on arXiv itself, with a note.
12
+ - When a topic sent to the arXiv source is written in arXiv's own query syntax (`search_query=all:...`, `submittedDate:[...]`), only the search words are passed on to OpenAlex.
13
+
9
14
  ## v0.5.45 - 2026-10-13
10
15
 
11
16
  - **Paper lookups recover instead of failing.**
@@ -159,6 +159,19 @@ function arxivFeedEntries(feed: Record<string, unknown>): Record<string, unknown
159
159
  return entries;
160
160
  }
161
161
 
162
+ // Plain search words from a query written in arXiv's API syntax
163
+ // (search_query=all:..., AND submittedDate:[...], &max_results=...).
164
+ export function arxivTopicText(query: string): string {
165
+ return query
166
+ .replace(/&?\b(?:max_results|start|sortBy|sortOrder)=[^&\s]*/gi, " ")
167
+ .replace(/\b(?:AND|OR|ANDNOT)\s+submittedDate:\[[^\]]*\]/gi, " ")
168
+ .replace(/\bsearch_query[=_:]+/gi, " ")
169
+ .replace(/\b(?:all|ti|abs|au|cat|co|jr):/gi, " ")
170
+ .replace(/[&"()]/g, " ")
171
+ .replace(/\s+/g, " ")
172
+ .trim();
173
+ }
174
+
162
175
  async function getArxivPapers(query: string): Promise<Record<string, unknown>> {
163
176
  const parsed = parseToolKeyValueQuery(stripGetPapersCommand(query));
164
177
  const requested = (parsed.flags.ids ?? parsed.text).split(/[,\s]+/).map((id) => id.trim()).filter(Boolean).slice(0, MAX_ARXIV_EXACT_RESULTS);
@@ -173,7 +186,7 @@ async function getArxivPapers(query: string): Promise<Record<string, unknown>> {
173
186
  if (!ids.length) {
174
187
  throw new ArxivTopicQueryError(
175
188
  `No arXiv IDs found in "${query}". The arxiv source only looks up papers by ID; search topics with source semanticscholar or openalex.`,
176
- parsed.text || stripGetPapersCommand(query),
189
+ arxivTopicText(parsed.text || stripGetPapersCommand(query)),
177
190
  );
178
191
  }
179
192
  const rows: Record<string, unknown>[] = [];
@@ -461,6 +461,22 @@ async function searchArxivWithFallback(params: ScienceDatabaseSearchParams): Pro
461
461
  }
462
462
  }
463
463
 
464
+ // OpenAlex indexes new arXiv preprints days late; arXiv itself answers for its
465
+ // own DOIs (10.48550/arXiv.<id>).
466
+ async function withArxivDoiFallback(
467
+ params: ScienceDatabaseSearchParams,
468
+ run: () => Promise<Record<string, unknown>>,
469
+ ): Promise<Record<string, unknown>> {
470
+ try {
471
+ return await run();
472
+ } catch (error) {
473
+ const arxivId = error instanceof Error ? /^OpenAlex has no work for doi:10\.48550\/arxiv\.(.+)$/i.exec(error.message)?.[1] : undefined;
474
+ if (!arxivId) throw error;
475
+ const result = await searchArxivWithFallback({ ...params, source: "arxiv", query: arxivId });
476
+ return { fallbackNote: `OpenAlex has no record of this arXiv DOI yet, so it was looked up on arXiv (${arxivId}).`, ...result };
477
+ }
478
+ }
479
+
464
480
  async function scienceDatabaseSearch(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
465
481
  if (params.source === "arxiv") return searchArxivWithFallback(params);
466
482
  if (params.source === "biorxiv") return searchPreprints(params, "biorxiv");
@@ -473,11 +489,11 @@ async function scienceDatabaseSearch(params: ScienceDatabaseSearchParams): Promi
473
489
  return withIndexFallback(semanticScholar, openAlex, (reason) => `Semantic Scholar was unavailable (${reason}); these results are from OpenAlex.`);
474
490
  }
475
491
  if (params.source === "openalex") {
476
- return withIndexFallback(
492
+ return withArxivDoiFallback(params, () => withIndexFallback(
477
493
  openAlex,
478
494
  OPENALEX_LOOKUP_QUERY.test(params.query.trim()) ? undefined : semanticScholar,
479
495
  (reason) => `OpenAlex was unavailable (${reason}); these results are from Semantic Scholar.`,
480
- );
496
+ ));
481
497
  }
482
498
  return searchEuropePmc(params);
483
499
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.45",
3
+ "version": "0.5.46",
4
4
  "description": "The open source AI research agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",