From d8d7e1a66f86a6a3558f479373a84a848eda0d23 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Mon, 25 May 2026 18:53:43 +0200 Subject: [PATCH] feat: add Node.js worker with Claude Code SDK --- worker/Dockerfile | 14 ++++++ worker/index.js | 109 ++++++++++++++++++++++++++++++++++++++++++++ worker/package.json | 10 ++++ 3 files changed, 133 insertions(+) create mode 100644 worker/Dockerfile create mode 100644 worker/index.js create mode 100644 worker/package.json diff --git a/worker/Dockerfile b/worker/Dockerfile new file mode 100644 index 0000000..2a3a5a7 --- /dev/null +++ b/worker/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-slim + +RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY package.json ./ +RUN npm install --production + +COPY index.js ./ + +EXPOSE 3001 + +CMD ["node", "index.js"] diff --git a/worker/index.js b/worker/index.js new file mode 100644 index 0000000..a585b60 --- /dev/null +++ b/worker/index.js @@ -0,0 +1,109 @@ +import express from "express"; +import { query } from "@anthropic-ai/claude-code"; + +const app = express(); +app.use(express.json({ limit: "10mb" })); + +const PORT = process.env.WORKER_PORT || 3001; +const DEFAULT_MODEL = process.env.CLAUDE_MODEL || "claude-sonnet-4-6"; + +app.get("/health", (_req, res) => { + res.json({ status: "ok" }); +}); + +app.post("/process", async (req, res) => { + const { prompt, system_prompt, model, session_id } = req.body; + + if (!prompt) { + return res + .status(400) + .json({ success: false, error: "prompt is required" }); + } + + try { + const options = { + model: model || DEFAULT_MODEL, + systemPrompt: system_prompt || undefined, + allowedTools: [], + }; + + if (session_id) { + options.resume = session_id; + } + + const messages = await query({ + prompt, + options, + }); + + const resultText = messages + .filter((m) => m.type === "text") + .map((m) => m.text) + .join("\n"); + + const conversationId = messages.length > 0 ? messages[0].conversationId : null; + + res.json({ + success: true, + result: resultText, + session_id: conversationId || session_id, + }); + } catch (error) { + console.error("Process error:", error.message); + res.json({ + success: false, + error: error.message, + }); + } +}); + +app.post("/summarize", async (req, res) => { + const { exchanges, mode } = req.body; + + if (!exchanges || exchanges.length === 0) { + return res + .status(400) + .json({ success: false, error: "exchanges is required" }); + } + + const exchangeText = exchanges + .map((e, i) => `Exchange ${i + 1}:\nQ: ${e.prompt}\nA: ${e.result}`) + .join("\n\n"); + + const summaryPrompt = + mode === "complete" + ? `Summarize these exchanges into a comprehensive overview. Be concise but thorough:\n\n${exchangeText}` + : `Summarize this exchange in 1-2 sentences:\n\n${exchangeText}`; + + try { + const messages = await query({ + prompt: summaryPrompt, + options: { + model: DEFAULT_MODEL, + systemPrompt: + "You are a summarizer. Respond with only the summary, no preamble.", + allowedTools: [], + }, + }); + + const summaryText = messages + .filter((m) => m.type === "text") + .map((m) => m.text) + .join("\n"); + + res.json({ + success: true, + summary: summaryText.trim(), + }); + } catch (error) { + console.error("Summarize error:", error.message); + res.json({ + success: false, + error: error.message, + }); + } +}); + +app.listen(PORT, () => { + console.log(`Worker listening on port ${PORT}`); +}); diff --git a/worker/package.json b/worker/package.json new file mode 100644 index 0000000..28cc569 --- /dev/null +++ b/worker/package.json @@ -0,0 +1,10 @@ +{ + "name": "opus-agent-worker", + "version": "0.1.0", + "private": true, + "type": "module", + "dependencies": { + "express": "^4.21.0", + "@anthropic-ai/claude-code": "^1.0.0" + } +}