locai 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/provider-factory.js +4 -0
- package/dist/providers/anthropic.d.ts +25 -0
- package/dist/providers/anthropic.js +178 -0
- package/dist/providers/gemini.js +3 -3
- package/dist/providers/openai.js +3 -3
- package/dist/utils/error-helper.js +3 -1
- package/dist/utils/input-validator.js +8 -1
- package/dist/utils/prompt-templates.js +18 -0
- package/dist/utils/rate-limiter.js +6 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as deepseekProvider from "../providers/deepseek.js";
|
|
2
2
|
import * as geminiProvider from "../providers/gemini.js";
|
|
3
3
|
import * as openaiProvider from "../providers/openai.js";
|
|
4
|
+
import * as anthropicProvider from "../providers/anthropic.js";
|
|
4
5
|
import * as dashscopeProvider from "../providers/dashscope.js";
|
|
5
6
|
import * as xaiProvider from "../providers/xai.js";
|
|
6
7
|
import FallbackProvider from "./fallback-provider.js";
|
|
@@ -23,6 +24,7 @@ class ProviderFactory {
|
|
|
23
24
|
dashscope: dashscopeProvider,
|
|
24
25
|
xai: xaiProvider,
|
|
25
26
|
openai: openaiProvider,
|
|
27
|
+
anthropic: anthropicProvider,
|
|
26
28
|
deepseek: deepseekProvider,
|
|
27
29
|
gemini: geminiProvider,
|
|
28
30
|
};
|
|
@@ -100,6 +102,7 @@ class ProviderFactory {
|
|
|
100
102
|
dashscope: process.env.DASHSCOPE_API_KEY,
|
|
101
103
|
xai: process.env.XAI_API_KEY,
|
|
102
104
|
openai: process.env.OPENAI_API_KEY,
|
|
105
|
+
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
103
106
|
deepseek: process.env.DEEPSEEK_API_KEY,
|
|
104
107
|
gemini: process.env.GEMINI_API_KEY,
|
|
105
108
|
};
|
|
@@ -129,6 +132,7 @@ class ProviderFactory {
|
|
|
129
132
|
dashscope: "DASHSCOPE_API_KEY",
|
|
130
133
|
xai: "XAI_API_KEY",
|
|
131
134
|
openai: "OPENAI_API_KEY",
|
|
135
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
132
136
|
deepseek: "DEEPSEEK_API_KEY",
|
|
133
137
|
gemini: "GEMINI_API_KEY",
|
|
134
138
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import BaseProvider, { ProviderConfig, TranslateOptions } from "./base-provider.js";
|
|
2
|
+
/**
|
|
3
|
+
* Provider implementation for Anthropic (Claude) models.
|
|
4
|
+
*/
|
|
5
|
+
declare class AnthropicProvider extends BaseProvider {
|
|
6
|
+
private client;
|
|
7
|
+
/**
|
|
8
|
+
* Create a new AnthropicProvider instance.
|
|
9
|
+
* @param {ProviderConfig} config - Provider configuration.
|
|
10
|
+
*/
|
|
11
|
+
constructor(config?: ProviderConfig);
|
|
12
|
+
getApiKey(): string | undefined;
|
|
13
|
+
getEndpoint(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Translate text using Anthropic.
|
|
16
|
+
*/
|
|
17
|
+
translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
18
|
+
analyze(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
19
|
+
chat(messages: any[], options?: ProviderConfig): Promise<string>;
|
|
20
|
+
extractTranslation(response: any, providerName: string): string;
|
|
21
|
+
}
|
|
22
|
+
declare function translate(text: string, sourceLang: string, targetLang: string, options?: TranslateOptions): Promise<string>;
|
|
23
|
+
declare function analyze(prompt: string, options?: ProviderConfig): Promise<string>;
|
|
24
|
+
declare function chat(messages: any[], options?: ProviderConfig): Promise<string>;
|
|
25
|
+
export { translate, analyze, chat, AnthropicProvider };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import BaseProvider from "./base-provider.js";
|
|
3
|
+
import { getPrompt, getAnalysisPrompt } from "../utils/prompt-templates.js";
|
|
4
|
+
import RetryHelper from "../utils/retry-helper.js";
|
|
5
|
+
/**
|
|
6
|
+
* Provider implementation for Anthropic (Claude) models.
|
|
7
|
+
*/
|
|
8
|
+
class AnthropicProvider extends BaseProvider {
|
|
9
|
+
client;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new AnthropicProvider instance.
|
|
12
|
+
* @param {ProviderConfig} config - Provider configuration.
|
|
13
|
+
*/
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
super("anthropic", config);
|
|
16
|
+
this.client = axios.create({
|
|
17
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
18
|
+
headers: {
|
|
19
|
+
...this.commonHeaders,
|
|
20
|
+
"x-api-key": this.getApiKey(),
|
|
21
|
+
"anthropic-version": "2023-06-01",
|
|
22
|
+
},
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
maxRedirects: 0,
|
|
25
|
+
validateStatus: (status) => status < 500,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
getApiKey() {
|
|
29
|
+
return process.env.ANTHROPIC_API_KEY;
|
|
30
|
+
}
|
|
31
|
+
getEndpoint() {
|
|
32
|
+
return "/messages";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Translate text using Anthropic.
|
|
36
|
+
*/
|
|
37
|
+
async translate(text, sourceLang, targetLang, options = {}) {
|
|
38
|
+
this.validateRequest(text, sourceLang, targetLang);
|
|
39
|
+
// Prioritize user config > options config > default model
|
|
40
|
+
// Anthropic models: claude-3-5-sonnet-latest, claude-3-5-haiku-latest, claude-3-opus-latest
|
|
41
|
+
const config = this.getConfig(options.apiConfig?.anthropic);
|
|
42
|
+
const model = config.model || "claude-haiku-4-5-20251001";
|
|
43
|
+
const promptData = getPrompt("anthropic", sourceLang, targetLang, text, options);
|
|
44
|
+
return RetryHelper.withRetry(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
47
|
+
model: model,
|
|
48
|
+
...promptData, // Contains { system: "...", messages: [...] }
|
|
49
|
+
max_tokens: config.maxTokens || 4096,
|
|
50
|
+
temperature: config.temperature,
|
|
51
|
+
});
|
|
52
|
+
this.validateResponse(response, this.name);
|
|
53
|
+
const translation = this.extractTranslation(response.data, this.name);
|
|
54
|
+
return this.sanitizeTranslation(translation);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.handleApiError(error, this.name);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}, {
|
|
61
|
+
maxRetries: options.retryOptions?.maxRetries || 2,
|
|
62
|
+
initialDelay: options.retryOptions?.initialDelay || 1000,
|
|
63
|
+
context: "Anthropic Provider",
|
|
64
|
+
logContext: {
|
|
65
|
+
source: sourceLang,
|
|
66
|
+
target: targetLang,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async analyze(prompt, options = {}) {
|
|
71
|
+
const config = this.getConfig({
|
|
72
|
+
model: options.model || "claude-3-5-sonnet-latest",
|
|
73
|
+
temperature: options.temperature || 0.2,
|
|
74
|
+
maxTokens: options.maxTokens || 1000,
|
|
75
|
+
});
|
|
76
|
+
const promptData = getAnalysisPrompt("anthropic", prompt, options);
|
|
77
|
+
return RetryHelper.withRetry(async () => {
|
|
78
|
+
try {
|
|
79
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
80
|
+
model: config.model,
|
|
81
|
+
...promptData,
|
|
82
|
+
max_tokens: config.maxTokens,
|
|
83
|
+
temperature: config.temperature,
|
|
84
|
+
});
|
|
85
|
+
this.validateResponse(response, this.name);
|
|
86
|
+
const result = this.extractTranslation(response.data, this.name);
|
|
87
|
+
return this.sanitizeTranslation(result);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.handleApiError(error, this.name);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}, {
|
|
94
|
+
maxRetries: options.maxRetries || 2,
|
|
95
|
+
initialDelay: options.initialDelay || 1000,
|
|
96
|
+
context: "Anthropic Provider Analysis",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async chat(messages, options = {}) {
|
|
100
|
+
const config = this.getConfig({
|
|
101
|
+
model: options.model || "claude-3-5-sonnet-latest",
|
|
102
|
+
temperature: options.temperature || 0.3,
|
|
103
|
+
maxTokens: options.maxTokens || 4096,
|
|
104
|
+
});
|
|
105
|
+
// Anthropic requires system prompt to be top-level, separate from messages array
|
|
106
|
+
// Note: This simple implementation assumes messages passed in might be mixed.
|
|
107
|
+
// Ideally, the caller should separate system prompt, but we can do a quick check here.
|
|
108
|
+
let systemPrompt = undefined;
|
|
109
|
+
const cleanMessages = messages.filter((msg) => {
|
|
110
|
+
if (msg.role === "system") {
|
|
111
|
+
systemPrompt = msg.content;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
return RetryHelper.withRetry(async () => {
|
|
117
|
+
try {
|
|
118
|
+
const payload = {
|
|
119
|
+
model: config.model,
|
|
120
|
+
messages: cleanMessages,
|
|
121
|
+
max_tokens: config.maxTokens,
|
|
122
|
+
temperature: config.temperature,
|
|
123
|
+
};
|
|
124
|
+
if (systemPrompt) {
|
|
125
|
+
payload.system = systemPrompt;
|
|
126
|
+
}
|
|
127
|
+
const response = await this.client.post(this.getEndpoint(), payload);
|
|
128
|
+
this.validateResponse(response, this.name);
|
|
129
|
+
const result = this.extractTranslation(response.data, this.name);
|
|
130
|
+
return this.sanitizeTranslation(result);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
this.handleApiError(error, this.name);
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}, {
|
|
137
|
+
maxRetries: options.maxRetries || 2,
|
|
138
|
+
initialDelay: options.initialDelay || 1000,
|
|
139
|
+
context: "Anthropic Provider Chat",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Override extractTranslation to handle Anthropic's specific response structure
|
|
143
|
+
extractTranslation(response, providerName) {
|
|
144
|
+
// Check for error response first
|
|
145
|
+
if (response.error) {
|
|
146
|
+
const errorMsg = response.error.message || response.error.error || JSON.stringify(response.error);
|
|
147
|
+
throw new Error(`api: ${providerName} - ${errorMsg}`);
|
|
148
|
+
}
|
|
149
|
+
// Anthropic format: { content: [{ type: 'text', text: '...' }] }
|
|
150
|
+
if (response.content && Array.isArray(response.content) && response.content.length > 0) {
|
|
151
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
152
|
+
if (textBlock && textBlock.text) {
|
|
153
|
+
return textBlock.text.trim();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Fallback to base implementation for other structures (though unlikely for Anthropic)
|
|
157
|
+
return super.extractTranslation(response, providerName);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Lazy singleton - created on first use
|
|
161
|
+
let anthropicProvider = null;
|
|
162
|
+
function getProvider() {
|
|
163
|
+
if (!anthropicProvider) {
|
|
164
|
+
anthropicProvider = new AnthropicProvider();
|
|
165
|
+
}
|
|
166
|
+
return anthropicProvider;
|
|
167
|
+
}
|
|
168
|
+
// Export both class and legacy functions
|
|
169
|
+
async function translate(text, sourceLang, targetLang, options = {}) {
|
|
170
|
+
return getProvider().translate(text, sourceLang, targetLang, options);
|
|
171
|
+
}
|
|
172
|
+
async function analyze(prompt, options = {}) {
|
|
173
|
+
return getProvider().analyze(prompt, options);
|
|
174
|
+
}
|
|
175
|
+
async function chat(messages, options = {}) {
|
|
176
|
+
return getProvider().chat(messages, options);
|
|
177
|
+
}
|
|
178
|
+
export { translate, analyze, chat, AnthropicProvider };
|
package/dist/providers/gemini.js
CHANGED
|
@@ -27,7 +27,7 @@ class GeminiProvider extends BaseProvider {
|
|
|
27
27
|
return process.env.GEMINI_API_KEY;
|
|
28
28
|
}
|
|
29
29
|
getEndpoint(model) {
|
|
30
|
-
return `/models/${model || "gemini-
|
|
30
|
+
return `/models/${model || "gemini-3-flash"}:generateContent`;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Translate text using Gemini.
|
|
@@ -35,7 +35,7 @@ class GeminiProvider extends BaseProvider {
|
|
|
35
35
|
async translate(text, sourceLang, targetLang, options = {}) {
|
|
36
36
|
this.validateRequest(text, sourceLang, targetLang);
|
|
37
37
|
const config = this.getConfig(options.apiConfig?.gemini);
|
|
38
|
-
const model = config.model || "gemini-
|
|
38
|
+
const model = config.model || "gemini-3-flash";
|
|
39
39
|
const apiKey = this.getApiKey();
|
|
40
40
|
if (!apiKey) {
|
|
41
41
|
throw new Error("GEMINI_API_KEY environment variable not found");
|
|
@@ -77,7 +77,7 @@ class GeminiProvider extends BaseProvider {
|
|
|
77
77
|
}
|
|
78
78
|
async analyze(prompt, options = {}) {
|
|
79
79
|
const config = this.getConfig({
|
|
80
|
-
model: options.model || "gemini-
|
|
80
|
+
model: options.model || "gemini-3-flash",
|
|
81
81
|
temperature: options.temperature || 0.2,
|
|
82
82
|
maxTokens: options.maxTokens || 1000,
|
|
83
83
|
});
|
package/dist/providers/openai.js
CHANGED
|
@@ -40,7 +40,7 @@ class OpenAIProvider extends BaseProvider {
|
|
|
40
40
|
return RetryHelper.withRetry(async () => {
|
|
41
41
|
try {
|
|
42
42
|
const response = await this.client.post(this.getEndpoint(), {
|
|
43
|
-
model: config.model || "gpt-
|
|
43
|
+
model: config.model || "gpt-5.2-chat-latest",
|
|
44
44
|
...promptData,
|
|
45
45
|
temperature: config.temperature,
|
|
46
46
|
max_completion_tokens: config.maxTokens || config.max_tokens,
|
|
@@ -65,7 +65,7 @@ class OpenAIProvider extends BaseProvider {
|
|
|
65
65
|
}
|
|
66
66
|
async analyze(prompt, options = {}) {
|
|
67
67
|
const config = this.getConfig({
|
|
68
|
-
model: options.model || "gpt-
|
|
68
|
+
model: options.model || "gpt-5.2-chat-latest",
|
|
69
69
|
temperature: options.temperature || 0.2,
|
|
70
70
|
maxTokens: options.maxTokens || 1000,
|
|
71
71
|
});
|
|
@@ -94,7 +94,7 @@ class OpenAIProvider extends BaseProvider {
|
|
|
94
94
|
}
|
|
95
95
|
async chat(messages, options = {}) {
|
|
96
96
|
const config = this.getConfig({
|
|
97
|
-
model: options.model || "gpt-
|
|
97
|
+
model: options.model || "gpt-5.2-chat-latest",
|
|
98
98
|
temperature: options.temperature || 0.3,
|
|
99
99
|
maxTokens: options.maxTokens || 2000,
|
|
100
100
|
});
|
|
@@ -321,6 +321,7 @@ class ErrorHelper {
|
|
|
321
321
|
deepseek: "DEEPSEEK_API_KEY",
|
|
322
322
|
dashscope: "DASHSCOPE_API_KEY",
|
|
323
323
|
xai: "XAI_API_KEY",
|
|
324
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
324
325
|
};
|
|
325
326
|
return envMap[provider?.toLowerCase()] || `${provider?.toUpperCase()}_API_KEY`;
|
|
326
327
|
}
|
|
@@ -334,6 +335,7 @@ class ErrorHelper {
|
|
|
334
335
|
deepseek: "https://platform.deepseek.com/api-keys",
|
|
335
336
|
dashscope: "https://dashscope.console.aliyun.com/",
|
|
336
337
|
xai: "https://console.x.ai/",
|
|
338
|
+
anthropic: "https://console.anthropic.com/settings/keys",
|
|
337
339
|
};
|
|
338
340
|
return urlMap[provider?.toLowerCase()] || "provider console";
|
|
339
341
|
}
|
|
@@ -341,7 +343,7 @@ class ErrorHelper {
|
|
|
341
343
|
* Suggest fallback provider
|
|
342
344
|
*/
|
|
343
345
|
static _suggestFallbackProvider(currentProvider) {
|
|
344
|
-
const fallbackOrder = ["openai", "gemini", "deepseek", "dashscope", "xai"];
|
|
346
|
+
const fallbackOrder = ["openai", "anthropic", "gemini", "deepseek", "dashscope", "xai"];
|
|
345
347
|
const current = currentProvider?.toLowerCase();
|
|
346
348
|
const index = fallbackOrder.indexOf(current);
|
|
347
349
|
if (index === -1 || index === fallbackOrder.length - 1) {
|
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
class InputValidator {
|
|
7
7
|
static LANGUAGE_CODE_PATTERN = /^[a-z]{2}(-[a-z]{2})?$/;
|
|
8
|
-
static VALID_PROVIDERS = [
|
|
8
|
+
static VALID_PROVIDERS = [
|
|
9
|
+
"dashscope",
|
|
10
|
+
"xai",
|
|
11
|
+
"openai",
|
|
12
|
+
"deepseek",
|
|
13
|
+
"gemini",
|
|
14
|
+
"anthropic",
|
|
15
|
+
];
|
|
9
16
|
static MAX_TEXT_LENGTH = 10000;
|
|
10
17
|
static MAX_KEY_LENGTH = 500;
|
|
11
18
|
static MAX_PATH_LENGTH = 1000;
|
|
@@ -236,6 +236,15 @@ Original Text: "${text}"`,
|
|
|
236
236
|
},
|
|
237
237
|
],
|
|
238
238
|
}),
|
|
239
|
+
anthropic: (sourceLang, targetLang, text, options) => ({
|
|
240
|
+
system: baseTranslationPromptTemplate(sourceLang, targetLang, text, options),
|
|
241
|
+
messages: [
|
|
242
|
+
{
|
|
243
|
+
role: "user",
|
|
244
|
+
content: text,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
}),
|
|
239
248
|
deepseek: (sourceLang, targetLang, text, options) => ({
|
|
240
249
|
messages: [
|
|
241
250
|
{
|
|
@@ -317,6 +326,15 @@ const analysisPrompts = {
|
|
|
317
326
|
max_tokens: options.maxTokens || 1000,
|
|
318
327
|
},
|
|
319
328
|
}),
|
|
329
|
+
anthropic: (text, options) => ({
|
|
330
|
+
system: "You are a context analysis assistant that helps identify the category and context of text.",
|
|
331
|
+
messages: [
|
|
332
|
+
{
|
|
333
|
+
role: "user",
|
|
334
|
+
content: baseAnalysisPromptTemplate(text, options),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
}),
|
|
320
338
|
deepseek: (text, options) => ({
|
|
321
339
|
messages: [
|
|
322
340
|
{
|
|
@@ -59,6 +59,12 @@ class RateLimiter {
|
|
|
59
59
|
lastReset: performance.now(),
|
|
60
60
|
maxConcurrent: 8,
|
|
61
61
|
},
|
|
62
|
+
anthropic: {
|
|
63
|
+
requestsPerMinute: 50,
|
|
64
|
+
currentRequests: 0,
|
|
65
|
+
lastReset: performance.now(),
|
|
66
|
+
maxConcurrent: 5,
|
|
67
|
+
},
|
|
62
68
|
};
|
|
63
69
|
this._processingLocks = {};
|
|
64
70
|
Object.keys(this.providers).forEach((provider) => {
|