condukt 0.16.9 → 0.16.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtimes/copilot/copilot-adapter.d.ts.map +1 -1
- package/dist/runtimes/copilot/copilot-adapter.js +12 -0
- package/dist/runtimes/copilot/copilot-adapter.js.map +1 -1
- package/dist/runtimes/copilot/copilot-backend.d.ts +93 -2
- package/dist/runtimes/copilot/copilot-backend.d.ts.map +1 -1
- package/dist/runtimes/copilot/index.d.ts +2 -2
- package/dist/runtimes/copilot/index.d.ts.map +1 -1
- package/dist/runtimes/copilot/index.js.map +1 -1
- package/dist/runtimes/copilot/sdk-backend.d.ts +5 -2
- package/dist/runtimes/copilot/sdk-backend.d.ts.map +1 -1
- package/dist/runtimes/copilot/sdk-backend.js +830 -113
- package/dist/runtimes/copilot/sdk-backend.js.map +1 -1
- package/dist/runtimes/copilot/subagents.d.ts +17 -0
- package/dist/runtimes/copilot/subagents.d.ts.map +1 -1
- package/dist/runtimes/copilot/subagents.js.map +1 -1
- package/dist/src/agent-node.d.ts +3 -2
- package/dist/src/agent-node.d.ts.map +1 -1
- package/dist/src/agent-node.js +4 -0
- package/dist/src/agent-node.js.map +1 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -0
- package/dist/src/agent.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/panel-node.d.ts +4 -2
- package/dist/src/panel-node.d.ts.map +1 -1
- package/dist/src/panel-node.js +4 -0
- package/dist/src/panel-node.js.map +1 -1
- package/dist/src/types.d.ts +92 -4
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -49,9 +49,45 @@ exports.SdkBackend = void 0;
|
|
|
49
49
|
const cp = __importStar(require("child_process"));
|
|
50
50
|
const fs = __importStar(require("fs"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const node_crypto_1 = require("node:crypto");
|
|
52
53
|
const types_1 = require("../../src/types");
|
|
53
54
|
const lifecycle_events_1 = require("./lifecycle-events");
|
|
54
55
|
const subagents_1 = require("./subagents");
|
|
56
|
+
// The CLI requires at least four messages before history.compact() can make
|
|
57
|
+
// progress. This is a runtime constraint, not a tuned safety threshold.
|
|
58
|
+
const MIN_MESSAGES_FOR_COMPACTION = 4;
|
|
59
|
+
function parseContextUsage(data) {
|
|
60
|
+
if (!data)
|
|
61
|
+
return undefined;
|
|
62
|
+
const currentTokens = data.currentTokens;
|
|
63
|
+
const tokenLimit = data.tokenLimit;
|
|
64
|
+
const messagesLength = data.messagesLength;
|
|
65
|
+
if (typeof currentTokens !== 'number'
|
|
66
|
+
|| !Number.isFinite(currentTokens)
|
|
67
|
+
|| currentTokens < 0
|
|
68
|
+
|| typeof tokenLimit !== 'number'
|
|
69
|
+
|| !Number.isFinite(tokenLimit)
|
|
70
|
+
|| tokenLimit <= 0
|
|
71
|
+
|| typeof messagesLength !== 'number'
|
|
72
|
+
|| !Number.isFinite(messagesLength)
|
|
73
|
+
|| messagesLength < 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
currentTokens,
|
|
78
|
+
tokenLimit,
|
|
79
|
+
messagesLength,
|
|
80
|
+
...(nonNegativeFiniteNumber(data.systemTokens) !== undefined
|
|
81
|
+
? { systemTokens: data.systemTokens }
|
|
82
|
+
: {}),
|
|
83
|
+
...(nonNegativeFiniteNumber(data.conversationTokens) !== undefined
|
|
84
|
+
? { conversationTokens: data.conversationTokens }
|
|
85
|
+
: {}),
|
|
86
|
+
...(nonNegativeFiniteNumber(data.toolDefinitionsTokens) !== undefined
|
|
87
|
+
? { toolDefinitionsTokens: data.toolDefinitionsTokens }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
55
91
|
function normalizeSdkEvent(event) {
|
|
56
92
|
if (typeof event !== 'object' || event === null)
|
|
57
93
|
return {};
|
|
@@ -64,15 +100,114 @@ function normalizeSdkEvent(event) {
|
|
|
64
100
|
: {}),
|
|
65
101
|
};
|
|
66
102
|
}
|
|
103
|
+
function isChildModelRequest(context) {
|
|
104
|
+
if (context.interactionType === 'conversation-compaction')
|
|
105
|
+
return false;
|
|
106
|
+
if (context.interactionType === 'conversation-subagent'
|
|
107
|
+
|| context.interactionType === 'conversation-sampling') {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// Generic child traffic must carry both sides of its parent relationship.
|
|
111
|
+
// Treat incomplete or ambiguous provenance as parent traffic so uncertainty
|
|
112
|
+
// cannot bypass parent guards.
|
|
113
|
+
return context.agentId !== undefined && context.parentAgentId !== undefined;
|
|
114
|
+
}
|
|
115
|
+
function modelRequestPurpose(interactionType, child, retry) {
|
|
116
|
+
if (interactionType === 'conversation-compaction')
|
|
117
|
+
return 'compaction';
|
|
118
|
+
if (retry)
|
|
119
|
+
return 'retry';
|
|
120
|
+
return child ? 'child' : 'normal-turn';
|
|
121
|
+
}
|
|
122
|
+
function observedRequestBodyTelemetry(request) {
|
|
123
|
+
const contentLength = request.headers.get('content-length');
|
|
124
|
+
const parsedLength = contentLength === null ? undefined : Number(contentLength);
|
|
125
|
+
return Number.isFinite(parsedLength) && parsedLength !== undefined && parsedLength >= 0
|
|
126
|
+
? { observedRequestBodyBytes: parsedLength }
|
|
127
|
+
: {};
|
|
128
|
+
}
|
|
129
|
+
function createObservedRequestHandler(RequestHandler, WebSocketForwarder, observe, usageFor, refreshParentUsage, retryState, canDispatch) {
|
|
130
|
+
return new class extends RequestHandler {
|
|
131
|
+
parentObservationTail = Promise.resolve();
|
|
132
|
+
async sendRequest(request, context) {
|
|
133
|
+
let serialized = observedRequestBodyTelemetry(request);
|
|
134
|
+
try {
|
|
135
|
+
const body = await request.clone().arrayBuffer();
|
|
136
|
+
serialized = {
|
|
137
|
+
observedRequestBodyBytes: body.byteLength,
|
|
138
|
+
observedRequestBodySha256: (0, node_crypto_1.createHash)('sha256')
|
|
139
|
+
.update(new Uint8Array(body))
|
|
140
|
+
.digest('hex'),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// A bodyless or non-cloneable request still carries content-length when known.
|
|
145
|
+
}
|
|
146
|
+
await this.observeRequest(context, serialized);
|
|
147
|
+
return super.sendRequest(request, context);
|
|
148
|
+
}
|
|
149
|
+
async openWebSocket(context) {
|
|
150
|
+
await this.observeRequest(context, {});
|
|
151
|
+
return new WebSocketForwarder(context);
|
|
152
|
+
}
|
|
153
|
+
observeRequest(context, serialized) {
|
|
154
|
+
const child = isChildModelRequest(context);
|
|
155
|
+
if (child)
|
|
156
|
+
return this.observeRequestNow(context, serialized, true);
|
|
157
|
+
const previous = this.parentObservationTail;
|
|
158
|
+
const observation = previous.then(() => this.observeRequestNow(context, serialized, false));
|
|
159
|
+
this.parentObservationTail = observation.catch(() => undefined);
|
|
160
|
+
return observation;
|
|
161
|
+
}
|
|
162
|
+
async observeRequestNow(context, serialized, child) {
|
|
163
|
+
const compaction = context.interactionType === 'conversation-compaction';
|
|
164
|
+
const retryAgentId = child ? context.agentId : undefined;
|
|
165
|
+
const retry = compaction || (child && retryAgentId === undefined)
|
|
166
|
+
? false
|
|
167
|
+
: retryState(retryAgentId, false);
|
|
168
|
+
const purpose = modelRequestPurpose(context.interactionType, child, retry);
|
|
169
|
+
const childUsage = child && context.agentId !== undefined
|
|
170
|
+
? usageFor(context.agentId)
|
|
171
|
+
: undefined;
|
|
172
|
+
const measurement = child
|
|
173
|
+
? childUsage === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: { usage: childUsage, observedInterRequestGrowth: 0 }
|
|
176
|
+
: await refreshParentUsage(purpose);
|
|
177
|
+
const usage = measurement?.usage;
|
|
178
|
+
observe({
|
|
179
|
+
requestId: context.requestId,
|
|
180
|
+
sessionId: context.sessionId,
|
|
181
|
+
agentId: context.agentId,
|
|
182
|
+
parentAgentId: context.parentAgentId,
|
|
183
|
+
purpose,
|
|
184
|
+
interactionType: context.interactionType,
|
|
185
|
+
currentTokens: usage?.currentTokens,
|
|
186
|
+
tokenLimit: usage?.tokenLimit,
|
|
187
|
+
messagesLength: usage?.messagesLength,
|
|
188
|
+
systemTokens: usage?.systemTokens,
|
|
189
|
+
conversationTokens: usage?.conversationTokens,
|
|
190
|
+
toolDefinitionsTokens: usage?.toolDefinitionsTokens,
|
|
191
|
+
...serialized,
|
|
192
|
+
});
|
|
193
|
+
if (!canDispatch(context, purpose, child, measurement)) {
|
|
194
|
+
throw new Error(`Blocked ${purpose} model request ${context.requestId}: parent context headroom has not been restored`);
|
|
195
|
+
}
|
|
196
|
+
if (retry)
|
|
197
|
+
retryState(retryAgentId, true);
|
|
198
|
+
}
|
|
199
|
+
}();
|
|
200
|
+
}
|
|
67
201
|
const NAMED_SDK_EVENTS = new Set([
|
|
68
202
|
'assistant.message', 'assistant.message_delta',
|
|
69
203
|
'assistant.reasoning', 'assistant.reasoning_delta',
|
|
204
|
+
'assistant.turn_retry',
|
|
70
205
|
'assistant.intent', 'assistant.usage',
|
|
71
206
|
'tool.execution_start', 'tool.execution_complete',
|
|
72
207
|
'tool.execution_partial_result',
|
|
73
208
|
'session.idle', 'session.task_complete', 'session.error',
|
|
74
209
|
'model.call_failure', 'abort',
|
|
75
|
-
'session.compaction_start', 'session.compaction_complete',
|
|
210
|
+
'session.usage_info', 'session.compaction_start', 'session.compaction_complete',
|
|
76
211
|
'subagent.started', 'subagent.completed', 'subagent.failed',
|
|
77
212
|
'permission.requested',
|
|
78
213
|
]);
|
|
@@ -336,6 +471,9 @@ class SdkBackend {
|
|
|
336
471
|
mcpConfigPath;
|
|
337
472
|
configDirectory;
|
|
338
473
|
subagentRoster;
|
|
474
|
+
subagentsEnabled;
|
|
475
|
+
maxDepth;
|
|
476
|
+
maxConcurrency;
|
|
339
477
|
extraPathDirs;
|
|
340
478
|
pathTools;
|
|
341
479
|
logger;
|
|
@@ -345,6 +483,9 @@ class SdkBackend {
|
|
|
345
483
|
this.mcpConfigPath = options.mcpConfigPath;
|
|
346
484
|
this.configDirectory = options.configDir;
|
|
347
485
|
this.subagentRoster = options.subagentRoster;
|
|
486
|
+
this.subagentsEnabled = options.subagentsEnabled ?? true;
|
|
487
|
+
this.maxDepth = options.maxDepth;
|
|
488
|
+
this.maxConcurrency = options.maxConcurrency;
|
|
348
489
|
this.extraPathDirs = options.extraPathDirs ?? [];
|
|
349
490
|
this.pathTools = options.pathTools ?? [];
|
|
350
491
|
this.logger = options.logger ?? types_1.NO_OP_LOGGER;
|
|
@@ -366,7 +507,7 @@ class SdkBackend {
|
|
|
366
507
|
if (cached)
|
|
367
508
|
return cached;
|
|
368
509
|
const resolution = this.resolveModelCapabilitiesUncached(client, model).catch((err) => {
|
|
369
|
-
this.logger.warn('Copilot model capability resolution failed;
|
|
510
|
+
this.logger.warn('Copilot model capability resolution failed; awaiting runtime context accounting', {
|
|
370
511
|
model,
|
|
371
512
|
reason: 'capability_resolution_failed',
|
|
372
513
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -382,7 +523,7 @@ class SdkBackend {
|
|
|
382
523
|
return {};
|
|
383
524
|
const selected = models.find(candidate => candidate.id === model);
|
|
384
525
|
if (!selected) {
|
|
385
|
-
this.logger.warn('Copilot model capability lookup failed;
|
|
526
|
+
this.logger.warn('Copilot model capability lookup failed; awaiting runtime context accounting', {
|
|
386
527
|
model,
|
|
387
528
|
reason: 'model_not_found',
|
|
388
529
|
});
|
|
@@ -402,7 +543,7 @@ class SdkBackend {
|
|
|
402
543
|
? contextWindowLimit
|
|
403
544
|
: undefined;
|
|
404
545
|
if (source === undefined || discoveredLimit === undefined) {
|
|
405
|
-
this.logger.warn('Copilot model capability lookup returned no usable token limit;
|
|
546
|
+
this.logger.warn('Copilot model capability lookup returned no usable token limit; awaiting runtime context accounting', {
|
|
406
547
|
model,
|
|
407
548
|
reason: 'invalid_limit',
|
|
408
549
|
maxPromptTokens: promptLimit,
|
|
@@ -410,28 +551,14 @@ class SdkBackend {
|
|
|
410
551
|
});
|
|
411
552
|
return {};
|
|
412
553
|
}
|
|
413
|
-
|
|
414
|
-
if (!isPositiveFiniteNumber(promptTokenCeiling)) {
|
|
415
|
-
this.logger.warn('Copilot model capability lookup produced no usable prompt-token ceiling; using proportional compaction thresholds only', {
|
|
416
|
-
model,
|
|
417
|
-
reason: 'invalid_computed_ceiling',
|
|
418
|
-
discoveredLimit,
|
|
419
|
-
limitSource: source,
|
|
420
|
-
});
|
|
421
|
-
return {};
|
|
422
|
-
}
|
|
423
|
-
this.logger.info('Resolved Copilot model prompt-token ceiling', {
|
|
554
|
+
this.logger.info('Resolved Copilot model prompt-token limit', {
|
|
424
555
|
model,
|
|
425
|
-
discoveredLimit,
|
|
556
|
+
reportedPromptTokenLimit: discoveredLimit,
|
|
426
557
|
limitSource: source,
|
|
427
|
-
promptTokenCeiling,
|
|
428
558
|
});
|
|
429
559
|
return {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
max_prompt_tokens: promptTokenCeiling,
|
|
433
|
-
},
|
|
434
|
-
},
|
|
560
|
+
reportedPromptTokenLimit: Math.floor(discoveredLimit),
|
|
561
|
+
limitSource: source,
|
|
435
562
|
};
|
|
436
563
|
}
|
|
437
564
|
listModels(client) {
|
|
@@ -443,7 +570,7 @@ class SdkBackend {
|
|
|
443
570
|
return await client.listModels();
|
|
444
571
|
}
|
|
445
572
|
catch (err) {
|
|
446
|
-
this.logger.warn('Copilot model capability discovery failed;
|
|
573
|
+
this.logger.warn('Copilot model capability discovery failed; awaiting runtime context accounting', {
|
|
447
574
|
reason: 'list_models_failed',
|
|
448
575
|
error: err instanceof Error ? err.message : String(err),
|
|
449
576
|
});
|
|
@@ -456,7 +583,7 @@ class SdkBackend {
|
|
|
456
583
|
if (options?.signal?.aborted) {
|
|
457
584
|
throw new Error('Session creation aborted');
|
|
458
585
|
}
|
|
459
|
-
return new SdkSession(config, this.mcpConfigPath, this.configDirectory, this.subagentRoster, this.extraPathDirs, this.pathTools, this.logger, (client, model) => this.resolveModelCapabilities(client, model));
|
|
586
|
+
return new SdkSession(config, this.mcpConfigPath, this.configDirectory, this.subagentRoster, this.subagentsEnabled, this.maxDepth, this.maxConcurrency, this.extraPathDirs, this.pathTools, this.logger, (client, model) => this.resolveModelCapabilities(client, model));
|
|
460
587
|
}
|
|
461
588
|
}
|
|
462
589
|
exports.SdkBackend = SdkBackend;
|
|
@@ -481,6 +608,9 @@ class SdkSession {
|
|
|
481
608
|
mcpConfigPath;
|
|
482
609
|
configDirectory;
|
|
483
610
|
backendRoster;
|
|
611
|
+
backendSubagentsEnabled;
|
|
612
|
+
backendMaxDepth;
|
|
613
|
+
backendMaxConcurrency;
|
|
484
614
|
extraPathDirs;
|
|
485
615
|
pathTools;
|
|
486
616
|
logger;
|
|
@@ -490,11 +620,23 @@ class SdkSession {
|
|
|
490
620
|
compactionTimer = null;
|
|
491
621
|
abortGraceTimer = null;
|
|
492
622
|
compactionInProgress = false;
|
|
623
|
+
parentCompactionGeneration = 0;
|
|
624
|
+
verifiedParentCompactionGeneration = 0;
|
|
625
|
+
compactionBaselinePromise;
|
|
626
|
+
proactiveCompactionPromise;
|
|
627
|
+
reportedPromptTokenLimit;
|
|
628
|
+
reportedPromptTokenLimitSource;
|
|
629
|
+
adaptiveCompactionPolicy;
|
|
630
|
+
previousParentRequestTokens;
|
|
631
|
+
largestObservedInterRequestGrowth = 0;
|
|
632
|
+
parentUsage;
|
|
633
|
+
latestContextDiagnostics;
|
|
634
|
+
childUsage = new Map();
|
|
635
|
+
retryingAgentIds = new Set();
|
|
636
|
+
retryingParent = false;
|
|
493
637
|
turnSettled = false;
|
|
494
|
-
compactionRecoveryInProgress = false;
|
|
495
638
|
aborted = false;
|
|
496
639
|
_turnText = new Map();
|
|
497
|
-
intentionalAborts = new WeakSet();
|
|
498
640
|
/**
|
|
499
641
|
* Maps toolCallId -> toolName for attributing tool_complete events.
|
|
500
642
|
* Populated from assistant.message toolRequests and tool.execution_start.
|
|
@@ -515,11 +657,56 @@ class SdkSession {
|
|
|
515
657
|
// SDK manages the CLI process internally; no direct PID access
|
|
516
658
|
return null;
|
|
517
659
|
}
|
|
518
|
-
|
|
660
|
+
history = {
|
|
661
|
+
summarizeForHandoff: async () => {
|
|
662
|
+
const sdkSession = this.activeSdkSession();
|
|
663
|
+
const result = await sdkSession.rpc.history.summarizeForHandoff();
|
|
664
|
+
this.logger.info('Copilot history handoff summary completed', {
|
|
665
|
+
summaryLength: result.summary.length,
|
|
666
|
+
});
|
|
667
|
+
return result.summary;
|
|
668
|
+
},
|
|
669
|
+
truncate: async (eventId) => {
|
|
670
|
+
const sdkSession = this.activeSdkSession();
|
|
671
|
+
const result = await sdkSession.rpc.history.truncate({ eventId });
|
|
672
|
+
this.logger.warn('Copilot history truncation completed', {
|
|
673
|
+
eventId,
|
|
674
|
+
eventsRemoved: result.eventsRemoved,
|
|
675
|
+
});
|
|
676
|
+
return result.eventsRemoved;
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
metadata = {
|
|
680
|
+
getContextAttribution: async () => {
|
|
681
|
+
const result = await this.activeSdkSession().rpc.metadata.getContextAttribution();
|
|
682
|
+
return result.contextAttribution ?? null;
|
|
683
|
+
},
|
|
684
|
+
getContextHeaviestMessages: async (limit) => {
|
|
685
|
+
return this.activeSdkSession().rpc.metadata.getContextHeaviestMessages(limit === undefined ? {} : { limit });
|
|
686
|
+
},
|
|
687
|
+
recomputeContextTokens: async (modelId = this.config.model) => {
|
|
688
|
+
return this.activeSdkSession().rpc.metadata.recomputeContextTokens({ modelId });
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
usage = {
|
|
692
|
+
getMetrics: async () => {
|
|
693
|
+
return this.activeSdkSession().rpc.usage.getMetrics();
|
|
694
|
+
},
|
|
695
|
+
};
|
|
696
|
+
activeSdkSession() {
|
|
697
|
+
const sdkSession = this._sdkSession;
|
|
698
|
+
if (!sdkSession)
|
|
699
|
+
throw new Error('SDK session is not active');
|
|
700
|
+
return sdkSession;
|
|
701
|
+
}
|
|
702
|
+
constructor(config, mcpConfigPath, configDirectory, backendRoster, backendSubagentsEnabled, backendMaxDepth, backendMaxConcurrency, extraPathDirs, pathTools, logger, resolveModelCapabilities) {
|
|
519
703
|
this.config = config;
|
|
520
704
|
this.mcpConfigPath = mcpConfigPath;
|
|
521
705
|
this.configDirectory = configDirectory;
|
|
522
706
|
this.backendRoster = backendRoster;
|
|
707
|
+
this.backendSubagentsEnabled = backendSubagentsEnabled;
|
|
708
|
+
this.backendMaxDepth = backendMaxDepth;
|
|
709
|
+
this.backendMaxConcurrency = backendMaxConcurrency;
|
|
523
710
|
this.extraPathDirs = extraPathDirs;
|
|
524
711
|
this.pathTools = pathTools;
|
|
525
712
|
this.logger = logger;
|
|
@@ -542,6 +729,16 @@ class SdkSession {
|
|
|
542
729
|
this._toolCallNames.clear();
|
|
543
730
|
this._callIdToParent.clear();
|
|
544
731
|
this._pendingPartials.clear();
|
|
732
|
+
this.parentUsage = undefined;
|
|
733
|
+
this.latestContextDiagnostics = undefined;
|
|
734
|
+
this.adaptiveCompactionPolicy = undefined;
|
|
735
|
+
this.previousParentRequestTokens = undefined;
|
|
736
|
+
this.largestObservedInterRequestGrowth = 0;
|
|
737
|
+
this.childUsage.clear();
|
|
738
|
+
this.retryingAgentIds.clear();
|
|
739
|
+
this.retryingParent = false;
|
|
740
|
+
this.compactionBaselinePromise = undefined;
|
|
741
|
+
this.proactiveCompactionPromise = undefined;
|
|
545
742
|
this.turnSettled = false;
|
|
546
743
|
this._run(prompt).catch((err) => {
|
|
547
744
|
this.fail(err instanceof Error ? err : new Error(String(err)), 'session.run');
|
|
@@ -558,7 +755,7 @@ class SdkSession {
|
|
|
558
755
|
const sdkModuleName = '@github/copilot-sdk';
|
|
559
756
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
560
757
|
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
561
|
-
const { CopilotClient, RuntimeConnection, approveAll } = await dynamicImport(sdkModuleName);
|
|
758
|
+
const { CopilotClient, CopilotRequestHandler, CopilotWebSocketForwarder, RuntimeConnection, approveAll, } = await dynamicImport(sdkModuleName);
|
|
562
759
|
// ---------------------------------------------------------------
|
|
563
760
|
// Build hardened environment (strip NODE_OPTIONS, extend PATH)
|
|
564
761
|
// ---------------------------------------------------------------
|
|
@@ -593,6 +790,55 @@ class SdkSession {
|
|
|
593
790
|
connection: RuntimeConnection.forStdio(),
|
|
594
791
|
env,
|
|
595
792
|
logLevel: 'warning',
|
|
793
|
+
requestHandler: createObservedRequestHandler(CopilotRequestHandler, CopilotWebSocketForwarder, (telemetry) => this.logModelRequest(telemetry), (agentId) => agentId ? this.childUsage.get(agentId) : this.parentUsage, (purpose) => {
|
|
794
|
+
const sdkSession = this._sdkSession;
|
|
795
|
+
return sdkSession && (purpose === 'compaction'
|
|
796
|
+
|| this.config.compactionMode === 'adaptive'
|
|
797
|
+
|| this.shouldCaptureRequestDiagnostics(purpose))
|
|
798
|
+
? this.refreshParentUsageBeforeRequest(sdkSession, purpose)
|
|
799
|
+
: Promise.resolve(this.parentUsage === undefined
|
|
800
|
+
? undefined
|
|
801
|
+
: { usage: this.parentUsage, observedInterRequestGrowth: 0 });
|
|
802
|
+
}, (agentId, consume) => {
|
|
803
|
+
if (agentId) {
|
|
804
|
+
const retry = this.retryingAgentIds.has(agentId);
|
|
805
|
+
if (retry && consume)
|
|
806
|
+
this.retryingAgentIds.delete(agentId);
|
|
807
|
+
return retry;
|
|
808
|
+
}
|
|
809
|
+
const retry = this.retryingParent;
|
|
810
|
+
if (retry && consume)
|
|
811
|
+
this.retryingParent = false;
|
|
812
|
+
return retry;
|
|
813
|
+
},
|
|
814
|
+
// Capability discovery also uses this handler before createSession has
|
|
815
|
+
// returned a session handle. Scope the allowance to traffic that has no
|
|
816
|
+
// session provenance so a delayed createSession cannot open a parent turn.
|
|
817
|
+
(context, purpose, child, measurement) => {
|
|
818
|
+
if (this.aborted || this.turnSettled)
|
|
819
|
+
return false;
|
|
820
|
+
if (this._sdkSession === null) {
|
|
821
|
+
return context.sessionId === undefined && context.agentId === undefined;
|
|
822
|
+
}
|
|
823
|
+
if (child)
|
|
824
|
+
return true;
|
|
825
|
+
const usage = measurement?.usage;
|
|
826
|
+
if (purpose === 'compaction') {
|
|
827
|
+
return usage !== undefined && usage.currentTokens < usage.tokenLimit;
|
|
828
|
+
}
|
|
829
|
+
if (this.parentCompactionGeneration > this.verifiedParentCompactionGeneration
|
|
830
|
+
|| this.compactionInProgress)
|
|
831
|
+
return false;
|
|
832
|
+
if (measurement === undefined)
|
|
833
|
+
return false;
|
|
834
|
+
const measuredUsage = measurement.usage;
|
|
835
|
+
if (measuredUsage.currentTokens + measurement.observedInterRequestGrowth
|
|
836
|
+
>= measuredUsage.tokenLimit)
|
|
837
|
+
return false;
|
|
838
|
+
if (this.config.compactionMode !== 'adaptive')
|
|
839
|
+
return true;
|
|
840
|
+
return this.canDispatchParentRequest(measuredUsage);
|
|
841
|
+
}),
|
|
596
842
|
});
|
|
597
843
|
this._client = client;
|
|
598
844
|
// ---------------------------------------------------------------
|
|
@@ -617,14 +863,30 @@ class SdkSession {
|
|
|
617
863
|
else {
|
|
618
864
|
roster = this.backendRoster;
|
|
619
865
|
}
|
|
620
|
-
const {
|
|
866
|
+
const { reportedPromptTokenLimit, limitSource, } = await this.resolveModelCapabilities(client, this.config.model);
|
|
867
|
+
this.reportedPromptTokenLimit = reportedPromptTokenLimit;
|
|
868
|
+
this.reportedPromptTokenLimitSource = limitSource;
|
|
869
|
+
const subagentsEnabled = this.config.subagentsEnabled ?? this.backendSubagentsEnabled;
|
|
870
|
+
const permissionHandler = subagentsEnabled
|
|
871
|
+
? approveAll
|
|
872
|
+
: (request, invocation) => {
|
|
873
|
+
const toolName = request.kind === 'custom-tool'
|
|
874
|
+
|| request.kind === 'mcp'
|
|
875
|
+
|| request.kind === 'hook'
|
|
876
|
+
? request.toolName
|
|
877
|
+
: undefined;
|
|
878
|
+
if (toolName === 'task') {
|
|
879
|
+
return {
|
|
880
|
+
kind: 'reject',
|
|
881
|
+
feedback: 'Model-issued sub-agent dispatch is disabled for this session.',
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
return approveAll(request, invocation);
|
|
885
|
+
};
|
|
621
886
|
const sessionConfig = {
|
|
622
887
|
model: this.config.model,
|
|
623
888
|
streaming: true,
|
|
624
|
-
|
|
625
|
-
// settings surface cannot carry a capability override to child sessions.
|
|
626
|
-
...(modelCapabilities !== undefined ? { modelCapabilities } : {}),
|
|
627
|
-
onPermissionRequest: approveAll,
|
|
889
|
+
onPermissionRequest: permissionHandler,
|
|
628
890
|
workingDirectory: this.config.cwd,
|
|
629
891
|
reasoningEffort: this.config.thinkingBudget,
|
|
630
892
|
...(this.config.contextTier !== undefined
|
|
@@ -639,9 +901,12 @@ class SdkSession {
|
|
|
639
901
|
...(this.config.availableTools !== undefined
|
|
640
902
|
? { availableTools: [...this.config.availableTools] }
|
|
641
903
|
: {}),
|
|
642
|
-
...(
|
|
643
|
-
|
|
644
|
-
|
|
904
|
+
...(() => {
|
|
905
|
+
const excludedTools = new Set(this.config.excludedTools ?? []);
|
|
906
|
+
if (!subagentsEnabled)
|
|
907
|
+
excludedTools.add('task');
|
|
908
|
+
return excludedTools.size > 0 ? { excludedTools: [...excludedTools] } : {};
|
|
909
|
+
})(),
|
|
645
910
|
...(this.config.customAgents !== undefined
|
|
646
911
|
? { customAgents: this.config.customAgents.map(toSdkCustomAgent) }
|
|
647
912
|
: {}),
|
|
@@ -661,15 +926,20 @@ class SdkSession {
|
|
|
661
926
|
// The SDK defaults this on, so every commit an agent composes carries a
|
|
662
927
|
// Co-authored-by trailer. Output should read as the repository owner's work.
|
|
663
928
|
coauthorEnabled: false,
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
929
|
+
// Stock documented settings are the default and do not override model
|
|
930
|
+
// capabilities. The reduced legacy thresholds remain available for
|
|
931
|
+
// comparative diagnostics; the measured adaptive controller is opt-in.
|
|
932
|
+
infiniteSessions: this.config.compactionMode === 'aggressive'
|
|
933
|
+
? {
|
|
934
|
+
enabled: true,
|
|
935
|
+
backgroundCompactionThreshold: 0.60,
|
|
936
|
+
bufferExhaustionThreshold: 0.75,
|
|
937
|
+
}
|
|
938
|
+
: {
|
|
939
|
+
enabled: true,
|
|
940
|
+
backgroundCompactionThreshold: 0.80,
|
|
941
|
+
bufferExhaustionThreshold: 0.95,
|
|
942
|
+
},
|
|
673
943
|
// Registered before createSession issues its RPC, closing the early-event
|
|
674
944
|
// gap for session.start and *_loaded events.
|
|
675
945
|
onEvent: (event) => this.handleEarlyEvent(normalizeSdkEvent(event)),
|
|
@@ -683,16 +953,23 @@ class SdkSession {
|
|
|
683
953
|
return;
|
|
684
954
|
}
|
|
685
955
|
this._sdkSession = sdkSession;
|
|
956
|
+
const maxDepth = this.config.maxDepth ?? this.backendMaxDepth;
|
|
957
|
+
const maxConcurrency = this.config.maxConcurrency ?? this.backendMaxConcurrency;
|
|
958
|
+
const subagentSettings = {
|
|
959
|
+
...(roster !== undefined && roster !== false ? { agents: roster } : {}),
|
|
960
|
+
...(maxDepth !== undefined ? { maxDepth } : {}),
|
|
961
|
+
...(maxConcurrency !== undefined ? { maxConcurrency } : {}),
|
|
962
|
+
};
|
|
686
963
|
// Apply the live override before any prompt can dispatch a subagent.
|
|
687
964
|
// This experimental RPC degrades safely if the installed CLI rejects it.
|
|
688
|
-
if (
|
|
965
|
+
if (Object.keys(subagentSettings).length > 0) {
|
|
689
966
|
try {
|
|
690
967
|
await sdkSession.rpc.tools.updateSubagentSettings({
|
|
691
|
-
subagents:
|
|
968
|
+
subagents: subagentSettings,
|
|
692
969
|
});
|
|
693
970
|
}
|
|
694
971
|
catch (err) {
|
|
695
|
-
this.logger.warn('Failed to apply Copilot subagent
|
|
972
|
+
this.logger.warn('Failed to apply Copilot subagent settings; using default settings', {
|
|
696
973
|
error: err instanceof Error ? err.message : String(err),
|
|
697
974
|
});
|
|
698
975
|
}
|
|
@@ -722,6 +999,9 @@ class SdkSession {
|
|
|
722
999
|
// ---------------------------------------------------------------
|
|
723
1000
|
// Send the prompt (fire-and-forget; events stream via handlers)
|
|
724
1001
|
// ---------------------------------------------------------------
|
|
1002
|
+
if (this.config.compactionMode === 'adaptive'
|
|
1003
|
+
&& !(await this.ensureCompactionHeadroom(sdkSession, 'pre-send')))
|
|
1004
|
+
return;
|
|
725
1005
|
await sdkSession.send({ prompt });
|
|
726
1006
|
}
|
|
727
1007
|
/**
|
|
@@ -874,7 +1154,7 @@ class SdkSession {
|
|
|
874
1154
|
});
|
|
875
1155
|
// --- Session idle (agent finished all work) ---
|
|
876
1156
|
sdkSession.on('session.idle', (e) => {
|
|
877
|
-
if (!isActive()
|
|
1157
|
+
if (!isActive())
|
|
878
1158
|
return;
|
|
879
1159
|
this.resetHeartbeat();
|
|
880
1160
|
if (!e.agentId)
|
|
@@ -882,7 +1162,7 @@ class SdkSession {
|
|
|
882
1162
|
});
|
|
883
1163
|
// --- task_complete → idle (some models fire this instead of session.idle) ---
|
|
884
1164
|
sdkSession.on('session.task_complete', (e) => {
|
|
885
|
-
if (!isActive()
|
|
1165
|
+
if (!isActive())
|
|
886
1166
|
return;
|
|
887
1167
|
this.resetHeartbeat();
|
|
888
1168
|
if (e.agentId)
|
|
@@ -904,13 +1184,11 @@ class SdkSession {
|
|
|
904
1184
|
// The CLI normally follows an abort event with session.idle. If that
|
|
905
1185
|
// terminal event is lost, fail quickly rather than waiting for heartbeat.
|
|
906
1186
|
sdkSession.on('abort', (e) => {
|
|
907
|
-
if (!isActive()
|
|
1187
|
+
if (!isActive())
|
|
908
1188
|
return;
|
|
909
1189
|
this.resetHeartbeat();
|
|
910
1190
|
if (this.logAgentScopedFailure(e))
|
|
911
1191
|
return;
|
|
912
|
-
if (this.intentionalAborts.delete(sdkSession))
|
|
913
|
-
return;
|
|
914
1192
|
if (this.abortGraceTimer)
|
|
915
1193
|
clearTimeout(this.abortGraceTimer);
|
|
916
1194
|
this.abortGraceTimer = setTimeout(() => {
|
|
@@ -963,7 +1241,34 @@ class SdkSession {
|
|
|
963
1241
|
Object.assign(error, { statusCode });
|
|
964
1242
|
if (errorCode !== undefined)
|
|
965
1243
|
Object.assign(error, { errorCode });
|
|
966
|
-
|
|
1244
|
+
const compactionFailed = data?.initiator === 'compaction'
|
|
1245
|
+
|| data?.source === 'compaction'
|
|
1246
|
+
|| this.compactionInProgress
|
|
1247
|
+
|| this.proactiveCompactionPromise !== undefined;
|
|
1248
|
+
this.fail(error, compactionFailed ? 'compaction.model.call_failure' : 'model.call_failure', {
|
|
1249
|
+
...data,
|
|
1250
|
+
...(compactionFailed ? { requestPurpose: 'compaction' } : {}),
|
|
1251
|
+
...(this.parentUsage !== undefined
|
|
1252
|
+
? {
|
|
1253
|
+
currentTokens: this.parentUsage.currentTokens,
|
|
1254
|
+
tokenLimit: this.parentUsage.tokenLimit,
|
|
1255
|
+
messagesLength: this.parentUsage.messagesLength,
|
|
1256
|
+
}
|
|
1257
|
+
: {}),
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
sdkSession.on('session.usage_info', (e) => {
|
|
1261
|
+
if (!isActive())
|
|
1262
|
+
return;
|
|
1263
|
+
this.handleContextUsage(sdkSession, e);
|
|
1264
|
+
});
|
|
1265
|
+
sdkSession.on('assistant.turn_retry', (e) => {
|
|
1266
|
+
if (!isActive())
|
|
1267
|
+
return;
|
|
1268
|
+
if (e.agentId)
|
|
1269
|
+
this.retryingAgentIds.add(e.agentId);
|
|
1270
|
+
else
|
|
1271
|
+
this.retryingParent = true;
|
|
967
1272
|
});
|
|
968
1273
|
// --- Context compaction (infinite sessions) ---
|
|
969
1274
|
// During compaction the model goes silent. SUSPEND the heartbeat entirely
|
|
@@ -971,10 +1276,12 @@ class SdkSession {
|
|
|
971
1276
|
sdkSession.on('session.compaction_start', (e) => {
|
|
972
1277
|
if (!isActive())
|
|
973
1278
|
return;
|
|
974
|
-
this.resetHeartbeat();
|
|
975
1279
|
if (e.agentId)
|
|
976
1280
|
return;
|
|
1281
|
+
this.resetHeartbeat();
|
|
977
1282
|
this.compactionInProgress = true;
|
|
1283
|
+
this.parentCompactionGeneration += 1;
|
|
1284
|
+
this.compactionBaselinePromise = this.captureContextDiagnostics(sdkSession, 'pre-compaction');
|
|
978
1285
|
// SUSPEND heartbeat — compaction silence is expected
|
|
979
1286
|
if (this.heartbeatTimer) {
|
|
980
1287
|
clearTimeout(this.heartbeatTimer);
|
|
@@ -1005,82 +1312,71 @@ class SdkSession {
|
|
|
1005
1312
|
return;
|
|
1006
1313
|
// If force-compact works, compaction_complete will fire naturally
|
|
1007
1314
|
}
|
|
1008
|
-
catch {
|
|
1009
|
-
// Re-check guards after await
|
|
1315
|
+
catch (err) {
|
|
1010
1316
|
if (!isActive())
|
|
1011
1317
|
return;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
clearTimeout(this.abortGraceTimer);
|
|
1030
|
-
this.abortGraceTimer = null;
|
|
1031
|
-
}
|
|
1032
|
-
// WeakSet entries are removed when the delayed intentional abort event
|
|
1033
|
-
// arrives. The handle itself becomes collectible after session cleanup.
|
|
1034
|
-
// Wait briefly for the abort to settle before re-sending.
|
|
1035
|
-
// The SDK resolves abort() on acknowledgement, not on idle.
|
|
1036
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1037
|
-
if (!isActive())
|
|
1038
|
-
return;
|
|
1039
|
-
await session.send({ prompt: 'Continue from where you left off.' });
|
|
1040
|
-
this.compactionRecoveryInProgress = false;
|
|
1041
|
-
this.resetHeartbeat();
|
|
1042
|
-
}
|
|
1043
|
-
catch {
|
|
1044
|
-
this.compactionRecoveryInProgress = false;
|
|
1045
|
-
try {
|
|
1046
|
-
process.stderr.write('[SdkBackend] Recovery failed — restarting heartbeat as safety net\n');
|
|
1047
|
-
}
|
|
1048
|
-
catch { /* */ }
|
|
1049
|
-
this.resetHeartbeat(); // Detect if model is truly dead instead of waiting for hours-long hard timeout
|
|
1050
|
-
}
|
|
1318
|
+
const usage = this.parentUsage;
|
|
1319
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1320
|
+
const fields = {
|
|
1321
|
+
requestPurpose: 'compaction',
|
|
1322
|
+
error: detail,
|
|
1323
|
+
...(usage !== undefined
|
|
1324
|
+
? {
|
|
1325
|
+
currentTokens: usage.currentTokens,
|
|
1326
|
+
tokenLimit: usage.tokenLimit,
|
|
1327
|
+
messagesLength: usage.messagesLength,
|
|
1328
|
+
systemTokens: usage.systemTokens,
|
|
1329
|
+
conversationTokens: usage.conversationTokens,
|
|
1330
|
+
toolDefinitionsTokens: usage.toolDefinitionsTokens,
|
|
1331
|
+
}
|
|
1332
|
+
: {}),
|
|
1333
|
+
};
|
|
1334
|
+
this.fail(new Error(`Compaction recovery request failed: ${detail}`), 'compaction.recovery_failure', fields);
|
|
1051
1335
|
}
|
|
1052
1336
|
}, 3 * 60 * 1000);
|
|
1053
1337
|
});
|
|
1054
1338
|
sdkSession.on('session.compaction_complete', (e) => {
|
|
1055
1339
|
if (!isActive())
|
|
1056
1340
|
return;
|
|
1057
|
-
if (e.agentId)
|
|
1058
|
-
this.resetHeartbeat();
|
|
1341
|
+
if (e.agentId)
|
|
1059
1342
|
return;
|
|
1060
|
-
}
|
|
1061
1343
|
this.compactionInProgress = false;
|
|
1062
1344
|
if (this.compactionTimer) {
|
|
1063
1345
|
clearTimeout(this.compactionTimer);
|
|
1064
1346
|
this.compactionTimer = null;
|
|
1065
1347
|
}
|
|
1066
|
-
// Restart heartbeat
|
|
1348
|
+
// Restart heartbeat while the asynchronous verification RPC runs. Any
|
|
1349
|
+
// ordinary parent request remains guarded at the request-handler seam.
|
|
1067
1350
|
this.resetHeartbeat();
|
|
1068
1351
|
const data = e.data;
|
|
1069
|
-
// Check if compaction actually succeeded
|
|
1070
1352
|
if (data?.success === false) {
|
|
1071
1353
|
const errMsg = typeof data.error === 'string' ? data.error : 'unknown reason';
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1354
|
+
const rawStatusCode = data.statusCode;
|
|
1355
|
+
const statusCode = typeof rawStatusCode === 'number'
|
|
1356
|
+
? ` (HTTP ${rawStatusCode})`
|
|
1357
|
+
: '';
|
|
1358
|
+
const usage = this.parentUsage;
|
|
1359
|
+
const fields = {
|
|
1360
|
+
...data,
|
|
1361
|
+
requestPurpose: 'compaction',
|
|
1362
|
+
...(usage !== undefined
|
|
1363
|
+
? {
|
|
1364
|
+
currentTokens: usage.currentTokens,
|
|
1365
|
+
tokenLimit: usage.tokenLimit,
|
|
1366
|
+
messagesLength: usage.messagesLength,
|
|
1367
|
+
systemTokens: usage.systemTokens,
|
|
1368
|
+
conversationTokens: usage.conversationTokens,
|
|
1369
|
+
toolDefinitionsTokens: usage.toolDefinitionsTokens,
|
|
1370
|
+
}
|
|
1371
|
+
: {}),
|
|
1372
|
+
};
|
|
1373
|
+
this.fail(new Error(`Compaction model call failed${statusCode}: ${errMsg}`), 'compaction.call_failure', fields);
|
|
1374
|
+
return;
|
|
1076
1375
|
}
|
|
1077
|
-
const
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
? `${pre} → ${post} tokens${removed != null ? ` (saved ${removed})` : ''}`
|
|
1082
|
-
: '';
|
|
1083
|
-
this.emit('compaction', 'complete', summary);
|
|
1376
|
+
const generation = this.parentCompactionGeneration;
|
|
1377
|
+
const baselinePromise = this.compactionBaselinePromise;
|
|
1378
|
+
this.compactionBaselinePromise = undefined;
|
|
1379
|
+
void this.verifyCompletedCompaction(sdkSession, data, generation, baselinePromise);
|
|
1084
1380
|
});
|
|
1085
1381
|
// --- Subagent lifecycle ---
|
|
1086
1382
|
// Sub-agents use their own event path (subagent_start/subagent_end).
|
|
@@ -1101,7 +1397,18 @@ class SdkSession {
|
|
|
1101
1397
|
this.resetHeartbeat();
|
|
1102
1398
|
const data = e.data;
|
|
1103
1399
|
const name = String(data?.agentDisplayName ?? data?.agentName ?? 'agent');
|
|
1400
|
+
const agentName = String(data?.agentName ?? name);
|
|
1401
|
+
const model = typeof data?.model === 'string' ? data.model : undefined;
|
|
1402
|
+
const totalTokens = nonNegativeFiniteNumber(data?.totalTokens);
|
|
1104
1403
|
const toolCallId = typeof data?.toolCallId === 'string' ? data.toolCallId : '';
|
|
1404
|
+
const fields = {
|
|
1405
|
+
agentName,
|
|
1406
|
+
model,
|
|
1407
|
+
cumulativeTokensConsumed: totalTokens,
|
|
1408
|
+
totalToolCalls: nonNegativeFiniteNumber(data?.totalToolCalls),
|
|
1409
|
+
durationMs: nonNegativeFiniteNumber(data?.durationMs),
|
|
1410
|
+
};
|
|
1411
|
+
this.logger.info('Copilot sub-agent cumulative token consumption', fields);
|
|
1105
1412
|
this.emit('subagent_end', name, { ...data, toolCallId });
|
|
1106
1413
|
});
|
|
1107
1414
|
sdkSession.on('subagent.failed', (e) => {
|
|
@@ -1265,6 +1572,10 @@ class SdkSession {
|
|
|
1265
1572
|
// Only the pre-handle creation window is handled here.
|
|
1266
1573
|
if (this._sdkSession)
|
|
1267
1574
|
return;
|
|
1575
|
+
if (e.type === 'session.usage_info') {
|
|
1576
|
+
this.handleContextUsage(undefined, e);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1268
1579
|
const eventClass = (0, lifecycle_events_1.classifySdkEvent)(e.type);
|
|
1269
1580
|
if (eventClass === 'informational')
|
|
1270
1581
|
return;
|
|
@@ -1350,6 +1661,409 @@ class SdkSession {
|
|
|
1350
1661
|
|| typeof data.error === 'string'
|
|
1351
1662
|
|| (data.error != null && typeof data.error === 'object');
|
|
1352
1663
|
}
|
|
1664
|
+
canDispatchParentRequest(usage) {
|
|
1665
|
+
const policy = this.adaptiveCompactionPolicy;
|
|
1666
|
+
if (usage === undefined || policy === undefined) {
|
|
1667
|
+
if (usage !== undefined) {
|
|
1668
|
+
this.failUnsafeContext('pre-send', usage, policy, 0, 'fresh parent context accounting was unavailable at the request-handler seam');
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
this.fail(new Error('Blocked parent model request: fresh context accounting was unavailable'), 'pre-send-context-accounting');
|
|
1672
|
+
}
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
if (usage.currentTokens < policy.threshold)
|
|
1676
|
+
return true;
|
|
1677
|
+
this.failUnsafeContext('pre-send', usage, policy, 0, 'the runtime attempted a parent model request before the required adaptive headroom was restored');
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
async captureContextDiagnostics(sdkSession, reason) {
|
|
1681
|
+
try {
|
|
1682
|
+
const [attributionResult, heaviestMessages, recomputed, usageMetrics] = await Promise.all([
|
|
1683
|
+
sdkSession.rpc.metadata.getContextAttribution(),
|
|
1684
|
+
sdkSession.rpc.metadata.getContextHeaviestMessages({}),
|
|
1685
|
+
sdkSession.rpc.metadata.recomputeContextTokens({ modelId: this.config.model }),
|
|
1686
|
+
sdkSession.rpc.usage.getMetrics(),
|
|
1687
|
+
]);
|
|
1688
|
+
const attribution = attributionResult.contextAttribution ?? null;
|
|
1689
|
+
const fixedPromptOverhead = attribution
|
|
1690
|
+
? Math.max(0, attribution.totalTokens - recomputed.messagesTokenCount)
|
|
1691
|
+
: recomputed.systemTokenCount;
|
|
1692
|
+
const diagnostics = {
|
|
1693
|
+
attribution,
|
|
1694
|
+
heaviestMessages,
|
|
1695
|
+
recomputed,
|
|
1696
|
+
usageMetrics,
|
|
1697
|
+
fixedPromptOverhead,
|
|
1698
|
+
};
|
|
1699
|
+
this.logger.info('Captured Copilot parent context diagnostics', {
|
|
1700
|
+
reason,
|
|
1701
|
+
successfulCompactions: attribution?.compactions.count,
|
|
1702
|
+
fixedPromptOverhead,
|
|
1703
|
+
contextAttribution: attribution,
|
|
1704
|
+
heaviestMessages,
|
|
1705
|
+
recomputedContextTokens: recomputed,
|
|
1706
|
+
usageMetrics,
|
|
1707
|
+
});
|
|
1708
|
+
return diagnostics;
|
|
1709
|
+
}
|
|
1710
|
+
catch (err) {
|
|
1711
|
+
this.logger.warn('Failed to capture Copilot parent context diagnostics', {
|
|
1712
|
+
reason,
|
|
1713
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1714
|
+
});
|
|
1715
|
+
return undefined;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
async measureParentUsage(sdkSession, reason) {
|
|
1719
|
+
const previous = this.parentUsage;
|
|
1720
|
+
try {
|
|
1721
|
+
const [contextResult, diagnostics] = await Promise.all([
|
|
1722
|
+
sdkSession.rpc.metadata.contextInfo({
|
|
1723
|
+
promptTokenLimit: previous?.tokenLimit ?? this.reportedPromptTokenLimit ?? 0,
|
|
1724
|
+
outputTokenLimit: 0,
|
|
1725
|
+
selectedModel: this.config.model,
|
|
1726
|
+
}),
|
|
1727
|
+
this.captureContextDiagnostics(sdkSession, reason),
|
|
1728
|
+
]);
|
|
1729
|
+
const contextInfo = contextResult.contextInfo;
|
|
1730
|
+
if (!contextInfo || !diagnostics) {
|
|
1731
|
+
this.logger.warn('Copilot parent context measurement returned incomplete context information', {
|
|
1732
|
+
reason,
|
|
1733
|
+
previousCurrentTokens: previous?.currentTokens,
|
|
1734
|
+
hasContextInfo: contextInfo !== null && contextInfo !== undefined,
|
|
1735
|
+
hasDiagnostics: diagnostics !== undefined,
|
|
1736
|
+
});
|
|
1737
|
+
return undefined;
|
|
1738
|
+
}
|
|
1739
|
+
const completeDiagnostics = diagnostics;
|
|
1740
|
+
this.latestContextDiagnostics = completeDiagnostics;
|
|
1741
|
+
const usage = {
|
|
1742
|
+
currentTokens: diagnostics.attribution?.totalTokens ?? diagnostics.recomputed.totalTokens,
|
|
1743
|
+
tokenLimit: contextInfo.promptTokenLimit,
|
|
1744
|
+
messagesLength: previous?.messagesLength ?? 0,
|
|
1745
|
+
systemTokens: diagnostics.recomputed.systemTokenCount,
|
|
1746
|
+
conversationTokens: diagnostics.recomputed.messagesTokenCount,
|
|
1747
|
+
toolDefinitionsTokens: contextInfo.toolDefinitionsTokens,
|
|
1748
|
+
};
|
|
1749
|
+
this.parentUsage = usage;
|
|
1750
|
+
this.updateAdaptiveCompactionPolicy(usage, completeDiagnostics);
|
|
1751
|
+
this.logger.info('Measured Copilot parent context', {
|
|
1752
|
+
reason,
|
|
1753
|
+
...usage,
|
|
1754
|
+
successfulCompactions: diagnostics.attribution?.compactions.count,
|
|
1755
|
+
fixedPromptOverhead: diagnostics.fixedPromptOverhead,
|
|
1756
|
+
contextAttribution: diagnostics.attribution,
|
|
1757
|
+
heaviestMessages: diagnostics.heaviestMessages,
|
|
1758
|
+
recomputedContextTokens: diagnostics.recomputed,
|
|
1759
|
+
usageMetrics: diagnostics.usageMetrics,
|
|
1760
|
+
...this.adaptivePolicyFields(),
|
|
1761
|
+
});
|
|
1762
|
+
return usage;
|
|
1763
|
+
}
|
|
1764
|
+
catch (err) {
|
|
1765
|
+
this.logger.warn('Failed to measure Copilot parent context', {
|
|
1766
|
+
reason,
|
|
1767
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1768
|
+
previousCurrentTokens: previous?.currentTokens,
|
|
1769
|
+
});
|
|
1770
|
+
return undefined;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
shouldCaptureRequestDiagnostics(purpose) {
|
|
1774
|
+
if (purpose === 'compaction')
|
|
1775
|
+
return false;
|
|
1776
|
+
const usage = this.parentUsage;
|
|
1777
|
+
if (!usage)
|
|
1778
|
+
return true;
|
|
1779
|
+
const contextInfoThreshold = this.config.compactionMode === 'aggressive'
|
|
1780
|
+
? usage.tokenLimit * 0.60
|
|
1781
|
+
: usage.tokenLimit * 0.80;
|
|
1782
|
+
return usage.currentTokens >= contextInfoThreshold;
|
|
1783
|
+
}
|
|
1784
|
+
async refreshParentUsageBeforeRequest(sdkSession, purpose) {
|
|
1785
|
+
const usage = await this.measureParentUsage(sdkSession, 'pre-request');
|
|
1786
|
+
if (!usage)
|
|
1787
|
+
return undefined;
|
|
1788
|
+
const previousRequestTokens = this.previousParentRequestTokens;
|
|
1789
|
+
const observedInterRequestGrowth = previousRequestTokens === undefined
|
|
1790
|
+
? 0
|
|
1791
|
+
: Math.max(0, usage.currentTokens - previousRequestTokens);
|
|
1792
|
+
if (observedInterRequestGrowth > this.largestObservedInterRequestGrowth) {
|
|
1793
|
+
this.largestObservedInterRequestGrowth = observedInterRequestGrowth;
|
|
1794
|
+
if (this.latestContextDiagnostics) {
|
|
1795
|
+
this.updateAdaptiveCompactionPolicy(usage);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
this.previousParentRequestTokens = usage.currentTokens;
|
|
1799
|
+
this.logger.info('Refreshed Copilot parent context before model request', {
|
|
1800
|
+
purpose,
|
|
1801
|
+
...usage,
|
|
1802
|
+
previousRequestTokens,
|
|
1803
|
+
...(previousRequestTokens === undefined ? {} : { observedInterRequestGrowth }),
|
|
1804
|
+
...this.adaptivePolicyFields(),
|
|
1805
|
+
});
|
|
1806
|
+
return { usage, observedInterRequestGrowth };
|
|
1807
|
+
}
|
|
1808
|
+
updateAdaptiveCompactionPolicy(usage, diagnostics = this.latestContextDiagnostics) {
|
|
1809
|
+
const reportedLimit = this.reportedPromptTokenLimit;
|
|
1810
|
+
const tokenLimit = reportedLimit === undefined
|
|
1811
|
+
? usage.tokenLimit
|
|
1812
|
+
: Math.min(reportedLimit, usage.tokenLimit);
|
|
1813
|
+
if (!isPositiveFiniteNumber(tokenLimit)) {
|
|
1814
|
+
this.adaptiveCompactionPolicy = undefined;
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
// Bootstrap has no tuned token seed. Prefer the SDK's source attribution so
|
|
1818
|
+
// fixed context costs are separated from conversation growth. Before that
|
|
1819
|
+
// diagnostic surface initializes, reserve the first exact recomputation.
|
|
1820
|
+
const fixedPromptOverhead = diagnostics?.fixedPromptOverhead ?? 0;
|
|
1821
|
+
const existingPolicy = this.adaptiveCompactionPolicy;
|
|
1822
|
+
const bootstrapSource = existingPolicy?.bootstrapSource
|
|
1823
|
+
?? (fixedPromptOverhead > 0 ? 'context-attribution' : 'first-recomputed-request');
|
|
1824
|
+
const initialBootstrapHeadroom = fixedPromptOverhead > 0
|
|
1825
|
+
? fixedPromptOverhead
|
|
1826
|
+
: diagnostics?.recomputed.totalTokens ?? usage.currentTokens;
|
|
1827
|
+
const bootstrapHeadroom = Math.max(existingPolicy?.bootstrapHeadroom ?? 0, initialBootstrapHeadroom);
|
|
1828
|
+
const headroom = bootstrapHeadroom + this.largestObservedInterRequestGrowth;
|
|
1829
|
+
this.adaptiveCompactionPolicy = {
|
|
1830
|
+
tokenLimit,
|
|
1831
|
+
threshold: Math.max(0, tokenLimit - headroom),
|
|
1832
|
+
headroom,
|
|
1833
|
+
bootstrapHeadroom,
|
|
1834
|
+
bootstrapSource,
|
|
1835
|
+
largestObservedInterRequestGrowth: this.largestObservedInterRequestGrowth,
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
adaptivePolicyFields() {
|
|
1839
|
+
const policy = this.adaptiveCompactionPolicy;
|
|
1840
|
+
return {
|
|
1841
|
+
reportedPromptTokenLimit: this.reportedPromptTokenLimit,
|
|
1842
|
+
reportedPromptTokenLimitSource: this.reportedPromptTokenLimitSource,
|
|
1843
|
+
adaptiveCompactionThreshold: policy?.threshold,
|
|
1844
|
+
adaptiveCompactionHeadroom: policy?.headroom,
|
|
1845
|
+
adaptiveBootstrapHeadroom: policy?.bootstrapHeadroom,
|
|
1846
|
+
adaptiveBootstrapSource: policy?.bootstrapSource,
|
|
1847
|
+
largestObservedInterRequestGrowth: policy?.largestObservedInterRequestGrowth
|
|
1848
|
+
?? this.largestObservedInterRequestGrowth,
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
logModelRequest(telemetry) {
|
|
1852
|
+
const fields = {
|
|
1853
|
+
...telemetry,
|
|
1854
|
+
...this.adaptivePolicyFields(),
|
|
1855
|
+
};
|
|
1856
|
+
this.logger.info('Copilot model request dispatch', fields);
|
|
1857
|
+
try {
|
|
1858
|
+
process.stderr.write(`[SdkBackend] MODEL REQUEST requestId=${telemetry.requestId} sessionId=${telemetry.sessionId ?? 'unknown'} agentId=${telemetry.agentId ?? 'parent'} parentAgentId=${telemetry.parentAgentId ?? 'none'} purpose=${telemetry.purpose} interactionType=${telemetry.interactionType ?? 'unknown'} currentTokens=${telemetry.currentTokens ?? 'unknown'} tokenLimit=${telemetry.tokenLimit ?? 'unknown'} messagesLength=${telemetry.messagesLength ?? 'unknown'} systemTokens=${telemetry.systemTokens ?? 'unknown'} conversationTokens=${telemetry.conversationTokens ?? 'unknown'} toolDefinitionsTokens=${telemetry.toolDefinitionsTokens ?? 'unknown'} adaptiveHeadroom=${this.adaptiveCompactionPolicy?.headroom ?? 'unknown'} largestInterRequestGrowth=${this.largestObservedInterRequestGrowth} observedRequestBodyBytes=${telemetry.observedRequestBodyBytes ?? 'unknown'} observedRequestBodyTokens=${telemetry.observedRequestBodyTokens ?? 'unavailable'} observedRequestBodySha256=${telemetry.observedRequestBodySha256 ?? 'unknown'}\n`);
|
|
1859
|
+
}
|
|
1860
|
+
catch { /* closed stream */ }
|
|
1861
|
+
}
|
|
1862
|
+
handleContextUsage(sdkSession, e) {
|
|
1863
|
+
const usage = parseContextUsage(e.data);
|
|
1864
|
+
if (!usage) {
|
|
1865
|
+
this.logger.warn('Copilot context usage event was missing valid accounting fields', {
|
|
1866
|
+
agentId: e.agentId,
|
|
1867
|
+
data: e.data,
|
|
1868
|
+
});
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (e.agentId) {
|
|
1872
|
+
this.childUsage.set(e.agentId, usage);
|
|
1873
|
+
}
|
|
1874
|
+
else {
|
|
1875
|
+
this.parentUsage = usage;
|
|
1876
|
+
if (this.latestContextDiagnostics) {
|
|
1877
|
+
this.updateAdaptiveCompactionPolicy(usage);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const fields = {
|
|
1881
|
+
agentId: e.agentId,
|
|
1882
|
+
...usage,
|
|
1883
|
+
...(!e.agentId ? this.adaptivePolicyFields() : {}),
|
|
1884
|
+
};
|
|
1885
|
+
const scope = e.agentId ? `subagent:${e.agentId}` : 'parent';
|
|
1886
|
+
this.logger.info(e.agentId ? 'Copilot sub-agent context usage' : 'Copilot parent context usage', fields);
|
|
1887
|
+
try {
|
|
1888
|
+
process.stderr.write(`[SdkBackend] CONTEXT USAGE scope=${scope} currentTokens=${usage.currentTokens} tokenLimit=${usage.tokenLimit} messagesLength=${usage.messagesLength} systemTokens=${usage.systemTokens ?? 'unknown'} conversationTokens=${usage.conversationTokens ?? 'unknown'} toolDefinitionsTokens=${usage.toolDefinitionsTokens ?? 'unknown'} ceiling=${!e.agentId ? this.adaptiveCompactionPolicy?.threshold ?? 'unknown' : 'isolated'} headroom=${!e.agentId ? this.adaptiveCompactionPolicy?.headroom ?? 'unknown' : 'isolated'}\n`);
|
|
1889
|
+
}
|
|
1890
|
+
catch { /* closed stream */ }
|
|
1891
|
+
if (e.agentId)
|
|
1892
|
+
return;
|
|
1893
|
+
const policy = this.adaptiveCompactionPolicy;
|
|
1894
|
+
if (this.config.compactionMode === 'adaptive'
|
|
1895
|
+
&& sdkSession
|
|
1896
|
+
&& policy
|
|
1897
|
+
&& usage.currentTokens >= policy.threshold) {
|
|
1898
|
+
void this.ensureCompactionHeadroom(sdkSession, 'usage-threshold');
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
ensureCompactionHeadroom(sdkSession, source) {
|
|
1902
|
+
if (this.proactiveCompactionPromise)
|
|
1903
|
+
return this.proactiveCompactionPromise;
|
|
1904
|
+
const policy = this.adaptiveCompactionPolicy;
|
|
1905
|
+
const usage = this.parentUsage;
|
|
1906
|
+
if (policy === undefined
|
|
1907
|
+
|| usage === undefined
|
|
1908
|
+
|| usage.currentTokens < policy.threshold) {
|
|
1909
|
+
return Promise.resolve(true);
|
|
1910
|
+
}
|
|
1911
|
+
const compaction = this.compactForHeadroom(sdkSession, source, usage, policy);
|
|
1912
|
+
this.proactiveCompactionPromise = compaction;
|
|
1913
|
+
void compaction.then(() => {
|
|
1914
|
+
if (this.proactiveCompactionPromise === compaction) {
|
|
1915
|
+
this.proactiveCompactionPromise = undefined;
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
return compaction;
|
|
1919
|
+
}
|
|
1920
|
+
async compactForHeadroom(sdkSession, source, before, policy) {
|
|
1921
|
+
if (this.aborted || this.turnSettled || this._sdkSession !== sdkSession)
|
|
1922
|
+
return false;
|
|
1923
|
+
if (before.messagesLength < MIN_MESSAGES_FOR_COMPACTION) {
|
|
1924
|
+
return this.failUnsafeContext(source, before, policy, 0, `only ${before.messagesLength} messages are present; the CLI requires ${MIN_MESSAGES_FOR_COMPACTION}, so the latest oversized context addition cannot be compacted`);
|
|
1925
|
+
}
|
|
1926
|
+
this.logger.warn('Copilot parent context crossed the adaptive compaction ceiling', {
|
|
1927
|
+
source,
|
|
1928
|
+
...before,
|
|
1929
|
+
...this.adaptivePolicyFields(),
|
|
1930
|
+
});
|
|
1931
|
+
try {
|
|
1932
|
+
const beforeDiagnostics = await this.captureContextDiagnostics(sdkSession, 'pre-compaction');
|
|
1933
|
+
const result = await sdkSession.rpc.history.compact();
|
|
1934
|
+
if (this.aborted || this.turnSettled || this._sdkSession !== sdkSession)
|
|
1935
|
+
return false;
|
|
1936
|
+
const afterUsage = await this.measureParentUsage(sdkSession, 'post-compaction');
|
|
1937
|
+
const afterDiagnostics = this.latestContextDiagnostics;
|
|
1938
|
+
if (!result.success || !beforeDiagnostics || !afterUsage || !afterDiagnostics) {
|
|
1939
|
+
return this.failUnsafeContext(source, afterUsage ?? before, policy, result.tokensRemoved, result.success
|
|
1940
|
+
? 'the compaction result could not be verified with exact token recomputation and attribution counts'
|
|
1941
|
+
: 'the history compaction RPC reported failure');
|
|
1942
|
+
}
|
|
1943
|
+
const beforeCompactions = beforeDiagnostics.attribution?.compactions.count;
|
|
1944
|
+
const afterCompactions = afterDiagnostics.attribution?.compactions.count;
|
|
1945
|
+
const afterPolicy = this.adaptiveCompactionPolicy ?? policy;
|
|
1946
|
+
this.logger.info('Verified Copilot parent context compaction', {
|
|
1947
|
+
source,
|
|
1948
|
+
beforeTokens: beforeDiagnostics.recomputed.totalTokens,
|
|
1949
|
+
afterTokens: afterDiagnostics.recomputed.totalTokens,
|
|
1950
|
+
beforeSuccessfulCompactions: beforeCompactions,
|
|
1951
|
+
afterSuccessfulCompactions: afterCompactions,
|
|
1952
|
+
tokensRemoved: result.tokensRemoved,
|
|
1953
|
+
messagesRemoved: result.messagesRemoved,
|
|
1954
|
+
messagesLength: afterUsage.messagesLength,
|
|
1955
|
+
contextAttribution: afterDiagnostics.attribution,
|
|
1956
|
+
heaviestMessages: afterDiagnostics.heaviestMessages,
|
|
1957
|
+
usageMetrics: afterDiagnostics.usageMetrics,
|
|
1958
|
+
...this.adaptivePolicyFields(),
|
|
1959
|
+
});
|
|
1960
|
+
if (beforeCompactions === undefined
|
|
1961
|
+
|| afterCompactions === undefined
|
|
1962
|
+
|| afterCompactions <= beforeCompactions) {
|
|
1963
|
+
return this.failUnsafeContext(source, afterUsage, afterPolicy, result.tokensRemoved, 'the successful compaction count did not advance');
|
|
1964
|
+
}
|
|
1965
|
+
if (afterDiagnostics.recomputed.totalTokens >= beforeDiagnostics.recomputed.totalTokens) {
|
|
1966
|
+
return this.failUnsafeContext(source, afterUsage, afterPolicy, result.tokensRemoved, 'exact token recomputation did not decrease after compaction; automatic truncation is unsafe without a caller-approved history boundary');
|
|
1967
|
+
}
|
|
1968
|
+
if (afterUsage.currentTokens >= afterPolicy.threshold) {
|
|
1969
|
+
return this.failUnsafeContext(source, afterUsage, afterPolicy, result.tokensRemoved, 'verified compaction reduced the context but did not restore the required adaptive headroom; automatic truncation is unsafe without a caller-approved history boundary');
|
|
1970
|
+
}
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
catch (err) {
|
|
1974
|
+
if (this.aborted || this.turnSettled || this._sdkSession !== sdkSession)
|
|
1975
|
+
return false;
|
|
1976
|
+
return this.failUnsafeContext(source, before, policy, 0, `history compaction RPC failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
async verifyCompletedCompaction(sdkSession, data, generation, baselinePromise) {
|
|
1980
|
+
const beforeUsage = this.parentUsage;
|
|
1981
|
+
const beforeDiagnostics = await baselinePromise;
|
|
1982
|
+
const after = await this.measureParentUsage(sdkSession, 'post-compaction');
|
|
1983
|
+
if (this.aborted
|
|
1984
|
+
|| this.turnSettled
|
|
1985
|
+
|| this._sdkSession !== sdkSession
|
|
1986
|
+
|| generation !== this.parentCompactionGeneration)
|
|
1987
|
+
return;
|
|
1988
|
+
const afterDiagnostics = this.latestContextDiagnostics;
|
|
1989
|
+
const policy = this.adaptiveCompactionPolicy;
|
|
1990
|
+
const beforeCompactions = beforeDiagnostics?.attribution?.compactions.count;
|
|
1991
|
+
const afterCompactions = afterDiagnostics?.attribution?.compactions.count;
|
|
1992
|
+
if (!beforeUsage
|
|
1993
|
+
|| !after
|
|
1994
|
+
|| !beforeDiagnostics
|
|
1995
|
+
|| !afterDiagnostics
|
|
1996
|
+
|| beforeCompactions === undefined
|
|
1997
|
+
|| afterCompactions === undefined) {
|
|
1998
|
+
const usage = after ?? beforeUsage;
|
|
1999
|
+
if (usage) {
|
|
2000
|
+
this.failUnsafeContext('compaction-complete', usage, policy, nonNegativeFiniteNumber(data?.tokensRemoved) ?? 0, 'successful compaction could not be verified with exact token recomputation and attribution counts');
|
|
2001
|
+
}
|
|
2002
|
+
else {
|
|
2003
|
+
this.fail(new Error('Successful compaction could not be re-measured'), 'compaction.verification_failure', data);
|
|
2004
|
+
}
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (afterCompactions <= beforeCompactions) {
|
|
2008
|
+
this.failUnsafeContext('compaction-complete', after, policy, nonNegativeFiniteNumber(data?.tokensRemoved) ?? 0, 'the successful compaction count did not advance');
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (afterDiagnostics.recomputed.totalTokens >= beforeDiagnostics.recomputed.totalTokens) {
|
|
2012
|
+
this.failUnsafeContext('compaction-complete', after, policy, nonNegativeFiniteNumber(data?.tokensRemoved) ?? 0, 'exact token recomputation did not decrease after compaction');
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (this.config.compactionMode === 'adaptive'
|
|
2016
|
+
&& policy
|
|
2017
|
+
&& after.currentTokens >= policy.threshold) {
|
|
2018
|
+
this.failUnsafeContext('compaction-complete', after, policy, nonNegativeFiniteNumber(data?.tokensRemoved) ?? 0, 'successful compaction did not restore the required adaptive headroom');
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const pre = nonNegativeFiniteNumber(data?.preCompactionTokens);
|
|
2022
|
+
const post = nonNegativeFiniteNumber(data?.postCompactionTokens);
|
|
2023
|
+
const removed = nonNegativeFiniteNumber(data?.tokensRemoved);
|
|
2024
|
+
this.logger.info('Verified completed Copilot parent compaction', {
|
|
2025
|
+
eventPreCompactionTokens: pre,
|
|
2026
|
+
eventPostCompactionTokens: post,
|
|
2027
|
+
measuredPreCompactionTokens: beforeDiagnostics.recomputed.totalTokens,
|
|
2028
|
+
measuredPostCompactionTokens: afterDiagnostics.recomputed.totalTokens,
|
|
2029
|
+
beforeSuccessfulCompactions: beforeCompactions,
|
|
2030
|
+
afterSuccessfulCompactions: afterCompactions,
|
|
2031
|
+
tokensRemoved: removed,
|
|
2032
|
+
contextAttribution: afterDiagnostics.attribution,
|
|
2033
|
+
heaviestMessages: afterDiagnostics.heaviestMessages,
|
|
2034
|
+
...this.adaptivePolicyFields(),
|
|
2035
|
+
});
|
|
2036
|
+
const summary = `${beforeDiagnostics.recomputed.totalTokens} → ${afterDiagnostics.recomputed.totalTokens} exact tokens (compactions ${beforeCompactions} → ${afterCompactions})`;
|
|
2037
|
+
this.verifiedParentCompactionGeneration = generation;
|
|
2038
|
+
this.emit('compaction', 'complete', summary);
|
|
2039
|
+
}
|
|
2040
|
+
failUnsafeContext(source, usage, policy, tokensRemoved, reason) {
|
|
2041
|
+
const fields = {
|
|
2042
|
+
source,
|
|
2043
|
+
currentTokens: usage.currentTokens,
|
|
2044
|
+
tokenLimit: policy?.tokenLimit ?? usage.tokenLimit,
|
|
2045
|
+
adaptiveCompactionThreshold: policy?.threshold,
|
|
2046
|
+
adaptiveCompactionHeadroom: policy?.headroom,
|
|
2047
|
+
adaptiveBootstrapHeadroom: policy?.bootstrapHeadroom,
|
|
2048
|
+
adaptiveBootstrapSource: policy?.bootstrapSource,
|
|
2049
|
+
largestObservedInterRequestGrowth: policy?.largestObservedInterRequestGrowth
|
|
2050
|
+
?? this.largestObservedInterRequestGrowth,
|
|
2051
|
+
tokensRemoved,
|
|
2052
|
+
messagesLength: usage.messagesLength,
|
|
2053
|
+
systemTokens: usage.systemTokens,
|
|
2054
|
+
conversationTokens: usage.conversationTokens,
|
|
2055
|
+
toolDefinitionsTokens: usage.toolDefinitionsTokens,
|
|
2056
|
+
reason,
|
|
2057
|
+
};
|
|
2058
|
+
const message = `Unsafe parent context: currentTokens=${usage.currentTokens}, tokenLimit=${fields.tokenLimit}, ceiling=${policy?.threshold ?? 'unknown'}, headroom=${policy?.headroom ?? 'unknown'}, tokensRemoved=${tokensRemoved}, messagesLength=${usage.messagesLength}; ${reason}`;
|
|
2059
|
+
this.logger.error('Copilot parent context could not be compacted safely', fields);
|
|
2060
|
+
try {
|
|
2061
|
+
process.stderr.write(`[SdkBackend] ADAPTIVE COMPACTION TERMINAL: ${message}\n`);
|
|
2062
|
+
}
|
|
2063
|
+
catch { /* closed stream */ }
|
|
2064
|
+
this.fail(new Error(message), 'adaptive-compaction', fields);
|
|
2065
|
+
return false;
|
|
2066
|
+
}
|
|
1353
2067
|
async resolvePendingRequest(sdkSession, e) {
|
|
1354
2068
|
if (this.aborted || this._sdkSession !== sdkSession)
|
|
1355
2069
|
return;
|
|
@@ -1417,7 +2131,10 @@ class SdkSession {
|
|
|
1417
2131
|
this.abortGraceTimer = null;
|
|
1418
2132
|
}
|
|
1419
2133
|
this.compactionInProgress = false;
|
|
1420
|
-
this.
|
|
2134
|
+
this.parentCompactionGeneration = 0;
|
|
2135
|
+
this.verifiedParentCompactionGeneration = 0;
|
|
2136
|
+
this.compactionBaselinePromise = undefined;
|
|
2137
|
+
this.proactiveCompactionPromise = undefined;
|
|
1421
2138
|
}
|
|
1422
2139
|
}
|
|
1423
2140
|
//# sourceMappingURL=sdk-backend.js.map
|