@absolutejs/ai 0.2.7 → 0.2.8

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.
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/ai/research.ts"],
4
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"
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) =>\n value.normalize(\"NFKC\").toLowerCase().replace(/\\s+/gu, \" \").trim();\n const requiredPhrases =\n input.requiredPhrases ??\n [...input.query.matchAll(/\"([^\"\\n]+)\"/gu)].map((match) => match[1]!);\n const sources = [...byUrl.values()].filter((source) =>\n requiredPhrases.every((phrase) =>\n normalize(`${source.title} ${source.excerpts.join(\" \")}`).includes(\n normalize(phrase),\n ),\n ),\n );\n if (sources.length < byUrl.size)\n limitations.push(\n \"Sources that did not establish the required literal entity were excluded; similarly named entities are not substitutes.\",\n );\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
6
  ],
7
- "mappings": "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",
7
+ "mappings": "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",
8
8
  "debugId": "0CBE93DA058E304664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,5 @@
1
1
  import type { ModelMetadata } from "./index";
2
- /** Reviewed provider support, independent of application feature availability. */
2
+ /** Reviewed provider support, independent of application feature availability.
3
+ * Anthropic entries come from the Models API (`max_input_tokens`, `capabilities`). */
3
4
  export declare const MODEL_METADATA: Readonly<Record<string, ModelMetadata>>;
4
5
  export declare const getModelMetadata: (provider: string, id: string) => ModelMetadata | undefined;
@@ -0,0 +1,38 @@
1
+ import { modelCapabilities } from "./index";
2
+ /** Display names for the providers the directory covers. */
3
+ export declare const MODEL_PROVIDERS: Readonly<Record<string, string>>;
4
+ export type ModelDirectoryEntry = {
5
+ provider: string;
6
+ /** The exact model ID sent to the provider. */
7
+ id: string;
8
+ name: string;
9
+ /** One short, plain sentence for pickers. */
10
+ description: string;
11
+ /** A current flagship worth showing first. */
12
+ featured?: boolean;
13
+ /** Superseded by a newer model in the same line; hide by default. */
14
+ legacy?: boolean;
15
+ };
16
+ /**
17
+ * Human-facing model directory for pickers, newest first within each provider.
18
+ * Capabilities and context windows come from MODEL_METADATA, not from here.
19
+ * Anthropic entries were checked against the Models API on 2026-09-25.
20
+ */
21
+ export declare const MODEL_DIRECTORY: readonly ModelDirectoryEntry[];
22
+ export declare const modelKey: (model: {
23
+ provider: string;
24
+ id: string;
25
+ }) => string;
26
+ export type ListedModel = ModelDirectoryEntry & {
27
+ key: string;
28
+ providerName: string;
29
+ capabilities: ReturnType<typeof modelCapabilities>;
30
+ contextWindow: number | null;
31
+ reasoningEfforts: readonly string[];
32
+ };
33
+ /** Directory entries joined with reviewed metadata, ready for a picker. */
34
+ export declare const listModels: (options?: {
35
+ providers?: readonly string[];
36
+ includeLegacy?: boolean;
37
+ }) => ListedModel[];
38
+ export declare const findModel: (provider: string, id: string) => ModelDirectoryEntry | undefined;
@@ -30,3 +30,4 @@ export type ModelCapability = keyof typeof MODEL_CAPABILITY_DESCRIPTIONS;
30
30
  export declare const modelCapabilities: (metadata?: ModelMetadata) => ModelCapability[];
31
31
  export { MODEL_METADATA, getModelMetadata } from "./catalog";
32
32
  export { MODEL_REASONING, REASONING_EFFORTS, getModelReasoning, isReasoningSelection, supportsReasoningSelection, reasoningSelectionLabel, type ModelReasoning, type ReasoningSelection, } from "./reasoning";
33
+ export { MODEL_DIRECTORY, MODEL_PROVIDERS, findModel, listModels, modelKey, type ListedModel, type ModelDirectoryEntry, } from "./directory";
@@ -8,7 +8,8 @@ export type ModelReasoning = {
8
8
  source: string;
9
9
  };
10
10
  export declare const isReasoningSelection: (value: unknown) => value is ReasoningSelection;
11
- /** Exact reviewed IDs; unknown models expose only the provider default. Reviewed 2026-09-20. */
11
+ /** Exact reviewed IDs; unknown models expose only the provider default. Reviewed 2026-09-20;
12
+ * Anthropic effort levels re-checked against the Models API 2026-09-25. */
12
13
  export declare const MODEL_REASONING: Readonly<Record<string, ModelReasoning>>;
13
14
  export declare const getModelReasoning: (provider: string, model: string) => ModelReasoning | undefined;
14
15
  export declare const supportsReasoningSelection: (provider: string, model: string, selection: ReasoningSelection) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/ai",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "homepage": "https://github.com/absolutejs/ai",
5
5
  "bugs": {
6
6
  "url": "https://github.com/absolutejs/ai/issues"