@sentry/junior 0.27.2 → 0.29.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 CHANGED
@@ -6,11 +6,17 @@ import {
6
6
  parseSkillInvocation
7
7
  } from "./chunk-ICIRAL6Y.js";
8
8
  import {
9
+ GEN_AI_PROVIDER_NAME,
10
+ MISSING_GATEWAY_CREDENTIALS_ERROR,
9
11
  SANDBOX_DATA_ROOT,
10
12
  SANDBOX_SKILLS_ROOT,
11
13
  SANDBOX_WORKSPACE_ROOT,
12
14
  botConfig,
13
15
  buildNonInteractiveShellScript,
16
+ completeObject,
17
+ completeText,
18
+ getGatewayApiKey,
19
+ getPiGatewayApiKeyOverride,
14
20
  getRuntimeDependencyProfileHash,
15
21
  getRuntimeMetadata,
16
22
  getSlackBotToken,
@@ -20,19 +26,18 @@ import {
20
26
  getStateAdapter,
21
27
  getVercelSandboxCredentials,
22
28
  isSnapshotMissingError,
29
+ resolveGatewayModel,
23
30
  resolveRuntimeDependencySnapshot,
24
31
  runNonInteractiveCommand,
25
32
  sandboxSkillDir,
26
- sandboxSkillFile,
27
- toOptionalTrimmed
28
- } from "./chunk-4PVJHUEV.js";
33
+ sandboxSkillFile
34
+ } from "./chunk-LEYD42MR.js";
29
35
  import {
30
36
  CredentialUnavailableError,
31
37
  buildOAuthTokenRequest,
32
38
  createChatSdkLogger,
33
39
  createPluginBroker,
34
40
  createRequestContext,
35
- extractGenAiUsageAttributes,
36
41
  extractGenAiUsageSummary,
37
42
  getActiveTraceId,
38
43
  getPluginDefinition,
@@ -2066,228 +2071,6 @@ function getTurnUserReplyAttachmentContext(message) {
2066
2071
  };
2067
2072
  }
2068
2073
 
2069
- // src/chat/pi/client.ts
2070
- import {
2071
- completeSimple,
2072
- getEnvApiKey,
2073
- getModels,
2074
- registerApiProvider
2075
- } from "@mariozechner/pi-ai";
2076
- import {
2077
- streamAnthropic,
2078
- streamSimpleAnthropic
2079
- } from "@mariozechner/pi-ai/anthropic";
2080
- registerApiProvider({
2081
- api: "anthropic-messages",
2082
- stream: streamAnthropic,
2083
- streamSimple: streamSimpleAnthropic
2084
- });
2085
- var GATEWAY_PROVIDER = "vercel-ai-gateway";
2086
- var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
2087
- var GEN_AI_OPERATION_CHAT = "chat";
2088
- var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
2089
- function getGatewayApiKey() {
2090
- return toOptionalTrimmed(getEnvApiKey("vercel-ai-gateway")) ?? toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
2091
- }
2092
- function getPiGatewayApiKeyOverride() {
2093
- return toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
2094
- }
2095
- function extractText(message) {
2096
- return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
2097
- }
2098
- function parseJsonCandidate(text) {
2099
- const trimmed = text.trim();
2100
- if (!trimmed) return void 0;
2101
- try {
2102
- return JSON.parse(trimmed);
2103
- } catch {
2104
- const fencedBlocks = [
2105
- ...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)
2106
- ];
2107
- for (const block of fencedBlocks) {
2108
- try {
2109
- return JSON.parse(block[1]);
2110
- } catch {
2111
- }
2112
- }
2113
- const openBraceIndex = trimmed.indexOf("{");
2114
- if (openBraceIndex >= 0) {
2115
- let depth = 0;
2116
- let inString = false;
2117
- let escaped = false;
2118
- for (let index = openBraceIndex; index < trimmed.length; index += 1) {
2119
- const char = trimmed[index];
2120
- if (inString) {
2121
- if (escaped) {
2122
- escaped = false;
2123
- continue;
2124
- }
2125
- if (char === "\\") {
2126
- escaped = true;
2127
- continue;
2128
- }
2129
- if (char === '"') {
2130
- inString = false;
2131
- }
2132
- continue;
2133
- }
2134
- if (char === '"') {
2135
- inString = true;
2136
- continue;
2137
- }
2138
- if (char === "{") {
2139
- depth += 1;
2140
- continue;
2141
- }
2142
- if (char === "}") {
2143
- depth -= 1;
2144
- if (depth === 0) {
2145
- const slice = trimmed.slice(openBraceIndex, index + 1);
2146
- try {
2147
- return JSON.parse(slice);
2148
- } catch {
2149
- break;
2150
- }
2151
- }
2152
- }
2153
- }
2154
- }
2155
- return void 0;
2156
- }
2157
- }
2158
- function resolveGatewayModel(modelId) {
2159
- const models = getModels(GATEWAY_PROVIDER);
2160
- const matched = models.find((model) => model.id === modelId);
2161
- if (!matched) {
2162
- throw new Error(`Unknown AI Gateway model id: ${modelId}`);
2163
- }
2164
- return matched;
2165
- }
2166
- async function completeText(params) {
2167
- const model = resolveGatewayModel(params.modelId);
2168
- const apiKey = getPiGatewayApiKeyOverride();
2169
- const requestMessagesAttribute = serializeGenAiAttribute(params.messages);
2170
- const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
2171
- const startAttributes = {
2172
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2173
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2174
- "gen_ai.request.model": params.modelId,
2175
- ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
2176
- ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
2177
- "app.ai.auth_mode": apiKey ? "oidc" : "api_key"
2178
- };
2179
- setSpanAttributes(startAttributes);
2180
- const message = await completeSimple(
2181
- model,
2182
- {
2183
- systemPrompt: params.system,
2184
- messages: params.messages
2185
- },
2186
- {
2187
- ...apiKey ? { apiKey } : {},
2188
- temperature: params.temperature,
2189
- maxTokens: params.maxTokens,
2190
- signal: params.signal,
2191
- metadata: params.metadata
2192
- }
2193
- );
2194
- const outputText = extractText(message);
2195
- const outputMessagesAttribute = serializeGenAiAttribute([
2196
- {
2197
- role: "assistant",
2198
- content: outputText ? [{ type: "text", text: outputText }] : []
2199
- }
2200
- ]);
2201
- const usageAttributes = extractGenAiUsageAttributes(message);
2202
- const endAttributes = {
2203
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2204
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2205
- "gen_ai.request.model": params.modelId,
2206
- ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
2207
- ...usageAttributes,
2208
- ...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {}
2209
- };
2210
- setSpanAttributes(endAttributes);
2211
- if (message.stopReason === "error") {
2212
- const providerMessage = message.errorMessage?.trim() || "Unknown provider error";
2213
- logWarn(
2214
- "ai_completion_provider_error",
2215
- {},
2216
- {
2217
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2218
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2219
- "gen_ai.request.model": params.modelId,
2220
- "error.message": providerMessage
2221
- },
2222
- "AI completion returned provider error"
2223
- );
2224
- throw new Error(`AI provider error: ${providerMessage}`);
2225
- }
2226
- return {
2227
- message,
2228
- text: outputText
2229
- };
2230
- }
2231
- async function completeObject(params) {
2232
- const startedAt = Date.now();
2233
- let text = "";
2234
- try {
2235
- ({ text } = await completeText({
2236
- modelId: params.modelId,
2237
- system: params.system,
2238
- temperature: params.temperature,
2239
- maxTokens: params.maxTokens,
2240
- signal: params.signal,
2241
- metadata: params.metadata,
2242
- messages: [
2243
- {
2244
- role: "user",
2245
- content: params.prompt,
2246
- timestamp: Date.now()
2247
- }
2248
- ]
2249
- }));
2250
- } catch (error) {
2251
- logException(
2252
- error,
2253
- "ai_completion_failed",
2254
- {},
2255
- {
2256
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2257
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2258
- "gen_ai.request.model": params.modelId,
2259
- "app.ai.duration_ms": Date.now() - startedAt
2260
- },
2261
- "AI object completion failed"
2262
- );
2263
- throw error;
2264
- }
2265
- const candidate = parseJsonCandidate(text);
2266
- const parsed = params.schema.safeParse(candidate);
2267
- if (!parsed.success) {
2268
- const preview = text.length > 400 ? `${text.slice(0, 400)}...` : text;
2269
- logWarn(
2270
- "ai_completion_schema_parse_failed",
2271
- {},
2272
- {
2273
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
2274
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
2275
- "gen_ai.request.model": params.modelId,
2276
- "app.ai.duration_ms": Date.now() - startedAt,
2277
- "app.ai.response_preview": preview
2278
- },
2279
- "AI object completion schema parse failed"
2280
- );
2281
- throw new Error(
2282
- `Model did not return valid JSON for schema: ${parsed.error.message}. Raw response: ${preview}`
2283
- );
2284
- }
2285
- return {
2286
- object: parsed.data,
2287
- text
2288
- };
2289
- }
2290
-
2291
2074
  // src/chat/slack/message.ts
2292
2075
  function getSlackMessageTs(message) {
2293
2076
  if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
@@ -3354,7 +3137,6 @@ function buildSystemPrompt(params) {
3354
3137
  [
3355
3138
  "- For factual or external questions, run tools/skills first, then answer from evidence.",
3356
3139
  "- Use tool descriptions as the source of truth for when each tool should or should not be called.",
3357
- "- Use `reportProgress` only for sparse, meaningful progress updates. Pass a short user-facing status message, and do not call it for every tool or small substep.",
3358
3140
  "- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
3359
3141
  "- Keep routine setup and research steps silent in user-facing replies. Do not narrate duplicate checks, credential issuance, file writes, or similar internal progress unless the result is user-relevant.",
3360
3142
  "- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
@@ -5068,11 +4850,11 @@ function createReadFileTool() {
5068
4850
  import { Type as Type6 } from "@sinclair/typebox";
5069
4851
  function createReportProgressTool() {
5070
4852
  return tool({
5071
- description: "Update assistant status with a short user-facing progress message. Use this sparingly for meaningful progress changes, not for every tool call or minor substep.",
4853
+ description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
5072
4854
  inputSchema: Type6.Object({
5073
4855
  message: Type6.String({
5074
4856
  minLength: 1,
5075
- description: "Short user-facing progress message. The UI truncates it if needed."
4857
+ description: "Short user-facing progress message."
5076
4858
  })
5077
4859
  })
5078
4860
  });
@@ -5346,7 +5128,7 @@ function createOperationKey(toolName, input) {
5346
5128
  // src/chat/tools/slack/channel-post-message.ts
5347
5129
  function createSlackChannelPostMessageTool(context, state) {
5348
5130
  return tool({
5349
- description: "Post a message in the active Slack channel context (outside the thread). Use this when the user explicitly asks to post/send/share/say something in the channel. Do not use for normal thread replies or speculative broadcasts. Do not claim a channel message was posted unless this tool succeeds in this turn.",
5131
+ description: "Post a message in the active Slack channel context (outside the thread). Use this only when the user explicitly asks to post/send/share/say something in the current channel. Do not use it for normal thread replies, speculative broadcasts, or requests targeting another named channel; explain that limitation instead. Do not claim a channel message was posted unless this tool succeeds in this turn.",
5350
5132
  inputSchema: Type9.Object({
5351
5133
  text: Type9.String({
5352
5134
  minLength: 1,
@@ -8715,7 +8497,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
8715
8497
  }
8716
8498
 
8717
8499
  // src/chat/tools/agent-tools.ts
8718
- function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, hooks) {
8500
+ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
8719
8501
  const shouldTrace = shouldEmitDevAgentTrace();
8720
8502
  return Object.entries(tools).map(([toolName, toolDef]) => ({
8721
8503
  name: toolName,
@@ -8725,7 +8507,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
8725
8507
  execute: async (toolCallId, params) => {
8726
8508
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
8727
8509
  const toolArgumentsAttribute = serializeGenAiAttribute(params);
8728
- hooks?.onToolCall?.(toolName);
8510
+ onToolCall?.(toolName);
8729
8511
  const traceToolContext = {
8730
8512
  ...spanContext,
8731
8513
  conversationId: spanContext.conversationId,
@@ -8843,7 +8625,7 @@ function isExecutionEscapeResponse(text) {
8843
8625
  if (!trimmed) return false;
8844
8626
  return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
8845
8627
  }
8846
- function parseJsonCandidate2(text) {
8628
+ function parseJsonCandidate(text) {
8847
8629
  const trimmed = text.trim();
8848
8630
  if (!trimmed) return void 0;
8849
8631
  try {
@@ -8871,7 +8653,7 @@ function isToolPayloadShape(payload) {
8871
8653
  return false;
8872
8654
  }
8873
8655
  function isRawToolPayloadResponse(text) {
8874
- const parsed = parseJsonCandidate2(text);
8656
+ const parsed = parseJsonCandidate(text);
8875
8657
  if (Array.isArray(parsed)) {
8876
8658
  return parsed.some((entry) => isToolPayloadShape(entry));
8877
8659
  }
@@ -9139,6 +8921,7 @@ function buildTurnResult(input) {
9139
8921
  shouldTrace,
9140
8922
  spanContext,
9141
8923
  usage,
8924
+ thinkingSelection,
9142
8925
  correlation,
9143
8926
  assistantUserName
9144
8927
  } = input;
@@ -9214,6 +8997,7 @@ function buildTurnResult(input) {
9214
8997
  outcome: resolvedOutcome,
9215
8998
  modelId: botConfig.modelId,
9216
8999
  assistantMessageCount: assistantMessages.length,
9000
+ thinkingLevel: thinkingSelection.thinkingLevel,
9217
9001
  toolCalls,
9218
9002
  toolResultCount: toolResults.length,
9219
9003
  toolErrorCount,
@@ -9236,6 +9020,120 @@ function buildTurnResult(input) {
9236
9020
  };
9237
9021
  }
9238
9022
 
9023
+ // src/chat/services/turn-thinking-level.ts
9024
+ import { z } from "zod";
9025
+ var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
9026
+ var MAX_ROUTER_CONTEXT_CHARS = 1200;
9027
+ var TURN_THINKING_LEVELS = ["none", "low", "medium", "high"];
9028
+ var turnExecutionProfileSchema = z.object({
9029
+ thinking_level: z.enum(TURN_THINKING_LEVELS),
9030
+ confidence: z.number().min(0).max(1),
9031
+ reason: z.string().min(1)
9032
+ });
9033
+ var DEFAULT_THINKING_LEVEL = "low";
9034
+ function trimContextForRouter(text) {
9035
+ const trimmed = text?.trim();
9036
+ if (!trimmed) {
9037
+ return void 0;
9038
+ }
9039
+ return trimmed.length <= MAX_ROUTER_CONTEXT_CHARS ? trimmed : trimmed.slice(-MAX_ROUTER_CONTEXT_CHARS);
9040
+ }
9041
+ function buildClassifierSystemPrompt() {
9042
+ return [
9043
+ "You route assistant turns to the cheapest thinking level that is still likely to succeed.",
9044
+ "Choose exactly one bucket: none, low, medium, or high.",
9045
+ "",
9046
+ "Use none for greetings, acknowledgments, and trivial single-step asks.",
9047
+ "Use low for straightforward explanations or simple one-step work.",
9048
+ "Use medium for investigations, ambiguous asks, multi-step analysis, or likely multi-tool work.",
9049
+ "Use high for code changes, debugging/root-cause analysis, research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
9050
+ "",
9051
+ "Return JSON only with thinking_level, confidence, and reason."
9052
+ ].join("\n");
9053
+ }
9054
+ function buildClassifierPrompt(args) {
9055
+ const sections = [];
9056
+ const context = trimContextForRouter(args.conversationContext);
9057
+ if (context) {
9058
+ sections.push("<thread-background>", context, "</thread-background>", "");
9059
+ }
9060
+ sections.push(
9061
+ "<turn-context>",
9062
+ `- active_skills: ${args.activeSkillNames.join(", ") || "none"}`,
9063
+ `- attachment_count: ${args.attachmentCount}`,
9064
+ "</turn-context>",
9065
+ "",
9066
+ '<current-instruction priority="highest">',
9067
+ args.messageText.trim() || "[empty]",
9068
+ "</current-instruction>"
9069
+ );
9070
+ for (const block of args.currentTurnBlocks ?? []) {
9071
+ const trimmed = block.trim();
9072
+ if (!trimmed) {
9073
+ continue;
9074
+ }
9075
+ sections.push("", trimmed);
9076
+ }
9077
+ return sections.join("\n");
9078
+ }
9079
+ async function selectTurnThinkingLevel(args) {
9080
+ const activeSkillNames = [...new Set(args.activeSkillNames ?? [])].sort();
9081
+ try {
9082
+ const result = await args.completeObject({
9083
+ modelId: args.fastModelId,
9084
+ schema: turnExecutionProfileSchema,
9085
+ maxTokens: 120,
9086
+ metadata: {
9087
+ modelId: args.fastModelId,
9088
+ threadId: args.context?.threadId ?? "",
9089
+ channelId: args.context?.channelId ?? "",
9090
+ requesterId: args.context?.requesterId ?? "",
9091
+ runId: args.context?.runId ?? ""
9092
+ },
9093
+ prompt: buildClassifierPrompt({
9094
+ activeSkillNames,
9095
+ attachmentCount: args.attachmentCount ?? 0,
9096
+ conversationContext: args.conversationContext,
9097
+ currentTurnBlocks: args.currentTurnBlocks,
9098
+ messageText: args.messageText
9099
+ }),
9100
+ thinkingLevel: "low",
9101
+ system: buildClassifierSystemPrompt(),
9102
+ temperature: 0
9103
+ });
9104
+ const parsed = turnExecutionProfileSchema.parse(result.object);
9105
+ if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
9106
+ return {
9107
+ confidence: parsed.confidence,
9108
+ thinkingLevel: DEFAULT_THINKING_LEVEL,
9109
+ reason: `low_confidence_default:${parsed.reason.trim()}`
9110
+ };
9111
+ }
9112
+ return {
9113
+ confidence: parsed.confidence,
9114
+ thinkingLevel: parsed.thinking_level,
9115
+ reason: parsed.reason.trim()
9116
+ };
9117
+ } catch {
9118
+ return {
9119
+ thinkingLevel: DEFAULT_THINKING_LEVEL,
9120
+ reason: "classifier_error_default"
9121
+ };
9122
+ }
9123
+ }
9124
+ function toAgentThinkingLevel(level) {
9125
+ switch (level) {
9126
+ case "none":
9127
+ return "off";
9128
+ case "low":
9129
+ return "low";
9130
+ case "medium":
9131
+ return "medium";
9132
+ case "high":
9133
+ return "high";
9134
+ }
9135
+ }
9136
+
9239
9137
  // src/chat/state/turn-session-store.ts
9240
9138
  var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
9241
9139
  var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -9513,6 +9411,7 @@ function createMcpAuthOrchestration(deps, abortAgent) {
9513
9411
 
9514
9412
  // src/chat/respond.ts
9515
9413
  var startupDiscoveryLogged = false;
9414
+ var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
9516
9415
  function buildOmittedImageAttachmentNotice(count) {
9517
9416
  return [
9518
9417
  "<omitted-image-attachments>",
@@ -9523,6 +9422,89 @@ function buildOmittedImageAttachmentNotice(count) {
9523
9422
  "</omitted-image-attachments>"
9524
9423
  ].join("\n");
9525
9424
  }
9425
+ function trimRouterAttachmentText(text) {
9426
+ const normalized = text.replaceAll("\0", " ").trim();
9427
+ if (!normalized) {
9428
+ return "";
9429
+ }
9430
+ return normalized.length <= MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS ? normalized : `${normalized.slice(0, MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS)}...`;
9431
+ }
9432
+ function supportsRouterTextPreview(mediaType) {
9433
+ const baseMediaType = mediaType.split(";", 1)[0]?.trim().toLowerCase();
9434
+ if (!baseMediaType) {
9435
+ return false;
9436
+ }
9437
+ return baseMediaType.startsWith("text/") || baseMediaType === "application/json" || baseMediaType === "application/xml" || baseMediaType === "application/x-www-form-urlencoded" || baseMediaType.endsWith("+json") || baseMediaType.endsWith("+xml");
9438
+ }
9439
+ function buildRouterAttachmentBlock(attachment) {
9440
+ if (attachment.promptText) {
9441
+ return trimRouterAttachmentText(attachment.promptText);
9442
+ }
9443
+ const header = [
9444
+ "<attachment>",
9445
+ `filename: ${attachment.filename ?? "unnamed"}`,
9446
+ `media_type: ${attachment.mediaType}`
9447
+ ];
9448
+ if (attachment.data && supportsRouterTextPreview(attachment.mediaType)) {
9449
+ const preview = trimRouterAttachmentText(attachment.data.toString("utf8"));
9450
+ if (preview) {
9451
+ return [
9452
+ ...header,
9453
+ "<text-preview>",
9454
+ preview,
9455
+ "</text-preview>",
9456
+ "</attachment>"
9457
+ ].join("\n");
9458
+ }
9459
+ }
9460
+ return [...header, "</attachment>"].join("\n");
9461
+ }
9462
+ function buildUserTurnInput(args) {
9463
+ const routerBlocks = [];
9464
+ const userContentParts = [
9465
+ { type: "text", text: args.userTurnText }
9466
+ ];
9467
+ if (args.omittedImageAttachmentCount > 0) {
9468
+ const omittedImagesNotice = buildOmittedImageAttachmentNotice(
9469
+ args.omittedImageAttachmentCount
9470
+ );
9471
+ userContentParts.push({ type: "text", text: omittedImagesNotice });
9472
+ routerBlocks.push(omittedImagesNotice);
9473
+ }
9474
+ for (const attachment of args.userAttachments ?? []) {
9475
+ routerBlocks.push(buildRouterAttachmentBlock(attachment));
9476
+ if (attachment.promptText) {
9477
+ userContentParts.push({
9478
+ type: "text",
9479
+ text: attachment.promptText
9480
+ });
9481
+ continue;
9482
+ }
9483
+ if (attachment.mediaType.startsWith("image/")) {
9484
+ if (!attachment.data) {
9485
+ throw new Error("Image attachment is missing image data");
9486
+ }
9487
+ userContentParts.push({
9488
+ type: "image",
9489
+ data: attachment.data.toString("base64"),
9490
+ mimeType: attachment.mediaType
9491
+ });
9492
+ continue;
9493
+ }
9494
+ if (!attachment.data) {
9495
+ throw new Error("Attachment is missing attachment data");
9496
+ }
9497
+ userContentParts.push({
9498
+ type: "text",
9499
+ text: encodeNonImageAttachmentForPrompt({
9500
+ data: attachment.data,
9501
+ mediaType: attachment.mediaType,
9502
+ filename: attachment.filename
9503
+ })
9504
+ });
9505
+ }
9506
+ return { routerBlocks, userContentParts };
9507
+ }
9526
9508
  function mcpToolsToDefinitions(mcpTools) {
9527
9509
  const defs = {};
9528
9510
  for (const tool2 of mcpTools) {
@@ -9550,6 +9532,7 @@ async function generateAssistantReply(messageText, context = {}) {
9550
9532
  let sandboxExecutor;
9551
9533
  let timedOut = false;
9552
9534
  let turnUsage;
9535
+ let thinkingSelection;
9553
9536
  const getSandboxMetadata = () => sandboxExecutor ? {
9554
9537
  sandboxId: sandboxExecutor.getSandboxId(),
9555
9538
  sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash()
@@ -9728,6 +9711,34 @@ async function generateAssistantReply(messageText, context = {}) {
9728
9711
  turnContext: { traceId: getActiveTraceId() }
9729
9712
  }
9730
9713
  );
9714
+ const { routerBlocks, userContentParts } = buildUserTurnInput({
9715
+ omittedImageAttachmentCount: context.omittedImageAttachmentCount ?? 0,
9716
+ userAttachments: context.userAttachments,
9717
+ userTurnText
9718
+ });
9719
+ thinkingSelection = await selectTurnThinkingLevel({
9720
+ activeSkillNames: activeSkills.map((skill) => skill.name),
9721
+ attachmentCount: context.userAttachments?.length,
9722
+ completeObject,
9723
+ conversationContext: context.conversationContext,
9724
+ context: {
9725
+ threadId: context.correlation?.threadId,
9726
+ channelId: context.correlation?.channelId,
9727
+ requesterId: context.correlation?.requesterId,
9728
+ runId: context.correlation?.runId
9729
+ },
9730
+ currentTurnBlocks: routerBlocks,
9731
+ fastModelId: botConfig.fastModelId,
9732
+ messageText: userInput
9733
+ });
9734
+ setSpanAttributes({
9735
+ "gen_ai.request.model": botConfig.modelId,
9736
+ "app.ai.reasoning_effort": thinkingSelection.thinkingLevel,
9737
+ "app.ai.thinking_level_reason": thinkingSelection.reason,
9738
+ ...thinkingSelection.confidence !== void 0 ? {
9739
+ "app.ai.thinking_level_confidence": thinkingSelection.confidence
9740
+ } : {}
9741
+ });
9731
9742
  timeoutResumeMessages = [];
9732
9743
  const generatedFiles = [];
9733
9744
  const replyFiles = [];
@@ -9889,44 +9900,6 @@ async function generateAssistantReply(messageText, context = {}) {
9889
9900
  runtimeMetadata: getRuntimeMetadata(),
9890
9901
  threadParticipants: context.threadParticipants
9891
9902
  });
9892
- const userContentParts = [{ type: "text", text: userTurnText }];
9893
- const omittedImageAttachmentCount = context.omittedImageAttachmentCount ?? 0;
9894
- if (omittedImageAttachmentCount > 0) {
9895
- userContentParts.push({
9896
- type: "text",
9897
- text: buildOmittedImageAttachmentNotice(omittedImageAttachmentCount)
9898
- });
9899
- }
9900
- for (const attachment of context.userAttachments ?? []) {
9901
- if (attachment.promptText) {
9902
- userContentParts.push({
9903
- type: "text",
9904
- text: attachment.promptText
9905
- });
9906
- } else if (attachment.mediaType.startsWith("image/")) {
9907
- if (!attachment.data) {
9908
- throw new Error("Image attachment is missing image data");
9909
- }
9910
- userContentParts.push({
9911
- type: "image",
9912
- data: attachment.data.toString("base64"),
9913
- mimeType: attachment.mediaType
9914
- });
9915
- } else {
9916
- if (!attachment.data) {
9917
- throw new Error("Attachment is missing attachment data");
9918
- }
9919
- const promptAttachment = {
9920
- data: attachment.data,
9921
- mediaType: attachment.mediaType,
9922
- filename: attachment.filename
9923
- };
9924
- userContentParts.push({
9925
- type: "text",
9926
- text: encodeNonImageAttachmentForPrompt(promptAttachment)
9927
- });
9928
- }
9929
- }
9930
9903
  const inputMessagesAttribute = serializeGenAiAttribute([
9931
9904
  {
9932
9905
  role: "system",
@@ -9937,21 +9910,8 @@ async function generateAssistantReply(messageText, context = {}) {
9937
9910
  content: userContentParts.map((part) => toObservablePromptPart(part))
9938
9911
  }
9939
9912
  ]);
9940
- const agentToolHooks = {
9941
- onToolCall: (toolName) => {
9942
- toolCalls.push(toolName);
9943
- Promise.resolve(context.onToolCall?.(toolName)).catch((error) => {
9944
- logWarn(
9945
- "streaming_tool_call_error",
9946
- {},
9947
- {
9948
- "error.message": error instanceof Error ? error.message : String(error),
9949
- "gen_ai.tool.name": toolName
9950
- },
9951
- "Failed to deliver tool call event to stream coordinator"
9952
- );
9953
- });
9954
- }
9913
+ const onToolCall = (toolName) => {
9914
+ toolCalls.push(toolName);
9955
9915
  };
9956
9916
  const baseAgentTools = createAgentTools(
9957
9917
  tools,
@@ -9961,7 +9921,7 @@ async function generateAssistantReply(messageText, context = {}) {
9961
9921
  sandboxExecutor,
9962
9922
  capabilityRuntime,
9963
9923
  pluginAuth,
9964
- agentToolHooks
9924
+ onToolCall
9965
9925
  );
9966
9926
  const agentTools = [...baseAgentTools];
9967
9927
  const syncMcpAgentTools = () => {
@@ -9975,7 +9935,7 @@ async function generateAssistantReply(messageText, context = {}) {
9975
9935
  sandboxExecutor,
9976
9936
  capabilityRuntime,
9977
9937
  pluginAuth,
9978
- agentToolHooks
9938
+ onToolCall
9979
9939
  );
9980
9940
  agentTools.length = 0;
9981
9941
  agentTools.push(...baseAgentTools, ...mcpAgentTools);
@@ -9986,6 +9946,7 @@ async function generateAssistantReply(messageText, context = {}) {
9986
9946
  initialState: {
9987
9947
  systemPrompt: baseInstructions,
9988
9948
  model: resolveGatewayModel(botConfig.modelId),
9949
+ thinkingLevel: toAgentThinkingLevel(thinkingSelection.thinkingLevel),
9989
9950
  tools: agentTools
9990
9951
  }
9991
9952
  });
@@ -10031,7 +9992,7 @@ async function generateAssistantReply(messageText, context = {}) {
10031
9992
  let completedAssistantTurn = false;
10032
9993
  try {
10033
9994
  if (resumedFromCheckpoint) {
10034
- agent.replaceMessages(existingCheckpoint.piMessages);
9995
+ agent.state.messages = existingCheckpoint.piMessages;
10035
9996
  }
10036
9997
  beforeMessageCount = agent.state.messages.length;
10037
9998
  await withSpan(
@@ -10073,6 +10034,9 @@ async function generateAssistantReply(messageText, context = {}) {
10073
10034
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10074
10035
  "gen_ai.operation.name": "invoke_agent",
10075
10036
  "gen_ai.request.model": botConfig.modelId,
10037
+ ...thinkingSelection ? {
10038
+ "app.ai.reasoning_effort": thinkingSelection.thinkingLevel
10039
+ } : {},
10076
10040
  "app.ai.turn_timeout_ms": botConfig.turnTimeoutMs
10077
10041
  },
10078
10042
  "Agent turn timed out and was aborted"
@@ -10117,6 +10081,7 @@ async function generateAssistantReply(messageText, context = {}) {
10117
10081
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10118
10082
  "gen_ai.operation.name": "invoke_agent",
10119
10083
  "gen_ai.request.model": botConfig.modelId,
10084
+ "app.ai.reasoning_effort": thinkingSelection.thinkingLevel,
10120
10085
  ...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
10121
10086
  }
10122
10087
  );
@@ -10148,6 +10113,7 @@ async function generateAssistantReply(messageText, context = {}) {
10148
10113
  shouldTrace,
10149
10114
  spanContext,
10150
10115
  usage: turnUsage,
10116
+ thinkingSelection,
10151
10117
  correlation: context.correlation,
10152
10118
  assistantUserName: context.assistant?.userName
10153
10119
  });
@@ -10234,6 +10200,9 @@ async function generateAssistantReply(messageText, context = {}) {
10234
10200
  outcome: "provider_error",
10235
10201
  modelId: botConfig.modelId,
10236
10202
  assistantMessageCount: 0,
10203
+ ...thinkingSelection ? {
10204
+ thinkingLevel: thinkingSelection.thinkingLevel
10205
+ } : {},
10237
10206
  toolCalls: [],
10238
10207
  toolResultCount: 0,
10239
10208
  toolErrorCount: 0,
@@ -10672,11 +10641,10 @@ function buildSlackReplyFooter(args) {
10672
10641
  value: formatSlackDuration(durationMs)
10673
10642
  });
10674
10643
  }
10675
- const traceId = args.traceId?.trim();
10676
- if (traceId) {
10644
+ if (args.thinkingLevel) {
10677
10645
  items.push({
10678
- label: "Trace",
10679
- value: traceId
10646
+ label: "Thinking",
10647
+ value: args.thinkingLevel
10680
10648
  });
10681
10649
  }
10682
10650
  return items.length > 0 ? { items } : void 0;
@@ -10999,7 +10967,7 @@ async function resumeSlackTurn(args) {
10999
10967
  const footer = buildSlackReplyFooter({
11000
10968
  conversationId: args.replyContext?.correlation?.conversationId ?? lockKey,
11001
10969
  durationMs: reply.diagnostics.durationMs,
11002
- traceId: getActiveTraceId(),
10970
+ thinkingLevel: reply.diagnostics.thinkingLevel,
11003
10971
  usage: reply.diagnostics.usage
11004
10972
  });
11005
10973
  await postSlackApiReplyPosts({
@@ -12296,14 +12264,14 @@ async function POST(request, waitUntil) {
12296
12264
  }
12297
12265
 
12298
12266
  // src/chat/services/subscribed-decision.ts
12299
- import { z } from "zod";
12300
- var replyDecisionSchema = z.object({
12301
- should_reply: z.boolean().describe("Whether Junior should respond to this thread message."),
12302
- should_unsubscribe: z.boolean().optional().describe(
12267
+ import { z as z2 } from "zod";
12268
+ var replyDecisionSchema = z2.object({
12269
+ should_reply: z2.boolean().describe("Whether Junior should respond to this thread message."),
12270
+ should_unsubscribe: z2.boolean().optional().describe(
12303
12271
  "Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
12304
12272
  ),
12305
- confidence: z.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
12306
- reason: z.string().optional().describe("Short reason for the decision.")
12273
+ confidence: z2.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
12274
+ reason: z2.string().optional().describe("Short reason for the decision.")
12307
12275
  });
12308
12276
  var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
12309
12277
  var LEADING_SLACK_MENTION_RE = /^\s*<@([A-Z0-9]+)(?:\|([^>]+))?>[\s,:-]*/i;
@@ -13993,7 +13961,7 @@ function createReplyToThread(deps) {
13993
13961
  slackChannelId: channelId,
13994
13962
  runId,
13995
13963
  assistantUserName: botConfig.userName,
13996
- modelId: botConfig.modelId
13964
+ modelId: reply.diagnostics.modelId
13997
13965
  };
13998
13966
  const diagnosticsAttributes = {
13999
13967
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
@@ -14004,6 +13972,9 @@ function createReplyToThread(deps) {
14004
13972
  "app.ai.tool_error_results": reply.diagnostics.toolErrorCount,
14005
13973
  "app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
14006
13974
  "app.ai.used_primary_text": reply.diagnostics.usedPrimaryText,
13975
+ ...reply.diagnostics.thinkingLevel ? {
13976
+ "app.ai.reasoning_effort": reply.diagnostics.thinkingLevel
13977
+ } : {},
14007
13978
  ...reply.diagnostics.stopReason ? {
14008
13979
  "gen_ai.response.finish_reasons": [
14009
13980
  reply.diagnostics.stopReason
@@ -14065,7 +14036,7 @@ function createReplyToThread(deps) {
14065
14036
  const replyFooter = buildSlackReplyFooter({
14066
14037
  conversationId,
14067
14038
  durationMs: reply.diagnostics.durationMs,
14068
- traceId: getActiveTraceId(),
14039
+ thinkingLevel: reply.diagnostics.thinkingLevel,
14069
14040
  usage: reply.diagnostics.usage
14070
14041
  });
14071
14042
  const shouldUseSlackFooter = Boolean(replyFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
@@ -1,6 +1,11 @@
1
1
  import {
2
+ extractGenAiUsageAttributes,
2
3
  getPluginRuntimeDependencies,
3
4
  getPluginRuntimePostinstall,
5
+ logException,
6
+ logWarn,
7
+ serializeGenAiAttribute,
8
+ setSpanAttributes,
4
9
  withSpan
5
10
  } from "./chunk-RZJDO55D.js";
6
11
 
@@ -8,6 +13,9 @@ import {
8
13
  import { createMemoryState } from "@chat-adapter/state-memory";
9
14
  import { createRedisState } from "@chat-adapter/state-redis";
10
15
 
16
+ // src/chat/config.ts
17
+ import { getModel } from "@mariozechner/pi-ai";
18
+
11
19
  // src/chat/optional-string.ts
12
20
  function toOptionalTrimmed(value) {
13
21
  if (!value) {
@@ -17,6 +25,233 @@ function toOptionalTrimmed(value) {
17
25
  return trimmed.length > 0 ? trimmed : void 0;
18
26
  }
19
27
 
28
+ // src/chat/pi/client.ts
29
+ import {
30
+ completeSimple,
31
+ getEnvApiKey,
32
+ getModels,
33
+ registerApiProvider
34
+ } from "@mariozechner/pi-ai";
35
+ import {
36
+ streamAnthropic,
37
+ streamSimpleAnthropic
38
+ } from "@mariozechner/pi-ai/anthropic";
39
+ registerApiProvider({
40
+ api: "anthropic-messages",
41
+ stream: streamAnthropic,
42
+ streamSimple: streamSimpleAnthropic
43
+ });
44
+ var GATEWAY_PROVIDER = "vercel-ai-gateway";
45
+ var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
46
+ var GEN_AI_OPERATION_CHAT = "chat";
47
+ var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
48
+ function getGatewayApiKey() {
49
+ return toOptionalTrimmed(getEnvApiKey("vercel-ai-gateway")) ?? toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
50
+ }
51
+ function getPiGatewayApiKeyOverride() {
52
+ return toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
53
+ }
54
+ function extractText(message) {
55
+ return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
56
+ }
57
+ function parseJsonCandidate(text) {
58
+ const trimmed = text.trim();
59
+ if (!trimmed) return void 0;
60
+ try {
61
+ return JSON.parse(trimmed);
62
+ } catch {
63
+ const fencedBlocks = [
64
+ ...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)
65
+ ];
66
+ for (const block of fencedBlocks) {
67
+ try {
68
+ return JSON.parse(block[1]);
69
+ } catch {
70
+ }
71
+ }
72
+ const openBraceIndex = trimmed.indexOf("{");
73
+ if (openBraceIndex >= 0) {
74
+ let depth = 0;
75
+ let inString = false;
76
+ let escaped = false;
77
+ for (let index = openBraceIndex; index < trimmed.length; index += 1) {
78
+ const char = trimmed[index];
79
+ if (inString) {
80
+ if (escaped) {
81
+ escaped = false;
82
+ continue;
83
+ }
84
+ if (char === "\\") {
85
+ escaped = true;
86
+ continue;
87
+ }
88
+ if (char === '"') {
89
+ inString = false;
90
+ }
91
+ continue;
92
+ }
93
+ if (char === '"') {
94
+ inString = true;
95
+ continue;
96
+ }
97
+ if (char === "{") {
98
+ depth += 1;
99
+ continue;
100
+ }
101
+ if (char === "}") {
102
+ depth -= 1;
103
+ if (depth === 0) {
104
+ const slice = trimmed.slice(openBraceIndex, index + 1);
105
+ try {
106
+ return JSON.parse(slice);
107
+ } catch {
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return void 0;
115
+ }
116
+ }
117
+ function resolveGatewayModel(modelId) {
118
+ const matched = getModels(GATEWAY_PROVIDER).find(
119
+ (model) => model.id === modelId
120
+ );
121
+ if (!matched) {
122
+ throw new Error(`Unknown AI Gateway model id: ${modelId}`);
123
+ }
124
+ return matched;
125
+ }
126
+ async function completeText(params) {
127
+ const model = resolveGatewayModel(params.modelId);
128
+ const apiKey = getPiGatewayApiKeyOverride();
129
+ const requestMessagesAttribute = serializeGenAiAttribute(params.messages);
130
+ const systemInstructionsAttribute = params.system ? serializeGenAiAttribute([{ type: "text", content: params.system }]) : void 0;
131
+ const startAttributes = {
132
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
133
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
134
+ "gen_ai.request.model": params.modelId,
135
+ ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
136
+ ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
137
+ "app.ai.auth_mode": apiKey ? "oidc" : "api_key",
138
+ ...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
139
+ };
140
+ setSpanAttributes(startAttributes);
141
+ const message = await completeSimple(
142
+ model,
143
+ {
144
+ systemPrompt: params.system,
145
+ messages: params.messages
146
+ },
147
+ {
148
+ ...apiKey ? { apiKey } : {},
149
+ temperature: params.temperature,
150
+ maxTokens: params.maxTokens,
151
+ reasoning: params.thinkingLevel,
152
+ signal: params.signal,
153
+ metadata: params.metadata
154
+ }
155
+ );
156
+ const outputText = extractText(message);
157
+ const outputMessagesAttribute = serializeGenAiAttribute([
158
+ {
159
+ role: "assistant",
160
+ content: outputText ? [{ type: "text", text: outputText }] : []
161
+ }
162
+ ]);
163
+ const usageAttributes = extractGenAiUsageAttributes(message);
164
+ const endAttributes = {
165
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
166
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
167
+ "gen_ai.request.model": params.modelId,
168
+ ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
169
+ ...usageAttributes,
170
+ ...message.stopReason ? { "gen_ai.response.finish_reasons": [message.stopReason] } : {},
171
+ ...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
172
+ };
173
+ setSpanAttributes(endAttributes);
174
+ if (message.stopReason === "error") {
175
+ const providerMessage = message.errorMessage?.trim() || "Unknown provider error";
176
+ logWarn(
177
+ "ai_completion_provider_error",
178
+ {},
179
+ {
180
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
181
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
182
+ "gen_ai.request.model": params.modelId,
183
+ "error.message": providerMessage
184
+ },
185
+ "AI completion returned provider error"
186
+ );
187
+ throw new Error(`AI provider error: ${providerMessage}`);
188
+ }
189
+ return {
190
+ message,
191
+ text: outputText
192
+ };
193
+ }
194
+ async function completeObject(params) {
195
+ const startedAt = Date.now();
196
+ let text = "";
197
+ try {
198
+ ({ text } = await completeText({
199
+ modelId: params.modelId,
200
+ system: params.system,
201
+ thinkingLevel: params.thinkingLevel,
202
+ temperature: params.temperature,
203
+ maxTokens: params.maxTokens,
204
+ signal: params.signal,
205
+ metadata: params.metadata,
206
+ messages: [
207
+ {
208
+ role: "user",
209
+ content: params.prompt,
210
+ timestamp: Date.now()
211
+ }
212
+ ]
213
+ }));
214
+ } catch (error) {
215
+ logException(
216
+ error,
217
+ "ai_completion_failed",
218
+ {},
219
+ {
220
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
221
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
222
+ "gen_ai.request.model": params.modelId,
223
+ "app.ai.duration_ms": Date.now() - startedAt
224
+ },
225
+ "AI object completion failed"
226
+ );
227
+ throw error;
228
+ }
229
+ const candidate = parseJsonCandidate(text);
230
+ const parsed = params.schema.safeParse(candidate);
231
+ if (!parsed.success) {
232
+ const preview = text.length > 400 ? `${text.slice(0, 400)}...` : text;
233
+ logWarn(
234
+ "ai_completion_schema_parse_failed",
235
+ {},
236
+ {
237
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
238
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
239
+ "gen_ai.request.model": params.modelId,
240
+ "app.ai.duration_ms": Date.now() - startedAt,
241
+ "app.ai.response_preview": preview
242
+ },
243
+ "AI object completion schema parse failed"
244
+ );
245
+ throw new Error(
246
+ `Model did not return valid JSON for schema: ${parsed.error.message}. Raw response: ${preview}`
247
+ );
248
+ }
249
+ return {
250
+ object: parsed.data,
251
+ text
252
+ };
253
+ }
254
+
20
255
  // src/chat/config.ts
21
256
  var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
22
257
  var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
@@ -79,15 +314,26 @@ function parseLoadingMessages(rawValue) {
79
314
  return value.trim();
80
315
  });
81
316
  }
317
+ var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
318
+ var DEFAULT_FAST_MODEL_ID = getModel(
319
+ "vercel-ai-gateway",
320
+ "openai/gpt-5.4-mini"
321
+ ).id;
322
+ function validateGatewayModelId(raw) {
323
+ const trimmed = toOptionalTrimmed(raw);
324
+ if (trimmed === void 0) return void 0;
325
+ resolveGatewayModel(trimmed);
326
+ return trimmed;
327
+ }
82
328
  function readBotConfig(env) {
83
329
  const functionMaxDurationSeconds = resolveFunctionMaxDurationSeconds(env);
84
330
  const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(functionMaxDurationSeconds);
85
331
  return {
86
332
  userName: env.JUNIOR_BOT_NAME ?? "junior",
87
- modelId: env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
88
- fastModelId: env.AI_FAST_MODEL ?? env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
333
+ modelId: validateGatewayModelId(env.AI_MODEL) ?? DEFAULT_MODEL_ID,
334
+ fastModelId: validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID,
89
335
  loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
90
- visionModelId: toOptionalTrimmed(env.AI_VISION_MODEL),
336
+ visionModelId: validateGatewayModelId(env.AI_VISION_MODEL),
91
337
  turnTimeoutMs: parseAgentTurnTimeoutMs(
92
338
  env.AGENT_TURN_TIMEOUT_MS,
93
339
  maxTurnTimeoutMs
@@ -842,7 +1088,13 @@ function isSnapshotMissingError(error) {
842
1088
  }
843
1089
 
844
1090
  export {
845
- toOptionalTrimmed,
1091
+ GEN_AI_PROVIDER_NAME,
1092
+ MISSING_GATEWAY_CREDENTIALS_ERROR,
1093
+ getGatewayApiKey,
1094
+ getPiGatewayApiKeyOverride,
1095
+ resolveGatewayModel,
1096
+ completeText,
1097
+ completeObject,
846
1098
  botConfig,
847
1099
  getSlackBotToken,
848
1100
  getSlackSigningSecret,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-4PVJHUEV.js";
4
+ } from "../chunk-LEYD42MR.js";
5
5
  import {
6
6
  getPluginProviders,
7
7
  getPluginRuntimeDependencies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.27.2",
3
+ "version": "0.29.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,8 +25,8 @@
25
25
  "@chat-adapter/state-memory": "4.26.0",
26
26
  "@chat-adapter/state-redis": "4.26.0",
27
27
  "@logtape/logtape": "^2.0.5",
28
- "@mariozechner/pi-agent-core": "0.59.0",
29
- "@mariozechner/pi-ai": "0.59.0",
28
+ "@mariozechner/pi-agent-core": "0.68.1",
29
+ "@mariozechner/pi-ai": "0.68.1",
30
30
  "@modelcontextprotocol/sdk": "1.29.0",
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "@slack/web-api": "^7.15.1",