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.tsBlame75 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
a5705cfClaude25/** Primary model for all AI features — code understanding, review, generation */
9769a35Claude26export const MODEL_SONNET = "claude-sonnet-4-6";
a5705cfClaude27/** Legacy constant — kept for backwards compatibility, do not use in new code */
3ef4c9dClaude28export const MODEL_HAIKU = "claude-haiku-4-5-20251001";
29
30/**
31 * Extract text content from an Anthropic message response.
32 */
33export function extractText(
34 message: Anthropic.Messages.Message
35): string {
36 for (const block of message.content) {
37 if (block.type === "text") return block.text;
38 }
39 return "";
40}
41
42/**
43 * Safely parse JSON from a Claude response that may include surrounding prose
44 * or a ```json code block.
45 */
46export function parseJsonResponse<T = unknown>(text: string): T | null {
47 // Prefer ```json blocks
48 const fenced = text.match(/```(?:json)?\s*(\{[\s\S]*?\}|\[[\s\S]*?\])\s*```/);
49 const candidate = fenced ? fenced[1] : null;
50 if (candidate) {
51 try {
52 return JSON.parse(candidate) as T;
53 } catch {
54 // fall through
55 }
56 }
57 // Fall back to first top-level {...} or [...]
58 const braceMatch = text.match(/\{[\s\S]*\}/);
59 if (braceMatch) {
60 try {
61 return JSON.parse(braceMatch[0]) as T;
62 } catch {
63 // fall through
64 }
65 }
66 const bracketMatch = text.match(/\[[\s\S]*\]/);
67 if (bracketMatch) {
68 try {
69 return JSON.parse(bracketMatch[0]) as T;
70 } catch {
71 return null;
72 }
73 }
74 return null;
75}