Skip to content
Open
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
63 changes: 63 additions & 0 deletions apps/app/src/app/cloud/managed-provider-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { CloudImportedProvider } from "./import-state";
import type { ModelOption, ProviderListItem } from "../types";

export function buildCloudManagedModelIdsByProvider(
importedCloudProviders: Record<string, CloudImportedProvider> | null | undefined,
): Map<string, Set<string>> {
const next = new Map<string, Set<string>>();
for (const imported of Object.values(importedCloudProviders ?? {})) {
const providerId = imported.providerId.trim();
if (!providerId) continue;
const modelIds = imported.modelIds.map((id) => id.trim()).filter(Boolean);
if (!modelIds.length) continue;
const merged = next.get(providerId) ?? new Set<string>();
for (const modelId of modelIds) merged.add(modelId);
next.set(providerId, merged);
}
return next;
}

export function isCloudManagedModelAllowed(
cloudManagedModelIdsByProvider: Map<string, Set<string>>,
providerId: string,
modelId: string,
) {
const allowedModelIds = cloudManagedModelIdsByProvider.get(providerId);
return !allowedModelIds || allowedModelIds.has(modelId);
}

export function hasCloudManagedModelAllowlist(
cloudManagedModelIdsByProvider: Map<string, Set<string>>,
providerId: string,
) {
return cloudManagedModelIdsByProvider.has(providerId);
}

export function buildCloudManagedModelOptions(input: {
providers: ProviderListItem[];
cloudManagedModelIdsByProvider: Map<string, Set<string>>;
isRecommendedProvider?: (providerId: string) => boolean;
}): ModelOption[] {
const options: ModelOption[] = [];
for (const provider of input.providers) {
const isCloudManaged = hasCloudManagedModelAllowlist(input.cloudManagedModelIdsByProvider, provider.id);
for (const [modelId, model] of Object.entries(provider.models)) {
if (!isCloudManagedModelAllowed(input.cloudManagedModelIdsByProvider, provider.id, modelId)) continue;
options.push({
providerID: provider.id,
modelID: modelId,
title: model.name || modelId,
description: provider.name,
behaviorTitle: "Reasoning",
behaviorLabel: "Default",
behaviorDescription: "",
behaviorValue: null,
isFree: false,
isConnected: true,
isRecommended: input.isRecommendedProvider?.(provider.id),
source: isCloudManaged || /^lpr_/i.test(provider.id) ? "cloud" : undefined,
});
}
}
return options;
}
49 changes: 49 additions & 0 deletions apps/app/src/app/lib/den.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,28 @@ export type DenOrgLlmProviderModel = {
export type DenOrgLlmProvider = {
id: string;
source: "models_dev" | "custom" | "openwork";
credentialKind: "api_key" | "opencode_oauth";
providerId: string;
name: string;
providerConfig: Record<string, unknown>;
hasApiKey: boolean;
hasOpencodeAuth: boolean;
hasCredential: boolean;
models: DenOrgLlmProviderModel[];
createdAt: string | null;
updatedAt: string | null;
};

export type DenOrgLlmProviderConnection = DenOrgLlmProvider & {
apiKey: string | null;
opencodeAuth: string | null;
};

export type DenManagedProviderSyncResult = {
status: "applied" | "failed";
providerCount: number;
revision: string;
reason?: string;
};

export type DenPluginConfigObjectType = "skill" | "agent" | "command" | "tool" | "mcp" | "hook" | "context" | "custom";
Expand Down Expand Up @@ -907,10 +918,13 @@ function parseDenOrgLlmProvider(value: unknown): DenOrgLlmProvider | null {
return {
id: value.id,
source: value.source,
credentialKind: value.credentialKind === "opencode_oauth" ? "opencode_oauth" : "api_key",
providerId: value.providerId,
name: value.name,
providerConfig: isRecord(value.providerConfig) ? value.providerConfig : {},
hasApiKey: value.hasApiKey === true,
hasOpencodeAuth: value.hasOpencodeAuth === true,
hasCredential: value.hasCredential === true || value.hasApiKey === true || value.hasOpencodeAuth === true,
models: Array.isArray(value.models)
? value.models.flatMap((model) => {
const parsed = parseDenOrgLlmProviderModel(model);
Expand Down Expand Up @@ -946,6 +960,20 @@ function getDenOrgLlmProviderConnection(payload: unknown): DenOrgLlmProviderConn
return {
...provider,
apiKey: typeof payload.llmProvider.apiKey === "string" ? payload.llmProvider.apiKey : null,
opencodeAuth: typeof payload.llmProvider.opencodeAuth === "string" ? payload.llmProvider.opencodeAuth : null,
};
}

function getDenManagedProviderSyncResult(payload: unknown): DenManagedProviderSyncResult | null {
if (!isRecord(payload)) return null;
if (payload.status !== "applied" && payload.status !== "failed") return null;
if (typeof payload.providerCount !== "number" || !Number.isInteger(payload.providerCount) || payload.providerCount < 0) return null;
if (typeof payload.revision !== "string") return null;
return {
status: payload.status,
providerCount: payload.providerCount,
revision: payload.revision,
...(typeof payload.reason === "string" ? { reason: payload.reason } : {}),
};
}

Expand Down Expand Up @@ -1771,6 +1799,27 @@ export function createDenClient(options: { baseUrl: string; apiBaseUrl?: string
return provider;
},

async syncWorkerManagedProviders(orgId: string, workerId: string): Promise<DenManagedProviderSyncResult> {
const payload = await requestJson<unknown>(
baseUrls,
`/v1/workers/${encodeURIComponent(workerId)}/managed-providers/sync`,
{
method: "POST",
token,
organizationId: orgId,
body: {},
},
);
const result = getDenManagedProviderSyncResult(payload);
if (!result) {
throw new DenApiError(500, "invalid_managed_provider_sync_payload", "Managed provider sync response was invalid.");
}
if (result.status !== "applied") {
throw new DenApiError(502, "managed_provider_sync_failed", result.reason ?? "Managed provider sync failed.");
}
return result;
},

async listOrgMarketplaces(orgId: string): Promise<DenOrgMarketplace[]> {
const payload = await requestJson<unknown>(
baseUrls,
Expand Down
2 changes: 2 additions & 0 deletions apps/app/src/app/lib/desktop-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export type WorkspaceInfo = {
openworkHostToken?: string | null;
openworkWorkspaceId?: string | null;
openworkWorkspaceName?: string | null;
openworkDenOrgId?: string | null;
openworkDenWorkerId?: string | null;
sandboxBackend?: "docker" | "microsandbox" | null;
sandboxRunId?: string | null;
sandboxContainerName?: string | null;
Expand Down
24 changes: 7 additions & 17 deletions apps/app/src/components/model-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { readHiddenModels } from "@/react-app/domains/session/modals/model-picke
import { Settings2 } from "lucide-react";
import { openModelPickerEvent } from "@/react-app/shell/new-providers-toast";
import { newProvidersEvent } from "@/app/lib/provider-events";
import { buildCloudManagedModelOptions } from "@/app/cloud/managed-provider-models";

function getProviderDisplayName(providerId: string) {
return providerId
Expand All @@ -44,7 +45,7 @@ function getProviderDisplayName(providerId: string) {
}

function useModelOptions(open: boolean) {
const { client, opencodeBaseUrl, selectedWorkspaceRoot } = useWorkspace();
const { client, opencodeBaseUrl, selectedWorkspaceRoot, cloudManagedModelIdsByProvider } = useWorkspace();
const checkDesktopRestriction = useCheckDesktopRestriction();

const { data, refetch } = useProviderListQuery({
Expand Down Expand Up @@ -78,21 +79,10 @@ function useModelOptions(open: boolean) {
restriction: "allowCustomProviders",
});

const options = getConnectedProviderItems(data)
.flatMap((provider) =>
Object.entries(provider.models).map(([id, model]) => ({
providerID: provider.id,
modelID: id,
title: model.name,
description: provider.name,
behaviorTitle: "Reasoning",
behaviorLabel: "Default",
behaviorDescription: "",
behaviorValue: null,
isFree: false,
isConnected: true,
})),
);
const options = buildCloudManagedModelOptions({
providers: getConnectedProviderItems(data),
cloudManagedModelIdsByProvider,
});

return options.filter((option) => {
if (
Expand All @@ -110,7 +100,7 @@ function useModelOptions(open: boolean) {

return true;
});
}, [checkDesktopRestriction, data]);
}, [checkDesktopRestriction, cloudManagedModelIdsByProvider, data]);
}

function groupByProvider(modelOptions: ModelOption[]) {
Expand Down
Loading