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
15 changes: 0 additions & 15 deletions scripts/generate-provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ function propertyName(service: string): string {

async function writeRegistry(filename: string, sources: ProviderSource[]): Promise<void> {
const services = sources.map((source) => source.service);
const executableActionIds = new Map<string, string[]>(
sources.map((source) => [
source.service,
source.definition.actions.map((action) => action.id).sort((a, b) => a.localeCompare(b)),
]),
);
const lines = [
'import type { ExecutorModule } from "./provider-loader.ts";',
"",
Expand All @@ -44,15 +38,6 @@ async function writeRegistry(filename: string, sources: ProviderSource[]): Promi
(service) => ` ${propertyName(service)}: (): Promise<ExecutorModule> => import("./${service}/executors.ts"),`,
),
"};",
"",
"/** Generated local executable action ids by provider. Do not hand-edit. */",
"export const executableActionIds: Record<string, string[]> = {",
...services.flatMap((service) => [
` ${propertyName(service)}: [`,
...(executableActionIds.get(service) ?? []).map((actionId) => ` ${JSON.stringify(actionId)},`),
" ],",
]),
"};",
];

const path = join(providersDir, filename);
Expand Down
36 changes: 35 additions & 1 deletion src/catalog-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ProviderDefinition } from "./core/types.ts";

import { describe, expect, it } from "vitest";
import { createCatalogStore } from "./catalog-store.ts";
import { createCatalogStore, resolveExecutableActionIds } from "./catalog-store.ts";

describe("catalog store", () => {
it("preserves optional provider descriptions without defaulting missing ones", () => {
Expand Down Expand Up @@ -72,4 +72,38 @@ describe("catalog store", () => {
properties: { message: { type: "string" } },
});
});

it("resolves every action from executable services alongside explicit action ids", () => {
const providers = [providerFixture("example", ["ping", "pong"]), providerFixture("remote", ["ping"])];

const catalog = createCatalogStore(providers, {
executableActionIds: resolveExecutableActionIds(providers, {
executableServices: ["example"],
executableActionIds: ["remote.ping"],
}),
});

expect(catalog.executableActionIds).toEqual(new Set(["example.ping", "example.pong", "remote.ping"]));
expect(catalog.actionsById.get("example.pong")?.execution.locallyExecutable).toBe(true);
});
});

function providerFixture(service: string, actionNames: string[]): ProviderDefinition {
return {
service,
displayName: service,
categories: ["Developer Tools"],
authTypes: ["no_auth"],
auth: [{ type: "no_auth" }],
actions: actionNames.map((name) => ({
id: `${service}.${name}`,
service,
name,
description: `${name} action.`,
requiredScopes: [],
providerPermissions: [],
inputSchema: {},
outputSchema: {},
})),
};
}
33 changes: 30 additions & 3 deletions src/catalog-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,19 @@ export type CatalogStore = {
executableActionIds: Set<string>;
};

export interface LoadCatalogOptions {
export interface CreateCatalogStoreOptions {
executableActionIds?: Iterable<string>;
}

export function createCatalogStore(providers: ProviderDefinition[], options: LoadCatalogOptions = {}): CatalogStore {
export interface LoadCatalogOptions extends CreateCatalogStoreOptions {
/** Mark every catalog action owned by these locally loaded provider services as executable. */
executableServices?: Iterable<string>;
}

export function createCatalogStore(
providers: ProviderDefinition[],
options: CreateCatalogStoreOptions = {},
): CatalogStore {
const sortedProviders = sortProviders(providers);
const executableActions = new Set(options.executableActionIds ?? []);
const runtimeProviders = sortedProviders.map((provider): RuntimeProviderDefinition => {
Expand Down Expand Up @@ -148,7 +156,26 @@ export async function loadCatalog(
return JSON.parse(content) as ProviderDefinition;
}),
);
return createCatalogStore(providers, options);
return createCatalogStore(providers, {
executableActionIds: resolveExecutableActionIds(providers, options),
});
}

/** Resolve provider-level executable services into the exact action ids present in a loaded catalog. */
export function resolveExecutableActionIds(
providers: ProviderDefinition[],
options: LoadCatalogOptions = {},
): Set<string> {
const actionIds = new Set(options.executableActionIds ?? []);
const services = new Set(options.executableServices ?? []);
for (const provider of providers) {
if (services.has(provider.service)) {
for (const action of provider.actions) {
actionIds.add(action.id);
}
}
}
return actionIds;
}

function createActionExecutionStatus(
Expand Down
4 changes: 2 additions & 2 deletions src/server/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ISecretCodec } from "./secrets/secret-codec-core.ts";
import { ActionPolicyService, parseActionPolicyList } from "../core/action-policy.ts";
import { parsePrivateNetworkAccessFlag, setPrivateNetworkAccessAllowed } from "../core/request.ts";
import { ProviderLoader } from "../providers/provider-loader.ts";
import { executableActionIds, executorModules } from "../providers/registry.cloudflare.generated.ts";
import { executorModules } from "../providers/registry.cloudflare.generated.ts";
import { isConsoleShellPath } from "./api/console-paths.ts";
import { loadCatalogFromAssets } from "./cloudflare/catalog-assets.ts";
import { readPositiveInteger, resolvePublicOrigin } from "./cloudflare/cloudflare-env.ts";
Expand Down Expand Up @@ -120,7 +120,7 @@ function writeWorkerLog(level: "error" | "info" | "warn"): (fields: unknown, mes

function loadCatalogOnce(assets: AssetsBinding): Promise<CatalogStore> {
catalogPromise ??= loadCatalogFromAssets(assets, {
executableActionIds: Object.values(executableActionIds).flat(),
executableServices: Object.keys(executorModules),
});
return catalogPromise;
}
Expand Down
6 changes: 4 additions & 2 deletions src/server/cloudflare/catalog-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CatalogStore, LoadCatalogOptions } from "../../catalog-store.ts";
import type { ProviderDefinition } from "../../core/types.ts";
import type { AssetsBinding } from "./cloudflare-bindings.ts";

import { createCatalogStore } from "../../catalog-store.ts";
import { createCatalogStore, resolveExecutableActionIds } from "../../catalog-store.ts";

const catalogAssetPath = "/catalog/apps.json";

Expand All @@ -11,7 +11,9 @@ export async function loadCatalogFromAssets(
options: LoadCatalogOptions = {},
): Promise<CatalogStore> {
const providers = (await readJsonAsset(assets, catalogAssetPath)) as ProviderDefinition[];
return createCatalogStore(providers, options);
return createCatalogStore(providers, {
executableActionIds: resolveExecutableActionIds(providers, options),
});
}

async function readJsonAsset(assets: AssetsBinding, path: string): Promise<unknown> {
Expand Down
4 changes: 2 additions & 2 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { loadCatalog } from "../catalog-store.ts";
import { ActionPolicyService, parseActionPolicyList } from "../core/action-policy.ts";
import { parsePrivateNetworkAccessFlag, setPrivateNetworkAccessAllowed } from "../core/request.ts";
import { ProviderLoader } from "../providers/provider-loader.ts";
import { executableActionIds, executorModules } from "../providers/registry.generated.ts";
import { executorModules } from "../providers/registry.generated.ts";
import { createRuntimeJwtVerifier } from "./api/runtime-jwt.ts";
import { registerStaticRoutes } from "./api/static-routes.ts";
import { createConnectApp } from "./connect-app.ts";
Expand Down Expand Up @@ -41,7 +41,7 @@ const builtRoot = join(process.cwd(), "dist/web");
const staticRoot = await resolveStaticRoot(builtRoot);
await mkdir(dataDir, { recursive: true });
const catalog = await loadCatalog(undefined, {
executableActionIds: Object.values(executableActionIds).flat(),
executableServices: Object.keys(executorModules),
});
const providerLoader = new ProviderLoader(executorModules);
const runtimeDatabase = new SqliteRuntimeDatabase(join(dataDir, "connect.sqlite"), {
Expand Down
Loading