@sentry/junior 0.80.1 → 0.82.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 (32) hide show
  1. package/dist/{agent-hooks-ZGTDOXQY.js → agent-hooks-5ZRILRC3.js} +1 -1
  2. package/dist/app.js +7 -905
  3. package/dist/chat/agent-dispatch/runner.d.ts +1 -1
  4. package/dist/chat/app/services.d.ts +1 -1
  5. package/dist/chat/egress/credentialed.d.ts +57 -0
  6. package/dist/chat/egress/plugin.d.ts +24 -0
  7. package/dist/chat/plugins/credential-hooks.d.ts +2 -0
  8. package/dist/chat/respond.d.ts +1 -1
  9. package/dist/chat/sandbox/{egress-credentials.d.ts → egress/credentials.d.ts} +28 -5
  10. package/dist/chat/sandbox/egress/policy.d.ts +32 -0
  11. package/dist/chat/sandbox/egress/proxy.d.ts +35 -0
  12. package/dist/chat/sandbox/{egress-session.d.ts → egress/session.d.ts} +41 -11
  13. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  14. package/dist/chat/tools/types.d.ts +2 -1
  15. package/dist/{chunk-W2QGQCKG.js → chunk-NC6LR6U4.js} +1062 -14
  16. package/dist/{chunk-Y3YUOEAZ.js → chunk-TR2G37II.js} +14 -5
  17. package/dist/{chunk-RV5RYIJW.js → chunk-UXPG6ZIN.js} +28 -0
  18. package/dist/cli/chat.js +3 -3
  19. package/dist/cli/env.js +7 -1
  20. package/dist/cli/init.js +1 -0
  21. package/dist/cli/main.js +1 -1
  22. package/dist/cli/plugins.js +1 -1
  23. package/dist/handlers/agent-dispatch.d.ts +1 -1
  24. package/dist/handlers/sandbox-egress-proxy.d.ts +2 -2
  25. package/dist/reporting.js +1 -1
  26. package/dist/{runner-JOVPVMIH.js → runner-VRFJLG2M.js} +2 -2
  27. package/package.json +6 -5
  28. package/dist/chat/sandbox/egress-policy.d.ts +0 -15
  29. package/dist/chat/sandbox/egress-proxy.d.ts +0 -19
  30. /package/dist/chat/sandbox/{egress-oidc.d.ts → egress/oidc.d.ts} +0 -0
  31. /package/dist/chat/sandbox/{egress-schemas.d.ts → egress/schemas.d.ts} +0 -0
  32. /package/dist/chat/sandbox/{egress-tracing.d.ts → egress/tracing.d.ts} +0 -0
@@ -42,7 +42,7 @@ import {
42
42
  getPlugins,
43
43
  getSlackToolContext,
44
44
  resolveChannelCapabilities
45
- } from "./chunk-Y3YUOEAZ.js";
45
+ } from "./chunk-TR2G37II.js";
46
46
  import {
47
47
  createPluginLogger,
48
48
  createPluginState
@@ -122,6 +122,7 @@ import {
122
122
  parseSkillInvocation
123
123
  } from "./chunk-WCXVEQPI.js";
124
124
  import {
125
+ CredentialUnavailableError,
125
126
  credentialContextSchema,
126
127
  pluginCatalogRuntime,
127
128
  resolveAuthTokenPlaceholder,
@@ -191,7 +192,7 @@ function getConfigDefaults() {
191
192
  return cloneDefaults(installDefaults);
192
193
  }
193
194
 
194
- // src/chat/sandbox/egress-tracing.ts
195
+ // src/chat/sandbox/egress/tracing.ts
195
196
  function isValidDomainPattern(domain) {
196
197
  if (domain.includes("*")) {
197
198
  return domain.startsWith("*.") && domain.indexOf("*", 1) === -1;
@@ -882,6 +883,17 @@ function parsePrefixFlag(extras) {
882
883
  error: "jr-rpc config list accepts optional --prefix <value>\n"
883
884
  };
884
885
  }
886
+ function buildUnknownKeyWarning(key) {
887
+ const knownKeys = pluginCatalogRuntime.getProviders().flatMap((plugin) => [...plugin.manifest.configKeys]);
888
+ if (knownKeys.includes(key)) {
889
+ return "";
890
+ }
891
+ const prefix = key.includes(".") ? key.slice(0, key.indexOf(".") + 1) : "";
892
+ const suggestions = prefix ? knownKeys.filter((k) => k.startsWith(prefix)) : knownKeys;
893
+ const hint = suggestions.length > 0 ? `Known keys with prefix "${prefix}": ${suggestions.join(", ")}` : `Known config keys: ${knownKeys.join(", ")}`;
894
+ return `Warning: "${key}" is not a recognized config key. ${hint}
895
+ `;
896
+ }
885
897
  async function handleConfigCommand(args, deps) {
886
898
  const usage = [
887
899
  "jr-rpc config get <key>",
@@ -960,6 +972,7 @@ ${usage}
960
972
  });
961
973
  }
962
974
  }
975
+ const unknownKeyWarning = buildUnknownKeyWarning(key);
963
976
  try {
964
977
  const entry = await configuration.set({
965
978
  key,
@@ -989,6 +1002,7 @@ ${usage}
989
1002
  updatedBy: entry.updatedBy,
990
1003
  source: entry.source
991
1004
  },
1005
+ stderr: unknownKeyWarning,
992
1006
  exitCode: 0
993
1007
  });
994
1008
  } catch (error) {
@@ -4191,7 +4205,7 @@ function createOperationKey(toolName, input) {
4191
4205
  // src/chat/tools/slack/channel-post-message.ts
4192
4206
  function createSlackChannelPostMessageTool(context, state) {
4193
4207
  return tool({
4194
- description: "Post a message in the active Slack channel context (outside the thread). Use this only when the user explicitly asks to post/send/share/say something in the current channel. Do not use it for normal thread replies, speculative broadcasts, or requests targeting another named channel; explain that limitation instead. Do not claim a channel message was posted unless this tool succeeds in this turn.",
4208
+ description: "Post a new top-level message to the current Slack channel. Use only when the user explicitly asks to post/send/share/say something to the channel. Do not use for thread replies, inline @mentions, or pinging mentioned users.",
4195
4209
  inputSchema: Type14.Object({
4196
4210
  text: Type14.String({
4197
4211
  minLength: 1,
@@ -7149,10 +7163,10 @@ async function startOAuthFlow(provider, input) {
7149
7163
  };
7150
7164
  }
7151
7165
 
7152
- // src/chat/sandbox/egress-session.ts
7166
+ // src/chat/sandbox/egress/session.ts
7153
7167
  import { createHmac, randomUUID, timingSafeEqual } from "crypto";
7154
7168
 
7155
- // src/chat/sandbox/egress-schemas.ts
7169
+ // src/chat/sandbox/egress/schemas.ts
7156
7170
  import { z } from "zod";
7157
7171
  import {
7158
7172
  pluginAuthorizationSchema,
@@ -7217,7 +7231,7 @@ function parseSandboxEgressPermissionDeniedSignal(value) {
7217
7231
  return result.success ? result.data : void 0;
7218
7232
  }
7219
7233
 
7220
- // src/chat/sandbox/egress-session.ts
7234
+ // src/chat/sandbox/egress/session.ts
7221
7235
  var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
7222
7236
  var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
7223
7237
  var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
@@ -7419,7 +7433,7 @@ async function consumeSandboxEgressPermissionDeniedSignal(egressId) {
7419
7433
  return signal;
7420
7434
  }
7421
7435
 
7422
- // src/chat/sandbox/egress-policy.ts
7436
+ // src/chat/sandbox/egress/policy.ts
7423
7437
  function matchesSandboxEgressDomain(host, domain) {
7424
7438
  return host.toLowerCase() === domain.toLowerCase();
7425
7439
  }
@@ -11199,6 +11213,1028 @@ function createMcpAuthOrchestration(input) {
11199
11213
  };
11200
11214
  }
11201
11215
 
11216
+ // src/chat/egress/plugin.ts
11217
+ import { randomUUID as randomUUID4 } from "crypto";
11218
+
11219
+ // src/chat/plugins/credential-hooks.ts
11220
+ import {
11221
+ pluginAuthorizationSchema as pluginAuthorizationSchema2,
11222
+ pluginCredentialResultSchema,
11223
+ pluginGrantSchema as pluginGrantSchema2,
11224
+ pluginProviderAccountSchema as pluginProviderAccountSchema2
11225
+ } from "@sentry/junior-plugin-api";
11226
+ function parseSchema(schema, value, message) {
11227
+ const result = schema.safeParse(value);
11228
+ if (!result.success) {
11229
+ throw new Error(message);
11230
+ }
11231
+ return result.data;
11232
+ }
11233
+ function parseAuthorization(value, pluginName) {
11234
+ if (value === void 0) {
11235
+ return void 0;
11236
+ }
11237
+ const authorization = parseSchema(
11238
+ pluginAuthorizationSchema2,
11239
+ value,
11240
+ `Plugin "${pluginName}" grant authorization is invalid`
11241
+ );
11242
+ if (authorization.provider !== pluginName) {
11243
+ throw new Error(
11244
+ `Plugin "${pluginName}" grant authorization provider must match the issuing plugin`
11245
+ );
11246
+ }
11247
+ return authorization;
11248
+ }
11249
+ function parseGrant(value, pluginName) {
11250
+ return parseSchema(
11251
+ pluginGrantSchema2,
11252
+ value,
11253
+ `Plugin "${pluginName}" grantForEgress returned an invalid grant`
11254
+ );
11255
+ }
11256
+ function pluginFor(provider) {
11257
+ return getPlugins().find((candidate) => candidate.manifest.name === provider);
11258
+ }
11259
+ function basePluginContext(plugin) {
11260
+ const pluginName = plugin.manifest.name;
11261
+ return {
11262
+ plugin: { name: pluginName },
11263
+ log: createPluginLogger(pluginName),
11264
+ db: getDb()
11265
+ };
11266
+ }
11267
+ function parseCredentialResult(value, pluginName) {
11268
+ const result = parseSchema(
11269
+ pluginCredentialResultSchema,
11270
+ value,
11271
+ `Plugin "${pluginName}" issueCredential result is invalid`
11272
+ );
11273
+ if (result.type === "lease") {
11274
+ parseAuthorization(result.lease.authorization, pluginName);
11275
+ return result;
11276
+ }
11277
+ if (result.type === "unavailable") {
11278
+ return result;
11279
+ }
11280
+ parseAuthorization(result.authorization, pluginName);
11281
+ return result;
11282
+ }
11283
+ async function selectPluginGrant(input) {
11284
+ const plugin = pluginFor(input.provider);
11285
+ const hook = plugin?.hooks?.grantForEgress;
11286
+ if (!plugin || !hook) {
11287
+ return void 0;
11288
+ }
11289
+ const result = await hook({
11290
+ ...basePluginContext(plugin),
11291
+ request: {
11292
+ ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
11293
+ method: input.method,
11294
+ ...input.operation ? { operation: input.operation } : {},
11295
+ url: input.upstreamUrl.toString()
11296
+ }
11297
+ });
11298
+ return result === void 0 ? void 0 : parseGrant(result, plugin.manifest.name);
11299
+ }
11300
+ async function onPluginEgressResponse(input) {
11301
+ const plugin = pluginFor(input.provider);
11302
+ const hook = plugin?.hooks?.onEgressResponse;
11303
+ if (!plugin || !hook) {
11304
+ return {};
11305
+ }
11306
+ let permissionDenied;
11307
+ await hook({
11308
+ ...basePluginContext(plugin),
11309
+ grant: input.grant,
11310
+ permissionDenied(message) {
11311
+ const trimmed = message.trim();
11312
+ if (!trimmed) {
11313
+ throw new Error(
11314
+ `Plugin "${plugin.manifest.name}" onEgressResponse permissionDenied message is empty`
11315
+ );
11316
+ }
11317
+ permissionDenied = { message: trimmed };
11318
+ },
11319
+ request: {
11320
+ method: input.method,
11321
+ ...input.operation ? { operation: input.operation } : {},
11322
+ url: input.upstreamUrl.toString()
11323
+ },
11324
+ response: input.response
11325
+ });
11326
+ return permissionDenied ? { permissionDenied } : {};
11327
+ }
11328
+ function hasEgressCredentialHooks(provider) {
11329
+ const hooks = pluginFor(provider)?.hooks;
11330
+ return Boolean(hooks?.grantForEgress || hooks?.issueCredential);
11331
+ }
11332
+ async function resolvePluginOAuthAccount(input) {
11333
+ const plugin = pluginFor(input.provider);
11334
+ const hook = plugin?.hooks?.resolveOAuthAccount;
11335
+ if (!plugin || !hook) {
11336
+ return void 0;
11337
+ }
11338
+ const account = await hook({
11339
+ ...basePluginContext(plugin),
11340
+ tokens: input.tokens
11341
+ });
11342
+ return account === void 0 ? void 0 : parseSchema(
11343
+ pluginProviderAccountSchema2,
11344
+ account,
11345
+ `Plugin "${plugin.manifest.name}" resolveOAuthAccount returned an invalid account`
11346
+ );
11347
+ }
11348
+ async function issuePluginCredential(input) {
11349
+ const plugin = pluginFor(input.provider);
11350
+ const hook = plugin?.hooks?.issueCredential;
11351
+ if (!plugin || !hook) {
11352
+ throw new Error(`Plugin "${input.provider}" has no issueCredential hook`);
11353
+ }
11354
+ const currentUserId = input.actor.type === "user" ? input.actor.userId : void 0;
11355
+ const credentialSubjectUserId = input.credentialSubject?.userId;
11356
+ const result = await hook({
11357
+ ...basePluginContext(plugin),
11358
+ actor: input.actor,
11359
+ grant: input.grant,
11360
+ ...input.credentialSubject ? { credentialSubject: input.credentialSubject } : {},
11361
+ tokens: {
11362
+ ...currentUserId ? {
11363
+ currentUser: {
11364
+ userId: currentUserId,
11365
+ get: async () => await input.userTokenStore.get(
11366
+ currentUserId,
11367
+ plugin.manifest.name
11368
+ ),
11369
+ set: async (tokens) => {
11370
+ await input.userTokenStore.set(
11371
+ currentUserId,
11372
+ plugin.manifest.name,
11373
+ tokens
11374
+ );
11375
+ },
11376
+ withRefresh: async (callback) => await input.userTokenStore.withRefresh(
11377
+ currentUserId,
11378
+ plugin.manifest.name,
11379
+ callback
11380
+ )
11381
+ }
11382
+ } : {},
11383
+ ...credentialSubjectUserId ? {
11384
+ credentialSubject: {
11385
+ userId: credentialSubjectUserId,
11386
+ get: async () => await input.userTokenStore.get(
11387
+ credentialSubjectUserId,
11388
+ plugin.manifest.name
11389
+ ),
11390
+ set: async (tokens) => {
11391
+ await input.userTokenStore.set(
11392
+ credentialSubjectUserId,
11393
+ plugin.manifest.name,
11394
+ tokens
11395
+ );
11396
+ },
11397
+ withRefresh: async (callback) => await input.userTokenStore.withRefresh(
11398
+ credentialSubjectUserId,
11399
+ plugin.manifest.name,
11400
+ callback
11401
+ )
11402
+ }
11403
+ } : {}
11404
+ }
11405
+ });
11406
+ return parseCredentialResult(result, plugin.manifest.name);
11407
+ }
11408
+
11409
+ // src/chat/sandbox/egress/credentials.ts
11410
+ var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
11411
+ var SandboxEgressCredentialError = class extends Error {
11412
+ authorization;
11413
+ grant;
11414
+ kind;
11415
+ provider;
11416
+ constructor(input) {
11417
+ super(input.message);
11418
+ this.name = "SandboxEgressCredentialError";
11419
+ this.authorization = input.authorization;
11420
+ this.grant = input.grant;
11421
+ this.kind = input.kind;
11422
+ this.provider = input.provider;
11423
+ }
11424
+ };
11425
+ function defaultGrantForProvider(input) {
11426
+ const access = HTTP_READ_METHODS.has(
11427
+ input.method.toUpperCase()
11428
+ ) ? "read" : "write";
11429
+ return {
11430
+ source: "broker",
11431
+ grant: {
11432
+ name: "default",
11433
+ access,
11434
+ reason: `sandbox-egress:${input.provider}:${access}`
11435
+ }
11436
+ };
11437
+ }
11438
+ function oauthAuthorizationForProvider(provider) {
11439
+ const oauth = pluginCatalogRuntime.getOAuthConfig(provider);
11440
+ return oauth ? {
11441
+ type: "oauth",
11442
+ provider,
11443
+ ...oauth.scope ? { scope: oauth.scope } : {}
11444
+ } : void 0;
11445
+ }
11446
+ function credentialSubjectFromContext(context) {
11447
+ return "subject" in context.credentials && context.credentials.subject ? { type: "user", userId: context.credentials.subject.userId } : void 0;
11448
+ }
11449
+ function assertLeaseTransformsOwnedByProvider(provider, lease) {
11450
+ for (const transform of lease.headerTransforms) {
11451
+ if (resolveSandboxEgressProviderForHost(transform.domain) !== provider) {
11452
+ throw new Error(
11453
+ `Credential lease for ${provider} included header transform for unowned domain ${transform.domain}`
11454
+ );
11455
+ }
11456
+ }
11457
+ }
11458
+ async function selectSandboxEgressGrant(input) {
11459
+ if (!hasEgressCredentialHooks(input.provider)) {
11460
+ return defaultGrantForProvider(input);
11461
+ }
11462
+ const pluginGrant = await selectPluginGrant({
11463
+ ...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
11464
+ ...input.operation ? { operation: input.operation } : {},
11465
+ provider: input.provider,
11466
+ method: input.method,
11467
+ upstreamUrl: input.upstreamUrl
11468
+ });
11469
+ if (!pluginGrant) {
11470
+ throw new Error(
11471
+ `Plugin "${input.provider}" grantForEgress must return a grant for sandbox egress`
11472
+ );
11473
+ }
11474
+ return { source: "plugin", grant: pluginGrant };
11475
+ }
11476
+ function authorizationForSandboxEgressGrant(provider, selection) {
11477
+ return selection.source === "broker" ? oauthAuthorizationForProvider(provider) : void 0;
11478
+ }
11479
+ async function sandboxEgressCredentialLease(provider, selection, context) {
11480
+ const { grant } = selection;
11481
+ const cached = await getSandboxEgressCredentialLease(
11482
+ provider,
11483
+ grant.name,
11484
+ context
11485
+ );
11486
+ if (cached) {
11487
+ if (selection.source === "plugin" && cached.grant.access !== grant.access) {
11488
+ throw new Error(
11489
+ `Cached credential lease for ${provider}/${grant.name} has ${cached.grant.access} access, but ${grant.access} was selected`
11490
+ );
11491
+ }
11492
+ return {
11493
+ ...cached,
11494
+ grant
11495
+ };
11496
+ }
11497
+ let lease;
11498
+ if (selection.source === "plugin") {
11499
+ const credentialSubject = credentialSubjectFromContext(context);
11500
+ const pluginResult = await issuePluginCredential({
11501
+ provider,
11502
+ grant,
11503
+ actor: context.credentials.actor,
11504
+ ...credentialSubject ? { credentialSubject } : {},
11505
+ userTokenStore: createUserTokenStore()
11506
+ });
11507
+ if (pluginResult.type === "needed") {
11508
+ throw new SandboxEgressCredentialError({
11509
+ provider,
11510
+ grant,
11511
+ kind: "auth_required",
11512
+ authorization: pluginResult.authorization,
11513
+ message: pluginResult.message
11514
+ });
11515
+ }
11516
+ if (pluginResult.type === "unavailable") {
11517
+ throw new SandboxEgressCredentialError({
11518
+ provider,
11519
+ grant,
11520
+ kind: "unavailable",
11521
+ message: pluginResult.message
11522
+ });
11523
+ }
11524
+ lease = pluginResult.lease;
11525
+ } else {
11526
+ try {
11527
+ lease = await issueProviderCredentialLease({
11528
+ context: context.credentials,
11529
+ provider,
11530
+ reason: grant.reason ?? `sandbox-egress:${provider}:default`
11531
+ });
11532
+ } catch (error) {
11533
+ if (error instanceof CredentialUnavailableError) {
11534
+ throw new SandboxEgressCredentialError({
11535
+ provider,
11536
+ grant,
11537
+ kind: "auth_required",
11538
+ authorization: authorizationForSandboxEgressGrant(
11539
+ provider,
11540
+ selection
11541
+ ),
11542
+ message: error.message
11543
+ });
11544
+ }
11545
+ throw error;
11546
+ }
11547
+ }
11548
+ const headerTransforms = lease.headerTransforms ?? [];
11549
+ if (headerTransforms.length === 0) {
11550
+ throw new Error(
11551
+ `Credential lease for ${provider} did not include header transforms`
11552
+ );
11553
+ }
11554
+ const leaseExpiresAtMs = Date.parse(lease.expiresAt);
11555
+ if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
11556
+ throw new Error(`Credential lease for ${provider} is expired`);
11557
+ }
11558
+ const authorization = selection.source === "broker" ? oauthAuthorizationForProvider(provider) : lease.authorization;
11559
+ const cachedLease = {
11560
+ provider,
11561
+ grant,
11562
+ ...lease.account ? { account: lease.account } : {},
11563
+ ...authorization ? { authorization } : {},
11564
+ expiresAt: lease.expiresAt,
11565
+ headerTransforms
11566
+ };
11567
+ assertLeaseTransformsOwnedByProvider(provider, cachedLease);
11568
+ await setSandboxEgressCredentialLease(context, cachedLease);
11569
+ return cachedLease;
11570
+ }
11571
+ function hasSandboxEgressLeaseTransformForHost(lease, host) {
11572
+ return lease.headerTransforms.some(
11573
+ (transform) => matchesSandboxEgressDomain(host, transform.domain)
11574
+ );
11575
+ }
11576
+
11577
+ // src/chat/egress/credentialed.ts
11578
+ import {
11579
+ EgressAuthRequired,
11580
+ EgressPolicyDenied
11581
+ } from "@sentry/junior-plugin-api";
11582
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
11583
+ "connection",
11584
+ "host",
11585
+ "keep-alive",
11586
+ "proxy-authenticate",
11587
+ "proxy-authorization",
11588
+ "te",
11589
+ "trailer",
11590
+ "transfer-encoding",
11591
+ "upgrade"
11592
+ ]);
11593
+ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
11594
+ "vercel-sandbox-oidc-token",
11595
+ "vercel-forwarded-host",
11596
+ "vercel-forwarded-scheme",
11597
+ "vercel-forwarded-port",
11598
+ "vercel-forwarded-path"
11599
+ ]);
11600
+ var TRACE_PROPAGATION_HEADERS = /* @__PURE__ */ new Set([
11601
+ "baggage",
11602
+ "sentry-trace",
11603
+ "traceparent"
11604
+ ]);
11605
+ var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
11606
+ "content-encoding",
11607
+ "content-length"
11608
+ ]);
11609
+ var UPSTREAM_TOKEN_REJECTION_STATUS = 401;
11610
+ var UPSTREAM_PERMISSION_REJECTION_STATUS = 403;
11611
+ var GRANT_SELECTION_BODY_TEXT_LIMIT_BYTES = 64 * 1024;
11612
+ var RESPONSE_BODY_TEXT_LIMIT_BYTES = 64 * 1024;
11613
+ function authRequiredResponse(input) {
11614
+ return new Response(
11615
+ `junior-auth-required provider=${input.provider} grant=${input.grant.name} access=${input.grant.access} 401 unauthorized
11616
+ ${input.message}`,
11617
+ {
11618
+ status: 401,
11619
+ headers: {
11620
+ "content-type": "text/plain; charset=utf-8",
11621
+ "cache-control": "no-store"
11622
+ }
11623
+ }
11624
+ );
11625
+ }
11626
+ function policyDeniedResponse(error) {
11627
+ return Response.json(
11628
+ { error: error.message },
11629
+ {
11630
+ status: 403,
11631
+ headers: {
11632
+ "cache-control": "no-store"
11633
+ }
11634
+ }
11635
+ );
11636
+ }
11637
+ function shouldLogSandboxEgressInfo() {
11638
+ const environment = (process.env.SENTRY_ENVIRONMENT ?? process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "").trim().toLowerCase();
11639
+ return environment !== "production";
11640
+ }
11641
+ function egressAttributes(input) {
11642
+ return {
11643
+ ...input.egressId ? { "app.sandbox.egress_id": input.egressId } : {},
11644
+ ...input.provider ? { "app.provider.name": input.provider } : {},
11645
+ ...input.grantName ? { "app.grant.name": input.grantName } : {},
11646
+ ...input.grantAccess ? { "app.grant.access": input.grantAccess } : {},
11647
+ ...input.grantReason ? { "app.grant.reason": input.grantReason } : {},
11648
+ ...input.host ? { "server.address": input.host } : {},
11649
+ ...input.method ? { "http.request.method": input.method } : {},
11650
+ ...input.path ? { "url.path": input.path } : {},
11651
+ ...input.status ? { "http.response.status_code": input.status } : {}
11652
+ };
11653
+ }
11654
+ function redactedProxyPath(pathname) {
11655
+ if (pathname.startsWith(`${SANDBOX_EGRESS_PROXY_PATH}/`)) {
11656
+ return `${SANDBOX_EGRESS_PROXY_PATH}/<token>`;
11657
+ }
11658
+ return pathname;
11659
+ }
11660
+ function routingAttributes(request, upstreamUrl) {
11661
+ const proxyUrl = new URL(request.url);
11662
+ const attributes = {
11663
+ "app.sandbox.egress.proxy_path": redactedProxyPath(proxyUrl.pathname)
11664
+ };
11665
+ if (upstreamUrl) {
11666
+ attributes["app.sandbox.egress.upstream_path"] = upstreamUrl.pathname;
11667
+ const gitService = upstreamUrl.searchParams.get("service");
11668
+ if (upstreamUrl.hostname.toLowerCase() === "github.com" && (gitService === "git-upload-pack" || gitService === "git-receive-pack")) {
11669
+ attributes["app.sandbox.egress.git_service"] = gitService;
11670
+ }
11671
+ }
11672
+ return attributes;
11673
+ }
11674
+ function displayedUpstreamPath(upstreamUrl) {
11675
+ const gitService = upstreamUrl.searchParams.get("service");
11676
+ if (upstreamUrl.hostname.toLowerCase() === "github.com" && (gitService === "git-upload-pack" || gitService === "git-receive-pack")) {
11677
+ return `${upstreamUrl.pathname}?service=${gitService}`;
11678
+ }
11679
+ return upstreamUrl.pathname;
11680
+ }
11681
+ function upstreamPermissionAttributes(provider, upstream) {
11682
+ if (provider !== "github") {
11683
+ return {};
11684
+ }
11685
+ return {
11686
+ "app.github.accepted_permissions": upstream.headers.get("x-accepted-github-permissions") ?? void 0,
11687
+ "app.github.sso": upstream.headers.get("x-github-sso") ?? void 0
11688
+ };
11689
+ }
11690
+ function githubPermissionHeaders(upstream) {
11691
+ const acceptedPermissions = upstream.headers.get(
11692
+ "x-accepted-github-permissions"
11693
+ );
11694
+ const sso = upstream.headers.get("x-github-sso");
11695
+ return {
11696
+ ...acceptedPermissions ? { acceptedPermissions } : {},
11697
+ ...sso ? { sso } : {}
11698
+ };
11699
+ }
11700
+ function permissionDeniedMessage(provider, grant) {
11701
+ return `${provider} returned HTTP 403 after Junior injected the ${grant.name} grant. Junior forwarded the request; this is not a local runtime block.`;
11702
+ }
11703
+ function isEgressAuthRequired(error) {
11704
+ return error instanceof EgressAuthRequired || error instanceof Error && error.name === "EgressAuthRequired";
11705
+ }
11706
+ function isEgressPolicyDenied(error) {
11707
+ return error instanceof EgressPolicyDenied || error instanceof Error && error.name === "EgressPolicyDenied";
11708
+ }
11709
+ function logSandboxEgressUpstreamRequest(input) {
11710
+ if (!shouldLogSandboxEgressInfo()) {
11711
+ return;
11712
+ }
11713
+ logInfo(
11714
+ "sandbox_egress_upstream_request",
11715
+ {},
11716
+ {
11717
+ ...egressAttributes({
11718
+ egressId: input.egressId,
11719
+ grantAccess: input.grantAccess,
11720
+ grantName: input.grantName,
11721
+ grantReason: input.grantReason,
11722
+ host: input.upstreamUrl.hostname,
11723
+ method: input.request.method,
11724
+ path: input.upstreamUrl.pathname,
11725
+ provider: input.provider,
11726
+ status: input.upstream.status
11727
+ }),
11728
+ ...routingAttributes(input.request, input.upstreamUrl),
11729
+ "app.sandbox.egress.upstream_ok": input.upstream.ok
11730
+ },
11731
+ `Sandbox egress ${input.request.method} ${input.upstreamUrl.hostname}${displayedUpstreamPath(input.upstreamUrl)} -> ${input.upstream.status}`
11732
+ );
11733
+ }
11734
+ async function requestBodyBytes(request) {
11735
+ if (request.method === "GET" || request.method === "HEAD" || request.body === null) {
11736
+ return void 0;
11737
+ }
11738
+ return await request.arrayBuffer();
11739
+ }
11740
+ function isGrantSelectionBodyVisible(input) {
11741
+ return input.provider === "github" && input.upstreamUrl.hostname.toLowerCase() === "api.github.com" && input.upstreamUrl.pathname.toLowerCase().endsWith("/graphql");
11742
+ }
11743
+ function grantSelectionBodyText(input) {
11744
+ if (input.body === void 0) {
11745
+ return void 0;
11746
+ }
11747
+ if (input.body.byteLength > GRANT_SELECTION_BODY_TEXT_LIMIT_BYTES) {
11748
+ if (!input.operation && input.provider === "github" && input.request.method.toUpperCase() === "POST" && input.upstreamUrl.hostname.toLowerCase() === "api.github.com" && input.upstreamUrl.pathname.toLowerCase().endsWith("/graphql")) {
11749
+ throw new EgressPolicyDenied(
11750
+ "GitHub GraphQL request body is too large for Junior to inspect before issuing credentials."
11751
+ );
11752
+ }
11753
+ return void 0;
11754
+ }
11755
+ return new TextDecoder().decode(input.body);
11756
+ }
11757
+ function responseContentLength(upstream) {
11758
+ const raw = upstream.headers.get("content-length");
11759
+ if (!raw) {
11760
+ return void 0;
11761
+ }
11762
+ const parsed = Number(raw);
11763
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
11764
+ }
11765
+ async function responseTextWithinLimit(upstream, maxBytes) {
11766
+ const limit = Math.min(
11767
+ Math.max(0, Math.floor(maxBytes)),
11768
+ RESPONSE_BODY_TEXT_LIMIT_BYTES
11769
+ );
11770
+ if (limit <= 0) {
11771
+ return void 0;
11772
+ }
11773
+ const contentLength = responseContentLength(upstream);
11774
+ if (contentLength !== void 0 && contentLength > limit) {
11775
+ return void 0;
11776
+ }
11777
+ let clone;
11778
+ try {
11779
+ clone = upstream.clone();
11780
+ } catch {
11781
+ return void 0;
11782
+ }
11783
+ const body = clone.body;
11784
+ if (!body) {
11785
+ return "";
11786
+ }
11787
+ const reader = body.getReader();
11788
+ const chunks = [];
11789
+ let bytes = 0;
11790
+ try {
11791
+ while (true) {
11792
+ const { done, value } = await reader.read();
11793
+ if (done) {
11794
+ break;
11795
+ }
11796
+ if (!value) {
11797
+ continue;
11798
+ }
11799
+ bytes += value.byteLength;
11800
+ if (bytes > limit) {
11801
+ await reader.cancel().catch(() => void 0);
11802
+ return void 0;
11803
+ }
11804
+ chunks.push(value);
11805
+ }
11806
+ } catch {
11807
+ await reader.cancel().catch(() => void 0);
11808
+ return void 0;
11809
+ } finally {
11810
+ reader.releaseLock();
11811
+ }
11812
+ const combined = new Uint8Array(bytes);
11813
+ let offset = 0;
11814
+ for (const chunk of chunks) {
11815
+ combined.set(chunk, offset);
11816
+ offset += chunk.byteLength;
11817
+ }
11818
+ return new TextDecoder().decode(combined);
11819
+ }
11820
+ function requestHeaders(request, lease, upstreamHost, tracePropagation) {
11821
+ const headers = new Headers();
11822
+ const mayPropagateTrace = shouldPropagateSandboxEgressTrace(
11823
+ upstreamHost,
11824
+ tracePropagation
11825
+ );
11826
+ request.headers.forEach((value, key) => {
11827
+ const normalized = key.toLowerCase();
11828
+ if (HOP_BY_HOP_HEADERS.has(normalized) || PROXY_ONLY_HEADERS.has(normalized)) {
11829
+ return;
11830
+ }
11831
+ if (TRACE_PROPAGATION_HEADERS.has(normalized) && !mayPropagateTrace) {
11832
+ return;
11833
+ }
11834
+ headers.append(key, value);
11835
+ });
11836
+ for (const transform of lease.headerTransforms) {
11837
+ if (!matchesSandboxEgressDomain(upstreamHost, transform.domain)) {
11838
+ continue;
11839
+ }
11840
+ for (const [key, value] of Object.entries(transform.headers)) {
11841
+ if (TRACE_PROPAGATION_HEADERS.has(key.toLowerCase()) && !mayPropagateTrace) {
11842
+ continue;
11843
+ }
11844
+ headers.set(key, value);
11845
+ }
11846
+ }
11847
+ return headers;
11848
+ }
11849
+ function responseHeaders(upstream) {
11850
+ const headers = new Headers();
11851
+ upstream.headers.forEach((value, key) => {
11852
+ const normalized = key.toLowerCase();
11853
+ if (!HOP_BY_HOP_HEADERS.has(normalized) && !DECODED_RESPONSE_HEADERS.has(normalized)) {
11854
+ headers.append(key, value);
11855
+ }
11856
+ });
11857
+ return headers;
11858
+ }
11859
+ function leaseLogAttributes(input) {
11860
+ return {
11861
+ ...egressAttributes({
11862
+ egressId: input.egressId,
11863
+ grantAccess: input.lease.grant.access,
11864
+ grantName: input.lease.grant.name,
11865
+ grantReason: input.lease.grant.reason,
11866
+ host: input.upstreamUrl.hostname,
11867
+ method: input.request.method,
11868
+ path: input.upstreamUrl.pathname,
11869
+ provider: input.provider,
11870
+ status: input.status
11871
+ }),
11872
+ ...routingAttributes(input.request, input.upstreamUrl),
11873
+ ...input.upstream ? upstreamPermissionAttributes(input.provider, input.upstream) : {}
11874
+ };
11875
+ }
11876
+ async function recordSandboxAuthRequired(input) {
11877
+ await setSandboxEgressAuthRequiredSignal(input.credentialContext, {
11878
+ provider: input.provider,
11879
+ grant: input.grant,
11880
+ kind: input.kind ?? "auth_required",
11881
+ ...input.authorization ? { authorization: input.authorization } : {},
11882
+ message: input.message
11883
+ });
11884
+ }
11885
+ async function recordSandboxPermissionDenied(input) {
11886
+ await setSandboxEgressPermissionDeniedSignal(input.credentialContext, {
11887
+ provider: input.provider,
11888
+ grant: input.lease.grant,
11889
+ ...input.lease.account ? { account: input.lease.account } : {},
11890
+ message: input.message,
11891
+ source: "upstream",
11892
+ status: input.upstream.status,
11893
+ upstreamHost: input.upstreamUrl.hostname,
11894
+ upstreamPath: displayedUpstreamPath(input.upstreamUrl),
11895
+ ...input.provider === "github" ? githubPermissionHeaders(input.upstream) : {}
11896
+ });
11897
+ }
11898
+ async function executeCredentialedEgressRequest(input) {
11899
+ const {
11900
+ activeEgressId,
11901
+ credentialContext,
11902
+ deps,
11903
+ operation,
11904
+ provider,
11905
+ request,
11906
+ upstreamUrl
11907
+ } = input;
11908
+ const bodyForGrantSelection = isGrantSelectionBodyVisible({
11909
+ provider,
11910
+ upstreamUrl
11911
+ }) ? await requestBodyBytes(request) : void 0;
11912
+ let grantSelection;
11913
+ try {
11914
+ grantSelection = await selectSandboxEgressGrant({
11915
+ bodyText: grantSelectionBodyText({
11916
+ body: bodyForGrantSelection,
11917
+ ...operation ? { operation } : {},
11918
+ provider,
11919
+ request,
11920
+ upstreamUrl
11921
+ }),
11922
+ ...operation ? { operation } : {},
11923
+ provider,
11924
+ method: request.method,
11925
+ upstreamUrl
11926
+ });
11927
+ } catch (error) {
11928
+ if (isEgressPolicyDenied(error)) {
11929
+ logWarn(
11930
+ "sandbox_egress_policy_denied",
11931
+ {},
11932
+ {
11933
+ ...egressAttributes({
11934
+ egressId: activeEgressId,
11935
+ host: upstreamUrl.hostname,
11936
+ method: request.method,
11937
+ path: upstreamUrl.pathname,
11938
+ provider,
11939
+ status: 403
11940
+ }),
11941
+ ...routingAttributes(request, upstreamUrl)
11942
+ },
11943
+ error.message
11944
+ );
11945
+ return policyDeniedResponse(error);
11946
+ }
11947
+ throw error;
11948
+ }
11949
+ const issueCredentialLease = deps.issueCredentialLease ?? sandboxEgressCredentialLease;
11950
+ const clearCredentialLease = deps.clearCredentialLease ?? clearSandboxEgressCredentialLease;
11951
+ const recordAuthRequired = deps.recordAuthRequired ?? recordSandboxAuthRequired;
11952
+ const recordPermissionDenied = deps.recordPermissionDenied ?? recordSandboxPermissionDenied;
11953
+ let lease;
11954
+ try {
11955
+ lease = await issueCredentialLease(
11956
+ provider,
11957
+ grantSelection,
11958
+ credentialContext
11959
+ );
11960
+ } catch (error) {
11961
+ if (error instanceof SandboxEgressCredentialError) {
11962
+ await recordAuthRequired({
11963
+ credentialContext,
11964
+ provider: error.provider,
11965
+ grant: error.grant,
11966
+ kind: error.kind,
11967
+ authorization: error.authorization,
11968
+ message: error.message
11969
+ });
11970
+ const isAuthRequired = error.kind === "auth_required";
11971
+ logWarn(
11972
+ isAuthRequired ? "sandbox_egress_credential_needed" : "sandbox_egress_credential_unavailable",
11973
+ {},
11974
+ {
11975
+ ...egressAttributes({
11976
+ egressId: activeEgressId,
11977
+ grantAccess: error.grant.access,
11978
+ grantName: error.grant.name,
11979
+ grantReason: error.grant.reason,
11980
+ host: upstreamUrl.hostname,
11981
+ method: request.method,
11982
+ path: upstreamUrl.pathname,
11983
+ provider: error.provider,
11984
+ status: 401
11985
+ }),
11986
+ ...routingAttributes(request, upstreamUrl)
11987
+ },
11988
+ isAuthRequired ? "Sandbox egress grant needs user authorization before issuing a credential lease" : "Sandbox egress credential lease is unavailable for selected grant"
11989
+ );
11990
+ return authRequiredResponse({
11991
+ provider: error.provider,
11992
+ grant: error.grant,
11993
+ message: error.message
11994
+ });
11995
+ }
11996
+ throw error;
11997
+ }
11998
+ const attributes = (status, upstream2) => leaseLogAttributes({
11999
+ egressId: activeEgressId,
12000
+ lease,
12001
+ provider,
12002
+ request,
12003
+ status,
12004
+ ...upstream2 ? { upstream: upstream2 } : {},
12005
+ upstreamUrl
12006
+ });
12007
+ if (!hasSandboxEgressLeaseTransformForHost(lease, upstreamUrl.hostname)) {
12008
+ logWarn(
12009
+ "sandbox_egress_transform_missing",
12010
+ {},
12011
+ {
12012
+ ...attributes(403),
12013
+ "app.sandbox.egress.transform_domains": lease.headerTransforms.map(
12014
+ (transform) => transform.domain
12015
+ )
12016
+ },
12017
+ "Sandbox egress credential lease does not cover forwarded host"
12018
+ );
12019
+ return Response.json(
12020
+ { error: "Credential lease does not cover forwarded host" },
12021
+ { status: 403 }
12022
+ );
12023
+ }
12024
+ const fetchImpl = deps.fetch ?? fetch;
12025
+ const headers = requestHeaders(
12026
+ request,
12027
+ lease,
12028
+ upstreamUrl.hostname,
12029
+ deps.tracePropagation ?? {}
12030
+ );
12031
+ const body = bodyForGrantSelection ?? await requestBodyBytes(request);
12032
+ const intercepted = await deps.interceptHttp?.({
12033
+ provider,
12034
+ request: new Request(upstreamUrl, {
12035
+ method: request.method,
12036
+ headers,
12037
+ ...body !== void 0 ? { body } : {}
12038
+ }),
12039
+ upstreamUrl
12040
+ });
12041
+ if (intercepted) {
12042
+ return intercepted;
12043
+ }
12044
+ const upstream = await fetchImpl(upstreamUrl, {
12045
+ method: request.method,
12046
+ headers,
12047
+ ...body !== void 0 ? { body } : {},
12048
+ redirect: "manual"
12049
+ });
12050
+ try {
12051
+ const effects = await onPluginEgressResponse({
12052
+ provider,
12053
+ grant: lease.grant,
12054
+ method: request.method,
12055
+ ...operation ? { operation } : {},
12056
+ upstreamUrl,
12057
+ response: {
12058
+ headers: new Headers(upstream.headers),
12059
+ readText: async (maxBytes) => await responseTextWithinLimit(upstream, maxBytes),
12060
+ status: upstream.status
12061
+ }
12062
+ });
12063
+ if (effects.permissionDenied) {
12064
+ await recordPermissionDenied({
12065
+ credentialContext,
12066
+ provider,
12067
+ lease,
12068
+ message: effects.permissionDenied.message,
12069
+ upstream,
12070
+ upstreamUrl
12071
+ });
12072
+ logWarn(
12073
+ "sandbox_egress_upstream_permission_classified",
12074
+ {},
12075
+ {
12076
+ ...attributes(upstream.status, upstream)
12077
+ },
12078
+ "Sandbox egress plugin classified upstream response as permission denied"
12079
+ );
12080
+ }
12081
+ } catch (error) {
12082
+ if (!isEgressAuthRequired(error)) {
12083
+ throw error;
12084
+ }
12085
+ await clearCredentialLease(provider, lease.grant.name, credentialContext);
12086
+ await recordAuthRequired({
12087
+ credentialContext,
12088
+ provider,
12089
+ grant: lease.grant,
12090
+ authorization: error.authorization ?? lease.authorization,
12091
+ message: error.message
12092
+ });
12093
+ logWarn(
12094
+ "sandbox_egress_upstream_auth_required_classified",
12095
+ {},
12096
+ {
12097
+ ...attributes(upstream.status, upstream)
12098
+ },
12099
+ "Sandbox egress plugin classified upstream response as auth required"
12100
+ );
12101
+ await upstream.body?.cancel().catch(() => void 0);
12102
+ return authRequiredResponse({
12103
+ provider,
12104
+ grant: lease.grant,
12105
+ message: error.message
12106
+ });
12107
+ }
12108
+ logSandboxEgressUpstreamRequest({
12109
+ egressId: activeEgressId,
12110
+ grantAccess: lease.grant.access,
12111
+ grantName: lease.grant.name,
12112
+ grantReason: lease.grant.reason,
12113
+ provider,
12114
+ request,
12115
+ upstream,
12116
+ upstreamUrl
12117
+ });
12118
+ if (upstream.status >= 400) {
12119
+ logWarn(
12120
+ "sandbox_egress_upstream_error_response",
12121
+ {},
12122
+ {
12123
+ ...attributes(upstream.status, upstream),
12124
+ "error.type": `http_${upstream.status}`
12125
+ },
12126
+ `Sandbox egress upstream returned HTTP ${upstream.status}`
12127
+ );
12128
+ }
12129
+ if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS || upstream.status === UPSTREAM_PERMISSION_REJECTION_STATUS) {
12130
+ logWarn(
12131
+ "sandbox_egress_upstream_auth_rejected",
12132
+ {},
12133
+ {
12134
+ ...attributes(upstream.status, upstream),
12135
+ ...upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? {
12136
+ "app.sandbox.egress.www_authenticate": upstream.headers.get("www-authenticate") ?? void 0
12137
+ } : {}
12138
+ },
12139
+ upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? "Sandbox egress upstream auth rejected injected credential" : "Sandbox egress upstream permission denied"
12140
+ );
12141
+ if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS) {
12142
+ await clearCredentialLease(provider, lease.grant.name, credentialContext);
12143
+ await recordAuthRequired({
12144
+ credentialContext,
12145
+ provider,
12146
+ grant: lease.grant,
12147
+ authorization: lease.authorization,
12148
+ message: `Provider rejected the injected ${provider} credential.`
12149
+ });
12150
+ await upstream.body?.cancel().catch(() => void 0);
12151
+ return authRequiredResponse({
12152
+ provider,
12153
+ grant: lease.grant,
12154
+ message: `Provider rejected the injected ${provider} credential.
12155
+ `
12156
+ });
12157
+ } else {
12158
+ await clearCredentialLease(provider, lease.grant.name, credentialContext);
12159
+ await recordPermissionDenied({
12160
+ credentialContext,
12161
+ provider,
12162
+ lease,
12163
+ message: permissionDeniedMessage(provider, lease.grant),
12164
+ upstream,
12165
+ upstreamUrl
12166
+ });
12167
+ }
12168
+ }
12169
+ return new Response(upstream.body, {
12170
+ status: upstream.status,
12171
+ statusText: upstream.statusText,
12172
+ headers: responseHeaders(upstream)
12173
+ });
12174
+ }
12175
+
12176
+ // src/chat/egress/plugin.ts
12177
+ function credentialContextForPluginEgress(credentialContext) {
12178
+ return {
12179
+ credentials: credentialContext,
12180
+ contextId: randomUUID4(),
12181
+ egressId: `plugin-egress:${randomUUID4()}`,
12182
+ expiresAtMs: Date.now() + 30 * 60 * 1e3
12183
+ };
12184
+ }
12185
+ function createPluginEgress(deps) {
12186
+ const credentialContext = deps.credentialContext ? credentialContextForPluginEgress(deps.credentialContext) : void 0;
12187
+ return {
12188
+ async fetch(input) {
12189
+ const operation = input.operation.trim();
12190
+ if (!operation) {
12191
+ throw new Error("Plugin egress operation is required");
12192
+ }
12193
+ if (!credentialContext) {
12194
+ throw new PluginCredentialFailureError(
12195
+ input.provider,
12196
+ `Cannot issue ${input.provider} credentials without a credential context.`
12197
+ );
12198
+ }
12199
+ const upstreamUrl = new URL(input.request.url);
12200
+ if (upstreamUrl.protocol !== "https:") {
12201
+ throw new Error("Plugin egress requires HTTPS provider URLs");
12202
+ }
12203
+ const resolvedProvider = resolveSandboxEgressProviderForHost(
12204
+ upstreamUrl.hostname
12205
+ );
12206
+ if (resolvedProvider !== input.provider) {
12207
+ throw new Error(
12208
+ `Plugin egress provider "${input.provider}" does not own ${upstreamUrl.hostname}`
12209
+ );
12210
+ }
12211
+ return await executeCredentialedEgressRequest({
12212
+ activeEgressId: credentialContext.egressId,
12213
+ credentialContext,
12214
+ deps: {
12215
+ ...deps.fetch ? { fetch: deps.fetch } : {},
12216
+ recordAuthRequired: async (signal) => {
12217
+ await deps.pluginAuth.handleAuthRequired({
12218
+ ...signal.authorization ? { authorization: signal.authorization } : {},
12219
+ grant: signal.grant,
12220
+ kind: signal.kind ?? "auth_required",
12221
+ message: signal.message,
12222
+ provider: signal.provider
12223
+ });
12224
+ },
12225
+ recordPermissionDenied: async () => {
12226
+ },
12227
+ tracePropagation: {}
12228
+ },
12229
+ operation,
12230
+ provider: input.provider,
12231
+ request: input.request,
12232
+ upstreamUrl
12233
+ });
12234
+ }
12235
+ };
12236
+ }
12237
+
11202
12238
  // src/chat/respond.ts
11203
12239
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
11204
12240
  function sleep3(ms) {
@@ -11811,6 +12847,23 @@ async function generateAssistantReplyInPrivacyContext(messageText2, context, con
11811
12847
  userText: userInput,
11812
12848
  artifactState: context.artifactState,
11813
12849
  configuration: configurationValues,
12850
+ egress: createPluginEgress({
12851
+ credentialContext: context.credentialContext,
12852
+ pluginAuth: {
12853
+ async handleAuthRequired(signal) {
12854
+ await pluginAuth.maybeHandleAuthSignal({
12855
+ auth_required: {
12856
+ ...signal.authorization ? { authorization: signal.authorization } : {},
12857
+ createdAtMs: Date.now(),
12858
+ grant: signal.grant,
12859
+ kind: signal.kind,
12860
+ message: signal.message,
12861
+ provider: signal.provider
12862
+ }
12863
+ });
12864
+ }
12865
+ }
12866
+ }),
11814
12867
  mcpToolManager: turnMcpToolManager,
11815
12868
  sandbox,
11816
12869
  surface,
@@ -13162,7 +14215,6 @@ export {
13162
14215
  setConfigDefaults,
13163
14216
  getConfigDefaults,
13164
14217
  createUserTokenStore,
13165
- issueProviderCredentialLease,
13166
14218
  listThreadReplies,
13167
14219
  postSlackMessage,
13168
14220
  deleteSlackMessage,
@@ -13177,14 +14229,8 @@ export {
13177
14229
  startOAuthFlow,
13178
14230
  SANDBOX_EGRESS_PROXY_PATH,
13179
14231
  parseSandboxEgressCredentialToken,
13180
- setSandboxEgressCredentialLease,
13181
- getSandboxEgressCredentialLease,
13182
- clearSandboxEgressCredentialLease,
13183
- setSandboxEgressAuthRequiredSignal,
13184
- setSandboxEgressPermissionDeniedSignal,
13185
14232
  normalizeSandboxEgressTracePropagationDomains,
13186
14233
  shouldPropagateSandboxEgressTrace,
13187
- matchesSandboxEgressDomain,
13188
14234
  resolveSandboxEgressProviderForHost,
13189
14235
  shouldEmitDevAgentTrace,
13190
14236
  AuthorizationFlowDisabledError,
@@ -13215,6 +14261,8 @@ export {
13215
14261
  markTurnFailed,
13216
14262
  isRedundantReactionAckText,
13217
14263
  finalizeMcpAuthorization,
14264
+ resolvePluginOAuthAccount,
14265
+ executeCredentialedEgressRequest,
13218
14266
  generateAssistantReply,
13219
14267
  estimateTextTokens,
13220
14268
  getAgentContextCompactionTriggerTokens,