Skip to content

Commit df923d8

Browse files
jerryliang64claude
andcommitted
feat(agent-tracing): rename TraceSession to Trace and support inputs in createTrace
- Rename `TraceSession` → `Trace`, `createSession` → `createTrace`, `CreateSessionOptions` → `CreateTraceOptions` - Add `inputs` option to `CreateTraceOptions` for injecting user messages into root run inputs - This aligns Claude tracer with LangGraph tracer which auto-populates `run.inputs.messages` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8a7eacc commit df923d8

4 files changed

Lines changed: 93 additions & 41 deletions

File tree

core/agent-tracing/claude.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export * from './index';
2-
export { ClaudeAgentTracer, TraceSession } from './src/ClaudeAgentTracer';
2+
export { ClaudeAgentTracer, Trace } from './src/ClaudeAgentTracer';

core/agent-tracing/src/ClaudeAgentTracer.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,32 @@ import {
1212
type ClaudeContentBlock,
1313
type ClaudeTokenUsage,
1414
type IRunCost,
15-
type CreateSessionOptions,
15+
type CreateTraceOptions,
1616
RunStatus,
1717
type TracerConfig,
1818
applyTracerConfig,
1919
} from './types';
2020

2121
/**
22-
* TraceSession - Manages state for a single agent execution with streaming support.
22+
* Trace - Manages state for a single agent execution with streaming support.
2323
* Allows processing messages one-by-one and logging them immediately.
2424
*/
25-
export class TraceSession {
25+
export class Trace {
2626
private traceId: string;
2727
private threadId?: string;
28+
private inputs?: Record<string, any>;
2829
private rootRun: Run | null = null;
2930
private rootRunId: string;
3031
private startTime: number;
3132
private executionOrder = 2; // Start at 2, root is 1
3233
private pendingToolUses = new Map<string, Run>();
3334
private tracer: ClaudeAgentTracer;
3435

35-
constructor(tracer: ClaudeAgentTracer, options?: CreateSessionOptions) {
36+
constructor(tracer: ClaudeAgentTracer, options?: CreateTraceOptions) {
3637
this.tracer = tracer;
3738
this.traceId = options?.traceId || randomUUID();
3839
this.threadId = options?.threadId;
40+
this.inputs = options?.inputs;
3941
this.rootRunId = randomUUID();
4042
this.startTime = Date.now();
4143
}
@@ -69,6 +71,9 @@ export class TraceSession {
6971
this.threadId = message.session_id;
7072
}
7173
this.rootRun = this.tracer.createRootRunInternal(message, this.startTime, this.traceId, this.rootRunId, this.threadId);
74+
if (this.inputs) {
75+
Object.assign(this.rootRun.inputs, this.inputs);
76+
}
7277
this.tracer.logTrace(this.rootRun, RunStatus.START);
7378
}
7479

@@ -192,7 +197,7 @@ export class TraceSession {
192197
* ClaudeAgentTracer - Converts Claude SDK messages to LangChain Run format
193198
* and logs them to the same remote logging system as LangGraphTracer.
194199
*
195-
* Supports both batch processing (processMessages) and streaming (createSession).
200+
* Supports both batch processing (processMessages) and streaming (createTrace).
196201
*/
197202
@SingletonProto({
198203
accessLevel: AccessLevel.PUBLIC,
@@ -216,26 +221,31 @@ export class ClaudeAgentTracer {
216221
}
217222

218223
/**
219-
* Create a new trace session for streaming message processing.
220-
* Use this for real-time tracing where messages arrive one-by-one.
224+
* Create a new trace for one agent execution.
225+
* Returns a Trace object for streaming message processing.
221226
*
222227
* @param options.traceId - Server-side trace ID for call chain linking. Defaults to a random UUID.
223-
* @param options.threadId - Thread ID (Claude SDK session ID), recorded in metadata.
228+
* @param options.threadId - Thread ID (conversation/session identifier), recorded in metadata.
229+
* @param options.inputs - Additional inputs to merge into root run's inputs (e.g. user messages).
224230
*
225231
* @example
226-
* const session = claudeTracer.createSession({ traceId: ctx.tracer.traceId, threadId });
232+
* const trace = claudeTracer.createTrace({
233+
* traceId: ctx.tracer.traceId,
234+
* threadId,
235+
* inputs: { messages: [{ role: 'user', content: 'hello' }] },
236+
* });
227237
* for await (const message of agent.run('task')) {
228-
* await session.processMessage(message);
238+
* await trace.processMessage(message);
229239
* }
230240
*/
231-
public createSession(options?: CreateSessionOptions): TraceSession {
232-
return new TraceSession(this, options);
241+
public createTrace(options?: CreateTraceOptions): Trace {
242+
return new Trace(this, options);
233243
}
234244

235245
/**
236246
* Main entry point - convert SDK messages to Run trees and log them.
237247
* Use this when you have all messages collected (batch processing).
238-
* For real-time streaming, use createSession() instead.
248+
* For real-time streaming, use createTrace() instead.
239249
*
240250
* Non-tracing message types (tool_progress, stream_event, status, etc.) are automatically filtered out.
241251
*/
@@ -246,17 +256,17 @@ export class ClaudeAgentTracer {
246256
return;
247257
}
248258

249-
// Pre-validate: ensure there is an init message before creating session
259+
// Pre-validate: ensure there is an init message before creating trace
250260
const hasInit = sdkMessages.some(m => m.type === 'system' && 'subtype' in m && m.subtype === 'init');
251261
if (!hasInit) {
252262
this.logger.warn('[ClaudeAgentTracer] No system/init message found');
253263
return;
254264
}
255265

256-
// Delegate to TraceSession for message processing
257-
const session = this.createSession();
266+
// Delegate to Trace for message processing
267+
const trace = this.createTrace();
258268
for (const msg of sdkMessages) {
259-
await session.processMessage(msg);
269+
await trace.processMessage(msg);
260270
}
261271
} catch (e) {
262272
this.logger.warn('[ClaudeAgentTracer] processMessages error:', e);

core/agent-tracing/src/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,14 @@ export const RunStatus = {
132132
} as const;
133133
export type RunStatus = (typeof RunStatus)[keyof typeof RunStatus];
134134

135-
/** Options for creating a new trace session. */
136-
export interface CreateSessionOptions {
135+
/** All context needed to create a trace for one agent execution. */
136+
export interface CreateTraceOptions {
137137
/** Server-side trace ID for linking to the request call chain. Defaults to a random UUID. */
138138
traceId?: string;
139-
/** Thread ID (Claude SDK session ID), recorded in metadata. */
139+
/** Thread ID (conversation/session identifier), recorded in metadata. */
140140
threadId?: string;
141+
/** Additional inputs to merge into root run's inputs (e.g. user messages). */
142+
inputs?: Record<string, any>;
141143
}
142144

143145
/** User-facing config passed to tracer.configure() */

core/agent-tracing/test/ClaudeAgentTracer.test.ts

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
173173
describe('Streaming mode + tool use', () => {
174174
it('should trace tool execution with session.processMessage', async () => {
175175
const { claudeTracer, capturedRuns } = createTestEnv();
176-
const session = claudeTracer.createSession();
176+
const trace = claudeTracer.createTrace();
177177

178178
// Feed messages one-by-one, including noise messages that should be filtered
179179
const messages: SDKMessage[] = [
@@ -186,7 +186,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
186186
];
187187

188188
for (const msg of messages) {
189-
await session.processMessage(msg);
189+
await trace.processMessage(msg);
190190
}
191191

192192
// Root run start + end
@@ -243,7 +243,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
243243
describe('Separate traceId and sessionId', () => {
244244
it('should use provided traceId and record threadId in metadata', async () => {
245245
const { claudeTracer, capturedRuns } = createTestEnv();
246-
const session = claudeTracer.createSession({
246+
const trace = claudeTracer.createTrace({
247247
traceId: 'server-trace-abc',
248248
threadId: 'my-thread-id',
249249
});
@@ -255,7 +255,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
255255
];
256256

257257
for (const msg of messages) {
258-
await session.processMessage(msg);
258+
await trace.processMessage(msg);
259259
}
260260

261261
// All runs should use the provided traceId
@@ -272,10 +272,10 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
272272

273273
it('should fallback threadId to init message session_id when not provided', async () => {
274274
const { claudeTracer, capturedRuns } = createTestEnv();
275-
const session = claudeTracer.createSession({ traceId: 'server-trace-xyz' });
275+
const trace = claudeTracer.createTrace({ traceId: 'server-trace-xyz' });
276276

277-
await session.processMessage(createMockInit());
278-
await session.processMessage(createMockResult());
277+
await trace.processMessage(createMockInit());
278+
await trace.processMessage(createMockResult());
279279

280280
const rootStart = capturedRuns.find(e => !e.run.parent_run_id && e.status === RunStatus.START);
281281
assert(rootStart);
@@ -287,6 +287,46 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
287287
});
288288
});
289289

290+
describe('Trace inputs in root run', () => {
291+
it('should merge inputs into root run inputs when provided via createTrace', async () => {
292+
const { claudeTracer, capturedRuns } = createTestEnv();
293+
const trace = claudeTracer.createTrace({
294+
inputs: { messages: [{ role: 'user', content: 'Hello, what is 1+1?' }] },
295+
});
296+
297+
const messages: SDKMessage[] = [
298+
createMockInit(),
299+
createMockAssistantTextOnly(),
300+
createMockResult(),
301+
];
302+
303+
for (const msg of messages) {
304+
await trace.processMessage(msg);
305+
}
306+
307+
const rootStart = capturedRuns.find(e => !e.run.parent_run_id && e.status === RunStatus.START);
308+
assert(rootStart, 'Should have root_run start');
309+
assert.deepStrictEqual(
310+
(rootStart.run.inputs as any).messages,
311+
[{ role: 'user', content: 'Hello, what is 1+1?' }],
312+
);
313+
// Original init fields should still be present
314+
assert.deepStrictEqual((rootStart.run.inputs as any).tools, [ 'Bash', 'Read' ]);
315+
});
316+
317+
it('should not have extra inputs when not provided', async () => {
318+
const { claudeTracer, capturedRuns } = createTestEnv();
319+
const trace = claudeTracer.createTrace();
320+
321+
await trace.processMessage(createMockInit());
322+
await trace.processMessage(createMockResult());
323+
324+
const rootStart = capturedRuns.find(e => !e.run.parent_run_id && e.status === RunStatus.START);
325+
assert(rootStart, 'Should have root_run start');
326+
assert.strictEqual((rootStart.run.inputs as any).messages, undefined);
327+
});
328+
});
329+
290330
describe('Batch mode + text-only', () => {
291331
it('should trace a text-only response via processMessages', async () => {
292332
const { claudeTracer, capturedRuns } = createTestEnv();
@@ -341,7 +381,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
341381
describe('Error scenario', () => {
342382
it('should trace an error result with ERROR status', async () => {
343383
const { claudeTracer, capturedRuns } = createTestEnv();
344-
const session = claudeTracer.createSession();
384+
const trace = claudeTracer.createTrace();
345385

346386
const messages: SDKMessage[] = [
347387
createMockInit(),
@@ -370,7 +410,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
370410
];
371411

372412
for (const msg of messages) {
373-
await session.processMessage(msg);
413+
await trace.processMessage(msg);
374414
}
375415

376416
// Root run should end with ERROR status
@@ -383,21 +423,21 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
383423
describe('Guard clauses — messages before init', () => {
384424
it('should warn and ignore assistant message received before init', async () => {
385425
const { claudeTracer, capturedRuns } = createTestEnv();
386-
const session = claudeTracer.createSession();
426+
const trace = claudeTracer.createTrace();
387427

388428
// Send assistant message without a preceding init
389-
await session.processMessage(createMockAssistantWithTool());
429+
await trace.processMessage(createMockAssistantWithTool());
390430

391431
// Nothing should have been traced
392432
assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured before init');
393433
});
394434

395435
it('should warn and ignore result message received before init', async () => {
396436
const { claudeTracer, capturedRuns } = createTestEnv();
397-
const session = claudeTracer.createSession();
437+
const trace = claudeTracer.createTrace();
398438

399439
// Send result message without a preceding init
400-
await session.processMessage(createMockResult());
440+
await trace.processMessage(createMockResult());
401441

402442
// Nothing should have been traced
403443
assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured before init');
@@ -407,7 +447,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
407447
describe('Pending tool runs cleanup on result', () => {
408448
it('should log ERROR for pending tool runs that never received a result', async () => {
409449
const { claudeTracer, capturedRuns } = createTestEnv();
410-
const session = claudeTracer.createSession();
450+
const trace = claudeTracer.createTrace();
411451

412452
// Init → assistant calls a tool → result arrives WITHOUT the tool_result
413453
const messages: SDKMessage[] = [
@@ -417,7 +457,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
417457
];
418458

419459
for (const msg of messages) {
420-
await session.processMessage(msg);
460+
await trace.processMessage(msg);
421461
}
422462

423463
// The pending tool run should have been force-closed with ERROR status
@@ -450,7 +490,7 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
450490
describe('Internal error handling', () => {
451491
it('should catch errors thrown inside processMessage without propagating', async () => {
452492
const { claudeTracer } = createTestEnv();
453-
const session = claudeTracer.createSession();
493+
const trace = claudeTracer.createTrace();
454494

455495
// Force an error by replacing logTrace with a throwing stub
456496
(claudeTracer as any).tracingService = {
@@ -461,16 +501,16 @@ describe('test/ClaudeAgentTracer.test.ts', () => {
461501

462502
// Should NOT throw — error is swallowed by the catch block
463503
await assert.doesNotReject(async () => {
464-
await session.processMessage(createMockInit());
504+
await trace.processMessage(createMockInit());
465505
});
466506
});
467507

468508
it('should catch errors thrown inside processMessages without propagating', async () => {
469509
const { claudeTracer } = createTestEnv();
470510

471-
// Replace createSession with a throwing stub to trigger the outer catch
472-
(claudeTracer as any).createSession = () => {
473-
throw new Error('unexpected createSession error');
511+
// Replace createTrace with a throwing stub to trigger the outer catch
512+
(claudeTracer as any).createTrace = () => {
513+
throw new Error('unexpected createTrace error');
474514
};
475515

476516
// Should NOT throw — error is swallowed by the catch block

0 commit comments

Comments
 (0)