@sentry/junior 0.55.0 → 0.57.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.
@@ -1,6 +1,7 @@
1
1
  import type { HeartbeatHookContext } from "@sentry/junior-plugin-api";
2
2
  /** Build the plugin-scoped heartbeat context that gates durable dispatch access. */
3
3
  export declare function createHeartbeatContext(args: {
4
+ legacyStatePrefixes?: string[];
4
5
  nowMs: number;
5
6
  plugin: string;
6
7
  }): HeartbeatHookContext;
@@ -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>;
@@ -5,6 +5,7 @@ export interface BotConfig {
5
5
  fastModelId: string;
6
6
  loadingMessages: string[];
7
7
  modelId: string;
8
+ modelContextWindowTokens?: number;
8
9
  visionModelId?: string;
9
10
  turnTimeoutMs: number;
10
11
  userName: string;
@@ -23,6 +23,7 @@ export declare function completeText(params: {
23
23
  modelId: string;
24
24
  system?: string;
25
25
  messages: Message[];
26
+ messageAttributeMode?: "content" | "metadata";
26
27
  thinkingLevel?: ThinkingLevel;
27
28
  temperature?: number;
28
29
  maxTokens?: number;
@@ -1,3 +1,6 @@
1
1
  import type { AgentPluginState } from "@sentry/junior-plugin-api";
2
+ export interface PluginStateOptions {
3
+ legacyStatePrefixes?: string[];
4
+ }
2
5
  /** Create a durable state namespace scoped to one trusted plugin. */
3
- export declare function createPluginState(plugin: string): AgentPluginState;
6
+ export declare function createPluginState(plugin: string, options?: PluginStateOptions): AgentPluginState;
@@ -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 requestMessagesAttribute = serializeGenAiAttribute(params.messages);
130
- const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
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
- role: "assistant",
167
- content: outputText ? [{ type: "text", text: outputText }] : []
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),
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-XI3CFWTA.js";
4
+ } from "../chunk-AA5TIFN5.js";
5
5
  import {
6
6
  getPluginProviders,
7
7
  getPluginRuntimeDependencies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.55.0",
3
+ "version": "0.57.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.55.0"
49
+ "@sentry/junior-plugin-api": "0.57.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@sentry/node": ">=10.0.0"
@@ -61,7 +61,8 @@
61
61
  "tsup": "^8.5.1",
62
62
  "typescript": "^6.0.3",
63
63
  "vercel": "^54.4.0",
64
- "vitest": "^4.1.7"
64
+ "vitest": "^4.1.7",
65
+ "@sentry/junior-scheduler": "0.57.0"
65
66
  },
66
67
  "scripts": {
67
68
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",
@@ -1,24 +0,0 @@
1
- import type { ScheduledCalendarFrequency, ScheduledTask, ScheduledTaskRecurrence } from "@/chat/scheduler/types";
2
- /** Parse an ISO timestamp into a finite Unix timestamp in milliseconds. */
3
- export declare function parseScheduleTimestamp(value: string): number | undefined;
4
- export interface ZonedDateTimeParts {
5
- day: number;
6
- hour: number;
7
- minute: number;
8
- month: number;
9
- second: number;
10
- weekday: number;
11
- year: number;
12
- }
13
- /** Resolve a UTC timestamp into calendar parts for a named time zone. */
14
- export declare function getZonedDateTimeParts(timestampMs: number, timezone: string): ZonedDateTimeParts;
15
- /** Build a calendar recurrence anchored to an exact first run timestamp. */
16
- export declare function buildCalendarRecurrence(args: {
17
- frequency: ScheduledCalendarFrequency;
18
- interval?: number;
19
- nextRunAtMs: number;
20
- timezone: string;
21
- weekdays?: number[];
22
- }): ScheduledTaskRecurrence;
23
- /** Return the next fire time after a completed run, when the task recurs. */
24
- export declare function getNextRunAtMs(task: ScheduledTask, scheduledForMs: number, afterMs?: number): number | undefined;
@@ -1,2 +0,0 @@
1
- /** Create Junior's built-in trusted scheduler plugin. */
2
- export declare function createSchedulerPlugin(): import("@sentry/junior-plugin-api").JuniorPlugin;
@@ -1,7 +0,0 @@
1
- import { type ScheduledRun, type ScheduledTask } from "@/chat/scheduler/types";
2
- /** Build the marker-delimited user prompt for one scheduled task execution. */
3
- export declare function buildScheduledTaskRunPrompt(args: {
4
- nowMs: number;
5
- run: ScheduledRun;
6
- task: ScheduledTask;
7
- }): string;
@@ -1,49 +0,0 @@
1
- import type { StateAdapter } from "chat";
2
- import type { ScheduledRun, ScheduledTask } from "@/chat/scheduler/types";
3
- export interface SchedulerStore {
4
- claimDueRun(args: {
5
- nowMs: number;
6
- }): Promise<ScheduledRun | undefined>;
7
- getRun(runId: string): Promise<ScheduledRun | undefined>;
8
- getTask(taskId: string): Promise<ScheduledTask | undefined>;
9
- listIncompleteRuns(): Promise<ScheduledRun[]>;
10
- listTasksForTeam(teamId: string): Promise<ScheduledTask[]>;
11
- markRunBlocked(args: {
12
- completedAtMs: number;
13
- errorMessage: string;
14
- runId: string;
15
- startedAtMs?: number;
16
- }): Promise<ScheduledRun | undefined>;
17
- markRunCompleted(args: {
18
- completedAtMs: number;
19
- resultMessageTs?: string;
20
- runId: string;
21
- startedAtMs: number;
22
- }): Promise<ScheduledRun | undefined>;
23
- markRunFailed(args: {
24
- completedAtMs: number;
25
- errorMessage: string;
26
- startedAtMs?: number;
27
- runId: string;
28
- }): Promise<ScheduledRun | undefined>;
29
- markRunSkipped(args: {
30
- completedAtMs: number;
31
- errorMessage: string;
32
- runId: string;
33
- }): Promise<ScheduledRun | undefined>;
34
- markRunDispatched(args: {
35
- claimedAtMs: number;
36
- dispatchId: string;
37
- nowMs: number;
38
- runId: string;
39
- }): Promise<ScheduledRun | undefined>;
40
- saveTask(task: ScheduledTask): Promise<void>;
41
- updateTaskAfterRun(args: {
42
- errorMessage?: string;
43
- nowMs: number;
44
- run: ScheduledRun;
45
- status: "blocked" | "completed" | "failed";
46
- }): Promise<void>;
47
- }
48
- /** Create the production scheduler store backed by Junior's state adapter. */
49
- export declare function createStateSchedulerStore(stateAdapter?: StateAdapter): SchedulerStore;
@@ -1,86 +0,0 @@
1
- export type ScheduledTaskStatus = "active" | "paused" | "blocked" | "deleted";
2
- export type ScheduledRunStatus = "pending" | "running" | "completed" | "failed" | "blocked" | "skipped";
3
- export interface ScheduledTaskPrincipal {
4
- slackUserId: string;
5
- fullName?: string;
6
- userName?: string;
7
- }
8
- export interface ScheduledTaskExecutionActor {
9
- type: "system";
10
- id: string;
11
- }
12
- export declare const SCHEDULED_TASK_SYSTEM_ACTOR: Readonly<{
13
- type: "system";
14
- id: string;
15
- }>;
16
- export interface ScheduledTaskDestination {
17
- platform: "slack";
18
- teamId: string;
19
- channelId: string;
20
- }
21
- export interface ScheduledTaskConversationAccess {
22
- audience: "direct" | "group" | "channel";
23
- visibility: "private" | "public" | "unknown";
24
- }
25
- export interface ScheduledTaskCredentialSubject {
26
- type: "user";
27
- userId: string;
28
- allowedWhen: "private-direct-conversation";
29
- }
30
- export type ScheduledCalendarFrequency = "daily" | "weekly" | "monthly" | "yearly";
31
- export interface ScheduledLocalTime {
32
- hour: number;
33
- minute: number;
34
- }
35
- export interface ScheduledTaskRecurrence {
36
- dayOfMonth?: number;
37
- frequency: ScheduledCalendarFrequency;
38
- interval: number;
39
- month?: number;
40
- startDate: string;
41
- time: ScheduledLocalTime;
42
- weekdays?: number[];
43
- }
44
- export interface ScheduledTaskSchedule {
45
- description: string;
46
- timezone: string;
47
- kind: "one_off" | "recurring";
48
- recurrence?: ScheduledTaskRecurrence;
49
- }
50
- export interface ScheduledTaskSpec {
51
- text: string;
52
- }
53
- export interface ScheduledTask {
54
- id: string;
55
- createdAtMs: number;
56
- createdBy: ScheduledTaskPrincipal;
57
- conversationAccess?: ScheduledTaskConversationAccess;
58
- credentialSubject?: ScheduledTaskCredentialSubject;
59
- destination: ScheduledTaskDestination;
60
- executionActor?: ScheduledTaskExecutionActor;
61
- lastRunAtMs?: number;
62
- nextRunAtMs?: number;
63
- originalRequest?: string;
64
- runNowAtMs?: number;
65
- schedule: ScheduledTaskSchedule;
66
- status: ScheduledTaskStatus;
67
- statusReason?: string;
68
- task: ScheduledTaskSpec;
69
- updatedAtMs: number;
70
- version: number;
71
- }
72
- export interface ScheduledRun {
73
- id: string;
74
- attempt: number;
75
- claimedAtMs: number;
76
- completedAtMs?: number;
77
- dispatchId?: string;
78
- errorMessage?: string;
79
- idempotencyKey: string;
80
- resultMessageTs?: string;
81
- scheduledForMs: number;
82
- startedAtMs?: number;
83
- status: ScheduledRunStatus;
84
- taskId: string;
85
- taskVersion: number;
86
- }
@@ -1,29 +0,0 @@
1
- import type { ToolRuntimeContext } from "@/chat/tools/types";
2
- /** Create a tool that stores a scheduled task for the active Slack context. */
3
- export declare function createSlackScheduleCreateTaskTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
4
- task: import("@sinclair/typebox").TString;
5
- schedule: import("@sinclair/typebox").TString;
6
- timezone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
7
- next_run_at: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
8
- recurrence: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"daily">, import("@sinclair/typebox").TLiteral<"weekly">, import("@sinclair/typebox").TLiteral<"monthly">, import("@sinclair/typebox").TLiteral<"yearly">]>>;
9
- }>>;
10
- /** Create a tool that lists scheduled tasks for the active Slack destination. */
11
- export declare function createSlackScheduleListTasksTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{}>>;
12
- /** Create a tool that edits a scheduled task in the active Slack destination. */
13
- export declare function createSlackScheduleUpdateTaskTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
14
- task_id: import("@sinclair/typebox").TString;
15
- task: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
16
- schedule: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
17
- timezone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
18
- next_run_at: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
19
- recurrence: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"daily">, import("@sinclair/typebox").TLiteral<"weekly">, import("@sinclair/typebox").TLiteral<"monthly">, import("@sinclair/typebox").TLiteral<"yearly">]>, import("@sinclair/typebox").TNull]>>;
20
- status: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"active">, import("@sinclair/typebox").TLiteral<"paused">, import("@sinclair/typebox").TLiteral<"blocked">]>>;
21
- }>>;
22
- /** Create a tool that removes a scheduled task from the active Slack destination. */
23
- export declare function createSlackScheduleDeleteTaskTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
24
- task_id: import("@sinclair/typebox").TString;
25
- }>>;
26
- /** Create a tool that marks an existing scheduled task due immediately. */
27
- export declare function createSlackScheduleRunTaskNowTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
28
- task_id: import("@sinclair/typebox").TString;
29
- }>>;