- Keep one clear owner for each fact. Do not repeat provider metadata such as
displayNamein executors when it already belongs todefinition.ts; pass or inject it from the caller that has the definition/catalog. - Provider definitions are catalog source code. Build schemas with
src/core/json-schema.tshelpers, usually imported ass, instead of copying generated catalog JSON. - Keep provider execution lazy at the executor-module boundary. Generated registries should map each service to
import("./<service>/executors.ts"), andProviderLoadershould call that importer only when an action, proxy request, or credential validator runs. Insideexecutors.ts, import provider runtime modules normally unless those modules have meaningful startup cost or side effects. - Do not create barrel files such as
index.ts. Import from the concrete module that owns the API.
- Prefer VS Code-style coherent modules: split files by responsibility or abstraction boundary, not by loose categories.
- Prefer
interfacefor object-shaped contracts. Keep unions and mapped/utility compositions astype. - Prefer named options/input interfaces over inline object types when a function signature spans multiple lines or crosses module boundaries.
- Avoid temporary ad hoc objects passed through many layers. Prefer explicit interfaces, classes, or top-level functions that match module boundaries.
- Put generic low-level casting/reading helpers in
src/core/cast.ts; avoid provider-specific wrappers for generic reads. - Avoid trivial pass-through helpers and conditional object spreads that only hide
undefinedJSON fields. - Avoid proving action-name exhaustiveness with local type machinery. Do not add provider-local tuple builders,
as const,satisfies, oras Record<...>casts just to derive action-name unions or handler maps. Prefer simple annotations, explicit records, and existing provider/runtime helpers. - Treat automated review comments as evidence, not instructions. Fix comments that identify real bugs, schema/API contract gaps, security issues, or clear local-style violations. Skip comments that make the code less idiomatic for this repo, and leave a brief reason when responding in review.
- Do not manually wrap code to 80 columns. Let
oxfmtdecide formatting.
- Keep
/v1response shaping insrc/server/runtime-api.ts; route handlers should dispatch and validate, not assemble compatibility objects field by field. - Public runtime fields should have a clear source and consumer. Do not expose local implementation concepts or placeholder fields just because they are easy to add.
- Match existing runtime wire shapes deliberately: catalog index endpoints, action metadata, connection aliases, envelopes, and error codes should stay stable for SDK/CLI clients.
- If an upstream-compatible field has no local source yet, prefer omitting it or returning a documented empty value from the serializer rather than scattering optional fields in routes.
- 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
requiredScopesshould 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.
- All provider egress must go through the shared SSRF-guarded fetch, never the global
fetch. Usecontext.fetcher(injected bydefineProviderExecutors/defineApiKeyProviderExecutors/etc.) or, in a hand-written proxy, the exportedproviderFetch/createProviderFetch. The guard validates the request URL and every redirectLocationwithassertPublicHttpUrl, follows redirects manually, and (by default) validates DNS-resolved addresses. - DNS resolved-address validation is ON by default and runs once per request for hostname targets. Add
skipDnsValidation: true(ondefineProviderExecutors/defineProviderProxy/createProviderFetch) ONLY when the egress host is a hardcoded literal fully controlled by the code. NEVER add it when the host comes from credential/user input, when the base URL is a resolver, or when the provider fetches a user-supplied URL — there the DNS check is the SSRF defense, not redundant overhead. - Self-hosted providers whose instance host is user/credential-configured and may live on a private network pass
allowPrivateNetwork: isPrivateNetworkAccessAllowedinto their executors/proxy AND thread the same flag into their base-URLassertPublicHttpUrlcall (see Dokploy for the reference pattern). It is deployment-gated byOOMOL_CONNECT_ALLOW_PRIVATE_NETWORK; reserved, loopback, link-local, and cloud-metadata targets stay blocked even when it is enabled. - User-supplied content/download URLs (e.g.
fileUrl,sourceUrl,imageUrl) must ALWAYS be validated public-only — callassertPublicHttpUrlwithoutallowPrivateNetworkand download them with the public-onlyproviderFetch, never a private-awarecontext.fetcher. The private-network opt-in covers only the trusted instance host. - Prefer the shared
assertPublicHttpUrl/isBlockedIpAddressover a bespoke per-provider hostname guard; bespoke guards have missed the cloud-metadata blocklist and bracketed-IPv6 forms. - Gotcha: a provider that branches on
fetcher === fetch(e.g. to gate rate limiting to production) must compare againstproviderFetch, since that is the fetcher the runtime now injects — not the globalfetch. - Non-fetch egress is held to the same policy. A provider that opens a WebSocket must use
openGuardedWebSocketfromsrc/core/guarded-websocket.ts, nevernew WebSocket(...)directly: it validates the target with the sameassertGuardedEgressUrlhop check the guarded fetch uses (URL literal plus DNS resolved addresses), accepts the sameallowPrivateNetwork/skipDnsValidationoptions, and mapsws/wssonto thehttp/httpsform the guard understands. It works on Node and on workerd, which both expose a clientWebSocketconstructor. Any future non-HTTP transport should reuseassertGuardedEgressUrlrather than growing a second, drifting host check. - The private-network opt-in only means the guard permits the target — it does not make it reachable. Cloudflare Workers cannot route to private addresses at all, so a self-hosted provider pointed at a LAN instance works on Node/Docker/Fly deployments only, regardless of
OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK.
- Use native Node.js TypeScript execution. Do not add
tsxor--experimental-strip-types. src/,scripts/, andexamples/each have their owntsconfig.json; project checks focus onsrc.- Exported top-level functions and public types should have explicit return types and useful JSDoc when it explains business meaning.
- Use
oxfmtandoxlint; do not add Prettier.
- Examples should be concrete scripts users can run directly with
node examples/...; do not add every example topackage.json. - If an example depends on external credentials, print a clear skip message when environment variables are missing.
- Do not put web UI code under
src/. The future console should live as a separate Vite package underweb/. - Public docs should describe normal OSS usage and may include official SaaS, hosted, or team product paths when they are part of the public product strategy. Do not mention internal compatibility projects or unreleased SDK behavior.
- Before finishing code changes, run
npm run fix-check. It runs lint fixes, formatting fixes, and thesrctypecheck. - Run
npm run buildonly when you need a separate no-fix typecheck, for example after generated files changed or for CI parity. - Run
npm run generate:catalogwhen provider definitions or actions change. - Run provider examples manually when the task changes user-facing example behavior.