Files

1 line
34 KiB
JSON

{"repo": "afatyoo/carbonio-ai-assitant", "files": {"gateway/src/config.js": {"b5550b2b75b7211c500835b45e227dbd32e3a59b": {"t": 1785746008.3910365, "n": 6394, "h": "e67c48525db3f1f4", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { parseScopedPolicy, resolveScopedPolicy } from './account-policy.js';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nconst readSecretFile = (filePath) => {\n\tif (!filePath) return '';\n\ttry {\n\t\treturn fs.readFileSync(filePath, 'utf8').trim();\n\t} catch {\n\t\treturn '';\n\t}\n};\n\nconst credentialDirectory = process.env.CREDENTIALS_DIRECTORY;\nconst credentialApiKey = readSecretFile(\n\tprocess.env.AI_AGENT_API_KEY_FILE ??\n\t\t(credentialDirectory ? path.join(credentialDirectory, 'carbonio-ai-agent-api-key') : '')\n);\n\nconst parseAllowlist = (value) =>\n\tString(value ?? '')\n\t\t.split(',')\n\t\t.map((item) => item.trim())\n\t\t.filter(Boolean);\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\t(process.env.AI_AGENT_URL ? inferProvider(process.env.AI_AGENT_URL) : 'custom');\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? credentialApiKey,\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst providerAllowlist = new Set(\n\tparseAllowlist(\n\t\tprocess.env.AI_PROVIDER_ALLOWLIST ?? 'openrouter,openai,anthropic,deepseek,gemini'\n\t)\n);\nconst modelPolicy = parseScopedPolicy(process.env.AI_MODEL_POLICY_JSON, 'AI_MODEL_POLICY_JSON');\n\nexport const getModelAllowlist = (account) => {\n\tconst configured = parseAllowlist(process.env.AI_MODEL_ALLOWLIST);\n\tconst globalAllowlist = configured.length ? configured : [runtimeConfig.model].filter(Boolean);\n\treturn resolveScopedPolicy(modelPolicy, account, globalAllowlist);\n};\n\nexport const assertModelAllowed = (model, account) => {\n\tconst normalized = String(model || runtimeConfig.model).trim();\n\tconst allowed = getModelAllowlist(account);\n\tif (!normalized || (!allowed.includes('*') && !allowed.includes(normalized))) {\n\t\tthrow new Error('Model is not allowed by administrator policy');\n\t}\n\treturn normalized;\n};\n\nconst validateCustomEndpoint = (url) => {\n\tif (process.env.AI_ALLOW_CUSTOM_ENDPOINT !== 'true') {\n\t\tthrow new Error('Custom endpoints are disabled by administrator policy');\n\t}\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== 'https:' && process.env.NODE_ENV === 'production') {\n\t\tthrow new Error('Custom endpoint must use HTTPS in production');\n\t}\n\tif (parsed.username || parsed.password) throw new Error('Endpoint credentials are not allowed');\n\tconst allowedHosts = parseAllowlist(process.env.AI_CUSTOM_ENDPOINT_HOSTS).map((host) =>\n\t\thost.toLowerCase()\n\t);\n\tif (!allowedHosts.length || !allowedHosts.includes(parsed.hostname.toLowerCase())) {\n\t\tthrow new Error('Custom endpoint host is not allowlisted');\n\t}\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tconst { apiKey: _apiKey, ...safeConfig } = runtimeConfig;\n\tfs.writeFileSync(configFile, JSON.stringify(safeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = (account) => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent',\n\tmodelAllowlist: getModelAllowlist(account),\n\tprocessingDisclosure:\n\t\t'Prompts and only the mailbox data needed for your request may be sent to the configured AI provider.'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (!providerAllowlist.has(nextProvider)) {\n\t\tthrow new Error('Provider is not allowed by administrator policy');\n\t}\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\tif (nextProvider === 'custom') validateCustomEndpoint(normalizedUrl);\n\tconst nextModel = assertModelAllowed(\n\t\ttypeof model === 'string' && model.trim() ? model.trim() : PROVIDERS[nextProvider].defaultModel\n\t);\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model = nextModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}, "f9a5ff342887d485e3872f12cae1e3717c26f2c0": {"t": 1785758749.1569743, "n": 6394, "h": "e67c48525db3f1f4", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { parseScopedPolicy, resolveScopedPolicy } from './account-policy.js';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nconst readSecretFile = (filePath) => {\n\tif (!filePath) return '';\n\ttry {\n\t\treturn fs.readFileSync(filePath, 'utf8').trim();\n\t} catch {\n\t\treturn '';\n\t}\n};\n\nconst credentialDirectory = process.env.CREDENTIALS_DIRECTORY;\nconst credentialApiKey = readSecretFile(\n\tprocess.env.AI_AGENT_API_KEY_FILE ??\n\t\t(credentialDirectory ? path.join(credentialDirectory, 'carbonio-ai-agent-api-key') : '')\n);\n\nconst parseAllowlist = (value) =>\n\tString(value ?? '')\n\t\t.split(',')\n\t\t.map((item) => item.trim())\n\t\t.filter(Boolean);\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\t(process.env.AI_AGENT_URL ? inferProvider(process.env.AI_AGENT_URL) : 'custom');\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? credentialApiKey,\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst providerAllowlist = new Set(\n\tparseAllowlist(\n\t\tprocess.env.AI_PROVIDER_ALLOWLIST ?? 'openrouter,openai,anthropic,deepseek,gemini'\n\t)\n);\nconst modelPolicy = parseScopedPolicy(process.env.AI_MODEL_POLICY_JSON, 'AI_MODEL_POLICY_JSON');\n\nexport const getModelAllowlist = (account) => {\n\tconst configured = parseAllowlist(process.env.AI_MODEL_ALLOWLIST);\n\tconst globalAllowlist = configured.length ? configured : [runtimeConfig.model].filter(Boolean);\n\treturn resolveScopedPolicy(modelPolicy, account, globalAllowlist);\n};\n\nexport const assertModelAllowed = (model, account) => {\n\tconst normalized = String(model || runtimeConfig.model).trim();\n\tconst allowed = getModelAllowlist(account);\n\tif (!normalized || (!allowed.includes('*') && !allowed.includes(normalized))) {\n\t\tthrow new Error('Model is not allowed by administrator policy');\n\t}\n\treturn normalized;\n};\n\nconst validateCustomEndpoint = (url) => {\n\tif (process.env.AI_ALLOW_CUSTOM_ENDPOINT !== 'true') {\n\t\tthrow new Error('Custom endpoints are disabled by administrator policy');\n\t}\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== 'https:' && process.env.NODE_ENV === 'production') {\n\t\tthrow new Error('Custom endpoint must use HTTPS in production');\n\t}\n\tif (parsed.username || parsed.password) throw new Error('Endpoint credentials are not allowed');\n\tconst allowedHosts = parseAllowlist(process.env.AI_CUSTOM_ENDPOINT_HOSTS).map((host) =>\n\t\thost.toLowerCase()\n\t);\n\tif (!allowedHosts.length || !allowedHosts.includes(parsed.hostname.toLowerCase())) {\n\t\tthrow new Error('Custom endpoint host is not allowlisted');\n\t}\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tconst { apiKey: _apiKey, ...safeConfig } = runtimeConfig;\n\tfs.writeFileSync(configFile, JSON.stringify(safeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = (account) => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent',\n\tmodelAllowlist: getModelAllowlist(account),\n\tprocessingDisclosure:\n\t\t'Prompts and only the mailbox data needed for your request may be sent to the configured AI provider.'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (!providerAllowlist.has(nextProvider)) {\n\t\tthrow new Error('Provider is not allowed by administrator policy');\n\t}\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\tif (nextProvider === 'custom') validateCustomEndpoint(normalizedUrl);\n\tconst nextModel = assertModelAllowed(\n\t\ttypeof model === 'string' && model.trim() ? model.trim() : PROVIDERS[nextProvider].defaultModel\n\t);\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model = nextModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}, "56a667392669efa6530b1c8ff486cf96193cd9e1": {"t": 1785758750.400861, "n": 5954, "h": "38cce79712fda14f", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { parseScopedPolicy, resolveScopedPolicy } from './account-policy.js';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nconst parseAllowlist = (value) =>\n\tString(value ?? '')\n\t\t.split(',')\n\t\t.map((item) => item.trim())\n\t\t.filter(Boolean);\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\t(process.env.AI_AGENT_URL ? inferProvider(process.env.AI_AGENT_URL) : 'custom');\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? savedConfig.apiKey ?? '',\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst providerAllowlist = new Set(\n\tparseAllowlist(\n\t\tprocess.env.AI_PROVIDER_ALLOWLIST ?? 'openrouter,openai,anthropic,deepseek,gemini'\n\t)\n);\nconst modelPolicy = parseScopedPolicy(process.env.AI_MODEL_POLICY_JSON, 'AI_MODEL_POLICY_JSON');\n\nexport const getModelAllowlist = (account) => {\n\tconst configured = parseAllowlist(process.env.AI_MODEL_ALLOWLIST);\n\tconst globalAllowlist = configured.length ? configured : [runtimeConfig.model].filter(Boolean);\n\treturn resolveScopedPolicy(modelPolicy, account, globalAllowlist);\n};\n\nexport const assertModelAllowed = (model, account) => {\n\tconst normalized = String(model || runtimeConfig.model).trim();\n\tconst allowed = getModelAllowlist(account);\n\tif (!normalized || (!allowed.includes('*') && !allowed.includes(normalized))) {\n\t\tthrow new Error('Model is not allowed by administrator policy');\n\t}\n\treturn normalized;\n};\n\nconst validateCustomEndpoint = (url) => {\n\tif (process.env.AI_ALLOW_CUSTOM_ENDPOINT !== 'true') {\n\t\tthrow new Error('Custom endpoints are disabled by administrator policy');\n\t}\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== 'https:' && process.env.NODE_ENV === 'production') {\n\t\tthrow new Error('Custom endpoint must use HTTPS in production');\n\t}\n\tif (parsed.username || parsed.password) throw new Error('Endpoint credentials are not allowed');\n\tconst allowedHosts = parseAllowlist(process.env.AI_CUSTOM_ENDPOINT_HOSTS).map((host) =>\n\t\thost.toLowerCase()\n\t);\n\tif (!allowedHosts.length || !allowedHosts.includes(parsed.hostname.toLowerCase())) {\n\t\tthrow new Error('Custom endpoint host is not allowlisted');\n\t}\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tfs.writeFileSync(configFile, JSON.stringify(runtimeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = (account) => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent',\n\tmodelAllowlist: getModelAllowlist(account),\n\tprocessingDisclosure:\n\t\t'Prompts and only the mailbox data needed for your request may be sent to the configured AI provider.'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (!providerAllowlist.has(nextProvider)) {\n\t\tthrow new Error('Provider is not allowed by administrator policy');\n\t}\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\tif (nextProvider === 'custom') validateCustomEndpoint(normalizedUrl);\n\tconst nextModel = assertModelAllowed(\n\t\ttypeof model === 'string' && model.trim() ? model.trim() : PROVIDERS[nextProvider].defaultModel\n\t);\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model = nextModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}, "a20145f0db794aef21172afa2151155b626de1d6": {"t": 1785758752.11081, "n": 5656, "h": "fc2dcbb03d303c11", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nconst parseAllowlist = (value) =>\n\tString(value ?? '')\n\t\t.split(',')\n\t\t.map((item) => item.trim())\n\t\t.filter(Boolean);\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\t(process.env.AI_AGENT_URL ? inferProvider(process.env.AI_AGENT_URL) : 'custom');\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? savedConfig.apiKey ?? '',\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst providerAllowlist = new Set(\n\tparseAllowlist(\n\t\tprocess.env.AI_PROVIDER_ALLOWLIST ?? 'openrouter,openai,anthropic,deepseek,gemini'\n\t)\n);\n\nexport const getModelAllowlist = () => {\n\tconst configured = parseAllowlist(process.env.AI_MODEL_ALLOWLIST);\n\treturn configured.length ? configured : [runtimeConfig.model].filter(Boolean);\n};\n\nexport const assertModelAllowed = (model) => {\n\tconst normalized = String(model || runtimeConfig.model).trim();\n\tconst allowed = getModelAllowlist();\n\tif (!normalized || (!allowed.includes('*') && !allowed.includes(normalized))) {\n\t\tthrow new Error('Model is not allowed by administrator policy');\n\t}\n\treturn normalized;\n};\n\nconst validateCustomEndpoint = (url) => {\n\tif (process.env.AI_ALLOW_CUSTOM_ENDPOINT !== 'true') {\n\t\tthrow new Error('Custom endpoints are disabled by administrator policy');\n\t}\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== 'https:' && process.env.NODE_ENV === 'production') {\n\t\tthrow new Error('Custom endpoint must use HTTPS in production');\n\t}\n\tif (parsed.username || parsed.password) throw new Error('Endpoint credentials are not allowed');\n\tconst allowedHosts = parseAllowlist(process.env.AI_CUSTOM_ENDPOINT_HOSTS).map((host) =>\n\t\thost.toLowerCase()\n\t);\n\tif (!allowedHosts.length || !allowedHosts.includes(parsed.hostname.toLowerCase())) {\n\t\tthrow new Error('Custom endpoint host is not allowlisted');\n\t}\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tfs.writeFileSync(configFile, JSON.stringify(runtimeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = () => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent',\n\tmodelAllowlist: getModelAllowlist(),\n\tprocessingDisclosure:\n\t\t'Prompts and only the mailbox data needed for your request may be sent to the configured AI provider.'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (!providerAllowlist.has(nextProvider)) {\n\t\tthrow new Error('Provider is not allowed by administrator policy');\n\t}\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\tif (nextProvider === 'custom') validateCustomEndpoint(normalizedUrl);\n\tconst nextModel = assertModelAllowed(\n\t\ttypeof model === 'string' && model.trim() ? model.trim() : PROVIDERS[nextProvider].defaultModel\n\t);\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model = nextModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}, "82902d7bf043995dc7dee2d2163e562154fe3ccf": {"t": 1785758753.5395455, "n": 3749, "h": "648bb8a083af354a", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\t(process.env.AI_AGENT_URL ? inferProvider(process.env.AI_AGENT_URL) : 'custom');\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? savedConfig.apiKey ?? '',\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tfs.writeFileSync(configFile, JSON.stringify(runtimeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = () => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model =\n\t\ttypeof model === 'string' && model.trim()\n\t\t\t? model.trim()\n\t\t\t: PROVIDERS[nextProvider].defaultModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}, "7fbb6d87014ee4d8f7967341bf205eb3158e9c7d": {"t": 1785758755.0921001, "n": 3733, "h": "1408202f46bb4abb", "c": "import fs from 'node:fs';\nimport path from 'node:path';\n\nconst configDirectory = path.resolve('.runtime');\nconst configFile = path.join(configDirectory, 'config.json');\n\nconst loadSavedConfig = () => {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(configFile, 'utf8'));\n\t} catch {\n\t\treturn {};\n\t}\n};\n\nconst savedConfig = loadSavedConfig();\n\nexport const PROVIDERS = {\n\topenrouter: {\n\t\tname: 'OpenRouter',\n\t\tendpoint: 'https://openrouter.ai/api/v1',\n\t\tdefaultModel: 'openrouter/free',\n\t\tprotocol: 'openai'\n\t},\n\topenai: {\n\t\tname: 'OpenAI',\n\t\tendpoint: 'https://api.openai.com/v1',\n\t\tdefaultModel: 'gpt-5.4-mini',\n\t\tprotocol: 'openai'\n\t},\n\tanthropic: {\n\t\tname: 'Anthropic Claude',\n\t\tendpoint: 'https://api.anthropic.com/v1/messages',\n\t\tdefaultModel: 'claude-sonnet-4-6',\n\t\tprotocol: 'anthropic'\n\t},\n\tdeepseek: {\n\t\tname: 'DeepSeek',\n\t\tendpoint: 'https://api.deepseek.com',\n\t\tdefaultModel: 'deepseek-v4-flash',\n\t\tprotocol: 'openai'\n\t},\n\tgemini: {\n\t\tname: 'Google Gemini',\n\t\tendpoint: 'https://generativelanguage.googleapis.com/v1beta',\n\t\tdefaultModel: 'gemini-3.5-flash',\n\t\tprotocol: 'gemini'\n\t},\n\tcustom: {\n\t\tname: 'Custom endpoint',\n\t\tendpoint: '',\n\t\tdefaultModel: '',\n\t\tprotocol: 'custom'\n\t}\n};\n\nconst inferProvider = (url) => {\n\tif (url?.includes('openrouter.ai')) return 'openrouter';\n\tif (url?.includes('api.openai.com')) return 'openai';\n\tif (url?.includes('anthropic.com')) return 'anthropic';\n\tif (url?.includes('deepseek.com')) return 'deepseek';\n\tif (url?.includes('generativelanguage.googleapis.com')) return 'gemini';\n\treturn url ? 'custom' : 'openrouter';\n};\n\nconst initialProvider =\n\tprocess.env.AI_AGENT_PROVIDER ??\n\tsavedConfig.provider ??\n\tinferProvider(process.env.AI_AGENT_URL ?? savedConfig.agentUrl);\nconst runtimeConfig = {\n\tprovider: initialProvider,\n\tagentUrl:\n\t\tprocess.env.AI_AGENT_URL ??\n\t\tsavedConfig.agentUrl ??\n\t\tPROVIDERS[initialProvider]?.endpoint ??\n\t\t'',\n\tapiKey: process.env.AI_AGENT_API_KEY ?? savedConfig.apiKey ?? '',\n\tmodel:\n\t\tprocess.env.AI_AGENT_MODEL ??\n\t\tsavedConfig.model ??\n\t\tPROVIDERS[initialProvider]?.defaultModel ??\n\t\t''\n};\n\nconst persistConfig = () => {\n\tfs.mkdirSync(configDirectory, { recursive: true, mode: 0o700 });\n\tfs.writeFileSync(configFile, JSON.stringify(runtimeConfig, null, 2), { mode: 0o600 });\n\tfs.chmodSync(configFile, 0o600);\n};\n\nexport const getAgentConfig = () => ({ ...runtimeConfig });\n\nexport const getPublicAgentConfig = () => ({\n\tprovider: runtimeConfig.provider,\n\tagentUrl: runtimeConfig.agentUrl,\n\thasApiKey: Boolean(runtimeConfig.apiKey),\n\tmodel: runtimeConfig.model,\n\tmode: runtimeConfig.provider === 'custom' && !runtimeConfig.agentUrl ? 'local-agent' : 'remote-agent'\n});\n\nexport const updateAgentConfig = ({\n\tprovider,\n\tagentUrl = '',\n\tapiKey,\n\tmodel,\n\tclearApiKey = false\n}) => {\n\tconst nextProvider = provider ?? runtimeConfig.provider;\n\tif (!PROVIDERS[nextProvider]) throw new Error('Unsupported provider');\n\tif (typeof agentUrl !== 'string') throw new Error('agentUrl must be a string');\n\tconst normalizedUrl =\n\t\tnextProvider === 'custom' ? agentUrl.trim() : PROVIDERS[nextProvider].endpoint;\n\tif (normalizedUrl) {\n\t\tconst parsed = new URL(normalizedUrl);\n\t\tif (!['http:', 'https:'].includes(parsed.protocol)) {\n\t\t\tthrow new Error('Agent URL must use HTTP or HTTPS');\n\t\t}\n\t}\n\n\tif (nextProvider !== runtimeConfig.provider && !apiKey) runtimeConfig.apiKey = '';\n\truntimeConfig.provider = nextProvider;\n\truntimeConfig.agentUrl = normalizedUrl;\n\truntimeConfig.model =\n\t\ttypeof model === 'string' && model.trim()\n\t\t\t? model.trim()\n\t\t\t: PROVIDERS[nextProvider].defaultModel;\n\tif (clearApiKey) runtimeConfig.apiKey = '';\n\tif (typeof apiKey === 'string' && apiKey.trim()) runtimeConfig.apiKey = apiKey.trim();\n\tpersistConfig();\n\n\treturn getPublicAgentConfig();\n};\n"}}}}