@oai2lmapi/opencode-provider 0.3.1 → 0.3.2-prerelease.20260122071058.f95b2ba
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 +83 -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 +81 -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 +436 -76
- 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 +144 -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,180 @@ ${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 (Object.keys(options).length > 0) {
|
|
1007
|
+
modelConfig.options = options;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
modelsConfig[model.id] = modelConfig;
|
|
1011
|
+
}
|
|
1012
|
+
const providerOptions = {};
|
|
1013
|
+
if (config?.baseURL) {
|
|
1014
|
+
providerOptions.baseURL = config.baseURL;
|
|
1015
|
+
} else {
|
|
1016
|
+
providerOptions.baseURL = "YOUR_API_BASE_URL";
|
|
958
1017
|
}
|
|
959
|
-
|
|
1018
|
+
if (config?.apiKey) {
|
|
1019
|
+
if (config.apiKey.startsWith("{env:")) {
|
|
1020
|
+
providerOptions.apiKey = config.apiKey;
|
|
1021
|
+
} else {
|
|
1022
|
+
providerOptions.apiKey = "{env:YOUR_API_KEY_ENV}";
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
providerOptions.apiKey = "{env:YOUR_API_KEY_ENV}";
|
|
1026
|
+
}
|
|
1027
|
+
if (config?.headers && Object.keys(config.headers).length > 0) {
|
|
1028
|
+
providerOptions.headers = config.headers;
|
|
1029
|
+
}
|
|
1030
|
+
const outputConfig = {
|
|
960
1031
|
provider: {
|
|
961
1032
|
[providerName]: {
|
|
962
|
-
name: providerName,
|
|
1033
|
+
name: config?.displayName || providerName,
|
|
963
1034
|
npm: "@oai2lmapi/opencode-provider",
|
|
964
|
-
options:
|
|
965
|
-
baseURL: "YOUR_API_BASE_URL",
|
|
966
|
-
apiKey: "{env:YOUR_API_KEY_ENV}"
|
|
967
|
-
},
|
|
1035
|
+
options: providerOptions,
|
|
968
1036
|
models: modelsConfig
|
|
969
1037
|
}
|
|
970
1038
|
}
|
|
971
1039
|
};
|
|
972
|
-
return JSON.stringify(
|
|
1040
|
+
return JSON.stringify(outputConfig, null, 2);
|
|
973
1041
|
}
|
|
974
|
-
function formatModelsForDisplay(models, providerName) {
|
|
1042
|
+
function formatModelsForDisplay(models, providerName, config) {
|
|
975
1043
|
const lines = [
|
|
976
1044
|
`# 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
1045
|
""
|
|
988
1046
|
];
|
|
1047
|
+
if (config?.baseURL) {
|
|
1048
|
+
lines.push(`**API Endpoint**: ${config.baseURL}`);
|
|
1049
|
+
lines.push("");
|
|
1050
|
+
}
|
|
1051
|
+
lines.push("## Quick Setup");
|
|
1052
|
+
lines.push("");
|
|
1053
|
+
lines.push("Add the following to your opencode.json:");
|
|
1054
|
+
lines.push("");
|
|
1055
|
+
lines.push("```json");
|
|
1056
|
+
lines.push(
|
|
1057
|
+
generateModelsConfig(models, providerName, config, config?.modelOverrides)
|
|
1058
|
+
);
|
|
1059
|
+
lines.push("```");
|
|
1060
|
+
lines.push("");
|
|
1061
|
+
lines.push("## Model Details");
|
|
1062
|
+
lines.push("");
|
|
989
1063
|
for (const model of models) {
|
|
990
|
-
const
|
|
991
|
-
const
|
|
1064
|
+
const patternMetadata = getModelMetadataFromPatterns2(model.id);
|
|
1065
|
+
const metadata = mergeMetadata(model.metadata, patternMetadata);
|
|
1066
|
+
const override = findModelOverride(model.id, config?.modelOverrides);
|
|
1067
|
+
if (override) {
|
|
1068
|
+
if (override.maxInputTokens !== void 0) {
|
|
1069
|
+
metadata.maxInputTokens = override.maxInputTokens;
|
|
1070
|
+
}
|
|
1071
|
+
if (override.maxOutputTokens !== void 0) {
|
|
1072
|
+
metadata.maxOutputTokens = override.maxOutputTokens;
|
|
1073
|
+
}
|
|
1074
|
+
if (override.supportsToolCalling !== void 0) {
|
|
1075
|
+
metadata.supportsToolCalling = override.supportsToolCalling;
|
|
1076
|
+
}
|
|
1077
|
+
if (override.supportsImageInput !== void 0) {
|
|
1078
|
+
metadata.supportsImageInput = override.supportsImageInput;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
992
1081
|
lines.push(`### ${model.id}`);
|
|
993
1082
|
if (model.name && model.name !== model.id) {
|
|
994
1083
|
lines.push(`- Name: ${model.name}`);
|
|
995
1084
|
}
|
|
996
|
-
lines.push(`- Context: ${
|
|
997
|
-
lines.push(`- Output: ${
|
|
998
|
-
lines.push(`- Tool Calling: ${
|
|
999
|
-
lines.push(`- Vision: ${
|
|
1085
|
+
lines.push(`- Context: ${metadata.maxInputTokens || "unknown"} tokens`);
|
|
1086
|
+
lines.push(`- Output: ${metadata.maxOutputTokens || "unknown"} tokens`);
|
|
1087
|
+
lines.push(`- Tool Calling: ${metadata.supportsToolCalling ? "Yes" : "No"}`);
|
|
1088
|
+
lines.push(`- Vision: ${metadata.supportsImageInput ? "Yes" : "No"}`);
|
|
1089
|
+
if (override?.usePromptBasedToolCalling) {
|
|
1090
|
+
lines.push(`- Uses Prompt-Based Tool Calling: Yes`);
|
|
1091
|
+
}
|
|
1000
1092
|
lines.push("");
|
|
1001
1093
|
}
|
|
1002
1094
|
return lines.join("\n");
|
|
1003
1095
|
}
|
|
1004
1096
|
async function oai2lmPlugin(_input) {
|
|
1097
|
+
const savedConfig = loadConfig();
|
|
1005
1098
|
return {
|
|
1006
1099
|
tool: {
|
|
1007
1100
|
oai2lm_discover: {
|
|
1008
1101
|
description: `Discover available models from an OpenAI-compatible API and generate opencode.json configuration.
|
|
1009
1102
|
|
|
1010
1103
|
This tool will:
|
|
1011
|
-
1. Connect to the specified API endpoint
|
|
1104
|
+
1. Connect to the specified API endpoint (or use oai2lm.json config)
|
|
1012
1105
|
2. Fetch all available models
|
|
1013
1106
|
3. Enrich them with metadata (token limits, capabilities)
|
|
1014
|
-
4.
|
|
1107
|
+
4. Apply any model overrides from oai2lm.json
|
|
1108
|
+
5. Generate ready-to-use opencode.json configuration
|
|
1015
1109
|
|
|
1016
|
-
|
|
1110
|
+
Configuration from oai2lm.json (~/.local/share/opencode/oai2lm.json) is automatically loaded.
|
|
1111
|
+
You only need to provide arguments if you want to override the config file settings.`,
|
|
1017
1112
|
parameters: {
|
|
1018
1113
|
type: "object",
|
|
1019
1114
|
properties: {
|
|
1020
1115
|
baseURL: {
|
|
1021
1116
|
type: "string",
|
|
1022
|
-
description: "Base URL of the OpenAI-compatible API (e.g., https://api.example.com/v1). If not provided,
|
|
1117
|
+
description: "Base URL of the OpenAI-compatible API (e.g., https://api.example.com/v1). If not provided, uses oai2lm.json config."
|
|
1023
1118
|
},
|
|
1024
1119
|
apiKey: {
|
|
1025
1120
|
type: "string",
|
|
1026
|
-
description: "API key for authentication. If not provided,
|
|
1121
|
+
description: "API key for authentication. If not provided, uses oai2lm.json config or environment."
|
|
1027
1122
|
},
|
|
1028
1123
|
providerName: {
|
|
1029
1124
|
type: "string",
|
|
1030
|
-
description: "Name for the provider in opencode.json (e.g., 'my-api'). Defaults to 'custom-provider'."
|
|
1125
|
+
description: "Name for the provider in opencode.json (e.g., 'my-api'). Defaults to config name or 'custom-provider'."
|
|
1031
1126
|
},
|
|
1032
1127
|
filter: {
|
|
1033
1128
|
type: "string",
|
|
1034
|
-
description: "Optional regex pattern to filter models (e.g., 'gpt|claude')"
|
|
1129
|
+
description: "Optional regex pattern to filter models (e.g., 'gpt|claude')."
|
|
1035
1130
|
}
|
|
1036
1131
|
},
|
|
1037
1132
|
required: []
|
|
1038
1133
|
},
|
|
1039
1134
|
execute: async (args) => {
|
|
1040
1135
|
try {
|
|
1041
|
-
const config = loadConfig();
|
|
1136
|
+
const config = loadConfig() || savedConfig;
|
|
1042
1137
|
const baseURL = args.baseURL || config?.baseURL || "";
|
|
1043
1138
|
const apiKey = args.apiKey || (config ? resolveApiKey(config) : "") || "";
|
|
1044
1139
|
const providerName = args.providerName || config?.name || "custom-provider";
|
|
@@ -1046,12 +1141,18 @@ Use this when you want to add a new OpenAI-compatible provider to OpenCode.`,
|
|
|
1046
1141
|
if (!baseURL) {
|
|
1047
1142
|
return `Error: No baseURL provided. Either:
|
|
1048
1143
|
1. Pass it as an argument: oai2lm_discover(baseURL: "https://api.example.com/v1")
|
|
1049
|
-
2. Or create an oai2lm.json config file
|
|
1144
|
+
2. Or create an oai2lm.json config file at ~/.local/share/opencode/oai2lm.json
|
|
1050
1145
|
|
|
1051
1146
|
Example oai2lm.json:
|
|
1052
1147
|
{
|
|
1053
1148
|
"baseURL": "https://api.example.com/v1",
|
|
1054
|
-
"apiKey": "
|
|
1149
|
+
"apiKey": "{env:MY_API_KEY}",
|
|
1150
|
+
"name": "my-api",
|
|
1151
|
+
"modelOverrides": {
|
|
1152
|
+
"gpt-4*": {
|
|
1153
|
+
"supportsImageInput": true
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1055
1156
|
}`;
|
|
1056
1157
|
}
|
|
1057
1158
|
let models = await discoverModels(baseURL, apiKey, config?.headers);
|
|
@@ -1062,7 +1163,7 @@ Example oai2lm.json:
|
|
|
1062
1163
|
if (models.length === 0) {
|
|
1063
1164
|
return `No models found at ${baseURL}/models. Check your API key and endpoint.`;
|
|
1064
1165
|
}
|
|
1065
|
-
return formatModelsForDisplay(models, providerName);
|
|
1166
|
+
return formatModelsForDisplay(models, providerName, config);
|
|
1066
1167
|
} catch (error) {
|
|
1067
1168
|
const message = error instanceof Error ? error.message : String(error);
|
|
1068
1169
|
return `Error discovering models: ${message}`;
|
|
@@ -1073,48 +1174,291 @@ Example oai2lm.json:
|
|
|
1073
1174
|
};
|
|
1074
1175
|
}
|
|
1075
1176
|
|
|
1076
|
-
// src/
|
|
1077
|
-
var
|
|
1078
|
-
function
|
|
1079
|
-
|
|
1080
|
-
|
|
1177
|
+
// src/enhancedModel.ts
|
|
1178
|
+
var idCounter = 0;
|
|
1179
|
+
function generateId() {
|
|
1180
|
+
return `id_${Date.now()}_${++idCounter}`;
|
|
1181
|
+
}
|
|
1182
|
+
var EnhancedLanguageModel = class {
|
|
1183
|
+
specificationVersion = "v2";
|
|
1184
|
+
modelId;
|
|
1185
|
+
baseModel;
|
|
1186
|
+
override;
|
|
1187
|
+
constructor(baseModel, modelId, override) {
|
|
1188
|
+
this.baseModel = baseModel;
|
|
1189
|
+
this.modelId = modelId;
|
|
1190
|
+
this.override = override;
|
|
1081
1191
|
}
|
|
1082
|
-
|
|
1083
|
-
return
|
|
1192
|
+
get provider() {
|
|
1193
|
+
return this.baseModel.provider;
|
|
1084
1194
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1195
|
+
get supportedUrls() {
|
|
1196
|
+
return this.baseModel.supportedUrls;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Generate text (non-streaming)
|
|
1200
|
+
*/
|
|
1201
|
+
async doGenerate(options) {
|
|
1202
|
+
let modifiedOptions = { ...options };
|
|
1203
|
+
const toolNames = [];
|
|
1204
|
+
if (this.override?.usePromptBasedToolCalling && options.tools && options.tools.length > 0) {
|
|
1205
|
+
const result2 = this.applyPromptBasedToolCalling(modifiedOptions);
|
|
1206
|
+
modifiedOptions = result2.options;
|
|
1207
|
+
toolNames.push(...result2.toolNames);
|
|
1208
|
+
}
|
|
1209
|
+
const result = await this.baseModel.doGenerate(modifiedOptions);
|
|
1210
|
+
if (this.override?.usePromptBasedToolCalling && toolNames.length > 0) {
|
|
1211
|
+
return this.processResultForToolCalls(result, toolNames);
|
|
1212
|
+
}
|
|
1213
|
+
return result;
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Stream text
|
|
1217
|
+
*/
|
|
1218
|
+
async doStream(options) {
|
|
1219
|
+
let modifiedOptions = { ...options };
|
|
1220
|
+
const toolNames = [];
|
|
1221
|
+
if (this.override?.usePromptBasedToolCalling && options.tools && options.tools.length > 0) {
|
|
1222
|
+
const result2 = this.applyPromptBasedToolCalling(modifiedOptions);
|
|
1223
|
+
modifiedOptions = result2.options;
|
|
1224
|
+
toolNames.push(...result2.toolNames);
|
|
1225
|
+
}
|
|
1226
|
+
const result = await this.baseModel.doStream(modifiedOptions);
|
|
1227
|
+
if (this.override?.usePromptBasedToolCalling && toolNames.length > 0) {
|
|
1228
|
+
return this.processStreamForToolCalls(result, toolNames);
|
|
1229
|
+
}
|
|
1230
|
+
return result;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Apply prompt-based tool calling by converting tools to system prompt
|
|
1234
|
+
*/
|
|
1235
|
+
applyPromptBasedToolCalling(options) {
|
|
1236
|
+
if (!options.tools || options.tools.length === 0) {
|
|
1237
|
+
return { options, toolNames: [] };
|
|
1238
|
+
}
|
|
1239
|
+
const functionTools = options.tools.filter(
|
|
1240
|
+
(tool) => tool.type === "function"
|
|
1241
|
+
);
|
|
1242
|
+
if (functionTools.length === 0) {
|
|
1243
|
+
return { options, toolNames: [] };
|
|
1244
|
+
}
|
|
1245
|
+
const toolDefinitions = functionTools.map((tool) => ({
|
|
1246
|
+
type: "function",
|
|
1247
|
+
name: tool.name,
|
|
1248
|
+
description: tool.description,
|
|
1249
|
+
// V2 uses inputSchema instead of parameters
|
|
1250
|
+
parameters: tool.inputSchema
|
|
1251
|
+
}));
|
|
1252
|
+
const toolNames = toolDefinitions.map((t) => t.name);
|
|
1253
|
+
const toolPrompt = generateXmlToolPrompt(toolDefinitions);
|
|
1254
|
+
const modifiedPrompt = options.prompt.map((msg) => {
|
|
1255
|
+
if (msg.role === "system") {
|
|
1256
|
+
const content = msg.content;
|
|
1257
|
+
return {
|
|
1258
|
+
...msg,
|
|
1259
|
+
content: content + "\n\n" + toolPrompt
|
|
1260
|
+
};
|
|
1091
1261
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1262
|
+
return msg;
|
|
1263
|
+
});
|
|
1264
|
+
const hasSystemMessage = modifiedPrompt.some((m) => m.role === "system");
|
|
1265
|
+
if (!hasSystemMessage) {
|
|
1266
|
+
modifiedPrompt.unshift({
|
|
1267
|
+
role: "system",
|
|
1268
|
+
content: toolPrompt
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
options: {
|
|
1273
|
+
...options,
|
|
1274
|
+
prompt: modifiedPrompt,
|
|
1275
|
+
tools: void 0,
|
|
1276
|
+
toolChoice: void 0
|
|
1277
|
+
},
|
|
1278
|
+
toolNames
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Process non-streaming result for XML tool calls
|
|
1283
|
+
*/
|
|
1284
|
+
processResultForToolCalls(result, toolNames) {
|
|
1285
|
+
const content = result.content;
|
|
1286
|
+
const textContentIndex = content.findIndex((c) => c.type === "text");
|
|
1287
|
+
if (textContentIndex === -1 || !content[textContentIndex].text) {
|
|
1288
|
+
return result;
|
|
1289
|
+
}
|
|
1290
|
+
const text = content[textContentIndex].text;
|
|
1291
|
+
const xmlToolCalls = parseXmlToolCalls(text, toolNames, {
|
|
1292
|
+
trimParameterWhitespace: this.override?.trimXmlToolParameterWhitespace ?? false
|
|
1293
|
+
});
|
|
1294
|
+
if (xmlToolCalls.length === 0) {
|
|
1295
|
+
return result;
|
|
1296
|
+
}
|
|
1297
|
+
const cleanedText = this.removeXmlToolCallsFromText(text, toolNames);
|
|
1298
|
+
const newContent = [];
|
|
1299
|
+
if (cleanedText.trim()) {
|
|
1300
|
+
newContent.push({
|
|
1301
|
+
type: "text",
|
|
1302
|
+
text: cleanedText
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
for (const tc of xmlToolCalls) {
|
|
1306
|
+
newContent.push({
|
|
1307
|
+
type: "tool-call",
|
|
1308
|
+
toolCallId: tc.id,
|
|
1309
|
+
toolName: tc.name,
|
|
1310
|
+
input: JSON.stringify(tc.arguments)
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
for (const c of content) {
|
|
1314
|
+
if (c.type !== "text") {
|
|
1315
|
+
newContent.push(c);
|
|
1094
1316
|
}
|
|
1095
1317
|
}
|
|
1318
|
+
return {
|
|
1319
|
+
...result,
|
|
1320
|
+
content: newContent,
|
|
1321
|
+
finishReason: "tool-calls"
|
|
1322
|
+
};
|
|
1096
1323
|
}
|
|
1097
|
-
|
|
1324
|
+
/**
|
|
1325
|
+
* Process streaming result for XML tool calls
|
|
1326
|
+
*
|
|
1327
|
+
* This wraps the stream to accumulate text and parse tool calls at the end.
|
|
1328
|
+
*/
|
|
1329
|
+
processStreamForToolCalls(result, toolNames) {
|
|
1330
|
+
const originalStream = result.stream;
|
|
1331
|
+
const override = this.override;
|
|
1332
|
+
const self = this;
|
|
1333
|
+
const transformedStream = new ReadableStream({
|
|
1334
|
+
async start(controller) {
|
|
1335
|
+
let accumulatedText = "";
|
|
1336
|
+
const bufferedParts = [];
|
|
1337
|
+
try {
|
|
1338
|
+
const reader = originalStream.getReader();
|
|
1339
|
+
while (true) {
|
|
1340
|
+
const { done, value } = await reader.read();
|
|
1341
|
+
if (done) {
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
const part = value;
|
|
1345
|
+
if (part.type === "text-delta") {
|
|
1346
|
+
accumulatedText += part.delta;
|
|
1347
|
+
}
|
|
1348
|
+
bufferedParts.push(part);
|
|
1349
|
+
}
|
|
1350
|
+
const xmlToolCalls = parseXmlToolCalls(accumulatedText, toolNames, {
|
|
1351
|
+
trimParameterWhitespace: override?.trimXmlToolParameterWhitespace ?? false
|
|
1352
|
+
});
|
|
1353
|
+
if (xmlToolCalls.length > 0) {
|
|
1354
|
+
const cleanedText = self.removeXmlToolCallsFromText(
|
|
1355
|
+
accumulatedText,
|
|
1356
|
+
toolNames
|
|
1357
|
+
);
|
|
1358
|
+
if (cleanedText.trim()) {
|
|
1359
|
+
controller.enqueue({
|
|
1360
|
+
type: "text-delta",
|
|
1361
|
+
id: generateId(),
|
|
1362
|
+
delta: cleanedText
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
for (const tc of xmlToolCalls) {
|
|
1366
|
+
controller.enqueue({
|
|
1367
|
+
type: "tool-call",
|
|
1368
|
+
toolCallId: tc.id,
|
|
1369
|
+
toolName: tc.name,
|
|
1370
|
+
input: JSON.stringify(tc.arguments)
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
controller.enqueue({
|
|
1374
|
+
type: "finish",
|
|
1375
|
+
finishReason: "tool-calls",
|
|
1376
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
|
|
1377
|
+
});
|
|
1378
|
+
} else {
|
|
1379
|
+
for (const part of bufferedParts) {
|
|
1380
|
+
controller.enqueue(part);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
controller.close();
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
controller.error(error);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
return {
|
|
1390
|
+
...result,
|
|
1391
|
+
stream: transformedStream
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Remove XML tool call blocks from text
|
|
1396
|
+
*/
|
|
1397
|
+
removeXmlToolCallsFromText(text, toolNames) {
|
|
1398
|
+
let result = text;
|
|
1399
|
+
for (const toolName of toolNames) {
|
|
1400
|
+
const regex = new RegExp(
|
|
1401
|
+
`<${this.escapeRegex(toolName)}>[\\s\\S]*?<\\/${this.escapeRegex(toolName)}>`,
|
|
1402
|
+
"g"
|
|
1403
|
+
);
|
|
1404
|
+
result = result.replace(regex, "");
|
|
1405
|
+
}
|
|
1406
|
+
return result.trim();
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Escape special regex characters
|
|
1410
|
+
*/
|
|
1411
|
+
escapeRegex(str) {
|
|
1412
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
function createEnhancedModel(baseModel, modelId, override) {
|
|
1416
|
+
if (override?.usePromptBasedToolCalling) {
|
|
1417
|
+
return new EnhancedLanguageModel(baseModel, modelId, override);
|
|
1418
|
+
}
|
|
1419
|
+
return baseModel;
|
|
1098
1420
|
}
|
|
1421
|
+
|
|
1422
|
+
// src/index.ts
|
|
1423
|
+
var modelCache = /* @__PURE__ */ new WeakMap();
|
|
1099
1424
|
function mergeWithConfig(options, config) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1425
|
+
const baseModelOverrides = config?.modelOverrides ?? {};
|
|
1426
|
+
const optionModelOverrides = options.modelOverrides ?? {};
|
|
1427
|
+
const modelLevelOverrides = {};
|
|
1428
|
+
if (options.models) {
|
|
1429
|
+
for (const [modelId, modelConfig] of Object.entries(options.models)) {
|
|
1430
|
+
if (modelConfig.options) {
|
|
1431
|
+
modelLevelOverrides[modelId] = modelConfig.options;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const mergedModelOverrides = {
|
|
1436
|
+
...baseModelOverrides,
|
|
1437
|
+
...optionModelOverrides
|
|
1438
|
+
};
|
|
1439
|
+
for (const [modelId, override] of Object.entries(modelLevelOverrides)) {
|
|
1440
|
+
mergedModelOverrides[modelId] = {
|
|
1441
|
+
...mergedModelOverrides[modelId],
|
|
1442
|
+
...override
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
let apiKey = options.apiKey;
|
|
1446
|
+
if (!apiKey && config) {
|
|
1447
|
+
apiKey = resolveApiKey(config);
|
|
1102
1448
|
}
|
|
1103
1449
|
return {
|
|
1104
1450
|
// Options take precedence over config
|
|
1105
|
-
baseURL: options.baseURL || config
|
|
1106
|
-
apiKey
|
|
1107
|
-
name: options.name || config
|
|
1451
|
+
baseURL: options.baseURL || config?.baseURL || "",
|
|
1452
|
+
apiKey,
|
|
1453
|
+
name: options.name || config?.name,
|
|
1108
1454
|
headers: {
|
|
1109
|
-
...config
|
|
1455
|
+
...config?.headers,
|
|
1110
1456
|
...options.headers
|
|
1111
1457
|
},
|
|
1112
|
-
modelFilter: options.modelFilter || config
|
|
1113
|
-
modelOverrides:
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
},
|
|
1117
|
-
useConfigFile: options.useConfigFile
|
|
1458
|
+
modelFilter: options.modelFilter || config?.modelFilter,
|
|
1459
|
+
modelOverrides: mergedModelOverrides,
|
|
1460
|
+
useConfigFile: options.useConfigFile,
|
|
1461
|
+
models: options.models
|
|
1118
1462
|
};
|
|
1119
1463
|
}
|
|
1120
1464
|
function createOai2lm(options) {
|
|
@@ -1125,8 +1469,9 @@ function createOai2lm(options) {
|
|
|
1125
1469
|
"baseURL is required. Provide it in options or in oai2lm.json config file."
|
|
1126
1470
|
);
|
|
1127
1471
|
}
|
|
1472
|
+
const baseURL = mergedOptions.baseURL;
|
|
1128
1473
|
const baseProvider = createOpenAICompatible({
|
|
1129
|
-
baseURL:
|
|
1474
|
+
baseURL: baseURL.replace(/\/+$/, ""),
|
|
1130
1475
|
name: mergedOptions.name || "oai2lm",
|
|
1131
1476
|
apiKey: mergedOptions.apiKey,
|
|
1132
1477
|
headers: mergedOptions.headers
|
|
@@ -1135,7 +1480,7 @@ function createOai2lm(options) {
|
|
|
1135
1480
|
async function discoverAndCache() {
|
|
1136
1481
|
const apiKey = mergedOptions.apiKey || "";
|
|
1137
1482
|
const models = await discoverModels(
|
|
1138
|
-
|
|
1483
|
+
baseURL,
|
|
1139
1484
|
apiKey,
|
|
1140
1485
|
mergedOptions.headers
|
|
1141
1486
|
);
|
|
@@ -1182,11 +1527,24 @@ function createOai2lm(options) {
|
|
|
1182
1527
|
}
|
|
1183
1528
|
return cache;
|
|
1184
1529
|
}
|
|
1530
|
+
function getOverride(modelId) {
|
|
1531
|
+
return findModelOverride(modelId, mergedOptions.modelOverrides);
|
|
1532
|
+
}
|
|
1185
1533
|
const provider = function(modelId) {
|
|
1186
|
-
|
|
1534
|
+
const baseModel = baseProvider(modelId);
|
|
1535
|
+
const override = getOverride(modelId);
|
|
1536
|
+
return createEnhancedModel(baseModel, modelId, override);
|
|
1537
|
+
};
|
|
1538
|
+
provider.languageModel = (modelId) => {
|
|
1539
|
+
const baseModel = baseProvider.languageModel(modelId);
|
|
1540
|
+
const override = getOverride(modelId);
|
|
1541
|
+
return createEnhancedModel(baseModel, modelId, override);
|
|
1542
|
+
};
|
|
1543
|
+
provider.chatModel = (modelId) => {
|
|
1544
|
+
const baseModel = baseProvider.chatModel(modelId);
|
|
1545
|
+
const override = getOverride(modelId);
|
|
1546
|
+
return createEnhancedModel(baseModel, modelId, override);
|
|
1187
1547
|
};
|
|
1188
|
-
provider.languageModel = (modelId) => baseProvider.languageModel(modelId);
|
|
1189
|
-
provider.chatModel = (modelId) => baseProvider.chatModel(modelId);
|
|
1190
1548
|
provider.completionModel = (modelId) => baseProvider.completionModel(modelId);
|
|
1191
1549
|
provider.textEmbeddingModel = (modelId) => baseProvider.textEmbeddingModel(modelId);
|
|
1192
1550
|
provider.imageModel = (modelId) => baseProvider.imageModel(modelId);
|
|
@@ -1205,6 +1563,8 @@ function createOai2lm(options) {
|
|
|
1205
1563
|
}
|
|
1206
1564
|
var index_default = createOai2lm;
|
|
1207
1565
|
export {
|
|
1566
|
+
EnhancedLanguageModel,
|
|
1567
|
+
createEnhancedModel,
|
|
1208
1568
|
createOai2lm,
|
|
1209
1569
|
index_default as default,
|
|
1210
1570
|
discoverModels,
|