@aexol/spectral 0.6.8 → 0.6.9
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.
|
@@ -45,8 +45,23 @@ function renderContentToString(content) {
|
|
|
45
45
|
}
|
|
46
46
|
if (typeof first.text === "string")
|
|
47
47
|
return first.text;
|
|
48
|
+
// For image/audio/binary content types, return a metadata placeholder
|
|
49
|
+
// instead of serializing the raw base64 data into the model context.
|
|
50
|
+
if (first.type === "image" || first.type === "audio") {
|
|
51
|
+
const mimeType = typeof first.mimeType === "string" ? first.mimeType : first.type;
|
|
52
|
+
const dataLen = typeof first.data === "string" ? first.data.length : 0;
|
|
53
|
+
return `[${first.type}: ${mimeType}${dataLen ? `, ${dataLen.toLocaleString()} bytes base64` : ""}]`;
|
|
54
|
+
}
|
|
48
55
|
try {
|
|
49
|
-
|
|
56
|
+
// Strip large binary fields from serialization to prevent context overflow.
|
|
57
|
+
const safe = content.map((c) => {
|
|
58
|
+
const copy = { ...c };
|
|
59
|
+
if ("data" in copy && typeof copy.data === "string" && copy.data.length > 200) {
|
|
60
|
+
copy.data = `[${copy.data.length.toLocaleString()} bytes base64 omitted]`;
|
|
61
|
+
}
|
|
62
|
+
return copy;
|
|
63
|
+
});
|
|
64
|
+
return JSON.stringify(safe, null, 2);
|
|
50
65
|
}
|
|
51
66
|
catch {
|
|
52
67
|
return String(content);
|
|
@@ -57,7 +57,14 @@ export function transformMcpContent(content) {
|
|
|
57
57
|
}
|
|
58
58
|
if (c.type === "resource") {
|
|
59
59
|
const resourceUri = c.resource?.uri ?? "(no URI)";
|
|
60
|
-
|
|
60
|
+
// Blob resources contain base64 binary data — never serialize the raw
|
|
61
|
+
// blob into the model context (causes context overflow and dead sessions).
|
|
62
|
+
const resourceContent = c.resource?.text ??
|
|
63
|
+
("blob" in (c.resource ?? {}) && c.resource.blob
|
|
64
|
+
? `[Binary blob: ${c.resource.mimeType ?? "application/octet-stream"}]`
|
|
65
|
+
: c.resource
|
|
66
|
+
? JSON.stringify(c.resource)
|
|
67
|
+
: "(no content)");
|
|
61
68
|
return truncateTextBlock({
|
|
62
69
|
type: "text",
|
|
63
70
|
text: `[Resource: ${resourceUri}]\n${resourceContent}`,
|
|
@@ -77,6 +84,15 @@ export function transformMcpContent(content) {
|
|
|
77
84
|
text: `[Audio content: ${c.mimeType ?? "audio/*"}]`,
|
|
78
85
|
};
|
|
79
86
|
}
|
|
80
|
-
|
|
87
|
+
// Unknown content type — serialize as text but strip any potentially
|
|
88
|
+
// large binary fields (data, blob) to avoid context overflow.
|
|
89
|
+
const safe = { ...c };
|
|
90
|
+
if ("data" in safe && typeof safe.data === "string" && safe.data.length > 200) {
|
|
91
|
+
safe.data = `[${safe.data.length.toLocaleString()} bytes base64 omitted]`;
|
|
92
|
+
}
|
|
93
|
+
if ("blob" in safe && typeof safe.blob === "string" && safe.blob.length > 200) {
|
|
94
|
+
safe.blob = `[${safe.blob.length.toLocaleString()} bytes base64 omitted]`;
|
|
95
|
+
}
|
|
96
|
+
return truncateTextBlock({ type: "text", text: JSON.stringify(safe) });
|
|
81
97
|
});
|
|
82
98
|
}
|
|
@@ -699,6 +699,12 @@ export class SessionStreamManager {
|
|
|
699
699
|
stream.loopOriginalPrompt = null;
|
|
700
700
|
stream.loopGoal = null;
|
|
701
701
|
stream.loopIterationCount = 0;
|
|
702
|
+
// Capture whether a turn is in-flight BEFORE we dispose the bridge.
|
|
703
|
+
// dispose() tears down pi, which can cause the in-flight prompt()
|
|
704
|
+
// promise to reject synchronously/microtask and emit an error event
|
|
705
|
+
// through handleBridgeEvent — which clears currentTurn. We must
|
|
706
|
+
// broadcast agent_end regardless of what dispose() does to currentTurn.
|
|
707
|
+
const hadTurn = stream.currentTurn != null;
|
|
702
708
|
// Dispose the pi bridge immediately — this tears down pi's session and
|
|
703
709
|
// unsubscribe. The bridge's own event handler is detached; no further
|
|
704
710
|
// events will flow. We broadcast agent_end ourselves below.
|
|
@@ -718,8 +724,10 @@ export class SessionStreamManager {
|
|
|
718
724
|
stream.currentMessageId = null;
|
|
719
725
|
stream.lastFlushedEventCount = 0;
|
|
720
726
|
// Broadcast agent_end so all subscribers close their open turn and
|
|
721
|
-
// re-enable their composers.
|
|
722
|
-
|
|
727
|
+
// re-enable their composers. Use the pre-disposal flag — dispose()
|
|
728
|
+
// may have cleared currentTurn via an error event from the torn-down
|
|
729
|
+
// pi session.
|
|
730
|
+
if (hadTurn) {
|
|
723
731
|
this.broadcast(stream, { type: "agent_end" });
|
|
724
732
|
stream.currentTurn = null;
|
|
725
733
|
}
|