Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions apps/controller/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8259,6 +8259,65 @@
}
}
},
"/api/v1/model-providers/instances/test-model": {
"post": {
"tags": [
"Model Providers"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
},
"baseUrl": {
"type": "string"
},
"modelId": {
"type": "string",
"minLength": 1
},
"instanceKey": {
"type": "string",
"minLength": 1
}
},
"required": [
"modelId",
"instanceKey"
]
}
}
}
},
"responses": {
"200": {
"description": "Test a model against a provider instance",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"error": {
"type": "string"
}
},
"required": [
"ok"
]
}
}
}
}
}
}
},
"/api/v1/model-providers/{providerId}/validate": {
"post": {
"tags": [
Expand Down Expand Up @@ -8322,6 +8381,70 @@
}
}
},
"/api/v1/model-providers/{providerId}/test-model": {
"post": {
"tags": [
"Model Providers"
],
"parameters": [
{
"schema": {
"type": "string"
},
"required": true,
"name": "providerId",
"in": "path"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
},
"baseUrl": {
"type": "string"
},
"modelId": {
"type": "string",
"minLength": 1
}
},
"required": [
"modelId"
]
}
}
}
},
"responses": {
"200": {
"description": "Test a model against provider credentials",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"ok": {
"type": "boolean"
},
"error": {
"type": "string"
}
},
"required": [
"ok"
]
}
}
}
}
}
}
},
"/api/v1/model-providers/minimax/oauth/status": {
"get": {
"tags": [
Expand Down
21 changes: 21 additions & 0 deletions apps/controller/src/lib/model-provider-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ function resolveDefaultBaseUrls(
return getDefaultProviderBaseUrls(providerId);
}

function normalizeGoogleNativeBaseUrl(baseUrl: string): string {
return (
normalizeProviderBaseUrl(baseUrl)?.replace(/\/(v1|v1beta|v1alpha)$/i, "") ??
baseUrl
);
}

function isProviderProxied(input: {
providerId: string;
baseUrl: string;
Expand All @@ -111,6 +118,20 @@ function isProviderProxied(input: {
.filter((value): value is string => value !== null),
);

if (input.providerId === "google") {
const normalizedGoogleBaseUrl = normalizeGoogleNativeBaseUrl(input.baseUrl);
const normalizedGoogleDefaultBaseUrls = new Set(
[...normalizedDefaultBaseUrls].map((value) =>
normalizeGoogleNativeBaseUrl(value),
),
);

return (
normalizedGoogleDefaultBaseUrls.size > 0 &&
!normalizedGoogleDefaultBaseUrls.has(normalizedGoogleBaseUrl)
);
}

return (
normalizedDefaultBaseUrls.size > 0 &&
!normalizedDefaultBaseUrls.has(normalizedBaseUrl)
Expand Down
11 changes: 9 additions & 2 deletions apps/controller/src/lib/openclaw-config-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,10 @@ function compileModelsConfig(
// with "Unknown model: link/...").
const hasUsableApiKey =
apiKey !== null && !(typeof apiKey === "string" && apiKey.length === 0);
providers[descriptor.runtimeKey] = {
const providerConfig: NonNullable<
OpenClawConfig["models"]
>["providers"][string] = {
baseUrl: descriptor.provider.baseUrl,
...(hasUsableApiKey ? { apiKey } : {}),
api: descriptor.apiKind,
...(descriptor.authHeader ? { authHeader: true } : {}),
...(descriptor.defaultHeaders
Expand All @@ -141,6 +142,12 @@ function compileModelsConfig(
),
),
};

if (hasUsableApiKey && apiKey !== undefined) {
providerConfig.apiKey = apiKey;
}

providers[descriptor.runtimeKey] = providerConfig;
}

const desktopCloud = isDesktopCloudConfig(config.desktop.cloud)
Expand Down
72 changes: 72 additions & 0 deletions apps/controller/src/routes/model-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
quotaFallbackResponseSchema,
restoreManagedBodySchema,
supportedByokProviderIds,
testProviderInstanceModelBodySchema,
testProviderModelBodySchema,
testProviderModelResponseSchema,
validateProviderInstanceBodySchema,
verifyProviderBodySchema,
verifyProviderResponseSchema,
Expand Down Expand Up @@ -178,6 +181,41 @@ export function registerModelRoutes(
},
);

app.openapi(
createRoute({
method: "post",
path: "/api/v1/model-providers/instances/test-model",
tags: ["Model Providers"],
request: {
body: {
content: {
"application/json": {
schema: testProviderInstanceModelBodySchema,
},
},
},
},
responses: {
200: {
content: {
"application/json": { schema: testProviderModelResponseSchema },
},
description: "Test a model against a provider instance",
},
},
}),
async (c) => {
const { instanceKey, ...input } = c.req.valid("json");
return c.json(
await container.modelProviderService.testProviderInstanceModel(
instanceKey,
input,
),
200,
);
},
);

app.openapi(
createRoute({
method: "post",
Expand Down Expand Up @@ -210,6 +248,40 @@ export function registerModelRoutes(
},
);

app.openapi(
createRoute({
method: "post",
path: "/api/v1/model-providers/{providerId}/test-model",
tags: ["Model Providers"],
request: {
params: verifyProviderIdParamSchema,
body: {
content: {
"application/json": { schema: testProviderModelBodySchema },
},
},
},
responses: {
200: {
content: {
"application/json": { schema: testProviderModelResponseSchema },
},
description: "Test a model against provider credentials",
},
},
}),
async (c) => {
const { providerId } = c.req.valid("param");
return c.json(
await container.modelProviderService.testProviderModel(
providerId,
c.req.valid("json"),
),
200,
);
},
);

app.openapi(
createRoute({
method: "get",
Expand Down
Loading
Loading