@caeliq/llms 1.0.59 → 1.0.61

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 (41) hide show
  1. package/README.md +41 -3
  2. package/dist/cjs/server.cjs +259 -224
  3. package/dist/cjs/server.cjs.map +4 -4
  4. package/dist/esm/server.mjs +260 -225
  5. package/dist/esm/server.mjs.map +4 -4
  6. package/dist/routing/inbound-pipeline.d.ts +8 -5
  7. package/dist/routing/protocol-adapter.d.ts +3 -4
  8. package/dist/routing/protocol-endpoints.d.ts +14 -2
  9. package/dist/server.d.ts +2 -0
  10. package/dist/tests/anthropic.client-policy.d.ts +1 -0
  11. package/dist/tests/assistant-turn-order.d.ts +1 -0
  12. package/dist/tests/cache-prefix-debug.d.ts +1 -0
  13. package/dist/tests/cross-protocol.cache-prefix.d.ts +1 -0
  14. package/dist/tests/cross-protocol.config-matrix.d.ts +1 -0
  15. package/dist/tests/cross-protocol.responses-grok.d.ts +1 -0
  16. package/dist/tests/mistral.thinking-history.d.ts +1 -0
  17. package/dist/tests/reasoning.effort-levels.d.ts +1 -0
  18. package/dist/tests/responses.reasoning-duplication.d.ts +1 -0
  19. package/dist/tests/sse-debug-tap.d.ts +1 -0
  20. package/dist/tests/sse.client-keepalive.d.ts +1 -0
  21. package/dist/tests/xai-auth.reliability.d.ts +1 -0
  22. package/dist/transformer/claude-auth.transformer.d.ts +26 -3
  23. package/dist/transformer/index.d.ts +2 -0
  24. package/dist/transformer/openai.responses.transformer.d.ts +1 -0
  25. package/dist/transformer/xai-auth.transformer.d.ts +24 -0
  26. package/dist/types/llm.d.ts +9 -1
  27. package/dist/utils/anthropic-client-policy.d.ts +45 -0
  28. package/dist/utils/anthropic-url.d.ts +1 -0
  29. package/dist/utils/cache-prefix-debug.d.ts +120 -0
  30. package/dist/utils/claude-billing.d.ts +12 -6
  31. package/dist/utils/claude-model-catalog.d.ts +2 -1
  32. package/dist/utils/gemini-thinking.d.ts +2 -1
  33. package/dist/utils/health-reporter.d.ts +13 -0
  34. package/dist/utils/openai.responses.util.d.ts +157 -1
  35. package/dist/utils/reasoning-effort.d.ts +13 -0
  36. package/dist/utils/redact.d.ts +1 -0
  37. package/dist/utils/sse/client-keepalive.d.ts +19 -0
  38. package/dist/utils/sse/index.d.ts +1 -0
  39. package/dist/utils/sse-debug-tap.d.ts +62 -0
  40. package/dist/utils/xai-auth.d.ts +37 -0
  41. package/package.json +12 -12
@@ -11,19 +11,167 @@ export declare function mapCallId(map: ResponsesCallIdMap, id: unknown, directio
11
11
  * Client Responses wire → Unified (Chat Completions shape).
12
12
  * Supports the Responses MVP subset; rejects CCR-unsupported stateful fields.
13
13
  */
14
- export declare function responsesRequestToUnified(body: any, callIdMap?: ResponsesCallIdMap): UnifiedChatRequest;
14
+ export declare function responsesRequestToUnified(body: any, callIdMap?: ResponsesCallIdMap, customToolNames?: Set<string>): UnifiedChatRequest;
15
+ export declare function isResponsesReasoningItemId(value: unknown): value is string;
16
+ /**
17
+ * Ciphertext Codex/OpenAI will verify. Item ids and Anthropic/Gemini
18
+ * signatures are not encrypted_content — replaying them 400s with
19
+ * `invalid_encrypted_content`.
20
+ */
21
+ export declare function responsesEncryptedContentFrom(value: unknown): string | undefined;
22
+ export interface UnifiedAssistantThinking {
23
+ content: string;
24
+ signature?: string;
25
+ encrypted_content?: string;
26
+ id?: string;
27
+ }
28
+ /** Anthropic/Gemini thinking.signature — never a Responses item id. */
29
+ export declare function anthropicThinkingSignatureFrom(thinking: {
30
+ signature?: string;
31
+ } | undefined): string | undefined;
32
+ /** Responses `reasoning` item → Unified assistant.thinking. */
33
+ export declare function thinkingFromResponsesReasoningItem(item: any): UnifiedAssistantThinking | undefined;
34
+ /**
35
+ * Record streamed reasoning summary text for one item. Late handlers
36
+ * (`output_item.done`, `response.completed`) consult this map so they can
37
+ * emit ciphertext / id without re-sending content — Unified
38
+ * `delta.thinking.content` is additive in every downstream consumer.
39
+ */
40
+ export declare function recordReasoningSummaryDelta(deliveredContentByItemId: Map<string, string>, itemId: unknown, delta: unknown): void;
41
+ /**
42
+ * Late Responses reasoning handlers exist to rescue `encrypted_content` /
43
+ * item id after summary deltas have already streamed the text. If this
44
+ * item's content was already delivered on the current stream, return
45
+ * replay metadata only. A terminal-only reasoning item (no summary
46
+ * deltas) still delivers content exactly once.
47
+ */
48
+ export declare function thinkingForLateReasoningItem(item: any, deliveredContentByItemId: Map<string, string>): UnifiedAssistantThinking | undefined;
49
+ /**
50
+ * Inverse of the inbound `text.format` → Chat Completions `response_format`
51
+ * mapping in `responsesRequestToUnified`. Shared by Codex and generic
52
+ * Responses outbound so the two reconstruct sites cannot drift.
53
+ */
54
+ export declare function responsesTextFormatFromResponseFormat(responseFormat: any): {
55
+ type: string;
56
+ name?: string;
57
+ schema?: any;
58
+ strict?: boolean;
59
+ } | undefined;
60
+ /** Unified assistant.thinking → Responses `reasoning` input/output item. */
61
+ export declare function thinkingFromUnifiedAssistant(message: any): UnifiedAssistantThinking | undefined;
62
+ /**
63
+ * Fixed assistant-turn order for every protocol:
64
+ * thinking → text → images → tool calls.
65
+ * Reordering here keeps cache prefixes stable across Anthropic, Chat,
66
+ * Responses, Gemini, and Mistral.
67
+ */
68
+ export interface CanonicalAssistantTurn {
69
+ thinking?: UnifiedAssistantThinking;
70
+ texts: Array<{
71
+ text: string;
72
+ cache_control?: any;
73
+ }>;
74
+ images: any[];
75
+ toolCalls: any[];
76
+ }
77
+ export declare function canonicalAssistantTurn(message: any): CanonicalAssistantTurn;
78
+ export declare function assistantTurnHasText(turn: CanonicalAssistantTurn): boolean;
79
+ export declare function responsesReasoningItemFromThinking(thinking: {
80
+ content?: string;
81
+ signature?: string;
82
+ encrypted_content?: string;
83
+ id?: string;
84
+ } | undefined, id?: string): any | null;
85
+ /** Synthetic argument key used to carry a `custom` tool's freeform text
86
+ * through the Unified/Chat Completions function-call shape. */
87
+ export declare const CUSTOM_TOOL_INPUT_KEY = "input";
88
+ /**
89
+ * Codex's apply_patch/apply_update grammar accepts only the exact markers
90
+ * `*** Begin Patch` and `*** End Patch` (no trailing asterisk triplet) as the
91
+ * first/last patch lines — anything else is rejected with "The first line of
92
+ * the patch must be '*** Begin Patch'". Models not trained on that grammar
93
+ * (e.g. Grok reaches for Claude's trailing-asterisk variant) emit
94
+ * `*** Begin Patch ***` / `*** End Patch ***`. Normalize those to the Codex
95
+ * form on the client-facing emission, scoped by the marker token itself so a
96
+ * rewrite can only ever hit an apply_patch-style payload regardless of the
97
+ * declared tool name.
98
+ */
99
+ export declare function normalizeCodexPatchMarkers(text: string): string;
100
+ /**
101
+ * Codex's `exec` tool runs raw JavaScript in a V8 isolate; a model that was
102
+ * never trained on that convention (Grok) sometimes fills the freeform input
103
+ * with a JSON shell-envelope instead — e.g. `{"cmd": "ls -la /tmp"}`. That is
104
+ * not valid JS (`{"cmd":…}` parses as a block with a string label →
105
+ * `SyntaxError: Unexpected token ':'`), so the shell call fails and the model
106
+ * wastes a turn retrying as `await tools.exec_command({cmd: …})`.
107
+ * Rewrite the envelope into exactly that retry shape on the client-facing
108
+ * emission. Scoped by the tool name and the whole-input-is-a-shell-object
109
+ * shape so a freeform tool that legitimately takes a JSON blob is never
110
+ * touched. Grok also alternates the key between `cmd` and `command`, and
111
+ * `exec_command` only accepts `cmd`, so normalize either to `cmd`.
112
+ */
113
+ export declare function normalizeExecCommandEnvelope(name: string | undefined, text: string): string;
114
+ /**
115
+ * A model that reaches for the patch tool through a shell hereditary habit
116
+ * wraps the patch in a heredoc instead of JS:
117
+ * apply_patch << 'PATCH'\n*** Begin Patch … *** End Patch\nPATCH
118
+ * That is a shell command, not the raw JS `exec` runs, so it fails with a
119
+ * syntax error and the model wastes a turn retrying as
120
+ * `await tools.apply_patch(…)`. Normalize the heredoc invocation into the JS
121
+ * call form. Whole-value heredoc match only; anything else passes through.
122
+ */
123
+ export declare function normalizeExecApplyPatchHeredoc(name: string | undefined, text: string): string;
124
+ /**
125
+ * `exec` may cross the wire as a plain function tool (arguments stay the JSON
126
+ * wrapper `{"input":"…"}`) rather than a custom tool (whose freeform input was
127
+ * unwrapped already). The envelope/heredoc normalizers above operate on the
128
+ * unwrapped inner text, so when only the wrapper is available, descend into it
129
+ * and normalize its inner `input` value with the same rules.
130
+ */
131
+ export declare function normalizeExecFunctionArguments(name: string | undefined, rawArguments: string, options?: CodexIsolateConventionsOptions): string;
132
+ /** Undo the CUSTOM_TOOL_INPUT_KEY wrapping applied in normalizeResponsesTools.
133
+ * Falls back to the raw text so a malformed/empty call still round-trips
134
+ * instead of vanishing. Also strips a whole-value shell heredoc wrapper
135
+ * (see stripHeredocWrapper). That strip is intentional on both paths this
136
+ * helper serves: client emission (so the Responses client sees clean
137
+ * freeform text) and CodexTransformer history replay (so the backend is
138
+ * not re-fed a wrapper the client never kept). Replay is therefore
139
+ * normalized, not byte-faithful to the model's original heredoc. */
140
+ export declare function unwrapCustomToolInput(rawArguments: string): string;
141
+ /**
142
+ * Codex V8 isolate calling conventions (`await tools.exec_command` /
143
+ * `await tools.apply_patch`). Default on: CCR's `/v1/responses` inbound
144
+ * is the Codex CLI path, and Grok-via-Chat recovery depends on it.
145
+ * Pass `false` for a generic Responses destination whose `exec` tool
146
+ * legitimately accepts `{"cmd": …}` JSON.
147
+ */
148
+ export interface CodexIsolateConventionsOptions {
149
+ codexIsolateConventions?: boolean;
150
+ }
151
+ /** Client-facing custom-tool input: unwrap the Unified JSON wrapper, then
152
+ * apply the exec/patch normalizers in one place so stream finalize, the
153
+ * completed skeleton, and the non-stream JSON path cannot drift. */
154
+ export declare function normalizeClientCustomToolInput(name: string | undefined, rawArguments: string, options?: CodexIsolateConventionsOptions): string;
15
155
  /** Unified Chat JSON → Responses API non-stream response. */
16
156
  export declare function unifiedResponseToResponses(chat: any, options?: {
17
157
  originalModel?: string;
18
158
  callIdMap?: ResponsesCallIdMap;
159
+ customToolNames?: Set<string>;
160
+ codexIsolateConventions?: boolean;
19
161
  }): any;
20
162
  export interface ResponsesStreamState {
21
163
  responseId: string;
22
164
  model?: string;
23
165
  textItemId: string;
24
166
  textStarted: boolean;
167
+ textClosed: boolean;
25
168
  textOutputIndex?: number;
26
169
  textContent: string;
170
+ closedTextItems: Array<{
171
+ id: string;
172
+ outputIndex: number;
173
+ content: string;
174
+ }>;
27
175
  toolCalls: Map<number, {
28
176
  id: string;
29
177
  name: string;
@@ -31,6 +179,7 @@ export interface ResponsesStreamState {
31
179
  added: boolean;
32
180
  outputIndex: number;
33
181
  emittedArgumentsLength: number;
182
+ isCustom: boolean;
34
183
  }>;
35
184
  nextOutputIndex: number;
36
185
  finished: boolean;
@@ -39,10 +188,17 @@ export interface ResponsesStreamState {
39
188
  finishReasonSeen: boolean;
40
189
  usage?: any;
41
190
  callIdMap: ResponsesCallIdMap;
191
+ customToolNames: Set<string>;
192
+ thinkingContent: string;
193
+ thinkingEncryptedContent?: string;
194
+ thinkingId?: string;
195
+ codexIsolateConventions: boolean;
42
196
  }
43
197
  export declare function createResponsesStreamState(options?: {
44
198
  model?: string;
45
199
  callIdMap?: ResponsesCallIdMap;
200
+ customToolNames?: Set<string>;
201
+ codexIsolateConventions?: boolean;
46
202
  }): ResponsesStreamState;
47
203
  /**
48
204
  * Convert one Unified Chat Completions chunk into zero or more Responses
@@ -0,0 +1,13 @@
1
+ import type { ThinkLevel, UnifiedChatRequest } from "../types/llm";
2
+ type UnifiedReasoning = UnifiedChatRequest["reasoning"];
3
+ /** Normalize effort tokens at the protocol boundary without rejecting extensions. */
4
+ export declare function normalizeReasoningEffort(value: unknown): ThinkLevel | undefined;
5
+ /** `none` and an explicit enabled:false are the canonical off signals. */
6
+ export declare function isReasoningDisabled(reasoning: UnifiedReasoning | undefined, thinking?: UnifiedChatRequest["thinking"]): boolean;
7
+ /** Build the canonical reasoning state used by every inbound protocol. */
8
+ export declare function canonicalReasoning(effortValue: unknown, enabledWhenEffortAbsent?: boolean): UnifiedReasoning | undefined;
9
+ /** Serialize Unified reasoning onto the Chat Completions wire shape in place. */
10
+ export declare function applyOpenAIChatReasoning(request: UnifiedChatRequest): UnifiedChatRequest;
11
+ /** Anthropic accepts low..max, while CCR/OpenAI may additionally emit minimal/ultra/none. */
12
+ export declare function toAnthropicReasoningEffort(effortValue: unknown): Exclude<ThinkLevel, "none" | "minimal" | "ultra"> | undefined;
13
+ export {};
@@ -24,6 +24,7 @@ export declare function sanitizeBodyForLog(value: string, maxBytes?: number): st
24
24
  export declare function sanitizeErrorForLog(error: unknown): {
25
25
  message: string;
26
26
  code?: string;
27
+ type?: string;
27
28
  name?: string;
28
29
  statusCode?: number;
29
30
  stack?: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Client-facing SSE keepalive.
3
+ *
4
+ * Claude Code shows "Waiting for API response · will retry in … · check your
5
+ * network" when no bytes arrive on the response stream for 20s (advisor: 90s),
6
+ * even before any retry has started — see Claude Code error docs. Anthropic's
7
+ * own `ping` events often arrive only every ~25–30s during long thinking /
8
+ * tool-argument streams, so a transparent proxy that only forwards upstream
9
+ * bytes trips that spinner on every slow Opus turn.
10
+ *
11
+ * Inject SSE comment frames (`: …\n\n`) after `idleMs` of silence. Comments are
12
+ * transport keepalives: EventSource / Anthropic SSE parsers ignore them, but
13
+ * they reset Claude Code's byte-idle timer. Default 10s is half the 20s warning.
14
+ */
15
+ export type SSEClientKeepaliveOptions = {
16
+ /** Silence before emitting a comment frame. Default 10_000. */
17
+ idleMs?: number;
18
+ };
19
+ export declare function withSSEClientKeepalive(body: ReadableStream<Uint8Array>, options?: SSEClientKeepaliveOptions): ReadableStream<Uint8Array>;
@@ -1,3 +1,4 @@
1
1
  export { SSEParserTransform } from './SSEParser.transform';
2
2
  export { SSESerializerTransform } from './SSESerializer.transform';
3
3
  export { rewriteStream } from './rewriteStream';
4
+ export { withSSEClientKeepalive, type SSEClientKeepaliveOptions, } from './client-keepalive';
@@ -0,0 +1,62 @@
1
+ import { type CacheAffinityHeaders, type CachePrefixDiff, type CachePrefixStage } from "./cache-prefix-debug";
2
+ export type UpstreamSSEDebugOptions = {
3
+ logger?: any;
4
+ reqId?: string | number;
5
+ provider?: string;
6
+ /** Routed model — snapshots are keyed per destination. */
7
+ model?: string;
8
+ /** Conversation / Claude session id used to pair consecutive cache snapshots. */
9
+ conversationId?: string;
10
+ /** Pipeline position this body was captured at. Defaults to `wire`. */
11
+ stage?: CachePrefixStage;
12
+ /** Codex (and similar) routing headers that pin prompt-cache affinity. */
13
+ cacheAffinity?: CacheAffinityHeaders;
14
+ /** Upstream status. Non-2xx bodies are diffed but never become the baseline. */
15
+ responseStatus?: number;
16
+ /** Client-leg diff, used to attribute a broken wire prefix to a stage. */
17
+ clientStageDiff?: CachePrefixDiff | null;
18
+ /** Outbound diff for this request, joined with the observed cache usage. */
19
+ cacheDiff?: CachePrefixDiff | null;
20
+ /** Cap for a single logged payload string (raw `data` field). */
21
+ maxBytes?: number;
22
+ };
23
+ /**
24
+ * Byte-preserving upstream response debug tap.
25
+ *
26
+ * For SSE: mirrors bytes to a background consumer that emits Codex-parity
27
+ * `recieved data` / `Original Response` logs (including Anthropic usage /
28
+ * cache fields on message_start / message_delta).
29
+ *
30
+ * Important: do **not** use `ReadableStream.tee()` here. Tee couples
31
+ * backpressure across both branches — a slow debug logger (pino file I/O on
32
+ * every thinking delta) stalls the client branch. Claude Code then idles long
33
+ * enough to surface "Waiting for API response · check your network" and retry
34
+ * while CCR eventually still finishes the upstream as HTTP 200.
35
+ *
36
+ * Instead, a TransformStream forwards each chunk to the client immediately and
37
+ * copies into a debug tunnel whose writable side has an infinite high-water
38
+ * mark, so debug I/O can never delay the client. The debug consumer drains the
39
+ * buffered copy at its own pace (memory-bound to the stream size).
40
+ *
41
+ * Exact-wire passthrough never enters transformer stream loggers; this tap is
42
+ * the single shared place that covers every outbound provider.
43
+ */
44
+ export declare function tapUpstreamSSEDebug(response: Response, opts: UpstreamSSEDebugOptions): Promise<Response>;
45
+ export type CacheStructureSummary = {
46
+ systemBreakpoints: number;
47
+ messageBreakpoints: number;
48
+ toolBreakpoints: number;
49
+ prompt_cache_key?: string;
50
+ lastAssistantBlockOrder?: string[];
51
+ };
52
+ /**
53
+ * Summarize outbound Anthropic/OpenAI cache-oriented request structure for
54
+ * debug verification (breakpoints / prompt_cache_key / assistant block order).
55
+ */
56
+ export declare function summarizeOutboundCacheStructure(body: Record<string, any> | null | undefined): CacheStructureSummary | null;
57
+ /**
58
+ * Snapshot the outbound body, diff it against the previous turn, and log both.
59
+ * Returns the diff so the caller can join it with the observed cache usage
60
+ * once upstream responds.
61
+ */
62
+ export declare function logOutboundCacheStructure(body: Record<string, any> | null | undefined, opts: UpstreamSSEDebugOptions): CachePrefixDiff | null;
@@ -0,0 +1,37 @@
1
+ declare const OAUTH_CONFIG: {
2
+ client_id: string;
3
+ device_authorization_endpoint: string;
4
+ token_endpoint: string;
5
+ scope: string;
6
+ };
7
+ export interface XaiTokens {
8
+ access_token: string;
9
+ refresh_token?: string;
10
+ id_token?: string;
11
+ token_type: string;
12
+ scope?: string;
13
+ expires_at?: number;
14
+ last_refresh?: number;
15
+ }
16
+ export interface DeviceCodeResponse {
17
+ device_code: string;
18
+ user_code: string;
19
+ verification_uri: string;
20
+ verification_uri_complete?: string;
21
+ expires_in?: number;
22
+ interval?: number;
23
+ }
24
+ declare function getAuthFilePath(): string;
25
+ export declare function loadTokens(): XaiTokens | null;
26
+ export declare function saveTokens(tokens: XaiTokens): void;
27
+ export declare function isTokenExpiring(tokens: XaiTokens, skewSeconds?: number): boolean;
28
+ export declare function requestDeviceCode(): Promise<DeviceCodeResponse>;
29
+ export declare function pollDeviceCodeToken(device: DeviceCodeResponse, options?: {
30
+ sleep?: (ms: number) => Promise<void>;
31
+ now?: () => number;
32
+ }): Promise<XaiTokens>;
33
+ export declare function refreshTokens(_refreshToken?: string): Promise<XaiTokens>;
34
+ export declare function getValidAccessToken(options?: {
35
+ force?: boolean;
36
+ }): Promise<XaiTokens>;
37
+ export { getAuthFilePath, OAUTH_CONFIG };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caeliq/llms",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "description": "A universal LLM API transformation server",
5
5
  "main": "dist/cjs/server.cjs",
6
6
  "module": "dist/esm/server.mjs",
@@ -30,29 +30,29 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@anthropic-ai/sdk": "^0.115.0",
33
- "@caeliq/ccr-shared": "^2.1.1",
34
- "@cursor/sdk": "^1.0.26",
33
+ "@caeliq/ccr-shared": "^2.1.3",
34
+ "@cursor/sdk": "^1.0.28",
35
35
  "@fastify/cors": "^11.3.0",
36
- "@fastify/rate-limit": "^10.3.0",
37
- "@google/genai": "^2.15.0",
36
+ "@fastify/rate-limit": "^11.2.0",
37
+ "@google/genai": "^2.17.1",
38
38
  "@huggingface/tokenizers": "^0.1.3",
39
39
  "dotenv": "^17.4.2",
40
- "fastify": "^5.11.2",
40
+ "fastify": "^5.12.0",
41
41
  "fastify-plugin": "^6.0.0",
42
- "google-auth-library": "^11.0.0",
42
+ "google-auth-library": "^11.0.2",
43
43
  "json5": "^2.2.3",
44
44
  "jsonrepair": "^3.15.0",
45
45
  "latex-to-unicode": "^0.1.0",
46
46
  "lru-cache": "^11.5.2",
47
- "openai": "^7.2.0",
47
+ "openai": "^7.4.0",
48
48
  "tiktoken": "^1.0.22",
49
- "undici": "^8.9.0",
49
+ "undici": "^8.10.0",
50
50
  "uuid": "^14.0.1"
51
51
  },
52
52
  "devDependencies": {
53
- "@types/node": "^26.1.2",
54
- "esbuild": "^0.28.1",
55
- "tsx": "^4.23.1",
53
+ "@types/node": "^26.2.0",
54
+ "esbuild": "^0.28.2",
55
+ "tsx": "^4.23.12",
56
56
  "typescript": "^6.0.3"
57
57
  },
58
58
  "engines": {