@uncensoredcode/openbridge 0.1.0
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/README.md +117 -0
- package/bin/openbridge.js +10 -0
- package/package.json +85 -0
- package/packages/cli/dist/args.d.ts +30 -0
- package/packages/cli/dist/args.js +160 -0
- package/packages/cli/dist/cli.d.ts +2 -0
- package/packages/cli/dist/cli.js +9 -0
- package/packages/cli/dist/index.d.ts +26 -0
- package/packages/cli/dist/index.js +76 -0
- package/packages/runtime/dist/assistant-protocol.d.ts +34 -0
- package/packages/runtime/dist/assistant-protocol.js +121 -0
- package/packages/runtime/dist/execution/in-process.d.ts +14 -0
- package/packages/runtime/dist/execution/in-process.js +45 -0
- package/packages/runtime/dist/execution/types.d.ts +49 -0
- package/packages/runtime/dist/execution/types.js +20 -0
- package/packages/runtime/dist/index.d.ts +86 -0
- package/packages/runtime/dist/index.js +60 -0
- package/packages/runtime/dist/normalizers/index.d.ts +6 -0
- package/packages/runtime/dist/normalizers/index.js +12 -0
- package/packages/runtime/dist/normalizers/legacy-packet.d.ts +6 -0
- package/packages/runtime/dist/normalizers/legacy-packet.js +131 -0
- package/packages/runtime/dist/output-sanitizer.d.ts +23 -0
- package/packages/runtime/dist/output-sanitizer.js +78 -0
- package/packages/runtime/dist/packet-extractor.d.ts +17 -0
- package/packages/runtime/dist/packet-extractor.js +43 -0
- package/packages/runtime/dist/packet-normalizer.d.ts +21 -0
- package/packages/runtime/dist/packet-normalizer.js +47 -0
- package/packages/runtime/dist/prompt-compiler.d.ts +28 -0
- package/packages/runtime/dist/prompt-compiler.js +301 -0
- package/packages/runtime/dist/protocol.d.ts +44 -0
- package/packages/runtime/dist/protocol.js +165 -0
- package/packages/runtime/dist/provider-failure.d.ts +52 -0
- package/packages/runtime/dist/provider-failure.js +236 -0
- package/packages/runtime/dist/provider.d.ts +40 -0
- package/packages/runtime/dist/provider.js +1 -0
- package/packages/runtime/dist/runtime.d.ts +86 -0
- package/packages/runtime/dist/runtime.js +462 -0
- package/packages/runtime/dist/session-bound-provider.d.ts +52 -0
- package/packages/runtime/dist/session-bound-provider.js +366 -0
- package/packages/runtime/dist/tool-name-aliases.d.ts +5 -0
- package/packages/runtime/dist/tool-name-aliases.js +13 -0
- package/packages/runtime/dist/tools/bash.d.ts +9 -0
- package/packages/runtime/dist/tools/bash.js +157 -0
- package/packages/runtime/dist/tools/edit.d.ts +9 -0
- package/packages/runtime/dist/tools/edit.js +94 -0
- package/packages/runtime/dist/tools/index.d.ts +39 -0
- package/packages/runtime/dist/tools/index.js +27 -0
- package/packages/runtime/dist/tools/list-dir.d.ts +9 -0
- package/packages/runtime/dist/tools/list-dir.js +127 -0
- package/packages/runtime/dist/tools/read.d.ts +9 -0
- package/packages/runtime/dist/tools/read.js +56 -0
- package/packages/runtime/dist/tools/registry.d.ts +15 -0
- package/packages/runtime/dist/tools/registry.js +38 -0
- package/packages/runtime/dist/tools/runtime-path.d.ts +7 -0
- package/packages/runtime/dist/tools/runtime-path.js +22 -0
- package/packages/runtime/dist/tools/search-files.d.ts +9 -0
- package/packages/runtime/dist/tools/search-files.js +149 -0
- package/packages/runtime/dist/tools/text-file.d.ts +32 -0
- package/packages/runtime/dist/tools/text-file.js +101 -0
- package/packages/runtime/dist/tools/workspace-path.d.ts +17 -0
- package/packages/runtime/dist/tools/workspace-path.js +70 -0
- package/packages/runtime/dist/tools/write.d.ts +9 -0
- package/packages/runtime/dist/tools/write.js +59 -0
- package/packages/server/dist/bridge/bridge-model-catalog.d.ts +56 -0
- package/packages/server/dist/bridge/bridge-model-catalog.js +100 -0
- package/packages/server/dist/bridge/bridge-runtime-service.d.ts +61 -0
- package/packages/server/dist/bridge/bridge-runtime-service.js +1386 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.d.ts +127 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.js +1026 -0
- package/packages/server/dist/bridge/index.d.ts +335 -0
- package/packages/server/dist/bridge/index.js +45 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.d.ts +69 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.js +186 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.d.ts +53 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.js +973 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.d.ts +17 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.js +95 -0
- package/packages/server/dist/bridge/providers/provider-streams.d.ts +80 -0
- package/packages/server/dist/bridge/providers/provider-streams.js +844 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.d.ts +194 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.js +198 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.d.ts +30 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.js +151 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.d.ts +36 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.js +164 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.d.ts +23 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.js +548 -0
- package/packages/server/dist/bridge/stores/provider-store.d.ts +94 -0
- package/packages/server/dist/bridge/stores/provider-store.js +143 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.d.ts +7 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.js +26 -0
- package/packages/server/dist/bridge/stores/session-package-store.d.ts +286 -0
- package/packages/server/dist/bridge/stores/session-package-store.js +1527 -0
- package/packages/server/dist/bridge/stores/session-store.d.ts +120 -0
- package/packages/server/dist/bridge/stores/session-store.js +139 -0
- package/packages/server/dist/cli/index.d.ts +9 -0
- package/packages/server/dist/cli/index.js +6 -0
- package/packages/server/dist/cli/main.d.ts +2 -0
- package/packages/server/dist/cli/main.js +9 -0
- package/packages/server/dist/cli/run-bridge-server-cli.d.ts +54 -0
- package/packages/server/dist/cli/run-bridge-server-cli.js +371 -0
- package/packages/server/dist/client/bridge-api-client.d.ts +61 -0
- package/packages/server/dist/client/bridge-api-client.js +267 -0
- package/packages/server/dist/client/index.d.ts +11 -0
- package/packages/server/dist/client/index.js +11 -0
- package/packages/server/dist/config/bridge-server-config.d.ts +52 -0
- package/packages/server/dist/config/bridge-server-config.js +118 -0
- package/packages/server/dist/config/index.d.ts +20 -0
- package/packages/server/dist/config/index.js +8 -0
- package/packages/server/dist/http/bridge-api-route-context.d.ts +14 -0
- package/packages/server/dist/http/bridge-api-route-context.js +1 -0
- package/packages/server/dist/http/create-bridge-api-server.d.ts +72 -0
- package/packages/server/dist/http/create-bridge-api-server.js +225 -0
- package/packages/server/dist/http/index.d.ts +5 -0
- package/packages/server/dist/http/index.js +5 -0
- package/packages/server/dist/http/parse-request.d.ts +6 -0
- package/packages/server/dist/http/parse-request.js +27 -0
- package/packages/server/dist/http/register-bridge-api-routes.d.ts +7 -0
- package/packages/server/dist/http/register-bridge-api-routes.js +17 -0
- package/packages/server/dist/http/routes/admin-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/admin-routes.js +135 -0
- package/packages/server/dist/http/routes/chat-completions-route.d.ts +7 -0
- package/packages/server/dist/http/routes/chat-completions-route.js +49 -0
- package/packages/server/dist/http/routes/health-routes.d.ts +6 -0
- package/packages/server/dist/http/routes/health-routes.js +7 -0
- package/packages/server/dist/http/routes/message-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/message-routes.js +7 -0
- package/packages/server/dist/index.d.ts +85 -0
- package/packages/server/dist/index.js +28 -0
- package/packages/server/dist/security/bridge-auth.d.ts +9 -0
- package/packages/server/dist/security/bridge-auth.js +41 -0
- package/packages/server/dist/security/cors-policy.d.ts +5 -0
- package/packages/server/dist/security/cors-policy.js +34 -0
- package/packages/server/dist/security/index.d.ts +16 -0
- package/packages/server/dist/security/index.js +12 -0
- package/packages/server/dist/security/redact-sensitive-values.d.ts +19 -0
- package/packages/server/dist/security/redact-sensitive-values.js +67 -0
- package/packages/server/dist/shared/api-schema.d.ts +133 -0
- package/packages/server/dist/shared/api-schema.js +1 -0
- package/packages/server/dist/shared/bridge-api-error.d.ts +17 -0
- package/packages/server/dist/shared/bridge-api-error.js +19 -0
- package/packages/server/dist/shared/index.d.ts +7 -0
- package/packages/server/dist/shared/index.js +7 -0
- package/packages/server/dist/shared/output.d.ts +5 -0
- package/packages/server/dist/shared/output.js +14 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { bridgeApiErrorModule } from "../../shared/bridge-api-error.js";
|
|
6
|
+
import { providerStoreModule } from "./provider-store.js";
|
|
7
|
+
import { sessionPackageStoreModule } from "./session-package-store.js";
|
|
8
|
+
const { buildSessionPackageMetadata, cloneConfig, cloneInstalledProviderPackage, cloneProvider, cloneSessionPackage, cloneSessionPackageMetadata, inferProviderFromSessionPackage, installedProviderPackageSchema, sessionPackageDeleteResponseSchema, sessionPackageSchema, sessionPackageMetadataSchema } = sessionPackageStoreModule;
|
|
9
|
+
const { createProviderRequestSchema, providerDeleteResponseSchema, providerSchema } = providerStoreModule;
|
|
10
|
+
const { BridgeApiError } = bridgeApiErrorModule;
|
|
11
|
+
const SESSION_VAULT_VERSION = 1;
|
|
12
|
+
const SESSION_VAULT_ALGORITHM = "aes-256-gcm";
|
|
13
|
+
const SESSION_VAULT_KEY_BYTES = 32;
|
|
14
|
+
const vaultIndexSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
version: z.literal(SESSION_VAULT_VERSION),
|
|
17
|
+
sessions: z.record(z.string(), sessionPackageMetadataSchema)
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
const vaultEntrySchema = z
|
|
21
|
+
.object({
|
|
22
|
+
version: z.literal(SESSION_VAULT_VERSION),
|
|
23
|
+
algorithm: z.literal(SESSION_VAULT_ALGORITHM),
|
|
24
|
+
handle: z.string().trim().min(1),
|
|
25
|
+
providerId: z.string().trim().min(1),
|
|
26
|
+
metadata: sessionPackageMetadataSchema,
|
|
27
|
+
iv: z.string().trim().min(1),
|
|
28
|
+
authTag: z.string().trim().min(1),
|
|
29
|
+
ciphertext: z.string().trim().min(1)
|
|
30
|
+
})
|
|
31
|
+
.strict();
|
|
32
|
+
const legacySessionPackageMetadataSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
handle: z.string().trim().min(1),
|
|
35
|
+
providerId: z.string().trim().min(1),
|
|
36
|
+
source: z.string().trim().min(1).optional(),
|
|
37
|
+
capturedAt: z.string().datetime({ offset: true }).optional(),
|
|
38
|
+
origin: z.string().url().optional(),
|
|
39
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
40
|
+
lastUsedAt: z.string().datetime({ offset: true }).optional(),
|
|
41
|
+
lastVerifiedAt: z.string().datetime({ offset: true }).optional(),
|
|
42
|
+
idleExpiresAt: z.string().datetime({ offset: true }).optional(),
|
|
43
|
+
absoluteExpiresAt: z.string().datetime({ offset: true }).optional(),
|
|
44
|
+
status: sessionPackageMetadataSchema.shape.status,
|
|
45
|
+
version: z.number().int().min(1)
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
const legacyVaultIndexSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
version: z.literal(SESSION_VAULT_VERSION),
|
|
51
|
+
sessions: z.record(z.string(), legacySessionPackageMetadataSchema)
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
54
|
+
const legacyVaultEntrySchema = z
|
|
55
|
+
.object({
|
|
56
|
+
version: z.literal(SESSION_VAULT_VERSION),
|
|
57
|
+
algorithm: z.literal(SESSION_VAULT_ALGORITHM),
|
|
58
|
+
handle: z.string().trim().min(1),
|
|
59
|
+
providerId: z.string().trim().min(1),
|
|
60
|
+
metadata: legacySessionPackageMetadataSchema,
|
|
61
|
+
iv: z.string().trim().min(1),
|
|
62
|
+
authTag: z.string().trim().min(1),
|
|
63
|
+
ciphertext: z.string().trim().min(1)
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
class SessionPackageVaultError extends Error {
|
|
67
|
+
constructor(message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "SessionPackageVaultError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function clearLocalSessionVault(input) {
|
|
73
|
+
const vaultPath = path.resolve(input.vaultPath);
|
|
74
|
+
const entriesPath = path.join(vaultPath, "entries");
|
|
75
|
+
const indexPath = path.join(vaultPath, "index.json");
|
|
76
|
+
const archivedEntriesPath = existsSync(entriesPath)
|
|
77
|
+
? `${entriesPath}.${crypto.randomUUID()}.clearing`
|
|
78
|
+
: null;
|
|
79
|
+
ensureSecureDir(vaultPath);
|
|
80
|
+
try {
|
|
81
|
+
if (archivedEntriesPath) {
|
|
82
|
+
renameSync(entriesPath, archivedEntriesPath);
|
|
83
|
+
}
|
|
84
|
+
ensureSecureDir(entriesPath);
|
|
85
|
+
writeAtomicJson(indexPath, vaultIndexSchema.parse({
|
|
86
|
+
version: SESSION_VAULT_VERSION,
|
|
87
|
+
sessions: {}
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
try {
|
|
92
|
+
if (archivedEntriesPath && existsSync(archivedEntriesPath)) {
|
|
93
|
+
rmSync(entriesPath, {
|
|
94
|
+
recursive: true,
|
|
95
|
+
force: true
|
|
96
|
+
});
|
|
97
|
+
renameSync(archivedEntriesPath, entriesPath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
if (error instanceof SessionPackageVaultError) {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
throw new SessionPackageVaultError("Failed to clear session vault data.");
|
|
105
|
+
}
|
|
106
|
+
if (!archivedEntriesPath) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
rmSync(archivedEntriesPath, {
|
|
111
|
+
recursive: true,
|
|
112
|
+
force: true
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new SessionPackageVaultError("Failed to remove previous session vault entries.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function createLocalSessionPackageStore(options) {
|
|
120
|
+
const vaultPath = path.resolve(options.vaultPath);
|
|
121
|
+
const keyPath = path.resolve(options.keyPath);
|
|
122
|
+
const entriesPath = path.join(vaultPath, "entries");
|
|
123
|
+
const indexPath = path.join(vaultPath, "index.json");
|
|
124
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
125
|
+
const key = loadOrCreateVaultKey({
|
|
126
|
+
keyMaterial: options.keyMaterial ?? process.env.BRIDGE_SESSION_VAULT_KEY ?? null,
|
|
127
|
+
keyPath
|
|
128
|
+
});
|
|
129
|
+
ensureSecureDir(vaultPath);
|
|
130
|
+
ensureSecureDir(entriesPath);
|
|
131
|
+
const store = {
|
|
132
|
+
listProviders() {
|
|
133
|
+
return Object.keys(readVaultIndex().sessions)
|
|
134
|
+
.map((providerId) => readInstalledPackage(providerId)?.provider ?? null)
|
|
135
|
+
.filter((provider) => provider !== null)
|
|
136
|
+
.map(cloneProvider)
|
|
137
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
138
|
+
},
|
|
139
|
+
getProvider(providerId) {
|
|
140
|
+
const stored = readInstalledPackage(providerId);
|
|
141
|
+
return stored ? cloneProvider(stored.provider) : null;
|
|
142
|
+
},
|
|
143
|
+
createProvider(input) {
|
|
144
|
+
const normalized = createProviderRequestSchema.parse(input);
|
|
145
|
+
if (readVaultMetadata(normalized.id)) {
|
|
146
|
+
throw new BridgeApiError({
|
|
147
|
+
statusCode: 409,
|
|
148
|
+
code: "provider_exists",
|
|
149
|
+
message: `Provider '${normalized.id}' already exists.`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const timestamp = now();
|
|
153
|
+
const provider = providerSchema.parse({
|
|
154
|
+
...normalized,
|
|
155
|
+
createdAt: timestamp,
|
|
156
|
+
updatedAt: timestamp
|
|
157
|
+
});
|
|
158
|
+
const installed = installedProviderPackageSchema.parse({
|
|
159
|
+
provider,
|
|
160
|
+
session: null
|
|
161
|
+
});
|
|
162
|
+
const metadata = buildSessionPackageMetadata(provider.id, null, {
|
|
163
|
+
existing: null,
|
|
164
|
+
now: timestamp,
|
|
165
|
+
handle: crypto.randomUUID()
|
|
166
|
+
});
|
|
167
|
+
writeInstalledPackage(metadata, installed);
|
|
168
|
+
return cloneProvider(provider);
|
|
169
|
+
},
|
|
170
|
+
updateProvider(providerId, patch) {
|
|
171
|
+
const existing = readInstalledPackage(providerId);
|
|
172
|
+
if (!existing) {
|
|
173
|
+
throw missingProviderError(providerId);
|
|
174
|
+
}
|
|
175
|
+
const nextProvider = providerSchema.parse({
|
|
176
|
+
...existing.provider,
|
|
177
|
+
...patch,
|
|
178
|
+
config: patch.config === undefined ? existing.provider.config : cloneConfig(patch.config),
|
|
179
|
+
updatedAt: createTimestamp(existing.provider.updatedAt, now())
|
|
180
|
+
});
|
|
181
|
+
const nextPackage = installedProviderPackageSchema.parse({
|
|
182
|
+
provider: nextProvider,
|
|
183
|
+
session: existing.session ? cloneSessionPackage(existing.session) : null
|
|
184
|
+
});
|
|
185
|
+
const nextMetadata = buildSessionPackageMetadata(providerId, nextPackage.session, {
|
|
186
|
+
existing: readVaultMetadata(providerId),
|
|
187
|
+
now: nextProvider.updatedAt,
|
|
188
|
+
handle: crypto.randomUUID()
|
|
189
|
+
});
|
|
190
|
+
writeInstalledPackage(nextMetadata, nextPackage);
|
|
191
|
+
return cloneProvider(nextProvider);
|
|
192
|
+
},
|
|
193
|
+
get(providerId) {
|
|
194
|
+
const metadata = readVaultMetadata(providerId);
|
|
195
|
+
if (!metadata || !metadata.hasSessionPackage || !isUsableMetadata(metadata, now())) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const installed = readInstalledPackage(providerId);
|
|
199
|
+
if (!installed?.session) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const lastUsedAt = now();
|
|
203
|
+
const nextMetadata = buildSessionPackageMetadata(providerId, installed.session, {
|
|
204
|
+
existing: {
|
|
205
|
+
...metadata,
|
|
206
|
+
lastUsedAt
|
|
207
|
+
},
|
|
208
|
+
now: lastUsedAt,
|
|
209
|
+
handle: crypto.randomUUID()
|
|
210
|
+
});
|
|
211
|
+
writeInstalledPackage(nextMetadata, installed);
|
|
212
|
+
return cloneSessionPackage(installed.session);
|
|
213
|
+
},
|
|
214
|
+
getStatus(providerId) {
|
|
215
|
+
const metadata = readVaultMetadata(providerId);
|
|
216
|
+
return metadata ? cloneSessionPackageMetadata(metadata) : null;
|
|
217
|
+
},
|
|
218
|
+
put(providerId, value) {
|
|
219
|
+
const normalized = cloneSessionPackage(value);
|
|
220
|
+
const existing = readInstalledPackage(providerId);
|
|
221
|
+
const timestamp = createTimestamp(existing?.provider.updatedAt, now());
|
|
222
|
+
const provider = inferProviderFromSessionPackage({
|
|
223
|
+
providerId,
|
|
224
|
+
value: normalized,
|
|
225
|
+
existing: existing?.provider ?? null,
|
|
226
|
+
now: timestamp
|
|
227
|
+
});
|
|
228
|
+
const nextPackage = installedProviderPackageSchema.parse({
|
|
229
|
+
provider,
|
|
230
|
+
session: normalized
|
|
231
|
+
});
|
|
232
|
+
const nextMetadata = buildSessionPackageMetadata(providerId, normalized, {
|
|
233
|
+
existing: readVaultMetadata(providerId),
|
|
234
|
+
now: timestamp,
|
|
235
|
+
handle: crypto.randomUUID()
|
|
236
|
+
});
|
|
237
|
+
writeInstalledPackage(nextMetadata, nextPackage);
|
|
238
|
+
return cloneSessionPackageMetadata(nextMetadata);
|
|
239
|
+
},
|
|
240
|
+
delete(providerId) {
|
|
241
|
+
const metadata = readVaultMetadata(providerId);
|
|
242
|
+
if (!metadata) {
|
|
243
|
+
throw missingProviderError(providerId);
|
|
244
|
+
}
|
|
245
|
+
const nextIndex = {
|
|
246
|
+
...readVaultIndex(),
|
|
247
|
+
sessions: {
|
|
248
|
+
...readVaultIndex().sessions
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
delete nextIndex.sessions[providerId];
|
|
252
|
+
writeVaultIndex(nextIndex, options.testHooks);
|
|
253
|
+
try {
|
|
254
|
+
unlinkSync(getEntryPath(metadata.handle));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error.code !== "ENOENT") {
|
|
258
|
+
throw new SessionPackageVaultError("Failed to remove session vault entry.");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const deleted = providerDeleteResponseSchema.parse({
|
|
262
|
+
ok: true,
|
|
263
|
+
id: providerId
|
|
264
|
+
});
|
|
265
|
+
return sessionPackageDeleteResponseSchema.parse({
|
|
266
|
+
ok: true,
|
|
267
|
+
providerId: deleted.id
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
deleteSession(providerId) {
|
|
271
|
+
const installed = readInstalledPackage(providerId);
|
|
272
|
+
if (!installed) {
|
|
273
|
+
throw missingProviderError(providerId);
|
|
274
|
+
}
|
|
275
|
+
const timestamp = createTimestamp(installed.provider.updatedAt, now());
|
|
276
|
+
const nextProvider = providerSchema.parse({
|
|
277
|
+
...installed.provider,
|
|
278
|
+
updatedAt: timestamp
|
|
279
|
+
});
|
|
280
|
+
const nextPackage = installedProviderPackageSchema.parse({
|
|
281
|
+
provider: nextProvider,
|
|
282
|
+
session: null
|
|
283
|
+
});
|
|
284
|
+
const nextMetadata = buildSessionPackageMetadata(providerId, null, {
|
|
285
|
+
existing: readVaultMetadata(providerId),
|
|
286
|
+
now: timestamp,
|
|
287
|
+
handle: crypto.randomUUID()
|
|
288
|
+
});
|
|
289
|
+
writeInstalledPackage(nextMetadata, nextPackage);
|
|
290
|
+
return sessionPackageDeleteResponseSchema.parse({
|
|
291
|
+
ok: true,
|
|
292
|
+
providerId
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
listPackages() {
|
|
296
|
+
return Object.keys(readVaultIndex().sessions)
|
|
297
|
+
.map((providerId) => readInstalledPackage(providerId))
|
|
298
|
+
.filter((entry) => entry !== null)
|
|
299
|
+
.map(cloneInstalledProviderPackage)
|
|
300
|
+
.sort((left, right) => left.provider.id.localeCompare(right.provider.id));
|
|
301
|
+
},
|
|
302
|
+
getPackage(providerId) {
|
|
303
|
+
const installed = readInstalledPackage(providerId);
|
|
304
|
+
return installed ? cloneInstalledProviderPackage(installed) : null;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
return store;
|
|
308
|
+
function readVaultMetadata(providerId) {
|
|
309
|
+
const metadata = readVaultIndex().sessions[providerId];
|
|
310
|
+
return metadata ? sessionPackageMetadataSchema.parse(metadata) : null;
|
|
311
|
+
}
|
|
312
|
+
function readInstalledPackage(providerId) {
|
|
313
|
+
const metadata = readVaultMetadata(providerId);
|
|
314
|
+
if (!metadata) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const entry = readVaultEntry(metadata);
|
|
318
|
+
return decryptInstalledPackage(entry, key);
|
|
319
|
+
}
|
|
320
|
+
function readVaultIndex() {
|
|
321
|
+
if (!existsSync(indexPath)) {
|
|
322
|
+
return vaultIndexSchema.parse({
|
|
323
|
+
version: SESSION_VAULT_VERSION,
|
|
324
|
+
sessions: {}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const raw = parseJsonFile(indexPath, z.unknown(), "Session vault index is invalid.");
|
|
328
|
+
const parsed = vaultIndexSchema.safeParse(raw);
|
|
329
|
+
if (parsed.success) {
|
|
330
|
+
return parsed.data;
|
|
331
|
+
}
|
|
332
|
+
const legacy = legacyVaultIndexSchema.safeParse(raw);
|
|
333
|
+
if (legacy.success) {
|
|
334
|
+
return vaultIndexSchema.parse({
|
|
335
|
+
version: legacy.data.version,
|
|
336
|
+
sessions: Object.fromEntries(Object.entries(legacy.data.sessions).map(([providerId, metadata]) => [
|
|
337
|
+
providerId,
|
|
338
|
+
{
|
|
339
|
+
...metadata,
|
|
340
|
+
hasSessionPackage: true
|
|
341
|
+
}
|
|
342
|
+
]))
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
throw new SessionPackageVaultError("Session vault index is invalid.");
|
|
346
|
+
}
|
|
347
|
+
function writeVaultIndex(index, testHooks) {
|
|
348
|
+
writeAtomicJson(indexPath, vaultIndexSchema.parse(index), testHooks);
|
|
349
|
+
}
|
|
350
|
+
function readVaultEntry(metadata) {
|
|
351
|
+
const entryPath = getEntryPath(metadata.handle);
|
|
352
|
+
const raw = parseJsonFile(entryPath, z.unknown(), "Session vault entry is invalid.");
|
|
353
|
+
const parsed = vaultEntrySchema.safeParse(raw);
|
|
354
|
+
const entry = parsed.success ? parsed.data : normalizeLegacyVaultEntry(raw);
|
|
355
|
+
if (entry.providerId !== metadata.providerId || entry.handle !== metadata.handle) {
|
|
356
|
+
throw new SessionPackageVaultError("Session vault entry metadata does not match the provider mapping.");
|
|
357
|
+
}
|
|
358
|
+
return entry;
|
|
359
|
+
}
|
|
360
|
+
function writeInstalledPackage(metadata, value) {
|
|
361
|
+
const entry = encryptInstalledPackage(metadata, value, key);
|
|
362
|
+
writeAtomicJson(getEntryPath(metadata.handle), entry, options.testHooks);
|
|
363
|
+
writeVaultIndex(vaultIndexSchema.parse({
|
|
364
|
+
...readVaultIndex(),
|
|
365
|
+
sessions: {
|
|
366
|
+
...readVaultIndex().sessions,
|
|
367
|
+
[metadata.providerId]: metadata
|
|
368
|
+
}
|
|
369
|
+
}), options.testHooks);
|
|
370
|
+
}
|
|
371
|
+
function getEntryPath(handle) {
|
|
372
|
+
return path.join(entriesPath, `${handle}.json`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function encryptInstalledPackage(metadata, value, key) {
|
|
376
|
+
const iv = crypto.randomBytes(12);
|
|
377
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
378
|
+
const plaintext = Buffer.from(JSON.stringify(installedProviderPackageSchema.parse(value)), "utf8");
|
|
379
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
380
|
+
const authTag = cipher.getAuthTag();
|
|
381
|
+
return vaultEntrySchema.parse({
|
|
382
|
+
version: SESSION_VAULT_VERSION,
|
|
383
|
+
algorithm: SESSION_VAULT_ALGORITHM,
|
|
384
|
+
handle: metadata.handle,
|
|
385
|
+
providerId: metadata.providerId,
|
|
386
|
+
metadata,
|
|
387
|
+
iv: iv.toString("base64"),
|
|
388
|
+
authTag: authTag.toString("base64"),
|
|
389
|
+
ciphertext: ciphertext.toString("base64")
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function decryptInstalledPackage(entry, key) {
|
|
393
|
+
try {
|
|
394
|
+
const decipher = crypto.createDecipheriv(SESSION_VAULT_ALGORITHM, key, Buffer.from(entry.iv, "base64"));
|
|
395
|
+
decipher.setAuthTag(Buffer.from(entry.authTag, "base64"));
|
|
396
|
+
const plaintext = Buffer.concat([
|
|
397
|
+
decipher.update(Buffer.from(entry.ciphertext, "base64")),
|
|
398
|
+
decipher.final()
|
|
399
|
+
]);
|
|
400
|
+
const decoded = JSON.parse(plaintext.toString("utf8"));
|
|
401
|
+
const installed = installedProviderPackageSchema.safeParse(decoded);
|
|
402
|
+
if (installed.success) {
|
|
403
|
+
return installed.data;
|
|
404
|
+
}
|
|
405
|
+
const legacySession = sessionPackageSchema.safeParse(decoded);
|
|
406
|
+
if (legacySession.success) {
|
|
407
|
+
return migrateLegacySessionPackage(entry, legacySession.data);
|
|
408
|
+
}
|
|
409
|
+
throw new Error("invalid installed package payload");
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
throw new SessionPackageVaultError("Session vault entry ciphertext is unreadable.");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function migrateLegacySessionPackage(entry, session) {
|
|
416
|
+
const provider = inferProviderFromSessionPackage({
|
|
417
|
+
providerId: entry.providerId,
|
|
418
|
+
value: session,
|
|
419
|
+
existing: null,
|
|
420
|
+
now: entry.metadata.lastUsedAt ?? entry.metadata.createdAt
|
|
421
|
+
});
|
|
422
|
+
return installedProviderPackageSchema.parse({
|
|
423
|
+
provider,
|
|
424
|
+
session
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function normalizeLegacyVaultEntry(raw) {
|
|
428
|
+
const legacy = legacyVaultEntrySchema.safeParse(raw);
|
|
429
|
+
if (!legacy.success) {
|
|
430
|
+
throw new SessionPackageVaultError("Session vault entry is invalid.");
|
|
431
|
+
}
|
|
432
|
+
return vaultEntrySchema.parse({
|
|
433
|
+
...legacy.data,
|
|
434
|
+
metadata: {
|
|
435
|
+
...legacy.data.metadata,
|
|
436
|
+
hasSessionPackage: true
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function loadOrCreateVaultKey(input) {
|
|
441
|
+
if (input.keyMaterial) {
|
|
442
|
+
return decodeVaultKeyMaterial(input.keyMaterial);
|
|
443
|
+
}
|
|
444
|
+
ensureSecureDir(path.dirname(input.keyPath));
|
|
445
|
+
try {
|
|
446
|
+
const fileValue = readFileSync(input.keyPath, "utf8").trim();
|
|
447
|
+
return decodeVaultKeyMaterial(fileValue);
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
if (error.code !== "ENOENT") {
|
|
451
|
+
if (error instanceof SessionPackageVaultError) {
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
throw new SessionPackageVaultError("Failed to load the session vault key.");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const createdKey = crypto.randomBytes(SESSION_VAULT_KEY_BYTES).toString("base64");
|
|
458
|
+
const fileDescriptor = openSync(input.keyPath, "wx", 0o600);
|
|
459
|
+
try {
|
|
460
|
+
writeFileSync(fileDescriptor, `${createdKey}\n`, "utf8");
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
closeSync(fileDescriptor);
|
|
464
|
+
}
|
|
465
|
+
chmodSync(input.keyPath, 0o600);
|
|
466
|
+
return decodeVaultKeyMaterial(createdKey);
|
|
467
|
+
}
|
|
468
|
+
function decodeVaultKeyMaterial(value) {
|
|
469
|
+
const key = Buffer.from(value.trim(), "base64");
|
|
470
|
+
if (key.byteLength !== SESSION_VAULT_KEY_BYTES) {
|
|
471
|
+
throw new SessionPackageVaultError("Session vault key must be base64-encoded 32-byte material.");
|
|
472
|
+
}
|
|
473
|
+
return key;
|
|
474
|
+
}
|
|
475
|
+
function parseJsonFile(targetPath, schema, message) {
|
|
476
|
+
try {
|
|
477
|
+
const raw = readFileSync(targetPath, "utf8");
|
|
478
|
+
return schema.parse(JSON.parse(raw));
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
if (error.code === "ENOENT") {
|
|
482
|
+
throw new SessionPackageVaultError("Session vault entry is missing.");
|
|
483
|
+
}
|
|
484
|
+
if (error instanceof SessionPackageVaultError) {
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
throw new SessionPackageVaultError(message);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function ensureSecureDir(targetPath) {
|
|
491
|
+
mkdirSync(targetPath, {
|
|
492
|
+
recursive: true,
|
|
493
|
+
mode: 0o700
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function writeAtomicJson(targetPath, value, testHooks) {
|
|
497
|
+
ensureSecureDir(path.dirname(targetPath));
|
|
498
|
+
const tempPath = `${targetPath}.${crypto.randomUUID()}.tmp`;
|
|
499
|
+
try {
|
|
500
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
501
|
+
encoding: "utf8",
|
|
502
|
+
mode: 0o600
|
|
503
|
+
});
|
|
504
|
+
testHooks?.afterTempWrite?.(targetPath, tempPath);
|
|
505
|
+
renameSync(tempPath, targetPath);
|
|
506
|
+
chmodSync(targetPath, 0o600);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
try {
|
|
510
|
+
unlinkSync(tempPath);
|
|
511
|
+
}
|
|
512
|
+
catch { }
|
|
513
|
+
if (error instanceof SessionPackageVaultError) {
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
throw new SessionPackageVaultError("Failed to commit session vault data.");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function isUsableMetadata(metadata, referenceTime) {
|
|
520
|
+
if (metadata.status !== "active" || !metadata.hasSessionPackage) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
if (metadata.absoluteExpiresAt && metadata.absoluteExpiresAt <= referenceTime) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
if (metadata.idleExpiresAt && metadata.idleExpiresAt <= referenceTime) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
function createTimestamp(previous, fallbackNow) {
|
|
532
|
+
const previousTime = previous ? Date.parse(previous) : 0;
|
|
533
|
+
const now = Date.parse(fallbackNow);
|
|
534
|
+
const nextTime = previousTime >= now ? previousTime + 1 : now;
|
|
535
|
+
return new Date(nextTime).toISOString();
|
|
536
|
+
}
|
|
537
|
+
function missingProviderError(id) {
|
|
538
|
+
return new BridgeApiError({
|
|
539
|
+
statusCode: 404,
|
|
540
|
+
code: "provider_not_found",
|
|
541
|
+
message: `Provider '${id}' was not found.`
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
export const localSessionPackageStoreModule = {
|
|
545
|
+
SessionPackageVaultError,
|
|
546
|
+
clearLocalSessionVault,
|
|
547
|
+
createLocalSessionPackageStore
|
|
548
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const providerSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
kind: z.ZodString;
|
|
5
|
+
label: z.ZodString;
|
|
6
|
+
enabled: z.ZodBoolean;
|
|
7
|
+
config: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
8
|
+
createdAt: z.ZodString;
|
|
9
|
+
updatedAt: z.ZodString;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
declare const createProviderRequestSchema: z.ZodObject<{
|
|
12
|
+
id: z.ZodString;
|
|
13
|
+
kind: z.ZodString;
|
|
14
|
+
label: z.ZodString;
|
|
15
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
config: z.ZodDefault<z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>>;
|
|
17
|
+
}, z.core.$strict>;
|
|
18
|
+
declare const updateProviderRequestSchema: z.ZodObject<{
|
|
19
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
20
|
+
label: z.ZodOptional<z.ZodString>;
|
|
21
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
config: z.ZodOptional<z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>>;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
declare const providerDeleteResponseSchema: z.ZodObject<{
|
|
25
|
+
ok: z.ZodLiteral<true>;
|
|
26
|
+
id: z.ZodString;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
type ProviderRecord = z.infer<typeof providerSchema>;
|
|
29
|
+
type CreateProviderRequest = z.infer<typeof createProviderRequestSchema>;
|
|
30
|
+
type UpdateProviderRequest = z.infer<typeof updateProviderRequestSchema>;
|
|
31
|
+
type ProviderDeleteResponse = z.infer<typeof providerDeleteResponseSchema>;
|
|
32
|
+
type ProviderStore = {
|
|
33
|
+
list(): ProviderRecord[];
|
|
34
|
+
get(id: string): ProviderRecord | null;
|
|
35
|
+
create(input: CreateProviderRequest): ProviderRecord;
|
|
36
|
+
update(id: string, patch: UpdateProviderRequest): ProviderRecord;
|
|
37
|
+
delete(id: string): ProviderDeleteResponse;
|
|
38
|
+
};
|
|
39
|
+
declare function createInMemoryProviderStore(): ProviderStore;
|
|
40
|
+
export declare const providerStoreModule: {
|
|
41
|
+
providerSchema: z.ZodObject<{
|
|
42
|
+
id: z.ZodString;
|
|
43
|
+
kind: z.ZodString;
|
|
44
|
+
label: z.ZodString;
|
|
45
|
+
enabled: z.ZodBoolean;
|
|
46
|
+
config: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
47
|
+
createdAt: z.ZodString;
|
|
48
|
+
updatedAt: z.ZodString;
|
|
49
|
+
}, z.core.$strip>;
|
|
50
|
+
createProviderRequestSchema: z.ZodObject<{
|
|
51
|
+
id: z.ZodString;
|
|
52
|
+
kind: z.ZodString;
|
|
53
|
+
label: z.ZodString;
|
|
54
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
55
|
+
config: z.ZodDefault<z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>>;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
updateProviderRequestSchema: z.ZodObject<{
|
|
58
|
+
kind: z.ZodOptional<z.ZodString>;
|
|
59
|
+
label: z.ZodOptional<z.ZodString>;
|
|
60
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
61
|
+
config: z.ZodOptional<z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>>;
|
|
62
|
+
}, z.core.$strict>;
|
|
63
|
+
providerIdParamsSchema: z.ZodObject<{
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
providerResponseSchema: z.ZodObject<{
|
|
67
|
+
provider: z.ZodObject<{
|
|
68
|
+
id: z.ZodString;
|
|
69
|
+
kind: z.ZodString;
|
|
70
|
+
label: z.ZodString;
|
|
71
|
+
enabled: z.ZodBoolean;
|
|
72
|
+
config: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
73
|
+
createdAt: z.ZodString;
|
|
74
|
+
updatedAt: z.ZodString;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
}, z.core.$strip>;
|
|
77
|
+
providerListResponseSchema: z.ZodObject<{
|
|
78
|
+
providers: z.ZodArray<z.ZodObject<{
|
|
79
|
+
id: z.ZodString;
|
|
80
|
+
kind: z.ZodString;
|
|
81
|
+
label: z.ZodString;
|
|
82
|
+
enabled: z.ZodBoolean;
|
|
83
|
+
config: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
84
|
+
createdAt: z.ZodString;
|
|
85
|
+
updatedAt: z.ZodString;
|
|
86
|
+
}, z.core.$strip>>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
providerDeleteResponseSchema: z.ZodObject<{
|
|
89
|
+
ok: z.ZodLiteral<true>;
|
|
90
|
+
id: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
createInMemoryProviderStore: typeof createInMemoryProviderStore;
|
|
93
|
+
};
|
|
94
|
+
export type { CreateProviderRequest, ProviderDeleteResponse, ProviderRecord, ProviderStore, UpdateProviderRequest };
|