@contentgrowth/llm-service 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import OpenAI from 'openai';
2
+ import Anthropic from '@anthropic-ai/sdk';
2
3
  import * as _google_genai from '@google/genai';
3
4
  import { GoogleGenAI } from '@google/genai';
4
5
 
@@ -158,35 +159,71 @@ declare class OpenAIProvider extends BaseLLMProvider {
158
159
  declare class AnthropicProvider extends BaseLLMProvider {
159
160
  models: any;
160
161
  defaultModel: any;
162
+ client: Anthropic;
161
163
  chat(userMessage: any, systemPrompt?: string, options?: {}): Promise<{
162
- text: any;
164
+ text: string;
163
165
  }>;
164
166
  chatCompletion(messages: any, systemPrompt: any, tools?: any, options?: {}): Promise<{
165
- content: any;
166
- tool_calls: any;
167
+ content: string;
168
+ tool_calls: {
169
+ id: string;
170
+ type: string;
171
+ function: {
172
+ name: string;
173
+ arguments: string;
174
+ };
175
+ }[];
167
176
  finishReason: string;
168
- _rawFinishReason: any;
177
+ _rawFinishReason: Anthropic.Messages.StopReason;
169
178
  _responseFormat: any;
170
179
  usage: {
171
- prompt_tokens: any;
172
- completion_tokens: any;
173
- total_tokens: any;
180
+ prompt_tokens: number;
181
+ completion_tokens: number;
182
+ total_tokens: number;
174
183
  };
175
184
  model: any;
176
185
  }>;
177
186
  _chatCompletionWithModel(messages: any, systemPrompt: any, tools: any, modelName: any, maxTokens: any, temperature: any, options?: {}): Promise<{
178
- content: any;
179
- tool_calls: any;
187
+ content: string;
188
+ tool_calls: {
189
+ id: string;
190
+ type: string;
191
+ function: {
192
+ name: string;
193
+ arguments: string;
194
+ };
195
+ }[];
180
196
  finishReason: string;
181
- _rawFinishReason: any;
197
+ _rawFinishReason: Anthropic.Messages.StopReason;
182
198
  _responseFormat: any;
183
199
  usage: {
184
- prompt_tokens: any;
185
- completion_tokens: any;
186
- total_tokens: any;
200
+ prompt_tokens: number;
201
+ completion_tokens: number;
202
+ total_tokens: number;
187
203
  };
188
204
  model: any;
189
205
  }>;
206
+ /**
207
+ * Convert standard {role, content, tool_calls, tool_call_id} messages
208
+ * to Anthropic's message format.
209
+ *
210
+ * Key differences from OpenAI:
211
+ * - No 'system' role in messages array (handled via top-level `system` param)
212
+ * - Tool results go as role:'user' with content type:'tool_result'
213
+ * - Assistant messages with tool calls use an array of content blocks
214
+ * - Roles must strictly alternate user/assistant
215
+ */
216
+ _formatMessages(messages: any): {
217
+ role: string;
218
+ content: any;
219
+ }[];
220
+ /**
221
+ * Convert OpenAI-style tool definitions to Anthropic format.
222
+ * OpenAI: { type: 'function', function: { name, description, parameters } }
223
+ * Anthropic: { name, description, input_schema }
224
+ */
225
+ _formatTools(tools: any): any;
226
+ executeTools(tool_calls: any, messages: any, tenantId: any, toolImplementations: any, env: any): Promise<void>;
190
227
  _getModelForTier(tier: any): any;
191
228
  _shouldAutoParse(options: any): boolean;
192
229
  _safeJsonParse(text: any): any;
@@ -365,6 +402,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
365
402
  getConfig(tenantId: any, env: any): Promise<{
366
403
  provider: any;
367
404
  apiKey: any;
405
+ baseURL: any;
368
406
  project: any;
369
407
  location: any;
370
408
  models: any;
@@ -373,6 +411,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
373
411
  } | {
374
412
  provider: any;
375
413
  apiKey: any;
414
+ baseURL: any;
376
415
  models: any;
377
416
  temperature: number;
378
417
  maxTokens: number;
@@ -384,6 +423,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
384
423
  provider: any;
385
424
  models: any;
386
425
  apiKey: any;
426
+ baseURL: any;
387
427
  project: any;
388
428
  location: any;
389
429
  temperature: number;
@@ -394,6 +434,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
394
434
  _getSystemConfig(env: any): {
395
435
  provider: any;
396
436
  apiKey: any;
437
+ baseURL: any;
397
438
  project: any;
398
439
  location: any;
399
440
  models: any;
@@ -402,6 +443,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
402
443
  } | {
403
444
  provider: any;
404
445
  apiKey: any;
446
+ baseURL: any;
405
447
  models: any;
406
448
  temperature: number;
407
449
  maxTokens: number;
@@ -472,6 +514,7 @@ declare class ConfigManager {
472
514
  static getConfig(tenantId: any, env: any): Promise<{
473
515
  provider: any;
474
516
  apiKey: any;
517
+ baseURL: any;
475
518
  project: any;
476
519
  location: any;
477
520
  models: any;
@@ -480,6 +523,7 @@ declare class ConfigManager {
480
523
  } | {
481
524
  provider: any;
482
525
  apiKey: any;
526
+ baseURL: any;
483
527
  models: any;
484
528
  temperature: number;
485
529
  maxTokens: number;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import OpenAI from 'openai';
2
+ import Anthropic from '@anthropic-ai/sdk';
2
3
  import * as _google_genai from '@google/genai';
3
4
  import { GoogleGenAI } from '@google/genai';
4
5
 
@@ -158,35 +159,71 @@ declare class OpenAIProvider extends BaseLLMProvider {
158
159
  declare class AnthropicProvider extends BaseLLMProvider {
159
160
  models: any;
160
161
  defaultModel: any;
162
+ client: Anthropic;
161
163
  chat(userMessage: any, systemPrompt?: string, options?: {}): Promise<{
162
- text: any;
164
+ text: string;
163
165
  }>;
164
166
  chatCompletion(messages: any, systemPrompt: any, tools?: any, options?: {}): Promise<{
165
- content: any;
166
- tool_calls: any;
167
+ content: string;
168
+ tool_calls: {
169
+ id: string;
170
+ type: string;
171
+ function: {
172
+ name: string;
173
+ arguments: string;
174
+ };
175
+ }[];
167
176
  finishReason: string;
168
- _rawFinishReason: any;
177
+ _rawFinishReason: Anthropic.Messages.StopReason;
169
178
  _responseFormat: any;
170
179
  usage: {
171
- prompt_tokens: any;
172
- completion_tokens: any;
173
- total_tokens: any;
180
+ prompt_tokens: number;
181
+ completion_tokens: number;
182
+ total_tokens: number;
174
183
  };
175
184
  model: any;
176
185
  }>;
177
186
  _chatCompletionWithModel(messages: any, systemPrompt: any, tools: any, modelName: any, maxTokens: any, temperature: any, options?: {}): Promise<{
178
- content: any;
179
- tool_calls: any;
187
+ content: string;
188
+ tool_calls: {
189
+ id: string;
190
+ type: string;
191
+ function: {
192
+ name: string;
193
+ arguments: string;
194
+ };
195
+ }[];
180
196
  finishReason: string;
181
- _rawFinishReason: any;
197
+ _rawFinishReason: Anthropic.Messages.StopReason;
182
198
  _responseFormat: any;
183
199
  usage: {
184
- prompt_tokens: any;
185
- completion_tokens: any;
186
- total_tokens: any;
200
+ prompt_tokens: number;
201
+ completion_tokens: number;
202
+ total_tokens: number;
187
203
  };
188
204
  model: any;
189
205
  }>;
206
+ /**
207
+ * Convert standard {role, content, tool_calls, tool_call_id} messages
208
+ * to Anthropic's message format.
209
+ *
210
+ * Key differences from OpenAI:
211
+ * - No 'system' role in messages array (handled via top-level `system` param)
212
+ * - Tool results go as role:'user' with content type:'tool_result'
213
+ * - Assistant messages with tool calls use an array of content blocks
214
+ * - Roles must strictly alternate user/assistant
215
+ */
216
+ _formatMessages(messages: any): {
217
+ role: string;
218
+ content: any;
219
+ }[];
220
+ /**
221
+ * Convert OpenAI-style tool definitions to Anthropic format.
222
+ * OpenAI: { type: 'function', function: { name, description, parameters } }
223
+ * Anthropic: { name, description, input_schema }
224
+ */
225
+ _formatTools(tools: any): any;
226
+ executeTools(tool_calls: any, messages: any, tenantId: any, toolImplementations: any, env: any): Promise<void>;
190
227
  _getModelForTier(tier: any): any;
191
228
  _shouldAutoParse(options: any): boolean;
192
229
  _safeJsonParse(text: any): any;
@@ -365,6 +402,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
365
402
  getConfig(tenantId: any, env: any): Promise<{
366
403
  provider: any;
367
404
  apiKey: any;
405
+ baseURL: any;
368
406
  project: any;
369
407
  location: any;
370
408
  models: any;
@@ -373,6 +411,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
373
411
  } | {
374
412
  provider: any;
375
413
  apiKey: any;
414
+ baseURL: any;
376
415
  models: any;
377
416
  temperature: number;
378
417
  maxTokens: number;
@@ -384,6 +423,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
384
423
  provider: any;
385
424
  models: any;
386
425
  apiKey: any;
426
+ baseURL: any;
387
427
  project: any;
388
428
  location: any;
389
429
  temperature: number;
@@ -394,6 +434,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
394
434
  _getSystemConfig(env: any): {
395
435
  provider: any;
396
436
  apiKey: any;
437
+ baseURL: any;
397
438
  project: any;
398
439
  location: any;
399
440
  models: any;
@@ -402,6 +443,7 @@ declare class DefaultConfigProvider extends BaseConfigProvider$1 {
402
443
  } | {
403
444
  provider: any;
404
445
  apiKey: any;
446
+ baseURL: any;
405
447
  models: any;
406
448
  temperature: number;
407
449
  maxTokens: number;
@@ -472,6 +514,7 @@ declare class ConfigManager {
472
514
  static getConfig(tenantId: any, env: any): Promise<{
473
515
  provider: any;
474
516
  apiKey: any;
517
+ baseURL: any;
475
518
  project: any;
476
519
  location: any;
477
520
  models: any;
@@ -480,6 +523,7 @@ declare class ConfigManager {
480
523
  } | {
481
524
  provider: any;
482
525
  apiKey: any;
526
+ baseURL: any;
483
527
  models: any;
484
528
  temperature: number;
485
529
  maxTokens: number;
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
74
74
  models: MODEL_CONFIGS[tenantConfig.provider],
75
75
  // for backward compatibility, api_key is deprecated and shall never be used!!
76
76
  apiKey: tenantConfig.apiKey || tenantConfig.api_key,
77
+ baseURL: tenantConfig.baseURL || tenantConfig.base_url || void 0,
77
78
  project: tenantConfig.project,
78
79
  location: tenantConfig.location,
79
80
  temperature: parseFloat(env.DEFAULT_TEMPERATURE || "0.7"),
@@ -87,9 +88,11 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
87
88
  const provider = ((_a = env.LLM_PROVIDER) == null ? void 0 : _a.toLowerCase()) || "openai";
88
89
  const providerDefaults = MODEL_CONFIGS[provider] || MODEL_CONFIGS["openai"];
89
90
  let apiKey;
91
+ let baseURL;
90
92
  let models = { ...providerDefaults };
91
93
  if (provider === "openai") {
92
94
  apiKey = env.OPENAI_API_KEY;
95
+ baseURL = env.OPENAI_BASE_URL || env.LLM_BASE_URL || void 0;
93
96
  models = {
94
97
  default: env.OPENAI_MODEL || providerDefaults.default,
95
98
  edge: env.OPENAI_MODEL_EDGE || providerDefaults.edge,
@@ -99,6 +102,7 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
99
102
  };
100
103
  } else if (provider === "gemini") {
101
104
  apiKey = env.GEMINI_API_KEY;
105
+ baseURL = env.GEMINI_BASE_URL || env.LLM_BASE_URL || void 0;
102
106
  models = {
103
107
  default: env.GEMINI_MODEL || providerDefaults.default,
104
108
  edge: env.GEMINI_MODEL_EDGE || providerDefaults.edge,
@@ -108,8 +112,19 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
108
112
  image: env.GEMINI_IMAGE_MODEL || providerDefaults.image,
109
113
  video: env.GEMINI_VIDEO_MODEL || providerDefaults.video
110
114
  };
115
+ } else if (provider === "anthropic") {
116
+ apiKey = env.ANTHROPIC_API_KEY;
117
+ baseURL = env.ANTHROPIC_BASE_URL || env.LLM_BASE_URL || void 0;
118
+ models = {
119
+ default: env.ANTHROPIC_MODEL || providerDefaults.default,
120
+ edge: env.ANTHROPIC_MODEL_EDGE || providerDefaults.edge,
121
+ fast: env.ANTHROPIC_MODEL_FAST || providerDefaults.fast,
122
+ cost: env.ANTHROPIC_MODEL_COST || providerDefaults.cost,
123
+ free: env.ANTHROPIC_MODEL_FREE || providerDefaults.free
124
+ };
111
125
  } else if (provider === "vertex") {
112
126
  apiKey = env.VERTEX_API_KEY;
127
+ baseURL = env.VERTEX_BASE_URL || env.LLM_BASE_URL || void 0;
113
128
  const project = env.VERTEX_PROJECT || env.GOOGLE_CLOUD_PROJECT;
114
129
  const location = env.VERTEX_LOCATION || env.GOOGLE_CLOUD_LOCATION || "us-central1";
115
130
  models = {
@@ -124,6 +139,7 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
124
139
  return {
125
140
  provider,
126
141
  apiKey,
142
+ baseURL,
127
143
  project,
128
144
  location,
129
145
  models,
@@ -134,6 +150,7 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
134
150
  return {
135
151
  provider,
136
152
  apiKey,
153
+ baseURL,
137
154
  models,
138
155
  temperature: parseFloat(env.DEFAULT_TEMPERATURE || "0.7"),
139
156
  maxTokens: parseInt(env.DEFAULT_MAX_TOKENS || "65536")
@@ -397,7 +414,11 @@ function extractTextAndJson(input) {
397
414
  var OpenAIProvider = class extends BaseLLMProvider {
398
415
  constructor(config) {
399
416
  super(config);
400
- this.client = new OpenAI({ apiKey: config.apiKey });
417
+ const clientOptions = { apiKey: config.apiKey };
418
+ if (config.baseURL) {
419
+ clientOptions.baseURL = config.baseURL;
420
+ }
421
+ this.client = new OpenAI(clientOptions);
401
422
  this.models = config.models;
402
423
  this.defaultModel = config.models.default;
403
424
  }
@@ -579,11 +600,13 @@ var GoogleProvider = class extends BaseLLMProvider {
579
600
  this.models = config.models;
580
601
  this.defaultModel = config.models.default;
581
602
  this._pendingOperations = /* @__PURE__ */ new Map();
603
+ const httpOptions = config.baseURL ? { baseUrl: config.baseURL } : void 0;
582
604
  if (config.provider === "vertex") {
583
605
  if (config.apiKey) {
584
606
  this.client = new GoogleGenAI({
585
607
  vertexai: true,
586
- apiKey: config.apiKey
608
+ apiKey: config.apiKey,
609
+ ...httpOptions ? { httpOptions } : {}
587
610
  });
588
611
  } else {
589
612
  if (!config.project) {
@@ -592,12 +615,14 @@ var GoogleProvider = class extends BaseLLMProvider {
592
615
  this.client = new GoogleGenAI({
593
616
  vertexai: true,
594
617
  project: config.project,
595
- location: config.location || "us-central1"
618
+ location: config.location || "us-central1",
619
+ ...httpOptions ? { httpOptions } : {}
596
620
  });
597
621
  }
598
622
  } else {
599
623
  this.client = new GoogleGenAI({
600
- apiKey: config.apiKey
624
+ apiKey: config.apiKey,
625
+ ...httpOptions ? { httpOptions } : {}
601
626
  });
602
627
  }
603
628
  }
@@ -1149,11 +1174,17 @@ ${prompt}` : prompt;
1149
1174
  };
1150
1175
 
1151
1176
  // src/llm/providers/anthropic-provider.js
1177
+ import Anthropic from "@anthropic-ai/sdk";
1152
1178
  var AnthropicProvider = class extends BaseLLMProvider {
1153
1179
  constructor(config) {
1154
1180
  super(config);
1155
1181
  this.models = config.models || {};
1156
1182
  this.defaultModel = this.models.default || "claude-3-5-sonnet-20241022";
1183
+ const clientOptions = { apiKey: config.apiKey || "" };
1184
+ if (config.baseURL) {
1185
+ clientOptions.baseURL = config.baseURL;
1186
+ }
1187
+ this.client = new Anthropic(clientOptions);
1157
1188
  }
1158
1189
  async chat(userMessage, systemPrompt = "", options = {}) {
1159
1190
  var _a;
@@ -1190,82 +1221,69 @@ var AnthropicProvider = class extends BaseLLMProvider {
1190
1221
  );
1191
1222
  }
1192
1223
  async _chatCompletionWithModel(messages, systemPrompt, tools, modelName, maxTokens, temperature, options = {}) {
1193
- const baseURL = this.config.baseURL || "https://api.anthropic.com";
1194
- const url = `${baseURL.replace(/\/$/, "")}/v1/messages`;
1195
- const formattedMessages = messages.map((m) => ({
1196
- role: m.role === "assistant" ? "assistant" : "user",
1197
- content: m.content
1198
- }));
1199
- const headers = {
1200
- "Content-Type": "application/json",
1201
- "x-api-key": this.config.apiKey || "",
1202
- "anthropic-version": "2023-06-01"
1203
- };
1204
- const payload = {
1205
- model: modelName,
1206
- messages: formattedMessages,
1207
- max_tokens: maxTokens,
1208
- temperature
1209
- };
1210
- if (systemPrompt) {
1211
- payload.system = systemPrompt;
1212
- }
1224
+ var _a, _b, _c, _d;
1225
+ let effectiveSystem = systemPrompt || void 0;
1213
1226
  if (options.responseFormat) {
1214
1227
  const formatType = typeof options.responseFormat === "string" ? options.responseFormat : options.responseFormat.type;
1215
1228
  if (formatType === "json" || formatType === "json_schema") {
1216
- const schemaText = options.responseFormat.schema || options.responseSchema ? `
1229
+ const schema = options.responseFormat.schema || options.responseSchema;
1230
+ const schemaText = schema ? `
1217
1231
 
1218
- Your output must comply strictly with this JSON Schema: ${JSON.stringify(options.responseFormat.schema || options.responseSchema)}` : "";
1232
+ Your output must comply strictly with this JSON Schema: ${JSON.stringify(schema)}` : "";
1219
1233
  const jsonPrompt = `IMPORTANT: You must respond ONLY in raw JSON format. Do not write any conversational text, explanations, or markdown code blocks (e.g. do NOT wrap your answer in \`\`\`json ... \`\`\`). Your response must start with '{' and end with '}'.${schemaText}`;
1220
- if (payload.system) {
1221
- payload.system = `${payload.system}
1234
+ effectiveSystem = effectiveSystem ? `${effectiveSystem}
1222
1235
 
1223
- ${jsonPrompt}`;
1224
- } else {
1225
- payload.system = jsonPrompt;
1226
- }
1236
+ ${jsonPrompt}` : jsonPrompt;
1227
1237
  }
1228
1238
  }
1229
- let responseText;
1230
- let responseData;
1239
+ const anthropicMessages = this._formatMessages(messages);
1240
+ const anthropicTools = tools ? this._formatTools(tools) : void 0;
1241
+ const requestParams = {
1242
+ model: modelName,
1243
+ max_tokens: maxTokens,
1244
+ temperature,
1245
+ messages: anthropicMessages,
1246
+ ...effectiveSystem ? { system: effectiveSystem } : {},
1247
+ ...anthropicTools ? { tools: anthropicTools } : {}
1248
+ };
1249
+ let response;
1231
1250
  try {
1232
- const res = await fetch(url, {
1233
- method: "POST",
1234
- headers,
1235
- body: JSON.stringify(payload)
1236
- });
1237
- responseText = await res.text();
1238
- if (!res.ok) {
1239
- console.error(`[AnthropicProvider] API request failed with status ${res.status}:`, responseText);
1240
- throw new LLMServiceException(`Anthropic API Error: ${responseText}`, res.status);
1241
- }
1242
- responseData = JSON.parse(responseText);
1251
+ response = await this.client.messages.create(requestParams);
1243
1252
  } catch (error) {
1244
1253
  console.error(`[AnthropicProvider] request failed (API Key: ${this._getMaskedApiKey()}):`, error);
1245
1254
  throw error;
1246
1255
  }
1247
- const contentBlock = responseData.content && responseData.content[0];
1248
- const contentText = contentBlock ? contentBlock.text : "";
1249
- if (!contentText) {
1250
- console.error("[AnthropicProvider] Model returned empty response content");
1256
+ const textBlocks = response.content.filter((b) => b.type === "text");
1257
+ const toolUseBlocks = response.content.filter((b) => b.type === "tool_use");
1258
+ const contentText = textBlocks.map((b) => b.text).join("");
1259
+ if (!contentText && toolUseBlocks.length === 0) {
1260
+ console.error("[AnthropicProvider] Model returned empty response (no text, no tool calls)");
1251
1261
  throw new LLMServiceException(
1252
1262
  "Model returned empty response. This usually means the prompt or schema is confusing the model.",
1253
1263
  500
1254
1264
  );
1255
1265
  }
1256
- const rawFinishReason = responseData.stop_reason;
1266
+ const toolCalls = toolUseBlocks.length > 0 ? toolUseBlocks.map((block) => ({
1267
+ id: block.id,
1268
+ type: "function",
1269
+ function: {
1270
+ name: block.name,
1271
+ // Anthropic returns args as a parsed object; serialize to string to match OpenAI shape
1272
+ arguments: JSON.stringify(block.input || {})
1273
+ }
1274
+ })) : null;
1275
+ const rawFinishReason = response.stop_reason;
1257
1276
  const normalizedFinishReason = this.normalizeFinishReason(rawFinishReason);
1258
1277
  const result = {
1259
1278
  content: contentText,
1260
- tool_calls: null,
1261
- // REST client tool calls mapping not required for playbook studio
1279
+ tool_calls: toolCalls,
1262
1280
  finishReason: normalizedFinishReason,
1263
1281
  _rawFinishReason: rawFinishReason,
1264
1282
  _responseFormat: options.responseFormat,
1265
1283
  usage: {
1266
- prompt_tokens: responseData.usage ? responseData.usage.input_tokens : 0,
1267
- completion_tokens: responseData.usage ? responseData.usage.output_tokens : 0,
1268
- total_tokens: responseData.usage ? responseData.usage.input_tokens + responseData.usage.output_tokens : 0
1284
+ prompt_tokens: ((_a = response.usage) == null ? void 0 : _a.input_tokens) || 0,
1285
+ completion_tokens: ((_b = response.usage) == null ? void 0 : _b.output_tokens) || 0,
1286
+ total_tokens: (((_c = response.usage) == null ? void 0 : _c.input_tokens) || 0) + (((_d = response.usage) == null ? void 0 : _d.output_tokens) || 0)
1269
1287
  },
1270
1288
  model: modelName
1271
1289
  };
@@ -1274,11 +1292,110 @@ ${jsonPrompt}`;
1274
1292
  }
1275
1293
  return result;
1276
1294
  }
1277
- _getModelForTier(tier) {
1278
- if (this.models[tier]) {
1279
- return this.models[tier];
1295
+ /**
1296
+ * Convert standard {role, content, tool_calls, tool_call_id} messages
1297
+ * to Anthropic's message format.
1298
+ *
1299
+ * Key differences from OpenAI:
1300
+ * - No 'system' role in messages array (handled via top-level `system` param)
1301
+ * - Tool results go as role:'user' with content type:'tool_result'
1302
+ * - Assistant messages with tool calls use an array of content blocks
1303
+ * - Roles must strictly alternate user/assistant
1304
+ */
1305
+ _formatMessages(messages) {
1306
+ const result = [];
1307
+ for (const msg of messages) {
1308
+ if (msg.role === "system") {
1309
+ continue;
1310
+ }
1311
+ if (msg.role === "tool") {
1312
+ const toolResultBlock = {
1313
+ type: "tool_result",
1314
+ tool_use_id: msg.tool_call_id,
1315
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
1316
+ };
1317
+ const last = result[result.length - 1];
1318
+ if (last && last.role === "user" && Array.isArray(last.content)) {
1319
+ last.content.push(toolResultBlock);
1320
+ } else {
1321
+ result.push({ role: "user", content: [toolResultBlock] });
1322
+ }
1323
+ continue;
1324
+ }
1325
+ if (msg.role === "assistant") {
1326
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
1327
+ const contentBlocks = [];
1328
+ if (msg.content) {
1329
+ contentBlocks.push({ type: "text", text: msg.content });
1330
+ }
1331
+ for (const tc of msg.tool_calls) {
1332
+ let inputArgs = tc.function.arguments;
1333
+ if (typeof inputArgs === "string") {
1334
+ try {
1335
+ inputArgs = JSON.parse(inputArgs);
1336
+ } catch (_) {
1337
+ inputArgs = {};
1338
+ }
1339
+ }
1340
+ contentBlocks.push({
1341
+ type: "tool_use",
1342
+ id: tc.id,
1343
+ name: tc.function.name,
1344
+ input: inputArgs
1345
+ });
1346
+ }
1347
+ result.push({ role: "assistant", content: contentBlocks });
1348
+ } else {
1349
+ result.push({ role: "assistant", content: msg.content || "" });
1350
+ }
1351
+ continue;
1352
+ }
1353
+ result.push({ role: "user", content: msg.content || "" });
1280
1354
  }
1281
- return this.defaultModel;
1355
+ return result;
1356
+ }
1357
+ /**
1358
+ * Convert OpenAI-style tool definitions to Anthropic format.
1359
+ * OpenAI: { type: 'function', function: { name, description, parameters } }
1360
+ * Anthropic: { name, description, input_schema }
1361
+ */
1362
+ _formatTools(tools) {
1363
+ return tools.map((tool) => {
1364
+ const fn = tool.function || tool;
1365
+ return {
1366
+ name: fn.name,
1367
+ description: fn.description || "",
1368
+ input_schema: fn.parameters || fn.input_schema || { type: "object", properties: {} }
1369
+ };
1370
+ });
1371
+ }
1372
+ async executeTools(tool_calls, messages, tenantId, toolImplementations, env) {
1373
+ const toolResults = await Promise.all(
1374
+ tool_calls.map(async (toolCall) => {
1375
+ const toolName = toolCall.function.name;
1376
+ const tool = toolImplementations[toolName];
1377
+ console.log(`[Tool Call] ${toolName} with arguments:`, toolCall.function.arguments);
1378
+ if (!tool) {
1379
+ console.error(`[Tool Error] Tool '${toolName}' not found`);
1380
+ return { tool_call_id: toolCall.id, output: JSON.stringify({ error: `Tool '${toolName}' not found.` }) };
1381
+ }
1382
+ try {
1383
+ const output = await tool(toolCall.function.arguments, { env, tenantId });
1384
+ const preview = typeof output === "string" ? output.length > 200 ? output.substring(0, 200) + "..." : output : JSON.stringify(output).substring(0, 200) + "...";
1385
+ console.log(`[Tool Result] ${toolName} returned:`, preview);
1386
+ return { tool_call_id: toolCall.id, output };
1387
+ } catch (error) {
1388
+ console.error(`[Tool Error] ${toolName} failed:`, error.message);
1389
+ return { tool_call_id: toolCall.id, output: JSON.stringify({ error: `Error executing tool '${toolName}': ${error.message}` }) };
1390
+ }
1391
+ })
1392
+ );
1393
+ toolResults.forEach(
1394
+ (result) => messages.push({ role: "tool", tool_call_id: result.tool_call_id, content: result.output })
1395
+ );
1396
+ }
1397
+ _getModelForTier(tier) {
1398
+ return this.models[tier] || this.defaultModel;
1282
1399
  }
1283
1400
  _shouldAutoParse(options) {
1284
1401
  if (!options.responseFormat) return false;
@@ -1299,15 +1416,10 @@ ${jsonPrompt}`;
1299
1416
  }
1300
1417
  normalizeFinishReason(providerReason) {
1301
1418
  const lower = (providerReason || "").toLowerCase();
1302
- if (lower === "end_turn") {
1303
- return "completed";
1304
- }
1305
- if (lower === "max_tokens") {
1306
- return "truncated";
1307
- }
1308
- if (lower === "stop_sequence") {
1309
- return "completed";
1310
- }
1419
+ if (lower === "end_turn") return "completed";
1420
+ if (lower === "max_tokens") return "truncated";
1421
+ if (lower === "stop_sequence") return "completed";
1422
+ if (lower === "tool_use") return "tool_call";
1311
1423
  return super.normalizeFinishReason(providerReason);
1312
1424
  }
1313
1425
  };