mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(ollama): scope embedding credentials to the selected host (#118753)
This commit is contained in:
committed by
GitHub
parent
94f431417b
commit
d857c8eebc
@@ -259,25 +259,159 @@ describe("ollama embedding provider", () => {
|
||||
).rejects.toThrow(/memory\.search\.remote\.apiKey: unresolved SecretRef/i);
|
||||
});
|
||||
|
||||
it("falls back to env key when provider apiKey is an unresolved SecretRef", async () => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "ollama-env");
|
||||
it.each(["ollama", "ollama-private"])(
|
||||
"resolves selected %s provider credential and header SecretRefs before ambient cloud auth",
|
||||
async (providerId) => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
vi.stubEnv("OLLAMA_PROXY_KEY", "synthetic-proxy-key");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "http://127.0.0.1:11434/v1",
|
||||
apiKey: { source: "env", provider: "default", id: "OLLAMA_API_KEY" },
|
||||
models: [],
|
||||
}),
|
||||
});
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig(
|
||||
{
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
},
|
||||
headers: {
|
||||
"X-Proxy-Auth": {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_PROXY_KEY",
|
||||
},
|
||||
},
|
||||
models: [],
|
||||
},
|
||||
providerId,
|
||||
),
|
||||
provider: providerId,
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "http://127.0.0.1:11434/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer ollama-env",
|
||||
},
|
||||
});
|
||||
});
|
||||
expectEmbeddingFetch(fetchMock, "https://selected-private-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Proxy-Auth": "synthetic-proxy-key",
|
||||
Authorization: "Bearer synthetic-selected-host-key",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ providerId: "ollama", surface: "apiKey", source: "env" },
|
||||
{ providerId: "ollama-private", surface: "apiKey", source: "env" },
|
||||
{ providerId: "ollama", surface: "headers", source: "env" },
|
||||
{ providerId: "ollama-private", surface: "headers", source: "env" },
|
||||
{ providerId: "ollama", surface: "apiKey", source: "file" },
|
||||
{ providerId: "ollama", surface: "headers", source: "file" },
|
||||
{ providerId: "ollama-private", surface: "apiKey", source: "exec" },
|
||||
{ providerId: "ollama-private", surface: "headers", source: "exec" },
|
||||
])(
|
||||
"fails closed before any request for unresolved $providerId $surface $source SecretRefs",
|
||||
async ({ providerId, surface, source }) => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
vi.stubEnv("OLLAMA_MISSING_SELECTED_SECRET", "");
|
||||
const fetchMock = mockEmbeddingFetch([1, 0]);
|
||||
const unavailableSecret = {
|
||||
source,
|
||||
provider: "default" as const,
|
||||
id:
|
||||
source === "file"
|
||||
? "/synthetic/missing-ollama-secret"
|
||||
: source === "exec"
|
||||
? "synthetic-missing-ollama-secret"
|
||||
: "OLLAMA_MISSING_SELECTED_SECRET",
|
||||
};
|
||||
const selectedHostKey = {
|
||||
source: "env" as const,
|
||||
provider: "default" as const,
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
};
|
||||
|
||||
await expect(
|
||||
createEmbeddingProvider({
|
||||
config: createProviderConfig(
|
||||
{
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: surface === "apiKey" ? unavailableSecret : selectedHostKey,
|
||||
...(surface === "headers"
|
||||
? {
|
||||
headers: {
|
||||
"X-Proxy-Auth": unavailableSecret,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
models: [],
|
||||
},
|
||||
providerId,
|
||||
),
|
||||
provider: providerId,
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
`models.providers.${providerId}.${
|
||||
surface === "headers" ? "headers.X-Proxy-Auth" : "apiKey"
|
||||
}`,
|
||||
);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
expect(fetchConfiguredLocalOriginWithSsrFGuardMock).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["OLLAMA_API_KEY", "ollama-local"])(
|
||||
"keeps explicit selected-host SecretRef value %s opaque",
|
||||
async (resolvedSecret) => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", resolvedSecret);
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
},
|
||||
models: [],
|
||||
}),
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://selected-private-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${resolvedSecret}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["$OLLAMA_SELECTED_HOST_KEY", "${OLLAMA_SELECTED_HOST_KEY}"])(
|
||||
"resolves selected-host env shorthand SecretRef %s before ambient cloud auth",
|
||||
async (apiKey) => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey,
|
||||
models: [],
|
||||
}),
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://selected-private-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer synthetic-selected-host-key",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("sends batch embeddings in one Ollama request", async () => {
|
||||
const { fetchMock, inputs } = mockBatchEmbeddingFetch(3);
|
||||
@@ -504,6 +638,95 @@ describe("ollama embedding provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["Authorization", "authorization", "AUTHORIZATION"])(
|
||||
"keeps explicit remote %s header ahead of ambient Ollama Cloud credentials",
|
||||
async (headerName) => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-tenant-b");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
remote: {
|
||||
baseUrl: "https://ollama.com",
|
||||
headers: { [headerName]: "Bearer synthetic-explicit-tenant-a" },
|
||||
},
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://ollama.com/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
[headerName]: "Bearer synthetic-explicit-tenant-a",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("uses explicit header auth without resolving an inactive selected-host apiKey SecretRef", async () => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_MISSING_SELECTED_SECRET", "");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_MISSING_SELECTED_SECRET",
|
||||
},
|
||||
models: [],
|
||||
}),
|
||||
remote: {
|
||||
baseUrl: "https://selected-private-host.invalid",
|
||||
headers: { authorization: "Bearer synthetic-explicit-tenant-a" },
|
||||
},
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://selected-private-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
authorization: "Bearer synthetic-explicit-tenant-a",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("skips a selected-host SecretRef header overridden case-insensitively by remote auth", async () => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
vi.stubEnv("OLLAMA_MISSING_SELECTED_SECRET", "");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
},
|
||||
headers: {
|
||||
"X-Proxy-Auth": {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_MISSING_SELECTED_SECRET",
|
||||
},
|
||||
},
|
||||
models: [],
|
||||
}),
|
||||
remote: {
|
||||
baseUrl: "https://selected-private-host.invalid",
|
||||
headers: { "x-proxy-auth": "synthetic-remote-header" },
|
||||
},
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://selected-private-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-proxy-auth": "synthetic-remote-header",
|
||||
Authorization: "Bearer synthetic-selected-host-key",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not attach provider apiKey to a different remote embedding host", async () => {
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
@@ -519,6 +742,74 @@ describe("ollama embedding provider", () => {
|
||||
expect(headers?.Authorization).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not forward selected-host SecretRefs to a different remote embedding host", async () => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_PROXY_KEY", "synthetic-selected-proxy-key");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
},
|
||||
headers: {
|
||||
"X-Selected-Host-Auth": {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_PROXY_KEY",
|
||||
},
|
||||
},
|
||||
models: [],
|
||||
}),
|
||||
remote: {
|
||||
baseUrl: "https://remote-embedding-host.invalid",
|
||||
apiKey: "synthetic-remote-key",
|
||||
headers: { "X-Remote-Auth": "synthetic-remote-header" },
|
||||
},
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://remote-embedding-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Remote-Auth": "synthetic-remote-header",
|
||||
Authorization: "Bearer synthetic-remote-key",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores an inactive selected-host SecretRef when remote credentials own another host", async () => {
|
||||
vi.stubEnv("OLLAMA_API_KEY", "synthetic-cloud-key");
|
||||
vi.stubEnv("OLLAMA_MISSING_SELECTED_SECRET", "");
|
||||
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
config: createProviderConfig({
|
||||
baseUrl: "https://selected-private-host.invalid/v1",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_MISSING_SELECTED_SECRET",
|
||||
},
|
||||
models: [],
|
||||
}),
|
||||
remote: {
|
||||
baseUrl: "https://remote-embedding-host.invalid",
|
||||
apiKey: "synthetic-remote-key",
|
||||
},
|
||||
});
|
||||
|
||||
expectEmbeddingFetch(fetchMock, "https://remote-embedding-host.invalid/api/embed", {
|
||||
input: "search_query: hello",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer synthetic-remote-key",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("attaches remote apiKey to a remote embedding host", async () => {
|
||||
const { fetchMock } = await embedTestQuery({
|
||||
remote: { baseUrl: "https://memory.example.com", apiKey: "remote-host-key" },
|
||||
@@ -624,6 +915,45 @@ describe("ollama embedding provider", () => {
|
||||
expect(otherTenant.runtime?.cacheKeyData).not.toEqual(result.runtime?.cacheKeyData);
|
||||
});
|
||||
|
||||
it("keys memory cache identity by resolved tenant SecretRefs without exposing their values", async () => {
|
||||
vi.stubEnv("OLLAMA_SELECTED_HOST_KEY", "synthetic-selected-host-key");
|
||||
vi.stubEnv("OLLAMA_SELECTED_TENANT", "synthetic-tenant-a");
|
||||
const options = {
|
||||
config: createProviderConfig(
|
||||
{
|
||||
api: "ollama",
|
||||
baseUrl: "https://selected-private-host.invalid",
|
||||
apiKey: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_HOST_KEY",
|
||||
},
|
||||
headers: {
|
||||
"X-Ollama-Tenant": {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OLLAMA_SELECTED_TENANT",
|
||||
},
|
||||
},
|
||||
models: [],
|
||||
},
|
||||
"ollama-private",
|
||||
),
|
||||
provider: "ollama-private",
|
||||
model: "qwen3-embedding:4b",
|
||||
};
|
||||
|
||||
const firstTenant = await createMemoryEmbeddingProvider(options);
|
||||
vi.stubEnv("OLLAMA_SELECTED_TENANT", "synthetic-tenant-b");
|
||||
const secondTenant = await createMemoryEmbeddingProvider(options);
|
||||
|
||||
expect(firstTenant.runtime?.cacheKeyData).not.toEqual(secondTenant.runtime?.cacheKeyData);
|
||||
expect(JSON.stringify(firstTenant.runtime?.cacheKeyData)).not.toContain("synthetic-tenant-a");
|
||||
expect(JSON.stringify(firstTenant.runtime?.cacheKeyData)).not.toContain(
|
||||
"synthetic-selected-host-key",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves configured provider aliases in the memory adapter", async () => {
|
||||
const result = await createMemoryEmbeddingProvider({
|
||||
config: createProviderConfig(
|
||||
|
||||
@@ -12,9 +12,11 @@ import {
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
coerceSecretRef,
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
} from "openclaw/plugin-sdk/secret-input";
|
||||
resolveConfiguredSecretInputString,
|
||||
} from "openclaw/plugin-sdk/secret-input-runtime";
|
||||
import {
|
||||
formatErrorMessage,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedOrigin,
|
||||
@@ -202,9 +204,12 @@ type OllamaEmbeddingResolvedKeys = {
|
||||
function resolveSourcedOllamaEmbeddingKey(params: {
|
||||
configString: string | undefined;
|
||||
declared: boolean;
|
||||
resolvedSecretRef?: boolean;
|
||||
}): OllamaEmbeddingSourceResolution {
|
||||
if (params.configString !== undefined) {
|
||||
if (!isNonSecretApiKeyMarker(params.configString)) {
|
||||
// Resolved SecretRefs are opaque credentials, even when their values happen
|
||||
// to match an ambient env marker or the synthetic local-auth placeholder.
|
||||
if (params.resolvedSecretRef || !isNonSecretApiKeyMarker(params.configString)) {
|
||||
return { apiKey: params.configString };
|
||||
}
|
||||
if (!isKnownEnvApiKeyMarker(params.configString)) {
|
||||
@@ -213,17 +218,35 @@ function resolveSourcedOllamaEmbeddingKey(params: {
|
||||
const envKey = resolveEnvApiKey("ollama")?.apiKey;
|
||||
return envKey && !isNonSecretApiKeyMarker(envKey) ? { apiKey: envKey } : "opt-out";
|
||||
}
|
||||
if (params.declared) {
|
||||
const envKey = resolveEnvApiKey("ollama")?.apiKey;
|
||||
return envKey && !isNonSecretApiKeyMarker(envKey) ? { apiKey: envKey } : "opt-out";
|
||||
}
|
||||
return "unset";
|
||||
return params.declared ? "opt-out" : "unset";
|
||||
}
|
||||
|
||||
function resolveOllamaEmbeddingResolvedKeys(
|
||||
async function resolveConfiguredOllamaEmbeddingSecret(params: {
|
||||
config: OpenClawConfig;
|
||||
value: unknown;
|
||||
path: string;
|
||||
}): Promise<string | undefined> {
|
||||
if (!coerceSecretRef(params.value, params.config.secrets?.defaults)) {
|
||||
return normalizeOptionalSecretInput(params.value);
|
||||
}
|
||||
const resolved = await resolveConfiguredSecretInputString({
|
||||
config: params.config,
|
||||
env: process.env,
|
||||
value: params.value,
|
||||
path: params.path,
|
||||
unresolvedReasonStyle: "detailed",
|
||||
});
|
||||
if (resolved.unresolvedRefReason) {
|
||||
throw new Error(resolved.unresolvedRefReason);
|
||||
}
|
||||
return normalizeOptionalSecretInput(resolved.value);
|
||||
}
|
||||
|
||||
async function resolveOllamaEmbeddingResolvedKeys(
|
||||
options: OllamaEmbeddingOptions,
|
||||
providerConfig: ReturnType<typeof resolveConfiguredProvider>,
|
||||
): OllamaEmbeddingResolvedKeys {
|
||||
providerOwnsHost: boolean,
|
||||
): Promise<OllamaEmbeddingResolvedKeys> {
|
||||
const remoteValue = options.remote?.apiKey;
|
||||
const remote = resolveSourcedOllamaEmbeddingKey({
|
||||
configString: resolveMemorySecretInputString({
|
||||
@@ -233,10 +256,18 @@ function resolveOllamaEmbeddingResolvedKeys(
|
||||
declared: hasConfiguredSecretInput(remoteValue),
|
||||
});
|
||||
const providerValue = providerConfig?.config.apiKey;
|
||||
const provider = resolveSourcedOllamaEmbeddingKey({
|
||||
configString: normalizeOptionalSecretInput(providerValue),
|
||||
declared: hasConfiguredSecretInput(providerValue),
|
||||
});
|
||||
let provider: OllamaEmbeddingSourceResolution = "unset";
|
||||
if (remote === "unset" && providerOwnsHost && providerConfig) {
|
||||
provider = resolveSourcedOllamaEmbeddingKey({
|
||||
configString: await resolveConfiguredOllamaEmbeddingSecret({
|
||||
config: options.config,
|
||||
value: providerValue,
|
||||
path: `models.providers.${providerConfig.providerId}.apiKey`,
|
||||
}),
|
||||
declared: hasConfiguredSecretInput(providerValue),
|
||||
resolvedSecretRef: Boolean(coerceSecretRef(providerValue, options.config.secrets?.defaults)),
|
||||
});
|
||||
}
|
||||
const envKey = resolveEnvApiKey("ollama")?.apiKey;
|
||||
const env = envKey && !isNonSecretApiKeyMarker(envKey) ? envKey : undefined;
|
||||
return { remote, provider, env };
|
||||
@@ -285,17 +316,12 @@ function isOllamaCloudBaseUrl(baseUrl: string): boolean {
|
||||
function selectOllamaEmbeddingApiKey(params: {
|
||||
resolved: OllamaEmbeddingResolvedKeys;
|
||||
baseUrl: string;
|
||||
baseUrlOrigin: OllamaEmbeddingBaseUrlOrigin;
|
||||
providerOwnedHost: string;
|
||||
providerOwnsHost: boolean;
|
||||
}): string | undefined {
|
||||
if (params.resolved.remote !== "unset") {
|
||||
return typeof params.resolved.remote === "object" ? params.resolved.remote.apiKey : undefined;
|
||||
}
|
||||
const reachesProviderHost =
|
||||
params.baseUrlOrigin === "provider-config" ||
|
||||
params.baseUrlOrigin === "default" ||
|
||||
areOllamaHostsEquivalent(params.baseUrl, params.providerOwnedHost);
|
||||
if (params.resolved.provider !== "unset" && reachesProviderHost) {
|
||||
if (params.resolved.provider !== "unset" && params.providerOwnsHost) {
|
||||
return typeof params.resolved.provider === "object"
|
||||
? params.resolved.provider.apiKey
|
||||
: undefined;
|
||||
@@ -306,30 +332,60 @@ function selectOllamaEmbeddingApiKey(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveOllamaEmbeddingClient(
|
||||
async function resolveOllamaEmbeddingClient(
|
||||
options: OllamaEmbeddingOptions,
|
||||
): OllamaEmbeddingClientConfig {
|
||||
): Promise<OllamaEmbeddingClientConfig> {
|
||||
const providerConfig = resolveConfiguredProvider(options);
|
||||
const { baseUrl, origin: baseUrlOrigin } = resolveOllamaEmbeddingBaseUrl({
|
||||
remoteBaseUrl: options.remote?.baseUrl,
|
||||
providerConfig,
|
||||
});
|
||||
const model = normalizeEmbeddingModel(options.model, options.provider);
|
||||
const headerOverrides = Object.assign(
|
||||
{},
|
||||
providerConfig?.config.headers,
|
||||
options.remote?.headers,
|
||||
const providerOwnedHost = resolveOllamaApiBase(readProviderBaseUrl(providerConfig?.config));
|
||||
// Provider keys and headers belong to this origin only; a remote override
|
||||
// must neither resolve nor inherit another host's configured credentials.
|
||||
const providerOwnsHost =
|
||||
baseUrlOrigin !== "remote-config" || areOllamaHostsEquivalent(baseUrl, providerOwnedHost);
|
||||
const remoteHeaderNames = new Set(
|
||||
Object.keys(options.remote?.headers ?? {}).map((headerName) => headerName.toLowerCase()),
|
||||
);
|
||||
const headerOverrides: Record<string, string> = {};
|
||||
if (providerOwnsHost && providerConfig?.config.headers) {
|
||||
for (const [headerName, headerValue] of Object.entries(providerConfig.config.headers)) {
|
||||
if (remoteHeaderNames.has(headerName.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
const resolvedValue = await resolveConfiguredOllamaEmbeddingSecret({
|
||||
config: options.config,
|
||||
value: headerValue,
|
||||
path: `models.providers.${providerConfig.providerId}.headers.${headerName}`,
|
||||
});
|
||||
if (resolvedValue) {
|
||||
headerOverrides[headerName] = resolvedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(headerOverrides, options.remote?.headers);
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
...headerOverrides,
|
||||
};
|
||||
const apiKey = selectOllamaEmbeddingApiKey({
|
||||
resolved: resolveOllamaEmbeddingResolvedKeys(options, providerConfig),
|
||||
baseUrl,
|
||||
baseUrlOrigin,
|
||||
providerOwnedHost: resolveOllamaApiBase(readProviderBaseUrl(providerConfig?.config)),
|
||||
});
|
||||
// Explicit HTTP auth owns its request; resolving a competing bearer can leak
|
||||
// another tenant's key or fail on a SecretRef that is already inactive.
|
||||
const hasAuthorizationHeader = Object.entries(headers).some(
|
||||
([name, value]) => name.toLowerCase() === "authorization" && value.trim().length > 0,
|
||||
);
|
||||
const apiKey = hasAuthorizationHeader
|
||||
? undefined
|
||||
: selectOllamaEmbeddingApiKey({
|
||||
resolved: await resolveOllamaEmbeddingResolvedKeys(
|
||||
options,
|
||||
providerConfig,
|
||||
providerOwnsHost,
|
||||
),
|
||||
baseUrl,
|
||||
providerOwnsHost,
|
||||
});
|
||||
if (apiKey) {
|
||||
headers.Authorization = `Bearer ${apiKey}`;
|
||||
}
|
||||
@@ -356,7 +412,7 @@ function resolveOllamaEmbeddingClient(
|
||||
export async function createOllamaEmbeddingProvider(
|
||||
options: OllamaEmbeddingOptions,
|
||||
): Promise<{ provider: OllamaEmbeddingProvider; client: OllamaEmbeddingClient }> {
|
||||
const client = resolveOllamaEmbeddingClient(options);
|
||||
const client = await resolveOllamaEmbeddingClient(options);
|
||||
const embedUrl = `${client.baseUrl.replace(/\/$/, "")}/api/embed`;
|
||||
|
||||
const embedMany = async (input: string | string[], signal?: AbortSignal): Promise<number[][]> => {
|
||||
|
||||
Reference in New Issue
Block a user