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
12 changes: 10 additions & 2 deletions server/lib/systemCapabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import os from 'os';
import { isLocalInstanceEndpoint } from './localEndpoint.js';
import { isAppleSilicon } from './platform.js';
import { getCudaCapability, getCudaComputeCapability } from './cudaCapability.js';

Expand Down Expand Up @@ -385,6 +386,13 @@ export function withHardwareCompatibility(item, capabilities, requirements) {
}

export function withProviderHardwareCompatibility(provider, capabilities) {
// These probes describe this PortOS host, not the machine serving a remote
// API (including a local CLI harness backed by a fleet GPU endpoint).
// Preserve requirements, but leave remote hardware unverified and selectable.
const endpoint = provider?.endpoint;
const remoteEndpoint = typeof endpoint === 'string' && URL.canParse(endpoint)
&& !isLocalInstanceEndpoint(endpoint);
const runtimeCapabilities = remoteEndpoint ? null : capabilities;
const hardwareRequirements = hardwareRequirementsForProvider(provider);
const modelRequirementLayers = provider?.modelHardwareRequirements || {};
const modelIds = [...new Set([
Expand All @@ -398,7 +406,7 @@ export function withProviderHardwareCompatibility(provider, capabilities) {
].filter((model) => typeof model === 'string' && model))];
const modelHardwareCompatibility = Object.fromEntries(modelIds.map((model) => {
const requirements = hardwareRequirementsForProviderModel(provider, model);
return [model, evaluateHardwareRequirements(requirements, capabilities)];
return [model, evaluateHardwareRequirements(requirements, runtimeCapabilities)];
}));
const hasModelRequirements = Object.values(modelHardwareCompatibility).some(
(compatibility) => Object.keys(compatibility.requirements).length > 0,
Expand All @@ -407,7 +415,7 @@ export function withProviderHardwareCompatibility(provider, capabilities) {
return {
...provider,
hardwareRequirements,
hardwareCompatibility: evaluateHardwareRequirements(hardwareRequirements, capabilities),
hardwareCompatibility: evaluateHardwareRequirements(hardwareRequirements, runtimeCapabilities),
modelHardwareCompatibility,
};
}
26 changes: 26 additions & 0 deletions server/lib/systemCapabilities.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,32 @@ describe('systemCapabilities', () => {
expect(provider.modelHardwareCompatibility.large.state).toBe('unavailable');
});

it('keeps fleet API and TUI models selectable without applying the caller hardware', () => {
for (const type of ['api', 'tui']) {
const provider = {
id: 'fleet-gpu',
type,
command: type === 'tui' ? 'opencode' : undefined,
endpoint: 'http://gpu.example.com:18022/v1',
vllmBacked: true,
models: ['qwen3.8-27b'],
hardwareRequirements: { platforms: ['win32'] },
modelHardwareRequirements: { 'qwen3.8-27b': { minMemoryGb: 256 } },
};
const remote = withProviderHardwareCompatibility(provider, APPLE_32GB);
expect(remote.hardwareCompatibility.state).toBe('unknown');
expect(remote.modelHardwareCompatibility['qwen3.8-27b']).toMatchObject({
state: 'unknown',
requirements: { minMemoryGb: 256, platforms: ['win32'] },
});
for (const endpoint of ['http://localhost:18022/v1', 'http://[::1]:18022/v1', '', undefined]) {
const local = withProviderHardwareCompatibility({ ...provider, endpoint }, APPLE_32GB);
expect(local.hardwareCompatibility.state).toBe('unavailable');
expect(local.modelHardwareCompatibility['qwen3.8-27b'].state).toBe('unavailable');
}
}
});

it('retains inferred local model compatibility without provider overrides', () => {
const provider = withProviderHardwareCompatibility({
id: 'ollama',
Expand Down