@genesislcap/ai-assistant 15.19.0 → 15.19.1
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/package.json +17 -18
- package/api-extractor.json +0 -11
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
- package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
- package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
- package/dist/dts/config/validate-providers.test.d.ts +0 -2
- package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
- package/dist/dts/main/blocked-state.test.d.ts +0 -2
- package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
- package/dist/dts/main/budget-meter.test.d.ts +0 -2
- package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
- package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
- package/dist/dts/main/file-attachments.test.d.ts +0 -2
- package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
- package/dist/dts/main/interaction-cost.test.d.ts +0 -2
- package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
- package/dist/dts/main/main.styles.test.d.ts +0 -2
- package/dist/dts/main/main.styles.test.d.ts.map +0 -1
- package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
- package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
- package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
- package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
- package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
- package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
- package/dist/dts/state/debug-event-log.test.d.ts +0 -2
- package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
- package/dist/dts/state/driver-registry.test.d.ts +0 -2
- package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
- package/dist/dts/state/interaction-context.test.d.ts +0 -2
- package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
- package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
- package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
- package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
- package/dist/dts/utils/condense-history.test.d.ts +0 -2
- package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
- package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
- package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
- package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
- package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
- package/dist/dts/utils/history-transform.test.d.ts +0 -2
- package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
- package/dist/dts/utils/message-partition.test.d.ts +0 -2
- package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-costs.test.d.ts +0 -2
- package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
- package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-usage.test.d.ts +0 -2
- package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
- package/dist/dts/utils/usage-rows.test.d.ts +0 -2
- package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
- package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
- package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
- package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
- package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
- package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
- package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
- package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
- package/dist/esm/config/define-stateful-agent.test.js +0 -53
- package/dist/esm/config/validate-providers.test.js +0 -100
- package/dist/esm/main/blocked-state.test.js +0 -1065
- package/dist/esm/main/budget-meter.test.js +0 -359
- package/dist/esm/main/cost-session-banking.test.js +0 -538
- package/dist/esm/main/file-attachments.test.js +0 -164
- package/dist/esm/main/interaction-cost.test.js +0 -107
- package/dist/esm/main/main.styles.test.js +0 -161
- package/dist/esm/main/persistence-broken-sources.test.js +0 -180
- package/dist/esm/main/popout-interaction-gate.test.js +0 -54
- package/dist/esm/state/ai-assistant-slice.test.js +0 -229
- package/dist/esm/state/debug-event-log.test.js +0 -117
- package/dist/esm/state/driver-registry.test.js +0 -55
- package/dist/esm/state/interaction-context.test.js +0 -78
- package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
- package/dist/esm/state/persistence/diagnostics.test.js +0 -280
- package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
- package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
- package/dist/esm/state/persistence/session-persister.test.js +0 -613
- package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
- package/dist/esm/utils/animation-exclusivity.test.js +0 -52
- package/dist/esm/utils/collect-session-models.test.js +0 -53
- package/dist/esm/utils/condense-history.test.js +0 -547
- package/dist/esm/utils/cost-session-history.test.js +0 -174
- package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
- package/dist/esm/utils/history-transform.test.js +0 -158
- package/dist/esm/utils/message-partition.test.js +0 -90
- package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
- package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
- package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
- package/dist/esm/utils/sum-costs.test.js +0 -114
- package/dist/esm/utils/sum-tokens.test.js +0 -51
- package/dist/esm/utils/sum-usage.test.js +0 -164
- package/dist/esm/utils/usage-rows.test.js +0 -189
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/license.txt +0 -46
- package/sandbox/tsconfig.json +0 -8
- package/src/channel/ai-activity-bus.ts +0 -209
- package/src/channel/ai-activity-channel.ts +0 -146
- package/src/chat-driver-node.ts +0 -159
- package/src/components/activity-halo/activity-halo.ts +0 -106
- package/src/components/agent-picker/agent-picker.constants.ts +0 -2
- package/src/components/agent-picker/agent-picker.styles.ts +0 -24
- package/src/components/agent-picker/agent-picker.template.ts +0 -54
- package/src/components/agent-picker/agent-picker.ts +0 -99
- package/src/components/agent-picker/index.ts +0 -1
- package/src/components/ai-driver/ai-driver.ts +0 -166
- package/src/components/ai-driver/index.ts +0 -1
- package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
- package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
- package/src/components/chat-bubble/chat-bubble.ts +0 -436
- package/src/components/chat-bubble/index.ts +0 -1
- package/src/components/chat-driver/align-event-globals.ts +0 -23
- package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
- package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
- package/src/components/chat-driver/chat-driver.test.ts +0 -3679
- package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
- package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
- package/src/components/chat-driver/chat-driver.ts +0 -3925
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
- package/src/components/chat-driver/index.ts +0 -1
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
- package/src/components/chat-interaction-wrapper/index.ts +0 -3
- package/src/components/chat-markdown/chat-markdown.ts +0 -89
- package/src/components/chat-markdown/index.ts +0 -1
- package/src/components/flowing-waves-indicator.ts +0 -286
- package/src/components/halo-overlay.ts +0 -184
- package/src/components/orchestrating-driver/index.ts +0 -1
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
- package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
- package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
- package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
- package/src/components/plasma-orb-indicator.ts +0 -281
- package/src/components/popout-manager/index.ts +0 -1
- package/src/components/popout-manager/popout-manager.ts +0 -212
- package/src/components/settings-modal/index.ts +0 -2
- package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
- package/src/components/settings-modal/settings-modal.styles.ts +0 -853
- package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
- package/src/components/settings-modal/settings-modal.template.ts +0 -943
- package/src/components/waves-indicator.ts +0 -241
- package/src/config/config.ts +0 -608
- package/src/config/define-stateful-agent.test.ts +0 -64
- package/src/config/define-stateful-agent.ts +0 -642
- package/src/config/fallback-agents.ts +0 -29
- package/src/config/index.ts +0 -2
- package/src/config/validate-providers.test.ts +0 -148
- package/src/config/validate-providers.ts +0 -58
- package/src/index.ts +0 -29
- package/src/main/blocked-state.test.ts +0 -1444
- package/src/main/budget-meter.test.ts +0 -477
- package/src/main/cost-session-banking.test.ts +0 -679
- package/src/main/file-attachments.test.ts +0 -215
- package/src/main/index.ts +0 -3
- package/src/main/interaction-cost.test.ts +0 -140
- package/src/main/main.styles.test.ts +0 -216
- package/src/main/main.styles.ts +0 -995
- package/src/main/main.template.ts +0 -1119
- package/src/main/main.ts +0 -5432
- package/src/main/main.types.ts +0 -225
- package/src/main/persistence-broken-sources.test.ts +0 -219
- package/src/main/popout-interaction-gate.test.ts +0 -87
- package/src/provider/ai-provider-switcher.ts +0 -40
- package/src/provider/assistant-app-settings.ts +0 -84
- package/src/state/ai-assistant-slice.test.ts +0 -336
- package/src/state/ai-assistant-slice.ts +0 -604
- package/src/state/debug-event-log.test.ts +0 -154
- package/src/state/debug-event-log.ts +0 -358
- package/src/state/driver-registry.test.ts +0 -70
- package/src/state/driver-registry.ts +0 -79
- package/src/state/interaction-context.test.ts +0 -90
- package/src/state/interaction-context.ts +0 -101
- package/src/state/persistence/build-timeline-entries.ts +0 -66
- package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
- package/src/state/persistence/diagnostics-cursors.ts +0 -80
- package/src/state/persistence/diagnostics.test.ts +0 -346
- package/src/state/persistence/diagnostics.ts +0 -220
- package/src/state/persistence/index.ts +0 -10
- package/src/state/persistence/persister-registry.ts +0 -55
- package/src/state/persistence/session-persistence-provider.test.ts +0 -136
- package/src/state/persistence/session-persistence-provider.ts +0 -226
- package/src/state/persistence/session-persistence.integration.test.ts +0 -141
- package/src/state/persistence/session-persister.test.ts +0 -787
- package/src/state/persistence/session-persister.ts +0 -917
- package/src/state/persistence/session-snapshot.test.ts +0 -352
- package/src/state/persistence/session-snapshot.ts +0 -344
- package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
- package/src/state/session-store.ts +0 -56
- package/src/styles/ai-colours.ts +0 -30
- package/src/styles/menu.ts +0 -187
- package/src/styles/settings-section.ts +0 -40
- package/src/suggestions/chat-suggestions.ts +0 -186
- package/src/tags/index.ts +0 -1
- package/src/types/ai-chat-widget.ts +0 -67
- package/src/types/interaction-context.ts +0 -82
- package/src/utils/animated-panel-toggle.ts +0 -62
- package/src/utils/animation-exclusivity.test.ts +0 -72
- package/src/utils/animation-exclusivity.ts +0 -40
- package/src/utils/banked-usage-baselines.ts +0 -90
- package/src/utils/collect-session-models.test.ts +0 -82
- package/src/utils/collect-session-models.ts +0 -65
- package/src/utils/condense-history.test.ts +0 -746
- package/src/utils/condense-history.ts +0 -448
- package/src/utils/cost-session-history.test.ts +0 -226
- package/src/utils/cost-session-history.ts +0 -218
- package/src/utils/derive-cost-session-title.test.ts +0 -63
- package/src/utils/derive-cost-session-title.ts +0 -76
- package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
- package/src/utils/flatten-sub-agent-messages.ts +0 -88
- package/src/utils/format-usd.ts +0 -26
- package/src/utils/history-transform.test.ts +0 -228
- package/src/utils/history-transform.ts +0 -163
- package/src/utils/index.ts +0 -3
- package/src/utils/logger.ts +0 -3
- package/src/utils/message-partition.test.ts +0 -129
- package/src/utils/message-partition.ts +0 -115
- package/src/utils/resolve-cost-history-config.test.ts +0 -38
- package/src/utils/resolve-cost-history-config.ts +0 -38
- package/src/utils/resolve-preference-baseline.test.ts +0 -84
- package/src/utils/resolve-preference-baseline.ts +0 -80
- package/src/utils/strip-agent-handlers.test.ts +0 -99
- package/src/utils/strip-agent-handlers.ts +0 -53
- package/src/utils/sum-costs.test.ts +0 -136
- package/src/utils/sum-costs.ts +0 -18
- package/src/utils/sum-tokens.test.ts +0 -93
- package/src/utils/sum-tokens.ts +0 -68
- package/src/utils/sum-usage.test.ts +0 -191
- package/src/utils/sum-usage.ts +0 -208
- package/src/utils/tool-fold.ts +0 -181
- package/src/utils/usage-rows.test.ts +0 -237
- package/src/utils/usage-rows.ts +0 -187
- package/src/utils/with-timeout.ts +0 -24
- package/tsconfig.json +0 -9
|
@@ -1,746 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage, CondensePolicy } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import {
|
|
4
|
-
applyCondensation,
|
|
5
|
-
CONDENSE_MIN_CHARS,
|
|
6
|
-
CONDENSED_ARGS_KEY,
|
|
7
|
-
type CondenseContext,
|
|
8
|
-
type CondensedEventDetail,
|
|
9
|
-
type RegisteredCondensePolicy,
|
|
10
|
-
} from './condense-history';
|
|
11
|
-
|
|
12
|
-
const suite = createLogicSuite('applyCondensation');
|
|
13
|
-
|
|
14
|
-
// Payloads must clear the driver-level floor to be condensed.
|
|
15
|
-
const big = (n: number = CONDENSE_MIN_CHARS + 500): string => 'x'.repeat(n);
|
|
16
|
-
|
|
17
|
-
const asstCall = (
|
|
18
|
-
id: string,
|
|
19
|
-
name: string,
|
|
20
|
-
args: Record<string, unknown> = {},
|
|
21
|
-
extra: Record<string, unknown> = {},
|
|
22
|
-
): ChatMessage => ({ role: 'assistant', content: '', toolCalls: [{ id, name, args, ...extra }] });
|
|
23
|
-
|
|
24
|
-
const toolMsg = (toolCallId: string, content: string): ChatMessage => ({
|
|
25
|
-
role: 'tool',
|
|
26
|
-
content: '',
|
|
27
|
-
toolResult: { toolCallId, content },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const reg = (
|
|
31
|
-
policy: CondensePolicy,
|
|
32
|
-
clocks: { iteration?: number; turn?: number; activation?: number; phaseEpoch?: number } = {},
|
|
33
|
-
): RegisteredCondensePolicy => ({
|
|
34
|
-
policy,
|
|
35
|
-
iteration: clocks.iteration ?? 1,
|
|
36
|
-
turn: clocks.turn ?? 1,
|
|
37
|
-
activation: clocks.activation ?? 1,
|
|
38
|
-
phaseEpoch: clocks.phaseEpoch ?? 1,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/** A CondenseContext for the pure transform. Defaults fire nothing; override per test. */
|
|
42
|
-
const ctx = (
|
|
43
|
-
o: {
|
|
44
|
-
modelCall?: number;
|
|
45
|
-
turn?: number;
|
|
46
|
-
activationEnded?: (a: number) => boolean;
|
|
47
|
-
phaseEnded?: (e: number) => boolean;
|
|
48
|
-
batchCalls?: number;
|
|
49
|
-
} = {},
|
|
50
|
-
): CondenseContext => ({
|
|
51
|
-
modelCall: o.modelCall ?? 0,
|
|
52
|
-
turn: o.turn ?? 0,
|
|
53
|
-
activationEnded: o.activationEnded ?? (() => false),
|
|
54
|
-
phaseEnded: o.phaseEnded ?? (() => false),
|
|
55
|
-
batchCalls: o.batchCalls,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const sink = () => {
|
|
59
|
-
const events: CondensedEventDetail[] = [];
|
|
60
|
-
return { events, on: (d: CondensedEventDetail) => events.push(d) };
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// supersession
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
suite('superseded response: collapses the earlier call, keeps the latest full', () => {
|
|
68
|
-
const history: ChatMessage[] = [
|
|
69
|
-
asstCall('r1', 'vfs_read', { path: 'A' }),
|
|
70
|
-
toolMsg('r1', big()),
|
|
71
|
-
asstCall('r2', 'vfs_read', { path: 'A' }),
|
|
72
|
-
toolMsg('r2', big()),
|
|
73
|
-
];
|
|
74
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
75
|
-
['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
76
|
-
['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
77
|
-
]);
|
|
78
|
-
const { events, on } = sink();
|
|
79
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
80
|
-
|
|
81
|
-
assert.match(out[1].toolResult!.content, /re-call to restore/);
|
|
82
|
-
assert.is(out[3].toolResult!.content, big(), 'latest call for the key stays full');
|
|
83
|
-
// Exactly one transition reported, for the earlier call's response.
|
|
84
|
-
assert.is(events.length, 1);
|
|
85
|
-
assert.is(events[0].toolCallId, 'r1');
|
|
86
|
-
assert.is(events[0].target, 'response');
|
|
87
|
-
assert.is(events[0].trigger, 'superseded:A');
|
|
88
|
-
// Input is never mutated; unchanged messages keep their identity.
|
|
89
|
-
assert.is(history[1].toolResult!.content, big(), 'stored history untouched');
|
|
90
|
-
assert.is(out[3], history[3], 'unchanged message reuses the same object');
|
|
91
|
-
assert.ok(out[1] !== history[1], 'collapsed message is a new object');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
suite('superseded crosses tools: a read collapses the earlier write args of the same key', () => {
|
|
95
|
-
const history: ChatMessage[] = [
|
|
96
|
-
asstCall('w1', 'vfs_write', { path: 'A', content: big() }),
|
|
97
|
-
toolMsg('w1', 'written'),
|
|
98
|
-
asstCall('r1', 'vfs_read', { path: 'A' }),
|
|
99
|
-
toolMsg('r1', big()),
|
|
100
|
-
];
|
|
101
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
102
|
-
['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
|
|
103
|
-
['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
104
|
-
]);
|
|
105
|
-
const { events, on } = sink();
|
|
106
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
107
|
-
|
|
108
|
-
// Earlier write is superseded by the later read of the same key → its args collapse.
|
|
109
|
-
assert.match(String(out[0].toolCalls![0].args.condensed), /re-call to restore/);
|
|
110
|
-
// The read is the latest for key A → its response stays full.
|
|
111
|
-
assert.is(out[3].toolResult!.content, big());
|
|
112
|
-
assert.is(events.length, 1);
|
|
113
|
-
assert.is(events[0].toolCallId, 'w1');
|
|
114
|
-
assert.is(events[0].target, 'args');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// age
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
suite('age: the model sees the result on `turns` turns, then it collapses', () => {
|
|
122
|
-
// Call made on iteration 1 → the result is first visible to the model on
|
|
123
|
-
// iteration 2. With turns:1 it may be read on that one turn, then collapses.
|
|
124
|
-
const history: ChatMessage[] = [asstCall('g1', 'grep_source', {}), toolMsg('g1', big())];
|
|
125
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
126
|
-
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
127
|
-
]);
|
|
128
|
-
const { events, on } = sink();
|
|
129
|
-
|
|
130
|
-
// Iteration 2 — its one allowed look: still full, nothing reported.
|
|
131
|
-
const seen = applyCondensation(history, policies, ctx({ modelCall: 2 }), on);
|
|
132
|
-
assert.is(seen[1].toolResult!.content, big(), 'full on the one turn it may be seen');
|
|
133
|
-
assert.is(events.length, 0, 'not collapsed while the model may still see it');
|
|
134
|
-
|
|
135
|
-
// Iteration 3 — one turn past the allowed view: collapses.
|
|
136
|
-
const collapsed = applyCondensation(history, policies, ctx({ modelCall: 3 }), on);
|
|
137
|
-
assert.match(collapsed[1].toolResult!.content, /aged out/);
|
|
138
|
-
assert.match(collapsed[1].toolResult!.content, /re-call to restore/);
|
|
139
|
-
assert.is(events.length, 1);
|
|
140
|
-
assert.is(events[0].trigger, 'age:1');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
// min-size floor
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
suite('payloads below the size floor are never condensed', () => {
|
|
148
|
-
const history: ChatMessage[] = [
|
|
149
|
-
asstCall('r1', 'vfs_read', { path: 'A' }),
|
|
150
|
-
toolMsg('r1', 'tiny'),
|
|
151
|
-
asstCall('r2', 'vfs_read', { path: 'A' }),
|
|
152
|
-
toolMsg('r2', big()),
|
|
153
|
-
];
|
|
154
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
155
|
-
['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
156
|
-
['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
157
|
-
]);
|
|
158
|
-
const { events, on } = sink();
|
|
159
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
160
|
-
|
|
161
|
-
assert.is(out[1].toolResult!.content, 'tiny', 'superseded but too small to bother');
|
|
162
|
-
assert.is(events.length, 0);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// result modes
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
|
|
169
|
-
suite('drop / pointer / replaceWith produce distinct collapsed forms', () => {
|
|
170
|
-
const mk = (id: string, result: CondensePolicy['response']): [ChatMessage, ChatMessage] => [
|
|
171
|
-
asstCall(id, 'vfs_read', { path: id }),
|
|
172
|
-
toolMsg(id, big()),
|
|
173
|
-
];
|
|
174
|
-
// Each call is superseded by a later same-key call so it actually fires.
|
|
175
|
-
const history: ChatMessage[] = [
|
|
176
|
-
...mk('drop', 'drop'),
|
|
177
|
-
asstCall('drop2', 'vfs_read', { path: 'drop' }),
|
|
178
|
-
toolMsg('drop2', big()),
|
|
179
|
-
...mk('ptr', 'pointer'),
|
|
180
|
-
asstCall('ptr2', 'vfs_read', { path: 'ptr' }),
|
|
181
|
-
toolMsg('ptr2', big()),
|
|
182
|
-
...mk('rep', { replaceWith: 'SUMMARY' }),
|
|
183
|
-
asstCall('rep2', 'vfs_read', { path: 'rep' }),
|
|
184
|
-
toolMsg('rep2', big()),
|
|
185
|
-
];
|
|
186
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
187
|
-
['drop', reg({ on: { kind: 'superseded', by: 'drop' }, response: 'drop' })],
|
|
188
|
-
['drop2', reg({ on: { kind: 'superseded', by: 'drop' }, response: 'drop' })],
|
|
189
|
-
['ptr', reg({ on: { kind: 'superseded', by: 'ptr' }, response: 'pointer' })],
|
|
190
|
-
['ptr2', reg({ on: { kind: 'superseded', by: 'ptr' }, response: 'pointer' })],
|
|
191
|
-
['rep', reg({ on: { kind: 'superseded', by: 'rep' }, response: { replaceWith: 'SUMMARY' } })],
|
|
192
|
-
['rep2', reg({ on: { kind: 'superseded', by: 'rep' }, response: { replaceWith: 'SUMMARY' } })],
|
|
193
|
-
]);
|
|
194
|
-
const { on } = sink();
|
|
195
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
196
|
-
|
|
197
|
-
// `drop` differs from `pointer` ONLY by the restore promise — it still identifies the call,
|
|
198
|
-
// because a stub too bare to say what the call did is how GENC-1494 started.
|
|
199
|
-
assert.match(out[1].toolResult!.content, /vfs_read/, 'drop names the tool');
|
|
200
|
-
assert.match(out[1].toolResult!.content, /result elided/, 'drop says what was elided');
|
|
201
|
-
assert.not.match(
|
|
202
|
-
out[1].toolResult!.content,
|
|
203
|
-
/re-call to restore/,
|
|
204
|
-
'drop makes no restore promise',
|
|
205
|
-
);
|
|
206
|
-
assert.match(
|
|
207
|
-
out[5].toolResult!.content,
|
|
208
|
-
/re-call to restore/,
|
|
209
|
-
'pointer carries the restore hint',
|
|
210
|
-
);
|
|
211
|
-
assert.is(out[9].toolResult!.content, 'SUMMARY', 'replaceWith is verbatim — no hint appended');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// GENC-1494: dropped args must NEVER collapse to `{}`. A bare empty object is
|
|
215
|
-
// indistinguishable from a call the model made with no arguments at all, and the model
|
|
216
|
-
// treats its own history as fact — a ui-builder run read its elided `vfs_write` calls as
|
|
217
|
-
// proof it was writing files with no content and re-wrote them for two hours. A bare marker
|
|
218
|
-
// is not enough either: `path` goes with the args, so the stub must still say WHICH file.
|
|
219
|
-
suite('args drop identifies the call; pointer stashes a stub under one key', () => {
|
|
220
|
-
const history: ChatMessage[] = [
|
|
221
|
-
asstCall('w1', 'vfs_write', { path: 'A', content: big() }),
|
|
222
|
-
toolMsg('w1', 'ok'),
|
|
223
|
-
asstCall('w2', 'vfs_write', { path: 'A', content: big() }),
|
|
224
|
-
toolMsg('w2', 'ok'),
|
|
225
|
-
];
|
|
226
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
227
|
-
['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'drop' })],
|
|
228
|
-
['w2', reg({ on: { kind: 'superseded', by: 'A' }, args: 'drop' })],
|
|
229
|
-
]);
|
|
230
|
-
const { on } = sink();
|
|
231
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
232
|
-
const dropped = out[0].toolCalls![0].args;
|
|
233
|
-
const stub = String(dropped[CONDENSED_ARGS_KEY]);
|
|
234
|
-
|
|
235
|
-
assert.ok(
|
|
236
|
-
Object.keys(dropped).length > 0,
|
|
237
|
-
'dropped args are never empty — that reads as a no-argument call',
|
|
238
|
-
);
|
|
239
|
-
assert.match(stub, /vfs_write/, 'stub names the tool');
|
|
240
|
-
assert.match(stub, /\bA\b/, 'stub names the superseded key — which file this call wrote');
|
|
241
|
-
assert.not.match(stub, /re-call to restore/, 'drop must not promise a restore it cannot honour');
|
|
242
|
-
assert.equal(out[2].toolCalls![0].args, { path: 'A', content: big() }, 'latest write kept');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// GENC-1494: trigger resolution picks the first LISTED trigger that is currently true, so it
|
|
246
|
-
// used to change after the collapse — with the usual `on: [{superseded}, {agentEnd}]`, a payload
|
|
247
|
-
// collapsed at `agentEnd` re-resolved to `superseded` the moment a newer same-key call landed,
|
|
248
|
-
// rewriting the stub and breaking the prompt cache a second time for an already-collapsed
|
|
249
|
-
// payload. In one reported session that was 22/22 args collapses and 97 response collapses.
|
|
250
|
-
suite('the fired trigger is latched, so a collapsed stub is never rewritten', () => {
|
|
251
|
-
const history: ChatMessage[] = [asstCall('r1', 'vfs_read', { path: 'A' }), toolMsg('r1', big())];
|
|
252
|
-
const entry = reg({
|
|
253
|
-
on: [{ kind: 'superseded', by: 'A' }, { kind: 'agentEnd' }],
|
|
254
|
-
response: 'pointer', // pointer embeds the reason, so a re-resolve is visible in the text
|
|
255
|
-
});
|
|
256
|
-
const policies = new Map<string, RegisteredCondensePolicy>([['r1', entry]]);
|
|
257
|
-
const { events, on } = sink();
|
|
258
|
-
|
|
259
|
-
// Pass 1: the activation has ended but nothing supersedes r1 yet → collapses as agentEnd.
|
|
260
|
-
const first = applyCondensation(history, policies, ctx({ activationEnded: () => true }), on);
|
|
261
|
-
assert.match(first[1].toolResult!.content, /agent finished/, 'collapses under agentEnd');
|
|
262
|
-
assert.is(events.length, 1);
|
|
263
|
-
assert.is(events[0].trigger, 'agentEnd');
|
|
264
|
-
|
|
265
|
-
// Pass 2: a newer read of the SAME key now exists, which would re-resolve the trigger to
|
|
266
|
-
// `superseded` (it is listed first). The already-collapsed stub must not change.
|
|
267
|
-
const grown: ChatMessage[] = [
|
|
268
|
-
...history,
|
|
269
|
-
asstCall('r2', 'vfs_read', { path: 'A' }),
|
|
270
|
-
toolMsg('r2', big()),
|
|
271
|
-
];
|
|
272
|
-
policies.set('r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' }));
|
|
273
|
-
const second = applyCondensation(grown, policies, ctx({ activationEnded: () => true }), on);
|
|
274
|
-
|
|
275
|
-
assert.match(second[1].toolResult!.content, /agent finished/, 'reason stays pinned to agentEnd');
|
|
276
|
-
assert.not.match(second[1].toolResult!.content, /superseded/, 'must not re-resolve');
|
|
277
|
-
assert.is(
|
|
278
|
-
second[1].toolResult!.content,
|
|
279
|
-
first[1].toolResult!.content,
|
|
280
|
-
'stub text is byte-stable',
|
|
281
|
-
);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
// envelope + metadata invariants
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
|
|
288
|
-
suite('collapsing args preserves the tool-call envelope and providerMetadata', () => {
|
|
289
|
-
const history: ChatMessage[] = [
|
|
290
|
-
asstCall(
|
|
291
|
-
'w1',
|
|
292
|
-
'vfs_write',
|
|
293
|
-
{ path: 'A', content: big() },
|
|
294
|
-
{ providerMetadata: { sig: 'keep' } },
|
|
295
|
-
),
|
|
296
|
-
toolMsg('w1', 'ok'),
|
|
297
|
-
asstCall('w2', 'vfs_write', { path: 'A', content: big() }),
|
|
298
|
-
toolMsg('w2', 'ok'),
|
|
299
|
-
];
|
|
300
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
301
|
-
['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
|
|
302
|
-
['w2', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
|
|
303
|
-
]);
|
|
304
|
-
const { on } = sink();
|
|
305
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
306
|
-
const tc = out[0].toolCalls![0];
|
|
307
|
-
assert.is(tc.id, 'w1', 'id preserved');
|
|
308
|
-
assert.is(tc.name, 'vfs_write', 'name preserved');
|
|
309
|
-
assert.equal(tc.providerMetadata, { sig: 'keep' }, 'reasoning signature round-trips');
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// ---------------------------------------------------------------------------
|
|
313
|
-
// report-once + no-op
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
|
|
316
|
-
suite('the event fires once per payload across repeated applies', () => {
|
|
317
|
-
const history: ChatMessage[] = [
|
|
318
|
-
asstCall('r1', 'vfs_read', { path: 'A' }),
|
|
319
|
-
toolMsg('r1', big()),
|
|
320
|
-
asstCall('r2', 'vfs_read', { path: 'A' }),
|
|
321
|
-
toolMsg('r2', big()),
|
|
322
|
-
];
|
|
323
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
324
|
-
['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
325
|
-
['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
|
|
326
|
-
]);
|
|
327
|
-
const { events, on } = sink();
|
|
328
|
-
applyCondensation(history, policies, ctx(), on);
|
|
329
|
-
applyCondensation(history, policies, ctx(), on);
|
|
330
|
-
applyCondensation(history, policies, ctx(), on);
|
|
331
|
-
assert.is(events.length, 1, 'collapse re-runs every call but the transition is reported once');
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
suite('an empty registry is a pure pass-through (same array reference)', () => {
|
|
335
|
-
const history: ChatMessage[] = [asstCall('r1', 'vfs_read'), toolMsg('r1', big())];
|
|
336
|
-
const { events, on } = sink();
|
|
337
|
-
const out = applyCondensation(history, new Map(), ctx(), on);
|
|
338
|
-
assert.is(out, history);
|
|
339
|
-
assert.is(events.length, 0);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// ---------------------------------------------------------------------------
|
|
343
|
-
// turnEnd
|
|
344
|
-
// ---------------------------------------------------------------------------
|
|
345
|
-
|
|
346
|
-
suite('turnEnd: full during its own turn, collapsed on a later turn', () => {
|
|
347
|
-
const history: ChatMessage[] = [asstCall('t1', 'read'), toolMsg('t1', big())];
|
|
348
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
349
|
-
['t1', reg({ on: { kind: 'turnEnd' }, response: 'pointer' }, { turn: 1 })],
|
|
350
|
-
]);
|
|
351
|
-
const { events, on } = sink();
|
|
352
|
-
|
|
353
|
-
const sameTurn = applyCondensation(history, policies, ctx({ turn: 1 }), on);
|
|
354
|
-
assert.is(sameTurn[1].toolResult!.content, big(), 'full for the rest of the request');
|
|
355
|
-
assert.is(events.length, 0);
|
|
356
|
-
|
|
357
|
-
const laterTurn = applyCondensation(history, policies, ctx({ turn: 2 }), on);
|
|
358
|
-
assert.match(laterTurn[1].toolResult!.content, /turn ended/);
|
|
359
|
-
assert.is(events.length, 1);
|
|
360
|
-
assert.is(events[0].trigger, 'turnEnd');
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// ---------------------------------------------------------------------------
|
|
364
|
-
// agentEnd
|
|
365
|
-
// ---------------------------------------------------------------------------
|
|
366
|
-
|
|
367
|
-
suite('agentEnd: kept while the agent is active, collapsed once its activation ends', () => {
|
|
368
|
-
const history: ChatMessage[] = [asstCall('a1', 'load'), toolMsg('a1', big())];
|
|
369
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
370
|
-
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { activation: 1 })],
|
|
371
|
-
]);
|
|
372
|
-
const { events, on } = sink();
|
|
373
|
-
|
|
374
|
-
const active = applyCondensation(history, policies, ctx({ activationEnded: () => false }), on);
|
|
375
|
-
assert.is(active[1].toolResult!.content, big(), 'full while the agent flow is active');
|
|
376
|
-
assert.is(events.length, 0);
|
|
377
|
-
|
|
378
|
-
const ended = applyCondensation(history, policies, ctx({ activationEnded: (a) => a === 1 }), on);
|
|
379
|
-
assert.match(ended[1].toolResult!.content, /agent finished/);
|
|
380
|
-
assert.is(events.length, 1);
|
|
381
|
-
assert.is(events[0].trigger, 'agentEnd');
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// ---------------------------------------------------------------------------
|
|
385
|
-
// phaseEnd
|
|
386
|
-
// ---------------------------------------------------------------------------
|
|
387
|
-
|
|
388
|
-
suite('phaseEnd: kept within the phase, collapsed once the app advances the epoch', () => {
|
|
389
|
-
// Registered in phase epoch 1.
|
|
390
|
-
const history: ChatMessage[] = [asstCall('p1', 'load_entity'), toolMsg('p1', big())];
|
|
391
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
392
|
-
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { phaseEpoch: 1 })],
|
|
393
|
-
]);
|
|
394
|
-
const { events, on } = sink();
|
|
395
|
-
|
|
396
|
-
// Still in phase 1 (no endPhase yet) — full.
|
|
397
|
-
const sameP = applyCondensation(history, policies, ctx({ phaseEnded: (e) => e < 1 }), on);
|
|
398
|
-
assert.is(sameP[1].toolResult!.content, big(), 'full while the phase is current');
|
|
399
|
-
assert.is(events.length, 0);
|
|
400
|
-
|
|
401
|
-
// endPhase() advanced the current epoch to 2 → the phase-1 payload collapses.
|
|
402
|
-
const ended = applyCondensation(history, policies, ctx({ phaseEnded: (e) => e < 2 }), on);
|
|
403
|
-
assert.match(ended[1].toolResult!.content, /phase ended/);
|
|
404
|
-
assert.match(ended[1].toolResult!.content, /re-call to restore/);
|
|
405
|
-
assert.is(events.length, 1);
|
|
406
|
-
assert.is(events[0].trigger, 'phaseEnd');
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
suite('phaseEnd: falls back to agentEnd when no phase boundary is ever declared', () => {
|
|
410
|
-
// Phase epoch never advances (phaseEnded always false), but the activation ends.
|
|
411
|
-
const history: ChatMessage[] = [asstCall('p1', 'load_entity'), toolMsg('p1', big())];
|
|
412
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
413
|
-
[
|
|
414
|
-
'p1',
|
|
415
|
-
reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { activation: 1, phaseEpoch: 1 }),
|
|
416
|
-
],
|
|
417
|
-
]);
|
|
418
|
-
const { events, on } = sink();
|
|
419
|
-
|
|
420
|
-
const active = applyCondensation(
|
|
421
|
-
history,
|
|
422
|
-
policies,
|
|
423
|
-
ctx({ phaseEnded: () => false, activationEnded: () => false }),
|
|
424
|
-
on,
|
|
425
|
-
);
|
|
426
|
-
assert.is(active[1].toolResult!.content, big(), 'full while both phase and agent are live');
|
|
427
|
-
assert.is(events.length, 0);
|
|
428
|
-
|
|
429
|
-
const disposed = applyCondensation(
|
|
430
|
-
history,
|
|
431
|
-
policies,
|
|
432
|
-
ctx({ phaseEnded: () => false, activationEnded: (a) => a === 1 }),
|
|
433
|
-
on,
|
|
434
|
-
);
|
|
435
|
-
assert.match(disposed[1].toolResult!.content, /phase ended/, 'agentEnd backstop collapses it');
|
|
436
|
-
assert.is(events.length, 1);
|
|
437
|
-
assert.is(events[0].trigger, 'phaseEnd');
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
// ---------------------------------------------------------------------------
|
|
441
|
-
// multi-trigger (OR / first-wins)
|
|
442
|
-
// ---------------------------------------------------------------------------
|
|
443
|
-
|
|
444
|
-
suite('multi-trigger: collapses when ANY listed trigger fires', () => {
|
|
445
|
-
// superseded OR turnEnd — only one call for key A (never superseded), but the turn ends.
|
|
446
|
-
const history: ChatMessage[] = [asstCall('m1', 'read', { path: 'A' }), toolMsg('m1', big())];
|
|
447
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
448
|
-
[
|
|
449
|
-
'm1',
|
|
450
|
-
reg(
|
|
451
|
-
{ on: [{ kind: 'superseded', by: 'A' }, { kind: 'turnEnd' }], response: 'pointer' },
|
|
452
|
-
{ turn: 1 },
|
|
453
|
-
),
|
|
454
|
-
],
|
|
455
|
-
]);
|
|
456
|
-
const { events, on } = sink();
|
|
457
|
-
|
|
458
|
-
assert.is(
|
|
459
|
-
applyCondensation(history, policies, ctx({ turn: 1 }), on)[1].toolResult!.content,
|
|
460
|
-
big(),
|
|
461
|
-
'neither trigger has fired',
|
|
462
|
-
);
|
|
463
|
-
const later = applyCondensation(history, policies, ctx({ turn: 2 }), on);
|
|
464
|
-
assert.match(later[1].toolResult!.content, /turn ended/, 'turnEnd fired via the OR');
|
|
465
|
-
assert.is(events[0].trigger, 'turnEnd');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
suite('multi-trigger first-wins: the earliest-listed firing trigger is the reason', () => {
|
|
469
|
-
// m1 is superseded by m2 AND its turn has ended; m2 is the latest (not superseded)
|
|
470
|
-
// but its turn has ended too. `superseded` is listed first.
|
|
471
|
-
const history: ChatMessage[] = [
|
|
472
|
-
asstCall('m1', 'read', { path: 'A' }),
|
|
473
|
-
toolMsg('m1', big()),
|
|
474
|
-
asstCall('m2', 'read', { path: 'A' }),
|
|
475
|
-
toolMsg('m2', big()),
|
|
476
|
-
];
|
|
477
|
-
const on0: CondensePolicy = {
|
|
478
|
-
on: [{ kind: 'superseded', by: 'A' }, { kind: 'turnEnd' }],
|
|
479
|
-
response: 'pointer',
|
|
480
|
-
};
|
|
481
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
482
|
-
['m1', reg(on0, { turn: 1 })],
|
|
483
|
-
['m2', reg(on0, { turn: 1 })],
|
|
484
|
-
]);
|
|
485
|
-
const { events, on } = sink();
|
|
486
|
-
applyCondensation(history, policies, ctx({ turn: 2 }), on);
|
|
487
|
-
|
|
488
|
-
assert.is(
|
|
489
|
-
events.find((e) => e.toolCallId === 'm1')?.trigger,
|
|
490
|
-
'superseded:A',
|
|
491
|
-
'first-listed firing trigger wins as the reason',
|
|
492
|
-
);
|
|
493
|
-
assert.is(
|
|
494
|
-
events.find((e) => e.toolCallId === 'm2')?.trigger,
|
|
495
|
-
'turnEnd',
|
|
496
|
-
'the supersession survivor still collapses via the next trigger',
|
|
497
|
-
);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
// ── batched collapse (GENC-1476) ───────────────────────────────────────────
|
|
501
|
-
// Condensation rewrites history in place, so collapsing the instant a trigger fires
|
|
502
|
-
// breaks the prompt cache on nearly every call of a re-read loop. Batching holds the
|
|
503
|
-
// collapse set still between boundaries.
|
|
504
|
-
|
|
505
|
-
const batch = createLogicSuite('applyCondensation batching');
|
|
506
|
-
|
|
507
|
-
/** A re-read loop: `n` reads of the same path, each registered one model-call apart. */
|
|
508
|
-
const reReadLoop = (n: number) => {
|
|
509
|
-
const history: ChatMessage[] = [];
|
|
510
|
-
const policies = new Map<string, RegisteredCondensePolicy>();
|
|
511
|
-
for (let i = 1; i <= n; i += 1) {
|
|
512
|
-
history.push(asstCall(`r${i}`, 'vfs_read', { path: 'A.tsx' }), toolMsg(`r${i}`, big()));
|
|
513
|
-
policies.set(
|
|
514
|
-
`r${i}`,
|
|
515
|
-
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: i }),
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
return { history, policies };
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
/** Result content of every tool message, in order. */
|
|
522
|
-
const results = (out: ChatMessage[]): string[] =>
|
|
523
|
-
out.filter((m) => m.role === 'tool').map((m) => m.toolResult?.content ?? '');
|
|
524
|
-
|
|
525
|
-
batch('without batching every superseded read collapses immediately', () => {
|
|
526
|
-
const { history, policies } = reReadLoop(4);
|
|
527
|
-
const out = results(applyCondensation(history, policies, ctx({ modelCall: 4 }), sink().on));
|
|
528
|
-
// Only the newest survives; the other three are already stubs.
|
|
529
|
-
assert.is(out.filter((c) => c.startsWith('[')).length, 3);
|
|
530
|
-
assert.is(out[3].length, big().length);
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
batch('batching leaves the prefix untouched between boundaries', () => {
|
|
534
|
-
const { history, policies } = reReadLoop(4);
|
|
535
|
-
// Boundary is floor(4/10)*10 = 0, so no call is within the batch yet.
|
|
536
|
-
const out = results(
|
|
537
|
-
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
538
|
-
);
|
|
539
|
-
assert.equal(
|
|
540
|
-
out.map((c) => c.length),
|
|
541
|
-
[big().length, big().length, big().length, big().length],
|
|
542
|
-
);
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
batch('the batch boundary collapses the backlog in one step', () => {
|
|
546
|
-
const { history, policies } = reReadLoop(12);
|
|
547
|
-
const before = results(
|
|
548
|
-
applyCondensation(history, policies, ctx({ modelCall: 9, batchCalls: 10 }), sink().on),
|
|
549
|
-
);
|
|
550
|
-
const after = results(
|
|
551
|
-
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
552
|
-
);
|
|
553
|
-
assert.is(before.filter((c) => c.startsWith('[')).length, 0);
|
|
554
|
-
// At the boundary, reads 1-9 collapse together; 10-12 are past it and stay full.
|
|
555
|
-
assert.is(after.filter((c) => c.startsWith('[')).length, 9);
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
batch('a call past the boundary neither collapses nor supersedes an older one', () => {
|
|
559
|
-
// The regression this guards: excluding the new call as a SUPERSEDER but still letting
|
|
560
|
-
// it collapse would elide the very result the agent just fetched.
|
|
561
|
-
const { history, policies } = reReadLoop(2);
|
|
562
|
-
policies.set(
|
|
563
|
-
'r2',
|
|
564
|
-
reg({ on: { kind: 'superseded', by: 'A.tsx' }, response: 'pointer' }, { iteration: 7 }),
|
|
565
|
-
);
|
|
566
|
-
const out = results(
|
|
567
|
-
applyCondensation(history, policies, ctx({ modelCall: 7, batchCalls: 5 }), sink().on),
|
|
568
|
-
);
|
|
569
|
-
assert.is(out[1].length, big().length, 'the newest read must stay full');
|
|
570
|
-
assert.is(out[0].length, big().length, 'the older read must not collapse early either');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
batch('age is measured against the boundary, not the live clock', () => {
|
|
574
|
-
const history = [asstCall('g1', 'grep', {}), toolMsg('g1', big())];
|
|
575
|
-
const policies = new Map([
|
|
576
|
-
['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
|
|
577
|
-
]);
|
|
578
|
-
// Live clock says age 4 > 1 (would collapse); the boundary says 0, so it holds.
|
|
579
|
-
const held = results(
|
|
580
|
-
applyCondensation(history, policies, ctx({ modelCall: 5, batchCalls: 10 }), sink().on),
|
|
581
|
-
);
|
|
582
|
-
assert.is(held[0].length, big().length);
|
|
583
|
-
const collapsed = results(
|
|
584
|
-
applyCondensation(history, policies, ctx({ modelCall: 10, batchCalls: 10 }), sink().on),
|
|
585
|
-
);
|
|
586
|
-
assert.ok(collapsed[0].startsWith('['));
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
batch('batchCalls of 1, 0 or absent all keep the historical behaviour', () => {
|
|
590
|
-
for (const batchCalls of [1, 0, undefined]) {
|
|
591
|
-
const { history, policies } = reReadLoop(3);
|
|
592
|
-
const out = results(
|
|
593
|
-
applyCondensation(history, policies, ctx({ modelCall: 3, batchCalls }), sink().on),
|
|
594
|
-
);
|
|
595
|
-
assert.is(out.filter((c) => c.startsWith('[')).length, 2, `batchCalls=${batchCalls}`);
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
batch('agentEnd is exempt from batching — it collapses as it fires', () => {
|
|
600
|
-
// An agent swap changes the system prompt and tool defs, which invalidates the cached
|
|
601
|
-
// prefix anyway, so deferring the collapse would hold a stale payload for nothing.
|
|
602
|
-
const history = [asstCall('a1', 'read_context', {}), toolMsg('a1', big())];
|
|
603
|
-
const policies = new Map([
|
|
604
|
-
['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { iteration: 7, activation: 1 })],
|
|
605
|
-
]);
|
|
606
|
-
const out = results(
|
|
607
|
-
applyCondensation(
|
|
608
|
-
history,
|
|
609
|
-
policies,
|
|
610
|
-
ctx({ modelCall: 7, batchCalls: 10, activationEnded: () => true }),
|
|
611
|
-
sink().on,
|
|
612
|
-
),
|
|
613
|
-
);
|
|
614
|
-
// Boundary is 0 and the call sits at 7, so a batched trigger would have been deferred.
|
|
615
|
-
assert.ok(out[0].startsWith('['));
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
batch('phaseEnd is exempt too', () => {
|
|
619
|
-
const history = [asstCall('p1', 'read_docs', {}), toolMsg('p1', big())];
|
|
620
|
-
const policies = new Map([
|
|
621
|
-
['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { iteration: 7, phaseEpoch: 1 })],
|
|
622
|
-
]);
|
|
623
|
-
const out = results(
|
|
624
|
-
applyCondensation(
|
|
625
|
-
history,
|
|
626
|
-
policies,
|
|
627
|
-
ctx({ modelCall: 7, batchCalls: 10, phaseEnded: () => true }),
|
|
628
|
-
sink().on,
|
|
629
|
-
),
|
|
630
|
-
);
|
|
631
|
-
assert.ok(out[0].startsWith('['));
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
batch('a mixed policy batches superseded but still honours agentEnd immediately', () => {
|
|
635
|
-
// The vfs shape: `on: [{ superseded }, { agentEnd }]`. Mid-run the supersession is
|
|
636
|
-
// deferred; once the agent ends the payload collapses regardless of the boundary.
|
|
637
|
-
const { history, policies } = reReadLoop(2);
|
|
638
|
-
for (const id of ['r1', 'r2'])
|
|
639
|
-
policies.set(
|
|
640
|
-
id,
|
|
641
|
-
reg(
|
|
642
|
-
{ on: [{ kind: 'superseded', by: 'A.tsx' }, { kind: 'agentEnd' }], response: 'pointer' },
|
|
643
|
-
{ iteration: id === 'r1' ? 1 : 2, activation: 1 },
|
|
644
|
-
),
|
|
645
|
-
);
|
|
646
|
-
const midRun = results(
|
|
647
|
-
applyCondensation(history, policies, ctx({ modelCall: 4, batchCalls: 10 }), sink().on),
|
|
648
|
-
);
|
|
649
|
-
assert.is(midRun.filter((c) => c.startsWith('[')).length, 0, 'supersession is deferred');
|
|
650
|
-
|
|
651
|
-
const afterSwap = results(
|
|
652
|
-
applyCondensation(
|
|
653
|
-
history,
|
|
654
|
-
policies,
|
|
655
|
-
ctx({ modelCall: 4, batchCalls: 10, activationEnded: () => true }),
|
|
656
|
-
sink().on,
|
|
657
|
-
),
|
|
658
|
-
);
|
|
659
|
-
assert.is(afterSwap.filter((c) => c.startsWith('[')).length, 2, 'agentEnd collapses both');
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
batch.run();
|
|
663
|
-
|
|
664
|
-
// ---------------------------------------------------------------------------
|
|
665
|
-
// image-bearing tool results (GENC-1508)
|
|
666
|
-
//
|
|
667
|
-
// A render tool returns a few words of text plus ~15KB of base64. Sizing the payload
|
|
668
|
-
// on `content` alone leaves it permanently under the floor, so the image rides every
|
|
669
|
-
// later turn — the exact bytes condensation exists to shed. And a stub that leaves the
|
|
670
|
-
// attachments in place claims a collapse that did not happen.
|
|
671
|
-
// ---------------------------------------------------------------------------
|
|
672
|
-
|
|
673
|
-
/** A tool result shaped like a mockup render: short text, one large image. */
|
|
674
|
-
const imageToolMsg = (toolCallId: string, dataLen: number): ChatMessage => ({
|
|
675
|
-
role: 'tool',
|
|
676
|
-
content: 'rendered',
|
|
677
|
-
toolResult: {
|
|
678
|
-
toolCallId,
|
|
679
|
-
content: 'rendered',
|
|
680
|
-
attachments: [
|
|
681
|
-
{ kind: 'image', name: 'mockup.png', mimeType: 'image/png', data: 'a'.repeat(dataLen) },
|
|
682
|
-
],
|
|
683
|
-
},
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
suite('image bytes count toward the condense floor, so a short-text render condenses', () => {
|
|
687
|
-
const history: ChatMessage[] = [
|
|
688
|
-
asstCall('m1', 'render_mockup', { html: '<p/>' }),
|
|
689
|
-
imageToolMsg('m1', CONDENSE_MIN_CHARS + 500),
|
|
690
|
-
asstCall('m2', 'render_mockup', { html: '<p/>' }),
|
|
691
|
-
imageToolMsg('m2', CONDENSE_MIN_CHARS + 500),
|
|
692
|
-
];
|
|
693
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
694
|
-
['m1', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
695
|
-
['m2', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
696
|
-
]);
|
|
697
|
-
const { on } = sink();
|
|
698
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
699
|
-
assert.ok(
|
|
700
|
-
out[1].toolResult!.content.startsWith('['),
|
|
701
|
-
'the superseded render is collapsed to a stub — sizing on content alone would skip it',
|
|
702
|
-
);
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
suite('a condensed image result drops its attachments, not just its text', () => {
|
|
706
|
-
const history: ChatMessage[] = [
|
|
707
|
-
asstCall('m1', 'render_mockup', { html: '<p/>' }),
|
|
708
|
-
imageToolMsg('m1', CONDENSE_MIN_CHARS + 500),
|
|
709
|
-
asstCall('m2', 'render_mockup', { html: '<p/>' }),
|
|
710
|
-
imageToolMsg('m2', CONDENSE_MIN_CHARS + 500),
|
|
711
|
-
];
|
|
712
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
713
|
-
['m1', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
714
|
-
['m2', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
715
|
-
]);
|
|
716
|
-
const { on } = sink();
|
|
717
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
718
|
-
assert.is(
|
|
719
|
-
out[1].toolResult!.attachments,
|
|
720
|
-
undefined,
|
|
721
|
-
'leaving the image attached would shed none of the bytes the stub claims were elided',
|
|
722
|
-
);
|
|
723
|
-
assert.equal(
|
|
724
|
-
out[3].toolResult!.attachments?.length,
|
|
725
|
-
1,
|
|
726
|
-
'the surviving latest render keeps its image',
|
|
727
|
-
);
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
suite('a short text-only result is still below the floor with no attachments', () => {
|
|
731
|
-
const history: ChatMessage[] = [
|
|
732
|
-
asstCall('s1', 'render_mockup', {}),
|
|
733
|
-
toolMsg('s1', 'rendered'),
|
|
734
|
-
asstCall('s2', 'render_mockup', {}),
|
|
735
|
-
toolMsg('s2', 'rendered'),
|
|
736
|
-
];
|
|
737
|
-
const policies = new Map<string, RegisteredCondensePolicy>([
|
|
738
|
-
['s1', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
739
|
-
['s2', reg({ on: { kind: 'superseded', by: 'mockup' }, response: 'pointer' })],
|
|
740
|
-
]);
|
|
741
|
-
const { on } = sink();
|
|
742
|
-
const out = applyCondensation(history, policies, ctx(), on);
|
|
743
|
-
assert.is(out[1].toolResult!.content, 'rendered', 'the floor still protects small payloads');
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
suite.run();
|