@outputai/core 0.15.1-next.15a30ef.0 → 0.15.1-next.2156c4b.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 (39) hide show
  1. package/package.json +49 -5
  2. package/src/helpers/path.js +36 -0
  3. package/src/llm/agent.js +141 -0
  4. package/src/llm/ai_provider.js +131 -0
  5. package/src/llm/ai_sdk_options.js +126 -0
  6. package/src/llm/consts.js +6 -0
  7. package/src/llm/deprecated_provider_aliases.js +8 -0
  8. package/src/llm/generate.js +88 -0
  9. package/src/llm/index.d.ts +631 -0
  10. package/src/llm/index.js +5 -0
  11. package/src/llm/prompt/content.js +70 -0
  12. package/src/llm/prompt/interpolations.js +58 -0
  13. package/src/llm/prompt/loader.js +67 -0
  14. package/src/llm/prompt/markup/attributes.js +52 -0
  15. package/src/llm/prompt/markup/nodes.js +71 -0
  16. package/src/llm/prompt/markup/tokenizer.js +3 -0
  17. package/src/llm/prompt/markup/tokens.js +26 -0
  18. package/src/llm/prompt/validations.js +129 -0
  19. package/src/llm/utils/cost.js +150 -0
  20. package/src/llm/utils/error_handler.js +53 -0
  21. package/src/llm/utils/file.js +42 -0
  22. package/src/llm/utils/google_vertex_auth.js +31 -0
  23. package/src/llm/utils/grounding.js +49 -0
  24. package/src/llm/utils/image.js +37 -0
  25. package/src/llm/utils/legacy_cost_attribute.js +75 -0
  26. package/src/llm/utils/metering.js +126 -0
  27. package/src/llm/utils/models.js +15 -0
  28. package/src/llm/utils/models_pricing.js +93 -0
  29. package/src/llm/utils/models_pricing_snapshot.json +4166 -0
  30. package/src/llm/utils/skills.js +74 -0
  31. package/src/llm/utils/sources.js +73 -0
  32. package/src/llm/utils/stream.js +75 -0
  33. package/src/llm/utils/tools.js +48 -0
  34. package/src/llm/utils/usage.js +156 -0
  35. package/src/llm/utils/usage_tools.js +23 -0
  36. package/src/llm/utils/wrap.js +277 -0
  37. package/src/llm/validations.js +234 -0
  38. package/src/sdk/helpers/path.d.ts +2 -0
  39. package/src/sdk/helpers/path.js +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.15.1-next.15a30ef.0",
3
+ "version": "0.15.1-next.2156c4b.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "engines": {
@@ -34,6 +34,10 @@
34
34
  "./http": {
35
35
  "types": "./src/http/index.d.ts",
36
36
  "import": "./src/http/index.js"
37
+ },
38
+ "./llm": {
39
+ "types": "./src/llm/index.d.ts",
40
+ "import": "./src/llm/index.js"
37
41
  }
38
42
  },
39
43
  "files": [
@@ -41,7 +45,8 @@
41
45
  "./dist",
42
46
  "./bin",
43
47
  "!**/*.spec.*",
44
- "!src/credentials"
48
+ "!src/credentials",
49
+ "!src/llm/fixtures"
45
50
  ],
46
51
  "bin": {
47
52
  "output-worker": "./bin/worker.sh",
@@ -62,9 +67,13 @@
62
67
  "@temporalio/common": "1.24.0",
63
68
  "@temporalio/worker": "1.24.0",
64
69
  "@temporalio/workflow": "1.24.0",
70
+ "decimal.js": "10.6.0",
71
+ "entities": "8.1.0",
65
72
  "folder-hash": "4.1.3",
73
+ "gray-matter": "4.0.3",
66
74
  "js-yaml": "5.4.2",
67
75
  "json-stream-stringify": "3.1.7",
76
+ "liquidjs": "10.29.0",
68
77
  "redis": "5.12.1",
69
78
  "stacktrace-parser": "0.1.11",
70
79
  "winston": "3.19.0"
@@ -72,13 +81,47 @@
72
81
  "peerDependencies": {
73
82
  "zod": ">=4.3 <5",
74
83
  "ky": ">=2 <3",
75
- "undici": ">=8 <9"
84
+ "undici": ">=8 <9",
85
+ "ai": ">=7 <8",
86
+ "@ai-sdk/amazon-bedrock": ">=5 <6",
87
+ "@ai-sdk/anthropic": ">=4 <5",
88
+ "@ai-sdk/azure": ">=4 <5",
89
+ "@ai-sdk/google-vertex": ">=5 <6",
90
+ "@ai-sdk/openai": ">=4 <5",
91
+ "@ai-sdk/perplexity": ">=4 <5"
92
+ },
93
+ "peerDependenciesMeta": {
94
+ "@ai-sdk/amazon-bedrock": {
95
+ "optional": true
96
+ },
97
+ "@ai-sdk/anthropic": {
98
+ "optional": true
99
+ },
100
+ "@ai-sdk/azure": {
101
+ "optional": true
102
+ },
103
+ "@ai-sdk/google-vertex": {
104
+ "optional": true
105
+ },
106
+ "@ai-sdk/openai": {
107
+ "optional": true
108
+ },
109
+ "@ai-sdk/perplexity": {
110
+ "optional": true
111
+ }
76
112
  },
77
113
  "devDependencies": {
78
114
  "@types/js-yaml": "4.0.9",
79
115
  "zod": "4.6.5",
80
116
  "ky": "2.0.2",
81
- "undici": "8.11.0"
117
+ "undici": "8.10.2",
118
+ "ai": "7.0.112",
119
+ "@ai-sdk/amazon-bedrock": "5.0.92",
120
+ "@ai-sdk/anthropic": "4.0.61",
121
+ "@ai-sdk/azure": "4.0.77",
122
+ "@ai-sdk/google-vertex": "5.0.91",
123
+ "@ai-sdk/openai": "4.0.73",
124
+ "@ai-sdk/perplexity": "4.0.49"
82
125
  },
83
126
  "license": "Apache-2.0",
84
127
  "publishConfig": {
@@ -101,6 +144,7 @@
101
144
  },
102
145
  "scripts": {
103
146
  "worker": "node ./src/worker/index.js",
104
- "build": "rm -rf ./dist && tsc && pnpm exec tsc -p ./tsconfig.typecheck.json"
147
+ "build": "rm -rf ./dist && tsc && pnpm exec tsc -p ./tsconfig.typecheck.json",
148
+ "take-models-pricing-snapshot": "node ./scripts/take_models_pricing_snapshot.js"
105
149
  }
106
150
  }
@@ -0,0 +1,36 @@
1
+ import * as stackTraceParser from 'stacktrace-parser';
2
+
3
+ // OS separator, but in a deterministic way, allowing this to work in Temporal's sandbox
4
+ // This avoids importing from node:path
5
+ const SEP = new Error().stack.includes( '/' ) ? '/' : '\\';
6
+ const transformSeparators = path => path.replaceAll( '/', SEP );
7
+
8
+ const defaultIgnorePaths = [
9
+ '/@outputai/core/',
10
+ '/@outputai/evals/',
11
+ '/sdk/core/',
12
+ '/sdk/evals/',
13
+ 'node:internal/',
14
+ 'evalmachine.',
15
+ 'webpack/bootstrap'
16
+ ];
17
+
18
+ /**
19
+ * Return the first immediate directory of the file invoking the code that called this function.
20
+ *
21
+ * Excludes `@outputai/core`, node, other internal paths, and any additional ignore paths.
22
+ *
23
+ * @param {string[]} [additionalIgnorePaths]
24
+ * @returns {string}
25
+ */
26
+ export const resolveInvocationDir = ( additionalIgnorePaths = [] ) => {
27
+ const stack = new Error().stack;
28
+ const lines = stackTraceParser.parse( stack );
29
+ const ignorePaths = [ ...additionalIgnorePaths, ...defaultIgnorePaths ].map( transformSeparators );
30
+
31
+ const frame = lines.find( l => !ignorePaths.some( p => l.file.includes( p ) ) );
32
+ if ( !frame ) {
33
+ throw new Error( `Invocation dir resolution via stack trace failed. Stack: ${stack}` );
34
+ }
35
+ return frame.file.replace( 'file://', '' ).split( SEP ).slice( 0, -1 ).join( SEP );
36
+ };
@@ -0,0 +1,141 @@
1
+ import { ToolLoopAgent as AIToolLoopAgent } from 'ai';
2
+ import { loadAiSdkTextOptions } from './ai_sdk_options.js';
3
+ import { wrapTextGeneration, wrapStream } from './utils/wrap.js';
4
+ import { Role } from './consts.js';
5
+ import { drainStream } from './utils/stream.js';
6
+ import { loadPrompt } from './prompt/loader.js';
7
+ import { loadSkills } from './utils/skills.js';
8
+ import * as Validator from './validations.js';
9
+ import { Logger } from '#runtime_logger';
10
+
11
+ export class Agent {
12
+ #agent;
13
+ #prompt;
14
+ #initialMessages;
15
+ #store;
16
+
17
+ constructor( args ) {
18
+ const { promptFile, promptObject, promptDir, variables, tools, output, stopWhen, messageStore } = Validator.parseAgentArgs( args );
19
+
20
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
21
+ const skills = loadSkills( prompt );
22
+
23
+ const { instructions, messages, ...aiOptions } = loadAiSdkTextOptions( { prompt, skills, tools, output, stopWhen } );
24
+
25
+ this.#agent = new AIToolLoopAgent( {
26
+ ...aiOptions,
27
+ ...( instructions.length > 0 ? { instructions } : {} )
28
+ } );
29
+
30
+ this.#prompt = prompt;
31
+ // `messages` is system-free but may still hold authored <assistant> blocks;
32
+ // seed only <user> turns into each generate()/stream() call.
33
+ this.#initialMessages = messages.filter( m => m.role === Role.USER );
34
+ this.#store = messageStore ?? null;
35
+ }
36
+
37
+ async #combineWithPreviousMessages( messages ) {
38
+ return [ ...this.#initialMessages, ...( await this.#store?.getMessages() ?? [] ), ...messages ];
39
+ }
40
+
41
+ async #storeMessages( messages ) {
42
+ if ( this.#store ) {
43
+ await this.#store.addMessages( messages );
44
+ }
45
+ }
46
+
47
+ async generate( args ) {
48
+ const { messages, abortSignal, toolChoice } = Validator.parseAgentGenerateArgs( args );
49
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
50
+
51
+ return wrapTextGeneration( {
52
+ name: 'Agent.generate',
53
+ prompt: this.#prompt,
54
+ fn: async ( { onStepEndHook } ) => {
55
+ const response = await this.#agent.generate( {
56
+ messages: combinedMessages,
57
+ allowSystemInMessages: true,
58
+ ...( abortSignal && { abortSignal } ),
59
+ ...( toolChoice && { toolChoice } ),
60
+ onStepEnd: onStepEndHook
61
+ } );
62
+ if ( response.finishReason !== 'error' ) {
63
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
64
+ }
65
+ return response;
66
+ }
67
+ } );
68
+ }
69
+
70
+ /**
71
+ * Generates a completed agent response over streaming transport, invoking `onChunk` as parts arrive.
72
+ */
73
+ async generateWithStreaming( args ) {
74
+ const { messages, abortSignal, toolChoice, onChunk } = Validator.parseAgentGenerateWithStreamingArgs( args );
75
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
76
+
77
+ return wrapTextGeneration( {
78
+ name: 'Agent.generateWithStreaming',
79
+ prompt: this.#prompt,
80
+ fn: async ( { onStepEndHook } ) => {
81
+ const state = { response: null };
82
+ const stream = await this.#agent.stream( {
83
+ messages: combinedMessages,
84
+ allowSystemInMessages: true,
85
+ ...( onChunk && { onChunk } ),
86
+ ...( abortSignal && { abortSignal } ),
87
+ ...( toolChoice && { toolChoice } ),
88
+ onStepEnd: onStepEndHook,
89
+ onEnd: res => {
90
+ state.response = res;
91
+ },
92
+ onError: _ => {} // Suppress AI-SDK console printing
93
+ } );
94
+
95
+ await drainStream( stream, abortSignal );
96
+
97
+ if ( !state.response ) {
98
+ throw new Error( 'Streaming completed without a response.' );
99
+ }
100
+
101
+ state.response.output = await stream.output;
102
+ if ( state.response.finishReason !== 'error' ) {
103
+ await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
104
+ }
105
+
106
+ return state.response;
107
+ }
108
+ } );
109
+ }
110
+
111
+ async stream( args ) {
112
+ const { messages, abortSignal, toolChoice, onChunk, onEnd, onError } = Validator.parseAgentStreamArgs( args );
113
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
114
+
115
+ return wrapStream( {
116
+ name: 'Agent.stream',
117
+ prompt: this.#prompt,
118
+ abortSignal,
119
+ fn: ( { onEndHook, onErrorHook, onStepEndHook, onAbortHook } ) => this.#agent.stream( {
120
+ messages: combinedMessages,
121
+ allowSystemInMessages: true,
122
+ ...( onChunk && { onChunk } ),
123
+ ...( abortSignal && { abortSignal } ),
124
+ ...( toolChoice && { toolChoice } ),
125
+ onStepEnd: onStepEndHook,
126
+ onAbort: onAbortHook,
127
+ onEnd: response =>
128
+ onEndHook( response, async parsedResponse => {
129
+ if ( response.finishReason !== 'error' ) {
130
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) )
131
+ .catch( error =>
132
+ Logger.error( 'Message store persistence failed', { namespace: 'LLM', error: error?.message || String( error ) } )
133
+ );
134
+ }
135
+ await onEnd?.( parsedResponse );
136
+ } ),
137
+ onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
138
+ } )
139
+ } );
140
+ }
141
+ }
@@ -0,0 +1,131 @@
1
+ import { z } from 'zod';
2
+ import { FatalError, ValidationError } from '#errors';
3
+ import { EnvHttpProxyAgent, fetch } from 'undici';
4
+ import { deprecatedProviderAliases } from './deprecated_provider_aliases.js';
5
+ import { resolveGoogleVertexAuthOptions } from './utils/google_vertex_auth.js';
6
+
7
+ /** This custom dispatcher has longer timeouts. */
8
+ const customDispatcher = new EnvHttpProxyAgent( {
9
+ headersTimeout: 15 * 60 * 1000, // 15 min
10
+ bodyTimeout: 15 * 60 * 1000,
11
+ allowH2: false // Ignore HTTP/2. Check: https://github.com/growthxai/output/issues/299
12
+ } );
13
+
14
+ /** This custom fetch instance uses the custom dispatcher */
15
+ const customFetch = ( input, init ) => fetch( input, { dispatcher: customDispatcher, ...init } );
16
+
17
+ /** Shipped providers. Their packages are optional peer dependencies, so any of them may be missing. */
18
+ const shippedProviders = {
19
+ 'amazon-bedrock': {
20
+ pkg: '@ai-sdk/amazon-bedrock',
21
+ load: async () => ( await import( '@ai-sdk/amazon-bedrock' ) ).createAmazonBedrock
22
+ },
23
+ anthropic: {
24
+ pkg: '@ai-sdk/anthropic',
25
+ load: async () => ( await import( '@ai-sdk/anthropic' ) ).createAnthropic
26
+ },
27
+ azure: {
28
+ pkg: '@ai-sdk/azure',
29
+ load: async () => ( await import( '@ai-sdk/azure' ) ).createAzure
30
+ },
31
+ 'google-vertex': {
32
+ pkg: '@ai-sdk/google-vertex',
33
+ load: async () => {
34
+ const { createVertex } = await import( '@ai-sdk/google-vertex' );
35
+ return options => createVertex( { ...options, ...resolveGoogleVertexAuthOptions() } );
36
+ }
37
+ },
38
+ openai: {
39
+ pkg: '@ai-sdk/openai',
40
+ load: async () => ( await import( '@ai-sdk/openai' ) ).createOpenAI
41
+ },
42
+ perplexity: {
43
+ pkg: '@ai-sdk/perplexity',
44
+ load: async () => ( await import( '@ai-sdk/perplexity' ) ).createPerplexity
45
+ }
46
+ };
47
+
48
+ /** Load a shipped provider initializer, or null when its package is not installed. */
49
+ const loadInitializer = async ( { pkg, load } ) => {
50
+ try {
51
+ return await load();
52
+ } catch ( error ) {
53
+ if ( error?.code === 'ERR_MODULE_NOT_FOUND' && String( error.message ).includes( `'${pkg}'` ) ) {
54
+ return null;
55
+ }
56
+ throw error;
57
+ }
58
+ };
59
+
60
+ /** Available provider to initialize: the shipped providers whose package is installed. */
61
+ const providerInitializers = Object.fromEntries(
62
+ ( await Promise.all( Object.entries( shippedProviders ).map( async ( [ name, provider ] ) => [ name, await loadInitializer( provider ) ] ) ) )
63
+ .filter( ( [ , initializer ] ) => initializer )
64
+ );
65
+
66
+ /** Providers already initialized due usage */
67
+ const initializedProviders = {};
68
+
69
+ /** Providers registered by the user */
70
+ const registeredProviders = {};
71
+
72
+ /**
73
+ * Get all available provider names: shipped providers whose package is installed, plus registered ones.
74
+ * @returns {string[]} Provider names
75
+ */
76
+ export const getProviderNames = () =>
77
+ new Set( Object.keys( providerInitializers ).concat( Object.keys( registeredProviders ) ) ).values().toArray();
78
+
79
+ const registerProviderSchema = z.object( {
80
+ name: z.string().min( 1, 'Provider name must be a non-empty string' ),
81
+ providerFn: z.function()
82
+ } );
83
+
84
+ /**
85
+ * Register or override an AI SDK provider factory by name.
86
+ * @param {string} name - Provider name used in prompt frontmatter
87
+ * @param {Function} providerFn - Factory function that receives a model id
88
+ * @returns {void}
89
+ */
90
+ export const registerProvider = ( name, providerFn ) => {
91
+ const result = registerProviderSchema.safeParse( { name, providerFn } );
92
+ if ( !result.success ) {
93
+ throw new ValidationError( `Invalid provider registration: ${z.prettifyError( result.error )}` );
94
+ }
95
+ if ( Object.hasOwn( deprecatedProviderAliases, name ) ) {
96
+ const canonical = deprecatedProviderAliases[name];
97
+ throw new ValidationError(
98
+ `Cannot register provider "${name}": that name is a deprecated alias for "${canonical}". Register "${canonical}" instead.`
99
+ );
100
+ }
101
+ registeredProviders[name] = providerFn;
102
+ };
103
+
104
+ /**
105
+ * Return a provider by its name.
106
+ * Look for registered providers first.
107
+ * If none, looks for initialized providers.
108
+ * Finally, looks for available provider initializers, and if found, init it.
109
+ *
110
+ * @param {string} name
111
+ * @returns {object} provider
112
+ */
113
+ export const getProvider = name => {
114
+ const provider = registeredProviders[name] ?? initializedProviders[name];
115
+
116
+ if ( provider ) {
117
+ return provider;
118
+ }
119
+ if ( providerInitializers[name] ) {
120
+ try {
121
+ return initializedProviders[name] = providerInitializers[name]( { fetch: customFetch } );
122
+ } catch ( error ) {
123
+ throw new FatalError( `Failed to initialize provider "${name}": ${error.message}`, { cause: error } );
124
+ }
125
+ }
126
+ if ( shippedProviders[name] ) {
127
+ throw new FatalError( `Provider "${name}" requires the "${shippedProviders[name].pkg}" package, which is not installed` );
128
+ }
129
+
130
+ throw new FatalError( `Unsupported provider "${name}"` );
131
+ };
@@ -0,0 +1,126 @@
1
+ import { loadImageModel, loadTextModel } from './utils/models.js';
2
+ import { buildLoadSkillTool, loadPromptTools } from './utils/tools.js';
3
+ import { Role } from './consts.js';
4
+ import { FatalError } from '#errors';
5
+ import { isStepCount } from 'ai';
6
+
7
+ const buildSkillsMessageContent = skills =>
8
+ 'Available skills (use load_skill to get full instructions):\n' +
9
+ skills.map( s => `- ${s.name}: ${s.description}` ).join( '\n' );
10
+
11
+ const textPromptConfigKeys = [
12
+ 'frequencyPenalty',
13
+ 'maxOutputTokens',
14
+ 'presencePenalty',
15
+ 'providerOptions',
16
+ 'seed',
17
+ 'stopSequences',
18
+ 'temperature',
19
+ 'topK',
20
+ 'topP'
21
+ ];
22
+
23
+ const imagePromptConfigKeys = [
24
+ 'aspectRatio',
25
+ 'maxImagesPerCall',
26
+ 'n',
27
+ 'providerOptions',
28
+ 'seed',
29
+ 'size'
30
+ ];
31
+
32
+ const select = ( keys, target ) =>
33
+ Object.fromEntries( Object.entries( target ).filter( ( [ k, v ] ) => keys.includes( k ) && v !== undefined ) );
34
+
35
+ /**
36
+ * Build options for AI SDK text generation.
37
+ *
38
+ * Splits system vs chat messages, applies prompt generation config, and merges tools
39
+ * with a step limit when any tools are present. Always sets `maxRetries` to 0.
40
+ *
41
+ * @param {object} args
42
+ * @param {object} args.prompt - Loaded prompt object
43
+ * @param {object} [args.tools] - Caller tools
44
+ * @param {Skill[]} args.skills - Resolved skills
45
+ * @param {unknown} [args.stopWhen] - Caller stop condition; otherwise prompt `maxSteps` when tools exist
46
+ * @param {unknown} [args.output] - Structured output spec
47
+ * @param {unknown} [args.toolChoice] - Tool choice, set only when tools exist
48
+ * @param {AbortSignal} [args.abortSignal] - Abort signal for the request
49
+ * @returns {object} AI SDK text options
50
+ */
51
+ export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output, toolChoice, abortSignal } ) => {
52
+ if ( prompt.messages.length === 0 ) {
53
+ throw new FatalError( `Prompt "${prompt.name}" has no chat-style messages. Add role-tagged blocks like <system> or <user>.` );
54
+ }
55
+
56
+ const instructions = prompt.messages.filter( m => m.role === Role.SYSTEM );
57
+ const messages = prompt.messages.filter( m => m.role !== Role.SYSTEM );
58
+
59
+ if ( skills.length > 0 ) {
60
+ const skillsMessageContent = buildSkillsMessageContent( skills );
61
+ if ( instructions.length > 0 ) {
62
+ instructions[0] = { ...instructions[0], content: `${instructions[0].content}\n\n${skillsMessageContent}` };
63
+ } else {
64
+ instructions.push( { role: Role.SYSTEM, content: skillsMessageContent } );
65
+ }
66
+ }
67
+
68
+ const options = {
69
+ allowSystemInMessages: true,
70
+ maxRetries: 0,
71
+ model: loadTextModel( prompt ),
72
+ instructions,
73
+ messages,
74
+ ...( output && { output } ),
75
+ ...( abortSignal && { abortSignal } ),
76
+ ...( stopWhen && { stopWhen } ),
77
+ ...select( textPromptConfigKeys, prompt.config )
78
+ };
79
+
80
+ // Tools parsing
81
+ const promptTools = loadPromptTools( prompt );
82
+ const skillsTools = skills.length > 0 ? { load_skill: buildLoadSkillTool( skills ) } : {};
83
+ const mergedTools = { ...promptTools, ...tools, ...skillsTools };
84
+ if ( Object.keys( mergedTools ).length > 0 ) {
85
+ options.tools = mergedTools;
86
+ if ( toolChoice ) {
87
+ options.toolChoice = toolChoice;
88
+ }
89
+ if ( !options.stopWhen ) {
90
+ options.stopWhen = isStepCount( prompt.config.maxSteps );
91
+ }
92
+ }
93
+
94
+ return options;
95
+ };
96
+
97
+ /**
98
+ * Build options for AI SDK image generation.
99
+ *
100
+ * Uses prompt instructions (with optional source images and mask) and prompt-owned
101
+ * image config (`n`, `size`, `aspectRatio`, `seed`, `maxImagesPerCall`, `providerOptions`).
102
+ * Always sets `maxRetries` to 0.
103
+ *
104
+ * @param {object} args
105
+ * @param {object} args.prompt - Loaded prompt object
106
+ * @param {unknown} [args.images] - Source images for image-to-image
107
+ * @param {unknown} [args.mask] - Inpainting mask
108
+ * @param {AbortSignal} [args.abortSignal] - Abort signal for the request
109
+ * @returns {object} AI SDK image options
110
+ */
111
+ export const loadAiSdkImageOptions = ( { prompt, images, mask, abortSignal } ) => {
112
+ if ( !prompt.instructions ) {
113
+ throw new FatalError( `Prompt "${prompt.name}" has no instructions. Image prompts must use plain instructions.` );
114
+ }
115
+ return {
116
+ maxRetries: 0,
117
+ model: loadImageModel( prompt ),
118
+ prompt: ( images || mask ) ? {
119
+ text: prompt.instructions,
120
+ ...( images && { images } ),
121
+ ...( mask && { mask } )
122
+ } : prompt.instructions,
123
+ ...( abortSignal && { abortSignal } ),
124
+ ...select( imagePromptConfigKeys, prompt.config )
125
+ };
126
+ };
@@ -0,0 +1,6 @@
1
+ /** Supported message roles */
2
+ export const Role = {
3
+ SYSTEM: 'system',
4
+ USER: 'user',
5
+ ASSISTANT: 'assistant'
6
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Deprecated prompt `provider` spellings → canonical shipped names.
3
+ * Remove this module when the aliases are dropped.
4
+ */
5
+ export const deprecatedProviderAliases = {
6
+ vertex: 'google-vertex',
7
+ bedrock: 'amazon-bedrock'
8
+ };
@@ -0,0 +1,88 @@
1
+ import * as AI from 'ai';
2
+ import { loadPrompt } from './prompt/loader.js';
3
+ import { wrapImageGeneration, wrapStream, wrapTextGeneration } from './utils/wrap.js';
4
+ import { loadAiSdkTextOptions, loadAiSdkImageOptions } from './ai_sdk_options.js';
5
+ import { drainStream } from './utils/stream.js';
6
+ import { loadSkills } from './utils/skills.js';
7
+ import * as Validator from './validations.js';
8
+
9
+ export const generateText = async args => {
10
+ const { promptFile, promptObject, variables, promptDir, ...aiOptions } = Validator.parseGenerateTextArgs( args );
11
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
12
+ const skills = loadSkills( prompt );
13
+
14
+ return wrapTextGeneration( {
15
+ name: 'generateText',
16
+ prompt,
17
+ fn: ( { onStepEndHook } ) => AI.generateText( {
18
+ ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
19
+ onStepEnd: onStepEndHook
20
+ } )
21
+ } );
22
+ };
23
+
24
+ export const streamText = args => {
25
+ const { promptFile, promptObject, variables, promptDir, onEnd, onError, onChunk, ...aiOptions } = Validator.parseStreamTextArgs( args );
26
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
27
+ const skills = loadSkills( prompt );
28
+
29
+ return wrapStream( {
30
+ name: 'streamText',
31
+ prompt,
32
+ abortSignal: aiOptions.abortSignal,
33
+ fn: ( { onEndHook, onErrorHook, onStepEndHook, onAbortHook } ) => AI.streamText( {
34
+ ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
35
+ ...( onChunk && { onChunk } ),
36
+ onStepEnd: onStepEndHook,
37
+ onAbort: onAbortHook,
38
+ onEnd: response => onEndHook( response, onEnd ),
39
+ onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
40
+ } )
41
+ } );
42
+ };
43
+
44
+ /**
45
+ * Generates a completed text response over streaming transport, invoking `onChunk` as parts arrive.
46
+ */
47
+ export const generateTextWithStreaming = async args => {
48
+ const { promptFile, promptObject, variables, promptDir, onChunk, ...aiOptions } = Validator.parseGenerateTextWithStreamingArgs( args );
49
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
50
+ const skills = loadSkills( prompt );
51
+
52
+ return wrapTextGeneration( {
53
+ name: 'generateTextWithStreaming',
54
+ prompt,
55
+ fn: async ( { onStepEndHook } ) => {
56
+ const state = { response: null };
57
+ const stream = AI.streamText( {
58
+ ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
59
+ ...( onChunk && { onChunk } ),
60
+ onStepEnd: onStepEndHook,
61
+ onEnd: res => {
62
+ state.response = res;
63
+ },
64
+ onError: _ => {} // Suppress AI-SDK console printing
65
+ } );
66
+
67
+ await drainStream( stream, aiOptions.abortSignal );
68
+
69
+ if ( !state.response ) {
70
+ throw new Error( 'Streaming completed without a response.' );
71
+ }
72
+
73
+ state.response.output = await stream.output;
74
+ return state.response;
75
+ }
76
+ } );
77
+ };
78
+
79
+ export const generateImage = async args => {
80
+ const { promptFile, promptObject, promptDir, variables, ...aiOptions } = Validator.parseGenerateImageArgs( args );
81
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
82
+
83
+ return wrapImageGeneration( {
84
+ name: 'generateImage',
85
+ prompt,
86
+ fn: () => AI.generateImage( loadAiSdkImageOptions( { prompt, ...aiOptions } ) )
87
+ } );
88
+ };