@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.
Files changed (145) hide show
  1. package/README.md +117 -0
  2. package/bin/openbridge.js +10 -0
  3. package/package.json +85 -0
  4. package/packages/cli/dist/args.d.ts +30 -0
  5. package/packages/cli/dist/args.js +160 -0
  6. package/packages/cli/dist/cli.d.ts +2 -0
  7. package/packages/cli/dist/cli.js +9 -0
  8. package/packages/cli/dist/index.d.ts +26 -0
  9. package/packages/cli/dist/index.js +76 -0
  10. package/packages/runtime/dist/assistant-protocol.d.ts +34 -0
  11. package/packages/runtime/dist/assistant-protocol.js +121 -0
  12. package/packages/runtime/dist/execution/in-process.d.ts +14 -0
  13. package/packages/runtime/dist/execution/in-process.js +45 -0
  14. package/packages/runtime/dist/execution/types.d.ts +49 -0
  15. package/packages/runtime/dist/execution/types.js +20 -0
  16. package/packages/runtime/dist/index.d.ts +86 -0
  17. package/packages/runtime/dist/index.js +60 -0
  18. package/packages/runtime/dist/normalizers/index.d.ts +6 -0
  19. package/packages/runtime/dist/normalizers/index.js +12 -0
  20. package/packages/runtime/dist/normalizers/legacy-packet.d.ts +6 -0
  21. package/packages/runtime/dist/normalizers/legacy-packet.js +131 -0
  22. package/packages/runtime/dist/output-sanitizer.d.ts +23 -0
  23. package/packages/runtime/dist/output-sanitizer.js +78 -0
  24. package/packages/runtime/dist/packet-extractor.d.ts +17 -0
  25. package/packages/runtime/dist/packet-extractor.js +43 -0
  26. package/packages/runtime/dist/packet-normalizer.d.ts +21 -0
  27. package/packages/runtime/dist/packet-normalizer.js +47 -0
  28. package/packages/runtime/dist/prompt-compiler.d.ts +28 -0
  29. package/packages/runtime/dist/prompt-compiler.js +301 -0
  30. package/packages/runtime/dist/protocol.d.ts +44 -0
  31. package/packages/runtime/dist/protocol.js +165 -0
  32. package/packages/runtime/dist/provider-failure.d.ts +52 -0
  33. package/packages/runtime/dist/provider-failure.js +236 -0
  34. package/packages/runtime/dist/provider.d.ts +40 -0
  35. package/packages/runtime/dist/provider.js +1 -0
  36. package/packages/runtime/dist/runtime.d.ts +86 -0
  37. package/packages/runtime/dist/runtime.js +462 -0
  38. package/packages/runtime/dist/session-bound-provider.d.ts +52 -0
  39. package/packages/runtime/dist/session-bound-provider.js +366 -0
  40. package/packages/runtime/dist/tool-name-aliases.d.ts +5 -0
  41. package/packages/runtime/dist/tool-name-aliases.js +13 -0
  42. package/packages/runtime/dist/tools/bash.d.ts +9 -0
  43. package/packages/runtime/dist/tools/bash.js +157 -0
  44. package/packages/runtime/dist/tools/edit.d.ts +9 -0
  45. package/packages/runtime/dist/tools/edit.js +94 -0
  46. package/packages/runtime/dist/tools/index.d.ts +39 -0
  47. package/packages/runtime/dist/tools/index.js +27 -0
  48. package/packages/runtime/dist/tools/list-dir.d.ts +9 -0
  49. package/packages/runtime/dist/tools/list-dir.js +127 -0
  50. package/packages/runtime/dist/tools/read.d.ts +9 -0
  51. package/packages/runtime/dist/tools/read.js +56 -0
  52. package/packages/runtime/dist/tools/registry.d.ts +15 -0
  53. package/packages/runtime/dist/tools/registry.js +38 -0
  54. package/packages/runtime/dist/tools/runtime-path.d.ts +7 -0
  55. package/packages/runtime/dist/tools/runtime-path.js +22 -0
  56. package/packages/runtime/dist/tools/search-files.d.ts +9 -0
  57. package/packages/runtime/dist/tools/search-files.js +149 -0
  58. package/packages/runtime/dist/tools/text-file.d.ts +32 -0
  59. package/packages/runtime/dist/tools/text-file.js +101 -0
  60. package/packages/runtime/dist/tools/workspace-path.d.ts +17 -0
  61. package/packages/runtime/dist/tools/workspace-path.js +70 -0
  62. package/packages/runtime/dist/tools/write.d.ts +9 -0
  63. package/packages/runtime/dist/tools/write.js +59 -0
  64. package/packages/server/dist/bridge/bridge-model-catalog.d.ts +56 -0
  65. package/packages/server/dist/bridge/bridge-model-catalog.js +100 -0
  66. package/packages/server/dist/bridge/bridge-runtime-service.d.ts +61 -0
  67. package/packages/server/dist/bridge/bridge-runtime-service.js +1386 -0
  68. package/packages/server/dist/bridge/chat-completions/chat-completion-service.d.ts +127 -0
  69. package/packages/server/dist/bridge/chat-completions/chat-completion-service.js +1026 -0
  70. package/packages/server/dist/bridge/index.d.ts +335 -0
  71. package/packages/server/dist/bridge/index.js +45 -0
  72. package/packages/server/dist/bridge/live-provider-extraction-canary.d.ts +69 -0
  73. package/packages/server/dist/bridge/live-provider-extraction-canary.js +186 -0
  74. package/packages/server/dist/bridge/providers/generic-provider-transport.d.ts +53 -0
  75. package/packages/server/dist/bridge/providers/generic-provider-transport.js +973 -0
  76. package/packages/server/dist/bridge/providers/provider-session-resolver.d.ts +17 -0
  77. package/packages/server/dist/bridge/providers/provider-session-resolver.js +95 -0
  78. package/packages/server/dist/bridge/providers/provider-streams.d.ts +80 -0
  79. package/packages/server/dist/bridge/providers/provider-streams.js +844 -0
  80. package/packages/server/dist/bridge/providers/provider-transport-profile.d.ts +194 -0
  81. package/packages/server/dist/bridge/providers/provider-transport-profile.js +198 -0
  82. package/packages/server/dist/bridge/providers/web-provider-transport.d.ts +30 -0
  83. package/packages/server/dist/bridge/providers/web-provider-transport.js +151 -0
  84. package/packages/server/dist/bridge/state/file-bridge-state-store.d.ts +36 -0
  85. package/packages/server/dist/bridge/state/file-bridge-state-store.js +164 -0
  86. package/packages/server/dist/bridge/stores/local-session-package-store.d.ts +23 -0
  87. package/packages/server/dist/bridge/stores/local-session-package-store.js +548 -0
  88. package/packages/server/dist/bridge/stores/provider-store.d.ts +94 -0
  89. package/packages/server/dist/bridge/stores/provider-store.js +143 -0
  90. package/packages/server/dist/bridge/stores/session-backed-provider-store.d.ts +7 -0
  91. package/packages/server/dist/bridge/stores/session-backed-provider-store.js +26 -0
  92. package/packages/server/dist/bridge/stores/session-package-store.d.ts +286 -0
  93. package/packages/server/dist/bridge/stores/session-package-store.js +1527 -0
  94. package/packages/server/dist/bridge/stores/session-store.d.ts +120 -0
  95. package/packages/server/dist/bridge/stores/session-store.js +139 -0
  96. package/packages/server/dist/cli/index.d.ts +9 -0
  97. package/packages/server/dist/cli/index.js +6 -0
  98. package/packages/server/dist/cli/main.d.ts +2 -0
  99. package/packages/server/dist/cli/main.js +9 -0
  100. package/packages/server/dist/cli/run-bridge-server-cli.d.ts +54 -0
  101. package/packages/server/dist/cli/run-bridge-server-cli.js +371 -0
  102. package/packages/server/dist/client/bridge-api-client.d.ts +61 -0
  103. package/packages/server/dist/client/bridge-api-client.js +267 -0
  104. package/packages/server/dist/client/index.d.ts +11 -0
  105. package/packages/server/dist/client/index.js +11 -0
  106. package/packages/server/dist/config/bridge-server-config.d.ts +52 -0
  107. package/packages/server/dist/config/bridge-server-config.js +118 -0
  108. package/packages/server/dist/config/index.d.ts +20 -0
  109. package/packages/server/dist/config/index.js +8 -0
  110. package/packages/server/dist/http/bridge-api-route-context.d.ts +14 -0
  111. package/packages/server/dist/http/bridge-api-route-context.js +1 -0
  112. package/packages/server/dist/http/create-bridge-api-server.d.ts +72 -0
  113. package/packages/server/dist/http/create-bridge-api-server.js +225 -0
  114. package/packages/server/dist/http/index.d.ts +5 -0
  115. package/packages/server/dist/http/index.js +5 -0
  116. package/packages/server/dist/http/parse-request.d.ts +6 -0
  117. package/packages/server/dist/http/parse-request.js +27 -0
  118. package/packages/server/dist/http/register-bridge-api-routes.d.ts +7 -0
  119. package/packages/server/dist/http/register-bridge-api-routes.js +17 -0
  120. package/packages/server/dist/http/routes/admin-routes.d.ts +7 -0
  121. package/packages/server/dist/http/routes/admin-routes.js +135 -0
  122. package/packages/server/dist/http/routes/chat-completions-route.d.ts +7 -0
  123. package/packages/server/dist/http/routes/chat-completions-route.js +49 -0
  124. package/packages/server/dist/http/routes/health-routes.d.ts +6 -0
  125. package/packages/server/dist/http/routes/health-routes.js +7 -0
  126. package/packages/server/dist/http/routes/message-routes.d.ts +7 -0
  127. package/packages/server/dist/http/routes/message-routes.js +7 -0
  128. package/packages/server/dist/index.d.ts +85 -0
  129. package/packages/server/dist/index.js +28 -0
  130. package/packages/server/dist/security/bridge-auth.d.ts +9 -0
  131. package/packages/server/dist/security/bridge-auth.js +41 -0
  132. package/packages/server/dist/security/cors-policy.d.ts +5 -0
  133. package/packages/server/dist/security/cors-policy.js +34 -0
  134. package/packages/server/dist/security/index.d.ts +16 -0
  135. package/packages/server/dist/security/index.js +12 -0
  136. package/packages/server/dist/security/redact-sensitive-values.d.ts +19 -0
  137. package/packages/server/dist/security/redact-sensitive-values.js +67 -0
  138. package/packages/server/dist/shared/api-schema.d.ts +133 -0
  139. package/packages/server/dist/shared/api-schema.js +1 -0
  140. package/packages/server/dist/shared/bridge-api-error.d.ts +17 -0
  141. package/packages/server/dist/shared/bridge-api-error.js +19 -0
  142. package/packages/server/dist/shared/index.d.ts +7 -0
  143. package/packages/server/dist/shared/index.js +7 -0
  144. package/packages/server/dist/shared/output.d.ts +5 -0
  145. package/packages/server/dist/shared/output.js +14 -0
@@ -0,0 +1,371 @@
1
+ import crypto from "node:crypto";
2
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { Command } from "commander";
7
+ import { ZodError } from "zod";
8
+ import { bridgeModule } from "../bridge/index.js";
9
+ import { clientModule } from "../client/index.js";
10
+ import { configModule } from "../config/index.js";
11
+ import { httpModule } from "../http/index.js";
12
+ import { securityModule } from "../security/index.js";
13
+ const { createBridgeChatCompletion, DEFAULT_BRIDGE_API_BASE_URL, streamBridgeChatCompletion } = clientModule;
14
+ const { getBridgeServerStartupWarnings, loadBridgeServerConfig } = configModule;
15
+ const { clearLocalSessionVault, formatLiveProviderExtractionCanaryResult, runLiveProviderExtractionCanary } = bridgeModule;
16
+ const { sanitizeSensitiveText } = securityModule;
17
+ const { startBridgeApiServer } = httpModule;
18
+ async function runBridgeServerCli(input) {
19
+ const stdout = input.stdout ?? process.stdout;
20
+ const stderr = input.stderr ?? process.stderr;
21
+ const stdin = input.stdin ?? process.stdin;
22
+ const startServer = input.startServer ?? startBridgeApiServer;
23
+ const runLiveCanary = input.runLiveCanary ?? runLiveProviderExtractionCanary;
24
+ let command;
25
+ try {
26
+ command = parseBridgeServerCliArgs({
27
+ argv: input.argv,
28
+ env: input.env
29
+ });
30
+ }
31
+ catch (error) {
32
+ stderr.write(`${formatCliError(error)}\n`);
33
+ return 1;
34
+ }
35
+ if (command.kind === "help") {
36
+ stdout.write(`${getBridgeServerCliHelpText()}\n`);
37
+ return 0;
38
+ }
39
+ try {
40
+ if (command.kind === "chat") {
41
+ const messages = [
42
+ command.system
43
+ ? {
44
+ role: "system",
45
+ content: command.system
46
+ }
47
+ : null,
48
+ {
49
+ role: "user",
50
+ content: command.message
51
+ }
52
+ ].filter((message) => message !== null);
53
+ if (command.stream) {
54
+ const contentStream = await streamBridgeChatCompletion({
55
+ baseUrl: command.baseUrl,
56
+ model: command.model,
57
+ messages,
58
+ fetchImpl: input.fetchImpl
59
+ });
60
+ for await (const chunk of contentStream) {
61
+ stdout.write(chunk);
62
+ }
63
+ stdout.write("\n");
64
+ return 0;
65
+ }
66
+ const response = await createBridgeChatCompletion({
67
+ baseUrl: command.baseUrl,
68
+ model: command.model,
69
+ messages,
70
+ fetchImpl: input.fetchImpl
71
+ });
72
+ const content = response.choices[0]?.message?.content;
73
+ if (typeof content !== "string") {
74
+ throw new Error("Bridge chat completion response did not include assistant text.");
75
+ }
76
+ stdout.write(`${content}\n`);
77
+ return 0;
78
+ }
79
+ if (command.kind === "live-canary") {
80
+ const result = await runLiveCanary(command);
81
+ const stream = result.ok ? stdout : stderr;
82
+ stream.write(`${formatLiveProviderExtractionCanaryResult(result)}\n`);
83
+ return result.ok ? 0 : 1;
84
+ }
85
+ if (command.kind === "clear-session-vault") {
86
+ const sessionVaultPath = requireConfigPath(command.config.sessionVaultPath, "sessionVaultPath");
87
+ clearLocalSessionVault({
88
+ vaultPath: sessionVaultPath
89
+ });
90
+ stdout.write(`Emptied session vault at ${sessionVaultPath}\n`);
91
+ return 0;
92
+ }
93
+ for (const warning of getBridgeServerStartupWarnings(command.config)) {
94
+ stderr.write(`Warning: ${warning}\n`);
95
+ }
96
+ if (command.kind === "serve") {
97
+ command = {
98
+ ...command,
99
+ config: await ensureCliVaultKey(command.config, {
100
+ env: input.env ?? process.env,
101
+ stdin,
102
+ stdout,
103
+ stderr,
104
+ promptForVaultKey: input.promptForVaultKey
105
+ })
106
+ };
107
+ }
108
+ const server = await startServer({
109
+ config: command.config
110
+ });
111
+ input.onServerStarted?.(server);
112
+ const address = server.address();
113
+ if (typeof address === "object" && address) {
114
+ stdout.write(`Bridge server listening on http://${address.address}:${address.port}\n`);
115
+ }
116
+ return 0;
117
+ }
118
+ catch (error) {
119
+ stderr.write(`${formatCliError(error)}\n`);
120
+ return 1;
121
+ }
122
+ }
123
+ function parseBridgeServerCliArgs(input) {
124
+ const env = input.env ?? process.env;
125
+ const args = [...input.argv];
126
+ if (args.includes("--help") || args.includes("-h") || args[0] === "help") {
127
+ return {
128
+ kind: "help"
129
+ };
130
+ }
131
+ let parsedCommand = null;
132
+ const program = buildBridgeServerCliProgram(env, {
133
+ onStart(options) {
134
+ parsedCommand = {
135
+ kind: "serve",
136
+ config: loadBridgeServerConfig(env, {
137
+ host: optionalNonEmptyString(options.host, "host") ?? undefined,
138
+ port: options.port,
139
+ authToken: optionalNonEmptyString(options.token, "token") ?? undefined
140
+ })
141
+ };
142
+ },
143
+ onChat(options) {
144
+ parsedCommand = {
145
+ kind: "chat",
146
+ baseUrl: optionalNonEmptyString(options.baseUrl, "base-url") ?? DEFAULT_BRIDGE_API_BASE_URL,
147
+ model: requireNonEmptyString(options.model, "model"),
148
+ message: requireNonEmptyString(options.message, "message"),
149
+ system: optionalNonEmptyString(options.system, "system") ?? undefined,
150
+ stream: options.stream === true
151
+ };
152
+ },
153
+ onLiveCanary(options) {
154
+ const providerId = optionalNonEmptyString(options.provider, "provider");
155
+ const modelId = optionalNonEmptyString(options.model, "model");
156
+ const prompt = optionalNonEmptyString(options.prompt, "prompt");
157
+ const expectedSubstring = optionalNonEmptyString(options.expectedSubstring, "expected-substring");
158
+ const config = loadBridgeServerConfig(env, {
159
+ stateRoot: optionalNonEmptyString(options.stateRoot, "state-root") ?? undefined,
160
+ defaultProvider: providerId ?? undefined,
161
+ defaultModel: modelId ?? undefined
162
+ });
163
+ parsedCommand = {
164
+ kind: "live-canary",
165
+ config,
166
+ stateRoot: config.stateRoot,
167
+ providerId: providerId ?? undefined,
168
+ modelId: modelId ?? undefined,
169
+ prompt: prompt ?? undefined,
170
+ expectedSubstring: expectedSubstring ?? undefined
171
+ };
172
+ },
173
+ onClearSessionVault(options) {
174
+ parsedCommand = {
175
+ kind: "clear-session-vault",
176
+ config: loadBridgeServerConfig(env, {
177
+ stateRoot: optionalNonEmptyString(options.stateRoot, "state-root") ?? undefined,
178
+ sessionVaultPath: optionalNonEmptyString(options.sessionVaultPath, "session-vault-path") ?? undefined
179
+ })
180
+ };
181
+ }
182
+ });
183
+ const normalizedArgv = args.length === 0 || args[0]?.startsWith("--") ? ["start", ...args] : args;
184
+ try {
185
+ program.parse(normalizedArgv, {
186
+ from: "user"
187
+ });
188
+ }
189
+ catch (error) {
190
+ throw normalizeCommanderError(error);
191
+ }
192
+ if (!parsedCommand) {
193
+ throw new Error("No openbridge command was selected.");
194
+ }
195
+ return parsedCommand;
196
+ }
197
+ function getBridgeServerCliHelpText() {
198
+ return buildBridgeServerCliProgram(process.env).helpInformation();
199
+ }
200
+ function optionalNonEmptyString(value, key) {
201
+ if (value === undefined) {
202
+ return null;
203
+ }
204
+ const trimmed = value.trim();
205
+ if (!trimmed) {
206
+ throw new Error(`${key} must be a non-empty string.`);
207
+ }
208
+ return trimmed;
209
+ }
210
+ function requireNonEmptyString(value, key) {
211
+ const normalized = optionalNonEmptyString(value, key);
212
+ if (!normalized) {
213
+ throw new Error(`${key} is required.`);
214
+ }
215
+ return normalized;
216
+ }
217
+ function formatCliError(error) {
218
+ if (error instanceof ZodError) {
219
+ return sanitizeSensitiveText(error.issues
220
+ .map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`)
221
+ .join("; "));
222
+ }
223
+ return sanitizeSensitiveText(error instanceof Error ? error.message : String(error));
224
+ }
225
+ function buildBridgeServerCliProgram(env, handlers = {}) {
226
+ const program = new Command();
227
+ program
228
+ .name("openbridge")
229
+ .description("Server-side openbridge commands.")
230
+ .showHelpAfterError()
231
+ .exitOverride()
232
+ .configureOutput({
233
+ writeOut() { },
234
+ writeErr() { }
235
+ });
236
+ program
237
+ .command("start")
238
+ .description("Start the standalone bridge HTTP server.")
239
+ .option("--host <host>", "Bind host", trimOrUndefined(env.BRIDGE_SERVER_HOST) ?? "127.0.0.1")
240
+ .option("--port <port>", "Bind port", trimOrUndefined(env.BRIDGE_SERVER_PORT))
241
+ .option("--token <token>", "Optional local bridge auth token", trimOrUndefined(env.BRIDGE_AUTH_TOKEN))
242
+ .action((options) => {
243
+ handlers.onStart?.(options);
244
+ });
245
+ program
246
+ .command("chat")
247
+ .description("Send a chat completion request through the standalone bridge HTTP API.")
248
+ .requiredOption("--model <id>", "Model id")
249
+ .requiredOption("--message <text>", "User message")
250
+ .option("--system <text>", "Optional system message")
251
+ .option("--base-url <url>", "Bridge base URL", trimOrUndefined(env.BRIDGE_API_BASE_URL) ??
252
+ trimOrUndefined(env.BRIDGE_SERVER_BASE_URL) ??
253
+ DEFAULT_BRIDGE_API_BASE_URL)
254
+ .option("--stream", "Stream assistant text deltas")
255
+ .action((options) => {
256
+ handlers.onChat?.(options);
257
+ });
258
+ program
259
+ .command("live-canary")
260
+ .description("Run the live provider extraction canary through the standalone bridge runtime.")
261
+ .option("--state-root <path>", "State root for bridge artifacts")
262
+ .option("--provider <id>", "Provider id")
263
+ .option("--model <id>", "Model id")
264
+ .option("--prompt <text>", "Prompt to send to the provider")
265
+ .option("--expected-substring <text>", "Substring expected in the extracted output")
266
+ .action((options) => {
267
+ handlers.onLiveCanary?.({
268
+ ...options,
269
+ stateRoot: options.stateRoot ? path.resolve(options.stateRoot) : undefined
270
+ });
271
+ });
272
+ program
273
+ .command("clear-session-vault")
274
+ .description("Remove all stored session packages from the local session vault.")
275
+ .option("--state-root <path>", "State root for bridge artifacts")
276
+ .option("--session-vault-path <path>", "Session vault path override")
277
+ .action((options) => {
278
+ handlers.onClearSessionVault?.({
279
+ ...options,
280
+ stateRoot: options.stateRoot ? path.resolve(options.stateRoot) : undefined,
281
+ sessionVaultPath: options.sessionVaultPath
282
+ ? path.resolve(options.sessionVaultPath)
283
+ : undefined
284
+ });
285
+ });
286
+ return program;
287
+ }
288
+ function normalizeCommanderError(error) {
289
+ if (error instanceof Error && "code" in error) {
290
+ const code = String(error.code);
291
+ if (code === "commander.helpDisplayed") {
292
+ return new Error(getBridgeServerCliHelpText());
293
+ }
294
+ }
295
+ return error;
296
+ }
297
+ function trimOrUndefined(value) {
298
+ const trimmed = value?.trim();
299
+ return trimmed ? trimmed : undefined;
300
+ }
301
+ async function ensureCliVaultKey(config, input) {
302
+ const sessionVaultKeyPath = requireConfigPath(config.sessionVaultKeyPath, "sessionVaultKeyPath");
303
+ if (trimOrUndefined(input.env.BRIDGE_SESSION_VAULT_KEY)) {
304
+ return config;
305
+ }
306
+ const existingKey = await readOptionalFile(sessionVaultKeyPath);
307
+ if (existingKey) {
308
+ return config;
309
+ }
310
+ if (!input.stdin.isTTY) {
311
+ throw new Error(`Session vault key is required. Set BRIDGE_SESSION_VAULT_KEY or place a base64 32-byte key at ${sessionVaultKeyPath}.`);
312
+ }
313
+ const promptForVaultKey = input.promptForVaultKey ?? defaultPromptForVaultKey;
314
+ const provided = (await promptForVaultKey({
315
+ keyPath: sessionVaultKeyPath,
316
+ stdin: input.stdin
317
+ })).trim();
318
+ if (provided) {
319
+ input.env.BRIDGE_SESSION_VAULT_KEY = provided;
320
+ process.env.BRIDGE_SESSION_VAULT_KEY = provided;
321
+ return loadBridgeServerConfig(input.env, config);
322
+ }
323
+ const generated = crypto.randomBytes(32).toString("base64");
324
+ await mkdir(path.dirname(sessionVaultKeyPath), {
325
+ recursive: true,
326
+ mode: 0o700
327
+ });
328
+ await writeFile(sessionVaultKeyPath, `${generated}\n`, {
329
+ encoding: "utf8",
330
+ mode: 0o600
331
+ });
332
+ await chmod(sessionVaultKeyPath, 0o600);
333
+ input.stderr.write(`Generated session vault key at ${sessionVaultKeyPath}\n`);
334
+ return config;
335
+ }
336
+ async function defaultPromptForVaultKey(input) {
337
+ const readline = createInterface({
338
+ input: input.stdin,
339
+ output: process.stdout,
340
+ terminal: true
341
+ });
342
+ try {
343
+ return await readline.question(`Session vault key is not configured.\nPaste a base64 32-byte key, or press Enter to generate one at ${input.keyPath}: `);
344
+ }
345
+ finally {
346
+ readline.close();
347
+ }
348
+ }
349
+ async function readOptionalFile(targetPath) {
350
+ try {
351
+ const value = (await readFile(targetPath, "utf8")).trim();
352
+ return value || null;
353
+ }
354
+ catch (error) {
355
+ if (error.code === "ENOENT") {
356
+ return null;
357
+ }
358
+ throw error;
359
+ }
360
+ }
361
+ function requireConfigPath(value, key) {
362
+ if (!value) {
363
+ throw new Error(`${key} is required.`);
364
+ }
365
+ return value;
366
+ }
367
+ export const runBridgeServerCliModule = {
368
+ runBridgeServerCli,
369
+ parseBridgeServerCliArgs,
370
+ getBridgeServerCliHelpText
371
+ };
@@ -0,0 +1,61 @@
1
+ import type { BridgeChatCompletionRequest, BridgeChatCompletionResponse, BridgeHealthResponse, BridgeMessageRequest, BridgeMessageResponse } from "../shared/api-schema.ts";
2
+ declare class BridgeApiHttpError extends Error {
3
+ readonly statusCode: number;
4
+ readonly code: string;
5
+ readonly details: Record<string, unknown> | undefined;
6
+ constructor(input: {
7
+ statusCode: number;
8
+ code: string;
9
+ message: string;
10
+ details?: Record<string, unknown>;
11
+ });
12
+ }
13
+ declare class BridgeApiConnectionError extends Error {
14
+ readonly baseUrl: string;
15
+ constructor(input: {
16
+ baseUrl: string;
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ type BridgeApiClientFetch = typeof fetch;
21
+ type SendBridgeMessageInput = Omit<BridgeMessageRequest, "sessionId"> & {
22
+ baseUrl: string;
23
+ sessionId: string;
24
+ fetchImpl?: BridgeApiClientFetch;
25
+ signal?: AbortSignal;
26
+ };
27
+ type CreateBridgeChatCompletionInput = Omit<BridgeChatCompletionRequest, "stream"> & {
28
+ baseUrl: string;
29
+ fetchImpl?: BridgeApiClientFetch;
30
+ signal?: AbortSignal;
31
+ };
32
+ type StreamBridgeChatCompletionInput = Omit<BridgeChatCompletionRequest, "stream"> & {
33
+ baseUrl: string;
34
+ fetchImpl?: BridgeApiClientFetch;
35
+ signal?: AbortSignal;
36
+ };
37
+ type CheckBridgeHealthInput = {
38
+ baseUrl: string;
39
+ fetchImpl?: BridgeApiClientFetch;
40
+ signal?: AbortSignal;
41
+ };
42
+ declare function sendBridgeMessage(input: SendBridgeMessageInput): Promise<BridgeMessageResponse>;
43
+ declare function checkBridgeHealth(input: CheckBridgeHealthInput): Promise<BridgeHealthResponse>;
44
+ declare function createBridgeChatCompletion(input: CreateBridgeChatCompletionInput): Promise<BridgeChatCompletionResponse>;
45
+ declare function streamBridgeChatCompletion(input: StreamBridgeChatCompletionInput): Promise<AsyncIterable<string>>;
46
+ declare function buildSessionMessageUrl(baseUrl: string, sessionId: string): string;
47
+ declare function buildHealthUrl(baseUrl: string): string;
48
+ declare function buildChatCompletionsUrl(baseUrl: string): string;
49
+ export declare const bridgeApiClientModule: {
50
+ DEFAULT_BRIDGE_API_BASE_URL: string;
51
+ BridgeApiHttpError: typeof BridgeApiHttpError;
52
+ BridgeApiConnectionError: typeof BridgeApiConnectionError;
53
+ sendBridgeMessage: typeof sendBridgeMessage;
54
+ checkBridgeHealth: typeof checkBridgeHealth;
55
+ createBridgeChatCompletion: typeof createBridgeChatCompletion;
56
+ streamBridgeChatCompletion: typeof streamBridgeChatCompletion;
57
+ buildSessionMessageUrl: typeof buildSessionMessageUrl;
58
+ buildHealthUrl: typeof buildHealthUrl;
59
+ buildChatCompletionsUrl: typeof buildChatCompletionsUrl;
60
+ };
61
+ export type { BridgeApiClientFetch, BridgeApiConnectionError, BridgeApiHttpError, CheckBridgeHealthInput, CreateBridgeChatCompletionInput, SendBridgeMessageInput, StreamBridgeChatCompletionInput };
@@ -0,0 +1,267 @@
1
+ const DEFAULT_BRIDGE_API_BASE_URL = "http://127.0.0.1:4318";
2
+ class BridgeApiHttpError extends Error {
3
+ statusCode;
4
+ code;
5
+ details;
6
+ constructor(input) {
7
+ super(input.message);
8
+ this.name = "BridgeApiHttpError";
9
+ this.statusCode = input.statusCode;
10
+ this.code = input.code;
11
+ this.details = input.details;
12
+ }
13
+ }
14
+ class BridgeApiConnectionError extends Error {
15
+ baseUrl;
16
+ constructor(input) {
17
+ const suffix = formatCauseMessage(input.cause);
18
+ super(`Failed to reach bridge server at ${input.baseUrl}.${suffix ? ` ${suffix}` : ""}`);
19
+ this.name = "BridgeApiConnectionError";
20
+ this.baseUrl = input.baseUrl;
21
+ this.cause = input.cause;
22
+ }
23
+ }
24
+ async function sendBridgeMessage(input) {
25
+ const response = await (input.fetchImpl ?? fetch)(buildSessionMessageUrl(input.baseUrl, input.sessionId), {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/json"
29
+ },
30
+ body: JSON.stringify({
31
+ input: input.input,
32
+ message: input.message,
33
+ provider: input.provider,
34
+ model: input.model,
35
+ metadata: input.metadata,
36
+ toolProfile: input.toolProfile
37
+ }),
38
+ signal: input.signal
39
+ });
40
+ if (!response.ok) {
41
+ throw await readBridgeApiError(response);
42
+ }
43
+ return response.json();
44
+ }
45
+ async function checkBridgeHealth(input) {
46
+ const response = await (input.fetchImpl ?? fetch)(buildHealthUrl(input.baseUrl), {
47
+ method: "GET",
48
+ signal: input.signal
49
+ });
50
+ if (!response.ok) {
51
+ throw await readBridgeApiError(response);
52
+ }
53
+ return response.json();
54
+ }
55
+ async function createBridgeChatCompletion(input) {
56
+ const response = await fetchBridgeApi(input.baseUrl, input.fetchImpl, buildChatCompletionsUrl(input.baseUrl), {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json"
60
+ },
61
+ body: JSON.stringify({
62
+ model: input.model,
63
+ messages: input.messages,
64
+ metadata: input.metadata
65
+ }),
66
+ signal: input.signal
67
+ });
68
+ if (!response.ok) {
69
+ throw await readBridgeApiError(response);
70
+ }
71
+ return response.json();
72
+ }
73
+ async function streamBridgeChatCompletion(input) {
74
+ const response = await fetchBridgeApi(input.baseUrl, input.fetchImpl, buildChatCompletionsUrl(input.baseUrl), {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify({
80
+ model: input.model,
81
+ messages: input.messages,
82
+ stream: true,
83
+ metadata: input.metadata
84
+ }),
85
+ signal: input.signal
86
+ });
87
+ if (!response.ok) {
88
+ throw await readBridgeApiError(response);
89
+ }
90
+ return readChatCompletionStream(response);
91
+ }
92
+ function buildSessionMessageUrl(baseUrl, sessionId) {
93
+ return new URL(`/v1/sessions/${encodeURIComponent(sessionId)}/messages`, ensureBaseUrl(baseUrl)).toString();
94
+ }
95
+ function buildHealthUrl(baseUrl) {
96
+ return new URL("/health", ensureBaseUrl(baseUrl)).toString();
97
+ }
98
+ function buildChatCompletionsUrl(baseUrl) {
99
+ return new URL("/v1/chat/completions", ensureBaseUrl(baseUrl)).toString();
100
+ }
101
+ async function readBridgeApiError(response) {
102
+ let payload = null;
103
+ try {
104
+ payload = (await response.json());
105
+ }
106
+ catch {
107
+ payload = null;
108
+ }
109
+ return new BridgeApiHttpError({
110
+ statusCode: response.status,
111
+ code: payload?.error.code ?? "http_error",
112
+ message: payload?.error.message ?? `Bridge API request failed with status ${response.status}.`,
113
+ details: payload?.error.details
114
+ });
115
+ }
116
+ function ensureBaseUrl(value) {
117
+ const trimmed = value.trim();
118
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
119
+ }
120
+ async function fetchBridgeApi(baseUrl, fetchImpl, url, init) {
121
+ try {
122
+ return await (fetchImpl ?? fetch)(url, init);
123
+ }
124
+ catch (error) {
125
+ throw new BridgeApiConnectionError({
126
+ baseUrl,
127
+ cause: error
128
+ });
129
+ }
130
+ }
131
+ async function* readChatCompletionStream(response) {
132
+ const stream = response.body;
133
+ if (!stream) {
134
+ throw new Error("Bridge chat stream did not include a response body.");
135
+ }
136
+ const reader = stream.getReader();
137
+ const decoder = new TextDecoder();
138
+ let buffer = "";
139
+ let sawDone = false;
140
+ try {
141
+ while (true) {
142
+ const result = await reader.read();
143
+ if (result.done) {
144
+ break;
145
+ }
146
+ buffer += decoder.decode(result.value, {
147
+ stream: true
148
+ });
149
+ for (const line of consumeSseLines(() => buffer, (value) => {
150
+ buffer = value;
151
+ })) {
152
+ const data = readSseDataLine(line);
153
+ if (data === null) {
154
+ continue;
155
+ }
156
+ if (data === "[DONE]") {
157
+ sawDone = true;
158
+ return;
159
+ }
160
+ const chunk = parseChatCompletionChunk(data);
161
+ const content = chunk.choices[0]?.delta?.content;
162
+ if (typeof content === "string" && content.length > 0) {
163
+ yield content;
164
+ }
165
+ }
166
+ }
167
+ buffer += decoder.decode();
168
+ for (const line of consumeSseLines(() => buffer, (value) => {
169
+ buffer = value;
170
+ })) {
171
+ const data = readSseDataLine(line);
172
+ if (data === null) {
173
+ continue;
174
+ }
175
+ if (data === "[DONE]") {
176
+ sawDone = true;
177
+ return;
178
+ }
179
+ const chunk = parseChatCompletionChunk(data);
180
+ const content = chunk.choices[0]?.delta?.content;
181
+ if (typeof content === "string" && content.length > 0) {
182
+ yield content;
183
+ }
184
+ }
185
+ }
186
+ finally {
187
+ reader.releaseLock();
188
+ }
189
+ if (buffer.trim().length > 0) {
190
+ throw new Error("Bridge chat stream ended with an incomplete SSE frame.");
191
+ }
192
+ if (!sawDone) {
193
+ throw new Error("Bridge chat stream ended before [DONE].");
194
+ }
195
+ }
196
+ function* consumeSseLines(readBuffer, writeBuffer) {
197
+ while (true) {
198
+ const buffer = readBuffer();
199
+ const newlineIndex = buffer.indexOf("\n");
200
+ if (newlineIndex < 0) {
201
+ return;
202
+ }
203
+ const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
204
+ writeBuffer(buffer.slice(newlineIndex + 1));
205
+ yield line;
206
+ }
207
+ }
208
+ function readSseDataLine(line) {
209
+ if (!line.startsWith("data:")) {
210
+ return null;
211
+ }
212
+ const value = line.slice(5).trimStart();
213
+ return value.length > 0 ? value : null;
214
+ }
215
+ function parseChatCompletionChunk(data) {
216
+ let payload;
217
+ try {
218
+ payload = JSON.parse(data);
219
+ }
220
+ catch {
221
+ throw new Error("Bridge chat stream contained invalid JSON.");
222
+ }
223
+ if (typeof payload !== "object" || payload === null) {
224
+ throw new Error("Bridge chat stream contained a malformed chunk.");
225
+ }
226
+ const chunk = payload;
227
+ const choices = chunk.choices;
228
+ if (chunk.object !== "chat.completion.chunk" || !Array.isArray(choices) || choices.length === 0) {
229
+ throw new Error("Bridge chat stream contained a malformed chunk.");
230
+ }
231
+ const choice = choices[0];
232
+ if (!choice || typeof choice !== "object") {
233
+ throw new Error("Bridge chat stream contained a malformed chunk.");
234
+ }
235
+ if (choice.delta !== undefined &&
236
+ (typeof choice.delta !== "object" || choice.delta === null || Array.isArray(choice.delta))) {
237
+ throw new Error("Bridge chat stream contained a malformed chunk.");
238
+ }
239
+ if (choice.delta &&
240
+ "content" in choice.delta &&
241
+ choice.delta.content !== undefined &&
242
+ typeof choice.delta.content !== "string") {
243
+ throw new Error("Bridge chat stream contained a malformed chunk.");
244
+ }
245
+ return {
246
+ object: "chat.completion.chunk",
247
+ choices
248
+ };
249
+ }
250
+ function formatCauseMessage(error) {
251
+ if (!(error instanceof Error)) {
252
+ return "";
253
+ }
254
+ return error.message.trim();
255
+ }
256
+ export const bridgeApiClientModule = {
257
+ DEFAULT_BRIDGE_API_BASE_URL,
258
+ BridgeApiHttpError,
259
+ BridgeApiConnectionError,
260
+ sendBridgeMessage,
261
+ checkBridgeHealth,
262
+ createBridgeChatCompletion,
263
+ streamBridgeChatCompletion,
264
+ buildSessionMessageUrl,
265
+ buildHealthUrl,
266
+ buildChatCompletionsUrl
267
+ };