Add tooling/react/api-workflow-builder — API playground - #27
Add tooling/react/api-workflow-builder — API playground#27Carlos Hebbinghaus (Chebbbing) wants to merge 3 commits into
Conversation
A dev tool for hand-testing the Corti API — schema-driven endpoint forms with a multi-profile OAuth token cache, plus a React Flow workflow builder that chains endpoint calls end-to-end (with runtime inputs, live audio recording for stream/transcribe WSS, and per-run history). Contents: - tooling/react/api-workflow-builder/ full project (Vite + React + TS + Tailwind + Express token proxy) - README.md new "Tooling" section + table row - .github/workflows/sdk-examples-ci.yml filter + matrix entry so lint + build run on PRs Introduces a new top-level "tooling/" category for developer tools that don't fit the product-integration examples (agents, ambient, dictation, embedded-assistant, proxy, sdk).
| data = text; | ||
| } | ||
| if (!r.ok) return res.status(r.status).json({ error: data, tokenUrl }); | ||
| res.json({ |
There was a problem hiding this comment.
Blocker: This route returns an all-purpose accessToken to the browser (client_credentials with only scope: openid). That token grants broad tenant API access, and the frontend caches it on the profile and sends it on every REST/WebSocket call.
Why it matters: If anything reads localStorage or DevTools network traffic (XSS, shared machine, extension), the whole tenant is exposed — not just one feature. Our examples only allow browser tokens when they are scoped or when there are explicit inline warnings that this is demo-only bad practice.
How to fix: Either (a) mint scoped tokens server-side — see ambient/typescript/basic-example/server.ts (scopes: ["streams"]) and dictation/typescript/basic-example/server.ts — or (b) proxy Corti REST on Express and keep tokens server-side (sdk/typescript/express-web-api/). If you keep browser tokens for this playground, add prominent warnings here and in ProfilesContext.tsx / requestExecutor.ts, modeled on sdk/typescript/next-auth-examples/README.md § Security notice.
| return res.status(400).json({ error: "clientId and clientSecret are required" }); | ||
| } | ||
|
|
||
| const tokenUrl = `https://auth.${region}.corti.app/realms/${tenant}/protocol/openid-connect/token`; |
There was a problem hiding this comment.
Should fix: Token minting uses raw fetch to auth.{region}.corti.app instead of @corti/sdk.
Why it matters: Every other server example in this repo uses CortiAuth.getToken() — it handles auth URLs, response parsing, and errors consistently, and makes scoped tokens straightforward later.
How to fix: Add "@corti/sdk": "latest" to package.json, create a small server/corti.ts (mirror sdk/typescript/express-web-api/src/lib/corti.ts), and replace this block with:
const cortiAuth = new CortiAuth({ tenantName: tenant, environment: region });
const data = await cortiAuth.getToken({ clientId, clientSecret });See sdk/typescript/express-web-api/src/routes/token.ts.
|
|
||
| app.get("/api/auth/env", (_req, res) => { | ||
| const env = readEnv(); | ||
| res.json({ |
There was a problem hiding this comment.
Blocker: GET /api/auth/env returns clientId, clientSecret, region, and tenant to any caller with no authentication.
Why it matters: This exists to power the "Import from .env" button, but exporting long-lived secrets over HTTP is a credential-exposure pattern we flag in examples.
How to fix (recommended credential flow):
- Change this endpoint to a status check only — safe to call on every page load:
Do not return
{ hasCredentials: true, region: "eu", tenant: "base" } // or { hasCredentials: false }
clientId/clientSecretin JSON. - On startup,
ProfilesPage/ProfilesProvidercalls this once. IfhasCredentials: trueand no profiles exist → auto-create the default profile (no import button). IfhasCredentials: false→ empty state prompts manual paste via + New profile. - When env is present,
POST /api/auth/tokenmints usingreadEnv()without the browser sending secrets. Manual profiles (fallback) still send credentials in the POST body.
Model: sdk/typescript/express-web-api/src/lib/corti.ts (secrets stay server-side).
| region: Region; | ||
| tenant: string; | ||
| clientId: string; | ||
| clientSecret: string; |
There was a problem hiding this comment.
Blocker: Profiles include clientSecret and are serialized to localStorage via profiles/storage.ts.
Why it matters: Long-lived client secrets in browser storage are never acceptable in production and are flagged in Corti example contributions — they survive page reloads and are readable by any script on the origin.
How to fix: For the default .env path, secrets should never reach the browser — server reads .env, status endpoint returns only hasCredentials, and token minting uses server-side readEnv(). Browser profiles for env-backed setup hold only non-secret metadata (region, tenant, name). Manual paste (when .env is absent) may still need clientSecret in the profile form, but prefer not persisting it in localStorage — send to POST /api/auth/token at mint time only. Add a Security notice in the README modeled on sdk/typescript/next-auth-examples/README.md if any client-side secret storage remains for the fallback path.
| const expiresAt = Date.now() + (res.expiresIn ?? 300) * 1000; | ||
| setProfiles((cur) => | ||
| cur.map((p) => | ||
| p.id === id ? { ...p, cachedToken: res.accessToken, tokenExpiresAt: expiresAt } : p, |
There was a problem hiding this comment.
Blocker: cachedToken (all-purpose access token) is stored on the profile and persisted through saveProfiles → localStorage.
Why it matters: Same as the server token route — this is a full-tenant credential in client-accessible storage, used for all subsequent API calls.
How to fix: Add explicit inline warnings at this handoff that the token has full tenant access and must never be exposed in production (use scoped tokens or server-side proxy). Better: don't persist tokens client-side; keep auth on the Express dev server. Compliant patterns: ambient/typescript/web-component/server.ts, sdk/typescript/express-web-api/.
| @@ -0,0 +1,311 @@ | |||
| // Metadata-driven definition of every Corti endpoint we expose in the catalog. | |||
There was a problem hiding this comment.
Consider (optional — not required for merge): Endpoint request/response shapes are defined entirely in-repo (src/endpoints/*.ts + this file). That's a lot to keep in sync when the API evolves.
Idea worth trying: import types from @corti/sdk for the fields this playground needs (create interaction body, document response, etc.) instead of hand-writing every BodyField / ResponseField. The SDK also carries validation schemas internally — they may not all be public/exportable today, but it's worth checking what's available and wiring what you can. Even partial adoption means API changes flow from SDK version bumps rather than manual edits across ~12 endpoint modules.
Raw fetch in requestExecutor.ts can stay for sending requests; the win here is not duplicating the schema layer. See how sdk/typescript/express-web-api/ leans on SDK types for payloads. No change needed for merge if full manual schemas are intentional.
|
|
||
| # --- Region + tenant --------------------------------------------------------- | ||
| # CORTI_ENVIRONMENT_ID: eu | us (defaults to eu when unset) | ||
| CORTI_ENVIRONMENT_ID=eu |
There was a problem hiding this comment.
Should fix: This project uses CORTI_ENVIRONMENT_ID, but sibling examples use CORTI_ENVIRONMENT (see sdk/typescript/express-web-api/.env.example).
Why it matters: Inconsistent env names break copy-paste between examples and shared tooling/docs.
How to fix: Rename to CORTI_ENVIRONMENT=eu in .env.example, server/index.ts readEnv(), and the README. You can keep reading the old name as a fallback during transition if needed.
|
|
||
| ### .env (optional) | ||
|
|
||
| Set these in the repo root as `.env` if you want one-click profile seeding: |
There was a problem hiding this comment.
Should fix: Two issues with the current credential docs/UX:
- Wrong path — this says repo root, but
server/index.tsreads.envfrom the project working directory. Credentials belong intooling/react/api-workflow-builder/.env(next to.env.example). - Wrong flow — remove the "Import from .env" button and document auto-load instead:
- Copy
.env.example→.envin this directory, fill in values, runnpm run dev→ app auto-detects credentials on startup (no extra click). - If
.envis missing/empty → server returns{ hasCredentials: false }and the Profiles empty state says to create a profile and paste credentials manually (+ New profile).
- Copy
Why it matters: Contributors expect the same flow as sdk/typescript/express-web-api/ — copy env file, run dev, it works.
How to fix: Rewrite First run / .env sections around auto-load + paste fallback. Remove all "Import from .env" references.
| - **Files can't persist across sessions.** File uploads live in an in-memory ref for a single page session. If you reload, re-attach the file (or use the runtime input modal on each Run). | ||
| - **Sequential execution only.** No retries, branches, per-node loops, or parallelism yet. | ||
| - **No automated tests.** This is a personal dev tool; verify by running the app. | ||
| - **`/api/auth/env` returns credentials to the browser.** Intentional — it's how the Import-from-.env button works — but a reminder that the Express layer is a dev convenience, not a production API. Don't deploy it. |
There was a problem hiding this comment.
Should fix: Update this section alongside the credential-flow change:
- Remove the
/api/auth/envreturns credentials bullet (that endpoint should become status-only). - Add a prominent
## ⚠️ Security noticenear the top of the README (not buried here), modeled onsdk/typescript/next-auth-examples/README.md— cover all-purpose tokens in the browser, anylocalStoragesecret storage on the manual fallback path, and that the Express server is dev-only (never deploy). - Document:
.envcredentials are auto-used on startup; manual paste is only the fallback when.envis absent.
| @@ -0,0 +1,41 @@ | |||
| { | |||
| "name": "corti-playground", | |||
There was a problem hiding this comment.
Nit (optional — not required for merge): name is corti-playground while the directory and CI project are api-workflow-builder.
Why it matters: Minor friction when searching node_modules or correlating logs with the repo path.
How to fix: Align to something like api-workflow-builder if you care — merge does not depend on this.
|
These comments were written with AI assistance and validated by a person. They may still be more robust than you expect — please ask if anything seems off or unclear. Thanks for the contribution — strong dev tool, close to merge-ready. Summary mirrors inline comments only. Blockers
Should fix
Consider (optional) — Import SDK types/schemas for endpoint defs instead of hand-maintaining all of Nits (optional) — |
There was a problem hiding this comment.
Pull request overview
Adds a new tooling/react/api-workflow-builder Vite/React dev tool (“API playground”) for hand-testing the Corti API, including schema-driven endpoint forms, multi-profile OAuth token minting/caching, and a React Flow workflow runner for chaining calls (plus Streams/Transcribe WSS runners). This also integrates the new tool into the repo’s top-level README and CI so it builds/lints when the folder changes.
Changes:
- Introduces a full standalone React/Vite app under
tooling/react/api-workflow-builder/with endpoint registry/types, request executor, profiles + workflows persistence, and UI components. - Adds a small Express dev proxy to mint OAuth tokens (to avoid browser CORS to the auth endpoint).
- Updates repo docs + GitHub Actions matrix/path filters to include the new tooling project.
Reviewed changes
Copilot reviewed 74 out of 75 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tooling/react/api-workflow-builder/vite.config.ts | Vite config + dev proxies for local token server and Corti REST (EU/US). |
| tooling/react/api-workflow-builder/tsconfig.json | TypeScript configuration for the new tooling app (src + server). |
| tooling/react/api-workflow-builder/tailwind.config.js | Tailwind theme tokens and content scanning configuration. |
| tooling/react/api-workflow-builder/src/workflows/types.ts | Workflow graph/run/result/error type definitions. |
| tooling/react/api-workflow-builder/src/workflows/storage.ts | Workflow persistence (localStorage). |
| tooling/react/api-workflow-builder/src/workflows/runtimeInputs.ts | Runtime input “ask” gathering + application into per-run snapshot. |
| tooling/react/api-workflow-builder/src/workflows/RunHistoryModal.tsx | UI for viewing past workflow runs from localStorage. |
| tooling/react/api-workflow-builder/src/workflows/refs.ts | Node ref/slug generation + upstream node discovery helpers. |
| tooling/react/api-workflow-builder/src/workflows/PreRunModal.tsx | UI modal for collecting runtime workflow inputs/files. |
| tooling/react/api-workflow-builder/src/workflows/NodeEditor.tsx | Workflow node editor panel + upstream field reference picker. |
| tooling/react/api-workflow-builder/src/workflows/history.ts | Run history persistence + shaping WorkflowRun into UI-friendly entries. |
| tooling/react/api-workflow-builder/src/workflows/errors.ts | Error redaction + “copy diagnostic blob” helpers. |
| tooling/react/api-workflow-builder/src/workflows/defaults.ts | Placeholder for seeded starter workflows + seed marker key. |
| tooling/react/api-workflow-builder/src/workflows/context.tsx | Workflows provider: CRUD + seeding + ref backfill + persistence. |
| tooling/react/api-workflow-builder/src/styles/index.css | Tailwind base stylesheet + base UI styling. |
| tooling/react/api-workflow-builder/src/streams/StreamsClient.ts | WebSocket client for /streams + URL helpers. |
| tooling/react/api-workflow-builder/src/streams/audio.ts | Mic/file audio capture + streaming helpers (MediaRecorder + PCM file streaming). |
| tooling/react/api-workflow-builder/src/profiles/types.ts | Profile/region types + REST/WSS base URL helpers. |
| tooling/react/api-workflow-builder/src/profiles/storage.ts | Profile persistence + active profile tracking (localStorage). |
| tooling/react/api-workflow-builder/src/pages/WorkflowsListPage.tsx | Workflows list UI + create/delete + export-all-to-clipboard. |
| tooling/react/api-workflow-builder/src/pages/ProfilesPage.tsx | Profiles list UI + import-from-.env entry point. |
| tooling/react/api-workflow-builder/src/pages/ProfileEditPage.tsx | Profile create/edit UI + “test mint token”. |
| tooling/react/api-workflow-builder/src/pages/EndpointsLayout.tsx | Two-pane endpoints layout (sidebar + content). |
| tooling/react/api-workflow-builder/src/pages/EndpointsCatalog.tsx | Endpoints landing/catalog page and empty state. |
| tooling/react/api-workflow-builder/src/pages/EndpointPage.tsx | Per-endpoint page; routes WSS endpoints to dedicated runners. |
| tooling/react/api-workflow-builder/src/main.tsx | App bootstrap + router + providers + CSS import. |
| tooling/react/api-workflow-builder/src/lib/requestExecutor.ts | Request preview builder + fetch executor + Vite proxy URL rewriting. |
| tooling/react/api-workflow-builder/src/lib/authApi.ts | Browser client for the local Express auth endpoints. |
| tooling/react/api-workflow-builder/src/endpoints/types.ts | Endpoint schema/types (params/body/pickers/response schema) + empty-values helper. |
| tooling/react/api-workflow-builder/src/endpoints/transcripts.ts | Transcripts endpoint definitions + pickers + response schema. |
| tooling/react/api-workflow-builder/src/endpoints/transcribe.ts | Transcribe WSS endpoint definition + config schema + response schema. |
| tooling/react/api-workflow-builder/src/endpoints/streams.ts | Streams WSS endpoint definition + config schema + response schema. |
| tooling/react/api-workflow-builder/src/endpoints/registry.ts | Central endpoint registry (groups + by-id map). |
| tooling/react/api-workflow-builder/src/endpoints/recordings.ts | Recordings endpoint definitions + pickers. |
| tooling/react/api-workflow-builder/src/endpoints/languages.ts | Supported language list + labels for enum pickers. |
| tooling/react/api-workflow-builder/src/endpoints/interactions.ts | Interactions endpoint definitions + interaction picker. |
| tooling/react/api-workflow-builder/src/endpoints/facts.ts | Facts endpoint definitions + fact picker + “fact groups” endpoint guess. |
| tooling/react/api-workflow-builder/src/endpoints/documents.ts | Documents endpoints + transcript import + preSendTransform segment spreading. |
| tooling/react/api-workflow-builder/src/endpoints/codes.ts | Coding endpoint + document-id body-mode picker + preSendTransform wrapping system. |
| tooling/react/api-workflow-builder/src/context/ProfilesContext.tsx | Profiles provider: CRUD + active selection + token mint/caching. |
| tooling/react/api-workflow-builder/src/components/ui/Pill.tsx | Pill UI component. |
| tooling/react/api-workflow-builder/src/components/ui/Modal.tsx | Modal UI component. |
| tooling/react/api-workflow-builder/src/components/ui/Input.tsx | Input/Textarea/Label/Select UI components. |
| tooling/react/api-workflow-builder/src/components/ui/Card.tsx | Card UI component. |
| tooling/react/api-workflow-builder/src/components/ui/Button.tsx | Button UI component with variants. |
| tooling/react/api-workflow-builder/src/components/TopBar.tsx | App top navigation + active profile selector. |
| tooling/react/api-workflow-builder/src/components/RequestRunner.tsx | Endpoint execution UI (preview, send, response/error panels). |
| tooling/react/api-workflow-builder/src/components/MultiPicker.tsx | Multi-select picker for schema array fields (fetch + static merge). |
| tooling/react/api-workflow-builder/src/components/JsonEditor.tsx | CodeMirror JSON editor wrapper. |
| tooling/react/api-workflow-builder/src/components/EndpointSidebar.tsx | Route-level endpoint sidebar wrapper. |
| tooling/react/api-workflow-builder/src/components/EndpointPicker.tsx | Searchable/grouped endpoint picker used by sidebar/workflow UI. |
| tooling/react/api-workflow-builder/src/App.tsx | Route wiring for Endpoints/Profiles/Workflows pages + layout selection. |
| tooling/react/api-workflow-builder/server/index.ts | Express dev server for /api/auth/env + /api/auth/token + /api/health. |
| tooling/react/api-workflow-builder/README.md | Project documentation (setup, usage, endpoint/workflow authoring). |
| tooling/react/api-workflow-builder/postcss.config.js | PostCSS config for Tailwind. |
| tooling/react/api-workflow-builder/package.json | Project manifest (deps, dev scripts, lint/build). |
| tooling/react/api-workflow-builder/index.html | Vite entry HTML. |
| tooling/react/api-workflow-builder/biome.json | Biome formatting/lint configuration for the tooling project. |
| tooling/react/api-workflow-builder/.gitignore | Tooling-project ignores (.env, build artifacts, local state). |
| tooling/react/api-workflow-builder/.env.example | Placeholder env template for optional profile import. |
| README.md | Adds “Tooling” section/table entry linking to the new playground. |
| .github/workflows/sdk-examples-ci.yml | Adds path filter + matrix entry so CI runs for this tooling project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <PreRunModal | ||
| open={pendingAsks !== null} | ||
| asks={pendingAsks ?? []} | ||
| onCancel={() => setPendingAsks(null)} | ||
| onRun={onSubmitAsks} | ||
| /> |
| /** | ||
| * Apply collected runtime inputs to produce a per-run workflow snapshot (mutates | ||
| * node values) and a per-run files dict (mutates filesRef). Returns the updated snapshot | ||
| * so the run uses the run-time values without polluting persisted state. | ||
| */ |
| configure: (proxy) => { | ||
| proxy.on("proxyReq", (proxyReq, req) => { | ||
| // Logs to the Vite terminal — shows exactly what's hitting Corti. | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[corti-eu →] ${proxyReq.method} ${proxyReq.path}`); | ||
| }); | ||
| proxy.on("proxyRes", (proxyRes, req) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[corti-eu ←] ${proxyRes.statusCode} ${req.url}`); | ||
| }); | ||
| }, |
| configure: (proxy) => { | ||
| proxy.on("proxyReq", (proxyReq, req) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[corti-us →] ${proxyReq.method} ${proxyReq.path}`); | ||
| }); | ||
| proxy.on("proxyRes", (proxyRes, req) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[corti-us ←] ${proxyRes.statusCode} ${req.url}`); | ||
| }); | ||
| }, |
| const region = String(body.region ?? "eu"); | ||
| const tenant = String(body.tenant ?? "base"); | ||
|
|
||
| if (!clientId || !clientSecret) { | ||
| return res.status(400).json({ error: "clientId and clientSecret are required" }); | ||
| } | ||
|
|
||
| const tokenUrl = `https://auth.${region}.corti.app/realms/${tenant}/protocol/openid-connect/token`; |
| 4. **Profiles → + New profile** → enter your Corti credentials. | ||
| 5. **Endpoints → List interactions → Send** — successful 200 confirms auth + REST + proxy paths are wired. | ||
| 6. **Workflows → the seeded starter workflow → Run workflow** — verifies template substitution + chained execution. |
| 4. Go to **Endpoints**, click any endpoint, fill in params/body, hit **Send**. Tokens mint lazily on first request and cache on the profile until expiry. | ||
| 5. **Workflows** ships with a few starter workflows so you can see a full chain from the outset — click any of them to inspect. **+ New workflow** to build your own; drag endpoints from the left panel onto the canvas, wire them from right handle to left handle, edit each node in the right panel, hit **Run workflow**. |
Fills the empty DEFAULT_WORKFLOWS placeholder with three ready-to-run starters and bumps SEED_MARKER_KEY to .v2 so existing installs pick them up. Tenant-specific IDs sanitised (assignedUserId runtime-prompted, patient.identifier + messageId auto-generated per run).
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Summary
Adds a new
tooling/category and a first example under it:tooling/react/api-workflow-builder/— a dev tool for hand-testing the Corti API.What it does
{region, tenant, clientId, clientSecret}bundles){{ref.field}}templates, one-click runWhy a new
tooling/categoryNone of the existing categories (
agents/,ambient/,dictation/,embedded-assistant/,proxy/,sdk/) fit — this isn't a product-integration example, it's a developer tool. Followed Natalia's guidance to invent a new one when nothing fits.What's in the PR
tooling/react/api-workflow-builder/— full project (Vite + React 18 + TS + Tailwind + tiny Express token-mint proxy).env.examplewith placeholders only — real credentials live in browser localStorage per-profileREADME.mdwith install, run, and how-to-test sectionsbiome.jsonmatchingagents/react/next-agent-chat's configREADME.md— new "Tooling" bullet in Use Cases + a "### Tooling" table row.github/workflows/sdk-examples-ci.yml— filter + matrix entry sonpm ci → lint → buildruns on PRs that touch this folderTest plan
npm ciintooling/react/api-workflow-builder/— clean installnpm run lint— Biome exits 0 (6 warnings, 5 infos, no errors)npm run build— TS + Vite build producesdist/npm run dev— Vite (5173) + Express token proxy (5174) boot; profile creation + endpoint send + workflow run all work end-to-endreact-api-workflow-builderon this PRNotes
git log -p -Sagainst the source repo before copying.envis gitignored; credentials never leave the local machine + browser🤖 Generated with Claude Code