@sentry/junior 0.57.0 → 0.58.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 (54) hide show
  1. package/dist/app.js +1301 -1278
  2. package/dist/chat/agent-dispatch/types.d.ts +0 -1
  3. package/dist/chat/conversation-privacy.d.ts +23 -0
  4. package/dist/chat/logging.d.ts +2 -0
  5. package/dist/chat/mcp/tool-manager.d.ts +18 -5
  6. package/dist/chat/mcp/tool-name.d.ts +2 -0
  7. package/dist/chat/pi/client.d.ts +2 -0
  8. package/dist/chat/pi/derived-state.d.ts +5 -0
  9. package/dist/chat/pi/traced-stream.d.ts +5 -1
  10. package/dist/chat/prompt.d.ts +3 -9
  11. package/dist/chat/respond-helpers.d.ts +5 -3
  12. package/dist/chat/respond.d.ts +1 -0
  13. package/dist/chat/runtime/conversation-message.d.ts +10 -0
  14. package/dist/chat/runtime/processing-reaction.d.ts +2 -4
  15. package/dist/chat/runtime/reply-executor.d.ts +13 -16
  16. package/dist/chat/runtime/slack-runtime.d.ts +19 -32
  17. package/dist/chat/runtime/thread-state.d.ts +1 -1
  18. package/dist/chat/runtime/turn-input.d.ts +29 -0
  19. package/dist/chat/runtime/turn-preparation.d.ts +4 -24
  20. package/dist/chat/runtime/turn.d.ts +2 -3
  21. package/dist/chat/sentry-links.d.ts +4 -0
  22. package/dist/chat/services/context-compaction.d.ts +3 -4
  23. package/dist/chat/services/pending-auth.d.ts +1 -1
  24. package/dist/chat/services/subscribed-reply-policy.d.ts +2 -13
  25. package/dist/chat/services/timeout-resume.d.ts +1 -2
  26. package/dist/chat/services/turn-session-record.d.ts +82 -0
  27. package/dist/chat/slack/assistant-thread/title.d.ts +4 -1
  28. package/dist/chat/state/artifacts.d.ts +1 -0
  29. package/dist/chat/state/conversation.d.ts +0 -1
  30. package/dist/chat/state/session-log.d.ts +117 -0
  31. package/dist/chat/state/ttl.d.ts +2 -0
  32. package/dist/chat/state/turn-session.d.ts +89 -0
  33. package/dist/chat/tools/advisor/tool.d.ts +2 -0
  34. package/dist/chat/tools/agent-tools.d.ts +2 -1
  35. package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -3
  36. package/dist/chat/tools/skill/search-mcp-tools.d.ts +15 -3
  37. package/dist/chat/tools/types.d.ts +0 -1
  38. package/dist/{chunk-AA5TIFN5.js → chunk-FKEKRBUB.js} +267 -735
  39. package/dist/{chunk-TTUY467K.js → chunk-H652GMDH.js} +30 -14
  40. package/dist/chunk-I4FDGMFI.js +950 -0
  41. package/dist/{chunk-D3G3YOU4.js → chunk-ITOW4DED.js} +1 -1
  42. package/dist/chunk-QDGD5WVN.js +708 -0
  43. package/dist/cli/check.js +2 -2
  44. package/dist/cli/init.js +0 -1
  45. package/dist/cli/snapshot-warmup.js +5 -3
  46. package/dist/instrumentation.js +3 -0
  47. package/dist/reporting.d.ts +113 -0
  48. package/dist/reporting.js +390 -0
  49. package/package.json +25 -11
  50. package/dist/chat/services/turn-checkpoint.d.ts +0 -74
  51. package/dist/chat/state/pi-session-message-store.d.ts +0 -15
  52. package/dist/chat/state/turn-session-store.d.ts +0 -49
  53. package/dist/handlers/diagnostics-dashboard.d.ts +0 -2
  54. package/dist/handlers/diagnostics.d.ts +0 -2
@@ -1,13 +1,211 @@
1
1
  import {
2
2
  extractGenAiUsageAttributes,
3
- getPluginRuntimeDependencies,
4
- getPluginRuntimePostinstall,
5
3
  logException,
6
4
  logWarn,
7
5
  serializeGenAiAttribute,
8
6
  setSpanAttributes,
7
+ toOptionalString,
9
8
  withSpan
10
- } from "./chunk-TTUY467K.js";
9
+ } from "./chunk-H652GMDH.js";
10
+
11
+ // src/chat/slack/context.ts
12
+ function toTrimmedSlackString(value) {
13
+ const normalized = toOptionalString(value);
14
+ return normalized?.trim() || void 0;
15
+ }
16
+ function parseSlackThreadId(threadId) {
17
+ const normalizedThreadId = toTrimmedSlackString(threadId);
18
+ if (!normalizedThreadId) {
19
+ return void 0;
20
+ }
21
+ const parts = normalizedThreadId.split(":");
22
+ if (parts.length !== 3 || parts[0] !== "slack") {
23
+ return void 0;
24
+ }
25
+ const channelId = toTrimmedSlackString(parts[1]);
26
+ const threadTs = toTrimmedSlackString(parts[2]);
27
+ if (!channelId || !threadTs) {
28
+ return void 0;
29
+ }
30
+ return { channelId, threadTs };
31
+ }
32
+ function resolveSlackChannelIdFromThreadId(threadId) {
33
+ return parseSlackThreadId(threadId)?.channelId;
34
+ }
35
+ function resolveSlackChannelIdFromMessage(message) {
36
+ const messageChannelId = toTrimmedSlackString(
37
+ message.channelId
38
+ );
39
+ if (messageChannelId) {
40
+ return messageChannelId;
41
+ }
42
+ const raw = message.raw;
43
+ if (raw && typeof raw === "object") {
44
+ const rawChannel = toTrimmedSlackString(
45
+ raw.channel
46
+ );
47
+ if (rawChannel) {
48
+ return rawChannel;
49
+ }
50
+ }
51
+ const threadId = toTrimmedSlackString(
52
+ message.threadId
53
+ );
54
+ return resolveSlackChannelIdFromThreadId(threadId);
55
+ }
56
+
57
+ // src/chat/conversation-privacy.ts
58
+ var SAFE_METADATA_KEY_LIMIT = 20;
59
+ function conversationPrivacyFromChannelId(channelId) {
60
+ const normalized = channelId?.trim();
61
+ if (!normalized) return void 0;
62
+ return normalized.startsWith("C") ? "public" : "private";
63
+ }
64
+ function conversationPrivacyFromConversationId(conversationId) {
65
+ if (!conversationId?.trim()) return void 0;
66
+ const slackThread = parseSlackThreadId(conversationId);
67
+ if (slackThread) {
68
+ return conversationPrivacyFromChannelId(slackThread.channelId);
69
+ }
70
+ return "private";
71
+ }
72
+ function resolveConversationPrivacy(input) {
73
+ return conversationPrivacyFromChannelId(input.channelId) ?? conversationPrivacyFromConversationId(input.conversationId);
74
+ }
75
+ function canExposeConversationPayload(input) {
76
+ return resolveConversationPrivacy(input) === "public";
77
+ }
78
+ function contentMetadata(content) {
79
+ if (typeof content === "string") {
80
+ return [{ type: "text", chars: content.length }];
81
+ }
82
+ if (!Array.isArray(content)) {
83
+ return { type: typeof content };
84
+ }
85
+ return content.map((part) => {
86
+ if (!part || typeof part !== "object") {
87
+ return { type: typeof part };
88
+ }
89
+ const record = part;
90
+ const type = typeof record.type === "string" ? record.type : "unknown";
91
+ return {
92
+ type,
93
+ ...typeof record.text === "string" ? { chars: record.text.length } : {},
94
+ ...typeof record.mimeType === "string" ? { mimeType: record.mimeType } : {},
95
+ ...typeof record.mediaType === "string" ? { mediaType: record.mediaType } : {},
96
+ ...typeof record.data === "string" ? { dataChars: record.data.length } : {}
97
+ };
98
+ });
99
+ }
100
+ function toGenAiMessageMetadata(message) {
101
+ const record = message && typeof message === "object" ? message : {};
102
+ return {
103
+ role: record.role,
104
+ content: contentMetadata(record.content)
105
+ };
106
+ }
107
+ function toGenAiTextMetadata(text) {
108
+ return { type: "text", chars: text.length };
109
+ }
110
+ function payloadType(payload) {
111
+ return Array.isArray(payload) ? "array" : typeof payload;
112
+ }
113
+ function payloadKeys(payload) {
114
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
115
+ return void 0;
116
+ }
117
+ const keys = Object.keys(payload).slice(
118
+ 0,
119
+ SAFE_METADATA_KEY_LIMIT
120
+ );
121
+ return keys.length > 0 ? keys : void 0;
122
+ }
123
+ function serializedLength(payload) {
124
+ const serialized = typeof payload === "string" ? payload : JSON.stringify(payload);
125
+ return serialized?.length ?? 0;
126
+ }
127
+ function toGenAiPayloadMetadata(payload) {
128
+ const base = {
129
+ type: payloadType(payload),
130
+ chars: serializedLength(payload)
131
+ };
132
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
133
+ return base;
134
+ }
135
+ const keys = payloadKeys(payload);
136
+ return {
137
+ ...base,
138
+ ...keys ? { keys } : {}
139
+ };
140
+ }
141
+ function toGenAiPayloadTraceAttributes(prefix, payload) {
142
+ const attributes = {
143
+ [`${prefix}.type`]: payloadType(payload),
144
+ [`${prefix}.size_chars`]: serializedLength(payload)
145
+ };
146
+ const keys = payloadKeys(payload);
147
+ if (keys) {
148
+ attributes[`${prefix}.keys`] = keys;
149
+ }
150
+ return attributes;
151
+ }
152
+ function summarizeContent(content) {
153
+ if (typeof content === "string") {
154
+ return { chars: content.length, partTypes: ["text"] };
155
+ }
156
+ if (!Array.isArray(content)) {
157
+ return {
158
+ chars: serializedLength(content),
159
+ partTypes: [payloadType(content)]
160
+ };
161
+ }
162
+ let chars = 0;
163
+ const partTypes = /* @__PURE__ */ new Set();
164
+ for (const part of content) {
165
+ if (!part || typeof part !== "object") {
166
+ chars += serializedLength(part);
167
+ partTypes.add(payloadType(part));
168
+ continue;
169
+ }
170
+ const record = part;
171
+ const type = typeof record.type === "string" ? record.type : "unknown";
172
+ partTypes.add(type);
173
+ if (typeof record.text === "string") {
174
+ chars += record.text.length;
175
+ } else if (typeof record.data === "string") {
176
+ chars += record.data.length;
177
+ } else {
178
+ chars += serializedLength(part);
179
+ }
180
+ }
181
+ return { chars, partTypes: [...partTypes] };
182
+ }
183
+ function toGenAiMessagesTraceAttributes(prefix, messages) {
184
+ let contentChars = 0;
185
+ const roles = /* @__PURE__ */ new Set();
186
+ const partTypes = /* @__PURE__ */ new Set();
187
+ for (const message of messages) {
188
+ if (!message || typeof message !== "object") {
189
+ contentChars += serializedLength(message);
190
+ continue;
191
+ }
192
+ const record = message;
193
+ if (typeof record.role === "string") {
194
+ roles.add(record.role);
195
+ }
196
+ const summary = summarizeContent(record.content);
197
+ contentChars += summary.chars;
198
+ for (const partType of summary.partTypes) {
199
+ partTypes.add(partType);
200
+ }
201
+ }
202
+ return {
203
+ [`${prefix}.message_count`]: messages.length,
204
+ [`${prefix}.content_chars`]: contentChars,
205
+ ...roles.size > 0 ? { [`${prefix}.roles`]: [...roles] } : {},
206
+ ...partTypes.size > 0 ? { [`${prefix}.part_types`]: [...partTypes] } : {}
207
+ };
208
+ }
11
209
 
12
210
  // src/chat/state/adapter.ts
13
211
  import { createMemoryState } from "@chat-adapter/state-memory";
@@ -43,6 +241,8 @@ registerApiProvider({
43
241
  });
44
242
  var GATEWAY_PROVIDER = "vercel-ai-gateway";
45
243
  var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
244
+ var GEN_AI_SERVER_ADDRESS = "ai-gateway.vercel.sh";
245
+ var GEN_AI_SERVER_PORT = 443;
46
246
  var GEN_AI_OPERATION_CHAT = "chat";
47
247
  var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
48
248
  function getGatewayApiKey() {
@@ -54,35 +254,6 @@ function getPiGatewayApiKeyOverride() {
54
254
  function extractText(message) {
55
255
  return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
56
256
  }
57
- function contentMetadata(content) {
58
- if (typeof content === "string") {
59
- return [{ type: "text", chars: content.length }];
60
- }
61
- if (!Array.isArray(content)) {
62
- return { type: typeof content };
63
- }
64
- return content.map((part) => {
65
- if (!part || typeof part !== "object") {
66
- return { type: typeof part };
67
- }
68
- const record = part;
69
- const type = typeof record.type === "string" ? record.type : "unknown";
70
- return {
71
- type,
72
- ...typeof record.text === "string" ? { chars: record.text.length } : {},
73
- ...typeof record.mimeType === "string" ? { mimeType: record.mimeType } : {},
74
- ...typeof record.mediaType === "string" ? { mediaType: record.mediaType } : {},
75
- ...typeof record.data === "string" ? { dataChars: record.data.length } : {}
76
- };
77
- });
78
- }
79
- function toMessageMetadata(message) {
80
- const record = message;
81
- return {
82
- role: record.role,
83
- content: contentMetadata(record.content)
84
- };
85
- }
86
257
  function parseJsonCandidate(text) {
87
258
  const trimmed = text.trim();
88
259
  if (!trimmed) return void 0;
@@ -155,29 +326,40 @@ function resolveGatewayModel(modelId) {
155
326
  async function completeText(params) {
156
327
  const model = resolveGatewayModel(params.modelId);
157
328
  const apiKey = getPiGatewayApiKeyOverride();
158
- const messageAttributeMode = params.messageAttributeMode ?? "content";
329
+ const privacy = resolveConversationPrivacy({
330
+ channelId: typeof params.metadata?.channelId === "string" ? params.metadata.channelId : void 0,
331
+ conversationId: typeof params.metadata?.conversationId === "string" ? params.metadata.conversationId : typeof params.metadata?.threadId === "string" ? params.metadata.threadId : void 0
332
+ });
333
+ const effectivePrivacy = privacy ?? "private";
334
+ const messageAttributeMode = params.messageAttributeMode ?? (effectivePrivacy === "public" ? "content" : "metadata");
159
335
  const requestMessagesAttribute = serializeGenAiAttribute(
160
- messageAttributeMode === "metadata" ? params.messages.map(toMessageMetadata) : params.messages
336
+ messageAttributeMode === "metadata" ? params.messages.map(toGenAiMessageMetadata) : params.messages
161
337
  );
162
338
  const systemInstructionsAttribute = params.system ? serializeGenAiAttribute(
163
- messageAttributeMode === "metadata" ? [{ type: "text", chars: params.system.length }] : [{ type: "text", content: params.system }]
339
+ messageAttributeMode === "metadata" ? [toGenAiTextMetadata(params.system)] : [{ type: "text", content: params.system }]
164
340
  ) : void 0;
165
341
  const baseAttributes = {
166
342
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
167
343
  "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
168
344
  "gen_ai.request.model": params.modelId,
345
+ "gen_ai.output.type": "text",
346
+ "server.address": GEN_AI_SERVER_ADDRESS,
347
+ "server.port": GEN_AI_SERVER_PORT,
348
+ "app.conversation.privacy": effectivePrivacy,
169
349
  ...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
170
350
  };
171
351
  const startAttributes = {
172
352
  ...baseAttributes,
353
+ ...toGenAiMessagesTraceAttributes("app.ai.input", params.messages),
354
+ ...params.system ? { "app.ai.system_instructions.content_chars": params.system.length } : {},
173
355
  ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
174
356
  ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
175
357
  "app.ai.auth_mode": apiKey ? "oidc" : "api_key"
176
358
  };
177
359
  return withSpan(
178
- "ai.chat_completion",
360
+ `${GEN_AI_OPERATION_CHAT} ${params.modelId}`,
179
361
  "gen_ai.chat",
180
- { modelId: params.modelId },
362
+ logContextFromMetadata(params.modelId, params.metadata),
181
363
  async () => {
182
364
  const message = await completeSimple(
183
365
  model,
@@ -199,7 +381,7 @@ async function completeText(params) {
199
381
  messageAttributeMode === "metadata" ? [
200
382
  {
201
383
  role: "assistant",
202
- content: outputText ? [{ type: "text", chars: outputText.length }] : []
384
+ content: outputText ? [toGenAiTextMetadata(outputText)] : []
203
385
  }
204
386
  ] : [
205
387
  {
@@ -211,6 +393,12 @@ async function completeText(params) {
211
393
  const usageAttributes = extractGenAiUsageAttributes(message);
212
394
  const endAttributes = {
213
395
  ...baseAttributes,
396
+ ...toGenAiMessagesTraceAttributes("app.ai.output", [
397
+ {
398
+ role: "assistant",
399
+ content: outputText ? [{ type: "text", text: outputText }] : []
400
+ }
401
+ ]),
214
402
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
215
403
  ...usageAttributes,
216
404
  ...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {}
@@ -237,6 +425,19 @@ async function completeText(params) {
237
425
  startAttributes
238
426
  );
239
427
  }
428
+ function logContextFromMetadata(modelId, metadata) {
429
+ const conversationId = typeof metadata?.conversationId === "string" ? metadata.conversationId : typeof metadata?.threadId === "string" ? metadata.threadId : void 0;
430
+ const slackThreadId = typeof metadata?.threadId === "string" ? metadata.threadId : void 0;
431
+ const slackChannelId = typeof metadata?.channelId === "string" ? metadata.channelId : void 0;
432
+ const runId = typeof metadata?.runId === "string" ? metadata.runId : void 0;
433
+ return {
434
+ modelId,
435
+ ...conversationId ? { conversationId } : {},
436
+ ...slackThreadId ? { slackThreadId } : {},
437
+ ...slackChannelId ? { slackChannelId } : {},
438
+ ...runId ? { runId } : {}
439
+ };
440
+ }
240
441
  async function completeObject(params) {
241
442
  let text = "";
242
443
  try {
@@ -665,6 +866,18 @@ function createStateAdapter() {
665
866
  { activeLockMaxAgeMs }
666
867
  );
667
868
  }
869
+ function getOptionalRedisStateAdapter() {
870
+ getStateAdapter();
871
+ return redisStateAdapter;
872
+ }
873
+ async function getConnectedStateContext() {
874
+ const adapter = getStateAdapter();
875
+ await adapter.connect();
876
+ return {
877
+ redisStateAdapter: getOptionalRedisStateAdapter(),
878
+ stateAdapter: adapter
879
+ };
880
+ }
668
881
  function getStateAdapter() {
669
882
  if (!stateAdapter) {
670
883
  stateAdapter = createStateAdapter();
@@ -683,698 +896,28 @@ async function disconnectStateAdapter() {
683
896
  }
684
897
  }
685
898
 
686
- // src/chat/sandbox/runtime-dependency-snapshots.ts
687
- import { createHash } from "crypto";
688
- import { Sandbox } from "@vercel/sandbox";
689
-
690
- // src/chat/sandbox/noninteractive-command.ts
691
- var NON_INTERACTIVE_ENV = {
692
- CI: "1",
693
- TERM: "dumb",
694
- NO_COLOR: "1",
695
- PAGER: "cat",
696
- GIT_PAGER: "cat",
697
- GH_PROMPT_DISABLED: "1",
698
- GH_NO_UPDATE_NOTIFIER: "1",
699
- GH_NO_EXTENSION_UPDATE_NOTIFIER: "1",
700
- GH_SPINNER_DISABLED: "1",
701
- GIT_TERMINAL_PROMPT: "0",
702
- GCM_INTERACTIVE: "never",
703
- DEBIAN_FRONTEND: "noninteractive",
704
- // Git credential isolation: prevent git from sending its own auth so the
705
- // sandbox egress proxy's header transforms are the sole credential source.
706
- GIT_ASKPASS: "/bin/true",
707
- GIT_CONFIG_NOSYSTEM: "1",
708
- GIT_CONFIG_COUNT: "2",
709
- GIT_CONFIG_KEY_0: "credential.helper",
710
- GIT_CONFIG_VALUE_0: "",
711
- GIT_CONFIG_KEY_1: "http.emptyAuth",
712
- GIT_CONFIG_VALUE_1: "true"
713
- };
714
- function shellQuote(value) {
715
- return `'${value.replace(/'/g, "'\\''")}'`;
716
- }
717
- function buildEnvExports(options) {
718
- const lines = [];
719
- if (options.pathPrefix) {
720
- lines.push(`export PATH="${options.pathPrefix}"`);
721
- }
722
- for (const [key, value] of Object.entries(NON_INTERACTIVE_ENV)) {
723
- lines.push(`export ${key}=${shellQuote(value)}`);
724
- }
725
- for (const [key, value] of Object.entries(options.env ?? {})) {
726
- lines.push(`export ${key}=${shellQuote(value)}`);
727
- }
728
- return lines;
729
- }
730
- function toCommandScript(input) {
731
- return [shellQuote(input.cmd), ...(input.args ?? []).map(shellQuote)].join(
732
- " "
733
- );
734
- }
735
- function buildNonInteractiveShellScript(script, options = {}) {
736
- return [...buildEnvExports(options), "exec </dev/null", script].join(" && ");
737
- }
738
- function buildNonInteractiveCommand(input) {
739
- return {
740
- cmd: "bash",
741
- args: [
742
- input.login ? "-lc" : "-c",
743
- buildNonInteractiveShellScript(toCommandScript(input), {
744
- env: input.env,
745
- pathPrefix: input.pathPrefix
746
- })
747
- ]
748
- };
749
- }
750
- async function runNonInteractiveCommand(sandbox, input) {
751
- const command = {
752
- ...buildNonInteractiveCommand(input),
753
- ...input.cwd ? { cwd: input.cwd } : {},
754
- ...input.sudo !== void 0 ? { sudo: input.sudo } : {}
755
- };
756
- return await sandbox.runCommand(command);
757
- }
758
-
759
- // src/chat/sandbox/credentials.ts
760
- function getVercelSandboxCredentials() {
761
- const token = toOptionalTrimmed(process.env.VERCEL_TOKEN);
762
- const teamId = toOptionalTrimmed(process.env.VERCEL_TEAM_ID);
763
- const projectId = toOptionalTrimmed(process.env.VERCEL_PROJECT_ID);
764
- if (token && teamId && projectId) {
765
- return { token, teamId, projectId };
766
- }
767
- return void 0;
768
- }
769
-
770
- // src/chat/sandbox/workspace.ts
771
- function createSandboxInstance(sandbox) {
772
- return {
773
- sandboxId: sandbox.name,
774
- get sandboxEgressId() {
775
- return sandbox.currentSession().sessionId;
776
- },
777
- fs: sandbox.fs,
778
- extendTimeout(duration) {
779
- return sandbox.extendTimeout(duration);
780
- },
781
- mkDir(path) {
782
- return sandbox.mkDir(path);
783
- },
784
- readFileToBuffer(input) {
785
- return sandbox.readFileToBuffer(input);
786
- },
787
- runCommand(input) {
788
- return sandbox.runCommand(input);
789
- },
790
- async snapshot() {
791
- const snapshot = await sandbox.snapshot();
792
- return { snapshotId: snapshot.snapshotId };
793
- },
794
- stop() {
795
- return sandbox.stop();
796
- },
797
- update(params) {
798
- return sandbox.update(params);
799
- },
800
- writeFiles(files) {
801
- return sandbox.writeFiles(files);
802
- }
803
- };
804
- }
805
-
806
- // src/chat/sandbox/paths.ts
807
- function normalizeWorkspaceRoot(input) {
808
- const candidate = (input ?? "").trim();
809
- if (!candidate) {
810
- return "/vercel/sandbox";
811
- }
812
- const normalized = candidate.replace(/\/+$/, "");
813
- return normalized.startsWith("/") ? normalized : `/${normalized}`;
814
- }
815
- var SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(
816
- process.env.VERCEL_SANDBOX_WORKSPACE_DIR
817
- );
818
- var SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
819
- var SANDBOX_DATA_ROOT = `${SANDBOX_WORKSPACE_ROOT}/data`;
820
- function sandboxSkillDir(skillName) {
821
- return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
822
- }
823
- function sandboxSkillFile(skillName) {
824
- return `${sandboxSkillDir(skillName)}/SKILL.md`;
825
- }
826
-
827
- // src/chat/sandbox/runtime-dependency-snapshots.ts
828
- var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
829
- var SNAPSHOT_LOCK_PREFIX = "junior:sandbox_snapshot_lock";
830
- var SNAPSHOT_PROFILE_VERSION = 1;
831
- var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
832
- var SNAPSHOT_BUILD_LOCK_TTL_MS = 10 * 60 * 1e3;
833
- var SNAPSHOT_WAIT_FOR_LOCK_MS = SNAPSHOT_BUILD_LOCK_TTL_MS + 30 * 1e3;
834
- var DEFAULT_FLOATING_DEP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
835
- function sleep(ms) {
836
- return new Promise((resolve) => {
837
- setTimeout(resolve, ms);
838
- });
839
- }
840
- function profileCacheKey(profileHash) {
841
- return `${SNAPSHOT_CACHE_PREFIX}:${profileHash}`;
842
- }
843
- function profileLockKey(profileHash) {
844
- return `${SNAPSHOT_LOCK_PREFIX}:${profileHash}`;
845
- }
846
- function isExactNpmVersion(version) {
847
- return /^\d+\.\d+\.\d+(?:[-+][a-z0-9.]+)?$/i.test(version.trim());
848
- }
849
- function hasFloatingSelector(dep) {
850
- return dep.type === "npm" && !isExactNpmVersion(dep.version);
851
- }
852
- function parseFloatingDepMaxAgeMs() {
853
- const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
854
- if (!raw?.trim()) {
855
- return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
856
- }
857
- const parsed = Number.parseInt(raw, 10);
858
- if (!Number.isFinite(parsed) || parsed < 0) {
859
- return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
860
- }
861
- return parsed;
862
- }
863
- function buildDependencyProfile(runtime) {
864
- const dependencies = getPluginRuntimeDependencies();
865
- const postinstall = getPluginRuntimePostinstall();
866
- if (dependencies.length === 0 && postinstall.length === 0) {
867
- return null;
868
- }
869
- const rebuildEpoch = process.env.SANDBOX_SNAPSHOT_REBUILD_EPOCH?.trim() ?? "";
870
- const hasFloatingVersions = dependencies.some((dep) => hasFloatingSelector(dep)) || postinstall.length > 0;
871
- const hashInput = JSON.stringify({
872
- version: SNAPSHOT_PROFILE_VERSION,
873
- runtime,
874
- rebuildEpoch,
875
- dependencies,
876
- postinstall
877
- });
878
- const profileHash = createHash("sha256").update(hashInput).digest("hex");
879
- return {
880
- profileHash,
881
- dependencyCount: dependencies.length,
882
- hasFloatingVersions,
883
- dependencies,
884
- postinstall
885
- };
886
- }
887
- function getRuntimeDependencyProfileHash(runtime) {
888
- return buildDependencyProfile(runtime)?.profileHash;
889
- }
890
- function shouldRebuildCachedSnapshot(profile, cached) {
891
- if (!profile.hasFloatingVersions) {
892
- return false;
893
- }
894
- const maxAgeMs = parseFloatingDepMaxAgeMs();
895
- if (maxAgeMs === 0) {
896
- return true;
897
- }
898
- return Date.now() - cached.createdAtMs > maxAgeMs;
899
- }
900
- async function getCachedSnapshot(profileHash) {
901
- try {
902
- const state = getStateAdapter();
903
- await state.connect();
904
- const raw = await state.get(profileCacheKey(profileHash));
905
- if (typeof raw !== "string") {
906
- return null;
907
- }
908
- const parsed = JSON.parse(raw);
909
- if (!parsed || typeof parsed !== "object" || typeof parsed.profileHash !== "string" || typeof parsed.snapshotId !== "string" || typeof parsed.runtime !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.dependencyCount !== "number") {
910
- return null;
911
- }
912
- return parsed;
913
- } catch {
914
- return null;
915
- }
916
- }
917
- async function setCachedSnapshot(entry) {
918
- const state = getStateAdapter();
919
- await state.connect();
920
- await state.set(
921
- profileCacheKey(entry.profileHash),
922
- JSON.stringify(entry),
923
- SNAPSHOT_CACHE_TTL_MS
924
- );
925
- }
926
- async function withSnapshotSpan(name, op, attributes, callback) {
927
- return await withSpan(name, op, {}, callback, attributes);
928
- }
929
- async function runOrThrow(sandbox, params, label) {
930
- const result = await runNonInteractiveCommand(sandbox, params);
931
- if (result.exitCode === 0) {
932
- return;
933
- }
934
- const stderr = (await result.stderr()).trim();
935
- const stdout = (await result.stdout()).trim();
936
- const detail = stderr || stdout || "command failed";
937
- throw new Error(`${label} failed: ${detail}`);
938
- }
939
- async function tryRun(sandbox, params) {
940
- const result = await runNonInteractiveCommand(sandbox, params);
941
- if (result.exitCode === 0) {
942
- return { ok: true };
943
- }
944
- const stderr = (await result.stderr()).trim();
945
- const stdout = (await result.stdout()).trim();
946
- return { ok: false, detail: stderr || stdout || "command failed" };
947
- }
948
- async function installGhCliViaDnf(sandbox) {
949
- const direct = await tryRun(sandbox, {
950
- cmd: "dnf",
951
- args: ["install", "-y", "gh"],
952
- sudo: true
953
- });
954
- if (direct.ok) {
955
- return;
956
- }
957
- const dnf5Repo = await tryRun(sandbox, {
958
- cmd: "dnf",
959
- args: [
960
- "config-manager",
961
- "addrepo",
962
- "--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
963
- ],
964
- sudo: true
965
- });
966
- if (!dnf5Repo.ok) {
967
- await runOrThrow(
968
- sandbox,
969
- {
970
- cmd: "dnf",
971
- args: ["install", "-y", "dnf-command(config-manager)"],
972
- sudo: true
973
- },
974
- "dnf install dnf-command(config-manager)"
975
- );
976
- await runOrThrow(
977
- sandbox,
978
- {
979
- cmd: "dnf",
980
- args: [
981
- "config-manager",
982
- "--add-repo",
983
- "https://cli.github.com/packages/rpm/gh-cli.repo"
984
- ],
985
- sudo: true
986
- },
987
- "dnf config-manager --add-repo gh-cli.repo"
988
- );
989
- }
990
- await runOrThrow(
991
- sandbox,
992
- {
993
- cmd: "dnf",
994
- args: ["install", "-y", "gh", "--repo", "gh-cli"],
995
- sudo: true
996
- },
997
- "dnf install gh --repo gh-cli"
998
- );
999
- }
1000
- function runtimeDependencyFilePath(url, sha256) {
1001
- let urlBasename = "package.rpm";
1002
- try {
1003
- const pathname = new URL(url).pathname;
1004
- const segments = pathname.split("/").filter(Boolean);
1005
- const candidate = segments[segments.length - 1];
1006
- if (candidate) {
1007
- urlBasename = candidate;
1008
- }
1009
- } catch {
1010
- }
1011
- const sanitizedBasename = urlBasename.replace(/[^a-zA-Z0-9._-]/g, "_");
1012
- return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
1013
- }
1014
- async function installRuntimeDependencies(sandbox, deps) {
1015
- const systemDeps = deps.filter(
1016
- (dep) => dep.type === "system"
1017
- );
1018
- const npmPackages = deps.filter(
1019
- (dep) => dep.type === "npm"
1020
- ).map((dep) => `${dep.package}@${dep.version}`);
1021
- if (systemDeps.length > 0) {
1022
- await withSnapshotSpan(
1023
- "sandbox.snapshot.install_system",
1024
- "sandbox.snapshot.install.system",
1025
- {
1026
- "app.sandbox.snapshot.install.system_count": systemDeps.length
1027
- },
1028
- async () => {
1029
- for (const dep of systemDeps) {
1030
- if ("url" in dep) {
1031
- const rpmPath = runtimeDependencyFilePath(dep.url, dep.sha256);
1032
- await runOrThrow(
1033
- sandbox,
1034
- {
1035
- cmd: "curl",
1036
- args: ["-fsSL", dep.url, "-o", rpmPath]
1037
- },
1038
- `curl download ${dep.url}`
1039
- );
1040
- const checksumResult = await runNonInteractiveCommand(sandbox, {
1041
- cmd: "sha256sum",
1042
- args: [rpmPath]
1043
- });
1044
- const checksumStdout = (await checksumResult.stdout()).trim();
1045
- const checksumStderr = (await checksumResult.stderr()).trim();
1046
- if (checksumResult.exitCode !== 0) {
1047
- throw new Error(
1048
- `sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
1049
- );
1050
- }
1051
- const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
1052
- if (!actualChecksum) {
1053
- throw new Error("sha256sum produced empty output");
1054
- }
1055
- if (actualChecksum !== dep.sha256) {
1056
- throw new Error(
1057
- `checksum mismatch for ${dep.url}: expected ${dep.sha256}, got ${actualChecksum}`
1058
- );
1059
- }
1060
- await runOrThrow(
1061
- sandbox,
1062
- {
1063
- cmd: "dnf",
1064
- args: ["install", "-y", rpmPath],
1065
- sudo: true
1066
- },
1067
- `dnf install ${dep.url}`
1068
- );
1069
- continue;
1070
- }
1071
- if (dep.package === "gh") {
1072
- await installGhCliViaDnf(sandbox);
1073
- continue;
1074
- }
1075
- await runOrThrow(
1076
- sandbox,
1077
- {
1078
- cmd: "dnf",
1079
- args: ["install", "-y", dep.package],
1080
- sudo: true
1081
- },
1082
- `dnf install ${dep.package}`
1083
- );
1084
- }
1085
- }
1086
- );
1087
- }
1088
- if (npmPackages.length > 0) {
1089
- await withSnapshotSpan(
1090
- "sandbox.snapshot.install_npm",
1091
- "sandbox.snapshot.install.npm",
1092
- {
1093
- "app.sandbox.snapshot.install.npm_count": npmPackages.length
1094
- },
1095
- async () => {
1096
- await runOrThrow(
1097
- sandbox,
1098
- {
1099
- cmd: "npm",
1100
- args: [
1101
- "install",
1102
- "--global",
1103
- "--prefix",
1104
- `${SANDBOX_WORKSPACE_ROOT}/.junior`,
1105
- ...npmPackages
1106
- ]
1107
- },
1108
- "npm install"
1109
- );
1110
- }
1111
- );
1112
- }
1113
- }
1114
- async function runRuntimePostinstall(sandbox, commands) {
1115
- if (commands.length === 0) {
1116
- return;
1117
- }
1118
- await withSnapshotSpan(
1119
- "sandbox.snapshot.runtime_postinstall",
1120
- "sandbox.snapshot.runtime_postinstall",
1121
- {
1122
- "app.sandbox.snapshot.runtime_postinstall.count": commands.length
1123
- },
1124
- async () => {
1125
- for (const command of commands) {
1126
- const result = await runNonInteractiveCommand(sandbox, {
1127
- cmd: command.cmd,
1128
- args: command.args,
1129
- login: true,
1130
- pathPrefix: `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`,
1131
- ...command.sudo !== void 0 ? { sudo: command.sudo } : {}
1132
- });
1133
- if (result.exitCode === 0) {
1134
- continue;
1135
- }
1136
- const stderr = (await result.stderr()).trim();
1137
- const stdout = (await result.stdout()).trim();
1138
- const detail = stderr || stdout || "command failed";
1139
- throw new Error(`runtime-postinstall ${command.cmd} failed: ${detail}`);
1140
- }
1141
- }
1142
- );
1143
- }
1144
- async function createDependencySnapshot(profile, runtime, timeoutMs) {
1145
- return await withSnapshotSpan(
1146
- "sandbox.snapshot.build",
1147
- "sandbox.snapshot.build",
1148
- {
1149
- "app.sandbox.runtime": runtime,
1150
- "app.sandbox.snapshot.dependency_count": profile.dependencyCount
1151
- },
1152
- async () => {
1153
- const sandboxCredentials = getVercelSandboxCredentials();
1154
- const sandbox = createSandboxInstance(
1155
- await Sandbox.create({
1156
- timeout: timeoutMs,
1157
- runtime,
1158
- ...sandboxCredentials ?? {}
1159
- })
1160
- );
1161
- try {
1162
- await installRuntimeDependencies(sandbox, profile.dependencies);
1163
- await runRuntimePostinstall(sandbox, profile.postinstall);
1164
- return await withSnapshotSpan(
1165
- "sandbox.snapshot.capture",
1166
- "sandbox.snapshot.capture",
1167
- {
1168
- "app.sandbox.snapshot.dependency_count": profile.dependencyCount
1169
- },
1170
- async () => {
1171
- const snapshot = await sandbox.snapshot();
1172
- return snapshot.snapshotId;
1173
- }
1174
- );
1175
- } finally {
1176
- try {
1177
- await sandbox.stop();
1178
- } catch {
1179
- }
1180
- }
1181
- }
1182
- );
1183
- }
1184
- async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks) {
1185
- const state = getStateAdapter();
1186
- await state.connect();
1187
- const lockKey = profileLockKey(profileHash);
1188
- const tryAcquireLock = async () => await state.acquireLock(lockKey, SNAPSHOT_BUILD_LOCK_TTL_MS);
1189
- let lock = await tryAcquireLock();
1190
- if (lock) {
1191
- try {
1192
- const result = await callback();
1193
- return {
1194
- snapshotId: result.snapshotId,
1195
- source: result.source,
1196
- waitedForLock: false
1197
- };
1198
- } finally {
1199
- await state.releaseLock(lock);
1200
- }
1201
- }
1202
- return await withSnapshotSpan(
1203
- "sandbox.snapshot.lock_wait",
1204
- "sandbox.snapshot.lock_wait",
1205
- {
1206
- "app.sandbox.snapshot.profile_hash": profileHash
1207
- },
1208
- async () => {
1209
- await hooks?.onWaitingForLock?.();
1210
- const waitUntil = Date.now() + SNAPSHOT_WAIT_FOR_LOCK_MS;
1211
- while (Date.now() < waitUntil) {
1212
- const cached2 = await getCachedSnapshot(profileHash);
1213
- if (cached2?.snapshotId && canUseCachedSnapshot(cached2)) {
1214
- return {
1215
- snapshotId: cached2.snapshotId,
1216
- source: "wait_cache",
1217
- waitedForLock: true
1218
- };
1219
- }
1220
- lock = await tryAcquireLock();
1221
- if (lock) {
1222
- try {
1223
- const result = await callback();
1224
- return {
1225
- snapshotId: result.snapshotId,
1226
- source: result.source,
1227
- waitedForLock: true
1228
- };
1229
- } finally {
1230
- await state.releaseLock(lock);
1231
- }
1232
- }
1233
- await sleep(500);
1234
- }
1235
- const cached = await getCachedSnapshot(profileHash);
1236
- if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
1237
- return {
1238
- snapshotId: cached.snapshotId,
1239
- source: "wait_cache",
1240
- waitedForLock: true
1241
- };
1242
- }
1243
- throw new Error("Timed out waiting for snapshot build lock");
1244
- }
1245
- );
1246
- }
1247
- function toResolveOutcome(forceRebuild, source, waitedForLock) {
1248
- if (source === "built") {
1249
- return forceRebuild ? "forced_rebuild" : "rebuilt";
1250
- }
1251
- if (waitedForLock || source === "wait_cache") {
1252
- return "cache_hit_after_lock_wait";
1253
- }
1254
- return "cache_hit";
1255
- }
1256
- function getRebuildReason(params) {
1257
- if (params.forceRebuild) {
1258
- return params.staleSnapshotId ? "snapshot_missing" : "force_rebuild";
1259
- }
1260
- if (params.cached?.snapshotId && params.shouldRebuildCached) {
1261
- return "floating_stale";
1262
- }
1263
- if (!params.cached?.snapshotId) {
1264
- return "cache_miss";
1265
- }
1266
- return void 0;
1267
- }
1268
- async function resolveRuntimeDependencySnapshot(params) {
1269
- return await withSnapshotSpan(
1270
- "sandbox.snapshot.resolve",
1271
- "sandbox.snapshot.resolve",
1272
- {
1273
- "app.sandbox.runtime": params.runtime,
1274
- "app.sandbox.snapshot.force_rebuild": Boolean(params.forceRebuild)
1275
- },
1276
- async () => {
1277
- await params.onProgress?.("resolve_start");
1278
- const resolveStartedAtMs = Date.now();
1279
- const profile = buildDependencyProfile(params.runtime);
1280
- if (!profile) {
1281
- return {
1282
- dependencyCount: 0,
1283
- cacheHit: false,
1284
- resolveOutcome: "no_profile"
1285
- };
1286
- }
1287
- const cached = await getCachedSnapshot(profile.profileHash);
1288
- const cachedNeedsRebuild = Boolean(
1289
- cached?.snapshotId && shouldRebuildCachedSnapshot(profile, cached)
1290
- );
1291
- if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
1292
- await params.onProgress?.("cache_hit");
1293
- return {
1294
- snapshotId: cached.snapshotId,
1295
- profileHash: profile.profileHash,
1296
- dependencyCount: profile.dependencyCount,
1297
- cacheHit: true,
1298
- resolveOutcome: "cache_hit"
1299
- };
1300
- }
1301
- const rebuildReason = getRebuildReason({
1302
- forceRebuild: params.forceRebuild,
1303
- staleSnapshotId: params.staleSnapshotId,
1304
- cached,
1305
- shouldRebuildCached: cachedNeedsRebuild
1306
- });
1307
- const canUseCachedSnapshot = (candidate) => {
1308
- if (params.forceRebuild) {
1309
- if (params.staleSnapshotId) {
1310
- return candidate.snapshotId !== params.staleSnapshotId;
1311
- }
1312
- return candidate.createdAtMs > resolveStartedAtMs;
1313
- }
1314
- return !shouldRebuildCachedSnapshot(profile, candidate);
1315
- };
1316
- const lockResult = await withBuildLock(
1317
- profile.profileHash,
1318
- async () => {
1319
- const latest = await getCachedSnapshot(profile.profileHash);
1320
- if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
1321
- await params.onProgress?.("cache_hit");
1322
- return {
1323
- snapshotId: latest.snapshotId,
1324
- source: "callback_cache"
1325
- };
1326
- }
1327
- await params.onProgress?.("building_snapshot");
1328
- const nextSnapshotId = await createDependencySnapshot(
1329
- profile,
1330
- params.runtime,
1331
- params.timeoutMs
1332
- );
1333
- await setCachedSnapshot({
1334
- profileHash: profile.profileHash,
1335
- snapshotId: nextSnapshotId,
1336
- runtime: params.runtime,
1337
- createdAtMs: Date.now(),
1338
- dependencyCount: profile.dependencyCount
1339
- });
1340
- await params.onProgress?.("build_complete");
1341
- return { snapshotId: nextSnapshotId, source: "built" };
1342
- },
1343
- canUseCachedSnapshot,
1344
- {
1345
- onWaitingForLock: async () => {
1346
- await params.onProgress?.("waiting_for_lock");
1347
- }
1348
- }
1349
- );
1350
- return {
1351
- snapshotId: lockResult.snapshotId,
1352
- profileHash: profile.profileHash,
1353
- dependencyCount: profile.dependencyCount,
1354
- cacheHit: lockResult.source !== "built",
1355
- resolveOutcome: toResolveOutcome(
1356
- Boolean(params.forceRebuild),
1357
- lockResult.source,
1358
- lockResult.waitedForLock
1359
- ),
1360
- ...rebuildReason ? { rebuildReason } : {}
1361
- };
1362
- }
1363
- );
1364
- }
1365
- function isSnapshotMissingError(error) {
1366
- const searchable = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
1367
- return searchable.includes("snapshot") && (searchable.includes("not found") || searchable.includes("unknown") || searchable.includes("404"));
1368
- }
1369
-
1370
899
  export {
900
+ toOptionalTrimmed,
901
+ parseSlackThreadId,
902
+ resolveSlackChannelIdFromThreadId,
903
+ resolveSlackChannelIdFromMessage,
904
+ resolveConversationPrivacy,
905
+ canExposeConversationPayload,
906
+ toGenAiMessageMetadata,
907
+ toGenAiTextMetadata,
908
+ toGenAiPayloadMetadata,
909
+ toGenAiPayloadTraceAttributes,
910
+ toGenAiMessagesTraceAttributes,
1371
911
  GEN_AI_PROVIDER_NAME,
912
+ GEN_AI_SERVER_ADDRESS,
913
+ GEN_AI_SERVER_PORT,
1372
914
  MISSING_GATEWAY_CREDENTIALS_ERROR,
1373
915
  getGatewayApiKey,
1374
916
  getPiGatewayApiKeyOverride,
1375
917
  resolveGatewayModel,
1376
918
  completeText,
1377
919
  completeObject,
920
+ getChatConfig,
1378
921
  botConfig,
1379
922
  getSlackBotToken,
1380
923
  getSlackSigningSecret,
@@ -1382,18 +925,7 @@ export {
1382
925
  getSlackClientSecret,
1383
926
  getRuntimeMetadata,
1384
927
  ACTIVE_LOCK_TTL_MS,
928
+ getConnectedStateContext,
1385
929
  getStateAdapter,
1386
- disconnectStateAdapter,
1387
- SANDBOX_WORKSPACE_ROOT,
1388
- SANDBOX_SKILLS_ROOT,
1389
- SANDBOX_DATA_ROOT,
1390
- sandboxSkillDir,
1391
- sandboxSkillFile,
1392
- buildNonInteractiveShellScript,
1393
- runNonInteractiveCommand,
1394
- getVercelSandboxCredentials,
1395
- createSandboxInstance,
1396
- getRuntimeDependencyProfileHash,
1397
- resolveRuntimeDependencySnapshot,
1398
- isSnapshotMissingError
930
+ disconnectStateAdapter
1399
931
  };