The Atlas Law OSS — its documentation, bound to its code
2 documents

First hour with Law OSS

Orientation: what Law OSS is, the bring-your-own-key model that shapes every feature, and where the six agents and the monorepo actually live.

packages/ai/src/index.ts112 lines · callAI L3–14
Outline 6 symbols
1import Anthropic from '@anthropic-ai/sdk'
2
3export async function callAI(
4 apiKey: string,
5 provider: string,
6 messages: Array<{ role: 'user' | 'assistant'; content: string }>,
7 systemPrompt: string,
8 maxTokens = 1800
9): Promise<string> {
10 if (provider === 'google' || provider === 'gemini') {
11 return callGemini(apiKey, messages, systemPrompt, maxTokens)
12 }
13 return callClaude(apiKey, messages, systemPrompt, maxTokens)
14}
15
16export async function* streamAI(
17 apiKey: string,
18 provider: string,
19 messages: Array<{ role: 'user' | 'assistant'; content: string }>,
20 systemPrompt: string,
21 maxTokens = 2000
22): AsyncGenerator<string> {
23 if (provider === 'google' || provider === 'gemini') {
24 const result = await callGemini(apiKey, messages, systemPrompt, maxTokens)
25 yield result
26 return
27 }
28 yield* streamClaude(apiKey, messages, systemPrompt, maxTokens)
29}
30
31async function callClaude(
32 apiKey: string,
33 messages: Array<{ role: 'user' | 'assistant'; content: string }>,
34 system: string,
35 maxTokens: number
36): Promise<string> {
37 const client = new Anthropic({ apiKey })
38 const response = await client.messages.create({
39 model: 'claude-sonnet-4-6',
40 max_tokens: maxTokens,
41 system,
42 messages,
43 })
44 return response.content
45 .filter(b => b.type === 'text')
46 .map(b => (b as { type: 'text'; text: string }).text)
47 .join('')
48}
49
50async function* streamClaude(
51 apiKey: string,
52 messages: Array<{ role: 'user' | 'assistant'; content: string }>,
53 system: string,
54 maxTokens: number
55): AsyncGenerator<string> {
56 const client = new Anthropic({ apiKey })
57 const stream = await client.messages.stream({
58 model: 'claude-sonnet-4-6',
59 max_tokens: maxTokens,
60 system,
61 messages,
62 })
63 for await (const event of stream) {
64 if (
65 event.type === 'content_block_delta' &&
66 event.delta.type === 'text_delta'
67 ) {
68 yield event.delta.text
69 }
70 }
71}
72
73async function callGemini(
74 apiKey: string,
75 messages: Array<{ role: 'user' | 'assistant'; content: string }>,
76 system: string,
77 maxTokens: number
78): Promise<string> {
79 const contents = messages.map(m => ({
80 role: m.role === 'assistant' ? 'model' : 'user',
81 parts: [{ text: m.content }],
82 }))
83 const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`
84 const r = await fetch(url, {
85 method: 'POST',
86 headers: { 'Content-Type': 'application/json' },
87 body: JSON.stringify({
88 system_instruction: { parts: [{ text: system }] },
89 contents,
90 generationConfig: { maxOutputTokens: maxTokens, temperature: 0.3 },
91 }),
92 })
93 const d = await r.json() as {
94 error?: { code: number; message: string }
95 candidates?: Array<{ content: { parts: Array<{ text: string }> } }>
96 }
97 if (d.error) throw new Error(`GEMINI_ERROR:${d.error.code}:${d.error.message}`)
98 return d.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
99}
100
101export async function verifyApiKey(
102 provider: string,
103 apiKey: string
104): Promise<boolean> {
105 if (!apiKey || apiKey.trim().length < 10) return false
106 if (provider === 'google' || provider === 'gemini') {
107 return apiKey.startsWith('AIza')
108 }
109 // Claude keys start with sk-ant-
110 return apiKey.startsWith('sk-ant-')
111}
112