@oai2lmapi/opencode-provider 0.3.1 → 0.3.2-prerelease.20260122071726.e4f214e

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.js CHANGED
@@ -85,6 +85,25 @@ function resolveApiKey(config) {
85
85
  }
86
86
  return process.env["OAI2LM_API_KEY"];
87
87
  }
88
+ function findModelOverride(modelId, overrides) {
89
+ if (!overrides) {
90
+ return void 0;
91
+ }
92
+ if (overrides[modelId]) {
93
+ return overrides[modelId];
94
+ }
95
+ for (const [pattern, override] of Object.entries(overrides)) {
96
+ if (pattern.includes("*") || pattern.includes("?")) {
97
+ const regex = new RegExp(
98
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
99
+ );
100
+ if (regex.test(modelId)) {
101
+ return override;
102
+ }
103
+ }
104
+ }
105
+ return void 0;
106
+ }
88
107
 
89
108
  // src/discover.ts
90
109
  async function discoverModels(baseURL, apiKey, headers) {
@@ -941,104 +960,183 @@ ${result}
941
960
  }
942
961
 
943
962
  // src/plugin.ts
944
- function generateModelsConfig(models, providerName = "custom-provider") {
963
+ function generateModelsConfig(models, providerName = "custom-provider", config, modelOverrides) {
945
964
  const modelsConfig = {};
946
965
  for (const model of models) {
947
- const metadata = getModelMetadataFromPatterns2(model.id);
948
- const merged = mergeMetadata(model.metadata, metadata);
949
- modelsConfig[model.id] = {
966
+ const patternMetadata = getModelMetadataFromPatterns2(model.id);
967
+ const metadata = mergeMetadata(model.metadata, patternMetadata);
968
+ const override = findModelOverride(model.id, modelOverrides);
969
+ if (override) {
970
+ if (override.maxInputTokens !== void 0) {
971
+ metadata.maxInputTokens = override.maxInputTokens;
972
+ }
973
+ if (override.maxOutputTokens !== void 0) {
974
+ metadata.maxOutputTokens = override.maxOutputTokens;
975
+ }
976
+ if (override.supportsToolCalling !== void 0) {
977
+ metadata.supportsToolCalling = override.supportsToolCalling;
978
+ }
979
+ if (override.supportsImageInput !== void 0) {
980
+ metadata.supportsImageInput = override.supportsImageInput;
981
+ }
982
+ }
983
+ const modelConfig = {
950
984
  name: model.name || model.id,
951
- tool_call: merged.supportsToolCalling ?? true,
952
- attachment: merged.supportsImageInput ?? false,
985
+ tool_call: metadata.supportsToolCalling ?? true,
986
+ attachment: metadata.supportsImageInput ?? false,
953
987
  limit: {
954
- context: merged.maxInputTokens || 128e3,
955
- output: merged.maxOutputTokens || 16384
988
+ context: metadata.maxInputTokens || 128e3,
989
+ output: metadata.maxOutputTokens || 16384
956
990
  }
957
991
  };
992
+ if (override) {
993
+ const options = {};
994
+ if (override.usePromptBasedToolCalling !== void 0) {
995
+ options.usePromptBasedToolCalling = override.usePromptBasedToolCalling;
996
+ }
997
+ if (override.trimXmlToolParameterWhitespace !== void 0) {
998
+ options.trimXmlToolParameterWhitespace = override.trimXmlToolParameterWhitespace;
999
+ }
1000
+ if (override.thinkingLevel !== void 0) {
1001
+ options.thinkingLevel = override.thinkingLevel;
1002
+ }
1003
+ if (override.temperature !== void 0) {
1004
+ options.temperature = override.temperature;
1005
+ }
1006
+ if (override.suppressChainOfThought !== void 0) {
1007
+ options.suppressChainOfThought = override.suppressChainOfThought;
1008
+ }
1009
+ if (Object.keys(options).length > 0) {
1010
+ modelConfig.options = options;
1011
+ }
1012
+ }
1013
+ modelsConfig[model.id] = modelConfig;
1014
+ }
1015
+ const providerOptions = {};
1016
+ if (config?.baseURL) {
1017
+ providerOptions.baseURL = config.baseURL;
1018
+ } else {
1019
+ providerOptions.baseURL = "YOUR_API_BASE_URL";
958
1020
  }
959
- const config = {
1021
+ if (config?.apiKey) {
1022
+ if (config.apiKey.startsWith("{env:")) {
1023
+ providerOptions.apiKey = config.apiKey;
1024
+ } else {
1025
+ providerOptions.apiKey = "{env:YOUR_API_KEY_ENV}";
1026
+ }
1027
+ } else {
1028
+ providerOptions.apiKey = "{env:YOUR_API_KEY_ENV}";
1029
+ }
1030
+ if (config?.headers && Object.keys(config.headers).length > 0) {
1031
+ providerOptions.headers = config.headers;
1032
+ }
1033
+ const outputConfig = {
960
1034
  provider: {
961
1035
  [providerName]: {
962
- name: providerName,
1036
+ name: config?.displayName || providerName,
963
1037
  npm: "@oai2lmapi/opencode-provider",
964
- options: {
965
- baseURL: "YOUR_API_BASE_URL",
966
- apiKey: "{env:YOUR_API_KEY_ENV}"
967
- },
1038
+ options: providerOptions,
968
1039
  models: modelsConfig
969
1040
  }
970
1041
  }
971
1042
  };
972
- return JSON.stringify(config, null, 2);
1043
+ return JSON.stringify(outputConfig, null, 2);
973
1044
  }
974
- function formatModelsForDisplay(models, providerName) {
1045
+ function formatModelsForDisplay(models, providerName, config) {
975
1046
  const lines = [
976
1047
  `# Discovered ${models.length} models`,
977
- "",
978
- "## Quick Setup",
979
- "",
980
- "Add the following to your opencode.json:",
981
- "",
982
- "```json",
983
- generateModelsConfig(models, providerName),
984
- "```",
985
- "",
986
- "## Model Details",
987
1048
  ""
988
1049
  ];
1050
+ if (config?.baseURL) {
1051
+ lines.push(`**API Endpoint**: ${config.baseURL}`);
1052
+ lines.push("");
1053
+ }
1054
+ lines.push("## Quick Setup");
1055
+ lines.push("");
1056
+ lines.push("Add the following to your opencode.json:");
1057
+ lines.push("");
1058
+ lines.push("```json");
1059
+ lines.push(
1060
+ generateModelsConfig(models, providerName, config, config?.modelOverrides)
1061
+ );
1062
+ lines.push("```");
1063
+ lines.push("");
1064
+ lines.push("## Model Details");
1065
+ lines.push("");
989
1066
  for (const model of models) {
990
- const metadata = getModelMetadataFromPatterns2(model.id);
991
- const merged = mergeMetadata(model.metadata, metadata);
1067
+ const patternMetadata = getModelMetadataFromPatterns2(model.id);
1068
+ const metadata = mergeMetadata(model.metadata, patternMetadata);
1069
+ const override = findModelOverride(model.id, config?.modelOverrides);
1070
+ if (override) {
1071
+ if (override.maxInputTokens !== void 0) {
1072
+ metadata.maxInputTokens = override.maxInputTokens;
1073
+ }
1074
+ if (override.maxOutputTokens !== void 0) {
1075
+ metadata.maxOutputTokens = override.maxOutputTokens;
1076
+ }
1077
+ if (override.supportsToolCalling !== void 0) {
1078
+ metadata.supportsToolCalling = override.supportsToolCalling;
1079
+ }
1080
+ if (override.supportsImageInput !== void 0) {
1081
+ metadata.supportsImageInput = override.supportsImageInput;
1082
+ }
1083
+ }
992
1084
  lines.push(`### ${model.id}`);
993
1085
  if (model.name && model.name !== model.id) {
994
1086
  lines.push(`- Name: ${model.name}`);
995
1087
  }
996
- lines.push(`- Context: ${merged.maxInputTokens || "unknown"} tokens`);
997
- lines.push(`- Output: ${merged.maxOutputTokens || "unknown"} tokens`);
998
- lines.push(`- Tool Calling: ${merged.supportsToolCalling ? "Yes" : "No"}`);
999
- lines.push(`- Vision: ${merged.supportsImageInput ? "Yes" : "No"}`);
1088
+ lines.push(`- Context: ${metadata.maxInputTokens || "unknown"} tokens`);
1089
+ lines.push(`- Output: ${metadata.maxOutputTokens || "unknown"} tokens`);
1090
+ lines.push(`- Tool Calling: ${metadata.supportsToolCalling ? "Yes" : "No"}`);
1091
+ lines.push(`- Vision: ${metadata.supportsImageInput ? "Yes" : "No"}`);
1092
+ if (override?.usePromptBasedToolCalling) {
1093
+ lines.push(`- Uses Prompt-Based Tool Calling: Yes`);
1094
+ }
1000
1095
  lines.push("");
1001
1096
  }
1002
1097
  return lines.join("\n");
1003
1098
  }
1004
1099
  async function oai2lmPlugin(_input) {
1100
+ const savedConfig = loadConfig();
1005
1101
  return {
1006
1102
  tool: {
1007
1103
  oai2lm_discover: {
1008
1104
  description: `Discover available models from an OpenAI-compatible API and generate opencode.json configuration.
1009
1105
 
1010
1106
  This tool will:
1011
- 1. Connect to the specified API endpoint
1107
+ 1. Connect to the specified API endpoint (or use oai2lm.json config)
1012
1108
  2. Fetch all available models
1013
1109
  3. Enrich them with metadata (token limits, capabilities)
1014
- 4. Generate ready-to-use opencode.json configuration
1110
+ 4. Apply any model overrides from oai2lm.json
1111
+ 5. Generate ready-to-use opencode.json configuration
1015
1112
 
1016
- Use this when you want to add a new OpenAI-compatible provider to OpenCode.`,
1113
+ Configuration from oai2lm.json (~/.local/share/opencode/oai2lm.json) is automatically loaded.
1114
+ You only need to provide arguments if you want to override the config file settings.`,
1017
1115
  parameters: {
1018
1116
  type: "object",
1019
1117
  properties: {
1020
1118
  baseURL: {
1021
1119
  type: "string",
1022
- description: "Base URL of the OpenAI-compatible API (e.g., https://api.example.com/v1). If not provided, will try to load from oai2lm.json config."
1120
+ description: "Base URL of the OpenAI-compatible API (e.g., https://api.example.com/v1). If not provided, uses oai2lm.json config."
1023
1121
  },
1024
1122
  apiKey: {
1025
1123
  type: "string",
1026
- description: "API key for authentication. If not provided, will try to load from config or environment."
1124
+ description: "API key for authentication. If not provided, uses oai2lm.json config or environment."
1027
1125
  },
1028
1126
  providerName: {
1029
1127
  type: "string",
1030
- description: "Name for the provider in opencode.json (e.g., 'my-api'). Defaults to 'custom-provider'."
1128
+ description: "Name for the provider in opencode.json (e.g., 'my-api'). Defaults to config name or 'custom-provider'."
1031
1129
  },
1032
1130
  filter: {
1033
1131
  type: "string",
1034
- description: "Optional regex pattern to filter models (e.g., 'gpt|claude')"
1132
+ description: "Optional regex pattern to filter models (e.g., 'gpt|claude')."
1035
1133
  }
1036
1134
  },
1037
1135
  required: []
1038
1136
  },
1039
1137
  execute: async (args) => {
1040
1138
  try {
1041
- const config = loadConfig();
1139
+ const config = loadConfig() || savedConfig;
1042
1140
  const baseURL = args.baseURL || config?.baseURL || "";
1043
1141
  const apiKey = args.apiKey || (config ? resolveApiKey(config) : "") || "";
1044
1142
  const providerName = args.providerName || config?.name || "custom-provider";
@@ -1046,12 +1144,18 @@ Use this when you want to add a new OpenAI-compatible provider to OpenCode.`,
1046
1144
  if (!baseURL) {
1047
1145
  return `Error: No baseURL provided. Either:
1048
1146
  1. Pass it as an argument: oai2lm_discover(baseURL: "https://api.example.com/v1")
1049
- 2. Or create an oai2lm.json config file with baseURL
1147
+ 2. Or create an oai2lm.json config file at ~/.local/share/opencode/oai2lm.json
1050
1148
 
1051
1149
  Example oai2lm.json:
1052
1150
  {
1053
1151
  "baseURL": "https://api.example.com/v1",
1054
- "apiKey": "your-api-key"
1152
+ "apiKey": "{env:MY_API_KEY}",
1153
+ "name": "my-api",
1154
+ "modelOverrides": {
1155
+ "gpt-4*": {
1156
+ "supportsImageInput": true
1157
+ }
1158
+ }
1055
1159
  }`;
1056
1160
  }
1057
1161
  let models = await discoverModels(baseURL, apiKey, config?.headers);
@@ -1062,7 +1166,7 @@ Example oai2lm.json:
1062
1166
  if (models.length === 0) {
1063
1167
  return `No models found at ${baseURL}/models. Check your API key and endpoint.`;
1064
1168
  }
1065
- return formatModelsForDisplay(models, providerName);
1169
+ return formatModelsForDisplay(models, providerName, config);
1066
1170
  } catch (error) {
1067
1171
  const message = error instanceof Error ? error.message : String(error);
1068
1172
  return `Error discovering models: ${message}`;
@@ -1073,48 +1177,416 @@ Example oai2lm.json:
1073
1177
  };
1074
1178
  }
1075
1179
 
1076
- // src/index.ts
1077
- var modelCache = /* @__PURE__ */ new WeakMap();
1078
- function findModelOverride(modelId, overrides) {
1079
- if (!overrides) {
1080
- return void 0;
1180
+ // src/enhancedModel.ts
1181
+ var idCounter = 0;
1182
+ function generateId() {
1183
+ return `id_${Date.now()}_${++idCounter}`;
1184
+ }
1185
+ var EnhancedLanguageModel = class {
1186
+ specificationVersion = "v2";
1187
+ modelId;
1188
+ baseModel;
1189
+ override;
1190
+ constructor(baseModel, modelId, override) {
1191
+ this.baseModel = baseModel;
1192
+ this.modelId = modelId;
1193
+ this.override = override;
1081
1194
  }
1082
- if (overrides[modelId]) {
1083
- return overrides[modelId];
1195
+ get provider() {
1196
+ return this.baseModel.provider;
1084
1197
  }
1085
- for (const [pattern, override] of Object.entries(overrides)) {
1086
- if (pattern.includes("*") || pattern.includes("?")) {
1087
- const regex = new RegExp(
1088
- "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
1198
+ get supportedUrls() {
1199
+ return this.baseModel.supportedUrls;
1200
+ }
1201
+ /**
1202
+ * Generate text (non-streaming)
1203
+ */
1204
+ async doGenerate(options) {
1205
+ let modifiedOptions = { ...options };
1206
+ modifiedOptions = this.applyModelOverrides(modifiedOptions);
1207
+ const toolNames = [];
1208
+ if (this.override?.usePromptBasedToolCalling && options.tools && options.tools.length > 0) {
1209
+ const result2 = this.applyPromptBasedToolCalling(modifiedOptions);
1210
+ modifiedOptions = result2.options;
1211
+ toolNames.push(...result2.toolNames);
1212
+ }
1213
+ let result = await this.baseModel.doGenerate(modifiedOptions);
1214
+ if (this.override?.suppressChainOfThought) {
1215
+ result = this.suppressReasoningContent(result);
1216
+ }
1217
+ if (this.override?.usePromptBasedToolCalling && toolNames.length > 0) {
1218
+ return this.processResultForToolCalls(result, toolNames);
1219
+ }
1220
+ return result;
1221
+ }
1222
+ /**
1223
+ * Stream text
1224
+ */
1225
+ async doStream(options) {
1226
+ let modifiedOptions = { ...options };
1227
+ modifiedOptions = this.applyModelOverrides(modifiedOptions);
1228
+ const toolNames = [];
1229
+ if (this.override?.usePromptBasedToolCalling && options.tools && options.tools.length > 0) {
1230
+ const result2 = this.applyPromptBasedToolCalling(modifiedOptions);
1231
+ modifiedOptions = result2.options;
1232
+ toolNames.push(...result2.toolNames);
1233
+ }
1234
+ let result = await this.baseModel.doStream(modifiedOptions);
1235
+ if (this.override?.suppressChainOfThought) {
1236
+ result = this.suppressReasoningInStream(result);
1237
+ }
1238
+ if (this.override?.usePromptBasedToolCalling && toolNames.length > 0) {
1239
+ return this.processStreamForToolCalls(result, toolNames);
1240
+ }
1241
+ return result;
1242
+ }
1243
+ /**
1244
+ * Apply model-level overrides to call options.
1245
+ * Handles temperature and thinkingLevel configuration.
1246
+ */
1247
+ applyModelOverrides(options) {
1248
+ let modifiedOptions = { ...options };
1249
+ if (this.override?.temperature !== void 0 && modifiedOptions.temperature === void 0) {
1250
+ modifiedOptions.temperature = this.override.temperature;
1251
+ }
1252
+ if (this.override?.thinkingLevel !== void 0) {
1253
+ modifiedOptions = this.applyThinkingLevel(
1254
+ modifiedOptions,
1255
+ this.override.thinkingLevel
1089
1256
  );
1090
- {
1257
+ }
1258
+ return modifiedOptions;
1259
+ }
1260
+ /**
1261
+ * Apply thinking level configuration via provider options.
1262
+ * Supports various providers that use different parameter names.
1263
+ */
1264
+ applyThinkingLevel(options, thinkingLevel) {
1265
+ const budget = this.thinkingLevelToBudget(thinkingLevel);
1266
+ if (budget === 0) {
1267
+ return options;
1268
+ }
1269
+ const providerOptions = {
1270
+ ...options.providerOptions,
1271
+ // OpenAI-compatible (o1, o3 models)
1272
+ openai: {
1273
+ ...options.providerOptions?.openai,
1274
+ reasoning_effort: thinkingLevel === "auto" ? "medium" : thinkingLevel === "high" ? "high" : thinkingLevel === "low" ? "low" : "medium"
1275
+ },
1276
+ // Anthropic (Claude 3.5+ with extended thinking)
1277
+ anthropic: {
1278
+ ...options.providerOptions?.anthropic,
1279
+ thinking: {
1280
+ type: "enabled",
1281
+ budget_tokens: budget
1282
+ }
1283
+ },
1284
+ // DeepSeek (reasoning models)
1285
+ deepseek: {
1286
+ ...options.providerOptions?.deepseek,
1287
+ reasoning_effort: thinkingLevel === "auto" ? "medium" : thinkingLevel === "high" ? "high" : thinkingLevel === "low" ? "low" : "medium"
1091
1288
  }
1092
- if (regex.test(modelId)) {
1093
- return override;
1289
+ };
1290
+ return {
1291
+ ...options,
1292
+ providerOptions
1293
+ };
1294
+ }
1295
+ /**
1296
+ * Convert thinking level to token budget.
1297
+ */
1298
+ thinkingLevelToBudget(level) {
1299
+ if (typeof level === "number") {
1300
+ return level;
1301
+ }
1302
+ switch (level) {
1303
+ case "none":
1304
+ return 0;
1305
+ case "low":
1306
+ return 4096;
1307
+ case "medium":
1308
+ return 16384;
1309
+ case "high":
1310
+ return 65536;
1311
+ case "auto":
1312
+ return 16384;
1313
+ // Default to medium
1314
+ default:
1315
+ return 0;
1316
+ }
1317
+ }
1318
+ /**
1319
+ * Suppress reasoning content from non-streaming result.
1320
+ */
1321
+ suppressReasoningContent(result) {
1322
+ const content = result.content;
1323
+ const filteredContent = content.filter((c) => c.type !== "reasoning");
1324
+ return {
1325
+ ...result,
1326
+ content: filteredContent
1327
+ };
1328
+ }
1329
+ /**
1330
+ * Suppress reasoning content from streaming result.
1331
+ */
1332
+ suppressReasoningInStream(result) {
1333
+ const originalStream = result.stream;
1334
+ const transformedStream = new ReadableStream({
1335
+ async start(controller) {
1336
+ const reader = originalStream.getReader();
1337
+ try {
1338
+ while (true) {
1339
+ const { done, value } = await reader.read();
1340
+ if (done) {
1341
+ break;
1342
+ }
1343
+ const partType = value.type;
1344
+ if (partType === "reasoning-start" || partType === "reasoning-delta" || partType === "reasoning-end") {
1345
+ continue;
1346
+ }
1347
+ controller.enqueue(value);
1348
+ }
1349
+ controller.close();
1350
+ } catch (error) {
1351
+ controller.error(error);
1352
+ }
1353
+ }
1354
+ });
1355
+ return {
1356
+ ...result,
1357
+ stream: transformedStream
1358
+ };
1359
+ }
1360
+ /**
1361
+ * Apply prompt-based tool calling by converting tools to system prompt
1362
+ */
1363
+ applyPromptBasedToolCalling(options) {
1364
+ if (!options.tools || options.tools.length === 0) {
1365
+ return { options, toolNames: [] };
1366
+ }
1367
+ const functionTools = options.tools.filter(
1368
+ (tool) => tool.type === "function"
1369
+ );
1370
+ if (functionTools.length === 0) {
1371
+ return { options, toolNames: [] };
1372
+ }
1373
+ const toolDefinitions = functionTools.map((tool) => ({
1374
+ type: "function",
1375
+ name: tool.name,
1376
+ description: tool.description,
1377
+ // V2 uses inputSchema instead of parameters
1378
+ parameters: tool.inputSchema
1379
+ }));
1380
+ const toolNames = toolDefinitions.map((t) => t.name);
1381
+ const toolPrompt = generateXmlToolPrompt(toolDefinitions);
1382
+ const modifiedPrompt = options.prompt.map((msg) => {
1383
+ if (msg.role === "system") {
1384
+ const content = msg.content;
1385
+ return {
1386
+ ...msg,
1387
+ content: content + "\n\n" + toolPrompt
1388
+ };
1094
1389
  }
1390
+ return msg;
1391
+ });
1392
+ const hasSystemMessage = modifiedPrompt.some((m) => m.role === "system");
1393
+ if (!hasSystemMessage) {
1394
+ modifiedPrompt.unshift({
1395
+ role: "system",
1396
+ content: toolPrompt
1397
+ });
1095
1398
  }
1399
+ return {
1400
+ options: {
1401
+ ...options,
1402
+ prompt: modifiedPrompt,
1403
+ tools: void 0,
1404
+ toolChoice: void 0
1405
+ },
1406
+ toolNames
1407
+ };
1096
1408
  }
1097
- return void 0;
1409
+ /**
1410
+ * Process non-streaming result for XML tool calls
1411
+ */
1412
+ processResultForToolCalls(result, toolNames) {
1413
+ const content = result.content;
1414
+ const textContentIndex = content.findIndex((c) => c.type === "text");
1415
+ if (textContentIndex === -1 || !content[textContentIndex].text) {
1416
+ return result;
1417
+ }
1418
+ const text = content[textContentIndex].text;
1419
+ const xmlToolCalls = parseXmlToolCalls(text, toolNames, {
1420
+ trimParameterWhitespace: this.override?.trimXmlToolParameterWhitespace ?? false
1421
+ });
1422
+ if (xmlToolCalls.length === 0) {
1423
+ return result;
1424
+ }
1425
+ const cleanedText = this.removeXmlToolCallsFromText(text, toolNames);
1426
+ const newContent = [];
1427
+ if (cleanedText.trim()) {
1428
+ newContent.push({
1429
+ type: "text",
1430
+ text: cleanedText
1431
+ });
1432
+ }
1433
+ for (const tc of xmlToolCalls) {
1434
+ newContent.push({
1435
+ type: "tool-call",
1436
+ toolCallId: tc.id,
1437
+ toolName: tc.name,
1438
+ input: JSON.stringify(tc.arguments)
1439
+ });
1440
+ }
1441
+ for (const c of content) {
1442
+ if (c.type !== "text") {
1443
+ newContent.push(c);
1444
+ }
1445
+ }
1446
+ return {
1447
+ ...result,
1448
+ content: newContent,
1449
+ finishReason: "tool-calls"
1450
+ };
1451
+ }
1452
+ /**
1453
+ * Process streaming result for XML tool calls
1454
+ *
1455
+ * This wraps the stream to accumulate text and parse tool calls at the end.
1456
+ */
1457
+ processStreamForToolCalls(result, toolNames) {
1458
+ const originalStream = result.stream;
1459
+ const override = this.override;
1460
+ const self = this;
1461
+ const transformedStream = new ReadableStream({
1462
+ async start(controller) {
1463
+ let accumulatedText = "";
1464
+ const bufferedParts = [];
1465
+ try {
1466
+ const reader = originalStream.getReader();
1467
+ while (true) {
1468
+ const { done, value } = await reader.read();
1469
+ if (done) {
1470
+ break;
1471
+ }
1472
+ const part = value;
1473
+ if (part.type === "text-delta") {
1474
+ accumulatedText += part.delta;
1475
+ }
1476
+ bufferedParts.push(part);
1477
+ }
1478
+ const xmlToolCalls = parseXmlToolCalls(accumulatedText, toolNames, {
1479
+ trimParameterWhitespace: override?.trimXmlToolParameterWhitespace ?? false
1480
+ });
1481
+ if (xmlToolCalls.length > 0) {
1482
+ const cleanedText = self.removeXmlToolCallsFromText(
1483
+ accumulatedText,
1484
+ toolNames
1485
+ );
1486
+ if (cleanedText.trim()) {
1487
+ controller.enqueue({
1488
+ type: "text-delta",
1489
+ id: generateId(),
1490
+ delta: cleanedText
1491
+ });
1492
+ }
1493
+ for (const tc of xmlToolCalls) {
1494
+ controller.enqueue({
1495
+ type: "tool-call",
1496
+ toolCallId: tc.id,
1497
+ toolName: tc.name,
1498
+ input: JSON.stringify(tc.arguments)
1499
+ });
1500
+ }
1501
+ controller.enqueue({
1502
+ type: "finish",
1503
+ finishReason: "tool-calls",
1504
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
1505
+ });
1506
+ } else {
1507
+ for (const part of bufferedParts) {
1508
+ controller.enqueue(part);
1509
+ }
1510
+ }
1511
+ controller.close();
1512
+ } catch (error) {
1513
+ controller.error(error);
1514
+ }
1515
+ }
1516
+ });
1517
+ return {
1518
+ ...result,
1519
+ stream: transformedStream
1520
+ };
1521
+ }
1522
+ /**
1523
+ * Remove XML tool call blocks from text
1524
+ */
1525
+ removeXmlToolCallsFromText(text, toolNames) {
1526
+ let result = text;
1527
+ for (const toolName of toolNames) {
1528
+ const regex = new RegExp(
1529
+ `<${this.escapeRegex(toolName)}>[\\s\\S]*?<\\/${this.escapeRegex(toolName)}>`,
1530
+ "g"
1531
+ );
1532
+ result = result.replace(regex, "");
1533
+ }
1534
+ return result.trim();
1535
+ }
1536
+ /**
1537
+ * Escape special regex characters
1538
+ */
1539
+ escapeRegex(str) {
1540
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1541
+ }
1542
+ };
1543
+ function createEnhancedModel(baseModel, modelId, override) {
1544
+ if (override?.usePromptBasedToolCalling || override?.temperature !== void 0 || override?.thinkingLevel !== void 0 || override?.suppressChainOfThought !== void 0) {
1545
+ return new EnhancedLanguageModel(baseModel, modelId, override);
1546
+ }
1547
+ return baseModel;
1098
1548
  }
1549
+
1550
+ // src/index.ts
1551
+ var modelCache = /* @__PURE__ */ new WeakMap();
1099
1552
  function mergeWithConfig(options, config) {
1100
- if (!config) {
1101
- return options;
1553
+ const baseModelOverrides = config?.modelOverrides ?? {};
1554
+ const optionModelOverrides = options.modelOverrides ?? {};
1555
+ const modelLevelOverrides = {};
1556
+ if (options.models) {
1557
+ for (const [modelId, modelConfig] of Object.entries(options.models)) {
1558
+ if (modelConfig.options) {
1559
+ modelLevelOverrides[modelId] = modelConfig.options;
1560
+ }
1561
+ }
1562
+ }
1563
+ const mergedModelOverrides = {
1564
+ ...baseModelOverrides,
1565
+ ...optionModelOverrides
1566
+ };
1567
+ for (const [modelId, override] of Object.entries(modelLevelOverrides)) {
1568
+ mergedModelOverrides[modelId] = {
1569
+ ...mergedModelOverrides[modelId],
1570
+ ...override
1571
+ };
1572
+ }
1573
+ let apiKey = options.apiKey;
1574
+ if (!apiKey && config) {
1575
+ apiKey = resolveApiKey(config);
1102
1576
  }
1103
1577
  return {
1104
1578
  // Options take precedence over config
1105
- baseURL: options.baseURL || config.baseURL || "",
1106
- apiKey: options.apiKey || resolveApiKey(config),
1107
- name: options.name || config.name,
1579
+ baseURL: options.baseURL || config?.baseURL || "",
1580
+ apiKey,
1581
+ name: options.name || config?.name,
1108
1582
  headers: {
1109
- ...config.headers,
1583
+ ...config?.headers,
1110
1584
  ...options.headers
1111
1585
  },
1112
- modelFilter: options.modelFilter || config.modelFilter,
1113
- modelOverrides: {
1114
- ...config.modelOverrides,
1115
- ...options.modelOverrides
1116
- },
1117
- useConfigFile: options.useConfigFile
1586
+ modelFilter: options.modelFilter || config?.modelFilter,
1587
+ modelOverrides: mergedModelOverrides,
1588
+ useConfigFile: options.useConfigFile,
1589
+ models: options.models
1118
1590
  };
1119
1591
  }
1120
1592
  function createOai2lm(options) {
@@ -1125,8 +1597,9 @@ function createOai2lm(options) {
1125
1597
  "baseURL is required. Provide it in options or in oai2lm.json config file."
1126
1598
  );
1127
1599
  }
1600
+ const baseURL = mergedOptions.baseURL;
1128
1601
  const baseProvider = createOpenAICompatible({
1129
- baseURL: mergedOptions.baseURL.replace(/\/+$/, ""),
1602
+ baseURL: baseURL.replace(/\/+$/, ""),
1130
1603
  name: mergedOptions.name || "oai2lm",
1131
1604
  apiKey: mergedOptions.apiKey,
1132
1605
  headers: mergedOptions.headers
@@ -1135,7 +1608,7 @@ function createOai2lm(options) {
1135
1608
  async function discoverAndCache() {
1136
1609
  const apiKey = mergedOptions.apiKey || "";
1137
1610
  const models = await discoverModels(
1138
- mergedOptions.baseURL,
1611
+ baseURL,
1139
1612
  apiKey,
1140
1613
  mergedOptions.headers
1141
1614
  );
@@ -1182,11 +1655,24 @@ function createOai2lm(options) {
1182
1655
  }
1183
1656
  return cache;
1184
1657
  }
1658
+ function getOverride(modelId) {
1659
+ return findModelOverride(modelId, mergedOptions.modelOverrides);
1660
+ }
1185
1661
  const provider = function(modelId) {
1186
- return baseProvider(modelId);
1662
+ const baseModel = baseProvider(modelId);
1663
+ const override = getOverride(modelId);
1664
+ return createEnhancedModel(baseModel, modelId, override);
1665
+ };
1666
+ provider.languageModel = (modelId) => {
1667
+ const baseModel = baseProvider.languageModel(modelId);
1668
+ const override = getOverride(modelId);
1669
+ return createEnhancedModel(baseModel, modelId, override);
1670
+ };
1671
+ provider.chatModel = (modelId) => {
1672
+ const baseModel = baseProvider.chatModel(modelId);
1673
+ const override = getOverride(modelId);
1674
+ return createEnhancedModel(baseModel, modelId, override);
1187
1675
  };
1188
- provider.languageModel = (modelId) => baseProvider.languageModel(modelId);
1189
- provider.chatModel = (modelId) => baseProvider.chatModel(modelId);
1190
1676
  provider.completionModel = (modelId) => baseProvider.completionModel(modelId);
1191
1677
  provider.textEmbeddingModel = (modelId) => baseProvider.textEmbeddingModel(modelId);
1192
1678
  provider.imageModel = (modelId) => baseProvider.imageModel(modelId);
@@ -1205,6 +1691,8 @@ function createOai2lm(options) {
1205
1691
  }
1206
1692
  var index_default = createOai2lm;
1207
1693
  export {
1694
+ EnhancedLanguageModel,
1695
+ createEnhancedModel,
1208
1696
  createOai2lm,
1209
1697
  index_default as default,
1210
1698
  discoverModels,