replicas-engine 0.1.699 → 0.1.701
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/README.md +1 -0
- package/dist/src/{chunk-UTGTXG54.js → chunk-OI5T44T7.js} +133 -62
- package/dist/src/headless-agent.js +1 -1
- package/dist/src/index.js +800 -251
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
MEMORY_ROOT,
|
|
69
69
|
MEMORY_SUMMARY_FILENAME,
|
|
70
70
|
MERGED_MESSAGE_SEPARATOR,
|
|
71
|
+
MODEL_REROUTED_METHOD,
|
|
71
72
|
REASONING_SUMMARY_PART_ADDED_METHOD,
|
|
72
73
|
REASONING_SUMMARY_TEXT_DELTA_METHOD,
|
|
73
74
|
REASONING_TEXT_DELTA_METHOD,
|
|
@@ -80,6 +81,7 @@ import {
|
|
|
80
81
|
THREAD_GOAL_UPDATED_METHOD,
|
|
81
82
|
THREAD_TOKEN_USAGE_UPDATED_METHOD,
|
|
82
83
|
TIMEOUT,
|
|
84
|
+
TOKEN_USAGE_PATH,
|
|
83
85
|
TURN_COMPLETED_METHOD,
|
|
84
86
|
TURN_PLAN_UPDATED_METHOD,
|
|
85
87
|
TURN_STARTED_METHOD,
|
|
@@ -116,6 +118,7 @@ import {
|
|
|
116
118
|
createErrorResult,
|
|
117
119
|
createProviderSlashCommand,
|
|
118
120
|
createSuccessResult,
|
|
121
|
+
createUuidFromHex,
|
|
119
122
|
decodePathSegments,
|
|
120
123
|
detectAgentQuotaLimit,
|
|
121
124
|
detectLanguageByPath,
|
|
@@ -126,6 +129,7 @@ import {
|
|
|
126
129
|
extractErrorText,
|
|
127
130
|
extractToolResultText,
|
|
128
131
|
fetchAiGatewayModels,
|
|
132
|
+
fetchModelsDevCatalog,
|
|
129
133
|
findGitCommitSignals,
|
|
130
134
|
findPrMergeSignals,
|
|
131
135
|
getChatHistoryPageSenders,
|
|
@@ -159,9 +163,12 @@ import {
|
|
|
159
163
|
isDefaultChat,
|
|
160
164
|
isGitHubUrl,
|
|
161
165
|
isLatestChatHistoryPage,
|
|
166
|
+
isNonEmptyString,
|
|
162
167
|
isRecord,
|
|
163
168
|
isSkillRegistryManifest,
|
|
164
169
|
isTerminalBackgroundTaskStatus,
|
|
170
|
+
isTokenUsageCounts,
|
|
171
|
+
isTokenUsageUploadRecord,
|
|
165
172
|
isTransientErrorText,
|
|
166
173
|
isValidAgentProvider,
|
|
167
174
|
isValidEngineLogSessionId,
|
|
@@ -191,15 +198,16 @@ import {
|
|
|
191
198
|
renumberCodexAspTranscriptItems,
|
|
192
199
|
resolveWarmHookConfig,
|
|
193
200
|
sanitizeCanvasFilename,
|
|
201
|
+
sendChatMessageRequestSchema,
|
|
194
202
|
serializeCanvasContentResponse,
|
|
195
203
|
shellQuotePosix,
|
|
196
204
|
stripAgentDiagnosticErrors
|
|
197
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-OI5T44T7.js";
|
|
198
206
|
|
|
199
207
|
// src/index.ts
|
|
200
208
|
import { serve } from "@hono/node-server";
|
|
201
209
|
import { Hono as Hono2 } from "hono";
|
|
202
|
-
import { existsSync as
|
|
210
|
+
import { existsSync as existsSync12 } from "fs";
|
|
203
211
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
204
212
|
import { connect } from "net";
|
|
205
213
|
|
|
@@ -2814,7 +2822,7 @@ async function registerDesktopPreview() {
|
|
|
2814
2822
|
}
|
|
2815
2823
|
|
|
2816
2824
|
// src/services/chat/chat-service.ts
|
|
2817
|
-
import { existsSync as
|
|
2825
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2818
2826
|
import { appendFile as appendFile4, copyFile, mkdir as mkdir17, readFile as readFile17, rename as rename3, rm as rm2 } from "fs/promises";
|
|
2819
2827
|
import { join as join29 } from "path";
|
|
2820
2828
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
@@ -3741,6 +3749,7 @@ var CodingAgentManager = class {
|
|
|
3741
3749
|
initialSessionId;
|
|
3742
3750
|
onSaveSessionId;
|
|
3743
3751
|
onEvent;
|
|
3752
|
+
onUsageUpdate;
|
|
3744
3753
|
hostOnTurnComplete;
|
|
3745
3754
|
onProcessingChanged;
|
|
3746
3755
|
onMessageStarted;
|
|
@@ -3751,6 +3760,8 @@ var CodingAgentManager = class {
|
|
|
3751
3760
|
this.initialSessionId = options.initialSessionId;
|
|
3752
3761
|
this.onSaveSessionId = options.onSaveSessionId;
|
|
3753
3762
|
this.onEvent = options.onEvent;
|
|
3763
|
+
this.onUsageUpdate = options.onUsageUpdate ?? (() => {
|
|
3764
|
+
});
|
|
3754
3765
|
this.hostOnTurnComplete = options.onTurnComplete;
|
|
3755
3766
|
this.onProcessingChanged = options.onProcessingChanged ?? (() => {
|
|
3756
3767
|
});
|
|
@@ -6738,9 +6749,17 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6738
6749
|
slashCommandsRequest = null;
|
|
6739
6750
|
steeredTempImagePaths = [];
|
|
6740
6751
|
steerLock = new AsyncLock();
|
|
6752
|
+
onCodexTurnStarted;
|
|
6753
|
+
onCodexTokenUsage;
|
|
6754
|
+
onCodexModelRerouted;
|
|
6755
|
+
onCodexTurnCompleted;
|
|
6741
6756
|
constructor(options) {
|
|
6742
6757
|
super({ ...options, provider: AGENT.CODEX });
|
|
6743
6758
|
this.historyFile = options.historyFilePath ? new CodexHistoryFile(options.historyFilePath) : null;
|
|
6759
|
+
this.onCodexTurnStarted = options.onCodexTurnStarted;
|
|
6760
|
+
this.onCodexTokenUsage = options.onCodexTokenUsage;
|
|
6761
|
+
this.onCodexModelRerouted = options.onCodexModelRerouted;
|
|
6762
|
+
this.onCodexTurnCompleted = options.onCodexTurnCompleted;
|
|
6744
6763
|
this.initializeManager(this.processMessageInternal.bind(this));
|
|
6745
6764
|
}
|
|
6746
6765
|
async initialize() {
|
|
@@ -7261,7 +7280,13 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
7261
7280
|
},
|
|
7262
7281
|
[THREAD_TOKEN_USAGE_UPDATED_METHOD]: (notification) => {
|
|
7263
7282
|
if (notification.params.threadId !== threadId) return;
|
|
7264
|
-
|
|
7283
|
+
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7284
|
+
this.onCodexTokenUsage?.(notification.params, occurredAt);
|
|
7285
|
+
this.emitCodexTokenUsage(notification.params.tokenUsage, model, occurredAt);
|
|
7286
|
+
},
|
|
7287
|
+
[MODEL_REROUTED_METHOD]: (notification) => {
|
|
7288
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
7289
|
+
this.onCodexModelRerouted?.(notification.params);
|
|
7265
7290
|
},
|
|
7266
7291
|
[THREAD_GOAL_UPDATED_METHOD]: (notification) => {
|
|
7267
7292
|
if (notification.params.threadId !== threadId) return;
|
|
@@ -7282,6 +7307,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
7282
7307
|
observedTurnId = notification.params.turn.id;
|
|
7283
7308
|
observedTurnIds.add(notification.params.turn.id);
|
|
7284
7309
|
this.activeTurnId = notification.params.turn.id;
|
|
7310
|
+
this.onCodexTurnStarted?.(threadId, notification.params.turn.id);
|
|
7285
7311
|
if (goalContinuationTimer) {
|
|
7286
7312
|
clearTimeout(goalContinuationTimer);
|
|
7287
7313
|
goalContinuationTimer = null;
|
|
@@ -7390,6 +7416,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
7390
7416
|
if (!observedTurnIds.has(notification.params.turn.id) && notification.params.turn.id !== this.activeTurnId) return;
|
|
7391
7417
|
observedTurnIds.add(notification.params.turn.id);
|
|
7392
7418
|
observedTurnId = notification.params.turn.id;
|
|
7419
|
+
this.onCodexTurnCompleted?.(threadId, notification.params.turn.id);
|
|
7393
7420
|
const completedTurn = recoverCompletedTurn(notification.params.turn, completedItems, agentMessageDeltas);
|
|
7394
7421
|
this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
|
|
7395
7422
|
this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
|
|
@@ -7432,6 +7459,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
7432
7459
|
observedTurnId = started.turn.id;
|
|
7433
7460
|
observedTurnIds.add(started.turn.id);
|
|
7434
7461
|
this.activeTurnId = started.turn.id;
|
|
7462
|
+
if (!alreadyObserved) this.onCodexTurnStarted?.(threadId, started.turn.id);
|
|
7435
7463
|
if (!alreadyObserved) {
|
|
7436
7464
|
attachUserMessage(started.turn);
|
|
7437
7465
|
this.mergeTranscriptTurn(threadId, started.turn);
|
|
@@ -8015,7 +8043,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
8015
8043
|
this.trackHistoryEvent(event);
|
|
8016
8044
|
this.onEvent(event);
|
|
8017
8045
|
}
|
|
8018
|
-
emitCodexTokenUsage(tokenUsage, model) {
|
|
8046
|
+
emitCodexTokenUsage(tokenUsage, model, updatedAt) {
|
|
8019
8047
|
const payload = buildCodexTokenUsageContextUsagePayload({
|
|
8020
8048
|
model,
|
|
8021
8049
|
modelContextWindow: tokenUsage.modelContextWindow,
|
|
@@ -8029,7 +8057,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
8029
8057
|
total: {
|
|
8030
8058
|
totalTokens: tokenUsage.total.totalTokens
|
|
8031
8059
|
},
|
|
8032
|
-
updatedAt
|
|
8060
|
+
updatedAt
|
|
8033
8061
|
});
|
|
8034
8062
|
const event = this.emitContextUsage(payload);
|
|
8035
8063
|
this.trackHistoryEvent(event);
|
|
@@ -8469,6 +8497,7 @@ ${instructions}
|
|
|
8469
8497
|
const outputTokens = finiteNumber(update.usage.outputTokens);
|
|
8470
8498
|
const rawTotalTokens = inputTokens + cacheReadTokens + cacheWriteTokens + outputTokens;
|
|
8471
8499
|
if (rawTotalTokens <= 0) return;
|
|
8500
|
+
this.onUsageUpdate(update, (/* @__PURE__ */ new Date()).toISOString());
|
|
8472
8501
|
const maxTokens = CURSOR_COMPOSER_CONTEXT_WINDOW;
|
|
8473
8502
|
const { totalTokens, totalProcessedTokens } = clampTokensToWindow(rawTotalTokens, maxTokens);
|
|
8474
8503
|
const categories = [
|
|
@@ -8811,8 +8840,10 @@ var DeepseekManager = class extends CodingAgentManager {
|
|
|
8811
8840
|
if (frame.type === "session/projection") {
|
|
8812
8841
|
if (frame.key === "contextPressure" && isRecord2(frame.value)) this.contextPressure = frame.value;
|
|
8813
8842
|
else if (frame.key === "contextBreakdown" && isRecord2(frame.value)) this.contextBreakdown = frame.value;
|
|
8814
|
-
else if (frame.key === "tokenUsage" && isRecord2(frame.value))
|
|
8815
|
-
|
|
8843
|
+
else if (frame.key === "tokenUsage" && isRecord2(frame.value)) {
|
|
8844
|
+
this.tokenUsage = frame.value;
|
|
8845
|
+
this.onUsageUpdate(frame.value, (/* @__PURE__ */ new Date()).toISOString());
|
|
8846
|
+
} else return;
|
|
8816
8847
|
this.emitDeepseekContextUsage();
|
|
8817
8848
|
return;
|
|
8818
8849
|
}
|
|
@@ -9162,6 +9193,7 @@ var AcpManager = class extends CodingAgentManager {
|
|
|
9162
9193
|
} else if (update.sessionUpdate === "current_mode_update" && this.modes) {
|
|
9163
9194
|
this.modes = { ...this.modes, currentModeId: update.currentModeId };
|
|
9164
9195
|
} else if (update.sessionUpdate === "usage_update" && !this.replaying) {
|
|
9196
|
+
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9165
9197
|
const { used, size } = update;
|
|
9166
9198
|
this.historyFile.append(this.emitContextUsage({
|
|
9167
9199
|
provider: this.acp.provider,
|
|
@@ -9171,7 +9203,7 @@ var AcpManager = class extends CodingAgentManager {
|
|
|
9171
9203
|
maxTokens: size,
|
|
9172
9204
|
percentage: size > 0 ? used / size * 100 : 0,
|
|
9173
9205
|
categories: [],
|
|
9174
|
-
updatedAt:
|
|
9206
|
+
updatedAt: occurredAt
|
|
9175
9207
|
}));
|
|
9176
9208
|
}
|
|
9177
9209
|
if (!this.replaying) {
|
|
@@ -9458,59 +9490,27 @@ import { delimiter, dirname as dirname8, join as join22 } from "path";
|
|
|
9458
9490
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9459
9491
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9460
9492
|
import { Agent } from "undici";
|
|
9461
|
-
import { z as
|
|
9493
|
+
import { z as z3 } from "zod";
|
|
9462
9494
|
|
|
9463
9495
|
// ../shared/src/credentials/opencode-go.ts
|
|
9464
|
-
import { z as z3 } from "zod";
|
|
9465
9496
|
var OPENCODE_GO_PROVIDER = "opencode-go";
|
|
9466
9497
|
var OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
9467
|
-
var OPENCODE_MODELS_DEV_URL = "https://models.dev/api.json";
|
|
9468
9498
|
var DEFAULT_OPENCODE_GO_MODEL = "glm-5.2";
|
|
9469
|
-
var opencodeGoModelsDevSchema = z3.object({
|
|
9470
|
-
"opencode-go": z3.object({
|
|
9471
|
-
models: z3.record(z3.string(), z3.object({
|
|
9472
|
-
id: z3.string().optional(),
|
|
9473
|
-
name: z3.string(),
|
|
9474
|
-
description: z3.string().optional(),
|
|
9475
|
-
status: z3.enum(["alpha", "beta", "deprecated"]).optional()
|
|
9476
|
-
}))
|
|
9477
|
-
}).optional()
|
|
9478
|
-
});
|
|
9479
|
-
function parseOpenCodeGoCatalog(value) {
|
|
9480
|
-
const parsed = opencodeGoModelsDevSchema.safeParse(value);
|
|
9481
|
-
const provider = parsed.success ? parsed.data["opencode-go"] : void 0;
|
|
9482
|
-
if (!provider) return null;
|
|
9483
|
-
const models = Object.fromEntries(
|
|
9484
|
-
Object.entries(provider.models).filter(([, definition]) => definition.status !== "deprecated" && definition.status !== "alpha").map(([id, definition]) => [id, {
|
|
9485
|
-
id: definition.id ?? id,
|
|
9486
|
-
name: definition.name,
|
|
9487
|
-
...definition.description ? { description: definition.description } : {},
|
|
9488
|
-
...definition.status ? { status: definition.status } : {}
|
|
9489
|
-
}])
|
|
9490
|
-
);
|
|
9491
|
-
if (Object.keys(models).length === 0) return null;
|
|
9492
|
-
return { models, authoritative: true };
|
|
9493
|
-
}
|
|
9494
|
-
var OPENCODE_GO_CATALOG_CACHE_MS = 5 * 6e4;
|
|
9495
|
-
var openCodeGoCatalogCache;
|
|
9496
|
-
var openCodeGoCatalogRequest;
|
|
9497
9499
|
async function fetchOpenCodeGoCatalog(fallbackModelIds = [DEFAULT_OPENCODE_GO_MODEL]) {
|
|
9498
|
-
if (openCodeGoCatalogCache && openCodeGoCatalogCache.expiresAt > Date.now()) {
|
|
9499
|
-
return openCodeGoCatalogCache.catalog;
|
|
9500
|
-
}
|
|
9501
|
-
openCodeGoCatalogRequest ??= (async () => {
|
|
9502
|
-
const response = await fetch(OPENCODE_MODELS_DEV_URL, { signal: AbortSignal.timeout(5e3) });
|
|
9503
|
-
if (!response.ok) throw new Error(`Failed to load OpenCode Go models (${response.status})`);
|
|
9504
|
-
const catalog = parseOpenCodeGoCatalog(await response.json());
|
|
9505
|
-
if (!catalog) throw new Error("OpenCode Go model catalog is unavailable");
|
|
9506
|
-
return catalog;
|
|
9507
|
-
})();
|
|
9508
9500
|
try {
|
|
9509
|
-
const
|
|
9510
|
-
|
|
9511
|
-
|
|
9501
|
+
const provider = (await fetchModelsDevCatalog())[OPENCODE_GO_PROVIDER];
|
|
9502
|
+
if (!provider) throw new Error("OpenCode Go model catalog is unavailable");
|
|
9503
|
+
const models = Object.fromEntries(
|
|
9504
|
+
Object.entries(provider.models).filter(([, definition]) => definition.status !== "deprecated" && definition.status !== "alpha").map(([id, definition]) => [id, {
|
|
9505
|
+
id: definition.id ?? id,
|
|
9506
|
+
name: definition.name,
|
|
9507
|
+
...definition.description ? { description: definition.description } : {},
|
|
9508
|
+
...definition.status ? { status: definition.status } : {}
|
|
9509
|
+
}])
|
|
9510
|
+
);
|
|
9511
|
+
if (Object.keys(models).length === 0) throw new Error("OpenCode Go model catalog is unavailable");
|
|
9512
|
+
return { models, authoritative: true };
|
|
9512
9513
|
} catch {
|
|
9513
|
-
if (openCodeGoCatalogCache) return openCodeGoCatalogCache.catalog;
|
|
9514
9514
|
return {
|
|
9515
9515
|
authoritative: false,
|
|
9516
9516
|
models: Object.fromEntries(fallbackModelIds.map((id) => [id, {
|
|
@@ -9518,8 +9518,6 @@ async function fetchOpenCodeGoCatalog(fallbackModelIds = [DEFAULT_OPENCODE_GO_MO
|
|
|
9518
9518
|
name: id === DEFAULT_OPENCODE_GO_MODEL ? "GLM 5.2" : id
|
|
9519
9519
|
}]))
|
|
9520
9520
|
};
|
|
9521
|
-
} finally {
|
|
9522
|
-
openCodeGoCatalogRequest = void 0;
|
|
9523
9521
|
}
|
|
9524
9522
|
}
|
|
9525
9523
|
|
|
@@ -9578,9 +9576,9 @@ var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
|
|
|
9578
9576
|
ultra: ["max", "xhigh", "high"],
|
|
9579
9577
|
ultracode: ["max", "xhigh", "high"]
|
|
9580
9578
|
};
|
|
9581
|
-
var opencodeAuthSchema =
|
|
9582
|
-
type:
|
|
9583
|
-
key:
|
|
9579
|
+
var opencodeAuthSchema = z3.record(z3.string(), z3.object({
|
|
9580
|
+
type: z3.string().optional(),
|
|
9581
|
+
key: z3.string().optional()
|
|
9584
9582
|
}));
|
|
9585
9583
|
async function hasOpencodeCredentials(provider) {
|
|
9586
9584
|
if (!existsSync7(OPENCODE_AUTH_PATH)) return false;
|
|
@@ -10311,6 +10309,7 @@ var OpencodeManager = class extends CodingAgentManager {
|
|
|
10311
10309
|
this.activeLinearForwarder.sendEvent(event);
|
|
10312
10310
|
}
|
|
10313
10311
|
recordOpencodePart(part) {
|
|
10312
|
+
if (part.type === "step-finish") this.onUsageUpdate(part, (/* @__PURE__ */ new Date()).toISOString());
|
|
10314
10313
|
this.recordHistoryEvent(`opencode-part-${part.type}`, { part }, this.historyFile);
|
|
10315
10314
|
this.forwardOpencodePartToLinear(part);
|
|
10316
10315
|
}
|
|
@@ -10565,6 +10564,7 @@ var PiManager = class extends CodingAgentManager {
|
|
|
10565
10564
|
return null;
|
|
10566
10565
|
}
|
|
10567
10566
|
handleEvent(event) {
|
|
10567
|
+
if (event.type === "message_end") this.onUsageUpdate(event, (/* @__PURE__ */ new Date()).toISOString());
|
|
10568
10568
|
const payload = eventPayload(event);
|
|
10569
10569
|
this.recordHistoryEvent(`pi-${event.type}`, payload, this.historyFile);
|
|
10570
10570
|
}
|
|
@@ -10597,7 +10597,7 @@ var PiManager = class extends CodingAgentManager {
|
|
|
10597
10597
|
|
|
10598
10598
|
// src/managers/relay-tools.ts
|
|
10599
10599
|
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
10600
|
-
import { z as
|
|
10600
|
+
import { z as z4 } from "zod";
|
|
10601
10601
|
|
|
10602
10602
|
// src/managers/relay-providers.ts
|
|
10603
10603
|
function isRelaySubagentProviderAllowed(provider, allowedProviders) {
|
|
@@ -10727,7 +10727,7 @@ async function getChatFinalResponse(chatId) {
|
|
|
10727
10727
|
return extractFinalResponse(history) ?? "[No response from subagent]";
|
|
10728
10728
|
}
|
|
10729
10729
|
function buildSpawnAgentTool(parentChatId, availability = {}, getAllowedProviders = () => monolithService.getRelaySubagentProviders(), getProviderAvailability = () => availability) {
|
|
10730
|
-
const providerEnum =
|
|
10730
|
+
const providerEnum = z4.enum(VALID_RELAY_SUBAGENT_PROVIDERS);
|
|
10731
10731
|
return tool(
|
|
10732
10732
|
"spawn_agent",
|
|
10733
10733
|
`Spawn a new subagent to perform a task. The subagent runs in its own chat with a fresh context window.
|
|
@@ -10742,8 +10742,8 @@ The tool blocks until the subagent completes and returns its final response.
|
|
|
10742
10742
|
You will also receive the chatId so you can send follow-up messages or clean up the chat.`,
|
|
10743
10743
|
{
|
|
10744
10744
|
provider: providerEnum.describe("Which agent to use. Choose from the providers listed as currently available in Relay guidance. Prefer an available code provider for code writing, claude for exploration/analysis, and relay for complex multi-step orchestration."),
|
|
10745
|
-
prompt:
|
|
10746
|
-
model:
|
|
10745
|
+
prompt: z4.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
|
|
10746
|
+
model: z4.string().optional().describe([
|
|
10747
10747
|
`Model override. Claude: ${AGENT_MODELS.claude.join(", ")} (opus is the default; sonnet is faster).`,
|
|
10748
10748
|
`Codex: ${AGENT_MODELS.codex.join(", ")}.`,
|
|
10749
10749
|
`Cursor: ${AGENT_MODELS.cursor.join(", ")}.`,
|
|
@@ -10753,11 +10753,11 @@ You will also receive the chatId so you can send follow-up messages or clean up
|
|
|
10753
10753
|
`Opencode: ${AGENT_MODELS.opencode.join(", ")}.`,
|
|
10754
10754
|
`Pi: ${AGENT_MODELS.pi.join(", ")}.`
|
|
10755
10755
|
].join(" ")),
|
|
10756
|
-
thinking_level:
|
|
10756
|
+
thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
|
|
10757
10757
|
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high; Codex, Cursor, DeepSeek Harness, fx, Kimi Code, Opencode, and Pi = medium."
|
|
10758
10758
|
),
|
|
10759
|
-
title:
|
|
10760
|
-
timeout_minutes:
|
|
10759
|
+
title: z4.string().optional().describe("Optional title for the subagent chat (for identification)."),
|
|
10760
|
+
timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
|
|
10761
10761
|
},
|
|
10762
10762
|
async (args) => {
|
|
10763
10763
|
try {
|
|
@@ -10838,13 +10838,13 @@ var messageAgentTool = tool(
|
|
|
10838
10838
|
|
|
10839
10839
|
The tool blocks until the subagent completes and returns its response.`,
|
|
10840
10840
|
{
|
|
10841
|
-
chatId:
|
|
10842
|
-
message:
|
|
10843
|
-
model:
|
|
10844
|
-
thinking_level:
|
|
10841
|
+
chatId: z4.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
|
|
10842
|
+
message: z4.string().describe("The follow-up message to send."),
|
|
10843
|
+
model: z4.string().optional().describe("Optional model override for this message."),
|
|
10844
|
+
thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
|
|
10845
10845
|
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
|
|
10846
10846
|
),
|
|
10847
|
-
timeout_minutes:
|
|
10847
|
+
timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
|
|
10848
10848
|
},
|
|
10849
10849
|
async (args) => {
|
|
10850
10850
|
try {
|
|
@@ -10882,7 +10882,7 @@ var deleteAgentTool = tool(
|
|
|
10882
10882
|
"delete_agent",
|
|
10883
10883
|
`Delete a subagent chat to free resources. Use this after a subagent has completed its work and you no longer need to send it follow-up messages.`,
|
|
10884
10884
|
{
|
|
10885
|
-
chatId:
|
|
10885
|
+
chatId: z4.string().describe("The chat ID of the subagent to delete.")
|
|
10886
10886
|
},
|
|
10887
10887
|
async (args) => {
|
|
10888
10888
|
try {
|
|
@@ -11248,6 +11248,17 @@ var RelayManager = class {
|
|
|
11248
11248
|
};
|
|
11249
11249
|
|
|
11250
11250
|
// src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
|
|
11251
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
11252
|
+
|
|
11253
|
+
// src/analytics/analytics-upload.ts
|
|
11254
|
+
async function uploadAnalytics(endpoint, body) {
|
|
11255
|
+
const response = await monolithRequest(endpoint, { body });
|
|
11256
|
+
if (!response.ok) {
|
|
11257
|
+
throw new Error(`Analytics upload failed: ${response.status} ${await response.text()}`);
|
|
11258
|
+
}
|
|
11259
|
+
}
|
|
11260
|
+
|
|
11261
|
+
// src/analytics/analytics-record-buffer.ts
|
|
11251
11262
|
import {
|
|
11252
11263
|
appendFile as appendFile3,
|
|
11253
11264
|
mkdir as mkdir16,
|
|
@@ -11256,51 +11267,13 @@ import {
|
|
|
11256
11267
|
rename as rename2,
|
|
11257
11268
|
unlink as unlink3
|
|
11258
11269
|
} from "fs/promises";
|
|
11270
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync } from "fs";
|
|
11259
11271
|
import { join as join24 } from "path";
|
|
11260
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
11261
|
-
|
|
11262
|
-
// src/analytics/agent/activity/skill-mcp-call-extractor.ts
|
|
11263
|
-
var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
|
|
11264
|
-
function mcpNameFromToolCall(message) {
|
|
11265
|
-
const parsedName = parseMcpToolName(message.tool);
|
|
11266
|
-
if (parsedName) return parsedName.server;
|
|
11267
|
-
if (message.tool.startsWith("mcp.")) {
|
|
11268
|
-
const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
|
|
11269
|
-
if (server && toolParts.length > 0) return server;
|
|
11270
|
-
}
|
|
11271
|
-
return NON_MCP_SERVERS.has(message.server) ? null : message.server;
|
|
11272
|
-
}
|
|
11273
|
-
function skillCallsFromCommand(message) {
|
|
11274
|
-
const skillNames = message.skillNames ?? [];
|
|
11275
|
-
return skillNames.map((skillName, index) => ({
|
|
11276
|
-
kind: "skill",
|
|
11277
|
-
id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
|
|
11278
|
-
skillName,
|
|
11279
|
-
occurredAt: message.timestamp
|
|
11280
|
-
}));
|
|
11281
|
-
}
|
|
11282
|
-
function extractSkillMcpCalls(messages, provider) {
|
|
11283
|
-
return messages.flatMap((message) => {
|
|
11284
|
-
if (message.type === "skill") {
|
|
11285
|
-
if (provider === "relay") return [];
|
|
11286
|
-
return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
|
|
11287
|
-
}
|
|
11288
|
-
if (message.type === "command") {
|
|
11289
|
-
return skillCallsFromCommand(message);
|
|
11290
|
-
}
|
|
11291
|
-
if (message.type !== "tool_call") return [];
|
|
11292
|
-
const mcpName = mcpNameFromToolCall(message);
|
|
11293
|
-
return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
|
|
11294
|
-
});
|
|
11295
|
-
}
|
|
11296
|
-
|
|
11297
|
-
// src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
|
|
11298
11272
|
var MAX_FAILED_RECORDS = 500;
|
|
11299
11273
|
var FLUSH_DEBOUNCE_MS = 15e3;
|
|
11300
11274
|
var MAX_UPLOADED_SEGMENTS = 50;
|
|
11301
11275
|
var UPLOADED_SUFFIX = ".uploaded";
|
|
11302
|
-
var
|
|
11303
|
-
var AgentChatActivityBuffer = class {
|
|
11276
|
+
var AnalyticsRecordBuffer = class {
|
|
11304
11277
|
constructor(options) {
|
|
11305
11278
|
this.options = options;
|
|
11306
11279
|
this.storageNames = [
|
|
@@ -11318,26 +11291,39 @@ var AgentChatActivityBuffer = class {
|
|
|
11318
11291
|
segmentFilePatterns;
|
|
11319
11292
|
failedRecords = [];
|
|
11320
11293
|
pendingAppends = /* @__PURE__ */ new Set();
|
|
11294
|
+
recordIds = /* @__PURE__ */ new Set();
|
|
11321
11295
|
flushTimer = null;
|
|
11322
11296
|
activeFlush = Promise.resolve({
|
|
11323
11297
|
flushed: 0,
|
|
11324
11298
|
failed: 0
|
|
11325
11299
|
});
|
|
11326
|
-
append(record) {
|
|
11327
|
-
const
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11300
|
+
append(record, retry = false) {
|
|
11301
|
+
const recordId = this.options.getRecordId?.(record);
|
|
11302
|
+
if (!retry && recordId) {
|
|
11303
|
+
if (this.recordIds.has(recordId)) return;
|
|
11304
|
+
this.recordIds.add(recordId);
|
|
11305
|
+
}
|
|
11306
|
+
const pending = mkdir16(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(this.liveFile, `${JSON.stringify(record)}
|
|
11307
|
+
`, "utf-8")).catch((error) => {
|
|
11331
11308
|
console.error(
|
|
11332
11309
|
`[${this.options.storageName}] Append failed, will retry on flush:`,
|
|
11333
11310
|
error
|
|
11334
11311
|
);
|
|
11335
|
-
if (this.failedRecords.length < MAX_FAILED_RECORDS)
|
|
11336
|
-
this.failedRecords.push(record);
|
|
11312
|
+
if (this.failedRecords.length < MAX_FAILED_RECORDS) this.failedRecords.push(record);
|
|
11337
11313
|
});
|
|
11338
11314
|
this.pendingAppends.add(pending);
|
|
11339
11315
|
void pending.finally(() => this.pendingAppends.delete(pending));
|
|
11340
11316
|
}
|
|
11317
|
+
readStoredRecords() {
|
|
11318
|
+
if (!existsSync8(ENGINE_DIR2)) return [];
|
|
11319
|
+
return readdirSync(ENGINE_DIR2, { withFileTypes: true }).filter((entry) => entry.isFile() && this.storageNames.some(
|
|
11320
|
+
(storageName) => entry.name === `${storageName}.jsonl` || entry.name.startsWith(`${storageName}.`) && (entry.name.endsWith(".jsonl") || entry.name.endsWith(`.jsonl${UPLOADED_SUFFIX}`))
|
|
11321
|
+
)).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync7(join24(ENGINE_DIR2, name), "utf-8"))).map((record) => {
|
|
11322
|
+
const recordId = this.options.getRecordId?.(record);
|
|
11323
|
+
if (recordId) this.recordIds.add(recordId);
|
|
11324
|
+
return record;
|
|
11325
|
+
});
|
|
11326
|
+
}
|
|
11341
11327
|
scheduleFlush() {
|
|
11342
11328
|
if (this.flushTimer) return;
|
|
11343
11329
|
this.flushTimer = setTimeout(() => {
|
|
@@ -11361,9 +11347,10 @@ var AgentChatActivityBuffer = class {
|
|
|
11361
11347
|
return this.activeFlush;
|
|
11362
11348
|
}
|
|
11363
11349
|
async runFlush() {
|
|
11364
|
-
for (const record of this.failedRecords.splice(0)) this.append(record);
|
|
11365
|
-
while (this.pendingAppends.size > 0)
|
|
11350
|
+
for (const record of this.failedRecords.splice(0)) this.append(record, true);
|
|
11351
|
+
while (this.pendingAppends.size > 0) {
|
|
11366
11352
|
await Promise.allSettled([...this.pendingAppends]);
|
|
11353
|
+
}
|
|
11367
11354
|
for (const storageName of this.storageNames) {
|
|
11368
11355
|
await rename2(
|
|
11369
11356
|
join24(ENGINE_DIR2, `${storageName}.jsonl`),
|
|
@@ -11375,8 +11362,7 @@ var AgentChatActivityBuffer = class {
|
|
|
11375
11362
|
let flushed = 0;
|
|
11376
11363
|
let failed = 0;
|
|
11377
11364
|
for (const entry of entries) {
|
|
11378
|
-
if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
|
|
11379
|
-
continue;
|
|
11365
|
+
if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry))) continue;
|
|
11380
11366
|
try {
|
|
11381
11367
|
await this.uploadSegment(join24(ENGINE_DIR2, entry));
|
|
11382
11368
|
flushed++;
|
|
@@ -11395,28 +11381,32 @@ var AgentChatActivityBuffer = class {
|
|
|
11395
11381
|
flushed++;
|
|
11396
11382
|
} catch (error) {
|
|
11397
11383
|
failed++;
|
|
11398
|
-
this.failedRecords.unshift(
|
|
11399
|
-
...failedRecords.slice(0, MAX_FAILED_RECORDS)
|
|
11400
|
-
);
|
|
11384
|
+
this.failedRecords.unshift(...failedRecords.slice(0, MAX_FAILED_RECORDS));
|
|
11401
11385
|
console.error(
|
|
11402
11386
|
`[${this.options.storageName}] Failed records upload failed, retained for retry:`,
|
|
11403
11387
|
error
|
|
11404
11388
|
);
|
|
11405
11389
|
}
|
|
11406
11390
|
}
|
|
11407
|
-
|
|
11408
|
-
(entry) => this.storageNames.some(
|
|
11391
|
+
if (!this.options.retainUploadedSegments) {
|
|
11392
|
+
const uploaded = entries.filter((entry) => this.storageNames.some(
|
|
11409
11393
|
(storageName) => entry.startsWith(`${storageName}.`)
|
|
11410
|
-
) && entry.endsWith(UPLOADED_SUFFIX)
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
}
|
|
11394
|
+
) && entry.endsWith(UPLOADED_SUFFIX)).sort();
|
|
11395
|
+
for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
|
|
11396
|
+
await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
|
|
11397
|
+
});
|
|
11398
|
+
}
|
|
11415
11399
|
}
|
|
11416
11400
|
return { flushed, failed };
|
|
11417
11401
|
}
|
|
11418
11402
|
async uploadSegment(filePath) {
|
|
11419
|
-
const records = (await readFile14(filePath, "utf-8"))
|
|
11403
|
+
const records = this.parseRecords(await readFile14(filePath, "utf-8"));
|
|
11404
|
+
if (records.length > 0) await this.options.upload(records);
|
|
11405
|
+
await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
|
|
11406
|
+
});
|
|
11407
|
+
}
|
|
11408
|
+
parseRecords(contents) {
|
|
11409
|
+
return contents.split("\n").flatMap((line) => {
|
|
11420
11410
|
try {
|
|
11421
11411
|
const parsed = JSON.parse(line);
|
|
11422
11412
|
return this.options.validate(parsed) ? [parsed] : [];
|
|
@@ -11424,16 +11414,51 @@ var AgentChatActivityBuffer = class {
|
|
|
11424
11414
|
return [];
|
|
11425
11415
|
}
|
|
11426
11416
|
});
|
|
11427
|
-
if (records.length > 0) await this.options.upload(records);
|
|
11428
|
-
await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
|
|
11429
|
-
});
|
|
11430
11417
|
}
|
|
11431
11418
|
};
|
|
11419
|
+
|
|
11420
|
+
// src/analytics/agent/activity/skill-mcp-call-extractor.ts
|
|
11421
|
+
var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
|
|
11422
|
+
function mcpNameFromToolCall(message) {
|
|
11423
|
+
const parsedName = parseMcpToolName(message.tool);
|
|
11424
|
+
if (parsedName) return parsedName.server;
|
|
11425
|
+
if (message.tool.startsWith("mcp.")) {
|
|
11426
|
+
const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
|
|
11427
|
+
if (server && toolParts.length > 0) return server;
|
|
11428
|
+
}
|
|
11429
|
+
return NON_MCP_SERVERS.has(message.server) ? null : message.server;
|
|
11430
|
+
}
|
|
11431
|
+
function skillCallsFromCommand(message) {
|
|
11432
|
+
const skillNames = message.skillNames ?? [];
|
|
11433
|
+
return skillNames.map((skillName, index) => ({
|
|
11434
|
+
kind: "skill",
|
|
11435
|
+
id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
|
|
11436
|
+
skillName,
|
|
11437
|
+
occurredAt: message.timestamp
|
|
11438
|
+
}));
|
|
11439
|
+
}
|
|
11440
|
+
function extractSkillMcpCalls(messages, provider) {
|
|
11441
|
+
return messages.flatMap((message) => {
|
|
11442
|
+
if (message.type === "skill") {
|
|
11443
|
+
if (provider === "relay") return [];
|
|
11444
|
+
return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
|
|
11445
|
+
}
|
|
11446
|
+
if (message.type === "command") {
|
|
11447
|
+
return skillCallsFromCommand(message);
|
|
11448
|
+
}
|
|
11449
|
+
if (message.type !== "tool_call") return [];
|
|
11450
|
+
const mcpName = mcpNameFromToolCall(message);
|
|
11451
|
+
return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
|
|
11452
|
+
});
|
|
11453
|
+
}
|
|
11454
|
+
|
|
11455
|
+
// src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
|
|
11456
|
+
var MAX_TRACKED_MESSAGES = 500;
|
|
11432
11457
|
var AgentChatActivityRecordTracker = class {
|
|
11433
11458
|
pendingRecords = /* @__PURE__ */ new Map();
|
|
11434
11459
|
buffer;
|
|
11435
11460
|
constructor(options) {
|
|
11436
|
-
this.buffer = new
|
|
11461
|
+
this.buffer = new AnalyticsRecordBuffer(options);
|
|
11437
11462
|
}
|
|
11438
11463
|
track(chatId, record) {
|
|
11439
11464
|
const records = this.pendingRecords.get(chatId) ?? [];
|
|
@@ -11456,17 +11481,20 @@ var AgentChatTurnActivityTracker = class {
|
|
|
11456
11481
|
activeTurns = /* @__PURE__ */ new Map();
|
|
11457
11482
|
turnBuffer;
|
|
11458
11483
|
constructor(uploadTurns) {
|
|
11459
|
-
this.turnBuffer = new
|
|
11484
|
+
this.turnBuffer = new AnalyticsRecordBuffer({
|
|
11460
11485
|
storageName: "chat-turn-activity",
|
|
11461
11486
|
legacyStorageNames: ["turn-usage"],
|
|
11462
11487
|
validate: isAgentChatTurnActivityRecord,
|
|
11463
|
-
upload: uploadTurns
|
|
11488
|
+
upload: uploadTurns,
|
|
11489
|
+
getRecordId: (turn) => turn.turnId
|
|
11464
11490
|
});
|
|
11465
11491
|
}
|
|
11466
11492
|
noteMessageAccepted(messageId, request) {
|
|
11467
11493
|
this.pendingMessages.set(messageId, {
|
|
11468
11494
|
model: request.model,
|
|
11469
|
-
senderUserId: request.senderUserId
|
|
11495
|
+
senderUserId: request.senderUserId,
|
|
11496
|
+
automationId: request.automationId,
|
|
11497
|
+
environmentId: request.environmentId
|
|
11470
11498
|
});
|
|
11471
11499
|
for (const key of this.pendingMessages.keys()) {
|
|
11472
11500
|
if (this.pendingMessages.size <= MAX_TRACKED_MESSAGES) break;
|
|
@@ -11477,16 +11505,23 @@ var AgentChatTurnActivityTracker = class {
|
|
|
11477
11505
|
const attributes = this.pendingMessages.get(messageId) ?? {};
|
|
11478
11506
|
const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
|
|
11479
11507
|
this.pendingMessages.delete(messageId);
|
|
11480
|
-
|
|
11508
|
+
const context = {
|
|
11509
|
+
chatId,
|
|
11481
11510
|
turnId: randomUUID8(),
|
|
11482
|
-
startedAtMs: Date.now(),
|
|
11483
11511
|
provider,
|
|
11484
11512
|
model: attributes.model ?? getDefaultAgentModel(provider),
|
|
11485
11513
|
senderUserId: attributes.senderUserId,
|
|
11514
|
+
automationId: attributes.automationId,
|
|
11515
|
+
environmentId: attributes.environmentId,
|
|
11486
11516
|
credentialMethod: credential?.method,
|
|
11487
|
-
credentialScope: credential?.scope
|
|
11517
|
+
credentialScope: credential?.scope
|
|
11518
|
+
};
|
|
11519
|
+
this.activeTurns.set(chatId, {
|
|
11520
|
+
...context,
|
|
11521
|
+
startedAtMs: Date.now(),
|
|
11488
11522
|
seenCallIds: /* @__PURE__ */ new Set()
|
|
11489
11523
|
});
|
|
11524
|
+
return context;
|
|
11490
11525
|
}
|
|
11491
11526
|
getActiveTurn(chatId) {
|
|
11492
11527
|
return this.activeTurns.get(chatId);
|
|
@@ -11515,39 +11550,32 @@ var AgentChatTurnActivityTracker = class {
|
|
|
11515
11550
|
return this.turnBuffer.flush();
|
|
11516
11551
|
}
|
|
11517
11552
|
};
|
|
11518
|
-
async function uploadAgentChatActivity(endpoint, body) {
|
|
11519
|
-
const response = await monolithRequest(endpoint, { body });
|
|
11520
|
-
if (!response.ok)
|
|
11521
|
-
throw new Error(
|
|
11522
|
-
`upload failed: ${response.status} ${await response.text()}`
|
|
11523
|
-
);
|
|
11524
|
-
}
|
|
11525
11553
|
var AgentChatActivityTrackerService = class {
|
|
11526
11554
|
turnActivityTracker;
|
|
11527
11555
|
skillActivityTracker;
|
|
11528
11556
|
mcpActivityTracker;
|
|
11529
11557
|
constructor() {
|
|
11530
11558
|
this.turnActivityTracker = new AgentChatTurnActivityTracker(
|
|
11531
|
-
(turns) =>
|
|
11559
|
+
(turns) => uploadAnalytics("/v1/engine/chat-turn-activity", { turns })
|
|
11532
11560
|
);
|
|
11533
11561
|
this.skillActivityTracker = new AgentChatActivityRecordTracker({
|
|
11534
11562
|
storageName: "skill-activity",
|
|
11535
11563
|
legacyStorageNames: ["skill-usage"],
|
|
11536
11564
|
validate: isAgentChatSkillActivityRecord,
|
|
11537
|
-
upload: (skills) =>
|
|
11565
|
+
upload: (skills) => uploadAnalytics("/v1/engine/skill-activity", { skills })
|
|
11538
11566
|
});
|
|
11539
11567
|
this.mcpActivityTracker = new AgentChatActivityRecordTracker({
|
|
11540
11568
|
storageName: "mcp-activity",
|
|
11541
11569
|
legacyStorageNames: ["mcp-usage"],
|
|
11542
11570
|
validate: isAgentChatMcpActivityRecord,
|
|
11543
|
-
upload: (mcps) =>
|
|
11571
|
+
upload: (mcps) => uploadAnalytics("/v1/engine/mcp-activity", { mcps })
|
|
11544
11572
|
});
|
|
11545
11573
|
}
|
|
11546
11574
|
noteMessageAccepted(messageId, request) {
|
|
11547
11575
|
this.turnActivityTracker.noteMessageAccepted(messageId, request);
|
|
11548
11576
|
}
|
|
11549
11577
|
noteTurnStarted(chatId, messageId, provider) {
|
|
11550
|
-
this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
|
|
11578
|
+
return this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
|
|
11551
11579
|
}
|
|
11552
11580
|
noteSkillMcpCalls(chatId, messages) {
|
|
11553
11581
|
const turn = this.turnActivityTracker.getActiveTurn(chatId);
|
|
@@ -11597,6 +11625,523 @@ var AgentChatActivityTrackerService = class {
|
|
|
11597
11625
|
}
|
|
11598
11626
|
};
|
|
11599
11627
|
|
|
11628
|
+
// src/analytics/token-usage/agent-token-usage-tracker.ts
|
|
11629
|
+
import { createHash as createHash2 } from "crypto";
|
|
11630
|
+
|
|
11631
|
+
// src/analytics/token-usage/types.ts
|
|
11632
|
+
function calculateCumulativeTokenUsageDelta(current, previous) {
|
|
11633
|
+
if (!previous || current.inputTokens < previous.inputTokens || current.outputTokens < previous.outputTokens || current.cacheReadInputTokens < previous.cacheReadInputTokens || current.cacheWriteInputTokens < previous.cacheWriteInputTokens) return current;
|
|
11634
|
+
return {
|
|
11635
|
+
inputTokens: current.inputTokens - previous.inputTokens,
|
|
11636
|
+
outputTokens: current.outputTokens - previous.outputTokens,
|
|
11637
|
+
cacheReadInputTokens: current.cacheReadInputTokens - previous.cacheReadInputTokens,
|
|
11638
|
+
cacheWriteInputTokens: current.cacheWriteInputTokens - previous.cacheWriteInputTokens
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11641
|
+
|
|
11642
|
+
// src/analytics/token-usage/claude/parse.ts
|
|
11643
|
+
function isClaudeModelUsage(value) {
|
|
11644
|
+
if (!isRecord(value) || !isTokenUsageCounts(value)) return false;
|
|
11645
|
+
return (value.costUsd === void 0 || typeof value.costUsd === "number" && Number.isFinite(value.costUsd) && value.costUsd >= 0) && (value.pricingProvider === void 0 || isNonEmptyString(value.pricingProvider));
|
|
11646
|
+
}
|
|
11647
|
+
function parseClaudeResult(payload) {
|
|
11648
|
+
if (!isRecord(payload) || payload.type !== "result") return null;
|
|
11649
|
+
if (!isNonEmptyString(payload.session_id) || !isRecord(payload.modelUsage)) return null;
|
|
11650
|
+
const snapshot = {};
|
|
11651
|
+
for (const [rawModel, rawUsage] of Object.entries(payload.modelUsage)) {
|
|
11652
|
+
if (!isNonEmptyString(rawModel) || !isRecord(rawUsage)) continue;
|
|
11653
|
+
const usage = {
|
|
11654
|
+
inputTokens: rawUsage.inputTokens,
|
|
11655
|
+
outputTokens: rawUsage.outputTokens,
|
|
11656
|
+
cacheReadInputTokens: rawUsage.cacheReadInputTokens,
|
|
11657
|
+
cacheWriteInputTokens: rawUsage.cacheCreationInputTokens,
|
|
11658
|
+
...typeof rawUsage.costUSD === "number" && Number.isFinite(rawUsage.costUSD) && rawUsage.costUSD >= 0 ? { costUsd: rawUsage.costUSD } : {},
|
|
11659
|
+
...isNonEmptyString(rawUsage.provider) ? { pricingProvider: rawUsage.provider } : {}
|
|
11660
|
+
};
|
|
11661
|
+
if (!isClaudeModelUsage(usage)) continue;
|
|
11662
|
+
const model = isNonEmptyString(rawUsage.canonicalModel) ? rawUsage.canonicalModel : rawModel.replace(/-\d{8}$/, "");
|
|
11663
|
+
const previous = snapshot[model];
|
|
11664
|
+
snapshot[model] = previous ? {
|
|
11665
|
+
inputTokens: previous.inputTokens + usage.inputTokens,
|
|
11666
|
+
outputTokens: previous.outputTokens + usage.outputTokens,
|
|
11667
|
+
cacheReadInputTokens: previous.cacheReadInputTokens + usage.cacheReadInputTokens,
|
|
11668
|
+
cacheWriteInputTokens: previous.cacheWriteInputTokens + usage.cacheWriteInputTokens,
|
|
11669
|
+
...previous.costUsd !== void 0 || usage.costUsd !== void 0 ? { costUsd: (previous.costUsd ?? 0) + (usage.costUsd ?? 0) } : {},
|
|
11670
|
+
...usage.pricingProvider ?? previous.pricingProvider ? { pricingProvider: usage.pricingProvider ?? previous.pricingProvider } : {}
|
|
11671
|
+
} : usage;
|
|
11672
|
+
}
|
|
11673
|
+
if (Object.keys(snapshot).length === 0) return null;
|
|
11674
|
+
return {
|
|
11675
|
+
sessionId: payload.session_id,
|
|
11676
|
+
snapshot
|
|
11677
|
+
};
|
|
11678
|
+
}
|
|
11679
|
+
|
|
11680
|
+
// src/analytics/token-usage/claude/index.ts
|
|
11681
|
+
function isClaudeTokenUsageCheckpoint(value) {
|
|
11682
|
+
return isRecord(value) && isNonEmptyString(value.chatId) && isNonEmptyString(value.sessionId) && isRecord(value.snapshot) && Object.values(value.snapshot).every(isClaudeModelUsage);
|
|
11683
|
+
}
|
|
11684
|
+
function normalizeClaudeTokenUsage(result, previous, context, occurredAt) {
|
|
11685
|
+
return Object.entries(result.snapshot).flatMap(([model, currentMetrics]) => {
|
|
11686
|
+
const previousMetrics = previous?.[model];
|
|
11687
|
+
const tokenCounts = calculateCumulativeTokenUsageDelta(currentMetrics, previousMetrics);
|
|
11688
|
+
const providerCostUsd = currentMetrics.costUsd === void 0 ? void 0 : previousMetrics?.costUsd === void 0 || currentMetrics.costUsd < previousMetrics.costUsd ? currentMetrics.costUsd : currentMetrics.costUsd - previousMetrics.costUsd;
|
|
11689
|
+
const pricingProvider = currentMetrics.pricingProvider ?? previousMetrics?.pricingProvider;
|
|
11690
|
+
return Object.values(tokenCounts).some((count) => count > 0) ? [{
|
|
11691
|
+
...context,
|
|
11692
|
+
model,
|
|
11693
|
+
...tokenCounts,
|
|
11694
|
+
harness: AGENT.CLAUDE,
|
|
11695
|
+
occurredAt,
|
|
11696
|
+
...pricingProvider ? { pricingProvider } : {},
|
|
11697
|
+
...providerCostUsd !== void 0 ? { reportedCostUsd: providerCostUsd } : {}
|
|
11698
|
+
}] : [];
|
|
11699
|
+
});
|
|
11700
|
+
}
|
|
11701
|
+
var ClaudeTokenUsageService = class {
|
|
11702
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
11703
|
+
record(event) {
|
|
11704
|
+
if (event.type !== "usage-updated") return void 0;
|
|
11705
|
+
const result = parseClaudeResult(event.payload);
|
|
11706
|
+
if (!result) return void 0;
|
|
11707
|
+
const snapshotsBySession = this.snapshots.get(event.chatId) ?? /* @__PURE__ */ new Map();
|
|
11708
|
+
this.snapshots.set(event.chatId, snapshotsBySession);
|
|
11709
|
+
const previous = snapshotsBySession.get(result.sessionId);
|
|
11710
|
+
const records = normalizeClaudeTokenUsage(
|
|
11711
|
+
result,
|
|
11712
|
+
previous,
|
|
11713
|
+
event.context,
|
|
11714
|
+
event.occurredAt
|
|
11715
|
+
);
|
|
11716
|
+
snapshotsBySession.set(result.sessionId, result.snapshot);
|
|
11717
|
+
return {
|
|
11718
|
+
records,
|
|
11719
|
+
checkpoint: {
|
|
11720
|
+
chatId: event.chatId,
|
|
11721
|
+
sessionId: result.sessionId,
|
|
11722
|
+
snapshot: result.snapshot
|
|
11723
|
+
}
|
|
11724
|
+
};
|
|
11725
|
+
}
|
|
11726
|
+
restoreCheckpoint(value) {
|
|
11727
|
+
if (!isClaudeTokenUsageCheckpoint(value)) return;
|
|
11728
|
+
const snapshotsBySession = this.snapshots.get(value.chatId) ?? /* @__PURE__ */ new Map();
|
|
11729
|
+
snapshotsBySession.set(value.sessionId, value.snapshot);
|
|
11730
|
+
this.snapshots.set(value.chatId, snapshotsBySession);
|
|
11731
|
+
}
|
|
11732
|
+
};
|
|
11733
|
+
|
|
11734
|
+
// src/analytics/token-usage/codex/parse.ts
|
|
11735
|
+
function isTokenCount(value) {
|
|
11736
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
11737
|
+
}
|
|
11738
|
+
function parseCodexTurn(value) {
|
|
11739
|
+
if (!isRecord(value) || !isNonEmptyString(value.threadId) || !isNonEmptyString(value.turnId)) return void 0;
|
|
11740
|
+
return { threadId: value.threadId, turnId: value.turnId };
|
|
11741
|
+
}
|
|
11742
|
+
function parseCodexUsage(value) {
|
|
11743
|
+
if (!isRecord(value) || !isTokenCount(value.inputTokens) || !isTokenCount(value.cachedInputTokens) || !isTokenCount(value.outputTokens)) {
|
|
11744
|
+
return void 0;
|
|
11745
|
+
}
|
|
11746
|
+
const cacheWriteInputTokens = value.cacheWriteInputTokens;
|
|
11747
|
+
if (cacheWriteInputTokens !== void 0 && !isTokenCount(cacheWriteInputTokens)) return void 0;
|
|
11748
|
+
return {
|
|
11749
|
+
inputTokens: value.inputTokens,
|
|
11750
|
+
cachedInputTokens: value.cachedInputTokens,
|
|
11751
|
+
outputTokens: value.outputTokens,
|
|
11752
|
+
...cacheWriteInputTokens === void 0 ? {} : { cacheWriteInputTokens }
|
|
11753
|
+
};
|
|
11754
|
+
}
|
|
11755
|
+
function parseCodexUsageUpdate(value) {
|
|
11756
|
+
if (!isRecord(value)) return void 0;
|
|
11757
|
+
const turn = parseCodexTurn(value);
|
|
11758
|
+
const usage = isRecord(value.tokenUsage) ? parseCodexUsage(value.tokenUsage.last) : void 0;
|
|
11759
|
+
if (!turn || !usage) return void 0;
|
|
11760
|
+
return { ...turn, usage };
|
|
11761
|
+
}
|
|
11762
|
+
function parseCodexModelReroute(value) {
|
|
11763
|
+
if (!isRecord(value) || !isNonEmptyString(value.toModel)) return void 0;
|
|
11764
|
+
const turn = parseCodexTurn(value);
|
|
11765
|
+
return turn ? { ...turn, model: value.toModel } : void 0;
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
// src/analytics/token-usage/codex/index.ts
|
|
11769
|
+
var CodexTokenUsageAdapter = class {
|
|
11770
|
+
constructor(chatId, getContext, recordEvent) {
|
|
11771
|
+
this.chatId = chatId;
|
|
11772
|
+
this.getContext = getContext;
|
|
11773
|
+
this.recordEvent = recordEvent;
|
|
11774
|
+
}
|
|
11775
|
+
chatId;
|
|
11776
|
+
getContext;
|
|
11777
|
+
recordEvent;
|
|
11778
|
+
onTurnStarted = (threadId, turnId) => {
|
|
11779
|
+
const context = this.getContext();
|
|
11780
|
+
if (!context) return;
|
|
11781
|
+
this.recordEvent({
|
|
11782
|
+
harness: AGENT.CODEX,
|
|
11783
|
+
type: "turn-started",
|
|
11784
|
+
chatId: this.chatId,
|
|
11785
|
+
payload: { threadId, turnId },
|
|
11786
|
+
context
|
|
11787
|
+
});
|
|
11788
|
+
};
|
|
11789
|
+
onTokenUsage = (notification, occurredAt) => {
|
|
11790
|
+
const context = this.getContext();
|
|
11791
|
+
if (!context) return;
|
|
11792
|
+
this.recordEvent({
|
|
11793
|
+
harness: AGENT.CODEX,
|
|
11794
|
+
type: "usage-updated",
|
|
11795
|
+
chatId: this.chatId,
|
|
11796
|
+
payload: notification,
|
|
11797
|
+
context,
|
|
11798
|
+
occurredAt
|
|
11799
|
+
});
|
|
11800
|
+
};
|
|
11801
|
+
onModelRerouted = (notification) => {
|
|
11802
|
+
if (!this.getContext()) return;
|
|
11803
|
+
this.recordEvent({
|
|
11804
|
+
harness: AGENT.CODEX,
|
|
11805
|
+
type: "model-rerouted",
|
|
11806
|
+
chatId: this.chatId,
|
|
11807
|
+
payload: notification
|
|
11808
|
+
});
|
|
11809
|
+
};
|
|
11810
|
+
onTurnCompleted = (threadId, turnId) => {
|
|
11811
|
+
this.recordEvent({
|
|
11812
|
+
harness: AGENT.CODEX,
|
|
11813
|
+
type: "turn-completed",
|
|
11814
|
+
chatId: this.chatId,
|
|
11815
|
+
payload: { threadId, turnId }
|
|
11816
|
+
});
|
|
11817
|
+
};
|
|
11818
|
+
};
|
|
11819
|
+
function getTurnKey({ threadId, turnId }) {
|
|
11820
|
+
return `${threadId}\0${turnId}`;
|
|
11821
|
+
}
|
|
11822
|
+
var CodexTokenUsageService = class {
|
|
11823
|
+
turns = /* @__PURE__ */ new Map();
|
|
11824
|
+
record(event) {
|
|
11825
|
+
switch (event.type) {
|
|
11826
|
+
case "turn-started": {
|
|
11827
|
+
const turn = parseCodexTurn(event.payload);
|
|
11828
|
+
if (!turn) return void 0;
|
|
11829
|
+
const key = getTurnKey(turn);
|
|
11830
|
+
if (!this.turns.has(key)) {
|
|
11831
|
+
this.turns.set(key, { ...turn, context: event.context });
|
|
11832
|
+
}
|
|
11833
|
+
return void 0;
|
|
11834
|
+
}
|
|
11835
|
+
case "usage-updated": {
|
|
11836
|
+
const update = parseCodexUsageUpdate(event.payload);
|
|
11837
|
+
if (!update) return void 0;
|
|
11838
|
+
const key = getTurnKey(update);
|
|
11839
|
+
const turn = this.turns.get(key) ?? {
|
|
11840
|
+
...update,
|
|
11841
|
+
context: event.context
|
|
11842
|
+
};
|
|
11843
|
+
turn.latestUsage = update.usage;
|
|
11844
|
+
turn.occurredAt = event.occurredAt;
|
|
11845
|
+
this.turns.set(key, turn);
|
|
11846
|
+
return void 0;
|
|
11847
|
+
}
|
|
11848
|
+
case "model-rerouted": {
|
|
11849
|
+
const reroute = parseCodexModelReroute(event.payload);
|
|
11850
|
+
if (!reroute) return void 0;
|
|
11851
|
+
const tracked = this.turns.get(getTurnKey(reroute));
|
|
11852
|
+
if (tracked) tracked.context = { ...tracked.context, model: reroute.model };
|
|
11853
|
+
return void 0;
|
|
11854
|
+
}
|
|
11855
|
+
case "turn-completed": {
|
|
11856
|
+
const turn = parseCodexTurn(event.payload);
|
|
11857
|
+
return turn ? this.finishTurn(event.chatId, turn) : void 0;
|
|
11858
|
+
}
|
|
11859
|
+
case "chat-ended": {
|
|
11860
|
+
const records = [];
|
|
11861
|
+
for (const turn of this.turns.values()) {
|
|
11862
|
+
if (turn.context.chatId !== event.chatId) continue;
|
|
11863
|
+
const batch = this.finishTurn(event.chatId, turn);
|
|
11864
|
+
if (batch) records.push(...batch.records);
|
|
11865
|
+
}
|
|
11866
|
+
return records.length > 0 ? { records } : void 0;
|
|
11867
|
+
}
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
finishTurn(chatId, turn) {
|
|
11871
|
+
const key = getTurnKey(turn);
|
|
11872
|
+
const tracked = this.turns.get(key);
|
|
11873
|
+
if (!tracked || tracked.context.chatId !== chatId) return void 0;
|
|
11874
|
+
this.turns.delete(key);
|
|
11875
|
+
if (!tracked.latestUsage || !tracked.occurredAt) return void 0;
|
|
11876
|
+
const cacheWriteInputTokens = tracked.latestUsage.cacheWriteInputTokens ?? 0;
|
|
11877
|
+
return {
|
|
11878
|
+
records: [{
|
|
11879
|
+
...tracked.context,
|
|
11880
|
+
harness: AGENT.CODEX,
|
|
11881
|
+
inputTokens: Math.max(
|
|
11882
|
+
0,
|
|
11883
|
+
tracked.latestUsage.inputTokens - tracked.latestUsage.cachedInputTokens - cacheWriteInputTokens
|
|
11884
|
+
),
|
|
11885
|
+
outputTokens: tracked.latestUsage.outputTokens,
|
|
11886
|
+
cacheReadInputTokens: tracked.latestUsage.cachedInputTokens,
|
|
11887
|
+
cacheWriteInputTokens,
|
|
11888
|
+
occurredAt: tracked.occurredAt
|
|
11889
|
+
}]
|
|
11890
|
+
};
|
|
11891
|
+
}
|
|
11892
|
+
};
|
|
11893
|
+
|
|
11894
|
+
// src/analytics/token-usage/cursor/index.ts
|
|
11895
|
+
function parseCursorTokenUsage(payload) {
|
|
11896
|
+
if (!isRecord(payload) || payload.type !== "turn-ended" || !isRecord(payload.usage)) return void 0;
|
|
11897
|
+
const usage = {
|
|
11898
|
+
inputTokens: payload.usage.inputTokens,
|
|
11899
|
+
outputTokens: payload.usage.outputTokens,
|
|
11900
|
+
cacheReadInputTokens: payload.usage.cacheReadTokens,
|
|
11901
|
+
cacheWriteInputTokens: payload.usage.cacheWriteTokens
|
|
11902
|
+
};
|
|
11903
|
+
return isTokenUsageCounts(usage) ? usage : void 0;
|
|
11904
|
+
}
|
|
11905
|
+
var CursorTokenUsageService = class {
|
|
11906
|
+
turns = /* @__PURE__ */ new Map();
|
|
11907
|
+
record(event) {
|
|
11908
|
+
if (event.type === "usage-updated") {
|
|
11909
|
+
const usage = parseCursorTokenUsage(event.payload);
|
|
11910
|
+
if (!usage) return void 0;
|
|
11911
|
+
const turnId2 = event.turnId ?? event.chatId;
|
|
11912
|
+
this.turns.set(`${event.chatId}\0${turnId2}`, {
|
|
11913
|
+
context: event.context,
|
|
11914
|
+
usage,
|
|
11915
|
+
occurredAt: event.occurredAt
|
|
11916
|
+
});
|
|
11917
|
+
return void 0;
|
|
11918
|
+
}
|
|
11919
|
+
if (event.type !== "chat-ended") return void 0;
|
|
11920
|
+
const turnId = event.turnId ?? event.chatId;
|
|
11921
|
+
const key = `${event.chatId}\0${turnId}`;
|
|
11922
|
+
const turn = this.turns.get(key);
|
|
11923
|
+
if (!turn) return void 0;
|
|
11924
|
+
this.turns.delete(key);
|
|
11925
|
+
const record = {
|
|
11926
|
+
...turn.context,
|
|
11927
|
+
harness: AGENT.CURSOR,
|
|
11928
|
+
...turn.usage,
|
|
11929
|
+
occurredAt: turn.occurredAt
|
|
11930
|
+
};
|
|
11931
|
+
return { records: [record] };
|
|
11932
|
+
}
|
|
11933
|
+
};
|
|
11934
|
+
|
|
11935
|
+
// src/analytics/token-usage/deepseek/index.ts
|
|
11936
|
+
function parseDeepseekTokenUsage(payload) {
|
|
11937
|
+
if (!isRecord(payload)) return void 0;
|
|
11938
|
+
const source = isRecord(payload.totals) ? payload.totals : payload;
|
|
11939
|
+
const snapshot = {
|
|
11940
|
+
inputTokens: source.uncachedInputTokens,
|
|
11941
|
+
outputTokens: source.outputTokens,
|
|
11942
|
+
cacheReadInputTokens: source.cacheReadTokens,
|
|
11943
|
+
cacheWriteInputTokens: source.cacheWriteTokens
|
|
11944
|
+
};
|
|
11945
|
+
return isTokenUsageCounts(snapshot) ? snapshot : void 0;
|
|
11946
|
+
}
|
|
11947
|
+
var DeepseekTokenUsageService = class {
|
|
11948
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
11949
|
+
record(event) {
|
|
11950
|
+
if (event.type !== "usage-updated") return void 0;
|
|
11951
|
+
const current = parseDeepseekTokenUsage(event.payload);
|
|
11952
|
+
if (!current) return void 0;
|
|
11953
|
+
const previous = this.snapshots.get(event.chatId);
|
|
11954
|
+
this.snapshots.set(event.chatId, current);
|
|
11955
|
+
const usage = calculateCumulativeTokenUsageDelta(current, previous);
|
|
11956
|
+
const records = Object.values(usage).some((count) => count > 0) ? [{ ...event.context, harness: AGENT.DEEPSEEK, ...usage, occurredAt: event.occurredAt }] : [];
|
|
11957
|
+
return {
|
|
11958
|
+
records,
|
|
11959
|
+
checkpoint: { chatId: event.chatId, snapshot: current }
|
|
11960
|
+
};
|
|
11961
|
+
}
|
|
11962
|
+
restoreCheckpoint(value) {
|
|
11963
|
+
if (!isRecord(value) || !isNonEmptyString(value.chatId) || !isTokenUsageCounts(value.snapshot)) return;
|
|
11964
|
+
this.snapshots.set(value.chatId, value.snapshot);
|
|
11965
|
+
}
|
|
11966
|
+
};
|
|
11967
|
+
|
|
11968
|
+
// src/analytics/token-usage/opencode/index.ts
|
|
11969
|
+
function parseOpencodeTokenUsage(payload) {
|
|
11970
|
+
if (!isRecord(payload) || payload.type !== "step-finish" || !isRecord(payload.tokens) || !isRecord(payload.tokens.cache)) return void 0;
|
|
11971
|
+
const usage = {
|
|
11972
|
+
inputTokens: payload.tokens.input,
|
|
11973
|
+
outputTokens: payload.tokens.output,
|
|
11974
|
+
cacheReadInputTokens: payload.tokens.cache.read,
|
|
11975
|
+
cacheWriteInputTokens: payload.tokens.cache.write
|
|
11976
|
+
};
|
|
11977
|
+
return isTokenUsageCounts(usage) ? usage : void 0;
|
|
11978
|
+
}
|
|
11979
|
+
var OpencodeTokenUsageService = class {
|
|
11980
|
+
record(event) {
|
|
11981
|
+
if (event.type !== "usage-updated") return void 0;
|
|
11982
|
+
const usage = parseOpencodeTokenUsage(event.payload);
|
|
11983
|
+
if (!usage) return void 0;
|
|
11984
|
+
const record = {
|
|
11985
|
+
...event.context,
|
|
11986
|
+
harness: AGENT.OPENCODE,
|
|
11987
|
+
...usage,
|
|
11988
|
+
occurredAt: event.occurredAt
|
|
11989
|
+
};
|
|
11990
|
+
return { records: [record] };
|
|
11991
|
+
}
|
|
11992
|
+
};
|
|
11993
|
+
|
|
11994
|
+
// src/analytics/token-usage/pi/index.ts
|
|
11995
|
+
function parsePiTokenUsage(payload) {
|
|
11996
|
+
if (!isRecord(payload) || payload.type !== "message_end" || !isRecord(payload.message) || payload.message.role !== "assistant" || !isRecord(payload.message.usage)) return void 0;
|
|
11997
|
+
const usage = {
|
|
11998
|
+
inputTokens: payload.message.usage.input,
|
|
11999
|
+
outputTokens: payload.message.usage.output,
|
|
12000
|
+
cacheReadInputTokens: payload.message.usage.cacheRead,
|
|
12001
|
+
cacheWriteInputTokens: payload.message.usage.cacheWrite
|
|
12002
|
+
};
|
|
12003
|
+
if (!isTokenUsageCounts(usage)) return void 0;
|
|
12004
|
+
const model = isNonEmptyString(payload.message.responseModel) ? payload.message.responseModel : isNonEmptyString(payload.message.model) ? payload.message.model : void 0;
|
|
12005
|
+
return { model, usage };
|
|
12006
|
+
}
|
|
12007
|
+
var PiTokenUsageService = class {
|
|
12008
|
+
record(event) {
|
|
12009
|
+
if (event.type !== "usage-updated") return void 0;
|
|
12010
|
+
const result = parsePiTokenUsage(event.payload);
|
|
12011
|
+
if (!result) return void 0;
|
|
12012
|
+
const record = {
|
|
12013
|
+
...event.context,
|
|
12014
|
+
harness: AGENT.PI,
|
|
12015
|
+
model: result.model ?? event.context.model,
|
|
12016
|
+
...result.usage,
|
|
12017
|
+
occurredAt: event.occurredAt
|
|
12018
|
+
};
|
|
12019
|
+
return { records: [record] };
|
|
12020
|
+
}
|
|
12021
|
+
};
|
|
12022
|
+
|
|
12023
|
+
// src/analytics/token-usage/agent-token-usage-factory.ts
|
|
12024
|
+
function createAgentTokenUsageService(harness) {
|
|
12025
|
+
switch (harness) {
|
|
12026
|
+
case AGENT.CLAUDE:
|
|
12027
|
+
return new ClaudeTokenUsageService();
|
|
12028
|
+
case AGENT.CODEX:
|
|
12029
|
+
return new CodexTokenUsageService();
|
|
12030
|
+
case AGENT.CURSOR:
|
|
12031
|
+
return new CursorTokenUsageService();
|
|
12032
|
+
case AGENT.DEEPSEEK:
|
|
12033
|
+
return new DeepseekTokenUsageService();
|
|
12034
|
+
case AGENT.OPENCODE:
|
|
12035
|
+
return new OpencodeTokenUsageService();
|
|
12036
|
+
case AGENT.PI:
|
|
12037
|
+
return new PiTokenUsageService();
|
|
12038
|
+
default:
|
|
12039
|
+
return void 0;
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
|
|
12043
|
+
// src/analytics/token-usage/agent-token-usage-tracker.ts
|
|
12044
|
+
var AgentTokenUsageTracker = class {
|
|
12045
|
+
activeTurns = /* @__PURE__ */ new Map();
|
|
12046
|
+
services = /* @__PURE__ */ new Map();
|
|
12047
|
+
buffer;
|
|
12048
|
+
constructor(uploadAnalytics2) {
|
|
12049
|
+
this.buffer = new AnalyticsRecordBuffer({
|
|
12050
|
+
storageName: "chat-token-usage",
|
|
12051
|
+
validate: (value) => isRecord(value) && isNonEmptyString(value.harness) && Array.isArray(value.records) && value.records.every(isTokenUsageUploadRecord),
|
|
12052
|
+
upload: (batches) => uploadAnalytics2(TOKEN_USAGE_PATH, {
|
|
12053
|
+
records: batches.flatMap(({ records }) => records)
|
|
12054
|
+
}),
|
|
12055
|
+
getRecordId: ({ harness, records }) => {
|
|
12056
|
+
const eventIds = records.flatMap((record) => record.eventId ? [record.eventId] : []);
|
|
12057
|
+
if (eventIds.length === 0 || eventIds.length !== records.length) return void 0;
|
|
12058
|
+
return [harness, ...eventIds].join("\0");
|
|
12059
|
+
},
|
|
12060
|
+
retainUploadedSegments: true
|
|
12061
|
+
});
|
|
12062
|
+
for (const batch of this.buffer.readStoredRecords()) {
|
|
12063
|
+
if (batch.checkpoint !== void 0) {
|
|
12064
|
+
this.getService(batch.harness)?.restoreCheckpoint?.(batch.checkpoint);
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
}
|
|
12068
|
+
startTurn(context) {
|
|
12069
|
+
this.activeTurns.set(context.chatId, context);
|
|
12070
|
+
}
|
|
12071
|
+
getTokenUsageContext(chatId) {
|
|
12072
|
+
const context = this.activeTurns.get(chatId);
|
|
12073
|
+
if (!context?.environmentId) return void 0;
|
|
12074
|
+
return {
|
|
12075
|
+
chatId: context.chatId,
|
|
12076
|
+
model: context.model,
|
|
12077
|
+
credentialMethod: context.credentialMethod,
|
|
12078
|
+
credentialScope: context.credentialScope,
|
|
12079
|
+
senderUserId: context.senderUserId,
|
|
12080
|
+
automationId: context.automationId,
|
|
12081
|
+
environmentId: context.environmentId
|
|
12082
|
+
};
|
|
12083
|
+
}
|
|
12084
|
+
noteAgentEvent(chatId, event) {
|
|
12085
|
+
if (event.type !== "claude-result") return;
|
|
12086
|
+
this.noteUsage(chatId, event.payload, event.timestamp);
|
|
12087
|
+
}
|
|
12088
|
+
noteUsage(chatId, payload, occurredAt) {
|
|
12089
|
+
const context = this.activeTurns.get(chatId);
|
|
12090
|
+
const tokenUsageContext = this.getTokenUsageContext(chatId);
|
|
12091
|
+
if (!context || !tokenUsageContext) return;
|
|
12092
|
+
this.recordEvent({
|
|
12093
|
+
harness: context.provider,
|
|
12094
|
+
type: "usage-updated",
|
|
12095
|
+
chatId,
|
|
12096
|
+
turnId: context.turnId,
|
|
12097
|
+
payload,
|
|
12098
|
+
context: tokenUsageContext,
|
|
12099
|
+
occurredAt
|
|
12100
|
+
});
|
|
12101
|
+
}
|
|
12102
|
+
endTurn(chatId) {
|
|
12103
|
+
const context = this.activeTurns.get(chatId);
|
|
12104
|
+
if (!context) return;
|
|
12105
|
+
this.recordEvent({
|
|
12106
|
+
harness: context.provider,
|
|
12107
|
+
type: "chat-ended",
|
|
12108
|
+
chatId,
|
|
12109
|
+
turnId: context.turnId
|
|
12110
|
+
});
|
|
12111
|
+
this.activeTurns.delete(chatId);
|
|
12112
|
+
}
|
|
12113
|
+
flush(finalizeInFlight = true) {
|
|
12114
|
+
if (finalizeInFlight) {
|
|
12115
|
+
for (const chatId of [...this.activeTurns.keys()]) this.endTurn(chatId);
|
|
12116
|
+
}
|
|
12117
|
+
return this.buffer.flush();
|
|
12118
|
+
}
|
|
12119
|
+
recordEvent(event) {
|
|
12120
|
+
const batch = this.getService(event.harness)?.record(event);
|
|
12121
|
+
if (!batch) return;
|
|
12122
|
+
this.buffer.append({
|
|
12123
|
+
harness: event.harness,
|
|
12124
|
+
records: batch.records.map((record) => ({
|
|
12125
|
+
...record,
|
|
12126
|
+
eventId: record.eventId ?? createUuidFromHex(createHash2("sha256").update(JSON.stringify([
|
|
12127
|
+
event.harness,
|
|
12128
|
+
event.type,
|
|
12129
|
+
event.chatId,
|
|
12130
|
+
event.turnId ?? "",
|
|
12131
|
+
record
|
|
12132
|
+
])).digest("hex"))
|
|
12133
|
+
})),
|
|
12134
|
+
checkpoint: batch.checkpoint
|
|
12135
|
+
});
|
|
12136
|
+
this.buffer.scheduleFlush();
|
|
12137
|
+
}
|
|
12138
|
+
getService(harness) {
|
|
12139
|
+
const service = this.services.get(harness) ?? createAgentTokenUsageService(harness);
|
|
12140
|
+
if (service) this.services.set(harness, service);
|
|
12141
|
+
return service;
|
|
12142
|
+
}
|
|
12143
|
+
};
|
|
12144
|
+
|
|
11600
12145
|
// src/services/keep-alive-service.ts
|
|
11601
12146
|
var KeepAliveService = class _KeepAliveService {
|
|
11602
12147
|
interval = null;
|
|
@@ -11872,7 +12417,7 @@ async function reconcileCanvasItems(filenames) {
|
|
|
11872
12417
|
|
|
11873
12418
|
// src/services/upload-chat-transcripts.ts
|
|
11874
12419
|
import { createReadStream } from "fs";
|
|
11875
|
-
import { createHash as
|
|
12420
|
+
import { createHash as createHash3 } from "crypto";
|
|
11876
12421
|
import { readFile as readFile16, readdir as readdir8, stat as stat4 } from "fs/promises";
|
|
11877
12422
|
import { basename as basename2, join as join27 } from "path";
|
|
11878
12423
|
|
|
@@ -11943,7 +12488,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
|
|
|
11943
12488
|
deletedAt: chat.deletedAt ?? null
|
|
11944
12489
|
} : {};
|
|
11945
12490
|
if (capture) {
|
|
11946
|
-
const hash =
|
|
12491
|
+
const hash = createHash3("sha256");
|
|
11947
12492
|
for await (const chunk of createReadStream(filePath)) hash.update(chunk);
|
|
11948
12493
|
metadata.captureId = capture.captureId;
|
|
11949
12494
|
metadata.captureReason = capture.reason;
|
|
@@ -12164,7 +12709,7 @@ function isCodexAvailable() {
|
|
|
12164
12709
|
return detectCodexAuthMethod() !== "none";
|
|
12165
12710
|
}
|
|
12166
12711
|
function isOpencodeAvailable() {
|
|
12167
|
-
return
|
|
12712
|
+
return existsSync9(OPENCODE_AUTH_PATH);
|
|
12168
12713
|
}
|
|
12169
12714
|
function isDeepseekAvailable() {
|
|
12170
12715
|
return Boolean(getDeepseekApiKey());
|
|
@@ -12176,7 +12721,7 @@ function isKimiAvailable() {
|
|
|
12176
12721
|
return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
|
|
12177
12722
|
}
|
|
12178
12723
|
function isPiAvailable() {
|
|
12179
|
-
return
|
|
12724
|
+
return existsSync9(PI_AUTH_PATH);
|
|
12180
12725
|
}
|
|
12181
12726
|
function isCursorAvailable() {
|
|
12182
12727
|
return Boolean(ENGINE_ENV.CURSOR_API_KEY);
|
|
@@ -12300,15 +12845,17 @@ function corruptChatsFilePath() {
|
|
|
12300
12845
|
return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
12301
12846
|
}
|
|
12302
12847
|
var ChatService = class {
|
|
12303
|
-
constructor(workingDirectory, agentChatActivityTrackerService2) {
|
|
12848
|
+
constructor(workingDirectory, agentChatActivityTrackerService2, agentTokenUsageTracker2) {
|
|
12304
12849
|
this.workingDirectory = workingDirectory;
|
|
12305
12850
|
this.agentChatActivityTrackerService = agentChatActivityTrackerService2;
|
|
12851
|
+
this.agentTokenUsageTracker = agentTokenUsageTracker2;
|
|
12306
12852
|
keepAliveService.setActivityCheck(
|
|
12307
12853
|
() => [...this.chats.values()].some((chat) => !chat.persisted.deletedAt && hasActiveAgentWork(chat))
|
|
12308
12854
|
);
|
|
12309
12855
|
}
|
|
12310
12856
|
workingDirectory;
|
|
12311
12857
|
agentChatActivityTrackerService;
|
|
12858
|
+
agentTokenUsageTracker;
|
|
12312
12859
|
chats = /* @__PURE__ */ new Map();
|
|
12313
12860
|
persistPromise = null;
|
|
12314
12861
|
persistQueued = false;
|
|
@@ -12528,7 +13075,7 @@ var ChatService = class {
|
|
|
12528
13075
|
async interrupt(chatId) {
|
|
12529
13076
|
const chat = this.requireChat(chatId);
|
|
12530
13077
|
const result = await chat.provider.interrupt();
|
|
12531
|
-
this.
|
|
13078
|
+
this.endTrackedTurn(chatId);
|
|
12532
13079
|
chat.hasActiveTurn = false;
|
|
12533
13080
|
chat.activeMessageId = null;
|
|
12534
13081
|
chat.pendingMessageIds = [];
|
|
@@ -12550,7 +13097,7 @@ var ChatService = class {
|
|
|
12550
13097
|
return { interrupted: false, queue: [], goal: null };
|
|
12551
13098
|
}
|
|
12552
13099
|
const interruptResult = await chat.provider.interrupt();
|
|
12553
|
-
this.
|
|
13100
|
+
this.endTrackedTurn(chatId);
|
|
12554
13101
|
chat.hasActiveTurn = false;
|
|
12555
13102
|
chat.activeMessageId = null;
|
|
12556
13103
|
chat.pendingMessageIds = [];
|
|
@@ -12677,7 +13224,7 @@ var ChatService = class {
|
|
|
12677
13224
|
messageId,
|
|
12678
13225
|
...chat.pendingMessageIds.filter((pendingMessageId) => pendingMessageId !== messageId && pendingMessageId !== interruptedMessageId)
|
|
12679
13226
|
];
|
|
12680
|
-
this.
|
|
13227
|
+
this.endTrackedTurn(chatId);
|
|
12681
13228
|
chat.hasActiveTurn = false;
|
|
12682
13229
|
chat.activeMessageId = null;
|
|
12683
13230
|
if (interruptedMessageId) {
|
|
@@ -12830,12 +13377,15 @@ var ChatService = class {
|
|
|
12830
13377
|
const chatsById = new Map(
|
|
12831
13378
|
[...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
|
|
12832
13379
|
);
|
|
12833
|
-
const [chatTranscripts, canvas, repoState, engineLogs, chatActivity] = await Promise.all([
|
|
13380
|
+
const [chatTranscripts, canvas, repoState, engineLogs, [chatActivity]] = await Promise.all([
|
|
12834
13381
|
flushAllChatTranscripts(chatsById, capture),
|
|
12835
13382
|
flushAllCanvasItems(),
|
|
12836
13383
|
flushRepoState(),
|
|
12837
13384
|
flushAllEngineLogs(),
|
|
12838
|
-
|
|
13385
|
+
Promise.all([
|
|
13386
|
+
this.agentChatActivityTrackerService.flush(),
|
|
13387
|
+
this.agentTokenUsageTracker.flush()
|
|
13388
|
+
])
|
|
12839
13389
|
]);
|
|
12840
13390
|
return {
|
|
12841
13391
|
chatTranscripts,
|
|
@@ -12865,6 +13415,9 @@ var ChatService = class {
|
|
|
12865
13415
|
const onProviderEvent = (event) => {
|
|
12866
13416
|
this.handleTurnEvent(persisted.id, event);
|
|
12867
13417
|
};
|
|
13418
|
+
const onProviderUsage = (payload, occurredAt) => {
|
|
13419
|
+
this.agentTokenUsageTracker.noteUsage(persisted.id, payload, occurredAt);
|
|
13420
|
+
};
|
|
12868
13421
|
const onMessageStarted = (message) => this.handleTurnStarted(persisted.id, message);
|
|
12869
13422
|
const onProcessingChanged = (processing) => {
|
|
12870
13423
|
if (processing) return;
|
|
@@ -12923,7 +13476,8 @@ var ChatService = class {
|
|
|
12923
13476
|
onTurnComplete: onProviderTurnComplete,
|
|
12924
13477
|
onEvent: onProviderEvent,
|
|
12925
13478
|
onProcessingChanged,
|
|
12926
|
-
onMessageStarted
|
|
13479
|
+
onMessageStarted,
|
|
13480
|
+
onUsageUpdate: onProviderUsage
|
|
12927
13481
|
});
|
|
12928
13482
|
} else if (persisted.provider === "deepseek") {
|
|
12929
13483
|
provider = new DeepseekManager({
|
|
@@ -12934,7 +13488,8 @@ var ChatService = class {
|
|
|
12934
13488
|
onTurnComplete: onProviderTurnComplete,
|
|
12935
13489
|
onEvent: onProviderEvent,
|
|
12936
13490
|
onProcessingChanged,
|
|
12937
|
-
onMessageStarted
|
|
13491
|
+
onMessageStarted,
|
|
13492
|
+
onUsageUpdate: onProviderUsage
|
|
12938
13493
|
});
|
|
12939
13494
|
} else if (persisted.provider === "fx") {
|
|
12940
13495
|
provider = new FxManager({
|
|
@@ -12967,7 +13522,8 @@ var ChatService = class {
|
|
|
12967
13522
|
onTurnComplete: onProviderTurnComplete,
|
|
12968
13523
|
onEvent: onProviderEvent,
|
|
12969
13524
|
onProcessingChanged,
|
|
12970
|
-
onMessageStarted
|
|
13525
|
+
onMessageStarted,
|
|
13526
|
+
onUsageUpdate: onProviderUsage
|
|
12971
13527
|
});
|
|
12972
13528
|
} else if (persisted.provider === "pi") {
|
|
12973
13529
|
provider = new PiManager({
|
|
@@ -12978,9 +13534,15 @@ var ChatService = class {
|
|
|
12978
13534
|
onTurnComplete: onProviderTurnComplete,
|
|
12979
13535
|
onEvent: onProviderEvent,
|
|
12980
13536
|
onProcessingChanged,
|
|
12981
|
-
onMessageStarted
|
|
13537
|
+
onMessageStarted,
|
|
13538
|
+
onUsageUpdate: onProviderUsage
|
|
12982
13539
|
});
|
|
12983
13540
|
} else {
|
|
13541
|
+
const codexTokenUsage = new CodexTokenUsageAdapter(
|
|
13542
|
+
persisted.id,
|
|
13543
|
+
() => this.agentTokenUsageTracker.getTokenUsageContext(persisted.id),
|
|
13544
|
+
(event) => this.agentTokenUsageTracker.recordEvent(event)
|
|
13545
|
+
);
|
|
12984
13546
|
provider = new CodexAspManager({
|
|
12985
13547
|
workingDirectory: this.workingDirectory,
|
|
12986
13548
|
historyFilePath: join29(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
@@ -12989,7 +13551,11 @@ var ChatService = class {
|
|
|
12989
13551
|
onTurnComplete: onProviderTurnComplete,
|
|
12990
13552
|
onEvent: onProviderEvent,
|
|
12991
13553
|
onProcessingChanged,
|
|
12992
|
-
onMessageStarted
|
|
13554
|
+
onMessageStarted,
|
|
13555
|
+
onCodexTurnStarted: codexTokenUsage.onTurnStarted,
|
|
13556
|
+
onCodexTokenUsage: codexTokenUsage.onTokenUsage,
|
|
13557
|
+
onCodexModelRerouted: codexTokenUsage.onModelRerouted,
|
|
13558
|
+
onCodexTurnCompleted: codexTokenUsage.onTurnCompleted
|
|
12993
13559
|
});
|
|
12994
13560
|
}
|
|
12995
13561
|
return {
|
|
@@ -13039,25 +13605,29 @@ var ChatService = class {
|
|
|
13039
13605
|
}
|
|
13040
13606
|
return chat;
|
|
13041
13607
|
}
|
|
13608
|
+
activateTurn(chat, messageId) {
|
|
13609
|
+
chat.hasActiveTurn = true;
|
|
13610
|
+
chat.activeMessageId = messageId;
|
|
13611
|
+
const context = this.agentChatActivityTrackerService.noteTurnStarted(
|
|
13612
|
+
chat.persisted.id,
|
|
13613
|
+
messageId,
|
|
13614
|
+
chat.persisted.provider
|
|
13615
|
+
);
|
|
13616
|
+
this.agentTokenUsageTracker.startTurn(context);
|
|
13617
|
+
chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
|
|
13618
|
+
chat.errorNotificationTargets.delete(messageId);
|
|
13619
|
+
}
|
|
13042
13620
|
async handleTurnStarted(chatId, message) {
|
|
13043
13621
|
const chat = this.getRuntimeChat(chatId);
|
|
13044
13622
|
if (!chat) return;
|
|
13045
13623
|
chat.pendingMessageIds = chat.pendingMessageIds.filter((messageId) => messageId !== message.id);
|
|
13046
|
-
chat.hasActiveTurn = true;
|
|
13047
|
-
chat.activeMessageId = message.id;
|
|
13048
13624
|
const { event: acceptedEvent, sender } = createTurnMetadata(
|
|
13049
13625
|
message,
|
|
13050
13626
|
message.id,
|
|
13051
13627
|
message.queuedAt
|
|
13052
13628
|
);
|
|
13053
13629
|
chat.acceptedUserEvents.set(message.id, acceptedEvent);
|
|
13054
|
-
this.
|
|
13055
|
-
chat.persisted.id,
|
|
13056
|
-
message.id,
|
|
13057
|
-
chat.persisted.provider
|
|
13058
|
-
);
|
|
13059
|
-
chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(message.id) ?? null;
|
|
13060
|
-
chat.errorNotificationTargets.delete(message.id);
|
|
13630
|
+
this.activateTurn(chat, message.id);
|
|
13061
13631
|
await this.publish({
|
|
13062
13632
|
type: "chat.turn.started",
|
|
13063
13633
|
payload: {
|
|
@@ -13074,6 +13644,22 @@ var ChatService = class {
|
|
|
13074
13644
|
return;
|
|
13075
13645
|
}
|
|
13076
13646
|
keepAliveService.noteActivity();
|
|
13647
|
+
if (!chat.hasActiveTurn && chat.pendingMessageIds.length > 0) {
|
|
13648
|
+
const messageId = chat.pendingMessageIds.shift();
|
|
13649
|
+
if (!messageId) {
|
|
13650
|
+
return;
|
|
13651
|
+
}
|
|
13652
|
+
this.activateTurn(chat, messageId);
|
|
13653
|
+
this.publish({
|
|
13654
|
+
type: "chat.turn.started",
|
|
13655
|
+
payload: {
|
|
13656
|
+
chatId: chat.persisted.id,
|
|
13657
|
+
messageId
|
|
13658
|
+
}
|
|
13659
|
+
}).catch(() => {
|
|
13660
|
+
});
|
|
13661
|
+
}
|
|
13662
|
+
this.agentTokenUsageTracker.noteAgentEvent(chatId, event);
|
|
13077
13663
|
const codexTranscript = getCodexTranscriptFromEvent(event);
|
|
13078
13664
|
if (chat.hasActiveTurn) {
|
|
13079
13665
|
const displayMessages = codexTranscript ? parseLatestCodexAspTranscriptTurn(codexTranscript) : parseDisplayMessages([event], chat.persisted.provider, null, {
|
|
@@ -13134,6 +13720,10 @@ var ChatService = class {
|
|
|
13134
13720
|
});
|
|
13135
13721
|
}
|
|
13136
13722
|
}
|
|
13723
|
+
endTrackedTurn(chatId) {
|
|
13724
|
+
this.agentTokenUsageTracker.endTurn(chatId);
|
|
13725
|
+
this.agentChatActivityTrackerService.noteTurnEnded(chatId);
|
|
13726
|
+
}
|
|
13137
13727
|
async handleTurnFinished(chatId) {
|
|
13138
13728
|
const chat = this.getRuntimeChat(chatId);
|
|
13139
13729
|
if (!chat) {
|
|
@@ -13146,7 +13736,7 @@ var ChatService = class {
|
|
|
13146
13736
|
}
|
|
13147
13737
|
chat.hasActiveTurn = false;
|
|
13148
13738
|
chat.activeMessageId = null;
|
|
13149
|
-
this.
|
|
13739
|
+
this.endTrackedTurn(chatId);
|
|
13150
13740
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13151
13741
|
const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
|
|
13152
13742
|
this.publish({
|
|
@@ -13584,14 +14174,14 @@ var RepoFileService = class {
|
|
|
13584
14174
|
|
|
13585
14175
|
// src/v1-routes.ts
|
|
13586
14176
|
import { Hono } from "hono";
|
|
13587
|
-
import { z as
|
|
14177
|
+
import { z as z5 } from "zod";
|
|
13588
14178
|
import { readdir as readdir11, stat as stat7, readFile as readFile21 } from "fs/promises";
|
|
13589
14179
|
import { join as join33, resolve as resolve3 } from "path";
|
|
13590
14180
|
|
|
13591
14181
|
// src/services/warm-hooks-service.ts
|
|
13592
14182
|
import { spawn as spawn6 } from "child_process";
|
|
13593
14183
|
import { readFile as readFile20 } from "fs/promises";
|
|
13594
|
-
import { existsSync as
|
|
14184
|
+
import { existsSync as existsSync10 } from "fs";
|
|
13595
14185
|
import { join as join32 } from "path";
|
|
13596
14186
|
|
|
13597
14187
|
// src/services/warm-hook-logs-service.ts
|
|
@@ -13713,7 +14303,7 @@ var warmHookLogsService = new WarmHookLogsService();
|
|
|
13713
14303
|
async function readRepoWarmHook(repoPath) {
|
|
13714
14304
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
13715
14305
|
const configPath = join32(repoPath, filename);
|
|
13716
|
-
if (!
|
|
14306
|
+
if (!existsSync10(configPath)) {
|
|
13717
14307
|
continue;
|
|
13718
14308
|
}
|
|
13719
14309
|
try {
|
|
@@ -13974,7 +14564,7 @@ ${combinedScript}` : combinedScript;
|
|
|
13974
14564
|
|
|
13975
14565
|
// src/services/terminal-service.ts
|
|
13976
14566
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
13977
|
-
import { existsSync as
|
|
14567
|
+
import { existsSync as existsSync11 } from "fs";
|
|
13978
14568
|
import { spawn as spawn7 } from "node-pty";
|
|
13979
14569
|
var MAX_REPLAY_CHARS = 1024 * 1024;
|
|
13980
14570
|
var MAX_TERMINAL_SESSIONS = 8;
|
|
@@ -13993,7 +14583,7 @@ var TerminalService = class {
|
|
|
13993
14583
|
});
|
|
13994
14584
|
}
|
|
13995
14585
|
const id = randomUUID10();
|
|
13996
|
-
const shell = process.env.SHELL &&
|
|
14586
|
+
const shell = process.env.SHELL && existsSync11(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
|
|
13997
14587
|
const pty = spawn7(shell, ["-l"], {
|
|
13998
14588
|
name: "xterm-256color",
|
|
13999
14589
|
cols,
|
|
@@ -14092,72 +14682,26 @@ var TerminalService = class {
|
|
|
14092
14682
|
var terminalService = new TerminalService();
|
|
14093
14683
|
|
|
14094
14684
|
// src/v1-routes.ts
|
|
14095
|
-
var
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
publicUrl: z6.string().min(1)
|
|
14099
|
-
});
|
|
14100
|
-
var terminalSizeSchema = z6.object({
|
|
14101
|
-
cols: z6.number().int().min(2).max(500),
|
|
14102
|
-
rows: z6.number().int().min(1).max(200)
|
|
14685
|
+
var createPreviewSchema = z5.object({
|
|
14686
|
+
port: z5.number().int().min(1).max(65535),
|
|
14687
|
+
publicUrl: z5.string().min(1)
|
|
14103
14688
|
});
|
|
14104
|
-
var
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
sequence: z6.number().int().nonnegative()
|
|
14689
|
+
var terminalSizeSchema = z5.object({
|
|
14690
|
+
cols: z5.number().int().min(2).max(500),
|
|
14691
|
+
rows: z5.number().int().min(1).max(200)
|
|
14108
14692
|
});
|
|
14109
|
-
var
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
model: z6.string().optional(),
|
|
14114
|
-
customInstructions: z6.string().optional(),
|
|
14115
|
-
planMode: z6.boolean().optional(),
|
|
14116
|
-
images: z6.array(z6.object({
|
|
14117
|
-
type: z6.literal("image"),
|
|
14118
|
-
source: z6.union([
|
|
14119
|
-
z6.object({
|
|
14120
|
-
type: z6.literal("base64"),
|
|
14121
|
-
media_type: imageMediaTypeSchema,
|
|
14122
|
-
data: z6.string().min(1)
|
|
14123
|
-
}),
|
|
14124
|
-
z6.object({
|
|
14125
|
-
type: z6.literal("url"),
|
|
14126
|
-
url: z6.string().url()
|
|
14127
|
-
})
|
|
14128
|
-
])
|
|
14129
|
-
})).optional(),
|
|
14130
|
-
thinkingLevel: z6.enum(VALID_THINKING_LEVELS).optional(),
|
|
14131
|
-
goalMode: z6.boolean().optional(),
|
|
14132
|
-
fastMode: z6.boolean().optional(),
|
|
14133
|
-
enableInteractiveTools: z6.boolean().optional(),
|
|
14134
|
-
type: z6.string().min(1).optional(),
|
|
14135
|
-
merge: z6.boolean().optional(),
|
|
14136
|
-
idempotencyKey: z6.string().min(1).max(128).optional(),
|
|
14137
|
-
senderUserId: z6.string().optional(),
|
|
14138
|
-
senderEmail: z6.string().optional(),
|
|
14139
|
-
senderDisplayName: z6.string().optional(),
|
|
14140
|
-
senderAvatarUrl: z6.string().optional(),
|
|
14141
|
-
errorNotificationTarget: z6.discriminatedUnion("type", [
|
|
14142
|
-
z6.object({ type: z6.literal("slack") }),
|
|
14143
|
-
z6.object({ type: z6.literal("linear"), sessionId: z6.string().min(1) }),
|
|
14144
|
-
z6.object({
|
|
14145
|
-
type: z6.literal("code_host"),
|
|
14146
|
-
provider: z6.enum(["github", "gitlab"]),
|
|
14147
|
-
resource: z6.enum(["issue", "pull_request"]),
|
|
14148
|
-
repositoryId: z6.string().min(1),
|
|
14149
|
-
resourceNumber: z6.number().int().positive()
|
|
14150
|
-
}),
|
|
14151
|
-
z6.object({ type: z6.literal("automation"), executionId: z6.string().min(1) })
|
|
14152
|
-
]).optional()
|
|
14693
|
+
var writeTerminalSessionSchema = z5.object({
|
|
14694
|
+
data: z5.string().max(64 * 1024),
|
|
14695
|
+
generation: z5.number().int().nonnegative(),
|
|
14696
|
+
sequence: z5.number().int().nonnegative()
|
|
14153
14697
|
});
|
|
14154
|
-
var respondToolInputSchema =
|
|
14155
|
-
requestId:
|
|
14156
|
-
selectionId:
|
|
14698
|
+
var respondToolInputSchema = z5.object({
|
|
14699
|
+
requestId: z5.string().min(1),
|
|
14700
|
+
selectionId: z5.string().min(1)
|
|
14157
14701
|
});
|
|
14158
|
-
var updateGoalSchema =
|
|
14159
|
-
objective:
|
|
14160
|
-
status:
|
|
14702
|
+
var updateGoalSchema = z5.object({
|
|
14703
|
+
objective: z5.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
|
|
14704
|
+
status: z5.enum(["active", "paused"]).optional()
|
|
14161
14705
|
}).refine((body) => body.objective !== void 0 || body.status !== void 0, {
|
|
14162
14706
|
message: "Goal objective or status required"
|
|
14163
14707
|
});
|
|
@@ -14332,7 +14876,7 @@ function createV1Routes(deps) {
|
|
|
14332
14876
|
});
|
|
14333
14877
|
app2.post("/chats/:chatId/messages", async (c) => {
|
|
14334
14878
|
try {
|
|
14335
|
-
const body =
|
|
14879
|
+
const body = sendChatMessageRequestSchema.strip().parse(await c.req.json());
|
|
14336
14880
|
const goalValidationError = getGoalCommandObjectiveValidationError(body.message, body.goalMode);
|
|
14337
14881
|
if (goalValidationError) {
|
|
14338
14882
|
return c.json(jsonError(goalValidationError), 400);
|
|
@@ -14371,7 +14915,7 @@ function createV1Routes(deps) {
|
|
|
14371
14915
|
const result = await deps.chatService.updateGoal(c.req.param("chatId"), body);
|
|
14372
14916
|
return c.json(result);
|
|
14373
14917
|
} catch (error) {
|
|
14374
|
-
if (error instanceof
|
|
14918
|
+
if (error instanceof z5.ZodError) {
|
|
14375
14919
|
return c.json(jsonError(error.issues[0]?.message || "Invalid goal update"), 400);
|
|
14376
14920
|
}
|
|
14377
14921
|
if (error instanceof ChatNotFoundError) {
|
|
@@ -15114,7 +15658,7 @@ async function timeStartupStep(name, fn) {
|
|
|
15114
15658
|
async function waitForInitializationGate() {
|
|
15115
15659
|
if (!ENGINE_ENV.REPLICAS_ENGINE_DEFER_INITIALIZATION) return;
|
|
15116
15660
|
const deadline = Date.now() + ENGINE_INIT_GATE_TIMEOUT_MS;
|
|
15117
|
-
while (!
|
|
15661
|
+
while (!existsSync12(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
|
|
15118
15662
|
if (Date.now() >= deadline) {
|
|
15119
15663
|
throw new Error("Timed out waiting for deferred engine initialization gate");
|
|
15120
15664
|
}
|
|
@@ -15179,7 +15723,12 @@ var authMiddleware = async (c, next) => {
|
|
|
15179
15723
|
await next();
|
|
15180
15724
|
};
|
|
15181
15725
|
var agentChatActivityTrackerService = new AgentChatActivityTrackerService();
|
|
15182
|
-
var
|
|
15726
|
+
var agentTokenUsageTracker = new AgentTokenUsageTracker(uploadAnalytics);
|
|
15727
|
+
var chatService = new ChatService(
|
|
15728
|
+
gitService.getWorkspaceRoot(),
|
|
15729
|
+
agentChatActivityTrackerService,
|
|
15730
|
+
agentTokenUsageTracker
|
|
15731
|
+
);
|
|
15183
15732
|
app.get("/health", async (c) => {
|
|
15184
15733
|
const requestedWaitMs = Number(c.req.query(ENGINE_HEALTH_WAIT_QUERY_PARAM));
|
|
15185
15734
|
const waitMs = Number.isFinite(requestedWaitMs) ? Math.min(Math.max(requestedWaitMs, 0), ENGINE_HEALTH_MAX_WAIT_MS) : 0;
|