@sentry/junior 0.203.0 → 0.204.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/agent-CMyTD4DD.js +17509 -0
- package/dist/agent-hooks-Bbcp9e33.js +4040 -0
- package/dist/agent-runner-DNb1A49M.js +678 -0
- package/dist/api/schema.d.ts +2728 -2639
- package/dist/api/schema.js +4 -169
- package/dist/api.d.ts +23 -17
- package/dist/api.js +3881 -5326
- package/dist/app-B1TOqBI8.d.ts +246 -0
- package/dist/app.d.ts +4 -520
- package/dist/app.js +9522 -11875
- package/dist/auth-pause-state-DosEalIG.js +168 -0
- package/dist/catalog-runtime-BReos-D4.js +7 -0
- package/dist/checkpoint-CNrPyRJN.js +2450 -0
- package/dist/cli/chat.d.ts +20 -0
- package/dist/cli/chat.js +229 -336
- package/dist/cli/check.d.ts +8 -0
- package/dist/cli/check.js +513 -806
- package/dist/cli/env.d.ts +13 -0
- package/dist/cli/env.js +63 -12
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.js +90 -159
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.js +43 -64
- package/dist/cli/plugins.d.ts +13 -0
- package/dist/cli/plugins.js +148 -227
- package/dist/cli/run.d.ts +16 -0
- package/dist/cli/run.js +52 -8
- package/dist/cli/snapshot-create.d.ts +4 -0
- package/dist/cli/snapshot-create.js +71 -125
- package/dist/cli/upgrade.d.ts +15 -0
- package/dist/cli/upgrade.js +232 -335
- package/dist/config-nGCW1OTG.js +941 -0
- package/dist/context-B45_WqVX.js +298 -0
- package/dist/conversation-privacy-B4voFRSm.js +182 -0
- package/dist/conversation-work-C6y3T5Hx.js +51 -0
- package/dist/credential-sync-kXr1hvO1.js +2660 -0
- package/dist/db-BeyXKJOI.d.ts +5 -0
- package/dist/destinations-BeBiJeEM.js +29 -0
- package/dist/execution-stats-DWfSihcH.js +373 -0
- package/dist/executor-DcMSlI5c.js +721 -0
- package/dist/experimental-CIXf7Uye.js +46 -0
- package/dist/history-ClE4toxe.d.ts +32 -0
- package/dist/history-CqTuSAdI.js +441 -0
- package/dist/instrumentation.d.ts +3 -3
- package/dist/instrumentation.js +130 -176
- package/dist/job-queue-Drn_LXlM.js +63 -0
- package/dist/logging-BzAk1S_s.js +1210 -0
- package/dist/model-profile-CZmVoGdX.js +49 -0
- package/dist/nitro.d.ts +31 -36
- package/dist/nitro.js +275 -456
- package/dist/oauth-callback-server-JiftokvA.js +130 -0
- package/dist/oauth-relay-CdCnu80W.js +62 -0
- package/dist/package-discovery-7a9YqLhE.js +455 -0
- package/dist/pending-auth-CWmSdZVg.js +185 -0
- package/dist/plugin-module-BKGOXca2.js +81 -0
- package/dist/plugins-BI9A1vKA.d.ts +95 -0
- package/dist/plugins-egKWjgue.js +99 -0
- package/dist/private-trace-result-BLvBjy_e.js +16 -0
- package/dist/profile-CrT5nXbw.js +861 -0
- package/dist/registry-DH1xUo2N.js +1419 -0
- package/dist/reporting-schema-DosTN__Q.js +105 -0
- package/dist/resolve-CZHnfxaS.js +546 -0
- package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
- package/dist/runner-CZV2sC46.js +392 -0
- package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
- package/dist/schema-Rn8-XEaN.js +1140 -0
- package/dist/sentry-CD3SdJl-.js +26 -0
- package/dist/skills-CIcDQSgM.js +316 -0
- package/dist/spawn-DdkUFRmq.js +29 -0
- package/dist/stats-DTUbAb88.js +2634 -0
- package/dist/store-C5qxF2ac.d.ts +208 -0
- package/dist/task-queue-CJIR4RF7.js +136 -0
- package/dist/task-runner-D-O3A-Cp.js +639 -0
- package/dist/thread-state-CRc9jTJS.js +431 -0
- package/dist/turn-execution-BkinIdJF.js +47 -0
- package/dist/turn-lifecycle-DQV19VTW.js +54 -0
- package/dist/usage-D3EbSO7w.js +296 -0
- package/dist/usage-schema-CKaDkH_j.js +20 -0
- package/dist/validation-C2CR-OX3.js +40 -0
- package/dist/vercel-queue-DIkVcfK4.js +167 -0
- package/dist/vercel.d.ts +6 -6
- package/dist/vercel.js +14 -6
- package/dist/version-CnXZ614E.d.ts +5 -0
- package/dist/version-DxcWdoKa.js +8 -0
- package/dist/version.d.ts +2 -4
- package/dist/version.js +2 -6
- package/dist/web-authorization-RMBxAR_t.js +76 -0
- package/dist/web-input-3EPfpf55.js +494 -0
- package/dist/work-fGofI7Op.js +509 -0
- package/dist/worker-CsOLq-mR.js +625 -0
- package/package.json +8 -8
- package/dist/agent-hooks-GB2VPTNA.js +0 -51
- package/dist/catalog-runtime-UBLIWD3O.js +0 -14
- package/dist/chunk-2LYHWMR4.js +0 -46
- package/dist/chunk-352OWAZH.js +0 -320
- package/dist/chunk-3Y47VMHV.js +0 -6
- package/dist/chunk-3YJSKOWF.js +0 -164
- package/dist/chunk-4KSSKTQ5.js +0 -229
- package/dist/chunk-4UO6FK4G.js +0 -64
- package/dist/chunk-5AHX53NQ.js +0 -717
- package/dist/chunk-5J3E5FWA.js +0 -700
- package/dist/chunk-5PDAG6O3.js +0 -206
- package/dist/chunk-6Y5B5PEX.js +0 -80
- package/dist/chunk-7GWQ5S5R.js +0 -2086
- package/dist/chunk-7H2VZTWN.js +0 -103
- package/dist/chunk-7NL7T6VU.js +0 -406
- package/dist/chunk-A5L27LE4.js +0 -387
- package/dist/chunk-ADIAWL34.js +0 -81
- package/dist/chunk-ASTEMBKJ.js +0 -519
- package/dist/chunk-CEMMLV5L.js +0 -98
- package/dist/chunk-CM5HNV2W.js +0 -131
- package/dist/chunk-CRTRBBRI.js +0 -105
- package/dist/chunk-DA2KGZZI.js +0 -13
- package/dist/chunk-DIB4GX3G.js +0 -8
- package/dist/chunk-DKIJ2RP5.js +0 -68
- package/dist/chunk-DWPN6SCH.js +0 -172
- package/dist/chunk-EDKUFGPL.js +0 -64
- package/dist/chunk-ENY6SKIU.js +0 -1228
- package/dist/chunk-F4MGCZOX.js +0 -2943
- package/dist/chunk-FA6POGLH.js +0 -70
- package/dist/chunk-FSUR7GTG.js +0 -20475
- package/dist/chunk-GKEOHJL5.js +0 -1856
- package/dist/chunk-GMCB6W5Y.js +0 -333
- package/dist/chunk-IE2VPG7O.js +0 -1135
- package/dist/chunk-IZ5B5IHQ.js +0 -3656
- package/dist/chunk-JAL3CH4G.js +0 -27
- package/dist/chunk-KIIEUXD3.js +0 -1764
- package/dist/chunk-KXU4NIL7.js +0 -692
- package/dist/chunk-MTM5IFCY.js +0 -1190
- package/dist/chunk-N7TMSOZH.js +0 -235
- package/dist/chunk-NCEBK6PH.js +0 -36
- package/dist/chunk-NHJU2ZAL.js +0 -56
- package/dist/chunk-P6KTRNVC.js +0 -321
- package/dist/chunk-PDO5BLNM.js +0 -22
- package/dist/chunk-PDRNLLFT.js +0 -3295
- package/dist/chunk-PEQXYSSE.js +0 -45
- package/dist/chunk-PKEX2AQX.js +0 -198
- package/dist/chunk-PSJCGIGO.js +0 -1163
- package/dist/chunk-PZUSUL55.js +0 -506
- package/dist/chunk-R45M4TUS.js +0 -37
- package/dist/chunk-RC745GGZ.js +0 -122
- package/dist/chunk-SP3QKZXJ.js +0 -820
- package/dist/chunk-TXKUKMVO.js +0 -417
- package/dist/chunk-UE4GPWSD.js +0 -931
- package/dist/chunk-UIE3R5XU.js +0 -17
- package/dist/chunk-UXPG6ZIN.js +0 -84
- package/dist/chunk-VFUK3X5B.js +0 -50
- package/dist/chunk-VNMJWREK.js +0 -787
- package/dist/chunk-VZ7CSOX2.js +0 -706
- package/dist/chunk-WCNQJKFT.js +0 -10
- package/dist/chunk-WDZR3QQM.js +0 -135
- package/dist/chunk-WLYVJCHV.js +0 -1433
- package/dist/chunk-WS7NAFTM.js +0 -1520
- package/dist/chunk-X6GDDWQH.js +0 -60
- package/dist/chunk-XPDEYNQI.js +0 -102
- package/dist/chunk-XRW44VGM.js +0 -58
- package/dist/chunk-Y2CM7HXH.js +0 -111
- package/dist/chunk-Y7X25LFY.js +0 -15
- package/dist/conversation-work-VIOQ5ZF3.js +0 -54
- package/dist/db-NGDJV7YH.js +0 -26
- package/dist/discovery-DWVDM7UK.js +0 -44
- package/dist/experimental-P5FZM4CU.js +0 -12
- package/dist/oauth-callback-server-YFY7YEGK.js +0 -243
- package/dist/oauth-relay-5XPVRPYB.js +0 -8
- package/dist/plugins-YTYJMMJF.js +0 -14
- package/dist/runner-ORTNSB2Q.js +0 -522
- package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
- package/dist/sentry-MB3NL3IR.js +0 -42
- package/dist/skills-H376WQOH.js +0 -27
- package/dist/spawn-T43WHLOS.js +0 -42
- package/dist/task-runner-LBZWF32V.js +0 -36
- package/dist/validation-REJLRKI6.js +0 -17
- package/dist/work-3KJ3RE76.js +0 -51
- package/dist/worker-QFEBU72E.js +0 -24
package/dist/chunk-7GWQ5S5R.js
DELETED
|
@@ -1,2086 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
credentialUserSubjectId
|
|
3
|
-
} from "./chunk-CEMMLV5L.js";
|
|
4
|
-
import {
|
|
5
|
-
discoverInstalledPluginPackageContent,
|
|
6
|
-
normalizePluginPackageNames
|
|
7
|
-
} from "./chunk-DWPN6SCH.js";
|
|
8
|
-
import {
|
|
9
|
-
pluginRoots
|
|
10
|
-
} from "./chunk-TXKUKMVO.js";
|
|
11
|
-
import {
|
|
12
|
-
logInfo,
|
|
13
|
-
logWarn,
|
|
14
|
-
setSpanAttributes
|
|
15
|
-
} from "./chunk-WS7NAFTM.js";
|
|
16
|
-
|
|
17
|
-
// src/chat/plugins/manifest.ts
|
|
18
|
-
import { z } from "zod";
|
|
19
|
-
import { parse as parseYaml } from "yaml";
|
|
20
|
-
|
|
21
|
-
// src/chat/plugins/inline-manifest-source.ts
|
|
22
|
-
function setDefined(target, key, value) {
|
|
23
|
-
if (value !== void 0) {
|
|
24
|
-
target[key] = value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function isRecord(value) {
|
|
28
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
29
|
-
}
|
|
30
|
-
function unqualifyManifestToken(name, value) {
|
|
31
|
-
if (typeof name === "string" && typeof value === "string" && value.startsWith(`${name}.`)) {
|
|
32
|
-
return value.slice(name.length + 1);
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
function inlineTokenListSource(name, values) {
|
|
37
|
-
if (values === void 0 || !Array.isArray(values)) {
|
|
38
|
-
return values;
|
|
39
|
-
}
|
|
40
|
-
return values.map((value) => unqualifyManifestToken(name, value));
|
|
41
|
-
}
|
|
42
|
-
function inlineCredentialsSource(credentials) {
|
|
43
|
-
if (credentials === void 0 || !isRecord(credentials)) {
|
|
44
|
-
return credentials;
|
|
45
|
-
}
|
|
46
|
-
const result = {};
|
|
47
|
-
setDefined(result, "type", credentials.type);
|
|
48
|
-
setDefined(result, "domains", credentials.domains);
|
|
49
|
-
setDefined(result, "api-headers", credentials.apiHeaders);
|
|
50
|
-
setDefined(result, "auth-token-env", credentials.authTokenEnv);
|
|
51
|
-
setDefined(
|
|
52
|
-
result,
|
|
53
|
-
"auth-token-placeholder",
|
|
54
|
-
credentials.authTokenPlaceholder
|
|
55
|
-
);
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
function inlineMcpSource(mcp) {
|
|
59
|
-
if (mcp === void 0 || !isRecord(mcp)) {
|
|
60
|
-
return mcp;
|
|
61
|
-
}
|
|
62
|
-
const result = {};
|
|
63
|
-
setDefined(result, "transport", mcp.transport);
|
|
64
|
-
setDefined(result, "url", mcp.url);
|
|
65
|
-
setDefined(result, "headers", mcp.headers);
|
|
66
|
-
setDefined(result, "allowed-tools", mcp.allowedTools);
|
|
67
|
-
setDefined(result, "wrapped-tools", mcp.wrappedTools);
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
function inlineOauthSource(oauth) {
|
|
71
|
-
if (oauth === void 0 || !isRecord(oauth)) {
|
|
72
|
-
return oauth;
|
|
73
|
-
}
|
|
74
|
-
const result = {};
|
|
75
|
-
setDefined(result, "client-id-env", oauth.clientIdEnv);
|
|
76
|
-
setDefined(result, "client-secret-env", oauth.clientSecretEnv);
|
|
77
|
-
setDefined(result, "authorize-endpoint", oauth.authorizeEndpoint);
|
|
78
|
-
setDefined(result, "token-endpoint", oauth.tokenEndpoint);
|
|
79
|
-
setDefined(result, "scope", oauth.scope);
|
|
80
|
-
setDefined(result, "authorize-params", oauth.authorizeParams);
|
|
81
|
-
setDefined(result, "token-auth-method", oauth.tokenAuthMethod);
|
|
82
|
-
setDefined(result, "token-extra-headers", oauth.tokenExtraHeaders);
|
|
83
|
-
setDefined(
|
|
84
|
-
result,
|
|
85
|
-
"treat-empty-scope-as-unreported",
|
|
86
|
-
oauth.treatEmptyScopeAsUnreported
|
|
87
|
-
);
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
function inlineTargetSource(name, target) {
|
|
91
|
-
if (target === void 0 || !isRecord(target)) {
|
|
92
|
-
return target;
|
|
93
|
-
}
|
|
94
|
-
const result = {};
|
|
95
|
-
setDefined(result, "type", target.type);
|
|
96
|
-
setDefined(
|
|
97
|
-
result,
|
|
98
|
-
"config-key",
|
|
99
|
-
unqualifyManifestToken(name, target.configKey)
|
|
100
|
-
);
|
|
101
|
-
setDefined(result, "command-flags", target.commandFlags);
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
function inlineManifestSource(manifest) {
|
|
105
|
-
const result = {};
|
|
106
|
-
setDefined(result, "name", manifest.name);
|
|
107
|
-
setDefined(result, "display-name", manifest.displayName);
|
|
108
|
-
setDefined(result, "description", manifest.description);
|
|
109
|
-
setDefined(
|
|
110
|
-
result,
|
|
111
|
-
"config-keys",
|
|
112
|
-
inlineTokenListSource(manifest.name, manifest.configKeys)
|
|
113
|
-
);
|
|
114
|
-
setDefined(result, "domains", manifest.domains);
|
|
115
|
-
setDefined(result, "api-headers", manifest.apiHeaders);
|
|
116
|
-
setDefined(result, "command-env", manifest.commandEnv);
|
|
117
|
-
setDefined(result, "env-vars", manifest.envVars);
|
|
118
|
-
setDefined(
|
|
119
|
-
result,
|
|
120
|
-
"credentials",
|
|
121
|
-
inlineCredentialsSource(manifest.credentials)
|
|
122
|
-
);
|
|
123
|
-
setDefined(result, "runtime-dependencies", manifest.runtimeDependencies);
|
|
124
|
-
setDefined(result, "runtime-postinstall", manifest.runtimePostinstall);
|
|
125
|
-
setDefined(result, "mcp", inlineMcpSource(manifest.mcp));
|
|
126
|
-
setDefined(result, "oauth", inlineOauthSource(manifest.oauth));
|
|
127
|
-
setDefined(
|
|
128
|
-
result,
|
|
129
|
-
"target",
|
|
130
|
-
inlineTargetSource(manifest.name, manifest.target)
|
|
131
|
-
);
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// src/chat/plugins/manifest.ts
|
|
136
|
-
var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
137
|
-
var SHORT_CONFIG_KEY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
|
|
138
|
-
var TARGET_FLAG_RE = /^-{1,2}[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
139
|
-
var AUTH_TOKEN_ENV_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
140
|
-
var ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
141
|
-
var ENV_PLACEHOLDER_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
142
|
-
var API_DOMAIN_RE = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
143
|
-
var RUNTIME_POSTINSTALL_CMD_RE = /^[A-Za-z0-9._/-]+$/;
|
|
144
|
-
var RESERVED_AUTHORIZE_PARAM_KEYS = /* @__PURE__ */ new Set([
|
|
145
|
-
"client_id",
|
|
146
|
-
"scope",
|
|
147
|
-
"state",
|
|
148
|
-
"redirect_uri",
|
|
149
|
-
"response_type"
|
|
150
|
-
]);
|
|
151
|
-
var FORBIDDEN_API_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
152
|
-
var FORBIDDEN_TOKEN_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
153
|
-
var trimmedString = z.string().transform((value) => value.trim());
|
|
154
|
-
var nonEmptyTrimmedString = trimmedString.pipe(
|
|
155
|
-
z.string().min(1, { error: "must be a non-empty string" })
|
|
156
|
-
);
|
|
157
|
-
var nonEmptyStringArraySchema = (fieldName, options = {}) => z.array(z.string(), {
|
|
158
|
-
error: "must be an array of strings when provided"
|
|
159
|
-
}).min(1, {
|
|
160
|
-
error: options.nonEmptyMessage ?? "must be a non-empty array of strings when provided"
|
|
161
|
-
}).transform((values, ctx) => {
|
|
162
|
-
const result = [];
|
|
163
|
-
const seen = /* @__PURE__ */ new Set();
|
|
164
|
-
for (const rawValue of values) {
|
|
165
|
-
const value = rawValue.trim();
|
|
166
|
-
if (!value) {
|
|
167
|
-
ctx.addIssue({
|
|
168
|
-
code: z.ZodIssueCode.custom,
|
|
169
|
-
message: `${fieldName} entries must be non-empty strings`
|
|
170
|
-
});
|
|
171
|
-
return z.NEVER;
|
|
172
|
-
}
|
|
173
|
-
if (seen.has(value)) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
seen.add(value);
|
|
177
|
-
result.push(value);
|
|
178
|
-
}
|
|
179
|
-
return result;
|
|
180
|
-
});
|
|
181
|
-
var envVarString = nonEmptyTrimmedString.refine(
|
|
182
|
-
(value) => AUTH_TOKEN_ENV_RE.test(value),
|
|
183
|
-
{
|
|
184
|
-
error: "must be an uppercase env var name"
|
|
185
|
-
}
|
|
186
|
-
);
|
|
187
|
-
var httpsUrlString = nonEmptyTrimmedString.superRefine((value, ctx) => {
|
|
188
|
-
let parsed;
|
|
189
|
-
try {
|
|
190
|
-
parsed = new URL(value);
|
|
191
|
-
} catch {
|
|
192
|
-
ctx.addIssue({
|
|
193
|
-
code: z.ZodIssueCode.custom,
|
|
194
|
-
message: "must be a valid URL"
|
|
195
|
-
});
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (parsed.protocol !== "https:") {
|
|
199
|
-
ctx.addIssue({
|
|
200
|
-
code: z.ZodIssueCode.custom,
|
|
201
|
-
message: "must use https"
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
var stringMapSchema = z.record(z.string(), z.unknown()).transform((record, ctx) => {
|
|
206
|
-
const entries = Object.entries(record);
|
|
207
|
-
const result = {};
|
|
208
|
-
const seen = /* @__PURE__ */ new Set();
|
|
209
|
-
for (const [rawKey, rawValue] of entries) {
|
|
210
|
-
const key = rawKey.trim();
|
|
211
|
-
if (!key) {
|
|
212
|
-
ctx.addIssue({
|
|
213
|
-
code: z.ZodIssueCode.custom,
|
|
214
|
-
message: "keys must be non-empty strings"
|
|
215
|
-
});
|
|
216
|
-
return z.NEVER;
|
|
217
|
-
}
|
|
218
|
-
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
|
219
|
-
ctx.addIssue({
|
|
220
|
-
code: z.ZodIssueCode.custom,
|
|
221
|
-
message: `${key} must be a non-empty string`
|
|
222
|
-
});
|
|
223
|
-
return z.NEVER;
|
|
224
|
-
}
|
|
225
|
-
const normalizedKey = key.toLowerCase();
|
|
226
|
-
if (seen.has(normalizedKey)) {
|
|
227
|
-
ctx.addIssue({
|
|
228
|
-
code: z.ZodIssueCode.custom,
|
|
229
|
-
message: `${key} is duplicated`
|
|
230
|
-
});
|
|
231
|
-
return z.NEVER;
|
|
232
|
-
}
|
|
233
|
-
seen.add(normalizedKey);
|
|
234
|
-
result[key] = rawValue.trim();
|
|
235
|
-
}
|
|
236
|
-
return result;
|
|
237
|
-
});
|
|
238
|
-
var domainsSchema = z.array(z.unknown()).min(1, {
|
|
239
|
-
error: "must be a non-empty array of strings"
|
|
240
|
-
}).transform((domains, ctx) => {
|
|
241
|
-
return domains.map((rawDomain) => {
|
|
242
|
-
const domain = typeof rawDomain === "string" ? rawDomain.trim().toLowerCase() : "";
|
|
243
|
-
if (!domain) {
|
|
244
|
-
ctx.addIssue({
|
|
245
|
-
code: z.ZodIssueCode.custom,
|
|
246
|
-
message: "entries must be non-empty strings"
|
|
247
|
-
});
|
|
248
|
-
return z.NEVER;
|
|
249
|
-
}
|
|
250
|
-
if (!API_DOMAIN_RE.test(domain)) {
|
|
251
|
-
ctx.addIssue({
|
|
252
|
-
code: z.ZodIssueCode.custom,
|
|
253
|
-
message: "entries must be valid domain names"
|
|
254
|
-
});
|
|
255
|
-
return z.NEVER;
|
|
256
|
-
}
|
|
257
|
-
return domain;
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
var baseCredentialsSchema = z.object({
|
|
261
|
-
domains: domainsSchema.optional()
|
|
262
|
-
}).passthrough();
|
|
263
|
-
var oauthBearerCredentialsSchema = baseCredentialsSchema.extend({
|
|
264
|
-
"api-headers": stringMapSchema.optional(),
|
|
265
|
-
"auth-token-env": envVarString,
|
|
266
|
-
"auth-token-placeholder": nonEmptyTrimmedString.optional(),
|
|
267
|
-
type: z.literal("oauth-bearer")
|
|
268
|
-
});
|
|
269
|
-
var runtimeDependencyEntrySchema = z.object({
|
|
270
|
-
type: z.enum(["npm", "system"]),
|
|
271
|
-
package: z.string().optional(),
|
|
272
|
-
version: z.string().optional(),
|
|
273
|
-
url: z.string().optional(),
|
|
274
|
-
sha256: z.string().optional()
|
|
275
|
-
}).passthrough();
|
|
276
|
-
var runtimePostinstallCommandSourceSchema = z.object({
|
|
277
|
-
cmd: nonEmptyTrimmedString,
|
|
278
|
-
args: z.array(z.string(), {
|
|
279
|
-
error: "args must be an array of strings when provided"
|
|
280
|
-
}).optional(),
|
|
281
|
-
sudo: z.boolean({
|
|
282
|
-
error: "sudo must be a boolean when provided"
|
|
283
|
-
}).optional()
|
|
284
|
-
}).passthrough();
|
|
285
|
-
var oauthSourceSchema = z.object({
|
|
286
|
-
"client-id-env": envVarString,
|
|
287
|
-
"client-secret-env": envVarString,
|
|
288
|
-
"authorize-endpoint": httpsUrlString,
|
|
289
|
-
"token-endpoint": httpsUrlString,
|
|
290
|
-
scope: nonEmptyTrimmedString.optional(),
|
|
291
|
-
"authorize-params": stringMapSchema.optional(),
|
|
292
|
-
"token-extra-headers": stringMapSchema.optional(),
|
|
293
|
-
"token-auth-method": nonEmptyTrimmedString.refine((value) => value === "body" || value === "basic", {
|
|
294
|
-
error: 'must be "body" or "basic"'
|
|
295
|
-
}).optional(),
|
|
296
|
-
"treat-empty-scope-as-unreported": z.boolean().optional()
|
|
297
|
-
}).passthrough();
|
|
298
|
-
var mcpSourceSchema = z.object({
|
|
299
|
-
transport: nonEmptyTrimmedString.refine((value) => value === "http", {
|
|
300
|
-
error: 'must be "http"'
|
|
301
|
-
}).optional(),
|
|
302
|
-
url: httpsUrlString,
|
|
303
|
-
headers: stringMapSchema.optional(),
|
|
304
|
-
"allowed-tools": nonEmptyStringArraySchema("allowed-tools").optional(),
|
|
305
|
-
"wrapped-tools": nonEmptyStringArraySchema("wrapped-tools").optional()
|
|
306
|
-
}).passthrough();
|
|
307
|
-
var targetSourceSchema = z.object({
|
|
308
|
-
type: nonEmptyTrimmedString.refine((value) => PLUGIN_NAME_RE.test(value), {
|
|
309
|
-
error: "type must be a lowercase target identifier"
|
|
310
|
-
}),
|
|
311
|
-
"config-key": nonEmptyTrimmedString,
|
|
312
|
-
"command-flags": nonEmptyStringArraySchema("command-flags").optional()
|
|
313
|
-
}).passthrough();
|
|
314
|
-
var manifestSourceSchema = z.object({
|
|
315
|
-
name: z.string().refine((value) => PLUGIN_NAME_RE.test(value), {
|
|
316
|
-
error: "invalid"
|
|
317
|
-
}),
|
|
318
|
-
"display-name": nonEmptyTrimmedString,
|
|
319
|
-
description: nonEmptyTrimmedString,
|
|
320
|
-
"config-keys": z.array(z.string(), {
|
|
321
|
-
error: "must be an array when provided"
|
|
322
|
-
}).optional(),
|
|
323
|
-
domains: domainsSchema.optional(),
|
|
324
|
-
"api-headers": stringMapSchema.optional(),
|
|
325
|
-
"command-env": stringMapSchema.optional(),
|
|
326
|
-
credentials: z.record(z.string(), z.unknown(), {
|
|
327
|
-
error: "must be an object when provided"
|
|
328
|
-
}).optional(),
|
|
329
|
-
"runtime-dependencies": z.array(z.unknown(), {
|
|
330
|
-
error: "must be an array"
|
|
331
|
-
}).optional(),
|
|
332
|
-
"runtime-postinstall": z.array(z.unknown(), {
|
|
333
|
-
error: "must be an array"
|
|
334
|
-
}).optional(),
|
|
335
|
-
"env-vars": z.record(z.string(), z.unknown(), {
|
|
336
|
-
error: "must be an object"
|
|
337
|
-
}).optional(),
|
|
338
|
-
mcp: z.record(z.string(), z.unknown(), {
|
|
339
|
-
error: "must be an object"
|
|
340
|
-
}).optional(),
|
|
341
|
-
oauth: z.record(z.string(), z.unknown(), {
|
|
342
|
-
error: "must be an object"
|
|
343
|
-
}).optional(),
|
|
344
|
-
target: z.record(z.string(), z.unknown(), {
|
|
345
|
-
error: "must be an object"
|
|
346
|
-
}).optional()
|
|
347
|
-
}).passthrough();
|
|
348
|
-
function setDefined2(target, key, value) {
|
|
349
|
-
if (value !== void 0) {
|
|
350
|
-
target[key] = value;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function manifestConfigPatch(config) {
|
|
354
|
-
const result = {};
|
|
355
|
-
setDefined2(result, "display-name", config.displayName);
|
|
356
|
-
setDefined2(result, "description", config.description);
|
|
357
|
-
setDefined2(result, "config-keys", config.configKeys);
|
|
358
|
-
setDefined2(result, "domains", config.domains);
|
|
359
|
-
setDefined2(result, "api-headers", config.apiHeaders);
|
|
360
|
-
setDefined2(result, "command-env", config.commandEnv);
|
|
361
|
-
setDefined2(result, "env-vars", config.envVars);
|
|
362
|
-
if (config.credentials !== void 0) {
|
|
363
|
-
if (!config.credentials) {
|
|
364
|
-
result.credentials = null;
|
|
365
|
-
} else {
|
|
366
|
-
const credentials = {};
|
|
367
|
-
setDefined2(credentials, "type", config.credentials.type);
|
|
368
|
-
setDefined2(credentials, "domains", config.credentials.domains);
|
|
369
|
-
setDefined2(credentials, "api-headers", config.credentials.apiHeaders);
|
|
370
|
-
setDefined2(
|
|
371
|
-
credentials,
|
|
372
|
-
"auth-token-env",
|
|
373
|
-
config.credentials.authTokenEnv
|
|
374
|
-
);
|
|
375
|
-
setDefined2(
|
|
376
|
-
credentials,
|
|
377
|
-
"auth-token-placeholder",
|
|
378
|
-
config.credentials.authTokenPlaceholder
|
|
379
|
-
);
|
|
380
|
-
result.credentials = credentials;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
setDefined2(result, "runtime-dependencies", config.runtimeDependencies);
|
|
384
|
-
setDefined2(result, "runtime-postinstall", config.runtimePostinstall);
|
|
385
|
-
if (config.mcp !== void 0) {
|
|
386
|
-
if (!config.mcp) {
|
|
387
|
-
result.mcp = null;
|
|
388
|
-
} else {
|
|
389
|
-
const mcp = {};
|
|
390
|
-
setDefined2(mcp, "transport", config.mcp.transport);
|
|
391
|
-
setDefined2(mcp, "url", config.mcp.url);
|
|
392
|
-
setDefined2(mcp, "headers", config.mcp.headers);
|
|
393
|
-
setDefined2(mcp, "allowed-tools", config.mcp.allowedTools);
|
|
394
|
-
setDefined2(mcp, "wrapped-tools", config.mcp.wrappedTools);
|
|
395
|
-
result.mcp = mcp;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (config.oauth !== void 0) {
|
|
399
|
-
if (!config.oauth) {
|
|
400
|
-
result.oauth = null;
|
|
401
|
-
} else {
|
|
402
|
-
const oauth = {};
|
|
403
|
-
setDefined2(oauth, "client-id-env", config.oauth.clientIdEnv);
|
|
404
|
-
setDefined2(oauth, "client-secret-env", config.oauth.clientSecretEnv);
|
|
405
|
-
setDefined2(oauth, "authorize-endpoint", config.oauth.authorizeEndpoint);
|
|
406
|
-
setDefined2(oauth, "token-endpoint", config.oauth.tokenEndpoint);
|
|
407
|
-
setDefined2(oauth, "scope", config.oauth.scope);
|
|
408
|
-
setDefined2(oauth, "authorize-params", config.oauth.authorizeParams);
|
|
409
|
-
setDefined2(oauth, "token-auth-method", config.oauth.tokenAuthMethod);
|
|
410
|
-
setDefined2(oauth, "token-extra-headers", config.oauth.tokenExtraHeaders);
|
|
411
|
-
setDefined2(
|
|
412
|
-
oauth,
|
|
413
|
-
"treat-empty-scope-as-unreported",
|
|
414
|
-
config.oauth.treatEmptyScopeAsUnreported
|
|
415
|
-
);
|
|
416
|
-
result.oauth = oauth;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (config.target !== void 0) {
|
|
420
|
-
if (!config.target) {
|
|
421
|
-
result.target = null;
|
|
422
|
-
} else {
|
|
423
|
-
const target = {};
|
|
424
|
-
setDefined2(target, "type", config.target.type);
|
|
425
|
-
setDefined2(target, "config-key", config.target.configKey);
|
|
426
|
-
setDefined2(target, "command-flags", config.target.commandFlags);
|
|
427
|
-
result.target = target;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return result;
|
|
431
|
-
}
|
|
432
|
-
function isPlainRecord(value) {
|
|
433
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
434
|
-
}
|
|
435
|
-
function mergeManifestConfig(base, patch, options = {}) {
|
|
436
|
-
const merged = { ...base };
|
|
437
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
438
|
-
if (options.root && key === "name") {
|
|
439
|
-
throw new Error("plugins.manifests cannot change plugin names");
|
|
440
|
-
}
|
|
441
|
-
if (value === null) {
|
|
442
|
-
delete merged[key];
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
const current = merged[key];
|
|
446
|
-
if (isPlainRecord(current) && isPlainRecord(value)) {
|
|
447
|
-
merged[key] = mergeManifestConfig(current, value);
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
merged[key] = value;
|
|
451
|
-
}
|
|
452
|
-
return merged;
|
|
453
|
-
}
|
|
454
|
-
function applyManifestConfig(source, config) {
|
|
455
|
-
const name = source.name;
|
|
456
|
-
if (typeof name !== "string") {
|
|
457
|
-
return source;
|
|
458
|
-
}
|
|
459
|
-
const manifestConfig = config?.manifests?.[name];
|
|
460
|
-
if (manifestConfig && "name" in manifestConfig) {
|
|
461
|
-
throw new Error("plugins.manifests cannot change plugin names");
|
|
462
|
-
}
|
|
463
|
-
return manifestConfig ? mergeManifestConfig(source, manifestConfigPatch(manifestConfig), {
|
|
464
|
-
root: true
|
|
465
|
-
}) : source;
|
|
466
|
-
}
|
|
467
|
-
function formatPath(path2) {
|
|
468
|
-
return path2.map((segment) => String(segment)).join(".");
|
|
469
|
-
}
|
|
470
|
-
function issueMessage(error, prefix) {
|
|
471
|
-
const issue = error.issues[0];
|
|
472
|
-
if (!issue) {
|
|
473
|
-
return prefix;
|
|
474
|
-
}
|
|
475
|
-
const suffix = formatPath(issue.path);
|
|
476
|
-
return suffix ? `${prefix}.${suffix} ${issue.message}` : `${prefix} ${issue.message}`;
|
|
477
|
-
}
|
|
478
|
-
function normalizeStringMap(value, prefix, options = {}) {
|
|
479
|
-
if (!value) {
|
|
480
|
-
return void 0;
|
|
481
|
-
}
|
|
482
|
-
const keys = Object.keys(value);
|
|
483
|
-
if (keys.length === 0) {
|
|
484
|
-
return void 0;
|
|
485
|
-
}
|
|
486
|
-
for (const key of keys) {
|
|
487
|
-
const normalizedKey = key.toLowerCase();
|
|
488
|
-
if (options.reservedKeys?.has(normalizedKey)) {
|
|
489
|
-
throw new Error(`${prefix}.${key} is reserved by the runtime`);
|
|
490
|
-
}
|
|
491
|
-
if (options.forbiddenKeys?.has(normalizedKey)) {
|
|
492
|
-
throw new Error(`${prefix}.${key} is not allowed`);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return value;
|
|
496
|
-
}
|
|
497
|
-
function envReferences(value) {
|
|
498
|
-
return Array.from(value.matchAll(ENV_PLACEHOLDER_RE), (match) => {
|
|
499
|
-
return match[1];
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
function assertDeclaredEnvReferences(value, envVars, context) {
|
|
503
|
-
for (const name of envReferences(value)) {
|
|
504
|
-
if (!Object.prototype.hasOwnProperty.call(envVars, name)) {
|
|
505
|
-
throw new Error(
|
|
506
|
-
`${context} references env var ${name} which is not declared in env-vars`
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
if (envVars[name]?.default !== void 0) {
|
|
510
|
-
throw new Error(
|
|
511
|
-
`${context} references env var ${name}, but API header env vars must not declare defaults`
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function normalizeRequiredApiHeaders(value, prefix, envVars) {
|
|
517
|
-
const apiHeaders = normalizeStringMap(value, prefix);
|
|
518
|
-
if (!apiHeaders) {
|
|
519
|
-
throw new Error(`${prefix} must contain at least one header`);
|
|
520
|
-
}
|
|
521
|
-
for (const [key, headerValue] of Object.entries(apiHeaders)) {
|
|
522
|
-
assertDeclaredEnvReferences(headerValue, envVars, `${prefix}.${key}`);
|
|
523
|
-
}
|
|
524
|
-
return apiHeaders;
|
|
525
|
-
}
|
|
526
|
-
function assertCommandEnvReferencesDeclared(value, envVars, context) {
|
|
527
|
-
for (const name of envReferences(value)) {
|
|
528
|
-
if (!Object.prototype.hasOwnProperty.call(envVars, name)) {
|
|
529
|
-
throw new Error(
|
|
530
|
-
`${context} references env var ${name} which is not declared in env-vars`
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
function expandCommandEnvPlaceholders(template, envVars, context) {
|
|
536
|
-
return template.replace(ENV_PLACEHOLDER_RE, (match, name) => {
|
|
537
|
-
const varName = name;
|
|
538
|
-
const declaration = envVars[varName];
|
|
539
|
-
if (declaration?.default === void 0) {
|
|
540
|
-
return match;
|
|
541
|
-
}
|
|
542
|
-
return expandEnvPlaceholders(match, envVars, context);
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
function normalizeCommandEnv(value, prefix, envVars) {
|
|
546
|
-
const env = normalizeStringMap(value, prefix);
|
|
547
|
-
if (!env) {
|
|
548
|
-
throw new Error(`${prefix} must contain at least one env var`);
|
|
549
|
-
}
|
|
550
|
-
for (const [key, envValue] of Object.entries(env)) {
|
|
551
|
-
if (!ENV_VAR_NAME_RE.test(key)) {
|
|
552
|
-
throw new Error(`${prefix}.${key} must be an uppercase env var name`);
|
|
553
|
-
}
|
|
554
|
-
assertCommandEnvReferencesDeclared(envValue, envVars, `${prefix}.${key}`);
|
|
555
|
-
}
|
|
556
|
-
return Object.fromEntries(
|
|
557
|
-
Object.entries(env).map(([key, envValue]) => [
|
|
558
|
-
key,
|
|
559
|
-
expandCommandEnvPlaceholders(envValue, envVars, `${prefix}.${key}`)
|
|
560
|
-
])
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
function assertCommandEnvDoesNotExposeHostSecretRefs(commandEnv, apiHeaders, credentials, oauth, pluginName) {
|
|
564
|
-
if (!commandEnv) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const hostOnlyRefs = /* @__PURE__ */ new Set();
|
|
568
|
-
for (const value of Object.values(apiHeaders ?? {})) {
|
|
569
|
-
for (const name of envReferences(value)) {
|
|
570
|
-
hostOnlyRefs.add(name);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (credentials) {
|
|
574
|
-
if (credentials.authTokenEnv) {
|
|
575
|
-
hostOnlyRefs.add(credentials.authTokenEnv);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (oauth) {
|
|
579
|
-
hostOnlyRefs.add(oauth.clientIdEnv);
|
|
580
|
-
hostOnlyRefs.add(oauth.clientSecretEnv);
|
|
581
|
-
}
|
|
582
|
-
for (const [key, value] of Object.entries(commandEnv)) {
|
|
583
|
-
for (const name of envReferences(value)) {
|
|
584
|
-
if (hostOnlyRefs.has(name)) {
|
|
585
|
-
throw new Error(
|
|
586
|
-
`Plugin ${pluginName} command-env.${key} references env var ${name}, but credential/API header env vars must stay host-only`
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function assertCommandEnvHostRefsAreExplicitlyExposed(commandEnv, envVars, pluginName) {
|
|
593
|
-
if (!commandEnv) {
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
for (const [key, value] of Object.entries(commandEnv)) {
|
|
597
|
-
for (const name of envReferences(value)) {
|
|
598
|
-
const declaration = envVars[name];
|
|
599
|
-
if (declaration && declaration.default === void 0 && declaration.exposeToCommandEnv !== true) {
|
|
600
|
-
throw new Error(
|
|
601
|
-
`Plugin ${pluginName} command-env.${key} references env var ${name}, but env-vars.${name} must set expose-to-command-env: true before host env can be exposed to sandbox`
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
function normalizeCredentials(data, name) {
|
|
608
|
-
if (data.type !== "oauth-bearer") {
|
|
609
|
-
throw new Error(
|
|
610
|
-
`Plugin ${name} has unsupported credentials.type: "${String(data.type)}"`
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
const result = oauthBearerCredentialsSchema.safeParse(data);
|
|
614
|
-
if (!result.success) {
|
|
615
|
-
throw new Error(issueMessage(result.error, `Plugin ${name} credentials`));
|
|
616
|
-
}
|
|
617
|
-
if (!result.data.domains) {
|
|
618
|
-
throw new Error(`Plugin ${name} credentials requires domains`);
|
|
619
|
-
}
|
|
620
|
-
const domains = result.data.domains;
|
|
621
|
-
const apiHeaders = result.data["api-headers"] ? normalizeStringMap(
|
|
622
|
-
result.data["api-headers"],
|
|
623
|
-
`Plugin ${name} credentials.api-headers`,
|
|
624
|
-
{ forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }
|
|
625
|
-
) : void 0;
|
|
626
|
-
return {
|
|
627
|
-
type: "oauth-bearer",
|
|
628
|
-
domains,
|
|
629
|
-
...apiHeaders ? { apiHeaders } : void 0,
|
|
630
|
-
authTokenEnv: result.data["auth-token-env"],
|
|
631
|
-
...result.data["auth-token-placeholder"] ? { authTokenPlaceholder: result.data["auth-token-placeholder"] } : void 0
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
function normalizeRuntimeDependencies(entries, name) {
|
|
635
|
-
const parsed = [];
|
|
636
|
-
const seen = /* @__PURE__ */ new Set();
|
|
637
|
-
for (const entry of entries) {
|
|
638
|
-
const result = runtimeDependencyEntrySchema.safeParse(entry);
|
|
639
|
-
if (!result.success) {
|
|
640
|
-
throw new Error(
|
|
641
|
-
issueMessage(
|
|
642
|
-
result.error,
|
|
643
|
-
`Plugin ${name} runtime-dependencies entries`
|
|
644
|
-
)
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
const record = result.data;
|
|
648
|
-
const packageName = typeof record.package === "string" ? record.package.trim() : "";
|
|
649
|
-
const packageUrl = typeof record.url === "string" ? record.url.trim() : "";
|
|
650
|
-
const version = record.version;
|
|
651
|
-
const sha256 = record.sha256;
|
|
652
|
-
if (record.type === "npm") {
|
|
653
|
-
if (!packageName) {
|
|
654
|
-
throw new Error(
|
|
655
|
-
`Plugin ${name} runtime dependency package must be a non-empty string`
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
if (record.url !== void 0 || sha256 !== void 0) {
|
|
659
|
-
throw new Error(
|
|
660
|
-
`Plugin ${name} npm runtime dependencies must only include package/version fields`
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
const normalizedVersion = typeof version === "string" ? version.trim() : "latest";
|
|
664
|
-
if (!normalizedVersion) {
|
|
665
|
-
throw new Error(
|
|
666
|
-
`Plugin ${name} runtime dependency version must be a non-empty string when provided`
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
const dedupeKey2 = `${record.type}:${packageName}:${normalizedVersion}`;
|
|
670
|
-
if (seen.has(dedupeKey2)) {
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
seen.add(dedupeKey2);
|
|
674
|
-
parsed.push({
|
|
675
|
-
type: "npm",
|
|
676
|
-
package: packageName,
|
|
677
|
-
version: normalizedVersion
|
|
678
|
-
});
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (version !== void 0) {
|
|
682
|
-
throw new Error(
|
|
683
|
-
`Plugin ${name} system runtime dependencies must not include a version`
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
if (packageName && packageUrl) {
|
|
687
|
-
throw new Error(
|
|
688
|
-
`Plugin ${name} system runtime dependencies must specify either package or url, not both`
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
if (!packageName && !packageUrl) {
|
|
692
|
-
throw new Error(
|
|
693
|
-
`Plugin ${name} system runtime dependencies must specify package or url`
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
if (packageName) {
|
|
697
|
-
if (sha256 !== void 0) {
|
|
698
|
-
throw new Error(
|
|
699
|
-
`Plugin ${name} system runtime dependency package entries must not include sha256`
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
const dedupeKey2 = `${record.type}:package:${packageName}`;
|
|
703
|
-
if (seen.has(dedupeKey2)) {
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
seen.add(dedupeKey2);
|
|
707
|
-
parsed.push({
|
|
708
|
-
type: "system",
|
|
709
|
-
package: packageName
|
|
710
|
-
});
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
if (!/^https:\/\//i.test(packageUrl)) {
|
|
714
|
-
throw new Error(
|
|
715
|
-
`Plugin ${name} system runtime dependency url must be an https URL`
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
const normalizedSha256 = typeof sha256 === "string" ? sha256.trim().toLowerCase() : "";
|
|
719
|
-
if (!/^[a-f0-9]{64}$/.test(normalizedSha256)) {
|
|
720
|
-
throw new Error(
|
|
721
|
-
`Plugin ${name} system runtime dependency url entries must include a valid sha256`
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
const dedupeKey = `${record.type}:url:${packageUrl}:${normalizedSha256}`;
|
|
725
|
-
if (seen.has(dedupeKey)) {
|
|
726
|
-
continue;
|
|
727
|
-
}
|
|
728
|
-
seen.add(dedupeKey);
|
|
729
|
-
parsed.push({
|
|
730
|
-
type: "system",
|
|
731
|
-
url: packageUrl,
|
|
732
|
-
sha256: normalizedSha256
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
return parsed.length > 0 ? parsed : void 0;
|
|
736
|
-
}
|
|
737
|
-
function normalizeRuntimePostinstall(commands, name) {
|
|
738
|
-
const parsed = [];
|
|
739
|
-
for (const command of commands) {
|
|
740
|
-
const result = runtimePostinstallCommandSourceSchema.safeParse(command);
|
|
741
|
-
if (!result.success) {
|
|
742
|
-
throw new Error(
|
|
743
|
-
issueMessage(result.error, `Plugin ${name} runtime-postinstall`)
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
if (!RUNTIME_POSTINSTALL_CMD_RE.test(result.data.cmd)) {
|
|
747
|
-
throw new Error(
|
|
748
|
-
`Plugin ${name} runtime-postinstall cmd must be a single executable token (letters, digits, ., _, /, -)`
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
const normalizedArgs = result.data.args?.map((arg) => arg.trim()).filter((arg) => arg.length > 0);
|
|
752
|
-
parsed.push({
|
|
753
|
-
cmd: result.data.cmd,
|
|
754
|
-
...normalizedArgs && normalizedArgs.length > 0 ? { args: normalizedArgs } : void 0,
|
|
755
|
-
...typeof result.data.sudo === "boolean" ? { sudo: result.data.sudo } : void 0
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
return parsed.length > 0 ? parsed : void 0;
|
|
759
|
-
}
|
|
760
|
-
var envVarDeclarationSchema = z.preprocess(
|
|
761
|
-
(value) => value === null || value === void 0 ? {} : value,
|
|
762
|
-
z.object({
|
|
763
|
-
default: z.string().optional(),
|
|
764
|
-
"expose-to-command-env": z.boolean().optional(),
|
|
765
|
-
exposeToCommandEnv: z.boolean().optional()
|
|
766
|
-
}).strict()
|
|
767
|
-
);
|
|
768
|
-
function normalizeEnvVars(data, pluginName) {
|
|
769
|
-
const normalized = {};
|
|
770
|
-
for (const [rawName, rawDecl] of Object.entries(data)) {
|
|
771
|
-
const name = rawName.trim();
|
|
772
|
-
if (!ENV_VAR_NAME_RE.test(name)) {
|
|
773
|
-
throw new Error(
|
|
774
|
-
`Plugin ${pluginName} env-vars key "${rawName}" must match [A-Z_][A-Z0-9_]*`
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
const parsed = envVarDeclarationSchema.safeParse(rawDecl);
|
|
778
|
-
if (!parsed.success) {
|
|
779
|
-
throw new Error(
|
|
780
|
-
issueMessage(parsed.error, `Plugin ${pluginName} env-vars.${name}`)
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
const decl = {};
|
|
784
|
-
if (parsed.data.default !== void 0) {
|
|
785
|
-
decl.default = parsed.data.default;
|
|
786
|
-
}
|
|
787
|
-
if (parsed.data["expose-to-command-env"] === true || parsed.data.exposeToCommandEnv === true) {
|
|
788
|
-
decl.exposeToCommandEnv = true;
|
|
789
|
-
}
|
|
790
|
-
normalized[name] = decl;
|
|
791
|
-
}
|
|
792
|
-
return normalized;
|
|
793
|
-
}
|
|
794
|
-
function expandEnvPlaceholders(template, envVars, context) {
|
|
795
|
-
return template.replace(ENV_PLACEHOLDER_RE, (_match, name) => {
|
|
796
|
-
const varName = name;
|
|
797
|
-
if (!Object.prototype.hasOwnProperty.call(envVars, varName)) {
|
|
798
|
-
throw new Error(
|
|
799
|
-
`${context} references env var ${varName} which is not declared in env-vars`
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
const decl = envVars[varName];
|
|
803
|
-
const fromProcess = process.env[varName];
|
|
804
|
-
if (fromProcess !== void 0 && fromProcess !== "") {
|
|
805
|
-
return fromProcess;
|
|
806
|
-
}
|
|
807
|
-
if (decl.default !== void 0) {
|
|
808
|
-
return decl.default;
|
|
809
|
-
}
|
|
810
|
-
throw new Error(
|
|
811
|
-
`${context} env var ${varName} is unset and has no default in env-vars`
|
|
812
|
-
);
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
function normalizeMcp(data, envVars, name) {
|
|
816
|
-
const prepared = { ...data };
|
|
817
|
-
if (typeof prepared.url === "string") {
|
|
818
|
-
prepared.url = expandEnvPlaceholders(
|
|
819
|
-
prepared.url,
|
|
820
|
-
envVars,
|
|
821
|
-
`Plugin ${name} mcp.url`
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
|
-
const result = mcpSourceSchema.safeParse(prepared);
|
|
825
|
-
if (!result.success) {
|
|
826
|
-
throw new Error(issueMessage(result.error, `Plugin ${name} mcp`));
|
|
827
|
-
}
|
|
828
|
-
const headers = result.data.headers ? normalizeStringMap(result.data.headers, `Plugin ${name} mcp.headers`, {
|
|
829
|
-
forbiddenKeys: FORBIDDEN_API_HEADER_NAMES
|
|
830
|
-
}) : void 0;
|
|
831
|
-
return {
|
|
832
|
-
transport: "http",
|
|
833
|
-
url: result.data.url,
|
|
834
|
-
...headers ? { headers } : void 0,
|
|
835
|
-
...result.data["allowed-tools"] ? { allowedTools: result.data["allowed-tools"] } : void 0,
|
|
836
|
-
...result.data["wrapped-tools"] ? { wrappedTools: result.data["wrapped-tools"] } : void 0
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
function parseManifestSource(parsedSource, dir, config, options) {
|
|
840
|
-
const source = applyManifestConfig(parsedSource, config);
|
|
841
|
-
const sourceResult = manifestSourceSchema.safeParse(source);
|
|
842
|
-
if (!sourceResult.success) {
|
|
843
|
-
const issue = sourceResult.error.issues[0];
|
|
844
|
-
const path2 = formatPath(issue?.path ?? []);
|
|
845
|
-
if (path2 === "name") {
|
|
846
|
-
throw new Error(`Invalid plugin name in ${dir}: "${parsedSource.name}"`);
|
|
847
|
-
}
|
|
848
|
-
if (path2 === "description") {
|
|
849
|
-
throw new Error(`Invalid plugin description in ${dir}`);
|
|
850
|
-
}
|
|
851
|
-
if (path2 === "config-keys") {
|
|
852
|
-
throw new Error(
|
|
853
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} config-keys must be an array when provided`
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
if (path2 === "domains") {
|
|
857
|
-
throw new Error(
|
|
858
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} ${path2} must be a non-empty array of domains`
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
if (path2 === "api-headers") {
|
|
862
|
-
throw new Error(
|
|
863
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} api-headers must be an object when provided`
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
if (path2 === "command-env") {
|
|
867
|
-
throw new Error(
|
|
868
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} command-env must be an object when provided`
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
if (path2 === "credentials") {
|
|
872
|
-
throw new Error(
|
|
873
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} credentials must be an object when provided`
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
if (path2 === "runtime-dependencies") {
|
|
877
|
-
throw new Error(
|
|
878
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} runtime-dependencies must be an array`
|
|
879
|
-
);
|
|
880
|
-
}
|
|
881
|
-
if (path2 === "runtime-postinstall") {
|
|
882
|
-
throw new Error(
|
|
883
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} runtime-postinstall must be an array`
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
if (path2 === "env-vars") {
|
|
887
|
-
throw new Error(
|
|
888
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} env-vars must be an object`
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
if (path2 === "mcp") {
|
|
892
|
-
throw new Error(
|
|
893
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} mcp must be an object`
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
|
-
if (path2 === "oauth") {
|
|
897
|
-
throw new Error(
|
|
898
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} oauth must be an object`
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
if (path2 === "target") {
|
|
902
|
-
throw new Error(
|
|
903
|
-
`Plugin ${String(parsedSource.name ?? "unknown")} target must be an object`
|
|
904
|
-
);
|
|
905
|
-
}
|
|
906
|
-
throw new Error(issue?.message ?? `Invalid plugin manifest in ${dir}`);
|
|
907
|
-
}
|
|
908
|
-
const data = sourceResult.data;
|
|
909
|
-
const configKeys = (data["config-keys"] ?? []).map((key) => {
|
|
910
|
-
if (!SHORT_CONFIG_KEY_RE.test(key)) {
|
|
911
|
-
throw new Error(`Invalid config key "${key}" in plugin ${data.name}`);
|
|
912
|
-
}
|
|
913
|
-
return `${data.name}.${key}`;
|
|
914
|
-
});
|
|
915
|
-
const envVars = data["env-vars"] ? normalizeEnvVars(data["env-vars"], data.name) : {};
|
|
916
|
-
const apiHeaders = data["api-headers"] ? normalizeRequiredApiHeaders(
|
|
917
|
-
data["api-headers"],
|
|
918
|
-
`Plugin ${data.name} api-headers`,
|
|
919
|
-
envVars
|
|
920
|
-
) : void 0;
|
|
921
|
-
const domains = data.domains;
|
|
922
|
-
if (apiHeaders && !domains) {
|
|
923
|
-
throw new Error(`Plugin ${data.name} api-headers requires domains`);
|
|
924
|
-
}
|
|
925
|
-
if (domains && !apiHeaders && !data.credentials && options?.allowHookManagedEgress !== true) {
|
|
926
|
-
throw new Error(
|
|
927
|
-
`Plugin ${data.name} domains requires credentials or api-headers`
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
const commandEnv = data["command-env"] ? normalizeCommandEnv(
|
|
931
|
-
data["command-env"],
|
|
932
|
-
`Plugin ${data.name} command-env`,
|
|
933
|
-
envVars
|
|
934
|
-
) : void 0;
|
|
935
|
-
const credentials = data.credentials ? normalizeCredentials(data.credentials, data.name) : void 0;
|
|
936
|
-
const runtimeDependencies = data["runtime-dependencies"] ? normalizeRuntimeDependencies(data["runtime-dependencies"], data.name) : void 0;
|
|
937
|
-
const runtimePostinstall = data["runtime-postinstall"] ? normalizeRuntimePostinstall(data["runtime-postinstall"], data.name) : void 0;
|
|
938
|
-
const mcp = data.mcp ? normalizeMcp(data.mcp, envVars, data.name) : void 0;
|
|
939
|
-
const manifest = {
|
|
940
|
-
name: data.name,
|
|
941
|
-
displayName: data["display-name"],
|
|
942
|
-
description: data.description,
|
|
943
|
-
configKeys,
|
|
944
|
-
...domains ? { domains } : void 0,
|
|
945
|
-
...apiHeaders ? { apiHeaders } : void 0,
|
|
946
|
-
...commandEnv ? { commandEnv } : void 0,
|
|
947
|
-
...Object.keys(envVars).length > 0 ? { envVars } : void 0,
|
|
948
|
-
...credentials ? { credentials } : void 0,
|
|
949
|
-
...runtimeDependencies ? { runtimeDependencies } : void 0,
|
|
950
|
-
...runtimePostinstall ? { runtimePostinstall } : void 0,
|
|
951
|
-
...mcp ? { mcp } : void 0
|
|
952
|
-
};
|
|
953
|
-
if (data.oauth) {
|
|
954
|
-
if (!credentials && options?.allowHookManagedEgress !== true) {
|
|
955
|
-
throw new Error(`Plugin ${data.name} oauth requires credentials`);
|
|
956
|
-
}
|
|
957
|
-
const result = oauthSourceSchema.safeParse(data.oauth);
|
|
958
|
-
if (!result.success) {
|
|
959
|
-
throw new Error(issueMessage(result.error, `Plugin ${data.name} oauth`));
|
|
960
|
-
}
|
|
961
|
-
const authorizeParams = result.data["authorize-params"] ? normalizeStringMap(
|
|
962
|
-
result.data["authorize-params"],
|
|
963
|
-
`Plugin ${data.name} oauth.authorize-params`,
|
|
964
|
-
{
|
|
965
|
-
reservedKeys: RESERVED_AUTHORIZE_PARAM_KEYS
|
|
966
|
-
}
|
|
967
|
-
) : void 0;
|
|
968
|
-
const tokenExtraHeaders = result.data["token-extra-headers"] ? normalizeStringMap(
|
|
969
|
-
result.data["token-extra-headers"],
|
|
970
|
-
`Plugin ${data.name} oauth.token-extra-headers`,
|
|
971
|
-
{
|
|
972
|
-
forbiddenKeys: FORBIDDEN_TOKEN_HEADER_NAMES
|
|
973
|
-
}
|
|
974
|
-
) : void 0;
|
|
975
|
-
manifest.oauth = {
|
|
976
|
-
clientIdEnv: result.data["client-id-env"],
|
|
977
|
-
clientSecretEnv: result.data["client-secret-env"],
|
|
978
|
-
authorizeEndpoint: result.data["authorize-endpoint"],
|
|
979
|
-
tokenEndpoint: result.data["token-endpoint"],
|
|
980
|
-
...result.data.scope ? { scope: result.data.scope } : void 0,
|
|
981
|
-
...authorizeParams ? { authorizeParams } : void 0,
|
|
982
|
-
...result.data["token-auth-method"] ? { tokenAuthMethod: result.data["token-auth-method"] } : void 0,
|
|
983
|
-
...tokenExtraHeaders ? { tokenExtraHeaders } : void 0,
|
|
984
|
-
...result.data["treat-empty-scope-as-unreported"] ? { treatEmptyScopeAsUnreported: true } : void 0
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
assertCommandEnvDoesNotExposeHostSecretRefs(
|
|
988
|
-
data["command-env"],
|
|
989
|
-
apiHeaders,
|
|
990
|
-
credentials,
|
|
991
|
-
manifest.oauth,
|
|
992
|
-
data.name
|
|
993
|
-
);
|
|
994
|
-
assertCommandEnvHostRefsAreExplicitlyExposed(
|
|
995
|
-
data["command-env"],
|
|
996
|
-
envVars,
|
|
997
|
-
data.name
|
|
998
|
-
);
|
|
999
|
-
if (data.target) {
|
|
1000
|
-
const result = targetSourceSchema.safeParse(data.target);
|
|
1001
|
-
if (!result.success) {
|
|
1002
|
-
throw new Error(issueMessage(result.error, `Plugin ${data.name} target`));
|
|
1003
|
-
}
|
|
1004
|
-
if (!SHORT_CONFIG_KEY_RE.test(result.data["config-key"])) {
|
|
1005
|
-
throw new Error(
|
|
1006
|
-
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" is invalid`
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
const qualifiedKey = `${data.name}.${result.data["config-key"]}`;
|
|
1010
|
-
if (!configKeys.includes(qualifiedKey)) {
|
|
1011
|
-
throw new Error(
|
|
1012
|
-
`Plugin ${data.name} target.config-key "${result.data["config-key"]}" must be listed in config-keys`
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
const commandFlags = result.data["command-flags"];
|
|
1016
|
-
if (commandFlags && commandFlags.some((flag) => !TARGET_FLAG_RE.test(flag))) {
|
|
1017
|
-
throw new Error(
|
|
1018
|
-
`Plugin ${data.name} target.command-flags must contain CLI flags like --repo or -R`
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
manifest.target = {
|
|
1022
|
-
type: result.data.type,
|
|
1023
|
-
configKey: qualifiedKey,
|
|
1024
|
-
...commandFlags ? { commandFlags } : void 0
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
return manifest;
|
|
1028
|
-
}
|
|
1029
|
-
function parsePluginManifest(raw, dir, config) {
|
|
1030
|
-
let parsedYaml;
|
|
1031
|
-
try {
|
|
1032
|
-
parsedYaml = parseYaml(raw);
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
throw new Error(
|
|
1035
|
-
`Invalid plugin manifest in ${dir}: ${error instanceof Error ? error.message : String(error)}`
|
|
1036
|
-
);
|
|
1037
|
-
}
|
|
1038
|
-
if (!parsedYaml || typeof parsedYaml !== "object" || Array.isArray(parsedYaml)) {
|
|
1039
|
-
throw new Error(`Invalid plugin manifest in ${dir}: expected an object`);
|
|
1040
|
-
}
|
|
1041
|
-
return parseManifestSource(parsedYaml, dir, config);
|
|
1042
|
-
}
|
|
1043
|
-
function parseInlinePluginManifest(manifest, dir, config) {
|
|
1044
|
-
return parseManifestSource(inlineManifestSource(manifest), dir, config, {
|
|
1045
|
-
allowHookManagedEgress: true
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
// src/chat/plugins/registry.ts
|
|
1050
|
-
import { readFileSync, readdirSync, statSync } from "fs";
|
|
1051
|
-
import path from "path";
|
|
1052
|
-
|
|
1053
|
-
// src/chat/plugins/auth/oauth-bearer-broker.ts
|
|
1054
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1055
|
-
|
|
1056
|
-
// src/chat/credentials/broker.ts
|
|
1057
|
-
var CredentialUnavailableError = class extends Error {
|
|
1058
|
-
provider;
|
|
1059
|
-
constructor(provider, message) {
|
|
1060
|
-
super(message);
|
|
1061
|
-
this.name = "CredentialUnavailableError";
|
|
1062
|
-
this.provider = provider;
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
|
|
1066
|
-
// src/chat/credentials/header-transforms.ts
|
|
1067
|
-
function mergeHeaderTransforms(transforms) {
|
|
1068
|
-
const byDomain = /* @__PURE__ */ new Map();
|
|
1069
|
-
for (const transform of transforms) {
|
|
1070
|
-
byDomain.set(transform.domain, {
|
|
1071
|
-
...byDomain.get(transform.domain) ?? {},
|
|
1072
|
-
...transform.headers
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
return [...byDomain.entries()].map(([domain, headers]) => ({
|
|
1076
|
-
domain,
|
|
1077
|
-
headers
|
|
1078
|
-
}));
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// src/chat/credentials/oauth-scope.ts
|
|
1082
|
-
function parseScope(scope) {
|
|
1083
|
-
if (!scope) {
|
|
1084
|
-
return [];
|
|
1085
|
-
}
|
|
1086
|
-
return [...new Set(scope.split(/\s+/).filter(Boolean))].sort();
|
|
1087
|
-
}
|
|
1088
|
-
function normalizeOAuthScope(scope) {
|
|
1089
|
-
const parsed = parseScope(scope);
|
|
1090
|
-
return parsed.length > 0 ? parsed.join(" ") : void 0;
|
|
1091
|
-
}
|
|
1092
|
-
function hasRequiredOAuthScope(storedScope, requiredScope) {
|
|
1093
|
-
const required = parseScope(requiredScope);
|
|
1094
|
-
if (required.length === 0) {
|
|
1095
|
-
return true;
|
|
1096
|
-
}
|
|
1097
|
-
const stored = new Set(parseScope(storedScope));
|
|
1098
|
-
if (stored.size === 0) {
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
return required.every((scope) => stored.has(scope));
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// src/chat/oauth-response.ts
|
|
1105
|
-
var MAX_OAUTH_ERROR_BODY_BYTES = 16 * 1024;
|
|
1106
|
-
var MAX_OAUTH_ERROR_BODY_READS = MAX_OAUTH_ERROR_BODY_BYTES;
|
|
1107
|
-
var MAX_OAUTH_ERROR_BODY_READ_MS = 5e3;
|
|
1108
|
-
var OAUTH_ERROR_BODY_READ_TIMEOUT = /* @__PURE__ */ Symbol("oauth-error-body-read-timeout");
|
|
1109
|
-
async function readOAuthErrorBodyChunk(reader) {
|
|
1110
|
-
let timeout;
|
|
1111
|
-
const timeoutResult = new Promise(
|
|
1112
|
-
(resolve) => {
|
|
1113
|
-
timeout = setTimeout(
|
|
1114
|
-
resolve,
|
|
1115
|
-
MAX_OAUTH_ERROR_BODY_READ_MS,
|
|
1116
|
-
OAUTH_ERROR_BODY_READ_TIMEOUT
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
);
|
|
1120
|
-
try {
|
|
1121
|
-
const result = await Promise.race([reader.read(), timeoutResult]);
|
|
1122
|
-
if (result === OAUTH_ERROR_BODY_READ_TIMEOUT) {
|
|
1123
|
-
await reader.cancel();
|
|
1124
|
-
throw new Error("OAuth error body read timed out");
|
|
1125
|
-
}
|
|
1126
|
-
return result;
|
|
1127
|
-
} finally {
|
|
1128
|
-
if (timeout) {
|
|
1129
|
-
clearTimeout(timeout);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
var OAuthProviderError = class extends Error {
|
|
1134
|
-
phase;
|
|
1135
|
-
provider;
|
|
1136
|
-
resourceHost;
|
|
1137
|
-
status;
|
|
1138
|
-
constructor(details) {
|
|
1139
|
-
super(`OAuth provider ${details.phase.replace("_", " ")} failed`);
|
|
1140
|
-
this.name = "OAuthProviderError";
|
|
1141
|
-
this.phase = details.phase;
|
|
1142
|
-
this.provider = details.provider;
|
|
1143
|
-
this.resourceHost = details.resourceHost;
|
|
1144
|
-
this.status = details.status;
|
|
1145
|
-
}
|
|
1146
|
-
};
|
|
1147
|
-
function getOAuthProviderErrorAttributes(error) {
|
|
1148
|
-
if (!(error instanceof OAuthProviderError)) {
|
|
1149
|
-
return {};
|
|
1150
|
-
}
|
|
1151
|
-
return {
|
|
1152
|
-
"app.credential.provider": error.provider,
|
|
1153
|
-
"app.oauth.error.phase": error.phase,
|
|
1154
|
-
"server.address": error.resourceHost,
|
|
1155
|
-
...error.status !== void 0 ? { "http.response.status_code": error.status } : void 0
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
async function readBoundedOAuthErrorBody(response) {
|
|
1159
|
-
if (!response.body) {
|
|
1160
|
-
return "";
|
|
1161
|
-
}
|
|
1162
|
-
const reader = response.body.getReader();
|
|
1163
|
-
const decoder = new TextDecoder();
|
|
1164
|
-
let bytesRead = 0;
|
|
1165
|
-
let reads = 0;
|
|
1166
|
-
let text = "";
|
|
1167
|
-
try {
|
|
1168
|
-
while (bytesRead < MAX_OAUTH_ERROR_BODY_BYTES && reads < MAX_OAUTH_ERROR_BODY_READS) {
|
|
1169
|
-
reads += 1;
|
|
1170
|
-
const { done, value } = await readOAuthErrorBodyChunk(reader);
|
|
1171
|
-
if (done) {
|
|
1172
|
-
return text + decoder.decode();
|
|
1173
|
-
}
|
|
1174
|
-
const remaining = MAX_OAUTH_ERROR_BODY_BYTES - bytesRead;
|
|
1175
|
-
const chunk = value.subarray(0, remaining);
|
|
1176
|
-
bytesRead += chunk.byteLength;
|
|
1177
|
-
text += decoder.decode(chunk, { stream: true });
|
|
1178
|
-
if (chunk.byteLength < value.byteLength) {
|
|
1179
|
-
await reader.cancel();
|
|
1180
|
-
return text + decoder.decode();
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
await reader.cancel();
|
|
1184
|
-
return text + decoder.decode();
|
|
1185
|
-
} finally {
|
|
1186
|
-
reader.releaseLock();
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
function fetchWithBoundedOAuthErrorBodies(fetchFn = globalThis.fetch, onErrorResponseStatus) {
|
|
1190
|
-
return (async (input, init) => {
|
|
1191
|
-
const response = await fetchFn(input, init);
|
|
1192
|
-
if (response.ok) {
|
|
1193
|
-
return response;
|
|
1194
|
-
}
|
|
1195
|
-
onErrorResponseStatus?.(response.status);
|
|
1196
|
-
if (!response.body) {
|
|
1197
|
-
return response;
|
|
1198
|
-
}
|
|
1199
|
-
let body = "";
|
|
1200
|
-
try {
|
|
1201
|
-
body = await readBoundedOAuthErrorBody(response);
|
|
1202
|
-
} catch {
|
|
1203
|
-
await response.body.cancel().catch(() => void 0);
|
|
1204
|
-
}
|
|
1205
|
-
return new Response(body, {
|
|
1206
|
-
headers: response.headers,
|
|
1207
|
-
status: response.status,
|
|
1208
|
-
statusText: response.statusText
|
|
1209
|
-
});
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// src/chat/plugins/command-env.ts
|
|
1214
|
-
var ENV_PLACEHOLDER_RE2 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
1215
|
-
function resolveValue(manifest, value) {
|
|
1216
|
-
let missing = false;
|
|
1217
|
-
const resolved = value.replace(ENV_PLACEHOLDER_RE2, (match, name) => {
|
|
1218
|
-
const envName = name;
|
|
1219
|
-
const declaration = manifest.envVars?.[envName];
|
|
1220
|
-
if (!declaration || declaration.default !== void 0) {
|
|
1221
|
-
return match;
|
|
1222
|
-
}
|
|
1223
|
-
const hostValue = process.env[envName];
|
|
1224
|
-
if (hostValue === void 0 || hostValue === "") {
|
|
1225
|
-
missing = true;
|
|
1226
|
-
return "";
|
|
1227
|
-
}
|
|
1228
|
-
return hostValue;
|
|
1229
|
-
});
|
|
1230
|
-
return missing ? void 0 : resolved;
|
|
1231
|
-
}
|
|
1232
|
-
function resolvePluginCommandEnv(manifest) {
|
|
1233
|
-
const env = {};
|
|
1234
|
-
for (const [key, value] of Object.entries(manifest.commandEnv ?? {})) {
|
|
1235
|
-
const resolved = resolveValue(manifest, value);
|
|
1236
|
-
if (resolved === void 0) {
|
|
1237
|
-
continue;
|
|
1238
|
-
}
|
|
1239
|
-
env[key] = resolved;
|
|
1240
|
-
}
|
|
1241
|
-
return env;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
// src/chat/plugins/auth/auth-token-placeholder.ts
|
|
1245
|
-
var DEFAULT_PLACEHOLDERS = {
|
|
1246
|
-
"oauth-bearer": "host_managed_credential"
|
|
1247
|
-
};
|
|
1248
|
-
function resolveAuthTokenPlaceholder(credentials) {
|
|
1249
|
-
return credentials.authTokenPlaceholder?.trim() || DEFAULT_PLACEHOLDERS[credentials.type];
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// src/chat/plugins/auth/api-headers-broker.ts
|
|
1253
|
-
import { randomUUID } from "crypto";
|
|
1254
|
-
var MAX_LEASE_MS = 60 * 60 * 1e3;
|
|
1255
|
-
var ENV_PLACEHOLDER_RE3 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
1256
|
-
function resolveHeaders(provider, headers) {
|
|
1257
|
-
return Object.fromEntries(
|
|
1258
|
-
Object.entries(headers).map(([key, value]) => {
|
|
1259
|
-
const resolved = value.replace(ENV_PLACEHOLDER_RE3, (_match, name) => {
|
|
1260
|
-
const envName = name;
|
|
1261
|
-
const envValue = process.env[envName]?.trim();
|
|
1262
|
-
if (!envValue) {
|
|
1263
|
-
throw new Error(
|
|
1264
|
-
`Missing ${envName} for API header provider "${provider}"`
|
|
1265
|
-
);
|
|
1266
|
-
}
|
|
1267
|
-
return envValue;
|
|
1268
|
-
});
|
|
1269
|
-
return [key, resolved];
|
|
1270
|
-
})
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
function resolveApiHeaderTransforms(manifest) {
|
|
1274
|
-
const { domains, apiHeaders } = manifest;
|
|
1275
|
-
if (!domains || !apiHeaders) {
|
|
1276
|
-
return [];
|
|
1277
|
-
}
|
|
1278
|
-
const resolvedHeaders = resolveHeaders(manifest.name, apiHeaders);
|
|
1279
|
-
return domains.map((domain) => ({
|
|
1280
|
-
domain,
|
|
1281
|
-
headers: resolvedHeaders
|
|
1282
|
-
}));
|
|
1283
|
-
}
|
|
1284
|
-
function createApiHeadersBroker(manifest) {
|
|
1285
|
-
const provider = manifest.name;
|
|
1286
|
-
return {
|
|
1287
|
-
async issue(input) {
|
|
1288
|
-
const headerTransforms = resolveApiHeaderTransforms(manifest);
|
|
1289
|
-
if (headerTransforms.length === 0) {
|
|
1290
|
-
throw new Error(`No API headers configured for plugin "${provider}"`);
|
|
1291
|
-
}
|
|
1292
|
-
return {
|
|
1293
|
-
id: randomUUID(),
|
|
1294
|
-
provider,
|
|
1295
|
-
env: resolvePluginCommandEnv(manifest),
|
|
1296
|
-
headerTransforms,
|
|
1297
|
-
expiresAt: new Date(Date.now() + MAX_LEASE_MS).toISOString(),
|
|
1298
|
-
metadata: {
|
|
1299
|
-
reason: input.reason
|
|
1300
|
-
}
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// src/chat/plugins/auth/oauth-request.ts
|
|
1307
|
-
import { z as z2 } from "zod";
|
|
1308
|
-
var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
1309
|
-
function requireNonEmptyTokenField(data, field) {
|
|
1310
|
-
const value = data[field];
|
|
1311
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
1312
|
-
throw new Error(`OAuth token response missing ${field}`);
|
|
1313
|
-
}
|
|
1314
|
-
return value;
|
|
1315
|
-
}
|
|
1316
|
-
function requireTokenResponseObject(data) {
|
|
1317
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
1318
|
-
throw new Error("OAuth token response must be an object");
|
|
1319
|
-
}
|
|
1320
|
-
return data;
|
|
1321
|
-
}
|
|
1322
|
-
var parsedOAuthTokenResponseSchema = z2.object({
|
|
1323
|
-
accessToken: z2.string().min(1),
|
|
1324
|
-
refreshToken: z2.string().min(1),
|
|
1325
|
-
expiresAt: z2.number().positive().optional(),
|
|
1326
|
-
refreshTokenExpiresAt: z2.number().positive().optional(),
|
|
1327
|
-
scope: z2.string().min(1).optional()
|
|
1328
|
-
}).strict();
|
|
1329
|
-
function contentTypeToBody(contentType, payload) {
|
|
1330
|
-
const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
|
|
1331
|
-
if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) {
|
|
1332
|
-
return new URLSearchParams(payload);
|
|
1333
|
-
}
|
|
1334
|
-
if (mediaType === "application/json" || mediaType.endsWith("+json")) {
|
|
1335
|
-
return JSON.stringify(payload);
|
|
1336
|
-
}
|
|
1337
|
-
throw new Error(`Unsupported OAuth token Content-Type: ${contentType}`);
|
|
1338
|
-
}
|
|
1339
|
-
function buildOAuthTokenRequest(input) {
|
|
1340
|
-
const headers = new Headers({ Accept: "application/json" });
|
|
1341
|
-
for (const [name, value] of Object.entries(input.tokenExtraHeaders ?? {})) {
|
|
1342
|
-
headers.set(name, value);
|
|
1343
|
-
}
|
|
1344
|
-
if (!headers.has("Content-Type")) {
|
|
1345
|
-
headers.set("Content-Type", DEFAULT_TOKEN_CONTENT_TYPE);
|
|
1346
|
-
}
|
|
1347
|
-
const payload = { ...input.payload };
|
|
1348
|
-
if (input.tokenAuthMethod === "basic") {
|
|
1349
|
-
headers.set(
|
|
1350
|
-
"Authorization",
|
|
1351
|
-
`Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`
|
|
1352
|
-
);
|
|
1353
|
-
} else {
|
|
1354
|
-
payload.client_id = input.clientId;
|
|
1355
|
-
payload.client_secret = input.clientSecret;
|
|
1356
|
-
}
|
|
1357
|
-
const contentType = headers.get("Content-Type") ?? DEFAULT_TOKEN_CONTENT_TYPE;
|
|
1358
|
-
const serializedHeaders = {};
|
|
1359
|
-
headers.forEach((value, key) => {
|
|
1360
|
-
serializedHeaders[key] = value;
|
|
1361
|
-
});
|
|
1362
|
-
return {
|
|
1363
|
-
headers: serializedHeaders,
|
|
1364
|
-
body: contentTypeToBody(contentType, payload)
|
|
1365
|
-
};
|
|
1366
|
-
}
|
|
1367
|
-
function parseOAuthTokenResponse(data, requestedScope, options) {
|
|
1368
|
-
const response = requireTokenResponseObject(data);
|
|
1369
|
-
const accessToken = requireNonEmptyTokenField(response, "access_token");
|
|
1370
|
-
const resolvedRefreshToken = response.refresh_token === void 0 ? options?.refreshToken : requireNonEmptyTokenField(response, "refresh_token");
|
|
1371
|
-
if (!resolvedRefreshToken) {
|
|
1372
|
-
throw new Error("OAuth token response missing refresh_token");
|
|
1373
|
-
}
|
|
1374
|
-
const expiresIn = response.expires_in;
|
|
1375
|
-
const refreshTokenExpiresIn = response.refresh_token_expires_in;
|
|
1376
|
-
const responseScope = response.scope;
|
|
1377
|
-
let scope;
|
|
1378
|
-
if (responseScope !== void 0) {
|
|
1379
|
-
if (typeof responseScope !== "string") {
|
|
1380
|
-
throw new Error("OAuth token response returned invalid scope");
|
|
1381
|
-
}
|
|
1382
|
-
const normalized = normalizeOAuthScope(responseScope);
|
|
1383
|
-
if (normalized !== void 0) {
|
|
1384
|
-
scope = normalized;
|
|
1385
|
-
} else if (options?.treatEmptyScopeAsUnreported) {
|
|
1386
|
-
scope = normalizeOAuthScope(requestedScope);
|
|
1387
|
-
} else {
|
|
1388
|
-
throw new Error("OAuth token response returned empty scope");
|
|
1389
|
-
}
|
|
1390
|
-
} else {
|
|
1391
|
-
scope = normalizeOAuthScope(requestedScope);
|
|
1392
|
-
}
|
|
1393
|
-
const result = {
|
|
1394
|
-
accessToken,
|
|
1395
|
-
refreshToken: resolvedRefreshToken,
|
|
1396
|
-
...scope ? { scope } : void 0
|
|
1397
|
-
};
|
|
1398
|
-
if (expiresIn !== void 0) {
|
|
1399
|
-
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
1400
|
-
throw new Error("OAuth token response returned invalid expires_in");
|
|
1401
|
-
}
|
|
1402
|
-
result.expiresAt = Date.now() + expiresIn * 1e3;
|
|
1403
|
-
}
|
|
1404
|
-
if (refreshTokenExpiresIn !== void 0) {
|
|
1405
|
-
if (typeof refreshTokenExpiresIn !== "number" || !Number.isFinite(refreshTokenExpiresIn) || refreshTokenExpiresIn <= 0) {
|
|
1406
|
-
throw new Error(
|
|
1407
|
-
"OAuth token response returned invalid refresh_token_expires_in"
|
|
1408
|
-
);
|
|
1409
|
-
}
|
|
1410
|
-
result.refreshTokenExpiresAt = Date.now() + refreshTokenExpiresIn * 1e3;
|
|
1411
|
-
}
|
|
1412
|
-
return parsedOAuthTokenResponseSchema.parse(result);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
// src/chat/plugins/auth/oauth-bearer-broker.ts
|
|
1416
|
-
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
1417
|
-
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
1418
|
-
var TOKEN_REFRESH_TIMEOUT_MS = 2e4;
|
|
1419
|
-
var OAuthRefreshRejectedError = class extends Error {
|
|
1420
|
-
constructor(message) {
|
|
1421
|
-
super(message);
|
|
1422
|
-
this.name = "OAuthRefreshRejectedError";
|
|
1423
|
-
}
|
|
1424
|
-
};
|
|
1425
|
-
function parseRefreshError(text) {
|
|
1426
|
-
if (!text.trim()) {
|
|
1427
|
-
return void 0;
|
|
1428
|
-
}
|
|
1429
|
-
try {
|
|
1430
|
-
const parsed = JSON.parse(text);
|
|
1431
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.error === "string" ? parsed.error : void 0;
|
|
1432
|
-
} catch {
|
|
1433
|
-
return void 0;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
async function refreshAccessToken(provider, refreshToken, oauth, requestedScope) {
|
|
1437
|
-
const clientId = process.env[oauth.clientIdEnv]?.trim();
|
|
1438
|
-
const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
|
|
1439
|
-
if (!clientId || !clientSecret) {
|
|
1440
|
-
throw new Error(
|
|
1441
|
-
`Missing ${oauth.clientIdEnv} or ${oauth.clientSecretEnv} for token refresh`
|
|
1442
|
-
);
|
|
1443
|
-
}
|
|
1444
|
-
const request = buildOAuthTokenRequest({
|
|
1445
|
-
clientId,
|
|
1446
|
-
clientSecret,
|
|
1447
|
-
payload: {
|
|
1448
|
-
grant_type: "refresh_token",
|
|
1449
|
-
refresh_token: refreshToken
|
|
1450
|
-
},
|
|
1451
|
-
tokenAuthMethod: oauth.tokenAuthMethod,
|
|
1452
|
-
tokenExtraHeaders: oauth.tokenExtraHeaders
|
|
1453
|
-
});
|
|
1454
|
-
const errorDetails = {
|
|
1455
|
-
phase: "token_refresh",
|
|
1456
|
-
provider,
|
|
1457
|
-
resourceHost: new URL(oauth.tokenEndpoint).hostname
|
|
1458
|
-
};
|
|
1459
|
-
let response;
|
|
1460
|
-
try {
|
|
1461
|
-
response = await fetch(oauth.tokenEndpoint, {
|
|
1462
|
-
method: "POST",
|
|
1463
|
-
headers: request.headers,
|
|
1464
|
-
body: request.body,
|
|
1465
|
-
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
|
|
1466
|
-
});
|
|
1467
|
-
} catch {
|
|
1468
|
-
throw new OAuthProviderError(errorDetails);
|
|
1469
|
-
}
|
|
1470
|
-
if (!response.ok) {
|
|
1471
|
-
let errorCode;
|
|
1472
|
-
try {
|
|
1473
|
-
errorCode = parseRefreshError(await readBoundedOAuthErrorBody(response));
|
|
1474
|
-
} catch {
|
|
1475
|
-
throw new OAuthProviderError({
|
|
1476
|
-
...errorDetails,
|
|
1477
|
-
status: response.status
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
if (errorCode === "invalid_grant" || errorCode === "bad_refresh_token") {
|
|
1481
|
-
throw new OAuthRefreshRejectedError("Token refresh was rejected");
|
|
1482
|
-
}
|
|
1483
|
-
throw new OAuthProviderError({
|
|
1484
|
-
...errorDetails,
|
|
1485
|
-
status: response.status
|
|
1486
|
-
});
|
|
1487
|
-
}
|
|
1488
|
-
try {
|
|
1489
|
-
const data = await response.json();
|
|
1490
|
-
return parseOAuthTokenResponse(data, requestedScope, {
|
|
1491
|
-
treatEmptyScopeAsUnreported: oauth.treatEmptyScopeAsUnreported,
|
|
1492
|
-
refreshToken
|
|
1493
|
-
});
|
|
1494
|
-
} catch {
|
|
1495
|
-
throw new OAuthProviderError(errorDetails);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
function getLeaseExpiry(expiresAt) {
|
|
1499
|
-
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
|
|
1500
|
-
}
|
|
1501
|
-
function canUseStoredToken(stored) {
|
|
1502
|
-
return stored !== void 0 && (stored.expiresAt === void 0 || stored.expiresAt > Date.now());
|
|
1503
|
-
}
|
|
1504
|
-
function shouldRefreshStoredToken(stored) {
|
|
1505
|
-
return stored !== void 0 && stored.expiresAt !== void 0 && stored.expiresAt - Date.now() < REFRESH_BUFFER_MS;
|
|
1506
|
-
}
|
|
1507
|
-
function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
1508
|
-
const provider = manifest.name;
|
|
1509
|
-
const { domains, apiHeaders, authTokenEnv } = credentials;
|
|
1510
|
-
const authTokenPlaceholder = resolveAuthTokenPlaceholder(credentials);
|
|
1511
|
-
const pluginHeaderTransforms = () => resolveApiHeaderTransforms(manifest);
|
|
1512
|
-
function buildLease(token, expiresAtMs, reason) {
|
|
1513
|
-
return {
|
|
1514
|
-
id: randomUUID2(),
|
|
1515
|
-
provider,
|
|
1516
|
-
env: {
|
|
1517
|
-
...resolvePluginCommandEnv(manifest),
|
|
1518
|
-
[authTokenEnv]: authTokenPlaceholder
|
|
1519
|
-
},
|
|
1520
|
-
headerTransforms: mergeHeaderTransforms([
|
|
1521
|
-
...pluginHeaderTransforms(),
|
|
1522
|
-
...domains.map((domain) => ({
|
|
1523
|
-
domain,
|
|
1524
|
-
headers: { ...apiHeaders ?? {}, Authorization: `Bearer ${token}` }
|
|
1525
|
-
}))
|
|
1526
|
-
]),
|
|
1527
|
-
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
1528
|
-
metadata: { reason }
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
return {
|
|
1532
|
-
async issue(input) {
|
|
1533
|
-
const envToken = process.env[authTokenEnv]?.trim();
|
|
1534
|
-
const oauth = manifest.oauth;
|
|
1535
|
-
const userSubjectId = credentialUserSubjectId(input.context);
|
|
1536
|
-
if (!oauth) {
|
|
1537
|
-
if (envToken) {
|
|
1538
|
-
return buildLease(envToken, Date.now() + MAX_LEASE_MS2, input.reason);
|
|
1539
|
-
}
|
|
1540
|
-
throw new CredentialUnavailableError(
|
|
1541
|
-
provider,
|
|
1542
|
-
`No ${provider} credentials available.`
|
|
1543
|
-
);
|
|
1544
|
-
}
|
|
1545
|
-
if (userSubjectId) {
|
|
1546
|
-
const stored = await deps.userTokenStore.get(userSubjectId, provider);
|
|
1547
|
-
if (stored) {
|
|
1548
|
-
if (!hasRequiredOAuthScope(stored.scope, oauth.scope)) {
|
|
1549
|
-
throw new CredentialUnavailableError(
|
|
1550
|
-
provider,
|
|
1551
|
-
`Your ${provider} connection needs to be reauthorized.`
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
if (shouldRefreshStoredToken(stored)) {
|
|
1555
|
-
try {
|
|
1556
|
-
return await deps.userTokenStore.withRefresh(
|
|
1557
|
-
userSubjectId,
|
|
1558
|
-
provider,
|
|
1559
|
-
async () => {
|
|
1560
|
-
const latest = await deps.userTokenStore.get(
|
|
1561
|
-
userSubjectId,
|
|
1562
|
-
provider
|
|
1563
|
-
);
|
|
1564
|
-
if (latest && !hasRequiredOAuthScope(latest.scope, oauth.scope)) {
|
|
1565
|
-
throw new CredentialUnavailableError(
|
|
1566
|
-
provider,
|
|
1567
|
-
`Your ${provider} connection needs to be reauthorized.`
|
|
1568
|
-
);
|
|
1569
|
-
}
|
|
1570
|
-
if (!shouldRefreshStoredToken(latest) && canUseStoredToken(latest)) {
|
|
1571
|
-
return buildLease(
|
|
1572
|
-
latest.accessToken,
|
|
1573
|
-
getLeaseExpiry(latest.expiresAt),
|
|
1574
|
-
input.reason
|
|
1575
|
-
);
|
|
1576
|
-
}
|
|
1577
|
-
if (!latest) {
|
|
1578
|
-
throw new CredentialUnavailableError(
|
|
1579
|
-
provider,
|
|
1580
|
-
`No ${provider} credentials available.`
|
|
1581
|
-
);
|
|
1582
|
-
}
|
|
1583
|
-
const refreshed = await refreshAccessToken(
|
|
1584
|
-
provider,
|
|
1585
|
-
latest.refreshToken,
|
|
1586
|
-
oauth,
|
|
1587
|
-
latest.scope ?? oauth.scope
|
|
1588
|
-
);
|
|
1589
|
-
if (!hasRequiredOAuthScope(refreshed.scope, oauth.scope)) {
|
|
1590
|
-
throw new CredentialUnavailableError(
|
|
1591
|
-
provider,
|
|
1592
|
-
`Your ${provider} connection needs to be reauthorized.`
|
|
1593
|
-
);
|
|
1594
|
-
}
|
|
1595
|
-
const refreshedTokens = {
|
|
1596
|
-
...latest.refreshTokenExpiresAt ? { refreshTokenExpiresAt: latest.refreshTokenExpiresAt } : void 0,
|
|
1597
|
-
...refreshed,
|
|
1598
|
-
...latest.account ? { account: latest.account } : void 0
|
|
1599
|
-
};
|
|
1600
|
-
await deps.userTokenStore.set(
|
|
1601
|
-
userSubjectId,
|
|
1602
|
-
provider,
|
|
1603
|
-
refreshedTokens
|
|
1604
|
-
);
|
|
1605
|
-
return buildLease(
|
|
1606
|
-
refreshed.accessToken,
|
|
1607
|
-
getLeaseExpiry(refreshed.expiresAt),
|
|
1608
|
-
input.reason
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
);
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
if (error instanceof CredentialUnavailableError) {
|
|
1614
|
-
throw error;
|
|
1615
|
-
}
|
|
1616
|
-
if (error instanceof OAuthRefreshRejectedError) {
|
|
1617
|
-
throw new CredentialUnavailableError(
|
|
1618
|
-
provider,
|
|
1619
|
-
`Your ${provider} connection has expired.`
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
throw error;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
if (canUseStoredToken(stored)) {
|
|
1626
|
-
return buildLease(
|
|
1627
|
-
stored.accessToken,
|
|
1628
|
-
getLeaseExpiry(stored.expiresAt),
|
|
1629
|
-
input.reason
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
throw new CredentialUnavailableError(
|
|
1633
|
-
provider,
|
|
1634
|
-
`Your ${provider} connection has expired.`
|
|
1635
|
-
);
|
|
1636
|
-
}
|
|
1637
|
-
throw new CredentialUnavailableError(
|
|
1638
|
-
provider,
|
|
1639
|
-
`No ${provider} credentials available.`
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1642
|
-
if (envToken) {
|
|
1643
|
-
return buildLease(envToken, getLeaseExpiry(), input.reason);
|
|
1644
|
-
}
|
|
1645
|
-
throw new CredentialUnavailableError(
|
|
1646
|
-
provider,
|
|
1647
|
-
`No ${provider} credentials available.`
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// src/chat/plugins/registry.ts
|
|
1654
|
-
function getLoggedPluginNames() {
|
|
1655
|
-
const globalState = globalThis;
|
|
1656
|
-
globalState.__juniorLoggedPluginNames ??= /* @__PURE__ */ new Set();
|
|
1657
|
-
return globalState.__juniorLoggedPluginNames;
|
|
1658
|
-
}
|
|
1659
|
-
function createLoadedPluginState(signature) {
|
|
1660
|
-
return {
|
|
1661
|
-
signature,
|
|
1662
|
-
pluginDefinitions: [],
|
|
1663
|
-
pluginMigrationRoots: /* @__PURE__ */ new Map(),
|
|
1664
|
-
domainToPlugin: /* @__PURE__ */ new Map(),
|
|
1665
|
-
pluginConfigKeys: /* @__PURE__ */ new Set(),
|
|
1666
|
-
pluginsByName: /* @__PURE__ */ new Map(),
|
|
1667
|
-
packageSkillRoots: /* @__PURE__ */ new Set()
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
function providerDomains(manifest) {
|
|
1671
|
-
return [
|
|
1672
|
-
.../* @__PURE__ */ new Set([
|
|
1673
|
-
...manifest.credentials?.domains ?? [],
|
|
1674
|
-
...manifest.domains ?? []
|
|
1675
|
-
])
|
|
1676
|
-
].sort((left, right) => left.localeCompare(right));
|
|
1677
|
-
}
|
|
1678
|
-
function registerPluginManifest(state, manifest, pluginDir, skillsDir, migrationsDir) {
|
|
1679
|
-
if (state.pluginsByName.has(manifest.name)) {
|
|
1680
|
-
throw new Error(`Duplicate plugin name "${manifest.name}"`);
|
|
1681
|
-
}
|
|
1682
|
-
for (const domain of providerDomains(manifest)) {
|
|
1683
|
-
const owner = state.domainToPlugin.get(domain);
|
|
1684
|
-
if (owner) {
|
|
1685
|
-
throw new Error(
|
|
1686
|
-
`Duplicate provider domain "${domain}" in plugin "${manifest.name}" already declared by plugin "${owner}". Use plugins.manifests in PluginCatalogConfig to change one plugin's domains or credentials.`
|
|
1687
|
-
);
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
const definition = {
|
|
1691
|
-
manifest,
|
|
1692
|
-
dir: pluginDir,
|
|
1693
|
-
...migrationsDir ? { migrationsDir } : void 0,
|
|
1694
|
-
...skillsDir ? { skillsDir } : void 0
|
|
1695
|
-
};
|
|
1696
|
-
state.pluginDefinitions.push(definition);
|
|
1697
|
-
state.pluginsByName.set(manifest.name, definition);
|
|
1698
|
-
if (definition.migrationsDir) {
|
|
1699
|
-
state.pluginMigrationRoots.set(manifest.name, definition.migrationsDir);
|
|
1700
|
-
}
|
|
1701
|
-
for (const key of manifest.configKeys) {
|
|
1702
|
-
state.pluginConfigKeys.add(key);
|
|
1703
|
-
}
|
|
1704
|
-
for (const domain of providerDomains(manifest)) {
|
|
1705
|
-
state.domainToPlugin.set(domain, manifest.name);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
function registerYamlPluginManifest(state, raw, pluginDir, config) {
|
|
1709
|
-
const manifest = parsePluginManifest(raw, pluginDir, config);
|
|
1710
|
-
const candidateSkillsDir = path.join(pluginDir, "skills");
|
|
1711
|
-
const hasSkillsDir = (() => {
|
|
1712
|
-
try {
|
|
1713
|
-
return statSync(candidateSkillsDir).isDirectory();
|
|
1714
|
-
} catch {
|
|
1715
|
-
return false;
|
|
1716
|
-
}
|
|
1717
|
-
})();
|
|
1718
|
-
registerPluginManifest(
|
|
1719
|
-
state,
|
|
1720
|
-
manifest,
|
|
1721
|
-
pluginDir,
|
|
1722
|
-
hasSkillsDir ? candidateSkillsDir : void 0
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
function normalizePluginRoots(roots) {
|
|
1726
|
-
const resolved = [];
|
|
1727
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1728
|
-
for (const root of roots) {
|
|
1729
|
-
const normalized = path.resolve(root);
|
|
1730
|
-
if (seen.has(normalized)) {
|
|
1731
|
-
continue;
|
|
1732
|
-
}
|
|
1733
|
-
seen.add(normalized);
|
|
1734
|
-
resolved.push(normalized);
|
|
1735
|
-
}
|
|
1736
|
-
return resolved;
|
|
1737
|
-
}
|
|
1738
|
-
function getPluginCatalogSource(runtime) {
|
|
1739
|
-
const packagedContent = discoverConfiguredPluginPackageContent(
|
|
1740
|
-
runtime.pluginConfig
|
|
1741
|
-
);
|
|
1742
|
-
const localRoots = normalizePluginRoots(pluginRoots());
|
|
1743
|
-
const manifestRoots = normalizePluginRoots([
|
|
1744
|
-
...localRoots,
|
|
1745
|
-
...packagedContent.manifestRoots
|
|
1746
|
-
]);
|
|
1747
|
-
const packagedSkillRoots = normalizePluginRoots(packagedContent.skillRoots);
|
|
1748
|
-
const inlineManifests = runtime.pluginConfig?.inlineManifests ?? [];
|
|
1749
|
-
return {
|
|
1750
|
-
inlineManifests,
|
|
1751
|
-
manifestRoots,
|
|
1752
|
-
packagedSkillRoots,
|
|
1753
|
-
packagedContent,
|
|
1754
|
-
signature: JSON.stringify({
|
|
1755
|
-
inlineManifests,
|
|
1756
|
-
manifestRoots,
|
|
1757
|
-
packages: packagedContent.packages.map((pkg) => ({
|
|
1758
|
-
dir: path.resolve(pkg.dir),
|
|
1759
|
-
hasMigrationsDir: pkg.hasMigrationsDir,
|
|
1760
|
-
hasSkillsDir: pkg.hasSkillsDir,
|
|
1761
|
-
packageName: pkg.packageName
|
|
1762
|
-
})).sort(
|
|
1763
|
-
(left, right) => left.packageName.localeCompare(right.packageName)
|
|
1764
|
-
),
|
|
1765
|
-
packagedSkillRoots,
|
|
1766
|
-
packageNames: [...packagedContent.packageNames].sort(),
|
|
1767
|
-
pluginConfig: runtime.pluginConfig ?? {}
|
|
1768
|
-
})
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
function normalizePluginCatalogConfig(config) {
|
|
1772
|
-
if (!config) {
|
|
1773
|
-
return void 0;
|
|
1774
|
-
}
|
|
1775
|
-
return {
|
|
1776
|
-
inlineManifests: config.inlineManifests ? structuredClone(config.inlineManifests) : void 0,
|
|
1777
|
-
packages: normalizePluginPackageNames(config.packages),
|
|
1778
|
-
...config.manifests ? { manifests: structuredClone(config.manifests) } : void 0
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
function clonePluginCatalogConfig(config) {
|
|
1782
|
-
if (!config) {
|
|
1783
|
-
return void 0;
|
|
1784
|
-
}
|
|
1785
|
-
return {
|
|
1786
|
-
...config.inlineManifests ? { inlineManifests: structuredClone(config.inlineManifests) } : void 0,
|
|
1787
|
-
packages: [...config.packages ?? []],
|
|
1788
|
-
...config.manifests ? { manifests: structuredClone(config.manifests) } : void 0
|
|
1789
|
-
};
|
|
1790
|
-
}
|
|
1791
|
-
function packageContentByName(packagedContent, packageName) {
|
|
1792
|
-
return packagedContent.packages.find(
|
|
1793
|
-
(pkg) => pkg.packageName === packageName
|
|
1794
|
-
);
|
|
1795
|
-
}
|
|
1796
|
-
function registerInlineManifests(state, source, config) {
|
|
1797
|
-
const migrationOwners = /* @__PURE__ */ new Map();
|
|
1798
|
-
for (const definition of source.inlineManifests) {
|
|
1799
|
-
const pkg = definition.packageName ? packageContentByName(source.packagedContent, definition.packageName) : void 0;
|
|
1800
|
-
const dir = pkg?.dir ?? process.cwd();
|
|
1801
|
-
const skillsDir = pkg?.hasSkillsDir ? path.join(pkg.dir, "skills") : void 0;
|
|
1802
|
-
const migrationsDir = pkg?.hasMigrationsDir && statSync(path.join(pkg.dir, "migrations"), {
|
|
1803
|
-
throwIfNoEntry: false
|
|
1804
|
-
})?.isDirectory() ? path.join(pkg.dir, "migrations") : void 0;
|
|
1805
|
-
const manifest = parseInlinePluginManifest(
|
|
1806
|
-
definition.manifest,
|
|
1807
|
-
dir,
|
|
1808
|
-
config
|
|
1809
|
-
);
|
|
1810
|
-
if (migrationsDir) {
|
|
1811
|
-
const owner = migrationOwners.get(migrationsDir);
|
|
1812
|
-
if (owner) {
|
|
1813
|
-
throw new Error(
|
|
1814
|
-
`Plugin "${manifest.name}" cannot share migrations directory with plugin "${owner}"`
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
migrationOwners.set(migrationsDir, manifest.name);
|
|
1818
|
-
}
|
|
1819
|
-
registerPluginManifest(state, manifest, dir, skillsDir, migrationsDir);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
function discoverConfiguredPluginPackageContent(config) {
|
|
1823
|
-
return discoverInstalledPluginPackageContent(process.cwd(), {
|
|
1824
|
-
packageNames: config?.packages
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
function buildLoadedPluginState(source, config) {
|
|
1828
|
-
const state = createLoadedPluginState(source.signature);
|
|
1829
|
-
for (const skillRoot of source.packagedSkillRoots) {
|
|
1830
|
-
state.packageSkillRoots.add(skillRoot);
|
|
1831
|
-
}
|
|
1832
|
-
registerInlineManifests(state, source, config);
|
|
1833
|
-
const roots = source.manifestRoots;
|
|
1834
|
-
for (const pluginsRoot of roots) {
|
|
1835
|
-
let entries;
|
|
1836
|
-
let rootStat;
|
|
1837
|
-
try {
|
|
1838
|
-
rootStat = statSync(pluginsRoot);
|
|
1839
|
-
} catch (error) {
|
|
1840
|
-
logWarn("plugin.root.read.failed", {
|
|
1841
|
-
"file.directory": pluginsRoot,
|
|
1842
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1843
|
-
});
|
|
1844
|
-
continue;
|
|
1845
|
-
}
|
|
1846
|
-
if (rootStat.isDirectory()) {
|
|
1847
|
-
const manifestPath = path.join(pluginsRoot, "plugin.yaml");
|
|
1848
|
-
let hasRootManifest = false;
|
|
1849
|
-
try {
|
|
1850
|
-
hasRootManifest = statSync(manifestPath).isFile();
|
|
1851
|
-
} catch {
|
|
1852
|
-
hasRootManifest = false;
|
|
1853
|
-
}
|
|
1854
|
-
if (hasRootManifest) {
|
|
1855
|
-
const rawRootManifest = readFileSync(manifestPath, "utf8");
|
|
1856
|
-
registerYamlPluginManifest(state, rawRootManifest, pluginsRoot, config);
|
|
1857
|
-
continue;
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
try {
|
|
1861
|
-
entries = readdirSync(pluginsRoot);
|
|
1862
|
-
} catch (error) {
|
|
1863
|
-
logWarn("plugin.root.read.failed", {
|
|
1864
|
-
"file.directory": pluginsRoot,
|
|
1865
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1866
|
-
});
|
|
1867
|
-
continue;
|
|
1868
|
-
}
|
|
1869
|
-
for (const entry of entries.sort()) {
|
|
1870
|
-
const pluginDir = path.join(pluginsRoot, entry);
|
|
1871
|
-
try {
|
|
1872
|
-
const stat = statSync(pluginDir);
|
|
1873
|
-
if (!stat.isDirectory()) continue;
|
|
1874
|
-
} catch {
|
|
1875
|
-
continue;
|
|
1876
|
-
}
|
|
1877
|
-
const manifestPath = path.join(pluginDir, "plugin.yaml");
|
|
1878
|
-
let raw;
|
|
1879
|
-
try {
|
|
1880
|
-
raw = readFileSync(manifestPath, "utf8");
|
|
1881
|
-
} catch {
|
|
1882
|
-
continue;
|
|
1883
|
-
}
|
|
1884
|
-
registerYamlPluginManifest(state, raw, pluginDir, config);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
for (const name of Object.keys(config?.manifests ?? {})) {
|
|
1888
|
-
if (!state.pluginsByName.has(name)) {
|
|
1889
|
-
throw new Error(
|
|
1890
|
-
`plugins.manifests.${name} does not match a loaded plugin`
|
|
1891
|
-
);
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
return state;
|
|
1895
|
-
}
|
|
1896
|
-
function logLoadedPlugins(state) {
|
|
1897
|
-
const loggedPluginNames = getLoggedPluginNames();
|
|
1898
|
-
for (const plugin of [...state.pluginDefinitions].sort(
|
|
1899
|
-
(left, right) => left.manifest.name.localeCompare(right.manifest.name)
|
|
1900
|
-
)) {
|
|
1901
|
-
if (loggedPluginNames.has(plugin.manifest.name)) {
|
|
1902
|
-
continue;
|
|
1903
|
-
}
|
|
1904
|
-
loggedPluginNames.add(plugin.manifest.name);
|
|
1905
|
-
logInfo("plugin.loaded", {
|
|
1906
|
-
"app.plugin.name": plugin.manifest.name,
|
|
1907
|
-
"app.plugin.config_key_count": plugin.manifest.configKeys.length,
|
|
1908
|
-
"app.plugin.has_mcp": Boolean(plugin.manifest.mcp),
|
|
1909
|
-
"file.directory": plugin.dir,
|
|
1910
|
-
...plugin.skillsDir ? { "app.file.skill_directory": plugin.skillsDir } : void 0
|
|
1911
|
-
});
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
function ensurePluginsLoaded(runtime) {
|
|
1915
|
-
const source = getPluginCatalogSource(runtime);
|
|
1916
|
-
if (runtime.loadedPluginState?.signature === source.signature) {
|
|
1917
|
-
return runtime.loadedPluginState;
|
|
1918
|
-
}
|
|
1919
|
-
const state = buildLoadedPluginState(source, runtime.pluginConfig);
|
|
1920
|
-
runtime.loadedPluginState = state;
|
|
1921
|
-
logLoadedPlugins(state);
|
|
1922
|
-
return state;
|
|
1923
|
-
}
|
|
1924
|
-
function createPluginCatalogRuntime() {
|
|
1925
|
-
const runtime = {};
|
|
1926
|
-
return {
|
|
1927
|
-
setConfig(config) {
|
|
1928
|
-
const previousConfig = clonePluginCatalogConfig(runtime.pluginConfig);
|
|
1929
|
-
runtime.pluginConfig = normalizePluginCatalogConfig(config);
|
|
1930
|
-
return previousConfig;
|
|
1931
|
-
},
|
|
1932
|
-
parseConfiguredInlineManifest(manifest, dir) {
|
|
1933
|
-
return parseInlinePluginManifest(manifest, dir, runtime.pluginConfig);
|
|
1934
|
-
},
|
|
1935
|
-
getPackageContent() {
|
|
1936
|
-
return discoverConfiguredPluginPackageContent(runtime.pluginConfig);
|
|
1937
|
-
},
|
|
1938
|
-
getSignature() {
|
|
1939
|
-
return ensurePluginsLoaded(runtime).signature;
|
|
1940
|
-
},
|
|
1941
|
-
getProviders() {
|
|
1942
|
-
return [...ensurePluginsLoaded(runtime).pluginDefinitions];
|
|
1943
|
-
},
|
|
1944
|
-
getMigrationRoots() {
|
|
1945
|
-
const state = ensurePluginsLoaded(runtime);
|
|
1946
|
-
return [...state.pluginMigrationRoots.entries()].map(([pluginName, dir]) => ({ pluginName, dir })).sort((left, right) => left.pluginName.localeCompare(right.pluginName));
|
|
1947
|
-
},
|
|
1948
|
-
getMcpProviders() {
|
|
1949
|
-
return ensurePluginsLoaded(runtime).pluginDefinitions.filter(
|
|
1950
|
-
(plugin) => Boolean(plugin.manifest.mcp)
|
|
1951
|
-
);
|
|
1952
|
-
},
|
|
1953
|
-
getRuntimeDependencies() {
|
|
1954
|
-
const state = ensurePluginsLoaded(runtime);
|
|
1955
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1956
|
-
const deps = [];
|
|
1957
|
-
for (const plugin of state.pluginDefinitions) {
|
|
1958
|
-
for (const dep of plugin.manifest.runtimeDependencies ?? []) {
|
|
1959
|
-
const key = dep.type === "npm" ? `${dep.type}:${dep.package}:${dep.version}` : "package" in dep ? `${dep.type}:package:${dep.package}` : `${dep.type}:url:${dep.url}:${dep.sha256}`;
|
|
1960
|
-
if (seen.has(key)) {
|
|
1961
|
-
continue;
|
|
1962
|
-
}
|
|
1963
|
-
seen.add(key);
|
|
1964
|
-
deps.push(dep);
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
return deps.sort((left, right) => {
|
|
1968
|
-
if (left.type !== right.type) {
|
|
1969
|
-
return left.type.localeCompare(right.type);
|
|
1970
|
-
}
|
|
1971
|
-
const leftIdentity = "package" in left ? `package:${left.package}` : `url:${left.url}:${left.sha256}`;
|
|
1972
|
-
const rightIdentity = "package" in right ? `package:${right.package}` : `url:${right.url}:${right.sha256}`;
|
|
1973
|
-
if (leftIdentity !== rightIdentity) {
|
|
1974
|
-
return leftIdentity.localeCompare(rightIdentity);
|
|
1975
|
-
}
|
|
1976
|
-
if (left.type === "npm" && right.type === "npm") {
|
|
1977
|
-
return left.version.localeCompare(right.version);
|
|
1978
|
-
}
|
|
1979
|
-
return 0;
|
|
1980
|
-
});
|
|
1981
|
-
},
|
|
1982
|
-
getRuntimePostinstall() {
|
|
1983
|
-
const state = ensurePluginsLoaded(runtime);
|
|
1984
|
-
const commands = [];
|
|
1985
|
-
for (const plugin of state.pluginDefinitions) {
|
|
1986
|
-
for (const command of plugin.manifest.runtimePostinstall ?? []) {
|
|
1987
|
-
commands.push({
|
|
1988
|
-
cmd: command.cmd,
|
|
1989
|
-
...command.args ? { args: [...command.args] } : void 0,
|
|
1990
|
-
...command.sudo !== void 0 ? { sudo: command.sudo } : void 0
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
return commands;
|
|
1995
|
-
},
|
|
1996
|
-
getOAuthConfig(provider) {
|
|
1997
|
-
const plugin = ensurePluginsLoaded(runtime).pluginsByName.get(provider);
|
|
1998
|
-
if (!plugin?.manifest.oauth) return void 0;
|
|
1999
|
-
const oauth = plugin.manifest.oauth;
|
|
2000
|
-
return {
|
|
2001
|
-
clientIdEnv: oauth.clientIdEnv,
|
|
2002
|
-
clientSecretEnv: oauth.clientSecretEnv,
|
|
2003
|
-
authorizeEndpoint: oauth.authorizeEndpoint,
|
|
2004
|
-
tokenEndpoint: oauth.tokenEndpoint,
|
|
2005
|
-
...oauth.scope ? { scope: oauth.scope } : void 0,
|
|
2006
|
-
...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : void 0,
|
|
2007
|
-
...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : void 0,
|
|
2008
|
-
...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : void 0,
|
|
2009
|
-
...oauth.treatEmptyScopeAsUnreported ? { treatEmptyScopeAsUnreported: true } : void 0,
|
|
2010
|
-
callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
|
|
2011
|
-
};
|
|
2012
|
-
},
|
|
2013
|
-
getSkillRoots() {
|
|
2014
|
-
const state = ensurePluginsLoaded(runtime);
|
|
2015
|
-
return [
|
|
2016
|
-
.../* @__PURE__ */ new Set([
|
|
2017
|
-
...state.pluginDefinitions.flatMap(
|
|
2018
|
-
(plugin) => plugin.skillsDir ? [plugin.skillsDir] : []
|
|
2019
|
-
),
|
|
2020
|
-
...state.packageSkillRoots
|
|
2021
|
-
])
|
|
2022
|
-
];
|
|
2023
|
-
},
|
|
2024
|
-
getForSkillPath(skillPath) {
|
|
2025
|
-
const state = ensurePluginsLoaded(runtime);
|
|
2026
|
-
const resolvedSkillPath = path.resolve(skillPath);
|
|
2027
|
-
return state.pluginDefinitions.find((plugin) => {
|
|
2028
|
-
if (!plugin.skillsDir) {
|
|
2029
|
-
return false;
|
|
2030
|
-
}
|
|
2031
|
-
const resolvedSkillsDir = path.resolve(plugin.skillsDir);
|
|
2032
|
-
return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${path.sep}`);
|
|
2033
|
-
});
|
|
2034
|
-
},
|
|
2035
|
-
getDefinition(provider) {
|
|
2036
|
-
return ensurePluginsLoaded(runtime).pluginsByName.get(provider);
|
|
2037
|
-
},
|
|
2038
|
-
getDisplayName(provider) {
|
|
2039
|
-
return ensurePluginsLoaded(runtime).pluginsByName.get(provider)?.manifest.displayName;
|
|
2040
|
-
},
|
|
2041
|
-
isProvider(provider) {
|
|
2042
|
-
return ensurePluginsLoaded(runtime).pluginsByName.has(provider);
|
|
2043
|
-
},
|
|
2044
|
-
isConfigKey(key) {
|
|
2045
|
-
return ensurePluginsLoaded(runtime).pluginConfigKeys.has(key);
|
|
2046
|
-
},
|
|
2047
|
-
createBroker(provider, deps) {
|
|
2048
|
-
const plugin = ensurePluginsLoaded(runtime).pluginsByName.get(provider);
|
|
2049
|
-
if (!plugin) {
|
|
2050
|
-
throw new Error(`Unknown plugin provider: "${provider}"`);
|
|
2051
|
-
}
|
|
2052
|
-
const { credentials, name } = plugin.manifest;
|
|
2053
|
-
if (!credentials && !plugin.manifest.apiHeaders) {
|
|
2054
|
-
throw new Error(
|
|
2055
|
-
`Provider "${name}" has no credentials or API headers configured`
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
let broker;
|
|
2059
|
-
if (!credentials) {
|
|
2060
|
-
broker = createApiHeadersBroker(plugin.manifest);
|
|
2061
|
-
} else {
|
|
2062
|
-
broker = createOAuthBearerBroker(plugin.manifest, credentials, deps);
|
|
2063
|
-
}
|
|
2064
|
-
setSpanAttributes({
|
|
2065
|
-
"app.plugin.name": name,
|
|
2066
|
-
"app.plugin.has_oauth": Boolean(plugin.manifest.oauth)
|
|
2067
|
-
});
|
|
2068
|
-
return broker;
|
|
2069
|
-
}
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
export {
|
|
2074
|
-
parsePluginManifest,
|
|
2075
|
-
parseInlinePluginManifest,
|
|
2076
|
-
CredentialUnavailableError,
|
|
2077
|
-
hasRequiredOAuthScope,
|
|
2078
|
-
OAuthProviderError,
|
|
2079
|
-
getOAuthProviderErrorAttributes,
|
|
2080
|
-
fetchWithBoundedOAuthErrorBodies,
|
|
2081
|
-
resolvePluginCommandEnv,
|
|
2082
|
-
resolveAuthTokenPlaceholder,
|
|
2083
|
-
buildOAuthTokenRequest,
|
|
2084
|
-
parseOAuthTokenResponse,
|
|
2085
|
-
createPluginCatalogRuntime
|
|
2086
|
-
};
|