Skip to content

Commit a230d53

Browse files
committed
feat: migrate to OpenAI Responses API
Replace Chat Completions API with Responses API across all adapters: - Update endpoint paths to use `/responses` instead of `/chat/completions` - Simplify request/response handling with new API format - Update model defaults to GPT-5 series (`gpt-5-mini`, `gpt-5-nano`) - Add formatting instructions to prevent quotation marks in translations - Improve error handling and stream processing - Update documentation for Azure OpenAI and OpenAI Compatible endpoints
1 parent 7a928ec commit a230d53

File tree

13 files changed

+331
-272
lines changed

13 files changed

+331
-272
lines changed

docs/configuration_manual_CN.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,27 @@
3131
https://api.openai.com
3232
```
3333
34-
- OpenAI Compatible:必填,需填入完整的 API URL,例如使用 Cloudflare AI Gateway 时,填入
34+
- OpenAI Compatible:必填,需填入完整的 API URL。目前仅支持 Responses API 端点(以 `/responses` 结尾)
3535
3636
```
37-
https://gateway.ai.cloudflare.com/v1/CLOUDFLARE_ACCOUNT_ID/GATEWAY_ID/openai/chat/completions
37+
https://gateway.ai.cloudflare.com/v1/CLOUDFLARE_ACCOUNT_ID/GATEWAY_ID/openai/responses
3838
```
3939
40-
- Azure OpenAI:必填,需填入完整的 API URL,格式为:
40+
注:本插件目前仅支持使用 Responses API 的 OpenAI 兼容服务
4141
42+
- Azure OpenAI:必填,需填入完整的 API URL。目前仅支持 Responses API,支持以下两种格式:
43+
44+
带部署名称:
45+
```
46+
https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/responses?api-version=preview
4247
```
43-
https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/chat/completions?api-version=API_VERSION
48+
49+
基础端点(模型在请求体中指定):
4450
```
51+
https://RESOURCE_NAME.openai.azure.com/openai/v1/responses?api-version=preview
52+
```
53+
54+
注:本插件目前仅支持 Azure OpenAI 的 Responses API,需使用 preview API 版本
4555
4656
- Google Gemini:可选,默认为:
4757

docs/configuration_manual_EN.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,27 @@
3030
https://api.openai.com
3131
```
3232
33-
- OpenAI Compatible: Required, complete API URL, for example when using Cloudflare AI Gateway:
33+
- OpenAI Compatible: Required, complete API URL. Currently only supports Responses API endpoint (ending with `/responses`):
3434
3535
```
36-
https://gateway.ai.cloudflare.com/v1/CLOUDFLARE_ACCOUNT_ID/GATEWAY_ID/openai/chat/completions
36+
https://gateway.ai.cloudflare.com/v1/CLOUDFLARE_ACCOUNT_ID/GATEWAY_ID/openai/responses
3737
```
3838
39-
- Azure OpenAI: Required, complete API URL in format:
39+
Note: This plugin currently only supports OpenAI compatible services that use the Responses API
4040
41+
- Azure OpenAI: Required, complete API URL. Currently only supports Responses API with the following two formats:
42+
43+
With deployment name:
44+
```
45+
https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/responses?api-version=preview
4146
```
42-
https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/chat/completions?api-version=API_VERSION
47+
48+
Base endpoint (model specified in request body):
4349
```
50+
https://RESOURCE_NAME.openai.azure.com/openai/v1/responses?api-version=preview
51+
```
52+
53+
Note: This plugin currently only supports Azure OpenAI's Responses API with preview API version
4454
4555
- Google Gemini: Optional, default value:
4656

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
],
1313
"homepage": "https://github.com/openai-translator/bob-plugin-openai-translator",
1414
"scripts": {
15-
"build": "rollup --config rollup.config.ts --configPlugin typescript"
15+
"build": "rollup --config rollup.config.ts --configPlugin typescript",
16+
"build:test": "node scripts/build-test.mjs"
1617
},
1718
"devDependencies": {
1819
"@bob-translate/types": "1.1.0",

public/info.json

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"type": "menu"
3434
},
3535
{
36-
"desc": "OpenAI: https://api.openai.com\n\nOpenAI Compatible: 完整的 API 地址,例如:https://gateway.ai.cloudflare.com/v1/CF_ACCOUNT_ID/GATEWAY_ID/openai/chat/completions\n\nAzure OpenAI: https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/chat/completions?api-version=API_VERSION\n\nGoogle Gemini: https://generativelanguage.googleapis.com/v1beta/models",
36+
"desc": "OpenAI: https://api.openai.com\n\nOpenAI Compatible: 完整的 API 地址,例如:https://gateway.ai.cloudflare.com/v1/CF_ACCOUNT_ID/GATEWAY_ID/openai/responses\n\nAzure OpenAI: https://RESOURCE_NAME.openai.azure.com/openai/deployments/DEPLOYMENT_NAME/responses?api-version=preview\n\nGoogle Gemini: https://generativelanguage.googleapis.com/v1beta/models",
3737
"identifier": "apiUrl",
3838
"textConfig": {
3939
"height": "55",
@@ -55,40 +55,32 @@
5555
"type": "text"
5656
},
5757
{
58-
"defaultValue": "gpt-4.1-nano",
58+
"defaultValue": "gpt-5-mini",
5959
"identifier": "model",
6060
"menuValues": [
6161
{
6262
"title": "Custom",
6363
"value": "custom"
6464
},
6565
{
66-
"title": "GPT-4.1",
67-
"value": "gpt-4.1"
66+
"title": "GPT-5 mini",
67+
"value": "gpt-5-mini"
6868
},
6969
{
70-
"title": "GPT-4.1 mini",
71-
"value": "gpt-4.1-mini"
72-
},
73-
{
74-
"title": "GPT-4.1 nano",
75-
"value": "gpt-4.1-nano"
70+
"title": "GPT-5 nano",
71+
"value": "gpt-5-nano"
7672
},
7773
{
78-
"title": "GPT-4o",
79-
"value": "gpt-4o"
80-
},
81-
{
82-
"title": "o4-mini",
83-
"value": "o4-mini"
74+
"title": "GPT-4.1 mini",
75+
"value": "gpt-4.1-mini"
8476
},
8577
{
86-
"title": "Gemini 2.5 Pro Preview 03-25",
87-
"value": "gemini-2.5-pro-preview-03-25"
78+
"title": "Gemini 2.5 Flash",
79+
"value": "gemini-2.5-flash"
8880
},
8981
{
90-
"title": "Gemini 2.5 Flash Preview 04-17",
91-
"value": "gemini-2.5-flash-preview-04-17"
82+
"title": "Gemini 2.5 Flash-Lite",
83+
"value": "gemini-2.5-flash-lite"
9284
}
9385
],
9486
"title": "模型",
@@ -98,7 +90,7 @@
9890
"desc": "可选项。当 Model 选择 Custom 时,此项为必填项。请填写有效的模型名称",
9991
"identifier": "customModel",
10092
"textConfig": {
101-
"placeholderText": "gpt-4.1-nano",
93+
"placeholderText": "gpt-5-nano",
10294
"type": "visible"
10395
},
10496
"title": "自定义模型",
@@ -156,7 +148,7 @@
156148
},
157149
{
158150
"defaultValue": "0.2",
159-
"desc": "可选项。温度值越高,生成的文本越随机。默认值为 0.2",
151+
"desc": "可选项。温度值越高,生成的文本越随机。默认值为 0.2。GPT-5 系列模型必须设置为 1",
160152
"identifier": "temperature",
161153
"textConfig": {
162154
"placeholderText": "0.2",
@@ -168,4 +160,4 @@
168160
],
169161
"summary": "AI powered translator",
170162
"version": "3.3.2"
171-
}
163+
}

scripts/build-test.mjs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env node
2+
3+
import fs from 'fs';
4+
import path from 'path';
5+
import { execSync } from 'child_process';
6+
import { fileURLToPath } from 'url';
7+
8+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
9+
const infoPath = path.join(__dirname, '..', 'public', 'info.json');
10+
const distPath = path.join(__dirname, '..', 'dist');
11+
12+
// Read and save original version
13+
const originalInfo = JSON.parse(fs.readFileSync(infoPath, 'utf8'));
14+
const originalVersion = originalInfo.version;
15+
16+
console.log(`Original version: ${originalVersion}`);
17+
18+
try {
19+
// Update version to 9.9.9
20+
const testInfo = { ...originalInfo, version: '9.9.9' };
21+
fs.writeFileSync(infoPath, JSON.stringify(testInfo, null, 2) + '\n');
22+
console.log('Version updated to 9.9.9 for testing');
23+
24+
// Build the plugin
25+
console.log('Building plugin...');
26+
execSync('pnpm build', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
27+
28+
// Create test package
29+
console.log('Creating test package...');
30+
execSync('zip -j -r openai-translator-test.bobplugin ./*', {
31+
stdio: 'inherit',
32+
cwd: distPath
33+
});
34+
35+
console.log('Test build complete!');
36+
37+
// Open dist folder
38+
console.log('Opening dist folder...');
39+
execSync(`open ${distPath}`);
40+
41+
} finally {
42+
// Always restore original version
43+
const currentInfo = JSON.parse(fs.readFileSync(infoPath, 'utf8'));
44+
currentInfo.version = originalVersion;
45+
fs.writeFileSync(infoPath, JSON.stringify(currentInfo, null, 2) + '\n');
46+
console.log(`Version restored to ${originalVersion}`);
47+
}

src/adapter/azure-openai.ts

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ServiceError, ValidationCompletion } from "@bob-translate/types";
22
import { handleValidateError } from "../utils";
3-
import { OpenAiChatCompletion } from "../types";
43
import { OpenAiAdapter } from "./openai";
54

65
export class AzureOpenAiAdapter extends OpenAiAdapter {
@@ -22,25 +21,13 @@ export class AzureOpenAiAdapter extends OpenAiAdapter {
2221
}
2322

2423
protected override extractErrorFromResponse(response: any): ServiceError {
25-
const errorData = response.data?.error;
26-
if (errorData) {
27-
const isAuthError = errorData.code === "401" ||
28-
errorData.code === "403" ||
29-
errorData.message?.toLowerCase().includes("key") ||
30-
errorData.message?.toLowerCase().includes("auth");
31-
32-
return {
33-
type: isAuthError ? "secretKey" : "api",
34-
message: errorData.message || "Unknown Azure OpenAI API error",
35-
addition: errorData.code,
36-
troubleshootingLink: this.config.troubleshootingLink
37-
};
24+
const result = super.extractErrorFromResponse(response);
25+
// Azure uses 403 for auth errors too
26+
if (response.response?.statusCode === 403) {
27+
result.type = "secretKey";
3828
}
39-
4029
return {
41-
type: "api",
42-
message: "Azure OpenAI API error",
43-
addition: JSON.stringify(response.data),
30+
...result,
4431
troubleshootingLink: this.config.troubleshootingLink
4532
};
4633
}
@@ -53,29 +40,28 @@ export class AzureOpenAiAdapter extends OpenAiAdapter {
5340
const header = this.buildHeaders(apiKey);
5441

5542
try {
43+
// Extract model from URL if it's in deployment format
44+
// Format: /openai/deployments/{deployment}/responses?api-version=preview
45+
const deploymentMatch = apiUrl.match(/\/deployments\/([^\/]+)\/responses/);
46+
const model = deploymentMatch ? deploymentMatch[1] : "gpt-5-nano";
47+
5648
const response = await $http.request({
5749
method: "POST",
5850
url: apiUrl,
5951
header,
6052
body: {
61-
messages: [{
62-
content: "You are a helpful assistant.",
63-
role: "system",
64-
}, {
65-
content: "Test connection.",
66-
role: "user",
67-
}],
68-
max_tokens: 5
69-
}
53+
model: model,
54+
input: "Test connectivity. You ONLY need to reply 'OK'.",
55+
},
7056
});
7157

7258
if (response.data.error) {
73-
handleValidateError(completion, this.extractErrorFromResponse(response));
74-
return;
59+
return handleValidateError(completion, this.extractErrorFromResponse(response));
7560
}
7661

77-
if ((response.data as OpenAiChatCompletion).choices.length > 0) {
78-
completion({ result: true });
62+
// Accept any successful response from Azure OpenAI
63+
if (response.data && !response.data.error) {
64+
return completion({ result: true });
7965
}
8066
} catch (error) {
8167
handleValidateError(completion, error);

src/adapter/base.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { HttpResponse, ServiceError, TextTranslateQuery, ValidationCompletion } from "@bob-translate/types";
22
import { handleGeneralError, convertToServiceError } from "../utils";
3-
import type { GeminiResponse, OpenAiChatCompletion, ServiceAdapter, ServiceAdapterConfig } from "../types";
3+
import type { GeminiResponse, OpenAiResponse, ServiceAdapter, ServiceAdapterConfig } from "../types";
44

55
export abstract class BaseAdapter implements ServiceAdapter {
66
protected constructor(protected readonly config: ServiceAdapterConfig) { }
@@ -25,7 +25,7 @@ export abstract class BaseAdapter implements ServiceAdapter {
2525

2626
abstract handleStream(streamData: { text: string }, query: TextTranslateQuery, targetText: string): string;
2727

28-
abstract parseResponse(response: HttpResponse<GeminiResponse | OpenAiChatCompletion>): string;
28+
abstract parseResponse(response: HttpResponse<GeminiResponse | OpenAiResponse>): string;
2929

3030
abstract testApiConnection(apiKey: string, apiUrl: string, completion: ValidationCompletion): Promise<void>;
3131

@@ -96,6 +96,8 @@ export abstract class BaseAdapter implements ServiceAdapter {
9696
query: TextTranslateQuery,
9797
): Promise<void> {
9898
let targetText = "";
99+
let streamError: ServiceError | null = null;
100+
99101
await $http.streamRequest({
100102
method: "POST",
101103
url,
@@ -112,14 +114,24 @@ export abstract class BaseAdapter implements ServiceAdapter {
112114
return;
113115
}
114116

115-
targetText = this.handleStream(
116-
{ text: streamData.text },
117-
query,
118-
targetText
119-
);
117+
try {
118+
targetText = this.handleStream(
119+
{ text: streamData.text },
120+
query,
121+
targetText
122+
);
123+
} catch (error) {
124+
// Save the error to be handled later
125+
if (error && typeof error === 'object' && 'message' in error) {
126+
streamError = error as ServiceError;
127+
}
128+
}
120129
},
121130
handler: (result) => {
122-
if (result.response.statusCode >= 400) {
131+
// If we caught an error from the stream, use that
132+
if (streamError) {
133+
handleGeneralError(query, streamError);
134+
} else if (result.response.statusCode >= 400) {
123135
handleGeneralError(query, this.extractErrorFromResponse(result));
124136
} else {
125137
this.handleStreamCompletion(query, targetText);

src/adapter/gemini.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HttpResponse, ServiceError, TextTranslateQuery, ValidationCompletion } from "@bob-translate/types";
2-
import type { OpenAiChatCompletion, GeminiResponse } from "../types";
2+
import type { OpenAiResponse, GeminiResponse } from "../types";
33
import { generatePrompts, handleValidateError } from "../utils";
44
import { BaseAdapter } from "./base";
55

@@ -70,7 +70,7 @@ export class GeminiAdapter extends BaseAdapter {
7070
return this.isStreamEnabled() ? `${baseUrl}?alt=sse` : baseUrl;
7171
}
7272

73-
public parseResponse(response: HttpResponse<GeminiResponse | OpenAiChatCompletion>): string {
73+
public parseResponse(response: HttpResponse<GeminiResponse | OpenAiResponse>): string {
7474
const { data } = response;
7575
if (typeof data === 'object' && 'candidates' in data) {
7676
if (!data?.candidates?.[0]?.content?.parts?.[0]?.text) {

src/adapter/openai-compatible.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { OpenAiAdapter } from "./openai";
22

33
export class OpenAiCompatibleAdapter extends OpenAiAdapter {
44
public override getTextGenerationUrl(apiUrl: string): string {
5+
if (!apiUrl.endsWith('/responses')) {
6+
throw new Error('API URL must end with /responses for OpenAI Compatible services');
7+
}
58
return apiUrl;
69
}
710

811
protected override getValidationUrl(apiUrl: string): string {
9-
return apiUrl.replace(/\/chat\/completions$/, '/models');
12+
return apiUrl.replace(/\/responses$/, '/models');
1013
}
1114
}

0 commit comments

Comments
 (0)