context_packet
context_packetTransforms ambiguous coding requests into ranked, source-labeled context for AI agents. Uses deterministic providers to deliver readable, prioritized information before read/edit cycles.
Instructions
Context scout for coding agents: turn a messy request into ranked, source-labeled, readable context using deterministic providers first (files, routes, symbols, schema, import graph, centrality, hot hints). Reef-backed enrichments add working-tree overlay metadata and active findings; use risksMinConfidence to suppress low-confidence risk speculation. Set MAKO_REEF_BACKED=legacy for the one-release rollback path. Read-only; does not refresh the index. Use as the first-mile packet before normal harness read/search/edit loops.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectId | No | ||
| projectRef | No | ||
| request | Yes | ||
| mode | No | ||
| focusFiles | No | ||
| focusSymbols | No | ||
| focusRoutes | No | ||
| focusDatabaseObjects | No | ||
| changedFiles | No | ||
| maxPrimaryContext | No | ||
| maxRelatedContext | No | ||
| budgetTokens | No | ||
| includeInstructions | No | ||
| includeRisks | No | ||
| risksMinConfidence | No | ||
| includeLiveHints | No | ||
| freshnessPolicy | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| toolName | Yes | ||
| projectId | Yes | ||
| projectRoot | Yes | ||
| request | Yes | ||
| mode | Yes | ||
| modePolicy | Yes | ||
| intent | Yes | ||
| primaryContext | Yes | ||
| relatedContext | Yes | ||
| activeFindings | Yes | ||
| symbols | Yes | ||
| routes | Yes | ||
| databaseObjects | Yes | ||
| risks | Yes | ||
| scopedInstructions | Yes | ||
| recommendedHarnessPattern | Yes | ||
| expandableTools | Yes | ||
| freshnessGate | Yes | ||
| indexFreshness | No | ||
| reefExecution | Yes | ||
| limits | Yes | ||
| warnings | Yes | ||
| _hints | Yes |
Implementation Reference
- The main handler function `contextPacketTool` that executes the context_packet tool logic. Orchestrates providers, ranking, risks, instructions, expandable tools, telemetry, and returns the full ContextPacketToolOutput.
export async function contextPacketTool( input: ContextPacketToolInput, options: ToolServiceOptions = {}, ): Promise<ContextPacketToolOutput> { return withProjectContext(input, options, async ({ project, projectStore }) => { const startedAtMs = Date.now(); const reefBacked = isReefBackedToolViewEnabled("context_packet"); const policy = resolveContextPacketModePolicy(input.mode); const enabledProviders = new Set(policy.enabledProviders); const includeRisks = input.includeRisks ?? policy.includeRisks; const risksMinConfidence = input.risksMinConfidence ?? 0; const includeInstructions = input.includeInstructions ?? policy.includeInstructions; const includeActiveFindings = policy.includeActiveFindings; const includeExpandableTools = policy.includeExpandableTools; const intent = detectContextPacketIntent(input); const freshnessGate = await ensureProjectFresh({ project, projectStore, options, reason: input.freshnessPolicy === "prefer_fresh" ? "context_packet prefer_fresh" : "context_packet", waitWhenIdle: input.freshnessPolicy === "prefer_fresh", }); const latestRun = projectStore.getLatestIndexRun(); const hotIndexCache = options.hotIndexCache ?? getDefaultHotIndexCache(); // Watcher-driven dirty paths trigger a path-scoped refresh in the // Phase 4 coordinator, which advances `latestRun.runId`. The hot // index keys on that runId, so a fresh index run automatically // invalidates and rebuilds — no explicit dirty marking needed. const hotIndex = hotIndexCache.getOrBuild({ projectId: project.projectId, projectRoot: project.canonicalPath, projectStore, ...(latestRun?.runId ? { indexRunId: latestRun.runId } : {}), }); const collected = collectContextPacketProviders({ input, intent, projectStore, hotIndex, enabledProviders, }); const workingTreeOverlayFacts = reefBacked ? collectWorkingTreeOverlayFacts(projectStore, project.projectId) : new Map<string, ProjectFact>(); const overlayCandidates = providerEnabled(policy, "working_tree_overlay") ? overlayFactCandidateSeeds({ input, factsByPath: workingTreeOverlayFacts, }) : []; const conventionCandidates = reefBacked && providerEnabled(policy, "reef_convention") ? conventionFactCandidateSeeds({ input, projectStore, projectId: project.projectId, }) : []; const candidateSeeds = [...collected.candidates, ...overlayCandidates, ...conventionCandidates]; const filesByPath = new Map(projectStore.listFiles().map((file) => [file.path, file] as const)); const freshnessByPath = freshnessDetailsForCandidates(project.canonicalPath, filesByPath, candidateSeeds); const indexFreshness = summarizeIndexFreshnessDetails([...freshnessByPath.values()]); const dirty = indexFreshness.state !== "fresh"; const changedFilesMissingOverlay = missingChangedOverlayFacts(input, workingTreeOverlayFacts); const maxPrimaryContext = input.maxPrimaryContext ?? policy.defaultMaxPrimaryContext ?? DEFAULT_MAX_PRIMARY_CONTEXT; const maxRelatedContext = input.maxRelatedContext ?? policy.defaultMaxRelatedContext ?? DEFAULT_MAX_RELATED_CONTEXT; const budgetTokens = input.budgetTokens ?? policy.defaultBudgetTokens ?? DEFAULT_BUDGET_TOKENS; const ranked = rankContextCandidates(candidateSeeds, { maxPrimaryContext, maxRelatedContext, budgetTokens, freshnessPolicy: input.freshnessPolicy ?? "report", freshnessByPath, focusFiles: new Set((input.focusFiles ?? []).map(normalizePath)), changedFiles: new Set((input.changedFiles ?? []).map(normalizePath)), }); const primaryContext = annotateContextOverlay(ranked.primaryContext, workingTreeOverlayFacts); const relatedContext = annotateContextOverlay(ranked.relatedContext, workingTreeOverlayFacts); const allContext = [...primaryContext, ...relatedContext]; const rawActiveFindings = reefBacked && (includeActiveFindings || includeRisks) ? collectRelevantActiveFindings({ input, projectStore, projectId: project.projectId, candidates: allContext, }) : []; const relevantFreshFindings = rawActiveFindings.filter((finding) => finding.freshness.state === "fresh"); const activeFindings = includeActiveFindings ? relevantFreshFindings : []; const staleActiveFindingsDropped = rawActiveFindings.length - relevantFreshFindings.length; const warnings = [...collected.warnings, ...freshnessGate.warnings]; if (ranked.budgetExhausted) { warnings.push("context packet was truncated by budgetTokens."); } if (dirty) { warnings.push("one or more indexed context files are stale, deleted, unindexed, or unknown; verify before relying on indexed evidence."); } if (changedFilesMissingOverlay.length > 0) { warnings.push(`${changedFilesMissingOverlay.length} changed file(s) have no working-tree overlay facts; context_packet is using indexed fallback where available.`); } if (allContext.length === 0) { warnings.push("no deterministic context candidates matched the request."); } if (!reefBacked) { warnings.push("Reef-backed context enrichments are disabled by MAKO_REEF_BACKED."); } if (staleActiveFindingsDropped > 0) { warnings.push(`Dropped ${staleActiveFindingsDropped} stale active finding(s) from edit-guiding context.`); } if (freshnessGate.status === "stale" || freshnessGate.status === "degraded") { warnings.push(`Project freshness gate is ${freshnessGate.status}: ${freshnessGate.reason}`); } const risks = !includeRisks ? [] : detectContextPacketRisks({ request: input.request, intent, candidates: allContext, indexFreshness, activeFindings: relevantFreshFindings, }).filter((risk) => risk.confidence >= risksMinConfidence); const scopedInstructions = !includeInstructions ? [] : loadScopedInstructions({ projectRoot: project.canonicalPath, candidates: allContext, }); emitContextPacketTelemetry({ projectStore, projectId: project.projectId, requestId: options.requestContext?.requestId, grade: primaryContext.length > 0 ? "full" : relatedContext.length > 0 ? "partial" : "no", reasonCodes: [ primaryContext.length > 0 ? "primary_context_returned" : "no_primary_context", dirty ? "dirty_index_reported" : "freshness_reported", ], reason: `context_packet returned ${allContext.length} readable candidate(s).`, }); const staleContextItems = allContext.filter((candidate) => candidate.freshness?.state !== undefined && candidate.freshness.state !== "fresh" ).length; const reefExecution = await buildReefToolExecution({ toolName: "context_packet", projectId: project.projectId, projectRoot: project.canonicalPath, options, startedAtMs, freshnessPolicy: "allow_stale_labeled", queryPath: reefBacked ? "reef_materialized_view" : "direct_live", staleEvidenceDropped: staleActiveFindingsDropped, staleEvidenceLabeled: staleContextItems, returnedCount: allContext.length + activeFindings.length, }); return { toolName: "context_packet", projectId: project.projectId, projectRoot: project.canonicalPath, request: input.request, mode: policy.mode, modePolicy: contextPacketModePolicySummary({ policy, includeInstructions, includeRisks, includeActiveFindings, includeExpandableTools, }), intent, primaryContext, relatedContext, activeFindings, symbols: collectSymbols(allContext), routes: collectRoutes(allContext), databaseObjects: collectDatabaseObjects(allContext), risks, scopedInstructions, recommendedHarnessPattern: buildRecommendedHarnessPattern({ intent, candidates: allContext, risks, indexFreshness, }), expandableTools: includeExpandableTools ? expandableTools(input, project.projectId, { dirty: dirty || freshnessGate.status === "stale" || freshnessGate.status === "degraded", needsWorkingTreeOverlay: changedFilesMissingOverlay.length > 0, policy, }) : [], freshnessGate, indexFreshness, reefExecution, limits: { budgetTokens, tokenEstimateMethod: "char_div_4", maxPrimaryContext, maxRelatedContext, providersRun: [ ...collected.providersRun, ...(overlayCandidates.length > 0 ? ["working_tree_overlay"] : []), ...(conventionCandidates.length > 0 ? ["reef_convention"] : []), ], providersSkipped: [ ...collected.providersSkipped, ...(!providerEnabled(policy, "working_tree_overlay") ? ["working_tree_overlay"] : []), ...(!providerEnabled(policy, "reef_convention") ? ["reef_convention"] : []), ], providersFailed: collected.providersFailed, candidatesConsidered: ranked.candidatesConsidered, candidatesReturned: ranked.candidatesReturned, }, warnings, }; }); } - packages/tools/src/tool-definitions.ts:713-721 (registration)Registration of the context_packet tool in the TOOL_DEFINITIONS array, wiring execute to contextPacketTool with input/output schemas.
{ name: "context_packet", category: "context", description: "Context scout for coding agents: turn a messy request into ranked, source-labeled, readable context using deterministic providers first (files, routes, symbols, schema, import graph, centrality, hot hints). Reef-backed enrichments add working-tree overlay metadata and active findings; use risksMinConfidence to suppress low-confidence risk speculation. Set MAKO_REEF_BACKED=legacy for the one-release rollback path. Read-only; does not refresh the index. Use as the first-mile packet before normal harness read/search/edit loops.", annotations: toolAnnotations("context_packet"), inputSchema: ContextPacketToolInputSchema, outputSchema: ContextPacketToolOutputSchema, execute: (input, options) => contextPacketTool(input as never, options), }, - Input/output type definitions and Zod schemas for ContextPacketToolInput, ContextPacketToolOutput, and all supporting types (candidates, symbols, routes, risks, instructions, etc.).
export interface ContextPacketToolInput extends ProjectLocatorInput { request: string; mode?: ContextPacketMode; focusFiles?: string[]; focusSymbols?: string[]; focusRoutes?: string[]; focusDatabaseObjects?: string[]; changedFiles?: string[]; maxPrimaryContext?: number; maxRelatedContext?: number; budgetTokens?: number; includeInstructions?: boolean; includeRisks?: boolean; risksMinConfidence?: number; includeLiveHints?: boolean; freshnessPolicy?: "report" | "prefer_fresh"; } export const ContextPacketToolInputSchema = ProjectLocatorInputObjectSchema.extend({ request: z.string().trim().min(1), mode: ContextPacketModeSchema.optional(), focusFiles: z.array(z.string().trim().min(1)).max(50).optional(), focusSymbols: z.array(z.string().trim().min(1)).max(50).optional(), focusRoutes: z.array(z.string().trim().min(1)).max(50).optional(), focusDatabaseObjects: z.array(z.string().trim().min(1)).max(50).optional(), changedFiles: z.array(z.string().trim().min(1)).max(100).optional(), maxPrimaryContext: z.number().int().min(1).max(30).optional(), maxRelatedContext: z.number().int().min(0).max(60).optional(), budgetTokens: z.number().int().min(256).max(12_000).optional(), includeInstructions: z.boolean().optional(), includeRisks: z.boolean().optional(), risksMinConfidence: z.number().min(0).max(1).optional(), includeLiveHints: z.boolean().optional(), freshnessPolicy: z.enum(["report", "prefer_fresh"]).optional(), }) satisfies z.ZodType<ContextPacketToolInput>; export interface ContextPacketIntent { primaryFamily: ContextPacketIntentFamily; families: Array<{ family: ContextPacketIntentFamily; confidence: number; signals: string[]; }>; entities: { files: string[]; symbols: string[]; routes: string[]; databaseObjects: string[]; quotedText: string[]; keywords: string[]; }; } export const ContextPacketIntentSchema = z.object({ primaryFamily: ContextPacketIntentFamilySchema, families: z.array(z.object({ family: ContextPacketIntentFamilySchema, confidence: z.number().min(0).max(1), signals: z.array(z.string().min(1)), })), entities: z.object({ files: z.array(z.string().min(1)), symbols: z.array(z.string().min(1)), routes: z.array(z.string().min(1)), databaseObjects: z.array(z.string().min(1)), quotedText: z.array(z.string().min(1)), keywords: z.array(z.string().min(1)), }), }) satisfies z.ZodType<ContextPacketIntent>; export interface ContextPacketReadableCandidate { id: string; kind: "file" | "symbol" | "route" | "database_object"; path?: string; lineStart?: number; lineEnd?: number; symbolName?: string; routeKey?: string; databaseObjectName?: string; source: ContextPacketSource; strategy: ContextPacketStrategy; whyIncluded: string; confidence: number; score: number; freshness?: IndexFreshnessDetail; evidenceRef?: string; metadata?: JsonObject; } export const ContextPacketReadableCandidateSchema = z.object({ id: z.string().min(1), kind: z.enum(["file", "symbol", "route", "database_object"]), path: z.string().min(1).optional(), lineStart: z.number().int().positive().optional(), lineEnd: z.number().int().positive().optional(), symbolName: z.string().min(1).optional(), routeKey: z.string().min(1).optional(), databaseObjectName: z.string().min(1).optional(), source: ContextPacketSourceSchema, strategy: ContextPacketStrategySchema, whyIncluded: z.string().min(1), confidence: z.number().min(0).max(1), score: z.number(), freshness: IndexFreshnessDetailSchema.optional(), evidenceRef: z.string().min(1).optional(), metadata: JsonObjectSchema.optional(), }) satisfies z.ZodType<ContextPacketReadableCandidate>; export interface ContextPacketSymbol { name: string; kind: string; path?: string; lineStart?: number; source: ContextPacketSource; whyIncluded: string; confidence: number; } export const ContextPacketSymbolSchema = z.object({ name: z.string().min(1), kind: z.string().min(1), path: z.string().min(1).optional(), lineStart: z.number().int().positive().optional(), source: ContextPacketSourceSchema, whyIncluded: z.string().min(1), confidence: z.number().min(0).max(1), }) satisfies z.ZodType<ContextPacketSymbol>; export interface ContextPacketRoute { routeKey: string; path?: string; method?: string; source: ContextPacketSource; whyIncluded: string; confidence: number; } export const ContextPacketRouteSchema = z.object({ routeKey: z.string().min(1), path: z.string().min(1).optional(), method: z.string().min(1).optional(), source: ContextPacketSourceSchema, whyIncluded: z.string().min(1), confidence: z.number().min(0).max(1), }) satisfies z.ZodType<ContextPacketRoute>; export interface ContextPacketDatabaseObject { objectType: "schema" | "table" | "view" | "rpc" | "function" | "policy" | "trigger" | "column" | "enum" | "unknown"; schemaName?: string; objectName: string; source: ContextPacketSource; whyIncluded: string; confidence: number; } export const ContextPacketDatabaseObjectSchema = z.object({ objectType: z.enum(["schema", "table", "view", "rpc", "function", "policy", "trigger", "column", "enum", "unknown"]), schemaName: z.string().min(1).optional(), objectName: z.string().min(1), source: ContextPacketSourceSchema, whyIncluded: z.string().min(1), confidence: z.number().min(0).max(1), }) satisfies z.ZodType<ContextPacketDatabaseObject>; export interface ContextPacketRisk { code: string; reason: string; source: "risk_detector" | "freshness" | "finding_ack_memory" | "open_loop"; severity: "info" | "low" | "medium" | "high"; recommendedHarnessStep?: string; confidence: number; } export const ContextPacketRiskSchema = z.object({ code: z.string().min(1), reason: z.string().min(1), source: z.enum(["risk_detector", "freshness", "finding_ack_memory", "open_loop"]), severity: z.enum(["info", "low", "medium", "high"]), recommendedHarnessStep: z.string().min(1).optional(), confidence: z.number().min(0).max(1), }) satisfies z.ZodType<ContextPacketRisk>; export interface ContextPacketInstruction { path: string; appliesTo: string[]; precedence: number; reason: string; excerpt: string; } export const ContextPacketInstructionSchema = z.object({ path: z.string().min(1), appliesTo: z.array(z.string().min(1)), precedence: z.number().int().nonnegative(), reason: z.string().min(1), excerpt: z.string(), }) satisfies z.ZodType<ContextPacketInstruction>; export interface ContextPacketExpandableTool { toolName: ToolName; suggestedArgs: JsonObject; reason: string; whenToUse: string; readOnly: boolean; } export const ContextPacketExpandableToolSchema = z.object({ toolName: ToolNameSchema, suggestedArgs: JsonObjectSchema, reason: z.string().min(1), whenToUse: z.string().min(1), readOnly: z.boolean(), }) satisfies z.ZodType<ContextPacketExpandableTool>; export interface ContextPacketLimits { budgetTokens: number; tokenEstimateMethod: "char_div_4"; maxPrimaryContext: number; maxRelatedContext: number; providersRun: string[]; providersSkipped: string[]; providersFailed: string[]; candidatesConsidered: number; candidatesReturned: number; } export const ContextPacketLimitsSchema = z.object({ budgetTokens: z.number().int().positive(), tokenEstimateMethod: z.literal("char_div_4"), maxPrimaryContext: z.number().int().nonnegative(), maxRelatedContext: z.number().int().nonnegative(), providersRun: z.array(z.string().min(1)), providersSkipped: z.array(z.string().min(1)), providersFailed: z.array(z.string().min(1)), candidatesConsidered: z.number().int().nonnegative(), candidatesReturned: z.number().int().nonnegative(), }) satisfies z.ZodType<ContextPacketLimits>; export interface ContextPacketModePolicySummary { enabledProviders: string[]; disabledProviders: string[]; includeInstructions: boolean; includeRisks: boolean; includeActiveFindings: boolean; includeExpandableTools: boolean; } export const ContextPacketModePolicySummarySchema = z.object({ enabledProviders: z.array(z.string().min(1)), disabledProviders: z.array(z.string().min(1)), includeInstructions: z.boolean(), includeRisks: z.boolean(), includeActiveFindings: z.boolean(), includeExpandableTools: z.boolean(), }) satisfies z.ZodType<ContextPacketModePolicySummary>; export interface ContextPacketToolOutput { toolName: "context_packet"; projectId: string; projectRoot: string; request: string; mode: ContextPacketMode; modePolicy: ContextPacketModePolicySummary; intent: ContextPacketIntent; primaryContext: ContextPacketReadableCandidate[]; relatedContext: ContextPacketReadableCandidate[]; activeFindings: ProjectFinding[]; symbols: ContextPacketSymbol[]; routes: ContextPacketRoute[]; databaseObjects: ContextPacketDatabaseObject[]; risks: ContextPacketRisk[]; scopedInstructions: ContextPacketInstruction[]; recommendedHarnessPattern: string[]; expandableTools: ContextPacketExpandableTool[]; freshnessGate: ProjectFreshnessGate; indexFreshness?: IndexFreshnessSummary; reefExecution: ReefToolExecution; limits: ContextPacketLimits; warnings: string[]; } export const ContextPacketToolOutputSchema = z.object({ toolName: z.literal("context_packet"), projectId: z.string().min(1), projectRoot: z.string().min(1), request: z.string().min(1), mode: ContextPacketModeSchema, modePolicy: ContextPacketModePolicySummarySchema, intent: ContextPacketIntentSchema, primaryContext: z.array(ContextPacketReadableCandidateSchema), relatedContext: z.array(ContextPacketReadableCandidateSchema), activeFindings: z.array(ProjectFindingSchema), symbols: z.array(ContextPacketSymbolSchema), routes: z.array(ContextPacketRouteSchema), databaseObjects: z.array(ContextPacketDatabaseObjectSchema), risks: z.array(ContextPacketRiskSchema), scopedInstructions: z.array(ContextPacketInstructionSchema), recommendedHarnessPattern: z.array(z.string().min(1)), expandableTools: z.array(ContextPacketExpandableToolSchema), freshnessGate: ProjectFreshnessGateSchema, indexFreshness: IndexFreshnessSummarySchema.optional(), reefExecution: ReefToolExecutionSchema, limits: ContextPacketLimitsSchema, warnings: z.array(z.string().min(1)), }) satisfies z.ZodType<ContextPacketToolOutput>; - Provider orchestration: runs sequential providers (file, route, schema, symbol, import_graph, repo_map, hot_hint) to collect candidate seeds.
const PROVIDERS: Array<{ name: ContextPacketProviderName; run: ProviderFn }> = [ { name: "file_provider", run: fileProvider }, { name: "route_provider", run: routeProvider }, { name: "schema_provider", run: schemaProvider }, { name: "symbol_provider", run: symbolProvider }, { name: "import_graph_provider", run: importGraphProvider }, { name: "repo_map_provider", run: repoMapProvider }, { name: "hot_hint_index", run: hotHintProvider }, ]; // Providers run sequentially. They all execute SQLite reads against one // `ProjectStore` handle; node-sqlite serializes statements anyway, so // fanning out with Promise.all would not parallelize the work and would // make per-provider failure isolation harder to read. export function collectContextPacketProviders(ctx: ProviderContext): ContextPacketProviderCollection { const candidates: ContextPacketCandidateSeed[] = []; const providersRun: string[] = []; const providersSkipped: string[] = []; const providersFailed: string[] = []; const warnings: string[] = []; for (const provider of PROVIDERS) { if (ctx.enabledProviders && !ctx.enabledProviders.has(provider.name)) { providersSkipped.push(provider.name); continue; } providersRun.push(provider.name); try { candidates.push(...provider.run(ctx)); } catch (error) { providersFailed.push(provider.name); warnings.push(`${provider.name} failed: ${error instanceof Error ? error.message : String(error)}`); } } return { candidates, providersRun, providersSkipped, providersFailed, warnings }; } - Candidate ranking logic: deduplicates, scores, sorts, and splits candidates into primaryContext/relatedContext with token budgeting.
export function rankContextCandidates( candidates: readonly ContextPacketCandidateSeed[], options: RankOptions, ): RankedContextCandidates { const merged = new Map<string, ContextPacketCandidateSeed>(); for (const candidate of candidates) { const key = candidateKey(candidate); const existing = merged.get(key); if (!existing || scoreCandidate(candidate, options) > scoreCandidate(existing, options)) { merged.set(key, candidate); } } const ranked = [...merged.values()] .map((candidate) => normalizeCandidate(candidate, options)) .filter((candidate): candidate is ContextPacketReadableCandidate => candidate != null) .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)); const primaryContext: ContextPacketReadableCandidate[] = []; const relatedContext: ContextPacketReadableCandidate[] = []; let usedTokens = 0; let budgetExhausted = false; for (const candidate of ranked) { const tokenCost = estimateCandidateTokens(candidate); if (usedTokens + tokenCost > options.budgetTokens && primaryContext.length > 0) { budgetExhausted = true; break; } if (primaryContext.length < options.maxPrimaryContext) { primaryContext.push(candidate); usedTokens += tokenCost; continue; } if (relatedContext.length < options.maxRelatedContext) { relatedContext.push(candidate); usedTokens += tokenCost; continue; } break; } return { primaryContext, relatedContext, candidatesConsidered: candidates.length, candidatesReturned: primaryContext.length + relatedContext.length, budgetExhausted, }; }