diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc1263a70a..9a24db2ead6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai - Codex app-server: stream commentary preambles into editable channel progress drafts without promoting them to final answers. - Codex migration: remove the bundled `codex-cli` backend and repair legacy `codex-cli/*` model refs to the Codex app-server route on `openai/*`. - Gateway/startup: add owner-level startup trace attribution for auth, plugin loading, lookup counts, and plugin sidecar services. (#81738) Thanks @samzong. +- Plugins/hooks: expose the resolved effective `contextTokenBudget` plus source/reference metadata on `llm_output` and sanitized `model_call_*` hook events/contexts so plugin cost and context-health alerts can use agent-level context caps. Fixes #64327. Thanks @BunsDev. - Channels/status reactions: wire `StatusReactionController` into WhatsApp message turns (queued → thinking → tool → done/error lifecycle, on par with Telegram and Discord), add `deploy`/`build`/`concierge` emoji categories with tool-token routing, and replace the status reaction defaults with self-explanatory emoji (🧠 thinking, 🛠️ tool, 💻 coding, 🌐 web, ⏳ stallSoft, ⚠️ stallHard, ✅ done, ❌ error, 🗜️ compacting) so stall and lifecycle reactions read as status indicators instead of emotional commentary. Fixes #59077. (#80612) Thanks @gado-ships-it. - Control UI: add a browser-local Text size setting in Appearance and Quick Settings, scaling chat and dense UI text while keeping inputs above the mobile Safari focus-zoom threshold. Fixes #8547. Thanks @BunsDev. - Docs: add a dedicated ds4 provider page with local DeepSeek V4 Flash config, on-demand startup, context sizing, and live verification steps. diff --git a/docs/plugins/hooks.md b/docs/plugins/hooks.md index 05cf023583b..22a50f38d3f 100644 --- a/docs/plugins/hooks.md +++ b/docs/plugins/hooks.md @@ -114,7 +114,7 @@ observation-only. - `model_call_started` / `model_call_ended` - observe sanitized provider/model call metadata, timing, outcome, and bounded request-id hashes without prompt or response content - `llm_input` - observe provider input (system prompt, prompt, history) -- `llm_output` - observe provider output +- `llm_output` - observe provider output, usage, and the resolved `contextTokenBudget` when available **Tools** @@ -287,7 +287,11 @@ that should not receive raw prompts, history, responses, headers, request bodies, or provider request IDs. These hooks include stable metadata such as `runId`, `callId`, `provider`, `model`, optional `api`/`transport`, terminal `durationMs`/`outcome`, and `upstreamRequestIdHash` when OpenClaw can derive a -bounded provider request-id hash. +bounded provider request-id hash. When the runtime has resolved context-window +metadata, the hook event and context also include `contextTokenBudget`, the +effective token budget after model/config/agent caps, plus +`contextWindowSource` and `contextWindowReferenceTokens` when a lower cap was +applied. `before_agent_finalize` runs only when a harness is about to accept a natural final assistant answer. It is not the `/stop` cancellation path and does not diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 74cc777c90a..28e35b2b672 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -62,6 +62,12 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt provider: "codex", modelId: "gpt-5.4-codex", model: createCodexTestModel("codex"), + contextTokenBudget: 150_000, + contextWindowInfo: { + tokens: 150_000, + referenceTokens: 200_000, + source: "agentContextTokens", + }, thinkLevel: "medium", disableTools: true, timeoutMs: 5_000, @@ -2647,19 +2653,34 @@ describe("runCodexAppServerAttempt", () => { resolvedRef?: string; runId?: string; sessionId?: string; + contextTokenBudget?: number; + contextWindowSource?: string; + contextWindowReferenceTokens?: number; + }, + { + runId?: string; + sessionId?: string; + contextTokenBudget?: number; + contextWindowSource?: string; + contextWindowReferenceTokens?: number; }, - { runId?: string; sessionId?: string }, ]; expect(llmOutputPayload.runId).toBe("run-1"); expect(llmOutputPayload.sessionId).toBe("session-1"); expect(llmOutputPayload.provider).toBe("codex"); expect(llmOutputPayload.model).toBe("gpt-5.4-codex"); + expect(llmOutputPayload.contextTokenBudget).toBe(150_000); + expect(llmOutputPayload.contextWindowSource).toBe("agentContextTokens"); + expect(llmOutputPayload.contextWindowReferenceTokens).toBe(200_000); expect(llmOutputPayload.resolvedRef).toBe("codex/gpt-5.4-codex"); expect(llmOutputPayload.harnessId).toBe("codex"); expect(llmOutputPayload.assistantTexts).toEqual(["hello back"]); expect(llmOutputPayload.lastAssistant?.role).toBe("assistant"); expect(llmOutputContext.runId).toBe("run-1"); expect(llmOutputContext.sessionId).toBe("session-1"); + expect(llmOutputContext.contextTokenBudget).toBe(150_000); + expect(llmOutputContext.contextWindowSource).toBe("agentContextTokens"); + expect(llmOutputContext.contextWindowReferenceTokens).toBe(200_000); const [agentEndPayload, agentEndContext] = mockCall(agentEnd, "agent_end") as [ { messages?: Array<{ role?: string }>; success?: boolean }, { runId?: string; sessionId?: string }, diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index cfba40ee4f6..e43f91073a2 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -561,6 +561,19 @@ export async function runCodexAppServerAttempt( }); const hadSessionFile = await pathExists(params.sessionFile); let historyMessages = (await readMirroredSessionHistoryMessages(params.sessionFile)) ?? []; + const hookContextWindowFields = { + ...(params.contextWindowInfo?.tokens + ? { contextTokenBudget: params.contextWindowInfo.tokens } + : params.contextTokenBudget + ? { contextTokenBudget: params.contextTokenBudget } + : {}), + ...(params.contextWindowInfo?.source + ? { contextWindowSource: params.contextWindowInfo.source } + : {}), + ...(params.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: params.contextWindowInfo.referenceTokens } + : {}), + }; const hookContext = { runId: params.runId, agentId: sessionAgentId, @@ -570,6 +583,7 @@ export async function runCodexAppServerAttempt( messageProvider: params.messageProvider ?? undefined, trigger: params.trigger, channelId: params.messageChannel ?? params.messageProvider ?? undefined, + ...hookContextWindowFields, }; if (activeContextEngine) { await bootstrapHarnessContextEngine({ @@ -1557,6 +1571,7 @@ export async function runCodexAppServerAttempt( sessionId: params.sessionId, provider: params.provider, model: params.modelId, + ...hookContextWindowFields, resolvedRef: params.runtimePlan?.observability.resolvedRef ?? `${params.provider}/${params.modelId}`, ...(params.runtimePlan?.observability.harnessId @@ -1798,6 +1813,7 @@ export async function runCodexAppServerAttempt( sessionId: params.sessionId, provider: params.provider, model: params.modelId, + ...hookContextWindowFields, resolvedRef: params.runtimePlan?.observability.resolvedRef ?? `${params.provider}/${params.modelId}`, ...(params.runtimePlan?.observability.harnessId diff --git a/src/agents/cli-runner.reliability.test.ts b/src/agents/cli-runner.reliability.test.ts index eb23e5e20b7..3a104711e23 100644 --- a/src/agents/cli-runner.reliability.test.ts +++ b/src/agents/cli-runner.reliability.test.ts @@ -147,6 +147,11 @@ function buildPreparedContext(params?: { reusableCliSession: params?.cliSessionId ? { sessionId: params.cliSessionId } : {}, modelId: "gpt-5.4", normalizedModel: "gpt-5.4", + contextWindowInfo: { + tokens: 150_000, + referenceTokens: 200_000, + source: "agentContextTokens", + }, systemPrompt: "You are a helpful assistant.", systemPromptReport: {} as PreparedCliRunContext["systemPromptReport"], bootstrapPromptWarningLines: [], @@ -665,13 +670,22 @@ describe("runCliAgent reliability", () => { expect(llmOutputEvent.sessionId).toBe("s1"); expect(llmOutputEvent.provider).toBe("codex-cli"); expect(llmOutputEvent.model).toBe("gpt-5.4"); + expect(llmOutputEvent.contextTokenBudget).toBe(150_000); + expect(llmOutputEvent.contextWindowSource).toBe("agentContextTokens"); + expect(llmOutputEvent.contextWindowReferenceTokens).toBe(200_000); expect(llmOutputEvent.assistantTexts).toEqual(["hello from cli"]); const lastAssistant = requireRecord(llmOutputEvent.lastAssistant, "last assistant"); expect(lastAssistant.role).toBe("assistant"); expect(lastAssistant.content).toEqual([{ type: "text", text: "hello from cli" }]); expect(lastAssistant.provider).toBe("codex-cli"); expect(lastAssistant.model).toBe("gpt-5.4"); - expect(callArg(hookRunner.runLlmOutput, 0, 1, "llm_output context")).toBeTypeOf("object"); + const llmOutputContext = requireRecord( + callArg(hookRunner.runLlmOutput, 0, 1, "llm_output context"), + "llm_output context", + ); + expect(llmOutputContext.contextTokenBudget).toBe(150_000); + expect(llmOutputContext.contextWindowSource).toBe("agentContextTokens"); + expect(llmOutputContext.contextWindowReferenceTokens).toBe(200_000); const agentEndEvent = requireRecord( callArg(hookRunner.runAgentEnd, 0, 0, "agent_end event"), diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index dbbf29041e4..5b4842a7430 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -166,6 +166,15 @@ export async function runPreparedCliAgent( sessionId: params.sessionId, workspaceDir: params.workspaceDir, trigger: params.trigger, + ...(context.contextWindowInfo?.tokens + ? { contextTokenBudget: context.contextWindowInfo.tokens } + : {}), + ...(context.contextWindowInfo?.source + ? { contextWindowSource: context.contextWindowInfo.source } + : {}), + ...(context.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: context.contextWindowInfo.referenceTokens } + : {}), ...buildAgentHookContextChannelFields(params), } as const; @@ -308,6 +317,15 @@ export async function runPreparedCliAgent( sessionId: params.sessionId, provider: params.provider, model: context.modelId, + ...(context.contextWindowInfo?.tokens + ? { contextTokenBudget: context.contextWindowInfo.tokens } + : {}), + ...(context.contextWindowInfo?.source + ? { contextWindowSource: context.contextWindowInfo.source } + : {}), + ...(context.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: context.contextWindowInfo.referenceTokens } + : {}), resolvedRef: `${params.provider}/${context.modelId}`, assistantTexts, ...(lastAssistant ? { lastAssistant } : {}), diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index d776ba66437..c2c5606051b 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -30,6 +30,8 @@ import { CLI_AUTH_EPOCH_VERSION, resolveCliAuthEpoch } from "../cli-auth-epoch.j import { resolveCliBackendConfig } from "../cli-backends.js"; import { hashCliSessionText, resolveCliSessionReuse } from "../cli-session.js"; import { claudeCliSessionTranscriptHasContent } from "../command/attempt-execution.helpers.js"; +import { resolveContextWindowInfo } from "../context-window-guard.js"; +import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { resolveHeartbeatPromptForSystemPrompt } from "../heartbeat-system-prompt.js"; import { resolveBootstrapMaxChars, @@ -156,6 +158,12 @@ export async function prepareCliRunContext( const modelId = (params.model ?? "default").trim() || "default"; const normalizedModel = normalizeCliModel(modelId, backendResolved.config); const modelDisplay = `${params.provider}/${modelId}`; + const contextWindowInfo = resolveContextWindowInfo({ + cfg: params.config, + provider: params.provider, + modelId, + defaultTokens: DEFAULT_CONTEXT_TOKENS, + }); const sessionLabel = params.sessionKey ?? params.sessionId; const { bootstrapFiles, contextFiles } = await prepareDeps.resolveBootstrapContextForRun({ @@ -471,6 +479,7 @@ export async function prepareCliRunContext( reusableCliSession, modelId, normalizedModel, + contextWindowInfo, systemPrompt, systemPromptReport, bootstrapPromptWarningLines: bootstrapPromptWarning.lines, diff --git a/src/agents/cli-runner/types.ts b/src/agents/cli-runner/types.ts index b44ce951b11..15d35e4b8dc 100644 --- a/src/agents/cli-runner/types.ts +++ b/src/agents/cli-runner/types.ts @@ -10,6 +10,7 @@ import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js"; import type { InputProvenance } from "../../sessions/input-provenance.js"; import type { BootstrapContextMode } from "../bootstrap-files.js"; import type { ResolvedCliBackend } from "../cli-backends.js"; +import type { ContextWindowInfo } from "../context-window-guard.js"; import type { CurrentTurnPromptContext, EmbeddedRunTrigger, @@ -114,6 +115,7 @@ export type PreparedCliRunContext = { reusableCliSession: CliReusableSession; modelId: string; normalizedModel: string; + contextWindowInfo?: ContextWindowInfo; systemPrompt: string; systemPromptReport: SessionSystemPromptReport; bootstrapPromptWarningLines: string[]; diff --git a/src/agents/harness/hook-context.ts b/src/agents/harness/hook-context.ts index 25a9bf19c9b..8d353a8eb9a 100644 --- a/src/agents/harness/hook-context.ts +++ b/src/agents/harness/hook-context.ts @@ -1,4 +1,7 @@ -import type { PluginHookAgentContext } from "../../plugins/hook-types.js"; +import type { + PluginHookAgentContext, + PluginHookContextWindowSource, +} from "../../plugins/hook-types.js"; export type AgentHarnessHookContext = { runId: string; @@ -12,6 +15,9 @@ export type AgentHarnessHookContext = { messageProvider?: string; trigger?: string; channelId?: string; + contextTokenBudget?: number; + contextWindowSource?: PluginHookContextWindowSource; + contextWindowReferenceTokens?: number; }; export function buildAgentHookContext(params: AgentHarnessHookContext): PluginHookAgentContext { @@ -27,5 +33,10 @@ export function buildAgentHookContext(params: AgentHarnessHookContext): PluginHo ...(params.messageProvider ? { messageProvider: params.messageProvider } : {}), ...(params.trigger ? { trigger: params.trigger } : {}), ...(params.channelId ? { channelId: params.channelId } : {}), + ...(params.contextTokenBudget ? { contextTokenBudget: params.contextTokenBudget } : {}), + ...(params.contextWindowSource ? { contextWindowSource: params.contextWindowSource } : {}), + ...(params.contextWindowReferenceTokens + ? { contextWindowReferenceTokens: params.contextWindowReferenceTokens } + : {}), }; } diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts index 2d1bd074c9f..de33df550d5 100644 --- a/src/agents/pi-embedded-runner/run.ts +++ b/src/agents/pi-embedded-runner/run.ts @@ -1288,6 +1288,7 @@ export async function runEmbeddedPiAgent( allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, contextEngine, contextTokenBudget: ctxInfo.tokens, + contextWindowInfo: ctxInfo, skillsSnapshot: params.skillsSnapshot, prompt, transcriptPrompt: params.transcriptPrompt, diff --git a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts index 261f3ba3c80..1204cc49932 100644 --- a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.test.ts @@ -390,6 +390,9 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => { model: "gpt-5.4", api: "openai-responses", transport: "http", + contextTokenBudget: 150_000, + contextWindowSource: "agentContextTokens", + contextWindowReferenceTokens: 200_000, trace: createDiagnosticTraceContext(), nextCallId: () => "call-hook", }, @@ -413,16 +416,25 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => { expect(startedEvent.model).toBe("gpt-5.4"); expect(startedEvent.api).toBe("openai-responses"); expect(startedEvent.transport).toBe("http"); + expect(startedEvent.contextTokenBudget).toBe(150_000); + expect(startedEvent.contextWindowSource).toBe("agentContextTokens"); + expect(startedEvent.contextWindowReferenceTokens).toBe(200_000); const startedCtx = requireMockRecordArg(started, 0, 1, "started hook context"); expect(startedCtx.runId).toBe("run-1"); expect(startedCtx.sessionKey).toBe("session-key"); expect(startedCtx.sessionId).toBe("session-id"); expect(startedCtx.modelProviderId).toBe("openai"); expect(startedCtx.modelId).toBe("gpt-5.4"); + expect(startedCtx.contextTokenBudget).toBe(150_000); + expect(startedCtx.contextWindowSource).toBe("agentContextTokens"); + expect(startedCtx.contextWindowReferenceTokens).toBe(200_000); const endedEvent = requireMockRecordArg(ended, 0, 0, "ended hook event"); expect(endedEvent.runId).toBe("run-1"); expect(endedEvent.callId).toBe("call-hook"); expect(endedEvent.outcome).toBe("completed"); + expect(endedEvent.contextTokenBudget).toBe(150_000); + expect(endedEvent.contextWindowSource).toBe("agentContextTokens"); + expect(endedEvent.contextWindowReferenceTokens).toBe(200_000); expectNumberField(endedEvent, "durationMs"); expectNumberField(endedEvent, "responseStreamBytes"); expectNumberField(endedEvent, "timeToFirstByteMs"); diff --git a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts index 1a1fbf0a550..88ca6d66217 100644 --- a/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts +++ b/src/agents/pi-embedded-runner/run/attempt.model-diagnostic-events.ts @@ -19,6 +19,7 @@ import { import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js"; import type { PluginHookAgentContext, + PluginHookContextWindowSource, PluginHookModelCallEndedEvent, PluginHookModelCallStartedEvent, } from "../../../plugins/hook-types.js"; @@ -33,6 +34,9 @@ type ModelCallDiagnosticContext = { model: string; api?: string; transport?: string; + contextTokenBudget?: number; + contextWindowSource?: PluginHookContextWindowSource; + contextWindowReferenceTokens?: number; trace: DiagnosticTraceContext; nextCallId: () => string; onStarted?: () => void; @@ -150,6 +154,11 @@ function baseModelCallEvent( model: ctx.model, ...(ctx.api && { api: ctx.api }), ...(ctx.transport && { transport: ctx.transport }), + ...(ctx.contextTokenBudget ? { contextTokenBudget: ctx.contextTokenBudget } : {}), + ...(ctx.contextWindowSource ? { contextWindowSource: ctx.contextWindowSource } : {}), + ...(ctx.contextWindowReferenceTokens + ? { contextWindowReferenceTokens: ctx.contextWindowReferenceTokens } + : {}), trace, }; } @@ -189,6 +198,13 @@ function modelCallHookEventBase(eventBase: ModelCallEventBase): PluginHookModelC model: eventBase.model, ...(eventBase.api ? { api: eventBase.api } : {}), ...(eventBase.transport ? { transport: eventBase.transport } : {}), + ...(eventBase.contextTokenBudget ? { contextTokenBudget: eventBase.contextTokenBudget } : {}), + ...(eventBase.contextWindowSource + ? { contextWindowSource: eventBase.contextWindowSource } + : {}), + ...(eventBase.contextWindowReferenceTokens + ? { contextWindowReferenceTokens: eventBase.contextWindowReferenceTokens } + : {}), }; } @@ -200,6 +216,13 @@ function modelCallHookContext(eventBase: ModelCallEventBase): PluginHookAgentCon ...(eventBase.sessionId ? { sessionId: eventBase.sessionId } : {}), modelProviderId: eventBase.provider, modelId: eventBase.model, + ...(eventBase.contextTokenBudget ? { contextTokenBudget: eventBase.contextTokenBudget } : {}), + ...(eventBase.contextWindowSource + ? { contextWindowSource: eventBase.contextWindowSource } + : {}), + ...(eventBase.contextWindowReferenceTokens + ? { contextWindowReferenceTokens: eventBase.contextWindowReferenceTokens } + : {}), }) as PluginHookAgentContext; } diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index bc741295de1..5f8d495709c 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -2447,6 +2447,15 @@ export async function runEmbeddedAttempt( model: params.modelId, api: params.model.api, transport: effectiveAgentTransport, + ...(params.contextWindowInfo?.tokens + ? { contextTokenBudget: params.contextWindowInfo.tokens } + : {}), + ...(params.contextWindowInfo?.source + ? { contextWindowSource: params.contextWindowInfo.source } + : {}), + ...(params.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: params.contextWindowInfo.referenceTokens } + : {}), trace: runTrace, nextCallId: () => `${params.runId}:model:${(diagnosticModelCallSeq += 1)}`, onStarted: () => { @@ -4006,6 +4015,15 @@ export async function runEmbeddedAttempt( sessionId: params.sessionId, provider: params.provider, model: params.modelId, + ...(params.contextWindowInfo?.tokens + ? { contextTokenBudget: params.contextWindowInfo.tokens } + : {}), + ...(params.contextWindowInfo?.source + ? { contextWindowSource: params.contextWindowInfo.source } + : {}), + ...(params.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: params.contextWindowInfo.referenceTokens } + : {}), resolvedRef: params.runtimePlan?.observability.resolvedRef ?? `${params.provider}/${params.modelId}`, @@ -4024,6 +4042,15 @@ export async function runEmbeddedAttempt( sessionId: params.sessionId, workspaceDir: params.workspaceDir, trigger: params.trigger, + ...(params.contextWindowInfo?.tokens + ? { contextTokenBudget: params.contextWindowInfo.tokens } + : {}), + ...(params.contextWindowInfo?.source + ? { contextWindowSource: params.contextWindowInfo.source } + : {}), + ...(params.contextWindowInfo?.referenceTokens + ? { contextWindowReferenceTokens: params.contextWindowInfo.referenceTokens } + : {}), ...buildAgentHookContextChannelFields(params), }, ) diff --git a/src/agents/pi-embedded-runner/run/types.ts b/src/agents/pi-embedded-runner/run/types.ts index b0c13cccf83..5517d444abb 100644 --- a/src/agents/pi-embedded-runner/run/types.ts +++ b/src/agents/pi-embedded-runner/run/types.ts @@ -26,12 +26,20 @@ type EmbeddedRunAttemptBase = Omit< "provider" | "model" | "authProfileId" | "authProfileIdSource" | "thinkLevel" | "lane" | "enqueue" >; +export type EmbeddedRunContextWindowInfo = { + tokens: number; + referenceTokens?: number; + source: "model" | "modelsConfig" | "agentContextTokens" | "default"; +}; + export type EmbeddedRunAttemptParams = EmbeddedRunAttemptBase & { initialReplayState?: EmbeddedRunReplayState; /** Pluggable context engine for ingest/assemble/compact lifecycle. */ contextEngine?: ContextEngine; /** Resolved model context window in tokens for assemble/compact budgeting. */ contextTokenBudget?: number; + /** Source metadata for the resolved model context budget. */ + contextWindowInfo?: EmbeddedRunContextWindowInfo; /** Resolved API key for this run when runtime auth did not replace it. */ resolvedApiKey?: string; /** Auth profile resolved for this attempt's provider/model call. */ diff --git a/src/infra/diagnostic-events.ts b/src/infra/diagnostic-events.ts index 7dffb5001ac..b35f16b5997 100644 --- a/src/infra/diagnostic-events.ts +++ b/src/infra/diagnostic-events.ts @@ -458,6 +458,9 @@ type DiagnosticModelCallBaseEvent = DiagnosticBaseEvent & { model: string; api?: string; transport?: string; + contextTokenBudget?: number; + contextWindowSource?: "model" | "modelsConfig" | "agentContextTokens" | "default"; + contextWindowReferenceTokens?: number; upstreamRequestIdHash?: string; }; diff --git a/src/plugins/hook-types.ts b/src/plugins/hook-types.ts index 0d5fca1e1c1..58aec3e3080 100644 --- a/src/plugins/hook-types.ts +++ b/src/plugins/hook-types.ts @@ -196,8 +196,20 @@ export type PluginHookAgentContext = { messageProvider?: string; trigger?: string; channelId?: string; + /** Resolved effective context-token budget after model/config/agent caps. */ + contextTokenBudget?: number; + /** Source that supplied the resolved context-token budget. */ + contextWindowSource?: PluginHookContextWindowSource; + /** Native/configured reference window when a lower cap wins. */ + contextWindowReferenceTokens?: number; }; +export type PluginHookContextWindowSource = + | "model" + | "modelsConfig" + | "agentContextTokens" + | "default"; + export type PluginHookBeforeAgentReplyEvent = { cleanedBody: string; }; @@ -229,6 +241,12 @@ export type PluginHookModelCallBaseEvent = { model: string; api?: string; transport?: string; + /** Resolved effective context-token budget after model/config/agent caps. */ + contextTokenBudget?: number; + /** Source that supplied the resolved context-token budget. */ + contextWindowSource?: PluginHookContextWindowSource; + /** Native/configured reference window when a lower cap wins. */ + contextWindowReferenceTokens?: number; }; export type PluginHookModelCallStartedEvent = PluginHookModelCallBaseEvent; @@ -249,6 +267,12 @@ export type PluginHookLlmOutputEvent = { sessionId: string; provider: string; model: string; + /** Resolved effective context-token budget after model/config/agent caps. */ + contextTokenBudget?: number; + /** Source that supplied the resolved context-token budget. */ + contextWindowSource?: PluginHookContextWindowSource; + /** Native/configured reference window when a lower cap wins. */ + contextWindowReferenceTokens?: number; /** * Fully resolved provider/model ref used for the call. *