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
@@ -768,3 +768,273 @@ describe('Token Usage Tracking', () => {
768
768
  expect(result.usage!.totalTokens).toBe(160)
769
769
  })
770
770
  })
771
+
772
+ // ============================================================================
773
+ // cachedTool Tests - Tool result caching with cleanup
774
+ // ============================================================================
775
+
776
+ import { cachedTool, type CachedTool } from '../src/tool-orchestration.js'
777
+
778
+ describe('cachedTool', () => {
779
+ describe('basic caching behavior', () => {
780
+ it('should cache tool results', async () => {
781
+ let executionCount = 0
782
+ const countingTool: Tool = {
783
+ name: 'counting',
784
+ description: 'Counts executions',
785
+ parameters: z.object({ key: z.string() }),
786
+ execute: async ({ key }) => {
787
+ executionCount++
788
+ return { key, count: executionCount }
789
+ },
790
+ }
791
+
792
+ const cached = cachedTool(countingTool, { ttl: 1000 })
793
+
794
+ // First call executes the tool
795
+ const result1 = await cached.execute({ key: 'test' })
796
+ expect(result1).toEqual({ key: 'test', count: 1 })
797
+
798
+ // Second call with same key returns cached result
799
+ const result2 = await cached.execute({ key: 'test' })
800
+ expect(result2).toEqual({ key: 'test', count: 1 })
801
+ expect(executionCount).toBe(1) // Only executed once
802
+
803
+ // Different key executes again
804
+ const result3 = await cached.execute({ key: 'other' })
805
+ expect(result3).toEqual({ key: 'other', count: 2 })
806
+ })
807
+
808
+ it('should expire cached entries after TTL', async () => {
809
+ vi.useFakeTimers()
810
+ let executionCount = 0
811
+ const countingTool: Tool = {
812
+ name: 'counting',
813
+ description: 'Counts executions',
814
+ parameters: z.object({ key: z.string() }),
815
+ execute: async ({ key }) => {
816
+ executionCount++
817
+ return { key, count: executionCount }
818
+ },
819
+ }
820
+
821
+ const cached = cachedTool(countingTool, { ttl: 100 })
822
+
823
+ // First call
824
+ await cached.execute({ key: 'test' })
825
+ expect(executionCount).toBe(1)
826
+
827
+ // After TTL expires
828
+ vi.advanceTimersByTime(150)
829
+
830
+ // Should execute again since cache expired
831
+ await cached.execute({ key: 'test' })
832
+ expect(executionCount).toBe(2)
833
+
834
+ vi.useRealTimers()
835
+ })
836
+ })
837
+
838
+ describe('cache cleanup', () => {
839
+ it('should periodically clean up expired entries', async () => {
840
+ vi.useFakeTimers()
841
+ let executionCount = 0
842
+ const countingTool: Tool = {
843
+ name: 'counting',
844
+ description: 'Counts executions',
845
+ parameters: z.object({ key: z.string() }),
846
+ execute: async ({ key }) => {
847
+ executionCount++
848
+ return { key, count: executionCount }
849
+ },
850
+ }
851
+
852
+ const cached = cachedTool(countingTool, {
853
+ ttl: 100,
854
+ cleanupIntervalMs: 50,
855
+ }) as CachedTool
856
+
857
+ // Execute to populate cache
858
+ await cached.execute({ key: 'entry1' })
859
+ await cached.execute({ key: 'entry2' })
860
+
861
+ expect(cached.cacheSize()).toBe(2)
862
+
863
+ // Wait for TTL to expire and cleanup to run
864
+ vi.advanceTimersByTime(150)
865
+
866
+ // Entries should be cleaned up automatically
867
+ expect(cached.cacheSize()).toBe(0)
868
+
869
+ // Cleanup timer
870
+ cached.destroy()
871
+ vi.useRealTimers()
872
+ })
873
+
874
+ it('should stop cleanup timer and clear cache when destroyed', async () => {
875
+ vi.useFakeTimers()
876
+ const countingTool: Tool = {
877
+ name: 'counting',
878
+ description: 'Counts executions',
879
+ parameters: z.object({ key: z.string() }),
880
+ execute: async ({ key }) => ({ key }),
881
+ }
882
+
883
+ const cached = cachedTool(countingTool, {
884
+ ttl: 100,
885
+ cleanupIntervalMs: 50,
886
+ }) as CachedTool
887
+
888
+ await cached.execute({ key: 'entry1' })
889
+ expect(cached.cacheSize()).toBe(1)
890
+
891
+ // Destroy stops cleanup timer and clears cache to prevent memory leaks
892
+ cached.destroy()
893
+
894
+ // Cache should be cleared immediately on destroy
895
+ expect(cached.cacheSize()).toBe(0)
896
+
897
+ // Advancing time should have no effect (timer is stopped)
898
+ vi.advanceTimersByTime(150)
899
+ expect(cached.cacheSize()).toBe(0)
900
+
901
+ vi.useRealTimers()
902
+ })
903
+
904
+ it('should clear all cache entries on clearCache()', async () => {
905
+ const countingTool: Tool = {
906
+ name: 'counting',
907
+ description: 'Counts executions',
908
+ parameters: z.object({ key: z.string() }),
909
+ execute: async ({ key }) => ({ key }),
910
+ }
911
+
912
+ const cached = cachedTool(countingTool, { ttl: 60000 }) as CachedTool
913
+
914
+ await cached.execute({ key: 'entry1' })
915
+ await cached.execute({ key: 'entry2' })
916
+ await cached.execute({ key: 'entry3' })
917
+
918
+ expect(cached.cacheSize()).toBe(3)
919
+
920
+ cached.clearCache()
921
+ expect(cached.cacheSize()).toBe(0)
922
+
923
+ cached.destroy()
924
+ })
925
+ })
926
+
927
+ describe('max cache size (LRU eviction)', () => {
928
+ it('should evict oldest entries when maxSize is reached', async () => {
929
+ vi.useFakeTimers()
930
+ let executionCount = 0
931
+ const countingTool: Tool = {
932
+ name: 'counting',
933
+ description: 'Counts executions',
934
+ parameters: z.object({ key: z.string() }),
935
+ execute: async ({ key }) => {
936
+ executionCount++
937
+ return { key, count: executionCount }
938
+ },
939
+ }
940
+
941
+ const cached = cachedTool(countingTool, {
942
+ ttl: 60000,
943
+ maxSize: 3,
944
+ }) as CachedTool
945
+
946
+ // Fill cache to max
947
+ await cached.execute({ key: 'a' })
948
+ vi.advanceTimersByTime(10)
949
+ await cached.execute({ key: 'b' })
950
+ vi.advanceTimersByTime(10)
951
+ await cached.execute({ key: 'c' })
952
+ vi.advanceTimersByTime(10)
953
+
954
+ expect(cached.cacheSize()).toBe(3)
955
+ expect(executionCount).toBe(3)
956
+
957
+ // Adding 4th entry should evict oldest ('a')
958
+ await cached.execute({ key: 'd' })
959
+ expect(cached.cacheSize()).toBe(3)
960
+
961
+ // Accessing 'a' should re-execute since it was evicted
962
+ await cached.execute({ key: 'a' })
963
+ expect(executionCount).toBe(5) // New execution
964
+
965
+ cached.destroy()
966
+ vi.useRealTimers()
967
+ })
968
+
969
+ it('should update LRU order on cache hit', async () => {
970
+ vi.useFakeTimers()
971
+ let executionCount = 0
972
+ const countingTool: Tool = {
973
+ name: 'counting',
974
+ description: 'Counts executions',
975
+ parameters: z.object({ key: z.string() }),
976
+ execute: async ({ key }) => {
977
+ executionCount++
978
+ return { key, count: executionCount }
979
+ },
980
+ }
981
+
982
+ const cached = cachedTool(countingTool, {
983
+ ttl: 60000,
984
+ maxSize: 3,
985
+ }) as CachedTool
986
+
987
+ // Fill cache: a, b, c (oldest to newest)
988
+ await cached.execute({ key: 'a' })
989
+ vi.advanceTimersByTime(10)
990
+ await cached.execute({ key: 'b' })
991
+ vi.advanceTimersByTime(10)
992
+ await cached.execute({ key: 'c' })
993
+ vi.advanceTimersByTime(10)
994
+
995
+ // Access 'a' to make it recently used
996
+ await cached.execute({ key: 'a' }) // Cache hit
997
+ vi.advanceTimersByTime(10)
998
+ expect(executionCount).toBe(3) // No new execution
999
+
1000
+ // Add 'd' - should evict 'b' (now oldest) not 'a'
1001
+ await cached.execute({ key: 'd' })
1002
+
1003
+ // 'b' was evicted, 'a' and 'c' remain
1004
+ await cached.execute({ key: 'b' }) // Should re-execute
1005
+ expect(executionCount).toBe(5)
1006
+
1007
+ await cached.execute({ key: 'a' }) // Still cached
1008
+ expect(executionCount).toBe(5)
1009
+
1010
+ cached.destroy()
1011
+ vi.useRealTimers()
1012
+ })
1013
+ })
1014
+
1015
+ describe('resource cleanup on tool destruction', () => {
1016
+ it('should clean up timers and memory when destroy is called', async () => {
1017
+ const tool: Tool = {
1018
+ name: 'test',
1019
+ description: 'Test tool',
1020
+ parameters: z.object({ key: z.string() }),
1021
+ execute: async ({ key }) => ({ key }),
1022
+ }
1023
+
1024
+ const cached = cachedTool(tool, {
1025
+ ttl: 1000,
1026
+ cleanupIntervalMs: 100,
1027
+ }) as CachedTool
1028
+
1029
+ await cached.execute({ key: 'test' })
1030
+ expect(cached.cacheSize()).toBe(1)
1031
+
1032
+ cached.destroy()
1033
+ expect(cached.cacheSize()).toBe(0)
1034
+
1035
+ // Should still work after destroy but without caching
1036
+ await cached.execute({ key: 'test2' })
1037
+ expect(cached.cacheSize()).toBe(0)
1038
+ })
1039
+ })
1040
+ })