main
1import * as path from "path";
2import * as os from "os";
3
4// Model aliases for easy selection
5export type ModelAlias =
6 // Anthropic
7 | "opus"
8 | "sonnet"
9 | "haiku"
10 // Google
11 | "gemini"
12 | "gemini-pro"
13 | "gemini-flash"
14 // OpenAI
15 | "gpt4"
16 | "gpt4o"
17 | "o1"
18 | "o3"
19 // GitHub Copilot
20 | "copilot"
21 | "copilot-claude"
22 // Ollama (local)
23 | "ollama"
24 | "llama"
25 | "qwen"
26 | "deepseek"
27 // Others
28 | "groq"
29 | "mistral";
30
31export type Provider =
32 | "anthropic"
33 | "google"
34 | "openai"
35 | "github"
36 | "ollama"
37 | "groq"
38 | "mistral"
39 | "openrouter";
40
41export interface ProviderConfig {
42 anthropic?: { apiKey: string };
43 google?: { apiKey: string };
44 openai?: { apiKey: string };
45 github?: { token: string };
46 ollama?: { baseUrl: string };
47 openrouter?: { apiKey: string };
48 groq?: { apiKey: string };
49 mistral?: { apiKey: string };
50}
51
52export interface ModelInfo {
53 provider: Provider;
54 model: string;
55}
56
57// Model prefix mapping for message parsing
58export const MODEL_PREFIXES: Record<string, ModelInfo> = {
59 // Anthropic
60 "opus:": { provider: "anthropic", model: "claude-opus-4-5-20250514" },
61 "o:": { provider: "anthropic", model: "claude-opus-4-5-20250514" },
62 "sonnet:": { provider: "anthropic", model: "claude-sonnet-4-20250514" },
63 "s:": { provider: "anthropic", model: "claude-sonnet-4-20250514" },
64 "haiku:": { provider: "anthropic", model: "claude-3-5-haiku-20241022" },
65 "h:": { provider: "anthropic", model: "claude-3-5-haiku-20241022" },
66
67 // Google
68 "gemini:": { provider: "google", model: "gemini-2.0-flash" },
69 "g:": { provider: "google", model: "gemini-2.0-flash" },
70 "gemini-pro:": { provider: "google", model: "gemini-2.5-pro-preview-05-06" },
71 "gp:": { provider: "google", model: "gemini-2.5-pro-preview-05-06" },
72
73 // OpenAI
74 "gpt:": { provider: "openai", model: "gpt-4o" },
75 "gpt4:": { provider: "openai", model: "gpt-4o" },
76 "o1:": { provider: "openai", model: "o1" },
77 "o3:": { provider: "openai", model: "o3-mini" },
78
79 // GitHub Copilot
80 "copilot:": { provider: "github", model: "gpt-4o" },
81 "cp:": { provider: "github", model: "gpt-4o" },
82 "copilot-claude:": { provider: "github", model: "claude-sonnet-4" },
83
84 // Ollama (local)
85 "ollama:": { provider: "ollama", model: "llama3.2" },
86 "llama:": { provider: "ollama", model: "llama3.2" },
87 "qwen:": { provider: "ollama", model: "qwen2.5" },
88 "deepseek:": { provider: "ollama", model: "deepseek-r1" },
89
90 // Others
91 "groq:": { provider: "groq", model: "llama-3.3-70b-versatile" },
92 "mistral:": { provider: "mistral", model: "mistral-large-latest" },
93};
94
95export interface XmppConfig {
96 jid: string;
97 password: string;
98 ownerJid: string;
99}
100
101export interface PathsConfig {
102 dataDir: string;
103 inboxPath: string;
104}
105
106export interface Config {
107 xmpp: XmppConfig;
108 llm: {
109 defaultModel: ModelInfo;
110 providers: ProviderConfig;
111 };
112 paths: PathsConfig;
113 debug: boolean;
114}
115
116function getEnvOrThrow(name: string): string {
117 const value = process.env[name];
118 if (!value) {
119 throw new Error(`Required environment variable ${name} is not set`);
120 }
121 return value;
122}
123
124function getEnvOrDefault(name: string, defaultValue: string): string {
125 return process.env[name] || defaultValue;
126}
127
128export function loadConfig(): Config {
129 const xmppJid = getEnvOrThrow("DANEEL_XMPP_JID");
130 const xmppPassword = getEnvOrThrow("DANEEL_XMPP_PASSWORD");
131 const ownerJid = getEnvOrThrow("DANEEL_OWNER_JID");
132
133 const dataDir = getEnvOrDefault(
134 "DANEEL_DATA_DIR",
135 path.join(process.cwd(), "data")
136 );
137
138 const inboxPath = getEnvOrDefault(
139 "DANEEL_INBOX_PATH",
140 path.join(os.homedir(), "org", "inbox.org")
141 );
142
143 const defaultModelPrefix = getEnvOrDefault("DANEEL_DEFAULT_MODEL", "sonnet");
144 const defaultModel = MODEL_PREFIXES[`${defaultModelPrefix}:`] || {
145 provider: "anthropic" as Provider,
146 model: "claude-sonnet-4-20250514",
147 };
148
149 const providers: ProviderConfig = {};
150
151 // Load provider credentials from environment
152 if (process.env.ANTHROPIC_API_KEY) {
153 providers.anthropic = { apiKey: process.env.ANTHROPIC_API_KEY };
154 }
155 if (process.env.GOOGLE_API_KEY) {
156 providers.google = { apiKey: process.env.GOOGLE_API_KEY };
157 }
158 if (process.env.OPENAI_API_KEY) {
159 providers.openai = { apiKey: process.env.OPENAI_API_KEY };
160 }
161 if (process.env.GITHUB_TOKEN) {
162 providers.github = { token: process.env.GITHUB_TOKEN };
163 }
164 if (process.env.OLLAMA_BASE_URL) {
165 providers.ollama = {
166 baseUrl: getEnvOrDefault("OLLAMA_BASE_URL", "http://localhost:11434"),
167 };
168 }
169 if (process.env.GROQ_API_KEY) {
170 providers.groq = { apiKey: process.env.GROQ_API_KEY };
171 }
172 if (process.env.MISTRAL_API_KEY) {
173 providers.mistral = { apiKey: process.env.MISTRAL_API_KEY };
174 }
175 if (process.env.OPENROUTER_API_KEY) {
176 providers.openrouter = { apiKey: process.env.OPENROUTER_API_KEY };
177 }
178
179 return {
180 xmpp: {
181 jid: xmppJid,
182 password: xmppPassword,
183 ownerJid,
184 },
185 llm: {
186 defaultModel,
187 providers,
188 },
189 paths: {
190 dataDir,
191 inboxPath,
192 },
193 debug: process.env.DANEEL_DEBUG === "true",
194 };
195}
196
197// Parse model prefix from message
198export function parseModelPrefix(message: string): {
199 model: ModelInfo | null;
200 content: string;
201} {
202 for (const [prefix, modelInfo] of Object.entries(MODEL_PREFIXES)) {
203 if (message.toLowerCase().startsWith(prefix)) {
204 return {
205 model: modelInfo,
206 content: message.slice(prefix.length).trim(),
207 };
208 }
209 }
210 return { model: null, content: message };
211}