From d36a9466a8782eba7279fa04bb866610e4baa72c Mon Sep 17 00:00:00 2001 From: hex2077 Date: Wed, 15 Oct 2025 22:05:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=B5=8B=E8=AF=95=E5=8F=AF?= =?UTF-8?q?=E7=94=A8=E6=80=A7=EF=BC=8C=E6=9A=82=E6=97=B6=E5=9B=9E=E6=BB=9A?= =?UTF-8?q?droid=E4=BE=9B=E5=BA=94=E5=95=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +-- config.json.example | 2 - package-lock.json | 2 +- provider_pools.json.example | 13 -- src/adapter.js | 44 ------ src/common.js | 13 +- src/droid/README.md | 227 ------------------------------- src/droid/droid-core.js | 257 ------------------------------------ src/droid/droid-strategy.js | 74 ----------- src/provider-strategies.js | 3 - test-droid.js | 59 --------- 11 files changed, 5 insertions(+), 710 deletions(-) delete mode 100644 src/droid/README.md delete mode 100644 src/droid/droid-core.js delete mode 100644 src/droid/droid-strategy.js delete mode 100644 test-droid.js diff --git a/README.md b/README.md index 9ea427a..c0e57e2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ ## 💡 核心优势 -* ✅ **多模型统一接入**:通过统一的 OpenAI 兼容接口,轻松接入 Gemini、OpenAI、Claude、Factory Droid、Kimi K2、GLM-4.5、Qwen Code 等多种主流大模型,并通过启动参数或请求头自由切换。 +* ✅ **多模型统一接入**:通过统一的 OpenAI 兼容接口,轻松接入 Gemini、OpenAI、Claude、Kimi K2、GLM-4.5、Qwen Code 等多种主流大模型,并通过启动参数或请求头自由切换。 * ✅ **突破官方限制**:利用 Gemini CLI 的 OAuth 授权,有效规避官方免费 API 的速率和配额限制,提升请求额度和使用频率。 * ✅ **免费使用 Claude Sonnet 4.5**:在 Kiro API 模式下,支持免费使用 Claude Sonnet 4.5 模型。 * ✅ **无缝兼容 OpenAI**:提供与 OpenAI API 完全兼容的接口,使 LobeChat, NextChat 等现有工具链和客户端能零成本接入所有支持模型。 @@ -119,11 +119,6 @@ * **Qwen Code 支持**: * **授权流程**:首次使用 Qwen Code 时,会自动在浏览器中打开授权页面。完成授权后,`oauth_creds.json` 文件将生成并存储在 `~/.qwen` 目录下。 * **模型参数**:请使用官方默认参数 `temperature=0` 和 `top_p=1`。 -* **Droid (Factory.ai) 支持**: - * **使用前提**:使用 Droid 需要[安装 Factory CLI](https://factory.ai/product/cli) 并完成认证登录,以生成 `~/.factory/auth.json` 文件。 - * **认证流程**:运行 `droid` 命令并按提示登录,OAuth tokens 将自动保存。 - * **优势**:无需单独的 API key,直接使用 Factory.ai 账号的 Claude 访问权限。 - * **详细文档**:查看 [Droid Provider README](src/droid/README.md) 了解完整配置说明。 * **Kiro API**: * **使用前提**:使用 Kiro API 需要[下载 Kiro 客户端](https://aibook.ren/archives/kiro-install)并完成授权登录,以生成 `kiro-auth-token.json` 文件。 * **最佳体验**:推荐配合 Claude Code 使用以获得最佳体验。 @@ -137,7 +132,6 @@ * `http://localhost:3000/openai-custom` - 使用 OpenAI 自定义供应商处理 Claude 请求。 * `http://localhost:3000/gemini-cli-oauth` - 使用 Gemini CLI OAuth 供应商处理 Claude 请求。 * `http://localhost:3000/openai-qwen-oauth` - 使用 Qwen OAuth 供应商处理 Claude 请求。 - * `http://localhost:3000/droid-factory-oauth` - 使用 Factory.ai Droid OAuth 供应商访问 Claude API。 这些 Path 路由不仅适用于直接 API 调用,也可在 Cline、Kilo 等编程 Agent 中配置 API 端点时使用,实现灵活的模型调用。例如,将 Agent 的 API 端点设置为 `http://localhost:3000/claude-kiro-oauth` 即可调用通过 Kiro OAuth 认证的 Claude 模型。 @@ -179,7 +173,6 @@ * **Gemini**: `~/.gemini/oauth_creds.json` * **Kiro**: `~/.aws/sso/cache/kiro-auth-token.json` * **Qwen**: `~/.qwen/oauth_creds.json` -* **Droid (Factory.ai)**: `~/.factory/auth.json` 其中 `~` 代表用户主目录。如果需要自定义路径,可以通过配置文件或环境变量进行设置。 @@ -263,7 +256,7 @@ $env:HTTP_PROXY="http://your_proxy_address:port" | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| -| `--model-provider` | string | gemini-cli-oauth | AI 模型提供商,可选值:openai-custom, claude-custom, gemini-cli-oauth, claude-kiro-oauth, openai-qwen-oauth, droid-factory-oauth | +| `--model-provider` | string | gemini-cli-oauth | AI 模型提供商,可选值:openai-custom, claude-custom, gemini-cli-oauth, claude-kiro-oauth, openai-qwen-oauth | ### 🧠 OpenAI 兼容提供商参数 @@ -300,13 +293,6 @@ $env:HTTP_PROXY="http://your_proxy_address:port" |------|------|--------|------| | `--qwen-oauth-creds-file` | string | null | Qwen OAuth 凭据 JSON 文件路径 (当 `model-provider` 为 `openai-qwen-oauth` 时必需) | -### 🤖 Droid (Factory.ai) OAuth 认证参数 - -| 参数 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| `--droid-auth-file` | string | ~/.factory/auth.json | Factory Droid OAuth 凭据文件路径 (当 `model-provider` 为 `droid-factory-oauth` 时可选) | -| `--droid-base-url` | string | https://api.anthropic.com | Droid 使用的 Claude API 基础 URL (可选) | - ### 📝 系统提示配置参数 | 参数 | 类型 | 默认值 | 说明 | @@ -362,9 +348,6 @@ node src/api-server.js --model-provider gemini-cli-oauth --gemini-oauth-creds-ba # 使用Gemini提供商(凭据文件) node src/api-server.js --model-provider gemini-cli-oauth --gemini-oauth-creds-file /path/to/credentials.json --project-id your-project-id -# 使用Droid (Factory.ai) 提供商 -node src/api-server.js --model-provider droid-factory-oauth - # 配置系统提示 node src/api-server.js --system-prompt-file custom-prompt.txt --system-prompt-mode append diff --git a/config.json.example b/config.json.example index c1aa668..c032ffa 100644 --- a/config.json.example +++ b/config.json.example @@ -13,8 +13,6 @@ "KIRO_OAUTH_CREDS_BASE64": null, "KIRO_OAUTH_CREDS_FILE_PATH": null, "QWEN_OAUTH_CREDS_FILE_PATH": null, - "DROID_AUTH_FILE": null, - "DROID_COMMAND": "droid", "SYSTEM_PROMPT_FILE_PATH": "input_system_prompt.txt", "SYSTEM_PROMPT_MODE": "overwrite", "PROMPT_LOG_BASE_NAME": "prompt_log", diff --git a/package-lock.json b/package-lock.json index 95af3d8..3779e5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "AIClient-2-API", + "name": "AIClient2API", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/provider_pools.json.example b/provider_pools.json.example index b30dd55..460d804 100644 --- a/provider_pools.json.example +++ b/provider_pools.json.example @@ -104,18 +104,5 @@ "errorCount": 0, "lastErrorTime": null } - ], - "droid-factory-oauth": [ - { - "DROID_AUTH_FILE": "~/.factory/auth.json", - "DROID_COMMAND": "droid", - "uuid": "9d8e7f6a-5c4b-3a2d-1e0f-9a8b7c6d5e4f", - "checkModelName": null, - "isHealthy": true, - "lastUsed": null, - "usageCount": 0, - "errorCount": 0, - "lastErrorTime": null - } ] } \ No newline at end of file diff --git a/src/adapter.js b/src/adapter.js index 15ee613..c295e2a 100644 --- a/src/adapter.js +++ b/src/adapter.js @@ -3,7 +3,6 @@ import { OpenAIApiService } from './openai/openai-core.js'; // 导入OpenAIApiSe import { ClaudeApiService } from './claude/claude-core.js'; // 导入ClaudeApiService import { KiroApiService } from './claude/claude-kiro.js'; // 导入KiroApiService import { QwenApiService } from './openai/qwen-core.js'; // 导入QwenApiService -import { DroidApiService } from './droid/droid-core.js'; // 导入DroidApiService import { MODEL_PROVIDER } from './common.js'; // 导入 MODEL_PROVIDER // 定义AI服务适配器接口 @@ -242,46 +241,6 @@ export class QwenApiServiceAdapter extends ApiServiceAdapter { } } -// Droid (Factory.ai) API 服务适配器 -export class DroidApiServiceAdapter extends ApiServiceAdapter { - constructor(config) { - super(); - this.droidApiService = new DroidApiService(config); - } - - async generateContent(model, requestBody) { - if (!this.droidApiService.isInitialized) { - console.warn("droidApiService not initialized, attempting to re-initialize..."); - await this.droidApiService.initialize(); - } - return this.droidApiService.generateContent(model, requestBody); - } - - async *generateContentStream(model, requestBody) { - if (!this.droidApiService.isInitialized) { - console.warn("droidApiService not initialized, attempting to re-initialize..."); - await this.droidApiService.initialize(); - } - yield* this.droidApiService.generateContentStream(model, requestBody); - } - - async listModels() { - if (!this.droidApiService.isInitialized) { - console.warn("droidApiService not initialized, attempting to re-initialize..."); - await this.droidApiService.initialize(); - } - return this.droidApiService.listModels(); - } - - async refreshToken() { - if (this.droidApiService.isExpiryDateNear()) { - console.log(`[Droid] Token expiry near, refreshing...`); - return this.droidApiService.refreshAccessToken(); - } - return Promise.resolve(); - } -} - // 用于存储服务适配器单例的映射 export const serviceInstances = {}; @@ -307,9 +266,6 @@ export function getServiceAdapter(config) { case MODEL_PROVIDER.QWEN_API: serviceInstances[providerKey] = new QwenApiServiceAdapter(config); break; - case MODEL_PROVIDER.DROID_API: - serviceInstances[providerKey] = new DroidApiServiceAdapter(config); - break; default: throw new Error(`Unsupported model provider: ${provider}`); } diff --git a/src/common.js b/src/common.js index 88d685d..b677d9a 100644 --- a/src/common.js +++ b/src/common.js @@ -16,7 +16,6 @@ export const MODEL_PROTOCOL_PREFIX = { GEMINI: 'gemini', OPENAI: 'openai', CLAUDE: 'claude', - DROID: 'droid', // Droid uses Claude protocol } export const MODEL_PROVIDER = { @@ -26,7 +25,6 @@ export const MODEL_PROVIDER = { CLAUDE_CUSTOM: 'claude-custom', KIRO_API: 'claude-kiro-oauth', QWEN_API: 'openai-qwen-oauth', - DROID_API: 'droid-factory-oauth', } /** @@ -37,17 +35,10 @@ export const MODEL_PROVIDER = { */ export function getProtocolPrefix(provider) { const hyphenIndex = provider.indexOf('-'); - let prefix = provider; if (hyphenIndex !== -1) { - prefix = provider.substring(0, hyphenIndex); + return provider.substring(0, hyphenIndex); } - - // Map droid protocol to claude (since droid uses Claude API) - if (prefix === 'droid') { - return 'claude'; - } - - return prefix; + return provider; // Return original if no hyphen is found } export const ENDPOINT_TYPE = { diff --git a/src/droid/README.md b/src/droid/README.md deleted file mode 100644 index abe5601..0000000 --- a/src/droid/README.md +++ /dev/null @@ -1,227 +0,0 @@ -# Droid (Factory.ai) Provider - -This adapter enables you to use Factory.ai's Droid CLI as an OpenAI-compatible API through AIClient2API. - -## Features - -- ✅ Uses your existing Droid CLI installation -- ✅ No need for API keys or token management -- ✅ Full Claude API compatibility -- ✅ Supports streaming and non-streaming responses -- ✅ Works with any OpenAI-compatible client - -## Prerequisites - -1. **Install Droid CLI** - ```bash - # Install Droid CLI from Factory.ai - # Visit: https://factory.ai/product/cli - ``` - -2. **Authenticate with Droid** - ```bash - droid - # Follow the prompts to login - ``` - -## Configuration - -### Using Command Line Arguments - -```bash -node src/api-server.js \ - --model-provider droid-factory-oauth \ - --port 3000 \ - --api-key your-api-key -``` - -### Using config.json - -Add to your `config.json`: - -```json -{ - "MODEL_PROVIDER": "droid-factory-oauth", - "PORT": 3000, - "API_KEY": "your-api-key" -} -``` - -### Using Environment Variables - -```bash -export MODEL_PROVIDER=droid-factory-oauth -export PORT=3000 -export API_KEY=your-api-key -node src/api-server.js -``` - -## Usage - -### With OpenAI-Compatible Clients - -Point your OpenAI-compatible client to the proxy: - -```python -import openai - -client = openai.OpenAI( - base_url="http://localhost:3000/v1", - api_key="your-api-key" -) - -response = client.chat.completions.create( - model="claude-sonnet-4-5-20250929", - messages=[ - {"role": "user", "content": "Hello!"} - ] -) -``` - -### With Claude SDK - -```python -import anthropic - -client = anthropic.Anthropic( - base_url="http://localhost:3000", - api_key="your-api-key" # Your proxy API key, not Anthropic's -) - -message = client.messages.create( - model="claude-sonnet-4-5-20250929", - max_tokens=1024, - messages=[ - {"role": "user", "content": "Hello, Claude!"} - ] -) -``` - -### With curl - -```bash -# Claude API format -curl http://localhost:3000/v1/messages \ - -H "Content-Type: application/json" \ - -H "x-api-key: your-api-key" \ - -H "anthropic-version: 2023-06-01" \ - -d '{ - "model": "claude-sonnet-4-5-20250929", - "max_tokens": 1024, - "messages": [ - {"role": "user", "content": "Hello!"} - ] - }' - -# OpenAI API format -curl http://localhost:3000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer your-api-key" \ - -d '{ - "model": "claude-sonnet-4-5-20250929", - "messages": [ - {"role": "user", "content": "Hello!"} - ] - }' -``` - -## Supported Models - -The Droid provider supports all Claude models available through Factory.ai: - -- `claude-sonnet-4-5-20250929` (default) -- `claude-3-7-sonnet-20250219` -- `claude-3-5-sonnet-20241022` -- `claude-3-opus-20240229` - -## How It Works - -The proxy converts OpenAI/Claude API requests into `droid exec` commands and streams the results back: - -1. Your app sends a request to the proxy (OpenAI or Claude format) -2. The proxy converts messages to a prompt -3. Executes `droid exec "your prompt"` -4. Streams the response back in the requested format - -## Troubleshooting - -### "Droid CLI is not installed or not in PATH" - -**Problem**: The droid command is not available - -**Solution**: -1. Install Droid CLI from https://factory.ai/product/cli -2. Make sure `droid` is in your system PATH -3. Test with: `droid --version` - -### "Droid command failed" - -**Problem**: Droid CLI returned an error - -**Solution**: -1. Make sure you are authenticated: `droid` -2. Test droid directly: `droid exec "hello"` -3. Check for any error messages from the droid CLI - -### Authentication Issues - -**Problem**: Droid needs authentication - -**Solution**: -1. Run `droid` to start an interactive session and authenticate -2. Follow the browser authentication flow -3. After authentication, the proxy will work automatically - -## Architecture - -``` -Your App → AIClient2API Proxy → droid exec command → Factory.ai - (Port 3000) (CLI process) -``` - -The Droid provider: -1. Receives API requests (OpenAI or Claude format) -2. Converts messages to prompt text -3. Spawns `droid exec "prompt"` process -4. Streams output back to client -5. Converts response to requested API format - -## Advanced Configuration - -### Custom Droid Command - -If your droid CLI is installed with a different name or path: - -```json -{ - "MODEL_PROVIDER": "droid-factory-oauth", - "DROID_COMMAND": "/custom/path/to/droid" -} -``` - -## Security Considerations - -1. **API key protection**: Use strong API keys for the proxy itself -2. **Local execution**: Droid CLI runs locally with your credentials -3. **Use HTTPS in production**: Don't expose the proxy over HTTP in production -4. **Access control**: Limit who can access your proxy server - -## Limitations - -- Requires Droid CLI to be installed and authenticated -- Each request spawns a new `droid exec` process -- Streaming support depends on droid CLI output behavior -- Authentication is managed by Droid CLI (not the proxy) - -## Contributing - -When contributing Droid-related changes: - -1. Test with actual Droid CLI installation -2. Ensure both streaming and non-streaming work -3. Update this README with any new features -4. Follow the existing code patterns in the project - -## License - -This provider follows the same license as the main AIClient2API project (GPLv3). diff --git a/src/droid/droid-core.js b/src/droid/droid-core.js deleted file mode 100644 index 2c8e795..0000000 --- a/src/droid/droid-core.js +++ /dev/null @@ -1,257 +0,0 @@ -import { spawn } from 'child_process'; -import { mkdtempSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; - -/** - * Droid (Factory.ai) API Service - * Wraps the Droid CLI to provide an API-compatible interface - */ -export class DroidApiService { - /** - * Constructor - * @param {object} config - Configuration object - */ - constructor(config = {}) { - this.config = config; - this.isInitialized = false; - this.droidCommand = config.DROID_COMMAND || 'droid'; - - // Create isolated working directory to prevent context leakage - this.isolatedWorkDir = mkdtempSync(join(tmpdir(), 'droid-isolated-')); - console.log('[Droid] Created isolated working directory:', this.isolatedWorkDir); - } - - /** - * Initialize the service by checking if droid CLI is available - * @returns {Promise} True if initialization succeeded - */ - async initialize() { - try { - console.log('[Droid] Initializing Droid API Service...'); - await this.executeDroidCommand(['--version']); - this.isInitialized = true; - console.log('[Droid] Initialization complete.'); - return true; - } catch (error) { - console.error('[Droid] Droid CLI not available:', error.message); - this.isInitialized = false; - throw new Error('Droid CLI is not installed or not in PATH. Please install from https://factory.ai/product/cli'); - } - } - - /** - * Execute droid CLI command and return output - * @param {Array} args - Command arguments - * @returns {Promise} Command output - */ - async executeDroidCommand(args) { - return new Promise((resolve, reject) => { - const droid = spawn(this.droidCommand, args); - let stdout = ''; - let stderr = ''; - - droid.stdout.on('data', (data) => { - stdout += data.toString(); - }); - - droid.stderr.on('data', (data) => { - stderr += data.toString(); - }); - - droid.on('close', (code) => { - // Droid CLI may output to stderr even on success - const output = stdout || stderr; - if (code !== 0 && !output) { - reject(new Error(`Droid command failed with code ${code}: ${stderr || stdout}`)); - } else { - resolve(output); - } - }); - - droid.on('error', (err) => { - reject(new Error(`Failed to spawn droid: ${err.message}`)); - }); - }); - } - - /** - * Convert Claude-format messages to a simple prompt string - * @param {Array} messages - Array of message objects - * @returns {string} Combined prompt string - */ - messagesToPrompt(messages) { - return messages.map(msg => { - // Handle both string and array content formats - let content = msg.content; - if (Array.isArray(content)) { - // Extract text from content blocks - content = content.map(block => block.text || '').join(''); - } - - if (msg.role === 'user') { - return content; - } else if (msg.role === 'assistant') { - return `Assistant: ${content}`; - } else if (msg.role === 'system') { - return `System: ${content}`; - } - return content; - }).join('\n\n'); - } - - /** - * Generates content (non-streaming) - * @param {string} model - Model name - * @param {object} requestBody - Request body (Claude format) - * @returns {Promise} Claude API response (Claude compatible format) - */ - async generateContent(model, requestBody) { - if (!this.isInitialized) { - await this.initialize(); - } - - try { - const prompt = this.messagesToPrompt(requestBody.messages); - console.log('[Droid DEBUG] Sending prompt to droid exec:', prompt); - console.log('[Droid DEBUG] Isolated working directory:', this.isolatedWorkDir); - const output = await this.executeDroidCommand([ - 'exec', - '--skip-permissions-unsafe', - '--cwd', this.isolatedWorkDir, - prompt - ]); - - return { - id: `msg_${Date.now()}`, - type: 'message', - role: 'assistant', - content: [{ - type: 'text', - text: output.trim() - }], - model: model, - stop_reason: 'end_turn', - usage: { - input_tokens: Math.ceil(prompt.length / 4), - output_tokens: Math.ceil(output.length / 4) - } - }; - } catch (error) { - throw this._handleError(error); - } - } - - /** - * Streams content generation - * @param {string} model - Model name - * @param {object} requestBody - Request body (Claude format) - * @returns {AsyncIterable} Claude API response stream (Claude compatible format) - */ - async *generateContentStream(model, requestBody) { - if (!this.isInitialized) { - await this.initialize(); - } - - const prompt = this.messagesToPrompt(requestBody.messages); - - yield { - type: 'message_start', - message: { - id: `msg_${Date.now()}`, - type: 'message', - role: 'assistant', - content: [], - model: model - } - }; - - yield { - type: 'content_block_start', - index: 0, - content_block: { type: 'text', text: '' } - }; - - const droid = spawn(this.droidCommand, [ - 'exec', - '--skip-permissions-unsafe', - '--cwd', this.isolatedWorkDir, - prompt - ]); - - let buffer = ''; - for await (const chunk of droid.stdout) { - const text = chunk.toString(); - buffer += text; - - yield { - type: 'content_block_delta', - index: 0, - delta: { type: 'text_delta', text: text } - }; - } - - yield { - type: 'content_block_stop', - index: 0 - }; - - yield { - type: 'message_delta', - delta: { stop_reason: 'end_turn' }, - usage: { output_tokens: Math.ceil(buffer.length / 4) } - }; - - yield { - type: 'message_stop' - }; - } - - /** - * Lists available models - * The Droid provider supports Claude models available through Factory.ai - * @returns {Promise} List of models - */ - async listModels() { - console.log('[Droid] Listing available models.'); - return { - data: [ - { - id: 'claude-sonnet-4-5-20250929', - object: 'model', - created: 1725494400, - owned_by: 'anthropic' - }, - { - id: 'claude-3-7-sonnet-20250219', - object: 'model', - created: 1708387200, - owned_by: 'anthropic' - }, - { - id: 'claude-3-5-sonnet-20241022', - object: 'model', - created: 1698019200, - owned_by: 'anthropic' - }, - { - id: 'claude-3-opus-20240229', - object: 'model', - created: 1709251200, - owned_by: 'anthropic' - } - ], - object: 'list' - }; - } - - /** - * Handle CLI errors - * @param {Error} error - Error object - * @returns {Error} Formatted error - */ - _handleError(error) { - console.error('[Droid] Error:', error.message); - return new Error(`Droid CLI error: ${error.message}`); - } -} diff --git a/src/droid/droid-strategy.js b/src/droid/droid-strategy.js deleted file mode 100644 index e2bfb6a..0000000 --- a/src/droid/droid-strategy.js +++ /dev/null @@ -1,74 +0,0 @@ -import { ProviderStrategy } from '../provider-strategy.js'; -import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../common.js'; - -/** - * Droid (Factory.ai) provider strategy implementation - * Follows Claude protocol since Droid uses Claude API - */ -class DroidStrategy extends ProviderStrategy { - extractModelAndStreamInfo(req, requestBody) { - const model = requestBody.model || 'claude-sonnet-4-5-20250929'; - const isStream = requestBody.stream === true; - return { model, isStream }; - } - - extractResponseText(response) { - if (response.type === 'content_block_delta' && response.delta) { - if (response.delta.type === 'text_delta') { - return response.delta.text; - } - if (response.delta.type === 'input_json_delta') { - return response.delta.partial_json; - } - } - if (response.content && Array.isArray(response.content)) { - return response.content - .filter(block => block.type === 'text' && block.text) - .map(block => block.text) - .join(''); - } else if (response.content && response.content.type === 'text') { - return response.content.text; - } - return ''; - } - - extractPromptText(requestBody) { - if (requestBody.messages && requestBody.messages.length > 0) { - const lastMessage = requestBody.messages[requestBody.messages.length - 1]; - if (lastMessage.content && Array.isArray(lastMessage.content)) { - return lastMessage.content.map(block => block.text || '').join(''); - } - return lastMessage.content || ''; - } - return ''; - } - - async applySystemPromptFromFile(config, requestBody) { - if (!config.SYSTEM_PROMPT_FILE_PATH) { - return requestBody; - } - - const filePromptContent = config.SYSTEM_PROMPT_CONTENT; - if (filePromptContent === null) { - return requestBody; - } - - const existingSystemText = extractSystemPromptFromRequestBody(requestBody, MODEL_PROTOCOL_PREFIX.CLAUDE); - - const newSystemText = config.SYSTEM_PROMPT_MODE === 'append' && existingSystemText - ? `${existingSystemText}\n${filePromptContent}` - : filePromptContent; - - requestBody.system = newSystemText; - console.log(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'droid'.`); - - return requestBody; - } - - async manageSystemPrompt(requestBody) { - const incomingSystemText = extractSystemPromptFromRequestBody(requestBody, MODEL_PROTOCOL_PREFIX.CLAUDE); - await this._updateSystemPromptFile(incomingSystemText, 'droid'); - } -} - -export { DroidStrategy }; diff --git a/src/provider-strategies.js b/src/provider-strategies.js index 077d28c..b1146ba 100644 --- a/src/provider-strategies.js +++ b/src/provider-strategies.js @@ -2,7 +2,6 @@ import { MODEL_PROTOCOL_PREFIX } from './common.js'; import { GeminiStrategy } from './gemini/gemini-strategy.js'; import { OpenAIStrategy } from './openai/openai-strategy.js'; import { ClaudeStrategy } from './claude/claude-strategy.js'; -import { DroidStrategy } from './droid/droid-strategy.js'; /** * Strategy factory that returns the appropriate strategy instance based on the provider protocol. @@ -16,8 +15,6 @@ class ProviderStrategyFactory { return new OpenAIStrategy(); case MODEL_PROTOCOL_PREFIX.CLAUDE: return new ClaudeStrategy(); - case MODEL_PROTOCOL_PREFIX.DROID: - return new DroidStrategy(); // Droid uses Claude-compatible protocol default: throw new Error(`Unsupported provider protocol: ${providerProtocol}`); } diff --git a/test-droid.js b/test-droid.js deleted file mode 100644 index fde17f6..0000000 --- a/test-droid.js +++ /dev/null @@ -1,59 +0,0 @@ -// Test script for Droid provider -import { DroidApiService } from './src/droid/droid-core.js'; -import { promises as fs } from 'fs'; - -async function testDroidProvider() { - console.log('🧪 Testing Droid Provider (CLI-based)...\n'); - - try { - // Test 1: Initialize DroidApiService (check droid CLI availability) - console.log('✅ Test 1: Initializing DroidApiService...'); - const service = new DroidApiService(); - await service.initialize(); - console.log(' DroidApiService initialized successfully'); - console.log(' isInitialized:', service.isInitialized); - - // Test 2: List models - console.log('\n✅ Test 2: Listing available models...'); - const models = await service.listModels(); - console.log(` Available models: ${models.data.length}`); - models.data.forEach(model => { - console.log(` - ${model.id}`); - }); - - // Test 3: Test simple request (non-streaming) - console.log('\n✅ Test 3: Testing simple request...'); - try { - const response = await service.generateContent('claude-sonnet-4-5-20250929', { - messages: [ - { role: 'user', content: 'Say "Hello from Droid test!" in one sentence.' } - ], - max_tokens: 50 - }); - console.log(' Response received:'); - console.log(' Model:', response.model); - console.log(' Stop reason:', response.stop_reason); - if (response.content && response.content[0]) { - console.log(' Content:', response.content[0].text); - } - } catch (error) { - console.error(' ❌ Request failed:', error.message); - console.log(' 💡 Make sure you are authenticated with: droid'); - } - - console.log('\n✅ All tests completed!'); - - } catch (error) { - console.error('\n❌ Test failed:', error.message); - console.error('Stack:', error.stack); - } -} - -// Run tests -testDroidProvider().then(() => { - console.log('\n🎉 Test suite finished'); - process.exit(0); -}).catch(error => { - console.error('\n💥 Test suite error:', error); - process.exit(1); -});