sparkecoder 0.1.138 → 0.1.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +212 -8
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +220 -9
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-BM99kjgq.d.ts → index-Cl_eUatM.d.ts} +103 -96
- package/dist/index.d.ts +5 -5
- package/dist/index.js +220 -9
- package/dist/index.js.map +1 -1
- package/dist/{schema-Dz-wABVY.d.ts → schema-BSz4MzhJ.d.ts} +3 -3
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +220 -9
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +3 -3
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__e5911ea8._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/{static/chunks/3f50fe2a802aa800.js → chunks/b0cae7e255cae74d.js} +1 -1
- package/web/.next/{static/chunks/3f50fe2a802aa800.js → standalone/web/.next/static/static/chunks/b0cae7e255cae74d.js} +1 -1
- package/web/.next/standalone/web/runtime-config.json +2 -2
- package/web/.next/standalone/web/src/components/chat-interface.tsx +19 -2
- package/web/.next/{standalone/web/.next/static/chunks/3f50fe2a802aa800.js → static/chunks/b0cae7e255cae74d.js} +1 -1
- /package/web/.next/standalone/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_ssgManifest.js +0 -0
- /package/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_buildManifest.js +0 -0
- /package/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{hJ9axFUPZg0HHJCXM9Oyx → QkKMkVPV-LLRD2i9PBP_Y}/_ssgManifest.js +0 -0
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'ai';
|
|
2
|
-
import '../schema-
|
|
3
|
-
export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-
|
|
4
|
-
import '../search-
|
|
2
|
+
import '../schema-BSz4MzhJ.js';
|
|
3
|
+
export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, M as MessageAttachment, d as buildSystemPrompt, e as buildTaskPromptAddendum } from '../index-Cl_eUatM.js';
|
|
4
|
+
import '../search-DOzC4ojH.js';
|
|
5
5
|
import 'drizzle-orm/sqlite-core';
|
|
6
6
|
import 'zod';
|
package/dist/agent/index.js
CHANGED
|
@@ -2220,7 +2220,29 @@ function markRespondedForThread(slackChannel2, threadTs) {
|
|
|
2220
2220
|
}
|
|
2221
2221
|
}
|
|
2222
2222
|
function resolveBatchOnTurnEnd(events, ok) {
|
|
2223
|
-
if (!ok)
|
|
2223
|
+
if (!ok) {
|
|
2224
|
+
for (const ev of events) {
|
|
2225
|
+
const key2 = eventKey(ev);
|
|
2226
|
+
const entry2 = ledger.get(key2);
|
|
2227
|
+
if (!entry2 || TERMINAL.has(entry2.state)) continue;
|
|
2228
|
+
entry2.state = "failed";
|
|
2229
|
+
entry2.updatedAt = Date.now();
|
|
2230
|
+
if (entry2.channel === "slack" && entry2.slackChannel) {
|
|
2231
|
+
if (entry2.messageTs) fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
2232
|
+
if (entry2.threadTs) maybePostFallback(entry2.slackChannel, entry2.threadTs);
|
|
2233
|
+
}
|
|
2234
|
+
recordEvent({
|
|
2235
|
+
source: "daemon",
|
|
2236
|
+
status: "failed",
|
|
2237
|
+
channel: entry2.channel,
|
|
2238
|
+
sessionId: entry2.sessionId,
|
|
2239
|
+
error: "turn failed; not replayed (avoids context-overflow spiral)",
|
|
2240
|
+
textSnippet: entry2.event.content.slice(0, 200),
|
|
2241
|
+
meta: { ackKey: entry2.key, ackState: "failed" }
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2224
2246
|
for (const ev of events) {
|
|
2225
2247
|
const key2 = eventKey(ev);
|
|
2226
2248
|
const entry2 = ledger.get(key2);
|
|
@@ -2272,11 +2294,7 @@ function failEntry(entry2) {
|
|
|
2272
2294
|
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
2273
2295
|
fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
2274
2296
|
if (entry2.threadTs) {
|
|
2275
|
-
|
|
2276
|
-
entry2.slackChannel,
|
|
2277
|
-
entry2.threadTs,
|
|
2278
|
-
`:warning: I wasn't able to handle this after ${entry2.attempts} attempt(s). It may need a human \u2014 flagging it here so it isn't lost.`
|
|
2279
|
-
);
|
|
2297
|
+
maybePostFallback(entry2.slackChannel, entry2.threadTs);
|
|
2280
2298
|
}
|
|
2281
2299
|
}
|
|
2282
2300
|
recordEvent({
|
|
@@ -2319,6 +2337,17 @@ function fireFallback(channel, threadTs, text) {
|
|
|
2319
2337
|
} catch {
|
|
2320
2338
|
}
|
|
2321
2339
|
}
|
|
2340
|
+
function maybePostFallback(channel, threadTs) {
|
|
2341
|
+
const now = Date.now();
|
|
2342
|
+
const last = lastFallbackAt.get(channel) ?? 0;
|
|
2343
|
+
if (now - last < FALLBACK_COOLDOWN_MS) return;
|
|
2344
|
+
lastFallbackAt.set(channel, now);
|
|
2345
|
+
fireFallback(
|
|
2346
|
+
channel,
|
|
2347
|
+
threadTs,
|
|
2348
|
+
"\u26A0\uFE0F I'm having trouble processing messages right now (likely an overloaded context or a backend error). A human may need to check on me \u2014 I'll pick back up once it's resolved."
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2322
2351
|
function startReconciler() {
|
|
2323
2352
|
if (reconcileTimer) return;
|
|
2324
2353
|
reconcileTimer = setInterval(() => {
|
|
@@ -2340,8 +2369,9 @@ function __listAcks() {
|
|
|
2340
2369
|
}
|
|
2341
2370
|
function __resetAcks() {
|
|
2342
2371
|
ledger.clear();
|
|
2372
|
+
lastFallbackAt.clear();
|
|
2343
2373
|
}
|
|
2344
|
-
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
2374
|
+
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, FALLBACK_COOLDOWN_MS, lastFallbackAt, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
2345
2375
|
var init_inbox_acks = __esm({
|
|
2346
2376
|
"src/orchestrator/inbox-acks.ts"() {
|
|
2347
2377
|
"use strict";
|
|
@@ -2352,6 +2382,8 @@ var init_inbox_acks = __esm({
|
|
|
2352
2382
|
RECONCILE_EVERY_MS = 6e4;
|
|
2353
2383
|
MAX_ATTEMPTS = 2;
|
|
2354
2384
|
PRUNE_AFTER_MS = 60 * 6e4;
|
|
2385
|
+
FALLBACK_COOLDOWN_MS = 15 * 6e4;
|
|
2386
|
+
lastFallbackAt = /* @__PURE__ */ new Map();
|
|
2355
2387
|
MAX_ENTRIES = 5e3;
|
|
2356
2388
|
TERMINAL = /* @__PURE__ */ new Set(["responded", "skipped", "handed_off", "failed"]);
|
|
2357
2389
|
SEP = "\u241F";
|
|
@@ -7702,11 +7734,108 @@ function capImageCount(messages, max = MAX_IMAGES_IN_CONTEXT) {
|
|
|
7702
7734
|
return mutated ? out : messages;
|
|
7703
7735
|
}
|
|
7704
7736
|
|
|
7737
|
+
// src/utils/sanitize-images.ts
|
|
7738
|
+
var SUPPORTED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
7739
|
+
var INVALID_IMAGE_PLACEHOLDER = "[invalid image omitted \u2014 the data was not a valid jpeg/png/gif/webp]";
|
|
7740
|
+
function hasImageMagic(bytes) {
|
|
7741
|
+
if (bytes.length < 12) return false;
|
|
7742
|
+
if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return true;
|
|
7743
|
+
if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return true;
|
|
7744
|
+
if (bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56) return true;
|
|
7745
|
+
if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return true;
|
|
7746
|
+
return false;
|
|
7747
|
+
}
|
|
7748
|
+
function extractBase64(part) {
|
|
7749
|
+
const raw = typeof part?.data === "string" ? part.data : typeof part?.image === "string" ? part.image : null;
|
|
7750
|
+
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
7751
|
+
if (/^https?:\/\//i.test(raw)) return null;
|
|
7752
|
+
const dataUrl = raw.match(/^data:[^;,]+;base64,([\s\S]*)$/);
|
|
7753
|
+
return dataUrl ? dataUrl[1] : raw;
|
|
7754
|
+
}
|
|
7755
|
+
function isInvalidImagePart(part) {
|
|
7756
|
+
if (!part || typeof part !== "object") return false;
|
|
7757
|
+
const t = part.type;
|
|
7758
|
+
if (t !== "image" && t !== "image-data" && t !== "media") return false;
|
|
7759
|
+
const mt = part.mediaType;
|
|
7760
|
+
const b64 = extractBase64(part);
|
|
7761
|
+
if (b64 === null) {
|
|
7762
|
+
return typeof mt === "string" && mt.startsWith("image/") && !SUPPORTED_IMAGE_TYPES.includes(mt);
|
|
7763
|
+
}
|
|
7764
|
+
let bytes;
|
|
7765
|
+
try {
|
|
7766
|
+
bytes = Buffer.from(b64, "base64");
|
|
7767
|
+
} catch {
|
|
7768
|
+
return true;
|
|
7769
|
+
}
|
|
7770
|
+
if (bytes.length === 0) return true;
|
|
7771
|
+
return !hasImageMagic(bytes);
|
|
7772
|
+
}
|
|
7773
|
+
function placeholder() {
|
|
7774
|
+
return { type: "text", text: INVALID_IMAGE_PLACEHOLDER };
|
|
7775
|
+
}
|
|
7776
|
+
function sanitizeInvalidImages(messages) {
|
|
7777
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7778
|
+
let mutated = false;
|
|
7779
|
+
let dropped = 0;
|
|
7780
|
+
const out = messages.slice();
|
|
7781
|
+
for (let i = 0; i < out.length; i++) {
|
|
7782
|
+
const msg = out[i];
|
|
7783
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7784
|
+
let contentCloned = false;
|
|
7785
|
+
const ensureCloned = () => {
|
|
7786
|
+
if (contentCloned) return;
|
|
7787
|
+
out[i] = { ...msg, content: [...msg.content] };
|
|
7788
|
+
contentCloned = true;
|
|
7789
|
+
};
|
|
7790
|
+
const content = () => out[i].content;
|
|
7791
|
+
for (let j = 0; j < content().length; j++) {
|
|
7792
|
+
const part = content()[j];
|
|
7793
|
+
if (isInvalidImagePart(part)) {
|
|
7794
|
+
ensureCloned();
|
|
7795
|
+
out[i].content[j] = placeholder();
|
|
7796
|
+
mutated = true;
|
|
7797
|
+
dropped++;
|
|
7798
|
+
continue;
|
|
7799
|
+
}
|
|
7800
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7801
|
+
const innerValue = part.output.value;
|
|
7802
|
+
let innerMutated = false;
|
|
7803
|
+
const newValue = innerValue.slice();
|
|
7804
|
+
for (let k = 0; k < newValue.length; k++) {
|
|
7805
|
+
if (isInvalidImagePart(newValue[k])) {
|
|
7806
|
+
newValue[k] = placeholder();
|
|
7807
|
+
innerMutated = true;
|
|
7808
|
+
dropped++;
|
|
7809
|
+
}
|
|
7810
|
+
}
|
|
7811
|
+
if (innerMutated) {
|
|
7812
|
+
ensureCloned();
|
|
7813
|
+
out[i].content[j] = {
|
|
7814
|
+
...part,
|
|
7815
|
+
output: { ...part.output, value: newValue }
|
|
7816
|
+
};
|
|
7817
|
+
mutated = true;
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
if (mutated) {
|
|
7823
|
+
console.warn(
|
|
7824
|
+
`[sanitize-images] Replaced ${dropped} invalid image part(s) with a text placeholder (non-image bytes / unsupported type) to avoid provider 400 "invalid image" errors.`
|
|
7825
|
+
);
|
|
7826
|
+
}
|
|
7827
|
+
return mutated ? out : messages;
|
|
7828
|
+
}
|
|
7829
|
+
|
|
7705
7830
|
// src/agent/model-limits.ts
|
|
7706
7831
|
var MODEL_LIMITS = {
|
|
7707
7832
|
"claude-opus-4-8": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7708
7833
|
"gpt-5.5": { contextWindow: 35e4, rollingTarget: 15e4 },
|
|
7709
|
-
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 }
|
|
7834
|
+
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7835
|
+
// Claude Opus 4.7 advertises a 1M-token context. Keyed WITH the provider
|
|
7836
|
+
// prefix because getModelLimits() matches the full id (e.g.
|
|
7837
|
+
// "anthropic/claude-opus-4.7") before falling back to the prefix table.
|
|
7838
|
+
"anthropic/claude-opus-4.7": { contextWindow: 1e6, rollingTarget: 15e4 }
|
|
7710
7839
|
};
|
|
7711
7840
|
var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
7712
7841
|
var PREFIX_DEFAULTS = {
|
|
@@ -7777,9 +7906,50 @@ ${summaryContent}`
|
|
|
7777
7906
|
messages = repairToolPairing(messages);
|
|
7778
7907
|
messages = ensureToolResultsFollowCalls(messages);
|
|
7779
7908
|
messages = ensureEndsWithUserOrTool(messages);
|
|
7909
|
+
messages = sanitizeInvalidImages(messages);
|
|
7780
7910
|
messages = capImageCount(messages);
|
|
7911
|
+
messages = this.enforceHardCap(messages);
|
|
7781
7912
|
return messages;
|
|
7782
7913
|
}
|
|
7914
|
+
/**
|
|
7915
|
+
* Drop oldest messages (and, as a last resort, hard-truncate text) until
|
|
7916
|
+
* the prompt fits the model's context window. Preserves any leading
|
|
7917
|
+
* summary system message and always keeps the most recent message, then
|
|
7918
|
+
* repairs tool pairing so dropping can't orphan a tool result.
|
|
7919
|
+
*/
|
|
7920
|
+
enforceHardCap(messages) {
|
|
7921
|
+
const { rollingTarget } = getModelLimits(this.modelId);
|
|
7922
|
+
const MAX_MESSAGES = 120;
|
|
7923
|
+
const tokenCeiling = Math.max(2e4, Math.floor(rollingTarget * 1.5));
|
|
7924
|
+
const tokens = messages.map((m) => this.messageTokens(m));
|
|
7925
|
+
let total = tokens.reduce((a, b) => a + b, 0);
|
|
7926
|
+
const hasLeadingSummary = messages.length > 0 && messages[0].role === "system";
|
|
7927
|
+
const firstBody = hasLeadingSummary ? 1 : 0;
|
|
7928
|
+
const lastIndex = messages.length - 1;
|
|
7929
|
+
const bodyCount = messages.length - firstBody;
|
|
7930
|
+
if (bodyCount <= MAX_MESSAGES && total <= tokenCeiling) return messages;
|
|
7931
|
+
let start = firstBody;
|
|
7932
|
+
const countDropTarget = messages.length - MAX_MESSAGES;
|
|
7933
|
+
while (start < countDropTarget && start < lastIndex) {
|
|
7934
|
+
total -= tokens[start];
|
|
7935
|
+
start += 1;
|
|
7936
|
+
}
|
|
7937
|
+
while (start < lastIndex && total > tokenCeiling) {
|
|
7938
|
+
total -= tokens[start];
|
|
7939
|
+
start += 1;
|
|
7940
|
+
}
|
|
7941
|
+
let out = hasLeadingSummary ? [messages[0], ...messages.slice(start)] : messages.slice(start);
|
|
7942
|
+
if (total > tokenCeiling) {
|
|
7943
|
+
out = out.map((m) => hardTruncateMessageText(m));
|
|
7944
|
+
}
|
|
7945
|
+
out = repairToolPairing(out);
|
|
7946
|
+
out = ensureToolResultsFollowCalls(out);
|
|
7947
|
+
out = ensureEndsWithUserOrTool(out);
|
|
7948
|
+
console.warn(
|
|
7949
|
+
`[Context] hard cap engaged for ${this.modelId}: trimmed ${messages.length}\u2192${out.length} msgs (ceiling ${tokenCeiling} est-tokens / ${MAX_MESSAGES} msgs).`
|
|
7950
|
+
);
|
|
7951
|
+
return out;
|
|
7952
|
+
}
|
|
7783
7953
|
// ---------------------------------------------------------------------------
|
|
7784
7954
|
// Phase 1 – Compact
|
|
7785
7955
|
// ---------------------------------------------------------------------------
|
|
@@ -8006,6 +8176,40 @@ ${summaryContent}`
|
|
|
8006
8176
|
this.summaries = [];
|
|
8007
8177
|
}
|
|
8008
8178
|
};
|
|
8179
|
+
var HARD_TRUNCATE_CHARS = 6e3;
|
|
8180
|
+
function truncateMiddle(s, cap) {
|
|
8181
|
+
if (s.length <= cap) return s;
|
|
8182
|
+
const half = Math.floor(cap / 2);
|
|
8183
|
+
return s.slice(0, half) + `
|
|
8184
|
+
...[truncated ${s.length - cap} chars to fit context]...
|
|
8185
|
+
` + s.slice(-half);
|
|
8186
|
+
}
|
|
8187
|
+
function truncateToolResultText(part) {
|
|
8188
|
+
const trunc = (r) => {
|
|
8189
|
+
if (typeof r === "string") return truncateMiddle(r, HARD_TRUNCATE_CHARS);
|
|
8190
|
+
if (r && typeof r === "object" && typeof r.text === "string") {
|
|
8191
|
+
return { ...r, text: truncateMiddle(r.text, HARD_TRUNCATE_CHARS) };
|
|
8192
|
+
}
|
|
8193
|
+
return r;
|
|
8194
|
+
};
|
|
8195
|
+
if (Array.isArray(part.result)) return { ...part, result: part.result.map(trunc) };
|
|
8196
|
+
if ("result" in part) return { ...part, result: trunc(part.result) };
|
|
8197
|
+
return part;
|
|
8198
|
+
}
|
|
8199
|
+
function hardTruncateMessageText(msg) {
|
|
8200
|
+
if (typeof msg.content === "string") {
|
|
8201
|
+
return { ...msg, content: truncateMiddle(msg.content, HARD_TRUNCATE_CHARS) };
|
|
8202
|
+
}
|
|
8203
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
8204
|
+
const parts = msg.content.map((part) => {
|
|
8205
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
8206
|
+
return { ...part, text: truncateMiddle(part.text, HARD_TRUNCATE_CHARS) };
|
|
8207
|
+
}
|
|
8208
|
+
if (part?.type === "tool-result") return truncateToolResultText(part);
|
|
8209
|
+
return part;
|
|
8210
|
+
});
|
|
8211
|
+
return { ...msg, content: parts };
|
|
8212
|
+
}
|
|
8009
8213
|
function stripBinaryContentForSummary(value) {
|
|
8010
8214
|
if (Array.isArray(value)) return value.map(stripBinaryContentForSummary);
|
|
8011
8215
|
if (!value || typeof value !== "object") return value;
|