ai-functions 2.1.3 → 2.4.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 (284) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +90 -1
  3. package/README.md +38 -0
  4. package/dist/ai-promise.d.ts +3 -3
  5. package/dist/ai-promise.d.ts.map +1 -1
  6. package/dist/ai-promise.js +135 -64
  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 +51 -858
  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.map +1 -1
  56. package/dist/budget.js +27 -14
  57. package/dist/budget.js.map +1 -1
  58. package/dist/cache.d.ts +23 -0
  59. package/dist/cache.d.ts.map +1 -1
  60. package/dist/cache.js +36 -15
  61. package/dist/cache.js.map +1 -1
  62. package/dist/context.d.ts +26 -8
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +64 -62
  65. package/dist/context.js.map +1 -1
  66. package/dist/digital-objects-registry.d.ts +229 -0
  67. package/dist/digital-objects-registry.d.ts.map +1 -0
  68. package/dist/digital-objects-registry.js +617 -0
  69. package/dist/digital-objects-registry.js.map +1 -0
  70. package/dist/embeddings.d.ts +2 -2
  71. package/dist/embeddings.d.ts.map +1 -1
  72. package/dist/errors.d.ts +22 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +35 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/eval/runner.d.ts +8 -0
  77. package/dist/eval/runner.d.ts.map +1 -1
  78. package/dist/eval/runner.js +41 -35
  79. package/dist/eval/runner.js.map +1 -1
  80. package/dist/eval-log/in-memory.d.ts +34 -0
  81. package/dist/eval-log/in-memory.d.ts.map +1 -0
  82. package/dist/eval-log/in-memory.js +84 -0
  83. package/dist/eval-log/in-memory.js.map +1 -0
  84. package/dist/eval-log/index.d.ts +29 -0
  85. package/dist/eval-log/index.d.ts.map +1 -0
  86. package/dist/eval-log/index.js +39 -0
  87. package/dist/eval-log/index.js.map +1 -0
  88. package/dist/eval-log/types.d.ts +101 -0
  89. package/dist/eval-log/types.d.ts.map +1 -0
  90. package/dist/eval-log/types.js +16 -0
  91. package/dist/eval-log/types.js.map +1 -0
  92. package/dist/function-registry.d.ts +176 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +685 -0
  95. package/dist/function-registry.js.map +1 -0
  96. package/dist/generate.d.ts +9 -3
  97. package/dist/generate.d.ts.map +1 -1
  98. package/dist/generate.js +18 -18
  99. package/dist/generate.js.map +1 -1
  100. package/dist/index.d.ts +18 -11
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +35 -18
  103. package/dist/index.js.map +1 -1
  104. package/dist/logger.d.ts +118 -0
  105. package/dist/logger.d.ts.map +1 -0
  106. package/dist/logger.js +187 -0
  107. package/dist/logger.js.map +1 -0
  108. package/dist/middleware/budget.d.ts +84 -0
  109. package/dist/middleware/budget.d.ts.map +1 -0
  110. package/dist/middleware/budget.js +110 -0
  111. package/dist/middleware/budget.js.map +1 -0
  112. package/dist/middleware/cache.d.ts +103 -0
  113. package/dist/middleware/cache.d.ts.map +1 -0
  114. package/dist/middleware/cache.js +228 -0
  115. package/dist/middleware/cache.js.map +1 -0
  116. package/dist/middleware/embed-cache.d.ts +99 -0
  117. package/dist/middleware/embed-cache.d.ts.map +1 -0
  118. package/dist/middleware/embed-cache.js +128 -0
  119. package/dist/middleware/embed-cache.js.map +1 -0
  120. package/dist/middleware/index.d.ts +11 -0
  121. package/dist/middleware/index.d.ts.map +1 -0
  122. package/dist/middleware/index.js +11 -0
  123. package/dist/middleware/index.js.map +1 -0
  124. package/dist/middleware/trace.d.ts +103 -0
  125. package/dist/middleware/trace.d.ts.map +1 -0
  126. package/dist/middleware/trace.js +176 -0
  127. package/dist/middleware/trace.js.map +1 -0
  128. package/dist/primitives.d.ts +120 -1
  129. package/dist/primitives.d.ts.map +1 -1
  130. package/dist/primitives.js +398 -26
  131. package/dist/primitives.js.map +1 -1
  132. package/dist/retry.d.ts +66 -1
  133. package/dist/retry.d.ts.map +1 -1
  134. package/dist/retry.js +115 -8
  135. package/dist/retry.js.map +1 -1
  136. package/dist/sandbox.d.ts +36 -0
  137. package/dist/sandbox.d.ts.map +1 -0
  138. package/dist/sandbox.js +44 -0
  139. package/dist/sandbox.js.map +1 -0
  140. package/dist/schema.js +2 -2
  141. package/dist/schema.js.map +1 -1
  142. package/dist/telemetry.d.ts +128 -0
  143. package/dist/telemetry.d.ts.map +1 -0
  144. package/dist/telemetry.js +285 -0
  145. package/dist/telemetry.js.map +1 -0
  146. package/dist/template.d.ts.map +1 -1
  147. package/dist/template.js +6 -1
  148. package/dist/template.js.map +1 -1
  149. package/dist/tool-orchestration.d.ts +66 -4
  150. package/dist/tool-orchestration.d.ts.map +1 -1
  151. package/dist/tool-orchestration.js +123 -23
  152. package/dist/tool-orchestration.js.map +1 -1
  153. package/dist/type-guards.d.ts +28 -0
  154. package/dist/type-guards.d.ts.map +1 -0
  155. package/dist/type-guards.js +29 -0
  156. package/dist/type-guards.js.map +1 -0
  157. package/dist/types.d.ts +155 -19
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js +36 -1
  160. package/dist/types.js.map +1 -1
  161. package/dist/wrap-for-v3.d.ts +80 -0
  162. package/dist/wrap-for-v3.d.ts.map +1 -0
  163. package/dist/wrap-for-v3.js +89 -0
  164. package/dist/wrap-for-v3.js.map +1 -0
  165. package/examples/00-quickstart.ts +232 -0
  166. package/examples/01-rag-chatbot.ts +212 -0
  167. package/examples/02-multi-agent-research.ts +290 -0
  168. package/examples/03-email-classification.ts +379 -0
  169. package/examples/04-content-moderation.ts +400 -0
  170. package/examples/05-document-extraction.ts +455 -0
  171. package/examples/06-streaming-chat-nextjs.ts +437 -0
  172. package/examples/07-cloudflare-worker.ts +483 -0
  173. package/examples/08-batch-processing.ts +491 -0
  174. package/examples/09-budget-constrained.ts +527 -0
  175. package/examples/10-tool-orchestration.ts +565 -0
  176. package/examples/11-retry-resilience.ts +403 -0
  177. package/examples/12-caching-strategies.ts +422 -0
  178. package/examples/README.md +145 -0
  179. package/package.json +29 -25
  180. package/src/ai-promise.ts +226 -140
  181. package/src/ai-schemas.ts +122 -0
  182. package/src/ai.ts +71 -1176
  183. package/src/batch/anthropic.ts +96 -161
  184. package/src/batch/bedrock.ts +203 -454
  185. package/src/batch/cloudflare.ts +99 -282
  186. package/src/batch/google.ts +91 -297
  187. package/src/batch/index.ts +4 -1
  188. package/src/batch/memory.ts +15 -10
  189. package/src/batch/openai.ts +65 -193
  190. package/src/batch/provider.ts +336 -0
  191. package/src/batch-map.ts +29 -24
  192. package/src/batch-queue.ts +200 -11
  193. package/src/budget.ts +31 -18
  194. package/src/cache.ts +45 -17
  195. package/src/context.ts +106 -77
  196. package/src/digital-objects-registry.ts +750 -0
  197. package/src/errors.ts +37 -0
  198. package/src/eval/runner.ts +60 -36
  199. package/src/eval-log/in-memory.ts +90 -0
  200. package/src/eval-log/index.ts +46 -0
  201. package/src/eval-log/types.ts +110 -0
  202. package/src/function-registry.ts +874 -0
  203. package/src/generate.ts +33 -28
  204. package/src/index.ts +122 -21
  205. package/src/logger.ts +232 -0
  206. package/src/middleware/budget.ts +171 -0
  207. package/src/middleware/cache.ts +299 -0
  208. package/src/middleware/embed-cache.ts +195 -0
  209. package/src/middleware/index.ts +23 -0
  210. package/src/middleware/trace.ts +248 -0
  211. package/src/primitives.ts +589 -62
  212. package/src/retry.ts +144 -18
  213. package/src/sandbox.ts +52 -0
  214. package/src/schema.ts +8 -8
  215. package/src/telemetry.ts +403 -0
  216. package/src/template.ts +8 -4
  217. package/src/tool-orchestration.ts +213 -48
  218. package/src/type-guards.ts +31 -0
  219. package/src/types.ts +186 -27
  220. package/src/wrap-for-v3.ts +105 -0
  221. package/test/ai-promise.test.ts +1080 -0
  222. package/test/ai-proxy.test.ts +1 -1
  223. package/test/batch-autosubmit-errors.test.ts +49 -37
  224. package/test/batch-blog-posts.test.ts +87 -129
  225. package/test/core-functions.test.ts +183 -579
  226. package/test/decide.test.ts +154 -322
  227. package/test/define.test.ts +211 -8
  228. package/test/digital-objects-registry.test.ts +760 -0
  229. package/test/embedding-cache-middleware.test.ts +140 -0
  230. package/test/fill-template.test.ts +89 -0
  231. package/test/generate-core.test.ts +140 -229
  232. package/test/implicit-batch.test.ts +22 -65
  233. package/test/retry-policy-integration.test.ts +117 -0
  234. package/test/sandbox-execution.test.ts +155 -0
  235. package/test/schema.test.ts +55 -19
  236. package/test/template.test.ts +1164 -0
  237. package/test/tool-orchestration.test.ts +270 -0
  238. package/test/wrap-for-v3.test.ts +612 -0
  239. package/vitest.config.js +6 -0
  240. package/vitest.config.ts +20 -0
  241. package/LICENSE +0 -21
  242. package/dist/rpc/auth.d.ts +0 -69
  243. package/dist/rpc/auth.d.ts.map +0 -1
  244. package/dist/rpc/auth.js +0 -136
  245. package/dist/rpc/auth.js.map +0 -1
  246. package/dist/rpc/client.d.ts +0 -62
  247. package/dist/rpc/client.d.ts.map +0 -1
  248. package/dist/rpc/client.js +0 -103
  249. package/dist/rpc/client.js.map +0 -1
  250. package/dist/rpc/deferred.d.ts +0 -60
  251. package/dist/rpc/deferred.d.ts.map +0 -1
  252. package/dist/rpc/deferred.js +0 -96
  253. package/dist/rpc/deferred.js.map +0 -1
  254. package/dist/rpc/index.d.ts +0 -22
  255. package/dist/rpc/index.d.ts.map +0 -1
  256. package/dist/rpc/index.js +0 -38
  257. package/dist/rpc/index.js.map +0 -1
  258. package/dist/rpc/local.d.ts +0 -42
  259. package/dist/rpc/local.d.ts.map +0 -1
  260. package/dist/rpc/local.js +0 -50
  261. package/dist/rpc/local.js.map +0 -1
  262. package/dist/rpc/server.d.ts +0 -165
  263. package/dist/rpc/server.d.ts.map +0 -1
  264. package/dist/rpc/server.js +0 -405
  265. package/dist/rpc/server.js.map +0 -1
  266. package/dist/rpc/session.d.ts +0 -32
  267. package/dist/rpc/session.d.ts.map +0 -1
  268. package/dist/rpc/session.js +0 -43
  269. package/dist/rpc/session.js.map +0 -1
  270. package/dist/rpc/transport.d.ts +0 -306
  271. package/dist/rpc/transport.d.ts.map +0 -1
  272. package/dist/rpc/transport.js +0 -731
  273. package/dist/rpc/transport.js.map +0 -1
  274. package/src/batch/anthropic.js +0 -256
  275. package/src/batch/bedrock.js +0 -584
  276. package/src/batch/cloudflare.js +0 -287
  277. package/src/batch/google.js +0 -359
  278. package/src/batch/index.js +0 -30
  279. package/src/batch/memory.js +0 -187
  280. package/src/batch/openai.js +0 -402
  281. package/src/eval/index.js +0 -7
  282. package/src/eval/models.js +0 -119
  283. package/src/eval/runner.js +0 -147
  284. package/test/schema.test.js +0 -96
@@ -0,0 +1,760 @@
1
+ /**
2
+ * Tests for DigitalObjectsFunctionRegistry
3
+ *
4
+ * This tests the persistent function registry implementation using digital-objects.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from 'vitest'
8
+ import { createMemoryProvider } from 'digital-objects'
9
+ import type { DigitalObjectsProvider } from 'digital-objects'
10
+ import {
11
+ createDigitalObjectsRegistry,
12
+ DigitalObjectsFunctionRegistry,
13
+ FUNCTION_NOUNS,
14
+ FUNCTION_VERBS,
15
+ } from '../src/digital-objects-registry.js'
16
+ import { defineFunction } from '../src/index.js'
17
+ import type { DefinedFunction } from '../src/types.js'
18
+
19
+ describe('createDigitalObjectsRegistry', () => {
20
+ let provider: DigitalObjectsProvider
21
+
22
+ beforeEach(() => {
23
+ provider = createMemoryProvider()
24
+ })
25
+
26
+ it('creates a registry with nouns defined', async () => {
27
+ const registry = await createDigitalObjectsRegistry({ provider })
28
+
29
+ // Check that nouns were defined
30
+ const nouns = await provider.listNouns()
31
+ const nounNames = nouns.map((n) => n.name)
32
+
33
+ expect(nounNames).toContain(FUNCTION_NOUNS.CODE)
34
+ expect(nounNames).toContain(FUNCTION_NOUNS.GENERATIVE)
35
+ expect(nounNames).toContain(FUNCTION_NOUNS.AGENTIC)
36
+ expect(nounNames).toContain(FUNCTION_NOUNS.HUMAN)
37
+ })
38
+
39
+ it('creates a registry with verbs defined', async () => {
40
+ const registry = await createDigitalObjectsRegistry({ provider })
41
+
42
+ // Check that verbs were defined
43
+ const verbs = await provider.listVerbs()
44
+ const verbNames = verbs.map((v) => v.name)
45
+
46
+ expect(verbNames).toContain(FUNCTION_VERBS.DEFINE)
47
+ expect(verbNames).toContain(FUNCTION_VERBS.CALL)
48
+ expect(verbNames).toContain(FUNCTION_VERBS.COMPLETE)
49
+ expect(verbNames).toContain(FUNCTION_VERBS.FAIL)
50
+ })
51
+
52
+ it('can skip auto-initialization', async () => {
53
+ const registry = await createDigitalObjectsRegistry({
54
+ provider,
55
+ autoInitialize: false,
56
+ })
57
+
58
+ // Should not have defined nouns yet
59
+ const nouns = await provider.listNouns()
60
+ expect(nouns.length).toBe(0)
61
+
62
+ // Can manually initialize
63
+ await registry.initialize()
64
+ const nounsAfter = await provider.listNouns()
65
+ expect(nounsAfter.length).toBe(4)
66
+ })
67
+ })
68
+
69
+ describe('DigitalObjectsFunctionRegistry', () => {
70
+ let provider: DigitalObjectsProvider
71
+ let registry: DigitalObjectsFunctionRegistry
72
+
73
+ beforeEach(async () => {
74
+ provider = createMemoryProvider()
75
+ registry = await createDigitalObjectsRegistry({ provider })
76
+ })
77
+
78
+ describe('set()', () => {
79
+ it('stores function definitions as Things', async () => {
80
+ const fn = defineFunction({
81
+ type: 'generative',
82
+ name: 'summarize',
83
+ args: { text: 'Text to summarize' },
84
+ output: 'string',
85
+ })
86
+
87
+ registry.set('summarize', fn)
88
+
89
+ // Wait for async storage to complete
90
+ await new Promise((resolve) => setTimeout(resolve, 10))
91
+
92
+ // Check that it was stored as a Thing
93
+ const things = await provider.find(FUNCTION_NOUNS.GENERATIVE, { name: 'summarize' })
94
+ expect(things.length).toBe(1)
95
+ expect(things[0].data).toMatchObject({
96
+ name: 'summarize',
97
+ type: 'generative',
98
+ output: 'string',
99
+ })
100
+ })
101
+
102
+ it('stores different function types in their respective nouns', async () => {
103
+ const codeFn = defineFunction({
104
+ type: 'code',
105
+ name: 'implement',
106
+ args: { spec: 'Spec' },
107
+ language: 'typescript',
108
+ })
109
+
110
+ const agenticFn = defineFunction({
111
+ type: 'agentic',
112
+ name: 'research',
113
+ args: { topic: 'Topic' },
114
+ instructions: 'Research thoroughly',
115
+ })
116
+
117
+ const humanFn = defineFunction({
118
+ type: 'human',
119
+ name: 'approve',
120
+ args: { amount: 'Amount' },
121
+ channel: 'web',
122
+ instructions: 'Review and approve',
123
+ })
124
+
125
+ registry.set('implement', codeFn)
126
+ registry.set('research', agenticFn)
127
+ registry.set('approve', humanFn)
128
+
129
+ await new Promise((resolve) => setTimeout(resolve, 10))
130
+
131
+ const codeThings = await provider.find(FUNCTION_NOUNS.CODE, { name: 'implement' })
132
+ const agenticThings = await provider.find(FUNCTION_NOUNS.AGENTIC, { name: 'research' })
133
+ const humanThings = await provider.find(FUNCTION_NOUNS.HUMAN, { name: 'approve' })
134
+
135
+ expect(codeThings.length).toBe(1)
136
+ expect(agenticThings.length).toBe(1)
137
+ expect(humanThings.length).toBe(1)
138
+ })
139
+ })
140
+
141
+ describe('get()', () => {
142
+ it('retrieves function definitions from cache', () => {
143
+ const fn = defineFunction({
144
+ type: 'generative',
145
+ name: 'translate',
146
+ args: { text: 'Text', lang: 'Target language' },
147
+ output: 'string',
148
+ })
149
+
150
+ registry.set('translate', fn)
151
+
152
+ const retrieved = registry.get('translate')
153
+ expect(retrieved).toBe(fn)
154
+ expect(retrieved?.definition.name).toBe('translate')
155
+ })
156
+
157
+ it('returns undefined for non-existent functions', () => {
158
+ const result = registry.get('nonexistent')
159
+ expect(result).toBeUndefined()
160
+ })
161
+ })
162
+
163
+ describe('getAsync()', () => {
164
+ it('retrieves function definitions from storage', async () => {
165
+ const fn = defineFunction({
166
+ type: 'generative',
167
+ name: 'analyze',
168
+ args: { data: 'Data to analyze' },
169
+ output: 'string',
170
+ })
171
+
172
+ await registry.setAsync('analyze', fn)
173
+
174
+ // Create a new registry to simulate fresh load
175
+ const newRegistry = await createDigitalObjectsRegistry({ provider })
176
+
177
+ const retrieved = await newRegistry.getAsync('analyze')
178
+ expect(retrieved).toBeDefined()
179
+ expect(retrieved?.definition.name).toBe('analyze')
180
+ expect(retrieved?.definition.type).toBe('generative')
181
+ })
182
+ })
183
+
184
+ describe('has()', () => {
185
+ it('checks if function exists in cache', () => {
186
+ expect(registry.has('test')).toBe(false)
187
+
188
+ const fn = defineFunction({
189
+ type: 'generative',
190
+ name: 'test',
191
+ args: {},
192
+ output: 'string',
193
+ })
194
+
195
+ registry.set('test', fn)
196
+ expect(registry.has('test')).toBe(true)
197
+ })
198
+ })
199
+
200
+ describe('hasAsync()', () => {
201
+ it('checks if function exists in storage', async () => {
202
+ const fn = defineFunction({
203
+ type: 'generative',
204
+ name: 'stored',
205
+ args: {},
206
+ output: 'string',
207
+ })
208
+
209
+ await registry.setAsync('stored', fn)
210
+
211
+ // Create new registry
212
+ const newRegistry = await createDigitalObjectsRegistry({ provider })
213
+
214
+ expect(await newRegistry.hasAsync('stored')).toBe(true)
215
+ expect(await newRegistry.hasAsync('notexist')).toBe(false)
216
+ })
217
+ })
218
+
219
+ describe('delete()', () => {
220
+ it('removes functions from cache', () => {
221
+ const fn = defineFunction({
222
+ type: 'generative',
223
+ name: 'toDelete',
224
+ args: {},
225
+ output: 'string',
226
+ })
227
+
228
+ registry.set('toDelete', fn)
229
+ expect(registry.has('toDelete')).toBe(true)
230
+
231
+ const result = registry.delete('toDelete')
232
+ expect(result).toBe(true)
233
+ expect(registry.has('toDelete')).toBe(false)
234
+ })
235
+
236
+ it('returns false for non-existent functions', () => {
237
+ const result = registry.delete('nonexistent')
238
+ expect(result).toBe(false)
239
+ })
240
+ })
241
+
242
+ describe('deleteAsync()', () => {
243
+ it('removes functions from storage', async () => {
244
+ const fn = defineFunction({
245
+ type: 'generative',
246
+ name: 'toRemove',
247
+ args: {},
248
+ output: 'string',
249
+ })
250
+
251
+ await registry.setAsync('toRemove', fn)
252
+
253
+ // Verify it exists
254
+ const things = await provider.find(FUNCTION_NOUNS.GENERATIVE, { name: 'toRemove' })
255
+ expect(things.length).toBe(1)
256
+
257
+ // Delete it
258
+ const result = await registry.deleteAsync('toRemove')
259
+ expect(result).toBe(true)
260
+
261
+ // Verify it's gone
262
+ const thingsAfter = await provider.find(FUNCTION_NOUNS.GENERATIVE, { name: 'toRemove' })
263
+ expect(thingsAfter.length).toBe(0)
264
+ })
265
+ })
266
+
267
+ describe('list() and listAsync()', () => {
268
+ it('list() returns all function names from cache', () => {
269
+ const fn1 = defineFunction({
270
+ type: 'generative',
271
+ name: 'func1',
272
+ args: {},
273
+ output: 'string',
274
+ })
275
+ const fn2 = defineFunction({
276
+ type: 'code',
277
+ name: 'func2',
278
+ args: {},
279
+ language: 'typescript',
280
+ })
281
+
282
+ registry.set('func1', fn1)
283
+ registry.set('func2', fn2)
284
+
285
+ const names = registry.list()
286
+ expect(names).toContain('func1')
287
+ expect(names).toContain('func2')
288
+ expect(names.length).toBe(2)
289
+ })
290
+
291
+ it('listAsync() returns all function names including from storage', async () => {
292
+ const fn1 = defineFunction({
293
+ type: 'generative',
294
+ name: 'funcA',
295
+ args: {},
296
+ output: 'string',
297
+ })
298
+ const fn2 = defineFunction({
299
+ type: 'agentic',
300
+ name: 'funcB',
301
+ args: {},
302
+ instructions: 'Do something',
303
+ })
304
+
305
+ await registry.setAsync('funcA', fn1)
306
+ await registry.setAsync('funcB', fn2)
307
+
308
+ const names = await registry.listAsync()
309
+ expect(names).toContain('funcA')
310
+ expect(names).toContain('funcB')
311
+ })
312
+ })
313
+
314
+ describe('getAll() equivalent - list + get pattern', () => {
315
+ it('returns all functions', () => {
316
+ const fn1 = defineFunction({
317
+ type: 'generative',
318
+ name: 'alpha',
319
+ args: {},
320
+ output: 'string',
321
+ })
322
+ const fn2 = defineFunction({
323
+ type: 'generative',
324
+ name: 'beta',
325
+ args: {},
326
+ output: 'string',
327
+ })
328
+ const fn3 = defineFunction({
329
+ type: 'code',
330
+ name: 'gamma',
331
+ args: {},
332
+ language: 'python',
333
+ })
334
+
335
+ registry.set('alpha', fn1)
336
+ registry.set('beta', fn2)
337
+ registry.set('gamma', fn3)
338
+
339
+ // getAll pattern: list all names then get each
340
+ const allFunctions = registry.list().map((name) => registry.get(name))
341
+
342
+ expect(allFunctions.length).toBe(3)
343
+ expect(allFunctions.map((f) => f?.definition.name)).toContain('alpha')
344
+ expect(allFunctions.map((f) => f?.definition.name)).toContain('beta')
345
+ expect(allFunctions.map((f) => f?.definition.name)).toContain('gamma')
346
+ })
347
+ })
348
+
349
+ describe('clear() and clearAsync()', () => {
350
+ it('clear() removes all functions from cache', () => {
351
+ const fn1 = defineFunction({
352
+ type: 'generative',
353
+ name: 'a',
354
+ args: {},
355
+ output: 'string',
356
+ })
357
+ const fn2 = defineFunction({
358
+ type: 'generative',
359
+ name: 'b',
360
+ args: {},
361
+ output: 'string',
362
+ })
363
+
364
+ registry.set('a', fn1)
365
+ registry.set('b', fn2)
366
+ expect(registry.list().length).toBe(2)
367
+
368
+ registry.clear()
369
+ expect(registry.list().length).toBe(0)
370
+ })
371
+
372
+ it('clearAsync() removes all functions from storage', async () => {
373
+ const fn = defineFunction({
374
+ type: 'generative',
375
+ name: 'toClear',
376
+ args: {},
377
+ output: 'string',
378
+ })
379
+
380
+ await registry.setAsync('toClear', fn)
381
+
382
+ // Verify stored
383
+ let things = await provider.list(FUNCTION_NOUNS.GENERATIVE)
384
+ expect(things.length).toBe(1)
385
+
386
+ await registry.clearAsync()
387
+
388
+ // Verify cleared
389
+ things = await provider.list(FUNCTION_NOUNS.GENERATIVE)
390
+ expect(things.length).toBe(0)
391
+ })
392
+ })
393
+ })
394
+
395
+ describe('DigitalObjectsFunctionRegistry - Action Tracking', () => {
396
+ let provider: DigitalObjectsProvider
397
+ let registry: DigitalObjectsFunctionRegistry
398
+
399
+ beforeEach(async () => {
400
+ provider = createMemoryProvider()
401
+ registry = await createDigitalObjectsRegistry({ provider })
402
+ })
403
+
404
+ describe('trackCall()', () => {
405
+ it('creates an Action for function invocation', async () => {
406
+ // First, store a function
407
+ const fn = defineFunction({
408
+ type: 'generative',
409
+ name: 'greet',
410
+ args: { name: 'Name to greet' },
411
+ output: 'string',
412
+ })
413
+ await registry.setAsync('greet', fn)
414
+
415
+ // Track a call
416
+ const callAction = await registry.trackCall('greet', { name: 'Alice' })
417
+
418
+ expect(callAction).toBeDefined()
419
+ expect(callAction.verb).toBe(FUNCTION_VERBS.CALL)
420
+ expect(callAction.data).toMatchObject({
421
+ args: { name: 'Alice' },
422
+ })
423
+ expect(callAction.id).toBeDefined()
424
+ })
425
+
426
+ it('links call action to the function Thing', async () => {
427
+ const fn = defineFunction({
428
+ type: 'generative',
429
+ name: 'process',
430
+ args: { input: 'Input data' },
431
+ output: 'string',
432
+ })
433
+ const thing = await registry.setAsync('process', fn)
434
+
435
+ const callAction = await registry.trackCall('process', { input: 'test' })
436
+
437
+ // The action's object should be the function thing's ID
438
+ expect(callAction.object).toBe(thing.id)
439
+ })
440
+ })
441
+
442
+ describe('trackCompletion()', () => {
443
+ it('creates an Action for successful completion', async () => {
444
+ const fn = defineFunction({
445
+ type: 'generative',
446
+ name: 'compute',
447
+ args: { value: 'Input value' },
448
+ output: 'string',
449
+ })
450
+ await registry.setAsync('compute', fn)
451
+
452
+ const callAction = await registry.trackCall('compute', { value: 42 })
453
+
454
+ const completionAction = await registry.trackCompletion(callAction.id, 'Result: 84', 150)
455
+
456
+ expect(completionAction).toBeDefined()
457
+ expect(completionAction.verb).toBe(FUNCTION_VERBS.COMPLETE)
458
+ expect(completionAction.object).toBe(callAction.id)
459
+ expect(completionAction.data).toMatchObject({
460
+ result: 'Result: 84',
461
+ duration: 150,
462
+ })
463
+ })
464
+
465
+ it('stores the result in the action data', async () => {
466
+ const fn = defineFunction({
467
+ type: 'generative',
468
+ name: 'calculate',
469
+ args: {},
470
+ output: 'string',
471
+ })
472
+ await registry.setAsync('calculate', fn)
473
+
474
+ const callAction = await registry.trackCall('calculate', {})
475
+ const completionAction = await registry.trackCompletion(callAction.id, {
476
+ computed: true,
477
+ value: 100,
478
+ })
479
+
480
+ expect(completionAction.data?.result).toEqual({ computed: true, value: 100 })
481
+ })
482
+ })
483
+
484
+ describe('trackFailure()', () => {
485
+ it('creates an Action for failed execution', async () => {
486
+ const fn = defineFunction({
487
+ type: 'generative',
488
+ name: 'failing',
489
+ args: {},
490
+ output: 'string',
491
+ })
492
+ await registry.setAsync('failing', fn)
493
+
494
+ const callAction = await registry.trackCall('failing', {})
495
+
496
+ const failureAction = await registry.trackFailure(callAction.id, 'Connection timeout', 5000)
497
+
498
+ expect(failureAction).toBeDefined()
499
+ expect(failureAction.verb).toBe(FUNCTION_VERBS.FAIL)
500
+ expect(failureAction.object).toBe(callAction.id)
501
+ expect(failureAction.data).toMatchObject({
502
+ error: 'Connection timeout',
503
+ duration: 5000,
504
+ })
505
+ })
506
+
507
+ it('stores error message in action data', async () => {
508
+ const fn = defineFunction({
509
+ type: 'generative',
510
+ name: 'erroring',
511
+ args: {},
512
+ output: 'string',
513
+ })
514
+ await registry.setAsync('erroring', fn)
515
+
516
+ const callAction = await registry.trackCall('erroring', {})
517
+ const failureAction = await registry.trackFailure(callAction.id, 'Invalid input provided')
518
+
519
+ expect(failureAction.data?.error).toBe('Invalid input provided')
520
+ })
521
+ })
522
+
523
+ describe('getCallHistory()', () => {
524
+ it('returns calls for a specific function', async () => {
525
+ const fn = defineFunction({
526
+ type: 'generative',
527
+ name: 'tracked',
528
+ args: { x: 'Input' },
529
+ output: 'string',
530
+ })
531
+ await registry.setAsync('tracked', fn)
532
+
533
+ // Make multiple calls
534
+ await registry.trackCall('tracked', { x: 1 })
535
+ await registry.trackCall('tracked', { x: 2 })
536
+ await registry.trackCall('tracked', { x: 3 })
537
+
538
+ const history = await registry.getCallHistory('tracked')
539
+
540
+ expect(history.length).toBe(3)
541
+ expect(history.every((a) => a.verb === FUNCTION_VERBS.CALL)).toBe(true)
542
+ })
543
+
544
+ it('returns empty array for function with no calls', async () => {
545
+ const fn = defineFunction({
546
+ type: 'generative',
547
+ name: 'unused',
548
+ args: {},
549
+ output: 'string',
550
+ })
551
+ await registry.setAsync('unused', fn)
552
+
553
+ const history = await registry.getCallHistory('unused')
554
+ expect(history).toEqual([])
555
+ })
556
+
557
+ it('returns empty array for non-existent function', async () => {
558
+ const history = await registry.getCallHistory('nonexistent')
559
+ expect(history).toEqual([])
560
+ })
561
+
562
+ it('only returns calls for the specified function', async () => {
563
+ const fn1 = defineFunction({
564
+ type: 'generative',
565
+ name: 'funcA',
566
+ args: {},
567
+ output: 'string',
568
+ })
569
+ const fn2 = defineFunction({
570
+ type: 'generative',
571
+ name: 'funcB',
572
+ args: {},
573
+ output: 'string',
574
+ })
575
+
576
+ await registry.setAsync('funcA', fn1)
577
+ await registry.setAsync('funcB', fn2)
578
+
579
+ await registry.trackCall('funcA', { data: 'a1' })
580
+ await registry.trackCall('funcA', { data: 'a2' })
581
+ await registry.trackCall('funcB', { data: 'b1' })
582
+
583
+ const historyA = await registry.getCallHistory('funcA')
584
+ const historyB = await registry.getCallHistory('funcB')
585
+
586
+ expect(historyA.length).toBe(2)
587
+ expect(historyB.length).toBe(1)
588
+ })
589
+ })
590
+
591
+ describe('getRecentCalls()', () => {
592
+ it('returns recent calls across all functions', async () => {
593
+ const fn1 = defineFunction({
594
+ type: 'generative',
595
+ name: 'recent1',
596
+ args: {},
597
+ output: 'string',
598
+ })
599
+ const fn2 = defineFunction({
600
+ type: 'code',
601
+ name: 'recent2',
602
+ args: {},
603
+ language: 'typescript',
604
+ })
605
+
606
+ await registry.setAsync('recent1', fn1)
607
+ await registry.setAsync('recent2', fn2)
608
+
609
+ await registry.trackCall('recent1', { call: 1 })
610
+ await registry.trackCall('recent2', { call: 2 })
611
+ await registry.trackCall('recent1', { call: 3 })
612
+
613
+ const recentCalls = await registry.getRecentCalls()
614
+
615
+ expect(recentCalls.length).toBe(3)
616
+ expect(recentCalls.every((a) => a.verb === FUNCTION_VERBS.CALL)).toBe(true)
617
+ })
618
+
619
+ it('respects the limit parameter', async () => {
620
+ const fn = defineFunction({
621
+ type: 'generative',
622
+ name: 'limited',
623
+ args: {},
624
+ output: 'string',
625
+ })
626
+ await registry.setAsync('limited', fn)
627
+
628
+ // Make 5 calls
629
+ for (let i = 0; i < 5; i++) {
630
+ await registry.trackCall('limited', { index: i })
631
+ }
632
+
633
+ const limited = await registry.getRecentCalls(3)
634
+ expect(limited.length).toBe(3)
635
+ })
636
+
637
+ it('defaults to 10 results', async () => {
638
+ const fn = defineFunction({
639
+ type: 'generative',
640
+ name: 'many',
641
+ args: {},
642
+ output: 'string',
643
+ })
644
+ await registry.setAsync('many', fn)
645
+
646
+ // Make 15 calls
647
+ for (let i = 0; i < 15; i++) {
648
+ await registry.trackCall('many', { index: i })
649
+ }
650
+
651
+ const recent = await registry.getRecentCalls()
652
+ expect(recent.length).toBe(10)
653
+ })
654
+ })
655
+
656
+ describe('getProvider()', () => {
657
+ it('returns the underlying provider', () => {
658
+ const returnedProvider = registry.getProvider()
659
+ expect(returnedProvider).toBe(provider)
660
+ })
661
+ })
662
+ })
663
+
664
+ describe('DigitalObjectsFunctionRegistry - Edge Cases', () => {
665
+ let provider: DigitalObjectsProvider
666
+ let registry: DigitalObjectsFunctionRegistry
667
+
668
+ beforeEach(async () => {
669
+ provider = createMemoryProvider()
670
+ registry = await createDigitalObjectsRegistry({ provider })
671
+ })
672
+
673
+ it('handles updating an existing function', async () => {
674
+ const fn1 = defineFunction({
675
+ type: 'generative',
676
+ name: 'evolving',
677
+ args: { v: 'version 1' },
678
+ output: 'string',
679
+ })
680
+
681
+ await registry.setAsync('evolving', fn1)
682
+
683
+ // Update with new version
684
+ const fn2 = defineFunction({
685
+ type: 'generative',
686
+ name: 'evolving',
687
+ args: { v: 'version 2', extra: 'new field' },
688
+ output: 'string',
689
+ })
690
+
691
+ await registry.setAsync('evolving', fn2)
692
+
693
+ // Should still have only one Thing
694
+ const things = await provider.find(FUNCTION_NOUNS.GENERATIVE, { name: 'evolving' })
695
+ expect(things.length).toBe(1)
696
+
697
+ // Retrieved function should be the updated one
698
+ const retrieved = registry.get('evolving')
699
+ expect(retrieved).toBe(fn2)
700
+ })
701
+
702
+ it('handles multiple initializations gracefully', async () => {
703
+ // Initialize multiple times - should not throw or duplicate nouns
704
+ await registry.initialize()
705
+ await registry.initialize()
706
+ await registry.initialize()
707
+
708
+ const nouns = await provider.listNouns()
709
+ expect(nouns.length).toBe(4) // Should still be exactly 4
710
+ })
711
+
712
+ it('preserves function type-specific fields', async () => {
713
+ const humanFn = defineFunction({
714
+ type: 'human',
715
+ name: 'approval',
716
+ args: { request: 'Request details' },
717
+ channel: 'email',
718
+ instructions: 'Please review and approve',
719
+ timeout: 86400000,
720
+ assignee: 'manager@example.com',
721
+ })
722
+
723
+ await registry.setAsync('approval', humanFn)
724
+
725
+ // Retrieve from a fresh registry
726
+ const newRegistry = await createDigitalObjectsRegistry({ provider })
727
+ const retrieved = await newRegistry.getAsync('approval')
728
+
729
+ expect(retrieved?.definition.type).toBe('human')
730
+ const def = retrieved?.definition as {
731
+ channel: string
732
+ instructions: string
733
+ timeout: number
734
+ assignee: string
735
+ }
736
+ expect(def.channel).toBe('email')
737
+ expect(def.instructions).toBe('Please review and approve')
738
+ expect(def.timeout).toBe(86400000)
739
+ expect(def.assignee).toBe('manager@example.com')
740
+ })
741
+
742
+ it('tracks define action when creating new function', async () => {
743
+ const fn = defineFunction({
744
+ type: 'generative',
745
+ name: 'newFunc',
746
+ args: {},
747
+ output: 'string',
748
+ })
749
+
750
+ await registry.setAsync('newFunc', fn)
751
+
752
+ // Should have a define action
753
+ const actions = await provider.listActions({ verb: FUNCTION_VERBS.DEFINE })
754
+ expect(actions.length).toBe(1)
755
+ expect(actions[0].data).toMatchObject({
756
+ name: 'newFunc',
757
+ type: 'generative',
758
+ })
759
+ })
760
+ })