Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8bb2f4f
Groq provider step 1: add config (GROQ_API_KEYS, GROQ_API, GROQ_MODEL…
allocsys Aug 26, 2026
a82c7b2
Groq provider step 3: extract connectors/glm/adapter.js's pure OpenAI…
allocsys Aug 26, 2026
c8c0fce
Groq provider step 3 (cont.): collapse connectors/glm/adapter.js to a…
allocsys Aug 26, 2026
0fe7fe9
Groq provider step 2: add connectors/groq/client.js, a thin OpenAI-co…
allocsys Aug 26, 2026
b860af8
Groq provider step 4: add a "groq" branch to providerChat, mirroring …
allocsys Aug 26, 2026
6123b8f
Groq provider step 7: add "groq" to delegate_agent's provider zod enu…
allocsys Aug 26, 2026
6b62a8f
Groq provider step 8a: add test/groq-client.test.js, mirroring test/g…
allocsys Aug 26, 2026
62579ee
Groq provider step 8b: update test/llm-router.test.js's mock path fro…
allocsys Aug 26, 2026
cd0ea33
Groq provider step 8c: add "groq" to agent-delegate-loop.test.js's pr…
allocsys Aug 26, 2026
2c2bdec
Groq provider step 8d: rename test/glm-adapter.test.js -> test/openai…
allocsys Aug 26, 2026
00a5edb
Update the renamed test's import path and describe-block labels to re…
allocsys Aug 26, 2026
12c515b
Update plan.md status: Groq steps 1-8 implemented + green in CI; flag…
allocsys Aug 27, 2026
fc6cf5c
Add the "Model choice -- CORRECTED" note that config.js's comment (ad…
allocsys Aug 27, 2026
393e8d3
Mark sequenced steps 1-8 done (implemented, tests green in CI) and fl…
allocsys Aug 27, 2026
1e735fd
Document GROQ_API_KEYS (missed in the Groq provider steps 1-8 impleme…
allocsys Aug 27, 2026
21e7f63
Add a GROQ_API_KEYS card to the env bundler (missed in the Groq provi…
allocsys Aug 27, 2026
59ceae4
Document the Groq provider in README.md (missed in the Groq provider …
allocsys Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Need connector tokens first? **[→ Get API keys](./docs/API_KEYS.md)** — one-
[![Jules Key](https://img.shields.io/badge/Jules-API_Key-4285F4?style=flat-square&logo=googlegemini)](https://jules.google.com/settings#api)
[![Exa Key](https://img.shields.io/badge/Exa-API_Key-000000?style=flat-square)](https://dashboard.exa.ai/api-keys)
[![OpenRouter Key](https://img.shields.io/badge/OpenRouter-API_Key-6467F2?style=flat-square)](https://openrouter.ai/keys)
[![Groq Key](https://img.shields.io/badge/Groq-API_Key-F55036?style=flat-square)](https://console.groq.com/keys)
[![Upstash Redis](https://img.shields.io/badge/Upstash-Redis-00E9A3?style=flat-square)](https://console.upstash.com/redis)
[![Generate Secret](https://img.shields.io/badge/Generate-MCP__SHARED__KEY-333333?style=flat-square)](https://generate-secret.vercel.app/32)

Expand Down Expand Up @@ -176,20 +177,25 @@ any in the first place.

`delegate_agent` — hand an open-ended, multi-step, read-only investigation
(e.g. "why is CI failing on PR #42", "summarize what changed in this repo over
the last week") to Gemini (default) or GLM instead of making 5-10 separate
manual tool calls. The model runs its own loop server-side across GitHub,
Cloudflare, and Notion (bounded by `max_steps`, default 6, hard cap 20) and
returns one synthesized answer. Falls through an ordered model cascade
(`GEMINI_MODEL` → `GEMINI_FALLBACK_MODELS`, or for GLM `GLM_MODEL` →
`GLM_FALLBACK_MODELS` across every key in `OPENROUTER_API_KEYS`) on rate
limits, with Redis-backed per-model cooldown so already-limited models are
skipped rather than retried. An explicit `provider: "gemini" | "glm"` arg
picks which one backs a given call (default: `DEFAULT_LLM_PROVIDER`, itself
defaulting to `"gemini"`) — the two are interchangeable in capability, not
just cost/speed; try `"glm"` if Gemini's output has been unreliable for a
given task. Ignored on a resume (the provider that started the run is
always reused, so a checkpointed conversation can't be corrupted by
resuming it on a different provider's wire format).
the last week") to Gemini (default), GLM, or Groq instead of making 5-10
separate manual tool calls. The model runs its own loop server-side across
GitHub, Cloudflare, and Notion (bounded by `max_steps`, default 6, hard cap
20) and returns one synthesized answer. Falls through an ordered model
cascade (`GEMINI_MODEL` → `GEMINI_FALLBACK_MODELS`, `GLM_MODEL` →
`GLM_FALLBACK_MODELS` across every key in `OPENROUTER_API_KEYS`, or
`GROQ_MODEL` → `GROQ_FALLBACK_MODELS` across every key in `GROQ_API_KEYS`)
on rate limits, with Redis-backed per-model cooldown so already-limited
models are skipped rather than retried. An explicit
`provider: "gemini" | "glm" | "groq"` arg picks which one backs a given
call (default: `DEFAULT_LLM_PROVIDER`, itself defaulting to `"gemini"`) —
all three are interchangeable in capability, not just cost/speed; try
`"glm"` or `"groq"` if Gemini's output has been unreliable for a given
task. GLM (via OpenRouter) is currently non-functional on a zero-credit
account — see `docs/API_KEYS.md` — so Groq is the practical free-tier
alternative to Gemini for now (request/token-rate-limited rather than
credit-balance-gated, no card required). Ignored on a resume (the provider
that started the run is always reused, so a checkpointed conversation
can't be corrupted by resuming it on a different provider's wire format).

`delegate_research` — web research, in one of two mutually-exclusive modes
selected by which args are passed:
Expand Down Expand Up @@ -299,6 +305,10 @@ All tokens are optional independently — a connector's tools fail at call time
| `GLM_MODEL` | Primary GLM model (via OpenRouter) for `provider: "glm"` delegation (default `z-ai/glm-4.6`) |
| `GLM_FALLBACK_MODELS` | Comma-separated fallback model list used on 429s, cascaded per `OPENROUTER_API_KEYS` key (default `z-ai/glm-4.5-air:free`) |
| `GLM_REQUEST_TIMEOUT_MS` | Defensive ceiling on a single GLM/OpenRouter call (default `55000`) |
| `GROQ_API_KEYS` | Comma-separated Groq API key(s) — required for `delegate_agent`'s `provider: "groq"` mode, unused otherwise; free-tier is request/token-rate-limited, not credit-balance-gated like OpenRouter/GLM |
| `GROQ_MODEL` | Primary Groq model for `provider: "groq"` delegation (default `openai/gpt-oss-120b`, a production model) |
| `GROQ_FALLBACK_MODELS` | Comma-separated fallback model list used on 429s, cascaded per `GROQ_API_KEYS` key (default `qwen/qwen3.6-27b` — stronger on benchmarks but a Groq **preview** model, kept as fallback rather than primary for availability reasons) |
| `GROQ_REQUEST_TIMEOUT_MS` | Defensive ceiling on a single Groq call (default `55000`) |
| `DEFAULT_LLM_PROVIDER` | Which provider `delegate_agent` uses when a call omits `provider` (default `gemini`) |
| `UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN` (or `KV_REST_API_URL` + `KV_REST_API_TOKEN`) | Optional — persists per-model rate-limit cooldowns and `delegate_agent` resume checkpoints across invocations; fails open if neither pair is set. Either naming works — the raw Upstash Marketplace integration names them `UPSTASH_REDIS_REST_*`, Vercel's own "KV" product (also Upstash-backed) names them `KV_REST_API_*`. |
| `DEFAULT_OWNER` | Default GitHub owner when omitted from a call (defaults to `allocsys`) |
Expand Down
57 changes: 57 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,63 @@ export const GLM_DEFAULT_MAX_OUTPUT_TOKENS = Number(process.env.GLM_DEFAULT_MAX_
// automatic best-model routing.
export const DEFAULT_LLM_PROVIDER = process.env.DEFAULT_LLM_PROVIDER || "gemini";

// ---------------------------------------------------------------------------
// Groq -- third `delegate_agent` provider option (see plan.md "Groq provider
// addition"), added because GLM/OpenRouter's free tier turned out to be
// gated by account credit balance (see plan.md "Current status" -- a
// zero-balance account is blocked from OpenRouter's free models too, not
// just paid ones). Groq's free tier is documented as request/token-rate-
// limited instead, not tied to a dollar balance, and needs no credit card.
// Also OpenAI-compatible like OpenRouter, so it reuses the same
// translation layer (see connectors/openai_shape/adapter.js, extracted
// from connectors/glm/adapter.js specifically so both providers share one
// implementation instead of two copies drifting apart).
//
// GROQ_API_KEYS is plural/comma-separated, same rotation pattern as
// OPENROUTER_API_KEYS/EXA_API_KEYS above.
export const GROQ_API_KEYS = (process.env.GROQ_API_KEYS || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
export const GROQ_API = "https://api.groq.com/openai/v1/chat/completions";

// Model choice verified directly against https://console.groq.com/docs/models
// on 2026-08-27 (not from third-party benchmarks alone): Groq explicitly
// classifies qwen/qwen3.6-27b as a PREVIEW model ("intended for evaluation
// purposes only... may be discontinued at short notice") despite it
// scoring highest on Groq's own intelligence ranking, while
// openai/gpt-oss-120b is a PRODUCTION model. For a persistent, unattended
// delegate_agent provider, availability stability matters more than a
// benchmark edge, so production is the default and the stronger-but-
// preview model is only the fallback -- do not swap this ordering without
// re-reading plan.md's "Model choice -- CORRECTED" note first.
export const GROQ_MODEL = process.env.GROQ_MODEL || "openai/gpt-oss-120b";
export const GROQ_FALLBACK_MODELS = (process.env.GROQ_FALLBACK_MODELS || "qwen/qwen3.6-27b")
.split(",")
.map((s) => s.trim())
.filter(Boolean);

// Same defensive-ceiling reasoning as GEMINI_REQUEST_TIMEOUT_MS/
// GLM_REQUEST_TIMEOUT_MS above.
export const GROQ_REQUEST_TIMEOUT_MS = Number(process.env.GROQ_REQUEST_TIMEOUT_MS) || 55000;

// Default cap on Groq's max_tokens when a caller doesn't specify one
// explicitly -- set UP FRONT this time, unlike GLM_DEFAULT_MAX_OUTPUT_TOKENS,
// which was only added reactively after a live 402 revealed OpenRouter has
// no sane default at all (see plan.md's "Current status" and the Groq
// section's step 1 note: don't repeat that discovery-by-failure cycle).
// 4096 follows Groq's own tool-use guidance ("set max_completion_tokens to
// 3000-4000 for complex tasks" -- see console.groq.com/docs on built-in
// tool use). NOT YET LIVE-VERIFIED: Groq's chat completions endpoint is
// OpenAI-compatible, but it's unconfirmed whether it honors the legacy
// `max_tokens` field name (what connectors/openai_shape/adapter.js and
// glm/client.js both send) the same way for every model, or whether some
// Groq models expect the newer `max_completion_tokens` name instead --
// this needs live-testing in plan.md step 9 before being treated as
// settled, exactly the kind of thing pre-emptive comments can flag but not
// substitute for actually running the smoke test.
export const GROQ_DEFAULT_MAX_OUTPUT_TOKENS = Number(process.env.GROQ_DEFAULT_MAX_OUTPUT_TOKENS) || 4096;

// ---------------------------------------------------------------------------
// Frontend/design delegate (connectors/frontend/) -- delegate_designer's
// write-capable agent loop (agent.js), backed by the existing Gemini
Expand Down
20 changes: 10 additions & 10 deletions connectors/gemini/agent_tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { z } from "zod";
import { runInvestigation } from "./agent_delegate.js";
import { doCreatePage } from "../notion/tools.js";
import { GEMINI_NOTION_ROOT_PAGE_ID, DEFAULT_LLM_PROVIDER, GLM_DEFAULT_MAX_OUTPUT_TOKENS } from "../../config.js";
import { GEMINI_NOTION_ROOT_PAGE_ID, DEFAULT_LLM_PROVIDER, GLM_DEFAULT_MAX_OUTPUT_TOKENS, GROQ_DEFAULT_MAX_OUTPUT_TOKENS } from "../../config.js";

export function register(server) {

Expand All @@ -35,20 +35,20 @@ export function register(server) {
log_to_notion: z.boolean().optional().describe("Whether to log the task, step-by-step tool calls, and final answer as a page under the Gemini section of Notion (default: false). Write always targets the fixed Gemini root page."),
resume_run_id: z.string().optional().describe("A runId returned from a previous failed/partial delegate_agent call. If its checkpoint is still live (1 hour TTL), continues that run's conversation instead of starting fresh."),
show_transcript: z.boolean().optional().describe("Include the full step-by-step tool-call transcript in the response, even on a successful run (default: false). Useful for debugging what Gemini actually called and in what order/grouping -- e.g. checking whether independent calls were batched into the same step. On a failed/partial run the transcript is always shown regardless of this flag."),
provider: z.enum(["gemini", "glm"]).optional()
provider: z.enum(["gemini", "glm", "groq"]).optional()
.describe(`DEFAULT: "${DEFAULT_LLM_PROVIDER}". ` +
`VALUES: "gemini" (Google Gemini API, needs GEMINI_API_KEY) | "glm" (Z.ai GLM via OpenRouter, needs OPENROUTER_API_KEYS). ` +
`CHOOSE: the two are interchangeable in capability, not just cost/speed -- switch to "glm" if Gemini's output has been unreliable for this task. ` +
`VALUES: "gemini" (Google Gemini API, needs GEMINI_API_KEY) | "glm" (Z.ai GLM via OpenRouter, needs OPENROUTER_API_KEYS -- NOTE 2026-08-27: currently non-functional on this deployment, OpenRouter account has no credit and none is being added; a zero-balance account is blocked from both paid and free OpenRouter routes, see plan.md "Current status") | "groq" (Groq, needs GROQ_API_KEYS -- the practical free-tier alternative while glm is blocked, request/token-rate-limited rather than credit-balance-gated). ` +
`CHOOSE: all three are interchangeable in capability, not just cost/speed -- switch away from "gemini" if its output has been unreliable for this task, or if it's hit its own rate-limit cooldown. ` +
`RESUME RULE: if resume_run_id resolves to a checkpoint that recorded a provider (any run started after this field existed), that recorded provider is always used and this argument is ignored -- switching providers mid-run risks corrupting the checkpointed conversation. If the checkpoint has no recorded provider (an older run), this argument is used as a fallback instead of erroring.`),
model: z.string().optional()
.describe(`DEFAULT: none set -- the chosen provider's own default model is used (GEMINI_MODEL or GLM_MODEL from config). ` +
`USE: override the specific model within the chosen provider, e.g. model: "z-ai/glm-4.5-air:free" with provider: "glm" to force OpenRouter's free-tier model instead of the default paid GLM_MODEL (useful when the account is low on OpenRouter credits). ` +
`WARNING -- CASCADE DISABLED: passing a model that differs from the provider's own default model skips that provider's fallback-model list entirely (GLM_FALLBACK_MODELS / GEMINI_FALLBACK_MODELS are NOT tried) -- only the requested model is used, so a 429/503 on it fails the call instead of cascading to another model. API-key rotation (OPENROUTER_API_KEYS) is unaffected either way and still applies. ` +
.describe(`DEFAULT: none set -- the chosen provider's own default model is used (GEMINI_MODEL, GLM_MODEL, or GROQ_MODEL from config, depending on provider). ` +
`USE: override the specific model within the chosen provider, e.g. model: "qwen/qwen3.6-27b" with provider: "groq" to use Groq's stronger-but-preview coding model instead of the production default, or model: "z-ai/glm-4.5-air:free" with provider: "glm" to force OpenRouter's free-tier model instead of the default paid GLM_MODEL. ` +
`WARNING -- CASCADE DISABLED: passing a model that differs from the provider's own default model skips that provider's fallback-model list entirely (GLM_FALLBACK_MODELS / GROQ_FALLBACK_MODELS / GEMINI_FALLBACK_MODELS are NOT tried) -- only the requested model is used, so a 429/503 on it fails the call instead of cascading to another model. API-key rotation (OPENROUTER_API_KEYS / GROQ_API_KEYS) is unaffected either way and still applies. ` +
`RESUME RULE: same as provider -- if resume_run_id resolves to a checkpoint that recorded a model, that recorded model is always used and this argument is ignored. If the checkpoint has no recorded model (an older run, or a run that didn't specify one), this argument is used as a fallback instead of erroring.`),
maxOutputTokens: z.number().optional()
.describe(`DEFAULT: for provider "gemini", none set (Gemini's own API default applies, no cap sent). For provider "glm", ${GLM_DEFAULT_MAX_OUTPUT_TOKENS} (GLM_DEFAULT_MAX_OUTPUT_TOKENS from config) if this argument is omitted. ` +
`USE: caps the per-turn (not whole-conversation) output token budget for each model call in the investigation loop. Raise this if answers are getting cut off mid-response; lower it if OpenRouter credits are tight. ` +
`WHY GLM NEEDS A DEFAULT: with no max_tokens at all, OpenRouter defaults a request to the target model's FULL max context (e.g. 65536 for z-ai/glm-4.6) -- on a credit-limited account this fails EVERY GLM call with a 402 "requires more credits, or fewer max_tokens" error regardless of which model is selected, so provider "glm" always sends a value even when this argument is omitted. ` +
.describe(`DEFAULT: for provider "gemini", none set (Gemini's own API default applies, no cap sent). For provider "glm", ${GLM_DEFAULT_MAX_OUTPUT_TOKENS} (GLM_DEFAULT_MAX_OUTPUT_TOKENS from config) if this argument is omitted. For provider "groq", ${GROQ_DEFAULT_MAX_OUTPUT_TOKENS} (GROQ_DEFAULT_MAX_OUTPUT_TOKENS from config) if omitted. ` +
`USE: caps the per-turn (not whole-conversation) output token budget for each model call in the investigation loop. Raise this if answers are getting cut off mid-response; lower it if OpenRouter credits are tight (glm) or Groq's per-model token-per-minute limit is being hit. ` +
`WHY GLM/GROQ NEED A DEFAULT: with no max_tokens at all, OpenRouter defaults a request to the target model's FULL max context (e.g. 65536 for z-ai/glm-4.6) -- on a credit-limited account this fails EVERY GLM call with a 402 "requires more credits, or fewer max_tokens" error regardless of which model is selected, so provider "glm" always sends a value even when this argument is omitted. Groq's default is set pre-emptively for the same class of reason, though this hasn't (yet) been forced by an identical live failure the way GLM's was. ` +
`RESUME RULE: same as provider/model -- if resume_run_id resolves to a checkpoint that recorded a value, that recorded value is always used and this argument is ignored. If the checkpoint has no recorded value (an older run), this argument (or the provider default above) is used as a fallback instead of erroring.`),
},
async ({ task, max_steps = 20, log_to_notion = false, resume_run_id, show_transcript = false, provider, model, maxOutputTokens }) => {
Expand Down
Loading