@vellumai/assistant 0.4.31 → 0.4.32
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 +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
- package/src/__tests__/anthropic-provider.test.ts +86 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/checker.test.ts +37 -98
- package/src/__tests__/commit-message-enrichment-service.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +6 -5
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +1 -19
- package/src/__tests__/followup-tools.test.ts +0 -30
- package/src/__tests__/gemini-provider.test.ts +79 -1
- package/src/__tests__/ipc-snapshot.test.ts +0 -4
- package/src/__tests__/managed-proxy-context.test.ts +163 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/openai-provider.test.ts +82 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
- package/src/__tests__/recurrence-types.test.ts +0 -15
- package/src/__tests__/schedule-tools.test.ts +28 -44
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/task-management-tools.test.ts +111 -0
- package/src/__tests__/twilio-config.test.ts +0 -3
- package/src/amazon/session.ts +30 -91
- package/src/calls/call-controller.ts +423 -571
- package/src/calls/finalize-call.ts +20 -0
- package/src/calls/relay-access-wait.ts +340 -0
- package/src/calls/relay-server.ts +267 -902
- package/src/calls/relay-setup-router.ts +307 -0
- package/src/calls/relay-verification.ts +280 -0
- package/src/calls/twilio-config.ts +1 -8
- package/src/calls/voice-control-protocol.ts +184 -0
- package/src/calls/voice-session-bridge.ts +1 -8
- package/src/config/agent-schema.ts +1 -1
- package/src/config/bundled-skills/followups/TOOLS.json +0 -4
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
- package/src/config/core-schema.ts +1 -1
- package/src/config/env.ts +0 -10
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +19 -0
- package/src/config/schema.ts +2 -2
- package/src/daemon/handlers/session-history.ts +398 -0
- package/src/daemon/handlers/session-user-message.ts +982 -0
- package/src/daemon/handlers/sessions.ts +9 -1338
- package/src/daemon/ipc-contract/sessions.ts +0 -6
- package/src/daemon/ipc-contract-inventory.json +0 -1
- package/src/daemon/lifecycle.ts +0 -29
- package/src/home-base/app-link-store.ts +0 -7
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-store.ts +0 -51
- package/src/memory/db-init.ts +5 -1
- package/src/memory/job-handlers/conflict.ts +24 -0
- package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
- package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/recall-cache.ts +0 -5
- package/src/memory/schema/calls.ts +274 -0
- package/src/memory/schema/contacts.ts +125 -0
- package/src/memory/schema/conversations.ts +129 -0
- package/src/memory/schema/guardian.ts +172 -0
- package/src/memory/schema/index.ts +8 -0
- package/src/memory/schema/infrastructure.ts +205 -0
- package/src/memory/schema/memory-core.ts +196 -0
- package/src/memory/schema/notifications.ts +191 -0
- package/src/memory/schema/tasks.ts +78 -0
- package/src/memory/schema.ts +1 -1385
- package/src/memory/slack-thread-store.ts +0 -69
- package/src/notifications/decisions-store.ts +2 -105
- package/src/notifications/deliveries-store.ts +0 -11
- package/src/notifications/preferences-store.ts +1 -58
- package/src/permissions/checker.ts +6 -17
- package/src/providers/anthropic/client.ts +6 -2
- package/src/providers/gemini/client.ts +13 -2
- package/src/providers/managed-proxy/constants.ts +55 -0
- package/src/providers/managed-proxy/context.ts +77 -0
- package/src/providers/registry.ts +112 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
- package/src/runtime/http-server.ts +83 -722
- package/src/runtime/http-types.ts +0 -16
- package/src/runtime/middleware/auth.ts +0 -12
- package/src/runtime/routes/app-routes.ts +33 -0
- package/src/runtime/routes/approval-routes.ts +32 -0
- package/src/runtime/routes/attachment-routes.ts +32 -0
- package/src/runtime/routes/brain-graph-routes.ts +27 -0
- package/src/runtime/routes/call-routes.ts +41 -0
- package/src/runtime/routes/channel-readiness-routes.ts +20 -0
- package/src/runtime/routes/channel-routes.ts +70 -0
- package/src/runtime/routes/contact-routes.ts +63 -0
- package/src/runtime/routes/conversation-attention-routes.ts +15 -0
- package/src/runtime/routes/conversation-routes.ts +190 -193
- package/src/runtime/routes/debug-routes.ts +15 -0
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/global-search-routes.ts +15 -0
- package/src/runtime/routes/guardian-action-routes.ts +22 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +20 -0
- package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
- package/src/runtime/routes/identity-routes.ts +20 -0
- package/src/runtime/routes/inbound-message-handler.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +6 -6
- package/src/runtime/routes/integration-routes.ts +83 -0
- package/src/runtime/routes/invite-routes.ts +31 -0
- package/src/runtime/routes/migration-routes.ts +30 -0
- package/src/runtime/routes/pairing-routes.ts +18 -0
- package/src/runtime/routes/secret-routes.ts +20 -0
- package/src/runtime/routes/surface-action-routes.ts +26 -0
- package/src/runtime/routes/trust-rules-routes.ts +31 -0
- package/src/runtime/routes/twilio-routes.ts +79 -0
- package/src/schedule/recurrence-types.ts +1 -11
- package/src/tools/followups/followup_create.ts +9 -3
- package/src/tools/mcp/mcp-tool-factory.ts +0 -17
- package/src/tools/memory/definitions.ts +0 -6
- package/src/tools/network/script-proxy/session-manager.ts +38 -3
- package/src/tools/schedule/create.ts +1 -3
- package/src/tools/schedule/update.ts +9 -6
- package/src/twitter/session.ts +29 -77
- package/src/util/cookie-session.ts +114 -0
- package/src/__tests__/conversation-routes.test.ts +0 -99
- package/src/__tests__/task-tools.test.ts +0 -685
- package/src/contacts/startup-migration.ts +0 -21
|
@@ -101,10 +101,6 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
101
101
|
type: "usage_request",
|
|
102
102
|
sessionId: "sess-001",
|
|
103
103
|
},
|
|
104
|
-
sandbox_set: {
|
|
105
|
-
type: "sandbox_set",
|
|
106
|
-
enabled: true,
|
|
107
|
-
},
|
|
108
104
|
cu_session_create: {
|
|
109
105
|
type: "cu_session_create",
|
|
110
106
|
sessionId: "cu-sess-001",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Mock logger to suppress output
|
|
4
|
+
mock.module("../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
// Mutable state for env and secure key stubs
|
|
12
|
+
let mockPlatformBaseUrl = "";
|
|
13
|
+
let mockAssistantApiKey: string | null = null;
|
|
14
|
+
|
|
15
|
+
mock.module("../config/env.js", () => ({
|
|
16
|
+
getPlatformBaseUrl: () => mockPlatformBaseUrl,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
20
|
+
getSecureKey: (key: string) => {
|
|
21
|
+
if (key === "credential:vellum:assistant_api_key") {
|
|
22
|
+
return mockAssistantApiKey;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
buildManagedBaseUrl,
|
|
30
|
+
hasManagedProxyPrereqs,
|
|
31
|
+
managedFallbackEnabledFor,
|
|
32
|
+
resolveManagedProxyContext,
|
|
33
|
+
} from "../providers/managed-proxy/context.js";
|
|
34
|
+
|
|
35
|
+
describe("resolveManagedProxyContext", () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
mockPlatformBaseUrl = "";
|
|
38
|
+
mockAssistantApiKey = null;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns disabled when platform URL is empty", () => {
|
|
42
|
+
mockPlatformBaseUrl = "";
|
|
43
|
+
mockAssistantApiKey = "sk-test-key";
|
|
44
|
+
|
|
45
|
+
const ctx = resolveManagedProxyContext();
|
|
46
|
+
expect(ctx.enabled).toBe(false);
|
|
47
|
+
expect(ctx.platformBaseUrl).toBe("");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("returns disabled when assistant API key is missing", () => {
|
|
51
|
+
mockPlatformBaseUrl = "https://platform.example.com";
|
|
52
|
+
mockAssistantApiKey = null;
|
|
53
|
+
|
|
54
|
+
const ctx = resolveManagedProxyContext();
|
|
55
|
+
expect(ctx.enabled).toBe(false);
|
|
56
|
+
expect(ctx.assistantApiKey).toBe("");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns disabled when both are missing", () => {
|
|
60
|
+
const ctx = resolveManagedProxyContext();
|
|
61
|
+
expect(ctx.enabled).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("returns enabled when both platform URL and API key are present", () => {
|
|
65
|
+
mockPlatformBaseUrl = "https://platform.example.com/";
|
|
66
|
+
mockAssistantApiKey = "sk-test-key";
|
|
67
|
+
|
|
68
|
+
const ctx = resolveManagedProxyContext();
|
|
69
|
+
expect(ctx.enabled).toBe(true);
|
|
70
|
+
expect(ctx.platformBaseUrl).toBe("https://platform.example.com");
|
|
71
|
+
expect(ctx.assistantApiKey).toBe("sk-test-key");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("strips trailing slashes from platform URL", () => {
|
|
75
|
+
mockPlatformBaseUrl = "https://platform.example.com///";
|
|
76
|
+
mockAssistantApiKey = "sk-test-key";
|
|
77
|
+
|
|
78
|
+
const ctx = resolveManagedProxyContext();
|
|
79
|
+
expect(ctx.platformBaseUrl).toBe("https://platform.example.com");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("hasManagedProxyPrereqs", () => {
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
mockPlatformBaseUrl = "";
|
|
86
|
+
mockAssistantApiKey = null;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("returns false when prerequisites are missing", () => {
|
|
90
|
+
expect(hasManagedProxyPrereqs()).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("returns true when prerequisites are satisfied", () => {
|
|
94
|
+
mockPlatformBaseUrl = "https://platform.example.com";
|
|
95
|
+
mockAssistantApiKey = "sk-test-key";
|
|
96
|
+
expect(hasManagedProxyPrereqs()).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("buildManagedBaseUrl", () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
mockPlatformBaseUrl = "https://platform.example.com";
|
|
103
|
+
mockAssistantApiKey = "sk-test-key";
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("builds correct URL for managed providers", () => {
|
|
107
|
+
expect(buildManagedBaseUrl("openai")).toBe(
|
|
108
|
+
"https://platform.example.com/v1/runtime-proxy/openai",
|
|
109
|
+
);
|
|
110
|
+
expect(buildManagedBaseUrl("anthropic")).toBe(
|
|
111
|
+
"https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
112
|
+
);
|
|
113
|
+
expect(buildManagedBaseUrl("gemini")).toBe(
|
|
114
|
+
"https://platform.example.com/v1/runtime-proxy/gemini",
|
|
115
|
+
);
|
|
116
|
+
expect(buildManagedBaseUrl("fireworks")).toBe(
|
|
117
|
+
"https://platform.example.com/v1/runtime-proxy/fireworks",
|
|
118
|
+
);
|
|
119
|
+
expect(buildManagedBaseUrl("openrouter")).toBe(
|
|
120
|
+
"https://platform.example.com/v1/runtime-proxy/openrouter",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("returns undefined for non-managed provider (ollama)", () => {
|
|
125
|
+
expect(buildManagedBaseUrl("ollama")).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("returns undefined for unknown provider", () => {
|
|
129
|
+
expect(buildManagedBaseUrl("unknown-provider")).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("returns undefined when prerequisites are missing", () => {
|
|
133
|
+
mockPlatformBaseUrl = "";
|
|
134
|
+
mockAssistantApiKey = null;
|
|
135
|
+
expect(buildManagedBaseUrl("openai")).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("managedFallbackEnabledFor", () => {
|
|
140
|
+
beforeEach(() => {
|
|
141
|
+
mockPlatformBaseUrl = "https://platform.example.com";
|
|
142
|
+
mockAssistantApiKey = "sk-test-key";
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("returns true for managed providers with prerequisites", () => {
|
|
146
|
+
expect(managedFallbackEnabledFor("openai")).toBe(true);
|
|
147
|
+
expect(managedFallbackEnabledFor("anthropic")).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("returns false for non-managed provider", () => {
|
|
151
|
+
expect(managedFallbackEnabledFor("ollama")).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("returns false for unknown provider", () => {
|
|
155
|
+
expect(managedFallbackEnabledFor("unknown")).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("returns false when prerequisites are missing", () => {
|
|
159
|
+
mockPlatformBaseUrl = "";
|
|
160
|
+
mockAssistantApiKey = null;
|
|
161
|
+
expect(managedFallbackEnabledFor("openai")).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -478,7 +478,7 @@ describe("Memory lifecycle E2E regression", () => {
|
|
|
478
478
|
confidence: 0.83,
|
|
479
479
|
importance: 0.7,
|
|
480
480
|
fingerprint: "fp-item-runtime-existing",
|
|
481
|
-
verificationState: "
|
|
481
|
+
verificationState: "user_reported",
|
|
482
482
|
scopeId: "default",
|
|
483
483
|
firstSeenAt: now + 200,
|
|
484
484
|
lastSeenAt: now + 200,
|
|
@@ -494,7 +494,7 @@ describe("Memory lifecycle E2E regression", () => {
|
|
|
494
494
|
confidence: 0.81,
|
|
495
495
|
importance: 0.7,
|
|
496
496
|
fingerprint: "fp-item-runtime-candidate",
|
|
497
|
-
verificationState: "
|
|
497
|
+
verificationState: "user_reported",
|
|
498
498
|
scopeId: "default",
|
|
499
499
|
firstSeenAt: now + 201,
|
|
500
500
|
lastSeenAt: now + 201,
|
|
@@ -1201,7 +1201,7 @@ describe("Memory regressions", () => {
|
|
|
1201
1201
|
status: "active",
|
|
1202
1202
|
confidence: 0.8,
|
|
1203
1203
|
fingerprint: "fp-conflict-existing",
|
|
1204
|
-
verificationState: "
|
|
1204
|
+
verificationState: "user_reported",
|
|
1205
1205
|
scopeId: "scope-conflicts",
|
|
1206
1206
|
firstSeenAt: now - 10_000,
|
|
1207
1207
|
lastSeenAt: now - 5_000,
|
|
@@ -1216,7 +1216,7 @@ describe("Memory regressions", () => {
|
|
|
1216
1216
|
status: "pending_clarification",
|
|
1217
1217
|
confidence: 0.8,
|
|
1218
1218
|
fingerprint: "fp-conflict-candidate",
|
|
1219
|
-
verificationState: "
|
|
1219
|
+
verificationState: "user_reported",
|
|
1220
1220
|
scopeId: "scope-conflicts",
|
|
1221
1221
|
firstSeenAt: now - 9_000,
|
|
1222
1222
|
lastSeenAt: now - 4_000,
|
|
@@ -1312,7 +1312,7 @@ describe("Memory regressions", () => {
|
|
|
1312
1312
|
status: "active",
|
|
1313
1313
|
confidence: 0.8,
|
|
1314
1314
|
fingerprint: "fp-conflict-existing-age",
|
|
1315
|
-
verificationState: "
|
|
1315
|
+
verificationState: "user_reported",
|
|
1316
1316
|
scopeId: "scope-conflicts-age",
|
|
1317
1317
|
firstSeenAt: now - 10_000,
|
|
1318
1318
|
lastSeenAt: now - 5_000,
|
|
@@ -1327,7 +1327,7 @@ describe("Memory regressions", () => {
|
|
|
1327
1327
|
status: "pending_clarification",
|
|
1328
1328
|
confidence: 0.8,
|
|
1329
1329
|
fingerprint: "fp-conflict-candidate-age",
|
|
1330
|
-
verificationState: "
|
|
1330
|
+
verificationState: "user_reported",
|
|
1331
1331
|
scopeId: "scope-conflicts-age",
|
|
1332
1332
|
firstSeenAt: now - 9_000,
|
|
1333
1333
|
lastSeenAt: now - 4_000,
|
|
@@ -1418,7 +1418,7 @@ describe("Memory regressions", () => {
|
|
|
1418
1418
|
status: "active",
|
|
1419
1419
|
confidence: 0.8,
|
|
1420
1420
|
fingerprint: "fp-conflict-existing-unrelated",
|
|
1421
|
-
verificationState: "
|
|
1421
|
+
verificationState: "user_reported",
|
|
1422
1422
|
scopeId: "scope-conflicts-unrelated",
|
|
1423
1423
|
firstSeenAt: now - 10_000,
|
|
1424
1424
|
lastSeenAt: now - 5_000,
|
|
@@ -1433,7 +1433,7 @@ describe("Memory regressions", () => {
|
|
|
1433
1433
|
status: "pending_clarification",
|
|
1434
1434
|
confidence: 0.8,
|
|
1435
1435
|
fingerprint: "fp-conflict-candidate-unrelated",
|
|
1436
|
-
verificationState: "
|
|
1436
|
+
verificationState: "user_reported",
|
|
1437
1437
|
scopeId: "scope-conflicts-unrelated",
|
|
1438
1438
|
firstSeenAt: now - 9_000,
|
|
1439
1439
|
lastSeenAt: now - 4_000,
|
|
@@ -78,8 +78,10 @@ mock.module("openai", () => ({
|
|
|
78
78
|
}));
|
|
79
79
|
|
|
80
80
|
// Import after mocking
|
|
81
|
+
import { FireworksProvider } from "../providers/fireworks/client.js";
|
|
81
82
|
import { OllamaProvider } from "../providers/ollama/client.js";
|
|
82
83
|
import { OpenAIProvider } from "../providers/openai/client.js";
|
|
84
|
+
import { OpenRouterProvider } from "../providers/openrouter/client.js";
|
|
83
85
|
|
|
84
86
|
// ---------------------------------------------------------------------------
|
|
85
87
|
// Helpers
|
|
@@ -844,3 +846,83 @@ describe("OpenAIProvider", () => {
|
|
|
844
846
|
});
|
|
845
847
|
});
|
|
846
848
|
});
|
|
849
|
+
|
|
850
|
+
// ---------------------------------------------------------------------------
|
|
851
|
+
// Managed proxy initialization
|
|
852
|
+
// ---------------------------------------------------------------------------
|
|
853
|
+
|
|
854
|
+
describe("managed proxy initialization", () => {
|
|
855
|
+
beforeEach(() => {
|
|
856
|
+
lastConstructorOptions = null;
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
test("OpenAIProvider initializes with managed proxy base URL", () => {
|
|
860
|
+
const managed = new OpenAIProvider("ast-key-123", "gpt-4o", {
|
|
861
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/openai",
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
expect(managed.name).toBe("openai");
|
|
865
|
+
expect(lastConstructorOptions).toEqual({
|
|
866
|
+
apiKey: "ast-key-123",
|
|
867
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/openai",
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
test("OpenAIProvider without baseURL calls provider directly", () => {
|
|
872
|
+
new OpenAIProvider("sk-user-key", "gpt-4o");
|
|
873
|
+
|
|
874
|
+
expect(lastConstructorOptions).toEqual({
|
|
875
|
+
apiKey: "sk-user-key",
|
|
876
|
+
baseURL: undefined,
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test("FireworksProvider initializes with managed proxy base URL", () => {
|
|
881
|
+
const managed = new FireworksProvider(
|
|
882
|
+
"ast-key-123",
|
|
883
|
+
"accounts/fireworks/models/llama-v3p1-70b-instruct",
|
|
884
|
+
{
|
|
885
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/fireworks",
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
expect(managed.name).toBe("fireworks");
|
|
890
|
+
expect(lastConstructorOptions).toEqual({
|
|
891
|
+
apiKey: "ast-key-123",
|
|
892
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/fireworks",
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test("FireworksProvider without managed baseURL uses default Fireworks URL", () => {
|
|
897
|
+
new FireworksProvider(
|
|
898
|
+
"fw-user-key",
|
|
899
|
+
"accounts/fireworks/models/llama-v3p1-70b-instruct",
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
expect(lastConstructorOptions).toEqual({
|
|
903
|
+
apiKey: "fw-user-key",
|
|
904
|
+
baseURL: "https://api.fireworks.ai/inference/v1",
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test("OpenRouterProvider initializes with managed proxy base URL", () => {
|
|
909
|
+
const managed = new OpenRouterProvider("ast-key-123", "openai/gpt-4o", {
|
|
910
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/openrouter",
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
expect(managed.name).toBe("openrouter");
|
|
914
|
+
expect(lastConstructorOptions).toEqual({
|
|
915
|
+
apiKey: "ast-key-123",
|
|
916
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/openrouter",
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
test("OpenRouterProvider without managed baseURL uses default OpenRouter URL", () => {
|
|
921
|
+
new OpenRouterProvider("or-user-key", "openai/gpt-4o");
|
|
922
|
+
|
|
923
|
+
expect(lastConstructorOptions).toEqual({
|
|
924
|
+
apiKey: "or-user-key",
|
|
925
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
});
|
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Mock the underlying dependencies of managed-proxy/context.js rather than
|
|
5
|
+
// the context module itself. This avoids global mock bleed: other test files
|
|
6
|
+
// that import context.js will still get the real implementation with their
|
|
7
|
+
// own dependency mocks.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
let mockPlatformBaseUrl = "";
|
|
10
|
+
let mockAssistantApiKey = "";
|
|
11
|
+
|
|
12
|
+
const actualEnv = await import("../config/env.js");
|
|
13
|
+
mock.module("../config/env.js", () => ({
|
|
14
|
+
...actualEnv,
|
|
15
|
+
getPlatformBaseUrl: () => mockPlatformBaseUrl,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const actualSecureKeys = await import("../security/secure-keys.js");
|
|
19
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
20
|
+
...actualSecureKeys,
|
|
21
|
+
getSecureKey: (key: string) => {
|
|
22
|
+
if (key === "credential:vellum:assistant_api_key") {
|
|
23
|
+
return mockAssistantApiKey || null;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
2
28
|
|
|
3
29
|
import {
|
|
4
30
|
getFailoverProvider,
|
|
5
31
|
initializeProviders,
|
|
32
|
+
listProviders,
|
|
6
33
|
resolveProviderSelection,
|
|
7
34
|
} from "../providers/registry.js";
|
|
8
35
|
|
|
@@ -125,3 +152,109 @@ describe("getFailoverProvider (fail-open)", () => {
|
|
|
125
152
|
expect(provider.name).not.toBe("failover");
|
|
126
153
|
});
|
|
127
154
|
});
|
|
155
|
+
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// Managed proxy fallback
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
describe("managed proxy fallback", () => {
|
|
161
|
+
function enableManagedProxy() {
|
|
162
|
+
mockPlatformBaseUrl = "https://platform.example.com";
|
|
163
|
+
mockAssistantApiKey = "ast-key-123";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function disableManagedProxy() {
|
|
167
|
+
mockPlatformBaseUrl = "";
|
|
168
|
+
mockAssistantApiKey = "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
test("openai registered via managed fallback when no user key but proxy context is valid", () => {
|
|
172
|
+
enableManagedProxy();
|
|
173
|
+
try {
|
|
174
|
+
initializeProviders({
|
|
175
|
+
apiKeys: { anthropic: "test-key" },
|
|
176
|
+
provider: "anthropic",
|
|
177
|
+
model: "test-model",
|
|
178
|
+
});
|
|
179
|
+
const registered = listProviders();
|
|
180
|
+
expect(registered).toContain("openai");
|
|
181
|
+
expect(registered).toContain("fireworks");
|
|
182
|
+
expect(registered).toContain("openrouter");
|
|
183
|
+
} finally {
|
|
184
|
+
disableManagedProxy();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("user key takes precedence over managed fallback", () => {
|
|
189
|
+
enableManagedProxy();
|
|
190
|
+
try {
|
|
191
|
+
initializeProviders({
|
|
192
|
+
apiKeys: { anthropic: "test-key", openai: "user-openai-key" },
|
|
193
|
+
provider: "anthropic",
|
|
194
|
+
model: "test-model",
|
|
195
|
+
});
|
|
196
|
+
// openai should be registered (via user key, not managed)
|
|
197
|
+
const registered = listProviders();
|
|
198
|
+
expect(registered).toContain("openai");
|
|
199
|
+
// fireworks/openrouter should also be registered via managed fallback
|
|
200
|
+
expect(registered).toContain("fireworks");
|
|
201
|
+
expect(registered).toContain("openrouter");
|
|
202
|
+
} finally {
|
|
203
|
+
disableManagedProxy();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("managed fallback not activated when proxy context is disabled", () => {
|
|
208
|
+
disableManagedProxy();
|
|
209
|
+
initializeProviders({
|
|
210
|
+
apiKeys: { anthropic: "test-key" },
|
|
211
|
+
provider: "anthropic",
|
|
212
|
+
model: "test-model",
|
|
213
|
+
});
|
|
214
|
+
const registered = listProviders();
|
|
215
|
+
expect(registered).not.toContain("openai");
|
|
216
|
+
expect(registered).not.toContain("fireworks");
|
|
217
|
+
expect(registered).not.toContain("openrouter");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("managed providers participate in failover selection", () => {
|
|
221
|
+
enableManagedProxy();
|
|
222
|
+
try {
|
|
223
|
+
initializeProviders({
|
|
224
|
+
apiKeys: { anthropic: "test-key" },
|
|
225
|
+
provider: "anthropic",
|
|
226
|
+
model: "test-model",
|
|
227
|
+
});
|
|
228
|
+
const selection = resolveProviderSelection("anthropic", [
|
|
229
|
+
"openai",
|
|
230
|
+
"fireworks",
|
|
231
|
+
]);
|
|
232
|
+
expect(selection.availableProviders).toEqual([
|
|
233
|
+
"anthropic",
|
|
234
|
+
"openai",
|
|
235
|
+
"fireworks",
|
|
236
|
+
]);
|
|
237
|
+
expect(selection.selectedPrimary).toBe("anthropic");
|
|
238
|
+
expect(selection.usedFallbackPrimary).toBe(false);
|
|
239
|
+
} finally {
|
|
240
|
+
disableManagedProxy();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("managed provider selected as primary when configured primary unavailable", () => {
|
|
245
|
+
enableManagedProxy();
|
|
246
|
+
try {
|
|
247
|
+
// No anthropic key, no gemini key — only managed providers available
|
|
248
|
+
initializeProviders({
|
|
249
|
+
apiKeys: {},
|
|
250
|
+
provider: "openai",
|
|
251
|
+
model: "test-model",
|
|
252
|
+
});
|
|
253
|
+
const selection = resolveProviderSelection("openai", ["fireworks"]);
|
|
254
|
+
expect(selection.selectedPrimary).toBe("openai");
|
|
255
|
+
expect(selection.usedFallbackPrimary).toBe(false);
|
|
256
|
+
} finally {
|
|
257
|
+
disableManagedProxy();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|