condukt 0.16.8 → 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 +845 -115
- 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 +5 -5
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +20 -15
- 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,22 +1162,33 @@ 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
|
-
if (
|
|
889
|
-
|
|
1168
|
+
if (e.agentId)
|
|
1169
|
+
return;
|
|
1170
|
+
if (e.data?.success === false) {
|
|
1171
|
+
const fields = {
|
|
1172
|
+
eventType: e.type ?? 'session.task_complete',
|
|
1173
|
+
...(typeof e.data.summary === 'string' ? { summary: e.data.summary } : {}),
|
|
1174
|
+
};
|
|
1175
|
+
this.logger.warn('Copilot task completion failed; session remains active', fields);
|
|
1176
|
+
try {
|
|
1177
|
+
process.stderr.write('[SdkBackend] TASK COMPLETION FAILED; session remains active\n');
|
|
1178
|
+
}
|
|
1179
|
+
catch { /* closed stream */ }
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
this.settleIdle();
|
|
890
1183
|
});
|
|
891
1184
|
// The CLI normally follows an abort event with session.idle. If that
|
|
892
1185
|
// terminal event is lost, fail quickly rather than waiting for heartbeat.
|
|
893
1186
|
sdkSession.on('abort', (e) => {
|
|
894
|
-
if (!isActive()
|
|
1187
|
+
if (!isActive())
|
|
895
1188
|
return;
|
|
896
1189
|
this.resetHeartbeat();
|
|
897
1190
|
if (this.logAgentScopedFailure(e))
|
|
898
1191
|
return;
|
|
899
|
-
if (this.intentionalAborts.delete(sdkSession))
|
|
900
|
-
return;
|
|
901
1192
|
if (this.abortGraceTimer)
|
|
902
1193
|
clearTimeout(this.abortGraceTimer);
|
|
903
1194
|
this.abortGraceTimer = setTimeout(() => {
|
|
@@ -950,7 +1241,34 @@ class SdkSession {
|
|
|
950
1241
|
Object.assign(error, { statusCode });
|
|
951
1242
|
if (errorCode !== undefined)
|
|
952
1243
|
Object.assign(error, { errorCode });
|
|
953
|
-
|
|
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;
|
|
954
1272
|
});
|
|
955
1273
|
// --- Context compaction (infinite sessions) ---
|
|
956
1274
|
// During compaction the model goes silent. SUSPEND the heartbeat entirely
|
|
@@ -958,10 +1276,12 @@ class SdkSession {
|
|
|
958
1276
|
sdkSession.on('session.compaction_start', (e) => {
|
|
959
1277
|
if (!isActive())
|
|
960
1278
|
return;
|
|
961
|
-
this.resetHeartbeat();
|
|
962
1279
|
if (e.agentId)
|
|
963
1280
|
return;
|
|
1281
|
+
this.resetHeartbeat();
|
|
964
1282
|
this.compactionInProgress = true;
|
|
1283
|
+
this.parentCompactionGeneration += 1;
|
|
1284
|
+
this.compactionBaselinePromise = this.captureContextDiagnostics(sdkSession, 'pre-compaction');
|
|
965
1285
|
// SUSPEND heartbeat — compaction silence is expected
|
|
966
1286
|
if (this.heartbeatTimer) {
|
|
967
1287
|
clearTimeout(this.heartbeatTimer);
|
|
@@ -992,82 +1312,71 @@ class SdkSession {
|
|
|
992
1312
|
return;
|
|
993
1313
|
// If force-compact works, compaction_complete will fire naturally
|
|
994
1314
|
}
|
|
995
|
-
catch {
|
|
996
|
-
// Re-check guards after await
|
|
1315
|
+
catch (err) {
|
|
997
1316
|
if (!isActive())
|
|
998
1317
|
return;
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
clearTimeout(this.abortGraceTimer);
|
|
1017
|
-
this.abortGraceTimer = null;
|
|
1018
|
-
}
|
|
1019
|
-
// WeakSet entries are removed when the delayed intentional abort event
|
|
1020
|
-
// arrives. The handle itself becomes collectible after session cleanup.
|
|
1021
|
-
// Wait briefly for the abort to settle before re-sending.
|
|
1022
|
-
// The SDK resolves abort() on acknowledgement, not on idle.
|
|
1023
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1024
|
-
if (!isActive())
|
|
1025
|
-
return;
|
|
1026
|
-
await session.send({ prompt: 'Continue from where you left off.' });
|
|
1027
|
-
this.compactionRecoveryInProgress = false;
|
|
1028
|
-
this.resetHeartbeat();
|
|
1029
|
-
}
|
|
1030
|
-
catch {
|
|
1031
|
-
this.compactionRecoveryInProgress = false;
|
|
1032
|
-
try {
|
|
1033
|
-
process.stderr.write('[SdkBackend] Recovery failed — restarting heartbeat as safety net\n');
|
|
1034
|
-
}
|
|
1035
|
-
catch { /* */ }
|
|
1036
|
-
this.resetHeartbeat(); // Detect if model is truly dead instead of waiting for hours-long hard timeout
|
|
1037
|
-
}
|
|
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);
|
|
1038
1335
|
}
|
|
1039
1336
|
}, 3 * 60 * 1000);
|
|
1040
1337
|
});
|
|
1041
1338
|
sdkSession.on('session.compaction_complete', (e) => {
|
|
1042
1339
|
if (!isActive())
|
|
1043
1340
|
return;
|
|
1044
|
-
if (e.agentId)
|
|
1045
|
-
this.resetHeartbeat();
|
|
1341
|
+
if (e.agentId)
|
|
1046
1342
|
return;
|
|
1047
|
-
}
|
|
1048
1343
|
this.compactionInProgress = false;
|
|
1049
1344
|
if (this.compactionTimer) {
|
|
1050
1345
|
clearTimeout(this.compactionTimer);
|
|
1051
1346
|
this.compactionTimer = null;
|
|
1052
1347
|
}
|
|
1053
|
-
// Restart heartbeat
|
|
1348
|
+
// Restart heartbeat while the asynchronous verification RPC runs. Any
|
|
1349
|
+
// ordinary parent request remains guarded at the request-handler seam.
|
|
1054
1350
|
this.resetHeartbeat();
|
|
1055
1351
|
const data = e.data;
|
|
1056
|
-
// Check if compaction actually succeeded
|
|
1057
1352
|
if (data?.success === false) {
|
|
1058
1353
|
const errMsg = typeof data.error === 'string' ? data.error : 'unknown reason';
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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;
|
|
1063
1375
|
}
|
|
1064
|
-
const
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
? `${pre} → ${post} tokens${removed != null ? ` (saved ${removed})` : ''}`
|
|
1069
|
-
: '';
|
|
1070
|
-
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);
|
|
1071
1380
|
});
|
|
1072
1381
|
// --- Subagent lifecycle ---
|
|
1073
1382
|
// Sub-agents use their own event path (subagent_start/subagent_end).
|
|
@@ -1088,7 +1397,18 @@ class SdkSession {
|
|
|
1088
1397
|
this.resetHeartbeat();
|
|
1089
1398
|
const data = e.data;
|
|
1090
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);
|
|
1091
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);
|
|
1092
1412
|
this.emit('subagent_end', name, { ...data, toolCallId });
|
|
1093
1413
|
});
|
|
1094
1414
|
sdkSession.on('subagent.failed', (e) => {
|
|
@@ -1252,6 +1572,10 @@ class SdkSession {
|
|
|
1252
1572
|
// Only the pre-handle creation window is handled here.
|
|
1253
1573
|
if (this._sdkSession)
|
|
1254
1574
|
return;
|
|
1575
|
+
if (e.type === 'session.usage_info') {
|
|
1576
|
+
this.handleContextUsage(undefined, e);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1255
1579
|
const eventClass = (0, lifecycle_events_1.classifySdkEvent)(e.type);
|
|
1256
1580
|
if (eventClass === 'informational')
|
|
1257
1581
|
return;
|
|
@@ -1337,6 +1661,409 @@ class SdkSession {
|
|
|
1337
1661
|
|| typeof data.error === 'string'
|
|
1338
1662
|
|| (data.error != null && typeof data.error === 'object');
|
|
1339
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
|
+
}
|
|
1340
2067
|
async resolvePendingRequest(sdkSession, e) {
|
|
1341
2068
|
if (this.aborted || this._sdkSession !== sdkSession)
|
|
1342
2069
|
return;
|
|
@@ -1404,7 +2131,10 @@ class SdkSession {
|
|
|
1404
2131
|
this.abortGraceTimer = null;
|
|
1405
2132
|
}
|
|
1406
2133
|
this.compactionInProgress = false;
|
|
1407
|
-
this.
|
|
2134
|
+
this.parentCompactionGeneration = 0;
|
|
2135
|
+
this.verifiedParentCompactionGeneration = 0;
|
|
2136
|
+
this.compactionBaselinePromise = undefined;
|
|
2137
|
+
this.proactiveCompactionPromise = undefined;
|
|
1408
2138
|
}
|
|
1409
2139
|
}
|
|
1410
2140
|
//# sourceMappingURL=sdk-backend.js.map
|