@vellumai/assistant 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +17 -27
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +113 -0
- package/src/__tests__/config-schema.test.ts +2 -2
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-title-service.test.ts +30 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- package/src/__tests__/openai-whisper.test.ts +93 -0
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/volume-security-guard.test.ts +155 -0
- package/src/cli/commands/conversations.ts +0 -18
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
- package/src/config/env-registry.ts +9 -0
- package/src/config/env.ts +8 -2
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +0 -12
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -0
- package/src/context/window-manager.ts +53 -2
- package/src/credential-execution/managed-catalog.ts +5 -15
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/daemon-control.ts +7 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/lifecycle.ts +10 -47
- package/src/daemon/providers-setup.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/hooks/manager.ts +7 -0
- package/src/instrument.ts +33 -1
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +26 -10
- package/src/memory/db-init.ts +5 -13
- package/src/memory/embedding-local.ts +11 -5
- package/src/memory/indexer.ts +15 -106
- package/src/memory/job-handlers/conversation-starters.ts +24 -36
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +0 -8
- package/src/memory/jobs-worker.ts +0 -20
- package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
- package/src/memory/migrations/index.ts +1 -3
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/slack/adapter.ts +29 -2
- package/src/oauth/connection-resolver.test.ts +22 -18
- package/src/oauth/connection-resolver.ts +92 -7
- package/src/oauth/platform-connection.test.ts +78 -69
- package/src/oauth/platform-connection.ts +12 -19
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/trust-client.ts +332 -0
- package/src/permissions/trust-store-interface.ts +105 -0
- package/src/permissions/trust-store.ts +531 -39
- package/src/platform/client.test.ts +148 -0
- package/src/platform/client.ts +71 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
- package/src/providers/speech-to-text/openai-whisper.ts +68 -0
- package/src/providers/speech-to-text/resolve.ts +9 -0
- package/src/providers/speech-to-text/types.ts +17 -0
- package/src/runtime/auth/route-policy.ts +14 -0
- package/src/runtime/auth/token-service.ts +133 -0
- package/src/runtime/http-server.ts +4 -2
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +44 -2
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/inbound-message-handler.ts +27 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
- package/src/runtime/routes/log-export-routes.ts +1 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +124 -2
- package/src/runtime/routes/secret-routes.ts +4 -1
- package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
- package/src/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +173 -0
- package/src/security/secure-keys.ts +65 -22
- package/src/signals/bash.ts +3 -0
- package/src/signals/cancel.ts +3 -0
- package/src/signals/confirm.ts +3 -0
- package/src/signals/conversation-undo.ts +3 -0
- package/src/signals/event-stream.ts +7 -0
- package/src/signals/shotgun.ts +3 -0
- package/src/signals/trust-rule.ts +3 -0
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
- package/src/telemetry/usage-telemetry-reporter.ts +21 -19
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/load.ts +9 -2
- package/src/util/device-id.ts +70 -7
- package/src/util/logger.ts +35 -9
- package/src/util/platform.ts +29 -5
- package/src/util/xml.ts +8 -0
- package/src/workspace/heartbeat-service.ts +5 -24
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- package/src/config/schemas/memory-simplified.ts +0 -101
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { getPlatformAssistantId } from "../config/env.js";
|
|
2
1
|
import { getConfig } from "../config/loader.js";
|
|
3
2
|
import { type Services, ServicesSchema } from "../config/schemas/services.js";
|
|
4
|
-
import {
|
|
3
|
+
import { VellumPlatformClient } from "../platform/client.js";
|
|
5
4
|
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
5
|
+
import { getLogger } from "../util/logger.js";
|
|
6
6
|
import { BYOOAuthConnection } from "./byo-connection.js";
|
|
7
7
|
import type { OAuthConnection } from "./connection.js";
|
|
8
8
|
import { getActiveConnection, getProvider } from "./oauth-store.js";
|
|
9
9
|
import { PlatformOAuthConnection } from "./platform-connection.js";
|
|
10
10
|
|
|
11
|
+
const log = getLogger("connection-resolver");
|
|
12
|
+
|
|
11
13
|
export interface ResolveOAuthConnectionOptions {
|
|
12
14
|
/** OAuth app client ID — narrows to a specific app when multiple BYO apps
|
|
13
15
|
* exist for the same provider. */
|
|
@@ -46,16 +48,32 @@ export async function resolveOAuthConnection(
|
|
|
46
48
|
if (managedKey && managedKey in ServicesSchema.shape) {
|
|
47
49
|
const services: Services = getConfig().services;
|
|
48
50
|
if (services[managedKey as keyof Services].mode === "managed") {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
+
const client = await VellumPlatformClient.create();
|
|
52
|
+
if (!client || !client.platformAssistantId) {
|
|
53
|
+
const detail = !client
|
|
54
|
+
? "missing platform prerequisites"
|
|
55
|
+
: "missing assistant ID";
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Platform-managed connection for "${providerKey}" cannot be created: ${detail}. ` +
|
|
58
|
+
`Log in to the Vellum platform or switch to using your own OAuth app.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const providerSlug = providerKey.replace(/^integration:/, "");
|
|
63
|
+
|
|
64
|
+
const connectionId = await resolvePlatformConnectionId({
|
|
65
|
+
client,
|
|
66
|
+
provider: providerSlug,
|
|
67
|
+
account,
|
|
68
|
+
});
|
|
69
|
+
|
|
51
70
|
return new PlatformOAuthConnection({
|
|
52
71
|
id: providerKey,
|
|
53
72
|
providerKey,
|
|
54
73
|
externalId: providerKey,
|
|
55
74
|
accountInfo: account ?? null,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
apiKey: ctx.assistantApiKey,
|
|
75
|
+
client,
|
|
76
|
+
connectionId,
|
|
59
77
|
});
|
|
60
78
|
}
|
|
61
79
|
}
|
|
@@ -98,3 +116,70 @@ export async function resolveOAuthConnection(
|
|
|
98
116
|
accountInfo: conn.accountInfo,
|
|
99
117
|
});
|
|
100
118
|
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Platform connection ID resolution
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
interface ResolvePlatformConnectionIdOptions {
|
|
125
|
+
client: VellumPlatformClient;
|
|
126
|
+
provider: string;
|
|
127
|
+
account?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fetch the platform-side connection ID for a managed provider by calling
|
|
132
|
+
* the List Connections endpoint.
|
|
133
|
+
*/
|
|
134
|
+
async function resolvePlatformConnectionId(
|
|
135
|
+
options: ResolvePlatformConnectionIdOptions,
|
|
136
|
+
): Promise<string> {
|
|
137
|
+
const { client, provider, account } = options;
|
|
138
|
+
|
|
139
|
+
const params = new URLSearchParams();
|
|
140
|
+
params.set("provider", provider);
|
|
141
|
+
params.set("status", "ACTIVE");
|
|
142
|
+
if (account) {
|
|
143
|
+
params.set("account_identifier", account);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const path = `/v1/assistants/${client.platformAssistantId}/oauth/connections/?${params.toString()}`;
|
|
147
|
+
const response = await client.fetch(path);
|
|
148
|
+
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
log.error(
|
|
151
|
+
{ status: response.status, provider },
|
|
152
|
+
"Failed to list platform OAuth connections",
|
|
153
|
+
);
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Failed to resolve platform connection for "${provider}": HTTP ${response.status}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const body = (await response.json()) as {
|
|
160
|
+
results?: Array<{ id: string; account_label?: string }>;
|
|
161
|
+
};
|
|
162
|
+
const connections = body.results ?? [];
|
|
163
|
+
|
|
164
|
+
if (connections.length === 0) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`No active platform OAuth connection found for provider "${provider}"` +
|
|
167
|
+
(account ? ` with account "${account}"` : "") +
|
|
168
|
+
". Connect the service on the Vellum platform first.",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (connections.length > 1 && !account) {
|
|
173
|
+
log.warn(
|
|
174
|
+
{
|
|
175
|
+
provider,
|
|
176
|
+
count: connections.length,
|
|
177
|
+
selectedId: connections[0].id,
|
|
178
|
+
},
|
|
179
|
+
"Multiple active platform connections found; using the most recently created. " +
|
|
180
|
+
"Pass an account option to select a specific connection.",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return connections[0].id;
|
|
185
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import type { VellumPlatformClient } from "../platform/client.js";
|
|
3
4
|
import { BackendError, VellumError } from "../util/errors.js";
|
|
4
5
|
import {
|
|
5
6
|
CredentialRequiredError,
|
|
@@ -7,40 +8,53 @@ import {
|
|
|
7
8
|
ProviderUnreachableError,
|
|
8
9
|
} from "./platform-connection.js";
|
|
9
10
|
|
|
11
|
+
function makeMockClient(
|
|
12
|
+
fetchImpl?: typeof globalThis.fetch,
|
|
13
|
+
): VellumPlatformClient {
|
|
14
|
+
const mockFetchFn =
|
|
15
|
+
fetchImpl ??
|
|
16
|
+
(mock(async () => {
|
|
17
|
+
return new Response(
|
|
18
|
+
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
19
|
+
{ status: 200 },
|
|
20
|
+
);
|
|
21
|
+
}) as unknown as typeof globalThis.fetch);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
baseUrl: "https://platform.example.com",
|
|
25
|
+
assistantApiKey: "test-api-key",
|
|
26
|
+
platformAssistantId: "asst-abc",
|
|
27
|
+
fetch: mock(async (path: string, init?: RequestInit) => {
|
|
28
|
+
const url = `https://platform.example.com${path}`;
|
|
29
|
+
const headers = new Headers(init?.headers);
|
|
30
|
+
headers.set("Authorization", "Api-Key test-api-key");
|
|
31
|
+
return mockFetchFn(url, { ...init, headers });
|
|
32
|
+
}),
|
|
33
|
+
} as unknown as VellumPlatformClient;
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
const DEFAULT_OPTIONS = {
|
|
11
37
|
id: "conn-1",
|
|
12
38
|
providerKey: "integration:google",
|
|
13
39
|
externalId: "ext-123",
|
|
14
40
|
accountInfo: "user@example.com",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
apiKey: "test-api-key",
|
|
41
|
+
client: makeMockClient(),
|
|
42
|
+
connectionId: "platform-conn-123",
|
|
18
43
|
};
|
|
19
44
|
|
|
20
45
|
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
46
|
test("successful proxied request", async () => {
|
|
32
47
|
const upstreamBody = { messages: [{ id: "msg-1", snippet: "Hello" }] };
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
async (url: string | URL | Request, init?: RequestInit) => {
|
|
36
|
-
expect(url).toBe(
|
|
37
|
-
"https://platform.example.com/v1/assistants/asst-abc/external-provider-proxy/
|
|
49
|
+
const client = makeMockClient(
|
|
50
|
+
mock(async (url: string | URL | Request, init?: RequestInit) => {
|
|
51
|
+
expect(String(url)).toBe(
|
|
52
|
+
"https://platform.example.com/v1/assistants/asst-abc/external-provider-proxy/platform-conn-123/",
|
|
38
53
|
);
|
|
39
54
|
expect(init?.method).toBe("POST");
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
55
|
+
const headers = new Headers(init?.headers);
|
|
56
|
+
expect(headers.get("Authorization")).toBe("Api-Key test-api-key");
|
|
57
|
+
expect(headers.get("Content-Type")).toBe("application/json");
|
|
44
58
|
|
|
45
59
|
const parsed = JSON.parse(init?.body as string);
|
|
46
60
|
expect(parsed).toEqual({
|
|
@@ -61,10 +75,13 @@ describe("PlatformOAuthConnection", () => {
|
|
|
61
75
|
}),
|
|
62
76
|
{ status: 200 },
|
|
63
77
|
);
|
|
64
|
-
},
|
|
65
|
-
)
|
|
78
|
+
}) as unknown as typeof globalThis.fetch,
|
|
79
|
+
);
|
|
66
80
|
|
|
67
|
-
const conn = new PlatformOAuthConnection(
|
|
81
|
+
const conn = new PlatformOAuthConnection({
|
|
82
|
+
...DEFAULT_OPTIONS,
|
|
83
|
+
client,
|
|
84
|
+
});
|
|
68
85
|
const result = await conn.request({
|
|
69
86
|
method: "GET",
|
|
70
87
|
path: "/gmail/v1/users/me/messages",
|
|
@@ -77,8 +94,8 @@ describe("PlatformOAuthConnection", () => {
|
|
|
77
94
|
});
|
|
78
95
|
|
|
79
96
|
test("forwards baseUrl when provided", async () => {
|
|
80
|
-
|
|
81
|
-
async (_url: string | URL | Request, init?: RequestInit) => {
|
|
97
|
+
const client = makeMockClient(
|
|
98
|
+
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
82
99
|
const parsed = JSON.parse(init?.body as string);
|
|
83
100
|
expect(parsed.request.baseUrl).toBe(
|
|
84
101
|
"https://www.googleapis.com/calendar/v3",
|
|
@@ -88,10 +105,10 @@ describe("PlatformOAuthConnection", () => {
|
|
|
88
105
|
JSON.stringify({ status: 200, headers: {}, body: {} }),
|
|
89
106
|
{ status: 200 },
|
|
90
107
|
);
|
|
91
|
-
},
|
|
92
|
-
)
|
|
108
|
+
}) as unknown as typeof globalThis.fetch,
|
|
109
|
+
);
|
|
93
110
|
|
|
94
|
-
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
111
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
95
112
|
await conn.request({
|
|
96
113
|
method: "GET",
|
|
97
114
|
path: "/calendars/primary/events",
|
|
@@ -100,8 +117,8 @@ describe("PlatformOAuthConnection", () => {
|
|
|
100
117
|
});
|
|
101
118
|
|
|
102
119
|
test("omits baseUrl from envelope when not provided", async () => {
|
|
103
|
-
|
|
104
|
-
async (_url: string | URL | Request, init?: RequestInit) => {
|
|
120
|
+
const client = makeMockClient(
|
|
121
|
+
mock(async (_url: string | URL | Request, init?: RequestInit) => {
|
|
105
122
|
const parsed = JSON.parse(init?.body as string);
|
|
106
123
|
expect("baseUrl" in parsed.request).toBe(false);
|
|
107
124
|
|
|
@@ -109,10 +126,10 @@ describe("PlatformOAuthConnection", () => {
|
|
|
109
126
|
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
110
127
|
{ status: 200 },
|
|
111
128
|
);
|
|
112
|
-
},
|
|
113
|
-
)
|
|
129
|
+
}) as unknown as typeof globalThis.fetch,
|
|
130
|
+
);
|
|
114
131
|
|
|
115
|
-
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
132
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
116
133
|
await conn.request({ method: "GET", path: "/some/path" });
|
|
117
134
|
});
|
|
118
135
|
|
|
@@ -127,22 +144,26 @@ describe("PlatformOAuthConnection", () => {
|
|
|
127
144
|
});
|
|
128
145
|
|
|
129
146
|
test("424 response throws CredentialRequiredError", async () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
const client = makeMockClient(
|
|
148
|
+
mock(
|
|
149
|
+
async () => new Response("", { status: 424 }),
|
|
150
|
+
) as unknown as typeof globalThis.fetch,
|
|
151
|
+
);
|
|
133
152
|
|
|
134
|
-
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
153
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
135
154
|
await expect(
|
|
136
155
|
conn.request({ method: "GET", path: "/test" }),
|
|
137
156
|
).rejects.toThrow(CredentialRequiredError);
|
|
138
157
|
});
|
|
139
158
|
|
|
140
159
|
test("502 response throws ProviderUnreachableError", async () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
const client = makeMockClient(
|
|
161
|
+
mock(
|
|
162
|
+
async () => new Response("", { status: 502 }),
|
|
163
|
+
) as unknown as typeof globalThis.fetch,
|
|
164
|
+
);
|
|
144
165
|
|
|
145
|
-
const conn = new PlatformOAuthConnection(DEFAULT_OPTIONS);
|
|
166
|
+
const conn = new PlatformOAuthConnection({ ...DEFAULT_OPTIONS, client });
|
|
146
167
|
await expect(
|
|
147
168
|
conn.request({ method: "GET", path: "/test" }),
|
|
148
169
|
).rejects.toThrow(ProviderUnreachableError);
|
|
@@ -155,36 +176,24 @@ describe("PlatformOAuthConnection", () => {
|
|
|
155
176
|
);
|
|
156
177
|
});
|
|
157
178
|
|
|
158
|
-
test("
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
...DEFAULT_OPTIONS,
|
|
171
|
-
platformBaseUrl: "https://platform.example.com/",
|
|
172
|
-
});
|
|
173
|
-
await conn.request({ method: "GET", path: "/test" });
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("strips integration: prefix from providerKey for slug", async () => {
|
|
177
|
-
globalThis.fetch = mock(async (url: string | URL | Request) => {
|
|
178
|
-
expect(String(url)).toContain("/external-provider-proxy/slack/");
|
|
179
|
-
return new Response(
|
|
180
|
-
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
181
|
-
{ status: 200 },
|
|
182
|
-
);
|
|
183
|
-
}) as unknown as typeof globalThis.fetch;
|
|
179
|
+
test("uses connectionId in proxy URL regardless of providerKey format", async () => {
|
|
180
|
+
const client = makeMockClient(
|
|
181
|
+
mock(async (url: string | URL | Request) => {
|
|
182
|
+
expect(String(url)).toContain(
|
|
183
|
+
"/external-provider-proxy/slack-conn-456/",
|
|
184
|
+
);
|
|
185
|
+
return new Response(
|
|
186
|
+
JSON.stringify({ status: 200, headers: {}, body: null }),
|
|
187
|
+
{ status: 200 },
|
|
188
|
+
);
|
|
189
|
+
}) as unknown as typeof globalThis.fetch,
|
|
190
|
+
);
|
|
184
191
|
|
|
185
192
|
const conn = new PlatformOAuthConnection({
|
|
186
193
|
...DEFAULT_OPTIONS,
|
|
194
|
+
client,
|
|
187
195
|
providerKey: "integration:slack",
|
|
196
|
+
connectionId: "slack-conn-456",
|
|
188
197
|
});
|
|
189
198
|
await conn.request({ method: "GET", path: "/test" });
|
|
190
199
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VellumPlatformClient } from "../platform/client.js";
|
|
1
2
|
import { BackendError } from "../util/errors.js";
|
|
2
3
|
import type {
|
|
3
4
|
OAuthConnection,
|
|
@@ -24,9 +25,9 @@ export interface PlatformOAuthConnectionOptions {
|
|
|
24
25
|
providerKey: string;
|
|
25
26
|
externalId: string;
|
|
26
27
|
accountInfo: string | null;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
client: VellumPlatformClient;
|
|
29
|
+
/** Platform-side connection ID used in the proxy URL path. */
|
|
30
|
+
connectionId: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export class PlatformOAuthConnection implements OAuthConnection {
|
|
@@ -35,18 +36,13 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
35
36
|
readonly externalId: string;
|
|
36
37
|
readonly accountInfo: string | null;
|
|
37
38
|
|
|
38
|
-
private readonly
|
|
39
|
-
private readonly
|
|
40
|
-
private readonly apiKey: string;
|
|
39
|
+
private readonly client: VellumPlatformClient;
|
|
40
|
+
private readonly connectionId: string;
|
|
41
41
|
|
|
42
42
|
constructor(options: PlatformOAuthConnectionOptions) {
|
|
43
|
-
|
|
44
|
-
if (!options.platformBaseUrl) missing.push("platform base URL");
|
|
45
|
-
if (!options.apiKey) missing.push("assistant API key");
|
|
46
|
-
if (!options.assistantId) missing.push("assistant ID");
|
|
47
|
-
if (missing.length > 0) {
|
|
43
|
+
if (!options.connectionId) {
|
|
48
44
|
throw new BackendError(
|
|
49
|
-
`Platform-managed connection for "${options.providerKey}" cannot be created: missing
|
|
45
|
+
`Platform-managed connection for "${options.providerKey}" cannot be created: missing connection ID. ` +
|
|
50
46
|
`Log in to the Vellum platform or switch to using your own OAuth app.`,
|
|
51
47
|
);
|
|
52
48
|
}
|
|
@@ -55,14 +51,12 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
55
51
|
this.providerKey = options.providerKey;
|
|
56
52
|
this.externalId = options.externalId;
|
|
57
53
|
this.accountInfo = options.accountInfo;
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
this.apiKey = options.apiKey;
|
|
54
|
+
this.client = options.client;
|
|
55
|
+
this.connectionId = options.connectionId;
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
64
|
-
const
|
|
65
|
-
const proxyUrl = `${this.platformBaseUrl}/v1/assistants/${this.assistantId}/external-provider-proxy/${providerSlug}/`;
|
|
59
|
+
const proxyPath = `/v1/assistants/${this.client.platformAssistantId}/external-provider-proxy/${this.connectionId}/`;
|
|
66
60
|
|
|
67
61
|
const body: Record<string, unknown> = {
|
|
68
62
|
request: {
|
|
@@ -75,10 +69,9 @@ export class PlatformOAuthConnection implements OAuthConnection {
|
|
|
75
69
|
},
|
|
76
70
|
};
|
|
77
71
|
|
|
78
|
-
const response = await fetch(
|
|
72
|
+
const response = await this.client.fetch(proxyPath, {
|
|
79
73
|
method: "POST",
|
|
80
74
|
headers: {
|
|
81
|
-
Authorization: `Api-Key ${this.apiKey}`,
|
|
82
75
|
"Content-Type": "application/json",
|
|
83
76
|
},
|
|
84
77
|
body: JSON.stringify(body),
|
|
@@ -2,7 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { getConfig } from "../config/loader.js";
|
|
4
4
|
import { getBundledSkillsDir } from "../config/skills.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
6
6
|
|
|
7
7
|
export interface DefaultRuleTemplate {
|
|
8
8
|
id: string;
|
|
@@ -116,7 +116,7 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
116
116
|
// Workspace prompt files — the agent should always be able to read, edit,
|
|
117
117
|
// and write these without prompting. Also allow `rm BOOTSTRAP.md` so the
|
|
118
118
|
// agent can delete it at the end of the onboarding ritual.
|
|
119
|
-
const workspaceDir =
|
|
119
|
+
const workspaceDir = getWorkspaceDir().replaceAll("\\", "/");
|
|
120
120
|
const WORKSPACE_PROMPT_FILES = [
|
|
121
121
|
"IDENTITY.md",
|
|
122
122
|
"USER.md",
|
|
@@ -163,7 +163,7 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
|
|
|
163
163
|
// Skill source directories — writing or editing skill source files should
|
|
164
164
|
// require explicit user approval so a compromised agent loop cannot silently
|
|
165
165
|
// modify skill code to escalate privileges.
|
|
166
|
-
const managedSkillsDir = join(
|
|
166
|
+
const managedSkillsDir = join(getWorkspaceDir(), "skills").replaceAll(
|
|
167
167
|
"\\",
|
|
168
168
|
"/",
|
|
169
169
|
);
|