mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-11 06:28:58 +00:00
fix(frontend): authenticate remaining gateway reads (#4827)
* fix(frontend): authenticate remaining gateway reads * fix(frontend): authenticate artifact reads * fix(frontend): include status in model errors
This commit is contained in:
parent
5b523bc979
commit
b341120a4a
@ -1,5 +1,7 @@
|
|||||||
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
import type { BaseStream } from "@langchain/langgraph-sdk/react";
|
||||||
|
|
||||||
|
import { fetch } from "@/core/api/fetcher";
|
||||||
|
|
||||||
import type { AgentThreadState } from "../threads";
|
import type { AgentThreadState } from "../threads";
|
||||||
|
|
||||||
import { buildWriteFileDraftContent } from "./preview";
|
import { buildWriteFileDraftContent } from "./preview";
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { throwGatewayApiError } from "@/core/api/errors";
|
||||||
|
import { fetch } from "@/core/api/fetcher";
|
||||||
|
|
||||||
import { getBackendBaseURL } from "../config";
|
import { getBackendBaseURL } from "../config";
|
||||||
import { isStaticWebsiteOnly } from "../static-mode";
|
import { isStaticWebsiteOnly } from "../static-mode";
|
||||||
|
|
||||||
@ -14,6 +17,12 @@ export async function loadModels(): Promise<ModelsResponse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${getBackendBaseURL()}/api/models`);
|
const res = await fetch(`${getBackendBaseURL()}/api/models`);
|
||||||
|
if (!res.ok) {
|
||||||
|
await throwGatewayApiError(
|
||||||
|
res,
|
||||||
|
`Failed to load models: ${res.status} ${res.statusText}`.trim(),
|
||||||
|
);
|
||||||
|
}
|
||||||
const data = (await res.json()) as Partial<ModelsResponse>;
|
const data = (await res.json()) as Partial<ModelsResponse>;
|
||||||
return {
|
return {
|
||||||
models: data.models ?? [],
|
models: data.models ?? [],
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { fetch } from "@/core/api/fetcher";
|
||||||
import { getBackendBaseURL } from "@/core/config";
|
import { getBackendBaseURL } from "@/core/config";
|
||||||
|
|
||||||
import type { WorkspaceChangesResponse } from "./types";
|
import type { WorkspaceChangesResponse } from "./types";
|
||||||
|
|||||||
@ -51,6 +51,7 @@ describe("loadArtifactContent", () => {
|
|||||||
expect(new Headers(init?.headers).get("Range")).toBe(
|
expect(new Headers(init?.headers).get("Range")).toBe(
|
||||||
`bytes=0-${ARTIFACT_PREVIEW_MAX_BYTES - 1}`,
|
`bytes=0-${ARTIFACT_PREVIEW_MAX_BYTES - 1}`,
|
||||||
);
|
);
|
||||||
|
expect(init?.credentials).toBe("include");
|
||||||
return new Response(bytes, {
|
return new Response(bytes, {
|
||||||
status: 206,
|
status: 206,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
71
frontend/tests/unit/core/models/api.test.ts
Normal file
71
frontend/tests/unit/core/models/api.test.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { afterEach, expect, test, rs } from "@rstest/core";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rs.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("loadModels includes credentials for authenticated gateway requests", async () => {
|
||||||
|
let requestedInit: RequestInit | undefined;
|
||||||
|
const fetchMock = rs.fn(
|
||||||
|
async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
requestedInit = init;
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "model-1",
|
||||||
|
name: "model-1",
|
||||||
|
model: "model-1",
|
||||||
|
display_name: "Model 1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
token_usage: { enabled: true },
|
||||||
|
}),
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
rs.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
const { loadModels } = await import("@/core/models/api");
|
||||||
|
|
||||||
|
await expect(loadModels()).resolves.toMatchObject({
|
||||||
|
models: [{ id: "model-1" }],
|
||||||
|
token_usage: { enabled: true },
|
||||||
|
});
|
||||||
|
expect(requestedInit?.credentials).toBe("include");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("loadModels rejects unsuccessful gateway responses", async () => {
|
||||||
|
rs.stubGlobal(
|
||||||
|
"fetch",
|
||||||
|
rs.fn(
|
||||||
|
async () =>
|
||||||
|
new Response(JSON.stringify({ detail: "Model registry unavailable" }), {
|
||||||
|
status: 503,
|
||||||
|
statusText: "Service Unavailable",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loadModels } = await import("@/core/models/api");
|
||||||
|
|
||||||
|
await expect(loadModels()).rejects.toThrow("Model registry unavailable");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("loadModels includes the status code when statusText is empty", async () => {
|
||||||
|
rs.stubGlobal(
|
||||||
|
"fetch",
|
||||||
|
rs.fn(
|
||||||
|
async () =>
|
||||||
|
new Response("upstream unavailable", {
|
||||||
|
status: 503,
|
||||||
|
statusText: "",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loadModels } = await import("@/core/models/api");
|
||||||
|
|
||||||
|
await expect(loadModels()).rejects.toThrow("Failed to load models: 503");
|
||||||
|
});
|
||||||
@ -47,4 +47,8 @@ test("fetchWorkspaceChanges can request file metadata without diffs", async () =
|
|||||||
const url = new URL(requestedUrl, "http://localhost");
|
const url = new URL(requestedUrl, "http://localhost");
|
||||||
expect(url.searchParams.get("include_files")).toBe("true");
|
expect(url.searchParams.get("include_files")).toBe("true");
|
||||||
expect(url.searchParams.get("include_diff")).toBe("false");
|
expect(url.searchParams.get("include_diff")).toBe("false");
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
expect.objectContaining({ credentials: "include" }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user