@vellumai/assistant 0.5.11 → 0.5.13
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/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
package/src/oauth/oauth-store.ts
CHANGED
|
@@ -45,12 +45,17 @@ export type OAuthConnectionRow = typeof oauthConnections.$inferSelect;
|
|
|
45
45
|
* Seed well-known provider profiles into the database. Uses INSERT … ON
|
|
46
46
|
* CONFLICT DO UPDATE so that implementation fields (authUrl, tokenUrl,
|
|
47
47
|
* tokenEndpointAuthMethod, userinfoUrl, extraParams, callbackTransport,
|
|
48
|
-
* pingUrl, pingMethod, pingHeaders, pingBody, managedServiceConfigKey
|
|
48
|
+
* pingUrl, pingMethod, pingHeaders, pingBody, managedServiceConfigKey,
|
|
49
|
+
* loopbackPort, injectionTemplates, appType, setupNotes,
|
|
50
|
+
* identityUrl, identityMethod, identityHeaders, identityBody,
|
|
51
|
+
* identityResponsePaths, identityFormat, identityOkField)
|
|
49
52
|
* and display metadata (displayName, description, dashboardUrl,
|
|
50
53
|
* clientIdPlaceholder, requiresClientSecret) propagate to existing
|
|
51
54
|
* installations on every startup, while user-customizable fields
|
|
52
|
-
* (defaultScopes, scopePolicy
|
|
53
|
-
* initial insert.
|
|
55
|
+
* (defaultScopes, scopePolicy) are only written on the
|
|
56
|
+
* initial insert. baseUrl is backfilled from seed data when null
|
|
57
|
+
* (e.g. legacy rows created before the column existed) but preserved
|
|
58
|
+
* if the user has set a custom value.
|
|
54
59
|
*/
|
|
55
60
|
export function seedProviders(
|
|
56
61
|
profiles: Array<{
|
|
@@ -74,6 +79,22 @@ export function seedProviders(
|
|
|
74
79
|
dashboardUrl?: string | null;
|
|
75
80
|
clientIdPlaceholder?: string | null;
|
|
76
81
|
requiresClientSecret?: boolean;
|
|
82
|
+
loopbackPort?: number;
|
|
83
|
+
injectionTemplates?: Array<{
|
|
84
|
+
hostPattern: string;
|
|
85
|
+
injectionType: string;
|
|
86
|
+
headerName: string;
|
|
87
|
+
valuePrefix: string;
|
|
88
|
+
}>;
|
|
89
|
+
appType?: string;
|
|
90
|
+
setupNotes?: string[];
|
|
91
|
+
identityUrl?: string;
|
|
92
|
+
identityMethod?: string;
|
|
93
|
+
identityHeaders?: Record<string, string>;
|
|
94
|
+
identityBody?: unknown;
|
|
95
|
+
identityResponsePaths?: string[];
|
|
96
|
+
identityFormat?: string;
|
|
97
|
+
identityOkField?: string;
|
|
77
98
|
}>,
|
|
78
99
|
): void {
|
|
79
100
|
const db = getDb();
|
|
@@ -99,6 +120,24 @@ export function seedProviders(
|
|
|
99
120
|
const dashboardUrl = p.dashboardUrl ?? null;
|
|
100
121
|
const clientIdPlaceholder = p.clientIdPlaceholder ?? null;
|
|
101
122
|
const requiresClientSecret = p.requiresClientSecret !== false ? 1 : 0;
|
|
123
|
+
const loopbackPort = p.loopbackPort ?? null;
|
|
124
|
+
const injectionTemplates = p.injectionTemplates
|
|
125
|
+
? JSON.stringify(p.injectionTemplates)
|
|
126
|
+
: null;
|
|
127
|
+
const appType = p.appType ?? null;
|
|
128
|
+
const setupNotes = p.setupNotes ? JSON.stringify(p.setupNotes) : null;
|
|
129
|
+
const identityUrl = p.identityUrl ?? null;
|
|
130
|
+
const identityMethod = p.identityMethod ?? null;
|
|
131
|
+
const identityHeaders = p.identityHeaders
|
|
132
|
+
? JSON.stringify(p.identityHeaders)
|
|
133
|
+
: null;
|
|
134
|
+
const identityBody =
|
|
135
|
+
p.identityBody !== undefined ? JSON.stringify(p.identityBody) : null;
|
|
136
|
+
const identityResponsePaths = p.identityResponsePaths
|
|
137
|
+
? JSON.stringify(p.identityResponsePaths)
|
|
138
|
+
: null;
|
|
139
|
+
const identityFormat = p.identityFormat ?? null;
|
|
140
|
+
const identityOkField = p.identityOkField ?? null;
|
|
102
141
|
|
|
103
142
|
db.insert(oauthProviders)
|
|
104
143
|
.values({
|
|
@@ -122,6 +161,17 @@ export function seedProviders(
|
|
|
122
161
|
dashboardUrl,
|
|
123
162
|
clientIdPlaceholder,
|
|
124
163
|
requiresClientSecret,
|
|
164
|
+
loopbackPort,
|
|
165
|
+
injectionTemplates,
|
|
166
|
+
appType,
|
|
167
|
+
setupNotes,
|
|
168
|
+
identityUrl,
|
|
169
|
+
identityMethod,
|
|
170
|
+
identityHeaders,
|
|
171
|
+
identityBody,
|
|
172
|
+
identityResponsePaths,
|
|
173
|
+
identityFormat,
|
|
174
|
+
identityOkField,
|
|
125
175
|
createdAt: now,
|
|
126
176
|
updatedAt: now,
|
|
127
177
|
})
|
|
@@ -132,6 +182,7 @@ export function seedProviders(
|
|
|
132
182
|
tokenUrl,
|
|
133
183
|
tokenEndpointAuthMethod,
|
|
134
184
|
userinfoUrl,
|
|
185
|
+
baseUrl: sql`COALESCE(${oauthProviders.baseUrl}, ${baseUrl})`,
|
|
135
186
|
extraParams,
|
|
136
187
|
callbackTransport,
|
|
137
188
|
pingUrl,
|
|
@@ -144,6 +195,17 @@ export function seedProviders(
|
|
|
144
195
|
dashboardUrl,
|
|
145
196
|
clientIdPlaceholder,
|
|
146
197
|
requiresClientSecret,
|
|
198
|
+
loopbackPort,
|
|
199
|
+
injectionTemplates,
|
|
200
|
+
appType,
|
|
201
|
+
setupNotes,
|
|
202
|
+
identityUrl,
|
|
203
|
+
identityMethod,
|
|
204
|
+
identityHeaders,
|
|
205
|
+
identityBody,
|
|
206
|
+
identityResponsePaths,
|
|
207
|
+
identityFormat,
|
|
208
|
+
identityOkField,
|
|
147
209
|
updatedAt: now,
|
|
148
210
|
},
|
|
149
211
|
})
|
|
@@ -192,6 +254,22 @@ export function registerProvider(params: {
|
|
|
192
254
|
dashboardUrl?: string;
|
|
193
255
|
clientIdPlaceholder?: string;
|
|
194
256
|
requiresClientSecret?: number;
|
|
257
|
+
loopbackPort?: number;
|
|
258
|
+
injectionTemplates?: Array<{
|
|
259
|
+
hostPattern: string;
|
|
260
|
+
injectionType: string;
|
|
261
|
+
headerName: string;
|
|
262
|
+
valuePrefix: string;
|
|
263
|
+
}>;
|
|
264
|
+
appType?: string;
|
|
265
|
+
setupNotes?: string[];
|
|
266
|
+
identityUrl?: string;
|
|
267
|
+
identityMethod?: string;
|
|
268
|
+
identityHeaders?: Record<string, string>;
|
|
269
|
+
identityBody?: unknown;
|
|
270
|
+
identityResponsePaths?: string[];
|
|
271
|
+
identityFormat?: string;
|
|
272
|
+
identityOkField?: string;
|
|
195
273
|
}): OAuthProviderRow {
|
|
196
274
|
const db = getDb();
|
|
197
275
|
const now = Date.now();
|
|
@@ -223,6 +301,26 @@ export function registerProvider(params: {
|
|
|
223
301
|
dashboardUrl: params.dashboardUrl ?? null,
|
|
224
302
|
clientIdPlaceholder: params.clientIdPlaceholder ?? null,
|
|
225
303
|
requiresClientSecret: params.requiresClientSecret ?? 1,
|
|
304
|
+
loopbackPort: params.loopbackPort ?? null,
|
|
305
|
+
injectionTemplates: params.injectionTemplates
|
|
306
|
+
? JSON.stringify(params.injectionTemplates)
|
|
307
|
+
: null,
|
|
308
|
+
appType: params.appType ?? null,
|
|
309
|
+
setupNotes: params.setupNotes ? JSON.stringify(params.setupNotes) : null,
|
|
310
|
+
identityUrl: params.identityUrl ?? null,
|
|
311
|
+
identityMethod: params.identityMethod ?? null,
|
|
312
|
+
identityHeaders: params.identityHeaders
|
|
313
|
+
? JSON.stringify(params.identityHeaders)
|
|
314
|
+
: null,
|
|
315
|
+
identityBody:
|
|
316
|
+
params.identityBody !== undefined
|
|
317
|
+
? JSON.stringify(params.identityBody)
|
|
318
|
+
: null,
|
|
319
|
+
identityResponsePaths: params.identityResponsePaths
|
|
320
|
+
? JSON.stringify(params.identityResponsePaths)
|
|
321
|
+
: null,
|
|
322
|
+
identityFormat: params.identityFormat ?? null,
|
|
323
|
+
identityOkField: params.identityOkField ?? null,
|
|
226
324
|
createdAt: now,
|
|
227
325
|
updatedAt: now,
|
|
228
326
|
};
|
|
@@ -232,6 +330,133 @@ export function registerProvider(params: {
|
|
|
232
330
|
return row;
|
|
233
331
|
}
|
|
234
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Update mutable fields on an existing provider. Only the fields explicitly
|
|
335
|
+
* provided (not `undefined`) are written; everything else is left unchanged.
|
|
336
|
+
* JSON fields (defaultScopes, scopePolicy, extraParams, pingHeaders, pingBody)
|
|
337
|
+
* are serialized with JSON.stringify before storage.
|
|
338
|
+
*
|
|
339
|
+
* Returns the updated provider row, or `undefined` if no provider with the
|
|
340
|
+
* given key exists.
|
|
341
|
+
*/
|
|
342
|
+
export function updateProvider(
|
|
343
|
+
providerKey: string,
|
|
344
|
+
params: Partial<{
|
|
345
|
+
authUrl: string;
|
|
346
|
+
tokenUrl: string;
|
|
347
|
+
tokenEndpointAuthMethod: string;
|
|
348
|
+
userinfoUrl: string;
|
|
349
|
+
pingUrl: string;
|
|
350
|
+
pingMethod: string;
|
|
351
|
+
pingHeaders: Record<string, string>;
|
|
352
|
+
pingBody: unknown;
|
|
353
|
+
baseUrl: string;
|
|
354
|
+
defaultScopes: string[];
|
|
355
|
+
scopePolicy: Record<string, unknown>;
|
|
356
|
+
extraParams: Record<string, string>;
|
|
357
|
+
callbackTransport: string;
|
|
358
|
+
displayName: string;
|
|
359
|
+
description: string;
|
|
360
|
+
dashboardUrl: string;
|
|
361
|
+
clientIdPlaceholder: string;
|
|
362
|
+
requiresClientSecret: boolean;
|
|
363
|
+
loopbackPort: number;
|
|
364
|
+
injectionTemplates: Array<{
|
|
365
|
+
hostPattern: string;
|
|
366
|
+
injectionType: string;
|
|
367
|
+
headerName: string;
|
|
368
|
+
valuePrefix: string;
|
|
369
|
+
}>;
|
|
370
|
+
appType: string;
|
|
371
|
+
setupNotes: string[];
|
|
372
|
+
identityUrl: string;
|
|
373
|
+
identityMethod: string;
|
|
374
|
+
identityHeaders: Record<string, string>;
|
|
375
|
+
identityBody: unknown;
|
|
376
|
+
identityResponsePaths: string[];
|
|
377
|
+
identityFormat: string;
|
|
378
|
+
identityOkField: string;
|
|
379
|
+
}>,
|
|
380
|
+
): OAuthProviderRow | undefined {
|
|
381
|
+
const existing = getProvider(providerKey);
|
|
382
|
+
if (!existing) return undefined;
|
|
383
|
+
|
|
384
|
+
const db = getDb();
|
|
385
|
+
const set: Record<string, unknown> = { updatedAt: Date.now() };
|
|
386
|
+
|
|
387
|
+
if (params.authUrl !== undefined) set.authUrl = params.authUrl;
|
|
388
|
+
if (params.tokenUrl !== undefined) set.tokenUrl = params.tokenUrl;
|
|
389
|
+
if (params.tokenEndpointAuthMethod !== undefined)
|
|
390
|
+
set.tokenEndpointAuthMethod = params.tokenEndpointAuthMethod;
|
|
391
|
+
if (params.userinfoUrl !== undefined) set.userinfoUrl = params.userinfoUrl;
|
|
392
|
+
if (params.pingUrl !== undefined) set.pingUrl = params.pingUrl;
|
|
393
|
+
if (params.pingMethod !== undefined) set.pingMethod = params.pingMethod;
|
|
394
|
+
if (params.pingHeaders !== undefined)
|
|
395
|
+
set.pingHeaders = JSON.stringify(params.pingHeaders);
|
|
396
|
+
if (params.pingBody !== undefined)
|
|
397
|
+
set.pingBody = JSON.stringify(params.pingBody);
|
|
398
|
+
if (params.baseUrl !== undefined) set.baseUrl = params.baseUrl;
|
|
399
|
+
if (params.defaultScopes !== undefined)
|
|
400
|
+
set.defaultScopes = JSON.stringify(params.defaultScopes);
|
|
401
|
+
if (params.scopePolicy !== undefined)
|
|
402
|
+
set.scopePolicy = JSON.stringify(params.scopePolicy);
|
|
403
|
+
if (params.extraParams !== undefined)
|
|
404
|
+
set.extraParams = JSON.stringify(params.extraParams);
|
|
405
|
+
if (params.callbackTransport !== undefined)
|
|
406
|
+
set.callbackTransport = params.callbackTransport;
|
|
407
|
+
if (params.displayName !== undefined) set.displayName = params.displayName;
|
|
408
|
+
if (params.description !== undefined) set.description = params.description;
|
|
409
|
+
if (params.dashboardUrl !== undefined) set.dashboardUrl = params.dashboardUrl;
|
|
410
|
+
if (params.clientIdPlaceholder !== undefined)
|
|
411
|
+
set.clientIdPlaceholder = params.clientIdPlaceholder;
|
|
412
|
+
if (params.requiresClientSecret !== undefined)
|
|
413
|
+
set.requiresClientSecret = params.requiresClientSecret ? 1 : 0;
|
|
414
|
+
if (params.loopbackPort !== undefined) set.loopbackPort = params.loopbackPort;
|
|
415
|
+
if (params.injectionTemplates !== undefined)
|
|
416
|
+
set.injectionTemplates = JSON.stringify(params.injectionTemplates);
|
|
417
|
+
if (params.appType !== undefined) set.appType = params.appType;
|
|
418
|
+
if (params.setupNotes !== undefined)
|
|
419
|
+
set.setupNotes = JSON.stringify(params.setupNotes);
|
|
420
|
+
if (params.identityUrl !== undefined) set.identityUrl = params.identityUrl;
|
|
421
|
+
if (params.identityMethod !== undefined)
|
|
422
|
+
set.identityMethod = params.identityMethod;
|
|
423
|
+
if (params.identityHeaders !== undefined)
|
|
424
|
+
set.identityHeaders = JSON.stringify(params.identityHeaders);
|
|
425
|
+
if (params.identityBody !== undefined)
|
|
426
|
+
set.identityBody = JSON.stringify(params.identityBody);
|
|
427
|
+
if (params.identityResponsePaths !== undefined)
|
|
428
|
+
set.identityResponsePaths = JSON.stringify(params.identityResponsePaths);
|
|
429
|
+
if (params.identityFormat !== undefined)
|
|
430
|
+
set.identityFormat = params.identityFormat;
|
|
431
|
+
if (params.identityOkField !== undefined)
|
|
432
|
+
set.identityOkField = params.identityOkField;
|
|
433
|
+
|
|
434
|
+
db.update(oauthProviders)
|
|
435
|
+
.set(set)
|
|
436
|
+
.where(eq(oauthProviders.providerKey, providerKey))
|
|
437
|
+
.run();
|
|
438
|
+
|
|
439
|
+
return getProvider(providerKey);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Delete a provider by its key. Returns `true` if a row was deleted,
|
|
444
|
+
* `false` if no provider with that key existed.
|
|
445
|
+
*
|
|
446
|
+
* Note: SQLite enforces the foreign-key constraint from `oauth_apps.provider_key`,
|
|
447
|
+
* so deleting a provider that has existing apps will throw.
|
|
448
|
+
*/
|
|
449
|
+
export function deleteProvider(providerKey: string): boolean {
|
|
450
|
+
const existing = getProvider(providerKey);
|
|
451
|
+
if (!existing) return false;
|
|
452
|
+
|
|
453
|
+
const db = getDb();
|
|
454
|
+
db.delete(oauthProviders)
|
|
455
|
+
.where(eq(oauthProviders.providerKey, providerKey))
|
|
456
|
+
.run();
|
|
457
|
+
return rawChanges() > 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
235
460
|
// ---------------------------------------------------------------------------
|
|
236
461
|
// App operations
|
|
237
462
|
// ---------------------------------------------------------------------------
|
|
@@ -35,7 +35,7 @@ function makeMockClient(
|
|
|
35
35
|
|
|
36
36
|
const DEFAULT_OPTIONS = {
|
|
37
37
|
id: "conn-1",
|
|
38
|
-
providerKey: "
|
|
38
|
+
providerKey: "google",
|
|
39
39
|
externalId: "ext-123",
|
|
40
40
|
accountInfo: "user@example.com",
|
|
41
41
|
client: makeMockClient(),
|
|
@@ -93,11 +93,11 @@ describe("PlatformOAuthConnection", () => {
|
|
|
93
93
|
expect(result.body).toEqual(upstreamBody);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
test("forwards baseUrl when provided", async () => {
|
|
96
|
+
test("forwards per-request baseUrl when provided", async () => {
|
|
97
97
|
const client = makeMockClient(
|
|
98
98
|
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
99
99
|
const parsed = JSON.parse(init?.body as string);
|
|
100
|
-
expect(parsed.request.
|
|
100
|
+
expect(parsed.request.base_url).toBe(
|
|
101
101
|
"https://www.googleapis.com/calendar/v3",
|
|
102
102
|
);
|
|
103
103
|
|
|
@@ -116,11 +116,61 @@ describe("PlatformOAuthConnection", () => {
|
|
|
116
116
|
});
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
test("
|
|
119
|
+
test("falls back to connection-level baseUrl when per-request baseUrl is absent", async () => {
|
|
120
120
|
const client = makeMockClient(
|
|
121
121
|
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
122
122
|
const parsed = JSON.parse(init?.body as string);
|
|
123
|
-
expect(
|
|
123
|
+
expect(parsed.request.base_url).toBe(
|
|
124
|
+
"https://gmail.googleapis.com/gmail/v1/users/me",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return new Response(
|
|
128
|
+
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
129
|
+
{ status: 200 },
|
|
130
|
+
);
|
|
131
|
+
}) as unknown as typeof globalThis.fetch,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const conn = new PlatformOAuthConnection({
|
|
135
|
+
...DEFAULT_OPTIONS,
|
|
136
|
+
client,
|
|
137
|
+
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
138
|
+
});
|
|
139
|
+
await conn.request({ method: "GET", path: "/messages" });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("per-request baseUrl overrides connection-level baseUrl", async () => {
|
|
143
|
+
const client = makeMockClient(
|
|
144
|
+
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
145
|
+
const parsed = JSON.parse(init?.body as string);
|
|
146
|
+
expect(parsed.request.base_url).toBe(
|
|
147
|
+
"https://www.googleapis.com/calendar/v3",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return new Response(
|
|
151
|
+
JSON.stringify({ status: 200, headers: {}, body: {} }),
|
|
152
|
+
{ status: 200 },
|
|
153
|
+
);
|
|
154
|
+
}) as unknown as typeof globalThis.fetch,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const conn = new PlatformOAuthConnection({
|
|
158
|
+
...DEFAULT_OPTIONS,
|
|
159
|
+
client,
|
|
160
|
+
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
161
|
+
});
|
|
162
|
+
await conn.request({
|
|
163
|
+
method: "GET",
|
|
164
|
+
path: "/calendars/primary/events",
|
|
165
|
+
baseUrl: "https://www.googleapis.com/calendar/v3",
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("omits base_url from envelope when neither connection nor request provides one", async () => {
|
|
170
|
+
const client = makeMockClient(
|
|
171
|
+
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
172
|
+
const parsed = JSON.parse(init?.body as string);
|
|
173
|
+
expect("base_url" in parsed.request).toBe(false);
|
|
124
174
|
|
|
125
175
|
return new Response(
|
|
126
176
|
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
@@ -192,7 +242,7 @@ describe("PlatformOAuthConnection", () => {
|
|
|
192
242
|
const conn = new PlatformOAuthConnection({
|
|
193
243
|
...DEFAULT_OPTIONS,
|
|
194
244
|
client,
|
|
195
|
-
providerKey: "
|
|
245
|
+
providerKey: "slack",
|
|
196
246
|
connectionId: "slack-conn-456",
|
|
197
247
|
});
|
|
198
248
|
await conn.request({ method: "GET", path: "/test" });
|
|
@@ -28,6 +28,9 @@ export interface PlatformOAuthConnectionOptions {
|
|
|
28
28
|
client: VellumPlatformClient;
|
|
29
29
|
/** Platform-side connection ID used in the proxy URL path. */
|
|
30
30
|
connectionId: string;
|
|
31
|
+
/** Provider API base URL (e.g. "https://gmail.googleapis.com/gmail/v1/users/me").
|
|
32
|
+
* Sent to the proxy so it can construct the full upstream URL. */
|
|
33
|
+
baseUrl?: string;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export class PlatformOAuthConnection implements OAuthConnection {
|
|
@@ -38,6 +41,7 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
38
41
|
|
|
39
42
|
private readonly client: VellumPlatformClient;
|
|
40
43
|
private readonly connectionId: string;
|
|
44
|
+
private readonly baseUrl: string | undefined;
|
|
41
45
|
|
|
42
46
|
constructor(options: PlatformOAuthConnectionOptions) {
|
|
43
47
|
if (!options.connectionId) {
|
|
@@ -53,6 +57,7 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
53
57
|
this.accountInfo = options.accountInfo;
|
|
54
58
|
this.client = options.client;
|
|
55
59
|
this.connectionId = options.connectionId;
|
|
60
|
+
this.baseUrl = options.baseUrl;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
@@ -65,7 +70,9 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
65
70
|
query: req.query ?? {},
|
|
66
71
|
headers: req.headers ?? {},
|
|
67
72
|
body: req.body ?? null,
|
|
68
|
-
...(req.baseUrl
|
|
73
|
+
...((req.baseUrl ?? this.baseUrl)
|
|
74
|
+
? { base_url: req.baseUrl ?? this.baseUrl }
|
|
75
|
+
: {}),
|
|
69
76
|
},
|
|
70
77
|
};
|
|
71
78
|
|