@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.
- package/README.md +2 -1
- package/dist/cjs/server.cjs +231 -221
- package/dist/cjs/server.cjs.map +4 -4
- package/dist/cursor-sdk/auth-exchange-cache.d.ts +23 -0
- package/dist/cursor-sdk/lifecycle-planner.d.ts +16 -4
- package/dist/cursor-sdk/prompt.d.ts +10 -0
- package/dist/cursor-sdk/session.d.ts +23 -0
- package/dist/cursor-sdk/turn-output.d.ts +3 -0
- package/dist/cursor-sdk/usage.d.ts +17 -0
- package/dist/esm/server.mjs +235 -225
- package/dist/esm/server.mjs.map +4 -4
- package/dist/routing/protocol-endpoints.d.ts +14 -0
- package/dist/tests/anthropic.message-start-usage.test.d.ts +1 -0
- package/dist/tests/cache-outcome.d.ts +1 -0
- package/dist/tests/cross-protocol.matrix.d.ts +1 -0
- package/dist/tests/cursor-sdk.auth-exchange-cache.d.ts +1 -0
- package/dist/tests/responses.encrypted-content-cache.d.ts +1 -0
- package/dist/tests/tool-content.multimodal.d.ts +1 -0
- package/dist/tests/wire-keep.d.ts +1 -0
- package/dist/transformer/codex.transformer.d.ts +20 -34
- package/dist/transformer/openai.responses.transformer.d.ts +1 -1
- package/dist/utils/cache-outcome.d.ts +79 -0
- package/dist/utils/cache-prefix-debug.d.ts +21 -1
- package/dist/utils/cacheControl.d.ts +8 -0
- package/dist/utils/deepseek.util.d.ts +1 -1
- package/dist/utils/message-debug.d.ts +12 -2
- package/dist/utils/nested-agent.d.ts +26 -0
- package/dist/utils/openai.responses.util.d.ts +11 -0
- package/dist/utils/request-latency.d.ts +4 -0
- package/dist/utils/responses.encrypted-content-cache.d.ts +42 -0
- package/dist/utils/sse-debug-tap.d.ts +12 -2
- package/dist/utils/stream-peek.d.ts +20 -0
- package/dist/utils/tool-content.d.ts +54 -0
- package/dist/utils/transformer-plan.d.ts +9 -0
- package/dist/utils/vertex-claude.util.d.ts +1 -1
- 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:
|
|
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
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
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"
|
|
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
|
-
*
|
|
33
|
-
*
|
|
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
|
-
* `
|
|
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.
|
|
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.
|
|
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",
|