Address CodeRabbit provider feedback
This commit is contained in:
1 parent
ea9075bde5
commit
abaf845e94
19 files changed
+293
-64
No files matched your search
@@ -19,16 +19,21 @@ export const MAX_AI_EVALUATION_LOOPS = 10;
|
||||
export const getAiProviderChatCompletionsUrl = (baseUrl = DEFAULT_AI_PROVIDER_BASE_URL) =>
|
||||
`${baseUrl.replace(/\/+$/u, "")}/chat/completions`;
|
||||
export const resolveAiEvaluationProviderConfig = (environment: Record<string, string | undefined>) => {
|
||||
const baseUrl = (environment.AI_PROVIDER_BASE_URL ?? DEFAULT_AI_PROVIDER_BASE_URL).replace(/\/+$/u, "");
|
||||
const configuredBaseUrl = environment.AI_PROVIDER_BASE_URL?.trim();
|
||||
const baseUrl = (configuredBaseUrl || DEFAULT_AI_PROVIDER_BASE_URL).replace(/\/+$/u, "");
|
||||
const homarrProvider = baseUrl.endsWith("/api/ai/v1");
|
||||
const openRouterProvider = baseUrl === DEFAULT_AI_PROVIDER_BASE_URL;
|
||||
const providerDefaultModel = homarrProvider ? "homarr/deepseek-v4-flash-latest" : DEFAULT_GENERATOR_MODEL;
|
||||
return {
|
||||
apiKey: environment.AI_PROVIDER_API_KEY ?? environment.OPENROUTER_API_KEY,
|
||||
apiKey: environment.AI_PROVIDER_API_KEY ?? (openRouterProvider ? environment.OPENROUTER_API_KEY : undefined),
|
||||
baseUrl,
|
||||
generatorModel: environment.AI_PROVIDER_MODEL ?? environment.OPENROUTER_GENERATOR_MODEL ?? providerDefaultModel,
|
||||
generatorModel:
|
||||
environment.AI_PROVIDER_MODEL ??
|
||||
(openRouterProvider ? environment.OPENROUTER_GENERATOR_MODEL : undefined) ??
|
||||
providerDefaultModel,
|
||||
judgeModel:
|
||||
environment.AI_PROVIDER_JUDGE_MODEL ??
|
||||
environment.OPENROUTER_JUDGE_MODEL ??
|
||||
(openRouterProvider ? environment.OPENROUTER_JUDGE_MODEL : undefined) ??
|
||||
(homarrProvider ? providerDefaultModel : DEFAULT_JUDGE_MODEL),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
buildEvaluationPrompt,
|
||||
buildJudgePrompt,
|
||||
buildRepairPrompt,
|
||||
DEFAULT_AI_PROVIDER_BASE_URL,
|
||||
DEFAULT_GENERATOR_MODEL,
|
||||
DEFAULT_JUDGE_MODEL,
|
||||
getAiProviderChatCompletionsUrl,
|
||||
@@ -196,6 +197,33 @@ describe("AI authoring evaluation", () => {
|
||||
generatorModel: "homarr/deepseek-v4-flash-latest",
|
||||
judgeModel: "homarr/deepseek-v4-flash-latest",
|
||||
});
|
||||
expect(
|
||||
resolveAiEvaluationProviderConfig({
|
||||
AI_PROVIDER_BASE_URL: " ",
|
||||
OPENROUTER_API_KEY: "legacy-key",
|
||||
OPENROUTER_GENERATOR_MODEL: "legacy-generator",
|
||||
OPENROUTER_JUDGE_MODEL: "legacy-judge",
|
||||
}),
|
||||
).toEqual({
|
||||
apiKey: "legacy-key",
|
||||
baseUrl: DEFAULT_AI_PROVIDER_BASE_URL,
|
||||
generatorModel: "legacy-generator",
|
||||
judgeModel: "legacy-judge",
|
||||
});
|
||||
expect(
|
||||
resolveAiEvaluationProviderConfig({
|
||||
AI_PROVIDER_BASE_URL: "https://homarr.dev/api/ai/v1",
|
||||
AI_PROVIDER_API_KEY: "workshop-token",
|
||||
OPENROUTER_API_KEY: "must-not-leak",
|
||||
OPENROUTER_GENERATOR_MODEL: "must-not-apply",
|
||||
OPENROUTER_JUDGE_MODEL: "must-not-apply",
|
||||
}),
|
||||
).toEqual({
|
||||
apiKey: "workshop-token",
|
||||
baseUrl: "https://homarr.dev/api/ai/v1",
|
||||
generatorModel: "homarr/deepseek-v4-flash-latest",
|
||||
judgeModel: "homarr/deepseek-v4-flash-latest",
|
||||
});
|
||||
});
|
||||
|
||||
it("requires exceptional goal, design, practicality, and complexity scores", () => {
|
||||
|
||||
@@ -2094,7 +2094,7 @@
|
||||
"exhaustedDescription": "Your Homarr provider allowance is used for today. It resets at midnight UTC.",
|
||||
"unavailableDescription": "Sign in to the Workshop or wait for your Homarr provider allowance to reset.",
|
||||
"signIn": "Sign in with GitHub",
|
||||
"reset": "Resets {relative} at",
|
||||
"reset": "Resets {relative} at <time></time>",
|
||||
"toolCalls": "Each model request and completed tool call uses one request.",
|
||||
"retry": "Reload allowance",
|
||||
"level": {
|
||||
|
||||
@@ -291,11 +291,12 @@ export class WorkshopBackend {
|
||||
public get currentUser(): WorkshopUser | null {
|
||||
const record = this.pocketBase.authStore.record;
|
||||
if (!record) return null;
|
||||
return workshopUserSchema.parse({
|
||||
const parsed = workshopUserSchema.safeParse({
|
||||
id: record.id,
|
||||
name: record.name || "",
|
||||
isAdmin: record.isAdmin === true,
|
||||
});
|
||||
return parsed.success ? parsed.data : null;
|
||||
}
|
||||
|
||||
public get authToken(): string | null {
|
||||
@@ -326,7 +327,9 @@ export class WorkshopBackend {
|
||||
provider: "github",
|
||||
});
|
||||
this.pocketBase.authStore.save(auth.token, auth.record);
|
||||
return this.currentUser;
|
||||
const user = this.currentUser;
|
||||
if (!user) throw new Error("The Workshop returned an invalid user session.");
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw workshopError(error, "GitHub sign-in failed");
|
||||
}
|
||||
@@ -351,11 +354,13 @@ export class WorkshopBackend {
|
||||
|
||||
public async listAssistantActivity(limit = 10, signal?: AbortSignal): Promise<WorkshopAssistantActivity[]> {
|
||||
try {
|
||||
const result = await this.pocketBase.collection("assistant_activity").getList(1, Math.min(Math.max(limit, 1), 50), {
|
||||
sort: "-created",
|
||||
signal: requestSignal(signal),
|
||||
requestKey: null,
|
||||
});
|
||||
const result = await this.pocketBase
|
||||
.collection("assistant_activity")
|
||||
.getList(1, Math.min(Math.max(limit, 1), 50), {
|
||||
sort: "-created",
|
||||
signal: requestSignal(signal),
|
||||
requestKey: null,
|
||||
});
|
||||
return result.items.flatMap((item) => {
|
||||
const parsed = workshopAssistantActivitySchema.safeParse(item);
|
||||
return parsed.success ? [parsed.data] : [];
|
||||
|
||||
@@ -15,16 +15,21 @@ vi.mock("pocketbase", () => {
|
||||
public readonly authStore = {
|
||||
token: "",
|
||||
record: null as Record<string, unknown> | null,
|
||||
isValid: false,
|
||||
get isValid() {
|
||||
try {
|
||||
const payload = JSON.parse(atob(this.token.split(".")[1] ?? "")) as { exp?: unknown };
|
||||
return typeof payload.exp === "number" && payload.exp > Date.now() / 1000;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
save: (token: string, record: Record<string, unknown>) => {
|
||||
this.authStore.token = token;
|
||||
this.authStore.record = record;
|
||||
this.authStore.isValid = true;
|
||||
},
|
||||
clear: () => {
|
||||
this.authStore.token = "";
|
||||
this.authStore.record = null;
|
||||
this.authStore.isValid = false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -63,6 +68,8 @@ import { WorkshopBackend } from "./backend";
|
||||
import type { WorkshopSubmissionRecord } from "./backend";
|
||||
import * as workshopClient from "./client";
|
||||
|
||||
const validWorkshopToken = "header.eyJleHAiOjQxMDI0NDQ4MDB9.signature";
|
||||
|
||||
describe("WorkshopBackend", () => {
|
||||
test("resolves the advertised client module", () => {
|
||||
expect(workshopClient.WorkshopBackend).toBe(WorkshopBackend);
|
||||
@@ -108,7 +115,7 @@ describe("WorkshopBackend", () => {
|
||||
test("reuses a valid Workshop session for the Homarr provider allowance", async () => {
|
||||
const client = new WorkshopBackend("https://workshop.example.com");
|
||||
expect(client.authToken).toBeNull();
|
||||
client.pocketBase.authStore.save("workshop-token", { id: "author-id", name: "octocat" } as never);
|
||||
client.pocketBase.authStore.save(validWorkshopToken, { id: "author-id", name: "octocat" } as never);
|
||||
mocks.send.mockResolvedValue({
|
||||
limit: 50,
|
||||
used: 7,
|
||||
@@ -117,13 +124,26 @@ describe("WorkshopBackend", () => {
|
||||
});
|
||||
|
||||
await expect(client.getAssistantUsage()).resolves.toMatchObject({ limit: 50, remaining: 43 });
|
||||
expect(client.authToken).toBe("workshop-token");
|
||||
expect(client.authToken).toBe(validWorkshopToken);
|
||||
expect(mocks.send).toHaveBeenCalledWith(
|
||||
"/api/ai/usage",
|
||||
expect.objectContaining({ method: "GET", requestKey: null, signal: expect.any(AbortSignal) }),
|
||||
);
|
||||
});
|
||||
|
||||
test("fails closed for expired tokens and malformed Workshop user records", () => {
|
||||
const client = new WorkshopBackend("https://workshop.example.com");
|
||||
client.pocketBase.authStore.save("header.eyJleHAiOjF9.signature", {
|
||||
id: "author-id",
|
||||
name: "octocat",
|
||||
} as never);
|
||||
expect(client.authToken).toBeNull();
|
||||
|
||||
client.pocketBase.authStore.save(validWorkshopToken, { id: 42 } as never);
|
||||
expect(client.currentUser).toBeNull();
|
||||
expect(client.authToken).toBeNull();
|
||||
});
|
||||
|
||||
test("loads only valid anonymous provider activity records", async () => {
|
||||
mocks.getList.mockResolvedValue({
|
||||
items: [
|
||||
@@ -155,7 +175,7 @@ describe("WorkshopBackend", () => {
|
||||
|
||||
test("sends the exported schema while leaving initial revision ownership to PocketBase", async () => {
|
||||
const client = new WorkshopBackend("https://workshop.example.com");
|
||||
client.pocketBase.authStore.save("token", { id: "author-id" } as never);
|
||||
client.pocketBase.authStore.save(validWorkshopToken, { id: "author-id" } as never);
|
||||
mocks.create.mockResolvedValue({ id: "submission-id" });
|
||||
vi.spyOn(client, "get").mockResolvedValue(listingRecord() as never);
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export const workshopAssistantUsageSchema = z.object({
|
||||
limit: z.number().int().nonnegative(),
|
||||
used: z.number().int().nonnegative(),
|
||||
remaining: z.number().int().nonnegative(),
|
||||
resetsAt: z.string().datetime({ offset: true }),
|
||||
resetsAt: z.iso.datetime({ offset: true }),
|
||||
});
|
||||
export type WorkshopAssistantUsage = z.infer<typeof workshopAssistantUsageSchema>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user