mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🪙 feat: Context-usage projection — data-provider + client wiring
Consumer side of the SDK-aligned context projection (agents `projectAgentContextUsage`). Adds the `/api/endpoints/context-projection` data-provider plumbing (endpoint, service, query key, `TContextProjectionRequest`) and a `useContextProjectionQuery` gated to fire only when no fresh snapshot covers the viewed branch. Wires `useTokenUsage` precedence to: live snapshot → fresh persisted snapshot (window matches the resolved one) → server projection → per-message estimate. A model/window switch marks the baked snapshot stale (its `maxContextTokens` no longer matches) and falls to the projection — closing the gauge's window-switch (G1) and snapshot-less-branch (G2) gaps. Snapshot and projection share the render-relevant fields, so they render uniformly. Backend endpoint + agents version bump land in follow-up commits. Includes the design spec (CONTEXT_PROJECTION_SPEC.md).
This commit is contained in:
parent
c820dfb9a0
commit
03ab2a7e73
7 changed files with 269 additions and 26 deletions
119
CONTEXT_PROJECTION_SPEC.md
Normal file
119
CONTEXT_PROJECTION_SPEC.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Context Projection — Aligning the Gauge with the SDK in Every State
|
||||
|
||||
**Branch:** `feat/context-projection` (off `origin/dev` @ `054fa4bfa7`)
|
||||
**Goal:** The gauge should always render the agents SDK's authoritative view of "what context am I dealing with" — before usage (pre-send / load / switch) and after (real provider usage). The client's `sumBranch` estimate is the one number that *isn't* the SDK's, and it's active exactly where we currently have no SDK data. This spec closes that gap.
|
||||
|
||||
---
|
||||
|
||||
## 1. Current state on `dev` — what already works
|
||||
|
||||
The layered design is further along than the stale `feat/context-usage-tracking` branch suggested. On dev:
|
||||
|
||||
- **Live stream / resume:** `on_context_usage` is post-prune and reconciled to real provider tokens (#13780). Authoritative.
|
||||
- **Persisted snapshot (Increment 1 — DONE):**
|
||||
- Backend writes the reconciled post-prune snapshot to `responseMessage.metadata.contextUsage` ([api/server/controllers/agents/client.js:897](api/server/controllers/agents/client.js), via `buildPersistedContextUsage` in [packages/api/src/agents/usage.ts:286](packages/api/src/agents/usage.ts)).
|
||||
- Client rehydrates per-branch snapshots from `metadata.contextUsage` on load — `hydrateSnapshots()` ([client/src/store/usage.ts:149](client/src/store/usage.ts)), called from `useTokenUsage` ([client/src/hooks/Chat/useTokenUsage.ts:128](client/src/hooks/Chat/useTokenUsage.ts)) into `snapshotsByAnchorFamily`, recovered per-branch via `findBranchSnapshotAnchor`.
|
||||
- **Estimate fallback** is summary-baseline-aware: it stops the branch walk at the deepest summarized response and adds `summaryBaseline`, so it no longer pegs at 100% forever after a compaction ([useTokenUsage.ts:235-241](client/src/hooks/Chat/useTokenUsage.ts)).
|
||||
|
||||
So a reloaded conversation **does** show the SDK's authoritative breakdown for any turn generated with the feature on. Increment 1 is shipped; this spec does **not** redo it.
|
||||
|
||||
---
|
||||
|
||||
## 2. Residual gaps (the two edge cases)
|
||||
|
||||
**G1 — Window / model switch staleness (snapshot-backed branches).**
|
||||
The snapshot path uses the max baked at generation time: `maxTokens = activeSnapshot.contextBudget ?? breakdown.maxContextTokens` ([useTokenUsage.ts:205](client/src/hooks/Chat/useTokenUsage.ts)), with **no comparison to the currently-resolved window** (`limits.maxContextTokens`). Switch the model/`maxContextTokens` without regenerating and the gauge shows the *old* window and the *old* prune boundary until the next send.
|
||||
|
||||
**G2 — Snapshot-less branches.**
|
||||
Any branch with no `metadata.contextUsage` falls to the `sumBranch` estimate, which sums every (post-summary) message with **no window cap and no prune awareness** ([utils/tokens.ts:96](client/src/utils/tokens.ts)). For a long un-summarized branch beyond the window it over-reports and pegs. Hit by: pre-feature history, non-agent endpoints, foreign/imported conversations, and the live edge of a branch not yet generated this session.
|
||||
|
||||
Both are precisely what an SDK projection resolves: it re-runs the *real* prune+budget math for the *current* config over the branch, regardless of whether a prior generation exists.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture: a third SDK-sourced layer
|
||||
|
||||
The view-model precedence becomes:
|
||||
|
||||
1. **Live snapshot** (streaming this branch) — unchanged.
|
||||
2. **Persisted snapshot** (`metadata.contextUsage`) — **only when its baked `maxContextTokens` equals the currently-resolved window** (fixes G1).
|
||||
3. **Projection** (NEW) — SDK-computed post-prune breakdown for `(branch, resolved config)`, fetched on load / branch-switch / model-switch.
|
||||
4. **`sumBranch` estimate** — last-resort fallback only (SDK absent, non-agent endpoint, pre-feature message with no stored counts).
|
||||
|
||||
The client never re-derives pruning. Layers 1–2 are "after usage" (real tokens); layer 3 is "before usage" (count-based but prune-accurate); layer 4 is the dumb fallback.
|
||||
|
||||
---
|
||||
|
||||
## 4. SDK primitive (`@librechat/agents`)
|
||||
|
||||
Good news: the pure pieces exist.
|
||||
|
||||
- `getMessagesWithinTokenLimit({ messages, maxContextTokens, indexTokenCountMap, instructionTokens, tokenCounter, ... })` → `PruningResult` ([src/messages/prune.ts:645](../../agents/src/messages/prune.ts)). Pure: no `AgentContext`, no model call. This *is* the windowed-subset selection.
|
||||
- `AgentContext.getTokenBudgetBreakdown(messages)` ([src/agents/AgentContext.ts:1261](../../agents/src/agents/AgentContext.ts)) assembles the `TokenBudgetBreakdown` (instructions / system / dynamic / toolSchema / summary / messageTokens / availableForMessages) — but needs context state.
|
||||
|
||||
**New SDK surface — `projectContextUsage(...)`:** a pure combiner that
|
||||
1. runs `getMessagesWithinTokenLimit` to get the kept subset + `remainingContextTokens`,
|
||||
2. sums kept messages' counts for `messageTokens`,
|
||||
3. assembles the same `TokenBudgetBreakdown` shape from caller-supplied structural counts (`instructionTokens`, `systemMessageTokens`, `toolSchemaTokens`, `summaryTokens`, `toolCount`, `reserveRatio`),
|
||||
4. returns `{ breakdown, remainingContextTokens, prePruneContextTokens }` — the same payload as `ON_CONTEXT_USAGE` minus the live usage fields.
|
||||
|
||||
Inputs are plain values + a `tokenCounter`; output reuses `TokenBudgetBreakdown` / mirrors `TContextUsageEvent`. No recency/reserve math is duplicated outside the SDK → no client/calibration drift. Optionally expose an `AgentContext.projectContextUsage(messages)` convenience wrapper for callers that already built a context.
|
||||
|
||||
Ship as an additive agents minor; LibreChat bumps after publish (gated on go-ahead).
|
||||
|
||||
---
|
||||
|
||||
## 5. LibreChat backend — projection endpoint
|
||||
|
||||
A sibling to the existing token-config route ([api/server/routes/endpoints.js:10](api/server/routes/endpoints.js), `TokenConfigController`):
|
||||
|
||||
`GET /api/endpoints/context-projection?conversationId=&messageId=&endpoint=&model=&maxContextTokens=&agent_id=&spec=`
|
||||
|
||||
Server flow (new `packages/api/src/endpoints/projection.ts`, thin JS controller wrapper):
|
||||
1. Load the branch (tail → root via `parentMessageId`) for `messageId`.
|
||||
2. Resolve config exactly like the run path does (`packages/api/src/agents/initialize.ts` chain) → instructions, tool schemas, summary, `maxContextTokens`, `reserveRatio`, tokenizer.
|
||||
3. Build `messages: BaseMessage[]` + `indexTokenCountMap` for the branch.
|
||||
4. Call SDK `projectContextUsage(...)` → breakdown.
|
||||
5. Return a `TContextUsageEvent`-shaped blob (no usage/cost; cost stays on the live path).
|
||||
|
||||
**Performance.** Structural counts (instruction/tool/summary tokens) depend on config, not the branch → cache per `(endpoint, model, agentId, summaryHash)`. Per-message counts come from stored `message.tokenCount` (already in DB). The projection itself is O(messages) arithmetic. So a load/switch projection is cheap once structural counts are warm.
|
||||
|
||||
**Open implementation question (flag for impl):** `indexTokenCountMap` is positional over the *formatted* `BaseMessage[]` (system message, tool messages, content parts), which is not 1:1 with DB messages. Decide: (a) map stored `tokenCount`s onto formatted positions, or (b) recount server-side via the SDK tokenizer (accurate, costlier — cache by message id). Recommend (a) with (b) as fallback when a message lacks a stored count.
|
||||
|
||||
---
|
||||
|
||||
## 6. Client wiring
|
||||
|
||||
- New `useContextProjectionQuery(conversation, tailId)` in `data-provider/Endpoints/queries.ts` — React Query, keyed on `(conversationId, tailId, resolvedEndpoint, model, maxContextTokens)`, `staleTime` generous (only refetch when the key changes). Gated off when `interface.contextUsage === false`.
|
||||
- `useTokenUsage` precedence update (§3): prefer the persisted snapshot only when `snapshot.maxContextTokens === limits.maxContextTokens`; otherwise use the projection; else estimate. This single guard fixes **G1**, and the projection covers **G2**.
|
||||
- Retire `sumBranch` to the explicit last-resort branch (keep it — it's the only thing that works with zero SDK data / non-agent endpoints).
|
||||
- `isEstimate` stays `true` only for the §3-layer-4 path; projection renders as authoritative (`isEstimate: false`) but flagged `projected: true` if we want a subtle "pre-send projection" affordance in the breakdown header.
|
||||
|
||||
---
|
||||
|
||||
## 7. Sequencing
|
||||
|
||||
1. **SDK:** `projectContextUsage` primitive + unit tests in `@librechat/agents`; PR, review, publish (on go-ahead).
|
||||
2. **LibreChat backend:** projection endpoint + `projection.ts` + structural-count cache (can be built/tested against the published SDK).
|
||||
3. **LibreChat client:** query + view-model precedence + G1 guard + estimate demotion.
|
||||
4. Bump `@librechat/agents` in `api/` + `packages/api/`; wire end-to-end.
|
||||
|
||||
Degrades cleanly: without the SDK release or the endpoint, dev's behavior (snapshot + summary-aware estimate) remains the fallback.
|
||||
|
||||
---
|
||||
|
||||
## 8. Decisions needed
|
||||
|
||||
- **SDK API shape:** pure `projectContextUsage(inputs)` (recommended) vs. `AgentContext.projectContextUsage(messages)` method vs. both.
|
||||
- **`indexTokenCountMap` source:** stored `tokenCount` mapping (recommended) vs. server recount.
|
||||
- **Projection trigger cadence:** every load/branch/model change (simplest) vs. only when the active snapshot's window ≠ resolved window (cheaper, but leaves G2's snapshot-less load on the estimate until something changes).
|
||||
- **Non-agent endpoints:** out of scope (no SDK) → keep estimate, or add a minimal server-side windowing for them too.
|
||||
|
||||
---
|
||||
|
||||
## 9. Verification
|
||||
|
||||
- SDK: unit tests for `projectContextUsage` (windowing parity with a live prune; reserve/recency boundary; summary/instruction subtraction).
|
||||
- Backend: `cd packages/api && npx jest projection` — branch load, config resolution, count mapping, payload shape.
|
||||
- Client: precedence tests (snapshot-fresh vs window-mismatch→projection vs snapshot-less→projection vs SDK-absent→estimate); window-switch reactivity.
|
||||
- E2E: load a long pre-feature conversation (projection, not pegged); switch model and confirm the gauge re-projects without regenerating; generate and confirm live snapshot supersedes projection.
|
||||
|
|
@ -41,6 +41,44 @@ export const useTokenConfigQuery = (
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Server-side context-usage projection for the viewed branch + resolved config
|
||||
* (agents SDK, no model call). Keyed on the fields that change what the next
|
||||
* call would send — branch tail, endpoint/model/agent, window — so a branch or
|
||||
* model/window switch refetches. Disabled until a branch tail is known.
|
||||
*/
|
||||
export const useContextProjectionQuery = (
|
||||
params: t.TContextProjectionRequest | null,
|
||||
config?: UseQueryOptions<t.TContextUsageEvent | null>,
|
||||
): QueryObserverResult<t.TContextUsageEvent | null> => {
|
||||
const queriesEnabled = useRecoilValue<boolean>(store.queriesEnabled);
|
||||
return useQuery<t.TContextUsageEvent | null>(
|
||||
[
|
||||
QueryKeys.contextProjection,
|
||||
params?.conversationId,
|
||||
params?.messageId,
|
||||
params?.endpoint,
|
||||
params?.model,
|
||||
params?.agentId,
|
||||
params?.maxContextTokens,
|
||||
],
|
||||
() => dataService.getContextProjection(params as t.TContextProjectionRequest),
|
||||
{
|
||||
staleTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
enabled:
|
||||
(config?.enabled ?? true) === true &&
|
||||
queriesEnabled &&
|
||||
params != null &&
|
||||
(params.conversationId?.length ?? 0) > 0 &&
|
||||
(params.messageId?.length ?? 0) > 0,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth-aware query key so unauthenticated (login page) and authenticated
|
||||
* (chat page) configs are cached independently, preventing stale
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { useEffect, useMemo, useRef } from 'react';
|
|||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Constants, QueryKeys } from 'librechat-data-provider';
|
||||
import type { TMessage, TConversation, TModelTokenomics } from 'librechat-data-provider';
|
||||
import type {
|
||||
TMessage,
|
||||
TConversation,
|
||||
TModelTokenomics,
|
||||
TContextUsageEvent,
|
||||
TContextProjectionRequest,
|
||||
} from 'librechat-data-provider';
|
||||
import type { BranchTotals, BranchUsage } from '~/utils/tokens';
|
||||
import type { ContextSnapshot } from '~/store/usage';
|
||||
import {
|
||||
|
|
@ -24,6 +30,7 @@ import {
|
|||
findBranchSnapshotAnchor,
|
||||
} from '~/utils';
|
||||
import { useLatestMessageId } from '~/hooks/Messages/useLatestMessage';
|
||||
import { useContextProjectionQuery } from '~/data-provider';
|
||||
import useTokenLimits from './useTokenLimits';
|
||||
|
||||
export interface TokenUsageParams {
|
||||
|
|
@ -79,6 +86,52 @@ export default function useTokenUsage({
|
|||
const setTotalUsage = useSetAtom(totalUsageFamily(conversationKey));
|
||||
const limits = useTokenLimits(conversation);
|
||||
|
||||
/** Deepest persisted/live snapshot on the viewed branch (present only for
|
||||
* turns generated with the feature on). Gates the projection fetch and is a
|
||||
* render source. */
|
||||
const branchSnapshot = useMemo(() => {
|
||||
if (snapshotsByAnchor.size === 0) {
|
||||
return null;
|
||||
}
|
||||
const anchor = findBranchSnapshotAnchor(
|
||||
conversationKey,
|
||||
branchTotals.tailId,
|
||||
snapshotsByAnchor,
|
||||
);
|
||||
return anchor != null ? (snapshotsByAnchor.get(anchor) ?? null) : null;
|
||||
}, [conversationKey, branchTotals.tailId, snapshotsByAnchor]);
|
||||
|
||||
const resolvedMax = limits.maxContextTokens;
|
||||
/** A persisted snapshot is "fresh" only when its window matches the resolved
|
||||
* one — a model/window switch leaves its baked breakdown stale (G1). */
|
||||
const branchSnapshotFresh =
|
||||
branchSnapshot != null &&
|
||||
(resolvedMax == null || branchSnapshot.breakdown.maxContextTokens === resolvedMax);
|
||||
|
||||
/** Ask the server to project the branch (agents SDK, no model call) when no
|
||||
* fresh snapshot covers it — snapshot-less branches (G2) and post window/
|
||||
* model switch (G1). Cached + refetched by branch/endpoint/model/window. */
|
||||
const projectionParams: TContextProjectionRequest | null =
|
||||
!isSubmitting &&
|
||||
!branchSnapshotFresh &&
|
||||
conversation?.conversationId != null &&
|
||||
conversation.conversationId !== Constants.NEW_CONVO &&
|
||||
branchTotals.tailId != null &&
|
||||
conversation.endpoint != null
|
||||
? {
|
||||
conversationId: conversation.conversationId,
|
||||
messageId: branchTotals.tailId,
|
||||
endpoint: conversation.endpoint,
|
||||
model: conversation.model ?? undefined,
|
||||
agentId: conversation.agent_id ?? undefined,
|
||||
spec: conversation.spec ?? undefined,
|
||||
maxContextTokens: resolvedMax,
|
||||
calibrationRatio: branchSnapshot?.calibrationRatio,
|
||||
}
|
||||
: null;
|
||||
const { data: projectionData } = useContextProjectionQuery(projectionParams);
|
||||
const projection = projectionData ?? null;
|
||||
|
||||
/** Branch/total provider usage is index-derived; the in-flight response is
|
||||
* the only live add (the pending holder), counted into both — it sits on the
|
||||
* active branch tail and inside the conversation. The backend prices each
|
||||
|
|
@ -186,40 +239,42 @@ export default function useTokenUsage({
|
|||
snapshot != null &&
|
||||
(isSubmitting || (snapshot.anchorMessageId != null && branchTotals.containsAnchor));
|
||||
|
||||
/** When the live snapshot belongs to another branch, recover this branch's
|
||||
* own finalized snapshot (if it was generated this session) by walking the
|
||||
* branch for its deepest stored anchor — keeps the granular rows on switch
|
||||
* instead of dropping to coarse totals. */
|
||||
let activeSnapshot: ContextSnapshot | null = currentActive ? snapshot : null;
|
||||
if (activeSnapshot == null && !isSubmitting && snapshotsByAnchor.size > 0) {
|
||||
const anchor = findBranchSnapshotAnchor(
|
||||
conversationKey,
|
||||
branchTotals.tailId,
|
||||
snapshotsByAnchor,
|
||||
);
|
||||
activeSnapshot = anchor != null ? (snapshotsByAnchor.get(anchor) ?? null) : null;
|
||||
/** Precedence: live/active snapshot → fresh persisted branch snapshot →
|
||||
* server projection (covers G1 stale-window + G2 snapshot-less branches) →
|
||||
* a window-stale persisted snapshot (still better than the coarse estimate)
|
||||
* → per-message estimate. Snapshot and projection share the render-relevant
|
||||
* fields, so they render uniformly. */
|
||||
let effective: ContextSnapshot | TContextUsageEvent | null = null;
|
||||
if (currentActive) {
|
||||
effective = snapshot;
|
||||
} else if (branchSnapshotFresh) {
|
||||
effective = branchSnapshot;
|
||||
} else if (projection != null) {
|
||||
effective = projection;
|
||||
} else if (branchSnapshot != null) {
|
||||
effective = branchSnapshot;
|
||||
}
|
||||
|
||||
if (activeSnapshot != null) {
|
||||
const breakdown = activeSnapshot.breakdown;
|
||||
const maxTokens = activeSnapshot.contextBudget ?? breakdown.maxContextTokens;
|
||||
if (effective != null) {
|
||||
const breakdown = effective.breakdown;
|
||||
const maxTokens = effective.contextBudget ?? breakdown.maxContextTokens;
|
||||
const instructionTokens =
|
||||
activeSnapshot.effectiveInstructionTokens ?? breakdown.instructionTokens;
|
||||
effective.effectiveInstructionTokens ?? breakdown.instructionTokens;
|
||||
const baseUsed =
|
||||
activeSnapshot.remainingContextTokens != null
|
||||
? maxTokens - activeSnapshot.remainingContextTokens
|
||||
effective.remainingContextTokens != null
|
||||
? maxTokens - effective.remainingContextTokens
|
||||
: instructionTokens + breakdown.messageTokens;
|
||||
/** The snapshot is pre-invoke: in-flight output rides on `liveTokens`
|
||||
* (0 unless streaming this branch), the last call's finalized output on
|
||||
* `completedOutputTokens`. */
|
||||
/** The snapshot/projection is pre-invoke: in-flight output rides on
|
||||
* `liveTokens` (0 unless streaming this branch), the last call's finalized
|
||||
* output on `completedOutputTokens` (absent on a projection → 0). */
|
||||
const usedTokens =
|
||||
Math.max(0, baseUsed) + liveTokens + (activeSnapshot.completedOutputTokens ?? 0);
|
||||
Math.max(0, baseUsed) + liveTokens + (effective.completedOutputTokens ?? 0);
|
||||
return {
|
||||
usedTokens,
|
||||
maxTokens,
|
||||
percent: maxTokens > 0 ? Math.min((usedTokens / maxTokens) * 100, 100) : 0,
|
||||
isEstimate: false,
|
||||
snapshot: activeSnapshot,
|
||||
snapshot: effective as ContextSnapshot,
|
||||
snapshotActive: true,
|
||||
branchTotals,
|
||||
branchUsage,
|
||||
|
|
@ -266,7 +321,8 @@ export default function useTokenUsage({
|
|||
hasUsage,
|
||||
liveTokens,
|
||||
limits,
|
||||
snapshotsByAnchor,
|
||||
conversationKey,
|
||||
branchSnapshot,
|
||||
branchSnapshotFresh,
|
||||
projection,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ export const aiEndpoints = () => `${BASE_URL}/api/endpoints`;
|
|||
|
||||
export const tokenConfig = () => `${BASE_URL}/api/endpoints/token-config`;
|
||||
|
||||
export const contextProjection = () => `${BASE_URL}/api/endpoints/context-projection`;
|
||||
|
||||
export const models = () => `${BASE_URL}/api/models`;
|
||||
|
||||
export const tokenizer = () => `${BASE_URL}/api/tokenizer`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { AxiosResponse } from 'axios';
|
||||
import type { TFileConfig } from './file-config';
|
||||
import type { TContextProjectionRequest, TContextUsageEvent } from './types/runs';
|
||||
import type * as t from './types';
|
||||
import * as permissions from './accessPermissions';
|
||||
import * as endpoints from './api-endpoints';
|
||||
|
|
@ -249,6 +250,12 @@ export const getTokenConfig = (): Promise<t.TTokenConfigMap> => {
|
|||
return request.get(endpoints.tokenConfig());
|
||||
};
|
||||
|
||||
export const getContextProjection = (
|
||||
payload: TContextProjectionRequest,
|
||||
): Promise<TContextUsageEvent | null> => {
|
||||
return request.post(endpoints.contextProjection(), payload);
|
||||
};
|
||||
|
||||
export const getModels = async (): Promise<t.TModelsConfig> => {
|
||||
return request.get(endpoints.models());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export enum QueryKeys {
|
|||
balance = 'balance',
|
||||
endpoints = 'endpoints',
|
||||
tokenConfig = 'tokenConfig',
|
||||
contextProjection = 'contextProjection',
|
||||
presets = 'presets',
|
||||
searchResults = 'searchResults',
|
||||
tokenCount = 'tokenCount',
|
||||
|
|
|
|||
|
|
@ -88,6 +88,26 @@ export type TContextUsageEvent = {
|
|||
completedOutputTokens?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request payload for a server-side context-usage projection: "what context
|
||||
* would the next call send for this branch under this config", computed by the
|
||||
* agents SDK without invoking the model. Powers the gauge in states the live
|
||||
* snapshot can't cover (page load of a snapshot-less branch, window/model
|
||||
* switch). `messageId` is the viewed branch's tail; the server walks its parent
|
||||
* chain.
|
||||
*/
|
||||
export type TContextProjectionRequest = {
|
||||
conversationId: string;
|
||||
messageId: string;
|
||||
endpoint: string;
|
||||
model?: string;
|
||||
agentId?: string;
|
||||
spec?: string;
|
||||
maxContextTokens?: number;
|
||||
/** Provider-calibrated ratio from a prior snapshot, applied as a static seed. */
|
||||
calibrationRatio?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Per-response usage rollup persisted on `responseMessage.metadata.usage`, in
|
||||
* display units (input excludes cache; output includes repaired completion).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue