@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 +274 -303
- package/dist/{chunk-4PVJHUEV.js → chunk-LEYD42MR.js} +256 -4
- package/dist/cli/snapshot-warmup.js +1 -1
- package/package.json +3 -3
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
9941
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
10676
|
-
if (traceId) {
|
|
10644
|
+
if (args.thinkingLevel) {
|
|
10677
10645
|
items.push({
|
|
10678
|
-
label: "
|
|
10679
|
-
value:
|
|
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
|
-
|
|
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 =
|
|
12301
|
-
should_reply:
|
|
12302
|
-
should_unsubscribe:
|
|
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:
|
|
12306
|
-
reason:
|
|
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:
|
|
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
|
-
|
|
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 ??
|
|
88
|
-
fastModelId: env.AI_FAST_MODEL ?? env.AI_MODEL ??
|
|
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:
|
|
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
|
-
|
|
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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
29
|
-
"@mariozechner/pi-ai": "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",
|