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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
## Providers

- Provider code normally lives in `src/providers/<service>/definition.ts`, `actions.ts`, `executors.ts`, and provider-local runtime helper files when needed.
- When purely migrating a provider from the OOMOL-hosted connector, do not copy or add provider-local tests because the source repository already owns that regression coverage. Tests may be removed from this repository after an OSS-originated provider change is reverse-ported and covered in private. Keep open-source-only shared-infrastructure tests beside the shared module rather than inside a provider directory.
- Prefer provider-local constants for official scopes, permissions, URLs, and API versions. Action `requiredScopes` should use provider-native scopes/capabilities, not private internal aliases.
- Avoid repeated action-name wiring. Define action handlers once and derive executor maps through shared provider runtime helpers when an existing helper fits. Do not add provider-local action-name unions, tuple builders, or casts solely to prove the handler keys to TypeScript.
- Do not import provider definitions from executor modules just to reuse metadata; inject catalog metadata from the server/loader side when needed.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test": "vitest run",
"typecheck": "npm run generate:registry && node scripts/typecheck.ts src scripts-all examples",
"generate:registry": "node scripts/generate-provider-registry.ts",
"generate:catalog": "npm run generate:registry && node scripts/generate-catalog.ts",
"generate:catalog": "node scripts/generate-catalog.ts",
"generate:provider": "node scripts/generate-provider.ts",
"runtime:data": "node scripts/runtime-data.ts",
"lint": "oxlint .",
Expand Down
22 changes: 13 additions & 9 deletions scripts/ensure-generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ const generatedPaths = new Set(registryPaths);

const sourceMtimeMs = await newestMtimeMs(sourcePaths);

const registriesFresh = await Promise.all(registryPaths.map((path) => isFreshFile(path, sourceMtimeMs)));
if (registriesFresh.some((fresh) => !fresh)) {
runNodeScript("scripts/generate-provider-registry.ts");
}

if (!(await isFreshCatalog(sourceMtimeMs))) {
const [registriesPresent, catalogFresh] = await Promise.all([
Promise.all(registryPaths.map((path) => isFile(path))),
isFreshCatalog(sourceMtimeMs),
]);
// A fresh catalog proves both registries were generated from the same provider source set.
if (!catalogFresh) {
runNodeScript("scripts/generate-catalog.ts");
} else if (registriesPresent.some((present) => !present)) {
runNodeScript("scripts/generate-provider-registry.ts");
}

function runNodeScript(script: string): void {
Expand All @@ -39,10 +41,10 @@ function runNodeScript(script: string): void {
}
}

async function isFreshFile(path: string, sourceMtimeMs: number): Promise<boolean> {
async function isFile(path: string): Promise<boolean> {
try {
const stats = await stat(path);
return stats.isFile() && stats.mtimeMs >= sourceMtimeMs;
return stats.isFile();
} catch (error) {
if (isNotFoundError(error)) {
return false;
Expand All @@ -63,7 +65,9 @@ async function isFreshCatalog(sourceMtimeMs: number): Promise<boolean> {
return false;
}

const catalogServices = jsonFiles.map((entry) => entry.name.slice(0, -".json".length)).sort();
const catalogServices = jsonFiles
.map((entry) => entry.name.slice(0, -".json".length))
.sort((a, b) => a.localeCompare(b));
if (
catalogServices.length !== services.length ||
catalogServices.some((service, index) => service !== services[index])
Expand Down
5 changes: 4 additions & 1 deletion scripts/generate-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { mkdir, rename, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { sortProviders } from "../src/core/catalog.ts";
import { assertProviderId } from "../src/core/provider-id.ts";
import { generateProviderRegistries } from "./generate-provider-registry.ts";
import { loadProviderSources } from "./provider-source.ts";

const outputDir = join(process.cwd(), "catalog/apps");
const catalogRootDir = join(process.cwd(), "catalog");
const tempOutputDir = join(catalogRootDir, `.apps-${process.pid}-${Date.now()}`);
const providers = (await loadProviderSources()).map((source) => source.definition);
const providerSources = await loadProviderSources();
await generateProviderRegistries(providerSources);
const providers = providerSources.map((source) => source.definition);
const apps = sortProviders(providers);

await mkdir(catalogRootDir, { recursive: true });
Expand Down
24 changes: 16 additions & 8 deletions scripts/generate-provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ import { join } from "node:path";
import { loadProviderSources } from "./provider-source.ts";

const providersDir = join(process.cwd(), "src/providers");
const providerSources = await loadProviderSources();

await Promise.all([
writeRegistry("registry.generated.ts", providerSources),
writeRegistry(
"registry.cloudflare.generated.ts",
providerSources.filter((source) => !source.nodeOnly),
),
]);
/**
* Generate provider registries from definitions already loaded by the caller.
*/
export async function generateProviderRegistries(providerSources: ProviderSource[]): Promise<void> {
await Promise.all([
writeRegistry("registry.generated.ts", providerSources),
writeRegistry(
"registry.cloudflare.generated.ts",
providerSources.filter((source) => !source.nodeOnly),
),
]);
}

if (import.meta.main) {
await generateProviderRegistries(await loadProviderSources());
}

function propertyName(service: string): string {
return /^[A-Za-z_$][\w$]*$/.test(service) ? service : JSON.stringify(service);
Expand Down
1 change: 1 addition & 0 deletions src/core/guarded-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const crossOriginCredentialHeaders = new Set([
"private-token",
"x-private-token",
"x-csrf-token",
"x-gotify-key",
"x-xsrf-token",
"x-goog-api-key",
"x-acs-security-token",
Expand Down
8 changes: 6 additions & 2 deletions src/core/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ export const jsonSchema = {
return withOptions({ type: "string", pattern }, options);
},

stringEnum(valuesOrDescription: string[] | string, optionsOrValues: JsonSchemaOptions | string[] = {}): JsonSchema {
const values = typeof valuesOrDescription === "string" ? (optionsOrValues as string[]) : valuesOrDescription;
stringEnum(
valuesOrDescription: readonly string[] | string,
optionsOrValues: JsonSchemaOptions | readonly string[] = {},
): JsonSchema {
const values =
typeof valuesOrDescription === "string" ? (optionsOrValues as readonly string[]) : valuesOrDescription;
const options =
typeof valuesOrDescription === "string"
? { description: valuesOrDescription }
Expand Down
16 changes: 8 additions & 8 deletions src/core/provider-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import type { ActionDefinition, JsonSchema } from "./types.ts";
* Input for defining one provider action without repeating provider-level
* fields in every action object.
*/
export type DefineProviderActionInput<TName extends string = string> = {
export interface DefineProviderActionInput<TName extends string = string> {
name: TName;
description: string;
inputSchema: JsonSchema;
outputSchema: JsonSchema;
requiredScopes?: string[];
providerPermissions?: string[];
followUpActions?: string[];
requiredScopes?: readonly string[];
providerPermissions?: readonly string[];
followUpActions?: readonly string[];
asyncLifecycle?: ActionDefinition["asyncLifecycle"];
};
}

export type ProviderActionDefinition<TName extends string = string> = ActionDefinition & { name: TName };

Expand All @@ -32,11 +32,11 @@ export function defineProviderAction<TName extends string>(
service,
name: input.name,
description: input.description,
requiredScopes: input.requiredScopes ?? [],
providerPermissions: input.providerPermissions ?? [],
requiredScopes: input.requiredScopes ? [...input.requiredScopes] : [],
providerPermissions: input.providerPermissions ? [...input.providerPermissions] : [],
inputSchema: input.inputSchema,
outputSchema: input.outputSchema,
followUpActions: input.followUpActions,
followUpActions: input.followUpActions ? [...input.followUpActions] : undefined,
asyncLifecycle: input.asyncLifecycle,
};
}
11 changes: 4 additions & 7 deletions src/providers/aliyun_oss/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ function createAliyunOssClient(input: AliyunClientOptions): AliyunOssClient {
return new AliOss({
accessKeyId: input.accessKeyId,
accessKeySecret: input.accessKeySecret,
...(input.securityToken ? { stsToken: input.securityToken } : {}),
stsToken: input.securityToken,
endpoint: stripProtocol(normalizeEndpoint(input.endpoint)),
...(input.bucket ? { bucket: input.bucket } : {}),
bucket: input.bucket,
secure: true,
}) as unknown as AliyunOssClient;
}
Expand Down Expand Up @@ -681,11 +681,8 @@ function normalizeAliyunError(error: unknown, phase: "validate" | "execute"): Pr
}

function readAliyunErrorStatus(error: unknown): number | undefined {
if (!error || typeof error !== "object") {
return undefined;
}

const record = error as Record<string, unknown>;
const record = optionalRecord(error);
if (!record) return undefined;
const status = record.status ?? record.statusCode ?? record.code;
return typeof status === "number" ? status : undefined;
}
Expand Down
35 changes: 0 additions & 35 deletions src/providers/aliyun_oss/network-access.test.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/providers/aliyun_sls/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,6 @@ const queryTimeProperties: Record<string, JsonSchema> = {
query: s.string("An optional Simple Log Service search or analytic statement."),
};

export type AliyunSlsActionName =
| "list_projects"
| "list_projects_across_regions"
| "list_logstores"
| "query_logs"
| "get_histograms";

export const aliyunSlsActions: ActionDefinition[] = [
defineProviderAction(service, {
name: "list_projects",
Expand Down
149 changes: 0 additions & 149 deletions src/providers/aliyun_sls/resources.test.ts

This file was deleted.

Loading
Loading