/**
* Cross-provider reasoning preservation via `preserveReasoningAsText`
*
* When an `Agent.prompt()` conversation runs against a "reasoning" model that
* emits `reasoning_content` (OpenAI o-series, DeepSeek reasoner, Anthropic
* extended-thinking-via-Bedrock, etc.), the model's intermediate thought
* stream is normally either:
*
* (a) native-echoed back when the next turn targets the same provider with
* a valid signature (Anthropic, Gemini own-issued path), OR
* (b) silently dropped on the next outbound conversion (everyone else).
*
* Opt into {@link AgentConfig.preserveReasoningAsText} to replace (b) with a
* downgrade — the prior reasoning is wrapped in `...`
* text and prepended to the next assistant message, so the receiving model
* has full context of what was previously considered. See #223 for design.
*
* Run:
* npx tsx examples/patterns/cross-provider-reasoning.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY
* OPENAI_API_KEY (uses an o-series model for the reasoning emit)
*/
import { Agent } from '../../src/index.js'
// --- Caveat: loop-detector interaction --------------------------------------
//
// Some OpenAI-compatible local models (and a few hosted ones at low quality
// tiers) re-emit `...` text back into their assistant
// response as if the tag were an instruction template. When this happens
// across multiple turns, the framework's loop-detector may flag the agent as
// stuck because the assistant text matches turn-over-turn.
//
// Mitigation if you hit this:
// - Set `loopDetection: { enabled: false }` on the AgentConfig (loses the
// safety net for other genuine loop cases — use as a debug measure only).
// - Switch to a model that ignores `` text on input (most o-series
// and Claude models do; problem is mostly with smaller local models).
// - Disable `preserveReasoningAsText` entirely; accept reasoning loss.
//
// The example below does NOT trigger the issue against gpt-5 / Claude, but is
// included so contributors testing local models know what to watch for.
// ----------------------------------------------------------------------------
async function main(): Promise {
if (!process.env['ANTHROPIC_API_KEY'] || !process.env['OPENAI_API_KEY']) {
console.log('Skip: ANTHROPIC_API_KEY and OPENAI_API_KEY both required.')
process.exit(0)
}
// Step 1: a reasoning-capable OpenAI model produces a turn that includes
// a `reasoning_content` chunk. The adapter extracts it into a ReasoningBlock
// with `provenance: 'openai'`, stored in the agent's persistent history.
const agent = new Agent({
name: 'cross-provider-demo',
model: 'gpt-5', // o-series-style reasoning model
provider: 'openai',
systemPrompt: 'You reason carefully then give a short final answer.',
preserveReasoningAsText: true,
// compressReasoningText defaults to `true` whenever preserve is true,
// so long reasoning chains are head+tail truncated to 1200 chars by
// default. Override with `{ minChars: N }` to tune, or `false` to disable
// (footgun — long CoT will eat your prompt budget).
compressReasoningText: { minChars: 1500 },
})
const firstAnswer = await agent.prompt(
'A train leaves Boston at 60 mph heading west. Another leaves NYC at 80 mph heading north. ' +
'After 2 hours, what is the straight-line distance between them? Show your reasoning briefly.',
)
console.log('--- Turn 1 (OpenAI o-series) ---')
console.log(firstAnswer.output.slice(0, 500))
// Step 2: swap the model to Anthropic mid-conversation. Without
// `preserveReasoningAsText`, the prior reasoning would be silently dropped
// because Anthropic's outbound only round-trips its own-signed thinking
// blocks. With opt-in on, the prior OpenAI reasoning is replayed as
// `` text so Claude sees the chain.
//
// (Note: in real code you'd construct a new Agent or use a per-call model
// override. The Agent instance owns its adapter; this example keeps things
// short and just illustrates the persistence model.)
console.log('\n(Hypothetical: if you constructed a second Agent against ' +
'Anthropic and replayed the same messageHistory, the prior OpenAI ' +
'reasoning would arrive as `` text on its first request.)')
}
void main().catch((err: unknown) => {
console.error(err)
process.exit(1)
})