@cogenta/agents 0.2.1 → 0.5.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 (242) hide show
  1. package/dist/agents/builtins.d.ts +51 -0
  2. package/dist/agents/builtins.d.ts.map +1 -0
  3. package/dist/agents/builtins.js +190 -0
  4. package/dist/agents/builtins.js.map +1 -0
  5. package/dist/agents/orchestrator.d.ts +98 -0
  6. package/dist/agents/orchestrator.d.ts.map +1 -0
  7. package/dist/agents/orchestrator.js +286 -0
  8. package/dist/agents/orchestrator.js.map +1 -0
  9. package/dist/agents/store.d.ts +71 -0
  10. package/dist/agents/store.d.ts.map +1 -0
  11. package/dist/agents/store.js +211 -0
  12. package/dist/agents/store.js.map +1 -0
  13. package/dist/agents/types.d.ts +10 -0
  14. package/dist/agents/types.d.ts.map +1 -1
  15. package/dist/assist/agent-identity.d.ts +44 -0
  16. package/dist/assist/agent-identity.d.ts.map +1 -0
  17. package/dist/assist/agent-identity.js +136 -0
  18. package/dist/assist/agent-identity.js.map +1 -0
  19. package/dist/assist/chat.d.ts +2 -0
  20. package/dist/assist/chat.d.ts.map +1 -1
  21. package/dist/assist/chat.js +14 -5
  22. package/dist/assist/chat.js.map +1 -1
  23. package/dist/assist/classify.d.ts +3 -2
  24. package/dist/assist/classify.d.ts.map +1 -1
  25. package/dist/assist/classify.js +30 -11
  26. package/dist/assist/classify.js.map +1 -1
  27. package/dist/assist/faq.d.ts +3 -2
  28. package/dist/assist/faq.d.ts.map +1 -1
  29. package/dist/assist/faq.js +31 -13
  30. package/dist/assist/faq.js.map +1 -1
  31. package/dist/assist/runtime.d.ts +8 -2
  32. package/dist/assist/runtime.d.ts.map +1 -1
  33. package/dist/assist/runtime.js +2 -2
  34. package/dist/assist/runtime.js.map +1 -1
  35. package/dist/assist/toolset.d.ts +10 -0
  36. package/dist/assist/toolset.d.ts.map +1 -1
  37. package/dist/assist/toolset.js +36 -8
  38. package/dist/assist/toolset.js.map +1 -1
  39. package/dist/assist/usage.d.ts +58 -0
  40. package/dist/assist/usage.d.ts.map +1 -0
  41. package/dist/assist/usage.js +53 -0
  42. package/dist/assist/usage.js.map +1 -0
  43. package/dist/assist/writing.d.ts +10 -9
  44. package/dist/assist/writing.d.ts.map +1 -1
  45. package/dist/assist/writing.js +126 -58
  46. package/dist/assist/writing.js.map +1 -1
  47. package/dist/audit/with-audit.d.ts +13 -0
  48. package/dist/audit/with-audit.d.ts.map +1 -1
  49. package/dist/audit/with-audit.js +6 -0
  50. package/dist/audit/with-audit.js.map +1 -1
  51. package/dist/autonomy/levels.d.ts +37 -0
  52. package/dist/autonomy/levels.d.ts.map +1 -0
  53. package/dist/autonomy/levels.js +51 -0
  54. package/dist/autonomy/levels.js.map +1 -0
  55. package/dist/conversations/store.d.ts +62 -0
  56. package/dist/conversations/store.d.ts.map +1 -0
  57. package/dist/conversations/store.js +0 -0
  58. package/dist/conversations/store.js.map +1 -0
  59. package/dist/identity/context.d.ts +8 -0
  60. package/dist/identity/context.d.ts.map +1 -1
  61. package/dist/identity/context.js +19 -4
  62. package/dist/identity/context.js.map +1 -1
  63. package/dist/identity/markdown.d.ts +38 -0
  64. package/dist/identity/markdown.d.ts.map +1 -0
  65. package/dist/identity/markdown.js +85 -0
  66. package/dist/identity/markdown.js.map +1 -0
  67. package/dist/index.d.ts +62 -5
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +33 -3
  70. package/dist/index.js.map +1 -1
  71. package/dist/progress/job-store.d.ts +41 -0
  72. package/dist/progress/job-store.d.ts.map +1 -0
  73. package/dist/progress/job-store.js +44 -0
  74. package/dist/progress/job-store.js.map +1 -0
  75. package/dist/progress/types.d.ts +21 -0
  76. package/dist/progress/types.d.ts.map +1 -0
  77. package/dist/progress/types.js +3 -0
  78. package/dist/progress/types.js.map +1 -0
  79. package/dist/prompts/render.d.ts +23 -0
  80. package/dist/prompts/render.d.ts.map +1 -0
  81. package/dist/prompts/render.js +35 -0
  82. package/dist/prompts/render.js.map +1 -0
  83. package/dist/prompts/seeds.d.ts +34 -0
  84. package/dist/prompts/seeds.d.ts.map +1 -0
  85. package/dist/prompts/seeds.js +166 -0
  86. package/dist/prompts/seeds.js.map +1 -0
  87. package/dist/prompts/store.d.ts +8 -0
  88. package/dist/prompts/store.d.ts.map +1 -0
  89. package/dist/prompts/store.js +153 -0
  90. package/dist/prompts/store.js.map +1 -0
  91. package/dist/prompts/types.d.ts +51 -0
  92. package/dist/prompts/types.d.ts.map +1 -0
  93. package/dist/prompts/types.js +24 -0
  94. package/dist/prompts/types.js.map +1 -0
  95. package/dist/providers/anthropic.d.ts +26 -3
  96. package/dist/providers/anthropic.d.ts.map +1 -1
  97. package/dist/providers/anthropic.js +60 -12
  98. package/dist/providers/anthropic.js.map +1 -1
  99. package/dist/providers/catalog.d.ts +41 -0
  100. package/dist/providers/catalog.d.ts.map +1 -0
  101. package/dist/providers/catalog.js +85 -0
  102. package/dist/providers/catalog.js.map +1 -0
  103. package/dist/providers/content-parts.d.ts +12 -0
  104. package/dist/providers/content-parts.d.ts.map +1 -0
  105. package/dist/providers/content-parts.js +18 -0
  106. package/dist/providers/content-parts.js.map +1 -0
  107. package/dist/providers/defaults.d.ts +44 -0
  108. package/dist/providers/defaults.d.ts.map +1 -0
  109. package/dist/providers/defaults.js +72 -0
  110. package/dist/providers/defaults.js.map +1 -0
  111. package/dist/providers/google.d.ts +20 -3
  112. package/dist/providers/google.d.ts.map +1 -1
  113. package/dist/providers/google.js +53 -12
  114. package/dist/providers/google.js.map +1 -1
  115. package/dist/providers/openai.d.ts +36 -4
  116. package/dist/providers/openai.d.ts.map +1 -1
  117. package/dist/providers/openai.js +113 -21
  118. package/dist/providers/openai.js.map +1 -1
  119. package/dist/providers/registry.d.ts +38 -21
  120. package/dist/providers/registry.d.ts.map +1 -1
  121. package/dist/providers/registry.js +64 -8
  122. package/dist/providers/registry.js.map +1 -1
  123. package/dist/providers/request-signal.d.ts +18 -0
  124. package/dist/providers/request-signal.d.ts.map +1 -0
  125. package/dist/providers/request-signal.js +21 -0
  126. package/dist/providers/request-signal.js.map +1 -0
  127. package/dist/providers/resolve.d.ts +13 -0
  128. package/dist/providers/resolve.d.ts.map +1 -0
  129. package/dist/providers/resolve.js +30 -0
  130. package/dist/providers/resolve.js.map +1 -0
  131. package/dist/providers/store.d.ts +70 -0
  132. package/dist/providers/store.d.ts.map +1 -0
  133. package/dist/providers/store.js +308 -0
  134. package/dist/providers/store.js.map +1 -0
  135. package/dist/providers/tool-names.d.ts +9 -0
  136. package/dist/providers/tool-names.d.ts.map +1 -0
  137. package/dist/providers/tool-names.js +56 -0
  138. package/dist/providers/tool-names.js.map +1 -0
  139. package/dist/providers/types.d.ts +87 -3
  140. package/dist/providers/types.d.ts.map +1 -1
  141. package/dist/rag/reference-documents/ingest.d.ts +22 -0
  142. package/dist/rag/reference-documents/ingest.d.ts.map +1 -0
  143. package/dist/rag/reference-documents/ingest.js +69 -0
  144. package/dist/rag/reference-documents/ingest.js.map +1 -0
  145. package/dist/rag/reference-documents/store.d.ts +23 -0
  146. package/dist/rag/reference-documents/store.d.ts.map +1 -0
  147. package/dist/rag/reference-documents/store.js +140 -0
  148. package/dist/rag/reference-documents/store.js.map +1 -0
  149. package/dist/rag/reference-documents/types.d.ts +52 -0
  150. package/dist/rag/reference-documents/types.d.ts.map +1 -0
  151. package/dist/rag/reference-documents/types.js +32 -0
  152. package/dist/rag/reference-documents/types.js.map +1 -0
  153. package/dist/runtime/loop.d.ts +29 -7
  154. package/dist/runtime/loop.d.ts.map +1 -1
  155. package/dist/runtime/loop.js +209 -44
  156. package/dist/runtime/loop.js.map +1 -1
  157. package/dist/runtime/retry.d.ts +2 -0
  158. package/dist/runtime/retry.d.ts.map +1 -1
  159. package/dist/runtime/retry.js +1 -0
  160. package/dist/runtime/retry.js.map +1 -1
  161. package/dist/runtime/types.d.ts +14 -3
  162. package/dist/runtime/types.d.ts.map +1 -1
  163. package/dist/site-plan/analyse-brief.d.ts +8 -0
  164. package/dist/site-plan/analyse-brief.d.ts.map +1 -1
  165. package/dist/site-plan/analyse-brief.js +17 -8
  166. package/dist/site-plan/analyse-brief.js.map +1 -1
  167. package/dist/site-plan/approval.d.ts +2 -2
  168. package/dist/site-plan/approval.d.ts.map +1 -1
  169. package/dist/site-plan/approval.js +34 -4
  170. package/dist/site-plan/approval.js.map +1 -1
  171. package/dist/site-plan/content-model.d.ts +14 -0
  172. package/dist/site-plan/content-model.d.ts.map +1 -1
  173. package/dist/site-plan/content-model.js +51 -9
  174. package/dist/site-plan/content-model.js.map +1 -1
  175. package/dist/site-plan/demo-content.d.ts.map +1 -1
  176. package/dist/site-plan/demo-content.js +1 -4
  177. package/dist/site-plan/demo-content.js.map +1 -1
  178. package/dist/site-plan/propose-plan.d.ts +10 -0
  179. package/dist/site-plan/propose-plan.d.ts.map +1 -1
  180. package/dist/site-plan/propose-plan.js +25 -1
  181. package/dist/site-plan/propose-plan.js.map +1 -1
  182. package/dist/site-plan/site-context.d.ts +81 -0
  183. package/dist/site-plan/site-context.d.ts.map +1 -0
  184. package/dist/site-plan/site-context.js +76 -0
  185. package/dist/site-plan/site-context.js.map +1 -0
  186. package/dist/site-plan/skin-candidates.d.ts +13 -1
  187. package/dist/site-plan/skin-candidates.d.ts.map +1 -1
  188. package/dist/site-plan/skin-candidates.js +15 -0
  189. package/dist/site-plan/skin-candidates.js.map +1 -1
  190. package/dist/site-plan/structural-gaps.d.ts +32 -0
  191. package/dist/site-plan/structural-gaps.d.ts.map +1 -0
  192. package/dist/site-plan/structural-gaps.js +77 -0
  193. package/dist/site-plan/structural-gaps.js.map +1 -0
  194. package/dist/site-plan/types.d.ts +7 -0
  195. package/dist/site-plan/types.d.ts.map +1 -1
  196. package/dist/skills/frontmatter.d.ts +19 -1
  197. package/dist/skills/frontmatter.d.ts.map +1 -1
  198. package/dist/skills/frontmatter.js +29 -5
  199. package/dist/skills/frontmatter.js.map +1 -1
  200. package/dist/skills/library.d.ts +133 -0
  201. package/dist/skills/library.d.ts.map +1 -0
  202. package/dist/skills/library.js +332 -0
  203. package/dist/skills/library.js.map +1 -0
  204. package/dist/skills/types.d.ts +8 -1
  205. package/dist/skills/types.d.ts.map +1 -1
  206. package/dist/skin/generate.d.ts +22 -1
  207. package/dist/skin/generate.d.ts.map +1 -1
  208. package/dist/skin/generate.js +60 -5
  209. package/dist/skin/generate.js.map +1 -1
  210. package/dist/theme-creator/propose-theme.d.ts +83 -0
  211. package/dist/theme-creator/propose-theme.d.ts.map +1 -0
  212. package/dist/theme-creator/propose-theme.js +234 -0
  213. package/dist/theme-creator/propose-theme.js.map +1 -0
  214. package/dist/tools/core/agent-delegate.d.ts +8 -5
  215. package/dist/tools/core/agent-delegate.d.ts.map +1 -1
  216. package/dist/tools/core/agent-delegate.js +16 -6
  217. package/dist/tools/core/agent-delegate.js.map +1 -1
  218. package/dist/tools/core/content-browse.d.ts +74 -0
  219. package/dist/tools/core/content-browse.d.ts.map +1 -0
  220. package/dist/tools/core/content-browse.js +63 -0
  221. package/dist/tools/core/content-browse.js.map +1 -0
  222. package/dist/tools/core/content-schema.d.ts +86 -0
  223. package/dist/tools/core/content-schema.d.ts.map +1 -0
  224. package/dist/tools/core/content-schema.js +83 -0
  225. package/dist/tools/core/content-schema.js.map +1 -0
  226. package/dist/tools/core/deps-scan.d.ts +25 -0
  227. package/dist/tools/core/deps-scan.d.ts.map +1 -0
  228. package/dist/tools/core/deps-scan.js +85 -0
  229. package/dist/tools/core/deps-scan.js.map +1 -0
  230. package/dist/tools/core/media.d.ts +18 -2
  231. package/dist/tools/core/media.d.ts.map +1 -1
  232. package/dist/tools/core/media.js +8 -2
  233. package/dist/tools/core/media.js.map +1 -1
  234. package/dist/tools/core/not-found-log.d.ts +44 -0
  235. package/dist/tools/core/not-found-log.d.ts.map +1 -0
  236. package/dist/tools/core/not-found-log.js +32 -0
  237. package/dist/tools/core/not-found-log.js.map +1 -0
  238. package/dist/tools/core/redirects.d.ts +51 -0
  239. package/dist/tools/core/redirects.d.ts.map +1 -0
  240. package/dist/tools/core/redirects.js +50 -0
  241. package/dist/tools/core/redirects.js.map +1 -0
  242. package/package.json +6 -4
@@ -0,0 +1,308 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
2
+ import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { CogentaError } from '@cogenta/core';
5
+ import { findProviderCatalogEntry } from './catalog.js';
6
+ /**
7
+ * L22 task 1bis's "Providers" screen: which LLM providers this site has
8
+ * configured, each one's API key, and its default model — persisted so an
9
+ * operator can turn a provider on from the admin instead of only via
10
+ * `COGENTA_LLM_API_KEY`/`cogenta.config.mjs`'s single `llm` section
11
+ * (`@cogenta/core`'s `llmSchema`, unchanged and still the install-time
12
+ * default — `agents/orchestrator.ts` imports it as the first configured
13
+ * provider the first time this store is read, so an existing site loses
14
+ * nothing by upgrading).
15
+ *
16
+ * Fiche 56 widened `provider` from the closed 3-literal union to a free
17
+ * string (`registry.ts`'s `ProviderName`) so a catalog id (OpenRouter,
18
+ * DeepSeek, Qwen, GLM, …) or an operator-chosen custom id both fit. `upsert`
19
+ * is the write boundary that keeps this safe: `assertValidProviderId` rejects
20
+ * anything that is not a plain slug (this store builds a filename directly
21
+ * from `provider` — see `fileFor` — so a malformed id is a path-traversal
22
+ * risk, not merely an aesthetic one), and a name absent from the catalog
23
+ * must carry its own `baseUrl` or it could never resolve to a working client
24
+ * (`registry.ts`'s `buildClient` throws `PROVIDER_CUSTOM_BASE_URL_REQUIRED`
25
+ * for exactly that case — this store refuses the same shape earlier, at the
26
+ * point an operator can still fix it, rather than at first agent run).
27
+ *
28
+ * "Jamais affichée en clair une fois enregistrée" (the lot's own words, same
29
+ * discipline as `create-cogenta`'s masked key prompt) rules out storing the
30
+ * key as recoverable plaintext an API response could echo back — but an LLM
31
+ * call still needs the real key at request time, which rules out a one-way
32
+ * hash (`@cogenta/auth`'s `ApiKeyStore` model: fine for a bearer token this
33
+ * site itself verifies, useless for a key a *vendor* must see). This store
34
+ * therefore encrypts at rest (AES-256-GCM, `node:crypto`, zero new
35
+ * dependency — R9/R10) with a key derived from `COGENTA_AUTH_SIGNING_KEY`
36
+ * (the secret every real deployment already has, R7 — no second secret to
37
+ * generate, rotate or lose) via `scryptSync` with a purpose-specific salt,
38
+ * so the derived key is never the literal signing key reused across
39
+ * purposes. `list()`/`get()` return only a masked preview; the plaintext key
40
+ * is decrypted solely inside `resolveProviderRegistryConfig`, which the
41
+ * runtime hands straight to `createProviderRegistry` and never logs or
42
+ * returns over the wire (R7).
43
+ */
44
+ /**
45
+ * A safe filename component and a reasonable admin-typed identifier: lower-
46
+ * case slug, 2-64 characters, no leading/trailing/doubled hyphen. Every
47
+ * catalog id in `catalog.ts` matches this by construction; the check exists
48
+ * for an operator-typed custom provider id.
49
+ */
50
+ const PROVIDER_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+){0,10}$/u;
51
+ function assertValidProviderId(provider) {
52
+ if (PROVIDER_ID_PATTERN.test(provider) && provider.length <= 64)
53
+ return;
54
+ throw new CogentaError({
55
+ code: 'PROVIDER_ID_INVALID',
56
+ message: `"${provider}" is not a valid provider id.`,
57
+ hint: 'Use lowercase letters, digits and single hyphens only (e.g. "openrouter", "my-vllm-server").',
58
+ });
59
+ }
60
+ /** A provider id the catalog does not know needs its own `baseUrl` — otherwise it could never resolve to a working client (see `registry.ts`'s `buildClient`). */
61
+ function assertResolvable(provider, baseUrl) {
62
+ if (findProviderCatalogEntry(provider) !== undefined)
63
+ return;
64
+ if (baseUrl !== undefined && baseUrl.trim().length > 0)
65
+ return;
66
+ throw new CogentaError({
67
+ code: 'PROVIDER_CUSTOM_BASE_URL_REQUIRED',
68
+ message: `"${provider}" is not a built-in provider — a custom provider needs a non-empty "baseUrl".`,
69
+ hint: 'Add a baseUrl pointing at an OpenAI-compatible chat completions endpoint, or use a catalog provider id.',
70
+ });
71
+ }
72
+ /** Bounds wide enough for any real model/deployment, narrow enough to catch a typo (a negative, a zero, or a value nobody would deliberately set) before it reaches an HTTP request. */
73
+ const TUNING_BOUNDS = {
74
+ maxOutputTokens: { min: 1, max: 200_000 },
75
+ requestTimeoutMs: { min: 1000, max: 600_000 },
76
+ maxCorrectionAttempts: { min: 1, max: 10 },
77
+ };
78
+ function assertValidTuning(input) {
79
+ for (const [field, bounds] of Object.entries(TUNING_BOUNDS)) {
80
+ const value = input[field];
81
+ if (value === undefined)
82
+ continue;
83
+ if (!Number.isInteger(value) || value < bounds.min || value > bounds.max) {
84
+ throw new CogentaError({
85
+ code: 'PROVIDER_TUNING_INVALID',
86
+ message: `"${field}" must be a whole number between ${bounds.min} and ${bounds.max}.`,
87
+ hint: 'Leave it empty to use the built-in default for this field.',
88
+ });
89
+ }
90
+ }
91
+ }
92
+ const KEY_DERIVATION_SALT = 'cogenta-provider-secrets-v1';
93
+ const ALGORITHM = 'aes-256-gcm';
94
+ function deriveKey(signingKey) {
95
+ return scryptSync(signingKey, KEY_DERIVATION_SALT, 32);
96
+ }
97
+ function mask(apiKey) {
98
+ const tail = apiKey.slice(-4);
99
+ return tail.length === 0 ? '••••' : `••••${tail}`;
100
+ }
101
+ function providerNotConfigured(provider) {
102
+ return new CogentaError({
103
+ code: 'PROVIDER_NOT_CONFIGURED',
104
+ message: `No API key is saved for "${provider}".`,
105
+ hint: 'Configure it from the admin\'s "Providers" screen first.',
106
+ });
107
+ }
108
+ function toSummary(record) {
109
+ return {
110
+ provider: record.provider,
111
+ enabled: record.enabled,
112
+ model: record.model,
113
+ ...(record.baseUrl === undefined ? {} : { baseUrl: record.baseUrl }),
114
+ maskedKey: record.maskedKey,
115
+ updatedAt: record.updatedAt,
116
+ ...(record.maxOutputTokens === undefined ? {} : { maxOutputTokens: record.maxOutputTokens }),
117
+ ...(record.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: record.requestTimeoutMs }),
118
+ ...(record.maxCorrectionAttempts === undefined
119
+ ? {}
120
+ : { maxCorrectionAttempts: record.maxCorrectionAttempts }),
121
+ };
122
+ }
123
+ /** One `<provider>.json` record per configured provider, under `options.dir` — the same one-file-per-record shape every other file store in this package uses (R1: no external service). */
124
+ export function createFileProviderConfigStore(options) {
125
+ const now = options.now ?? (() => new Date());
126
+ const key = deriveKey(options.signingKey);
127
+ const ready = mkdir(options.dir, { recursive: true });
128
+ /**
129
+ * The one place every method that touches disk builds a path from
130
+ * `provider` — validating here, rather than only in `upsert`, is what
131
+ * closes the path-traversal gap `PATCH`/`DELETE /api/providers/:provider`
132
+ * would otherwise reopen: the router no longer allowlists `provider`
133
+ * against a fixed name list before calling `setEnabled`/`updateSettings`/
134
+ * `remove` (fiche 56 removed that gate to admit catalog and custom ids),
135
+ * so this store is the only remaining checkpoint before an attacker-
136
+ * controlled string like `../../agents/some-agent` reaches `readFile`/
137
+ * `writeFile`/`rm`. A caller of `get`/`decryptKey` on a malformed id gets
138
+ * the same `PROVIDER_ID_INVALID` an `upsert` would — never a silent
139
+ * "not found" that would let the check be bypassed by relying on ENOENT.
140
+ */
141
+ function fileFor(provider) {
142
+ assertValidProviderId(provider);
143
+ return join(options.dir, `${provider}.json`);
144
+ }
145
+ function encrypt(plaintext) {
146
+ const iv = randomBytes(12);
147
+ const cipher = createCipheriv(ALGORITHM, key, iv);
148
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
149
+ return {
150
+ iv: iv.toString('base64'),
151
+ authTag: cipher.getAuthTag().toString('base64'),
152
+ ciphertext: ciphertext.toString('base64'),
153
+ };
154
+ }
155
+ function decrypt(record) {
156
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(record.iv, 'base64'));
157
+ decipher.setAuthTag(Buffer.from(record.authTag, 'base64'));
158
+ const plaintext = Buffer.concat([
159
+ decipher.update(Buffer.from(record.ciphertext, 'base64')),
160
+ decipher.final(),
161
+ ]);
162
+ return plaintext.toString('utf8');
163
+ }
164
+ async function readRecord(provider) {
165
+ // `fileFor` (which validates `provider`) is called outside the try
166
+ // block on purpose: a `PROVIDER_ID_INVALID` it throws must propagate as
167
+ // itself, never be caught below and rewritten into a misleading
168
+ // "the file may be corrupted" `INTERNAL` error.
169
+ const path = fileFor(provider);
170
+ try {
171
+ const raw = await readFile(path, 'utf8');
172
+ return JSON.parse(raw);
173
+ }
174
+ catch (error) {
175
+ if (error.code === 'ENOENT')
176
+ return null;
177
+ throw new CogentaError({
178
+ code: 'INTERNAL',
179
+ message: `Could not read the saved configuration for provider "${provider}".`,
180
+ hint: 'The file may be corrupted; consider removing it and reconfiguring the provider.',
181
+ cause: error,
182
+ });
183
+ }
184
+ }
185
+ return {
186
+ async list() {
187
+ await ready;
188
+ const filenames = await readdir(options.dir).catch(() => []);
189
+ const records = [];
190
+ for (const filename of filenames) {
191
+ if (!filename.endsWith('.json'))
192
+ continue;
193
+ const provider = filename.replace(/\.json$/u, '');
194
+ if (!PROVIDER_ID_PATTERN.test(provider))
195
+ continue;
196
+ const record = await readRecord(provider);
197
+ if (record !== null)
198
+ records.push(toSummary(record));
199
+ }
200
+ return records.sort((a, b) => a.provider.localeCompare(b.provider));
201
+ },
202
+ async get(provider) {
203
+ await ready;
204
+ const record = await readRecord(provider);
205
+ return record === null ? undefined : toSummary(record);
206
+ },
207
+ async upsert(input) {
208
+ await ready;
209
+ assertValidProviderId(input.provider);
210
+ assertResolvable(input.provider, input.baseUrl);
211
+ assertValidTuning(input);
212
+ const { iv, authTag, ciphertext } = encrypt(input.apiKey);
213
+ const record = {
214
+ provider: input.provider,
215
+ enabled: input.enabled ?? true,
216
+ model: input.model,
217
+ ...(input.baseUrl === undefined ? {} : { baseUrl: input.baseUrl }),
218
+ maskedKey: mask(input.apiKey),
219
+ iv,
220
+ authTag,
221
+ ciphertext,
222
+ updatedAt: now().toISOString(),
223
+ ...(input.maxOutputTokens === undefined ? {} : { maxOutputTokens: input.maxOutputTokens }),
224
+ ...(input.requestTimeoutMs === undefined
225
+ ? {}
226
+ : { requestTimeoutMs: input.requestTimeoutMs }),
227
+ ...(input.maxCorrectionAttempts === undefined
228
+ ? {}
229
+ : { maxCorrectionAttempts: input.maxCorrectionAttempts }),
230
+ };
231
+ await writeFile(fileFor(input.provider), JSON.stringify(record, null, 2), 'utf8');
232
+ return toSummary(record);
233
+ },
234
+ async setEnabled(provider, enabled) {
235
+ await ready;
236
+ const existing = await readRecord(provider);
237
+ if (existing === null)
238
+ throw providerNotConfigured(provider);
239
+ const updated = { ...existing, enabled, updatedAt: now().toISOString() };
240
+ await writeFile(fileFor(provider), JSON.stringify(updated, null, 2), 'utf8');
241
+ return toSummary(updated);
242
+ },
243
+ async remove(provider) {
244
+ await ready;
245
+ await rm(fileFor(provider), { force: true });
246
+ },
247
+ async updateSettings(provider, patch) {
248
+ await ready;
249
+ const existing = await readRecord(provider);
250
+ if (existing === null)
251
+ throw providerNotConfigured(provider);
252
+ const nextBaseUrl = patch.baseUrl !== undefined
253
+ ? patch.baseUrl
254
+ : existing.baseUrl === undefined
255
+ ? undefined
256
+ : existing.baseUrl;
257
+ assertResolvable(provider, nextBaseUrl);
258
+ assertValidTuning({
259
+ ...(patch.maxOutputTokens === undefined || patch.maxOutputTokens === null
260
+ ? {}
261
+ : { maxOutputTokens: patch.maxOutputTokens }),
262
+ ...(patch.requestTimeoutMs === undefined || patch.requestTimeoutMs === null
263
+ ? {}
264
+ : { requestTimeoutMs: patch.requestTimeoutMs }),
265
+ ...(patch.maxCorrectionAttempts === undefined || patch.maxCorrectionAttempts === null
266
+ ? {}
267
+ : { maxCorrectionAttempts: patch.maxCorrectionAttempts }),
268
+ });
269
+ // `null` clears a tuning field back to "use the built-in default";
270
+ // `undefined` leaves it exactly as saved — the reason this can't just
271
+ // be `patch.x ?? existing.x` the way `model` above is. Starting from
272
+ // `existing` stripped of all three (rather than the full record) and
273
+ // re-adding only what survives is what lets a `null` actually delete
274
+ // the key instead of merely being overwritten back to its old value.
275
+ const { maxOutputTokens: _existingMaxOutputTokens, requestTimeoutMs: _existingRequestTimeoutMs, maxCorrectionAttempts: _existingMaxCorrectionAttempts, ...existingWithoutTuning } = existing;
276
+ const nextMaxOutputTokens = patch.maxOutputTokens === undefined
277
+ ? existing.maxOutputTokens
278
+ : (patch.maxOutputTokens ?? undefined);
279
+ const nextRequestTimeoutMs = patch.requestTimeoutMs === undefined
280
+ ? existing.requestTimeoutMs
281
+ : (patch.requestTimeoutMs ?? undefined);
282
+ const nextMaxCorrectionAttempts = patch.maxCorrectionAttempts === undefined
283
+ ? existing.maxCorrectionAttempts
284
+ : (patch.maxCorrectionAttempts ?? undefined);
285
+ const updated = {
286
+ ...existingWithoutTuning,
287
+ model: patch.model ?? existing.model,
288
+ ...(nextBaseUrl === undefined ? {} : { baseUrl: nextBaseUrl }),
289
+ updatedAt: now().toISOString(),
290
+ ...(nextMaxOutputTokens === undefined ? {} : { maxOutputTokens: nextMaxOutputTokens }),
291
+ ...(nextRequestTimeoutMs === undefined ? {} : { requestTimeoutMs: nextRequestTimeoutMs }),
292
+ ...(nextMaxCorrectionAttempts === undefined
293
+ ? {}
294
+ : { maxCorrectionAttempts: nextMaxCorrectionAttempts }),
295
+ };
296
+ await writeFile(fileFor(provider), JSON.stringify(updated, null, 2), 'utf8');
297
+ return toSummary(updated);
298
+ },
299
+ async decryptKey(provider) {
300
+ await ready;
301
+ const record = await readRecord(provider);
302
+ if (record === null)
303
+ throw providerNotConfigured(provider);
304
+ return decrypt(record);
305
+ },
306
+ };
307
+ }
308
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/providers/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ChatRequest } from './types.js';
2
+ export declare function encodeToolName(name: string): string;
3
+ /**
4
+ * The decoder for one request. Throws when two distinct tool names would
5
+ * collide on the wire (`a.b` next to `a__b`) — a misconfiguration worth a
6
+ * loud error, never a silently wrong tool.
7
+ */
8
+ export declare function createToolNameDecoder(request: ChatRequest): (wire: string) => string;
9
+ //# sourceMappingURL=tool-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/providers/tool-names.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AA4B7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAmBpF"}
@@ -0,0 +1,56 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ /**
3
+ * Contract C names its tools with a dot (`content.read`, `redirects.create`,
4
+ * `code.propose_patch`); every vendor wire format refuses that character in
5
+ * a function name — OpenAI-compatible endpoints (OpenAI, DeepSeek, Qwen,
6
+ * OpenRouter…) and Anthropic both enforce `^[a-zA-Z0-9_-]+$`, and Gemini
7
+ * has its own narrower rules. Found live: DeepSeek answered every agent run
8
+ * with `400 Invalid 'tools[0].function.name': string does not match
9
+ * pattern` before the request ever reached a model, and nothing in the
10
+ * catalog could have worked with a single tool declared.
11
+ *
12
+ * The fix lives here rather than in the tool names themselves: contract C
13
+ * is frozen, and a dot is the right separator for humans, the audit log
14
+ * and the permission taxonomy. Each adapter encodes on the way out and
15
+ * decodes on the way back, so no other layer ever sees a wire name.
16
+ *
17
+ * Encoding is deterministic (`.` and anything else outside the safe set →
18
+ * `__`), which is what lets an assistant message from earlier in the
19
+ * conversation — one that echoes a previous tool call by name — be encoded
20
+ * without any state. Decoding needs the reverse map, built from the request
21
+ * itself; a wire name the map does not know (a model inventing a tool) is
22
+ * returned unchanged, and the runtime's own registry refuses it downstream
23
+ * exactly as it would have before.
24
+ */
25
+ const UNSAFE = /[^a-zA-Z0-9_-]/gu;
26
+ export function encodeToolName(name) {
27
+ return name.replace(UNSAFE, '__');
28
+ }
29
+ /**
30
+ * The decoder for one request. Throws when two distinct tool names would
31
+ * collide on the wire (`a.b` next to `a__b`) — a misconfiguration worth a
32
+ * loud error, never a silently wrong tool.
33
+ */
34
+ export function createToolNameDecoder(request) {
35
+ const byWire = new Map();
36
+ const register = (name) => {
37
+ const wire = encodeToolName(name);
38
+ const existing = byWire.get(wire);
39
+ if (existing !== undefined && existing !== name) {
40
+ throw new CogentaError({
41
+ code: 'PROVIDER_REQUEST_FAILED',
42
+ message: `Tool names "${existing}" and "${name}" are indistinguishable once encoded for the provider ("${wire}").`,
43
+ hint: 'Rename one of the two tools so they differ by more than characters the wire format refuses.',
44
+ });
45
+ }
46
+ byWire.set(wire, name);
47
+ };
48
+ for (const tool of request.tools ?? [])
49
+ register(tool.name);
50
+ for (const message of request.messages) {
51
+ for (const call of message.toolCalls ?? [])
52
+ register(call.name);
53
+ }
54
+ return (wire) => byWire.get(wire) ?? wire;
55
+ }
56
+ //# sourceMappingURL=tool-names.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-names.js","sourceRoot":"","sources":["../../src/providers/tool-names.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAG5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,GAAG,kBAAkB,CAAA;AAEjC,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAoB;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAQ,EAAE;QACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YAChD,MAAM,IAAI,YAAY,CAAC;gBACrB,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,eAAe,QAAQ,UAAU,IAAI,2DAA2D,IAAI,KAAK;gBAClH,IAAI,EAAE,6FAA6F;aACpG,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACxB,CAAC,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3D,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,SAAS,IAAI,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;AAC3C,CAAC"}
@@ -13,10 +13,33 @@ export interface ProviderToolCall {
13
13
  readonly name: string;
14
14
  readonly input: Readonly<Record<string, unknown>>;
15
15
  }
16
+ /** A plain-text segment of a multimodal message. */
17
+ export interface ChatTextPart {
18
+ readonly type: 'text';
19
+ readonly text: string;
20
+ }
21
+ /**
22
+ * An inline image segment of a multimodal message. `data` is raw base64 —
23
+ * no `data:` URI prefix — so each adapter can format it however its own
24
+ * wire protocol wants it (a data URL for OpenAI, a bare base64 field for
25
+ * Anthropic and Google).
26
+ */
27
+ export interface ChatImagePart {
28
+ readonly type: 'image';
29
+ readonly mediaType: string;
30
+ readonly data: string;
31
+ }
32
+ export type ChatContentPart = ChatTextPart | ChatImagePart;
16
33
  export interface ChatMessage {
17
34
  readonly role: ChatRole;
18
- /** Absent (not empty-string) when a turn is tool calls with no accompanying text. */
19
- readonly content?: string;
35
+ /**
36
+ * Absent (not empty-string) when a turn is tool calls with no accompanying
37
+ * text. A plain `string` is the common case and every existing caller
38
+ * keeps compiling and behaving identically; an array of `ChatContentPart`
39
+ * lets a caller attach images alongside text (e.g. the Theme Creator
40
+ * agent handing a model a screenshot a user uploaded).
41
+ */
42
+ readonly content?: string | readonly ChatContentPart[];
20
43
  /** Set on an `assistant` message that requested tool calls. */
21
44
  readonly toolCalls?: readonly ProviderToolCall[];
22
45
  /** Set on a `tool` message: which call this is the result of. */
@@ -35,7 +58,14 @@ export interface ChatRequest {
35
58
  readonly system?: string;
36
59
  readonly messages: readonly ChatMessage[];
37
60
  readonly tools?: readonly ProviderToolSpec[];
38
- readonly maxTokens: number;
61
+ /**
62
+ * Absent lets the client fall back to its own configured
63
+ * `maxOutputTokens` (an admin-set property of the provider/model, not a
64
+ * literal a caller should be picking) — see `ProviderClient.maxOutputTokens`.
65
+ * A caller only sets this for a genuine, call-specific reason to differ
66
+ * from that default.
67
+ */
68
+ readonly maxTokens?: number;
39
69
  readonly temperature?: number;
40
70
  }
41
71
  export type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence';
@@ -58,6 +88,60 @@ export interface ChatOptions {
58
88
  export interface ProviderClient {
59
89
  readonly name: string;
60
90
  readonly model: string;
91
+ /**
92
+ * Whether this client can accept a `ChatImagePart` in a message's content.
93
+ * Absent means "unknown, assume no" — a caller building a multimodal
94
+ * message should check this before attaching an image, and degrade
95
+ * (text-only, with an explicit note) when it is not `true`.
96
+ */
97
+ readonly supportsVision?: boolean;
98
+ /**
99
+ * The completion budget every call through this client defaults to when
100
+ * a request does not set its own `maxTokens` — a property of *which
101
+ * model this is*, not of any one call site. A reasoning-tier model can
102
+ * spend thousands of tokens "thinking" before it ever writes a visible
103
+ * answer, and that reasoning counts against this same budget; a plain
104
+ * instruct model needs far less. This is why it lives on the provider
105
+ * (admin-configurable from `/admin/providers`, `resolve.ts` resolves it
106
+ * from `ProviderConfigStore`), not as a constant scattered across every
107
+ * caller — a real, reproduced bug (`docs`/changeset history: DeepSeek's
108
+ * `deepseek-v4-flash` hitting `finish_reason: "length"` with empty
109
+ * content at a too-low hardcoded ceiling) is exactly what a per-call
110
+ * hardcoded number cannot adapt to.
111
+ *
112
+ * **Always a concrete number, never `undefined`** — fiche feedback: this
113
+ * used to be optional, with the site-wide floor a resolved adapter falls
114
+ * back to (when an admin never set a per-provider override) living as a
115
+ * TypeScript constant (`FALLBACK_MAX_OUTPUT_TOKENS`), invisible and
116
+ * unreachable from the admin. That floor is now itself a real site
117
+ * setting (`assistant.defaultMaxOutputTokens`, `SITE_SETTINGS_REGISTRY`
118
+ * in `@cogenta/schema`) a resolved adapter
119
+ * (`createAnthropicClient`/`createOpenAiClient`/`createGoogleClient`)
120
+ * always merges in before ever returning a client — so a caller never
121
+ * needs its own `?? literal`, and the type now says so.
122
+ */
123
+ readonly maxOutputTokens: number;
124
+ /**
125
+ * How many times a generate-validate-correct loop (skin generation, brief
126
+ * analysis, content-model/demo-content proposals, the base-theme choice)
127
+ * retries this client before giving up. Same reasoning as
128
+ * `maxOutputTokens`, site-wide floor included:
129
+ * `assistant.defaultMaxCorrectionAttempts` — always a concrete number.
130
+ */
131
+ readonly maxCorrectionAttempts: number;
132
+ /**
133
+ * How long a single HTTP call to this provider is allowed to run before
134
+ * being aborted — both the raw request timeout each adapter applies via
135
+ * `requestSignalWithTimeout`, and the per-model-call timeout the LangGraph
136
+ * agent loop (`runtime/loop.ts`) enforces around `chat()`. A slower
137
+ * (often reasoning-tier) model genuinely needs more wall-clock time, not
138
+ * just more tokens; a fixed timeout picked without knowing which model an
139
+ * admin configured is the same class of mistake as a fixed token budget.
140
+ * Site-wide floor: `assistant.defaultRequestTimeoutSeconds` — always a
141
+ * concrete number, in milliseconds here (the setting itself is seconds,
142
+ * matching what the admin form shows).
143
+ */
144
+ readonly requestTimeoutMs: number;
61
145
  chat(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;
62
146
  }
63
147
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAA;AAEpD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAChD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,oJAAoJ;AACpJ,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACxD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,eAAe,CAAA;AAEjF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC/C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,yHAAyH;IACzH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED,wHAAwH;AACxH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACzE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { ExtractedDocument } from '../../documents/extract-text.js';
2
+ import type { EmbeddingProvider } from '../embeddings/types.js';
3
+ import type { VectorStore } from '../vector/types.js';
4
+ export interface ReferenceDocumentIngestOptions {
5
+ readonly store: VectorStore;
6
+ readonly embeddings: EmbeddingProvider;
7
+ readonly siteId: string;
8
+ }
9
+ /**
10
+ * Chunks, embeds and stores one uploaded reference document under the
11
+ * reserved pseudo-collection (`types.ts`). `documentId` is the
12
+ * `ReferenceDocumentStore` row's own id — reusing it as the vector
13
+ * `entryId` is what lets `removeReferenceDocument` and a re-upload address
14
+ * exactly the chunks this document produced, the same way a content entry's
15
+ * id already does for `withVectorIndexing`.
16
+ */
17
+ export declare function ingestReferenceDocument(document: Pick<ExtractedDocument, 'filename' | 'text'>, documentId: string, options: ReferenceDocumentIngestOptions): Promise<{
18
+ readonly chunkCount: number;
19
+ }>;
20
+ /** Removes every chunk a document produced — an upload's own undo, and what a delete from the admin screen calls. */
21
+ export declare function removeReferenceDocumentVectors(store: VectorStore, siteId: string, documentId: string): Promise<void>;
22
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/rag/reference-documents/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AAGxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA4BnE,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC,EACtD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B1C;AAED,qHAAqH;AACrH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,69 @@
1
+ import { chunkDocument } from '../chunking/chunk-document.js';
2
+ import { REFERENCE_DOCUMENT_COLLECTION, REFERENCE_DOCUMENT_LOCALE, REFERENCE_DOCUMENT_STATUS, } from './types.js';
3
+ /**
4
+ * The one real user of `chunkDocument` (L18 task 15) against an actual
5
+ * multi-chunk document — `withVectorIndexing`'s own `recordFor` deliberately
6
+ * uses one chunk per entry instead (see that file's comment: real chunking
7
+ * "is a piece of work of its own"). A reference document has no blocks to
8
+ * read, only the flat string `extractDocumentText` already produced, so this
9
+ * is that piece of work, scoped to exactly what an uploaded document needs:
10
+ * paragraphs standing in for blocks, a blank line standing in for the
11
+ * boundary between them, and nothing claiming to be a heading (a plain-text
12
+ * or a PDF extraction has no reliable way to tell one from a paragraph, so
13
+ * claiming otherwise would just fabricate section boundaries `chunkDocument`
14
+ * would then treat as if a human had marked them).
15
+ */
16
+ function paragraphsOf(text) {
17
+ return text
18
+ .split(/\n{2,}/u)
19
+ .map((paragraph) => paragraph.trim())
20
+ .filter((paragraph) => paragraph.length > 0)
21
+ .map((paragraph, index) => ({ id: String(index), text: paragraph }));
22
+ }
23
+ /**
24
+ * Chunks, embeds and stores one uploaded reference document under the
25
+ * reserved pseudo-collection (`types.ts`). `documentId` is the
26
+ * `ReferenceDocumentStore` row's own id — reusing it as the vector
27
+ * `entryId` is what lets `removeReferenceDocument` and a re-upload address
28
+ * exactly the chunks this document produced, the same way a content entry's
29
+ * id already does for `withVectorIndexing`.
30
+ */
31
+ export async function ingestReferenceDocument(document, documentId, options) {
32
+ const blocks = paragraphsOf(document.text);
33
+ if (blocks.length === 0)
34
+ return { chunkCount: 0 };
35
+ const chunks = chunkDocument({
36
+ documentId,
37
+ title: document.filename,
38
+ blocks,
39
+ });
40
+ if (chunks.length === 0)
41
+ return { chunkCount: 0 };
42
+ const vectors = await options.embeddings.embed(chunks.map((chunk) => chunk.text));
43
+ const records = [];
44
+ chunks.forEach((chunk, index) => {
45
+ const vector = vectors[index];
46
+ if (vector === undefined)
47
+ return;
48
+ records.push({
49
+ siteId: options.siteId,
50
+ collection: REFERENCE_DOCUMENT_COLLECTION,
51
+ entryId: documentId,
52
+ locale: REFERENCE_DOCUMENT_LOCALE,
53
+ status: REFERENCE_DOCUMENT_STATUS,
54
+ chunk,
55
+ vector,
56
+ });
57
+ });
58
+ await options.store.upsert(records);
59
+ return { chunkCount: records.length };
60
+ }
61
+ /** Removes every chunk a document produced — an upload's own undo, and what a delete from the admin screen calls. */
62
+ export async function removeReferenceDocumentVectors(store, siteId, documentId) {
63
+ await store.removeEntries({
64
+ siteId,
65
+ collection: REFERENCE_DOCUMENT_COLLECTION,
66
+ entryIds: [documentId],
67
+ });
68
+ }
69
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/rag/reference-documents/ingest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAI7D,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;GAYG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI;SACR,KAAK,CAAC,SAAS,CAAC;SAChB,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACpC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3C,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;AACxE,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,QAAsD,EACtD,UAAkB,EAClB,OAAuC;IAEvC,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAEjD,MAAM,MAAM,GAAG,aAAa,CAAC;QAC3B,UAAU;QACV,KAAK,EAAE,QAAQ,CAAC,QAAQ;QACxB,MAAM;KACP,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAEjF,MAAM,OAAO,GAAmB,EAAE,CAAA;IAClC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QAChC,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE,6BAA6B;YACzC,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,yBAAyB;YACjC,MAAM,EAAE,yBAAyB;YACjC,KAAK;YACL,MAAM;SACP,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;AACvC,CAAC;AAED,qHAAqH;AACrH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,KAAkB,EAClB,MAAc,EACd,UAAkB;IAElB,MAAM,KAAK,CAAC,aAAa,CAAC;QACxB,MAAM;QACN,UAAU,EAAE,6BAA6B;QACzC,QAAQ,EAAE,CAAC,UAAU,CAAC;KACvB,CAAC,CAAA;AACJ,CAAC"}