@lukaplayground/aikit 1.0.1 → 1.1.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/README.md +0 -13
- package/dist/aikit.js +108 -570
- package/dist/aikit.js.map +1 -1
- package/dist/aikit.min.js +1 -1
- package/dist/aikit.min.js.map +1 -1
- package/dist/aikit.umd.js +108 -570
- package/dist/aikit.umd.js.map +1 -1
- package/package.json +9 -7
package/dist/aikit.js
CHANGED
|
@@ -886,187 +886,58 @@ class OpenAIAdapter extends BaseAdapter {
|
|
|
886
886
|
constructor() {
|
|
887
887
|
super();
|
|
888
888
|
this.baseURL = 'https://api.openai.com/v1';
|
|
889
|
-
this.defaultModel = 'gpt-3.5-turbo
|
|
889
|
+
this.defaultModel = 'gpt-4o-mini'; // ← 변경: gpt-3.5-turbo에서
|
|
890
|
+
|
|
891
|
+
// 사용 가능한 모델 목록
|
|
892
|
+
this.availableModels = [
|
|
893
|
+
'gpt-4o',
|
|
894
|
+
'gpt-4o-mini',
|
|
895
|
+
'gpt-4-turbo',
|
|
896
|
+
'gpt-3.5-turbo'
|
|
897
|
+
];
|
|
890
898
|
}
|
|
891
899
|
|
|
892
900
|
/**
|
|
893
901
|
* Send chat request to OpenAI
|
|
894
902
|
* @override
|
|
895
903
|
*/
|
|
896
|
-
async chat(
|
|
897
|
-
this.
|
|
898
|
-
|
|
899
|
-
const { message, apiKey, options = {} } = request;
|
|
900
|
-
|
|
901
|
-
const requestBody = {
|
|
902
|
-
model: options.model || this.defaultModel,
|
|
903
|
-
messages: this.buildMessages(message, options),
|
|
904
|
-
temperature: options.temperature ?? 0.7,
|
|
905
|
-
max_tokens: options.maxTokens || options.max_tokens,
|
|
906
|
-
top_p: options.topP || options.top_p,
|
|
907
|
-
frequency_penalty: options.frequencyPenalty || options.frequency_penalty,
|
|
908
|
-
presence_penalty: options.presencePenalty || options.presence_penalty,
|
|
909
|
-
stream: false
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
// Remove undefined values
|
|
913
|
-
Object.keys(requestBody).forEach(key => {
|
|
914
|
-
if (requestBody[key] === undefined) {
|
|
915
|
-
delete requestBody[key];
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
const fetchOptions = {
|
|
920
|
-
method: 'POST',
|
|
921
|
-
headers: this.buildHeaders(apiKey),
|
|
922
|
-
body: JSON.stringify(requestBody)
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
const rawResponse = await this.withRetry(() =>
|
|
926
|
-
this.makeRequest(`${this.baseURL}/chat/completions`, fetchOptions)
|
|
927
|
-
);
|
|
928
|
-
|
|
929
|
-
return this.normalizeResponse(rawResponse);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
/**
|
|
933
|
-
* Build messages array from user message
|
|
934
|
-
* @private
|
|
935
|
-
*/
|
|
936
|
-
buildMessages(message, options) {
|
|
937
|
-
const messages = [];
|
|
938
|
-
|
|
939
|
-
// Add system message if provided
|
|
940
|
-
if (options.systemMessage) {
|
|
941
|
-
messages.push({
|
|
942
|
-
role: 'system',
|
|
943
|
-
content: options.systemMessage
|
|
944
|
-
});
|
|
945
|
-
}
|
|
904
|
+
async chat(message, options = {}) {
|
|
905
|
+
const model = options.model || this.defaultModel;
|
|
906
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
946
907
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
messages.push(...options.history);
|
|
908
|
+
if (!apiKey) {
|
|
909
|
+
throw new Error('OpenAI API key is required');
|
|
950
910
|
}
|
|
951
911
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
912
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
913
|
+
method: 'POST',
|
|
914
|
+
headers: {
|
|
915
|
+
'Content-Type': 'application/json',
|
|
916
|
+
'Authorization': `Bearer ${apiKey}`
|
|
917
|
+
},
|
|
918
|
+
body: JSON.stringify({
|
|
919
|
+
model: model,
|
|
920
|
+
messages: [
|
|
921
|
+
{
|
|
922
|
+
role: 'user',
|
|
923
|
+
content: message
|
|
924
|
+
}
|
|
925
|
+
],
|
|
926
|
+
...options
|
|
927
|
+
})
|
|
956
928
|
});
|
|
957
929
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Extract text from OpenAI response
|
|
963
|
-
* @override
|
|
964
|
-
*/
|
|
965
|
-
extractText(rawResponse) {
|
|
966
|
-
if (!rawResponse.choices || rawResponse.choices.length === 0) {
|
|
967
|
-
throw new Error('Invalid OpenAI response: no choices returned');
|
|
930
|
+
if (!response.ok) {
|
|
931
|
+
const error = await response.text();
|
|
932
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
968
933
|
}
|
|
969
934
|
|
|
970
|
-
const
|
|
971
|
-
return firstChoice.message?.content || '';
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Extract usage information
|
|
976
|
-
* @override
|
|
977
|
-
*/
|
|
978
|
-
extractUsage(rawResponse) {
|
|
979
|
-
const usage = rawResponse.usage || {};
|
|
980
|
-
|
|
981
|
-
return {
|
|
982
|
-
promptTokens: usage.prompt_tokens || 0,
|
|
983
|
-
completionTokens: usage.completion_tokens || 0,
|
|
984
|
-
totalTokens: usage.total_tokens || 0,
|
|
985
|
-
// Normalized names for compatibility
|
|
986
|
-
input: usage.prompt_tokens || 0,
|
|
987
|
-
output: usage.completion_tokens || 0,
|
|
988
|
-
total: usage.total_tokens || 0
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Extract model name
|
|
994
|
-
* @override
|
|
995
|
-
*/
|
|
996
|
-
extractModel(rawResponse) {
|
|
997
|
-
return rawResponse.model || this.defaultModel;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* Extract finish reason
|
|
1002
|
-
* @override
|
|
1003
|
-
*/
|
|
1004
|
-
extractFinishReason(rawResponse) {
|
|
1005
|
-
if (!rawResponse.choices || rawResponse.choices.length === 0) {
|
|
1006
|
-
return 'unknown';
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
return rawResponse.choices[0].finish_reason || 'unknown';
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Stream chat (for future implementation)
|
|
1014
|
-
* @param {Object} request - Request object
|
|
1015
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1016
|
-
*/
|
|
1017
|
-
async streamChat(request, onChunk) {
|
|
1018
|
-
// TODO: Implement streaming support
|
|
1019
|
-
throw new Error('Streaming is not yet implemented for OpenAI adapter');
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* List available models
|
|
1024
|
-
* @param {string} apiKey - API key
|
|
1025
|
-
*/
|
|
1026
|
-
async listModels(apiKey) {
|
|
1027
|
-
const fetchOptions = {
|
|
1028
|
-
method: 'GET',
|
|
1029
|
-
headers: this.buildHeaders(apiKey)
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
const response = await this.makeRequest(`${this.baseURL}/models`, fetchOptions);
|
|
1033
|
-
|
|
1034
|
-
// Filter to chat models only
|
|
1035
|
-
const chatModels = response.data.filter(model =>
|
|
1036
|
-
model.id.includes('gpt') || model.id.includes('turbo')
|
|
1037
|
-
);
|
|
1038
|
-
|
|
1039
|
-
return chatModels.map(model => ({
|
|
1040
|
-
id: model.id,
|
|
1041
|
-
created: model.created,
|
|
1042
|
-
ownedBy: model.owned_by
|
|
1043
|
-
}));
|
|
1044
|
-
}
|
|
935
|
+
const data = await response.json();
|
|
1045
936
|
|
|
1046
|
-
/**
|
|
1047
|
-
* Generate embeddings
|
|
1048
|
-
* @param {string} text - Text to embed
|
|
1049
|
-
* @param {string} apiKey - API key
|
|
1050
|
-
* @param {Object} options - Options
|
|
1051
|
-
*/
|
|
1052
|
-
async createEmbedding(text, apiKey, options = {}) {
|
|
1053
|
-
const requestBody = {
|
|
1054
|
-
model: options.model || 'text-embedding-ada-002',
|
|
1055
|
-
input: text
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
|
-
const fetchOptions = {
|
|
1059
|
-
method: 'POST',
|
|
1060
|
-
headers: this.buildHeaders(apiKey),
|
|
1061
|
-
body: JSON.stringify(requestBody)
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
const response = await this.makeRequest(`${this.baseURL}/embeddings`, fetchOptions);
|
|
1065
|
-
|
|
1066
937
|
return {
|
|
1067
|
-
|
|
1068
|
-
model:
|
|
1069
|
-
usage:
|
|
938
|
+
content: data.choices[0].message.content,
|
|
939
|
+
model: data.model,
|
|
940
|
+
usage: data.usage
|
|
1070
941
|
};
|
|
1071
942
|
}
|
|
1072
943
|
}
|
|
@@ -1081,192 +952,74 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
1081
952
|
constructor() {
|
|
1082
953
|
super();
|
|
1083
954
|
this.baseURL = 'https://api.anthropic.com/v1';
|
|
1084
|
-
this.defaultModel = 'claude-3-sonnet-
|
|
955
|
+
this.defaultModel = 'claude-3-5-sonnet-20241022'; // ← 변경
|
|
1085
956
|
this.apiVersion = '2023-06-01';
|
|
957
|
+
|
|
958
|
+
// 사용 가능한 모델 목록
|
|
959
|
+
this.availableModels = [
|
|
960
|
+
'claude-3-5-sonnet-20241022',
|
|
961
|
+
'claude-3-5-haiku-20241022',
|
|
962
|
+
'claude-3-opus-20240229',
|
|
963
|
+
'claude-3-sonnet-20240229',
|
|
964
|
+
'claude-3-haiku-20240307'
|
|
965
|
+
];
|
|
1086
966
|
}
|
|
1087
967
|
|
|
1088
968
|
/**
|
|
1089
969
|
* Send chat request to Claude
|
|
1090
970
|
* @override
|
|
1091
971
|
*/
|
|
1092
|
-
async chat(
|
|
1093
|
-
this.
|
|
1094
|
-
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
const requestBody = {
|
|
1098
|
-
model: options.model || this.defaultModel,
|
|
1099
|
-
messages: this.buildMessages(message, options),
|
|
1100
|
-
max_tokens: options.maxTokens || options.max_tokens || 1024,
|
|
1101
|
-
temperature: options.temperature ?? 1.0,
|
|
1102
|
-
top_p: options.topP || options.top_p,
|
|
1103
|
-
top_k: options.topK || options.top_k,
|
|
1104
|
-
stream: false
|
|
1105
|
-
};
|
|
972
|
+
async chat(message, options = {}) {
|
|
973
|
+
const model = options.model || this.defaultModel;
|
|
974
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
975
|
+
const maxTokens = options.maxTokens || 1024;
|
|
1106
976
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
requestBody.system = options.systemMessage;
|
|
977
|
+
if (!apiKey) {
|
|
978
|
+
throw new Error('Claude API key is required');
|
|
1110
979
|
}
|
|
1111
980
|
|
|
1112
|
-
|
|
1113
|
-
Object.keys(requestBody).forEach(key => {
|
|
1114
|
-
if (requestBody[key] === undefined) {
|
|
1115
|
-
delete requestBody[key];
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
|
|
1119
|
-
const fetchOptions = {
|
|
981
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
1120
982
|
method: 'POST',
|
|
1121
|
-
headers:
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
buildHeaders(apiKey) {
|
|
1137
|
-
return {
|
|
1138
|
-
'Content-Type': 'application/json',
|
|
1139
|
-
'x-api-key': apiKey,
|
|
1140
|
-
'anthropic-version': this.apiVersion
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Build messages array from user message
|
|
1146
|
-
* @private
|
|
1147
|
-
*/
|
|
1148
|
-
buildMessages(message, options) {
|
|
1149
|
-
const messages = [];
|
|
1150
|
-
|
|
1151
|
-
// Add conversation history if provided
|
|
1152
|
-
if (options.history && Array.isArray(options.history)) {
|
|
1153
|
-
// Claude uses 'user' and 'assistant' roles
|
|
1154
|
-
messages.push(...options.history.map(msg => ({
|
|
1155
|
-
role: msg.role === 'system' ? 'user' : msg.role,
|
|
1156
|
-
content: msg.content
|
|
1157
|
-
})));
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// Add current user message
|
|
1161
|
-
messages.push({
|
|
1162
|
-
role: 'user',
|
|
1163
|
-
content: message
|
|
983
|
+
headers: {
|
|
984
|
+
'Content-Type': 'application/json',
|
|
985
|
+
'x-api-key': apiKey,
|
|
986
|
+
'anthropic-version': this.apiVersion
|
|
987
|
+
},
|
|
988
|
+
body: JSON.stringify({
|
|
989
|
+
model: model,
|
|
990
|
+
max_tokens: maxTokens,
|
|
991
|
+
messages: [
|
|
992
|
+
{
|
|
993
|
+
role: 'user',
|
|
994
|
+
content: message
|
|
995
|
+
}
|
|
996
|
+
]
|
|
997
|
+
})
|
|
1164
998
|
});
|
|
1165
999
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
/**
|
|
1170
|
-
* Extract text from Claude response
|
|
1171
|
-
* @override
|
|
1172
|
-
*/
|
|
1173
|
-
extractText(rawResponse) {
|
|
1174
|
-
if (!rawResponse.content || rawResponse.content.length === 0) {
|
|
1175
|
-
throw new Error('Invalid Claude response: no content returned');
|
|
1000
|
+
if (!response.ok) {
|
|
1001
|
+
const error = await response.text();
|
|
1002
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
1176
1003
|
}
|
|
1177
1004
|
|
|
1178
|
-
|
|
1179
|
-
const textBlocks = rawResponse.content
|
|
1180
|
-
.filter(block => block.type === 'text')
|
|
1181
|
-
.map(block => block.text);
|
|
1182
|
-
|
|
1183
|
-
return textBlocks.join('\n');
|
|
1184
|
-
}
|
|
1005
|
+
const data = await response.json();
|
|
1185
1006
|
|
|
1186
|
-
/**
|
|
1187
|
-
* Extract usage information
|
|
1188
|
-
* @override
|
|
1189
|
-
*/
|
|
1190
|
-
extractUsage(rawResponse) {
|
|
1191
|
-
const usage = rawResponse.usage || {};
|
|
1192
|
-
|
|
1193
1007
|
return {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
output: usage.output_tokens || 0,
|
|
1200
|
-
total: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Extract model name
|
|
1206
|
-
* @override
|
|
1207
|
-
*/
|
|
1208
|
-
extractModel(rawResponse) {
|
|
1209
|
-
return rawResponse.model || this.defaultModel;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
/**
|
|
1213
|
-
* Extract finish reason
|
|
1214
|
-
* @override
|
|
1215
|
-
*/
|
|
1216
|
-
extractFinishReason(rawResponse) {
|
|
1217
|
-
return rawResponse.stop_reason || 'unknown';
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
/**
|
|
1221
|
-
* Stream chat (for future implementation)
|
|
1222
|
-
* @param {Object} request - Request object
|
|
1223
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1224
|
-
*/
|
|
1225
|
-
async streamChat(request, onChunk) {
|
|
1226
|
-
// TODO: Implement streaming support
|
|
1227
|
-
throw new Error('Streaming is not yet implemented for Claude adapter');
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Get model information
|
|
1232
|
-
*/
|
|
1233
|
-
getAvailableModels() {
|
|
1234
|
-
return [
|
|
1235
|
-
{
|
|
1236
|
-
id: 'claude-3-opus-20240229',
|
|
1237
|
-
name: 'Claude 3 Opus',
|
|
1238
|
-
description: 'Most powerful model, best for complex tasks',
|
|
1239
|
-
maxTokens: 4096
|
|
1240
|
-
},
|
|
1241
|
-
{
|
|
1242
|
-
id: 'claude-3-sonnet-20240229',
|
|
1243
|
-
name: 'Claude 3 Sonnet',
|
|
1244
|
-
description: 'Balanced performance and speed',
|
|
1245
|
-
maxTokens: 4096
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
id: 'claude-3-haiku-20240307',
|
|
1249
|
-
name: 'Claude 3 Haiku',
|
|
1250
|
-
description: 'Fastest model, best for simple tasks',
|
|
1251
|
-
maxTokens: 4096
|
|
1252
|
-
},
|
|
1253
|
-
{
|
|
1254
|
-
id: 'claude-2.1',
|
|
1255
|
-
name: 'Claude 2.1',
|
|
1256
|
-
description: 'Previous generation model',
|
|
1257
|
-
maxTokens: 4096
|
|
1008
|
+
content: data.content[0].text,
|
|
1009
|
+
model: data.model,
|
|
1010
|
+
usage: {
|
|
1011
|
+
input_tokens: data.usage.input_tokens,
|
|
1012
|
+
output_tokens: data.usage.output_tokens
|
|
1258
1013
|
}
|
|
1259
|
-
|
|
1014
|
+
};
|
|
1260
1015
|
}
|
|
1261
1016
|
|
|
1262
1017
|
/**
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
* This is a rough estimate
|
|
1018
|
+
* Estimate token count (Claude uses characters)
|
|
1019
|
+
* @private
|
|
1266
1020
|
*/
|
|
1267
1021
|
estimateTokens(text) {
|
|
1268
|
-
//
|
|
1269
|
-
// ~2 characters per token for code
|
|
1022
|
+
// Claude roughly: 1 token ≈ 4 characters for English
|
|
1270
1023
|
const avgCharsPerToken = text.match(/[a-zA-Z0-9_]/g) ? 4 : 3;
|
|
1271
1024
|
return Math.ceil(text.length / avgCharsPerToken);
|
|
1272
1025
|
}
|
|
@@ -1282,269 +1035,54 @@ class GeminiAdapter extends BaseAdapter {
|
|
|
1282
1035
|
constructor() {
|
|
1283
1036
|
super();
|
|
1284
1037
|
this.baseURL = 'https://generativelanguage.googleapis.com/v1beta';
|
|
1285
|
-
this.defaultModel = 'gemini-
|
|
1038
|
+
this.defaultModel = 'gemini-1.5-flash'; // ← 변경
|
|
1039
|
+
|
|
1040
|
+
// 사용 가능한 모델 목록
|
|
1041
|
+
this.availableModels = [
|
|
1042
|
+
'gemini-1.5-flash',
|
|
1043
|
+
'gemini-1.5-flash-8b',
|
|
1044
|
+
'gemini-1.5-pro',
|
|
1045
|
+
'gemini-2.0-flash-exp'
|
|
1046
|
+
];
|
|
1286
1047
|
}
|
|
1287
1048
|
|
|
1288
1049
|
/**
|
|
1289
1050
|
* Send chat request to Gemini
|
|
1290
1051
|
* @override
|
|
1291
1052
|
*/
|
|
1292
|
-
async chat(
|
|
1293
|
-
this.validateRequest(request);
|
|
1294
|
-
|
|
1295
|
-
const { message, apiKey, options = {} } = request;
|
|
1053
|
+
async chat(message, options = {}) {
|
|
1296
1054
|
const model = options.model || this.defaultModel;
|
|
1055
|
+
const apiKey = options.apiKey || this.apiKey;
|
|
1297
1056
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
generationConfig: {
|
|
1301
|
-
temperature: options.temperature ?? 0.9,
|
|
1302
|
-
topK: options.topK || options.top_k,
|
|
1303
|
-
topP: (options.topP || options.top_p) ?? 1,
|
|
1304
|
-
maxOutputTokens: options.maxTokens || options.max_tokens || options.maxOutputTokens,
|
|
1305
|
-
stopSequences: options.stopSequences || options.stop
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
// Add safety settings if provided
|
|
1310
|
-
if (options.safetySettings) {
|
|
1311
|
-
requestBody.safetySettings = options.safetySettings;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// Remove undefined values
|
|
1315
|
-
if (requestBody.generationConfig) {
|
|
1316
|
-
Object.keys(requestBody.generationConfig).forEach(key => {
|
|
1317
|
-
if (requestBody.generationConfig[key] === undefined) {
|
|
1318
|
-
delete requestBody.generationConfig[key];
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1057
|
+
if (!apiKey) {
|
|
1058
|
+
throw new Error('Gemini API key is required');
|
|
1321
1059
|
}
|
|
1322
1060
|
|
|
1323
1061
|
const url = `${this.baseURL}/models/${model}:generateContent?key=${apiKey}`;
|
|
1324
1062
|
|
|
1325
|
-
const
|
|
1063
|
+
const response = await fetch(url, {
|
|
1326
1064
|
method: 'POST',
|
|
1327
1065
|
headers: {
|
|
1328
1066
|
'Content-Type': 'application/json'
|
|
1329
1067
|
},
|
|
1330
|
-
body: JSON.stringify(
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
return this.normalizeResponse(rawResponse);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Build contents array from user message
|
|
1342
|
-
* @private
|
|
1343
|
-
*/
|
|
1344
|
-
buildContents(message, options) {
|
|
1345
|
-
const contents = [];
|
|
1346
|
-
|
|
1347
|
-
// Add conversation history if provided
|
|
1348
|
-
if (options.history && Array.isArray(options.history)) {
|
|
1349
|
-
options.history.forEach(msg => {
|
|
1350
|
-
contents.push({
|
|
1351
|
-
role: msg.role === 'assistant' ? 'model' : 'user',
|
|
1352
|
-
parts: [{ text: msg.content }]
|
|
1353
|
-
});
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
// Add current user message
|
|
1358
|
-
contents.push({
|
|
1359
|
-
role: 'user',
|
|
1360
|
-
parts: [{ text: message }]
|
|
1068
|
+
body: JSON.stringify({
|
|
1069
|
+
contents: [{
|
|
1070
|
+
parts: [{
|
|
1071
|
+
text: message
|
|
1072
|
+
}]
|
|
1073
|
+
}]
|
|
1074
|
+
})
|
|
1361
1075
|
});
|
|
1362
1076
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
/**
|
|
1367
|
-
* Extract text from Gemini response
|
|
1368
|
-
* @override
|
|
1369
|
-
*/
|
|
1370
|
-
extractText(rawResponse) {
|
|
1371
|
-
if (!rawResponse.candidates || rawResponse.candidates.length === 0) {
|
|
1372
|
-
throw new Error('Invalid Gemini response: no candidates returned');
|
|
1077
|
+
if (!response.ok) {
|
|
1078
|
+
const error = await response.text();
|
|
1079
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
1373
1080
|
}
|
|
1374
1081
|
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
if (!candidate.content || !candidate.content.parts) {
|
|
1378
|
-
throw new Error('Invalid Gemini response: no content parts');
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Combine all text parts
|
|
1382
|
-
const textParts = candidate.content.parts
|
|
1383
|
-
.filter(part => part.text)
|
|
1384
|
-
.map(part => part.text);
|
|
1385
|
-
|
|
1386
|
-
return textParts.join('\n');
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* Extract usage information
|
|
1391
|
-
* @override
|
|
1392
|
-
*/
|
|
1393
|
-
extractUsage(rawResponse) {
|
|
1394
|
-
const metadata = rawResponse.usageMetadata || {};
|
|
1395
|
-
|
|
1396
|
-
return {
|
|
1397
|
-
promptTokens: metadata.promptTokenCount || 0,
|
|
1398
|
-
completionTokens: metadata.candidatesTokenCount || 0,
|
|
1399
|
-
totalTokens: metadata.totalTokenCount || 0,
|
|
1400
|
-
// Normalized names for compatibility
|
|
1401
|
-
input: metadata.promptTokenCount || 0,
|
|
1402
|
-
output: metadata.candidatesTokenCount || 0,
|
|
1403
|
-
total: metadata.totalTokenCount || 0
|
|
1404
|
-
};
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
/**
|
|
1408
|
-
* Extract model name
|
|
1409
|
-
* @override
|
|
1410
|
-
*/
|
|
1411
|
-
extractModel(rawResponse) {
|
|
1412
|
-
return rawResponse.modelVersion || this.defaultModel;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
/**
|
|
1416
|
-
* Extract finish reason
|
|
1417
|
-
* @override
|
|
1418
|
-
*/
|
|
1419
|
-
extractFinishReason(rawResponse) {
|
|
1420
|
-
if (!rawResponse.candidates || rawResponse.candidates.length === 0) {
|
|
1421
|
-
return 'unknown';
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
return rawResponse.candidates[0].finishReason || 'STOP';
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/**
|
|
1428
|
-
* Stream chat (for future implementation)
|
|
1429
|
-
* @param {Object} request - Request object
|
|
1430
|
-
* @param {Function} onChunk - Callback for each chunk
|
|
1431
|
-
*/
|
|
1432
|
-
async streamChat(request, onChunk) {
|
|
1433
|
-
// TODO: Implement streaming support
|
|
1434
|
-
throw new Error('Streaming is not yet implemented for Gemini adapter');
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
/**
|
|
1438
|
-
* Get available models
|
|
1439
|
-
*/
|
|
1440
|
-
getAvailableModels() {
|
|
1441
|
-
return [
|
|
1442
|
-
{
|
|
1443
|
-
id: 'gemini-pro',
|
|
1444
|
-
name: 'Gemini Pro',
|
|
1445
|
-
description: 'Best for text generation',
|
|
1446
|
-
maxTokens: 8192
|
|
1447
|
-
},
|
|
1448
|
-
{
|
|
1449
|
-
id: 'gemini-pro-vision',
|
|
1450
|
-
name: 'Gemini Pro Vision',
|
|
1451
|
-
description: 'Multimodal model for text and images',
|
|
1452
|
-
maxTokens: 4096
|
|
1453
|
-
},
|
|
1454
|
-
{
|
|
1455
|
-
id: 'gemini-ultra',
|
|
1456
|
-
name: 'Gemini Ultra',
|
|
1457
|
-
description: 'Most capable model (limited access)',
|
|
1458
|
-
maxTokens: 8192
|
|
1459
|
-
}
|
|
1460
|
-
];
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
/**
|
|
1464
|
-
* List models via API
|
|
1465
|
-
* @param {string} apiKey - API key
|
|
1466
|
-
*/
|
|
1467
|
-
async listModels(apiKey) {
|
|
1468
|
-
const url = `${this.baseURL}/models?key=${apiKey}`;
|
|
1469
|
-
|
|
1470
|
-
const fetchOptions = {
|
|
1471
|
-
method: 'GET',
|
|
1472
|
-
headers: {
|
|
1473
|
-
'Content-Type': 'application/json'
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
|
|
1477
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1478
|
-
|
|
1479
|
-
return response.models
|
|
1480
|
-
.filter(model => model.supportedGenerationMethods?.includes('generateContent'))
|
|
1481
|
-
.map(model => ({
|
|
1482
|
-
id: model.name.replace('models/', ''),
|
|
1483
|
-
displayName: model.displayName,
|
|
1484
|
-
description: model.description,
|
|
1485
|
-
inputTokenLimit: model.inputTokenLimit,
|
|
1486
|
-
outputTokenLimit: model.outputTokenLimit
|
|
1487
|
-
}));
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
/**
|
|
1491
|
-
* Count tokens
|
|
1492
|
-
* @param {string} text - Text to count tokens for
|
|
1493
|
-
* @param {string} apiKey - API key
|
|
1494
|
-
* @param {string} model - Model name
|
|
1495
|
-
*/
|
|
1496
|
-
async countTokens(text, apiKey, model = this.defaultModel) {
|
|
1497
|
-
const url = `${this.baseURL}/models/${model}:countTokens?key=${apiKey}`;
|
|
1498
|
-
|
|
1499
|
-
const requestBody = {
|
|
1500
|
-
contents: [{
|
|
1501
|
-
role: 'user',
|
|
1502
|
-
parts: [{ text }]
|
|
1503
|
-
}]
|
|
1504
|
-
};
|
|
1505
|
-
|
|
1506
|
-
const fetchOptions = {
|
|
1507
|
-
method: 'POST',
|
|
1508
|
-
headers: {
|
|
1509
|
-
'Content-Type': 'application/json'
|
|
1510
|
-
},
|
|
1511
|
-
body: JSON.stringify(requestBody)
|
|
1512
|
-
};
|
|
1513
|
-
|
|
1514
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1515
|
-
|
|
1516
|
-
return {
|
|
1517
|
-
totalTokens: response.totalTokens || 0
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
/**
|
|
1522
|
-
* Generate embeddings
|
|
1523
|
-
* @param {string} text - Text to embed
|
|
1524
|
-
* @param {string} apiKey - API key
|
|
1525
|
-
*/
|
|
1526
|
-
async createEmbedding(text, apiKey) {
|
|
1527
|
-
const model = 'embedding-001';
|
|
1528
|
-
const url = `${this.baseURL}/models/${model}:embedContent?key=${apiKey}`;
|
|
1529
|
-
|
|
1530
|
-
const requestBody = {
|
|
1531
|
-
content: {
|
|
1532
|
-
parts: [{ text }]
|
|
1533
|
-
}
|
|
1534
|
-
};
|
|
1535
|
-
|
|
1536
|
-
const fetchOptions = {
|
|
1537
|
-
method: 'POST',
|
|
1538
|
-
headers: {
|
|
1539
|
-
'Content-Type': 'application/json'
|
|
1540
|
-
},
|
|
1541
|
-
body: JSON.stringify(requestBody)
|
|
1542
|
-
};
|
|
1543
|
-
|
|
1544
|
-
const response = await this.makeRequest(url, fetchOptions);
|
|
1082
|
+
const data = await response.json();
|
|
1545
1083
|
|
|
1546
1084
|
return {
|
|
1547
|
-
|
|
1085
|
+
content: data.candidates[0].content.parts[0].text,
|
|
1548
1086
|
model: model
|
|
1549
1087
|
};
|
|
1550
1088
|
}
|