From 51d0a5436defec023b8851b85020da437dd4905d Mon Sep 17 00:00:00 2001 From: NeilJo-GY <43027886+NeilJo-GY@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:31:37 +0800 Subject: [PATCH] feat(composer): list Host official shelf when the hop is official MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official path was showing only the listing SKU because Host model-status collapses supported_models to the authorized id. Chat now loads the same geo-filtered OpenRouter Catalog as Settings Official ยท OpenRouter, with published rates, and leaves BYO on the machine self-report. Co-authored-by: Cursor --- packages/agent-chat/src/gateway.ts | 6 ++ .../src/ranch-shell/RanchChatShell.tsx | 94 ++++++++++++++++--- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/packages/agent-chat/src/gateway.ts b/packages/agent-chat/src/gateway.ts index affb594..03a2107 100644 --- a/packages/agent-chat/src/gateway.ts +++ b/packages/agent-chat/src/gateway.ts @@ -463,6 +463,9 @@ export type GatewayClient = { inference_path?: "byo" | "official" | string | null; host_inference_ready?: boolean; official_models?: string[]; + official_key_geo?: string; + official_region?: string; + official_default_model_id?: string | null; providers?: Array<{ id: string; kind: "byo" | "official" | string; @@ -752,6 +755,9 @@ export function createGatewayClient( kind: "byo" | "official" | string; brand?: string; }>; + official_key_geo?: string; + official_region?: string; + official_default_model_id?: string | null; mismatched: boolean; markup_percent?: number | null; supported_models?: string[]; diff --git a/packages/agent-chat/src/ranch-shell/RanchChatShell.tsx b/packages/agent-chat/src/ranch-shell/RanchChatShell.tsx index 91ede5c..f27c92e 100644 --- a/packages/agent-chat/src/ranch-shell/RanchChatShell.tsx +++ b/packages/agent-chat/src/ranch-shell/RanchChatShell.tsx @@ -12,6 +12,7 @@ import { import { ChatGatewayError, createGatewayClient, + officialCatalogRates, syncCatalogRates, type GatewayClient, type MyAgentSummary, @@ -46,7 +47,7 @@ import { NewChatPicker } from "./NewChatPicker"; import { NewComposeMenu } from "./NewComposeMenu"; import { ConnectAgentModal } from "./ConnectAgentModal"; import { copyConnectPromptWithInvite } from "./connectPrompt"; -import { officialV0SupportsModel } from "./officialV0"; +import { officialShelfAllows, officialV0SupportsModel } from "./officialV0"; import { RANCH_LOCALE_OPTIONS, ranchMessages, @@ -523,6 +524,7 @@ function modelFitsComposerPath( if (isByoVendor(vendor, args.byoVendor)) return false; return ( args.officialIds.some((item) => sameModelId(item, mid)) || + args.catalogIds.some((item) => sameModelId(item, mid)) || args.fallback.some((item) => sameModelId(item, mid)) || sameModelId(mid, args.listed) ); @@ -564,7 +566,8 @@ function pickComposerModelForPath(args: { return ( args.fallback.find((item) => sameModelId(item, args.wanted)) || args.officialIds.find((item) => sameModelId(item, args.wanted)) || - (sameModelId(args.wanted, args.listed) ? args.listed : null) + args.catalogIds.find((item) => sameModelId(item, args.wanted)) || + (sameModelId(args.wanted, args.listed) ? args.listed : args.wanted) ); } if (args.runtime && modelFitsComposerPath(args.runtime, path)) { @@ -1917,6 +1920,7 @@ export function RanchChatShell(props: RanchChatShellProps) { mismatched: boolean; official_channel: boolean; official_models: string[]; + official_key_geo?: string; markup_percent?: number | null; supported_models: string[]; model_options: Array<{ @@ -1939,6 +1943,8 @@ export function RanchChatShell(props: RanchChatShellProps) { ); const [composerCatalogLoading, setComposerCatalogLoading] = useState(false); const composerMenuRef = useRef(null); + const composerCatalogRef = useRef(composerCatalog); + composerCatalogRef.current = composerCatalog; /** Last Settings listing seen in this chat; change drops the composer pin. */ const composerListedRef = useRef<{ chatId: string; listed: string } | null>(null); const [ownedAgentLoading, setOwnedAgentLoading] = useState(false); @@ -3107,6 +3113,8 @@ export function RanchChatShell(props: RanchChatShellProps) { mismatched: !!row.mismatched, official_channel: listedOfficial, official_models: officialIds, + official_key_geo: + typeof row.official_key_geo === "string" ? row.official_key_geo : "", markup_percent: typeof row.markup_percent === "number" ? row.markup_percent : null, supported_models: supported, @@ -3125,6 +3133,9 @@ export function RanchChatShell(props: RanchChatShellProps) { composerListedRef.current = listed ? { chatId: active.chat_id, listed } : null; + const catalogIds = listedOfficial + ? composerCatalogRef.current.map((row) => row.id) + : []; const nextModel = pickComposerModelForPath({ official: listedOfficial, openRouterByo: isOpenRouterByoListed(listed, runtime, supported), @@ -3133,10 +3144,20 @@ export function RanchChatShell(props: RanchChatShellProps) { runtime, fallback, officialIds, - catalogIds: [], + catalogIds, }); - setSelectedModelId(nextModel); - writeComposerModelPick(active.chat_id, nextModel); + const catalogPending = listedOfficial && catalogIds.length === 0; + if ( + catalogPending && + stored && + !listedChanged && + officialV0SupportsModel(stored) + ) { + setSelectedModelId(stored); + } else { + setSelectedModelId(nextModel); + writeComposerModelPick(active.chat_id, nextModel); + } }) .catch(() => { if (!cancelled) { @@ -3169,12 +3190,19 @@ export function RanchChatShell(props: RanchChatShellProps) { }, [composerMenuOpen]); useEffect(() => { + const official = Boolean(composerModel?.official_channel); const openRouterByo = isOpenRouterByoListed( composerModel?.listed_model_id, composerModel?.runtime_model_id, composerModel?.supported_models, ); - if (composerModel?.official_channel || !openRouterByo) { + if (!official && !openRouterByo) { + setComposerCatalog([]); + setComposerCatalogLoading(false); + return; + } + const keyGeo = (composerModel?.official_key_geo || "").trim(); + if (official && !keyGeo) { setComposerCatalog([]); setComposerCatalogLoading(false); return; @@ -3198,10 +3226,12 @@ export function RanchChatShell(props: RanchChatShellProps) { for (const row of data.items) { const src = (row.source || "openrouter").toLowerCase(); if (src && src !== "openrouter") continue; - const quote = syncCatalogRates(row); - if (!quote) continue; const id = (row.model_id || "").trim(); if (!id) continue; + if (official && !officialShelfAllows(id, keyGeo)) continue; + const quote = official ? officialCatalogRates(row) : syncCatalogRates(row); + if (!quote) continue; + if (quote.input < 0 || quote.output < 0) continue; if (acc.some((item) => sameModelId(item.id, id))) continue; acc.push({ id, in: quote.input, out: quote.output }); } @@ -3221,6 +3251,7 @@ export function RanchChatShell(props: RanchChatShellProps) { }, [ client, composerModel?.official_channel, + composerModel?.official_key_geo, composerModel?.listed_model_id, composerModel?.runtime_model_id, ]); @@ -3243,6 +3274,34 @@ export function RanchChatShell(props: RanchChatShellProps) { selectedModelId, ]); + useEffect(() => { + if (!composerModel?.official_channel || !active?.chat_id) return; + if (composerCatalogLoading || composerCatalog.length === 0) return; + const stored = readComposerModelPick(active.chat_id); + const next = pickComposerModelForPath({ + official: true, + openRouterByo: false, + wanted: stored, + listed: composerModel.listed_model_id ?? null, + runtime: composerModel.runtime_model_id ?? null, + fallback: composerModel.supported_models, + officialIds: composerModel.official_models, + catalogIds: composerCatalog.map((row) => row.id), + }); + if (!next || sameModelId(next, selectedModelId)) return; + setSelectedModelId(next); + writeComposerModelPick(active.chat_id, next); + }, [ + active?.chat_id, + composerCatalog, + composerCatalogLoading, + composerModel?.listed_model_id, + composerModel?.official_channel, + (composerModel?.official_models ?? []).join("\u0001"), + composerModel?.runtime_model_id, + (composerModel?.supported_models ?? []).join("\u0001"), + ]); + useEffect(() => { if (!composerMenuOpen) return; const onDoc = (e: MouseEvent) => { @@ -4795,11 +4854,12 @@ export function RanchChatShell(props: RanchChatShellProps) { const fromApi = composerModel.supported_models.length ? composerModel.supported_models : [listed].filter(Boolean); + const fromShelf = composerCatalog.map((row) => row.id); const seen = new Set(); const out: string[] = []; - // Official shelf stays in Settings. Chat lists machine self-report - // (plus the listing id only when it is a BYO/Store SKU). - const source = official ? fromApi : [listed, ...fromApi]; + const source = official + ? [listed, ...fromShelf] + : [listed, ...fromApi]; for (const id of source) { if (!id || !id.trim()) continue; const k = id.toLowerCase(); @@ -4817,6 +4877,17 @@ export function RanchChatShell(props: RanchChatShellProps) { options[0] || ""; const priceFor = (id: string) => { + const cat = composerCatalog.find((row) => sameModelId(row.id, id)); + if (official && cat) { + return t.composerModelPrice( + formatUsdPerMillion( + applyMarkupUsd(cat.in, composerModel.markup_percent), + ), + formatUsdPerMillion( + applyMarkupUsd(cat.out, composerModel.markup_percent), + ), + ); + } const priced = composerModel.model_options.find((o) => sameModelId(o.model_id, id), ); @@ -4835,7 +4906,6 @@ export function RanchChatShell(props: RanchChatShellProps) { return `${formatUsdPerMillion(inn)} in`; } } - const cat = composerCatalog.find((row) => sameModelId(row.id, id)); if (cat) { return t.composerModelPrice( formatUsdPerMillion(