ai-functions 2.1.1 → 2.3.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 (286) hide show
  1. package/.turbo/turbo-build.log +1 -4
  2. package/CHANGELOG.md +68 -1
  3. package/README.md +397 -157
  4. package/dist/ai-promise.d.ts +50 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +410 -51
  7. package/dist/ai-promise.js.map +1 -1
  8. package/dist/ai-schemas.d.ts +56 -0
  9. package/dist/ai-schemas.d.ts.map +1 -0
  10. package/dist/ai-schemas.js +53 -0
  11. package/dist/ai-schemas.js.map +1 -0
  12. package/dist/ai.d.ts +16 -242
  13. package/dist/ai.d.ts.map +1 -1
  14. package/dist/ai.js +54 -837
  15. package/dist/ai.js.map +1 -1
  16. package/dist/batch/anthropic.d.ts +6 -4
  17. package/dist/batch/anthropic.d.ts.map +1 -1
  18. package/dist/batch/anthropic.js +83 -145
  19. package/dist/batch/anthropic.js.map +1 -1
  20. package/dist/batch/bedrock.d.ts +8 -30
  21. package/dist/batch/bedrock.d.ts.map +1 -1
  22. package/dist/batch/bedrock.js +155 -338
  23. package/dist/batch/bedrock.js.map +1 -1
  24. package/dist/batch/cloudflare.d.ts +8 -20
  25. package/dist/batch/cloudflare.d.ts.map +1 -1
  26. package/dist/batch/cloudflare.js +68 -189
  27. package/dist/batch/cloudflare.js.map +1 -1
  28. package/dist/batch/google.d.ts +6 -20
  29. package/dist/batch/google.d.ts.map +1 -1
  30. package/dist/batch/google.js +70 -238
  31. package/dist/batch/google.js.map +1 -1
  32. package/dist/batch/index.d.ts +4 -1
  33. package/dist/batch/index.d.ts.map +1 -1
  34. package/dist/batch/index.js +4 -1
  35. package/dist/batch/index.js.map +1 -1
  36. package/dist/batch/memory.d.ts +1 -1
  37. package/dist/batch/memory.d.ts.map +1 -1
  38. package/dist/batch/memory.js +14 -10
  39. package/dist/batch/memory.js.map +1 -1
  40. package/dist/batch/openai.d.ts +11 -14
  41. package/dist/batch/openai.d.ts.map +1 -1
  42. package/dist/batch/openai.js +52 -156
  43. package/dist/batch/openai.js.map +1 -1
  44. package/dist/batch/provider.d.ts +111 -0
  45. package/dist/batch/provider.d.ts.map +1 -0
  46. package/dist/batch/provider.js +233 -0
  47. package/dist/batch/provider.js.map +1 -0
  48. package/dist/batch-map.d.ts.map +1 -1
  49. package/dist/batch-map.js +23 -17
  50. package/dist/batch-map.js.map +1 -1
  51. package/dist/batch-queue.d.ts +65 -0
  52. package/dist/batch-queue.d.ts.map +1 -1
  53. package/dist/batch-queue.js +169 -14
  54. package/dist/batch-queue.js.map +1 -1
  55. package/dist/budget.d.ts +272 -0
  56. package/dist/budget.d.ts.map +1 -0
  57. package/dist/budget.js +513 -0
  58. package/dist/budget.js.map +1 -0
  59. package/dist/cache.d.ts +295 -0
  60. package/dist/cache.d.ts.map +1 -0
  61. package/dist/cache.js +433 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/context.d.ts +42 -8
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +64 -62
  66. package/dist/context.js.map +1 -1
  67. package/dist/digital-objects-registry.d.ts +229 -0
  68. package/dist/digital-objects-registry.d.ts.map +1 -0
  69. package/dist/digital-objects-registry.js +617 -0
  70. package/dist/digital-objects-registry.js.map +1 -0
  71. package/dist/embeddings.d.ts +2 -2
  72. package/dist/embeddings.d.ts.map +1 -1
  73. package/dist/errors.d.ts +22 -0
  74. package/dist/errors.d.ts.map +1 -0
  75. package/dist/errors.js +35 -0
  76. package/dist/errors.js.map +1 -0
  77. package/dist/eval/runner.d.ts +10 -1
  78. package/dist/eval/runner.d.ts.map +1 -1
  79. package/dist/eval/runner.js +41 -35
  80. package/dist/eval/runner.js.map +1 -1
  81. package/dist/eval-log/in-memory.d.ts +34 -0
  82. package/dist/eval-log/in-memory.d.ts.map +1 -0
  83. package/dist/eval-log/in-memory.js +84 -0
  84. package/dist/eval-log/in-memory.js.map +1 -0
  85. package/dist/eval-log/index.d.ts +29 -0
  86. package/dist/eval-log/index.d.ts.map +1 -0
  87. package/dist/eval-log/index.js +39 -0
  88. package/dist/eval-log/index.js.map +1 -0
  89. package/dist/eval-log/types.d.ts +101 -0
  90. package/dist/eval-log/types.d.ts.map +1 -0
  91. package/dist/eval-log/types.js +16 -0
  92. package/dist/eval-log/types.js.map +1 -0
  93. package/dist/function-registry.d.ts +116 -0
  94. package/dist/function-registry.d.ts.map +1 -0
  95. package/dist/function-registry.js +546 -0
  96. package/dist/function-registry.js.map +1 -0
  97. package/dist/generate.d.ts +9 -3
  98. package/dist/generate.d.ts.map +1 -1
  99. package/dist/generate.js +18 -22
  100. package/dist/generate.js.map +1 -1
  101. package/dist/index.d.ts +35 -20
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +89 -42
  104. package/dist/index.js.map +1 -1
  105. package/dist/logger.d.ts +118 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +187 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/middleware/budget.d.ts +84 -0
  110. package/dist/middleware/budget.d.ts.map +1 -0
  111. package/dist/middleware/budget.js +110 -0
  112. package/dist/middleware/budget.js.map +1 -0
  113. package/dist/middleware/cache.d.ts +103 -0
  114. package/dist/middleware/cache.d.ts.map +1 -0
  115. package/dist/middleware/cache.js +228 -0
  116. package/dist/middleware/cache.js.map +1 -0
  117. package/dist/middleware/embed-cache.d.ts +99 -0
  118. package/dist/middleware/embed-cache.d.ts.map +1 -0
  119. package/dist/middleware/embed-cache.js +128 -0
  120. package/dist/middleware/embed-cache.js.map +1 -0
  121. package/dist/middleware/index.d.ts +11 -0
  122. package/dist/middleware/index.d.ts.map +1 -0
  123. package/dist/middleware/index.js +11 -0
  124. package/dist/middleware/index.js.map +1 -0
  125. package/dist/middleware/trace.d.ts +103 -0
  126. package/dist/middleware/trace.d.ts.map +1 -0
  127. package/dist/middleware/trace.js +176 -0
  128. package/dist/middleware/trace.js.map +1 -0
  129. package/dist/primitives.d.ts +120 -1
  130. package/dist/primitives.d.ts.map +1 -1
  131. package/dist/primitives.js +398 -26
  132. package/dist/primitives.js.map +1 -1
  133. package/dist/retry.d.ts +368 -0
  134. package/dist/retry.d.ts.map +1 -0
  135. package/dist/retry.js +646 -0
  136. package/dist/retry.js.map +1 -0
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +2 -10
  139. package/dist/schema.js.map +1 -1
  140. package/dist/telemetry.d.ts +128 -0
  141. package/dist/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry.js +285 -0
  143. package/dist/telemetry.js.map +1 -0
  144. package/dist/template.d.ts.map +1 -1
  145. package/dist/template.js +6 -1
  146. package/dist/template.js.map +1 -1
  147. package/dist/tool-orchestration.d.ts +453 -0
  148. package/dist/tool-orchestration.d.ts.map +1 -0
  149. package/dist/tool-orchestration.js +763 -0
  150. package/dist/tool-orchestration.js.map +1 -0
  151. package/dist/type-guards.d.ts +28 -0
  152. package/dist/type-guards.d.ts.map +1 -0
  153. package/dist/type-guards.js +29 -0
  154. package/dist/type-guards.js.map +1 -0
  155. package/dist/types.d.ts +135 -17
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js +36 -1
  158. package/dist/types.js.map +1 -1
  159. package/dist/wrap-for-v3.d.ts +80 -0
  160. package/dist/wrap-for-v3.d.ts.map +1 -0
  161. package/dist/wrap-for-v3.js +89 -0
  162. package/dist/wrap-for-v3.js.map +1 -0
  163. package/examples/00-quickstart.ts +232 -0
  164. package/examples/01-rag-chatbot.ts +212 -0
  165. package/examples/02-multi-agent-research.ts +290 -0
  166. package/examples/03-email-classification.ts +379 -0
  167. package/examples/04-content-moderation.ts +400 -0
  168. package/examples/05-document-extraction.ts +455 -0
  169. package/examples/06-streaming-chat-nextjs.ts +437 -0
  170. package/examples/07-cloudflare-worker.ts +483 -0
  171. package/examples/08-batch-processing.ts +491 -0
  172. package/examples/09-budget-constrained.ts +527 -0
  173. package/examples/10-tool-orchestration.ts +565 -0
  174. package/examples/11-retry-resilience.ts +403 -0
  175. package/examples/12-caching-strategies.ts +422 -0
  176. package/examples/README.md +145 -0
  177. package/package.json +10 -6
  178. package/src/ai-promise.ts +528 -99
  179. package/src/ai-schemas.ts +122 -0
  180. package/src/ai.ts +69 -1153
  181. package/src/batch/anthropic.ts +96 -161
  182. package/src/batch/bedrock.ts +203 -454
  183. package/src/batch/cloudflare.ts +99 -282
  184. package/src/batch/google.ts +91 -297
  185. package/src/batch/index.ts +4 -1
  186. package/src/batch/memory.ts +15 -10
  187. package/src/batch/openai.ts +65 -193
  188. package/src/batch/provider.ts +336 -0
  189. package/src/batch-map.ts +29 -24
  190. package/src/batch-queue.ts +200 -11
  191. package/src/budget.ts +740 -0
  192. package/src/cache.ts +681 -0
  193. package/src/context.ts +122 -76
  194. package/src/digital-objects-registry.ts +750 -0
  195. package/src/errors.ts +37 -0
  196. package/src/eval/runner.ts +63 -38
  197. package/src/eval-log/in-memory.ts +90 -0
  198. package/src/eval-log/index.ts +46 -0
  199. package/src/eval-log/types.ts +110 -0
  200. package/src/function-registry.ts +671 -0
  201. package/src/generate.ts +33 -33
  202. package/src/index.ts +325 -49
  203. package/src/logger.ts +232 -0
  204. package/src/middleware/budget.ts +171 -0
  205. package/src/middleware/cache.ts +299 -0
  206. package/src/middleware/embed-cache.ts +195 -0
  207. package/src/middleware/index.ts +23 -0
  208. package/src/middleware/trace.ts +248 -0
  209. package/src/primitives.ts +589 -62
  210. package/src/retry.ts +902 -0
  211. package/src/schema.ts +8 -17
  212. package/src/telemetry.ts +403 -0
  213. package/src/template.ts +8 -4
  214. package/src/tool-orchestration.ts +1173 -0
  215. package/src/type-guards.ts +31 -0
  216. package/src/types.ts +164 -25
  217. package/src/wrap-for-v3.ts +105 -0
  218. package/test/ai-promise.test.ts +1080 -0
  219. package/test/ai-proxy.test.ts +1 -1
  220. package/test/backward-compat.test.ts +147 -0
  221. package/test/batch-autosubmit-errors.test.ts +610 -0
  222. package/test/batch-blog-posts.test.ts +87 -129
  223. package/test/budget-tracking.test.ts +800 -0
  224. package/test/cache.test.ts +712 -0
  225. package/test/context-isolation.test.ts +687 -0
  226. package/test/core-functions.test.ts +183 -579
  227. package/test/decide.test.ts +154 -322
  228. package/test/define.test.ts +211 -8
  229. package/test/digital-objects-registry.test.ts +760 -0
  230. package/test/embedding-cache-middleware.test.ts +140 -0
  231. package/test/evals/deterministic.eval.test.ts +376 -0
  232. package/test/generate-core.test.ts +140 -229
  233. package/test/implicit-batch.test.ts +22 -65
  234. package/test/json-parse-error-handling.test.ts +463 -0
  235. package/test/retry-policy-integration.test.ts +117 -0
  236. package/test/retry.test.ts +1016 -0
  237. package/test/schema.test.ts +55 -19
  238. package/test/streaming.test.ts +316 -0
  239. package/test/template.test.ts +1164 -0
  240. package/test/tool-orchestration.test.ts +1040 -0
  241. package/test/wrap-for-v3.test.ts +612 -0
  242. package/vitest.config.js +6 -0
  243. package/vitest.config.ts +20 -0
  244. package/dist/rpc/auth.d.ts +0 -69
  245. package/dist/rpc/auth.d.ts.map +0 -1
  246. package/dist/rpc/auth.js +0 -136
  247. package/dist/rpc/auth.js.map +0 -1
  248. package/dist/rpc/client.d.ts +0 -62
  249. package/dist/rpc/client.d.ts.map +0 -1
  250. package/dist/rpc/client.js +0 -103
  251. package/dist/rpc/client.js.map +0 -1
  252. package/dist/rpc/deferred.d.ts +0 -60
  253. package/dist/rpc/deferred.d.ts.map +0 -1
  254. package/dist/rpc/deferred.js +0 -96
  255. package/dist/rpc/deferred.js.map +0 -1
  256. package/dist/rpc/index.d.ts +0 -22
  257. package/dist/rpc/index.d.ts.map +0 -1
  258. package/dist/rpc/index.js +0 -38
  259. package/dist/rpc/index.js.map +0 -1
  260. package/dist/rpc/local.d.ts +0 -42
  261. package/dist/rpc/local.d.ts.map +0 -1
  262. package/dist/rpc/local.js +0 -50
  263. package/dist/rpc/local.js.map +0 -1
  264. package/dist/rpc/server.d.ts +0 -165
  265. package/dist/rpc/server.d.ts.map +0 -1
  266. package/dist/rpc/server.js +0 -405
  267. package/dist/rpc/server.js.map +0 -1
  268. package/dist/rpc/session.d.ts +0 -32
  269. package/dist/rpc/session.d.ts.map +0 -1
  270. package/dist/rpc/session.js +0 -43
  271. package/dist/rpc/session.js.map +0 -1
  272. package/dist/rpc/transport.d.ts +0 -306
  273. package/dist/rpc/transport.d.ts.map +0 -1
  274. package/dist/rpc/transport.js +0 -731
  275. package/dist/rpc/transport.js.map +0 -1
  276. package/src/batch/anthropic.js +0 -256
  277. package/src/batch/bedrock.js +0 -584
  278. package/src/batch/cloudflare.js +0 -287
  279. package/src/batch/google.js +0 -359
  280. package/src/batch/index.js +0 -30
  281. package/src/batch/memory.js +0 -187
  282. package/src/batch/openai.js +0 -402
  283. package/src/eval/index.js +0 -7
  284. package/src/eval/models.js +0 -119
  285. package/src/eval/runner.js +0 -147
  286. package/test/schema.test.js +0 -96
@@ -0,0 +1,750 @@
1
+ /**
2
+ * DigitalObjectsFunctionRegistry - Persistent function registry using digital-objects
3
+ *
4
+ * This implementation stores function definitions as Things and function calls as Actions,
5
+ * providing full persistence and audit trail capabilities.
6
+ *
7
+ * Nouns:
8
+ * - CodeFunction: Functions that generate executable code
9
+ * - GenerativeFunction: Functions that use AI to generate content
10
+ * - AgenticFunction: Functions that run in a loop with tools
11
+ * - HumanFunction: Functions that require human input/approval
12
+ *
13
+ * Verbs:
14
+ * - define: Function definition action
15
+ * - call: Function invocation action
16
+ * - complete: Successful completion action
17
+ * - fail: Failed execution action
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
22
+ import type { DigitalObjectsProvider, Thing, Action } from 'digital-objects'
23
+
24
+ import type {
25
+ FunctionRegistry,
26
+ DefinedFunction,
27
+ FunctionDefinition,
28
+ CodeFunctionDefinition,
29
+ GenerativeFunctionDefinition,
30
+ AgenticFunctionDefinition,
31
+ HumanFunctionDefinition,
32
+ } from './types.js'
33
+ import { getLogger } from './logger.js'
34
+
35
+ /**
36
+ * Noun names for function types
37
+ */
38
+ export const FUNCTION_NOUNS = {
39
+ CODE: 'CodeFunction',
40
+ GENERATIVE: 'GenerativeFunction',
41
+ AGENTIC: 'AgenticFunction',
42
+ HUMAN: 'HumanFunction',
43
+ } as const
44
+
45
+ /**
46
+ * Verb names for function actions
47
+ */
48
+ export const FUNCTION_VERBS = {
49
+ DEFINE: 'define',
50
+ CALL: 'call',
51
+ COMPLETE: 'complete',
52
+ FAIL: 'fail',
53
+ } as const
54
+
55
+ /**
56
+ * Stored function definition shape
57
+ */
58
+ export interface StoredFunctionDefinition {
59
+ name: string
60
+ type: 'code' | 'generative' | 'agentic' | 'human'
61
+ description?: string
62
+ args: unknown
63
+ returnType?: unknown
64
+ // Type-specific fields
65
+ /** Inline deterministic code body for a `code` function (handlers are not persistable). */
66
+ code?: string
67
+ language?: string
68
+ instructions?: string
69
+ /** @deprecated Legacy code-authoring fields; retained only for back-compat reads. */
70
+ includeTests?: boolean
71
+ /** @deprecated Legacy code-authoring fields; retained only for back-compat reads. */
72
+ includeExamples?: boolean
73
+ output?: string
74
+ system?: string
75
+ promptTemplate?: string
76
+ model?: string
77
+ temperature?: number
78
+ tools?: unknown[]
79
+ maxIterations?: number
80
+ stream?: boolean
81
+ channel?: string
82
+ timeout?: number
83
+ assignee?: string
84
+ }
85
+
86
+ /**
87
+ * Function call data stored in actions
88
+ */
89
+ export interface FunctionCallData {
90
+ args: unknown
91
+ result?: unknown
92
+ error?: string
93
+ duration?: number
94
+ }
95
+
96
+ /**
97
+ * Options for creating a DigitalObjectsFunctionRegistry
98
+ */
99
+ export interface DigitalObjectsRegistryOptions {
100
+ /** The digital-objects provider to use for storage */
101
+ provider: DigitalObjectsProvider
102
+ /** Whether to auto-initialize nouns and verbs (default: true) */
103
+ autoInitialize?: boolean
104
+ }
105
+
106
+ /**
107
+ * Map function type to noun name
108
+ */
109
+ function typeToNoun(type: FunctionDefinition['type']): string {
110
+ switch (type) {
111
+ case 'code':
112
+ return FUNCTION_NOUNS.CODE
113
+ case 'generative':
114
+ return FUNCTION_NOUNS.GENERATIVE
115
+ case 'agentic':
116
+ return FUNCTION_NOUNS.AGENTIC
117
+ case 'human':
118
+ return FUNCTION_NOUNS.HUMAN
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Convert a FunctionDefinition to storable data
124
+ */
125
+ function definitionToData(definition: FunctionDefinition): StoredFunctionDefinition {
126
+ const base: StoredFunctionDefinition = {
127
+ name: definition.name,
128
+ type: definition.type,
129
+ ...(definition.description !== undefined && { description: definition.description }),
130
+ args: definition.args,
131
+ ...(definition.returnType !== undefined && { returnType: definition.returnType }),
132
+ }
133
+
134
+ switch (definition.type) {
135
+ case 'code': {
136
+ const codeDef = definition as CodeFunctionDefinition
137
+ // A `handler` is a live function reference and cannot be persisted; only
138
+ // an inline `code` body round-trips. Definitions stored with a handler
139
+ // (no `code`) must be re-supplied with their handler when reloaded.
140
+ return {
141
+ ...base,
142
+ ...(codeDef.code !== undefined && { code: codeDef.code }),
143
+ ...(codeDef.language !== undefined && { language: codeDef.language }),
144
+ ...(codeDef.instructions !== undefined && { instructions: codeDef.instructions }),
145
+ }
146
+ }
147
+ case 'generative': {
148
+ const genDef = definition as GenerativeFunctionDefinition
149
+ return {
150
+ ...base,
151
+ ...(genDef.output !== undefined && { output: genDef.output }),
152
+ ...(genDef.system !== undefined && { system: genDef.system }),
153
+ ...(genDef.promptTemplate !== undefined && { promptTemplate: genDef.promptTemplate }),
154
+ ...(genDef.model !== undefined && { model: genDef.model }),
155
+ ...(genDef.temperature !== undefined && { temperature: genDef.temperature }),
156
+ }
157
+ }
158
+ case 'agentic': {
159
+ const agentDef = definition as AgenticFunctionDefinition
160
+ return {
161
+ ...base,
162
+ instructions: agentDef.instructions,
163
+ ...(agentDef.promptTemplate !== undefined && { promptTemplate: agentDef.promptTemplate }),
164
+ ...(agentDef.tools !== undefined && { tools: agentDef.tools }),
165
+ ...(agentDef.maxIterations !== undefined && { maxIterations: agentDef.maxIterations }),
166
+ ...(agentDef.model !== undefined && { model: agentDef.model }),
167
+ ...(agentDef.stream !== undefined && { stream: agentDef.stream }),
168
+ }
169
+ }
170
+ case 'human': {
171
+ const humanDef = definition as HumanFunctionDefinition
172
+ return {
173
+ ...base,
174
+ ...(humanDef.channel !== undefined && { channel: humanDef.channel }),
175
+ instructions: humanDef.instructions,
176
+ ...(humanDef.promptTemplate !== undefined && { promptTemplate: humanDef.promptTemplate }),
177
+ ...(humanDef.timeout !== undefined && { timeout: humanDef.timeout }),
178
+ ...(humanDef.assignee !== undefined && { assignee: humanDef.assignee }),
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Convert stored data back to a FunctionDefinition
186
+ */
187
+ function dataToDefinition(data: StoredFunctionDefinition): FunctionDefinition {
188
+ switch (data.type) {
189
+ case 'code': {
190
+ const def = {
191
+ type: 'code' as const,
192
+ name: data.name,
193
+ args: data.args,
194
+ } as CodeFunctionDefinition
195
+ if (data.description !== undefined)
196
+ (def as { description: string }).description = data.description
197
+ if (data.returnType !== undefined)
198
+ (def as { returnType: unknown }).returnType = data.returnType
199
+ if (data.code !== undefined) (def as { code: string }).code = data.code
200
+ if (data.language !== undefined)
201
+ (def as { language: CodeFunctionDefinition['language'] }).language =
202
+ data.language as CodeFunctionDefinition['language']
203
+ if (data.instructions !== undefined)
204
+ (def as { instructions: string }).instructions = data.instructions
205
+ return def
206
+ }
207
+ case 'generative': {
208
+ const def: GenerativeFunctionDefinition = {
209
+ type: 'generative',
210
+ name: data.name,
211
+ args: data.args,
212
+ output: (data.output ?? 'string') as GenerativeFunctionDefinition['output'],
213
+ }
214
+ if (data.description !== undefined) def.description = data.description
215
+ if (data.returnType !== undefined) def.returnType = data.returnType
216
+ if (data.system !== undefined) def.system = data.system
217
+ if (data.promptTemplate !== undefined) def.promptTemplate = data.promptTemplate
218
+ if (data.model !== undefined) def.model = data.model
219
+ if (data.temperature !== undefined) def.temperature = data.temperature
220
+ return def
221
+ }
222
+ case 'agentic': {
223
+ const def = {
224
+ type: 'agentic' as const,
225
+ name: data.name,
226
+ args: data.args,
227
+ instructions: data.instructions ?? '',
228
+ } as AgenticFunctionDefinition
229
+ if (data.description !== undefined)
230
+ (def as { description: string }).description = data.description
231
+ if (data.returnType !== undefined)
232
+ (def as { returnType: unknown }).returnType = data.returnType
233
+ if (data.promptTemplate !== undefined)
234
+ (def as { promptTemplate: string }).promptTemplate = data.promptTemplate
235
+ if (data.tools !== undefined)
236
+ (def as { tools: AgenticFunctionDefinition['tools'] }).tools =
237
+ data.tools as AgenticFunctionDefinition['tools']
238
+ if (data.maxIterations !== undefined)
239
+ (def as { maxIterations: number }).maxIterations = data.maxIterations
240
+ if (data.model !== undefined) (def as { model: string }).model = data.model
241
+ if (data.stream !== undefined) (def as { stream: boolean }).stream = data.stream
242
+ return def
243
+ }
244
+ case 'human': {
245
+ const def: HumanFunctionDefinition = {
246
+ type: 'human',
247
+ name: data.name,
248
+ args: data.args,
249
+ channel: (data.channel ?? 'web') as HumanFunctionDefinition['channel'],
250
+ instructions: data.instructions ?? '',
251
+ }
252
+ if (data.description !== undefined) def.description = data.description
253
+ if (data.returnType !== undefined) def.returnType = data.returnType
254
+ if (data.promptTemplate !== undefined) def.promptTemplate = data.promptTemplate
255
+ if (data.timeout !== undefined) def.timeout = data.timeout
256
+ if (data.assignee !== undefined) def.assignee = data.assignee
257
+ return def
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * DigitalObjectsFunctionRegistry - Persistent function registry using digital-objects
264
+ *
265
+ * This class implements the FunctionRegistry interface using digital-objects for storage.
266
+ * Function definitions are stored as Things, and function calls are tracked as Actions.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * import { createMemoryProvider } from 'digital-objects'
271
+ * import { createDigitalObjectsRegistry, defineFunction } from 'ai-functions'
272
+ *
273
+ * const provider = createMemoryProvider()
274
+ * const registry = await createDigitalObjectsRegistry({ provider })
275
+ *
276
+ * // Define a function
277
+ * const summarize = defineFunction({
278
+ * type: 'generative',
279
+ * name: 'summarize',
280
+ * args: { text: 'Text to summarize' },
281
+ * output: 'string',
282
+ * })
283
+ *
284
+ * // Store it in the registry
285
+ * registry.set('summarize', summarize)
286
+ *
287
+ * // Later, retrieve it
288
+ * const fn = registry.get('summarize')
289
+ * if (fn) {
290
+ * const result = await fn.call({ text: 'Long article...' })
291
+ * }
292
+ * ```
293
+ */
294
+ export class DigitalObjectsFunctionRegistry implements FunctionRegistry {
295
+ private provider: DigitalObjectsProvider
296
+ private initialized = false
297
+ private autoInitialize: boolean
298
+ private initPromise: Promise<void> | null = null
299
+
300
+ // In-memory cache for DefinedFunction instances (they contain the call implementation)
301
+ private functionCache = new Map<string, DefinedFunction>()
302
+
303
+ constructor(options: DigitalObjectsRegistryOptions) {
304
+ this.provider = options.provider
305
+ this.autoInitialize = options.autoInitialize ?? true
306
+ }
307
+
308
+ /**
309
+ * Initialize the registry by defining all necessary nouns and verbs
310
+ */
311
+ async initialize(): Promise<void> {
312
+ if (this.initialized) return
313
+ if (this.initPromise) return this.initPromise
314
+
315
+ this.initPromise = this._initialize()
316
+ await this.initPromise
317
+ this.initialized = true
318
+ }
319
+
320
+ private async _initialize(): Promise<void> {
321
+ // Define function type nouns
322
+ await Promise.all([
323
+ this.provider.defineNoun({
324
+ name: FUNCTION_NOUNS.CODE,
325
+ description: 'Function that generates executable code',
326
+ schema: {
327
+ name: 'string',
328
+ description: 'string?',
329
+ language: 'string?',
330
+ instructions: 'string?',
331
+ },
332
+ }),
333
+ this.provider.defineNoun({
334
+ name: FUNCTION_NOUNS.GENERATIVE,
335
+ description: 'Function that uses AI to generate content',
336
+ schema: {
337
+ name: 'string',
338
+ description: 'string?',
339
+ output: 'string',
340
+ system: 'string?',
341
+ promptTemplate: 'string?',
342
+ },
343
+ }),
344
+ this.provider.defineNoun({
345
+ name: FUNCTION_NOUNS.AGENTIC,
346
+ description: 'Function that runs in a loop with tools',
347
+ schema: {
348
+ name: 'string',
349
+ description: 'string?',
350
+ instructions: 'string',
351
+ maxIterations: 'number?',
352
+ },
353
+ }),
354
+ this.provider.defineNoun({
355
+ name: FUNCTION_NOUNS.HUMAN,
356
+ description: 'Function that requires human input or approval',
357
+ schema: {
358
+ name: 'string',
359
+ description: 'string?',
360
+ channel: 'string',
361
+ instructions: 'string',
362
+ },
363
+ }),
364
+ ])
365
+
366
+ // Define function action verbs
367
+ await Promise.all([
368
+ this.provider.defineVerb({
369
+ name: FUNCTION_VERBS.DEFINE,
370
+ description: 'Define a new function',
371
+ }),
372
+ this.provider.defineVerb({
373
+ name: FUNCTION_VERBS.CALL,
374
+ description: 'Call/invoke a function',
375
+ }),
376
+ this.provider.defineVerb({
377
+ name: FUNCTION_VERBS.COMPLETE,
378
+ description: 'Mark a function call as successfully completed',
379
+ }),
380
+ this.provider.defineVerb({
381
+ name: FUNCTION_VERBS.FAIL,
382
+ description: 'Mark a function call as failed',
383
+ }),
384
+ ])
385
+ }
386
+
387
+ /**
388
+ * Ensure the registry is initialized before operations
389
+ */
390
+ private async ensureInitialized(): Promise<void> {
391
+ if (this.autoInitialize && !this.initialized) {
392
+ await this.initialize()
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Get a function by name
398
+ */
399
+ get(name: string): DefinedFunction | undefined {
400
+ // Return from cache if available
401
+ return this.functionCache.get(name)
402
+ }
403
+
404
+ /**
405
+ * Get a function by name (async version for loading from storage)
406
+ */
407
+ async getAsync(name: string): Promise<DefinedFunction | undefined> {
408
+ await this.ensureInitialized()
409
+
410
+ // Check cache first
411
+ const cached = this.functionCache.get(name)
412
+ if (cached) return cached
413
+
414
+ // Search across all function nouns
415
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
416
+ const things = await this.provider.find<StoredFunctionDefinition>(noun, { name })
417
+ const firstThing = things[0]
418
+ if (firstThing) {
419
+ const definition = dataToDefinition(firstThing.data)
420
+ // Note: The caller needs to provide the call implementation
421
+ // This returns the definition info but not a callable function
422
+ return {
423
+ definition,
424
+ call: async () => {
425
+ throw new Error(
426
+ `Function '${name}' was loaded from storage but has no call implementation. ` +
427
+ 'Use defineFunction() to create a callable function.'
428
+ )
429
+ },
430
+ asTool: () => ({
431
+ name: definition.name,
432
+ description: definition.description ?? `Execute ${definition.name}`,
433
+ parameters: { type: 'object', properties: {}, required: [] },
434
+ handler: async () => {
435
+ throw new Error('Function loaded from storage is not callable')
436
+ },
437
+ }),
438
+ }
439
+ }
440
+ }
441
+
442
+ return undefined
443
+ }
444
+
445
+ /**
446
+ * Store a function definition
447
+ */
448
+ set(name: string, fn: DefinedFunction): void {
449
+ // Store in cache for immediate access
450
+ this.functionCache.set(name, fn)
451
+
452
+ // Store in digital-objects asynchronously (fire and forget for sync interface)
453
+ this.setAsync(name, fn).catch((err) => {
454
+ getLogger().error(`Failed to persist function '${name}' to digital-objects:`, err)
455
+ })
456
+ }
457
+
458
+ /**
459
+ * Store a function definition (async version)
460
+ */
461
+ async setAsync(name: string, fn: DefinedFunction): Promise<Thing<StoredFunctionDefinition>> {
462
+ await this.ensureInitialized()
463
+
464
+ const definition = fn.definition
465
+ const noun = typeToNoun(definition.type)
466
+ const data = definitionToData(definition)
467
+
468
+ // Check if function already exists
469
+ const existing = await this.provider.find<StoredFunctionDefinition>(noun, { name })
470
+ const existingThing = existing[0]
471
+
472
+ let thing: Thing<StoredFunctionDefinition>
473
+ if (existingThing) {
474
+ // Update existing
475
+ thing = await this.provider.update<StoredFunctionDefinition>(existingThing.id, data)
476
+ } else {
477
+ // Create new
478
+ thing = await this.provider.create<StoredFunctionDefinition>(noun, data)
479
+
480
+ // Record the define action
481
+ await this.provider.perform(
482
+ FUNCTION_VERBS.DEFINE,
483
+ undefined, // subject (could be user ID in future)
484
+ thing.id,
485
+ { name, type: definition.type }
486
+ )
487
+ }
488
+
489
+ // Update cache
490
+ this.functionCache.set(name, fn)
491
+
492
+ return thing
493
+ }
494
+
495
+ /**
496
+ * Check if a function exists
497
+ */
498
+ has(name: string): boolean {
499
+ return this.functionCache.has(name)
500
+ }
501
+
502
+ /**
503
+ * Check if a function exists (async version that also checks storage)
504
+ */
505
+ async hasAsync(name: string): Promise<boolean> {
506
+ if (this.functionCache.has(name)) return true
507
+
508
+ await this.ensureInitialized()
509
+
510
+ // Search across all function nouns
511
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
512
+ const things = await this.provider.find<StoredFunctionDefinition>(noun, { name })
513
+ if (things.length > 0) return true
514
+ }
515
+
516
+ return false
517
+ }
518
+
519
+ /**
520
+ * List all function names
521
+ */
522
+ list(): string[] {
523
+ return Array.from(this.functionCache.keys())
524
+ }
525
+
526
+ /**
527
+ * List all function names (async version that includes storage)
528
+ */
529
+ async listAsync(): Promise<string[]> {
530
+ await this.ensureInitialized()
531
+
532
+ const names = new Set<string>(this.functionCache.keys())
533
+
534
+ // Get all functions from storage
535
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
536
+ const things = await this.provider.list<StoredFunctionDefinition>(noun)
537
+ for (const thing of things) {
538
+ names.add(thing.data.name)
539
+ }
540
+ }
541
+
542
+ return Array.from(names)
543
+ }
544
+
545
+ /**
546
+ * Delete a function
547
+ */
548
+ delete(name: string): boolean {
549
+ const existed = this.functionCache.has(name)
550
+ this.functionCache.delete(name)
551
+
552
+ // Delete from storage asynchronously
553
+ this.deleteAsync(name).catch((err) => {
554
+ getLogger().error(`Failed to delete function '${name}' from digital-objects:`, err)
555
+ })
556
+
557
+ return existed
558
+ }
559
+
560
+ /**
561
+ * Delete a function (async version)
562
+ */
563
+ async deleteAsync(name: string): Promise<boolean> {
564
+ await this.ensureInitialized()
565
+
566
+ this.functionCache.delete(name)
567
+
568
+ // Search across all function nouns and delete
569
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
570
+ const things = await this.provider.find<StoredFunctionDefinition>(noun, { name })
571
+ for (const thing of things) {
572
+ await this.provider.delete(thing.id)
573
+ }
574
+ if (things.length > 0) return true
575
+ }
576
+
577
+ return false
578
+ }
579
+
580
+ /**
581
+ * Clear all functions
582
+ */
583
+ clear(): void {
584
+ this.functionCache.clear()
585
+
586
+ // Clear storage asynchronously
587
+ this.clearAsync().catch((err) => {
588
+ getLogger().error('Failed to clear functions from digital-objects:', err)
589
+ })
590
+ }
591
+
592
+ /**
593
+ * Clear all functions (async version)
594
+ */
595
+ async clearAsync(): Promise<void> {
596
+ await this.ensureInitialized()
597
+
598
+ this.functionCache.clear()
599
+
600
+ // Delete all functions from storage
601
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
602
+ const things = await this.provider.list<StoredFunctionDefinition>(noun)
603
+ for (const thing of things) {
604
+ await this.provider.delete(thing.id)
605
+ }
606
+ }
607
+ }
608
+
609
+ // ============================================================================
610
+ // Function Call Tracking (Actions)
611
+ // ============================================================================
612
+
613
+ /**
614
+ * Record a function call as an Action
615
+ */
616
+ async trackCall(functionName: string, args: unknown): Promise<Action<FunctionCallData>> {
617
+ await this.ensureInitialized()
618
+
619
+ // Find the function thing
620
+ let functionId: string | undefined
621
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
622
+ const things = await this.provider.find<StoredFunctionDefinition>(noun, {
623
+ name: functionName,
624
+ })
625
+ const firstThing = things[0]
626
+ if (firstThing) {
627
+ functionId = firstThing.id
628
+ break
629
+ }
630
+ }
631
+
632
+ return this.provider.perform<FunctionCallData>(
633
+ FUNCTION_VERBS.CALL,
634
+ undefined, // subject (caller)
635
+ functionId, // object (the function)
636
+ { args }
637
+ )
638
+ }
639
+
640
+ /**
641
+ * Record a successful function completion
642
+ */
643
+ async trackCompletion(
644
+ callActionId: string,
645
+ result: unknown,
646
+ duration?: number
647
+ ): Promise<Action<FunctionCallData>> {
648
+ await this.ensureInitialized()
649
+
650
+ const data: FunctionCallData = { args: undefined, result }
651
+ if (duration !== undefined) data.duration = duration
652
+ return this.provider.perform<FunctionCallData>(
653
+ FUNCTION_VERBS.COMPLETE,
654
+ undefined,
655
+ callActionId,
656
+ data
657
+ )
658
+ }
659
+
660
+ /**
661
+ * Record a function failure
662
+ */
663
+ async trackFailure(
664
+ callActionId: string,
665
+ error: string,
666
+ duration?: number
667
+ ): Promise<Action<FunctionCallData>> {
668
+ await this.ensureInitialized()
669
+
670
+ const data: FunctionCallData = { args: undefined, error }
671
+ if (duration !== undefined) data.duration = duration
672
+ return this.provider.perform<FunctionCallData>(
673
+ FUNCTION_VERBS.FAIL,
674
+ undefined,
675
+ callActionId,
676
+ data
677
+ )
678
+ }
679
+
680
+ /**
681
+ * Get call history for a function
682
+ */
683
+ async getCallHistory(functionName: string): Promise<Action<FunctionCallData>[]> {
684
+ await this.ensureInitialized()
685
+
686
+ // Find the function thing
687
+ for (const noun of Object.values(FUNCTION_NOUNS)) {
688
+ const things = await this.provider.find<StoredFunctionDefinition>(noun, {
689
+ name: functionName,
690
+ })
691
+ const firstThing = things[0]
692
+ if (firstThing) {
693
+ return this.provider.listActions<FunctionCallData>({
694
+ verb: FUNCTION_VERBS.CALL,
695
+ object: firstThing.id,
696
+ })
697
+ }
698
+ }
699
+
700
+ return []
701
+ }
702
+
703
+ /**
704
+ * Get all recent function calls
705
+ */
706
+ async getRecentCalls(limit = 10): Promise<Action<FunctionCallData>[]> {
707
+ await this.ensureInitialized()
708
+
709
+ return this.provider.listActions<FunctionCallData>({
710
+ verb: FUNCTION_VERBS.CALL,
711
+ limit,
712
+ })
713
+ }
714
+
715
+ /**
716
+ * Get the underlying provider for advanced operations
717
+ */
718
+ getProvider(): DigitalObjectsProvider {
719
+ return this.provider
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Create a DigitalObjectsFunctionRegistry
725
+ *
726
+ * @param options - Configuration options including the provider
727
+ * @returns An initialized DigitalObjectsFunctionRegistry
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * import { createMemoryProvider } from 'digital-objects'
732
+ * import { createDigitalObjectsRegistry } from 'ai-functions'
733
+ *
734
+ * const provider = createMemoryProvider()
735
+ * const registry = await createDigitalObjectsRegistry({ provider })
736
+ *
737
+ * // Use the registry
738
+ * registry.set('myFunc', definedFunction)
739
+ * const fn = registry.get('myFunc')
740
+ * ```
741
+ */
742
+ export async function createDigitalObjectsRegistry(
743
+ options: DigitalObjectsRegistryOptions
744
+ ): Promise<DigitalObjectsFunctionRegistry> {
745
+ const registry = new DigitalObjectsFunctionRegistry(options)
746
+ if (options.autoInitialize !== false) {
747
+ await registry.initialize()
748
+ }
749
+ return registry
750
+ }