@outputai/llm 0.6.1-dev.daae905.0 → 0.6.1-next.2cc4685.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 (45) hide show
  1. package/package.json +2 -2
  2. package/src/agent.js +15 -9
  3. package/src/agent.spec.js +295 -214
  4. package/src/ai_model.js +79 -36
  5. package/src/ai_model.spec.js +31 -13
  6. package/src/ai_sdk.js +55 -79
  7. package/src/ai_sdk.spec.js +464 -611
  8. package/src/ai_sdk_options.js +61 -0
  9. package/src/ai_sdk_options.spec.js +164 -0
  10. package/src/cost/index.js +1 -1
  11. package/src/index.d.ts +230 -175
  12. package/src/index.js +2 -2
  13. package/src/prompt/escape.js +65 -0
  14. package/src/prompt/escape.spec.js +159 -0
  15. package/src/{load_content.js → prompt/load_content.js} +1 -22
  16. package/src/{load_content.spec.js → prompt/load_content.spec.js} +6 -6
  17. package/src/prompt/loader.js +49 -0
  18. package/src/prompt/loader.spec.js +274 -0
  19. package/src/{prompt_loader_validation.spec.js → prompt/loader_validation.spec.js} +40 -7
  20. package/src/prompt/parser.js +19 -0
  21. package/src/{parser.spec.js → prompt/parser.spec.js} +74 -29
  22. package/src/prompt/prepare_text.js +27 -0
  23. package/src/prompt/prepare_text.spec.js +141 -0
  24. package/src/{skill.js → prompt/skill.js} +19 -0
  25. package/src/prompt/skill.spec.js +172 -0
  26. package/src/{prompt_validations.js → prompt/validations.js} +32 -6
  27. package/src/{prompt_validations.spec.js → prompt/validations.spec.js} +189 -1
  28. package/src/utils/__fixtures__/image_response.json +38 -0
  29. package/src/utils/__fixtures__/stream_response.json +294 -0
  30. package/src/utils/__fixtures__/text_response.json +201 -0
  31. package/src/utils/error_handler.js +65 -0
  32. package/src/utils/error_handler.spec.js +195 -0
  33. package/src/utils/image.js +10 -0
  34. package/src/utils/image.spec.js +20 -0
  35. package/src/utils/response_wrappers.js +46 -19
  36. package/src/utils/response_wrappers.spec.js +130 -70
  37. package/src/utils/source_extraction.js +17 -27
  38. package/src/utils/trace.js +2 -3
  39. package/src/utils/trace.spec.js +9 -13
  40. package/src/validations.js +54 -2
  41. package/src/validations.spec.js +166 -0
  42. package/src/parser.js +0 -28
  43. package/src/prompt_loader.js +0 -80
  44. package/src/prompt_loader.spec.js +0 -358
  45. package/src/skill.d.ts +0 -49
package/src/ai_model.js CHANGED
@@ -31,8 +31,45 @@ const registerProviderSchema = z.object( {
31
31
  providerFn: z.function()
32
32
  } );
33
33
 
34
+ const modelPromptSchema = z.object( {
35
+ config: z.object( {
36
+ provider: z.string().min( 1 ),
37
+ model: z.string().min( 1 )
38
+ } ).loose()
39
+ } ).loose();
40
+
41
+ const toolsPromptSchema = z.object( {
42
+ config: z.object( {
43
+ provider: z.string().min( 1 ),
44
+ tools: z.record( z.string(), z.record( z.string(), z.unknown() ) ).optional()
45
+ } ).loose()
46
+ } ).loose();
47
+
34
48
  const toolConfigSchema = z.record( z.string(), z.unknown() );
35
49
 
50
+ const parseModelPrompt = prompt => {
51
+ const result = modelPromptSchema.safeParse( prompt );
52
+ if ( !result.success ) {
53
+ throw new ValidationError( `Invalid model prompt config: ${z.prettifyError( result.error )}` );
54
+ }
55
+ return result.data.config;
56
+ };
57
+
58
+ const parseToolsPrompt = prompt => {
59
+ const result = toolsPromptSchema.safeParse( prompt );
60
+ if ( !result.success ) {
61
+ throw new ValidationError( `Invalid tools prompt config: ${z.prettifyError( result.error )}` );
62
+ }
63
+ return result.data.config;
64
+ };
65
+
66
+ /**
67
+ * Register or override an AI SDK provider factory by name.
68
+ *
69
+ * @param {string} name - Provider name used in prompt frontmatter
70
+ * @param {Function} providerFn - Factory function that receives a model id
71
+ * @returns {void}
72
+ */
36
73
  export function registerProvider( name, providerFn ) {
37
74
  const result = registerProviderSchema.safeParse( { name, providerFn } );
38
75
  if ( !result.success ) {
@@ -41,25 +78,23 @@ export function registerProvider( name, providerFn ) {
41
78
  providers[name] = providerFn;
42
79
  }
43
80
 
81
+ /**
82
+ * List all currently registered provider names.
83
+ *
84
+ * @returns {string[]} Provider names
85
+ */
44
86
  export const getRegisteredProviders = () => Object.keys( providers );
45
87
 
46
- export function loadModel( prompt ) {
47
- const config = prompt?.config;
48
-
49
- if ( !config ) {
50
- throw new Error( 'Prompt is missing config object' );
51
- }
52
-
88
+ /**
89
+ * Load a text model from a loaded prompt config.
90
+ *
91
+ * @param {object} prompt - Loaded prompt object with `config.provider` and `config.model`
92
+ * @returns {unknown} AI SDK language model
93
+ */
94
+ export function loadTextModel( prompt ) {
95
+ const config = parseModelPrompt( prompt );
53
96
  const { provider: providerName, model: modelName } = config;
54
97
 
55
- if ( !providerName ) {
56
- throw new Error( 'Prompt config is missing "provider" field' );
57
- }
58
-
59
- if ( !modelName ) {
60
- throw new Error( 'Prompt config is missing "model" field' );
61
- }
62
-
63
98
  const provider = providers[providerName];
64
99
 
65
100
  if ( !provider ) {
@@ -70,34 +105,42 @@ export function loadModel( prompt ) {
70
105
  return provider( modelName );
71
106
  }
72
107
 
73
- export function loadTools( prompt ) {
74
- const config = prompt?.config;
75
-
76
- if ( !config ) {
77
- throw new Error( 'Prompt is missing config object' );
78
- }
108
+ /**
109
+ * Load an image model from a loaded prompt config.
110
+ *
111
+ * @param {object} prompt - Loaded prompt object with `config.provider` and `config.model`
112
+ * @returns {unknown} AI SDK image model
113
+ */
114
+ export function loadImageModel( prompt ) {
115
+ const config = parseModelPrompt( prompt );
116
+ const { provider: providerName, model: modelName } = config;
79
117
 
80
- const { tools: toolsConfig, provider: providerName } = config;
118
+ const provider = providers[providerName];
81
119
 
82
- if ( !toolsConfig ) {
83
- return null;
120
+ if ( !provider ) {
121
+ const availableProviders = Object.keys( providers ).join( ', ' );
122
+ throw new Error( `Invalid provider "${providerName}". Valid providers: ${availableProviders}` );
84
123
  }
85
124
 
86
- if ( Array.isArray( toolsConfig ) ) {
87
- throw new Error(
88
- 'tools must be an object with tool configurations, got array. ' +
89
- 'Use "tools: { googleSearch: {} }" not "tools: [googleSearch]"'
90
- );
125
+ const imageModelFactory = provider.image ?? provider.imageModel;
126
+ if ( typeof imageModelFactory !== 'function' ) {
127
+ throw new Error( `Provider "${providerName}" does not support image models.` );
91
128
  }
92
129
 
93
- if ( typeof toolsConfig !== 'object' ) {
94
- throw new Error(
95
- `tools must be an object, got ${typeof toolsConfig}. ` +
96
- 'Use "tools: { googleSearch: {} }"'
97
- );
98
- }
130
+ return imageModelFactory( modelName );
131
+ }
132
+
133
+ /**
134
+ * Load provider-specific tools configured in a prompt.
135
+ *
136
+ * @param {object} prompt - Loaded prompt object with `config.provider` and optional `config.tools`
137
+ * @returns {Record<string, unknown> | null} AI SDK tools, or null when none are configured
138
+ */
139
+ export function loadTools( prompt ) {
140
+ const config = parseToolsPrompt( prompt );
141
+ const { tools: toolsConfig, provider: providerName } = config;
99
142
 
100
- if ( Object.keys( toolsConfig ).length === 0 ) {
143
+ if ( !toolsConfig || Object.keys( toolsConfig ).length === 0 ) {
101
144
  return null;
102
145
  }
103
146
 
@@ -2,6 +2,7 @@ import { it, expect, vi, afterEach, describe } from 'vitest';
2
2
 
3
3
  const providerFactoryOptions = vi.hoisted( () => ( {} ) );
4
4
  const openaiImpl = vi.hoisted( () => vi.fn( model => `openai:${model}` ) );
5
+ const openaiImageImpl = vi.hoisted( () => vi.fn( model => `openai-image:${model}` ) );
5
6
  const azureImpl = vi.hoisted( () => vi.fn( model => `azure:${model}` ) );
6
7
  const anthropicImpl = vi.hoisted( () => vi.fn( model => `anthropic:${model}` ) );
7
8
  const bedrockImpl = vi.hoisted( () => vi.fn( model => `bedrock:${model}` ) );
@@ -11,6 +12,7 @@ const vertexImpl = vi.hoisted( () => vi.fn( model => `vertex:${model}` ) );
11
12
  // OpenAI mock with tools support
12
13
  vi.mock( '@ai-sdk/openai', () => {
13
14
  const openaiMock = ( ...values ) => openaiImpl( ...values );
15
+ openaiMock.image = ( ...values ) => openaiImageImpl( ...values );
14
16
  openaiMock.tools = {
15
17
  webSearch: ( config = {} ) => ( { type: 'webSearch', config } )
16
18
  };
@@ -93,14 +95,14 @@ vi.mock( '@ai-sdk/google-vertex', () => {
93
95
  };
94
96
  } );
95
97
 
96
- import { loadModel, loadTools, registerProvider, getRegisteredProviders, providers, builtInProviders } from './ai_model.js';
98
+ import { loadImageModel, loadTextModel, loadTools, registerProvider, getRegisteredProviders, providers, builtInProviders } from './ai_model.js';
97
99
 
98
100
  afterEach( async () => {
99
101
  await vi.resetModules();
100
102
  vi.clearAllMocks();
101
103
  } );
102
104
 
103
- describe( 'loadModel', () => {
105
+ describe( 'loadTextModel', () => {
104
106
  it( 'initializes built-in providers with custom fetch', () => {
105
107
  expect( providerFactoryOptions ).toMatchObject( {
106
108
  azure: { fetch: expect.any( Function ) },
@@ -113,7 +115,7 @@ describe( 'loadModel', () => {
113
115
  } );
114
116
 
115
117
  it( 'loads model using selected provider', () => {
116
- const result = loadModel( { config: { provider: 'openai', model: 'gpt-4o-mini' } } );
118
+ const result = loadTextModel( { config: { provider: 'openai', model: 'gpt-4o-mini' } } );
117
119
 
118
120
  expect( result ).toBe( 'openai:gpt-4o-mini' );
119
121
  expect( openaiImpl ).toHaveBeenCalledWith( 'gpt-4o-mini' );
@@ -122,20 +124,36 @@ describe( 'loadModel', () => {
122
124
  } );
123
125
 
124
126
  it( 'loads model using bedrock provider', () => {
125
- const result = loadModel( { config: { provider: 'bedrock', model: 'anthropic.claude-sonnet-4-20250514-v1:0' } } );
127
+ const result = loadTextModel( { config: { provider: 'bedrock', model: 'anthropic.claude-sonnet-4-20250514-v1:0' } } );
126
128
 
127
129
  expect( result ).toBe( 'bedrock:anthropic.claude-sonnet-4-20250514-v1:0' );
128
130
  expect( bedrockImpl ).toHaveBeenCalledWith( 'anthropic.claude-sonnet-4-20250514-v1:0' );
129
131
  } );
130
132
 
131
133
  it( 'loads model using perplexity provider', () => {
132
- const result = loadModel( { config: { provider: 'perplexity', model: 'sonar-pro' } } );
134
+ const result = loadTextModel( { config: { provider: 'perplexity', model: 'sonar-pro' } } );
133
135
 
134
136
  expect( result ).toBe( 'perplexity:sonar-pro' );
135
137
  expect( perplexityImpl ).toHaveBeenCalledWith( 'sonar-pro' );
136
138
  } );
137
139
  } );
138
140
 
141
+ describe( 'loadImageModel', () => {
142
+ it( 'loads image model using provider image factory', () => {
143
+ const result = loadImageModel( { config: { provider: 'openai', model: 'gpt-image-1' } } );
144
+
145
+ expect( result ).toBe( 'openai-image:gpt-image-1' );
146
+ expect( openaiImageImpl ).toHaveBeenCalledWith( 'gpt-image-1' );
147
+ expect( openaiImpl ).not.toHaveBeenCalled();
148
+ } );
149
+
150
+ it( 'throws a clear error when provider does not support image models', () => {
151
+ expect( () => loadImageModel( {
152
+ config: { provider: 'azure', model: 'gpt-image-1' }
153
+ } ) ).toThrow( 'Provider "azure" does not support image models.' );
154
+ } );
155
+ } );
156
+
139
157
  describe( 'loadTools', () => {
140
158
  // Category 1: Basic Functionality (5 tests)
141
159
  describe( 'Basic Functionality', () => {
@@ -593,7 +611,7 @@ describe( 'loadTools', () => {
593
611
  provider: 'vertex',
594
612
  tools: [ 'googleSearch', 'urlContext' ]
595
613
  }
596
- } ) ).toThrow( 'tools must be an object with tool configurations, got array' );
614
+ } ) ).toThrow( /Invalid tools prompt config/ );
597
615
  } );
598
616
 
599
617
  it( 'throws error for string format', () => {
@@ -602,7 +620,7 @@ describe( 'loadTools', () => {
602
620
  provider: 'vertex',
603
621
  tools: 'googleSearch'
604
622
  }
605
- } ) ).toThrow( 'tools must be an object' );
623
+ } ) ).toThrow( /Invalid tools prompt config/ );
606
624
  } );
607
625
 
608
626
  it( 'throws error for number format', () => {
@@ -611,7 +629,7 @@ describe( 'loadTools', () => {
611
629
  provider: 'vertex',
612
630
  tools: 123
613
631
  }
614
- } ) ).toThrow( 'tools must be an object' );
632
+ } ) ).toThrow( /Invalid tools prompt config/ );
615
633
  } );
616
634
 
617
635
  it( 'throws error for provider without tools support', () => {
@@ -656,7 +674,7 @@ describe( 'loadTools', () => {
656
674
  provider: 'vertex',
657
675
  tools: { googleSearch: null }
658
676
  }
659
- } ) ).toThrow( /Invalid config for tool "googleSearch".*expected record, received null/s );
677
+ } ) ).toThrow( /Invalid tools prompt config.*expected record, received null/s );
660
678
  } );
661
679
 
662
680
  it( 'throws error when tool config is a string', () => {
@@ -665,7 +683,7 @@ describe( 'loadTools', () => {
665
683
  provider: 'vertex',
666
684
  tools: { googleSearch: 'MODE_DYNAMIC' }
667
685
  }
668
- } ) ).toThrow( /Invalid config for tool "googleSearch".*expected record, received string/s );
686
+ } ) ).toThrow( /Invalid tools prompt config.*expected record, received string/s );
669
687
  } );
670
688
 
671
689
  it( 'throws error for unknown tool on Bedrock with dynamic tool listing', () => {
@@ -753,11 +771,11 @@ describe( 'registerProvider', () => {
753
771
  Object.assign( providers, builtInProviders );
754
772
  } );
755
773
 
756
- it( 'registers a custom provider and uses it in loadModel', () => {
774
+ it( 'registers a custom provider and uses it in loadTextModel', () => {
757
775
  const customProvider = vi.fn( model => `custom:${model}` );
758
776
  registerProvider( 'custom', customProvider );
759
777
 
760
- const result = loadModel( { config: { provider: 'custom', model: 'my-model' } } );
778
+ const result = loadTextModel( { config: { provider: 'custom', model: 'my-model' } } );
761
779
 
762
780
  expect( result ).toBe( 'custom:my-model' );
763
781
  expect( customProvider ).toHaveBeenCalledWith( 'my-model' );
@@ -767,7 +785,7 @@ describe( 'registerProvider', () => {
767
785
  const overrideOpenai = vi.fn( model => `override:${model}` );
768
786
  registerProvider( 'openai', overrideOpenai );
769
787
 
770
- const result = loadModel( { config: { provider: 'openai', model: 'gpt-custom' } } );
788
+ const result = loadTextModel( { config: { provider: 'openai', model: 'gpt-custom' } } );
771
789
 
772
790
  expect( result ).toBe( 'override:gpt-custom' );
773
791
  } );
package/src/ai_sdk.js CHANGED
@@ -1,113 +1,89 @@
1
- import { loadModel, loadTools } from './ai_model.js';
1
+ import { types as utilTypes } from 'node:util';
2
2
  import * as AI from 'ai';
3
3
  import { stepCountIs } from 'ai';
4
- import { validateGenerateTextArgs, validateStreamTextArgs } from './validations.js';
5
- import { loadPrompt } from './prompt_loader.js';
6
- import { buildSystemSkillsVar, buildLoadSkillTool, loadPromptSkills, loadColocatedSkills } from './skill.js';
4
+ import { ValidationError } from '@outputai/core';
5
+ import { validateGenerateTextArgs, validateStreamTextArgs, validateGenerateImageArgs } from './validations.js';
6
+ import { loadPrompt } from './prompt/loader.js';
7
7
  import { startTrace, endTraceWithError } from './utils/trace.js';
8
- import { wrapTextResponse, wrapStreamOnFinishResponse } from './utils/response_wrappers.js';
9
-
10
- export const loadAiSdkOptionsFromPrompt = prompt => {
11
- const options = {
12
- model: loadModel( prompt ),
13
- messages: prompt.messages,
14
- providerOptions: prompt.config.providerOptions
15
- };
16
-
17
- if ( Number.isFinite( prompt.config.temperature ) ) {
18
- options.temperature = prompt.config.temperature;
19
- }
20
-
21
- if ( prompt.config.maxTokens ) {
22
- options.maxOutputTokens = prompt.config.maxTokens;
23
- }
24
-
25
- const tools = loadTools( prompt );
26
- if ( tools ) {
27
- options.tools = tools;
28
- }
29
-
30
- return options;
31
- };
32
-
33
- export const hydratePromptTemplate = ( prompt, variables, promptDir, callerSkills, callerTools = {} ) => {
34
- const meta = loadPrompt( prompt, variables, promptDir );
35
-
36
- // Resolve skills: explicit frontmatter paths > colocated auto-discovery
37
- const hasExplicitSkills = meta.config.skills && meta.promptFileDir;
38
- const frontmatterSkills = hasExplicitSkills ?
39
- loadPromptSkills( meta.config.skills, meta.promptFileDir ) :
40
- [];
41
- const autoSkills = !hasExplicitSkills && meta.promptFileDir ?
42
- loadColocatedSkills( meta.promptFileDir ) :
43
- [];
44
- const resolvedSkills = [ ...frontmatterSkills, ...autoSkills, ...callerSkills ];
45
-
46
- const tools = resolvedSkills.length > 0 ?
47
- { load_skill: buildLoadSkillTool( resolvedSkills ), ...callerTools } :
48
- callerTools;
49
-
50
- const skillsMessage = resolvedSkills.length > 0 ?
51
- { role: 'system', content: buildSystemSkillsVar( resolvedSkills ) } :
52
- null;
53
-
54
- if ( skillsMessage ) {
55
- // Merge into existing system message to avoid provider errors with multiple system messages
56
- const systemMsg = meta.messages.find( m => m.role === 'system' );
57
- if ( systemMsg ) {
58
- systemMsg.content = `${systemMsg.content}\n\n${skillsMessage.content}`;
59
- } else {
60
- meta.messages.unshift( skillsMessage );
61
- }
62
- }
63
-
64
- return { loadedPrompt: meta, allVariables: variables, tools };
65
- };
8
+ import { wrapTextResponse, wrapStreamOnFinishResponse, wrapImageResponse } from './utils/response_wrappers.js';
9
+ import { loadAiSdkTextOptions, loadAiSdkImageOptions } from './ai_sdk_options.js';
10
+ import { prepareTextPrompt } from './prompt/prepare_text.js';
11
+ import { mapAiError } from './utils/error_handler.js';
66
12
 
67
13
  export async function generateText( { prompt, variables, promptDir, skills = [], maxSteps = 10, ...aiSdkArgs } ) {
68
- const callerSkills = typeof skills === 'function' ? await skills( variables ) : skills;
69
- const { loadedPrompt, allVariables, tools } =
70
- hydratePromptTemplate( prompt, variables, promptDir, callerSkills, aiSdkArgs.tools );
71
- const hasTools = Object.keys( tools ).length > 0;
14
+ validateGenerateTextArgs( { prompt, variables, promptDir, skills, maxSteps } );
72
15
 
73
- validateGenerateTextArgs( { prompt, variables: allVariables } );
16
+ const parsedSkills = typeof skills === 'function' ? await skills( variables ) : skills;
17
+ const { loadedPrompt, tools } = prepareTextPrompt( { prompt, variables, promptDir, skills: parsedSkills, tools: aiSdkArgs.tools } );
74
18
 
75
- const traceId = startTrace( { name: 'generateText', prompt, variables: allVariables, loadedPrompt } );
19
+ const traceId = startTrace( { name: 'generateText', prompt, variables, loadedPrompt } );
76
20
  const { model: modelId } = loadedPrompt.config;
77
21
 
78
22
  try {
79
23
  const response = await AI.generateText( {
80
- ...loadAiSdkOptionsFromPrompt( loadedPrompt ),
24
+ ...loadAiSdkTextOptions( loadedPrompt ),
81
25
  maxRetries: 0,
82
26
  ...aiSdkArgs,
83
- ...( hasTools ? { tools } : {} ),
84
- ...( hasTools && !aiSdkArgs.stopWhen ? { stopWhen: stepCountIs( maxSteps ) } : {} )
27
+ ...( tools && { tools } ),
28
+ ...( tools && !aiSdkArgs.stopWhen ? { stopWhen: stepCountIs( maxSteps ) } : {} )
85
29
  } );
86
30
  return wrapTextResponse( { traceId, modelId, response } );
87
- } catch ( error ) {
31
+ } catch ( originalError ) {
32
+ const error = mapAiError( originalError );
88
33
  endTraceWithError( { traceId, error } );
89
34
  throw error;
90
35
  }
91
36
  }
92
37
 
93
- export function streamText( { prompt, variables, onFinish, onError, ...aiSdkArgs } ) {
94
- validateStreamTextArgs( { prompt, variables } );
95
- const loadedPrompt = loadPrompt( prompt, variables );
38
+ export function streamText( { prompt, variables, promptDir, skills = [], maxSteps = 10, onFinish, onError: _onError, ...aiSdkArgs } ) {
39
+ validateStreamTextArgs( { prompt, variables, promptDir, skills, maxSteps } );
40
+
41
+ const parsedSkills = typeof skills === 'function' ? skills( variables ) : skills;
42
+ if ( utilTypes.isPromise( parsedSkills ) ) {
43
+ throw new ValidationError( 'streamText() skills must be synchronous because streamText() returns a stream immediately.' );
44
+ }
45
+ const { loadedPrompt, tools } = prepareTextPrompt( { prompt, variables, promptDir, skills: parsedSkills, tools: aiSdkArgs.tools } );
46
+
96
47
  const traceId = startTrace( { name: 'streamText', prompt, variables, loadedPrompt } );
97
48
  const { model: modelId } = loadedPrompt.config;
98
49
 
99
50
  try {
100
51
  return AI.streamText( {
101
- ...loadAiSdkOptionsFromPrompt( loadedPrompt ),
52
+ ...loadAiSdkTextOptions( loadedPrompt ),
102
53
  maxRetries: 0,
103
54
  ...aiSdkArgs,
55
+ ...( tools && { tools } ),
56
+ ...( tools && !aiSdkArgs.stopWhen ? { stopWhen: stepCountIs( maxSteps ) } : {} ),
104
57
  ...wrapStreamOnFinishResponse( { traceId, modelId, onFinish } ),
105
58
  onError( event ) {
106
- endTraceWithError( { traceId, error: event.error } );
107
- onError?.( event );
59
+ const error = mapAiError( event.error );
60
+ endTraceWithError( { traceId, error } );
61
+ _onError?.( { ...event, error } );
108
62
  }
109
63
  } );
110
- } catch ( error ) {
64
+ } catch ( originalError ) {
65
+ const error = mapAiError( originalError );
66
+ endTraceWithError( { traceId, error } );
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ export async function generateImage( { prompt, variables, promptDir, images, mask, ...aiSdkArgs } ) {
72
+ validateGenerateImageArgs( { prompt, variables, promptDir, images, mask } );
73
+
74
+ const loadedPrompt = loadPrompt( prompt, variables, promptDir );
75
+ const traceId = startTrace( { name: 'generateImage', prompt, variables, loadedPrompt } );
76
+ const { model: modelId } = loadedPrompt.config;
77
+
78
+ try {
79
+ const response = await AI.generateImage( {
80
+ ...loadAiSdkImageOptions( { prompt: loadedPrompt, images, mask } ),
81
+ maxRetries: 0,
82
+ ...aiSdkArgs
83
+ } );
84
+ return wrapImageResponse( { traceId, modelId, response } );
85
+ } catch ( originalError ) {
86
+ const error = mapAiError( originalError );
111
87
  endTraceWithError( { traceId, error } );
112
88
  throw error;
113
89
  }