ai-functions 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-test.log +105 -0
- package/README.md +190 -86
- package/TODO.md +138 -0
- package/dist/ai-promise.d.ts +219 -0
- package/dist/ai-promise.d.ts.map +1 -0
- package/dist/ai-promise.js +610 -0
- package/dist/ai-promise.js.map +1 -0
- package/dist/ai.d.ts +285 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +842 -0
- package/dist/ai.js.map +1 -0
- package/dist/batch/anthropic.d.ts +23 -0
- package/dist/batch/anthropic.d.ts.map +1 -0
- package/dist/batch/anthropic.js +257 -0
- package/dist/batch/anthropic.js.map +1 -0
- package/dist/batch/bedrock.d.ts +64 -0
- package/dist/batch/bedrock.d.ts.map +1 -0
- package/dist/batch/bedrock.js +586 -0
- package/dist/batch/bedrock.js.map +1 -0
- package/dist/batch/cloudflare.d.ts +37 -0
- package/dist/batch/cloudflare.d.ts.map +1 -0
- package/dist/batch/cloudflare.js +289 -0
- package/dist/batch/cloudflare.js.map +1 -0
- package/dist/batch/google.d.ts +41 -0
- package/dist/batch/google.d.ts.map +1 -0
- package/dist/batch/google.js +360 -0
- package/dist/batch/google.js.map +1 -0
- package/dist/batch/index.d.ts +31 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +31 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/memory.d.ts +44 -0
- package/dist/batch/memory.d.ts.map +1 -0
- package/dist/batch/memory.js +188 -0
- package/dist/batch/memory.js.map +1 -0
- package/dist/batch/openai.d.ts +37 -0
- package/dist/batch/openai.d.ts.map +1 -0
- package/dist/batch/openai.js +403 -0
- package/dist/batch/openai.js.map +1 -0
- package/dist/batch-map.d.ts +125 -0
- package/dist/batch-map.d.ts.map +1 -0
- package/dist/batch-map.js +406 -0
- package/dist/batch-map.js.map +1 -0
- package/dist/batch-queue.d.ts +273 -0
- package/dist/batch-queue.d.ts.map +1 -0
- package/dist/batch-queue.js +271 -0
- package/dist/batch-queue.js.map +1 -0
- package/dist/context.d.ts +133 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +267 -0
- package/dist/context.js.map +1 -0
- package/dist/embeddings.d.ts +123 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +170 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/eval/index.d.ts +8 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +8 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/models.d.ts +66 -0
- package/dist/eval/models.d.ts.map +1 -0
- package/dist/eval/models.js +120 -0
- package/dist/eval/models.js.map +1 -0
- package/dist/eval/runner.d.ts +64 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +148 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/generate.d.ts +168 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +174 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +292 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +471 -0
- package/dist/primitives.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +9 -0
- package/dist/providers/cloudflare.d.ts.map +1 -0
- package/dist/providers/cloudflare.js +9 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/schema.d.ts +54 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/schema.js.map +1 -0
- package/dist/template.d.ts +73 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +129 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +474 -106
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -8
- package/dist/types.js.map +1 -1
- package/evalite.config.ts +19 -0
- package/evals/README.md +212 -0
- package/evals/classification.eval.ts +108 -0
- package/evals/marketing.eval.ts +370 -0
- package/evals/math.eval.ts +94 -0
- package/evals/run-evals.ts +166 -0
- package/evals/structured-output.eval.ts +143 -0
- package/evals/writing.eval.ts +117 -0
- package/examples/batch-blog-posts.ts +160 -0
- package/package.json +57 -57
- package/src/ai-promise.ts +784 -0
- package/src/ai.ts +1183 -0
- package/src/batch/anthropic.ts +375 -0
- package/src/batch/bedrock.ts +801 -0
- package/src/batch/cloudflare.ts +421 -0
- package/src/batch/google.ts +491 -0
- package/src/batch/index.ts +31 -0
- package/src/batch/memory.ts +253 -0
- package/src/batch/openai.ts +557 -0
- package/src/batch-map.ts +534 -0
- package/src/batch-queue.ts +493 -0
- package/src/context.ts +332 -0
- package/src/embeddings.ts +244 -0
- package/src/eval/index.ts +8 -0
- package/src/eval/models.ts +158 -0
- package/src/eval/runner.ts +217 -0
- package/src/generate.ts +245 -0
- package/src/index.ts +154 -0
- package/src/primitives.ts +612 -0
- package/src/providers/cloudflare.ts +15 -0
- package/src/providers/index.ts +14 -0
- package/src/schema.ts +147 -0
- package/src/template.ts +209 -0
- package/src/types.ts +540 -0
- package/test/README.md +105 -0
- package/test/ai-proxy.test.ts +192 -0
- package/test/async-iterators.test.ts +327 -0
- package/test/batch-background.test.ts +482 -0
- package/test/batch-blog-posts.test.ts +387 -0
- package/test/blog-generation.test.ts +510 -0
- package/test/browse-read.test.ts +611 -0
- package/test/core-functions.test.ts +694 -0
- package/test/decide.test.ts +393 -0
- package/test/define.test.ts +274 -0
- package/test/e2e-bedrock-manual.ts +163 -0
- package/test/e2e-bedrock.test.ts +191 -0
- package/test/e2e-flex-gateway.ts +157 -0
- package/test/e2e-flex-manual.ts +183 -0
- package/test/e2e-flex.test.ts +209 -0
- package/test/e2e-google-manual.ts +178 -0
- package/test/e2e-google.test.ts +216 -0
- package/test/embeddings.test.ts +284 -0
- package/test/evals/define-function.eval.test.ts +379 -0
- package/test/evals/primitives.eval.test.ts +384 -0
- package/test/function-types.test.ts +492 -0
- package/test/generate-core.test.ts +319 -0
- package/test/generate.test.ts +163 -0
- package/test/implicit-batch.test.ts +422 -0
- package/test/schema.test.ts +109 -0
- package/test/tagged-templates.test.ts +302 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +42 -0
- package/LICENSE +0 -21
- package/bin/cli.js +0 -5
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -38
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/index.test.d.ts +0 -2
- package/dist/cli/index.test.d.ts.map +0 -1
- package/dist/cli/index.test.js +0 -35
- package/dist/cli/index.test.js.map +0 -1
- package/dist/constants/models.d.ts +0 -10
- package/dist/constants/models.d.ts.map +0 -1
- package/dist/constants/models.js +0 -12
- package/dist/constants/models.js.map +0 -1
- package/dist/converters/index.d.ts +0 -3
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -3
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/model.d.ts +0 -4
- package/dist/converters/model.d.ts.map +0 -1
- package/dist/converters/model.js +0 -19
- package/dist/converters/model.js.map +0 -1
- package/dist/converters/schema.d.ts +0 -4
- package/dist/converters/schema.d.ts.map +0 -1
- package/dist/converters/schema.js +0 -25
- package/dist/converters/schema.js.map +0 -1
- package/dist/core/responses.d.ts +0 -5
- package/dist/core/responses.d.ts.map +0 -1
- package/dist/core/responses.js +0 -16
- package/dist/core/responses.js.map +0 -1
- package/dist/core/responses.test.d.ts +0 -2
- package/dist/core/responses.test.d.ts.map +0 -1
- package/dist/core/responses.test.js +0 -31
- package/dist/core/responses.test.js.map +0 -1
- package/dist/errors.d.ts +0 -6
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -9
- package/dist/errors.js.map +0 -1
- package/dist/examples/streaming.test.d.ts +0 -2
- package/dist/examples/streaming.test.d.ts.map +0 -1
- package/dist/examples/streaming.test.js +0 -176
- package/dist/examples/streaming.test.js.map +0 -1
- package/dist/factory/__tests__/index.test.d.ts +0 -2
- package/dist/factory/__tests__/index.test.d.ts.map +0 -1
- package/dist/factory/__tests__/index.test.js +0 -430
- package/dist/factory/__tests__/index.test.js.map +0 -1
- package/dist/factory/__tests__/list.test.d.ts +0 -2
- package/dist/factory/__tests__/list.test.d.ts.map +0 -1
- package/dist/factory/__tests__/list.test.js +0 -92
- package/dist/factory/__tests__/list.test.js.map +0 -1
- package/dist/factory/index.d.ts +0 -20
- package/dist/factory/index.d.ts.map +0 -1
- package/dist/factory/index.js +0 -287
- package/dist/factory/index.js.map +0 -1
- package/dist/factory/index.test.d.ts +0 -2
- package/dist/factory/index.test.d.ts.map +0 -1
- package/dist/factory/index.test.js +0 -287
- package/dist/factory/index.test.js.map +0 -1
- package/dist/factory/list.d.ts +0 -3
- package/dist/factory/list.d.ts.map +0 -1
- package/dist/factory/list.js +0 -221
- package/dist/factory/list.js.map +0 -1
- package/dist/factory/list.test.d.ts +0 -2
- package/dist/factory/list.test.d.ts.map +0 -1
- package/dist/factory/list.test.js +0 -84
- package/dist/factory/list.test.js.map +0 -1
- package/dist/generate/index.d.ts +0 -5
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -17
- package/dist/generate/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/list/await.d.ts +0 -3
- package/dist/list/await.d.ts.map +0 -1
- package/dist/list/await.js +0 -28
- package/dist/list/await.js.map +0 -1
- package/dist/list/constants.d.ts +0 -4
- package/dist/list/constants.d.ts.map +0 -1
- package/dist/list/constants.js +0 -5
- package/dist/list/constants.js.map +0 -1
- package/dist/list/create-function.d.ts +0 -3
- package/dist/list/create-function.d.ts.map +0 -1
- package/dist/list/create-function.js +0 -11
- package/dist/list/create-function.js.map +0 -1
- package/dist/list/index.d.ts +0 -4
- package/dist/list/index.d.ts.map +0 -1
- package/dist/list/index.js +0 -5
- package/dist/list/index.js.map +0 -1
- package/dist/list/prompt.d.ts +0 -3
- package/dist/list/prompt.d.ts.map +0 -1
- package/dist/list/prompt.js +0 -6
- package/dist/list/prompt.js.map +0 -1
- package/dist/list/schemas.d.ts +0 -4
- package/dist/list/schemas.d.ts.map +0 -1
- package/dist/list/schemas.js +0 -8
- package/dist/list/schemas.js.map +0 -1
- package/dist/list/stream.d.ts +0 -3
- package/dist/list/stream.d.ts.map +0 -1
- package/dist/list/stream.js +0 -33
- package/dist/list/stream.js.map +0 -1
- package/dist/list/types.d.ts +0 -11
- package/dist/list/types.d.ts.map +0 -1
- package/dist/list/types.js +0 -2
- package/dist/list/types.js.map +0 -1
- package/dist/list/validation.d.ts +0 -3
- package/dist/list/validation.d.ts.map +0 -1
- package/dist/list/validation.js +0 -12
- package/dist/list/validation.js.map +0 -1
- package/dist/providers/config.d.ts +0 -4
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -21
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/config.test.d.ts +0 -2
- package/dist/providers/config.test.d.ts.map +0 -1
- package/dist/providers/config.test.js +0 -37
- package/dist/providers/config.test.js.map +0 -1
- package/dist/proxy/constants.d.ts +0 -4
- package/dist/proxy/constants.d.ts.map +0 -1
- package/dist/proxy/constants.js +0 -5
- package/dist/proxy/constants.js.map +0 -1
- package/dist/proxy/create-function.d.ts +0 -4
- package/dist/proxy/create-function.d.ts.map +0 -1
- package/dist/proxy/create-function.js +0 -24
- package/dist/proxy/create-function.js.map +0 -1
- package/dist/proxy/create-proxy.d.ts +0 -2
- package/dist/proxy/create-proxy.d.ts.map +0 -1
- package/dist/proxy/create-proxy.js +0 -11
- package/dist/proxy/create-proxy.js.map +0 -1
- package/dist/proxy/function-generator.d.ts +0 -9
- package/dist/proxy/function-generator.d.ts.map +0 -1
- package/dist/proxy/function-generator.js +0 -29
- package/dist/proxy/function-generator.js.map +0 -1
- package/dist/proxy/index.d.ts +0 -4
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js +0 -4
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/prompt.d.ts +0 -2
- package/dist/proxy/prompt.d.ts.map +0 -1
- package/dist/proxy/prompt.js +0 -6
- package/dist/proxy/prompt.js.map +0 -1
- package/dist/proxy/types.d.ts +0 -7
- package/dist/proxy/types.d.ts.map +0 -1
- package/dist/proxy/types.js +0 -2
- package/dist/proxy/types.js.map +0 -1
- package/dist/queue/manager.d.ts +0 -5
- package/dist/queue/manager.d.ts.map +0 -1
- package/dist/queue/manager.js +0 -37
- package/dist/queue/manager.js.map +0 -1
- package/dist/queue/manager.test.d.ts +0 -2
- package/dist/queue/manager.test.d.ts.map +0 -1
- package/dist/queue/manager.test.js +0 -52
- package/dist/queue/manager.test.js.map +0 -1
- package/dist/schema-converter.d.ts +0 -4
- package/dist/schema-converter.d.ts.map +0 -1
- package/dist/schema-converter.js +0 -30
- package/dist/schema-converter.js.map +0 -1
- package/dist/stream/index.d.ts +0 -7
- package/dist/stream/index.d.ts.map +0 -1
- package/dist/stream/index.js +0 -23
- package/dist/stream/index.js.map +0 -1
- package/dist/streaming/utils.d.ts +0 -4
- package/dist/streaming/utils.d.ts.map +0 -1
- package/dist/streaming/utils.js +0 -131
- package/dist/streaming/utils.js.map +0 -1
- package/dist/streaming/utils.test.d.ts +0 -2
- package/dist/streaming/utils.test.d.ts.map +0 -1
- package/dist/streaming/utils.test.js +0 -84
- package/dist/streaming/utils.test.js.map +0 -1
- package/dist/templates/result.d.ts +0 -7
- package/dist/templates/result.d.ts.map +0 -1
- package/dist/templates/result.js +0 -40
- package/dist/templates/result.js.map +0 -1
- package/dist/templates/result.test.d.ts +0 -2
- package/dist/templates/result.test.d.ts.map +0 -1
- package/dist/templates/result.test.js +0 -75
- package/dist/templates/result.test.js.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/test/setup.js +0 -21
- package/dist/test/setup.js.map +0 -1
- package/dist/test-types.d.ts +0 -13
- package/dist/test-types.d.ts.map +0 -1
- package/dist/test-types.js +0 -55
- package/dist/test-types.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/list.d.ts +0 -10
- package/dist/types/list.d.ts.map +0 -1
- package/dist/types/list.js +0 -2
- package/dist/types/list.js.map +0 -1
- package/dist/types/model.d.ts +0 -7
- package/dist/types/model.d.ts.map +0 -1
- package/dist/types/model.js +0 -2
- package/dist/types/model.js.map +0 -1
- package/dist/types/options.d.ts +0 -25
- package/dist/types/options.d.ts.map +0 -1
- package/dist/types/options.js +0 -2
- package/dist/types/options.js.map +0 -1
- package/dist/types/schema.d.ts +0 -5
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/types/schema.js +0 -2
- package/dist/types/schema.js.map +0 -1
- package/dist/utils/__tests__/request-handler.test.d.ts +0 -2
- package/dist/utils/__tests__/request-handler.test.d.ts.map +0 -1
- package/dist/utils/__tests__/request-handler.test.js +0 -134
- package/dist/utils/__tests__/request-handler.test.js.map +0 -1
- package/dist/utils/__tests__/schema.test.d.ts +0 -2
- package/dist/utils/__tests__/schema.test.d.ts.map +0 -1
- package/dist/utils/__tests__/schema.test.js +0 -49
- package/dist/utils/__tests__/schema.test.js.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.d.ts +0 -2
- package/dist/utils/__tests__/stream-progress.test.d.ts.map +0 -1
- package/dist/utils/__tests__/stream-progress.test.js +0 -85
- package/dist/utils/__tests__/stream-progress.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/request-handler.d.ts +0 -17
- package/dist/utils/request-handler.d.ts.map +0 -1
- package/dist/utils/request-handler.js +0 -105
- package/dist/utils/request-handler.js.map +0 -1
- package/dist/utils/schema.d.ts +0 -11
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/schema.js +0 -51
- package/dist/utils/schema.js.map +0 -1
- package/dist/utils/stream-progress.d.ts +0 -17
- package/dist/utils/stream-progress.d.ts.map +0 -1
- package/dist/utils/stream-progress.js +0 -86
- package/dist/utils/stream-progress.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -30
- package/dist/utils/validation.js.map +0 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for function type inference from name + args
|
|
3
|
+
*
|
|
4
|
+
* The ai.* magic proxy infers function type based on:
|
|
5
|
+
* 1. Function name (e.g., fizzBuzz → Code, storyBrand → Generative)
|
|
6
|
+
* 2. Argument structure (e.g., { max: 100 } → Code parameters)
|
|
7
|
+
* 3. Argument values (e.g., { amount: 50000 } → Human approval needed)
|
|
8
|
+
*
|
|
9
|
+
* Function Types:
|
|
10
|
+
* - Generative: Generate content (text, json, lists, images)
|
|
11
|
+
* - Code: Generate and execute code
|
|
12
|
+
* - Agentic: Multi-step tasks with tools
|
|
13
|
+
* - Human: Requires human input or approval
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Type definitions
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
type FunctionType = 'generative' | 'code' | 'agentic' | 'human'
|
|
23
|
+
|
|
24
|
+
interface InferredFunction {
|
|
25
|
+
type: FunctionType
|
|
26
|
+
name: string
|
|
27
|
+
args: unknown
|
|
28
|
+
execute: () => Promise<unknown>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Mock type inference logic
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Infer function type from name patterns
|
|
37
|
+
*/
|
|
38
|
+
function inferTypeFromName(name: string): FunctionType | null {
|
|
39
|
+
// Code patterns - computing, algorithms, transformations
|
|
40
|
+
const codePatterns = [
|
|
41
|
+
/^(fizz|buzz|calculate|compute|sort|filter|map|reduce|transform)/i,
|
|
42
|
+
/^(validate|parse|format|convert|encode|decode|hash|compress)/i,
|
|
43
|
+
/^(find|search|match|replace|split|join|merge)/i,
|
|
44
|
+
/(algorithm|function|method|handler|processor)$/i,
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// Agentic patterns - tasks, workflows, automation
|
|
48
|
+
const agenticPatterns = [
|
|
49
|
+
/^(launch|deploy|publish|schedule|automate)/i,
|
|
50
|
+
/^(research|investigate|analyze|audit|monitor)/i,
|
|
51
|
+
/^(create|build|setup|configure|integrate)/i,
|
|
52
|
+
/(workflow|pipeline|process|task|job)$/i,
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
// Human patterns - approval, review, decisions
|
|
56
|
+
const humanPatterns = [
|
|
57
|
+
/^(approve|reject|review|verify|confirm)/i,
|
|
58
|
+
/^(decide|choose|select|pick)/i,
|
|
59
|
+
/(approval|review|confirmation)$/i,
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
// Check patterns in order of specificity
|
|
63
|
+
for (const pattern of humanPatterns) {
|
|
64
|
+
if (pattern.test(name)) return 'human'
|
|
65
|
+
}
|
|
66
|
+
for (const pattern of agenticPatterns) {
|
|
67
|
+
if (pattern.test(name)) return 'agentic'
|
|
68
|
+
}
|
|
69
|
+
for (const pattern of codePatterns) {
|
|
70
|
+
if (pattern.test(name)) return 'code'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Default to generative
|
|
74
|
+
return 'generative'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Infer function type from argument structure
|
|
79
|
+
*/
|
|
80
|
+
function inferTypeFromArgs(args: unknown): FunctionType | null {
|
|
81
|
+
if (typeof args !== 'object' || args === null) return null
|
|
82
|
+
|
|
83
|
+
const argObj = args as Record<string, unknown>
|
|
84
|
+
const keys = Object.keys(argObj)
|
|
85
|
+
|
|
86
|
+
// Code indicators - numeric parameters, input/output pairs
|
|
87
|
+
const codeIndicators = ['max', 'min', 'count', 'input', 'output', 'format', 'precision']
|
|
88
|
+
if (codeIndicators.some(k => keys.includes(k))) {
|
|
89
|
+
return 'code'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Agentic indicators - URLs, targets, configurations
|
|
93
|
+
const agenticIndicators = ['url', 'target', 'destination', 'webhook', 'schedule']
|
|
94
|
+
if (agenticIndicators.some(k => keys.includes(k))) {
|
|
95
|
+
return 'agentic'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Human indicators - requires explicit approval flags
|
|
99
|
+
const humanIndicators = ['requiresApproval', 'needsReview', 'manualStep']
|
|
100
|
+
if (humanIndicators.some(k => keys.includes(k))) {
|
|
101
|
+
return 'human'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Infer if human approval needed based on values (subjective judgment)
|
|
109
|
+
*/
|
|
110
|
+
function requiresHumanApproval(name: string, args: unknown): boolean {
|
|
111
|
+
if (typeof args !== 'object' || args === null) return false
|
|
112
|
+
|
|
113
|
+
const argObj = args as Record<string, unknown>
|
|
114
|
+
|
|
115
|
+
// High-value financial transactions
|
|
116
|
+
if ('amount' in argObj && typeof argObj.amount === 'number') {
|
|
117
|
+
if (argObj.amount > 10000) return true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sensitive operations
|
|
121
|
+
if ('action' in argObj) {
|
|
122
|
+
const sensitiveActions = ['delete', 'terminate', 'cancel', 'refund', 'transfer']
|
|
123
|
+
if (sensitiveActions.includes(String(argObj.action).toLowerCase())) {
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Large scale operations
|
|
129
|
+
if ('count' in argObj && typeof argObj.count === 'number') {
|
|
130
|
+
if (argObj.count > 1000) return true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// External communications
|
|
134
|
+
if (name.toLowerCase().includes('email') || name.toLowerCase().includes('notify')) {
|
|
135
|
+
if ('recipients' in argObj && Array.isArray(argObj.recipients)) {
|
|
136
|
+
if (argObj.recipients.length > 100) return true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Full type inference combining name, args, and values
|
|
145
|
+
*/
|
|
146
|
+
function inferFunctionType(name: string, args: unknown): FunctionType {
|
|
147
|
+
// First check if human approval is needed based on values
|
|
148
|
+
if (requiresHumanApproval(name, args)) {
|
|
149
|
+
return 'human'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Then check args structure
|
|
153
|
+
const argsType = inferTypeFromArgs(args)
|
|
154
|
+
if (argsType) return argsType
|
|
155
|
+
|
|
156
|
+
// Finally fall back to name inference
|
|
157
|
+
return inferTypeFromName(name) || 'generative'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Type inference from name tests
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
describe('type inference from function name', () => {
|
|
165
|
+
describe('Code function patterns', () => {
|
|
166
|
+
it('infers code type from algorithm names', () => {
|
|
167
|
+
expect(inferTypeFromName('fizzBuzz')).toBe('code')
|
|
168
|
+
expect(inferTypeFromName('calculateSum')).toBe('code')
|
|
169
|
+
expect(inferTypeFromName('computeHash')).toBe('code')
|
|
170
|
+
expect(inferTypeFromName('sortArray')).toBe('code')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('infers code type from transformation names', () => {
|
|
174
|
+
expect(inferTypeFromName('validateEmail')).toBe('code')
|
|
175
|
+
expect(inferTypeFromName('parseJSON')).toBe('code')
|
|
176
|
+
expect(inferTypeFromName('formatDate')).toBe('code')
|
|
177
|
+
expect(inferTypeFromName('convertCurrency')).toBe('code')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('infers code type from data operation names', () => {
|
|
181
|
+
expect(inferTypeFromName('findUser')).toBe('code')
|
|
182
|
+
expect(inferTypeFromName('searchRecords')).toBe('code')
|
|
183
|
+
expect(inferTypeFromName('filterProducts')).toBe('code')
|
|
184
|
+
expect(inferTypeFromName('mergeArrays')).toBe('code')
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('Generative function patterns', () => {
|
|
189
|
+
it('infers generative type from content names', () => {
|
|
190
|
+
expect(inferTypeFromName('storyBrand')).toBe('generative')
|
|
191
|
+
expect(inferTypeFromName('blogPost')).toBe('generative')
|
|
192
|
+
expect(inferTypeFromName('productDescription')).toBe('generative')
|
|
193
|
+
expect(inferTypeFromName('marketingCopy')).toBe('generative')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('defaults unknown names to generative', () => {
|
|
197
|
+
expect(inferTypeFromName('foo')).toBe('generative')
|
|
198
|
+
expect(inferTypeFromName('customThing')).toBe('generative')
|
|
199
|
+
expect(inferTypeFromName('randomName')).toBe('generative')
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('Agentic function patterns', () => {
|
|
204
|
+
it('infers agentic type from automation names', () => {
|
|
205
|
+
expect(inferTypeFromName('launchProduct')).toBe('agentic')
|
|
206
|
+
expect(inferTypeFromName('deployApplication')).toBe('agentic')
|
|
207
|
+
expect(inferTypeFromName('publishArticle')).toBe('agentic')
|
|
208
|
+
expect(inferTypeFromName('scheduleTask')).toBe('agentic')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('infers agentic type from research names', () => {
|
|
212
|
+
expect(inferTypeFromName('researchMarket')).toBe('agentic')
|
|
213
|
+
expect(inferTypeFromName('investigateIssue')).toBe('agentic')
|
|
214
|
+
expect(inferTypeFromName('analyzeCompetitors')).toBe('agentic')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('infers agentic type from workflow suffixes', () => {
|
|
218
|
+
expect(inferTypeFromName('onboardingWorkflow')).toBe('agentic')
|
|
219
|
+
expect(inferTypeFromName('deploymentPipeline')).toBe('agentic')
|
|
220
|
+
expect(inferTypeFromName('dataProcess')).toBe('agentic')
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
describe('Human function patterns', () => {
|
|
225
|
+
it('infers human type from approval names', () => {
|
|
226
|
+
expect(inferTypeFromName('approveRefund')).toBe('human')
|
|
227
|
+
expect(inferTypeFromName('rejectApplication')).toBe('human')
|
|
228
|
+
expect(inferTypeFromName('reviewCode')).toBe('human')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('infers human type from decision names', () => {
|
|
232
|
+
expect(inferTypeFromName('decideOutcome')).toBe('human')
|
|
233
|
+
expect(inferTypeFromName('chooseOption')).toBe('human')
|
|
234
|
+
expect(inferTypeFromName('selectWinner')).toBe('human')
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Type inference from args tests
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
describe('type inference from argument structure', () => {
|
|
244
|
+
describe('Code argument patterns', () => {
|
|
245
|
+
it('infers code from numeric parameters', () => {
|
|
246
|
+
expect(inferTypeFromArgs({ max: 100 })).toBe('code')
|
|
247
|
+
expect(inferTypeFromArgs({ min: 0, max: 100 })).toBe('code')
|
|
248
|
+
expect(inferTypeFromArgs({ count: 10 })).toBe('code')
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('infers code from input/output pairs', () => {
|
|
252
|
+
expect(inferTypeFromArgs({ input: 'data', format: 'json' })).toBe('code')
|
|
253
|
+
expect(inferTypeFromArgs({ output: 'file.txt' })).toBe('code')
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('Agentic argument patterns', () => {
|
|
258
|
+
it('infers agentic from URL parameters', () => {
|
|
259
|
+
expect(inferTypeFromArgs({ url: 'https://example.com' })).toBe('agentic')
|
|
260
|
+
expect(inferTypeFromArgs({ target: 'https://api.example.com' })).toBe('agentic')
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('infers agentic from webhook/schedule parameters', () => {
|
|
264
|
+
expect(inferTypeFromArgs({ webhook: 'https://hooks.example.com' })).toBe('agentic')
|
|
265
|
+
expect(inferTypeFromArgs({ schedule: '0 9 * * *' })).toBe('agentic')
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
describe('Human argument patterns', () => {
|
|
270
|
+
it('infers human from explicit approval flags', () => {
|
|
271
|
+
expect(inferTypeFromArgs({ requiresApproval: true })).toBe('human')
|
|
272
|
+
expect(inferTypeFromArgs({ needsReview: true })).toBe('human')
|
|
273
|
+
expect(inferTypeFromArgs({ manualStep: true })).toBe('human')
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
describe('No inference from args', () => {
|
|
278
|
+
it('returns null for unrecognized arg patterns', () => {
|
|
279
|
+
expect(inferTypeFromArgs({ name: 'John' })).toBeNull()
|
|
280
|
+
expect(inferTypeFromArgs({ text: 'Hello' })).toBeNull()
|
|
281
|
+
expect(inferTypeFromArgs({})).toBeNull()
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('returns null for non-object args', () => {
|
|
285
|
+
expect(inferTypeFromArgs('string')).toBeNull()
|
|
286
|
+
expect(inferTypeFromArgs(123)).toBeNull()
|
|
287
|
+
expect(inferTypeFromArgs(null)).toBeNull()
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Human approval from values (subjective judgment)
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
describe('human approval from values (subjective judgment)', () => {
|
|
297
|
+
describe('high-value transactions', () => {
|
|
298
|
+
it('requires human approval for large amounts', () => {
|
|
299
|
+
expect(requiresHumanApproval('processRefund', { amount: 50000 })).toBe(true)
|
|
300
|
+
expect(requiresHumanApproval('processRefund', { amount: 12.99 })).toBe(false)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('threshold is $10,000', () => {
|
|
304
|
+
expect(requiresHumanApproval('transfer', { amount: 9999 })).toBe(false)
|
|
305
|
+
expect(requiresHumanApproval('transfer', { amount: 10001 })).toBe(true)
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
describe('sensitive actions', () => {
|
|
310
|
+
it('requires approval for destructive actions', () => {
|
|
311
|
+
expect(requiresHumanApproval('performAction', { action: 'delete' })).toBe(true)
|
|
312
|
+
expect(requiresHumanApproval('performAction', { action: 'terminate' })).toBe(true)
|
|
313
|
+
expect(requiresHumanApproval('performAction', { action: 'cancel' })).toBe(true)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('does not require approval for safe actions', () => {
|
|
317
|
+
expect(requiresHumanApproval('performAction', { action: 'read' })).toBe(false)
|
|
318
|
+
expect(requiresHumanApproval('performAction', { action: 'list' })).toBe(false)
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
describe('large scale operations', () => {
|
|
323
|
+
it('requires approval for operations affecting many items', () => {
|
|
324
|
+
expect(requiresHumanApproval('bulkOperation', { count: 5000 })).toBe(true)
|
|
325
|
+
expect(requiresHumanApproval('bulkOperation', { count: 100 })).toBe(false)
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
describe('mass communications', () => {
|
|
330
|
+
it('requires approval for mass emails', () => {
|
|
331
|
+
const manyRecipients = Array(500).fill('user@example.com')
|
|
332
|
+
expect(requiresHumanApproval('sendEmail', { recipients: manyRecipients })).toBe(true)
|
|
333
|
+
|
|
334
|
+
const fewRecipients = ['user1@example.com', 'user2@example.com']
|
|
335
|
+
expect(requiresHumanApproval('sendEmail', { recipients: fewRecipients })).toBe(false)
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Combined inference tests
|
|
342
|
+
// ============================================================================
|
|
343
|
+
|
|
344
|
+
describe('combined function type inference', () => {
|
|
345
|
+
it('prioritizes human approval based on values', () => {
|
|
346
|
+
// Even though name suggests code, high amount triggers human
|
|
347
|
+
expect(inferFunctionType('processRefund', { amount: 50000 })).toBe('human')
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('uses args when name is ambiguous', () => {
|
|
351
|
+
expect(inferFunctionType('handleData', { max: 100 })).toBe('code')
|
|
352
|
+
expect(inferFunctionType('handleData', { url: 'https://api.com' })).toBe('agentic')
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('falls back to name when args are generic', () => {
|
|
356
|
+
expect(inferFunctionType('fizzBuzz', { n: 100 })).toBe('code')
|
|
357
|
+
expect(inferFunctionType('storyBrand', { hero: 'developers' })).toBe('generative')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
describe('README examples', () => {
|
|
361
|
+
it('ai.fizzBuzz({ max: 100 }) → code', () => {
|
|
362
|
+
expect(inferFunctionType('fizzBuzz', { max: 100 })).toBe('code')
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('ai.storyBrand({ hero: "developers" }) → generative', () => {
|
|
366
|
+
expect(inferFunctionType('storyBrand', { hero: 'developers' })).toBe('generative')
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('ai.launchProductHunt({ product }) → agentic', () => {
|
|
370
|
+
expect(inferFunctionType('launchProductHunt', { product: {} })).toBe('agentic')
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('ai.processRefund({ amount: 12.99 }) → generative (small amount, automated)', () => {
|
|
374
|
+
// Small refund can be automated - falls through to generative
|
|
375
|
+
// The key insight is it's NOT human (which would be triggered by high amounts)
|
|
376
|
+
expect(inferFunctionType('processRefund', { amount: 12.99 })).toBe('generative')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('ai.processRefund({ amount: 50000 }) → human (large amount)', () => {
|
|
380
|
+
// Large refund needs human approval
|
|
381
|
+
expect(inferFunctionType('processRefund', { amount: 50000 })).toBe('human')
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// Magic proxy behavior tests
|
|
388
|
+
// ============================================================================
|
|
389
|
+
|
|
390
|
+
describe('magic proxy ai.* behavior', () => {
|
|
391
|
+
// Mock the magic proxy
|
|
392
|
+
const mockDefine = vi.fn()
|
|
393
|
+
const mockExecute = vi.fn()
|
|
394
|
+
|
|
395
|
+
function createMockAiProxy() {
|
|
396
|
+
return new Proxy(
|
|
397
|
+
{},
|
|
398
|
+
{
|
|
399
|
+
get(_target, prop) {
|
|
400
|
+
if (typeof prop !== 'string') return undefined
|
|
401
|
+
|
|
402
|
+
// Return a function that infers type and executes
|
|
403
|
+
return (args: unknown) => {
|
|
404
|
+
const type = inferFunctionType(prop, args)
|
|
405
|
+
mockDefine(prop, type, args)
|
|
406
|
+
return mockExecute(prop, type, args)
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
beforeEach(() => {
|
|
414
|
+
mockDefine.mockReset()
|
|
415
|
+
mockExecute.mockReset()
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('infers type when accessing property', async () => {
|
|
419
|
+
const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
|
|
420
|
+
|
|
421
|
+
mockExecute.mockResolvedValue('1, 2, Fizz, 4, Buzz...')
|
|
422
|
+
|
|
423
|
+
await ai.fizzBuzz({ max: 100 })
|
|
424
|
+
|
|
425
|
+
expect(mockDefine).toHaveBeenCalledWith('fizzBuzz', 'code', { max: 100 })
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('creates new function for each call', async () => {
|
|
429
|
+
const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
|
|
430
|
+
|
|
431
|
+
mockExecute.mockResolvedValue('result')
|
|
432
|
+
|
|
433
|
+
await ai.functionA({ a: 1 })
|
|
434
|
+
await ai.functionB({ b: 2 })
|
|
435
|
+
|
|
436
|
+
expect(mockDefine).toHaveBeenCalledTimes(2)
|
|
437
|
+
expect(mockDefine).toHaveBeenNthCalledWith(1, 'functionA', expect.any(String), { a: 1 })
|
|
438
|
+
expect(mockDefine).toHaveBeenNthCalledWith(2, 'functionB', expect.any(String), { b: 2 })
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('same function name can have different types based on args', async () => {
|
|
442
|
+
const ai = createMockAiProxy() as Record<string, (args: unknown) => Promise<unknown>>
|
|
443
|
+
|
|
444
|
+
mockExecute.mockResolvedValue('result')
|
|
445
|
+
|
|
446
|
+
// Same name, different args → different types
|
|
447
|
+
await ai.process({ max: 100 }) // code (due to max)
|
|
448
|
+
await ai.process({ url: 'https://api.com' }) // agentic (due to url)
|
|
449
|
+
await ai.process({ amount: 50000 }) // human (due to high amount)
|
|
450
|
+
|
|
451
|
+
expect(mockDefine).toHaveBeenNthCalledWith(1, 'process', 'code', { max: 100 })
|
|
452
|
+
expect(mockDefine).toHaveBeenNthCalledWith(2, 'process', 'agentic', { url: 'https://api.com' })
|
|
453
|
+
expect(mockDefine).toHaveBeenNthCalledWith(3, 'process', 'human', { amount: 50000 })
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// Function type execution behavior
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
describe('function type execution behavior', () => {
|
|
462
|
+
it('documents expected behavior per type', () => {
|
|
463
|
+
const typeBehaviors = {
|
|
464
|
+
generative: {
|
|
465
|
+
description: 'Generate content',
|
|
466
|
+
examples: ['ai.storyBrand()', 'ai.blogPost()', 'ai.productDescription()'],
|
|
467
|
+
returns: 'Generated content (text, JSON, etc.)',
|
|
468
|
+
},
|
|
469
|
+
code: {
|
|
470
|
+
description: 'Generate and execute code',
|
|
471
|
+
examples: ['ai.fizzBuzz()', 'ai.validateEmail()', 'ai.sortArray()'],
|
|
472
|
+
returns: 'Execution result',
|
|
473
|
+
integration: 'Uses ai-sandbox evaluate()',
|
|
474
|
+
},
|
|
475
|
+
agentic: {
|
|
476
|
+
description: 'Multi-step tasks with tools',
|
|
477
|
+
examples: ['ai.launchProductHunt()', 'ai.researchCompetitors()'],
|
|
478
|
+
returns: 'Task completion result',
|
|
479
|
+
features: ['Tool use', 'Multi-step execution', 'External integrations'],
|
|
480
|
+
},
|
|
481
|
+
human: {
|
|
482
|
+
description: 'Requires human input or approval',
|
|
483
|
+
examples: ['ai.approveRefund()', 'ai.processRefund({ amount: 50000 })'],
|
|
484
|
+
returns: 'Result after human approval',
|
|
485
|
+
flow: ['Generate proposal', 'Wait for approval', 'Execute if approved'],
|
|
486
|
+
},
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
expect(Object.keys(typeBehaviors)).toHaveLength(4)
|
|
490
|
+
expect(typeBehaviors.code.integration).toBe('Uses ai-sandbox evaluate()')
|
|
491
|
+
})
|
|
492
|
+
})
|