@sentry/junior 0.74.1 → 0.76.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.
Files changed (121) hide show
  1. package/README.md +1 -1
  2. package/bin/junior.mjs +4 -66
  3. package/dist/agent-hooks-ZOE7RIED.js +37 -0
  4. package/dist/api-reference.d.ts +3 -1
  5. package/dist/app.js +5516 -5422
  6. package/dist/build/copy-build-content.d.ts +1 -1
  7. package/dist/build/virtual-config.d.ts +2 -2
  8. package/dist/chat/agent-dispatch/context.d.ts +2 -3
  9. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  10. package/dist/chat/agent-dispatch/types.d.ts +2 -1
  11. package/dist/chat/config.d.ts +3 -0
  12. package/dist/chat/credentials/state-adapter-token-store.d.ts +2 -0
  13. package/dist/chat/credentials/subject.d.ts +3 -3
  14. package/dist/chat/credentials/user-token-store.d.ts +17 -12
  15. package/dist/chat/db.d.ts +8 -0
  16. package/dist/chat/mcp/auth-store.d.ts +2 -1
  17. package/dist/chat/mcp/oauth.d.ts +2 -1
  18. package/dist/chat/oauth-flow.d.ts +3 -1
  19. package/dist/chat/pi/client.d.ts +15 -7
  20. package/dist/chat/plugins/agent-hooks.d.ts +20 -13
  21. package/dist/chat/plugins/auth/oauth-request.d.ts +11 -7
  22. package/dist/chat/plugins/credential-hooks.d.ts +6 -6
  23. package/dist/chat/plugins/logging.d.ts +2 -2
  24. package/dist/chat/plugins/model.d.ts +9 -0
  25. package/dist/chat/plugins/package-discovery.d.ts +2 -1
  26. package/dist/chat/plugins/prompt.d.ts +5 -0
  27. package/dist/chat/plugins/registry.d.ts +4 -0
  28. package/dist/chat/plugins/state.d.ts +3 -5
  29. package/dist/chat/plugins/task-callback.d.ts +5 -0
  30. package/dist/chat/plugins/task-message.d.ts +23 -0
  31. package/dist/chat/plugins/task-queue.d.ts +5 -0
  32. package/dist/chat/plugins/task-runner.d.ts +12 -0
  33. package/dist/chat/plugins/task-signing.d.ts +31 -0
  34. package/dist/chat/plugins/types.d.ts +1 -0
  35. package/dist/chat/plugins/validation.d.ts +5 -0
  36. package/dist/chat/prompt.d.ts +15 -1
  37. package/dist/chat/requester.d.ts +6 -5
  38. package/dist/chat/respond-helpers.d.ts +2 -0
  39. package/dist/chat/respond.d.ts +13 -2
  40. package/dist/chat/runtime/agent-continue-runner.d.ts +4 -0
  41. package/dist/chat/runtime/reply-executor.d.ts +5 -1
  42. package/dist/chat/runtime/slack-resume.d.ts +10 -2
  43. package/dist/chat/runtime/slack-runtime.d.ts +6 -1
  44. package/dist/chat/sandbox/egress-credentials.d.ts +8 -8
  45. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  46. package/dist/chat/sentry.d.ts +1 -0
  47. package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -1
  48. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -1
  49. package/dist/chat/services/subscribed-decision.d.ts +2 -2
  50. package/dist/chat/services/turn-session-record.d.ts +11 -7
  51. package/dist/chat/sql/db.d.ts +3 -0
  52. package/dist/chat/sql/executor.d.ts +7 -0
  53. package/dist/chat/sql/neon.d.ts +2 -4
  54. package/dist/chat/sql/postgres.d.ts +6 -0
  55. package/dist/chat/state/turn-session.d.ts +8 -5
  56. package/dist/chat/task-execution/state.d.ts +7 -2
  57. package/dist/chat/task-execution/worker.d.ts +1 -1
  58. package/dist/chat/tools/agent-tools.d.ts +9 -2
  59. package/dist/chat/tools/slack/context.d.ts +2 -2
  60. package/dist/chat/tools/types.d.ts +7 -4
  61. package/dist/chat/vercel-queue-client.d.ts +3 -0
  62. package/dist/{chunk-YOHFWWBV.js → chunk-2ECJXSVQ.js} +5 -107
  63. package/dist/{chunk-OR6NQJ5E.js → chunk-4SCWV7TJ.js} +3 -3
  64. package/dist/chunk-4UO6FK4G.js +64 -0
  65. package/dist/chunk-56TBVRJG.js +115 -0
  66. package/dist/{chunk-3BYAPS6B.js → chunk-EJN6G5A2.js} +17 -11
  67. package/dist/{chunk-SQGMG7OD.js → chunk-HHDUKWVG.js} +508 -149
  68. package/dist/{chunk-6UP2Z2RZ.js → chunk-JBASI5VV.js} +7 -7
  69. package/dist/chunk-KNFROR7R.js +127 -0
  70. package/dist/{chunk-HYHKTFG2.js → chunk-KOIMO7S3.js} +186 -910
  71. package/dist/chunk-MLKGABMK.js +9 -0
  72. package/dist/chunk-NFTMTIP3.js +964 -0
  73. package/dist/chunk-NYKJ3KON.js +1082 -0
  74. package/dist/{chunk-SJHUF3DP.js → chunk-OJ53FYVG.js} +2 -10
  75. package/dist/{chunk-KVZL5NZS.js → chunk-Q3XNY442.js} +17 -7
  76. package/dist/{chunk-YRDS7VKO.js → chunk-Q6XFTRV5.js} +2 -2
  77. package/dist/chunk-R6Z5XWY3.js +1076 -0
  78. package/dist/chunk-RV5RYIJW.js +56 -0
  79. package/dist/chunk-SG5WAA7H.js +132 -0
  80. package/dist/chunk-ST6YNAXG.js +54 -0
  81. package/dist/{chunk-GM7HTXYC.js → chunk-T77LUIX3.js} +148 -151
  82. package/dist/{chunk-CYUI7JU5.js → chunk-VALUBQ7R.js} +22 -30
  83. package/dist/chunk-XBBC6W45.js +71 -0
  84. package/dist/chunk-Y2CM7HXH.js +111 -0
  85. package/dist/{chunk-F6HWCPOC.js → chunk-Y5OFBCBZ.js} +1 -1
  86. package/dist/{chunk-M4FLLXXD.js → chunk-Z4CIQ3EB.js} +5 -1
  87. package/dist/{chunk-7Q5YOUUT.js → chunk-ZLMBNBUG.js} +146 -52
  88. package/dist/{chunk-2LUZA3LY.js → chunk-ZQB37HUX.js} +11 -11
  89. package/dist/cli/chat.js +87 -8
  90. package/dist/cli/check.js +8 -7
  91. package/dist/cli/env.js +4 -53
  92. package/dist/cli/init.js +6 -1
  93. package/dist/cli/main.js +84 -0
  94. package/dist/cli/plugins.js +244 -0
  95. package/dist/cli/run.js +5 -52
  96. package/dist/cli/snapshot-warmup.js +12 -11
  97. package/dist/cli/upgrade.js +385 -26
  98. package/dist/db-7A7PFRGL.js +17 -0
  99. package/dist/deployment.d.ts +1 -0
  100. package/dist/handlers/sandbox-egress-route.d.ts +4 -0
  101. package/dist/handlers/slack-webhook.d.ts +4 -0
  102. package/dist/handlers/webhooks.d.ts +6 -13
  103. package/dist/instrumentation.js +14 -18
  104. package/dist/nitro.d.ts +1 -1
  105. package/dist/nitro.js +67 -101
  106. package/dist/plugin-module.d.ts +21 -0
  107. package/dist/plugins-PZMDS7AT.js +15 -0
  108. package/dist/plugins.d.ts +9 -5
  109. package/dist/registry-OIPAJU2O.js +46 -0
  110. package/dist/reporting/conversations.d.ts +3 -3
  111. package/dist/reporting.d.ts +6 -5
  112. package/dist/reporting.js +42 -28
  113. package/dist/{runner-27NP2TEO.js → runner-KPLNHDCV.js} +77 -19
  114. package/dist/sentry-4CP5NNQ5.js +31 -0
  115. package/dist/validation-SLA6IGF7.js +15 -0
  116. package/dist/vercel.js +1 -1
  117. package/package.json +14 -11
  118. package/dist/chat/conversations/configured.d.ts +0 -5
  119. package/dist/chat/conversations/state.d.ts +0 -4
  120. package/dist/chunk-2KG3PWR4.js +0 -17
  121. package/dist/chunk-JL2SLRAT.js +0 -1970
@@ -0,0 +1,56 @@
1
+ // src/cli/env.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ function envFileNames(nodeEnv) {
5
+ return [
6
+ `.env.${nodeEnv}.local`,
7
+ ...nodeEnv === "test" ? [] : [".env.local"],
8
+ `.env.${nodeEnv}`,
9
+ ".env",
10
+ ".env.example"
11
+ ];
12
+ }
13
+ function hasEnvRootMarker(dir) {
14
+ return fs.existsSync(path.join(dir, "package.json")) || fs.existsSync(path.join(dir, "pnpm-workspace.yaml"));
15
+ }
16
+ function resolveCliEnvRoots(cwd) {
17
+ const roots = [];
18
+ const seen = /* @__PURE__ */ new Set();
19
+ const addRoot = (candidate) => {
20
+ const resolved = path.resolve(candidate);
21
+ if (seen.has(resolved)) {
22
+ return;
23
+ }
24
+ seen.add(resolved);
25
+ roots.push(resolved);
26
+ };
27
+ let current = path.resolve(cwd);
28
+ addRoot(current);
29
+ while (true) {
30
+ if (hasEnvRootMarker(current)) {
31
+ addRoot(current);
32
+ }
33
+ const parent = path.dirname(current);
34
+ if (parent === current) {
35
+ break;
36
+ }
37
+ current = parent;
38
+ }
39
+ return roots;
40
+ }
41
+ function loadCliEnvFiles(cwd = process.cwd()) {
42
+ const nodeEnv = process.env.NODE_ENV ?? "development";
43
+ for (const root of resolveCliEnvRoots(cwd)) {
44
+ for (const envFile of envFileNames(nodeEnv)) {
45
+ const absolutePath = path.join(root, envFile);
46
+ if (!fs.existsSync(absolutePath)) {
47
+ continue;
48
+ }
49
+ process.loadEnvFile(absolutePath);
50
+ }
51
+ }
52
+ }
53
+
54
+ export {
55
+ loadCliEnvFiles
56
+ };
@@ -0,0 +1,132 @@
1
+ // src/plugins.ts
2
+ function cloneManifests(manifests) {
3
+ return manifests ? structuredClone(manifests) : void 0;
4
+ }
5
+ function cloneInlineManifests(registrations) {
6
+ const inlineManifests = registrations.flatMap(
7
+ (plugin) => plugin.manifest ? [
8
+ {
9
+ manifest: {
10
+ ...structuredClone(plugin.manifest),
11
+ capabilities: plugin.manifest.capabilities?.map(
12
+ (capability) => capability.includes(".") ? capability : `${plugin.manifest.name}.${capability}`
13
+ ) ?? [],
14
+ configKeys: plugin.manifest.configKeys?.map(
15
+ (key) => key.includes(".") ? key : `${plugin.manifest.name}.${key}`
16
+ ) ?? [],
17
+ ...plugin.manifest.target ? {
18
+ target: {
19
+ ...plugin.manifest.target,
20
+ configKey: plugin.manifest.target.configKey.includes(".") ? plugin.manifest.target.configKey : `${plugin.manifest.name}.${plugin.manifest.target.configKey}`
21
+ }
22
+ } : {}
23
+ },
24
+ ...plugin.packageName ? { packageName: plugin.packageName } : {}
25
+ }
26
+ ] : []
27
+ );
28
+ return inlineManifests.length > 0 ? inlineManifests : void 0;
29
+ }
30
+ function assertUniquePluginNames(registrations) {
31
+ const seen = /* @__PURE__ */ new Set();
32
+ for (const plugin of registrations) {
33
+ const name = plugin.manifest.name;
34
+ if (seen.has(name)) {
35
+ throw new Error(`Duplicate plugin registration name "${name}"`);
36
+ }
37
+ seen.add(name);
38
+ }
39
+ }
40
+ function assertUniquePackageNames(packageNames) {
41
+ const seen = /* @__PURE__ */ new Set();
42
+ for (const packageName of packageNames) {
43
+ if (seen.has(packageName)) {
44
+ throw new Error(`Duplicate plugin package name "${packageName}"`);
45
+ }
46
+ seen.add(packageName);
47
+ }
48
+ }
49
+ function normalizePluginInput(input) {
50
+ if (typeof input === "string") {
51
+ return { packageName: input };
52
+ }
53
+ return { registration: input };
54
+ }
55
+ function defineJuniorPlugins(inputs, options = {}) {
56
+ const normalized = inputs.map(normalizePluginInput);
57
+ const packageNames = normalized.flatMap(
58
+ (input) => input.packageName ? [input.packageName] : []
59
+ );
60
+ const registrations = normalized.flatMap(
61
+ (input) => input.registration ? [input.registration] : []
62
+ );
63
+ assertUniquePackageNames(packageNames);
64
+ assertUniquePluginNames(registrations);
65
+ const manifests = cloneManifests(options.manifests);
66
+ return {
67
+ packageNames,
68
+ registrations: registrations.map((plugin) => ({ ...plugin })),
69
+ ...manifests ? { manifests } : {}
70
+ };
71
+ }
72
+ function pluginCatalogConfigFromPluginSet(pluginSet) {
73
+ if (!pluginSet) {
74
+ return void 0;
75
+ }
76
+ const packages = [
77
+ .../* @__PURE__ */ new Set([
78
+ ...pluginSet.packageNames,
79
+ ...pluginSet.registrations.flatMap(
80
+ (plugin) => plugin.packageName ? [plugin.packageName] : []
81
+ )
82
+ ])
83
+ ];
84
+ const manifests = cloneManifests(pluginSet.manifests);
85
+ const inlineManifests = cloneInlineManifests(pluginSet.registrations);
86
+ if (packages.length === 0 && !manifests && !inlineManifests) {
87
+ return void 0;
88
+ }
89
+ return {
90
+ ...inlineManifests ? { inlineManifests } : {},
91
+ ...packages.length > 0 ? { packages } : {},
92
+ ...manifests ? { manifests } : {}
93
+ };
94
+ }
95
+ function readEnvPluginPackages(env = process.env) {
96
+ const value = env.JUNIOR_PLUGIN_PACKAGES;
97
+ if (!value) {
98
+ return void 0;
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(value);
103
+ } catch (error) {
104
+ throw new Error("JUNIOR_PLUGIN_PACKAGES must be valid JSON", {
105
+ cause: error
106
+ });
107
+ }
108
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string" || !item.trim())) {
109
+ throw new Error(
110
+ "JUNIOR_PLUGIN_PACKAGES must be a JSON array of package names"
111
+ );
112
+ }
113
+ return parsed;
114
+ }
115
+ function pluginCatalogConfigFromEnv(env = process.env) {
116
+ const packages = readEnvPluginPackages(env);
117
+ return packages ? { packages } : void 0;
118
+ }
119
+ function pluginRuntimeRegistrationsFromPluginSet(pluginSet) {
120
+ return pluginSet?.registrations.filter((plugin) => plugin.hooks || plugin.tasks) ?? [];
121
+ }
122
+ function pluginCliRegistrationsFromPluginSet(pluginSet) {
123
+ return pluginSet?.registrations.filter((plugin) => plugin.cli) ?? [];
124
+ }
125
+
126
+ export {
127
+ defineJuniorPlugins,
128
+ pluginCatalogConfigFromPluginSet,
129
+ pluginCatalogConfigFromEnv,
130
+ pluginRuntimeRegistrationsFromPluginSet,
131
+ pluginCliRegistrationsFromPluginSet
132
+ };
@@ -0,0 +1,54 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-MLKGABMK.js";
4
+
5
+ // src/chat/sentry.ts
6
+ var sentry_exports = {};
7
+ __export(sentry_exports, {
8
+ captureException: () => captureException,
9
+ continueTrace: () => continueTrace,
10
+ flush: () => flush,
11
+ getClient: () => getClient,
12
+ getGlobalScope: () => getGlobalScope,
13
+ init: () => init,
14
+ setTag: () => setTag,
15
+ setUser: () => setUser,
16
+ startInactiveSpan: () => startInactiveSpan,
17
+ startSpan: () => startSpan,
18
+ vercelAIIntegration: () => vercelAIIntegration,
19
+ withActiveSpan: () => withActiveSpan,
20
+ withScope: () => withScope
21
+ });
22
+ import {
23
+ captureException,
24
+ continueTrace,
25
+ flush,
26
+ getClient,
27
+ getGlobalScope,
28
+ init,
29
+ setTag,
30
+ setUser,
31
+ startInactiveSpan,
32
+ startSpan,
33
+ vercelAIIntegration,
34
+ withActiveSpan,
35
+ withScope
36
+ } from "@sentry/node";
37
+ export * from "@sentry/node";
38
+
39
+ export {
40
+ sentry_exports,
41
+ captureException,
42
+ continueTrace,
43
+ flush,
44
+ getClient,
45
+ getGlobalScope,
46
+ init,
47
+ setTag,
48
+ setUser,
49
+ startInactiveSpan,
50
+ startSpan,
51
+ vercelAIIntegration,
52
+ withActiveSpan,
53
+ withScope
54
+ };
@@ -6,7 +6,7 @@ import {
6
6
  setSpanAttributes,
7
7
  toOptionalString,
8
8
  withSpan
9
- } from "./chunk-3BYAPS6B.js";
9
+ } from "./chunk-EJN6G5A2.js";
10
10
 
11
11
  // src/chat/turn-context-tag.ts
12
12
  var TURN_CONTEXT_TAG = "runtime-turn-context";
@@ -166,47 +166,6 @@ function getUserMessageContent(message) {
166
166
  function isRuntimeTurnContextPart(part, marker) {
167
167
  return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.startsWith(marker);
168
168
  }
169
- function prependRuntimeTurnContext(message, turnContextPrompt) {
170
- const content = getUserMessageContent(message);
171
- if (!content) {
172
- return void 0;
173
- }
174
- const contextIndex = content.findIndex(
175
- (part) => isRuntimeTurnContextPart(part, RUNTIME_TURN_CONTEXT_START)
176
- );
177
- if (contextIndex >= 0) {
178
- return void 0;
179
- }
180
- return {
181
- ...message,
182
- content: [{ type: "text", text: turnContextPrompt }, ...content]
183
- };
184
- }
185
- function prependMissingRuntimeTurnContext(messages, turnContextPrompt) {
186
- if (hasRuntimeTurnContext(messages)) {
187
- return messages;
188
- }
189
- for (let index = messages.length - 1; index >= 0; index -= 1) {
190
- const updated = prependRuntimeTurnContext(
191
- messages[index],
192
- turnContextPrompt
193
- );
194
- if (!updated) {
195
- continue;
196
- }
197
- const nextMessages = [...messages];
198
- nextMessages[index] = updated;
199
- return nextMessages;
200
- }
201
- return [
202
- ...messages,
203
- {
204
- role: "user",
205
- content: [{ type: "text", text: turnContextPrompt }],
206
- timestamp: Date.now()
207
- }
208
- ];
209
- }
210
169
  function hasRuntimeTurnContext(messages) {
211
170
  return messages.some(
212
171
  (message) => getUserMessageContent(message)?.some(
@@ -287,6 +246,8 @@ import {
287
246
  getModels,
288
247
  registerApiProvider
289
248
  } from "@earendil-works/pi-ai";
249
+ import { createGatewayProvider } from "@ai-sdk/gateway";
250
+ import { embedMany, generateObject } from "ai";
290
251
  import {
291
252
  streamAnthropic,
292
253
  streamSimpleAnthropic
@@ -550,76 +511,17 @@ var GEN_AI_PROVIDER_NAME = GATEWAY_PROVIDER;
550
511
  var GEN_AI_SERVER_ADDRESS = "ai-gateway.vercel.sh";
551
512
  var GEN_AI_SERVER_PORT = 443;
552
513
  var GEN_AI_OPERATION_CHAT = "chat";
514
+ var GEN_AI_OPERATION_EMBEDDINGS = "embeddings";
553
515
  var MISSING_GATEWAY_CREDENTIALS_ERROR = "Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)";
554
516
  function getGatewayApiKey() {
555
517
  return toOptionalTrimmed(getEnvApiKey("vercel-ai-gateway")) ?? toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
556
518
  }
557
- function getPiGatewayApiKeyOverride() {
558
- return toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN);
519
+ function getPiGatewayApiKey() {
520
+ return getGatewayApiKey();
559
521
  }
560
522
  function extractText(message) {
561
523
  return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
562
524
  }
563
- function parseJsonCandidate2(text) {
564
- const trimmed = text.trim();
565
- if (!trimmed) return void 0;
566
- try {
567
- return JSON.parse(trimmed);
568
- } catch {
569
- const fencedBlocks = [
570
- ...trimmed.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)
571
- ];
572
- for (const block of fencedBlocks) {
573
- try {
574
- return JSON.parse(block[1]);
575
- } catch {
576
- }
577
- }
578
- const openBraceIndex = trimmed.indexOf("{");
579
- if (openBraceIndex >= 0) {
580
- let depth = 0;
581
- let inString = false;
582
- let escaped = false;
583
- for (let index = openBraceIndex; index < trimmed.length; index += 1) {
584
- const char = trimmed[index];
585
- if (inString) {
586
- if (escaped) {
587
- escaped = false;
588
- continue;
589
- }
590
- if (char === "\\") {
591
- escaped = true;
592
- continue;
593
- }
594
- if (char === '"') {
595
- inString = false;
596
- }
597
- continue;
598
- }
599
- if (char === '"') {
600
- inString = true;
601
- continue;
602
- }
603
- if (char === "{") {
604
- depth += 1;
605
- continue;
606
- }
607
- if (char === "}") {
608
- depth -= 1;
609
- if (depth === 0) {
610
- const slice = trimmed.slice(openBraceIndex, index + 1);
611
- try {
612
- return JSON.parse(slice);
613
- } catch {
614
- break;
615
- }
616
- }
617
- }
618
- }
619
- }
620
- return void 0;
621
- }
622
- }
623
525
  function resolveGatewayModel(modelId) {
624
526
  const matched = getModels(GATEWAY_PROVIDER).find(
625
527
  (model) => model.id === modelId
@@ -631,7 +533,8 @@ function resolveGatewayModel(modelId) {
631
533
  }
632
534
  async function completeText(params) {
633
535
  const model = resolveGatewayModel(params.modelId);
634
- const apiKey = getPiGatewayApiKeyOverride();
536
+ const apiKey = getPiGatewayApiKey();
537
+ const authMode = toOptionalTrimmed(process.env.AI_GATEWAY_API_KEY) ? "api_key" : toOptionalTrimmed(process.env.VERCEL_OIDC_TOKEN) ? "oidc" : "api_key";
635
538
  const privacy = resolveConversationPrivacy({
636
539
  channelId: typeof params.metadata?.channelId === "string" ? params.metadata.channelId : void 0,
637
540
  conversationId: typeof params.metadata?.conversationId === "string" ? params.metadata.conversationId : typeof params.metadata?.threadId === "string" ? params.metadata.threadId : void 0
@@ -660,7 +563,7 @@ async function completeText(params) {
660
563
  ...params.system ? { "app.ai.system_instructions.content_chars": params.system.length } : {},
661
564
  ...systemInstructionsAttribute ? { "gen_ai.system_instructions": systemInstructionsAttribute } : {},
662
565
  ...requestMessagesAttribute ? { "gen_ai.input.messages": requestMessagesAttribute } : {},
663
- "app.ai.auth_mode": apiKey ? "oidc" : "api_key"
566
+ "app.ai.auth_mode": authMode
664
567
  };
665
568
  return withSpan(
666
569
  `${GEN_AI_OPERATION_CHAT} ${params.modelId}`,
@@ -750,30 +653,50 @@ function logContextFromMetadata(modelId, metadata) {
750
653
  };
751
654
  }
752
655
  async function completeObject(params) {
753
- let text = "";
656
+ const apiKey = getGatewayApiKey();
657
+ const provider = createGatewayProvider(apiKey ? { apiKey } : {});
754
658
  try {
755
- ({ text } = await completeText({
756
- modelId: params.modelId,
757
- system: params.system,
758
- thinkingLevel: params.thinkingLevel,
759
- temperature: params.temperature,
760
- maxTokens: params.maxTokens,
761
- signal: params.signal,
762
- metadata: params.metadata,
763
- messages: [
764
- {
765
- role: "user",
766
- content: params.prompt,
767
- timestamp: Date.now()
768
- }
769
- ]
770
- }));
659
+ const result = await withSpan(
660
+ `${GEN_AI_OPERATION_CHAT} ${params.modelId}`,
661
+ "gen_ai.chat",
662
+ logContextFromMetadata(params.modelId, params.metadata),
663
+ async () => await generateObject({
664
+ model: provider.chat(params.modelId),
665
+ schema: params.schema,
666
+ prompt: params.prompt,
667
+ ...params.system !== void 0 ? { system: params.system } : {},
668
+ ...params.temperature !== void 0 ? { temperature: params.temperature } : {},
669
+ ...params.maxTokens !== void 0 ? { maxOutputTokens: params.maxTokens } : {},
670
+ ...params.signal !== void 0 ? { abortSignal: params.signal } : {}
671
+ }),
672
+ {
673
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
674
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
675
+ "gen_ai.request.model": params.modelId,
676
+ "gen_ai.output.type": "json",
677
+ "server.address": GEN_AI_SERVER_ADDRESS,
678
+ "server.port": GEN_AI_SERVER_PORT,
679
+ ...params.thinkingLevel ? { "app.ai.reasoning_effort": params.thinkingLevel } : {}
680
+ }
681
+ );
682
+ setSpanAttributes({
683
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
684
+ "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
685
+ "gen_ai.request.model": params.modelId,
686
+ "gen_ai.output.type": "json",
687
+ "server.address": GEN_AI_SERVER_ADDRESS,
688
+ "server.port": GEN_AI_SERVER_PORT,
689
+ "gen_ai.response.finish_reasons": [result.finishReason],
690
+ ...extractGenAiUsageAttributes(result.usage)
691
+ });
692
+ return { object: result.object };
771
693
  } catch (error) {
772
- if (isProviderRetryError(error)) {
773
- throw error;
694
+ const providerError = createProviderError(error);
695
+ if (isProviderRetryError(providerError)) {
696
+ throw providerError;
774
697
  }
775
698
  logException(
776
- error,
699
+ providerError,
777
700
  "ai_completion_failed",
778
701
  {},
779
702
  {
@@ -783,31 +706,72 @@ async function completeObject(params) {
783
706
  },
784
707
  "AI object completion failed"
785
708
  );
786
- throw error;
709
+ throw providerError;
787
710
  }
788
- const candidate = parseJsonCandidate2(text);
789
- const parsed = params.schema.safeParse(candidate);
790
- if (!parsed.success) {
791
- const preview = text.length > 400 ? `${text.slice(0, 400)}...` : text;
792
- logWarn(
793
- "ai_completion_schema_parse_failed",
794
- {},
711
+ }
712
+ async function embedTexts(params) {
713
+ const texts = params.texts.map((text) => text.trim());
714
+ if (texts.length === 0 || texts.some((text) => text.length === 0)) {
715
+ throw new Error("Embedding text is required.");
716
+ }
717
+ const apiKey = getGatewayApiKey();
718
+ const provider = createGatewayProvider(apiKey ? { apiKey } : {});
719
+ try {
720
+ const result = await withSpan(
721
+ `${GEN_AI_OPERATION_EMBEDDINGS} ${params.modelId}`,
722
+ "gen_ai.embeddings",
723
+ logContextFromMetadata(params.modelId, params.metadata),
724
+ async () => await embedMany({
725
+ model: provider.embeddingModel(params.modelId),
726
+ values: texts,
727
+ ...params.signal !== void 0 ? { abortSignal: params.signal } : {}
728
+ }),
795
729
  {
796
730
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
797
- "gen_ai.operation.name": GEN_AI_OPERATION_CHAT,
731
+ "gen_ai.operation.name": GEN_AI_OPERATION_EMBEDDINGS,
798
732
  "gen_ai.request.model": params.modelId,
799
- "app.ai.response_preview": preview
800
- },
801
- "AI object completion schema parse failed"
733
+ "gen_ai.output.type": "embedding",
734
+ "server.address": GEN_AI_SERVER_ADDRESS,
735
+ "server.port": GEN_AI_SERVER_PORT
736
+ }
802
737
  );
803
- throw new Error(
804
- `Model did not return valid JSON for schema: ${parsed.error.message}. Raw response: ${preview}`
738
+ const dimensions = result.embeddings[0]?.length;
739
+ if (result.embeddings.length !== texts.length || !dimensions || !result.embeddings.every((embedding) => embedding.length === dimensions)) {
740
+ throw new Error("Embedding provider returned invalid vectors.");
741
+ }
742
+ setSpanAttributes({
743
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
744
+ "gen_ai.operation.name": GEN_AI_OPERATION_EMBEDDINGS,
745
+ "gen_ai.request.model": params.modelId,
746
+ "gen_ai.output.type": "embedding",
747
+ "server.address": GEN_AI_SERVER_ADDRESS,
748
+ "server.port": GEN_AI_SERVER_PORT,
749
+ ...extractGenAiUsageAttributes(result.usage)
750
+ });
751
+ return {
752
+ dimensions,
753
+ model: params.modelId,
754
+ provider: GEN_AI_PROVIDER_NAME,
755
+ vectors: result.embeddings
756
+ };
757
+ } catch (error) {
758
+ const providerError = createProviderError(error);
759
+ if (isProviderRetryError(providerError)) {
760
+ throw providerError;
761
+ }
762
+ logException(
763
+ providerError,
764
+ "ai_embeddings_failed",
765
+ {},
766
+ {
767
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
768
+ "gen_ai.operation.name": GEN_AI_OPERATION_EMBEDDINGS,
769
+ "gen_ai.request.model": params.modelId
770
+ },
771
+ "AI embeddings failed"
805
772
  );
773
+ throw providerError;
806
774
  }
807
- return {
808
- object: parsed.data,
809
- text
810
- };
811
775
  }
812
776
 
813
777
  // src/chat/slack/emoji.ts
@@ -926,7 +890,7 @@ function parseSlashCommand(rawValue) {
926
890
  }
927
891
  return command;
928
892
  }
929
- var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
893
+ var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.5").id;
930
894
  var DEFAULT_FAST_MODEL_ID = getModel(
931
895
  "vercel-ai-gateway",
932
896
  "openai/gpt-5.4-mini"
@@ -935,12 +899,16 @@ var DEFAULT_ADVISOR_MODEL_ID = getModel(
935
899
  "vercel-ai-gateway",
936
900
  "openai/gpt-5.5"
937
901
  ).id;
902
+ var DEFAULT_EMBEDDING_MODEL_ID = "openai/text-embedding-3-small";
938
903
  function validateGatewayModelId(raw) {
939
904
  const trimmed = toOptionalTrimmed(raw);
940
905
  if (trimmed === void 0) return void 0;
941
906
  resolveGatewayModel(trimmed);
942
907
  return trimmed;
943
908
  }
909
+ function validateEmbeddingModelId(raw) {
910
+ return toOptionalTrimmed(raw);
911
+ }
944
912
  function readAdvisorConfig(env) {
945
913
  return {
946
914
  modelId: validateGatewayModelId(env.AI_ADVISOR_MODEL) ?? DEFAULT_ADVISOR_MODEL_ID,
@@ -963,14 +931,17 @@ function parseReactionEmoji(envName, rawValue, defaultEmoji) {
963
931
  function readBotConfig(env) {
964
932
  const functionMaxDurationSeconds = resolveFunctionMaxDurationSeconds(env);
965
933
  const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(functionMaxDurationSeconds);
934
+ const modelId = validateGatewayModelId(env.AI_MODEL) ?? DEFAULT_MODEL_ID;
935
+ const fastModelId = validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID;
966
936
  return {
967
- userName: env.JUNIOR_BOT_NAME ?? "junior",
968
- modelId: validateGatewayModelId(env.AI_MODEL) ?? DEFAULT_MODEL_ID,
937
+ userName: toOptionalTrimmed(env.JUNIOR_BOT_NAME) ?? "junior",
938
+ modelId,
969
939
  modelContextWindowTokens: parseOptionalPositiveInteger(
970
940
  "AI_MODEL_CONTEXT_WINDOW_TOKENS",
971
941
  env.AI_MODEL_CONTEXT_WINDOW_TOKENS
972
942
  ),
973
- fastModelId: validateGatewayModelId(env.AI_FAST_MODEL ?? env.AI_MODEL) ?? DEFAULT_FAST_MODEL_ID,
943
+ fastModelId,
944
+ embeddingModelId: validateEmbeddingModelId(env.AI_EMBEDDING_MODEL) ?? DEFAULT_EMBEDDING_MODEL_ID,
974
945
  loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
975
946
  visionModelId: validateGatewayModelId(env.AI_VISION_MODEL),
976
947
  turnTimeoutMs: parseAgentTurnTimeoutMs(
@@ -983,12 +954,38 @@ function readBotConfig(env) {
983
954
  function readJuniorDatabaseUrl(env) {
984
955
  return toOptionalTrimmed(env.JUNIOR_DATABASE_URL) ?? toOptionalTrimmed(env.DATABASE_URL);
985
956
  }
957
+ function isLocalDatabaseUrl(databaseUrl) {
958
+ if (!databaseUrl) {
959
+ return false;
960
+ }
961
+ try {
962
+ const { hostname } = new URL(databaseUrl);
963
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
964
+ } catch {
965
+ return false;
966
+ }
967
+ }
968
+ function readSqlDriver(env, databaseUrl) {
969
+ const value = toOptionalTrimmed(env.JUNIOR_DATABASE_DRIVER);
970
+ if (value === void 0) {
971
+ if (isLocalDatabaseUrl(databaseUrl)) {
972
+ return "postgres";
973
+ }
974
+ return "neon";
975
+ }
976
+ if (value === "neon" || value === "postgres") {
977
+ return value;
978
+ }
979
+ throw new Error("JUNIOR_DATABASE_DRIVER must be postgres or neon");
980
+ }
986
981
  function readChatConfig(env = process.env) {
982
+ const databaseUrl = readJuniorDatabaseUrl(env);
987
983
  return {
988
984
  bot: readBotConfig(env),
989
985
  functionMaxDurationSeconds: resolveFunctionMaxDurationSeconds(env),
990
986
  sql: {
991
- databaseUrl: readJuniorDatabaseUrl(env)
987
+ databaseUrl,
988
+ driver: readSqlDriver(env, databaseUrl)
992
989
  },
993
990
  slack: {
994
991
  botToken: toOptionalTrimmed(env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(env.SLACK_BOT_USER_TOKEN),
@@ -1076,7 +1073,6 @@ export {
1076
1073
  isToolResultError,
1077
1074
  isAssistantMessage,
1078
1075
  getPiMessageRole,
1079
- prependMissingRuntimeTurnContext,
1080
1076
  hasRuntimeTurnContext,
1081
1077
  stripRuntimeTurnContext,
1082
1078
  extractAssistantText,
@@ -1090,10 +1086,11 @@ export {
1090
1086
  GEN_AI_SERVER_PORT,
1091
1087
  MISSING_GATEWAY_CREDENTIALS_ERROR,
1092
1088
  getGatewayApiKey,
1093
- getPiGatewayApiKeyOverride,
1089
+ getPiGatewayApiKey,
1094
1090
  resolveGatewayModel,
1095
1091
  completeText,
1096
1092
  completeObject,
1093
+ embedTexts,
1097
1094
  normalizeSlackEmojiName,
1098
1095
  FUNCTION_TIMEOUT_BUFFER_SECONDS,
1099
1096
  getChatConfig,