@juspay/neurolink 12.17.3 → 12.18.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 -7
- package/dist/browser/neurolink.min.js +405 -404
- package/dist/neurolink.js +27 -2
- package/dist/providers/googleAiStudio/client.js +10 -0
- package/dist/proxy/proxyContextPreflight.js +85 -23
- package/dist/proxy/proxyHistoryTruncation.d.ts +24 -0
- package/dist/proxy/proxyHistoryTruncation.js +171 -0
- package/dist/proxy/vertexAnthropicFallback.d.ts +82 -0
- package/dist/proxy/vertexAnthropicFallback.js +339 -0
- package/dist/server/routes/claudeProxyRoutes.js +39 -0
- package/dist/types/proxy.d.ts +22 -0
- package/dist/types/proxyContext.d.ts +19 -1
- package/docs-site/static/search-index.json +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Claude-to-Vertex passthrough.
|
|
3
|
+
*
|
|
4
|
+
* Claude on Vertex speaks the Anthropic Messages API, so a fallback to it needs
|
|
5
|
+
* no translation at all — the client's body goes upstream essentially verbatim
|
|
6
|
+
* and the SSE comes back in the shape the client already expects.
|
|
7
|
+
*
|
|
8
|
+
* This exists because the SDK/translation path cannot carry an agentic turn:
|
|
9
|
+
* it runs its own tool loop, and a request whose tools have no executor (every
|
|
10
|
+
* proxied request, since the caller executes them) ends with the pending tool
|
|
11
|
+
* call discarded and a step-cap apology returned as assistant text. Tools,
|
|
12
|
+
* `tool_use`, `tool_result`, `cache_control` and thinking blocks all survive
|
|
13
|
+
* here because nothing reshapes them.
|
|
14
|
+
*/
|
|
15
|
+
import { GoogleAuth } from "google-auth-library";
|
|
16
|
+
/** Vertex pins the Anthropic API version in the body, not a header. */
|
|
17
|
+
const VERTEX_ANTHROPIC_VERSION = "vertex-2023-10-16";
|
|
18
|
+
const CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
19
|
+
/**
|
|
20
|
+
* Fields the Anthropic first-party API accepts that Vertex's publisher endpoint
|
|
21
|
+
* does not.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately short. `model` moves into the URL, and `context_management` is
|
|
24
|
+
* refused outright ("context_management: Extra inputs are not permitted").
|
|
25
|
+
* Everything else the client sends is carried, including `metadata` and
|
|
26
|
+
* `thinking`, both of which Vertex accepts — dropping them would silently
|
|
27
|
+
* discard caller intent.
|
|
28
|
+
*/
|
|
29
|
+
const UNSUPPORTED_FIELDS = ["model", "context_management"];
|
|
30
|
+
/**
|
|
31
|
+
* Reasoning effort levels Vertex's Claude models accept, and how a level they
|
|
32
|
+
* do not accept maps onto one they do.
|
|
33
|
+
*
|
|
34
|
+
* Claude Code sends `xhigh` for the Claude 5 family; `claude-opus-4-6` refuses
|
|
35
|
+
* it by name ("Supported levels: high, low, max, medium"). Mapping it to the
|
|
36
|
+
* top supported level preserves the caller's intent — run this turn as hard as
|
|
37
|
+
* the model allows — where dropping the field would quietly downgrade it.
|
|
38
|
+
*/
|
|
39
|
+
const VERTEX_EFFORT_BY_LEVEL = {
|
|
40
|
+
low: "low",
|
|
41
|
+
medium: "medium",
|
|
42
|
+
high: "high",
|
|
43
|
+
max: "max",
|
|
44
|
+
xhigh: "max",
|
|
45
|
+
};
|
|
46
|
+
function record(value) {
|
|
47
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Translate `output_config` for Vertex.
|
|
51
|
+
*
|
|
52
|
+
* An effort level Vertex knows passes through; one it does not is mapped onto
|
|
53
|
+
* the nearest level it does. A level with no mapping at all loses only the
|
|
54
|
+
* `effort` key, never the sibling settings beside it.
|
|
55
|
+
*/
|
|
56
|
+
export function mapVertexOutputConfig(value) {
|
|
57
|
+
if (!record(value)) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const effort = value.effort;
|
|
61
|
+
if (typeof effort !== "string") {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
const mapped = VERTEX_EFFORT_BY_LEVEL[effort.toLowerCase()];
|
|
65
|
+
if (mapped !== undefined) {
|
|
66
|
+
return { ...value, effort: mapped };
|
|
67
|
+
}
|
|
68
|
+
const { effort: _unsupported, ...rest } = value;
|
|
69
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
70
|
+
}
|
|
71
|
+
/** Strip what Vertex rejects and pin the API version it requires. */
|
|
72
|
+
export function buildVertexAnthropicPayload(body) {
|
|
73
|
+
const dropped = new Set(UNSUPPORTED_FIELDS);
|
|
74
|
+
const payload = Object.fromEntries(Object.entries(body).filter(([key]) => !dropped.has(key)));
|
|
75
|
+
if ("output_config" in payload) {
|
|
76
|
+
const mapped = mapVertexOutputConfig(payload.output_config);
|
|
77
|
+
if (mapped === undefined) {
|
|
78
|
+
delete payload.output_config;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
payload.output_config = mapped;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ...payload, anthropic_version: VERTEX_ANTHROPIC_VERSION };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Publisher endpoint for a Claude model on Vertex.
|
|
88
|
+
*
|
|
89
|
+
* Streaming and non-streaming are different verbs on the same resource, so the
|
|
90
|
+
* caller's `stream` flag selects the verb rather than changing the body.
|
|
91
|
+
*/
|
|
92
|
+
export function buildVertexAnthropicUrl(target) {
|
|
93
|
+
const verb = target.stream ? "streamRawPredict" : "rawPredict";
|
|
94
|
+
// The global endpoint is not a region: it drops the host prefix entirely,
|
|
95
|
+
// while the path still carries `locations/global`. Prefixing it yields
|
|
96
|
+
// `global-aiplatform.googleapis.com`, which does not resolve to an API.
|
|
97
|
+
const host = target.location === "global"
|
|
98
|
+
? "aiplatform.googleapis.com"
|
|
99
|
+
: `${target.location}-aiplatform.googleapis.com`;
|
|
100
|
+
return (`https://${host}/v1/projects/${target.projectId}/locations/` +
|
|
101
|
+
`${target.location}/publishers/anthropic/models/${target.model}:${verb}`);
|
|
102
|
+
}
|
|
103
|
+
let cachedAuth;
|
|
104
|
+
function auth() {
|
|
105
|
+
cachedAuth ??= new GoogleAuth({ scopes: [CLOUD_PLATFORM_SCOPE] });
|
|
106
|
+
return cachedAuth;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Send one Anthropic-shaped request to Vertex and return the upstream response
|
|
110
|
+
* untouched, so the caller can stream its bytes straight to the client.
|
|
111
|
+
*/
|
|
112
|
+
export async function dispatchVertexAnthropicPassthrough(request) {
|
|
113
|
+
const token = await auth().getAccessToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
throw new Error("Vertex passthrough could not obtain a Google access token; " +
|
|
116
|
+
"check Application Default Credentials for this project");
|
|
117
|
+
}
|
|
118
|
+
const stream = request.body.stream === true;
|
|
119
|
+
const url = buildVertexAnthropicUrl({
|
|
120
|
+
projectId: request.projectId,
|
|
121
|
+
location: request.location,
|
|
122
|
+
model: request.model,
|
|
123
|
+
stream,
|
|
124
|
+
});
|
|
125
|
+
return fetch(url, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
authorization: `Bearer ${token}`,
|
|
129
|
+
"content-type": "application/json",
|
|
130
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(buildVertexAnthropicPayload(request.body)),
|
|
133
|
+
...(request.signal ? { signal: request.signal } : {}),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** Claude models are the only publisher models this passthrough addresses. */
|
|
137
|
+
export function isVertexAnthropicModel(model) {
|
|
138
|
+
return model.toLowerCase().startsWith("claude");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve where Vertex traffic is addressed, using the same environment the
|
|
142
|
+
* Vertex provider reads so a fallback lands on the project and region the rest
|
|
143
|
+
* of the process already uses.
|
|
144
|
+
*/
|
|
145
|
+
export function resolveVertexTarget() {
|
|
146
|
+
const projectId = process.env.GOOGLE_CLOUD_PROJECT ??
|
|
147
|
+
process.env.VERTEX_PROJECT_ID ??
|
|
148
|
+
process.env.GOOGLE_VERTEX_PROJECT ??
|
|
149
|
+
process.env.GOOGLE_CLOUD_PROJECT_ID;
|
|
150
|
+
if (!projectId?.trim()) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
const location = process.env.GOOGLE_CLOUD_LOCATION ??
|
|
154
|
+
process.env.VERTEX_LOCATION ??
|
|
155
|
+
process.env.GOOGLE_VERTEX_LOCATION ??
|
|
156
|
+
"us-central1";
|
|
157
|
+
return { projectId: projectId.trim(), location };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Run one Claude-on-Vertex fallback attempt and hand back a response the route
|
|
161
|
+
* can serve directly.
|
|
162
|
+
*
|
|
163
|
+
* A non-2xx upstream throws so the caller's attempt loop records the failure
|
|
164
|
+
* and moves to the next hop, exactly as the other providers behave.
|
|
165
|
+
*/
|
|
166
|
+
export async function executeVertexAnthropicFallback(args) {
|
|
167
|
+
const target = resolveVertexTarget();
|
|
168
|
+
if (!target) {
|
|
169
|
+
throw new Error("Vertex fallback needs a Google Cloud project; set GOOGLE_CLOUD_PROJECT");
|
|
170
|
+
}
|
|
171
|
+
const upstream = await dispatchVertexAnthropicPassthrough({
|
|
172
|
+
body: args.body,
|
|
173
|
+
projectId: target.projectId,
|
|
174
|
+
location: target.location,
|
|
175
|
+
model: args.model,
|
|
176
|
+
...(args.signal ? { signal: args.signal } : {}),
|
|
177
|
+
});
|
|
178
|
+
if (!upstream.ok) {
|
|
179
|
+
const detail = await upstream.text().catch(() => "");
|
|
180
|
+
throw new Error(`Vertex ${args.model} responded ${upstream.status}: ${detail.slice(0, 300)}`);
|
|
181
|
+
}
|
|
182
|
+
// A non-streaming reply is one JSON object with no `data:` lines, so the SSE
|
|
183
|
+
// observer would read nothing from it and report all-zero usage as if it were
|
|
184
|
+
// real. Parse that shape directly instead, and still hand the client the exact
|
|
185
|
+
// bytes Vertex sent.
|
|
186
|
+
if (args.onTerminal && args.body.stream !== true) {
|
|
187
|
+
const text = await upstream.text();
|
|
188
|
+
args.onTerminal({ usage: readJsonUsage(text), status: upstream.status });
|
|
189
|
+
return new Response(text, {
|
|
190
|
+
status: upstream.status,
|
|
191
|
+
headers: {
|
|
192
|
+
"content-type": upstream.headers.get("content-type") ?? "application/json",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// The body is already Anthropic-shaped, so it is forwarded untouched rather
|
|
197
|
+
// than parsed and re-serialized. Usage is read from a copy of each chunk so
|
|
198
|
+
// this hop is metered like every other one instead of going dark.
|
|
199
|
+
const body = upstream.body && args.onTerminal
|
|
200
|
+
? observeVertexUsage(upstream.body, args.onTerminal)
|
|
201
|
+
: upstream.body;
|
|
202
|
+
return new Response(body, {
|
|
203
|
+
status: upstream.status,
|
|
204
|
+
headers: {
|
|
205
|
+
"content-type": upstream.headers.get("content-type") ??
|
|
206
|
+
(args.body.stream === true ? "text/event-stream" : "application/json"),
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Read token usage out of an Anthropic SSE stream.
|
|
212
|
+
*
|
|
213
|
+
* `message_start` carries the input side and the cache breakdown;
|
|
214
|
+
* `message_delta` carries the running output count, the last of which is the
|
|
215
|
+
* turn's total. Both are accumulated so a passthrough hop reports the same
|
|
216
|
+
* usage as any other, rather than going unmetered.
|
|
217
|
+
*/
|
|
218
|
+
function readUsageEvent(event, into) {
|
|
219
|
+
const message = event.message;
|
|
220
|
+
const usage = (event.type === "message_start" && record(message)
|
|
221
|
+
? message.usage
|
|
222
|
+
: event.usage);
|
|
223
|
+
if (!record(usage)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
227
|
+
if (event.type === "message_start") {
|
|
228
|
+
into.inputTokens = num(usage.input_tokens);
|
|
229
|
+
into.cacheCreationTokens = num(usage.cache_creation_input_tokens);
|
|
230
|
+
into.cacheReadTokens = num(usage.cache_read_input_tokens);
|
|
231
|
+
}
|
|
232
|
+
// Every message_delta restates the running total, so the last one wins.
|
|
233
|
+
const output = num(usage.output_tokens);
|
|
234
|
+
if (output > 0) {
|
|
235
|
+
into.outputTokens = output;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Read usage out of a non-streaming Anthropic reply.
|
|
240
|
+
*
|
|
241
|
+
* `rawPredict` returns a single `{"type":"message","usage":{...}}` object, which
|
|
242
|
+
* carries the whole turn's counts at once rather than splitting them across
|
|
243
|
+
* `message_start` and `message_delta`.
|
|
244
|
+
*/
|
|
245
|
+
export function readJsonUsage(text) {
|
|
246
|
+
const usage = {
|
|
247
|
+
inputTokens: 0,
|
|
248
|
+
outputTokens: 0,
|
|
249
|
+
cacheCreationTokens: 0,
|
|
250
|
+
cacheReadTokens: 0,
|
|
251
|
+
};
|
|
252
|
+
try {
|
|
253
|
+
const parsed = JSON.parse(text);
|
|
254
|
+
readUsageEvent({ ...parsed, type: "message_start", message: parsed }, usage);
|
|
255
|
+
// A single object states the final output count directly.
|
|
256
|
+
const raw = record(parsed.usage) ? parsed.usage.output_tokens : undefined;
|
|
257
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
258
|
+
usage.outputTokens = raw;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// An unparseable body is reported as zero rather than guessed at.
|
|
263
|
+
}
|
|
264
|
+
return usage;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Forward a stream unchanged while observing its usage events.
|
|
268
|
+
*
|
|
269
|
+
* The bytes are passed through untouched — parsing happens on a copy of each
|
|
270
|
+
* chunk — so the client sees exactly what Vertex sent.
|
|
271
|
+
*
|
|
272
|
+
* A transform's `flush` only runs on a clean end, which would leave a stream
|
|
273
|
+
* error or a client disconnect with no terminal record at all. An explicit
|
|
274
|
+
* reader loop is used instead, so `onTerminal` fires exactly once on every
|
|
275
|
+
* exit: normal end, upstream failure, and cancellation.
|
|
276
|
+
*/
|
|
277
|
+
export function observeVertexUsage(body, onTerminal) {
|
|
278
|
+
const usage = {
|
|
279
|
+
inputTokens: 0,
|
|
280
|
+
outputTokens: 0,
|
|
281
|
+
cacheCreationTokens: 0,
|
|
282
|
+
cacheReadTokens: 0,
|
|
283
|
+
};
|
|
284
|
+
const decoder = new TextDecoder();
|
|
285
|
+
const reader = body.getReader();
|
|
286
|
+
let pending = "";
|
|
287
|
+
let reported = false;
|
|
288
|
+
const report = (status, errorMessage) => {
|
|
289
|
+
if (reported) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
reported = true;
|
|
293
|
+
onTerminal({ usage, status, ...(errorMessage ? { errorMessage } : {}) });
|
|
294
|
+
};
|
|
295
|
+
const consume = (chunk) => {
|
|
296
|
+
pending += decoder.decode(chunk, { stream: true });
|
|
297
|
+
const lines = pending.split("\n");
|
|
298
|
+
// Keep the trailing fragment; it completes on a later chunk.
|
|
299
|
+
pending = lines.pop() ?? "";
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
if (!line.startsWith("data:")) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const raw = line.slice(5).trim();
|
|
305
|
+
if (!raw || raw === "[DONE]") {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
readUsageEvent(JSON.parse(raw), usage);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// A partial or non-JSON payload is never fatal to the passthrough.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
return new ReadableStream({
|
|
317
|
+
async pull(controller) {
|
|
318
|
+
try {
|
|
319
|
+
const { done, value } = await reader.read();
|
|
320
|
+
if (done) {
|
|
321
|
+
report(200);
|
|
322
|
+
controller.close();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
consume(value);
|
|
326
|
+
controller.enqueue(value);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
report(502, error instanceof Error ? error.message : String(error));
|
|
330
|
+
controller.error(error);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
cancel(reason) {
|
|
334
|
+
// The client went away mid-stream; tokens already spent still count.
|
|
335
|
+
report(499, reason === undefined ? undefined : String(reason));
|
|
336
|
+
return reader.cancel(reason);
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
@@ -28,6 +28,7 @@ import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quot
|
|
|
28
28
|
import { buildClaudeError, ClaudeStreamSerializer, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
|
|
29
29
|
import { getProxyUpstreamFailure } from "../../proxy/proxyFailureDetails.js";
|
|
30
30
|
import { CodexFallbackResponseError, CodexFallbackStreamError, consumeCodexFallbackResponse, createCodexFallbackStream, convertClaudeRequestToCodex, } from "../../proxy/codexFallback.js";
|
|
31
|
+
import { executeVertexAnthropicFallback, isVertexAnthropicModel, } from "../../proxy/vertexAnthropicFallback.js";
|
|
31
32
|
import { isProxyRequestFinalized, getProxyRequestAccounting, registerProxyResponseObserver, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
|
|
32
33
|
import { buildAnthropicModelsListResponse, buildTranslationOptions, handleTranslatedJsonRequest, handleTranslatedStreamRequest, } from "../../proxy/proxyTranslationEngine.js";
|
|
33
34
|
import { tracers } from "../../telemetry/tracers.js";
|
|
@@ -4046,6 +4047,44 @@ async function tryConfiguredClaudeFallbackChain(args) {
|
|
|
4046
4047
|
logFinalRequest,
|
|
4047
4048
|
});
|
|
4048
4049
|
}
|
|
4050
|
+
else if (fallback.provider === "vertex" &&
|
|
4051
|
+
isVertexAnthropicModel(fallback.model)) {
|
|
4052
|
+
// Claude on Vertex speaks the Anthropic Messages API, so the request
|
|
4053
|
+
// goes up as-is. The SDK path below runs its own tool loop and drops
|
|
4054
|
+
// the caller's pending tool call, which strands every agentic turn.
|
|
4055
|
+
const vertexModel = fallback.model;
|
|
4056
|
+
const vertexAccount = `vertex/${vertexModel}`;
|
|
4057
|
+
response = await executeVertexAnthropicFallback({
|
|
4058
|
+
body,
|
|
4059
|
+
model: vertexModel,
|
|
4060
|
+
...(ctx.abortSignal ? { signal: ctx.abortSignal } : {}),
|
|
4061
|
+
onTerminal: ({ usage, status, errorMessage }) => {
|
|
4062
|
+
// The hop finalizes itself, like the Codex path does. Nothing else
|
|
4063
|
+
// ends this tracer: the route returns before the stream drains, so
|
|
4064
|
+
// without this the root span stays open and cost, request and
|
|
4065
|
+
// duration metrics are never emitted.
|
|
4066
|
+
//
|
|
4067
|
+
// Billing has to follow the model that served the turn. Left on the
|
|
4068
|
+
// requested alias, the pricing lookup misses and the charge is
|
|
4069
|
+
// silently dropped, which is why the Vertex hop had token counters
|
|
4070
|
+
// but no `proxy_cost_usd_total` series at all.
|
|
4071
|
+
tracer?.setModelSubstitution(body.model, vertexModel, "vertex");
|
|
4072
|
+
// Without this the spend lands under the Anthropic account that
|
|
4073
|
+
// never served the turn, hiding Vertex cost inside a subscription
|
|
4074
|
+
// account's figures.
|
|
4075
|
+
tracer?.setServedAccount(vertexAccount, "vertex");
|
|
4076
|
+
// setUsage must precede end(), which is what turns usage into cost.
|
|
4077
|
+
tracer?.setUsage(usage);
|
|
4078
|
+
tracer?.end(status, Date.now() - requestStartTime);
|
|
4079
|
+
logFinalRequest(status, vertexAccount, "vertex", status === 200 ? undefined : "vertex_fallback_stream", errorMessage, {
|
|
4080
|
+
inputTokens: usage.inputTokens,
|
|
4081
|
+
outputTokens: usage.outputTokens,
|
|
4082
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
4083
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
4084
|
+
});
|
|
4085
|
+
},
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4049
4088
|
else {
|
|
4050
4089
|
const availability = await ProviderHealthChecker.checkFallbackProviderAvailability(fallback.provider, fallback.model);
|
|
4051
4090
|
if (!availability.available) {
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -4174,4 +4174,26 @@ export type ProxyServiceInstallSettings = {
|
|
|
4174
4174
|
port?: number;
|
|
4175
4175
|
environment: Record<string, string>;
|
|
4176
4176
|
};
|
|
4177
|
+
/** Where a native Claude-to-Vertex passthrough is addressed. */
|
|
4178
|
+
export type VertexAnthropicPassthroughTarget = {
|
|
4179
|
+
projectId: string;
|
|
4180
|
+
location: string;
|
|
4181
|
+
model: string;
|
|
4182
|
+
stream: boolean;
|
|
4183
|
+
};
|
|
4184
|
+
/** One native Claude-to-Vertex passthrough dispatch. */
|
|
4185
|
+
export type VertexAnthropicPassthroughRequest = {
|
|
4186
|
+
body: Readonly<Record<string, unknown>>;
|
|
4187
|
+
projectId: string;
|
|
4188
|
+
location: string;
|
|
4189
|
+
model: string;
|
|
4190
|
+
signal?: AbortSignal;
|
|
4191
|
+
};
|
|
4192
|
+
/** How a Claude-on-Vertex passthrough ended, so the hop can be finalized once. */
|
|
4193
|
+
export type VertexPassthroughTerminal = {
|
|
4194
|
+
usage: UsageContext;
|
|
4195
|
+
/** 200 served, 499 client cancelled, 502 upstream stream failure. */
|
|
4196
|
+
status: number;
|
|
4197
|
+
errorMessage?: string;
|
|
4198
|
+
};
|
|
4177
4199
|
export {};
|
|
@@ -7,6 +7,10 @@ export type ProxyContextPolicy = {
|
|
|
7
7
|
models?: Record<string, {
|
|
8
8
|
contextWindow: number;
|
|
9
9
|
maxOutputTokens?: number;
|
|
10
|
+
/** Input estimate above which history is reduced. Bounds per-turn cost. */
|
|
11
|
+
compactAtTokens?: number;
|
|
12
|
+
/** Input estimate to reduce to. Must be below compactAtTokens. */
|
|
13
|
+
compactToTokens?: number;
|
|
10
14
|
}>;
|
|
11
15
|
};
|
|
12
16
|
export type ProxyContextEvidence = {
|
|
@@ -24,7 +28,11 @@ export type ProxyContextEvidence = {
|
|
|
24
28
|
multimodalEstimate: boolean;
|
|
25
29
|
originalToolCount: number;
|
|
26
30
|
retainedToolCount: number;
|
|
27
|
-
historyModified:
|
|
31
|
+
historyModified: boolean;
|
|
32
|
+
/** Complete history units dropped by pre-dispatch truncation. */
|
|
33
|
+
historyUnitsRemoved?: number;
|
|
34
|
+
/** Input estimate before truncation, when truncation ran. */
|
|
35
|
+
inputTokensBeforeTruncation?: number;
|
|
28
36
|
};
|
|
29
37
|
export type ProxyPreparedContext<T> = {
|
|
30
38
|
body: T;
|
|
@@ -33,3 +41,13 @@ export type ProxyPreparedContext<T> = {
|
|
|
33
41
|
totalTokensReservation: number;
|
|
34
42
|
evidence: ProxyContextEvidence;
|
|
35
43
|
};
|
|
44
|
+
/** Estimator injected into history truncation so it matches preflight accounting. */
|
|
45
|
+
export type ProxyHistoryEstimate = (value: unknown) => number;
|
|
46
|
+
/** The history container a proxy body carries. */
|
|
47
|
+
export type ProxyHistoryField = "messages" | "input" | "conversationMessages";
|
|
48
|
+
export type ProxyHistoryTruncationResult<T> = {
|
|
49
|
+
body: T;
|
|
50
|
+
historyModified: boolean;
|
|
51
|
+
unitsRemoved: number;
|
|
52
|
+
itemsRemoved: number;
|
|
53
|
+
};
|