Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

ai-client.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

ai-client.tsBlame80 lines · 1 contributor
3ef4c9dClaude1/**
2 * Shared Anthropic client + helpers for all AI-powered features.
3 * Centralised here so model choice, caching, and key handling live in one place.
4 */
5
6import Anthropic from "@anthropic-ai/sdk";
7import { config } from "./config";
8
9let _client: Anthropic | null = null;
10
11export function getAnthropic(): Anthropic {
12 if (!_client) {
13 if (!config.anthropicApiKey) {
14 throw new Error("ANTHROPIC_API_KEY is not set");
15 }
16 _client = new Anthropic({ apiKey: config.anthropicApiKey });
17 }
18 return _client;
19}
20
21export function isAiAvailable(): boolean {
22 return !!config.anthropicApiKey;
23}
24
40e7738Claude25/**
26 * Default model for code understanding + review.
27 * Owner-mandated to `claude-sonnet-4-6` — the most reliable model for the
28 * gluecron workload as of this build. Override at runtime with MODEL_SONNET
29 * env if a future model proves better; do not edit the constant.
30 */
31export const MODEL_SONNET = process.env.MODEL_SONNET || "claude-sonnet-4-6";
32/** Fast model for lightweight tasks (commit messages, titles, completions). */
33export const MODEL_HAIKU = process.env.MODEL_HAIKU || "claude-haiku-4-5-20251001";
3ef4c9dClaude34
35/**
36 * Extract text content from an Anthropic message response.
37 */
38export function extractText(
39 message: Anthropic.Messages.Message
40): string {
41 for (const block of message.content) {
42 if (block.type === "text") return block.text;
43 }
44 return "";
45}
46
47/**
48 * Safely parse JSON from a Claude response that may include surrounding prose
49 * or a ```json code block.
50 */
51export function parseJsonResponse<T = unknown>(text: string): T | null {
52 // Prefer ```json blocks
53 const fenced = text.match(/```(?:json)?\s*(\{[\s\S]*?\}|\[[\s\S]*?\])\s*```/);
54 const candidate = fenced ? fenced[1] : null;
55 if (candidate) {
56 try {
57 return JSON.parse(candidate) as T;
58 } catch {
59 // fall through
60 }
61 }
62 // Fall back to first top-level {...} or [...]
63 const braceMatch = text.match(/\{[\s\S]*\}/);
64 if (braceMatch) {
65 try {
66 return JSON.parse(braceMatch[0]) as T;
67 } catch {
68 // fall through
69 }
70 }
71 const bracketMatch = text.match(/\[[\s\S]*\]/);
72 if (bracketMatch) {
73 try {
74 return JSON.parse(bracketMatch[0]) as T;
75 } catch {
76 return null;
77 }
78 }
79 return null;
80}