@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/README.md +8 -0
- package/dist/cli.js +86 -15
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +15 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/enhancedModel.d.ts +103 -0
- package/dist/enhancedModel.d.ts.map +1 -0
- package/dist/index.d.ts +23 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +563 -75
- package/dist/index.js.map +3 -3
- package/dist/plugin.d.ts +15 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +147 -43
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -1
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
|
|
948
|
-
const
|
|
949
|
-
|
|
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:
|
|
952
|
-
attachment:
|
|
985
|
+
tool_call: metadata.supportsToolCalling ?? true,
|
|
986
|
+
attachment: metadata.supportsImageInput ?? false,
|
|
953
987
|
limit: {
|
|
954
|
-
context:
|
|
955
|
-
output:
|
|
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
|
-
|
|
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(
|
|
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
|
|
991
|
-
const
|
|
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: ${
|
|
997
|
-
lines.push(`- Output: ${
|
|
998
|
-
lines.push(`- Tool Calling: ${
|
|
999
|
-
lines.push(`- Vision: ${
|
|
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.
|
|
1110
|
+
4. Apply any model overrides from oai2lm.json
|
|
1111
|
+
5. Generate ready-to-use opencode.json configuration
|
|
1015
1112
|
|
|
1016
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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": "
|
|
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/
|
|
1077
|
-
var
|
|
1078
|
-
function
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
1083
|
-
return
|
|
1195
|
+
get provider() {
|
|
1196
|
+
return this.baseModel.provider;
|
|
1084
1197
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1101
|
-
|
|
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
|
|
1106
|
-
apiKey
|
|
1107
|
-
name: options.name || config
|
|
1579
|
+
baseURL: options.baseURL || config?.baseURL || "",
|
|
1580
|
+
apiKey,
|
|
1581
|
+
name: options.name || config?.name,
|
|
1108
1582
|
headers: {
|
|
1109
|
-
...config
|
|
1583
|
+
...config?.headers,
|
|
1110
1584
|
...options.headers
|
|
1111
1585
|
},
|
|
1112
|
-
modelFilter: options.modelFilter || config
|
|
1113
|
-
modelOverrides:
|
|
1114
|
-
|
|
1115
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|