Skip to content

Commit a044ea3

Browse files
adrian-cowhamclaudetoubatbrian
authored
(inference): add debug metadata headers to inference requests (#1208)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Brian Yin <brian.yin@livekit.io>
1 parent c773698 commit a044ea3

5 files changed

Lines changed: 48 additions & 9 deletions

File tree

.changeset/rare-impalas-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@livekit/agents": patch
3+
---
4+
5+
(inference): add debug metadata headers to inference requests

agents/src/inference/interruption/http_transport.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TransformStream } from 'stream/web';
77
import { z } from 'zod';
88
import { APIConnectionError, APIError, APIStatusError, isAPIError } from '../../_exceptions.js';
99
import { log } from '../../log.js';
10-
import { createAccessToken } from '../utils.js';
10+
import { buildMetadataHeaders, createAccessToken } from '../utils.js';
1111
import { InterruptionCacheEntry } from './interruption_cache_entry.js';
1212
import type { OverlappingSpeechEvent } from './types.js';
1313
import type { BoundedCache } from './utils.js';
@@ -55,6 +55,7 @@ export async function predictHTTP(
5555
const response = await ofetch(url.toString(), {
5656
retry: 0,
5757
headers: {
58+
...buildMetadataHeaders(),
5859
'Content-Type': 'application/octet-stream',
5960
Authorization: `Bearer ${options.token}`,
6061
},

agents/src/inference/interruption/ws_transport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { z } from 'zod';
88
import { APIConnectionError, APIStatusError, APITimeoutError } from '../../_exceptions.js';
99
import { log } from '../../log.js';
1010
import TypedPromise from '../../typed_promise.js';
11-
import { createAccessToken } from '../utils.js';
11+
import { buildMetadataHeaders, createAccessToken } from '../utils.js';
1212
import { InterruptionCacheEntry } from './interruption_cache_entry.js';
1313
import type { OverlappingSpeechEvent } from './types.js';
1414
import type { BoundedCache } from './utils.js';
@@ -80,7 +80,7 @@ async function connectWebSocket(
8080
const url = `${baseUrl}/bargein`;
8181

8282
const ws = new WebSocket(url, {
83-
headers: { Authorization: `Bearer ${token}` },
83+
headers: { ...buildMetadataHeaders(), Authorization: `Bearer ${token}` },
8484
});
8585

8686
await new TypedPromise<void, APIStatusError | APITimeoutError | APIConnectionError>(

agents/src/inference/llm.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import * as llm from '../llm/index.js';
77
import type { APIConnectOptions } from '../types.js';
88
import { DEFAULT_API_CONNECT_OPTIONS } from '../types.js';
99
import { type Expand, toError } from '../utils.js';
10-
import { type AnyString, createAccessToken, getDefaultInferenceUrl } from './utils.js';
10+
import {
11+
type AnyString,
12+
buildMetadataHeaders,
13+
createAccessToken,
14+
getDefaultInferenceUrl,
15+
} from './utils.js';
1116

1217
export type OpenAIModels =
1318
| 'openai/gpt-5.4'
@@ -323,13 +328,14 @@ export class LLMStream extends llm.LLMStream {
323328
);
324329
}
325330

331+
const extraHeaders: Record<string, string> = {
332+
...buildMetadataHeaders(),
333+
...((requestOptions.extra_headers as Record<string, string> | undefined) ?? {}),
334+
};
326335
if (this.provider) {
327-
const extraHeaders = requestOptions.extra_headers
328-
? (requestOptions.extra_headers as Record<string, string>)
329-
: {};
330336
extraHeaders['X-LiveKit-Inference-Provider'] = this.provider;
331-
requestOptions.extra_headers = extraHeaders;
332337
}
338+
requestOptions.extra_headers = extraHeaders;
333339

334340
const stream = await this.client.chat.completions.create(
335341
{

agents/src/inference/utils.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import { AccessToken } from 'livekit-server-sdk';
55
import { WebSocket } from 'ws';
66
import { APIConnectionError, APIStatusError } from '../_exceptions.js';
7+
import { getJobContext } from '../job.js';
8+
import { version } from '../version.js';
79

810
export type AnyString = string & NonNullable<unknown>;
911

@@ -46,13 +48,38 @@ export async function createAccessToken(
4648
return await token.toJwt();
4749
}
4850

51+
/**
52+
* Build metadata headers for inference requests.
53+
* Includes SDK version/platform, and optionally room/job IDs from the current job context.
54+
*/
55+
export function buildMetadataHeaders(): Record<string, string> {
56+
const headers: Record<string, string> = {
57+
'User-Agent': `livekit-agents-js/${version} (node ${process.version})`,
58+
};
59+
60+
try {
61+
const ctx = getJobContext();
62+
const roomSid = ctx.job.room?.sid;
63+
if (roomSid) {
64+
headers['X-LiveKit-Room-Id'] = roomSid;
65+
}
66+
if (ctx.job.id) {
67+
headers['X-LiveKit-Job-Id'] = ctx.job.id;
68+
}
69+
} catch {
70+
// No job context available — standalone inference usage
71+
}
72+
73+
return headers;
74+
}
75+
4976
export async function connectWs(
5077
url: string,
5178
headers: Record<string, string>,
5279
timeoutMs: number,
5380
): Promise<WebSocket> {
5481
return new Promise<WebSocket>((resolve, reject) => {
55-
const socket = new WebSocket(url, { headers: headers });
82+
const socket = new WebSocket(url, { headers: { ...buildMetadataHeaders(), ...headers } });
5683

5784
const timeout = setTimeout(() => {
5885
reject(new APIConnectionError({ message: 'Timeout connecting to LiveKit WebSocket' }));

0 commit comments

Comments
 (0)