@borgee/agents-host 0.2.94 → 0.2.101
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/agents-host.d.ts +8 -0
- package/dist/agents-host.js +266 -64
- package/dist/background-runs.d.ts +4 -0
- package/dist/background-runs.js +8 -4
- package/dist/chat/chat-control-plane.d.ts +14 -3
- package/dist/chat/sdk-chat-control-plane.d.ts +31 -5
- package/dist/chat/sdk-chat-control-plane.js +134 -3
- package/dist/context/skill-manual.js +1 -1
- package/dist/execution-telemetry.d.ts +96 -0
- package/dist/execution-telemetry.js +583 -0
- package/dist/gateway/channel-file-workspace.d.ts +15 -0
- package/dist/gateway/channel-file-workspace.js +130 -0
- package/dist/gateway/localhost-gateway.js +140 -0
- package/dist/plugin-sdk.js +85 -4
- package/dist/plugin-sdk.js.map +2 -2
- package/dist/policy/gateway-authorization.d.ts +1 -1
- package/dist/policy/gateway-authorization.js +22 -2
- package/dist/progress-to-activity.d.ts +3 -3
- package/dist/providers/claude/adapter.d.ts +2 -1
- package/dist/providers/claude/adapter.js +11 -0
- package/dist/providers/claude/cli-client.d.ts +3 -1
- package/dist/providers/claude/cli-client.js +267 -13
- package/dist/providers/codex/adapter.js +1 -0
- package/dist/providers/codex/cli-client.js +5 -0
- package/dist/providers/copilot/adapter.js +4 -0
- package/dist/providers/copilot/cli-client.js +11 -1
- package/dist/providers/copilot/sdk-session.d.ts +12 -3
- package/dist/providers/copilot/sdk-session.js +222 -5
- package/dist/providers/create-provider.js +4 -0
- package/dist/providers/prompt-usage.d.ts +8 -0
- package/dist/providers/prompt-usage.js +52 -0
- package/dist/state-paths.d.ts +2 -0
- package/dist/state-paths.js +6 -0
- package/dist/types.d.ts +41 -0
- package/dist/typing-lease.d.ts +14 -0
- package/dist/typing-lease.js +31 -0
- package/package.json +9 -9
- package/skills/borgee-agent/SKILL.md +12 -4
- package/skills/borgee-agent/scripts/borgee-agent.mjs +89 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +90 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActiveSession, ContentBlock } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { type MessageOptions, type PermissionHandler, type ResumeSessionConfig, type SessionConfig, type SessionEventHandler } from '@github/copilot-sdk';
|
|
3
|
-
import type { ProviderBackgroundRunProgress } from '../../types.js';
|
|
3
|
+
import type { ProviderBackgroundRunProgress, ProviderGenerateOptions } from '../../types.js';
|
|
4
4
|
type CopilotSessionUpdate = Awaited<ReturnType<ActiveSession['nextUpdate']>>;
|
|
5
5
|
type CopilotPromptResponse = Awaited<ReturnType<ActiveSession['prompt']>>;
|
|
6
6
|
interface CopilotSdkTaskInfo {
|
|
@@ -77,6 +77,7 @@ export declare class CopilotSdkSessionAdapter {
|
|
|
77
77
|
private readonly onAutonomousReply;
|
|
78
78
|
private readonly onAutonomousTurnPresenceChanged;
|
|
79
79
|
private readonly onBackgroundTaskExecutionsChanged;
|
|
80
|
+
private readonly backgroundUsageOwnerTimeoutMs;
|
|
80
81
|
readonly transport = "sdk";
|
|
81
82
|
private readonly pendingUpdates;
|
|
82
83
|
private readonly updateWaiters;
|
|
@@ -98,11 +99,12 @@ export declare class CopilotSdkSessionAdapter {
|
|
|
98
99
|
private readonly backgroundTaskExecutions;
|
|
99
100
|
private backgroundTaskExecutionsDirty;
|
|
100
101
|
private readonly backgroundWaits;
|
|
102
|
+
private readonly backgroundUsageOwners;
|
|
101
103
|
private lastSyntheticExecutionStartedAt;
|
|
102
104
|
private closePromise?;
|
|
103
|
-
constructor(session: CopilotSdkSessionLike, onBackgroundRun: (run: ProviderBackgroundRunProgress) => void, onBackgroundAgentPresenceChanged: (active: boolean) => void, onBackgroundRunError: (error: Error) => void, onAutonomousReply: (reply: CopilotAutonomousReply) => void, onAutonomousTurnPresenceChanged: (active: boolean) => void, initialBackgroundTaskExecutions?: Readonly<Record<string, CopilotBackgroundExecutionState>>, onBackgroundTaskExecutionsChanged?: (executions: Readonly<Record<string, CopilotBackgroundExecutionState>>) => Promise<void
|
|
105
|
+
constructor(session: CopilotSdkSessionLike, onBackgroundRun: (run: ProviderBackgroundRunProgress) => void, onBackgroundAgentPresenceChanged: (active: boolean) => void, onBackgroundRunError: (error: Error) => void, onAutonomousReply: (reply: CopilotAutonomousReply) => void, onAutonomousTurnPresenceChanged: (active: boolean) => void, initialBackgroundTaskExecutions?: Readonly<Record<string, CopilotBackgroundExecutionState>>, onBackgroundTaskExecutionsChanged?: (executions: Readonly<Record<string, CopilotBackgroundExecutionState>>) => Promise<void>, backgroundUsageOwnerTimeoutMs?: number);
|
|
104
106
|
get sessionId(): string;
|
|
105
|
-
prompt(input: string | ContentBlock[]): Promise<CopilotPromptResponse>;
|
|
107
|
+
prompt(input: string | ContentBlock[], telemetry?: Pick<ProviderGenerateOptions, 'onExecutionContinuation' | 'onUsage'>): Promise<CopilotPromptResponse>;
|
|
106
108
|
nextUpdate(): Promise<CopilotSessionUpdate>;
|
|
107
109
|
interruptMainTurn(): Promise<boolean>;
|
|
108
110
|
cancelAllBackgroundWork(): Promise<boolean>;
|
|
@@ -139,6 +141,13 @@ export declare class CopilotSdkSessionAdapter {
|
|
|
139
141
|
private resolveBackgroundWait;
|
|
140
142
|
private backgroundWaitFor;
|
|
141
143
|
private handleOwnedTurnEvent;
|
|
144
|
+
private recordTurnUsage;
|
|
145
|
+
private deliverAccumulatedTurnUsage;
|
|
146
|
+
private recordBackgroundUsage;
|
|
147
|
+
private settleBackgroundUsageOwner;
|
|
148
|
+
private settleBackgroundUsageGroup;
|
|
149
|
+
private updateBackgroundUsageRegistryState;
|
|
150
|
+
private refreshMissingBackgroundTerminalGrace;
|
|
142
151
|
private interruptAttachedTurn;
|
|
143
152
|
private finishActiveTurn;
|
|
144
153
|
private resolveEventTurnId;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { accessSync, constants as fsConstants, existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { delimiter, extname, isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { CopilotClient, RuntimeConnection, } from '@github/copilot-sdk';
|
|
4
|
+
import { normalizeProviderCallUsage } from '../prompt-usage.js';
|
|
4
5
|
function isExecutableFile(path) {
|
|
5
6
|
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
6
7
|
return false;
|
|
@@ -79,6 +80,15 @@ export function createCopilotSdkSessionConfig(cwd, onPermissionRequest) {
|
|
|
79
80
|
onPermissionRequest,
|
|
80
81
|
};
|
|
81
82
|
}
|
|
83
|
+
const DEFAULT_BACKGROUND_USAGE_OWNER_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
84
|
+
function addUsage(current, next) {
|
|
85
|
+
return {
|
|
86
|
+
inputTokens: (current?.inputTokens ?? 0) + next.inputTokens,
|
|
87
|
+
outputTokens: (current?.outputTokens ?? 0) + next.outputTokens,
|
|
88
|
+
cacheReadTokens: (current?.cacheReadTokens ?? 0) + next.cacheReadTokens,
|
|
89
|
+
cacheWriteTokens: (current?.cacheWriteTokens ?? 0) + next.cacheWriteTokens,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
82
92
|
const BACKGROUND_COMPLETION_SIGNAL_TTL_MS = 30_000;
|
|
83
93
|
const MAX_BACKGROUND_COMPLETION_SIGNALS = 32;
|
|
84
94
|
function normalizeError(error) {
|
|
@@ -193,6 +203,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
193
203
|
onAutonomousReply;
|
|
194
204
|
onAutonomousTurnPresenceChanged;
|
|
195
205
|
onBackgroundTaskExecutionsChanged;
|
|
206
|
+
backgroundUsageOwnerTimeoutMs;
|
|
196
207
|
transport = 'sdk';
|
|
197
208
|
pendingUpdates = [];
|
|
198
209
|
updateWaiters = [];
|
|
@@ -214,9 +225,10 @@ export class CopilotSdkSessionAdapter {
|
|
|
214
225
|
backgroundTaskExecutions;
|
|
215
226
|
backgroundTaskExecutionsDirty = false;
|
|
216
227
|
backgroundWaits = new Map();
|
|
228
|
+
backgroundUsageOwners = new Map();
|
|
217
229
|
lastSyntheticExecutionStartedAt = 0;
|
|
218
230
|
closePromise;
|
|
219
|
-
constructor(session, onBackgroundRun, onBackgroundAgentPresenceChanged, onBackgroundRunError, onAutonomousReply, onAutonomousTurnPresenceChanged, initialBackgroundTaskExecutions = {}, onBackgroundTaskExecutionsChanged = async () => { }) {
|
|
231
|
+
constructor(session, onBackgroundRun, onBackgroundAgentPresenceChanged, onBackgroundRunError, onAutonomousReply, onAutonomousTurnPresenceChanged, initialBackgroundTaskExecutions = {}, onBackgroundTaskExecutionsChanged = async () => { }, backgroundUsageOwnerTimeoutMs = DEFAULT_BACKGROUND_USAGE_OWNER_TIMEOUT_MS) {
|
|
220
232
|
this.session = session;
|
|
221
233
|
this.onBackgroundRun = onBackgroundRun;
|
|
222
234
|
this.onBackgroundAgentPresenceChanged = onBackgroundAgentPresenceChanged;
|
|
@@ -224,6 +236,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
224
236
|
this.onAutonomousReply = onAutonomousReply;
|
|
225
237
|
this.onAutonomousTurnPresenceChanged = onAutonomousTurnPresenceChanged;
|
|
226
238
|
this.onBackgroundTaskExecutionsChanged = onBackgroundTaskExecutionsChanged;
|
|
239
|
+
this.backgroundUsageOwnerTimeoutMs = backgroundUsageOwnerTimeoutMs;
|
|
227
240
|
this.backgroundTaskExecutions = new Map(Object.entries(initialBackgroundTaskExecutions).map(([taskId, execution]) => [
|
|
228
241
|
taskId,
|
|
229
242
|
{ ...execution },
|
|
@@ -233,7 +246,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
233
246
|
get sessionId() {
|
|
234
247
|
return this.session.sessionId;
|
|
235
248
|
}
|
|
236
|
-
async prompt(input) {
|
|
249
|
+
async prompt(input, telemetry = {}) {
|
|
237
250
|
if (this.closePromise) {
|
|
238
251
|
throw new Error('Copilot SDK session is closed');
|
|
239
252
|
}
|
|
@@ -249,7 +262,13 @@ export class CopilotSdkSessionAdapter {
|
|
|
249
262
|
const turn = {
|
|
250
263
|
messageIdsWithDeltas: new Set(),
|
|
251
264
|
ownedTurnIds: new Set(),
|
|
265
|
+
usageKeys: new Set(),
|
|
266
|
+
backgroundAgentIds: new Set(),
|
|
267
|
+
backgroundSucceeded: true,
|
|
268
|
+
onUsage: telemetry.onUsage,
|
|
269
|
+
onExecutionContinuation: telemetry.onExecutionContinuation,
|
|
252
270
|
userMessageObserved: false,
|
|
271
|
+
usageDelivered: false,
|
|
253
272
|
aborted: false,
|
|
254
273
|
cancelRequested: false,
|
|
255
274
|
resolve: resolveTurn,
|
|
@@ -263,6 +282,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
263
282
|
if (this.activeTurn === turn) {
|
|
264
283
|
this.activeTurn = undefined;
|
|
265
284
|
}
|
|
285
|
+
this.deliverAccumulatedTurnUsage(turn);
|
|
266
286
|
turn.reject(normalizeError(error));
|
|
267
287
|
}
|
|
268
288
|
return completion;
|
|
@@ -338,9 +358,16 @@ export class CopilotSdkSessionAdapter {
|
|
|
338
358
|
this.autonomousTurnIdByRuntimeTurnId.clear();
|
|
339
359
|
this.autonomousTurnIdByInteractionId.clear();
|
|
340
360
|
this.refreshAutonomousTurnPresence();
|
|
361
|
+
for (const owner of new Set(this.backgroundUsageOwners.values())) {
|
|
362
|
+
this.settleBackgroundUsageGroup(owner, false);
|
|
363
|
+
}
|
|
364
|
+
this.backgroundUsageOwners.clear();
|
|
341
365
|
this.closePromise = (async () => {
|
|
342
366
|
this.unsubscribe();
|
|
343
|
-
this.activeTurn
|
|
367
|
+
if (this.activeTurn) {
|
|
368
|
+
this.deliverAccumulatedTurnUsage(this.activeTurn);
|
|
369
|
+
this.activeTurn.reject(reason ?? new Error('Copilot SDK session closed'));
|
|
370
|
+
}
|
|
344
371
|
this.activeTurn = undefined;
|
|
345
372
|
for (const waiter of this.updateWaiters.splice(0)) {
|
|
346
373
|
waiter.reject(reason ?? new Error('Copilot SDK session closed'));
|
|
@@ -398,10 +425,11 @@ export class CopilotSdkSessionAdapter {
|
|
|
398
425
|
}
|
|
399
426
|
const fingerprint = JSON.stringify(run);
|
|
400
427
|
const previous = this.reportedBackgroundTasks.get(run.providerRunId);
|
|
428
|
+
this.reportedBackgroundTasks.set(run.providerRunId, run);
|
|
429
|
+
this.updateBackgroundUsageRegistryState(run.providerRunId, run.state === 'running' || run.state === 'waiting');
|
|
401
430
|
if (previous && JSON.stringify(previous) === fingerprint) {
|
|
402
431
|
continue;
|
|
403
432
|
}
|
|
404
|
-
this.reportedBackgroundTasks.set(run.providerRunId, run);
|
|
405
433
|
this.onBackgroundRun(run);
|
|
406
434
|
}
|
|
407
435
|
for (const [providerRunId, previous] of this.reportedBackgroundTasks) {
|
|
@@ -411,6 +439,10 @@ export class CopilotSdkSessionAdapter {
|
|
|
411
439
|
this.backgroundTaskExecutionsDirty = true;
|
|
412
440
|
}
|
|
413
441
|
this.backgroundWaits.delete(providerRunId);
|
|
442
|
+
if (this.activeTurn?.backgroundAgentIds.delete(providerRunId)) {
|
|
443
|
+
this.activeTurn.backgroundSucceeded = false;
|
|
444
|
+
}
|
|
445
|
+
this.refreshMissingBackgroundTerminalGrace(providerRunId);
|
|
414
446
|
if (previous.state === 'running' || previous.state === 'waiting') {
|
|
415
447
|
this.onBackgroundRun({
|
|
416
448
|
...previous,
|
|
@@ -666,7 +698,46 @@ export class CopilotSdkSessionAdapter {
|
|
|
666
698
|
this.noteBackgroundCompletion(event);
|
|
667
699
|
return;
|
|
668
700
|
}
|
|
701
|
+
if (event.type === 'session.error') {
|
|
702
|
+
if (event.agentId) {
|
|
703
|
+
if (this.activeTurn?.backgroundAgentIds.delete(event.agentId)) {
|
|
704
|
+
this.activeTurn.backgroundSucceeded = false;
|
|
705
|
+
}
|
|
706
|
+
this.settleBackgroundUsageOwner(event.agentId, false);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
for (const owner of new Set(this.backgroundUsageOwners.values())) {
|
|
710
|
+
this.settleBackgroundUsageGroup(owner, false);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
669
714
|
if (event.agentId) {
|
|
715
|
+
if (event.type === 'subagent.started' && this.activeTurn) {
|
|
716
|
+
this.activeTurn.backgroundAgentIds.add(event.agentId);
|
|
717
|
+
}
|
|
718
|
+
else if (event.type === 'assistant.usage') {
|
|
719
|
+
if (this.activeTurn &&
|
|
720
|
+
this.activeTurn.backgroundAgentIds.has(event.agentId)) {
|
|
721
|
+
this.recordTurnUsage(this.activeTurn, event);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
this.recordBackgroundUsage(event.agentId, event);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else if (event.type === 'subagent.completed' ||
|
|
728
|
+
event.type === 'subagent.failed') {
|
|
729
|
+
const succeeded = event.type === 'subagent.completed' && event.data.cancelled !== true;
|
|
730
|
+
if (this.activeTurn?.backgroundAgentIds.delete(event.agentId) && !succeeded) {
|
|
731
|
+
this.activeTurn.backgroundSucceeded = false;
|
|
732
|
+
}
|
|
733
|
+
this.settleBackgroundUsageOwner(event.agentId, succeeded);
|
|
734
|
+
}
|
|
735
|
+
else if (event.type === 'abort') {
|
|
736
|
+
if (this.activeTurn?.backgroundAgentIds.delete(event.agentId)) {
|
|
737
|
+
this.activeTurn.backgroundSucceeded = false;
|
|
738
|
+
}
|
|
739
|
+
this.settleBackgroundUsageOwner(event.agentId, false);
|
|
740
|
+
}
|
|
670
741
|
this.handleBackgroundAgentEvent(event, event.agentId);
|
|
671
742
|
return;
|
|
672
743
|
}
|
|
@@ -906,12 +977,18 @@ export class CopilotSdkSessionAdapter {
|
|
|
906
977
|
rawOutput: event.data.result ?? event.data.error,
|
|
907
978
|
}));
|
|
908
979
|
return;
|
|
980
|
+
case 'assistant.usage': {
|
|
981
|
+
this.recordTurnUsage(this.activeTurn, event);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
909
984
|
case 'abort':
|
|
985
|
+
this.deliverAccumulatedTurnUsage(this.activeTurn);
|
|
910
986
|
this.activeTurn.aborted = true;
|
|
911
987
|
return;
|
|
912
988
|
case 'session.error': {
|
|
913
989
|
const turn = this.activeTurn;
|
|
914
990
|
this.activeTurn = undefined;
|
|
991
|
+
this.deliverAccumulatedTurnUsage(turn);
|
|
915
992
|
turn.reject(new Error(event.data.message));
|
|
916
993
|
return;
|
|
917
994
|
}
|
|
@@ -923,6 +1000,93 @@ export class CopilotSdkSessionAdapter {
|
|
|
923
1000
|
return;
|
|
924
1001
|
}
|
|
925
1002
|
}
|
|
1003
|
+
recordTurnUsage(turn, event) {
|
|
1004
|
+
const usageKey = event.data.apiCallId ?? event.id;
|
|
1005
|
+
if (turn.usageKeys.has(usageKey))
|
|
1006
|
+
return;
|
|
1007
|
+
const usage = normalizeProviderCallUsage(event.data);
|
|
1008
|
+
if (!usage)
|
|
1009
|
+
return;
|
|
1010
|
+
turn.usageKeys.add(usageKey);
|
|
1011
|
+
turn.usage = addUsage(turn.usage, usage);
|
|
1012
|
+
}
|
|
1013
|
+
deliverAccumulatedTurnUsage(turn) {
|
|
1014
|
+
if (!turn.usage || turn.usageDelivered)
|
|
1015
|
+
return;
|
|
1016
|
+
turn.usageDelivered = true;
|
|
1017
|
+
turn.onUsage?.({ ...turn.usage });
|
|
1018
|
+
}
|
|
1019
|
+
recordBackgroundUsage(agentId, event) {
|
|
1020
|
+
const owner = this.backgroundUsageOwners.get(agentId);
|
|
1021
|
+
if (!owner)
|
|
1022
|
+
return;
|
|
1023
|
+
const usageKey = event.data.apiCallId ?? event.id;
|
|
1024
|
+
if (owner.usageKeys.has(usageKey))
|
|
1025
|
+
return;
|
|
1026
|
+
const usage = normalizeProviderCallUsage(event.data);
|
|
1027
|
+
if (!usage)
|
|
1028
|
+
return;
|
|
1029
|
+
owner.usageKeys.add(usageKey);
|
|
1030
|
+
owner.onUsage?.(usage);
|
|
1031
|
+
this.refreshMissingBackgroundTerminalGrace(agentId);
|
|
1032
|
+
}
|
|
1033
|
+
settleBackgroundUsageOwner(agentId, succeeded) {
|
|
1034
|
+
const owner = this.backgroundUsageOwners.get(agentId);
|
|
1035
|
+
if (!owner)
|
|
1036
|
+
return;
|
|
1037
|
+
const timer = owner.missingTerminalTimers.get(agentId);
|
|
1038
|
+
if (timer)
|
|
1039
|
+
clearTimeout(timer);
|
|
1040
|
+
owner.missingTerminalTimers.delete(agentId);
|
|
1041
|
+
owner.succeeded &&= succeeded;
|
|
1042
|
+
owner.agentIds.delete(agentId);
|
|
1043
|
+
this.backgroundUsageOwners.delete(agentId);
|
|
1044
|
+
if (owner.agentIds.size === 0) {
|
|
1045
|
+
this.settleBackgroundUsageGroup(owner, owner.succeeded);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
settleBackgroundUsageGroup(owner, succeeded) {
|
|
1049
|
+
for (const timer of owner.missingTerminalTimers.values()) {
|
|
1050
|
+
clearTimeout(timer);
|
|
1051
|
+
}
|
|
1052
|
+
owner.missingTerminalTimers.clear();
|
|
1053
|
+
for (const agentId of owner.agentIds) {
|
|
1054
|
+
if (this.backgroundUsageOwners.get(agentId) === owner) {
|
|
1055
|
+
this.backgroundUsageOwners.delete(agentId);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
owner.agentIds.clear();
|
|
1059
|
+
owner.resolve(succeeded && owner.succeeded);
|
|
1060
|
+
}
|
|
1061
|
+
updateBackgroundUsageRegistryState(agentId, active) {
|
|
1062
|
+
const owner = this.backgroundUsageOwners.get(agentId);
|
|
1063
|
+
if (!owner)
|
|
1064
|
+
return;
|
|
1065
|
+
const existing = owner.missingTerminalTimers.get(agentId);
|
|
1066
|
+
if (active) {
|
|
1067
|
+
if (existing)
|
|
1068
|
+
clearTimeout(existing);
|
|
1069
|
+
owner.missingTerminalTimers.delete(agentId);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
this.refreshMissingBackgroundTerminalGrace(agentId);
|
|
1073
|
+
}
|
|
1074
|
+
refreshMissingBackgroundTerminalGrace(agentId) {
|
|
1075
|
+
const owner = this.backgroundUsageOwners.get(agentId);
|
|
1076
|
+
if (!owner)
|
|
1077
|
+
return;
|
|
1078
|
+
const registryRun = this.reportedBackgroundTasks.get(agentId);
|
|
1079
|
+
if (registryRun?.state === 'running' || registryRun?.state === 'waiting') {
|
|
1080
|
+
this.updateBackgroundUsageRegistryState(agentId, true);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const existing = owner.missingTerminalTimers.get(agentId);
|
|
1084
|
+
if (existing)
|
|
1085
|
+
clearTimeout(existing);
|
|
1086
|
+
owner.missingTerminalTimers.set(agentId, setTimeout(() => {
|
|
1087
|
+
this.settleBackgroundUsageOwner(agentId, false);
|
|
1088
|
+
}, this.backgroundUsageOwnerTimeoutMs));
|
|
1089
|
+
}
|
|
926
1090
|
async interruptAttachedTurn(turn) {
|
|
927
1091
|
try {
|
|
928
1092
|
const result = await this.session.rpc.interruptMainTurn({ flushQueued: false });
|
|
@@ -935,6 +1099,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
935
1099
|
return;
|
|
936
1100
|
}
|
|
937
1101
|
this.activeTurn = undefined;
|
|
1102
|
+
this.deliverAccumulatedTurnUsage(turn);
|
|
938
1103
|
turn.reject(normalizeError(error));
|
|
939
1104
|
}
|
|
940
1105
|
}
|
|
@@ -946,7 +1111,58 @@ export class CopilotSdkSessionAdapter {
|
|
|
946
1111
|
this.activeTurn = undefined;
|
|
947
1112
|
const stopReason = aborted || turn.aborted ? 'cancelled' : 'end_turn';
|
|
948
1113
|
this.emitUpdate(stopUpdate(stopReason));
|
|
949
|
-
turn.
|
|
1114
|
+
if (!turn.backgroundSucceeded) {
|
|
1115
|
+
turn.onExecutionContinuation?.(Promise.resolve(false));
|
|
1116
|
+
}
|
|
1117
|
+
if (turn.backgroundAgentIds.size > 0 &&
|
|
1118
|
+
turn.onExecutionContinuation) {
|
|
1119
|
+
let resolveContinuation;
|
|
1120
|
+
const completion = new Promise((resolve) => {
|
|
1121
|
+
resolveContinuation = resolve;
|
|
1122
|
+
});
|
|
1123
|
+
const owner = {
|
|
1124
|
+
agentIds: new Set(turn.backgroundAgentIds),
|
|
1125
|
+
usageKeys: new Set(turn.usageKeys),
|
|
1126
|
+
onUsage: turn.onUsage,
|
|
1127
|
+
succeeded: true,
|
|
1128
|
+
missingTerminalTimers: new Map(),
|
|
1129
|
+
resolve: resolveContinuation,
|
|
1130
|
+
};
|
|
1131
|
+
for (const agentId of owner.agentIds) {
|
|
1132
|
+
const previous = this.backgroundUsageOwners.get(agentId);
|
|
1133
|
+
if (previous) {
|
|
1134
|
+
const previousTimer = previous.missingTerminalTimers.get(agentId);
|
|
1135
|
+
if (previousTimer)
|
|
1136
|
+
clearTimeout(previousTimer);
|
|
1137
|
+
previous.missingTerminalTimers.delete(agentId);
|
|
1138
|
+
previous.agentIds.delete(agentId);
|
|
1139
|
+
previous.succeeded = false;
|
|
1140
|
+
if (previous.agentIds.size === 0) {
|
|
1141
|
+
this.settleBackgroundUsageGroup(previous, false);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
this.backgroundUsageOwners.set(agentId, owner);
|
|
1145
|
+
this.refreshMissingBackgroundTerminalGrace(agentId);
|
|
1146
|
+
}
|
|
1147
|
+
turn.onExecutionContinuation(completion);
|
|
1148
|
+
}
|
|
1149
|
+
turn.resolve({
|
|
1150
|
+
stopReason,
|
|
1151
|
+
...(turn.usage && !turn.usageDelivered
|
|
1152
|
+
? {
|
|
1153
|
+
usage: {
|
|
1154
|
+
inputTokens: turn.usage.inputTokens,
|
|
1155
|
+
outputTokens: turn.usage.outputTokens,
|
|
1156
|
+
totalTokens: turn.usage.inputTokens +
|
|
1157
|
+
turn.usage.outputTokens +
|
|
1158
|
+
turn.usage.cacheReadTokens +
|
|
1159
|
+
turn.usage.cacheWriteTokens,
|
|
1160
|
+
cachedReadTokens: turn.usage.cacheReadTokens,
|
|
1161
|
+
cachedWriteTokens: turn.usage.cacheWriteTokens,
|
|
1162
|
+
},
|
|
1163
|
+
}
|
|
1164
|
+
: {}),
|
|
1165
|
+
});
|
|
950
1166
|
}
|
|
951
1167
|
resolveEventTurnId(event) {
|
|
952
1168
|
switch (event.type) {
|
|
@@ -956,6 +1172,7 @@ export class CopilotSdkSessionAdapter {
|
|
|
956
1172
|
case 'tool.execution_start':
|
|
957
1173
|
case 'tool.execution_complete':
|
|
958
1174
|
return event.data.turnId ?? this.currentRootTurnId;
|
|
1175
|
+
case 'assistant.usage':
|
|
959
1176
|
case 'assistant.idle':
|
|
960
1177
|
return this.currentRootTurnId ?? this.lastRootTurnId;
|
|
961
1178
|
default:
|
|
@@ -59,6 +59,10 @@ class CliBackedProviderV2 {
|
|
|
59
59
|
async generateReply(input, options) {
|
|
60
60
|
const text = await this.cli.generateReply(input.turn, {
|
|
61
61
|
onProgress: createAwaitingUserProgressHandler(options?.onProgress),
|
|
62
|
+
...(options?.onUsage ? { onUsage: options.onUsage } : {}),
|
|
63
|
+
...(options?.onExecutionContinuation
|
|
64
|
+
? { onExecutionContinuation: options.onExecutionContinuation }
|
|
65
|
+
: {}),
|
|
62
66
|
});
|
|
63
67
|
// Read the session AFTER the turn: a first turn on a channel opens the
|
|
64
68
|
// session as it runs, so before this point there is nothing to report.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProviderTokenUsage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ACP prompt usage defines input and cached tokens as separate counters.
|
|
4
|
+
* Provider totals, context occupancy, and thought tokens have no equivalent in
|
|
5
|
+
* the execution ledger and are deliberately not projected.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeAcpPromptUsage(value: unknown): ProviderTokenUsage | undefined;
|
|
8
|
+
export declare function normalizeProviderCallUsage(value: unknown): ProviderTokenUsage | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function tokenCount(value, optional = false) {
|
|
2
|
+
if (value == null && optional)
|
|
3
|
+
return 0;
|
|
4
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
|
|
5
|
+
return undefined;
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* ACP prompt usage defines input and cached tokens as separate counters.
|
|
10
|
+
* Provider totals, context occupancy, and thought tokens have no equivalent in
|
|
11
|
+
* the execution ledger and are deliberately not projected.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeAcpPromptUsage(value) {
|
|
14
|
+
if (!value || typeof value !== 'object')
|
|
15
|
+
return undefined;
|
|
16
|
+
const usage = value;
|
|
17
|
+
const inputTokens = tokenCount(usage.inputTokens);
|
|
18
|
+
const outputTokens = tokenCount(usage.outputTokens);
|
|
19
|
+
const cacheReadTokens = tokenCount(usage.cachedReadTokens, true);
|
|
20
|
+
const cacheWriteTokens = tokenCount(usage.cachedWriteTokens, true);
|
|
21
|
+
if (inputTokens === undefined ||
|
|
22
|
+
outputTokens === undefined ||
|
|
23
|
+
cacheReadTokens === undefined ||
|
|
24
|
+
cacheWriteTokens === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };
|
|
28
|
+
}
|
|
29
|
+
export function normalizeProviderCallUsage(value) {
|
|
30
|
+
if (!value || typeof value !== 'object')
|
|
31
|
+
return undefined;
|
|
32
|
+
const usage = value;
|
|
33
|
+
const reported = [
|
|
34
|
+
usage.inputTokens,
|
|
35
|
+
usage.outputTokens,
|
|
36
|
+
usage.cacheReadTokens,
|
|
37
|
+
usage.cacheWriteTokens,
|
|
38
|
+
];
|
|
39
|
+
if (reported.every((tokens) => tokens == null))
|
|
40
|
+
return undefined;
|
|
41
|
+
const inputTokens = tokenCount(usage.inputTokens, true);
|
|
42
|
+
const outputTokens = tokenCount(usage.outputTokens, true);
|
|
43
|
+
const cacheReadTokens = tokenCount(usage.cacheReadTokens, true);
|
|
44
|
+
const cacheWriteTokens = tokenCount(usage.cacheWriteTokens, true);
|
|
45
|
+
if (inputTokens === undefined ||
|
|
46
|
+
outputTokens === undefined ||
|
|
47
|
+
cacheReadTokens === undefined ||
|
|
48
|
+
cacheWriteTokens === undefined) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };
|
|
52
|
+
}
|
package/dist/state-paths.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare function resolveLocalConfigAgentStateRoot(rootPath: string, agent
|
|
|
23
23
|
export declare function resolveSharedProtocolKickoffDecisionPath(stateRootDir: string, channelId: string, anchorMessageId: string): string;
|
|
24
24
|
export declare function resolveSharedProtocolStatusPath(stateRootDir: string, channelId: string, anchorMessageId: string): string;
|
|
25
25
|
export declare function resolveConnectionsStatePath(stateRootDir: string): string;
|
|
26
|
+
export declare function resolveExecutionTelemetryOutboxPath(stateRootDir: string): string;
|
|
27
|
+
export declare function resolveExecutionDeliveryJournalPath(stateRootDir: string): string;
|
|
26
28
|
export declare function resolveAttentionStatePath(stateRootDir: string, channelId: string): string;
|
|
27
29
|
export declare function resolveCompactionStatePath(stateRootDir: string, channelId: string): string;
|
|
28
30
|
export declare function resolveAuthorizationAuditPath(stateRootDir: string): string;
|
package/dist/state-paths.js
CHANGED
|
@@ -138,6 +138,12 @@ export function resolveSharedProtocolStatusPath(stateRootDir, channelId, anchorM
|
|
|
138
138
|
export function resolveConnectionsStatePath(stateRootDir) {
|
|
139
139
|
return join(stateRootDir, 'connections-state.sqlite');
|
|
140
140
|
}
|
|
141
|
+
export function resolveExecutionTelemetryOutboxPath(stateRootDir) {
|
|
142
|
+
return join(stateRootDir, 'execution-telemetry-outbox.json');
|
|
143
|
+
}
|
|
144
|
+
export function resolveExecutionDeliveryJournalPath(stateRootDir) {
|
|
145
|
+
return join(stateRootDir, 'execution-delivery-journal.json');
|
|
146
|
+
}
|
|
141
147
|
export function resolveAttentionStatePath(stateRootDir, channelId) {
|
|
142
148
|
return join(stateRootDir, 'attention-state', `${encodeSegment(channelId)}.json`);
|
|
143
149
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExecutionGrant } from './plugin-sdk.js';
|
|
1
2
|
export type ProviderKind = 'claude' | 'codex' | 'copilot';
|
|
2
3
|
export type ProjectionStrategy = 'session-brief' | 'turn-full' | 'message-only' | 'turn-thin' | 'file-brief';
|
|
3
4
|
export interface ProviderCommandConfig {
|
|
@@ -111,6 +112,8 @@ export interface ChannelMessageEvent {
|
|
|
111
112
|
content_type?: string;
|
|
112
113
|
attachments?: HostedMessageAttachment[];
|
|
113
114
|
created_at?: number;
|
|
115
|
+
/** Server-issued capability used only by the execution telemetry rail. */
|
|
116
|
+
execution_grant?: ExecutionGrant;
|
|
114
117
|
[key: string]: unknown;
|
|
115
118
|
}
|
|
116
119
|
export interface MeResponseUser {
|
|
@@ -135,6 +138,36 @@ export interface ChannelHistoryEntry {
|
|
|
135
138
|
createdAt: number;
|
|
136
139
|
editedAt?: number;
|
|
137
140
|
}
|
|
141
|
+
export interface ChannelFileEntry {
|
|
142
|
+
id: string;
|
|
143
|
+
ownerUserId: string;
|
|
144
|
+
channelId: string;
|
|
145
|
+
path: string;
|
|
146
|
+
parentPath: string;
|
|
147
|
+
name: string;
|
|
148
|
+
isDirectory: boolean;
|
|
149
|
+
mimeType: string;
|
|
150
|
+
sizeBytes: number;
|
|
151
|
+
source: string;
|
|
152
|
+
sourceMessageId: string;
|
|
153
|
+
createdAt: number;
|
|
154
|
+
updatedAt: number;
|
|
155
|
+
}
|
|
156
|
+
export interface ChannelFileContent {
|
|
157
|
+
path: string;
|
|
158
|
+
filename: string;
|
|
159
|
+
contentType: string;
|
|
160
|
+
sizeBytes: number;
|
|
161
|
+
base64?: string;
|
|
162
|
+
text?: string;
|
|
163
|
+
}
|
|
164
|
+
export interface PublishChannelFileInput {
|
|
165
|
+
channelId: string;
|
|
166
|
+
path: string;
|
|
167
|
+
contentType: string;
|
|
168
|
+
base64: string;
|
|
169
|
+
overwrite: boolean;
|
|
170
|
+
}
|
|
138
171
|
export interface HostedMessageAttachment {
|
|
139
172
|
url: string;
|
|
140
173
|
filename?: string;
|
|
@@ -454,6 +487,12 @@ export interface ProviderReply {
|
|
|
454
487
|
*/
|
|
455
488
|
sessionId?: string;
|
|
456
489
|
}
|
|
490
|
+
export interface ProviderTokenUsage {
|
|
491
|
+
inputTokens: number;
|
|
492
|
+
outputTokens: number;
|
|
493
|
+
cacheReadTokens: number;
|
|
494
|
+
cacheWriteTokens: number;
|
|
495
|
+
}
|
|
457
496
|
export interface PostedMessage {
|
|
458
497
|
messageId: string;
|
|
459
498
|
}
|
|
@@ -588,4 +627,6 @@ export type ProviderProgressUpdate = {
|
|
|
588
627
|
};
|
|
589
628
|
export interface ProviderGenerateOptions {
|
|
590
629
|
onProgress?: (update: ProviderProgressUpdate) => void;
|
|
630
|
+
onUsage?: (usage: ProviderTokenUsage) => void;
|
|
631
|
+
onExecutionContinuation?: (completion: Promise<boolean>) => void;
|
|
591
632
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TypingLease {
|
|
2
|
+
renew(): void;
|
|
3
|
+
stop(): void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Keeps typing truthful without making provider completion its only release.
|
|
7
|
+
*
|
|
8
|
+
* A lease reports one pulse immediately. Visible answer updates may renew it
|
|
9
|
+
* at most once per interval; nothing is scheduled between updates. Browser
|
|
10
|
+
* expiry bounds every pulse, so a provider turn can keep running tools or
|
|
11
|
+
* background work while the activity rail says "working" without a hidden
|
|
12
|
+
* periodic heartbeat surviving indefinitely.
|
|
13
|
+
*/
|
|
14
|
+
export declare function startTypingLease(reportTyping: () => void, now?: () => number): TypingLease;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const TYPING_PULSE_INTERVAL_MS = 2_000;
|
|
2
|
+
/**
|
|
3
|
+
* Keeps typing truthful without making provider completion its only release.
|
|
4
|
+
*
|
|
5
|
+
* A lease reports one pulse immediately. Visible answer updates may renew it
|
|
6
|
+
* at most once per interval; nothing is scheduled between updates. Browser
|
|
7
|
+
* expiry bounds every pulse, so a provider turn can keep running tools or
|
|
8
|
+
* background work while the activity rail says "working" without a hidden
|
|
9
|
+
* periodic heartbeat surviving indefinitely.
|
|
10
|
+
*/
|
|
11
|
+
export function startTypingLease(reportTyping, now = () => performance.now()) {
|
|
12
|
+
let closed = false;
|
|
13
|
+
let lastPulseAt = now();
|
|
14
|
+
reportTyping();
|
|
15
|
+
return {
|
|
16
|
+
renew: () => {
|
|
17
|
+
if (closed) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const currentTime = now();
|
|
21
|
+
if (currentTime - lastPulseAt < TYPING_PULSE_INTERVAL_MS) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
lastPulseAt = currentTime;
|
|
25
|
+
reportTyping();
|
|
26
|
+
},
|
|
27
|
+
stop: () => {
|
|
28
|
+
closed = true;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@borgee/agents-host",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.101",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -38,24 +38,24 @@
|
|
|
38
38
|
"tsx": "^4.20.5",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
40
|
"vitest": "^4.1.5",
|
|
41
|
-
"@borgee/plugin-sdk": "0.
|
|
41
|
+
"@borgee/plugin-sdk": "0.15.3"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"predev": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
44
|
+
"predev": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
45
45
|
"dev": "tsx src/index.ts",
|
|
46
|
-
"precli": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
46
|
+
"precli": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
47
47
|
"cli": "tsx src/cli.ts",
|
|
48
|
-
"prestart": "pnpm --filter @borgee/plugin-sdk build",
|
|
48
|
+
"prestart": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
49
49
|
"start": "node dist/index.js",
|
|
50
|
-
"prebuild": "pnpm --filter @borgee/plugin-sdk build",
|
|
50
|
+
"prebuild": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
51
51
|
"build": "tsc && node scripts/bundle-plugin-sdk.mjs && node scripts/vendor-claude-acp.mjs",
|
|
52
52
|
"test:package": "node --test --test-timeout=10000 scripts/packed-package-descriptor.test.mjs && pnpm run build && node scripts/test-packed-claude-lifecycle.mjs",
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
|
-
"pretest": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
54
|
+
"pretest": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
55
55
|
"test": "vitest run --testTimeout=10000",
|
|
56
|
-
"pretest:e2e": "pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
56
|
+
"pretest:e2e": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build && pnpm run vendor:claude",
|
|
57
57
|
"test:e2e": "vitest run --config vitest.e2e.config.ts --testTimeout=120000",
|
|
58
|
-
"pretypecheck": "pnpm --filter @borgee/plugin-sdk build",
|
|
58
|
+
"pretypecheck": "pnpm --filter @borgee/agent-remote-protocol build && pnpm --filter @borgee/plugin-sdk build",
|
|
59
59
|
"vendor:claude": "node scripts/vendor-claude-acp.mjs"
|
|
60
60
|
}
|
|
61
61
|
}
|