github-router 0.3.274 → 0.3.276
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-Cqh_A9yx.js → attribution-settings-VOPvSn6v.js} +65 -49
- package/dist/attribution-settings-VOPvSn6v.js.map +1 -0
- package/dist/{auth-Q1tfmfCT.js → auth-CYoRwhC9.js} +3 -3
- package/dist/{auth-Q1tfmfCT.js.map → auth-CYoRwhC9.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-CHLz4tU_.js → check-usage-DDUyg8ZB.js} +4 -4
- package/dist/{check-usage-CHLz4tU_.js.map → check-usage-DDUyg8ZB.js.map} +1 -1
- package/dist/{claude-CqFkkQGo.js → claude-OTHh7iKz.js} +26 -16
- package/dist/claude-OTHh7iKz.js.map +1 -0
- package/dist/{codex-gGkDJHZJ.js → codex-tCxK30Su.js} +5 -5
- package/dist/{codex-gGkDJHZJ.js.map → codex-tCxK30Su.js.map} +1 -1
- package/dist/{debug-CuuxCk_J.js → debug-CJgsxoVw.js} +2 -2
- package/dist/{debug-CuuxCk_J.js.map → debug-CJgsxoVw.js.map} +1 -1
- package/dist/engine-BKJKMAzh.js +2 -0
- package/dist/{gate-discovery-e114xla0.js → gate-discovery-nf7PGgnS.js} +5 -5
- package/dist/{gate-discovery-e114xla0.js.map → gate-discovery-nf7PGgnS.js.map} +1 -1
- package/dist/{get-copilot-usage-BRN18uMJ.js → get-copilot-usage-D1OAH0EU.js} +2 -2
- package/dist/{get-copilot-usage-BRN18uMJ.js.map → get-copilot-usage-D1OAH0EU.js.map} +1 -1
- package/dist/{internal-artifact-open-CDqPfegE.js → internal-artifact-open-BCz9lnq0.js} +2 -2
- package/dist/{internal-artifact-open-CDqPfegE.js.map → internal-artifact-open-BCz9lnq0.js.map} +1 -1
- package/dist/{internal-plan-review-CH9qTHoZ.js → internal-plan-review-jRMpHu68.js} +3 -3
- package/dist/{internal-plan-review-CH9qTHoZ.js.map → internal-plan-review-jRMpHu68.js.map} +1 -1
- package/dist/{internal-prompt-submit-Bve1s4z_.js → internal-prompt-submit-CqWRFCuv.js} +4 -4
- package/dist/{internal-prompt-submit-Bve1s4z_.js.map → internal-prompt-submit-CqWRFCuv.js.map} +1 -1
- package/dist/{internal-session-bind-Cc8bMXHK.js → internal-session-bind-BiHWDxuC.js} +2 -2
- package/dist/{internal-session-bind-Cc8bMXHK.js.map → internal-session-bind-BiHWDxuC.js.map} +1 -1
- package/dist/{internal-stop-hook-BLPxEImO.js → internal-stop-hook-Ds9LwmZA.js} +5 -5
- package/dist/{internal-stop-hook-BLPxEImO.js.map → internal-stop-hook-Ds9LwmZA.js.map} +1 -1
- package/dist/{internal-stop-review-CWE7_QVv.js → internal-stop-review-BOFj7R3q.js} +2 -2
- package/dist/{internal-stop-review-CWE7_QVv.js.map → internal-stop-review-BOFj7R3q.js.map} +1 -1
- package/dist/{lifecycle-AgNOtFRv.js → lifecycle-AGXnd-ZY.js} +2 -2
- package/dist/{lifecycle-AgNOtFRv.js.map → lifecycle-AGXnd-ZY.js.map} +1 -1
- package/dist/lifecycle-DCrKbaIU.js +2 -0
- package/dist/lifecycle-KKSTKwd6.js +2 -0
- package/dist/{lifecycle-0XrazT05.js → lifecycle-LA4rAuAL.js} +2 -2
- package/dist/{lifecycle-0XrazT05.js.map → lifecycle-LA4rAuAL.js.map} +1 -1
- package/dist/main.js +14 -14
- package/dist/{models-d0gkuYGy.js → models-4Q45Q2Dw.js} +3 -3
- package/dist/{models-d0gkuYGy.js.map → models-4Q45Q2Dw.js.map} +1 -1
- package/dist/{orchestration-B_Q3ncXI.js → orchestration-CpkG8ScN.js} +2 -2
- package/dist/{orchestration-B_Q3ncXI.js.map → orchestration-CpkG8ScN.js.map} +1 -1
- package/dist/paths-DN3Nio42.js +2 -0
- package/dist/{paths-Bg1_DWhi.js → paths-j2B7b0DZ.js} +4 -4
- package/dist/{paths-Bg1_DWhi.js.map → paths-j2B7b0DZ.js.map} +1 -1
- package/dist/{peer-mcp-personas-pzk1zcDa.js → peer-mcp-personas-DRL_xT4h.js} +660 -120
- package/dist/{peer-mcp-personas-pzk1zcDa.js.map → peer-mcp-personas-DRL_xT4h.js.map} +1 -1
- package/dist/{plan-review-hook-BKOcW5s4.js → plan-review-hook-Dk5zTNke.js} +2 -2
- package/dist/{plan-review-hook-BKOcW5s4.js.map → plan-review-hook-Dk5zTNke.js.map} +1 -1
- package/dist/{prompt-submit-hook-F7Zih_au.js → prompt-submit-hook-lvTWwaTV.js} +2 -2
- package/dist/{prompt-submit-hook-F7Zih_au.js.map → prompt-submit-hook-lvTWwaTV.js.map} +1 -1
- package/dist/{provision-BIUvGrMS.js → provision-HzZ547dW.js} +4 -4
- package/dist/{provision-BIUvGrMS.js.map → provision-HzZ547dW.js.map} +1 -1
- package/dist/{serve-BYbEyMoF.js → serve-CM3OmF4I.js} +21 -12
- package/dist/serve-CM3OmF4I.js.map +1 -0
- package/dist/{server-setup-CNKsKERG.js → server-setup-C9r7jYnz.js} +6 -6
- package/dist/{server-setup-CNKsKERG.js.map → server-setup-C9r7jYnz.js.map} +1 -1
- package/dist/{start-Cy_fEtEw.js → start-KAUCsDao.js} +3 -3
- package/dist/{start-Cy_fEtEw.js.map → start-KAUCsDao.js.map} +1 -1
- package/dist/{stop-gate-hook-BqpumJtl.js → stop-gate-hook-CaUrldLh.js} +2 -2
- package/dist/{stop-gate-hook-BqpumJtl.js.map → stop-gate-hook-CaUrldLh.js.map} +1 -1
- package/dist/{stop-gate-policy-C0XOBZhB.js → stop-gate-policy-lAabj_pP.js} +2 -2
- package/dist/{stop-gate-policy-C0XOBZhB.js.map → stop-gate-policy-lAabj_pP.js.map} +1 -1
- package/dist/{token-Bz6HoH2H.js → token-CxPaBEUS.js} +2 -2
- package/dist/{token-Bz6HoH2H.js.map → token-CxPaBEUS.js.map} +1 -1
- package/package.json +1 -1
- package/dist/attribution-settings-Cqh_A9yx.js.map +0 -1
- package/dist/claude-CqFkkQGo.js.map +0 -1
- package/dist/engine-BM2mIXsc.js +0 -2
- package/dist/lifecycle-2M0IThRr.js +0 -2
- package/dist/lifecycle-C8kFsAj-.js +0 -2
- package/dist/paths-BbHtQAOs.js +0 -2
- package/dist/serve-BYbEyMoF.js.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { t as getPackageVersion } from "./version-_Q1WpsQp.js";
|
|
2
|
-
import { t as PATHS } from "./paths-
|
|
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-
|
|
2
|
+
import { t as PATHS } from "./paths-j2B7b0DZ.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-CxPaBEUS.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
|
-
import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-
|
|
5
|
+
import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-LA4rAuAL.js";
|
|
6
6
|
import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-CGJbNeHb.js";
|
|
7
7
|
import { n as ArtifactError, r as applyInsecureTls, t as ArtifactClient } from "./client-CQMfroGV.js";
|
|
8
|
-
import { n as isPidAlive$1, r as registerColbertExitHandlers, s as trackChild, t as getColbertInstanceUuid } from "./lifecycle-
|
|
9
|
-
import { h as runGateChecks, m as sealedGateIds, p as resolveSealedGate } from "./stop-gate-hook-
|
|
10
|
-
import { t as liveExec } from "./orchestration-
|
|
8
|
+
import { n as isPidAlive$1, r as registerColbertExitHandlers, s as trackChild, t as getColbertInstanceUuid } from "./lifecycle-AGXnd-ZY.js";
|
|
9
|
+
import { h as runGateChecks, m as sealedGateIds, p as resolveSealedGate } from "./stop-gate-hook-CaUrldLh.js";
|
|
10
|
+
import { t as liveExec } from "./orchestration-CpkG8ScN.js";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import consola from "consola";
|
|
13
13
|
import { chmodSync, closeSync, constants, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
@@ -18470,7 +18470,7 @@ function logAudit$1(record) {
|
|
|
18470
18470
|
try {
|
|
18471
18471
|
const fs = await import("node:fs/promises");
|
|
18472
18472
|
const path = await import("node:path");
|
|
18473
|
-
const { PATHS } = await import("./paths-
|
|
18473
|
+
const { PATHS } = await import("./paths-DN3Nio42.js");
|
|
18474
18474
|
const dir = path.join(PATHS.APP_DIR, "browser-mcp");
|
|
18475
18475
|
await fs.mkdir(dir, { recursive: true });
|
|
18476
18476
|
const line = JSON.stringify({
|
|
@@ -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
|
-
*
|
|
19230
|
+
* Decide, per image, what goes on the wire.
|
|
19231
|
+
*
|
|
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.
|
|
19123
19236
|
*
|
|
19124
|
-
*
|
|
19125
|
-
*
|
|
19126
|
-
*
|
|
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 {
|
|
@@ -26040,13 +26501,23 @@ function geminiAvailable(source = state) {
|
|
|
26040
26501
|
/**
|
|
26041
26502
|
* First id in `chain` that is present in the live catalog. With
|
|
26042
26503
|
* `requireToolCalls`, skips an entry whose catalog record does not advertise
|
|
26043
|
-
* `tool_calls` (strict `!== true`, so absent metadata fails closed).
|
|
26044
|
-
*
|
|
26045
|
-
*
|
|
26504
|
+
* `tool_calls` (strict `!== true`, so absent metadata fails closed). With
|
|
26505
|
+
* `minContextTokens`, skips an entry whose advertised context window is below
|
|
26506
|
+
* that floor (absent metadata likewise fails closed). Returns undefined when
|
|
26507
|
+
* the catalog is unavailable or nothing in the chain matches, so every caller
|
|
26508
|
+
* degrades gracefully rather than throwing on a thin catalog.
|
|
26046
26509
|
*
|
|
26047
26510
|
* Extracted from `resolveOpenAiFrontier` so the per-agent resolvers below share
|
|
26048
26511
|
* one walk instead of hand-copying it. Ids are matched EXACTLY against
|
|
26049
26512
|
* `catalog.id` — no slug translation, matching the pre-existing behavior.
|
|
26513
|
+
*
|
|
26514
|
+
* `minContextTokens` is OPT-IN because the two constraints are genuinely
|
|
26515
|
+
* per-agent: the `generic*` chains promise 1M end to end, while `scoutModel`
|
|
26516
|
+
* deliberately keeps a 400K last resort for its wider availability. Enforcing
|
|
26517
|
+
* the floor here rather than by comment is what stops a chain silently
|
|
26518
|
+
* degrading when an id's advertised window shrinks upstream — `withOneMSuffix`
|
|
26519
|
+
* would then just omit the `[1m]` bracket, and the agent would be budgeted at
|
|
26520
|
+
* Claude Code's 200K default with no signal that anything changed.
|
|
26050
26521
|
*/
|
|
26051
26522
|
function firstPresentInCatalog(chain, opts) {
|
|
26052
26523
|
const models = state.models?.data;
|
|
@@ -26055,6 +26526,7 @@ function firstPresentInCatalog(chain, opts) {
|
|
|
26055
26526
|
const found = models.find((m) => m.id === id);
|
|
26056
26527
|
if (!found) continue;
|
|
26057
26528
|
if (opts?.requireToolCalls && found.capabilities?.supports?.tool_calls !== true) continue;
|
|
26529
|
+
if (opts?.minContextTokens != null && (found.capabilities?.limits?.max_context_window_tokens ?? 0) < opts.minContextTokens) continue;
|
|
26058
26530
|
return id;
|
|
26059
26531
|
}
|
|
26060
26532
|
}
|
|
@@ -26134,9 +26606,66 @@ function scribeModel() {
|
|
|
26134
26606
|
* caller drop the agent instead, so the lead falls back to the CLI's `Explore`
|
|
26135
26607
|
* (same behavior as before `scout` existed) rather than to an expensive
|
|
26136
26608
|
* impostor wearing the cheap agent's name.
|
|
26609
|
+
*
|
|
26610
|
+
* `gpt-5.6-luna` sits between the two originals because the old chain fell
|
|
26611
|
+
* straight from a 1M model to 400K `gpt-5.4-mini`, which loses the `[1m]`
|
|
26612
|
+
* bracket and drops Claude Code's accounting to its 200K default. Luna is
|
|
26613
|
+
* cheaper than mini, keeps 1M, and is cross-vendor from the primary, so it
|
|
26614
|
+
* covers a Gemini-side outage that a same-vendor entry would not.
|
|
26615
|
+
*
|
|
26616
|
+
* Deliberately NO `minContextTokens` floor, unlike the `generic*` resolvers:
|
|
26617
|
+
* `gpt-5.4-mini` is retained as the last resort precisely BECAUSE it is the
|
|
26618
|
+
* widest-availability id here (its `restricted_to` includes `individual_trial`
|
|
26619
|
+
* and `edu`, which neither flash nor luna does). On a thin non-enterprise
|
|
26620
|
+
* catalog a 400K scout beats no scout.
|
|
26137
26621
|
*/
|
|
26138
26622
|
function scoutModel() {
|
|
26139
|
-
return firstPresentInCatalog([
|
|
26623
|
+
return firstPresentInCatalog([
|
|
26624
|
+
EXPLORE_DEFAULT_MODEL,
|
|
26625
|
+
"gpt-5.6-luna",
|
|
26626
|
+
DEFAULT_MODEL
|
|
26627
|
+
], { requireToolCalls: true });
|
|
26628
|
+
}
|
|
26629
|
+
/** Model for `generic` — the mid-tier catch-all. Absent → the agent is dropped.
|
|
26630
|
+
*
|
|
26631
|
+
* `gpt-5.6-sol` is deliberately NOT in this chain: the OpenAI frontier coder is
|
|
26632
|
+
* already `implementer`'s job, and a catch-all that quietly costs frontier
|
|
26633
|
+
* rates is the opposite of what this agent is for. Both entries are 1M+ and
|
|
26634
|
+
* mid-to-high capability, which is the most the description may claim. */
|
|
26635
|
+
function genericModel() {
|
|
26636
|
+
return firstPresentInCatalog(["gpt-5.6-terra", REVIEW_DEFAULT_MODEL], {
|
|
26637
|
+
requireToolCalls: true,
|
|
26638
|
+
minContextTokens: ONE_M_TOKENS
|
|
26639
|
+
});
|
|
26640
|
+
}
|
|
26641
|
+
/** Model for `generic-fast` — the Gemini flash tier. Absent → dropped.
|
|
26642
|
+
*
|
|
26643
|
+
* Both entries are the same vendor, context, price point and `minimal..high`
|
|
26644
|
+
* effort ladder, so the agent's identity survives the fallback intact. That is
|
|
26645
|
+
* why the fallback is `gemini-3.5-flash` and not `gpt-5.6-luna`, which would
|
|
26646
|
+
* otherwise be the natural cross-vendor choice: luna is `genericCheapModel`'s
|
|
26647
|
+
* only entry, and using it in both places would collapse two roster entries
|
|
26648
|
+
* onto one model in the degraded case. */
|
|
26649
|
+
function genericFastModel() {
|
|
26650
|
+
return firstPresentInCatalog([EXPLORE_DEFAULT_MODEL, "gemini-3.5-flash"], {
|
|
26651
|
+
requireToolCalls: true,
|
|
26652
|
+
minContextTokens: ONE_M_TOKENS
|
|
26653
|
+
});
|
|
26654
|
+
}
|
|
26655
|
+
/** Model for `generic-cheap` — the cheapest catch-all. Absent → dropped.
|
|
26656
|
+
*
|
|
26657
|
+
* Single-entry by design (see `genericFastModel` for why luna is not shared).
|
|
26658
|
+
* `gpt-5.6-luna` is the cheapest id in the catalog and undercuts even the 400K
|
|
26659
|
+
* `gpt-5.4-mini`, while carrying 1.05M context and the full `none..max` effort
|
|
26660
|
+
* ladder — so unlike the flash tier it propagates a CLI effort pick above
|
|
26661
|
+
* `high` rather than clamping it. No `-mini`/`-lite`/`-haiku` model in the
|
|
26662
|
+
* catalog serves 1M, which is why the cheap catch-all is a `gpt-5.6-*` slug
|
|
26663
|
+
* rather than a mini one. */
|
|
26664
|
+
function genericCheapModel() {
|
|
26665
|
+
return firstPresentInCatalog(["gpt-5.6-luna"], {
|
|
26666
|
+
requireToolCalls: true,
|
|
26667
|
+
minContextTokens: ONE_M_TOKENS
|
|
26668
|
+
});
|
|
26140
26669
|
}
|
|
26141
26670
|
/**
|
|
26142
26671
|
* Gate for the worker tools (`explore`, `review`, `implement`).
|
|
@@ -26461,7 +26990,7 @@ function toolEntries(scope) {
|
|
|
26461
26990
|
imagePaths: {
|
|
26462
26991
|
type: "array",
|
|
26463
26992
|
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.
|
|
26993
|
+
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
26994
|
},
|
|
26466
26995
|
effort: {
|
|
26467
26996
|
type: "string",
|
|
@@ -33382,8 +33911,14 @@ function buildPeerAwarenessSnippet(opts) {
|
|
|
33382
33911
|
criticList.push("`opus_critic` (Opus 5)");
|
|
33383
33912
|
const codexCliClause = opts.codexCli ? " `mcp__codex-cli__codex` dispatches to `codex-implementer` (gpt-5.3-codex with workspace-write) for end-to-end coding tasks." : "";
|
|
33384
33913
|
const para2Parts = [`\`mcp__${searchKey}__code\` is the one-stop code search (no extra model call). Its DEFAULT mode (or \`mode:"semantic"\`) ranks by MEANING via ColBERT over a per-workspace index, the first thing to reach for on intent/concept questions ("where is retry/backoff handled", "how does auth work"); when that index isn't ready it transparently falls back to lexical (the response \`source\` says which engine ran). Forced modes cover the rest: \`lexical\` (BM25F-ranked + tree-sitter, best for exact symbols), \`exact\`, \`regex\`, \`complete\` (exhaustive set), \`ast_pattern\`+\`ast_lang\` for multi-line AST shapes, \`scan\` for a whole-workspace symbol outline, \`multiline\` for cross-line regex. Multiple queries can run in a single turn. The index covers code-shaped files; for unstructured files (logs, \`.csv\`, \`.env*\`, config-only wiring), \`grep\`/\`glob\` still apply.`];
|
|
33385
|
-
if (opts.workerToolsAvailable) para2Parts.push(`\`worker-*\` are background Agent subagents (subagent_type) that run the matching worker in its own context and deliver the result as a completion notification, so a long run never blocks the turn: \`worker-explore\` (read-only research), \`worker-review\` (reads the code to verify a change or claim), \`worker-plan\` (ordered implementation plan), \`worker-implement\` (edit/write/bash; ALWAYS runs in an isolated git worktree and returns the diff via a saved patch file; for in-place edits use the \`implementer\` subagent), \`worker-test\` (independent test author; also always worktree-isolated). The raw \`mcp__${workersKey}__*\` tools they call are guarded (a direct main-thread call is redirected to the matching agent); Workers themselves have \`code_search\`.`);
|
|
33386
|
-
|
|
33914
|
+
if (opts.workerToolsAvailable) para2Parts.push(`\`worker-*\` are background Agent subagents (subagent_type) that run the matching worker in its own context and deliver the result as a completion notification, so a long run never blocks the turn: \`worker-explore\` (read-only research), \`worker-review\` (reads the code to verify a change or claim), \`worker-plan\` (ordered implementation plan), \`worker-implement\` (edit/write/bash; ALWAYS runs in an isolated git worktree and returns the diff via a saved patch file; for in-place edits use the \`implementer\` subagent), \`worker-test\` (independent test author; also always worktree-isolated)${opts.browseAvailable ? ", `worker-browse` (autonomous browser agent driving a real browser)" : ""}. The raw \`mcp__${workersKey}__*\` tools they call are guarded (a direct main-thread call is redirected to the matching agent); Workers themselves have \`code_search\`.`);
|
|
33915
|
+
const catchAllNames = [
|
|
33916
|
+
opts.genericAvailable === false ? void 0 : "`generic`",
|
|
33917
|
+
opts.genericFastAvailable === false ? void 0 : "`generic-fast`",
|
|
33918
|
+
opts.genericCheapAvailable === false ? void 0 : "`generic-cheap`"
|
|
33919
|
+
].filter((n) => n != null);
|
|
33920
|
+
const catchAllClause = catchAllNames.length > 0 ? ` Catch-alls on non-lead models, for work no specialist fits, cheapest last: ${catchAllNames.join(", ")}.` : "";
|
|
33921
|
+
para2Parts.push(`Native subagents (Task), each in its own context so heavy work never fills yours: \`implementer\` (you know what to build), \`reviewer\` (something exists and you want it assessed, including reproducing and root-causing a failure), \`brainstorm\` (you do not yet know which approach to take)${opts.scoutAvailable === false ? "" : ", `scout` (find or understand something in the repo, cheap)"}, \`scribe\` (docs and ADRs that trail the code).${catchAllClause}`);
|
|
33387
33922
|
if (opts.workerToolsAvailable) para2Parts.push(`For a bounded, well-scoped implementation, prefer the \`implementer\` subagent over \`worker-implement\`; reach for \`worker-implement\` only when you specifically need git-worktree isolation, parallel variants, or a throwaway experiment.`);
|
|
33388
33923
|
if (opts.workerToolsAvailable) para2Parts.push(`\`mcp__${orchestrateKey}__decompose\` composes an open-ended ask into a typed, VERIFIED workflow IR (a strong driver decorrelated by a cross-lab critic, so the decompose step isn't a single point of failure), and \`mcp__${orchestrateKey}__run_workflow\` executes that IR through a frozen kernel delivering max(orchestrated, baseline) over a sealed executable gate, so it never ships worse than a plain single-model run. \`mcp__${orchestrateKey}__verify_workflow\` checks an IR's floor invariants before you run it, and \`mcp__${orchestrateKey}__attest_step\` audits that a finished run's producers were each checked by a different lab. They suit non-trivial, role-separated asks; a trivial ask does not need them.`);
|
|
33389
33924
|
else para2Parts.push(`\`mcp__${orchestrateKey}__verify_workflow\` statically checks a workflow IR's floor invariants and \`mcp__${orchestrateKey}__attest_step\` audits a run's cross-lab lineage (the \`decompose\`/\`run_workflow\` composer + kernel need the worker backend, unavailable here).`);
|
|
@@ -33416,13 +33951,18 @@ function buildPeerAwarenessSnippet(opts) {
|
|
|
33416
33951
|
*/
|
|
33417
33952
|
function buildPeerAwarenessSummary(opts) {
|
|
33418
33953
|
const key = (g) => opts.groupKeys?.[g] ?? GROUP_META[g].preferredKey;
|
|
33954
|
+
const summaryCatchAlls = [
|
|
33955
|
+
opts.genericAvailable === false ? void 0 : "`generic`",
|
|
33956
|
+
opts.genericFastAvailable === false ? void 0 : "`generic-fast`",
|
|
33957
|
+
opts.genericCheapAvailable === false ? void 0 : "`generic-cheap`"
|
|
33958
|
+
].filter((n) => n != null);
|
|
33419
33959
|
const lines = [
|
|
33420
33960
|
"## Injected capabilities (summary)",
|
|
33421
33961
|
"",
|
|
33422
|
-
`Native subagents (Task), each in its own context: \`implementer\` (you know what to build), \`reviewer\` (something exists and you want it assessed, including reproducing and root-causing a failure), \`brainstorm\` (you do not yet know which approach to take),
|
|
33962
|
+
`Native subagents (Task), each in its own context: \`implementer\` (you know what to build), \`reviewer\` (something exists and you want it assessed, including reproducing and root-causing a failure), \`brainstorm\` (you do not yet know which approach to take)${opts.scoutAvailable === false ? "" : ", `scout` (find or understand something in the repo, cheap)"}, \`scribe\` (docs and ADRs that trail the code).${summaryCatchAlls.length > 0 ? ` Catch-alls on non-lead models for work no specialist fits, cheapest last: ${summaryCatchAlls.join(", ")}.` : ""} They read the repo and can run things; the peer critics below cannot, so reach for \`reviewer\` when an assessment needs execution or repo context and for a critic when you already hold the artifact.`,
|
|
33423
33963
|
`A layer of MCP tools, background workers, and skills is injected into this session. Cross-lab peer critics under \`mcp__${key("peers")}__*\` (plus the \`peer-review-coordinator\` subagent) review plans and diffs adversarially, and Claude Code's built-in \`advisor\` catches approach drift. \`mcp__${key("search")}__code\` is meaning-first code search and \`mcp__${key("search")}__web\` returns citable web sources.`
|
|
33424
33964
|
];
|
|
33425
|
-
if (opts.workerToolsAvailable) lines.push(`Background \`worker-*\` agents (explore, review, plan, implement, test) run delegated work in their own context without blocking your turn, and \`mcp__${key("orchestrate")}__*\` composes, verifies, and runs floor-raising workflows.`);
|
|
33965
|
+
if (opts.workerToolsAvailable) lines.push(`Background \`worker-*\` agents (explore, review, plan, implement, test${opts.browseAvailable ? ", browse" : ""}) run delegated work in their own context without blocking your turn, and \`mcp__${key("orchestrate")}__*\` composes, verifies, and runs floor-raising workflows.`);
|
|
33426
33966
|
if (opts.standInAvailable) lines.push(`\`mcp__${key("decide")}__stand_in\` returns a three-lab consensus for a decision when the user is unavailable.`);
|
|
33427
33967
|
if (opts.browseAvailable) lines.push(`\`mcp__${key("browser")}__*\` drives a real Chrome or Edge browser.`);
|
|
33428
33968
|
if (opts.fleetAvailable) lines.push(`\`mcp__${key("fleet")}__*\` drives remote ai-or-die coding sessions.`);
|
|
@@ -34596,6 +35136,6 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
|
|
|
34596
35136
|
return [...new Set(names)];
|
|
34597
35137
|
}
|
|
34598
35138
|
//#endregion
|
|
34599
|
-
export { browserToolsEnabled as $, searchWeb as A,
|
|
35139
|
+
export { browserToolsEnabled as $, searchWeb as A, CONDENSED_OPERATING_SEQUENCE as At, buildAnthropicErrorEvent as B, UPSTREAM_INACTIVITY_TIMEOUT_MS as Bt, buildToolbeltAwareness as C, provisionBrowserAssets as Ct, TOOLBELT_TOOLS$1 as D, extractTarGzMember as Dt, vscodeRipgrepPath as E, provisionAndIndexColbert as Et, isAdvisorRequested as F, DEFAULT_CLAUDE_MODEL_FALLBACKS as Ft, relayAnthropicStream as G, withOneMSuffix as Gt, isControllerClosedError as H, pickClaudeDefault as Ht, formatThinkingRepairDecline as I, DEFAULT_CODEX_MODEL as It, agentToolsEnabled as J, handleMcpDelete as K, withInstallLock as Kt, rememberThinkingHistoryRepair as L, DEFAULT_CODEX_MODEL_FALLBACKS as Lt, ADVISOR_TOOL_INSTRUCTIONS as M, shouldUseInsecureTls as Mt, buildAdvisorStream as N, collapsePathKeys as Nt, assetFor as O, extractZipMember as Ot, injectAdvisorTool as P, toolbeltPathOverride as Pt, browserCompoundToolsEnabled as Q, repairKnownThinkingHistory as R, DEFAULT_PORT as Rt, availableToolCommands as S, parseJsonOrDiagnose as St, toolbeltSkipSet as T, colbertDegradedWarning as Tt, logStreamError as U, upstreamAllowH2 as Ut, buildOpenAIErrorEvent as V, generateRandomPort as Vt, readIteratorWithTimeout as W, upstreamMaxConnections as Wt, brainstormModel as X, artifactToolsEnabled as Y, browseAgentEnabled as Z, appendPlanReminder as _, pickEndpoint as _t, buildPeerAwarenessSnippet as a, nativeSubagentModel as at, runWorkerAgent as b, MAX_RESPONSE_BODY_BYTES as bt, personasFor as c, scribeModel as ct, EXPLORE_DEFAULT_MODEL as d, shimDefaultsToXhigh as dt, fleetToolsEnabled as et, EXPLORE_DEFAULT_THINKING as f, countTokens as ft, TEST_DEFAULT_MODEL as g, resolveMcpToolTimeoutMs as gt, REVIEW_DEFAULT_MODEL as h, assembleResponsesPayload as ht, buildAgentPrompt as i, genericModel as it, ADVISOR_INTERNAL_TOOL_NAME as j, DEFINITION_OF_GREATNESS as jt, satisfiesMinVersion as k, warmTreeSitterPool as kt, BROWSE_DEFAULT_MODEL as l, standInToolEnabled as lt, PLAN_DEFAULT_MODEL as m, getTokenCount as mt, MCP_GROUPS as n, genericCheapModel as nt, buildPeerAwarenessSummary as o, reviewerModel as ot, IMPLEMENT_DEFAULT_MODEL as p, createMessages as pt, handleMcpPost as q, assertMcpToolSurfaceConsistent as r, genericFastModel as rt, enumerateInjectedMcpToolNames as s, scoutModel as st, GROUP_META as t, geminiAvailable as tt, DEFAULT_MODEL as u, workerToolsEnabled as ut, resolveModeDefaults as v, createResponses as vt, toolbeltEnabled as w, hasSupportedBrowserInstalled as wt, buildEnv as x, readResponseBodyCapped as xt, resolveWorkerRunOpts as y, createChatCompletions as yt, repairRejectedThinkingHistory as z, UPSTREAM_FETCH_TIMEOUT_MS as zt };
|
|
34600
35140
|
|
|
34601
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
35141
|
+
//# sourceMappingURL=peer-mcp-personas-DRL_xT4h.js.map
|