@lobu/gateway 3.0.9 → 3.0.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/dist/api/platform.d.ts.map +1 -1
- package/dist/api/platform.js +7 -26
- package/dist/api/platform.js.map +1 -1
- package/dist/auth/mcp/proxy.d.ts +14 -0
- package/dist/auth/mcp/proxy.d.ts.map +1 -1
- package/dist/auth/mcp/proxy.js +149 -13
- package/dist/auth/mcp/proxy.js.map +1 -1
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +29 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/connections/chat-instance-manager.d.ts.map +1 -1
- package/dist/connections/chat-instance-manager.js +2 -1
- package/dist/connections/chat-instance-manager.js.map +1 -1
- package/dist/connections/interaction-bridge.d.ts +9 -2
- package/dist/connections/interaction-bridge.d.ts.map +1 -1
- package/dist/connections/interaction-bridge.js +121 -261
- package/dist/connections/interaction-bridge.js.map +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/interactions.d.ts +9 -43
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +10 -52
- package/dist/interactions.js.map +1 -1
- package/dist/routes/public/agent.d.ts +4 -0
- package/dist/routes/public/agent.d.ts.map +1 -1
- package/dist/routes/public/agent.js +21 -0
- package/dist/routes/public/agent.js.map +1 -1
- package/dist/services/core-services.d.ts.map +1 -1
- package/dist/services/core-services.js +4 -0
- package/dist/services/core-services.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/agent-config-routes.test.ts +0 -254
- package/src/__tests__/agent-history-routes.test.ts +0 -72
- package/src/__tests__/agent-routes.test.ts +0 -68
- package/src/__tests__/agent-schedules-routes.test.ts +0 -59
- package/src/__tests__/agent-settings-store.test.ts +0 -323
- package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
- package/src/__tests__/bedrock-openai-service.test.ts +0 -157
- package/src/__tests__/bedrock-provider-module.test.ts +0 -56
- package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
- package/src/__tests__/chat-response-bridge.test.ts +0 -131
- package/src/__tests__/config-memory-plugins.test.ts +0 -92
- package/src/__tests__/config-request-store.test.ts +0 -127
- package/src/__tests__/connection-routes.test.ts +0 -144
- package/src/__tests__/core-services-store-selection.test.ts +0 -92
- package/src/__tests__/docker-deployment.test.ts +0 -1211
- package/src/__tests__/embedded-deployment.test.ts +0 -342
- package/src/__tests__/grant-store.test.ts +0 -148
- package/src/__tests__/http-proxy.test.ts +0 -281
- package/src/__tests__/instruction-service.test.ts +0 -37
- package/src/__tests__/link-buttons.test.ts +0 -112
- package/src/__tests__/lobu.test.ts +0 -32
- package/src/__tests__/mcp-config-service.test.ts +0 -347
- package/src/__tests__/mcp-proxy.test.ts +0 -694
- package/src/__tests__/message-handler-bridge.test.ts +0 -17
- package/src/__tests__/model-selection.test.ts +0 -172
- package/src/__tests__/oauth-templates.test.ts +0 -39
- package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
- package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
- package/src/__tests__/provider-inheritance.test.ts +0 -212
- package/src/__tests__/routes/cli-auth.test.ts +0 -337
- package/src/__tests__/routes/interactions.test.ts +0 -121
- package/src/__tests__/secret-proxy.test.ts +0 -85
- package/src/__tests__/session-manager.test.ts +0 -572
- package/src/__tests__/setup.ts +0 -133
- package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
- package/src/__tests__/slack-routes.test.ts +0 -161
- package/src/__tests__/system-config-resolver.test.ts +0 -75
- package/src/__tests__/system-message-limiter.test.ts +0 -89
- package/src/__tests__/system-skills-service.test.ts +0 -362
- package/src/__tests__/transcription-service.test.ts +0 -222
- package/src/__tests__/utils/rate-limiter.test.ts +0 -102
- package/src/__tests__/worker-connection-manager.test.ts +0 -497
- package/src/__tests__/worker-job-router.test.ts +0 -722
- package/src/api/index.ts +0 -1
- package/src/api/platform.ts +0 -292
- package/src/api/response-renderer.ts +0 -157
- package/src/auth/agent-metadata-store.ts +0 -168
- package/src/auth/api-auth-middleware.ts +0 -69
- package/src/auth/api-key-provider-module.ts +0 -213
- package/src/auth/base-provider-module.ts +0 -201
- package/src/auth/bedrock/provider-module.ts +0 -110
- package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
- package/src/auth/chatgpt/device-code-client.ts +0 -218
- package/src/auth/chatgpt/index.ts +0 -1
- package/src/auth/claude/oauth-module.ts +0 -280
- package/src/auth/cli/token-service.ts +0 -249
- package/src/auth/external/client.ts +0 -560
- package/src/auth/external/device-code-client.ts +0 -235
- package/src/auth/mcp/config-service.ts +0 -420
- package/src/auth/mcp/proxy.ts +0 -1086
- package/src/auth/mcp/string-substitution.ts +0 -17
- package/src/auth/mcp/tool-cache.ts +0 -90
- package/src/auth/oauth/base-client.ts +0 -267
- package/src/auth/oauth/client.ts +0 -153
- package/src/auth/oauth/credentials.ts +0 -7
- package/src/auth/oauth/providers.ts +0 -69
- package/src/auth/oauth/state-store.ts +0 -150
- package/src/auth/oauth-templates.ts +0 -179
- package/src/auth/provider-catalog.ts +0 -220
- package/src/auth/provider-model-options.ts +0 -41
- package/src/auth/settings/agent-settings-store.ts +0 -565
- package/src/auth/settings/auth-profiles-manager.ts +0 -216
- package/src/auth/settings/index.ts +0 -12
- package/src/auth/settings/model-preference-store.ts +0 -52
- package/src/auth/settings/model-selection.ts +0 -135
- package/src/auth/settings/resolved-settings-view.ts +0 -298
- package/src/auth/settings/template-utils.ts +0 -44
- package/src/auth/settings/token-service.ts +0 -88
- package/src/auth/system-env-store.ts +0 -98
- package/src/auth/user-agents-store.ts +0 -68
- package/src/channels/binding-service.ts +0 -214
- package/src/channels/index.ts +0 -4
- package/src/cli/gateway.ts +0 -1312
- package/src/cli/index.ts +0 -74
- package/src/commands/built-in-commands.ts +0 -80
- package/src/commands/command-dispatcher.ts +0 -94
- package/src/commands/command-reply-adapters.ts +0 -27
- package/src/config/file-loader.ts +0 -618
- package/src/config/index.ts +0 -588
- package/src/config/network-allowlist.ts +0 -71
- package/src/connections/chat-instance-manager.ts +0 -1284
- package/src/connections/chat-response-bridge.ts +0 -618
- package/src/connections/index.ts +0 -7
- package/src/connections/interaction-bridge.ts +0 -831
- package/src/connections/message-handler-bridge.ts +0 -440
- package/src/connections/platform-auth-methods.ts +0 -15
- package/src/connections/types.ts +0 -84
- package/src/gateway/connection-manager.ts +0 -291
- package/src/gateway/index.ts +0 -698
- package/src/gateway/job-router.ts +0 -201
- package/src/gateway-main.ts +0 -200
- package/src/index.ts +0 -41
- package/src/infrastructure/queue/index.ts +0 -12
- package/src/infrastructure/queue/queue-producer.ts +0 -148
- package/src/infrastructure/queue/redis-queue.ts +0 -361
- package/src/infrastructure/queue/types.ts +0 -133
- package/src/infrastructure/redis/system-message-limiter.ts +0 -94
- package/src/interactions/config-request-store.ts +0 -198
- package/src/interactions.ts +0 -363
- package/src/lobu.ts +0 -311
- package/src/metrics/prometheus.ts +0 -159
- package/src/modules/module-system.ts +0 -179
- package/src/orchestration/base-deployment-manager.ts +0 -900
- package/src/orchestration/deployment-utils.ts +0 -98
- package/src/orchestration/impl/docker-deployment.ts +0 -620
- package/src/orchestration/impl/embedded-deployment.ts +0 -268
- package/src/orchestration/impl/index.ts +0 -8
- package/src/orchestration/impl/k8s/deployment.ts +0 -1061
- package/src/orchestration/impl/k8s/helpers.ts +0 -610
- package/src/orchestration/impl/k8s/index.ts +0 -1
- package/src/orchestration/index.ts +0 -333
- package/src/orchestration/message-consumer.ts +0 -584
- package/src/orchestration/scheduled-wakeup.ts +0 -704
- package/src/permissions/approval-policy.ts +0 -36
- package/src/permissions/grant-store.ts +0 -219
- package/src/platform/file-handler.ts +0 -66
- package/src/platform/link-buttons.ts +0 -57
- package/src/platform/renderer-utils.ts +0 -44
- package/src/platform/response-renderer.ts +0 -84
- package/src/platform/unified-thread-consumer.ts +0 -194
- package/src/platform.ts +0 -318
- package/src/proxy/http-proxy.ts +0 -752
- package/src/proxy/proxy-manager.ts +0 -81
- package/src/proxy/secret-proxy.ts +0 -402
- package/src/proxy/token-refresh-job.ts +0 -143
- package/src/routes/internal/audio.ts +0 -141
- package/src/routes/internal/device-auth.ts +0 -652
- package/src/routes/internal/files.ts +0 -226
- package/src/routes/internal/history.ts +0 -69
- package/src/routes/internal/images.ts +0 -127
- package/src/routes/internal/interactions.ts +0 -84
- package/src/routes/internal/middleware.ts +0 -23
- package/src/routes/internal/schedule.ts +0 -226
- package/src/routes/internal/types.ts +0 -22
- package/src/routes/openapi-auto.ts +0 -239
- package/src/routes/public/agent-access.ts +0 -23
- package/src/routes/public/agent-config.ts +0 -675
- package/src/routes/public/agent-history.ts +0 -422
- package/src/routes/public/agent-schedules.ts +0 -296
- package/src/routes/public/agent.ts +0 -1086
- package/src/routes/public/agents.ts +0 -373
- package/src/routes/public/channels.ts +0 -191
- package/src/routes/public/cli-auth.ts +0 -896
- package/src/routes/public/connections.ts +0 -574
- package/src/routes/public/landing.ts +0 -16
- package/src/routes/public/oauth.ts +0 -147
- package/src/routes/public/settings-auth.ts +0 -104
- package/src/routes/public/slack.ts +0 -173
- package/src/routes/shared/agent-ownership.ts +0 -101
- package/src/routes/shared/token-verifier.ts +0 -34
- package/src/services/bedrock-model-catalog.ts +0 -217
- package/src/services/bedrock-openai-service.ts +0 -658
- package/src/services/core-services.ts +0 -1072
- package/src/services/image-generation-service.ts +0 -257
- package/src/services/instruction-service.ts +0 -318
- package/src/services/mcp-registry.ts +0 -94
- package/src/services/platform-helpers.ts +0 -287
- package/src/services/session-manager.ts +0 -262
- package/src/services/settings-resolver.ts +0 -74
- package/src/services/system-config-resolver.ts +0 -89
- package/src/services/system-skills-service.ts +0 -229
- package/src/services/transcription-service.ts +0 -684
- package/src/session.ts +0 -110
- package/src/spaces/index.ts +0 -1
- package/src/spaces/space-resolver.ts +0 -17
- package/src/stores/in-memory-agent-store.ts +0 -403
- package/src/stores/redis-agent-store.ts +0 -279
- package/src/utils/public-url.ts +0 -44
- package/src/utils/rate-limiter.ts +0 -94
- package/tsconfig.json +0 -33
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterAll,
|
|
3
|
-
beforeAll,
|
|
4
|
-
beforeEach,
|
|
5
|
-
describe,
|
|
6
|
-
expect,
|
|
7
|
-
test,
|
|
8
|
-
} from "bun:test";
|
|
9
|
-
import { MockRedisClient } from "@lobu/core/testing";
|
|
10
|
-
import { AgentSettingsStore } from "../auth/settings/agent-settings-store";
|
|
11
|
-
|
|
12
|
-
const TEST_ENCRYPTION_KEY =
|
|
13
|
-
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
14
|
-
|
|
15
|
-
let originalEncryptionKey: string | undefined;
|
|
16
|
-
|
|
17
|
-
beforeAll(() => {
|
|
18
|
-
originalEncryptionKey = process.env.ENCRYPTION_KEY;
|
|
19
|
-
process.env.ENCRYPTION_KEY = TEST_ENCRYPTION_KEY;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterAll(() => {
|
|
23
|
-
if (originalEncryptionKey !== undefined) {
|
|
24
|
-
process.env.ENCRYPTION_KEY = originalEncryptionKey;
|
|
25
|
-
} else {
|
|
26
|
-
delete process.env.ENCRYPTION_KEY;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
function createStore(redis?: MockRedisClient) {
|
|
31
|
-
const r = redis ?? new MockRedisClient();
|
|
32
|
-
const store = new AgentSettingsStore(r as any);
|
|
33
|
-
return { store, redis: r };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
describe("AgentSettingsStore", () => {
|
|
37
|
-
let redis: MockRedisClient;
|
|
38
|
-
let store: AgentSettingsStore;
|
|
39
|
-
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
const created = createStore();
|
|
42
|
-
redis = created.redis;
|
|
43
|
-
store = created.store;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("CRUD basics", () => {
|
|
47
|
-
test("saveSettings stores and getSettings retrieves", async () => {
|
|
48
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
49
|
-
const result = await store.getSettings("agent-1");
|
|
50
|
-
expect(result).not.toBeNull();
|
|
51
|
-
expect(result!.model).toBe("claude-sonnet-4");
|
|
52
|
-
expect(result!.updatedAt).toBeGreaterThan(0);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("getSettings returns null for non-existent agent", async () => {
|
|
56
|
-
const result = await store.getSettings("missing");
|
|
57
|
-
expect(result).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("updateSettings merges with existing", async () => {
|
|
61
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
62
|
-
await store.updateSettings("agent-1", { soulMd: "Be helpful" });
|
|
63
|
-
const result = await store.getSettings("agent-1");
|
|
64
|
-
expect(result!.model).toBe("claude-sonnet-4");
|
|
65
|
-
expect(result!.soulMd).toBe("Be helpful");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("deleteSettings removes settings", async () => {
|
|
69
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
70
|
-
await store.deleteSettings("agent-1");
|
|
71
|
-
const result = await store.getSettings("agent-1");
|
|
72
|
-
expect(result).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("hasSettings returns boolean", async () => {
|
|
76
|
-
expect(await store.hasSettings("agent-1")).toBe(false);
|
|
77
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
78
|
-
expect(await store.hasSettings("agent-1")).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe("partial update merging", () => {
|
|
83
|
-
test("merges new fields with existing", async () => {
|
|
84
|
-
await store.saveSettings("agent-1", {
|
|
85
|
-
model: "claude-sonnet-4",
|
|
86
|
-
soulMd: "Original",
|
|
87
|
-
});
|
|
88
|
-
await store.updateSettings("agent-1", { userMd: "New field" });
|
|
89
|
-
const result = await store.getSettings("agent-1");
|
|
90
|
-
expect(result!.model).toBe("claude-sonnet-4");
|
|
91
|
-
expect(result!.soulMd).toBe("Original");
|
|
92
|
-
expect(result!.userMd).toBe("New field");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("overwrites overlapping fields", async () => {
|
|
96
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
97
|
-
await store.updateSettings("agent-1", { model: "claude-opus-4" });
|
|
98
|
-
const result = await store.getSettings("agent-1");
|
|
99
|
-
expect(result!.model).toBe("claude-opus-4");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("creates if no existing settings", async () => {
|
|
103
|
-
await store.updateSettings("agent-1", { model: "claude-opus-4" });
|
|
104
|
-
const result = await store.getSettings("agent-1");
|
|
105
|
-
expect(result).not.toBeNull();
|
|
106
|
-
expect(result!.model).toBe("claude-opus-4");
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe("encryption of authProfiles.credential", () => {
|
|
111
|
-
test("credential is encrypted in Redis and decrypted on read", async () => {
|
|
112
|
-
const apiKey = "sk-ant-secret-key-12345";
|
|
113
|
-
await store.saveSettings("agent-1", {
|
|
114
|
-
authProfiles: [
|
|
115
|
-
{
|
|
116
|
-
id: "profile-1",
|
|
117
|
-
provider: "anthropic",
|
|
118
|
-
model: "claude-sonnet-4",
|
|
119
|
-
credential: apiKey,
|
|
120
|
-
label: "test",
|
|
121
|
-
authType: "api-key",
|
|
122
|
-
createdAt: Date.now(),
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Check raw value in Redis has enc:v1: prefix
|
|
128
|
-
const rawData = await redis.get("agent:settings:agent-1");
|
|
129
|
-
expect(rawData).not.toBeNull();
|
|
130
|
-
const parsed = JSON.parse(rawData!);
|
|
131
|
-
expect(parsed.authProfiles[0].credential).toStartWith("enc:v1:");
|
|
132
|
-
expect(parsed.authProfiles[0].credential).not.toBe(apiKey);
|
|
133
|
-
|
|
134
|
-
// Check decrypted on read
|
|
135
|
-
const result = await store.getSettings("agent-1");
|
|
136
|
-
expect(result!.authProfiles![0].credential).toBe(apiKey);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("encryption of refreshToken", () => {
|
|
141
|
-
test("refreshToken is encrypted in Redis and decrypted on read", async () => {
|
|
142
|
-
const refreshToken = "rt-secret-refresh-token-xyz";
|
|
143
|
-
await store.saveSettings("agent-1", {
|
|
144
|
-
authProfiles: [
|
|
145
|
-
{
|
|
146
|
-
id: "profile-1",
|
|
147
|
-
provider: "anthropic",
|
|
148
|
-
model: "claude-sonnet-4",
|
|
149
|
-
credential: "sk-key",
|
|
150
|
-
label: "test",
|
|
151
|
-
authType: "oauth",
|
|
152
|
-
metadata: {
|
|
153
|
-
refreshToken,
|
|
154
|
-
email: "user@example.com",
|
|
155
|
-
},
|
|
156
|
-
createdAt: Date.now(),
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Check raw value in Redis
|
|
162
|
-
const rawData = await redis.get("agent:settings:agent-1");
|
|
163
|
-
const parsed = JSON.parse(rawData!);
|
|
164
|
-
expect(parsed.authProfiles[0].metadata.refreshToken).toStartWith(
|
|
165
|
-
"enc:v1:"
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Check decrypted on read
|
|
169
|
-
const result = await store.getSettings("agent-1");
|
|
170
|
-
expect(result!.authProfiles![0].metadata!.refreshToken).toBe(
|
|
171
|
-
refreshToken
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("no double-encryption", () => {
|
|
177
|
-
test("already encrypted values are not re-encrypted", async () => {
|
|
178
|
-
// Save once to encrypt
|
|
179
|
-
await store.saveSettings("agent-1", {
|
|
180
|
-
authProfiles: [
|
|
181
|
-
{
|
|
182
|
-
id: "profile-1",
|
|
183
|
-
provider: "anthropic",
|
|
184
|
-
model: "claude-sonnet-4",
|
|
185
|
-
credential: "sk-key",
|
|
186
|
-
label: "test",
|
|
187
|
-
authType: "api-key",
|
|
188
|
-
createdAt: Date.now(),
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const rawAfterFirst = await redis.get("agent:settings:agent-1");
|
|
194
|
-
const parsedFirst = JSON.parse(rawAfterFirst!);
|
|
195
|
-
const encryptedCredential = parsedFirst.authProfiles[0].credential;
|
|
196
|
-
|
|
197
|
-
// Update to re-save (simulating save with already-encrypted value)
|
|
198
|
-
await store.updateSettings("agent-1", { model: "claude-opus-4" });
|
|
199
|
-
|
|
200
|
-
const rawAfterSecond = await redis.get("agent:settings:agent-1");
|
|
201
|
-
const parsedSecond = JSON.parse(rawAfterSecond!);
|
|
202
|
-
|
|
203
|
-
// The credential should still be encrypted, not double-encrypted
|
|
204
|
-
// Both should decrypt to the same plaintext
|
|
205
|
-
const result = await store.getSettings("agent-1");
|
|
206
|
-
expect(result!.authProfiles![0].credential).toBe("sk-key");
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe("graceful plaintext when ENCRYPTION_KEY missing", () => {
|
|
211
|
-
test("values stored as plaintext without encryption key", async () => {
|
|
212
|
-
const savedKey = process.env.ENCRYPTION_KEY;
|
|
213
|
-
delete process.env.ENCRYPTION_KEY;
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const { store: noEncStore, redis: noEncRedis } = createStore();
|
|
217
|
-
|
|
218
|
-
const apiKey = "sk-plaintext-key";
|
|
219
|
-
await noEncStore.saveSettings("agent-1", {
|
|
220
|
-
authProfiles: [
|
|
221
|
-
{
|
|
222
|
-
id: "profile-1",
|
|
223
|
-
provider: "anthropic",
|
|
224
|
-
model: "claude-sonnet-4",
|
|
225
|
-
credential: apiKey,
|
|
226
|
-
label: "test",
|
|
227
|
-
authType: "api-key",
|
|
228
|
-
createdAt: Date.now(),
|
|
229
|
-
},
|
|
230
|
-
],
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Raw value should be plaintext (no enc:v1: prefix)
|
|
234
|
-
const rawData = await noEncRedis.get("agent:settings:agent-1");
|
|
235
|
-
const parsed = JSON.parse(rawData!);
|
|
236
|
-
expect(parsed.authProfiles[0].credential).toBe(apiKey);
|
|
237
|
-
expect(parsed.authProfiles[0].credential).not.toStartWith("enc:v1:");
|
|
238
|
-
|
|
239
|
-
// Read should also return plaintext
|
|
240
|
-
const result = await noEncStore.getSettings("agent-1");
|
|
241
|
-
expect(result!.authProfiles![0].credential).toBe(apiKey);
|
|
242
|
-
} finally {
|
|
243
|
-
process.env.ENCRYPTION_KEY = savedKey;
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe("findTemplateAgentId", () => {
|
|
249
|
-
test("returns first agent with installedProviders", async () => {
|
|
250
|
-
// Agent without installedProviders
|
|
251
|
-
await store.saveSettings("agent-no-providers", {
|
|
252
|
-
model: "claude-sonnet-4",
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Agent with installedProviders
|
|
256
|
-
await store.saveSettings("agent-with-providers", {
|
|
257
|
-
model: "claude-opus-4",
|
|
258
|
-
installedProviders: [
|
|
259
|
-
{
|
|
260
|
-
id: "anthropic",
|
|
261
|
-
displayName: "Anthropic",
|
|
262
|
-
envVarName: "ANTHROPIC_API_KEY",
|
|
263
|
-
upstreamBaseUrl: "https://api.anthropic.com",
|
|
264
|
-
},
|
|
265
|
-
],
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const templateId = await store.findTemplateAgentId();
|
|
269
|
-
expect(templateId).toBe("agent-with-providers");
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test("returns null when no agents have providers", async () => {
|
|
273
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
274
|
-
await store.saveSettings("agent-2", { model: "claude-opus-4" });
|
|
275
|
-
|
|
276
|
-
const templateId = await store.findTemplateAgentId();
|
|
277
|
-
expect(templateId).toBeNull();
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
describe("findSandboxAgentIds", () => {
|
|
282
|
-
test("returns agent IDs referencing template", async () => {
|
|
283
|
-
const templateId = "template-agent";
|
|
284
|
-
|
|
285
|
-
await store.saveSettings(templateId, {
|
|
286
|
-
model: "claude-opus-4",
|
|
287
|
-
installedProviders: [
|
|
288
|
-
{
|
|
289
|
-
id: "anthropic",
|
|
290
|
-
displayName: "Anthropic",
|
|
291
|
-
envVarName: "ANTHROPIC_API_KEY",
|
|
292
|
-
upstreamBaseUrl: "https://api.anthropic.com",
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
await store.saveSettings("sandbox-1", {
|
|
298
|
-
model: "claude-sonnet-4",
|
|
299
|
-
templateAgentId: templateId,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
await store.saveSettings("sandbox-2", {
|
|
303
|
-
model: "claude-sonnet-4",
|
|
304
|
-
templateAgentId: templateId,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Unrelated agent
|
|
308
|
-
await store.saveSettings("other-agent", {
|
|
309
|
-
model: "claude-sonnet-4",
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const sandboxIds = await store.findSandboxAgentIds(templateId);
|
|
313
|
-
expect(sandboxIds).toHaveLength(2);
|
|
314
|
-
expect(sandboxIds.sort()).toEqual(["sandbox-1", "sandbox-2"]);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test("returns empty array when no sandboxes exist", async () => {
|
|
318
|
-
await store.saveSettings("agent-1", { model: "claude-sonnet-4" });
|
|
319
|
-
const sandboxIds = await store.findSandboxAgentIds("non-existent");
|
|
320
|
-
expect(sandboxIds).toEqual([]);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { BedrockModelCatalog } from "../services/bedrock-model-catalog";
|
|
3
|
-
|
|
4
|
-
describe("BedrockModelCatalog", () => {
|
|
5
|
-
test("returns configured models from the loader", async () => {
|
|
6
|
-
const catalog = new BedrockModelCatalog({
|
|
7
|
-
cacheTtlMs: 0,
|
|
8
|
-
loadModels: async () => [
|
|
9
|
-
{
|
|
10
|
-
id: "amazon.nova-lite-v1:0",
|
|
11
|
-
label: "Amazon / Nova Lite",
|
|
12
|
-
inputModalities: ["TEXT"],
|
|
13
|
-
outputModalities: ["TEXT"],
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
await expect(catalog.listModelOptions()).resolves.toEqual([
|
|
19
|
-
{
|
|
20
|
-
id: "amazon.nova-lite-v1:0",
|
|
21
|
-
label: "Amazon / Nova Lite",
|
|
22
|
-
},
|
|
23
|
-
]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("falls back to the static registry when AWS loading fails", async () => {
|
|
27
|
-
const catalog = new BedrockModelCatalog({
|
|
28
|
-
cacheTtlMs: 0,
|
|
29
|
-
loadModels: async () => {
|
|
30
|
-
throw new Error("boom");
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const models = await catalog.listModelOptions();
|
|
35
|
-
expect(models.length).toBeGreaterThan(0);
|
|
36
|
-
expect(models.some((model) => model.id === "amazon.nova-lite-v1:0")).toBe(
|
|
37
|
-
true
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { Model } from "@mariozechner/pi-ai/dist/types.js";
|
|
3
|
-
import { BedrockModelCatalog } from "../services/bedrock-model-catalog";
|
|
4
|
-
import { BedrockOpenAIService } from "../services/bedrock-openai-service";
|
|
5
|
-
|
|
6
|
-
function createCatalog() {
|
|
7
|
-
return new BedrockModelCatalog({
|
|
8
|
-
cacheTtlMs: 0,
|
|
9
|
-
loadModels: async () => [
|
|
10
|
-
{
|
|
11
|
-
id: "amazon.nova-lite-v1:0",
|
|
12
|
-
label: "Amazon / Nova Lite",
|
|
13
|
-
providerName: "Amazon",
|
|
14
|
-
modelName: "Nova Lite",
|
|
15
|
-
inputModalities: ["TEXT"],
|
|
16
|
-
outputModalities: ["TEXT"],
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("BedrockOpenAIService", () => {
|
|
23
|
-
test("lists models in OpenAI format", async () => {
|
|
24
|
-
const service = new BedrockOpenAIService({
|
|
25
|
-
modelCatalog: createCatalog(),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const response = await service
|
|
29
|
-
.getApp()
|
|
30
|
-
.request("http://localhost/openai/a/test-agent/v1/models");
|
|
31
|
-
|
|
32
|
-
expect(response.status).toBe(200);
|
|
33
|
-
expect(await response.json()).toEqual({
|
|
34
|
-
object: "list",
|
|
35
|
-
data: [
|
|
36
|
-
{
|
|
37
|
-
id: "amazon.nova-lite-v1:0",
|
|
38
|
-
object: "model",
|
|
39
|
-
created: 0,
|
|
40
|
-
owned_by: "amazon-bedrock",
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("streams OpenAI chat completion chunks for Bedrock events", async () => {
|
|
47
|
-
process.env.AWS_REGION = "eu-west-1";
|
|
48
|
-
|
|
49
|
-
const service = new BedrockOpenAIService({
|
|
50
|
-
modelCatalog: createCatalog(),
|
|
51
|
-
modelResolver: () => undefined,
|
|
52
|
-
bedrockStreamer: (() =>
|
|
53
|
-
(async function* () {
|
|
54
|
-
yield { type: "text_delta", delta: "hello" };
|
|
55
|
-
yield {
|
|
56
|
-
type: "toolcall_start",
|
|
57
|
-
contentIndex: 1,
|
|
58
|
-
toolCall: { id: "call_1", name: "lookup" },
|
|
59
|
-
};
|
|
60
|
-
yield {
|
|
61
|
-
type: "toolcall_delta",
|
|
62
|
-
contentIndex: 1,
|
|
63
|
-
delta: '{"city":"London"}',
|
|
64
|
-
};
|
|
65
|
-
yield {
|
|
66
|
-
type: "done",
|
|
67
|
-
reason: "toolUse",
|
|
68
|
-
message: {
|
|
69
|
-
usage: {
|
|
70
|
-
input: 10,
|
|
71
|
-
output: 4,
|
|
72
|
-
cacheRead: 2,
|
|
73
|
-
totalTokens: 16,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
})()) as any,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const response = await service
|
|
81
|
-
.getApp()
|
|
82
|
-
.request("http://localhost/openai/a/test-agent/v1/chat/completions", {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers: { "Content-Type": "application/json" },
|
|
85
|
-
body: JSON.stringify({
|
|
86
|
-
model: "amazon.nova-lite-v1:0",
|
|
87
|
-
stream: true,
|
|
88
|
-
stream_options: { include_usage: true },
|
|
89
|
-
messages: [{ role: "user", content: "hi" }],
|
|
90
|
-
tools: [
|
|
91
|
-
{
|
|
92
|
-
type: "function",
|
|
93
|
-
function: {
|
|
94
|
-
name: "lookup",
|
|
95
|
-
parameters: { type: "object", properties: {} },
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
}),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(response.status).toBe(200);
|
|
103
|
-
const body = await response.text();
|
|
104
|
-
expect(body).toContain('"content":"hello"');
|
|
105
|
-
expect(body).toContain('"tool_calls":[{"index":0,"id":"call_1"');
|
|
106
|
-
expect(body).toContain('"finish_reason":"tool_calls"');
|
|
107
|
-
expect(body).toContain('"prompt_tokens":12');
|
|
108
|
-
expect(body).toContain("[DONE]");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("builds a dynamic Bedrock runtime model when not in registry", async () => {
|
|
112
|
-
process.env.AWS_REGION = "eu-west-1";
|
|
113
|
-
|
|
114
|
-
let resolvedModel: Model<"bedrock-converse-stream"> | null = null;
|
|
115
|
-
|
|
116
|
-
const service = new BedrockOpenAIService({
|
|
117
|
-
modelCatalog: new BedrockModelCatalog({
|
|
118
|
-
cacheTtlMs: 0,
|
|
119
|
-
loadModels: async () => [
|
|
120
|
-
{
|
|
121
|
-
id: "custom.model-v1:0",
|
|
122
|
-
label: "Custom / Model",
|
|
123
|
-
providerName: "Custom",
|
|
124
|
-
modelName: "Model",
|
|
125
|
-
inputModalities: ["TEXT"],
|
|
126
|
-
outputModalities: ["TEXT"],
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
}),
|
|
130
|
-
modelResolver: () => undefined,
|
|
131
|
-
bedrockStreamer: ((model: Model<"bedrock-converse-stream">) => {
|
|
132
|
-
resolvedModel = model;
|
|
133
|
-
return (async function* () {
|
|
134
|
-
yield { type: "done", reason: "stop", message: { usage: {} } };
|
|
135
|
-
})();
|
|
136
|
-
}) as any,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const response = await service
|
|
140
|
-
.getApp()
|
|
141
|
-
.request("http://localhost/openai/a/test-agent/v1/chat/completions", {
|
|
142
|
-
method: "POST",
|
|
143
|
-
headers: { "Content-Type": "application/json" },
|
|
144
|
-
body: JSON.stringify({
|
|
145
|
-
model: "custom.model-v1:0",
|
|
146
|
-
stream: true,
|
|
147
|
-
messages: [{ role: "user", content: "hi" }],
|
|
148
|
-
}),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
expect(response.status).toBe(200);
|
|
152
|
-
await response.text();
|
|
153
|
-
expect(resolvedModel?.id).toBe("custom.model-v1:0");
|
|
154
|
-
expect(resolvedModel?.provider).toBe("amazon-bedrock");
|
|
155
|
-
expect(resolvedModel?.api).toBe("bedrock-converse-stream");
|
|
156
|
-
});
|
|
157
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { BedrockProviderModule } from "../auth/bedrock/provider-module";
|
|
3
|
-
import { BedrockModelCatalog } from "../services/bedrock-model-catalog";
|
|
4
|
-
|
|
5
|
-
describe("BedrockProviderModule", () => {
|
|
6
|
-
const createModule = () =>
|
|
7
|
-
new BedrockProviderModule(
|
|
8
|
-
{} as any,
|
|
9
|
-
new BedrockModelCatalog({
|
|
10
|
-
cacheTtlMs: 0,
|
|
11
|
-
loadModels: async () => [
|
|
12
|
-
{
|
|
13
|
-
id: "amazon.nova-lite-v1:0",
|
|
14
|
-
label: "Amazon / Nova Lite",
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
})
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
test("registers as an OpenAI-compatible dynamic provider", () => {
|
|
21
|
-
const module = createModule();
|
|
22
|
-
const metadata = module.getProviderMetadata();
|
|
23
|
-
|
|
24
|
-
expect(metadata).toEqual({
|
|
25
|
-
sdkCompat: "openai",
|
|
26
|
-
defaultModel: "amazon.nova-lite-v1:0",
|
|
27
|
-
baseUrlEnvVar: "AMAZON_BEDROCK_BASE_URL",
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("points workers at the gateway-owned Bedrock route", () => {
|
|
32
|
-
const module = createModule();
|
|
33
|
-
|
|
34
|
-
expect(
|
|
35
|
-
module.getProxyBaseUrlMappings(
|
|
36
|
-
"http://gateway:8080/api/proxy",
|
|
37
|
-
"agent-123"
|
|
38
|
-
)
|
|
39
|
-
).toEqual({
|
|
40
|
-
AMAZON_BEDROCK_BASE_URL:
|
|
41
|
-
"http://gateway:8080/api/bedrock/openai/a/agent-123",
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("returns Bedrock model options under the amazon-bedrock provider prefix", async () => {
|
|
46
|
-
const module = createModule();
|
|
47
|
-
const options = await module.getModelOptions("agent-1", "user-1");
|
|
48
|
-
|
|
49
|
-
expect(options).toEqual([
|
|
50
|
-
{
|
|
51
|
-
value: "amazon-bedrock/amazon.nova-lite-v1:0",
|
|
52
|
-
label: "Amazon / Nova Lite",
|
|
53
|
-
},
|
|
54
|
-
]);
|
|
55
|
-
});
|
|
56
|
-
});
|