Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions src/db/repositories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { and, desc, eq, gte, inArray, not, or, sql, type SQL } from "drizzle-orm";

Check notice on line 1 in src/db/repositories.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/db/repositories.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/db/repositories.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check notice on line 1 in src/db/repositories.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
import { getDb } from "./client";
import {
advisories,
Expand Down Expand Up @@ -2145,6 +2145,24 @@
return Number(row?.total ?? 0);
}

export async function countByokAiReviewEventsForRepoSince(env: Env, repoFullName: string, sinceIso: string): Promise<number> {
const db = getDb(env.DB);
const [row] = await db
.select({ total: sql<number>`count(*)` })
.from(aiUsageEvents)
.where(
and(
gte(aiUsageEvents.createdAt, sinceIso),
eq(aiUsageEvents.feature, "ai_review_pr"),
eq(aiUsageEvents.status, "ok"),
sql`${aiUsageEvents.model} like 'byok:%'`,
sql`json_extract(${aiUsageEvents.metadataJson}, '$.repoFullName') = ${repoFullName}`,
),
);
/* v8 ignore next -- SQL aggregate count always returns one row; fallback protects D1 driver anomalies. */
return Number(row?.total ?? 0);
}

export async function upsertContributorScoringProfile(env: Env, profile: ContributorScoringProfileRecord): Promise<void> {
const db = getDb(env.DB);
await db
Expand Down
2 changes: 2 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare global {

Check notice on line 1 in src/env.d.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/env.d.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/env.d.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check notice on line 1 in src/env.d.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
interface Env {
DB: D1Database;
JOBS: Queue;
Expand All @@ -10,6 +10,8 @@
AI_PUBLIC_COMMENTS_ENABLED?: string;
WORKERS_AI_SUMMARY_MODEL?: string;
AI_DAILY_NEURON_BUDGET?: string;
/** Per-repository/day cap for maintainer-paid BYOK AI review provider calls. */
AI_BYOK_DAILY_REPO_LIMIT?: string;
AI_MAX_OUTPUT_TOKENS?: string;
/** Optional Cloudflare AI Gateway id. When set, free Workers-AI review calls route through the gateway
* for caching, rate-limiting, request logging, and fallback. Unset = direct binding calls (unchanged). */
Expand Down
19 changes: 16 additions & 3 deletions src/services/ai-review.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Gittensory AI maintainer review (the `aiReview` capability).

Check notice on line 1 in src/services/ai-review.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in src/services/ai-review.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in src/services/ai-review.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check notice on line 1 in src/services/ai-review.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
//
// Two layers, both opt-in and both fail-safe (no AI / errors / over-budget / unsafe output → no public
// text and no gate finding; gittensory NEVER blocks because the model spoke):
Expand All @@ -13,9 +13,10 @@
// confirmed Gittensor contributors (the gate enforces that downstream).
//
// Every public string (notes + defect title/detail) is forced through `sanitizePublicComment`; anything
// that trips the public/private boundary is dropped, not published. Every model call is metered against
// the shared daily neuron budget and audited via `recordAiUsageEvent`.
import { recordAiUsageEvent, sumAiEstimatedNeuronsSince } from "../db/repositories";
// that trips the public/private boundary is dropped, not published. Free Workers-AI calls are metered against
// the shared daily neuron budget; maintainer-paid BYOK calls have a separate repo/day cap. All calls
// are audited via `recordAiUsageEvent`.
import { countByokAiReviewEventsForRepoSince, recordAiUsageEvent, sumAiEstimatedNeuronsSince } from "../db/repositories";
import { sanitizePublicComment } from "../queue-intelligence";

/**
Expand Down Expand Up @@ -227,6 +228,9 @@
* the existing fail-safe null path. Mirrors the github/gittensor fetch-timeout convention. */
const AI_PROVIDER_TIMEOUT_MS = 20_000;

/** Default per-repository/day cap for maintainer-paid BYOK advisory calls. */
const DEFAULT_BYOK_DAILY_REPO_LIMIT = 25;

/** Why a BYOK advisory call produced no review — surfaced in the audit event for observability (never a key). */
type ProviderFailure = "timeout" | "http_error" | "exception";
type ProviderReviewOutcome = { review: ModelReview | null; failure?: ProviderFailure };
Expand Down Expand Up @@ -328,6 +332,15 @@
return { status: "quota_exceeded", estimatedNeurons, remainingBudget };
}

if (input.providerKey) {
const byokDailyLimit = clampNumber(Number(env.AI_BYOK_DAILY_REPO_LIMIT || DEFAULT_BYOK_DAILY_REPO_LIMIT), 0, 10_000);
const byokUsed = await countByokAiReviewEventsForRepoSince(env, input.repoFullName, utcDayStartIso());
if (byokUsed >= byokDailyLimit) {
await record(env, input, "quota_exceeded", 0, `BYOK daily repo limit ${byokDailyLimit} reached`);
return { status: "quota_exceeded", estimatedNeurons, remainingBudget };
}
}

// Advisory write-up: BYOK frontier model if configured, else the free Workers-AI primary (with fallback).
let byokFailure: ProviderFailure | undefined;
let advisoryReview: ModelReview | null;
Expand Down
19 changes: 17 additions & 2 deletions test/unit/ai-review.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from "vitest";

Check notice on line 1 in test/unit/ai-review.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in test/unit/ai-review.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in test/unit/ai-review.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check notice on line 1 in test/unit/ai-review.test.ts

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
import {
__aiReviewInternals,
AI_CONSENSUS_FLOOR,
Expand Down Expand Up @@ -64,15 +64,30 @@
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ content: [{ type: "text", text: reviewJson({ assessment: "BYOK advisory." }) }] }), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const run = vi.fn();
// Free budget is exhausted (1 neuron), but a BYOK advisory bills the maintainer's account, so it still runs.
const env = createTestEnv({ AI: { run } as unknown as Ai, AI_SUMMARIES_ENABLED: "true", AI_PUBLIC_COMMENTS_ENABLED: "true", AI_DAILY_NEURON_BUDGET: "1" });
// Free budget is exhausted (1 neuron), but a BYOK advisory bills the maintainer's account, so it still runs
// while the separate BYOK repo/day quota has capacity.
const env = createTestEnv({ AI: { run } as unknown as Ai, AI_SUMMARIES_ENABLED: "true", AI_PUBLIC_COMMENTS_ENABLED: "true", AI_DAILY_NEURON_BUDGET: "1", AI_BYOK_DAILY_REPO_LIMIT: "1" });
const result = await runGittensoryAiReview(env, { ...baseInput, providerKey: { provider: "anthropic", key: "sk-ant-secret" } });
expect(result.status).toBe("ok");
expect(result.status === "ok" && result.advisoryNotes).toContain("BYOK advisory.");
expect(result.status === "ok" && result.estimatedNeurons).toBe(0); // advisory-only BYOK consumes no free budget
expect(fetchMock).toHaveBeenCalled();
expect(run).not.toHaveBeenCalled();
});

it("enforces a separate per-repo daily quota before BYOK provider calls", async () => {
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ content: [{ type: "text", text: reviewJson({ assessment: "BYOK advisory." }) }] }), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const run = vi.fn();
const env = createTestEnv({ AI: { run } as unknown as Ai, AI_SUMMARIES_ENABLED: "true", AI_PUBLIC_COMMENTS_ENABLED: "true", AI_DAILY_NEURON_BUDGET: "0", AI_BYOK_DAILY_REPO_LIMIT: "1" });
const providerKey = { provider: "anthropic" as const, key: "sk-ant-secret" };

await expect(runGittensoryAiReview(env, { ...baseInput, providerKey })).resolves.toMatchObject({ status: "ok" });
await expect(runGittensoryAiReview(env, { ...baseInput, prNumber: 8, providerKey })).resolves.toMatchObject({ status: "quota_exceeded" });

expect(fetchMock).toHaveBeenCalledTimes(1);
expect(run).not.toHaveBeenCalled();
});
});

describe("AI Gateway routing for free Workers-AI calls", () => {
Expand Down
1 change: 1 addition & 0 deletions wrangler.jsonc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{

Check notice on line 1 in wrangler.jsonc

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in wrangler.jsonc

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in wrangler.jsonc

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check notice on line 1 in wrangler.jsonc

View check run for this annotation

Deleted GitHub App / Gittensory Context

PR author has maintainer association

This PR appears to come from a maintainer-associated account.
"$schema": "node_modules/wrangler/config-schema.json",
// Auto-deploys to production via Cloudflare Workers Builds on push to `main`
// (deploy command: `npm run deploy:api` — applies pending D1 migrations, then `wrangler deploy`).
Expand Down Expand Up @@ -38,6 +38,7 @@
"AI_PUBLIC_COMMENTS_ENABLED": "false",
"WORKERS_AI_SUMMARY_MODEL": "@cf/meta/llama-3.1-8b-instruct-fp8-fast",
"AI_DAILY_NEURON_BUDGET": "10000",
"AI_BYOK_DAILY_REPO_LIMIT": "25",
"AI_MAX_OUTPUT_TOKENS": "256",
"AI_GATEWAY_ID": "",
"ADMIN_GITHUB_LOGINS": "JSONbored",
Expand Down