


















@@ -4,7 +4,10 @@ import {
44resolveCopilotApiToken,
55} from "openclaw/plugin-sdk/github-copilot-token";
66import {
7-createGitHubCopilotEmbeddingProvider,
7+buildRemoteBaseUrlPolicy,
8+sanitizeAndNormalizeEmbedding,
9+withRemoteHttpResponse,
10+type MemoryEmbeddingProvider,
811type MemoryEmbeddingProviderAdapter,
912} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
1013import { fetchWithSsrFGuard, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -44,6 +47,15 @@ type CopilotModelEntry = {
4447supported_endpoints?: unknown;
4548};
464950+type GitHubCopilotEmbeddingClient = {
51+githubToken: string;
52+model: string;
53+baseUrl?: string;
54+headers?: Record<string, string>;
55+env?: NodeJS.ProcessEnv;
56+fetchImpl?: typeof fetch;
57+};
58+4759function isCopilotSetupError(err: unknown): boolean {
4860if (!(err instanceof Error)) {
4961return false;
@@ -147,9 +159,126 @@ function pickBestModel(available: string[], userModel?: string): string {
147159throw new Error("No embedding models available from GitHub Copilot");
148160}
149161162+function parseGitHubCopilotEmbeddingPayload(payload: unknown, expectedCount: number): number[][] {
163+if (!payload || typeof payload !== "object") {
164+throw new Error("GitHub Copilot embeddings response missing data[]");
165+}
166+const data = (payload as { data?: unknown }).data;
167+if (!Array.isArray(data)) {
168+throw new Error("GitHub Copilot embeddings response missing data[]");
169+}
170+171+const vectors = Array.from<number[] | undefined>({ length: expectedCount });
172+for (const entry of data) {
173+if (!entry || typeof entry !== "object") {
174+throw new Error("GitHub Copilot embeddings response contains an invalid entry");
175+}
176+const indexValue = (entry as { index?: unknown }).index;
177+const embedding = (entry as { embedding?: unknown }).embedding;
178+const index = typeof indexValue === "number" ? indexValue : Number.NaN;
179+if (!Number.isInteger(index)) {
180+throw new Error("GitHub Copilot embeddings response contains an invalid index");
181+}
182+if (index < 0 || index >= expectedCount) {
183+throw new Error("GitHub Copilot embeddings response contains an out-of-range index");
184+}
185+if (vectors[index] !== undefined) {
186+throw new Error("GitHub Copilot embeddings response contains duplicate indexes");
187+}
188+if (!Array.isArray(embedding) || !embedding.every((value) => typeof value === "number")) {
189+throw new Error("GitHub Copilot embeddings response contains an invalid embedding");
190+}
191+vectors[index] = sanitizeAndNormalizeEmbedding(embedding);
192+}
193+194+for (let index = 0; index < expectedCount; index += 1) {
195+if (vectors[index] === undefined) {
196+throw new Error("GitHub Copilot embeddings response missing vectors for some inputs");
197+}
198+}
199+return vectors as number[][];
200+}
201+202+async function resolveGitHubCopilotEmbeddingSession(client: GitHubCopilotEmbeddingClient): Promise<{
203+baseUrl: string;
204+headers: Record<string, string>;
205+}> {
206+const token = await resolveCopilotApiToken({
207+githubToken: client.githubToken,
208+env: client.env,
209+fetchImpl: client.fetchImpl,
210+});
211+const baseUrl = client.baseUrl?.trim() || token.baseUrl || DEFAULT_COPILOT_API_BASE_URL;
212+return {
213+ baseUrl,
214+headers: {
215+ ...COPILOT_HEADERS_STATIC,
216+ ...client.headers,
217+Authorization: `Bearer ${token.token}`,
218+},
219+};
220+}
221+222+async function createGitHubCopilotEmbeddingProvider(
223+client: GitHubCopilotEmbeddingClient,
224+): Promise<{ provider: MemoryEmbeddingProvider; client: GitHubCopilotEmbeddingClient }> {
225+const initialSession = await resolveGitHubCopilotEmbeddingSession(client);
226+227+const embed = async (input: string[]): Promise<number[][]> => {
228+if (input.length === 0) {
229+return [];
230+}
231+232+const session = await resolveGitHubCopilotEmbeddingSession(client);
233+const url = `${session.baseUrl.replace(/\/$/, "")}/embeddings`;
234+return await withRemoteHttpResponse({
235+ url,
236+fetchImpl: client.fetchImpl,
237+ssrfPolicy: buildRemoteBaseUrlPolicy(session.baseUrl),
238+init: {
239+method: "POST",
240+headers: session.headers,
241+body: JSON.stringify({ model: client.model, input }),
242+},
243+onResponse: async (response) => {
244+if (!response.ok) {
245+throw new Error(
246+`GitHub Copilot embeddings HTTP ${response.status}: ${await response.text()}`,
247+);
248+}
249+250+let payload: unknown;
251+try {
252+payload = await response.json();
253+} catch {
254+throw new Error("GitHub Copilot embeddings returned invalid JSON");
255+}
256+return parseGitHubCopilotEmbeddingPayload(payload, input.length);
257+},
258+});
259+};
260+261+return {
262+provider: {
263+id: COPILOT_EMBEDDING_PROVIDER_ID,
264+model: client.model,
265+embedQuery: async (text) => {
266+const [vector] = await embed([text]);
267+return vector ?? [];
268+},
269+embedBatch: embed,
270+},
271+client: {
272+ ...client,
273+baseUrl: initialSession.baseUrl,
274+},
275+};
276+}
277+150278export const githubCopilotMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapter = {
151279id: COPILOT_EMBEDDING_PROVIDER_ID,
152280transport: "remote",
281+authProviderId: COPILOT_EMBEDDING_PROVIDER_ID,
153282autoSelectPriority: 15,
154283allowExplicitWhenConfiguredAuto: true,
155284shouldContinueAutoSelection: (err: unknown) => isCopilotSetupError(err),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。