Typed helper-namespace codegen from relayfile adapter manifests
Problem
Slice B (merged as PR#314) established the runtime pattern for f.slack — a
helper namespace on Ctx that lowers each verb to an existing agent+effect
kernel primitive. It ships one canonical provider, and the argument types on
that provider are hand-written in packages/surface/src/slack.ts.
For the other 50 providers to inherit the same "wrong-channel-name is a compile
error" ergonomics, each helper's TypeScript surface has to reflect the real
verb signatures of its adapter. Hand-writing 50 of them is not the plan, and
without types every f.notion.appendBlock({ paret: … }) typo is a runtime
crash instead of a red squiggle.
Scope
Build-time codegen. Every generated file is types-only sugar; runtime dispatch
still flows through B's slack.ts-shape helper.
Deliverables:
-
scripts/generate-helpers.mjs — walks
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/*/
and for each provider consumes:
<provider>.mapping.yaml when present (github, notion — the canonical
mapping form) for the write-path verbs;
discovery/ when present (slack, github, linear, notion) for
resource-level read/list surfaces;
- The hand-tuned ergonomic client at
relayfile-adapters/packages/relay-helpers/src/<provider>.ts for the
top-provider set that already has one (slack, github, linear, telegram,
reddit) — reused verbatim so we don't regress on their bespoke argument
shapes;
- Otherwise the uniform generated shape from
relay-helpers/src/generated/clients.ts (providerClient<'<name>'>).
Output: packages/surface/src/helpers/<provider>.ts — one file per
provider, exporting a typed helper class/interface plus the verb signatures.
-
packages/surface/src/helpers/index.ts — re-exports every generated helper
and a Helpers type union that Ctx widens against.
-
packages/surface/src/context.ts — the existing Ctx gains a generated
index-signature over declared helpers, so f.<provider> resolves and is
type-checked. Runtime lookup remains B's dispatcher.
-
Regeneration guard: packages/surface/scripts/check-generated-helpers.mjs
is added to the surface package's bun run typecheck:regressions chain so
drift between the checked-in generated files and a fresh regen fails CI.
-
Docs — a short packages/surface/src/helpers/README.md explaining
"generated, do not edit by hand; re-run npm run gen -w @relayflows/surface after bumping the adapter version".
Zero runtime cost. Zero net new StepKinds. The kernel never sees these files.
Acceptance evidence
f.slack.post('#test', 'hi') — typechecks (regression against B's shape).
f.slack.post(42, 'hi') — TS compile error, channel requires string.
f.github.createIssue({ title: 'x', body: 'y', repo: 'foo/bar' }) —
typechecks; a wrong key (e.g. bodyy) is a compile error.
f.notion.appendBlock — typechecks against notion's mapping YAML; a wrong
key is a compile error.
f.linear.createIssue — typechecks; wrong key = compile error.
f.stripe.createInvoice — typechecks (uniform generated shape, since
stripe has no ergonomic hand-tuned client in relay-helpers).
f.notarealprovider.anything — TS compile error, notarealprovider is not
a key on the generated helper union.
- Snapshot-test: the checked-in
packages/surface/src/helpers/{slack,github,notion,linear,stripe}.ts
files match what a fresh scripts/generate-helpers.mjs produces (drift
guard).
- YAML dialect is unchanged in this PR (declared out of scope; slice B and
the runtime dispatcher own the YAML expansion story).
packages/sdk and packages/surface typecheck + tests remain green.
Non-goals
- Runtime helper dispatch changes — B (PR#314) already ships that path; N only
adds types.
- YAML dialect helper verbs (
slack:, notion: compile-time expansion in
YAML) — later slice.
- All 51 providers in this PR. Ship the pattern with five:
slack,
github, notion, linear, stripe. The generator must be complete
enough to emit the remaining 46 in a follow-up PR without further design.
- Auto-regenerating on every relayfile bump — a manual
npm run gen
invocation plus CI drift-check is enough. A cron/hook is a follow-up.
- Provider auth / mount discovery — helpers stay declarative; the runtime
path (B) already resolves credentials at preflight.
Files touched
scripts/generate-helpers.mjs — new.
packages/surface/scripts/check-generated-helpers.mjs — new.
packages/surface/src/helpers/ — new dir; five generated files + an
index.ts + a README.md.
packages/surface/src/context.ts — extend Ctx with helper namespaces.
packages/surface/package.json — add the gen script; add
@relayfile/adapter-core (or the specific mapping-consuming dep) as a
devDependency scoped to codegen only.
packages/surface/tests/helpers-typecheck-pass.test-d.ts — new
typecheck-only test asserting the pass cases.
packages/surface/tests/helpers-typecheck-fail.test-d.ts — new
typecheck-only test asserting the fail cases via // @ts-expect-error.
packages/surface/tests/helpers.snapshot.test.ts — snapshot test for the
generated file contents.
docs/SURFACE.md — a short paragraph in §2 rule 3 pointing at the generated
index.
Files to read for context
docs/SURFACE.md §2 rule 3 (line 65-82).
packages/surface/src/context.ts, packages/surface/src/slack.ts (post-B).
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/{slack,github,notion,linear,stripe}/ —
mapping.yaml + discovery/ + src/.
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/relay-helpers/src/{slack,github,linear,telegram,reddit}.ts —
the bespoke ergonomic clients whose argument shapes should be preserved.
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/relay-helpers/src/generated/clients.ts —
the uniform generated shape and header format we should mirror.
packages/sdk/scripts/make-cli-executable.mjs,
scripts/pack-release.mjs — existing repo codegen conventions.
Test plan
- Fresh regen of the five providers is byte-identical to what's checked in
(snapshot test + CI drift check).
packages/surface: bun run test, bun run typecheck,
bun run typecheck:regressions all green.
packages/sdk: npm run typecheck, npm run typecheck:tests,
./node_modules/.bin/vitest run tests/authored-flow.test.ts green (B's
runtime path is unaffected).
- Kernel workspace unchanged.
Dependency ordering
- B (PR#314) is merged. Nothing else blocks this.
- N does not conflict with the other open spec PRs (A/C/E/G) because the
helpers/ directory is net-new. C's f.mcp work touches Ctx; a small
merge on context.ts will be needed if C lands first.
PR
- Title:
feat(surface): typed helper namespaces via codegen from relayfile adapters (#<issue>)
- Base:
main.
- Push-only pattern (gh CLI is 401 on finn-mini): agent pushes the branch
over the SSH remote and DMs N pushed sha=<HEAD> to the lead; the lead
opens the PR from a laptop.
Typed helper-namespace codegen from relayfile adapter manifests
Problem
Slice B (merged as PR#314) established the runtime pattern for
f.slack— ahelper namespace on
Ctxthat lowers each verb to an existing agent+effectkernel primitive. It ships one canonical provider, and the argument types on
that provider are hand-written in
packages/surface/src/slack.ts.For the other 50 providers to inherit the same "wrong-channel-name is a compile
error" ergonomics, each helper's TypeScript surface has to reflect the real
verb signatures of its adapter. Hand-writing 50 of them is not the plan, and
without types every
f.notion.appendBlock({ paret: … })typo is a runtimecrash instead of a red squiggle.
Scope
Build-time codegen. Every generated file is types-only sugar; runtime dispatch
still flows through B's
slack.ts-shape helper.Deliverables:
scripts/generate-helpers.mjs— walks/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/*/and for each provider consumes:
<provider>.mapping.yamlwhen present (github, notion — the canonicalmapping form) for the write-path verbs;
discovery/when present (slack, github, linear, notion) forresource-level read/list surfaces;
relayfile-adapters/packages/relay-helpers/src/<provider>.tsfor thetop-provider set that already has one (slack, github, linear, telegram,
reddit) — reused verbatim so we don't regress on their bespoke argument
shapes;
relay-helpers/src/generated/clients.ts(providerClient<'<name>'>).Output:
packages/surface/src/helpers/<provider>.ts— one file perprovider, exporting a typed helper class/interface plus the verb signatures.
packages/surface/src/helpers/index.ts— re-exports every generated helperand a
Helperstype union thatCtxwidens against.packages/surface/src/context.ts— the existingCtxgains a generatedindex-signature over declared helpers, so
f.<provider>resolves and istype-checked. Runtime lookup remains B's dispatcher.
Regeneration guard:
packages/surface/scripts/check-generated-helpers.mjsis added to the surface package's
bun run typecheck:regressionschain sodrift between the checked-in generated files and a fresh regen fails CI.
Docs — a short
packages/surface/src/helpers/README.mdexplaining"generated, do not edit by hand; re-run
npm run gen -w @relayflows/surfaceafter bumping the adapter version".Zero runtime cost. Zero net new StepKinds. The kernel never sees these files.
Acceptance evidence
f.slack.post('#test', 'hi')— typechecks (regression against B's shape).f.slack.post(42, 'hi')— TS compile error,channelrequiresstring.f.github.createIssue({ title: 'x', body: 'y', repo: 'foo/bar' })—typechecks; a wrong key (e.g.
bodyy) is a compile error.f.notion.appendBlock— typechecks against notion's mapping YAML; a wrongkey is a compile error.
f.linear.createIssue— typechecks; wrong key = compile error.f.stripe.createInvoice— typechecks (uniform generated shape, sincestripe has no ergonomic hand-tuned client in relay-helpers).
f.notarealprovider.anything— TS compile error,notarealprovideris nota key on the generated helper union.
packages/surface/src/helpers/{slack,github,notion,linear,stripe}.tsfiles match what a fresh
scripts/generate-helpers.mjsproduces (driftguard).
the runtime dispatcher own the YAML expansion story).
packages/sdkandpackages/surfacetypecheck + tests remain green.Non-goals
adds types.
slack:,notion:compile-time expansion inYAML) — later slice.
slack,github,notion,linear,stripe. The generator must be completeenough to emit the remaining 46 in a follow-up PR without further design.
npm run geninvocation plus CI drift-check is enough. A cron/hook is a follow-up.
path (B) already resolves credentials at preflight.
Files touched
scripts/generate-helpers.mjs— new.packages/surface/scripts/check-generated-helpers.mjs— new.packages/surface/src/helpers/— new dir; five generated files + anindex.ts+ aREADME.md.packages/surface/src/context.ts— extendCtxwith helper namespaces.packages/surface/package.json— add thegenscript; add@relayfile/adapter-core(or the specific mapping-consuming dep) as adevDependency scoped to codegen only.
packages/surface/tests/helpers-typecheck-pass.test-d.ts— newtypecheck-only test asserting the pass cases.
packages/surface/tests/helpers-typecheck-fail.test-d.ts— newtypecheck-only test asserting the fail cases via
// @ts-expect-error.packages/surface/tests/helpers.snapshot.test.ts— snapshot test for thegenerated file contents.
docs/SURFACE.md— a short paragraph in §2 rule 3 pointing at the generatedindex.
Files to read for context
docs/SURFACE.md§2 rule 3 (line 65-82).packages/surface/src/context.ts,packages/surface/src/slack.ts(post-B)./Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/{slack,github,notion,linear,stripe}/—mapping.yaml + discovery/ + src/.
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/relay-helpers/src/{slack,github,linear,telegram,reddit}.ts—the bespoke ergonomic clients whose argument shapes should be preserved.
/Users/khaliqgant/Projects/AgentWorkforce/relayfile-adapters/packages/relay-helpers/src/generated/clients.ts—the uniform generated shape and header format we should mirror.
packages/sdk/scripts/make-cli-executable.mjs,scripts/pack-release.mjs— existing repo codegen conventions.Test plan
(snapshot test + CI drift check).
packages/surface:bun run test,bun run typecheck,bun run typecheck:regressionsall green.packages/sdk:npm run typecheck,npm run typecheck:tests,./node_modules/.bin/vitest run tests/authored-flow.test.tsgreen (B'sruntime path is unaffected).
Dependency ordering
helpers/ directory is net-new. C's
f.mcpwork touchesCtx; a smallmerge on
context.tswill be needed if C lands first.PR
feat(surface): typed helper namespaces via codegen from relayfile adapters (#<issue>)main.over the SSH remote and DMs
N pushed sha=<HEAD>to the lead; the leadopens the PR from a laptop.