Stamp AI models with their provider for correct grouping (#1320)

Back-port the core of v5 fix 1de1392c9 to v6 (minimal, not the full
dedup refactor). The model list API now sets ModelInfo.Provider (stamped
in prefixProviderModels and passed through HandleListModels), and the
frontend groupModelsByProvider prefers the server-supplied provider over
splitting the model id, so models with opaque ids (e.g. Ollama-hosted
llama3-8b) group under the correct provider. Adds a grouping regression
test.
This commit is contained in:
rcourtman
2026-06-04 10:47:40 +01:00
parent 2db82fc302
commit 1713662379
6 changed files with 23 additions and 1 deletions
+3
View File
@@ -29,6 +29,9 @@ export interface ModelInfo {
description?: string;
is_default?: boolean;
notable?: boolean;
// Authoritative provider for this model, supplied by the server. Preferred
// over deriving the provider from the (possibly opaque) model id (#1320).
provider?: string;
}
export interface AISettings {
@@ -247,5 +247,17 @@ describe('patrolFormat', () => {
const groups = groupModelsByProvider(models);
expect(groups.get('no-provider')).toHaveLength(1);
});
it('prefers the server-supplied provider over the id prefix (#1320)', () => {
const models = [
// Opaque id (no recognizable provider prefix) but an explicit provider.
{ id: 'llama3-8b', name: 'Llama 3 8B', provider: 'ollama' },
{ id: 'qwen3.5-27b', name: 'Qwen', provider: 'ollama' },
];
const groups = groupModelsByProvider(models);
expect(groups.get('ollama')).toHaveLength(2);
expect(groups.has('llama3-8b')).toBe(false);
});
});
});
+4 -1
View File
@@ -6,6 +6,7 @@ interface ModelInfo {
name: string;
description?: string;
notable?: boolean;
provider?: string;
}
interface PartialRunRecord {
@@ -175,7 +176,9 @@ export function formatPatrolRuntimeFailureSummary(input: {
export function groupModelsByProvider(models: ModelInfo[]): Map<string, ModelInfo[]> {
const groups = new Map<string, ModelInfo[]>();
for (const model of models) {
const [provider] = model.id.split(':');
// Prefer the server-supplied provider; fall back to the id prefix for
// models that predate the provider field or have an opaque id (#1320).
const provider = model.provider?.trim() || model.id.split(':')[0];
if (!groups.has(provider)) {
groups.set(provider, []);
}
+1
View File
@@ -144,6 +144,7 @@ type ModelInfo struct {
Description string `json:"description,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
Notable bool `json:"notable"` // Whether this is a "latest and greatest" model
Provider string `json:"provider,omitempty"`
}
// Provider defines the interface for AI providers
+1
View File
@@ -4644,6 +4644,7 @@ func prefixProviderModels(providerName string, models []providers.ModelInfo) []p
Description: description,
CreatedAt: m.CreatedAt,
Notable: m.Notable,
Provider: providerName,
})
}
return prefixed
+2
View File
@@ -3229,6 +3229,7 @@ func (h *AISettingsHandler) HandleListModels(w http.ResponseWriter, r *http.Requ
Description string `json:"description,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
Notable bool `json:"notable"`
Provider string `json:"provider,omitempty"`
}
type Response struct {
@@ -3264,6 +3265,7 @@ func (h *AISettingsHandler) HandleListModels(w http.ResponseWriter, r *http.Requ
Description: m.Description,
CreatedAt: m.CreatedAt,
Notable: m.Notable,
Provider: m.Provider,
})
}