@caeliq/llms 1.0.66 → 1.0.68

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 (44) hide show
  1. package/README.md +2 -1
  2. package/dist/cjs/server.cjs +232 -222
  3. package/dist/cjs/server.cjs.map +4 -4
  4. package/dist/cursor-sdk/auth-exchange-cache.d.ts +23 -0
  5. package/dist/cursor-sdk/events-to-sse.d.ts +3 -3
  6. package/dist/cursor-sdk/lifecycle-planner.d.ts +21 -4
  7. package/dist/cursor-sdk/prompt.d.ts +10 -0
  8. package/dist/cursor-sdk/session.d.ts +44 -0
  9. package/dist/cursor-sdk/shared.d.ts +7 -0
  10. package/dist/cursor-sdk/turn-output.d.ts +3 -0
  11. package/dist/cursor-sdk/usage.d.ts +27 -3
  12. package/dist/esm/server.mjs +232 -222
  13. package/dist/esm/server.mjs.map +4 -4
  14. package/dist/routing/protocol-endpoints.d.ts +14 -0
  15. package/dist/server.d.ts +1 -1
  16. package/dist/session-registry.d.ts +36 -0
  17. package/dist/tests/anthropic.message-start-usage.test.d.ts +1 -0
  18. package/dist/tests/cache-outcome.d.ts +1 -0
  19. package/dist/tests/cross-protocol.matrix.d.ts +1 -0
  20. package/dist/tests/cursor-sdk.auth-exchange-cache.d.ts +1 -0
  21. package/dist/tests/cursor-sdk.shared.d.ts +1 -0
  22. package/dist/tests/cursor-sdk.tool-id-alias.d.ts +1 -0
  23. package/dist/tests/responses.encrypted-content-cache.d.ts +1 -0
  24. package/dist/tests/session-registry.d.ts +1 -0
  25. package/dist/tests/tool-content.multimodal.d.ts +1 -0
  26. package/dist/tests/wire-keep.d.ts +1 -0
  27. package/dist/transformer/codex.transformer.d.ts +20 -34
  28. package/dist/transformer/openai.responses.transformer.d.ts +1 -1
  29. package/dist/transformer/opencode-headers.transformer.d.ts +1 -2
  30. package/dist/utils/cache-outcome.d.ts +79 -0
  31. package/dist/utils/cache-prefix-debug.d.ts +21 -1
  32. package/dist/utils/cacheControl.d.ts +8 -0
  33. package/dist/utils/deepseek.util.d.ts +1 -1
  34. package/dist/utils/message-debug.d.ts +32 -4
  35. package/dist/utils/nested-agent.d.ts +26 -0
  36. package/dist/utils/openai.responses.util.d.ts +11 -0
  37. package/dist/utils/request-latency.d.ts +4 -0
  38. package/dist/utils/responses.encrypted-content-cache.d.ts +42 -0
  39. package/dist/utils/sse-debug-tap.d.ts +12 -2
  40. package/dist/utils/stream-peek.d.ts +20 -0
  41. package/dist/utils/tool-content.d.ts +54 -0
  42. package/dist/utils/transformer-plan.d.ts +9 -0
  43. package/dist/utils/vertex-claude.util.d.ts +1 -1
  44. package/package.json +5 -5
@@ -1,5 +1,6 @@
1
1
  import type { RouterScenarioType } from "../utils/router";
2
2
  import type { AnthropicClientKind, AnthropicProviderMode } from "../utils/anthropic-client-policy";
3
+ import type { ResponsesCallIdMap } from "../utils/openai.responses.util";
3
4
  /**
4
5
  * Inbound client protocols supported by CCR's gateway lifecycle.
5
6
  */
@@ -35,11 +36,24 @@ export interface ClientProtocolContext {
35
36
  anthropicPolicyApplied?: boolean;
36
37
  anthropicSystemTransformed?: boolean;
37
38
  claudeAuthToolNameMap?: Map<string, string>;
39
+ /** Per-request Responses call/result correlation across both pipeline legs. */
40
+ responsesCallIdMap?: ResponsesCallIdMap;
41
+ responsesCustomToolNames?: Set<string>;
38
42
  /** Claude Code routing metadata extracted without mutating the source billing block. */
39
43
  claudeCodeSubagent?: boolean;
44
+ /**
45
+ * Nested/worker agent on any inbound protocol (Claude Code Task, OpenCode
46
+ * child session, Codex x-openai-subagent, Cursor fork boilerplate).
47
+ */
48
+ nestedAgent?: boolean;
40
49
  taggedSubagentModel?: string;
41
50
  /** Transformer that owns this client protocol */
42
51
  ownerTransformerName: string;
52
+ /**
53
+ * Client conversation id captured from the original wire (never harness
54
+ * version or system text). Used for prompt-cache affinity and Codex headers.
55
+ */
56
+ sessionId?: string;
43
57
  }
44
58
  export interface ProtocolRouteMatch {
45
59
  protocol: ClientProtocol;
package/dist/server.d.ts CHANGED
@@ -59,7 +59,7 @@ export { pluginManager, tokenSpeedPlugin, getTokenSpeedStats, getGlobalTokenSpee
59
59
  export { SSEParserTransform, SSESerializerTransform, rewriteStream } from "./utils/sse";
60
60
  export { isClientAbortError } from "./utils/retry";
61
61
  export { sanitizeHeadersForLog, diffHeadersForLog, sanitizeBodyForLog, DEFAULT_LOG_BODY_MAX_BYTES, } from "./utils/redact";
62
- export { logMessageBody, bodyToLogString, shouldLogRequestBodies, shouldLogSSEEvents, resolveLogBodyMaxBytes, isTruthyConfigFlag, type MessageDebugDirection, } from "./utils/message-debug";
62
+ export { logMessageBody, bodyToLogString, shouldLogSSEEvents, resolveLogBodyMaxBytes, resolveLogBodySelection, isTruthyConfigFlag, type MessageDebugDirection, } from "./utils/message-debug";
63
63
  export { tapUpstreamSSEDebug, tapClientSSEDebug, } from "./utils/sse-debug-tap";
64
64
  export { exchangeAuthorizationCode, fetchUserEmail, resolveProjectId, saveTokens, loadTokens, getValidAccessToken, getValidAccessToken as getAntigravityAccessToken, ANTIGRAVITY_CLIENT_ID, ANTIGRAVITY_CLIENT_SECRET, ANTIGRAVITY_REDIRECT_URI, ANTIGRAVITY_SCOPES, type AntigravityTokens, } from "./utils/antigravity-auth";
65
65
  export { getValidAccessToken as getClaudeAccessToken } from "./utils/claude-auth";
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Persistent registry for every session identity CCR mints.
3
+ *
4
+ * Two families, one mechanism — the stable lookup key already exists in each
5
+ * case (Zen conversation id, cursor buildSessionKey hash); only the minted
6
+ * value used to live in process memory and died on restart:
7
+ *
8
+ * - "zen": conversationKey -> x-opencode-session (ses_…)
9
+ * - "cursor": sessionKey -> { agentId, workspaceDir, model }
10
+ *
11
+ * The file lives under CCR_HOME (the mounted ~/.claude-code-router volume),
12
+ * so bindings survive both restarts and image rebuilds. Plain JSON: values
13
+ * are short strings, not blobs (unlike cursor-opencode-provider's pb.gz,
14
+ * which persists raw Cursor protocol state we never see — the SDK owns that).
15
+ *
16
+ * Synchronous API, tiny file (capped entries), persistence on mint/delete
17
+ * only — never on the hot read path.
18
+ */
19
+ export type PersistedSession = {
20
+ /** The fixed CCR-minted id (ses_… for zen; SDK agentId for cursor). */
21
+ sessionId: string;
22
+ workspaceDir?: string;
23
+ model?: string;
24
+ updatedAt: number;
25
+ };
26
+ export declare const SESSION_REGISTRY_TTL_MS: number;
27
+ /** Fixed id previously minted for this family+key, or undefined. */
28
+ export declare function getPersistedSession(family: string, key: string, now?: number): PersistedSession | undefined;
29
+ /** Record a newly minted fixed id. Overwrites any prior binding. */
30
+ export declare function putPersistedSession(family: string, key: string, value: Omit<PersistedSession, "updatedAt">, now?: number): PersistedSession;
31
+ /** Forget a binding (session retire, Zen bucket re-roll). */
32
+ export declare function deletePersistedSession(family: string, key: string): void;
33
+ /** Drop expired bindings; returns the number pruned. */
34
+ export declare function pruneSessionRegistry(now?: number): number;
35
+ /** Test hook: drop in-memory state (the file itself is keyed by env dir). */
36
+ export declare function resetSessionRegistryForTests(): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,51 +1,37 @@
1
- import { UnifiedChatRequest } from "../types/llm";
2
1
  import { Transformer } from "../types/transformer";
2
+ /**
3
+ * ChatGPT/Codex backend auth + Responses-wire constraints.
4
+ *
5
+ * Body conversion is owned by `openai-responses`. Configure
6
+ * `transformer.use: ["openai-responses", "codex"]`. Same-protocol
7
+ * Responses clients keep `input[]` (including `reasoning.encrypted_content`);
8
+ * this transformer only stamps auth, Codex headers, `store: false`, and
9
+ * `stream: true`.
10
+ */
3
11
  export declare class CodexTransformer implements Transformer {
4
12
  name: string;
13
+ requestPhase: "headers";
5
14
  logger?: any;
6
15
  private streamIntent;
7
- transformRequestIn(request: UnifiedChatRequest, provider: any, context?: any): Promise<Record<string, any>>;
16
+ transformRequestIn(request: any, provider: any, context?: any): Promise<Record<string, any>>;
8
17
  auth(request: any, provider: any): Promise<any>;
9
18
  private resolveAuth;
10
19
  private buildAuthHeaders;
11
20
  private recoverUnauthorizedAuth;
12
21
  private resolvePatAuth;
13
22
  private requestPatAuth;
23
+ /**
24
+ * Transport quirks only. Do not convert Responses → Chat — openai-responses
25
+ * owns that, and same-protocol keep must forward native `input[]` /
26
+ * `encrypted_content` events unchanged.
27
+ */
14
28
  transformResponseOut(response: Response, context?: {
15
29
  req?: {
16
30
  id?: string;
17
31
  };
18
32
  }): Promise<Response>;
19
- private transformResponseOutInner;
20
- private convertStreamEvent;
21
- /**
22
- * Consume a Codex SSE response fully and return a single OpenAI
23
- * ChatCompletion JSON. Used for non-streaming Anthropic SDK calls
24
- * (e.g. client.beta.messages.create with stream:false) where the
25
- * SDK expects a flat BetaMessage. The SDK accumulates the response
26
- * by reading a single JSON object, not by parsing SSE, so we
27
- * have to materialize the response here.
28
- */
29
- /**
30
- * Re-emit a single OpenAI ChatCompletion JSON as a one-shot SSE stream of
31
- * `chat.completion.chunk` events, so a streaming caller (which expects SSE)
32
- * still receives the content when codex returned a flat JSON instead of a
33
- * stream. The downstream anthropic transformer's stream reader parses each
34
- * `data:` line as a chunk, so we split the full message into proper delta
35
- * chunks (text content first, then a final chunk with finish_reason + usage)
36
- * — emitting the raw chat.completion would put the text under `message`
37
- * instead of `delta` and the content would be dropped.
38
- */
39
- private jsonToSseStream;
40
- /**
41
- * Peek the first chunk of the response body to distinguish flat JSON from
42
- * SSE without Response.clone(). JSON drains the body into text; SSE returns
43
- * a new Response whose stream replays the peeked chunk then continues from
44
- * the same reader.
45
- */
46
- private readBodyAndPeek;
47
- private collectSseIntoChatCompletion;
48
- private normalizeRequestContent;
49
- private convertResponseToChat;
50
- private buildImageContent;
33
+ private normalizeCodexTransport;
34
+ private ensureSseContentType;
35
+ private jsonToSseBytes;
36
+ private collectSseIntoResponses;
51
37
  }
@@ -16,7 +16,7 @@ export declare class OpenAIResponsesTransformer implements Transformer {
16
16
  transformResponseIn(response: Response, context?: TransformerContext): Promise<Response>;
17
17
  private convertUnifiedStreamToResponses;
18
18
  transformRequestIn(request: UnifiedChatRequest, provider?: any, context?: any): Promise<UnifiedChatRequest>;
19
- transformResponseOut(response: Response): Promise<Response>;
19
+ transformResponseOut(response: Response, context?: TransformerContext): Promise<Response>;
20
20
  /**
21
21
  * Convert one Responses stream event to a Chat chunk. `choices[0].index` is
22
22
  * always 0 — parallel-call identity lives in `delta.tool_calls[n].index`,
@@ -3,8 +3,6 @@ export declare class OpencodeHeadersTransformer implements Transformer {
3
3
  name: string;
4
4
  ownsTransport: boolean;
5
5
  requestPhase: "transport";
6
- private sessionCache;
7
- private readonly MAX_SESSIONS;
8
6
  private lastTimestamp;
9
7
  private counter;
10
8
  transformRequestIn(request: any, provider: any, context: any): Promise<Record<string, any>>;
@@ -33,6 +31,7 @@ export declare class OpencodeHeadersTransformer implements Transformer {
33
31
  private exponentialRetryDelayMs;
34
32
  private retryAfterHeaders;
35
33
  private ensurePromptCacheKey;
34
+ private ensurePromptCacheRetention;
36
35
  private fingerprintConversation;
37
36
  private getOrCreateSessionId;
38
37
  private invalidateSession;
@@ -0,0 +1,79 @@
1
+ import type { CachePrefixDiff } from "./cache-prefix-debug";
2
+ /**
3
+ * Provider-family-specific cache contracts. OpenAI-style message prefix +
4
+ * prompt_cache_key is only one of several; Cursor uses conversation/lifecycle,
5
+ * Anthropic uses ephemeral breakpoints, Gemini uses cachedContent resources.
6
+ */
7
+ export type CacheFamily = "openai_prefix" | "anthropic_ephemeral" | "gemini_cached_content" | "cursor_conversation" | "deepseek_prefix" | "unknown";
8
+ export type CacheVerdict = "cold" | "warm-start" | "hit" | "partial" | "expected-miss" | "unexpected-miss" | "unknown";
9
+ export type CachePrediction = {
10
+ family: CacheFamily;
11
+ firstTurn: boolean;
12
+ /** Provider-specific: was a hit expected this turn? */
13
+ predictedHit: boolean;
14
+ /** Why we predicted miss/hit (for logs). */
15
+ reason: string;
16
+ /** OpenAI-style prefix intactness when available (diagnostic for Cursor). */
17
+ prefixIntact?: boolean;
18
+ firstDivergencePath?: string;
19
+ approxPrefixTokensLost?: number;
20
+ conversationId?: string;
21
+ conversationIdSource?: string;
22
+ /** Cursor lifecycle action when family is cursor_conversation. */
23
+ lifecycleAction?: string;
24
+ hostPrefixIntact?: boolean;
25
+ };
26
+ export type CursorCacheLifecycle = {
27
+ sessionKey?: string;
28
+ action: string;
29
+ reason?: string;
30
+ };
31
+ export type ResolveCacheFamilyInput = {
32
+ provider?: string;
33
+ model?: string;
34
+ body?: Record<string, any> | null;
35
+ cursorLifecycle?: CursorCacheLifecycle | null;
36
+ };
37
+ /**
38
+ * Prefer explicit provider/transformer id; fall back to body sniffing.
39
+ */
40
+ export declare function resolveCacheFamily(input: ResolveCacheFamilyInput): CacheFamily;
41
+ /**
42
+ * Join what we predicted against what upstream reported.
43
+ * Labels intentionally match the historical sse-debug-tap strings.
44
+ */
45
+ export declare function classifyCacheOutcome(prediction: CachePrediction | null | undefined, hitRatio: number | undefined): CacheVerdict;
46
+ /** OpenAI Chat/Responses, Codex, Zen, Cerebras, OpenRouter, Mistral, xAI. */
47
+ export declare function predictOpenAiPrefix(diff: CachePrefixDiff | null | undefined): CachePrediction;
48
+ /** DeepSeek uses the same outbound prefix contract; hit tokens are separate. */
49
+ export declare function predictDeepSeekPrefix(diff: CachePrefixDiff | null | undefined): CachePrediction;
50
+ /**
51
+ * Anthropic caches only when ephemeral breakpoints exist and the covered
52
+ * prefix stayed intact. No breakpoints → predicted miss.
53
+ */
54
+ export declare function predictAnthropicEphemeral(diff: CachePrefixDiff | null | undefined, body?: Record<string, any> | null): CachePrediction;
55
+ export declare function __resetGeminiCachedContentNamesForTests(): void;
56
+ export declare function predictGeminiCachedContent(opts: {
57
+ diff?: CachePrefixDiff | null;
58
+ body?: Record<string, any> | null;
59
+ conversationId?: string;
60
+ }): CachePrediction;
61
+ /**
62
+ * Cursor has no prompt_cache_key. Prediction follows lifecycle:
63
+ * resume/incremental → hit expected; retire/replay or fresh send → miss expected.
64
+ */
65
+ export declare function predictCursorConversation(opts: {
66
+ lifecycle?: CursorCacheLifecycle | null;
67
+ diff?: CachePrefixDiff | null;
68
+ }): CachePrediction;
69
+ /**
70
+ * Build the right prediction for this outbound leg.
71
+ */
72
+ export declare function buildCachePrediction(opts: {
73
+ provider?: string;
74
+ model?: string;
75
+ body?: Record<string, any> | null;
76
+ diff?: CachePrefixDiff | null;
77
+ cursorLifecycle?: CursorCacheLifecycle | null;
78
+ conversationId?: string;
79
+ }): CachePrediction;
@@ -35,7 +35,9 @@ export type CachePrefixSnapshot = {
35
35
  segments: CachePrefixSegment[];
36
36
  };
37
37
  export type CachePrefixChange = "none" | "appended" | "modified" | "removed";
38
- export type CachePrefixIdSource = "session" | "cache_key" | "fingerprint";
38
+ export type CachePrefixIdSource = "session" | "cache_key" | "fingerprint"
39
+ /** Parent session id mixed with first substantive user text (Claude Code Task). */
40
+ | "subagent";
39
41
  export type CachePrefixDiff = {
40
42
  conversationId: string;
41
43
  conversationIdSource: CachePrefixIdSource;
@@ -91,6 +93,8 @@ export type CachePrefixDiffOptions = {
91
93
  * become the baseline the following turn is judged against.
92
94
  */
93
95
  commit?: boolean;
96
+ /** Override when `conversationId` is a derived subagent key rather than the raw session. */
97
+ conversationIdSource?: CachePrefixIdSource;
94
98
  };
95
99
  export declare function __resetCachePrefixSnapshotsForTests(): void;
96
100
  /**
@@ -102,6 +106,22 @@ export declare function diffCachePrefixSnapshots(conversationId: string, previou
102
106
  stage?: CachePrefixStage;
103
107
  conversationIdSource?: CachePrefixIdSource;
104
108
  }): CachePrefixDiff;
109
+ /**
110
+ * Snapshot key for consecutive cache-prefix diffs.
111
+ *
112
+ * Claude Code Tasks share the parent `session_id`. Mixing first substantive
113
+ * user text keeps parent vs fork (and two forks) from overwriting one baseline
114
+ * and reporting 25k-token "modified" misses.
115
+ */
116
+ export declare function resolveCachePrefixConversationId(opts: {
117
+ sessionId?: string;
118
+ isSubagent?: boolean;
119
+ nestedAgent?: boolean;
120
+ firstUserText?: string;
121
+ }): {
122
+ id?: string;
123
+ source?: CachePrefixIdSource;
124
+ };
105
125
  /**
106
126
  * Compare this outbound body to the last one for the conversation, then
107
127
  * remember the current snapshot. Returns null when there is nothing cacheable.
@@ -28,6 +28,14 @@ export declare function stripMessagesCacheControl(messages: UnifiedMessage[]): U
28
28
  * but non-Anthropic providers reject it on tool definitions.
29
29
  */
30
30
  export declare function stripToolsCacheControl(tools: UnifiedTool[] | undefined): UnifiedTool[] | undefined;
31
+ /**
32
+ * Conversation id from the client wire. Never uses harness version, billing
33
+ * markers, or system-prompt text.
34
+ */
35
+ export declare function extractClientSessionId(input: {
36
+ body?: any;
37
+ headers?: unknown;
38
+ }): string | undefined;
31
39
  export declare function deriveCacheSessionKey(context: any, request: UnifiedChatRequest): string | undefined;
32
40
  export declare function selectCacheBreakpoints(request: UnifiedChatRequest, options: {
33
41
  maxBreakpoints: number;
@@ -11,7 +11,7 @@ type AssistantResponseRecorder = {
11
11
  };
12
12
  export declare function assistantNeedsReasoningForToolContext(message: MessageLike, priorMessages: MessageLike[]): boolean;
13
13
  export declare function isDeepSeekThinkingRequest(request: Pick<UnifiedChatRequest, "model" | "thinking" | "enable_thinking" | "reasoning">, provider?: Pick<LLMProvider, "name" | "baseUrl">): boolean;
14
- export declare function buildReasoningCacheNamespace(request: Pick<UnifiedChatRequest, "model" | "thinking" | "enable_thinking" | "reasoning">, provider?: Pick<LLMProvider, "name" | "baseUrl">): string;
14
+ export declare function buildReasoningCacheNamespace(request: Pick<UnifiedChatRequest, "model" | "thinking" | "enable_thinking" | "reasoning">, provider?: Pick<LLMProvider, "name" | "baseUrl">, context?: TransformerContext): string;
15
15
  export declare function prepareReasoningReplay(request: UnifiedChatRequest, provider: Pick<LLMProvider, "name" | "baseUrl"> | undefined, context?: TransformerContext): {
16
16
  restoredFromCache: number;
17
17
  restoredFromThinking: number;
@@ -4,9 +4,18 @@ export declare function isTruthyConfigFlag(value: unknown): boolean;
4
4
  export declare function resolveLogBodyMaxBytes(configService: {
5
5
  get?: (key: string) => unknown;
6
6
  } | null | undefined): number;
7
- export declare function shouldLogRequestBodies(configService: {
7
+ /**
8
+ * Body capture selector (`LOG_REQUEST_BODY_PARTS`): comma-separated top-level
9
+ * body fields (`system,tools`), or `full` for the whole body. Unset/empty
10
+ * disables capture. Unknown names are kept and reported as not-found at write
11
+ * time so typos stay visible.
12
+ */
13
+ export type LogBodySelection = "full" | string[];
14
+ export declare function resolveLogBodySelection(configService: {
8
15
  get?: (key: string) => unknown;
9
- } | null | undefined): boolean;
16
+ } | null | undefined): LogBodySelection | undefined;
17
+ /** Capture files live next to the rotated server logs. */
18
+ export declare function defaultLogBodyDir(): string;
10
19
  export declare function shouldLogSSEEvents(configService: {
11
20
  get?: (key: string) => unknown;
12
21
  } | null | undefined): boolean;
@@ -27,9 +36,28 @@ export type MessageBodyLogOptions = {
27
36
  maxBytes?: number;
28
37
  /** Override the historical `type` field when needed. */
29
38
  type?: string;
39
+ /**
40
+ * Resolved `LOG_REQUEST_BODY_PARTS` (see resolveLogBodySelection).
41
+ * `"full"` stores the whole body; a list stores one file per named
42
+ * top-level field. The log carries one manifest line per part (with sha256)
43
+ * instead of the body. Undefined keeps the legacy inline whole-body dump.
44
+ */
45
+ selection?: LogBodySelection | undefined;
46
+ /** Code-level directory override (tests); defaults to the logs folder. */
47
+ bodiesDir?: string;
30
48
  };
31
49
  /**
32
- * Opt-in full message-body capture with a stable direction tag so operators
33
- * can grep client↔CCR and CCR↔provider legs independently.
50
+ * Compact keep-wire snapshot for debug logs. Full transcripts stay behind
51
+ * LOG_REQUEST_BODY; this is how keep (Responses/Chat) shows encrypted
52
+ * replay on `ccr→provider` without a 200k-token dump.
34
53
  */
54
+ export declare function summarizeKeepWire(body: unknown): Record<string, unknown>;
55
+ export declare function logKeepWire(body: unknown, opts: {
56
+ logger?: {
57
+ debug?: (...args: any[]) => void;
58
+ };
59
+ reqId?: string | number;
60
+ provider?: string;
61
+ model?: string;
62
+ }): void;
35
63
  export declare function logMessageBody(body: unknown, opts: MessageBodyLogOptions): void;
@@ -0,0 +1,26 @@
1
+ /** First user-turn text only — never system (billing / harness version). */
2
+ export declare function firstUserText(request: unknown): string;
3
+ export declare function isHarnessUserNoise(text: string): boolean;
4
+ export declare function userMessageTextParts(content: unknown): string[];
5
+ /**
6
+ * First user text that distinguishes a worker transcript.
7
+ * Shared reminder/caveat preambles are skipped so parallel Tasks do not collide.
8
+ */
9
+ export declare function firstSubstantiveUserText(request: unknown): string;
10
+ /** Statusline / spinner polls — must not supersede or become a cache baseline. */
11
+ export declare function isStatuslinePollTurn(request: unknown): boolean;
12
+ /**
13
+ * Nested/worker agent on any inbound protocol.
14
+ *
15
+ * Claude Code: `cc_is_subagent`.
16
+ * OpenCode / Kilocode / MiMo: child session + `x-parent-session-id`.
17
+ * Kilocode gateway: `X-KILOCODE-PARENT-TASKID`.
18
+ * Codex: `x-openai-subagent`.
19
+ * Cursor/Claude forks: `<fork-boilerplate>` / worker-fork opening text.
20
+ * Grok CLI: child `x-grok-session-id` (unique) plus opening-text mix.
21
+ */
22
+ export declare function detectNestedAgent(input: {
23
+ headers?: unknown;
24
+ body?: unknown;
25
+ claudeCodeSubagent?: boolean;
26
+ }): boolean;
@@ -7,6 +7,17 @@ export interface ResponsesCallIdMap {
7
7
  export declare function createCallIdMap(): ResponsesCallIdMap;
8
8
  /** Sanitize and remember a stable per-turn mapping for function call correlation. */
9
9
  export declare function mapCallId(map: ResponsesCallIdMap, id: unknown, direction?: "client_to_unified" | "unified_to_client"): string | undefined;
10
+ /**
11
+ * Enforce the Responses call_id contract without rebuilding an exact-wire body.
12
+ *
13
+ * Same-protocol wire keep deliberately skips the Responses owner's full
14
+ * transformRequestIn so images/files/reasoning/cache fields remain byte-faithful.
15
+ * Call ids are still a provider validation boundary, though: Cursor-style
16
+ * composite ids can exceed 64 characters. Rewrite only the identity field on
17
+ * call/output items and reuse the normalization map so paired items and hash
18
+ * collisions resolve identically in both directions.
19
+ */
20
+ export declare function sanitizeResponsesWireCallIds(body: any, callIdMap?: ResponsesCallIdMap): any;
10
21
  /**
11
22
  * Client Responses wire → Unified (Chat Completions shape).
12
23
  * Supports the Responses MVP subset; rejects CCR-unsupported stateful fields.
@@ -10,9 +10,13 @@ export type RequestLatency = {
10
10
  protocol?: string;
11
11
  provider?: string;
12
12
  model?: string;
13
+ method?: string;
14
+ url?: string;
13
15
  scenario?: string;
14
16
  bypass?: boolean;
17
+ wireKeep?: boolean;
15
18
  tokenCount?: number;
19
+ tokenCountSource?: "exact" | "estimate" | "skipped";
16
20
  inputBytes?: number;
17
21
  upstreamAttempts?: number;
18
22
  cancelled?: boolean;
@@ -0,0 +1,42 @@
1
+ import { LLMProvider, UnifiedChatRequest, UnifiedMessage } from "../types/llm";
2
+ import { TransformerContext } from "../types/transformer";
3
+ type MessageLike = Pick<UnifiedMessage, "role" | "content" | "thinking" | "tool_calls" | "tool_call_id" | "reasoning_content"> & {
4
+ name?: string;
5
+ };
6
+ export type EncryptedReasoningPayload = {
7
+ encrypted_content: string;
8
+ content?: string;
9
+ id?: string;
10
+ };
11
+ export type EncryptedReasoningStreamRecorder = {
12
+ observe(event: any): void;
13
+ completedOutput(): any[] | undefined;
14
+ discard(): void;
15
+ };
16
+ /**
17
+ * Accumulate only the opaque reasoning/tool state needed for replay. Some
18
+ * Responses-compatible hosts omit full output from response.completed, so the
19
+ * cache cannot depend on that one event. Bounds make malformed streams fail
20
+ * closed (no cache write) without affecting client-visible stream conversion.
21
+ */
22
+ export declare function createEncryptedReasoningStreamRecorder(): EncryptedReasoningStreamRecorder;
23
+ export declare function isCrossProtocolResponsesClient(context?: TransformerContext): boolean;
24
+ export declare function buildEncryptedReasoningCacheNamespace(request: Pick<UnifiedChatRequest, "model" | "thinking" | "reasoning">, provider?: Pick<LLMProvider, "name" | "baseUrl">, context?: TransformerContext): string;
25
+ /**
26
+ * Anthropic/Chat clients cannot round-trip Responses `encrypted_content`.
27
+ * For those inbound protocols, request ciphertext from the destination and
28
+ * restore it onto assistant tool turns from a local cache keyed like DeepSeek's
29
+ * reasoning replay cache.
30
+ */
31
+ export declare function prepareEncryptedReasoningReplay(request: UnifiedChatRequest, provider: Pick<LLMProvider, "name" | "baseUrl"> | undefined, context?: TransformerContext): {
32
+ restoredFromCache: number;
33
+ includeRequested: boolean;
34
+ };
35
+ export declare function hasEncryptedReasoningContext(context?: TransformerContext): boolean;
36
+ export declare function recordEncryptedReasoningResponseMessage(message: MessageLike | null | undefined, context?: TransformerContext): number;
37
+ /**
38
+ * Build a cacheable assistant message from a Responses `output` array.
39
+ * Ciphertext often arrives only on the terminal reasoning item.
40
+ */
41
+ export declare function assistantMessageFromResponsesOutput(output: any[] | undefined): MessageLike | null;
42
+ export {};
@@ -1,4 +1,5 @@
1
- import { type CacheAffinityHeaders, type CachePrefixDiff, type CachePrefixStage } from "./cache-prefix-debug";
1
+ import { type CacheAffinityHeaders, type CachePrefixDiff, type CachePrefixIdSource, type CachePrefixStage } from "./cache-prefix-debug";
2
+ import { type CachePrediction, type CursorCacheLifecycle } from "./cache-outcome";
2
3
  import type { MessageDebugDirection } from "./message-debug";
3
4
  export type UpstreamSSEDebugOptions = {
4
5
  logger?: any;
@@ -8,6 +9,9 @@ export type UpstreamSSEDebugOptions = {
8
9
  model?: string;
9
10
  /** Conversation / Claude session id used to pair consecutive cache snapshots. */
10
11
  conversationId?: string;
12
+ conversationIdSource?: CachePrefixIdSource;
13
+ /** When false, diff against the last baseline but do not replace it. */
14
+ commitCachePrefix?: boolean;
11
15
  /** Pipeline position this body was captured at. Defaults to `wire`. */
12
16
  stage?: CachePrefixStage;
13
17
  /** Codex (and similar) routing headers that pin prompt-cache affinity. */
@@ -18,6 +22,12 @@ export type UpstreamSSEDebugOptions = {
18
22
  clientStageDiff?: CachePrefixDiff | null;
19
23
  /** Outbound diff for this request, joined with the observed cache usage. */
20
24
  cacheDiff?: CachePrefixDiff | null;
25
+ /** Outbound body used to resolve Anthropic/Gemini family signals. */
26
+ outboundBody?: Record<string, any> | null;
27
+ /** Precomputed prediction; built from cacheDiff + family when absent. */
28
+ cachePrediction?: CachePrediction | null;
29
+ /** Cursor lifecycle plan for conversation-cache prediction. */
30
+ cursorLifecycle?: CursorCacheLifecycle | null;
21
31
  /** Cap for a single logged payload string (raw `data` field). */
22
32
  maxBytes?: number;
23
33
  /**
@@ -50,7 +60,7 @@ export type ClientSSEDebugOptions = {
50
60
  * Byte-preserving upstream response debug tap.
51
61
  *
52
62
  * For SSE: mirrors bytes to a background consumer that emits Codex-parity
53
- * `recieved data` / `Original Response` logs (including Anthropic usage /
63
+ * `received data` / `Original Response` logs (including Anthropic usage /
54
64
  * cache fields on message_start / message_delta).
55
65
  *
56
66
  * Important: do **not** use `ReadableStream.tee()` here. Tee couples
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sniff a response body as JSON vs SSE without Response.clone().
3
+ *
4
+ * Cloudflare (Codex) often strips Content-Type from SSE. The first
5
+ * non-whitespace byte is `{`/`[` for a JSON object/array and anything
6
+ * else (typically `d` from `data:`) for SSE. JSON drains into text;
7
+ * SSE returns a new Response that replays the peeked chunk then
8
+ * continues from the same reader.
9
+ */
10
+ export type PeekedResponseBody = {
11
+ kind: "json";
12
+ firstChar: string;
13
+ text: string;
14
+ } | {
15
+ kind: "sse";
16
+ response: Response;
17
+ } | {
18
+ kind: "empty";
19
+ };
20
+ export declare function peekResponseBody(response: Response): Promise<PeekedResponseBody>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Multimodal tool-result helpers.
3
+ *
4
+ * Responses / OpenCode attach images (and files) on function_call_output as
5
+ * structured parts. Unified carries them as text / image_url / file parts on
6
+ * role:"tool". Destinations differ:
7
+ * - Responses/Codex: re-emit input_image / input_file in output[]
8
+ * - Anthropic: image / document blocks inside tool_result.content
9
+ * - Gemini: text in functionResponse + sibling inlineData parts
10
+ * - Chat Completions / Mistral: string tool content only — extract media
11
+ * into a follow-up user message (OpenCode's pattern for non-supporting APIs)
12
+ */
13
+ export type UnifiedToolPart = {
14
+ type: "text";
15
+ text: string;
16
+ cache_control?: any;
17
+ } | {
18
+ type: "image_url";
19
+ image_url: {
20
+ url: string;
21
+ detail?: string;
22
+ };
23
+ media_type?: string;
24
+ cache_control?: any;
25
+ } | {
26
+ type: "file";
27
+ filename?: string;
28
+ file_data?: string;
29
+ file_url?: string;
30
+ media_type?: string;
31
+ cache_control?: any;
32
+ };
33
+ export declare function isUnifiedToolMediaPart(part: any): part is Extract<UnifiedToolPart, {
34
+ type: "image_url" | "file";
35
+ }>;
36
+ /** Normalize string | part[] tool content into a part list. */
37
+ export declare function normalizeUnifiedToolParts(content: unknown): UnifiedToolPart[];
38
+ export declare function unifiedToolTextOnly(content: unknown): string;
39
+ export declare function unifiedToolHasMedia(content: unknown): boolean;
40
+ /** Anthropic tool_result.content: string or (text|image|document)[]. */
41
+ export declare function unifiedToolContentToAnthropic(content: unknown): string | any[];
42
+ /**
43
+ * Anthropic inbound tool_result.content → Unified tool content
44
+ * (string or text/image_url/file parts).
45
+ */
46
+ export declare function anthropicToolResultToUnified(content: unknown): string | any[];
47
+ /** Gemini sibling inlineData / fileData parts for tool media. */
48
+ export declare function unifiedToolMediaToGeminiParts(content: unknown): any[];
49
+ /**
50
+ * Chat Completions / Mistral: string-only tool content. Pull media out of
51
+ * tool messages and insert a synthetic user message after each contiguous
52
+ * tool-result group so vision still reaches the model.
53
+ */
54
+ export declare function extractToolMediaForStringToolApis(messages: any[]): any[];
@@ -17,5 +17,14 @@ export declare function compileTransformerPlan(providerUse: Transformer[] | unde
17
17
  skipName?: string;
18
18
  }): CompiledTransformerPlan;
19
19
  export declare function isExactProtocolResponsePlan(plan: CompiledTransformerPlan, endpointTransformer: Transformer, clientProtocolOwnerName: string | undefined): boolean;
20
+ export declare function isExactProtocolRequestPlan(plan: CompiledTransformerPlan, endpointTransformer: Transformer, clientProtocolOwnerName: string | undefined): boolean;
21
+ /**
22
+ * v1 allowlist: middleware known to be safe on a kept native wire.
23
+ * `OpenAI` is handled separately — it always runs. `reasoning` is
24
+ * Chat-shaped and only allowed with an OpenAI owner. Everything else
25
+ * (Anthropic/Responses owners) does not run Unified-only middleware.
26
+ */
27
+ export declare function isWireSafeMiddlewareForKeep(name: string | undefined, ownerName: string | undefined): boolean;
28
+ export declare function planContains(plan: CompiledTransformerPlan, name: string): boolean;
20
29
  /** Cancel a Response body when a newer transport result replaces it. */
21
30
  export declare function cancelReplacedProviderResponse(previous: Response | undefined | null, next: Response | undefined | null): void;