From c38f93d6f902803b3c4673d820350db3cc86e643 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 12:44:35 -0700 Subject: [PATCH 1/6] docs: document error codes in errors and retries guide Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/error-codes-docs.md | 4 +++ .../docs/foundations/errors-and-retries.mdx | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .changeset/error-codes-docs.md diff --git a/.changeset/error-codes-docs.md b/.changeset/error-codes-docs.md new file mode 100644 index 0000000000..4a24de2048 --- /dev/null +++ b/.changeset/error-codes-docs.md @@ -0,0 +1,4 @@ +--- +--- + +Document error codes (USER_ERROR, RUNTIME_ERROR) in errors and retries guide diff --git a/docs/content/docs/foundations/errors-and-retries.mdx b/docs/content/docs/foundations/errors-and-retries.mdx index 061425c139..06304114f3 100644 --- a/docs/content/docs/foundations/errors-and-retries.mdx +++ b/docs/content/docs/foundations/errors-and-retries.mdx @@ -139,6 +139,34 @@ callApi.maxRetries = 5; // Retry up to 5 times on failure (6 total attempts) step can run up to 4 times total (1 initial attempt + 3 retries). +## Error Codes + +When a workflow run fails, the error includes a `code` that classifies the failure. You can access it programmatically via the `Run` class: + +```typescript lineNumbers +import { WorkflowRunFailedError } from "workflow"; + +const run = await start(myWorkflow, [input]); + +try { + const result = await run.returnValue; +} catch (err) { + if (WorkflowRunFailedError.is(err)) { + console.log(err.cause.code); // "USER_ERROR" or "RUNTIME_ERROR" + console.log(err.cause.message); // The error message + } +} +``` + +| Code | Meaning | +| --- | --- | +| `USER_ERROR` | An error thrown in your workflow or step code (including propagated step failures like `FatalError`) | +| `RUNTIME_ERROR` | An internal runtime error such as a corrupted event log or missing data. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) | + + + The error code is also available on the run entity via the CLI (`workflow inspect runs --withData`) in the `error.code` field, and as an OTEL span attribute (`workflow.error.code`) for observability. + + ## Rolling Back Failed Steps When a workflow fails partway through, it can leave the system in an inconsistent state. From d77acb75365615d56e095902f2070e6f98c6ff38 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 12:45:53 -0700 Subject: [PATCH 2/6] changes Signed-off-by: Pranay Prakash --- .changeset/error-codes-docs.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.changeset/error-codes-docs.md b/.changeset/error-codes-docs.md index 4a24de2048..a845151cc8 100644 --- a/.changeset/error-codes-docs.md +++ b/.changeset/error-codes-docs.md @@ -1,4 +1,2 @@ --- --- - -Document error codes (USER_ERROR, RUNTIME_ERROR) in errors and retries guide From 3ae51e89415d5a04f59668e557cbfe10c9712439 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 13:02:59 -0700 Subject: [PATCH 3/6] docs: address PR review comments on error codes guide - Add missing `start` import from `workflow/api` - Note that `cause.code` may be undefined for older runs - Remove invalid `--withData` flag and use `npx workflow` CLI style - Remove empty changeset (docs-only change needs no changeset) Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/error-codes-docs.md | 2 -- docs/content/docs/foundations/errors-and-retries.mdx | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 .changeset/error-codes-docs.md diff --git a/.changeset/error-codes-docs.md b/.changeset/error-codes-docs.md deleted file mode 100644 index a845151cc8..0000000000 --- a/.changeset/error-codes-docs.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/docs/content/docs/foundations/errors-and-retries.mdx b/docs/content/docs/foundations/errors-and-retries.mdx index 06304114f3..5dab8d6984 100644 --- a/docs/content/docs/foundations/errors-and-retries.mdx +++ b/docs/content/docs/foundations/errors-and-retries.mdx @@ -141,10 +141,11 @@ callApi.maxRetries = 5; // Retry up to 5 times on failure (6 total attempts) ## Error Codes -When a workflow run fails, the error includes a `code` that classifies the failure. You can access it programmatically via the `Run` class: +When a workflow run fails, the error may include a `code` that classifies the failure. You can access it programmatically via the `Run` class: ```typescript lineNumbers import { WorkflowRunFailedError } from "workflow"; +import { start } from "workflow/api"; const run = await start(myWorkflow, [input]); @@ -152,7 +153,7 @@ try { const result = await run.returnValue; } catch (err) { if (WorkflowRunFailedError.is(err)) { - console.log(err.cause.code); // "USER_ERROR" or "RUNTIME_ERROR" + console.log(err.cause.code); // "USER_ERROR", "RUNTIME_ERROR", or undefined console.log(err.cause.message); // The error message } } @@ -164,7 +165,7 @@ try { | `RUNTIME_ERROR` | An internal runtime error such as a corrupted event log or missing data. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) | - The error code is also available on the run entity via the CLI (`workflow inspect runs --withData`) in the `error.code` field, and as an OTEL span attribute (`workflow.error.code`) for observability. + The error code is also available on the run entity via the CLI (`npx workflow inspect runs `) in the `error.code` field, and as an OTEL span attribute (`workflow.error.code`) for observability. ## Rolling Back Failed Steps From b4bbbaae6ab9272ffbacdc56280c8cf056503c99 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 14:45:13 -0700 Subject: [PATCH 4/6] fix: correct WorkflowRunFailedError import and docs typecheck - Import from `@workflow/errors` instead of `workflow` (not re-exported) - Add WorkflowRunFailedError to import inference map - Add myWorkflow placeholder to docs globals Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/content/docs/foundations/errors-and-retries.mdx | 2 +- packages/docs-typecheck/src/docs-globals.d.ts | 3 +++ packages/docs-typecheck/src/import-inference.ts | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/foundations/errors-and-retries.mdx b/docs/content/docs/foundations/errors-and-retries.mdx index 5dab8d6984..2727b4a989 100644 --- a/docs/content/docs/foundations/errors-and-retries.mdx +++ b/docs/content/docs/foundations/errors-and-retries.mdx @@ -144,7 +144,7 @@ callApi.maxRetries = 5; // Retry up to 5 times on failure (6 total attempts) When a workflow run fails, the error may include a `code` that classifies the failure. You can access it programmatically via the `Run` class: ```typescript lineNumbers -import { WorkflowRunFailedError } from "workflow"; +import { WorkflowRunFailedError } from "@workflow/errors"; import { start } from "workflow/api"; const run = await start(myWorkflow, [input]); diff --git a/packages/docs-typecheck/src/docs-globals.d.ts b/packages/docs-typecheck/src/docs-globals.d.ts index 8bfa8e09af..4f7f01133c 100644 --- a/packages/docs-typecheck/src/docs-globals.d.ts +++ b/packages/docs-typecheck/src/docs-globals.d.ts @@ -172,4 +172,7 @@ declare global { const MAX_STEPS: number; const reportId: string; const userId: string; + + // Workflow placeholders used in examples + const myWorkflow: (...args: any[]) => Promise; } diff --git a/packages/docs-typecheck/src/import-inference.ts b/packages/docs-typecheck/src/import-inference.ts index bdcf062c31..bd3e78f0bc 100644 --- a/packages/docs-typecheck/src/import-inference.ts +++ b/packages/docs-typecheck/src/import-inference.ts @@ -36,6 +36,9 @@ const SYMBOL_IMPORTS: Record = { WorkflowWritableStreamOptions: { module: 'workflow', isType: true }, RetryableErrorOptions: { module: 'workflow', isType: true }, + // From '@workflow/errors' + WorkflowRunFailedError: { module: '@workflow/errors' }, + // From 'workflow/api' start: { module: 'workflow/api' }, getRun: { module: 'workflow/api' }, From c3af2e3859a5a9a5619c5728f6967cd8b1fba4d7 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 15:11:20 -0700 Subject: [PATCH 5/6] fix: remove import inference from docs typecheck, add explicit imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the import-inference system that auto-added imports to doc snippets before typechecking. This was masking real issues — if a snippet imported from the wrong module or was missing an import, the typechecker silently fixed it instead of catching the bug. All 24 affected doc snippets now have explicit, correct imports that match what users would actually write. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/content/docs/ai/defining-tools.mdx | 6 + docs/content/docs/ai/index.mdx | 3 + docs/content/docs/ai/message-queueing.mdx | 2 + docs/content/docs/ai/sleep-and-delays.mdx | 2 + .../workflow-ai/durable-agent.mdx | 2 + .../api-reference/workflow-api/get-run.mdx | 2 + .../api-reference/workflow/define-hook.mdx | 2 + .../api-reference/workflow/get-writable.mdx | 1 + docs/content/docs/errors/hook-conflict.mdx | 5 + .../webhook-invalid-respond-with-value.mdx | 10 + .../docs/errors/webhook-response-not-sent.mdx | 8 + docs/content/docs/foundations/streaming.mdx | 6 + .../docs-typecheck/src/__tests__/docs.test.ts | 7 +- .../docs-typecheck/src/import-inference.ts | 543 ------------------ packages/docs-typecheck/src/index.ts | 1 - 15 files changed, 54 insertions(+), 546 deletions(-) delete mode 100644 packages/docs-typecheck/src/import-inference.ts diff --git a/docs/content/docs/ai/defining-tools.mdx b/docs/content/docs/ai/defining-tools.mdx index cc5780eaaf..aef99741c9 100644 --- a/docs/content/docs/ai/defining-tools.mdx +++ b/docs/content/docs/ai/defining-tools.mdx @@ -23,6 +23,9 @@ Just like in regular AI SDK tool definitions, tool in DurableAgent are called wi When you tool needs access to the full message history, you can access it via the `messages` property of the tool call context: ```typescript title="tools.ts" lineNumbers +import { Experimental_Agent as Agent } from "ai"; +import type { ModelMessage } from "ai"; + async function getWeather( { city }: { city: string }, { messages, toolCallId }: { messages: ModelMessage[], toolCallId: string }) { // [!code highlight] @@ -65,6 +68,9 @@ Tools can be implemented either at the step level or the workflow level, with di Tools can also combine both by starting out on the workflow level, and calling into steps for I/O operations, like so: ```typescript title="tools.ts" lineNumbers +import { sleep } from "workflow"; +import type { LanguageModel, ModelMessage } from "ai"; + // Step: handles I/O with retries async function performFetch(url: string) { "use step"; diff --git a/docs/content/docs/ai/index.mdx b/docs/content/docs/ai/index.mdx index 176db0e700..66de5531bb 100644 --- a/docs/content/docs/ai/index.mdx +++ b/docs/content/docs/ai/index.mdx @@ -123,6 +123,9 @@ The core code that makes all of this happen is quite simple. Here's a breakdown Our API route makes a simple call to [AI SDK's `Agent` class](https://ai-sdk.dev/docs/agents/overview), which is a simple wrapper around [AI SDK's `streamText` function](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#streamtext). This is also where we pass tools to the agent. ```typescript title="app/api/chat/route.ts" lineNumbers +import { Experimental_Agent as Agent } from "ai"; +import type { LanguageModel } from "ai"; + export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const agent = new Agent({ // [!code highlight] diff --git a/docs/content/docs/ai/message-queueing.mdx b/docs/content/docs/ai/message-queueing.mdx index 4cdaad58b2..ca02665fcc 100644 --- a/docs/content/docs/ai/message-queueing.mdx +++ b/docs/content/docs/ai/message-queueing.mdx @@ -32,6 +32,8 @@ If you just need basic multi-turn conversations where messages arrive between tu The `prepareStep` callback runs before each step in the agent loop. It receives the current state and can modify the messages sent to the model: ```typescript lineNumbers +import type { ModelMessage, LanguageModel } from "ai"; + interface PrepareStepInfo { model: string | (() => Promise); // Current model stepNumber: number; // 0-indexed step count diff --git a/docs/content/docs/ai/sleep-and-delays.mdx b/docs/content/docs/ai/sleep-and-delays.mdx index f3eb33415d..95c4ae6b55 100644 --- a/docs/content/docs/ai/sleep-and-delays.mdx +++ b/docs/content/docs/ai/sleep-and-delays.mdx @@ -162,6 +162,8 @@ Aside from providing `sleep()` as a tool, there are other use cases for Agents t When hitting API rate limits, use `RetryableError` with a delay: ```typescript lineNumbers +import { sleep, RetryableError } from "workflow"; + async function callRateLimitedAPI(endpoint: string) { "use step"; diff --git a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx index 19e8ffde90..580b4b38a1 100644 --- a/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx +++ b/docs/content/docs/api-reference/workflow-ai/durable-agent.mdx @@ -317,6 +317,8 @@ async function multiToolAgentWorkflow(userQuery: string) { ```typescript import { DurableAgent } from "@workflow/ai/agent"; +import { getWritable } from "workflow"; +import type { UIMessageChunk } from "ai"; import { z } from "zod"; async function searchProducts({ query }: { query: string }) { diff --git a/docs/content/docs/api-reference/workflow-api/get-run.mdx b/docs/content/docs/api-reference/workflow-api/get-run.mdx index ecf48c3dd6..a2ff7ff080 100644 --- a/docs/content/docs/api-reference/workflow-api/get-run.mdx +++ b/docs/content/docs/api-reference/workflow-api/get-run.mdx @@ -144,6 +144,8 @@ export async function POST(req: Request) { You can also target specific sleep calls by correlation ID: ```typescript lineNumbers +import { getRun } from "workflow/api"; + const run = getRun("my-run-id"); // @setup const { stoppedCount } = await run.wakeUp({ correlationIds: ["wait_abc123"], diff --git a/docs/content/docs/api-reference/workflow/define-hook.mdx b/docs/content/docs/api-reference/workflow/define-hook.mdx index 2d64078a93..ad0d697f30 100644 --- a/docs/content/docs/api-reference/workflow/define-hook.mdx +++ b/docs/content/docs/api-reference/workflow/define-hook.mdx @@ -193,6 +193,8 @@ export const approvalHook = defineHook({ Tokens are used to identify a specific hook and for resuming a hook. You can customize the token to be more specific to a use case. ```typescript lineNumbers +import { defineHook } from "workflow"; + const slackHook = defineHook<{ text: string; userId: string }>(); export async function slackBotWorkflow(channelId: string) { diff --git a/docs/content/docs/api-reference/workflow/get-writable.mdx b/docs/content/docs/api-reference/workflow/get-writable.mdx index 19680fc249..a82257bcd4 100644 --- a/docs/content/docs/api-reference/workflow/get-writable.mdx +++ b/docs/content/docs/api-reference/workflow/get-writable.mdx @@ -208,6 +208,7 @@ Here's a more complex example showing how you might stream AI chat responses: ```typescript lineNumbers import { getWritable } from "workflow"; import { generateId, streamText, type UIMessageChunk } from "ai"; +import type { ModelMessage } from "ai"; export async function chat(messages: ModelMessage[]) { "use workflow"; diff --git a/docs/content/docs/errors/hook-conflict.mdx b/docs/content/docs/errors/hook-conflict.mdx index 6cf7f3f9f2..c6b0c71f82 100644 --- a/docs/content/docs/errors/hook-conflict.mdx +++ b/docs/content/docs/errors/hook-conflict.mdx @@ -46,6 +46,8 @@ export async function processPayment() { **Solution:** Use unique tokens that include the run ID or other unique identifiers. ```typescript lineNumbers +import { createHook } from "workflow"; + export async function processPayment(orderId: string) { "use workflow"; @@ -60,6 +62,8 @@ export async function processPayment(orderId: string) { The safest approach is to let the Workflow runtime generate a unique token automatically: ```typescript lineNumbers +import { createHook } from "workflow"; + export async function processPayment() { "use workflow"; @@ -74,6 +78,7 @@ export async function processPayment() { When a hook conflict occurs, awaiting the hook will throw a `WorkflowRuntimeError`. You can catch this error to handle the conflict gracefully: ```typescript lineNumbers +import { createHook } from "workflow"; import { WorkflowRuntimeError } from "@workflow/errors"; export async function processPayment(orderId: string) { diff --git a/docs/content/docs/errors/webhook-invalid-respond-with-value.mdx b/docs/content/docs/errors/webhook-invalid-respond-with-value.mdx index 4eb470463a..dc341d324d 100644 --- a/docs/content/docs/errors/webhook-invalid-respond-with-value.mdx +++ b/docs/content/docs/errors/webhook-invalid-respond-with-value.mdx @@ -43,6 +43,8 @@ export async function webhookWorkflow() { **Solution:** Use `"manual"` or provide a `Response` object. ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Fixed - use "manual" export async function webhookWorkflow() { "use workflow"; @@ -74,6 +76,8 @@ export async function webhookWorkflow() { **Solution:** Create a proper `Response` object. ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Fixed - use Response constructor export async function webhookWorkflow() { "use workflow"; @@ -89,6 +93,8 @@ export async function webhookWorkflow() { ### Default Behavior (202 Response) ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Returns 202 Accepted automatically const webhook = await createWebhook(); const request = await webhook; @@ -98,6 +104,8 @@ const request = await webhook; ### Manual Response ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Manual response control const webhook = await createWebhook({ respondWith: "manual", @@ -120,6 +128,8 @@ await request.respondWith( ### Pre-defined Response ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Immediate response const webhook = await createWebhook({ respondWith: new Response("Request received", { status: 200 }), diff --git a/docs/content/docs/errors/webhook-response-not-sent.mdx b/docs/content/docs/errors/webhook-response-not-sent.mdx index ae9011d0bd..15a95124a5 100644 --- a/docs/content/docs/errors/webhook-response-not-sent.mdx +++ b/docs/content/docs/errors/webhook-response-not-sent.mdx @@ -49,6 +49,8 @@ export async function webhookWorkflow() { **Solution:** Always call `request.respondWith()` when using manual response mode. ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Fixed - response sent export async function webhookWorkflow() { "use workflow"; @@ -92,6 +94,8 @@ export async function webhookWorkflow() { **Solution:** Ensure all code paths send a response. ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Fixed - response sent in all branches export async function webhookWorkflow() { "use workflow"; @@ -135,6 +139,8 @@ export async function webhookWorkflow() { **Solution:** Use try-catch to handle errors and send appropriate responses. ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Fixed - error handling with response export async function webhookWorkflow() { "use workflow"; @@ -163,6 +169,8 @@ export async function webhookWorkflow() { If you don't need custom response control, consider using the default response mode which automatically returns a `202 Accepted` response: ```typescript lineNumbers +import { createWebhook } from "workflow"; + // Automatic 202 response - no manual response needed export async function webhookWorkflow() { "use workflow"; diff --git a/docs/content/docs/foundations/streaming.mdx b/docs/content/docs/foundations/streaming.mdx index f1ae0b383d..9015d65fbb 100644 --- a/docs/content/docs/foundations/streaming.mdx +++ b/docs/content/docs/foundations/streaming.mdx @@ -164,6 +164,8 @@ Workflow functions must be deterministic to support replay. Since streams bypass For more on determinism and replay, see [Workflows and Steps](/docs/foundations/workflows-and-steps). ```typescript title="workflows/bad-example.ts" lineNumbers +import { getWritable } from "workflow"; + export async function badWorkflow() { "use workflow"; @@ -176,6 +178,8 @@ export async function badWorkflow() { ``` ```typescript title="workflows/good-example.ts" lineNumbers +import { getWritable } from "workflow"; + export async function goodWorkflow() { "use workflow"; @@ -501,6 +505,8 @@ If a lock is not released, the step function's HTTP request cannot terminate. Ev **Close streams when done:** ```typescript lineNumbers +import { getWritable } from "workflow"; + async function finalizeStream() { "use step"; diff --git a/packages/docs-typecheck/src/__tests__/docs.test.ts b/packages/docs-typecheck/src/__tests__/docs.test.ts index a1b586c79d..cb5ad9135d 100644 --- a/packages/docs-typecheck/src/__tests__/docs.test.ts +++ b/packages/docs-typecheck/src/__tests__/docs.test.ts @@ -4,7 +4,6 @@ import { fileURLToPath } from 'node:url'; import { globSync } from 'glob'; import { describe, expect, it } from 'vitest'; import { extractCodeSamples } from '../extractor.js'; -import { addInferredImports } from '../import-inference.js'; import { formatResult, typeCheckBatch } from '../type-checker.js'; import type { CodeSample, @@ -79,7 +78,11 @@ for (const relativeFile of allDocFiles) { if (sample.skipTypeCheck) { skippedSamples.push({ relativeFile, sample }); } else { - const processed = addInferredImports(sample); + const processed: ProcessedCodeSample = { + ...sample, + processedSource: sample.source, + addedImports: [], + }; allSamples.push({ relativeFile, sample, processed }); } } diff --git a/packages/docs-typecheck/src/import-inference.ts b/packages/docs-typecheck/src/import-inference.ts deleted file mode 100644 index bd3e78f0bc..0000000000 --- a/packages/docs-typecheck/src/import-inference.ts +++ /dev/null @@ -1,543 +0,0 @@ -import ts from 'typescript'; -import type { CodeSample, ProcessedCodeSample } from './types.js'; - -interface ImportMapping { - module: string; - isType?: boolean; - /** If set, import this name and alias it to the symbol name */ - importAs?: string; -} - -/** - * Mapping of known symbols to their source modules - */ -const SYMBOL_IMPORTS: Record = { - // From 'workflow' (re-exports from @workflow/core) - FatalError: { module: 'workflow' }, - RetryableError: { module: 'workflow' }, - createHook: { module: 'workflow' }, - createWebhook: { module: 'workflow' }, - defineHook: { module: 'workflow' }, - sleep: { module: 'workflow' }, - getStepMetadata: { module: 'workflow' }, - getWorkflowMetadata: { module: 'workflow' }, - getWritable: { module: 'workflow' }, - fetch: { module: 'workflow' }, - - // Types from 'workflow' - Hook: { module: 'workflow', isType: true }, - HookOptions: { module: 'workflow', isType: true }, - RequestWithResponse: { module: 'workflow', isType: true }, - Webhook: { module: 'workflow', isType: true }, - WebhookOptions: { module: 'workflow', isType: true }, - StepMetadata: { module: 'workflow', isType: true }, - WorkflowMetadata: { module: 'workflow', isType: true }, - TypedHook: { module: 'workflow', isType: true }, - WorkflowWritableStreamOptions: { module: 'workflow', isType: true }, - RetryableErrorOptions: { module: 'workflow', isType: true }, - - // From '@workflow/errors' - WorkflowRunFailedError: { module: '@workflow/errors' }, - - // From 'workflow/api' - start: { module: 'workflow/api' }, - getRun: { module: 'workflow/api' }, - runStep: { module: 'workflow/api' }, - resumeHook: { module: 'workflow/api' }, - resumeWebhook: { module: 'workflow/api' }, - getHookByToken: { module: 'workflow/api' }, - Run: { module: 'workflow/api' }, - - // Types from 'workflow/api' - Event: { module: 'workflow/api', isType: true }, - StartOptions: { module: 'workflow/api', isType: true }, - WorkflowRun: { module: 'workflow/api', isType: true }, - WorkflowReadableStreamOptions: { module: 'workflow/api', isType: true }, - - // From '@workflow/next' - withWorkflow: { module: '@workflow/next' }, - - // From '@workflow/ai' - createDurableAgent: { module: '@workflow/ai' }, - DurableAgent: { module: '@workflow/ai' }, // Class (both type and value) - - // Third-party - z: { module: 'zod' }, - - // AI SDK exports - Output: { module: 'ai' }, - Agent: { module: 'ai', importAs: 'Experimental_Agent' }, // AI SDK exports as Experimental_Agent - tool: { module: 'ai' }, - streamText: { module: 'ai' }, - generateText: { module: 'ai' }, - streamObject: { module: 'ai' }, - generateObject: { module: 'ai' }, - generateId: { module: 'ai' }, - convertToModelMessages: { module: 'ai' }, - createUIMessageStreamResponse: { module: 'ai' }, - UIMessage: { module: 'ai', isType: true }, - UIMessageChunk: { module: 'ai', isType: true }, - ModelMessage: { module: 'ai', isType: true }, - LanguageModel: { module: 'ai', isType: true }, -}; - -/** - * Finds all identifiers used in the source code - */ -function findUsedIdentifiers(sourceCode: string): Set { - const identifiers = new Set(); - - try { - const sourceFile = ts.createSourceFile( - 'sample.ts', - sourceCode, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS - ); - - function visit(node: ts.Node) { - if (ts.isIdentifier(node)) { - identifiers.add(node.text); - } - ts.forEachChild(node, visit); - } - - visit(sourceFile); - } catch { - // If parsing fails, fall back to regex-based extraction - const identifierRegex = /\b([A-Za-z_$][A-Za-z0-9_$]*)\b/g; - let match; - while ((match = identifierRegex.exec(sourceCode)) !== null) { - identifiers.add(match[1]); - } - } - - return identifiers; -} - -/** - * Finds identifiers that are declared locally in the source - */ -function findLocalDeclarations(sourceCode: string): Set { - const locals = new Set(); - - try { - const sourceFile = ts.createSourceFile( - 'sample.ts', - sourceCode, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS - ); - - function visit(node: ts.Node) { - // Variable declarations: const x = ..., let y = ... - if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { - locals.add(node.name.text); - } - - // Function declarations: function foo() {} - if (ts.isFunctionDeclaration(node) && node.name) { - locals.add(node.name.text); - } - - // Class declarations: class Foo {} - if (ts.isClassDeclaration(node) && node.name) { - locals.add(node.name.text); - } - - // Parameters: (x, y) => ... - if (ts.isParameter(node) && ts.isIdentifier(node.name)) { - locals.add(node.name.text); - } - - // Type aliases: type Foo = ... - if (ts.isTypeAliasDeclaration(node)) { - locals.add(node.name.text); - } - - // Interface declarations: interface Foo {} - if (ts.isInterfaceDeclaration(node)) { - locals.add(node.name.text); - } - - // Enum declarations: enum Foo {} - if (ts.isEnumDeclaration(node)) { - locals.add(node.name.text); - } - - ts.forEachChild(node, visit); - } - - visit(sourceFile); - } catch { - // Ignore parse errors for incomplete snippets - } - - return locals; -} - -/** - * Extracts existing imports from the source code - */ -function findExistingImports(sourceCode: string): Set { - const imported = new Set(); - - try { - const sourceFile = ts.createSourceFile( - 'sample.ts', - sourceCode, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS - ); - - for (const statement of sourceFile.statements) { - if (ts.isImportDeclaration(statement)) { - const clause = statement.importClause; - if (clause) { - // Default import - if (clause.name) { - imported.add(clause.name.text); - } - // Named imports - if (clause.namedBindings) { - if (ts.isNamedImports(clause.namedBindings)) { - for (const element of clause.namedBindings.elements) { - imported.add(element.name.text); - } - } else if (ts.isNamespaceImport(clause.namedBindings)) { - imported.add(clause.namedBindings.name.text); - } - } - } - } - } - } catch { - // Ignore parse errors - } - - return imported; -} - -/** - * Generates import statements for missing symbols - */ -function generateImports(missingSymbols: Map): string[] { - // Group by module, tracking aliases separately - const byModule = new Map< - string, - { - values: string[]; - types: string[]; - aliases: Array<{ from: string; to: string }>; - } - >(); - - for (const [symbol, mapping] of missingSymbols) { - const existing = byModule.get(mapping.module) || { - values: [], - types: [], - aliases: [], - }; - - if (mapping.importAs) { - // This symbol needs to be imported with an alias - existing.aliases.push({ from: mapping.importAs, to: symbol }); - } else if (mapping.isType) { - existing.types.push(symbol); - } else { - existing.values.push(symbol); - } - byModule.set(mapping.module, existing); - } - - // Generate import statements - const imports: string[] = []; - - for (const [module, { values, types, aliases }] of byModule) { - const allSymbols: string[] = []; - - // Add value imports - allSymbols.push(...values.sort()); - - // Add aliased imports (e.g., "Experimental_Agent as Agent") - for (const { from, to } of aliases) { - allSymbols.push(`${from} as ${to}`); - } - - // Add type imports with 'type' prefix - for (const t of types.sort()) { - allSymbols.push(`type ${t}`); - } - - if (allSymbols.length > 0) { - imports.push(`import { ${allSymbols.join(', ')} } from '${module}';`); - } - } - - return imports.sort(); -} - -/** - * Processes a code sample to add inferred imports - */ -export function addInferredImports(sample: CodeSample): ProcessedCodeSample { - const { source } = sample; - - // Find all identifiers used - const usedIdentifiers = findUsedIdentifiers(source); - - // Find locally declared identifiers - const localDeclarations = findLocalDeclarations(source); - - // Find existing imports - const existingImports = findExistingImports(source); - - // Determine which symbols need to be imported - const missingSymbols = new Map(); - - for (const identifier of usedIdentifiers) { - // Skip if locally declared - if (localDeclarations.has(identifier)) continue; - - // Skip if already imported - if (existingImports.has(identifier)) continue; - - // Skip common globals/keywords - if (isBuiltinOrKeyword(identifier)) continue; - - // Check if we have a mapping for this symbol - const mapping = SYMBOL_IMPORTS[identifier]; - if (mapping) { - missingSymbols.set(identifier, mapping); - } - } - - // Generate import statements - const addedImports = generateImports(missingSymbols); - - // Prepend imports to source - const processedSource = - addedImports.length > 0 - ? addedImports.join('\n') + '\n\n' + source - : source; - - return { - ...sample, - processedSource, - addedImports, - }; -} - -/** - * Checks if an identifier is a built-in or keyword - */ -function isBuiltinOrKeyword(identifier: string): boolean { - const builtins = new Set([ - // JavaScript keywords - 'abstract', - 'arguments', - 'await', - 'boolean', - 'break', - 'byte', - 'case', - 'catch', - 'char', - 'class', - 'const', - 'continue', - 'debugger', - 'default', - 'delete', - 'do', - 'double', - 'else', - 'enum', - 'eval', - 'export', - 'extends', - 'false', - 'final', - 'finally', - 'float', - 'for', - 'function', - 'goto', - 'if', - 'implements', - 'import', - 'in', - 'instanceof', - 'int', - 'interface', - 'let', - 'long', - 'native', - 'new', - 'null', - 'package', - 'private', - 'protected', - 'public', - 'return', - 'short', - 'static', - 'super', - 'switch', - 'synchronized', - 'this', - 'throw', - 'throws', - 'transient', - 'true', - 'try', - 'typeof', - 'undefined', - 'var', - 'void', - 'volatile', - 'while', - 'with', - 'yield', - - // TypeScript keywords - 'any', - 'as', - 'asserts', - 'async', - 'bigint', - 'declare', - 'from', - 'get', - 'infer', - 'is', - 'keyof', - 'module', - 'namespace', - 'never', - 'of', - 'readonly', - 'require', - 'set', - 'string', - 'number', - 'symbol', - 'type', - 'unique', - 'unknown', - - // Common globals - 'console', - 'process', - 'global', - 'globalThis', - 'window', - 'document', - 'JSON', - 'Math', - 'Date', - 'Array', - 'Object', - 'String', - 'Number', - 'Boolean', - 'Symbol', - 'BigInt', - 'Map', - 'Set', - 'WeakMap', - 'WeakSet', - 'Promise', - 'Proxy', - 'Reflect', - 'Error', - 'TypeError', - 'ReferenceError', - 'SyntaxError', - 'RangeError', - 'URIError', - 'EvalError', - 'RegExp', - 'Function', - 'Uint8Array', - 'Int8Array', - 'Uint16Array', - 'Int16Array', - 'Uint32Array', - 'Int32Array', - 'Float32Array', - 'Float64Array', - 'ArrayBuffer', - 'DataView', - 'Buffer', - 'setTimeout', - 'setInterval', - 'clearTimeout', - 'clearInterval', - 'setImmediate', - 'clearImmediate', - 'queueMicrotask', - - // Web APIs - 'Request', - 'Response', - 'Headers', - 'URL', - 'URLSearchParams', - 'FormData', - 'Blob', - 'File', - 'ReadableStream', - 'WritableStream', - 'TransformStream', - 'TextEncoder', - 'TextDecoder', - 'AbortController', - 'AbortSignal', - 'crypto', - 'performance', - 'navigator', - 'location', - 'history', - 'localStorage', - 'sessionStorage', - 'indexedDB', - 'WebSocket', - 'EventSource', - 'Worker', - 'SharedWorker', - 'ServiceWorker', - 'Notification', - 'Intl', - 'atob', - 'btoa', - - // Common type names that don't need imports - 'Partial', - 'Required', - 'Readonly', - 'Record', - 'Pick', - 'Omit', - 'Exclude', - 'Extract', - 'NonNullable', - 'Parameters', - 'ConstructorParameters', - 'ReturnType', - 'InstanceType', - 'ThisParameterType', - 'OmitThisParameter', - 'ThisType', - 'Awaited', - 'Uppercase', - 'Lowercase', - 'Capitalize', - 'Uncapitalize', - ]); - - return builtins.has(identifier); -} diff --git a/packages/docs-typecheck/src/index.ts b/packages/docs-typecheck/src/index.ts index a93d938092..7f529ba7c6 100644 --- a/packages/docs-typecheck/src/index.ts +++ b/packages/docs-typecheck/src/index.ts @@ -1,5 +1,4 @@ export { extractCodeSamples, extractCodeSamplesFromFile } from './extractor.js'; -export { addInferredImports } from './import-inference.js'; export { formatResult, typeCheck, typeCheckBatch } from './type-checker.js'; export type { CodeSample, From 0dc2a305da7a59af99ce87681049bf834126f145 Mon Sep 17 00:00:00 2001 From: Pranay Prakash Date: Wed, 18 Mar 2026 15:39:14 -0700 Subject: [PATCH 6/6] fix: remove unused sleep import from rate limiting example Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/content/docs/ai/sleep-and-delays.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/ai/sleep-and-delays.mdx b/docs/content/docs/ai/sleep-and-delays.mdx index 95c4ae6b55..69abc1b912 100644 --- a/docs/content/docs/ai/sleep-and-delays.mdx +++ b/docs/content/docs/ai/sleep-and-delays.mdx @@ -162,7 +162,7 @@ Aside from providing `sleep()` as a tool, there are other use cases for Agents t When hitting API rate limits, use `RetryableError` with a delay: ```typescript lineNumbers -import { sleep, RetryableError } from "workflow"; +import { RetryableError } from "workflow"; async function callRateLimitedAPI(endpoint: string) { "use step";