@sentry/junior 0.204.0 → 0.206.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/{agent-CMyTD4DD.js → agent-C9yuiZUF.js} +277 -85
- package/dist/{agent-hooks-Bbcp9e33.js → agent-hooks-DNjlz2A-.js} +1084 -16
- package/dist/{agent-runner-DNb1A49M.js → agent-runner-E67lFrDG.js} +5 -5
- package/dist/api/schema.d.ts +85 -0
- package/dist/api/schema.js +2 -2
- package/dist/api.js +44 -511
- package/dist/{app-B1TOqBI8.d.ts → app-AxGmJHwK.d.ts} +8 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +56 -84
- package/dist/{auth-pause-state-DosEalIG.js → auth-pause-state-MVZ6xvEK.js} +6 -6
- package/dist/briefs-CvJFQvhJ.js +398 -0
- package/dist/{catalog-runtime-BReos-D4.js → catalog-runtime-TBR8n4e1.js} +1 -1
- package/dist/{checkpoint-CNrPyRJN.js → checkpoint-BY47jJCB.js} +7 -6
- package/dist/cli/chat.js +14 -12
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +1 -0
- package/dist/cli/main.js +5 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/run.d.ts +2 -1
- package/dist/cli/run.js +2 -1
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.js +3 -3
- package/dist/{config-nGCW1OTG.js → client-ceE7NtvC.js} +4 -367
- package/dist/config-CIE83sR2.js +386 -0
- package/dist/{schema-Rn8-XEaN.js → conversation-AA4R1VUn.js} +580 -558
- package/dist/{conversation-privacy-B4voFRSm.js → conversation-privacy-Auyq26XQ.js} +1 -1
- package/dist/{credential-sync-kXr1hvO1.js → credential-sync-BO33aNQd.js} +22 -20
- package/dist/{execution-stats-DWfSihcH.js → execution-stats-SiS2FLcv.js} +4 -4
- package/dist/{executor-DcMSlI5c.js → executor-BfbYPW63.js} +31 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-Drn_LXlM.js → job-queue-CyTc24Es.js} +1 -1
- package/dist/{logging-BzAk1S_s.js → logging-D37bayDc.js} +1 -1
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-JiftokvA.js → oauth-callback-server-BtfOeHTZ.js} +2 -2
- package/dist/{pending-auth-CWmSdZVg.js → pending-auth-BPUzcs7n.js} +2 -2
- package/dist/{profile-CrT5nXbw.js → profile-CSixooTi.js} +4 -3
- package/dist/prompt-3gqNY1Ka.js +552 -0
- package/dist/{registry-DH1xUo2N.js → registry-DiR0-xRg.js} +1 -1
- package/dist/{resolve-CZHnfxaS.js → resolve-Bn5q__9b.js} +2 -2
- package/dist/{runner-CZV2sC46.js → runner-DFx3dIJH.js} +9 -9
- package/dist/{sandbox-egress-signals-bHZfmhbd.js → sandbox-egress-signals-bwvjihxA.js} +1 -1
- package/dist/schema-8GXFfclK.js +559 -0
- package/dist/{skills-CIcDQSgM.js → skills-BhM5qyCF.js} +2 -2
- package/dist/{spawn-DdkUFRmq.js → spawn-Dk0PqdH6.js} +2 -2
- package/dist/{stats-DTUbAb88.js → stats-DKRP4rsY.js} +26 -12
- package/dist/{task-queue-CJIR4RF7.js → task-queue-CdpTvu4R.js} +3 -3
- package/dist/{task-runner-D-O3A-Cp.js → task-runner-alpjVSxQ.js} +10 -10
- package/dist/{thread-state-CRc9jTJS.js → thread-state-CRK7aA41.js} +5 -5
- package/dist/{turn-execution-BkinIdJF.js → turn-execution-XtyzIHog.js} +3 -3
- package/dist/{turn-lifecycle-DQV19VTW.js → turn-lifecycle-VbwZLfBv.js} +1 -1
- package/dist/{usage-D3EbSO7w.js → usage-D4A48Bdl.js} +4 -125
- package/dist/{validation-C2CR-OX3.js → validation-jn_46o4U.js} +1 -1
- package/dist/{version-DxcWdoKa.js → version-DcfJxB4z.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/web-authorization-CsfLSfEs.js +38 -0
- package/dist/{web-input-3EPfpf55.js → web-input-BPp_sc7t.js} +6 -6
- package/dist/{work-fGofI7Op.js → work-Di-ubSGg.js} +9 -9
- package/dist/{worker-CsOLq-mR.js → worker-Cu_D5Mr8.js} +5 -4
- package/migrations/0040_conversation_briefs.sql +17 -0
- package/migrations/meta/0040_snapshot.json +3516 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/dist/history-CqTuSAdI.js +0 -441
- package/dist/usage-schema-CKaDkH_j.js +0 -20
- package/dist/web-authorization-RMBxAR_t.js +0 -76
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
|
|
2
|
+
import { o as resolveGatewayModel, w as toOptionalTrimmed } from "./client-ceE7NtvC.js";
|
|
3
|
+
import { f as logWarn } from "./logging-D37bayDc.js";
|
|
4
|
+
import { c as TURN_REASONING_LEVELS, l as parseTurnReasoningLevel, o as modelProfileSchema } from "./model-profile-CZmVoGdX.js";
|
|
5
|
+
import { getModel } from "@earendil-works/pi-ai/compat";
|
|
6
|
+
//#region src/chat/slack/emoji.ts
|
|
7
|
+
const SLACK_EMOJI_NAME_RE = /^(?:[a-z0-9_+-]+)(?:::(?:skin-tone-[2-6]))?$/;
|
|
8
|
+
function normalizeSlackEmojiName(value) {
|
|
9
|
+
const trimmed = value.trim().toLowerCase();
|
|
10
|
+
if (!trimmed) return null;
|
|
11
|
+
const normalized = trimmed.startsWith(":") && trimmed.endsWith(":") ? trimmed.slice(1, -1) : trimmed;
|
|
12
|
+
return SLACK_EMOJI_NAME_RE.test(normalized) ? normalized : null;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/chat/config.ts
|
|
16
|
+
var config_exports = /* @__PURE__ */ __exportAll({
|
|
17
|
+
CONVERSATION_WORK_SOFT_YIELD_BUFFER_SECONDS: () => 40,
|
|
18
|
+
DEFAULT_SQL_STATEMENT_TIMEOUT_MS: () => DEFAULT_SQL_STATEMENT_TIMEOUT_MS,
|
|
19
|
+
FUNCTION_TIMEOUT_BUFFER_SECONDS: () => 20,
|
|
20
|
+
botConfig: () => botConfig,
|
|
21
|
+
configureFunctionMaxDurationSeconds: () => configureFunctionMaxDurationSeconds,
|
|
22
|
+
getChatConfig: () => getChatConfig,
|
|
23
|
+
getRuntimeMetadata: () => getRuntimeMetadata,
|
|
24
|
+
getSlackBotToken: () => getSlackBotToken,
|
|
25
|
+
getSlackClientId: () => getSlackClientId,
|
|
26
|
+
getSlackClientSecret: () => getSlackClientSecret,
|
|
27
|
+
getSlackReactionConfig: () => getSlackReactionConfig,
|
|
28
|
+
getSlackSigningSecret: () => getSlackSigningSecret,
|
|
29
|
+
readChatConfig: () => readChatConfig,
|
|
30
|
+
setBotModelConfig: () => setBotModelConfig,
|
|
31
|
+
setProfiles: () => setProfiles,
|
|
32
|
+
setSlackReactionConfig: () => setSlackReactionConfig
|
|
33
|
+
});
|
|
34
|
+
const MIN_AGENT_TURN_TIMEOUT_MS = 1e4;
|
|
35
|
+
const DEFAULT_AGENT_TURN_TIMEOUT_MS = 72e4;
|
|
36
|
+
const MAX_SLICES_PER_TURN = 100;
|
|
37
|
+
/**
|
|
38
|
+
* Max tool calls for one turn, including later execution slices of that turn.
|
|
39
|
+
* Chosen from production dispatch traces: healthy runs cluster well under 50;
|
|
40
|
+
* runaway CI-watch loops land in the hundreds. Raised from 150 after that cap
|
|
41
|
+
* fired on legitimate long-form human turns, not just runaway automation.
|
|
42
|
+
*/
|
|
43
|
+
const MAX_TOOL_CALLS_PER_TURN = 250;
|
|
44
|
+
/**
|
|
45
|
+
* Max consecutive automated turns before event wakes stop until a user message.
|
|
46
|
+
* Resource-event CI watches and event-task loops are the common runaway paths.
|
|
47
|
+
* Sentry telemetry has no direct instrumentation for how long real automated
|
|
48
|
+
* chains run (only failure paths on the notice are logged, and those show zero
|
|
49
|
+
* hits in the last 30d), so this is sized qualitatively: it stays well under
|
|
50
|
+
* the runaway CI-watch-loop range noted for maxToolCallsPerTurn (hundreds of
|
|
51
|
+
* tool calls) while giving multi-phase event-task/scheduled-task work enough
|
|
52
|
+
* headroom to avoid pausing on legitimate longer-running automation.
|
|
53
|
+
*/
|
|
54
|
+
const MAX_CONSECUTIVE_AUTOMATED_TURNS = 25;
|
|
55
|
+
const DEFAULT_FUNCTION_MAX_DURATION_SECONDS = 300;
|
|
56
|
+
const DEFAULT_SLACK_SLASH_COMMAND = "/jr";
|
|
57
|
+
const DEFAULT_PROCESSING_REACTION_EMOJI = "eyes";
|
|
58
|
+
const DEFAULT_COMPLETED_REACTION_EMOJI = "white_check_mark";
|
|
59
|
+
const DEFAULT_MODEL_CONTEXT_WINDOW_TOKENS = 4e5;
|
|
60
|
+
const DEFAULT_SQL_STATEMENT_TIMEOUT_MS = 3e4;
|
|
61
|
+
const DEFAULT_ASSISTANT_LOADING_MESSAGES = [
|
|
62
|
+
"Consulting the orb",
|
|
63
|
+
"Bribing the gremlins",
|
|
64
|
+
"Shuffling the papers dramatically",
|
|
65
|
+
"Summoning the right stack trace",
|
|
66
|
+
"Negotiating with the mutex",
|
|
67
|
+
"Poking the internet with a stick",
|
|
68
|
+
"Asking the docs nicely",
|
|
69
|
+
"Searching for the least cursed path",
|
|
70
|
+
"Pretending this was obvious",
|
|
71
|
+
"Waking up the test suite",
|
|
72
|
+
"Untangling the spaghetti carefully",
|
|
73
|
+
"Rattling the command line"
|
|
74
|
+
];
|
|
75
|
+
function parseAgentTurnTimeoutMs(rawValue, maxTimeoutMs) {
|
|
76
|
+
const value = Number.parseInt(rawValue ?? "", 10);
|
|
77
|
+
if (Number.isNaN(value)) return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(DEFAULT_AGENT_TURN_TIMEOUT_MS, maxTimeoutMs));
|
|
78
|
+
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(value, maxTimeoutMs));
|
|
79
|
+
}
|
|
80
|
+
function resolveFunctionMaxDurationSeconds(functionMaxDurationSeconds) {
|
|
81
|
+
return functionMaxDurationSeconds ?? DEFAULT_FUNCTION_MAX_DURATION_SECONDS;
|
|
82
|
+
}
|
|
83
|
+
function resolveConversationWorkSoftYieldAfterMs(functionMaxDurationSeconds) {
|
|
84
|
+
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, (functionMaxDurationSeconds - 40) * 1e3);
|
|
85
|
+
}
|
|
86
|
+
function resolveMaxTurnTimeoutMs(functionMaxDurationSeconds) {
|
|
87
|
+
const budgetSeconds = functionMaxDurationSeconds - 20;
|
|
88
|
+
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, budgetSeconds * 1e3);
|
|
89
|
+
}
|
|
90
|
+
function parseLoadingMessages(rawValue) {
|
|
91
|
+
const trimmed = rawValue?.trim();
|
|
92
|
+
if (!trimmed) return [...DEFAULT_ASSISTANT_LOADING_MESSAGES];
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(trimmed);
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error("JUNIOR_LOADING_MESSAGES must be a JSON array of strings");
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(parsed)) throw new Error("JUNIOR_LOADING_MESSAGES must be a JSON array of strings");
|
|
100
|
+
return parsed.map((value, index) => {
|
|
101
|
+
if (typeof value !== "string") throw new Error(`JUNIOR_LOADING_MESSAGES[${index}] must be a string`);
|
|
102
|
+
return value.trim();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function parseOptionalPositiveInteger(envName, rawValue) {
|
|
106
|
+
const trimmed = toOptionalTrimmed(rawValue);
|
|
107
|
+
if (trimmed === void 0) return;
|
|
108
|
+
const value = Number.parseInt(trimmed, 10);
|
|
109
|
+
if (!Number.isSafeInteger(value) || value <= 0 || String(value) !== trimmed) throw new Error(`${envName} must be a positive integer`);
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function parseSlashCommand(rawValue) {
|
|
113
|
+
const command = toOptionalTrimmed(rawValue) ?? DEFAULT_SLACK_SLASH_COMMAND;
|
|
114
|
+
if (!command.startsWith("/") || /\s/.test(command)) throw new Error("JUNIOR_SLASH_COMMAND must start with / and contain no whitespace");
|
|
115
|
+
return command;
|
|
116
|
+
}
|
|
117
|
+
function parseConversationWorkEnabled(rawValue) {
|
|
118
|
+
const value = toOptionalTrimmed(rawValue)?.toLowerCase();
|
|
119
|
+
if (value === void 0 || value === "true") return true;
|
|
120
|
+
if (value === "false") return false;
|
|
121
|
+
throw new Error("JUNIOR_CONVERSATION_WORK_ENABLED must be true or false");
|
|
122
|
+
}
|
|
123
|
+
function parseCrossActorMidRunMode(rawValue) {
|
|
124
|
+
const value = toOptionalTrimmed(rawValue) ?? "follow_up";
|
|
125
|
+
if (value === "follow_up" || value === "steer") return value;
|
|
126
|
+
throw new Error("JUNIOR_CROSS_ACTOR_MID_RUN_MODE must be follow_up or steer");
|
|
127
|
+
}
|
|
128
|
+
const DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "xai/grok-4.5").id;
|
|
129
|
+
const DEFAULT_FAST_MODEL_ID = getModel("vercel-ai-gateway", "anthropic/claude-haiku-4.5").id;
|
|
130
|
+
const DEFAULT_GUARDIAN_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.6-luna").id;
|
|
131
|
+
const DEFAULT_HANDOFF_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.6-sol").id;
|
|
132
|
+
const DEFAULT_WEB_SEARCH_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
|
|
133
|
+
const DEFAULT_EMBEDDING_MODEL_ID = "openai/text-embedding-3-small";
|
|
134
|
+
const DEFAULT_IMAGE_GENERATION_MODEL_ID = "google/gemini-3-pro-image";
|
|
135
|
+
function validateGatewayModelId(raw) {
|
|
136
|
+
const trimmed = toOptionalTrimmed(raw);
|
|
137
|
+
if (trimmed === void 0) return void 0;
|
|
138
|
+
resolveGatewayModel(trimmed);
|
|
139
|
+
return trimmed;
|
|
140
|
+
}
|
|
141
|
+
function validateEmbeddingModelId(raw) {
|
|
142
|
+
return toOptionalTrimmed(raw);
|
|
143
|
+
}
|
|
144
|
+
function requireModelId(configName, raw, validate) {
|
|
145
|
+
const modelId = validate(raw);
|
|
146
|
+
if (!modelId) throw new Error(`${configName} must not be empty`);
|
|
147
|
+
return modelId;
|
|
148
|
+
}
|
|
149
|
+
const DEFAULT_STANDARD_PROFILE_DESCRIPTION = "Use for default assistant work: lookups, explanations, ordinary tool use, short answers, and light investigation of one source. Avoid for implementation, debugging, multi-file changes, architecture decisions, or research across several systems.";
|
|
150
|
+
const DEFAULT_HANDOFF_PROFILE_DESCRIPTION = "Use for coding and difficult multi-step work: implementation, debugging, root-cause analysis, broad refactors, multi-file changes, architecture decisions, and research across several systems. Avoid for simple lookups, short answers, single-file reads, or ordinary tool use that the default profile can finish.";
|
|
151
|
+
function parseOptionalProfileDescription(rawDescription, configName, profile) {
|
|
152
|
+
if (rawDescription === void 0) return;
|
|
153
|
+
if (typeof rawDescription !== "string") throw new Error(`${configName}.${profile}.description must be a string when set`);
|
|
154
|
+
const description = toOptionalTrimmed(rawDescription);
|
|
155
|
+
if (!description) throw new Error(`${configName}.${profile}.description must not be empty when set`);
|
|
156
|
+
return description;
|
|
157
|
+
}
|
|
158
|
+
function parseOptionalProfileReasoningLevel(rawReasoningLevel, configName, profile) {
|
|
159
|
+
if (rawReasoningLevel === void 0) return;
|
|
160
|
+
try {
|
|
161
|
+
return parseTurnReasoningLevel(rawReasoningLevel);
|
|
162
|
+
} catch {
|
|
163
|
+
throw new Error(`${configName}.${profile}.reasoningLevel must be one of ${TURN_REASONING_LEVELS.join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function parseProfileConfig(rawProfile, configName, profile) {
|
|
167
|
+
if (typeof rawProfile === "string") {
|
|
168
|
+
const modelId = validateGatewayModelId(rawProfile);
|
|
169
|
+
if (!modelId) throw new Error(`${configName}.${profile} must not be empty`);
|
|
170
|
+
return { modelId };
|
|
171
|
+
}
|
|
172
|
+
if (!rawProfile || typeof rawProfile !== "object" || Array.isArray(rawProfile)) throw new Error(`${configName}.${profile} must be a model id string or an object with modelId`);
|
|
173
|
+
const rawModelId = rawProfile.modelId;
|
|
174
|
+
if (typeof rawModelId !== "string") throw new Error(`${configName}.${profile}.modelId must be a model id string`);
|
|
175
|
+
const modelId = validateGatewayModelId(rawModelId);
|
|
176
|
+
if (!modelId) throw new Error(`${configName}.${profile}.modelId must not be empty`);
|
|
177
|
+
const description = parseOptionalProfileDescription(rawProfile.description, configName, profile);
|
|
178
|
+
const reasoningLevel = parseOptionalProfileReasoningLevel(rawProfile.reasoningLevel, configName, profile);
|
|
179
|
+
return {
|
|
180
|
+
modelId,
|
|
181
|
+
...description ? { description } : void 0,
|
|
182
|
+
...reasoningLevel ? { reasoningLevel } : void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function parseProfileMap(rawProfiles, configName) {
|
|
186
|
+
if (!rawProfiles || typeof rawProfiles !== "object" || Array.isArray(rawProfiles)) throw new Error(`${configName} must be ${configName === "AI_MODEL_PROFILES" ? "a JSON object" : "an object"}`);
|
|
187
|
+
const profiles = {};
|
|
188
|
+
for (const [profile, rawProfile] of Object.entries(rawProfiles)) {
|
|
189
|
+
if (!modelProfileSchema.safeParse(profile).success) throw new Error(`${configName} profile "${profile}" must match ^[a-z][a-z0-9_-]*$`);
|
|
190
|
+
profiles[profile] = parseProfileConfig(rawProfile, configName, profile);
|
|
191
|
+
}
|
|
192
|
+
return profiles;
|
|
193
|
+
}
|
|
194
|
+
function parseProfiles(rawValue, standardModelId, handoffModelId) {
|
|
195
|
+
const profiles = {
|
|
196
|
+
standard: {
|
|
197
|
+
modelId: standardModelId,
|
|
198
|
+
description: DEFAULT_STANDARD_PROFILE_DESCRIPTION
|
|
199
|
+
},
|
|
200
|
+
handoff: {
|
|
201
|
+
modelId: handoffModelId,
|
|
202
|
+
description: DEFAULT_HANDOFF_PROFILE_DESCRIPTION,
|
|
203
|
+
reasoningLevel: "high"
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const trimmed = toOptionalTrimmed(rawValue);
|
|
207
|
+
if (trimmed === void 0) return profiles;
|
|
208
|
+
let parsed;
|
|
209
|
+
try {
|
|
210
|
+
parsed = JSON.parse(trimmed);
|
|
211
|
+
} catch {
|
|
212
|
+
throw new Error("AI_MODEL_PROFILES must be a JSON object");
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
...profiles,
|
|
216
|
+
...parseProfileMap(parsed, "AI_MODEL_PROFILES")
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function parseReactionEmoji(envName, rawValue, defaultEmoji) {
|
|
220
|
+
const trimmed = toOptionalTrimmed(rawValue);
|
|
221
|
+
if (trimmed === void 0) return defaultEmoji;
|
|
222
|
+
const normalized = normalizeSlackEmojiName(trimmed);
|
|
223
|
+
if (!normalized) throw new Error(`${envName} must be a valid Slack emoji name (for example "eyes" or ":white_check_mark:")`);
|
|
224
|
+
return normalized;
|
|
225
|
+
}
|
|
226
|
+
function warnDeprecatedProfileEnv(env) {
|
|
227
|
+
for (const envName of [
|
|
228
|
+
"AI_MODEL",
|
|
229
|
+
"AI_HANDOFF_MODEL",
|
|
230
|
+
"AI_MODEL_PROFILES"
|
|
231
|
+
]) if (toOptionalTrimmed(env[envName]) !== void 0) logWarn("config.profile_env.deprecated", {
|
|
232
|
+
"app.config.env_name": envName,
|
|
233
|
+
"app.config.replacement": "createApp({ defaultProfile, profiles })"
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function readBotConfig(env, functionMaxDurationSeconds) {
|
|
237
|
+
warnDeprecatedProfileEnv(env);
|
|
238
|
+
const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(functionMaxDurationSeconds);
|
|
239
|
+
const modelId = validateGatewayModelId(env.AI_MODEL) ?? DEFAULT_MODEL_ID;
|
|
240
|
+
const reasoningLevel = toOptionalTrimmed(env.AI_REASONING_LEVEL);
|
|
241
|
+
const fastModelId = validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID;
|
|
242
|
+
const guardianModelId = validateGatewayModelId(env.AI_GUARDIAN_MODEL) ?? DEFAULT_GUARDIAN_MODEL_ID;
|
|
243
|
+
const handoffModelId = validateGatewayModelId(env.AI_HANDOFF_MODEL) ?? DEFAULT_HANDOFF_MODEL_ID;
|
|
244
|
+
return {
|
|
245
|
+
userName: toOptionalTrimmed(env.JUNIOR_BOT_NAME) ?? "junior",
|
|
246
|
+
defaultProfile: "standard",
|
|
247
|
+
crossActorMidRunMode: parseCrossActorMidRunMode(env.JUNIOR_CROSS_ACTOR_MID_RUN_MODE),
|
|
248
|
+
profiles: parseProfiles(env.AI_MODEL_PROFILES, modelId, handoffModelId),
|
|
249
|
+
contextWindowTokens: parseOptionalPositiveInteger("AI_MODEL_CONTEXT_WINDOW_TOKENS", env.AI_MODEL_CONTEXT_WINDOW_TOKENS) ?? DEFAULT_MODEL_CONTEXT_WINDOW_TOKENS,
|
|
250
|
+
reasoningLevel: reasoningLevel === void 0 ? void 0 : parseTurnReasoningLevel(reasoningLevel),
|
|
251
|
+
fastModelId,
|
|
252
|
+
guardianModelId,
|
|
253
|
+
imageGenerationModelId: toOptionalTrimmed(env.AI_IMAGE_MODEL) ?? DEFAULT_IMAGE_GENERATION_MODEL_ID,
|
|
254
|
+
embeddingModelId: validateEmbeddingModelId(env.AI_EMBEDDING_MODEL) ?? DEFAULT_EMBEDDING_MODEL_ID,
|
|
255
|
+
loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
|
|
256
|
+
visionModelId: validateGatewayModelId(env.AI_VISION_MODEL),
|
|
257
|
+
maxSlicesPerTurn: MAX_SLICES_PER_TURN,
|
|
258
|
+
maxToolCallsPerTurn: MAX_TOOL_CALLS_PER_TURN,
|
|
259
|
+
maxConsecutiveAutomatedTurns: MAX_CONSECUTIVE_AUTOMATED_TURNS,
|
|
260
|
+
turnTimeoutMs: parseAgentTurnTimeoutMs(env.AGENT_TURN_TIMEOUT_MS, maxTurnTimeoutMs),
|
|
261
|
+
webSearchModelId: validateGatewayModelId(env.AI_WEB_SEARCH_MODEL) ?? DEFAULT_WEB_SEARCH_MODEL_ID
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function readDatabaseUrl(env) {
|
|
265
|
+
const databaseUrl = toOptionalTrimmed(env.DATABASE_URL);
|
|
266
|
+
if (!databaseUrl) throw new Error("DATABASE_URL is required");
|
|
267
|
+
return databaseUrl;
|
|
268
|
+
}
|
|
269
|
+
function isLocalDatabaseUrl(databaseUrl) {
|
|
270
|
+
try {
|
|
271
|
+
const { hostname } = new URL(databaseUrl);
|
|
272
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function readSqlDriver(env, databaseUrl) {
|
|
278
|
+
const value = toOptionalTrimmed(env.JUNIOR_DATABASE_DRIVER);
|
|
279
|
+
if (value === void 0) {
|
|
280
|
+
if (isLocalDatabaseUrl(databaseUrl)) return "postgres";
|
|
281
|
+
return "neon";
|
|
282
|
+
}
|
|
283
|
+
if (value === "neon" || value === "postgres") return value;
|
|
284
|
+
throw new Error("JUNIOR_DATABASE_DRIVER must be postgres or neon");
|
|
285
|
+
}
|
|
286
|
+
function readSqlStatementTimeoutMs(env) {
|
|
287
|
+
const rawValue = toOptionalTrimmed(env.JUNIOR_SQL_STATEMENT_TIMEOUT_MS);
|
|
288
|
+
if (rawValue === void 0) return DEFAULT_SQL_STATEMENT_TIMEOUT_MS;
|
|
289
|
+
const value = Number.parseInt(rawValue, 10);
|
|
290
|
+
if (!Number.isSafeInteger(value) || value < 0 || String(value) !== rawValue) throw new Error("JUNIOR_SQL_STATEMENT_TIMEOUT_MS must be a non-negative integer");
|
|
291
|
+
return value === 0 ? false : value;
|
|
292
|
+
}
|
|
293
|
+
/** Parse all chat configuration from environment variables. */
|
|
294
|
+
function readChatConfig(env = process.env, functionMaxDurationSeconds = DEFAULT_FUNCTION_MAX_DURATION_SECONDS) {
|
|
295
|
+
const databaseUrl = readDatabaseUrl(env);
|
|
296
|
+
const resolvedFunctionMaxDurationSeconds = resolveFunctionMaxDurationSeconds(functionMaxDurationSeconds);
|
|
297
|
+
return {
|
|
298
|
+
bot: readBotConfig(env, resolvedFunctionMaxDurationSeconds),
|
|
299
|
+
functionMaxDurationSeconds: resolvedFunctionMaxDurationSeconds,
|
|
300
|
+
conversationWorkEnabled: parseConversationWorkEnabled(env.JUNIOR_CONVERSATION_WORK_ENABLED),
|
|
301
|
+
conversationWorkSoftYieldAfterMs: resolveConversationWorkSoftYieldAfterMs(resolvedFunctionMaxDurationSeconds),
|
|
302
|
+
sql: {
|
|
303
|
+
databaseUrl,
|
|
304
|
+
driver: readSqlDriver(env, databaseUrl),
|
|
305
|
+
statementTimeoutMs: readSqlStatementTimeoutMs(env)
|
|
306
|
+
},
|
|
307
|
+
slack: {
|
|
308
|
+
botToken: toOptionalTrimmed(env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(env.SLACK_BOT_USER_TOKEN),
|
|
309
|
+
clientId: toOptionalTrimmed(env.SLACK_CLIENT_ID),
|
|
310
|
+
clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET),
|
|
311
|
+
completedReactionEmoji: DEFAULT_COMPLETED_REACTION_EMOJI,
|
|
312
|
+
processingReactionEmoji: DEFAULT_PROCESSING_REACTION_EMOJI,
|
|
313
|
+
signingSecret: toOptionalTrimmed(env.SLACK_SIGNING_SECRET),
|
|
314
|
+
slashCommand: parseSlashCommand(env.JUNIOR_SLASH_COMMAND)
|
|
315
|
+
},
|
|
316
|
+
state: {
|
|
317
|
+
adapter: env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory" ? "memory" : "redis",
|
|
318
|
+
keyPrefix: toOptionalTrimmed(env.JUNIOR_STATE_KEY_PREFIX),
|
|
319
|
+
redisUrl: toOptionalTrimmed(env.REDIS_URL)
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
/** Chat configuration parsed once at module load from the process environment. */
|
|
324
|
+
const chatConfig = readChatConfig(process.env);
|
|
325
|
+
/** Apply the host execution budget injected by juniorNitro(). */
|
|
326
|
+
function configureFunctionMaxDurationSeconds(functionMaxDurationSeconds) {
|
|
327
|
+
const resolved = resolveFunctionMaxDurationSeconds(functionMaxDurationSeconds);
|
|
328
|
+
chatConfig.functionMaxDurationSeconds = resolved;
|
|
329
|
+
chatConfig.conversationWorkSoftYieldAfterMs = resolveConversationWorkSoftYieldAfterMs(resolved);
|
|
330
|
+
chatConfig.bot.turnTimeoutMs = parseAgentTurnTimeoutMs(process.env.AGENT_TURN_TIMEOUT_MS, resolveMaxTurnTimeoutMs(resolved));
|
|
331
|
+
}
|
|
332
|
+
/** Return the chat configuration (parsed once at startup). */
|
|
333
|
+
function getChatConfig() {
|
|
334
|
+
return chatConfig;
|
|
335
|
+
}
|
|
336
|
+
/** Bot configuration derived from environment at module load. */
|
|
337
|
+
const botConfig = chatConfig.bot;
|
|
338
|
+
function getSlackBotToken() {
|
|
339
|
+
return chatConfig.slack.botToken;
|
|
340
|
+
}
|
|
341
|
+
function getSlackSigningSecret() {
|
|
342
|
+
return chatConfig.slack.signingSecret;
|
|
343
|
+
}
|
|
344
|
+
function getSlackClientId() {
|
|
345
|
+
return chatConfig.slack.clientId;
|
|
346
|
+
}
|
|
347
|
+
function getSlackClientSecret() {
|
|
348
|
+
return chatConfig.slack.clientSecret;
|
|
349
|
+
}
|
|
350
|
+
/** Return runtime metadata (version from deploy environment). */
|
|
351
|
+
function getRuntimeMetadata() {
|
|
352
|
+
return { version: toOptionalTrimmed(process.env.VERCEL_GIT_COMMIT_SHA) };
|
|
353
|
+
}
|
|
354
|
+
/** Apply model overrides from createApp(). */
|
|
355
|
+
function setBotModelConfig(config) {
|
|
356
|
+
if (config.embeddingModelId !== void 0) botConfig.embeddingModelId = requireModelId("embeddingModelId", config.embeddingModelId, validateEmbeddingModelId);
|
|
357
|
+
if (config.fastModelId !== void 0) botConfig.fastModelId = requireModelId("fastModelId", config.fastModelId, validateGatewayModelId);
|
|
358
|
+
if (config.guardianModelId !== void 0) botConfig.guardianModelId = requireModelId("guardianModelId", config.guardianModelId, validateGatewayModelId);
|
|
359
|
+
if (config.imageGenerationModelId !== void 0) botConfig.imageGenerationModelId = requireModelId("imageGenerationModelId", config.imageGenerationModelId, validateEmbeddingModelId);
|
|
360
|
+
if (config.visionModelId !== void 0) botConfig.visionModelId = requireModelId("visionModelId", config.visionModelId, validateGatewayModelId);
|
|
361
|
+
if (config.webSearchModelId !== void 0) botConfig.webSearchModelId = requireModelId("webSearchModelId", config.webSearchModelId, validateGatewayModelId);
|
|
362
|
+
}
|
|
363
|
+
/** Apply profiles from createApp(). */
|
|
364
|
+
function setProfiles(profiles, defaultProfile) {
|
|
365
|
+
if (!profiles || !defaultProfile) throw new Error("profiles and defaultProfile must be configured together");
|
|
366
|
+
const configuredProfiles = parseProfileMap(profiles, "profiles");
|
|
367
|
+
const selectedDefault = defaultProfile;
|
|
368
|
+
if (!modelProfileSchema.safeParse(selectedDefault).success) throw new Error(`defaultProfile "${selectedDefault}" must match ^[a-z][a-z0-9_-]*$`);
|
|
369
|
+
if (!Object.hasOwn(configuredProfiles, selectedDefault)) throw new Error(`defaultProfile "${selectedDefault}" is not configured`);
|
|
370
|
+
botConfig.profiles = configuredProfiles;
|
|
371
|
+
botConfig.defaultProfile = selectedDefault;
|
|
372
|
+
}
|
|
373
|
+
/** Return the current Slack reaction emoji config. */
|
|
374
|
+
function getSlackReactionConfig() {
|
|
375
|
+
return {
|
|
376
|
+
completedReactionEmoji: chatConfig.slack.completedReactionEmoji,
|
|
377
|
+
processingReactionEmoji: chatConfig.slack.processingReactionEmoji
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/** Apply Slack reaction emoji overrides from createApp() options, validating names. */
|
|
381
|
+
function setSlackReactionConfig(overrides) {
|
|
382
|
+
if (overrides.processingReactionEmoji !== void 0) chatConfig.slack.processingReactionEmoji = parseReactionEmoji("processingReactionEmoji", overrides.processingReactionEmoji, chatConfig.slack.processingReactionEmoji);
|
|
383
|
+
if (overrides.completedReactionEmoji !== void 0) chatConfig.slack.completedReactionEmoji = parseReactionEmoji("completedReactionEmoji", overrides.completedReactionEmoji, chatConfig.slack.completedReactionEmoji);
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
386
|
+
export { getChatConfig as a, getSlackClientId as c, getSlackSigningSecret as d, setBotModelConfig as f, normalizeSlackEmojiName as h, configureFunctionMaxDurationSeconds as i, getSlackClientSecret as l, setSlackReactionConfig as m, botConfig as n, getRuntimeMetadata as o, setProfiles as p, config_exports as r, getSlackBotToken as s, DEFAULT_SQL_STATEMENT_TIMEOUT_MS as t, getSlackReactionConfig as u };
|