quoroom 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/out/mcp/api-server.js +124 -38
- package/out/mcp/cli.js +126 -40
- package/out/mcp/server.js +40 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://www.npmjs.com/package/quoroom)
|
|
11
|
-
[](#)
|
|
12
12
|
[](https://github.com/quoroom-ai/room/stargazers)
|
|
13
13
|
[](https://github.com/quoroom-ai/room/releases/latest)
|
|
14
14
|
[](https://github.com/quoroom-ai/room/releases/latest)
|
|
@@ -59,7 +59,7 @@ The architecture draws from swarm intelligence research: decentralized decision-
|
|
|
59
59
|
|
|
60
60
|
Quoroom is an open research project exploring autonomous agent collectives. Each collective (a **Room**) is a self-governing swarm of agents.
|
|
61
61
|
|
|
62
|
-
- **Queen** — strategic brain, supports Claude/Codex subscriptions and OpenAI/Claude API
|
|
62
|
+
- **Queen** — strategic brain, supports Claude/Codex subscriptions and OpenAI/Claude/Gemini API
|
|
63
63
|
- **Workers** — specialized agents that use the queen model
|
|
64
64
|
- **Quorum** — agents deliberate and vote on decisions
|
|
65
65
|
- **Keeper** — the human who sets goals and funds the wallet
|
|
@@ -250,7 +250,7 @@ Open **http://localhost:3700** (or the port shown in your terminal). The dashboa
|
|
|
250
250
|
The **Clerk** tab is your global assistant for the whole local system (not a single room).
|
|
251
251
|
|
|
252
252
|
- Clerk is a full assistant, not only commentary: it can reason, remember, and execute actions for the keeper
|
|
253
|
-
- Setup paths: Claude subscription (`claude`), Codex subscription (`codex`), OpenAI API (`openai:gpt-4o-mini`), Anthropic API (`anthropic:claude-3-5-sonnet-latest`)
|
|
253
|
+
- Setup paths: Claude subscription (`claude`), Codex subscription (`codex`), OpenAI API (`openai:gpt-4o-mini`), Anthropic API (`anthropic:claude-3-5-sonnet-latest`), Gemini API (`gemini:gemini-2.5-flash`)
|
|
254
254
|
- API keys entered in Clerk Setup are validated before saving
|
|
255
255
|
- Clerk can answer and do: room lifecycle, room settings, task creation, reminders, inter-room messaging, and keeper communication
|
|
256
256
|
- Clerk can act proactively through scheduled tasks/reminders and activity-driven commentary
|
|
@@ -260,9 +260,9 @@ The **Clerk** tab is your global assistant for the whole local system (not a sin
|
|
|
260
260
|
|
|
261
261
|
API key resolution for Clerk API models:
|
|
262
262
|
|
|
263
|
-
1. Any room credential (`openai_api_key` or `
|
|
264
|
-
2. Clerk-saved API key (`clerk_openai_api_key` / `clerk_anthropic_api_key`)
|
|
265
|
-
3. Environment variable (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`)
|
|
263
|
+
1. Any room credential (`openai_api_key`, `anthropic_api_key`, or `gemini_api_key`)
|
|
264
|
+
2. Clerk-saved API key (`clerk_openai_api_key` / `clerk_anthropic_api_key` / `clerk_gemini_api_key`)
|
|
265
|
+
3. Environment variable (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY`)
|
|
266
266
|
|
|
267
267
|
See full guide: [docs/CLERK.md](docs/CLERK.md)
|
|
268
268
|
|
|
@@ -536,10 +536,11 @@ Use your existing Claude or ChatGPT subscription, or bring an API key.
|
|
|
536
536
|
| `codex` | OpenAI Codex CLI | Spawns CLI process | `npm i -g @openai/codex` |
|
|
537
537
|
| `openai:gpt-4o-mini` | OpenAI API | HTTP REST | `OPENAI_API_KEY` |
|
|
538
538
|
| `anthropic:claude-3-5-sonnet-latest` | Anthropic API | HTTP REST | `ANTHROPIC_API_KEY` |
|
|
539
|
+
| `gemini:gemini-2.5-flash` | Gemini API | HTTP REST | `GEMINI_API_KEY` |
|
|
539
540
|
|
|
540
541
|
**CLI models** (`claude`, `codex`) — Full agentic loop with tool use via the CLI. Session continuity via `--resume`. On Windows, `.cmd` wrappers are auto-resolved to underlying `.js` scripts to bypass the cmd.exe 8191-char argument limit.
|
|
541
542
|
|
|
542
|
-
**API models** (`openai:*`, `anthropic:*`) — Direct HTTP calls. Support multi-turn tool-calling loops. API keys resolve from: room credentials → Clerk-saved keys → environment variables. `anthropic:*` also accepts the `claude-api:` prefix.
|
|
543
|
+
**API models** (`openai:*`, `anthropic:*`, `gemini:*`) — Direct HTTP calls. Support multi-turn tool-calling loops. API keys resolve from: room credentials → Clerk-saved keys → environment variables. `anthropic:*` also accepts the `claude-api:` prefix. `gemini:*` uses Google's OpenAI-compatible endpoint.
|
|
543
544
|
|
|
544
545
|
Workers inherit the queen's model by default, or can use a separate API model.
|
|
545
546
|
|
package/out/mcp/api-server.js
CHANGED
|
@@ -9914,7 +9914,7 @@ var require_package = __commonJS({
|
|
|
9914
9914
|
"package.json"(exports2, module2) {
|
|
9915
9915
|
module2.exports = {
|
|
9916
9916
|
name: "quoroom",
|
|
9917
|
-
version: "0.1.
|
|
9917
|
+
version: "0.1.30",
|
|
9918
9918
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
9919
9919
|
main: "./out/mcp/server.js",
|
|
9920
9920
|
bin: {
|
|
@@ -13045,7 +13045,9 @@ function getClerkUsageToday(db2, source) {
|
|
|
13045
13045
|
};
|
|
13046
13046
|
}
|
|
13047
13047
|
function clerkApiKeySetting(provider) {
|
|
13048
|
-
|
|
13048
|
+
if (provider === "openai_api") return "clerk_openai_api_key";
|
|
13049
|
+
if (provider === "gemini_api") return "clerk_gemini_api_key";
|
|
13050
|
+
return "clerk_anthropic_api_key";
|
|
13049
13051
|
}
|
|
13050
13052
|
function setClerkApiKey(db2, provider, value) {
|
|
13051
13053
|
const trimmed = value.trim();
|
|
@@ -23388,7 +23390,7 @@ async function executeAgent(options) {
|
|
|
23388
23390
|
if (model === "codex" || model.startsWith("codex:")) {
|
|
23389
23391
|
return executeCodex(options);
|
|
23390
23392
|
}
|
|
23391
|
-
if (model === "openai" || model.startsWith("openai:")) {
|
|
23393
|
+
if (model === "openai" || model.startsWith("openai:") || model === "gemini" || model.startsWith("gemini:")) {
|
|
23392
23394
|
if (options.toolDefs && options.toolDefs.length > 0 && options.onToolCall) {
|
|
23393
23395
|
return executeOpenAiWithTools(options);
|
|
23394
23396
|
}
|
|
@@ -23558,10 +23560,34 @@ async function executeCodex(options) {
|
|
|
23558
23560
|
});
|
|
23559
23561
|
});
|
|
23560
23562
|
}
|
|
23563
|
+
function resolveOpenAiCompatible(model, apiKeyOverride) {
|
|
23564
|
+
const trimmed = model.trim();
|
|
23565
|
+
if (trimmed === "gemini" || trimmed.startsWith("gemini:")) {
|
|
23566
|
+
const apiKey2 = apiKeyOverride?.trim() || (process.env.GEMINI_API_KEY || "").trim();
|
|
23567
|
+
if (!apiKey2) return null;
|
|
23568
|
+
return {
|
|
23569
|
+
apiKey: apiKey2,
|
|
23570
|
+
url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
23571
|
+
defaultModel: "gemini-2.5-flash",
|
|
23572
|
+
label: "Gemini",
|
|
23573
|
+
prefix: "gemini"
|
|
23574
|
+
};
|
|
23575
|
+
}
|
|
23576
|
+
const apiKey = apiKeyOverride?.trim() || (process.env.OPENAI_API_KEY || "").trim();
|
|
23577
|
+
if (!apiKey) return null;
|
|
23578
|
+
return {
|
|
23579
|
+
apiKey,
|
|
23580
|
+
url: "https://api.openai.com/v1/chat/completions",
|
|
23581
|
+
defaultModel: "gpt-4o-mini",
|
|
23582
|
+
label: "OpenAI",
|
|
23583
|
+
prefix: "openai"
|
|
23584
|
+
};
|
|
23585
|
+
}
|
|
23561
23586
|
async function executeOpenAiWithTools(options) {
|
|
23562
|
-
const
|
|
23563
|
-
if (!
|
|
23564
|
-
const
|
|
23587
|
+
const config = resolveOpenAiCompatible(options.model, options.apiKey);
|
|
23588
|
+
if (!config) return immediateError(`Missing ${options.model.startsWith("gemini") ? "Gemini" : "OpenAI"} API key.`);
|
|
23589
|
+
const { apiKey, url: apiUrl, defaultModel, label: providerLabel, prefix } = config;
|
|
23590
|
+
const modelName = parseModelSuffix(options.model, prefix) || defaultModel;
|
|
23565
23591
|
const startTime = Date.now();
|
|
23566
23592
|
const maxTurns = options.maxTurns ?? 10;
|
|
23567
23593
|
const previousTurns = options.previousMessages ?? [];
|
|
@@ -23585,7 +23611,7 @@ Continue working toward the goal.` : options.prompt
|
|
|
23585
23611
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
23586
23612
|
let json;
|
|
23587
23613
|
try {
|
|
23588
|
-
const response = await fetch(
|
|
23614
|
+
const response = await fetch(apiUrl, {
|
|
23589
23615
|
method: "POST",
|
|
23590
23616
|
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
23591
23617
|
body: JSON.stringify({ model: modelName, messages, tools: options.toolDefs }),
|
|
@@ -23593,7 +23619,7 @@ Continue working toward the goal.` : options.prompt
|
|
|
23593
23619
|
});
|
|
23594
23620
|
json = await response.json();
|
|
23595
23621
|
if (!response.ok) {
|
|
23596
|
-
return { output:
|
|
23622
|
+
return { output: `${providerLabel} API ${response.status}: ${extractApiError(json)}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut: false, usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
|
|
23597
23623
|
}
|
|
23598
23624
|
} catch (err) {
|
|
23599
23625
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
@@ -23737,11 +23763,13 @@ Continue working toward the goal.` : options.prompt
|
|
|
23737
23763
|
return { output: finalOutput || "Actions completed.", exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false, usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
|
|
23738
23764
|
}
|
|
23739
23765
|
async function executeOpenAiApi(options) {
|
|
23740
|
-
const
|
|
23741
|
-
if (!
|
|
23742
|
-
|
|
23766
|
+
const config = resolveOpenAiCompatible(options.model, options.apiKey);
|
|
23767
|
+
if (!config) {
|
|
23768
|
+
const isGemini = options.model.startsWith("gemini");
|
|
23769
|
+
return immediateError(`Missing ${isGemini ? "Gemini" : "OpenAI"} API key. Set room credential "${isGemini ? "gemini_api_key" : "openai_api_key"}" or ${isGemini ? "GEMINI_API_KEY" : "OPENAI_API_KEY"}.`);
|
|
23743
23770
|
}
|
|
23744
|
-
const
|
|
23771
|
+
const { apiKey, url: apiUrl, defaultModel, label: providerLabel, prefix } = config;
|
|
23772
|
+
const modelName = parseModelSuffix(options.model, prefix) || defaultModel;
|
|
23745
23773
|
const messages = [];
|
|
23746
23774
|
if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
|
|
23747
23775
|
messages.push({ role: "user", content: options.prompt });
|
|
@@ -23750,7 +23778,7 @@ async function executeOpenAiApi(options) {
|
|
|
23750
23778
|
const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
23751
23779
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
23752
23780
|
try {
|
|
23753
|
-
const response = await fetch(
|
|
23781
|
+
const response = await fetch(apiUrl, {
|
|
23754
23782
|
method: "POST",
|
|
23755
23783
|
headers: {
|
|
23756
23784
|
"Authorization": `Bearer ${apiKey}`,
|
|
@@ -23765,7 +23793,7 @@ async function executeOpenAiApi(options) {
|
|
|
23765
23793
|
const json = await response.json();
|
|
23766
23794
|
if (!response.ok) {
|
|
23767
23795
|
return {
|
|
23768
|
-
output:
|
|
23796
|
+
output: `${providerLabel} API ${response.status}: ${extractApiError(json)}`,
|
|
23769
23797
|
exitCode: 1,
|
|
23770
23798
|
durationMs: Date.now() - startTime,
|
|
23771
23799
|
sessionId: null,
|
|
@@ -23981,13 +24009,13 @@ Respond ONLY with a JSON object (no markdown, no explanation):
|
|
|
23981
24009
|
}`;
|
|
23982
24010
|
const timeoutMs = 6e4;
|
|
23983
24011
|
try {
|
|
23984
|
-
if (model === "openai" || model.startsWith("openai:")) {
|
|
23985
|
-
const
|
|
23986
|
-
if (!
|
|
23987
|
-
const modelName = parseModelSuffix(model,
|
|
23988
|
-
const response = await fetch(
|
|
24012
|
+
if (model === "openai" || model.startsWith("openai:") || model === "gemini" || model.startsWith("gemini:")) {
|
|
24013
|
+
const config = resolveOpenAiCompatible(model, apiKey);
|
|
24014
|
+
if (!config) return null;
|
|
24015
|
+
const modelName = parseModelSuffix(model, config.prefix) || config.defaultModel;
|
|
24016
|
+
const response = await fetch(config.url, {
|
|
23989
24017
|
method: "POST",
|
|
23990
|
-
headers: { "Authorization": `Bearer ${
|
|
24018
|
+
headers: { "Authorization": `Bearer ${config.apiKey}`, "Content-Type": "application/json" },
|
|
23991
24019
|
body: JSON.stringify({ model: modelName, messages: [{ role: "user", content: compressionPrompt }] }),
|
|
23992
24020
|
signal: AbortSignal.timeout(timeoutMs)
|
|
23993
24021
|
});
|
|
@@ -24017,16 +24045,17 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
24017
24045
|
if (!apiKey) {
|
|
24018
24046
|
return immediateError("Missing API key for station execution.");
|
|
24019
24047
|
}
|
|
24020
|
-
const
|
|
24048
|
+
const isOpenAiCompat = options.model.startsWith("openai:") || options.model.startsWith("gemini:");
|
|
24021
24049
|
const messages = [];
|
|
24022
24050
|
if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
|
|
24023
24051
|
messages.push({ role: "user", content: options.prompt });
|
|
24024
24052
|
let url;
|
|
24025
24053
|
let headers;
|
|
24026
24054
|
let body;
|
|
24027
|
-
if (
|
|
24028
|
-
const
|
|
24029
|
-
|
|
24055
|
+
if (isOpenAiCompat) {
|
|
24056
|
+
const config = resolveOpenAiCompatible(options.model, apiKey);
|
|
24057
|
+
const modelName = config ? parseModelSuffix(options.model, config.prefix) || config.defaultModel : parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
|
|
24058
|
+
url = config?.url ?? "https://api.openai.com/v1/chat/completions";
|
|
24030
24059
|
headers = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
|
|
24031
24060
|
body = JSON.stringify({ model: modelName, messages });
|
|
24032
24061
|
} else {
|
|
@@ -24064,7 +24093,7 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
24064
24093
|
}
|
|
24065
24094
|
try {
|
|
24066
24095
|
const parsed = JSON.parse(result.stdout);
|
|
24067
|
-
const output =
|
|
24096
|
+
const output = isOpenAiCompat ? extractOpenAiText(parsed) : extractAnthropicText(parsed);
|
|
24068
24097
|
return { output, exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
|
|
24069
24098
|
} catch {
|
|
24070
24099
|
return {
|
|
@@ -24385,6 +24414,7 @@ function getModelProvider(model) {
|
|
|
24385
24414
|
if (normalized === "anthropic" || normalized.startsWith("anthropic:") || normalized.startsWith("claude-api:")) {
|
|
24386
24415
|
return "anthropic_api";
|
|
24387
24416
|
}
|
|
24417
|
+
if (normalized === "gemini" || normalized.startsWith("gemini:")) return "gemini_api";
|
|
24388
24418
|
return "claude_subscription";
|
|
24389
24419
|
}
|
|
24390
24420
|
async function getModelAuthStatus(db2, roomId, model) {
|
|
@@ -24395,6 +24425,9 @@ async function getModelAuthStatus(db2, roomId, model) {
|
|
|
24395
24425
|
if (provider === "anthropic_api") {
|
|
24396
24426
|
return resolveApiAuthStatus(db2, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY", provider);
|
|
24397
24427
|
}
|
|
24428
|
+
if (provider === "gemini_api") {
|
|
24429
|
+
return resolveApiAuthStatus(db2, roomId, "gemini_api_key", "GEMINI_API_KEY", provider);
|
|
24430
|
+
}
|
|
24398
24431
|
let ready = false;
|
|
24399
24432
|
if (provider === "claude_subscription") {
|
|
24400
24433
|
ready = checkClaudeCliAvailable().available;
|
|
@@ -24420,6 +24453,9 @@ function resolveApiKeyForModel(db2, roomId, model) {
|
|
|
24420
24453
|
if (provider === "anthropic_api") {
|
|
24421
24454
|
return resolveApiKey(db2, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY");
|
|
24422
24455
|
}
|
|
24456
|
+
if (provider === "gemini_api") {
|
|
24457
|
+
return resolveApiKey(db2, roomId, "gemini_api_key", "GEMINI_API_KEY");
|
|
24458
|
+
}
|
|
24423
24459
|
return void 0;
|
|
24424
24460
|
}
|
|
24425
24461
|
function resolveApiAuthStatus(db2, roomId, credentialName, envVar, provider) {
|
|
@@ -24471,6 +24507,9 @@ function getClerkCredential(db2, credentialName) {
|
|
|
24471
24507
|
if (credentialName === "anthropic_api_key") {
|
|
24472
24508
|
return getClerkApiKey(db2, "anthropic_api");
|
|
24473
24509
|
}
|
|
24510
|
+
if (credentialName === "gemini_api_key") {
|
|
24511
|
+
return getClerkApiKey(db2, "gemini_api");
|
|
24512
|
+
}
|
|
24474
24513
|
return null;
|
|
24475
24514
|
}
|
|
24476
24515
|
function getRoomCredential(db2, roomId, credentialName) {
|
|
@@ -25811,10 +25850,10 @@ async function runCycle(db2, roomId, worker, maxTurns, options) {
|
|
|
25811
25850
|
options?.onCycleLifecycle?.("created", cycle.id, roomId);
|
|
25812
25851
|
try {
|
|
25813
25852
|
const provider = getModelProvider(model);
|
|
25814
|
-
if (provider === "openai_api" || provider === "anthropic_api") {
|
|
25853
|
+
if (provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") {
|
|
25815
25854
|
const apiKeyCheck = resolveApiKeyForModel(db2, roomId, model);
|
|
25816
25855
|
if (!apiKeyCheck) {
|
|
25817
|
-
const label = provider === "openai_api" ? "OpenAI" : "Anthropic";
|
|
25856
|
+
const label = provider === "openai_api" ? "OpenAI" : provider === "gemini_api" ? "Gemini" : "Anthropic";
|
|
25818
25857
|
const msg = `Missing ${label} API key. Set it in Room Settings or the Setup Guide.`;
|
|
25819
25858
|
logBuffer2.addSynthetic("error", msg);
|
|
25820
25859
|
logBuffer2.flush();
|
|
@@ -27835,8 +27874,8 @@ function maskKey2(key) {
|
|
|
27835
27874
|
return `${trimmed.slice(0, 7)}...${trimmed.slice(-4)}`;
|
|
27836
27875
|
}
|
|
27837
27876
|
function getClerkApiAuthState(db2, provider) {
|
|
27838
|
-
const credentialName = provider === "openai_api" ? "openai_api_key" : "anthropic_api_key";
|
|
27839
|
-
const envVar = provider === "openai_api" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
27877
|
+
const credentialName = provider === "openai_api" ? "openai_api_key" : provider === "gemini_api" ? "gemini_api_key" : "anthropic_api_key";
|
|
27878
|
+
const envVar = provider === "openai_api" ? "OPENAI_API_KEY" : provider === "gemini_api" ? "GEMINI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
27840
27879
|
const roomCredential = findAnyRoomCredential2(db2, credentialName);
|
|
27841
27880
|
const savedKey = getClerkApiKey(db2, provider);
|
|
27842
27881
|
const envKey = (process.env[envVar] || "").trim() || null;
|
|
@@ -27852,7 +27891,8 @@ function getClerkApiAuthState(db2, provider) {
|
|
|
27852
27891
|
function getClerkApiAuth(db2) {
|
|
27853
27892
|
return {
|
|
27854
27893
|
openai: getClerkApiAuthState(db2, "openai_api"),
|
|
27855
|
-
anthropic: getClerkApiAuthState(db2, "anthropic_api")
|
|
27894
|
+
anthropic: getClerkApiAuthState(db2, "anthropic_api"),
|
|
27895
|
+
gemini: getClerkApiAuthState(db2, "gemini_api")
|
|
27856
27896
|
};
|
|
27857
27897
|
}
|
|
27858
27898
|
function autoConfigureClerkModel(db2) {
|
|
@@ -27872,6 +27912,9 @@ function resolveClerkApiKey(db2, model) {
|
|
|
27872
27912
|
if (provider === "anthropic_api") {
|
|
27873
27913
|
return findAnyRoomCredential2(db2, "anthropic_api_key") || getClerkApiKey(db2, "anthropic_api") || (process.env.ANTHROPIC_API_KEY || void 0);
|
|
27874
27914
|
}
|
|
27915
|
+
if (provider === "gemini_api") {
|
|
27916
|
+
return findAnyRoomCredential2(db2, "gemini_api_key") || getClerkApiKey(db2, "gemini_api") || (process.env.GEMINI_API_KEY || void 0);
|
|
27917
|
+
}
|
|
27875
27918
|
return void 0;
|
|
27876
27919
|
}
|
|
27877
27920
|
function clipText(value, max = 240) {
|
|
@@ -28019,6 +28062,11 @@ function buildClerkModelPlan(preferredModel) {
|
|
|
28019
28062
|
uniquePush(plan, CLERK_FALLBACK_SUBSCRIPTION_MODEL);
|
|
28020
28063
|
uniquePush(plan, CLERK_FALLBACK_OPENAI_MODEL);
|
|
28021
28064
|
uniquePush(plan, DEFAULT_CLERK_MODEL);
|
|
28065
|
+
} else if (provider === "gemini_api") {
|
|
28066
|
+
uniquePush(plan, CLERK_FALLBACK_SUBSCRIPTION_MODEL);
|
|
28067
|
+
uniquePush(plan, CLERK_FALLBACK_OPENAI_MODEL);
|
|
28068
|
+
uniquePush(plan, DEFAULT_CLERK_MODEL);
|
|
28069
|
+
uniquePush(plan, CLERK_FALLBACK_ANTHROPIC_MODEL);
|
|
28022
28070
|
}
|
|
28023
28071
|
return plan;
|
|
28024
28072
|
}
|
|
@@ -28027,7 +28075,7 @@ function buildExecutionCandidates(db2, preferredModel) {
|
|
|
28027
28075
|
for (const model of buildClerkModelPlan(preferredModel)) {
|
|
28028
28076
|
const provider = getModelProvider(model);
|
|
28029
28077
|
const apiKey = resolveClerkApiKey(db2, model);
|
|
28030
|
-
if ((provider === "openai_api" || provider === "anthropic_api") && !apiKey) continue;
|
|
28078
|
+
if ((provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") && !apiKey) continue;
|
|
28031
28079
|
candidates.push({ model, apiKey });
|
|
28032
28080
|
}
|
|
28033
28081
|
if (candidates.length === 0) {
|
|
@@ -29700,6 +29748,25 @@ async function validateOpenAiKey(value) {
|
|
|
29700
29748
|
clearTimeout(timer);
|
|
29701
29749
|
}
|
|
29702
29750
|
}
|
|
29751
|
+
async function validateGeminiKey(value) {
|
|
29752
|
+
const controller = new AbortController();
|
|
29753
|
+
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
29754
|
+
try {
|
|
29755
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(value)}`, {
|
|
29756
|
+
method: "GET",
|
|
29757
|
+
signal: controller.signal
|
|
29758
|
+
});
|
|
29759
|
+
if (res.ok) return { ok: true };
|
|
29760
|
+
const body = await res.json().catch(() => null);
|
|
29761
|
+
const message = extractApiError2(body) || `HTTP ${res.status}`;
|
|
29762
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
29763
|
+
} catch (err) {
|
|
29764
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29765
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
29766
|
+
} finally {
|
|
29767
|
+
clearTimeout(timer);
|
|
29768
|
+
}
|
|
29769
|
+
}
|
|
29703
29770
|
async function validateAnthropicKey(value) {
|
|
29704
29771
|
const controller = new AbortController();
|
|
29705
29772
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
@@ -30037,11 +30104,11 @@ function registerClerkRoutes(router) {
|
|
|
30037
30104
|
const body = ctx.body || {};
|
|
30038
30105
|
const provider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
30039
30106
|
const key = typeof body.key === "string" ? body.key.trim() : "";
|
|
30040
|
-
if (provider !== "openai_api" && provider !== "anthropic_api") {
|
|
30041
|
-
return { status: 400, error: "provider must be openai_api or
|
|
30107
|
+
if (provider !== "openai_api" && provider !== "anthropic_api" && provider !== "gemini_api") {
|
|
30108
|
+
return { status: 400, error: "provider must be openai_api, anthropic_api, or gemini_api" };
|
|
30042
30109
|
}
|
|
30043
30110
|
if (!key) return { status: 400, error: "key is required" };
|
|
30044
|
-
const result = provider === "openai_api" ? await validateOpenAiKey(key) : await validateAnthropicKey(key);
|
|
30111
|
+
const result = provider === "openai_api" ? await validateOpenAiKey(key) : provider === "gemini_api" ? await validateGeminiKey(key) : await validateAnthropicKey(key);
|
|
30045
30112
|
if (!result.ok) return { status: 400, error: result.error };
|
|
30046
30113
|
setClerkApiKey(ctx.db, provider, key);
|
|
30047
30114
|
return { data: { ok: true, apiAuth: getClerkApiAuth(ctx.db) } };
|
|
@@ -33111,7 +33178,7 @@ function semverGt(a, b) {
|
|
|
33111
33178
|
}
|
|
33112
33179
|
function getCurrentVersion() {
|
|
33113
33180
|
try {
|
|
33114
|
-
return true ? "0.1.
|
|
33181
|
+
return true ? "0.1.30" : null.version;
|
|
33115
33182
|
} catch {
|
|
33116
33183
|
return "0.0.0";
|
|
33117
33184
|
}
|
|
@@ -33270,7 +33337,7 @@ var cachedVersion = null;
|
|
|
33270
33337
|
function getVersion3() {
|
|
33271
33338
|
if (cachedVersion) return cachedVersion;
|
|
33272
33339
|
try {
|
|
33273
|
-
cachedVersion = true ? "0.1.
|
|
33340
|
+
cachedVersion = true ? "0.1.30" : null.version;
|
|
33274
33341
|
} catch {
|
|
33275
33342
|
cachedVersion = "unknown";
|
|
33276
33343
|
}
|
|
@@ -33614,7 +33681,7 @@ function maskCredential(credential) {
|
|
|
33614
33681
|
return { ...credential, valueEncrypted: "***" };
|
|
33615
33682
|
}
|
|
33616
33683
|
var VALIDATION_TIMEOUT_MS2 = 8e3;
|
|
33617
|
-
var SUPPORTED_CREDENTIALS = /* @__PURE__ */ new Set(["openai_api_key", "anthropic_api_key"]);
|
|
33684
|
+
var SUPPORTED_CREDENTIALS = /* @__PURE__ */ new Set(["openai_api_key", "anthropic_api_key", "gemini_api_key"]);
|
|
33618
33685
|
function extractApiError3(payload) {
|
|
33619
33686
|
if (!payload || typeof payload !== "object") return null;
|
|
33620
33687
|
const record = payload;
|
|
@@ -33649,6 +33716,25 @@ async function validateOpenAiKey2(value) {
|
|
|
33649
33716
|
clearTimeout(timer);
|
|
33650
33717
|
}
|
|
33651
33718
|
}
|
|
33719
|
+
async function validateGeminiKey2(value) {
|
|
33720
|
+
const controller = new AbortController();
|
|
33721
|
+
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS2);
|
|
33722
|
+
try {
|
|
33723
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(value)}`, {
|
|
33724
|
+
method: "GET",
|
|
33725
|
+
signal: controller.signal
|
|
33726
|
+
});
|
|
33727
|
+
if (res.ok) return { ok: true };
|
|
33728
|
+
const body = await res.json().catch(() => null);
|
|
33729
|
+
const message = extractApiError3(body) || `HTTP ${res.status}`;
|
|
33730
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
33731
|
+
} catch (err) {
|
|
33732
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33733
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
33734
|
+
} finally {
|
|
33735
|
+
clearTimeout(timer);
|
|
33736
|
+
}
|
|
33737
|
+
}
|
|
33652
33738
|
async function validateAnthropicKey2(value) {
|
|
33653
33739
|
const controller = new AbortController();
|
|
33654
33740
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS2);
|
|
@@ -33695,7 +33781,7 @@ function registerCredentialRoutes(router) {
|
|
|
33695
33781
|
if (!SUPPORTED_CREDENTIALS.has(name)) {
|
|
33696
33782
|
return { status: 400, error: `Validation not supported for credential: ${name}` };
|
|
33697
33783
|
}
|
|
33698
|
-
const result = name === "openai_api_key" ? await validateOpenAiKey2(value) : await validateAnthropicKey2(value);
|
|
33784
|
+
const result = name === "openai_api_key" ? await validateOpenAiKey2(value) : name === "gemini_api_key" ? await validateGeminiKey2(value) : await validateAnthropicKey2(value);
|
|
33699
33785
|
if (!result.ok) return { status: 400, error: result.error };
|
|
33700
33786
|
return { data: { ok: true } };
|
|
33701
33787
|
});
|
package/out/mcp/cli.js
CHANGED
|
@@ -24228,7 +24228,9 @@ function getClerkUsageToday(db3, source) {
|
|
|
24228
24228
|
};
|
|
24229
24229
|
}
|
|
24230
24230
|
function clerkApiKeySetting(provider) {
|
|
24231
|
-
|
|
24231
|
+
if (provider === "openai_api") return "clerk_openai_api_key";
|
|
24232
|
+
if (provider === "gemini_api") return "clerk_gemini_api_key";
|
|
24233
|
+
return "clerk_anthropic_api_key";
|
|
24232
24234
|
}
|
|
24233
24235
|
function setClerkApiKey(db3, provider, value) {
|
|
24234
24236
|
const trimmed = value.trim();
|
|
@@ -26734,7 +26736,7 @@ async function executeAgent(options) {
|
|
|
26734
26736
|
if (model === "codex" || model.startsWith("codex:")) {
|
|
26735
26737
|
return executeCodex(options);
|
|
26736
26738
|
}
|
|
26737
|
-
if (model === "openai" || model.startsWith("openai:")) {
|
|
26739
|
+
if (model === "openai" || model.startsWith("openai:") || model === "gemini" || model.startsWith("gemini:")) {
|
|
26738
26740
|
if (options.toolDefs && options.toolDefs.length > 0 && options.onToolCall) {
|
|
26739
26741
|
return executeOpenAiWithTools(options);
|
|
26740
26742
|
}
|
|
@@ -26904,10 +26906,34 @@ async function executeCodex(options) {
|
|
|
26904
26906
|
});
|
|
26905
26907
|
});
|
|
26906
26908
|
}
|
|
26909
|
+
function resolveOpenAiCompatible(model, apiKeyOverride) {
|
|
26910
|
+
const trimmed = model.trim();
|
|
26911
|
+
if (trimmed === "gemini" || trimmed.startsWith("gemini:")) {
|
|
26912
|
+
const apiKey2 = apiKeyOverride?.trim() || (process.env.GEMINI_API_KEY || "").trim();
|
|
26913
|
+
if (!apiKey2) return null;
|
|
26914
|
+
return {
|
|
26915
|
+
apiKey: apiKey2,
|
|
26916
|
+
url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
26917
|
+
defaultModel: "gemini-2.5-flash",
|
|
26918
|
+
label: "Gemini",
|
|
26919
|
+
prefix: "gemini"
|
|
26920
|
+
};
|
|
26921
|
+
}
|
|
26922
|
+
const apiKey = apiKeyOverride?.trim() || (process.env.OPENAI_API_KEY || "").trim();
|
|
26923
|
+
if (!apiKey) return null;
|
|
26924
|
+
return {
|
|
26925
|
+
apiKey,
|
|
26926
|
+
url: "https://api.openai.com/v1/chat/completions",
|
|
26927
|
+
defaultModel: "gpt-4o-mini",
|
|
26928
|
+
label: "OpenAI",
|
|
26929
|
+
prefix: "openai"
|
|
26930
|
+
};
|
|
26931
|
+
}
|
|
26907
26932
|
async function executeOpenAiWithTools(options) {
|
|
26908
|
-
const
|
|
26909
|
-
if (!
|
|
26910
|
-
const
|
|
26933
|
+
const config2 = resolveOpenAiCompatible(options.model, options.apiKey);
|
|
26934
|
+
if (!config2) return immediateError(`Missing ${options.model.startsWith("gemini") ? "Gemini" : "OpenAI"} API key.`);
|
|
26935
|
+
const { apiKey, url: apiUrl, defaultModel, label: providerLabel, prefix } = config2;
|
|
26936
|
+
const modelName = parseModelSuffix(options.model, prefix) || defaultModel;
|
|
26911
26937
|
const startTime = Date.now();
|
|
26912
26938
|
const maxTurns = options.maxTurns ?? 10;
|
|
26913
26939
|
const previousTurns = options.previousMessages ?? [];
|
|
@@ -26931,7 +26957,7 @@ Continue working toward the goal.` : options.prompt
|
|
|
26931
26957
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
26932
26958
|
let json;
|
|
26933
26959
|
try {
|
|
26934
|
-
const response = await fetch(
|
|
26960
|
+
const response = await fetch(apiUrl, {
|
|
26935
26961
|
method: "POST",
|
|
26936
26962
|
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
26937
26963
|
body: JSON.stringify({ model: modelName, messages, tools: options.toolDefs }),
|
|
@@ -26939,7 +26965,7 @@ Continue working toward the goal.` : options.prompt
|
|
|
26939
26965
|
});
|
|
26940
26966
|
json = await response.json();
|
|
26941
26967
|
if (!response.ok) {
|
|
26942
|
-
return { output:
|
|
26968
|
+
return { output: `${providerLabel} API ${response.status}: ${extractApiError(json)}`, exitCode: 1, durationMs: Date.now() - startTime, sessionId: null, timedOut: false, usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
|
|
26943
26969
|
}
|
|
26944
26970
|
} catch (err) {
|
|
26945
26971
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
@@ -27083,11 +27109,13 @@ Continue working toward the goal.` : options.prompt
|
|
|
27083
27109
|
return { output: finalOutput || "Actions completed.", exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false, usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
|
|
27084
27110
|
}
|
|
27085
27111
|
async function executeOpenAiApi(options) {
|
|
27086
|
-
const
|
|
27087
|
-
if (!
|
|
27088
|
-
|
|
27112
|
+
const config2 = resolveOpenAiCompatible(options.model, options.apiKey);
|
|
27113
|
+
if (!config2) {
|
|
27114
|
+
const isGemini = options.model.startsWith("gemini");
|
|
27115
|
+
return immediateError(`Missing ${isGemini ? "Gemini" : "OpenAI"} API key. Set room credential "${isGemini ? "gemini_api_key" : "openai_api_key"}" or ${isGemini ? "GEMINI_API_KEY" : "OPENAI_API_KEY"}.`);
|
|
27089
27116
|
}
|
|
27090
|
-
const
|
|
27117
|
+
const { apiKey, url: apiUrl, defaultModel, label: providerLabel, prefix } = config2;
|
|
27118
|
+
const modelName = parseModelSuffix(options.model, prefix) || defaultModel;
|
|
27091
27119
|
const messages = [];
|
|
27092
27120
|
if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
|
|
27093
27121
|
messages.push({ role: "user", content: options.prompt });
|
|
@@ -27096,7 +27124,7 @@ async function executeOpenAiApi(options) {
|
|
|
27096
27124
|
const timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
27097
27125
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27098
27126
|
try {
|
|
27099
|
-
const response = await fetch(
|
|
27127
|
+
const response = await fetch(apiUrl, {
|
|
27100
27128
|
method: "POST",
|
|
27101
27129
|
headers: {
|
|
27102
27130
|
"Authorization": `Bearer ${apiKey}`,
|
|
@@ -27111,7 +27139,7 @@ async function executeOpenAiApi(options) {
|
|
|
27111
27139
|
const json = await response.json();
|
|
27112
27140
|
if (!response.ok) {
|
|
27113
27141
|
return {
|
|
27114
|
-
output:
|
|
27142
|
+
output: `${providerLabel} API ${response.status}: ${extractApiError(json)}`,
|
|
27115
27143
|
exitCode: 1,
|
|
27116
27144
|
durationMs: Date.now() - startTime,
|
|
27117
27145
|
sessionId: null,
|
|
@@ -27327,13 +27355,13 @@ Respond ONLY with a JSON object (no markdown, no explanation):
|
|
|
27327
27355
|
}`;
|
|
27328
27356
|
const timeoutMs = 6e4;
|
|
27329
27357
|
try {
|
|
27330
|
-
if (model === "openai" || model.startsWith("openai:")) {
|
|
27331
|
-
const
|
|
27332
|
-
if (!
|
|
27333
|
-
const modelName = parseModelSuffix(model,
|
|
27334
|
-
const response = await fetch(
|
|
27358
|
+
if (model === "openai" || model.startsWith("openai:") || model === "gemini" || model.startsWith("gemini:")) {
|
|
27359
|
+
const config2 = resolveOpenAiCompatible(model, apiKey);
|
|
27360
|
+
if (!config2) return null;
|
|
27361
|
+
const modelName = parseModelSuffix(model, config2.prefix) || config2.defaultModel;
|
|
27362
|
+
const response = await fetch(config2.url, {
|
|
27335
27363
|
method: "POST",
|
|
27336
|
-
headers: { "Authorization": `Bearer ${
|
|
27364
|
+
headers: { "Authorization": `Bearer ${config2.apiKey}`, "Content-Type": "application/json" },
|
|
27337
27365
|
body: JSON.stringify({ model: modelName, messages: [{ role: "user", content: compressionPrompt }] }),
|
|
27338
27366
|
signal: AbortSignal.timeout(timeoutMs)
|
|
27339
27367
|
});
|
|
@@ -27363,16 +27391,17 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
27363
27391
|
if (!apiKey) {
|
|
27364
27392
|
return immediateError("Missing API key for station execution.");
|
|
27365
27393
|
}
|
|
27366
|
-
const
|
|
27394
|
+
const isOpenAiCompat = options.model.startsWith("openai:") || options.model.startsWith("gemini:");
|
|
27367
27395
|
const messages = [];
|
|
27368
27396
|
if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
|
|
27369
27397
|
messages.push({ role: "user", content: options.prompt });
|
|
27370
27398
|
let url;
|
|
27371
27399
|
let headers;
|
|
27372
27400
|
let body;
|
|
27373
|
-
if (
|
|
27374
|
-
const
|
|
27375
|
-
|
|
27401
|
+
if (isOpenAiCompat) {
|
|
27402
|
+
const config2 = resolveOpenAiCompatible(options.model, apiKey);
|
|
27403
|
+
const modelName = config2 ? parseModelSuffix(options.model, config2.prefix) || config2.defaultModel : parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
|
|
27404
|
+
url = config2?.url ?? "https://api.openai.com/v1/chat/completions";
|
|
27376
27405
|
headers = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
|
|
27377
27406
|
body = JSON.stringify({ model: modelName, messages });
|
|
27378
27407
|
} else {
|
|
@@ -27410,7 +27439,7 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
27410
27439
|
}
|
|
27411
27440
|
try {
|
|
27412
27441
|
const parsed = JSON.parse(result.stdout);
|
|
27413
|
-
const output =
|
|
27442
|
+
const output = isOpenAiCompat ? extractOpenAiText(parsed) : extractAnthropicText(parsed);
|
|
27414
27443
|
return { output, exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
|
|
27415
27444
|
} catch {
|
|
27416
27445
|
return {
|
|
@@ -27450,6 +27479,7 @@ function getModelProvider(model) {
|
|
|
27450
27479
|
if (normalized === "anthropic" || normalized.startsWith("anthropic:") || normalized.startsWith("claude-api:")) {
|
|
27451
27480
|
return "anthropic_api";
|
|
27452
27481
|
}
|
|
27482
|
+
if (normalized === "gemini" || normalized.startsWith("gemini:")) return "gemini_api";
|
|
27453
27483
|
return "claude_subscription";
|
|
27454
27484
|
}
|
|
27455
27485
|
async function getModelAuthStatus(db3, roomId, model) {
|
|
@@ -27460,6 +27490,9 @@ async function getModelAuthStatus(db3, roomId, model) {
|
|
|
27460
27490
|
if (provider === "anthropic_api") {
|
|
27461
27491
|
return resolveApiAuthStatus(db3, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY", provider);
|
|
27462
27492
|
}
|
|
27493
|
+
if (provider === "gemini_api") {
|
|
27494
|
+
return resolveApiAuthStatus(db3, roomId, "gemini_api_key", "GEMINI_API_KEY", provider);
|
|
27495
|
+
}
|
|
27463
27496
|
let ready = false;
|
|
27464
27497
|
if (provider === "claude_subscription") {
|
|
27465
27498
|
ready = checkClaudeCliAvailable().available;
|
|
@@ -27485,6 +27518,9 @@ function resolveApiKeyForModel(db3, roomId, model) {
|
|
|
27485
27518
|
if (provider === "anthropic_api") {
|
|
27486
27519
|
return resolveApiKey(db3, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY");
|
|
27487
27520
|
}
|
|
27521
|
+
if (provider === "gemini_api") {
|
|
27522
|
+
return resolveApiKey(db3, roomId, "gemini_api_key", "GEMINI_API_KEY");
|
|
27523
|
+
}
|
|
27488
27524
|
return void 0;
|
|
27489
27525
|
}
|
|
27490
27526
|
function resolveApiAuthStatus(db3, roomId, credentialName, envVar, provider) {
|
|
@@ -27536,6 +27572,9 @@ function getClerkCredential(db3, credentialName) {
|
|
|
27536
27572
|
if (credentialName === "anthropic_api_key") {
|
|
27537
27573
|
return getClerkApiKey(db3, "anthropic_api");
|
|
27538
27574
|
}
|
|
27575
|
+
if (credentialName === "gemini_api_key") {
|
|
27576
|
+
return getClerkApiKey(db3, "gemini_api");
|
|
27577
|
+
}
|
|
27539
27578
|
return null;
|
|
27540
27579
|
}
|
|
27541
27580
|
function getRoomCredential(db3, roomId, credentialName) {
|
|
@@ -52537,7 +52576,7 @@ var server_exports = {};
|
|
|
52537
52576
|
async function main() {
|
|
52538
52577
|
const server = new McpServer({
|
|
52539
52578
|
name: "quoroom",
|
|
52540
|
-
version: true ? "0.1.
|
|
52579
|
+
version: true ? "0.1.30" : "0.0.0"
|
|
52541
52580
|
});
|
|
52542
52581
|
registerMemoryTools(server);
|
|
52543
52582
|
registerSchedulerTools(server);
|
|
@@ -54056,10 +54095,10 @@ async function runCycle(db3, roomId, worker, maxTurns, options) {
|
|
|
54056
54095
|
options?.onCycleLifecycle?.("created", cycle.id, roomId);
|
|
54057
54096
|
try {
|
|
54058
54097
|
const provider = getModelProvider(model);
|
|
54059
|
-
if (provider === "openai_api" || provider === "anthropic_api") {
|
|
54098
|
+
if (provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") {
|
|
54060
54099
|
const apiKeyCheck = resolveApiKeyForModel(db3, roomId, model);
|
|
54061
54100
|
if (!apiKeyCheck) {
|
|
54062
|
-
const label = provider === "openai_api" ? "OpenAI" : "Anthropic";
|
|
54101
|
+
const label = provider === "openai_api" ? "OpenAI" : provider === "gemini_api" ? "Gemini" : "Anthropic";
|
|
54063
54102
|
const msg = `Missing ${label} API key. Set it in Room Settings or the Setup Guide.`;
|
|
54064
54103
|
logBuffer2.addSynthetic("error", msg);
|
|
54065
54104
|
logBuffer2.flush();
|
|
@@ -54603,7 +54642,7 @@ var require_package = __commonJS({
|
|
|
54603
54642
|
"package.json"(exports2, module2) {
|
|
54604
54643
|
module2.exports = {
|
|
54605
54644
|
name: "quoroom",
|
|
54606
|
-
version: "0.1.
|
|
54645
|
+
version: "0.1.30",
|
|
54607
54646
|
description: "Autonomous AI agent collective engine \u2014 Queen, Workers, Quorum",
|
|
54608
54647
|
main: "./out/mcp/server.js",
|
|
54609
54648
|
bin: {
|
|
@@ -55648,8 +55687,8 @@ function maskKey2(key) {
|
|
|
55648
55687
|
return `${trimmed.slice(0, 7)}...${trimmed.slice(-4)}`;
|
|
55649
55688
|
}
|
|
55650
55689
|
function getClerkApiAuthState(db3, provider) {
|
|
55651
|
-
const credentialName = provider === "openai_api" ? "openai_api_key" : "anthropic_api_key";
|
|
55652
|
-
const envVar = provider === "openai_api" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
55690
|
+
const credentialName = provider === "openai_api" ? "openai_api_key" : provider === "gemini_api" ? "gemini_api_key" : "anthropic_api_key";
|
|
55691
|
+
const envVar = provider === "openai_api" ? "OPENAI_API_KEY" : provider === "gemini_api" ? "GEMINI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
55653
55692
|
const roomCredential = findAnyRoomCredential2(db3, credentialName);
|
|
55654
55693
|
const savedKey = getClerkApiKey(db3, provider);
|
|
55655
55694
|
const envKey = (process.env[envVar] || "").trim() || null;
|
|
@@ -55665,7 +55704,8 @@ function getClerkApiAuthState(db3, provider) {
|
|
|
55665
55704
|
function getClerkApiAuth(db3) {
|
|
55666
55705
|
return {
|
|
55667
55706
|
openai: getClerkApiAuthState(db3, "openai_api"),
|
|
55668
|
-
anthropic: getClerkApiAuthState(db3, "anthropic_api")
|
|
55707
|
+
anthropic: getClerkApiAuthState(db3, "anthropic_api"),
|
|
55708
|
+
gemini: getClerkApiAuthState(db3, "gemini_api")
|
|
55669
55709
|
};
|
|
55670
55710
|
}
|
|
55671
55711
|
function autoConfigureClerkModel(db3) {
|
|
@@ -55685,6 +55725,9 @@ function resolveClerkApiKey(db3, model) {
|
|
|
55685
55725
|
if (provider === "anthropic_api") {
|
|
55686
55726
|
return findAnyRoomCredential2(db3, "anthropic_api_key") || getClerkApiKey(db3, "anthropic_api") || (process.env.ANTHROPIC_API_KEY || void 0);
|
|
55687
55727
|
}
|
|
55728
|
+
if (provider === "gemini_api") {
|
|
55729
|
+
return findAnyRoomCredential2(db3, "gemini_api_key") || getClerkApiKey(db3, "gemini_api") || (process.env.GEMINI_API_KEY || void 0);
|
|
55730
|
+
}
|
|
55688
55731
|
return void 0;
|
|
55689
55732
|
}
|
|
55690
55733
|
function clipText(value, max = 240) {
|
|
@@ -55832,6 +55875,11 @@ function buildClerkModelPlan(preferredModel) {
|
|
|
55832
55875
|
uniquePush(plan, CLERK_FALLBACK_SUBSCRIPTION_MODEL);
|
|
55833
55876
|
uniquePush(plan, CLERK_FALLBACK_OPENAI_MODEL);
|
|
55834
55877
|
uniquePush(plan, DEFAULT_CLERK_MODEL);
|
|
55878
|
+
} else if (provider === "gemini_api") {
|
|
55879
|
+
uniquePush(plan, CLERK_FALLBACK_SUBSCRIPTION_MODEL);
|
|
55880
|
+
uniquePush(plan, CLERK_FALLBACK_OPENAI_MODEL);
|
|
55881
|
+
uniquePush(plan, DEFAULT_CLERK_MODEL);
|
|
55882
|
+
uniquePush(plan, CLERK_FALLBACK_ANTHROPIC_MODEL);
|
|
55835
55883
|
}
|
|
55836
55884
|
return plan;
|
|
55837
55885
|
}
|
|
@@ -55840,7 +55888,7 @@ function buildExecutionCandidates(db3, preferredModel) {
|
|
|
55840
55888
|
for (const model of buildClerkModelPlan(preferredModel)) {
|
|
55841
55889
|
const provider = getModelProvider(model);
|
|
55842
55890
|
const apiKey = resolveClerkApiKey(db3, model);
|
|
55843
|
-
if ((provider === "openai_api" || provider === "anthropic_api") && !apiKey) continue;
|
|
55891
|
+
if ((provider === "openai_api" || provider === "anthropic_api" || provider === "gemini_api") && !apiKey) continue;
|
|
55844
55892
|
candidates.push({ model, apiKey });
|
|
55845
55893
|
}
|
|
55846
55894
|
if (candidates.length === 0) {
|
|
@@ -57546,6 +57594,25 @@ async function validateOpenAiKey(value) {
|
|
|
57546
57594
|
clearTimeout(timer);
|
|
57547
57595
|
}
|
|
57548
57596
|
}
|
|
57597
|
+
async function validateGeminiKey(value) {
|
|
57598
|
+
const controller = new AbortController();
|
|
57599
|
+
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
57600
|
+
try {
|
|
57601
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(value)}`, {
|
|
57602
|
+
method: "GET",
|
|
57603
|
+
signal: controller.signal
|
|
57604
|
+
});
|
|
57605
|
+
if (res.ok) return { ok: true };
|
|
57606
|
+
const body = await res.json().catch(() => null);
|
|
57607
|
+
const message = extractApiError2(body) || `HTTP ${res.status}`;
|
|
57608
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
57609
|
+
} catch (err) {
|
|
57610
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57611
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
57612
|
+
} finally {
|
|
57613
|
+
clearTimeout(timer);
|
|
57614
|
+
}
|
|
57615
|
+
}
|
|
57549
57616
|
async function validateAnthropicKey(value) {
|
|
57550
57617
|
const controller = new AbortController();
|
|
57551
57618
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
@@ -57883,11 +57950,11 @@ function registerClerkRoutes(router) {
|
|
|
57883
57950
|
const body = ctx.body || {};
|
|
57884
57951
|
const provider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
57885
57952
|
const key = typeof body.key === "string" ? body.key.trim() : "";
|
|
57886
|
-
if (provider !== "openai_api" && provider !== "anthropic_api") {
|
|
57887
|
-
return { status: 400, error: "provider must be openai_api or
|
|
57953
|
+
if (provider !== "openai_api" && provider !== "anthropic_api" && provider !== "gemini_api") {
|
|
57954
|
+
return { status: 400, error: "provider must be openai_api, anthropic_api, or gemini_api" };
|
|
57888
57955
|
}
|
|
57889
57956
|
if (!key) return { status: 400, error: "key is required" };
|
|
57890
|
-
const result = provider === "openai_api" ? await validateOpenAiKey(key) : await validateAnthropicKey(key);
|
|
57957
|
+
const result = provider === "openai_api" ? await validateOpenAiKey(key) : provider === "gemini_api" ? await validateGeminiKey(key) : await validateAnthropicKey(key);
|
|
57891
57958
|
if (!result.ok) return { status: 400, error: result.error };
|
|
57892
57959
|
setClerkApiKey(ctx.db, provider, key);
|
|
57893
57960
|
return { data: { ok: true, apiAuth: getClerkApiAuth(ctx.db) } };
|
|
@@ -60404,7 +60471,7 @@ function semverGt(a, b) {
|
|
|
60404
60471
|
}
|
|
60405
60472
|
function getCurrentVersion() {
|
|
60406
60473
|
try {
|
|
60407
|
-
return true ? "0.1.
|
|
60474
|
+
return true ? "0.1.30" : null.version;
|
|
60408
60475
|
} catch {
|
|
60409
60476
|
return "0.0.0";
|
|
60410
60477
|
}
|
|
@@ -60589,7 +60656,7 @@ var init_updateChecker = __esm({
|
|
|
60589
60656
|
function getVersion3() {
|
|
60590
60657
|
if (cachedVersion) return cachedVersion;
|
|
60591
60658
|
try {
|
|
60592
|
-
cachedVersion = true ? "0.1.
|
|
60659
|
+
cachedVersion = true ? "0.1.30" : null.version;
|
|
60593
60660
|
} catch {
|
|
60594
60661
|
cachedVersion = "unknown";
|
|
60595
60662
|
}
|
|
@@ -60992,6 +61059,25 @@ async function validateOpenAiKey2(value) {
|
|
|
60992
61059
|
clearTimeout(timer);
|
|
60993
61060
|
}
|
|
60994
61061
|
}
|
|
61062
|
+
async function validateGeminiKey2(value) {
|
|
61063
|
+
const controller = new AbortController();
|
|
61064
|
+
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS2);
|
|
61065
|
+
try {
|
|
61066
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(value)}`, {
|
|
61067
|
+
method: "GET",
|
|
61068
|
+
signal: controller.signal
|
|
61069
|
+
});
|
|
61070
|
+
if (res.ok) return { ok: true };
|
|
61071
|
+
const body = await res.json().catch(() => null);
|
|
61072
|
+
const message = extractApiError3(body) || `HTTP ${res.status}`;
|
|
61073
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
61074
|
+
} catch (err) {
|
|
61075
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61076
|
+
return { ok: false, error: `Gemini key validation failed: ${message}` };
|
|
61077
|
+
} finally {
|
|
61078
|
+
clearTimeout(timer);
|
|
61079
|
+
}
|
|
61080
|
+
}
|
|
60995
61081
|
async function validateAnthropicKey2(value) {
|
|
60996
61082
|
const controller = new AbortController();
|
|
60997
61083
|
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS2);
|
|
@@ -61038,7 +61124,7 @@ function registerCredentialRoutes(router) {
|
|
|
61038
61124
|
if (!SUPPORTED_CREDENTIALS.has(name)) {
|
|
61039
61125
|
return { status: 400, error: `Validation not supported for credential: ${name}` };
|
|
61040
61126
|
}
|
|
61041
|
-
const result = name === "openai_api_key" ? await validateOpenAiKey2(value) : await validateAnthropicKey2(value);
|
|
61127
|
+
const result = name === "openai_api_key" ? await validateOpenAiKey2(value) : name === "gemini_api_key" ? await validateGeminiKey2(value) : await validateAnthropicKey2(value);
|
|
61042
61128
|
if (!result.ok) return { status: 400, error: result.error };
|
|
61043
61129
|
return { data: { ok: true } };
|
|
61044
61130
|
});
|
|
@@ -61078,7 +61164,7 @@ var init_credentials2 = __esm({
|
|
|
61078
61164
|
init_db_queries();
|
|
61079
61165
|
init_event_bus();
|
|
61080
61166
|
VALIDATION_TIMEOUT_MS2 = 8e3;
|
|
61081
|
-
SUPPORTED_CREDENTIALS = /* @__PURE__ */ new Set(["openai_api_key", "anthropic_api_key"]);
|
|
61167
|
+
SUPPORTED_CREDENTIALS = /* @__PURE__ */ new Set(["openai_api_key", "anthropic_api_key", "gemini_api_key"]);
|
|
61082
61168
|
}
|
|
61083
61169
|
});
|
|
61084
61170
|
|
|
@@ -67018,7 +67104,7 @@ __export(update_exports, {
|
|
|
67018
67104
|
});
|
|
67019
67105
|
function getCurrentVersion2() {
|
|
67020
67106
|
try {
|
|
67021
|
-
return true ? "0.1.
|
|
67107
|
+
return true ? "0.1.30" : null.version;
|
|
67022
67108
|
} catch {
|
|
67023
67109
|
return "0.0.0";
|
|
67024
67110
|
}
|
package/out/mcp/server.js
CHANGED
|
@@ -9389,7 +9389,9 @@ function getClerkUsageToday(db2, source) {
|
|
|
9389
9389
|
};
|
|
9390
9390
|
}
|
|
9391
9391
|
function clerkApiKeySetting(provider) {
|
|
9392
|
-
|
|
9392
|
+
if (provider === "openai_api") return "clerk_openai_api_key";
|
|
9393
|
+
if (provider === "gemini_api") return "clerk_gemini_api_key";
|
|
9394
|
+
return "clerk_anthropic_api_key";
|
|
9393
9395
|
}
|
|
9394
9396
|
function setClerkApiKey(db2, provider, value) {
|
|
9395
9397
|
const trimmed = value.trim();
|
|
@@ -35035,6 +35037,29 @@ async function fetchReferredRooms(cloudRoomId) {
|
|
|
35035
35037
|
}
|
|
35036
35038
|
|
|
35037
35039
|
// src/shared/agent-executor.ts
|
|
35040
|
+
function resolveOpenAiCompatible(model, apiKeyOverride) {
|
|
35041
|
+
const trimmed = model.trim();
|
|
35042
|
+
if (trimmed === "gemini" || trimmed.startsWith("gemini:")) {
|
|
35043
|
+
const apiKey2 = apiKeyOverride?.trim() || (process.env.GEMINI_API_KEY || "").trim();
|
|
35044
|
+
if (!apiKey2) return null;
|
|
35045
|
+
return {
|
|
35046
|
+
apiKey: apiKey2,
|
|
35047
|
+
url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
35048
|
+
defaultModel: "gemini-2.5-flash",
|
|
35049
|
+
label: "Gemini",
|
|
35050
|
+
prefix: "gemini"
|
|
35051
|
+
};
|
|
35052
|
+
}
|
|
35053
|
+
const apiKey = apiKeyOverride?.trim() || (process.env.OPENAI_API_KEY || "").trim();
|
|
35054
|
+
if (!apiKey) return null;
|
|
35055
|
+
return {
|
|
35056
|
+
apiKey,
|
|
35057
|
+
url: "https://api.openai.com/v1/chat/completions",
|
|
35058
|
+
defaultModel: "gpt-4o-mini",
|
|
35059
|
+
label: "OpenAI",
|
|
35060
|
+
prefix: "openai"
|
|
35061
|
+
};
|
|
35062
|
+
}
|
|
35038
35063
|
function parseModelSuffix(model, prefix) {
|
|
35039
35064
|
const trimmed = model.trim();
|
|
35040
35065
|
if (trimmed === prefix) return "";
|
|
@@ -35095,16 +35120,17 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
35095
35120
|
if (!apiKey) {
|
|
35096
35121
|
return immediateError("Missing API key for station execution.");
|
|
35097
35122
|
}
|
|
35098
|
-
const
|
|
35123
|
+
const isOpenAiCompat = options.model.startsWith("openai:") || options.model.startsWith("gemini:");
|
|
35099
35124
|
const messages = [];
|
|
35100
35125
|
if (options.systemPrompt) messages.push({ role: "system", content: options.systemPrompt });
|
|
35101
35126
|
messages.push({ role: "user", content: options.prompt });
|
|
35102
35127
|
let url;
|
|
35103
35128
|
let headers;
|
|
35104
35129
|
let body;
|
|
35105
|
-
if (
|
|
35106
|
-
const
|
|
35107
|
-
|
|
35130
|
+
if (isOpenAiCompat) {
|
|
35131
|
+
const config2 = resolveOpenAiCompatible(options.model, apiKey);
|
|
35132
|
+
const modelName = config2 ? parseModelSuffix(options.model, config2.prefix) || config2.defaultModel : parseModelSuffix(options.model, "openai") || "gpt-4o-mini";
|
|
35133
|
+
url = config2?.url ?? "https://api.openai.com/v1/chat/completions";
|
|
35108
35134
|
headers = { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" };
|
|
35109
35135
|
body = JSON.stringify({ model: modelName, messages });
|
|
35110
35136
|
} else {
|
|
@@ -35142,7 +35168,7 @@ async function executeApiOnStation(cloudRoomId, stationId, options) {
|
|
|
35142
35168
|
}
|
|
35143
35169
|
try {
|
|
35144
35170
|
const parsed = JSON.parse(result.stdout);
|
|
35145
|
-
const output =
|
|
35171
|
+
const output = isOpenAiCompat ? extractOpenAiText(parsed) : extractAnthropicText(parsed);
|
|
35146
35172
|
return { output, exitCode: 0, durationMs: Date.now() - startTime, sessionId: null, timedOut: false };
|
|
35147
35173
|
} catch {
|
|
35148
35174
|
return {
|
|
@@ -35168,6 +35194,7 @@ function getModelProvider(model) {
|
|
|
35168
35194
|
if (normalized === "anthropic" || normalized.startsWith("anthropic:") || normalized.startsWith("claude-api:")) {
|
|
35169
35195
|
return "anthropic_api";
|
|
35170
35196
|
}
|
|
35197
|
+
if (normalized === "gemini" || normalized.startsWith("gemini:")) return "gemini_api";
|
|
35171
35198
|
return "claude_subscription";
|
|
35172
35199
|
}
|
|
35173
35200
|
function resolveApiKeyForModel(db2, roomId, model) {
|
|
@@ -35178,6 +35205,9 @@ function resolveApiKeyForModel(db2, roomId, model) {
|
|
|
35178
35205
|
if (provider === "anthropic_api") {
|
|
35179
35206
|
return resolveApiKey(db2, roomId, "anthropic_api_key", "ANTHROPIC_API_KEY");
|
|
35180
35207
|
}
|
|
35208
|
+
if (provider === "gemini_api") {
|
|
35209
|
+
return resolveApiKey(db2, roomId, "gemini_api_key", "GEMINI_API_KEY");
|
|
35210
|
+
}
|
|
35181
35211
|
return void 0;
|
|
35182
35212
|
}
|
|
35183
35213
|
function resolveApiKey(db2, roomId, credentialName, envVar) {
|
|
@@ -35205,6 +35235,9 @@ function getClerkCredential(db2, credentialName) {
|
|
|
35205
35235
|
if (credentialName === "anthropic_api_key") {
|
|
35206
35236
|
return getClerkApiKey(db2, "anthropic_api");
|
|
35207
35237
|
}
|
|
35238
|
+
if (credentialName === "gemini_api_key") {
|
|
35239
|
+
return getClerkApiKey(db2, "gemini_api");
|
|
35240
|
+
}
|
|
35208
35241
|
return null;
|
|
35209
35242
|
}
|
|
35210
35243
|
function getRoomCredential(db2, roomId, credentialName) {
|
|
@@ -49220,7 +49253,7 @@ init_db();
|
|
|
49220
49253
|
async function main() {
|
|
49221
49254
|
const server = new McpServer({
|
|
49222
49255
|
name: "quoroom",
|
|
49223
|
-
version: true ? "0.1.
|
|
49256
|
+
version: true ? "0.1.30" : "0.0.0"
|
|
49224
49257
|
});
|
|
49225
49258
|
registerMemoryTools(server);
|
|
49226
49259
|
registerSchedulerTools(server);
|