diff --git a/.env.example b/.env.example index b49feab5d5..d12c9b23cf 100644 --- a/.env.example +++ b/.env.example @@ -463,11 +463,13 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review # # synthesis = both review, then ONE merged decision. # AI_ON_MERGE=either # synthesis merge rule: either (block if EITHER reviewer flags) | # # both (block only when both do). Ignored unless AI_COMBINE=synthesis. -# AI_DAILY_NEURON_BUDGET=10000000 # daily spend cap (Cloudflare Workers AI "neurons") shared by AI -# # summaries, the free consensus-defect reviewer pair, and the -# # AI-slop scorer. Default 10,000,000/day; clamped to [0, 10000000]. -# # Only meters FREE Workers-AI calls — BYOK/provider calls above bill -# # the maintainer's own account and are not counted against this. +# AI_DAILY_NEURON_BUDGET=10000000 # daily spend cap (unit name is a Cloudflare Workers AI "neurons" +# # holdover; applied as a provider-agnostic heuristic budget) shared +# # by AI summaries, the free/default consensus-defect reviewer pair, +# # and the AI-slop scorer. Default 10,000,000/day; clamped to +# # [0, 10000000]. Only meters the free/default-reviewer path — +# # BYOK/provider calls above bill the maintainer's own account and +# # are not counted against this. # Ollama reviewer (AI_PROVIDER=ollama). Defaults: OLLAMA_AI_BASE_URL=http://localhost:11434/v1, # OLLAMA_AI_MODEL=llama3.1, no API key. Set the base URL to http://ollama:11434/v1 when using the compose # --profile ollama service. diff --git a/.gittensory.yml b/.gittensory.yml index 5314c48262..5a37ba67b6 100644 --- a/.gittensory.yml +++ b/.gittensory.yml @@ -40,7 +40,7 @@ gate: minScore: 60 # aiReview: # opt-in AI maintainer review (off by default; needs the AI flags enabled) # mode: advisory # block | advisory | off — block only blocks on a dual-model consensus defect - # byok: false # use a maintainer Anthropic/OpenAI key for the write-up; consensus stays free Workers AI + # byok: false # use a maintainer Anthropic/OpenAI key for the write-up; consensus stays on the free/default reviewer # allAuthors: false # true reviews every PR author with the selected self-host model(s) # provider: anthropic # anthropic | openai — which BYOK provider (the secret key is set via the dashboard, never here) # model: claude-3-5-sonnet-latest # optional model override for the BYOK write-up diff --git a/src/config/gittensory-repo-focus-manifest.ts b/src/config/gittensory-repo-focus-manifest.ts index 183df583ab..14172cd922 100644 --- a/src/config/gittensory-repo-focus-manifest.ts +++ b/src/config/gittensory-repo-focus-manifest.ts @@ -44,7 +44,7 @@ gate: minScore: 60 # aiReview: # opt-in AI maintainer review (off by default; needs the AI flags enabled) # mode: advisory # block | advisory | off — block only blocks on a dual-model consensus defect - # byok: false # use a maintainer Anthropic/OpenAI key for the write-up; consensus stays free Workers AI + # byok: false # use a maintainer Anthropic/OpenAI key for the write-up; consensus stays on the free/default reviewer # allAuthors: false # true reviews every PR author with the selected self-host model(s) # provider: anthropic # anthropic | openai — which BYOK provider (the secret key is set via the dashboard, never here) # model: claude-3-5-sonnet-latest # optional model override for the BYOK write-up diff --git a/src/db/repositories.ts b/src/db/repositories.ts index ee1fb7ab1f..5dfc18ce04 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -962,8 +962,9 @@ async function recordAiKeyChange( /** * Decrypt a repo's BYOK key for an AI call. Returns null when no key is configured OR the encryption - * secret is unavailable OR decryption fails — so the caller silently falls back to free Workers AI and - * a misconfiguration never blocks the review. The plaintext key must be used immediately and never cached. + * secret is unavailable OR decryption fails — so the caller silently falls back to the free/default + * reviewer and a misconfiguration never blocks the review. The plaintext key must be used immediately + * and never cached. */ export async function getDecryptedRepositoryAiKey(env: Env, fullName: string): Promise { const secret = env.TOKEN_ENCRYPTION_SECRET; diff --git a/src/env.d.ts b/src/env.d.ts index 7eba6a4542..66dc0c5c2b 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -47,7 +47,13 @@ declare global { PUBLIC_SITE_ORIGIN?: string; AI_SUMMARIES_ENABLED?: string; AI_PUBLIC_COMMENTS_ENABLED?: string; + /** Model id for a genuine Cloudflare Workers AI binding only — no live deployment (hosted or self-host) + * binds `env.AI` to Workers AI today (see CONVERGENCE_RUNBOOK.md), and self-host discards any + * `@cf/`-prefixed value here. Self-host operators should use the provider-specific `*_AI_MODEL` vars below. */ WORKERS_AI_SUMMARY_MODEL?: string; + /** Daily spend cap in Cloudflare Workers AI "neurons" for the free/default-reviewer path (shared across + * ai-review/ai-slop/ai-summaries/planner). The unit name is a Workers-AI holdover; it's applied as a + * provider-agnostic heuristic budget regardless of which configured provider actually serves the request. */ AI_DAILY_NEURON_BUDGET?: string; /** Per-repository/day cap for maintainer-paid BYOK AI review provider calls. */ AI_BYOK_DAILY_REPO_LIMIT?: string; diff --git a/src/review/adapters.ts b/src/review/adapters.ts index 74b6cf6669..7ac655a1a2 100644 --- a/src/review/adapters.ts +++ b/src/review/adapters.ts @@ -52,8 +52,9 @@ export function reviewVectorAdapter(vectorize: Vectorize): VectorAdapter { }; } -// ── Inference (Workers AI → InferenceAdapter). Feature-gated. Mirrors `ai.run(model, options)`; the cast -// bridges the overloaded Workers-AI `run` to the portable single-signature shape. ── +// ── Inference (the Ai-shaped adapter → InferenceAdapter). Feature-gated. Mirrors `ai.run(model, options)`; +// the cast bridges the overloaded `run` signature to the portable single-signature shape. `ai` is +// Workers AI historically, and on self-host is the generic provider router (src/selfhost/ai.ts). ── export function reviewInferenceAdapter(ai: Ai): InferenceAdapter { return { run: (model, options) => (ai as unknown as { run(m: string, o: Record): Promise }).run(model, options) }; } diff --git a/src/review/rag.ts b/src/review/rag.ts index 9b2c0e0df3..980312432a 100644 --- a/src/review/rag.ts +++ b/src/review/rag.ts @@ -22,7 +22,7 @@ // additive module. The host injects concrete adapters at the call site. // ── Injected infra interfaces (inlined from reviewbot src/platform/types.ts) ────────────────────── -// These mirror the platform-adapter shapes so the host can pass its Vectorize/Workers-AI/D1-backed +// These mirror the platform-adapter shapes so the host can pass its Vectorize/self-host-AI/D1-backed // implementations unchanged; nothing here depends on env bindings. /** Vector search surface (Vectorize → Qdrant / pgvector / sqlite-vec). */ @@ -43,7 +43,8 @@ export interface VectorAdapter { deleteByIds(ids: string[]): Promise; } -/** Inference (Workers AI → Ollama / OpenAI-compatible). Mirrors `ai.run(model, options)`. */ +/** Inference (the configured AI provider — self-host Codex/Claude Code/Ollama/OpenAI-compatible, or the + * legacy Workers-AI binding). Mirrors `ai.run(model, options)`. */ export interface InferenceAdapter { run(model: string, options: Record): Promise; } @@ -87,7 +88,10 @@ export type RagRetrievalResult = { }; /** bge-m3: large context window → a whole file/function embeds as one coherent chunk (fewer vectors - * than 512-token models, which helps both quality and the free-tier vector budget). */ + * than 512-token models, which helps both quality and the free-tier vector budget). This is a Workers-AI + * model id; the self-host embed path (`createOpenAiCompatibleAi` in src/selfhost/ai.ts) discards any + * `@cf/`-prefixed id and substitutes its own configured/default embed model (`AI_EMBED_MODEL`), so this + * constant only matters for a genuine Cloudflare Workers AI inference binding. */ export const EMBED_MODEL = "@cf/baai/bge-m3"; /** Default bge-m3 output dimension. Self-host can override this when QDRANT_DIM selects another model width. */ export const RAG_DIMENSIONS = 1024; @@ -96,7 +100,8 @@ const CHUNK_CHARS = 16000; // per-file chunk budget; only files larger than this const CHUNK_OVERLAP = 1500; /** Hard per-repo stored-vector cap — the free-tier guard. Source is prioritized so it survives the cap. */ export const MAX_CHUNKS_PER_REPO = 1500; -const EMBED_BATCH = 96; // Workers AI caps embedding input at 100 items/call +const EMBED_BATCH = 96; // Workers AI caps embedding input at 100 items/call; kept as a conservative general +// bound — other embed providers (Ollama/vLLM/etc via the self-host adapter) may not share this exact cap. const MAX_CONTEXT_CHARS = 14000; // bound the injected block (mirrors diff/knowledge budgets) export const MAX_FILE_BYTES = 1_000_000; // skip files larger than ~1MB diff --git a/src/types.ts b/src/types.ts index 9334b4858e..6a140e3922 100644 --- a/src/types.ts +++ b/src/types.ts @@ -677,7 +677,8 @@ export type RepositorySettings = { /** Slop-risk threshold (0-100) at/above which `slopGateMode: block` blocks. Default 60 (the `high` band). */ slopGateMinScore?: number | null | undefined; /** AI-assisted slop advisory (the `slopAiAdvisory` capability). When true AND `slopGateMode != off`, a - * free Workers-AI pass adds an ADVISORY-only `ai_slop_advisory` finding for semantic slop the + * free/default-reviewer pass (the configured self-host provider, or the legacy Workers-AI pair when + * none is configured) adds an ADVISORY-only `ai_slop_advisory` finding for semantic slop the * deterministic detector cannot quantify. It NEVER feeds slopRisk or the gate (only the deterministic * core blocks). Default false — opt-in via `.gittensory.yml gate.slop.aiAdvisory`. */ slopAiAdvisory: boolean; @@ -686,13 +687,14 @@ export type RepositorySettings = { * like every other blocker). Default `off` — AI is opt-in. */ aiReviewMode: GateRuleMode; /** Bring-your-own-key: when true and a provider key is configured for the repo, the advisory AI review - * is generated by the maintainer's frontier model (Anthropic/OpenAI) instead of free Workers AI. The - * consensus blocker always uses the free Workers-AI model pair regardless, so BYOK never changes who + * is generated by the maintainer's frontier model (Anthropic/OpenAI) instead of the free/default + * reviewer. The consensus blocker always uses the free/default reviewer pair regardless (the configured + * self-host provider, or the legacy Workers-AI pair when none is configured), so BYOK never changes who * can be blocked. Default false. */ aiReviewByok: boolean; /** Config-as-code BYOK provider for the advisory write-up. `null` = use the configured key's own - * provider. When set, it must match the stored key's provider or BYOK is skipped (Workers-AI fallback). - * The secret key itself is never here — only via the encrypted key store. */ + * provider. When set, it must match the stored key's provider or BYOK is skipped (falls back to the + * free/default reviewer). The secret key itself is never here — only via the encrypted key store. */ aiReviewProvider?: "anthropic" | "openai" | null | undefined; /** Config-as-code model override for the BYOK advisory write-up (e.g. "claude-3-5-sonnet-latest"). * `null` = use the key record's model, else a conservative per-provider default. */