mixdog 0.9.52 → 0.9.53
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 +1 -1
- package/scripts/bench-run.mjs +2 -2
- package/scripts/compact-pressure-test.mjs +104 -0
- package/scripts/desktop-session-bridge-test.mjs +704 -0
- package/scripts/freevar-smoke.mjs +7 -4
- package/scripts/lifecycle-api-test.mjs +65 -4
- package/scripts/max-output-recovery-test.mjs +31 -0
- package/scripts/memory-core-input-test.mjs +10 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +63 -3
- package/scripts/openai-ws-early-settle-test.mjs +40 -0
- package/scripts/parent-abort-link-test.mjs +24 -0
- package/scripts/process-lifecycle-test.mjs +80 -22
- package/scripts/provider-contract-test.mjs +257 -0
- package/scripts/provider-toolcall-test.mjs +172 -10
- package/scripts/session-orphan-sweep-test.mjs +27 -1
- package/src/lib/keychain-cjs.cjs +36 -23
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -13
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
- package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -300
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +18 -266
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +15 -130
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +5 -115
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -71
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +47 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +72 -77
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +20 -223
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +305 -127
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +22 -298
- package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -71
- package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
- package/src/runtime/channels/backends/discord.mjs +6 -6
- package/src/runtime/channels/lib/config.mjs +15 -2
- package/src/runtime/channels/lib/memory-client.mjs +20 -3
- package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
- package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
- package/src/runtime/channels/lib/worker-main.mjs +16 -5
- package/src/runtime/memory/index.mjs +24 -198
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
- package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +0 -4
- package/src/runtime/memory/lib/query-handlers.mjs +2 -122
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
- package/src/runtime/search/lib/http-fetch.mjs +1 -1
- package/src/runtime/shared/config.mjs +58 -13
- package/src/runtime/shared/llm/cost.mjs +14 -4
- package/src/runtime/shared/memory-snapshot.mjs +236 -0
- package/src/runtime/shared/process-lifecycle.mjs +92 -19
- package/src/runtime/shared/process-shutdown.mjs +6 -0
- package/src/runtime/shared/resource-admission.mjs +7 -2
- package/src/session-runtime/channel-config-api.mjs +7 -7
- package/src/session-runtime/config-lifecycle.mjs +20 -17
- package/src/session-runtime/cwd-plugins.mjs +9 -7
- package/src/session-runtime/env.mjs +1 -2
- package/src/session-runtime/hitch-profile.mjs +45 -0
- package/src/session-runtime/lifecycle-api.mjs +36 -9
- package/src/session-runtime/mcp-glue.mjs +6 -11
- package/src/session-runtime/provider-init-key.mjs +17 -0
- package/src/session-runtime/runtime-core.mjs +44 -103
- package/src/session-runtime/runtime-paths.mjs +20 -0
- package/src/session-runtime/runtime-tool-routing.mjs +55 -0
- package/src/session-runtime/tool-catalog-data.mjs +51 -0
- package/src/session-runtime/tool-catalog.mjs +15 -89
- package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
- package/src/standalone/agent-tool/worker-rows.mjs +93 -0
- package/src/standalone/agent-tool.mjs +12 -162
- package/src/standalone/channel-admin.mjs +29 -0
- package/src/tui/App.jsx +11 -5
- package/src/tui/dist/index.mjs +202 -65
- package/src/tui/engine/session-api-ext.mjs +37 -4
- package/src/tui/index.jsx +1 -0
- package/src/tui/lib/voice-setup.mjs +5 -5
- package/scripts/_devtools-stub.mjs +0 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -288
|
@@ -14,14 +14,27 @@ function _usageNum(value) {
|
|
|
14
14
|
return Number.isFinite(n) ? n : 0;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function _combineUsageWithWarmup(actual, warmup) {
|
|
17
|
+
export function _combineUsageWithWarmup(actual, warmup, { separateMainContext = false } = {}) {
|
|
18
18
|
if (!warmup) return actual;
|
|
19
|
-
if (!actual)
|
|
19
|
+
if (!actual) {
|
|
20
|
+
// A warmup-only settle is billable, but has no corresponding main
|
|
21
|
+
// request whose context footprint can be measured.
|
|
22
|
+
const combined = {
|
|
23
|
+
...warmup,
|
|
24
|
+
warmupInputTokens: _usageNum(warmup.inputTokens),
|
|
25
|
+
warmupCachedTokens: _usageNum(warmup.cachedTokens),
|
|
26
|
+
warmupOutputTokens: _usageNum(warmup.outputTokens),
|
|
27
|
+
warmupPromptTokens: _usageNum(warmup.promptTokens),
|
|
28
|
+
warmupCacheWriteTokens: _usageNum(warmup.cacheWriteTokens),
|
|
29
|
+
};
|
|
30
|
+
if (separateMainContext) combined.mainUsageAvailable = false;
|
|
31
|
+
return combined;
|
|
32
|
+
}
|
|
20
33
|
const actualRaw = actual.raw || {};
|
|
21
34
|
const warmupRaw = warmup.raw || {};
|
|
22
35
|
const actualTicks = _usageNum(actualRaw.cost_in_usd_ticks);
|
|
23
36
|
const warmupTicks = _usageNum(warmupRaw.cost_in_usd_ticks);
|
|
24
|
-
|
|
37
|
+
const combined = {
|
|
25
38
|
...actual,
|
|
26
39
|
inputTokens: _usageNum(actual.inputTokens) + _usageNum(warmup.inputTokens),
|
|
27
40
|
outputTokens: _usageNum(actual.outputTokens) + _usageNum(warmup.outputTokens),
|
|
@@ -30,12 +43,26 @@ export function _combineUsageWithWarmup(actual, warmup) {
|
|
|
30
43
|
warmupInputTokens: _usageNum(warmup.inputTokens),
|
|
31
44
|
warmupCachedTokens: _usageNum(warmup.cachedTokens),
|
|
32
45
|
warmupOutputTokens: _usageNum(warmup.outputTokens),
|
|
46
|
+
warmupPromptTokens: _usageNum(warmup.promptTokens),
|
|
47
|
+
warmupCacheWriteTokens: _usageNum(warmup.cacheWriteTokens),
|
|
33
48
|
raw: {
|
|
34
49
|
...actualRaw,
|
|
35
50
|
warmup_usage: warmupRaw,
|
|
36
51
|
...(actualTicks || warmupTicks ? { cost_in_usd_ticks: actualTicks + warmupTicks } : {}),
|
|
37
52
|
},
|
|
38
53
|
};
|
|
54
|
+
if (separateMainContext) {
|
|
55
|
+
// OAuth startup prewarm is billable but is not part of the main
|
|
56
|
+
// request's context footprint. Keep these fields opt-in so shared xAI
|
|
57
|
+
// usage objects retain their prior shape and accounting behavior.
|
|
58
|
+
combined.mainInputTokens = _usageNum(actual.inputTokens);
|
|
59
|
+
combined.mainOutputTokens = _usageNum(actual.outputTokens);
|
|
60
|
+
combined.mainCachedTokens = _usageNum(actual.cachedTokens);
|
|
61
|
+
combined.mainPromptTokens = _usageNum(actual.promptTokens);
|
|
62
|
+
combined.mainCacheWriteTokens = _usageNum(actual.cacheWriteTokens);
|
|
63
|
+
combined.mainUsageAvailable = true;
|
|
64
|
+
}
|
|
65
|
+
return combined;
|
|
39
66
|
}
|
|
40
67
|
|
|
41
68
|
export function _parseEvent(raw) {
|
|
@@ -98,9 +98,29 @@ export const WS_PRE_RESPONSE_CREATED_MS = (() => {
|
|
|
98
98
|
// Single inter-chunk idle timer. Resets on EVERY received frame — any frame,
|
|
99
99
|
// including metadata/keepalive, proves the socket is live.
|
|
100
100
|
export const WS_INTER_CHUNK_MS = PROVIDER_WS_INTER_CHUNK_TIMEOUT_MS;
|
|
101
|
+
// Bound the second allocation performed when ws RawData is decoded to UTF-8.
|
|
102
|
+
// The ws library has already assembled the Buffer by this point, so this check
|
|
103
|
+
// must stay before data.toString() below. xAI shares this stream implementation
|
|
104
|
+
// but retains its existing unbounded behavior.
|
|
105
|
+
export const OPENAI_WS_MAX_INCOMING_FRAME_BYTES = 16 * 1024 * 1024;
|
|
101
106
|
const X_CODEX_TURN_STATE_HEADER = 'x-codex-turn-state';
|
|
102
107
|
const WS_TRACE_ENABLED = process.env.MIXDOG_WS_TRACE === '1';
|
|
103
108
|
|
|
109
|
+
function _incomingFrameByteLength(data) {
|
|
110
|
+
if (typeof data === 'string') return Buffer.byteLength(data);
|
|
111
|
+
if (Array.isArray(data)) {
|
|
112
|
+
let total = 0;
|
|
113
|
+
for (const chunk of data) {
|
|
114
|
+
const size = Number(chunk?.byteLength ?? chunk?.length);
|
|
115
|
+
if (!Number.isFinite(size) || size < 0) return null;
|
|
116
|
+
total += size;
|
|
117
|
+
}
|
|
118
|
+
return total;
|
|
119
|
+
}
|
|
120
|
+
const size = Number(data?.byteLength ?? data?.length);
|
|
121
|
+
return Number.isFinite(size) && size >= 0 ? size : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
function _writeWsLifecycleTrace(lifecycle) {
|
|
105
125
|
process.stderr.write(`[ws-trace] t=${new Date().toISOString()} lifecycle=${lifecycle}\n`);
|
|
106
126
|
}
|
|
@@ -207,13 +227,6 @@ function _traceWsHeaderKeys(entry, event, midState, traceProvider, model) {
|
|
|
207
227
|
else entry.wsHeaderKeysEmptyTraced = true;
|
|
208
228
|
} catch {}
|
|
209
229
|
}
|
|
210
|
-
// _wsErrLabel moved to openai-ws-pool.mjs (imported above).
|
|
211
|
-
// Delta/matching helpers (_sansInput, _stableStringify, _cloneJson,
|
|
212
|
-
// _logicalResponseItemMatch, _stripResponseItemsFromHead, _computeDelta,
|
|
213
|
-
// _estimateFrameTokens) → openai-ws-delta.mjs. Usage/event helpers
|
|
214
|
-
// (_combineUsageWithWarmup, _parseEvent, _incompleteReasonFromEvent,
|
|
215
|
-
// _isMaxOutputIncompleteReason, _httpStatusFromWsClose) → openai-ws-events.mjs.
|
|
216
|
-
// All imported + re-exported at the top of this file.
|
|
217
230
|
// tool_search_call.arguments parse. Module-scope (exported) for direct test
|
|
218
231
|
// coverage. Same policy as the function_call_arguments.done path and
|
|
219
232
|
// openai-oauth _parseJsonObject —
|
|
@@ -260,6 +273,9 @@ export async function _streamResponse({
|
|
|
260
273
|
}
|
|
261
274
|
const preResponseCreatedMs = _positiveInt(_timeouts?.preResponseCreatedMs, WS_PRE_RESPONSE_CREATED_MS);
|
|
262
275
|
const interChunkMs = _positiveInt(_timeouts?.interChunkMs, WS_INTER_CHUNK_MS);
|
|
276
|
+
const maxIncomingFrameBytes = traceProvider === 'xai'
|
|
277
|
+
? 0
|
|
278
|
+
: _positiveInt(_timeouts?.maxIncomingFrameBytes, OPENAI_WS_MAX_INCOMING_FRAME_BYTES);
|
|
263
279
|
// First-MEANINGFUL-frame deadline. Distinct from preResponseCreatedMs (a
|
|
264
280
|
// short pre-created byte-silence window that resetIdle clears on the FIRST
|
|
265
281
|
// frame of any kind): this timer is cleared only by a meaningful response
|
|
@@ -777,6 +793,20 @@ export async function _streamResponse({
|
|
|
777
793
|
};
|
|
778
794
|
|
|
779
795
|
messageHandler = (data) => {
|
|
796
|
+
const frameBytes = _incomingFrameByteLength(data);
|
|
797
|
+
if (maxIncomingFrameBytes > 0 && frameBytes != null && frameBytes > maxIncomingFrameBytes) {
|
|
798
|
+
const err = new Error(
|
|
799
|
+
`OpenAI WebSocket response frame is too large (${frameBytes} bytes; limit ${maxIncomingFrameBytes} bytes); request is retryable`,
|
|
800
|
+
);
|
|
801
|
+
err.code = 'EOPENAIWSFRAMETOOLARGE';
|
|
802
|
+
err.wsFrameTooLarge = true;
|
|
803
|
+
err.retryable = true;
|
|
804
|
+
terminalError = err;
|
|
805
|
+
midState.wsFrameTooLarge = true;
|
|
806
|
+
try { socket.close(1009, 'frame_too_large'); } catch {}
|
|
807
|
+
finish();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
780
810
|
if (midState.sendSpan && midState.sendStartedAt != null
|
|
781
811
|
&& midState.sendSpanAttemptFirstEvent !== true) {
|
|
782
812
|
midState.sendSpanAttemptFirstEvent = true;
|
|
@@ -1383,24 +1413,3 @@ export async function _streamResponse({
|
|
|
1383
1413
|
});
|
|
1384
1414
|
}
|
|
1385
1415
|
|
|
1386
|
-
/**
|
|
1387
|
-
* Classify a handshake error for retry eligibility.
|
|
1388
|
-
*
|
|
1389
|
-
* Default-deny: anything we don't recognize as transient returns null (treat
|
|
1390
|
-
* as permanent). Permanent buckets (401/403/404/429) also return null — the
|
|
1391
|
-
* server has made a deterministic decision that a retry can't change.
|
|
1392
|
-
*
|
|
1393
|
-
* Returns one of:
|
|
1394
|
-
* 'timeout' — `ws` handshakeTimeout fired
|
|
1395
|
-
* 'reset' — ECONNRESET / socket hang up
|
|
1396
|
-
* 'dns' — EAI_AGAIN / ENOTFOUND / EAI_NODATA
|
|
1397
|
-
* 'refused' — ECONNREFUSED
|
|
1398
|
-
* 'network' — ENETUNREACH / EHOSTUNREACH / EPIPE
|
|
1399
|
-
* 'acquire_timeout' — hard client-side open/acquire deadline fired
|
|
1400
|
-
* 'http_5xx' (with specific status e.g. 'http_503') — server overload
|
|
1401
|
-
* null — not retryable
|
|
1402
|
-
*/
|
|
1403
|
-
// Thin re-export wrapper: handshake classification now lives in the shared
|
|
1404
|
-
// retry-classifier (classifyHandshakeError). Kept here as a named export so
|
|
1405
|
-
// internal call sites (_acquireWithRetry) and any external importer keep
|
|
1406
|
-
// resolving the same symbol.
|
|
@@ -363,10 +363,7 @@ export function classifyMidstreamError(err, signals, policy = {}) {
|
|
|
363
363
|
return _classifyMidstreamWs(err, signals, attemptIndex, policy)
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
//
|
|
367
|
-
// carries sawResponseCreated / sawCompleted / emittedText / emittedToolCall /
|
|
368
|
-
// wsCloseCode / firstByteTimeout / wsSendFailed /
|
|
369
|
-
// userAbort / watchdogAbort / responseFailedPayload exactly as before.
|
|
366
|
+
// WebSocket classification consumes the provider's stream-state signals.
|
|
370
367
|
function _classifyMidstreamWs(err, state, attemptIndex, policy) {
|
|
371
368
|
if (state.sawCompleted) return null
|
|
372
369
|
// Once a tool call has been dispatched, no transport outcome is replay-safe.
|
|
@@ -374,6 +371,9 @@ function _classifyMidstreamWs(err, state, attemptIndex, policy) {
|
|
|
374
371
|
// already be executing, so retry/fallback could duplicate its side effect.
|
|
375
372
|
if (state.emittedToolCall) return null
|
|
376
373
|
if (state.emittedText || err?.liveTextEmitted) return null
|
|
374
|
+
if (err?.wsFrameTooLarge || state.wsFrameTooLarge) {
|
|
375
|
+
return _allowMidstream('ws_frame_too_large', attemptIndex, policy)
|
|
376
|
+
}
|
|
377
377
|
if (state.firstByteTimeout || err?.firstByteTimeout) {
|
|
378
378
|
return _allowMidstream('first_byte_timeout', attemptIndex, policy)
|
|
379
379
|
}
|
|
@@ -450,9 +450,7 @@ function _classifyMidstreamWs(err, state, attemptIndex, policy) {
|
|
|
450
450
|
return null
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
-
//
|
|
454
|
-
// carries sawMessageStart / sawCompleted / emittedToolCall / userAbort /
|
|
455
|
-
// watchdogAbort exactly as before.
|
|
453
|
+
// SSE classification consumes the provider's stream-state signals.
|
|
456
454
|
function _classifyMidstreamSse(err, state, attemptIndex, policy) {
|
|
457
455
|
if (attemptIndex >= policy.defaultRetries) return null
|
|
458
456
|
if (state.sawCompleted) return null
|
|
@@ -641,6 +641,14 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
641
641
|
// additive — callers that ignore these fields keep working.
|
|
642
642
|
deltaCachedRead: response.usage.cachedTokens || 0,
|
|
643
643
|
deltaCacheWrite: response.usage.cacheWriteTokens || 0,
|
|
644
|
+
// Billing deltas include OAuth WS warmup. Context
|
|
645
|
+
// snapshots/baselines must describe only the main send.
|
|
646
|
+
contextInputTokens: response.usage.mainInputTokens ?? response.usage.inputTokens ?? 0,
|
|
647
|
+
contextOutputTokens: response.usage.mainOutputTokens ?? response.usage.outputTokens ?? 0,
|
|
648
|
+
contextPromptTokens: response.usage.mainPromptTokens ?? response.usage.promptTokens ?? 0,
|
|
649
|
+
contextCachedReadTokens: response.usage.mainCachedTokens ?? response.usage.cachedTokens ?? 0,
|
|
650
|
+
contextCacheWriteTokens: response.usage.mainCacheWriteTokens ?? response.usage.cacheWriteTokens ?? 0,
|
|
651
|
+
contextUsageAvailable: response.usage.mainUsageAvailable !== false,
|
|
644
652
|
sendTools,
|
|
645
653
|
ts: Date.now(),
|
|
646
654
|
}));
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { isAgentOwner } from '../agent-owner.mjs';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_COMPACTION_BUFFER_TOKENS = 0;
|
|
4
|
+
export const DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
5
|
+
export const DEFAULT_MAIN_COMPACTION_BUFFER_RATIO = 0.05;
|
|
6
|
+
export const MAX_COMPACTION_BUFFER_RATIO = 0.25;
|
|
7
|
+
const MAX_BUFFER_INPUT_RATIO = 0.999_999;
|
|
8
|
+
export const DEFAULT_COMPACTION_KEEP_TOKENS = 8_000;
|
|
9
|
+
const LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
10
|
+
|
|
11
|
+
export function positiveTokenInt(value) {
|
|
12
|
+
const n = Number(value);
|
|
13
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
14
|
+
}
|
|
15
|
+
function envTokenInt(name) {
|
|
16
|
+
return positiveTokenInt(process.env[name]);
|
|
17
|
+
}
|
|
18
|
+
export function normalizeCompactionBufferRatio(value, fallback = DEFAULT_COMPACTION_BUFFER_RATIO) {
|
|
19
|
+
const n = Number(value);
|
|
20
|
+
if (Number.isFinite(n) && n > 0) return n > 1 ? n / 100 : n;
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
export function resolveBufferRatioCandidate(percentInputs = [], ratioInputs = []) {
|
|
24
|
+
for (const raw of percentInputs) {
|
|
25
|
+
const n = Number(raw);
|
|
26
|
+
if (Number.isFinite(n) && n > 0) return Math.min(MAX_BUFFER_INPUT_RATIO, n / 100);
|
|
27
|
+
}
|
|
28
|
+
for (const raw of ratioInputs) {
|
|
29
|
+
const n = Number(raw);
|
|
30
|
+
if (Number.isFinite(n) && n > 0) return Math.min(MAX_BUFFER_INPUT_RATIO, n > 1 ? n / 100 : n);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
export function resolveCompactBufferRatio(cfg = {}) {
|
|
35
|
+
const resolved = resolveBufferRatioCandidate(
|
|
36
|
+
[cfg.bufferPercent, cfg.bufferPct, process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT],
|
|
37
|
+
[cfg.bufferRatio, cfg.bufferFraction, process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO],
|
|
38
|
+
);
|
|
39
|
+
return normalizeCompactionBufferRatio(resolved, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
40
|
+
}
|
|
41
|
+
function positiveTokenCandidate(values = []) {
|
|
42
|
+
for (const value of values) {
|
|
43
|
+
const tokens = positiveTokenInt(value);
|
|
44
|
+
if (tokens) return tokens;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function resolveMainBufferSetting(cfg = {}) {
|
|
49
|
+
const configTokens = positiveTokenCandidate([cfg.mainBufferTokens, cfg.mainBuffer]);
|
|
50
|
+
if (configTokens) return { tokens: configTokens };
|
|
51
|
+
const configRatio = resolveBufferRatioCandidate(
|
|
52
|
+
[cfg.mainBufferPercent, cfg.mainBufferPct],
|
|
53
|
+
[cfg.mainBufferRatio, cfg.mainBufferFraction],
|
|
54
|
+
);
|
|
55
|
+
if (configRatio !== null) return { ratio: configRatio };
|
|
56
|
+
const envTokens = positiveTokenCandidate([process.env.MIXDOG_MAIN_COMPACT_BUFFER_TOKENS]);
|
|
57
|
+
if (envTokens) return { tokens: envTokens };
|
|
58
|
+
const envRatio = resolveBufferRatioCandidate(
|
|
59
|
+
[process.env.MIXDOG_MAIN_COMPACT_BUFFER_PERCENT],
|
|
60
|
+
[process.env.MIXDOG_MAIN_COMPACT_BUFFER_RATIO],
|
|
61
|
+
);
|
|
62
|
+
return envRatio === null ? null : { ratio: envRatio };
|
|
63
|
+
}
|
|
64
|
+
export function resolveMainCompactBufferRatio(cfg = {}) {
|
|
65
|
+
const setting = resolveMainBufferSetting(cfg);
|
|
66
|
+
return setting?.ratio ?? DEFAULT_MAIN_COMPACTION_BUFFER_RATIO;
|
|
67
|
+
}
|
|
68
|
+
export function compactionBufferTokensForBoundary(boundaryTokens, opts = {}) {
|
|
69
|
+
const boundary = Math.max(0, Math.floor(Number(boundaryTokens) || 0));
|
|
70
|
+
const explicit = Math.max(0, Math.floor(Number(opts.explicitTokens) || 0));
|
|
71
|
+
if (!boundary) return explicit;
|
|
72
|
+
const maxRatio = normalizeCompactionBufferRatio(opts.maxRatio, MAX_COMPACTION_BUFFER_RATIO);
|
|
73
|
+
const cap = Math.max(0, Math.floor(boundary * maxRatio));
|
|
74
|
+
if (explicit > 0) return Math.max(0, Math.min(explicit, cap));
|
|
75
|
+
const ratio = normalizeCompactionBufferRatio(opts.ratio, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
76
|
+
return Math.max(0, Math.min(Math.floor(boundary * ratio), cap));
|
|
77
|
+
}
|
|
78
|
+
export function isPersistedZeroBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
79
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
80
|
+
if (!boundary) return false;
|
|
81
|
+
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
82
|
+
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
83
|
+
const n = Number(process.env[envName]);
|
|
84
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
85
|
+
}
|
|
86
|
+
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
87
|
+
const n = Number(cfg?.[key]);
|
|
88
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
89
|
+
}
|
|
90
|
+
const ratio = Number(cfg?.bufferRatio);
|
|
91
|
+
if (Number.isFinite(ratio) && ratio > 0) return false;
|
|
92
|
+
const explicitTokens = Number(cfg?.bufferTokens ?? cfg?.buffer);
|
|
93
|
+
return Number.isFinite(explicitTokens) && explicitTokens === 0;
|
|
94
|
+
}
|
|
95
|
+
export function isLegacyDefaultBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
96
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
97
|
+
if (!boundary) return false;
|
|
98
|
+
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
99
|
+
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
100
|
+
const n = Number(process.env[envName]);
|
|
101
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
102
|
+
}
|
|
103
|
+
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
104
|
+
const n = Number(cfg?.[key]);
|
|
105
|
+
if (Number.isFinite(n) && n > 0) return false;
|
|
106
|
+
}
|
|
107
|
+
const explicitTokens = positiveTokenInt(cfg?.bufferTokens ?? cfg?.buffer);
|
|
108
|
+
const ratio = Number(cfg?.bufferRatio);
|
|
109
|
+
if (!explicitTokens || !Number.isFinite(ratio) || Math.abs(ratio - LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO) > 1e-9) return false;
|
|
110
|
+
const expectedTokens = Math.floor(boundary * LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
111
|
+
const cfgBoundary = positiveTokenInt(cfg?.boundaryTokens);
|
|
112
|
+
const cfgTrigger = positiveTokenInt(cfg?.triggerTokens);
|
|
113
|
+
return explicitTokens === expectedTokens
|
|
114
|
+
|| (cfgBoundary === boundary && cfgTrigger > 0 && explicitTokens === Math.max(0, boundary - cfgTrigger));
|
|
115
|
+
}
|
|
116
|
+
export function compactBufferConfigForBoundary(cfg = {}, boundaryTokens = 0) {
|
|
117
|
+
const base = cfg || {};
|
|
118
|
+
if (!isLegacyDefaultBufferTelemetry(base, boundaryTokens)
|
|
119
|
+
&& !isPersistedZeroBufferTelemetry(base, boundaryTokens)) return base;
|
|
120
|
+
return { ...base, bufferTokens: null, buffer: null, bufferRatio: null };
|
|
121
|
+
}
|
|
122
|
+
export function resolveCompactBufferTokens(boundaryTokens, cfg = {}, opts = {}) {
|
|
123
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
124
|
+
const effectiveCfg = compactBufferConfigForBoundary(cfg, boundary);
|
|
125
|
+
const configured = positiveTokenInt(effectiveCfg.bufferTokens ?? effectiveCfg.buffer)
|
|
126
|
+
|| envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS') || 0;
|
|
127
|
+
if (!boundary) return configured || positiveTokenInt(opts.defaultTokens) || DEFAULT_COMPACTION_BUFFER_TOKENS;
|
|
128
|
+
return compactionBufferTokensForBoundary(boundary, {
|
|
129
|
+
explicitTokens: configured,
|
|
130
|
+
ratio: resolveCompactBufferRatio(effectiveCfg),
|
|
131
|
+
maxRatio: opts.maxRatio ?? MAX_COMPACTION_BUFFER_RATIO,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export function resolveMainCompactBufferTokens(boundaryTokens, cfg = {}, opts = {}) {
|
|
135
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
136
|
+
const setting = resolveMainBufferSetting(cfg);
|
|
137
|
+
const configured = setting?.tokens || 0;
|
|
138
|
+
if (!boundary) return configured || positiveTokenInt(opts.defaultTokens) || DEFAULT_COMPACTION_BUFFER_TOKENS;
|
|
139
|
+
return compactionBufferTokensForBoundary(boundary, {
|
|
140
|
+
explicitTokens: configured,
|
|
141
|
+
ratio: setting?.ratio ?? DEFAULT_MAIN_COMPACTION_BUFFER_RATIO,
|
|
142
|
+
maxRatio: opts.maxRatio ?? MAX_COMPACTION_BUFFER_RATIO,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
export function resolveCompactTriggerTokens(sessionOrConfig = {}, boundaryTokens = 0) {
|
|
146
|
+
return resolveSessionCompactPolicy(sessionOrConfig, boundaryTokens).triggerTokens;
|
|
147
|
+
}
|
|
148
|
+
export function resolveSessionCompactPolicy(sessionOrConfig = {}, boundaryTokens = 0) {
|
|
149
|
+
const cfg = sessionOrConfig?.compaction || sessionOrConfig || {};
|
|
150
|
+
const boundary = positiveTokenInt(boundaryTokens);
|
|
151
|
+
if (!boundary) {
|
|
152
|
+
return {
|
|
153
|
+
autoCompactTokenLimit: null,
|
|
154
|
+
triggerTokens: null,
|
|
155
|
+
bufferTokens: 0,
|
|
156
|
+
bufferRatio: isAgentOwner(sessionOrConfig)
|
|
157
|
+
? resolveCompactBufferRatio(cfg)
|
|
158
|
+
: resolveMainCompactBufferRatio(cfg),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const rawLimit = positiveTokenInt(sessionOrConfig?.autoCompactTokenLimit ?? cfg?.autoCompactTokenLimit);
|
|
162
|
+
const explicitLimit = rawLimit && rawLimit < boundary ? rawLimit : null;
|
|
163
|
+
let triggerTokens;
|
|
164
|
+
if (explicitLimit) triggerTokens = explicitLimit;
|
|
165
|
+
else if (isAgentOwner(sessionOrConfig)) triggerTokens = Math.max(1, boundary - resolveCompactBufferTokens(boundary, cfg));
|
|
166
|
+
else triggerTokens = Math.max(1, boundary - resolveMainCompactBufferTokens(boundary, cfg));
|
|
167
|
+
const bufferTokens = Math.max(0, boundary - triggerTokens);
|
|
168
|
+
const bufferRatio = bufferTokens / boundary;
|
|
169
|
+
return { autoCompactTokenLimit: explicitLimit, triggerTokens, bufferTokens, bufferRatio };
|
|
170
|
+
}
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { isOffloadedToolResultText } from './tool-result-offload.mjs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { providerNativeToolPrefixCount } from '../../../../session-runtime/provider-request-tools.mjs';
|
|
4
|
-
import { isAgentOwner } from '../agent-owner.mjs';
|
|
5
4
|
import { contentImageDescriptors, contentToText } from '../providers/media-normalization.mjs';
|
|
5
|
+
export {
|
|
6
|
+
DEFAULT_COMPACTION_BUFFER_TOKENS,
|
|
7
|
+
DEFAULT_COMPACTION_BUFFER_RATIO,
|
|
8
|
+
DEFAULT_MAIN_COMPACTION_BUFFER_RATIO,
|
|
9
|
+
MAX_COMPACTION_BUFFER_RATIO,
|
|
10
|
+
DEFAULT_COMPACTION_KEEP_TOKENS,
|
|
11
|
+
positiveTokenInt,
|
|
12
|
+
normalizeCompactionBufferRatio,
|
|
13
|
+
resolveBufferRatioCandidate,
|
|
14
|
+
resolveCompactBufferRatio,
|
|
15
|
+
resolveMainCompactBufferRatio,
|
|
16
|
+
compactionBufferTokensForBoundary,
|
|
17
|
+
isPersistedZeroBufferTelemetry,
|
|
18
|
+
isLegacyDefaultBufferTelemetry,
|
|
19
|
+
compactBufferConfigForBoundary,
|
|
20
|
+
resolveCompactBufferTokens,
|
|
21
|
+
resolveMainCompactBufferTokens,
|
|
22
|
+
resolveCompactTriggerTokens,
|
|
23
|
+
resolveSessionCompactPolicy,
|
|
24
|
+
} from './context-compaction-policy.mjs';
|
|
6
25
|
|
|
7
26
|
// ---------------------------------------------------------------------------
|
|
8
27
|
// Conservative, Unicode-aware token estimator.
|
|
@@ -377,228 +396,6 @@ function contextSummaryResult(state, count) {
|
|
|
377
396
|
};
|
|
378
397
|
}
|
|
379
398
|
|
|
380
|
-
export const DEFAULT_COMPACTION_BUFFER_TOKENS = 0;
|
|
381
|
-
export const DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
382
|
-
// Main/user recall-fasttrack compacts at 95% by default. Lower triggers remain
|
|
383
|
-
// opt-in through the mainBuffer* configuration and MIXDOG_MAIN_COMPACT_BUFFER_*.
|
|
384
|
-
export const DEFAULT_MAIN_COMPACTION_BUFFER_RATIO = 0.05;
|
|
385
|
-
export const MAX_COMPACTION_BUFFER_RATIO = 0.25;
|
|
386
|
-
const MAX_BUFFER_INPUT_RATIO = 0.999_999;
|
|
387
|
-
export const DEFAULT_COMPACTION_KEEP_TOKENS = 8_000;
|
|
388
|
-
const LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO = 0.1;
|
|
389
|
-
|
|
390
|
-
export function positiveTokenInt(value) {
|
|
391
|
-
const n = Number(value);
|
|
392
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function envTokenInt(name) {
|
|
396
|
-
return positiveTokenInt(process.env[name]);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export function normalizeCompactionBufferRatio(value, fallback = DEFAULT_COMPACTION_BUFFER_RATIO) {
|
|
400
|
-
const n = Number(value);
|
|
401
|
-
if (Number.isFinite(n) && n > 0) return n > 1 ? n / 100 : n;
|
|
402
|
-
return fallback;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Percent-named inputs (bufferPercent / bufferPct / *_BUFFER_PERCENT) carry a
|
|
406
|
-
// PERCENT: 1 means 1% (0.01). Ratio-named inputs (bufferRatio / bufferFraction)
|
|
407
|
-
// carry a fraction: 0.01 means 1%, and a legacy value > 1 is read as a percent.
|
|
408
|
-
export function resolveBufferRatioCandidate(percentInputs = [], ratioInputs = []) {
|
|
409
|
-
for (const raw of percentInputs) {
|
|
410
|
-
const n = Number(raw);
|
|
411
|
-
if (Number.isFinite(n) && n > 0) return Math.min(MAX_BUFFER_INPUT_RATIO, n / 100);
|
|
412
|
-
}
|
|
413
|
-
for (const raw of ratioInputs) {
|
|
414
|
-
const n = Number(raw);
|
|
415
|
-
if (Number.isFinite(n) && n > 0) return Math.min(MAX_BUFFER_INPUT_RATIO, n > 1 ? n / 100 : n);
|
|
416
|
-
}
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export function resolveCompactBufferRatio(cfg = {}) {
|
|
421
|
-
const resolved = resolveBufferRatioCandidate(
|
|
422
|
-
[cfg.bufferPercent, cfg.bufferPct, process.env.MIXDOG_AGENT_COMPACT_BUFFER_PERCENT],
|
|
423
|
-
[cfg.bufferRatio, cfg.bufferFraction, process.env.MIXDOG_AGENT_COMPACT_BUFFER_RATIO],
|
|
424
|
-
);
|
|
425
|
-
return normalizeCompactionBufferRatio(resolved, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Main/user settings are deliberately separate from the semantic-agent buffer:
|
|
429
|
-
// `buffer*` and MIXDOG_AGENT_COMPACT_BUFFER_* remain agent-only controls.
|
|
430
|
-
// Source precedence is config before environment; within each source the fixed
|
|
431
|
-
// unit precedence is tokens, percent, then ratio. Invalid/zero values are
|
|
432
|
-
// ignored, and percent/ratio inputs are capped below 100%/1.0.
|
|
433
|
-
function positiveTokenCandidate(values = []) {
|
|
434
|
-
for (const value of values) {
|
|
435
|
-
const tokens = positiveTokenInt(value);
|
|
436
|
-
if (tokens) return tokens;
|
|
437
|
-
}
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function resolveMainBufferSetting(cfg = {}) {
|
|
442
|
-
const configTokens = positiveTokenCandidate([cfg.mainBufferTokens, cfg.mainBuffer]);
|
|
443
|
-
if (configTokens) return { tokens: configTokens };
|
|
444
|
-
const configRatio = resolveBufferRatioCandidate(
|
|
445
|
-
[cfg.mainBufferPercent, cfg.mainBufferPct],
|
|
446
|
-
[cfg.mainBufferRatio, cfg.mainBufferFraction],
|
|
447
|
-
);
|
|
448
|
-
if (configRatio !== null) return { ratio: configRatio };
|
|
449
|
-
|
|
450
|
-
const envTokens = positiveTokenCandidate([process.env.MIXDOG_MAIN_COMPACT_BUFFER_TOKENS]);
|
|
451
|
-
if (envTokens) return { tokens: envTokens };
|
|
452
|
-
const envRatio = resolveBufferRatioCandidate(
|
|
453
|
-
[process.env.MIXDOG_MAIN_COMPACT_BUFFER_PERCENT],
|
|
454
|
-
[process.env.MIXDOG_MAIN_COMPACT_BUFFER_RATIO],
|
|
455
|
-
);
|
|
456
|
-
return envRatio === null ? null : { ratio: envRatio };
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
export function resolveMainCompactBufferRatio(cfg = {}) {
|
|
460
|
-
const setting = resolveMainBufferSetting(cfg);
|
|
461
|
-
return setting?.ratio ?? DEFAULT_MAIN_COMPACTION_BUFFER_RATIO;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export function compactionBufferTokensForBoundary(boundaryTokens, opts = {}) {
|
|
465
|
-
const boundary = Math.max(0, Math.floor(Number(boundaryTokens) || 0));
|
|
466
|
-
const explicit = Math.max(0, Math.floor(Number(opts.explicitTokens) || 0));
|
|
467
|
-
if (!boundary) return explicit;
|
|
468
|
-
const maxRatio = normalizeCompactionBufferRatio(opts.maxRatio, MAX_COMPACTION_BUFFER_RATIO);
|
|
469
|
-
const cap = Math.max(0, Math.floor(boundary * maxRatio));
|
|
470
|
-
if (explicit > 0) return Math.max(0, Math.min(explicit, cap));
|
|
471
|
-
const ratio = normalizeCompactionBufferRatio(opts.ratio, DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
472
|
-
return Math.max(0, Math.min(Math.floor(boundary * ratio), cap));
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export function isPersistedZeroBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
476
|
-
const boundary = positiveTokenInt(boundaryTokens);
|
|
477
|
-
if (!boundary) return false;
|
|
478
|
-
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
479
|
-
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
480
|
-
const n = Number(process.env[envName]);
|
|
481
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
482
|
-
}
|
|
483
|
-
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
484
|
-
const n = Number(cfg?.[key]);
|
|
485
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
486
|
-
}
|
|
487
|
-
const ratio = Number(cfg?.bufferRatio);
|
|
488
|
-
if (Number.isFinite(ratio) && ratio > 0) return false;
|
|
489
|
-
const explicitTokens = Number(cfg?.bufferTokens ?? cfg?.buffer);
|
|
490
|
-
if (!Number.isFinite(explicitTokens) || explicitTokens !== 0) return false;
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
export function isLegacyDefaultBufferTelemetry(cfg = {}, boundaryTokens = 0) {
|
|
495
|
-
const boundary = positiveTokenInt(boundaryTokens);
|
|
496
|
-
if (!boundary) return false;
|
|
497
|
-
if (envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')) return false;
|
|
498
|
-
for (const envName of ['MIXDOG_AGENT_COMPACT_BUFFER_PERCENT', 'MIXDOG_AGENT_COMPACT_BUFFER_RATIO']) {
|
|
499
|
-
const n = Number(process.env[envName]);
|
|
500
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
501
|
-
}
|
|
502
|
-
// Percent/fraction-named fields are operator config. Legacy/default
|
|
503
|
-
// telemetry persisted bufferTokens + bufferRatio after a check/compact pass.
|
|
504
|
-
for (const key of ['bufferPercent', 'bufferPct', 'bufferFraction']) {
|
|
505
|
-
const n = Number(cfg?.[key]);
|
|
506
|
-
if (Number.isFinite(n) && n > 0) return false;
|
|
507
|
-
}
|
|
508
|
-
const explicitTokens = positiveTokenInt(cfg?.bufferTokens ?? cfg?.buffer);
|
|
509
|
-
const ratio = Number(cfg?.bufferRatio);
|
|
510
|
-
if (!explicitTokens || !Number.isFinite(ratio) || Math.abs(ratio - LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO) > 1e-9) return false;
|
|
511
|
-
const expectedTokens = Math.floor(boundary * LEGACY_DEFAULT_COMPACTION_BUFFER_RATIO);
|
|
512
|
-
const cfgBoundary = positiveTokenInt(cfg?.boundaryTokens);
|
|
513
|
-
const cfgTrigger = positiveTokenInt(cfg?.triggerTokens);
|
|
514
|
-
return explicitTokens === expectedTokens
|
|
515
|
-
|| (cfgBoundary === boundary && cfgTrigger > 0 && explicitTokens === Math.max(0, boundary - cfgTrigger));
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export function compactBufferConfigForBoundary(cfg = {}, boundaryTokens = 0) {
|
|
519
|
-
const base = cfg || {};
|
|
520
|
-
if (!isLegacyDefaultBufferTelemetry(base, boundaryTokens)
|
|
521
|
-
&& !isPersistedZeroBufferTelemetry(base, boundaryTokens)) {
|
|
522
|
-
return base;
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
...base,
|
|
526
|
-
bufferTokens: null,
|
|
527
|
-
buffer: null,
|
|
528
|
-
bufferRatio: null,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
export function resolveCompactBufferTokens(boundaryTokens, cfg = {}, opts = {}) {
|
|
533
|
-
const boundary = positiveTokenInt(boundaryTokens);
|
|
534
|
-
const effectiveCfg = compactBufferConfigForBoundary(cfg, boundary);
|
|
535
|
-
const configured = positiveTokenInt(effectiveCfg.bufferTokens ?? effectiveCfg.buffer)
|
|
536
|
-
|| envTokenInt('MIXDOG_AGENT_COMPACT_BUFFER_TOKENS')
|
|
537
|
-
|| 0;
|
|
538
|
-
if (!boundary) return configured || positiveTokenInt(opts.defaultTokens) || DEFAULT_COMPACTION_BUFFER_TOKENS;
|
|
539
|
-
return compactionBufferTokensForBoundary(boundary, {
|
|
540
|
-
explicitTokens: configured,
|
|
541
|
-
ratio: resolveCompactBufferRatio(effectiveCfg),
|
|
542
|
-
maxRatio: opts.maxRatio ?? MAX_COMPACTION_BUFFER_RATIO,
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
export function resolveMainCompactBufferTokens(boundaryTokens, cfg = {}, opts = {}) {
|
|
547
|
-
const boundary = positiveTokenInt(boundaryTokens);
|
|
548
|
-
const setting = resolveMainBufferSetting(cfg);
|
|
549
|
-
const configured = setting?.tokens || 0;
|
|
550
|
-
if (!boundary) return configured || positiveTokenInt(opts.defaultTokens) || DEFAULT_COMPACTION_BUFFER_TOKENS;
|
|
551
|
-
return compactionBufferTokensForBoundary(boundary, {
|
|
552
|
-
explicitTokens: configured,
|
|
553
|
-
ratio: setting?.ratio ?? DEFAULT_MAIN_COMPACTION_BUFFER_RATIO,
|
|
554
|
-
maxRatio: opts.maxRatio ?? MAX_COMPACTION_BUFFER_RATIO,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
export function resolveCompactTriggerTokens(sessionOrConfig = {}, boundaryTokens = 0) {
|
|
559
|
-
return resolveSessionCompactPolicy(sessionOrConfig, boundaryTokens).triggerTokens;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Single source of truth for per-session compaction policy math. Manager
|
|
563
|
-
// (compactTriggerForSession), the turn loop (resolveWorkerCompactPolicy), and
|
|
564
|
-
// the /context gauge all derive their trigger/buffer from here so the numbers
|
|
565
|
-
// never diverge. Rules:
|
|
566
|
-
// - a truly-explicit sub-boundary auto-compact limit always wins
|
|
567
|
-
// (trigger = limit) for every session type;
|
|
568
|
-
// - agent-owned semantic sessions otherwise keep the default early-trigger
|
|
569
|
-
// buffer (config-driven, default 10% -> compact at 90% of the boundary);
|
|
570
|
-
// - main/user recall-fasttrack sessions use their independently configurable
|
|
571
|
-
// buffer (default 25% -> compact at 75% of the effective boundary).
|
|
572
|
-
// Returns the sanitized explicit limit (null when absent/legacy full-window)
|
|
573
|
-
// plus triggerTokens / bufferTokens / bufferRatio for the given boundary.
|
|
574
|
-
export function resolveSessionCompactPolicy(sessionOrConfig = {}, boundaryTokens = 0) {
|
|
575
|
-
const cfg = sessionOrConfig?.compaction || sessionOrConfig || {};
|
|
576
|
-
const boundary = positiveTokenInt(boundaryTokens);
|
|
577
|
-
if (!boundary) {
|
|
578
|
-
return {
|
|
579
|
-
autoCompactTokenLimit: null,
|
|
580
|
-
triggerTokens: null,
|
|
581
|
-
bufferTokens: 0,
|
|
582
|
-
bufferRatio: isAgentOwner(sessionOrConfig)
|
|
583
|
-
? resolveCompactBufferRatio(cfg)
|
|
584
|
-
: resolveMainCompactBufferRatio(cfg),
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
const rawLimit = positiveTokenInt(sessionOrConfig?.autoCompactTokenLimit ?? cfg?.autoCompactTokenLimit);
|
|
588
|
-
const explicitLimit = rawLimit && rawLimit < boundary ? rawLimit : null;
|
|
589
|
-
let triggerTokens;
|
|
590
|
-
if (explicitLimit) {
|
|
591
|
-
triggerTokens = explicitLimit;
|
|
592
|
-
} else if (isAgentOwner(sessionOrConfig)) {
|
|
593
|
-
triggerTokens = Math.max(1, boundary - resolveCompactBufferTokens(boundary, cfg));
|
|
594
|
-
} else {
|
|
595
|
-
triggerTokens = Math.max(1, boundary - resolveMainCompactBufferTokens(boundary, cfg));
|
|
596
|
-
}
|
|
597
|
-
const bufferTokens = Math.max(0, boundary - triggerTokens);
|
|
598
|
-
const bufferRatio = bufferTokens / boundary;
|
|
599
|
-
return { autoCompactTokenLimit: explicitLimit, triggerTokens, bufferTokens, bufferRatio };
|
|
600
|
-
}
|
|
601
|
-
|
|
602
399
|
function stripSystemReminder(text) {
|
|
603
400
|
return String(text || '')
|
|
604
401
|
.replace(/^\s*<system-reminder>\s*/i, '')
|