@takk/racs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +40 -0
  4. package/README.md +381 -0
  5. package/SECURITY.md +57 -0
  6. package/dist/cli/index.js +3016 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/edge/index.cjs +2000 -0
  9. package/dist/edge/index.cjs.map +1 -0
  10. package/dist/edge/index.d.cts +598 -0
  11. package/dist/edge/index.d.ts +598 -0
  12. package/dist/edge/index.js +1987 -0
  13. package/dist/edge/index.js.map +1 -0
  14. package/dist/index.cjs +2071 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +39 -0
  17. package/dist/index.d.ts +39 -0
  18. package/dist/index.js +2057 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/integrations/index.cjs +123 -0
  21. package/dist/integrations/index.cjs.map +1 -0
  22. package/dist/integrations/index.d.cts +285 -0
  23. package/dist/integrations/index.d.ts +285 -0
  24. package/dist/integrations/index.js +117 -0
  25. package/dist/integrations/index.js.map +1 -0
  26. package/dist/otel/index.cjs +93 -0
  27. package/dist/otel/index.cjs.map +1 -0
  28. package/dist/otel/index.d.cts +105 -0
  29. package/dist/otel/index.d.ts +105 -0
  30. package/dist/otel/index.js +91 -0
  31. package/dist/otel/index.js.map +1 -0
  32. package/dist/types-DQ7-9sk3.d.cts +758 -0
  33. package/dist/types-DQ7-9sk3.d.ts +758 -0
  34. package/dist/vercel/index.cjs +209 -0
  35. package/dist/vercel/index.cjs.map +1 -0
  36. package/dist/vercel/index.d.cts +210 -0
  37. package/dist/vercel/index.d.ts +210 -0
  38. package/dist/vercel/index.js +206 -0
  39. package/dist/vercel/index.js.map +1 -0
  40. package/dist/web/index.cjs +2000 -0
  41. package/dist/web/index.cjs.map +1 -0
  42. package/dist/web/index.d.cts +2 -0
  43. package/dist/web/index.d.ts +2 -0
  44. package/dist/web/index.js +1987 -0
  45. package/dist/web/index.js.map +1 -0
  46. package/package.json +189 -0
@@ -0,0 +1,210 @@
1
+ import { j as ProviderId, i as PromptSegment, R as RACS } from '../types-DQ7-9sk3.js';
2
+
3
+ /**
4
+ * Packaged Vercel AI SDK middleware for RACS (Remote Agent Context Store): one middleware
5
+ * object that plans cache directives before each call, applies them through provider
6
+ * options, and records the usage the provider reports back, for both generate and stream
7
+ * paths.
8
+ *
9
+ * Structural by design. This module declares its own shapes ({@link CallOptionsLike},
10
+ * {@link GenerateResultLike}, {@link StreamResultLike}, {@link RacsMiddleware}) instead of
11
+ * importing from the `ai` package, so the package keeps its zero-runtime-dependency
12
+ * invariant. The shapes match the `LanguageModelV3Middleware` contract structurally, which
13
+ * is all the Vercel AI SDK requires: pass the object to `wrapLanguageModel({ model, middleware })`
14
+ * and it just works, with no version coupling to the `ai` dependency graph.
15
+ *
16
+ * The product invariant holds here too: the middleware never performs a provider call. It
17
+ * decorates the params the host's own model call will send, and reads the usage the host's
18
+ * own call already produced.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+
23
+ /**
24
+ * Structural stand-in for the Vercel AI SDK call options handed to `transformParams`,
25
+ * `wrapGenerate`, and `wrapStream`. Only the fields the middleware touches are named,
26
+ * everything else flows through the index signature untouched.
27
+ */
28
+ interface CallOptionsLike {
29
+ /** System instructions, a string in most hosts, tolerated in any shape. */
30
+ system?: unknown;
31
+ /** The prompt, usually an array of messages, tolerated in any shape. */
32
+ prompt?: unknown;
33
+ /** Tool definitions, hashed as one stable segment when present. */
34
+ tools?: unknown;
35
+ /** Provider-namespaced options, where cache directives and the RACS stash land. */
36
+ providerOptions?: Record<string, Record<string, unknown>>;
37
+ /** Every other call option flows through unread and unmodified. */
38
+ [k: string]: unknown;
39
+ }
40
+ /**
41
+ * Structural stand-in for one Vercel AI SDK generate result, the subset usage recording reads.
42
+ * Field availability varies by SDK version and provider, which is why every read falls
43
+ * back along a documented chain, see {@link racsMiddleware}.
44
+ */
45
+ interface GenerateResultLike {
46
+ /** Normalized usage as the Vercel AI SDK reports it, every field optional in the wild. */
47
+ usage?: {
48
+ /** Total billed input tokens, cached and uncached together. */
49
+ inputTokens?: number;
50
+ /** Cached input tokens in the flattened spelling some versions use. */
51
+ cachedInputTokens?: number;
52
+ /** Cached read and write detail in the nested spelling other versions use. */
53
+ inputTokenDetails?: {
54
+ cacheReadTokens?: number;
55
+ cacheWriteTokens?: number;
56
+ };
57
+ };
58
+ /** Raw provider metadata, the fallback source for Anthropic cache counters. */
59
+ providerMetadata?: Record<string, Record<string, unknown>>;
60
+ /** Every other result field flows through unread and unmodified. */
61
+ [k: string]: unknown;
62
+ }
63
+ /** Structural stand-in for one Vercel AI SDK stream result: the part stream plus passthrough. */
64
+ interface StreamResultLike {
65
+ /** The stream of parts the host consumes, teed by the middleware, never consumed by it. */
66
+ stream: ReadableStream<unknown>;
67
+ /** Every other result field flows through unread and unmodified. */
68
+ [k: string]: unknown;
69
+ }
70
+ /**
71
+ * The middleware surface, structurally compatible with `LanguageModelV3Middleware` from
72
+ * the Vercel AI SDK. Hand the object to `wrapLanguageModel({ model, middleware })`.
73
+ */
74
+ interface RacsMiddleware {
75
+ /** Middleware specification version the Vercel AI SDK dispatches on. */
76
+ readonly middlewareVersion: 'v3';
77
+ /** Plans the call and decorates `providerOptions` with cache directives and the stash. */
78
+ transformParams(input: {
79
+ params: CallOptionsLike;
80
+ }): Promise<CallOptionsLike>;
81
+ /** Runs the wrapped generate call and records its usage into the engine ledger. */
82
+ wrapGenerate(input: {
83
+ doGenerate: () => PromiseLike<GenerateResultLike>;
84
+ params: CallOptionsLike;
85
+ }): Promise<GenerateResultLike>;
86
+ /** Tees the wrapped stream and records usage from its finish part, parts untouched. */
87
+ wrapStream(input: {
88
+ doStream: () => PromiseLike<StreamResultLike>;
89
+ params: CallOptionsLike;
90
+ }): Promise<StreamResultLike>;
91
+ }
92
+ /** Construction options for {@link racsMiddleware}. */
93
+ interface RacsMiddlewareOptions {
94
+ /** Provider every call through this middleware targets, selects the adapter family. */
95
+ readonly provider: ProviderId;
96
+ /** Model identifier, verbatim, for plan identity and pricing lookups. */
97
+ readonly model: string;
98
+ /**
99
+ * Replaces {@link defaultSegmenter}. Provide one when the host knows its own prompt
100
+ * anatomy, exact token counts, or stability declarations better than the structural
101
+ * default can guess them, which is most production hosts eventually.
102
+ */
103
+ readonly segmenter?: (params: CallOptionsLike) => readonly PromptSegment[];
104
+ /**
105
+ * Time source returning milliseconds since the Unix epoch, used to stamp recorded
106
+ * usage. Without it, recording omits the timestamp and the engine stamps it with its
107
+ * own injected clock, so the wall clock is never read here directly.
108
+ */
109
+ readonly clock?: () => number;
110
+ }
111
+ /**
112
+ * The default prompt segmenter, fully overridable through
113
+ * {@link RacsMiddlewareOptions.segmenter}. It maps the conventional Vercel AI SDK call anatomy
114
+ * onto RACS segments:
115
+ *
116
+ * - `system` (a string, or the head of the `prompt` message array when that head carries
117
+ * the system role) becomes one `'system'` segment declared stable.
118
+ * - `tools` becomes one `'tools'` segment declared stable, keyed by the JSON
119
+ * serialization of the tools value, so any byte-level churn in tool definitions
120
+ * surfaces through the `'unstable-tools'` and drift machinery.
121
+ * - every prompt message except the final one becomes one `'history'` segment declared
122
+ * semi, history grows monotonically in well-behaved agents.
123
+ * - the final message becomes one `'dynamic'` segment declared volatile, it is the live
124
+ * turn and differs on every call.
125
+ *
126
+ * Every read is defensive over unknown shapes: non-array prompts, message-less calls, and
127
+ * unstringifiable values degrade to fewer or emptier segments, never to a throw. Content
128
+ * is capped at {@link MAX_HASH_CONTENT_CHARS} characters per segment for hashing, so
129
+ * token estimates on very long segments are floors.
130
+ *
131
+ * @param params - The call options to segment, any structural shape tolerated.
132
+ * @returns Segments in request order, never empty.
133
+ */
134
+ declare function defaultSegmenter(params: CallOptionsLike): readonly PromptSegment[];
135
+ /**
136
+ * Creates the packaged RACS middleware for the Vercel AI SDK.
137
+ *
138
+ * What each hook does:
139
+ *
140
+ * `transformParams` runs {@link RACS.plan} over the segmented prompt and applies the
141
+ * directives through provider options:
142
+ * - Breakpoint family (`'breakpoint'` directives): sets
143
+ * `providerOptions.anthropic.cacheControl = { type: 'ephemeral' }`, with `ttl: '1h'`
144
+ * when any planned breakpoint chose the 1-hour tier. Placement note: the Vercel AI SDK applies
145
+ * message-level `cacheControl` to the last content block of a message; RACS sets the
146
+ * option at the request level, which the Anthropic provider honors for system and
147
+ * tools. Finer per-block placement, for example a marker after each planned
148
+ * `segmentId`, belongs to the host, which owns the message array.
149
+ * - Routing-key family (`'routing-key'` directives): sets
150
+ * `providerOptions.openai.promptCacheKey` to the deterministic key, plus
151
+ * `promptCacheRetention: '24h'` when the plan requested extended retention. For
152
+ * routing-key providers other than OpenAI the key is also mirrored under
153
+ * `providerOptions.racs.routingKey`, so hosts of those providers can forward it to
154
+ * whatever request field their provider reads.
155
+ * - Resource and passive families: no parameter changes, the host owns resource
156
+ * lifecycle calls and passive providers expose no surface.
157
+ * - Always: stashes `{ prefixKey, planId, provider, model }` under
158
+ * `providerOptions.racs`, the join key the wrap hooks read back at recording time.
159
+ * Provider-namespaced options unknown to a provider are ignored by it, so the stash
160
+ * rides the params without affecting the call.
161
+ *
162
+ * `wrapGenerate` awaits the wrapped call and records one {@link RACS} usage with these
163
+ * fallback chains, covering every Vercel AI SDK version and provider spelling in circulation:
164
+ * - `cacheReadTokens`: `usage.inputTokenDetails.cacheReadTokens`, then
165
+ * `usage.cachedInputTokens`, then `providerMetadata.anthropic.cacheReadInputTokens`,
166
+ * then zero.
167
+ * - `cacheWriteTokens5m`: `usage.inputTokenDetails.cacheWriteTokens`, then
168
+ * `providerMetadata.anthropic.cacheCreationInputTokens`, omitted when neither exists.
169
+ * - `inputTokens`: `usage.inputTokens`, then zero, normalized to the all-in convention
170
+ * {@link CacheUsage.inputTokens} documents: when the resolved input count is smaller
171
+ * than the resolved cached reads plus writes, the counts are provably exclusive (raw
172
+ * Anthropic counters surfaced through provider metadata) and the cached subsets are
173
+ * added back; the inline comment in the recorder documents the heuristic and its limit.
174
+ * When the `providerOptions.racs` stash is absent, because the host skipped
175
+ * `transformParams` or stripped the options, recording proceeds silently without prefix
176
+ * attribution, the call still aggregates into ledger totals. When the wrapped call
177
+ * throws, the error is rethrown and nothing is recorded: no usage exists to record. This
178
+ * is deliberately asymmetric with noeticos-style outcome tracking, where failures are
179
+ * themselves the signal; here the ledger accounts tokens, and a failed call billed none.
180
+ *
181
+ * `wrapStream` tees the result stream through a TransformStream that watches for the
182
+ * finish-shaped part (an object with `type: 'finish'` carrying usage under `part.usage`
183
+ * or `part.totalUsage`), passes every part through untouched, and records once on flush
184
+ * with the same fallback chains as `wrapGenerate`. Stream errors propagate to the
185
+ * consumer unchanged, and an errored stream never reaches flush, so nothing is recorded
186
+ * for it.
187
+ *
188
+ * @param racs - The engine that plans and accounts, see {@link RACS}.
189
+ * @param options - Provider, model, and the optional segmenter and clock overrides.
190
+ * @returns The middleware object, see {@link RacsMiddleware}.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * import { anthropic } from '@ai-sdk/anthropic';
195
+ * import { wrapLanguageModel } from 'ai';
196
+ * import { createRACS } from '@takk/racs';
197
+ * import { racsMiddleware } from '@takk/racs/vercel';
198
+ *
199
+ * const racs = createRACS();
200
+ * const model = wrapLanguageModel({
201
+ * model: anthropic('claude-sonnet-4-5'),
202
+ * middleware: racsMiddleware(racs, { provider: 'anthropic', model: 'claude-sonnet-4-5' }),
203
+ * });
204
+ * // Use `model` with generateText or streamText as usual, then:
205
+ * const { hitRatio, savedUsd } = racs.stats();
206
+ * ```
207
+ */
208
+ declare function racsMiddleware(racs: RACS, options: RacsMiddlewareOptions): RacsMiddleware;
209
+
210
+ export { type CallOptionsLike, type GenerateResultLike, type RacsMiddleware, type RacsMiddlewareOptions, type StreamResultLike, defaultSegmenter, racsMiddleware };
@@ -0,0 +1,206 @@
1
+ // src/vercel/index.ts
2
+ var MAX_HASH_CONTENT_CHARS = 8e3;
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null;
5
+ }
6
+ function safeStringify(value) {
7
+ try {
8
+ const text = JSON.stringify(value);
9
+ return typeof text === "string" ? text : void 0;
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ }
14
+ function capForHashing(text) {
15
+ return text.length > MAX_HASH_CONTENT_CHARS ? text.slice(0, MAX_HASH_CONTENT_CHARS) : text;
16
+ }
17
+ function countOf(value) {
18
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
19
+ }
20
+ function defaultSegmenter(params) {
21
+ const segments = [];
22
+ let messages = Array.isArray(params.prompt) ? params.prompt : [];
23
+ let systemText;
24
+ if (typeof params.system === "string" && params.system !== "") {
25
+ systemText = params.system;
26
+ } else {
27
+ const head = messages[0];
28
+ if (isRecord(head) && head.role === "system") {
29
+ const content = head.content;
30
+ systemText = typeof content === "string" ? content : safeStringify(content) ?? "";
31
+ messages = messages.slice(1);
32
+ }
33
+ }
34
+ if (systemText !== void 0) {
35
+ segments.push({
36
+ id: "system",
37
+ role: "system",
38
+ stability: "stable",
39
+ content: capForHashing(systemText)
40
+ });
41
+ }
42
+ if (params.tools !== void 0 && params.tools !== null) {
43
+ const text = safeStringify(params.tools);
44
+ if (text !== void 0 && text !== "[]" && text !== "{}") {
45
+ segments.push({
46
+ id: "tools",
47
+ role: "tools",
48
+ stability: "stable",
49
+ content: capForHashing(text)
50
+ });
51
+ }
52
+ }
53
+ if (messages.length > 1) {
54
+ segments.push({
55
+ id: "history",
56
+ role: "history",
57
+ stability: "semi",
58
+ content: capForHashing(safeStringify(messages.slice(0, -1)) ?? "")
59
+ });
60
+ }
61
+ if (messages.length > 0) {
62
+ segments.push({
63
+ id: "dynamic",
64
+ role: "dynamic",
65
+ stability: "volatile",
66
+ content: capForHashing(safeStringify(messages[messages.length - 1]) ?? "")
67
+ });
68
+ } else if (typeof params.prompt === "string" && params.prompt !== "") {
69
+ segments.push({
70
+ id: "dynamic",
71
+ role: "dynamic",
72
+ stability: "volatile",
73
+ content: capForHashing(params.prompt)
74
+ });
75
+ }
76
+ if (segments.length === 0) {
77
+ segments.push({ id: "dynamic", role: "dynamic", stability: "volatile", content: "" });
78
+ }
79
+ return segments;
80
+ }
81
+ function racsMiddleware(racs, options) {
82
+ const segmenter = options.segmenter ?? defaultSegmenter;
83
+ const prefixKeyOf = (params) => {
84
+ const stash = params.providerOptions?.racs;
85
+ if (stash === void 0) {
86
+ return void 0;
87
+ }
88
+ const prefixKey = stash.prefixKey;
89
+ return typeof prefixKey === "string" && prefixKey !== "" ? prefixKey : void 0;
90
+ };
91
+ const recordUsage = (usageLike, metadataLike, params) => {
92
+ const usage = isRecord(usageLike) ? usageLike : {};
93
+ const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : {};
94
+ const metadata = isRecord(metadataLike) ? metadataLike : {};
95
+ const anthropic = isRecord(metadata.anthropic) ? metadata.anthropic : {};
96
+ const cacheReadTokens = countOf(details.cacheReadTokens) ?? countOf(usage.cachedInputTokens) ?? countOf(anthropic.cacheReadInputTokens) ?? 0;
97
+ const cacheWriteTokens5m = countOf(details.cacheWriteTokens) ?? countOf(anthropic.cacheCreationInputTokens);
98
+ const rawInputTokens = countOf(usage.inputTokens) ?? 0;
99
+ const cachedSubsets = cacheReadTokens + (cacheWriteTokens5m ?? 0);
100
+ const inputTokens = rawInputTokens < cachedSubsets ? rawInputTokens + cachedSubsets : rawInputTokens;
101
+ const prefixKey = prefixKeyOf(params);
102
+ racs.record({
103
+ provider: options.provider,
104
+ model: options.model,
105
+ ...prefixKey !== void 0 ? { prefixKey } : {},
106
+ inputTokens,
107
+ cacheReadTokens,
108
+ ...cacheWriteTokens5m !== void 0 ? { cacheWriteTokens5m } : {},
109
+ ...options.clock !== void 0 ? { timestamp: options.clock() } : {}
110
+ });
111
+ };
112
+ return {
113
+ middlewareVersion: "v3",
114
+ transformParams: ({ params }) => {
115
+ const segments = segmenter(params);
116
+ if (segments.length === 0) {
117
+ return Promise.resolve(params);
118
+ }
119
+ const plan = racs.plan({ provider: options.provider, model: options.model, segments });
120
+ let hasBreakpoint = false;
121
+ let wantsOneHour = false;
122
+ let routingKey;
123
+ let routingRetention;
124
+ for (const directive of plan.directives) {
125
+ if (directive.kind === "breakpoint") {
126
+ hasBreakpoint = true;
127
+ if (directive.ttl === "1h") {
128
+ wantsOneHour = true;
129
+ }
130
+ } else if (directive.kind === "routing-key") {
131
+ routingKey = directive.key;
132
+ routingRetention = directive.retention;
133
+ }
134
+ }
135
+ const providerOptions = {
136
+ ...params.providerOptions
137
+ };
138
+ if (hasBreakpoint) {
139
+ providerOptions.anthropic = {
140
+ ...providerOptions.anthropic,
141
+ cacheControl: { type: "ephemeral", ...wantsOneHour ? { ttl: "1h" } : {} }
142
+ };
143
+ }
144
+ let routingMirror = {};
145
+ if (routingKey !== void 0) {
146
+ providerOptions.openai = {
147
+ ...providerOptions.openai,
148
+ promptCacheKey: routingKey,
149
+ ...routingRetention !== void 0 ? { promptCacheRetention: "24h" } : {}
150
+ };
151
+ if (plan.provider !== "openai") {
152
+ routingMirror = {
153
+ routingKey,
154
+ ...routingRetention !== void 0 ? { retention: "24h" } : {}
155
+ };
156
+ }
157
+ }
158
+ providerOptions.racs = {
159
+ ...providerOptions.racs,
160
+ ...routingMirror,
161
+ prefixKey: plan.prefixKey,
162
+ planId: plan.planId,
163
+ provider: plan.provider,
164
+ model: plan.model
165
+ };
166
+ return Promise.resolve({ ...params, providerOptions });
167
+ },
168
+ wrapGenerate: async ({ doGenerate, params }) => {
169
+ const result = await doGenerate();
170
+ recordUsage(result.usage, result.providerMetadata, params);
171
+ return result;
172
+ },
173
+ wrapStream: async ({ doStream, params }) => {
174
+ const result = await doStream();
175
+ let sawFinish = false;
176
+ let finishUsage;
177
+ let finishMetadata;
178
+ const watcher = new TransformStream({
179
+ transform: (part, controller) => {
180
+ if (!sawFinish && isRecord(part) && part.type === "finish") {
181
+ sawFinish = true;
182
+ const direct = part.usage;
183
+ const total = part.totalUsage;
184
+ if (isRecord(direct)) {
185
+ finishUsage = direct;
186
+ } else if (isRecord(total)) {
187
+ finishUsage = total;
188
+ }
189
+ finishMetadata = part.providerMetadata;
190
+ }
191
+ controller.enqueue(part);
192
+ },
193
+ flush: () => {
194
+ if (sawFinish) {
195
+ recordUsage(finishUsage, finishMetadata, params);
196
+ }
197
+ }
198
+ });
199
+ return { ...result, stream: result.stream.pipeThrough(watcher) };
200
+ }
201
+ };
202
+ }
203
+
204
+ export { defaultSegmenter, racsMiddleware };
205
+ //# sourceMappingURL=index.js.map
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vercel/index.ts"],"names":[],"mappings":";AAqHA,IAAM,sBAAA,GAAyB,GAAA;AAG/B,SAAS,SAAS,KAAA,EAAkD;AAClE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAMA,SAAS,cAAc,KAAA,EAAoC;AACzD,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACjC,IAAA,OAAO,OAAO,IAAA,KAAS,QAAA,GAAW,IAAA,GAAO,KAAA,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,cAAc,IAAA,EAAsB;AAC3C,EAAA,OAAO,KAAK,MAAA,GAAS,sBAAA,GAAyB,KAAK,KAAA,CAAM,CAAA,EAAG,sBAAsB,CAAA,GAAI,IAAA;AACxF;AAGA,SAAS,QAAQ,KAAA,EAAoC;AACnD,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,MAAA,CAAO,SAAS,KAAK,CAAA,IAAK,KAAA,IAAS,CAAA,GAAI,KAAA,GAAQ,MAAA;AACrF;AAyBO,SAAS,iBAAiB,MAAA,EAAmD;AAClF,EAAA,MAAM,WAA4B,EAAC;AACnC,EAAA,IAAI,QAAA,GAA+B,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA,GAAI,MAAA,CAAO,SAAS,EAAC;AAEnF,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,OAAO,MAAA,CAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,WAAW,EAAA,EAAI;AAC7D,IAAA,UAAA,GAAa,MAAA,CAAO,MAAA;AAAA,EACtB,CAAA,MAAO;AACL,IAAA,MAAM,IAAA,GAAO,SAAS,CAAC,CAAA;AACvB,IAAA,IAAI,QAAA,CAAS,IAAI,CAAA,IAAK,IAAA,CAAK,SAAS,QAAA,EAAU;AAC5C,MAAA,MAAM,UAAU,IAAA,CAAK,OAAA;AACrB,MAAA,UAAA,GAAa,OAAO,OAAA,KAAY,QAAA,GAAW,OAAA,GAAW,aAAA,CAAc,OAAO,CAAA,IAAK,EAAA;AAChF,MAAA,QAAA,GAAW,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,IAC7B;AAAA,EACF;AACA,EAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,EAAA,EAAI,QAAA;AAAA,MACJ,IAAA,EAAM,QAAA;AAAA,MACN,SAAA,EAAW,QAAA;AAAA,MACX,OAAA,EAAS,cAAc,UAAU;AAAA,KAClC,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,IAAa,MAAA,CAAO,UAAU,IAAA,EAAM;AACvD,IAAA,MAAM,IAAA,GAAO,aAAA,CAAc,MAAA,CAAO,KAAK,CAAA;AACvC,IAAA,IAAI,IAAA,KAAS,MAAA,IAAa,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACxD,MAAA,QAAA,CAAS,IAAA,CAAK;AAAA,QACZ,EAAA,EAAI,OAAA;AAAA,QACJ,IAAA,EAAM,OAAA;AAAA,QACN,SAAA,EAAW,QAAA;AAAA,QACX,OAAA,EAAS,cAAc,IAAI;AAAA,OAC5B,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,EAAA,EAAI,SAAA;AAAA,MACJ,IAAA,EAAM,SAAA;AAAA,MACN,SAAA,EAAW,MAAA;AAAA,MACX,OAAA,EAAS,cAAc,aAAA,CAAc,QAAA,CAAS,MAAM,CAAA,EAAG,EAAE,CAAC,CAAA,IAAK,EAAE;AAAA,KAClE,CAAA;AAAA,EACH;AACA,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,EAAA,EAAI,SAAA;AAAA,MACJ,IAAA,EAAM,SAAA;AAAA,MACN,SAAA,EAAW,UAAA;AAAA,MACX,OAAA,EAAS,cAAc,aAAA,CAAc,QAAA,CAAS,SAAS,MAAA,GAAS,CAAC,CAAC,CAAA,IAAK,EAAE;AAAA,KAC1E,CAAA;AAAA,EACH,WAAW,OAAO,MAAA,CAAO,WAAW,QAAA,IAAY,MAAA,CAAO,WAAW,EAAA,EAAI;AACpE,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,EAAA,EAAI,SAAA;AAAA,MACJ,IAAA,EAAM,SAAA;AAAA,MACN,SAAA,EAAW,UAAA;AAAA,MACX,OAAA,EAAS,aAAA,CAAc,MAAA,CAAO,MAAM;AAAA,KACrC,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AAGzB,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,EAAA,EAAI,SAAA,EAAW,IAAA,EAAM,WAAW,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,EAAA,EAAI,CAAA;AAAA,EACtF;AACA,EAAA,OAAO,QAAA;AACT;AA2EO,SAAS,cAAA,CAAe,MAAY,OAAA,EAAgD;AACzF,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,gBAAA;AAGvC,EAAA,MAAM,WAAA,GAAc,CAAC,MAAA,KAAgD;AACnE,IAAA,MAAM,KAAA,GAAQ,OAAO,eAAA,EAAiB,IAAA;AACtC,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,MAAM,YAAY,KAAA,CAAM,SAAA;AACxB,IAAA,OAAO,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,KAAc,KAAK,SAAA,GAAY,MAAA;AAAA,EACzE,CAAA;AAGA,EAAA,MAAM,WAAA,GAAc,CAClB,SAAA,EACA,YAAA,EACA,MAAA,KACS;AACT,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,SAAS,CAAA,GAAI,YAAY,EAAC;AACjD,IAAA,MAAM,UAAU,QAAA,CAAS,KAAA,CAAM,iBAAiB,CAAA,GAAI,KAAA,CAAM,oBAAoB,EAAC;AAC/E,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,YAAY,CAAA,GAAI,eAAe,EAAC;AAC1D,IAAA,MAAM,YAAY,QAAA,CAAS,QAAA,CAAS,SAAS,CAAA,GAAI,QAAA,CAAS,YAAY,EAAC;AAEvE,IAAA,MAAM,eAAA,GACJ,OAAA,CAAQ,OAAA,CAAQ,eAAe,CAAA,IAC/B,OAAA,CAAQ,KAAA,CAAM,iBAAiB,CAAA,IAC/B,OAAA,CAAQ,SAAA,CAAU,oBAAoB,CAAA,IACtC,CAAA;AACF,IAAA,MAAM,qBACJ,OAAA,CAAQ,OAAA,CAAQ,gBAAgB,CAAA,IAAK,OAAA,CAAQ,UAAU,wBAAwB,CAAA;AACjF,IAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,KAAA,CAAM,WAAW,CAAA,IAAK,CAAA;AAUrD,IAAA,MAAM,aAAA,GAAgB,mBAAmB,kBAAA,IAAsB,CAAA,CAAA;AAC/D,IAAA,MAAM,WAAA,GACJ,cAAA,GAAiB,aAAA,GAAgB,cAAA,GAAiB,aAAA,GAAgB,cAAA;AACpE,IAAA,MAAM,SAAA,GAAY,YAAY,MAAM,CAAA;AAEpC,IAAA,IAAA,CAAK,MAAA,CAAO;AAAA,MACV,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,OAAO,OAAA,CAAQ,KAAA;AAAA,MACf,GAAI,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,KAAc,EAAC;AAAA,MAC/C,WAAA;AAAA,MACA,eAAA;AAAA,MACA,GAAI,kBAAA,KAAuB,MAAA,GAAY,EAAE,kBAAA,KAAuB,EAAC;AAAA,MACjE,GAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,GAAY,EAAE,WAAW,OAAA,CAAQ,KAAA,EAAM,EAAE,GAAI;AAAC,KACrE,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,iBAAA,EAAmB,IAAA;AAAA,IAEnB,eAAA,EAAiB,CAAC,EAAE,MAAA,EAAO,KAAgC;AACzD,MAAA,MAAM,QAAA,GAAW,UAAU,MAAM,CAAA;AACjC,MAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AAGzB,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,MAC/B;AACA,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,OAAA,CAAQ,QAAA,EAAU,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,QAAA,EAAU,CAAA;AAErF,MAAA,IAAI,aAAA,GAAgB,KAAA;AACpB,MAAA,IAAI,YAAA,GAAe,KAAA;AACnB,MAAA,IAAI,UAAA;AACJ,MAAA,IAAI,gBAAA;AACJ,MAAA,KAAA,MAAW,SAAA,IAAa,KAAK,UAAA,EAAY;AACvC,QAAA,IAAI,SAAA,CAAU,SAAS,YAAA,EAAc;AACnC,UAAA,aAAA,GAAgB,IAAA;AAChB,UAAA,IAAI,SAAA,CAAU,QAAQ,IAAA,EAAM;AAC1B,YAAA,YAAA,GAAe,IAAA;AAAA,UACjB;AAAA,QACF,CAAA,MAAA,IAAW,SAAA,CAAU,IAAA,KAAS,aAAA,EAAe;AAC3C,UAAA,UAAA,GAAa,SAAA,CAAU,GAAA;AACvB,UAAA,gBAAA,GAAmB,SAAA,CAAU,SAAA;AAAA,QAC/B;AAAA,MAGF;AAEA,MAAA,MAAM,eAAA,GAA2D;AAAA,QAC/D,GAAG,MAAA,CAAO;AAAA,OACZ;AACA,MAAA,IAAI,aAAA,EAAe;AACjB,QAAA,eAAA,CAAgB,SAAA,GAAY;AAAA,UAC1B,GAAG,eAAA,CAAgB,SAAA;AAAA,UACnB,YAAA,EAAc,EAAE,IAAA,EAAM,WAAA,EAAa,GAAI,YAAA,GAAe,EAAE,GAAA,EAAK,IAAA,EAAK,GAAI,EAAC;AAAG,SAC5E;AAAA,MACF;AACA,MAAA,IAAI,gBAAyC,EAAC;AAC9C,MAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,QAAA,eAAA,CAAgB,MAAA,GAAS;AAAA,UACvB,GAAG,eAAA,CAAgB,MAAA;AAAA,UACnB,cAAA,EAAgB,UAAA;AAAA,UAChB,GAAI,gBAAA,KAAqB,MAAA,GAAY,EAAE,oBAAA,EAAsB,KAAA,KAAU;AAAC,SAC1E;AACA,QAAA,IAAI,IAAA,CAAK,aAAa,QAAA,EAAU;AAC9B,UAAA,aAAA,GAAgB;AAAA,YACd,UAAA;AAAA,YACA,GAAI,gBAAA,KAAqB,MAAA,GAAY,EAAE,SAAA,EAAW,KAAA,KAAU;AAAC,WAC/D;AAAA,QACF;AAAA,MACF;AACA,MAAA,eAAA,CAAgB,IAAA,GAAO;AAAA,QACrB,GAAG,eAAA,CAAgB,IAAA;AAAA,QACnB,GAAG,aAAA;AAAA,QACH,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,OAAO,IAAA,CAAK;AAAA,OACd;AAEA,MAAA,OAAO,QAAQ,OAAA,CAAQ,EAAE,GAAG,MAAA,EAAQ,iBAAiB,CAAA;AAAA,IACvD,CAAA;AAAA,IAEA,YAAA,EAAc,OAAO,EAAE,UAAA,EAAY,QAAO,KAAmC;AAG3E,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,MAAA,WAAA,CAAY,MAAA,CAAO,KAAA,EAAO,MAAA,CAAO,gBAAA,EAAkB,MAAM,CAAA;AACzD,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IAEA,UAAA,EAAY,OAAO,EAAE,QAAA,EAAU,QAAO,KAAiC;AACrE,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,EAAS;AAC9B,MAAA,IAAI,SAAA,GAAY,KAAA;AAChB,MAAA,IAAI,WAAA;AACJ,MAAA,IAAI,cAAA;AACJ,MAAA,MAAM,OAAA,GAAU,IAAI,eAAA,CAAkC;AAAA,QACpD,SAAA,EAAW,CAAC,IAAA,EAAM,UAAA,KAAqB;AACrC,UAAA,IAAI,CAAC,SAAA,IAAa,QAAA,CAAS,IAAI,CAAA,IAAK,IAAA,CAAK,SAAS,QAAA,EAAU;AAC1D,YAAA,SAAA,GAAY,IAAA;AACZ,YAAA,MAAM,SAAS,IAAA,CAAK,KAAA;AACpB,YAAA,MAAM,QAAQ,IAAA,CAAK,UAAA;AACnB,YAAA,IAAI,QAAA,CAAS,MAAM,CAAA,EAAG;AACpB,cAAA,WAAA,GAAc,MAAA;AAAA,YAChB,CAAA,MAAA,IAAW,QAAA,CAAS,KAAK,CAAA,EAAG;AAC1B,cAAA,WAAA,GAAc,KAAA;AAAA,YAChB;AACA,YAAA,cAAA,GAAiB,IAAA,CAAK,gBAAA;AAAA,UACxB;AACA,UAAA,UAAA,CAAW,QAAQ,IAAI,CAAA;AAAA,QACzB,CAAA;AAAA,QACA,OAAO,MAAY;AAGjB,UAAA,IAAI,SAAA,EAAW;AACb,YAAA,WAAA,CAAY,WAAA,EAAa,gBAAgB,MAAM,CAAA;AAAA,UACjD;AAAA,QACF;AAAA,OACD,CAAA;AACD,MAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAA,EAAQ,OAAO,MAAA,CAAO,WAAA,CAAY,OAAO,CAAA,EAAE;AAAA,IACjE;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * Packaged Vercel AI SDK middleware for RACS (Remote Agent Context Store): one middleware\n * object that plans cache directives before each call, applies them through provider\n * options, and records the usage the provider reports back, for both generate and stream\n * paths.\n *\n * Structural by design. This module declares its own shapes ({@link CallOptionsLike},\n * {@link GenerateResultLike}, {@link StreamResultLike}, {@link RacsMiddleware}) instead of\n * importing from the `ai` package, so the package keeps its zero-runtime-dependency\n * invariant. The shapes match the `LanguageModelV3Middleware` contract structurally, which\n * is all the Vercel AI SDK requires: pass the object to `wrapLanguageModel({ model, middleware })`\n * and it just works, with no version coupling to the `ai` dependency graph.\n *\n * The product invariant holds here too: the middleware never performs a provider call. It\n * decorates the params the host's own model call will send, and reads the usage the host's\n * own call already produced.\n *\n * @packageDocumentation\n */\n\nimport type { PromptSegment, ProviderId, RACS } from '../types.js';\n\n/**\n * Structural stand-in for the Vercel AI SDK call options handed to `transformParams`,\n * `wrapGenerate`, and `wrapStream`. Only the fields the middleware touches are named,\n * everything else flows through the index signature untouched.\n */\nexport interface CallOptionsLike {\n /** System instructions, a string in most hosts, tolerated in any shape. */\n system?: unknown;\n /** The prompt, usually an array of messages, tolerated in any shape. */\n prompt?: unknown;\n /** Tool definitions, hashed as one stable segment when present. */\n tools?: unknown;\n /** Provider-namespaced options, where cache directives and the RACS stash land. */\n providerOptions?: Record<string, Record<string, unknown>>;\n /** Every other call option flows through unread and unmodified. */\n [k: string]: unknown;\n}\n\n/**\n * Structural stand-in for one Vercel AI SDK generate result, the subset usage recording reads.\n * Field availability varies by SDK version and provider, which is why every read falls\n * back along a documented chain, see {@link racsMiddleware}.\n */\nexport interface GenerateResultLike {\n /** Normalized usage as the Vercel AI SDK reports it, every field optional in the wild. */\n usage?: {\n /** Total billed input tokens, cached and uncached together. */\n inputTokens?: number;\n /** Cached input tokens in the flattened spelling some versions use. */\n cachedInputTokens?: number;\n /** Cached read and write detail in the nested spelling other versions use. */\n inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };\n };\n /** Raw provider metadata, the fallback source for Anthropic cache counters. */\n providerMetadata?: Record<string, Record<string, unknown>>;\n /** Every other result field flows through unread and unmodified. */\n [k: string]: unknown;\n}\n\n/** Structural stand-in for one Vercel AI SDK stream result: the part stream plus passthrough. */\nexport interface StreamResultLike {\n /** The stream of parts the host consumes, teed by the middleware, never consumed by it. */\n stream: ReadableStream<unknown>;\n /** Every other result field flows through unread and unmodified. */\n [k: string]: unknown;\n}\n\n/**\n * The middleware surface, structurally compatible with `LanguageModelV3Middleware` from\n * the Vercel AI SDK. Hand the object to `wrapLanguageModel({ model, middleware })`.\n */\nexport interface RacsMiddleware {\n /** Middleware specification version the Vercel AI SDK dispatches on. */\n readonly middlewareVersion: 'v3';\n /** Plans the call and decorates `providerOptions` with cache directives and the stash. */\n transformParams(input: { params: CallOptionsLike }): Promise<CallOptionsLike>;\n /** Runs the wrapped generate call and records its usage into the engine ledger. */\n wrapGenerate(input: {\n doGenerate: () => PromiseLike<GenerateResultLike>;\n params: CallOptionsLike;\n }): Promise<GenerateResultLike>;\n /** Tees the wrapped stream and records usage from its finish part, parts untouched. */\n wrapStream(input: {\n doStream: () => PromiseLike<StreamResultLike>;\n params: CallOptionsLike;\n }): Promise<StreamResultLike>;\n}\n\n/** Construction options for {@link racsMiddleware}. */\nexport interface RacsMiddlewareOptions {\n /** Provider every call through this middleware targets, selects the adapter family. */\n readonly provider: ProviderId;\n /** Model identifier, verbatim, for plan identity and pricing lookups. */\n readonly model: string;\n /**\n * Replaces {@link defaultSegmenter}. Provide one when the host knows its own prompt\n * anatomy, exact token counts, or stability declarations better than the structural\n * default can guess them, which is most production hosts eventually.\n */\n readonly segmenter?: (params: CallOptionsLike) => readonly PromptSegment[];\n /**\n * Time source returning milliseconds since the Unix epoch, used to stamp recorded\n * usage. Without it, recording omits the timestamp and the engine stamps it with its\n * own injected clock, so the wall clock is never read here directly.\n */\n readonly clock?: () => number;\n}\n\n/**\n * Cap on segment content length used for hashing. Segments exist to be keyed and\n * estimated, not stored, and hashing the first 8000 characters keys realistic prompts\n * uniquely while keeping the segmenter allocation-bounded on huge documents. Token\n * estimates for longer segments are therefore floors, hosts needing exact economics\n * should pass a custom segmenter with explicit `tokens`.\n */\nconst MAX_HASH_CONTENT_CHARS = 8000;\n\n/** True for any non-null object, the first gate of every structural check. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * JSON-stringifies defensively: circular structures and other stringify throws come back\n * absent instead of propagating, and `undefined` input stays absent.\n */\nfunction safeStringify(value: unknown): string | undefined {\n try {\n const text = JSON.stringify(value);\n return typeof text === 'string' ? text : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Applies the {@link MAX_HASH_CONTENT_CHARS} cap. */\nfunction capForHashing(text: string): string {\n return text.length > MAX_HASH_CONTENT_CHARS ? text.slice(0, MAX_HASH_CONTENT_CHARS) : text;\n}\n\n/** Reads one value as a finite non-negative token count, anything else is absent. */\nfunction countOf(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\n/**\n * The default prompt segmenter, fully overridable through\n * {@link RacsMiddlewareOptions.segmenter}. It maps the conventional Vercel AI SDK call anatomy\n * onto RACS segments:\n *\n * - `system` (a string, or the head of the `prompt` message array when that head carries\n * the system role) becomes one `'system'` segment declared stable.\n * - `tools` becomes one `'tools'` segment declared stable, keyed by the JSON\n * serialization of the tools value, so any byte-level churn in tool definitions\n * surfaces through the `'unstable-tools'` and drift machinery.\n * - every prompt message except the final one becomes one `'history'` segment declared\n * semi, history grows monotonically in well-behaved agents.\n * - the final message becomes one `'dynamic'` segment declared volatile, it is the live\n * turn and differs on every call.\n *\n * Every read is defensive over unknown shapes: non-array prompts, message-less calls, and\n * unstringifiable values degrade to fewer or emptier segments, never to a throw. Content\n * is capped at {@link MAX_HASH_CONTENT_CHARS} characters per segment for hashing, so\n * token estimates on very long segments are floors.\n *\n * @param params - The call options to segment, any structural shape tolerated.\n * @returns Segments in request order, never empty.\n */\nexport function defaultSegmenter(params: CallOptionsLike): readonly PromptSegment[] {\n const segments: PromptSegment[] = [];\n let messages: readonly unknown[] = Array.isArray(params.prompt) ? params.prompt : [];\n\n let systemText: string | undefined;\n if (typeof params.system === 'string' && params.system !== '') {\n systemText = params.system;\n } else {\n const head = messages[0];\n if (isRecord(head) && head.role === 'system') {\n const content = head.content;\n systemText = typeof content === 'string' ? content : (safeStringify(content) ?? '');\n messages = messages.slice(1);\n }\n }\n if (systemText !== undefined) {\n segments.push({\n id: 'system',\n role: 'system',\n stability: 'stable',\n content: capForHashing(systemText),\n });\n }\n\n if (params.tools !== undefined && params.tools !== null) {\n const text = safeStringify(params.tools);\n if (text !== undefined && text !== '[]' && text !== '{}') {\n segments.push({\n id: 'tools',\n role: 'tools',\n stability: 'stable',\n content: capForHashing(text),\n });\n }\n }\n\n if (messages.length > 1) {\n segments.push({\n id: 'history',\n role: 'history',\n stability: 'semi',\n content: capForHashing(safeStringify(messages.slice(0, -1)) ?? ''),\n });\n }\n if (messages.length > 0) {\n segments.push({\n id: 'dynamic',\n role: 'dynamic',\n stability: 'volatile',\n content: capForHashing(safeStringify(messages[messages.length - 1]) ?? ''),\n });\n } else if (typeof params.prompt === 'string' && params.prompt !== '') {\n segments.push({\n id: 'dynamic',\n role: 'dynamic',\n stability: 'volatile',\n content: capForHashing(params.prompt),\n });\n }\n\n if (segments.length === 0) {\n // A plan needs at least one segment; an empty call degrades to one empty volatile\n // turn so planning still runs and accounting still attributes the call.\n segments.push({ id: 'dynamic', role: 'dynamic', stability: 'volatile', content: '' });\n }\n return segments;\n}\n\n/**\n * Creates the packaged RACS middleware for the Vercel AI SDK.\n *\n * What each hook does:\n *\n * `transformParams` runs {@link RACS.plan} over the segmented prompt and applies the\n * directives through provider options:\n * - Breakpoint family (`'breakpoint'` directives): sets\n * `providerOptions.anthropic.cacheControl = { type: 'ephemeral' }`, with `ttl: '1h'`\n * when any planned breakpoint chose the 1-hour tier. Placement note: the Vercel AI SDK applies\n * message-level `cacheControl` to the last content block of a message; RACS sets the\n * option at the request level, which the Anthropic provider honors for system and\n * tools. Finer per-block placement, for example a marker after each planned\n * `segmentId`, belongs to the host, which owns the message array.\n * - Routing-key family (`'routing-key'` directives): sets\n * `providerOptions.openai.promptCacheKey` to the deterministic key, plus\n * `promptCacheRetention: '24h'` when the plan requested extended retention. For\n * routing-key providers other than OpenAI the key is also mirrored under\n * `providerOptions.racs.routingKey`, so hosts of those providers can forward it to\n * whatever request field their provider reads.\n * - Resource and passive families: no parameter changes, the host owns resource\n * lifecycle calls and passive providers expose no surface.\n * - Always: stashes `{ prefixKey, planId, provider, model }` under\n * `providerOptions.racs`, the join key the wrap hooks read back at recording time.\n * Provider-namespaced options unknown to a provider are ignored by it, so the stash\n * rides the params without affecting the call.\n *\n * `wrapGenerate` awaits the wrapped call and records one {@link RACS} usage with these\n * fallback chains, covering every Vercel AI SDK version and provider spelling in circulation:\n * - `cacheReadTokens`: `usage.inputTokenDetails.cacheReadTokens`, then\n * `usage.cachedInputTokens`, then `providerMetadata.anthropic.cacheReadInputTokens`,\n * then zero.\n * - `cacheWriteTokens5m`: `usage.inputTokenDetails.cacheWriteTokens`, then\n * `providerMetadata.anthropic.cacheCreationInputTokens`, omitted when neither exists.\n * - `inputTokens`: `usage.inputTokens`, then zero, normalized to the all-in convention\n * {@link CacheUsage.inputTokens} documents: when the resolved input count is smaller\n * than the resolved cached reads plus writes, the counts are provably exclusive (raw\n * Anthropic counters surfaced through provider metadata) and the cached subsets are\n * added back; the inline comment in the recorder documents the heuristic and its limit.\n * When the `providerOptions.racs` stash is absent, because the host skipped\n * `transformParams` or stripped the options, recording proceeds silently without prefix\n * attribution, the call still aggregates into ledger totals. When the wrapped call\n * throws, the error is rethrown and nothing is recorded: no usage exists to record. This\n * is deliberately asymmetric with noeticos-style outcome tracking, where failures are\n * themselves the signal; here the ledger accounts tokens, and a failed call billed none.\n *\n * `wrapStream` tees the result stream through a TransformStream that watches for the\n * finish-shaped part (an object with `type: 'finish'` carrying usage under `part.usage`\n * or `part.totalUsage`), passes every part through untouched, and records once on flush\n * with the same fallback chains as `wrapGenerate`. Stream errors propagate to the\n * consumer unchanged, and an errored stream never reaches flush, so nothing is recorded\n * for it.\n *\n * @param racs - The engine that plans and accounts, see {@link RACS}.\n * @param options - Provider, model, and the optional segmenter and clock overrides.\n * @returns The middleware object, see {@link RacsMiddleware}.\n *\n * @example\n * ```ts\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { wrapLanguageModel } from 'ai';\n * import { createRACS } from '@takk/racs';\n * import { racsMiddleware } from '@takk/racs/vercel';\n *\n * const racs = createRACS();\n * const model = wrapLanguageModel({\n * model: anthropic('claude-sonnet-4-5'),\n * middleware: racsMiddleware(racs, { provider: 'anthropic', model: 'claude-sonnet-4-5' }),\n * });\n * // Use `model` with generateText or streamText as usual, then:\n * const { hitRatio, savedUsd } = racs.stats();\n * ```\n */\nexport function racsMiddleware(racs: RACS, options: RacsMiddlewareOptions): RacsMiddleware {\n const segmenter = options.segmenter ?? defaultSegmenter;\n\n /** Reads the prefix key from the `providerOptions.racs` stash, absent when missing. */\n const prefixKeyOf = (params: CallOptionsLike): string | undefined => {\n const stash = params.providerOptions?.racs;\n if (stash === undefined) {\n return undefined;\n }\n const prefixKey = stash.prefixKey;\n return typeof prefixKey === 'string' && prefixKey !== '' ? prefixKey : undefined;\n };\n\n /** Builds and records one usage from any usage-shaped object plus provider metadata. */\n const recordUsage = (\n usageLike: unknown,\n metadataLike: unknown,\n params: CallOptionsLike,\n ): void => {\n const usage = isRecord(usageLike) ? usageLike : {};\n const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : {};\n const metadata = isRecord(metadataLike) ? metadataLike : {};\n const anthropic = isRecord(metadata.anthropic) ? metadata.anthropic : {};\n\n const cacheReadTokens =\n countOf(details.cacheReadTokens) ??\n countOf(usage.cachedInputTokens) ??\n countOf(anthropic.cacheReadInputTokens) ??\n 0;\n const cacheWriteTokens5m =\n countOf(details.cacheWriteTokens) ?? countOf(anthropic.cacheCreationInputTokens);\n const rawInputTokens = countOf(usage.inputTokens) ?? 0;\n // All-in normalization, see CacheUsage.inputTokens: SDK versions that surface the raw\n // Anthropic counters (providerMetadata.anthropic.cacheCreationInputTokens and peers)\n // report EXCLUSIVE counts where usage.inputTokens carries only the fresh uncached\n // input. Detection: an all-in input can never be smaller than the sum of its own\n // cached subsets, so rawInput < reads + writes proves the counts are exclusive and\n // the subsets are added back. Known limit: exclusive counts whose fresh input\n // outweighs reads plus writes are indistinguishable from all-in counts and pass\n // through unadjusted, undercounting total input; hosts on such SDK versions should\n // normalize in their own segmenter-side accounting before recording.\n const cachedSubsets = cacheReadTokens + (cacheWriteTokens5m ?? 0);\n const inputTokens =\n rawInputTokens < cachedSubsets ? rawInputTokens + cachedSubsets : rawInputTokens;\n const prefixKey = prefixKeyOf(params);\n\n racs.record({\n provider: options.provider,\n model: options.model,\n ...(prefixKey !== undefined ? { prefixKey } : {}),\n inputTokens,\n cacheReadTokens,\n ...(cacheWriteTokens5m !== undefined ? { cacheWriteTokens5m } : {}),\n ...(options.clock !== undefined ? { timestamp: options.clock() } : {}),\n });\n };\n\n return {\n middlewareVersion: 'v3',\n\n transformParams: ({ params }): Promise<CallOptionsLike> => {\n const segments = segmenter(params);\n if (segments.length === 0) {\n // A custom segmenter declared nothing to plan; the params pass through unchanged\n // rather than feeding the engine an input it would reject.\n return Promise.resolve(params);\n }\n const plan = racs.plan({ provider: options.provider, model: options.model, segments });\n\n let hasBreakpoint = false;\n let wantsOneHour = false;\n let routingKey: string | undefined;\n let routingRetention: '24h' | undefined;\n for (const directive of plan.directives) {\n if (directive.kind === 'breakpoint') {\n hasBreakpoint = true;\n if (directive.ttl === '1h') {\n wantsOneHour = true;\n }\n } else if (directive.kind === 'routing-key') {\n routingKey = directive.key;\n routingRetention = directive.retention;\n }\n // 'resource' and 'none' directives change no parameters by design: resource\n // lifecycle calls belong to the host, passive providers expose no surface.\n }\n\n const providerOptions: Record<string, Record<string, unknown>> = {\n ...params.providerOptions,\n };\n if (hasBreakpoint) {\n providerOptions.anthropic = {\n ...providerOptions.anthropic,\n cacheControl: { type: 'ephemeral', ...(wantsOneHour ? { ttl: '1h' } : {}) },\n };\n }\n let routingMirror: Record<string, unknown> = {};\n if (routingKey !== undefined) {\n providerOptions.openai = {\n ...providerOptions.openai,\n promptCacheKey: routingKey,\n ...(routingRetention !== undefined ? { promptCacheRetention: '24h' } : {}),\n };\n if (plan.provider !== 'openai') {\n routingMirror = {\n routingKey,\n ...(routingRetention !== undefined ? { retention: '24h' } : {}),\n };\n }\n }\n providerOptions.racs = {\n ...providerOptions.racs,\n ...routingMirror,\n prefixKey: plan.prefixKey,\n planId: plan.planId,\n provider: plan.provider,\n model: plan.model,\n };\n\n return Promise.resolve({ ...params, providerOptions });\n },\n\n wrapGenerate: async ({ doGenerate, params }): Promise<GenerateResultLike> => {\n // No try/catch around the wrapped call: a throw means no usage exists to record,\n // so it propagates unrecorded, see the asymmetry note on racsMiddleware.\n const result = await doGenerate();\n recordUsage(result.usage, result.providerMetadata, params);\n return result;\n },\n\n wrapStream: async ({ doStream, params }): Promise<StreamResultLike> => {\n const result = await doStream();\n let sawFinish = false;\n let finishUsage: unknown;\n let finishMetadata: unknown;\n const watcher = new TransformStream<unknown, unknown>({\n transform: (part, controller): void => {\n if (!sawFinish && isRecord(part) && part.type === 'finish') {\n sawFinish = true;\n const direct = part.usage;\n const total = part.totalUsage;\n if (isRecord(direct)) {\n finishUsage = direct;\n } else if (isRecord(total)) {\n finishUsage = total;\n }\n finishMetadata = part.providerMetadata;\n }\n controller.enqueue(part);\n },\n flush: (): void => {\n // Flush fires only when the source stream completes cleanly; an errored stream\n // propagates its error to the consumer and records nothing.\n if (sawFinish) {\n recordUsage(finishUsage, finishMetadata, params);\n }\n },\n });\n return { ...result, stream: result.stream.pipeThrough(watcher) };\n },\n };\n}\n"]}