@absolutejs/ai 0.2.6 → 0.2.7

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.
@@ -0,0 +1,84 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/ai/research.ts
5
+ import {
6
+ boundedQueries,
7
+ evidenceText,
8
+ sourceSupportsQuote
9
+ } from "@absolutejs/search";
10
+ var researchWithEvidence = async (input) => {
11
+ const requested = input.queries ?? boundedQueries(input.query);
12
+ const maxQueries = input.maxQueries ?? 2;
13
+ if (!Number.isInteger(maxQueries) || maxQueries < 1)
14
+ throw new Error("Research requires a positive query budget");
15
+ const searches = [];
16
+ const limitations = [];
17
+ const generatedAt = new Date().toISOString();
18
+ if (requested.length > maxQueries)
19
+ limitations.push("Query budget reached; some requested research was not performed");
20
+ for (const query of requested.slice(0, maxQueries)) {
21
+ input.signal?.throwIfAborted();
22
+ searches.push(await input.provider.search({
23
+ query,
24
+ mode: "context",
25
+ signal: input.signal,
26
+ maxTokens: input.maxTokens ?? 4096,
27
+ freshness: input.freshness
28
+ }));
29
+ }
30
+ input.signal?.throwIfAborted();
31
+ const byUrl = new Map;
32
+ for (const search of searches) {
33
+ limitations.push(...search.limitations);
34
+ for (const source of search.sources) {
35
+ const previous = byUrl.get(source.url);
36
+ byUrl.set(source.url, previous ? {
37
+ ...previous,
38
+ excerpts: [
39
+ ...new Set([...previous.excerpts, ...source.excerpts])
40
+ ]
41
+ } : source);
42
+ }
43
+ }
44
+ const normalize = (value) => value.normalize("NFKC").toLowerCase().replace(/\s+/gu, " ").trim();
45
+ const requiredPhrases = input.requiredPhrases ?? [...input.query.matchAll(/"([^"\n]+)"/gu)].map((match) => match[1]);
46
+ const sources = [...byUrl.values()].filter((source) => requiredPhrases.every((phrase) => normalize(`${source.title} ${source.excerpts.join(" ")}`).includes(normalize(phrase))));
47
+ if (sources.length < byUrl.size)
48
+ limitations.push("Sources that did not establish the required literal entity were excluded; similarly named entities are not substitutes.");
49
+ const complete = searches.length > 0 && searches.every((search) => search.status === "ok" || search.status === "empty") && requested.length <= maxQueries;
50
+ const base = { searches, sources, limitations, generatedAt };
51
+ if (!sources.length)
52
+ return {
53
+ ...base,
54
+ status: complete ? "empty" : "unavailable",
55
+ text: "",
56
+ findings: []
57
+ };
58
+ input.signal?.throwIfAborted();
59
+ const synthesis = await input.synthesize(evidenceText(sources), input.signal);
60
+ const findings = synthesis.findings.filter((finding) => {
61
+ const source = sources.find((source2) => source2.id === finding.sourceId);
62
+ return typeof finding.claim === "string" && Boolean(finding.claim.trim()) && typeof finding.quote === "string" && source && sourceSupportsQuote(source, finding.quote);
63
+ });
64
+ if (findings.length !== synthesis.findings.length)
65
+ limitations.push("Unsupported source references or quotations were excluded");
66
+ limitations.push(...synthesis.limitations ?? []);
67
+ const text = findings.map((finding) => `${finding.claim}
68
+ Evidence: ${finding.quote}
69
+ Source: ${sources.find((source) => source.id === finding.sourceId).url}`).join(`
70
+
71
+ `);
72
+ return {
73
+ ...base,
74
+ status: complete && findings.length > 0 && !limitations.length ? "grounded" : "partial",
75
+ text,
76
+ findings
77
+ };
78
+ };
79
+ export {
80
+ researchWithEvidence
81
+ };
82
+
83
+ //# debugId=0CBE93DA058E304664756E2164756E21
84
+ //# sourceMappingURL=research.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ai/research.ts"],
4
+ "sourcesContent": [
5
+ "import {\n boundedQueries,\n evidenceText,\n sourceSupportsQuote,\n type SearchProvider,\n type SearchResult,\n type SearchSource,\n} from \"@absolutejs/search\";\nexport type ResearchFinding = {\n claim: string;\n sourceId: string;\n quote: string;\n eventDate?: string;\n};\nexport type ResearchEvidence = {\n status: \"grounded\" | \"partial\" | \"empty\" | \"unavailable\";\n text: string;\n findings: ResearchFinding[];\n sources: SearchSource[];\n searches: SearchResult[];\n limitations: string[];\n generatedAt: string;\n};\nexport type ResearchSynthesis = {\n findings: ResearchFinding[];\n limitations?: string[];\n};\n/** Retrieval and synthesis are separate injected capabilities. No hosted search is enabled here. */\nexport const researchWithEvidence = async (input: {\n query: string;\n queries?: string[];\n provider: SearchProvider;\n signal?: AbortSignal;\n maxQueries?: number;\n maxTokens?: number;\n freshness?: string;\n /** Required literal entity phrases; defaults to quoted phrases in the question. */\n requiredPhrases?: string[];\n synthesize: (\n evidence: string,\n signal?: AbortSignal,\n ) => Promise<ResearchSynthesis>;\n}): Promise<ResearchEvidence> => {\n const requested = input.queries ?? boundedQueries(input.query);\n const maxQueries = input.maxQueries ?? 2;\n if (!Number.isInteger(maxQueries) || maxQueries < 1)\n throw new Error(\"Research requires a positive query budget\");\n const searches: SearchResult[] = [];\n const limitations: string[] = [];\n const generatedAt = new Date().toISOString();\n if (requested.length > maxQueries)\n limitations.push(\n \"Query budget reached; some requested research was not performed\",\n );\n for (const query of requested.slice(0, maxQueries)) {\n input.signal?.throwIfAborted();\n searches.push(\n await input.provider.search({\n query,\n mode: \"context\",\n signal: input.signal,\n maxTokens: input.maxTokens ?? 4096,\n freshness: input.freshness,\n }),\n );\n }\n input.signal?.throwIfAborted();\n const byUrl = new Map<string, SearchSource>();\n for (const search of searches) {\n limitations.push(...search.limitations);\n for (const source of search.sources) {\n const previous = byUrl.get(source.url);\n byUrl.set(\n source.url,\n previous\n ? {\n ...previous,\n excerpts: [\n ...new Set([...previous.excerpts, ...source.excerpts]),\n ],\n }\n : source,\n );\n }\n }\n const normalize = (value:string) => value.normalize(\"NFKC\").toLowerCase().replace(/\\s+/gu,\" \").trim();\n const requiredPhrases = input.requiredPhrases ?? [...input.query.matchAll(/\"([^\"\\n]+)\"/gu)].map(match=>match[1]!);\n const sources = [...byUrl.values()].filter(source=>requiredPhrases.every(phrase=>normalize(`${source.title} ${source.excerpts.join(\" \")}`).includes(normalize(phrase))));\n if (sources.length < byUrl.size) limitations.push(\"Sources that did not establish the required literal entity were excluded; similarly named entities are not substitutes.\");\n const complete =\n searches.length > 0 &&\n searches.every(\n (search) => search.status === \"ok\" || search.status === \"empty\",\n ) &&\n requested.length <= maxQueries;\n const base = { searches, sources, limitations, generatedAt };\n if (!sources.length)\n return {\n ...base,\n status: complete ? \"empty\" : \"unavailable\",\n text: \"\",\n findings: [],\n };\n input.signal?.throwIfAborted();\n const synthesis = await input.synthesize(evidenceText(sources), input.signal);\n const findings = synthesis.findings.filter((finding) => {\n const source = sources.find((source) => source.id === finding.sourceId);\n return (\n typeof finding.claim === \"string\" &&\n Boolean(finding.claim.trim()) &&\n typeof finding.quote === \"string\" &&\n source &&\n sourceSupportsQuote(source, finding.quote)\n );\n });\n if (findings.length !== synthesis.findings.length)\n limitations.push(\n \"Unsupported source references or quotations were excluded\",\n );\n limitations.push(...(synthesis.limitations ?? []));\n const text = findings\n .map(\n (finding) =>\n `${finding.claim}\\nEvidence: ${finding.quote}\\nSource: ${sources.find((source) => source.id === finding.sourceId)!.url}`,\n )\n .join(\"\\n\\n\");\n // Quotation/source checks establish provenance, not semantic entailment. Host review remains necessary.\n return {\n ...base,\n status:\n complete && findings.length > 0 && !limitations.length\n ? \"grounded\"\n : \"partial\",\n text,\n findings,\n };\n};\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "0CBE93DA058E304664756E2164756E21",
9
+ "names": []
10
+ }
@@ -6,6 +6,7 @@ export type GenerateAIToolCall = {
6
6
  input: unknown;
7
7
  };
8
8
  export type GenerateAIOptions = {
9
+ maxRetries?: number;
9
10
  contextPolicy?: AIContextPolicy | false;
10
11
  provider: AIProviderConfig;
11
12
  model: string;
@@ -73,6 +74,7 @@ export declare const toProviderTools: (tools: AIToolMap) => AIProviderToolDefini
73
74
  */
74
75
  export declare const generateAIWithTools: (options: GenerateAIWithToolsOptions) => Promise<GenerateAIWithToolsResult>;
75
76
  export type GenerateObjectAIOptions<T> = {
77
+ maxRetries?: number;
76
78
  contextPolicy?: AIContextPolicy | false;
77
79
  provider: AIProviderConfig;
78
80
  model: string;
@@ -0,0 +1,33 @@
1
+ import { type SearchProvider, type SearchResult, type SearchSource } from "@absolutejs/search";
2
+ export type ResearchFinding = {
3
+ claim: string;
4
+ sourceId: string;
5
+ quote: string;
6
+ eventDate?: string;
7
+ };
8
+ export type ResearchEvidence = {
9
+ status: "grounded" | "partial" | "empty" | "unavailable";
10
+ text: string;
11
+ findings: ResearchFinding[];
12
+ sources: SearchSource[];
13
+ searches: SearchResult[];
14
+ limitations: string[];
15
+ generatedAt: string;
16
+ };
17
+ export type ResearchSynthesis = {
18
+ findings: ResearchFinding[];
19
+ limitations?: string[];
20
+ };
21
+ /** Retrieval and synthesis are separate injected capabilities. No hosted search is enabled here. */
22
+ export declare const researchWithEvidence: (input: {
23
+ query: string;
24
+ queries?: string[];
25
+ provider: SearchProvider;
26
+ signal?: AbortSignal;
27
+ maxQueries?: number;
28
+ maxTokens?: number;
29
+ freshness?: string;
30
+ /** Required literal entity phrases; defaults to quoted phrases in the question. */
31
+ requiredPhrases?: string[];
32
+ synthesize: (evidence: string, signal?: AbortSignal) => Promise<ResearchSynthesis>;
33
+ }) => Promise<ResearchEvidence>;
@@ -0,0 +1 @@
1
+ export {};
@@ -204,6 +204,8 @@ export type ReasoningConfig = {
204
204
  effort?: ReasoningEffort;
205
205
  };
206
206
  export type AIProviderStreamParams = {
207
+ /** Override transport retries for operations whose host owns attempt accounting. */
208
+ maxRetries?: number;
207
209
  /**
208
210
  * Mark the system prompt as cacheable (Anthropic prompt caching). The system
209
211
  * block is sent with `cache_control: ephemeral`, so repeated calls reusing the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/ai",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "homepage": "https://github.com/absolutejs/ai",
5
5
  "bugs": {
6
6
  "url": "https://github.com/absolutejs/ai/issues"
@@ -92,11 +92,16 @@
92
92
  "./models": {
93
93
  "import": "./dist/ai/models/index.js",
94
94
  "types": "./dist/src/ai/models/index.d.ts"
95
+ },
96
+ "./research": {
97
+ "types": "./dist/src/ai/research.d.ts",
98
+ "import": "./dist/ai/research.js"
95
99
  }
96
100
  },
97
101
  "dependencies": {
98
102
  "@absolutejs/linked-providers": "0.0.2",
99
- "@openrouter/sdk": "^1.2.47"
103
+ "@openrouter/sdk": "^1.2.47",
104
+ "@absolutejs/search": "^0.1.0"
100
105
  },
101
106
  "peerDependencies": {
102
107
  "@absolutejs/isolated-jsc": ">= 0.4.0",