@outputai/llm 0.11.1-next.3a40eca.0 → 0.11.1-next.a07f3e4.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 (69) hide show
  1. package/package.json +22 -17
  2. package/src/agent.js +114 -88
  3. package/src/ai_provider.js +2 -2
  4. package/src/ai_sdk_options.js +93 -39
  5. package/src/consts.js +6 -0
  6. package/src/generate.js +82 -0
  7. package/src/index.d.ts +335 -278
  8. package/src/index.js +3 -4
  9. package/src/prompt/content.js +70 -0
  10. package/src/prompt/{escape.js → interpolations.js} +11 -18
  11. package/src/prompt/loader.js +45 -39
  12. package/src/prompt/markup/attributes.js +52 -0
  13. package/src/prompt/markup/nodes.js +71 -0
  14. package/src/prompt/markup/tokenizer.js +3 -0
  15. package/src/prompt/markup/tokens.js +26 -0
  16. package/src/prompt/validations.js +100 -68
  17. package/src/utils/cost.js +137 -0
  18. package/src/{prompt/load_content.js → utils/file.js} +12 -15
  19. package/src/utils/legacy_cost_attribute.js +75 -0
  20. package/src/utils/models.js +15 -0
  21. package/src/utils/models_pricing.js +68 -0
  22. package/src/utils/skills.js +74 -0
  23. package/src/utils/sources.js +48 -0
  24. package/src/utils/stream.js +22 -0
  25. package/src/utils/tools.js +47 -0
  26. package/src/utils/usage.js +121 -0
  27. package/src/utils/wrap.js +198 -0
  28. package/src/validations.js +188 -28
  29. package/src/agent.spec.js +0 -491
  30. package/src/ai_model.js +0 -60
  31. package/src/ai_model.spec.js +0 -259
  32. package/src/ai_provider.spec.js +0 -217
  33. package/src/ai_sdk.js +0 -92
  34. package/src/ai_sdk.spec.js +0 -564
  35. package/src/ai_sdk_options.spec.js +0 -230
  36. package/src/cost/fetch_models_pricing.js +0 -60
  37. package/src/cost/fetch_models_pricing.spec.js +0 -168
  38. package/src/cost/fixtures/models_api_light.json +0 -675
  39. package/src/cost/index.js +0 -58
  40. package/src/cost/index.spec.js +0 -350
  41. package/src/prompt/block_options.js +0 -58
  42. package/src/prompt/block_options.spec.js +0 -71
  43. package/src/prompt/blocks.js +0 -47
  44. package/src/prompt/blocks.spec.js +0 -63
  45. package/src/prompt/escape.spec.js +0 -159
  46. package/src/prompt/load_content.spec.js +0 -83
  47. package/src/prompt/loader.spec.js +0 -361
  48. package/src/prompt/loader_validation.spec.js +0 -128
  49. package/src/prompt/parser.js +0 -16
  50. package/src/prompt/parser.spec.js +0 -186
  51. package/src/prompt/prepare_text.js +0 -27
  52. package/src/prompt/prepare_text.spec.js +0 -141
  53. package/src/prompt/skill.js +0 -128
  54. package/src/prompt/skill.spec.js +0 -172
  55. package/src/prompt/validations.spec.js +0 -641
  56. package/src/utils/__fixtures__/image_response.json +0 -38
  57. package/src/utils/__fixtures__/stream_response.json +0 -294
  58. package/src/utils/__fixtures__/text_response.json +0 -201
  59. package/src/utils/error_handler.spec.js +0 -230
  60. package/src/utils/image.spec.js +0 -20
  61. package/src/utils/message.js +0 -3
  62. package/src/utils/message.spec.js +0 -29
  63. package/src/utils/response_wrappers.js +0 -100
  64. package/src/utils/response_wrappers.spec.js +0 -240
  65. package/src/utils/source_extraction.js +0 -53
  66. package/src/utils/source_extraction.spec.js +0 -194
  67. package/src/utils/trace.js +0 -19
  68. package/src/utils/trace.spec.js +0 -112
  69. package/src/validations.spec.js +0 -166
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/llm",
3
- "version": "0.11.1-next.3a40eca.0",
3
+ "version": "0.11.1-next.a07f3e4.0",
4
4
  "description": "Framework abstraction to interact with LLM models",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -9,32 +9,37 @@
9
9
  "node": ">=24.15.0"
10
10
  },
11
11
  "files": [
12
- "./src"
12
+ "./src/!(*.spec).js",
13
+ "./src/index.d.ts",
14
+ "./src/prompt/!(*.spec).js",
15
+ "./src/prompt/markup/!(*.spec).js",
16
+ "./src/utils/!(*.spec).js"
13
17
  ],
14
18
  "dependencies": {
19
+ "decimal.js": "10.6.0",
15
20
  "entities": "8.0.0",
16
21
  "gray-matter": "4.0.3",
17
22
  "liquidjs": "10.27.2",
18
23
  "undici": "8.9.0",
19
- "@outputai/core": "0.11.1-next.3a40eca.0"
24
+ "@outputai/core": "0.11.1-next.a07f3e4.0"
20
25
  },
21
26
  "devDependencies": {
22
- "@ai-sdk/amazon-bedrock": "4.0.138",
23
- "@ai-sdk/anthropic": "3.0.81",
24
- "@ai-sdk/azure": "3.0.68",
25
- "@ai-sdk/google-vertex": "4.0.140",
26
- "@ai-sdk/openai": "3.0.67",
27
- "@ai-sdk/perplexity": "3.0.33",
28
- "ai": "6.0.168"
27
+ "@ai-sdk/amazon-bedrock": "5.0.57",
28
+ "@ai-sdk/anthropic": "4.0.39",
29
+ "@ai-sdk/azure": "4.0.43",
30
+ "@ai-sdk/google-vertex": "5.0.54",
31
+ "@ai-sdk/openai": "4.0.42",
32
+ "@ai-sdk/perplexity": "4.0.29",
33
+ "ai": "7.0.66"
29
34
  },
30
35
  "peerDependencies": {
31
- "@ai-sdk/amazon-bedrock": ">=4 <5",
32
- "@ai-sdk/anthropic": ">=3 <4",
33
- "@ai-sdk/azure": ">=3 <4",
34
- "@ai-sdk/google-vertex": ">=4 <5",
35
- "@ai-sdk/openai": ">=3 <4",
36
- "@ai-sdk/perplexity": ">=3 <4",
37
- "ai": ">=6 <7"
36
+ "@ai-sdk/amazon-bedrock": ">=5 <6",
37
+ "@ai-sdk/anthropic": ">=4 <5",
38
+ "@ai-sdk/azure": ">=4 <5",
39
+ "@ai-sdk/google-vertex": ">=5 <6",
40
+ "@ai-sdk/openai": ">=4 <5",
41
+ "@ai-sdk/perplexity": ">=4 <5",
42
+ "ai": ">=7 <8"
38
43
  },
39
44
  "license": "Apache-2.0",
40
45
  "publishConfig": {
package/src/agent.js CHANGED
@@ -1,112 +1,138 @@
1
- import { ValidationError } from '@outputai/core';
2
- import { Path } from '@outputai/core/sdk/helpers';
3
- import { ToolLoopAgent as AIToolLoopAgent, stepCountIs } from 'ai';
1
+ import { ToolLoopAgent as AIToolLoopAgent } from 'ai';
4
2
  import { loadAiSdkTextOptions } from './ai_sdk_options.js';
5
- import { prepareTextPrompt } from './prompt/prepare_text.js';
6
- import { startTrace, endTraceWithError } from './utils/trace.js';
7
- import { wrapTextResponse, wrapStreamOnFinishResponse } from './utils/response_wrappers.js';
8
- import { ROLE, isRole } from './utils/message.js';
9
- export { skill } from './prompt/skill.js';
10
-
11
- export const createMemoryConversationStore = () => {
12
- const messages = [];
13
- return {
14
- getMessages: () => messages,
15
- addMessages: newMessages => messages.push( ...newMessages )
16
- };
17
- };
18
-
19
- export class Agent extends AIToolLoopAgent {
3
+ import { wrapGeneration, 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 '@outputai/core';
10
+
11
+ export class Agent {
12
+ #agent;
20
13
  #prompt;
21
- #modelId;
22
- #providerId;
23
14
  #initialMessages;
24
15
  #store;
25
16
 
26
- constructor( {
27
- prompt,
28
- promptDir,
29
- variables = {},
30
- skills = [],
31
- tools: toolsArg,
32
- stopWhen,
33
- maxSteps = 10,
34
- conversationStore,
35
- ...rest
36
- } ) {
37
- if ( !prompt ) {
38
- throw new ValidationError( 'Agent requires a prompt' );
39
- }
40
-
41
- // Must be captured synchronously — Temporal async activity execution
42
- // breaks the call stack, so Path.resolveInvocationDir() fails if called lazily.
43
- const resolvedPromptDir = promptDir ?? Path.resolveInvocationDir();
17
+ constructor( args ) {
18
+ const { promptFile, promptObject, promptDir, variables, tools, output, stopWhen, messageStore } = Validator.parseAgentArgs( args );
44
19
 
45
- const { loadedPrompt, tools } = prepareTextPrompt( { prompt, variables, promptDir: resolvedPromptDir, skills, tools: toolsArg } );
20
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
21
+ const skills = loadSkills( prompt );
46
22
 
47
- const { system, messages, ...constructorOptions } = loadAiSdkTextOptions( loadedPrompt );
23
+ const { instructions, messages, ...aiOptions } = loadAiSdkTextOptions( { prompt, skills, tools, output, stopWhen } );
48
24
 
49
- // loadAiSdkTextOptions routes system blocks to the `system` slot (preserving
50
- // per-message providerOptions); pass them as the agent's `instructions`.
51
- super( {
52
- ...constructorOptions,
53
- ...( system.length > 0 ? { instructions: system } : {} ),
54
- ...( tools ? { tools } : {} ),
55
- stopWhen: stopWhen ?? stepCountIs( maxSteps ),
56
- ...rest
25
+ this.#agent = new AIToolLoopAgent( {
26
+ ...aiOptions,
27
+ ...( instructions.length > 0 ? { instructions } : {} )
57
28
  } );
58
29
 
59
30
  this.#prompt = prompt;
60
- this.#modelId = loadedPrompt.config.model;
61
- this.#providerId = loadedPrompt.config.provider;
62
- // `messages` is system-free but may still hold authored <assistant>/<tool>
63
- // blocks; seed only <user> turns into each generate()/stream() call.
64
- this.#initialMessages = messages.filter( isRole( ROLE.USER ) );
65
- this.#store = conversationStore ?? null;
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;
66
35
  }
67
36
 
68
- async #fetchMessages( userMessages ) {
69
- const priorMessages = this.#store ? await this.#store.getMessages() : [];
70
- return [ ...this.#initialMessages, ...priorMessages, ...userMessages ];
37
+ async #combineWithPreviousMessages( messages ) {
38
+ return [ ...this.#initialMessages, ...( await this.#store?.getMessages() ?? [] ), ...messages ];
71
39
  }
72
40
 
73
- async #storeMessages( userMessages, result ) {
41
+ async #storeMessages( messages ) {
74
42
  if ( this.#store ) {
75
- await this.#store.addMessages( [ ...userMessages, ...( result.response?.messages ?? [] ) ] );
43
+ await this.#store.addMessages( messages );
76
44
  }
77
45
  }
78
46
 
79
- async generate( { messages: userMessages = [], ...callOptions } = {} ) {
80
- const traceId = startTrace( { name: 'Agent.generate', prompt: this.#prompt } );
81
- try {
82
- const messages = await this.#fetchMessages( userMessages );
83
- const response = await super.generate( { messages, allowSystemInMessages: true, ...callOptions } );
84
- const wrapped = await wrapTextResponse( { traceId, response, providerId: this.#providerId, modelId: this.#modelId } );
85
- await this.#storeMessages( userMessages, wrapped );
86
- return wrapped;
87
- } catch ( error ) {
88
- endTraceWithError( { traceId, error } );
89
- throw error;
90
- }
47
+ async generate( args ) {
48
+ const { messages, abortSignal, toolChoice } = Validator.parseAgentGenerateArgs( args );
49
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
50
+
51
+ return wrapGeneration( {
52
+ name: 'Agent.generate',
53
+ prompt: this.#prompt,
54
+ fn: async () => {
55
+ const response = await this.#agent.generate( {
56
+ messages: combinedMessages,
57
+ allowSystemInMessages: true,
58
+ ...( abortSignal && { abortSignal } ),
59
+ ...( toolChoice && { toolChoice } )
60
+ } );
61
+ if ( response.finishReason !== 'error' ) {
62
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
63
+ }
64
+ return response;
65
+ }
66
+ } );
91
67
  }
92
68
 
93
- async stream( { messages: userMessages = [], onFinish, onError, ...callOptions } = {} ) {
94
- const traceId = startTrace( { name: 'Agent.stream', prompt: this.#prompt } );
95
- try {
96
- const messages = await this.#fetchMessages( userMessages );
97
- return super.stream( {
98
- messages,
99
- allowSystemInMessages: true,
100
- ...callOptions,
101
- ...wrapStreamOnFinishResponse( { traceId, modelId: this.#modelId, providerId: this.#providerId, onFinish } ),
102
- onError( event ) {
103
- endTraceWithError( { traceId, error: event.error } );
104
- onError?.( event );
69
+ /**
70
+ * Generates a completed agent response over streaming transport, invoking `onChunk` as parts arrive.
71
+ */
72
+ async generateWithStreaming( args ) {
73
+ const { messages, abortSignal, toolChoice, onChunk } = Validator.parseAgentGenerateWithStreamingArgs( args );
74
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
75
+
76
+ return wrapGeneration( {
77
+ name: 'Agent.generateWithStreaming',
78
+ prompt: this.#prompt,
79
+ fn: async () => {
80
+ const state = { response: null };
81
+ const stream = await this.#agent.stream( {
82
+ messages: combinedMessages,
83
+ allowSystemInMessages: true,
84
+ ...( onChunk && { onChunk } ),
85
+ ...( abortSignal && { abortSignal } ),
86
+ ...( toolChoice && { toolChoice } ),
87
+ onEnd: res => {
88
+ state.response = res;
89
+ },
90
+ onError: _ => {} // Suppress AI-SDK console printing
91
+ } );
92
+
93
+ await drainStream( stream, abortSignal );
94
+
95
+ if ( !state.response ) {
96
+ throw new Error( 'Agent streaming generation completed without a response.' );
105
97
  }
106
- } );
107
- } catch ( error ) {
108
- endTraceWithError( { traceId, error } );
109
- throw error;
110
- }
98
+
99
+ state.response.output = await stream.output;
100
+ if ( state.response.finishReason !== 'error' ) {
101
+ await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
102
+ }
103
+
104
+ return state.response;
105
+ }
106
+ } );
107
+ }
108
+
109
+ async stream( args ) {
110
+ const { messages, abortSignal, toolChoice, onChunk, onEnd, onError } = Validator.parseAgentStreamArgs( args );
111
+ const combinedMessages = await this.#combineWithPreviousMessages( messages );
112
+
113
+ return wrapStream( {
114
+ name: 'Agent.stream',
115
+ prompt: this.#prompt,
116
+ abortSignal,
117
+ fn: ( { onEndHook, onErrorHook } ) => this.#agent.stream( {
118
+ messages: combinedMessages,
119
+ allowSystemInMessages: true,
120
+ ...( onChunk && { onChunk } ),
121
+ ...( abortSignal && { abortSignal } ),
122
+ ...( toolChoice && { toolChoice } ),
123
+ onEnd: response =>
124
+ onEndHook( response, async parsedResponse => {
125
+ if ( response.finishReason !== 'error' ) {
126
+ await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) )
127
+ .catch( error => Logger.error( 'Agent.stream message store persistence failed', {
128
+ namespace: 'LLM',
129
+ error: error instanceof Error ? error.message : String( error )
130
+ } ) );
131
+ }
132
+ await onEnd?.( parsedResponse );
133
+ } ),
134
+ onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
135
+ } )
136
+ } );
111
137
  }
112
138
  }
@@ -53,7 +53,7 @@ const registerProviderSchema = z.object( {
53
53
  * @param {Function} providerFn - Factory function that receives a model id
54
54
  * @returns {void}
55
55
  */
56
- export function registerProvider( name, providerFn ) {
56
+ export const registerProvider = ( name, providerFn ) => {
57
57
  const result = registerProviderSchema.safeParse( { name, providerFn } );
58
58
  if ( !result.success ) {
59
59
  throw new ValidationError( `Invalid provider registration: ${z.prettifyError( result.error )}` );
@@ -65,7 +65,7 @@ export function registerProvider( name, providerFn ) {
65
65
  );
66
66
  }
67
67
  registeredProviders[name] = providerFn;
68
- }
68
+ };
69
69
 
70
70
  /**
71
71
  * Return a provider by its name.
@@ -1,72 +1,126 @@
1
- import { loadImageModel, loadTextModel, loadTools } from './ai_model.js';
2
- import { resolveMessageProviderOptions } from './prompt/block_options.js';
3
- import { ROLE, isRole } from './utils/message.js';
1
+ import { loadImageModel, loadTextModel } from './utils/models.js';
2
+ import { buildLoadSkillTool, loadPromptTools } from './utils/tools.js';
3
+ import { Role } from './consts.js';
4
4
  import { FatalError } from '@outputai/core';
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 ) );
5
34
 
6
35
  /**
7
- * Convert a loaded prompt into AI SDK text generation options.
36
+ * Build options for AI SDK text generation.
8
37
  *
9
- * System blocks are routed to the `system` option (as `SystemModelMessage[]`, so
10
- * per-message providerOptions like `cacheControl` are preserved) rather than left
11
- * in `messages` — the AI SDK flags system roles inside `messages` as a prompt
12
- * injection risk, and `system` is the provider-recommended slot.
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.
13
40
  *
14
- * @param {object} prompt - Loaded prompt object
15
- * @returns {object} Options for AI SDK text calls
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
16
50
  */
17
- export const loadAiSdkTextOptions = prompt => {
51
+ export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output, toolChoice, abortSignal } ) => {
18
52
  if ( prompt.messages.length === 0 ) {
19
53
  throw new FatalError( `Prompt "${prompt.name}" has no chat-style messages. Add role-tagged blocks like <system> or <user>.` );
20
54
  }
21
- const isSystem = isRole( ROLE.SYSTEM );
22
- const resolvedMessages = resolveMessageProviderOptions( prompt );
23
55
 
24
- const options = {
25
- model: loadTextModel( prompt ),
26
- system: resolvedMessages.filter( isSystem ),
27
- messages: resolvedMessages.filter( message => !isSystem( message ) ),
28
- providerOptions: prompt.config.providerOptions
29
- };
56
+ const instructions = prompt.messages.filter( m => m.role === Role.SYSTEM );
57
+ const messages = prompt.messages.filter( m => m.role !== Role.SYSTEM );
30
58
 
31
- if ( Number.isFinite( prompt.config.temperature ) ) {
32
- options.temperature = prompt.config.temperature;
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
+ }
33
66
  }
34
67
 
35
- if ( prompt.config.maxTokens ) {
36
- options.maxOutputTokens = prompt.config.maxTokens;
37
- }
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
+ };
38
79
 
39
- const tools = loadTools( prompt );
40
- if ( tools ) {
41
- options.tools = tools;
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
+ }
42
92
  }
43
93
 
44
94
  return options;
45
95
  };
46
96
 
47
97
  /**
48
- * Convert a loaded prompt into AI SDK image generation options.
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.
49
103
  *
50
- * @param {object} prompt - Loaded prompt object
51
- * @returns {object} Options for AI SDK image calls
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
52
110
  */
53
- export const loadAiSdkImageOptions = ( { prompt, images, mask } ) => {
111
+ export const loadAiSdkImageOptions = ( { prompt, images, mask, abortSignal } ) => {
54
112
  if ( !prompt.instructions ) {
55
113
  throw new FatalError( `Prompt "${prompt.name}" has no instructions. Image prompts must use plain instructions.` );
56
114
  }
57
- const options = {
115
+ return {
116
+ maxRetries: 0,
58
117
  model: loadImageModel( prompt ),
59
118
  prompt: ( images || mask ) ? {
60
119
  text: prompt.instructions,
61
120
  ...( images && { images } ),
62
121
  ...( mask && { mask } )
63
122
  } : prompt.instructions,
64
- providerOptions: prompt.config.providerOptions
123
+ ...( abortSignal && { abortSignal } ),
124
+ ...select( imagePromptConfigKeys, prompt.config )
65
125
  };
66
- for ( const key of [ 'n', 'maxImagesPerCall', 'size', 'aspectRatio', 'seed' ] ) {
67
- if ( prompt.config[key] !== undefined ) {
68
- options[key] = prompt.config[key];
69
- }
70
- }
71
- return options;
72
126
  };
package/src/consts.js ADDED
@@ -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,82 @@
1
+ import * as AI from 'ai';
2
+ import { loadPrompt } from './prompt/loader.js';
3
+ import { wrapGeneration, wrapStream } 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 wrapGeneration( {
15
+ name: 'generateText',
16
+ prompt,
17
+ fn: () => AI.generateText( loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ) )
18
+ } );
19
+ };
20
+
21
+ export const streamText = args => {
22
+ const { promptFile, promptObject, variables, promptDir, onEnd, onError, onChunk, ...aiOptions } = Validator.parseStreamTextArgs( args );
23
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
24
+ const skills = loadSkills( prompt );
25
+
26
+ return wrapStream( {
27
+ name: 'streamText',
28
+ prompt,
29
+ abortSignal: aiOptions.abortSignal,
30
+ fn: ( { onEndHook, onErrorHook } ) => AI.streamText( {
31
+ ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
32
+ ...( onChunk && { onChunk } ),
33
+ onEnd: response => onEndHook( response, onEnd ),
34
+ onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
35
+ } )
36
+ } );
37
+ };
38
+
39
+ /**
40
+ * Generates a completed text response over streaming transport, invoking `onChunk` as parts arrive.
41
+ */
42
+ export const generateTextWithStreaming = async args => {
43
+ const { promptFile, promptObject, variables, promptDir, onChunk, ...aiOptions } = Validator.parseGenerateTextWithStreamingArgs( args );
44
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
45
+ const skills = loadSkills( prompt );
46
+
47
+ return wrapGeneration( {
48
+ name: 'generateTextWithStreaming',
49
+ prompt,
50
+ fn: async () => {
51
+ const state = { response: null };
52
+ const stream = AI.streamText( {
53
+ ...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
54
+ ...( onChunk && { onChunk } ),
55
+ onEnd: res => {
56
+ state.response = res;
57
+ },
58
+ onError: _ => {} // Suppress AI-SDK console printing
59
+ } );
60
+
61
+ await drainStream( stream, aiOptions.abortSignal );
62
+
63
+ if ( !state.response ) {
64
+ throw new Error( 'Streaming generation completed without a response.' );
65
+ }
66
+
67
+ state.response.output = await stream.output;
68
+ return state.response;
69
+ }
70
+ } );
71
+ };
72
+
73
+ export const generateImage = async args => {
74
+ const { promptFile, promptObject, promptDir, variables, ...aiOptions } = Validator.parseGenerateImageArgs( args );
75
+ const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
76
+
77
+ return wrapGeneration( {
78
+ name: 'generateImage',
79
+ prompt,
80
+ fn: () => AI.generateImage( loadAiSdkImageOptions( { prompt, ...aiOptions } ) )
81
+ } );
82
+ };