@sourcepress/ai 0.1.0

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.
Files changed (147) hide show
  1. package/.omc/state/last-tool-error.json +7 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.turbo/turbo-test.log +24 -0
  4. package/LICENSE +21 -0
  5. package/dist/__tests__/budget.test.d.ts +2 -0
  6. package/dist/__tests__/budget.test.d.ts.map +1 -0
  7. package/dist/__tests__/budget.test.js +96 -0
  8. package/dist/__tests__/budget.test.js.map +1 -0
  9. package/dist/__tests__/classify.test.d.ts +2 -0
  10. package/dist/__tests__/classify.test.d.ts.map +1 -0
  11. package/dist/__tests__/classify.test.js +72 -0
  12. package/dist/__tests__/classify.test.js.map +1 -0
  13. package/dist/__tests__/eval-runner.test.d.ts +2 -0
  14. package/dist/__tests__/eval-runner.test.d.ts.map +1 -0
  15. package/dist/__tests__/eval-runner.test.js +171 -0
  16. package/dist/__tests__/eval-runner.test.js.map +1 -0
  17. package/dist/__tests__/extract.test.d.ts +2 -0
  18. package/dist/__tests__/extract.test.d.ts.map +1 -0
  19. package/dist/__tests__/extract.test.js +79 -0
  20. package/dist/__tests__/extract.test.js.map +1 -0
  21. package/dist/__tests__/find-gaps.test.d.ts +2 -0
  22. package/dist/__tests__/find-gaps.test.d.ts.map +1 -0
  23. package/dist/__tests__/find-gaps.test.js +82 -0
  24. package/dist/__tests__/find-gaps.test.js.map +1 -0
  25. package/dist/__tests__/generate.test.d.ts +2 -0
  26. package/dist/__tests__/generate.test.d.ts.map +1 -0
  27. package/dist/__tests__/generate.test.js +68 -0
  28. package/dist/__tests__/generate.test.js.map +1 -0
  29. package/dist/__tests__/improve-prompt.test.d.ts +2 -0
  30. package/dist/__tests__/improve-prompt.test.d.ts.map +1 -0
  31. package/dist/__tests__/improve-prompt.test.js +32 -0
  32. package/dist/__tests__/improve-prompt.test.js.map +1 -0
  33. package/dist/__tests__/intent-impact.test.d.ts +2 -0
  34. package/dist/__tests__/intent-impact.test.d.ts.map +1 -0
  35. package/dist/__tests__/intent-impact.test.js +51 -0
  36. package/dist/__tests__/intent-impact.test.js.map +1 -0
  37. package/dist/__tests__/judge.test.d.ts +2 -0
  38. package/dist/__tests__/judge.test.d.ts.map +1 -0
  39. package/dist/__tests__/judge.test.js +61 -0
  40. package/dist/__tests__/judge.test.js.map +1 -0
  41. package/dist/__tests__/score.test.d.ts +2 -0
  42. package/dist/__tests__/score.test.d.ts.map +1 -0
  43. package/dist/__tests__/score.test.js +50 -0
  44. package/dist/__tests__/score.test.js.map +1 -0
  45. package/dist/__tests__/staleness.test.d.ts +2 -0
  46. package/dist/__tests__/staleness.test.d.ts.map +1 -0
  47. package/dist/__tests__/staleness.test.js +66 -0
  48. package/dist/__tests__/staleness.test.js.map +1 -0
  49. package/dist/budget.d.ts +13 -0
  50. package/dist/budget.d.ts.map +1 -0
  51. package/dist/budget.js +40 -0
  52. package/dist/budget.js.map +1 -0
  53. package/dist/eval/runner.d.ts +34 -0
  54. package/dist/eval/runner.d.ts.map +1 -0
  55. package/dist/eval/runner.js +128 -0
  56. package/dist/eval/runner.js.map +1 -0
  57. package/dist/functions/classify.d.ts +5 -0
  58. package/dist/functions/classify.d.ts.map +1 -0
  59. package/dist/functions/classify.js +43 -0
  60. package/dist/functions/classify.js.map +1 -0
  61. package/dist/functions/extract.d.ts +5 -0
  62. package/dist/functions/extract.d.ts.map +1 -0
  63. package/dist/functions/extract.js +57 -0
  64. package/dist/functions/extract.js.map +1 -0
  65. package/dist/functions/find-gaps.d.ts +5 -0
  66. package/dist/functions/find-gaps.d.ts.map +1 -0
  67. package/dist/functions/find-gaps.js +51 -0
  68. package/dist/functions/find-gaps.js.map +1 -0
  69. package/dist/functions/generate.d.ts +5 -0
  70. package/dist/functions/generate.d.ts.map +1 -0
  71. package/dist/functions/generate.js +39 -0
  72. package/dist/functions/generate.js.map +1 -0
  73. package/dist/functions/improve-prompt.d.ts +5 -0
  74. package/dist/functions/improve-prompt.d.ts.map +1 -0
  75. package/dist/functions/improve-prompt.js +38 -0
  76. package/dist/functions/improve-prompt.js.map +1 -0
  77. package/dist/functions/index.d.ts +11 -0
  78. package/dist/functions/index.d.ts.map +1 -0
  79. package/dist/functions/index.js +11 -0
  80. package/dist/functions/index.js.map +1 -0
  81. package/dist/functions/intent-impact.d.ts +5 -0
  82. package/dist/functions/intent-impact.d.ts.map +1 -0
  83. package/dist/functions/intent-impact.js +45 -0
  84. package/dist/functions/intent-impact.js.map +1 -0
  85. package/dist/functions/judge.d.ts +5 -0
  86. package/dist/functions/judge.d.ts.map +1 -0
  87. package/dist/functions/judge.js +32 -0
  88. package/dist/functions/judge.js.map +1 -0
  89. package/dist/functions/model-factory.d.ts +4 -0
  90. package/dist/functions/model-factory.d.ts.map +1 -0
  91. package/dist/functions/model-factory.js +52 -0
  92. package/dist/functions/model-factory.js.map +1 -0
  93. package/dist/functions/score.d.ts +5 -0
  94. package/dist/functions/score.d.ts.map +1 -0
  95. package/dist/functions/score.js +47 -0
  96. package/dist/functions/score.js.map +1 -0
  97. package/dist/functions/staleness.d.ts +5 -0
  98. package/dist/functions/staleness.d.ts.map +1 -0
  99. package/dist/functions/staleness.js +45 -0
  100. package/dist/functions/staleness.js.map +1 -0
  101. package/dist/functions/usage.d.ts +8 -0
  102. package/dist/functions/usage.d.ts.map +1 -0
  103. package/dist/functions/usage.js +13 -0
  104. package/dist/functions/usage.js.map +1 -0
  105. package/dist/index.d.ts +8 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +6 -0
  108. package/dist/index.js.map +1 -0
  109. package/dist/provider.d.ts +10 -0
  110. package/dist/provider.d.ts.map +1 -0
  111. package/dist/provider.js +32 -0
  112. package/dist/provider.js.map +1 -0
  113. package/dist/types.d.ts +207 -0
  114. package/dist/types.d.ts.map +1 -0
  115. package/dist/types.js +2 -0
  116. package/dist/types.js.map +1 -0
  117. package/package.json +41 -0
  118. package/src/__tests__/budget.test.ts +103 -0
  119. package/src/__tests__/classify.test.ts +90 -0
  120. package/src/__tests__/eval-runner.test.ts +199 -0
  121. package/src/__tests__/extract.test.ts +92 -0
  122. package/src/__tests__/find-gaps.test.ts +93 -0
  123. package/src/__tests__/generate.test.ts +92 -0
  124. package/src/__tests__/improve-prompt.test.ts +42 -0
  125. package/src/__tests__/intent-impact.test.ts +62 -0
  126. package/src/__tests__/judge.test.ts +78 -0
  127. package/src/__tests__/score.test.ts +61 -0
  128. package/src/__tests__/staleness.test.ts +77 -0
  129. package/src/budget.ts +47 -0
  130. package/src/eval/runner.ts +163 -0
  131. package/src/functions/classify.ts +54 -0
  132. package/src/functions/extract.ts +72 -0
  133. package/src/functions/find-gaps.ts +65 -0
  134. package/src/functions/generate.ts +51 -0
  135. package/src/functions/improve-prompt.ts +48 -0
  136. package/src/functions/index.ts +10 -0
  137. package/src/functions/intent-impact.ts +56 -0
  138. package/src/functions/judge.ts +41 -0
  139. package/src/functions/model-factory.ts +60 -0
  140. package/src/functions/score.ts +56 -0
  141. package/src/functions/staleness.ts +54 -0
  142. package/src/functions/usage.ts +25 -0
  143. package/src/index.ts +47 -0
  144. package/src/provider.ts +41 -0
  145. package/src/types.ts +225 -0
  146. package/tsconfig.json +5 -0
  147. package/vitest.config.ts +2 -0
@@ -0,0 +1,65 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { FindGapsInput, FindGapsResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const findGapsSchema = z.object({
10
+ gaps: z.array(
11
+ z.object({
12
+ entity_name: z.string(),
13
+ entity_type: z.string(),
14
+ suggested_content_type: z.string(),
15
+ priority: z.enum(["high", "medium", "low"]),
16
+ reason: z.string(),
17
+ }),
18
+ ),
19
+ });
20
+
21
+ export async function findGaps(
22
+ input: FindGapsInput,
23
+ provider: ResolvedProvider,
24
+ budget: BudgetTracker,
25
+ ): Promise<FindGapsResult> {
26
+ const entityList = input.entities.map((e) => `- ${e.name} (${e.type})`).join("\n");
27
+ const relationList =
28
+ input.relations.length > 0
29
+ ? input.relations.map((r) => `- ${r.from} --[${r.type}]--> ${r.to}`).join("\n")
30
+ : "No relations yet.";
31
+ const contentList =
32
+ input.existing_content.length > 0
33
+ ? input.existing_content.map((c) => `- ${c.path}: "${c.title}" — ${c.summary}`).join("\n")
34
+ : "No content yet.";
35
+ const businessContext = input.business_context
36
+ ? `\nBusiness context:\n${input.business_context}\n`
37
+ : "";
38
+
39
+ const { object, usage } = await generateObject({
40
+ model: await createModel(provider),
41
+ schema: findGapsSchema,
42
+ prompt: `You are a content gap analyzer for a content engine. Identify entities from the knowledge graph that should have dedicated content pages but currently don't.
43
+
44
+ Knowledge graph entities:
45
+ ${entityList}
46
+
47
+ Relations:
48
+ ${relationList}
49
+
50
+ Existing content:
51
+ ${contentList}
52
+ ${businessContext}
53
+
54
+ For each gap, determine:
55
+ - Which entity lacks content
56
+ - What type of content would best serve it (blog-post, case-study, service-page, landing-page, documentation)
57
+ - Priority: "high" (core business entity, many relations), "medium" (useful but not critical), "low" (nice to have)
58
+ - Why this gap matters
59
+
60
+ Only report genuine gaps.`,
61
+ });
62
+
63
+ const tokenUsage = recordUsage(budget, provider, usage, "findGaps");
64
+ return { gaps: object.gaps, usage: tokenUsage };
65
+ }
@@ -0,0 +1,51 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { GenerateInput, GenerateResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const generateSchema = z.object({
10
+ frontmatter: z.record(z.unknown()),
11
+ body: z.string(),
12
+ });
13
+
14
+ export async function generate(
15
+ input: GenerateInput,
16
+ provider: ResolvedProvider,
17
+ budget: BudgetTracker,
18
+ ): Promise<GenerateResult> {
19
+ const intentSection = input.intent
20
+ ? `\nIntent/tone-of-voice rules:\n---\n${input.intent}\n---\n`
21
+ : "";
22
+
23
+ const schemaSection = input.collection_schema
24
+ ? `\nExpected frontmatter fields:\n${JSON.stringify(input.collection_schema, null, 2)}\n`
25
+ : "";
26
+
27
+ const { object, usage } = await generateObject({
28
+ model: await createModel(provider),
29
+ schema: generateSchema,
30
+ prompt: `${input.generation_prompt}
31
+
32
+ You are generating content of type "${input.content_type}" for a content engine. Use ONLY the knowledge context provided below. Do not invent facts.
33
+
34
+ Knowledge context:
35
+ ---
36
+ ${input.knowledge_context}
37
+ ---
38
+ ${intentSection}${schemaSection}
39
+ Return a JSON object with:
40
+ - "frontmatter": an object with metadata fields (title, description, etc.)
41
+ - "body": the full content body in markdown format`,
42
+ });
43
+
44
+ const tokenUsage = recordUsage(budget, provider, usage, "generate");
45
+
46
+ return {
47
+ content: object.body,
48
+ frontmatter: object.frontmatter,
49
+ usage: tokenUsage,
50
+ };
51
+ }
@@ -0,0 +1,48 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { ImprovePromptInput, ImprovePromptResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const improvePromptSchema = z.object({
10
+ improved_prompt: z.string(),
11
+ changes_summary: z.string(),
12
+ });
13
+
14
+ export async function improvePrompt(
15
+ input: ImprovePromptInput,
16
+ provider: ResolvedProvider,
17
+ budget: BudgetTracker,
18
+ ): Promise<ImprovePromptResult> {
19
+ const { object, usage } = await generateObject({
20
+ model: await createModel(provider),
21
+ schema: improvePromptSchema,
22
+ prompt: `You are a prompt engineer improving a content generation prompt. The current prompt was used to generate "${input.content_type}" content, but the judge gave it a score of ${input.judge_score}/100.
23
+
24
+ Current generation prompt:
25
+ ---
26
+ ${input.current_prompt}
27
+ ---
28
+
29
+ Judge's reasoning for the low score:
30
+ ---
31
+ ${input.judge_reasoning}
32
+ ---
33
+
34
+ Improve the generation prompt to address the judge's feedback. Keep the same overall structure but make it more specific about what the judge found lacking. Do NOT change the fundamental approach — just refine the instructions.
35
+
36
+ Return:
37
+ - "improved_prompt": the full improved prompt text
38
+ - "changes_summary": a brief summary of what you changed and why`,
39
+ });
40
+
41
+ const tokenUsage = recordUsage(budget, provider, usage, "improvePrompt");
42
+
43
+ return {
44
+ improved_prompt: object.improved_prompt,
45
+ changes_summary: object.changes_summary,
46
+ usage: tokenUsage,
47
+ };
48
+ }
@@ -0,0 +1,10 @@
1
+ export { classify } from "./classify.js";
2
+ export { extract } from "./extract.js";
3
+ export { score } from "./score.js";
4
+ export { judge } from "./judge.js";
5
+ export { detectStaleness } from "./staleness.js";
6
+ export { analyzeIntentImpact } from "./intent-impact.js";
7
+ export { findGaps } from "./find-gaps.js";
8
+ export { generate } from "./generate.js";
9
+ export { improvePrompt } from "./improve-prompt.js";
10
+ export { recordUsage } from "./usage.js";
@@ -0,0 +1,56 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { IntentImpactInput, IntentImpactResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const intentImpactSchema = z.object({
10
+ affected_pages: z.array(
11
+ z.object({ path: z.string(), impact: z.enum(["high", "medium", "low"]), reason: z.string() }),
12
+ ),
13
+ summary: z.string(),
14
+ });
15
+
16
+ export async function analyzeIntentImpact(
17
+ input: IntentImpactInput,
18
+ provider: ResolvedProvider,
19
+ budget: BudgetTracker,
20
+ ): Promise<IntentImpactResult> {
21
+ const contentList = input.content_pages
22
+ .map((p) => `Page: ${p.path}\n---\n${p.text}\n---`)
23
+ .join("\n\n");
24
+ const { object, usage } = await generateObject({
25
+ model: await createModel(provider),
26
+ schema: intentImpactSchema,
27
+ prompt: `You are an intent impact analyzer for a content engine. When editorial intent rules change, you identify which published content pages are affected.
28
+
29
+ Previous intent rules:
30
+ ---
31
+ ${input.previous_intent}
32
+ ---
33
+
34
+ New intent rules:
35
+ ---
36
+ ${input.changed_intent}
37
+ ---
38
+
39
+ Analyze each content page and determine if it violates the NEW rules (but was fine under the old rules).
40
+
41
+ Impact levels:
42
+ - "high": Directly violates new rules (e.g., uses a now-forbidden word, wrong tone)
43
+ - "medium": Partially misaligned, needs revision
44
+ - "low": Minor adjustment needed
45
+
46
+ Only include pages that are actually affected.
47
+
48
+ Content pages:
49
+ ${contentList}
50
+
51
+ Return the list of affected pages with impact level and reason, plus a brief summary.`,
52
+ });
53
+
54
+ const tokenUsage = recordUsage(budget, provider, usage, "analyzeIntentImpact");
55
+ return { affected_pages: object.affected_pages, summary: object.summary, usage: tokenUsage };
56
+ }
@@ -0,0 +1,41 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { JudgeInput, JudgeResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const judgeSchema = z.object({ score: z.number().min(0).max(100), reasoning: z.string() });
10
+
11
+ export async function judge(
12
+ input: JudgeInput,
13
+ provider: ResolvedProvider,
14
+ budget: BudgetTracker,
15
+ ): Promise<JudgeResult> {
16
+ const intentSection = input.intent
17
+ ? `\nIntent/tone-of-voice rules:\n---\n${input.intent}\n---\n`
18
+ : "";
19
+ const { object, usage } = await generateObject({
20
+ model: await createModel(provider),
21
+ schema: judgeSchema,
22
+ prompt: `${input.judge_prompt}
23
+
24
+ You are evaluating a content draft against a gold standard example. Compare quality, structure, specificity, tone, and accuracy.
25
+
26
+ Gold standard (this is what "perfect" looks like):
27
+ ---
28
+ ${input.gold_standard}
29
+ ---
30
+ ${intentSection}
31
+ Draft to evaluate:
32
+ ---
33
+ ${input.draft}
34
+ ---
35
+
36
+ Return a single score (0-100) and detailed reasoning explaining the score.`,
37
+ });
38
+
39
+ const tokenUsage = recordUsage(budget, provider, usage, "judge");
40
+ return { score: object.score, reasoning: object.reasoning, usage: tokenUsage };
41
+ }
@@ -0,0 +1,60 @@
1
+ import type { LanguageModel } from "ai";
2
+ import type { ResolvedProvider } from "../provider.js";
3
+
4
+ export async function createModel(provider: ResolvedProvider): Promise<LanguageModel> {
5
+ switch (provider.provider) {
6
+ case "anthropic":
7
+ return createAnthropicModel(provider);
8
+ case "openai":
9
+ return createOpenAIModel(provider);
10
+ case "local":
11
+ return createLocalModel(provider);
12
+ default:
13
+ throw new Error(`Unknown provider: ${provider.provider}`);
14
+ }
15
+ }
16
+
17
+ async function createAnthropicModel(provider: ResolvedProvider): Promise<LanguageModel> {
18
+ try {
19
+ const { createAnthropic } = await import("@ai-sdk/anthropic");
20
+ const anthropic = createAnthropic({
21
+ apiKey: provider.apiKey,
22
+ ...(provider.baseUrl && { baseURL: provider.baseUrl }),
23
+ });
24
+ return anthropic(provider.model) as unknown as LanguageModel;
25
+ } catch {
26
+ throw new Error(
27
+ 'Provider "anthropic" requires @ai-sdk/anthropic. Install it with: pnpm add @ai-sdk/anthropic',
28
+ );
29
+ }
30
+ }
31
+
32
+ async function createOpenAIModel(provider: ResolvedProvider): Promise<LanguageModel> {
33
+ try {
34
+ const { createOpenAI } = await import("@ai-sdk/openai");
35
+ const openai = createOpenAI({
36
+ apiKey: provider.apiKey,
37
+ ...(provider.baseUrl && { baseURL: provider.baseUrl }),
38
+ });
39
+ return openai(provider.model) as unknown as LanguageModel;
40
+ } catch {
41
+ throw new Error(
42
+ 'Provider "openai" requires @ai-sdk/openai. Install it with: pnpm add @ai-sdk/openai',
43
+ );
44
+ }
45
+ }
46
+
47
+ async function createLocalModel(provider: ResolvedProvider): Promise<LanguageModel> {
48
+ try {
49
+ const { createOpenAI } = await import("@ai-sdk/openai");
50
+ const local = createOpenAI({
51
+ baseURL: provider.baseUrl ?? "http://localhost:11434/v1",
52
+ apiKey: "local",
53
+ });
54
+ return local(provider.model) as unknown as LanguageModel;
55
+ } catch {
56
+ throw new Error(
57
+ 'Provider "local" requires @ai-sdk/openai (used as compatible client). Install it with: pnpm add @ai-sdk/openai',
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,56 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { ScoreInput, ScoreResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const scoreSchema = z.object({
10
+ score: z.number().min(0).max(100),
11
+ issues: z.array(z.string()),
12
+ strengths: z.array(z.string()),
13
+ });
14
+
15
+ export async function score(
16
+ input: ScoreInput,
17
+ provider: ResolvedProvider,
18
+ budget: BudgetTracker,
19
+ ): Promise<ScoreResult> {
20
+ const collectionContext = input.collection_name
21
+ ? `This content belongs to the "${input.collection_name}" collection.`
22
+ : "";
23
+ const { object, usage } = await generateObject({
24
+ model: await createModel(provider),
25
+ schema: scoreSchema,
26
+ prompt: `You are a content quality evaluator for a content engine. Score the following content against the provided intent rules.
27
+
28
+ Score 0-100 where:
29
+ - 90-100: Excellent — fully matches intent, no issues
30
+ - 70-89: Good — mostly matches, minor issues
31
+ - 50-69: Fair — partially matches, notable issues
32
+ - 0-49: Poor — significant mismatches with intent
33
+
34
+ ${collectionContext}
35
+
36
+ Intent rules:
37
+ ---
38
+ ${input.intent}
39
+ ---
40
+
41
+ Content to evaluate:
42
+ ---
43
+ ${input.content}
44
+ ---
45
+
46
+ Provide a score, list of issues (things that don't match intent), and strengths (things that match well).`,
47
+ });
48
+
49
+ const tokenUsage = recordUsage(budget, provider, usage, "score");
50
+ return {
51
+ score: object.score,
52
+ issues: object.issues,
53
+ strengths: object.strengths,
54
+ usage: tokenUsage,
55
+ };
56
+ }
@@ -0,0 +1,54 @@
1
+ import { generateObject } from "ai";
2
+ import { z } from "zod";
3
+ import type { BudgetTracker } from "../budget.js";
4
+ import type { ResolvedProvider } from "../provider.js";
5
+ import type { StalenessInput, StalenessResult } from "../types.js";
6
+ import { createModel } from "./model-factory.js";
7
+ import { recordUsage } from "./usage.js";
8
+
9
+ const stalenessSchema = z.object({
10
+ stale: z.boolean(),
11
+ reason: z.string(),
12
+ stale_sources: z.array(z.string()),
13
+ confidence: z.number().min(0).max(1),
14
+ });
15
+
16
+ export async function detectStaleness(
17
+ input: StalenessInput,
18
+ provider: ResolvedProvider,
19
+ budget: BudgetTracker,
20
+ ): Promise<StalenessResult> {
21
+ const sourceSections = input.source_texts
22
+ .map((s) => `Source: ${s.path} (updated: ${s.updated_at})\n---\n${s.text}\n---`)
23
+ .join("\n\n");
24
+ const { object, usage } = await generateObject({
25
+ model: await createModel(provider),
26
+ schema: stalenessSchema,
27
+ prompt: `You are a content freshness analyzer. Determine if the published content is stale compared to its source knowledge files.
28
+
29
+ Content is stale if the source files contain NEW INFORMATION that is not reflected in the content. Minor formatting changes or rewording in sources do NOT make content stale.
30
+
31
+ Published content (generated at ${input.content_generated_at}):
32
+ ---
33
+ ${input.content}
34
+ ---
35
+
36
+ Source knowledge files:
37
+ ${sourceSections}
38
+
39
+ Determine:
40
+ 1. Is the content stale? (true/false)
41
+ 2. Why or why not?
42
+ 3. Which specific source files made it stale? (list their paths)
43
+ 4. How confident are you? (0.0-1.0)`,
44
+ });
45
+
46
+ const tokenUsage = recordUsage(budget, provider, usage, "detectStaleness");
47
+ return {
48
+ stale: object.stale,
49
+ reason: object.reason,
50
+ stale_sources: object.stale_sources,
51
+ confidence: object.confidence,
52
+ usage: tokenUsage,
53
+ };
54
+ }
@@ -0,0 +1,25 @@
1
+ import type { BudgetTracker } from "../budget.js";
2
+ import type { ResolvedProvider } from "../provider.js";
3
+ import { estimateCost } from "../provider.js";
4
+ import type { TokenUsage } from "../types.js";
5
+
6
+ export function recordUsage(
7
+ budget: BudgetTracker,
8
+ provider: ResolvedProvider,
9
+ usage: { promptTokens?: number; completionTokens?: number } | undefined,
10
+ functionName: string,
11
+ ): TokenUsage {
12
+ const tokenUsage: TokenUsage = {
13
+ input_tokens: usage?.promptTokens ?? 0,
14
+ output_tokens: usage?.completionTokens ?? 0,
15
+ estimated_cost_usd: estimateCost(
16
+ provider.model,
17
+ usage?.promptTokens ?? 0,
18
+ usage?.completionTokens ?? 0,
19
+ ),
20
+ function_name: functionName,
21
+ timestamp: new Date().toISOString(),
22
+ };
23
+ budget.record(tokenUsage);
24
+ return tokenUsage;
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,47 @@
1
+ export { BudgetTracker } from "./budget.js";
2
+ export { resolveProvider, estimateCost } from "./provider.js";
3
+ export type { ResolvedProvider } from "./provider.js";
4
+ export {
5
+ classify,
6
+ extract,
7
+ score,
8
+ judge,
9
+ detectStaleness,
10
+ analyzeIntentImpact,
11
+ findGaps,
12
+ generate,
13
+ improvePrompt,
14
+ recordUsage,
15
+ } from "./functions/index.js";
16
+ export { createModel } from "./functions/model-factory.js";
17
+ export { EvalRunner } from "./eval/runner.js";
18
+ export type {
19
+ AIProviderConfig,
20
+ BudgetConfig,
21
+ BudgetStatus,
22
+ TokenUsage,
23
+ ClassifyInput,
24
+ ClassifyResult,
25
+ ExtractInput,
26
+ ExtractResult,
27
+ ExtractedEntityResult,
28
+ ExtractedRelationResult,
29
+ ScoreInput,
30
+ ScoreResult,
31
+ JudgeInput,
32
+ JudgeResult,
33
+ StalenessInput,
34
+ StalenessResult,
35
+ IntentImpactInput,
36
+ IntentImpactResult,
37
+ FindGapsInput,
38
+ FindGapsResult,
39
+ IntelligenceProvider,
40
+ GenerateInput,
41
+ GenerateResult,
42
+ ImprovePromptInput,
43
+ ImprovePromptResult,
44
+ EvalResult,
45
+ EvalRunConfig,
46
+ EvalRunResult,
47
+ } from "./types.js";
@@ -0,0 +1,41 @@
1
+ import type { AIProviderConfig } from "./types.js";
2
+
3
+ export interface ResolvedProvider {
4
+ provider: AIProviderConfig["provider"];
5
+ model: string;
6
+ apiKey?: string;
7
+ baseUrl?: string;
8
+ }
9
+
10
+ export function resolveProvider(config: AIProviderConfig): ResolvedProvider {
11
+ return {
12
+ provider: config.provider,
13
+ model: config.model,
14
+ apiKey: config.apiKey,
15
+ baseUrl: config.baseUrl,
16
+ };
17
+ }
18
+
19
+ const COST_TABLE: Record<string, { input: number; output: number }> = {
20
+ "claude-sonnet-4-6": { input: 3.0, output: 15.0 },
21
+ "claude-sonnet-4-5": { input: 3.0, output: 15.0 },
22
+ "claude-haiku-4-5": { input: 0.25, output: 1.25 },
23
+ "claude-opus-4-6": { input: 15.0, output: 75.0 },
24
+ "gpt-4o": { input: 2.5, output: 10.0 },
25
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
26
+ local: { input: 0, output: 0 },
27
+ };
28
+
29
+ export function estimateCost(model: string, inputTokens: number, outputTokens: number): number {
30
+ let costs = COST_TABLE[model];
31
+ if (!costs) {
32
+ for (const [key, value] of Object.entries(COST_TABLE)) {
33
+ if (model.startsWith(key)) {
34
+ costs = value;
35
+ break;
36
+ }
37
+ }
38
+ }
39
+ if (!costs) costs = { input: 3.0, output: 15.0 };
40
+ return (inputTokens / 1_000_000) * costs.input + (outputTokens / 1_000_000) * costs.output;
41
+ }