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}