Repository navigation
Conversation
Call OpenRouter from an Activity with the openai client pointed at OpenRouter, client retries off so Temporal owns every attempt, error classification with Retry-After as the next retry delay, heartbeats, and OpenRouter response caching so a retried identical request is billed at zero. The Workflow fans a prompt batch out under bounded concurrency and reports skipped prompts instead of failing the batch.
8814f48 to
bdab719
Compare
main's lockfile carries a pnpm 9 checksum; CI installs pnpm 10, which expects sha256 and refuses a frozen install. Regenerated with pnpm 10.34 so CI can run.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Workflow cancellation and concurrency edge cases are mishandled, and Worker connection configuration can diverge from the starter.
Review effort: Balanced
Findings: 1
Open (4)
What changed in this PR
Adds an OpenRouter sample demonstrating Temporal-managed retries, cached retry requests, and bounded prompt batching.
Changes:
- Implements OpenRouter Activities, Workflow, Worker, and client.
- Adds fake-fetch and Workflow integration tests.
- Registers the sample in documentation, CI, and workspace metadata.
| File | Description |
|---|---|
README.md |
Lists the OpenRouter sample. |
pnpm-lock.yaml |
Adds resolved sample dependencies. |
openrouter/tsconfig.json |
Configures TypeScript compilation. |
openrouter/src/workflows.ts |
Implements bounded prompt batching. |
openrouter/src/worker.ts |
Runs the Worker and Activities. |
openrouter/src/shared.ts |
Defines shared inputs and results. |
openrouter/src/mocha/workflows.test.ts |
Tests batch result handling. |
openrouter/src/mocha/activities.test.ts |
Tests requests, errors, and caching. |
openrouter/src/client.ts |
Starts and displays batch results. |
openrouter/src/activities.ts |
Calls OpenRouter with Temporal retries. |
openrouter/README.md |
Documents setup and behavior. |
openrouter/package.json |
Defines dependencies and scripts. |
openrouter/.prettierrc |
Configures formatting. |
openrouter/.prettierignore |
Excludes generated output. |
openrouter/.post-create |
Provides startup instructions. |
openrouter/.nvmrc |
Selects Node.js 22. |
openrouter/.npmrc |
Disables package-lock generation. |
openrouter/.gitignore |
Excludes generated files. |
openrouter/.eslintrc.js |
Configures linting. |
openrouter/.eslintignore |
Excludes generated files from linting. |
openrouter/.env.example |
Documents environment variables. |
.scripts/list-of-samples.json |
Registers the sample. |
.github/workflows/ci.yml |
Adds the sample to CI. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ry-After dates, reported cost - Rethrow Workflow cancellation instead of recording it as a skipped prompt. - Reject a non-positive maxConcurrency and cap runners at the prompt count. - Worker loads the same env-config connection options as the client. - Parse the HTTP-date form of Retry-After as well as delta-seconds. - A response without usage.cost reports an unknown cost (null), not zero, and the batch total is named reportedCostUsd with its scope documented. - .post-create mentions OPENROUTER_API_KEY; excluded from the shared-file copy.
brianstrauch
left a comment
There was a problem hiding this comment.
Requesting changes for the three issues below: configured namespaces are ignored, in-flight Activity requests do not react to cancellation, and the README promises per-attempt cost records that are not persisted in Workflow history.
Validation: build, lint, and all 10 tests passed. Additional probes with fake OpenRouter responses reproduced the namespace and cancellation issues and verified the retry-history behavior.
…ding - Client and Worker pass the namespace from the loaded connection config. - The OpenRouter request carries the Activity's cancellation signal; an abort caused by cancellation surfaces as CancelledFailure. Test added. - README describes what Event History records versus Worker logs.
…lation coverage - A 402 whose error.metadata.limit_source is openrouter_in_flight_budget is transient per OpenRouter's docs; retry it after Retry-After instead of treating it as out of credits. - openai's APIError.error is already the inner error object, so the message path never matched and every failure message carried the status twice. - The client parses its flags explicitly, rejects unknown ones, and accepts --max-concurrency; .env.example removed since nothing read it. - Empty or non-positive Retry-After values are ignored. - Any error while cancellation is pending surfaces as CancelledFailure. - Cancellation test waits for the request to start instead of sleeping, and a Workflow-level cancellation test was added.
- --model and --max-concurrency require a value; --max-concurrency must be a positive integer, checked client-side instead of silently defaulting. - Only ActivityFailure is recorded as a skipped prompt; anything else propagates. - Non-numeric error codes fall back to 500 instead of becoming a bogus type. - Tests for a plain 4xx via the APIError path and for connection errors propagating unchanged. - README: 408 listed as retryable, two-prompt output matches its command, Other options moved out of the --fail-once explanation.
brianstrauch
left a comment
There was a problem hiding this comment.
Reviewed at 244a83a. Build, lint, and all 19 tests passed. One non-blocking cost-reporting suggestion is noted inline.
reportedCostUsd is a subtotal of known costs; unknownCostCount makes that visible in the result and the client output instead of printing $0 for a batch with unknown charges.



Adds an
openroutersample: a Workflow that fans one Activity out per prompt through OpenRouter's Auto Router and returns answer, model, and reported cost per prompt.The Activity uses the
openaiclient pointed at OpenRouter withmaxRetries: 0, classifies 4xx as non-retryable and 408/429/5xx as retryable, passesRetry-Afterthrough asnextRetryDelay, heartbeats, checks for OpenRouter's 200-with-error body, and sendsX-OpenRouter-Cache: trueso a retried identical request is served from OpenRouter's cache at $0. A--fail-onceflag on the client demonstrates that; verified live with aHITat $0 on attempt 2.Tests use a fake
fetchand a fake Activity; no API key needed. Registered in the CI project list and the sample list. This is the TypeScript port of temporalio/samples-python#366 (prompt_batch); thebudget_gatescenario stays Python-only for now.Note: this also regenerates
pnpm-lock.yamlwith pnpm 10. The lockfile on main still has a pnpm 9packageExtensionsChecksum, and CI installs pnpm 10, which refuses a frozen install with it; main has not had a green CI run since September 1 for that reason.Merge after temporalio/samples-python#366: the README links to the Python
openrouter/prompt_batchandbudget_gatesamples on main.