@sentry/junior 0.55.0 → 0.56.0
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/app.js +481 -80
- package/dist/chat/app/services.d.ts +3 -0
- package/dist/chat/config.d.ts +1 -0
- package/dist/chat/pi/client.d.ts +1 -0
- package/dist/chat/runtime/reply-executor.d.ts +2 -0
- package/dist/chat/services/context-budget.d.ts +14 -0
- package/dist/chat/services/context-compaction.d.ts +33 -0
- package/dist/chat/slack/assistant-thread/status-scheduler.d.ts +1 -1
- package/dist/{chunk-XI3CFWTA.js → chunk-AA5TIFN5.js} +64 -8
- package/dist/cli/snapshot-warmup.js +1 -1
- package/package.json +2 -2
package/dist/app.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
runNonInteractiveCommand,
|
|
33
33
|
sandboxSkillDir,
|
|
34
34
|
sandboxSkillFile
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-AA5TIFN5.js";
|
|
36
36
|
import {
|
|
37
37
|
CredentialUnavailableError,
|
|
38
38
|
buildOAuthTokenRequest,
|
|
@@ -10745,6 +10745,21 @@ function parseEnv(raw) {
|
|
|
10745
10745
|
Object.entries(raw).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
|
|
10746
10746
|
);
|
|
10747
10747
|
}
|
|
10748
|
+
function sandboxStreamInterruptedResult(toolName) {
|
|
10749
|
+
return {
|
|
10750
|
+
content: [
|
|
10751
|
+
{
|
|
10752
|
+
type: "text",
|
|
10753
|
+
text: `Sandbox command stream was interrupted during ${toolName}. The operation did not complete reliably. It may have produced side effects; inspect the workspace or retry only if it is safe.`
|
|
10754
|
+
}
|
|
10755
|
+
],
|
|
10756
|
+
details: {
|
|
10757
|
+
ok: false,
|
|
10758
|
+
error: "stream_interrupted",
|
|
10759
|
+
tool: toolName
|
|
10760
|
+
}
|
|
10761
|
+
};
|
|
10762
|
+
}
|
|
10748
10763
|
function createSandboxExecutor(options) {
|
|
10749
10764
|
let availableSkills = [];
|
|
10750
10765
|
let referenceFiles = [];
|
|
@@ -11096,23 +11111,30 @@ function createSandboxExecutor(options) {
|
|
|
11096
11111
|
}
|
|
11097
11112
|
return await executeBashTool(rawInput, bashCommand);
|
|
11098
11113
|
}
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11114
|
+
try {
|
|
11115
|
+
if (params.toolName === "readFile") {
|
|
11116
|
+
return await executeReadFileTool(rawInput);
|
|
11117
|
+
}
|
|
11118
|
+
if (params.toolName === "editFile") {
|
|
11119
|
+
return await executeEditFileTool(rawInput);
|
|
11120
|
+
}
|
|
11121
|
+
if (params.toolName === "grep") {
|
|
11122
|
+
return await executeGrepTool(rawInput);
|
|
11123
|
+
}
|
|
11124
|
+
if (params.toolName === "findFiles") {
|
|
11125
|
+
return await executeFindFilesTool(rawInput);
|
|
11126
|
+
}
|
|
11127
|
+
if (params.toolName === "listDir") {
|
|
11128
|
+
return await executeListDirTool(rawInput);
|
|
11129
|
+
}
|
|
11130
|
+
if (params.toolName === "writeFile") {
|
|
11131
|
+
return await executeWriteFileTool(rawInput);
|
|
11132
|
+
}
|
|
11133
|
+
} catch (error) {
|
|
11134
|
+
if (!isSandboxCommandStreamInterruptedError(error)) {
|
|
11135
|
+
throw error;
|
|
11136
|
+
}
|
|
11137
|
+
return { result: sandboxStreamInterruptedResult(params.toolName) };
|
|
11116
11138
|
}
|
|
11117
11139
|
throw new Error(`unsupported sandbox tool: ${params.toolName}`);
|
|
11118
11140
|
};
|
|
@@ -14087,7 +14109,7 @@ function buildUserTurnInput(args) {
|
|
|
14087
14109
|
}
|
|
14088
14110
|
return { routerBlocks, userContentParts };
|
|
14089
14111
|
}
|
|
14090
|
-
async function generateAssistantReply(
|
|
14112
|
+
async function generateAssistantReply(messageText2, context = {}) {
|
|
14091
14113
|
const replyStartedAtMs = Date.now();
|
|
14092
14114
|
let timeoutResumeConversationId;
|
|
14093
14115
|
let timeoutResumeSessionId;
|
|
@@ -14156,7 +14178,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
14156
14178
|
}
|
|
14157
14179
|
let baseInstructions = "";
|
|
14158
14180
|
let configurationValues;
|
|
14159
|
-
const userInput =
|
|
14181
|
+
const userInput = messageText2;
|
|
14160
14182
|
if (shouldTrace) {
|
|
14161
14183
|
const inboundAttachmentCount = context.inboundAttachmentCount ?? 0;
|
|
14162
14184
|
const promptAttachmentCount = context.userAttachments?.length ?? 0;
|
|
@@ -14886,9 +14908,56 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
14886
14908
|
}
|
|
14887
14909
|
}
|
|
14888
14910
|
|
|
14911
|
+
// src/chat/services/context-budget.ts
|
|
14912
|
+
var COMPACTION_TRIGGER_INPUT_RATIO = 0.75;
|
|
14913
|
+
var COMPACTION_OUTPUT_RESERVE_RATIO = 0.25;
|
|
14914
|
+
var COMPACTION_TARGET_RATIO = 0.8;
|
|
14915
|
+
var FALLBACK_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
14916
|
+
var FALLBACK_MAX_OUTPUT_TOKENS = 128e3;
|
|
14917
|
+
function positiveInteger2(value, fallback) {
|
|
14918
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
14919
|
+
}
|
|
14920
|
+
function estimateTextTokens(text) {
|
|
14921
|
+
return Math.ceil(text.length / 4);
|
|
14922
|
+
}
|
|
14923
|
+
function calculateContextCompactionTriggerTokens(model) {
|
|
14924
|
+
const contextWindow = positiveInteger2(
|
|
14925
|
+
model.contextWindow,
|
|
14926
|
+
FALLBACK_CONTEXT_WINDOW_TOKENS
|
|
14927
|
+
);
|
|
14928
|
+
const maxTokens = positiveInteger2(
|
|
14929
|
+
model.maxTokens,
|
|
14930
|
+
FALLBACK_MAX_OUTPUT_TOKENS
|
|
14931
|
+
);
|
|
14932
|
+
const outputReserve = Math.min(
|
|
14933
|
+
maxTokens,
|
|
14934
|
+
Math.floor(contextWindow * COMPACTION_OUTPUT_RESERVE_RATIO)
|
|
14935
|
+
);
|
|
14936
|
+
const usableInputTokens = Math.max(1, contextWindow - outputReserve);
|
|
14937
|
+
return Math.max(
|
|
14938
|
+
1,
|
|
14939
|
+
Math.floor(usableInputTokens * COMPACTION_TRIGGER_INPUT_RATIO)
|
|
14940
|
+
);
|
|
14941
|
+
}
|
|
14942
|
+
function calculateContextCompactionTargetTokens(triggerTokens) {
|
|
14943
|
+
return Math.max(1, Math.floor(triggerTokens * COMPACTION_TARGET_RATIO));
|
|
14944
|
+
}
|
|
14945
|
+
function getAgentContextCompactionTriggerTokens() {
|
|
14946
|
+
const model = resolveGatewayModel(botConfig.modelId);
|
|
14947
|
+
return calculateContextCompactionTriggerTokens({
|
|
14948
|
+
contextWindow: botConfig.modelContextWindowTokens ?? model.contextWindow,
|
|
14949
|
+
maxTokens: model.maxTokens
|
|
14950
|
+
});
|
|
14951
|
+
}
|
|
14952
|
+
function getConversationContextCompactionTriggerTokens() {
|
|
14953
|
+
const model = resolveGatewayModel(botConfig.fastModelId);
|
|
14954
|
+
return calculateContextCompactionTriggerTokens({
|
|
14955
|
+
contextWindow: model.contextWindow,
|
|
14956
|
+
maxTokens: model.maxTokens
|
|
14957
|
+
});
|
|
14958
|
+
}
|
|
14959
|
+
|
|
14889
14960
|
// src/chat/services/conversation-memory.ts
|
|
14890
|
-
var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
|
|
14891
|
-
var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
|
|
14892
14961
|
var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
14893
14962
|
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
14894
14963
|
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
@@ -14899,9 +14968,6 @@ function generateConversationId(prefix) {
|
|
|
14899
14968
|
function normalizeConversationText(text) {
|
|
14900
14969
|
return text.trim().replace(/\s+/g, " ").slice(0, CONTEXT_MAX_MESSAGE_CHARS);
|
|
14901
14970
|
}
|
|
14902
|
-
function estimateTokenCount(text) {
|
|
14903
|
-
return Math.ceil(text.length / 4);
|
|
14904
|
-
}
|
|
14905
14971
|
function buildImageContextSuffix(message, conversation) {
|
|
14906
14972
|
const byFileId = conversation?.vision.byFileId;
|
|
14907
14973
|
const imageFileIds = message.meta?.imageFileIds ?? [];
|
|
@@ -14931,7 +14997,7 @@ function renderConversationMessageLine(message, conversation) {
|
|
|
14931
14997
|
}
|
|
14932
14998
|
function updateConversationStats(conversation) {
|
|
14933
14999
|
const contextText = buildConversationContext(conversation);
|
|
14934
|
-
conversation.stats.estimatedContextTokens =
|
|
15000
|
+
conversation.stats.estimatedContextTokens = estimateTextTokens(
|
|
14935
15001
|
contextText ?? ""
|
|
14936
15002
|
);
|
|
14937
15003
|
conversation.stats.totalMessageCount = conversation.messages.length;
|
|
@@ -15128,7 +15194,9 @@ async function compactConversationIfNeededWithDeps(conversation, context, deps)
|
|
|
15128
15194
|
setSpanAttributes({
|
|
15129
15195
|
"app.context_tokens_estimated": estimatedTokens
|
|
15130
15196
|
});
|
|
15131
|
-
|
|
15197
|
+
const triggerTokens = getConversationContextCompactionTriggerTokens();
|
|
15198
|
+
const targetTokens = calculateContextCompactionTargetTokens(triggerTokens);
|
|
15199
|
+
while (estimatedTokens > triggerTokens && conversation.messages.length > CONTEXT_MIN_LIVE_MESSAGES) {
|
|
15132
15200
|
const compactCount = Math.min(
|
|
15133
15201
|
CONTEXT_COMPACTION_BATCH_SIZE,
|
|
15134
15202
|
conversation.messages.length - CONTEXT_MIN_LIVE_MESSAGES
|
|
@@ -15156,9 +15224,11 @@ async function compactConversationIfNeededWithDeps(conversation, context, deps)
|
|
|
15156
15224
|
estimatedTokens = conversation.stats.estimatedContextTokens;
|
|
15157
15225
|
setSpanAttributes({
|
|
15158
15226
|
"app.compaction_messages_covered": compactCount,
|
|
15227
|
+
"app.compaction.trigger_tokens": triggerTokens,
|
|
15228
|
+
"app.compaction.target_tokens": targetTokens,
|
|
15159
15229
|
"app.context_tokens_estimated": estimatedTokens
|
|
15160
15230
|
});
|
|
15161
|
-
if (estimatedTokens <=
|
|
15231
|
+
if (estimatedTokens <= targetTokens) {
|
|
15162
15232
|
break;
|
|
15163
15233
|
}
|
|
15164
15234
|
}
|
|
@@ -16815,9 +16885,13 @@ function createAssistantStatusScheduler(args) {
|
|
|
16815
16885
|
}
|
|
16816
16886
|
};
|
|
16817
16887
|
return {
|
|
16818
|
-
start() {
|
|
16888
|
+
start(status) {
|
|
16819
16889
|
active = true;
|
|
16820
16890
|
clearPending();
|
|
16891
|
+
if (status) {
|
|
16892
|
+
void postRenderedStatus(status);
|
|
16893
|
+
return;
|
|
16894
|
+
}
|
|
16821
16895
|
currentKey = "initial";
|
|
16822
16896
|
void postStatus(getInitialStatusText(), loadingMessages);
|
|
16823
16897
|
},
|
|
@@ -17731,13 +17805,13 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
17731
17805
|
const currentState = await getPersistedThreadState(threadId);
|
|
17732
17806
|
const conversation = coerceThreadConversationState(currentState);
|
|
17733
17807
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
17734
|
-
const
|
|
17808
|
+
const userMessage2 = getTurnUserMessage(conversation, sessionId);
|
|
17735
17809
|
const statePatch = buildDeliveredTurnStatePatch({
|
|
17736
17810
|
artifacts,
|
|
17737
17811
|
conversation,
|
|
17738
17812
|
reply,
|
|
17739
17813
|
sessionId,
|
|
17740
|
-
userMessageId:
|
|
17814
|
+
userMessageId: userMessage2?.id
|
|
17741
17815
|
});
|
|
17742
17816
|
await persistThreadStateById(threadId, {
|
|
17743
17817
|
...statePatch
|
|
@@ -17800,7 +17874,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
17800
17874
|
requesterId: authSession.userId
|
|
17801
17875
|
});
|
|
17802
17876
|
const resolvedSessionId = pendingAuth?.sessionId ?? authSession.sessionId;
|
|
17803
|
-
const
|
|
17877
|
+
const userMessage2 = getTurnUserMessage(conversation, resolvedSessionId);
|
|
17804
17878
|
if (pendingAuth) {
|
|
17805
17879
|
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
17806
17880
|
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
@@ -17815,14 +17889,14 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
17815
17889
|
} else if (conversation.processing.activeTurnId !== authSession.sessionId) {
|
|
17816
17890
|
return;
|
|
17817
17891
|
}
|
|
17818
|
-
if (!
|
|
17892
|
+
if (!userMessage2) {
|
|
17819
17893
|
return;
|
|
17820
17894
|
}
|
|
17821
17895
|
await resumeAuthorizedRequest({
|
|
17822
|
-
messageText:
|
|
17896
|
+
messageText: userMessage2.text,
|
|
17823
17897
|
channelId: authSession.channelId,
|
|
17824
17898
|
threadTs: authSession.threadTs,
|
|
17825
|
-
messageTs: getTurnUserSlackMessageTs(
|
|
17899
|
+
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
17826
17900
|
lockKey: threadId,
|
|
17827
17901
|
connectedText: "",
|
|
17828
17902
|
beforeStart: async () => {
|
|
@@ -18188,13 +18262,13 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
18188
18262
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
18189
18263
|
const conversation = coerceThreadConversationState(currentState);
|
|
18190
18264
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
18191
|
-
const
|
|
18265
|
+
const userMessage2 = getTurnUserMessage(conversation, args.sessionId);
|
|
18192
18266
|
const statePatch = buildDeliveredTurnStatePatch({
|
|
18193
18267
|
artifacts,
|
|
18194
18268
|
conversation,
|
|
18195
18269
|
reply: args.reply,
|
|
18196
18270
|
sessionId: args.sessionId,
|
|
18197
|
-
userMessageId:
|
|
18271
|
+
userMessageId: userMessage2?.id
|
|
18198
18272
|
});
|
|
18199
18273
|
await persistThreadStateById(args.conversationId, {
|
|
18200
18274
|
...statePatch
|
|
@@ -18269,7 +18343,7 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
18269
18343
|
requesterId: stored.userId
|
|
18270
18344
|
});
|
|
18271
18345
|
const resolvedSessionId = pendingAuth?.sessionId ?? stored.resumeSessionId;
|
|
18272
|
-
const
|
|
18346
|
+
const userMessage2 = resolvedSessionId ? getTurnUserMessage(conversation, resolvedSessionId) : void 0;
|
|
18273
18347
|
if (pendingAuth) {
|
|
18274
18348
|
if (!isPendingAuthLatestRequest(conversation, pendingAuth)) {
|
|
18275
18349
|
clearPendingAuth(conversation, pendingAuth.sessionId);
|
|
@@ -18284,21 +18358,21 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
18284
18358
|
return true;
|
|
18285
18359
|
}
|
|
18286
18360
|
} else {
|
|
18287
|
-
if (!
|
|
18361
|
+
if (!userMessage2?.author?.userId) {
|
|
18288
18362
|
return false;
|
|
18289
18363
|
}
|
|
18290
18364
|
if (conversation.processing.activeTurnId !== stored.resumeSessionId) {
|
|
18291
18365
|
return true;
|
|
18292
18366
|
}
|
|
18293
18367
|
}
|
|
18294
|
-
if (!
|
|
18368
|
+
if (!userMessage2?.author?.userId || !resolvedSessionId) {
|
|
18295
18369
|
return false;
|
|
18296
18370
|
}
|
|
18297
18371
|
await resumeSlackTurn({
|
|
18298
|
-
messageText: stored.pendingMessage ??
|
|
18372
|
+
messageText: stored.pendingMessage ?? userMessage2.text,
|
|
18299
18373
|
channelId: stored.channelId,
|
|
18300
18374
|
threadTs: stored.threadTs,
|
|
18301
|
-
messageTs: getTurnUserSlackMessageTs(
|
|
18375
|
+
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
18302
18376
|
lockKey: stored.resumeConversationId,
|
|
18303
18377
|
initialText: "",
|
|
18304
18378
|
beforeStart: async () => {
|
|
@@ -19229,7 +19303,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
19229
19303
|
);
|
|
19230
19304
|
const conversation = coerceThreadConversationState(currentState);
|
|
19231
19305
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
19232
|
-
const
|
|
19306
|
+
const userMessage2 = getTurnUserMessage(
|
|
19233
19307
|
conversation,
|
|
19234
19308
|
args.checkpoint.sessionId
|
|
19235
19309
|
);
|
|
@@ -19238,7 +19312,7 @@ async function persistCompletedReplyState2(args) {
|
|
|
19238
19312
|
conversation,
|
|
19239
19313
|
reply: args.reply,
|
|
19240
19314
|
sessionId: args.checkpoint.sessionId,
|
|
19241
|
-
userMessageId:
|
|
19315
|
+
userMessageId: userMessage2?.id
|
|
19242
19316
|
});
|
|
19243
19317
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
19244
19318
|
...statePatch
|
|
@@ -19310,8 +19384,8 @@ async function resumeTimedOutTurn(payload) {
|
|
|
19310
19384
|
);
|
|
19311
19385
|
const conversation = coerceThreadConversationState(currentState);
|
|
19312
19386
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
19313
|
-
const
|
|
19314
|
-
if (!
|
|
19387
|
+
const userMessage2 = getTurnUserMessage(conversation, payload.sessionId);
|
|
19388
|
+
if (!userMessage2?.author?.userId) {
|
|
19315
19389
|
throw new Error(
|
|
19316
19390
|
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
19317
19391
|
);
|
|
@@ -19323,24 +19397,24 @@ async function resumeTimedOutTurn(payload) {
|
|
|
19323
19397
|
thread.channelId
|
|
19324
19398
|
);
|
|
19325
19399
|
const conversationContext = buildConversationContext(conversation, {
|
|
19326
|
-
excludeMessageId:
|
|
19400
|
+
excludeMessageId: userMessage2.id
|
|
19327
19401
|
});
|
|
19328
19402
|
const sandbox = getPersistedSandboxState(currentState);
|
|
19329
19403
|
return {
|
|
19330
|
-
messageText:
|
|
19331
|
-
messageTs: getTurnUserSlackMessageTs(
|
|
19404
|
+
messageText: userMessage2.text,
|
|
19405
|
+
messageTs: getTurnUserSlackMessageTs(userMessage2),
|
|
19332
19406
|
replyContext: {
|
|
19333
19407
|
requester: {
|
|
19334
|
-
userId:
|
|
19335
|
-
userName:
|
|
19336
|
-
fullName:
|
|
19408
|
+
userId: userMessage2.author.userId,
|
|
19409
|
+
userName: userMessage2.author.userName,
|
|
19410
|
+
fullName: userMessage2.author.fullName
|
|
19337
19411
|
},
|
|
19338
19412
|
correlation: {
|
|
19339
19413
|
conversationId: payload.conversationId,
|
|
19340
19414
|
turnId: payload.sessionId,
|
|
19341
19415
|
channelId: thread.channelId,
|
|
19342
19416
|
threadTs: thread.threadTs,
|
|
19343
|
-
requesterId:
|
|
19417
|
+
requesterId: userMessage2.author.userId
|
|
19344
19418
|
},
|
|
19345
19419
|
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
19346
19420
|
artifactState: artifacts,
|
|
@@ -19359,7 +19433,7 @@ async function resumeTimedOutTurn(payload) {
|
|
|
19359
19433
|
conversation
|
|
19360
19434
|
});
|
|
19361
19435
|
},
|
|
19362
|
-
...getTurnUserReplyAttachmentContext(
|
|
19436
|
+
...getTurnUserReplyAttachmentContext(userMessage2)
|
|
19363
19437
|
},
|
|
19364
19438
|
onSuccess: async (reply) => {
|
|
19365
19439
|
await persistCompletedReplyState2({ checkpoint, reply });
|
|
@@ -20178,6 +20252,294 @@ function createSlackTurnRuntime(deps) {
|
|
|
20178
20252
|
};
|
|
20179
20253
|
}
|
|
20180
20254
|
|
|
20255
|
+
// src/chat/services/context-compaction.ts
|
|
20256
|
+
import {
|
|
20257
|
+
estimateContextTokens,
|
|
20258
|
+
estimateTokens
|
|
20259
|
+
} from "@earendil-works/pi-agent-core";
|
|
20260
|
+
var RETAINED_USER_MESSAGE_TOKENS = 2e4;
|
|
20261
|
+
var MAX_SUMMARY_INPUT_CHARS = 8e4;
|
|
20262
|
+
var MAX_VISIBLE_CONTEXT_CHARS = 2e4;
|
|
20263
|
+
var MAX_SUMMARY_CHARS = 6e3;
|
|
20264
|
+
var MAX_RENDERED_MESSAGE_CHARS = 4e3;
|
|
20265
|
+
var COMPACTION_SUMMARY_PREFIX = "Context handoff summary for future Junior turns:";
|
|
20266
|
+
var OMITTED_OLDER_CONTEXT_NOTICE = "[older context omitted]";
|
|
20267
|
+
function textPart(value) {
|
|
20268
|
+
if (value && typeof value === "object" && value.type === "text" && typeof value.text === "string") {
|
|
20269
|
+
return value.text;
|
|
20270
|
+
}
|
|
20271
|
+
return void 0;
|
|
20272
|
+
}
|
|
20273
|
+
function messageText(message) {
|
|
20274
|
+
const content = message.content;
|
|
20275
|
+
if (!Array.isArray(content)) {
|
|
20276
|
+
return typeof content === "string" ? content : "";
|
|
20277
|
+
}
|
|
20278
|
+
return content.map(textPart).filter(Boolean).join("\n").trim();
|
|
20279
|
+
}
|
|
20280
|
+
function sanitizeText(text) {
|
|
20281
|
+
return text.replace(
|
|
20282
|
+
/<data_base64>[\s\S]*?<\/data_base64>/g,
|
|
20283
|
+
"<data_base64>[omitted]</data_base64>"
|
|
20284
|
+
).replace(
|
|
20285
|
+
/data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=]+/gi,
|
|
20286
|
+
"[image data omitted]"
|
|
20287
|
+
).replaceAll("\0", " ").trim();
|
|
20288
|
+
}
|
|
20289
|
+
function truncateToTokenBudget(text, maxTokens) {
|
|
20290
|
+
const maxChars = Math.max(0, maxTokens * 4);
|
|
20291
|
+
if (text.length <= maxChars) {
|
|
20292
|
+
return text;
|
|
20293
|
+
}
|
|
20294
|
+
return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
20295
|
+
}
|
|
20296
|
+
function isCompactionSummary(text) {
|
|
20297
|
+
return text.trimStart().startsWith(COMPACTION_SUMMARY_PREFIX);
|
|
20298
|
+
}
|
|
20299
|
+
function isPayloadHeavy(text) {
|
|
20300
|
+
return /<data_base64>[\s\S]*?<\/data_base64>|data:image\/[a-z0-9.+-]+;base64,/i.test(
|
|
20301
|
+
text
|
|
20302
|
+
);
|
|
20303
|
+
}
|
|
20304
|
+
function userMessage(text) {
|
|
20305
|
+
return {
|
|
20306
|
+
role: "user",
|
|
20307
|
+
content: [{ type: "text", text }],
|
|
20308
|
+
timestamp: Date.now()
|
|
20309
|
+
};
|
|
20310
|
+
}
|
|
20311
|
+
function selectRetainedUserMessages(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
|
|
20312
|
+
const stripped = stripRuntimeTurnContext(messages);
|
|
20313
|
+
const selected = [];
|
|
20314
|
+
let remaining = maxTokens;
|
|
20315
|
+
for (const message of [...stripped].reverse()) {
|
|
20316
|
+
if (message.role !== "user" || remaining <= 0) {
|
|
20317
|
+
continue;
|
|
20318
|
+
}
|
|
20319
|
+
const text = sanitizeText(messageText(message));
|
|
20320
|
+
if (!text || isCompactionSummary(text) || isPayloadHeavy(text)) {
|
|
20321
|
+
continue;
|
|
20322
|
+
}
|
|
20323
|
+
const tokens = estimateTextTokens(text);
|
|
20324
|
+
if (tokens <= remaining) {
|
|
20325
|
+
selected.push(text);
|
|
20326
|
+
remaining -= tokens;
|
|
20327
|
+
continue;
|
|
20328
|
+
}
|
|
20329
|
+
const truncated = truncateToTokenBudget(text, remaining);
|
|
20330
|
+
if (truncated) {
|
|
20331
|
+
selected.push(truncated);
|
|
20332
|
+
}
|
|
20333
|
+
break;
|
|
20334
|
+
}
|
|
20335
|
+
return selected.reverse().map(userMessage);
|
|
20336
|
+
}
|
|
20337
|
+
function renderMessageForSummary(message) {
|
|
20338
|
+
const role = message.role;
|
|
20339
|
+
if (typeof role !== "string") {
|
|
20340
|
+
return void 0;
|
|
20341
|
+
}
|
|
20342
|
+
const text = sanitizeText(messageText(message));
|
|
20343
|
+
if (!text) {
|
|
20344
|
+
return void 0;
|
|
20345
|
+
}
|
|
20346
|
+
const trimmed = text.length > MAX_RENDERED_MESSAGE_CHARS ? `${text.slice(0, MAX_RENDERED_MESSAGE_CHARS).trimEnd()}...` : text;
|
|
20347
|
+
return `[${role}] ${trimmed}`;
|
|
20348
|
+
}
|
|
20349
|
+
function keepTail(text, maxChars) {
|
|
20350
|
+
if (text.length <= maxChars) {
|
|
20351
|
+
return text;
|
|
20352
|
+
}
|
|
20353
|
+
const prefix = `${OMITTED_OLDER_CONTEXT_NOTICE}
|
|
20354
|
+
`;
|
|
20355
|
+
return `${prefix}${text.slice(Math.max(0, text.length - maxChars + prefix.length))}`;
|
|
20356
|
+
}
|
|
20357
|
+
function renderSummaryInput(piMessages, conversationContext) {
|
|
20358
|
+
const lines = [];
|
|
20359
|
+
const visibleContext = conversationContext?.trim();
|
|
20360
|
+
if (visibleContext) {
|
|
20361
|
+
lines.push(
|
|
20362
|
+
"<visible-thread-context>",
|
|
20363
|
+
keepTail(visibleContext, MAX_VISIBLE_CONTEXT_CHARS),
|
|
20364
|
+
"</visible-thread-context>",
|
|
20365
|
+
""
|
|
20366
|
+
);
|
|
20367
|
+
}
|
|
20368
|
+
const renderedPiMessages = stripRuntimeTurnContext(piMessages).map(renderMessageForSummary).filter((line) => Boolean(line));
|
|
20369
|
+
if (renderedPiMessages.length > 0) {
|
|
20370
|
+
const piEnvelopeChars = "<pi-history>\n</pi-history>".length + 2;
|
|
20371
|
+
const piHistory = keepTail(
|
|
20372
|
+
renderedPiMessages.join("\n"),
|
|
20373
|
+
Math.max(
|
|
20374
|
+
1,
|
|
20375
|
+
MAX_SUMMARY_INPUT_CHARS - lines.join("\n").length - piEnvelopeChars
|
|
20376
|
+
)
|
|
20377
|
+
);
|
|
20378
|
+
lines.push("<pi-history>", piHistory, "</pi-history>");
|
|
20379
|
+
}
|
|
20380
|
+
return keepTail(lines.join("\n"), MAX_SUMMARY_INPUT_CHARS);
|
|
20381
|
+
}
|
|
20382
|
+
async function summarizeContext(args, deps) {
|
|
20383
|
+
const source = renderSummaryInput(args.piMessages, args.conversationContext);
|
|
20384
|
+
const result = await deps.completeText({
|
|
20385
|
+
modelId: botConfig.fastModelId,
|
|
20386
|
+
messageAttributeMode: "metadata",
|
|
20387
|
+
temperature: 0,
|
|
20388
|
+
messages: [
|
|
20389
|
+
{
|
|
20390
|
+
role: "user",
|
|
20391
|
+
content: [
|
|
20392
|
+
"You are performing a CONTEXT CHECKPOINT COMPACTION for Junior.",
|
|
20393
|
+
"Create a concise handoff summary for another model that will continue this Slack thread.",
|
|
20394
|
+
"",
|
|
20395
|
+
"Include:",
|
|
20396
|
+
"- Current outstanding asks",
|
|
20397
|
+
"- Key decisions, completed work, and outcomes",
|
|
20398
|
+
"- Durable constraints, user preferences, IDs, URLs, artifacts, canvas links, sandbox references, and auth state",
|
|
20399
|
+
"- Clear next steps and unresolved blockers",
|
|
20400
|
+
"",
|
|
20401
|
+
"Do not invent details. Do not include raw secrets or credentials.",
|
|
20402
|
+
"",
|
|
20403
|
+
source
|
|
20404
|
+
].join("\n"),
|
|
20405
|
+
timestamp: Date.now()
|
|
20406
|
+
}
|
|
20407
|
+
],
|
|
20408
|
+
metadata: {
|
|
20409
|
+
modelId: botConfig.fastModelId,
|
|
20410
|
+
threadId: args.metadata?.threadId ?? "",
|
|
20411
|
+
channelId: args.metadata?.channelId ?? "",
|
|
20412
|
+
requesterId: args.metadata?.requesterId ?? "",
|
|
20413
|
+
runId: args.metadata?.runId ?? ""
|
|
20414
|
+
}
|
|
20415
|
+
});
|
|
20416
|
+
const summary = result.text.trim();
|
|
20417
|
+
if (!summary) {
|
|
20418
|
+
throw new Error("Compaction summary was empty");
|
|
20419
|
+
}
|
|
20420
|
+
return summary.slice(0, MAX_SUMMARY_CHARS);
|
|
20421
|
+
}
|
|
20422
|
+
function estimateHistoryTokens(messages) {
|
|
20423
|
+
const stripped = stripRuntimeTurnContext(messages);
|
|
20424
|
+
const usageEstimate = estimateContextTokens(stripped).tokens;
|
|
20425
|
+
const structuralEstimate = stripped.reduce(
|
|
20426
|
+
(total, message) => total + estimateTokens(message),
|
|
20427
|
+
0
|
|
20428
|
+
);
|
|
20429
|
+
return Math.max(usageEstimate, structuralEstimate);
|
|
20430
|
+
}
|
|
20431
|
+
function buildReplacementHistory(args) {
|
|
20432
|
+
return [
|
|
20433
|
+
...selectRetainedUserMessages(args.messages),
|
|
20434
|
+
userMessage(`${COMPACTION_SUMMARY_PREFIX}
|
|
20435
|
+
${args.summary}`)
|
|
20436
|
+
];
|
|
20437
|
+
}
|
|
20438
|
+
function createCompactionSessionId(previousSessionId) {
|
|
20439
|
+
return `compaction_${previousSessionId}`;
|
|
20440
|
+
}
|
|
20441
|
+
async function loadCompactionSource(args) {
|
|
20442
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
20443
|
+
args.conversationId,
|
|
20444
|
+
args.previousSessionId
|
|
20445
|
+
);
|
|
20446
|
+
if (!checkpoint) {
|
|
20447
|
+
return { reason: "missing_context" };
|
|
20448
|
+
}
|
|
20449
|
+
if (checkpoint.state !== "completed") {
|
|
20450
|
+
return { reason: "not_completed" };
|
|
20451
|
+
}
|
|
20452
|
+
const messages = checkpoint.piMessages;
|
|
20453
|
+
if (messages.length) {
|
|
20454
|
+
return {
|
|
20455
|
+
estimatedTokens: estimateHistoryTokens(messages),
|
|
20456
|
+
messages
|
|
20457
|
+
};
|
|
20458
|
+
}
|
|
20459
|
+
return { reason: "missing_context" };
|
|
20460
|
+
}
|
|
20461
|
+
async function maybeCompactWithDeps(args, deps) {
|
|
20462
|
+
const source = await loadCompactionSource({
|
|
20463
|
+
conversationId: args.conversationId,
|
|
20464
|
+
previousSessionId: args.previousSessionId
|
|
20465
|
+
});
|
|
20466
|
+
if ("reason" in source) {
|
|
20467
|
+
return { compacted: false, reason: source.reason };
|
|
20468
|
+
}
|
|
20469
|
+
const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens();
|
|
20470
|
+
if (source.estimatedTokens <= triggerTokens) {
|
|
20471
|
+
return { compacted: false, reason: "below_threshold" };
|
|
20472
|
+
}
|
|
20473
|
+
args.onCompactionStart?.();
|
|
20474
|
+
let summary;
|
|
20475
|
+
try {
|
|
20476
|
+
summary = await summarizeContext(
|
|
20477
|
+
{
|
|
20478
|
+
conversationContext: args.conversationContext,
|
|
20479
|
+
piMessages: source.messages,
|
|
20480
|
+
metadata: args.metadata
|
|
20481
|
+
},
|
|
20482
|
+
deps
|
|
20483
|
+
);
|
|
20484
|
+
} catch (error) {
|
|
20485
|
+
logWarn(
|
|
20486
|
+
"context_compaction_summary_failed",
|
|
20487
|
+
{
|
|
20488
|
+
slackThreadId: args.metadata?.threadId,
|
|
20489
|
+
slackUserId: args.metadata?.requesterId,
|
|
20490
|
+
slackChannelId: args.metadata?.channelId,
|
|
20491
|
+
runId: args.metadata?.runId,
|
|
20492
|
+
assistantUserName: botConfig.userName,
|
|
20493
|
+
modelId: botConfig.fastModelId
|
|
20494
|
+
},
|
|
20495
|
+
{
|
|
20496
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
20497
|
+
},
|
|
20498
|
+
"Context compaction failed; continuing with prior history"
|
|
20499
|
+
);
|
|
20500
|
+
return { compacted: false, reason: "summary_failed" };
|
|
20501
|
+
}
|
|
20502
|
+
return await writeCompactedThreadContext(args, source.messages, summary, {
|
|
20503
|
+
estimatedTokens: source.estimatedTokens,
|
|
20504
|
+
triggerTokens
|
|
20505
|
+
});
|
|
20506
|
+
}
|
|
20507
|
+
async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
|
|
20508
|
+
const replacement = buildReplacementHistory({
|
|
20509
|
+
messages: trimTrailingAssistantMessages(sourceMessages),
|
|
20510
|
+
summary
|
|
20511
|
+
});
|
|
20512
|
+
const nextSessionId = createCompactionSessionId(args.previousSessionId);
|
|
20513
|
+
await upsertAgentTurnSessionCheckpoint({
|
|
20514
|
+
conversationId: args.conversationId,
|
|
20515
|
+
sessionId: nextSessionId,
|
|
20516
|
+
sliceId: 1,
|
|
20517
|
+
state: "completed",
|
|
20518
|
+
piMessages: replacement
|
|
20519
|
+
});
|
|
20520
|
+
args.conversation.processing.lastSessionId = nextSessionId;
|
|
20521
|
+
updateConversationStats(args.conversation);
|
|
20522
|
+
setSpanAttributes({
|
|
20523
|
+
"app.compaction.input_messages": sourceMessages.length,
|
|
20524
|
+
"app.compaction.retained_messages": replacement.length - 1,
|
|
20525
|
+
"app.compaction.summary_chars": summary.length,
|
|
20526
|
+
"app.compaction.previous_session_id": args.previousSessionId,
|
|
20527
|
+
"app.compaction.next_session_id": nextSessionId,
|
|
20528
|
+
...context.triggerTokens !== void 0 ? { "app.compaction.trigger_tokens": context.triggerTokens } : {},
|
|
20529
|
+
"app.context_tokens_estimated": context.estimatedTokens
|
|
20530
|
+
});
|
|
20531
|
+
return {
|
|
20532
|
+
compacted: true,
|
|
20533
|
+
piMessages: replacement,
|
|
20534
|
+
sessionId: nextSessionId
|
|
20535
|
+
};
|
|
20536
|
+
}
|
|
20537
|
+
function createContextCompactor(deps) {
|
|
20538
|
+
return {
|
|
20539
|
+
maybeCompact: async (args) => await maybeCompactWithDeps(args, deps)
|
|
20540
|
+
};
|
|
20541
|
+
}
|
|
20542
|
+
|
|
20181
20543
|
// src/chat/slack/user.ts
|
|
20182
20544
|
var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
20183
20545
|
var userCache = /* @__PURE__ */ new Map();
|
|
@@ -20814,6 +21176,10 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
20814
21176
|
const conversationMemory = createConversationMemoryService({
|
|
20815
21177
|
completeText: overrides.conversationMemory?.completeText ?? completeText
|
|
20816
21178
|
});
|
|
21179
|
+
const contextCompactor = createContextCompactor({
|
|
21180
|
+
completeText: overrides.contextCompactor?.completeText ?? completeText,
|
|
21181
|
+
autoCompactionTriggerTokens: overrides.contextCompactor?.autoCompactionTriggerTokens
|
|
21182
|
+
});
|
|
20817
21183
|
const visionContext = createVisionContextService({
|
|
20818
21184
|
completeText: overrides.visionContext?.completeText ?? completeText,
|
|
20819
21185
|
listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
|
|
@@ -20821,7 +21187,9 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
20821
21187
|
});
|
|
20822
21188
|
return {
|
|
20823
21189
|
conversationMemory,
|
|
21190
|
+
contextCompactor,
|
|
20824
21191
|
replyExecutor: {
|
|
21192
|
+
contextCompactor: overrides.replyExecutor?.contextCompactor ?? contextCompactor,
|
|
20825
21193
|
generateAssistantReply: overrides.replyExecutor?.generateAssistantReply ?? generateAssistantReply,
|
|
20826
21194
|
getAwaitingTurnContinuationRequest: overrides.replyExecutor?.getAwaitingTurnContinuationRequest ?? getAwaitingTurnContinuationRequest,
|
|
20827
21195
|
lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
|
|
@@ -20967,7 +21335,7 @@ function buildCanvasRecoveryReply(canvasUrl) {
|
|
|
20967
21335
|
async function loadPiMessagesForTurn(args) {
|
|
20968
21336
|
const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
|
|
20969
21337
|
if (!args.conversationId) {
|
|
20970
|
-
return fallback;
|
|
21338
|
+
return { piMessages: fallback };
|
|
20971
21339
|
}
|
|
20972
21340
|
if (args.activeTurnId) {
|
|
20973
21341
|
const checkpoint2 = await getAgentTurnSessionCheckpoint(
|
|
@@ -20975,19 +21343,27 @@ async function loadPiMessagesForTurn(args) {
|
|
|
20975
21343
|
args.activeTurnId
|
|
20976
21344
|
);
|
|
20977
21345
|
if (checkpoint2?.piMessages.length) {
|
|
20978
|
-
return
|
|
20979
|
-
|
|
20980
|
-
|
|
21346
|
+
return {
|
|
21347
|
+
piMessages: stripRuntimeTurnContext(
|
|
21348
|
+
trimTrailingAssistantMessages(checkpoint2.piMessages)
|
|
21349
|
+
)
|
|
21350
|
+
};
|
|
20981
21351
|
}
|
|
20982
21352
|
}
|
|
20983
21353
|
if (!args.lastSessionId) {
|
|
20984
|
-
return fallback;
|
|
21354
|
+
return { piMessages: fallback };
|
|
20985
21355
|
}
|
|
20986
21356
|
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
20987
21357
|
args.conversationId,
|
|
20988
21358
|
args.lastSessionId
|
|
20989
21359
|
);
|
|
20990
|
-
|
|
21360
|
+
if (checkpoint?.state === "completed" && checkpoint.piMessages.length > 0) {
|
|
21361
|
+
return {
|
|
21362
|
+
compactionSessionId: args.lastSessionId,
|
|
21363
|
+
piMessages: stripRuntimeTurnContext(checkpoint.piMessages)
|
|
21364
|
+
};
|
|
21365
|
+
}
|
|
21366
|
+
return { piMessages: fallback };
|
|
20991
21367
|
}
|
|
20992
21368
|
function createReplyToThread(deps) {
|
|
20993
21369
|
return async function replyToThread(thread, message, options = {}) {
|
|
@@ -21218,17 +21594,14 @@ function createReplyToThread(deps) {
|
|
|
21218
21594
|
}
|
|
21219
21595
|
);
|
|
21220
21596
|
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(message.attachments) ? countPotentialImageAttachments(message.attachments) : 0;
|
|
21221
|
-
const piMessages = await loadPiMessagesForTurn({
|
|
21222
|
-
conversationId,
|
|
21223
|
-
activeTurnId,
|
|
21224
|
-
lastSessionId: lastSessionIdForHistory,
|
|
21225
|
-
fallback: preparedState.conversation.piMessages
|
|
21226
|
-
});
|
|
21227
21597
|
const status = createSlackAdapterAssistantStatusSession({
|
|
21228
21598
|
channelId: assistantThreadContext?.channelId,
|
|
21229
21599
|
threadTs: assistantThreadContext?.threadTs,
|
|
21230
21600
|
getSlackAdapter: deps.getSlackAdapter
|
|
21231
21601
|
});
|
|
21602
|
+
const compactingStatus = {
|
|
21603
|
+
text: "Compacting context"
|
|
21604
|
+
};
|
|
21232
21605
|
const postThreadReply = async (payload, stage) => {
|
|
21233
21606
|
await beforeFirstResponsePost();
|
|
21234
21607
|
try {
|
|
@@ -21248,24 +21621,52 @@ function createReplyToThread(deps) {
|
|
|
21248
21621
|
throw error;
|
|
21249
21622
|
}
|
|
21250
21623
|
};
|
|
21251
|
-
status.start();
|
|
21252
|
-
const assistantTitleTask = maybeUpdateAssistantTitle({
|
|
21253
|
-
assistantThreadContext,
|
|
21254
|
-
assistantUserName: botConfig.userName,
|
|
21255
|
-
artifacts: preparedState.artifacts,
|
|
21256
|
-
channelId,
|
|
21257
|
-
conversation: preparedState.conversation,
|
|
21258
|
-
generateThreadTitle: deps.services.generateThreadTitle,
|
|
21259
|
-
getSlackAdapter: deps.getSlackAdapter,
|
|
21260
|
-
modelId: botConfig.fastModelId,
|
|
21261
|
-
requesterId: message.author.userId,
|
|
21262
|
-
runId,
|
|
21263
|
-
threadId
|
|
21264
|
-
});
|
|
21265
21624
|
let persistedAtLeastOnce = false;
|
|
21266
21625
|
let shouldPersistFailureState = true;
|
|
21267
21626
|
let latestArtifacts = preparedState.artifacts;
|
|
21268
21627
|
try {
|
|
21628
|
+
const loadedPiMessages = await loadPiMessagesForTurn({
|
|
21629
|
+
conversationId,
|
|
21630
|
+
activeTurnId,
|
|
21631
|
+
lastSessionId: lastSessionIdForHistory,
|
|
21632
|
+
fallback: preparedState.conversation.piMessages
|
|
21633
|
+
});
|
|
21634
|
+
let piMessages = loadedPiMessages.piMessages;
|
|
21635
|
+
if (conversationId && loadedPiMessages.compactionSessionId && piMessages?.length) {
|
|
21636
|
+
const compaction = await deps.services.contextCompactor.maybeCompact({
|
|
21637
|
+
conversation: preparedState.conversation,
|
|
21638
|
+
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
21639
|
+
conversationId,
|
|
21640
|
+
metadata: {
|
|
21641
|
+
threadId,
|
|
21642
|
+
requesterId: message.author.userId,
|
|
21643
|
+
channelId,
|
|
21644
|
+
runId
|
|
21645
|
+
},
|
|
21646
|
+
onCompactionStart: () => status.start(compactingStatus),
|
|
21647
|
+
previousSessionId: loadedPiMessages.compactionSessionId
|
|
21648
|
+
});
|
|
21649
|
+
if (compaction.compacted) {
|
|
21650
|
+
piMessages = compaction.piMessages;
|
|
21651
|
+
await persistThreadState(thread, {
|
|
21652
|
+
conversation: preparedState.conversation
|
|
21653
|
+
});
|
|
21654
|
+
}
|
|
21655
|
+
}
|
|
21656
|
+
status.start();
|
|
21657
|
+
const assistantTitleTask = maybeUpdateAssistantTitle({
|
|
21658
|
+
assistantThreadContext,
|
|
21659
|
+
assistantUserName: botConfig.userName,
|
|
21660
|
+
artifacts: preparedState.artifacts,
|
|
21661
|
+
channelId,
|
|
21662
|
+
conversation: preparedState.conversation,
|
|
21663
|
+
generateThreadTitle: deps.services.generateThreadTitle,
|
|
21664
|
+
getSlackAdapter: deps.getSlackAdapter,
|
|
21665
|
+
modelId: botConfig.fastModelId,
|
|
21666
|
+
requesterId: message.author.userId,
|
|
21667
|
+
runId,
|
|
21668
|
+
threadId
|
|
21669
|
+
});
|
|
21269
21670
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
21270
21671
|
let reply = await deps.services.generateAssistantReply(userText, {
|
|
21271
21672
|
requester: {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type ConversationMemoryDeps, type ConversationMemoryService } from "@/chat/services/conversation-memory";
|
|
2
|
+
import { type ContextCompactor, type ContextCompactorDeps } from "@/chat/services/context-compaction";
|
|
2
3
|
import { type SubscribedReplyPolicy, type SubscribedReplyPolicyDeps } from "@/chat/services/subscribed-reply-policy";
|
|
3
4
|
import type { ReplyExecutorServices } from "@/chat/runtime/reply-executor";
|
|
4
5
|
import { type VisionContextDeps, type VisionContextService } from "@/chat/services/vision-context";
|
|
5
6
|
export interface JuniorRuntimeServices {
|
|
6
7
|
conversationMemory: ConversationMemoryService;
|
|
8
|
+
contextCompactor: ContextCompactor;
|
|
7
9
|
replyExecutor: ReplyExecutorServices;
|
|
8
10
|
subscribedReplyPolicy: SubscribedReplyPolicy;
|
|
9
11
|
visionContext: VisionContextService;
|
|
10
12
|
}
|
|
11
13
|
export interface JuniorRuntimeServiceOverrides {
|
|
12
14
|
conversationMemory?: Partial<ConversationMemoryDeps>;
|
|
15
|
+
contextCompactor?: Partial<ContextCompactorDeps>;
|
|
13
16
|
replyExecutor?: Partial<Omit<ReplyExecutorServices, "generateThreadTitle">>;
|
|
14
17
|
subscribedReplyPolicy?: Partial<SubscribedReplyPolicyDeps>;
|
|
15
18
|
visionContext?: Partial<VisionContextDeps>;
|
package/dist/chat/config.d.ts
CHANGED
package/dist/chat/pi/client.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ import type { SlackAdapter } from "@chat-adapter/slack";
|
|
|
3
3
|
import { generateAssistantReply as generateAssistantReplyImpl } from "@/chat/respond";
|
|
4
4
|
import type { PreparedTurnState } from "@/chat/runtime/turn-preparation";
|
|
5
5
|
import { type ConversationMemoryService } from "@/chat/services/conversation-memory";
|
|
6
|
+
import type { ContextCompactor } from "@/chat/services/context-compaction";
|
|
6
7
|
import { lookupSlackUser } from "@/chat/slack/user";
|
|
7
8
|
import type { TurnContinuationRequest } from "@/chat/services/timeout-resume";
|
|
8
9
|
export interface ReplyExecutorServices {
|
|
10
|
+
contextCompactor: ContextCompactor;
|
|
9
11
|
generateAssistantReply: typeof generateAssistantReplyImpl;
|
|
10
12
|
generateThreadTitle: ConversationMemoryService["generateThreadTitle"];
|
|
11
13
|
getAwaitingTurnContinuationRequest: (args: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ModelContextBudget {
|
|
2
|
+
contextWindow: number;
|
|
3
|
+
maxTokens: number;
|
|
4
|
+
}
|
|
5
|
+
/** Estimate text tokens with the shared coarse heuristic used for local budgets. */
|
|
6
|
+
export declare function estimateTextTokens(text: string): number;
|
|
7
|
+
/** Derive the automatic compaction threshold from model context capacity. */
|
|
8
|
+
export declare function calculateContextCompactionTriggerTokens(model: ModelContextBudget): number;
|
|
9
|
+
/** Derive the post-compaction target from the automatic trigger threshold. */
|
|
10
|
+
export declare function calculateContextCompactionTargetTokens(triggerTokens: number): number;
|
|
11
|
+
/** Resolve the automatic compaction threshold for the active agent model. */
|
|
12
|
+
export declare function getAgentContextCompactionTriggerTokens(): number;
|
|
13
|
+
/** Resolve the visible conversation compaction threshold for the auxiliary model. */
|
|
14
|
+
export declare function getConversationContextCompactionTriggerTokens(): number;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { completeText } from "@/chat/pi/client";
|
|
2
|
+
import type { PiMessage } from "@/chat/pi/messages";
|
|
3
|
+
import type { ThreadConversationState } from "@/chat/state/conversation";
|
|
4
|
+
export interface ContextCompactorDeps {
|
|
5
|
+
completeText: typeof completeText;
|
|
6
|
+
autoCompactionTriggerTokens?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ContextCompactor {
|
|
9
|
+
maybeCompact: (args: CompactContextArgs) => Promise<CompactContextResult>;
|
|
10
|
+
}
|
|
11
|
+
export interface CompactContextArgs {
|
|
12
|
+
conversation: ThreadConversationState;
|
|
13
|
+
conversationContext?: string;
|
|
14
|
+
conversationId: string;
|
|
15
|
+
onCompactionStart?: () => void;
|
|
16
|
+
previousSessionId: string;
|
|
17
|
+
metadata?: {
|
|
18
|
+
channelId?: string;
|
|
19
|
+
requesterId?: string;
|
|
20
|
+
runId?: string;
|
|
21
|
+
threadId?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface CompactContextResult {
|
|
25
|
+
compacted: boolean;
|
|
26
|
+
piMessages?: PiMessage[];
|
|
27
|
+
reason?: "below_threshold" | "missing_context" | "not_completed" | "summary_failed";
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Build retained user messages for a compacted Pi replacement history. */
|
|
31
|
+
export declare function selectRetainedUserMessages(messages: PiMessage[], maxTokens?: number): PiMessage[];
|
|
32
|
+
/** Build the service that owns local context compaction and checkpoint forks. */
|
|
33
|
+
export declare function createContextCompactor(deps: ContextCompactorDeps): ContextCompactor;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AssistantStatusSpec } from "@/chat/slack/assistant-thread/status-render";
|
|
2
2
|
export type TimerHandle = ReturnType<typeof setTimeout>;
|
|
3
3
|
export interface AssistantStatusSession {
|
|
4
|
-
start: () => void;
|
|
4
|
+
start: (status?: AssistantStatusSpec) => void;
|
|
5
5
|
stop: () => Promise<void>;
|
|
6
6
|
update: (status: AssistantStatusSpec) => void;
|
|
7
7
|
}
|
|
@@ -54,6 +54,35 @@ function getPiGatewayApiKeyOverride() {
|
|
|
54
54
|
function extractText(message) {
|
|
55
55
|
return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
|
|
56
56
|
}
|
|
57
|
+
function contentMetadata(content) {
|
|
58
|
+
if (typeof content === "string") {
|
|
59
|
+
return [{ type: "text", chars: content.length }];
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(content)) {
|
|
62
|
+
return { type: typeof content };
|
|
63
|
+
}
|
|
64
|
+
return content.map((part) => {
|
|
65
|
+
if (!part || typeof part !== "object") {
|
|
66
|
+
return { type: typeof part };
|
|
67
|
+
}
|
|
68
|
+
const record = part;
|
|
69
|
+
const type = typeof record.type === "string" ? record.type : "unknown";
|
|
70
|
+
return {
|
|
71
|
+
type,
|
|
72
|
+
...typeof record.text === "string" ? { chars: record.text.length } : {},
|
|
73
|
+
...typeof record.mimeType === "string" ? { mimeType: record.mimeType } : {},
|
|
74
|
+
...typeof record.mediaType === "string" ? { mediaType: record.mediaType } : {},
|
|
75
|
+
...typeof record.data === "string" ? { dataChars: record.data.length } : {}
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function toMessageMetadata(message) {
|
|
80
|
+
const record = message;
|
|
81
|
+
return {
|
|
82
|
+
role: record.role,
|
|
83
|
+
content: contentMetadata(record.content)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
57
86
|
function parseJsonCandidate(text) {
|
|
58
87
|
const trimmed = text.trim();
|
|
59
88
|
if (!trimmed) return void 0;
|
|
@@ -126,8 +155,13 @@ function resolveGatewayModel(modelId) {
|
|
|
126
155
|
async function completeText(params) {
|
|
127
156
|
const model = resolveGatewayModel(params.modelId);
|
|
128
157
|
const apiKey = getPiGatewayApiKeyOverride();
|
|
129
|
-
const
|
|
130
|
-
const
|
|
158
|
+
const messageAttributeMode = params.messageAttributeMode ?? "content";
|
|
159
|
+
const requestMessagesAttribute = serializeGenAiAttribute(
|
|
160
|
+
messageAttributeMode === "metadata" ? params.messages.map(toMessageMetadata) : params.messages
|
|
161
|
+
);
|
|
162
|
+
const systemInstructionsAttribute = params.system ? serializeGenAiAttribute(
|
|
163
|
+
messageAttributeMode === "metadata" ? [{ type: "text", chars: params.system.length }] : [{ type: "text", content: params.system }]
|
|
164
|
+
) : void 0;
|
|
131
165
|
const baseAttributes = {
|
|
132
166
|
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
133
167
|
"gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
|
|
@@ -161,12 +195,19 @@ async function completeText(params) {
|
|
|
161
195
|
}
|
|
162
196
|
);
|
|
163
197
|
const outputText = extractText(message);
|
|
164
|
-
const outputMessagesAttribute = serializeGenAiAttribute(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
198
|
+
const outputMessagesAttribute = serializeGenAiAttribute(
|
|
199
|
+
messageAttributeMode === "metadata" ? [
|
|
200
|
+
{
|
|
201
|
+
role: "assistant",
|
|
202
|
+
content: outputText ? [{ type: "text", chars: outputText.length }] : []
|
|
203
|
+
}
|
|
204
|
+
] : [
|
|
205
|
+
{
|
|
206
|
+
role: "assistant",
|
|
207
|
+
content: outputText ? [{ type: "text", text: outputText }] : []
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
);
|
|
170
211
|
const usageAttributes = extractGenAiUsageAttributes(message);
|
|
171
212
|
const endAttributes = {
|
|
172
213
|
...baseAttributes,
|
|
@@ -336,6 +377,17 @@ function parseAdvisorThinkingLevel(rawValue) {
|
|
|
336
377
|
`AI_ADVISOR_THINKING_LEVEL must be one of: minimal, low, medium, high, xhigh`
|
|
337
378
|
);
|
|
338
379
|
}
|
|
380
|
+
function parseOptionalPositiveInteger(envName, rawValue) {
|
|
381
|
+
const trimmed = toOptionalTrimmed(rawValue);
|
|
382
|
+
if (trimmed === void 0) {
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
const value = Number.parseInt(trimmed, 10);
|
|
386
|
+
if (!Number.isSafeInteger(value) || value <= 0 || String(value) !== trimmed) {
|
|
387
|
+
throw new Error(`${envName} must be a positive integer`);
|
|
388
|
+
}
|
|
389
|
+
return value;
|
|
390
|
+
}
|
|
339
391
|
var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
|
|
340
392
|
var DEFAULT_FAST_MODEL_ID = getModel(
|
|
341
393
|
"vercel-ai-gateway",
|
|
@@ -363,6 +415,10 @@ function readBotConfig(env) {
|
|
|
363
415
|
return {
|
|
364
416
|
userName: env.JUNIOR_BOT_NAME ?? "junior",
|
|
365
417
|
modelId: validateGatewayModelId(env.AI_MODEL) ?? DEFAULT_MODEL_ID,
|
|
418
|
+
modelContextWindowTokens: parseOptionalPositiveInteger(
|
|
419
|
+
"AI_MODEL_CONTEXT_WINDOW_TOKENS",
|
|
420
|
+
env.AI_MODEL_CONTEXT_WINDOW_TOKENS
|
|
421
|
+
),
|
|
366
422
|
fastModelId: validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID,
|
|
367
423
|
loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
|
|
368
424
|
visionModelId: validateGatewayModelId(env.AI_VISION_MODEL),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"node-html-markdown": "^2.0.0",
|
|
47
47
|
"yaml": "^2.9.0",
|
|
48
48
|
"zod": "^4.4.3",
|
|
49
|
-
"@sentry/junior-plugin-api": "0.
|
|
49
|
+
"@sentry/junior-plugin-api": "0.56.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@sentry/node": ">=10.0.0"
|