@tyvm/knowhow 0.0.156 → 0.0.158

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 (155) hide show
  1. package/README.md +17 -1
  2. package/package.json +1 -1
  3. package/src/agents/base/base.ts +176 -39
  4. package/src/auth/browserLogin.ts +2 -13
  5. package/src/auth/environmentAuth.ts +51 -0
  6. package/src/auth/jwtStore.ts +70 -0
  7. package/src/auth/keyAuth.ts +4 -15
  8. package/src/auth/registerIdentity.ts +8 -8
  9. package/src/chat/modules/AgentModule.ts +3 -3
  10. package/src/chat/modules/RemoteSyncModule.ts +45 -17
  11. package/src/cli.ts +2 -0
  12. package/src/clients/anthropic.ts +156 -79
  13. package/src/clients/attribution.ts +53 -0
  14. package/src/clients/http.ts +211 -84
  15. package/src/clients/index.ts +45 -13
  16. package/src/clients/openai.ts +441 -220
  17. package/src/clients/openrouter.ts +10 -3
  18. package/src/clients/pricing/fireworks.ts +32 -2
  19. package/src/clients/pricing/openai.ts +15 -1
  20. package/src/clients/pricing/types.ts +15 -0
  21. package/src/clients/responseMetadata.ts +221 -0
  22. package/src/clients/types.ts +49 -4
  23. package/src/commands/mcp.ts +1 -0
  24. package/src/commands/misc.ts +1 -1
  25. package/src/commands/remote.ts +77 -0
  26. package/src/config.ts +9 -1
  27. package/src/login.ts +54 -42
  28. package/src/remotes.ts +110 -0
  29. package/src/services/KnowhowClient.ts +12 -16
  30. package/src/services/Mcp.ts +94 -7
  31. package/src/services/McpWebsocketTransport.ts +14 -11
  32. package/src/types.ts +12 -0
  33. package/tests/unit/McpWebsocketTransport.test.ts +56 -0
  34. package/tests/unit/auth/jwtStore.test.ts +55 -0
  35. package/tests/unit/auth/registerIdentity.test.ts +8 -2
  36. package/tests/unit/clients/anthropic.responseMetadata.test.ts +81 -0
  37. package/tests/unit/clients/attribution.test.ts +185 -0
  38. package/tests/unit/clients/http.responseMetadata.test.ts +86 -0
  39. package/tests/unit/clients/openai.responseMetadata.test.ts +90 -0
  40. package/tests/unit/clients/responseMetadata.test.ts +123 -0
  41. package/tests/unit/commands/remote.test.ts +79 -0
  42. package/tests/unit/login.test.ts +64 -3
  43. package/tests/unit/services/KnowhowClient.test.ts +2 -2
  44. package/tests/unit/services/Mcp.test.ts +2 -2
  45. package/ts_build/package.json +1 -1
  46. package/ts_build/src/agents/base/base.d.ts +8 -3
  47. package/ts_build/src/agents/base/base.js +112 -26
  48. package/ts_build/src/agents/base/base.js.map +1 -1
  49. package/ts_build/src/auth/browserLogin.js +2 -7
  50. package/ts_build/src/auth/browserLogin.js.map +1 -1
  51. package/ts_build/src/auth/environmentAuth.d.ts +5 -0
  52. package/ts_build/src/auth/environmentAuth.js +48 -0
  53. package/ts_build/src/auth/environmentAuth.js.map +1 -0
  54. package/ts_build/src/auth/jwtStore.d.ts +4 -0
  55. package/ts_build/src/auth/jwtStore.js +65 -0
  56. package/ts_build/src/auth/jwtStore.js.map +1 -0
  57. package/ts_build/src/auth/keyAuth.d.ts +1 -1
  58. package/ts_build/src/auth/keyAuth.js +4 -16
  59. package/ts_build/src/auth/keyAuth.js.map +1 -1
  60. package/ts_build/src/auth/registerIdentity.js +6 -7
  61. package/ts_build/src/auth/registerIdentity.js.map +1 -1
  62. package/ts_build/src/chat/modules/AgentModule.js +3 -3
  63. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  64. package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +1 -0
  65. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -7
  66. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  67. package/ts_build/src/cli.js +2 -0
  68. package/ts_build/src/cli.js.map +1 -1
  69. package/ts_build/src/clients/anthropic.d.ts +1 -0
  70. package/ts_build/src/clients/anthropic.js +83 -38
  71. package/ts_build/src/clients/anthropic.js.map +1 -1
  72. package/ts_build/src/clients/attribution.d.ts +2 -0
  73. package/ts_build/src/clients/attribution.js +32 -0
  74. package/ts_build/src/clients/attribution.js.map +1 -0
  75. package/ts_build/src/clients/http.js +100 -20
  76. package/ts_build/src/clients/http.js.map +1 -1
  77. package/ts_build/src/clients/index.d.ts +2 -1
  78. package/ts_build/src/clients/index.js +24 -6
  79. package/ts_build/src/clients/index.js.map +1 -1
  80. package/ts_build/src/clients/openai.d.ts +2 -1
  81. package/ts_build/src/clients/openai.js +225 -99
  82. package/ts_build/src/clients/openai.js.map +1 -1
  83. package/ts_build/src/clients/openrouter.d.ts +2 -2
  84. package/ts_build/src/clients/openrouter.js +6 -2
  85. package/ts_build/src/clients/openrouter.js.map +1 -1
  86. package/ts_build/src/clients/pricing/fireworks.d.ts +4 -0
  87. package/ts_build/src/clients/pricing/fireworks.js +14 -2
  88. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  89. package/ts_build/src/clients/pricing/openai.js +15 -1
  90. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  91. package/ts_build/src/clients/pricing/types.d.ts +6 -0
  92. package/ts_build/src/clients/pricing/types.js.map +1 -1
  93. package/ts_build/src/clients/responseMetadata.d.ts +15 -0
  94. package/ts_build/src/clients/responseMetadata.js +141 -0
  95. package/ts_build/src/clients/responseMetadata.js.map +1 -0
  96. package/ts_build/src/clients/types.d.ts +18 -1
  97. package/ts_build/src/clients/types.js +2 -1
  98. package/ts_build/src/clients/types.js.map +1 -1
  99. package/ts_build/src/commands/mcp.js.map +1 -1
  100. package/ts_build/src/commands/misc.js +1 -1
  101. package/ts_build/src/commands/misc.js.map +1 -1
  102. package/ts_build/src/commands/remote.d.ts +2 -0
  103. package/ts_build/src/commands/remote.js +70 -0
  104. package/ts_build/src/commands/remote.js.map +1 -0
  105. package/ts_build/src/config.js +8 -0
  106. package/ts_build/src/config.js.map +1 -1
  107. package/ts_build/src/login.d.ts +4 -3
  108. package/ts_build/src/login.js +34 -35
  109. package/ts_build/src/login.js.map +1 -1
  110. package/ts_build/src/remotes.d.ts +14 -0
  111. package/ts_build/src/remotes.js +117 -0
  112. package/ts_build/src/remotes.js.map +1 -0
  113. package/ts_build/src/services/KnowhowClient.d.ts +1 -1
  114. package/ts_build/src/services/KnowhowClient.js +12 -14
  115. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  116. package/ts_build/src/services/Mcp.d.ts +1 -1
  117. package/ts_build/src/services/Mcp.js +68 -7
  118. package/ts_build/src/services/Mcp.js.map +1 -1
  119. package/ts_build/src/services/McpWebsocketTransport.d.ts +1 -0
  120. package/ts_build/src/services/McpWebsocketTransport.js +11 -6
  121. package/ts_build/src/services/McpWebsocketTransport.js.map +1 -1
  122. package/ts_build/src/types.d.ts +13 -0
  123. package/ts_build/src/types.js.map +1 -1
  124. package/ts_build/tests/unit/McpWebsocketTransport.test.d.ts +1 -0
  125. package/ts_build/tests/unit/McpWebsocketTransport.test.js +47 -0
  126. package/ts_build/tests/unit/McpWebsocketTransport.test.js.map +1 -0
  127. package/ts_build/tests/unit/auth/jwtStore.test.d.ts +1 -0
  128. package/ts_build/tests/unit/auth/jwtStore.test.js +46 -0
  129. package/ts_build/tests/unit/auth/jwtStore.test.js.map +1 -0
  130. package/ts_build/tests/unit/auth/registerIdentity.test.js +8 -2
  131. package/ts_build/tests/unit/auth/registerIdentity.test.js.map +1 -1
  132. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.d.ts +1 -0
  133. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js +77 -0
  134. package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js.map +1 -0
  135. package/ts_build/tests/unit/clients/attribution.test.d.ts +1 -0
  136. package/ts_build/tests/unit/clients/attribution.test.js +153 -0
  137. package/ts_build/tests/unit/clients/attribution.test.js.map +1 -0
  138. package/ts_build/tests/unit/clients/http.responseMetadata.test.d.ts +1 -0
  139. package/ts_build/tests/unit/clients/http.responseMetadata.test.js +77 -0
  140. package/ts_build/tests/unit/clients/http.responseMetadata.test.js.map +1 -0
  141. package/ts_build/tests/unit/clients/openai.responseMetadata.test.d.ts +1 -0
  142. package/ts_build/tests/unit/clients/openai.responseMetadata.test.js +86 -0
  143. package/ts_build/tests/unit/clients/openai.responseMetadata.test.js.map +1 -0
  144. package/ts_build/tests/unit/clients/responseMetadata.test.d.ts +1 -0
  145. package/ts_build/tests/unit/clients/responseMetadata.test.js +96 -0
  146. package/ts_build/tests/unit/clients/responseMetadata.test.js.map +1 -0
  147. package/ts_build/tests/unit/commands/remote.test.d.ts +1 -0
  148. package/ts_build/tests/unit/commands/remote.test.js +70 -0
  149. package/ts_build/tests/unit/commands/remote.test.js.map +1 -0
  150. package/ts_build/tests/unit/login.test.js +44 -3
  151. package/ts_build/tests/unit/login.test.js.map +1 -1
  152. package/ts_build/tests/unit/services/KnowhowClient.test.js +2 -2
  153. package/ts_build/tests/unit/services/KnowhowClient.test.js.map +1 -1
  154. package/ts_build/tests/unit/services/Mcp.test.js +2 -2
  155. package/ts_build/tests/unit/services/Mcp.test.js.map +1 -1
@@ -1,5 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { getConfigSync } from "../config";
3
+ import { emitResponseMetadata } from "./responseMetadata";
3
4
  import { OpenAiTextPricing } from "./pricing";
4
5
  import { ContextLimits } from "./contextLimits";
5
6
  import {
@@ -59,10 +60,39 @@ export class GenericOpenAiClient implements GenericClient {
59
60
  this.setKey(apiKey);
60
61
  }
61
62
 
63
+ private emitErrorMetadata(
64
+ options:
65
+ | CompletionOptions
66
+ | EmbeddingOptions
67
+ | AudioTranscriptionOptions
68
+ | AudioGenerationOptions,
69
+ error: unknown
70
+ ) {
71
+ if (!error || typeof error !== "object") return;
72
+ const apiError = error as {
73
+ status?: number;
74
+ headers?: Headers | Record<string, unknown>;
75
+ request_id?: string;
76
+ requestID?: string;
77
+ response?: Response;
78
+ };
79
+ const statusCode = apiError.status ?? apiError.response?.status;
80
+ const headers = apiError.headers ?? apiError.response?.headers;
81
+ if (typeof statusCode === "number" || headers) {
82
+ emitResponseMetadata(options, {
83
+ statusCode,
84
+ headers,
85
+ requestId: apiError.request_id ?? apiError.requestID,
86
+ });
87
+ }
88
+ }
89
+
62
90
  setKey(apiKey: string) {
63
91
  this.apiKey = apiKey;
64
92
  this.client = new OpenAI({
65
93
  apiKey,
94
+ // SDK retries hide individual responses, which breaks response-scoped telemetry.
95
+ maxRetries: 0,
66
96
  ...(config?.openaiBaseUrl && { baseURL: config.openaiBaseUrl }),
67
97
  });
68
98
  }
@@ -77,7 +107,9 @@ export class GenericOpenAiClient implements GenericClient {
77
107
  return this.detectReasoningEffort(messages);
78
108
  }
79
109
 
80
- detectReasoningEffort(messages: CompletionOptions["messages"]): "low" | "medium" | "high" {
110
+ detectReasoningEffort(
111
+ messages: CompletionOptions["messages"]
112
+ ): "low" | "medium" | "high" {
81
113
  const effortMap: Record<string, "low" | "medium" | "high"> = {
82
114
  ultrathink: "high",
83
115
  "think hard": "high",
@@ -110,8 +142,11 @@ export class GenericOpenAiClient implements GenericClient {
110
142
  return "medium"; // Default to medium if no specific effort is mentioned
111
143
  }
112
144
 
113
- resolveReasoningEffort(options: CompletionOptions): "low" | "medium" | "high" {
114
- const effort = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
145
+ resolveReasoningEffort(
146
+ options: CompletionOptions
147
+ ): "low" | "medium" | "high" {
148
+ const effort =
149
+ options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
115
150
  // Chat Completions only accepts low/medium/high. Preserve newer values on
116
151
  // the Responses API, but map them to the nearest legacy level here.
117
152
  if (effort === "none" || effort === "minimal") return "low";
@@ -125,7 +160,8 @@ export class GenericOpenAiClient implements GenericClient {
125
160
  * If the requested level is not supported, picks the lowest supported level.
126
161
  */
127
162
  resolveReasoningEffortForModel(options: CompletionOptions): string {
128
- const raw = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
163
+ const raw =
164
+ options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
129
165
  // Anthropic calls its highest level `max`; OpenAI calls it `xhigh`.
130
166
  // Accept both provider-neutral CLI spellings.
131
167
  const requested = raw === "max" ? "xhigh" : raw;
@@ -175,10 +211,12 @@ export class GenericOpenAiClient implements GenericClient {
175
211
  content: Array.isArray(msg.content)
176
212
  ? textParts.length
177
213
  ? textParts
178
- : [{
179
- type: "text",
180
- text: "Image output attached in the following user message.",
181
- }]
214
+ : [
215
+ {
216
+ type: "text",
217
+ text: "Image output attached in the following user message.",
218
+ },
219
+ ]
182
220
  : msg.content || "",
183
221
  };
184
222
 
@@ -199,15 +237,13 @@ export class GenericOpenAiClient implements GenericClient {
199
237
  for (const part of content) {
200
238
  if (part.type === "text") {
201
239
  result.push({ type: "input_text", text: part.text });
202
- }
203
- else if (part.type === "image_url") {
240
+ } else if (part.type === "image_url") {
204
241
  result.push({
205
242
  type: "input_image",
206
243
  image_url: part.image_url.url,
207
244
  detail: part.image_url.detail || "auto",
208
245
  });
209
- }
210
- else {
246
+ } else {
211
247
  throw new Error(
212
248
  `OpenAI Responses API conversion does not support ${part.type} content.`
213
249
  );
@@ -226,20 +262,41 @@ export class GenericOpenAiClient implements GenericClient {
226
262
 
227
263
  const openaiMessages = this.toChatCompletionMessages(options.messages);
228
264
 
229
- const response = await this.client.chat.completions.create({
230
- model: options.model,
231
- messages: openaiMessages,
232
- max_tokens: options.max_tokens,
233
- ...(OpenAiReasoningModels.includes(options.model) && !this.isReasoningDisabled(options) && {
234
- max_tokens: undefined,
235
- max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
236
- reasoning_effort: this.resolveReasoningEffort(options),
237
- }),
238
- ...(options.tools?.length && {
239
- tools: options.tools,
240
- tool_choice: options.tool_choice ?? "auto",
241
- }),
242
- }, { signal: options.signal });
265
+ let result;
266
+ try {
267
+ result = await this.client.chat.completions
268
+ .create(
269
+ {
270
+ model: options.model,
271
+ messages: openaiMessages,
272
+ max_tokens: options.max_tokens,
273
+ ...(OpenAiReasoningModels.includes(options.model) &&
274
+ !this.isReasoningDisabled(options) && {
275
+ max_tokens: undefined,
276
+ max_completion_tokens: Math.max(
277
+ options.max_tokens ?? 0,
278
+ 16_000
279
+ ),
280
+ reasoning_effort: this.resolveReasoningEffort(options),
281
+ }),
282
+ ...(options.tools?.length && {
283
+ tools: options.tools,
284
+ tool_choice: options.tool_choice ?? "auto",
285
+ }),
286
+ },
287
+ { signal: options.signal }
288
+ )
289
+ .withResponse();
290
+ } catch (error) {
291
+ this.emitErrorMetadata(options, error);
292
+ throw error;
293
+ }
294
+ const { data: response, response: nativeResponse, request_id } = result;
295
+ emitResponseMetadata(options, {
296
+ statusCode: nativeResponse.status,
297
+ headers: nativeResponse.headers,
298
+ requestId: request_id,
299
+ });
243
300
 
244
301
  const usdCost = this.calculateCost(options.model, response.usage);
245
302
 
@@ -255,19 +312,24 @@ export class GenericOpenAiClient implements GenericClient {
255
312
  })),
256
313
 
257
314
  model: options.model,
258
- usage: response.usage ? {
259
- prompt_tokens: response.usage.prompt_tokens ?? 0,
260
- completion_tokens: response.usage.completion_tokens ?? 0,
261
- total_tokens: response.usage.total_tokens,
262
- prompt_tokens_details: {
263
- cached_tokens: response.usage.prompt_tokens_details?.cached_tokens ?? 0,
264
- },
265
- ...(response.usage.completion_tokens_details?.reasoning_tokens != null && {
266
- output_tokens_details: {
267
- reasoning_tokens: response.usage.completion_tokens_details.reasoning_tokens,
268
- },
269
- }),
270
- } : undefined,
315
+ usage: response.usage
316
+ ? {
317
+ prompt_tokens: response.usage.prompt_tokens ?? 0,
318
+ completion_tokens: response.usage.completion_tokens ?? 0,
319
+ total_tokens: response.usage.total_tokens,
320
+ prompt_tokens_details: {
321
+ cached_tokens:
322
+ response.usage.prompt_tokens_details?.cached_tokens ?? 0,
323
+ },
324
+ ...(response.usage.completion_tokens_details?.reasoning_tokens !=
325
+ null && {
326
+ output_tokens_details: {
327
+ reasoning_tokens:
328
+ response.usage.completion_tokens_details.reasoning_tokens,
329
+ },
330
+ }),
331
+ }
332
+ : undefined,
271
333
  usd_cost: usdCost,
272
334
  };
273
335
  }
@@ -279,7 +341,12 @@ export class GenericOpenAiClient implements GenericClient {
279
341
  */
280
342
  async *createChatCompletionStream(
281
343
  options: CompletionOptions
282
- ): AsyncGenerator<{ delta?: string; done: boolean; usage?: CompletionResponse['usage']; usd_cost?: number }> {
344
+ ): AsyncGenerator<{
345
+ delta?: string;
346
+ done: boolean;
347
+ usage?: CompletionResponse["usage"];
348
+ usd_cost?: number;
349
+ }> {
283
350
  if (OpenAiResponsesOnlyModels.includes(options.model)) {
284
351
  // Fallback: non-streaming for Responses-only models
285
352
  const result = await this.createChatCompletion(options);
@@ -290,19 +357,36 @@ export class GenericOpenAiClient implements GenericClient {
290
357
 
291
358
  const openaiMessages = this.toChatCompletionMessages(options.messages);
292
359
 
293
- const stream = await this.client.chat.completions.create({
294
- model: options.model,
295
- messages: openaiMessages,
296
- max_tokens: options.max_tokens,
297
- stream: true,
298
- stream_options: { include_usage: true },
299
- ...(options.tools?.length && {
300
- tools: options.tools,
301
- tool_choice: options.tool_choice ?? "auto",
302
- }),
303
- }, { signal: options.signal });
304
-
305
- let usage: CompletionResponse['usage'] | undefined;
360
+ let result;
361
+ try {
362
+ result = await this.client.chat.completions
363
+ .create(
364
+ {
365
+ model: options.model,
366
+ messages: openaiMessages,
367
+ max_tokens: options.max_tokens,
368
+ stream: true,
369
+ stream_options: { include_usage: true },
370
+ ...(options.tools?.length && {
371
+ tools: options.tools,
372
+ tool_choice: options.tool_choice ?? "auto",
373
+ }),
374
+ },
375
+ { signal: options.signal }
376
+ )
377
+ .withResponse();
378
+ } catch (error) {
379
+ this.emitErrorMetadata(options, error);
380
+ throw error;
381
+ }
382
+ const { data: stream, response: nativeResponse, request_id } = result;
383
+ emitResponseMetadata(options, {
384
+ statusCode: nativeResponse.status,
385
+ headers: nativeResponse.headers,
386
+ requestId: request_id,
387
+ });
388
+
389
+ let usage: CompletionResponse["usage"] | undefined;
306
390
  for await (const chunk of stream) {
307
391
  const delta = chunk.choices[0]?.delta?.content;
308
392
  if (delta) {
@@ -317,7 +401,10 @@ export class GenericOpenAiClient implements GenericClient {
317
401
  }
318
402
  }
319
403
  const usdCost = usage
320
- ? this.calculateCost(options.model, usage as OpenAI.ChatCompletion["usage"])
404
+ ? this.calculateCost(
405
+ options.model,
406
+ usage as OpenAI.ChatCompletion["usage"]
407
+ )
321
408
  : undefined;
322
409
  yield { done: true, usage, usd_cost: usdCost };
323
410
  }
@@ -345,18 +432,27 @@ export class GenericOpenAiClient implements GenericClient {
345
432
  let inString = false;
346
433
  let escaped = false;
347
434
  for (const ch of repaired) {
348
- if (escaped) { escaped = false; continue; }
349
- if (ch === '\\' && inString) { escaped = true; continue; }
350
- if (ch === '"') { inString = !inString; continue; }
435
+ if (escaped) {
436
+ escaped = false;
437
+ continue;
438
+ }
439
+ if (ch === "\\" && inString) {
440
+ escaped = true;
441
+ continue;
442
+ }
443
+ if (ch === '"') {
444
+ inString = !inString;
445
+ continue;
446
+ }
351
447
  if (!inString) {
352
- if (ch === '{' || ch === '[') depth++;
353
- else if (ch === '}' || ch === ']') depth--;
448
+ if (ch === "{" || ch === "[") depth++;
449
+ else if (ch === "}" || ch === "]") depth--;
354
450
  }
355
451
  }
356
452
  // If we're inside a string, close it
357
453
  if (inString) repaired += '"';
358
454
  // Close any open objects/arrays
359
- for (let i = 0; i < depth; i++) repaired += '}';
455
+ for (let i = 0; i < depth; i++) repaired += "}";
360
456
  try {
361
457
  JSON.parse(repaired);
362
458
  return repaired;
@@ -374,7 +470,11 @@ export class GenericOpenAiClient implements GenericClient {
374
470
  */
375
471
  private getReasoningItems(msg: any): any[] {
376
472
  const details = (msg as any)._reasoning_details;
377
- if (details && details.provider === "openai" && Array.isArray(details.items)) {
473
+ if (
474
+ details &&
475
+ details.provider === "openai" &&
476
+ Array.isArray(details.items)
477
+ ) {
378
478
  return details.items;
379
479
  }
380
480
  return (msg as any)._reasoning_items ?? [];
@@ -384,16 +484,15 @@ export class GenericOpenAiClient implements GenericClient {
384
484
  options: CompletionOptions
385
485
  ): Promise<CompletionResponse> {
386
486
  // Extract system message to use as instructions
387
- const systemMessages = options.messages.filter(
388
- (m) => m.role === "system"
389
- );
487
+ const systemMessages = options.messages.filter((m) => m.role === "system");
390
488
  const nonSystemMessages = options.messages.filter(
391
489
  (m) => m.role !== "system"
392
490
  );
393
- const instructions = systemMessages
394
- .map((m) => (typeof m.content === "string" ? m.content : ""))
395
- .join("\n")
396
- .trim() || undefined;
491
+ const instructions =
492
+ systemMessages
493
+ .map((m) => (typeof m.content === "string" ? m.content : ""))
494
+ .join("\n")
495
+ .trim() || undefined;
397
496
 
398
497
  // Convert chat messages to Responses API input items
399
498
  // The Responses API accepts: user/assistant/system messages and function_call_output items
@@ -401,66 +500,71 @@ export class GenericOpenAiClient implements GenericClient {
401
500
  const MAX_TOOL_OUTPUT = 10_485_000;
402
501
  const truncateToolOutput = (s: string) =>
403
502
  s.length > MAX_TOOL_OUTPUT
404
- ? s.slice(0, MAX_TOOL_OUTPUT) + "\n\n[TRUNCATED: output exceeded 10MB API limit]"
503
+ ? s.slice(0, MAX_TOOL_OUTPUT) +
504
+ "\n\n[TRUNCATED: output exceeded 10MB API limit]"
405
505
  : s;
406
- const input: any[] = nonSystemMessages.map((msg) => {
407
- if (msg.role === "tool") {
408
- // OpenAI function outputs cannot directly contain image content. Send
409
- // the required function output, then attach images as a user message.
410
- const parts = Array.isArray(msg.content) ? msg.content : [];
411
- const imageParts = parts.filter((part) => part.type === "image_url");
412
- const text = parts
413
- .filter((part) => part.type === "text")
414
- .map((part: any) => part.text)
415
- .join("\n");
416
- const toolOutput = {
417
- type: "function_call_output",
418
- call_id: msg.tool_call_id,
419
- output: truncateToolOutput(typeof msg.content === "string"
420
- ? msg.content
421
- : text || "Image output attached in the following user message."),
506
+ const input: any[] = nonSystemMessages
507
+ .map((msg) => {
508
+ if (msg.role === "tool") {
509
+ // OpenAI function outputs cannot directly contain image content. Send
510
+ // the required function output, then attach images as a user message.
511
+ const parts = Array.isArray(msg.content) ? msg.content : [];
512
+ const imageParts = parts.filter((part) => part.type === "image_url");
513
+ const text = parts
514
+ .filter((part) => part.type === "text")
515
+ .map((part: any) => part.text)
516
+ .join("\n");
517
+ const toolOutput = {
518
+ type: "function_call_output",
519
+ call_id: msg.tool_call_id,
520
+ output: truncateToolOutput(
521
+ typeof msg.content === "string"
522
+ ? msg.content
523
+ : text || "Image output attached in the following user message."
524
+ ),
525
+ };
526
+ if (!imageParts.length) return toolOutput;
527
+ return [
528
+ toolOutput,
529
+ { role: "user", content: this.toResponseContent(imageParts) },
530
+ ];
531
+ }
532
+ if (msg.role === "assistant" && msg.tool_calls?.length) {
533
+ // assistant message with tool calls → function_call items.
534
+ // If the prior turn captured reasoning items (encrypted_content path),
535
+ // re-inject them *before* the function_call items so the model keeps its
536
+ // chain-of-thought instead of re-deriving it from scratch.
537
+ const reasoningItems = this.getReasoningItems(msg);
538
+ return [
539
+ ...reasoningItems,
540
+ ...msg.tool_calls.map((tc) => ({
541
+ type: "function_call",
542
+ // id must start with 'fc_'; call_id is the original call_ ID used for function_call_output matching
543
+ id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
544
+ call_id: tc.id,
545
+ name: tc.function.name,
546
+ arguments: tc.function.arguments,
547
+ })),
548
+ ];
549
+ }
550
+ if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
551
+ // assistant message (no tool calls) that carried reasoning items →
552
+ // re-inject reasoning items, then the visible message content.
553
+ return [
554
+ ...this.getReasoningItems(msg),
555
+ {
556
+ role: msg.role,
557
+ content: this.toResponseContent(msg.content),
558
+ },
559
+ ];
560
+ }
561
+ // Regular user/assistant message
562
+ return {
563
+ role: msg.role,
564
+ content: this.toResponseContent(msg.content),
422
565
  };
423
- if (!imageParts.length) return toolOutput;
424
- return [
425
- toolOutput,
426
- { role: "user", content: this.toResponseContent(imageParts) },
427
- ];
428
- }
429
- if (msg.role === "assistant" && msg.tool_calls?.length) {
430
- // assistant message with tool calls → function_call items.
431
- // If the prior turn captured reasoning items (encrypted_content path),
432
- // re-inject them *before* the function_call items so the model keeps its
433
- // chain-of-thought instead of re-deriving it from scratch.
434
- const reasoningItems = this.getReasoningItems(msg);
435
- return [
436
- ...reasoningItems,
437
- ...msg.tool_calls.map((tc) => ({
438
- type: "function_call",
439
- // id must start with 'fc_'; call_id is the original call_ ID used for function_call_output matching
440
- id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
441
- call_id: tc.id,
442
- name: tc.function.name,
443
- arguments: tc.function.arguments,
444
- })),
445
- ];
446
- }
447
- if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
448
- // assistant message (no tool calls) that carried reasoning items →
449
- // re-inject reasoning items, then the visible message content.
450
- return [
451
- ...this.getReasoningItems(msg),
452
- {
453
- role: msg.role,
454
- content: this.toResponseContent(msg.content),
455
- },
456
- ];
457
- }
458
- // Regular user/assistant message
459
- return {
460
- role: msg.role,
461
- content: this.toResponseContent(msg.content),
462
- };
463
- }).flat();
566
+ })
567
+ .flat();
464
568
 
465
569
  // Convert Chat Completions tool definitions to Responses API FunctionTool format
466
570
  const tools = options.tools?.map((tool) => ({
@@ -481,38 +585,53 @@ export class GenericOpenAiClient implements GenericClient {
481
585
  OpenAiReasoningModels.includes(options.model) &&
482
586
  !this.isReasoningDisabled(options);
483
587
 
484
- const response = await this.client.responses.create({
485
- model: options.model as any,
486
- input,
487
- ...(instructions && { instructions }),
488
- ...(store && { store: true }),
489
- ...(!store && { store: false }),
490
- ...(options.previous_response_id && {
491
- previous_response_id: options.previous_response_id,
492
- }),
493
- // When stateless, request encrypted reasoning so we can re-inject it next turn.
494
- ...(!store && wantsReasoning && {
495
- include: ["reasoning.encrypted_content"],
496
- }),
497
- // Don't limit max_output_tokens for Responses API - codex truncates tool call arguments when limited
498
- ...(OpenAiReasoningModels.includes(options.model) && {
499
- reasoning: {
500
- ...(this.isReasoningDisabled(options)
501
- ? { effort: "none" }
502
- : {
503
- effort: this.resolveReasoningEffortForModel(options),
504
- ...(options.reasoning_summary && { summary: "auto" }),
505
- }),
506
- },
507
- }),
508
- ...(wantsReasoning && {
509
- max_output_tokens: Math.max(options.max_tokens || 0, 16000),
510
- }),
511
- ...(tools?.length && {
512
- tools,
513
- tool_choice: options.tool_choice ?? "auto",
514
- }),
515
- } as any);
588
+ let result;
589
+ try {
590
+ result = await this.client.responses
591
+ .create({
592
+ model: options.model as any,
593
+ input,
594
+ ...(instructions && { instructions }),
595
+ ...(store && { store: true }),
596
+ ...(!store && { store: false }),
597
+ ...(options.previous_response_id && {
598
+ previous_response_id: options.previous_response_id,
599
+ }),
600
+ // When stateless, request encrypted reasoning so we can re-inject it next turn.
601
+ ...(!store &&
602
+ wantsReasoning && {
603
+ include: ["reasoning.encrypted_content"],
604
+ }),
605
+ // Don't limit max_output_tokens for Responses API - codex truncates tool call arguments when limited
606
+ ...(OpenAiReasoningModels.includes(options.model) && {
607
+ reasoning: {
608
+ ...(this.isReasoningDisabled(options)
609
+ ? { effort: "none" }
610
+ : {
611
+ effort: this.resolveReasoningEffortForModel(options),
612
+ ...(options.reasoning_summary && { summary: "auto" }),
613
+ }),
614
+ },
615
+ }),
616
+ ...(wantsReasoning && {
617
+ max_output_tokens: Math.max(options.max_tokens || 0, 16000),
618
+ }),
619
+ ...(tools?.length && {
620
+ tools,
621
+ tool_choice: options.tool_choice ?? "auto",
622
+ }),
623
+ } as any)
624
+ .withResponse();
625
+ } catch (error) {
626
+ this.emitErrorMetadata(options, error);
627
+ throw error;
628
+ }
629
+ const { data: response, response: nativeResponse, request_id } = result;
630
+ emitResponseMetadata(options, {
631
+ statusCode: nativeResponse.status,
632
+ headers: nativeResponse.headers,
633
+ requestId: request_id,
634
+ });
516
635
 
517
636
  // Map Responses API usage to Chat Completions usage format
518
637
  const usage = response.usage
@@ -525,9 +644,11 @@ export class GenericOpenAiClient implements GenericClient {
525
644
  cached_tokens:
526
645
  response.usage.input_tokens_details?.cached_tokens ?? 0,
527
646
  },
528
- ...((response.usage as any).output_tokens_details?.reasoning_tokens != null && {
647
+ ...((response.usage as any).output_tokens_details?.reasoning_tokens !=
648
+ null && {
529
649
  output_tokens_details: {
530
- reasoning_tokens: (response.usage as any).output_tokens_details.reasoning_tokens,
650
+ reasoning_tokens: (response.usage as any).output_tokens_details
651
+ .reasoning_tokens,
531
652
  },
532
653
  }),
533
654
  }
@@ -574,7 +695,9 @@ export class GenericOpenAiClient implements GenericClient {
574
695
  try {
575
696
  JSON.parse(repairedArgs);
576
697
  } catch (e) {
577
- console.warn(`[Responses API] Invalid JSON arguments for ${fc.name} after repair: ${e.message}`);
698
+ console.warn(
699
+ `[Responses API] Invalid JSON arguments for ${fc.name} after repair: ${e.message}`
700
+ );
578
701
  }
579
702
  toolCalls.push({
580
703
  // Store call_id so function_call_output.call_id matches it in subsequent turns
@@ -640,16 +763,25 @@ export class GenericOpenAiClient implements GenericClient {
640
763
  };
641
764
  }
642
765
 
643
-
644
766
  pricesPerMillion() {
645
767
  return OpenAiTextPricing;
646
768
  }
647
769
 
648
- getPricing(model?: string): import("./pricing/types").ModelPricing | Record<string, import("./pricing/types").ModelPricing> | undefined {
770
+ getPricing(
771
+ model?: string
772
+ ):
773
+ | import("./pricing/types").ModelPricing
774
+ | Record<string, import("./pricing/types").ModelPricing>
775
+ | undefined {
649
776
  if (model !== undefined) {
650
- return OpenAiTextPricing[model as keyof typeof OpenAiTextPricing] as import("./pricing/types").ModelPricing | undefined;
777
+ return OpenAiTextPricing[model as keyof typeof OpenAiTextPricing] as
778
+ | import("./pricing/types").ModelPricing
779
+ | undefined;
651
780
  }
652
- return OpenAiTextPricing as unknown as Record<string, import("./pricing/types").ModelPricing>;
781
+ return OpenAiTextPricing as unknown as Record<
782
+ string,
783
+ import("./pricing/types").ModelPricing
784
+ >;
653
785
  }
654
786
 
655
787
  calculateCost(
@@ -668,10 +800,12 @@ export class GenericOpenAiClient implements GenericClient {
668
800
  ("prompt_tokens_details" in usage &&
669
801
  usage.prompt_tokens_details?.cached_tokens) ||
670
802
  0;
671
- const cachedInputCost = (cachedInputTokens * (pricing.cached_input ?? 0)) / 1e6;
803
+ const cachedInputCost =
804
+ (cachedInputTokens * (pricing.cached_input ?? 0)) / 1e6;
672
805
 
673
806
  const inputTokens = usage.prompt_tokens;
674
- const inputCost = ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
807
+ const inputCost =
808
+ ((inputTokens - cachedInputTokens) * (pricing.input ?? 0)) / 1e6;
675
809
 
676
810
  const outputTokens =
677
811
  ("completion_tokens" in usage && usage?.completion_tokens) || 0;
@@ -684,7 +818,9 @@ export class GenericOpenAiClient implements GenericClient {
684
818
  async getModels(modality?: ModelModality): Promise<{ id: string }[]> {
685
819
  if (modality) {
686
820
  const map: Partial<Record<ModelModality, string[]>> = {
687
- completion: [...new Set([...OpenAiChatModels, ...OpenAiResponsesOnlyModels])],
821
+ completion: [
822
+ ...new Set([...OpenAiChatModels, ...OpenAiResponsesOnlyModels]),
823
+ ],
688
824
  embedding: OpenAiEmbeddingModelsList,
689
825
  image: OpenAiImageModels,
690
826
  audio: [...OpenAiTTSModels, ...OpenAiTranscriptionModels],
@@ -701,9 +837,27 @@ export class GenericOpenAiClient implements GenericClient {
701
837
  }
702
838
 
703
839
  async createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse> {
704
- const openAiEmbedding = await this.client.embeddings.create({
705
- input: options.input,
706
- model: options.model,
840
+ let result;
841
+ try {
842
+ result = await this.client.embeddings
843
+ .create({
844
+ input: options.input,
845
+ model: options.model,
846
+ })
847
+ .withResponse();
848
+ } catch (error) {
849
+ this.emitErrorMetadata(options, error);
850
+ throw error;
851
+ }
852
+ const {
853
+ data: openAiEmbedding,
854
+ response: nativeResponse,
855
+ request_id,
856
+ } = result;
857
+ emitResponseMetadata(options, {
858
+ statusCode: nativeResponse.status,
859
+ headers: nativeResponse.headers,
860
+ requestId: request_id,
707
861
  });
708
862
 
709
863
  return {
@@ -724,19 +878,39 @@ export class GenericOpenAiClient implements GenericClient {
724
878
  file = await OpenAI.toFile(options.file, fileName);
725
879
  }
726
880
 
727
- const response = await this.client.audio.transcriptions.create({
728
- file,
729
- model: options.model || "whisper-1",
730
- language: options.language,
731
- prompt: options.prompt,
732
- response_format: options.response_format || "verbose_json",
733
- temperature: options.temperature,
734
- }, { signal: options.signal });
881
+ let result;
882
+ try {
883
+ result = await this.client.audio.transcriptions
884
+ .create(
885
+ {
886
+ file,
887
+ model: options.model || "whisper-1",
888
+ language: options.language,
889
+ prompt: options.prompt,
890
+ response_format: options.response_format || "verbose_json",
891
+ temperature: options.temperature,
892
+ },
893
+ { signal: options.signal }
894
+ )
895
+ .withResponse();
896
+ } catch (error) {
897
+ this.emitErrorMetadata(options, error);
898
+ throw error;
899
+ }
900
+ const { data: response, response: nativeResponse, request_id } = result;
901
+ emitResponseMetadata(options, {
902
+ statusCode: nativeResponse.status,
903
+ headers: nativeResponse.headers,
904
+ requestId: request_id,
905
+ });
735
906
 
736
907
  // Calculate cost: $0.006 per minute for Whisper
737
- const duration = typeof response === "object" && "duration" in response && typeof response.duration === "number"
738
- ? response.duration
739
- : undefined;
908
+ const duration =
909
+ typeof response === "object" &&
910
+ "duration" in response &&
911
+ typeof response.duration === "number"
912
+ ? response.duration
913
+ : undefined;
740
914
  const usdCost = duration ? (duration / 60) * 0.006 : undefined;
741
915
 
742
916
  if (typeof response === "string") {
@@ -761,13 +935,30 @@ export class GenericOpenAiClient implements GenericClient {
761
935
  async createAudioGeneration(
762
936
  options: AudioGenerationOptions
763
937
  ): Promise<AudioGenerationResponse> {
764
- const response = await this.client.audio.speech.create({
765
- model: options.model,
766
- input: options.input,
767
- voice: options.voice as any,
768
- response_format: options.response_format || "mp3",
769
- speed: options.speed,
770
- }, { signal: options.signal });
938
+ let result;
939
+ try {
940
+ result = await this.client.audio.speech
941
+ .create(
942
+ {
943
+ model: options.model,
944
+ input: options.input,
945
+ voice: options.voice as any,
946
+ response_format: options.response_format || "mp3",
947
+ speed: options.speed,
948
+ },
949
+ { signal: options.signal }
950
+ )
951
+ .withResponse();
952
+ } catch (error) {
953
+ this.emitErrorMetadata(options, error);
954
+ throw error;
955
+ }
956
+ const { data: response, response: nativeResponse, request_id } = result;
957
+ emitResponseMetadata(options, {
958
+ statusCode: nativeResponse.status,
959
+ headers: nativeResponse.headers,
960
+ requestId: request_id,
961
+ });
771
962
 
772
963
  const buffer = Buffer.from(await response.arrayBuffer());
773
964
 
@@ -787,16 +978,19 @@ export class GenericOpenAiClient implements GenericClient {
787
978
  async createImageGeneration(
788
979
  options: ImageGenerationOptions
789
980
  ): Promise<ImageGenerationResponse> {
790
- const response = await this.client.images.generate({
791
- model: options.model,
792
- prompt: options.prompt,
793
- n: options.n,
794
- size: options.size,
795
- quality: options.quality,
796
- style: options.style,
797
- response_format: options.response_format,
798
- user: options.user,
799
- }, { signal: options.signal });
981
+ const response = await this.client.images.generate(
982
+ {
983
+ model: options.model,
984
+ prompt: options.prompt,
985
+ n: options.n,
986
+ size: options.size,
987
+ quality: options.quality,
988
+ style: options.style,
989
+ response_format: options.response_format,
990
+ user: options.user,
991
+ },
992
+ { signal: options.signal }
993
+ );
800
994
 
801
995
  // Cost calculation varies by model and settings
802
996
  // DALL-E 3: $0.040-$0.120 per image depending on quality/size
@@ -873,16 +1067,23 @@ export class GenericOpenAiClient implements GenericClient {
873
1067
  };
874
1068
  }
875
1069
 
876
- async getVideoStatus(options: VideoStatusOptions): Promise<VideoStatusResponse> {
1070
+ async getVideoStatus(
1071
+ options: VideoStatusOptions
1072
+ ): Promise<VideoStatusResponse> {
877
1073
  const apiKey = this.apiKey || process.env.OPENAI_KEY;
878
1074
  if (!apiKey) throw new Error("OpenAI API key not set");
879
- const response = await fetch(`https://api.openai.com/v1/videos/${options.jobId}`, {
880
- method: "GET",
881
- headers: { Authorization: `Bearer ${apiKey}` },
882
- });
1075
+ const response = await fetch(
1076
+ `https://api.openai.com/v1/videos/${options.jobId}`,
1077
+ {
1078
+ method: "GET",
1079
+ headers: { Authorization: `Bearer ${apiKey}` },
1080
+ }
1081
+ );
883
1082
  if (!response.ok) {
884
1083
  const errorText = await response.text();
885
- throw new Error(`OpenAI getVideoStatus failed: ${response.status} ${errorText}`);
1084
+ throw new Error(
1085
+ `OpenAI getVideoStatus failed: ${response.status} ${errorText}`
1086
+ );
886
1087
  }
887
1088
  const data = await response.json();
888
1089
  let status: VideoStatusResponse["status"] = "in_progress";
@@ -898,18 +1099,25 @@ export class GenericOpenAiClient implements GenericClient {
898
1099
  };
899
1100
  }
900
1101
 
901
- async downloadVideo(options: FileDownloadOptions): Promise<FileDownloadResponse> {
1102
+ async downloadVideo(
1103
+ options: FileDownloadOptions
1104
+ ): Promise<FileDownloadResponse> {
902
1105
  const apiKey = this.apiKey || process.env.OPENAI_KEY;
903
1106
  if (!apiKey) throw new Error("OpenAI API key not set");
904
1107
  const fileId = options.fileId;
905
1108
  if (!fileId) throw new Error("downloadVideo requires fileId (the jobId)");
906
- const response = await fetch(`https://api.openai.com/v1/videos/${fileId}/content`, {
907
- method: "GET",
908
- headers: { Authorization: `Bearer ${apiKey}` },
909
- });
1109
+ const response = await fetch(
1110
+ `https://api.openai.com/v1/videos/${fileId}/content`,
1111
+ {
1112
+ method: "GET",
1113
+ headers: { Authorization: `Bearer ${apiKey}` },
1114
+ }
1115
+ );
910
1116
  if (!response.ok) {
911
1117
  const errorText = await response.text();
912
- throw new Error(`OpenAI downloadVideo failed: ${response.status} ${errorText}`);
1118
+ throw new Error(
1119
+ `OpenAI downloadVideo failed: ${response.status} ${errorText}`
1120
+ );
913
1121
  }
914
1122
  const mimeType = response.headers.get("content-type") || "video/mp4";
915
1123
  return { data: Buffer.from(await response.arrayBuffer()), mimeType };
@@ -920,7 +1128,9 @@ export class GenericOpenAiClient implements GenericClient {
920
1128
  if (!apiKey) throw new Error("OpenAI API key not set");
921
1129
  const formData = new FormData();
922
1130
  formData.append("purpose", "assistants");
923
- const blob = new Blob([new Uint8Array(options.data)], { type: options.mimeType || "application/octet-stream" });
1131
+ const blob = new Blob([new Uint8Array(options.data)], {
1132
+ type: options.mimeType || "application/octet-stream",
1133
+ });
924
1134
  formData.append("file", blob, options.fileName || "upload");
925
1135
  const response = await fetch("https://api.openai.com/v1/files", {
926
1136
  method: "POST",
@@ -929,31 +1139,42 @@ export class GenericOpenAiClient implements GenericClient {
929
1139
  });
930
1140
  if (!response.ok) {
931
1141
  const errorText = await response.text();
932
- throw new Error(`OpenAI uploadFile failed: ${response.status} ${errorText}`);
1142
+ throw new Error(
1143
+ `OpenAI uploadFile failed: ${response.status} ${errorText}`
1144
+ );
933
1145
  }
934
1146
  const data = await response.json();
935
1147
  return { fileId: data.id, uri: data.uri };
936
1148
  }
937
1149
 
938
- async downloadFile(options: FileDownloadOptions): Promise<FileDownloadResponse> {
1150
+ async downloadFile(
1151
+ options: FileDownloadOptions
1152
+ ): Promise<FileDownloadResponse> {
939
1153
  const apiKey = this.apiKey || process.env.OPENAI_KEY;
940
1154
  if (!apiKey) throw new Error("OpenAI API key not set");
941
1155
  const fileId = options.fileId;
942
1156
  if (!fileId) throw new Error("downloadFile requires fileId");
943
- const response = await fetch(`https://api.openai.com/v1/files/${fileId}/content`, {
944
- method: "GET",
945
- headers: { Authorization: `Bearer ${apiKey}` },
946
- });
1157
+ const response = await fetch(
1158
+ `https://api.openai.com/v1/files/${fileId}/content`,
1159
+ {
1160
+ method: "GET",
1161
+ headers: { Authorization: `Bearer ${apiKey}` },
1162
+ }
1163
+ );
947
1164
  if (!response.ok) {
948
1165
  const errorText = await response.text();
949
- throw new Error(`OpenAI downloadFile failed: ${response.status} ${errorText}`);
1166
+ throw new Error(
1167
+ `OpenAI downloadFile failed: ${response.status} ${errorText}`
1168
+ );
950
1169
  }
951
1170
  const mimeType = response.headers.get("content-type") || undefined;
952
1171
  const data = Buffer.from(await response.arrayBuffer());
953
1172
  return { data, mimeType };
954
1173
  }
955
1174
 
956
- getContextLimit(model: string): { contextLimit: number; threshold: number } | undefined {
1175
+ getContextLimit(
1176
+ model: string
1177
+ ): { contextLimit: number; threshold: number } | undefined {
957
1178
  const contextLimit = ContextLimits[model];
958
1179
  if (contextLimit === undefined) return undefined;
959
1180
  const pricing = OpenAiTextPricing[model];