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
package/src/ai.ts CHANGED
@@ -1,112 +1,83 @@
1
1
  /**
2
2
  * AI() and ai() - Core AI function constructors
3
3
  *
4
- * These provide the main entry points for AI-powered functions,
5
- * with full RPC promise pipelining support via rpc.do.
4
+ * This module is a re-export layer that delegates to focused modules:
5
+ * - ai-schemas.ts - Schema-based AI function generation
6
+ * - function-registry.ts - Function definition and registry
7
+ * - primitives.ts - Auto-define proxy functionality (`define`, `createSmartAI`, `aiProxy`)
8
+ *
9
+ * All exports are maintained for backward compatibility.
6
10
  */
7
11
 
8
- // TODO: Re-enable RPC imports when rpc.do is published
9
- // import { RPC, http, ws, type RPCProxy, type RPCPromise as RpcPromiseType } from 'rpc.do'
12
+ // ============================================================================
13
+ // Re-export from ai-schemas.ts - Schema-based AI functions
14
+ // ============================================================================
15
+ export {
16
+ createSchemaFunctions,
17
+ type AISchemaOptions,
18
+ type SchemaFunctions,
19
+ type InferSimpleSchemaResult,
20
+ } from './ai-schemas.js'
10
21
 
11
- /**
12
- * Base class for RPC service targets
13
- * This is a placeholder for services that want to expose methods over RPC
14
- */
15
- export abstract class RpcTarget {
16
- // Subclasses implement methods that will be exposed over RPC
17
- }
22
+ // ============================================================================
23
+ // Re-export from function-registry.ts - Function definition and registry
24
+ // ============================================================================
25
+ export {
26
+ defineFunction,
27
+ createDefinedFunction,
28
+ createFunctionRegistry,
29
+ functions,
30
+ resetGlobalRegistry,
31
+ convertArgsToJSONSchema,
32
+ fillTemplate,
33
+ generateCode,
34
+ } from './function-registry.js'
18
35
 
19
- /**
20
- * Options for RPC session (connection to remote AI service)
21
- */
22
- export interface RPCSessionOptions {
23
- /** WebSocket URL for RPC connection */
24
- wsUrl?: string
25
- /** HTTP URL for RPC fallback */
26
- httpUrl?: string
27
- /** Authentication token */
28
- token?: string
29
- }
30
- import { generateObject } from './generate.js'
31
- import type { SimpleSchema } from './schema.js'
32
- import type { LanguageModel } from 'ai'
33
- import type {
34
- AIClient,
35
- AIFunctionDefinition,
36
- AIGenerateOptions,
37
- AIGenerateResult,
38
- JSONSchema,
39
- ImageOptions,
40
- ImageResult,
41
- VideoOptions,
42
- VideoResult,
43
- WriteOptions,
44
- ListResult,
45
- ListsResult,
46
- FunctionDefinition,
47
- DefinedFunction,
48
- CodeFunctionDefinition,
49
- CodeFunctionResult,
50
- GenerativeFunctionDefinition,
51
- AgenticFunctionDefinition,
52
- HumanFunctionDefinition,
53
- HumanChannel,
54
- CodeLanguage,
55
- FunctionRegistry,
56
- AutoDefineResult
57
- } from './types.js'
58
- import { schema as convertSchema, type SimpleSchema as SimpleSchemaType } from './schema.js'
36
+ // ============================================================================
37
+ // Re-export from primitives.ts - Auto-define proxy functionality
38
+ // (formerly in ai-proxy.ts; inlined to give property-access tracking a single
39
+ // owner in ai-promise.ts)
40
+ // ============================================================================
41
+ export { define, createSmartAI, aiProxy as ai, type AIProxy } from './primitives.js'
59
42
 
60
- /**
61
- * Options for creating an AI RPC client instance
62
- */
63
- export interface AIClientOptions extends RPCSessionOptions {
64
- /** Default model to use */
65
- model?: string
66
- /** Default temperature */
67
- temperature?: number
68
- /** Default max tokens */
69
- maxTokens?: number
70
- /** Custom functions available to the AI */
71
- functions?: AIFunctionDefinition[]
72
- }
43
+ // ============================================================================
44
+ // Local exports - withTemplate utility
45
+ // ============================================================================
73
46
 
74
47
  /**
75
- * Options for AI schema functions (subset of generateObject options)
48
+ * Helper to create a function that supports both regular calls and tagged template literals
49
+ * @example
50
+ * const fn = withTemplate((prompt) => doSomething(prompt))
51
+ * fn('hello') // regular call
52
+ * fn`hello ${name}` // tagged template literal
76
53
  */
77
- export interface AISchemaOptions {
78
- /** Model to use (string alias or LanguageModel) */
79
- model?: string | LanguageModel
80
- /** System prompt */
81
- system?: string
82
- /** Generation mode */
83
- mode?: 'auto' | 'json' | 'tool'
84
- /** Temperature (0-2) */
85
- temperature?: number
86
- /** Top P sampling */
87
- topP?: number
88
- /** Top K sampling */
89
- topK?: number
90
- /** Presence penalty */
91
- presencePenalty?: number
92
- /** Frequency penalty */
93
- frequencyPenalty?: number
94
- /** Max tokens to generate */
95
- maxTokens?: number
96
- /** Max retries on failure */
97
- maxRetries?: number
98
- /** Abort signal */
99
- abortSignal?: AbortSignal
100
- /** Custom headers */
101
- headers?: Record<string, string>
54
+ export function withTemplate<TArgs extends unknown[], TReturn>(
55
+ fn: (prompt: string, ...args: TArgs) => TReturn
56
+ ): ((prompt: string, ...args: TArgs) => TReturn) &
57
+ ((strings: TemplateStringsArray, ...values: unknown[]) => TReturn) {
58
+ return function (promptOrStrings: string | TemplateStringsArray, ...args: unknown[]): TReturn {
59
+ if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
60
+ // Tagged template literal call - pass empty args for optional params
61
+ const strings = promptOrStrings as TemplateStringsArray
62
+ const values = args
63
+ const prompt = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '')
64
+ // Cast required: When called as a tagged template, additional args aren't provided.
65
+ // TArgs represents optional parameters that default to empty. TypeScript can't express
66
+ // that TArgs can be satisfied by an empty array when all elements are optional.
67
+ return fn(prompt, ...([] as unknown as TArgs))
68
+ }
69
+ // Regular function call
70
+ return fn(promptOrStrings as string, ...(args as TArgs))
71
+ } as ((prompt: string, ...args: TArgs) => TReturn) &
72
+ ((strings: TemplateStringsArray, ...values: unknown[]) => TReturn)
102
73
  }
103
74
 
104
- /**
105
- * Schema-based functions
106
- */
107
- type SchemaFunctions<T extends Record<string, SimpleSchema>> = {
108
- [K in keyof T]: (prompt?: string, options?: AISchemaOptions) => Promise<InferSimpleSchemaResult<T[K]>>
109
- }
75
+ // ============================================================================
76
+ // AI() - Main entry point (schema-based functions)
77
+ // ============================================================================
78
+
79
+ import type { SimpleSchema } from './schema.js'
80
+ import { createSchemaFunctions, type AISchemaOptions, type SchemaFunctions } from './ai-schemas.js'
110
81
 
111
82
  /**
112
83
  * Create AI functions from schemas
@@ -144,1085 +115,7 @@ type SchemaFunctions<T extends Record<string, SimpleSchema>> = {
144
115
  export function AI<T extends Record<string, SimpleSchema>>(
145
116
  schemas: T,
146
117
  defaultOptions?: AISchemaOptions
147
- ): SchemaFunctions<T>
148
-
149
- /**
150
- * Create an AI RPC client instance
151
- *
152
- * @example
153
- * ```ts
154
- * // Connect to remote AI service
155
- * const ai = AI({ wsUrl: 'wss://ai.example.com/rpc' })
156
- *
157
- * // Use promise pipelining - single round trip!
158
- * const result = ai.generate({ prompt: 'Hello' })
159
- * const upper = result.text.map(t => t.toUpperCase())
160
- * console.log(await upper)
161
- *
162
- * // Dynamic function calling
163
- * const summary = await ai.summarize({ text: longText })
164
- * ```
165
- */
166
- export function AI(options: AIClientOptions): AIClient & Record<string, (...args: unknown[]) => Promise<unknown>>
167
-
168
- export function AI<T extends Record<string, SimpleSchema>>(
169
- schemasOrOptions: T | AIClientOptions,
170
- defaultOptions?: AISchemaOptions
171
- ): SchemaFunctions<T> | (AIClient & Record<string, (...args: unknown[]) => Promise<unknown>>) {
172
- // Check if this is RPC client mode
173
- if (isAIClientOptions(schemasOrOptions)) {
174
- // TODO: Re-enable RPC client when rpc.do is published
175
- // const { model, temperature, maxTokens, functions, wsUrl, httpUrl, token } = schemasOrOptions
176
- //
177
- // // Create transport based on provided URLs
178
- // let transport
179
- // if (wsUrl) {
180
- // transport = ws(wsUrl, token ? () => token : undefined)
181
- // } else if (httpUrl) {
182
- // transport = http(httpUrl, token ? () => token : undefined)
183
- // } else {
184
- // throw new Error('AI client requires either wsUrl or httpUrl')
185
- // }
186
- //
187
- // // Create RPC client
188
- // const rpcClient = RPC<AIClient>(transport)
189
- //
190
- // // Create a proxy that handles both defined methods and dynamic function calls
191
- // return new Proxy(rpcClient, {
192
- // get(target, prop: string) {
193
- // // Return existing methods
194
- // if (prop in target) {
195
- // return (target as unknown as Record<string, unknown>)[prop]
196
- // }
197
- //
198
- // // Handle dynamic function calls (ai.functionName())
199
- // return (...args: unknown[]) => {
200
- // const client = target as unknown as AIClient
201
- // return client.do(prop, args.length === 1 ? args[0] : args)
202
- // }
203
- // }
204
- // }) as AIClient & Record<string, (...args: unknown[]) => Promise<unknown>>
205
- throw new Error('RPC client mode requires rpc.do package (not yet published). Use schema-based mode instead.')
206
- }
207
-
208
- // Schema functions mode - create a function for each schema
209
- return createSchemaFunctions(schemasOrOptions as Record<string, SimpleSchema>, defaultOptions) as SchemaFunctions<T>
210
- }
211
-
212
- /**
213
- * Check if options are AI client options vs schemas
214
- */
215
- function isAIClientOptions(value: unknown): value is AIClientOptions {
216
- if (typeof value !== 'object' || value === null) return false
217
- const obj = value as Record<string, unknown>
218
- return 'wsUrl' in obj || 'httpUrl' in obj || 'functions' in obj
219
- }
220
-
221
- /**
222
- * Infer result type from simple schema
223
- */
224
- type InferSimpleSchemaResult<T> = T extends string
225
- ? string
226
- : T extends [string]
227
- ? string[]
228
- : T extends { [K: string]: SimpleSchema }
229
- ? { [K in keyof T]: InferSimpleSchemaResult<T[K]> }
230
- : unknown
231
-
232
- /**
233
- * Create schema-based functions from a map of schemas
234
- */
235
- function createSchemaFunctions<T extends Record<string, SimpleSchema>>(
236
- schemas: T,
237
- defaultOptions: AISchemaOptions = {}
238
118
  ): SchemaFunctions<T> {
239
- const functions: Record<string, (prompt?: string, options?: AISchemaOptions) => Promise<unknown>> = {}
240
-
241
- for (const [name, schema] of Object.entries(schemas)) {
242
- functions[name] = async (prompt?: string, options?: AISchemaOptions) => {
243
- const mergedOptions = { ...defaultOptions, ...options }
244
- const { model = 'sonnet', system, ...rest } = mergedOptions
245
-
246
- // Build prompt from schema descriptions if none provided
247
- const schemaPrompt = prompt || buildPromptFromSchema(schema)
248
-
249
- const result = await generateObject({
250
- model,
251
- schema,
252
- prompt: schemaPrompt,
253
- system,
254
- ...rest,
255
- })
256
-
257
- return result.object
258
- }
259
- }
260
-
261
- return functions as SchemaFunctions<T>
262
- }
263
-
264
- /**
265
- * Build a prompt by extracting descriptions from the schema
266
- */
267
- function buildPromptFromSchema(schema: SimpleSchema, path = ''): string {
268
- if (typeof schema === 'string') {
269
- return schema
270
- }
271
-
272
- if (Array.isArray(schema)) {
273
- return schema[0] as string || 'Generate items'
274
- }
275
-
276
- if (typeof schema === 'object' && schema !== null) {
277
- const descriptions: string[] = []
278
- for (const [key, value] of Object.entries(schema)) {
279
- const subPrompt = buildPromptFromSchema(value as SimpleSchema, path ? `${path}.${key}` : key)
280
- if (subPrompt) {
281
- descriptions.push(`${key}: ${subPrompt}`)
282
- }
283
- }
284
- return descriptions.length > 0 ? `Generate the following:\n${descriptions.join('\n')}` : ''
285
- }
286
-
287
- return ''
288
- }
289
-
290
- /**
291
- * Create a defined function from a function definition
292
- */
293
- function createDefinedFunction<TOutput, TInput>(
294
- definition: FunctionDefinition<TOutput, TInput>
295
- ): DefinedFunction<TOutput, TInput> {
296
- const call = async (args: TInput): Promise<TOutput> => {
297
- switch (definition.type) {
298
- case 'code':
299
- return executeCodeFunction(definition, args) as Promise<TOutput>
300
- case 'generative':
301
- return executeGenerativeFunction(definition, args) as Promise<TOutput>
302
- case 'agentic':
303
- return executeAgenticFunction(definition, args) as Promise<TOutput>
304
- case 'human':
305
- return executeHumanFunction(definition, args) as Promise<TOutput>
306
- default:
307
- throw new Error(`Unknown function type: ${(definition as FunctionDefinition).type}`)
308
- }
309
- }
310
-
311
- const asTool = (): AIFunctionDefinition<TOutput, TInput> => {
312
- return {
313
- name: definition.name,
314
- description: definition.description || `Execute ${definition.name}`,
315
- parameters: convertArgsToJSONSchema(definition.args),
316
- handler: call,
317
- }
318
- }
319
-
320
- return { definition, call, asTool }
321
- }
322
-
323
- /**
324
- * Convert args schema to JSON Schema
325
- */
326
- function convertArgsToJSONSchema(args: unknown): JSONSchema {
327
- // If it's already a JSON schema-like object
328
- if (typeof args === 'object' && args !== null && 'type' in args) {
329
- return args as JSONSchema
330
- }
331
-
332
- // Convert SimpleSchema to JSON Schema
333
- const properties: Record<string, JSONSchema> = {}
334
- const required: string[] = []
335
-
336
- if (typeof args === 'object' && args !== null) {
337
- for (const [key, value] of Object.entries(args as Record<string, unknown>)) {
338
- required.push(key) // All properties required for cross-provider compatibility
339
- properties[key] = convertValueToJSONSchema(value)
340
- }
341
- }
342
-
343
- return {
344
- type: 'object',
345
- properties,
346
- required,
347
- additionalProperties: false, // Required for OpenAI compatibility
348
- }
349
- }
350
-
351
- /**
352
- * Convert a single value to JSON Schema
353
- */
354
- function convertValueToJSONSchema(value: unknown): JSONSchema {
355
- if (typeof value === 'string') {
356
- // Check for type hints: 'description (number)', 'description (boolean)', etc.
357
- const typeMatch = value.match(/^(.+?)\s*\((number|boolean|integer|date)\)$/i)
358
- if (typeMatch) {
359
- const description = typeMatch[1]!
360
- const type = typeMatch[2]!
361
- switch (type.toLowerCase()) {
362
- case 'number':
363
- return { type: 'number', description: description.trim() }
364
- case 'integer':
365
- return { type: 'integer', description: description.trim() }
366
- case 'boolean':
367
- return { type: 'boolean', description: description.trim() }
368
- case 'date':
369
- return { type: 'string', format: 'date-time', description: description.trim() }
370
- }
371
- }
372
-
373
- // Check for enum: 'option1 | option2 | option3'
374
- if (value.includes(' | ')) {
375
- const options = value.split(' | ').map(s => s.trim())
376
- return { type: 'string', enum: options }
377
- }
378
-
379
- return { type: 'string', description: value }
380
- }
381
-
382
- if (Array.isArray(value) && value.length === 1) {
383
- const [desc] = value
384
- if (typeof desc === 'string') {
385
- return { type: 'array', items: { type: 'string' }, description: desc }
386
- }
387
- if (typeof desc === 'number') {
388
- return { type: 'array', items: { type: 'number' } }
389
- }
390
- return { type: 'array', items: convertValueToJSONSchema(desc) }
391
- }
392
-
393
- if (typeof value === 'object' && value !== null) {
394
- return convertArgsToJSONSchema(value)
395
- }
396
-
397
- return { type: 'string' }
398
- }
399
-
400
- /**
401
- * Fill template with values
402
- */
403
- function fillTemplate(template: string, args: Record<string, unknown>): string {
404
- return template.replace(/\{\{(\w+)\}\}/g, (_, key) => String(args[key] ?? ''))
405
- }
406
-
407
- /**
408
- * Execute a code function - generates code with tests and examples
409
- */
410
- async function executeCodeFunction<TInput>(
411
- definition: CodeFunctionDefinition<unknown, TInput>,
412
- args: TInput
413
- ): Promise<CodeFunctionResult> {
414
- const { name, description, language = 'typescript', instructions, includeTests = true, includeExamples = true } = definition
415
-
416
- const argsDescription = JSON.stringify(args, null, 2)
417
-
418
- const result = await generateObject({
419
- model: 'sonnet',
420
- schema: {
421
- code: 'The complete implementation code with JSDoc comments',
422
- tests: includeTests ? 'Vitest test code for the implementation' : undefined,
423
- examples: includeExamples ? 'Example usage code' : undefined,
424
- documentation: 'JSDoc or equivalent documentation string',
425
- },
426
- system: `You are an expert ${language} developer. Generate clean, well-documented, production-ready code.`,
427
- prompt: `Generate a ${language} function with the following specification:
428
-
429
- Name: ${name}
430
- Description: ${description || 'No description provided'}
431
- Arguments: ${argsDescription}
432
- Return Type: ${JSON.stringify(definition.returnType)}
433
-
434
- ${instructions ? `Additional Instructions: ${instructions}` : ''}
435
-
436
- Requirements:
437
- - Include comprehensive JSDoc comments
438
- - Follow best practices for ${language}
439
- - Handle edge cases appropriately
440
- ${includeTests ? '- Include vitest tests that cover main functionality and edge cases' : ''}
441
- ${includeExamples ? '- Include example usage showing how to call the function' : ''}`,
442
- })
443
-
444
- const obj = result.object as { code: string; tests?: string; examples?: string; documentation: string }
445
- return {
446
- code: obj.code,
447
- tests: obj.tests,
448
- examples: obj.examples,
449
- language,
450
- documentation: obj.documentation,
451
- }
452
- }
453
-
454
- /**
455
- * Execute a generative function - uses AI to generate content
456
- */
457
- async function executeGenerativeFunction<TOutput, TInput>(
458
- definition: GenerativeFunctionDefinition<TOutput, TInput>,
459
- args: TInput
460
- ): Promise<TOutput> {
461
- const { output, system, promptTemplate, model = 'sonnet', temperature, returnType } = definition
462
-
463
- const prompt = promptTemplate
464
- ? fillTemplate(promptTemplate, args as Record<string, unknown>)
465
- : JSON.stringify(args)
466
-
467
- switch (output) {
468
- case 'string': {
469
- const result = await generateObject({
470
- model,
471
- schema: { text: 'The generated text response' },
472
- system,
473
- prompt,
474
- temperature,
475
- })
476
- return (result.object as { text: string }).text as TOutput
477
- }
478
-
479
- case 'object': {
480
- const objectSchema = returnType || { result: 'The generated result' }
481
- const result = await generateObject({
482
- model,
483
- schema: objectSchema as SimpleSchemaType,
484
- system,
485
- prompt,
486
- temperature,
487
- })
488
- return result.object as TOutput
489
- }
490
-
491
- case 'image': {
492
- const client = getDefaultAIClient()
493
- // Cast required: client.image() returns ImageResult but TOutput is determined by the
494
- // output parameter at runtime. TypeScript can't narrow TOutput based on output='image'.
495
- return client.image(prompt) as unknown as Promise<TOutput>
496
- }
497
-
498
- case 'video': {
499
- const client = getDefaultAIClient()
500
- // Cast required: client.video() returns VideoResult but TOutput is determined by the
501
- // output parameter at runtime. TypeScript can't narrow TOutput based on output='video'.
502
- return client.video(prompt) as unknown as Promise<TOutput>
503
- }
504
-
505
- default:
506
- throw new Error(`Unknown output type: ${output}`)
507
- }
508
- }
509
-
510
- /**
511
- * Execute an agentic function - runs in a loop with tools
512
- */
513
- async function executeAgenticFunction<TOutput, TInput>(
514
- definition: AgenticFunctionDefinition<TOutput, TInput>,
515
- args: TInput
516
- ): Promise<TOutput> {
517
- const { instructions, promptTemplate, tools = [], maxIterations = 10, model = 'sonnet', returnType } = definition
518
-
519
- const prompt = promptTemplate
520
- ? fillTemplate(promptTemplate, args as Record<string, unknown>)
521
- : JSON.stringify(args)
522
-
523
- // Build system prompt with tool descriptions
524
- const toolDescriptions = tools.map(t => `- ${t.name}: ${t.description}`).join('\n')
525
- const systemPrompt = `${instructions}
526
-
527
- Available tools:
528
- ${toolDescriptions || 'No tools available'}
529
-
530
- Work step by step to accomplish the task. When you have completed the task, provide your final result.`
531
-
532
- let iteration = 0
533
- const toolResults: unknown[] = []
534
-
535
- // Simple agent loop
536
- while (iteration < maxIterations) {
537
- iteration++
538
-
539
- const result = await generateObject({
540
- model,
541
- schema: {
542
- thinking: 'Your step-by-step reasoning',
543
- toolCall: {
544
- name: 'Tool to call (or "done" if finished)',
545
- arguments: 'Arguments for the tool as JSON string',
546
- },
547
- finalResult: returnType || 'The final result if done',
548
- },
549
- system: systemPrompt,
550
- prompt: `Task: ${prompt}
551
-
552
- Previous tool results:
553
- ${toolResults.map((r, i) => `Step ${i + 1}: ${JSON.stringify(r)}`).join('\n') || 'None yet'}
554
-
555
- What is your next step?`,
556
- })
557
-
558
- const response = result.object as {
559
- thinking: string
560
- toolCall: { name: string; arguments: string }
561
- finalResult: unknown
562
- }
563
-
564
- if (response.toolCall.name === 'done' || response.finalResult) {
565
- return response.finalResult as TOutput
566
- }
567
-
568
- // Execute tool call
569
- const tool = tools.find(t => t.name === response.toolCall.name)
570
- if (tool) {
571
- let toolArgs: Record<string, unknown>
572
- try {
573
- toolArgs = JSON.parse(response.toolCall.arguments || '{}')
574
- } catch (e) {
575
- toolResults.push({
576
- error: `Invalid tool arguments: ${(e as Error).message}`,
577
- })
578
- continue
579
- }
580
- const toolResult = await tool.handler(toolArgs)
581
- toolResults.push({ tool: response.toolCall.name, result: toolResult })
582
- } else {
583
- toolResults.push({ error: `Tool not found: ${response.toolCall.name}` })
584
- }
585
- }
586
-
587
- throw new Error(`Agent exceeded maximum iterations (${maxIterations})`)
588
- }
589
-
590
- /**
591
- * Execute a human function - generates UI and waits for human input
592
- *
593
- * **Note: This function currently returns placeholder/mock data.**
594
- *
595
- * In a complete implementation, this function would:
596
- * 1. Generate channel-specific UI (Slack blocks, email templates, web forms, etc.)
597
- * 2. Send the generated UI to the appropriate channel
598
- * 3. Wait for human response with optional timeout
599
- * 4. Validate and return the human's response
600
- *
601
- * The current implementation generates the UI artifacts but returns a pending
602
- * placeholder instead of actually sending to the channel and waiting for response.
603
- * This allows testing the UI generation without requiring actual channel integrations.
604
- *
605
- * @param definition - The human function definition with channel and instructions
606
- * @param args - Arguments to pass to the function
607
- * @returns A placeholder result with `_pending: true` and generated artifacts
608
- *
609
- * @example
610
- * ```ts
611
- * // The result will be a placeholder, not an actual human response:
612
- * // {
613
- * // _pending: true,
614
- * // channel: 'workspace',
615
- * // artifacts: { ... generated UI ... },
616
- * // expectedResponseType: { approved: boolean }
617
- * // }
618
- * ```
619
- */
620
- async function executeHumanFunction<TOutput, TInput>(
621
- definition: HumanFunctionDefinition<TOutput, TInput>,
622
- args: TInput
623
- ): Promise<TOutput> {
624
- const { channel, instructions, promptTemplate, returnType } = definition
625
-
626
- const prompt = promptTemplate
627
- ? fillTemplate(promptTemplate, args as Record<string, unknown>)
628
- : JSON.stringify(args)
629
-
630
- // Generate channel-specific UI
631
- const uiSchema: Record<string, SimpleSchemaType> = {
632
- // New HumanChannel types
633
- chat: {
634
- message: 'Chat message to send',
635
- options: ['Response options if applicable'],
636
- },
637
- email: {
638
- subject: 'Email subject',
639
- html: 'Email HTML body',
640
- text: 'Plain text fallback',
641
- },
642
- phone: {
643
- script: 'Phone call script',
644
- keyPoints: ['Key points to cover'],
645
- },
646
- sms: {
647
- text: 'SMS message text (max 160 chars)',
648
- },
649
- workspace: {
650
- blocks: ['Workspace/Slack BlockKit blocks as JSON array'],
651
- text: 'Plain text fallback',
652
- },
653
- web: {
654
- component: 'React component code for the form',
655
- schema: 'JSON schema for the form fields',
656
- },
657
- // Legacy fallback
658
- custom: {
659
- data: 'Structured data for custom implementation',
660
- instructions: 'Instructions for the human',
661
- },
662
- }
663
-
664
- const result = await generateObject({
665
- model: 'sonnet',
666
- schema: uiSchema[channel] ?? uiSchema.custom,
667
- system: `Generate ${channel} UI/content for a human-in-the-loop task.`,
668
- prompt: `Task: ${instructions}
669
-
670
- Input data:
671
- ${prompt}
672
-
673
- Expected response format:
674
- ${JSON.stringify(returnType)}
675
-
676
- Generate the appropriate ${channel} UI/content to collect this response from a human.`,
677
- })
678
-
679
- // NOTE: This is a placeholder implementation.
680
- // In a real implementation, this would:
681
- // 1. Send the generated UI to the appropriate channel (Slack, email, etc.)
682
- // 2. Wait for human response with optional timeout
683
- // 3. Return the validated response
684
- //
685
- // Current behavior: Returns generated artifacts as a pending placeholder
686
- // Cast required: This is a placeholder implementation that returns a different shape
687
- // than TOutput. When fully implemented, this would return actual TOutput from human input.
688
- // The _pending flag allows consumers to detect placeholder vs real responses.
689
- return {
690
- _pending: true,
691
- channel,
692
- artifacts: result.object,
693
- expectedResponseType: returnType,
694
- } as unknown as TOutput
695
- }
696
-
697
- /**
698
- * Helper to create a function that supports both regular calls and tagged template literals
699
- * @example
700
- * const fn = withTemplate((prompt) => doSomething(prompt))
701
- * fn('hello') // regular call
702
- * fn`hello ${name}` // tagged template literal
703
- */
704
- export function withTemplate<TArgs extends unknown[], TReturn>(
705
- fn: (prompt: string, ...args: TArgs) => TReturn
706
- ): ((prompt: string, ...args: TArgs) => TReturn) & ((strings: TemplateStringsArray, ...values: unknown[]) => TReturn) {
707
- return function (promptOrStrings: string | TemplateStringsArray, ...args: unknown[]): TReturn {
708
- if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
709
- // Tagged template literal call - pass empty args for optional params
710
- const strings = promptOrStrings as TemplateStringsArray
711
- const values = args
712
- const prompt = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '')
713
- // Cast required: When called as a tagged template, additional args aren't provided.
714
- // TArgs represents optional parameters that default to empty. TypeScript can't express
715
- // that TArgs can be satisfied by an empty array when all elements are optional.
716
- return fn(prompt, ...([] as unknown as TArgs))
717
- }
718
- // Regular function call
719
- return fn(promptOrStrings as string, ...(args as TArgs))
720
- } as ((prompt: string, ...args: TArgs) => TReturn) & ((strings: TemplateStringsArray, ...values: unknown[]) => TReturn)
721
- }
722
-
723
- // Default client management
724
- let defaultClient: AIClient | null = null
725
-
726
- /**
727
- * Configure the default AI client
728
- */
729
- export function configureAI(options: AIClientOptions): void {
730
- defaultClient = AI(options) as AIClient
731
- }
732
-
733
- /**
734
- * Get the default AI client, throwing if not configured
735
- */
736
- function getDefaultAIClient(): AIClient {
737
- if (!defaultClient) {
738
- // Try to auto-configure from environment
739
- const wsUrl = typeof process !== 'undefined' ? process.env?.AI_WS_URL : undefined
740
- const httpUrl = typeof process !== 'undefined' ? process.env?.AI_HTTP_URL : undefined
741
-
742
- if (wsUrl) {
743
- // Explicitly type the options to call the correct AI() overload.
744
- // Without this, TypeScript might incorrectly match the schema overload.
745
- const options: AIClientOptions = { wsUrl }
746
- defaultClient = AI(options)
747
- } else if (httpUrl) {
748
- // Explicitly type the options to call the correct AI() overload.
749
- const options: AIClientOptions = { httpUrl }
750
- defaultClient = AI(options)
751
- } else {
752
- throw new Error(
753
- 'AI client not configured. Call configureAI() first or set AI_WS_URL/AI_HTTP_URL environment variables.'
754
- )
755
- }
756
- }
757
- return defaultClient
758
- }
759
-
760
- /**
761
- * Base class for implementing AI services
762
- *
763
- * Extend this class to create your own AI service implementation.
764
- *
765
- * @example
766
- * ```ts
767
- * class MyAIService extends AIServiceTarget {
768
- * async generate(options: AIGenerateOptions): Promise<AIGenerateResult> {
769
- * // Your implementation
770
- * }
771
- * }
772
- * ```
773
- */
774
- export abstract class AIServiceTarget extends RpcTarget implements AIClient {
775
- abstract generate(options: AIGenerateOptions): Promise<AIGenerateResult>
776
- abstract do(action: string, context?: unknown): Promise<unknown>
777
- abstract is(value: unknown, type: string | JSONSchema): Promise<boolean>
778
- abstract code(prompt: string, language?: string): Promise<string>
779
- abstract decide<T extends string>(options: T[], context?: string): Promise<T>
780
- abstract diagram(description: string, format?: 'mermaid' | 'svg' | 'ascii'): Promise<string>
781
- abstract image(prompt: string, options?: ImageOptions): Promise<ImageResult>
782
- abstract video(prompt: string, options?: VideoOptions): Promise<VideoResult>
783
- abstract write(prompt: string, options?: WriteOptions): Promise<string>
784
- abstract list(prompt: string): Promise<ListResult>
785
- abstract lists(prompt: string): Promise<ListsResult>
786
- }
787
-
788
- /**
789
- * Standalone function for defining AI functions
790
- *
791
- * @example
792
- * ```ts
793
- * import { defineFunction } from 'ai-functions'
794
- *
795
- * const summarize = defineFunction({
796
- * type: 'generative',
797
- * name: 'summarize',
798
- * args: { text: 'Text to summarize' },
799
- * output: 'string',
800
- * promptTemplate: 'Summarize: {{text}}',
801
- * })
802
- *
803
- * const result = await summarize.call({ text: 'Long article...' })
804
- * ```
805
- */
806
- export function defineFunction<TOutput, TInput>(
807
- definition: FunctionDefinition<TOutput, TInput>
808
- ): DefinedFunction<TOutput, TInput> {
809
- return createDefinedFunction(definition)
810
- }
811
-
812
- // ============================================================================
813
- // Function Registry
814
- // ============================================================================
815
-
816
- /**
817
- * In-memory function registry
818
- */
819
- class InMemoryFunctionRegistry implements FunctionRegistry {
820
- private functions = new Map<string, DefinedFunction>()
821
-
822
- get(name: string): DefinedFunction | undefined {
823
- return this.functions.get(name)
824
- }
825
-
826
- set(name: string, fn: DefinedFunction): void {
827
- this.functions.set(name, fn)
828
- }
829
-
830
- has(name: string): boolean {
831
- return this.functions.has(name)
832
- }
833
-
834
- list(): string[] {
835
- return Array.from(this.functions.keys())
836
- }
837
-
838
- delete(name: string): boolean {
839
- return this.functions.delete(name)
840
- }
841
-
842
- clear(): void {
843
- this.functions.clear()
844
- }
845
- }
846
-
847
- /**
848
- * Global function registry
849
- *
850
- * Note: This is in-memory only. For persistence, use mdxai or mdxdb packages.
851
- */
852
- export const functions: FunctionRegistry = new InMemoryFunctionRegistry()
853
-
854
- // ============================================================================
855
- // Auto-Define Functions
856
- // ============================================================================
857
-
858
- /**
859
- * Analyze a function call and determine what type of function it should be
860
- */
861
- async function analyzeFunction(
862
- name: string,
863
- args: Record<string, unknown>
864
- ): Promise<AutoDefineResult> {
865
- // Convert camelCase/snake_case to readable name
866
- const readableName = name
867
- .replace(/([A-Z])/g, ' $1')
868
- .replace(/_/g, ' ')
869
- .toLowerCase()
870
- .trim()
871
-
872
- const argDescriptions = Object.entries(args)
873
- .map(([key, value]) => {
874
- const type = Array.isArray(value) ? 'array' : typeof value
875
- return ` - ${key}: ${type} (example: ${JSON.stringify(value).slice(0, 50)})`
876
- })
877
- .join('\n')
878
-
879
- const result = await generateObject({
880
- model: 'sonnet',
881
- schema: {
882
- type: 'code | generative | agentic | human',
883
- reasoning: 'Why this function type is appropriate (1-2 sentences)',
884
- description: 'What this function does',
885
- output: 'string | object | image | video | audio',
886
- returnType: 'Schema for the return type as a SimpleSchema object',
887
- system: 'System prompt for the AI (if generative/agentic)',
888
- promptTemplate: 'Prompt template with {{arg}} placeholders',
889
- instructions: 'Instructions for agentic/human functions',
890
- needsTools: 'true | false',
891
- suggestedTools: ['Names of tools that might be needed'],
892
- channel: 'slack | email | web | sms | custom',
893
- },
894
- system: `You are an expert at designing AI functions. Analyze the function name and arguments to determine the best function type.
895
-
896
- Function Types:
897
- - "code": For generating executable code (calculations, algorithms, data transformations)
898
- - "generative": For generating content (text, summaries, translations, creative writing, structured data)
899
- - "agentic": For complex tasks requiring multiple steps, research, or tool use (research, planning, multi-step workflows)
900
- - "human": For tasks requiring human judgment, approval, or input (approvals, reviews, decisions)
901
-
902
- Guidelines:
903
- - Most functions should be "generative" - they generate content or structured data
904
- - Use "code" only when actual executable code needs to be generated
905
- - Use "agentic" when the task requires research, multiple steps, or external tool use
906
- - Use "human" when human judgment/approval is essential`,
907
- prompt: `Analyze this function call and determine how to define it:
908
-
909
- Function Name: ${name}
910
- Readable Name: ${readableName}
911
- Arguments:
912
- ${argDescriptions || ' (no arguments)'}
913
-
914
- Determine:
915
- 1. What type of function this should be
916
- 2. What it should return
917
- 3. How it should be implemented`,
918
- })
919
-
920
- const analysis = result.object as {
921
- type: string
922
- reasoning: string
923
- description: string
924
- output: string
925
- returnType: unknown
926
- system: string
927
- promptTemplate: string
928
- instructions: string
929
- needsTools: string
930
- suggestedTools: string[]
931
- channel: string
932
- }
933
-
934
- // Build the function definition based on the analysis
935
- let definition: FunctionDefinition
936
-
937
- const baseDefinition = {
938
- name,
939
- description: analysis.description,
940
- args: inferArgsSchema(args),
941
- returnType: analysis.returnType as SimpleSchemaType,
942
- }
943
-
944
- switch (analysis.type) {
945
- case 'code':
946
- definition = {
947
- ...baseDefinition,
948
- type: 'code' as const,
949
- language: 'typescript' as const,
950
- instructions: analysis.instructions,
951
- }
952
- break
953
-
954
- case 'agentic':
955
- definition = {
956
- ...baseDefinition,
957
- type: 'agentic' as const,
958
- instructions: analysis.instructions || `Complete the ${readableName} task`,
959
- promptTemplate: analysis.promptTemplate,
960
- tools: [], // Tools would need to be provided separately
961
- maxIterations: 10,
962
- }
963
- break
964
-
965
- case 'human':
966
- definition = {
967
- ...baseDefinition,
968
- type: 'human' as const,
969
- channel: (analysis.channel || 'web') as HumanChannel,
970
- instructions: analysis.instructions || `Please review and respond to this ${readableName} request`,
971
- promptTemplate: analysis.promptTemplate,
972
- }
973
- break
974
-
975
- case 'generative':
976
- default:
977
- definition = {
978
- ...baseDefinition,
979
- type: 'generative' as const,
980
- output: (analysis.output || 'object') as 'string' | 'object' | 'image' | 'video',
981
- system: analysis.system,
982
- promptTemplate: analysis.promptTemplate || `{{${Object.keys(args)[0] || 'input'}}}`,
983
- }
984
- break
985
- }
986
-
987
- return {
988
- type: analysis.type as 'code' | 'generative' | 'agentic' | 'human',
989
- reasoning: analysis.reasoning,
990
- definition,
991
- }
992
- }
993
-
994
- /**
995
- * Infer a schema from example arguments
996
- */
997
- function inferArgsSchema(args: Record<string, unknown>): Record<string, string | string[] | Record<string, unknown>> {
998
- const schema: Record<string, string | string[] | Record<string, unknown>> = {}
999
-
1000
- for (const [key, value] of Object.entries(args)) {
1001
- if (typeof value === 'string') {
1002
- schema[key] = `The ${key.replace(/([A-Z])/g, ' $1').toLowerCase()}`
1003
- } else if (typeof value === 'number') {
1004
- schema[key] = `The ${key.replace(/([A-Z])/g, ' $1').toLowerCase()} (number)`
1005
- } else if (typeof value === 'boolean') {
1006
- schema[key] = `Whether ${key.replace(/([A-Z])/g, ' $1').toLowerCase()} (boolean)`
1007
- } else if (Array.isArray(value)) {
1008
- if (value.length > 0 && typeof value[0] === 'string') {
1009
- schema[key] = [`List of ${key.replace(/([A-Z])/g, ' $1').toLowerCase()}`]
1010
- } else {
1011
- schema[key] = [`Items for ${key.replace(/([A-Z])/g, ' $1').toLowerCase()}`]
1012
- }
1013
- } else if (typeof value === 'object' && value !== null) {
1014
- schema[key] = inferArgsSchema(value as Record<string, unknown>)
1015
- } else {
1016
- schema[key] = `The ${key.replace(/([A-Z])/g, ' $1').toLowerCase()}`
1017
- }
1018
- }
1019
-
1020
- return schema
1021
- }
1022
-
1023
- /**
1024
- * Auto-define a function based on its name and arguments, or define with explicit definition
1025
- *
1026
- * When called with (name, args), uses AI to analyze and determine:
1027
- * - What type of function it should be (code, generative, agentic, human)
1028
- * - What it should return
1029
- * - How it should be implemented
1030
- *
1031
- * When called with a FunctionDefinition, creates the function directly.
1032
- *
1033
- * @example
1034
- * ```ts
1035
- * // Auto-define from name and example args
1036
- * const planTrip = await define('planTrip', { destination: 'Tokyo', travelers: 2 })
1037
- *
1038
- * // Or define explicitly
1039
- * const summarize = define.generative({
1040
- * name: 'summarize',
1041
- * args: { text: 'Text to summarize' },
1042
- * output: 'string',
1043
- * })
1044
- *
1045
- * // Or with full definition
1046
- * const fn = defineFunction({
1047
- * type: 'generative',
1048
- * name: 'translate',
1049
- * args: { text: 'Text', lang: 'Target language' },
1050
- * output: 'string',
1051
- * })
1052
- * ```
1053
- */
1054
- async function autoDefineImpl(
1055
- name: string,
1056
- args: Record<string, unknown>
1057
- ): Promise<DefinedFunction> {
1058
- // Check if already defined
1059
- const existing = functions.get(name)
1060
- if (existing) {
1061
- return existing
1062
- }
1063
-
1064
- // Analyze and define the function
1065
- const { definition } = await analyzeFunction(name, args)
1066
-
1067
- // Create the defined function
1068
- const definedFn = createDefinedFunction(definition)
1069
-
1070
- // Store in registry
1071
- functions.set(name, definedFn)
1072
-
1073
- return definedFn
1074
- }
1075
-
1076
- /**
1077
- * Define functions - auto-define or use typed helpers
1078
- */
1079
- export const define = Object.assign(autoDefineImpl, {
1080
- /**
1081
- * Define a code generation function
1082
- */
1083
- code: <TOutput, TInput>(
1084
- definition: Omit<CodeFunctionDefinition<TOutput, TInput>, 'type'>
1085
- ): DefinedFunction<TOutput, TInput> => {
1086
- const fn = defineFunction({ type: 'code', ...definition } as CodeFunctionDefinition<TOutput, TInput>)
1087
- functions.set(definition.name, fn as DefinedFunction)
1088
- return fn
1089
- },
1090
-
1091
- /**
1092
- * Define a generative function
1093
- */
1094
- generative: <TOutput, TInput>(
1095
- definition: Omit<GenerativeFunctionDefinition<TOutput, TInput>, 'type'>
1096
- ): DefinedFunction<TOutput, TInput> => {
1097
- const fn = defineFunction({ type: 'generative', ...definition } as GenerativeFunctionDefinition<TOutput, TInput>)
1098
- functions.set(definition.name, fn as DefinedFunction)
1099
- return fn
1100
- },
1101
-
1102
- /**
1103
- * Define an agentic function
1104
- */
1105
- agentic: <TOutput, TInput>(
1106
- definition: Omit<AgenticFunctionDefinition<TOutput, TInput>, 'type'>
1107
- ): DefinedFunction<TOutput, TInput> => {
1108
- const fn = defineFunction({ type: 'agentic', ...definition } as AgenticFunctionDefinition<TOutput, TInput>)
1109
- functions.set(definition.name, fn as DefinedFunction)
1110
- return fn
1111
- },
1112
-
1113
- /**
1114
- * Define a human-in-the-loop function
1115
- */
1116
- human: <TOutput, TInput>(
1117
- definition: Omit<HumanFunctionDefinition<TOutput, TInput>, 'type'>
1118
- ): DefinedFunction<TOutput, TInput> => {
1119
- const fn = defineFunction({ type: 'human', ...definition } as HumanFunctionDefinition<TOutput, TInput>)
1120
- functions.set(definition.name, fn as DefinedFunction)
1121
- return fn
1122
- },
1123
- })
1124
-
1125
- // ============================================================================
1126
- // AI() - Smart AI Client with Auto-Definition
1127
- // ============================================================================
1128
-
1129
- /** Known built-in method names that should not be auto-defined */
1130
- const BUILTIN_METHODS = new Set([
1131
- 'do', 'is', 'code', 'decide', 'diagram', 'generate', 'image', 'video', 'write', 'list', 'lists',
1132
- 'functions', 'define', 'defineFunction', 'then', 'catch', 'finally',
1133
- ])
1134
-
1135
- /**
1136
- * Create a smart AI client that auto-defines functions on first call
1137
- *
1138
- * @example
1139
- * ```ts
1140
- * const ai = AI()
1141
- *
1142
- * // First call - auto-defines the function
1143
- * const trip = await ai.planTrip({
1144
- * destination: 'Tokyo',
1145
- * dates: { start: '2024-03-01', end: '2024-03-10' },
1146
- * travelers: 2,
1147
- * })
1148
- *
1149
- * // Second call - uses cached definition (in-memory)
1150
- * const trip2 = await ai.planTrip({
1151
- * destination: 'Paris',
1152
- * dates: { start: '2024-06-01', end: '2024-06-07' },
1153
- * travelers: 4,
1154
- * })
1155
- *
1156
- * // Access registry and define
1157
- * console.log(ai.functions.list()) // ['planTrip']
1158
- * ai.define.generative({ name: 'summarize', ... })
1159
- * ```
1160
- */
1161
- function createSmartAI(): AIProxy {
1162
- const base = {
1163
- functions,
1164
- define,
1165
- defineFunction,
1166
- }
1167
-
1168
- return new Proxy(base as AIProxy, {
1169
- get(target, prop: string) {
1170
- // Return built-in properties
1171
- if (prop in target) {
1172
- return (target as Record<string, unknown>)[prop]
1173
- }
1174
-
1175
- // Skip internal properties
1176
- if (typeof prop === 'symbol' || prop.startsWith('_') || BUILTIN_METHODS.has(prop)) {
1177
- return undefined
1178
- }
1179
-
1180
- // Return a function that auto-defines and calls
1181
- return async (args: Record<string, unknown> = {}) => {
1182
- // Check if function is already defined
1183
- let fn = functions.get(prop)
1184
-
1185
- if (!fn) {
1186
- // Auto-define the function
1187
- fn = await define(prop, args)
1188
- }
1189
-
1190
- // Call the function
1191
- return fn.call(args)
1192
- }
1193
- },
1194
- })
1195
- }
1196
-
1197
- /**
1198
- * Type for the AI proxy with auto-define capability
1199
- */
1200
- export interface AIProxy {
1201
- /** Function registry */
1202
- functions: FunctionRegistry
1203
- /** Define functions */
1204
- define: typeof define
1205
- /** Define a function with full definition */
1206
- defineFunction: typeof defineFunction
1207
- /** Dynamic function calls */
1208
- [key: string]: unknown
119
+ // Schema functions mode - create a function for each schema
120
+ return createSchemaFunctions(schemas, defaultOptions)
1209
121
  }
1210
-
1211
- /**
1212
- * Default AI instance with auto-define capability
1213
- *
1214
- * @example
1215
- * ```ts
1216
- * import { ai } from 'ai-functions'
1217
- *
1218
- * // Auto-define and call
1219
- * const result = await ai.summarize({ text: 'Long article...' })
1220
- *
1221
- * // Access functions registry
1222
- * ai.functions.list()
1223
- *
1224
- * // Define explicitly
1225
- * ai.define.generative({ name: 'translate', ... })
1226
- * ```
1227
- */
1228
- export const ai: AIProxy = createSmartAI()