-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Commander V1 autocomplete scaffold #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Very small safety filter for V1. Extend as needed. | ||
| const dangerousPatterns = [ | ||
| /\bkill\b/i, | ||
| /\bweapon\b/i, | ||
| /\bhack(?:ing|er)?\b/i, | ||
| /\bpassword\b/i, | ||
| /\bcredit card\b/i, | ||
| /\bssn\b/i | ||
| ]; | ||
|
|
||
| function isDangerous(text) { | ||
| if (!text) return false; | ||
| for (const re of dangerousPatterns) { | ||
| if (re.test(text)) return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| module.exports = { isDangerous }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,127 +1,59 @@ | ||
| import http from "node:http"; | ||
| import { AGENTS, buildAgentPrompt } from "./agents.js"; | ||
| import { addNovelty, rankSuggestions } from "./ranker.js"; | ||
|
|
||
| const PORT = Number(process.env.PORT || 3000); | ||
| const MODEL = process.env.COMMANDER_MODEL || "gpt-5.6-luna"; | ||
| const MAX_SUGGESTIONS = Math.min(Number(process.env.MAX_SUGGESTIONS || 5), AGENTS.length); | ||
| const CACHE_TTL_MS = Number(process.env.CACHE_TTL_MS || 60000); | ||
| const cache = new Map(); | ||
|
|
||
| function json(res, status, body) { | ||
| res.writeHead(status, { | ||
| "content-type": "application/json; charset=utf-8", | ||
| "access-control-allow-origin": "*", | ||
| "access-control-allow-methods": "POST, OPTIONS", | ||
| "access-control-allow-headers": "content-type" | ||
| }); | ||
| res.end(JSON.stringify(body)); | ||
| } | ||
|
|
||
| function readBody(req) { | ||
| return new Promise((resolve, reject) => { | ||
| let raw = ""; | ||
| req.on("data", chunk => { | ||
| raw += chunk; | ||
| if (raw.length > 100_000) req.destroy(); | ||
| }); | ||
| req.on("end", () => { | ||
| try { resolve(JSON.parse(raw || "{}")); } | ||
| catch { reject(new Error("Invalid JSON")); } | ||
| }); | ||
| req.on("error", reject); | ||
| }); | ||
| } | ||
|
|
||
| function extractJson(text) { | ||
| const cleaned = String(text || "").replace(/^```json\s*/i, "").replace(/```$/i, "").trim(); | ||
| const start = cleaned.indexOf("{"); | ||
| const end = cleaned.lastIndexOf("}"); | ||
| if (start < 0 || end < start) throw new Error("Model did not return JSON"); | ||
| return JSON.parse(cleaned.slice(start, end + 1)); | ||
| } | ||
|
|
||
| async function callModel(agent, promptPartial, context) { | ||
| if (!process.env.OPENAI_API_KEY) { | ||
| return { | ||
| text: `[mock:${agent.id}] Improve this prompt: ${promptPartial}`, | ||
| score: 0.5 | ||
| }; | ||
| } | ||
|
|
||
| const response = await fetch("https://api.openai.com/v1/responses", { | ||
| method: "POST", | ||
| headers: { | ||
| "authorization": `Bearer ${process.env.OPENAI_API_KEY}`, | ||
| "content-type": "application/json" | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: MODEL, | ||
| input: buildAgentPrompt(agent, promptPartial, context), | ||
| text: { format: { type: "json_object" } } | ||
| }) | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const message = await response.text(); | ||
| throw new Error(`Model request failed (${response.status}): ${message.slice(0, 300)}`); | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
| const outputText = data.output_text || data.output?.flatMap(x => x.content || []).find(x => x.text)?.text; | ||
| const parsed = extractJson(outputText); | ||
| return { text: parsed.text, score: Number(parsed.score) }; | ||
| } | ||
|
|
||
| async function suggest({ promptPartial, context = "", max_suggestions = MAX_SUGGESTIONS }) { | ||
| const key = JSON.stringify({ promptPartial, context, max_suggestions }); | ||
| const cached = cache.get(key); | ||
| if (cached && cached.expires > Date.now()) return cached.value; | ||
|
|
||
| const agents = AGENTS.slice(0, Math.max(1, Math.min(Number(max_suggestions), AGENTS.length))); | ||
| const results = await Promise.allSettled( | ||
| agents.map(async agent => { | ||
| const result = await callModel(agent, promptPartial, context); | ||
| return { | ||
| id: `${agent.id}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, | ||
| text: result.text, | ||
| score: result.score, | ||
| type: agent.type, | ||
| agent: agent.id | ||
| }; | ||
| }) | ||
| ); | ||
| const express = require('express'); | ||
| const path = require('path'); | ||
| const helmet = require('helmet'); | ||
| const rateLimit = require('express-rate-limit'); | ||
| const cors = require('cors'); | ||
|
Comment on lines
+1
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A fresh checkout followed by Useful? React with 👍 / 👎. |
||
| const { orchestrate } = require('./orchestrator'); | ||
| const { combineSelected } = require('./combine'); | ||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The target commit contains no Useful? React with 👍 / 👎. |
||
|
|
||
| const app = express(); | ||
| app.use(helmet()); | ||
| app.use(cors()); | ||
| app.use(express.json({ limit: '64kb' })); | ||
|
|
||
| const limiter = rateLimit({ windowMs: 60_000, max: 120 }); | ||
| app.use(limiter); | ||
|
|
||
| // serve static frontend | ||
| app.use(express.static(path.join(__dirname, '..', 'public'))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The target commit tracks no Useful? React with 👍 / 👎. |
||
|
|
||
| app.get('/api/health', (req, res) => { | ||
| res.json({ status: 'ok', version: 'v1' }); | ||
| }); | ||
|
|
||
| const suggestions = results | ||
| .filter(r => r.status === "fulfilled") | ||
| .map(r => r.value); | ||
| app.post('/api/suggest', async (req, res) => { | ||
| try { | ||
| const body = req.body || {}; | ||
| const { promptPartial, cursorIndex = null, context = {}, max_suggestions = 5, all = false } = body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a deployment sets the documented Useful? React with 👍 / 👎. |
||
|
|
||
| const ranked = rankSuggestions(addNovelty(suggestions, promptPartial), Number(max_suggestions)); | ||
| const value = { suggestions: ranked, model: MODEL, cached: false }; | ||
| cache.set(key, { value, expires: Date.now() + CACHE_TTL_MS }); | ||
| return value; | ||
| } | ||
| if (!promptPartial || typeof promptPartial !== 'string') return res.status(400).json({ error: 'promptPartial (string) required' }); | ||
| if (max_suggestions <= 0) return res.status(400).json({ error: 'max_suggestions must be positive' }); | ||
|
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Requests with prompt text between the former 20,000-character limit and the 64 KB JSON-body limit now pass this validation and are forwarded to generation instead of receiving a 400 response. In deployments with model generation enabled, those oversized requests can multiply token cost and latency across the orchestrated agents; retain an explicit prompt-length limit here. Useful? React with 👍 / 👎. |
||
|
|
||
| const server = http.createServer(async (req, res) => { | ||
| if (req.method === "OPTIONS") return json(res, 204, {}); | ||
| if (req.method !== "POST" || req.url !== "/api/suggest") { | ||
| return json(res, 404, { error: "Not found" }); | ||
| const suggestions = await orchestrate({ promptPartial, cursorIndex, context, max_suggestions, all }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The newly added Useful? React with 👍 / 👎. |
||
| res.json({ suggestions }); | ||
| } catch (err) { | ||
| console.error('api/suggest error', err && err.stack || err); | ||
| res.status(500).json({ error: 'generation_failed' }); | ||
| } | ||
| }); | ||
|
|
||
| app.post('/api/combine', async (req, res) => { | ||
| try { | ||
| const body = await readBody(req); | ||
| const promptPartial = String(body.promptPartial || "").trim(); | ||
| if (!promptPartial) return json(res, 400, { error: "promptPartial is required" }); | ||
| if (promptPartial.length > 20_000) return json(res, 400, { error: "promptPartial is too long" }); | ||
|
|
||
| const result = await suggest(body); | ||
| return json(res, 200, result); | ||
| } catch (error) { | ||
| console.error(error); | ||
| return json(res, 500, { error: error.message || "Internal server error" }); | ||
| const body = req.body || {}; | ||
| const { selected = [], promptPartial = '', context = {} } = body; | ||
| if (!Array.isArray(selected) || selected.length === 0) return res.status(400).json({ error: 'selected array required' }); | ||
|
|
||
| const combined = await combineSelected({ selected, promptPartial, context }); | ||
| res.json({ combined }); | ||
| } catch (err) { | ||
| console.error('api/combine error', err && err.stack || err); | ||
| res.status(500).json({ error: 'combine_failed' }); | ||
| } | ||
| }); | ||
|
|
||
| server.listen(PORT, () => { | ||
| console.log(`Commander listening on http://localhost:${PORT}`); | ||
| }); | ||
| if (require.main === module) { | ||
| const port = process.env.PORT || 3000; | ||
| app.listen(port, () => console.log(`Commander v1 listening on http://localhost:${port}`)); | ||
| } | ||
|
|
||
| module.exports = app; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| const request = require('supertest'); | ||
| const app = require('../src/server'); | ||
|
|
||
| test('GET /api/health returns ok', async () => { | ||
| const res = await request(app).get('/api/health'); | ||
| expect(res.status).toBe(200); | ||
| expect(res.body.status).toBe('ok'); | ||
| }); | ||
|
|
||
| test('POST /api/suggest returns suggestions in mock mode', async () => { | ||
| const res = await request(app).post('/api/suggest').send({ promptPartial: 'I want to know about' }); | ||
| expect(res.status).toBe(200); | ||
| expect(Array.isArray(res.body.suggestions)).toBe(true); | ||
| expect(res.body.suggestions.length).toBeGreaterThan(0); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because
package.jsonstill declares"type": "module", running the configurednpm startevaluates this file as an ES module and immediately throwsReferenceError: require is not definedat this line, before the server can bind a port. Convert this file and its exports to ESM or explicitly use CommonJS files/configuration so every API endpoint remains reachable.Useful? React with 👍 / 👎.