@timurproko/a1 0.1.8-dev.332 → 0.1.8-dev.368
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/README.md +40 -11
- package/dist/composition/owned-ui.d.ts +2 -0
- package/dist/composition/owned-ui.js +20 -3
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +2 -0
- package/dist/contracts/owned-ui/model.d.ts +38 -3
- package/dist/contracts/owned-ui/prompt-history.d.ts +18 -0
- package/dist/contracts/owned-ui/prompt-suggestions.d.ts +1 -1
- package/dist/contracts/owned-ui/prompt-suggestions.js +4 -1
- package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
- package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
- package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
- package/dist/contracts/owned-ui/validation.js +45 -2
- package/dist/contracts/presentation/index.d.ts +8 -0
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
- package/dist/features/prompt-history/image-sidecar.js +130 -0
- package/dist/features/prompt-history/index.d.ts +2 -0
- package/dist/features/prompt-history/index.js +1 -0
- package/dist/features/prompt-history/paths.d.ts +1 -0
- package/dist/features/prompt-history/paths.js +2 -1
- package/dist/features/prompt-history/store.d.ts +1 -1
- package/dist/features/prompt-history/store.js +46 -2
- package/dist/features/prompt-history/worker.js +1 -1
- package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
- package/dist/features/prompt-suggestions/diagnostics.js +82 -0
- package/dist/features/prompt-suggestions/index.d.ts +1 -0
- package/dist/features/prompt-suggestions/index.js +1 -0
- package/dist/foundation/release/bootstrap.d.ts +2 -0
- package/dist/foundation/release/bootstrap.js +54 -18
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/update-launch.d.ts +6 -0
- package/dist/foundation/release/update-launch.js +17 -0
- package/dist/foundation/release/update.d.ts +2 -0
- package/dist/foundation/release/update.js +20 -21
- package/dist/foundation/supervision/main.js +5 -2
- package/dist/foundation/supervision/server.js +40 -13
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +9 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +11 -8
- package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
- package/dist/integrations/pi/components/prompt-input-port.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +66 -11
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +9 -5
- package/dist/integrations/pi/components/shell-presenters-info.js +7 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +6 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +81 -49
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +17 -4
- package/dist/integrations/pi/components/shell-shared-facade.js +2 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +20 -4
- package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
- package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +5 -0
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -2
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +41 -33
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +12 -7
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +2 -1
- package/dist/integrations/pi/engine/adapter.js +181 -84
- package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
- package/dist/integrations/pi/engine/tool-rendering.js +122 -0
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +30 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +128 -1
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +8 -0
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +30 -2
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +11 -3
- package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +126 -51
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +24 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +108 -38
- package/dist/integrations/pi/session-ui/session-shell.js +51 -31
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -1
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +106 -16
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +35 -11
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -1
- package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
- package/dist/integrations/pi/tui-runtime/index.js +1 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
- package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +2 -3
- package/dist/ui/components/line-input.js +4 -20
- package/dist/ui/components/mouse.d.ts +1 -1
- package/dist/ui/components/mouse.js +4 -3
- package/dist/ui/components/prompt-input.d.ts +32 -0
- package/dist/ui/components/prompt-input.js +54 -0
- package/docs/architecture/boundaries.md +1 -0
- package/docs/architecture/history-editor-provenance.md +5 -1
- package/docs/architecture/project-structure.md +1 -0
- package/docs/architecture/prompt-suggestions.md +68 -0
- package/docs/architecture/toolchain.md +1 -1
- package/docs/architecture/ui-reference-provenance.md +23 -0
- package/docs/ci-release-runbook.md +45 -7
- package/docs/features/modal-content-interaction.md +37 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapted from @earendil-works/pi-coding-agent 0.84.2
|
|
3
3
|
* packages/coding-agent/src/modes/interactive/components/footer.ts (MIT).
|
|
4
|
-
* Modifications: consumes neutral owned-UI view data rather than a concrete AgentSession
|
|
4
|
+
* Modifications: consumes neutral owned-UI view data rather than a concrete AgentSession;
|
|
5
|
+
* bare A1 colors the effective level-name span while the comparison profile retains pinned styling.
|
|
5
6
|
*/
|
|
6
7
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
7
8
|
import { truncateToWidth, visibleWidth } from "#pi-tui";
|
|
@@ -9,9 +10,11 @@ import { piTheme } from "../../theme.js";
|
|
|
9
10
|
export class SessionFooter {
|
|
10
11
|
getView;
|
|
11
12
|
cwd;
|
|
12
|
-
|
|
13
|
+
profile;
|
|
14
|
+
constructor(getView, cwd, profile = "pi") {
|
|
13
15
|
this.getView = getView;
|
|
14
16
|
this.cwd = cwd;
|
|
17
|
+
this.profile = profile;
|
|
15
18
|
}
|
|
16
19
|
invalidate() { }
|
|
17
20
|
dispose() { }
|
|
@@ -60,12 +63,14 @@ export class SessionFooter {
|
|
|
60
63
|
leftWidth = visibleWidth(left);
|
|
61
64
|
}
|
|
62
65
|
const modelName = view.activeModel?.modelId ?? "no-model";
|
|
63
|
-
const rightWithoutProvider =
|
|
64
|
-
? modelName
|
|
65
|
-
|
|
66
|
+
const rightWithoutProvider = this.profile === "a1"
|
|
67
|
+
? view.activeModel === null ? theme.fg("dim", modelName)
|
|
68
|
+
: theme.fg("dim", `${modelName} • `) + theme.getThinkingBorderColor(view.thinkingLevel)(view.thinkingLevel)
|
|
69
|
+
: view.activeModel === null || view.thinkingLevel === "off" ? modelName : `${modelName} • ${view.thinkingLevel}`;
|
|
66
70
|
let right = rightWithoutProvider;
|
|
67
71
|
if ((view.status.footer?.availableProviderCount ?? 1) > 1 && view.activeModel) {
|
|
68
|
-
|
|
72
|
+
const provider = `(${view.activeModel.providerId}) `;
|
|
73
|
+
right = (this.profile === "a1" ? theme.fg("dim", provider) : provider) + rightWithoutProvider;
|
|
69
74
|
if (leftWidth + 2 + visibleWidth(right) > width)
|
|
70
75
|
right = rightWithoutProvider;
|
|
71
76
|
}
|
|
@@ -85,7 +90,7 @@ export class SessionFooter {
|
|
|
85
90
|
}
|
|
86
91
|
const lines = [
|
|
87
92
|
truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")),
|
|
88
|
-
theme.fg("dim", left) + theme.fg("dim", line.slice(left.length)),
|
|
93
|
+
theme.fg("dim", left) + (this.profile === "a1" ? line.slice(left.length) : theme.fg("dim", line.slice(left.length))),
|
|
89
94
|
];
|
|
90
95
|
const statuses = view.status.footer?.extensionStatuses ?? [];
|
|
91
96
|
if (statuses.length > 0) {
|
|
@@ -500,7 +500,7 @@ export class HistoryEditorCore {
|
|
|
500
500
|
// Render top border (with scroll indicator if scrolled down)
|
|
501
501
|
if (this.persistentHistory && this.historyIndex >= 0) {
|
|
502
502
|
const overflow = this.scrollOffset > 0 ? ` · ↑ ${this.scrollOffset} more` : "";
|
|
503
|
-
const label = `───
|
|
503
|
+
const label = `─── ${this.history.length - this.historyIndex}/${this.history.length}${overflow} `;
|
|
504
504
|
const shown = truncateToWidth(label, width);
|
|
505
505
|
const remaining = Math.max(0, width - visibleWidth(shown));
|
|
506
506
|
result.push(this.borderColor(shown.slice(0, 4))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PiSessionForkPrompt, PiSessionSelection } from "./session-selection.js";
|
|
2
2
|
import { type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, type OwnedUiCommand, type OwnedUiCommandOutcome, type OwnedUiImageAttachment, type OwnedUiPromptSuggestionGeneratorPort, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot } from "../../../contracts/owned-ui/index.js";
|
|
3
|
+
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, type OwnedUiCommand, type OwnedUiCommandOutcome, type OwnedUiImageAttachment, type OwnedUiPromptSuggestionGeneratorPort, type OwnedUiPromptSuggestionReasoning, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot } from "../../../contracts/owned-ui/index.js";
|
|
4
4
|
import { type PiAuthenticationProviderOption, type PiBashWorkflowResult, type PiPinnedSettingsCallback, type PiPinnedSettingsSnapshot, type PiWorkflowAutocompleteCommand, type PiWorkflowHost, type PiWorkflowInteractionHost, type PiWorkflowOption, type PiWorkflowRequest, type PiWorkflowResult } from "./workflows.js";
|
|
5
5
|
import { PiSettingsIntegration } from "./settings-integration.js";
|
|
6
6
|
import type { PiSettingOwnerHandlers } from "./settings-effects.js";
|
|
@@ -145,6 +145,7 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
|
|
|
145
145
|
currentSessionFile(): string | null;
|
|
146
146
|
currentSessionResumeMetadata(): PiSessionResumeMetadata | null;
|
|
147
147
|
get disposed(): boolean;
|
|
148
|
+
suggestionReasoningPolicy(): OwnedUiPromptSuggestionReasoning;
|
|
148
149
|
generate(request: OwnedUiPromptSuggestionRequest): Promise<OwnedUiPromptSuggestionResult>;
|
|
149
150
|
start(): Promise<OwnedUiSessionViewModel>;
|
|
150
151
|
onEvent(listener: (event: OwnedUiEvent) => void): () => void;
|
|
@@ -8,8 +8,9 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
9
9
|
import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
|
|
10
10
|
import { readPinnedCommandChangelog } from "./changelog.js";
|
|
11
|
+
import { toolRenderingInput } from "./tool-rendering.js";
|
|
11
12
|
import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
|
|
13
|
+
import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, acceptsTranscriptUpdate, transcriptToolState, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
|
|
13
14
|
import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
|
|
14
15
|
import { createPiRuntimeIntegration } from "./runtime-integration.js";
|
|
15
16
|
import { PiSessionCommandIntegration } from "./session-integration.js";
|
|
@@ -216,6 +217,17 @@ export class PiEngineAdapter {
|
|
|
216
217
|
get disposed() {
|
|
217
218
|
return this.#disposed;
|
|
218
219
|
}
|
|
220
|
+
suggestionReasoningPolicy() {
|
|
221
|
+
const session = this.#session;
|
|
222
|
+
if (!session?.model)
|
|
223
|
+
return "unavailable";
|
|
224
|
+
if (!session.model.reasoning)
|
|
225
|
+
return "ordinary";
|
|
226
|
+
const levels = session.getAvailableThinkingLevels?.() ?? [];
|
|
227
|
+
// Compatibility: capability order, not the user's current setting or a provider-name heuristic.
|
|
228
|
+
return ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
229
|
+
.find(level => levels.includes(level)) ?? "unavailable";
|
|
230
|
+
}
|
|
219
231
|
async generate(request) {
|
|
220
232
|
assertOwnedUiPromptSuggestionRequest(request);
|
|
221
233
|
const identity = request.identity;
|
|
@@ -230,52 +242,50 @@ export class PiEngineAdapter {
|
|
|
230
242
|
|| activeModel === null
|
|
231
243
|
|| identity.model.providerId !== activeModel.providerId
|
|
232
244
|
|| identity.model.modelId !== activeModel.modelId) {
|
|
233
|
-
return { identity, text: null };
|
|
245
|
+
return { identity, outcome: request.signal.aborted ? "cancelled" : "unavailable", text: null };
|
|
234
246
|
}
|
|
235
247
|
const model = session.model;
|
|
236
248
|
const agentState = session.agent.state;
|
|
237
|
-
|
|
238
|
-
|
|
249
|
+
const policy = this.suggestionReasoningPolicy();
|
|
250
|
+
if (model === undefined || policy === "unavailable" || typeof runtime.services.modelRuntime.completeSimple !== "function") {
|
|
251
|
+
return { identity, outcome: "unavailable", text: null };
|
|
239
252
|
}
|
|
240
253
|
const messages = agentState.messages.filter(message => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
|
|
241
|
-
const reasoning =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const result = { identity, text: null };
|
|
265
|
-
assertOwnedUiPromptSuggestionResult(result);
|
|
266
|
-
return result;
|
|
254
|
+
const reasoning = policy === "ordinary" || policy === "off" ? undefined : policy;
|
|
255
|
+
let response;
|
|
256
|
+
try {
|
|
257
|
+
response = await runtime.services.modelRuntime.completeSimple(model, {
|
|
258
|
+
systemPrompt: agentState.systemPrompt,
|
|
259
|
+
messages: [
|
|
260
|
+
...messages,
|
|
261
|
+
{ role: "user", content: CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, timestamp: Date.now() },
|
|
262
|
+
],
|
|
263
|
+
tools: agentState.tools,
|
|
264
|
+
}, {
|
|
265
|
+
signal: request.signal,
|
|
266
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return { identity, outcome: request.signal.aborted ? "cancelled" : "provider-failure", text: null };
|
|
271
|
+
}
|
|
272
|
+
if (request.signal.aborted || (isRecord(response) && response.stopReason === "aborted")) {
|
|
273
|
+
return { identity, outcome: "cancelled", text: null };
|
|
274
|
+
}
|
|
275
|
+
if (!isRecord(response) || stringValue(response.errorMessage) !== undefined || response.stopReason === "error") {
|
|
276
|
+
return { identity, outcome: "provider-failure", text: null };
|
|
267
277
|
}
|
|
268
278
|
const content = Array.isArray(response.content) ? response.content : [];
|
|
269
|
-
if (content.some(block => isRecord(block) && block.type === "toolCall")) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
if (response.stopReason === "length" || content.some(block => isRecord(block) && block.type === "toolCall")) {
|
|
280
|
+
return { identity, outcome: "rejected", text: null };
|
|
281
|
+
}
|
|
282
|
+
// Security: validate all text, not just the first block of a multi-part response.
|
|
283
|
+
const rawText = content.filter(block => isRecord(block) && block.type === "text")
|
|
284
|
+
.map(block => stringValue(block.text) ?? "").join("\n");
|
|
285
|
+
const text = normalizePromptSuggestionCandidate(rawText);
|
|
286
|
+
const result = text === null
|
|
287
|
+
? { identity, outcome: rawText.trim() ? "rejected" : "empty", text: null }
|
|
288
|
+
: { identity, outcome: "candidate", text };
|
|
279
289
|
assertOwnedUiPromptSuggestionResult(result);
|
|
280
290
|
return result;
|
|
281
291
|
}
|
|
@@ -1989,6 +1999,8 @@ export class PiEngineAdapter {
|
|
|
1989
1999
|
this.#reconcileActiveModelAvailability();
|
|
1990
2000
|
this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
|
|
1991
2001
|
this.#transcriptImageAssets.clear();
|
|
2002
|
+
// Invariant: a new binding cannot inherit arguments/results from a reused invocation id.
|
|
2003
|
+
this.#setTranscript([]);
|
|
1992
2004
|
this.#rebuildTranscript(session.messages, "finalized");
|
|
1993
2005
|
const generation = this.#sessionGeneration;
|
|
1994
2006
|
this.#unsubscribe = session.subscribe(event => {
|
|
@@ -2119,6 +2131,7 @@ export class PiEngineAdapter {
|
|
|
2119
2131
|
}
|
|
2120
2132
|
case "message_end":
|
|
2121
2133
|
this.#upsertMessageBlock(event.message, "finalized");
|
|
2134
|
+
this.#settleFailedDeclarations(event.message);
|
|
2122
2135
|
// Compatibility: preserve the same semantic boundary v2 counted. Transcript block
|
|
2123
2136
|
// finalization is intentionally not a substitute: rebuilds, retries,
|
|
2124
2137
|
// thinking parts, and tool rows can all finalize independently.
|
|
@@ -2164,13 +2177,18 @@ export class PiEngineAdapter {
|
|
|
2164
2177
|
case "agent_end": {
|
|
2165
2178
|
if (event.type === "agent_end" && event.willRetry === true)
|
|
2166
2179
|
return;
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2180
|
+
// Protocol: agent_end is run-local; only settlement reads the complete session scope.
|
|
2181
|
+
const finalMessages = event.type === "agent_settled"
|
|
2182
|
+
? this.#session?.messages ?? []
|
|
2183
|
+
: Array.isArray(event.messages) ? event.messages : [];
|
|
2184
|
+
if (event.type === "agent_end")
|
|
2185
|
+
this.#mergeRunTranscript(finalMessages);
|
|
2186
|
+
else if (finalMessages.length > 0)
|
|
2171
2187
|
this.#rebuildTranscript(finalMessages, "finalized");
|
|
2172
|
-
|
|
2173
|
-
|
|
2188
|
+
// Invariant: missing final messages must not erase accumulated content or invent tool outcomes.
|
|
2189
|
+
if (finalMessages.length === 0)
|
|
2190
|
+
this.#setTranscript(this.#transcript.map(block => block.status === "live" && transcriptToolState(block) === undefined
|
|
2191
|
+
? { ...block, status: "finalized", revision: block.revision + 1 } : block));
|
|
2174
2192
|
// Compatibility: ending a turn leaves the working state, as the recorded pinned baseline does, but
|
|
2175
2193
|
// it leaves only that state: a compaction or retry being shown outlives the turn
|
|
2176
2194
|
// that ended under it. Settlement ends the run, and with it every state — the
|
|
@@ -2286,6 +2304,29 @@ export class PiEngineAdapter {
|
|
|
2286
2304
|
autoCompactEnabled: this.#runtime?.services.settingsManager?.getCompactionEnabled?.() ?? true,
|
|
2287
2305
|
};
|
|
2288
2306
|
}
|
|
2307
|
+
// Invariant: run-local completion may restate messages, but never owns transcript membership.
|
|
2308
|
+
#mergeRunTranscript(messages) {
|
|
2309
|
+
const positions = new Map();
|
|
2310
|
+
const sessionOccurrences = new Map();
|
|
2311
|
+
for (const [index, message] of (this.#session?.messages ?? []).entries()) {
|
|
2312
|
+
const key = messageFallbackKey(message, index);
|
|
2313
|
+
const occurrence = sessionOccurrences.get(key) ?? 0;
|
|
2314
|
+
sessionOccurrences.set(key, occurrence + 1);
|
|
2315
|
+
positions.set(message, { index, occurrence });
|
|
2316
|
+
}
|
|
2317
|
+
const runOccurrences = new Map();
|
|
2318
|
+
for (const [index, message] of messages.entries()) {
|
|
2319
|
+
const key = messageFallbackKey(message, index);
|
|
2320
|
+
const occurrence = runOccurrences.get(key) ?? 0;
|
|
2321
|
+
runOccurrences.set(key, occurrence + 1);
|
|
2322
|
+
const position = positions.get(message) ?? { index, occurrence };
|
|
2323
|
+
for (const block of this.#messageBlocks(message, "finalized", position.index, position.occurrence)) {
|
|
2324
|
+
this.#upsertTranscriptBlock(block);
|
|
2325
|
+
}
|
|
2326
|
+
this.#settleFailedDeclarations(message);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
// Invariant: full replacement is reserved for session-authoritative scope (bind/settlement).
|
|
2289
2330
|
#rebuildTranscript(messages, status) {
|
|
2290
2331
|
const blocks = [];
|
|
2291
2332
|
const blockIndexes = new Map();
|
|
@@ -2294,7 +2335,7 @@ export class PiEngineAdapter {
|
|
|
2294
2335
|
const key = messageFallbackKey(message, index);
|
|
2295
2336
|
const occurrence = occurrences.get(key) ?? 0;
|
|
2296
2337
|
occurrences.set(key, occurrence + 1);
|
|
2297
|
-
for (const block of this.#messageBlocks(message, status, index, occurrence)) {
|
|
2338
|
+
for (const block of this.#messageBlocks(message, status, index, occurrence, id => blocks[blockIndexes.get(id) ?? -1] ?? this.#transcriptBlock(id))) {
|
|
2298
2339
|
const existingIndex = blockIndexes.get(block.id);
|
|
2299
2340
|
if (existingIndex === undefined) {
|
|
2300
2341
|
blockIndexes.set(block.id, blocks.length);
|
|
@@ -2318,7 +2359,8 @@ export class PiEngineAdapter {
|
|
|
2318
2359
|
// it — otherwise every turn that ends re-renders the whole session.
|
|
2319
2360
|
this.#setTranscript(blocks.map(block => {
|
|
2320
2361
|
const existing = this.#transcriptBlock(block.id);
|
|
2321
|
-
|
|
2362
|
+
const next = existing === undefined ? block : retainCompletedArguments(existing, block);
|
|
2363
|
+
return existing !== undefined && sameBlockContent(existing, next) ? existing : next;
|
|
2322
2364
|
}));
|
|
2323
2365
|
}
|
|
2324
2366
|
#upsertMessageBlock(message, status) {
|
|
@@ -2328,7 +2370,7 @@ export class PiEngineAdapter {
|
|
|
2328
2370
|
this.#upsertTranscriptBlock(block);
|
|
2329
2371
|
return first;
|
|
2330
2372
|
}
|
|
2331
|
-
#messageBlocks(message, status, fallbackIndex, occurrence) {
|
|
2373
|
+
#messageBlocks(message, status, fallbackIndex, occurrence, currentBlock = id => this.#transcriptBlock(id)) {
|
|
2332
2374
|
if (!isRecord(message) || typeof message.role !== "string")
|
|
2333
2375
|
return [];
|
|
2334
2376
|
const baseId = this.#messageBlockId(message, fallbackIndex, status, occurrence);
|
|
@@ -2409,25 +2451,24 @@ export class PiEngineAdapter {
|
|
|
2409
2451
|
: this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
|
|
2410
2452
|
if (toolCallId !== undefined)
|
|
2411
2453
|
this.#toolBlockIds.set(toolCallId, blockId);
|
|
2412
|
-
const existing =
|
|
2454
|
+
const existing = currentBlock(blockId);
|
|
2413
2455
|
const existingPayload = isRecord(existing?.payload) ? existing.payload : undefined;
|
|
2456
|
+
const payload = {
|
|
2457
|
+
role: "toolResult", toolCallId: toolCallId ?? null,
|
|
2458
|
+
toolName: stringValue(message.toolName) ?? stringValue(existingPayload?.toolName) ?? "unknown",
|
|
2459
|
+
argsComplete: true, partialResult: status === "live", isError: message.isError === true,
|
|
2460
|
+
};
|
|
2461
|
+
const rendering = toolRenderingInput({ args: undefined, previousArgs: existing?.toolRendering,
|
|
2462
|
+
result: message, payload, image: part => this.#imageReferences([part], "tool-result")[0] });
|
|
2414
2463
|
return [{
|
|
2415
2464
|
id: blockId,
|
|
2416
2465
|
kind: "tool-result",
|
|
2417
2466
|
status,
|
|
2467
|
+
toolState: { argsComplete: true, execution: status === "live" ? "running" : message.isError === true ? "failed" : "succeeded" },
|
|
2418
2468
|
revision: this.#nextBlockRevision(blockId),
|
|
2419
2469
|
title: stringValue(message.toolName) ?? existing?.title ?? "Tool result",
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
payload: {
|
|
2423
|
-
...(existingPayload ?? {}),
|
|
2424
|
-
role: "toolResult",
|
|
2425
|
-
toolCallId: toolCallId ?? null,
|
|
2426
|
-
toolName: stringValue(message.toolName) ?? stringValue(existingPayload?.toolName) ?? "unknown",
|
|
2427
|
-
argsComplete: true,
|
|
2428
|
-
isError: message.isError === true,
|
|
2429
|
-
details: jsonSummary(message.details),
|
|
2430
|
-
},
|
|
2470
|
+
...rendering,
|
|
2471
|
+
payload,
|
|
2431
2472
|
}];
|
|
2432
2473
|
}
|
|
2433
2474
|
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
@@ -2457,23 +2498,61 @@ export class PiEngineAdapter {
|
|
|
2457
2498
|
const toolCallId = stringValue(item.id) ?? `${baseId}:${blocks.length}`;
|
|
2458
2499
|
const blockId = this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
|
|
2459
2500
|
this.#toolBlockIds.set(toolCallId, blockId);
|
|
2501
|
+
const failure = status === "finalized" ? this.#declarationFailure(message) : undefined;
|
|
2502
|
+
const payload = { toolCallId, toolName: stringValue(item.name) ?? "unknown",
|
|
2503
|
+
argsComplete: failure === undefined && status === "finalized",
|
|
2504
|
+
...(failure === undefined ? {} : { isError: true }) };
|
|
2505
|
+
const rendering = toolRenderingInput({ args: item.arguments, payload,
|
|
2506
|
+
...(failure === undefined ? {} : { result: { content: [{ type: "text", text: failure.text }] } }),
|
|
2507
|
+
image: () => undefined });
|
|
2460
2508
|
blocks.push({
|
|
2461
2509
|
id: blockId,
|
|
2462
|
-
kind: "tool-call",
|
|
2463
|
-
status,
|
|
2510
|
+
kind: failure === undefined ? "tool-call" : "tool-result",
|
|
2511
|
+
status: failure === undefined ? "live" : "finalized",
|
|
2512
|
+
toolState: {
|
|
2513
|
+
argsComplete: failure === undefined && status === "finalized",
|
|
2514
|
+
execution: failure?.execution ?? "pending",
|
|
2515
|
+
},
|
|
2464
2516
|
revision: this.#nextBlockRevision(blockId),
|
|
2465
2517
|
title: stringValue(item.name) ?? "Tool",
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
toolName: stringValue(item.name) ?? "unknown",
|
|
2470
|
-
arguments: jsonSummary(item.arguments),
|
|
2471
|
-
argsComplete: status === "finalized",
|
|
2472
|
-
},
|
|
2518
|
+
...rendering,
|
|
2519
|
+
text: failure?.text ?? jsonSummary(item.arguments).summary,
|
|
2520
|
+
payload,
|
|
2473
2521
|
});
|
|
2474
2522
|
}
|
|
2475
2523
|
return blocks;
|
|
2476
2524
|
}
|
|
2525
|
+
#declarationFailure(message) {
|
|
2526
|
+
// Provenance: pinned interactive-mode message_end and history reconstruction settle pending tools.
|
|
2527
|
+
if (message.role !== "assistant")
|
|
2528
|
+
return undefined;
|
|
2529
|
+
if (message.stopReason === "aborted") {
|
|
2530
|
+
const attempts = this.#session?.retryAttempt ?? 0;
|
|
2531
|
+
return { execution: "aborted", text: attempts > 0
|
|
2532
|
+
? `Aborted after ${attempts} retry attempt${attempts > 1 ? "s" : ""}` : "Operation aborted" };
|
|
2533
|
+
}
|
|
2534
|
+
return message.stopReason === "error"
|
|
2535
|
+
? { execution: "failed", text: stringValue(message.errorMessage) || "Error" } : undefined;
|
|
2536
|
+
}
|
|
2537
|
+
#settleFailedDeclarations(message) {
|
|
2538
|
+
if (!isRecord(message))
|
|
2539
|
+
return;
|
|
2540
|
+
const failure = this.#declarationFailure(message);
|
|
2541
|
+
if (failure === undefined)
|
|
2542
|
+
return;
|
|
2543
|
+
for (const block of this.#transcript) {
|
|
2544
|
+
const state = transcriptToolState(block);
|
|
2545
|
+
if (state === undefined || state.execution !== "pending" && state.execution !== "running")
|
|
2546
|
+
continue;
|
|
2547
|
+
const payload = { ...(isRecord(block.payload) ? block.payload : {}), partialResult: false, isError: true };
|
|
2548
|
+
this.#upsertTranscriptBlock({
|
|
2549
|
+
...block, kind: "tool-result", status: "finalized", revision: block.revision + 1,
|
|
2550
|
+
...toolRenderingInput({ args: undefined, previousArgs: block.toolRendering, payload,
|
|
2551
|
+
result: { content: [{ type: "text", text: failure.text }] }, image: () => undefined }),
|
|
2552
|
+
toolState: { ...state, execution: failure.execution }, payload,
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2477
2556
|
#imageReferences(content, source) {
|
|
2478
2557
|
if (!Array.isArray(content))
|
|
2479
2558
|
return [];
|
|
@@ -2499,34 +2578,37 @@ export class PiEngineAdapter {
|
|
|
2499
2578
|
const blockId = this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
|
|
2500
2579
|
this.#toolBlockIds.set(toolCallId, blockId);
|
|
2501
2580
|
const ended = event.type === "tool_execution_end";
|
|
2581
|
+
const existing = this.#transcriptBlock(blockId);
|
|
2582
|
+
const state = existing === undefined ? undefined : transcriptToolState(existing);
|
|
2583
|
+
// Invariant: duplicate starts cannot blank accumulated output; late events cannot reopen a settled invocation.
|
|
2584
|
+
if (state !== undefined && (state.execution !== "pending" && state.execution !== "running"
|
|
2585
|
+
|| event.type === "tool_execution_start" && state.execution === "running"))
|
|
2586
|
+
return;
|
|
2502
2587
|
const source = ended ? event.result : event.partialResult;
|
|
2588
|
+
const payload = { toolCallId, toolName: stringValue(event.toolName) ?? "unknown",
|
|
2589
|
+
partialResult: event.type === "tool_execution_update", argsComplete: true, isError: event.isError === true };
|
|
2590
|
+
const rendering = toolRenderingInput({ args: event.args, previousArgs: existing?.toolRendering, result: source,
|
|
2591
|
+
payload, image: part => this.#imageReferences([part], "tool-result")[0] });
|
|
2503
2592
|
this.#upsertTranscriptBlock({
|
|
2504
2593
|
id: blockId,
|
|
2505
2594
|
kind: ended ? "tool-result" : "tool-call",
|
|
2506
2595
|
status: ended ? "finalized" : "live",
|
|
2596
|
+
toolState: { argsComplete: true, execution: ended ? event.isError === true ? "failed" : "succeeded" : "running" },
|
|
2507
2597
|
revision: this.#nextBlockRevision(blockId),
|
|
2508
2598
|
title: stringValue(event.toolName) ?? "Tool",
|
|
2509
|
-
|
|
2510
|
-
payload
|
|
2511
|
-
toolCallId,
|
|
2512
|
-
toolName: stringValue(event.toolName) ?? "unknown",
|
|
2513
|
-
arguments: jsonSummary(event.args),
|
|
2514
|
-
// Performance: a partial result repeats the whole accumulated output on every chunk;
|
|
2515
|
-
// summarizing it each time would cost quadratic work over the stream.
|
|
2516
|
-
result: ended ? jsonSummary(source) : { summary: "", json: null },
|
|
2517
|
-
partialResult: event.type === "tool_execution_update",
|
|
2518
|
-
argsComplete: ended,
|
|
2519
|
-
isError: event.isError === true,
|
|
2520
|
-
},
|
|
2599
|
+
...rendering,
|
|
2600
|
+
payload,
|
|
2521
2601
|
});
|
|
2522
2602
|
}
|
|
2523
2603
|
#upsertTranscriptBlock(block) {
|
|
2524
2604
|
const index = this.#transcriptIndex.get(block.id);
|
|
2525
2605
|
if (index !== undefined) {
|
|
2526
2606
|
const existing = this.#transcript[index];
|
|
2527
|
-
// Invariant:
|
|
2528
|
-
if (existing
|
|
2607
|
+
// Invariant: argument completion is not execution finality; stale phases still stay rejected.
|
|
2608
|
+
if (existing !== undefined && !acceptsTranscriptUpdate(existing, block))
|
|
2529
2609
|
return;
|
|
2610
|
+
if (existing !== undefined)
|
|
2611
|
+
block = retainCompletedArguments(existing, block);
|
|
2530
2612
|
// Performance: nothing is emitted for a block that repeats itself, and keeping the
|
|
2531
2613
|
// revision keeps the rows it already rendered.
|
|
2532
2614
|
if (existing !== undefined && sameBlockContent(existing, block))
|
|
@@ -2676,6 +2758,9 @@ export class PiEngineAdapter {
|
|
|
2676
2758
|
this.#lifecycle = this.#disposed ? "stopped" : canResume ? "ready" : "failed";
|
|
2677
2759
|
this.#editor = { ...this.#editor, submitEnabled: canResume && !this.#disposed };
|
|
2678
2760
|
this.#viewRevision += 1;
|
|
2761
|
+
// Invariant: overload publishes one authoritative view; settle tool phases before finalizing other live blocks.
|
|
2762
|
+
this.#settleFailedDeclarations({ role: "assistant", stopReason: cancelled ? "aborted" : "error",
|
|
2763
|
+
errorMessage: "Tool result unavailable after UI delivery overload" });
|
|
2679
2764
|
this.#setTranscript(this.#transcript.map(block => block.status === "live" ? { ...block, status: "finalized", revision: block.revision + 1 } : block));
|
|
2680
2765
|
for (const [correlationId, result] of this.#reservedOutcomes) {
|
|
2681
2766
|
this.#deliver(this.#event({ type: "command-outcome", correlationId, ...result }));
|
|
@@ -3224,6 +3309,16 @@ function compactResourceLabel(path) {
|
|
|
3224
3309
|
const segments = path.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
3225
3310
|
return segments.at(-1) ?? path;
|
|
3226
3311
|
}
|
|
3312
|
+
/** A later error may restate a declaration, but cannot make completed arguments incomplete. */
|
|
3313
|
+
function retainCompletedArguments(current, next) {
|
|
3314
|
+
if (current.toolState?.argsComplete !== true || next.toolState === undefined || next.toolState.argsComplete)
|
|
3315
|
+
return next;
|
|
3316
|
+
return { ...next, toolState: { ...next.toolState, argsComplete: true },
|
|
3317
|
+
...(next.toolRendering === undefined || current.toolRendering === undefined ? {} : {
|
|
3318
|
+
toolRendering: { ...next.toolRendering, arguments: current.toolRendering.arguments },
|
|
3319
|
+
}),
|
|
3320
|
+
payload: { ...(isRecord(next.payload) ? next.payload : {}), argsComplete: true } };
|
|
3321
|
+
}
|
|
3227
3322
|
/**
|
|
3228
3323
|
* Whether two blocks say the same thing. A block that says what it already said is not a
|
|
3229
3324
|
* new revision: the shell renders a block once per revision, so bumping one it did not
|
|
@@ -3234,6 +3329,8 @@ function sameBlockContent(left, right) {
|
|
|
3234
3329
|
&& left.status === right.status
|
|
3235
3330
|
&& left.title === right.title
|
|
3236
3331
|
&& left.text === right.text
|
|
3332
|
+
&& sameValue(left.toolState, right.toolState)
|
|
3333
|
+
&& sameValue(left.toolRendering, right.toolRendering)
|
|
3237
3334
|
&& sameValue(left.payload, right.payload)
|
|
3238
3335
|
&& sameValue(left.imageReferences ?? [], right.imageReferences ?? []);
|
|
3239
3336
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OwnedUiToolRendering, OwnedUiTranscriptImageReference } from "../../../contracts/owned-ui/index.js";
|
|
2
|
+
/** Text lives once on the block; content parts refer to UTF-16 slices or opaque image references. */
|
|
3
|
+
export declare function toolRenderingInput(options: {
|
|
4
|
+
readonly args: unknown;
|
|
5
|
+
readonly previousArgs?: OwnedUiToolRendering | undefined;
|
|
6
|
+
readonly result?: unknown;
|
|
7
|
+
readonly payload: Record<string, unknown>;
|
|
8
|
+
readonly image: (value: unknown) => OwnedUiTranscriptImageReference | undefined;
|
|
9
|
+
}): {
|
|
10
|
+
text: string;
|
|
11
|
+
toolRendering: OwnedUiToolRendering;
|
|
12
|
+
imageReferences: readonly OwnedUiTranscriptImageReference[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const MAX_METADATA_BYTES = 64 * 1024;
|
|
2
|
+
/** Copy JSON metadata without diagnostic depth/array truncation or traversing result text/images. */
|
|
3
|
+
function renderingJson(value) {
|
|
4
|
+
let remaining = MAX_METADATA_BYTES;
|
|
5
|
+
const encoded = JSON.stringify(value, function (key, item) {
|
|
6
|
+
if (key && !Array.isArray(this))
|
|
7
|
+
remaining -= jsonStringBytes(key) + 1;
|
|
8
|
+
if (typeof item === "string")
|
|
9
|
+
remaining -= jsonStringBytes(item);
|
|
10
|
+
else if (item === null || typeof item === "boolean" || typeof item === "number" && Number.isFinite(item)) {
|
|
11
|
+
remaining -= String(item).length;
|
|
12
|
+
}
|
|
13
|
+
else if (typeof item === "object")
|
|
14
|
+
remaining -= 2;
|
|
15
|
+
else if (item === undefined)
|
|
16
|
+
return item;
|
|
17
|
+
else
|
|
18
|
+
throw new TypeError("unsupported JSON metadata");
|
|
19
|
+
if (--remaining < 0)
|
|
20
|
+
throw new RangeError("metadata exceeds the supported payload limit");
|
|
21
|
+
return item;
|
|
22
|
+
});
|
|
23
|
+
if (encoded === undefined)
|
|
24
|
+
throw new TypeError("unsupported JSON metadata");
|
|
25
|
+
if (Buffer.byteLength(encoded) > MAX_METADATA_BYTES)
|
|
26
|
+
throw new RangeError("metadata exceeds the supported payload limit");
|
|
27
|
+
return JSON.parse(encoded);
|
|
28
|
+
}
|
|
29
|
+
function jsonStringBytes(value) {
|
|
30
|
+
// Performance: reject very large strings before allocating their escaped representation.
|
|
31
|
+
if (value.length > MAX_METADATA_BYTES)
|
|
32
|
+
throw new RangeError("metadata exceeds the supported payload limit");
|
|
33
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
34
|
+
}
|
|
35
|
+
/** Text lives once on the block; content parts refer to UTF-16 slices or opaque image references. */
|
|
36
|
+
export function toolRenderingInput(options) {
|
|
37
|
+
const warnings = new Set();
|
|
38
|
+
let args = {};
|
|
39
|
+
try {
|
|
40
|
+
args = options.args === undefined && options.previousArgs !== undefined
|
|
41
|
+
? options.previousArgs.arguments : renderingJson(options.args ?? {});
|
|
42
|
+
if (options.args === undefined && options.previousArgs?.unavailable?.includes("Tool arguments unavailable")) {
|
|
43
|
+
warnings.add("Tool arguments unavailable: unsupported or oversized rendering data.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
warnings.add("Tool arguments unavailable: unsupported or oversized rendering data.");
|
|
48
|
+
}
|
|
49
|
+
const imageReferences = [];
|
|
50
|
+
const content = [];
|
|
51
|
+
const texts = [];
|
|
52
|
+
let length = 0;
|
|
53
|
+
const source = isRecord(options.result) ? options.result : undefined;
|
|
54
|
+
const rawContent = source === undefined ? options.result : source.content;
|
|
55
|
+
const parts = typeof rawContent === "string" ? [{ type: "text", text: rawContent }] : rawContent;
|
|
56
|
+
if (Array.isArray(parts)) {
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
if (isRecord(part) && part.type === "text" && typeof part.text === "string") {
|
|
59
|
+
if (texts.length > 0)
|
|
60
|
+
length++;
|
|
61
|
+
const start = length;
|
|
62
|
+
texts.push(part.text);
|
|
63
|
+
length += part.text.length;
|
|
64
|
+
content.push({ type: "text", start, end: length });
|
|
65
|
+
}
|
|
66
|
+
else if (isRecord(part) && part.type === "image") {
|
|
67
|
+
const reference = imageReferences.length < 16 ? options.image(part) : undefined;
|
|
68
|
+
if (reference === undefined)
|
|
69
|
+
warnings.add("Image unavailable: unsupported data or attachment limit exceeded.");
|
|
70
|
+
else {
|
|
71
|
+
content.push({ type: "image", imageIndex: imageReferences.length });
|
|
72
|
+
imageReferences.push(reference);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
warnings.add("Tool content unavailable: unsupported content part.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (options.result !== undefined)
|
|
80
|
+
warnings.add("Tool content unavailable: malformed result.");
|
|
81
|
+
let details;
|
|
82
|
+
if (source?.details !== undefined) {
|
|
83
|
+
try {
|
|
84
|
+
details = renderingJson(source.details);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
warnings.add("Tool details unavailable: unsupported or oversized rendering data.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const text = texts.join("\n");
|
|
91
|
+
let toolRendering = {
|
|
92
|
+
arguments: args,
|
|
93
|
+
...(options.result === undefined ? {} : { result: { content, ...(details === undefined ? {} : { details }) } }),
|
|
94
|
+
};
|
|
95
|
+
const warning = () => [...warnings].join(" ");
|
|
96
|
+
const fits = () => Buffer.byteLength(JSON.stringify({ payload: options.payload, toolRendering: {
|
|
97
|
+
...toolRendering, ...(warnings.size === 0 ? {} : { unavailable: warning() }),
|
|
98
|
+
} })) <= MAX_METADATA_BYTES;
|
|
99
|
+
if (!fits()) {
|
|
100
|
+
warnings.add("Tool details unavailable: rendering metadata exceeds the supported payload limit.");
|
|
101
|
+
if (toolRendering.result)
|
|
102
|
+
toolRendering = { ...toolRendering, result: { content } };
|
|
103
|
+
}
|
|
104
|
+
if (!fits()) {
|
|
105
|
+
warnings.add("Tool arguments unavailable: rendering metadata exceeds the supported payload limit.");
|
|
106
|
+
toolRendering = { ...toolRendering, arguments: {} };
|
|
107
|
+
}
|
|
108
|
+
if (!fits()) {
|
|
109
|
+
// Rationale: too many content boundaries exceed the metadata budget; retain text/images with an explicit fallback.
|
|
110
|
+
warnings.add("Tool content boundaries unavailable: rendering metadata exceeds the supported payload limit.");
|
|
111
|
+
toolRendering = { ...toolRendering, result: { content: [
|
|
112
|
+
{ type: "text", start: 0, end: text.length },
|
|
113
|
+
...imageReferences.map((_, imageIndex) => ({ type: "image", imageIndex })),
|
|
114
|
+
] } };
|
|
115
|
+
}
|
|
116
|
+
if (warnings.size > 0)
|
|
117
|
+
toolRendering = { ...toolRendering, unavailable: warning() };
|
|
118
|
+
return { text, toolRendering, imageReferences };
|
|
119
|
+
}
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
122
|
+
}
|