@eddyskywalker/dsh-chatgpt-subscription 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -1
- package/lib/client.js +45 -43
- package/lib/client.js.map +1 -1
- package/lib/index.js +1424 -211
- package/lib/types/client/subagent/SubagentCard.d.ts.map +1 -1
- package/lib/types/client/subagent/styles.d.ts +1 -1
- package/lib/types/client/subagent/styles.d.ts.map +1 -1
- package/lib/types/compat.d.ts +1 -1
- package/lib/types/host/cache-warmup/install.d.ts.map +1 -1
- package/lib/types/host/cache-warmup/projection.d.ts.map +1 -1
- package/lib/types/host/kimi-code/adapter.d.ts +15 -0
- package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
- package/lib/types/host/kimi-code/cache-wait.d.ts +125 -0
- package/lib/types/host/kimi-code/cache-wait.d.ts.map +1 -0
- package/lib/types/host/kimi-code/mapper.d.ts +71 -4
- package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/presets/dispatch/agent.cordis.yml +2 -2
package/lib/index.js
CHANGED
|
@@ -145,7 +145,7 @@ const ROUTE_PREFIX$5 = "/api/dsh-chatgpt-subscription";
|
|
|
145
145
|
* `test/responses-client.test.ts` asserts the two agree, so a release that
|
|
146
146
|
* forgets this constant fails the suite instead of shipping quietly.
|
|
147
147
|
*/
|
|
148
|
-
const PLUGIN_VERSION = "0.
|
|
148
|
+
const PLUGIN_VERSION = "0.50.0";
|
|
149
149
|
const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
|
|
150
150
|
const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
|
|
151
151
|
const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
|
|
@@ -5289,9 +5289,9 @@ function requestImage$1(fetchFn, credentials, prompt, turnId, signal) {
|
|
|
5289
5289
|
});
|
|
5290
5290
|
}
|
|
5291
5291
|
function readBase64Image(value) {
|
|
5292
|
-
const root = record$
|
|
5292
|
+
const root = record$7(value);
|
|
5293
5293
|
const data = Array.isArray(root?.data) ? root.data[0] : null;
|
|
5294
|
-
const image = string$3(record$
|
|
5294
|
+
const image = string$3(record$7(data)?.b64_json) ?? string$3(record$7(data)?.image_base64) ?? string$3(root?.b64_json) ?? string$3(root?.image);
|
|
5295
5295
|
if (image === null) throw new HarnessError("Codex image response did not include image data.", "CODEX_IMAGE_RESPONSE_INVALID");
|
|
5296
5296
|
return image;
|
|
5297
5297
|
}
|
|
@@ -5305,7 +5305,7 @@ function decodeImageBytes(base64, maxBytes) {
|
|
|
5305
5305
|
function maxGeneratedImageBytes(attachments) {
|
|
5306
5306
|
return Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
|
|
5307
5307
|
}
|
|
5308
|
-
function record$
|
|
5308
|
+
function record$7(value) {
|
|
5309
5309
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
5310
5310
|
}
|
|
5311
5311
|
function string$3(value) {
|
|
@@ -9043,7 +9043,7 @@ function buildImageRequest(prompt, projectId, aspectRatio) {
|
|
|
9043
9043
|
requestId: `img_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
9044
9044
|
};
|
|
9045
9045
|
}
|
|
9046
|
-
function record$
|
|
9046
|
+
function record$6(value) {
|
|
9047
9047
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
9048
9048
|
}
|
|
9049
9049
|
function string$2(value) {
|
|
@@ -9073,16 +9073,16 @@ function parseImageResponse(body) {
|
|
|
9073
9073
|
} catch {
|
|
9074
9074
|
continue;
|
|
9075
9075
|
}
|
|
9076
|
-
const root = record$
|
|
9077
|
-
const response = record$
|
|
9078
|
-
const error = record$
|
|
9076
|
+
const root = record$6(frame);
|
|
9077
|
+
const response = record$6(root?.response) ?? root;
|
|
9078
|
+
const error = record$6(root?.error) ?? record$6(response?.error);
|
|
9079
9079
|
if (error) throw new HarnessError(`Antigravity image generation failed: ${string$2(error.message) ?? "unknown error"}`, "ANTIGRAVITY_IMAGE_FAILED");
|
|
9080
|
-
const candidate = record$
|
|
9081
|
-
const parts = Array.isArray(record$
|
|
9080
|
+
const candidate = record$6(Array.isArray(response?.candidates) ? response.candidates[0] : void 0);
|
|
9081
|
+
const parts = Array.isArray(record$6(candidate?.content)?.parts) ? record$6(candidate?.content).parts : [];
|
|
9082
9082
|
for (const raw of parts) {
|
|
9083
|
-
const part = record$
|
|
9083
|
+
const part = record$6(raw);
|
|
9084
9084
|
if (!part || part.thought === true) continue;
|
|
9085
|
-
const inline = record$
|
|
9085
|
+
const inline = record$6(part.inlineData) ?? record$6(part.inline_data);
|
|
9086
9086
|
const data = string$2(inline?.data);
|
|
9087
9087
|
if (inline && data) {
|
|
9088
9088
|
result.images.push({
|
|
@@ -9093,7 +9093,7 @@ function parseImageResponse(body) {
|
|
|
9093
9093
|
}
|
|
9094
9094
|
if (typeof part.text === "string") result.text += part.text;
|
|
9095
9095
|
}
|
|
9096
|
-
const blockReason = string$2(record$
|
|
9096
|
+
const blockReason = string$2(record$6(response?.promptFeedback)?.blockReason);
|
|
9097
9097
|
result.finishReason = string$2(candidate?.finishReason) ?? (blockReason ? `PROMPT_BLOCKED:${blockReason}` : result.finishReason);
|
|
9098
9098
|
result.finishMessage = string$2(candidate?.finishMessage) ?? result.finishMessage;
|
|
9099
9099
|
}
|
|
@@ -9429,13 +9429,13 @@ function normalizeSearchResult(data, maxResults) {
|
|
|
9429
9429
|
};
|
|
9430
9430
|
}
|
|
9431
9431
|
function readSources(data) {
|
|
9432
|
-
const root = record$
|
|
9432
|
+
const root = record$5(data);
|
|
9433
9433
|
if (root === null) return [];
|
|
9434
9434
|
const candidates = [
|
|
9435
9435
|
root.sources,
|
|
9436
9436
|
root.results,
|
|
9437
|
-
record$
|
|
9438
|
-
record$
|
|
9437
|
+
record$5(root.search_result)?.sources,
|
|
9438
|
+
record$5(root.web_search)?.sources
|
|
9439
9439
|
];
|
|
9440
9440
|
for (const candidate of candidates) {
|
|
9441
9441
|
if (!Array.isArray(candidate)) continue;
|
|
@@ -9445,7 +9445,7 @@ function readSources(data) {
|
|
|
9445
9445
|
return [];
|
|
9446
9446
|
}
|
|
9447
9447
|
function readSource(value) {
|
|
9448
|
-
const data = record$
|
|
9448
|
+
const data = record$5(value);
|
|
9449
9449
|
if (data === null) return null;
|
|
9450
9450
|
const url = string$1(data.url) ?? string$1(data.link) ?? string$1(data.uri);
|
|
9451
9451
|
if (url === null || !isHttpUrl(url)) return null;
|
|
@@ -9474,7 +9474,7 @@ function readSource(value) {
|
|
|
9474
9474
|
* counterpart of `output`, not a fallback for it.
|
|
9475
9475
|
*/
|
|
9476
9476
|
function readContent(data) {
|
|
9477
|
-
const root = record$
|
|
9477
|
+
const root = record$5(data);
|
|
9478
9478
|
if (root === null) return void 0;
|
|
9479
9479
|
return string$1(root.content) ?? string$1(root.output_text) ?? string$1(root.summary) ?? string$1(root.output) ?? void 0;
|
|
9480
9480
|
}
|
|
@@ -9489,7 +9489,7 @@ function dedupeSources(sources) {
|
|
|
9489
9489
|
}
|
|
9490
9490
|
return result;
|
|
9491
9491
|
}
|
|
9492
|
-
function record$
|
|
9492
|
+
function record$5(value) {
|
|
9493
9493
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9494
9494
|
}
|
|
9495
9495
|
function string$1(value) {
|
|
@@ -11292,7 +11292,7 @@ function offloadOldestInputImages(input, maxBytes = maxRequestImageBytes$2()) {
|
|
|
11292
11292
|
for (const item of input) {
|
|
11293
11293
|
if (!Array.isArray(item.content)) continue;
|
|
11294
11294
|
for (const [index, block] of item.content.entries()) {
|
|
11295
|
-
if (record$
|
|
11295
|
+
if (record$4(block) === null || block.type !== "input_image") continue;
|
|
11296
11296
|
const url = block.image_url;
|
|
11297
11297
|
if (typeof url !== "string") continue;
|
|
11298
11298
|
retained.push({
|
|
@@ -11492,14 +11492,14 @@ function toolParametersForCodex(toolName, parameters, allowSandboxRetry) {
|
|
|
11492
11492
|
const augmentCommandTool = isCommandTool(toolName);
|
|
11493
11493
|
if (!hideSandboxControls && toolName !== "run_code" && !augmentCommandTool) return parameters;
|
|
11494
11494
|
const cloned = structuredClone(parameters);
|
|
11495
|
-
const properties = record$
|
|
11495
|
+
const properties = record$4(cloned.properties);
|
|
11496
11496
|
if (properties !== null && hideSandboxControls) {
|
|
11497
11497
|
delete properties.sandbox_permissions;
|
|
11498
11498
|
delete properties.justification;
|
|
11499
11499
|
}
|
|
11500
11500
|
if (hideSandboxControls && Array.isArray(cloned.required)) cloned.required = cloned.required.filter((name) => name !== "sandbox_permissions" && name !== "justification");
|
|
11501
11501
|
if (toolName === "run_code" && properties !== null) {
|
|
11502
|
-
const code = record$
|
|
11502
|
+
const code = record$4(properties.code);
|
|
11503
11503
|
if (code !== null) {
|
|
11504
11504
|
const current = typeof code.description === "string" ? code.description.trim() : "";
|
|
11505
11505
|
const compatibility = "Strict JavaScript/TypeScript source. Nested shell commands are string data: template literals may consume ${...}, backticks, backslashes, and escape sequences before PowerShell, Bash, or POSIX sh sees them; String.raw still performs ${...} interpolation. Prefer ordinary quoted string arrays joined with \"\\n\", or write a script file with a dedicated file tool.";
|
|
@@ -11517,7 +11517,7 @@ function toolParametersForCodex(toolName, parameters, allowSandboxRetry) {
|
|
|
11517
11517
|
return cloned;
|
|
11518
11518
|
}
|
|
11519
11519
|
function appendPropertyDescription(value, addition) {
|
|
11520
|
-
const property = record$
|
|
11520
|
+
const property = record$4(value);
|
|
11521
11521
|
if (property === null) return;
|
|
11522
11522
|
const current = typeof property.description === "string" ? property.description.trim() : "";
|
|
11523
11523
|
if (current.includes(addition)) return;
|
|
@@ -11528,7 +11528,7 @@ function isCommandTool(name) {
|
|
|
11528
11528
|
return normalized === "pwsh" || normalized === "powershell" || normalized === "bash" || normalized === "sh" || normalized === "shell";
|
|
11529
11529
|
}
|
|
11530
11530
|
function hasSandboxControls(parameters) {
|
|
11531
|
-
const properties = record$
|
|
11531
|
+
const properties = record$4(parameters.properties);
|
|
11532
11532
|
return properties !== null && ("sandbox_permissions" in properties || "justification" in properties);
|
|
11533
11533
|
}
|
|
11534
11534
|
function recentSandboxRetryToolNames(messages) {
|
|
@@ -11740,11 +11740,11 @@ function replayOutputItems(message) {
|
|
|
11740
11740
|
const replay = message.source?.replayState;
|
|
11741
11741
|
if (typeof replay !== "object" || replay === null || Array.isArray(replay)) return null;
|
|
11742
11742
|
const envelope = replay;
|
|
11743
|
-
const items = Array.isArray(envelope.outputItems) ? envelope.outputItems : Array.isArray(record$
|
|
11743
|
+
const items = Array.isArray(envelope.outputItems) ? envelope.outputItems : Array.isArray(record$4(envelope.response)?.outputItems) ? record$4(envelope.response).outputItems : null;
|
|
11744
11744
|
if (!Array.isArray(items)) return null;
|
|
11745
11745
|
return structuredClone(items.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)));
|
|
11746
11746
|
}
|
|
11747
|
-
function record$
|
|
11747
|
+
function record$4(value) {
|
|
11748
11748
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
11749
11749
|
}
|
|
11750
11750
|
//#endregion
|
|
@@ -12145,7 +12145,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
|
|
|
12145
12145
|
return;
|
|
12146
12146
|
}
|
|
12147
12147
|
if (type === "response.content_part.done") {
|
|
12148
|
-
const part = record$
|
|
12148
|
+
const part = record$3(event.part);
|
|
12149
12149
|
const partType = string(part?.type);
|
|
12150
12150
|
if (partType === "output_text" || partType === "refusal") yield* adoptText(string(part?.text) ?? "");
|
|
12151
12151
|
return;
|
|
@@ -12177,7 +12177,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
|
|
|
12177
12177
|
return;
|
|
12178
12178
|
}
|
|
12179
12179
|
if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
12180
|
-
const item = record$
|
|
12180
|
+
const item = record$3(event.item);
|
|
12181
12181
|
if (item !== null && type === "response.output_item.done") replayOutput.push(structuredClone(item));
|
|
12182
12182
|
if (string(item?.type) !== "function_call") {
|
|
12183
12183
|
if (type === "response.output_item.done") yield* adoptText(messageItemText(item));
|
|
@@ -12237,18 +12237,18 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
|
|
|
12237
12237
|
return;
|
|
12238
12238
|
}
|
|
12239
12239
|
if (type === "response.completed" || type === "response.incomplete") {
|
|
12240
|
-
const completed = record$
|
|
12241
|
-
usage = mapUsage$2(record$
|
|
12240
|
+
const completed = record$3(event.response);
|
|
12241
|
+
usage = mapUsage$2(record$3(completed?.usage));
|
|
12242
12242
|
const output = completed?.output;
|
|
12243
12243
|
if (Array.isArray(output)) {
|
|
12244
|
-
replayOutput = output.filter((item) => record$
|
|
12245
|
-
for (const item of output) yield* adoptText(messageItemText(record$
|
|
12244
|
+
replayOutput = output.filter((item) => record$3(item) !== null).map((item) => structuredClone(item));
|
|
12245
|
+
for (const item of output) yield* adoptText(messageItemText(record$3(item)));
|
|
12246
12246
|
}
|
|
12247
12247
|
terminal = type === "response.incomplete" ? { kind: "max-tokens" } : { kind: "stop" };
|
|
12248
12248
|
return;
|
|
12249
12249
|
}
|
|
12250
12250
|
if (type === "response.failed" || type === "error") {
|
|
12251
|
-
const error = record$
|
|
12251
|
+
const error = record$3(event.error) ?? record$3(record$3(event.response)?.error);
|
|
12252
12252
|
const message = string(error?.message) ?? "Codex generation failed.";
|
|
12253
12253
|
const rawCode = string(error?.code)?.toLowerCase();
|
|
12254
12254
|
const isOverload = message.toLowerCase().includes("overload") || message.toLowerCase().includes("server error") || rawCode === "server_error" || rawCode === "service_unavailable" || rawCode === "internal_error";
|
|
@@ -12274,7 +12274,7 @@ async function* parseResponsesStream(response, signal, hiddenSandboxControls = /
|
|
|
12274
12274
|
} catch {
|
|
12275
12275
|
throw new LlmError("Codex returned malformed streaming JSON.", "PROTOCOL_ERROR");
|
|
12276
12276
|
}
|
|
12277
|
-
const valueRecord = record$
|
|
12277
|
+
const valueRecord = record$3(event);
|
|
12278
12278
|
if (valueRecord !== null) yield* consume(valueRecord);
|
|
12279
12279
|
}
|
|
12280
12280
|
}
|
|
@@ -12380,10 +12380,10 @@ function mapUsage$2(value) {
|
|
|
12380
12380
|
if (value === null) return null;
|
|
12381
12381
|
const totalInput = number(value.input_tokens) ?? 0;
|
|
12382
12382
|
const outputTokens = number(value.output_tokens) ?? 0;
|
|
12383
|
-
const details = record$
|
|
12383
|
+
const details = record$3(value.input_tokens_details);
|
|
12384
12384
|
const cached = number(details?.cached_tokens);
|
|
12385
12385
|
const written = number(details?.cache_write_tokens);
|
|
12386
|
-
const reasoning = number(record$
|
|
12386
|
+
const reasoning = number(record$3(value.output_tokens_details)?.reasoning_tokens);
|
|
12387
12387
|
return {
|
|
12388
12388
|
inputTokens: Math.max(0, totalInput - (cached ?? 0) - (written ?? 0)),
|
|
12389
12389
|
outputTokens,
|
|
@@ -12402,7 +12402,7 @@ function isSafeJsonArguments(value) {
|
|
|
12402
12402
|
}
|
|
12403
12403
|
function stripSandboxControls(value) {
|
|
12404
12404
|
try {
|
|
12405
|
-
const parsedRecord = record$
|
|
12405
|
+
const parsedRecord = record$3(JSON.parse(value));
|
|
12406
12406
|
if (parsedRecord === null) return value;
|
|
12407
12407
|
let changed = false;
|
|
12408
12408
|
for (const name of ["sandbox_permissions", "justification"]) if (name in parsedRecord) {
|
|
@@ -12439,14 +12439,14 @@ function messageItemText(item) {
|
|
|
12439
12439
|
if (item === null || string(item.type) !== "message" || !Array.isArray(item.content)) return "";
|
|
12440
12440
|
let text = "";
|
|
12441
12441
|
for (const part of item.content) {
|
|
12442
|
-
const value = record$
|
|
12442
|
+
const value = record$3(part);
|
|
12443
12443
|
const kind = string(value?.type);
|
|
12444
12444
|
if (kind !== "output_text" && kind !== "refusal") continue;
|
|
12445
12445
|
text += string(value?.text) ?? "";
|
|
12446
12446
|
}
|
|
12447
12447
|
return text;
|
|
12448
12448
|
}
|
|
12449
|
-
function record$
|
|
12449
|
+
function record$3(value) {
|
|
12450
12450
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
12451
12451
|
}
|
|
12452
12452
|
function string(value) {
|
|
@@ -13539,7 +13539,7 @@ function mapCodexUsage(value) {
|
|
|
13539
13539
|
return parseCodexUsage(value).buckets;
|
|
13540
13540
|
}
|
|
13541
13541
|
function parseCodexUsage(value) {
|
|
13542
|
-
const data = record$
|
|
13542
|
+
const data = record$2(value);
|
|
13543
13543
|
if (data === null) return structuredClone(EMPTY_USAGE);
|
|
13544
13544
|
const planType = typeof data.plan_type === "string" ? data.plan_type : null;
|
|
13545
13545
|
const buckets = [];
|
|
@@ -13548,21 +13548,21 @@ function parseCodexUsage(value) {
|
|
|
13548
13548
|
addBucket(buckets, usedIds, "code-review", "Code review", planType, data.code_review_rate_limit);
|
|
13549
13549
|
const additional = Array.isArray(data.additional_rate_limits) ? data.additional_rate_limits : [];
|
|
13550
13550
|
for (const [index, value] of additional.entries()) {
|
|
13551
|
-
const limit = record$
|
|
13551
|
+
const limit = record$2(value);
|
|
13552
13552
|
if (limit === null) continue;
|
|
13553
|
-
const idSource = text$
|
|
13554
|
-
addBucket(buckets, usedIds, uniqueId(slug(idSource), usedIds), readableLimitName(text$
|
|
13553
|
+
const idSource = text$4(limit.limit_name) ?? text$4(limit.metered_feature) ?? `additional-${index + 1}`;
|
|
13554
|
+
addBucket(buckets, usedIds, uniqueId(slug(idSource), usedIds), readableLimitName(text$4(limit.limit_name) ?? text$4(limit.metered_feature) ?? idSource), planType, limit.rate_limit);
|
|
13555
13555
|
}
|
|
13556
13556
|
return {
|
|
13557
13557
|
buckets,
|
|
13558
13558
|
credits: mapCredits(data.credits),
|
|
13559
|
-
individualLimit: mapIndividualLimit(record$
|
|
13560
|
-
spendControlReached: boolean(record$
|
|
13559
|
+
individualLimit: mapIndividualLimit(record$2(data.spend_control)?.individual_limit),
|
|
13560
|
+
spendControlReached: boolean(record$2(data.spend_control)?.reached),
|
|
13561
13561
|
resetCredits: mapResetCredits(data.rate_limit_reset_credits)
|
|
13562
13562
|
};
|
|
13563
13563
|
}
|
|
13564
13564
|
function addBucket(result, usedIds, id, name, planType, value) {
|
|
13565
|
-
const source = record$
|
|
13565
|
+
const source = record$2(value);
|
|
13566
13566
|
if (source === null) return;
|
|
13567
13567
|
const primary = mapWindow(source.primary_window);
|
|
13568
13568
|
const secondary = mapWindow(source.secondary_window);
|
|
@@ -13578,7 +13578,7 @@ function addBucket(result, usedIds, id, name, planType, value) {
|
|
|
13578
13578
|
usedIds.add(id);
|
|
13579
13579
|
}
|
|
13580
13580
|
function mapWindow(value) {
|
|
13581
|
-
const data = record$
|
|
13581
|
+
const data = record$2(value);
|
|
13582
13582
|
if (data === null) return null;
|
|
13583
13583
|
const used = numeric(data.used_percent);
|
|
13584
13584
|
if (used === void 0) return null;
|
|
@@ -13591,7 +13591,7 @@ function mapWindow(value) {
|
|
|
13591
13591
|
};
|
|
13592
13592
|
}
|
|
13593
13593
|
function mapCredits(value) {
|
|
13594
|
-
const data = record$
|
|
13594
|
+
const data = record$2(value);
|
|
13595
13595
|
if (data === null) return null;
|
|
13596
13596
|
const hasCredits = boolean(data.has_credits);
|
|
13597
13597
|
const unlimited = boolean(data.unlimited);
|
|
@@ -13604,7 +13604,7 @@ function mapCredits(value) {
|
|
|
13604
13604
|
};
|
|
13605
13605
|
}
|
|
13606
13606
|
function mapIndividualLimit(value) {
|
|
13607
|
-
const data = record$
|
|
13607
|
+
const data = record$2(value);
|
|
13608
13608
|
if (data === null) return null;
|
|
13609
13609
|
const remaining = numeric(data.remaining_percent);
|
|
13610
13610
|
const reset = numeric(data.reset_at);
|
|
@@ -13619,7 +13619,7 @@ function mapIndividualLimit(value) {
|
|
|
13619
13619
|
};
|
|
13620
13620
|
}
|
|
13621
13621
|
function mapResetCredits(value) {
|
|
13622
|
-
const data = record$
|
|
13622
|
+
const data = record$2(value);
|
|
13623
13623
|
if (data === null) return null;
|
|
13624
13624
|
const available = numeric(data.available_count);
|
|
13625
13625
|
if (available === void 0) return null;
|
|
@@ -13629,14 +13629,14 @@ function mapResetCredits(value) {
|
|
|
13629
13629
|
};
|
|
13630
13630
|
}
|
|
13631
13631
|
function parseResetCredits(value) {
|
|
13632
|
-
const data = record$
|
|
13632
|
+
const data = record$2(value);
|
|
13633
13633
|
if (data === null) return {
|
|
13634
13634
|
availableCount: 0,
|
|
13635
13635
|
expiresAt: null,
|
|
13636
13636
|
availableCreditIds: []
|
|
13637
13637
|
};
|
|
13638
|
-
const available = (Array.isArray(data.credits) ? data.credits : []).map(record$
|
|
13639
|
-
id: text$
|
|
13638
|
+
const available = (Array.isArray(data.credits) ? data.credits : []).map(record$2).filter((credit) => credit !== null && credit.status === "available").map((credit) => ({
|
|
13639
|
+
id: text$4(credit.id),
|
|
13640
13640
|
expiresAt: timestamp(credit.expires_at)
|
|
13641
13641
|
})).filter((credit) => credit.id !== null).sort((left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER));
|
|
13642
13642
|
const reported = numeric(data.available_count);
|
|
@@ -13653,7 +13653,7 @@ function timestamp(value) {
|
|
|
13653
13653
|
const parsed = Date.parse(value);
|
|
13654
13654
|
return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : null;
|
|
13655
13655
|
}
|
|
13656
|
-
function record$
|
|
13656
|
+
function record$2(value) {
|
|
13657
13657
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
13658
13658
|
}
|
|
13659
13659
|
function boolean(value) {
|
|
@@ -13670,7 +13670,7 @@ function decimalText(value) {
|
|
|
13670
13670
|
const trimmed = value.trim();
|
|
13671
13671
|
return trimmed !== "" ? trimmed : null;
|
|
13672
13672
|
}
|
|
13673
|
-
function text$
|
|
13673
|
+
function text$4(value) {
|
|
13674
13674
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
13675
13675
|
}
|
|
13676
13676
|
function slug(value) {
|
|
@@ -25877,11 +25877,73 @@ const TOOL_SUBAGENT = /(?:^|[-_/])(?:subagent|agent|task|delegate|ralph|teammate
|
|
|
25877
25877
|
const TOOL_SOURCE = /(?:^|[-_/])(?:read|view|cat|open|edit|write|patch|apply_patch|notebook|document|diff)(?:[-_/]|$)/i;
|
|
25878
25878
|
/** 独立开关覆盖的检索工具;只有明确导航任务才允许进入新投影。 */
|
|
25879
25879
|
const TOOL_RETRIEVAL = /^(?:read|web_fetch|grep|glob)$/;
|
|
25880
|
+
/**
|
|
25881
|
+
* 多节/多读输出进入新投影的最小长度门槛(字符)。
|
|
25882
|
+
*
|
|
25883
|
+
* 短于这个长度、又没有 spill 的普通源码与契约读一律逐字保留:
|
|
25884
|
+
* 只有「确定已经大到装不进预算」的输出才值得换成摘要。
|
|
25885
|
+
*/
|
|
25886
|
+
const MULTI_OUTPUT_MIN_CHARS = 2e4;
|
|
25880
25887
|
/** 一行文本的裁剪(超长行才裁剪,并留下省略号)。 */
|
|
25881
25888
|
function clip(text, max) {
|
|
25882
25889
|
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
25883
25890
|
}
|
|
25884
25891
|
/**
|
|
25892
|
+
* DSH 的输出上限提示:正文被截断,全文落在临时文件里。
|
|
25893
|
+
*
|
|
25894
|
+
* 省略说明可能是精确或未知字节数,还可能跟着一段图片计数;这些形态都由 DSH 的 formatSpillNotice 产生。
|
|
25895
|
+
* 路径本身可能带 `.txt` 之类的后缀,所以路径用**非贪婪**捕获,末尾的句点单独留给正则。
|
|
25896
|
+
*/
|
|
25897
|
+
const SPILL_NOTICE = /\n\n\((?:Omitted \d+ bytes\.|More bytes were omitted\.)(?: Omitted \d+ images\.)? Full formatted result stored at: ([^\r\n]+?)(?:\. Use read with offset\/limit, or grep this path to search within it)?\.\)\s*$/;
|
|
25898
|
+
/**
|
|
25899
|
+
* 读回一个 spill 文件的完整 UTF-8 内容。
|
|
25900
|
+
*
|
|
25901
|
+
* 文件系统依赖在这里动态引入:`contracts.ts` 必须保持零 Node 依赖,而投影是宿主侧代码。
|
|
25902
|
+
* 读不到、不是普通文件、或超过单结果字节上限时一律返回 undefined —— 宁可少投影,
|
|
25903
|
+
* 也不能把一份读了一半的原文当成全文入库。
|
|
25904
|
+
* @param filePath - spill 提示里给出的路径。
|
|
25905
|
+
* @returns 完整内容,或 undefined。
|
|
25906
|
+
*/
|
|
25907
|
+
async function readSpillFile(filePath) {
|
|
25908
|
+
try {
|
|
25909
|
+
const { stat, readFile } = await import("node:fs/promises");
|
|
25910
|
+
const stats = await stat(filePath);
|
|
25911
|
+
if (!stats.isFile()) return void 0;
|
|
25912
|
+
if (stats.size > CACHE_WARMUP_DEFAULTS.maxResultBytes) return void 0;
|
|
25913
|
+
return await readFile(filePath, "utf8");
|
|
25914
|
+
} catch {
|
|
25915
|
+
return;
|
|
25916
|
+
}
|
|
25917
|
+
}
|
|
25918
|
+
/**
|
|
25919
|
+
* 处理 DSH 的 spill 提示。
|
|
25920
|
+
*
|
|
25921
|
+
* 读得到全文:投影与入库都用全文,`cache_warmup_result` 才能翻到**未截断**的输出。
|
|
25922
|
+
* 读不到:剥掉提示尾部,剩下的正文照常参与投影与入库。
|
|
25923
|
+
* @param raw - 工具结果原文。
|
|
25924
|
+
* @returns 解析结果。
|
|
25925
|
+
*/
|
|
25926
|
+
async function resolveSpillText(raw) {
|
|
25927
|
+
const match = SPILL_NOTICE.exec(raw);
|
|
25928
|
+
if (match === null) return {
|
|
25929
|
+
text: raw,
|
|
25930
|
+
spilled: false,
|
|
25931
|
+
recovered: false
|
|
25932
|
+
};
|
|
25933
|
+
const filePath = match[1].trim();
|
|
25934
|
+
const full = filePath === "" ? void 0 : await readSpillFile(filePath);
|
|
25935
|
+
if (full !== void 0) return {
|
|
25936
|
+
text: full,
|
|
25937
|
+
spilled: true,
|
|
25938
|
+
recovered: true
|
|
25939
|
+
};
|
|
25940
|
+
return {
|
|
25941
|
+
text: raw.slice(0, match.index),
|
|
25942
|
+
spilled: true,
|
|
25943
|
+
recovered: false
|
|
25944
|
+
};
|
|
25945
|
+
}
|
|
25946
|
+
/**
|
|
25885
25947
|
* 剥离终端 ANSI/CSI 转义序列(如 `\x1b[31mFAIL\x1b[0m`)与回车符 `\r`。
|
|
25886
25948
|
*
|
|
25887
25949
|
* JSON 解码后,原先源码/日志里的 `\u001b[31m` 会还原成控制字符 `\x1b`;
|
|
@@ -25959,6 +26021,10 @@ function buildTestSections(evidence) {
|
|
|
25959
26021
|
const SHELL_ERROR_LINE = /(?:error|fatal|panic|traceback|exception|EACCES|EPERM|ENOENT|EEXIST|ELIFECYCLE|ERR!|FAILED|permission denied|cannot |could not |not found)/i;
|
|
25960
26022
|
/** TypeScript 编译器报错首行:`path(line,col): error TSxxxx:` 或 `path:line:col - error TSxxxx:`。 */
|
|
25961
26023
|
const TS_DIAGNOSTIC_HEADER = /^(?:.+?\(\d+,\d+\):\s+error\s+TS\d+:|.+?:\d+:\d+\s+-\s+error\s+TS\d+:)/;
|
|
26024
|
+
/** shell 预览里最多列出的错误行数;超出的部分用 ref 提示补读。 */
|
|
26025
|
+
const SHELL_ERROR_MAX_LINES = 25;
|
|
26026
|
+
/** shell 预览里错误集最多占用的字符数(行数上限先到,这个只是兜底)。 */
|
|
26027
|
+
const SHELL_ERROR_MAX_CHARS = 6500;
|
|
25962
26028
|
/**
|
|
25963
26029
|
* 紧跟在 TypeScript 诊断首行后面的续行(缩进的类型比对、代码高亮行、波浪线指示行),
|
|
25964
26030
|
* 最多连带收集 6 行,使模型无需补读就能看全 `Type A is not assignable to Type B` 的完整原因。
|
|
@@ -26085,15 +26151,27 @@ function buildShellSections(text) {
|
|
|
26085
26151
|
}
|
|
26086
26152
|
if (SHELL_ERROR_LINE.test(trimmed)) {
|
|
26087
26153
|
errorCount += 1;
|
|
26088
|
-
errors.push(` ${clip(trimmed,
|
|
26154
|
+
errors.push(` ${clip(trimmed, 240)}`);
|
|
26089
26155
|
}
|
|
26090
26156
|
}
|
|
26091
26157
|
if (errorCount > 300 || errors.length > 600) return void 0;
|
|
26092
26158
|
const exitMatch = /(?:exit code|exited with code|process exited with code)[:=\s]+(-?\d+)/i.exec(text) ?? /\[exit code: (-?\d+)\]/i.exec(text);
|
|
26093
26159
|
const sections = [];
|
|
26094
26160
|
if (exitMatch !== null) sections.push({ lines: [`exit code: ${exitMatch[1]}`] });
|
|
26095
|
-
|
|
26096
|
-
|
|
26161
|
+
const shownErrors = [];
|
|
26162
|
+
let shownChars = 0;
|
|
26163
|
+
for (const line of errors) {
|
|
26164
|
+
if (shownErrors.length >= SHELL_ERROR_MAX_LINES) break;
|
|
26165
|
+
if (shownChars + line.length + 1 > SHELL_ERROR_MAX_CHARS) break;
|
|
26166
|
+
shownErrors.push(line);
|
|
26167
|
+
shownChars += line.length + 1;
|
|
26168
|
+
}
|
|
26169
|
+
const truncated = shownErrors.length < errors.length;
|
|
26170
|
+
sections.push({ lines: [truncated ? `errors: ${errorCount} (first ${shownErrors.length} shown; full set via ref)` : `errors: ${errorCount} (complete set)`] });
|
|
26171
|
+
if (shownErrors.length > 0) {
|
|
26172
|
+
const body = truncated ? [...shownErrors, ` … ${errors.length - shownErrors.length} more error lines (full list via ref)`] : shownErrors;
|
|
26173
|
+
sections.push({ lines: body });
|
|
26174
|
+
}
|
|
26097
26175
|
const nonEmpty = lines.map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
|
|
26098
26176
|
const head = nonEmpty.slice(0, 5).map((line) => ` ${clip(line.trim(), 160)}`);
|
|
26099
26177
|
const tail = nonEmpty.slice(-5).map((line) => ` ${clip(line.trim(), 160)}`);
|
|
@@ -26309,6 +26387,42 @@ function planRetrievalOutline(text, toolName) {
|
|
|
26309
26387
|
};
|
|
26310
26388
|
}
|
|
26311
26389
|
}
|
|
26390
|
+
/**
|
|
26391
|
+
* 读出装着日志字段的那个对象的元数据。
|
|
26392
|
+
* @param holder - 装着日志字段的对象(顶层信封或 1 层嵌套的内层对象)。
|
|
26393
|
+
* @returns 读到的元数据;两者都没有时 undefined。
|
|
26394
|
+
*/
|
|
26395
|
+
function shellMetadataOf(holder) {
|
|
26396
|
+
const job = isPlainRecord(holder.job) ? holder.job : void 0;
|
|
26397
|
+
let exitCode;
|
|
26398
|
+
if (typeof holder.exitCode === "number" && Number.isFinite(holder.exitCode)) exitCode = holder.exitCode;
|
|
26399
|
+
if (exitCode === void 0 && typeof job?.exitCode === "number" && Number.isFinite(job.exitCode)) exitCode = job.exitCode;
|
|
26400
|
+
if (exitCode === void 0 && typeof job?.detail === "string") {
|
|
26401
|
+
const matched = /(?:exit code|exited with code|process exited with code)\s*[:=]?\s*(-?\d+)/i.exec(job.detail);
|
|
26402
|
+
if (matched !== null) exitCode = Number(matched[1]);
|
|
26403
|
+
}
|
|
26404
|
+
const status = typeof job?.status === "string" ? job.status : void 0;
|
|
26405
|
+
if (exitCode === void 0 && status === void 0) return void 0;
|
|
26406
|
+
return {
|
|
26407
|
+
...exitCode === void 0 ? {} : { exitCode },
|
|
26408
|
+
...status === void 0 ? {} : { status }
|
|
26409
|
+
};
|
|
26410
|
+
}
|
|
26411
|
+
/**
|
|
26412
|
+
* 把信封里的退出码与状态补到 shell 预览的最前面。
|
|
26413
|
+
* @param sections - `buildShellSections` 交出来的段落。
|
|
26414
|
+
* @param meta - 信封元数据。
|
|
26415
|
+
* @returns 补过元数据的段落。
|
|
26416
|
+
*/
|
|
26417
|
+
function withShellMetadata(sections, meta) {
|
|
26418
|
+
if (meta === void 0) return sections;
|
|
26419
|
+
const lines = [];
|
|
26420
|
+
const hasExit = sections.some((section) => section.lines[0]?.startsWith("exit code:"));
|
|
26421
|
+
if (meta.exitCode !== void 0 && !hasExit) lines.push(`exit code: ${meta.exitCode}`);
|
|
26422
|
+
if (meta.status !== void 0) lines.push(`status: ${meta.status}`);
|
|
26423
|
+
if (lines.length === 0) return sections;
|
|
26424
|
+
return [{ lines }, ...sections];
|
|
26425
|
+
}
|
|
26312
26426
|
/** 契约/类型定义:命中两行以上声明就当它是契约,保持原样。 */
|
|
26313
26427
|
function looksLikeContract(text) {
|
|
26314
26428
|
let declarations = 0;
|
|
@@ -26368,16 +26482,53 @@ function isJsonDocument(text) {
|
|
|
26368
26482
|
}
|
|
26369
26483
|
return false;
|
|
26370
26484
|
}
|
|
26371
|
-
/**
|
|
26485
|
+
/**
|
|
26486
|
+
* PTC `run_code` JSON envelope 中允许独立投影日志的字段白名单。
|
|
26487
|
+
*
|
|
26488
|
+
* `'text'` 在列:`tools.job_output` 返回的正是 `{ text, job }`,日志正文就叫 `text`。
|
|
26489
|
+
*/
|
|
26372
26490
|
const JSON_ENVELOPE_LOG_KEYS = [
|
|
26373
26491
|
"stdout",
|
|
26374
26492
|
"stderr",
|
|
26375
26493
|
"output",
|
|
26376
26494
|
"logs",
|
|
26377
|
-
"log"
|
|
26495
|
+
"log",
|
|
26496
|
+
"text"
|
|
26378
26497
|
];
|
|
26379
26498
|
/** JSON envelope 其余字段允许占用的最大序列化字符数(防止把含批量源码的混合 envelope 压掉)。 */
|
|
26380
26499
|
const JSON_ENVELOPE_MAX_OTHER_CHARS = 800;
|
|
26500
|
+
/** 判断一个值是不是普通 JSON 对象(不是数组,也不是 null)。 */
|
|
26501
|
+
function isPlainRecord(value) {
|
|
26502
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26503
|
+
}
|
|
26504
|
+
/** 重建信封:只把预览写回被替换的键,其余字段原样保留。 */
|
|
26505
|
+
function rebuildEnvelope(binding, innerPreview) {
|
|
26506
|
+
if (binding.nested === void 0) return JSON.stringify({
|
|
26507
|
+
...binding.record,
|
|
26508
|
+
[binding.key]: innerPreview
|
|
26509
|
+
});
|
|
26510
|
+
return JSON.stringify({
|
|
26511
|
+
...binding.record,
|
|
26512
|
+
[binding.nested.key]: {
|
|
26513
|
+
...binding.nested.record,
|
|
26514
|
+
[binding.key]: innerPreview
|
|
26515
|
+
}
|
|
26516
|
+
});
|
|
26517
|
+
}
|
|
26518
|
+
/** 信封外壳的序列化长度:被替换的字段先按空串算。 */
|
|
26519
|
+
function envelopeShellLength(binding) {
|
|
26520
|
+
if (binding.nested === void 0) return JSON.stringify({
|
|
26521
|
+
...binding.record,
|
|
26522
|
+
[binding.key]: ""
|
|
26523
|
+
}).length;
|
|
26524
|
+
return JSON.stringify({
|
|
26525
|
+
...binding.record,
|
|
26526
|
+
[binding.nested.key]: {
|
|
26527
|
+
...binding.nested.record,
|
|
26528
|
+
[binding.key]: ""
|
|
26529
|
+
}
|
|
26530
|
+
}).length;
|
|
26531
|
+
}
|
|
26381
26532
|
/**
|
|
26382
26533
|
* 解开一层或两层 JSON 包装(支持顶层 JSON 字符串 `"..."` 或双重序列化的 `"{...}"`)。
|
|
26383
26534
|
*/
|
|
@@ -26421,10 +26572,10 @@ function parseJsonCandidate(text) {
|
|
|
26421
26572
|
*
|
|
26422
26573
|
* 规则刻意保守:
|
|
26423
26574
|
* - 截断 JSON、数组一律返回 undefined;
|
|
26575
|
+
* - 白名单字段可以出现在**顶层**(`{ stdout: "..." }`),也可以出现在**1 层嵌套**里
|
|
26576
|
+
* (`{ testOutput: { text: "...", job: {...} } }`,即 `tools.job_output` 的形状);
|
|
26424
26577
|
* - 对象 envelope 除白名单字段外,其余字段序列化后的总长度不超过 `JSON_ENVELOPE_MAX_OTHER_CHARS`;
|
|
26425
|
-
* - 解码并剥离 ANSI
|
|
26426
|
-
* - 返回的 `storeContent` 把解码后的多行日志直接展开(对象 envelope 还会把其余元数据缩进在头部),
|
|
26427
|
-
* 彻底避免单行 minified JSON 因没有真实 `\n` 导致 `store.page` 的按行分页失效。
|
|
26578
|
+
* - 解码并剥离 ANSI 后的日志文本自身必须能通过非源码/非契约的结构化日志投影计划。
|
|
26428
26579
|
*/
|
|
26429
26580
|
function extractJsonEnvelopeLogField(text, toolName) {
|
|
26430
26581
|
const candidate = parseJsonCandidate(text);
|
|
@@ -26437,33 +26588,498 @@ function extractJsonEnvelopeLogField(text, toolName) {
|
|
|
26437
26588
|
return { plan };
|
|
26438
26589
|
}
|
|
26439
26590
|
const { record } = candidate;
|
|
26440
|
-
|
|
26441
|
-
let candidateText = "";
|
|
26591
|
+
const candidates = [];
|
|
26442
26592
|
for (const key of JSON_ENVELOPE_LOG_KEYS) {
|
|
26443
|
-
const
|
|
26444
|
-
if (typeof
|
|
26445
|
-
|
|
26446
|
-
|
|
26593
|
+
const value = record[key];
|
|
26594
|
+
if (typeof value === "string" && value.length > 0) candidates.push({
|
|
26595
|
+
binding: {
|
|
26596
|
+
record,
|
|
26597
|
+
key
|
|
26598
|
+
},
|
|
26599
|
+
text: value
|
|
26600
|
+
});
|
|
26601
|
+
}
|
|
26602
|
+
for (const [outerKey, outerValue] of Object.entries(record)) {
|
|
26603
|
+
if (!isPlainRecord(outerValue)) continue;
|
|
26604
|
+
for (const key of JSON_ENVELOPE_LOG_KEYS) {
|
|
26605
|
+
const value = outerValue[key];
|
|
26606
|
+
if (typeof value === "string" && value.length > 0) candidates.push({
|
|
26607
|
+
binding: {
|
|
26608
|
+
record,
|
|
26609
|
+
key,
|
|
26610
|
+
nested: {
|
|
26611
|
+
key: outerKey,
|
|
26612
|
+
record: outerValue
|
|
26613
|
+
}
|
|
26614
|
+
},
|
|
26615
|
+
text: value
|
|
26616
|
+
});
|
|
26447
26617
|
}
|
|
26448
26618
|
}
|
|
26449
|
-
if (
|
|
26619
|
+
if (candidates.length === 0) return void 0;
|
|
26620
|
+
candidates.sort((left, right) => right.text.length - left.text.length);
|
|
26621
|
+
const picked = candidates[0];
|
|
26622
|
+
const binding = picked.binding;
|
|
26623
|
+
const candidateText = picked.text;
|
|
26624
|
+
const boundKey = binding.key;
|
|
26625
|
+
const nested = binding.nested;
|
|
26626
|
+
const holder = nested === void 0 ? record : nested.record;
|
|
26450
26627
|
const rest = {};
|
|
26451
26628
|
for (const [key, value] of Object.entries(record)) {
|
|
26452
|
-
if (key ===
|
|
26629
|
+
if (nested === void 0 ? key === boundKey : key === nested.key) continue;
|
|
26453
26630
|
if (typeof value === "string" && (value.length > 400 || looksLikeSourceCode(value) || looksLikeContract(value))) return;
|
|
26454
26631
|
rest[key] = value;
|
|
26455
26632
|
}
|
|
26456
|
-
|
|
26633
|
+
let restJson;
|
|
26634
|
+
if (nested === void 0) restJson = JSON.stringify(rest);
|
|
26635
|
+
else {
|
|
26636
|
+
const innerRest = {};
|
|
26637
|
+
for (const [key, value] of Object.entries(nested.record)) {
|
|
26638
|
+
if (key === boundKey) continue;
|
|
26639
|
+
if (typeof value === "string" && (value.length > 400 || looksLikeSourceCode(value) || looksLikeContract(value))) return;
|
|
26640
|
+
innerRest[key] = value;
|
|
26641
|
+
}
|
|
26642
|
+
restJson = JSON.stringify({
|
|
26643
|
+
...rest,
|
|
26644
|
+
[nested.key]: innerRest
|
|
26645
|
+
});
|
|
26646
|
+
}
|
|
26647
|
+
if (restJson.length > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
|
|
26457
26648
|
const cleanField = stripAnsi(candidateText);
|
|
26458
26649
|
if (isJsonDocument(cleanField) || isMixedEvidenceOutput(cleanField)) return void 0;
|
|
26459
26650
|
const plan = planPlainTextProjection(cleanField, toolName);
|
|
26460
|
-
if (plan.kind !== "preview")
|
|
26651
|
+
if (plan.kind !== "preview") {
|
|
26652
|
+
if (plan.strategy === "verbatim-unparsed") {
|
|
26653
|
+
const meta = shellMetadataOf(holder);
|
|
26654
|
+
if (meta !== void 0) {
|
|
26655
|
+
const sections = buildShellSections(cleanField);
|
|
26656
|
+
if (sections !== void 0) return {
|
|
26657
|
+
envelope: binding,
|
|
26658
|
+
plan: {
|
|
26659
|
+
kind: "preview",
|
|
26660
|
+
strategy: "shell-output",
|
|
26661
|
+
sections: withShellMetadata(sections, meta)
|
|
26662
|
+
}
|
|
26663
|
+
};
|
|
26664
|
+
}
|
|
26665
|
+
}
|
|
26666
|
+
const multiSection = planMultiSectionProjection(cleanField);
|
|
26667
|
+
if (multiSection !== void 0) return {
|
|
26668
|
+
envelope: binding,
|
|
26669
|
+
plan: multiSection
|
|
26670
|
+
};
|
|
26671
|
+
return;
|
|
26672
|
+
}
|
|
26673
|
+
return {
|
|
26674
|
+
envelope: binding,
|
|
26675
|
+
plan
|
|
26676
|
+
};
|
|
26677
|
+
}
|
|
26678
|
+
/** 规范 glob 路径表。 */
|
|
26679
|
+
function canonicalGlobPaths(value) {
|
|
26680
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
26681
|
+
const paths = [];
|
|
26682
|
+
for (const entry of value) {
|
|
26683
|
+
if (typeof entry !== "string") return void 0;
|
|
26684
|
+
paths.push(entry);
|
|
26685
|
+
}
|
|
26686
|
+
return paths;
|
|
26687
|
+
}
|
|
26688
|
+
/** 规范 grep 命中表。 */
|
|
26689
|
+
function canonicalGrepMatches(value) {
|
|
26690
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
26691
|
+
const matches = [];
|
|
26692
|
+
for (const entry of value) {
|
|
26693
|
+
if (!isPlainRecord(entry)) return void 0;
|
|
26694
|
+
if (typeof entry.path !== "string") return void 0;
|
|
26695
|
+
if (!Number.isSafeInteger(entry.lineNumber)) return void 0;
|
|
26696
|
+
if (typeof entry.line !== "string") return void 0;
|
|
26697
|
+
matches.push({
|
|
26698
|
+
path: entry.path,
|
|
26699
|
+
lineNumber: entry.lineNumber,
|
|
26700
|
+
line: entry.line
|
|
26701
|
+
});
|
|
26702
|
+
}
|
|
26703
|
+
return matches;
|
|
26704
|
+
}
|
|
26705
|
+
/** 规范读取结果。 */
|
|
26706
|
+
function canonicalReadResult(value) {
|
|
26707
|
+
if (!isPlainRecord(value)) return void 0;
|
|
26708
|
+
if (typeof value.path !== "string" || value.path.trim() === "") return void 0;
|
|
26709
|
+
if (!Array.isArray(value.lines) || value.lines.length === 0) return void 0;
|
|
26710
|
+
const lines = [];
|
|
26711
|
+
for (const entry of value.lines) {
|
|
26712
|
+
if (!isPlainRecord(entry)) return void 0;
|
|
26713
|
+
if (!Number.isSafeInteger(entry.number)) return void 0;
|
|
26714
|
+
if (typeof entry.text !== "string") return void 0;
|
|
26715
|
+
lines.push({
|
|
26716
|
+
number: entry.number,
|
|
26717
|
+
text: entry.text
|
|
26718
|
+
});
|
|
26719
|
+
}
|
|
26461
26720
|
return {
|
|
26462
|
-
|
|
26721
|
+
path: value.path,
|
|
26722
|
+
...typeof value.offset === "number" && Number.isFinite(value.offset) ? { offset: value.offset } : {},
|
|
26723
|
+
lines
|
|
26724
|
+
};
|
|
26725
|
+
}
|
|
26726
|
+
/** 在顶层或 1 层嵌套里找规范 grep 命中表。 */
|
|
26727
|
+
function findGrepBinding(record) {
|
|
26728
|
+
const top = canonicalGrepMatches(record.matches);
|
|
26729
|
+
if (top !== void 0) return {
|
|
26730
|
+
binding: {
|
|
26463
26731
|
record,
|
|
26464
|
-
key:
|
|
26732
|
+
key: "matches"
|
|
26465
26733
|
},
|
|
26466
|
-
|
|
26734
|
+
matches: top
|
|
26735
|
+
};
|
|
26736
|
+
for (const [outerKey, outerValue] of Object.entries(record)) {
|
|
26737
|
+
if (!isPlainRecord(outerValue)) continue;
|
|
26738
|
+
const nested = canonicalGrepMatches(outerValue.matches);
|
|
26739
|
+
if (nested !== void 0) return {
|
|
26740
|
+
binding: {
|
|
26741
|
+
record,
|
|
26742
|
+
key: "matches",
|
|
26743
|
+
nested: {
|
|
26744
|
+
key: outerKey,
|
|
26745
|
+
record: outerValue
|
|
26746
|
+
}
|
|
26747
|
+
},
|
|
26748
|
+
matches: nested
|
|
26749
|
+
};
|
|
26750
|
+
}
|
|
26751
|
+
}
|
|
26752
|
+
/** 在顶层或 1 层嵌套里找规范 glob 路径表。 */
|
|
26753
|
+
function findGlobBinding(record) {
|
|
26754
|
+
const top = canonicalGlobPaths(record.paths);
|
|
26755
|
+
if (top !== void 0) return {
|
|
26756
|
+
binding: {
|
|
26757
|
+
record,
|
|
26758
|
+
key: "paths"
|
|
26759
|
+
},
|
|
26760
|
+
paths: top,
|
|
26761
|
+
...typeof record.root === "string" ? { root: record.root } : {}
|
|
26762
|
+
};
|
|
26763
|
+
for (const [outerKey, outerValue] of Object.entries(record)) {
|
|
26764
|
+
if (!isPlainRecord(outerValue)) continue;
|
|
26765
|
+
const nested = canonicalGlobPaths(outerValue.paths);
|
|
26766
|
+
if (nested !== void 0) return {
|
|
26767
|
+
binding: {
|
|
26768
|
+
record,
|
|
26769
|
+
key: "paths",
|
|
26770
|
+
nested: {
|
|
26771
|
+
key: outerKey,
|
|
26772
|
+
record: outerValue
|
|
26773
|
+
}
|
|
26774
|
+
},
|
|
26775
|
+
paths: nested,
|
|
26776
|
+
...typeof outerValue.root === "string" ? { root: outerValue.root } : {}
|
|
26777
|
+
};
|
|
26778
|
+
}
|
|
26779
|
+
}
|
|
26780
|
+
/** grep 信封的预览段落:复用已有的完整计数策略。 */
|
|
26781
|
+
function buildGrepEnvelopeSections(matches) {
|
|
26782
|
+
const files = /* @__PURE__ */ new Map();
|
|
26783
|
+
for (const match of matches) {
|
|
26784
|
+
const entry = files.get(match.path);
|
|
26785
|
+
if (entry === void 0) files.set(match.path, {
|
|
26786
|
+
count: 1,
|
|
26787
|
+
first: match.line
|
|
26788
|
+
});
|
|
26789
|
+
else entry.count += 1;
|
|
26790
|
+
}
|
|
26791
|
+
return buildGrepSections({
|
|
26792
|
+
files,
|
|
26793
|
+
total: matches.length
|
|
26794
|
+
});
|
|
26795
|
+
}
|
|
26796
|
+
/** glob 信封的预览段落:复用已有的扩展名分布策略。 */
|
|
26797
|
+
function buildGlobEnvelopeSections(paths, root) {
|
|
26798
|
+
const sections = buildGlobSections(paths.join("\n"));
|
|
26799
|
+
if (root === void 0) return sections;
|
|
26800
|
+
return [{ lines: [`root: ${clip(root, 160)}`] }, ...sections];
|
|
26801
|
+
}
|
|
26802
|
+
/**
|
|
26803
|
+
* PTC 的结构化工具返回信封:`grep` 的 `matches` 与 `glob` 的 `paths`。
|
|
26804
|
+
*
|
|
26805
|
+
* 两种形状都认:顶层 `{ matches }` / `{ root, paths }`,以及 1 层嵌套
|
|
26806
|
+
* `{ batch: { matches } }` / `{ batch: { root, paths } }`。
|
|
26807
|
+
*
|
|
26808
|
+
* 落库用缩进 JSON 而不是原始的 minified 文本:minified 文本里没有真换行,
|
|
26809
|
+
* `store.page` 的「按行翻页」会退化成一次吐完一整行;缩进后每行一个字段,
|
|
26810
|
+
* 翻页干净,而且外层与内层的同级元数据一个不少。
|
|
26811
|
+
*/
|
|
26812
|
+
function extractStructuredToolEnvelope(text) {
|
|
26813
|
+
const candidate = parseJsonCandidate(text);
|
|
26814
|
+
if (candidate === void 0 || candidate.kind !== "object") return void 0;
|
|
26815
|
+
const { record } = candidate;
|
|
26816
|
+
if (collectCompositeEntries(record).length !== 1) return void 0;
|
|
26817
|
+
const grep = findGrepBinding(record);
|
|
26818
|
+
if (grep !== void 0) {
|
|
26819
|
+
if (envelopeShellLength(grep.binding) > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
|
|
26820
|
+
return {
|
|
26821
|
+
binding: grep.binding,
|
|
26822
|
+
plan: {
|
|
26823
|
+
kind: "preview",
|
|
26824
|
+
strategy: "grep-results",
|
|
26825
|
+
sections: buildGrepEnvelopeSections(grep.matches)
|
|
26826
|
+
},
|
|
26827
|
+
storeContent: JSON.stringify(record, null, 2)
|
|
26828
|
+
};
|
|
26829
|
+
}
|
|
26830
|
+
const glob = findGlobBinding(record);
|
|
26831
|
+
if (glob !== void 0) {
|
|
26832
|
+
if (envelopeShellLength(glob.binding) > JSON_ENVELOPE_MAX_OTHER_CHARS) return void 0;
|
|
26833
|
+
return {
|
|
26834
|
+
binding: glob.binding,
|
|
26835
|
+
plan: {
|
|
26836
|
+
kind: "preview",
|
|
26837
|
+
strategy: "glob-results",
|
|
26838
|
+
sections: buildGlobEnvelopeSections(glob.paths, glob.root)
|
|
26839
|
+
},
|
|
26840
|
+
storeContent: JSON.stringify(record, null, 2)
|
|
26841
|
+
};
|
|
26842
|
+
}
|
|
26843
|
+
}
|
|
26844
|
+
/** 日志正文的错误行数与尾部若干行。 */
|
|
26845
|
+
function logSummaryOf(text) {
|
|
26846
|
+
const lines = text.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
|
|
26847
|
+
const errorLines = lines.filter((line) => SHELL_ERROR_LINE.test(line)).length;
|
|
26848
|
+
const summary = [];
|
|
26849
|
+
if (errorLines > 0) summary.push(`errors: ${errorLines}`);
|
|
26850
|
+
for (const line of lines.slice(-2)) summary.push(clip(line.trim(), 160));
|
|
26851
|
+
return summary;
|
|
26852
|
+
}
|
|
26853
|
+
/**
|
|
26854
|
+
* 把一个普通 JSON 对象分类成复合信封里的一个成员。
|
|
26855
|
+
*
|
|
26856
|
+
* 认得四种形状:读取结果、grep 命中表、glob 路径表、日志正文。
|
|
26857
|
+
* 分不出来就返回 undefined —— 宁可少认一个成员,也不把不相干的数据写进摘要。
|
|
26858
|
+
* @param key - 该成员所在的键。
|
|
26859
|
+
* @param value - 该成员的值。
|
|
26860
|
+
* @returns 识别出的成员,或 undefined。
|
|
26861
|
+
*/
|
|
26862
|
+
function classifyCompositeEntry(key, value) {
|
|
26863
|
+
const jsonLength = JSON.stringify(value)?.length ?? 0;
|
|
26864
|
+
const matches = canonicalGrepMatches(value.matches);
|
|
26865
|
+
if (matches !== void 0) {
|
|
26866
|
+
const counts = /* @__PURE__ */ new Map();
|
|
26867
|
+
for (const match of matches) counts.set(match.path, (counts.get(match.path) ?? 0) + 1);
|
|
26868
|
+
const files = [...counts.entries()].map(([path, count]) => ({
|
|
26869
|
+
path,
|
|
26870
|
+
count
|
|
26871
|
+
})).sort((left, right) => right.count - left.count);
|
|
26872
|
+
return {
|
|
26873
|
+
kind: "grep",
|
|
26874
|
+
key,
|
|
26875
|
+
jsonLength,
|
|
26876
|
+
total: matches.length,
|
|
26877
|
+
files
|
|
26878
|
+
};
|
|
26879
|
+
}
|
|
26880
|
+
const paths = canonicalGlobPaths(value.paths);
|
|
26881
|
+
if (paths !== void 0) return {
|
|
26882
|
+
kind: "glob",
|
|
26883
|
+
key,
|
|
26884
|
+
jsonLength,
|
|
26885
|
+
count: paths.length,
|
|
26886
|
+
...typeof value.root === "string" ? { root: value.root } : {},
|
|
26887
|
+
sample: paths.slice(0, 3)
|
|
26888
|
+
};
|
|
26889
|
+
const read = canonicalReadResult(value);
|
|
26890
|
+
if (read !== void 0) {
|
|
26891
|
+
let first = Number.POSITIVE_INFINITY;
|
|
26892
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
26893
|
+
for (const line of read.lines) {
|
|
26894
|
+
if (line.number < first) first = line.number;
|
|
26895
|
+
if (line.number > last) last = line.number;
|
|
26896
|
+
}
|
|
26897
|
+
return {
|
|
26898
|
+
kind: "read",
|
|
26899
|
+
key,
|
|
26900
|
+
jsonLength,
|
|
26901
|
+
path: read.path,
|
|
26902
|
+
lineCount: read.lines.length,
|
|
26903
|
+
first,
|
|
26904
|
+
last,
|
|
26905
|
+
firstLines: read.lines.slice(0, 2).map((line) => `${line.number}: ${line.text}`)
|
|
26906
|
+
};
|
|
26907
|
+
}
|
|
26908
|
+
for (const logKey of JSON_ENVELOPE_LOG_KEYS) {
|
|
26909
|
+
const logText = value[logKey];
|
|
26910
|
+
if (typeof logText !== "string" || logText.length === 0) continue;
|
|
26911
|
+
if (looksLikeSourceCode(logText) || looksLikeContract(logText) || isMixedEvidenceOutput(logText)) return void 0;
|
|
26912
|
+
const meta = shellMetadataOf(value);
|
|
26913
|
+
return {
|
|
26914
|
+
kind: "log",
|
|
26915
|
+
key,
|
|
26916
|
+
jsonLength,
|
|
26917
|
+
chars: logText.length,
|
|
26918
|
+
...meta?.exitCode === void 0 ? {} : { exitCode: meta.exitCode },
|
|
26919
|
+
...meta?.status === void 0 ? {} : { status: meta.status },
|
|
26920
|
+
summary: logSummaryOf(logText)
|
|
26921
|
+
};
|
|
26922
|
+
}
|
|
26923
|
+
}
|
|
26924
|
+
/**
|
|
26925
|
+
* 收集一份 JSON 信封里的全部成员(顶层、1 层嵌套、数组元素)。
|
|
26926
|
+
* @param record - 顶层记录。
|
|
26927
|
+
* @returns 识别出的成员。
|
|
26928
|
+
*/
|
|
26929
|
+
function collectCompositeEntries(record) {
|
|
26930
|
+
const entries = [];
|
|
26931
|
+
const push = (key, value) => {
|
|
26932
|
+
const entry = classifyCompositeEntry(key, value);
|
|
26933
|
+
if (entry === void 0) return false;
|
|
26934
|
+
entries.push(entry);
|
|
26935
|
+
return true;
|
|
26936
|
+
};
|
|
26937
|
+
const root = classifyCompositeEntry("(root)", record);
|
|
26938
|
+
if (root !== void 0) entries.push(root);
|
|
26939
|
+
for (const [key, value] of Object.entries(record)) {
|
|
26940
|
+
if (Array.isArray(value)) {
|
|
26941
|
+
value.forEach((item, index) => {
|
|
26942
|
+
if (isPlainRecord(item)) push(`${key}[${index}]`, item);
|
|
26943
|
+
});
|
|
26944
|
+
continue;
|
|
26945
|
+
}
|
|
26946
|
+
if (!isPlainRecord(value)) continue;
|
|
26947
|
+
if (push(key, value)) continue;
|
|
26948
|
+
for (const [subKey, subValue] of Object.entries(value)) if (isPlainRecord(subValue)) push(`${key}.${subKey}`, subValue);
|
|
26949
|
+
}
|
|
26950
|
+
return entries;
|
|
26951
|
+
}
|
|
26952
|
+
/** 复合信封的预览段落:逐个成员给出它是什么、有多大。 */
|
|
26953
|
+
function buildCompositeSections(entries) {
|
|
26954
|
+
const allReads = entries.every((entry) => entry.kind === "read");
|
|
26955
|
+
const shown = entries.slice(0, 20);
|
|
26956
|
+
const summaries = [];
|
|
26957
|
+
const firstLines = [];
|
|
26958
|
+
const details = [];
|
|
26959
|
+
for (const entry of shown) {
|
|
26960
|
+
if (entry.kind === "read") {
|
|
26961
|
+
summaries.push(` ${clip(entry.key, 60)}: ${clip(entry.path, 120)} lines ${entry.first}-${entry.last} (${entry.lineCount})`);
|
|
26962
|
+
continue;
|
|
26963
|
+
}
|
|
26964
|
+
if (entry.kind === "grep") {
|
|
26965
|
+
summaries.push(` ${clip(entry.key, 60)}: grep matches=${entry.total} across ${entry.files.length} files`);
|
|
26966
|
+
for (const file of entry.files.slice(0, 3)) summaries.push(` ${clip(file.path, 100)}: ${file.count}`);
|
|
26967
|
+
continue;
|
|
26968
|
+
}
|
|
26969
|
+
if (entry.kind === "glob") {
|
|
26970
|
+
const root = entry.root === void 0 ? "" : ` root=${clip(entry.root, 80)}`;
|
|
26971
|
+
summaries.push(` ${clip(entry.key, 60)}: glob paths=${entry.count}${root}`);
|
|
26972
|
+
for (const sample of entry.sample) summaries.push(` ${clip(sample, 140)}`);
|
|
26973
|
+
continue;
|
|
26974
|
+
}
|
|
26975
|
+
const state = [entry.exitCode === void 0 ? "" : `exit code=${entry.exitCode}`, entry.status === void 0 ? "" : `status=${entry.status}`].filter((part) => part !== "").join(" ");
|
|
26976
|
+
summaries.push(` ${clip(entry.key, 60)}: log chars=${entry.chars}${state === "" ? "" : ` (${state})`}`);
|
|
26977
|
+
for (const line of entry.summary) details.push(` ${clip(line, 160)}`);
|
|
26978
|
+
}
|
|
26979
|
+
if (entries.length > shown.length) summaries.push(` … ${entries.length - shown.length} more entries (full list via ref)`);
|
|
26980
|
+
for (const entry of shown.slice(0, 3)) {
|
|
26981
|
+
if (entry.kind !== "read") continue;
|
|
26982
|
+
firstLines.push(` ${clip(entry.path, 100)}`);
|
|
26983
|
+
for (const line of entry.firstLines) firstLines.push(` ${clip(line, 160)}`);
|
|
26984
|
+
}
|
|
26985
|
+
const sections = [{ lines: [allReads ? `reads: ${entries.length} (complete set)` : `results: ${entries.length} (complete set)`] }, { lines: summaries }];
|
|
26986
|
+
const extra = [...firstLines, ...details];
|
|
26987
|
+
if (extra.length > 0) sections.push({
|
|
26988
|
+
lines: [allReads ? "first lines:" : "detail:", ...extra],
|
|
26989
|
+
optional: true
|
|
26990
|
+
});
|
|
26991
|
+
return sections;
|
|
26992
|
+
}
|
|
26993
|
+
/**
|
|
26994
|
+
* 复合 JSON 信封的投影计划。
|
|
26995
|
+
*
|
|
26996
|
+
* 一份结果里混着多个成员(grep + read + glob + log)时,逐个成员摘要:
|
|
26997
|
+
* 模型要的是「这一趟拿到了什么、各自多大」,而不是某一份成员的完整正文。
|
|
26998
|
+
*
|
|
26999
|
+
* 触发条件:已经超长(或被 spill 截断)、成员不止一个、或单个成员本身就装不下预算。
|
|
27000
|
+
* @param text - 工具结果原文。
|
|
27001
|
+
* @param budgetMaxChars - 当前预览预算的字符上限。
|
|
27002
|
+
* @param oversized - 是否已经超长或被 spill 截断。
|
|
27003
|
+
* @returns 投影计划,或 undefined。
|
|
27004
|
+
*/
|
|
27005
|
+
function extractCompositeEnvelope(text, budgetMaxChars, oversized) {
|
|
27006
|
+
const candidate = parseJsonCandidate(text);
|
|
27007
|
+
if (candidate === void 0 || candidate.kind !== "object") return void 0;
|
|
27008
|
+
const { record } = candidate;
|
|
27009
|
+
const entries = collectCompositeEntries(record);
|
|
27010
|
+
if (entries.length === 0) return void 0;
|
|
27011
|
+
if (!(oversized || entries.length >= 2 || entries.some((entry) => entry.jsonLength > budgetMaxChars))) return void 0;
|
|
27012
|
+
return {
|
|
27013
|
+
kind: "preview",
|
|
27014
|
+
strategy: "file-outline",
|
|
27015
|
+
sections: buildCompositeSections(entries),
|
|
27016
|
+
storeContent: JSON.stringify(record, null, 2)
|
|
27017
|
+
};
|
|
27018
|
+
}
|
|
27019
|
+
/** 多节输出的分节头:整行大写,且至少含一个分隔符(空格、斜杠、连字符)。 */
|
|
27020
|
+
const MULTI_SECTION_HEADER = /^[A-Z][A-Z0-9]*(?:[ _/()-][A-Z0-9]+)*$/;
|
|
27021
|
+
/** 判断一行是不是分节头;光秃秃一个大写词不算一节。 */
|
|
27022
|
+
function isMultiSectionHeader(trimmed) {
|
|
27023
|
+
if (trimmed.length < 4) return false;
|
|
27024
|
+
if (!MULTI_SECTION_HEADER.test(trimmed)) return false;
|
|
27025
|
+
return /[ _/-]/.test(trimmed);
|
|
27026
|
+
}
|
|
27027
|
+
/** 多节输出的预览段落:分了几节、每节多少行与行范围、每节前几行。 */
|
|
27028
|
+
function buildMultiSectionSections(text) {
|
|
27029
|
+
const lines = text.split("\n");
|
|
27030
|
+
const headers = [];
|
|
27031
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
27032
|
+
const trimmed = lines[index].trim();
|
|
27033
|
+
if (!isMultiSectionHeader(trimmed)) continue;
|
|
27034
|
+
if ((lines[index + 1] ?? "").trim() === "") continue;
|
|
27035
|
+
headers.push({
|
|
27036
|
+
name: trimmed,
|
|
27037
|
+
start: index
|
|
27038
|
+
});
|
|
27039
|
+
}
|
|
27040
|
+
if (headers.length < 2) return void 0;
|
|
27041
|
+
const summaries = [];
|
|
27042
|
+
const firstLines = [];
|
|
27043
|
+
for (let index = 0; index < headers.length; index += 1) {
|
|
27044
|
+
const header = headers[index];
|
|
27045
|
+
const end = headers[index + 1]?.start ?? lines.length;
|
|
27046
|
+
const body = lines.slice(header.start + 1, end).map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
|
|
27047
|
+
let first = Number.POSITIVE_INFINITY;
|
|
27048
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
27049
|
+
for (const line of body) {
|
|
27050
|
+
const matched = /^\s*(\d+)(?::|\t|\|)/.exec(line);
|
|
27051
|
+
if (matched === null) continue;
|
|
27052
|
+
const number = Number(matched[1]);
|
|
27053
|
+
if (number < first) first = number;
|
|
27054
|
+
if (number > last) last = number;
|
|
27055
|
+
}
|
|
27056
|
+
const range = Number.isFinite(first) && Number.isFinite(last) ? ` (${first}-${last})` : "";
|
|
27057
|
+
summaries.push(` ${clip(header.name, 80)}: ${body.length} lines${range}`);
|
|
27058
|
+
}
|
|
27059
|
+
for (let index = 0; index < Math.min(headers.length, 3); index += 1) {
|
|
27060
|
+
const header = headers[index];
|
|
27061
|
+
const end = headers[index + 1]?.start ?? lines.length;
|
|
27062
|
+
const body = lines.slice(header.start + 1, end).map((line) => line.trim()).filter((line) => line !== "");
|
|
27063
|
+
firstLines.push(` ${clip(header.name, 80)}`);
|
|
27064
|
+
for (const line of body.slice(0, 2)) firstLines.push(` ${clip(line, 160)}`);
|
|
27065
|
+
}
|
|
27066
|
+
return [
|
|
27067
|
+
{ lines: [`sections: ${headers.length} (complete set)`] },
|
|
27068
|
+
{ lines: summaries },
|
|
27069
|
+
{
|
|
27070
|
+
lines: ["first lines:", ...firstLines],
|
|
27071
|
+
optional: true
|
|
27072
|
+
}
|
|
27073
|
+
];
|
|
27074
|
+
}
|
|
27075
|
+
/** 多节输出的投影计划。 */
|
|
27076
|
+
function planMultiSectionProjection(text) {
|
|
27077
|
+
const sections = buildMultiSectionSections(text);
|
|
27078
|
+
if (sections === void 0) return void 0;
|
|
27079
|
+
return {
|
|
27080
|
+
kind: "preview",
|
|
27081
|
+
strategy: "structured-rows",
|
|
27082
|
+
sections
|
|
26467
27083
|
};
|
|
26468
27084
|
}
|
|
26469
27085
|
/** 对一段纯文本(非 JSON 文档)做投影计划判定。 */
|
|
@@ -26524,8 +27140,11 @@ function planPlainTextProjection(rawText, toolName) {
|
|
|
26524
27140
|
* 然后是 JSON envelope(只允许纯日志字段投影,混合证据一律原样),
|
|
26525
27141
|
* 接着是测试失败集、grep、结构化、glob、shell 这些有明确形状的,
|
|
26526
27142
|
* 最后落到「无法解析 → 原样」。
|
|
27143
|
+
*
|
|
27144
|
+
* `spilled` 与超长门槛决定多节/多读输出还有没有资格进新投影:
|
|
27145
|
+
* 短于门槛、又没有 spill 的普通源码与契约读一律逐字保留。
|
|
26527
27146
|
*/
|
|
26528
|
-
function planProjection(text, toolName, navigation, retrievalOnly) {
|
|
27147
|
+
function planProjection(text, toolName, navigation, retrievalOnly, spilled = false, budgetMaxChars = Number.POSITIVE_INFINITY) {
|
|
26529
27148
|
if (TOOL_SUBAGENT.test(toolName)) return {
|
|
26530
27149
|
kind: "verbatim",
|
|
26531
27150
|
strategy: "verbatim-subagent-report"
|
|
@@ -26535,10 +27154,21 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
|
|
|
26535
27154
|
kind: "verbatim",
|
|
26536
27155
|
strategy: "verbatim-intent"
|
|
26537
27156
|
};
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
27157
|
+
const oversized = text.length >= MULTI_OUTPUT_MIN_CHARS || spilled;
|
|
27158
|
+
if (TOOL_SOURCE.test(toolName) && navigation !== true) {
|
|
27159
|
+
if (oversized && !isJsonDocument(text)) {
|
|
27160
|
+
const multiSection = planMultiSectionProjection(text);
|
|
27161
|
+
if (multiSection !== void 0) return multiSection;
|
|
27162
|
+
}
|
|
27163
|
+
return {
|
|
27164
|
+
kind: "verbatim",
|
|
27165
|
+
strategy: "verbatim-source-code"
|
|
27166
|
+
};
|
|
27167
|
+
}
|
|
27168
|
+
if (oversized && !isJsonDocument(text)) {
|
|
27169
|
+
const multiSection = planMultiSectionProjection(text);
|
|
27170
|
+
if (multiSection !== void 0) return multiSection;
|
|
27171
|
+
}
|
|
26542
27172
|
if (looksLikeContract(text)) return {
|
|
26543
27173
|
kind: "verbatim",
|
|
26544
27174
|
strategy: "verbatim-contract"
|
|
@@ -26556,6 +27186,14 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
|
|
|
26556
27186
|
if (outline !== void 0) return outline;
|
|
26557
27187
|
}
|
|
26558
27188
|
if (isJsonDocument(text)) {
|
|
27189
|
+
const structuredTool = extractStructuredToolEnvelope(text);
|
|
27190
|
+
if (structuredTool !== void 0) return {
|
|
27191
|
+
kind: "preview",
|
|
27192
|
+
strategy: structuredTool.plan.strategy,
|
|
27193
|
+
sections: structuredTool.plan.sections,
|
|
27194
|
+
envelope: structuredTool.binding,
|
|
27195
|
+
storeContent: structuredTool.storeContent
|
|
27196
|
+
};
|
|
26559
27197
|
const extracted = extractJsonEnvelopeLogField(text, toolName);
|
|
26560
27198
|
if (extracted !== void 0) return {
|
|
26561
27199
|
kind: "preview",
|
|
@@ -26563,6 +27201,8 @@ function planProjection(text, toolName, navigation, retrievalOnly) {
|
|
|
26563
27201
|
sections: extracted.plan.sections,
|
|
26564
27202
|
...extracted.envelope === void 0 ? {} : { envelope: extracted.envelope }
|
|
26565
27203
|
};
|
|
27204
|
+
const composite = extractCompositeEnvelope(text, budgetMaxChars, oversized);
|
|
27205
|
+
if (composite !== void 0) return composite;
|
|
26566
27206
|
return {
|
|
26567
27207
|
kind: "verbatim",
|
|
26568
27208
|
strategy: "verbatim-unparsed"
|
|
@@ -26581,34 +27221,30 @@ function footerFor(refId) {
|
|
|
26581
27221
|
* 入库失败一律降级为「保持原样」—— 没有 ref 的预览等于把原文丢了。
|
|
26582
27222
|
*/
|
|
26583
27223
|
async function decide(args) {
|
|
26584
|
-
const {
|
|
27224
|
+
const { input, budget } = args;
|
|
27225
|
+
const spill = await resolveSpillText(args.text);
|
|
27226
|
+
const text = spill.text;
|
|
26585
27227
|
if (text.length <= budget.maxChars) return { reason: "verbatim-small" };
|
|
26586
|
-
const plan = planProjection(text, input.toolName, input.navigation, input.retrievalOnly);
|
|
27228
|
+
const plan = planProjection(text, input.toolName, input.navigation, input.retrievalOnly, spill.spilled, budget.maxChars);
|
|
26587
27229
|
if (plan.kind === "verbatim") return { reason: plan.strategy };
|
|
26588
27230
|
if (input.isError === true && plan.strategy !== "test-failure" && plan.strategy !== "shell-output") return { reason: "verbatim-unparsed" };
|
|
26589
27231
|
const header = `[cache-warmup preview] strategy=${plan.strategy} input=${text.length}ch`;
|
|
26590
27232
|
const footerBudget = footerFor(`ref://${input.sessionId}/${"0".repeat(64)}`).length;
|
|
26591
27233
|
const jsonEscaped = plan.envelope !== void 0;
|
|
26592
|
-
const
|
|
26593
|
-
|
|
26594
|
-
[plan.envelope.key]: ""
|
|
26595
|
-
}).length;
|
|
26596
|
-
const bodyBudget = budget.maxChars - header.length - footerBudget - envelopeShellLength - (jsonEscaped ? 4 : 2);
|
|
27234
|
+
const envelopeShell = plan.envelope === void 0 ? 0 : envelopeShellLength(plan.envelope);
|
|
27235
|
+
const bodyBudget = budget.maxChars - header.length - footerBudget - envelopeShell - (jsonEscaped ? 4 : 2);
|
|
26597
27236
|
if (bodyBudget <= 0) return { reason: "verbatim-budget" };
|
|
26598
27237
|
const body = fitSections(plan.sections, bodyBudget, jsonEscaped);
|
|
26599
27238
|
if (body === void 0) return { reason: "verbatim-budget" };
|
|
26600
27239
|
const store = input.store;
|
|
26601
27240
|
if (store === void 0) return { reason: "store-unavailable" };
|
|
26602
27241
|
const placeholderInner = `${header}\n${body}\n${footerFor(`ref://${input.sessionId}/${"0".repeat(64)}`)}`;
|
|
26603
|
-
if ((plan.envelope === void 0 ? placeholderInner :
|
|
26604
|
-
...plan.envelope.record,
|
|
26605
|
-
[plan.envelope.key]: placeholderInner
|
|
26606
|
-
})).length > budget.maxChars) return { reason: "verbatim-budget" };
|
|
27242
|
+
if ((plan.envelope === void 0 ? placeholderInner : rebuildEnvelope(plan.envelope, placeholderInner)).length > budget.maxChars) return { reason: "verbatim-budget" };
|
|
26607
27243
|
let reference;
|
|
26608
27244
|
try {
|
|
26609
27245
|
reference = await store.put({
|
|
26610
27246
|
sessionId: input.sessionId,
|
|
26611
|
-
content: text,
|
|
27247
|
+
content: plan.storeContent ?? text,
|
|
26612
27248
|
toolName: input.toolName,
|
|
26613
27249
|
at: args.at
|
|
26614
27250
|
});
|
|
@@ -26616,15 +27252,13 @@ async function decide(args) {
|
|
|
26616
27252
|
return { reason: "store-unavailable" };
|
|
26617
27253
|
}
|
|
26618
27254
|
const innerPreview = `${header}\n${body}\n${footerFor(reference.refId)}`;
|
|
26619
|
-
const preview = plan.envelope === void 0 ? innerPreview :
|
|
26620
|
-
...plan.envelope.record,
|
|
26621
|
-
[plan.envelope.key]: innerPreview
|
|
26622
|
-
});
|
|
27255
|
+
const preview = plan.envelope === void 0 ? innerPreview : rebuildEnvelope(plan.envelope, innerPreview);
|
|
26623
27256
|
if (preview.length > budget.maxChars) return { reason: "verbatim-budget" };
|
|
26624
27257
|
return {
|
|
26625
27258
|
preview,
|
|
26626
27259
|
refId: reference.refId,
|
|
26627
|
-
strategy: plan.strategy
|
|
27260
|
+
strategy: plan.strategy,
|
|
27261
|
+
inputChars: text.length
|
|
26628
27262
|
};
|
|
26629
27263
|
}
|
|
26630
27264
|
/** 块数组里文本字符数的合计。 */
|
|
@@ -26651,9 +27285,8 @@ async function projectToolResult(input) {
|
|
|
26651
27285
|
at
|
|
26652
27286
|
};
|
|
26653
27287
|
if (typeof input.content === "string") {
|
|
26654
|
-
const text = input.content;
|
|
26655
27288
|
const decision = await decide({
|
|
26656
|
-
text,
|
|
27289
|
+
text: input.content,
|
|
26657
27290
|
input,
|
|
26658
27291
|
budget,
|
|
26659
27292
|
at
|
|
@@ -26663,8 +27296,8 @@ async function projectToolResult(input) {
|
|
|
26663
27296
|
content: input.content,
|
|
26664
27297
|
changed: false,
|
|
26665
27298
|
metrics: {
|
|
26666
|
-
inputChars:
|
|
26667
|
-
outputChars:
|
|
27299
|
+
inputChars: input.content.length,
|
|
27300
|
+
outputChars: input.content.length,
|
|
26668
27301
|
omittedChars: 0,
|
|
26669
27302
|
strategy: decision.reason
|
|
26670
27303
|
}
|
|
@@ -26676,9 +27309,9 @@ async function projectToolResult(input) {
|
|
|
26676
27309
|
refId: decision.refId,
|
|
26677
27310
|
preview: decision.preview,
|
|
26678
27311
|
metrics: {
|
|
26679
|
-
inputChars:
|
|
27312
|
+
inputChars: decision.inputChars,
|
|
26680
27313
|
outputChars: decision.preview.length,
|
|
26681
|
-
omittedChars: Math.max(0,
|
|
27314
|
+
omittedChars: Math.max(0, decision.inputChars - decision.preview.length),
|
|
26682
27315
|
strategy: decision.strategy
|
|
26683
27316
|
}
|
|
26684
27317
|
};
|
|
@@ -26728,9 +27361,9 @@ async function projectToolResult(input) {
|
|
|
26728
27361
|
refId: decision.refId,
|
|
26729
27362
|
preview: decision.preview,
|
|
26730
27363
|
metrics: {
|
|
26731
|
-
inputChars:
|
|
27364
|
+
inputChars: decision.inputChars,
|
|
26732
27365
|
outputChars: decision.preview.length,
|
|
26733
|
-
omittedChars: Math.max(0,
|
|
27366
|
+
omittedChars: Math.max(0, decision.inputChars - decision.preview.length),
|
|
26734
27367
|
strategy: decision.strategy
|
|
26735
27368
|
}
|
|
26736
27369
|
};
|
|
@@ -28295,7 +28928,7 @@ async function persistRegion(region) {
|
|
|
28295
28928
|
*/
|
|
28296
28929
|
const lastActiveBySession = /* @__PURE__ */ new Map();
|
|
28297
28930
|
/** Bound on remembered sessions. */
|
|
28298
|
-
const MAX_TRACKED_SESSIONS$
|
|
28931
|
+
const MAX_TRACKED_SESSIONS$2 = 256;
|
|
28299
28932
|
/**
|
|
28300
28933
|
* Note that a session just did something.
|
|
28301
28934
|
*
|
|
@@ -28308,7 +28941,7 @@ function markSessionActive(sessionId, now = Date.now()) {
|
|
|
28308
28941
|
const key = sessionId.trim();
|
|
28309
28942
|
lastActiveBySession.delete(key);
|
|
28310
28943
|
lastActiveBySession.set(key, now);
|
|
28311
|
-
if (lastActiveBySession.size <= MAX_TRACKED_SESSIONS$
|
|
28944
|
+
if (lastActiveBySession.size <= MAX_TRACKED_SESSIONS$2) return;
|
|
28312
28945
|
const oldest = lastActiveBySession.keys().next();
|
|
28313
28946
|
if (oldest.done !== true) lastActiveBySession.delete(oldest.value);
|
|
28314
28947
|
}
|
|
@@ -31542,6 +32175,110 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$3, preserveThin
|
|
|
31542
32175
|
return body;
|
|
31543
32176
|
}
|
|
31544
32177
|
/**
|
|
32178
|
+
* How the side-channel title request opens its prompt.
|
|
32179
|
+
*
|
|
32180
|
+
* Matching the opening identifies the request even when the provenance stamp
|
|
32181
|
+
* is absent - a harness generation that does not stamp it still asks for the
|
|
32182
|
+
* title in exactly this wording.
|
|
32183
|
+
*/
|
|
32184
|
+
const TITLE_REQUEST_PROMPT_PREFIX = "Generate the session title from this JSON array of human messages:";
|
|
32185
|
+
/**
|
|
32186
|
+
* How the side-channel title request opens its system prompt.
|
|
32187
|
+
*
|
|
32188
|
+
* Only a request that carries NO tools can be the title request: the real
|
|
32189
|
+
* conversation always carries the session's tool set, so the (absent) tool
|
|
32190
|
+
* list is what separates the two shapes.
|
|
32191
|
+
*/
|
|
32192
|
+
const TITLE_REQUEST_SYSTEM_PREFIX = "Create a concise title for an AI coding-assistant session";
|
|
32193
|
+
/**
|
|
32194
|
+
* The first non-empty text a message body contributes.
|
|
32195
|
+
*
|
|
32196
|
+
* The same ordering {@link textOf} folds a body in, but it stops at the first
|
|
32197
|
+
* contribution instead of joining all of them: a caller that only needs to know
|
|
32198
|
+
* where the text STARTS must not pay for the whole conversation. The join in
|
|
32199
|
+
* {@link textOf} puts the blocks in order and the empty ones contribute
|
|
32200
|
+
* nothing, so the first non-empty contribution is where the joined text begins.
|
|
32201
|
+
*
|
|
32202
|
+
* @param content - The body of one message.
|
|
32203
|
+
* @returns The first non-empty text it contributes, or undefined.
|
|
32204
|
+
*/
|
|
32205
|
+
function firstTextOf(content) {
|
|
32206
|
+
if (typeof content === "string") {
|
|
32207
|
+
const text = sanitizeText$7(content);
|
|
32208
|
+
return text === "" ? void 0 : text;
|
|
32209
|
+
}
|
|
32210
|
+
if (!Array.isArray(content)) return void 0;
|
|
32211
|
+
for (const block of content) {
|
|
32212
|
+
if (!isRecord$45(block)) continue;
|
|
32213
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
32214
|
+
const text = sanitizeText$7(block.text);
|
|
32215
|
+
if (text !== "") return text;
|
|
32216
|
+
continue;
|
|
32217
|
+
}
|
|
32218
|
+
if (block.type === "tool-result") {
|
|
32219
|
+
const nested = firstTextOf(block.content);
|
|
32220
|
+
if (nested !== void 0) return nested;
|
|
32221
|
+
}
|
|
32222
|
+
}
|
|
32223
|
+
}
|
|
32224
|
+
/**
|
|
32225
|
+
* The text the folded system prompt starts with.
|
|
32226
|
+
*
|
|
32227
|
+
* Same ordering as {@link leadingSystemText}: `options.system` leads, and the
|
|
32228
|
+
* system-role messages follow in history order. Only the first contribution
|
|
32229
|
+
* matters here, so the scan stops at it.
|
|
32230
|
+
*
|
|
32231
|
+
* @param options - The request being inspected.
|
|
32232
|
+
* @returns The leading text of the system prompt, or undefined when there is none.
|
|
32233
|
+
*/
|
|
32234
|
+
function leadingSystemTextStart(options) {
|
|
32235
|
+
if (typeof options.system === "string" && options.system.trim() !== "") return options.system;
|
|
32236
|
+
for (const message of options.messages) {
|
|
32237
|
+
if (message.role !== "system") continue;
|
|
32238
|
+
if (messageToolsOf(message) !== void 0) continue;
|
|
32239
|
+
const text = firstTextOf(message.content);
|
|
32240
|
+
if (text !== void 0) return text;
|
|
32241
|
+
}
|
|
32242
|
+
}
|
|
32243
|
+
/**
|
|
32244
|
+
* Whether this request is the side-channel one that titles a session.
|
|
32245
|
+
*
|
|
32246
|
+
* A title request is asked on the same adapter, under the same session id, and
|
|
32247
|
+
* with the same credential as the conversation it titles - but it shares
|
|
32248
|
+
* nothing else with it. Left alone it would:
|
|
32249
|
+
*
|
|
32250
|
+
* - send the session's prompt cache key, so the service would route the title
|
|
32251
|
+
* prompt at the entry the conversation cached;
|
|
32252
|
+
* - overwrite the session's prefix snapshot, so the next real turn would be
|
|
32253
|
+
* told its system prompt or its tool list changed; and
|
|
32254
|
+
* - file its usage under the session, so the session's cache ratio would
|
|
32255
|
+
* describe a prompt the session never sent.
|
|
32256
|
+
*
|
|
32257
|
+
* The detection is deliberately structural rather than a flag on the options:
|
|
32258
|
+
* the caller is the harness, and the harness states a title request by its
|
|
32259
|
+
* CONTENT, not by anything this route could switch on.
|
|
32260
|
+
*
|
|
32261
|
+
* @param options - The request about to be mapped.
|
|
32262
|
+
* @returns Whether the request is the side-channel title request.
|
|
32263
|
+
*/
|
|
32264
|
+
function isSideChannelTitleRequest(options) {
|
|
32265
|
+
if (options === void 0 || options === null) return false;
|
|
32266
|
+
const messages = Array.isArray(options.messages) ? options.messages : [];
|
|
32267
|
+
const tools = options.tools;
|
|
32268
|
+
const toolLess = tools === void 0 || tools.length === 0;
|
|
32269
|
+
for (const message of messages) {
|
|
32270
|
+
if (!isRecord$45(message)) continue;
|
|
32271
|
+
if (message.source?.kind === "dsh-session-title-llm") return true;
|
|
32272
|
+
const text = firstTextOf(message.content);
|
|
32273
|
+
if (toolLess && text !== void 0 && text.startsWith(TITLE_REQUEST_PROMPT_PREFIX)) return true;
|
|
32274
|
+
}
|
|
32275
|
+
if (toolLess) {
|
|
32276
|
+
const systemText = leadingSystemTextStart(options);
|
|
32277
|
+
if (systemText !== void 0 && systemText.startsWith(TITLE_REQUEST_SYSTEM_PREFIX)) return true;
|
|
32278
|
+
}
|
|
32279
|
+
return false;
|
|
32280
|
+
}
|
|
32281
|
+
/**
|
|
31545
32282
|
* Stable identifier for the conversation this request belongs to.
|
|
31546
32283
|
*
|
|
31547
32284
|
* The key is derived from the session identifier alone — never from message
|
|
@@ -31554,6 +32291,7 @@ function buildOpenAIRequest(options, images = NO_RESOLVED_IMAGES$3, preserveThin
|
|
|
31554
32291
|
* all rather than a key that will drift.
|
|
31555
32292
|
*/
|
|
31556
32293
|
function promptCacheKey(options) {
|
|
32294
|
+
if (isSideChannelTitleRequest(options)) return void 0;
|
|
31557
32295
|
if (typeof options.sessionId === "string" && options.sessionId.trim() !== "") return `dsh-${options.sessionId.trim()}`;
|
|
31558
32296
|
}
|
|
31559
32297
|
function anthropicUserContent$2(message, images, toolCallIds) {
|
|
@@ -31696,8 +32434,9 @@ function buildAnthropicRequest(options, images = NO_RESOLVED_IMAGES$3, media = {
|
|
|
31696
32434
|
}
|
|
31697
32435
|
/** Build the body for whichever endpoint serves `wire`. */
|
|
31698
32436
|
function buildRequest(options, wire, images = NO_RESOLVED_IMAGES$3, preserveThinking = preserveThinkingEnabled(), media = {}) {
|
|
32437
|
+
if (isSideChannelTitleRequest(options)) return wire === "anthropic" ? buildAnthropicRequest(options, images, media) : buildOpenAIRequest(options, images, preserveThinking, media);
|
|
31699
32438
|
const cause = trackPrefixStability(options);
|
|
31700
|
-
recordDriftCause(cause === "stable" && (pendingImageOffloads.get(cacheScopeKey(options.sessionId)) ?? 0) > 0 ? "image-offload" : cause, options.sessionId);
|
|
32439
|
+
recordDriftCause(cause === "stable" && (pendingImageOffloads.get(cacheScopeKey(options.sessionId)) ?? 0) > 0 ? "image-offload" : cause, options.sessionId, options);
|
|
31701
32440
|
return wire === "anthropic" ? buildAnthropicRequest(options, images, media) : buildOpenAIRequest(options, images, preserveThinking, media);
|
|
31702
32441
|
}
|
|
31703
32442
|
/**
|
|
@@ -31794,10 +32533,11 @@ function oversizedBodyMessage(bytes, limit, body, context) {
|
|
|
31794
32533
|
const parts = requestBodyBreakdown(body);
|
|
31795
32534
|
return `Kimi Code rejected the request before sending: the serialized body is ${bytes} bytes, above the ${limit}-byte limit this route enforces. Composition: ${`images ${parts.imageBytes} bytes, tool schemas ${parts.toolSchemaBytes} bytes, conversation text and system prompt ${parts.otherBytes} bytes`}. ${bodyFitRemedy(parts, context)}`;
|
|
31796
32535
|
}
|
|
31797
|
-
function createStreamState$5(wire, sessionId) {
|
|
32536
|
+
function createStreamState$5(wire, sessionId, sideChannel = false) {
|
|
31798
32537
|
return {
|
|
31799
32538
|
wire,
|
|
31800
32539
|
sessionId: typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : void 0,
|
|
32540
|
+
sideChannel,
|
|
31801
32541
|
blocks: [],
|
|
31802
32542
|
current: null,
|
|
31803
32543
|
reasoningRuns: /* @__PURE__ */ new Map(),
|
|
@@ -32224,7 +32964,7 @@ const UNSCOPED_CACHE_KEY = "";
|
|
|
32224
32964
|
* count: eviction only begins once a host has served hundreds of them, and it
|
|
32225
32965
|
* evicts the least recently written key, which is a stale session by then.
|
|
32226
32966
|
*/
|
|
32227
|
-
const MAX_TRACKED_SESSIONS = 256;
|
|
32967
|
+
const MAX_TRACKED_SESSIONS$1 = 256;
|
|
32228
32968
|
/**
|
|
32229
32969
|
* Composite key separating one session's numbers from another's.
|
|
32230
32970
|
*
|
|
@@ -32242,7 +32982,7 @@ function cacheScopeKey(sessionId, accountId) {
|
|
|
32242
32982
|
function rememberScoped(store, key, value) {
|
|
32243
32983
|
store.delete(key);
|
|
32244
32984
|
store.set(key, value);
|
|
32245
|
-
if (store.size <= MAX_TRACKED_SESSIONS) return;
|
|
32985
|
+
if (store.size <= MAX_TRACKED_SESSIONS$1) return;
|
|
32246
32986
|
const oldest = store.keys().next();
|
|
32247
32987
|
if (oldest.done !== true) store.delete(oldest.value);
|
|
32248
32988
|
}
|
|
@@ -32257,19 +32997,31 @@ function rememberScoped(store, key, value) {
|
|
|
32257
32997
|
*/
|
|
32258
32998
|
const prefixSnapshots = /* @__PURE__ */ new Map();
|
|
32259
32999
|
/**
|
|
33000
|
+
* Hash the prefix-breaking inputs of one request.
|
|
33001
|
+
*
|
|
33002
|
+
* @param options - The request about to be built.
|
|
33003
|
+
* @returns The hashes of its system prompt and its tool list.
|
|
33004
|
+
*/
|
|
33005
|
+
function prefixFingerprints(options) {
|
|
33006
|
+
const systemText = leadingSystemText$7(options) ?? "";
|
|
33007
|
+
const tools = options.tools ?? [];
|
|
33008
|
+
return {
|
|
33009
|
+
systemPromptHash: systemText === "" ? null : fingerprint(systemText),
|
|
33010
|
+
toolsHash: tools.length === 0 ? null : fingerprint(JSON.stringify(tools.map((tool) => [
|
|
33011
|
+
tool.name,
|
|
33012
|
+
tool.description ?? "",
|
|
33013
|
+
tool.parameters ?? {}
|
|
33014
|
+
])))
|
|
33015
|
+
};
|
|
33016
|
+
}
|
|
33017
|
+
/**
|
|
32260
33018
|
* Fingerprint the current request's stability inputs and diff them against the
|
|
32261
33019
|
* last request. Called once per request build; the result is read back when the
|
|
32262
33020
|
* stream reports usage so a low-or-zero cache read can be explained.
|
|
32263
33021
|
*/
|
|
32264
33022
|
function trackPrefixStability(options) {
|
|
32265
|
-
|
|
32266
|
-
const systemPromptHash
|
|
32267
|
-
const tools = options.tools ?? [];
|
|
32268
|
-
const toolsHash = tools.length === 0 ? null : fingerprint(JSON.stringify(tools.map((tool) => [
|
|
32269
|
-
tool.name,
|
|
32270
|
-
tool.description ?? "",
|
|
32271
|
-
tool.parameters ?? {}
|
|
32272
|
-
])));
|
|
33023
|
+
if (isSideChannelTitleRequest(options)) return "stable";
|
|
33024
|
+
const { systemPromptHash, toolsHash } = prefixFingerprints(options);
|
|
32273
33025
|
const cacheKey = promptCacheKey(options) ?? null;
|
|
32274
33026
|
const scope = cacheScopeKey(options.sessionId);
|
|
32275
33027
|
const previous = prefixSnapshots.get(scope);
|
|
@@ -32287,8 +33039,16 @@ function trackPrefixStability(options) {
|
|
|
32287
33039
|
}
|
|
32288
33040
|
/** Drift cause recorded for each session. */
|
|
32289
33041
|
const driftCauses = /* @__PURE__ */ new Map();
|
|
32290
|
-
/**
|
|
32291
|
-
|
|
33042
|
+
/**
|
|
33043
|
+
* Record the cause computed for one in-flight request.
|
|
33044
|
+
*
|
|
33045
|
+
* The options are optional and only used to recognise a side-channel request:
|
|
33046
|
+
* a title request carries its own prefix, so attributing it to the session
|
|
33047
|
+
* would blame the session's next cache miss on the title request's system
|
|
33048
|
+
* prompt or on its (absent) tool list.
|
|
33049
|
+
*/
|
|
33050
|
+
function recordDriftCause(cause, sessionId, options) {
|
|
33051
|
+
if (options !== void 0 && isSideChannelTitleRequest(options)) return;
|
|
32292
33052
|
rememberScoped(driftCauses, cacheScopeKey(sessionId), cause);
|
|
32293
33053
|
}
|
|
32294
33054
|
/**
|
|
@@ -32363,7 +33123,7 @@ function markPendingImageOffload(sessionId, newlyDroppedCount) {
|
|
|
32363
33123
|
* so an image given back stays given back, and the next turn has to know
|
|
32364
33124
|
* which ones those are.
|
|
32365
33125
|
*/
|
|
32366
|
-
function recordSessionImageBudget(sessionId, input) {
|
|
33126
|
+
function recordSessionImageBudget(sessionId, input, options) {
|
|
32367
33127
|
const scope = cacheScopeKey(sessionId);
|
|
32368
33128
|
const named = sessionId !== void 0 && sessionId.trim() !== "";
|
|
32369
33129
|
const keys = named ? sessionDroppedImageKeys.get(scope) ?? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set();
|
|
@@ -32377,7 +33137,7 @@ function recordSessionImageBudget(sessionId, input) {
|
|
|
32377
33137
|
droppedKeys: named ? [...keys] : []
|
|
32378
33138
|
});
|
|
32379
33139
|
markPendingImageOffload(sessionId, input.newlyDroppedCount);
|
|
32380
|
-
if (input.newlyDroppedCount > 0 && getLastDriftCause(sessionId) === "stable") recordDriftCause("image-offload", sessionId);
|
|
33140
|
+
if (input.newlyDroppedCount > 0 && getLastDriftCause(sessionId) === "stable") recordDriftCause("image-offload", sessionId, options);
|
|
32381
33141
|
}
|
|
32382
33142
|
/**
|
|
32383
33143
|
* What one session's last request carried of images, when it is known.
|
|
@@ -32466,6 +33226,7 @@ const EMPTY_CACHE_STATS = {
|
|
|
32466
33226
|
/** Record one request's usage into its own session's totals. */
|
|
32467
33227
|
function recordCacheStats(state, sessionId, accountId) {
|
|
32468
33228
|
if (!state.sawUsage) return;
|
|
33229
|
+
if (state.sideChannel) return;
|
|
32469
33230
|
const scope = cacheScopeKey(sessionId ?? state.sessionId, accountId);
|
|
32470
33231
|
const previous = cacheStatsByScope.get(scope) ?? EMPTY_CACHE_STATS;
|
|
32471
33232
|
rememberScoped(cacheStatsByScope, scope, {
|
|
@@ -33931,6 +34692,155 @@ function toolUpdateMode$1(modelId, catalog) {
|
|
|
33931
34692
|
].includes(raw)) return void 0;
|
|
33932
34693
|
return "in-history";
|
|
33933
34694
|
}
|
|
34695
|
+
/**
|
|
34696
|
+
* How long a cold-start follower waits for the leader warming the same prefix.
|
|
34697
|
+
*
|
|
34698
|
+
* The leader only has to reach the response headers: past that the service is
|
|
34699
|
+
* already processing the prefix, and a follower that waited out the whole
|
|
34700
|
+
* budget would have lost more than the shared warm-up was worth.
|
|
34701
|
+
*/
|
|
34702
|
+
const COLD_START_WARMUP_WAIT_MS = 2500;
|
|
34703
|
+
/** How many prefix groups are remembered as warm. */
|
|
34704
|
+
const MAX_WARM_PREFIX_GROUPS = 256;
|
|
34705
|
+
/**
|
|
34706
|
+
* When each prefix group was last warmed.
|
|
34707
|
+
*
|
|
34708
|
+
* An entry older than the cache lifetime is treated as cold, because the
|
|
34709
|
+
* service's own entry is gone by then too. Bounded, so a long-lived process
|
|
34710
|
+
* cannot accumulate one entry per distinct prefix forever.
|
|
34711
|
+
*/
|
|
34712
|
+
const warmPrefixTimestamps = /* @__PURE__ */ new Map();
|
|
34713
|
+
/**
|
|
34714
|
+
* The request currently warming each prefix group.
|
|
34715
|
+
*
|
|
34716
|
+
* The promise is resolved when that leader reaches the response headers, which
|
|
34717
|
+
* is what releases the followers waiting behind it.
|
|
34718
|
+
*/
|
|
34719
|
+
const inFlightColdLeaders = /* @__PURE__ */ new Map();
|
|
34720
|
+
/** When each leader started, so a hung leader does not own its group forever. */
|
|
34721
|
+
const inFlightColdLeaderStartedAt = /* @__PURE__ */ new Map();
|
|
34722
|
+
/**
|
|
34723
|
+
* The resolver belonging to each in-flight leader.
|
|
34724
|
+
*
|
|
34725
|
+
* Kept beside the promise it settles, because a promise cannot be resolved
|
|
34726
|
+
* from the outside.
|
|
34727
|
+
*/
|
|
34728
|
+
const coldLeaderReleases = /* @__PURE__ */ new Map();
|
|
34729
|
+
/** The gate of a request that neither waits for nor leads a cold start. */
|
|
34730
|
+
const NO_COLD_START_GATE = {
|
|
34731
|
+
isLeader: false,
|
|
34732
|
+
release: () => void 0,
|
|
34733
|
+
markWarm: () => void 0
|
|
34734
|
+
};
|
|
34735
|
+
/**
|
|
34736
|
+
* The group of requests that address the same cached prefix.
|
|
34737
|
+
*
|
|
34738
|
+
* The service derives its cache from the request content, so two requests with
|
|
34739
|
+
* the same model, the same system prompt and the same tool list hit the same
|
|
34740
|
+
* entry. The region is part of the key because each region is a separate
|
|
34741
|
+
* service with a cache of its own.
|
|
34742
|
+
*
|
|
34743
|
+
* A request with no system prompt has no prefix worth warming, and gets no
|
|
34744
|
+
* group at all.
|
|
34745
|
+
*
|
|
34746
|
+
* @param region - The region the request will be answered from.
|
|
34747
|
+
* @param options - The request about to be sent.
|
|
34748
|
+
* @returns The group key, or undefined when the request has no prefix.
|
|
34749
|
+
*/
|
|
34750
|
+
function coldStartGroupKey(region, options) {
|
|
34751
|
+
const { systemPromptHash, toolsHash } = prefixFingerprints(options);
|
|
34752
|
+
if (systemPromptHash === null) return void 0;
|
|
34753
|
+
return `${region}:${options.model}:${systemPromptHash}:${toolsHash ?? "none"}`;
|
|
34754
|
+
}
|
|
34755
|
+
/**
|
|
34756
|
+
* Remember that a prefix group is warm.
|
|
34757
|
+
*
|
|
34758
|
+
* @param groupKey - The group that was just warmed.
|
|
34759
|
+
*/
|
|
34760
|
+
function rememberWarmPrefix(groupKey) {
|
|
34761
|
+
warmPrefixTimestamps.delete(groupKey);
|
|
34762
|
+
warmPrefixTimestamps.set(groupKey, Date.now());
|
|
34763
|
+
while (warmPrefixTimestamps.size > MAX_WARM_PREFIX_GROUPS) {
|
|
34764
|
+
const oldest = warmPrefixTimestamps.keys().next();
|
|
34765
|
+
if (oldest.done === true) break;
|
|
34766
|
+
warmPrefixTimestamps.delete(oldest.value);
|
|
34767
|
+
}
|
|
34768
|
+
}
|
|
34769
|
+
/**
|
|
34770
|
+
* Wait for the leader of a prefix group, for the wait budget, or for an abort.
|
|
34771
|
+
*
|
|
34772
|
+
* The timer and the abort listener are removed whichever way the race ends, so
|
|
34773
|
+
* a follower that proceeds leaves neither behind.
|
|
34774
|
+
*
|
|
34775
|
+
* @param leader - The promise the in-flight leader settles.
|
|
34776
|
+
* @param signal - The caller's abort signal; an abort ends the wait.
|
|
34777
|
+
*/
|
|
34778
|
+
function raceColdStartLeader(leader, signal) {
|
|
34779
|
+
const cleanups = [];
|
|
34780
|
+
const budget = new Promise((resolve) => {
|
|
34781
|
+
const timer = setTimeout(() => resolve(), COLD_START_WARMUP_WAIT_MS);
|
|
34782
|
+
cleanups.push(() => clearTimeout(timer));
|
|
34783
|
+
});
|
|
34784
|
+
const aborted = new Promise((resolve) => {
|
|
34785
|
+
if (signal.aborted) {
|
|
34786
|
+
resolve();
|
|
34787
|
+
return;
|
|
34788
|
+
}
|
|
34789
|
+
const onAbort = () => resolve();
|
|
34790
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
34791
|
+
cleanups.push(() => signal.removeEventListener("abort", onAbort));
|
|
34792
|
+
});
|
|
34793
|
+
return Promise.race([
|
|
34794
|
+
leader,
|
|
34795
|
+
budget,
|
|
34796
|
+
aborted
|
|
34797
|
+
]).finally(() => {
|
|
34798
|
+
for (const cleanup of cleanups) cleanup();
|
|
34799
|
+
});
|
|
34800
|
+
}
|
|
34801
|
+
/**
|
|
34802
|
+
* Join the cold-start singleflight for one prefix group.
|
|
34803
|
+
*
|
|
34804
|
+
* A warm group returns at once. A cold group that already has a leader waits
|
|
34805
|
+
* for that leader — but never longer than {@link COLD_START_WARMUP_WAIT_MS}.
|
|
34806
|
+
* A cold group with no leader becomes the leader itself.
|
|
34807
|
+
*
|
|
34808
|
+
* @param groupKey - The prefix group this request belongs to.
|
|
34809
|
+
* @param signal - The caller's abort signal; an abort ends the wait.
|
|
34810
|
+
* @returns The gate this request holds.
|
|
34811
|
+
*/
|
|
34812
|
+
async function acquireColdStartWarmup(groupKey, signal) {
|
|
34813
|
+
const warmedAt = warmPrefixTimestamps.get(groupKey);
|
|
34814
|
+
if (warmedAt !== void 0 && Date.now() - warmedAt < 15e5) return NO_COLD_START_GATE;
|
|
34815
|
+
if (inFlightColdLeaders.get(groupKey) !== void 0) {
|
|
34816
|
+
const startedAt = inFlightColdLeaderStartedAt.get(groupKey);
|
|
34817
|
+
if (startedAt !== void 0 && Date.now() - startedAt >= 2500) coldLeaderReleases.get(groupKey)?.();
|
|
34818
|
+
}
|
|
34819
|
+
const leader = inFlightColdLeaders.get(groupKey);
|
|
34820
|
+
if (leader !== void 0) {
|
|
34821
|
+
await raceColdStartLeader(leader, signal);
|
|
34822
|
+
return NO_COLD_START_GATE;
|
|
34823
|
+
}
|
|
34824
|
+
let resolveLeader;
|
|
34825
|
+
const promise = new Promise((resolve) => {
|
|
34826
|
+
resolveLeader = resolve;
|
|
34827
|
+
});
|
|
34828
|
+
inFlightColdLeaders.set(groupKey, promise);
|
|
34829
|
+
inFlightColdLeaderStartedAt.set(groupKey, Date.now());
|
|
34830
|
+
const release = () => {
|
|
34831
|
+
resolveLeader();
|
|
34832
|
+
if (coldLeaderReleases.get(groupKey) !== release) return;
|
|
34833
|
+
coldLeaderReleases.delete(groupKey);
|
|
34834
|
+
inFlightColdLeaders.delete(groupKey);
|
|
34835
|
+
inFlightColdLeaderStartedAt.delete(groupKey);
|
|
34836
|
+
};
|
|
34837
|
+
coldLeaderReleases.set(groupKey, release);
|
|
34838
|
+
return {
|
|
34839
|
+
isLeader: true,
|
|
34840
|
+
release,
|
|
34841
|
+
markWarm: () => rememberWarmPrefix(groupKey)
|
|
34842
|
+
};
|
|
34843
|
+
}
|
|
33934
34844
|
/** Sessions already told that their prefix broke, so the log stays readable. */
|
|
33935
34845
|
const driftWarned = /* @__PURE__ */ new Set();
|
|
33936
34846
|
/**
|
|
@@ -34204,11 +35114,12 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
34204
35114
|
const wire = wireForCatalogEntry(options.model, catalog);
|
|
34205
35115
|
const entry = catalog.find((model) => model.id === options.model);
|
|
34206
35116
|
const normalized = normalizeGenerateOptions(options);
|
|
35117
|
+
const sideChannel = isSideChannelTitleRequest(normalized);
|
|
34207
35118
|
const images = await resolveRequestImages$1(normalized, this.options.attachments, signal, KIMI_CODE_REQUEST_IMAGE_MAX_EDGE);
|
|
34208
35119
|
const priorDroppedKeys = getSessionDroppedImageKeys(options.sessionId, options.sessionId === void 0 ? void 0 : sessionCacheState.read(options.sessionId)?.budgetDropped);
|
|
34209
35120
|
const stickyOffload = offloadRequestImagesWithStickyBudget(normalized, maxRequestImageBytes$1(), lowRequestImageBytes(), images, priorDroppedKeys);
|
|
34210
35121
|
const imageTrimmed = stickyOffload.options;
|
|
34211
|
-
markPendingImageOffload(options.sessionId, stickyOffload.newlyDroppedKeys.length);
|
|
35122
|
+
if (!sideChannel) markPendingImageOffload(options.sessionId, stickyOffload.newlyDroppedKeys.length);
|
|
34212
35123
|
const gatedEffort = coerceThinkingEffort(options.model, mapReasoningEffort(options.reasoningEffort === void 0 || options.reasoningEffort === null ? void 0 : String(options.reasoningEffort)));
|
|
34213
35124
|
if (gatedEffort.coercedFrom !== null) recordThinkingCoercion({
|
|
34214
35125
|
model: options.model,
|
|
@@ -34384,7 +35295,22 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
34384
35295
|
throw new LlmError(`Kimi Code request failed: ${error instanceof Error ? error.message : String(error)}`, "TRANSPORT", { cause: error });
|
|
34385
35296
|
}
|
|
34386
35297
|
};
|
|
34387
|
-
|
|
35298
|
+
const coldGroupKey = sideChannel ? void 0 : coldStartGroupKey(region, requestOptions);
|
|
35299
|
+
const coldGate = coldGroupKey === void 0 ? void 0 : await acquireColdStartWarmup(coldGroupKey, signal);
|
|
35300
|
+
let coldGateSettled = false;
|
|
35301
|
+
const settleColdGate = (warm) => {
|
|
35302
|
+
if (coldGateSettled) return;
|
|
35303
|
+
coldGateSettled = true;
|
|
35304
|
+
coldGate?.release();
|
|
35305
|
+
if (warm) coldGate?.markWarm();
|
|
35306
|
+
};
|
|
35307
|
+
response = await post(body).then((value) => {
|
|
35308
|
+
settleColdGate(value.ok);
|
|
35309
|
+
return value;
|
|
35310
|
+
}, (error) => {
|
|
35311
|
+
settleColdGate(false);
|
|
35312
|
+
throw error;
|
|
35313
|
+
});
|
|
34388
35314
|
let detail = response.ok ? "" : (await response.text().catch(() => "")).slice(0, 2e3);
|
|
34389
35315
|
if (response.status === 404 && uploads !== void 0 && uploads.cited.length > 0 && isMissingResourceBody(detail)) {
|
|
34390
35316
|
const uploader = this.mediaFiles();
|
|
@@ -34428,7 +35354,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
34428
35354
|
...after === void 0 ? {} : { providerRetryAfterMs: after }
|
|
34429
35355
|
});
|
|
34430
35356
|
}
|
|
34431
|
-
if (measured !== void 0) {
|
|
35357
|
+
if (measured !== void 0 && !sideChannel) {
|
|
34432
35358
|
const requestImageBytes = requestImagePayloadLengths(measured.options, images).reduce((sum, bytes) => sum + bytes, 0);
|
|
34433
35359
|
const trimmedKeys = new Set(collectRemainingImageKeys(imageTrimmed, images));
|
|
34434
35360
|
const measuredKeys = new Set(collectRemainingImageKeys(measured.options, images));
|
|
@@ -34439,14 +35365,14 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
34439
35365
|
newlyDroppedCount: stickyOffload.newlyDroppedKeys.length + bodyFitDroppedKeys.length,
|
|
34440
35366
|
requestImageBytes,
|
|
34441
35367
|
droppedImages: stickyOffload.droppedCount + bodyFitDroppedKeys.length
|
|
34442
|
-
});
|
|
35368
|
+
}, requestOptions);
|
|
34443
35369
|
}
|
|
34444
35370
|
if (response.body === null) throw new LlmError("Kimi Code returned an empty response body", "PROVIDER_ERROR");
|
|
34445
35371
|
const reader = response.body.getReader();
|
|
34446
35372
|
const decoder = new TextDecoder();
|
|
34447
|
-
const state = createStreamState$5(wire, requestOptions.sessionId);
|
|
35373
|
+
const state = createStreamState$5(wire, requestOptions.sessionId, sideChannel);
|
|
34448
35374
|
applyCacheWriteTiers(state, response.headers);
|
|
34449
|
-
markSessionActive(requestOptions.sessionId);
|
|
35375
|
+
if (!sideChannel) markSessionActive(requestOptions.sessionId);
|
|
34450
35376
|
let buffer = "";
|
|
34451
35377
|
/**
|
|
34452
35378
|
* Set the moment ANY chunk reaches the caller, and never cleared.
|
|
@@ -34492,7 +35418,7 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
34492
35418
|
throw streamFailure(error, signal);
|
|
34493
35419
|
}
|
|
34494
35420
|
} finally {
|
|
34495
|
-
if (state.sawUsage) noteSessionCacheState(requestOptions.sessionId ?? state.sessionId, state);
|
|
35421
|
+
if (state.sawUsage && !sideChannel) noteSessionCacheState(requestOptions.sessionId ?? state.sessionId, state);
|
|
34496
35422
|
reader.cancel().catch(() => void 0);
|
|
34497
35423
|
}
|
|
34498
35424
|
}
|
|
@@ -44240,7 +45166,7 @@ function ms(value) {
|
|
|
44240
45166
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
44241
45167
|
}
|
|
44242
45168
|
/** A non-empty string, or undefined. */
|
|
44243
|
-
function text$
|
|
45169
|
+
function text$3(value) {
|
|
44244
45170
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
44245
45171
|
}
|
|
44246
45172
|
function withTimeout$6(signal, ms_) {
|
|
@@ -44335,9 +45261,9 @@ async function callAcrossHosts(input) {
|
|
|
44335
45261
|
function parseResetCard(raw) {
|
|
44336
45262
|
const row = asRecord$9(raw);
|
|
44337
45263
|
if (row === void 0) return void 0;
|
|
44338
|
-
const cardId = text$
|
|
44339
|
-
const sourceType = text$
|
|
44340
|
-
const outBizNo = text$
|
|
45264
|
+
const cardId = text$3(row.card_id);
|
|
45265
|
+
const sourceType = text$3(row.source_type);
|
|
45266
|
+
const outBizNo = text$3(row.out_biz_no);
|
|
44341
45267
|
const expiresAtMs = ms(row.expire_time);
|
|
44342
45268
|
const createdAtMs = ms(row.create_time);
|
|
44343
45269
|
if (cardId === void 0 || sourceType === void 0 || outBizNo === void 0) return void 0;
|
|
@@ -44360,8 +45286,8 @@ function parseResetCard(raw) {
|
|
|
44360
45286
|
function parseResetCardFlow(raw) {
|
|
44361
45287
|
const row = asRecord$9(raw);
|
|
44362
45288
|
if (row === void 0) return void 0;
|
|
44363
|
-
const cardId = text$
|
|
44364
|
-
const eventType = text$
|
|
45289
|
+
const cardId = text$3(row.card_id);
|
|
45290
|
+
const eventType = text$3(row.event_type);
|
|
44365
45291
|
const createdAtMs = ms(row.create_time);
|
|
44366
45292
|
if (cardId === void 0 || createdAtMs === void 0) return void 0;
|
|
44367
45293
|
if (eventType !== "GRANT" && eventType !== "USE") return void 0;
|
|
@@ -44459,7 +45385,7 @@ async function useResetCard(credentials, card, options = {}) {
|
|
|
44459
45385
|
}),
|
|
44460
45386
|
options
|
|
44461
45387
|
});
|
|
44462
|
-
const outcome = text$
|
|
45388
|
+
const outcome = text$3(asRecord$9(payload)?.outcome);
|
|
44463
45389
|
if (outcome === void 0 || !RESET_OUTCOMES.has(outcome)) throw new MinimaxCodeResetCardError("reset-card use answered without a usable outcome", null, readStatusCode(payload));
|
|
44464
45390
|
return outcome;
|
|
44465
45391
|
}
|
|
@@ -69293,7 +70219,7 @@ function createClinePassStore({ historyLimit = 100 } = {}) {
|
|
|
69293
70219
|
*
|
|
69294
70220
|
* @module dsh-chatgpt-subscription/cline-pass-tools
|
|
69295
70221
|
*/
|
|
69296
|
-
function text$
|
|
70222
|
+
function text$2(value) {
|
|
69297
70223
|
return [{
|
|
69298
70224
|
type: "text",
|
|
69299
70225
|
text: value
|
|
@@ -69567,7 +70493,7 @@ function registerClinePassTools(ctx, options) {
|
|
|
69567
70493
|
}
|
|
69568
70494
|
}
|
|
69569
70495
|
},
|
|
69570
|
-
render: (_args, value) => text$
|
|
70496
|
+
render: (_args, value) => text$2([
|
|
69571
70497
|
`${value.displayName} provider "cline-pass" @ ${value.baseURL}`,
|
|
69572
70498
|
`route registered=${value.routeRegistered}`,
|
|
69573
70499
|
`accountMode=${value.accountMode}`,
|
|
@@ -69661,7 +70587,7 @@ function registerClinePassTools(ctx, options) {
|
|
|
69661
70587
|
if (model.lastProvider !== "") lines.push(` last=${model.lastProvider} (${model.lastMs}ms)`);
|
|
69662
70588
|
return lines.join("\n");
|
|
69663
70589
|
});
|
|
69664
|
-
return text$
|
|
70590
|
+
return text$2([
|
|
69665
70591
|
`${value.models.length} Cline Pass model(s); official catalog=${value.catalogCount}${value.catalogSources.length === 0 ? "" : ` via ${value.catalogSources.join(",")}`}`,
|
|
69666
70592
|
...value.added.length === 0 ? [] : [`newly adopted: ${value.added.join(", ")}`],
|
|
69667
70593
|
...blocks
|
|
@@ -69798,8 +70724,8 @@ function registerClinePassTools(ctx, options) {
|
|
|
69798
70724
|
}
|
|
69799
70725
|
},
|
|
69800
70726
|
render: (_args, value) => {
|
|
69801
|
-
if (!value.ok) return text$
|
|
69802
|
-
return text$
|
|
70727
|
+
if (!value.ok) return text$2(`probe ${value.model} failed: ${value.error}`);
|
|
70728
|
+
return text$2([
|
|
69803
70729
|
`probe ${value.model}: ok in ${value.ms}ms`,
|
|
69804
70730
|
`pipeline=${value.pipeline} pinnable=${value.pinnable} canonicalSlug=${value.canonicalSlug || "-"} lastProvider=${value.lastProvider || "-"}`,
|
|
69805
70731
|
value.upstreams.length === 0 ? "upstreams: (none discovered)" : `upstreams (${value.upstreams.length}): ${value.upstreams.join(", ")}`,
|
|
@@ -69886,8 +70812,8 @@ function registerClinePassTools(ctx, options) {
|
|
|
69886
70812
|
}
|
|
69887
70813
|
},
|
|
69888
70814
|
render: (_args, value) => {
|
|
69889
|
-
if (!value.ok) return text$
|
|
69890
|
-
return text$
|
|
70815
|
+
if (!value.ok) return text$2(`validate ${value.model} failed: ${value.error}`);
|
|
70816
|
+
return text$2([
|
|
69891
70817
|
`validate ${value.model}: ${value.results.length} upstream(s)`,
|
|
69892
70818
|
`available=${value.summary.ok} rate-limited=${value.summary.limited} not-pinnable=${value.summary.bad} auth-failed=${value.summary.auth} unknown=${value.summary.unknown}`,
|
|
69893
70819
|
...value.results.map((entry) => `${entry.upstream}: ${statusLabel(entry.status)} (${entry.ms}ms)${entry.note === "" ? "" : ` — ${entry.note}`}`)
|
|
@@ -70007,7 +70933,7 @@ function registerClinePassTools(ctx, options) {
|
|
|
70007
70933
|
},
|
|
70008
70934
|
render: (_args, value) => {
|
|
70009
70935
|
const trace = value.trace.map((entry) => ` ${entry.upstream} -> HTTP ${entry.status} in ${entry.ms}ms${entry.note === "" ? "" : ` — ${entry.note}`}`);
|
|
70010
|
-
return text$
|
|
70936
|
+
return text$2([
|
|
70011
70937
|
value.ok ? `test ${value.model}: ok in ${value.ms}ms via ${value.actual || "(unknown)"}` : `test ${value.model}: FAILED — ${value.error}`,
|
|
70012
70938
|
...value.warnings.map((warning) => `⚠ ${warning}`),
|
|
70013
70939
|
trace.length === 0 ? "" : `attempts (${trace.length}):`,
|
|
@@ -70127,7 +71053,7 @@ function registerClinePassTools(ctx, options) {
|
|
|
70127
71053
|
}
|
|
70128
71054
|
}
|
|
70129
71055
|
},
|
|
70130
|
-
render: (_args, value) => text$
|
|
71056
|
+
render: (_args, value) => text$2([
|
|
70131
71057
|
`pinned ${value.model}`,
|
|
70132
71058
|
`upstreams: ${value.pinned.length === 0 ? "(auto)" : value.pinned.join(" > ")}`,
|
|
70133
71059
|
`exclude: ${value.excluded.length === 0 ? "(none)" : value.excluded.join(", ")}`,
|
|
@@ -70237,7 +71163,7 @@ function registerClinePassTools(ctx, options) {
|
|
|
70237
71163
|
}
|
|
70238
71164
|
}
|
|
70239
71165
|
},
|
|
70240
|
-
render: (_args, value) => text$
|
|
71166
|
+
render: (_args, value) => text$2([
|
|
70241
71167
|
`accounts.${value.action}: ${value.ok ? "ok" : "FAILED"}`,
|
|
70242
71168
|
value.note === "" ? "" : `note: ${value.note}`,
|
|
70243
71169
|
...value.accounts.map((account) => ` ${account.alias} (${account.id})${account.enabled ? "" : " (disabled)"}${account.authStatus ? ` [${account.authStatus}]` : ""}`)
|
|
@@ -70372,8 +71298,8 @@ function registerClinePassTools(ctx, options) {
|
|
|
70372
71298
|
}
|
|
70373
71299
|
},
|
|
70374
71300
|
render: (_args, value) => {
|
|
70375
|
-
if (value.entries.length === 0) return text$
|
|
70376
|
-
return text$
|
|
71301
|
+
if (value.entries.length === 0) return text$2("No Cline Pass model calls recorded yet in this process.");
|
|
71302
|
+
return text$2([`last ${value.entries.length} of ${value.total} call(s):`, ...value.entries.map((entry) => `${entry.ts === 0 ? "-" : new Date(entry.ts).toISOString()} ${entry.model} -> ${entry.provider || "(auto)"} ${entry.ttft > 0 ? `${entry.ttft}ms` : "—"}/${entry.ms}ms account=${entry.account || "-"}${entry.error === "" ? "" : ` error=${entry.error}`}`)].join("\n"));
|
|
70377
71303
|
}
|
|
70378
71304
|
},
|
|
70379
71305
|
async execute(args) {
|
|
@@ -83906,7 +84832,7 @@ When in doubt, clarify before escalating; if a task turns out more complex than
|
|
|
83906
84832
|
- G6 Prohibited: Answering your own questions, making product/design decisions on the user's behalf, skipping the interview to plan directly, or treating a casual remark about one detail as full interview sign-off.
|
|
83907
84833
|
|
|
83908
84834
|
## R1 · Responsibility Boundary (Layered by Tier)
|
|
83909
|
-
For L2 tasks, never execute work yourself outside these
|
|
84835
|
+
For L2 tasks, never execute work yourself outside these seven categories: (1) Planning & decomposition; (2) Dispatching tasks; (3) Review; (4) Acceptance verification; (5) Documentation & delivery summary; (6) Tasks subagents cannot perform; (7) Independent parallel-module implementation that shares no files, unfinished interfaces, or acceptance dependencies with active subagents and can be verified independently.
|
|
83910
84836
|
For L0/L1 tasks, execute directly—do not spawn subagents just to look like you are orchestrating.
|
|
83911
84837
|
|
|
83912
84838
|
## R2 · Model Routing (Check Availability First, Then Decide How to Dispatch)
|
|
@@ -83962,7 +84888,7 @@ All three rules govern the boundary of a turn: after dispatching, before continu
|
|
|
83962
84888
|
[Waiting for Subagents] By default, end your turn without calling any more tools and let the subagent completion notification wake you—notifications are built-in; do not manufacture your own wait.
|
|
83963
84889
|
- The notification is a post-hoc signal. It tells you a child stopped running; it does not tell you the work is correct, and nothing about it arrives before the work is done. Treat it as "there is now something to review", never as "this passed". The pre-hoc control is the acceptance criteria in the brief (R5); the review itself is R6.
|
|
83964
84890
|
- Never run sleep, polling loops, watch, or any long-running placeholder command just to "hang around": they burn context and budget for zero information.
|
|
83965
|
-
- If you have independent coordinator work in hand (the
|
|
84891
|
+
- If you have independent coordinator work in hand (the seven categories in R1, including non-overlapping parallel-module development, reviewing another subagent's output, or reading evidence you need), do it first and then end your turn. Do not manufacture work merely to keep a provider cache warm.
|
|
83966
84892
|
- Only when you are genuinely blocked and must collect a result immediately should you call job_output({ wait: true }) for a bounded wait; that is a collection tool, not a timer.
|
|
83967
84893
|
- Before ending the turn, state clearly: who was dispatched, what you are waiting for, and what you will do once the evidence arrives. Never leave the user guessing progress from silence.
|
|
83968
84894
|
|
|
@@ -84575,10 +85501,10 @@ const DELIVERY_FAILURE_MARKERS = [
|
|
|
84575
85501
|
"is closing",
|
|
84576
85502
|
"ACTIVATION_CLOSING"
|
|
84577
85503
|
];
|
|
84578
|
-
function record(value) {
|
|
85504
|
+
function record$1(value) {
|
|
84579
85505
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
84580
85506
|
}
|
|
84581
|
-
function text(value) {
|
|
85507
|
+
function text$1(value) {
|
|
84582
85508
|
return typeof value === "string" ? value : void 0;
|
|
84583
85509
|
}
|
|
84584
85510
|
function count(value) {
|
|
@@ -84593,8 +85519,8 @@ function oneLine(value, max) {
|
|
|
84593
85519
|
function sessionTag(session) {
|
|
84594
85520
|
if (session === void 0) return "session=?";
|
|
84595
85521
|
const id = typeof session.id === "string" && session.id !== "" ? session.id : "?";
|
|
84596
|
-
const origin = text(session.header?.origin);
|
|
84597
|
-
const parent = text(session.header?.parentSession);
|
|
85522
|
+
const origin = text$1(session.header?.origin);
|
|
85523
|
+
const parent = text$1(session.header?.parentSession);
|
|
84598
85524
|
return `session=${id}${parent === void 0 || parent === "" ? "" : ` parent=${parent}`}${origin === void 0 ? "" : ` origin=${origin}`}`;
|
|
84599
85525
|
}
|
|
84600
85526
|
/** Block-type histogram plus whether a message carries anything the user can see. */
|
|
@@ -84604,11 +85530,11 @@ function blocksSummary(content) {
|
|
|
84604
85530
|
let visible = false;
|
|
84605
85531
|
let chars = 0;
|
|
84606
85532
|
for (const block of content) {
|
|
84607
|
-
const value = record(block);
|
|
84608
|
-
const type = text(value?.type) ?? "unknown";
|
|
85533
|
+
const value = record$1(block);
|
|
85534
|
+
const type = text$1(value?.type) ?? "unknown";
|
|
84609
85535
|
counts.set(type, (counts.get(type) ?? 0) + 1);
|
|
84610
85536
|
if (type === "tool-call") visible = true;
|
|
84611
|
-
const body = text(value?.text);
|
|
85537
|
+
const body = text$1(value?.text);
|
|
84612
85538
|
if (body !== void 0) {
|
|
84613
85539
|
chars += body.length;
|
|
84614
85540
|
if (type === "text" && body.trim().length > 0) visible = true;
|
|
@@ -84645,9 +85571,9 @@ var RelayProbe = class {
|
|
|
84645
85571
|
}
|
|
84646
85572
|
}
|
|
84647
85573
|
step(session, event) {
|
|
84648
|
-
const type = text(event?.type);
|
|
85574
|
+
const type = text$1(event?.type);
|
|
84649
85575
|
if (type === void 0) return;
|
|
84650
|
-
const data = record(event.data);
|
|
85576
|
+
const data = record$1(event.data);
|
|
84651
85577
|
if (data === void 0) return;
|
|
84652
85578
|
switch (type) {
|
|
84653
85579
|
case "agent/inbox/spliced":
|
|
@@ -84680,9 +85606,9 @@ var RelayProbe = class {
|
|
|
84680
85606
|
this.observedRequest(data);
|
|
84681
85607
|
return;
|
|
84682
85608
|
case "assistant/message": {
|
|
84683
|
-
const message = record(data.message);
|
|
84684
|
-
const source = record(message?.source);
|
|
84685
|
-
const model = source === void 0 ? "" : ` model=${text(source.provider) ?? "?"}/${text(source.model) ?? "?"}`;
|
|
85609
|
+
const message = record$1(data.message);
|
|
85610
|
+
const source = record$1(message?.source);
|
|
85611
|
+
const model = source === void 0 ? "" : ` model=${text$1(source.provider) ?? "?"}/${text$1(source.model) ?? "?"}`;
|
|
84686
85612
|
this.line(`assistant turn=${count(data.turn) ?? "?"} step=${count(data.step) ?? "?"}${model} ${blocksSummary(message?.content)}`);
|
|
84687
85613
|
return;
|
|
84688
85614
|
}
|
|
@@ -84691,16 +85617,16 @@ var RelayProbe = class {
|
|
|
84691
85617
|
}
|
|
84692
85618
|
/** A delivery entering (or leaving) the parent's inbox. */
|
|
84693
85619
|
onSplice(session, data) {
|
|
84694
|
-
const target = text(data.target) ?? "?";
|
|
85620
|
+
const target = text$1(data.target) ?? "?";
|
|
84695
85621
|
const inserted = Array.isArray(data.inserted) ? data.inserted : [];
|
|
84696
85622
|
const removed = count(data.removedCount) ?? 0;
|
|
84697
85623
|
const outcomes = [];
|
|
84698
85624
|
for (const entry of inserted) {
|
|
84699
|
-
const message = record(entry);
|
|
84700
|
-
const source = record(message?.source);
|
|
84701
|
-
const kind = text(source?.kind) ?? "";
|
|
85625
|
+
const message = record$1(entry);
|
|
85626
|
+
const source = record$1(message?.source);
|
|
85627
|
+
const kind = text$1(source?.kind) ?? "";
|
|
84702
85628
|
if (!RELAY_SOURCE_KINDS.includes(kind)) continue;
|
|
84703
|
-
outcomes.push(`kind=${kind}${this.formTag(source)} msg=${text(message?.id) ?? "?"}`);
|
|
85629
|
+
outcomes.push(`kind=${kind}${this.formTag(source)} msg=${text$1(message?.id) ?? "?"}`);
|
|
84704
85630
|
}
|
|
84705
85631
|
if (outcomes.length > 0) {
|
|
84706
85632
|
this.watch(session);
|
|
@@ -84709,84 +85635,84 @@ var RelayProbe = class {
|
|
|
84709
85635
|
}
|
|
84710
85636
|
if (!this.observed.has(session.id)) return;
|
|
84711
85637
|
if (removed === 0 && inserted.length === 0) return;
|
|
84712
|
-
const outcome = text(data.outcome);
|
|
85638
|
+
const outcome = text$1(data.outcome);
|
|
84713
85639
|
this.line(`inbox-splice ${sessionTag(session)} target=${target} inserted=${inserted.length} removed=${removed}${outcome === void 0 ? "" : ` outcome=${outcome}`} pending=${this.pending(session)}`);
|
|
84714
85640
|
}
|
|
84715
85641
|
/** A message committed to the session surface — for a relay, the claim. */
|
|
84716
85642
|
onUserMessage(session, data) {
|
|
84717
|
-
const message = record(data.message);
|
|
84718
|
-
const source = record(message?.source);
|
|
84719
|
-
const kind = text(source?.kind) ?? "?";
|
|
85643
|
+
const message = record$1(data.message);
|
|
85644
|
+
const source = record$1(message?.source);
|
|
85645
|
+
const kind = text$1(source?.kind) ?? "?";
|
|
84720
85646
|
const boundary = RELAY_SOURCE_KINDS.includes(kind);
|
|
84721
85647
|
if (boundary) this.watch(session);
|
|
84722
85648
|
if (!boundary && !this.observed.has(session.id)) return;
|
|
84723
|
-
const sender = text(source?.senderSessionId);
|
|
85649
|
+
const sender = text$1(source?.senderSessionId);
|
|
84724
85650
|
const senderTag = sender === void 0 || sender === "" ? "" : ` sender=${sender}`;
|
|
84725
|
-
this.line(`user-committed ${sessionTag(session)} kind=${kind}${this.formTag(source)} msg=${text(message?.id) ?? "?"}${senderTag} turn=${count(data.turn) ?? "-"} ${blocksSummary(message?.content)} pending=${this.pending(session)}`);
|
|
85651
|
+
this.line(`user-committed ${sessionTag(session)} kind=${kind}${this.formTag(source)} msg=${text$1(message?.id) ?? "?"}${senderTag} turn=${count(data.turn) ?? "-"} ${blocksSummary(message?.content)} pending=${this.pending(session)}`);
|
|
84726
85652
|
if (boundary) this.line(`agent-state ${this.agentState(session)}`);
|
|
84727
85653
|
}
|
|
84728
85654
|
/** A child attempting to report to its parent. */
|
|
84729
85655
|
onToolCall(session, data) {
|
|
84730
|
-
const message = record(data.message);
|
|
85656
|
+
const message = record$1(data.message);
|
|
84731
85657
|
if (!Array.isArray(message?.content)) return;
|
|
84732
85658
|
for (const block of message.content) {
|
|
84733
|
-
const value = record(block);
|
|
84734
|
-
if (text(value?.type) !== "tool-call") continue;
|
|
84735
|
-
const name = text(value?.name) ?? "";
|
|
85659
|
+
const value = record$1(block);
|
|
85660
|
+
if (text$1(value?.type) !== "tool-call") continue;
|
|
85661
|
+
const name = text$1(value?.name) ?? "";
|
|
84736
85662
|
if (!CHILD_SEND_TOOLS.includes(name)) continue;
|
|
84737
|
-
this.line(`child-send ${sessionTag(session)} tool=${name} call=${text(value?.id) ?? "?"}`);
|
|
85663
|
+
this.line(`child-send ${sessionTag(session)} tool=${name} call=${text$1(value?.id) ?? "?"}`);
|
|
84738
85664
|
}
|
|
84739
85665
|
}
|
|
84740
85666
|
/** A tool result that may be a refused delivery. */
|
|
84741
85667
|
onToolResult(session, data) {
|
|
84742
|
-
const message = record(data.message);
|
|
85668
|
+
const message = record$1(data.message);
|
|
84743
85669
|
if (!Array.isArray(message?.content)) return;
|
|
84744
85670
|
for (const block of message.content) {
|
|
84745
|
-
const value = record(block);
|
|
84746
|
-
if (value === void 0 || text(value.type) !== "tool-result") continue;
|
|
85671
|
+
const value = record$1(block);
|
|
85672
|
+
if (value === void 0 || text$1(value.type) !== "tool-result") continue;
|
|
84747
85673
|
const body = this.toolResultText(value.content);
|
|
84748
85674
|
const marker = DELIVERY_FAILURE_MARKERS.find((candidate) => body.includes(candidate));
|
|
84749
85675
|
if (marker === void 0) continue;
|
|
84750
|
-
this.line(`child-send-failed ${sessionTag(session)} call=${text(value.toolCallId) ?? "?"} marker=${JSON.stringify(marker)} excerpt=${JSON.stringify(oneLine(body, EXCERPT_MAX))}`);
|
|
85676
|
+
this.line(`child-send-failed ${sessionTag(session)} call=${text$1(value.toolCallId) ?? "?"} marker=${JSON.stringify(marker)} excerpt=${JSON.stringify(oneLine(body, EXCERPT_MAX))}`);
|
|
84751
85677
|
}
|
|
84752
85678
|
}
|
|
84753
85679
|
toolResultText(content) {
|
|
84754
85680
|
if (!Array.isArray(content)) return "";
|
|
84755
85681
|
const parts = [];
|
|
84756
85682
|
for (const block of content) {
|
|
84757
|
-
const body = text(record(block)?.text);
|
|
85683
|
+
const body = text$1(record$1(block)?.text);
|
|
84758
85684
|
if (body !== void 0) parts.push(body);
|
|
84759
85685
|
}
|
|
84760
85686
|
return parts.join(" ");
|
|
84761
85687
|
}
|
|
84762
85688
|
observedRequest(data) {
|
|
84763
|
-
const config = record(record(data.header)?.config);
|
|
85689
|
+
const config = record$1(record$1(data.header)?.config);
|
|
84764
85690
|
if (config === void 0) return;
|
|
84765
85691
|
const series = data.startsSeries === true ? " startsSeries=yes" : "";
|
|
84766
|
-
this.line(`request provider=${text(config.provider) ?? "?"} model=${text(config.model) ?? "?"} reason=${text(data.reason) ?? "-"}${series}`);
|
|
85692
|
+
this.line(`request provider=${text$1(config.provider) ?? "?"} model=${text$1(config.model) ?? "?"} reason=${text$1(data.reason) ?? "-"}${series}`);
|
|
84767
85693
|
}
|
|
84768
85694
|
turnReason(reason) {
|
|
84769
|
-
const value = record(reason);
|
|
85695
|
+
const value = record$1(reason);
|
|
84770
85696
|
if (value === void 0) return oneLine(String(reason ?? "?"), SUMMARY_MAX);
|
|
84771
|
-
const kind = text(value.kind) ?? "?";
|
|
84772
|
-
const failure = record(value.error);
|
|
85697
|
+
const kind = text$1(value.kind) ?? "?";
|
|
85698
|
+
const failure = record$1(value.error);
|
|
84773
85699
|
if (failure === void 0) return kind;
|
|
84774
|
-
const code = text(failure.code) ?? "?";
|
|
84775
|
-
const message = text(failure.message);
|
|
85700
|
+
const code = text$1(failure.code) ?? "?";
|
|
85701
|
+
const message = text$1(failure.message);
|
|
84776
85702
|
return message === void 0 ? `${kind} code=${code}` : `${kind} code=${code} message=${JSON.stringify(oneLine(message, SUMMARY_MAX))}`;
|
|
84777
85703
|
}
|
|
84778
85704
|
formTag(source) {
|
|
84779
85705
|
if (source === void 0) return "";
|
|
84780
|
-
const form = text(source.form);
|
|
84781
|
-
const summary = text(source.summary);
|
|
84782
|
-
const plugin = text(source.plugin);
|
|
85706
|
+
const form = text$1(source.form);
|
|
85707
|
+
const summary = text$1(source.summary);
|
|
85708
|
+
const plugin = text$1(source.plugin);
|
|
84783
85709
|
return `${form === void 0 ? "" : ` form=${form}`}${plugin === void 0 ? "" : ` plugin=${plugin}`}${summary === void 0 ? "" : ` summary=${JSON.stringify(oneLine(summary, SUMMARY_MAX))}`}`;
|
|
84784
85710
|
}
|
|
84785
85711
|
/** Live snapshot of the Agent behind this Session, when it is resident. */
|
|
84786
85712
|
agentState(session) {
|
|
84787
85713
|
const agent = this.options.agents?.get(session.id);
|
|
84788
85714
|
if (agent === void 0) return `${sessionTag(session)} resident=no`;
|
|
84789
|
-
const status = text(agent.status) ?? "?";
|
|
85715
|
+
const status = text$1(agent.status) ?? "?";
|
|
84790
85716
|
return `${sessionTag(session)} resident=yes status=${status} pending=${agent.inbox === void 0 ? "?" : `nextStep:${agent.inbox.nextStep?.length ?? 0},nextTurn:${agent.inbox.nextTurn?.length ?? 0}`}`;
|
|
84791
85717
|
}
|
|
84792
85718
|
pending(session) {
|
|
@@ -85211,7 +86137,7 @@ function asRegistry$1(value) {
|
|
|
85211
86137
|
* Both reads are guarded: a host without the reader, and one without the
|
|
85212
86138
|
* service, must leave this plugin loadable rather than failing its import.
|
|
85213
86139
|
*/
|
|
85214
|
-
function agentRegistry$
|
|
86140
|
+
function agentRegistry$2(ctx) {
|
|
85215
86141
|
try {
|
|
85216
86142
|
const registry = asRegistry$1(typeof ctx.get === "function" ? ctx.get("agents") : void 0);
|
|
85217
86143
|
if (registry !== void 0) return registry;
|
|
@@ -85290,7 +86216,7 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
85290
86216
|
function resolveAgent(sessionId) {
|
|
85291
86217
|
if (sessionId === void 0) return void 0;
|
|
85292
86218
|
try {
|
|
85293
|
-
const registered = agentRegistry$
|
|
86219
|
+
const registered = agentRegistry$2(ctx)?.get(sessionId);
|
|
85294
86220
|
if (registered !== void 0 && registered !== null) return registered;
|
|
85295
86221
|
} catch {}
|
|
85296
86222
|
return known.get(sessionId);
|
|
@@ -85678,7 +86604,7 @@ function asRegistry(value) {
|
|
|
85678
86604
|
return value !== null && typeof value === "object" && typeof value.get === "function" ? value : void 0;
|
|
85679
86605
|
}
|
|
85680
86606
|
/** The host's Agent registry, read defensively through both available routes. */
|
|
85681
|
-
function agentRegistry(ctx) {
|
|
86607
|
+
function agentRegistry$1(ctx) {
|
|
85682
86608
|
try {
|
|
85683
86609
|
const registry = asRegistry(typeof ctx.get === "function" ? ctx.get("agents") : void 0);
|
|
85684
86610
|
if (registry !== void 0) return registry;
|
|
@@ -85748,7 +86674,7 @@ function installNoAnswerResume(ctx, options = {}) {
|
|
|
85748
86674
|
function resolveAgent(sessionId) {
|
|
85749
86675
|
if (sessionId === void 0) return void 0;
|
|
85750
86676
|
try {
|
|
85751
|
-
const registered = agentRegistry(ctx)?.get(sessionId);
|
|
86677
|
+
const registered = agentRegistry$1(ctx)?.get(sessionId);
|
|
85752
86678
|
if (registered !== void 0 && registered !== null) return registered;
|
|
85753
86679
|
} catch {}
|
|
85754
86680
|
}
|
|
@@ -86575,11 +87501,13 @@ function installCacheWarmup(ctx, options) {
|
|
|
86575
87501
|
*
|
|
86576
87502
|
* 决策取舍写在代码里,因为它全是「什么时候**不动**」:
|
|
86577
87503
|
* - 不是 `accept`(`block` 的 feedback 是别人的东西);
|
|
86578
|
-
* - 已经带 `value`(执行本地的 canonical 值,replay 与结构化消费方读它,
|
|
86579
|
-
* 替换掉等于把一份 JSON 换成字符串);
|
|
86580
87504
|
* - 工具名不是 `run_code`、线路不是本线、会话拿不到;
|
|
86581
87505
|
* - 没有存储、内容为空。
|
|
86582
87506
|
* 上面任何一条成立就原样返回同一个决策对象。
|
|
87507
|
+
*
|
|
87508
|
+
* 决策带了 `value` 不再是「不动」的理由:核心没真正生成预览时,末尾仍然
|
|
87509
|
+
* 原样交回同一个决策对象(`value` 一点不丢);只有核心真的换了内容,
|
|
87510
|
+
* 才换成带预览的 `accept` 决策。
|
|
86583
87511
|
* @param exec - 工具执行上下文。
|
|
86584
87512
|
* @param result - 归一化后的执行结果。
|
|
86585
87513
|
* @param decision - `next()` 交回来的决策。
|
|
@@ -86597,7 +87525,6 @@ function installCacheWarmup(ctx, options) {
|
|
|
86597
87525
|
const navigation = retrievalTool || ptcRetrieval ? cacheWarmupNavigationIntentOf(exec.agent) : void 0;
|
|
86598
87526
|
if (retrievalTool && navigation !== true) return decision;
|
|
86599
87527
|
if (sourceTool && !settings.enabled && navigation !== true) return decision;
|
|
86600
|
-
if (sourceTool && decision.value !== void 0 && navigation !== true) return decision;
|
|
86601
87528
|
const route = cacheWarmupRouteOf(exec.agent);
|
|
86602
87529
|
if (route.provider !== "kimi-code") return decision;
|
|
86603
87530
|
const sessionId = route.sessionId;
|
|
@@ -86672,14 +87599,292 @@ function blocksOf(content) {
|
|
|
86672
87599
|
}];
|
|
86673
87600
|
return content;
|
|
86674
87601
|
}
|
|
86675
|
-
/**
|
|
86676
|
-
|
|
86677
|
-
|
|
86678
|
-
|
|
86679
|
-
|
|
86680
|
-
|
|
86681
|
-
|
|
86682
|
-
|
|
87602
|
+
/** Opt-in switch for one bounded warmup during a delegation batch. */
|
|
87603
|
+
const CACHE_WAIT_WARMUP_ENV = "DSH_KIMI_CODE_CACHE_WAIT_WARMUP";
|
|
87604
|
+
const MAX_TRACKED_SESSIONS = 256;
|
|
87605
|
+
/** Pure cache-wait policy; no child deadline or forced progress is involved. */
|
|
87606
|
+
function evaluateCacheWait(input) {
|
|
87607
|
+
if (!input.waiting || !input.hasChildren || input.lastActiveAt === void 0) return { kind: "none" };
|
|
87608
|
+
if (!Number.isFinite(input.lastActiveAt) || !Number.isFinite(input.now)) return { kind: "none" };
|
|
87609
|
+
const decisionMs = input.decisionMs ?? 12e5;
|
|
87610
|
+
const ttlMs = input.ttlMs ?? 15e5;
|
|
87611
|
+
const minWarmupTokens = input.minWarmupTokens ?? 1e5;
|
|
87612
|
+
const idleMs = Math.max(0, input.now - input.lastActiveAt);
|
|
87613
|
+
if (idleMs >= ttlMs) return { kind: "cold" };
|
|
87614
|
+
if (idleMs < decisionMs) return {
|
|
87615
|
+
kind: "schedule",
|
|
87616
|
+
delayMs: decisionMs - idleMs
|
|
87617
|
+
};
|
|
87618
|
+
if (input.warmupEnabled && !input.warmupUsed && input.totalTokens !== void 0 && Number.isFinite(input.totalTokens) && input.totalTokens >= minWarmupTokens) return { kind: "warmup" };
|
|
87619
|
+
return { kind: "cold" };
|
|
87620
|
+
}
|
|
87621
|
+
/** The one opt-in message, built through the generation-safe harness factory. */
|
|
87622
|
+
function createCacheWaitWarmupMessage() {
|
|
87623
|
+
return createUserMessage$1({
|
|
87624
|
+
content: [{
|
|
87625
|
+
type: "text",
|
|
87626
|
+
text: "Automatic cache maintenance while delegated work continues. This is not a new task and must not change priorities or cause tool calls. Reply with one brief acknowledgment, then continue waiting for existing results. Do not request or manufacture progress from any subagent."
|
|
87627
|
+
}],
|
|
87628
|
+
source: {
|
|
87629
|
+
kind: PLUGIN_MESSAGE_SOURCE_KIND,
|
|
87630
|
+
form: "notice",
|
|
87631
|
+
summary: boundContextSummary("Cache maintenance only; delegated work continues unchanged.")
|
|
87632
|
+
}
|
|
87633
|
+
});
|
|
87634
|
+
}
|
|
87635
|
+
/** Whether the deployment explicitly opted into bounded warmup. */
|
|
87636
|
+
function cacheWaitWarmupEnabled(env = process.env) {
|
|
87637
|
+
const raw = env[CACHE_WAIT_WARMUP_ENV]?.trim().toLowerCase();
|
|
87638
|
+
return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
|
|
87639
|
+
}
|
|
87640
|
+
function record(value) {
|
|
87641
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
87642
|
+
}
|
|
87643
|
+
function text(value) {
|
|
87644
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
87645
|
+
}
|
|
87646
|
+
function defaultTimer() {
|
|
87647
|
+
return {
|
|
87648
|
+
set(callback, delayMs) {
|
|
87649
|
+
const handle = setTimeout(callback, delayMs);
|
|
87650
|
+
handle.unref?.();
|
|
87651
|
+
return handle;
|
|
87652
|
+
},
|
|
87653
|
+
clear(handle) {
|
|
87654
|
+
clearTimeout(handle);
|
|
87655
|
+
}
|
|
87656
|
+
};
|
|
87657
|
+
}
|
|
87658
|
+
function defaultActivity(sessionId) {
|
|
87659
|
+
const durable = sessionCacheState.read(sessionId);
|
|
87660
|
+
const lastActiveAt = sessionLastActiveAt(sessionId) ?? durable?.lastActiveAt;
|
|
87661
|
+
if (lastActiveAt === void 0) return void 0;
|
|
87662
|
+
return {
|
|
87663
|
+
lastActiveAt,
|
|
87664
|
+
totalTokens: durable?.totalTokens
|
|
87665
|
+
};
|
|
87666
|
+
}
|
|
87667
|
+
function agentRegistry(ctx) {
|
|
87668
|
+
try {
|
|
87669
|
+
const value = ctx.get?.("agents") ?? ctx.agents;
|
|
87670
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return value;
|
|
87671
|
+
} catch {}
|
|
87672
|
+
}
|
|
87673
|
+
/** Host-side cache-wait state machine. It never sends anything to a child. */
|
|
87674
|
+
var KimiCacheWaitController = class {
|
|
87675
|
+
options;
|
|
87676
|
+
states = /* @__PURE__ */ new Map();
|
|
87677
|
+
parentByChild = /* @__PURE__ */ new Map();
|
|
87678
|
+
providerBySession = /* @__PURE__ */ new Map();
|
|
87679
|
+
now;
|
|
87680
|
+
timer;
|
|
87681
|
+
agents;
|
|
87682
|
+
activity;
|
|
87683
|
+
kimiSession;
|
|
87684
|
+
warmup;
|
|
87685
|
+
logger;
|
|
87686
|
+
decisionMs;
|
|
87687
|
+
ttlMs;
|
|
87688
|
+
minWarmupTokens;
|
|
87689
|
+
disposed = false;
|
|
87690
|
+
constructor(options = {}) {
|
|
87691
|
+
this.options = options;
|
|
87692
|
+
this.now = options.now ?? (() => Date.now());
|
|
87693
|
+
this.timer = options.timer ?? defaultTimer();
|
|
87694
|
+
this.agents = options.agents;
|
|
87695
|
+
this.activity = options.activity ?? defaultActivity;
|
|
87696
|
+
this.kimiSession = options.kimiSession;
|
|
87697
|
+
this.warmup = options.warmup === true;
|
|
87698
|
+
this.logger = options.logger;
|
|
87699
|
+
this.decisionMs = options.decisionMs ?? 12e5;
|
|
87700
|
+
this.ttlMs = options.ttlMs ?? 15e5;
|
|
87701
|
+
this.minWarmupTokens = options.minWarmupTokens ?? 1e5;
|
|
87702
|
+
}
|
|
87703
|
+
/** Observe one durable Session event. */
|
|
87704
|
+
observe(session, event) {
|
|
87705
|
+
if (this.disposed) return;
|
|
87706
|
+
try {
|
|
87707
|
+
const type = text(event.type);
|
|
87708
|
+
if (type === void 0) return;
|
|
87709
|
+
const data = record(event.data);
|
|
87710
|
+
if (type === "request/header") {
|
|
87711
|
+
const provider = text(record(record(data?.header)?.config)?.provider);
|
|
87712
|
+
if (provider !== void 0) this.providerBySession.set(session.id, provider);
|
|
87713
|
+
return;
|
|
87714
|
+
}
|
|
87715
|
+
if (type === "subagent/catalog") {
|
|
87716
|
+
const childId = text(data?.childId);
|
|
87717
|
+
if (childId === void 0 || childId === session.id) return;
|
|
87718
|
+
const state = this.stateFor(session.id);
|
|
87719
|
+
const newChild = !state.children.has(childId);
|
|
87720
|
+
state.children.add(childId);
|
|
87721
|
+
this.parentByChild.set(childId, session.id);
|
|
87722
|
+
if (newChild) {
|
|
87723
|
+
state.warmupUsed = false;
|
|
87724
|
+
state.handled = false;
|
|
87725
|
+
}
|
|
87726
|
+
this.reconcile(session, state);
|
|
87727
|
+
return;
|
|
87728
|
+
}
|
|
87729
|
+
if (type === "subagent/end") {
|
|
87730
|
+
this.removeChild(text(data?.id));
|
|
87731
|
+
return;
|
|
87732
|
+
}
|
|
87733
|
+
if (type === "user/message") {
|
|
87734
|
+
const source = record(record(data?.message)?.source);
|
|
87735
|
+
if (text(source?.kind) === "subagent-settled") this.removeChild(text(source?.senderSessionId));
|
|
87736
|
+
return;
|
|
87737
|
+
}
|
|
87738
|
+
const state = this.stateFor(session.id);
|
|
87739
|
+
if (type === "turn/start") {
|
|
87740
|
+
state.waiting = false;
|
|
87741
|
+
this.clearTimer(state);
|
|
87742
|
+
return;
|
|
87743
|
+
}
|
|
87744
|
+
if (type === "turn/end") {
|
|
87745
|
+
state.waiting = true;
|
|
87746
|
+
state.handled = false;
|
|
87747
|
+
this.reconcile(session, state);
|
|
87748
|
+
}
|
|
87749
|
+
} catch (error) {
|
|
87750
|
+
this.logger?.warn(`kimi-cache-wait: ignored an invalid event: ${error instanceof Error ? error.message : String(error)}`);
|
|
87751
|
+
}
|
|
87752
|
+
}
|
|
87753
|
+
/** Forget a disposed Agent and any parent/child links it owned. */
|
|
87754
|
+
disposeAgent(agent) {
|
|
87755
|
+
const id = text(agent.id);
|
|
87756
|
+
if (id === void 0) return;
|
|
87757
|
+
this.dropState(id);
|
|
87758
|
+
this.removeChild(id);
|
|
87759
|
+
this.providerBySession.delete(id);
|
|
87760
|
+
}
|
|
87761
|
+
/** Test/diagnostic view; no mutable state escapes. */
|
|
87762
|
+
snapshot(sessionId) {
|
|
87763
|
+
const state = this.states.get(sessionId);
|
|
87764
|
+
return {
|
|
87765
|
+
waiting: state?.waiting ?? false,
|
|
87766
|
+
childCount: state?.children.size ?? 0,
|
|
87767
|
+
handled: state?.handled ?? false,
|
|
87768
|
+
warmupUsed: state?.warmupUsed ?? false,
|
|
87769
|
+
scheduled: state?.timer !== void 0,
|
|
87770
|
+
...state?.lastDecision === void 0 ? {} : { lastDecision: state.lastDecision }
|
|
87771
|
+
};
|
|
87772
|
+
}
|
|
87773
|
+
dispose() {
|
|
87774
|
+
if (this.disposed) return;
|
|
87775
|
+
this.disposed = true;
|
|
87776
|
+
for (const state of this.states.values()) this.clearTimer(state);
|
|
87777
|
+
this.states.clear();
|
|
87778
|
+
this.parentByChild.clear();
|
|
87779
|
+
this.providerBySession.clear();
|
|
87780
|
+
}
|
|
87781
|
+
stateFor(sessionId) {
|
|
87782
|
+
const existing = this.states.get(sessionId);
|
|
87783
|
+
if (existing !== void 0) {
|
|
87784
|
+
this.states.delete(sessionId);
|
|
87785
|
+
this.states.set(sessionId, existing);
|
|
87786
|
+
return existing;
|
|
87787
|
+
}
|
|
87788
|
+
const state = {
|
|
87789
|
+
children: /* @__PURE__ */ new Set(),
|
|
87790
|
+
waiting: false,
|
|
87791
|
+
handled: false,
|
|
87792
|
+
warmupUsed: false
|
|
87793
|
+
};
|
|
87794
|
+
this.states.set(sessionId, state);
|
|
87795
|
+
if (this.states.size > MAX_TRACKED_SESSIONS) {
|
|
87796
|
+
const oldest = this.states.keys().next();
|
|
87797
|
+
if (oldest.done !== true) this.dropState(oldest.value);
|
|
87798
|
+
}
|
|
87799
|
+
return state;
|
|
87800
|
+
}
|
|
87801
|
+
dropState(sessionId) {
|
|
87802
|
+
const state = this.states.get(sessionId);
|
|
87803
|
+
if (state === void 0) return;
|
|
87804
|
+
this.clearTimer(state);
|
|
87805
|
+
for (const child of state.children) this.parentByChild.delete(child);
|
|
87806
|
+
this.states.delete(sessionId);
|
|
87807
|
+
}
|
|
87808
|
+
removeChild(childId) {
|
|
87809
|
+
if (childId === void 0) return;
|
|
87810
|
+
const parent = this.parentByChild.get(childId);
|
|
87811
|
+
if (parent === void 0) return;
|
|
87812
|
+
this.parentByChild.delete(childId);
|
|
87813
|
+
const state = this.states.get(parent);
|
|
87814
|
+
if (state === void 0) return;
|
|
87815
|
+
state.children.delete(childId);
|
|
87816
|
+
if (state.children.size === 0) this.clearTimer(state);
|
|
87817
|
+
}
|
|
87818
|
+
clearTimer(state) {
|
|
87819
|
+
if (state.timer === void 0) return;
|
|
87820
|
+
this.timer.clear(state.timer);
|
|
87821
|
+
state.timer = void 0;
|
|
87822
|
+
}
|
|
87823
|
+
isKimiSession(session) {
|
|
87824
|
+
if (this.kimiSession !== void 0) return this.kimiSession(session);
|
|
87825
|
+
try {
|
|
87826
|
+
const current = text(session.requestHeader?.()?.config?.provider);
|
|
87827
|
+
if (current !== void 0) return current === "kimi-code";
|
|
87828
|
+
} catch {}
|
|
87829
|
+
const latest = this.providerBySession.get(session.id);
|
|
87830
|
+
if (latest !== void 0) return latest === "kimi-code";
|
|
87831
|
+
return this.activity(session.id)?.lastActiveAt !== void 0;
|
|
87832
|
+
}
|
|
87833
|
+
reconcile(session, state) {
|
|
87834
|
+
this.clearTimer(state);
|
|
87835
|
+
if (this.disposed || !state.waiting || state.handled || state.children.size === 0) return;
|
|
87836
|
+
if (!this.isKimiSession(session)) return;
|
|
87837
|
+
const activity = this.activity(session.id);
|
|
87838
|
+
if (activity === void 0) return;
|
|
87839
|
+
const action = evaluateCacheWait({
|
|
87840
|
+
waiting: state.waiting,
|
|
87841
|
+
hasChildren: state.children.size > 0,
|
|
87842
|
+
now: this.now(),
|
|
87843
|
+
lastActiveAt: activity.lastActiveAt,
|
|
87844
|
+
totalTokens: activity.totalTokens,
|
|
87845
|
+
warmupEnabled: this.warmup,
|
|
87846
|
+
warmupUsed: state.warmupUsed,
|
|
87847
|
+
decisionMs: this.decisionMs,
|
|
87848
|
+
ttlMs: this.ttlMs,
|
|
87849
|
+
minWarmupTokens: this.minWarmupTokens
|
|
87850
|
+
});
|
|
87851
|
+
if (action.kind === "none") return;
|
|
87852
|
+
if (action.kind === "schedule") {
|
|
87853
|
+
state.timer = this.timer.set(() => {
|
|
87854
|
+
state.timer = void 0;
|
|
87855
|
+
this.reconcile(session, state);
|
|
87856
|
+
}, action.delayMs);
|
|
87857
|
+
return;
|
|
87858
|
+
}
|
|
87859
|
+
state.handled = true;
|
|
87860
|
+
state.lastDecision = action.kind;
|
|
87861
|
+
if (action.kind === "cold") return;
|
|
87862
|
+
state.warmupUsed = true;
|
|
87863
|
+
try {
|
|
87864
|
+
const agent = this.agents?.get(session.id);
|
|
87865
|
+
if (agent?.steer === void 0) throw new Error("the parent Agent is not available for warmup");
|
|
87866
|
+
agent.steer(createCacheWaitWarmupMessage());
|
|
87867
|
+
this.logger?.info?.(`kimi-cache-wait: queued one bounded warmup for session ${session.id}`);
|
|
87868
|
+
} catch (error) {
|
|
87869
|
+
this.logger?.warn(`kimi-cache-wait: warmup skipped for session ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87870
|
+
}
|
|
87871
|
+
}
|
|
87872
|
+
};
|
|
87873
|
+
/** Install the host listener. Older hosts without the event bus remain inert. */
|
|
87874
|
+
function installKimiCacheWait(ctx, options = {}) {
|
|
87875
|
+
if (typeof ctx.on !== "function") return void 0;
|
|
87876
|
+
const agents = options.agents ?? agentRegistry(ctx);
|
|
87877
|
+
const controller = new KimiCacheWaitController({
|
|
87878
|
+
...options,
|
|
87879
|
+
agents
|
|
87880
|
+
});
|
|
87881
|
+
const disposers = [ctx.on("session/event", (session, event) => controller.observe(session, event)), ctx.on("agent/disposed", ({ agent }) => controller.disposeAgent(agent))];
|
|
87882
|
+
return () => {
|
|
87883
|
+
controller.dispose();
|
|
87884
|
+
for (const handle of disposers) if (typeof handle === "function") handle();
|
|
87885
|
+
else handle?.dispose?.();
|
|
87886
|
+
};
|
|
87887
|
+
}
|
|
86683
87888
|
//#endregion
|
|
86684
87889
|
//#region src/host/cache-warmup/settings.ts
|
|
86685
87890
|
/**
|
|
@@ -94847,6 +96052,14 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
94847
96052
|
};
|
|
94848
96053
|
}
|
|
94849
96054
|
}), "dsh-chatgpt-subscription: cache warmup");
|
|
96055
|
+
ctx.effect(() => {
|
|
96056
|
+
const dispose = installKimiCacheWait(ctx, { warmup: cacheWaitWarmupEnabled(process.env) });
|
|
96057
|
+
if (dispose === void 0) {
|
|
96058
|
+
ctx.logger.warn("[dsh-chatgpt-subscription] Kimi cache wait skipped: this harness exposes no event bus");
|
|
96059
|
+
return () => void 0;
|
|
96060
|
+
}
|
|
96061
|
+
return dispose;
|
|
96062
|
+
}, "dsh-chatgpt-subscription: Kimi cache wait");
|
|
94850
96063
|
const probeEnvFile = relayProbeEnvFile();
|
|
94851
96064
|
if (relayProbeEnabled(process.env, { envFile: probeEnvFile })) {
|
|
94852
96065
|
const probePath = relayProbeLogPath(process.env, { envFile: probeEnvFile });
|