Merge branch 'main' into fix/issue-2954-image-maxbytes-config

This commit is contained in:
MD. SHAMSUL ALAM 2026-01-28 03:50:36 +06:00 committed by GitHub
commit f212d1bc8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 9 deletions

View File

@ -69,6 +69,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.

View File

@ -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,110 @@ 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" },
{ ...makeModel("beta-model"), provider: "beta" },
{
...makeModel("alpha-model"),
provider: "alpha",
baseUrl: "http://alpha.local",
api: undefined,
},
{
...makeModel("beta-model"),
provider: "beta",
baseUrl: "http://beta.local",
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: {
baseUrl: "http://localhost:8000",
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: {
baseUrl: "http://localhost:8000",
api: "openai-responses",
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",
});
});
});
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");
});
});

View File

@ -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<string, { models?: ModelDefinitionConfig[] }>,
providers: Record<string, InlineProviderConfig>,
): 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 },