feat: add Claude API bridge - transparent OpenAI-to-Anthropic protocol conversion

When provider is set to "claude" in config, all OpenAI-compatible API calls
are automatically bridged to Anthropic Claude Messages API, including:

- Non-streaming and streaming chat completions
- Tool calls (function calling) with full bidirectional conversion
- Eino multi-agent via HTTP transport hook (claudeRoundTripper)
- System message extraction, auth header conversion (Bearer → x-api-key)
- SSE stream format conversion (content_block_delta → OpenAI delta)
- TestOpenAI handler support for Claude connectivity testing

Zero impact when provider is "openai" or empty (default behavior unchanged).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
donnel
2026-04-16 09:54:37 +08:00
parent 715240dc5e
commit 4442e7de30
8 changed files with 1140 additions and 56 deletions
+8
View File
@@ -102,6 +102,10 @@ async function loadConfig(loadTools = true) {
currentConfig = await response.json();
// 填充OpenAI配置
const providerEl = document.getElementById('openai-provider');
if (providerEl) {
providerEl.value = currentConfig.openai.provider || 'openai';
}
document.getElementById('openai-api-key').value = currentConfig.openai.api_key || '';
document.getElementById('openai-base-url').value = currentConfig.openai.base_url || '';
document.getElementById('openai-model').value = currentConfig.openai.model || '';
@@ -753,6 +757,7 @@ async function applySettings() {
});
// 验证必填字段
const provider = document.getElementById('openai-provider')?.value || 'openai';
const apiKey = document.getElementById('openai-api-key').value.trim();
const baseUrl = document.getElementById('openai-base-url').value.trim();
const model = document.getElementById('openai-model').value.trim();
@@ -821,6 +826,7 @@ async function applySettings() {
const wecomAgentIdVal = document.getElementById('robot-wecom-agent-id')?.value.trim();
const config = {
openai: {
provider: provider,
api_key: apiKey,
base_url: baseUrl,
model: model
@@ -981,6 +987,7 @@ async function testOpenAIConnection() {
const btn = document.getElementById('test-openai-btn');
const resultEl = document.getElementById('test-openai-result');
const provider = document.getElementById('openai-provider')?.value || 'openai';
const baseUrl = document.getElementById('openai-base-url').value.trim();
const apiKey = document.getElementById('openai-api-key').value.trim();
const model = document.getElementById('openai-model').value.trim();
@@ -1001,6 +1008,7 @@ async function testOpenAIConnection() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: provider,
base_url: baseUrl,
api_key: apiKey,
model: model
+7
View File
@@ -1365,6 +1365,13 @@
<div class="settings-subsection">
<h4 data-i18n="settingsBasic.openaiConfig">OpenAI 配置</h4>
<div class="settings-form">
<div class="form-group">
<label for="openai-provider">API 提供商</label>
<select id="openai-provider" style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 6px; background: var(--card-bg, #fff); color: var(--text-color, #2d3748); font-size: 0.875rem;">
<option value="openai">OpenAI / 兼容 OpenAI 协议</option>
<option value="claude">Claude (Anthropic Messages API)</option>
</select>
</div>
<div class="form-group">
<label for="openai-base-url">Base URL <span style="color: red;">*</span></label>
<input type="text" id="openai-base-url" data-i18n="settingsBasic.openaiBaseUrlPlaceholder" data-i18n-attr="placeholder" placeholder="https://api.openai.com/v1" required />