Skip to content

Commit 1aa7caf

Browse files
hugenshencursoragentsallyom
authored
fix(github-copilot): bound model discovery and embeddings JSON response (#96499)
* fix(github-copilot): bound model discovery and embeddings JSON response reads The GitHub Copilot embeddings plugin already bounds its error response bodies via readResponseTextLimited, but the success JSON reads for both model discovery and the embeddings call used unbounded response.json(). Route both through readProviderJsonResponse (16 MiB cap). Update isCopilotSetupError to recognise the new error label prefix so auto-selection still falls through on malformed discovery responses. Update tests to use proper Response objects and the new error messages. AI-assisted. Co-authored-by: Cursor <[email protected]> * fix(github-copilot): use memory embedding response cap Signed-off-by: sallyom <[email protected]> --------- Signed-off-by: sallyom <[email protected]> Co-authored-by: Cursor <[email protected]> Co-authored-by: sallyom <[email protected]>
1 parent 66e2fcc commit 1aa7caf

2 files changed

Lines changed: 24 additions & 28 deletions

File tree

extensions/github-copilot/embeddings.test.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,16 @@ function mockDiscoveryResponse(spec: {
7575
json?: unknown;
7676
text?: string;
7777
}) {
78+
const status = spec.status ?? (spec.ok ? 200 : 500);
79+
const response =
80+
spec.json !== undefined
81+
? new Response(JSON.stringify(spec.json), {
82+
status,
83+
headers: { "Content-Type": "application/json" },
84+
})
85+
: new Response(spec.text ?? "", { status });
7886
fetchWithSsrFGuardMock.mockImplementationOnce(async () => ({
79-
response: {
80-
ok: spec.ok,
81-
status: spec.status ?? (spec.ok ? 200 : 500),
82-
json: async () => spec.json,
83-
text: async () => spec.text ?? "",
84-
},
87+
response,
8588
release: vi.fn(async () => {}),
8689
}));
8790
}
@@ -228,20 +231,16 @@ describe("githubCopilotMemoryEmbeddingProviderAdapter", () => {
228231

229232
it("wraps invalid discovery JSON as a setup error", async () => {
230233
fetchWithSsrFGuardMock.mockImplementationOnce(async () => ({
231-
response: {
232-
ok: true,
234+
response: new Response("not-valid-json{{{", {
233235
status: 200,
234-
json: async () => {
235-
throw new SyntaxError("bad json");
236-
},
237-
text: async () => "",
238-
},
236+
headers: { "Content-Type": "application/json" },
237+
}),
239238
release: vi.fn(async () => {}),
240239
}));
241240

242241
await expect(
243242
githubCopilotMemoryEmbeddingProviderAdapter.create(defaultCreateOptions()),
244-
).rejects.toThrow("GitHub Copilot model discovery returned invalid JSON");
243+
).rejects.toThrow("github-copilot.model-discovery: malformed JSON response");
245244
});
246245

247246
it("bounds model discovery error bodies", async () => {
@@ -360,7 +359,7 @@ describe("githubCopilotMemoryEmbeddingProviderAdapter", () => {
360359
).toBe(true);
361360
expect(
362361
shouldContinueAutoSelection(
363-
new Error("GitHub Copilot model discovery returned invalid JSON"),
362+
new Error("github-copilot.model-discovery: malformed JSON response"),
364363
),
365364
).toBe(true);
366365
expect(shouldContinueAutoSelection(new Error("Network timeout"))).toBe(false);

extensions/github-copilot/embeddings.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {
77
type MemoryEmbeddingProviderAdapter,
88
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
99
import { buildCopilotIdeHeaders } from "openclaw/plugin-sdk/provider-auth";
10-
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
10+
import {
11+
readProviderJsonResponse,
12+
readResponseTextLimited,
13+
} from "openclaw/plugin-sdk/provider-http";
1114
import { resolveConfiguredSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
1215
import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
1316
import { resolveFirstGithubToken } from "./auth.js";
@@ -29,6 +32,7 @@ const COPILOT_HEADERS_STATIC: Record<string, string> = {
2932
...buildCopilotIdeHeaders(),
3033
};
3134
const COPILOT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
35+
const COPILOT_EMBEDDINGS_RESPONSE_MAX_BYTES = 64 * 1024 * 1024;
3236

3337
function buildSsrfPolicy(baseUrl: string): SsrFPolicy | undefined {
3438
try {
@@ -70,6 +74,7 @@ function isCopilotSetupError(err: unknown): boolean {
7074
err.message.includes("Copilot token response") ||
7175
err.message.includes("No embedding models available") ||
7276
err.message.includes("GitHub Copilot model discovery") ||
77+
err.message.includes("github-copilot.model-discovery") ||
7378
err.message.includes("GitHub Copilot embedding model") ||
7479
err.message.includes("Unexpected response from GitHub Copilot token endpoint")
7580
);
@@ -100,12 +105,7 @@ async function discoverEmbeddingModels(params: {
100105
const detail = await readResponseTextLimited(response, COPILOT_ERROR_BODY_LIMIT_BYTES);
101106
throw new Error(`GitHub Copilot model discovery HTTP ${response.status}: ${detail}`);
102107
}
103-
let payload: unknown;
104-
try {
105-
payload = await response.json();
106-
} catch {
107-
throw new Error("GitHub Copilot model discovery returned invalid JSON");
108-
}
108+
const payload = await readProviderJsonResponse(response, "github-copilot.model-discovery");
109109
const allModels = Array.isArray((payload as { data?: unknown })?.data)
110110
? ((payload as { data: CopilotModelEntry[] }).data ?? [])
111111
: [];
@@ -246,12 +246,9 @@ async function createGitHubCopilotEmbeddingProvider(
246246
throw new Error(`GitHub Copilot embeddings HTTP ${response.status}: ${detail}`);
247247
}
248248

249-
let payload: unknown;
250-
try {
251-
payload = await response.json();
252-
} catch {
253-
throw new Error("GitHub Copilot embeddings returned invalid JSON");
254-
}
249+
const payload = await readProviderJsonResponse(response, "github-copilot.embeddings", {
250+
maxBytes: COPILOT_EMBEDDINGS_RESPONSE_MAX_BYTES,
251+
});
255252
return parseGitHubCopilotEmbeddingPayload(payload, input.length);
256253
},
257254
});

0 commit comments

Comments
 (0)