Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6b44b1b
feat(engine): POST /provider/probe one-token reachability probe with …
aarontrowbridge Oct 28, 2026
8bd9f3a
test(service): RED — model-reachability cache, rate limit, demote, ru…
aarontrowbridge Oct 28, 2026
89db4ab
feat(service): model-reachability cache + scheduler + GET/refresh/dem…
aarontrowbridge Oct 28, 2026
b6f4708
test(app): RED — picker reachability merge: visible set, toggle count…
aarontrowbridge Oct 28, 2026
f78c29a
feat(app): model picker hides dead models — 'Show unavailable (N)' wi…
aarontrowbridge Oct 28, 2026
9f07829
test(app): RED — dead-pick: demote-class vs transient turn errors, fr…
aarontrowbridge Oct 28, 2026
c092d07
feat(app): fail fast on a dead pick — demote on denied/unavailable/in…
aarontrowbridge Oct 28, 2026
d974e7e
test(app): RED — picker short list: favorites → recommended → recent …
aarontrowbridge Oct 28, 2026
491289a
feat(app): model picker short list — favorites (star, persisted) → re…
aarontrowbridge Oct 28, 2026
be98bd2
Merge remote-tracking branch 'origin/opencode/1989-dead-pick' into am…
aarontrowbridge Oct 28, 2026
fccbd34
test(app): RED — picker row meta formatter (price rounding / free / m…
aarontrowbridge Oct 28, 2026
9026bc1
feat(app): model picker row meta — $in/$out · cache per Mtok, context…
aarontrowbridge Oct 28, 2026
5e4ae2c
test(engine): RED — probe retries once at a max_output_tokens floor (…
aarontrowbridge Oct 28, 2026
ee09bcc
fix(engine): probe retries once at a max_output_tokens floor (parsed …
aarontrowbridge Oct 28, 2026
ca0328e
Merge remote-tracking branch 'origin/amico/issue-1979-model-picker' i…
Oct 28, 2026
0a58964
test(engine,app): RED — Meta max_output_tokens floor wording retries …
aarontrowbridge Oct 28, 2026
3ae2e92
fix(engine,app): probe classifier broadens the max_output_tokens floo…
aarontrowbridge Oct 28, 2026
a5ae805
test(app): RED — picker row renders the name with a full-name title a…
aarontrowbridge Oct 28, 2026
e7a7a1c
fix(app): picker rows are two lines — name owns line 1 (wraps ≤2, tit…
aarontrowbridge Oct 28, 2026
f7be6d5
fix(app): picker scroll area max-h-[min(420px,60vh)] (V2) and V1 popo…
aarontrowbridge Oct 28, 2026
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
64 changes: 50 additions & 14 deletions packages/app-bundle/manifest.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// amicode#1986 (#1979 S3): the picker's reachability data source — fetches the
// S2 snapshot through the overlay's amicode-fetch (per-active-server Basic
// auth) and exposes a Re-check. Every failure (non-amicode host, 404, network,
// ok:false) resolves to `undefined` ⇒ the pure merge passes the list through
// untouched, so the picker degrades to exactly upstream behaviour.
import { createResource, createSignal } from "solid-js"
import { ServerConnection, useServer } from "@/context/server"
import { amicodeGet, amicodePost } from "@/utils/amicode-fetch"
import {
parseReachabilityResponse,
REACHABILITY_REFRESH_ROUTE,
REACHABILITY_ROUTE,
type ReachabilitySnapshot,
} from "./dialog-select-model-reachability"

export function createModelReachability() {
const server = useServer()
const [snapshot, { refetch }] = createResource(
() => (server.current ? ServerConnection.key(server.current) : undefined),
async (): Promise<ReachabilitySnapshot | undefined> => {
try {
return parseReachabilityResponse(await amicodeGet(server.current, REACHABILITY_ROUTE))
} catch {
return undefined
}
},
)
const [showUnavailable, setShowUnavailable] = createSignal(false)
const [rechecking, setRechecking] = createSignal(false)

return {
/** undefined ⇒ passthrough (no data / route unavailable). */
snapshot: (): ReachabilitySnapshot | undefined => (snapshot.error ? undefined : snapshot.latest),
/** amicode#1989: true until the first fetch settles (a demote waits for it
* so it never overrides a fresher probe it has not seen yet). */
loading: () => snapshot.loading,
showUnavailable,
setShowUnavailable,
rechecking,
refetch: () => void refetch(),
recheck: async () => {
if (rechecking()) return
setRechecking(true)
try {
await amicodePost(server.current, REACHABILITY_REFRESH_ROUTE, {})
} catch {
// a rate-limited / failed refresh still re-reads whatever the cache holds
} finally {
setRechecking(false)
void refetch()
}
},
}
}

export type ModelReachabilityState = ReturnType<typeof createModelReachability>
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// amicode#1986 (#1979 S3): the picker's reachability merge — pure, so the
// visible-set rules are pinned without mounting the SolidJS picker.
import { describe, expect, test } from "bun:test"
import {
parseReachabilityResponse,
partitionByReachability,
reachabilityReason,
type ReachabilitySnapshot,
} from "./dialog-select-model-reachability"

type Item = { id: string; provider: { id: string } }
const item = (provider: string, id: string): Item => ({ id, provider: { id: provider } })
const keyOf = (x: Item) => ({ providerID: x.provider.id, modelID: x.id })
const ids = (xs: Item[]) => xs.map((x) => `${x.provider.id}:${x.id}`)

const row = (provider: string, model: string, status: string, reason: string | null = null) => ({
provider,
model,
status,
reason,
latency_ms: status === "unchecked" ? null : 12,
checked_at: status === "unchecked" ? null : 1_700_000_000_000,
credential_fp: "fp1",
source: status === "unchecked" ? null : "probe",
})
const prov = (provider: string, probed: boolean) => ({
provider,
credential_fp: probed ? "fp1" : null,
last_probe_at: probed ? 1_700_000_000_000 : null,
last_attempt_at: probed ? 1_700_000_000_000 : null,
last_error: null,
next_probe_at: null,
})
const response = (models: unknown[], providers: unknown[]) => ({
ok: true,
error: null,
catalog_known: true,
models,
providers,
})

const items = [
item("bedrock", "claude-ok"),
item("bedrock", "claude-denied"),
item("bedrock", "claude-unavailable"),
item("bedrock", "claude-unchecked"),
item("bedrock", "claude-invalid"),
item("bedrock", "claude-error"),
item("bedrock", "claude-ratelimited"),
item("bedrock", "claude-norow"),
]
const mixed = parseReachabilityResponse(
response(
[
row("bedrock", "claude-ok", "ok"),
row("bedrock", "claude-denied", "denied", "AccessDeniedException"),
row("bedrock", "claude-unavailable", "unavailable"),
row("bedrock", "claude-unchecked", "unchecked"),
row("bedrock", "claude-invalid", "invalid"),
row("bedrock", "claude-error", "error", "socket hang up"),
row("bedrock", "claude-ratelimited", "rate-limited"),
],
[prov("bedrock", true)],
),
) as ReachabilitySnapshot

describe("parseReachabilityResponse", () => {
test("a well-formed S2 body parses to a snapshot", () => {
expect(mixed).toBeDefined()
expect(mixed.models.length).toBe(7)
})

test("route unavailable / malformed / ok:false → undefined (passthrough)", () => {
expect(parseReachabilityResponse(undefined)).toBeUndefined()
expect(parseReachabilityResponse("nope")).toBeUndefined()
expect(parseReachabilityResponse({ ok: false, error: "cache_unavailable: x" })).toBeUndefined()
expect(parseReachabilityResponse({ ok: true, models: "x" })).toBeUndefined()
})

test("rows with an unknown status are dropped, not trusted", () => {
const snap = parseReachabilityResponse(response([row("a", "m", "ok"), row("a", "n", "great")], []))
expect(snap?.models.map((m) => m.model)).toEqual(["m"])
})
})

describe("partitionByReachability", () => {
test("ok + rate-limited show by default; dead and probed-provider unchecked hide", () => {
const p = partitionByReachability(items, keyOf, mixed)
expect(ids(p.shown)).toEqual(["bedrock:claude-ok", "bedrock:claude-ratelimited"])
expect(ids(p.hidden)).toEqual([
"bedrock:claude-denied",
"bedrock:claude-unavailable",
"bedrock:claude-unchecked",
"bedrock:claude-invalid",
"bedrock:claude-error",
"bedrock:claude-norow",
])
expect(p.hiddenCount).toBe(6)
})

test("the toggle reveals hidden models greyed, with human reasons", () => {
const p = partitionByReachability(items, keyOf, mixed)
expect(ids(p.list(false))).toEqual(ids(p.shown))
expect(ids(p.list(true))).toEqual([...ids(p.shown), ...ids(p.hidden)])
const reasons = Object.fromEntries(p.hidden.map((x) => [x.id, reachabilityReason(p.view(x))]))
expect(reasons["claude-denied"]).toEqual({ key: "dialog.model.reachability.reason.denied" })
expect(reasons["claude-unavailable"]).toEqual({ key: "dialog.model.reachability.reason.unavailable" })
expect(reasons["claude-invalid"]).toEqual({ key: "dialog.model.reachability.reason.invalid" })
expect(reasons["claude-error"]).toEqual({ text: "socket hang up" })
expect(reasons["claude-unchecked"]).toEqual({ key: "dialog.model.reachability.reason.unchecked" })
expect(reasons["claude-norow"]).toEqual({ key: "dialog.model.reachability.reason.unchecked" })
for (const x of p.hidden) expect(p.view(x).greyed).toBe(true)
})

test("error without a reason falls back to 'Probe failed'", () => {
const snap = parseReachabilityResponse(response([row("a", "m", "error")], [prov("a", true)]))
const p = partitionByReachability([item("a", "m")], keyOf, snap)
expect(reachabilityReason(p.view(p.hidden[0]))).toEqual({ key: "dialog.model.reachability.reason.error" })
})

test("rate-limited is shown with a warning; ok has none", () => {
const p = partitionByReachability(items, keyOf, mixed)
expect(p.view(item("bedrock", "claude-ratelimited")).tag).toBe("rate-limited")
expect(p.view(item("bedrock", "claude-ratelimited")).greyed).toBe(false)
expect(p.view(item("bedrock", "claude-ok")).tag).toBeUndefined()
})

test("unchecked models of a never-probed provider show, labelled unchecked", () => {
const snap = parseReachabilityResponse(
response(
[row("bedrock", "claude-ok", "ok"), row("openai", "gpt-a", "unchecked")],
[prov("bedrock", true), prov("openai", false)],
),
)
const list = [item("bedrock", "claude-ok"), item("openai", "gpt-a"), item("openai", "gpt-b")]
const p = partitionByReachability(list, keyOf, snap)
expect(ids(p.shown)).toEqual(["bedrock:claude-ok", "openai:gpt-a", "openai:gpt-b"])
expect(p.hiddenCount).toBe(0)
expect(p.view(item("openai", "gpt-a")).tag).toBe("unchecked")
expect(p.view(item("openai", "gpt-b")).tag).toBe("unchecked")
expect(p.view(item("openai", "gpt-a")).status).toBe("unchecked")
})

test("the currently selected model is retained even if dead, and marked", () => {
const p = partitionByReachability(items, keyOf, mixed, { providerID: "bedrock", modelID: "claude-denied" })
expect(ids(p.shown)).toEqual(["bedrock:claude-ok", "bedrock:claude-denied", "bedrock:claude-ratelimited"])
expect(p.hiddenCount).toBe(5)
const v = p.view(item("bedrock", "claude-denied"))
expect(v.tag).toBe("selected-dead")
expect(v.greyed).toBe(false)
expect(reachabilityReason(v)).toEqual({ key: "dialog.model.reachability.reason.denied" })
})

test("route unavailable → passthrough: everything shows exactly as today", () => {
const p = partitionByReachability(items, keyOf, undefined, { providerID: "bedrock", modelID: "claude-denied" })
expect(ids(p.shown)).toEqual(ids(items))
expect(p.hiddenCount).toBe(0)
expect(ids(p.list(true))).toEqual(ids(items))
for (const x of items) {
const v = p.view(x)
expect(v.tag).toBeUndefined()
expect(v.greyed).toBe(false)
expect(reachabilityReason(v)).toBeUndefined()
}
})
})
Loading
Loading