CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | /**
* Block D9 — Copilot-style completion HTTP surface.
*
* POST /api/copilot/completions (authed — PAT / OAuth / session)
* Body: { prefix, suffix?, language?, repo? }
* Returns: { completion, model, cached }
*
* GET /api/copilot/ping (unauthed)
* Returns: { ok: true, aiAvailable: boolean }
*
* Keep per-user limits tight: IDE plugins call this on every keystroke, so a
* misbehaving client can otherwise exhaust the shared Anthropic quota fast.
*/
import { Hono } from "hono";
import { requireAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { rateLimit } from "../middleware/rate-limit";
import { completeCode } from "../lib/ai-completion";
import { isAiAvailable } from "../lib/ai-client";
const copilot = new Hono<AuthEnv>();
// Tight per-caller limit: 60/min. NOTE: `rateLimit` keys by bearer-token
// prefix when Authorization is present, otherwise by IP — so session-cookie
// callers share a single IP bucket. That's acceptable for an IDE endpoint
// where the expected caller is almost always a PAT/OAuth token.
const completionLimit = rateLimit(60, 60_000, "copilot");
copilot.get("/api/copilot/ping", (c) => {
return c.json({ ok: true, aiAvailable: isAiAvailable() });
});
copilot.post(
"/api/copilot/completions",
completionLimit,
requireAuth,
async (c) => {
let body: any;
try {
body = await c.req.json();
} catch {
return c.json({ error: "invalid JSON body" }, 400);
}
if (!body || typeof body !== "object") {
return c.json({ error: "body must be a JSON object" }, 400);
}
const { prefix, suffix, language, repo } = body as {
prefix?: unknown;
suffix?: unknown;
language?: unknown;
repo?: unknown;
};
if (typeof prefix !== "string" || prefix.length === 0) {
return c.json({ error: "prefix (non-empty string) is required" }, 400);
}
const result = await completeCode({
prefix,
suffix: typeof suffix === "string" ? suffix : undefined,
language: typeof language === "string" ? language : undefined,
repoHint: typeof repo === "string" ? repo : undefined,
});
return c.json(result);
}
);
export default copilot;
|