fix: native fetch with OAuth Bearer auth
This commit is contained in:
@@ -5,7 +5,6 @@
|
|||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.39.0",
|
|
||||||
"express": "^4.19.2"
|
"express": "^4.19.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
server.js
64
server.js
@@ -2,7 +2,6 @@ const express = require('express');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { createRequire } = require('module');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json({ limit: '10mb' }));
|
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) => {
|
app.post('/api/chat', async (req, res) => {
|
||||||
const { text, model = 'sonnet' } = req.body;
|
const { text, model = 'sonnet' } = req.body;
|
||||||
if (!text?.trim()) return res.status(400).json({ error: 'text required' });
|
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();
|
const accessToken = getAccessToken();
|
||||||
|
|
||||||
if (!accessToken) {
|
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');
|
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);
|
const heartbeat = setInterval(() => res.write(': ping\n\n'), 15000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = createClient(accessToken);
|
const apiResp = await fetch('https://api.anthropic.com/v1/messages', {
|
||||||
const stream = await client.messages.stream({
|
method: 'POST',
|
||||||
model: modelId,
|
headers: {
|
||||||
max_tokens: 8192,
|
'Content-Type': 'application/json',
|
||||||
messages: [{ role: 'user', content: text }],
|
'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 (!apiResp.ok) {
|
||||||
if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
|
const errText = await apiResp.text();
|
||||||
res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`);
|
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) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user