From 6bf2f0eee6cc149d0998768e4395c5a51dce8d98 Mon Sep 17 00:00:00 2001 From: lploc94 Date: Tue, 27 Jan 2026 18:49:58 +0700 Subject: [PATCH 1/4] fix(models): inherit baseUrl and api from provider config When using custom providers with inline model definitions, the baseUrl and api properties from the provider config were not being passed to the individual models. This caused requests to be sent to the wrong endpoint or with the wrong API format. Changes: - buildInlineProviderModels now copies baseUrl from provider to models - buildInlineProviderModels now inherits api from provider if not set on model - resolveModel fallback path now includes baseUrl from provider config Co-Authored-By: Claude (claude-opus-4.5) --- src/agents/pi-embedded-runner/model.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 1d7201ea9..1792e6706 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -8,15 +8,25 @@ import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { normalizeModelCompat } from "../model-compat.js"; import { normalizeProviderId } from "../model-selection.js"; -type InlineModelEntry = ModelDefinitionConfig & { provider: string }; +type InlineModelEntry = ModelDefinitionConfig & { provider: string; baseUrl?: string }; +type InlineProviderConfig = { + baseUrl?: string; + api?: ModelDefinitionConfig["api"]; + models?: ModelDefinitionConfig[]; +}; export function buildInlineProviderModels( - providers: Record, + providers: Record, ): InlineModelEntry[] { return Object.entries(providers).flatMap(([providerId, entry]) => { const trimmed = providerId.trim(); if (!trimmed) return []; - return (entry?.models ?? []).map((model) => ({ ...model, provider: trimmed })); + return (entry?.models ?? []).map((model) => ({ + ...model, + provider: trimmed, + baseUrl: entry?.baseUrl, + api: model.api ?? entry?.api, + })); }); } @@ -72,6 +82,7 @@ export function resolveModel( name: modelId, api: providerCfg?.api ?? "openai-responses", provider, + baseUrl: providerCfg?.baseUrl, reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, From 4656dcef05d1edfb3a4b7ac9d37f130014e055bd Mon Sep 17 00:00:00 2001 From: lploc94 Date: Tue, 27 Jan 2026 19:08:35 +0700 Subject: [PATCH 2/4] test(models): add tests for baseUrl and api inheritance Add test cases to verify: - baseUrl is inherited from provider when model does not specify it - api is inherited from provider when model does not specify it - model-level api takes precedence over provider-level api - both baseUrl and api can be inherited together Co-Authored-By: Claude (claude-opus-4.5) --- src/agents/pi-embedded-runner/model.test.ts | 66 ++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index b59735623..e7416e3da 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -22,8 +22,70 @@ describe("buildInlineProviderModels", () => { const result = buildInlineProviderModels(providers); expect(result).toEqual([ - { ...makeModel("alpha-model"), provider: "alpha" }, - { ...makeModel("beta-model"), provider: "beta" }, + { ...makeModel("alpha-model"), provider: "alpha", baseUrl: undefined, api: undefined }, + { ...makeModel("beta-model"), provider: "beta", baseUrl: undefined, api: undefined }, ]); }); + + it("inherits baseUrl from provider when model does not specify it", () => { + const providers = { + custom: { + baseUrl: "http://localhost:8000", + models: [makeModel("custom-model")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].baseUrl).toBe("http://localhost:8000"); + }); + + it("inherits api from provider when model does not specify it", () => { + const providers = { + custom: { + api: "anthropic-messages", + models: [makeModel("custom-model")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].api).toBe("anthropic-messages"); + }); + + it("model-level api takes precedence over provider-level api", () => { + const providers = { + custom: { + api: "openai-chat", + models: [{ ...makeModel("custom-model"), api: "anthropic-messages" as const }], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0].api).toBe("anthropic-messages"); + }); + + it("inherits both baseUrl and api from provider config", () => { + const providers = { + custom: { + baseUrl: "http://localhost:10000", + api: "anthropic-messages", + models: [makeModel("claude-opus-4.5")], + }, + }; + + const result = buildInlineProviderModels(providers); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + provider: "custom", + baseUrl: "http://localhost:10000", + api: "anthropic-messages", + name: "claude-opus-4.5", + }); + }); }); From 4768b59c27c3603b8f7e6d81a37d69299cc52b9d Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 27 Jan 2026 16:34:27 -0500 Subject: [PATCH 3/4] fix: local updates for PR #2740 Co-authored-by: lploc94 --- src/agents/pi-embedded-runner/model.test.ts | 53 ++++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index e7416e3da..cdcb5fe8e 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -1,6 +1,12 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; -import { buildInlineProviderModels } from "./model.js"; +vi.mock("@mariozechner/pi-coding-agent", () => ({ + discoverAuthStorage: vi.fn(() => ({ mocked: true })), + discoverModels: vi.fn(() => ({ find: vi.fn(() => null) })), +})); + +import type { MoltbotConfig } from "../../config/config.js"; +import { buildInlineProviderModels, resolveModel } from "./model.js"; const makeModel = (id: string) => ({ id, @@ -15,15 +21,25 @@ const makeModel = (id: string) => ({ describe("buildInlineProviderModels", () => { it("attaches provider ids to inline models", () => { const providers = { - " alpha ": { models: [makeModel("alpha-model")] }, - beta: { models: [makeModel("beta-model")] }, + " alpha ": { baseUrl: "http://alpha.local", models: [makeModel("alpha-model")] }, + beta: { baseUrl: "http://beta.local", models: [makeModel("beta-model")] }, }; const result = buildInlineProviderModels(providers); expect(result).toEqual([ - { ...makeModel("alpha-model"), provider: "alpha", baseUrl: undefined, api: undefined }, - { ...makeModel("beta-model"), provider: "beta", baseUrl: undefined, api: undefined }, + { + ...makeModel("alpha-model"), + provider: "alpha", + baseUrl: "http://alpha.local", + api: undefined, + }, + { + ...makeModel("beta-model"), + provider: "beta", + baseUrl: "http://beta.local", + api: undefined, + }, ]); }); @@ -44,6 +60,7 @@ describe("buildInlineProviderModels", () => { it("inherits api from provider when model does not specify it", () => { const providers = { custom: { + baseUrl: "http://localhost:8000", api: "anthropic-messages", models: [makeModel("custom-model")], }, @@ -58,7 +75,8 @@ describe("buildInlineProviderModels", () => { it("model-level api takes precedence over provider-level api", () => { const providers = { custom: { - api: "openai-chat", + baseUrl: "http://localhost:8000", + api: "openai-responses", models: [{ ...makeModel("custom-model"), api: "anthropic-messages" as const }], }, }; @@ -89,3 +107,24 @@ describe("buildInlineProviderModels", () => { }); }); }); + +describe("resolveModel", () => { + it("includes provider baseUrl in fallback model", () => { + const cfg = { + models: { + providers: { + custom: { + baseUrl: "http://localhost:9000", + models: [], + }, + }, + }, + } as MoltbotConfig; + + const result = resolveModel("custom", "missing-model", "/tmp/agent", cfg); + + expect(result.model?.baseUrl).toBe("http://localhost:9000"); + expect(result.model?.provider).toBe("custom"); + expect(result.model?.id).toBe("missing-model"); + }); +}); From 9b16a6be3d53a30066d57a5fcc4120bb083e7af2 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 27 Jan 2026 16:44:15 -0500 Subject: [PATCH 4/4] fix: inherit provider baseUrl/api for inline models (#2740) (thanks @lploc94) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f15cc3b0..ac94054e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ Status: unreleased. - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). ### Fixes +- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94. - Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355. - macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee. - Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.