






























@@ -5,6 +5,7 @@ import { resolveAgentDir } from "../agents/agent-scope.js";
55import { createConfigIO, resetConfigRuntimeState } from "../config/config.js";
66import type { EmbeddingProviderAdapter } from "../plugins/embedding-providers.js";
77import type { MemoryEmbeddingProviderAdapter } from "../plugins/memory-embedding-providers.js";
8+import { startOpenAiCompatGatewayServer } from "./openai-compatible-http.test-helpers.js";
89import { getFreePort, installGatewayTestHooks, testState } from "./test-helpers.js";
9101011installGatewayTestHooks({ scope: "suite" });
@@ -47,7 +48,7 @@ let createGenericEmbeddingProviderMock: ReturnType<
4748}>
4849>
4950>;
50-let enabledServer: Awaited<ReturnType<typeof startServer>>;
51+let enabledServer: Awaited<ReturnType<typeof startOpenAiCompatGatewayServer>>;
5152let enabledPort: number;
52535354beforeAll(async () => {
@@ -122,7 +123,12 @@ beforeAll(async () => {
122123registerEmbeddingProvider(openAiCompatibleAdapter);
123124({ startGatewayServer } = await import("./server.js"));
124125enabledPort = await getFreePort();
125-enabledServer = await startServer(enabledPort, { openAiChatCompletionsEnabled: true });
126+enabledServer = await startOpenAiCompatGatewayServer({
127+ startGatewayServer,
128+port: enabledPort,
129+auth: { mode: "token", token: "secret" },
130+openAiChatCompletionsEnabled: true,
131+});
126132});
127133128134afterAll(async () => {
@@ -132,15 +138,6 @@ afterAll(async () => {
132138vi.resetModules();
133139});
134140135-async function startServer(port: number, opts?: { openAiChatCompletionsEnabled?: boolean }) {
136-return await startGatewayServer(port, {
137-host: "127.0.0.1",
138-auth: { mode: "token", token: "secret" },
139-controlUiEnabled: false,
140-openAiChatCompletionsEnabled: opts?.openAiChatCompletionsEnabled ?? false,
141-});
142-}
143-144141async function postEmbeddings(body: unknown, headers?: Record<string, string>) {
145142return await fetch(`http://127.0.0.1:${enabledPort}/v1/embeddings`, {
146143method: "POST",
@@ -154,6 +151,60 @@ async function postEmbeddings(body: unknown, headers?: Record<string, string>) {
154151});
155152}
156153154+async function expectDefaultEmbeddingResponse(res: Response) {
155+expect(res.status).toBe(200);
156+const json = (await res.json()) as {
157+object?: string;
158+data?: Array<{ object?: string; embedding?: number[] }>;
159+};
160+expect(json.object).toBe("list");
161+expect(json.data?.[0]?.object).toBe("embedding");
162+expect(json.data?.[0]?.embedding).toEqual([0.1, 0.2]);
163+}
164+165+async function expectEmbeddingData(
166+res: Response,
167+expected: Array<{ object: "embedding"; index: number; embedding: number[] }>,
168+) {
169+expect(res.status).toBe(200);
170+const json = (await res.json()) as {
171+data?: Array<{ embedding?: number[]; index?: number }>;
172+};
173+expect(json.data).toEqual(expected);
174+}
175+176+async function expectInvalidEmbeddingRequest(res: Response, message?: string) {
177+expect(res.status).toBe(400);
178+const json = (await res.json()) as { error?: { type?: string; message?: string } };
179+if (message === undefined) {
180+expect(json.error?.type).toBe("invalid_request_error");
181+return;
182+}
183+expect(json.error).toEqual({
184+type: "invalid_request_error",
185+ message,
186+});
187+}
188+189+async function expectGenericProviderEmbeddingRequest(expectedProviderCall: {
190+provider: string;
191+model: string;
192+dimensions: number;
193+inputType: string;
194+queryInputType: string;
195+documentInputType: string;
196+}) {
197+const res = await postEmbeddings({
198+model: "openclaw/default",
199+input: ["a", "b"],
200+});
201+await expectEmbeddingData(res, [
202+{ object: "embedding", index: 0, embedding: [9.1, 9.2] },
203+{ object: "embedding", index: 1, embedding: [10.1, 9.2] },
204+]);
205+expect(latestCreateGenericEmbeddingProviderOptions()).toMatchObject(expectedProviderCall);
206+}
207+157208function latestCreateEmbeddingProviderOptions(): {
158209agentDir?: string;
159210model?: string;
@@ -189,24 +240,13 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
189240model: "openclaw/default",
190241input: "hello",
191242});
192-expect(single.status).toBe(200);
193-const singleJson = (await single.json()) as {
194-object?: string;
195-data?: Array<{ object?: string; embedding?: number[]; index?: number }>;
196-};
197-expect(singleJson.object).toBe("list");
198-expect(singleJson.data?.[0]?.object).toBe("embedding");
199-expect(singleJson.data?.[0]?.embedding).toEqual([0.1, 0.2]);
243+await expectDefaultEmbeddingResponse(single);
200244201245const batch = await postEmbeddings({
202246model: "openclaw/default",
203247input: ["a", "b"],
204248});
205-expect(batch.status).toBe(200);
206-const batchJson = (await batch.json()) as {
207-data?: Array<{ embedding?: number[]; index?: number }>;
208-};
209-expect(batchJson.data).toEqual([
249+await expectEmbeddingData(batch, [
210250{ object: "embedding", index: 0, embedding: [0.1, 0.2] },
211251{ object: "embedding", index: 1, embedding: [1.1, 1.2] },
212252]);
@@ -249,9 +289,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
249289model: "openclaw/default",
250290input: [{ nope: true }],
251291});
252-expect(res.status).toBe(400);
253-const json = (await res.json()) as { error?: { type?: string } };
254-expect(json.error?.type).toBe("invalid_request_error");
292+await expectInvalidEmbeddingRequest(res);
255293});
256294257295it("ignores narrower declared scopes for shared-secret bearer auth", async () => {
@@ -262,14 +300,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
262300},
263301{ "x-openclaw-scopes": "operator.read" },
264302);
265-expect(res.status).toBe(200);
266-const json = (await res.json()) as {
267-object?: string;
268-data?: Array<{ object?: string; embedding?: number[] }>;
269-};
270-expect(json.object).toBe("list");
271-expect(json.data?.[0]?.object).toBe("embedding");
272-expect(json.data?.[0]?.embedding).toEqual([0.1, 0.2]);
303+await expectDefaultEmbeddingResponse(res);
273304});
274305275306it("allows requests with an empty declared scopes header", async () => {
@@ -280,14 +311,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
280311},
281312{ "x-openclaw-scopes": "" },
282313);
283-expect(res.status).toBe(200);
284-const json = (await res.json()) as {
285-object?: string;
286-data?: Array<{ object?: string; embedding?: number[] }>;
287-};
288-expect(json.object).toBe("list");
289-expect(json.data?.[0]?.object).toBe("embedding");
290-expect(json.data?.[0]?.embedding).toEqual([0.1, 0.2]);
314+await expectDefaultEmbeddingResponse(res);
291315});
292316293317it("allows requests when the operator scopes header is missing", async () => {
@@ -302,14 +326,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
302326input: "hello",
303327}),
304328});
305-expect(res.status).toBe(200);
306-const json = (await res.json()) as {
307-object?: string;
308-data?: Array<{ object?: string; embedding?: number[] }>;
309-};
310-expect(json.object).toBe("list");
311-expect(json.data?.[0]?.object).toBe("embedding");
312-expect(json.data?.[0]?.embedding).toEqual([0.1, 0.2]);
329+await expectDefaultEmbeddingResponse(res);
313330});
314331315332it("routes explicit OpenAI-compatible embeddings through generic providers", async () => {
@@ -329,20 +346,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
329346};
330347resetConfigRuntimeState();
331348332-const res = await postEmbeddings({
333-model: "openclaw/default",
334-input: ["a", "b"],
335-});
336-expect(res.status).toBe(200);
337-const json = (await res.json()) as {
338-data?: Array<{ embedding?: number[]; index?: number }>;
339-};
340-expect(json.data).toEqual([
341-{ object: "embedding", index: 0, embedding: [9.1, 9.2] },
342-{ object: "embedding", index: 1, embedding: [10.1, 9.2] },
343-]);
344-const lastCall = latestCreateGenericEmbeddingProviderOptions();
345-expect(lastCall).toMatchObject({
349+await expectGenericProviderEmbeddingRequest({
346350provider: "openai-compatible",
347351model: "nomic-embed-text",
348352dimensions: 768,
@@ -386,20 +390,7 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
386390};
387391resetConfigRuntimeState();
388392389-const res = await postEmbeddings({
390-model: "openclaw/default",
391-input: ["a", "b"],
392-});
393-expect(res.status).toBe(200);
394-const json = (await res.json()) as {
395-data?: Array<{ embedding?: number[]; index?: number }>;
396-};
397-expect(json.data).toEqual([
398-{ object: "embedding", index: 0, embedding: [9.1, 9.2] },
399-{ object: "embedding", index: 1, embedding: [10.1, 9.2] },
400-]);
401-const lastCall = latestCreateGenericEmbeddingProviderOptions();
402-expect(lastCall).toMatchObject({
393+await expectGenericProviderEmbeddingRequest({
403394provider: "tenant-embeddings",
404395model: "nomic-embed-text",
405396dimensions: 768,
@@ -414,12 +405,10 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
414405model: "ollama/nomic-embed-text",
415406input: "hello",
416407});
417-expect(res.status).toBe(400);
418-const json = (await res.json()) as { error?: { type?: string; message?: string } };
419-expect(json.error).toEqual({
420-type: "invalid_request_error",
421-message: "Invalid `model`. Use `openclaw` or `openclaw/<agentId>`.",
422-});
408+await expectInvalidEmbeddingRequest(
409+res,
410+"Invalid `model`. Use `openclaw` or `openclaw/<agentId>`.",
411+);
423412});
424413425414it("rejects disallowed x-openclaw-model provider overrides", async () => {
@@ -430,25 +419,18 @@ describe("OpenAI-compatible embeddings HTTP API (e2e)", () => {
430419},
431420{ "x-openclaw-model": "ollama/nomic-embed-text" },
432421);
433-expect(res.status).toBe(400);
434-const json = (await res.json()) as { error?: { type?: string; message?: string } };
435-expect(json.error).toEqual({
436-type: "invalid_request_error",
437-message: "This agent does not allow that embedding provider on `/v1/embeddings`.",
438-});
422+await expectInvalidEmbeddingRequest(
423+res,
424+"This agent does not allow that embedding provider on `/v1/embeddings`.",
425+);
439426});
440427441428it("rejects oversized batches", async () => {
442429const res = await postEmbeddings({
443430model: "openclaw/default",
444431input: Array.from({ length: 129 }, () => "x"),
445432});
446-expect(res.status).toBe(400);
447-const json = (await res.json()) as { error?: { type?: string; message?: string } };
448-expect(json.error).toEqual({
449-type: "invalid_request_error",
450-message: "Too many inputs (max 128).",
451-});
433+await expectInvalidEmbeddingRequest(res, "Too many inputs (max 128).");
452434});
453435454436it("sanitizes provider failures", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。