|
|
@@ -1,12 +1,15 @@
|
|
|
/**
|
|
|
* The measurement service's positional surface fold: the per-node priced
|
|
|
* surface `measure()` serves and compaction plans against. The projection
|
|
|
- * units deliberately do NOT share this fold — their state must stay O(1)
|
|
|
- * for the persisted checkpoint, so they ride `surface-projection.ts`'s
|
|
|
- * shadow-price protocol instead. Fully metered logs stay in agreement by
|
|
|
- * construction: both price through `estimate.ts`, and every logged shadow
|
|
|
- * price is derived from THIS fold's nodes by the replace producer. A
|
|
|
- * projection replacement without a claim deliberately folds with zero delta.
|
|
|
+ * units do NOT share this fold — their state must stay O(1) for the
|
|
|
+ * persisted checkpoint, so they ride `surface-projection.ts`'s shadow-price
|
|
|
+ * protocol; the two agree because both price through `estimate.ts` and every
|
|
|
+ * logged shadow price derives from this fold's nodes.
|
|
|
+ *
|
|
|
+ * The fold is a plan/commit pair: {@link planSurfaceTokens} runs every
|
|
|
+ * fallible step read-only and {@link commitSurfaceTokens} mutates in place,
|
|
|
+ * so a throw leaves the caller's state untouched and the same malformed
|
|
|
+ * event fails identically on every retry.
|
|
|
*
|
|
|
* @module @deepseek-ai/dsh-token-meter/surface-fold
|
|
|
*/
|
|
|
@@ -16,50 +19,61 @@ import type { SurfaceEvent } from '@deepseek-ai/dsh-session'
|
|
|
import type { TokenSurfaceNode } from './types.ts'
|
|
|
import { estimateMessage } from './estimate.ts'
|
|
|
|
|
|
-/** One surface event's placement and cost against the surface preceding it. */
|
|
|
-export interface SurfaceTokenFold {
|
|
|
+/** One validated surface transition that has not mutated the priced surface yet. */
|
|
|
+export interface SurfaceTokenPlan {
|
|
|
/** Heuristic price of the event's own message; 0 when it derives none. */
|
|
|
readonly tokens: number
|
|
|
- /** The surface after the event, detached from the input. */
|
|
|
- readonly nodes: TokenSurfaceNode[]
|
|
|
/** Signed change in the surface total: `tokens` minus anything shadowed. */
|
|
|
readonly deltaTokens: number
|
|
|
+ /** The priced node the commit inserts for this event. */
|
|
|
+ readonly node: TokenSurfaceNode
|
|
|
+ /** Commit position: `append`, or the inclusive replaced index range. */
|
|
|
+ readonly target: 'append' | { readonly startIdx: number; readonly endIdx: number }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Fold one surface event onto a priced surface.
|
|
|
- *
|
|
|
- * Total and allocation-fresh: the caller assigns the result rather than
|
|
|
- * mutating in place, so a throw here leaves the caller's state untouched and
|
|
|
- * the same malformed event fails identically on every retry.
|
|
|
+ * Validate and price one surface event without mutating the surface.
|
|
|
* @param nodes - the priced surface preceding this event, in model-visible order.
|
|
|
* @param event - the surface event to place.
|
|
|
- * @returns the event's price, the next surface, and the signed total delta.
|
|
|
+ * @returns the plan for {@link commitSurfaceTokens}.
|
|
|
* @throws when a replacement names a range absent from `nodes` — committed
|
|
|
* logs are surface-validated at append time, so an unresolvable range is log
|
|
|
* corruption and must fail loud rather than skip the event.
|
|
|
*/
|
|
|
-export function foldSurfaceTokens(
|
|
|
+export function planSurfaceTokens(
|
|
|
nodes: readonly TokenSurfaceNode[],
|
|
|
event: SurfaceEvent,
|
|
|
-): SurfaceTokenFold {
|
|
|
+): SurfaceTokenPlan {
|
|
|
const message = deriveEventMessage(event)
|
|
|
const tokens = message === null ? 0 : estimateMessage(message)
|
|
|
+ const node = { seq: event.seq, tokens }
|
|
|
const op = event.surfaceOp
|
|
|
if (op === 'append') {
|
|
|
- return { tokens, nodes: [...nodes, { seq: event.seq, tokens }], deltaTokens: tokens }
|
|
|
+ return { tokens, deltaTokens: tokens, node, target: 'append' }
|
|
|
}
|
|
|
- const startIdx = nodes.findIndex(node => node.seq === op.start)
|
|
|
- const endIdx = nodes.findIndex(node => node.seq === op.end)
|
|
|
+ const startIdx = nodes.findIndex(candidate => candidate.seq === op.start)
|
|
|
+ const endIdx = nodes.findIndex(candidate => candidate.seq === op.end)
|
|
|
if (startIdx === -1 || endIdx === -1 || startIdx > endIdx) {
|
|
|
throw new Error(
|
|
|
`token surface: replace at seq ${event.seq} has invalid current range ${op.start}-${op.end}`,
|
|
|
)
|
|
|
}
|
|
|
- const removed = nodes
|
|
|
- .slice(startIdx, endIdx + 1)
|
|
|
- .reduce((total, node) => total + node.tokens, 0)
|
|
|
- const next = [...nodes]
|
|
|
- next.splice(startIdx, endIdx - startIdx + 1, { seq: event.seq, tokens })
|
|
|
- return { tokens, nodes: next, deltaTokens: tokens - removed }
|
|
|
+ let removed = 0
|
|
|
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- startIdx..endIdx are validated indices
|
|
|
+ for (let index = startIdx; index <= endIdx; index += 1) removed += nodes[index]!.tokens
|
|
|
+ return { tokens, deltaTokens: tokens - removed, node, target: { startIdx, endIdx } }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Apply one validated plan to the priced surface in place; infallible, so it
|
|
|
+ * cannot leave a half-applied surface behind.
|
|
|
+ * @param nodes - the exact priced surface the plan was built against.
|
|
|
+ * @param plan - the transition returned by {@link planSurfaceTokens}.
|
|
|
+ */
|
|
|
+export function commitSurfaceTokens(nodes: TokenSurfaceNode[], plan: SurfaceTokenPlan): void {
|
|
|
+ if (plan.target === 'append') {
|
|
|
+ nodes.push(plan.node)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ nodes.splice(plan.target.startIdx, plan.target.endIdx - plan.target.startIdx + 1, plan.node)
|
|
|
}
|