agent: Add reasoning token support for OpenAI reasoning models

- Extract and display reasoning tokens from both Chat Completions and Responses APIs
- Add smart preflight detection to check reasoning support per model/API (cached per agent)
- Support both reasoning_text (o1/o3) and summary_text (gpt-5) formats
- Display reasoning tokens with  symbol in console and TUI renderers
- Only send reasoning parameters to models that support them
- Fix event type from "thinking" to "reasoning" for consistency

Note: Chat Completions API only returns reasoning token counts, not content (by design).
Only Responses API exposes actual thinking/reasoning events.
This commit is contained in:
Mario Zechner 2025-08-10 00:32:30 +02:00
parent 9157411034
commit 62d9eefc2a
8 changed files with 284 additions and 15 deletions

View file

@ -13,6 +13,7 @@ export class ConsoleRenderer implements AgentEventReceiver {
private lastOutputTokens = 0;
private lastCacheReadTokens = 0;
private lastCacheWriteTokens = 0;
private lastReasoningTokens = 0;
private startAnimation(text: string = "Thinking"): void {
if (this.isAnimating || !this.isTTY) return;
@ -54,6 +55,11 @@ export class ConsoleRenderer implements AgentEventReceiver {
`${this.lastInputTokens.toLocaleString()}${this.lastOutputTokens.toLocaleString()}`,
);
// Add reasoning tokens if present
if (this.lastReasoningTokens > 0) {
metricsText += chalk.dim(`${this.lastReasoningTokens.toLocaleString()}`);
}
// Add cache info if available
if (this.lastCacheReadTokens > 0 || this.lastCacheWriteTokens > 0) {
const cacheText: string[] = [];
@ -96,7 +102,7 @@ export class ConsoleRenderer implements AgentEventReceiver {
this.startAnimation();
break;
case "thinking":
case "reasoning":
this.stopAnimation();
console.log(chalk.dim("[thinking]"));
console.log(chalk.dim(event.text));
@ -162,6 +168,7 @@ export class ConsoleRenderer implements AgentEventReceiver {
this.lastOutputTokens = event.outputTokens;
this.lastCacheReadTokens = event.cacheReadTokens;
this.lastCacheWriteTokens = event.cacheWriteTokens;
this.lastReasoningTokens = event.reasoningTokens;
// Don't stop animation for this event
break;
}

View file

@ -61,6 +61,7 @@ export class TuiRenderer implements AgentEventReceiver {
private lastOutputTokens = 0;
private lastCacheReadTokens = 0;
private lastCacheWriteTokens = 0;
private lastReasoningTokens = 0;
private toolCallCount = 0;
private tokenStatusComponent: TextComponent | null = null;
@ -185,7 +186,7 @@ export class TuiRenderer implements AgentEventReceiver {
this.statusContainer.addChild(this.currentLoadingAnimation);
break;
case "thinking": {
case "reasoning": {
// Show thinking in dim text
const thinkingContainer = new Container();
thinkingContainer.addChild(new TextComponent(chalk.dim("[thinking]")));
@ -264,6 +265,7 @@ export class TuiRenderer implements AgentEventReceiver {
this.lastOutputTokens = event.outputTokens;
this.lastCacheReadTokens = event.cacheReadTokens;
this.lastCacheWriteTokens = event.cacheWriteTokens;
this.lastReasoningTokens = event.reasoningTokens;
this.updateTokenDisplay();
break;
@ -291,6 +293,11 @@ export class TuiRenderer implements AgentEventReceiver {
// Build token display text
let tokenText = chalk.dim(`${this.lastInputTokens.toLocaleString()}${this.lastOutputTokens.toLocaleString()}`);
// Add reasoning tokens if present
if (this.lastReasoningTokens > 0) {
tokenText += chalk.dim(`${this.lastReasoningTokens.toLocaleString()}`);
}
// Add cache info if available
if (this.lastCacheReadTokens > 0 || this.lastCacheWriteTokens > 0) {
const cacheText: string[] = [];