@sentry/junior 0.28.0 → 0.30.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.
@@ -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 ?? "openai/gpt-5.4",
88
- fastModelId: env.AI_FAST_MODEL ?? env.AI_MODEL ?? "openai/gpt-5.4-mini",
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-375D5V4U.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.28.0",
3
+ "version": "0.30.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",