@vercel/connect 2.3.1 → 2.3.3
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/eve/connection-authorization.d.ts +18 -0
- package/dist/eve/connection-authorization.js +10 -1
- package/dist/eve/slack-credentials.d.ts +4 -1
- package/dist/eve/slack-credentials.js +9 -0
- package/dist/internal/connect-manifest.d.ts +46 -0
- package/dist/internal/connect-manifest.js +10 -0
- package/dist/internal/eve-manifest.d.ts +2 -0
- package/dist/internal/eve-manifest.js +195 -0
- package/dist/internal/provider-manifest.d.ts +3 -0
- package/dist/internal/provider-manifest.js +3 -0
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.js +1 -0
- package/package.json +6 -2
|
@@ -77,6 +77,12 @@ export interface EveAuthorizationOptions {
|
|
|
77
77
|
* Vercel Connect app.
|
|
78
78
|
*/
|
|
79
79
|
readonly principalType?: 'app' | 'user';
|
|
80
|
+
/**
|
|
81
|
+
* Stable service identifier included in eve's external-resource snapshot.
|
|
82
|
+
* Set this when the connection should participate in Connect manifest
|
|
83
|
+
* generation; connector-only runtime usage may leave it unset.
|
|
84
|
+
*/
|
|
85
|
+
readonly service?: string;
|
|
80
86
|
/**
|
|
81
87
|
* Extra parameters forwarded to every `getTokenResponse` /
|
|
82
88
|
* `startAuthorization` call, minus `subject` — the helper derives
|
|
@@ -194,6 +200,10 @@ export type EveAuthorizationInput = string | EveAuthorizationOptions;
|
|
|
194
200
|
* URL) can resolve UID → `scl_...` against the Vercel Connect API;
|
|
195
201
|
* both forms address the same connector.
|
|
196
202
|
*
|
|
203
|
+
* Newer helpers may include a complete external-resource requirement for
|
|
204
|
+
* Eve's build tooling. It remains optional so existing connector-only metadata
|
|
205
|
+
* assignments stay source-compatible.
|
|
206
|
+
*
|
|
197
207
|
* The marker is purely metadata — it does not influence the runtime
|
|
198
208
|
* token-fetching identity, which continues to be driven by the
|
|
199
209
|
* `getToken` / `startAuthorization` / `completeAuthorization`
|
|
@@ -201,7 +211,15 @@ export type EveAuthorizationInput = string | EveAuthorizationOptions;
|
|
|
201
211
|
* also sent as the managed OAuth UID.
|
|
202
212
|
*/
|
|
203
213
|
export interface VercelConnectMetadata {
|
|
214
|
+
/** Raw connector UID or service-connector key passed to the helper. */
|
|
204
215
|
readonly connector: string;
|
|
216
|
+
/** Complete credential requirement consumed by eve build tooling. */
|
|
217
|
+
readonly requirement?: {
|
|
218
|
+
readonly method?: string;
|
|
219
|
+
readonly reference: string;
|
|
220
|
+
readonly service: string;
|
|
221
|
+
readonly subjectTypes: readonly ('app' | 'user')[];
|
|
222
|
+
};
|
|
205
223
|
}
|
|
206
224
|
/**
|
|
207
225
|
* Augments the standard Eve authorization shape with the
|
|
@@ -34,7 +34,16 @@ import { ConnectError, ConnectorInstallationRequiredError, deleteTokenCacheEntry
|
|
|
34
34
|
import { provisionEveOAuthConnector } from './provision-oauth-connector.js';
|
|
35
35
|
export function connect(input) {
|
|
36
36
|
const options = normalizeAuthorizationOptions(input);
|
|
37
|
-
const
|
|
37
|
+
const requirement = options.service === undefined
|
|
38
|
+
? undefined
|
|
39
|
+
: {
|
|
40
|
+
reference: `connector:${options.connector}`,
|
|
41
|
+
service: options.service,
|
|
42
|
+
subjectTypes: [options.principalType ?? 'user'],
|
|
43
|
+
};
|
|
44
|
+
const vercelConnect = requirement === undefined
|
|
45
|
+
? { connector: options.connector }
|
|
46
|
+
: { connector: options.connector, requirement };
|
|
38
47
|
const evict = makeEvict(options);
|
|
39
48
|
if (options.principalType === 'app') {
|
|
40
49
|
return { ...buildNonInteractiveDefinition(options), vercelConnect, evict };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SlackChannelCredentials } from 'eve/channels/slack';
|
|
2
2
|
import { type ConnectOptions, type ConnectTokenParams } from '../index.js';
|
|
3
|
+
import type { VercelConnectMetadata } from './connection-authorization.js';
|
|
3
4
|
/**
|
|
4
5
|
* Token parameters accepted by {@link connectSlackCredentials}.
|
|
5
6
|
*
|
|
@@ -43,4 +44,6 @@ export type ConnectSlackCredentialsParams = Omit<ConnectTokenParams, 'subject'>;
|
|
|
43
44
|
* connectSlackCredentials("scl_...", { installationId: workspaceId });
|
|
44
45
|
* ```
|
|
45
46
|
*/
|
|
46
|
-
export declare function connectSlackCredentials(connector: string, params?: ConnectSlackCredentialsParams, options?: ConnectOptions): SlackChannelCredentials
|
|
47
|
+
export declare function connectSlackCredentials(connector: string, params?: ConnectSlackCredentialsParams, options?: ConnectOptions): SlackChannelCredentials & {
|
|
48
|
+
readonly vercelConnect: VercelConnectMetadata;
|
|
49
|
+
};
|
|
@@ -38,5 +38,14 @@ export function connectSlackCredentials(connector, params = {}, options) {
|
|
|
38
38
|
return {
|
|
39
39
|
botToken: () => getToken(connector, { ...params, subject: { type: 'app' } }, options),
|
|
40
40
|
webhookVerifier: vercelOidc(),
|
|
41
|
+
vercelConnect: {
|
|
42
|
+
connector,
|
|
43
|
+
requirement: {
|
|
44
|
+
method: 'slack',
|
|
45
|
+
reference: `connector:${connector}`,
|
|
46
|
+
service: 'slack',
|
|
47
|
+
subjectTypes: ['app'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
41
50
|
};
|
|
42
51
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const CONNECT_MANIFEST_KIND = "vercel-connect-manifest";
|
|
2
|
+
export declare const CONNECT_MANIFEST_VERSION = 1;
|
|
3
|
+
export type JsonObject = {
|
|
4
|
+
readonly [key: string]: JsonValue;
|
|
5
|
+
};
|
|
6
|
+
export type JsonValue = boolean | null | number | string | readonly JsonValue[] | JsonObject;
|
|
7
|
+
export type ConnectSubjectType = 'app' | 'user';
|
|
8
|
+
export type ConnectInterface = {
|
|
9
|
+
readonly protocol: 'mcp' | 'openapi';
|
|
10
|
+
readonly url: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly npm: string;
|
|
13
|
+
readonly protocol: 'custom';
|
|
14
|
+
readonly url: string;
|
|
15
|
+
};
|
|
16
|
+
export interface ConnectRequirement {
|
|
17
|
+
readonly connect: {
|
|
18
|
+
readonly manifest: JsonObject;
|
|
19
|
+
readonly service: string;
|
|
20
|
+
readonly subjectTypes: readonly ConnectSubjectType[];
|
|
21
|
+
readonly type?: string;
|
|
22
|
+
};
|
|
23
|
+
readonly interfaces: readonly ConnectInterface[];
|
|
24
|
+
readonly target: string;
|
|
25
|
+
readonly triggers: readonly {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
}[];
|
|
28
|
+
readonly uses: readonly {
|
|
29
|
+
readonly kind: 'channel' | 'connection';
|
|
30
|
+
readonly logicalPath: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
export interface ConnectManifest {
|
|
35
|
+
readonly generator: {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly version: string;
|
|
38
|
+
};
|
|
39
|
+
readonly kind: typeof CONNECT_MANIFEST_KIND;
|
|
40
|
+
readonly requirements: readonly ConnectRequirement[];
|
|
41
|
+
readonly schemaVersion: typeof CONNECT_MANIFEST_VERSION;
|
|
42
|
+
}
|
|
43
|
+
export declare function createConnectManifest(input: {
|
|
44
|
+
readonly generator: ConnectManifest['generator'];
|
|
45
|
+
readonly requirements: readonly ConnectRequirement[];
|
|
46
|
+
}): ConnectManifest;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const CONNECT_MANIFEST_KIND = 'vercel-connect-manifest';
|
|
2
|
+
export const CONNECT_MANIFEST_VERSION = 1;
|
|
3
|
+
export function createConnectManifest(input) {
|
|
4
|
+
return {
|
|
5
|
+
generator: input.generator,
|
|
6
|
+
kind: CONNECT_MANIFEST_KIND,
|
|
7
|
+
requirements: input.requirements,
|
|
8
|
+
schemaVersion: CONNECT_MANIFEST_VERSION,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createConnectManifest, } from './connect-manifest.js';
|
|
2
|
+
import { RFC_7591_MANIFEST } from './provider-manifest.js';
|
|
3
|
+
const EVE_SNAPSHOT_KIND = 'eve-external-resources';
|
|
4
|
+
const EVE_SNAPSHOT_VERSION = 1;
|
|
5
|
+
export function createConnectManifestFromEve(input) {
|
|
6
|
+
const snapshot = parseEveSnapshot(input);
|
|
7
|
+
return createConnectManifest({
|
|
8
|
+
generator: snapshot.generator,
|
|
9
|
+
requirements: collectConnectRequirements(snapshot.resources),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function collectConnectRequirements(resources) {
|
|
13
|
+
const requirements = new Map();
|
|
14
|
+
for (const resource of resources) {
|
|
15
|
+
const next = toConnectRequirement(resource);
|
|
16
|
+
const current = requirements.get(next.target);
|
|
17
|
+
if (current === undefined) {
|
|
18
|
+
requirements.set(next.target, next);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
assertCompatibleRequirements(current, next);
|
|
22
|
+
requirements.set(next.target, {
|
|
23
|
+
...current,
|
|
24
|
+
interfaces: appendDistinct(current.interfaces, next.interfaces),
|
|
25
|
+
triggers: appendDistinct(current.triggers, next.triggers),
|
|
26
|
+
uses: appendDistinct(current.uses, next.uses),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return [...requirements.values()];
|
|
30
|
+
}
|
|
31
|
+
function assertCompatibleRequirements(current, next) {
|
|
32
|
+
const target = current.target;
|
|
33
|
+
if (current.connect.service !== next.connect.service) {
|
|
34
|
+
throw conflictingRequirement(target, 'service');
|
|
35
|
+
}
|
|
36
|
+
if (current.connect.type !== next.connect.type) {
|
|
37
|
+
throw conflictingRequirement(target, 'method');
|
|
38
|
+
}
|
|
39
|
+
if (canonicalJson([...current.connect.subjectTypes].sort()) !==
|
|
40
|
+
canonicalJson([...next.connect.subjectTypes].sort())) {
|
|
41
|
+
throw conflictingRequirement(target, 'subjectTypes');
|
|
42
|
+
}
|
|
43
|
+
if (canonicalJson(current.connect.manifest) !==
|
|
44
|
+
canonicalJson(next.connect.manifest)) {
|
|
45
|
+
throw conflictingRequirement(target, 'provider manifest');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function conflictingRequirement(target, field) {
|
|
49
|
+
return new Error(`Conflicting declarations for credential target "${target}": ${field} must agree.`);
|
|
50
|
+
}
|
|
51
|
+
function appendDistinct(current, additional) {
|
|
52
|
+
const seen = new Set(current.map(value => canonicalJson(value)));
|
|
53
|
+
return [
|
|
54
|
+
...current,
|
|
55
|
+
...additional.filter(value => {
|
|
56
|
+
const key = canonicalJson(value);
|
|
57
|
+
if (seen.has(key))
|
|
58
|
+
return false;
|
|
59
|
+
seen.add(key);
|
|
60
|
+
return true;
|
|
61
|
+
}),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
function canonicalJson(value) {
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
return `[${value.map(item => canonicalJson(item)).join(',')}]`;
|
|
67
|
+
}
|
|
68
|
+
if (value !== null && typeof value === 'object') {
|
|
69
|
+
const record = value;
|
|
70
|
+
return `{${Object.keys(record)
|
|
71
|
+
.sort()
|
|
72
|
+
.map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
73
|
+
.join(',')}}`;
|
|
74
|
+
}
|
|
75
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
76
|
+
}
|
|
77
|
+
function toConnectRequirement(resource) {
|
|
78
|
+
return {
|
|
79
|
+
connect: {
|
|
80
|
+
manifest: resource.kind === 'channel' ? resource.manifest : RFC_7591_MANIFEST,
|
|
81
|
+
service: resource.credentials.service,
|
|
82
|
+
subjectTypes: resource.credentials.subjectTypes,
|
|
83
|
+
...(resource.credentials.method === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { type: resource.credentials.method }),
|
|
86
|
+
},
|
|
87
|
+
interfaces: resource.kind === 'connection'
|
|
88
|
+
? [{ protocol: resource.protocol.type, url: resource.protocol.url }]
|
|
89
|
+
: [],
|
|
90
|
+
target: resource.credentials.reference,
|
|
91
|
+
triggers: resource.kind === 'channel' ? [resource.route] : [],
|
|
92
|
+
uses: [
|
|
93
|
+
{
|
|
94
|
+
kind: resource.kind,
|
|
95
|
+
logicalPath: resource.logicalPath,
|
|
96
|
+
name: resource.name,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function parseEveSnapshot(input) {
|
|
102
|
+
const snapshot = record(input, 'snapshot');
|
|
103
|
+
if (snapshot.kind !== EVE_SNAPSHOT_KIND ||
|
|
104
|
+
snapshot.schemaVersion !== EVE_SNAPSHOT_VERSION) {
|
|
105
|
+
throw new Error('Expected an eve-external-resources snapshot with schemaVersion 1.');
|
|
106
|
+
}
|
|
107
|
+
const generator = record(snapshot.generator, 'snapshot.generator');
|
|
108
|
+
if (generator.name !== 'eve')
|
|
109
|
+
throw new Error('Expected snapshot.generator.name to be "eve".');
|
|
110
|
+
const version = nonEmptyString(generator.version, 'snapshot.generator.version');
|
|
111
|
+
if (!Array.isArray(snapshot.resources))
|
|
112
|
+
throw new Error('Expected snapshot.resources to be an array.');
|
|
113
|
+
return {
|
|
114
|
+
generator: { name: 'eve', version },
|
|
115
|
+
kind: EVE_SNAPSHOT_KIND,
|
|
116
|
+
resources: snapshot.resources.map((value, index) => parseResource(value, index)),
|
|
117
|
+
schemaVersion: EVE_SNAPSHOT_VERSION,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function parseResource(value, index) {
|
|
121
|
+
const path = `snapshot.resources[${index}]`;
|
|
122
|
+
const resource = record(value, path);
|
|
123
|
+
const kind = resource.kind;
|
|
124
|
+
if (kind !== 'channel' && kind !== 'connection') {
|
|
125
|
+
throw new Error(`Expected ${path}.kind to be "channel" or "connection".`);
|
|
126
|
+
}
|
|
127
|
+
const common = {
|
|
128
|
+
credentials: parseCredentials(resource.credentials, `${path}.credentials`),
|
|
129
|
+
logicalPath: nonEmptyString(resource.logicalPath, `${path}.logicalPath`),
|
|
130
|
+
name: nonEmptyString(resource.name, `${path}.name`),
|
|
131
|
+
};
|
|
132
|
+
if (kind === 'connection') {
|
|
133
|
+
const protocol = record(resource.protocol, `${path}.protocol`);
|
|
134
|
+
const type = protocol.type;
|
|
135
|
+
if (type !== 'mcp' && type !== 'openapi') {
|
|
136
|
+
throw new Error(`Expected ${path}.protocol.type to be "mcp" or "openapi".`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...common,
|
|
140
|
+
kind,
|
|
141
|
+
protocol: {
|
|
142
|
+
type,
|
|
143
|
+
url: nonEmptyString(protocol.url, `${path}.protocol.url`),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const route = record(resource.route, `${path}.route`);
|
|
148
|
+
return {
|
|
149
|
+
...common,
|
|
150
|
+
kind,
|
|
151
|
+
logicalPath: common.logicalPath,
|
|
152
|
+
manifest: jsonObject(resource.manifest, `${path}.manifest`),
|
|
153
|
+
name: common.name,
|
|
154
|
+
route: { path: nonEmptyString(route.path, `${path}.route.path`) },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function parseCredentials(value, path) {
|
|
158
|
+
const credentials = record(value, path);
|
|
159
|
+
if (!Array.isArray(credentials.subjectTypes) ||
|
|
160
|
+
credentials.subjectTypes.length === 0) {
|
|
161
|
+
throw new Error(`Expected ${path}.subjectTypes to be a non-empty array.`);
|
|
162
|
+
}
|
|
163
|
+
const subjectTypes = credentials.subjectTypes.map((value, index) => {
|
|
164
|
+
if (value !== 'app' && value !== 'user') {
|
|
165
|
+
throw new Error(`Expected ${path}.subjectTypes[${index}] to be "app" or "user".`);
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
});
|
|
169
|
+
const method = optionalNonEmptyString(credentials.method, `${path}.method`);
|
|
170
|
+
return {
|
|
171
|
+
...(method === undefined ? {} : { method }),
|
|
172
|
+
reference: nonEmptyString(credentials.reference, `${path}.reference`),
|
|
173
|
+
service: nonEmptyString(credentials.service, `${path}.service`),
|
|
174
|
+
subjectTypes,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function jsonObject(value, path) {
|
|
178
|
+
const parsed = JSON.parse(JSON.stringify(value));
|
|
179
|
+
return record(parsed, path);
|
|
180
|
+
}
|
|
181
|
+
function optionalNonEmptyString(value, path) {
|
|
182
|
+
return value === undefined ? undefined : nonEmptyString(value, path);
|
|
183
|
+
}
|
|
184
|
+
function nonEmptyString(value, path) {
|
|
185
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
186
|
+
throw new Error(`Expected ${path} to be a non-empty string.`);
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
function record(value, path) {
|
|
191
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
192
|
+
throw new Error(`Expected ${path} to be an object.`);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createConnectManifestFromEve as experimental_createConnectManifestFromEveResources } from '../internal/eve-manifest.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/connect",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/eve/index.d.ts",
|
|
22
22
|
"default": "./dist/eve/index.js"
|
|
23
23
|
},
|
|
24
|
+
"./manifest": {
|
|
25
|
+
"types": "./dist/manifest/index.d.ts",
|
|
26
|
+
"default": "./dist/manifest/index.js"
|
|
27
|
+
},
|
|
24
28
|
"./betterauth": {
|
|
25
29
|
"types": "./dist/betterauth/index.d.ts",
|
|
26
30
|
"default": "./dist/betterauth/index.js"
|
|
@@ -46,7 +50,7 @@
|
|
|
46
50
|
"dist"
|
|
47
51
|
],
|
|
48
52
|
"dependencies": {
|
|
49
|
-
"@vercel/oidc": "3.8.
|
|
53
|
+
"@vercel/oidc": "3.8.9"
|
|
50
54
|
},
|
|
51
55
|
"peerDependencies": {
|
|
52
56
|
"@ai-sdk/mcp": "^1 || ^2",
|