@vellumai/assistant 0.4.45 → 0.4.48
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 +6 -6
- package/docs/architecture/memory.md +1 -1
- package/docs/architecture/scheduling.md +2 -3
- package/docs/architecture/security.md +5 -5
- package/docs/trusted-contact-access.md +5 -6
- package/package.json +4 -1
- package/src/__tests__/avatar-e2e.test.ts +18 -219
- package/src/__tests__/avatar-generator.test.ts +5 -57
- package/src/__tests__/browser-fill-credential.test.ts +5 -2
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
- package/src/__tests__/channel-readiness-routes.test.ts +20 -19
- package/src/__tests__/cli.test.ts +23 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
- package/src/__tests__/credential-broker-server-use.test.ts +22 -21
- package/src/__tests__/credential-broker.test.ts +2 -1
- package/src/__tests__/credential-metadata-store.test.ts +240 -18
- package/src/__tests__/credential-resolve.test.ts +5 -4
- package/src/__tests__/credential-security-e2e.test.ts +8 -8
- package/src/__tests__/credential-security-invariants.test.ts +104 -7
- package/src/__tests__/credential-vault-unit.test.ts +22 -20
- package/src/__tests__/credential-vault.test.ts +284 -12
- package/src/__tests__/credentials-cli.test.ts +11 -6
- package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
- package/src/__tests__/gemini-image-service.test.ts +75 -45
- package/src/__tests__/gemini-provider.test.ts +9 -6
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
- package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
- package/src/__tests__/guardian-grant-minting.test.ts +35 -0
- package/src/__tests__/integration-status.test.ts +53 -21
- package/src/__tests__/managed-proxy-context.test.ts +5 -3
- package/src/__tests__/media-generate-image.test.ts +63 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
- package/src/__tests__/messaging-send-tool.test.ts +4 -6
- package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schema-transforms.test.ts +226 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
- package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +5 -3
- package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +0 -9
- package/src/__tests__/slack-channel-config.test.ts +9 -8
- package/src/__tests__/slack-share-routes.test.ts +11 -6
- package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
- package/src/__tests__/twilio-config.test.ts +2 -1
- package/src/__tests__/twilio-provider.test.ts +4 -2
- package/src/__tests__/twilio-routes.test.ts +5 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/AGENTS.md +1 -1
- package/src/calls/call-domain.ts +7 -4
- package/src/calls/twilio-config.ts +2 -1
- package/src/calls/twilio-provider.ts +2 -1
- package/src/calls/twilio-rest.ts +2 -2
- package/src/cli/commands/browser-relay.ts +40 -15
- package/src/cli/commands/credentials.ts +9 -8
- package/src/cli/commands/oauth.ts +1 -1
- package/src/cli.ts +3 -2
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
- package/src/config/bundled-skills/gmail/SKILL.md +4 -4
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
- package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +70 -29
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
- package/src/config/bundled-skills/messaging/SKILL.md +6 -6
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
- package/src/config/bundled-skills/messaging/tools/shared.ts +11 -11
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +5 -5
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
- package/src/config/loader.ts +6 -0
- package/src/daemon/computer-use-session.ts +7 -1
- package/src/daemon/guardian-action-generators.ts +4 -5
- package/src/daemon/handlers/config-slack-channel.ts +37 -20
- package/src/daemon/handlers/config-telegram.ts +33 -20
- package/src/daemon/lifecycle.ts +9 -1
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/ride-shotgun-handler.ts +3 -1
- package/src/daemon/session-messaging.ts +3 -1
- package/src/daemon/session-tool-setup.ts +18 -2
- package/src/daemon/session.ts +1 -1
- package/src/email/providers/index.ts +2 -1
- package/src/instrument.ts +15 -1
- package/src/media/app-icon-generator.ts +30 -4
- package/src/media/avatar-router.ts +28 -62
- package/src/media/gemini-image-service.ts +28 -2
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/guardian-action-store.ts +1 -1
- package/src/memory/schema/guardian.ts +1 -1
- package/src/messaging/provider.ts +16 -10
- package/src/messaging/providers/gmail/adapter.ts +40 -23
- package/src/messaging/providers/gmail/client.ts +203 -122
- package/src/messaging/providers/gmail/people-client.ts +26 -18
- package/src/messaging/providers/slack/adapter.ts +29 -19
- package/src/messaging/providers/slack/client.ts +265 -78
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -4
- package/src/messaging/providers/whatsapp/adapter.ts +6 -3
- package/src/messaging/registry.ts +2 -1
- package/src/oauth/byo-connection.test.ts +436 -0
- package/src/oauth/byo-connection.ts +112 -0
- package/src/oauth/connect-orchestrator.ts +27 -0
- package/src/oauth/connection-resolver.ts +34 -0
- package/src/oauth/connection.ts +38 -0
- package/src/oauth/platform-connection.test.ts +163 -0
- package/src/oauth/platform-connection.ts +110 -0
- package/src/oauth/provider-base-urls.ts +21 -0
- package/src/oauth/provider-profiles.ts +1 -1
- package/src/oauth/token-persistence.ts +20 -20
- package/src/permissions/checker.ts +6 -1
- package/src/prompts/system-prompt.ts +52 -15
- package/src/prompts/templates/BOOTSTRAP.md +1 -1
- package/src/providers/gemini/client.ts +15 -6
- package/src/providers/managed-proxy/constants.ts +2 -2
- package/src/providers/managed-proxy/context.ts +5 -1
- package/src/providers/ratelimit.ts +17 -0
- package/src/providers/registry.ts +2 -2
- package/src/runtime/AGENTS.md +18 -1
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +2 -1
- package/src/runtime/channel-readiness-service.ts +168 -195
- package/src/runtime/channel-readiness-types.ts +4 -0
- package/src/runtime/guardian-action-conversation-turn.ts +1 -3
- package/src/runtime/guardian-action-followup-executor.ts +1 -2
- package/src/runtime/guardian-action-message-composer.ts +3 -23
- package/src/runtime/http-server.ts +9 -4
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/middleware/rate-limiter.ts +74 -20
- package/src/runtime/middleware/twilio-validation.ts +1 -3
- package/src/runtime/routes/channel-readiness-routes.ts +2 -0
- package/src/runtime/routes/diagnostics-routes.ts +11 -9
- package/src/runtime/routes/guardian-approval-interception.ts +20 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +71 -25
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -5
- package/src/runtime/routes/integrations/slack/share.ts +3 -2
- package/src/runtime/routes/integrations/twilio.ts +6 -5
- package/src/runtime/routes/secret-routes.ts +3 -2
- package/src/runtime/routes/settings-routes.ts +75 -17
- package/src/runtime/telegram-streaming-delivery.test.ts +132 -0
- package/src/runtime/telegram-streaming-delivery.ts +11 -1
- package/src/schedule/integration-status.ts +5 -4
- package/src/security/credential-key.ts +170 -0
- package/src/security/token-manager.ts +36 -7
- package/src/tools/apps/definitions.ts +0 -5
- package/src/tools/assets/materialize.ts +0 -5
- package/src/tools/assets/search.ts +0 -5
- package/src/tools/browser/headless-browser.ts +1 -67
- package/src/tools/claude-code/claude-code.ts +0 -5
- package/src/tools/computer-use/request-computer-control.ts +0 -5
- package/src/tools/credentials/broker.ts +6 -4
- package/src/tools/credentials/metadata-store.ts +72 -20
- package/src/tools/credentials/resolve.ts +2 -1
- package/src/tools/credentials/vault.ts +77 -16
- package/src/tools/filesystem/edit.ts +1 -6
- package/src/tools/filesystem/read.ts +0 -5
- package/src/tools/filesystem/write.ts +1 -6
- package/src/tools/host-filesystem/edit.ts +1 -6
- package/src/tools/host-filesystem/read.ts +1 -6
- package/src/tools/host-filesystem/write.ts +1 -6
- package/src/tools/mcp/mcp-tool-factory.ts +18 -1
- package/src/tools/memory/definitions.ts +0 -5
- package/src/tools/network/web-fetch.ts +0 -5
- package/src/tools/network/web-search.ts +0 -5
- package/src/tools/schema-transforms.ts +99 -0
- package/src/tools/skills/load.ts +0 -5
- package/src/tools/swarm/delegate.ts +0 -5
- package/src/tools/system/avatar-generator.ts +3 -44
- package/src/tools/ui-surface/definitions.ts +0 -15
- package/src/tools/watch/screen-watch.ts +0 -5
- package/src/version.ts +10 -0
- package/src/watcher/providers/github.ts +51 -52
- package/src/watcher/providers/gmail.ts +88 -80
- package/src/watcher/providers/google-calendar.ts +93 -86
- package/src/watcher/providers/linear.ts +87 -93
- package/src/__tests__/avatar-router.test.ts +0 -149
- package/src/__tests__/managed-avatar-client.test.ts +0 -337
- package/src/config/bundled-skills/doordash/SKILL.md +0 -170
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +0 -205
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -74
- package/src/config/bundled-skills/doordash/doordash-cli.ts +0 -1081
- package/src/config/bundled-skills/doordash/doordash-entry.ts +0 -22
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +0 -787
- package/src/config/bundled-skills/doordash/lib/client.ts +0 -1069
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +0 -85
- package/src/config/bundled-skills/doordash/lib/queries.ts +0 -28
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +0 -94
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +0 -203
- package/src/config/bundled-skills/doordash/lib/session.ts +0 -96
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +0 -61
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +0 -380
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +0 -55
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +0 -43
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +0 -49
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +0 -6
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +0 -246
- package/src/config/bundled-skills/doordash/lib/types.ts +0 -367
- package/src/media/avatar-types.ts +0 -53
- package/src/media/managed-avatar-client.ts +0 -225
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeAll,
|
|
9
|
+
beforeEach,
|
|
10
|
+
describe,
|
|
11
|
+
expect,
|
|
12
|
+
mock,
|
|
13
|
+
test,
|
|
14
|
+
} from "bun:test";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Mock logger
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
mock.module("../util/logger.js", () => ({
|
|
21
|
+
getLogger: () =>
|
|
22
|
+
new Proxy({} as Record<string, unknown>, {
|
|
23
|
+
get: () => () => {},
|
|
24
|
+
}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Use encrypted backend with a temp store path
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
import { _setStorePath } from "../security/encrypted-store.js";
|
|
32
|
+
import { _resetBackend } from "../security/secure-keys.js";
|
|
33
|
+
|
|
34
|
+
const TEST_DIR = join(
|
|
35
|
+
tmpdir(),
|
|
36
|
+
`vellum-byo-conn-test-${randomBytes(4).toString("hex")}`,
|
|
37
|
+
);
|
|
38
|
+
const STORE_PATH = join(TEST_DIR, "keys.enc");
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Mock OAuth2 token refresh
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
let mockRefreshOAuth2Token: ReturnType<
|
|
45
|
+
typeof mock<
|
|
46
|
+
() => Promise<{
|
|
47
|
+
accessToken: string;
|
|
48
|
+
expiresIn: number;
|
|
49
|
+
refreshToken?: string;
|
|
50
|
+
}>
|
|
51
|
+
>
|
|
52
|
+
>;
|
|
53
|
+
|
|
54
|
+
mock.module("../security/oauth2.js", () => {
|
|
55
|
+
mockRefreshOAuth2Token = mock(() =>
|
|
56
|
+
Promise.resolve({
|
|
57
|
+
accessToken: "refreshed-access-token",
|
|
58
|
+
expiresIn: 3600,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
refreshOAuth2Token: mockRefreshOAuth2Token,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Imports (after mocks)
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
import {
|
|
71
|
+
_resetMigrationFlag,
|
|
72
|
+
credentialKey,
|
|
73
|
+
} from "../security/credential-key.js";
|
|
74
|
+
import { setSecureKey } from "../security/secure-keys.js";
|
|
75
|
+
import {
|
|
76
|
+
_resetInflightRefreshes,
|
|
77
|
+
_resetRefreshBreakers,
|
|
78
|
+
} from "../security/token-manager.js";
|
|
79
|
+
import {
|
|
80
|
+
_setMetadataPath,
|
|
81
|
+
upsertCredentialMetadata,
|
|
82
|
+
} from "../tools/credentials/metadata-store.js";
|
|
83
|
+
import { BYOOAuthConnection } from "./byo-connection.js";
|
|
84
|
+
import { resolveOAuthConnection } from "./connection-resolver.js";
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Mock fetch
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
const originalFetch = globalThis.fetch;
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
let mockFetch: ReturnType<typeof mock<any>>;
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Setup / teardown
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
beforeAll(() => {
|
|
99
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterAll(() => {
|
|
103
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
104
|
+
globalThis.fetch = originalFetch;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
_setStorePath(STORE_PATH);
|
|
109
|
+
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
110
|
+
_resetBackend();
|
|
111
|
+
_resetRefreshBreakers();
|
|
112
|
+
_resetInflightRefreshes();
|
|
113
|
+
_resetMigrationFlag();
|
|
114
|
+
|
|
115
|
+
// Default mock fetch returning 200 JSON
|
|
116
|
+
mockFetch = mock(() =>
|
|
117
|
+
Promise.resolve(
|
|
118
|
+
new Response(JSON.stringify({ ok: true }), {
|
|
119
|
+
status: 200,
|
|
120
|
+
headers: { "content-type": "application/json" },
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
globalThis.fetch = originalFetch;
|
|
129
|
+
// Clean up store for next test
|
|
130
|
+
try {
|
|
131
|
+
rmSync(STORE_PATH, { force: true });
|
|
132
|
+
rmSync(join(TEST_DIR, "metadata.json"), { force: true });
|
|
133
|
+
} catch {
|
|
134
|
+
// ignore
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function setupCredential(
|
|
139
|
+
service: string,
|
|
140
|
+
opts?: { expiresAt?: number; grantedScopes?: string[] },
|
|
141
|
+
) {
|
|
142
|
+
setSecureKey(credentialKey(service, "access_token"), "test-access-token");
|
|
143
|
+
setSecureKey(credentialKey(service, "refresh_token"), "test-refresh-token");
|
|
144
|
+
setSecureKey(credentialKey(service, "client_secret"), "test-client-secret");
|
|
145
|
+
upsertCredentialMetadata(service, "access_token", {
|
|
146
|
+
expiresAt: opts?.expiresAt ?? Date.now() + 3600 * 1000,
|
|
147
|
+
grantedScopes: opts?.grantedScopes ?? ["read", "write"],
|
|
148
|
+
oauth2TokenUrl: "https://oauth2.googleapis.com/token",
|
|
149
|
+
oauth2ClientId: "test-client-id",
|
|
150
|
+
hasRefreshToken: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createConnection(service = "integration:gmail"): BYOOAuthConnection {
|
|
155
|
+
return new BYOOAuthConnection({
|
|
156
|
+
id: "test-cred-id",
|
|
157
|
+
providerKey: service,
|
|
158
|
+
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
159
|
+
accountInfo: null,
|
|
160
|
+
grantedScopes: ["read", "write"],
|
|
161
|
+
credentialService: service,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Tests
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
describe("BYOOAuthConnection", () => {
|
|
170
|
+
describe("request()", () => {
|
|
171
|
+
test("makes authenticated request with Bearer token", async () => {
|
|
172
|
+
setupCredential("integration:gmail");
|
|
173
|
+
const conn = createConnection();
|
|
174
|
+
|
|
175
|
+
const result = await conn.request({
|
|
176
|
+
method: "GET",
|
|
177
|
+
path: "/messages",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(result.status).toBe(200);
|
|
181
|
+
expect(result.body).toEqual({ ok: true });
|
|
182
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
183
|
+
|
|
184
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
185
|
+
expect(url).toBe(
|
|
186
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages",
|
|
187
|
+
);
|
|
188
|
+
expect((init as RequestInit).headers).toMatchObject({
|
|
189
|
+
Authorization: "Bearer test-access-token",
|
|
190
|
+
"Content-Type": "application/json",
|
|
191
|
+
});
|
|
192
|
+
expect((init as RequestInit).method).toBe("GET");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("appends query parameters", async () => {
|
|
196
|
+
setupCredential("integration:gmail");
|
|
197
|
+
const conn = createConnection();
|
|
198
|
+
|
|
199
|
+
await conn.request({
|
|
200
|
+
method: "GET",
|
|
201
|
+
path: "/messages",
|
|
202
|
+
query: { maxResults: "10", labelIds: "INBOX" },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const [url] = mockFetch.mock.calls[0];
|
|
206
|
+
const parsed = new URL(url as string);
|
|
207
|
+
expect(parsed.searchParams.get("maxResults")).toBe("10");
|
|
208
|
+
expect(parsed.searchParams.get("labelIds")).toBe("INBOX");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("uses per-request baseUrl override", async () => {
|
|
212
|
+
setupCredential("integration:gmail");
|
|
213
|
+
const conn = createConnection();
|
|
214
|
+
|
|
215
|
+
await conn.request({
|
|
216
|
+
method: "GET",
|
|
217
|
+
path: "/calendars",
|
|
218
|
+
baseUrl: "https://www.googleapis.com/calendar/v3",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const [url] = mockFetch.mock.calls[0];
|
|
222
|
+
expect(url).toBe("https://www.googleapis.com/calendar/v3/calendars");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("sends JSON body for POST requests", async () => {
|
|
226
|
+
setupCredential("integration:gmail");
|
|
227
|
+
const conn = createConnection();
|
|
228
|
+
|
|
229
|
+
await conn.request({
|
|
230
|
+
method: "POST",
|
|
231
|
+
path: "/messages/send",
|
|
232
|
+
body: { raw: "base64-encoded-email" },
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
236
|
+
expect((init as RequestInit).body).toBe(
|
|
237
|
+
JSON.stringify({ raw: "base64-encoded-email" }),
|
|
238
|
+
);
|
|
239
|
+
expect((init as RequestInit).method).toBe("POST");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("retries once on 401 response", async () => {
|
|
243
|
+
setupCredential("integration:gmail");
|
|
244
|
+
const conn = createConnection();
|
|
245
|
+
|
|
246
|
+
// First call returns 401, second returns 200
|
|
247
|
+
let callCount = 0;
|
|
248
|
+
globalThis.fetch = mock(() => {
|
|
249
|
+
callCount++;
|
|
250
|
+
if (callCount === 1) {
|
|
251
|
+
return Promise.resolve(new Response("Unauthorized", { status: 401 }));
|
|
252
|
+
}
|
|
253
|
+
return Promise.resolve(
|
|
254
|
+
new Response(JSON.stringify({ ok: true }), { status: 200 }),
|
|
255
|
+
);
|
|
256
|
+
}) as unknown as typeof fetch;
|
|
257
|
+
|
|
258
|
+
const result = await conn.request({
|
|
259
|
+
method: "GET",
|
|
260
|
+
path: "/messages",
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(result.status).toBe(200);
|
|
264
|
+
expect(result.body).toEqual({ ok: true });
|
|
265
|
+
expect(callCount).toBe(2);
|
|
266
|
+
// Verify refresh was called
|
|
267
|
+
expect(mockRefreshOAuth2Token).toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("handles empty response body", async () => {
|
|
271
|
+
setupCredential("integration:gmail");
|
|
272
|
+
const conn = createConnection();
|
|
273
|
+
|
|
274
|
+
globalThis.fetch = mock(() =>
|
|
275
|
+
Promise.resolve(new Response("", { status: 204 })),
|
|
276
|
+
) as unknown as typeof fetch;
|
|
277
|
+
|
|
278
|
+
const result = await conn.request({
|
|
279
|
+
method: "DELETE",
|
|
280
|
+
path: "/messages/123",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(result.status).toBe(204);
|
|
284
|
+
expect(result.body).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("handles non-JSON response body", async () => {
|
|
288
|
+
setupCredential("integration:gmail");
|
|
289
|
+
const conn = createConnection();
|
|
290
|
+
|
|
291
|
+
globalThis.fetch = mock(() =>
|
|
292
|
+
Promise.resolve(new Response("plain text response", { status: 200 })),
|
|
293
|
+
) as unknown as typeof fetch;
|
|
294
|
+
|
|
295
|
+
const result = await conn.request({
|
|
296
|
+
method: "GET",
|
|
297
|
+
path: "/raw",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result.status).toBe(200);
|
|
301
|
+
expect(result.body).toBe("plain text response");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("returns response headers", async () => {
|
|
305
|
+
setupCredential("integration:gmail");
|
|
306
|
+
const conn = createConnection();
|
|
307
|
+
|
|
308
|
+
globalThis.fetch = mock(() =>
|
|
309
|
+
Promise.resolve(
|
|
310
|
+
new Response(JSON.stringify({}), {
|
|
311
|
+
status: 200,
|
|
312
|
+
headers: {
|
|
313
|
+
"x-ratelimit-remaining": "99",
|
|
314
|
+
"content-type": "application/json",
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
),
|
|
318
|
+
) as unknown as typeof fetch;
|
|
319
|
+
|
|
320
|
+
const result = await conn.request({
|
|
321
|
+
method: "GET",
|
|
322
|
+
path: "/messages",
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(result.headers["x-ratelimit-remaining"]).toBe("99");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("includes custom request headers", async () => {
|
|
329
|
+
setupCredential("integration:gmail");
|
|
330
|
+
const conn = createConnection();
|
|
331
|
+
|
|
332
|
+
await conn.request({
|
|
333
|
+
method: "GET",
|
|
334
|
+
path: "/messages",
|
|
335
|
+
headers: { "X-Custom-Header": "custom-value" },
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
339
|
+
expect((init as RequestInit).headers).toMatchObject({
|
|
340
|
+
"X-Custom-Header": "custom-value",
|
|
341
|
+
Authorization: "Bearer test-access-token",
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe("proactive token refresh", () => {
|
|
347
|
+
test("refreshes token when near expiry (within 5-minute buffer)", async () => {
|
|
348
|
+
// Set token to expire in 2 minutes (within 5-min buffer)
|
|
349
|
+
setupCredential("integration:gmail", {
|
|
350
|
+
expiresAt: Date.now() + 2 * 60 * 1000,
|
|
351
|
+
});
|
|
352
|
+
const conn = createConnection();
|
|
353
|
+
|
|
354
|
+
await conn.request({
|
|
355
|
+
method: "GET",
|
|
356
|
+
path: "/messages",
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Token should have been refreshed proactively
|
|
360
|
+
expect(mockRefreshOAuth2Token).toHaveBeenCalled();
|
|
361
|
+
|
|
362
|
+
// The request should use the refreshed token
|
|
363
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
364
|
+
expect((init as RequestInit).headers).toMatchObject({
|
|
365
|
+
Authorization: "Bearer refreshed-access-token",
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe("withToken()", () => {
|
|
371
|
+
test("provides valid token to callback", async () => {
|
|
372
|
+
setupCredential("integration:gmail");
|
|
373
|
+
const conn = createConnection();
|
|
374
|
+
|
|
375
|
+
const result = await conn.withToken(async (token) => {
|
|
376
|
+
return `got-${token}`;
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(result).toBe("got-test-access-token");
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("retries callback on 401 error", async () => {
|
|
383
|
+
setupCredential("integration:gmail");
|
|
384
|
+
const conn = createConnection();
|
|
385
|
+
|
|
386
|
+
let callCount = 0;
|
|
387
|
+
const result = await conn.withToken(async (token) => {
|
|
388
|
+
callCount++;
|
|
389
|
+
if (callCount === 1) {
|
|
390
|
+
const err = new Error("Unauthorized");
|
|
391
|
+
(err as Error & { status: number }).status = 401;
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
return `got-${token}`;
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(callCount).toBe(2);
|
|
398
|
+
expect(result).toBe("got-refreshed-access-token");
|
|
399
|
+
expect(mockRefreshOAuth2Token).toHaveBeenCalled();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe("missing credential", () => {
|
|
404
|
+
test("throws when no access token exists", async () => {
|
|
405
|
+
const conn = createConnection();
|
|
406
|
+
|
|
407
|
+
await expect(
|
|
408
|
+
conn.request({ method: "GET", path: "/messages" }),
|
|
409
|
+
).rejects.toThrow(/No access token found/);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("resolveOAuthConnection", () => {
|
|
415
|
+
test("returns a BYOOAuthConnection for valid credential", () => {
|
|
416
|
+
setupCredential("integration:gmail");
|
|
417
|
+
const conn = resolveOAuthConnection("integration:gmail");
|
|
418
|
+
|
|
419
|
+
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
420
|
+
expect(conn.providerKey).toBe("integration:gmail");
|
|
421
|
+
expect(conn.grantedScopes).toEqual(["read", "write"]);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("throws when no credential metadata exists", () => {
|
|
425
|
+
expect(() => resolveOAuthConnection("integration:unknown")).toThrow(
|
|
426
|
+
/No credential found for "integration:unknown"/,
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("throws when no base URL configured", () => {
|
|
431
|
+
setupCredential("integration:custom-service");
|
|
432
|
+
expect(() => resolveOAuthConnection("integration:custom-service")).toThrow(
|
|
433
|
+
/No base URL configured for "integration:custom-service"/,
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYO (Bring-Your-Own) OAuth connection implementation.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing `withValidToken` token management infrastructure to
|
|
5
|
+
* provide the OAuthConnection interface. Delegates all token resolution,
|
|
6
|
+
* proactive refresh, circuit breaker, and retry-on-401 logic to
|
|
7
|
+
* `withValidToken` from `token-manager.ts`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { withValidToken } from "../security/token-manager.js";
|
|
11
|
+
import { getLogger } from "../util/logger.js";
|
|
12
|
+
import type {
|
|
13
|
+
OAuthConnection,
|
|
14
|
+
OAuthConnectionRequest,
|
|
15
|
+
OAuthConnectionResponse,
|
|
16
|
+
} from "./connection.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("byo-oauth-connection");
|
|
19
|
+
|
|
20
|
+
/** Default per-request timeout to prevent hung requests from blocking indefinitely. */
|
|
21
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
22
|
+
|
|
23
|
+
export interface BYOOAuthConnectionOptions {
|
|
24
|
+
id: string;
|
|
25
|
+
providerKey: string;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
accountInfo: string | null;
|
|
28
|
+
grantedScopes: string[];
|
|
29
|
+
credentialService: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class BYOOAuthConnection implements OAuthConnection {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly providerKey: string;
|
|
35
|
+
readonly accountInfo: string | null;
|
|
36
|
+
readonly grantedScopes: string[];
|
|
37
|
+
|
|
38
|
+
private readonly baseUrl: string;
|
|
39
|
+
private readonly credentialService: string;
|
|
40
|
+
|
|
41
|
+
constructor(opts: BYOOAuthConnectionOptions) {
|
|
42
|
+
this.id = opts.id;
|
|
43
|
+
this.providerKey = opts.providerKey;
|
|
44
|
+
this.baseUrl = opts.baseUrl;
|
|
45
|
+
this.accountInfo = opts.accountInfo;
|
|
46
|
+
this.grantedScopes = opts.grantedScopes;
|
|
47
|
+
this.credentialService = opts.credentialService;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
51
|
+
return withValidToken(this.credentialService, async (token) => {
|
|
52
|
+
const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
|
|
53
|
+
let fullUrl = `${effectiveBaseUrl}${req.path}`;
|
|
54
|
+
|
|
55
|
+
if (req.query && Object.keys(req.query).length > 0) {
|
|
56
|
+
const params = new URLSearchParams(req.query);
|
|
57
|
+
fullUrl += `?${params.toString()}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.debug(
|
|
61
|
+
{ method: req.method, url: fullUrl, provider: this.providerKey },
|
|
62
|
+
"Making authenticated request",
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const resp = await fetch(fullUrl, {
|
|
66
|
+
method: req.method,
|
|
67
|
+
headers: {
|
|
68
|
+
...(req.body ? { "Content-Type": "application/json" } : {}),
|
|
69
|
+
...req.headers,
|
|
70
|
+
Authorization: `Bearer ${token}`,
|
|
71
|
+
},
|
|
72
|
+
body: req.body ? JSON.stringify(req.body) : undefined,
|
|
73
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (resp.status === 401) {
|
|
77
|
+
// Throw with a status property so withValidToken detects the 401
|
|
78
|
+
// and triggers its refresh-and-retry logic.
|
|
79
|
+
const err = new Error(`HTTP 401 from ${this.providerKey}`);
|
|
80
|
+
(err as Error & { status: number }).status = 401;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return buildResponse(resp);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async withToken<T>(fn: (token: string) => Promise<T>): Promise<T> {
|
|
89
|
+
return withValidToken(this.credentialService, fn);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function buildResponse(resp: Response): Promise<OAuthConnectionResponse> {
|
|
94
|
+
const headers: Record<string, string> = {};
|
|
95
|
+
resp.headers.forEach((value, key) => {
|
|
96
|
+
headers[key] = value;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
let body: unknown;
|
|
100
|
+
const text = await resp.text().catch(() => "");
|
|
101
|
+
if (text) {
|
|
102
|
+
try {
|
|
103
|
+
body = JSON.parse(text);
|
|
104
|
+
} catch {
|
|
105
|
+
body = text;
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
body = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { status: resp.status, headers, body };
|
|
112
|
+
}
|
|
@@ -52,6 +52,18 @@ export interface OAuthConnectOptions {
|
|
|
52
52
|
/** Tools allowed to use the resulting credential. */
|
|
53
53
|
allowedTools?: string[];
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Called when the deferred (non-interactive) flow completes — either
|
|
57
|
+
* successfully after tokens are stored, or on failure. Lets callers
|
|
58
|
+
* surface the outcome via SSE events, logs, etc.
|
|
59
|
+
*/
|
|
60
|
+
onDeferredComplete?: (result: {
|
|
61
|
+
success: boolean;
|
|
62
|
+
service: string;
|
|
63
|
+
accountInfo?: string;
|
|
64
|
+
error?: string;
|
|
65
|
+
}) => void;
|
|
66
|
+
|
|
55
67
|
// Optional overrides — when provided, these take precedence over the
|
|
56
68
|
// provider profile. This lets callers connect custom / unknown providers.
|
|
57
69
|
authUrl?: string;
|
|
@@ -214,11 +226,21 @@ export async function orchestrateOAuthConnect(
|
|
|
214
226
|
},
|
|
215
227
|
"Deferred OAuth2 flow completed — tokens stored",
|
|
216
228
|
);
|
|
229
|
+
options.onDeferredComplete?.({
|
|
230
|
+
success: true,
|
|
231
|
+
service: resolvedService,
|
|
232
|
+
accountInfo: stored.accountInfo ?? accountInfo,
|
|
233
|
+
});
|
|
217
234
|
} catch (err) {
|
|
218
235
|
log.error(
|
|
219
236
|
{ err, service: resolvedService },
|
|
220
237
|
"Failed to store tokens from deferred OAuth2 flow",
|
|
221
238
|
);
|
|
239
|
+
options.onDeferredComplete?.({
|
|
240
|
+
success: false,
|
|
241
|
+
service: resolvedService,
|
|
242
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
243
|
+
});
|
|
222
244
|
}
|
|
223
245
|
})
|
|
224
246
|
.catch((err) => {
|
|
@@ -226,6 +248,11 @@ export async function orchestrateOAuthConnect(
|
|
|
226
248
|
{ err, service: resolvedService },
|
|
227
249
|
"Deferred OAuth2 flow failed",
|
|
228
250
|
);
|
|
251
|
+
options.onDeferredComplete?.({
|
|
252
|
+
success: false,
|
|
253
|
+
service: resolvedService,
|
|
254
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
255
|
+
});
|
|
229
256
|
});
|
|
230
257
|
|
|
231
258
|
return {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getCredentialMetadata } from "../tools/credentials/metadata-store.js";
|
|
2
|
+
import { BYOOAuthConnection } from "./byo-connection.js";
|
|
3
|
+
import type { OAuthConnection } from "./connection.js";
|
|
4
|
+
import { getProviderBaseUrl } from "./provider-base-urls.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve an OAuthConnection for a given credential service.
|
|
8
|
+
* Currently always creates a BYO connection from existing credential store.
|
|
9
|
+
* Will be extended to create Platform connections when storage model supports it.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveOAuthConnection(
|
|
12
|
+
credentialService: string,
|
|
13
|
+
): OAuthConnection {
|
|
14
|
+
const meta = getCredentialMetadata(credentialService, "access_token");
|
|
15
|
+
if (!meta) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`No credential found for "${credentialService}". Authorization required.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const baseUrl = getProviderBaseUrl(credentialService);
|
|
22
|
+
if (!baseUrl) {
|
|
23
|
+
throw new Error(`No base URL configured for "${credentialService}".`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return new BYOOAuthConnection({
|
|
27
|
+
id: meta.credentialId,
|
|
28
|
+
providerKey: credentialService,
|
|
29
|
+
baseUrl,
|
|
30
|
+
accountInfo: null,
|
|
31
|
+
grantedScopes: meta.grantedScopes ?? [],
|
|
32
|
+
credentialService,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface OAuthConnectionRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string; // relative, e.g. "/2/tweets"
|
|
4
|
+
query?: Record<string, string>;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
body?: unknown; // JSON-serializable
|
|
7
|
+
/**
|
|
8
|
+
* Override the connection's default base URL for this request.
|
|
9
|
+
* Required for providers that span multiple API hosts sharing
|
|
10
|
+
* one OAuth token (e.g. Google: Gmail, Calendar, People all
|
|
11
|
+
* use the same credential but different base URLs).
|
|
12
|
+
*/
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OAuthConnectionResponse {
|
|
17
|
+
status: number;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OAuthConnection {
|
|
23
|
+
/** Make an authenticated HTTP request through this connection. */
|
|
24
|
+
request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute a callback with a valid raw access token. This is an escape hatch
|
|
28
|
+
* for provider-specific endpoints that don't fit the relative-path model
|
|
29
|
+
* (e.g. Gmail batch API on a different host). Throws for platform connections
|
|
30
|
+
* where raw tokens are not available locally.
|
|
31
|
+
*/
|
|
32
|
+
withToken<T>(fn: (token: string) => Promise<T>): Promise<T>;
|
|
33
|
+
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly providerKey: string;
|
|
36
|
+
readonly accountInfo: string | null;
|
|
37
|
+
readonly grantedScopes: string[];
|
|
38
|
+
}
|