@dianshuv/copilot-api 0.8.0 → 0.9.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/dist/main.mjs +394 -72
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -1348,7 +1348,7 @@ const patchClaude = defineCommand({
|
|
|
1348
1348
|
|
|
1349
1349
|
//#endregion
|
|
1350
1350
|
//#region package.json
|
|
1351
|
-
var version = "0.
|
|
1351
|
+
var version = "0.9.0";
|
|
1352
1352
|
|
|
1353
1353
|
//#endregion
|
|
1354
1354
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -3042,6 +3042,158 @@ const awaitApproval = async () => {
|
|
|
3042
3042
|
if (!await consola.prompt(`Accept incoming request?`, { type: "confirm" })) throw new HTTPError("Request rejected", 403, JSON.stringify({ message: "Request rejected" }));
|
|
3043
3043
|
};
|
|
3044
3044
|
|
|
3045
|
+
//#endregion
|
|
3046
|
+
//#region src/lib/echo-model.ts
|
|
3047
|
+
/**
|
|
3048
|
+
* Capture the requested model id from a raw request `model` value, classifying
|
|
3049
|
+
* it into the three-state contract. `undefined`/missing → `absent`; `""` →
|
|
3050
|
+
* `empty`; any other string → `present`.
|
|
3051
|
+
*/
|
|
3052
|
+
function captureRequestedModel(rawModel) {
|
|
3053
|
+
if (rawModel === void 0 || rawModel === null) return { kind: "absent" };
|
|
3054
|
+
if (typeof rawModel !== "string") return { kind: "absent" };
|
|
3055
|
+
if (rawModel === "") return { kind: "empty" };
|
|
3056
|
+
return {
|
|
3057
|
+
kind: "present",
|
|
3058
|
+
value: rawModel
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Resolve a {@link RequestedModel} into the action to take on a model field:
|
|
3063
|
+
* - `{ write: true, value }` → set the field to `value`.
|
|
3064
|
+
* - `{ write: false, omit: true }` → remove the field (absent case).
|
|
3065
|
+
* - `null` → leave the field untouched (context-missing case).
|
|
3066
|
+
*/
|
|
3067
|
+
function resolveFieldAction(requested) {
|
|
3068
|
+
if (requested.kind === "present") return { value: requested.value };
|
|
3069
|
+
if (requested.kind === "empty") return { value: "" };
|
|
3070
|
+
if (requested.kind === "absent") return { omit: true };
|
|
3071
|
+
return null;
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Apply the requested-model action to a single `model`-like key on a shallow
|
|
3075
|
+
* clone of `obj`. Returns a new object; never mutates `obj`. If `obj` does not
|
|
3076
|
+
* own `key`, it is returned (cloned) unchanged regardless of the action — we
|
|
3077
|
+
* only ever rewrite a field the upstream payload actually carries (supports
|
|
3078
|
+
* AC-MALFORMED-SSE: no model field → original passthrough).
|
|
3079
|
+
*/
|
|
3080
|
+
function rewriteKey(obj, key, requested) {
|
|
3081
|
+
if (!Object.hasOwn(obj, key)) return obj;
|
|
3082
|
+
const action = resolveFieldAction(requested);
|
|
3083
|
+
if (action === null) return obj;
|
|
3084
|
+
if ("omit" in action) {
|
|
3085
|
+
const { [key]: _omitted, ...rest } = obj;
|
|
3086
|
+
return rest;
|
|
3087
|
+
}
|
|
3088
|
+
return {
|
|
3089
|
+
...obj,
|
|
3090
|
+
[key]: action.value
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Core rewrite over a plain record. Rewrites the documented model fields:
|
|
3095
|
+
* - top-level `model`, top-level `modelVersion` (Gemini),
|
|
3096
|
+
* - nested `message.model` (Anthropic message_start),
|
|
3097
|
+
* - nested `response.model` (OpenAI Responses event).
|
|
3098
|
+
* Returns a shallow clone; never mutates the input.
|
|
3099
|
+
*/
|
|
3100
|
+
function echoRecord(body, requested) {
|
|
3101
|
+
let out = rewriteKey(body, "model", requested);
|
|
3102
|
+
out = rewriteKey(out, "modelVersion", requested);
|
|
3103
|
+
if (isRecord$1(out.message) && Object.hasOwn(out.message, "model")) {
|
|
3104
|
+
const newMessage = rewriteKey(out.message, "model", requested);
|
|
3105
|
+
if (newMessage !== out.message) out = {
|
|
3106
|
+
...out,
|
|
3107
|
+
message: newMessage
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
if (isRecord$1(out.response) && Object.hasOwn(out.response, "model")) {
|
|
3111
|
+
const newResponse = rewriteKey(out.response, "model", requested);
|
|
3112
|
+
if (newResponse !== out.response) out = {
|
|
3113
|
+
...out,
|
|
3114
|
+
response: newResponse
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
return out;
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Rewrite the documented client-facing model field(s) of a JSON response body
|
|
3121
|
+
* to the requested model id. Handles every supported non-stream/body shape:
|
|
3122
|
+
* - top-level `model` (OpenAI chat/completions & Responses bodies, Anthropic
|
|
3123
|
+
* messages body, embeddings),
|
|
3124
|
+
* - nested `message.model` (Anthropic `message_start` event object),
|
|
3125
|
+
* - nested `response.model` (OpenAI Responses streaming event object),
|
|
3126
|
+
* - top-level `modelVersion` (Gemini body & chunk).
|
|
3127
|
+
*
|
|
3128
|
+
* Returns a shallow-cloned object; the input is never mutated. Fields that are
|
|
3129
|
+
* not present are left as-is (a payload with no model field round-trips
|
|
3130
|
+
* unchanged), supporting AC-MALFORMED-SSE.
|
|
3131
|
+
*
|
|
3132
|
+
* The generic is constrained to `object` (not an index-signature shape) so it
|
|
3133
|
+
* accepts the project's domain interfaces (`AnthropicResponse`,
|
|
3134
|
+
* `ChatCompletionChunk`, …) directly without forcing callers to widen them.
|
|
3135
|
+
*/
|
|
3136
|
+
function echoModelInResponseBody(body, requested) {
|
|
3137
|
+
return echoRecord(body, requested);
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Ensure a response body's top-level `model` field equals the requested model
|
|
3141
|
+
* id — **setting it even when the body omits it**. This differs from
|
|
3142
|
+
* {@link echoModelInResponseBody}, which only rewrites a `model` field the
|
|
3143
|
+
* payload already carries (the passthrough rule that protects malformed-SSE /
|
|
3144
|
+
* model-less events). Some upstreams omit `model` from an otherwise-valid
|
|
3145
|
+
* response body (notably the Copilot embeddings endpoint, whose 200 body carries
|
|
3146
|
+
* only `data` + `usage`); for those, the documented client-facing contract is
|
|
3147
|
+
* still "`response.model` == R", so the field must be added, not skipped.
|
|
3148
|
+
*
|
|
3149
|
+
* Three-state per AC-MISSING-MODEL, keyed on the REQUESTER's model (not the
|
|
3150
|
+
* upstream's):
|
|
3151
|
+
* - `present` → set `model` to R (added if absent, overwritten if present).
|
|
3152
|
+
* - `empty` → set `model` to "" (the client sent an empty string).
|
|
3153
|
+
* - `absent` → omit `model` (client sent no model → never invent one); if the
|
|
3154
|
+
* body happened to carry an upstream `model`, drop it.
|
|
3155
|
+
* - `context-missing` → leave the body untouched (defensive passthrough).
|
|
3156
|
+
*
|
|
3157
|
+
* Only the documented client's own string R is ever written — never the upstream
|
|
3158
|
+
* id — so this introduces no side channel (AC-NO-SIDECHANNEL). Returns a shallow
|
|
3159
|
+
* clone; the input is never mutated (protects the AC-OBS data source).
|
|
3160
|
+
*/
|
|
3161
|
+
function echoTopLevelModel(body, requested) {
|
|
3162
|
+
if (requested.kind === "context-missing") return body;
|
|
3163
|
+
const rec = body;
|
|
3164
|
+
if (requested.kind === "absent") {
|
|
3165
|
+
if (!Object.hasOwn(rec, "model")) return body;
|
|
3166
|
+
const { model: _dropped, ...rest } = rec;
|
|
3167
|
+
return rest;
|
|
3168
|
+
}
|
|
3169
|
+
const value = requested.kind === "present" ? requested.value : "";
|
|
3170
|
+
return {
|
|
3171
|
+
...rec,
|
|
3172
|
+
model: value
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
function isRecord$1(value) {
|
|
3176
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3179
|
+
* Rewrite the model field of an already-parsed SSE event payload to the
|
|
3180
|
+
* requested model id, dispatched by protocol shape:
|
|
3181
|
+
* - Anthropic `message_start` → `message.model`,
|
|
3182
|
+
* - OpenAI chunk → top-level `model`,
|
|
3183
|
+
* - OpenAI Responses event → nested `response.model`,
|
|
3184
|
+
* - Gemini chunk → `modelVersion`.
|
|
3185
|
+
*
|
|
3186
|
+
* The field dispatch is identical to {@link echoModelInResponseBody} (both
|
|
3187
|
+
* operate on the same documented model fields), so this delegates to the same
|
|
3188
|
+
* core rather than duplicating the shape logic — the two exports exist to name
|
|
3189
|
+
* the two responsibilities (body vs parsed-event) at call sites, per the PRD's
|
|
3190
|
+
* single-policy-point design. Events with no model field round-trip unchanged
|
|
3191
|
+
* (AC-MALFORMED-SSE); the input is never mutated.
|
|
3192
|
+
*/
|
|
3193
|
+
function echoModelInParsedEvent(event, requested) {
|
|
3194
|
+
return echoRecord(event, requested);
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3045
3197
|
//#endregion
|
|
3046
3198
|
//#region src/lib/message-sanitizer.ts
|
|
3047
3199
|
const startPattern = /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\n*/;
|
|
@@ -3448,13 +3600,39 @@ function isOneMillionSuffixedClaudeId(modelId) {
|
|
|
3448
3600
|
}
|
|
3449
3601
|
|
|
3450
3602
|
//#endregion
|
|
3451
|
-
//#region src/
|
|
3452
|
-
|
|
3603
|
+
//#region src/lib/headers.ts
|
|
3604
|
+
/**
|
|
3605
|
+
* Vendor-neutral header-bag helpers.
|
|
3606
|
+
*
|
|
3607
|
+
* HTTP header names are case-insensitive, but a plain-object header bag is
|
|
3608
|
+
* case-sensitive on its keys. Code that wants to look up "anthropic-beta"
|
|
3609
|
+
* without knowing whether some other producer wrote "Anthropic-Beta" needs
|
|
3610
|
+
* `findHeaderKey`. Code that wants to set a header without creating a
|
|
3611
|
+
* second case variant of the same name needs `setHeader`.
|
|
3612
|
+
*/
|
|
3453
3613
|
/** Case-insensitive lookup of a header key in a plain-object header bag. */
|
|
3454
3614
|
function findHeaderKey(headers, name) {
|
|
3455
3615
|
const lower = name.toLowerCase();
|
|
3456
3616
|
return Object.keys(headers).find((k) => k.toLowerCase() === lower);
|
|
3457
3617
|
}
|
|
3618
|
+
/** Case-insensitive read of a header value. */
|
|
3619
|
+
function getHeader(headers, name) {
|
|
3620
|
+
const key = findHeaderKey(headers, name);
|
|
3621
|
+
return key === void 0 ? void 0 : headers[key];
|
|
3622
|
+
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Set a header value at the existing case variant if one is present, else at
|
|
3625
|
+
* the supplied canonical name. Prevents a second key (different case) from
|
|
3626
|
+
* being added for the same logical header.
|
|
3627
|
+
*/
|
|
3628
|
+
function setHeader(headers, name, value) {
|
|
3629
|
+
const key = findHeaderKey(headers, name) ?? name;
|
|
3630
|
+
headers[key] = value;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
//#endregion
|
|
3634
|
+
//#region src/services/copilot/create-chat-completions.ts
|
|
3635
|
+
const GPT_MODEL_PATTERN = /^gpt-/i;
|
|
3458
3636
|
const createChatCompletions = async (payload, options) => {
|
|
3459
3637
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
3460
3638
|
const vendor = options?.resolvedModel?.vendor;
|
|
@@ -3887,6 +4065,32 @@ function extractErrorMetrics(error) {
|
|
|
3887
4065
|
* Shared utilities for request handlers.
|
|
3888
4066
|
* Contains common functions used by both OpenAI and Anthropic message handlers.
|
|
3889
4067
|
*/
|
|
4068
|
+
/**
|
|
4069
|
+
* Resolve the requested model id (R) carried on the response context into a
|
|
4070
|
+
* {@link RequestedModel}. A context that never captured R (`undefined`) is
|
|
4071
|
+
* treated as `context-missing` so the echo passes the upstream value through
|
|
4072
|
+
* unchanged — it must never invent an id or throw.
|
|
4073
|
+
*/
|
|
4074
|
+
function requestedModelOf(ctx) {
|
|
4075
|
+
return ctx.requestedModel ?? { kind: "context-missing" };
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Echo the requested model id into a JSON response body at the client write-out
|
|
4079
|
+
* boundary. Thin context-aware wrapper over {@link echoModelInResponseBody};
|
|
4080
|
+
* MUST be called AFTER history/posthog/TUI have read the upstream value.
|
|
4081
|
+
*/
|
|
4082
|
+
function echoResponseBody(body, ctx) {
|
|
4083
|
+
return echoModelInResponseBody(body, requestedModelOf(ctx));
|
|
4084
|
+
}
|
|
4085
|
+
/**
|
|
4086
|
+
* Echo the requested model id into an already-parsed SSE event payload at the
|
|
4087
|
+
* client write-out boundary. Thin context-aware wrapper over
|
|
4088
|
+
* {@link echoModelInParsedEvent}; MUST be called AFTER the stream accumulator
|
|
4089
|
+
* (the observability data source) has read the upstream value.
|
|
4090
|
+
*/
|
|
4091
|
+
function echoParsedEvent(event, ctx) {
|
|
4092
|
+
return echoModelInParsedEvent(event, requestedModelOf(ctx));
|
|
4093
|
+
}
|
|
3890
4094
|
/** Helper to update tracker model */
|
|
3891
4095
|
function updateTrackerModel(trackingId, model, resolvedModel) {
|
|
3892
4096
|
if (!trackingId) return;
|
|
@@ -4115,6 +4319,7 @@ function getReasoningTokensFromOpenAIUsage(usage) {
|
|
|
4115
4319
|
async function handleCompletion$1(c) {
|
|
4116
4320
|
const originalPayload = await c.req.json();
|
|
4117
4321
|
consola.debug("Request payload:", JSON.stringify(originalPayload).slice(-400));
|
|
4322
|
+
const requestedModel = captureRequestedModel(originalPayload.model);
|
|
4118
4323
|
const trackingId = c.get("trackingId");
|
|
4119
4324
|
const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
|
|
4120
4325
|
updateTrackerModel(trackingId, originalPayload.model);
|
|
@@ -4131,7 +4336,8 @@ async function handleCompletion$1(c) {
|
|
|
4131
4336
|
temperature: originalPayload.temperature ?? void 0
|
|
4132
4337
|
}),
|
|
4133
4338
|
trackingId,
|
|
4134
|
-
startTime
|
|
4339
|
+
startTime,
|
|
4340
|
+
requestedModel
|
|
4135
4341
|
};
|
|
4136
4342
|
const selectedModel = findModelById(originalPayload.model);
|
|
4137
4343
|
await logTokenCount(originalPayload, selectedModel);
|
|
@@ -4236,7 +4442,7 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx, payload) {
|
|
|
4236
4442
|
reasoningTokens,
|
|
4237
4443
|
stopReason: choice.finish_reason
|
|
4238
4444
|
});
|
|
4239
|
-
return c.json(response);
|
|
4445
|
+
return c.json(echoResponseBody(response, ctx));
|
|
4240
4446
|
}
|
|
4241
4447
|
function buildResponseContent(choice) {
|
|
4242
4448
|
return {
|
|
@@ -4291,15 +4497,14 @@ async function handleStreamingResponse$1(opts) {
|
|
|
4291
4497
|
}]
|
|
4292
4498
|
};
|
|
4293
4499
|
await stream.writeSSE({
|
|
4294
|
-
data: JSON.stringify(markerChunk),
|
|
4500
|
+
data: JSON.stringify(echoParsedEvent(markerChunk, ctx)),
|
|
4295
4501
|
event: "message"
|
|
4296
4502
|
});
|
|
4297
4503
|
acc.content += marker;
|
|
4298
4504
|
}
|
|
4299
4505
|
for await (const chunk of response) {
|
|
4300
4506
|
consola.debug("Streaming chunk:", JSON.stringify(chunk));
|
|
4301
|
-
|
|
4302
|
-
await stream.writeSSE(chunk);
|
|
4507
|
+
await accumulateAndEchoChunk(chunk, acc, checkRepetition, ctx, stream);
|
|
4303
4508
|
}
|
|
4304
4509
|
recordStreamSuccess(acc, payload.model, ctx);
|
|
4305
4510
|
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, acc.reasoningTokens, {
|
|
@@ -4332,45 +4537,70 @@ async function handleStreamingResponse$1(opts) {
|
|
|
4332
4537
|
}]
|
|
4333
4538
|
};
|
|
4334
4539
|
await stream.writeSSE({
|
|
4335
|
-
data: JSON.stringify(markerChunk),
|
|
4540
|
+
data: JSON.stringify(echoParsedEvent(markerChunk, ctx)),
|
|
4336
4541
|
event: "message"
|
|
4337
4542
|
});
|
|
4338
4543
|
} catch {}
|
|
4339
4544
|
}
|
|
4340
4545
|
}
|
|
4341
|
-
function
|
|
4342
|
-
if (!chunk.data || chunk.data === "[DONE]")
|
|
4546
|
+
async function accumulateAndEchoChunk(chunk, acc, checkRepetition, ctx, stream) {
|
|
4547
|
+
if (!chunk.data || chunk.data === "[DONE]") {
|
|
4548
|
+
await stream.writeSSE(chunk);
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4551
|
+
let parsed;
|
|
4343
4552
|
try {
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4553
|
+
parsed = JSON.parse(chunk.data);
|
|
4554
|
+
} catch {
|
|
4555
|
+
await stream.writeSSE(chunk);
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4559
|
+
await stream.writeSSE(chunk);
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
try {
|
|
4563
|
+
accumulateParsedChunk(parsed, acc, checkRepetition);
|
|
4564
|
+
} catch {}
|
|
4565
|
+
if (!Object.hasOwn(parsed, "model")) {
|
|
4566
|
+
await stream.writeSSE(chunk);
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
const echoed = echoParsedEvent(parsed, ctx);
|
|
4570
|
+
await stream.writeSSE({
|
|
4571
|
+
...chunk,
|
|
4572
|
+
data: JSON.stringify(echoed)
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
function accumulateParsedChunk(parsed, acc, checkRepetition) {
|
|
4576
|
+
if (parsed.model && !acc.model) acc.model = parsed.model;
|
|
4577
|
+
if (parsed.usage) {
|
|
4578
|
+
acc.inputTokens = parsed.usage.prompt_tokens;
|
|
4579
|
+
acc.outputTokens = parsed.usage.completion_tokens;
|
|
4580
|
+
acc.reasoningTokens = getReasoningTokensFromOpenAIUsage(parsed.usage) ?? 0;
|
|
4581
|
+
}
|
|
4582
|
+
const choice = parsed.choices?.[0];
|
|
4583
|
+
if (choice) {
|
|
4584
|
+
if (choice.delta?.content) {
|
|
4585
|
+
acc.content += choice.delta.content;
|
|
4586
|
+
checkRepetition(choice.delta.content);
|
|
4587
|
+
}
|
|
4588
|
+
if (choice.delta?.tool_calls) for (const tc of choice.delta.tool_calls) {
|
|
4589
|
+
const idx = tc.index;
|
|
4590
|
+
if (!acc.toolCallMap.has(idx)) acc.toolCallMap.set(idx, {
|
|
4591
|
+
id: tc.id ?? "",
|
|
4592
|
+
name: tc.function?.name ?? "",
|
|
4593
|
+
arguments: ""
|
|
4594
|
+
});
|
|
4595
|
+
const item = acc.toolCallMap.get(idx);
|
|
4596
|
+
if (item) {
|
|
4597
|
+
if (tc.id) item.id = tc.id;
|
|
4598
|
+
if (tc.function?.name) item.name = tc.function.name;
|
|
4599
|
+
if (tc.function?.arguments) item.arguments += tc.function.arguments;
|
|
4370
4600
|
}
|
|
4371
|
-
if (choice.finish_reason) acc.finishReason = choice.finish_reason;
|
|
4372
4601
|
}
|
|
4373
|
-
|
|
4602
|
+
if (choice.finish_reason) acc.finishReason = choice.finish_reason;
|
|
4603
|
+
}
|
|
4374
4604
|
}
|
|
4375
4605
|
function recordStreamSuccess(acc, fallbackModel, ctx) {
|
|
4376
4606
|
for (const tc of acc.toolCallMap.values()) if (tc.id && tc.name) acc.toolCalls.push(tc);
|
|
@@ -4450,10 +4680,30 @@ const createEmbeddings = async (payload) => {
|
|
|
4450
4680
|
//#endregion
|
|
4451
4681
|
//#region src/routes/embeddings/route.ts
|
|
4452
4682
|
const embeddingRoutes = new Hono();
|
|
4683
|
+
function isRecord(value) {
|
|
4684
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4685
|
+
}
|
|
4453
4686
|
embeddingRoutes.post("/", async (c) => {
|
|
4687
|
+
const startTime = Date.now();
|
|
4454
4688
|
try {
|
|
4455
|
-
const
|
|
4456
|
-
|
|
4689
|
+
const rawBody = await c.req.json();
|
|
4690
|
+
const payload = rawBody;
|
|
4691
|
+
const requestedModel = captureRequestedModel(isRecord(rawBody) ? rawBody.model : void 0);
|
|
4692
|
+
const response = await createEmbeddings(payload);
|
|
4693
|
+
if (!isRecord(response)) return c.json(response);
|
|
4694
|
+
const upstreamModel = typeof response.model === "string" ? response.model : "";
|
|
4695
|
+
const usage = response.usage;
|
|
4696
|
+
captureRequest({
|
|
4697
|
+
model: upstreamModel,
|
|
4698
|
+
inputTokens: isRecord(usage) && typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0,
|
|
4699
|
+
outputTokens: 0,
|
|
4700
|
+
durationMs: Date.now() - startTime,
|
|
4701
|
+
success: true,
|
|
4702
|
+
stream: false,
|
|
4703
|
+
toolCount: 0,
|
|
4704
|
+
endpoint: "embeddings"
|
|
4705
|
+
});
|
|
4706
|
+
return c.json(echoTopLevelModel(response, requestedModel));
|
|
4457
4707
|
} catch (error) {
|
|
4458
4708
|
return forwardError(c, error);
|
|
4459
4709
|
}
|
|
@@ -4805,7 +5055,7 @@ function buildUsageMetadata(usage) {
|
|
|
4805
5055
|
|
|
4806
5056
|
//#endregion
|
|
4807
5057
|
//#region src/routes/gemini/handler.ts
|
|
4808
|
-
async function handleGeminiGenerate(c, model, isStream) {
|
|
5058
|
+
async function handleGeminiGenerate(c, model, isStream, requestedModel) {
|
|
4809
5059
|
try {
|
|
4810
5060
|
const geminiRequest = await c.req.json();
|
|
4811
5061
|
consola.debug("Gemini request for model:", model, "stream:", isStream);
|
|
@@ -4830,7 +5080,8 @@ async function handleGeminiGenerate(c, model, isStream) {
|
|
|
4830
5080
|
temperature: payload.temperature ?? void 0
|
|
4831
5081
|
}),
|
|
4832
5082
|
trackingId,
|
|
4833
|
-
startTime
|
|
5083
|
+
startTime,
|
|
5084
|
+
requestedModel
|
|
4834
5085
|
};
|
|
4835
5086
|
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload, { resolvedModel: selectedModel }));
|
|
4836
5087
|
ctx.queueWaitMs = queueWaitMs;
|
|
@@ -4853,7 +5104,7 @@ async function handleGeminiGenerate(c, model, isStream) {
|
|
|
4853
5104
|
continue;
|
|
4854
5105
|
}
|
|
4855
5106
|
const geminiChunks = translateOpenAIChunkToGemini(chunk, streamState);
|
|
4856
|
-
for (const gc of geminiChunks) await s.write(`data: ${JSON.stringify(gc)}\n\n`);
|
|
5107
|
+
for (const gc of geminiChunks) await s.write(`data: ${JSON.stringify(echoParsedEvent(gc, ctx))}\n\n`);
|
|
4857
5108
|
}
|
|
4858
5109
|
recordResponse(ctx.historyId, {
|
|
4859
5110
|
success: true,
|
|
@@ -4921,7 +5172,7 @@ function handleNonStreamResponse(c, response, model, ctx, payload) {
|
|
|
4921
5172
|
stopReason: response.choices[0]?.finish_reason,
|
|
4922
5173
|
toolCount: payload.tools?.length ?? 0
|
|
4923
5174
|
});
|
|
4924
|
-
return c.json(geminiResponse);
|
|
5175
|
+
return c.json(echoResponseBody(geminiResponse, ctx));
|
|
4925
5176
|
}
|
|
4926
5177
|
|
|
4927
5178
|
//#endregion
|
|
@@ -4957,11 +5208,13 @@ geminiRoutes.post("/:modelAction", async (c) => {
|
|
|
4957
5208
|
const modelAction = c.req.param("modelAction");
|
|
4958
5209
|
const colonIndex = modelAction.lastIndexOf(":");
|
|
4959
5210
|
if (colonIndex === -1) return geminiError(c, 400, "INVALID_ARGUMENT", "Missing action in URL");
|
|
4960
|
-
const
|
|
5211
|
+
const rawModel = modelAction.slice(0, Math.max(0, colonIndex));
|
|
5212
|
+
const requestedModel = captureRequestedModel(rawModel);
|
|
5213
|
+
const model = resolveGeminiModelAlias(rawModel);
|
|
4961
5214
|
const action = modelAction.slice(Math.max(0, colonIndex + 1));
|
|
4962
5215
|
switch (action) {
|
|
4963
|
-
case "generateContent": return handleGeminiGenerate(c, model, false);
|
|
4964
|
-
case "streamGenerateContent": return handleGeminiGenerate(c, model, true);
|
|
5216
|
+
case "generateContent": return handleGeminiGenerate(c, model, false, requestedModel);
|
|
5217
|
+
case "streamGenerateContent": return handleGeminiGenerate(c, model, true, requestedModel);
|
|
4965
5218
|
case "countTokens": return handleGeminiCountTokens(c, model);
|
|
4966
5219
|
default: return geminiError(c, 400, "INVALID_ARGUMENT", `Unknown action: ${action}`);
|
|
4967
5220
|
}
|
|
@@ -7121,15 +7374,18 @@ async function createAnthropicMessages(payload, options) {
|
|
|
7121
7374
|
const headers = {
|
|
7122
7375
|
...copilotHeaders(state, {
|
|
7123
7376
|
vision: enableVision,
|
|
7124
|
-
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
7377
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel",
|
|
7378
|
+
modelRequestHeaders: resolvedModel?.request_headers
|
|
7125
7379
|
}),
|
|
7126
7380
|
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
7127
7381
|
"anthropic-version": "2023-06-01"
|
|
7128
7382
|
};
|
|
7129
|
-
const
|
|
7130
|
-
|
|
7131
|
-
if (options?.injectContext1mBeta)
|
|
7132
|
-
if (
|
|
7383
|
+
const proxyBeta = buildAnthropicBetaHeaders(filteredPayload.model, resolvedModel)["anthropic-beta"];
|
|
7384
|
+
let mergedBeta = mergeBetaFeatures(getHeader(headers, "anthropic-beta"), proxyBeta);
|
|
7385
|
+
if (options?.injectContext1mBeta) mergedBeta = appendContext1mBeta(mergedBeta);
|
|
7386
|
+
if (options?.clientAnthropicBetaHeader) mergedBeta = mergeBetaFeatures(mergedBeta, options.clientAnthropicBetaHeader);
|
|
7387
|
+
if (mergedBeta.length > 0) setHeader(headers, "anthropic-beta", mergedBeta);
|
|
7388
|
+
if (!("context_management" in filteredPayload) && isContextEditingEnabled(filteredPayload.model)) {
|
|
7133
7389
|
const hasThinking = filteredPayload.thinking?.type === "enabled";
|
|
7134
7390
|
const cm = buildContextManagement(state.contextEditingMode, hasThinking);
|
|
7135
7391
|
if (cm) {
|
|
@@ -7178,7 +7434,11 @@ function stripServerToolsFromPayload(tools) {
|
|
|
7178
7434
|
}
|
|
7179
7435
|
return result.length > 0 ? result : void 0;
|
|
7180
7436
|
}
|
|
7181
|
-
/**
|
|
7437
|
+
/**
|
|
7438
|
+
* Effective 1M context window. Two ways a request lands on this size:
|
|
7439
|
+
* (a) base model id + context-1m-2025-08-07 beta header (e.g. claude-opus-4.8),
|
|
7440
|
+
* (b) a distinct upstream "-1m-internal" model id (e.g. claude-opus-4.7-1m-internal).
|
|
7441
|
+
*/
|
|
7182
7442
|
const ONE_MILLION_CONTEXT_WINDOW_TOKENS = 1e6;
|
|
7183
7443
|
/**
|
|
7184
7444
|
* Convert a Claude model id from the client-facing dash convention to the
|
|
@@ -7212,7 +7472,17 @@ function resolveAnthropicModelForDirectPath(modelId) {
|
|
|
7212
7472
|
}
|
|
7213
7473
|
if (modelId.endsWith("-1m")) {
|
|
7214
7474
|
const baseId = modelId.slice(0, -3);
|
|
7215
|
-
|
|
7475
|
+
const dottedBaseId = dashToDotClaudeId(baseId);
|
|
7476
|
+
for (const candidateId of [`${baseId}-1m-internal`, `${dottedBaseId}-1m-internal`]) {
|
|
7477
|
+
const internal = findModelById(candidateId);
|
|
7478
|
+
if (internal?.vendor === "Anthropic") return {
|
|
7479
|
+
model: internal,
|
|
7480
|
+
baseModelId: candidateId,
|
|
7481
|
+
oneMillionFallback: false,
|
|
7482
|
+
effectiveContextWindowTokens: ONE_MILLION_CONTEXT_WINDOW_TOKENS
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
for (const candidateId of [baseId, dottedBaseId]) {
|
|
7216
7486
|
const base = findModelById(candidateId);
|
|
7217
7487
|
if (base?.vendor === "Anthropic") return {
|
|
7218
7488
|
model: base,
|
|
@@ -7928,11 +8198,13 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
|
|
|
7928
8198
|
}
|
|
7929
8199
|
} else if (state.autoTruncate && !selectedModel) consola.debug(`[Anthropic] Model '${anthropicPayload.model}' not found, skipping auto-truncate`);
|
|
7930
8200
|
if (state.manualApprove) await awaitApproval();
|
|
8201
|
+
const clientAnthropicBetaHeader = c.req.header("anthropic-beta");
|
|
7931
8202
|
try {
|
|
7932
8203
|
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
|
|
7933
8204
|
initiator: initiatorOverride,
|
|
7934
8205
|
injectContext1mBeta: needsContext1mBeta,
|
|
7935
|
-
errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0
|
|
8206
|
+
errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0,
|
|
8207
|
+
clientAnthropicBetaHeader
|
|
7936
8208
|
}));
|
|
7937
8209
|
ctx.queueWaitMs = queueWaitMs;
|
|
7938
8210
|
if (Symbol.asyncIterator in response) {
|
|
@@ -8022,7 +8294,40 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
|
|
|
8022
8294
|
if (state.verbose && truncateResult?.wasCompacted) finalResponse = prependMarkerToResponse(response, createTruncationMarker$1(truncateResult));
|
|
8023
8295
|
logServerToolBlocks(finalResponse.content);
|
|
8024
8296
|
finalResponse = filterServerToolBlocksFromResponse(finalResponse);
|
|
8025
|
-
return c.json(finalResponse);
|
|
8297
|
+
return c.json(echoResponseBody(finalResponse, ctx));
|
|
8298
|
+
}
|
|
8299
|
+
/**
|
|
8300
|
+
* Echo the requester's original model id into a single already-serialized
|
|
8301
|
+
* native-Anthropic SSE `data:` payload at the write-out boundary.
|
|
8302
|
+
*
|
|
8303
|
+
* In the native Anthropic stream only `message_start` carries a model field
|
|
8304
|
+
* (`message.model`), so every other event type is forwarded byte-for-byte —
|
|
8305
|
+
* this both avoids a needless re-parse per text delta and guarantees events
|
|
8306
|
+
* with no model field round-trip unchanged. For `message_start`, the payload
|
|
8307
|
+
* is parsed and run through the shared echo policy point; the re-serialized
|
|
8308
|
+
* form is returned ONLY when the echo actually rewrote the object (identity
|
|
8309
|
+
* change). When nothing was rewritten — a model-less `message_start`, or
|
|
8310
|
+
* `context-missing` R — the original `forwardData` bytes are returned verbatim
|
|
8311
|
+
* so a passthrough never re-minifies / re-orders the upstream frame. If the
|
|
8312
|
+
* payload fails to parse (malformed upstream frame), the original string is
|
|
8313
|
+
* likewise returned untouched so the stream is never corrupted or interrupted
|
|
8314
|
+
* (AC-MALFORMED-SSE).
|
|
8315
|
+
*
|
|
8316
|
+
* Operates on the serialized output of the server-tool rewrite (not the raw
|
|
8317
|
+
* upstream frame), so the echo composes with any index remap that step made.
|
|
8318
|
+
*/
|
|
8319
|
+
function echoForwardData(forwardData, eventType, ctx) {
|
|
8320
|
+
if (eventType !== "message_start") return forwardData;
|
|
8321
|
+
let parsed;
|
|
8322
|
+
try {
|
|
8323
|
+
parsed = JSON.parse(forwardData);
|
|
8324
|
+
} catch {
|
|
8325
|
+
return forwardData;
|
|
8326
|
+
}
|
|
8327
|
+
if (typeof parsed !== "object" || parsed === null) return forwardData;
|
|
8328
|
+
const echoed = echoParsedEvent(parsed, ctx);
|
|
8329
|
+
if (echoed === parsed) return forwardData;
|
|
8330
|
+
return JSON.stringify(echoed);
|
|
8026
8331
|
}
|
|
8027
8332
|
/**
|
|
8028
8333
|
* Handle streaming direct Anthropic response (passthrough SSE events)
|
|
@@ -8049,9 +8354,10 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
8049
8354
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") checkRepetition(event.delta.text);
|
|
8050
8355
|
const forwardData = serverToolFilter.rewriteEvent(event, rawEvent.data);
|
|
8051
8356
|
if (forwardData === null) continue;
|
|
8357
|
+
const echoedData = echoForwardData(forwardData, event.type, ctx);
|
|
8052
8358
|
await stream.writeSSE({
|
|
8053
8359
|
event: rawEvent.event || event.type,
|
|
8054
|
-
data:
|
|
8360
|
+
data: echoedData
|
|
8055
8361
|
});
|
|
8056
8362
|
}
|
|
8057
8363
|
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
@@ -8231,7 +8537,7 @@ function handleNonStreamingResponse(opts) {
|
|
|
8231
8537
|
toolCount: anthropicPayload.tools?.length ?? 0,
|
|
8232
8538
|
stopReason: anthropicResponse.stop_reason ?? void 0
|
|
8233
8539
|
});
|
|
8234
|
-
return c.json(anthropicResponse);
|
|
8540
|
+
return c.json(echoResponseBody(anthropicResponse, ctx));
|
|
8235
8541
|
}
|
|
8236
8542
|
async function handleStreamingResponse(opts) {
|
|
8237
8543
|
const { stream, response, toolNameMapping, anthropicPayload, ctx } = opts;
|
|
@@ -8255,7 +8561,8 @@ async function handleStreamingResponse(opts) {
|
|
|
8255
8561
|
toolNameMapping,
|
|
8256
8562
|
streamState,
|
|
8257
8563
|
acc,
|
|
8258
|
-
checkRepetition
|
|
8564
|
+
checkRepetition,
|
|
8565
|
+
ctx
|
|
8259
8566
|
});
|
|
8260
8567
|
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
8261
8568
|
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, void 0, {
|
|
@@ -8318,7 +8625,7 @@ async function sendTruncationMarkerEvent(stream, streamState, marker) {
|
|
|
8318
8625
|
streamState.contentBlockIndex++;
|
|
8319
8626
|
}
|
|
8320
8627
|
async function processStreamChunks(opts) {
|
|
8321
|
-
const { stream, response, toolNameMapping, streamState, acc, checkRepetition } = opts;
|
|
8628
|
+
const { stream, response, toolNameMapping, streamState, acc, checkRepetition, ctx } = opts;
|
|
8322
8629
|
for await (const rawEvent of response) {
|
|
8323
8630
|
consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent));
|
|
8324
8631
|
if (rawEvent.data === "[DONE]") break;
|
|
@@ -8336,9 +8643,10 @@ async function processStreamChunks(opts) {
|
|
|
8336
8643
|
consola.debug("Translated Anthropic event:", JSON.stringify(event));
|
|
8337
8644
|
processAnthropicEvent(event, acc);
|
|
8338
8645
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") checkRepetition(event.delta.text);
|
|
8646
|
+
const echoed = echoParsedEvent(event, ctx);
|
|
8339
8647
|
await stream.writeSSE({
|
|
8340
|
-
event:
|
|
8341
|
-
data: JSON.stringify(
|
|
8648
|
+
event: echoed.type,
|
|
8649
|
+
data: JSON.stringify(echoed)
|
|
8342
8650
|
});
|
|
8343
8651
|
}
|
|
8344
8652
|
}
|
|
@@ -8357,6 +8665,7 @@ function resolveModelFromBetaHeader(model, betaHeader) {
|
|
|
8357
8665
|
async function handleCompletion(c) {
|
|
8358
8666
|
const anthropicPayload = await c.req.json();
|
|
8359
8667
|
consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
|
|
8668
|
+
const requestedModel = captureRequestedModel(anthropicPayload.model);
|
|
8360
8669
|
const betaHeader = c.req.header("anthropic-beta");
|
|
8361
8670
|
anthropicPayload.model = resolveModelFromBetaHeader(anthropicPayload.model, betaHeader);
|
|
8362
8671
|
logToolInfo(anthropicPayload);
|
|
@@ -8381,7 +8690,8 @@ async function handleCompletion(c) {
|
|
|
8381
8690
|
system: extractSystemPrompt(anthropicPayload.system)
|
|
8382
8691
|
}),
|
|
8383
8692
|
trackingId,
|
|
8384
|
-
startTime
|
|
8693
|
+
startTime,
|
|
8694
|
+
requestedModel
|
|
8385
8695
|
};
|
|
8386
8696
|
if (useDirectAnthropicApi) return handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiatorOverride);
|
|
8387
8697
|
return handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOverride);
|
|
@@ -8545,13 +8855,23 @@ const createResponses = async (payload, { vision, initiator, resolvedModel }) =>
|
|
|
8545
8855
|
//#endregion
|
|
8546
8856
|
//#region src/routes/responses/stream-id-sync.ts
|
|
8547
8857
|
const createStreamIdTracker = () => ({ outputItems: /* @__PURE__ */ new Map() });
|
|
8548
|
-
|
|
8858
|
+
/**
|
|
8859
|
+
* Rewrite a single Responses SSE event's `data`: synchronize item ids AND echo
|
|
8860
|
+
* the requester's model id into any nested `response.model` the event carries,
|
|
8861
|
+
* from one parse/serialize. Events whose payload has no `response.model` (text
|
|
8862
|
+
* deltas, the `error` event, …) round-trip with their model untouched
|
|
8863
|
+
* (AC-MALFORMED-SSE); unparseable/empty `data` is forwarded byte-for-byte.
|
|
8864
|
+
*
|
|
8865
|
+
* The echo is applied AFTER the handler's history/tracking has read the upstream
|
|
8866
|
+
* values from the same chunks, preserving the observability split (AC-OBS).
|
|
8867
|
+
*/
|
|
8868
|
+
const fixStreamIds = (data, event, tracker, requestedModel) => {
|
|
8549
8869
|
if (!data) return data;
|
|
8550
|
-
const
|
|
8870
|
+
const echoed = echoModelInParsedEvent(JSON.parse(data), requestedModel);
|
|
8551
8871
|
switch (event) {
|
|
8552
|
-
case "response.output_item.added": return handleOutputItemAdded(
|
|
8553
|
-
case "response.output_item.done": return handleOutputItemDone(
|
|
8554
|
-
default: return handleItemId(
|
|
8872
|
+
case "response.output_item.added": return handleOutputItemAdded(echoed, tracker);
|
|
8873
|
+
case "response.output_item.done": return handleOutputItemDone(echoed, tracker);
|
|
8874
|
+
default: return handleItemId(echoed, tracker);
|
|
8555
8875
|
}
|
|
8556
8876
|
};
|
|
8557
8877
|
const handleOutputItemAdded = (parsed, tracker) => {
|
|
@@ -8764,6 +9084,7 @@ const TERMINAL_EVENTS = new Set([
|
|
|
8764
9084
|
]);
|
|
8765
9085
|
const handleResponses = async (c) => {
|
|
8766
9086
|
let payload = await c.req.json();
|
|
9087
|
+
const requestedModel = captureRequestedModel(payload.model);
|
|
8767
9088
|
if (state.normalizeResponsesCallIds) payload = normalizeCallIds(payload);
|
|
8768
9089
|
consola.debug("Responses request payload:", JSON.stringify(payload));
|
|
8769
9090
|
const trackingId = c.get("trackingId");
|
|
@@ -8786,7 +9107,8 @@ const handleResponses = async (c) => {
|
|
|
8786
9107
|
const ctx = {
|
|
8787
9108
|
historyId,
|
|
8788
9109
|
trackingId,
|
|
8789
|
-
startTime
|
|
9110
|
+
startTime,
|
|
9111
|
+
requestedModel
|
|
8790
9112
|
};
|
|
8791
9113
|
const selectedModel = findModelById(payload.model);
|
|
8792
9114
|
if (!(selectedModel?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) {
|
|
@@ -8827,7 +9149,7 @@ const handleResponses = async (c) => {
|
|
|
8827
9149
|
const parsed = JSON.parse(rawData);
|
|
8828
9150
|
if (typeof parsed.sequence_number === "number") lastSequenceNumber = parsed.sequence_number;
|
|
8829
9151
|
} catch {}
|
|
8830
|
-
const processedData = fixStreamIds(rawData, eventType, idTracker);
|
|
9152
|
+
const processedData = fixStreamIds(rawData, eventType, idTracker, requestedModel);
|
|
8831
9153
|
await stream.writeSSE({
|
|
8832
9154
|
id: chunk.id,
|
|
8833
9155
|
event: eventType,
|
|
@@ -8890,7 +9212,7 @@ const handleResponses = async (c) => {
|
|
|
8890
9212
|
toolCount: tools.length
|
|
8891
9213
|
});
|
|
8892
9214
|
consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
|
|
8893
|
-
return c.json(result);
|
|
9215
|
+
return c.json(echoResponseBody(result, ctx));
|
|
8894
9216
|
} catch (error) {
|
|
8895
9217
|
recordErrorResponse(ctx, model, error, "responses", stream);
|
|
8896
9218
|
failTracking(trackingId, error);
|