




















@@ -47,6 +47,28 @@ function buildModelsResponse(models: Array<{ id: string; supported_endpoints?: u
4747return { data: models };
4848}
494950+function cancelTrackedResponse(
51+text: string,
52+init: ResponseInit,
53+): {
54+response: Response;
55+wasCanceled: () => boolean;
56+} {
57+let canceled = false;
58+const stream = new ReadableStream<Uint8Array>({
59+start(controller) {
60+controller.enqueue(new TextEncoder().encode(text));
61+},
62+cancel() {
63+canceled = true;
64+},
65+});
66+return {
67+response: new Response(stream, init),
68+wasCanceled: () => canceled,
69+};
70+}
71+5072function mockDiscoveryResponse(spec: {
5173ok: boolean;
5274status?: number;
@@ -116,6 +138,7 @@ describe("githubCopilotMemoryEmbeddingProviderAdapter", () => {
116138117139afterEach(() => {
118140vi.restoreAllMocks();
141+vi.unstubAllGlobals();
119142resolveConfiguredSecretInputStringMock.mockReset();
120143resolveFirstGithubTokenMock.mockReset();
121144resolveCopilotApiTokenMock.mockReset();
@@ -221,6 +244,63 @@ describe("githubCopilotMemoryEmbeddingProviderAdapter", () => {
221244).rejects.toThrow("GitHub Copilot model discovery returned invalid JSON");
222245});
223246247+it("bounds model discovery error bodies", async () => {
248+const tracked = cancelTrackedResponse(`${"discovery denied ".repeat(1024)}tail`, {
249+status: 503,
250+headers: { "content-type": "text/plain" },
251+});
252+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
253+fetchWithSsrFGuardMock.mockImplementationOnce(async () => ({
254+response: tracked.response,
255+release: vi.fn(async () => {}),
256+}));
257+258+let caught: Error | undefined;
259+try {
260+await githubCopilotMemoryEmbeddingProviderAdapter.create(defaultCreateOptions());
261+} catch (error) {
262+caught = error as Error;
263+}
264+265+expect(caught?.message).toContain("GitHub Copilot model discovery HTTP 503");
266+expect(caught?.message).toContain("discovery denied");
267+expect(caught?.message).not.toContain("tail");
268+expect(caught?.message.length).toBeLessThan(8_300);
269+expect(tracked.wasCanceled()).toBe(true);
270+expect(textSpy).not.toHaveBeenCalled();
271+});
272+273+it("bounds embeddings error bodies", async () => {
274+mockDiscoveryResponse({
275+ok: true,
276+json: buildModelsResponse([
277+{ id: "text-embedding-3-small", supported_endpoints: ["/v1/embeddings"] },
278+]),
279+});
280+const tracked = cancelTrackedResponse(`${"embedding denied ".repeat(1024)}tail`, {
281+status: 429,
282+headers: { "content-type": "text/plain" },
283+});
284+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
285+const fetchImpl = vi.fn(async () => tracked.response);
286+vi.stubGlobal("fetch", fetchImpl);
287+const result = await githubCopilotMemoryEmbeddingProviderAdapter.create(defaultCreateOptions());
288+289+let caught: Error | undefined;
290+try {
291+await result.provider?.embedQuery("hello");
292+} catch (error) {
293+caught = error as Error;
294+}
295+296+expect(caught?.message).toContain("GitHub Copilot embeddings HTTP 429");
297+expect(caught?.message).toContain("embedding denied");
298+expect(caught?.message).not.toContain("tail");
299+expect(caught?.message.length).toBeLessThan(8_300);
300+expect(tracked.wasCanceled()).toBe(true);
301+expect(textSpy).not.toHaveBeenCalled();
302+});
303+224304it("honors remote overrides when creating the provider", async () => {
225305resolveConfiguredSecretInputStringMock.mockResolvedValue({ value: "gh_remote_token" });
226306mockDiscoveryResponse({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。