@sentry/junior 0.68.0 → 0.69.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 +1464 -732
- package/dist/build/virtual-config.d.ts +2 -2
- package/dist/chat/agent-dispatch/heartbeat.d.ts +2 -2
- package/dist/chat/agent-dispatch/store.d.ts +4 -1
- package/dist/chat/agent-dispatch/types.d.ts +2 -4
- package/dist/chat/agent-dispatch/validation.d.ts +3 -2
- package/dist/chat/credentials/context.d.ts +49 -24
- package/dist/chat/credentials/user-token-store.d.ts +6 -0
- package/dist/chat/destination.d.ts +12 -0
- package/dist/chat/ingress/message-router.d.ts +1 -1
- package/dist/chat/mcp/auth-store.d.ts +2 -0
- package/dist/chat/mcp/oauth.d.ts +2 -0
- package/dist/chat/oauth-flow.d.ts +7 -0
- package/dist/chat/plugins/agent-hooks.d.ts +9 -9
- package/dist/chat/plugins/auth/auth-token-placeholder.d.ts +2 -2
- package/dist/chat/plugins/auth/oauth-request.d.ts +3 -1
- package/dist/chat/plugins/credential-hooks.d.ts +34 -0
- package/dist/chat/plugins/logging.d.ts +1 -1
- package/dist/chat/plugins/state.d.ts +1 -1
- package/dist/chat/plugins/types.d.ts +19 -23
- package/dist/chat/respond.d.ts +2 -0
- package/dist/chat/runtime/reply-executor.d.ts +3 -1
- package/dist/chat/runtime/slack-runtime.d.ts +8 -3
- package/dist/chat/sandbox/egress-credentials.d.ts +33 -0
- package/dist/chat/sandbox/egress-schemas.d.ts +105 -0
- package/dist/chat/sandbox/egress-session.d.ts +17 -17
- package/dist/chat/sandbox/sandbox.d.ts +3 -0
- package/dist/chat/sandbox/session.d.ts +1 -0
- package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -0
- package/dist/chat/services/pending-auth.d.ts +2 -0
- package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -0
- package/dist/chat/services/provider-retry.d.ts +13 -4
- package/dist/chat/services/timeout-resume.d.ts +2 -0
- package/dist/chat/services/turn-session-record.d.ts +6 -0
- package/dist/chat/slack/attachment-fetchers.d.ts +11 -0
- package/dist/chat/state/conversation.d.ts +1 -0
- package/dist/chat/state/turn-session.d.ts +4 -0
- package/dist/chat/task-execution/queue.d.ts +2 -0
- package/dist/chat/task-execution/store.d.ts +5 -0
- package/dist/chat/task-execution/vercel-callback.d.ts +4 -0
- package/dist/chat/task-execution/vercel-queue.d.ts +2 -0
- package/dist/chat/task-execution/worker.d.ts +4 -2
- package/dist/chat/tools/slack/context.d.ts +3 -0
- package/dist/chat/tools/types.d.ts +21 -2
- package/dist/chunk-76YMBKW7.js +326 -0
- package/dist/{chunk-PIVOJIUD.js → chunk-B5HKWWQB.js} +9 -5
- package/dist/chunk-BBXYXOJW.js +1858 -0
- package/dist/{chunk-V47RLIO2.js → chunk-GT67ZWZQ.js} +4 -4
- package/dist/{chunk-75UZ4JLC.js → chunk-IGLNC5H6.js} +21 -9
- package/dist/{chunk-EBVQXCD2.js → chunk-JS4HURDT.js} +362 -280
- package/dist/{chunk-UQQSW7QB.js → chunk-N3MORKTH.js} +74 -331
- package/dist/chunk-R62YWUNO.js +264 -0
- package/dist/{chunk-OIIXZOOC.js → chunk-UXG6TU2U.js} +311 -2015
- package/dist/cli/check.js +4 -4
- package/dist/cli/snapshot-warmup.js +5 -4
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +21 -19
- package/dist/plugins.d.ts +2 -2
- package/dist/reporting.d.ts +2 -2
- package/dist/reporting.js +13 -11
- package/package.json +6 -4
- package/dist/chat/plugins/auth/github-app-broker.d.ts +0 -4
- package/dist/chat/plugins/github-permissions.d.ts +0 -11
- package/dist/chat/queue/thread-message-dispatcher.d.ts +0 -33
- package/dist/chunk-KVZL5NZS.js +0 -519
|
@@ -1,1324 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
// src/chat/coerce.ts
|
|
11
|
-
function toOptionalString(value) {
|
|
12
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
13
|
-
}
|
|
14
|
-
function toOptionalNumber(value) {
|
|
15
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
16
|
-
}
|
|
17
|
-
function isRecord(value) {
|
|
18
|
-
return typeof value === "object" && value !== null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// src/chat/logging.ts
|
|
22
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
23
|
-
import path from "path";
|
|
24
|
-
import { styleText } from "util";
|
|
25
|
-
import {
|
|
26
|
-
ConfigError,
|
|
27
|
-
configureSync,
|
|
28
|
-
getConfig,
|
|
29
|
-
getLogger
|
|
30
|
-
} from "@logtape/logtape";
|
|
31
|
-
var MAX_STRING_VALUE = 1200;
|
|
32
|
-
var SECRETS_RE = [
|
|
33
|
-
/\b(sk-[A-Za-z0-9_-]{20,})\b/g,
|
|
34
|
-
/\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g,
|
|
35
|
-
/\bBearer\s+([A-Za-z0-9._\-+=]{20,})\b/gi,
|
|
36
|
-
/\b[A-Z0-9_]+(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\s*[=:]\s*([^\s"']{8,})/gi
|
|
37
|
-
];
|
|
38
|
-
var LEGACY_KEY_MAP = {
|
|
39
|
-
error: "exception.message",
|
|
40
|
-
"error.stack": "exception.stacktrace",
|
|
41
|
-
"gen_ai.system": "gen_ai.provider.name",
|
|
42
|
-
"gen_ai.request.messages": "gen_ai.input.messages",
|
|
43
|
-
"gen_ai.response.text": "gen_ai.output.messages",
|
|
44
|
-
"messaging.conversation.id": "messaging.message.conversation_id",
|
|
45
|
-
bytes: "file.size",
|
|
46
|
-
media_type: "app.file.mime_type",
|
|
47
|
-
skillDir: "file.path",
|
|
48
|
-
root: "file.directory",
|
|
49
|
-
originalLength: "app.output.original_length",
|
|
50
|
-
parsedLength: "app.output.parsed_length",
|
|
51
|
-
directiveMode: "app.output.directive_mode",
|
|
52
|
-
fileCount: "app.output.file_count",
|
|
53
|
-
attempt: "app.retry.attempt",
|
|
54
|
-
steps: "app.ai.steps",
|
|
55
|
-
toolCalls: "app.ai.tool_calls",
|
|
56
|
-
toolResults: "app.ai.tool_results",
|
|
57
|
-
finishReason: "gen_ai.response.finish_reasons",
|
|
58
|
-
sources: "app.ai.sources",
|
|
59
|
-
generatedFiles: "app.ai.generated_files",
|
|
60
|
-
resultFiles: "app.ai.result_files",
|
|
61
|
-
responseMessages: "app.ai.response_messages",
|
|
62
|
-
stepDiagnostics: "app.ai.step_diagnostics",
|
|
63
|
-
inferredSkill: "app.skill.name",
|
|
64
|
-
inferredScore: "app.skill.score"
|
|
65
|
-
};
|
|
66
|
-
function normalizeGenAiFinishReason(reason) {
|
|
67
|
-
return reason === "toolUse" ? "tool_use" : reason;
|
|
68
|
-
}
|
|
69
|
-
function normalizeGenAiFinishReasons(value) {
|
|
70
|
-
if (typeof value === "string" && value.trim()) {
|
|
71
|
-
return [normalizeGenAiFinishReason(value)];
|
|
72
|
-
}
|
|
73
|
-
if (!Array.isArray(value)) {
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
return value.map(
|
|
77
|
-
(reason) => typeof reason === "string" ? normalizeGenAiFinishReason(reason) : reason
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
var contextStorage = new AsyncLocalStorage();
|
|
81
|
-
var logRecordSinks = /* @__PURE__ */ new Set();
|
|
82
|
-
var LOGTAPE_BODY_KEY = "__logtape_body";
|
|
83
|
-
var ROOT_LOGGER_CATEGORY = ["junior"];
|
|
84
|
-
var CONSOLE_PRIORITY_KEYS = [
|
|
85
|
-
"gen_ai.conversation.id",
|
|
86
|
-
"event.name",
|
|
87
|
-
"app.log.source",
|
|
88
|
-
"exception.message",
|
|
89
|
-
"messaging.message.id",
|
|
90
|
-
"trace_id",
|
|
91
|
-
"span_id",
|
|
92
|
-
"messaging.message.conversation_id",
|
|
93
|
-
"messaging.destination.name",
|
|
94
|
-
"app.run.id",
|
|
95
|
-
"app.message.kind"
|
|
96
|
-
];
|
|
97
|
-
var CONSOLE_PRIORITY_INDEX = new Map(
|
|
98
|
-
CONSOLE_PRIORITY_KEYS.map((key, index) => [key, index])
|
|
99
|
-
);
|
|
100
|
-
var CONSOLE_ALWAYS_HIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
101
|
-
"gen_ai.agent.name",
|
|
102
|
-
"app.platform",
|
|
103
|
-
"enduser.id",
|
|
104
|
-
"enduser.pseudo.id",
|
|
105
|
-
"http.request.method",
|
|
106
|
-
"messaging.system",
|
|
107
|
-
"url.full",
|
|
108
|
-
"url.path",
|
|
109
|
-
"user_agent.original"
|
|
110
|
-
]);
|
|
111
|
-
var CONSOLE_DROP_WHEN_COUNTED_KEYS = /* @__PURE__ */ new Set([
|
|
112
|
-
"app.capability.names",
|
|
113
|
-
"app.capability.providers",
|
|
114
|
-
"app.config.keys"
|
|
115
|
-
]);
|
|
116
|
-
var CONSOLE_PREVIEW_KEYS = /* @__PURE__ */ new Set([
|
|
117
|
-
"gen_ai.input.messages",
|
|
118
|
-
"gen_ai.output.messages",
|
|
119
|
-
"gen_ai.tool.call.arguments",
|
|
120
|
-
"gen_ai.tool.call.result"
|
|
121
|
-
]);
|
|
122
|
-
var SENTRY_TAG_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
|
|
123
|
-
"app.platform",
|
|
124
|
-
"messaging.system",
|
|
125
|
-
"app.actor.type",
|
|
126
|
-
"gen_ai.agent.name",
|
|
127
|
-
"gen_ai.request.model",
|
|
128
|
-
"app.skill.name",
|
|
129
|
-
"http.request.method",
|
|
130
|
-
"url.path"
|
|
131
|
-
]);
|
|
132
|
-
function getSentryEnvironment() {
|
|
133
|
-
return (process.env.SENTRY_ENVIRONMENT ?? process.env.VERCEL_ENV ?? process.env.NODE_ENV ?? "").trim().toLowerCase();
|
|
134
|
-
}
|
|
135
|
-
function shouldSuppressInfoLog(level) {
|
|
136
|
-
return getSentryEnvironment() === "production" && level === "info";
|
|
137
|
-
}
|
|
138
|
-
function shouldEmitConsole(level) {
|
|
139
|
-
if (process.env.NODE_ENV === "test") {
|
|
140
|
-
return level === "error";
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
function isDevelopmentLoggingMode() {
|
|
145
|
-
if (process.env.NODE_ENV !== "development") {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
if (process.env.CI) {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
function shouldUseDevelopmentConsoleFormat() {
|
|
154
|
-
if (!isDevelopmentLoggingMode()) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
return process.env.JUNIOR_LOG_FORMAT?.trim().toLowerCase() !== "structured";
|
|
158
|
-
}
|
|
159
|
-
function shouldUsePrettyConsole(level) {
|
|
160
|
-
if (level === "warn" || level === "error") {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
return shouldUseDevelopmentConsoleFormat();
|
|
164
|
-
}
|
|
165
|
-
function shouldUseConsoleColor() {
|
|
166
|
-
if (!shouldUseDevelopmentConsoleFormat()) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
if (process.env.NO_COLOR) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
return process.env.FORCE_COLOR?.trim() === "1" || Boolean(process.stdout?.isTTY) || Boolean(process.stderr?.isTTY);
|
|
173
|
-
}
|
|
174
|
-
function formatConsoleTimestamp(timestamp) {
|
|
175
|
-
if (shouldUseDevelopmentConsoleFormat()) {
|
|
176
|
-
return timestamp.toTimeString().slice(0, 8);
|
|
177
|
-
}
|
|
178
|
-
return timestamp.toISOString();
|
|
179
|
-
}
|
|
180
|
-
function findNextBlankLineBoundary(input, start) {
|
|
181
|
-
const lfBoundary = input.indexOf("\n\n", start);
|
|
182
|
-
const crlfBoundary = input.indexOf("\r\n\r\n", start);
|
|
183
|
-
if (lfBoundary === -1 && crlfBoundary === -1) {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
if (lfBoundary === -1) {
|
|
187
|
-
return { start: crlfBoundary, end: crlfBoundary + 4 };
|
|
188
|
-
}
|
|
189
|
-
if (crlfBoundary === -1 || lfBoundary < crlfBoundary) {
|
|
190
|
-
return { start: lfBoundary, end: lfBoundary + 2 };
|
|
191
|
-
}
|
|
192
|
-
return { start: crlfBoundary, end: crlfBoundary + 4 };
|
|
193
|
-
}
|
|
194
|
-
function redactPrivateKeyBlocks(input) {
|
|
195
|
-
const beginPrefix = "-----BEGIN ";
|
|
196
|
-
const footerMarker = "-----";
|
|
197
|
-
let cursor = 0;
|
|
198
|
-
let output = "";
|
|
199
|
-
while (cursor < input.length) {
|
|
200
|
-
const begin = input.indexOf(beginPrefix, cursor);
|
|
201
|
-
if (begin === -1) {
|
|
202
|
-
output += input.slice(cursor);
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
const labelStart = begin + beginPrefix.length;
|
|
206
|
-
const labelEnd = input.indexOf(footerMarker, labelStart);
|
|
207
|
-
if (labelEnd === -1) {
|
|
208
|
-
output += input.slice(cursor);
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
const label = input.slice(labelStart, labelEnd);
|
|
212
|
-
if (!label.endsWith("PRIVATE KEY")) {
|
|
213
|
-
output += input.slice(cursor, labelEnd + footerMarker.length);
|
|
214
|
-
cursor = labelEnd + footerMarker.length;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
const header = input.slice(begin, labelEnd + footerMarker.length);
|
|
218
|
-
const footer = `-----END ${label}-----`;
|
|
219
|
-
const footerStart = input.indexOf(footer, labelEnd + footerMarker.length);
|
|
220
|
-
if (footerStart === -1) {
|
|
221
|
-
const resumeBoundary = findNextBlankLineBoundary(
|
|
222
|
-
input,
|
|
223
|
-
labelEnd + footerMarker.length
|
|
224
|
-
);
|
|
225
|
-
output += input.slice(cursor, begin);
|
|
226
|
-
output += `${header}
|
|
227
|
-
...redacted...`;
|
|
228
|
-
if (!resumeBoundary) {
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
output += input.slice(resumeBoundary.start, resumeBoundary.end);
|
|
232
|
-
cursor = resumeBoundary.end;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
output += input.slice(cursor, begin);
|
|
236
|
-
output += `${header}
|
|
237
|
-
...redacted...
|
|
238
|
-
${footer}`;
|
|
239
|
-
cursor = footerStart + footer.length;
|
|
240
|
-
}
|
|
241
|
-
return output;
|
|
242
|
-
}
|
|
243
|
-
function redactSecrets(input) {
|
|
244
|
-
let out = redactPrivateKeyBlocks(input);
|
|
245
|
-
for (const pattern of SECRETS_RE) {
|
|
246
|
-
out = out.replace(pattern, (full, token) => {
|
|
247
|
-
if (typeof token !== "string") {
|
|
248
|
-
return "***";
|
|
249
|
-
}
|
|
250
|
-
if (token.length < 12) {
|
|
251
|
-
return full.replace(token, "***");
|
|
252
|
-
}
|
|
253
|
-
return full.replace(token, `${token.slice(0, 4)}...${token.slice(-4)}`);
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return out;
|
|
257
|
-
}
|
|
258
|
-
function toSnakeCase(value) {
|
|
259
|
-
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
260
|
-
}
|
|
261
|
-
function isSemanticKey(key) {
|
|
262
|
-
return /^[a-z][a-z0-9_]*(\.[a-z0-9_][a-z0-9_-]*)+$/.test(key);
|
|
263
|
-
}
|
|
264
|
-
function normalizeAttributeKey(key) {
|
|
265
|
-
const mapped = LEGACY_KEY_MAP[key];
|
|
266
|
-
if (mapped) {
|
|
267
|
-
return mapped;
|
|
268
|
-
}
|
|
269
|
-
if (isSemanticKey(key)) {
|
|
270
|
-
return key;
|
|
271
|
-
}
|
|
272
|
-
if (key === "platform") return "app.platform";
|
|
273
|
-
if (key === "request.id") return "app.request.id";
|
|
274
|
-
const snake = toSnakeCase(key);
|
|
275
|
-
if (!snake) {
|
|
276
|
-
return "app.attribute";
|
|
277
|
-
}
|
|
278
|
-
return `app.${snake}`;
|
|
279
|
-
}
|
|
280
|
-
function sanitizePrimitive(value) {
|
|
281
|
-
if (value === null || value === void 0) return void 0;
|
|
282
|
-
if (typeof value === "string") {
|
|
283
|
-
const trimmed = value.trim();
|
|
284
|
-
if (!trimmed) return void 0;
|
|
285
|
-
const redacted = redactSecrets(trimmed);
|
|
286
|
-
return redacted.length > MAX_STRING_VALUE ? `${redacted.slice(0, MAX_STRING_VALUE)}...` : redacted;
|
|
287
|
-
}
|
|
288
|
-
if (typeof value === "number") {
|
|
289
|
-
return Number.isFinite(value) ? value : void 0;
|
|
290
|
-
}
|
|
291
|
-
if (typeof value === "boolean") return value;
|
|
292
|
-
if (value instanceof Error) {
|
|
293
|
-
return redactSecrets(value.message);
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
const json = JSON.stringify(value);
|
|
297
|
-
if (!json) return void 0;
|
|
298
|
-
const redacted = redactSecrets(json);
|
|
299
|
-
return redacted.length > MAX_STRING_VALUE ? `${redacted.slice(0, MAX_STRING_VALUE)}...` : redacted;
|
|
300
|
-
} catch {
|
|
301
|
-
return void 0;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function sanitizeValue(value) {
|
|
305
|
-
if (Array.isArray(value)) {
|
|
306
|
-
const sanitized = value.filter((entry) => typeof entry === "string").map((entry) => sanitizePrimitive(entry)).filter((entry) => typeof entry === "string");
|
|
307
|
-
return sanitized.length > 0 ? sanitized : void 0;
|
|
308
|
-
}
|
|
309
|
-
return sanitizePrimitive(value);
|
|
310
|
-
}
|
|
311
|
-
function contextToAttributes(context) {
|
|
312
|
-
const attributes = {
|
|
313
|
-
"gen_ai.conversation.id": context.conversationId,
|
|
314
|
-
"app.platform": context.platform,
|
|
315
|
-
"app.request.id": context.requestId,
|
|
316
|
-
"messaging.system": context.platform === "slack" ? "slack" : context.platform,
|
|
317
|
-
"messaging.message.conversation_id": context.slackThreadId,
|
|
318
|
-
"messaging.destination.name": context.slackChannelId,
|
|
319
|
-
"enduser.id": context.slackUserId,
|
|
320
|
-
"enduser.pseudo.id": context.slackUserName,
|
|
321
|
-
"app.run.id": context.runId,
|
|
322
|
-
"app.actor.type": context.actorType,
|
|
323
|
-
"app.actor.id": context.actorId,
|
|
324
|
-
"gen_ai.agent.name": context.assistantUserName,
|
|
325
|
-
"gen_ai.request.model": context.modelId,
|
|
326
|
-
"app.skill.name": context.skillName,
|
|
327
|
-
"http.request.method": context.httpMethod,
|
|
328
|
-
"url.path": context.httpPath,
|
|
329
|
-
"url.full": context.urlFull,
|
|
330
|
-
"user_agent.original": context.userAgent
|
|
331
|
-
};
|
|
332
|
-
const normalized = {};
|
|
333
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
334
|
-
const sanitized = sanitizeValue(value);
|
|
335
|
-
if (sanitized !== void 0) normalized[key] = sanitized;
|
|
336
|
-
}
|
|
337
|
-
return normalized;
|
|
338
|
-
}
|
|
339
|
-
function getTraceCorrelationAttributes() {
|
|
340
|
-
const sentry = sentry_exports;
|
|
341
|
-
if (typeof sentry.getActiveSpan !== "function" || typeof sentry.spanToJSON !== "function") {
|
|
342
|
-
return {};
|
|
343
|
-
}
|
|
344
|
-
try {
|
|
345
|
-
const span = sentry.getActiveSpan();
|
|
346
|
-
if (!span) return {};
|
|
347
|
-
const json = sentry.spanToJSON(span);
|
|
348
|
-
const attrs = {};
|
|
349
|
-
if (json.trace_id) attrs.trace_id = json.trace_id;
|
|
350
|
-
if (json.span_id) attrs.span_id = json.span_id;
|
|
351
|
-
return attrs;
|
|
352
|
-
} catch {
|
|
353
|
-
return {};
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function mergeAttributes(...maps) {
|
|
357
|
-
const merged = {};
|
|
358
|
-
for (const map of maps) {
|
|
359
|
-
if (!map) continue;
|
|
360
|
-
for (const [rawKey, rawValue] of Object.entries(map)) {
|
|
361
|
-
const key = normalizeAttributeKey(rawKey);
|
|
362
|
-
const value = sanitizeValue(
|
|
363
|
-
key === "gen_ai.response.finish_reasons" ? normalizeGenAiFinishReasons(rawValue) : rawValue
|
|
364
|
-
);
|
|
365
|
-
if (value !== void 0) {
|
|
366
|
-
merged[key] = value;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return merged;
|
|
371
|
-
}
|
|
372
|
-
function fromLogTapeLevel(level) {
|
|
373
|
-
if (level === "warning") {
|
|
374
|
-
return "warn";
|
|
375
|
-
}
|
|
376
|
-
if (level === "fatal") {
|
|
377
|
-
return "error";
|
|
378
|
-
}
|
|
379
|
-
if (level === "trace") {
|
|
380
|
-
return "debug";
|
|
381
|
-
}
|
|
382
|
-
return level;
|
|
383
|
-
}
|
|
384
|
-
function getLogSource(category) {
|
|
385
|
-
if (category.length <= ROOT_LOGGER_CATEGORY.length) {
|
|
386
|
-
return void 0;
|
|
387
|
-
}
|
|
388
|
-
const sourceParts = category.slice(ROOT_LOGGER_CATEGORY.length);
|
|
389
|
-
return sourceParts.length > 0 ? sourceParts.join(".") : void 0;
|
|
390
|
-
}
|
|
391
|
-
function toEmittedLogRecord(record) {
|
|
392
|
-
const properties = { ...record.properties };
|
|
393
|
-
const rawBody = properties[LOGTAPE_BODY_KEY];
|
|
394
|
-
delete properties[LOGTAPE_BODY_KEY];
|
|
395
|
-
const attributes = mergeAttributes(properties);
|
|
396
|
-
const source = getLogSource(record.category);
|
|
397
|
-
if (source && attributes["app.log.source"] === void 0) {
|
|
398
|
-
attributes["app.log.source"] = source;
|
|
399
|
-
}
|
|
400
|
-
const body = toOptionalString(rawBody) ?? record.message.map(
|
|
401
|
-
(segment) => typeof segment === "string" ? segment : String(segment ?? "")
|
|
402
|
-
).join("");
|
|
403
|
-
const eventName = toOptionalString(attributes["event.name"]) ?? "log_record_emitted";
|
|
404
|
-
return {
|
|
405
|
-
level: fromLogTapeLevel(record.level),
|
|
406
|
-
eventName,
|
|
407
|
-
body,
|
|
408
|
-
attributes
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
function createConsoleSink() {
|
|
412
|
-
return (record) => {
|
|
413
|
-
const emitted = toEmittedLogRecord(record);
|
|
414
|
-
emitConsole(
|
|
415
|
-
emitted.level,
|
|
416
|
-
emitted.eventName,
|
|
417
|
-
emitted.body,
|
|
418
|
-
emitted.attributes
|
|
419
|
-
);
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function createSentrySink() {
|
|
423
|
-
return (record) => {
|
|
424
|
-
const emitted = toEmittedLogRecord(record);
|
|
425
|
-
emitSentry(emitted.level, emitted.body, emitted.attributes);
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
function createRecordSink() {
|
|
429
|
-
return (record) => {
|
|
430
|
-
const emitted = toEmittedLogRecord(record);
|
|
431
|
-
for (const sink of logRecordSinks) {
|
|
432
|
-
try {
|
|
433
|
-
sink(emitted);
|
|
434
|
-
} catch {
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
var rootLogger;
|
|
440
|
-
var ownsLogTapeBackend = false;
|
|
441
|
-
var usesDirectEmissionFallback = false;
|
|
442
|
-
function ensureLoggerBackend() {
|
|
443
|
-
if (rootLogger || usesDirectEmissionFallback) {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (getConfig() !== null) {
|
|
447
|
-
usesDirectEmissionFallback = true;
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
try {
|
|
451
|
-
configureSync({
|
|
452
|
-
sinks: {
|
|
453
|
-
console: createConsoleSink(),
|
|
454
|
-
sentry: createSentrySink(),
|
|
455
|
-
records: createRecordSink()
|
|
456
|
-
},
|
|
457
|
-
loggers: [
|
|
458
|
-
{
|
|
459
|
-
category: [...ROOT_LOGGER_CATEGORY],
|
|
460
|
-
sinks: ["console", "sentry", "records"],
|
|
461
|
-
lowestLevel: "debug"
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
category: ["logtape"],
|
|
465
|
-
sinks: ["console"],
|
|
466
|
-
lowestLevel: "error"
|
|
467
|
-
}
|
|
468
|
-
],
|
|
469
|
-
contextLocalStorage: contextStorage
|
|
470
|
-
});
|
|
471
|
-
ownsLogTapeBackend = true;
|
|
472
|
-
rootLogger = getLogger([...ROOT_LOGGER_CATEGORY]);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
if (error instanceof ConfigError && getConfig() !== null) {
|
|
475
|
-
usesDirectEmissionFallback = true;
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
throw error;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
function getLogTapeLogger(category = []) {
|
|
482
|
-
ensureLoggerBackend();
|
|
483
|
-
if (!rootLogger) {
|
|
484
|
-
throw new Error("LogTape backend is unavailable");
|
|
485
|
-
}
|
|
486
|
-
let logger = rootLogger;
|
|
487
|
-
for (const part of category) {
|
|
488
|
-
logger = logger.getChild(part);
|
|
489
|
-
}
|
|
490
|
-
return logger;
|
|
491
|
-
}
|
|
492
|
-
function emitSentry(level, body, attributes) {
|
|
493
|
-
if (shouldSuppressInfoLog(level)) {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
const sentry = sentry_exports;
|
|
497
|
-
const loggerFn = sentry.logger?.[level];
|
|
498
|
-
if (typeof loggerFn === "function") {
|
|
499
|
-
loggerFn(body, attributes);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const sentryWithScope = sentry.withScope;
|
|
503
|
-
const sentryCaptureMessage = sentry.captureMessage;
|
|
504
|
-
const sentryLevel = level === "warn" ? "warning" : level;
|
|
505
|
-
if (typeof sentryWithScope === "function" && typeof sentryCaptureMessage === "function") {
|
|
506
|
-
sentryWithScope((scope) => {
|
|
507
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
508
|
-
scope.setExtra(key, value);
|
|
509
|
-
}
|
|
510
|
-
sentryCaptureMessage(body, sentryLevel);
|
|
511
|
-
});
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
if (typeof sentryCaptureMessage === "function") {
|
|
515
|
-
sentryCaptureMessage(body, sentryLevel);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
function formatConsoleLevel(level) {
|
|
519
|
-
if (level === "debug") return "DBG";
|
|
520
|
-
if (level === "info") return "INF";
|
|
521
|
-
if (level === "warn") return "WRN";
|
|
522
|
-
return "ERR";
|
|
523
|
-
}
|
|
524
|
-
function consoleLevelStyle(level) {
|
|
525
|
-
if (level === "error") return "red";
|
|
526
|
-
if (level === "warn") return "yellow";
|
|
527
|
-
if (level === "info") return "green";
|
|
528
|
-
return "blue";
|
|
529
|
-
}
|
|
530
|
-
function quoteConsoleValue(value) {
|
|
531
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
532
|
-
}
|
|
533
|
-
function formatConsoleValue(value) {
|
|
534
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
535
|
-
return String(value);
|
|
536
|
-
}
|
|
537
|
-
if (Array.isArray(value)) {
|
|
538
|
-
return quoteConsoleValue(JSON.stringify(value));
|
|
539
|
-
}
|
|
540
|
-
if (/^[A-Za-z0-9._:/@+-]+$/.test(value)) {
|
|
541
|
-
return value;
|
|
542
|
-
}
|
|
543
|
-
return quoteConsoleValue(value);
|
|
544
|
-
}
|
|
545
|
-
function shouldShowConsoleDestinationName(eventName) {
|
|
546
|
-
return /^(app_home_|oauth_|queue_|slash_command_|slack_|webhook_)/.test(
|
|
547
|
-
eventName
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
function shouldShowConsoleModel(level, eventName) {
|
|
551
|
-
if (level === "warn" || level === "error") {
|
|
552
|
-
return true;
|
|
553
|
-
}
|
|
554
|
-
return eventName.startsWith("ai_") || eventName.startsWith("assistant_") || eventName === "agent_turn_started" || eventName === "agent_turn_completed" || eventName === "agent_turn_provider_error";
|
|
555
|
-
}
|
|
556
|
-
function shouldHideConsoleAttribute(level, eventName, key, attributes) {
|
|
557
|
-
if (CONSOLE_ALWAYS_HIDDEN_KEYS.has(key)) {
|
|
558
|
-
return true;
|
|
559
|
-
}
|
|
560
|
-
if (CONSOLE_DROP_WHEN_COUNTED_KEYS.has(key)) {
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
if (key === "messaging.message.conversation_id" && attributes[key] === attributes["gen_ai.conversation.id"]) {
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
|
-
if (key === "app.message.id" && attributes[key] === attributes["messaging.message.id"]) {
|
|
567
|
-
return true;
|
|
568
|
-
}
|
|
569
|
-
if (key === "messaging.destination.name" && !shouldShowConsoleDestinationName(eventName)) {
|
|
570
|
-
return true;
|
|
571
|
-
}
|
|
572
|
-
if (key === "gen_ai.request.model" && !shouldShowConsoleModel(level, eventName)) {
|
|
573
|
-
return true;
|
|
574
|
-
}
|
|
575
|
-
if (key === "gen_ai.provider.name" && eventName.startsWith("agent_tool_call_") && level !== "warn" && level !== "error") {
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
if (key === "gen_ai.operation.name" && eventName.startsWith("agent_tool_call_")) {
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
581
|
-
return false;
|
|
582
|
-
}
|
|
583
|
-
function summarizeConsoleString(value, maxChars) {
|
|
584
|
-
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
585
|
-
if (collapsed.length <= maxChars) {
|
|
586
|
-
return collapsed;
|
|
587
|
-
}
|
|
588
|
-
return `${collapsed.slice(0, maxChars)}... [${collapsed.length} chars]`;
|
|
589
|
-
}
|
|
590
|
-
function abbreviateConsoleId(value) {
|
|
591
|
-
if (value.length <= 20) {
|
|
592
|
-
return value;
|
|
593
|
-
}
|
|
594
|
-
return `${value.slice(0, 12)}...${value.slice(-4)}`;
|
|
595
|
-
}
|
|
596
|
-
function toRelativeConsolePath(value) {
|
|
597
|
-
const normalized = value.trim();
|
|
598
|
-
if (!normalized) {
|
|
599
|
-
return normalized;
|
|
600
|
-
}
|
|
601
|
-
try {
|
|
602
|
-
const relative = path.relative(process.cwd(), normalized);
|
|
603
|
-
if (relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
604
|
-
return relative;
|
|
605
|
-
}
|
|
606
|
-
} catch {
|
|
607
|
-
}
|
|
608
|
-
return normalized;
|
|
609
|
-
}
|
|
610
|
-
function pushPrettyConsoleToken(tokens, token) {
|
|
611
|
-
if (!token || tokens.includes(token)) {
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
tokens.push(token);
|
|
615
|
-
}
|
|
616
|
-
function numericConsoleToken(label, value) {
|
|
617
|
-
return typeof value === "number" ? `${label}=${value}` : void 0;
|
|
618
|
-
}
|
|
619
|
-
function stringConsoleToken(label, value) {
|
|
620
|
-
const normalized = toOptionalString(value);
|
|
621
|
-
return normalized ? `${label}=${normalized}` : void 0;
|
|
622
|
-
}
|
|
623
|
-
function booleanConsoleToken(label, value) {
|
|
624
|
-
return typeof value === "boolean" ? `${label}=${value ? "yes" : "no"}` : void 0;
|
|
625
|
-
}
|
|
626
|
-
function shouldShowPrettyCorrelation(eventName) {
|
|
627
|
-
return !(eventName === "plugin_loaded" || eventName === "startup_discovery_summary" || eventName === "capability_catalog_loaded" || eventName.endsWith("_loaded"));
|
|
628
|
-
}
|
|
629
|
-
function getPrettyConsoleSummaryTokens(level, eventName, attributes) {
|
|
630
|
-
const tokens = [];
|
|
631
|
-
pushPrettyConsoleToken(
|
|
632
|
-
tokens,
|
|
633
|
-
toOptionalString(attributes["app.log.source"]) ?? void 0
|
|
634
|
-
);
|
|
635
|
-
pushPrettyConsoleToken(
|
|
636
|
-
tokens,
|
|
637
|
-
eventName.startsWith("trusted_plugin_heartbeat") ? stringConsoleToken("plugin", attributes["app.plugin.name"]) : toOptionalString(attributes["app.plugin.name"]) ?? void 0
|
|
638
|
-
);
|
|
639
|
-
pushPrettyConsoleToken(
|
|
640
|
-
tokens,
|
|
641
|
-
numericConsoleToken("caps", attributes["app.plugin.capability_count"])
|
|
642
|
-
);
|
|
643
|
-
pushPrettyConsoleToken(
|
|
644
|
-
tokens,
|
|
645
|
-
numericConsoleToken("config", attributes["app.plugin.config_key_count"])
|
|
646
|
-
);
|
|
647
|
-
pushPrettyConsoleToken(
|
|
648
|
-
tokens,
|
|
649
|
-
booleanConsoleToken("mcp", attributes["app.plugin.has_mcp"])
|
|
650
|
-
);
|
|
651
|
-
pushPrettyConsoleToken(
|
|
652
|
-
tokens,
|
|
653
|
-
numericConsoleToken("plugins", attributes["app.plugin.count"])
|
|
654
|
-
);
|
|
655
|
-
pushPrettyConsoleToken(
|
|
656
|
-
tokens,
|
|
657
|
-
numericConsoleToken("dispatches", attributes["app.dispatch.count"])
|
|
658
|
-
);
|
|
659
|
-
pushPrettyConsoleToken(
|
|
660
|
-
tokens,
|
|
661
|
-
numericConsoleToken("skills", attributes["app.skill.count"])
|
|
662
|
-
);
|
|
663
|
-
pushPrettyConsoleToken(
|
|
664
|
-
tokens,
|
|
665
|
-
numericConsoleToken("capabilities", attributes["app.capability.count"])
|
|
666
|
-
);
|
|
667
|
-
pushPrettyConsoleToken(
|
|
668
|
-
tokens,
|
|
669
|
-
numericConsoleToken("config", attributes["app.config.key_count"])
|
|
670
|
-
);
|
|
671
|
-
pushPrettyConsoleToken(
|
|
672
|
-
tokens,
|
|
673
|
-
numericConsoleToken("chars", attributes["app.message.length"])
|
|
674
|
-
);
|
|
675
|
-
pushPrettyConsoleToken(
|
|
676
|
-
tokens,
|
|
677
|
-
numericConsoleToken(
|
|
678
|
-
"attachments",
|
|
679
|
-
attributes["app.message.attachment_count"]
|
|
680
|
-
)
|
|
681
|
-
);
|
|
682
|
-
const rawAttachmentCount = toOptionalNumber(
|
|
683
|
-
attributes["app.message.attachment_count"]
|
|
684
|
-
);
|
|
685
|
-
const promptAttachmentCount = toOptionalNumber(
|
|
686
|
-
attributes["app.message.prompt_attachment_count"]
|
|
687
|
-
);
|
|
688
|
-
if (promptAttachmentCount !== void 0 && promptAttachmentCount !== rawAttachmentCount) {
|
|
689
|
-
pushPrettyConsoleToken(
|
|
690
|
-
tokens,
|
|
691
|
-
numericConsoleToken("prompt_attachments", promptAttachmentCount)
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
const filePath = toOptionalString(attributes["file.path"]);
|
|
695
|
-
if (filePath && eventName.endsWith("_loaded")) {
|
|
696
|
-
pushPrettyConsoleToken(tokens, toRelativeConsolePath(filePath));
|
|
697
|
-
}
|
|
698
|
-
if (shouldShowPrettyCorrelation(eventName)) {
|
|
699
|
-
const conversationId = toOptionalString(
|
|
700
|
-
attributes["gen_ai.conversation.id"]
|
|
701
|
-
);
|
|
702
|
-
const messageId = toOptionalString(attributes["messaging.message.id"]);
|
|
703
|
-
if (conversationId) {
|
|
704
|
-
pushPrettyConsoleToken(
|
|
705
|
-
tokens,
|
|
706
|
-
`conv=${abbreviateConsoleId(conversationId)}`
|
|
707
|
-
);
|
|
708
|
-
}
|
|
709
|
-
if (messageId) {
|
|
710
|
-
pushPrettyConsoleToken(tokens, `msg=${abbreviateConsoleId(messageId)}`);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
const model = toOptionalString(attributes["gen_ai.request.model"]);
|
|
714
|
-
if (model && shouldShowConsoleModel(level, eventName)) {
|
|
715
|
-
pushPrettyConsoleToken(tokens, `model=${model}`);
|
|
716
|
-
}
|
|
717
|
-
return tokens;
|
|
718
|
-
}
|
|
719
|
-
function projectConsoleValue(level, key, value) {
|
|
720
|
-
if ((level === "debug" || level === "info") && CONSOLE_PREVIEW_KEYS.has(key) && typeof value === "string") {
|
|
721
|
-
return summarizeConsoleString(
|
|
722
|
-
value,
|
|
723
|
-
key === "gen_ai.tool.call.result" ? 220 : 140
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
return value;
|
|
727
|
-
}
|
|
728
|
-
function projectConsoleAttributes(level, eventName, attributes) {
|
|
729
|
-
const projected = {};
|
|
730
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
731
|
-
if (shouldHideConsoleAttribute(level, eventName, key, attributes)) {
|
|
732
|
-
continue;
|
|
733
|
-
}
|
|
734
|
-
const nextValue = projectConsoleValue(level, key, value);
|
|
735
|
-
if (nextValue !== void 0) {
|
|
736
|
-
projected[key] = nextValue;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
return projected;
|
|
740
|
-
}
|
|
741
|
-
function formatConsoleLine(level, eventName, body, attributes) {
|
|
742
|
-
const timestamp = /* @__PURE__ */ new Date();
|
|
743
|
-
const useColor = shouldUseConsoleColor();
|
|
744
|
-
const levelStyle = consoleLevelStyle(level);
|
|
745
|
-
const colorize = (text, style) => useColor ? styleText(style, text) : text;
|
|
746
|
-
if (shouldUsePrettyConsole(level)) {
|
|
747
|
-
const summaryTokens = getPrettyConsoleSummaryTokens(
|
|
748
|
-
level,
|
|
749
|
-
eventName,
|
|
750
|
-
attributes
|
|
751
|
-
);
|
|
752
|
-
const summary = [body, ...summaryTokens].join(" ");
|
|
753
|
-
return [
|
|
754
|
-
colorize(formatConsoleTimestamp(timestamp), "gray"),
|
|
755
|
-
colorize(formatConsoleLevel(level), levelStyle),
|
|
756
|
-
summary
|
|
757
|
-
].join(" ");
|
|
758
|
-
}
|
|
759
|
-
const parts = [
|
|
760
|
-
`${colorize(formatConsoleTimestamp(timestamp), "gray")} ${colorize(formatConsoleLevel(level), levelStyle)} ${body}`
|
|
761
|
-
];
|
|
762
|
-
const projectedAttributes = projectConsoleAttributes(
|
|
763
|
-
level,
|
|
764
|
-
eventName,
|
|
765
|
-
attributes
|
|
766
|
-
);
|
|
767
|
-
const sortedAttributes = Object.entries(projectedAttributes).sort(
|
|
768
|
-
([left], [right]) => {
|
|
769
|
-
const leftRank = CONSOLE_PRIORITY_INDEX.get(left);
|
|
770
|
-
const rightRank = CONSOLE_PRIORITY_INDEX.get(right);
|
|
771
|
-
if (leftRank !== void 0 || rightRank !== void 0) {
|
|
772
|
-
if (leftRank === void 0) return 1;
|
|
773
|
-
if (rightRank === void 0) return -1;
|
|
774
|
-
return leftRank - rightRank;
|
|
775
|
-
}
|
|
776
|
-
return left.localeCompare(right);
|
|
777
|
-
}
|
|
778
|
-
);
|
|
779
|
-
for (const [key, value] of sortedAttributes) {
|
|
780
|
-
const rendered = `${colorize(key, "cyan")}=${colorize(formatConsoleValue(value), "dim")}`;
|
|
781
|
-
parts.push(rendered);
|
|
782
|
-
}
|
|
783
|
-
return parts.join(" ");
|
|
784
|
-
}
|
|
785
|
-
function emitConsole(level, eventName, body, attributes) {
|
|
786
|
-
if (!shouldEmitConsole(level)) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
const line = formatConsoleLine(level, eventName, body, attributes);
|
|
790
|
-
if (level === "error") {
|
|
791
|
-
console.error(line);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
if (level === "warn") {
|
|
795
|
-
console.warn(line);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
if (level === "info") {
|
|
799
|
-
console.info(line);
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
console.debug(line);
|
|
803
|
-
}
|
|
804
|
-
function emitDirect(level, eventName, body, attributes) {
|
|
805
|
-
for (const sink of logRecordSinks) {
|
|
806
|
-
try {
|
|
807
|
-
sink({
|
|
808
|
-
level,
|
|
809
|
-
eventName,
|
|
810
|
-
body,
|
|
811
|
-
attributes
|
|
812
|
-
});
|
|
813
|
-
} catch {
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
emitConsole(level, eventName, body, attributes);
|
|
817
|
-
emitSentry(level, body, attributes);
|
|
818
|
-
}
|
|
819
|
-
function emitRecord(category, level, eventName, attrs = {}, body) {
|
|
820
|
-
ensureLoggerBackend();
|
|
821
|
-
const traceAttributes = getTraceCorrelationAttributes();
|
|
822
|
-
const normalizedEventName = toSnakeCase(eventName);
|
|
823
|
-
const message = body ? redactSecrets(body) : normalizedEventName;
|
|
824
|
-
const source = getLogSource([...ROOT_LOGGER_CATEGORY, ...category]);
|
|
825
|
-
const contextAttributes = ownsLogTapeBackend ? void 0 : contextStorage.getStore();
|
|
826
|
-
const attributes = mergeAttributes(contextAttributes, traceAttributes, {
|
|
827
|
-
"event.name": normalizedEventName,
|
|
828
|
-
...source ? { "app.log.source": source } : {},
|
|
829
|
-
...attrs
|
|
830
|
-
});
|
|
831
|
-
if (usesDirectEmissionFallback) {
|
|
832
|
-
emitDirect(level, normalizedEventName, message, attributes);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
const logger = getLogTapeLogger(category);
|
|
836
|
-
const properties = {
|
|
837
|
-
[LOGTAPE_BODY_KEY]: message,
|
|
838
|
-
...attributes
|
|
839
|
-
};
|
|
840
|
-
if (level === "error") {
|
|
841
|
-
logger.error(`{${LOGTAPE_BODY_KEY}}`, properties);
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
if (level === "warn") {
|
|
845
|
-
logger.warn(`{${LOGTAPE_BODY_KEY}}`, properties);
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
if (level === "info") {
|
|
849
|
-
logger.info(`{${LOGTAPE_BODY_KEY}}`, properties);
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
logger.debug(`{${LOGTAPE_BODY_KEY}}`, properties);
|
|
853
|
-
}
|
|
854
|
-
function emit(level, eventName, attrs = {}, body) {
|
|
855
|
-
emitRecord([], level, eventName, attrs, body);
|
|
856
|
-
}
|
|
857
|
-
var log = {
|
|
858
|
-
debug(eventName, attrs = {}, body) {
|
|
859
|
-
emit("debug", eventName, attrs, body);
|
|
860
|
-
},
|
|
861
|
-
info(eventName, attrs = {}, body) {
|
|
862
|
-
emit("info", eventName, attrs, body);
|
|
863
|
-
},
|
|
864
|
-
warn(eventName, attrs = {}, body) {
|
|
865
|
-
emit("warn", eventName, attrs, body);
|
|
866
|
-
},
|
|
867
|
-
error(eventName, attrs = {}, body) {
|
|
868
|
-
emit("error", eventName, attrs, body);
|
|
869
|
-
},
|
|
870
|
-
exception(eventName, error, attrs = {}, body, context) {
|
|
871
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
872
|
-
emit(
|
|
873
|
-
"error",
|
|
874
|
-
eventName,
|
|
875
|
-
{
|
|
876
|
-
...attrs,
|
|
877
|
-
"error.type": normalizedError.name,
|
|
878
|
-
"exception.type": normalizedError.name,
|
|
879
|
-
"exception.message": normalizedError.message,
|
|
880
|
-
"exception.stacktrace": normalizedError.stack
|
|
881
|
-
},
|
|
882
|
-
body ?? normalizedError.message
|
|
883
|
-
);
|
|
884
|
-
let eventId;
|
|
885
|
-
const sentryWithScope = sentry_exports.withScope;
|
|
886
|
-
const sentryCaptureException = sentry_exports.captureException;
|
|
887
|
-
if (typeof sentryWithScope === "function" && typeof sentryCaptureException === "function") {
|
|
888
|
-
sentryWithScope((scope) => {
|
|
889
|
-
if (context) {
|
|
890
|
-
setSentryScopeContext(scope, context);
|
|
891
|
-
}
|
|
892
|
-
for (const [key, value] of Object.entries(
|
|
893
|
-
mergeAttributes(contextStorage.getStore(), attrs)
|
|
894
|
-
)) {
|
|
895
|
-
scope.setExtra(key, value);
|
|
896
|
-
}
|
|
897
|
-
eventId = sentryCaptureException(normalizedError);
|
|
898
|
-
});
|
|
899
|
-
return eventId;
|
|
900
|
-
}
|
|
901
|
-
if (typeof sentryCaptureException === "function") {
|
|
902
|
-
if (context) {
|
|
903
|
-
setSentryUser(sentryUserIdentityFromContext(context));
|
|
904
|
-
}
|
|
905
|
-
eventId = sentryCaptureException(normalizedError);
|
|
906
|
-
}
|
|
907
|
-
return eventId;
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
var CHAT_SDK_LEVEL_PRIORITY = {
|
|
911
|
-
debug: 10,
|
|
912
|
-
info: 20,
|
|
913
|
-
warn: 30,
|
|
914
|
-
error: 40
|
|
915
|
-
};
|
|
916
|
-
function resolveChatSdkLogLevel() {
|
|
917
|
-
if (isDevelopmentLoggingMode()) {
|
|
918
|
-
return "warn";
|
|
919
|
-
}
|
|
920
|
-
return "info";
|
|
921
|
-
}
|
|
922
|
-
function shouldEmitChatSdkLevel(level, minimumLevel) {
|
|
923
|
-
if (minimumLevel === "silent") {
|
|
924
|
-
return false;
|
|
925
|
-
}
|
|
926
|
-
return CHAT_SDK_LEVEL_PRIORITY[level] >= CHAT_SDK_LEVEL_PRIORITY[minimumLevel];
|
|
927
|
-
}
|
|
928
|
-
function renderChatSdkArgument(value) {
|
|
929
|
-
if (value === null || value === void 0) {
|
|
930
|
-
return "";
|
|
931
|
-
}
|
|
932
|
-
if (typeof value === "string") {
|
|
933
|
-
return value;
|
|
934
|
-
}
|
|
935
|
-
if (value instanceof Error) {
|
|
936
|
-
return value.message;
|
|
937
|
-
}
|
|
938
|
-
try {
|
|
939
|
-
return JSON.stringify(value);
|
|
940
|
-
} catch {
|
|
941
|
-
return String(value);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
function formatChatSdkBody(message, args) {
|
|
945
|
-
const renderedArgs = args.map((arg) => renderChatSdkArgument(arg).trim()).filter((arg) => arg.length > 0);
|
|
946
|
-
if (renderedArgs.length === 0) {
|
|
947
|
-
return message;
|
|
948
|
-
}
|
|
949
|
-
return `${message} ${renderedArgs.join(" ")}`;
|
|
950
|
-
}
|
|
951
|
-
function createChatSdkLoggerImpl(category, minimumLevel) {
|
|
952
|
-
const emitChatSdkLog = (level, message, args) => {
|
|
953
|
-
if (!shouldEmitChatSdkLevel(level, minimumLevel)) {
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
emitRecord(
|
|
957
|
-
category,
|
|
958
|
-
level === "warn" ? "warn" : level,
|
|
959
|
-
level === "error" ? "chat_sdk_error" : level === "warn" ? "chat_sdk_warning" : "chat_sdk_log",
|
|
960
|
-
args.length > 0 ? {
|
|
961
|
-
"app.log.args": args.length === 1 ? args[0] : args
|
|
962
|
-
} : {},
|
|
963
|
-
formatChatSdkBody(message, args)
|
|
964
|
-
);
|
|
965
|
-
};
|
|
966
|
-
return {
|
|
967
|
-
child(prefix) {
|
|
968
|
-
return createChatSdkLoggerImpl([...category, prefix], minimumLevel);
|
|
969
|
-
},
|
|
970
|
-
debug(message, ...args) {
|
|
971
|
-
emitChatSdkLog("debug", message, args);
|
|
972
|
-
},
|
|
973
|
-
info(message, ...args) {
|
|
974
|
-
emitChatSdkLog("info", message, args);
|
|
975
|
-
},
|
|
976
|
-
warn(message, ...args) {
|
|
977
|
-
emitChatSdkLog("warn", message, args);
|
|
978
|
-
},
|
|
979
|
-
error(message, ...args) {
|
|
980
|
-
emitChatSdkLog("error", message, args);
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
function createChatSdkLogger() {
|
|
985
|
-
return createChatSdkLoggerImpl(["chat-sdk"], resolveChatSdkLogLevel());
|
|
986
|
-
}
|
|
987
|
-
function withLogContext(context, callback) {
|
|
988
|
-
const next = mergeAttributes(
|
|
989
|
-
contextStorage.getStore(),
|
|
990
|
-
contextToAttributes(context)
|
|
991
|
-
);
|
|
992
|
-
return contextStorage.run(next, callback);
|
|
993
|
-
}
|
|
994
|
-
function setLogContext(context) {
|
|
995
|
-
const merged = mergeAttributes(
|
|
996
|
-
contextStorage.getStore(),
|
|
997
|
-
contextToAttributes(context)
|
|
998
|
-
);
|
|
999
|
-
contextStorage.enterWith(merged);
|
|
1000
|
-
}
|
|
1001
|
-
function getLogContextAttributes() {
|
|
1002
|
-
return contextStorage.getStore() ?? {};
|
|
1003
|
-
}
|
|
1004
|
-
function createLogContextFromRequest(request, context = {}) {
|
|
1005
|
-
const url = new URL(request.url);
|
|
1006
|
-
return {
|
|
1007
|
-
...context,
|
|
1008
|
-
requestId: context.requestId ?? request.headers.get("x-request-id") ?? void 0,
|
|
1009
|
-
httpMethod: request.method,
|
|
1010
|
-
httpPath: url.pathname,
|
|
1011
|
-
urlFull: url.toString(),
|
|
1012
|
-
userAgent: request.headers.get("user-agent") ?? void 0
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
function toSpanAttributes(context) {
|
|
1016
|
-
const attrs = contextToAttributes(context);
|
|
1017
|
-
return Object.fromEntries(
|
|
1018
|
-
Object.entries(attrs).filter(
|
|
1019
|
-
([, value]) => typeof value === "string" && value.length > 0
|
|
1020
|
-
)
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
function setSentryTagsFromContext(context) {
|
|
1024
|
-
const attrs = contextToAttributes(context);
|
|
1025
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
1026
|
-
if (!SENTRY_TAG_ATTRIBUTE_KEYS.has(key)) {
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
1029
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1030
|
-
sentry_exports.setTag(key, value);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
function sentryUserIdentityFromContext(context) {
|
|
1035
|
-
if (context.slackUserId) {
|
|
1036
|
-
return {
|
|
1037
|
-
id: context.slackUserId,
|
|
1038
|
-
...context.slackUserName ? { username: context.slackUserName } : {},
|
|
1039
|
-
...context.slackUserEmail ? { email: context.slackUserEmail } : {}
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
return void 0;
|
|
1043
|
-
}
|
|
1044
|
-
function sentryUserFromIdentity(identity) {
|
|
1045
|
-
return {
|
|
1046
|
-
id: identity.id,
|
|
1047
|
-
ip_address: null,
|
|
1048
|
-
...identity.username ? { username: identity.username } : {},
|
|
1049
|
-
...identity.email ? { email: identity.email } : {}
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
function setSentryUser(identity) {
|
|
1053
|
-
if (!identity) return;
|
|
1054
|
-
sentry_exports.setUser(sentryUserFromIdentity(identity));
|
|
1055
|
-
}
|
|
1056
|
-
function setSentryScopeContext(scope, context) {
|
|
1057
|
-
const attrs = contextToAttributes(context);
|
|
1058
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
1059
|
-
if (!SENTRY_TAG_ATTRIBUTE_KEYS.has(key)) {
|
|
1060
|
-
continue;
|
|
1061
|
-
}
|
|
1062
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1063
|
-
scope.setTag(key, value);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
const identity = sentryUserIdentityFromContext(context);
|
|
1067
|
-
if (identity) {
|
|
1068
|
-
scope.setUser(sentryUserFromIdentity(identity));
|
|
1069
|
-
}
|
|
1070
|
-
scope.setContext("app", attrs);
|
|
1071
|
-
}
|
|
1072
|
-
function toSpanAttributeValue(value) {
|
|
1073
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1074
|
-
return value;
|
|
1075
|
-
}
|
|
1076
|
-
if (!Array.isArray(value)) {
|
|
1077
|
-
return void 0;
|
|
1078
|
-
}
|
|
1079
|
-
const sanitized = value.filter(
|
|
1080
|
-
(entry) => typeof entry === "string"
|
|
1081
|
-
);
|
|
1082
|
-
return sanitized.length > 0 ? sanitized : void 0;
|
|
1083
|
-
}
|
|
1084
|
-
function normalizeSpanAttributes(attributes) {
|
|
1085
|
-
const normalized = {};
|
|
1086
|
-
for (const [rawKey, value] of Object.entries(attributes)) {
|
|
1087
|
-
const key = normalizeAttributeKey(rawKey);
|
|
1088
|
-
const normalizedValue = toSpanAttributeValue(
|
|
1089
|
-
key === "gen_ai.response.finish_reasons" ? normalizeGenAiFinishReasons(value) : value
|
|
1090
|
-
);
|
|
1091
|
-
if (normalizedValue !== void 0) {
|
|
1092
|
-
normalized[key] = normalizedValue;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
return normalized;
|
|
1096
|
-
}
|
|
1097
|
-
function logInfo(eventName, context = {}, attributes = {}, body) {
|
|
1098
|
-
log.info(eventName, { ...toSpanAttributes(context), ...attributes }, body);
|
|
1099
|
-
}
|
|
1100
|
-
function logWarn(eventName, context = {}, attributes = {}, body) {
|
|
1101
|
-
log.warn(eventName, { ...toSpanAttributes(context), ...attributes }, body);
|
|
1102
|
-
}
|
|
1103
|
-
function logError(eventName, context = {}, attributes = {}, body) {
|
|
1104
|
-
log.error(eventName, { ...toSpanAttributes(context), ...attributes }, body);
|
|
1105
|
-
}
|
|
1106
|
-
function logException(error, eventName, context = {}, attributes = {}, body) {
|
|
1107
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
1108
|
-
return log.exception(
|
|
1109
|
-
eventName,
|
|
1110
|
-
normalizedError,
|
|
1111
|
-
{ ...toSpanAttributes(context), ...attributes },
|
|
1112
|
-
body,
|
|
1113
|
-
context
|
|
1114
|
-
);
|
|
1115
|
-
}
|
|
1116
|
-
function setTags(context = {}) {
|
|
1117
|
-
setLogContext(context);
|
|
1118
|
-
setSentryTagsFromContext(context);
|
|
1119
|
-
setSentryUser(sentryUserIdentityFromContext(context));
|
|
1120
|
-
}
|
|
1121
|
-
function createRequestContext(request, context = {}) {
|
|
1122
|
-
return createLogContextFromRequest(request, context);
|
|
1123
|
-
}
|
|
1124
|
-
async function withContext(context, callback) {
|
|
1125
|
-
return withLogContext(context, callback);
|
|
1126
|
-
}
|
|
1127
|
-
async function withSpan(name, op, context, callback, attributes = {}) {
|
|
1128
|
-
const normalizedAttributes = normalizeSpanAttributes(attributes);
|
|
1129
|
-
return withLogContext(context, () => {
|
|
1130
|
-
const inheritedAttributes = getLogContextAttributes();
|
|
1131
|
-
return sentry_exports.startSpan(
|
|
1132
|
-
{
|
|
1133
|
-
name,
|
|
1134
|
-
op,
|
|
1135
|
-
attributes: {
|
|
1136
|
-
...inheritedAttributes,
|
|
1137
|
-
...normalizedAttributes
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
callback
|
|
1141
|
-
);
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1144
|
-
function setSpanAttributes(attributes) {
|
|
1145
|
-
const sentry = sentry_exports;
|
|
1146
|
-
const span = sentry.getActiveSpan?.();
|
|
1147
|
-
if (!span) {
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
const setAttribute = span.setAttribute;
|
|
1151
|
-
if (typeof setAttribute !== "function") {
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
for (const [key, value] of Object.entries(
|
|
1155
|
-
normalizeSpanAttributes(attributes)
|
|
1156
|
-
)) {
|
|
1157
|
-
setAttribute.call(span, key, value);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
function setSpanStatus(status) {
|
|
1161
|
-
const sentry = sentry_exports;
|
|
1162
|
-
const span = sentry.getActiveSpan?.();
|
|
1163
|
-
if (!span) {
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
const setStatus = span.setStatus;
|
|
1167
|
-
if (typeof setStatus !== "function") {
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
setStatus.call(span, status === "ok" ? "ok" : "internal_error");
|
|
1171
|
-
}
|
|
1172
|
-
function getActiveTraceId() {
|
|
1173
|
-
const sentry = sentry_exports;
|
|
1174
|
-
if (typeof sentry.getActiveSpan !== "function" || typeof sentry.spanToJSON !== "function") {
|
|
1175
|
-
return void 0;
|
|
1176
|
-
}
|
|
1177
|
-
try {
|
|
1178
|
-
const span = sentry.getActiveSpan();
|
|
1179
|
-
if (!span) {
|
|
1180
|
-
return void 0;
|
|
1181
|
-
}
|
|
1182
|
-
return toOptionalString(sentry.spanToJSON(span).trace_id);
|
|
1183
|
-
} catch {
|
|
1184
|
-
return void 0;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
var TURN_FAILURE_RESPONSE_TEMPLATE = "I ran into an internal error while processing that. Reference: `event_id={eventId}`.";
|
|
1188
|
-
function buildTurnFailureResponse(eventId) {
|
|
1189
|
-
return TURN_FAILURE_RESPONSE_TEMPLATE.replace("{eventId}", eventId);
|
|
1190
|
-
}
|
|
1191
|
-
var GEN_AI_DEFAULT_MAX_ATTRIBUTE_CHARS = 12e3;
|
|
1192
|
-
var GEN_AI_MAX_STRING_CHARS = 2e3;
|
|
1193
|
-
var GEN_AI_MAX_ARRAY_ITEMS = 50;
|
|
1194
|
-
var GEN_AI_MAX_OBJECT_KEYS = 50;
|
|
1195
|
-
function truncateGenAiString(value, maxChars) {
|
|
1196
|
-
return value.length > maxChars ? `${value.slice(0, maxChars)}...` : value;
|
|
1197
|
-
}
|
|
1198
|
-
function sanitizeGenAiValue(value, seen, depth, keyName) {
|
|
1199
|
-
if (value === null || value === void 0) {
|
|
1200
|
-
return void 0;
|
|
1201
|
-
}
|
|
1202
|
-
if (typeof value === "string") {
|
|
1203
|
-
const shouldTreatAsBlob = (keyName === "data" || keyName === "base64" || keyName?.endsWith("_base64") === true) && value.length > 256;
|
|
1204
|
-
if (shouldTreatAsBlob) {
|
|
1205
|
-
return `[omitted:${value.length}]`;
|
|
1206
|
-
}
|
|
1207
|
-
return truncateGenAiString(redactSecrets(value), GEN_AI_MAX_STRING_CHARS);
|
|
1208
|
-
}
|
|
1209
|
-
if (typeof value === "number") {
|
|
1210
|
-
return Number.isFinite(value) ? value : void 0;
|
|
1211
|
-
}
|
|
1212
|
-
if (typeof value === "boolean") {
|
|
1213
|
-
return value;
|
|
1214
|
-
}
|
|
1215
|
-
if (depth >= 8) {
|
|
1216
|
-
return "[depth_limit]";
|
|
1217
|
-
}
|
|
1218
|
-
if (Array.isArray(value)) {
|
|
1219
|
-
return value.slice(0, GEN_AI_MAX_ARRAY_ITEMS).map((entry) => sanitizeGenAiValue(entry, seen, depth + 1)).filter((entry) => entry !== void 0);
|
|
1220
|
-
}
|
|
1221
|
-
if (typeof value !== "object") {
|
|
1222
|
-
return redactSecrets(String(value));
|
|
1223
|
-
}
|
|
1224
|
-
if (seen.has(value)) {
|
|
1225
|
-
return "[circular]";
|
|
1226
|
-
}
|
|
1227
|
-
seen.add(value);
|
|
1228
|
-
const record = value;
|
|
1229
|
-
const out = {};
|
|
1230
|
-
for (const [key, entryValue] of Object.entries(record).slice(
|
|
1231
|
-
0,
|
|
1232
|
-
GEN_AI_MAX_OBJECT_KEYS
|
|
1233
|
-
)) {
|
|
1234
|
-
const sanitized = sanitizeGenAiValue(entryValue, seen, depth + 1, key);
|
|
1235
|
-
if (sanitized !== void 0) {
|
|
1236
|
-
out[key] = sanitized;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
return out;
|
|
1240
|
-
}
|
|
1241
|
-
function serializeGenAiAttribute(value, maxChars = GEN_AI_DEFAULT_MAX_ATTRIBUTE_CHARS) {
|
|
1242
|
-
const sanitized = sanitizeGenAiValue(value, /* @__PURE__ */ new WeakSet(), 0);
|
|
1243
|
-
if (sanitized === void 0) {
|
|
1244
|
-
return void 0;
|
|
1245
|
-
}
|
|
1246
|
-
const serialized = typeof sanitized === "string" ? sanitized : JSON.stringify(sanitized);
|
|
1247
|
-
if (!serialized) {
|
|
1248
|
-
return void 0;
|
|
1249
|
-
}
|
|
1250
|
-
return truncateGenAiString(redactSecrets(serialized), maxChars);
|
|
1251
|
-
}
|
|
1252
|
-
function asRecord(value) {
|
|
1253
|
-
return value && typeof value === "object" ? value : void 0;
|
|
1254
|
-
}
|
|
1255
|
-
function toFiniteTokenCount(value) {
|
|
1256
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1257
|
-
return void 0;
|
|
1258
|
-
}
|
|
1259
|
-
const rounded = Math.floor(value);
|
|
1260
|
-
return rounded >= 0 ? rounded : void 0;
|
|
1261
|
-
}
|
|
1262
|
-
function sumTokenCounts(...values) {
|
|
1263
|
-
let total = 0;
|
|
1264
|
-
let hasValue = false;
|
|
1265
|
-
for (const value of values) {
|
|
1266
|
-
if (value === void 0) {
|
|
1267
|
-
continue;
|
|
1268
|
-
}
|
|
1269
|
-
total += value;
|
|
1270
|
-
hasValue = true;
|
|
1271
|
-
}
|
|
1272
|
-
return hasValue ? total : void 0;
|
|
1273
|
-
}
|
|
1274
|
-
var PI_USAGE_FIELDS = [
|
|
1275
|
-
["input", "inputTokens"],
|
|
1276
|
-
["output", "outputTokens"],
|
|
1277
|
-
["cacheRead", "cachedInputTokens"],
|
|
1278
|
-
["cacheWrite", "cacheCreationTokens"],
|
|
1279
|
-
["totalTokens", "totalTokens"]
|
|
1280
|
-
];
|
|
1281
|
-
function readPiUsage(source) {
|
|
1282
|
-
const record = asRecord(source);
|
|
1283
|
-
if (!record) {
|
|
1284
|
-
return {};
|
|
1285
|
-
}
|
|
1286
|
-
const usage = asRecord(record.usage) ?? record;
|
|
1287
|
-
const summary = {};
|
|
1288
|
-
for (const [piKey, ourKey] of PI_USAGE_FIELDS) {
|
|
1289
|
-
const value = toFiniteTokenCount(usage[piKey]) ?? toFiniteTokenCount(usage[ourKey]);
|
|
1290
|
-
if (value !== void 0) {
|
|
1291
|
-
summary[ourKey] = value;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
return summary;
|
|
1295
|
-
}
|
|
1296
|
-
function extractGenAiUsageSummary(...sources) {
|
|
1297
|
-
const summary = {};
|
|
1298
|
-
for (const source of sources) {
|
|
1299
|
-
const single = readPiUsage(source);
|
|
1300
|
-
for (const field of Object.keys(single)) {
|
|
1301
|
-
const value = single[field];
|
|
1302
|
-
if (value === void 0) continue;
|
|
1303
|
-
summary[field] = (summary[field] ?? 0) + value;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
return summary;
|
|
1307
|
-
}
|
|
1308
|
-
function extractGenAiUsageAttributes(...sources) {
|
|
1309
|
-
const { inputTokens, outputTokens, cachedInputTokens, cacheCreationTokens } = extractGenAiUsageSummary(...sources);
|
|
1310
|
-
const semanticInputTokens = sumTokenCounts(
|
|
1311
|
-
inputTokens,
|
|
1312
|
-
cachedInputTokens,
|
|
1313
|
-
cacheCreationTokens
|
|
1314
|
-
);
|
|
1315
|
-
return {
|
|
1316
|
-
...semanticInputTokens !== void 0 ? { "gen_ai.usage.input_tokens": semanticInputTokens } : {},
|
|
1317
|
-
...outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": outputTokens } : {},
|
|
1318
|
-
...cachedInputTokens !== void 0 ? { "gen_ai.usage.cache_read.input_tokens": cachedInputTokens } : {},
|
|
1319
|
-
...cacheCreationTokens !== void 0 ? { "gen_ai.usage.cache_creation.input_tokens": cacheCreationTokens } : {}
|
|
1320
|
-
};
|
|
1321
|
-
}
|
|
2
|
+
discoverInstalledPluginPackageContent,
|
|
3
|
+
logInfo,
|
|
4
|
+
logWarn,
|
|
5
|
+
normalizePluginPackageNames,
|
|
6
|
+
pluginRoots,
|
|
7
|
+
setSpanAttributes
|
|
8
|
+
} from "./chunk-BBXYXOJW.js";
|
|
1322
9
|
|
|
1323
10
|
// src/chat/plugins/manifest.ts
|
|
1324
11
|
import { z } from "zod";
|
|
@@ -1330,7 +17,7 @@ function setDefined(target, key, value) {
|
|
|
1330
17
|
target[key] = value;
|
|
1331
18
|
}
|
|
1332
19
|
}
|
|
1333
|
-
function
|
|
20
|
+
function isRecord(value) {
|
|
1334
21
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1335
22
|
}
|
|
1336
23
|
function unqualifyManifestToken(name, value) {
|
|
@@ -1346,7 +33,7 @@ function inlineTokenListSource(name, values) {
|
|
|
1346
33
|
return values.map((value) => unqualifyManifestToken(name, value));
|
|
1347
34
|
}
|
|
1348
35
|
function inlineCredentialsSource(credentials) {
|
|
1349
|
-
if (credentials === void 0 || !
|
|
36
|
+
if (credentials === void 0 || !isRecord(credentials)) {
|
|
1350
37
|
return credentials;
|
|
1351
38
|
}
|
|
1352
39
|
const result = {};
|
|
@@ -1359,20 +46,10 @@ function inlineCredentialsSource(credentials) {
|
|
|
1359
46
|
"auth-token-placeholder",
|
|
1360
47
|
credentials.authTokenPlaceholder
|
|
1361
48
|
);
|
|
1362
|
-
if (credentials.type === "github-app") {
|
|
1363
|
-
setDefined(result, "app-id-env", credentials.appIdEnv);
|
|
1364
|
-
setDefined(result, "private-key-env", credentials.privateKeyEnv);
|
|
1365
|
-
setDefined(result, "installation-id-env", credentials.installationIdEnv);
|
|
1366
|
-
setDefined(
|
|
1367
|
-
result,
|
|
1368
|
-
"system-read-permissions",
|
|
1369
|
-
credentials.systemReadPermissions
|
|
1370
|
-
);
|
|
1371
|
-
}
|
|
1372
49
|
return result;
|
|
1373
50
|
}
|
|
1374
51
|
function inlineMcpSource(mcp) {
|
|
1375
|
-
if (mcp === void 0 || !
|
|
52
|
+
if (mcp === void 0 || !isRecord(mcp)) {
|
|
1376
53
|
return mcp;
|
|
1377
54
|
}
|
|
1378
55
|
const result = {};
|
|
@@ -1383,7 +60,7 @@ function inlineMcpSource(mcp) {
|
|
|
1383
60
|
return result;
|
|
1384
61
|
}
|
|
1385
62
|
function inlineOauthSource(oauth) {
|
|
1386
|
-
if (oauth === void 0 || !
|
|
63
|
+
if (oauth === void 0 || !isRecord(oauth)) {
|
|
1387
64
|
return oauth;
|
|
1388
65
|
}
|
|
1389
66
|
const result = {};
|
|
@@ -1395,10 +72,15 @@ function inlineOauthSource(oauth) {
|
|
|
1395
72
|
setDefined(result, "authorize-params", oauth.authorizeParams);
|
|
1396
73
|
setDefined(result, "token-auth-method", oauth.tokenAuthMethod);
|
|
1397
74
|
setDefined(result, "token-extra-headers", oauth.tokenExtraHeaders);
|
|
75
|
+
setDefined(
|
|
76
|
+
result,
|
|
77
|
+
"treat-empty-scope-as-unreported",
|
|
78
|
+
oauth.treatEmptyScopeAsUnreported
|
|
79
|
+
);
|
|
1398
80
|
return result;
|
|
1399
81
|
}
|
|
1400
82
|
function inlineTargetSource(name, target) {
|
|
1401
|
-
if (target === void 0 || !
|
|
83
|
+
if (target === void 0 || !isRecord(target)) {
|
|
1402
84
|
return target;
|
|
1403
85
|
}
|
|
1404
86
|
const result = {};
|
|
@@ -1446,103 +128,6 @@ function inlineManifestSource(manifest) {
|
|
|
1446
128
|
return result;
|
|
1447
129
|
}
|
|
1448
130
|
|
|
1449
|
-
// src/chat/plugins/github-permissions.ts
|
|
1450
|
-
var KNOWN_GITHUB_PERMISSION_SCOPES = /* @__PURE__ */ new Set([
|
|
1451
|
-
"actions",
|
|
1452
|
-
"administration",
|
|
1453
|
-
"checks",
|
|
1454
|
-
"codespaces",
|
|
1455
|
-
"contents",
|
|
1456
|
-
"deployments",
|
|
1457
|
-
"environments",
|
|
1458
|
-
"issues",
|
|
1459
|
-
"metadata",
|
|
1460
|
-
"packages",
|
|
1461
|
-
"pages",
|
|
1462
|
-
"pull_requests",
|
|
1463
|
-
"repository_hooks",
|
|
1464
|
-
"repository_projects",
|
|
1465
|
-
"secret_scanning_alerts",
|
|
1466
|
-
"secrets",
|
|
1467
|
-
"security_events",
|
|
1468
|
-
"statuses",
|
|
1469
|
-
"vulnerability_alerts",
|
|
1470
|
-
"workflows"
|
|
1471
|
-
]);
|
|
1472
|
-
var DEFAULT_GITHUB_SYSTEM_READ_SCOPES = /* @__PURE__ */ new Set([
|
|
1473
|
-
"actions",
|
|
1474
|
-
"checks",
|
|
1475
|
-
"contents",
|
|
1476
|
-
"issues",
|
|
1477
|
-
"metadata",
|
|
1478
|
-
"pull_requests",
|
|
1479
|
-
"statuses"
|
|
1480
|
-
]);
|
|
1481
|
-
function normalizeGitHubPermissionScope(rawScope) {
|
|
1482
|
-
return rawScope.trim().replace(/-/g, "_");
|
|
1483
|
-
}
|
|
1484
|
-
function normalizeGitHubSystemReadPermissionScopes(scopes, context) {
|
|
1485
|
-
return scopes.map((rawScope) => {
|
|
1486
|
-
const scope = normalizeGitHubPermissionScope(rawScope);
|
|
1487
|
-
if (!KNOWN_GITHUB_PERMISSION_SCOPES.has(scope)) {
|
|
1488
|
-
throw new Error(`${context} contains unsupported scope "${rawScope}"`);
|
|
1489
|
-
}
|
|
1490
|
-
return scope;
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
function githubCapabilitiesToPermissions(capabilities, pluginName) {
|
|
1494
|
-
const permissions = {};
|
|
1495
|
-
const prefix = `${pluginName}.`;
|
|
1496
|
-
for (const capability of capabilities) {
|
|
1497
|
-
if (!capability.startsWith(prefix)) {
|
|
1498
|
-
throw new Error(`Unsupported GitHub capability: ${capability}`);
|
|
1499
|
-
}
|
|
1500
|
-
const suffix = capability.slice(prefix.length);
|
|
1501
|
-
const lastDot = suffix.lastIndexOf(".");
|
|
1502
|
-
if (lastDot === -1) {
|
|
1503
|
-
throw new Error(`Unsupported GitHub capability: ${capability}`);
|
|
1504
|
-
}
|
|
1505
|
-
const scopeRaw = suffix.slice(0, lastDot);
|
|
1506
|
-
const level = suffix.slice(lastDot + 1);
|
|
1507
|
-
if (level !== "read" && level !== "write") {
|
|
1508
|
-
throw new Error(`Unsupported GitHub capability: ${capability}`);
|
|
1509
|
-
}
|
|
1510
|
-
const scope = normalizeGitHubPermissionScope(scopeRaw);
|
|
1511
|
-
if (!KNOWN_GITHUB_PERMISSION_SCOPES.has(scope)) {
|
|
1512
|
-
throw new Error(`Unsupported GitHub capability: ${capability}`);
|
|
1513
|
-
}
|
|
1514
|
-
const existing = permissions[scope];
|
|
1515
|
-
permissions[scope] = existing === "write" || level === "write" ? "write" : "read";
|
|
1516
|
-
}
|
|
1517
|
-
return permissions;
|
|
1518
|
-
}
|
|
1519
|
-
function githubSystemReadPermissionsFromScopes(scopes) {
|
|
1520
|
-
const readOnly = {
|
|
1521
|
-
metadata: "read"
|
|
1522
|
-
};
|
|
1523
|
-
for (const scope of normalizeGitHubSystemReadPermissionScopes(
|
|
1524
|
-
scopes,
|
|
1525
|
-
"GitHub system read permissions"
|
|
1526
|
-
)) {
|
|
1527
|
-
readOnly[scope] = "read";
|
|
1528
|
-
}
|
|
1529
|
-
return readOnly;
|
|
1530
|
-
}
|
|
1531
|
-
function githubInstallationReadPermissions(permissions, allowedScopes) {
|
|
1532
|
-
const readOnly = {
|
|
1533
|
-
metadata: "read"
|
|
1534
|
-
};
|
|
1535
|
-
for (const [scope, level] of Object.entries(permissions ?? {})) {
|
|
1536
|
-
if (!allowedScopes.has(scope) || !KNOWN_GITHUB_PERMISSION_SCOPES.has(scope)) {
|
|
1537
|
-
continue;
|
|
1538
|
-
}
|
|
1539
|
-
if (level === "read" || level === "write" || level === "admin") {
|
|
1540
|
-
readOnly[scope] = "read";
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
return readOnly;
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
131
|
// src/chat/plugins/manifest.ts
|
|
1547
132
|
var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
1548
133
|
var SHORT_CAPABILITY_RE = /^[a-z0-9-]+(\.[a-z0-9-]+)*$/;
|
|
@@ -1670,23 +255,14 @@ var domainsSchema = z.array(z.unknown()).min(1, {
|
|
|
1670
255
|
});
|
|
1671
256
|
});
|
|
1672
257
|
var baseCredentialsSchema = z.object({
|
|
1673
|
-
domains: domainsSchema.optional()
|
|
1674
|
-
"api-headers": stringMapSchema.optional(),
|
|
1675
|
-
"auth-token-env": envVarString,
|
|
1676
|
-
"auth-token-placeholder": nonEmptyTrimmedString.optional()
|
|
258
|
+
domains: domainsSchema.optional()
|
|
1677
259
|
}).passthrough();
|
|
1678
260
|
var oauthBearerCredentialsSchema = baseCredentialsSchema.extend({
|
|
261
|
+
"api-headers": stringMapSchema.optional(),
|
|
262
|
+
"auth-token-env": envVarString,
|
|
263
|
+
"auth-token-placeholder": nonEmptyTrimmedString.optional(),
|
|
1679
264
|
type: z.literal("oauth-bearer")
|
|
1680
265
|
});
|
|
1681
|
-
var githubAppCredentialsSchema = baseCredentialsSchema.extend({
|
|
1682
|
-
type: z.literal("github-app"),
|
|
1683
|
-
"app-id-env": envVarString,
|
|
1684
|
-
"private-key-env": envVarString,
|
|
1685
|
-
"installation-id-env": envVarString,
|
|
1686
|
-
"system-read-permissions": nonEmptyStringArraySchema(
|
|
1687
|
-
"system-read-permissions"
|
|
1688
|
-
).optional()
|
|
1689
|
-
});
|
|
1690
266
|
var runtimeDependencyEntrySchema = z.object({
|
|
1691
267
|
type: z.enum(["npm", "system"]),
|
|
1692
268
|
package: z.string().optional(),
|
|
@@ -1713,7 +289,8 @@ var oauthSourceSchema = z.object({
|
|
|
1713
289
|
"token-extra-headers": stringMapSchema.optional(),
|
|
1714
290
|
"token-auth-method": nonEmptyTrimmedString.refine((value) => value === "body" || value === "basic", {
|
|
1715
291
|
error: 'must be "body" or "basic"'
|
|
1716
|
-
}).optional()
|
|
292
|
+
}).optional(),
|
|
293
|
+
"treat-empty-scope-as-unreported": z.boolean().optional()
|
|
1717
294
|
}).passthrough();
|
|
1718
295
|
var mcpSourceSchema = z.object({
|
|
1719
296
|
transport: nonEmptyTrimmedString.refine((value) => value === "http", {
|
|
@@ -1798,22 +375,6 @@ function manifestConfigPatch(config) {
|
|
|
1798
375
|
"auth-token-placeholder",
|
|
1799
376
|
config.credentials.authTokenPlaceholder
|
|
1800
377
|
);
|
|
1801
|
-
setDefined2(credentials, "app-id-env", config.credentials.appIdEnv);
|
|
1802
|
-
setDefined2(
|
|
1803
|
-
credentials,
|
|
1804
|
-
"private-key-env",
|
|
1805
|
-
config.credentials.privateKeyEnv
|
|
1806
|
-
);
|
|
1807
|
-
setDefined2(
|
|
1808
|
-
credentials,
|
|
1809
|
-
"installation-id-env",
|
|
1810
|
-
config.credentials.installationIdEnv
|
|
1811
|
-
);
|
|
1812
|
-
setDefined2(
|
|
1813
|
-
credentials,
|
|
1814
|
-
"system-read-permissions",
|
|
1815
|
-
config.credentials.systemReadPermissions
|
|
1816
|
-
);
|
|
1817
378
|
result.credentials = credentials;
|
|
1818
379
|
}
|
|
1819
380
|
}
|
|
@@ -1844,6 +405,11 @@ function manifestConfigPatch(config) {
|
|
|
1844
405
|
setDefined2(oauth, "authorize-params", config.oauth.authorizeParams);
|
|
1845
406
|
setDefined2(oauth, "token-auth-method", config.oauth.tokenAuthMethod);
|
|
1846
407
|
setDefined2(oauth, "token-extra-headers", config.oauth.tokenExtraHeaders);
|
|
408
|
+
setDefined2(
|
|
409
|
+
oauth,
|
|
410
|
+
"treat-empty-scope-as-unreported",
|
|
411
|
+
config.oauth.treatEmptyScopeAsUnreported
|
|
412
|
+
);
|
|
1847
413
|
result.oauth = oauth;
|
|
1848
414
|
}
|
|
1849
415
|
}
|
|
@@ -1895,8 +461,8 @@ function applyManifestConfig(source, config) {
|
|
|
1895
461
|
root: true
|
|
1896
462
|
}) : source;
|
|
1897
463
|
}
|
|
1898
|
-
function formatPath(
|
|
1899
|
-
return
|
|
464
|
+
function formatPath(path2) {
|
|
465
|
+
return path2.map((segment) => String(segment)).join(".");
|
|
1900
466
|
}
|
|
1901
467
|
function issueMessage(error, prefix) {
|
|
1902
468
|
const issue = error.issues[0];
|
|
@@ -2002,11 +568,8 @@ function assertCommandEnvDoesNotExposeHostSecretRefs(commandEnv, apiHeaders, cre
|
|
|
2002
568
|
}
|
|
2003
569
|
}
|
|
2004
570
|
if (credentials) {
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
hostOnlyRefs.add(credentials.appIdEnv);
|
|
2008
|
-
hostOnlyRefs.add(credentials.privateKeyEnv);
|
|
2009
|
-
hostOnlyRefs.add(credentials.installationIdEnv);
|
|
571
|
+
if (credentials.authTokenEnv) {
|
|
572
|
+
hostOnlyRefs.add(credentials.authTokenEnv);
|
|
2010
573
|
}
|
|
2011
574
|
}
|
|
2012
575
|
if (oauth) {
|
|
@@ -2039,13 +602,12 @@ function assertCommandEnvHostRefsAreExplicitlyExposed(commandEnv, envVars, plugi
|
|
|
2039
602
|
}
|
|
2040
603
|
}
|
|
2041
604
|
function normalizeCredentials(data, name) {
|
|
2042
|
-
|
|
2043
|
-
if (!schema) {
|
|
605
|
+
if (data.type !== "oauth-bearer") {
|
|
2044
606
|
throw new Error(
|
|
2045
607
|
`Plugin ${name} has unsupported credentials.type: "${String(data.type)}"`
|
|
2046
608
|
);
|
|
2047
609
|
}
|
|
2048
|
-
const result =
|
|
610
|
+
const result = oauthBearerCredentialsSchema.safeParse(data);
|
|
2049
611
|
if (!result.success) {
|
|
2050
612
|
throw new Error(issueMessage(result.error, `Plugin ${name} credentials`));
|
|
2051
613
|
}
|
|
@@ -2053,39 +615,17 @@ function normalizeCredentials(data, name) {
|
|
|
2053
615
|
throw new Error(`Plugin ${name} credentials requires domains`);
|
|
2054
616
|
}
|
|
2055
617
|
const domains = result.data.domains;
|
|
2056
|
-
if (result.data.type === "oauth-bearer") {
|
|
2057
|
-
const apiHeaders2 = result.data["api-headers"] ? normalizeStringMap(
|
|
2058
|
-
result.data["api-headers"],
|
|
2059
|
-
`Plugin ${name} credentials.api-headers`,
|
|
2060
|
-
{ forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }
|
|
2061
|
-
) : void 0;
|
|
2062
|
-
return {
|
|
2063
|
-
type: "oauth-bearer",
|
|
2064
|
-
domains,
|
|
2065
|
-
...apiHeaders2 ? { apiHeaders: apiHeaders2 } : {},
|
|
2066
|
-
authTokenEnv: result.data["auth-token-env"],
|
|
2067
|
-
...result.data["auth-token-placeholder"] ? { authTokenPlaceholder: result.data["auth-token-placeholder"] } : {}
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
618
|
const apiHeaders = result.data["api-headers"] ? normalizeStringMap(
|
|
2071
619
|
result.data["api-headers"],
|
|
2072
620
|
`Plugin ${name} credentials.api-headers`,
|
|
2073
621
|
{ forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }
|
|
2074
622
|
) : void 0;
|
|
2075
|
-
const systemReadPermissions = result.data["system-read-permissions"] ? normalizeGitHubSystemReadPermissionScopes(
|
|
2076
|
-
result.data["system-read-permissions"],
|
|
2077
|
-
`Plugin ${name} credentials.system-read-permissions`
|
|
2078
|
-
) : void 0;
|
|
2079
623
|
return {
|
|
2080
|
-
type: "
|
|
624
|
+
type: "oauth-bearer",
|
|
2081
625
|
domains,
|
|
2082
626
|
...apiHeaders ? { apiHeaders } : {},
|
|
2083
627
|
authTokenEnv: result.data["auth-token-env"],
|
|
2084
|
-
...result.data["auth-token-placeholder"] ? { authTokenPlaceholder: result.data["auth-token-placeholder"] } : {}
|
|
2085
|
-
appIdEnv: result.data["app-id-env"],
|
|
2086
|
-
privateKeyEnv: result.data["private-key-env"],
|
|
2087
|
-
installationIdEnv: result.data["installation-id-env"],
|
|
2088
|
-
...systemReadPermissions ? { systemReadPermissions } : {}
|
|
628
|
+
...result.data["auth-token-placeholder"] ? { authTokenPlaceholder: result.data["auth-token-placeholder"] } : {}
|
|
2089
629
|
};
|
|
2090
630
|
}
|
|
2091
631
|
function normalizeRuntimeDependencies(entries, name) {
|
|
@@ -2292,74 +832,74 @@ function normalizeMcp(data, envVars, name) {
|
|
|
2292
832
|
...result.data["allowed-tools"] ? { allowedTools: result.data["allowed-tools"] } : {}
|
|
2293
833
|
};
|
|
2294
834
|
}
|
|
2295
|
-
function parseManifestSource(parsedSource, dir, config) {
|
|
835
|
+
function parseManifestSource(parsedSource, dir, config, options) {
|
|
2296
836
|
const source = applyManifestConfig(parsedSource, config);
|
|
2297
837
|
const sourceResult = manifestSourceSchema.safeParse(source);
|
|
2298
838
|
if (!sourceResult.success) {
|
|
2299
839
|
const issue = sourceResult.error.issues[0];
|
|
2300
|
-
const
|
|
2301
|
-
if (
|
|
840
|
+
const path2 = formatPath(issue?.path ?? []);
|
|
841
|
+
if (path2 === "name") {
|
|
2302
842
|
throw new Error(`Invalid plugin name in ${dir}: "${parsedSource.name}"`);
|
|
2303
843
|
}
|
|
2304
|
-
if (
|
|
844
|
+
if (path2 === "description") {
|
|
2305
845
|
throw new Error(`Invalid plugin description in ${dir}`);
|
|
2306
846
|
}
|
|
2307
|
-
if (
|
|
847
|
+
if (path2 === "capabilities") {
|
|
2308
848
|
throw new Error(
|
|
2309
849
|
`Plugin ${String(parsedSource.name ?? "unknown")} capabilities must be an array when provided`
|
|
2310
850
|
);
|
|
2311
851
|
}
|
|
2312
|
-
if (
|
|
852
|
+
if (path2 === "config-keys") {
|
|
2313
853
|
throw new Error(
|
|
2314
854
|
`Plugin ${String(parsedSource.name ?? "unknown")} config-keys must be an array when provided`
|
|
2315
855
|
);
|
|
2316
856
|
}
|
|
2317
|
-
if (
|
|
857
|
+
if (path2 === "domains") {
|
|
2318
858
|
throw new Error(
|
|
2319
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} ${
|
|
859
|
+
`Plugin ${String(parsedSource.name ?? "unknown")} ${path2} must be a non-empty array of domains`
|
|
2320
860
|
);
|
|
2321
861
|
}
|
|
2322
|
-
if (
|
|
862
|
+
if (path2 === "api-headers") {
|
|
2323
863
|
throw new Error(
|
|
2324
864
|
`Plugin ${String(parsedSource.name ?? "unknown")} api-headers must be an object when provided`
|
|
2325
865
|
);
|
|
2326
866
|
}
|
|
2327
|
-
if (
|
|
867
|
+
if (path2 === "command-env") {
|
|
2328
868
|
throw new Error(
|
|
2329
869
|
`Plugin ${String(parsedSource.name ?? "unknown")} command-env must be an object when provided`
|
|
2330
870
|
);
|
|
2331
871
|
}
|
|
2332
|
-
if (
|
|
872
|
+
if (path2 === "credentials") {
|
|
2333
873
|
throw new Error(
|
|
2334
874
|
`Plugin ${String(parsedSource.name ?? "unknown")} credentials must be an object when provided`
|
|
2335
875
|
);
|
|
2336
876
|
}
|
|
2337
|
-
if (
|
|
877
|
+
if (path2 === "runtime-dependencies") {
|
|
2338
878
|
throw new Error(
|
|
2339
879
|
`Plugin ${String(parsedSource.name ?? "unknown")} runtime-dependencies must be an array`
|
|
2340
880
|
);
|
|
2341
881
|
}
|
|
2342
|
-
if (
|
|
882
|
+
if (path2 === "runtime-postinstall") {
|
|
2343
883
|
throw new Error(
|
|
2344
884
|
`Plugin ${String(parsedSource.name ?? "unknown")} runtime-postinstall must be an array`
|
|
2345
885
|
);
|
|
2346
886
|
}
|
|
2347
|
-
if (
|
|
887
|
+
if (path2 === "env-vars") {
|
|
2348
888
|
throw new Error(
|
|
2349
889
|
`Plugin ${String(parsedSource.name ?? "unknown")} env-vars must be an object`
|
|
2350
890
|
);
|
|
2351
891
|
}
|
|
2352
|
-
if (
|
|
892
|
+
if (path2 === "mcp") {
|
|
2353
893
|
throw new Error(
|
|
2354
894
|
`Plugin ${String(parsedSource.name ?? "unknown")} mcp must be an object`
|
|
2355
895
|
);
|
|
2356
896
|
}
|
|
2357
|
-
if (
|
|
897
|
+
if (path2 === "oauth") {
|
|
2358
898
|
throw new Error(
|
|
2359
899
|
`Plugin ${String(parsedSource.name ?? "unknown")} oauth must be an object`
|
|
2360
900
|
);
|
|
2361
901
|
}
|
|
2362
|
-
if (
|
|
902
|
+
if (path2 === "target") {
|
|
2363
903
|
throw new Error(
|
|
2364
904
|
`Plugin ${String(parsedSource.name ?? "unknown")} target must be an object`
|
|
2365
905
|
);
|
|
@@ -2391,7 +931,7 @@ function parseManifestSource(parsedSource, dir, config) {
|
|
|
2391
931
|
if (apiHeaders && !domains) {
|
|
2392
932
|
throw new Error(`Plugin ${data.name} api-headers requires domains`);
|
|
2393
933
|
}
|
|
2394
|
-
if (domains && !apiHeaders && !data.credentials) {
|
|
934
|
+
if (domains && !apiHeaders && !data.credentials && options?.allowHookManagedEgress !== true) {
|
|
2395
935
|
throw new Error(
|
|
2396
936
|
`Plugin ${data.name} domains requires credentials or api-headers`
|
|
2397
937
|
);
|
|
@@ -2420,14 +960,9 @@ function parseManifestSource(parsedSource, dir, config) {
|
|
|
2420
960
|
...mcp ? { mcp } : {}
|
|
2421
961
|
};
|
|
2422
962
|
if (data.oauth) {
|
|
2423
|
-
if (!credentials) {
|
|
963
|
+
if (!credentials && options?.allowHookManagedEgress !== true) {
|
|
2424
964
|
throw new Error(`Plugin ${data.name} oauth requires credentials`);
|
|
2425
965
|
}
|
|
2426
|
-
if (credentials.type !== "oauth-bearer") {
|
|
2427
|
-
throw new Error(
|
|
2428
|
-
`Plugin ${data.name} oauth requires credentials.type "oauth-bearer"`
|
|
2429
|
-
);
|
|
2430
|
-
}
|
|
2431
966
|
const result = oauthSourceSchema.safeParse(data.oauth);
|
|
2432
967
|
if (!result.success) {
|
|
2433
968
|
throw new Error(issueMessage(result.error, `Plugin ${data.name} oauth`));
|
|
@@ -2454,7 +989,8 @@ function parseManifestSource(parsedSource, dir, config) {
|
|
|
2454
989
|
...result.data.scope ? { scope: result.data.scope } : {},
|
|
2455
990
|
...authorizeParams ? { authorizeParams } : {},
|
|
2456
991
|
...result.data["token-auth-method"] ? { tokenAuthMethod: result.data["token-auth-method"] } : {},
|
|
2457
|
-
...tokenExtraHeaders ? { tokenExtraHeaders } : {}
|
|
992
|
+
...tokenExtraHeaders ? { tokenExtraHeaders } : {},
|
|
993
|
+
...result.data["treat-empty-scope-as-unreported"] ? { treatEmptyScopeAsUnreported: true } : {}
|
|
2458
994
|
};
|
|
2459
995
|
}
|
|
2460
996
|
assertCommandEnvDoesNotExposeHostSecretRefs(
|
|
@@ -2471,382 +1007,60 @@ function parseManifestSource(parsedSource, dir, config) {
|
|
|
2471
1007
|
);
|
|
2472
1008
|
if (data.target) {
|
|
2473
1009
|
const result = targetSourceSchema.safeParse(data.target);
|
|
2474
|
-
if (!result.success) {
|
|
2475
|
-
throw new Error(issueMessage(result.error, `Plugin ${data.name} target`));
|
|
2476
|
-
}
|
|
2477
|
-
if (!SHORT_CONFIG_KEY_RE.test(result.data["config-key"])) {
|
|
2478
|
-
throw new Error(
|
|
2479
|
-
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" is invalid`
|
|
2480
|
-
);
|
|
2481
|
-
}
|
|
2482
|
-
const qualifiedKey = `${data.name}.${result.data["config-key"]}`;
|
|
2483
|
-
if (!configKeys.includes(qualifiedKey)) {
|
|
2484
|
-
throw new Error(
|
|
2485
|
-
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" must be listed in config-keys`
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
const commandFlags = result.data["command-flags"];
|
|
2489
|
-
if (commandFlags && commandFlags.some((flag) => !TARGET_FLAG_RE.test(flag))) {
|
|
2490
|
-
throw new Error(
|
|
2491
|
-
`Plugin ${data.name} target.command-flags must contain CLI flags like --repo or -R`
|
|
2492
|
-
);
|
|
2493
|
-
}
|
|
2494
|
-
manifest.target = {
|
|
2495
|
-
type: result.data.type,
|
|
2496
|
-
configKey: qualifiedKey,
|
|
2497
|
-
...commandFlags ? { commandFlags } : {}
|
|
2498
|
-
};
|
|
2499
|
-
}
|
|
2500
|
-
return manifest;
|
|
2501
|
-
}
|
|
2502
|
-
function parsePluginManifest(raw, dir, config) {
|
|
2503
|
-
let parsedYaml;
|
|
2504
|
-
try {
|
|
2505
|
-
parsedYaml = parseYaml(raw);
|
|
2506
|
-
} catch (error) {
|
|
2507
|
-
throw new Error(
|
|
2508
|
-
`Invalid plugin manifest in ${dir}: ${error instanceof Error ? error.message : String(error)}`
|
|
2509
|
-
);
|
|
2510
|
-
}
|
|
2511
|
-
if (!parsedYaml || typeof parsedYaml !== "object" || Array.isArray(parsedYaml)) {
|
|
2512
|
-
throw new Error(`Invalid plugin manifest in ${dir}: expected an object`);
|
|
2513
|
-
}
|
|
2514
|
-
return parseManifestSource(parsedYaml, dir, config);
|
|
2515
|
-
}
|
|
2516
|
-
function parseInlinePluginManifest(manifest, dir, config) {
|
|
2517
|
-
return parseManifestSource(inlineManifestSource(manifest), dir, config);
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
|
-
// src/chat/plugins/registry.ts
|
|
2521
|
-
import { readFileSync, readdirSync, statSync } from "fs";
|
|
2522
|
-
import path2 from "path";
|
|
2523
|
-
|
|
2524
|
-
// src/chat/plugins/auth/github-app-broker.ts
|
|
2525
|
-
import { createPrivateKey, createSign, randomUUID as randomUUID2 } from "crypto";
|
|
2526
|
-
|
|
2527
|
-
// src/chat/credentials/header-transforms.ts
|
|
2528
|
-
function mergeHeaderTransforms(transforms) {
|
|
2529
|
-
const byDomain = /* @__PURE__ */ new Map();
|
|
2530
|
-
for (const transform of transforms) {
|
|
2531
|
-
byDomain.set(transform.domain, {
|
|
2532
|
-
...byDomain.get(transform.domain) ?? {},
|
|
2533
|
-
...transform.headers
|
|
2534
|
-
});
|
|
2535
|
-
}
|
|
2536
|
-
return [...byDomain.entries()].map(([domain, headers]) => ({
|
|
2537
|
-
domain,
|
|
2538
|
-
headers
|
|
2539
|
-
}));
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2542
|
-
// src/chat/plugins/command-env.ts
|
|
2543
|
-
var ENV_PLACEHOLDER_RE2 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
2544
|
-
function resolveValue(manifest, value) {
|
|
2545
|
-
let missing = false;
|
|
2546
|
-
const resolved = value.replace(ENV_PLACEHOLDER_RE2, (match, name) => {
|
|
2547
|
-
const envName = name;
|
|
2548
|
-
const declaration = manifest.envVars?.[envName];
|
|
2549
|
-
if (!declaration || declaration.default !== void 0) {
|
|
2550
|
-
return match;
|
|
2551
|
-
}
|
|
2552
|
-
const hostValue = process.env[envName];
|
|
2553
|
-
if (hostValue === void 0 || hostValue === "") {
|
|
2554
|
-
missing = true;
|
|
2555
|
-
return "";
|
|
2556
|
-
}
|
|
2557
|
-
return hostValue;
|
|
2558
|
-
});
|
|
2559
|
-
return missing ? void 0 : resolved;
|
|
2560
|
-
}
|
|
2561
|
-
function resolvePluginCommandEnv(manifest) {
|
|
2562
|
-
const env = {};
|
|
2563
|
-
for (const [key, value] of Object.entries(manifest.commandEnv ?? {})) {
|
|
2564
|
-
const resolved = resolveValue(manifest, value);
|
|
2565
|
-
if (resolved === void 0) {
|
|
2566
|
-
continue;
|
|
2567
|
-
}
|
|
2568
|
-
env[key] = resolved;
|
|
2569
|
-
}
|
|
2570
|
-
return env;
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
// src/chat/plugins/auth/api-headers-broker.ts
|
|
2574
|
-
import { randomUUID } from "crypto";
|
|
2575
|
-
var MAX_LEASE_MS = 60 * 60 * 1e3;
|
|
2576
|
-
var ENV_PLACEHOLDER_RE3 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
2577
|
-
function resolveHeaders(provider, headers) {
|
|
2578
|
-
return Object.fromEntries(
|
|
2579
|
-
Object.entries(headers).map(([key, value]) => {
|
|
2580
|
-
const resolved = value.replace(ENV_PLACEHOLDER_RE3, (_match, name) => {
|
|
2581
|
-
const envName = name;
|
|
2582
|
-
const envValue = process.env[envName]?.trim();
|
|
2583
|
-
if (!envValue) {
|
|
2584
|
-
throw new Error(
|
|
2585
|
-
`Missing ${envName} for API header provider "${provider}"`
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
return envValue;
|
|
2589
|
-
});
|
|
2590
|
-
return [key, resolved];
|
|
2591
|
-
})
|
|
2592
|
-
);
|
|
2593
|
-
}
|
|
2594
|
-
function resolveApiHeaderTransforms(manifest) {
|
|
2595
|
-
const { domains, apiHeaders } = manifest;
|
|
2596
|
-
if (!domains || !apiHeaders) {
|
|
2597
|
-
return [];
|
|
2598
|
-
}
|
|
2599
|
-
const resolvedHeaders = resolveHeaders(manifest.name, apiHeaders);
|
|
2600
|
-
return domains.map((domain) => ({
|
|
2601
|
-
domain,
|
|
2602
|
-
headers: resolvedHeaders
|
|
2603
|
-
}));
|
|
2604
|
-
}
|
|
2605
|
-
function createApiHeadersBroker(manifest) {
|
|
2606
|
-
const provider = manifest.name;
|
|
2607
|
-
return {
|
|
2608
|
-
async issue(input) {
|
|
2609
|
-
const headerTransforms = resolveApiHeaderTransforms(manifest);
|
|
2610
|
-
if (headerTransforms.length === 0) {
|
|
2611
|
-
throw new Error(`No API headers configured for plugin "${provider}"`);
|
|
2612
|
-
}
|
|
2613
|
-
return {
|
|
2614
|
-
id: randomUUID(),
|
|
2615
|
-
provider,
|
|
2616
|
-
env: resolvePluginCommandEnv(manifest),
|
|
2617
|
-
headerTransforms,
|
|
2618
|
-
expiresAt: new Date(Date.now() + MAX_LEASE_MS).toISOString(),
|
|
2619
|
-
metadata: {
|
|
2620
|
-
reason: input.reason
|
|
2621
|
-
}
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
};
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
// src/chat/plugins/auth/auth-token-placeholder.ts
|
|
2628
|
-
var DEFAULT_PLACEHOLDERS = {
|
|
2629
|
-
"oauth-bearer": "host_managed_credential",
|
|
2630
|
-
"github-app": "ghp_host_managed_credential"
|
|
2631
|
-
};
|
|
2632
|
-
function resolveAuthTokenPlaceholder(credentials) {
|
|
2633
|
-
return credentials.authTokenPlaceholder?.trim() || DEFAULT_PLACEHOLDERS[credentials.type];
|
|
2634
|
-
}
|
|
2635
|
-
|
|
2636
|
-
// src/chat/plugins/auth/github-app-broker.ts
|
|
2637
|
-
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
2638
|
-
function base64Url(input) {
|
|
2639
|
-
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2640
|
-
}
|
|
2641
|
-
function normalizePrivateKey(raw) {
|
|
2642
|
-
let normalized = raw.trim();
|
|
2643
|
-
if (normalized.startsWith('"') && normalized.endsWith('"') || normalized.startsWith("'") && normalized.endsWith("'")) {
|
|
2644
|
-
normalized = normalized.slice(1, -1);
|
|
2645
|
-
}
|
|
2646
|
-
normalized = normalized.replace(/\r\n/g, "\n");
|
|
2647
|
-
if (normalized.includes("\\n")) {
|
|
2648
|
-
normalized = normalized.replace(/\\n/g, "\n");
|
|
2649
|
-
}
|
|
2650
|
-
if (!normalized.includes("-----BEGIN")) {
|
|
2651
|
-
try {
|
|
2652
|
-
const decoded = Buffer.from(normalized, "base64").toString("utf8").trim();
|
|
2653
|
-
if (decoded.includes("-----BEGIN")) {
|
|
2654
|
-
normalized = decoded;
|
|
2655
|
-
}
|
|
2656
|
-
} catch {
|
|
1010
|
+
if (!result.success) {
|
|
1011
|
+
throw new Error(issueMessage(result.error, `Plugin ${data.name} target`));
|
|
1012
|
+
}
|
|
1013
|
+
if (!SHORT_CONFIG_KEY_RE.test(result.data["config-key"])) {
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" is invalid`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
const qualifiedKey = `${data.name}.${result.data["config-key"]}`;
|
|
1019
|
+
if (!configKeys.includes(qualifiedKey)) {
|
|
1020
|
+
throw new Error(
|
|
1021
|
+
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" must be listed in config-keys`
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
const commandFlags = result.data["command-flags"];
|
|
1025
|
+
if (commandFlags && commandFlags.some((flag) => !TARGET_FLAG_RE.test(flag))) {
|
|
1026
|
+
throw new Error(
|
|
1027
|
+
`Plugin ${data.name} target.command-flags must contain CLI flags like --repo or -R`
|
|
1028
|
+
);
|
|
2657
1029
|
}
|
|
1030
|
+
manifest.target = {
|
|
1031
|
+
type: result.data.type,
|
|
1032
|
+
configKey: qualifiedKey,
|
|
1033
|
+
...commandFlags ? { commandFlags } : {}
|
|
1034
|
+
};
|
|
2658
1035
|
}
|
|
2659
|
-
return
|
|
1036
|
+
return manifest;
|
|
2660
1037
|
}
|
|
2661
|
-
function
|
|
2662
|
-
|
|
2663
|
-
if (!raw) {
|
|
2664
|
-
throw new Error(`Missing ${envName}`);
|
|
2665
|
-
}
|
|
2666
|
-
const normalized = normalizePrivateKey(raw);
|
|
2667
|
-
let key;
|
|
1038
|
+
function parsePluginManifest(raw, dir, config) {
|
|
1039
|
+
let parsedYaml;
|
|
2668
1040
|
try {
|
|
2669
|
-
|
|
2670
|
-
} catch {
|
|
2671
|
-
throw new Error(
|
|
2672
|
-
`Invalid ${envName}: expected a PEM-encoded RSA private key (raw PEM, escaped newlines, or base64-encoded PEM)`
|
|
2673
|
-
);
|
|
2674
|
-
}
|
|
2675
|
-
if (key.asymmetricKeyType !== "rsa") {
|
|
1041
|
+
parsedYaml = parseYaml(raw);
|
|
1042
|
+
} catch (error) {
|
|
2676
1043
|
throw new Error(
|
|
2677
|
-
`Invalid ${
|
|
1044
|
+
`Invalid plugin manifest in ${dir}: ${error instanceof Error ? error.message : String(error)}`
|
|
2678
1045
|
);
|
|
2679
1046
|
}
|
|
2680
|
-
|
|
2681
|
-
}
|
|
2682
|
-
function createAppJwt(appId, privateKeyEnv) {
|
|
2683
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
2684
|
-
const header = { alg: "RS256", typ: "JWT" };
|
|
2685
|
-
const payload = { iat: now - 60, exp: now + 9 * 60, iss: appId };
|
|
2686
|
-
const encodedHeader = base64Url(JSON.stringify(header));
|
|
2687
|
-
const encodedPayload = base64Url(JSON.stringify(payload));
|
|
2688
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
2689
|
-
const signer = createSign("RSA-SHA256");
|
|
2690
|
-
signer.update(signingInput);
|
|
2691
|
-
signer.end();
|
|
2692
|
-
const signature = signer.sign(getPrivateKey(privateKeyEnv)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2693
|
-
return `${signingInput}.${signature}`;
|
|
2694
|
-
}
|
|
2695
|
-
function resolveAppId(appIdEnv) {
|
|
2696
|
-
const appId = process.env[appIdEnv]?.trim();
|
|
2697
|
-
if (!appId) {
|
|
2698
|
-
throw new Error(`Missing ${appIdEnv}`);
|
|
2699
|
-
}
|
|
2700
|
-
return appId;
|
|
2701
|
-
}
|
|
2702
|
-
async function githubRequest(apiBase, path3, params) {
|
|
2703
|
-
const response = await fetch(`${apiBase}${path3}`, {
|
|
2704
|
-
method: params.method ?? "GET",
|
|
2705
|
-
headers: {
|
|
2706
|
-
Accept: "application/vnd.github+json",
|
|
2707
|
-
Authorization: `Bearer ${params.token}`,
|
|
2708
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
2709
|
-
...params.body ? { "Content-Type": "application/json" } : {}
|
|
2710
|
-
},
|
|
2711
|
-
...params.body ? { body: JSON.stringify(params.body) } : {}
|
|
2712
|
-
});
|
|
2713
|
-
const text = await response.text();
|
|
2714
|
-
let parsed = void 0;
|
|
2715
|
-
if (text) {
|
|
2716
|
-
try {
|
|
2717
|
-
parsed = JSON.parse(text);
|
|
2718
|
-
} catch {
|
|
2719
|
-
parsed = void 0;
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
if (!response.ok) {
|
|
2723
|
-
const message = parsed && typeof parsed === "object" && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `GitHub API error ${response.status}`;
|
|
2724
|
-
throw new Error(message);
|
|
1047
|
+
if (!parsedYaml || typeof parsedYaml !== "object" || Array.isArray(parsedYaml)) {
|
|
1048
|
+
throw new Error(`Invalid plugin manifest in ${dir}: expected an object`);
|
|
2725
1049
|
}
|
|
2726
|
-
return
|
|
1050
|
+
return parseManifestSource(parsedYaml, dir, config);
|
|
2727
1051
|
}
|
|
2728
|
-
function
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
return normalizedDomain === "api.github.com" || normalizedDomain.startsWith("api.");
|
|
1052
|
+
function parseInlinePluginManifest(manifest, dir, config) {
|
|
1053
|
+
return parseManifestSource(inlineManifestSource(manifest), dir, config, {
|
|
1054
|
+
allowHookManagedEgress: true
|
|
2732
1055
|
});
|
|
2733
|
-
if (!apiDomain) {
|
|
2734
|
-
throw new Error("GitHub App provider requires an API domain");
|
|
2735
|
-
}
|
|
2736
|
-
return apiDomain;
|
|
2737
|
-
}
|
|
2738
|
-
function createGitHubAppBroker(manifest, credentials) {
|
|
2739
|
-
const provider = manifest.name;
|
|
2740
|
-
const {
|
|
2741
|
-
domains,
|
|
2742
|
-
apiHeaders,
|
|
2743
|
-
authTokenEnv,
|
|
2744
|
-
appIdEnv,
|
|
2745
|
-
privateKeyEnv,
|
|
2746
|
-
installationIdEnv
|
|
2747
|
-
} = credentials;
|
|
2748
|
-
const apiDomain = resolveGitHubApiDomain(credentials);
|
|
2749
|
-
const apiBase = `https://${apiDomain}`;
|
|
2750
|
-
const placeholder = resolveAuthTokenPlaceholder(credentials);
|
|
2751
|
-
const pluginHeaderTransforms = () => resolveApiHeaderTransforms(manifest);
|
|
2752
|
-
const leaseDomains = [...new Set(domains)];
|
|
2753
|
-
function authorizationFor(domain, token) {
|
|
2754
|
-
if (isGitSmartHttpDomain(domain)) {
|
|
2755
|
-
return `Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
|
|
2756
|
-
}
|
|
2757
|
-
return `Bearer ${token}`;
|
|
2758
|
-
}
|
|
2759
|
-
function isGitSmartHttpDomain(domain) {
|
|
2760
|
-
const normalizedDomain = domain.toLowerCase();
|
|
2761
|
-
const normalizedApiDomain = apiDomain.toLowerCase();
|
|
2762
|
-
return normalizedDomain === "github.com" || normalizedApiDomain.startsWith("api.") && normalizedDomain === normalizedApiDomain.slice("api.".length);
|
|
2763
|
-
}
|
|
2764
|
-
const permissions = manifest.capabilities?.length ? githubCapabilitiesToPermissions(manifest.capabilities, provider) : void 0;
|
|
2765
|
-
const systemReadPermissions = credentials.systemReadPermissions?.length ? githubSystemReadPermissionsFromScopes(credentials.systemReadPermissions) : void 0;
|
|
2766
|
-
async function resolveTokenPermissions(params) {
|
|
2767
|
-
if (!params.systemActor) {
|
|
2768
|
-
return permissions;
|
|
2769
|
-
}
|
|
2770
|
-
if (systemReadPermissions) {
|
|
2771
|
-
return systemReadPermissions;
|
|
2772
|
-
}
|
|
2773
|
-
const installation = await githubRequest(apiBase, `/app/installations/${params.installationId}`, {
|
|
2774
|
-
token: params.appJwt
|
|
2775
|
-
});
|
|
2776
|
-
return githubInstallationReadPermissions(
|
|
2777
|
-
installation.permissions,
|
|
2778
|
-
DEFAULT_GITHUB_SYSTEM_READ_SCOPES
|
|
2779
|
-
);
|
|
2780
|
-
}
|
|
2781
|
-
function createLease(params) {
|
|
2782
|
-
return {
|
|
2783
|
-
id: randomUUID2(),
|
|
2784
|
-
provider,
|
|
2785
|
-
env: {
|
|
2786
|
-
...resolvePluginCommandEnv(manifest),
|
|
2787
|
-
[authTokenEnv]: placeholder
|
|
2788
|
-
},
|
|
2789
|
-
headerTransforms: mergeHeaderTransforms([
|
|
2790
|
-
...pluginHeaderTransforms(),
|
|
2791
|
-
...leaseDomains.map((domain) => ({
|
|
2792
|
-
domain,
|
|
2793
|
-
headers: {
|
|
2794
|
-
...apiHeaders ?? {},
|
|
2795
|
-
Authorization: authorizationFor(domain, params.token)
|
|
2796
|
-
}
|
|
2797
|
-
}))
|
|
2798
|
-
]),
|
|
2799
|
-
expiresAt: new Date(params.expiresAtMs).toISOString(),
|
|
2800
|
-
metadata: {
|
|
2801
|
-
installationId: String(params.installationId),
|
|
2802
|
-
reason: params.reason
|
|
2803
|
-
}
|
|
2804
|
-
};
|
|
2805
|
-
}
|
|
2806
|
-
function resolveInstallationId() {
|
|
2807
|
-
const installationIdRaw = process.env[installationIdEnv]?.trim();
|
|
2808
|
-
if (!installationIdRaw) {
|
|
2809
|
-
throw new Error(`Missing ${installationIdEnv}`);
|
|
2810
|
-
}
|
|
2811
|
-
const installationId = Number(installationIdRaw);
|
|
2812
|
-
if (!Number.isFinite(installationId)) {
|
|
2813
|
-
throw new Error(`Invalid ${installationIdEnv}`);
|
|
2814
|
-
}
|
|
2815
|
-
return installationId;
|
|
2816
|
-
}
|
|
2817
|
-
return {
|
|
2818
|
-
async issue(input) {
|
|
2819
|
-
const installationId = resolveInstallationId();
|
|
2820
|
-
const appId = resolveAppId(appIdEnv);
|
|
2821
|
-
const appJwt = createAppJwt(appId, privateKeyEnv);
|
|
2822
|
-
const tokenPermissions = await resolveTokenPermissions({
|
|
2823
|
-
appJwt,
|
|
2824
|
-
installationId,
|
|
2825
|
-
systemActor: input.context.actor.type === "system"
|
|
2826
|
-
});
|
|
2827
|
-
const tokenRequestBody = tokenPermissions ? { permissions: tokenPermissions } : {};
|
|
2828
|
-
const accessTokenResponse = await githubRequest(apiBase, `/app/installations/${installationId}/access_tokens`, {
|
|
2829
|
-
method: "POST",
|
|
2830
|
-
token: appJwt,
|
|
2831
|
-
body: tokenRequestBody
|
|
2832
|
-
});
|
|
2833
|
-
const providerExpiresAtMs = Date.parse(accessTokenResponse.expires_at);
|
|
2834
|
-
const expiresAtMs = Math.min(
|
|
2835
|
-
providerExpiresAtMs,
|
|
2836
|
-
Date.now() + MAX_LEASE_MS2
|
|
2837
|
-
);
|
|
2838
|
-
return createLease({
|
|
2839
|
-
installationId,
|
|
2840
|
-
token: accessTokenResponse.token,
|
|
2841
|
-
expiresAtMs,
|
|
2842
|
-
reason: input.reason
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
};
|
|
2846
1056
|
}
|
|
2847
1057
|
|
|
1058
|
+
// src/chat/plugins/registry.ts
|
|
1059
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
1060
|
+
import path from "path";
|
|
1061
|
+
|
|
2848
1062
|
// src/chat/plugins/auth/oauth-bearer-broker.ts
|
|
2849
|
-
import { randomUUID as
|
|
1063
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2850
1064
|
|
|
2851
1065
|
// src/chat/credentials/broker.ts
|
|
2852
1066
|
var CredentialUnavailableError = class extends Error {
|
|
@@ -2858,6 +1072,9 @@ var CredentialUnavailableError = class extends Error {
|
|
|
2858
1072
|
}
|
|
2859
1073
|
};
|
|
2860
1074
|
|
|
1075
|
+
// src/chat/credentials/context.ts
|
|
1076
|
+
import { z as z2 } from "zod";
|
|
1077
|
+
|
|
2861
1078
|
// src/chat/services/requester-identity.ts
|
|
2862
1079
|
var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
2863
1080
|
var EMAIL_PATTERN = /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/;
|
|
@@ -2943,83 +1160,56 @@ function slackActorIdentity(userId, profile) {
|
|
|
2943
1160
|
}
|
|
2944
1161
|
|
|
2945
1162
|
// src/chat/credentials/context.ts
|
|
1163
|
+
var exactActorIdSchema = z2.string().refine((value) => parseActorUserId(value) === value);
|
|
1164
|
+
var credentialSubjectBindingSchema = z2.object({
|
|
1165
|
+
type: z2.literal("slack-direct-conversation"),
|
|
1166
|
+
teamId: z2.string().min(1),
|
|
1167
|
+
channelId: z2.string().min(1),
|
|
1168
|
+
signature: z2.string().min(1)
|
|
1169
|
+
}).strict();
|
|
1170
|
+
var credentialUserActorSchema = z2.object({
|
|
1171
|
+
type: z2.literal("user"),
|
|
1172
|
+
userId: exactActorIdSchema
|
|
1173
|
+
}).strict();
|
|
1174
|
+
var credentialSystemActorSchema = z2.object({
|
|
1175
|
+
type: z2.literal("system"),
|
|
1176
|
+
id: exactActorIdSchema
|
|
1177
|
+
}).strict();
|
|
1178
|
+
var credentialSubjectSchema = z2.object({
|
|
1179
|
+
type: z2.literal("user"),
|
|
1180
|
+
userId: exactActorIdSchema,
|
|
1181
|
+
allowedWhen: z2.literal("private-direct-conversation"),
|
|
1182
|
+
binding: credentialSubjectBindingSchema
|
|
1183
|
+
}).strict();
|
|
1184
|
+
var credentialContextSchema = z2.union([
|
|
1185
|
+
z2.object({
|
|
1186
|
+
actor: credentialUserActorSchema
|
|
1187
|
+
}).strict(),
|
|
1188
|
+
z2.object({
|
|
1189
|
+
actor: credentialSystemActorSchema,
|
|
1190
|
+
subject: credentialSubjectSchema.optional()
|
|
1191
|
+
}).strict()
|
|
1192
|
+
]);
|
|
2946
1193
|
function credentialUserSubjectId(context) {
|
|
2947
1194
|
if (context.actor.type === "user") {
|
|
2948
1195
|
return context.actor.userId;
|
|
2949
1196
|
}
|
|
2950
|
-
return context.subject?.userId;
|
|
2951
|
-
}
|
|
2952
|
-
function parseCredentialContext(value) {
|
|
2953
|
-
if (!value || typeof value !== "object") {
|
|
2954
|
-
return void 0;
|
|
2955
|
-
}
|
|
2956
|
-
const record = value;
|
|
2957
|
-
const actor = parseActor(record.actor);
|
|
2958
|
-
if (!actor) {
|
|
2959
|
-
return void 0;
|
|
2960
|
-
}
|
|
2961
|
-
if (actor.type === "user") {
|
|
2962
|
-
if ("subject" in record && record.subject !== void 0) {
|
|
2963
|
-
return void 0;
|
|
2964
|
-
}
|
|
2965
|
-
return { actor };
|
|
2966
|
-
}
|
|
2967
|
-
if (!("subject" in record) || record.subject === void 0) {
|
|
2968
|
-
return { actor };
|
|
2969
|
-
}
|
|
2970
|
-
const subject = parseSubject(record.subject);
|
|
2971
|
-
if (!subject) {
|
|
2972
|
-
return void 0;
|
|
2973
|
-
}
|
|
2974
|
-
return {
|
|
2975
|
-
actor,
|
|
2976
|
-
subject
|
|
2977
|
-
};
|
|
2978
|
-
}
|
|
2979
|
-
function parseActor(value) {
|
|
2980
|
-
if (value && typeof value === "object") {
|
|
2981
|
-
const record = value;
|
|
2982
|
-
const userId = parseActorUserId(
|
|
2983
|
-
typeof record.userId === "string" ? record.userId : void 0
|
|
2984
|
-
);
|
|
2985
|
-
if (record.type === "user" && userId) {
|
|
2986
|
-
return { type: "user", userId };
|
|
2987
|
-
}
|
|
2988
|
-
const systemId = typeof record.id === "string" && record.id.length > 0 && record.id === record.id.trim() && record.id.toLowerCase() !== "unknown" ? record.id : void 0;
|
|
2989
|
-
if (record.type === "system" && systemId) {
|
|
2990
|
-
return { type: "system", id: systemId };
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
return void 0;
|
|
1197
|
+
return "subject" in context ? context.subject?.userId : void 0;
|
|
2994
1198
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
}
|
|
3005
|
-
const binding = record.binding;
|
|
3006
|
-
if (binding.type !== "slack-direct-conversation" || typeof binding.teamId !== "string" || !binding.teamId || typeof binding.channelId !== "string" || !binding.channelId || typeof binding.signature !== "string" || !binding.signature) {
|
|
3007
|
-
return void 0;
|
|
3008
|
-
}
|
|
3009
|
-
return {
|
|
3010
|
-
type: "user",
|
|
3011
|
-
userId,
|
|
3012
|
-
allowedWhen: "private-direct-conversation",
|
|
3013
|
-
binding: {
|
|
3014
|
-
type: "slack-direct-conversation",
|
|
3015
|
-
teamId: binding.teamId,
|
|
3016
|
-
channelId: binding.channelId,
|
|
3017
|
-
signature: binding.signature
|
|
3018
|
-
}
|
|
3019
|
-
};
|
|
3020
|
-
}
|
|
1199
|
+
|
|
1200
|
+
// src/chat/credentials/header-transforms.ts
|
|
1201
|
+
function mergeHeaderTransforms(transforms) {
|
|
1202
|
+
const byDomain = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const transform of transforms) {
|
|
1204
|
+
byDomain.set(transform.domain, {
|
|
1205
|
+
...byDomain.get(transform.domain) ?? {},
|
|
1206
|
+
...transform.headers
|
|
1207
|
+
});
|
|
3021
1208
|
}
|
|
3022
|
-
return
|
|
1209
|
+
return [...byDomain.entries()].map(([domain, headers]) => ({
|
|
1210
|
+
domain,
|
|
1211
|
+
headers
|
|
1212
|
+
}));
|
|
3023
1213
|
}
|
|
3024
1214
|
|
|
3025
1215
|
// src/chat/credentials/oauth-scope.ts
|
|
@@ -3045,6 +1235,99 @@ function hasRequiredOAuthScope(storedScope, requiredScope) {
|
|
|
3045
1235
|
return required.every((scope) => stored.has(scope));
|
|
3046
1236
|
}
|
|
3047
1237
|
|
|
1238
|
+
// src/chat/plugins/command-env.ts
|
|
1239
|
+
var ENV_PLACEHOLDER_RE2 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
1240
|
+
function resolveValue(manifest, value) {
|
|
1241
|
+
let missing = false;
|
|
1242
|
+
const resolved = value.replace(ENV_PLACEHOLDER_RE2, (match, name) => {
|
|
1243
|
+
const envName = name;
|
|
1244
|
+
const declaration = manifest.envVars?.[envName];
|
|
1245
|
+
if (!declaration || declaration.default !== void 0) {
|
|
1246
|
+
return match;
|
|
1247
|
+
}
|
|
1248
|
+
const hostValue = process.env[envName];
|
|
1249
|
+
if (hostValue === void 0 || hostValue === "") {
|
|
1250
|
+
missing = true;
|
|
1251
|
+
return "";
|
|
1252
|
+
}
|
|
1253
|
+
return hostValue;
|
|
1254
|
+
});
|
|
1255
|
+
return missing ? void 0 : resolved;
|
|
1256
|
+
}
|
|
1257
|
+
function resolvePluginCommandEnv(manifest) {
|
|
1258
|
+
const env = {};
|
|
1259
|
+
for (const [key, value] of Object.entries(manifest.commandEnv ?? {})) {
|
|
1260
|
+
const resolved = resolveValue(manifest, value);
|
|
1261
|
+
if (resolved === void 0) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
env[key] = resolved;
|
|
1265
|
+
}
|
|
1266
|
+
return env;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/chat/plugins/auth/auth-token-placeholder.ts
|
|
1270
|
+
var DEFAULT_PLACEHOLDERS = {
|
|
1271
|
+
"oauth-bearer": "host_managed_credential"
|
|
1272
|
+
};
|
|
1273
|
+
function resolveAuthTokenPlaceholder(credentials) {
|
|
1274
|
+
return credentials.authTokenPlaceholder?.trim() || DEFAULT_PLACEHOLDERS[credentials.type];
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// src/chat/plugins/auth/api-headers-broker.ts
|
|
1278
|
+
import { randomUUID } from "crypto";
|
|
1279
|
+
var MAX_LEASE_MS = 60 * 60 * 1e3;
|
|
1280
|
+
var ENV_PLACEHOLDER_RE3 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
1281
|
+
function resolveHeaders(provider, headers) {
|
|
1282
|
+
return Object.fromEntries(
|
|
1283
|
+
Object.entries(headers).map(([key, value]) => {
|
|
1284
|
+
const resolved = value.replace(ENV_PLACEHOLDER_RE3, (_match, name) => {
|
|
1285
|
+
const envName = name;
|
|
1286
|
+
const envValue = process.env[envName]?.trim();
|
|
1287
|
+
if (!envValue) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`Missing ${envName} for API header provider "${provider}"`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
return envValue;
|
|
1293
|
+
});
|
|
1294
|
+
return [key, resolved];
|
|
1295
|
+
})
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
function resolveApiHeaderTransforms(manifest) {
|
|
1299
|
+
const { domains, apiHeaders } = manifest;
|
|
1300
|
+
if (!domains || !apiHeaders) {
|
|
1301
|
+
return [];
|
|
1302
|
+
}
|
|
1303
|
+
const resolvedHeaders = resolveHeaders(manifest.name, apiHeaders);
|
|
1304
|
+
return domains.map((domain) => ({
|
|
1305
|
+
domain,
|
|
1306
|
+
headers: resolvedHeaders
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
function createApiHeadersBroker(manifest) {
|
|
1310
|
+
const provider = manifest.name;
|
|
1311
|
+
return {
|
|
1312
|
+
async issue(input) {
|
|
1313
|
+
const headerTransforms = resolveApiHeaderTransforms(manifest);
|
|
1314
|
+
if (headerTransforms.length === 0) {
|
|
1315
|
+
throw new Error(`No API headers configured for plugin "${provider}"`);
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
id: randomUUID(),
|
|
1319
|
+
provider,
|
|
1320
|
+
env: resolvePluginCommandEnv(manifest),
|
|
1321
|
+
headerTransforms,
|
|
1322
|
+
expiresAt: new Date(Date.now() + MAX_LEASE_MS).toISOString(),
|
|
1323
|
+
metadata: {
|
|
1324
|
+
reason: input.reason
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
3048
1331
|
// src/chat/plugins/auth/oauth-request.ts
|
|
3049
1332
|
var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
3050
1333
|
function requireNonEmptyTokenField(data, field) {
|
|
@@ -3092,19 +1375,26 @@ function buildOAuthTokenRequest(input) {
|
|
|
3092
1375
|
body: contentTypeToBody(contentType, payload)
|
|
3093
1376
|
};
|
|
3094
1377
|
}
|
|
3095
|
-
function parseOAuthTokenResponse(data,
|
|
1378
|
+
function parseOAuthTokenResponse(data, requestedScope, options) {
|
|
3096
1379
|
const accessToken = requireNonEmptyTokenField(data, "access_token");
|
|
3097
1380
|
const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
|
|
3098
1381
|
const expiresIn = data.expires_in;
|
|
3099
1382
|
const responseScope = data.scope;
|
|
3100
1383
|
let scope;
|
|
3101
1384
|
if (responseScope !== void 0) {
|
|
3102
|
-
if (typeof responseScope !== "string"
|
|
1385
|
+
if (typeof responseScope !== "string") {
|
|
3103
1386
|
throw new Error("OAuth token response returned invalid scope");
|
|
3104
1387
|
}
|
|
3105
|
-
|
|
1388
|
+
const normalized = normalizeOAuthScope(responseScope);
|
|
1389
|
+
if (normalized !== void 0) {
|
|
1390
|
+
scope = normalized;
|
|
1391
|
+
} else if (options?.treatEmptyScopeAsUnreported) {
|
|
1392
|
+
scope = normalizeOAuthScope(requestedScope);
|
|
1393
|
+
} else {
|
|
1394
|
+
throw new Error("OAuth token response returned empty scope");
|
|
1395
|
+
}
|
|
3106
1396
|
} else {
|
|
3107
|
-
scope = normalizeOAuthScope(
|
|
1397
|
+
scope = normalizeOAuthScope(requestedScope);
|
|
3108
1398
|
}
|
|
3109
1399
|
if (expiresIn === void 0) {
|
|
3110
1400
|
return { accessToken, refreshToken, ...scope ? { scope } : {} };
|
|
@@ -3121,9 +1411,26 @@ function parseOAuthTokenResponse(data, fallbackScope) {
|
|
|
3121
1411
|
}
|
|
3122
1412
|
|
|
3123
1413
|
// src/chat/plugins/auth/oauth-bearer-broker.ts
|
|
3124
|
-
var
|
|
1414
|
+
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
3125
1415
|
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
3126
|
-
|
|
1416
|
+
var OAuthRefreshRejectedError = class extends Error {
|
|
1417
|
+
constructor(message) {
|
|
1418
|
+
super(message);
|
|
1419
|
+
this.name = "OAuthRefreshRejectedError";
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
function parseRefreshError(text) {
|
|
1423
|
+
if (!text.trim()) {
|
|
1424
|
+
return void 0;
|
|
1425
|
+
}
|
|
1426
|
+
try {
|
|
1427
|
+
const parsed = JSON.parse(text);
|
|
1428
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.error === "string" ? parsed.error : void 0;
|
|
1429
|
+
} catch {
|
|
1430
|
+
return void 0;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
async function refreshAccessToken(refreshToken, oauth, requestedScope) {
|
|
3127
1434
|
const clientId = process.env[oauth.clientIdEnv]?.trim();
|
|
3128
1435
|
const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
|
|
3129
1436
|
if (!clientId || !clientSecret) {
|
|
@@ -3147,13 +1454,23 @@ async function refreshAccessToken(refreshToken, oauth, fallbackScope) {
|
|
|
3147
1454
|
body: request.body
|
|
3148
1455
|
});
|
|
3149
1456
|
if (!response.ok) {
|
|
3150
|
-
|
|
1457
|
+
const errorCode = parseRefreshError(await response.text());
|
|
1458
|
+
if (errorCode === "invalid_grant" || errorCode === "bad_refresh_token") {
|
|
1459
|
+
throw new OAuthRefreshRejectedError(
|
|
1460
|
+
`Token refresh rejected: ${errorCode}`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
throw new Error(
|
|
1464
|
+
`Token refresh failed: ${response.status}${errorCode ? ` ${errorCode}` : ""}`
|
|
1465
|
+
);
|
|
3151
1466
|
}
|
|
3152
1467
|
const data = await response.json();
|
|
3153
|
-
return parseOAuthTokenResponse(data,
|
|
1468
|
+
return parseOAuthTokenResponse(data, requestedScope, {
|
|
1469
|
+
treatEmptyScopeAsUnreported: oauth.treatEmptyScopeAsUnreported
|
|
1470
|
+
});
|
|
3154
1471
|
}
|
|
3155
1472
|
function getLeaseExpiry(expiresAt) {
|
|
3156
|
-
return expiresAt ? Math.min(expiresAt, Date.now() +
|
|
1473
|
+
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
|
|
3157
1474
|
}
|
|
3158
1475
|
function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
3159
1476
|
const provider = manifest.name;
|
|
@@ -3162,7 +1479,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
3162
1479
|
const pluginHeaderTransforms = () => resolveApiHeaderTransforms(manifest);
|
|
3163
1480
|
function buildLease(token, expiresAtMs, reason) {
|
|
3164
1481
|
return {
|
|
3165
|
-
id:
|
|
1482
|
+
id: randomUUID2(),
|
|
3166
1483
|
provider,
|
|
3167
1484
|
env: {
|
|
3168
1485
|
...resolvePluginCommandEnv(manifest),
|
|
@@ -3186,7 +1503,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
3186
1503
|
const userSubjectId = credentialUserSubjectId(input.context);
|
|
3187
1504
|
if (!oauth) {
|
|
3188
1505
|
if (envToken) {
|
|
3189
|
-
return buildLease(envToken, Date.now() +
|
|
1506
|
+
return buildLease(envToken, Date.now() + MAX_LEASE_MS2, input.reason);
|
|
3190
1507
|
}
|
|
3191
1508
|
throw new CredentialUnavailableError(
|
|
3192
1509
|
provider,
|
|
@@ -3216,7 +1533,15 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
3216
1533
|
`Your ${provider} connection needs to be reauthorized.`
|
|
3217
1534
|
);
|
|
3218
1535
|
}
|
|
3219
|
-
|
|
1536
|
+
const refreshedTokens = {
|
|
1537
|
+
...refreshed,
|
|
1538
|
+
...stored.account ? { account: stored.account } : {}
|
|
1539
|
+
};
|
|
1540
|
+
await deps.userTokenStore.set(
|
|
1541
|
+
userSubjectId,
|
|
1542
|
+
provider,
|
|
1543
|
+
refreshedTokens
|
|
1544
|
+
);
|
|
3220
1545
|
return buildLease(
|
|
3221
1546
|
refreshed.accessToken,
|
|
3222
1547
|
getLeaseExpiry(refreshed.expiresAt),
|
|
@@ -3226,17 +1551,13 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
3226
1551
|
if (error instanceof CredentialUnavailableError) {
|
|
3227
1552
|
throw error;
|
|
3228
1553
|
}
|
|
3229
|
-
if (
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
input.reason
|
|
1554
|
+
if (error instanceof OAuthRefreshRejectedError) {
|
|
1555
|
+
throw new CredentialUnavailableError(
|
|
1556
|
+
provider,
|
|
1557
|
+
`Your ${provider} connection has expired.`
|
|
3234
1558
|
);
|
|
3235
1559
|
}
|
|
3236
|
-
throw
|
|
3237
|
-
provider,
|
|
3238
|
-
`Your ${provider} connection has expired.`
|
|
3239
|
-
);
|
|
1560
|
+
throw error;
|
|
3240
1561
|
}
|
|
3241
1562
|
}
|
|
3242
1563
|
if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
|
|
@@ -3336,14 +1657,14 @@ function registerYamlPluginManifest(state, raw, pluginDir) {
|
|
|
3336
1657
|
state,
|
|
3337
1658
|
manifest,
|
|
3338
1659
|
pluginDir,
|
|
3339
|
-
|
|
1660
|
+
path.join(pluginDir, "skills")
|
|
3340
1661
|
);
|
|
3341
1662
|
}
|
|
3342
1663
|
function normalizePluginRoots(roots) {
|
|
3343
1664
|
const resolved = [];
|
|
3344
1665
|
const seen = /* @__PURE__ */ new Set();
|
|
3345
1666
|
for (const root of roots) {
|
|
3346
|
-
const normalized =
|
|
1667
|
+
const normalized = path.resolve(root);
|
|
3347
1668
|
if (seen.has(normalized)) {
|
|
3348
1669
|
continue;
|
|
3349
1670
|
}
|
|
@@ -3402,7 +1723,7 @@ function registerInlineManifests(state, source) {
|
|
|
3402
1723
|
for (const definition of source.inlineManifests) {
|
|
3403
1724
|
const pkg = definition.packageName ? packageContentByName(source.packagedContent, definition.packageName) : void 0;
|
|
3404
1725
|
const dir = pkg?.dir ?? process.cwd();
|
|
3405
|
-
const skillsDir = pkg?.hasSkillsDir ?
|
|
1726
|
+
const skillsDir = pkg?.hasSkillsDir ? path.join(pkg.dir, "skills") : void 0;
|
|
3406
1727
|
const manifest = parseInlinePluginManifest(
|
|
3407
1728
|
definition.manifest,
|
|
3408
1729
|
dir,
|
|
@@ -3441,7 +1762,7 @@ function buildLoadedPluginState(source) {
|
|
|
3441
1762
|
continue;
|
|
3442
1763
|
}
|
|
3443
1764
|
if (rootStat.isDirectory()) {
|
|
3444
|
-
const manifestPath =
|
|
1765
|
+
const manifestPath = path.join(pluginsRoot, "plugin.yaml");
|
|
3445
1766
|
let hasRootManifest = false;
|
|
3446
1767
|
try {
|
|
3447
1768
|
hasRootManifest = statSync(manifestPath).isFile();
|
|
@@ -3469,14 +1790,14 @@ function buildLoadedPluginState(source) {
|
|
|
3469
1790
|
continue;
|
|
3470
1791
|
}
|
|
3471
1792
|
for (const entry of entries.sort()) {
|
|
3472
|
-
const pluginDir =
|
|
1793
|
+
const pluginDir = path.join(pluginsRoot, entry);
|
|
3473
1794
|
try {
|
|
3474
1795
|
const stat = statSync(pluginDir);
|
|
3475
1796
|
if (!stat.isDirectory()) continue;
|
|
3476
1797
|
} catch {
|
|
3477
1798
|
continue;
|
|
3478
1799
|
}
|
|
3479
|
-
const manifestPath =
|
|
1800
|
+
const manifestPath = path.join(pluginDir, "plugin.yaml");
|
|
3480
1801
|
let raw;
|
|
3481
1802
|
try {
|
|
3482
1803
|
raw = readFileSync(manifestPath, "utf8");
|
|
@@ -3618,6 +1939,7 @@ function getPluginOAuthConfig(provider) {
|
|
|
3618
1939
|
...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : {},
|
|
3619
1940
|
...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : {},
|
|
3620
1941
|
...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : {},
|
|
1942
|
+
...oauth.treatEmptyScopeAsUnreported ? { treatEmptyScopeAsUnreported: true } : {},
|
|
3621
1943
|
callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
|
|
3622
1944
|
};
|
|
3623
1945
|
}
|
|
@@ -3634,13 +1956,13 @@ function getPluginSkillRoots() {
|
|
|
3634
1956
|
}
|
|
3635
1957
|
function getPluginForSkillPath(skillPath) {
|
|
3636
1958
|
const state = ensurePluginsLoaded();
|
|
3637
|
-
const resolvedSkillPath =
|
|
1959
|
+
const resolvedSkillPath = path.resolve(skillPath);
|
|
3638
1960
|
return state.pluginDefinitions.find((plugin) => {
|
|
3639
1961
|
if (!plugin.skillsDir) {
|
|
3640
1962
|
return false;
|
|
3641
1963
|
}
|
|
3642
|
-
const resolvedSkillsDir =
|
|
3643
|
-
return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${
|
|
1964
|
+
const resolvedSkillsDir = path.resolve(plugin.skillsDir);
|
|
1965
|
+
return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${path.sep}`);
|
|
3644
1966
|
});
|
|
3645
1967
|
}
|
|
3646
1968
|
function getPluginDefinition(provider) {
|
|
@@ -3666,12 +1988,8 @@ function createPluginBroker(provider, deps) {
|
|
|
3666
1988
|
let broker;
|
|
3667
1989
|
if (!credentials) {
|
|
3668
1990
|
broker = createApiHeadersBroker(plugin.manifest);
|
|
3669
|
-
} else if (credentials.type === "oauth-bearer") {
|
|
3670
|
-
broker = createOAuthBearerBroker(plugin.manifest, credentials, deps);
|
|
3671
|
-
} else if (credentials.type === "github-app") {
|
|
3672
|
-
broker = createGitHubAppBroker(plugin.manifest, credentials);
|
|
3673
1991
|
} else {
|
|
3674
|
-
|
|
1992
|
+
broker = createOAuthBearerBroker(plugin.manifest, credentials, deps);
|
|
3675
1993
|
}
|
|
3676
1994
|
setSpanAttributes({
|
|
3677
1995
|
"app.plugin.name": name,
|
|
@@ -3682,30 +2000,6 @@ function createPluginBroker(provider, deps) {
|
|
|
3682
2000
|
}
|
|
3683
2001
|
|
|
3684
2002
|
export {
|
|
3685
|
-
toOptionalString,
|
|
3686
|
-
toOptionalNumber,
|
|
3687
|
-
isRecord,
|
|
3688
|
-
normalizeGenAiFinishReason,
|
|
3689
|
-
createChatSdkLogger,
|
|
3690
|
-
getLogContextAttributes,
|
|
3691
|
-
setSentryUser,
|
|
3692
|
-
logInfo,
|
|
3693
|
-
logWarn,
|
|
3694
|
-
logError,
|
|
3695
|
-
logException,
|
|
3696
|
-
setTags,
|
|
3697
|
-
createRequestContext,
|
|
3698
|
-
withContext,
|
|
3699
|
-
withSpan,
|
|
3700
|
-
setSpanAttributes,
|
|
3701
|
-
setSpanStatus,
|
|
3702
|
-
getActiveTraceId,
|
|
3703
|
-
buildTurnFailureResponse,
|
|
3704
|
-
serializeGenAiAttribute,
|
|
3705
|
-
extractGenAiUsageSummary,
|
|
3706
|
-
extractGenAiUsageAttributes,
|
|
3707
|
-
resolvePluginCommandEnv,
|
|
3708
|
-
resolveAuthTokenPlaceholder,
|
|
3709
2003
|
parsePluginManifest,
|
|
3710
2004
|
parseInlinePluginManifest,
|
|
3711
2005
|
CredentialUnavailableError,
|
|
@@ -3713,8 +2007,10 @@ export {
|
|
|
3713
2007
|
isActorUserId,
|
|
3714
2008
|
buildActorIdentity,
|
|
3715
2009
|
slackActorIdentity,
|
|
3716
|
-
|
|
2010
|
+
credentialContextSchema,
|
|
3717
2011
|
hasRequiredOAuthScope,
|
|
2012
|
+
resolvePluginCommandEnv,
|
|
2013
|
+
resolveAuthTokenPlaceholder,
|
|
3718
2014
|
buildOAuthTokenRequest,
|
|
3719
2015
|
parseOAuthTokenResponse,
|
|
3720
2016
|
setPluginCatalogConfig,
|