Files
sechenov/server.js

80 lines
2.5 KiB
JavaScript

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' }));
app.use(express.static(path.join(__dirname, 'public')));
const MODELS = {
sonnet: 'claude-sonnet-4-6',
haiku: 'claude-haiku-4-5-20251001',
};
function getAccessToken() {
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
const credsPath = path.join(configDir, '.credentials.json');
try {
const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8'));
return creds?.claudeAiOauth?.accessToken || null;
} catch (e) {
return null;
}
}
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' });
const modelId = MODELS[model] || MODELS.sonnet;
const accessToken = getAccessToken();
if (!accessToken) {
return res.status(500).json({ error: 'No credentials found in ' + (process.env.CLAUDE_CONFIG_DIR || '~/.claude') });
}
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
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 }],
});
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`);
}
}
} catch (err) {
console.error('[api error]', err.message);
res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
} finally {
clearInterval(heartbeat);
res.write('data: [DONE]\n\n');
res.end();
}
});
app.listen(8108, () => console.log('sechenov :8108'));