github-router 0.3.273 → 0.3.275
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/{attribution-settings-4nEQq_tn.js → attribution-settings-ZOaCUrYY.js} +4 -4
- package/dist/attribution-settings-ZOaCUrYY.js.map +1 -0
- package/dist/{auth-3so8RT91.js → auth-Q1tfmfCT.js} +2 -2
- package/dist/{auth-3so8RT91.js.map → auth-Q1tfmfCT.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-goXyc3qS.js → check-usage-CHLz4tU_.js} +3 -3
- package/dist/{check-usage-goXyc3qS.js.map → check-usage-CHLz4tU_.js.map} +1 -1
- package/dist/{claude-BNZD-bQN.js → claude-Cl4gYoOZ.js} +7 -7
- package/dist/{claude-BNZD-bQN.js.map → claude-Cl4gYoOZ.js.map} +1 -1
- package/dist/{codex-C_MA9uy5.js → codex-D_IXUgKf.js} +5 -5
- package/dist/{codex-C_MA9uy5.js.map → codex-D_IXUgKf.js.map} +1 -1
- package/dist/engine-Drs3cxLW.js +2 -0
- package/dist/{gate-discovery-DvA4xObh.js → gate-discovery-Br448Md6.js} +2 -2
- package/dist/{gate-discovery-DvA4xObh.js.map → gate-discovery-Br448Md6.js.map} +1 -1
- package/dist/{get-copilot-usage-nZXbvSi8.js → get-copilot-usage-BRN18uMJ.js} +2 -2
- package/dist/{get-copilot-usage-nZXbvSi8.js.map → get-copilot-usage-BRN18uMJ.js.map} +1 -1
- package/dist/{internal-stop-hook-WVgnwt5U.js → internal-stop-hook-DMLQ8vRZ.js} +2 -2
- package/dist/{internal-stop-hook-WVgnwt5U.js.map → internal-stop-hook-DMLQ8vRZ.js.map} +1 -1
- package/dist/main.js +8 -8
- package/dist/{models-BmGGH6pP.js → models-d0gkuYGy.js} +2 -2
- package/dist/{models-BmGGH6pP.js.map → models-d0gkuYGy.js.map} +1 -1
- package/dist/{peer-mcp-personas-DAZU1OfN.js → peer-mcp-personas-TMOZJhfy.js} +566 -105
- package/dist/{peer-mcp-personas-DAZU1OfN.js.map → peer-mcp-personas-TMOZJhfy.js.map} +1 -1
- package/dist/{provision-C3UIA1Tf.js → provision-BJ5vUpmp.js} +2 -2
- package/dist/{provision-C3UIA1Tf.js.map → provision-BJ5vUpmp.js.map} +1 -1
- package/dist/{serve-DPgawWWq.js → serve-DMA82DFJ.js} +6 -6
- package/dist/{serve-DPgawWWq.js.map → serve-DMA82DFJ.js.map} +1 -1
- package/dist/{server-setup-I5KYkuxE.js → server-setup-CVfqSkh_.js} +6 -5
- package/dist/{server-setup-I5KYkuxE.js.map → server-setup-CVfqSkh_.js.map} +1 -1
- package/dist/{start-CWWqjjwm.js → start-BGze5TnZ.js} +3 -3
- package/dist/{start-CWWqjjwm.js.map → start-BGze5TnZ.js.map} +1 -1
- package/dist/{token-Bz7iN8US.js → token-Bz6HoH2H.js} +365 -46
- package/dist/token-Bz6HoH2H.js.map +1 -0
- package/package.json +1 -1
- package/dist/attribution-settings-4nEQq_tn.js.map +0 -1
- package/dist/engine-CHN8j6BV.js +0 -2
- package/dist/token-Bz7iN8US.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as getPackageVersion } from "./version-_Q1WpsQp.js";
|
|
2
2
|
import { t as PATHS } from "./paths-Bg1_DWhi.js";
|
|
3
|
-
import { A as state, C as GITHUB_GRAPHQL_URL, D as githubAgentGraphQLHeaders, E as copilotVersion, O as githubAgentHeaders, S as GITHUB_API_BASE_URL, T as copilotHeaders, b as sanitizeTransportText, d as resolveModel, f as sleep$2, g as HTTPError, h as isAllowedCopilotHost, i as tryRefreshAndRetry, v as classifyTransportError, w as copilotBaseUrl, x as withTransientRetry, y as fetchWithTransientRetry } from "./token-
|
|
3
|
+
import { A as state, C as GITHUB_GRAPHQL_URL, D as githubAgentGraphQLHeaders, E as copilotVersion, O as githubAgentHeaders, S as GITHUB_API_BASE_URL, T as copilotHeaders, b as sanitizeTransportText, d as resolveModel, f as sleep$2, g as HTTPError, h as isAllowedCopilotHost, i as tryRefreshAndRetry, v as classifyTransportError, w as copilotBaseUrl, x as withTransientRetry, y as fetchWithTransientRetry } from "./token-Bz6HoH2H.js";
|
|
4
4
|
import { a as resolveExecutable, c as runManagedExeCapture, i as parseIntEnv, l as spawnTaskkillBestEffort, r as parseBoolEnv } from "./exec-y8C_MU8A.js";
|
|
5
5
|
import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-0XrazT05.js";
|
|
6
6
|
import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-CGJbNeHb.js";
|
|
@@ -19052,14 +19052,42 @@ function currentInFlight() {
|
|
|
19052
19052
|
//#endregion
|
|
19053
19053
|
//#region src/lib/vision-preflight.ts
|
|
19054
19054
|
/**
|
|
19055
|
-
* Outbound vision
|
|
19055
|
+
* Outbound vision handling.
|
|
19056
19056
|
*
|
|
19057
|
-
*
|
|
19058
|
-
*
|
|
19059
|
-
*
|
|
19060
|
-
*
|
|
19061
|
-
*
|
|
19062
|
-
*
|
|
19057
|
+
* WHO DECIDES THE IMAGE LIMIT
|
|
19058
|
+
*
|
|
19059
|
+
* Copilot does, and this module no longer guesses. The catalog advertises a
|
|
19060
|
+
* `max_prompt_images` limit, and that field was measured against the live API
|
|
19061
|
+
* on 2026-08-10 across all 23 vision-capable models:
|
|
19062
|
+
*
|
|
19063
|
+
* - gemini-3.x catalog 10 → upstream enforces exactly 10
|
|
19064
|
+
* - gpt-5.6-sol catalog 1 → upstream enforces 50
|
|
19065
|
+
* - gpt-5.5 catalog 1 → accepted 120, no ceiling found
|
|
19066
|
+
* - claude-opus-5 catalog 1 → accepted 200, no ceiling found
|
|
19067
|
+
* - claude-sonnet/haiku catalog 5 → accepted 32+
|
|
19068
|
+
*
|
|
19069
|
+
* So the field is accurate for one family and understates the truth by 50x to
|
|
19070
|
+
* 200x everywhere else. Note the real ceiling is not even uniform WITHIN a
|
|
19071
|
+
* family: gpt-5.6-sol stops at 50 while gpt-5.5 took 120, which is the sharpest
|
|
19072
|
+
* argument against substituting one hardcoded number for another. A local cardinality reject built on it rejected at 2
|
|
19073
|
+
* what upstream serves at 50, and — because the count was taken over the whole
|
|
19074
|
+
* assembled payload including replayed history — the caller could not act on
|
|
19075
|
+
* the error, so every retry reproduced it and the session was finished.
|
|
19076
|
+
*
|
|
19077
|
+
* Upstream also enforces rules this module does not model at all (minimum pixel
|
|
19078
|
+
* dimensions on grok-4.5, per-model media-type support on gpt-4o), which is the
|
|
19079
|
+
* general argument: local pre-validation cannot be complete, and where it
|
|
19080
|
+
* guesses it is wrong.
|
|
19081
|
+
*
|
|
19082
|
+
* WHAT WE DO INSTEAD
|
|
19083
|
+
*
|
|
19084
|
+
* Send it, and recover from upstream's answer. Copilot's rejections name the
|
|
19085
|
+
* real number ("maximum allowed for model gemini-3.6-flash is 10, got 16",
|
|
19086
|
+
* "Exceeded maximum number of images (50) allowed in the request"), so
|
|
19087
|
+
* `parseUpstreamImageCeiling` reads it, `planOutboundImages` prunes to it, the
|
|
19088
|
+
* transport retries once, and `rememberImageCeiling` keeps it for the process
|
|
19089
|
+
* lifetime so the round trip is paid once per model per boot. The learned value
|
|
19090
|
+
* is an observation rather than a guess, and it self-heals if Copilot changes.
|
|
19063
19091
|
*
|
|
19064
19092
|
* WHY ONE CHOKEPOINT, NOT PER-ADAPTER CHECKS
|
|
19065
19093
|
*
|
|
@@ -19067,38 +19095,82 @@ function currentInFlight() {
|
|
|
19067
19095
|
* block, a block nested inside a `tool_result`, the synthetic follow-up user
|
|
19068
19096
|
* message the shim emits because a tool-output item cannot carry images, images
|
|
19069
19097
|
* already present in replayed conversation history, and peer-critic
|
|
19070
|
-
* attachments.
|
|
19098
|
+
* attachments. Handling this in each adapter means each adapter has to
|
|
19071
19099
|
* rediscover every one of those shapes, and the ones it forgets fail silently.
|
|
19072
|
-
* So
|
|
19073
|
-
*
|
|
19074
|
-
* is therefore total by construction.
|
|
19100
|
+
* So it runs ONCE, on the fully assembled payload, immediately before transport
|
|
19101
|
+
* serialization, and is therefore total by construction.
|
|
19075
19102
|
*
|
|
19076
|
-
*
|
|
19103
|
+
* NOTHING HERE FAILS A REQUEST
|
|
19077
19104
|
*
|
|
19078
|
-
*
|
|
19079
|
-
*
|
|
19080
|
-
* module
|
|
19105
|
+
* A defect that is fatal for a whole request is fatal forever once the image is
|
|
19106
|
+
* in replayed history, because the caller cannot edit history. So every defect
|
|
19107
|
+
* this module recognises drops that one image and replaces it IN PLACE with a
|
|
19108
|
+
* short text note the model can read and relay. In place, never deletion: a
|
|
19109
|
+
* turn whose only content was a screenshot would otherwise become an empty
|
|
19110
|
+
* content array, which upstream does reject.
|
|
19081
19111
|
*
|
|
19082
|
-
*
|
|
19083
|
-
*
|
|
19084
|
-
*
|
|
19085
|
-
*
|
|
19086
|
-
* - model present, vision supported, limits absent → CONSERVATIVE FLOOR
|
|
19087
|
-
* (`FLOOR_MAX_IMAGES` / `FLOOR_MAX_IMAGE_BYTES`), and the message says so,
|
|
19088
|
-
* so the caller can tell a real limit from an assumed one.
|
|
19089
|
-
*
|
|
19090
|
-
* Error strings are returned to callers and end up in front of a model, so they
|
|
19091
|
-
* name the model, the numbers, and the fix — never a stack, a local path, or a
|
|
19092
|
-
* raw catalog object.
|
|
19112
|
+
* Note text carries no ordinals and no running totals. Claude Code replays the
|
|
19113
|
+
* whole transcript every turn, so a note reading "image 1 of 2" would become
|
|
19114
|
+
* "image 1 of 3" on the next image and invalidate the prompt-cache prefix from
|
|
19115
|
+
* the earliest omission onward.
|
|
19093
19116
|
*/
|
|
19094
19117
|
/**
|
|
19095
|
-
*
|
|
19096
|
-
* vision-capable model in the live catalog reports
|
|
19097
|
-
*
|
|
19118
|
+
* Applied when a model advertises vision but publishes no size limit. Every
|
|
19119
|
+
* vision-capable model in the live catalog reports 3 MiB, so this is the
|
|
19120
|
+
* observed value rather than a guess. There is deliberately no image-COUNT
|
|
19121
|
+
* floor: that is the number upstream turned out to own.
|
|
19098
19122
|
*/
|
|
19099
|
-
const FLOOR_MAX_IMAGES = 1;
|
|
19100
19123
|
const FLOOR_MAX_IMAGE_BYTES = 3145728;
|
|
19101
19124
|
/**
|
|
19125
|
+
* Ceilings observed from upstream rejections, keyed by model id. Process
|
|
19126
|
+
* lifetime only, and deliberately not persisted: a stale number on disk would
|
|
19127
|
+
* be exactly the catalog problem again, one indirection further away.
|
|
19128
|
+
*/
|
|
19129
|
+
const learnedCeilings = /* @__PURE__ */ new Map();
|
|
19130
|
+
function learnedImageCeiling(modelId) {
|
|
19131
|
+
return learnedCeilings.get(modelId);
|
|
19132
|
+
}
|
|
19133
|
+
function rememberImageCeiling(modelId, ceiling) {
|
|
19134
|
+
if (!Number.isInteger(ceiling) || ceiling < 1) return;
|
|
19135
|
+
learnedCeilings.set(modelId, ceiling);
|
|
19136
|
+
}
|
|
19137
|
+
/** True when anything has been learned at all — a cheap pre-parse gate. */
|
|
19138
|
+
function hasLearnedImageCeilings() {
|
|
19139
|
+
return learnedCeilings.size > 0;
|
|
19140
|
+
}
|
|
19141
|
+
/**
|
|
19142
|
+
* Both rejection shapes Copilot was observed to emit carry the real ceiling:
|
|
19143
|
+
*
|
|
19144
|
+
* too many images: maximum allowed for model gemini-3.6-flash is 10, got 16
|
|
19145
|
+
* Exceeded maximum number of images (50) allowed in the request.
|
|
19146
|
+
*
|
|
19147
|
+
* Anything else returns `undefined`, and the caller forwards upstream's error
|
|
19148
|
+
* untouched. A parser that guesses would reintroduce the failure this module
|
|
19149
|
+
* was rewritten to remove.
|
|
19150
|
+
*/
|
|
19151
|
+
const CEILING_PATTERNS = [/maximum allowed for model \S+ is (\d+)/i, /exceeded maximum number of images \((\d+)\)/i];
|
|
19152
|
+
function parseUpstreamImageCeiling(body) {
|
|
19153
|
+
if (!/image/i.test(body)) return void 0;
|
|
19154
|
+
for (const pattern of CEILING_PATTERNS) {
|
|
19155
|
+
const match = pattern.exec(body);
|
|
19156
|
+
if (!match) continue;
|
|
19157
|
+
const ceiling = Number(match[1]);
|
|
19158
|
+
if (Number.isInteger(ceiling) && ceiling >= 1 && ceiling <= 1e4) return ceiling;
|
|
19159
|
+
}
|
|
19160
|
+
}
|
|
19161
|
+
/**
|
|
19162
|
+
* Read an error body without consuming it, so the existing error path can still
|
|
19163
|
+
* report the same text downstream. Never throws: a body we cannot read simply
|
|
19164
|
+
* yields no ceiling and upstream's error is forwarded as it always was.
|
|
19165
|
+
*/
|
|
19166
|
+
async function peekErrorBody(response) {
|
|
19167
|
+
try {
|
|
19168
|
+
return await response.clone().text();
|
|
19169
|
+
} catch {
|
|
19170
|
+
return "";
|
|
19171
|
+
}
|
|
19172
|
+
}
|
|
19173
|
+
/**
|
|
19102
19174
|
* Split a `data:<mime>;base64,<payload>` URI. Returns `null` for any other URL
|
|
19103
19175
|
* shape (including a plain remote `https://` reference, which the caller
|
|
19104
19176
|
* handles separately because its bytes are not ours to inspect).
|
|
@@ -19111,45 +19183,162 @@ function parseDataUrl(url) {
|
|
|
19111
19183
|
base64: match[2]
|
|
19112
19184
|
};
|
|
19113
19185
|
}
|
|
19114
|
-
|
|
19186
|
+
/**
|
|
19187
|
+
* Judge one image on its own merits. Returns `undefined` when it is fine.
|
|
19188
|
+
*
|
|
19189
|
+
* Remote-URL images are never judged here: the bytes live on someone else's
|
|
19190
|
+
* server, so any local claim about them would be fiction.
|
|
19191
|
+
*/
|
|
19192
|
+
function imageDefect(image, modelId, maxBytes, assumed, allowedTypes) {
|
|
19193
|
+
if (image.url !== void 0 && image.base64 === void 0) return void 0;
|
|
19194
|
+
if (image.declaredMimeType === void 0 || image.declaredMimeType.length === 0) return {
|
|
19195
|
+
reason: "no media type declared",
|
|
19196
|
+
note: "[image removed: no media type was declared, so it could not be sent]"
|
|
19197
|
+
};
|
|
19198
|
+
if (image.base64 === void 0) return void 0;
|
|
19199
|
+
const bytes = decodeBase64Strict(image.base64);
|
|
19200
|
+
if (!bytes) return {
|
|
19201
|
+
reason: "not valid base64",
|
|
19202
|
+
note: "[image removed: the image data was not valid base64]"
|
|
19203
|
+
};
|
|
19204
|
+
if (bytes.length > maxBytes) return {
|
|
19205
|
+
reason: `${bytes.length} bytes over model ${modelId}'s ${maxBytes}-byte limit${assumed}`,
|
|
19206
|
+
note: `[image removed: it is over this model's ${maxBytes}-byte limit; re-capture at a smaller scale or lower quality]`
|
|
19207
|
+
};
|
|
19208
|
+
const actual = detectImageMimeType(bytes);
|
|
19209
|
+
if (!actual) return {
|
|
19210
|
+
reason: `declared ${image.declaredMimeType} but the bytes are not a supported image`,
|
|
19211
|
+
note: `[image removed: it is declared ${image.declaredMimeType} but its bytes are not a supported image (jpeg, png, webp, gif, heic, heif)]`
|
|
19212
|
+
};
|
|
19213
|
+
if (actual !== image.declaredMimeType) return {
|
|
19214
|
+
reason: `declared ${image.declaredMimeType} but the bytes are ${actual}`,
|
|
19215
|
+
note: `[image removed: it is declared ${image.declaredMimeType} but its bytes are ${actual}; send the correct media type]`
|
|
19216
|
+
};
|
|
19217
|
+
if (allowedTypes && allowedTypes.length > 0 && !allowedTypes.includes(actual)) return {
|
|
19218
|
+
reason: `${actual} is not in model ${modelId}'s accepted media types`,
|
|
19219
|
+
note: `[image removed: ${actual} is not accepted by this model; it accepts ${allowedTypes.join(", ")}]`
|
|
19220
|
+
};
|
|
19221
|
+
}
|
|
19222
|
+
function keepAll(count) {
|
|
19115
19223
|
return {
|
|
19116
|
-
|
|
19117
|
-
|
|
19224
|
+
verdicts: Array.from({ length: count }, () => ({ keep: true })),
|
|
19225
|
+
kept: count,
|
|
19226
|
+
dropped: 0
|
|
19118
19227
|
};
|
|
19119
19228
|
}
|
|
19120
19229
|
/**
|
|
19121
|
-
*
|
|
19122
|
-
* advertised vision capability.
|
|
19230
|
+
* Decide, per image, what goes on the wire.
|
|
19123
19231
|
*
|
|
19124
|
-
*
|
|
19125
|
-
*
|
|
19126
|
-
*
|
|
19232
|
+
* `maxImages` is the CARDINALITY budget, and it comes only from a ceiling
|
|
19233
|
+
* upstream actually stated — never from the catalog, whose `max_prompt_images`
|
|
19234
|
+
* was measured wrong for 20 of 23 models. Omit it and no image is dropped for
|
|
19235
|
+
* being one too many.
|
|
19236
|
+
*
|
|
19237
|
+
* Validity is judged BEFORE the budget so a malformed image cannot consume a
|
|
19238
|
+
* slot and evict a good one, and the budget pass never rewrites a verdict the
|
|
19239
|
+
* validity pass already set — a malformed image must not be reported as a
|
|
19240
|
+
* cardinality eviction.
|
|
19127
19241
|
*/
|
|
19128
|
-
function
|
|
19129
|
-
if (images.length === 0) return {
|
|
19242
|
+
function planOutboundImages(modelId, images, options) {
|
|
19243
|
+
if (images.length === 0) return {
|
|
19244
|
+
verdicts: [],
|
|
19245
|
+
kept: 0,
|
|
19246
|
+
dropped: 0
|
|
19247
|
+
};
|
|
19130
19248
|
const model = state.models?.data.find((m) => m.id === modelId);
|
|
19131
|
-
|
|
19132
|
-
if (
|
|
19133
|
-
|
|
19134
|
-
|
|
19249
|
+
const maxImages = options?.maxImages;
|
|
19250
|
+
if (!model) {
|
|
19251
|
+
if (maxImages === void 0) return keepAll(images.length);
|
|
19252
|
+
}
|
|
19253
|
+
const supports = model?.capabilities?.supports;
|
|
19254
|
+
if (model && supports?.vision !== true) {
|
|
19255
|
+
const note = `[image removed: model ${modelId} does not accept image input; select a vision-capable model to send images]`;
|
|
19256
|
+
return {
|
|
19257
|
+
verdicts: images.map(() => ({
|
|
19258
|
+
keep: false,
|
|
19259
|
+
reason: `model ${modelId} does not support image input`,
|
|
19260
|
+
note
|
|
19261
|
+
})),
|
|
19262
|
+
kept: 0,
|
|
19263
|
+
dropped: images.length
|
|
19264
|
+
};
|
|
19265
|
+
}
|
|
19266
|
+
const limits = model?.capabilities?.limits?.vision;
|
|
19135
19267
|
const maxBytes = limits?.max_prompt_image_size ?? FLOOR_MAX_IMAGE_BYTES;
|
|
19136
|
-
const assumed = limits?.
|
|
19137
|
-
if (images.length > maxImages) return reject(`Model ${modelId} accepts at most ${maxImages} image(s) per request${assumed}, but this request carries ${images.length}. Send fewer images, or use a model with a higher image limit.`);
|
|
19268
|
+
const assumed = limits?.max_prompt_image_size === void 0 ? " (assumed; the model publishes no image size limit)" : "";
|
|
19138
19269
|
const allowedTypes = limits?.supported_media_types;
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
|
|
19150
|
-
|
|
19270
|
+
const verdicts = images.map((image) => {
|
|
19271
|
+
const defect = model ? imageDefect(image, modelId, maxBytes, assumed, allowedTypes) : imageDefect(image, modelId, Number.POSITIVE_INFINITY, "", void 0);
|
|
19272
|
+
return defect ? {
|
|
19273
|
+
keep: false,
|
|
19274
|
+
reason: defect.reason,
|
|
19275
|
+
note: defect.note
|
|
19276
|
+
} : { keep: true };
|
|
19277
|
+
});
|
|
19278
|
+
if (maxImages !== void 0) {
|
|
19279
|
+
const overflowNote = `[image removed: this model accepts at most ${maxImages} image${maxImages === 1 ? "" : "s"} per request, so only the most recent were sent]`;
|
|
19280
|
+
let admitted = 0;
|
|
19281
|
+
for (let index = verdicts.length - 1; index >= 0; index--) {
|
|
19282
|
+
const verdict = verdicts[index];
|
|
19283
|
+
if (!verdict.keep) continue;
|
|
19284
|
+
if (admitted < maxImages) {
|
|
19285
|
+
admitted++;
|
|
19286
|
+
continue;
|
|
19287
|
+
}
|
|
19288
|
+
verdict.keep = false;
|
|
19289
|
+
verdict.reason = `over model ${modelId}'s ${maxImages}-image ceiling`;
|
|
19290
|
+
verdict.note = overflowNote;
|
|
19291
|
+
}
|
|
19151
19292
|
}
|
|
19152
|
-
|
|
19293
|
+
const kept = verdicts.filter((v) => v.keep).length;
|
|
19294
|
+
return {
|
|
19295
|
+
verdicts,
|
|
19296
|
+
kept,
|
|
19297
|
+
dropped: verdicts.length - kept
|
|
19298
|
+
};
|
|
19299
|
+
}
|
|
19300
|
+
/**
|
|
19301
|
+
* Emit one warn line per drop. Rewriting a request body on the hot path with no
|
|
19302
|
+
* record is not acceptable: the note is addressed to the model, and the model
|
|
19303
|
+
* may or may not relay it, so the operator needs their own channel.
|
|
19304
|
+
*/
|
|
19305
|
+
function logImagePlan(modelId, plan) {
|
|
19306
|
+
if (plan.dropped === 0) return;
|
|
19307
|
+
const reasons = [...new Set(plan.verdicts.filter((v) => !v.keep).map((v) => v.reason))];
|
|
19308
|
+
consola.warn(`Dropped ${plan.dropped} of ${plan.verdicts.length} image(s) bound for ${modelId} (${plan.kept} kept): ${reasons.join("; ")}`);
|
|
19309
|
+
}
|
|
19310
|
+
function responsesImageUrl(part) {
|
|
19311
|
+
if (part === null || typeof part !== "object") return void 0;
|
|
19312
|
+
const p = part;
|
|
19313
|
+
if (p.type !== "input_image" || typeof p.image_url !== "string") return void 0;
|
|
19314
|
+
return p.image_url;
|
|
19315
|
+
}
|
|
19316
|
+
function chatImageUrl(part) {
|
|
19317
|
+
if (part === null || typeof part !== "object") return void 0;
|
|
19318
|
+
const p = part;
|
|
19319
|
+
if (p.type !== "image_url") return void 0;
|
|
19320
|
+
const url = p.image_url?.url;
|
|
19321
|
+
return typeof url === "string" ? url : void 0;
|
|
19322
|
+
}
|
|
19323
|
+
/** Anthropic-native `{type:"image", source:{type:"base64", media_type, data}}`. */
|
|
19324
|
+
function anthropicImage(part) {
|
|
19325
|
+
if (part === null || typeof part !== "object") return void 0;
|
|
19326
|
+
const p = part;
|
|
19327
|
+
if (p.type !== "image") return void 0;
|
|
19328
|
+
const source = p.source;
|
|
19329
|
+
if (!source) return void 0;
|
|
19330
|
+
if (typeof source.url === "string" && typeof source.data !== "string") return { url: source.url };
|
|
19331
|
+
return {
|
|
19332
|
+
base64: typeof source.data === "string" ? source.data : void 0,
|
|
19333
|
+
declaredMimeType: typeof source.media_type === "string" ? source.media_type : void 0
|
|
19334
|
+
};
|
|
19335
|
+
}
|
|
19336
|
+
function fromUrl(url) {
|
|
19337
|
+
const parsed = parseDataUrl(url);
|
|
19338
|
+
return parsed ? {
|
|
19339
|
+
base64: parsed.base64,
|
|
19340
|
+
declaredMimeType: parsed.mimeType
|
|
19341
|
+
} : { url };
|
|
19153
19342
|
}
|
|
19154
19343
|
/** Collect the images on an assembled Copilot `/responses` payload. */
|
|
19155
19344
|
function imagesInResponsesPayload(input) {
|
|
@@ -19160,10 +19349,8 @@ function imagesInResponsesPayload(input) {
|
|
|
19160
19349
|
const content = item.content;
|
|
19161
19350
|
if (!Array.isArray(content)) continue;
|
|
19162
19351
|
for (const part of content) {
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
if (p.type !== "input_image" || typeof p.image_url !== "string") continue;
|
|
19166
|
-
images.push(fromUrl(p.image_url));
|
|
19352
|
+
const url = responsesImageUrl(part);
|
|
19353
|
+
if (url !== void 0) images.push(fromUrl(url));
|
|
19167
19354
|
}
|
|
19168
19355
|
}
|
|
19169
19356
|
return images;
|
|
@@ -19177,40 +19364,167 @@ function imagesInChatPayload(messages) {
|
|
|
19177
19364
|
const content = message.content;
|
|
19178
19365
|
if (!Array.isArray(content)) continue;
|
|
19179
19366
|
for (const part of content) {
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
if (p.type !== "image_url") continue;
|
|
19183
|
-
const url = p.image_url?.url;
|
|
19184
|
-
if (typeof url !== "string") continue;
|
|
19185
|
-
images.push(fromUrl(url));
|
|
19367
|
+
const url = chatImageUrl(part);
|
|
19368
|
+
if (url !== void 0) images.push(fromUrl(url));
|
|
19186
19369
|
}
|
|
19187
19370
|
}
|
|
19188
19371
|
return images;
|
|
19189
19372
|
}
|
|
19190
|
-
function fromUrl(url) {
|
|
19191
|
-
const parsed = parseDataUrl(url);
|
|
19192
|
-
return parsed ? {
|
|
19193
|
-
base64: parsed.base64,
|
|
19194
|
-
declaredMimeType: parsed.mimeType
|
|
19195
|
-
} : { url };
|
|
19196
|
-
}
|
|
19197
19373
|
/**
|
|
19198
|
-
*
|
|
19374
|
+
* Visit every Anthropic image part in order, descending into the nested
|
|
19375
|
+
* `content` a `tool_result` carries.
|
|
19199
19376
|
*
|
|
19200
|
-
*
|
|
19201
|
-
*
|
|
19202
|
-
*
|
|
19203
|
-
* rejection would produce, except it names the actual problem and no upstream
|
|
19204
|
-
* request was made. Tests assert that second property: a preflight failure must
|
|
19205
|
-
* cost zero upstream calls.
|
|
19377
|
+
* The extractor and the pruner BOTH drive off this one walker, so their
|
|
19378
|
+
* traversal orders cannot drift apart — which is the failure that would drop
|
|
19379
|
+
* the wrong image.
|
|
19206
19380
|
*/
|
|
19207
|
-
function
|
|
19208
|
-
const
|
|
19209
|
-
if (
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19381
|
+
function walkAnthropicParts(part, visit) {
|
|
19382
|
+
const image = anthropicImage(part);
|
|
19383
|
+
if (image !== void 0) {
|
|
19384
|
+
visit(image);
|
|
19385
|
+
return;
|
|
19386
|
+
}
|
|
19387
|
+
if (part === null || typeof part !== "object") return;
|
|
19388
|
+
const nested = part.content;
|
|
19389
|
+
if (!Array.isArray(nested)) return;
|
|
19390
|
+
for (const inner of nested) walkAnthropicParts(inner, visit);
|
|
19391
|
+
}
|
|
19392
|
+
/** Collect the images on an Anthropic-native `/v1/messages` body. */
|
|
19393
|
+
function imagesInAnthropicMessages(messages) {
|
|
19394
|
+
const images = [];
|
|
19395
|
+
if (!Array.isArray(messages)) return images;
|
|
19396
|
+
for (const message of messages) {
|
|
19397
|
+
if (message === null || typeof message !== "object") continue;
|
|
19398
|
+
const content = message.content;
|
|
19399
|
+
if (!Array.isArray(content)) continue;
|
|
19400
|
+
for (const part of content) walkAnthropicParts(part, (image) => images.push(image));
|
|
19401
|
+
}
|
|
19402
|
+
return images;
|
|
19403
|
+
}
|
|
19404
|
+
/**
|
|
19405
|
+
* Thrown when the pruner's walk does not visit exactly as many images as the
|
|
19406
|
+
* planner judged. Fail loudly: defaulting an unjudged image to "keep" would
|
|
19407
|
+
* turn a traversal-drift bug into a silently leaked image.
|
|
19408
|
+
*/
|
|
19409
|
+
var ImageIndexDriftError = class extends Error {};
|
|
19410
|
+
function assertConsumed(imageIndex, verdicts) {
|
|
19411
|
+
if (imageIndex !== verdicts.length) throw new ImageIndexDriftError(`image traversal drift: pruner visited ${imageIndex} image(s), planner judged ${verdicts.length}`);
|
|
19412
|
+
}
|
|
19413
|
+
/**
|
|
19414
|
+
* Replace each dropped image with a text part IN PLACE, preserving position.
|
|
19415
|
+
* Untouched items and parts keep their original object identity, so nothing is
|
|
19416
|
+
* needlessly copied and the caller's payload is never mutated.
|
|
19417
|
+
*/
|
|
19418
|
+
function pruneImagesFromResponsesInput(input, verdicts) {
|
|
19419
|
+
if (!Array.isArray(input)) return input;
|
|
19420
|
+
let imageIndex = 0;
|
|
19421
|
+
const out = input.map((item) => {
|
|
19422
|
+
if (item === null || typeof item !== "object") return item;
|
|
19423
|
+
const content = item.content;
|
|
19424
|
+
if (!Array.isArray(content)) return item;
|
|
19425
|
+
let replaced = false;
|
|
19426
|
+
const parts = content.map((part) => {
|
|
19427
|
+
if (responsesImageUrl(part) === void 0) return part;
|
|
19428
|
+
const verdict = verdicts[imageIndex++];
|
|
19429
|
+
if (!verdict || verdict.keep) return part;
|
|
19430
|
+
replaced = true;
|
|
19431
|
+
return {
|
|
19432
|
+
type: "input_text",
|
|
19433
|
+
text: verdict.note
|
|
19434
|
+
};
|
|
19435
|
+
});
|
|
19436
|
+
return replaced ? {
|
|
19437
|
+
...item,
|
|
19438
|
+
content: parts
|
|
19439
|
+
} : item;
|
|
19440
|
+
});
|
|
19441
|
+
assertConsumed(imageIndex, verdicts);
|
|
19442
|
+
return out;
|
|
19443
|
+
}
|
|
19444
|
+
function pruneImagesFromChatMessages(messages, verdicts) {
|
|
19445
|
+
if (!Array.isArray(messages)) return messages;
|
|
19446
|
+
let imageIndex = 0;
|
|
19447
|
+
const out = messages.map((message) => {
|
|
19448
|
+
if (message === null || typeof message !== "object") return message;
|
|
19449
|
+
const content = message.content;
|
|
19450
|
+
if (!Array.isArray(content)) return message;
|
|
19451
|
+
let replaced = false;
|
|
19452
|
+
const parts = content.map((part) => {
|
|
19453
|
+
if (chatImageUrl(part) === void 0) return part;
|
|
19454
|
+
const verdict = verdicts[imageIndex++];
|
|
19455
|
+
if (!verdict || verdict.keep) return part;
|
|
19456
|
+
replaced = true;
|
|
19457
|
+
return {
|
|
19458
|
+
type: "text",
|
|
19459
|
+
text: verdict.note
|
|
19460
|
+
};
|
|
19461
|
+
});
|
|
19462
|
+
return replaced ? {
|
|
19463
|
+
...message,
|
|
19464
|
+
content: parts
|
|
19465
|
+
} : message;
|
|
19466
|
+
});
|
|
19467
|
+
assertConsumed(imageIndex, verdicts);
|
|
19468
|
+
return out;
|
|
19469
|
+
}
|
|
19470
|
+
function pruneImagesFromAnthropicMessages(messages, verdicts) {
|
|
19471
|
+
if (!Array.isArray(messages)) return messages;
|
|
19472
|
+
let imageIndex = 0;
|
|
19473
|
+
const replacePart = (part) => {
|
|
19474
|
+
if (anthropicImage(part) !== void 0) {
|
|
19475
|
+
const verdict = verdicts[imageIndex++];
|
|
19476
|
+
if (!verdict || verdict.keep) return {
|
|
19477
|
+
part,
|
|
19478
|
+
replaced: false
|
|
19479
|
+
};
|
|
19480
|
+
return {
|
|
19481
|
+
part: {
|
|
19482
|
+
type: "text",
|
|
19483
|
+
text: verdict.note
|
|
19484
|
+
},
|
|
19485
|
+
replaced: true
|
|
19486
|
+
};
|
|
19487
|
+
}
|
|
19488
|
+
if (part !== null && typeof part === "object") {
|
|
19489
|
+
const nested = part.content;
|
|
19490
|
+
if (Array.isArray(nested)) {
|
|
19491
|
+
let nestedReplaced = false;
|
|
19492
|
+
const inner = nested.map((innerPart) => {
|
|
19493
|
+
const result = replacePart(innerPart);
|
|
19494
|
+
if (result.replaced) nestedReplaced = true;
|
|
19495
|
+
return result.part;
|
|
19496
|
+
});
|
|
19497
|
+
if (nestedReplaced) return {
|
|
19498
|
+
part: {
|
|
19499
|
+
...part,
|
|
19500
|
+
content: inner
|
|
19501
|
+
},
|
|
19502
|
+
replaced: true
|
|
19503
|
+
};
|
|
19504
|
+
}
|
|
19505
|
+
}
|
|
19506
|
+
return {
|
|
19507
|
+
part,
|
|
19508
|
+
replaced: false
|
|
19509
|
+
};
|
|
19510
|
+
};
|
|
19511
|
+
const out = messages.map((message) => {
|
|
19512
|
+
if (message === null || typeof message !== "object") return message;
|
|
19513
|
+
const content = message.content;
|
|
19514
|
+
if (!Array.isArray(content)) return message;
|
|
19515
|
+
let replaced = false;
|
|
19516
|
+
const parts = content.map((part) => {
|
|
19517
|
+
const result = replacePart(part);
|
|
19518
|
+
if (result.replaced) replaced = true;
|
|
19519
|
+
return result.part;
|
|
19520
|
+
});
|
|
19521
|
+
return replaced ? {
|
|
19522
|
+
...message,
|
|
19523
|
+
content: parts
|
|
19524
|
+
} : message;
|
|
19525
|
+
});
|
|
19526
|
+
assertConsumed(imageIndex, verdicts);
|
|
19527
|
+
return out;
|
|
19214
19528
|
}
|
|
19215
19529
|
//#endregion
|
|
19216
19530
|
//#region src/lib/diagnose-response.ts
|
|
@@ -19336,8 +19650,35 @@ async function readResponseBodyCapped(response, routePath, capBytes = MAX_RESPON
|
|
|
19336
19650
|
*/
|
|
19337
19651
|
const createChatCompletions = async (payload, modelHeaders, callerSignal, retryTransient = false) => {
|
|
19338
19652
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
19339
|
-
const
|
|
19340
|
-
|
|
19653
|
+
const carriesImages = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
|
|
19654
|
+
let enableVision = carriesImages;
|
|
19655
|
+
/**
|
|
19656
|
+
* Apply a cardinality budget plus the per-image checks on the fully assembled
|
|
19657
|
+
* payload — the single outbound chokepoint, so every path that can introduce
|
|
19658
|
+
* an image (top-level block, nested tool_result, the shim's synthetic
|
|
19659
|
+
* follow-up user message, replayed history, peer attachments) is covered
|
|
19660
|
+
* without each of them re-deriving the rules.
|
|
19661
|
+
*
|
|
19662
|
+
* `enableVision` is recomputed: if nothing survives, sending
|
|
19663
|
+
* `copilot-vision-request: true` would trade one 400 for another.
|
|
19664
|
+
*/
|
|
19665
|
+
const applyImagePlan = (maxImages) => {
|
|
19666
|
+
try {
|
|
19667
|
+
const plan = planOutboundImages(payload.model, imagesInChatPayload(payload.messages), maxImages === void 0 ? void 0 : { maxImages });
|
|
19668
|
+
if (plan.dropped === 0) return false;
|
|
19669
|
+
logImagePlan(payload.model, plan);
|
|
19670
|
+
payload = {
|
|
19671
|
+
...payload,
|
|
19672
|
+
messages: pruneImagesFromChatMessages(payload.messages, plan.verdicts)
|
|
19673
|
+
};
|
|
19674
|
+
enableVision = plan.kept > 0;
|
|
19675
|
+
return true;
|
|
19676
|
+
} catch (error) {
|
|
19677
|
+
consola.warn(`Image pruning skipped for ${payload.model}: ${String(error)}`);
|
|
19678
|
+
return false;
|
|
19679
|
+
}
|
|
19680
|
+
};
|
|
19681
|
+
if (carriesImages) applyImagePlan(learnedImageCeiling(payload.model));
|
|
19341
19682
|
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
19342
19683
|
const url = `${copilotBaseUrl(state)}/chat/completions`;
|
|
19343
19684
|
const doFetch = () => {
|
|
@@ -19358,10 +19699,21 @@ const createChatCompletions = async (payload, modelHeaders, callerSignal, retryT
|
|
|
19358
19699
|
return fetch(url, fetchInit);
|
|
19359
19700
|
};
|
|
19360
19701
|
const withRefresh = () => tryRefreshAndRetry(doFetch, "/chat/completions");
|
|
19361
|
-
const
|
|
19702
|
+
const send = () => retryTransient ? fetchWithTransientRetry(withRefresh, {
|
|
19362
19703
|
signal: callerSignal,
|
|
19363
19704
|
label: "/chat/completions"
|
|
19364
|
-
}) :
|
|
19705
|
+
}) : withRefresh();
|
|
19706
|
+
let response = await send();
|
|
19707
|
+
if (!response.ok && carriesImages) {
|
|
19708
|
+
const ceiling = parseUpstreamImageCeiling(await peekErrorBody(response));
|
|
19709
|
+
if (ceiling !== void 0) {
|
|
19710
|
+
rememberImageCeiling(payload.model, ceiling);
|
|
19711
|
+
if (applyImagePlan(ceiling)) {
|
|
19712
|
+
response.body?.cancel();
|
|
19713
|
+
response = await send();
|
|
19714
|
+
}
|
|
19715
|
+
}
|
|
19716
|
+
}
|
|
19365
19717
|
if (!response.ok) {
|
|
19366
19718
|
let errorBody;
|
|
19367
19719
|
try {
|
|
@@ -19400,8 +19752,32 @@ const createChatCompletions = async (payload, modelHeaders, callerSignal, retryT
|
|
|
19400
19752
|
*/
|
|
19401
19753
|
const createResponses = async (payload, modelHeaders, callerSignal, retryTransient = false) => {
|
|
19402
19754
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
19403
|
-
const
|
|
19404
|
-
|
|
19755
|
+
const carriesImages = detectVision(payload.input);
|
|
19756
|
+
let enableVision = carriesImages;
|
|
19757
|
+
/**
|
|
19758
|
+
* Apply a cardinality budget plus the per-image checks, rewriting `payload`
|
|
19759
|
+
* in place of the caller's object. Returns whether anything changed.
|
|
19760
|
+
*
|
|
19761
|
+
* `enableVision` is recomputed here: if nothing survives, sending
|
|
19762
|
+
* `copilot-vision-request: true` would trade one 400 for another.
|
|
19763
|
+
*/
|
|
19764
|
+
const applyImagePlan = (maxImages) => {
|
|
19765
|
+
try {
|
|
19766
|
+
const plan = planOutboundImages(payload.model, imagesInResponsesPayload(payload.input), maxImages === void 0 ? void 0 : { maxImages });
|
|
19767
|
+
if (plan.dropped === 0) return false;
|
|
19768
|
+
logImagePlan(payload.model, plan);
|
|
19769
|
+
payload = {
|
|
19770
|
+
...payload,
|
|
19771
|
+
input: pruneImagesFromResponsesInput(payload.input, plan.verdicts)
|
|
19772
|
+
};
|
|
19773
|
+
enableVision = plan.kept > 0;
|
|
19774
|
+
return true;
|
|
19775
|
+
} catch (error) {
|
|
19776
|
+
consola.warn(`Image pruning skipped for ${payload.model}: ${String(error)}`);
|
|
19777
|
+
return false;
|
|
19778
|
+
}
|
|
19779
|
+
};
|
|
19780
|
+
if (carriesImages) applyImagePlan(learnedImageCeiling(payload.model));
|
|
19405
19781
|
const isAgentCall = detectAgentCall(payload.input);
|
|
19406
19782
|
const url = `${copilotBaseUrl(state)}/responses`;
|
|
19407
19783
|
const doFetch = () => {
|
|
@@ -19422,10 +19798,21 @@ const createResponses = async (payload, modelHeaders, callerSignal, retryTransie
|
|
|
19422
19798
|
return fetch(url, fetchInit);
|
|
19423
19799
|
};
|
|
19424
19800
|
const withRefresh = () => tryRefreshAndRetry(doFetch, "/responses");
|
|
19425
|
-
const
|
|
19801
|
+
const send = () => retryTransient ? fetchWithTransientRetry(withRefresh, {
|
|
19426
19802
|
signal: callerSignal,
|
|
19427
19803
|
label: "/responses"
|
|
19428
|
-
}) :
|
|
19804
|
+
}) : withRefresh();
|
|
19805
|
+
let response = await send();
|
|
19806
|
+
if (!response.ok && carriesImages) {
|
|
19807
|
+
const ceiling = parseUpstreamImageCeiling(await peekErrorBody(response));
|
|
19808
|
+
if (ceiling !== void 0) {
|
|
19809
|
+
rememberImageCeiling(payload.model, ceiling);
|
|
19810
|
+
if (applyImagePlan(ceiling)) {
|
|
19811
|
+
response.body?.cancel();
|
|
19812
|
+
response = await send();
|
|
19813
|
+
}
|
|
19814
|
+
}
|
|
19815
|
+
}
|
|
19429
19816
|
if (!response.ok) {
|
|
19430
19817
|
let bodyText;
|
|
19431
19818
|
try {
|
|
@@ -25498,7 +25885,7 @@ function capToolResultText(content, capBytes) {
|
|
|
25498
25885
|
* loaded into memory, let alone base64-expanded by a third.
|
|
25499
25886
|
*
|
|
25500
25887
|
* The per-model check still runs later at the transport boundary
|
|
25501
|
-
* (`
|
|
25888
|
+
* (`planOutboundImages`); this is the cheap guard, not the authority.
|
|
25502
25889
|
*/
|
|
25503
25890
|
const MAX_ATTACHMENT_BYTES = 3145728;
|
|
25504
25891
|
const MAX_TOTAL_ATTACHMENT_BYTES = 12582912;
|
|
@@ -25522,7 +25909,7 @@ const READ_FLAGS = typeof constants.O_NOFOLLOW === "number" ? constants.O_RDONLY
|
|
|
25522
25909
|
async function loadPeerImages(paths, workspace) {
|
|
25523
25910
|
if (paths.length > 10) return {
|
|
25524
25911
|
ok: false,
|
|
25525
|
-
error: `imagePaths: ${paths.length} paths exceeds the 10-image
|
|
25912
|
+
error: `imagePaths: ${paths.length} paths exceeds the 10-image per-call budget. Send fewer, or split across calls.`
|
|
25526
25913
|
};
|
|
25527
25914
|
let workspaceAbs;
|
|
25528
25915
|
try {
|
|
@@ -25857,6 +26244,67 @@ function buildHeaders(extraHeaders) {
|
|
|
25857
26244
|
};
|
|
25858
26245
|
}
|
|
25859
26246
|
/**
|
|
26247
|
+
* Prune the images in a parsed Anthropic request body down to `ceiling` and
|
|
26248
|
+
* re-serialize. Returns `undefined` when nothing needed dropping.
|
|
26249
|
+
*
|
|
26250
|
+
* Never throws. A latent traversal bug in the pruner must not convert an error
|
|
26251
|
+
* we could have forwarded into a proxy crash — the entire point of this change
|
|
26252
|
+
* is that an image problem stops being fatal.
|
|
26253
|
+
*
|
|
26254
|
+
* This is the one place the proxy re-serializes a `/v1/messages` body it would
|
|
26255
|
+
* otherwise forward byte-for-byte, so it inherits the usual JSON round-trip
|
|
26256
|
+
* caveat for integers beyond 2^53. It runs only when a ceiling is in hand:
|
|
26257
|
+
* either one upstream just stated, or one learned earlier in this process.
|
|
26258
|
+
*/
|
|
26259
|
+
function applyCeiling(parsed, ceiling) {
|
|
26260
|
+
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
26261
|
+
try {
|
|
26262
|
+
const plan = planOutboundImages(model, imagesInAnthropicMessages(parsed.messages), { maxImages: ceiling });
|
|
26263
|
+
if (plan.dropped === 0) return void 0;
|
|
26264
|
+
logImagePlan(model, plan);
|
|
26265
|
+
return JSON.stringify({
|
|
26266
|
+
...parsed,
|
|
26267
|
+
messages: pruneImagesFromAnthropicMessages(parsed.messages, plan.verdicts)
|
|
26268
|
+
});
|
|
26269
|
+
} catch (error) {
|
|
26270
|
+
consola.warn(`Image pruning skipped for ${model || "unknown model"}: ${String(error)}`);
|
|
26271
|
+
return;
|
|
26272
|
+
}
|
|
26273
|
+
}
|
|
26274
|
+
function parseBody(body) {
|
|
26275
|
+
try {
|
|
26276
|
+
return JSON.parse(body);
|
|
26277
|
+
} catch {
|
|
26278
|
+
return;
|
|
26279
|
+
}
|
|
26280
|
+
}
|
|
26281
|
+
/** Prune to a ceiling this process already learned for the body's own model. */
|
|
26282
|
+
function pruneToLearnedCeiling(body) {
|
|
26283
|
+
const parsed = parseBody(body);
|
|
26284
|
+
if (!parsed) return void 0;
|
|
26285
|
+
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
26286
|
+
if (model.length === 0) return void 0;
|
|
26287
|
+
const ceiling = learnedImageCeiling(model);
|
|
26288
|
+
return ceiling === void 0 ? void 0 : applyCeiling(parsed, ceiling);
|
|
26289
|
+
}
|
|
26290
|
+
/** Prune to a ceiling upstream just stated, and remember it for next time. */
|
|
26291
|
+
function pruneToStatedCeiling(body, ceiling) {
|
|
26292
|
+
const parsed = parseBody(body);
|
|
26293
|
+
if (!parsed) return void 0;
|
|
26294
|
+
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
26295
|
+
if (model.length > 0) rememberImageCeiling(model, ceiling);
|
|
26296
|
+
return applyCeiling(parsed, ceiling);
|
|
26297
|
+
}
|
|
26298
|
+
/**
|
|
26299
|
+
* Cheap pre-parse gate for the proactive path only. Whitespace-tolerant: a
|
|
26300
|
+
* client that pretty-prints its JSON still matches. Deliberately NOT used to
|
|
26301
|
+
* gate the recovery path — gating recovery on a string probe means a probe miss
|
|
26302
|
+
* silently restores the fatal 400 this whole mechanism removes.
|
|
26303
|
+
*/
|
|
26304
|
+
function mayCarryImages(body) {
|
|
26305
|
+
return /"type"\s*:\s*"image"/.test(body);
|
|
26306
|
+
}
|
|
26307
|
+
/**
|
|
25860
26308
|
* Forward an Anthropic Messages API request to Copilot's native /v1/messages endpoint.
|
|
25861
26309
|
* Returns the raw Response so callers can handle streaming vs non-streaming.
|
|
25862
26310
|
*
|
|
@@ -25879,6 +26327,7 @@ async function createMessages(body, extraHeaders, callerSignal, retryTransient =
|
|
|
25879
26327
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
25880
26328
|
const url = `${copilotBaseUrl(state)}/v1/messages?beta=true`;
|
|
25881
26329
|
consola.debug(`Forwarding to ${url}`);
|
|
26330
|
+
if (hasLearnedImageCeilings() && mayCarryImages(body)) body = pruneToLearnedCeiling(body) ?? body;
|
|
25882
26331
|
const doFetch = () => {
|
|
25883
26332
|
const fetchInit = {
|
|
25884
26333
|
method: "POST",
|
|
@@ -25893,10 +26342,22 @@ async function createMessages(body, extraHeaders, callerSignal, retryTransient =
|
|
|
25893
26342
|
return fetch(url, fetchInit);
|
|
25894
26343
|
};
|
|
25895
26344
|
const withRefresh = () => tryRefreshAndRetry(doFetch, "/v1/messages");
|
|
25896
|
-
const
|
|
26345
|
+
const send = () => retryTransient ? fetchWithTransientRetry(withRefresh, {
|
|
25897
26346
|
signal: callerSignal,
|
|
25898
26347
|
label: "/v1/messages"
|
|
25899
|
-
}) :
|
|
26348
|
+
}) : withRefresh();
|
|
26349
|
+
let response = await send();
|
|
26350
|
+
if (!response.ok) {
|
|
26351
|
+
const ceiling = parseUpstreamImageCeiling(await peekErrorBody(response));
|
|
26352
|
+
if (ceiling !== void 0) {
|
|
26353
|
+
const pruned = pruneToStatedCeiling(body, ceiling);
|
|
26354
|
+
if (pruned !== void 0) {
|
|
26355
|
+
response.body?.cancel();
|
|
26356
|
+
body = pruned;
|
|
26357
|
+
response = await send();
|
|
26358
|
+
}
|
|
26359
|
+
}
|
|
26360
|
+
}
|
|
25900
26361
|
if (!response.ok) {
|
|
25901
26362
|
let errorBody;
|
|
25902
26363
|
try {
|
|
@@ -26461,7 +26922,7 @@ function toolEntries(scope) {
|
|
|
26461
26922
|
imagePaths: {
|
|
26462
26923
|
type: "array",
|
|
26463
26924
|
items: { type: "string" },
|
|
26464
|
-
description: "Optional paths to image files (screenshots, diagrams, charts) INSIDE the workspace for the persona to LOOK AT. The proxy reads and encodes them, so no image data passes through your context. Content is identified by bytes, not extension; jpeg/png/webp/gif/heic/heif, 3 MiB each. Note paths are confined to the proxy's working directory.
|
|
26925
|
+
description: "Optional paths to image files (screenshots, diagrams, charts) INSIDE the workspace for the persona to LOOK AT. The proxy reads and encodes them, so no image data passes through your context. Content is identified by bytes, not extension; jpeg/png/webp/gif/heic/heif, 3 MiB each and 12 MiB in total, up to 10 per call. Note paths are confined to the proxy's working directory."
|
|
26465
26926
|
},
|
|
26466
26927
|
effort: {
|
|
26467
26928
|
type: "string",
|
|
@@ -34598,4 +35059,4 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
|
|
|
34598
35059
|
//#endregion
|
|
34599
35060
|
export { browserToolsEnabled as $, searchWeb as A, collapsePathKeys as At, buildAnthropicErrorEvent as B, upstreamAllowH2 as Bt, buildToolbeltAwareness as C, provisionAndIndexColbert as Ct, TOOLBELT_TOOLS$1 as D, CONDENSED_OPERATING_SEQUENCE as Dt, vscodeRipgrepPath as E, warmTreeSitterPool as Et, isAdvisorRequested as F, DEFAULT_PORT as Ft, relayAnthropicStream as G, isControllerClosedError as H, withOneMSuffix as Ht, formatThinkingRepairDecline as I, UPSTREAM_FETCH_TIMEOUT_MS as It, agentToolsEnabled as J, handleMcpDelete as K, rememberThinkingHistoryRepair as L, UPSTREAM_INACTIVITY_TIMEOUT_MS as Lt, ADVISOR_TOOL_INSTRUCTIONS as M, DEFAULT_CLAUDE_MODEL_FALLBACKS as Mt, buildAdvisorStream as N, DEFAULT_CODEX_MODEL as Nt, assetFor as O, DEFINITION_OF_GREATNESS as Ot, injectAdvisorTool as P, DEFAULT_CODEX_MODEL_FALLBACKS as Pt, browserCompoundToolsEnabled as Q, repairKnownThinkingHistory as R, generateRandomPort as Rt, availableToolCommands as S, colbertDegradedWarning as St, toolbeltSkipSet as T, extractZipMember as Tt, logStreamError as U, withInstallLock as Ut, buildOpenAIErrorEvent as V, upstreamMaxConnections as Vt, readIteratorWithTimeout as W, brainstormModel as X, artifactToolsEnabled as Y, browseAgentEnabled as Z, appendPlanReminder as _, MAX_RESPONSE_BODY_BYTES as _t, buildPeerAwarenessSnippet as a, scribeModel as at, runWorkerAgent as b, provisionBrowserAssets as bt, personasFor as c, shimDefaultsToXhigh as ct, EXPLORE_DEFAULT_MODEL as d, getTokenCount as dt, fleetToolsEnabled as et, EXPLORE_DEFAULT_THINKING as f, assembleResponsesPayload as ft, TEST_DEFAULT_MODEL as g, createChatCompletions as gt, REVIEW_DEFAULT_MODEL as h, createResponses as ht, buildAgentPrompt as i, scoutModel as it, ADVISOR_INTERNAL_TOOL_NAME as j, toolbeltPathOverride as jt, satisfiesMinVersion as k, shouldUseInsecureTls as kt, BROWSE_DEFAULT_MODEL as l, countTokens as lt, PLAN_DEFAULT_MODEL as m, pickEndpoint as mt, MCP_GROUPS as n, nativeSubagentModel as nt, buildPeerAwarenessSummary as o, standInToolEnabled as ot, IMPLEMENT_DEFAULT_MODEL as p, resolveMcpToolTimeoutMs as pt, handleMcpPost as q, assertMcpToolSurfaceConsistent as r, reviewerModel as rt, enumerateInjectedMcpToolNames as s, workerToolsEnabled as st, GROUP_META as t, geminiAvailable as tt, DEFAULT_MODEL as u, createMessages as ut, resolveModeDefaults as v, readResponseBodyCapped as vt, toolbeltEnabled as w, extractTarGzMember as wt, buildEnv as x, hasSupportedBrowserInstalled as xt, resolveWorkerRunOpts as y, parseJsonOrDiagnose as yt, repairRejectedThinkingHistory as z, pickClaudeDefault as zt };
|
|
34600
35061
|
|
|
34601
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
35062
|
+
//# sourceMappingURL=peer-mcp-personas-TMOZJhfy.js.map
|