@ank1015/providers 0.0.1

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 (99) hide show
  1. package/README.md +453 -0
  2. package/biome.json +43 -0
  3. package/dist/agent/agent-loop.d.ts +5 -0
  4. package/dist/agent/agent-loop.d.ts.map +1 -0
  5. package/dist/agent/agent-loop.js +219 -0
  6. package/dist/agent/agent-loop.js.map +1 -0
  7. package/dist/agent/types.d.ts +67 -0
  8. package/dist/agent/types.d.ts.map +1 -0
  9. package/dist/agent/types.js +3 -0
  10. package/dist/agent/types.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +29 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/models.d.ts +3 -0
  16. package/dist/models.d.ts.map +1 -0
  17. package/dist/models.generated.d.ts +247 -0
  18. package/dist/models.generated.d.ts.map +1 -0
  19. package/dist/models.generated.js +315 -0
  20. package/dist/models.generated.js.map +1 -0
  21. package/dist/models.js +41 -0
  22. package/dist/models.js.map +1 -0
  23. package/dist/providers/convert.d.ts +6 -0
  24. package/dist/providers/convert.d.ts.map +1 -0
  25. package/dist/providers/convert.js +207 -0
  26. package/dist/providers/convert.js.map +1 -0
  27. package/dist/providers/google.d.ts +26 -0
  28. package/dist/providers/google.d.ts.map +1 -0
  29. package/dist/providers/google.js +434 -0
  30. package/dist/providers/google.js.map +1 -0
  31. package/dist/providers/openai.d.ts +17 -0
  32. package/dist/providers/openai.d.ts.map +1 -0
  33. package/dist/providers/openai.js +396 -0
  34. package/dist/providers/openai.js.map +1 -0
  35. package/dist/stream.d.ts +4 -0
  36. package/dist/stream.d.ts.map +1 -0
  37. package/dist/stream.js +40 -0
  38. package/dist/stream.js.map +1 -0
  39. package/dist/test-google-agent-loop.d.ts +2 -0
  40. package/dist/test-google-agent-loop.d.ts.map +1 -0
  41. package/dist/test-google-agent-loop.js +186 -0
  42. package/dist/test-google-agent-loop.js.map +1 -0
  43. package/dist/test-google.d.ts +2 -0
  44. package/dist/test-google.d.ts.map +1 -0
  45. package/dist/test-google.js +41 -0
  46. package/dist/test-google.js.map +1 -0
  47. package/dist/types.d.ts +187 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +10 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/utils/event-stream.d.ts +16 -0
  52. package/dist/utils/event-stream.d.ts.map +1 -0
  53. package/dist/utils/event-stream.js +61 -0
  54. package/dist/utils/event-stream.js.map +1 -0
  55. package/dist/utils/json-parse.d.ts +9 -0
  56. package/dist/utils/json-parse.d.ts.map +1 -0
  57. package/dist/utils/json-parse.js +32 -0
  58. package/dist/utils/json-parse.js.map +1 -0
  59. package/dist/utils/sanitize-unicode.d.ts +22 -0
  60. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  61. package/dist/utils/sanitize-unicode.js +29 -0
  62. package/dist/utils/sanitize-unicode.js.map +1 -0
  63. package/dist/utils/validation.d.ts +11 -0
  64. package/dist/utils/validation.d.ts.map +1 -0
  65. package/dist/utils/validation.js +61 -0
  66. package/dist/utils/validation.js.map +1 -0
  67. package/package.json +33 -0
  68. package/src/agent/agent-loop.ts +275 -0
  69. package/src/agent/types.ts +80 -0
  70. package/src/index.ts +72 -0
  71. package/src/models.generated.ts +314 -0
  72. package/src/models.ts +45 -0
  73. package/src/providers/convert.ts +222 -0
  74. package/src/providers/google.ts +496 -0
  75. package/src/providers/openai.ts +437 -0
  76. package/src/stream.ts +60 -0
  77. package/src/types.ts +198 -0
  78. package/src/utils/event-stream.ts +60 -0
  79. package/src/utils/json-parse.ts +28 -0
  80. package/src/utils/sanitize-unicode.ts +25 -0
  81. package/src/utils/validation.ts +69 -0
  82. package/test/core/agent-loop.test.ts +958 -0
  83. package/test/core/stream.test.ts +409 -0
  84. package/test/data/red-circle.png +0 -0
  85. package/test/data/superintelligentwill.pdf +0 -0
  86. package/test/edge-cases/general.test.ts +565 -0
  87. package/test/integration/e2e.test.ts +530 -0
  88. package/test/models/cost.test.ts +499 -0
  89. package/test/models/registry.test.ts +298 -0
  90. package/test/providers/convert.test.ts +846 -0
  91. package/test/providers/google-schema.test.ts +666 -0
  92. package/test/providers/google-stream.test.ts +369 -0
  93. package/test/providers/openai-stream.test.ts +251 -0
  94. package/test/utils/event-stream.test.ts +289 -0
  95. package/test/utils/json-parse.test.ts +344 -0
  96. package/test/utils/sanitize-unicode.test.ts +329 -0
  97. package/test/utils/validation.test.ts +614 -0
  98. package/tsconfig.json +21 -0
  99. package/vitest.config.ts +9 -0
@@ -0,0 +1,314 @@
1
+ // This file is auto-generated by scripts/generate-models.ts
2
+ // Do not edit manually - run 'npm run generate-models' to update
3
+
4
+ import type { Model } from "./types.js";
5
+
6
+ export const MODELS = {
7
+ // anthropic: {
8
+ // "claude-haiku-4-5": {
9
+ // id: "claude-haiku-4-5",
10
+ // name: "Claude Haiku 4.5 (latest)",
11
+ // api: "anthropic-messages",
12
+ // provider: "anthropic",
13
+ // baseUrl: "https://api.anthropic.com",
14
+ // reasoning: true,
15
+ // input: ["text", "image"],
16
+ // cost: {
17
+ // input: 1,
18
+ // output: 5,
19
+ // cacheRead: 0.1,
20
+ // cacheWrite: 1.25,
21
+ // },
22
+ // contextWindow: 200000,
23
+ // maxTokens: 64000,
24
+ // } satisfies Model<"anthropic-messages">,
25
+ // "claude-opus-4-5": {
26
+ // id: "claude-opus-4-5",
27
+ // name: "Claude Opus 4.5 (latest)",
28
+ // api: "anthropic-messages",
29
+ // provider: "anthropic",
30
+ // baseUrl: "https://api.anthropic.com",
31
+ // reasoning: true,
32
+ // input: ["text", "image"],
33
+ // cost: {
34
+ // input: 5,
35
+ // output: 25,
36
+ // cacheRead: 0.5,
37
+ // cacheWrite: 6.25,
38
+ // },
39
+ // contextWindow: 200000,
40
+ // maxTokens: 64000,
41
+ // } satisfies Model<"anthropic-messages">,
42
+ // "claude-sonnet-4-5": {
43
+ // id: "claude-sonnet-4-5",
44
+ // name: "Claude Sonnet 4.5 (latest)",
45
+ // api: "anthropic-messages",
46
+ // provider: "anthropic",
47
+ // baseUrl: "https://api.anthropic.com",
48
+ // reasoning: true,
49
+ // input: ["text", "image"],
50
+ // cost: {
51
+ // input: 3,
52
+ // output: 15,
53
+ // cacheRead: 0.3,
54
+ // cacheWrite: 3.75,
55
+ // },
56
+ // contextWindow: 200000,
57
+ // maxTokens: 64000,
58
+ // } satisfies Model<"anthropic-messages">,
59
+ // },
60
+ google: {
61
+ "gemini-2.5-flash-preview-05-20": {
62
+ id: "gemini-2.5-flash-preview-05-20",
63
+ name: "Gemini 2.5 Flash Preview 05-20",
64
+ api: "google",
65
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
66
+ reasoning: true,
67
+ input: ["text", "image"],
68
+ cost: {
69
+ input: 0.15,
70
+ output: 0.6,
71
+ cacheRead: 0.0375,
72
+ cacheWrite: 0,
73
+ },
74
+ contextWindow: 1048576,
75
+ maxTokens: 65536,
76
+ } satisfies Model<"google">,
77
+ "gemini-flash-lite-latest": {
78
+ id: "gemini-flash-lite-latest",
79
+ name: "Gemini Flash-Lite Latest",
80
+ api: "google",
81
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
82
+ reasoning: true,
83
+ input: ["text", "image", "file"],
84
+ cost: {
85
+ input: 0.1,
86
+ output: 0.4,
87
+ cacheRead: 0.025,
88
+ cacheWrite: 0,
89
+ },
90
+ contextWindow: 1048576,
91
+ maxTokens: 65536,
92
+ } satisfies Model<"google">,
93
+ "gemini-3-pro-preview": {
94
+ id: "gemini-3-pro-preview",
95
+ name: "Gemini 3 Pro Preview",
96
+ api: "google",
97
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
98
+ reasoning: true,
99
+ input: ["text", "image", "file"],
100
+ cost: {
101
+ input: 2,
102
+ output: 12,
103
+ cacheRead: 0.2,
104
+ cacheWrite: 0,
105
+ },
106
+ contextWindow: 1000000,
107
+ maxTokens: 64000,
108
+ } satisfies Model<"google">,
109
+ "gemini-2.5-flash": {
110
+ id: "gemini-2.5-flash",
111
+ name: "Gemini 2.5 Flash",
112
+ api: "google",
113
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
114
+ reasoning: true,
115
+ input: ["text", "image", "file"],
116
+ cost: {
117
+ input: 0.3,
118
+ output: 2.5,
119
+ cacheRead: 0.075,
120
+ cacheWrite: 0,
121
+ },
122
+ contextWindow: 1048576,
123
+ maxTokens: 65536,
124
+ } satisfies Model<"google">,
125
+ },
126
+ openai: {
127
+ "gpt-5.1-codex": {
128
+ id: "gpt-5.1-codex",
129
+ name: "GPT-5.1 Codex",
130
+ api: "openai",
131
+ baseUrl: "https://api.openai.com/v1",
132
+ reasoning: true,
133
+ input: ["text", "image", "file"],
134
+ cost: {
135
+ input: 1.25,
136
+ output: 10,
137
+ cacheRead: 0.125,
138
+ cacheWrite: 0,
139
+ },
140
+ contextWindow: 400000,
141
+ maxTokens: 128000,
142
+ } satisfies Model<"openai">,
143
+ "gpt-5.1-codex-mini": {
144
+ id: "gpt-5.1-codex-mini",
145
+ name: "GPT-5.1 Codex mini",
146
+ api: "openai",
147
+ baseUrl: "https://api.openai.com/v1",
148
+ reasoning: true,
149
+ input: ["text", "image", "file"],
150
+ cost: {
151
+ input: 0.25,
152
+ output: 2,
153
+ cacheRead: 0.025,
154
+ cacheWrite: 0,
155
+ },
156
+ contextWindow: 400000,
157
+ maxTokens: 128000,
158
+ } satisfies Model<"openai">,
159
+ "gpt-5.1": {
160
+ id: "gpt-5.1",
161
+ name: "GPT-5.1",
162
+ api: "openai",
163
+ baseUrl: "https://api.openai.com/v1",
164
+ reasoning: true,
165
+ input: ["text", "image", "file"],
166
+ cost: {
167
+ input: 1.25,
168
+ output: 10,
169
+ cacheRead: 0.13,
170
+ cacheWrite: 0,
171
+ },
172
+ contextWindow: 400000,
173
+ maxTokens: 128000,
174
+ } satisfies Model<"openai">,
175
+ "codex-mini-latest": {
176
+ id: "codex-mini-latest",
177
+ name: "Codex Mini",
178
+ api: "openai",
179
+ baseUrl: "https://api.openai.com/v1",
180
+ reasoning: true,
181
+ input: ["text"],
182
+ cost: {
183
+ input: 1.5,
184
+ output: 6,
185
+ cacheRead: 0.375,
186
+ cacheWrite: 0,
187
+ },
188
+ contextWindow: 200000,
189
+ maxTokens: 100000,
190
+ } satisfies Model<"openai">,
191
+ "gpt-5-nano": {
192
+ id: "gpt-5-nano",
193
+ name: "GPT-5 Nano",
194
+ api: "openai",
195
+ baseUrl: "https://api.openai.com/v1",
196
+ reasoning: true,
197
+ input: ["text", "image", "file"],
198
+ cost: {
199
+ input: 0.05,
200
+ output: 0.4,
201
+ cacheRead: 0.01,
202
+ cacheWrite: 0,
203
+ },
204
+ contextWindow: 400000,
205
+ maxTokens: 128000,
206
+ } satisfies Model<"openai">,
207
+ "gpt-5-codex": {
208
+ id: "gpt-5-codex",
209
+ name: "GPT-5-Codex",
210
+ api: "openai",
211
+ baseUrl: "https://api.openai.com/v1",
212
+ reasoning: true,
213
+ input: ["text", "image", "file"],
214
+ cost: {
215
+ input: 1.25,
216
+ output: 10,
217
+ cacheRead: 0.125,
218
+ cacheWrite: 0,
219
+ },
220
+ contextWindow: 400000,
221
+ maxTokens: 128000,
222
+ } satisfies Model<"openai">,
223
+ "gpt-5-mini": {
224
+ id: "gpt-5-mini",
225
+ name: "GPT-5 Mini",
226
+ api: "openai",
227
+ baseUrl: "https://api.openai.com/v1",
228
+ reasoning: true,
229
+ input: ["text", "image", "file"],
230
+ cost: {
231
+ input: 0.25,
232
+ output: 2,
233
+ cacheRead: 0.03,
234
+ cacheWrite: 0,
235
+ },
236
+ contextWindow: 400000,
237
+ maxTokens: 128000,
238
+ } satisfies Model<"openai">,
239
+ "gpt-5.1-codex-max": {
240
+ id: "gpt-5.1-codex-max",
241
+ name: "GPT-5.1 Codex Max",
242
+ api: "openai",
243
+ baseUrl: "https://api.openai.com/v1",
244
+ reasoning: true,
245
+ input: ["text", "image", "file"],
246
+ cost: {
247
+ input: 1.25,
248
+ output: 10,
249
+ cacheRead: 0.125,
250
+ cacheWrite: 0,
251
+ },
252
+ contextWindow: 400000,
253
+ maxTokens: 128000,
254
+ } satisfies Model<"openai">,
255
+ "gpt-5": {
256
+ id: "gpt-5",
257
+ name: "GPT-5",
258
+ api: "openai",
259
+ baseUrl: "https://api.openai.com/v1",
260
+ reasoning: true,
261
+ input: ["text", "image", "file"],
262
+ cost: {
263
+ input: 1.25,
264
+ output: 10,
265
+ cacheRead: 0.13,
266
+ cacheWrite: 0,
267
+ },
268
+ contextWindow: 400000,
269
+ maxTokens: 128000,
270
+ } satisfies Model<"openai">,
271
+ "gpt-5-pro": {
272
+ id: "gpt-5-pro",
273
+ name: "GPT-5 Pro",
274
+ api: "openai",
275
+ baseUrl: "https://api.openai.com/v1",
276
+ reasoning: true,
277
+ input: ["text", "image", "file"],
278
+ cost: {
279
+ input: 15,
280
+ output: 120,
281
+ cacheRead: 0,
282
+ cacheWrite: 0,
283
+ },
284
+ contextWindow: 400000,
285
+ maxTokens: 272000,
286
+ } satisfies Model<"openai">,
287
+ "gpt-5.1-chat-latest": {
288
+ id: "gpt-5.1-chat-latest",
289
+ name: "GPT-5.1 Chat",
290
+ api: "openai",
291
+ baseUrl: "https://api.openai.com/v1",
292
+ reasoning: true,
293
+ input: ["text", "image", "file"],
294
+ cost: {
295
+ input: 1.25,
296
+ output: 10,
297
+ cacheRead: 0.125,
298
+ cacheWrite: 0,
299
+ },
300
+ contextWindow: 128000,
301
+ maxTokens: 16384,
302
+ } satisfies Model<"openai">,
303
+ },
304
+ // groq: {
305
+ // },
306
+ // cerebras: {
307
+ // },
308
+ // xai: {
309
+ // },
310
+ // zai: {
311
+ // },
312
+ // openrouter: {
313
+ // }
314
+ } as const;
package/src/models.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { MODELS } from "./models.generated.js";
2
+ import type { Api, Model, Usage } from "./types.js";
3
+
4
+ const modelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
5
+
6
+ // Initialize registry from MODELS on module load
7
+ for (const [provider, models] of Object.entries(MODELS)) {
8
+ const providerModels = new Map<string, Model<Api>>();
9
+ for (const [id, model] of Object.entries(models)) {
10
+ providerModels.set(id, model as Model<Api>);
11
+ }
12
+ modelRegistry.set(provider, providerModels);
13
+ }
14
+
15
+ // type ModelApi<
16
+ // TProvider extends KnownProvider,
17
+ // TModelId extends keyof (typeof MODELS)[TProvider],
18
+ // > = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;
19
+
20
+ // export function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(
21
+ // provider: TProvider,
22
+ // modelId: TModelId,
23
+ // ): Model<ModelApi<TProvider, TModelId>> {
24
+ // return modelRegistry.get(provider)?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;
25
+ // }
26
+
27
+ // export function getProviders(): KnownProvider[] {
28
+ // return Array.from(modelRegistry.keys()) as KnownProvider[];
29
+ // }
30
+
31
+ // export function getModels<TProvider extends KnownProvider>(
32
+ // provider: TProvider,
33
+ // ): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {
34
+ // const models = modelRegistry.get(provider);
35
+ // return models ? (Array.from(models.values()) as Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[]) : [];
36
+ // }
37
+
38
+ export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
39
+ usage.cost.input = (model.cost.input / 1000000) * usage.input;
40
+ usage.cost.output = (model.cost.output / 1000000) * usage.output;
41
+ usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
42
+ usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
43
+ usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
44
+ return usage.cost;
45
+ }
@@ -0,0 +1,222 @@
1
+ import { Context, Model, Tool } from "../types";
2
+ import { ResponseInput, ResponseInputItem, ResponseInputMessageContentList, ResponseInputImage, ResponseInputFile, ResponseInputText, ResponseFunctionCallOutputItemList } from "openai/resources/responses/responses.js";
3
+ import { sanitizeSurrogates } from "../utils/sanitize-unicode";
4
+ import { ContentListUnion, Part } from "@google/genai";
5
+
6
+ export function buildOpenAIMessages(model: Model<'openai'> ,context: Context): ResponseInput {
7
+
8
+ const openAIMessages: ResponseInput = [];
9
+ if(context.systemPrompt){
10
+ openAIMessages.push({
11
+ role: 'developer',
12
+ content: sanitizeSurrogates(context.systemPrompt)
13
+ })
14
+ };
15
+
16
+ for(let i=0; i<context.messages.length; i++){
17
+ const message = context.messages[i];
18
+ // normalize for user message
19
+ if(message.role === 'user'){
20
+ const contents: ResponseInputMessageContentList = [];
21
+ for (let p=0; p< message.content.length; p++){
22
+ const content = message.content[p];
23
+ if(content.type === 'text'){
24
+ contents.push({
25
+ type: 'input_text',
26
+ text: content.content
27
+ })
28
+ }
29
+ if(content.type === 'image' && model.input.includes("image")){
30
+ contents.push({
31
+ type: 'input_image',
32
+ detail: 'auto',
33
+ image_url: `data:${content.mimeType};base64,${content.data}`
34
+ })
35
+ }
36
+ if(content.type === 'file' && model.input.includes("file")){
37
+ contents.push({
38
+ type: 'input_file',
39
+ file_data: `data:${content.mimeType};base64,${content.data}`
40
+ })
41
+ }
42
+ }
43
+ openAIMessages.push({
44
+ role: 'user',
45
+ content: contents
46
+ })
47
+ }
48
+
49
+ // normalize for tool results
50
+ if(message.role === 'toolResult'){
51
+ const toolOutputs: ResponseFunctionCallOutputItemList = []
52
+ let hasText = false;
53
+ let hasImg = false;
54
+ let hasFile = false;
55
+ for (let p=0; p< message.content.length; p++){
56
+ const content = message.content[p];
57
+ if(content.type === 'text'){
58
+ toolOutputs.push({
59
+ type: 'input_text',
60
+ text: content.content
61
+ })
62
+ hasText = true;
63
+ }
64
+ if(content.type === 'image' && model.input.includes("image")){
65
+ toolOutputs.push({
66
+ type: 'input_image',
67
+ detail: 'auto',
68
+ image_url: `data:${content.mimeType};base64,${content.data}`
69
+ })
70
+ hasImg = true
71
+ }
72
+ if(content.type === 'file' && model.input.includes("file")){
73
+ toolOutputs.push({
74
+ type: 'input_file',
75
+ file_data: `data:${content.mimeType};base64,${content.data}`
76
+ })
77
+ hasFile = true
78
+ }
79
+ }
80
+ if(!hasText && (hasImg || hasFile)){
81
+ toolOutputs.push({
82
+ type: 'input_text',
83
+ text: '(see attached)'
84
+ })
85
+ }
86
+ const toolResultInput: ResponseInputItem.FunctionCallOutput = {
87
+ call_id: message.toolCallId!,
88
+ output: toolOutputs,
89
+ type: 'function_call_output',
90
+ }
91
+ openAIMessages.push(toolResultInput)
92
+ }
93
+
94
+ // normalize for Assistant message
95
+ if(message.role === 'assistant'){
96
+ if(message._provider === 'openai'){
97
+ for(let p=0; p<message.message.output.length; p++){
98
+ const outputPart = message.message.output[p];
99
+ if(outputPart.type === 'function_call' || outputPart.type === 'message' || outputPart.type === 'reasoning' ){
100
+ openAIMessages.push(outputPart);
101
+ }
102
+ }
103
+ }
104
+ // TODO Implement other provider conversions
105
+ else{
106
+ throw new Error(
107
+ `Cannot convert ${message._provider} assistant message to ${model.api} format. ` +
108
+ `Cross-provider conversion for ${message._provider} → ${model.api} is not yet implemented.`
109
+ );
110
+ }
111
+ }
112
+
113
+ }
114
+
115
+ return openAIMessages;
116
+ }
117
+
118
+
119
+ export function buildGoogleMessages(model: Model<'google'> ,context: Context): ContentListUnion {
120
+ const contents: ContentListUnion = []
121
+
122
+ for (let i=0; i< context.messages.length; i++){
123
+ const message = context.messages[i];
124
+
125
+ if(message.role === 'user'){
126
+ const parts: Part[] = [];
127
+ for(let p=0; p<message.content.length; p++){
128
+ const messageContent = message.content[p];
129
+ if(messageContent.type === 'text'){
130
+ parts.push({
131
+ text: messageContent.content
132
+ })
133
+ }
134
+ if(messageContent.type === 'image' && model.input.includes("image")){
135
+ parts.push({
136
+ inlineData: {
137
+ mimeType: messageContent.mimeType,
138
+ data: messageContent.data
139
+ }
140
+ })
141
+ }
142
+ if(messageContent.type === 'file' && model.input.includes("file")){
143
+ parts.push({
144
+ inlineData: {
145
+ mimeType: messageContent.mimeType,
146
+ data: messageContent.data
147
+ }
148
+ })
149
+ }
150
+ }
151
+ contents.push({
152
+ role: 'user',
153
+ parts
154
+ })
155
+ }
156
+
157
+ if(message.role === 'toolResult'){
158
+ const parts : Part[] = [];
159
+ let textRes = '(see attached:)';
160
+ for(let p=0; p<message.content.length; p++){
161
+ const messageContent = message.content[p];
162
+ if(messageContent.type === 'text'){
163
+ textRes = messageContent.content
164
+ }
165
+ if(messageContent.type === 'image' && model.input.includes("image")){
166
+ parts.push({
167
+ inlineData: {
168
+ mimeType: messageContent.mimeType,
169
+ data: messageContent.data
170
+ }
171
+ })
172
+ }
173
+ if(messageContent.type === 'file' && model.input.includes("file")){
174
+ parts.push({
175
+ inlineData: {
176
+ mimeType: messageContent.mimeType,
177
+ data: messageContent.data
178
+ }
179
+ })
180
+ }
181
+ }
182
+ contents.push({
183
+ role: 'user',
184
+ parts: [
185
+ {
186
+ functionResponse: {
187
+ id: message.toolCallId,
188
+ name: message.toolName,
189
+ parts,
190
+ response: {
191
+ result: textRes,
192
+ isError: message.isError
193
+ }
194
+ }
195
+ }
196
+ ]
197
+ })
198
+ }
199
+
200
+ if(message.role === 'assistant'){
201
+ if(message._provider === 'google'){
202
+ if(message.message.candidates){
203
+ for(let p=0; p< message.message.candidates?.length; p++){
204
+ const candidate = message.message.candidates[p];
205
+ if(candidate.content){
206
+ contents.push(candidate.content)
207
+ }
208
+ }
209
+ }
210
+ }
211
+ // TODO Implement other provider conversions
212
+ else{
213
+ throw new Error(
214
+ `Cannot convert ${message._provider} assistant message to ${model.api} format. ` +
215
+ `Cross-provider conversion for ${message._provider} → ${model.api} is not yet implemented.`
216
+ );
217
+ }
218
+ }
219
+
220
+ }
221
+ return contents;
222
+ }