@sentry/junior 0.116.1 → 0.117.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.
Files changed (67) hide show
  1. package/dist/{agent-hooks-GUAAS3XH.js → agent-hooks-5IK4LPLP.js} +8 -8
  2. package/dist/api/conversations/detail.d.ts +7 -0
  3. package/dist/api/conversations/event-list.d.ts +7 -0
  4. package/dist/api/conversations/events.d.ts +1 -1
  5. package/dist/api/schema/conversation.d.ts +28 -0
  6. package/dist/api/schema.js +1 -1
  7. package/dist/api.js +25 -12
  8. package/dist/app.js +32 -30
  9. package/dist/{catalog-runtime-WMPC4J4U.js → catalog-runtime-MTUYW3AA.js} +5 -5
  10. package/dist/chat/agent/prompt.d.ts +2 -0
  11. package/dist/chat/agent/request.d.ts +2 -1
  12. package/dist/chat/agent/resume.d.ts +2 -0
  13. package/dist/chat/agent/tools.d.ts +1 -0
  14. package/dist/chat/conversations/history.d.ts +22 -1
  15. package/dist/chat/conversations/projection.d.ts +6 -0
  16. package/dist/chat/mcp/client.d.ts +4 -0
  17. package/dist/chat/mcp/errors.d.ts +22 -1
  18. package/dist/chat/oauth-response.d.ts +21 -0
  19. package/dist/chat/plugins/prompt.d.ts +9 -0
  20. package/dist/chat/plugins/types.d.ts +2 -21
  21. package/dist/chat/sandbox/snapshot/install.d.ts +6 -0
  22. package/dist/chat/sandbox/snapshot/profile.d.ts +14 -0
  23. package/dist/chat/sandbox/snapshot/resolve.d.ts +22 -0
  24. package/dist/chat/sandbox/snapshot/span.d.ts +2 -0
  25. package/dist/chat/services/context-compaction-marker.d.ts +1 -0
  26. package/dist/chat/services/turn-session-record.d.ts +2 -0
  27. package/dist/chat/state/turn-session.d.ts +2 -0
  28. package/dist/chat/tools/query-conversation-events.d.ts +1 -1
  29. package/dist/chat/tools/sandbox/view-image.d.ts +11 -0
  30. package/dist/chat/tools/types.d.ts +7 -0
  31. package/dist/{chunk-RYFAVWP3.js → chunk-27KWTFP6.js} +33 -12
  32. package/dist/{chunk-N3C4XCRE.js → chunk-3GE3F4LT.js} +1 -1
  33. package/dist/{chunk-XA3VLFPK.js → chunk-5O3JDQDG.js} +154 -20
  34. package/dist/{chunk-CGXLCMWI.js → chunk-6G45D3YG.js} +114 -10
  35. package/dist/{chunk-F3SWGSPG.js → chunk-72Z3HNTQ.js} +1 -1
  36. package/dist/{chunk-DLLW5ITL.js → chunk-AKG4TZNH.js} +2 -2
  37. package/dist/{chunk-TC4ZEM2D.js → chunk-BFIRIZPH.js} +9 -0
  38. package/dist/{chunk-OY2PQQ4P.js → chunk-CU7HTGNI.js} +1 -1
  39. package/dist/{chunk-CY7NXM32.js → chunk-DS27ZBSP.js} +4 -2
  40. package/dist/{chunk-VBMMMVVQ.js → chunk-FAWBONPC.js} +404 -443
  41. package/dist/{chunk-7CSVG54J.js → chunk-KU6ALEJC.js} +1 -1
  42. package/dist/{chunk-H35BDAUS.js → chunk-LN765Y4N.js} +6 -6
  43. package/dist/{chunk-KNCELO2M.js → chunk-LUMEBOWC.js} +1 -1
  44. package/dist/{chunk-GDAXWEIJ.js → chunk-NHZEUHKH.js} +514 -344
  45. package/dist/{chunk-ZYHLNTC2.js → chunk-STUI6NIL.js} +78 -16
  46. package/dist/{chunk-2YYPXQNQ.js → chunk-W5JEQQEA.js} +1 -1
  47. package/dist/{chunk-JPKGW6L6.js → chunk-WEK3V5RI.js} +73 -32
  48. package/dist/{chunk-WLT72MRP.js → chunk-WUNPRM76.js} +1 -1
  49. package/dist/{chunk-FAEALXEH.js → chunk-YRWJ3KEU.js} +2 -2
  50. package/dist/{chunk-BFYVIOTS.js → chunk-ZRQUQJUV.js} +12 -2
  51. package/dist/cli/chat.js +21 -21
  52. package/dist/cli/check.js +6 -6
  53. package/dist/cli/main.js +1 -1
  54. package/dist/cli/plugins.js +11 -11
  55. package/dist/cli/{snapshot-warmup.js → snapshot-create.js} +17 -19
  56. package/dist/cli/upgrade.js +5 -5
  57. package/dist/{db-FH4R5ZQ4.js → db-J2ITKS63.js} +5 -5
  58. package/dist/handlers/oauth-html.d.ts +4 -2
  59. package/dist/instrumentation.js +2 -2
  60. package/dist/nitro.js +2 -2
  61. package/dist/{oauth-callback-server-C2SSJ3FQ.js → oauth-callback-server-NQYKBT24.js} +15 -15
  62. package/dist/{runner-NAALFIXM.js → runner-53V5GIQM.js} +10 -10
  63. package/dist/{skills-UN2JXYYF.js → skills-BU3ZMLVB.js} +6 -6
  64. package/dist/{validation-EEF4ROHR.js → validation-74EIKKE6.js} +6 -6
  65. package/dist/version.js +1 -1
  66. package/package.json +5 -5
  67. package/dist/chat/sandbox/runtime-dependency-snapshots.d.ts +0 -20
@@ -0,0 +1,21 @@
1
+ export type OAuthProviderErrorPhase = "token_exchange" | "token_refresh";
2
+ export interface OAuthProviderErrorDetails {
3
+ phase: OAuthProviderErrorPhase;
4
+ provider: string;
5
+ resourceHost: string;
6
+ status?: number;
7
+ }
8
+ /** Safe failure at a generic OAuth provider boundary. */
9
+ export declare class OAuthProviderError extends Error {
10
+ readonly phase: OAuthProviderErrorPhase;
11
+ readonly provider: string;
12
+ readonly resourceHost: string;
13
+ readonly status?: number;
14
+ constructor(details: OAuthProviderErrorDetails);
15
+ }
16
+ /** Return safe generic OAuth provider fields for logs and spans. */
17
+ export declare function getOAuthProviderErrorAttributes(error: unknown): Record<string, string | number>;
18
+ /** Read only the bounded prefix needed to classify an OAuth error response. */
19
+ export declare function readBoundedOAuthErrorBody(response: Response): Promise<string>;
20
+ /** Preserve small OAuth error responses while bounding bodies before SDK parsing. */
21
+ export declare function fetchWithBoundedOAuthErrorBodies(fetchFn?: typeof fetch, onErrorResponseStatus?: (status: number) => void): typeof fetch;
@@ -1,4 +1,13 @@
1
+ /** Validated plugin context retained for the current turn. */
2
+ export interface PluginTurnContext {
3
+ content: Record<string, unknown>;
4
+ kind: string;
5
+ loadedAtMs: number;
6
+ pluginName: string;
7
+ version: number;
8
+ }
1
9
  export interface PluginPromptContributionContext {
10
+ context?: PluginTurnContext;
2
11
  id: string;
3
12
  pluginName: string;
4
13
  text: string;
@@ -1,4 +1,6 @@
1
+ import type { PluginRuntimeDependency, PluginRuntimePostinstallCommand } from "@sentry/junior-plugin-api";
1
2
  import type { UserTokenStore } from "@/chat/credentials/user-token-store";
3
+ export type { PluginNpmRuntimeDependency, PluginRuntimeDependency, PluginRuntimePostinstallCommand, PluginSystemRuntimeDependency, PluginSystemRuntimeDependencyFromUrl, } from "@sentry/junior-plugin-api";
2
4
  export interface PluginOAuthConfig {
3
5
  clientIdEnv: string;
4
6
  clientSecretEnv: string;
@@ -28,26 +30,6 @@ export interface OAuthBearerCredentials {
28
30
  authTokenPlaceholder?: string;
29
31
  }
30
32
  export type PluginCredentials = OAuthBearerCredentials;
31
- export interface PluginNpmRuntimeDependency {
32
- type: "npm";
33
- package: string;
34
- version: string;
35
- }
36
- export interface PluginSystemRuntimeDependency {
37
- type: "system";
38
- package: string;
39
- }
40
- export interface PluginSystemRuntimeDependencyFromUrl {
41
- type: "system";
42
- url: string;
43
- sha256: string;
44
- }
45
- export type PluginRuntimeDependency = PluginNpmRuntimeDependency | PluginSystemRuntimeDependency | PluginSystemRuntimeDependencyFromUrl;
46
- export interface PluginRuntimePostinstallCommand {
47
- cmd: string;
48
- args?: string[];
49
- sudo?: boolean;
50
- }
51
33
  export interface PluginMcpHttpConfig {
52
34
  transport: "http";
53
35
  url: string;
@@ -148,4 +130,3 @@ export interface InlinePluginManifestDefinition {
148
130
  manifest: PluginManifest;
149
131
  packageName?: string;
150
132
  }
151
- export {};
@@ -0,0 +1,6 @@
1
+ import type { PluginRuntimeDependency, PluginRuntimePostinstallCommand } from "@/chat/plugins/types";
2
+ import type { SandboxSession } from "@/chat/sandbox/workspace";
3
+ /** Install declared system and npm dependencies into a snapshot sandbox. */
4
+ export declare function dependencies(sandbox: SandboxSession, values: PluginRuntimeDependency[]): Promise<void>;
5
+ /** Run declared postinstall commands after dependency binaries are available. */
6
+ export declare function postinstall(sandbox: SandboxSession, commands: PluginRuntimePostinstallCommand[]): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import type { PluginRuntimeDependency, PluginRuntimePostinstallCommand } from "@/chat/plugins/types";
2
+ export type Profile = {
3
+ hash: string;
4
+ dependencyCount: number;
5
+ floating: boolean;
6
+ dependencies: PluginRuntimeDependency[];
7
+ postinstall: PluginRuntimePostinstallCommand[];
8
+ };
9
+ /** Build the dependency profile that selects a reusable sandbox snapshot. */
10
+ export declare function create(runtime: string): Profile | null;
11
+ /** Return the current dependency profile hash without building its snapshot. */
12
+ export declare function hash(runtime: string): string | undefined;
13
+ /** Decide whether a cached snapshot has outlived a floating profile. */
14
+ export declare function isStale(profile: Profile, createdAtMs: number): boolean;
@@ -0,0 +1,22 @@
1
+ export type ResolveOutcome = "no_profile" | "cache_hit" | "cache_hit_after_lock_wait" | "rebuilt" | "forced_rebuild";
2
+ export type RebuildReason = "cache_miss" | "floating_stale" | "force_rebuild" | "snapshot_missing";
3
+ /** Result of resolving the reusable snapshot for one dependency profile. */
4
+ export interface Snapshot {
5
+ snapshotId?: string;
6
+ profileHash?: string;
7
+ dependencyCount: number;
8
+ cacheHit: boolean;
9
+ resolveOutcome: ResolveOutcome;
10
+ rebuildReason?: RebuildReason;
11
+ }
12
+ export type ProgressPhase = "resolve_start" | "cache_hit" | "waiting_for_lock" | "building_snapshot" | "build_complete";
13
+ /** Resolve or build the reusable snapshot for the current dependency profile. */
14
+ export declare function resolve(params: {
15
+ runtime: string;
16
+ timeoutMs: number;
17
+ forceRebuild?: boolean;
18
+ staleSnapshotId?: string;
19
+ onProgress?: (phase: ProgressPhase) => void | Promise<void>;
20
+ }): Promise<Snapshot>;
21
+ /** Identify provider errors that mean a referenced snapshot no longer exists. */
22
+ export declare function isMissingError(error: unknown): boolean;
@@ -0,0 +1,2 @@
1
+ /** Record one stage of dependency snapshot resolution or creation. */
2
+ export declare function trace<T>(name: string, op: string, attributes: Record<string, unknown>, callback: () => Promise<T>): Promise<T>;
@@ -1,5 +1,6 @@
1
1
  import type { PiMessage } from "@/chat/pi/messages";
2
2
  export declare const COMPACTION_SUMMARY_PREFIX = "Context compaction summary for future Junior turns:";
3
+ export declare const ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX = "Active-turn context checkpoint. If no newer user instruction follows this checkpoint, continue the same unfinished task now using the authoritative instructions above and this summary as internal continuation state. If a newer user instruction follows, treat this checkpoint as prior context and follow that newer instruction. Do not reply with a plan or summary solely because this checkpoint appeared:";
3
4
  export declare const MODEL_HANDOFF_SUMMARY_PREFIX = "Model handoff checkpoint. Continue the outstanding request now using this summary as the complete prior context:";
4
5
  /** Return whether text is one of Junior's durable compacted-context markers. */
5
6
  export declare function isCompactionSummaryText(text: string): boolean;
@@ -4,6 +4,7 @@ import type { Destination, Actor, Source } from "@sentry/junior-plugin-api";
4
4
  import type { PiMessage } from "@/chat/pi/messages";
5
5
  import type { ConversationMessageProvenance } from "@/chat/conversations/provenance";
6
6
  import { type AgentTurnUsage } from "@/chat/usage";
7
+ import type { PluginTurnContext } from "@/chat/plugins/prompt";
7
8
  export interface TurnSessionContext {
8
9
  conversationId: string;
9
10
  sessionId: string;
@@ -40,6 +41,7 @@ export declare function persistRunningSessionRecord(args: {
40
41
  actor?: Actor;
41
42
  reasoningLevel?: string;
42
43
  surface?: AgentTurnSurface;
44
+ turnContexts?: PluginTurnContext[];
43
45
  turnStartMessageIndex?: number;
44
46
  }): Promise<boolean>;
45
47
  /**
@@ -12,6 +12,7 @@ import { type Destination, type Source } from "@sentry/junior-plugin-api";
12
12
  import { type PiMessage } from "@/chat/pi/messages";
13
13
  import { type Actor } from "@/chat/actor";
14
14
  import { type ConversationMessageProvenance } from "@/chat/conversations/provenance";
15
+ import type { PluginTurnContext } from "@/chat/plugins/prompt";
15
16
  import { type AgentTurnUsage } from "@/chat/usage";
16
17
  import type { ConversationPrivacy } from "@/chat/conversation-privacy";
17
18
  import type { ConversationStore } from "@/chat/conversations/store";
@@ -95,6 +96,7 @@ export declare function upsertAgentTurnSessionRecord(args: {
95
96
  errorMessage?: string;
96
97
  resumedFromSliceId?: number;
97
98
  traceId?: string;
99
+ turnContexts?: PluginTurnContext[];
98
100
  turnStartMessageIndex?: number;
99
101
  ttlMs?: number;
100
102
  }): Promise<AgentTurnSessionRecord>;
@@ -9,7 +9,7 @@ export declare function createQueryConversationEventsTool(context: ToolRuntimeCo
9
9
  after_seq?: number | null | undefined;
10
10
  before_seq?: number | null | undefined;
11
11
  limit?: number | null | undefined;
12
- types?: ("message" | "handoff" | "user_message" | "assistant_message" | "tool_result" | "compaction" | "mcp_provider_connected" | "authorization_requested" | "authorization_completed" | "tool_execution_started" | "message_updated" | "message_handled" | "messages_summarized" | "turn_started" | "turn_routed" | "turn_completed" | "turn_failed" | "subagent_started" | "subagent_ended")[] | null | undefined;
12
+ types?: ("message" | "handoff" | "user_message" | "assistant_message" | "tool_result" | "compaction" | "mcp_provider_connected" | "authorization_requested" | "authorization_completed" | "tool_execution_started" | "message_updated" | "message_handled" | "messages_summarized" | "turn_started" | "turn_routed" | "turn_context" | "turn_completed" | "turn_failed" | "subagent_started" | "subagent_ended")[] | null | undefined;
13
13
  };
14
14
  execute?: ((input: unknown, options: import("./definition").ToolExecuteOptions) => {
15
15
  [x: string]: unknown;
@@ -0,0 +1,11 @@
1
+ import type { SandboxWorkspace } from "@/chat/sandbox/workspace";
2
+ import type { ViewImageToolDeps } from "@/chat/tools/types";
3
+ /** Create a tool that loads a sandbox image into the active model context. */
4
+ export declare function createViewImageTool(sandbox: SandboxWorkspace, supportsImageInput: () => boolean, deps?: ViewImageToolDeps): Omit<import("../definition").AnyToolDefinition, "inputSchema" | "outputSchema" | "prepareArguments" | "execute"> & {
5
+ inputSchema: import("../definition").JsonSchemaObject;
6
+ outputSchema?: undefined;
7
+ prepareArguments(args: unknown): {
8
+ path: string;
9
+ };
10
+ execute?: (input: unknown, options: import("../definition").ToolExecuteOptions) => Promise<import("@/chat/tool-support/zod-tool").ContentOnlyToolResult> | import("@/chat/tool-support/zod-tool").ContentOnlyToolResult;
11
+ };
@@ -34,6 +34,10 @@ export interface WebSearchToolDeps {
34
34
  max_results?: number;
35
35
  }) => Promise<JuniorToolResult> | JuniorToolResult;
36
36
  }
37
+ /** Optional host image reader for deterministic tool execution without sandbox I/O. */
38
+ export interface ViewImageToolDeps {
39
+ readFile?: (path: string) => Promise<Buffer | null | undefined>;
40
+ }
37
41
  export interface ToolHooks {
38
42
  /**
39
43
  * Materialize generated files and return sandbox paths that exist before the
@@ -44,6 +48,7 @@ export interface ToolHooks {
44
48
  onSkillLoaded?: (skill: Skill) => void | LoadSkillMetadata | Promise<void | LoadSkillMetadata>;
45
49
  toolOverrides?: {
46
50
  imageGenerate?: ImageGenerateToolDeps;
51
+ viewImage?: ViewImageToolDeps;
47
52
  webFetch?: WebFetchToolDeps;
48
53
  webSearch?: WebSearchToolDeps;
49
54
  };
@@ -70,6 +75,8 @@ interface BaseToolRuntimeContext {
70
75
  egress: PluginEgress;
71
76
  mcpToolManager?: McpToolManager;
72
77
  workspace: SandboxWorkspace;
78
+ /** Report whether the model currently executing the turn accepts images. */
79
+ supportsImageInput?: () => boolean;
73
80
  }
74
81
  interface SlackToolRuntimeContext extends BaseToolRuntimeContext {
75
82
  destination: SlackDestination;
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-MU6HHZEN.js";
5
5
  import {
6
6
  getStateAdapter
7
- } from "./chunk-7CSVG54J.js";
7
+ } from "./chunk-KU6ALEJC.js";
8
8
  import {
9
9
  agentHistoryItemSchema,
10
10
  contextProvenance,
@@ -16,7 +16,7 @@ import {
16
16
  instructionProvenanceFor,
17
17
  sanitizePostgresJson,
18
18
  withConversationEventLock
19
- } from "./chunk-BFYVIOTS.js";
19
+ } from "./chunk-ZRQUQJUV.js";
20
20
  import {
21
21
  sleep
22
22
  } from "./chunk-4ZNGQH7C.js";
@@ -25,7 +25,7 @@ import {
25
25
  escapeXml,
26
26
  resolveGatewayModel,
27
27
  unwrapCurrentInstruction
28
- } from "./chunk-CY7NXM32.js";
28
+ } from "./chunk-DS27ZBSP.js";
29
29
  import {
30
30
  toStoredSlackActor
31
31
  } from "./chunk-HZF5P5UN.js";
@@ -34,7 +34,7 @@ import {
34
34
  logException,
35
35
  logWarn,
36
36
  setSpanAttributes
37
- } from "./chunk-F3SWGSPG.js";
37
+ } from "./chunk-72Z3HNTQ.js";
38
38
  import {
39
39
  isRecord,
40
40
  toOptionalNumber,
@@ -412,16 +412,29 @@ async function commitMessagesLocked(args, executor) {
412
412
  });
413
413
  if (matchingPrefix === current.messages.length) {
414
414
  const newMessages = nextLocalMessages.slice(matchingPrefix);
415
- await eventStore.append(
416
- args.conversationId,
417
- newMessages.map((message, index) => ({
415
+ const turnContext = args.turnContext;
416
+ const turnContextEvents = turnContext?.contexts.map((context, index) => ({
417
+ idempotencyKey: `turn:${turnContext.turnId}:context:${context.pluginName}:${index}`,
418
+ createdAtMs: context.loadedAtMs,
419
+ data: {
420
+ type: "turn_context",
421
+ turnId: turnContext.turnId,
422
+ pluginName: context.pluginName,
423
+ kind: context.kind,
424
+ version: context.version,
425
+ content: context.content
426
+ }
427
+ })) ?? [];
428
+ await eventStore.append(args.conversationId, [
429
+ ...newMessages.map((message, index) => ({
418
430
  data: historyItemFromPiMessage(
419
431
  message,
420
432
  nextLocalProvenance[matchingPrefix + index]
421
433
  ),
422
434
  createdAtMs: messageTimestamp(message)
423
- }))
424
- );
435
+ })),
436
+ ...turnContextEvents
437
+ ]);
425
438
  } else {
426
439
  throw new Error(
427
440
  `Agent history for ${args.conversationId} changed before its committed boundary`
@@ -818,6 +831,7 @@ function materializeAgentTurnSessionRecord(stored, piProjection, turnStartMessag
818
831
  function restoreRuntimeContext(messages, runtimeContext) {
819
832
  if (!runtimeContext || runtimeContext.length === 0) return messages;
820
833
  const restored = [...messages];
834
+ const unmatchedRuntimeContext = [];
821
835
  for (const runtimeMessage of runtimeContext) {
822
836
  const runtime = runtimeMessage;
823
837
  const targetIndex = restored.findIndex((message) => {
@@ -825,7 +839,7 @@ function restoreRuntimeContext(messages, runtimeContext) {
825
839
  return candidate.role === "user" && candidate.timestamp === runtime.timestamp;
826
840
  });
827
841
  if (targetIndex < 0) {
828
- restored.push(runtimeMessage);
842
+ unmatchedRuntimeContext.push(runtimeMessage);
829
843
  continue;
830
844
  }
831
845
  const target = restored[targetIndex];
@@ -837,7 +851,7 @@ function restoreRuntimeContext(messages, runtimeContext) {
837
851
  ]
838
852
  };
839
853
  }
840
- return restored;
854
+ return [...unmatchedRuntimeContext, ...restored];
841
855
  }
842
856
  async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
843
857
  const stateAdapter = getStateAdapter();
@@ -1022,7 +1036,13 @@ async function upsertAgentTurnSessionRecord(args) {
1022
1036
  conversationId: args.conversationId,
1023
1037
  messages: args.piMessages,
1024
1038
  ...instructionActor ? { newMessageProvenance: instructionProvenanceFor(instructionActor) } : {},
1025
- ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {}
1039
+ ...args.trailingMessageProvenance ? { trailingMessageProvenance: args.trailingMessageProvenance } : {},
1040
+ ...args.turnContexts && args.turnContexts.length > 0 ? {
1041
+ turnContext: {
1042
+ contexts: args.turnContexts,
1043
+ turnId: args.sessionId
1044
+ }
1045
+ } : {}
1026
1046
  });
1027
1047
  const durableTurnStartMessageIndex = args.turnStartMessageIndex === void 0 ? void 0 : stripRuntimeTurnContext(
1028
1048
  args.piMessages.slice(0, args.turnStartMessageIndex)
@@ -1294,6 +1314,7 @@ async function persistRunningSessionRecord(args) {
1294
1314
  ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
1295
1315
  modelId: args.modelId,
1296
1316
  ...args.reasoningLevel ? { reasoningLevel: args.reasoningLevel } : {},
1317
+ ...args.turnContexts ? { turnContexts: args.turnContexts } : {},
1297
1318
  ...args.actor ?? latestSessionRecord?.actor ? { actor: args.actor ?? latestSessionRecord?.actor } : {},
1298
1319
  ...getActiveTraceId() ?? latestSessionRecord?.traceId ? { traceId: getActiveTraceId() ?? latestSessionRecord?.traceId } : {},
1299
1320
  ...(args.turnStartMessageIndex ?? latestSessionRecord?.turnStartMessageIndex) !== void 0 ? {
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "@sentry/junior",
4
- version: "0.116.1",
4
+ version: "0.117.0",
5
5
  private: false,
6
6
  publishConfig: {
7
7
  access: "public"
@@ -5,7 +5,7 @@ import {
5
5
  logInfo,
6
6
  logWarn,
7
7
  setSpanAttributes
8
- } from "./chunk-F3SWGSPG.js";
8
+ } from "./chunk-72Z3HNTQ.js";
9
9
  import {
10
10
  discoverInstalledPluginPackageContent,
11
11
  normalizePluginPackageNames
@@ -1097,6 +1097,115 @@ function hasRequiredOAuthScope(storedScope, requiredScope) {
1097
1097
  return required.every((scope) => stored.has(scope));
1098
1098
  }
1099
1099
 
1100
+ // src/chat/oauth-response.ts
1101
+ var MAX_OAUTH_ERROR_BODY_BYTES = 16 * 1024;
1102
+ var MAX_OAUTH_ERROR_BODY_READS = MAX_OAUTH_ERROR_BODY_BYTES;
1103
+ var MAX_OAUTH_ERROR_BODY_READ_MS = 5e3;
1104
+ var OAUTH_ERROR_BODY_READ_TIMEOUT = /* @__PURE__ */ Symbol("oauth-error-body-read-timeout");
1105
+ async function readOAuthErrorBodyChunk(reader) {
1106
+ let timeout;
1107
+ const timeoutResult = new Promise(
1108
+ (resolve) => {
1109
+ timeout = setTimeout(
1110
+ resolve,
1111
+ MAX_OAUTH_ERROR_BODY_READ_MS,
1112
+ OAUTH_ERROR_BODY_READ_TIMEOUT
1113
+ );
1114
+ }
1115
+ );
1116
+ try {
1117
+ const result = await Promise.race([reader.read(), timeoutResult]);
1118
+ if (result === OAUTH_ERROR_BODY_READ_TIMEOUT) {
1119
+ await reader.cancel();
1120
+ throw new Error("OAuth error body read timed out");
1121
+ }
1122
+ return result;
1123
+ } finally {
1124
+ if (timeout) {
1125
+ clearTimeout(timeout);
1126
+ }
1127
+ }
1128
+ }
1129
+ var OAuthProviderError = class extends Error {
1130
+ phase;
1131
+ provider;
1132
+ resourceHost;
1133
+ status;
1134
+ constructor(details) {
1135
+ super(`OAuth provider ${details.phase.replace("_", " ")} failed`);
1136
+ this.name = "OAuthProviderError";
1137
+ this.phase = details.phase;
1138
+ this.provider = details.provider;
1139
+ this.resourceHost = details.resourceHost;
1140
+ this.status = details.status;
1141
+ }
1142
+ };
1143
+ function getOAuthProviderErrorAttributes(error) {
1144
+ if (!(error instanceof OAuthProviderError)) {
1145
+ return {};
1146
+ }
1147
+ return {
1148
+ "app.credential.provider": error.provider,
1149
+ "app.oauth.error.phase": error.phase,
1150
+ "server.address": error.resourceHost,
1151
+ ...error.status !== void 0 ? { "http.response.status_code": error.status } : {}
1152
+ };
1153
+ }
1154
+ async function readBoundedOAuthErrorBody(response) {
1155
+ if (!response.body) {
1156
+ return "";
1157
+ }
1158
+ const reader = response.body.getReader();
1159
+ const decoder = new TextDecoder();
1160
+ let bytesRead = 0;
1161
+ let reads = 0;
1162
+ let text = "";
1163
+ try {
1164
+ while (bytesRead < MAX_OAUTH_ERROR_BODY_BYTES && reads < MAX_OAUTH_ERROR_BODY_READS) {
1165
+ reads += 1;
1166
+ const { done, value } = await readOAuthErrorBodyChunk(reader);
1167
+ if (done) {
1168
+ return text + decoder.decode();
1169
+ }
1170
+ const remaining = MAX_OAUTH_ERROR_BODY_BYTES - bytesRead;
1171
+ const chunk = value.subarray(0, remaining);
1172
+ bytesRead += chunk.byteLength;
1173
+ text += decoder.decode(chunk, { stream: true });
1174
+ if (chunk.byteLength < value.byteLength) {
1175
+ await reader.cancel();
1176
+ return text + decoder.decode();
1177
+ }
1178
+ }
1179
+ await reader.cancel();
1180
+ return text + decoder.decode();
1181
+ } finally {
1182
+ reader.releaseLock();
1183
+ }
1184
+ }
1185
+ function fetchWithBoundedOAuthErrorBodies(fetchFn = globalThis.fetch, onErrorResponseStatus) {
1186
+ return (async (input, init) => {
1187
+ const response = await fetchFn(input, init);
1188
+ if (response.ok) {
1189
+ return response;
1190
+ }
1191
+ onErrorResponseStatus?.(response.status);
1192
+ if (!response.body) {
1193
+ return response;
1194
+ }
1195
+ let body = "";
1196
+ try {
1197
+ body = await readBoundedOAuthErrorBody(response);
1198
+ } catch {
1199
+ await response.body.cancel().catch(() => void 0);
1200
+ }
1201
+ return new Response(body, {
1202
+ headers: response.headers,
1203
+ status: response.status,
1204
+ statusText: response.statusText
1205
+ });
1206
+ });
1207
+ }
1208
+
1100
1209
  // src/chat/plugins/command-env.ts
1101
1210
  var ENV_PLACEHOLDER_RE2 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
1102
1211
  function resolveValue(manifest, value) {
@@ -1320,7 +1429,7 @@ function parseRefreshError(text) {
1320
1429
  return void 0;
1321
1430
  }
1322
1431
  }
1323
- async function refreshAccessToken(refreshToken, oauth, requestedScope) {
1432
+ async function refreshAccessToken(provider, refreshToken, oauth, requestedScope) {
1324
1433
  const clientId = process.env[oauth.clientIdEnv]?.trim();
1325
1434
  const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
1326
1435
  if (!clientId || !clientSecret) {
@@ -1338,28 +1447,49 @@ async function refreshAccessToken(refreshToken, oauth, requestedScope) {
1338
1447
  tokenAuthMethod: oauth.tokenAuthMethod,
1339
1448
  tokenExtraHeaders: oauth.tokenExtraHeaders
1340
1449
  });
1341
- const response = await fetch(oauth.tokenEndpoint, {
1342
- method: "POST",
1343
- headers: request.headers,
1344
- body: request.body,
1345
- signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
1346
- });
1450
+ const errorDetails = {
1451
+ phase: "token_refresh",
1452
+ provider,
1453
+ resourceHost: new URL(oauth.tokenEndpoint).hostname
1454
+ };
1455
+ let response;
1456
+ try {
1457
+ response = await fetch(oauth.tokenEndpoint, {
1458
+ method: "POST",
1459
+ headers: request.headers,
1460
+ body: request.body,
1461
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
1462
+ });
1463
+ } catch {
1464
+ throw new OAuthProviderError(errorDetails);
1465
+ }
1347
1466
  if (!response.ok) {
1348
- const errorCode = parseRefreshError(await response.text());
1467
+ let errorCode;
1468
+ try {
1469
+ errorCode = parseRefreshError(await readBoundedOAuthErrorBody(response));
1470
+ } catch {
1471
+ throw new OAuthProviderError({
1472
+ ...errorDetails,
1473
+ status: response.status
1474
+ });
1475
+ }
1349
1476
  if (errorCode === "invalid_grant" || errorCode === "bad_refresh_token") {
1350
- throw new OAuthRefreshRejectedError(
1351
- `Token refresh rejected: ${errorCode}`
1352
- );
1477
+ throw new OAuthRefreshRejectedError("Token refresh was rejected");
1353
1478
  }
1354
- throw new Error(
1355
- `Token refresh failed: ${response.status}${errorCode ? ` ${errorCode}` : ""}`
1356
- );
1479
+ throw new OAuthProviderError({
1480
+ ...errorDetails,
1481
+ status: response.status
1482
+ });
1483
+ }
1484
+ try {
1485
+ const data = await response.json();
1486
+ return parseOAuthTokenResponse(data, requestedScope, {
1487
+ treatEmptyScopeAsUnreported: oauth.treatEmptyScopeAsUnreported,
1488
+ refreshToken
1489
+ });
1490
+ } catch {
1491
+ throw new OAuthProviderError(errorDetails);
1357
1492
  }
1358
- const data = await response.json();
1359
- return parseOAuthTokenResponse(data, requestedScope, {
1360
- treatEmptyScopeAsUnreported: oauth.treatEmptyScopeAsUnreported,
1361
- refreshToken
1362
- });
1363
1493
  }
1364
1494
  function getLeaseExpiry(expiresAt) {
1365
1495
  return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
@@ -1447,6 +1577,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
1447
1577
  );
1448
1578
  }
1449
1579
  const refreshed = await refreshAccessToken(
1580
+ provider,
1450
1581
  latest.refreshToken,
1451
1582
  oauth,
1452
1583
  latest.scope ?? oauth.scope
@@ -1955,6 +2086,9 @@ export {
1955
2086
  parseInlinePluginManifest,
1956
2087
  CredentialUnavailableError,
1957
2088
  hasRequiredOAuthScope,
2089
+ OAuthProviderError,
2090
+ getOAuthProviderErrorAttributes,
2091
+ fetchWithBoundedOAuthErrorBodies,
1958
2092
  resolvePluginCommandEnv,
1959
2093
  resolveAuthTokenPlaceholder,
1960
2094
  buildOAuthTokenRequest,