@juspay/neurolink 12.20.1 → 12.21.0
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/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +372 -372
- package/dist/cli/commands/decide.d.ts +22 -0
- package/dist/cli/commands/decide.js +287 -0
- package/dist/cli/parser.js +6 -0
- package/dist/neurolink.js +8 -0
- package/dist/proxy/codexFallback.d.ts +27 -9
- package/dist/proxy/codexFallback.js +84 -13
- package/dist/proxy/openaiFormat.js +8 -1
- package/dist/proxy/vertexAnthropicFallback.d.ts +17 -1
- package/dist/proxy/vertexAnthropicFallback.js +25 -1
- package/dist/routing/classifierStrategies.js +7 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +17 -0
- package/dist/server/routes/claudeProxyRoutes.js +53 -5
- package/dist/types/classifierRouter.d.ts +4 -0
- package/dist/types/cli.d.ts +12 -0
- package/dist/types/providers.d.ts +8 -0
- package/dist/types/proxy.d.ts +20 -4
- package/dist/utils/anthropicCacheBreakpoints.d.ts +22 -1
- package/dist/utils/anthropicCacheBreakpoints.js +113 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.js +27 -2
- package/dist/utils/pricing.js +69 -2
- package/docs-site/static/search-index.json +1 -1
- package/package.json +1 -1
|
@@ -175,6 +175,22 @@ declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>)
|
|
|
175
175
|
* diagnostics the operator wants.
|
|
176
176
|
*/
|
|
177
177
|
declare function redactHeadersForBorrower(headers: Record<string, string>): Record<string, string>;
|
|
178
|
+
/**
|
|
179
|
+
* The model that actually served a fallback turn.
|
|
180
|
+
*
|
|
181
|
+
* One request can leave more than one leg's model in metadata: the Codex leg
|
|
182
|
+
* records its model on the failure path before the chain moves on, and the
|
|
183
|
+
* configured chain tries Codex first. Choosing by key precedence therefore
|
|
184
|
+
* billed a Vertex-served turn as the Codex model — the exact misattribution
|
|
185
|
+
* this file's pricing fix exists to prevent. The account type that reached
|
|
186
|
+
* `logFinalRequest` is the only authoritative signal for which leg answered,
|
|
187
|
+
* so select on that and ignore the other leg's leftovers.
|
|
188
|
+
*/
|
|
189
|
+
export declare function resolveServedFallbackModel(metadata: {
|
|
190
|
+
codexFallbackModel?: unknown;
|
|
191
|
+
vertexFallbackModel?: unknown;
|
|
192
|
+
}, accountType: string | undefined): string | undefined;
|
|
193
|
+
declare function clearClaudeFallbackTerminalEvidence(ctx: ServerContext): void;
|
|
178
194
|
declare function executeClaudeFallbackTranslation(args: {
|
|
179
195
|
ctx: ServerContext;
|
|
180
196
|
body: ClaudeRequest;
|
|
@@ -462,6 +478,7 @@ export declare function isTransientHttpFailure(status: number, errBody: string):
|
|
|
462
478
|
export declare function isUpstreamOverload(status: number, errBody: string): boolean;
|
|
463
479
|
export declare function redactProviderErrorMessage(message: string): string;
|
|
464
480
|
export declare const __testHooks: {
|
|
481
|
+
clearClaudeFallbackTerminalEvidence: typeof clearClaudeFallbackTerminalEvidence;
|
|
465
482
|
normalizeQuotaForAccounts: typeof normalizeQuotaForAccounts;
|
|
466
483
|
resolveHomeIndex: typeof resolveHomeIndex;
|
|
467
484
|
maybeResetPrimaryToHome: typeof maybeResetPrimaryToHome;
|
|
@@ -100,6 +100,16 @@ function resolveRequestLogAccountIdentity(accountLabel, accountType) {
|
|
|
100
100
|
provider: "anthropic",
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
if (accountType === "vertex") {
|
|
104
|
+
// A native fallback leg has no OAuth login — the account IS the served
|
|
105
|
+
// model, e.g. "vertex/claude-opus-4-6". Without this branch `provider`
|
|
106
|
+
// stays unset, `annotateRequestPricing` falls back to
|
|
107
|
+
// "openai-compatible", and that resolves to a cross-provider scan which
|
|
108
|
+
// prices the turn from the *requested* alias in the Anthropic table
|
|
109
|
+
// instead of the served model in the Vertex one — a 2.5x under-report on
|
|
110
|
+
// the only leg that bills in real currency.
|
|
111
|
+
return { accountKey: accountLabel, provider: "vertex" };
|
|
112
|
+
}
|
|
103
113
|
return {};
|
|
104
114
|
}
|
|
105
115
|
// ---------------------------------------------------------------------------
|
|
@@ -3567,11 +3577,29 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
3567
3577
|
isClaudeClientRequest: isLikelyClaudeClient(clientHeaders, clientSnapshotBody),
|
|
3568
3578
|
};
|
|
3569
3579
|
}
|
|
3580
|
+
/**
|
|
3581
|
+
* The model that actually served a fallback turn.
|
|
3582
|
+
*
|
|
3583
|
+
* One request can leave more than one leg's model in metadata: the Codex leg
|
|
3584
|
+
* records its model on the failure path before the chain moves on, and the
|
|
3585
|
+
* configured chain tries Codex first. Choosing by key precedence therefore
|
|
3586
|
+
* billed a Vertex-served turn as the Codex model — the exact misattribution
|
|
3587
|
+
* this file's pricing fix exists to prevent. The account type that reached
|
|
3588
|
+
* `logFinalRequest` is the only authoritative signal for which leg answered,
|
|
3589
|
+
* so select on that and ignore the other leg's leftovers.
|
|
3590
|
+
*/
|
|
3591
|
+
export function resolveServedFallbackModel(metadata, accountType) {
|
|
3592
|
+
const served = accountType === "vertex"
|
|
3593
|
+
? metadata.vertexFallbackModel
|
|
3594
|
+
: metadata.codexFallbackModel;
|
|
3595
|
+
return typeof served === "string" && served.length > 0 ? served : undefined;
|
|
3596
|
+
}
|
|
3570
3597
|
function clearClaudeFallbackTerminalEvidence(ctx) {
|
|
3571
3598
|
for (const key of [
|
|
3572
3599
|
"codexFallbackAccount",
|
|
3573
3600
|
"codexFallbackAccountType",
|
|
3574
3601
|
"codexFallbackModel",
|
|
3602
|
+
"vertexFallbackModel",
|
|
3575
3603
|
"codexFallbackFailureUsage",
|
|
3576
3604
|
"codexFallbackDeferredFailure",
|
|
3577
3605
|
"sdkFallbackFailure",
|
|
@@ -4054,6 +4082,19 @@ async function tryConfiguredClaudeFallbackChain(args) {
|
|
|
4054
4082
|
// the caller's pending tool call, which strands every agentic turn.
|
|
4055
4083
|
const vertexModel = fallback.model;
|
|
4056
4084
|
const vertexAccount = `vertex/${vertexModel}`;
|
|
4085
|
+
// A Codex leg that already failed left its own terminal evidence
|
|
4086
|
+
// behind — its model, account, deferred-failure flag and failure
|
|
4087
|
+
// usage. Every one of those is read again when this turn finalizes:
|
|
4088
|
+
// the model would be logged and priced in place of the Vertex one,
|
|
4089
|
+
// the deferred-failure branch would re-attribute the tracer to
|
|
4090
|
+
// "openai" after the Vertex leg set it, and the failure usage would
|
|
4091
|
+
// merge its reasoning tokens into this turn's counts. Start the leg
|
|
4092
|
+
// clean, the same way a fresh upstream dispatch does.
|
|
4093
|
+
clearClaudeFallbackTerminalEvidence(ctx);
|
|
4094
|
+
// Billing follows the model that served the turn. The request log
|
|
4095
|
+
// otherwise records `body.model` — the alias the client asked for —
|
|
4096
|
+
// and prices Opus-on-Vertex at Sonnet rates.
|
|
4097
|
+
ctx.metadata.vertexFallbackModel = vertexModel;
|
|
4057
4098
|
response = await executeVertexAnthropicFallback({
|
|
4058
4099
|
body,
|
|
4059
4100
|
model: vertexModel,
|
|
@@ -6212,18 +6253,24 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
6212
6253
|
recordFinalSuccess(finalAccountLabel, finalAccountType);
|
|
6213
6254
|
}
|
|
6214
6255
|
const traceCtx = tracer?.getTraceContext();
|
|
6256
|
+
const servedModel = resolveServedFallbackModel(ctx.metadata, finalAccountType);
|
|
6215
6257
|
logRequest({
|
|
6216
6258
|
timestamp: new Date().toISOString(),
|
|
6217
6259
|
requestId: ctx.requestId,
|
|
6218
6260
|
method: ctx.method,
|
|
6219
6261
|
path: ctx.path,
|
|
6220
|
-
model:
|
|
6221
|
-
|
|
6222
|
-
: body.model,
|
|
6223
|
-
...(typeof ctx.metadata.codexFallbackModel === "string"
|
|
6262
|
+
model: servedModel ?? body.model,
|
|
6263
|
+
...(servedModel
|
|
6224
6264
|
? {
|
|
6225
6265
|
requestedModel: body.model,
|
|
6226
|
-
|
|
6266
|
+
// Codex reports input inclusive of cached tokens; the Vertex
|
|
6267
|
+
// Anthropic wire reports them separately, exactly as the native
|
|
6268
|
+
// path does, so the flag stays Codex-only. Keyed off the serving
|
|
6269
|
+
// account for the same reason `servedModel` is: a failed Codex leg
|
|
6270
|
+
// leaves its model behind on a turn Vertex went on to serve.
|
|
6271
|
+
...(finalAccountType === "vertex"
|
|
6272
|
+
? {}
|
|
6273
|
+
: { inputIncludesCachedTokens: false }),
|
|
6227
6274
|
}
|
|
6228
6275
|
: {}),
|
|
6229
6276
|
...(ctx.metadata.contextPreflight
|
|
@@ -8777,6 +8824,7 @@ export function redactProviderErrorMessage(message) {
|
|
|
8777
8824
|
// spinning up a full proxy. Keep this surface small.
|
|
8778
8825
|
// ---------------------------------------------------------------------------
|
|
8779
8826
|
export const __testHooks = {
|
|
8827
|
+
clearClaudeFallbackTerminalEvidence,
|
|
8780
8828
|
normalizeQuotaForAccounts,
|
|
8781
8829
|
resolveHomeIndex,
|
|
8782
8830
|
maybeResetPrimaryToHome,
|
|
@@ -296,6 +296,10 @@ export type ClassifierRouterInput = {
|
|
|
296
296
|
requiresVision?: boolean;
|
|
297
297
|
thinkingLevel?: string;
|
|
298
298
|
sessionId?: string;
|
|
299
|
+
/** Whether this request is tied to a session, independent of the (withheld) session id itself. */
|
|
300
|
+
sessionBound?: boolean;
|
|
301
|
+
/** Number of prior conversation messages the caller supplied, when known. */
|
|
302
|
+
priorMessageCount?: number;
|
|
299
303
|
};
|
|
300
304
|
/** Enriched per-model metadata used while ranking pool members. */
|
|
301
305
|
export type ClassifierModelMeta = {
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1296,6 +1296,18 @@ export type AutoresearchInitArgs = {
|
|
|
1296
1296
|
provider?: string;
|
|
1297
1297
|
model?: string;
|
|
1298
1298
|
};
|
|
1299
|
+
/** Arguments for `neurolink decide` — the third inference type's CLI surface. */
|
|
1300
|
+
export type CliDecideArgs = {
|
|
1301
|
+
state?: string;
|
|
1302
|
+
stateFile?: string;
|
|
1303
|
+
questions?: string;
|
|
1304
|
+
questionsFile?: string;
|
|
1305
|
+
provider?: string;
|
|
1306
|
+
model?: string;
|
|
1307
|
+
timeout?: number;
|
|
1308
|
+
format?: "text" | "json";
|
|
1309
|
+
debug?: boolean;
|
|
1310
|
+
};
|
|
1299
1311
|
/** Base options shared across all `neurolink evaluate` subcommands. */
|
|
1300
1312
|
export type BaseEvaluateArgs = {
|
|
1301
1313
|
json?: boolean;
|
|
@@ -2281,6 +2281,14 @@ export type VertexAnthropicTool = {
|
|
|
2281
2281
|
};
|
|
2282
2282
|
cache_control?: VertexAnthropicCacheControl;
|
|
2283
2283
|
};
|
|
2284
|
+
/**
|
|
2285
|
+
* Supplies the bearer token for a Vertex publisher call.
|
|
2286
|
+
*
|
|
2287
|
+
* Exists so a test can stand in for the Google credential lookup: gaxios
|
|
2288
|
+
* resolves its transport to node-fetch rather than `globalThis.fetch`, so a
|
|
2289
|
+
* test that swaps the global cannot intercept the token exchange.
|
|
2290
|
+
*/
|
|
2291
|
+
export type VertexAccessTokenProvider = () => Promise<string | null | undefined>;
|
|
2284
2292
|
/** Input to `applyVertexAnthropicCacheBreakpoints`. */
|
|
2285
2293
|
export type VertexAnthropicCacheInput = {
|
|
2286
2294
|
system?: string;
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -42,10 +42,27 @@ export type ModelRouterInterface = {
|
|
|
42
42
|
getModelMappings?: () => ModelMapping[];
|
|
43
43
|
getPassthroughModels?: () => string[];
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Anthropic prompt-cache breakpoint marker.
|
|
47
|
+
*
|
|
48
|
+
* Anthropic caching is explicit: the prefix up to each marker is cached, and a
|
|
49
|
+
* request carrying none is cached not at all. Four markers is the hard ceiling.
|
|
50
|
+
*/
|
|
51
|
+
export type ClaudeCacheControl = {
|
|
52
|
+
type: "ephemeral";
|
|
53
|
+
/**
|
|
54
|
+
* How long the marked prefix stays cached. The API accepts a 5-minute or a
|
|
55
|
+
* 1-hour window, priced at 1.25x and 2x base input respectively; omitting it
|
|
56
|
+
* means 5 minutes. Without this field a caller cannot express the 1-hour
|
|
57
|
+
* marker at all.
|
|
58
|
+
*/
|
|
59
|
+
ttl?: "5m" | "1h";
|
|
60
|
+
};
|
|
45
61
|
/** A single text block in a Claude content array. */
|
|
46
62
|
export type ClaudeTextBlock = {
|
|
47
63
|
type: "text";
|
|
48
64
|
text: string;
|
|
65
|
+
cache_control?: ClaudeCacheControl;
|
|
49
66
|
};
|
|
50
67
|
/** A single image block in a Claude content array. */
|
|
51
68
|
export type ClaudeImageBlock = {
|
|
@@ -69,6 +86,7 @@ export type ClaudeToolResultBlock = {
|
|
|
69
86
|
type: "tool_result";
|
|
70
87
|
tool_use_id: string;
|
|
71
88
|
content: string | ClaudeContentBlock[];
|
|
89
|
+
cache_control?: ClaudeCacheControl;
|
|
72
90
|
};
|
|
73
91
|
/** A thinking/reasoning block in a Claude content array. */
|
|
74
92
|
export type ClaudeThinkingBlock = {
|
|
@@ -86,6 +104,7 @@ export type ClaudeTool = {
|
|
|
86
104
|
name: string;
|
|
87
105
|
description?: string;
|
|
88
106
|
input_schema: Record<string, unknown>;
|
|
107
|
+
cache_control?: ClaudeCacheControl;
|
|
89
108
|
};
|
|
90
109
|
/** Metadata attached to a Claude Messages API request. */
|
|
91
110
|
export type ClaudeMetadata = {
|
|
@@ -99,10 +118,7 @@ export type ClaudeRequest = {
|
|
|
99
118
|
model: string;
|
|
100
119
|
messages: ClaudeMessage[];
|
|
101
120
|
max_tokens: number;
|
|
102
|
-
system?: string |
|
|
103
|
-
type: "text";
|
|
104
|
-
text: string;
|
|
105
|
-
}>;
|
|
121
|
+
system?: string | ClaudeTextBlock[];
|
|
106
122
|
temperature?: number;
|
|
107
123
|
top_p?: number;
|
|
108
124
|
top_k?: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VertexAnthropicCacheInput, VertexAnthropicCacheOutput, VertexAnthropicMessage } from "../types/index.js";
|
|
1
|
+
import type { ClaudeRequest, VertexAnthropicCacheInput, VertexAnthropicCacheOutput, VertexAnthropicMessage } from "../types/index.js";
|
|
2
2
|
/** Anthropic allows at most four `cache_control` breakpoints per request. */
|
|
3
3
|
export declare const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4;
|
|
4
4
|
/**
|
|
@@ -41,3 +41,24 @@ export declare function countAnthropicCacheMarkers(input: {
|
|
|
41
41
|
* the input array is cloned, never mutated.
|
|
42
42
|
*/
|
|
43
43
|
export declare function applyAnthropicHistoryCacheBreakpoints(input: ReadonlyArray<VertexAnthropicMessage>, budget: number): VertexAnthropicMessage[];
|
|
44
|
+
/**
|
|
45
|
+
* Breakpoint placement for a Claude Messages request the proxy built itself,
|
|
46
|
+
* rather than one a client authored.
|
|
47
|
+
*
|
|
48
|
+
* A request translated from another wire format arrives with no markers at
|
|
49
|
+
* all, because the format it came from has no way to express one: OpenAI-shaped
|
|
50
|
+
* callers rely on automatic prefix caching, which Anthropic does not do. A
|
|
51
|
+
* faithful translation is therefore cached at 0% on every turn, and a caller
|
|
52
|
+
* that fell back here from an exhausted pool burns the replacement quota faster
|
|
53
|
+
* than the one it was spared.
|
|
54
|
+
*
|
|
55
|
+
* Placement follows Anthropic's `tools → system → messages` rendering: one
|
|
56
|
+
* marker on the last system block covers tools *and* system in a single
|
|
57
|
+
* breakpoint, and the rest roll along the tail of the history so a growing
|
|
58
|
+
* conversation keeps its settled prefix cached. Markers the caller supplied are
|
|
59
|
+
* counted first and never displaced — this only spends what is left of the
|
|
60
|
+
* four.
|
|
61
|
+
*
|
|
62
|
+
* Pure: inputs are cloned, never mutated.
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyClaudeRequestCacheBreakpoints(request: ClaudeRequest): ClaudeRequest;
|
|
@@ -148,3 +148,116 @@ function markLastContentBlock(messages, i) {
|
|
|
148
148
|
messages[i] = { ...message, content };
|
|
149
149
|
return true;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Breakpoint placement for a Claude Messages request the proxy built itself,
|
|
153
|
+
* rather than one a client authored.
|
|
154
|
+
*
|
|
155
|
+
* A request translated from another wire format arrives with no markers at
|
|
156
|
+
* all, because the format it came from has no way to express one: OpenAI-shaped
|
|
157
|
+
* callers rely on automatic prefix caching, which Anthropic does not do. A
|
|
158
|
+
* faithful translation is therefore cached at 0% on every turn, and a caller
|
|
159
|
+
* that fell back here from an exhausted pool burns the replacement quota faster
|
|
160
|
+
* than the one it was spared.
|
|
161
|
+
*
|
|
162
|
+
* Placement follows Anthropic's `tools → system → messages` rendering: one
|
|
163
|
+
* marker on the last system block covers tools *and* system in a single
|
|
164
|
+
* breakpoint, and the rest roll along the tail of the history so a growing
|
|
165
|
+
* conversation keeps its settled prefix cached. Markers the caller supplied are
|
|
166
|
+
* counted first and never displaced — this only spends what is left of the
|
|
167
|
+
* four.
|
|
168
|
+
*
|
|
169
|
+
* Pure: inputs are cloned, never mutated.
|
|
170
|
+
*/
|
|
171
|
+
export function applyClaudeRequestCacheBreakpoints(request) {
|
|
172
|
+
const existing = countAnthropicCacheMarkers({
|
|
173
|
+
system: request.system,
|
|
174
|
+
tools: request.tools,
|
|
175
|
+
messages: [],
|
|
176
|
+
});
|
|
177
|
+
const inMessages = request.messages.reduce((n, m) => n +
|
|
178
|
+
(Array.isArray(m.content)
|
|
179
|
+
? m.content.filter((b) => "cache_control" in b && b.cache_control)
|
|
180
|
+
.length
|
|
181
|
+
: 0), 0);
|
|
182
|
+
let budget = MAX_BREAKPOINTS - existing - inMessages;
|
|
183
|
+
if (budget <= 0) {
|
|
184
|
+
return request;
|
|
185
|
+
}
|
|
186
|
+
const out = { ...request };
|
|
187
|
+
// One marker on the stable head. The last system block is the better anchor
|
|
188
|
+
// than the last tool, because system renders after tools and so a single
|
|
189
|
+
// marker there caches both.
|
|
190
|
+
if (Array.isArray(out.system) && out.system.length > 0 && existing === 0) {
|
|
191
|
+
const system = out.system.map((b) => ({ ...b }));
|
|
192
|
+
system[system.length - 1] = {
|
|
193
|
+
...system[system.length - 1],
|
|
194
|
+
cache_control: CLAUDE_EPHEMERAL,
|
|
195
|
+
};
|
|
196
|
+
out.system = system;
|
|
197
|
+
budget--;
|
|
198
|
+
}
|
|
199
|
+
else if ((!out.system || (Array.isArray(out.system) && out.system.length === 0)) &&
|
|
200
|
+
out.tools &&
|
|
201
|
+
out.tools.length > 0 &&
|
|
202
|
+
existing === 0) {
|
|
203
|
+
const tools = out.tools.map((t) => ({ ...t }));
|
|
204
|
+
tools[tools.length - 1] = {
|
|
205
|
+
...tools[tools.length - 1],
|
|
206
|
+
cache_control: CLAUDE_EPHEMERAL,
|
|
207
|
+
};
|
|
208
|
+
out.tools = tools;
|
|
209
|
+
budget--;
|
|
210
|
+
}
|
|
211
|
+
if (budget > 0) {
|
|
212
|
+
out.messages = applyClaudeHistoryBreakpoints(out.messages, budget);
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
const CLAUDE_EPHEMERAL = { type: "ephemeral" };
|
|
217
|
+
/**
|
|
218
|
+
* Roll `budget` markers backwards along the history, marking the last content
|
|
219
|
+
* block of each message that can carry one. Only text and tool_result blocks
|
|
220
|
+
* accept `cache_control`; a message ending in anything else is skipped without
|
|
221
|
+
* spending budget.
|
|
222
|
+
*
|
|
223
|
+
* A message whose content is a plain string is promoted to the single text
|
|
224
|
+
* block the marker has to live on. Skipping strings instead would have left an
|
|
225
|
+
* ordinary conversation with no history breakpoint at all, since the OpenAI
|
|
226
|
+
* bridge emits every plain-text turn as a string — which is precisely the
|
|
227
|
+
* growing history this is here to keep cached.
|
|
228
|
+
*/
|
|
229
|
+
function applyClaudeHistoryBreakpoints(input, budget) {
|
|
230
|
+
const messages = input.map((m) => ({ ...m }));
|
|
231
|
+
let remaining = Math.max(0, Math.min(budget, MAX_BREAKPOINTS));
|
|
232
|
+
for (let i = messages.length - 1; i >= 0 && remaining > 0; i--) {
|
|
233
|
+
const content = messages[i].content;
|
|
234
|
+
if (typeof content === "string") {
|
|
235
|
+
if (content.length === 0) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
messages[i] = {
|
|
239
|
+
...messages[i],
|
|
240
|
+
content: [
|
|
241
|
+
{ type: "text", text: content, cache_control: CLAUDE_EPHEMERAL },
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
remaining--;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const last = content[content.length - 1];
|
|
251
|
+
if (last.type !== "text" && last.type !== "tool_result") {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (last.cache_control) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const blocks = content.map((b) => ({ ...b }));
|
|
258
|
+
blocks[blocks.length - 1] = { ...last, cache_control: CLAUDE_EPHEMERAL };
|
|
259
|
+
messages[i] = { ...messages[i], content: blocks };
|
|
260
|
+
remaining--;
|
|
261
|
+
}
|
|
262
|
+
return messages;
|
|
263
|
+
}
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ declare class NeuroLinkLogger {
|
|
|
19
19
|
private logs;
|
|
20
20
|
private maxLogs;
|
|
21
21
|
private isDebugMode;
|
|
22
|
+
private diagnosticsToStderr;
|
|
22
23
|
private eventEmitter?;
|
|
23
24
|
constructor();
|
|
24
25
|
/**
|
|
@@ -49,6 +50,14 @@ declare class NeuroLinkLogger {
|
|
|
49
50
|
* @param level - The minimum log level to process ("debug", "info", "warn", or "error")
|
|
50
51
|
*/
|
|
51
52
|
setLogLevel(level: LogLevel): void;
|
|
53
|
+
/**
|
|
54
|
+
* Routes debug and info output to stderr instead of stdout. The CLI turns
|
|
55
|
+
* this on for `--format json`, where stdout carries the payload and any
|
|
56
|
+
* diagnostic line in it would make the output unparseable.
|
|
57
|
+
*
|
|
58
|
+
* @param enabled - True to send debug/info to stderr; false restores stdout
|
|
59
|
+
*/
|
|
60
|
+
setDiagnosticsToStderr(enabled: boolean): void;
|
|
52
61
|
/**
|
|
53
62
|
* Determines whether a message with the given log level should be processed.
|
|
54
63
|
* This method considers both the configured log level and the current debug mode.
|
|
@@ -222,6 +231,7 @@ export declare const logger: {
|
|
|
222
231
|
table: (data: unknown) => void;
|
|
223
232
|
shouldLog: (level: LogLevel) => boolean;
|
|
224
233
|
setLogLevel: (level: LogLevel) => void;
|
|
234
|
+
setDiagnosticsToStderr: (enabled: boolean) => void;
|
|
225
235
|
getLogs: (level?: LogLevel) => LogEntry[];
|
|
226
236
|
clearLogs: () => void;
|
|
227
237
|
setEventEmitter: (emitter: {
|
package/dist/utils/logger.js
CHANGED
|
@@ -36,11 +36,24 @@ const UPPERCASE_LOG_LEVELS = {
|
|
|
36
36
|
warn: "WARN",
|
|
37
37
|
error: "ERROR",
|
|
38
38
|
};
|
|
39
|
+
const FORMAT_FLAGS = ["--format", "-f", "--output-format"];
|
|
40
|
+
/**
|
|
41
|
+
* True when the process was started with JSON output requested. Read from
|
|
42
|
+
* argv on every write, like the `--debug` check in `shouldLog`: modules log
|
|
43
|
+
* while they are still being imported, before the CLI middleware that calls
|
|
44
|
+
* `setDiagnosticsToStderr` has run.
|
|
45
|
+
*/
|
|
46
|
+
function argvRequestsJsonOutput() {
|
|
47
|
+
const argv = process.argv;
|
|
48
|
+
return argv.some((arg, index) => (FORMAT_FLAGS.includes(arg) && argv[index + 1] === "json") ||
|
|
49
|
+
FORMAT_FLAGS.some((flag) => arg === `${flag}=json`));
|
|
50
|
+
}
|
|
39
51
|
class NeuroLinkLogger {
|
|
40
52
|
logLevel = "info";
|
|
41
53
|
logs = [];
|
|
42
54
|
maxLogs = 1000;
|
|
43
55
|
isDebugMode;
|
|
56
|
+
diagnosticsToStderr = false;
|
|
44
57
|
eventEmitter;
|
|
45
58
|
constructor() {
|
|
46
59
|
// Cache debug mode check to avoid repeated array searches
|
|
@@ -86,6 +99,16 @@ class NeuroLinkLogger {
|
|
|
86
99
|
setLogLevel(level) {
|
|
87
100
|
this.logLevel = level;
|
|
88
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Routes debug and info output to stderr instead of stdout. The CLI turns
|
|
104
|
+
* this on for `--format json`, where stdout carries the payload and any
|
|
105
|
+
* diagnostic line in it would make the output unparseable.
|
|
106
|
+
*
|
|
107
|
+
* @param enabled - True to send debug/info to stderr; false restores stdout
|
|
108
|
+
*/
|
|
109
|
+
setDiagnosticsToStderr(enabled) {
|
|
110
|
+
this.diagnosticsToStderr = enabled;
|
|
111
|
+
}
|
|
89
112
|
/**
|
|
90
113
|
* Determines whether a message with the given log level should be processed.
|
|
91
114
|
* This method considers both the configured log level and the current debug mode.
|
|
@@ -209,9 +232,10 @@ class NeuroLinkLogger {
|
|
|
209
232
|
* @param data - Optional additional data to log.
|
|
210
233
|
*/
|
|
211
234
|
outputToConsole(level, prefix, message, data) {
|
|
235
|
+
const toStderr = this.diagnosticsToStderr || argvRequestsJsonOutput();
|
|
212
236
|
const logMethod = {
|
|
213
|
-
debug: console.debug,
|
|
214
|
-
info: console.info,
|
|
237
|
+
debug: toStderr ? console.error : console.debug,
|
|
238
|
+
info: toStderr ? console.error : console.info,
|
|
215
239
|
warn: console.warn,
|
|
216
240
|
error: console.error,
|
|
217
241
|
}[level];
|
|
@@ -467,6 +491,7 @@ export const logger = {
|
|
|
467
491
|
shouldLog: (level) => neuroLinkLogger.shouldLog(level),
|
|
468
492
|
// Expose structured logging methods
|
|
469
493
|
setLogLevel: (level) => neuroLinkLogger.setLogLevel(level),
|
|
494
|
+
setDiagnosticsToStderr: (enabled) => neuroLinkLogger.setDiagnosticsToStderr(enabled),
|
|
470
495
|
getLogs: (level) => neuroLinkLogger.getLogs(level),
|
|
471
496
|
clearLogs: () => neuroLinkLogger.clearLogs(),
|
|
472
497
|
setEventEmitter: (emitter) => neuroLinkLogger.setEventEmitter(emitter),
|
package/dist/utils/pricing.js
CHANGED
|
@@ -78,8 +78,28 @@ const PRICING = {
|
|
|
78
78
|
// Anthropic (direct API) — updated March 2026
|
|
79
79
|
anthropic: {
|
|
80
80
|
// Claude 5 family. Rates from platform.claude.com/docs/en/about-claude/pricing
|
|
81
|
-
// (checked 2026-
|
|
82
|
-
//
|
|
81
|
+
// (checked 2026-09-23). A 5-minute cache write is 1.25x base input. The cache
|
|
82
|
+
// hit multiplier is 0.1x for most models but 0.05x on Opus 5.5 and 0.025x on
|
|
83
|
+
// Fable/Mythos 5.1, so those three are listed in full rather than left to the
|
|
84
|
+
// longest-prefix fallback — which would silently quote the 5.0 family's rates.
|
|
85
|
+
"claude-fable-5-1": {
|
|
86
|
+
input: 10.0 / 1_000_000,
|
|
87
|
+
output: 50.0 / 1_000_000,
|
|
88
|
+
cacheRead: 0.25 / 1_000_000,
|
|
89
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
90
|
+
},
|
|
91
|
+
"claude-mythos-5-1": {
|
|
92
|
+
input: 10.0 / 1_000_000,
|
|
93
|
+
output: 50.0 / 1_000_000,
|
|
94
|
+
cacheRead: 0.25 / 1_000_000,
|
|
95
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
96
|
+
},
|
|
97
|
+
"claude-opus-5-5": {
|
|
98
|
+
input: 4.0 / 1_000_000,
|
|
99
|
+
output: 20.0 / 1_000_000,
|
|
100
|
+
cacheRead: 0.2 / 1_000_000,
|
|
101
|
+
cacheCreation: 5.0 / 1_000_000,
|
|
102
|
+
},
|
|
83
103
|
"claude-fable-5": {
|
|
84
104
|
input: 10.0 / 1_000_000,
|
|
85
105
|
output: 50.0 / 1_000_000,
|
|
@@ -226,6 +246,53 @@ const PRICING = {
|
|
|
226
246
|
},
|
|
227
247
|
// Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
|
|
228
248
|
vertex: {
|
|
249
|
+
// Claude 5 family. Absent here until now, so a Vertex leg serving any of
|
|
250
|
+
// them resolved to no rate at all and booked the turn at $0 — on the one
|
|
251
|
+
// provider in the chain that bills in real currency.
|
|
252
|
+
// `isVertexAnthropicModel` admits every `claude-*` id, so the whole
|
|
253
|
+
// family needs an entry, not only the ones the chain names today.
|
|
254
|
+
"claude-fable-5-1": {
|
|
255
|
+
input: 10.0 / 1_000_000,
|
|
256
|
+
output: 50.0 / 1_000_000,
|
|
257
|
+
cacheRead: 0.25 / 1_000_000,
|
|
258
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
259
|
+
},
|
|
260
|
+
"claude-mythos-5-1": {
|
|
261
|
+
input: 10.0 / 1_000_000,
|
|
262
|
+
output: 50.0 / 1_000_000,
|
|
263
|
+
cacheRead: 0.25 / 1_000_000,
|
|
264
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
265
|
+
},
|
|
266
|
+
"claude-fable-5": {
|
|
267
|
+
input: 10.0 / 1_000_000,
|
|
268
|
+
output: 50.0 / 1_000_000,
|
|
269
|
+
cacheRead: 1.0 / 1_000_000,
|
|
270
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
271
|
+
},
|
|
272
|
+
"claude-mythos-5": {
|
|
273
|
+
input: 10.0 / 1_000_000,
|
|
274
|
+
output: 50.0 / 1_000_000,
|
|
275
|
+
cacheRead: 1.0 / 1_000_000,
|
|
276
|
+
cacheCreation: 12.5 / 1_000_000,
|
|
277
|
+
},
|
|
278
|
+
"claude-opus-5-5": {
|
|
279
|
+
input: 4.0 / 1_000_000,
|
|
280
|
+
output: 20.0 / 1_000_000,
|
|
281
|
+
cacheRead: 0.2 / 1_000_000,
|
|
282
|
+
cacheCreation: 5.0 / 1_000_000,
|
|
283
|
+
},
|
|
284
|
+
"claude-opus-5": {
|
|
285
|
+
input: 5.0 / 1_000_000,
|
|
286
|
+
output: 25.0 / 1_000_000,
|
|
287
|
+
cacheRead: 0.5 / 1_000_000,
|
|
288
|
+
cacheCreation: 6.25 / 1_000_000,
|
|
289
|
+
},
|
|
290
|
+
"claude-sonnet-5": {
|
|
291
|
+
input: 2.0 / 1_000_000,
|
|
292
|
+
output: 10.0 / 1_000_000,
|
|
293
|
+
cacheRead: 0.2 / 1_000_000,
|
|
294
|
+
cacheCreation: 2.5 / 1_000_000,
|
|
295
|
+
},
|
|
229
296
|
"claude-sonnet-4-6": {
|
|
230
297
|
input: 3.0 / 1_000_000,
|
|
231
298
|
output: 15.0 / 1_000_000,
|