Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/world-vercel-caller-user-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/world-vercel': patch
---

Append caller-provided user-agent tokens to the world-vercel user-agent instead of discarding them.
12 changes: 12 additions & 0 deletions packages/world-vercel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ import { setWorld } from '@workflow/core/runtime';
setWorld(createWorld({ dispatcher: new Agent({ connections: 16 }) }));
```

## Caller user agent

Pass a `User-Agent` header to append a caller-specific product token while
preserving the world-vercel token:

```ts
import { createWorld } from '@workflow/world-vercel';

const world = createWorld({
headers: { 'User-Agent': 'my-framework/1.2.3' },
});
```
11 changes: 11 additions & 0 deletions packages/world-vercel/src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ describe('getHeaders', () => {
expect(headers.get('x-workflow-test-limit-overrides')).toBeNull();
});

it('appends a caller user-agent token to the world-vercel user-agent', () => {
const headers = getHeaders(
{ headers: { 'User-Agent': 'eve/0.18.1' } },
{ usingProxy: false }
);

expect(headers.get('User-Agent')).toMatch(
/^@workflow\/world-vercel\/\S+ node-\S+ \S+ \([^)]*\)(?: \S+)? eve\/0\.18\.1$/
);
});

it('forwards WORKFLOW_TEST_LIMIT_OVERRIDES verbatim as x-workflow-test-limit-overrides', () => {
const overrides = '{"STREAM_MAX_DURATION_MS":5000}';
process.env.WORKFLOW_TEST_LIMIT_OVERRIDES = overrides;
Expand Down
16 changes: 15 additions & 1 deletion packages/world-vercel/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,17 @@ export function serializeError<T extends { error?: SerializedData }>(
return data;
}

/**
* Joins User-Agent product tokens with spaces per the RFC 9110 `User-Agent`
* grammar (`product *( RWS ( product / comment ) )`), skipping empty parts.
* Never join UA products with `Headers.append()` — repeated header values
* combine with `", "`, and a comma glued to a product token breaks
* whitespace-delimited parsers on the receiving side.
*/
const joinUserAgentProducts = (
...products: (string | null | undefined)[]
): string => products.filter(Boolean).join(' ');

const getUserAgent = () => {
const deploymentId = process.env.VERCEL_DEPLOYMENT_ID;
if (deploymentId) {
Expand Down Expand Up @@ -239,7 +250,10 @@ export const getHeaders = (
): Headers => {
const projectConfig = config?.projectConfig;
const headers = new Headers(config?.headers);
headers.set('User-Agent', getUserAgent());
headers.set(
'User-Agent',
joinUserAgentProducts(getUserAgent(), headers.get('User-Agent'))
);
const testLimitOverrides = getTestLimitOverridesHeader();
if (testLimitOverrides) {
headers.set(TEST_LIMIT_OVERRIDES_HEADER, testLimitOverrides);
Expand Down
Loading