|
| 1 | +import { test, expect } from './fixtures' |
| 2 | + |
| 3 | +/** |
| 4 | + * End-to-end regression coverage for #735: root-level observability |
| 5 | + * `metadata` on `chat()` must never be forwarded onto the provider wire |
| 6 | + * request. |
| 7 | + * |
| 8 | + * In `@tanstack/ai-openrouter` 0.13.x the chat-completions mapper copied |
| 9 | + * root `metadata` into OpenRouter's `chatRequest.metadata`. The |
| 10 | + * `@openrouter/sdk` validates that field as `Record<string, string>` |
| 11 | + * client-side, so structured observability metadata (objects, arrays — |
| 12 | + * the documented usage for middleware/devtools consumers) failed Zod |
| 13 | + * validation before the request ever left the process, killing every |
| 14 | + * call with `RUN_ERROR`. |
| 15 | + * |
| 16 | + * Wire-shape coverage lives in the unit tests |
| 17 | + * `packages/ai-openrouter/tests/openrouter-adapter.test.ts` and |
| 18 | + * `openrouter-responses-adapter.test.ts`, which inspect the request |
| 19 | + * handed to the SDK directly. What this spec covers (which those |
| 20 | + * cannot): the full HTTP path — test → route → `chat()` → adapter → |
| 21 | + * real `@openrouter/sdk` outbound validation — tolerates structured |
| 22 | + * root metadata. Pre-fix, the SDK's own Zod schema rejects the request |
| 23 | + * and the stream emits RUN_ERROR instead of completing. |
| 24 | + */ |
| 25 | +test.describe('root observability metadata — wire path', () => { |
| 26 | + test('chat completes end-to-end on OpenRouter without the root metadata reaching the wire request', async ({ |
| 27 | + request, |
| 28 | + testId, |
| 29 | + aimockPort, |
| 30 | + }) => { |
| 31 | + const body = { |
| 32 | + threadId: 'thread-root-meta-1', |
| 33 | + runId: 'run-root-meta-1', |
| 34 | + state: {}, |
| 35 | + messages: [ |
| 36 | + { id: 'u1', role: 'user', content: '[chat] recommend a guitar' }, |
| 37 | + ], |
| 38 | + tools: [], |
| 39 | + context: [], |
| 40 | + forwardedProps: { |
| 41 | + provider: 'openrouter', |
| 42 | + feature: 'chat', |
| 43 | + testId, |
| 44 | + aimockPort, |
| 45 | + // Opt-in flag handled by `api.chat.ts` — passes structured |
| 46 | + // root-level observability metadata (arrays, nested objects) to |
| 47 | + // `chat()`. The adapter must keep it off the provider request; |
| 48 | + // pre-fix, the SDK's own outbound Zod validation rejects the |
| 49 | + // request before it reaches aimock and the stream ends in |
| 50 | + // RUN_ERROR. |
| 51 | + structuredRootMetadata: true, |
| 52 | + }, |
| 53 | + } |
| 54 | + const response = await request.post('/api/chat', { |
| 55 | + data: body, |
| 56 | + headers: { 'Content-Type': 'application/json' }, |
| 57 | + }) |
| 58 | + expect( |
| 59 | + response.ok(), |
| 60 | + `expected 200, got ${response.status()}: ${await response.text()}`, |
| 61 | + ).toBe(true) |
| 62 | + const text = await response.text() |
| 63 | + expect(text).toContain('RUN_FINISHED') |
| 64 | + // No RUN_ERROR — the @openrouter/sdk's outbound Record<string, string> |
| 65 | + // validation never saw the structured metadata. |
| 66 | + expect(text).not.toContain('RUN_ERROR') |
| 67 | + }) |
| 68 | +}) |
0 commit comments