briyah 1.1.4 → 1.1.6

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 (89) hide show
  1. package/README.md +14 -1
  2. package/data/common/config/image_models.json +141 -21
  3. package/data/common/config/image_models_full.json +135 -48
  4. package/data/common/config/markup +1 -1
  5. package/data/common/config/story_models.json +41 -38
  6. package/data/common/prompts/character/progress_character.prompt +4 -0
  7. package/data/common/prompts/character/update_portrait.prompt +31 -0
  8. package/data/common/prompts/narrator/perceive.prompt +23 -1
  9. package/data/common/prompts/narrator/progress_plot.prompt +1 -3
  10. package/data/common/prompts/story_moderator/moderate.json +36 -29
  11. package/data/common/prompts/story_moderator/moderate.prompt +63 -18
  12. package/dist-sdk/server/src/ai/LLM/base-ai.service.d.ts +5 -7
  13. package/dist-sdk/server/src/ai/LLM/base-ai.service.js +7 -1
  14. package/dist-sdk/server/src/ai/LLM/fal.service.d.ts +5 -1
  15. package/dist-sdk/server/src/ai/LLM/fal.service.js +93 -21
  16. package/dist-sdk/server/src/ai/LLM/googleai.service.d.ts +1 -1
  17. package/dist-sdk/server/src/ai/LLM/googleai.service.js +20 -29
  18. package/dist-sdk/server/src/ai/LLM/openai.service.d.ts +5 -1
  19. package/dist-sdk/server/src/ai/LLM/openai.service.js +58 -71
  20. package/dist-sdk/server/src/ai/LLM/together.service.d.ts +5 -1
  21. package/dist-sdk/server/src/ai/LLM/together.service.js +57 -31
  22. package/dist-sdk/server/src/ai/agent-config.d.ts +1 -0
  23. package/dist-sdk/server/src/ai/agent-store.service.js +4 -0
  24. package/dist-sdk/server/src/ai/agent.d.ts +8 -1
  25. package/dist-sdk/server/src/ai/agent.js +21 -3
  26. package/dist-sdk/server/src/ai/model_prices.js +21 -1
  27. package/dist-sdk/server/src/app.controller.d.ts +4 -0
  28. package/dist-sdk/server/src/app.controller.js +51 -1
  29. package/dist-sdk/server/src/app.service.d.ts +3 -1
  30. package/dist-sdk/server/src/app.service.js +22 -14
  31. package/dist-sdk/server/src/room/message.d.ts +1 -0
  32. package/dist-sdk/server/src/room/message.js +1 -0
  33. package/dist-sdk/server/src/room/room-store.service.d.ts +1 -0
  34. package/dist-sdk/server/src/room/room-store.service.js +3 -0
  35. package/dist-sdk/server/src/room/room.d.ts +3 -0
  36. package/dist-sdk/server/src/room/room.js +27 -7
  37. package/dist-sdk/server/src/story/story-store.service.d.ts +1 -0
  38. package/dist-sdk/server/src/story/story-store.service.js +38 -0
  39. package/dist-sdk/server/src/story/story.service.d.ts +4 -1
  40. package/dist-sdk/server/src/story/story.service.js +233 -55
  41. package/dist-sdk/shared/types/app.types.d.ts +17 -0
  42. package/docs/assets/hierarchy.js +1 -1
  43. package/docs/assets/search.js +1 -1
  44. package/docs/classes/Agent.html +19 -15
  45. package/docs/classes/Briyah.html +12 -12
  46. package/docs/classes/BriyahConfigService.html +5 -5
  47. package/docs/classes/Room.html +26 -24
  48. package/docs/classes/RoomMessage.html +11 -10
  49. package/docs/enums/MessageAction.html +3 -3
  50. package/docs/hierarchy.html +1 -1
  51. package/docs/index.html +15 -3
  52. package/docs/interfaces/AgentInfo.html +2 -2
  53. package/docs/interfaces/AgentMessagesResponse.html +2 -2
  54. package/docs/interfaces/AppService.html +128 -113
  55. package/docs/interfaces/Artifact.html +3 -3
  56. package/docs/interfaces/ArtifactMetadata.html +2 -2
  57. package/docs/interfaces/AttachDocumentResponse.html +2 -2
  58. package/docs/interfaces/BriyahConfigOptions.html +6 -6
  59. package/docs/interfaces/ChapterInfo.html +2 -2
  60. package/docs/interfaces/Character.html +2 -2
  61. package/docs/interfaces/CreateAgentResponse.html +2 -2
  62. package/docs/interfaces/CreateRoomResponse.html +2 -2
  63. package/docs/interfaces/CreateStoryResponse.html +2 -2
  64. package/docs/interfaces/FileList.html +2 -2
  65. package/docs/interfaces/LoggingOptions.html +5 -5
  66. package/docs/interfaces/Message.html +2 -2
  67. package/docs/interfaces/ModelInfo.html +3 -2
  68. package/docs/interfaces/PreparedPromptResponse.html +2 -2
  69. package/docs/interfaces/ProcessTextResponse.html +2 -2
  70. package/docs/interfaces/PromptFile.html +2 -2
  71. package/docs/interfaces/PromptFileContent.html +2 -2
  72. package/docs/interfaces/PromptFilesResponse.html +2 -2
  73. package/docs/interfaces/PromptFolder.html +2 -2
  74. package/docs/interfaces/PromptFoldersResponse.html +2 -2
  75. package/docs/interfaces/RoomDetails.html +2 -2
  76. package/docs/interfaces/RoomInfo.html +2 -2
  77. package/docs/interfaces/RoomMessagesResponse.html +2 -2
  78. package/docs/interfaces/StoryErrorEvent.html +3 -3
  79. package/docs/interfaces/StoryIdea.html +2 -2
  80. package/docs/interfaces/StoryInfo.html +5 -3
  81. package/docs/interfaces/StoryIntroduceCharacterEvent.html +3 -3
  82. package/docs/interfaces/StoryProgressChapterEvent.html +3 -3
  83. package/docs/interfaces/StoryState.html +5 -5
  84. package/docs/interfaces/StoryStateEvent.html +3 -3
  85. package/docs/interfaces/Transaction.html +2 -2
  86. package/docs/interfaces/TransactionHistoryResponse.html +2 -2
  87. package/docs/modules.html +1 -1
  88. package/docs/types/PromptScope.html +1 -1
  89. package/package.json +1 -1
@@ -53,6 +53,11 @@ This request will *not* become part of the readable story, only the narrator's r
53
53
  3. Responder: The one character you choose to respond next to best maintain the flow of the conversation and further the story.
54
54
  4. Targets: List of all characters who would realistically be able to hear the message (even if they shouldn't respond).
55
55
  - When processing a message from the Narrator, the targets array should only include characters who are present for the situation being narrated.
56
+ 5. Update Portrait: List the names of any characters whose visible appearance has just changed in a way that should be reflected in their portrait image.
57
+ - Include a character's name when the current message describes a meaningful change to their visible appearance: new or different clothing/outfit, significant injury (wounds, blood, bandages), physical transformation, or a notable item they are now visibly carrying or wearing.
58
+ - Use the character's exact name as it appears in the Available Characters list.
59
+ - Leave the array empty if no visible appearance changes occurred.
60
+ - Minor or invisible changes (emotional state alone, unseen actions) do NOT warrant a portrait update.
56
61
 
57
62
  ## Content Moderation
58
63
  - Do *not* allow profanity or swear words like 'hell', 'shit', and 'damn' or 'damned'.
@@ -67,7 +72,8 @@ You must respond with a valid JSON object in the following format:
67
72
  "action": "relay", // or "relay_silent" for internal processing
68
73
  "content": "relay", // or modified message body
69
74
  "name": "CharacterName1", // Name of character who should respond next
70
- "targets": ["CharacterName1", "CharacterName2"] // List of all characters who would realistically be able to hear the message
75
+ "targets": ["CharacterName1", "CharacterName2"], // List of all characters who would realistically be able to hear the message
76
+ "update_portrait": [] // Names of characters whose visible appearance changed; empty if none
71
77
  }
72
78
 
73
79
  ## Conversation Rules
@@ -81,10 +87,10 @@ You must respond with a valid JSON object in the following format:
81
87
  - **Important**:Avoid lengthy conversations that exclude the Human Character completely. Give the Human Character a chance to interact with the story.
82
88
 
83
89
  ## Introductions
84
- - Only set "name" to the exact character name (nickname) of one of the characters in **Available Characters** list.
85
- - However, if a message from the Human Character directly addresses someone in the story who is not listed in the **Available Characters** list, you may introduce that character like this:
86
- - Set the **name** field to a single-word character name (nickname) of the referenced character.
87
- - Doing this will give the system the opportunity to create a new character profile for that character using the name you provide so that they will appear in the **Available Characters** list in the future.
90
+ - Only set "name" to the exact character name (nickname) of one of the characters in the **Available Characters** list.
91
+ - If a message from the Human Character (or any character) directly addresses someone who is NOT listed in the **Available Characters** list, route the message to the Narrator. The Narrator has access to the secret plot plan and can identify whether the addressed person is a planned character; do NOT guess at a name yourself.
92
+ - Set "name" to "Narrator" and "targets" to ["Narrator"].
93
+ - Use "action": "relay" so the dialog still becomes part of the readable story.
88
94
  - If you receive a message from the system like "INTRODUCE [Character name]", relay the INTRODUCE [Character name] message to the Narrator so they may properly introduce the new character.
89
95
 
90
96
  ## Example Responses
@@ -105,7 +111,8 @@ Correct Response:
105
111
  "action": "relay_silent",
106
112
  "content": "PASS",
107
113
  "name": "Narrator",
108
- "targets": ["Narrator"]
114
+ "targets": ["Narrator"],
115
+ "update_portrait": []
109
116
  }
110
117
  - Mike is choosing not to act or speak, so select another appropriate character (or the narrator) to speak next instead.
111
118
  - Use "relay_silent" because PASS is a meta-action and shouldn't appear in the story
@@ -118,7 +125,8 @@ Correct Response:
118
125
  "action": "relay",
119
126
  "content": "relay",
120
127
  "name": "Mike",
121
- "targets": ["Mike", "Bob"]
128
+ "targets": ["Mike", "Bob"],
129
+ "update_portrait": []
122
130
  }
123
131
  - According to the current situation, only Mike, Jessica, and Bob are within hearing distance of each other.
124
132
  - Jessica is speaking, and only Mike and Bob can hear. Sergei and Carter are not present.
@@ -134,7 +142,8 @@ Correct Response:
134
142
  "action": "relay",
135
143
  "content": "relay",
136
144
  "name": "Jessica",
137
- "targets": ["Jessica", "Bob"]
145
+ "targets": ["Jessica", "Bob"],
146
+ "update_portrait": []
138
147
  }
139
148
  - According to the current situation, only Mike, Jessica, and Bob are within hearing distance of each other.
140
149
  - Mike is speaking, and only Jessica and Bob can hear. Sergei and Carter are not present.
@@ -150,7 +159,8 @@ Correct Response:
150
159
  "action": "relay_silent",
151
160
  "content": "relay",
152
161
  "name": "Narrator",
153
- "targets": ["Narrator"]
162
+ "targets": ["Narrator"],
163
+ "update_portrait": []
154
164
  }
155
165
  - The sender is attempting to perform an action with the message
156
166
  - Only the Narrator is able to handle the character's request to perform an action.
@@ -167,7 +177,8 @@ Correct Response:
167
177
  "action": "relay",
168
178
  "content": "relay",
169
179
  "name": "Mike",
170
- "targets": ["Mike", "Jessica", "Bob"]
180
+ "targets": ["Mike", "Jessica", "Bob"],
181
+ "update_portrait": []
171
182
  }
172
183
  - This is not dialog and there is no obvious responder, so choose the human character to respond because they are involved in the current situation.
173
184
  - All characters involved in the current situation should be targets of the message
@@ -182,25 +193,28 @@ Correct Response:
182
193
  "action": "relay",
183
194
  "content": "relay",
184
195
  "name": "Sergei",
185
- "targets": ["Sergei", "Carter"]
196
+ "targets": ["Sergei", "Carter"],
197
+ "update_portrait": []
186
198
  }
187
199
  - This is not dialog and there is no obvious responder, so choose an character involved in the current situation to be the responder
188
200
  - All characters involved in the current situation should be targets of the message
189
201
  - Mike, Jessica, and Bob should not be targets of this message because they are not involved in the situation.
190
202
 
191
203
 
192
- ### Example 6: Character addresses another character who is not listed in the Available Characters list
204
+ ### Example 6: Character addresses someone not listed in the Available Characters list
193
205
  Recent Message Sender: Mike (human character)
194
206
  Recent Message: Hello, Albert Flanagan!
195
207
  Correct Response:
196
208
  {
197
209
  "action": "relay",
198
210
  "content": "relay",
199
- "name": "Albert",
200
- "targets": ["Albert"]
211
+ "name": "Narrator",
212
+ "targets": ["Narrator"],
213
+ "update_portrait": []
201
214
  }
202
- - If Human Character directly addresses someone not listed in the **Available Characters** list, create a single-word name for the referenced character.
203
- - The single-word name you choose may appear in the **Available Characters** list in the future if the user decides to introduce them as a new character.
215
+ - "Albert Flanagan" is not in the Available Characters list.
216
+ - Route to the Narrator. The Narrator can consult the plot plan to identify the addressed character (and emit an appropriate INTRODUCE situation) or narrate an alternative response.
217
+ - Use "relay" so Mike's dialog still appears in the readable story.
204
218
 
205
219
  ### Example 7: Message contains explicit content
206
220
  Recent Message Sender: Mike
@@ -210,7 +224,8 @@ Correct Response:
210
224
  "action": "relay",
211
225
  "content": "[message content modified to remove explicit content]",
212
226
  "name": "Narrator",
213
- "targets": ["Narrator"]
227
+ "targets": ["Narrator"],
228
+ "update_portrait": []
214
229
  }
215
230
  - If the message contains explicit content, just remove the explicit content if possible or modify the message to something more appropriate.
216
231
 
@@ -222,8 +237,38 @@ Correct Response:
222
237
  "action": "relay_silent",
223
238
  "content": "relay",
224
239
  "name": "",
225
- "targets": []
240
+ "targets": [],
241
+ "update_portrait": []
226
242
  }
227
243
  - Use the relay_silent action and leave the name field blank and set the targets list to an empty array.
228
244
  - The message will be silently relayed to the Narrator to perform the introduction.
229
245
 
246
+
247
+ ### Example 9: Narrator describes a visible appearance change
248
+ Recent Message Sender: Narrator
249
+ Recent Message: The arrow grazes Jessica's arm, tearing her sleeve and leaving a bloody gash. She wraps a strip of cloth around the wound and keeps moving.
250
+ Correct Response:
251
+ {
252
+ "action": "relay",
253
+ "content": "relay",
254
+ "name": "Mike",
255
+ "targets": ["Mike", "Jessica", "Bob"],
256
+ "update_portrait": ["Jessica"]
257
+ }
258
+ - Jessica's visible appearance has changed (torn sleeve, wound, makeshift bandage), so her portrait should be updated.
259
+ - All characters present in the current situation are targets.
260
+
261
+
262
+ ### Example 10: Character action with no visible change
263
+ Recent Message Sender: Bob
264
+ Recent Message: Bob wields his new sword proudly above his head
265
+ Correct Response:
266
+ {
267
+ "action": "relay_silent",
268
+ "content": "relay",
269
+ "name": "Narrator",
270
+ "targets": ["Narrator"],
271
+ "update_portrait": ["Bob"]
272
+ }
273
+ - Bob just got a new weapon (or other visible item) that we would want to include in his portrait.
274
+
@@ -4,12 +4,6 @@ import { ArtifactService } from '../artifact.service';
4
4
  import { AgentStoreService } from '../agent-store.service';
5
5
  import { BalanceService } from '../../app/balance.service';
6
6
  import { ModelInfo } from '../../../../shared/types/app.types';
7
- export interface ImageGenerationOptions {
8
- model?: string;
9
- size?: '1024x1024' | '1536x1024' | '1024x1536' | 'auto';
10
- quality?: 'auto' | 'high' | 'medium' | 'low';
11
- format?: 'png' | 'jpeg' | 'webp';
12
- }
13
7
  export declare class BaseAiService {
14
8
  protected modelsCache: ModelInfo[];
15
9
  protected _isAvailable: boolean;
@@ -28,7 +22,11 @@ export declare class BaseAiService {
28
22
  protected fetchModelsFromApi(): Promise<ModelInfo[]>;
29
23
  createAgent(configService: ConfigurationService, agentStoreService: AgentStoreService, balanceService: BalanceService, artifactService: ArtifactService, agentName: string, agentNickname: string, description: string, promptFolder: string, modelName: string, storageDir?: string): Agent;
30
24
  textPrompt(agent: Agent, prompt: string, _jsonSchema?: any, _saveResponse?: boolean, _promptInstructions?: string, _cacheConfig?: any, _maxOutputChars?: number): Promise<any>;
31
- generateImage(_agent: Agent, _prompt: string, _width: number, _height: number, _quality: string, _referenceImageArtifactIds?: string[], _options?: ImageGenerationOptions): Promise<{
25
+ generateImage(_agent: Agent, _prompt: string, _imageProperties?: Record<string, any>): Promise<{
26
+ artifactId?: string;
27
+ error?: any;
28
+ }>;
29
+ editImage(_agent: Agent, _prompt: string, _imageProperties?: Record<string, any>, _referenceImageArtifactIds?: string[]): Promise<{
32
30
  artifactId?: string;
33
31
  error?: any;
34
32
  }>;
@@ -177,12 +177,18 @@ let BaseAiService = class BaseAiService {
177
177
  }
178
178
  throw new Error('Method not implemented in base class');
179
179
  }
180
- async generateImage(_agent, _prompt, _width, _height, _quality, _referenceImageArtifactIds, _options) {
180
+ async generateImage(_agent, _prompt, _imageProperties) {
181
181
  if (!this.isAvailable) {
182
182
  return Promise.reject(new Error(`${this.getServiceName()} service is not available. Check API key configuration.`));
183
183
  }
184
184
  throw new Error(`generateImage method not implemented for ${this.getServiceName()}`);
185
185
  }
186
+ async editImage(_agent, _prompt, _imageProperties, _referenceImageArtifactIds) {
187
+ if (!this.isAvailable) {
188
+ return Promise.reject(new Error(`${this.getServiceName()} service is not available. Check API key configuration.`));
189
+ }
190
+ throw new Error(`editImage method not implemented for ${this.getServiceName()}`);
191
+ }
186
192
  async loadReferenceImages(agent, artifactIds) {
187
193
  const images = [];
188
194
  for (const artifactId of artifactIds) {
@@ -11,7 +11,11 @@ export declare class FalAiService extends BaseAiService {
11
11
  textPrompt(agent: Agent, prompt: string, jsonSchema?: any, saveResponse?: boolean, promptInstructions?: string, _cacheConfig?: any, maxOutputChars?: number): Promise<any>;
12
12
  private computeMessageCost;
13
13
  private computeImageCost;
14
- generateImage(agent: Agent, prompt: string, width: number, height: number, quality: string, referenceImageArtifactIds?: string[]): Promise<{
14
+ generateImage(agent: Agent, prompt: string, imageProperties?: Record<string, any>): Promise<{
15
+ artifactId?: string;
16
+ error?: any;
17
+ }>;
18
+ editImage(agent: Agent, prompt: string, imageProperties?: Record<string, any>, referenceImageArtifactIds?: string[]): Promise<{
15
19
  artifactId?: string;
16
20
  error?: any;
17
21
  }>;
@@ -218,25 +218,37 @@ let FalAiService = class FalAiService extends base_ai_service_1.BaseAiService {
218
218
  agent.balanceService.decrementBalance(cost, markup);
219
219
  }
220
220
  }
221
- computeImageCost(agent) {
221
+ computeImageCost(agent, centsPerImage) {
222
222
  let modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
223
223
  if (!modelInfo && !agent.modelName.startsWith('fal_ai/')) {
224
224
  modelInfo = (0, model_prices_1.getModelPrices)()[`fal_ai/${agent.modelName}`];
225
225
  }
226
- if (!modelInfo || !modelInfo.output_cost_per_image) {
227
- logger_1.logger.warn(`No image price info found for ${agent.modelName}. Skipping cost tracking.`);
226
+ let cost;
227
+ if (modelInfo?.output_cost_per_image != null) {
228
+ cost = modelInfo.output_cost_per_image;
229
+ }
230
+ else if (centsPerImage != null) {
231
+ cost = centsPerImage / 100;
232
+ }
233
+ else {
234
+ logger_1.logger.warn(`No price info for model ${agent.modelName}. Skipping cost tracking.`);
228
235
  return;
229
236
  }
230
- const cost = modelInfo.output_cost_per_image;
231
- const markup = 0;
232
237
  agent.totalCost += cost;
233
- agent.totalMarkup += markup;
234
- logger_1.logger.log(`Cost for image generation: ${cost.toFixed(4)} (markup: ${markup.toFixed(4)})`);
238
+ logger_1.logger.log(`Image cost for ${agent.agentName}: $${cost.toFixed(4)}`);
235
239
  if (agent.balanceService && cost > 0) {
236
- agent.balanceService.decrementBalance(cost, markup);
240
+ agent.balanceService.decrementBalance(cost, 0);
237
241
  }
238
242
  }
239
- async generateImage(agent, prompt, width, height, quality, referenceImageArtifactIds) {
243
+ async generateImage(agent, prompt, imageProperties) {
244
+ const { centsPerImage, ...apiProperties } = imageProperties ?? {};
245
+ let modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
246
+ if (!modelInfo && !agent.modelName.startsWith('fal_ai/')) {
247
+ modelInfo = (0, model_prices_1.getModelPrices)()[`fal_ai/${agent.modelName}`];
248
+ }
249
+ if (modelInfo?.output_cost_per_image == null && centsPerImage == null) {
250
+ return { error: `No pricing configured for image model "${agent.modelName}". Add a modelPrices entry or set centsPerImage in image_models.json.` };
251
+ }
240
252
  if (agent.balanceService && !agent.disableBalanceCheck) {
241
253
  const hasSufficientBalance = agent.balanceService.hasSufficientBalance();
242
254
  if (!hasSufficientBalance) {
@@ -249,23 +261,14 @@ let FalAiService = class FalAiService extends base_ai_service_1.BaseAiService {
249
261
  endpoint = `fal-ai/${endpoint}`;
250
262
  }
251
263
  const input = {
252
- prompt: prompt,
253
- image_size: {
254
- width: width,
255
- height: height,
256
- },
264
+ prompt,
257
265
  num_images: 1,
258
- output_format: 'png',
266
+ ...apiProperties,
259
267
  };
260
- if (referenceImageArtifactIds && referenceImageArtifactIds.length > 0) {
261
- input.image_urls = await this.loadReferenceImages(agent, referenceImageArtifactIds);
262
- if (!endpoint.endsWith('/edit'))
263
- endpoint += '/edit';
264
- }
265
268
  const result = (await client_1.fal.subscribe(endpoint, {
266
269
  input: input,
267
270
  }));
268
- this.computeImageCost(agent);
271
+ this.computeImageCost(agent, centsPerImage);
269
272
  let base64Data;
270
273
  if (result.data.images && result.data.images[0]) {
271
274
  const image = result.data.images[0];
@@ -305,6 +308,75 @@ let FalAiService = class FalAiService extends base_ai_service_1.BaseAiService {
305
308
  return { error };
306
309
  }
307
310
  }
311
+ async editImage(agent, prompt, imageProperties, referenceImageArtifactIds) {
312
+ const { centsPerImage, ...apiProperties } = imageProperties ?? {};
313
+ let modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
314
+ if (!modelInfo && !agent.modelName.startsWith('fal_ai/')) {
315
+ modelInfo = (0, model_prices_1.getModelPrices)()[`fal_ai/${agent.modelName}`];
316
+ }
317
+ if (modelInfo?.output_cost_per_image == null && centsPerImage == null) {
318
+ return { error: `No pricing configured for image model "${agent.modelName}". Add a modelPrices entry or set centsPerImage in image_models.json.` };
319
+ }
320
+ if (agent.balanceService && !agent.disableBalanceCheck) {
321
+ const hasSufficientBalance = agent.balanceService.hasSufficientBalance();
322
+ if (!hasSufficientBalance) {
323
+ throw new errors_1.InsufficientBalanceError('Insufficient balance for image generation. Please add funds to continue.');
324
+ }
325
+ }
326
+ try {
327
+ let endpoint = agent.modelName;
328
+ if (!endpoint.startsWith('fal-ai/')) {
329
+ endpoint = `fal-ai/${endpoint}`;
330
+ }
331
+ const input = {
332
+ prompt,
333
+ num_images: 1,
334
+ ...apiProperties,
335
+ };
336
+ if (referenceImageArtifactIds && referenceImageArtifactIds.length > 0) {
337
+ input.image_urls = await this.loadReferenceImages(agent, referenceImageArtifactIds);
338
+ }
339
+ const result = (await client_1.fal.subscribe(endpoint, { input }));
340
+ this.computeImageCost(agent, centsPerImage);
341
+ let base64Data;
342
+ if (result.data.images && result.data.images[0]) {
343
+ const image = result.data.images[0];
344
+ if (image.url) {
345
+ const imageResponse = await fetch(image.url);
346
+ const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
347
+ base64Data = imageBuffer.toString('base64');
348
+ }
349
+ else if (image.content) {
350
+ base64Data = image.content;
351
+ }
352
+ else {
353
+ throw new Error('No image data found in response');
354
+ }
355
+ }
356
+ else if (result.data.image) {
357
+ if (result.data.image.url) {
358
+ const imageResponse = await fetch(result.data.image.url);
359
+ const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
360
+ base64Data = imageBuffer.toString('base64');
361
+ }
362
+ else if (result.data.image.content) {
363
+ base64Data = result.data.image.content;
364
+ }
365
+ else {
366
+ throw new Error('No image data found in response');
367
+ }
368
+ }
369
+ else {
370
+ throw new Error('Unexpected response format from Fal AI');
371
+ }
372
+ const artifactId = await this.saveImageAsArtifact(agent, base64Data, prompt);
373
+ return { artifactId };
374
+ }
375
+ catch (error) {
376
+ logger_1.logger.error('Error editing image with Fal AI:', error.message || error);
377
+ return { error };
378
+ }
379
+ }
308
380
  };
309
381
  exports.FalAiService = FalAiService;
310
382
  exports.FalAiService = FalAiService = __decorate([
@@ -10,7 +10,7 @@ export declare class GoogleAiService extends BaseAiService {
10
10
  fetchModelsFromApi(): Promise<ModelInfo[]>;
11
11
  addToConversationHistory(agent: Agent, message: string, fromSelf?: boolean): void;
12
12
  textPrompt(agent: Agent, prompt: string, jsonSchema?: any, saveResponse?: boolean, promptInstructions?: string, _cacheConfig?: any, maxOutputChars?: number): Promise<any>;
13
- generateImage(agent: Agent, prompt: string, width: number, height: number, quality: string, _referenceImageArtifactIds?: string[]): Promise<{
13
+ generateImage(agent: Agent, prompt: string, imageProperties?: Record<string, any>): Promise<{
14
14
  artifactId?: string;
15
15
  error?: any;
16
16
  }>;
@@ -153,7 +153,12 @@ let GoogleAiService = class GoogleAiService extends base_ai_service_1.BaseAiServ
153
153
  }
154
154
  return responseText;
155
155
  }
156
- async generateImage(agent, prompt, width, height, quality, _referenceImageArtifactIds) {
156
+ async generateImage(agent, prompt, imageProperties) {
157
+ const { centsPerImage, ...apiProperties } = imageProperties ?? {};
158
+ const modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
159
+ if ((!modelInfo || !modelInfo.output_cost_per_image) && centsPerImage == null) {
160
+ return { error: `No pricing configured for image model "${agent.modelName}". Add a modelPrices entry or set centsPerImage in image_models.json.` };
161
+ }
157
162
  if (agent.balanceService && !agent.disableBalanceCheck) {
158
163
  const hasSufficientBalance = agent.balanceService.hasSufficientBalance();
159
164
  if (!hasSufficientBalance) {
@@ -161,32 +166,10 @@ let GoogleAiService = class GoogleAiService extends base_ai_service_1.BaseAiServ
161
166
  }
162
167
  }
163
168
  try {
164
- const modelName = agent.modelName;
165
- let aspectRatio = '1:1';
166
- if (width && height) {
167
- const ratio = width / height;
168
- if (ratio > 1.4)
169
- aspectRatio = '16:9';
170
- else if (ratio > 1.2)
171
- aspectRatio = '3:2';
172
- else if (ratio < 0.7)
173
- aspectRatio = '9:16';
174
- else if (ratio < 0.8)
175
- aspectRatio = '2:3';
176
- }
177
- const config = {
178
- numberOfImages: 1,
179
- aspectRatio: aspectRatio,
180
- };
181
- if (quality && quality !== 'auto') {
182
- config.outputOptions = {
183
- compressionQuality: quality === 'high' ? 90 : quality === 'medium' ? 70 : 50,
184
- };
185
- }
186
169
  const response = await this.googleAI.models.generateImages({
187
- model: modelName,
188
- prompt: prompt,
189
- config: config,
170
+ model: agent.modelName,
171
+ prompt,
172
+ config: { ...apiProperties },
190
173
  });
191
174
  let base64Data;
192
175
  if (response.generatedImages?.[0]?.image?.imageBytes) {
@@ -198,7 +181,7 @@ let GoogleAiService = class GoogleAiService extends base_ai_service_1.BaseAiServ
198
181
  else {
199
182
  throw new Error('No image data in response');
200
183
  }
201
- this.computeImageCost(agent, modelName);
184
+ this.computeImageCost(agent, agent.modelName, centsPerImage);
202
185
  const artifactId = await this.saveImageAsArtifact(agent, base64Data, prompt);
203
186
  return { artifactId };
204
187
  }
@@ -228,10 +211,18 @@ let GoogleAiService = class GoogleAiService extends base_ai_service_1.BaseAiServ
228
211
  agent.balanceService.decrementBalance(cost, markup);
229
212
  }
230
213
  }
231
- computeImageCost(agent, modelName) {
214
+ computeImageCost(agent, modelName, centsPerImage) {
232
215
  const modelInfo = (0, model_prices_1.getModelPrices)()[modelName];
233
216
  if (!modelInfo || !modelInfo.output_cost_per_image) {
234
- logger_1.logger.error(`No image price info found for model ${modelName}`);
217
+ if (centsPerImage != null) {
218
+ const cost = centsPerImage / 100;
219
+ agent.totalCost += cost;
220
+ logger_1.logger.log(`Image cost for ${agent.agentName}: $${cost.toFixed(4)}`);
221
+ if (agent.balanceService && cost > 0)
222
+ agent.balanceService.decrementBalance(cost, 0);
223
+ return;
224
+ }
225
+ logger_1.logger.warn(`No image price info found for model ${modelName}. Skipping cost tracking.`);
235
226
  return;
236
227
  }
237
228
  const outputCost = modelInfo.output_cost_per_image;
@@ -14,7 +14,11 @@ export declare class OpenAiService extends BaseAiService {
14
14
  addToConversationHistory(agent: Agent, message: string, fromSelf?: boolean, developer?: boolean): void;
15
15
  private computeMessageCost;
16
16
  private computeImageCost;
17
- generateImage(agent: Agent, prompt: string, width: number, height: number, quality: string, referenceImageArtifactIds?: string[]): Promise<{
17
+ generateImage(agent: Agent, prompt: string, imageProperties?: Record<string, any>): Promise<{
18
+ artifactId?: string;
19
+ error?: any;
20
+ }>;
21
+ editImage(agent: Agent, prompt: string, imageProperties?: Record<string, any>, referenceImageArtifactIds?: string[]): Promise<{
18
22
  artifactId?: string;
19
23
  error?: any;
20
24
  }>;
@@ -315,60 +315,31 @@ let OpenAiService = class OpenAiService extends base_ai_service_1.BaseAiService
315
315
  agent.balanceService.decrementBalance(cost, markup);
316
316
  }
317
317
  }
318
- computeImageCost(agent, usage) {
318
+ computeImageCost(agent, centsPerImage) {
319
319
  const modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
320
- if (!modelInfo) {
321
- logger_1.logger.error(`No price info found for model ${agent.modelName}`);
322
- return;
320
+ let cost;
321
+ if (modelInfo?.output_cost_per_image != null) {
322
+ cost = modelInfo.output_cost_per_image;
323
323
  }
324
- const inputImageTokens = usage.input_tokens_details?.image_tokens || 0;
325
- const inputTextTokens = usage.input_tokens_details?.text_tokens || 0;
326
- const outputImageTokens = usage.output_tokens_details?.image_tokens || 0;
327
- const outputTextTokens = usage.output_tokens_details?.text_tokens || 0;
328
- const totalInputTokens = usage.input_tokens || 0;
329
- const totalOutputTokens = usage.output_tokens || 0;
330
- agent.totalInputTokens += totalInputTokens;
331
- agent.totalOutputTokens += totalOutputTokens;
332
- let inputCost = 0;
333
- if (inputImageTokens > 0) {
334
- const cachedImageTokens = usage.input_tokens_details?.cached_image_tokens || 0;
335
- const uncachedImageTokens = inputImageTokens - cachedImageTokens;
336
- inputCost += uncachedImageTokens * (modelInfo.input_cost_per_image_token || 0);
337
- if (cachedImageTokens > 0) {
338
- inputCost +=
339
- cachedImageTokens *
340
- (modelInfo.cache_read_input_image_token_cost ||
341
- modelInfo.input_cost_per_image_token * 0.5);
342
- }
324
+ else if (centsPerImage != null) {
325
+ cost = centsPerImage / 100;
343
326
  }
344
- if (inputTextTokens > 0) {
345
- const cachedTextTokens = usage.input_tokens_details?.cached_text_tokens || 0;
346
- const uncachedTextTokens = inputTextTokens - cachedTextTokens;
347
- inputCost += uncachedTextTokens * (modelInfo.input_cost_per_token || 0);
348
- if (cachedTextTokens > 0) {
349
- inputCost +=
350
- cachedTextTokens *
351
- (modelInfo.cache_read_input_text_token_cost ||
352
- modelInfo.input_cost_per_token * 0.5);
353
- }
354
- }
355
- let outputCost = 0;
356
- if (outputImageTokens > 0) {
357
- outputCost += outputImageTokens * (modelInfo.output_cost_per_image_token || 0);
358
- }
359
- if (outputTextTokens > 0) {
360
- outputCost += outputTextTokens * (modelInfo.output_cost_per_token || 0);
327
+ else {
328
+ logger_1.logger.warn(`No price info for model ${agent.modelName}. Skipping cost tracking.`);
329
+ return;
361
330
  }
362
- const markup = outputCost * 0;
363
- const cost = inputCost + outputCost + markup;
364
331
  agent.totalCost += cost;
365
- agent.totalMarkup += markup;
366
- logger_1.logger.log(`Cost for image generation: ${cost} (markup: ${markup.toFixed(4)})`);
332
+ logger_1.logger.log(`Image cost for ${agent.agentName}: $${cost.toFixed(4)}`);
367
333
  if (agent.balanceService && cost > 0) {
368
- agent.balanceService.decrementBalance(cost, markup);
334
+ agent.balanceService.decrementBalance(cost, 0);
369
335
  }
370
336
  }
371
- async generateImage(agent, prompt, width, height, quality, referenceImageArtifactIds) {
337
+ async generateImage(agent, prompt, imageProperties) {
338
+ const { centsPerImage, ...apiProperties } = imageProperties ?? {};
339
+ const modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
340
+ if (modelInfo?.output_cost_per_image == null && centsPerImage == null) {
341
+ return { error: `No pricing configured for image model "${agent.modelName}". Add a modelPrices entry or set centsPerImage in image_models.json.` };
342
+ }
372
343
  if (agent.balanceService) {
373
344
  const hasSufficientBalance = agent.balanceService.hasSufficientBalance();
374
345
  if (!hasSufficientBalance) {
@@ -376,31 +347,13 @@ let OpenAiService = class OpenAiService extends base_ai_service_1.BaseAiService
376
347
  }
377
348
  }
378
349
  try {
379
- let imageResponse;
380
- const size = `${width}x${height}`;
381
- if (referenceImageArtifactIds && referenceImageArtifactIds.length > 0) {
382
- const referenceImages = await this.loadReferenceImages(agent, referenceImageArtifactIds);
383
- imageResponse = await this.openai.images.edit({
384
- model: agent.modelName,
385
- prompt: prompt,
386
- image: referenceImages.length === 1 ? referenceImages[0] : referenceImages,
387
- size: size,
388
- n: 1,
389
- });
390
- }
391
- else {
392
- imageResponse = await this.openai.images.generate({
393
- model: agent.modelName,
394
- prompt: prompt,
395
- n: 1,
396
- size: size,
397
- quality: quality || 'auto',
398
- moderation: 'low',
399
- });
400
- }
401
- if (imageResponse.usage) {
402
- this.computeImageCost(agent, imageResponse.usage);
403
- }
350
+ const imageResponse = await this.openai.images.generate({
351
+ model: agent.modelName,
352
+ prompt,
353
+ n: 1,
354
+ ...apiProperties,
355
+ });
356
+ this.computeImageCost(agent, centsPerImage);
404
357
  const base64Data = imageResponse.data[0].b64_json;
405
358
  const artifactId = await this.saveImageAsArtifact(agent, base64Data, prompt);
406
359
  return { artifactId };
@@ -411,6 +364,40 @@ let OpenAiService = class OpenAiService extends base_ai_service_1.BaseAiService
411
364
  return { error };
412
365
  }
413
366
  }
367
+ async editImage(agent, prompt, imageProperties, referenceImageArtifactIds) {
368
+ const { centsPerImage, ...apiProperties } = imageProperties ?? {};
369
+ const modelInfo = (0, model_prices_1.getModelPrices)()[agent.modelName];
370
+ if (modelInfo?.output_cost_per_image == null && centsPerImage == null) {
371
+ return { error: `No pricing configured for image model "${agent.modelName}". Add a modelPrices entry or set centsPerImage in image_models.json.` };
372
+ }
373
+ if (agent.balanceService) {
374
+ const hasSufficientBalance = agent.balanceService.hasSufficientBalance();
375
+ if (!hasSufficientBalance) {
376
+ throw new errors_1.InsufficientBalanceError('Insufficient balance for image generation. Please add funds to continue.');
377
+ }
378
+ }
379
+ try {
380
+ const referenceImages = referenceImageArtifactIds?.length
381
+ ? await this.loadReferenceImages(agent, referenceImageArtifactIds)
382
+ : [];
383
+ const imageResponse = await this.openai.images.edit({
384
+ model: agent.modelName,
385
+ prompt,
386
+ image: referenceImages.length === 1 ? referenceImages[0] : referenceImages,
387
+ n: 1,
388
+ ...apiProperties,
389
+ });
390
+ this.computeImageCost(agent, centsPerImage);
391
+ const base64Data = imageResponse.data[0].b64_json;
392
+ const artifactId = await this.saveImageAsArtifact(agent, base64Data, prompt);
393
+ return { artifactId };
394
+ }
395
+ catch (error) {
396
+ logger_1.logger.error('Error editing image:', error);
397
+ logger_1.logger.log(prompt);
398
+ return { error };
399
+ }
400
+ }
414
401
  };
415
402
  exports.OpenAiService = OpenAiService;
416
403
  exports.OpenAiService = OpenAiService = __decorate([