mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
nathanc/shared-queue-http-handler
26 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
264ddff67b |
Add WebSocket transport for step-execution event writes (opt-in) (#3084)
* sdk side for workflow server websockets Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * hardcoded workflow server Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * debug info Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * more debug Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * remove unnecessary debug Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * default on websockets, and override url Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fix for missing funcs Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * make websockets opt outo Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * enable ws again Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * [revert later] reduce test to single test, test both http and ws at the same time Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * empty Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Run full suite with and without ws Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * empty Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * improve e2e test Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * minimize tests Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fallback to http when proxy present Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * default to websockets, remove matrix Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * remove smoke test Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * update to new protocol Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * adjust for new protocol (runid in path) Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fix ws transport error Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * blank Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fix ws dep Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fix ws external: only accelerators, not ws itself Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * add dedicated WS-transport e2e job; flip WS default back to opt-in Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * build all packages before local vercel build (needs workflow/nitro on disk) Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * force NITRO_PRESET=vercel for the local vercel build step Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * install vercel CLI once instead of npx-ing it per command Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * add changeset for WS events transport Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Harden the WS events transport and gate its e2e jobs Follow-ups from review of the WS transport. CI: `e2e-vercel-ws-transport` was wired into the `summary` job but not into `e2e-required-check`, so all three WS jobs could fail while the required check stayed green. Added to both branches of the status validation — including the `workflow-server-test` label branch, where the job runs under the same gating as `e2e-vercel-prod`. Transport: - `reqId` and the pending-reply map are now per connection rather than per transport. The protocol defines `reqId` as a per-connection counter, so a reconnected socket restarts at 1; with one shared map that collided with the previous socket's still-registered waiters. It also makes the superseded-socket guard structural instead of something the close path has to remember. - Post-open socket errors are no longer silent. The only `'error'` listener closed over the connect promise's `reject`, already settled once `'open'` fired, so every broken pipe / 1009 / protocol fault was swallowed and its requests hung with no per-request timeout to save them. Now logged, and the connection is torn down. - An unexpected close reconnects eagerly instead of waiting for the next write, since a socket breaking mid-run means more writes are coming. Bounded by exponential backoff, an attempt cap that falls back to lazy reconnect, a bail-out when a newer socket is already live, and an `unref()`ed timer so a backoff window can't delay handler exit. - `ws.send()` failures reject their request. `send()` doesn't throw on a non-OPEN socket — it reports through a callback we weren't passing — so the request just sat in `pending` forever. - The reserved `reqId: -1` malformed-frame reply and undecodable frames are logged loudly instead of dropped. - Auth headers resolve once per socket via a thunk, not once per event. The bearer only rides the upgrade, so the old code awaited `getVercelOidcToken()` on every write and discarded all but the first. Re-resolving on reconnect also means a new socket gets a fresh token. Adapter: a reply with no numeric status now fails closed. Defaulting to 200 reported a write as applied whenever the client met a frame it didn't understand — and the protocol is explicitly designed to grow new response variants. Tests: 24 new unit tests over the paths the e2e suite can't reach on demand (send failure mid-flight, error after open, late close from a superseded socket, reconnect backoff and give-up, sentinel/undecodable frame logging, one-token-per-socket) plus the adapter's fail-closed and typed-error mapping. Co-Authored-By: Shalabh Chaturvedi <shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * ci: re-trigger to confirm the prior e2e failures were flake No code change. The 5 failures on 0bb21e7 clustered in a ~20s window across HTTP-path jobs (example/nuxt on the same test, sveltekit on a timeout) and one WS job (sleepingWorkflow's clock-skew assertion), which points at the environment rather than the transport changes. Re-running to confirm. Co-Authored-By: Shalabh Chaturvedi <shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Add WS wire-contract conformance tests and pin the transport gate Three gaps in the existing coverage. **The HTTP path was already covered** — `events-v4.test.ts` has 22 tests, including five directly on `createWorkflowRunEventV4` over HTTP (alias URL, frame meta contents, response decoding, skipPreload/stateUpdatedAt forwarding). Those run with the gate unset, so they do confirm the two-branch refactor didn't disturb HTTP. No new tests needed there. **But nothing pinned the gate itself.** Every HTTP assertion stays green if the default flips to WS, because the transports are built to be indistinguishable at the result layer — and an earlier revision of this branch did flip the default deliberately, for benchmarking. Added tests for `isWsEventsTransportEnabled()` across values, and one that drives a real HTTP request through a MockAgent while asserting the WS transport is never constructed. **Nothing verified the bytes.** `ws-transport.test.ts` replies with whatever the test hands it, which proves the client's lifecycle but not that its frames are what workflow-server accepts. That's the drift the spec doc exists to prevent, and it already happened once: event meta flat on the frame where the server wanted it nested under `event`, with both sides' tests passing. `ws-protocol-conformance.test.ts` pairs the real client stack (through `createWorkflowRunEventV4`) with a fixture mirroring the server route's per-message handling: decode one frame, validate against a local copy of `WsRequestFrameSchema`, dispatch, encode the reply the way `replyMeta` does. `experimental_upgradeWebSocket` needs a real Vercel runtime, so the socket is faked — everything above it is genuine. Covers: the frame shape the server accepts (and that `reqId`/`type`/ `runId` don't leak into the event meta), payload passthrough, exactly one frame per message, 409 → the same typed error HTTP raises, fail-closed on an unknown reply variant, and reqId correlation across concurrent writes. Plus golden byte fixtures, since the schema copy is the one thing here that can silently drift. This is the "golden-frame interop test" the server spec lists as an open gap; the matching half still needs to land in workflow-server. Verified the conformance suite is not vacuous: flattening the client's frame meta fails 5 of its tests. Co-Authored-By: Shalabh Chaturvedi <shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * match the HTTP RetryAgent's transient-failure policy on WS HTTP event writes go through an undici RetryAgent (RETRY_AGENT_OPTIONS): 5xx and transient connection errors are retried in-process, honoring Retry-After. The WS path never touches undici, so it shipped with no transient-failure handling at all — a single 503 or a mid-write reset surfaced straight to the step runtime and cost a whole step retry where HTTP would have absorbed it in milliseconds. That gap is invisible in a passing test run: writes still succeed, they just cost far more. So copy the policy rather than reinvent it — [500, 502, 503, 504] plus transport failures, undici's default backoff, Retry-After honored, and 429 deliberately excluded for the same reason RETRY_AGENT_OPTIONS excludes it (a firewall challenge this client cannot solve, which in-process retries only amplify). Adds WsTransportError so retryability is a typed property of the failure rather than something the adapter infers by string-matching. Splits resolveWsTransport()/wsReplyStatus() out of postEventFrameOverWs so the retry loop stays readable. The existing "fails closed on an error frame" test used status 500, which is now absorbed by the retry — switched to 403 so it keeps testing fail-closed rather than accidentally testing no-retry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * lazy-load `ws` so the default HTTP path never evaluates it events-v4.ts imports ws-transport.js unconditionally — the transport gate is a runtime branch, not a build-time one — so a top-level `import { WebSocket } from 'ws'` put `ws` and its optional native accelerators on the module-init path of every deployment, including the overwhelming majority that never opt in and never open a socket. Defer it to the first connect, memoized as a promise so concurrent first connects share one import. WebSocket.OPEN becomes an inlined constant so the readyState check doesn't pull the module in just to read it off the constructor. This does NOT remove the need for the bufferutil/utf-8-validate externals this branch also adds: webpack and Rollup both statically follow a dynamic import(), so the build-time story is unchanged. What it buys is that a deployment which never enables the transport never *evaluates* `ws`, so a mis-bundled accelerator can't break it. The test lives in its own file because vitest caches a vi.mock factory result for the life of the module registry — once any test in a file has connected, the factory never runs again and the counter can't distinguish "loaded lazily" from "loaded at import". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * release idle WS transports instead of renewing them forever The transports map was never pruned and WsEventsTransport had no way to close. Combined with eager reconnect that made a connection immortal by construction: the server drains at its own maxDuration and closes, the client immediately reopens, and the server pins a fresh invocation — for a run that finished long ago. A warm container ended up holding a live socket, and a live server invocation, for every runId it had ever served. workflow-server#683 already lists "one invocation stays resident per run rather than per write" as a known gap; this made it "per run, forever". Add close() plus a 60s idle release. There is no "run complete" signal to hang teardown off — the events adapter is a stateless per-write call — so idleness is the available proxy. 60s sits well below the server's ~680s drain deadline, so the client releases rather than the server reclaiming, and well above the gap between steps of an active run. scheduleReconnect() now bails when closed: close() closes the socket, which fires the same close handler an unexpected drop would, and without the guard the transport would instantly reconnect what it just released. request() revives an idle-closed transport rather than failing the write, re-registering itself only if nothing newer has claimed the map slot. Eviction therefore costs one handshake, not an error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * refresh the bearer on an auth_expiry drain workflow-server#683 tags a drain frame with why it is closing: max_duration means the socket aged out and a plain reconnect is right, auth_expiry means the *bearer* ran out and reconnecting with the same one just earns a 401. This client logged the drain and ignored the reason, so against #683 an auth_expiry drain would burn all five reconnect attempts against a token the server had already rejected, then give up. Parse the reason (absent reads as max_duration, so this stays correct against the currently-deployed server) and thread forceRefresh through the getHeaders thunk, which triggers @vercel/oidc's refresh path via a wide expirationBufferMs. Worth being precise about when that can actually help. getVercelOidcToken resolves getContext().headers['x-vercel-oidc-token'] ?? env, and refreshToken() only writes the env var — the request-context header wins. So inside a deployed function there is genuinely no fresher token mid-invocation and the refresh is a no-op; outside one (CLI, local dev, a long-lived server) it works. That makes the guard the load-bearing half: if the re-resolved bearer is byte-identical, decline to reconnect, say so, and wait for the next write — which usually arrives on a new invocation carrying a new token. That failure is marked non-retryable so the retry loop doesn't spin on it either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * document the WS path's instrumentation gap The HTTP branch goes through fetchV4 -> instrumentedFetch, which is not just a fetch wrapper: it opens the OTEL CLIENT span, injects trace context, sets the cache-bust header, emits the DEBUG logs, and routes through the global fetch that Vercel's observability "outgoing requests" view instruments. The comment on fetchV4 records why that matters — bypassing it via undici.request() is exactly what once made v4 event traffic disappear from the log viewer. The WS branch bypasses all of it. With the flag on, per-event writes have no client span, propagate no trace context to workflow-server, and don't appear in the outgoing-requests view; the server's own transport-tagged request metrics are the only remaining signal. That's acceptable for an opt-in POC behind a flag and unacceptable as a default, so write it down where someone deciding to flip the default will read it: instrumenting the transport is a prerequisite for that, not a follow-up nicety. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * ship the ws-accelerator externals instead of documenting a workaround `bufferutil` and `utf-8-validate` are optional native accelerators for `ws`, and neither is installed by default. Every bundler has to be told to leave them alone, for two different reasons: Rollup/Vite/Nitro fail the build outright (`Could not resolve "bufferutil" imported by "ws"`), while webpack bundles the JS wrapper without its native `.node` binding and throws `bufferUtil.mask is not a function` at runtime. The webpack half shipped in `@workflow/next`. The Rollup half only existed in `workbench/vite` and `workbench/tanstack-start` as `nitro.rollupConfig.external` — app configs, not shipped code. So a real user of `@workflow/vite`, `@workflow/nitro`, `@workflow/nuxt`, `@workflow/sveltekit` or `@workflow/astro` hit the same build failure the workbench had already worked around, and had to rediscover the fix. Fix it where it propagates: `workflowTransformPlugin` in `@workflow/rollup`, which all of those integrations already install. It is already the home of exactly this pattern for the optional `@opentelemetry/api` peer, so this sits next to its closest precedent. Note the treatment is deliberately the inverse of the OTEL one, which is externalized only when it *can't* be resolved. The OTEL API must load for tracing to work, so a self-contained output has to bundle it when present. These accelerators must specifically NOT load — they are a performance nicety with a correct try/catch fallback in `ws` — so unconditional external is both simpler and safer than risking a half-bundled native module. The two workbench configs drop their local copies, which is what proves the shipped fix actually works rather than being masked by them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Shalabh Chaturvedi <7066873+shalabhc@users.noreply.github.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * one retry policy for both transports, and no unanswerable waiters Two review findings on the WS events transport. **Retry belongs to `event-retry.ts`, not the adapter.** The WS path had its own retry loop, justified as mirroring undici's `RetryAgent`. That justification was wrong: `RetryHandler` defaults `methods` to GET/HEAD/ OPTIONS/PUT/DELETE/TRACE and nothing overrides it, so the `RetryAgent` never retried an event POST on either transport — which is precisely why `event-retry.ts` exists. Worse, that loop sat *inside* `withEventPostRetry`, so it defeated a compile-checked safety gate: `EVENT_RETRY_ELIGIBILITY` marks `step_started`, `step_retrying` and `hook_received` non-retryable (a replayed `step_started` double-increments `attempt`), and those frames were re-sent up to five times before the gate ever saw a failure. For eligible types the two loops multiplied: 3 outer attempts x 6 inner, with an inner backoff reaching 30s against an outer base deliberately set to 100ms. `postEventFrameOverWs` now makes one attempt and translates failures into the vocabulary that policy already speaks — a transport failure becomes a `WorkflowWorldError` with `code: 'TRANSPORT'`, exactly as `utils.ts` does for a failed `fetch`, and `isRetryableEventPostError` gains one clause keyed on that code. `WsTransportError` loses its `retryable` flag; its only consumer was the deleted loop. Two deliberate consequences. The code-keyed clause broadens HTTP in-process retry to `UND_ERR_CONNECT`, `UND_ERR_CLOSED` and `EAI_AGAIN`, which were in utils.ts's transient set but missing from event-retry.ts's — two hand-maintained lists collapsed into one semantic code. And the stale-token case (drain for auth expiry, refresh yields the same bearer) now gets two in-process attempts that cannot succeed, ~300ms before it falls through to queue redelivery; that is cheaper than keeping a WS-specific policy alive for one call site. `TIMEOUT` is deliberately not in the clause: utils.ts maps a caller-supplied `AbortError` onto it, and a cancelled write must not be re-issued. A status-less reply also stops being a bare `Error` — as one it failed `WorkflowWorldError.is()` and surfaced a protocol version skew as a USER_ERROR. It is now `code: 'PARSE_ERROR'`, the same code utils.ts uses for an unreadable HTTP body, and for the same reason: the write may or may not have landed. **No waiter is left unanswerable.** An undecodable frame, the server's malformed-frame sentinel (`reqId: -1`) and a non-numeric `reqId` were logged and dropped. None can be correlated by construction, so the request that provoked them stayed in `pending` with nothing in existence able to settle it — freed only by the server's own drain (~680s from connect), typically past the invocation's `maxDuration`. Each now fails the connection: every waiter learns why, and the socket is replaced. A reply for an id nobody is waiting on stays log-and-drop, deliberately — that request already settled, so nothing is orphaned, and failing the socket would punish healthy in-flight writes. A per-request deadline backs that up for whatever is left, including a server that accepts a frame and never answers it. Same knob as the HTTP path (`WORKFLOW_REQUEST_TIMEOUT_MS`, 60s), whose doc comment already describes this exact hang-to-SIGTERM pathology. One existing idle-teardown test needed the deadline raised: the idle window and the default deadline are both 60s, so a request could not outlive the former without also outliving the latter. The test is about `inFlight > 0` suppressing the teardown, so it now sets the deadline out of the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Open the ws socket when the invocation starts, not on its first write Lazily connecting bills the whole handshake — an upgrade round-trip plus the OIDC token mint that rides it — to whichever event a fresh invocation writes first. When that is a `step_started` issued as the step body is already running, the event's server-recorded timestamp lands later than the work it describes: the step looks shorter than it was. That is the shape of the e2e timing failure on this branch, where a 9s step measured 6.5s from `getStepMetadata().stepStartedAt`. The queue handler is the earliest point that knows the run id, and a message delivered for a run means writes are coming, so `warmWsEventsTransport` starts the handshake there. By the first write it is done or in flight, and the write just uses it. Nothing about it is load-bearing: - It doesn't await, and can't fail the handler. A warm that fails logs and stops — a never-opened first connect is precisely the case `connect`'s close handler already declines to retry, so no backoff loop starts for a run that may never write. The first real write connects as it would have anyway, carrying the shared retry policy. - No-op unless `WORKFLOW_EVENTS_TRANSPORT=ws`, and no-op for the api-workflow proxy World, which can't serve an upgrade at all — the same fallback the write path takes. - Warming arms the idle timer as if a request had settled, so an invocation that warms and never writes (a health probe carrying the run id it is about to create) releases its socket on the usual 60s rather than stranding it. The socket is not `unref`'d, so a stranded one would hold this process and a server invocation open. Also closes a race that warming makes reachable: `close()` can only drop the connection it can see, so a release landing mid-handshake left the socket to install itself afterwards onto a transport already evicted from the cache, which nothing would then ever close. The `open` handler now declines to adopt a socket whose transport was released while it was connecting. This was already reachable via the eager reconnect path, just much harder to hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * changeset: just the env var Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * inline the ws-accelerator predicate at its only call site Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * refactor(world-vercel): trim ws-transport comments Comments were 47% of the file. Cut the historical narration, the restatements of adjacent code, and the repeated rationale (the `unref` reasoning appeared four times, per-connection reqId three), keeping the non-obvious facts: `ws.send()` reports failure via callback instead of throwing, reqId is per-connection so `pending` must be too, the unknown-reqId case is deliberately non-fatal, the auth_expiry same-token bail-out, and why the idle timeout exists at all. No code changes. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * own transport selection in the transport module `events-v4.ts` was assembling the WS transport itself: reading the opt-in flag, resolving the URL, deciding which Worlds can use a socket, minting the per-connection header thunk, and holding the two once-per-process log latches. None of that is about turning an event into a frame, which is what the rest of that file does. Move it next to the socket it configures — `events-v4.ts` now consumes one seam (`resolveWsTransport`) plus the gate, and `queue.ts` gets `warmWsEventsTransport` from the module that owns the warm. `headersToRecord` now lives in `http-core.ts` because both callers need it and neither may import the other: `events-v4` already depends on the transport, so the reverse edge would be a cycle. Test fallout, and the reason the move is worth it: `events-v4-ws.test.ts` mocked `getWsEventsTransport` to observe the resolve step, which no longer intercepts anything now that the call is intra-module — an ESM mock replaces a module's exports, not its own call sites. That mock's tests were only ever about selection, so they move to `ws-transport.test.ts`, where the real selection code runs against the existing fake-socket harness instead of a stub. `resetWsEventsTransportsForTest` clears the log latches so the once-per-process assertions don't depend on test order. What stays behind mocks `resolveWsTransport` and covers what that file is actually for: reply frame in, `Response`-shaped result out — including the null-resolve fallback to HTTP, which nothing covered before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * import `ws` statically The lazy `import('ws')` was there to keep the package off the module-init path of deployments that never opt in — `events-v4.ts` imports this module unconditionally, since the transport gate is a runtime branch. Measured, that buys ~17ms: `require('ws')` is 16.5-18.0ms cold, 13 modules, and neither `bufferutil` nor `utf-8-validate` loads (optional peers, absent by default). Bundle size is identical either way — webpack and Rollup both statically follow a dynamic `import()`, which is why the externals in `@workflow/builders` are unaffected by this change. For 17ms it cost a memoized promise, an inlined `WS_READY_STATE_OPEN` (so a readyState check wouldn't force the module to load just to read a constant off the constructor), and a whole test file — `ws-transport-lazy.test.ts` had to live alone, because vitest caches a `vi.mock` factory result for the lifetime of a module registry, so only a file that connects exactly once can observe the laziness at all. It also skewed the thing this branch exists to measure. The import lands inside the first connect, so on a warm container it is billed to whichever event write opens the socket, inflating the timestamp of the step it labels — the same distortion the queue pre-warm was added to remove. Also drops `WS_READY_STATE_OPEN` in favour of `WebSocket.OPEN`, now that reading it is free. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * tighten the comments on the ws transport Comments only — no code changes in this commit. Cuts ~150 lines of prose across the WS additions. The rule applied: keep the design factors a future reader needs (why the connection is scoped to a run, why a bad reply takes the socket down, why the accelerators are externalized unconditionally, why `TIMEOUT` is excluded from the `TRANSPORT` classification) and drop the narrative of how the code got here — which revision did what, what an earlier attempt got wrong, what was measured on the way. That history lives in the PR and the git log, where it doesn't have to be re-read on every visit to the file. Biggest reductions: the retry essay above `postEventFrameOverWs` (30 lines to 11), the flag's OTEL-gap note (34 to 13), the OIDC refresh explainer (26 to 14), the accelerator rationale in `@workflow/builders` (26 to 14), and the conformance suite's header (28 to 17). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * inject W3C trace context on the ws upgrade Frames carry no headers, so the upgrade is the only place this transport can propagate context; the server parents a run's event spans to whichever invocation opened the socket. Covered in trace-propagation.test.ts, both with and without an active span. Splits the opt-in gate into an import-free ws-transport-enabled.ts so callers can answer it without loading this module (used by the next commit). Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * load the ws transport module only when it is enabled Both call sites read the gate from the import-free module and dynamically import ws-transport.js behind a true result, so a deployment on the HTTP default never pays ws's ~17ms of module init. The queue pre-warm absorbs it for one that opted in, keeping it off the first event write. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * document WORKFLOW_EVENTS_TRANSPORT as experimental Names the instrumentation gap (no client span per write) and the proxy path where the variable is ignored. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * correct why the ws accelerators are externalized No bundler fails the build on the unresolvable require — verified against Rollup 4.62. webpack half-bundles the native module and Vite substitutes a stub that makes the require succeed; both leave bufferUtil.mask undefined and throw only once a frame reaches the native masker at 48 bytes, which every CBOR event frame does. Same claim was repeated in the rollup plugin and its test. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * trim the WORKFLOW_EVENTS_TRANSPORT docs to user level Mirrors the other Vercel World env vars: same facts on both pages, each in its page's format. The instrumentation and socket-lifetime detail belongs in the code, not in a user-facing reference. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * cover the vite bundler in the ws transport lane Vite substitutes a stub for ws's absent native accelerators rather than failing the require, so nothing catches it until a masked frame reaches 48 bytes — and this job's three existing lanes are esbuild, turbopack and nitro. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * claim only what is measured about rollup and the ws accelerators The rationale asserted plain Rollup was "safe by accident" via a mechanism only ever observed in a minimal repro. Nitro traces and externalizes `ws` in a production build, so the bundled path is not reached there at all. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Give the events socket an explicit lifetime instead of an idle timer `openWsChannel` / `closeWsChannel` bracket one invocation of the flow route, and are the only calls anywhere that create a channel. Writes ask `resolveWsTransport` whether one is open — a lookup now, never a create — and take pooled HTTP when it says no. That removes the reason the idle timeout existed. A lazily-created socket has no owner, so a timer was the only thing able to end it, and the socket is not `unref`'d: the process could not exit, and a server invocation stayed pinned, for the full window past the last write. It also settles `run_created`. The trigger path opens no channel, so a lone write no longer pays for a handshake it cannot amortize — `start()` runs in an arbitrary request handler with no boundary the SDK can see. Refcounted rather than a flag: inline step executions ride the flow topic on per-step topics, so a run's steps can be concurrent invocations in one instance sharing the channel, and the first to finish must not cut the others short. A failed connect closes the channel so the invocation's writes fall back to HTTP instead of each paying its own doomed handshake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Name the one reply header the WS path does not map The server copies six headers into an `event_ack`'s meta and this record maps five. The sixth, `X-API-Deprecated`, is inert today — the v4 route's middleware chain has no deprecation middleware to set it — but the record is the only header source a WS reply has, so an unmapped key is gone rather than merely unread, which is not true of the `Response` the HTTP path returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * docs: note that WORKFLOW_EVENTS_TRANSPORT=ws is ignored on the proxy path The api-workflow proxy is an HTTP-only REST gateway and does not forward a WebSocket upgrade, so a World configured with projectConfig keeps writing events over HTTP regardless of the setting. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * ci: gate the ws-transport e2e lanes on a label Three real `vercel deploy`s per run is too much to charge every unrelated PR in the repo for a transport that is off by default. PRs opt in with `ws-transport-test` (or `workflow-server-test`, which already exists to test the half of this the protocol lives in); main keeps the signal on every commit. The required aggregate has to allow the lane to be skipped in that case, so its status is asserted only when the lane was actually supposed to run. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * chore: regenerate pnpm-lock against current main main resolved `ws` to 8.20.0 as a transitive peer; this branch adds it as a direct dependency of world-vercel and floats it forward, which rewrites every `openai@x(ws@y)` peer key in the lockfile. Merging main textually combined the two, leaving those keys pointing at a `ws` entry the merged file no longer had — `--frozen-lockfile` then failed with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY on the PR's merge ref. Regenerated from main's lockfile so ours is a minimal delta on top of it. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * fix(world-vercel): align ws on the version main already resolves The lockfile broke on the PR's merge ref, not on this branch's head: main resolves ws@8.20.0 as a transitive peer, and a `^8.21.1` direct dep here floated it forward, rewriting all 73 `(ws@8.20.0)` peer keys. Git merged the two lockfiles without a conflict but left main-side keys pointing at a ws entry the merged file no longer had, so `--frozen-lockfile` failed with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY. `^8.20.0` resolves to the copy main already has, so the lockfile delta is the two importer entries instead of a repo-wide rewrite that re-breaks every time main moves. Also keeps one ws in the store rather than two. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Bind the channel release to the instance it claimed closeWsChannel resolved the transport by URL, but the refcount lives on the instance. A channel is evicted from the map as soon as it closes — a refused upgrade does that on the connect path — so the next opener for the same run registers a different instance under the same URL, and the first invocation's close then decremented that one instead. It dropped a socket a live invocation was still writing over, and for the event types EVENT_RETRY_ELIGIBILITY marks non-retryable there is no second attempt to carry the in-flight write over HTTP. openWsChannel now returns an idempotent release closed over the transport it incremented, and queue.ts holds that instead of re-resolving the run. The close awaits the open's own promise, so it also can no longer land ahead of the claim it releases. Also names the scope of the connect-failure de-opt: it covers the handshake only, so a channel that connects and then fails every write keeps taking the WS path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Decode a transport result, not a Response Main extracted the v4 POST decode into a helper typed `Response` while this branch narrowed the POST result to `FrameResponseLike`, because the WS branch synthesizes its result rather than holding a real `Response`. The two merge without a textual conflict and then fail to typecheck. Widen the helper: it reads only the two members `FrameResponseLike` declares, and a `Response` still satisfies them, so the HTTP call sites are unchanged. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Re-run CI Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Re-run CI Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Reconcile the WS transport with main's v4 POST rework main moved the materialized POST result off the `x-wf-*` response headers and onto a typed CBOR body, and added a second response shape: two callers now POST with `Accept: application/vnd.workflow.v4-frames` and read back a sentinel-terminated sequence of frames. A frame stream has no representation in a protocol that pairs one reply frame with one request frame, so the WS switch moves off the shared poster and onto `createWorkflowRunEventV4` alone — the materialized write, which is the hot per-step path this branch exists to shorten. `run_started` and the `hook_received` preload stay on HTTP. `decodeCreateEventResponse` takes `FrameResponseLike` rather than `Response` because the WS branch has none to hand over; a real `Response` satisfies the interface, so the HTTP callers are unchanged. The ids now come out of the CBOR body, so `replyMetaToHeaderRecord` no longer maps any `x-wf-*` name — only the two headers `errorFromV4Response` reads. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Re-run CI Resample the WS-arm sleepingWorkflow failure: it has now recurred on a second axis (nextjs-turbopack, 7709ms; previously vite, 7570ms), so the arm needs more samples before the skew can be called WS-specific or repo-wide flake. Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Re-run CI Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * Re-run CI Co-Authored-By: shalabhc <shalabh.chaturvedi@vercel.com> * blank * blank --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
11dc036854 |
ci: stop deploying changeset-release/main, run its e2e against production (#3243)
* ci: stop deploying changeset-release/main, run its e2e against production The changesets action force-pushes `changeset-release/main`, and it can point at exactly main's HEAD SHA. Vercel keeps one commit status per project per SHA, so when both a production deployment (from main) and a preview deployment (from changeset-release/main) are built for the same commit, whichever finishes last owns the status. On 2026-07-30 the preview finished last, so `vercel/wait-for-deployment-action` — which reads the deployment ID out of that status — handed production e2e runs a preview deployment ID and forked runs across environments. Disable git deployments for that branch in every Vercel project rooted in this repo, and give the changeset PR's Vercel e2e lanes a deployment to test that actually exists: main's production deployment for the PR's base SHA, resolved by SHA so a mid-flight production build is waited out rather than silently replaced by an older one. Signed-off-by: Pranay Prakash <pranay.gp@gmail.com> * ci: resolve changeset-release e2e deployments with the wait action, tokenless Per review: with changeset-release/main no longer deployed, main SHAs can never again be deployed to a second environment of these projects, so the per-SHA commit status the action reads is unambiguous for exactly this lane. Reuse vercel/wait-for-deployment-action with environment: production and sha pinned to the PR base SHA instead of the Vercel-API polling script, drop the script and its VERCEL_TOKEN usage, and inherit the action's inactive/skipped-build handling. Signed-off-by: Pranay Prakash <pranay.gp@gmail.com> --------- Signed-off-by: Pranay Prakash <pranay.gp@gmail.com> |
||
|
|
a09d00135b | Revert "Statically inject workflow world target" (#2752) (#3142) | ||
|
|
421ff4f349 |
Bump e2e framework versions (#2814)
* Fix SvelteKit config loading * Bump e2e framework versions |
||
|
|
0f557d5ae4 |
Statically inject workflow world target (#2752)
* Statically inject workflow world target * Fix static world injection in host bundles * Fix static world injection gaps * Fix Vite Nitro server startup * Fix Nitro pg-native aliasing * Fix static world target CI gaps * Fix static world dev rebuild gaps * Avoid broad runtime alias in Nitro * Refresh Next dev route for step HMR * Externalize Nest target world * Use canary HMR rediscovery timeout * Bundle local world in Nest builds * Dedupe world target helpers and fix SvelteKit chunk patch guard |
||
|
|
68d225d510 | chore: ignore workflow swc caches (#2640) | ||
|
|
c1242e8dc5 |
[nitro] Use nitro v3 functionRules for workflow routes (#1575)
Signed-off-by: Peter Wielander <peter.wielander@vercel.com> Co-authored-by: Peter Wielander <mittgfu@gmail.com> Co-authored-by: Peter Wielander <peter.wielander@vercel.com> |
||
|
|
56ba32feb8 | Bump vite (#1827) | ||
|
|
cdf90d5a38 |
Rename Workflow DevKit to Workflow SDK, remove beta badge, add tweet wall (#1541)
* Rename Workflow DevKit to Workflow SDK, remove beta badge, add tweet wall - Rename "Workflow DevKit" to "Workflow SDK" across all files (~108 files) - Rename standalone "WDK" references to "Workflow SDK" - Remove beta badge from homepage hero - Add tweet wall component to homepage with 4 builder testimonials Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rename Workflow DevKit to Workflow SDK, remove beta badge, add tweet wall - Rename "Workflow DevKit" to "Workflow SDK" across all files (~108 files) - Rename standalone "WDK" references to "Workflow SDK" - Remove beta badge from homepage hero - Add tweet wall component to homepage with 4 builder testimonials Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Harpreet Arora <harpreet.txt@gmail.com> * Address review: fix missed trigger phrase renames and bump skill versions - Rename "workflow devkit" to "workflow sdk" in trigger phrases for both skill files - Bump workflow-init SKILL.md version to 1.1 - Bump workflow SKILL.md version to 1.5 - Note: CLAUDE.md is a symlink to AGENTS.md, already renamed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Harpreet Arora <harpreet.txt@gmail.com> * link correct tweet --------- Signed-off-by: Harpreet Arora <harpreet.txt@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Karthik Kalyanaraman <karthik.kalyanaraman@vercel.com> |
||
|
|
74aea7b0af |
Add DurableAgent compat tests, e2e tests, and migrate to AI SDK v6 (#1362)
* Add DurableAgent compat tests, e2e agent tests, and migrate to AI SDK v6 - Port ToolLoopAgent test suite as DurableAgent compatibility spec (34 tests, all expected to fail — each maps to a feature gap to implement) - Add e2e workflow definitions using mock LLM providers (no API keys needed) - Add e2e test file for DurableAgent workflows - Migrate all AI SDK types from V2 to V3 (LanguageModelV2 → V3, etc.) - Drop AI SDK v5 support: ai peer dep ^5||^6 → ^6, @ai-sdk/provider ^2||^3 → ^3 - Update ai catalog version from 5.0.104 to 6.0.116 - Simplify CompatibleLanguageModel to just LanguageModelV3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address PR review feedback - Remove providerExecuted guard on tool-result stream parts (V3: all tool-results are provider-executed by definition) - Remove providerExecuted spread from tool-output-available UI chunks - Replace inline MockLanguageModelV3 with import from ai/test (works without msw in AI SDK v6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove streamTextIterator mock from compat tests, use it.fails for gaps Tests now exercise the real DurableAgent code path instead of mocking the core iterator. 5 tests pass (features DurableAgent already has), 29 are marked it.fails() for known API gaps that will alert when fixed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Implement Tier 1+2 gaps, add @workflow/ai/test mock provider, wire e2e in CI DurableAgent API additions: - Add `instructions` (string | SystemModelMessage | SystemModelMessage[]) as alias for deprecated `system` on constructor - Add `onStepFinish` and `onFinish` on constructor, merged with stream options (constructor first, then stream — matching ToolLoopAgent) - Add `timeout` on stream options (converted to AbortSignal) - Add `text`, `finishReason`, `totalUsage` to onFinish event Test infrastructure: - Add @workflow/ai/test export with `mockModel()` wrapper that wraps MockLanguageModelV3 from ai/test as an async step function - E2e workflows now use mockModel() + convertArrayToReadableStream from @workflow/ai/test instead of inline V2 mock models - Add e2e-agent.test.ts to test:e2e script so it runs in CI - Flip 6 compat tests from it.fails → it (now passing) Score: 11 passing / 23 it.fails (was 5/29) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove e2e agent tests — mock models can't serialize across step boundary The workflow runtime serializes step arguments, and function closures (like mock model doStream callbacks) aren't serializable. Mock models only work in unit tests where 'use step' is a no-op. Real e2e agent tests would need either a mock HTTP server or real provider credentials. Also removes 'use step' from mockModel wrapper (closures aren't serializable) and reverts test:e2e script and example workbench dep. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add working e2e agent tests with mock model step factories Mock model factories use the same 'use step' pattern as real providers (anthropic, openai). Closure variables are bound to locals at the step body level so the SWC plugin detects them via __private_getClosureVars. All 4 e2e tests pass against local dev server: - agentBasicE2e: text response (11s) - agentToolCallE2e: single tool call + text (11s) - agentMultiStepE2e: 3 sequential tool calls (12s) - agentErrorToolE2e: FatalError recovery (11s) Also adds e2e-agent.test.ts to test:e2e script for CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use @workflow/ai/test package imports for e2e mock models Split mock provider into two files to work around SWC constructor closure bug: mock-create.ts has the model creation logic, mock.ts has the 'use step' wrappers that capture only serializable args (strings, plain object arrays). Exports mockTextModel(text) and mockSequenceModel(responses) — same 'use step' pattern as real providers (anthropic, openai, etc.). E2e workflows now import directly from @workflow/ai/test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Simplify mock provider, add comprehensive e2e tests for all features + gaps Mock provider: - Replace mock-create.ts with mock-function-wrapper.ts that simply wraps MockLanguageModelV3 constructor in a function (SWC class closure bug) - mockTextModel/mockSequenceModel use mockProvider() from wrapper file - Bind closure vars at step body level (_text = text) for SWC detection - Fix AbortController not available in workflow VM sandbox E2e tests (13 total, all passing): - Core: basic text, tool call, multi-step, error recovery (4) - Callbacks: onStepFinish constructor+stream, onFinish constructor+stream (2) - Features: instructions, timeout (2) - GAPs documented: onStart, onStepStart, onToolCallStart, onToolCallFinish, prepareCall (5 — complete but callbacks not called) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add tool approval (needsApproval) gap tests, fix SWC closure var binding Unit tests: 2 new it.fails() tests for tool approval - needsApproval: true should pause agent (pending tool call, no result) - needsApproval as function should receive tool input E2e tests: 1 new test for tool approval gap - Documents that needsApproval is currently ignored (tool executes anyway) Also fixes: - Bind closure vars at step body level in mock provider (_text = text, _responses = responses) so SWC plugin detects them - Guard AbortController usage in workflow VM (not available in sandbox) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add default args for agent e2e workflows in UI definitions The nextjs-turbopack UI calls workflows with hardcoded default args. Without these entries, agent workflows were called with no args, causing prompt=undefined → ModelMessage validation failure. Also removes debug logging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add DurableAgent chat UI with tools, update docs for AI SDK v6 Chat UI: - Tab-based layout with Workflows (existing) and DurableAgent Chat tabs - Chat powered by DurableAgent + WorkflowChatTransport + ai-elements - Tools: getWeather (fake data), calculate (math expressions) - Uses createUIMessageStreamResponse for proper stream serialization - Reconnect route at /api/chat/[runId]/stream - ai-elements components: conversation, message, prompt-input, tool - onStepFinish + onFinish callbacks with console logging Docs (AI SDK v6 migration): - system → instructions in DurableAgent constructor examples (10 places) - LanguageModelV2Prompt → LanguageModelV3Prompt in type references (3 places) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix tool rendering, add reasoning support, model picker, observability links - Fix tool part rendering: use `input`/`output` props (not `args`/`result`) and `tool-{name}` part type (AI SDK v6 format) - Add reasoning support for Opus 4.5 via providerOptions - Model picker: Haiku 4.5, Sonnet 4, Opus 4.5 (reasoning), GPT-5.2, GPT-5.3 - Fix observability links: localhost:3456 for local, Vercel dashboard for prod - Add suggestions above prompt input - Add MessageParts component handling text, tool, reasoning, step-start - Add loading spinner for submitted state - Update docs: system → instructions, LanguageModelV2Prompt → V3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix tool output rendering: use input/output props on ToolInput/ToolOutput Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix: Documentation for `PrepareStepInfo` and `PrepareStepResult` interfaces references the obsolete `LanguageModelV2` type while the codebase has fully migrated to `LanguageModelV3`. This commit fixes the issue reported at docs/content/docs/ai/message-queueing.mdx:36 **Bug explanation:** The codebase migrated from AI SDK V2 to V3. In `packages/ai/src/agent/types.ts`, `CompatibleLanguageModel` is defined as `LanguageModelV3` (from `@ai-sdk/provider`). The actual TypeScript interfaces in `packages/ai/src/agent/durable-agent.ts` use `string | (() => Promise<CompatibleLanguageModel>)` which resolves to `LanguageModelV3`. However, the documentation in `docs/content/docs/ai/message-queueing.mdx` at lines 36 and 43 still referenced `LanguageModelV2` for the `model` field in both `PrepareStepInfo` and `PrepareStepResult`. This is inconsistent because: 1. The `messages` fields in the same interfaces were correctly updated to `LanguageModelV3Prompt` 2. The actual source code uses `LanguageModelV3` via `CompatibleLanguageModel` 3. There is no `LanguageModelV2` type anywhere in the codebase This would mislead developers reading the documentation into using the wrong type. **Fix explanation:** Changed both `LanguageModelV2` references to `LanguageModelV3` on lines 36 and 43 of the documentation file, matching the actual codebase types. Verified no other stale `LanguageModelV2` references remain in the docs directory. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: pranaygp <pranay.gp@gmail.com> * Fix instructions tests: flip from it.fails to it, update snapshots The 3 instructions tests (string, SystemModelMessage, array) now pass. The snapshots include the assistant reply message from the agent loop, which is a behavioral difference from ToolLoopAgent (DurableAgent captures the prompt after the full loop iteration). Score: 14 passing / 22 it.fails (was 11/25) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix getReadable call: pass startIndex as options object Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix docs type errors and turbopack build Docs: - Add await to convertToModelMessages() calls (now async in AI SDK v6) - Change LanguageModelV3Prompt → ModelMessage[] in type references - Change LanguageModelV3 → LanguageModel in PrepareStepInfo - Update docs-globals.d.ts convertToModelMessages return type - Add LanguageModel to import inference map DurableAgent: - Update OutputSpecification to match AI SDK v6 Output interface (type→name, parsePartial→parsePartialOutput, parseOutput→parseCompleteOutput, responseFormat now PromiseLike) Turbopack build: - Remove streamdown plugins from MessageResponse (plugins prop API changed in streamdown 2.4.0, causing type mismatch in CI) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix pnpm-workspace.yaml: use double quotes for catalog entries The stage-workbench-with-tarballs.mjs script only strips double quotes when parsing catalog keys. Single-quoted @-scoped entries (e.g., '@types/node') weren't matched, causing "unresolved catalog dependencies" errors in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix CI build failures, add changeset - Remove streamdown plugins from reasoning.tsx (same CI type mismatch) - Cast ToolHeader type prop and WorkflowChatTransport to fix type errors - Fix pnpm-workspace.yaml single→double quotes for staging script - Add minor changeset for @workflow/ai (breaking: AI SDK v6 migration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Sync webpack workbench with turbopack: add chat UI deps and symlinks - Symlink app-shell.tsx, chat-client.tsx, agent_chat workflow, chat API routes into nextjs-webpack - Add matching deps: streamdown, @streamdown/*, shiki, cmdk, nanoid, motion, @radix-ui/react-use-controllable-state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix circular symlink: restore chat-client.tsx as real file in turbopack The previous commit accidentally converted turbopack's chat-client.tsx into a circular symlink pointing to itself. Webpack's symlink to it then couldn't resolve, breaking both builds on Vercel. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add missing deps to webpack: use-stick-to-bottom, radix-ui, @vercel/blob Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix model picker: merge body params via prepareSendMessagesRequest WorkflowChatTransport sends { messages } by default, ignoring the body option from ChatRequestOptions. Use prepareSendMessagesRequest to merge { messages, ...body } so the model selection reaches the API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix WorkflowChatTransport: forward body/headers from ChatRequestOptions The transport hardcoded body: undefined when calling prepareSendMessagesRequest, so extra body params (like model selection) from sendMessage({ body: { model } }) were silently dropped. Now forwards options.body and options.headers to both prepareSendMessagesRequest and the default request body. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix model IDs: use real AI Gateway model names gpt-5.2 and gpt-5.3 don't exist in the AI Gateway. Replace with gpt-4o and gpt-4o-mini. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use correct AI Gateway model IDs: Opus 4.5, GPT-5.2, GPT-5.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Enable reasoning for all model providers - Anthropic: thinking.type='enabled' with 10k token budget - OpenAI: reasoningEffort='high' - Instructions kept for all models (no longer conditionally removed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix OpenAI reasoning: use 'medium' effort (GPT-5.3 doesn't support 'high') Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address all PR review comments - Change changeset from minor to patch (repo convention) - Use ?? instead of || for system/instructions fallback - Clean up timeout: store ID, clearTimeout in finally, { once: true } listeners - Update class docstring example to use instructions - Map unrecognized finish reasons to 'other' with validation - Fix duplicate test, align assertion for unrecognized type - Support ^ exponentiation in calculate tool - Remove debug console.log from chat client - Fix ReactNode/ComponentProps type imports in UI components - Remove unused MockLanguageModelV3 re-exports from mock.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove accidentally created empty mock2.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Change changeset back to minor for breaking AI SDK v6 migration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix agent e2e tests: add Vercel world setup for CI The agent e2e tests only configured the local filesystem world but not the Vercel world backend. On CI (Vercel prod tests), this caused VercelOidcTokenError because the world wasn't initialized. Now matches the setup pattern from e2e.test.ts: configures Vercel world with OIDC token and project config from CI environment variables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Deduplicate e2e test utilities: extract shared code to utils.ts Extract manifest fetching, workflow lookup, world setup, and types into shared utils.ts. Both e2e.test.ts and e2e-agent.test.ts now import from the same source, eliminating ~200 lines of duplication. Shared utilities: - WorkflowManifest interface - fetchManifest() with caching - getWorkflowMetadata() with retry and fallback - setupWorld() handling local/Vercel/Postgres backends Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix missing deploymentUrl args in e2e.test.ts after utils refactor Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add @workflow/ai dep to all workbenches for agent e2e tests All workbenches now have @workflow/ai as a dependency and the 100_durable_agent_e2e.ts symlink, so agent e2e tests run everywhere. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix missing imports in e2e.test.ts: add fetchManifest and sleep The utils refactor removed these imports but they're still used: - fetchManifest: used in stepFunctionAsStartArgWorkflow test - sleep (setTimeout): used in webhookWorkflow test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> |
||
|
|
5b5b36a03b |
[e2e] Remove /api/hook and /api/test-health-check endpoints, call APIs directly
- Remove debug logging from bench.bench.ts - Remove awaitReturnValue() wrapper, use run.returnValue directly in benchmarks - Add changeset for Nitro builder manifest fix - Refactor hookWorkflow tests to call getHookByToken()/resumeHook() directly (pass hook object to resumeHook to avoid duplicate lookups) - Refactor queue-based health check test to call healthCheck() directly - Assert specific error message for invalid hook token test - Remove /api/hook and /api/test-health-check endpoints from all workbench apps (nextjs-turbopack, nextjs-webpack, vite, hono, express, fastify, sveltekit, astro, nuxt, nitro-v2, nitro-v3, nest, example) |
||
|
|
82c209c669 | Add missing env for tests on deploy (#973) | ||
|
|
86f62f2779 |
Refactor e2e tests to no longer use "trigger" endpoint (#958)
## Summary
Refactors the E2E tests to call `start()` from `workflow/api` directly instead of going through the `/api/trigger` HTTP endpoint in each workbench app. This removes a layer of indirection — the tests now use the same API that users would use to start workflows programmatically.
### Before
```ts
const run = await triggerWorkflow('addTenWorkflow', [123]);
const returnValue = await getWorkflowReturnValue(run.runId);
```
- `triggerWorkflow()` sent an HTTP POST to `/api/trigger` on the workbench app
- The workbench app looked up the workflow function, called `start()`, and returned the run ID
- `getWorkflowReturnValue()` polled `GET /api/trigger?runId=...` until the workflow completed
### After
```ts
const run = await start(await e2e('addTenWorkflow'), [123]);
const returnValue = await run.returnValue;
```
- `e2e()` / `getWorkflowMetadata()` fetches the manifest from `/.well-known/workflow/v1/manifest.json` to look up the correct `workflowId`
- `start()` is called directly from the test process via the configured World
- `run.returnValue` polls for completion via the World (no HTTP polling endpoint needed)
### Changes
**`packages/core/e2e/e2e.test.ts`**
- Removed `triggerWorkflow()` and `getWorkflowReturnValue()` helpers
- Added `fetchManifest()` to fetch and cache the workflow manifest from the deployment
- Added `getWorkflowMetadata(file, fn)` to look up `{ workflowId }` from the manifest
- Added `e2e(fn)` shorthand for the common case of `workflows/99_e2e.ts`
- All tests call `start()` and `run.returnValue` directly
- Error tests use `.catch()` to inspect `WorkflowRunFailedError`
- Output stream tests use `run.getReadable()` directly (skipped on local world where cross-process streaming isn't supported)
- `beforeAll` configures the local World with the correct data directory and base URL
- Pages Router tests use `startWorkflowViaHttp()` to specifically validate the HTTP trigger path
**Workbench apps (hono, express, fastify, nest)**
- Removed `/api/trigger` route handlers
- Kept `/api/hook`, `/api/test-direct-step-call`, `/api/test-health-check` endpoints
- Re-added `_workflows.js` side-effect import for hono/express/fastify to maintain Nitro's HMR dependency graph
**Deleted trigger-only route files** from: nextjs-turbopack, nextjs-webpack, vite, sveltekit, astro, nuxt, nitro-v2, nitro-v3, example
**`.github/workflows/tests.yml`**
- Added `WORKFLOW_PUBLIC_MANIFEST: '1'` to all E2E test jobs
### Dependencies
Stacked on #963 which adds `WORKFLOW_PUBLIC_MANIFEST` support to all framework builders.
|
||
|
|
1060f9d04a | Change user input/output to be binary data at the World interface (#853) | ||
|
|
a2fc53a0dc |
Support class static methods with "use step" / "use workflow" (#753)
The SWC compiler plugin had logic to walk through class static methods with "use step" / "use workflow", but no actual transformation was being applied. This fixes that. |
||
|
|
61fdb41e1b |
Add queue-based health check (#743)
* feat: add queue-based health check to bypass Deployment Protection
- Add HealthCheckPayloadSchema and HEALTH_CHECK_STREAM_PREFIX to @workflow/world
- Add healthCheck() method to Queue interface
- Update workflow and step handlers to detect and respond to health check messages
- Implement healthCheck() in world-local, world-vercel, and world-postgres
The queue-based health check sends a message through the queue pipeline,
which bypasses Vercel's Deployment Protection. The handler writes a response
to a stream that the caller reads to confirm health.
This complements the existing HTTP-based ?__health approach which still works
for local development and when bypass headers are available.
* refactor: move healthCheck to core package as utility function
Instead of adding healthCheck to the World interface (which duplicated
the same implementation across all worlds), this is now a utility function
in @workflow/core that takes the World as a parameter.
Usage:
import { healthCheck } from '@workflow/core';
const result = await healthCheck(world, 'workflow');
This is cleaner because:
- Single implementation instead of 3 identical ones
- World implementations remain simple
- No changes needed to the World interface
* .
* refactor: move health check types from world to core
Health check types (HealthCheckPayloadSchema, HealthCheckResult, etc.)
are now defined in @workflow/core since that's where they're used.
The HealthCheckPayloadSchema is still part of QueuePayloadSchema in
world (so the queue accepts health check messages), but it's not
exported from the public API.
* .
* Refactor health check implementation based on code review feedback (#746)
* Initial plan
* Address PR review comments: export types, fix race condition, improve error handling
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Add queue-based health check test and document security considerations
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Replace 'any' type with proper type guards for health check response
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Extract health check queue names as constants and improve type guards
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* .
* Fix e2e test
* .
* .
* .
* fix(ai): preserve providerMetadata as providerOptions in multi-turn tool calls (#733)
When tool calls are added to the conversation history, map providerMetadata
to providerOptions following the AI SDK convention. This fixes Gemini thinking
models that require thoughtSignature to be preserved across multi-turn tool calls,
preventing the error 'function call is missing a thought_signature'.
Fixes #727
* Local ui cli flag (#744)
* [web] Increase contrast on attribute items in sidebar (#736)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* [world] Remove pause and resume events, actions and states (#751)
* Version Packages (beta) (#735)
* .
* .
* Update turbo inputs to include shared config (#752)
* Update turbo inputs to include shared config
* Apply suggestions from code review
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
* feat(web): add self-hosted mode for world configuration (#747)
* feat(web): add self-hosted mode for world configuration
When WORKFLOW_TARGET_WORLD env var is set, the web UI operates in
self-hosted mode where the world configuration is locked to server-side
environment variables and cannot be changed via query params or UI.
- Add getHardcodedConfig server action to detect self-hosted mode
- Modify getWorldFromEnv to use server env vars in hardcoded mode
- Create WorldConfigContext to provide config state app-wide
- Update settings sidebar to show locked state with disabled inputs
- Update connection status to show PostgreSQL backend info
- Mask sensitive values (postgres URL) in hardcoded mode UI
* fix: address PR review feedback
- Remove unused ConfigMode type export
- Fix postgres substring to undefined (tooltip has details)
- Extract buildEnvMapFromProcessEnv helper to reduce duplication
- Remove unused EnvMap import from layout-client
- Import HardcodedConfig from web-shared/server instead of re-defining
* Fix: PostgreSQL URL parameter missing from configParsers, causing loss of postgres URL configuration on page reload in dynamic mode
* fix(cli): clear WORKFLOW_TARGET_WORLD when spawning web server
The CLI sets WORKFLOW_TARGET_WORLD as an env var, which the spawned
Next.js server inherits. This caused the web UI to enter self-hosted
mode even when launched via CLI.
Now we explicitly clear WORKFLOW_TARGET_WORLD from the server's
environment so it starts in dynamic mode where config comes from
query params as intended.
* refactor(web): use server-side env vars for world config
BREAKING CHANGE: The web UI no longer supports configuring the world
backend via URL query parameters. Configuration is now read exclusively
from server-side environment variables.
Changes:
- Remove query param parsing from @workflow/web config.ts
- Add ServerConfig interface with non-sensitive display info
- Update all components to use useServerConfig() hook
- Settings sidebar is now read-only
- CLI passes env vars to spawned web server instead of query params
- Server actions use process.env directly (envMap param reserved for future use)
This simplifies the architecture and improves security by never sending
sensitive data (connection strings, auth tokens) to the client.
* fix(web): fix settings sidebar overflow and shorten data dir path
- Add truncate/overflow handling to settings sidebar config values
- Add shortenPath() helper to abbreviate long file paths:
- Replaces home directory with ~
- Shows .../last-two-segments if still too long
- Add title attributes for full path on hover
* Update changeest
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
* Version Packages (beta) (#755)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Update packages/world/src/queue.ts
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
* [web] Tidy wake-up and re-enqueue buttons (#737)
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* [cli] Use dotenv to resolve .env and .env.local files on startup (#765)
* Use temporary workflow-server deployment URL
* feat: add queue-based health check to bypass Deployment Protection
- Add HealthCheckPayloadSchema and HEALTH_CHECK_STREAM_PREFIX to @workflow/world
- Add healthCheck() method to Queue interface
- Update workflow and step handlers to detect and respond to health check messages
- Implement healthCheck() in world-local, world-vercel, and world-postgres
The queue-based health check sends a message through the queue pipeline,
which bypasses Vercel's Deployment Protection. The handler writes a response
to a stream that the caller reads to confirm health.
This complements the existing HTTP-based ?__health approach which still works
for local development and when bypass headers are available.
* refactor: move healthCheck to core package as utility function
Instead of adding healthCheck to the World interface (which duplicated
the same implementation across all worlds), this is now a utility function
in @workflow/core that takes the World as a parameter.
Usage:
import { healthCheck } from '@workflow/core';
const result = await healthCheck(world, 'workflow');
This is cleaner because:
- Single implementation instead of 3 identical ones
- World implementations remain simple
- No changes needed to the World interface
* .
* refactor: move health check types from world to core
Health check types (HealthCheckPayloadSchema, HealthCheckResult, etc.)
are now defined in @workflow/core since that's where they're used.
The HealthCheckPayloadSchema is still part of QueuePayloadSchema in
world (so the queue accepts health check messages), but it's not
exported from the public API.
* .
* Refactor health check implementation based on code review feedback (#746)
* Initial plan
* Address PR review comments: export types, fix race condition, improve error handling
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Add queue-based health check test and document security considerations
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Replace 'any' type with proper type guards for health check response
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* Extract health check queue names as constants and improve type guards
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
* .
* Fix e2e test
* .
* .
* .
* .
* .
* Update packages/world/src/queue.ts
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
* Use temporary workflow-server deployment URL
* .
* .
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TooTallNate <71256+TooTallNate@users.noreply.github.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Vercel Release Bot <88769842+vercel-release-bot@users.noreply.github.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
||
|
|
a8f48c5a08 | add benchmarking (#460) | ||
|
|
6dd17500da |
refactor: move rollup plugin to own package (#382)
* refactor: move rollup plugin to own package * refactor(sveltekit): update sveltekit to use rollup package * Update packages/rollup/src/index.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * chore(deps): missing nitro dep * chore(deps): remove unused deps from @workflow/rollup * changeset * chore: project LICENSE symlinks * refactor: move @swc/core version to pnpm workspace * chore: cleanup and add readme --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
edb69c3c0d |
fix: port detection, postgres nitro apps and nitro app testing (#363)
* add debug logs * add polling for port test * nuxt may be hmring on ignored files/folders * Revert "nuxt may be hmring on ignored files/folders" This reverts commit c57c3f0dd9e1b757825c777e12b63e050ca44227. * Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * Update packages/world-local/src/config.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * revert getPort * test new pid to port cmd * extend sleep time on tests * fix * fix race condition in tests * add logs * add fallback from pid-port * remove pid-port fallback * revert config ts * test: add getPort test * update tests for concurrent calls * temp * trigger rebuild * feat: add windows get port support * fix port sorting * fix parsing logic and filtering * fformat * update ports logi * revert * windows port hack * refactor: optional chaining on windows * test: change ordering of config tests * remove wrong test * simplify windows getPort impl * use execFileSync instead of execFile * use execa * disable test cache * Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * update * debug * windows cmd * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * lockfile * fix: windows port detection * simplify stuff * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * test: revert gh copilot * fix * increase sleep * revert logs * revert turbo * revert * revert * changeset * Update packages/utils/src/get-port.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * format * init postgres world for express and hono * add posgres world to nitro config * add postgres world start to sveltekit * add postgres world plugin to nitro apps * Update workbench/sveltekit/src/hooks.server.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * Update workbench/vite/vite.config.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * fix nuxt plugin * revert vercel compiled hook on nitro * . * revert * update * CI WILL BE GREEN * remove log in nitro --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> |
||
|
|
10ce313d56 |
postgres: fix tests (#394)
* postgres: use non-deprecated drizzle signatures Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * postgres: store metadata in the hook Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * core: do not rely on module cache for world config. instead, use a global and a symbol. this makes sure that streamers can use in-memory event emitters and that it won't be compiled away into the different flow.js and step.js files. this was figured out when i was adding a hooks tests to world-testing. Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * add postgres world to all workbench packages we try to run them with the postgres world but it's not installed Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * Replace jsonb with cbor because zero byte does not work in jsonb :( Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * fix error handling: attempts start at 0 now, and not 1 like when we released. so initial attempt in postgres should reflect that. Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * drain stuff Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * fallback metadata to metadataJson Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * Make code more readable Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> * apply Vade fix Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> --------- Signed-off-by: Gal Schlezinger <gal@spitfire.co.il> |
||
|
|
ee25bd9d55 |
refactor: upgrade to latest nitro v3 (#293)
* refactor: migrate to latest nitro v3 Signed-off-by: Pooya Parsa <pooya@pi0.io> * update workaround * fix(nitro): nitro builder using deprecated srcDir * fix: add back nitro config * fix: testing matrix dirs * fix(hono): externalize nitro workflow output dir * chore: format * fix(nitro): externalize .nitro/workflow folder * docs(hono): update getting started * changeset * update docs and workbench * improve plugin patch * docs: preserve code style * update `getWorkflowDirs` * update vite workbench * test: fix hono dev config * fix: wrong api file path in hono dev config * fix(nitro): check all dirs for builder * chore: add comments --------- Signed-off-by: Pooya Parsa <pooya@pi0.io> Co-authored-by: Adrian Lam <me@adriandlam.com> |
||
|
|
945a946812 |
Normalize Workbenches (#283)
* Normalize Workbenches Normalize trigger scripts across workbenches fix: include hono in local build test test: include src dir for test test: add workflow dir config in test to fix sveltekit dev tests add temp 7_full in example wokrflow format fix(sveltekit): detecting workflow folders and customizable dir Remove 7_full and 1_simple error replace API symlink in webpack workbench Fix sveltekit and vite tests Fix sveltekit symlinks Test fixes Fix sveltekit workflows path Dont symlink routes in vite Include e2e tests for hono and vite fix error tests post normalization wip - attempted fixes * Add claude demo command * fix: normalize workbench tests (#292) * Proper stacktrace propogation in world Proper stacktrace propogation in world * Standardize the error type in the world spec * Normalize Workbenches Normalize trigger scripts across workbenches fix: include hono in local build test test: include src dir for test test: add workflow dir config in test to fix sveltekit dev tests add temp 7_full in example wokrflow format fix(sveltekit): detecting workflow folders and customizable dir Remove 7_full and 1_simple error replace API symlink in webpack workbench Fix sveltekit and vite tests Fix sveltekit symlinks Test fixes Fix sveltekit workflows path Dont symlink routes in vite Include e2e tests for hono and vite * fix error tests post normalization * fix(sveltekit): reading file on hmr delete * changeset * fix(vite): add resolve symlink script * fix(vite): missing building on hmr * test local builder in vite * test: increase timeout on hookWorkflow * test: ignore vite based apps in crossFileWorkflow * test: fix nitro based apps status codes * fix: intercept default vite spa handler on 404 workflow routes * fix: vite hook route returning 422 * test: use 422 for hookWorkflow expected * test: fix hono returning 404 * chore: add comment to middleware to clarify * make api route for duplicate case * revert * revert: nitro builder * add back nitro unhandled rejection logic * test: add hono * changeset * fix: unused method * fix: remove duplicate import * remove * chore: add comments to clarify * test remove vite symlink script --------- Co-authored-by: Pranay Prakash <pranay.gp@gmail.com> * refactor: add top level resolve symlinks script * fix: cleanup builder directories (#319) * fix: add sveltekit server routes to builder * fix: remove root workflow dir check * fix missing root level workflow route * Fix: The constructor now hardcodes `dirs: ['src/routes', 'src/lib']` which silently ignores any user\-provided `dirs` option passed to the plugin\, breaking the documented API and removing support for custom workflow directories\. * Fix: The test expectations don\'t match the new implementation of `getWorkflowDirs()`\. The mock provides `scanDirs` which the new code no longer uses\, and the new implementation adds scanning of `routesDir` and `apiDir` instead\. * fix(nitro): use src dir --------- Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * refactor(nitro): use suppressUndefinedRejections * revert: sveltekit builder --------- Co-authored-by: Adrian <me@adriandlam.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
fb8153bec4 |
feat: add Nuxt module and documentation (#187)
* feat: add Nuxt module and documentation * fix: add the prepare in the build command * Apply suggestion from @vercel[bot] Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * chore: simplify usage without /nuxt * wip * Update nuxt.mdx Co-Authored-By: Daniel Roe <daniel@roe.dev> * add clean command and ignore prepare * use @workflow/nitro * Changeset Added a Nuxt module and updated documentation accordingly. * feat: enable typescript plugin in tsconfig * chore: revert accordion value * chore: use symlink * fix: json parsing in trigger route for nitro-v2 * ci: add e2e tests for nuxt * ci: add nuxt to test matrix * chore: make sure to add /nuxt to avoid conflicts when importing entry-points * chore: move typescriptPlugin option to Nitro * chore: body is already parsed * fix: use readRawBody * fix: use rawBody in hook.post too * chore: add missing import (but not required) * chore: update pm * Create resolve-symlinks.sh * chore: add also node-rs/xxhash * Update create-test-matrix.mjs * update matrix * Update create-test-matrix.mjs * remove symlink to nitro-v2 --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Daniel Roe <daniel@roe.dev> Co-authored-by: Pranay Prakash <pranay.gp@gmail.com> Co-authored-by: Adrian Lam <me@adriandlam.com> |
||
|
|
98c36f1eb0 |
feat: add hmr and fix dev tests (#199)
* add hmr and dev tests for nitro and sveltekit * changeset * revert: e2e testing code * add streams.ts to workbench apps * fix: test confnigs * fix: hmr failing on new files for sveltekit plugin * lockfile * switch testing to use 3_stream.ts * fix: sveltekit hmr test file import * fix: nextjs testing file * remove stuff * changeset * refactor(tests): expose config through matrix config * fix: add symlink for nextjs turbopack * add resolve symlinks script * fix: nextjs-webpack resolve symlinks script |
||
|
|
278c3eeeb2 |
chore: update license (#192)
* chore: update license on vite workbench app * update cargo toml |
||
|
|
e814dba6f2 |
feat: workflow/vite plugin (#172)
* feat: nitro+vite support and workbench * update package.json files * test: add local-build test for vite * update to vite plugin * fix imports * dedup nitro * update * update nitro * allow passing options via plugin * Update packages/nitro/src/vite.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * test: add vite tests * trigger ci * fix: missing js extension from builder and rollup * refactor: move debug => unenv under vite plugin * alternative workaround * Update packages/nitro/src/vite.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * rename plugin * Update packages/cli/package.json Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> * lockfile --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Adrian Lam <me@adriandlam.com> |