@juspay/neurolink 12.19.0 → 12.19.2

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.
@@ -22,6 +22,24 @@ export declare class CodexFallbackStreamError extends Error {
22
22
  readonly usage?: CodexFallbackResult["usage"];
23
23
  constructor(payload: Record<string, unknown>, eventType: string, usage?: CodexFallbackResult["usage"]);
24
24
  }
25
+ /**
26
+ * Stable per-conversation routing key for OpenAI prompt caching.
27
+ *
28
+ * Anthropic caching is explicit — the client marks breakpoints and the provider
29
+ * honours them. OpenAI caching is automatic: a prefix is reused only when the
30
+ * request lands on infrastructure already holding it, and `prompt_cache_key` is
31
+ * what pins a conversation to one. Without it this hop takes its chances on
32
+ * routing, which is the worst case for a fallback that arrives in bursts
33
+ * separated by long gaps.
34
+ *
35
+ * Claude Code carries its session id inside `metadata.user_id`, alongside
36
+ * account and device identifiers. Only the session id is read, and it is
37
+ * hashed, so no account or device identifier reaches the upstream. When no
38
+ * session id can be recovered the field is omitted rather than filled with
39
+ * something shared: a key common to every conversation would pin unrelated
40
+ * prefixes onto one cache instead of separating them.
41
+ */
42
+ export declare function codexPromptCacheKey(body: ClaudeRequest): string | undefined;
25
43
  /** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
26
44
  export declare function convertClaudeRequestToCodex(body: ClaudeRequest, model: string, reasoningEffort?: CodexReasoningEffort): CodexResponsesRequest;
27
45
  /**
@@ -6,6 +6,7 @@
6
6
  * in the native Codex proxy handler so fallback traffic follows the same pool
7
7
  * rules as a native Codex request.
8
8
  */
9
+ import { createHash } from "node:crypto";
9
10
  import { ClaudeStreamSerializer, generateToolUseId } from "./claudeFormat.js";
10
11
  import { classifyProxyFailureCode } from "./proxyFailureDetails.js";
11
12
  import { extractCodexUsage } from "./codexUsage.js";
@@ -235,6 +236,45 @@ function convertClaudeMessage(role, content) {
235
236
  flushMessage();
236
237
  return input;
237
238
  }
239
+ /**
240
+ * Stable per-conversation routing key for OpenAI prompt caching.
241
+ *
242
+ * Anthropic caching is explicit — the client marks breakpoints and the provider
243
+ * honours them. OpenAI caching is automatic: a prefix is reused only when the
244
+ * request lands on infrastructure already holding it, and `prompt_cache_key` is
245
+ * what pins a conversation to one. Without it this hop takes its chances on
246
+ * routing, which is the worst case for a fallback that arrives in bursts
247
+ * separated by long gaps.
248
+ *
249
+ * Claude Code carries its session id inside `metadata.user_id`, alongside
250
+ * account and device identifiers. Only the session id is read, and it is
251
+ * hashed, so no account or device identifier reaches the upstream. When no
252
+ * session id can be recovered the field is omitted rather than filled with
253
+ * something shared: a key common to every conversation would pin unrelated
254
+ * prefixes onto one cache instead of separating them.
255
+ */
256
+ export function codexPromptCacheKey(body) {
257
+ const raw = body.metadata?.user_id;
258
+ if (typeof raw !== "string" || raw.length === 0) {
259
+ return undefined;
260
+ }
261
+ let parsed;
262
+ try {
263
+ parsed = JSON.parse(raw);
264
+ }
265
+ catch {
266
+ return undefined;
267
+ }
268
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
269
+ return undefined;
270
+ }
271
+ const fields = parsed;
272
+ const sessionId = fields.session_id ?? fields.parent_session_id;
273
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
274
+ return undefined;
275
+ }
276
+ return createHash("sha256").update(sessionId).digest("hex");
277
+ }
238
278
  /** Convert a Claude Messages request into the ChatGPT Codex Responses shape. */
239
279
  export function convertClaudeRequestToCodex(body, model, reasoningEffort) {
240
280
  const input = body.messages.flatMap((message) => message.role === "system"
@@ -250,6 +290,10 @@ export function convertClaudeRequestToCodex(body, model, reasoningEffort) {
250
290
  ? { reasoning: { effort: reasoningEffort } }
251
291
  : {}),
252
292
  };
293
+ const promptCacheKey = codexPromptCacheKey(body);
294
+ if (promptCacheKey) {
295
+ request.prompt_cache_key = promptCacheKey;
296
+ }
253
297
  const instructions = buildSystemInstructions(body);
254
298
  if (instructions) {
255
299
  request.instructions = instructions;
@@ -21,7 +21,6 @@ import type { UsageContext, VertexPassthroughTerminal, VertexAnthropicPassthroug
21
21
  * `effort` key, never the sibling settings beside it.
22
22
  */
23
23
  export declare function mapVertexOutputConfig(value: unknown): Record<string, unknown> | undefined;
24
- /** Strip what Vertex rejects and pin the API version it requires. */
25
24
  export declare function buildVertexAnthropicPayload(body: Readonly<Record<string, unknown>>): Record<string, unknown>;
26
25
  /**
27
26
  * Publisher endpoint for a Claude model on Vertex.
@@ -69,6 +69,33 @@ export function mapVertexOutputConfig(value) {
69
69
  return Object.keys(rest).length > 0 ? rest : undefined;
70
70
  }
71
71
  /** Strip what Vertex rejects and pin the API version it requires. */
72
+ /**
73
+ * Vertex refuses `role: "system"` messages outright — "role 'system' is not
74
+ * supported on this model" — where the first-party Messages API accepts them
75
+ * anywhere except index 0.
76
+ *
77
+ * Claude Code interleaves them as positional notices: a file changed on disk
78
+ * since it was last read, a tool became available, an MCP server dropped. Their
79
+ * meaning depends on where they sit, so hoisting them into the top-level
80
+ * `system` parameter would strip exactly what makes them useful, and dropping
81
+ * them would silently lose context the turn depends on. They become user turns
82
+ * instead, which keeps both the text and the position. Consecutive same-role
83
+ * turns are accepted, so this never produces an invalid sequence.
84
+ */
85
+ function normalizeVertexRoles(messages) {
86
+ if (!Array.isArray(messages)) {
87
+ return messages;
88
+ }
89
+ let changed = false;
90
+ const mapped = messages.map((message) => {
91
+ if (!record(message) || message.role !== "system") {
92
+ return message;
93
+ }
94
+ changed = true;
95
+ return { ...message, role: "user" };
96
+ });
97
+ return changed ? mapped : messages;
98
+ }
72
99
  export function buildVertexAnthropicPayload(body) {
73
100
  const dropped = new Set(UNSUPPORTED_FIELDS);
74
101
  const payload = Object.fromEntries(Object.entries(body).filter(([key]) => !dropped.has(key)));
@@ -81,6 +108,9 @@ export function buildVertexAnthropicPayload(body) {
81
108
  payload.output_config = mapped;
82
109
  }
83
110
  }
111
+ if ("messages" in payload) {
112
+ payload.messages = normalizeVertexRoles(payload.messages);
113
+ }
84
114
  return { ...payload, anthropic_version: VERTEX_ANTHROPIC_VERSION };
85
115
  }
86
116
  /**
@@ -156,6 +156,8 @@ export type CodexResponsesRequest = {
156
156
  input: CodexResponsesInputItem[];
157
157
  stream: true;
158
158
  store: false;
159
+ /** Pins one conversation to the cache that already holds its prefix. */
160
+ prompt_cache_key?: string;
159
161
  reasoning?: {
160
162
  effort: CodexReasoningEffort;
161
163
  };
package/docs/changelog.md CHANGED
@@ -1,15 +1,24 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to NeuroLink are documented in this changelog.
3
+ **The current release notes live on GitHub Releases:
4
+ [github.com/juspay/neurolink/releases](https://github.com/juspay/neurolink/releases)** — generated
5
+ automatically by semantic-release on every publish, so they are always current. An RSS/Atom feed
6
+ is available at [releases.atom](https://github.com/juspay/neurolink/releases.atom).
4
7
 
5
- For the complete and most up-to-date changelog, please visit:
6
- **[CHANGELOG.md](https://github.com/juspay/neurolink/blob/release/CHANGELOG.md)** in the GitHub repository.
8
+ > `CHANGELOG.md` in the repository is **not** a reliable source of the current version.
9
+ > `@semantic-release/git` was deliberately removed so that nothing pushes back to the `release`
10
+ > branch (branch protection rejects pushes that carry no check runs), which means the committed
11
+ > `CHANGELOG.md` stops at the last version that was committed by hand. It still ships inside the
12
+ > published npm package, where it is generated at publish time and is correct.
7
13
 
8
14
  ---
9
15
 
10
- ## Latest Releases
16
+ ## Release highlights
11
17
 
12
- ### v9.14.0 (Current Release - February 28, 2026)
18
+ The entries below are hand-written highlights for selected past releases. They are an archive,
19
+ not a complete or current list — use GitHub Releases above for that.
20
+
21
+ ### v9.14.0 (February 28, 2026)
13
22
 
14
23
  **Features:**
15
24