




























@@ -17,6 +17,7 @@ import {
1717import {
1818createProviderHttpError,
1919providerOperationRetryConfig,
20+readProviderJsonObjectResponse,
2021} from "openclaw/plugin-sdk/provider-http";
2122import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
2223import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -90,6 +91,52 @@ type GeminiEmbeddingRequest = {
9091};
9192export type GeminiTextEmbeddingRequest = GeminiEmbeddingRequest;
929394+function asRecord(value: unknown): Record<string, unknown> | undefined {
95+return typeof value === "object" && value !== null && !Array.isArray(value)
96+ ? (value as Record<string, unknown>)
97+ : undefined;
98+}
99+100+function malformedGeminiEmbeddingResponse(): Error {
101+return new Error("gemini embeddings failed: malformed JSON response");
102+}
103+104+function readGeminiEmbeddingValues(value: unknown): number[] {
105+if (!Array.isArray(value)) {
106+throw malformedGeminiEmbeddingResponse();
107+}
108+for (const entry of value) {
109+if (typeof entry !== "number" || !Number.isFinite(entry)) {
110+throw malformedGeminiEmbeddingResponse();
111+}
112+}
113+return value;
114+}
115+116+function readGeminiSingleEmbedding(payload: Record<string, unknown>): number[] {
117+const embedding = asRecord(payload.embedding);
118+if (!embedding) {
119+throw malformedGeminiEmbeddingResponse();
120+}
121+return readGeminiEmbeddingValues(embedding.values);
122+}
123+124+function readGeminiBatchEmbeddings(
125+payload: Record<string, unknown>,
126+expectedCount: number,
127+): number[][] {
128+if (!Array.isArray(payload.embeddings) || payload.embeddings.length !== expectedCount) {
129+throw malformedGeminiEmbeddingResponse();
130+}
131+return payload.embeddings.map((entry) => {
132+const embedding = asRecord(entry);
133+if (!embedding) {
134+throw malformedGeminiEmbeddingResponse();
135+}
136+return readGeminiEmbeddingValues(embedding.values);
137+});
138+}
139+93140/** Builds the text-only Gemini embedding request shape used across direct and batch APIs. */
94141export function buildGeminiTextEmbeddingRequest(params: {
95142text: string;
@@ -195,10 +242,7 @@ async function fetchGeminiEmbeddingPayload(params: {
195242client: GeminiEmbeddingClient;
196243endpoint: string;
197244body: unknown;
198-}): Promise<{
199-embedding?: { values?: number[] };
200-embeddings?: Array<{ values?: number[] }>;
201-}> {
245+}): Promise<Record<string, unknown>> {
202246return await executeWithApiKeyRotation({
203247provider: "google",
204248apiKeys: params.client.apiKeys,
@@ -221,10 +265,7 @@ async function fetchGeminiEmbeddingPayload(params: {
221265if (!res.ok) {
222266throw await createProviderHttpError(res, "gemini embeddings failed");
223267}
224-return (await res.json()) as {
225-embedding?: { values?: number[] };
226-embeddings?: Array<{ values?: number[] }>;
227-};
268+return await readProviderJsonObjectResponse(res, "gemini embeddings failed");
228269},
229270});
230271},
@@ -288,7 +329,7 @@ export async function createGeminiEmbeddingProvider(
288329outputDimensionality: isV2 ? outputDimensionality : undefined,
289330}),
290331});
291-return sanitizeAndNormalizeEmbedding(payload.embedding?.values ?? []);
332+return sanitizeAndNormalizeEmbedding(readGeminiSingleEmbedding(payload));
292333};
293334294335const embedBatchInputs = async (inputs: EmbeddingInput[]): Promise<number[][]> => {
@@ -309,8 +350,8 @@ export async function createGeminiEmbeddingProvider(
309350),
310351},
311352});
312-const embeddings = Array.isArray(payload.embeddings) ? payload.embeddings : [];
313-return inputs.map((_, index) => sanitizeAndNormalizeEmbedding(embeddings[index]?.values ?? []));
353+const embeddings = readGeminiBatchEmbeddings(payload, inputs.length);
354+return embeddings.map((values) => sanitizeAndNormalizeEmbedding(values));
314355};
315356316357const embedBatch = async (texts: string[]): Promise<number[][]> => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。