@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.
Files changed (209) hide show
  1. package/Dockerfile +42 -9
  2. package/docs/architecture/integrations.md +34 -32
  3. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  4. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  5. package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
  7. package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
  8. package/openapi.yaml +87 -9
  9. package/package.json +1 -1
  10. package/src/__tests__/catalog-cache.test.ts +164 -0
  11. package/src/__tests__/catalog-search.test.ts +61 -0
  12. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  13. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  14. package/src/__tests__/conversation-error.test.ts +3 -2
  15. package/src/__tests__/credential-security-invariants.test.ts +9 -15
  16. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  17. package/src/__tests__/credential-vault.test.ts +25 -33
  18. package/src/__tests__/credentials-cli.test.ts +3 -3
  19. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  20. package/src/__tests__/first-greeting.test.ts +7 -0
  21. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  22. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  23. package/src/__tests__/host-file-proxy.test.ts +89 -0
  24. package/src/__tests__/integration-status.test.ts +5 -5
  25. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  26. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  27. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  28. package/src/__tests__/navigate-settings-tab.test.ts +6 -2
  29. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  30. package/src/__tests__/oauth-cli.test.ts +126 -119
  31. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  32. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  33. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  34. package/src/__tests__/platform.test.ts +3 -168
  35. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  36. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  37. package/src/__tests__/skill-feature-flags.test.ts +8 -0
  38. package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  41. package/src/__tests__/slack-share-routes.test.ts +5 -5
  42. package/src/__tests__/system-prompt.test.ts +39 -0
  43. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
  44. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  45. package/src/cli/AGENTS.md +47 -7
  46. package/src/cli/commands/browser-relay.ts +2 -17
  47. package/src/cli/commands/contacts.ts +6 -4
  48. package/src/cli/commands/conversations.ts +13 -1
  49. package/src/cli/commands/credential-execution.ts +16 -1
  50. package/src/cli/commands/credentials.ts +2 -8
  51. package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
  52. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
  53. package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
  54. package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
  55. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  56. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  57. package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
  58. package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
  59. package/src/cli/commands/oauth/apps.ts +63 -44
  60. package/src/cli/commands/oauth/connect.ts +187 -155
  61. package/src/cli/commands/oauth/disconnect.ts +27 -75
  62. package/src/cli/commands/oauth/index.ts +36 -46
  63. package/src/cli/commands/oauth/mode.ts +22 -34
  64. package/src/cli/commands/oauth/ping.ts +19 -45
  65. package/src/cli/commands/oauth/providers.ts +569 -62
  66. package/src/cli/commands/oauth/request.ts +36 -48
  67. package/src/cli/commands/oauth/shared.ts +1 -19
  68. package/src/cli/commands/oauth/status.ts +14 -25
  69. package/src/cli/commands/oauth/token.ts +25 -34
  70. package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
  71. package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
  72. package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
  73. package/src/cli/commands/platform/connect.ts +104 -0
  74. package/src/cli/commands/platform/disconnect.ts +118 -0
  75. package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
  76. package/src/cli/commands/sequence.ts +5 -4
  77. package/src/cli/commands/shotgun.ts +16 -0
  78. package/src/cli/commands/skills.ts +173 -41
  79. package/src/cli/commands/usage.ts +5 -11
  80. package/src/cli/lib/daemon-credential-client.ts +22 -38
  81. package/src/cli/program.ts +1 -1
  82. package/src/config/assistant-feature-flags.ts +3 -7
  83. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  84. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  85. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  86. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  87. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  88. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  89. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  90. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  91. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  92. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  93. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  94. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  95. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  96. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  97. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  98. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  99. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  100. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  101. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  102. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  103. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  104. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  105. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  106. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  107. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
  108. package/src/config/bundled-tool-registry.ts +5 -0
  109. package/src/config/feature-flag-registry.json +2 -2
  110. package/src/credential-execution/client.ts +15 -3
  111. package/src/daemon/conversation-agent-loop.ts +2 -0
  112. package/src/daemon/conversation-error.ts +36 -6
  113. package/src/daemon/conversation-messaging.ts +9 -0
  114. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  115. package/src/daemon/conversation-surfaces.ts +120 -14
  116. package/src/daemon/conversation.ts +5 -0
  117. package/src/daemon/first-greeting.ts +6 -1
  118. package/src/daemon/handlers/skills.ts +148 -3
  119. package/src/daemon/host-bash-proxy.ts +16 -0
  120. package/src/daemon/host-cu-proxy.ts +16 -0
  121. package/src/daemon/host-file-proxy.ts +16 -0
  122. package/src/daemon/lifecycle.ts +56 -5
  123. package/src/daemon/message-types/conversations.ts +1 -0
  124. package/src/daemon/message-types/guardian-actions.ts +2 -0
  125. package/src/daemon/message-types/host-bash.ts +6 -1
  126. package/src/daemon/message-types/host-cu.ts +6 -1
  127. package/src/daemon/message-types/host-file.ts +6 -1
  128. package/src/daemon/message-types/integrations.ts +0 -1
  129. package/src/daemon/server.ts +29 -2
  130. package/src/hooks/cli.ts +74 -0
  131. package/src/inbound/platform-callback-registration.ts +7 -12
  132. package/src/index.ts +0 -12
  133. package/src/mcp/client.ts +6 -1
  134. package/src/mcp/manager.ts +2 -1
  135. package/src/memory/conversation-crud.ts +92 -3
  136. package/src/memory/conversation-key-store.ts +26 -0
  137. package/src/memory/conversation-queries.ts +6 -6
  138. package/src/memory/db-init.ts +16 -0
  139. package/src/memory/journal-memory.ts +8 -2
  140. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  141. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  142. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  143. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  144. package/src/memory/migrations/index.ts +4 -0
  145. package/src/memory/migrations/registry.ts +8 -0
  146. package/src/memory/schema/oauth.ts +11 -0
  147. package/src/messaging/provider.ts +13 -12
  148. package/src/messaging/providers/gmail/adapter.ts +44 -35
  149. package/src/messaging/providers/slack/adapter.ts +63 -33
  150. package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
  151. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  152. package/src/notifications/adapters/telegram.ts +78 -2
  153. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  154. package/src/oauth/byo-connection.test.ts +22 -24
  155. package/src/oauth/connect-orchestrator.ts +37 -76
  156. package/src/oauth/connect-types.ts +7 -65
  157. package/src/oauth/connection-resolver.test.ts +13 -13
  158. package/src/oauth/connection-resolver.ts +3 -4
  159. package/src/oauth/identity-verifier.ts +177 -0
  160. package/src/oauth/oauth-store.ts +228 -3
  161. package/src/oauth/platform-connection.test.ts +56 -6
  162. package/src/oauth/platform-connection.ts +8 -1
  163. package/src/oauth/seed-providers.ts +247 -34
  164. package/src/permissions/checker.ts +127 -1
  165. package/src/prompts/journal-context.ts +4 -1
  166. package/src/prompts/system-prompt.ts +54 -9
  167. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  168. package/src/providers/anthropic/client.ts +2 -33
  169. package/src/runtime/guardian-action-service.ts +7 -2
  170. package/src/runtime/http-server.ts +12 -18
  171. package/src/runtime/http-types.ts +8 -1
  172. package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
  173. package/src/runtime/routes/conversation-management-routes.ts +31 -0
  174. package/src/runtime/routes/conversation-routes.ts +79 -4
  175. package/src/runtime/routes/guardian-action-routes.ts +15 -2
  176. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  177. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  178. package/src/runtime/routes/oauth-apps.ts +2 -1
  179. package/src/runtime/routes/secret-routes.ts +45 -15
  180. package/src/runtime/routes/settings-routes.ts +12 -19
  181. package/src/runtime/routes/skills-routes.ts +45 -4
  182. package/src/schedule/integration-status.ts +2 -2
  183. package/src/security/ces-rpc-credential-backend.ts +19 -16
  184. package/src/security/oauth-completion-page.ts +153 -0
  185. package/src/security/oauth2.ts +3 -17
  186. package/src/security/secure-keys.ts +207 -7
  187. package/src/security/token-manager.ts +3 -6
  188. package/src/signals/bash.ts +6 -1
  189. package/src/skills/catalog-cache.ts +44 -0
  190. package/src/skills/catalog-search.ts +18 -0
  191. package/src/tools/browser/browser-manager.ts +2 -2
  192. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  193. package/src/tools/credentials/vault.ts +34 -45
  194. package/src/tools/host-terminal/host-shell.ts +16 -3
  195. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  196. package/src/tools/skills/sandbox-runner.ts +16 -3
  197. package/src/tools/terminal/shell.ts +16 -3
  198. package/src/util/logger.ts +11 -1
  199. package/src/util/platform.ts +1 -91
  200. package/src/util/sentry-log-stream.ts +51 -0
  201. package/src/watcher/providers/github.ts +2 -2
  202. package/src/watcher/providers/gmail.ts +1 -1
  203. package/src/watcher/providers/google-calendar.ts +1 -1
  204. package/src/watcher/providers/linear.ts +2 -2
  205. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  206. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  207. package/src/workspace/migrations/registry.ts +2 -0
  208. package/src/cli/commands/oauth/connections.ts +0 -255
  209. package/src/oauth/provider-behaviors.ts +0 -634
@@ -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, baseUrl) are only written on the
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: "integration:google",
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.baseUrl).toBe(
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("omits baseUrl from envelope when not provided", async () => {
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("baseUrl" in parsed.request).toBe(false);
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: "integration:slack",
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 ? { baseUrl: req.baseUrl } : {}),
73
+ ...((req.baseUrl ?? this.baseUrl)
74
+ ? { base_url: req.baseUrl ?? this.baseUrl }
75
+ : {}),
69
76
  },
70
77
  };
71
78