From 68591cd70e8e3e5c76614f4a26779fa66c6ecefe Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Tue, 28 Jul 2026 16:56:12 +0100 Subject: [PATCH] fix(models): include Kimi in preview picker Generated-By: PostHog Code Task-Id: dd739bea-9df5-4e0b-a5e2-42f6b8f8f207 --- .../src/services/agent/agent.test.ts | 44 ++++++++++++++++++- .../src/services/agent/agent.ts | 10 +++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/packages/workspace-server/src/services/agent/agent.test.ts b/packages/workspace-server/src/services/agent/agent.test.ts index 1109fbf868..12e7c3f7ef 100644 --- a/packages/workspace-server/src/services/agent/agent.test.ts +++ b/packages/workspace-server/src/services/agent/agent.test.ts @@ -125,9 +125,15 @@ vi.mock("@posthog/agent/gateway-models", () => ({ DEFAULT_GATEWAY_MODEL: "claude-opus-4-8", DEFAULT_CODEX_MODEL: "gpt-5.5", fetchGatewayModels: vi.fn().mockResolvedValue([]), - formatGatewayModelName: vi.fn(), + formatGatewayModelName: vi.fn((model) => model.id), + getClaudeModelRecency: vi.fn(() => 0), getProviderName: vi.fn(), + isAnthropicModel: vi.fn((model) => model.owned_by === "anthropic"), isBlockedModelId: vi.fn().mockReturnValue(false), + isCloudflareModel: vi.fn((model) => model.owned_by === "cloudflare"), + isModalModel: vi.fn((model) => model.owned_by === "modal"), + isOpenAIModel: vi.fn((model) => model.owned_by === "openai"), + pickAllowedModel: vi.fn((_models, preferredModelId) => preferredModelId), })); vi.mock("@posthog/agent/adapters/claude/session/jsonl-hydration", () => ({ @@ -151,6 +157,7 @@ vi.mock("node:fs", async (importOriginal) => { }); // --- Import after mocks --- +import { fetchGatewayModels } from "@posthog/agent/gateway-models"; import type { RegisteredFolder } from "../folders/schemas"; import { AgentService, @@ -183,6 +190,7 @@ function createMockDependencies() { }, agentAuthAdapter: { getCurrentCredentials: vi.fn().mockResolvedValue(null), + gatewayAuthToken: vi.fn().mockResolvedValue("gateway-token"), ensureGatewayProxy: vi.fn().mockResolvedValue("http://127.0.0.1:9999"), configureProcessEnv: vi.fn().mockResolvedValue(undefined), createPosthogConfig: vi.fn((credentials) => ({ @@ -296,6 +304,40 @@ describe("AgentService", () => { vi.unstubAllGlobals(); }); + it("includes Modal models in Claude preview options", async () => { + vi.mocked(fetchGatewayModels).mockResolvedValueOnce([ + { + id: "claude-opus-4-8", + owned_by: "anthropic", + context_window: 1_000_000, + supports_streaming: true, + supports_vision: true, + allowed: true, + }, + { + id: "moonshotai/kimi-k3", + owned_by: "modal", + context_window: 262_144, + supports_streaming: true, + supports_vision: false, + allowed: true, + }, + ]); + + const options = await service.getPreviewConfigOptions( + "https://us.posthog.com", + "claude", + ); + + const modelOption = options.find((option) => option.id === "model"); + expect(modelOption).toMatchObject({ + type: "select", + options: expect.arrayContaining([ + expect.objectContaining({ value: "moonshotai/kimi-k3" }), + ]), + }); + }); + describe("mcp-apps config resolver", () => { function registeredResolver(): (serverName: string) => Promise { const call = deps.mcpAppsService.setConfigResolver.mock.calls[0]; diff --git a/packages/workspace-server/src/services/agent/agent.ts b/packages/workspace-server/src/services/agent/agent.ts index 9654cb30b7..f23d47859a 100644 --- a/packages/workspace-server/src/services/agent/agent.ts +++ b/packages/workspace-server/src/services/agent/agent.ts @@ -36,6 +36,7 @@ import { getProviderName, isAnthropicModel, isCloudflareModel, + isModalModel, isOpenAIModel, pickAllowedModel, } from "@posthog/agent/gateway-models"; @@ -2402,14 +2403,15 @@ For git operations while detached: authToken: (await this.agentAuthAdapter.gatewayAuthToken()) ?? undefined, }); - // The Claude adapter can also drive Cloudflare `@cf/` models the gateway serves over its - // Anthropic-Messages surface, so the preview/default-model path must offer them too — otherwise an - // advertised `@cf/*` model is dropped here and the pre-session run falls back to Opus. + // The Claude adapter can drive non-Anthropic models that the gateway exposes through its + // Anthropic-Messages surface, so preview filtering must match the session adapter. const modelFilter = adapter === "codex" ? isOpenAIModel : (model: GatewayModel) => - isAnthropicModel(model) || isCloudflareModel(model); + isAnthropicModel(model) || + isCloudflareModel(model) || + isModalModel(model); const adapterModels = gatewayModels.filter((model) => modelFilter(model)); const modelOptions = adapterModels.map((model) => ({