ai-functions 2.1.3 → 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 (277) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -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 +116 -0
  93. package/dist/function-registry.d.ts.map +1 -0
  94. package/dist/function-registry.js +546 -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/schema.js +2 -2
  137. package/dist/schema.js.map +1 -1
  138. package/dist/telemetry.d.ts +128 -0
  139. package/dist/telemetry.d.ts.map +1 -0
  140. package/dist/telemetry.js +285 -0
  141. package/dist/telemetry.js.map +1 -0
  142. package/dist/template.d.ts.map +1 -1
  143. package/dist/template.js +6 -1
  144. package/dist/template.js.map +1 -1
  145. package/dist/tool-orchestration.d.ts +66 -4
  146. package/dist/tool-orchestration.d.ts.map +1 -1
  147. package/dist/tool-orchestration.js +123 -23
  148. package/dist/tool-orchestration.js.map +1 -1
  149. package/dist/type-guards.d.ts +28 -0
  150. package/dist/type-guards.d.ts.map +1 -0
  151. package/dist/type-guards.js +29 -0
  152. package/dist/type-guards.js.map +1 -0
  153. package/dist/types.d.ts +135 -17
  154. package/dist/types.d.ts.map +1 -1
  155. package/dist/types.js +36 -1
  156. package/dist/types.js.map +1 -1
  157. package/dist/wrap-for-v3.d.ts +80 -0
  158. package/dist/wrap-for-v3.d.ts.map +1 -0
  159. package/dist/wrap-for-v3.js +89 -0
  160. package/dist/wrap-for-v3.js.map +1 -0
  161. package/examples/00-quickstart.ts +232 -0
  162. package/examples/01-rag-chatbot.ts +212 -0
  163. package/examples/02-multi-agent-research.ts +290 -0
  164. package/examples/03-email-classification.ts +379 -0
  165. package/examples/04-content-moderation.ts +400 -0
  166. package/examples/05-document-extraction.ts +455 -0
  167. package/examples/06-streaming-chat-nextjs.ts +437 -0
  168. package/examples/07-cloudflare-worker.ts +483 -0
  169. package/examples/08-batch-processing.ts +491 -0
  170. package/examples/09-budget-constrained.ts +527 -0
  171. package/examples/10-tool-orchestration.ts +565 -0
  172. package/examples/11-retry-resilience.ts +403 -0
  173. package/examples/12-caching-strategies.ts +422 -0
  174. package/examples/README.md +145 -0
  175. package/package.json +28 -25
  176. package/src/ai-promise.ts +226 -140
  177. package/src/ai-schemas.ts +122 -0
  178. package/src/ai.ts +69 -1176
  179. package/src/batch/anthropic.ts +96 -161
  180. package/src/batch/bedrock.ts +203 -454
  181. package/src/batch/cloudflare.ts +99 -282
  182. package/src/batch/google.ts +91 -297
  183. package/src/batch/index.ts +4 -1
  184. package/src/batch/memory.ts +15 -10
  185. package/src/batch/openai.ts +65 -193
  186. package/src/batch/provider.ts +336 -0
  187. package/src/batch-map.ts +29 -24
  188. package/src/batch-queue.ts +200 -11
  189. package/src/budget.ts +31 -18
  190. package/src/cache.ts +45 -17
  191. package/src/context.ts +106 -77
  192. package/src/digital-objects-registry.ts +750 -0
  193. package/src/errors.ts +37 -0
  194. package/src/eval/runner.ts +60 -36
  195. package/src/eval-log/in-memory.ts +90 -0
  196. package/src/eval-log/index.ts +46 -0
  197. package/src/eval-log/types.ts +110 -0
  198. package/src/function-registry.ts +671 -0
  199. package/src/generate.ts +33 -28
  200. package/src/index.ts +119 -21
  201. package/src/logger.ts +232 -0
  202. package/src/middleware/budget.ts +171 -0
  203. package/src/middleware/cache.ts +299 -0
  204. package/src/middleware/embed-cache.ts +195 -0
  205. package/src/middleware/index.ts +23 -0
  206. package/src/middleware/trace.ts +248 -0
  207. package/src/primitives.ts +589 -62
  208. package/src/retry.ts +144 -18
  209. package/src/schema.ts +8 -8
  210. package/src/telemetry.ts +403 -0
  211. package/src/template.ts +8 -4
  212. package/src/tool-orchestration.ts +213 -48
  213. package/src/type-guards.ts +31 -0
  214. package/src/types.ts +164 -25
  215. package/src/wrap-for-v3.ts +105 -0
  216. package/test/ai-promise.test.ts +1080 -0
  217. package/test/ai-proxy.test.ts +1 -1
  218. package/test/batch-autosubmit-errors.test.ts +49 -37
  219. package/test/batch-blog-posts.test.ts +87 -129
  220. package/test/core-functions.test.ts +183 -579
  221. package/test/decide.test.ts +154 -322
  222. package/test/define.test.ts +211 -8
  223. package/test/digital-objects-registry.test.ts +760 -0
  224. package/test/embedding-cache-middleware.test.ts +140 -0
  225. package/test/generate-core.test.ts +140 -229
  226. package/test/implicit-batch.test.ts +22 -65
  227. package/test/retry-policy-integration.test.ts +117 -0
  228. package/test/schema.test.ts +55 -19
  229. package/test/template.test.ts +1164 -0
  230. package/test/tool-orchestration.test.ts +270 -0
  231. package/test/wrap-for-v3.test.ts +612 -0
  232. package/vitest.config.js +6 -0
  233. package/vitest.config.ts +20 -0
  234. package/LICENSE +0 -21
  235. package/dist/rpc/auth.d.ts +0 -69
  236. package/dist/rpc/auth.d.ts.map +0 -1
  237. package/dist/rpc/auth.js +0 -136
  238. package/dist/rpc/auth.js.map +0 -1
  239. package/dist/rpc/client.d.ts +0 -62
  240. package/dist/rpc/client.d.ts.map +0 -1
  241. package/dist/rpc/client.js +0 -103
  242. package/dist/rpc/client.js.map +0 -1
  243. package/dist/rpc/deferred.d.ts +0 -60
  244. package/dist/rpc/deferred.d.ts.map +0 -1
  245. package/dist/rpc/deferred.js +0 -96
  246. package/dist/rpc/deferred.js.map +0 -1
  247. package/dist/rpc/index.d.ts +0 -22
  248. package/dist/rpc/index.d.ts.map +0 -1
  249. package/dist/rpc/index.js +0 -38
  250. package/dist/rpc/index.js.map +0 -1
  251. package/dist/rpc/local.d.ts +0 -42
  252. package/dist/rpc/local.d.ts.map +0 -1
  253. package/dist/rpc/local.js +0 -50
  254. package/dist/rpc/local.js.map +0 -1
  255. package/dist/rpc/server.d.ts +0 -165
  256. package/dist/rpc/server.d.ts.map +0 -1
  257. package/dist/rpc/server.js +0 -405
  258. package/dist/rpc/server.js.map +0 -1
  259. package/dist/rpc/session.d.ts +0 -32
  260. package/dist/rpc/session.d.ts.map +0 -1
  261. package/dist/rpc/session.js +0 -43
  262. package/dist/rpc/session.js.map +0 -1
  263. package/dist/rpc/transport.d.ts +0 -306
  264. package/dist/rpc/transport.d.ts.map +0 -1
  265. package/dist/rpc/transport.js +0 -731
  266. package/dist/rpc/transport.js.map +0 -1
  267. package/src/batch/anthropic.js +0 -256
  268. package/src/batch/bedrock.js +0 -584
  269. package/src/batch/cloudflare.js +0 -287
  270. package/src/batch/google.js +0 -359
  271. package/src/batch/index.js +0 -30
  272. package/src/batch/memory.js +0 -187
  273. package/src/batch/openai.js +0 -402
  274. package/src/eval/index.js +0 -7
  275. package/src/eval/models.js +0 -119
  276. package/src/eval/runner.js +0 -147
  277. package/test/schema.test.js +0 -96
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Function Registry - Storage and management of defined AI functions
3
+ *
4
+ * This module provides the registry for storing and retrieving defined functions,
5
+ * including the global registry and factory for creating isolated registries.
6
+ */
7
+
8
+ import { generateObject } from './generate.js'
9
+ import type { SimpleSchema } from './schema.js'
10
+ import type {
11
+ AIFunctionDefinition,
12
+ JSONSchema,
13
+ FunctionDefinition,
14
+ DefinedFunction,
15
+ CodeFunctionDefinition,
16
+ CodeGenerationDefinition,
17
+ GenerativeFunctionDefinition,
18
+ AgenticFunctionDefinition,
19
+ HumanFunctionDefinition,
20
+ HumanFunctionPending,
21
+ FunctionRegistry,
22
+ } from './types.js'
23
+ import { PENDING_HUMAN_RESULT_SYMBOL } from './types.js'
24
+ import { schema as convertSchema, type SimpleSchema as SimpleSchemaType } from './schema.js'
25
+ import { getLogger } from './logger.js'
26
+
27
+ // ============================================================================
28
+ // JSON Schema Conversion
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Convert args schema to JSON Schema
33
+ */
34
+ export function convertArgsToJSONSchema(args: unknown): JSONSchema {
35
+ // If it's already a JSON schema-like object
36
+ if (typeof args === 'object' && args !== null && 'type' in args) {
37
+ return args as JSONSchema
38
+ }
39
+
40
+ // Convert SimpleSchema to JSON Schema
41
+ const properties: Record<string, JSONSchema> = {}
42
+ const required: string[] = []
43
+
44
+ if (typeof args === 'object' && args !== null) {
45
+ for (const [key, value] of Object.entries(args as Record<string, unknown>)) {
46
+ required.push(key) // All properties required for cross-provider compatibility
47
+ properties[key] = convertValueToJSONSchema(value)
48
+ }
49
+ }
50
+
51
+ return {
52
+ type: 'object',
53
+ properties,
54
+ required,
55
+ additionalProperties: false, // Required for OpenAI compatibility
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Convert a single value to JSON Schema
61
+ */
62
+ function convertValueToJSONSchema(value: unknown): JSONSchema {
63
+ if (typeof value === 'string') {
64
+ // Check for type hints: 'description (number)', 'description (boolean)', etc.
65
+ const typeMatch = value.match(/^(.+?)\s*\((number|boolean|integer|date)\)$/i)
66
+ if (typeMatch) {
67
+ const description = typeMatch[1]!
68
+ const type = typeMatch[2]!
69
+ switch (type.toLowerCase()) {
70
+ case 'number':
71
+ return { type: 'number', description: description.trim() }
72
+ case 'integer':
73
+ return { type: 'integer', description: description.trim() }
74
+ case 'boolean':
75
+ return { type: 'boolean', description: description.trim() }
76
+ case 'date':
77
+ return { type: 'string', format: 'date-time', description: description.trim() }
78
+ }
79
+ }
80
+
81
+ // Check for enum: 'option1 | option2 | option3'
82
+ if (value.includes(' | ')) {
83
+ const options = value.split(' | ').map((s) => s.trim())
84
+ return { type: 'string', enum: options }
85
+ }
86
+
87
+ return { type: 'string', description: value }
88
+ }
89
+
90
+ if (Array.isArray(value) && value.length === 1) {
91
+ const [desc] = value
92
+ if (typeof desc === 'string') {
93
+ return { type: 'array', items: { type: 'string' }, description: desc }
94
+ }
95
+ if (typeof desc === 'number') {
96
+ return { type: 'array', items: { type: 'number' } }
97
+ }
98
+ return { type: 'array', items: convertValueToJSONSchema(desc) }
99
+ }
100
+
101
+ if (typeof value === 'object' && value !== null) {
102
+ return convertArgsToJSONSchema(value)
103
+ }
104
+
105
+ return { type: 'string' }
106
+ }
107
+
108
+ // ============================================================================
109
+ // Template Utilities
110
+ // ============================================================================
111
+
112
+ /**
113
+ * Fill template with values
114
+ */
115
+ export function fillTemplate(template: string, args: Record<string, unknown>): string {
116
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => String(args[key] ?? ''))
117
+ }
118
+
119
+ // ============================================================================
120
+ // Function Executors
121
+ // ============================================================================
122
+
123
+ /**
124
+ * Execute a **deterministic** code function.
125
+ *
126
+ * `Code` is the deterministic kind: no model is consulted at call time. This
127
+ * invokes the definition's `handler` (canonical) or, if only an inline `code`
128
+ * body was supplied, deterministically compiles and runs it. The result is
129
+ * returned directly.
130
+ *
131
+ * This is a deliberate change from the previous behavior, where `type: 'code'`
132
+ * LLM-generated source at call time. That code-*authoring* behavior now lives
133
+ * in {@link generateCode} (and the `generate('code', …)` primitive), so that
134
+ * `Code` can carry the "deterministic handler" contract consumers depend on
135
+ * (ADR-0033). See the package README migration note.
136
+ *
137
+ * @throws if neither `handler` nor `code` is provided, or if an inline `code`
138
+ * body is in a non-evaluable language.
139
+ */
140
+ async function executeCodeFunction<TOutput, TInput>(
141
+ definition: CodeFunctionDefinition<TOutput, TInput>,
142
+ args: TInput
143
+ ): Promise<TOutput> {
144
+ const { handler, code, language = 'typescript', name } = definition
145
+
146
+ if (typeof handler === 'function') {
147
+ return await handler(args)
148
+ }
149
+
150
+ if (typeof code === 'string' && code.length > 0) {
151
+ return await runInlineCode<TOutput, TInput>(code, args, language, name)
152
+ }
153
+
154
+ throw new Error(
155
+ `Code function '${name}' has no handler or inline code. ` +
156
+ `'code' functions are deterministic and require a handler: (input) => output ` +
157
+ `(or an inline 'code' body). To have a model *author* code instead, use ` +
158
+ `generateCode() or define a 'generative' function.`
159
+ )
160
+ }
161
+
162
+ /**
163
+ * Deterministically compile and run an inline `code` body for a Code function.
164
+ *
165
+ * The body is treated as a function whose single parameter is the parsed
166
+ * `args` object and whose `return` value is the result. Only the
167
+ * JS/TS-compatible languages can be evaluated in-process; other languages are
168
+ * carried as metadata for an external runtime and are rejected here.
169
+ *
170
+ * No model is involved — this is a pure `new Function(...)` compile of the
171
+ * supplied source, so the same body always produces the same behavior.
172
+ */
173
+ async function runInlineCode<TOutput, TInput>(
174
+ code: string,
175
+ args: TInput,
176
+ language: string,
177
+ name: string
178
+ ): Promise<TOutput> {
179
+ if (language !== 'typescript' && language !== 'javascript') {
180
+ throw new Error(
181
+ `Code function '${name}' has an inline 'code' body in language '${language}', ` +
182
+ `which cannot be evaluated in-process. Pass a 'handler' instead, or run it ` +
183
+ `in an external deterministic runtime.`
184
+ )
185
+ }
186
+
187
+ const body = /\breturn\b/.test(code) ? code : `return (${code})`
188
+ let fn: (input: TInput) => TOutput | Promise<TOutput>
189
+ try {
190
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
191
+ fn = new Function('args', `"use strict";\n${body}`) as (
192
+ input: TInput
193
+ ) => TOutput | Promise<TOutput>
194
+ } catch (e) {
195
+ throw new Error(
196
+ `Code function '${name}' has an invalid inline 'code' body: ${(e as Error).message}`
197
+ )
198
+ }
199
+ return await fn(args)
200
+ }
201
+
202
+ /**
203
+ * Author code with a model — the explicit, opt-in code-*generation* path.
204
+ *
205
+ * This is the behavior `type: 'code'` used to have implicitly at call time.
206
+ * It has been split out so that `Code` functions can be deterministic. Calling
207
+ * this **does** consult a model and returns the generated source as a string;
208
+ * it does not produce a deterministic, repeatable handler.
209
+ *
210
+ * @param definition - The code-authoring spec ({@link CodeGenerationDefinition})
211
+ * @param args - Concrete inputs / refinements for the requested code
212
+ * @returns The generated source code as a string
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * import { generateCode } from 'ai-functions'
217
+ *
218
+ * const src = await generateCode(
219
+ * { name: 'calculateTax', args: { amount: '(number)', rate: '(number)' }, language: 'typescript' },
220
+ * { amount: 100, rate: 0.2 }
221
+ * )
222
+ * ```
223
+ */
224
+ export async function generateCode<TInput>(
225
+ definition: CodeGenerationDefinition<TInput>,
226
+ args?: TInput
227
+ ): Promise<string> {
228
+ const { name, description, language = 'typescript', instructions, returnType, model = 'sonnet' } =
229
+ definition
230
+
231
+ const argsDescription = JSON.stringify(args ?? definition.args, null, 2)
232
+
233
+ const result = await generateObject({
234
+ model,
235
+ schema: {
236
+ code: `The complete ${language} implementation code. Output ONLY the raw code without markdown formatting or code blocks.`,
237
+ },
238
+ system: `You are an expert ${language} developer. Generate clean, well-documented, production-ready code. Output ONLY the code itself, without any markdown code fences or language tags.`,
239
+ prompt: `Generate a ${language} function/query with the following specification:
240
+
241
+ Name: ${name}
242
+ Description: ${description || 'No description provided'}
243
+ Arguments: ${argsDescription}
244
+ Return Type: ${JSON.stringify(returnType)}
245
+
246
+ ${instructions ? `Additional Instructions: ${instructions}` : ''}
247
+
248
+ Requirements:
249
+ - Include appropriate comments/documentation
250
+ - Follow best practices for ${language}
251
+ - Handle edge cases appropriately
252
+ - Return ONLY the code without markdown formatting`,
253
+ })
254
+
255
+ return (result.object as { code: string }).code
256
+ }
257
+
258
+ /**
259
+ * Execute a generative function - uses AI to generate content
260
+ */
261
+ async function executeGenerativeFunction<TOutput, TInput>(
262
+ definition: GenerativeFunctionDefinition<TOutput, TInput>,
263
+ args: TInput
264
+ ): Promise<TOutput> {
265
+ const { output, system, promptTemplate, model = 'sonnet', temperature, returnType } = definition
266
+
267
+ const prompt = promptTemplate
268
+ ? fillTemplate(promptTemplate, args as Record<string, unknown>)
269
+ : JSON.stringify(args)
270
+
271
+ switch (output) {
272
+ case 'string': {
273
+ const result = await generateObject({
274
+ model,
275
+ schema: { text: 'The generated text response' },
276
+ prompt,
277
+ ...(system !== undefined && { system }),
278
+ ...(temperature !== undefined && { temperature }),
279
+ })
280
+ return (result.object as { text: string }).text as TOutput
281
+ }
282
+
283
+ case 'object': {
284
+ const objectSchema = returnType || { result: 'The generated result' }
285
+ const result = await generateObject({
286
+ model,
287
+ schema: objectSchema as SimpleSchemaType,
288
+ prompt,
289
+ ...(system !== undefined && { system }),
290
+ ...(temperature !== undefined && { temperature }),
291
+ })
292
+ return result.object as TOutput
293
+ }
294
+
295
+ case 'image':
296
+ throw new Error(
297
+ 'Image generation via generative functions is not yet implemented. ' +
298
+ 'Use the image() primitive directly instead.'
299
+ )
300
+
301
+ case 'video':
302
+ throw new Error(
303
+ 'Video generation via generative functions is not yet implemented. ' +
304
+ 'Use the video() primitive directly instead.'
305
+ )
306
+
307
+ default:
308
+ throw new Error(`Unknown output type: ${output}`)
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Execute an agentic function - runs in a loop with tools
314
+ */
315
+ async function executeAgenticFunction<TOutput, TInput>(
316
+ definition: AgenticFunctionDefinition<TOutput, TInput>,
317
+ args: TInput
318
+ ): Promise<TOutput> {
319
+ const {
320
+ instructions,
321
+ promptTemplate,
322
+ tools = [],
323
+ maxIterations = 10,
324
+ model = 'sonnet',
325
+ returnType,
326
+ } = definition
327
+
328
+ const prompt = promptTemplate
329
+ ? fillTemplate(promptTemplate, args as Record<string, unknown>)
330
+ : JSON.stringify(args)
331
+
332
+ // Build system prompt with tool descriptions
333
+ const toolDescriptions = tools.map((t) => `- ${t.name}: ${t.description}`).join('\n')
334
+ const systemPrompt = `${instructions}
335
+
336
+ Available tools:
337
+ ${toolDescriptions || 'No tools available'}
338
+
339
+ Work step by step to accomplish the task. When you have completed the task, provide your final result.`
340
+
341
+ let iteration = 0
342
+ const toolResults: unknown[] = []
343
+
344
+ // Simple agent loop
345
+ while (iteration < maxIterations) {
346
+ iteration++
347
+
348
+ const result = await generateObject({
349
+ model,
350
+ schema: {
351
+ thinking: 'Your step-by-step reasoning',
352
+ toolCall: {
353
+ name: 'Tool to call (or "done" if finished)',
354
+ arguments: 'Arguments for the tool as JSON string',
355
+ },
356
+ finalResult: returnType || 'The final result if done',
357
+ },
358
+ system: systemPrompt,
359
+ prompt: `Task: ${prompt}
360
+
361
+ Previous tool results:
362
+ ${toolResults.map((r, i) => `Step ${i + 1}: ${JSON.stringify(r)}`).join('\n') || 'None yet'}
363
+
364
+ What is your next step?`,
365
+ })
366
+
367
+ const response = result.object as {
368
+ thinking: string
369
+ toolCall: { name: string; arguments: string }
370
+ finalResult: unknown
371
+ }
372
+
373
+ if (response.toolCall.name === 'done' || response.finalResult) {
374
+ return response.finalResult as TOutput
375
+ }
376
+
377
+ // Execute tool call
378
+ const tool = tools.find((t) => t.name === response.toolCall.name)
379
+ if (tool) {
380
+ let toolArgs: Record<string, unknown>
381
+ try {
382
+ toolArgs = JSON.parse(response.toolCall.arguments || '{}')
383
+ } catch (e) {
384
+ toolResults.push({
385
+ error: `Invalid tool arguments: ${(e as Error).message}`,
386
+ })
387
+ continue
388
+ }
389
+ const toolResult = await tool.handler(toolArgs)
390
+ toolResults.push({ tool: response.toolCall.name, result: toolResult })
391
+ } else {
392
+ toolResults.push({ error: `Tool not found: ${response.toolCall.name}` })
393
+ }
394
+ }
395
+
396
+ throw new Error(`Agent exceeded maximum iterations (${maxIterations})`)
397
+ }
398
+
399
+ /**
400
+ * Execute a human function - generates UI and waits for human input
401
+ *
402
+ * **Note: This function currently returns a pending placeholder.**
403
+ *
404
+ * In a complete implementation, this function would:
405
+ * 1. Generate channel-specific UI (Slack blocks, email templates, web forms, etc.)
406
+ * 2. Send the generated UI to the appropriate channel
407
+ * 3. Wait for human response with optional timeout
408
+ * 4. Validate and return the human's response
409
+ *
410
+ * The current implementation generates the UI artifacts but returns a pending
411
+ * placeholder instead of actually sending to the channel and waiting for response.
412
+ * This allows testing the UI generation without requiring actual channel integrations.
413
+ *
414
+ * **Important:** Use `isPendingHumanResult()` to check if the result is pending
415
+ * before attempting to use it as the expected output type.
416
+ *
417
+ * @param definition - The human function definition with channel and instructions
418
+ * @param args - Arguments to pass to the function
419
+ * @returns Either the actual TOutput from human input, or a HumanFunctionPending placeholder
420
+ *
421
+ * @example
422
+ * ```ts
423
+ * import { isPendingHumanResult } from 'ai-functions'
424
+ *
425
+ * const result = await approveRefund({ amount: 500 })
426
+ *
427
+ * if (isPendingHumanResult(result)) {
428
+ * // Handle pending state
429
+ * console.log('Awaiting human approval via:', result.channel)
430
+ * return { status: 'pending' }
431
+ * }
432
+ *
433
+ * // result is the actual approval response
434
+ * console.log('Approved:', result.approved)
435
+ * ```
436
+ */
437
+ async function executeHumanFunction<TOutput, TInput>(
438
+ definition: HumanFunctionDefinition<TOutput, TInput>,
439
+ args: TInput
440
+ ): Promise<TOutput | HumanFunctionPending<TOutput>> {
441
+ const { channel, instructions, promptTemplate, returnType } = definition
442
+
443
+ const prompt = promptTemplate
444
+ ? fillTemplate(promptTemplate, args as Record<string, unknown>)
445
+ : JSON.stringify(args)
446
+
447
+ // Generate channel-specific UI
448
+ const uiSchema: Record<string, SimpleSchemaType> = {
449
+ // New HumanChannel types
450
+ chat: {
451
+ message: 'Chat message to send',
452
+ options: ['Response options if applicable'],
453
+ },
454
+ email: {
455
+ subject: 'Email subject',
456
+ html: 'Email HTML body',
457
+ text: 'Plain text fallback',
458
+ },
459
+ phone: {
460
+ script: 'Phone call script',
461
+ keyPoints: ['Key points to cover'],
462
+ },
463
+ sms: {
464
+ text: 'SMS message text (max 160 chars)',
465
+ },
466
+ workspace: {
467
+ blocks: ['Workspace/Slack BlockKit blocks as JSON array'],
468
+ text: 'Plain text fallback',
469
+ },
470
+ web: {
471
+ component: 'React component code for the form',
472
+ schema: 'JSON schema for the form fields',
473
+ },
474
+ // Legacy fallback
475
+ custom: {
476
+ data: 'Structured data for custom implementation',
477
+ instructions: 'Instructions for the human',
478
+ },
479
+ }
480
+
481
+ const result = await generateObject({
482
+ model: 'sonnet',
483
+ schema: uiSchema[channel] ?? uiSchema['custom'],
484
+ system: `Generate ${channel} UI/content for a human-in-the-loop task.`,
485
+ prompt: `Task: ${instructions}
486
+
487
+ Input data:
488
+ ${prompt}
489
+
490
+ Expected response format:
491
+ ${JSON.stringify(returnType)}
492
+
493
+ Generate the appropriate ${channel} UI/content to collect this response from a human.`,
494
+ })
495
+
496
+ // Runtime warning for developers
497
+ getLogger().warn(
498
+ `[HumanFunction] Returning pending placeholder for channel '${channel}'. ` +
499
+ `Use isPendingHumanResult() to check before using the result. ` +
500
+ `Full channel integration is not yet implemented.`
501
+ )
502
+
503
+ // Return a properly typed pending result
504
+ // The symbol marker allows isPendingHumanResult() to reliably identify this
505
+ const pendingResult: HumanFunctionPending<TOutput> = {
506
+ [PENDING_HUMAN_RESULT_SYMBOL]: true,
507
+ _pending: true,
508
+ channel,
509
+ artifacts: result.object,
510
+ expectedResponseType: returnType as TOutput,
511
+ }
512
+
513
+ return pendingResult
514
+ }
515
+
516
+ // ============================================================================
517
+ // Defined Function Creation
518
+ // ============================================================================
519
+
520
+ /**
521
+ * Create a defined function from a function definition
522
+ */
523
+ export function createDefinedFunction<TOutput, TInput>(
524
+ definition: FunctionDefinition<TOutput, TInput>
525
+ ): DefinedFunction<TOutput, TInput> {
526
+ const call = async (args: TInput): Promise<TOutput> => {
527
+ switch (definition.type) {
528
+ case 'code':
529
+ return executeCodeFunction(definition, args) as Promise<TOutput>
530
+ case 'generative':
531
+ return executeGenerativeFunction(definition, args) as Promise<TOutput>
532
+ case 'agentic':
533
+ return executeAgenticFunction(definition, args) as Promise<TOutput>
534
+ case 'human':
535
+ return executeHumanFunction(definition, args) as Promise<TOutput>
536
+ default:
537
+ throw new Error(`Unknown function type: ${(definition as FunctionDefinition).type}`)
538
+ }
539
+ }
540
+
541
+ const asTool = (): AIFunctionDefinition<TOutput, TInput> => {
542
+ return {
543
+ name: definition.name,
544
+ description: definition.description || `Execute ${definition.name}`,
545
+ parameters: convertArgsToJSONSchema(definition.args),
546
+ handler: call,
547
+ }
548
+ }
549
+
550
+ return { definition, call, asTool }
551
+ }
552
+
553
+ /**
554
+ * Standalone function for defining AI functions
555
+ *
556
+ * @example
557
+ * ```ts
558
+ * import { defineFunction } from 'ai-functions'
559
+ *
560
+ * const summarize = defineFunction({
561
+ * type: 'generative',
562
+ * name: 'summarize',
563
+ * args: { text: 'Text to summarize' },
564
+ * output: 'string',
565
+ * promptTemplate: 'Summarize: {{text}}',
566
+ * })
567
+ *
568
+ * const result = await summarize.call({ text: 'Long article...' })
569
+ * ```
570
+ */
571
+ export function defineFunction<TOutput, TInput>(
572
+ definition: FunctionDefinition<TOutput, TInput>
573
+ ): DefinedFunction<TOutput, TInput> {
574
+ return createDefinedFunction(definition)
575
+ }
576
+
577
+ // ============================================================================
578
+ // Function Registry Implementation
579
+ // ============================================================================
580
+
581
+ /**
582
+ * In-memory function registry
583
+ */
584
+ class InMemoryFunctionRegistry implements FunctionRegistry {
585
+ private functions = new Map<string, DefinedFunction>()
586
+
587
+ get(name: string): DefinedFunction | undefined {
588
+ return this.functions.get(name)
589
+ }
590
+
591
+ set(name: string, fn: DefinedFunction): void {
592
+ this.functions.set(name, fn)
593
+ }
594
+
595
+ has(name: string): boolean {
596
+ return this.functions.has(name)
597
+ }
598
+
599
+ list(): string[] {
600
+ return Array.from(this.functions.keys())
601
+ }
602
+
603
+ delete(name: string): boolean {
604
+ return this.functions.delete(name)
605
+ }
606
+
607
+ clear(): void {
608
+ this.functions.clear()
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Factory function to create a new isolated function registry instance.
614
+ *
615
+ * Use this when you need:
616
+ * - Test isolation: Each test can have its own registry
617
+ * - Scoped registries: Different parts of an app can have separate registries
618
+ * - Custom lifecycle management: Control when registries are created/destroyed
619
+ *
620
+ * @example
621
+ * ```ts
622
+ * // Create isolated registry for tests
623
+ * const registry = createFunctionRegistry()
624
+ * const fn = defineFunction({ ... })
625
+ * registry.set('myFunc', fn)
626
+ *
627
+ * // Later, registry can be discarded without affecting global state
628
+ * ```
629
+ *
630
+ * @returns A new FunctionRegistry instance
631
+ */
632
+ export function createFunctionRegistry(): FunctionRegistry {
633
+ return new InMemoryFunctionRegistry()
634
+ }
635
+
636
+ /**
637
+ * Global function registry
638
+ *
639
+ * Note: This is in-memory only. For persistence, use mdxai or mdxdb packages.
640
+ *
641
+ * **Lifecycle:**
642
+ * - Created once at module load time
643
+ * - Shared across the entire application
644
+ * - Use `resetGlobalRegistry()` in tests to clear state between test runs
645
+ * - For isolated registries, use `createFunctionRegistry()` instead
646
+ */
647
+ export const functions: FunctionRegistry = new InMemoryFunctionRegistry()
648
+
649
+ /**
650
+ * Reset the global function registry to a clean state.
651
+ *
652
+ * **Important:** This is primarily intended for test cleanup to ensure
653
+ * test isolation. In production code, prefer using `createFunctionRegistry()`
654
+ * for isolated registries.
655
+ *
656
+ * @example
657
+ * ```ts
658
+ * // In test setup/teardown
659
+ * beforeEach(() => {
660
+ * resetGlobalRegistry()
661
+ * })
662
+ *
663
+ * // Or after each test
664
+ * afterEach(() => {
665
+ * resetGlobalRegistry()
666
+ * })
667
+ * ```
668
+ */
669
+ export function resetGlobalRegistry(): void {
670
+ functions.clear()
671
+ }