1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5import { describe, test, expect } from "bun:test";
6import { resolveModel } from "../src/agent/model-resolver.js";
7import type { RumiloConfig } from "../src/config/schema.js";
8import { ConfigError } from "../src/util/errors.js";
9
10// Minimal config stub for tests
11const stubConfig: RumiloConfig = {
12 defaults: { model: "test:m", cleanup: true },
13 web: { model: "test:m" },
14 repo: { model: "test:m", default_depth: 1, blob_limit: "5m" },
15 custom_models: {},
16};
17
18describe("resolveModel - colon handling (issue #5)", () => {
19 test("model string with multiple colons preserves segments after second colon", () => {
20 // e.g. "openrouter:google/gemini-2.5-pro:free" should parse as
21 // provider = "openrouter", modelName = "google/gemini-2.5-pro:free"
22 // This will throw from getModel (unknown provider) but the parsed modelName
23 // should contain the full string after the first colon.
24 // We test via custom: prefix where we can control resolution.
25 const config: RumiloConfig = {
26 ...stubConfig,
27 custom_models: {
28 "name:with:colons": {
29 id: "test-id",
30 name: "test",
31 api: "openai",
32 provider: "test",
33 base_url: "http://localhost",
34 reasoning: false,
35 input: ["text"],
36 cost: { input: 0, output: 0 },
37 context_window: 1000,
38 max_tokens: 500,
39 },
40 },
41 };
42 // "custom:name:with:colons" should split as provider="custom", modelName="name:with:colons"
43 const model = resolveModel("custom:name:with:colons", config);
44 expect(model.id).toBe("test-id");
45 });
46
47 test("simple provider:model still works", () => {
48 // This will call getModel which may throw for unknown providers,
49 // but at minimum the split should be correct. Test with custom.
50 const config: RumiloConfig = {
51 ...stubConfig,
52 custom_models: {
53 "simple": {
54 id: "simple-id",
55 name: "simple",
56 api: "openai",
57 provider: "test",
58 base_url: "http://localhost",
59 reasoning: false,
60 input: ["text"],
61 cost: { input: 0, output: 0 },
62 context_window: 1000,
63 max_tokens: 500,
64 },
65 },
66 };
67 const model = resolveModel("custom:simple", config);
68 expect(model.id).toBe("simple-id");
69 });
70
71 test("rejects model string without colon", () => {
72 expect(() => resolveModel("nocodelimiter", stubConfig)).toThrow(ConfigError);
73 });
74});