@vellumai/assistant 0.4.48 → 0.4.49
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/ARCHITECTURE.md +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -83,6 +83,20 @@ function extractNonAuthHeaders(
|
|
|
83
83
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Convert URLSearchParams to a query record, collapsing multi-valued keys into arrays.
|
|
88
|
+
*/
|
|
89
|
+
function paramsToQuery(
|
|
90
|
+
params: URLSearchParams,
|
|
91
|
+
): Record<string, string | string[]> {
|
|
92
|
+
const result: Record<string, string | string[]> = {};
|
|
93
|
+
for (const key of new Set(params.keys())) {
|
|
94
|
+
const values = params.getAll(key);
|
|
95
|
+
result[key] = values.length === 1 ? values[0] : values;
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
/**
|
|
87
101
|
* Extract the JSON body from request options for use with OAuthConnection.
|
|
88
102
|
*/
|
|
@@ -102,6 +116,7 @@ async function request<T>(
|
|
|
102
116
|
connection: OAuthConnection,
|
|
103
117
|
path: string,
|
|
104
118
|
options?: GmailRequestOptions,
|
|
119
|
+
query?: Record<string, string | string[]>,
|
|
105
120
|
): Promise<T> {
|
|
106
121
|
const canRetry = options?.retryable ?? isIdempotent(options);
|
|
107
122
|
const method = (options?.method ?? "GET").toUpperCase();
|
|
@@ -112,6 +127,7 @@ async function request<T>(
|
|
|
112
127
|
resp = await connection.request({
|
|
113
128
|
method,
|
|
114
129
|
path,
|
|
130
|
+
query,
|
|
115
131
|
headers: {
|
|
116
132
|
"Content-Type": "application/json",
|
|
117
133
|
...extractNonAuthHeaders(options),
|
|
@@ -171,7 +187,12 @@ export async function listMessages(
|
|
|
171
187
|
if (labelIds) {
|
|
172
188
|
for (const id of labelIds) params.append("labelIds", id);
|
|
173
189
|
}
|
|
174
|
-
return request<GmailMessageListResponse>(
|
|
190
|
+
return request<GmailMessageListResponse>(
|
|
191
|
+
connection,
|
|
192
|
+
"/messages",
|
|
193
|
+
undefined,
|
|
194
|
+
paramsToQuery(params),
|
|
195
|
+
);
|
|
175
196
|
}
|
|
176
197
|
|
|
177
198
|
/** Get a single message by ID. */
|
|
@@ -187,7 +208,12 @@ export async function getMessage(
|
|
|
187
208
|
for (const h of metadataHeaders) params.append("metadataHeaders", h);
|
|
188
209
|
}
|
|
189
210
|
if (fields) params.set("fields", fields);
|
|
190
|
-
return request<GmailMessage>(
|
|
211
|
+
return request<GmailMessage>(
|
|
212
|
+
connection,
|
|
213
|
+
`/messages/${messageId}`,
|
|
214
|
+
undefined,
|
|
215
|
+
paramsToQuery(params),
|
|
216
|
+
);
|
|
191
217
|
}
|
|
192
218
|
|
|
193
219
|
/**
|
|
@@ -332,10 +358,44 @@ async function executeBatchCall(
|
|
|
332
358
|
return connection.withToken(doBatchFetch);
|
|
333
359
|
}
|
|
334
360
|
|
|
361
|
+
/** Max concurrent individual getMessage requests (matches batch concurrency) */
|
|
362
|
+
const INDIVIDUAL_CONCURRENCY = BATCH_CONCURRENCY;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Fetch all messages individually using getMessage (no batch endpoint).
|
|
366
|
+
* Used as a fallback when the batch API is unavailable (e.g. platform connections
|
|
367
|
+
* that cannot expose raw tokens for the multipart batch endpoint).
|
|
368
|
+
*
|
|
369
|
+
* Processes messages in waves of INDIVIDUAL_CONCURRENCY to avoid unbounded
|
|
370
|
+
* parallelism that would trigger 429s on high-volume paths like senderDigest.
|
|
371
|
+
*/
|
|
372
|
+
async function fetchMessagesIndividually(
|
|
373
|
+
connection: OAuthConnection,
|
|
374
|
+
messageIds: string[],
|
|
375
|
+
format: GmailMessageFormat,
|
|
376
|
+
metadataHeaders?: string[],
|
|
377
|
+
fields?: string,
|
|
378
|
+
): Promise<GmailMessage[]> {
|
|
379
|
+
const results: GmailMessage[] = [];
|
|
380
|
+
for (let i = 0; i < messageIds.length; i += INDIVIDUAL_CONCURRENCY) {
|
|
381
|
+
const wave = messageIds.slice(i, i + INDIVIDUAL_CONCURRENCY);
|
|
382
|
+
const waveResults = await Promise.all(
|
|
383
|
+
wave.map((id) =>
|
|
384
|
+
getMessage(connection, id, format, metadataHeaders, fields),
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
results.push(...waveResults);
|
|
388
|
+
}
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
|
|
335
392
|
/**
|
|
336
393
|
* Get multiple messages using Gmail's batch HTTP endpoint.
|
|
337
394
|
* Packs up to 100 sub-requests per HTTP call and runs up to BATCH_CONCURRENCY calls in parallel.
|
|
338
395
|
* Falls back to individual getMessage for any sub-requests that fail within a batch.
|
|
396
|
+
*
|
|
397
|
+
* For connections that do not support raw token access (e.g. platform-managed connections),
|
|
398
|
+
* falls back to fetching each message individually via connection.request().
|
|
339
399
|
*/
|
|
340
400
|
export async function batchGetMessages(
|
|
341
401
|
connection: OAuthConnection,
|
|
@@ -359,6 +419,26 @@ export async function batchGetMessages(
|
|
|
359
419
|
];
|
|
360
420
|
}
|
|
361
421
|
|
|
422
|
+
// Try batch API first; fall back to individual fetches if withToken is unavailable
|
|
423
|
+
// (e.g. platform-managed connections where raw tokens cannot be exposed).
|
|
424
|
+
let useBatch = true;
|
|
425
|
+
try {
|
|
426
|
+
// Probe withToken availability with a no-op call
|
|
427
|
+
await connection.withToken(async (token) => token);
|
|
428
|
+
} catch {
|
|
429
|
+
useBatch = false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!useBatch) {
|
|
433
|
+
return fetchMessagesIndividually(
|
|
434
|
+
connection,
|
|
435
|
+
messageIds,
|
|
436
|
+
format,
|
|
437
|
+
metadataHeaders,
|
|
438
|
+
fields,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
362
442
|
const results = new Array<GmailMessage | undefined>(messageIds.length).fill(
|
|
363
443
|
undefined,
|
|
364
444
|
);
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { OAuthConnection } from "../../../oauth/connection.js";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
const GOOGLE_PEOPLE_BASE_URL = "https://people.googleapis.com/v1";
|
|
8
9
|
import { GmailApiError } from "./client.js";
|
|
9
10
|
import type {
|
|
10
11
|
PeopleConnectionsResponse,
|
|
@@ -16,10 +17,12 @@ const PERSON_FIELDS = "names,emailAddresses,phoneNumbers,organizations";
|
|
|
16
17
|
async function request<T>(
|
|
17
18
|
connection: OAuthConnection,
|
|
18
19
|
path: string,
|
|
20
|
+
query?: Record<string, string | string[]>,
|
|
19
21
|
): Promise<T> {
|
|
20
22
|
const resp = await connection.request({
|
|
21
23
|
method: "GET",
|
|
22
24
|
path,
|
|
25
|
+
query,
|
|
23
26
|
baseUrl: GOOGLE_PEOPLE_BASE_URL,
|
|
24
27
|
});
|
|
25
28
|
|
|
@@ -44,14 +47,15 @@ export async function listContacts(
|
|
|
44
47
|
pageSize = 50,
|
|
45
48
|
pageToken?: string,
|
|
46
49
|
): Promise<PeopleConnectionsResponse> {
|
|
47
|
-
const
|
|
50
|
+
const query: Record<string, string> = {
|
|
48
51
|
personFields: PERSON_FIELDS,
|
|
49
52
|
pageSize: String(pageSize),
|
|
50
|
-
}
|
|
51
|
-
if (pageToken)
|
|
53
|
+
};
|
|
54
|
+
if (pageToken) query.pageToken = pageToken;
|
|
52
55
|
return request<PeopleConnectionsResponse>(
|
|
53
56
|
connection,
|
|
54
|
-
|
|
57
|
+
"/people/me/connections",
|
|
58
|
+
query,
|
|
55
59
|
);
|
|
56
60
|
}
|
|
57
61
|
|
|
@@ -60,12 +64,8 @@ export async function searchContacts(
|
|
|
60
64
|
connection: OAuthConnection,
|
|
61
65
|
query: string,
|
|
62
66
|
): Promise<PeopleSearchResponse> {
|
|
63
|
-
|
|
67
|
+
return request<PeopleSearchResponse>(connection, "/people:searchContacts", {
|
|
64
68
|
query,
|
|
65
69
|
readMask: PERSON_FIELDS,
|
|
66
70
|
});
|
|
67
|
-
return request<PeopleSearchResponse>(
|
|
68
|
-
connection,
|
|
69
|
-
`/people:searchContacts?${params}`,
|
|
70
|
-
);
|
|
71
71
|
}
|
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* with OAuth tokens, Telegram delivery is proxied through the gateway which
|
|
7
7
|
* owns the bot token and handles Telegram API retries.
|
|
8
8
|
*
|
|
9
|
-
* The `
|
|
10
|
-
* because delivery is authenticated via the gateway's bearer
|
|
11
|
-
* a per-user OAuth token.
|
|
9
|
+
* The `connectionOrToken` parameter in MessagingProvider methods is unused
|
|
10
|
+
* for Telegram because delivery is authenticated via the gateway's bearer
|
|
11
|
+
* token, not a per-user OAuth token.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { getGatewayInternalBaseUrl } from "../../../config/env.js";
|
|
15
15
|
import { getOrCreateConversation } from "../../../memory/conversation-key-store.js";
|
|
16
16
|
import * as externalConversationStore from "../../../memory/external-conversation-store.js";
|
|
17
|
+
import type { OAuthConnection } from "../../../oauth/connection.js";
|
|
18
|
+
import { getConnectionByProvider } from "../../../oauth/oauth-store.js";
|
|
17
19
|
import { mintDaemonDeliveryToken } from "../../../runtime/auth/token-service.js";
|
|
18
20
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
19
21
|
import { getSecureKey } from "../../../security/secure-keys.js";
|
|
@@ -31,7 +33,7 @@ import type {
|
|
|
31
33
|
} from "../../provider-types.js";
|
|
32
34
|
import * as telegram from "./client.js";
|
|
33
35
|
|
|
34
|
-
/** Resolve the gateway base URL
|
|
36
|
+
/** Resolve the gateway base URL. */
|
|
35
37
|
function getGatewayUrl(): string {
|
|
36
38
|
return getGatewayInternalBaseUrl();
|
|
37
39
|
}
|
|
@@ -53,23 +55,21 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
53
55
|
capabilities: new Set(["send"]),
|
|
54
56
|
|
|
55
57
|
/**
|
|
56
|
-
* Custom connectivity check
|
|
57
|
-
*
|
|
58
|
-
* stored as credential/telegram/bot_token. This method lets the
|
|
59
|
-
* registry detect that Telegram credentials exist.
|
|
58
|
+
* Custom connectivity check using the oauth_connection record as the
|
|
59
|
+
* single source of truth, consistent with integration-status.ts.
|
|
60
60
|
*
|
|
61
61
|
* Both bot_token and webhook_secret are required — the gateway's
|
|
62
62
|
* /deliver/telegram endpoint rejects requests without the webhook
|
|
63
63
|
* secret, so partial credentials would cause every send to fail.
|
|
64
64
|
*/
|
|
65
65
|
isConnected(): boolean {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
!!getSecureKey(credentialKey("telegram", "webhook_secret"))
|
|
69
|
-
);
|
|
66
|
+
const conn = getConnectionByProvider("telegram");
|
|
67
|
+
return !!(conn && conn.status === "active");
|
|
70
68
|
},
|
|
71
69
|
|
|
72
|
-
async testConnection(
|
|
70
|
+
async testConnection(
|
|
71
|
+
_connectionOrToken: OAuthConnection | string,
|
|
72
|
+
): Promise<ConnectionInfo> {
|
|
73
73
|
const botToken = getBotToken();
|
|
74
74
|
if (!botToken) {
|
|
75
75
|
return {
|
|
@@ -114,7 +114,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
114
114
|
},
|
|
115
115
|
|
|
116
116
|
async sendMessage(
|
|
117
|
-
|
|
117
|
+
_connectionOrToken: OAuthConnection | string,
|
|
118
118
|
conversationId: string,
|
|
119
119
|
text: string,
|
|
120
120
|
_options?: SendOptions,
|
|
@@ -152,7 +152,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
152
152
|
// interact with chats where users have initiated contact or the bot
|
|
153
153
|
// has been added to a group.
|
|
154
154
|
async listConversations(
|
|
155
|
-
|
|
155
|
+
_connectionOrToken: OAuthConnection | string,
|
|
156
156
|
_options?: ListOptions,
|
|
157
157
|
): Promise<Conversation[]> {
|
|
158
158
|
return [];
|
|
@@ -160,7 +160,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
160
160
|
|
|
161
161
|
// Telegram Bot API does not provide message history retrieval.
|
|
162
162
|
async getHistory(
|
|
163
|
-
|
|
163
|
+
_connectionOrToken: OAuthConnection | string,
|
|
164
164
|
_conversationId: string,
|
|
165
165
|
_options?: HistoryOptions,
|
|
166
166
|
): Promise<Message[]> {
|
|
@@ -169,7 +169,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
169
169
|
|
|
170
170
|
// Telegram Bot API does not support message search.
|
|
171
171
|
async search(
|
|
172
|
-
|
|
172
|
+
_connectionOrToken: OAuthConnection | string,
|
|
173
173
|
_query: string,
|
|
174
174
|
_options?: SearchOptions,
|
|
175
175
|
): Promise<SearchResult> {
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
* endpoint. Delivery is proxied through the gateway which owns the Meta Cloud API
|
|
6
6
|
* credentials (phone_number_id + access_token).
|
|
7
7
|
*
|
|
8
|
-
* The `
|
|
9
|
-
* because delivery is authenticated via the gateway's bearer
|
|
10
|
-
* a per-user OAuth token.
|
|
8
|
+
* The `connectionOrToken` parameter in MessagingProvider methods is unused
|
|
9
|
+
* for WhatsApp because delivery is authenticated via the gateway's bearer
|
|
10
|
+
* token, not a per-user OAuth token.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { getGatewayInternalBaseUrl } from "../../../config/env.js";
|
|
14
14
|
import { getOrCreateConversation } from "../../../memory/conversation-key-store.js";
|
|
15
15
|
import * as externalConversationStore from "../../../memory/external-conversation-store.js";
|
|
16
|
+
import type { OAuthConnection } from "../../../oauth/connection.js";
|
|
16
17
|
import { mintDaemonDeliveryToken } from "../../../runtime/auth/token-service.js";
|
|
17
18
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
18
19
|
import { getSecureKey } from "../../../security/secure-keys.js";
|
|
@@ -61,7 +62,9 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
61
62
|
return hasWhatsAppCredentials();
|
|
62
63
|
},
|
|
63
64
|
|
|
64
|
-
async testConnection(
|
|
65
|
+
async testConnection(
|
|
66
|
+
_connectionOrToken: OAuthConnection | string,
|
|
67
|
+
): Promise<ConnectionInfo> {
|
|
65
68
|
if (!hasWhatsAppCredentials()) {
|
|
66
69
|
return {
|
|
67
70
|
connected: false,
|
|
@@ -89,7 +92,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
89
92
|
},
|
|
90
93
|
|
|
91
94
|
async sendMessage(
|
|
92
|
-
|
|
95
|
+
_connectionOrToken: OAuthConnection | string,
|
|
93
96
|
conversationId: string,
|
|
94
97
|
text: string,
|
|
95
98
|
options?: SendOptions,
|
|
@@ -133,7 +136,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
133
136
|
|
|
134
137
|
// WhatsApp does not support listing conversations via this provider.
|
|
135
138
|
async listConversations(
|
|
136
|
-
|
|
139
|
+
_connectionOrToken: OAuthConnection | string,
|
|
137
140
|
_options?: ListOptions,
|
|
138
141
|
): Promise<Conversation[]> {
|
|
139
142
|
return [];
|
|
@@ -141,7 +144,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
141
144
|
|
|
142
145
|
// WhatsApp does not provide message history retrieval via the gateway.
|
|
143
146
|
async getHistory(
|
|
144
|
-
|
|
147
|
+
_connectionOrToken: OAuthConnection | string,
|
|
145
148
|
_conversationId: string,
|
|
146
149
|
_options?: HistoryOptions,
|
|
147
150
|
): Promise<Message[]> {
|
|
@@ -150,7 +153,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
150
153
|
|
|
151
154
|
// WhatsApp does not support message search.
|
|
152
155
|
async search(
|
|
153
|
-
|
|
156
|
+
_connectionOrToken: OAuthConnection | string,
|
|
154
157
|
_query: string,
|
|
155
158
|
_options?: SearchOptions,
|
|
156
159
|
): Promise<SearchResult> {
|
|
@@ -2,21 +2,9 @@
|
|
|
2
2
|
* Messaging provider registry — register/lookup providers by platform ID.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { getConfig } from "../config/loader.js";
|
|
7
|
-
import { credentialKey } from "../security/credential-key.js";
|
|
8
|
-
import { getSecureKey } from "../security/secure-keys.js";
|
|
5
|
+
import { isProviderConnected } from "../oauth/oauth-store.js";
|
|
9
6
|
import type { MessagingProvider } from "./provider.js";
|
|
10
7
|
|
|
11
|
-
/**
|
|
12
|
-
* Per-platform feature flag keys. Platforms not listed here are allowed
|
|
13
|
-
* by default (undeclared keys resolve to `true`).
|
|
14
|
-
*/
|
|
15
|
-
const PLATFORM_FLAG_KEYS: Record<string, string> = {
|
|
16
|
-
gmail: "feature_flags.messaging.gmail.enabled",
|
|
17
|
-
telegram: "feature_flags.messaging.telegram.enabled",
|
|
18
|
-
};
|
|
19
|
-
|
|
20
8
|
const providers = new Map<string, MessagingProvider>();
|
|
21
9
|
|
|
22
10
|
export function registerMessagingProvider(provider: MessagingProvider): void {
|
|
@@ -31,32 +19,14 @@ export function getMessagingProvider(id: string): MessagingProvider {
|
|
|
31
19
|
`Messaging provider "${id}" not found. Available: ${available}`,
|
|
32
20
|
);
|
|
33
21
|
}
|
|
34
|
-
assertPlatformEnabled(id);
|
|
35
22
|
return provider;
|
|
36
23
|
}
|
|
37
24
|
|
|
38
|
-
export function isPlatformEnabled(platformId: string): boolean {
|
|
39
|
-
const flagKey = PLATFORM_FLAG_KEYS[platformId];
|
|
40
|
-
if (!flagKey) return true;
|
|
41
|
-
return isAssistantFeatureFlagEnabled(flagKey, getConfig());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function assertPlatformEnabled(platformId: string): void {
|
|
45
|
-
if (!isPlatformEnabled(platformId)) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`The ${platformId} platform is not enabled. Enable it in Settings > Features.`,
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
25
|
/** Return all registered providers that have stored credentials. */
|
|
53
26
|
export function getConnectedProviders(): MessagingProvider[] {
|
|
54
27
|
return Array.from(providers.values()).filter((p) => {
|
|
55
28
|
if (p.isConnected) return p.isConnected();
|
|
56
|
-
|
|
57
|
-
credentialKey(p.credentialService, "access_token"),
|
|
58
|
-
);
|
|
59
|
-
return token !== undefined;
|
|
29
|
+
return isProviderConnected(p.credentialService);
|
|
60
30
|
});
|
|
61
31
|
}
|
|
62
32
|
|
|
@@ -351,11 +351,6 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
|
|
|
351
351
|
title: "Voice Response",
|
|
352
352
|
body: str(payload.preview, "A voice response is ready"),
|
|
353
353
|
}),
|
|
354
|
-
|
|
355
|
-
"ride_shotgun.invitation": (payload) => ({
|
|
356
|
-
title: "Ride Shotgun",
|
|
357
|
-
body: str(payload.message, "You have been invited to ride shotgun"),
|
|
358
|
-
}),
|
|
359
354
|
};
|
|
360
355
|
|
|
361
356
|
/**
|
|
@@ -37,7 +37,10 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
|
|
|
37
37
|
id: "user.send_notification",
|
|
38
38
|
description: "User-initiated notification via assistant tool",
|
|
39
39
|
},
|
|
40
|
-
{
|
|
40
|
+
{
|
|
41
|
+
id: "schedule.notify",
|
|
42
|
+
description: "Scheduled notification triggered (one-shot or recurring)",
|
|
43
|
+
},
|
|
41
44
|
{ id: "schedule.complete", description: "Scheduled task finished running" },
|
|
42
45
|
{
|
|
43
46
|
id: "guardian.question",
|
|
@@ -89,10 +92,6 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
|
|
|
89
92
|
id: "voice.response_ready",
|
|
90
93
|
description: "Voice response ready for playback",
|
|
91
94
|
},
|
|
92
|
-
{
|
|
93
|
-
id: "ride_shotgun.invitation",
|
|
94
|
-
description: "Invitation to ride shotgun on a session",
|
|
95
|
-
},
|
|
96
95
|
] as const;
|
|
97
96
|
|
|
98
97
|
export type NotificationSourceEventName =
|
|
@@ -63,14 +63,49 @@ mock.module("../security/oauth2.js", () => {
|
|
|
63
63
|
};
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Mock oauth-store — token-manager reads refresh config from SQLite
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/** Mutable per-test map of provider connections for getConnectionByProvider */
|
|
71
|
+
const mockConnections = new Map<
|
|
72
|
+
string,
|
|
73
|
+
{
|
|
74
|
+
id: string;
|
|
75
|
+
providerKey: string;
|
|
76
|
+
oauthAppId: string;
|
|
77
|
+
expiresAt: number | null;
|
|
78
|
+
grantedScopes?: string;
|
|
79
|
+
accountInfo?: string | null;
|
|
80
|
+
}
|
|
81
|
+
>();
|
|
82
|
+
const mockApps = new Map<
|
|
83
|
+
string,
|
|
84
|
+
{ id: string; providerKey: string; clientId: string }
|
|
85
|
+
>();
|
|
86
|
+
const mockProviders = new Map<
|
|
87
|
+
string,
|
|
88
|
+
{
|
|
89
|
+
key: string;
|
|
90
|
+
tokenUrl: string;
|
|
91
|
+
tokenEndpointAuthMethod?: string;
|
|
92
|
+
baseUrl?: string;
|
|
93
|
+
}
|
|
94
|
+
>();
|
|
95
|
+
|
|
96
|
+
mock.module("./oauth-store.js", () => ({
|
|
97
|
+
getConnectionByProvider: (service: string) => mockConnections.get(service),
|
|
98
|
+
getApp: (id: string) => mockApps.get(id),
|
|
99
|
+
getProvider: (key: string) => mockProviders.get(key),
|
|
100
|
+
updateConnection: () => {},
|
|
101
|
+
getMostRecentAppByProvider: () => undefined,
|
|
102
|
+
listConnections: () => [],
|
|
103
|
+
}));
|
|
104
|
+
|
|
66
105
|
// ---------------------------------------------------------------------------
|
|
67
106
|
// Imports (after mocks)
|
|
68
107
|
// ---------------------------------------------------------------------------
|
|
69
108
|
|
|
70
|
-
import {
|
|
71
|
-
_resetMigrationFlag,
|
|
72
|
-
credentialKey,
|
|
73
|
-
} from "../security/credential-key.js";
|
|
74
109
|
import { setSecureKey } from "../security/secure-keys.js";
|
|
75
110
|
import {
|
|
76
111
|
_resetInflightRefreshes,
|
|
@@ -88,7 +123,6 @@ import { resolveOAuthConnection } from "./connection-resolver.js";
|
|
|
88
123
|
// ---------------------------------------------------------------------------
|
|
89
124
|
|
|
90
125
|
const originalFetch = globalThis.fetch;
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
126
|
let mockFetch: ReturnType<typeof mock<any>>;
|
|
93
127
|
|
|
94
128
|
// ---------------------------------------------------------------------------
|
|
@@ -110,7 +144,10 @@ beforeEach(() => {
|
|
|
110
144
|
_resetBackend();
|
|
111
145
|
_resetRefreshBreakers();
|
|
112
146
|
_resetInflightRefreshes();
|
|
113
|
-
|
|
147
|
+
// Clear mock oauth-store maps
|
|
148
|
+
mockConnections.clear();
|
|
149
|
+
mockApps.clear();
|
|
150
|
+
mockProviders.clear();
|
|
114
151
|
|
|
115
152
|
// Default mock fetch returning 200 JSON
|
|
116
153
|
mockFetch = mock(() =>
|
|
@@ -139,16 +176,40 @@ function setupCredential(
|
|
|
139
176
|
service: string,
|
|
140
177
|
opts?: { expiresAt?: number; grantedScopes?: string[] },
|
|
141
178
|
) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
// Seed mock oauth-store maps so token-manager can resolve refresh config
|
|
180
|
+
const appId = `app-${service}`;
|
|
181
|
+
const connId = `conn-${service}`;
|
|
182
|
+
mockProviders.set(service, {
|
|
183
|
+
key: service,
|
|
184
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
185
|
+
// Only well-known providers (gmail) have a baseUrl; custom services don't
|
|
186
|
+
baseUrl:
|
|
187
|
+
service === "integration:gmail"
|
|
188
|
+
? "https://gmail.googleapis.com/gmail/v1/users/me"
|
|
189
|
+
: undefined,
|
|
190
|
+
});
|
|
191
|
+
mockApps.set(appId, {
|
|
192
|
+
id: appId,
|
|
193
|
+
providerKey: service,
|
|
194
|
+
clientId: "test-client-id",
|
|
195
|
+
});
|
|
196
|
+
mockConnections.set(service, {
|
|
197
|
+
id: connId,
|
|
198
|
+
providerKey: service,
|
|
199
|
+
oauthAppId: appId,
|
|
146
200
|
expiresAt: opts?.expiresAt ?? Date.now() + 3600 * 1000,
|
|
147
|
-
grantedScopes: opts?.grantedScopes ?? ["read", "write"],
|
|
148
|
-
|
|
149
|
-
oauth2ClientId: "test-client-id",
|
|
150
|
-
hasRefreshToken: true,
|
|
201
|
+
grantedScopes: JSON.stringify(opts?.grantedScopes ?? ["read", "write"]),
|
|
202
|
+
accountInfo: null,
|
|
151
203
|
});
|
|
204
|
+
// Store access token in oauth-store key format
|
|
205
|
+
setSecureKey(`oauth_connection/${connId}/access_token`, "test-access-token");
|
|
206
|
+
// Store refresh token and client_secret in secure keys (token-manager reads them)
|
|
207
|
+
setSecureKey(
|
|
208
|
+
`oauth_connection/${connId}/refresh_token`,
|
|
209
|
+
"test-refresh-token",
|
|
210
|
+
);
|
|
211
|
+
setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
|
|
212
|
+
upsertCredentialMetadata(service, "access_token", {});
|
|
152
213
|
}
|
|
153
214
|
|
|
154
215
|
function createConnection(service = "integration:gmail"): BYOOAuthConnection {
|
|
@@ -185,10 +246,10 @@ describe("BYOOAuthConnection", () => {
|
|
|
185
246
|
expect(url).toBe(
|
|
186
247
|
"https://gmail.googleapis.com/gmail/v1/users/me/messages",
|
|
187
248
|
);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
249
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
250
|
+
expect(headers.get("Authorization")).toBe("Bearer test-access-token");
|
|
251
|
+
// GET requests have no body, so Content-Type should not be set
|
|
252
|
+
expect(headers.has("Content-Type")).toBe(false);
|
|
192
253
|
expect((init as RequestInit).method).toBe("GET");
|
|
193
254
|
});
|
|
194
255
|
|
|
@@ -237,6 +298,9 @@ describe("BYOOAuthConnection", () => {
|
|
|
237
298
|
JSON.stringify({ raw: "base64-encoded-email" }),
|
|
238
299
|
);
|
|
239
300
|
expect((init as RequestInit).method).toBe("POST");
|
|
301
|
+
// POST requests with a body should include Content-Type
|
|
302
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
303
|
+
expect(headers.get("Content-Type")).toBe("application/json");
|
|
240
304
|
});
|
|
241
305
|
|
|
242
306
|
test("retries once on 401 response", async () => {
|
|
@@ -336,10 +400,9 @@ describe("BYOOAuthConnection", () => {
|
|
|
336
400
|
});
|
|
337
401
|
|
|
338
402
|
const [, init] = mockFetch.mock.calls[0];
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
});
|
|
403
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
404
|
+
expect(headers.get("X-Custom-Header")).toBe("custom-value");
|
|
405
|
+
expect(headers.get("Authorization")).toBe("Bearer test-access-token");
|
|
343
406
|
});
|
|
344
407
|
});
|
|
345
408
|
|
|
@@ -361,9 +424,10 @@ describe("BYOOAuthConnection", () => {
|
|
|
361
424
|
|
|
362
425
|
// The request should use the refreshed token
|
|
363
426
|
const [, init] = mockFetch.mock.calls[0];
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
427
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
428
|
+
expect(headers.get("Authorization")).toBe(
|
|
429
|
+
"Bearer refreshed-access-token",
|
|
430
|
+
);
|
|
367
431
|
});
|
|
368
432
|
});
|
|
369
433
|
|
|
@@ -433,4 +497,41 @@ describe("resolveOAuthConnection", () => {
|
|
|
433
497
|
/No base URL configured for "integration:custom-service"/,
|
|
434
498
|
);
|
|
435
499
|
});
|
|
500
|
+
|
|
501
|
+
test("resolves base URL via app's canonical providerKey for custom credential_service", () => {
|
|
502
|
+
// Set up a well-known provider with a baseUrl
|
|
503
|
+
mockProviders.set("github", {
|
|
504
|
+
key: "github",
|
|
505
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
506
|
+
baseUrl: "https://api.github.com",
|
|
507
|
+
});
|
|
508
|
+
// The custom credential service has no provider entry of its own
|
|
509
|
+
// (getProvider("integration:github-work") returns undefined)
|
|
510
|
+
|
|
511
|
+
// App points to the canonical "github" provider
|
|
512
|
+
const appId = "app-github-work";
|
|
513
|
+
mockApps.set(appId, {
|
|
514
|
+
id: appId,
|
|
515
|
+
providerKey: "github",
|
|
516
|
+
clientId: "test-client-id",
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Connection uses the custom credential service as its providerKey
|
|
520
|
+
const connId = "conn-github-work";
|
|
521
|
+
mockConnections.set("integration:github-work", {
|
|
522
|
+
id: connId,
|
|
523
|
+
providerKey: "integration:github-work",
|
|
524
|
+
oauthAppId: appId,
|
|
525
|
+
expiresAt: Date.now() + 3600 * 1000,
|
|
526
|
+
grantedScopes: JSON.stringify(["repo"]),
|
|
527
|
+
accountInfo: null,
|
|
528
|
+
});
|
|
529
|
+
setSecureKey(`oauth_connection/${connId}/access_token`, "ghp-test-token");
|
|
530
|
+
|
|
531
|
+
const conn = resolveOAuthConnection("integration:github-work");
|
|
532
|
+
|
|
533
|
+
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
534
|
+
expect(conn.providerKey).toBe("integration:github-work");
|
|
535
|
+
expect(conn.grantedScopes).toEqual(["repo"]);
|
|
536
|
+
});
|
|
436
537
|
});
|