Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .papercuts/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,14 @@ symlink with this checkout's own npm ci. Full type-check and lint then passed.
- E2E chat-title expectations assume the deterministic chat-model route. On a Mac where the native Foundation Models helper reports `ready`, automatic titles come from Apple Intelligence instead, so `chat-message-queue` sidebar-title lookups fail locally while passing in CI; probe the helper or move it aside before treating those failures as regressions.
- `git add` on the tracked-but-ignored `.papercuts/troubleshooting.md` still needs `-f` after conflict resolution.

# Custom model options

- Fresh worktree has no `.memory/` or dependencies; inspected existing implementation and installed dependencies before validation.
- Video input is not supported by Aiden's chat attachment transport. The capability option must describe server support without advertising video uploads.
- Review found capability consumers outside the desktop/native picker (Bot inventory and Telegram) and assistant artifact images in raw history; added projection and role-aware image-limit regressions.
- Frozen runtime contribution snapshots require a copied tool policy; added a real harness test covering base and extension tools.
- Hosted verify hit a pre-existing Git cancellation fixture race: a short marker poll expired while push was still running, then cleanup removed its wrapper. Replaced delay/count coordination with a bounded marker handshake and awaited cancellation cleanup.

## 2026-09-12 — MCP maintenance implementation

- MCP SDK1.30.0 closes HTTP transports during OAuth redirection but expects finishAuth to reuse the same object and discovered metadata. Restart only its exchange request lifetime, retaining owner cancellation.
Expand Down
10 changes: 10 additions & 0 deletions android/app/src/test/java/sbtbiswas/AidenOnTheGo/AidenChatTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import java.util.UUID
class AidenChatTest {
private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }

@Test
fun testCustomModelOverridesPreserveImageAndVisibilityFlags() {
val catalog = json.decodeFromString<AidenModelCatalog>("""
{"providers":[{"id":"custom:tailnet","label":"Private","models":[{"id":"text","label":"Text","supportsImages":false},{"id":"vision","label":"Vision","supportsImages":true,"hidden":true}]}],"defaults":{}}
""".trimIndent())
assertFalse(catalog.providers.first().models.first().acceptsImageInput)
assertTrue(catalog.providers.first().models.last().acceptsImageInput)
assertEquals(listOf("text"), catalog.visibleProviders.first().models.map { it.id })
}

@Test
fun testHiddenAndAllHiddenProviderModelsStayOutOfNewSelections() {
val wire = """
Expand Down
9 changes: 9 additions & 0 deletions ios/AidenOnTheGoTests/AidenChatTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,15 @@ final class AidenChatTests: XCTestCase {
XCTAssertEqual(catalog.visibleProviders.first?.models.map(\.id), ["gemini-flash"])
}

func testCustomModelOverridesPreserveImageAndVisibilityFlags() throws {
let catalog = try JSONDecoder().decode(AidenModelCatalog.self, from: Data(
#"{"providers":[{"id":"custom:tailnet","label":"Private","models":[{"id":"text","label":"Text","supportsImages":false},{"id":"vision","label":"Vision","supportsImages":true,"hidden":true}]}],"defaults":{}}"#.utf8
))
XCTAssertFalse(try XCTUnwrap(catalog.providers.first?.models.first).acceptsImageInput)
XCTAssertTrue(try XCTUnwrap(catalog.providers.first?.models.last).acceptsImageInput)
XCTAssertEqual(catalog.visibleProviders.first?.models.map(\.id), ["text"])
}

func testModelCatalogPreservesThinkingDefaultAndRequiredThinkingPresentation() throws {
let catalog = try JSONDecoder().decode(
AidenModelCatalog.self,
Expand Down
3 changes: 3 additions & 0 deletions main/handlers/providers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { parseCustomModelOptions } from "../../renderer/shared/custom-model-options.js";
import { isCompactionEngine } from "../../renderer/shared/compaction.js";
// Provider configuration + API key IPC handlers. Thin — logic lives in services.

Expand Down Expand Up @@ -114,6 +115,8 @@ function parseModelMetadata(value: unknown): Record<string, ProviderModelMetadat
modelId,
{
source,
overrides: parseCustomModelOptions(metadata.overrides),
manuallyAdded: metadata.manuallyAdded === true ? true : undefined,
name: typeof metadata.name === "string" && metadata.name ? metadata.name : undefined,
type: optionalModelType(metadata.type),
vision: typeof metadata.vision === "boolean" ? metadata.vision : undefined,
Expand Down
2 changes: 1 addition & 1 deletion main/services/aiden-remote-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class AidenRemoteModelService {
const model: AidenRemoteModelProjection = {
id,
label: bounded(metadata?.name ?? id, 256),
supportsImages: metadata?.vision === true,
supportsImages: (metadata?.overrides?.vision ?? metadata?.vision) === true && metadata?.overrides?.maxImages !== 0,
Comment thread
pullfrog[bot] marked this conversation as resolved.
...(isModelHidden(settings.hiddenModelsByProvider, provider.id, id)
? { hidden: true }
: {}),
Expand Down
12 changes: 9 additions & 3 deletions main/services/bot-capability-inventory-ports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ test("inventory ports project safe exact facts and conservative unavailable conn
kind: "openai",
label: "Provider",
baseUrl: "https://example.invalid/v1",
models: ["chat", "embed"],
modelMetadata: { embed: { source: "provider", type: "embedding" } },
models: ["chat", "embed", "zero", "disabled"],
modelMetadata: {
embed: { source: "provider", type: "embedding" },
chat: { source: "provider", vision: false, overrides: { vision: true } },
zero: { source: "provider", vision: true, overrides: { maxImages: 0 } },
disabled: { source: "provider", vision: true, overrides: { vision: false } },
},
needsKey: true,
hasKey: true,
},
Expand Down Expand Up @@ -77,7 +82,8 @@ test("inventory ports project safe exact facts and conservative unavailable conn
ports.inspectSkills(signal),
ports.inspectOtherCapabilities(signal),
]);
assert.equal(providers[0]?.models.length, 1);
assert.equal(providers[0]?.models.length, 3);
assert.deepEqual(providers[0]?.models.map(model => model.supportsImages), [true, false, false]);
assert.equal(files.fullMac.scopeFingerprint, HASH);
assert.equal(files.approvedLocations[0]?.label, "Documents");
assert.equal(shell.shellFingerprint, HASH);
Expand Down
2 changes: 1 addition & 1 deletion main/services/bot-capability-inventory-ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function providerInventory(
sourceId: modelId,
label: metadata?.name ?? modelId,
available,
supportsImages: metadata?.vision === true,
supportsImages: (metadata?.overrides?.vision ?? metadata?.vision) === true && metadata?.overrides?.maxImages !== 0,
modelFingerprint: botCapabilityFactsFingerprint({
providerId: provider.id,
modelId,
Expand Down
12 changes: 12 additions & 0 deletions main/services/config-store-core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2883,3 +2883,15 @@ test("global skills preference persists independently of individual skill choice
assert.equal((await h.store.getSettings()).skillsEnabled, true);
assert.deepEqual(await h.store.listSkills(), [skill]);
});

test("custom model overrides survive restart and reset through the config store", async (t) => {
const h = await harness(t);
const configured = { ...provider, modelMetadata: { "qwen3-8b": { source: "lmstudio" as const, overrides: { vision: true, maxImages: 2 } } } };
await h.store.saveProvider(configured);
const loaded = await h.store.getProvider(provider.id);
assert.deepEqual(loaded?.modelMetadata?.["qwen3-8b"].overrides, { vision: true, maxImages: 2 });
await h.store.saveProvider({ ...loaded!, modelMetadata: { "qwen3-8b": { source: "lmstudio" } } });
const reset = await h.store.getProvider(provider.id);
assert.equal(reset?.modelMetadata?.["qwen3-8b"].overrides, undefined);
assert.equal(reset?.customModelOptions, undefined);
});
8 changes: 7 additions & 1 deletion main/services/config-store-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,13 @@ export function createConfigStore(
const { intent, cache } = splitStoredProvider(provider);
const stored = await mutatePortable((config) => {
const idx = config.providers.findIndex((p) => p.id === intent.id);
if (idx >= 0) config.providers[idx] = { ...config.providers[idx], ...intent };
if (idx >= 0) {
config.providers[idx] = { ...config.providers[idx], ...intent };
// An explicit metadata save replaces user overrides, including reset.
if (provider.modelMetadata !== undefined && intent.customModelOptions === undefined) {
delete config.providers[idx].customModelOptions;
}
}
else config.providers.push(intent);
return structuredClone(config.providers.find((p) => p.id === intent.id)!);
}, isCurrent);
Expand Down
Loading