@sentry/junior 0.57.0 → 0.59.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.
- package/dist/app.js +1311 -1284
- package/dist/chat/agent-dispatch/store.d.ts +4 -2
- package/dist/chat/agent-dispatch/types.d.ts +0 -1
- package/dist/chat/conversation-privacy.d.ts +23 -0
- package/dist/chat/logging.d.ts +2 -0
- package/dist/chat/mcp/tool-manager.d.ts +18 -5
- package/dist/chat/mcp/tool-name.d.ts +2 -0
- package/dist/chat/pi/client.d.ts +2 -0
- package/dist/chat/pi/derived-state.d.ts +5 -0
- package/dist/chat/pi/traced-stream.d.ts +5 -1
- package/dist/chat/prompt.d.ts +3 -9
- package/dist/chat/respond-helpers.d.ts +5 -3
- package/dist/chat/respond.d.ts +1 -0
- package/dist/chat/runtime/conversation-message.d.ts +10 -0
- package/dist/chat/runtime/processing-reaction.d.ts +2 -4
- package/dist/chat/runtime/reply-executor.d.ts +13 -16
- package/dist/chat/runtime/slack-runtime.d.ts +19 -32
- package/dist/chat/runtime/thread-state.d.ts +1 -1
- package/dist/chat/runtime/turn-input.d.ts +29 -0
- package/dist/chat/runtime/turn-preparation.d.ts +4 -24
- package/dist/chat/runtime/turn.d.ts +2 -3
- package/dist/chat/sentry-links.d.ts +4 -0
- package/dist/chat/services/context-compaction.d.ts +3 -4
- package/dist/chat/services/pending-auth.d.ts +1 -1
- package/dist/chat/services/subscribed-reply-policy.d.ts +2 -13
- package/dist/chat/services/timeout-resume.d.ts +1 -2
- package/dist/chat/services/turn-session-record.d.ts +82 -0
- package/dist/chat/slack/assistant-thread/title.d.ts +4 -1
- package/dist/chat/state/artifacts.d.ts +1 -0
- package/dist/chat/state/conversation.d.ts +0 -1
- package/dist/chat/state/session-log.d.ts +117 -0
- package/dist/chat/state/ttl.d.ts +2 -0
- package/dist/chat/state/turn-session.d.ts +89 -0
- package/dist/chat/tools/advisor/tool.d.ts +2 -0
- package/dist/chat/tools/agent-tools.d.ts +2 -1
- package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -3
- package/dist/chat/tools/skill/search-mcp-tools.d.ts +15 -3
- package/dist/chat/tools/types.d.ts +0 -1
- package/dist/{chunk-AA5TIFN5.js → chunk-FKEKRBUB.js} +267 -735
- package/dist/{chunk-TTUY467K.js → chunk-H652GMDH.js} +30 -14
- package/dist/chunk-I4FDGMFI.js +950 -0
- package/dist/{chunk-D3G3YOU4.js → chunk-ITOW4DED.js} +1 -1
- package/dist/chunk-QDGD5WVN.js +708 -0
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +0 -1
- package/dist/cli/snapshot-warmup.js +5 -3
- package/dist/instrumentation.js +3 -0
- package/dist/reporting.d.ts +113 -0
- package/dist/reporting.js +390 -0
- package/package.json +25 -11
- package/dist/chat/services/turn-checkpoint.d.ts +0 -74
- package/dist/chat/state/pi-session-message-store.d.ts +0 -15
- package/dist/chat/state/turn-session-store.d.ts +0 -49
- package/dist/handlers/diagnostics-dashboard.d.ts +0 -2
- 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-
|
|
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
|
|
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(
|
|
336
|
+
messageAttributeMode === "metadata" ? params.messages.map(toGenAiMessageMetadata) : params.messages
|
|
161
337
|
);
|
|
162
338
|
const systemInstructionsAttribute = params.system ? serializeGenAiAttribute(
|
|
163
|
-
messageAttributeMode === "metadata" ? [
|
|
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
|
-
|
|
360
|
+
`${GEN_AI_OPERATION_CHAT} ${params.modelId}`,
|
|
179
361
|
"gen_ai.chat",
|
|
180
|
-
|
|
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 ? [
|
|
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
|
};
|