@corgelabs/corge 0.0.1 → 1.0.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 (341) hide show
  1. package/dist/ai/index.d.ts +3 -0
  2. package/dist/ai/index.d.ts.map +1 -0
  3. package/dist/ai/index.js +3 -0
  4. package/dist/ai/index.js.map +1 -0
  5. package/dist/ai/schema.d.ts +83 -0
  6. package/dist/ai/schema.d.ts.map +1 -0
  7. package/dist/ai/schema.js +19 -0
  8. package/dist/ai/schema.js.map +1 -0
  9. package/dist/ai/service.d.ts +14 -0
  10. package/dist/ai/service.d.ts.map +1 -0
  11. package/dist/ai/service.js +266 -0
  12. package/dist/ai/service.js.map +1 -0
  13. package/dist/ai/utils.d.ts +10 -0
  14. package/dist/ai/utils.d.ts.map +1 -0
  15. package/dist/ai/utils.js +27 -0
  16. package/dist/ai/utils.js.map +1 -0
  17. package/dist/attachments/index.d.ts +4 -0
  18. package/dist/attachments/index.d.ts.map +1 -0
  19. package/dist/attachments/index.js +4 -0
  20. package/dist/attachments/index.js.map +1 -0
  21. package/dist/attachments/routes.d.ts +75 -0
  22. package/dist/attachments/routes.d.ts.map +1 -0
  23. package/dist/attachments/routes.js +18 -0
  24. package/dist/attachments/routes.js.map +1 -0
  25. package/dist/attachments/schema.d.ts +61 -0
  26. package/dist/attachments/schema.d.ts.map +1 -0
  27. package/dist/attachments/schema.js +30 -0
  28. package/dist/attachments/schema.js.map +1 -0
  29. package/dist/attachments/service.d.ts +27 -0
  30. package/dist/attachments/service.d.ts.map +1 -0
  31. package/dist/attachments/service.js +224 -0
  32. package/dist/attachments/service.js.map +1 -0
  33. package/dist/attachments/utils.d.ts +9 -0
  34. package/dist/attachments/utils.d.ts.map +1 -0
  35. package/dist/attachments/utils.js +80 -0
  36. package/dist/attachments/utils.js.map +1 -0
  37. package/dist/auth/index.d.ts +4 -0
  38. package/dist/auth/index.d.ts.map +1 -0
  39. package/dist/auth/index.js +4 -0
  40. package/dist/auth/index.js.map +1 -0
  41. package/dist/auth/oidc.d.ts +25 -0
  42. package/dist/auth/oidc.d.ts.map +1 -0
  43. package/dist/auth/oidc.js +298 -0
  44. package/dist/auth/oidc.js.map +1 -0
  45. package/dist/auth/recovery.d.ts +41 -0
  46. package/dist/auth/recovery.d.ts.map +1 -0
  47. package/dist/auth/recovery.js +250 -0
  48. package/dist/auth/recovery.js.map +1 -0
  49. package/dist/auth/routes.d.ts +226 -0
  50. package/dist/auth/routes.d.ts.map +1 -0
  51. package/dist/auth/routes.js +96 -0
  52. package/dist/auth/routes.js.map +1 -0
  53. package/dist/auth/schema.d.ts +158 -0
  54. package/dist/auth/schema.d.ts.map +1 -0
  55. package/dist/auth/schema.js +82 -0
  56. package/dist/auth/schema.js.map +1 -0
  57. package/dist/auth/service.d.ts +96 -0
  58. package/dist/auth/service.d.ts.map +1 -0
  59. package/dist/auth/service.js +129 -0
  60. package/dist/auth/service.js.map +1 -0
  61. package/dist/auth/session.d.ts +51 -0
  62. package/dist/auth/session.d.ts.map +1 -0
  63. package/dist/auth/session.js +344 -0
  64. package/dist/auth/session.js.map +1 -0
  65. package/dist/auth/utils.d.ts +17 -0
  66. package/dist/auth/utils.d.ts.map +1 -0
  67. package/dist/auth/utils.js +96 -0
  68. package/dist/auth/utils.js.map +1 -0
  69. package/dist/bin/corge.js +547 -0
  70. package/dist/chat/context.d.ts +10 -0
  71. package/dist/chat/context.d.ts.map +1 -0
  72. package/dist/chat/context.js +221 -0
  73. package/dist/chat/context.js.map +1 -0
  74. package/dist/chat/index.d.ts +4 -0
  75. package/dist/chat/index.d.ts.map +1 -0
  76. package/dist/chat/index.js +4 -0
  77. package/dist/chat/index.js.map +1 -0
  78. package/dist/chat/routes.d.ts +160 -0
  79. package/dist/chat/routes.d.ts.map +1 -0
  80. package/dist/chat/routes.js +210 -0
  81. package/dist/chat/routes.js.map +1 -0
  82. package/dist/chat/schema.d.ts +285 -0
  83. package/dist/chat/schema.d.ts.map +1 -0
  84. package/dist/chat/schema.js +74 -0
  85. package/dist/chat/schema.js.map +1 -0
  86. package/dist/chat/service.d.ts +64 -0
  87. package/dist/chat/service.d.ts.map +1 -0
  88. package/dist/chat/service.js +279 -0
  89. package/dist/chat/service.js.map +1 -0
  90. package/dist/chat/stream.d.ts +53 -0
  91. package/dist/chat/stream.d.ts.map +1 -0
  92. package/dist/chat/stream.js +339 -0
  93. package/dist/chat/stream.js.map +1 -0
  94. package/dist/chat/utils.d.ts +61 -0
  95. package/dist/chat/utils.d.ts.map +1 -0
  96. package/dist/chat/utils.js +285 -0
  97. package/dist/chat/utils.js.map +1 -0
  98. package/dist/cli/builder.d.ts +6 -0
  99. package/dist/cli/builder.d.ts.map +1 -0
  100. package/dist/cli/builder.js +91 -0
  101. package/dist/cli/builder.js.map +1 -0
  102. package/dist/cli/deployer.d.ts +2 -0
  103. package/dist/cli/deployer.d.ts.map +1 -0
  104. package/dist/cli/deployer.js +152 -0
  105. package/dist/cli/deployer.js.map +1 -0
  106. package/dist/cli/dev-server.d.ts +2 -0
  107. package/dist/cli/dev-server.d.ts.map +1 -0
  108. package/dist/cli/dev-server.js +70 -0
  109. package/dist/cli/dev-server.js.map +1 -0
  110. package/dist/cli/env.d.ts +4 -0
  111. package/dist/cli/env.d.ts.map +1 -0
  112. package/dist/cli/env.js +68 -0
  113. package/dist/cli/env.js.map +1 -0
  114. package/dist/cli/init.d.ts +6 -0
  115. package/dist/cli/init.d.ts.map +1 -0
  116. package/dist/cli/init.js +103 -0
  117. package/dist/cli/init.js.map +1 -0
  118. package/dist/config.d.ts +262 -0
  119. package/dist/config.d.ts.map +1 -0
  120. package/dist/config.js +212 -0
  121. package/dist/config.js.map +1 -0
  122. package/dist/db/index.d.ts +3 -0
  123. package/dist/db/index.d.ts.map +1 -0
  124. package/dist/db/index.js +3 -0
  125. package/dist/db/index.js.map +1 -0
  126. package/dist/db/schema.d.ts +410 -0
  127. package/dist/db/schema.d.ts.map +1 -0
  128. package/dist/db/schema.js +294 -0
  129. package/dist/db/schema.js.map +1 -0
  130. package/dist/db/service.d.ts +42 -0
  131. package/dist/db/service.d.ts.map +1 -0
  132. package/dist/db/service.js +372 -0
  133. package/dist/db/service.js.map +1 -0
  134. package/dist/db/utils.d.ts +25 -0
  135. package/dist/db/utils.d.ts.map +1 -0
  136. package/dist/db/utils.js +188 -0
  137. package/dist/db/utils.js.map +1 -0
  138. package/dist/emails/index.d.ts +4 -0
  139. package/dist/emails/index.d.ts.map +1 -0
  140. package/dist/emails/index.js +4 -0
  141. package/dist/emails/index.js.map +1 -0
  142. package/dist/emails/routes.d.ts +191 -0
  143. package/dist/emails/routes.d.ts.map +1 -0
  144. package/dist/emails/routes.js +39 -0
  145. package/dist/emails/routes.js.map +1 -0
  146. package/dist/emails/schema.d.ts +237 -0
  147. package/dist/emails/schema.d.ts.map +1 -0
  148. package/dist/emails/schema.js +98 -0
  149. package/dist/emails/schema.js.map +1 -0
  150. package/dist/emails/service.d.ts +22 -0
  151. package/dist/emails/service.d.ts.map +1 -0
  152. package/dist/emails/service.js +337 -0
  153. package/dist/emails/service.js.map +1 -0
  154. package/dist/emails/utils.d.ts +49 -0
  155. package/dist/emails/utils.d.ts.map +1 -0
  156. package/dist/emails/utils.js +341 -0
  157. package/dist/emails/utils.js.map +1 -0
  158. package/dist/files/index.d.ts +4 -0
  159. package/dist/files/index.d.ts.map +1 -0
  160. package/dist/files/index.js +4 -0
  161. package/dist/files/index.js.map +1 -0
  162. package/dist/files/routes.d.ts +72 -0
  163. package/dist/files/routes.d.ts.map +1 -0
  164. package/dist/files/routes.js +38 -0
  165. package/dist/files/routes.js.map +1 -0
  166. package/dist/files/schema.d.ts +57 -0
  167. package/dist/files/schema.d.ts.map +1 -0
  168. package/dist/files/schema.js +39 -0
  169. package/dist/files/schema.js.map +1 -0
  170. package/dist/files/service.d.ts +27 -0
  171. package/dist/files/service.d.ts.map +1 -0
  172. package/dist/files/service.js +175 -0
  173. package/dist/files/service.js.map +1 -0
  174. package/dist/files/utils.d.ts +3 -0
  175. package/dist/files/utils.d.ts.map +1 -0
  176. package/dist/files/utils.js +63 -0
  177. package/dist/files/utils.js.map +1 -0
  178. package/dist/index.d.ts +875 -0
  179. package/dist/index.d.ts.map +1 -0
  180. package/dist/index.js +9919 -0
  181. package/dist/index.js.map +7 -0
  182. package/dist/payments/billing.d.ts +29 -0
  183. package/dist/payments/billing.d.ts.map +1 -0
  184. package/dist/payments/billing.js +378 -0
  185. package/dist/payments/billing.js.map +1 -0
  186. package/dist/payments/index.d.ts +4 -0
  187. package/dist/payments/index.d.ts.map +1 -0
  188. package/dist/payments/index.js +4 -0
  189. package/dist/payments/index.js.map +1 -0
  190. package/dist/payments/routes.d.ts +340 -0
  191. package/dist/payments/routes.d.ts.map +1 -0
  192. package/dist/payments/routes.js +56 -0
  193. package/dist/payments/routes.js.map +1 -0
  194. package/dist/payments/schema.d.ts +412 -0
  195. package/dist/payments/schema.d.ts.map +1 -0
  196. package/dist/payments/schema.js +260 -0
  197. package/dist/payments/schema.js.map +1 -0
  198. package/dist/payments/service.d.ts +31 -0
  199. package/dist/payments/service.d.ts.map +1 -0
  200. package/dist/payments/service.js +322 -0
  201. package/dist/payments/service.js.map +1 -0
  202. package/dist/payments/utils.d.ts +61 -0
  203. package/dist/payments/utils.d.ts.map +1 -0
  204. package/dist/payments/utils.js +369 -0
  205. package/dist/payments/utils.js.map +1 -0
  206. package/dist/router/guard.d.ts +19 -0
  207. package/dist/router/guard.d.ts.map +1 -0
  208. package/dist/router/guard.js +243 -0
  209. package/dist/router/guard.js.map +1 -0
  210. package/dist/router/index.d.ts +3 -0
  211. package/dist/router/index.d.ts.map +1 -0
  212. package/dist/router/index.js +3 -0
  213. package/dist/router/index.js.map +1 -0
  214. package/dist/router/schema.d.ts +103 -0
  215. package/dist/router/schema.d.ts.map +1 -0
  216. package/dist/router/schema.js +282 -0
  217. package/dist/router/schema.js.map +1 -0
  218. package/dist/router/service.d.ts +25 -0
  219. package/dist/router/service.d.ts.map +1 -0
  220. package/dist/router/service.js +339 -0
  221. package/dist/router/service.js.map +1 -0
  222. package/dist/tasks/index.d.ts +4 -0
  223. package/dist/tasks/index.d.ts.map +1 -0
  224. package/dist/tasks/index.js +4 -0
  225. package/dist/tasks/index.js.map +1 -0
  226. package/dist/tasks/routes.d.ts +54 -0
  227. package/dist/tasks/routes.d.ts.map +1 -0
  228. package/dist/tasks/routes.js +195 -0
  229. package/dist/tasks/routes.js.map +1 -0
  230. package/dist/tasks/schema.d.ts +261 -0
  231. package/dist/tasks/schema.d.ts.map +1 -0
  232. package/dist/tasks/schema.js +35 -0
  233. package/dist/tasks/schema.js.map +1 -0
  234. package/dist/tasks/service.d.ts +32 -0
  235. package/dist/tasks/service.d.ts.map +1 -0
  236. package/dist/tasks/service.js +201 -0
  237. package/dist/tasks/service.js.map +1 -0
  238. package/dist/tasks/steps.d.ts +18 -0
  239. package/dist/tasks/steps.d.ts.map +1 -0
  240. package/dist/tasks/steps.js +365 -0
  241. package/dist/tasks/steps.js.map +1 -0
  242. package/dist/tasks/utils.d.ts +75 -0
  243. package/dist/tasks/utils.d.ts.map +1 -0
  244. package/dist/tasks/utils.js +311 -0
  245. package/dist/tasks/utils.js.map +1 -0
  246. package/dist/tasks/watcher.d.ts +13 -0
  247. package/dist/tasks/watcher.d.ts.map +1 -0
  248. package/dist/tasks/watcher.js +255 -0
  249. package/dist/tasks/watcher.js.map +1 -0
  250. package/dist/tasks/worker.d.ts +28 -0
  251. package/dist/tasks/worker.d.ts.map +1 -0
  252. package/dist/tasks/worker.js +326 -0
  253. package/dist/tasks/worker.js.map +1 -0
  254. package/dist/utils/index.d.ts +3 -0
  255. package/dist/utils/index.d.ts.map +1 -0
  256. package/dist/utils/index.js +3 -0
  257. package/dist/utils/index.js.map +1 -0
  258. package/dist/utils/schema.d.ts +79 -0
  259. package/dist/utils/schema.d.ts.map +1 -0
  260. package/dist/utils/schema.js +41 -0
  261. package/dist/utils/schema.js.map +1 -0
  262. package/dist/utils/service.d.ts +32 -0
  263. package/dist/utils/service.d.ts.map +1 -0
  264. package/dist/utils/service.js +384 -0
  265. package/dist/utils/service.js.map +1 -0
  266. package/docs/SDK_SPEC.md +1645 -0
  267. package/infra/firestore.base.indexes.json +138 -0
  268. package/infra/firestore.base.rules +152 -0
  269. package/infra/storage.base.rules +8 -0
  270. package/package.json +59 -13
  271. package/src/ai/index.ts +2 -0
  272. package/src/ai/schema.ts +64 -0
  273. package/src/ai/service.ts +317 -0
  274. package/src/ai/utils.ts +38 -0
  275. package/src/attachments/index.ts +3 -0
  276. package/src/attachments/routes.ts +28 -0
  277. package/src/attachments/schema.ts +45 -0
  278. package/src/attachments/service.ts +315 -0
  279. package/src/attachments/utils.ts +115 -0
  280. package/src/auth/index.ts +3 -0
  281. package/src/auth/oidc.ts +419 -0
  282. package/src/auth/recovery.ts +365 -0
  283. package/src/auth/routes.ts +142 -0
  284. package/src/auth/schema.ts +181 -0
  285. package/src/auth/service.ts +183 -0
  286. package/src/auth/session.ts +414 -0
  287. package/src/auth/utils.ts +106 -0
  288. package/src/chat/context.ts +310 -0
  289. package/src/chat/index.ts +3 -0
  290. package/src/chat/routes.ts +264 -0
  291. package/src/chat/schema.ts +295 -0
  292. package/src/chat/service.ts +360 -0
  293. package/src/chat/stream.ts +481 -0
  294. package/src/chat/utils.ts +370 -0
  295. package/src/cli/builder.ts +93 -0
  296. package/src/cli/deployer.ts +184 -0
  297. package/src/cli/dev-server.ts +77 -0
  298. package/src/cli/env.ts +89 -0
  299. package/src/cli/init.ts +125 -0
  300. package/src/config.ts +447 -0
  301. package/src/db/index.ts +2 -0
  302. package/src/db/schema.ts +409 -0
  303. package/src/db/service.ts +477 -0
  304. package/src/db/utils.ts +250 -0
  305. package/src/emails/index.ts +3 -0
  306. package/src/emails/routes.ts +64 -0
  307. package/src/emails/schema.ts +239 -0
  308. package/src/emails/service.ts +488 -0
  309. package/src/emails/utils.ts +449 -0
  310. package/src/files/index.ts +3 -0
  311. package/src/files/routes.ts +44 -0
  312. package/src/files/schema.ts +76 -0
  313. package/src/files/service.ts +238 -0
  314. package/src/files/utils.ts +77 -0
  315. package/src/index.ts +425 -0
  316. package/src/payments/billing.ts +488 -0
  317. package/src/payments/index.ts +3 -0
  318. package/src/payments/routes.ts +92 -0
  319. package/src/payments/schema.ts +401 -0
  320. package/src/payments/service.ts +445 -0
  321. package/src/payments/utils.ts +472 -0
  322. package/src/router/guard.ts +319 -0
  323. package/src/router/index.ts +2 -0
  324. package/src/router/schema.ts +458 -0
  325. package/src/router/service.ts +453 -0
  326. package/src/tasks/index.ts +11 -0
  327. package/src/tasks/routes.ts +230 -0
  328. package/src/tasks/schema.ts +310 -0
  329. package/src/tasks/service.ts +254 -0
  330. package/src/tasks/steps.ts +472 -0
  331. package/src/tasks/utils.ts +445 -0
  332. package/src/tasks/watcher.ts +307 -0
  333. package/src/tasks/worker.ts +461 -0
  334. package/src/utils/index.ts +2 -0
  335. package/src/utils/schema.ts +69 -0
  336. package/src/utils/service.ts +461 -0
  337. package/tests/e2e.test.ts +2531 -0
  338. package/tsconfig.base.json +12 -0
  339. package/README.md +0 -2
  340. package/index.d.ts +0 -1
  341. package/index.js +0 -1
@@ -0,0 +1,317 @@
1
+ import { createGoogleVertex } from '@ai-sdk/google-vertex'
2
+ import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic'
3
+ import { HTTPClient, Mistral } from '@mistralai/mistralai'
4
+ import { TypeSafeClient } from '@typesafe-ai/sdk'
5
+ import { embed, generateText, Output, type LanguageModel, type ModelMessage } from 'ai'
6
+ import type { Core } from '../index'
7
+ import {
8
+ DEFAULT_EXTRACTION_PROMPT,
9
+ envelopeSchema,
10
+ type AiGenerateOptions,
11
+ type AiGenerateResult,
12
+ type AiUsageSummary,
13
+ type DecideResult,
14
+ type JevQuestion,
15
+ type VisionOptions,
16
+ type VisionResult
17
+ } from './schema'
18
+ import { formatOcrPages, normalizeUsage } from './utils'
19
+
20
+ let cachedVertexKey = ''
21
+ let cachedVertexAnthropic: ReturnType<typeof createVertexAnthropic> | undefined
22
+ let cachedVertexEmbedding: ReturnType<typeof createGoogleVertex> | undefined
23
+
24
+ let cachedJevKey = ''
25
+ let cachedJev: TypeSafeClient | undefined
26
+
27
+ let cachedMistralKey = ''
28
+ let cachedMistral: Mistral | undefined
29
+
30
+ export class AiService {
31
+ readonly #core: Core
32
+
33
+ constructor(coreClient: Core) {
34
+ this.#core = coreClient
35
+ }
36
+
37
+ #vertexProviders = () => {
38
+ const project = this.#core.config.gcpProject
39
+ const location = this.#core.config.models.vertexLocation
40
+ const key = `${project}:${location}`
41
+ if (!cachedVertexAnthropic || !cachedVertexEmbedding || cachedVertexKey !== key) {
42
+ cachedVertexKey = key
43
+ cachedVertexAnthropic = createVertexAnthropic({ project, location })
44
+ cachedVertexEmbedding = createGoogleVertex({
45
+ project,
46
+ location: location === 'global' ? 'us-central1' : location
47
+ })
48
+ }
49
+ return { vertexAnthropic: cachedVertexAnthropic, vertexEmbeddingProvider: cachedVertexEmbedding }
50
+ }
51
+
52
+ languageModel = (modelId: string = this.#core.config.models.vertex): LanguageModel =>
53
+ this.#vertexProviders().vertexAnthropic(modelId)
54
+
55
+ #jevClient = (apiKey: string): TypeSafeClient =>
56
+ cachedJev && cachedJevKey === apiKey
57
+ ? cachedJev
58
+ : ((cachedJevKey = apiKey),
59
+ (cachedJev = new TypeSafeClient({ apiKey, defaultModel: this.#core.config.models.jev })))
60
+
61
+ #mistralClient = (): Mistral => {
62
+ const apiKey = this.#core.env.MISTRAL_API_KEY
63
+ if (cachedMistral && cachedMistralKey === apiKey) return cachedMistral
64
+ const httpClient = new HTTPClient()
65
+ httpClient.addHook('response', async (res) => {
66
+ const limit = Number(res.headers.get('x-ratelimit-limit-ocr-pages-minute') ?? '0')
67
+ const remHeader = res.headers.get('x-ratelimit-remaining-ocr-pages-minute')
68
+ const remaining = res.status === 429 ? 0 : remHeader !== null ? Number(remHeader) : undefined
69
+ if (limit > 0 || remaining !== undefined) {
70
+ await this.#core.internals.resilience.syncLimit('mistral', remaining, limit)
71
+ }
72
+ })
73
+ cachedMistralKey = apiKey
74
+ return (cachedMistral = new Mistral({
75
+ apiKey,
76
+ httpClient,
77
+ retryConfig: {
78
+ strategy: 'backoff',
79
+ backoff: { initialInterval: 5000, maxInterval: 20000, exponent: 1.5, maxElapsedTime: 90000 },
80
+ retryConnectionErrors: true
81
+ }
82
+ }))
83
+ }
84
+
85
+ describeImage = async (documentUrl: string, signal?: AbortSignal): Promise<string> =>
86
+ this.#core.internals.resilience.circuitBreaker('vertex', async () => {
87
+ await this.#core.internals.resilience.acquireLimit('ai')
88
+ const match = /^data:([^;,]+);base64,(.+)$/.exec(documentUrl)
89
+ const mediaType = match?.[1] ?? 'image/jpeg'
90
+ const base64 = match?.[2] ?? documentUrl
91
+ const modelId = this.#core.config.models.vertex
92
+ const { text } = await generateText({
93
+ model: this.languageModel(modelId),
94
+ abortSignal: signal,
95
+ messages: [
96
+ {
97
+ role: 'user',
98
+ content: [
99
+ { type: 'file', data: base64, mediaType },
100
+ { type: 'text', text: 'Describe what is illustrated on this image concisely in markdown.' }
101
+ ]
102
+ }
103
+ ]
104
+ })
105
+ const trimmed = text.trim()
106
+ if (!trimmed) throw new Error('vision model returned empty description')
107
+ return trimmed
108
+ })
109
+
110
+ #vertexVisionFallback = async (documentUrl: string, opts?: VisionOptions): Promise<VisionResult> => {
111
+ const markdown = await this.describeImage(documentUrl, opts?.signal)
112
+ let annotation: Record<string, unknown> = {}
113
+ const modelId = this.#core.config.models.vertex
114
+ let usage: AiUsageSummary | undefined
115
+ if (opts?.schema) {
116
+ await this.#core.internals.resilience.acquireLimit('ai')
117
+ const match = /^data:([^;,]+);base64,(.+)$/.exec(documentUrl)
118
+ const mediaType = match?.[1] ?? 'application/pdf'
119
+ const base64 = match?.[2] ?? documentUrl
120
+ const res = await this.#core.internals.resilience.circuitBreaker('vertex', () =>
121
+ generateText({
122
+ model: this.languageModel(modelId),
123
+ abortSignal: opts.signal,
124
+ messages: [
125
+ {
126
+ role: 'user',
127
+ content: [
128
+ { type: 'file', data: base64, mediaType },
129
+ {
130
+ type: 'text',
131
+ text: `${opts.prompt ?? DEFAULT_EXTRACTION_PROMPT}\n\nJSON Schema:\n${JSON.stringify(opts.schema)}\n\nRespond with ONLY a valid JSON object.`
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ })
137
+ )
138
+ usage = normalizeUsage(modelId, res.usage)
139
+ const jsonMatch = /\{[\s\S]*\}/.exec(res.text)
140
+ if (jsonMatch?.[0]) {
141
+ try {
142
+ annotation = JSON.parse(jsonMatch[0]) as Record<string, unknown>
143
+ } catch {}
144
+ }
145
+ }
146
+ return { markdown, annotation, ...(usage ? { usage } : {}) }
147
+ }
148
+
149
+ vision = async (documentUrl: string, opts?: VisionOptions): Promise<VisionResult> => {
150
+ const isImage = documentUrl.startsWith('data:image')
151
+ const hasSchema = Boolean(opts?.schema)
152
+ const allowFallback = opts?.fallbackVision !== false
153
+ await this.#core.internals.payments.enforceAiQuota()
154
+
155
+ return this.#core.internals.resilience.circuitBreaker(
156
+ 'mistral',
157
+ async () => {
158
+ await this.#core.internals.resilience.acquireLimit('mistral')
159
+ const modelId = this.#core.config.models.mistralOcr
160
+ const result = await this.#mistralClient().ocr.process(
161
+ {
162
+ model: modelId,
163
+ confidenceScoresGranularity: 'block',
164
+ pages: !hasSchema && !isImage ? Array.from({ length: 10 }, (_, i) => i) : undefined,
165
+ document: isImage
166
+ ? { type: 'image_url' as const, imageUrl: documentUrl }
167
+ : { type: 'document_url' as const, documentUrl },
168
+ ...(opts?.schema && {
169
+ documentAnnotationFormat: {
170
+ type: 'json_schema' as const,
171
+ jsonSchema: { name: 'fields', schemaDefinition: opts.schema, strict: true }
172
+ },
173
+ documentAnnotationPrompt: opts.prompt ?? DEFAULT_EXTRACTION_PROMPT
174
+ })
175
+ },
176
+ opts?.signal ? { fetchOptions: { signal: opts.signal } } : undefined
177
+ )
178
+ const pagesProcessed = result.usageInfo?.pagesProcessed ?? result.pages.length
179
+ if (pagesProcessed > 1) {
180
+ await this.#core.internals.resilience.chargeLimit('mistral', pagesProcessed - 1)
181
+ }
182
+ let markdown = formatOcrPages(result.pages)
183
+ const annotation = JSON.parse(result.documentAnnotation ?? '{}') as Record<string, unknown>
184
+
185
+ if (!markdown && (opts?.fallbackVision ?? !hasSchema)) {
186
+ markdown = await this.describeImage(documentUrl, opts?.signal)
187
+ }
188
+
189
+ const usage = normalizeUsage(modelId, null, pagesProcessed)
190
+ await this.#core.internals.payments.trackAiUsage({
191
+ model: usage.model,
192
+ inputTokens: usage.inputTokens,
193
+ outputTokens: usage.outputTokens,
194
+ totalTokens: usage.totalTokens,
195
+ source: 'vision'
196
+ })
197
+ return { markdown, annotation, usage }
198
+ },
199
+ allowFallback ? () => this.#vertexVisionFallback(documentUrl, opts) : undefined,
200
+ { isDev: this.#core.env.isDev }
201
+ )
202
+ }
203
+
204
+ decide = async (state: unknown, questions: Record<string, JevQuestion>): Promise<DecideResult> =>
205
+ this.#core.internals.resilience.circuitBreaker(
206
+ 'jev',
207
+ async () => {
208
+ await this.#core.internals.payments.enforceAiQuota()
209
+ await this.#core.internals.resilience.acquireLimit('jev')
210
+ const raw = await this.#jevClient(this.#core.env.TYPESAFE_API_KEY).systemOne({
211
+ state: state as never,
212
+ questions: questions as never
213
+ })
214
+ const parsed = envelopeSchema.safeParse(raw)
215
+ if (parsed.success && Object.keys(questions).every((q) => parsed.data.answers[q]?.type === questions[q]?.type)) {
216
+ const usage = normalizeUsage(parsed.data.model || this.#core.config.models.jev, null)
217
+ await this.#core.internals.payments.trackAiUsage({
218
+ model: usage.model,
219
+ inputTokens: usage.inputTokens,
220
+ outputTokens: usage.outputTokens,
221
+ totalTokens: usage.totalTokens,
222
+ source: 'decide'
223
+ })
224
+ return {
225
+ ...parsed.data,
226
+ usage
227
+ }
228
+ }
229
+ throw new Error('Jev decision returned no valid answer envelope')
230
+ },
231
+ undefined,
232
+ { isDev: this.#core.env.isDev }
233
+ )
234
+
235
+ generate = async <O = string>(
236
+ promptOrMessages: string | ModelMessage[],
237
+ opts?: AiGenerateOptions<O>
238
+ ): Promise<AiGenerateResult<O>> =>
239
+ this.#core.internals.resilience.circuitBreaker('vertex', async () => {
240
+ await this.#core.internals.payments.enforceAiQuota()
241
+ await this.#core.internals.resilience.acquireLimit('ai')
242
+ const modelId = opts?.model ?? this.#core.config.models.vertex
243
+ const model = this.languageModel(modelId)
244
+ const messages: ModelMessage[] =
245
+ typeof promptOrMessages === 'string'
246
+ ? [{ role: 'user', content: [{ type: 'text', text: promptOrMessages }] }]
247
+ : promptOrMessages
248
+
249
+ if (opts?.schema) {
250
+ const res = await generateText({
251
+ model,
252
+ system: opts.system,
253
+ messages,
254
+ output: Output.object({ schema: opts.schema }),
255
+ abortSignal: opts.signal
256
+ })
257
+ const norm = normalizeUsage(modelId, res.usage)
258
+ await this.#core.internals.payments.trackAiUsage({
259
+ model: norm.model,
260
+ inputTokens: norm.inputTokens,
261
+ outputTokens: norm.outputTokens,
262
+ totalTokens: norm.totalTokens,
263
+ source: 'generate'
264
+ })
265
+ return {
266
+ text: res.text,
267
+ output: res.output as O,
268
+ usage: res.usage
269
+ }
270
+ }
271
+
272
+ const res = await generateText({
273
+ model,
274
+ system: opts?.system,
275
+ messages,
276
+ abortSignal: opts?.signal
277
+ })
278
+ const norm = normalizeUsage(modelId, res.usage)
279
+ await this.#core.internals.payments.trackAiUsage({
280
+ model: norm.model,
281
+ inputTokens: norm.inputTokens,
282
+ outputTokens: norm.outputTokens,
283
+ totalTokens: norm.totalTokens,
284
+ source: 'generate'
285
+ })
286
+ return {
287
+ text: res.text,
288
+ output: res.text as unknown as O,
289
+ usage: res.usage
290
+ }
291
+ })
292
+
293
+ embed = async (text: string): Promise<number[] | null> => {
294
+ const normalized = text.trim()
295
+ if (normalized.length < 15) return null
296
+ try {
297
+ return await this.#core.internals.resilience.circuitBreaker('vertex', async () => {
298
+ await this.#core.internals.resilience.acquireLimit('ai')
299
+ const embeddingModel = this.#vertexProviders().vertexEmbeddingProvider.textEmbeddingModel(
300
+ this.#core.config.models.vertexEmbedding
301
+ )
302
+ const { embedding } = await embed({
303
+ model: embeddingModel,
304
+ value: normalized.slice(0, 8000),
305
+ dimensions: 768,
306
+ abortSignal: AbortSignal.timeout(15_000),
307
+ providerOptions: {
308
+ vertex: { outputDimensionality: 768 }
309
+ }
310
+ })
311
+ return Array.isArray(embedding) && embedding.length === 768 ? embedding : null
312
+ })
313
+ } catch {
314
+ return null
315
+ }
316
+ }
317
+ }
@@ -0,0 +1,38 @@
1
+ import type { OCRPageObject } from '@mistralai/mistralai/models/components'
2
+ import type { AiUsageSummary } from './schema'
3
+
4
+ export const formatOcrPages = (pages: OCRPageObject[]): string =>
5
+ pages
6
+ .map((p, i) => {
7
+ const text = p.blocks
8
+ ? p.blocks
9
+ .filter(
10
+ (b) =>
11
+ b.type !== 'image' &&
12
+ ('confidenceScores' in b ? (b.confidenceScores?.averageContentConfidenceScore ?? 1) : 1) >= 0.5
13
+ )
14
+ .map((b) => ('content' in b ? b.content.trim() : ''))
15
+ .filter(Boolean)
16
+ .join('\n\n')
17
+ : p.markdown.replace(/!\[[^\]]*\]\([^)]*\)/g, '').trim()
18
+ return text ? `### Page ${i + 1}\n${text}` : ''
19
+ })
20
+ .filter(Boolean)
21
+ .join('\n\n')
22
+
23
+ export const normalizeUsage = (
24
+ model: string,
25
+ raw?: { inputTokens?: number; outputTokens?: number; totalTokens?: number; tokens?: number } | null,
26
+ pages?: number
27
+ ): AiUsageSummary => {
28
+ const inputTokens = raw?.inputTokens ?? raw?.tokens ?? 0
29
+ const outputTokens = raw?.outputTokens ?? 0
30
+ const totalTokens = raw?.totalTokens ?? (inputTokens + outputTokens || (pages ? pages * 1000 : 0))
31
+ return {
32
+ model,
33
+ inputTokens,
34
+ outputTokens,
35
+ totalTokens,
36
+ ...(pages !== undefined ? { pages } : {})
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ export * from './schema'
2
+ export { AttachmentsService } from './service'
3
+ export { coreAttachmentsRoutes } from './routes'
@@ -0,0 +1,28 @@
1
+ import { core } from '../index'
2
+ import type { StepRunner } from '../tasks'
3
+ import {
4
+ processAttachmentsInput,
5
+ processAttachmentsOutput,
6
+ scanAttachmentInput,
7
+ scanAttachmentOutput,
8
+ type ProcessAttachmentsInput,
9
+ type ScanAttachmentInput
10
+ } from './schema'
11
+
12
+ export const coreAttachmentsRoutes = {
13
+ 'attachments.scan': {
14
+ mode: 'workflow' as const,
15
+ input: scanAttachmentInput,
16
+ output: scanAttachmentOutput,
17
+ shortCircuit: ({ input }: { input: ScanAttachmentInput }) => core.internals.context.scanShortCircuit({ input }),
18
+ handler: ({ input, step }: { input: ScanAttachmentInput; step: StepRunner }) =>
19
+ core.attachments.scan(input, { step })
20
+ },
21
+ 'attachments.process': {
22
+ mode: 'workflow' as const,
23
+ input: processAttachmentsInput,
24
+ output: processAttachmentsOutput,
25
+ handler: ({ input, step }: { input: ProcessAttachmentsInput; step: StepRunner }) =>
26
+ core.attachments.process(input, { step })
27
+ }
28
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod'
2
+ import { CORE_CONFIG } from '../config'
3
+ import { fileRef, fileScan } from '../files'
4
+
5
+ export const processedAttachment = z.union([
6
+ z.object({ ref: fileRef, scan: fileScan }),
7
+ z.object({ ref: fileRef, error: z.string() })
8
+ ])
9
+ export type ProcessedAttachment = z.infer<typeof processedAttachment>
10
+
11
+ export interface AttachmentTypeSpec<S extends z.ZodType = z.ZodType> {
12
+ description: string
13
+ schema: S
14
+ }
15
+ export type AttachmentTypesMap = Record<string, AttachmentTypeSpec>
16
+
17
+ export const defineAttachmentTypes = <T extends AttachmentTypesMap>(types: T): T => types
18
+
19
+ const registeredAttachmentTypes: AttachmentTypesMap = {}
20
+
21
+ export const registerAttachmentTypes = (types?: AttachmentTypesMap, reset = false): void => {
22
+ if (reset) {
23
+ for (const k of Object.keys(registeredAttachmentTypes)) delete registeredAttachmentTypes[k]
24
+ }
25
+ if (!types) return
26
+ Object.assign(registeredAttachmentTypes, types)
27
+ }
28
+
29
+ export const getRegisteredAttachmentTypes = (): AttachmentTypesMap => registeredAttachmentTypes
30
+
31
+ export const scanAttachmentInput = z.object({ ref: fileRef, type: z.string().optional() })
32
+ export const scanAttachmentOutput = fileScan
33
+ export type ScanAttachmentInput = z.input<typeof scanAttachmentInput>
34
+ export type ScanAttachmentOutput = z.infer<typeof scanAttachmentOutput>
35
+
36
+ export const attachmentInput = z.union([fileRef, scanAttachmentInput])
37
+ export const processAttachmentsInput = z.object({
38
+ refs: z
39
+ .array(attachmentInput)
40
+ .min(1)
41
+ .refine((arr) => arr.length <= CORE_CONFIG.limits.maxAttachments, 'Too many attachments')
42
+ })
43
+ export const processAttachmentsOutput = z.object({ attachments: z.array(processedAttachment) })
44
+ export type ProcessAttachmentsInput = z.input<typeof processAttachmentsInput>
45
+ export type ProcessAttachmentsOutput = z.infer<typeof processAttachmentsOutput>