@sentry/junior 0.116.1 → 0.118.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 (93) hide show
  1. package/dist/{agent-hooks-GUAAS3XH.js → agent-hooks-J2FB7XD6.js} +8 -10
  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/stats.d.ts +22 -0
  7. package/dist/api/schema.d.ts +2 -0
  8. package/dist/api/schema.js +7 -3
  9. package/dist/api/stats.d.ts +16 -0
  10. package/dist/api/user-pages/routes.d.ts +4 -0
  11. package/dist/api.js +192 -18
  12. package/dist/app.js +53 -46
  13. package/dist/{catalog-runtime-WMPC4J4U.js → catalog-runtime-UHHLU53I.js} +5 -5
  14. package/dist/chat/agent/prompt.d.ts +6 -2
  15. package/dist/chat/agent/request.d.ts +2 -1
  16. package/dist/chat/agent/resume.d.ts +2 -0
  17. package/dist/chat/agent/tools.d.ts +1 -0
  18. package/dist/chat/conversations/history.d.ts +22 -1
  19. package/dist/chat/conversations/projection.d.ts +6 -0
  20. package/dist/chat/mcp/client.d.ts +4 -0
  21. package/dist/chat/mcp/errors.d.ts +22 -1
  22. package/dist/chat/oauth-response.d.ts +21 -0
  23. package/dist/chat/plugins/prompt.d.ts +9 -0
  24. package/dist/chat/plugins/types.d.ts +2 -21
  25. package/dist/chat/plugins/user-pages.d.ts +15 -0
  26. package/dist/chat/plugins/viewer-actors.d.ts +6 -0
  27. package/dist/chat/prompt.d.ts +1 -2
  28. package/dist/chat/sandbox/paths.d.ts +2 -0
  29. package/dist/chat/sandbox/sandbox.d.ts +3 -1
  30. package/dist/chat/sandbox/snapshot/install.d.ts +6 -0
  31. package/dist/chat/sandbox/snapshot/profile.d.ts +14 -0
  32. package/dist/chat/sandbox/snapshot/resolve.d.ts +22 -0
  33. package/dist/chat/sandbox/snapshot/span.d.ts +2 -0
  34. package/dist/chat/sandbox/workspace.d.ts +1 -0
  35. package/dist/chat/services/context-compaction-marker.d.ts +1 -0
  36. package/dist/chat/services/turn-session-record.d.ts +2 -0
  37. package/dist/chat/skills.d.ts +1 -2
  38. package/dist/chat/state/turn-session.d.ts +2 -0
  39. package/dist/chat/tools/index.d.ts +4 -1
  40. package/dist/chat/tools/query-conversation-events.d.ts +1 -1
  41. package/dist/chat/tools/sandbox/file-utils.d.ts +11 -0
  42. package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
  43. package/dist/chat/tools/sandbox/grep.d.ts +2 -1
  44. package/dist/chat/tools/sandbox/view-image.d.ts +11 -0
  45. package/dist/chat/tools/types.d.ts +7 -0
  46. package/dist/{chunk-XA3VLFPK.js → chunk-3LGCVRBQ.js} +154 -20
  47. package/dist/{chunk-JPKGW6L6.js → chunk-3MUSSFMP.js} +78 -39
  48. package/dist/{chunk-7CSVG54J.js → chunk-3N3FMCKN.js} +30 -2
  49. package/dist/{chunk-VBMMMVVQ.js → chunk-4LPPH7Y6.js} +405 -446
  50. package/dist/{chunk-2YYPXQNQ.js → chunk-4SOQPUF4.js} +1 -1
  51. package/dist/{chunk-H35BDAUS.js → chunk-6BGUAOYP.js} +8 -8
  52. package/dist/{chunk-GDAXWEIJ.js → chunk-6K2QE2UI.js} +731 -424
  53. package/dist/{chunk-DLLW5ITL.js → chunk-7GADTCNS.js} +30 -9
  54. package/dist/{chunk-TC4ZEM2D.js → chunk-7SM3P4XR.js} +28 -1
  55. package/dist/{chunk-WKU7TY22.js → chunk-FIANEE7Z.js} +3 -1
  56. package/dist/{chunk-RXRQJF7Y.js → chunk-GCGJGSQL.js} +21 -0
  57. package/dist/{chunk-ZYHLNTC2.js → chunk-GT2ZUALJ.js} +584 -25
  58. package/dist/{chunk-KNCELO2M.js → chunk-HN6LFYG3.js} +1 -1
  59. package/dist/{chunk-N3C4XCRE.js → chunk-HWUBBHLK.js} +1 -1
  60. package/dist/{chunk-BFYVIOTS.js → chunk-KC5EE27Y.js} +13 -3
  61. package/dist/chunk-L72GDKSN.js +37 -0
  62. package/dist/{chunk-F3SWGSPG.js → chunk-MLZFK4KP.js} +1 -1
  63. package/dist/{chunk-RYFAVWP3.js → chunk-PJPKHGLG.js} +34 -15
  64. package/dist/{chunk-CY7NXM32.js → chunk-PWRFTDTY.js} +4 -2
  65. package/dist/{chunk-WLT72MRP.js → chunk-QWCF4RAC.js} +1 -1
  66. package/dist/{chunk-CGXLCMWI.js → chunk-S6R23MFB.js} +117 -17
  67. package/dist/{chunk-OY2PQQ4P.js → chunk-UXLYAVYL.js} +1 -1
  68. package/dist/cli/chat.js +23 -24
  69. package/dist/cli/check.js +6 -6
  70. package/dist/cli/main.js +1 -1
  71. package/dist/cli/plugins.js +12 -14
  72. package/dist/cli/{snapshot-warmup.js → snapshot-create.js} +17 -20
  73. package/dist/cli/upgrade.js +7 -7
  74. package/dist/db/schema/stats.d.ts +93 -0
  75. package/dist/db/schema.d.ts +94 -1
  76. package/dist/{db-FH4R5ZQ4.js → db-PMRHFSYL.js} +6 -6
  77. package/dist/handlers/oauth-html.d.ts +4 -2
  78. package/dist/instrumentation.js +2 -2
  79. package/dist/nitro.js +3 -3
  80. package/dist/{oauth-callback-server-C2SSJ3FQ.js → oauth-callback-server-QJUC7S4X.js} +15 -17
  81. package/dist/{plugins-IUJA4VBU.js → plugins-HW4VWW6G.js} +1 -1
  82. package/dist/{runner-NAALFIXM.js → runner-PZMV6JQW.js} +10 -12
  83. package/dist/{skills-UN2JXYYF.js → skills-W4JETYSV.js} +6 -6
  84. package/dist/stats.d.ts +18 -0
  85. package/dist/{validation-EEF4ROHR.js → validation-IJLNHZSU.js} +6 -6
  86. package/dist/version.js +1 -1
  87. package/migrations/0011_sour_golden_guardian.sql +8 -0
  88. package/migrations/meta/0011_snapshot.json +1195 -0
  89. package/migrations/meta/_journal.json +8 -1
  90. package/package.json +5 -5
  91. package/dist/chat/sandbox/runtime-dependency-snapshots.d.ts +0 -20
  92. package/dist/chunk-4ZNGQH7C.js +0 -25
  93. package/dist/chunk-FAEALXEH.js +0 -517
@@ -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>;
@@ -11,6 +11,7 @@ export interface SandboxCommandInput {
11
11
  env?: Record<string, string>;
12
12
  signal?: AbortSignal;
13
13
  sudo?: boolean;
14
+ timeoutMs?: number;
14
15
  }
15
16
  export interface SandboxFileStat {
16
17
  isDirectory(): boolean;
@@ -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
  /**
@@ -31,7 +31,6 @@ export interface Skill extends SkillMetadata {
31
31
  }
32
32
  export interface SkillInvocation {
33
33
  skillName: string;
34
- args: string;
35
34
  }
36
35
  export interface DiscoverSkillsOptions {
37
36
  additionalRoots?: string[];
@@ -40,7 +39,7 @@ export interface DiscoverSkillsOptions {
40
39
  export declare function resetSkillDiscoveryCache(): void;
41
40
  /** Scan all configured skill roots and return discovered skill metadata, using a cache when roots match. */
42
41
  export declare function discoverSkills(options?: DiscoverSkillsOptions): Promise<SkillMetadata[]>;
43
- /** Extract a skill invocation (name + args) from a user message, or return null if none matches. */
42
+ /** Extract an explicit skill invocation from a user message. */
44
43
  export declare function parseSkillInvocation(messageText: string, availableSkills: SkillMetadata[]): SkillInvocation | null;
45
44
  /** Look up a skill by name from the available set. */
46
45
  export declare function findSkillByName(skillName: string, available: SkillMetadata[]): SkillMetadata | null;
@@ -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>;
@@ -2,5 +2,8 @@ import type { SkillMetadata } from "@/chat/skills";
2
2
  import type { ToolRegistry } from "@/chat/tools/definition";
3
3
  import type { ToolHooks, ToolRuntimeContext } from "@/chat/tools/types";
4
4
  export type { ToolHooks, ToolRuntimeContext };
5
+ export interface CreateToolsOptions {
6
+ includeLoadSkill?: boolean;
7
+ }
5
8
  /** Build the model-facing tool registry from runtime-owned context and capabilities. */
6
- export declare function createTools(availableSkills: SkillMetadata[], hooks: ToolHooks | undefined, context: ToolRuntimeContext): ToolRegistry;
9
+ export declare function createTools(availableSkills: SkillMetadata[], hooks: ToolHooks | undefined, context: ToolRuntimeContext, options?: CreateToolsOptions): ToolRegistry;
@@ -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;
@@ -1,8 +1,19 @@
1
1
  import type { SandboxCommandInput, SandboxCommandResult, SandboxFileSystem } from "@/chat/sandbox/workspace";
2
2
  export type { SandboxFileSystem };
3
3
  export type SandboxCommandRunner = (input: SandboxCommandInput) => Promise<SandboxCommandResult>;
4
+ /** Safe measurements produced by one sandbox-backed search operation. */
5
+ export interface SandboxSearchTelemetry {
6
+ emittedLineCount: number;
7
+ limit: number;
8
+ limitReached: boolean;
9
+ parsedRecordCount: number;
10
+ rawOutputBytes: number;
11
+ resultCount: number;
12
+ resultBytes: number;
13
+ }
4
14
  export declare const MAX_TEXT_CHARS = 60000;
5
15
  export declare const RIPGREP_EXCLUDED_GLOBS: readonly ["!**/.git/**", "!**/node_modules/**"];
16
+ export declare const DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 30000;
6
17
  export type TextSearchResultDetails = {
7
18
  ok: true;
8
19
  status?: "success";
@@ -1,4 +1,4 @@
1
- import { type SandboxCommandRunner, type SandboxFileSystem, type TextSearchResultDetails, type TextSearchToolResult } from "@/chat/tools/sandbox/file-utils";
1
+ import { type SandboxCommandRunner, type SandboxFileSystem, type SandboxSearchTelemetry, type TextSearchResultDetails, type TextSearchToolResult } from "@/chat/tools/sandbox/file-utils";
2
2
  import { type JuniorToolResultEnvelope } from "@/chat/tool-support/structured-result";
3
3
  interface FindFilesSuccessResult {
4
4
  content: JuniorToolResultEnvelope["content"];
@@ -17,6 +17,7 @@ type FindFilesResult = FindFilesSuccessResult | TextSearchToolResult;
17
17
  export declare function findFiles(params: {
18
18
  fs: SandboxFileSystem;
19
19
  limit?: unknown;
20
+ onTelemetry?: (telemetry: SandboxSearchTelemetry) => void;
20
21
  path?: string;
21
22
  pattern: string;
22
23
  runCommand?: SandboxCommandRunner;
@@ -1,4 +1,4 @@
1
- import { type SandboxCommandRunner, type SandboxFileSystem, type TextSearchResultDetails, type TextSearchToolResult } from "@/chat/tools/sandbox/file-utils";
1
+ import { type SandboxCommandRunner, type SandboxFileSystem, type SandboxSearchTelemetry, type TextSearchResultDetails, type TextSearchToolResult } from "@/chat/tools/sandbox/file-utils";
2
2
  interface GrepResultData {
3
3
  context: number;
4
4
  glob?: string;
@@ -31,6 +31,7 @@ export declare function grepFiles(params: {
31
31
  ignoreCase?: boolean;
32
32
  limit?: unknown;
33
33
  literal?: boolean;
34
+ onTelemetry?: (telemetry: SandboxSearchTelemetry) => void;
34
35
  path?: string;
35
36
  pattern: string;
36
37
  runCommand?: SandboxCommandRunner;
@@ -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;
@@ -5,7 +5,7 @@ import {
5
5
  logInfo,
6
6
  logWarn,
7
7
  setSpanAttributes
8
- } from "./chunk-F3SWGSPG.js";
8
+ } from "./chunk-MLZFK4KP.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,