@caeliq/llms 1.0.66 → 1.0.67

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 (36) hide show
  1. package/README.md +2 -1
  2. package/dist/cjs/server.cjs +231 -221
  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/lifecycle-planner.d.ts +16 -4
  6. package/dist/cursor-sdk/prompt.d.ts +10 -0
  7. package/dist/cursor-sdk/session.d.ts +23 -0
  8. package/dist/cursor-sdk/turn-output.d.ts +3 -0
  9. package/dist/cursor-sdk/usage.d.ts +17 -0
  10. package/dist/esm/server.mjs +235 -225
  11. package/dist/esm/server.mjs.map +4 -4
  12. package/dist/routing/protocol-endpoints.d.ts +14 -0
  13. package/dist/tests/anthropic.message-start-usage.test.d.ts +1 -0
  14. package/dist/tests/cache-outcome.d.ts +1 -0
  15. package/dist/tests/cross-protocol.matrix.d.ts +1 -0
  16. package/dist/tests/cursor-sdk.auth-exchange-cache.d.ts +1 -0
  17. package/dist/tests/responses.encrypted-content-cache.d.ts +1 -0
  18. package/dist/tests/tool-content.multimodal.d.ts +1 -0
  19. package/dist/tests/wire-keep.d.ts +1 -0
  20. package/dist/transformer/codex.transformer.d.ts +20 -34
  21. package/dist/transformer/openai.responses.transformer.d.ts +1 -1
  22. package/dist/utils/cache-outcome.d.ts +79 -0
  23. package/dist/utils/cache-prefix-debug.d.ts +21 -1
  24. package/dist/utils/cacheControl.d.ts +8 -0
  25. package/dist/utils/deepseek.util.d.ts +1 -1
  26. package/dist/utils/message-debug.d.ts +12 -2
  27. package/dist/utils/nested-agent.d.ts +26 -0
  28. package/dist/utils/openai.responses.util.d.ts +11 -0
  29. package/dist/utils/request-latency.d.ts +4 -0
  30. package/dist/utils/responses.encrypted-content-cache.d.ts +42 -0
  31. package/dist/utils/sse-debug-tap.d.ts +12 -2
  32. package/dist/utils/stream-peek.d.ts +20 -0
  33. package/dist/utils/tool-content.d.ts +54 -0
  34. package/dist/utils/transformer-plan.d.ts +9 -0
  35. package/dist/utils/vertex-claude.util.d.ts +1 -1
  36. package/package.json +2 -2
@@ -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;
@@ -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`,
@@ -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;
@@ -29,7 +29,17 @@ export type MessageBodyLogOptions = {
29
29
  type?: string;
30
30
  };
31
31
  /**
32
- * Opt-in full message-body capture with a stable direction tag so operators
33
- * can grep client↔CCR and CCR↔provider legs independently.
32
+ * Compact keep-wire snapshot for debug logs. Full transcripts stay behind
33
+ * LOG_REQUEST_BODY; this is how keep (Responses/Chat) shows encrypted
34
+ * replay on `ccr→provider` without a 200k-token dump.
34
35
  */
36
+ export declare function summarizeKeepWire(body: unknown): Record<string, unknown>;
37
+ export declare function logKeepWire(body: unknown, opts: {
38
+ logger?: {
39
+ debug?: (...args: any[]) => void;
40
+ };
41
+ reqId?: string | number;
42
+ provider?: string;
43
+ model?: string;
44
+ }): void;
35
45
  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;
@@ -2,7 +2,7 @@ import { UnifiedChatRequest } from "../types/llm";
2
2
  interface ClaudeMessage {
3
3
  role: "user" | "assistant";
4
4
  content: Array<{
5
- type: "text" | "image" | "tool_use" | "tool_result";
5
+ type: "text" | "image" | "document" | "tool_use" | "tool_result";
6
6
  text?: string;
7
7
  source?: {
8
8
  type: "base64";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caeliq/llms",
3
- "version": "1.0.66",
3
+ "version": "1.0.67",
4
4
  "description": "A universal LLM API transformation server",
5
5
  "main": "dist/cjs/server.cjs",
6
6
  "module": "dist/esm/server.mjs",
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@anthropic-ai/sdk": "^0.120.0",
33
- "@caeliq/ccr-shared": "^2.1.8",
33
+ "@caeliq/ccr-shared": "^2.1.9",
34
34
  "@cursor/sdk": "^1.0.30",
35
35
  "@fastify/cors": "^11.3.0",
36
36
  "@fastify/rate-limit": "^11.2.0",