@vellumai/credential-executor 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/command-executor.test.ts +2 -2
- package/src/__tests__/http-executor.test.ts +4 -4
- package/src/__tests__/local-materializers.test.ts +32 -32
- package/src/__tests__/managed-lazy-getters.test.ts +61 -12
- package/src/__tests__/managed-materializers.test.ts +1 -1
- package/src/managed-lazy-getters.ts +18 -6
- package/src/managed-main.ts +18 -10
- package/src/server.ts +8 -8
|
@@ -71,19 +71,19 @@ describe("handles", () => {
|
|
|
71
71
|
|
|
72
72
|
describe("localOAuthHandle", () => {
|
|
73
73
|
test("constructs the expected format", () => {
|
|
74
|
-
expect(localOAuthHandle("
|
|
75
|
-
"local_oauth:
|
|
74
|
+
expect(localOAuthHandle("google", "conn-123")).toBe(
|
|
75
|
+
"local_oauth:google/conn-123",
|
|
76
76
|
);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test("roundtrips
|
|
80
|
-
const raw = localOAuthHandle("
|
|
79
|
+
test("roundtrips through parseHandle", () => {
|
|
80
|
+
const raw = localOAuthHandle("slack", "conn-abc");
|
|
81
81
|
const result = parseHandle(raw);
|
|
82
82
|
expect(result.ok).toBe(true);
|
|
83
83
|
if (!result.ok) return;
|
|
84
84
|
expect(result.handle.type).toBe(HandleType.LocalOAuth);
|
|
85
85
|
if (result.handle.type !== HandleType.LocalOAuth) return;
|
|
86
|
-
expect(result.handle.providerKey).toBe("
|
|
86
|
+
expect(result.handle.providerKey).toBe("slack");
|
|
87
87
|
expect(result.handle.connectionId).toBe("conn-abc");
|
|
88
88
|
});
|
|
89
89
|
});
|
|
@@ -133,7 +133,7 @@ describe("handles", () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test("rejects local_oauth with no slash", () => {
|
|
136
|
-
const result = parseHandle("local_oauth:
|
|
136
|
+
const result = parseHandle("local_oauth:google");
|
|
137
137
|
expect(result.ok).toBe(false);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -150,7 +150,7 @@ describe("handles", () => {
|
|
|
150
150
|
).not.toThrow();
|
|
151
151
|
expect(() =>
|
|
152
152
|
CredentialHandleSchema.parse(
|
|
153
|
-
"local_oauth:
|
|
153
|
+
"local_oauth:google/conn-1",
|
|
154
154
|
),
|
|
155
155
|
).not.toThrow();
|
|
156
156
|
expect(() =>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 2. **Local OAuth** — references a locally persisted OAuth connection.
|
|
15
15
|
* Format: `local_oauth:<providerKey>/<connectionId>` where providerKey
|
|
16
|
-
*
|
|
16
|
+
* is the bare provider name (e.g. `google`).
|
|
17
17
|
*
|
|
18
18
|
* 3. **Managed OAuth** — references an OAuth connection managed by the
|
|
19
19
|
* platform. Format: `platform_oauth:<connectionId>` where connectionId
|
|
@@ -50,7 +50,7 @@ export interface LocalStaticHandle {
|
|
|
50
50
|
|
|
51
51
|
export interface LocalOAuthHandle {
|
|
52
52
|
type: typeof HandleType.LocalOAuth;
|
|
53
|
-
/** Provider key (e.g. "
|
|
53
|
+
/** Provider key (e.g. "google", "slack"). */
|
|
54
54
|
providerKey: string;
|
|
55
55
|
/** Connection identifier. */
|
|
56
56
|
connectionId: string;
|
|
@@ -146,8 +146,9 @@ export function parseHandle(raw: string): ParseHandleResult {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
case HandleType.LocalOAuth: {
|
|
149
|
-
// providerKey
|
|
150
|
-
//
|
|
149
|
+
// providerKey is typically a bare name (e.g. "google"), but legacy handles
|
|
150
|
+
// may contain a colon (e.g. "integration:google"), so we split on the
|
|
151
|
+
// *last* "/" to separate providerKey from connectionId.
|
|
151
152
|
const lastSlashIdx = rest.lastIndexOf("/");
|
|
152
153
|
if (
|
|
153
154
|
lastSlashIdx === -1 ||
|
|
@@ -33,6 +33,13 @@ export const HandshakeRequestSchema = z.object({
|
|
|
33
33
|
* can use it for platform credential materialisation.
|
|
34
34
|
*/
|
|
35
35
|
assistantApiKey: z.string().optional(),
|
|
36
|
+
/**
|
|
37
|
+
* Optional platform assistant ID passed from the assistant runtime.
|
|
38
|
+
* In managed (sidecar) mode with warm pools, the PLATFORM_ASSISTANT_ID
|
|
39
|
+
* env var may not be set at CES startup. The assistant forwards it here
|
|
40
|
+
* so CES can use it for platform credential materialisation.
|
|
41
|
+
*/
|
|
42
|
+
assistantId: z.string().optional(),
|
|
36
43
|
});
|
|
37
44
|
export type HandshakeRequest = z.infer<typeof HandshakeRequestSchema>;
|
|
38
45
|
|
|
@@ -422,6 +422,11 @@ export type ListAuditRecordsResponse = z.infer<
|
|
|
422
422
|
export const UpdateManagedCredentialSchema = z.object({
|
|
423
423
|
/** The assistant API key to push to CES for platform credential materialization. */
|
|
424
424
|
assistantApiKey: z.string(), // nosemgrep: not-a-secret
|
|
425
|
+
/**
|
|
426
|
+
* Optional platform assistant ID. In warm-pool mode the ID may not be
|
|
427
|
+
* available at CES startup; the assistant pushes it here once provisioned.
|
|
428
|
+
*/
|
|
429
|
+
assistantId: z.string().optional(),
|
|
425
430
|
});
|
|
426
431
|
export type UpdateManagedCredential = z.infer<
|
|
427
432
|
typeof UpdateManagedCredentialSchema
|
|
@@ -98,7 +98,7 @@ export interface SecureKeyBackend {
|
|
|
98
98
|
export interface OAuthConnectionRecord {
|
|
99
99
|
/** Unique identifier for this connection. */
|
|
100
100
|
id: string;
|
|
101
|
-
/** Provider key (e.g. "
|
|
101
|
+
/** Provider key (e.g. "google", "slack"). */
|
|
102
102
|
providerKey: string;
|
|
103
103
|
/** Account identifier (e.g. email address). */
|
|
104
104
|
accountInfo: string | null;
|
package/package.json
CHANGED
|
@@ -1482,7 +1482,7 @@ describe("executeAuthenticatedCommand — integration: local OAuth", () => {
|
|
|
1482
1482
|
});
|
|
1483
1483
|
addCommandGrant(
|
|
1484
1484
|
deps.persistentStore,
|
|
1485
|
-
"local_oauth:
|
|
1485
|
+
"local_oauth:google/conn-123",
|
|
1486
1486
|
digest,
|
|
1487
1487
|
"list",
|
|
1488
1488
|
);
|
|
@@ -1490,7 +1490,7 @@ describe("executeAuthenticatedCommand — integration: local OAuth", () => {
|
|
|
1490
1490
|
const request: ExecuteCommandRequest = {
|
|
1491
1491
|
bundleDigest: digest,
|
|
1492
1492
|
profileName: "list",
|
|
1493
|
-
credentialHandle: "local_oauth:
|
|
1493
|
+
credentialHandle: "local_oauth:google/conn-123",
|
|
1494
1494
|
argv: ["list", "--format", "json"],
|
|
1495
1495
|
workspaceDir: testWorkspaceDir,
|
|
1496
1496
|
purpose: "OAuth pipeline test",
|
|
@@ -102,7 +102,7 @@ function buildOAuthConnection(
|
|
|
102
102
|
): OAuthConnectionRecord {
|
|
103
103
|
return {
|
|
104
104
|
id: overrides.id ?? "conn-uuid-1",
|
|
105
|
-
providerKey: overrides.providerKey ?? "
|
|
105
|
+
providerKey: overrides.providerKey ?? "google",
|
|
106
106
|
accountInfo: overrides.accountInfo ?? "user@example.com",
|
|
107
107
|
grantedScopes: overrides.grantedScopes ?? ["openid", "email"],
|
|
108
108
|
accessTokenPath: overrides.accessTokenPath ??
|
|
@@ -434,7 +434,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
434
434
|
beforeEach(() => {
|
|
435
435
|
connection = buildOAuthConnection({
|
|
436
436
|
id: "conn-google-1",
|
|
437
|
-
providerKey: "
|
|
437
|
+
providerKey: "google",
|
|
438
438
|
expiresAt: Date.now() + 3600000, // valid for 1 hour
|
|
439
439
|
});
|
|
440
440
|
|
|
@@ -451,7 +451,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
451
451
|
});
|
|
452
452
|
|
|
453
453
|
test("successful OAuth request with matching grant", async () => {
|
|
454
|
-
const handle = localOAuthHandle("
|
|
454
|
+
const handle = localOAuthHandle("google", "conn-google-1");
|
|
455
455
|
|
|
456
456
|
fixture.persistentStore.add({
|
|
457
457
|
id: "grant-google-calendar",
|
|
@@ -492,7 +492,7 @@ describe("HTTP executor: local OAuth", () => {
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
test("OAuth token scrubbed from response body", async () => {
|
|
495
|
-
const handle = localOAuthHandle("
|
|
495
|
+
const handle = localOAuthHandle("google", "conn-google-1");
|
|
496
496
|
|
|
497
497
|
fixture.persistentStore.add({
|
|
498
498
|
id: "grant-google-debug",
|
|
@@ -98,7 +98,7 @@ function buildOAuthConnection(
|
|
|
98
98
|
): OAuthConnectionRecord {
|
|
99
99
|
return {
|
|
100
100
|
id: overrides.id ?? "conn-uuid-1",
|
|
101
|
-
providerKey: overrides.providerKey ?? "
|
|
101
|
+
providerKey: overrides.providerKey ?? "google",
|
|
102
102
|
accountInfo: overrides.accountInfo ?? "user@example.com",
|
|
103
103
|
grantedScopes: overrides.grantedScopes ?? ["openid", "email"],
|
|
104
104
|
accessTokenPath: overrides.accessTokenPath ??
|
|
@@ -227,10 +227,10 @@ describe("local OAuth subject resolution", () => {
|
|
|
227
227
|
test("resolves a valid OAuth handle to an OAuth subject", () => {
|
|
228
228
|
const conn = buildOAuthConnection({
|
|
229
229
|
id: "conn-abc",
|
|
230
|
-
providerKey: "
|
|
230
|
+
providerKey: "google",
|
|
231
231
|
});
|
|
232
232
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
233
|
-
const handle = localOAuthHandle("
|
|
233
|
+
const handle = localOAuthHandle("google", "conn-abc");
|
|
234
234
|
|
|
235
235
|
const result = resolveLocalSubject(handle, deps);
|
|
236
236
|
|
|
@@ -239,12 +239,12 @@ describe("local OAuth subject resolution", () => {
|
|
|
239
239
|
expect(result.subject.type).toBe(HandleType.LocalOAuth);
|
|
240
240
|
if (result.subject.type !== HandleType.LocalOAuth) return;
|
|
241
241
|
expect(result.subject.connection.id).toBe("conn-abc");
|
|
242
|
-
expect(result.subject.connection.providerKey).toBe("
|
|
242
|
+
expect(result.subject.connection.providerKey).toBe("google");
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
test("fails when the connection does not exist", () => {
|
|
246
246
|
const deps = createResolverDeps({ oauthConnections: [] });
|
|
247
|
-
const handle = localOAuthHandle("
|
|
247
|
+
const handle = localOAuthHandle("google", "missing-conn");
|
|
248
248
|
|
|
249
249
|
const result = resolveLocalSubject(handle, deps);
|
|
250
250
|
|
|
@@ -257,19 +257,19 @@ describe("local OAuth subject resolution", () => {
|
|
|
257
257
|
test("fails when provider key in handle does not match connection", () => {
|
|
258
258
|
const conn = buildOAuthConnection({
|
|
259
259
|
id: "conn-xyz",
|
|
260
|
-
providerKey: "
|
|
260
|
+
providerKey: "slack",
|
|
261
261
|
});
|
|
262
262
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
263
263
|
// Handle says google but connection is slack
|
|
264
|
-
const handle = localOAuthHandle("
|
|
264
|
+
const handle = localOAuthHandle("google", "conn-xyz");
|
|
265
265
|
|
|
266
266
|
const result = resolveLocalSubject(handle, deps);
|
|
267
267
|
|
|
268
268
|
expect(result.ok).toBe(false);
|
|
269
269
|
if (result.ok) return;
|
|
270
270
|
expect(result.error).toMatch(/providerKey/);
|
|
271
|
-
expect(result.error).toMatch(/
|
|
272
|
-
expect(result.error).toMatch(/
|
|
271
|
+
expect(result.error).toMatch(/slack/);
|
|
272
|
+
expect(result.error).toMatch(/google/);
|
|
273
273
|
});
|
|
274
274
|
});
|
|
275
275
|
|
|
@@ -378,13 +378,13 @@ describe("OAuth token materialisation", () => {
|
|
|
378
378
|
test("materialises a valid non-expired access token", async () => {
|
|
379
379
|
const conn = buildOAuthConnection({
|
|
380
380
|
id: "conn-1",
|
|
381
|
-
providerKey: "
|
|
381
|
+
providerKey: "google",
|
|
382
382
|
// Token expires in the future (1 hour from now)
|
|
383
383
|
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
384
384
|
hasRefreshToken: true,
|
|
385
385
|
});
|
|
386
386
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
387
|
-
const handle = localOAuthHandle("
|
|
387
|
+
const handle = localOAuthHandle("google", "conn-1");
|
|
388
388
|
|
|
389
389
|
const resolved = resolveLocalSubject(handle, deps);
|
|
390
390
|
expect(resolved.ok).toBe(true);
|
|
@@ -408,11 +408,11 @@ describe("OAuth token materialisation", () => {
|
|
|
408
408
|
test("fails when no access token is stored (disconnected connection)", async () => {
|
|
409
409
|
const conn = buildOAuthConnection({
|
|
410
410
|
id: "conn-disconnected",
|
|
411
|
-
providerKey: "
|
|
411
|
+
providerKey: "slack",
|
|
412
412
|
hasRefreshToken: false,
|
|
413
413
|
});
|
|
414
414
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
415
|
-
const handle = localOAuthHandle("
|
|
415
|
+
const handle = localOAuthHandle("slack", "conn-disconnected");
|
|
416
416
|
|
|
417
417
|
const resolved = resolveLocalSubject(handle, deps);
|
|
418
418
|
expect(resolved.ok).toBe(true);
|
|
@@ -435,14 +435,14 @@ describe("OAuth token materialisation", () => {
|
|
|
435
435
|
test("fails when token is expired and hasRefreshToken is false", async () => {
|
|
436
436
|
const conn = buildOAuthConnection({
|
|
437
437
|
id: "conn-expired-no-refresh",
|
|
438
|
-
providerKey: "
|
|
438
|
+
providerKey: "google",
|
|
439
439
|
// Token expired 10 minutes ago
|
|
440
440
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
441
441
|
hasRefreshToken: false,
|
|
442
442
|
});
|
|
443
443
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
444
444
|
const handle = localOAuthHandle(
|
|
445
|
-
"
|
|
445
|
+
"google",
|
|
446
446
|
"conn-expired-no-refresh",
|
|
447
447
|
);
|
|
448
448
|
|
|
@@ -469,12 +469,12 @@ describe("OAuth token materialisation", () => {
|
|
|
469
469
|
test("materialises a token with null expiresAt (no expiry info)", async () => {
|
|
470
470
|
const conn = buildOAuthConnection({
|
|
471
471
|
id: "conn-noexpiry",
|
|
472
|
-
providerKey: "
|
|
472
|
+
providerKey: "github",
|
|
473
473
|
expiresAt: null,
|
|
474
474
|
hasRefreshToken: false,
|
|
475
475
|
});
|
|
476
476
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
477
|
-
const handle = localOAuthHandle("
|
|
477
|
+
const handle = localOAuthHandle("github", "conn-noexpiry");
|
|
478
478
|
|
|
479
479
|
const resolved = resolveLocalSubject(handle, deps);
|
|
480
480
|
expect(resolved.ok).toBe(true);
|
|
@@ -504,13 +504,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
504
504
|
test("refreshes an expired token and returns the new access token", async () => {
|
|
505
505
|
const conn = buildOAuthConnection({
|
|
506
506
|
id: "conn-expired",
|
|
507
|
-
providerKey: "
|
|
507
|
+
providerKey: "google",
|
|
508
508
|
// Token expired 10 minutes ago
|
|
509
509
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
510
510
|
hasRefreshToken: true,
|
|
511
511
|
});
|
|
512
512
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
513
|
-
const handle = localOAuthHandle("
|
|
513
|
+
const handle = localOAuthHandle("google", "conn-expired");
|
|
514
514
|
|
|
515
515
|
const resolved = resolveLocalSubject(handle, deps);
|
|
516
516
|
expect(resolved.ok).toBe(true);
|
|
@@ -545,12 +545,12 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
545
545
|
test("fails when token is expired but no refresh function is configured", async () => {
|
|
546
546
|
const conn = buildOAuthConnection({
|
|
547
547
|
id: "conn-no-refresh-fn",
|
|
548
|
-
providerKey: "
|
|
548
|
+
providerKey: "google",
|
|
549
549
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
550
550
|
hasRefreshToken: true,
|
|
551
551
|
});
|
|
552
552
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
553
|
-
const handle = localOAuthHandle("
|
|
553
|
+
const handle = localOAuthHandle("google", "conn-no-refresh-fn");
|
|
554
554
|
|
|
555
555
|
const resolved = resolveLocalSubject(handle, deps);
|
|
556
556
|
expect(resolved.ok).toBe(true);
|
|
@@ -577,13 +577,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
577
577
|
test("fails when token is expired and no refresh token is stored", async () => {
|
|
578
578
|
const conn = buildOAuthConnection({
|
|
579
579
|
id: "conn-no-stored-refresh",
|
|
580
|
-
providerKey: "
|
|
580
|
+
providerKey: "google",
|
|
581
581
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
582
582
|
hasRefreshToken: true,
|
|
583
583
|
});
|
|
584
584
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
585
585
|
const handle = localOAuthHandle(
|
|
586
|
-
"
|
|
586
|
+
"google",
|
|
587
587
|
"conn-no-stored-refresh",
|
|
588
588
|
);
|
|
589
589
|
|
|
@@ -617,13 +617,13 @@ describe("OAuth refresh-on-expiry", () => {
|
|
|
617
617
|
test("fails when refresh function returns a failure result", async () => {
|
|
618
618
|
const conn = buildOAuthConnection({
|
|
619
619
|
id: "conn-refresh-fail",
|
|
620
|
-
providerKey: "
|
|
620
|
+
providerKey: "google",
|
|
621
621
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
622
622
|
hasRefreshToken: true,
|
|
623
623
|
});
|
|
624
624
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
625
625
|
const handle = localOAuthHandle(
|
|
626
|
-
"
|
|
626
|
+
"google",
|
|
627
627
|
"conn-refresh-fail",
|
|
628
628
|
);
|
|
629
629
|
|
|
@@ -662,12 +662,12 @@ describe("refresh circuit breaker", () => {
|
|
|
662
662
|
test("trips after repeated refresh failures and returns error", async () => {
|
|
663
663
|
const conn = buildOAuthConnection({
|
|
664
664
|
id: "conn-breaker",
|
|
665
|
-
providerKey: "
|
|
665
|
+
providerKey: "google",
|
|
666
666
|
expiresAt: Date.now() - 10 * 60 * 1000,
|
|
667
667
|
hasRefreshToken: true,
|
|
668
668
|
});
|
|
669
669
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
670
|
-
const handle = localOAuthHandle("
|
|
670
|
+
const handle = localOAuthHandle("google", "conn-breaker");
|
|
671
671
|
|
|
672
672
|
const resolved = resolveLocalSubject(handle, deps);
|
|
673
673
|
expect(resolved.ok).toBe(true);
|
|
@@ -724,7 +724,7 @@ describe("deterministic fail-closed behaviour", () => {
|
|
|
724
724
|
|
|
725
725
|
// Missing OAuth connection
|
|
726
726
|
const r3 = resolveLocalSubject(
|
|
727
|
-
localOAuthHandle("
|
|
727
|
+
localOAuthHandle("x", "missing-conn"),
|
|
728
728
|
deps,
|
|
729
729
|
);
|
|
730
730
|
expect(r3.ok).toBe(false);
|
|
@@ -765,10 +765,10 @@ describe("deterministic fail-closed behaviour", () => {
|
|
|
765
765
|
test("OAuth disconnection detected before any refresh attempt", async () => {
|
|
766
766
|
const conn = buildOAuthConnection({
|
|
767
767
|
id: "conn-disco",
|
|
768
|
-
providerKey: "
|
|
768
|
+
providerKey: "slack",
|
|
769
769
|
});
|
|
770
770
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
771
|
-
const handle = localOAuthHandle("
|
|
771
|
+
const handle = localOAuthHandle("slack", "conn-disco");
|
|
772
772
|
|
|
773
773
|
const resolved = resolveLocalSubject(handle, deps);
|
|
774
774
|
expect(resolved.ok).toBe(true);
|
|
@@ -831,12 +831,12 @@ describe("end-to-end local materialisation", () => {
|
|
|
831
831
|
test("full pipeline: resolve OAuth handle -> materialise token", async () => {
|
|
832
832
|
const conn = buildOAuthConnection({
|
|
833
833
|
id: "conn-e2e",
|
|
834
|
-
providerKey: "
|
|
834
|
+
providerKey: "linear",
|
|
835
835
|
expiresAt: Date.now() + 3600 * 1000,
|
|
836
836
|
hasRefreshToken: true,
|
|
837
837
|
});
|
|
838
838
|
const deps = createResolverDeps({ oauthConnections: [conn] });
|
|
839
|
-
const handle = localOAuthHandle("
|
|
839
|
+
const handle = localOAuthHandle("linear", "conn-e2e");
|
|
840
840
|
|
|
841
841
|
// Step 1: Resolve
|
|
842
842
|
const resolved = resolveLocalSubject(handle, deps);
|
|
@@ -11,6 +11,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
11
11
|
import {
|
|
12
12
|
buildLazyGetters,
|
|
13
13
|
type ApiKeyRef,
|
|
14
|
+
type AssistantIdRef,
|
|
14
15
|
} from "../managed-lazy-getters.js";
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -20,9 +21,10 @@ import {
|
|
|
20
21
|
describe("managed lazy getters — before API key arrives", () => {
|
|
21
22
|
test("apiKeyRef starts empty and managed subject options are undefined", () => {
|
|
22
23
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
24
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
23
25
|
const { getManagedSubjectOptions } = buildLazyGetters({
|
|
24
26
|
platformBaseUrl: "https://api.vellum.ai",
|
|
25
|
-
|
|
27
|
+
assistantIdRef,
|
|
26
28
|
apiKeyRef,
|
|
27
29
|
});
|
|
28
30
|
|
|
@@ -32,9 +34,10 @@ describe("managed lazy getters — before API key arrives", () => {
|
|
|
32
34
|
|
|
33
35
|
test("apiKeyRef starts empty and managed materializer options are undefined", () => {
|
|
34
36
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
37
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
35
38
|
const { getManagedMaterializerOptions } = buildLazyGetters({
|
|
36
39
|
platformBaseUrl: "https://api.vellum.ai",
|
|
37
|
-
|
|
40
|
+
assistantIdRef,
|
|
38
41
|
apiKeyRef,
|
|
39
42
|
});
|
|
40
43
|
|
|
@@ -43,9 +46,10 @@ describe("managed lazy getters — before API key arrives", () => {
|
|
|
43
46
|
|
|
44
47
|
test("getAssistantApiKey returns empty string when ref is empty and no env var", () => {
|
|
45
48
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
49
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
46
50
|
const { getAssistantApiKey } = buildLazyGetters({
|
|
47
51
|
platformBaseUrl: "https://api.vellum.ai",
|
|
48
|
-
|
|
52
|
+
assistantIdRef,
|
|
49
53
|
apiKeyRef,
|
|
50
54
|
});
|
|
51
55
|
|
|
@@ -60,9 +64,10 @@ describe("managed lazy getters — before API key arrives", () => {
|
|
|
60
64
|
describe("managed lazy getters — after API key arrives via handshake", () => {
|
|
61
65
|
test("setting apiKeyRef.current enables managed subject options", () => {
|
|
62
66
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
67
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
63
68
|
const { getManagedSubjectOptions } = buildLazyGetters({
|
|
64
69
|
platformBaseUrl: "https://api.vellum.ai",
|
|
65
|
-
|
|
70
|
+
assistantIdRef,
|
|
66
71
|
apiKeyRef,
|
|
67
72
|
});
|
|
68
73
|
|
|
@@ -79,9 +84,10 @@ describe("managed lazy getters — after API key arrives via handshake", () => {
|
|
|
79
84
|
|
|
80
85
|
test("setting apiKeyRef.current enables managed materializer options", () => {
|
|
81
86
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
87
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
82
88
|
const { getManagedMaterializerOptions } = buildLazyGetters({
|
|
83
89
|
platformBaseUrl: "https://api.vellum.ai",
|
|
84
|
-
|
|
90
|
+
assistantIdRef,
|
|
85
91
|
apiKeyRef,
|
|
86
92
|
});
|
|
87
93
|
|
|
@@ -98,10 +104,11 @@ describe("managed lazy getters — after API key arrives via handshake", () => {
|
|
|
98
104
|
|
|
99
105
|
test("returned options contain the exact key from the ref (not a stale copy)", () => {
|
|
100
106
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
107
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
101
108
|
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
102
109
|
buildLazyGetters({
|
|
103
110
|
platformBaseUrl: "https://api.vellum.ai",
|
|
104
|
-
|
|
111
|
+
assistantIdRef,
|
|
105
112
|
apiKeyRef,
|
|
106
113
|
});
|
|
107
114
|
|
|
@@ -122,11 +129,12 @@ describe("managed lazy getters — after API key arrives via handshake", () => {
|
|
|
122
129
|
describe("managed lazy getters — lazy resolution timing", () => {
|
|
123
130
|
test("handlers built before key arrives resolve the key at call time", () => {
|
|
124
131
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
132
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
125
133
|
|
|
126
134
|
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
127
135
|
buildLazyGetters({
|
|
128
136
|
platformBaseUrl: "https://api.vellum.ai",
|
|
129
|
-
|
|
137
|
+
assistantIdRef,
|
|
130
138
|
apiKeyRef,
|
|
131
139
|
});
|
|
132
140
|
|
|
@@ -147,10 +155,11 @@ describe("managed lazy getters — lazy resolution timing", () => {
|
|
|
147
155
|
|
|
148
156
|
test("deps object with getter properties resolves lazily (mirrors httpDeps pattern)", () => {
|
|
149
157
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
158
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
150
159
|
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
151
160
|
buildLazyGetters({
|
|
152
161
|
platformBaseUrl: "https://api.vellum.ai",
|
|
153
|
-
|
|
162
|
+
assistantIdRef,
|
|
154
163
|
apiKeyRef,
|
|
155
164
|
});
|
|
156
165
|
|
|
@@ -182,9 +191,10 @@ describe("managed lazy getters — lazy resolution timing", () => {
|
|
|
182
191
|
|
|
183
192
|
test("env var fallback is used when ref is empty", () => {
|
|
184
193
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
194
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
185
195
|
const { getAssistantApiKey, getManagedSubjectOptions } = buildLazyGetters({
|
|
186
196
|
platformBaseUrl: "https://api.vellum.ai",
|
|
187
|
-
|
|
197
|
+
assistantIdRef,
|
|
188
198
|
apiKeyRef,
|
|
189
199
|
envApiKey: "vak_env_fallback",
|
|
190
200
|
});
|
|
@@ -198,9 +208,10 @@ describe("managed lazy getters — lazy resolution timing", () => {
|
|
|
198
208
|
|
|
199
209
|
test("handshake-provided key takes precedence over env var", () => {
|
|
200
210
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
211
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
201
212
|
const { getAssistantApiKey } = buildLazyGetters({
|
|
202
213
|
platformBaseUrl: "https://api.vellum.ai",
|
|
203
|
-
|
|
214
|
+
assistantIdRef,
|
|
204
215
|
apiKeyRef,
|
|
205
216
|
envApiKey: "vak_env_key",
|
|
206
217
|
});
|
|
@@ -219,10 +230,11 @@ describe("managed lazy getters — lazy resolution timing", () => {
|
|
|
219
230
|
describe("managed lazy getters — missing platform config fields", () => {
|
|
220
231
|
test("missing platformBaseUrl returns undefined even with API key", () => {
|
|
221
232
|
const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
|
|
233
|
+
const assistantIdRef: AssistantIdRef = { current: "ast_abc123" };
|
|
222
234
|
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
223
235
|
buildLazyGetters({
|
|
224
236
|
platformBaseUrl: "",
|
|
225
|
-
|
|
237
|
+
assistantIdRef,
|
|
226
238
|
apiKeyRef,
|
|
227
239
|
});
|
|
228
240
|
|
|
@@ -232,14 +244,51 @@ describe("managed lazy getters — missing platform config fields", () => {
|
|
|
232
244
|
|
|
233
245
|
test("missing assistantId returns undefined even with API key", () => {
|
|
234
246
|
const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
|
|
247
|
+
const assistantIdRef: AssistantIdRef = { current: "" };
|
|
235
248
|
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
236
249
|
buildLazyGetters({
|
|
237
250
|
platformBaseUrl: "https://api.vellum.ai",
|
|
238
|
-
|
|
251
|
+
assistantIdRef,
|
|
239
252
|
apiKeyRef,
|
|
240
253
|
});
|
|
241
254
|
|
|
242
255
|
expect(getManagedSubjectOptions()).toBeUndefined();
|
|
243
256
|
expect(getManagedMaterializerOptions()).toBeUndefined();
|
|
244
257
|
});
|
|
258
|
+
|
|
259
|
+
test("assistantIdRef updated after build enables options (warm-pool scenario)", () => {
|
|
260
|
+
/**
|
|
261
|
+
* Verifies that updating assistantIdRef.current after buildLazyGetters
|
|
262
|
+
* makes previously-undefined options become defined — the core fix for
|
|
263
|
+
* warm-pool pods where PLATFORM_ASSISTANT_ID is empty at CES startup.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
// GIVEN an API key is available but assistant ID is empty (warm-pool startup)
|
|
267
|
+
const apiKeyRef: ApiKeyRef = { current: "vak_test_key" };
|
|
268
|
+
const assistantIdRef: AssistantIdRef = { current: "" };
|
|
269
|
+
const { getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
270
|
+
buildLazyGetters({
|
|
271
|
+
platformBaseUrl: "https://api.vellum.ai",
|
|
272
|
+
assistantIdRef,
|
|
273
|
+
apiKeyRef,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// WHEN options are checked before assistant ID arrives
|
|
277
|
+
// THEN they are undefined
|
|
278
|
+
expect(getManagedSubjectOptions()).toBeUndefined();
|
|
279
|
+
expect(getManagedMaterializerOptions()).toBeUndefined();
|
|
280
|
+
|
|
281
|
+
// WHEN the assistant ID arrives via handshake/RPC
|
|
282
|
+
assistantIdRef.current = "ast_provisioned_123";
|
|
283
|
+
|
|
284
|
+
// THEN the same getter functions now return valid options
|
|
285
|
+
const subOpts = getManagedSubjectOptions();
|
|
286
|
+
expect(subOpts).toBeDefined();
|
|
287
|
+
expect(subOpts!.assistantId).toBe("ast_provisioned_123");
|
|
288
|
+
expect(subOpts!.assistantApiKey).toBe("vak_test_key");
|
|
289
|
+
|
|
290
|
+
const matOpts = getManagedMaterializerOptions();
|
|
291
|
+
expect(matOpts).toBeDefined();
|
|
292
|
+
expect(matOpts!.assistantId).toBe("ast_provisioned_123");
|
|
293
|
+
});
|
|
245
294
|
});
|
|
@@ -264,7 +264,7 @@ describe("resolveManagedSubject", () => {
|
|
|
264
264
|
|
|
265
265
|
test("rejects a local_oauth handle", async () => {
|
|
266
266
|
const result = await resolveManagedSubject(
|
|
267
|
-
"local_oauth:
|
|
267
|
+
"local_oauth:google/conn_local1",
|
|
268
268
|
{
|
|
269
269
|
platformBaseUrl: TEST_PLATFORM_URL,
|
|
270
270
|
assistantApiKey: TEST_API_KEY,
|
|
@@ -22,9 +22,19 @@ export interface ApiKeyRef {
|
|
|
22
22
|
current: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Mutable reference to the platform assistant ID. For warm-pool pods the
|
|
27
|
+
* PLATFORM_ASSISTANT_ID env var is empty at startup; the assistant forwards
|
|
28
|
+
* the ID via the handshake or update_managed_credential RPC after
|
|
29
|
+
* provisioning, and `.current` is updated so lazy getters pick it up.
|
|
30
|
+
*/
|
|
31
|
+
export interface AssistantIdRef {
|
|
32
|
+
current: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export interface LazyGetterOptions {
|
|
26
36
|
platformBaseUrl: string;
|
|
27
|
-
|
|
37
|
+
assistantIdRef: AssistantIdRef;
|
|
28
38
|
apiKeyRef: ApiKeyRef;
|
|
29
39
|
envApiKey?: string;
|
|
30
40
|
}
|
|
@@ -43,22 +53,24 @@ export interface LazyGetters {
|
|
|
43
53
|
* (chicken-and-egg: key is provisioned after hatch).
|
|
44
54
|
*/
|
|
45
55
|
export function buildLazyGetters(opts: LazyGetterOptions): LazyGetters {
|
|
46
|
-
const { platformBaseUrl,
|
|
56
|
+
const { platformBaseUrl, assistantIdRef, apiKeyRef, envApiKey } = opts;
|
|
47
57
|
|
|
48
58
|
const getAssistantApiKey = (): string =>
|
|
49
59
|
apiKeyRef.current || envApiKey || "";
|
|
50
60
|
|
|
51
61
|
const getManagedSubjectOptions = (): ManagedSubjectResolverOptions | undefined => {
|
|
52
62
|
const key = getAssistantApiKey();
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
const id = assistantIdRef.current;
|
|
64
|
+
return platformBaseUrl && key && id
|
|
65
|
+
? { platformBaseUrl, assistantApiKey: key, assistantId: id }
|
|
55
66
|
: undefined;
|
|
56
67
|
};
|
|
57
68
|
|
|
58
69
|
const getManagedMaterializerOptions = (): ManagedMaterializerOptions | undefined => {
|
|
59
70
|
const key = getAssistantApiKey();
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
const id = assistantIdRef.current;
|
|
72
|
+
return platformBaseUrl && key && id
|
|
73
|
+
? { platformBaseUrl, assistantApiKey: key, assistantId: id }
|
|
62
74
|
: undefined;
|
|
63
75
|
};
|
|
64
76
|
|
package/src/managed-main.ts
CHANGED
|
@@ -55,7 +55,7 @@ import { buildCesEgressHooks } from "./commands/egress-hooks.js";
|
|
|
55
55
|
import { resolveManagedSubject } from "./subjects/managed.js";
|
|
56
56
|
import { materializeManagedToken } from "./materializers/managed-platform.js";
|
|
57
57
|
import { HandleType, parseHandle } from "@vellumai/ces-contracts";
|
|
58
|
-
import { buildLazyGetters, type ApiKeyRef } from "./managed-lazy-getters.js";
|
|
58
|
+
import { buildLazyGetters, type ApiKeyRef, type AssistantIdRef } from "./managed-lazy-getters.js";
|
|
59
59
|
import { MANAGED_LOCAL_STATIC_REJECTION_ERROR } from "./managed-errors.js";
|
|
60
60
|
import type { SecureKeyBackend } from "@vellumai/credential-storage";
|
|
61
61
|
import { createLocalSecureKeyBackend } from "./materializers/local-secure-key-backend.js";
|
|
@@ -91,7 +91,7 @@ function ensureDataDirs(): void {
|
|
|
91
91
|
// Build RPC handler registry (managed mode)
|
|
92
92
|
// ---------------------------------------------------------------------------
|
|
93
93
|
|
|
94
|
-
function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef, secureKeyBackend: SecureKeyBackend): RpcHandlerRegistry {
|
|
94
|
+
function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef, assistantIdRef: AssistantIdRef, secureKeyBackend: SecureKeyBackend): RpcHandlerRegistry {
|
|
95
95
|
// -- Grant stores ----------------------------------------------------------
|
|
96
96
|
const persistentGrantStore = new PersistentGrantStore(
|
|
97
97
|
getCesGrantsDir("managed"),
|
|
@@ -112,20 +112,19 @@ function buildHandlers(sessionIdRef: SessionIdRef, apiKeyRef: ApiKeyRef, secureK
|
|
|
112
112
|
// We use a lazy getter so the handshake-provided key takes effect even
|
|
113
113
|
// though handlers are built before the handshake completes.
|
|
114
114
|
const platformBaseUrl = process.env["VELLUM_PLATFORM_URL"] ?? "";
|
|
115
|
-
const assistantId = process.env["PLATFORM_ASSISTANT_ID"] ?? "";
|
|
116
115
|
|
|
117
116
|
const { getAssistantApiKey, getManagedSubjectOptions, getManagedMaterializerOptions } =
|
|
118
117
|
buildLazyGetters({
|
|
119
118
|
platformBaseUrl,
|
|
120
|
-
|
|
119
|
+
assistantIdRef,
|
|
121
120
|
apiKeyRef,
|
|
122
121
|
envApiKey: process.env["ASSISTANT_API_KEY"] || "",
|
|
123
122
|
});
|
|
124
123
|
|
|
125
|
-
if (!platformBaseUrl
|
|
124
|
+
if (!platformBaseUrl) {
|
|
126
125
|
warn(
|
|
127
|
-
"VELLUM_PLATFORM_URL
|
|
128
|
-
"Managed credential materialisation will depend on the handshake-provided
|
|
126
|
+
"VELLUM_PLATFORM_URL not set. " +
|
|
127
|
+
"Managed credential materialisation will depend on the handshake-provided values.",
|
|
129
128
|
);
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -570,7 +569,8 @@ async function main(): Promise<void> {
|
|
|
570
569
|
// are available to handlers at call time (after the handshake completes).
|
|
571
570
|
const sessionIdRef: SessionIdRef = { current: `ces-managed-${Date.now()}` };
|
|
572
571
|
const apiKeyRef: ApiKeyRef = { current: "" };
|
|
573
|
-
const
|
|
572
|
+
const assistantIdRef: AssistantIdRef = { current: process.env["PLATFORM_ASSISTANT_ID"] ?? "" };
|
|
573
|
+
const handlers = buildHandlers(sessionIdRef, apiKeyRef, assistantIdRef, secureKeyBackend);
|
|
574
574
|
|
|
575
575
|
const server = new CesRpcServer({
|
|
576
576
|
input: connection.readable,
|
|
@@ -585,16 +585,24 @@ async function main(): Promise<void> {
|
|
|
585
585
|
process.stderr.write(`[ces-managed] ERROR: ${msg} ${args.map(String).join(" ")}\n`),
|
|
586
586
|
},
|
|
587
587
|
signal: controller.signal,
|
|
588
|
-
onHandshakeComplete: (hsSessionId, hsApiKey) => {
|
|
588
|
+
onHandshakeComplete: (hsSessionId, hsApiKey, hsAssistantId) => {
|
|
589
589
|
sessionIdRef.current = hsSessionId;
|
|
590
590
|
if (hsApiKey) {
|
|
591
591
|
apiKeyRef.current = hsApiKey;
|
|
592
592
|
log(`Received assistant API key via handshake`);
|
|
593
593
|
}
|
|
594
|
+
if (hsAssistantId) {
|
|
595
|
+
assistantIdRef.current = hsAssistantId;
|
|
596
|
+
log(`Received assistant ID via handshake`);
|
|
597
|
+
}
|
|
594
598
|
},
|
|
595
|
-
onApiKeyUpdate: (newKey) => {
|
|
599
|
+
onApiKeyUpdate: (newKey, newAssistantId) => {
|
|
596
600
|
apiKeyRef.current = newKey;
|
|
597
601
|
log(`Assistant API key updated via RPC`);
|
|
602
|
+
if (newAssistantId) {
|
|
603
|
+
assistantIdRef.current = newAssistantId;
|
|
604
|
+
log(`Assistant ID updated via RPC`);
|
|
605
|
+
}
|
|
598
606
|
},
|
|
599
607
|
});
|
|
600
608
|
|
package/src/server.ts
CHANGED
|
@@ -88,10 +88,10 @@ export interface CesServerOptions {
|
|
|
88
88
|
logger?: Pick<Console, "log" | "warn" | "error">;
|
|
89
89
|
/** Optional abort signal to shut down the server. */
|
|
90
90
|
signal?: AbortSignal;
|
|
91
|
-
/** Callback invoked when the handshake completes with the negotiated session ID and optional API key. */
|
|
92
|
-
onHandshakeComplete?: (sessionId: string, assistantApiKey?: string) => void;
|
|
93
|
-
/** Callback invoked when the assistant pushes an updated API key after hatch. */
|
|
94
|
-
onApiKeyUpdate?: (assistantApiKey: string) => void;
|
|
91
|
+
/** Callback invoked when the handshake completes with the negotiated session ID and optional API key / assistant ID. */
|
|
92
|
+
onHandshakeComplete?: (sessionId: string, assistantApiKey?: string, assistantId?: string) => void;
|
|
93
|
+
/** Callback invoked when the assistant pushes an updated API key (and optionally assistant ID) after hatch. */
|
|
94
|
+
onApiKeyUpdate?: (assistantApiKey: string, assistantId?: string) => void;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// ---------------------------------------------------------------------------
|
|
@@ -104,7 +104,7 @@ export class CesRpcServer {
|
|
|
104
104
|
private readonly handlers: RpcHandlerRegistry;
|
|
105
105
|
private readonly logger: Pick<Console, "log" | "warn" | "error">;
|
|
106
106
|
private readonly signal?: AbortSignal;
|
|
107
|
-
private readonly onHandshakeComplete?: (sessionId: string, assistantApiKey?: string) => void;
|
|
107
|
+
private readonly onHandshakeComplete?: (sessionId: string, assistantApiKey?: string, assistantId?: string) => void;
|
|
108
108
|
|
|
109
109
|
private handshakeComplete = false;
|
|
110
110
|
private sessionId: string | null = null;
|
|
@@ -123,8 +123,8 @@ export class CesRpcServer {
|
|
|
123
123
|
if (options.onApiKeyUpdate) {
|
|
124
124
|
const onUpdate = options.onApiKeyUpdate;
|
|
125
125
|
this.handlers[CesRpcMethod.UpdateManagedCredential] = (request: unknown) => {
|
|
126
|
-
const { assistantApiKey } = request as { assistantApiKey: string };
|
|
127
|
-
onUpdate(assistantApiKey);
|
|
126
|
+
const { assistantApiKey, assistantId } = request as { assistantApiKey: string; assistantId?: string };
|
|
127
|
+
onUpdate(assistantApiKey, assistantId);
|
|
128
128
|
return { updated: true };
|
|
129
129
|
};
|
|
130
130
|
}
|
|
@@ -267,7 +267,7 @@ export class CesRpcServer {
|
|
|
267
267
|
this.handshakeComplete = true;
|
|
268
268
|
this.sessionId = req.sessionId;
|
|
269
269
|
this.logger.log(`[ces-server] Handshake accepted for session ${req.sessionId}`);
|
|
270
|
-
this.onHandshakeComplete?.(req.sessionId, req.assistantApiKey);
|
|
270
|
+
this.onHandshakeComplete?.(req.sessionId, req.assistantApiKey, req.assistantId);
|
|
271
271
|
} else {
|
|
272
272
|
this.logger.warn(
|
|
273
273
|
`[ces-server] Handshake rejected: version mismatch (got ${req.protocolVersion}, expected ${CES_PROTOCOL_VERSION})`,
|