diff --git a/plugins/tracing/dist/index.mjs b/plugins/tracing/dist/index.mjs index 6c20f5b..756a337 100644 --- a/plugins/tracing/dist/index.mjs +++ b/plugins/tracing/dist/index.mjs @@ -45170,7 +45170,7 @@ var require_src$2 = /* @__PURE__ */ __commonJSMin(((exports) => { })); //#endregion -//#region ../../node_modules/.pnpm/@langfuse+otel@5.4.1_@opentelemetry+api@1.9.1_@opentelemetry+core@2.7.1_@opentelemetry+api@1._ekes344w3htofsunm64fqkhuni/node_modules/@langfuse/otel/dist/index.mjs +//#region ../../node_modules/.pnpm/@langfuse+otel@5.4.1_@opentelemetry+api@1.9.1_@opentelemetry+core@2.7.1_@opentelemetry+_e259bc6b5059078fd5966601016fecbc/node_modules/@langfuse/otel/dist/index.mjs var import_src$1 = require_src$9(); var import_src$2 = require_src$4(); var import_src$3 = require_src$2(); @@ -46727,7 +46727,9 @@ function parseSession(lines) { } if (line.type === "turn_context") { const t = ensureTurn(ts); - t.model = line.payload.model ?? t.model; + const p = line.payload; + t.model = p.model ?? t.model; + t.reasoningEffort = typeof p.reasoning_effort === "string" ? p.reasoning_effort : typeof p.effort === "string" ? p.effort : t.reasoningEffort; t.invocationParams = line.payload; continue; } @@ -46826,6 +46828,7 @@ function parseSession(lines) { if (et === "user_message" && typeof p.message === "string") { if (!turn.userInput) turn.userInput = p.message; } else if (et === "agent_message" && typeof p.message === "string") turn.lastAgentMessage = p.message; + else if (et === "thread_settings_applied" && typeof p.thread_settings?.reasoning_effort === "string") turn.reasoningEffort = p.thread_settings.reasoning_effort; else if (et === "token_count") { if (p.info?.total_token_usage) turn.totalUsage = p.info.total_token_usage; closeStep(ts, p.info?.last_token_usage ?? void 0); @@ -47052,6 +47055,7 @@ async function emitTurn(turn, sessionMeta, ctx) { "codex.turn_id": turn.turnId, "codex.thread_id": sessionMeta.sessionId, "codex.model": turn.model, + "codex.reasoning_effort": turn.reasoningEffort, "codex.model_provider": sessionMeta.modelProvider, "codex.cli_version": sessionMeta.cliVersion, "codex.aborted": turn.aborted, @@ -47069,8 +47073,12 @@ async function emitTurn(turn, sessionMeta, ctx) { input: i === 0 ? turn.userInput != null ? clip(turn.userInput) : void 0 : previousToolResults, output: buildGenerationOutput(step, clip), model: turn.model, + modelParameters: turn.reasoningEffort ? { reasoning_effort: turn.reasoningEffort } : void 0, usageDetails: toUsageDetails(step.usage), - metadata: { "codex.step_index": i } + metadata: { + "codex.step_index": i, + "codex.reasoning_effort": turn.reasoningEffort + } }, { asType: "generation", startTime: new Date(step.startTime), diff --git a/plugins/tracing/src/parse.ts b/plugins/tracing/src/parse.ts index c9b3d5f..2b84a36 100644 --- a/plugins/tracing/src/parse.ts +++ b/plugins/tracing/src/parse.ts @@ -13,6 +13,7 @@ import type { TokenUsage, ToolCall, Turn, + TurnContextPayload, } from "./types.js"; import { isPrimitive, toText } from "./utils.js"; @@ -183,8 +184,14 @@ export function parseSession(lines: RolloutLine[]): { if (line.type === "turn_context") { const t = ensureTurn(ts); - const p = line.payload as { model?: string }; + const p = line.payload as TurnContextPayload; t.model = p.model ?? t.model; + t.reasoningEffort = + typeof p.reasoning_effort === "string" + ? p.reasoning_effort + : typeof p.effort === "string" + ? p.effort + : t.reasoningEffort; t.invocationParams = line.payload as Record; continue; } @@ -305,6 +312,11 @@ export function parseSession(lines: RolloutLine[]): { if (!turn!.userInput) turn!.userInput = p.message; } else if (et === "agent_message" && typeof p.message === "string") { turn!.lastAgentMessage = p.message; + } else if ( + et === "thread_settings_applied" && + typeof p.thread_settings?.reasoning_effort === "string" + ) { + turn!.reasoningEffort = p.thread_settings.reasoning_effort; } else if (et === "token_count") { if (p.info?.total_token_usage) turn!.totalUsage = p.info.total_token_usage; closeStep(ts, p.info?.last_token_usage ?? undefined); diff --git a/plugins/tracing/src/trace.ts b/plugins/tracing/src/trace.ts index 24b4f0e..c112a0c 100644 --- a/plugins/tracing/src/trace.ts +++ b/plugins/tracing/src/trace.ts @@ -229,6 +229,7 @@ async function emitTurn( "codex.turn_id": turn.turnId, "codex.thread_id": sessionMeta.sessionId, "codex.model": turn.model, + "codex.reasoning_effort": turn.reasoningEffort, "codex.model_provider": sessionMeta.modelProvider, "codex.cli_version": sessionMeta.cliVersion, "codex.aborted": turn.aborted, @@ -257,8 +258,14 @@ async function emitTurn( : previousToolResults, output: buildGenerationOutput(step, clip), model: turn.model, + modelParameters: turn.reasoningEffort + ? { reasoning_effort: turn.reasoningEffort } + : undefined, usageDetails: toUsageDetails(step.usage), - metadata: { "codex.step_index": i }, + metadata: { + "codex.step_index": i, + "codex.reasoning_effort": turn.reasoningEffort, + }, }, { asType: "generation", diff --git a/plugins/tracing/src/types.ts b/plugins/tracing/src/types.ts index af8477f..59fe105 100644 --- a/plugins/tracing/src/types.ts +++ b/plugins/tracing/src/types.ts @@ -107,6 +107,8 @@ export type ResponseItem = export type TurnContextPayload = { model?: string; + effort?: string; + reasoning_effort?: string; [key: string]: unknown; }; @@ -123,6 +125,12 @@ export type EventMsgPayload = { type: string; turn_id?: string | null; call_id?: string; + /** thread_settings_applied */ + thread_settings?: { + model?: string; + reasoning_effort?: string; + [key: string]: unknown; + } | null; /** token_count */ info?: { total_token_usage?: TokenUsage; @@ -208,6 +216,7 @@ export type Turn = { startTime: number; endTime: number; model?: string; + reasoningEffort?: string; invocationParams?: Record; userInput?: string; finalOutput?: string; diff --git a/plugins/tracing/test/parse.test.ts b/plugins/tracing/test/parse.test.ts index b01e4ef..8142d38 100644 --- a/plugins/tracing/test/parse.test.ts +++ b/plugins/tracing/test/parse.test.ts @@ -36,6 +36,7 @@ describe("parseSession", () => { expect(turn.completed).toBe(true); expect(turn.aborted).toBe(false); expect(turn.model).toBe("gpt-5.4"); + expect(turn.reasoningEffort).toBe("medium"); expect(turn.userInput).toBe("List the files in the repo"); expect(turn.finalOutput).toBe("There are two files: file1.txt and file2.txt."); expect(turn.totalUsage?.total_tokens).toBe(300); @@ -79,6 +80,38 @@ describe("parseSession", () => { expect(turn.endTime).toBe(Date.parse("2026-06-03T11:00:05.000Z")); }); + it("captures reasoning effort from applied thread settings", () => { + const lines: RolloutLine[] = [ + { timestamp: "2026-06-03T12:00:00.000Z", type: "session_meta", payload: { id: "s" } }, + { + timestamp: "2026-06-03T12:00:01.000Z", + type: "event_msg", + payload: { type: "task_started", turn_id: "t" }, + }, + { + timestamp: "2026-06-03T12:00:01.100Z", + type: "turn_context", + payload: { model: "gpt-5.6-luna", effort: "xhigh" }, + }, + { + timestamp: "2026-06-03T12:00:01.200Z", + type: "event_msg", + payload: { + type: "thread_settings_applied", + thread_settings: { reasoning_effort: "max" }, + }, + }, + { + timestamp: "2026-06-03T12:00:02.000Z", + type: "event_msg", + payload: { type: "task_complete", turn_id: "t" }, + }, + ]; + + const { turns } = parseSession(lines); + expect(turns[0].reasoningEffort).toBe("max"); + }); + it("records subagent threads from sub_agent_activity, ignoring non-started kinds", () => { const event = (ts: string, payload: Record): RolloutLine => ({ timestamp: ts, diff --git a/plugins/tracing/test/trace.test.ts b/plugins/tracing/test/trace.test.ts index c3f66a9..53ac599 100644 --- a/plugins/tracing/test/trace.test.ts +++ b/plugins/tracing/test/trace.test.ts @@ -79,6 +79,7 @@ describe("convertRollout", () => { expect(parentId(root!)).toBeUndefined(); // top-level turn = its own trace expect(attr(root!, "langfuse.observation.input")).toContain("List the files"); expect(attr(root!, "langfuse.observation.output")).toContain("two files"); + expect(attr(root!, "langfuse.observation.metadata.codex.reasoning_effort")).toBe("medium"); // Backdated to the turn's task_started timestamp. expect(startMs(root!)).toBe(Date.parse("2026-06-03T10:00:01.000Z")); @@ -93,6 +94,10 @@ describe("convertRollout", () => { expect(gen.name).toBe("LLM"); expect(parentId(gen)).toBe(root!.spanContext().spanId); expect(attr(gen, "langfuse.observation.model.name")).toBe("gpt-5.4"); + expect(attr(gen, "langfuse.observation.model.parameters")).toBe( + JSON.stringify({ reasoning_effort: "medium" }), + ); + expect(attr(gen, "langfuse.observation.metadata.codex.reasoning_effort")).toBe("medium"); } // Usage is sent in Langfuse's OpenAI-compatible shape. Langfuse then // normalizes the inclusive parent counts and nested detail counts.