@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,163 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CredentialRequiredError,
|
|
5
|
+
PlatformOAuthConnection,
|
|
6
|
+
ProviderUnreachableError,
|
|
7
|
+
} from "./platform-connection.js";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_OPTIONS = {
|
|
10
|
+
id: "conn-1",
|
|
11
|
+
providerKey: "integration:gmail",
|
|
12
|
+
externalId: "ext-123",
|
|
13
|
+
accountInfo: "user@example.com",
|
|
14
|
+
grantedScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
15
|
+
assistantId: "asst-abc",
|
|
16
|
+
platformBaseUrl: "https://platform.example.com",
|
|
17
|
+
apiKey: "test-api-key",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("PlatformOAuthConnection", () => {
|
|
21
|
+
let originalFetch: typeof globalThis.fetch;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
originalFetch = globalThis.fetch;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
globalThis.fetch = originalFetch;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("successful proxied request", async () => {
|
|
32
|
+
const upstreamBody = { messages: [{ id: "msg-1", snippet: "Hello" }] };
|
|
33
|
+
|
|
34
|
+
globalThis.fetch = mock(
|
|
35
|
+
async (url: string | URL | Request, init?: RequestInit) => {
|
|
36
|
+
expect(url).toBe(
|
|
37
|
+
"https://platform.example.com/v1/assistants/asst-abc/external-provider-proxy/gmail/",
|
|
38
|
+
);
|
|
39
|
+
expect(init?.method).toBe("POST");
|
|
40
|
+
expect(init?.headers).toEqual({
|
|
41
|
+
Authorization: "Api-Key test-api-key",
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const parsed = JSON.parse(init?.body as string);
|
|
46
|
+
expect(parsed).toEqual({
|
|
47
|
+
request: {
|
|
48
|
+
method: "GET",
|
|
49
|
+
path: "/gmail/v1/users/me/messages",
|
|
50
|
+
query: { maxResults: "10" },
|
|
51
|
+
headers: {},
|
|
52
|
+
body: null,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return new Response(
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
status: 200,
|
|
59
|
+
headers: { "content-type": "application/json" },
|
|
60
|
+
body: upstreamBody,
|
|
61
|
+
}),
|
|
62
|
+
{ status: 200 },
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
) as unknown as typeof globalThis.fetch;
|
|
66
|
+
|
|
67
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
68
|
+
const result = await conn.request({
|
|
69
|
+
method: "GET",
|
|
70
|
+
path: "/gmail/v1/users/me/messages",
|
|
71
|
+
query: { maxResults: "10" },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.status).toBe(200);
|
|
75
|
+
expect(result.headers).toEqual({ "content-type": "application/json" });
|
|
76
|
+
expect(result.body).toEqual(upstreamBody);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("forwards baseUrl when provided", async () => {
|
|
80
|
+
globalThis.fetch = mock(
|
|
81
|
+
async (_url: string | URL | Request, init?: RequestInit) => {
|
|
82
|
+
const parsed = JSON.parse(init?.body as string);
|
|
83
|
+
expect(parsed.request.baseUrl).toBe(
|
|
84
|
+
"https://www.googleapis.com/calendar/v3",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return new Response(
|
|
88
|
+
JSON.stringify({ status: 200, headers: {}, body: {} }),
|
|
89
|
+
{ status: 200 },
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
) as unknown as typeof globalThis.fetch;
|
|
93
|
+
|
|
94
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
95
|
+
await conn.request({
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: "/calendars/primary/events",
|
|
98
|
+
baseUrl: "https://www.googleapis.com/calendar/v3",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("omits baseUrl from envelope when not provided", async () => {
|
|
103
|
+
globalThis.fetch = mock(
|
|
104
|
+
async (_url: string | URL | Request, init?: RequestInit) => {
|
|
105
|
+
const parsed = JSON.parse(init?.body as string);
|
|
106
|
+
expect("baseUrl" in parsed.request).toBe(false);
|
|
107
|
+
|
|
108
|
+
return new Response(
|
|
109
|
+
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
110
|
+
{ status: 200 },
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
) as unknown as typeof globalThis.fetch;
|
|
114
|
+
|
|
115
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
116
|
+
await conn.request({ method: "GET", path: "/some/path" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("424 response throws CredentialRequiredError", async () => {
|
|
120
|
+
globalThis.fetch = mock(async () => {
|
|
121
|
+
return new Response("", { status: 424 });
|
|
122
|
+
}) as unknown as typeof globalThis.fetch;
|
|
123
|
+
|
|
124
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
125
|
+
await expect(
|
|
126
|
+
conn.request({ method: "GET", path: "/test" }),
|
|
127
|
+
).rejects.toThrow(CredentialRequiredError);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("502 response throws ProviderUnreachableError", async () => {
|
|
131
|
+
globalThis.fetch = mock(async () => {
|
|
132
|
+
return new Response("", { status: 502 });
|
|
133
|
+
}) as unknown as typeof globalThis.fetch;
|
|
134
|
+
|
|
135
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
136
|
+
await expect(
|
|
137
|
+
conn.request({ method: "GET", path: "/test" }),
|
|
138
|
+
).rejects.toThrow(ProviderUnreachableError);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("withToken throws clear error", async () => {
|
|
142
|
+
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
143
|
+
await expect(conn.withToken(async (token) => token)).rejects.toThrow(
|
|
144
|
+
"Raw token access is not supported for platform-managed connections. Use connection.request() instead.",
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("strips integration: prefix from providerKey for slug", async () => {
|
|
149
|
+
globalThis.fetch = mock(async (url: string | URL | Request) => {
|
|
150
|
+
expect(String(url)).toContain("/external-provider-proxy/slack/");
|
|
151
|
+
return new Response(
|
|
152
|
+
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
153
|
+
{ status: 200 },
|
|
154
|
+
);
|
|
155
|
+
}) as unknown as typeof globalThis.fetch;
|
|
156
|
+
|
|
157
|
+
const conn = new PlatformOAuthConnection({
|
|
158
|
+
...DEFAULT_OPTIONS,
|
|
159
|
+
providerKey: "integration:slack",
|
|
160
|
+
});
|
|
161
|
+
await conn.request({ method: "GET", path: "/test" });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
OAuthConnection,
|
|
3
|
+
OAuthConnectionRequest,
|
|
4
|
+
OAuthConnectionResponse,
|
|
5
|
+
} from "./connection.js";
|
|
6
|
+
|
|
7
|
+
export class CredentialRequiredError extends Error {
|
|
8
|
+
constructor(message = "Connection not set up on platform") {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "CredentialRequiredError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ProviderUnreachableError extends Error {
|
|
15
|
+
constructor(message = "Provider is unreachable") {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "ProviderUnreachableError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PlatformOAuthConnectionOptions {
|
|
22
|
+
id: string;
|
|
23
|
+
providerKey: string;
|
|
24
|
+
externalId: string;
|
|
25
|
+
accountInfo: string | null;
|
|
26
|
+
grantedScopes: string[];
|
|
27
|
+
assistantId: string;
|
|
28
|
+
platformBaseUrl: string;
|
|
29
|
+
apiKey: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class PlatformOAuthConnection implements OAuthConnection {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly providerKey: string;
|
|
35
|
+
readonly externalId: string;
|
|
36
|
+
readonly accountInfo: string | null;
|
|
37
|
+
readonly grantedScopes: string[];
|
|
38
|
+
|
|
39
|
+
private readonly assistantId: string;
|
|
40
|
+
private readonly platformBaseUrl: string;
|
|
41
|
+
private readonly apiKey: string;
|
|
42
|
+
|
|
43
|
+
constructor(options: PlatformOAuthConnectionOptions) {
|
|
44
|
+
this.id = options.id;
|
|
45
|
+
this.providerKey = options.providerKey;
|
|
46
|
+
this.externalId = options.externalId;
|
|
47
|
+
this.accountInfo = options.accountInfo;
|
|
48
|
+
this.grantedScopes = options.grantedScopes;
|
|
49
|
+
this.assistantId = options.assistantId;
|
|
50
|
+
this.platformBaseUrl = options.platformBaseUrl;
|
|
51
|
+
this.apiKey = options.apiKey;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
55
|
+
const providerSlug = this.providerKey.replace(/^integration:/, "");
|
|
56
|
+
const proxyUrl = `${this.platformBaseUrl}/v1/assistants/${this.assistantId}/external-provider-proxy/${providerSlug}/`;
|
|
57
|
+
|
|
58
|
+
const body: Record<string, unknown> = {
|
|
59
|
+
request: {
|
|
60
|
+
method: req.method,
|
|
61
|
+
path: req.path,
|
|
62
|
+
query: req.query ?? {},
|
|
63
|
+
headers: req.headers ?? {},
|
|
64
|
+
body: req.body ?? null,
|
|
65
|
+
...(req.baseUrl ? { baseUrl: req.baseUrl } : {}),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const response = await fetch(proxyUrl, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Api-Key ${this.apiKey}`,
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(body),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (response.status === 424) {
|
|
79
|
+
throw new CredentialRequiredError();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (response.status === 502) {
|
|
83
|
+
throw new ProviderUnreachableError();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Platform proxy returned unexpected status ${response.status}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const json = (await response.json()) as {
|
|
93
|
+
status: number;
|
|
94
|
+
headers: Record<string, string>;
|
|
95
|
+
body: unknown;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
status: json.status,
|
|
100
|
+
headers: json.headers,
|
|
101
|
+
body: json.body,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async withToken<T>(_fn: (token: string) => Promise<T>): Promise<T> {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"Raw token access is not supported for platform-managed connections. Use connection.request() instead.",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Default base URL per credential service. Used by the connection when no per-request override is provided. */
|
|
2
|
+
export const PROVIDER_BASE_URLS: Record<string, string> = {
|
|
3
|
+
"integration:gmail": "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
4
|
+
"integration:slack": "https://slack.com/api",
|
|
5
|
+
"integration:twitter": "https://api.x.com",
|
|
6
|
+
"integration:notion": "https://api.notion.com",
|
|
7
|
+
"integration:linear": "https://api.linear.app",
|
|
8
|
+
"integration:github": "https://api.github.com",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Alternative base URLs for providers that span multiple API hosts
|
|
13
|
+
* sharing one OAuth token. Callers pass these via `request({ baseUrl })`.
|
|
14
|
+
*/
|
|
15
|
+
export const GOOGLE_CALENDAR_BASE_URL =
|
|
16
|
+
"https://www.googleapis.com/calendar/v3";
|
|
17
|
+
export const GOOGLE_PEOPLE_BASE_URL = "https://people.googleapis.com/v1";
|
|
18
|
+
|
|
19
|
+
export function getProviderBaseUrl(providerKey: string): string | undefined {
|
|
20
|
+
return PROVIDER_BASE_URLS[providerKey];
|
|
21
|
+
}
|
|
@@ -67,7 +67,7 @@ export const PROVIDER_PROFILES: Record<string, OAuthProviderProfile> = {
|
|
|
67
67
|
],
|
|
68
68
|
extraParams: { access_type: "offline", prompt: "consent" },
|
|
69
69
|
callbackTransport: "loopback",
|
|
70
|
-
setupSkillId: "google-oauth-
|
|
70
|
+
setupSkillId: "google-oauth-applescript",
|
|
71
71
|
setup: {
|
|
72
72
|
displayName: "Google (Gmail & Calendar)",
|
|
73
73
|
dashboardUrl: "https://console.cloud.google.com/apis/credentials",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* orchestrator without duplicating storage logic.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { credentialKey, migrateKeys } from "../security/credential-key.js";
|
|
9
10
|
import type {
|
|
10
11
|
OAuth2FlowResult,
|
|
11
12
|
TokenEndpointAuthMethod,
|
|
@@ -14,10 +15,7 @@ import {
|
|
|
14
15
|
deleteSecureKeyAsync,
|
|
15
16
|
setSecureKeyAsync,
|
|
16
17
|
} from "../security/secure-keys.js";
|
|
17
|
-
import {
|
|
18
|
-
deleteCredentialMetadata,
|
|
19
|
-
upsertCredentialMetadata,
|
|
20
|
-
} from "../tools/credentials/metadata-store.js";
|
|
18
|
+
import { upsertCredentialMetadata } from "../tools/credentials/metadata-store.js";
|
|
21
19
|
import type { CredentialInjectionTemplate } from "../tools/credentials/policy-types.js";
|
|
22
20
|
import { runPostConnectHook } from "../tools/credentials/post-connect-hooks.js";
|
|
23
21
|
|
|
@@ -56,6 +54,8 @@ export interface StoreOAuth2TokensParams {
|
|
|
56
54
|
export async function storeOAuth2Tokens(
|
|
57
55
|
params: StoreOAuth2TokensParams,
|
|
58
56
|
): Promise<{ accountInfo?: string }> {
|
|
57
|
+
migrateKeys();
|
|
58
|
+
|
|
59
59
|
const {
|
|
60
60
|
service,
|
|
61
61
|
tokens,
|
|
@@ -71,7 +71,7 @@ export async function storeOAuth2Tokens(
|
|
|
71
71
|
} = params;
|
|
72
72
|
|
|
73
73
|
const tokenStored = await setSecureKeyAsync(
|
|
74
|
-
|
|
74
|
+
credentialKey(service, "access_token"),
|
|
75
75
|
tokens.accessToken,
|
|
76
76
|
);
|
|
77
77
|
if (!tokenStored) {
|
|
@@ -97,17 +97,11 @@ export async function storeOAuth2Tokens(
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
`credential:${service}:client_id`,
|
|
103
|
-
clientId,
|
|
104
|
-
);
|
|
105
|
-
if (!clientIdStored) {
|
|
106
|
-
throw new Error("Failed to store client_id in secure storage");
|
|
107
|
-
}
|
|
100
|
+
// client_id is stored in metadata only (oauth2ClientId field) — not the
|
|
101
|
+
// secure store. token-manager.ts reads it from meta?.oauth2ClientId.
|
|
108
102
|
if (clientSecret) {
|
|
109
103
|
const clientSecretStored = await setSecureKeyAsync(
|
|
110
|
-
|
|
104
|
+
credentialKey(service, "client_secret"),
|
|
111
105
|
clientSecret,
|
|
112
106
|
);
|
|
113
107
|
if (!clientSecretStored) {
|
|
@@ -121,7 +115,6 @@ export async function storeOAuth2Tokens(
|
|
|
121
115
|
grantedScopes,
|
|
122
116
|
oauth2TokenUrl: tokenUrl,
|
|
123
117
|
oauth2ClientId: clientId,
|
|
124
|
-
oauth2ClientSecret: clientSecret ?? null,
|
|
125
118
|
...(tokenEndpointAuthMethod
|
|
126
119
|
? { oauth2TokenEndpointAuthMethod: tokenEndpointAuthMethod }
|
|
127
120
|
: {}),
|
|
@@ -143,11 +136,14 @@ export async function storeOAuth2Tokens(
|
|
|
143
136
|
setNestedValue,
|
|
144
137
|
} = await import("../config/loader.js");
|
|
145
138
|
const raw = loadRawConfig();
|
|
139
|
+
|
|
140
|
+
// Write to the namespaced path
|
|
146
141
|
setNestedValue(
|
|
147
142
|
raw,
|
|
148
|
-
`integrations
|
|
143
|
+
`integrations.${service}.accountInfo`,
|
|
149
144
|
resolvedAccountInfo,
|
|
150
145
|
);
|
|
146
|
+
|
|
151
147
|
saveRawConfig(raw);
|
|
152
148
|
invalidateConfigCache();
|
|
153
149
|
} catch {
|
|
@@ -157,18 +153,22 @@ export async function storeOAuth2Tokens(
|
|
|
157
153
|
|
|
158
154
|
if (tokens.refreshToken) {
|
|
159
155
|
const refreshStored = await setSecureKeyAsync(
|
|
160
|
-
|
|
156
|
+
credentialKey(service, "refresh_token"),
|
|
161
157
|
tokens.refreshToken,
|
|
162
158
|
);
|
|
163
159
|
if (refreshStored) {
|
|
164
|
-
upsertCredentialMetadata(service, "
|
|
160
|
+
upsertCredentialMetadata(service, "access_token", {
|
|
161
|
+
hasRefreshToken: true,
|
|
162
|
+
});
|
|
165
163
|
}
|
|
166
164
|
} else {
|
|
167
165
|
// Re-auth grants that omit refresh_token must clear any stale stored
|
|
168
166
|
// token — otherwise withValidToken() will attempt refresh with invalid
|
|
169
167
|
// credentials.
|
|
170
|
-
await deleteSecureKeyAsync(
|
|
171
|
-
|
|
168
|
+
await deleteSecureKeyAsync(credentialKey(service, "refresh_token"));
|
|
169
|
+
upsertCredentialMetadata(service, "access_token", {
|
|
170
|
+
hasRefreshToken: false,
|
|
171
|
+
});
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Run any provider-specific post-connect actions (e.g. Slack welcome DM)
|
|
@@ -48,7 +48,11 @@ function riskCacheKey(
|
|
|
48
48
|
workingDir?: string,
|
|
49
49
|
manifestOverride?: ManifestOverride,
|
|
50
50
|
): string {
|
|
51
|
-
|
|
51
|
+
// Strip `reason` before computing the cache key — it is cosmetic and varies
|
|
52
|
+
// per invocation even for identical tool operations, causing unnecessary
|
|
53
|
+
// cache misses.
|
|
54
|
+
const { reason: _reason, ...cacheableInput } = input;
|
|
55
|
+
const inputJson = JSON.stringify(cacheableInput);
|
|
52
56
|
const hash = createHash("sha256")
|
|
53
57
|
.update(inputJson)
|
|
54
58
|
.update("\0")
|
|
@@ -139,6 +143,7 @@ const LOW_RISK_PROGRAMS = new Set([
|
|
|
139
143
|
"tree",
|
|
140
144
|
"du",
|
|
141
145
|
"df",
|
|
146
|
+
"assistant",
|
|
142
147
|
]);
|
|
143
148
|
|
|
144
149
|
// High-risk shell programs / patterns
|
|
@@ -109,7 +109,11 @@ export function isOnboardingComplete(): boolean {
|
|
|
109
109
|
* 3. If BOOTSTRAP.md exists, append first-run ritual instructions
|
|
110
110
|
* 4. Append skills catalog from ~/.vellum/workspace/skills
|
|
111
111
|
*/
|
|
112
|
-
export
|
|
112
|
+
export interface BuildSystemPromptOptions {
|
|
113
|
+
hasNoClient?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
|
|
113
117
|
const soulPath = getWorkspacePromptPath("SOUL.md");
|
|
114
118
|
const identityPath = getWorkspacePromptPath("IDENTITY.md");
|
|
115
119
|
const userPath = getWorkspacePromptPath("USER.md");
|
|
@@ -156,13 +160,14 @@ export function buildSystemPrompt(): string {
|
|
|
156
160
|
);
|
|
157
161
|
}
|
|
158
162
|
if (getIsContainerized()) parts.push(buildContainerizedSection());
|
|
159
|
-
|
|
163
|
+
const hasNoClient = options?.hasNoClient ?? false;
|
|
164
|
+
parts.push(buildConfigSection(hasNoClient));
|
|
160
165
|
parts.push(buildCliReferenceSection());
|
|
161
166
|
parts.push(buildPostToolResponseSection());
|
|
162
167
|
parts.push(buildExternalCommsIdentitySection());
|
|
163
168
|
parts.push(buildChannelAwarenessSection());
|
|
164
169
|
const config = getConfig();
|
|
165
|
-
parts.push(buildToolPermissionSection());
|
|
170
|
+
if (!hasNoClient) parts.push(buildToolPermissionSection());
|
|
166
171
|
parts.push(buildTaskScheduleReminderRoutingSection());
|
|
167
172
|
if (
|
|
168
173
|
isAssistantFeatureFlagEnabled(
|
|
@@ -181,9 +186,9 @@ export function buildSystemPrompt(): string {
|
|
|
181
186
|
if (!isOnboardingComplete()) {
|
|
182
187
|
parts.push(buildStarterTaskPlaybookSection());
|
|
183
188
|
}
|
|
184
|
-
parts.push(buildSystemPermissionSection());
|
|
189
|
+
if (!hasNoClient) parts.push(buildSystemPermissionSection());
|
|
185
190
|
parts.push(buildSwarmGuidanceSection());
|
|
186
|
-
parts.push(buildAccessPreferenceSection());
|
|
191
|
+
parts.push(buildAccessPreferenceSection(hasNoClient));
|
|
187
192
|
parts.push(buildIntegrationSection());
|
|
188
193
|
parts.push(buildMemoryPersistenceSection());
|
|
189
194
|
parts.push(buildMemoryRecallSection());
|
|
@@ -349,7 +354,7 @@ function buildInChatConfigurationSection(): string {
|
|
|
349
354
|
"",
|
|
350
355
|
"### Avatar Customisation",
|
|
351
356
|
"",
|
|
352
|
-
'You can change your avatar appearance using the `set_avatar` tool. When the user asks to change, update, or customise your avatar, use `set_avatar` with a `description` parameter describing the desired appearance (e.g. "a friendly purple cat with green eyes wearing a tiny hat"). The tool generates an avatar image and updates all connected clients automatically.
|
|
357
|
+
'You can change your avatar appearance using the `set_avatar` tool. When the user asks to change, update, or customise your avatar, use `set_avatar` with a `description` parameter describing the desired appearance (e.g. "a friendly purple cat with green eyes wearing a tiny hat"). The tool generates an avatar image and updates all connected clients automatically.',
|
|
353
358
|
"",
|
|
354
359
|
"**After generating a new avatar**, always update the `## Avatar` section in `IDENTITY.md` with a brief description of the current avatar appearance. This ensures you remember what you look like across sessions. Example:",
|
|
355
360
|
"```",
|
|
@@ -402,9 +407,9 @@ export function buildPhoneCallsRoutingSection(): string {
|
|
|
402
407
|
"### Trigger phrases",
|
|
403
408
|
'- "Set up phone calling" / "enable calls"',
|
|
404
409
|
'- "Make a call to..." / "call [number/business]"',
|
|
405
|
-
'- "Configure Twilio" (
|
|
410
|
+
'- "Configure Twilio" (for voice calls)',
|
|
406
411
|
'- "Can you make phone calls?"',
|
|
407
|
-
'- "Set up my phone number"
|
|
412
|
+
'- "Set up my phone number"',
|
|
408
413
|
"",
|
|
409
414
|
"### What it does",
|
|
410
415
|
"The skill handles the full phone calling lifecycle:",
|
|
@@ -572,7 +577,23 @@ export function buildSwarmGuidanceSection(): string {
|
|
|
572
577
|
].join("\n");
|
|
573
578
|
}
|
|
574
579
|
|
|
575
|
-
function buildAccessPreferenceSection(): string {
|
|
580
|
+
function buildAccessPreferenceSection(hasNoClient: boolean): string {
|
|
581
|
+
if (hasNoClient) {
|
|
582
|
+
return [
|
|
583
|
+
"## External Service Access Preference",
|
|
584
|
+
"",
|
|
585
|
+
"When interacting with external services (GitHub, Slack, Linear, Jira, cloud providers, etc.),",
|
|
586
|
+
"follow this priority order:",
|
|
587
|
+
"",
|
|
588
|
+
"1. **Sandbox first (`bash`)** — Always try to do things in your own sandbox environment first.",
|
|
589
|
+
" If a tool (git, curl, jq, etc.) is not installed, install it yourself using `bash`",
|
|
590
|
+
" (e.g. `apt-get install -y git`). The sandbox is your own machine — you have full control.",
|
|
591
|
+
"2. **web_fetch** — For public endpoints or simple API calls that don't need auth.",
|
|
592
|
+
"3. **Browser automation as last resort** — Only when the task genuinely requires a browser",
|
|
593
|
+
" (e.g., no API exists, visual interaction needed, or OAuth consent screen).",
|
|
594
|
+
].join("\n");
|
|
595
|
+
}
|
|
596
|
+
|
|
576
597
|
return [
|
|
577
598
|
"## External Service Access Preference",
|
|
578
599
|
"",
|
|
@@ -635,10 +656,14 @@ function buildIntegrationSection(): string {
|
|
|
635
656
|
const raw = loadRawConfig();
|
|
636
657
|
const lines = ["## Connected Services", ""];
|
|
637
658
|
for (const cred of oauthCreds) {
|
|
638
|
-
const acctInfo = getNestedValue(
|
|
659
|
+
const acctInfo = (getNestedValue(
|
|
639
660
|
raw,
|
|
640
|
-
`integrations
|
|
641
|
-
)
|
|
661
|
+
`integrations.${cred.service}.accountInfo`,
|
|
662
|
+
) ??
|
|
663
|
+
// Fallback: legacy config path used before the namespace migration
|
|
664
|
+
getNestedValue(raw, `integrations.accountInfo.${cred.service}`)) as
|
|
665
|
+
| string
|
|
666
|
+
| undefined;
|
|
642
667
|
const state = acctInfo ? `Connected (${acctInfo})` : "Connected";
|
|
643
668
|
lines.push(`- **${cred.service}**: ${state}`);
|
|
644
669
|
}
|
|
@@ -751,10 +776,12 @@ function buildPostToolResponseSection(): string {
|
|
|
751
776
|
" → Call document_create",
|
|
752
777
|
"",
|
|
753
778
|
"For permission-gated tools, send one short context sentence immediately before the tool call so the user can make an informed allow/deny decision.",
|
|
779
|
+
"",
|
|
780
|
+
'**Reason field:** For every tool call, include a `reason` parameter — a brief, non-technical explanation of what you are doing and why. This is shown to the user as a live status update. Use simple language a non-technical person would understand (e.g. "Checking your project settings" not "file_read config.ts").',
|
|
754
781
|
].join("\n");
|
|
755
782
|
}
|
|
756
783
|
|
|
757
|
-
function buildConfigSection(): string {
|
|
784
|
+
function buildConfigSection(hasNoClient: boolean): string {
|
|
758
785
|
// Always use `file_edit` (not `host_file_edit`) for workspace files — file_edit
|
|
759
786
|
// handles sandbox path mapping internally, and host_file_edit is permission-gated
|
|
760
787
|
// which would trigger approval prompts for routine workspace updates.
|
|
@@ -763,10 +790,14 @@ function buildConfigSection(): string {
|
|
|
763
790
|
const config = getConfig();
|
|
764
791
|
const configPreamble = `Your configuration directory is \`${hostWorkspaceDir}/\`.`;
|
|
765
792
|
|
|
793
|
+
const fileToolGuidance = hasNoClient
|
|
794
|
+
? `${configPreamble} **Always use \`file_read\` and \`file_edit\` for these files** — they are inside your sandbox working directory:`
|
|
795
|
+
: `${configPreamble} **Always use \`file_read\` and \`file_edit\` (not \`host_file_read\` / \`host_file_edit\`) for these files** — they are inside your sandbox working directory and do not require host access or user approval:`;
|
|
796
|
+
|
|
766
797
|
return [
|
|
767
798
|
"## Configuration",
|
|
768
799
|
`- **Active model**: \`${config.model}\` (provider: ${config.provider})`,
|
|
769
|
-
|
|
800
|
+
fileToolGuidance,
|
|
770
801
|
"",
|
|
771
802
|
"- `IDENTITY.md` — Your name, nature, personality, and emoji. Updated during the first-run ritual.",
|
|
772
803
|
"- `SOUL.md` — Core principles, personality, and evolution guidance. Your behavioral foundation.",
|
|
@@ -801,7 +832,13 @@ function buildConfigSection(): string {
|
|
|
801
832
|
"- They rename you or change your role",
|
|
802
833
|
"- Your avatar appearance changes (update the `## Avatar` section with a description of the new look)",
|
|
803
834
|
"",
|
|
804
|
-
|
|
835
|
+
...(hasNoClient
|
|
836
|
+
? [
|
|
837
|
+
"When reading or updating workspace files, always use the sandbox tools (`file_read`, `file_edit`).",
|
|
838
|
+
]
|
|
839
|
+
: [
|
|
840
|
+
"When reading or updating workspace files, always use the sandbox tools (`file_read`, `file_edit`). Never use `host_file_read` or `host_file_edit` for workspace files — those are for host-only resources outside your workspace.",
|
|
841
|
+
]),
|
|
805
842
|
"",
|
|
806
843
|
"When updating, read the file first, then make a targeted edit. Include all useful information, but don't bloat the files over time",
|
|
807
844
|
].join("\n");
|
|
@@ -40,7 +40,7 @@ Then figure out together:
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
The two actions MUST have different labels and prompts. Double-check before calling ui_show that you are not repeating the same suggestion.
|
|
43
|
-
If `ui_show` is not available (voice
|
|
43
|
+
If `ui_show` is not available (voice or other non-dashboard channels), present the two suggestions as plain text messages instead, numbered so the user can reply with which one they'd like. If the user types a response instead of clicking, continue via the text path. If they want to defer both suggestions and do something else entirely, that's fine too.
|
|
44
44
|
|
|
45
45
|
## Requirements
|
|
46
46
|
|
|
@@ -16,6 +16,10 @@ export interface GeminiProviderOptions {
|
|
|
16
16
|
streamTimeoutMs?: number;
|
|
17
17
|
/** When set, routes requests through the managed proxy at this base URL. */
|
|
18
18
|
managedBaseUrl?: string;
|
|
19
|
+
/** Vertex AI project placeholder (used with managed proxy). */
|
|
20
|
+
vertexProject?: string;
|
|
21
|
+
/** Vertex AI location placeholder (used with managed proxy). */
|
|
22
|
+
vertexLocation?: string;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export class GeminiProvider implements Provider {
|
|
@@ -29,12 +33,17 @@ export class GeminiProvider implements Provider {
|
|
|
29
33
|
model: string,
|
|
30
34
|
options: GeminiProviderOptions = {},
|
|
31
35
|
) {
|
|
32
|
-
this.client =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
this.client = options.managedBaseUrl
|
|
37
|
+
? new GoogleGenAI({
|
|
38
|
+
vertexai: true,
|
|
39
|
+
project: options.vertexProject ?? "proxy",
|
|
40
|
+
location: options.vertexLocation ?? "us-central1",
|
|
41
|
+
httpOptions: {
|
|
42
|
+
baseUrl: options.managedBaseUrl,
|
|
43
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
: new GoogleGenAI({ apiKey });
|
|
38
47
|
this.model = model;
|
|
39
48
|
this.streamTimeoutMs = options.streamTimeoutMs ?? 300_000;
|
|
40
49
|
}
|
|
@@ -29,12 +29,12 @@ export const MANAGED_PROVIDER_META: Record<string, ManagedProviderMeta> = {
|
|
|
29
29
|
anthropic: {
|
|
30
30
|
name: "anthropic",
|
|
31
31
|
managed: true,
|
|
32
|
-
proxyPath: "/v1/runtime-proxy/
|
|
32
|
+
proxyPath: "/v1/runtime-proxy/vertex",
|
|
33
33
|
},
|
|
34
34
|
gemini: {
|
|
35
35
|
name: "gemini",
|
|
36
36
|
managed: true,
|
|
37
|
-
proxyPath: "/v1/runtime-proxy/
|
|
37
|
+
proxyPath: "/v1/runtime-proxy/vertex",
|
|
38
38
|
},
|
|
39
39
|
fireworks: {
|
|
40
40
|
name: "fireworks",
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { getPlatformBaseUrl } from "../../config/env.js";
|
|
13
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
13
14
|
import { getSecureKey } from "../../security/secure-keys.js";
|
|
14
15
|
import { MANAGED_PROVIDER_META } from "./constants.js";
|
|
15
16
|
|
|
16
17
|
/** Storage key for the assistant API key credential. */
|
|
17
|
-
const ASSISTANT_API_KEY_STORAGE_KEY =
|
|
18
|
+
const ASSISTANT_API_KEY_STORAGE_KEY = credentialKey(
|
|
19
|
+
"vellum",
|
|
20
|
+
"assistant_api_key",
|
|
21
|
+
);
|
|
18
22
|
|
|
19
23
|
export interface ManagedProxyContext {
|
|
20
24
|
/** Whether managed proxy prerequisites are satisfied. */
|