diff --git a/package.json b/package.json index 1c55221..0f57120 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "start": "node server.js" }, "dependencies": { - "@anthropic-ai/sdk": "^0.39.0", "express": "^4.19.2" } } diff --git a/server.js b/server.js index 4765fef..3847735 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,6 @@ const express = require('express'); const path = require('path'); const fs = require('fs'); const os = require('os'); -const { createRequire } = require('module'); const app = express(); app.use(express.json({ limit: '10mb' })); @@ -24,17 +23,6 @@ function getAccessToken() { } } -function createClient(accessToken) { - const Anthropic = require('@anthropic-ai/sdk'); - // OAuth token: use as apiKey — SDK sends x-api-key header - // Some versions also support authToken for Bearer auth - try { - return new Anthropic({ authToken: accessToken }); - } catch (e) { - return new Anthropic({ apiKey: accessToken }); - } -} - app.post('/api/chat', async (req, res) => { const { text, model = 'sonnet' } = req.body; if (!text?.trim()) return res.status(400).json({ error: 'text required' }); @@ -43,7 +31,7 @@ app.post('/api/chat', async (req, res) => { const accessToken = getAccessToken(); if (!accessToken) { - return res.status(500).json({ error: 'No credentials found in ' + (process.env.CLAUDE_CONFIG_DIR || '~/.claude') }); + return res.status(500).json({ error: 'No credentials in ' + (process.env.CLAUDE_CONFIG_DIR || '~/.claude') }); } res.setHeader('Content-Type', 'text/event-stream'); @@ -54,16 +42,50 @@ app.post('/api/chat', async (req, res) => { const heartbeat = setInterval(() => res.write(': ping\n\n'), 15000); try { - const client = createClient(accessToken); - const stream = await client.messages.stream({ - model: modelId, - max_tokens: 8192, - messages: [{ role: 'user', content: text }], + const apiResp = await fetch('https://api.anthropic.com/v1/messages', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}`, + 'anthropic-version': '2023-06-01', + 'anthropic-beta': 'oauth-2025-04-20', + }, + body: JSON.stringify({ + model: modelId, + max_tokens: 8192, + stream: true, + system: 'You are Claude, a helpful assistant. Respond directly and concisely.', + messages: [{ role: 'user', content: text }], + }), }); - for await (const event of stream) { - if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') { - res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`); + if (!apiResp.ok) { + const errText = await apiResp.text(); + res.write(`data: ${JSON.stringify({ error: `${apiResp.status}: ${errText.substring(0, 500)}` })}\n\n`); + return; + } + + const reader = apiResp.body.getReader(); + const decoder = new TextDecoder(); + let buf = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buf += decoder.decode(value, { stream: true }); + const events = buf.split('\n\n'); + buf = events.pop(); + for (const ev of events) { + const dataLine = ev.split('\n').find(l => l.startsWith('data: ')); + if (!dataLine) continue; + const payload = dataLine.slice(6); + if (payload === '[DONE]') continue; + try { + const obj = JSON.parse(payload); + if (obj.type === 'content_block_delta' && obj.delta?.type === 'text_delta') { + res.write(`data: ${JSON.stringify({ text: obj.delta.text })}\n\n`); + } + } catch (e) {} } } } catch (err) {