@uncensoredcode/openbridge 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uncensoredcode/openbridge",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "OpenBridge runtime, server, and CLI for bridging AI providers through a unified interface.",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -1,5 +1,6 @@
1
1
  const PROVIDER_TOOL_NAME_ALIASES = {
2
- execute_shell_command: "bash"
2
+ execute_shell_command: "bash",
3
+ code_interpreter: "bash"
3
4
  };
4
5
  function normalizeProviderToolName(name) {
5
6
  const trimmed = name.trim();
@@ -36,7 +36,7 @@ const EXPLICIT_CHAT_COMPLETION_HEADER_KEYS = [
36
36
  "x-thread-id"
37
37
  ];
38
38
  async function handleBridgeChatCompletionRequest(input) {
39
- const body = input.body;
39
+ const body = normalizeRecoverableChatCompletionRequest(input.body);
40
40
  assertSupportedChatCompletionRequest(body);
41
41
  const resolvedModel = resolveBridgeModel(input.providerStore.list(), body.model);
42
42
  if (!resolvedModel) {
@@ -300,6 +300,29 @@ const chatCompletionsRequestSchema = z
300
300
  metadata: z.record(z.string(), z.unknown()).optional()
301
301
  })
302
302
  .strict();
303
+ function normalizeRecoverableChatCompletionRequest(body) {
304
+ const messages = normalizeRecoverableChatCompletionMessages(body.messages);
305
+ return messages === body.messages
306
+ ? body
307
+ : {
308
+ ...body,
309
+ messages
310
+ };
311
+ }
312
+ function normalizeRecoverableChatCompletionMessages(messages) {
313
+ const normalized = [];
314
+ let changed = false;
315
+ for (const message of messages) {
316
+ const previous = normalized.at(-1);
317
+ if (message.role === "user" && previous?.role === "user") {
318
+ normalized[normalized.length - 1] = message;
319
+ changed = true;
320
+ continue;
321
+ }
322
+ normalized.push(message);
323
+ }
324
+ return changed ? normalized : messages;
325
+ }
303
326
  function assertSupportedChatCompletionRequest(body) {
304
327
  if (body.n !== undefined && body.n !== 1) {
305
328
  throw unsupportedChatCompletionsRequest("n", "Only n=1 is currently supported by the standalone bridge chat completions endpoint.");
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { bridgeApiErrorModule } from "../../shared/bridge-api-error.js";
6
6
  import { providerStoreModule } from "./provider-store.js";
7
7
  import { sessionPackageStoreModule } from "./session-package-store.js";
8
- const { buildSessionPackageMetadata, cloneConfig, cloneInstalledProviderPackage, cloneProvider, cloneSessionPackage, cloneSessionPackageMetadata, inferProviderFromSessionPackage, installedProviderPackageSchema, sessionPackageDeleteResponseSchema, sessionPackageSchema, sessionPackageMetadataSchema } = sessionPackageStoreModule;
8
+ const { buildQwenConversationRequestBody, buildSessionPackageMetadata, cloneConfig, cloneInstalledProviderPackage, cloneProvider, cloneSessionPackage, cloneSessionPackageMetadata, inferProviderFromSessionPackage, installedProviderPackageSchema, sessionPackageDeleteResponseSchema, sessionPackageSchema, sessionPackageMetadataSchema } = sessionPackageStoreModule;
9
9
  const { createProviderRequestSchema, providerDeleteResponseSchema, providerSchema } = providerStoreModule;
10
10
  const { BridgeApiError } = bridgeApiErrorModule;
11
11
  const SESSION_VAULT_VERSION = 1;
@@ -315,7 +315,7 @@ function createLocalSessionPackageStore(options) {
315
315
  return null;
316
316
  }
317
317
  const entry = readVaultEntry(metadata);
318
- return decryptInstalledPackage(entry, key);
318
+ return normalizeInstalledProviderPackage(decryptInstalledPackage(entry, key));
319
319
  }
320
320
  function readVaultIndex() {
321
321
  if (!existsSync(indexPath)) {
@@ -372,6 +372,53 @@ function createLocalSessionPackageStore(options) {
372
372
  return path.join(entriesPath, `${handle}.json`);
373
373
  }
374
374
  }
375
+ function normalizeInstalledProviderPackage(installed) {
376
+ const transport = readInstalledQwenTransport(installed.provider.config);
377
+ if (!transport) {
378
+ return installed;
379
+ }
380
+ const request = readInstalledTransportRequest(transport);
381
+ if (!request || !("body" in request)) {
382
+ return installed;
383
+ }
384
+ const normalizedBody = buildQwenConversationRequestBody(request.body);
385
+ if (JSON.stringify(normalizedBody) === JSON.stringify(request.body)) {
386
+ return installed;
387
+ }
388
+ return installedProviderPackageSchema.parse({
389
+ provider: {
390
+ ...cloneProvider(installed.provider),
391
+ config: {
392
+ ...cloneConfig(installed.provider.config),
393
+ transport: {
394
+ ...transport,
395
+ request: {
396
+ ...request,
397
+ body: normalizedBody
398
+ }
399
+ }
400
+ }
401
+ },
402
+ session: installed.session ? cloneSessionPackage(installed.session) : null
403
+ });
404
+ }
405
+ function readInstalledQwenTransport(config) {
406
+ const transport = typeof config.transport === "object" &&
407
+ config.transport !== null &&
408
+ !Array.isArray(config.transport)
409
+ ? config.transport
410
+ : null;
411
+ const request = readInstalledTransportRequest(transport);
412
+ const url = typeof request?.url === "string" ? request.url.trim() : "";
413
+ return /^https:\/\/chat\.qwen\.ai\/api\/v2\/chat\/completions\b/u.test(url) ? transport : null;
414
+ }
415
+ function readInstalledTransportRequest(transport) {
416
+ return typeof transport?.request === "object" &&
417
+ transport.request !== null &&
418
+ !Array.isArray(transport.request)
419
+ ? transport.request
420
+ : null;
421
+ }
375
422
  function encryptInstalledPackage(metadata, value, key) {
376
423
  const iv = crypto.randomBytes(12);
377
424
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
@@ -165,6 +165,7 @@ declare function inferProviderFromSessionPackage(input: {
165
165
  createdAt: string;
166
166
  updatedAt: string;
167
167
  };
168
+ declare function buildQwenConversationRequestBody(requestBody: unknown): unknown;
168
169
  export declare const sessionPackageStoreModule: {
169
170
  sessionPackageSchema: z.ZodObject<{
170
171
  schemaVersion: z.ZodOptional<z.ZodNumber>;
@@ -274,6 +275,7 @@ export declare const sessionPackageStoreModule: {
274
275
  }, z.core.$strict>>;
275
276
  }, z.core.$strict>;
276
277
  createInMemorySessionPackageStore: typeof createInMemorySessionPackageStore;
278
+ buildQwenConversationRequestBody: typeof buildQwenConversationRequestBody;
277
279
  buildSessionPackageStatus: typeof buildSessionPackageStatus;
278
280
  cloneProvider: typeof cloneProvider;
279
281
  cloneConfig: typeof cloneConfig;
@@ -416,7 +416,9 @@ function inferQwenConversationTransport(selectedRequest, value) {
416
416
  method: String(selectedRequest.method).toUpperCase(),
417
417
  url: "https://chat.qwen.ai/api/v2/chat/completions?chat_id={{conversationId}}",
418
418
  headers: {},
419
- ...(requestBody === undefined ? {} : { body: requestBody })
419
+ ...(requestBody === undefined
420
+ ? {}
421
+ : { body: buildQwenConversationRequestBody(requestBody) })
420
422
  },
421
423
  response: {
422
424
  contentPaths: DEFAULT_SSE_CONTENT_PATHS,
@@ -926,6 +928,60 @@ function buildQwenBootstrapRequestBody(selectedRequest) {
926
928
  project_id: projectId
927
929
  };
928
930
  }
931
+ function buildQwenConversationRequestBody(requestBody) {
932
+ if (typeof requestBody !== "object" || requestBody === null || Array.isArray(requestBody)) {
933
+ return requestBody;
934
+ }
935
+ const base = structuredClone(requestBody);
936
+ if ("chat_id" in base) {
937
+ base.chat_id = "{{conversationId}}";
938
+ }
939
+ if ("model" in base) {
940
+ base.model = "{{modelId}}";
941
+ }
942
+ if ("timestamp" in base) {
943
+ base.timestamp = "{{unixTimestampSec}}";
944
+ }
945
+ if ("parent_id" in base) {
946
+ base.parent_id = "{{parentIdOrNull}}";
947
+ }
948
+ if (Array.isArray(base.messages) && base.messages.length > 0) {
949
+ base.messages = base.messages.map((message, index) => index === 0 ? buildQwenConversationUserMessage(message) : message);
950
+ }
951
+ return base;
952
+ }
953
+ function buildQwenConversationUserMessage(message) {
954
+ if (typeof message !== "object" || message === null || Array.isArray(message)) {
955
+ return {
956
+ fid: "{{messageId}}",
957
+ role: "user",
958
+ content: "{{prompt}}"
959
+ };
960
+ }
961
+ const nextMessage = structuredClone(message);
962
+ nextMessage.fid = "{{messageId}}";
963
+ nextMessage.role = "user";
964
+ nextMessage.content = "{{prompt}}";
965
+ if ("timestamp" in nextMessage) {
966
+ nextMessage.timestamp = "{{unixTimestampSec}}";
967
+ }
968
+ if ("models" in nextMessage) {
969
+ nextMessage.models = ["{{modelId}}"];
970
+ }
971
+ if ("parentId" in nextMessage) {
972
+ nextMessage.parentId = "{{parentIdOrNull}}";
973
+ }
974
+ if ("parent_id" in nextMessage) {
975
+ nextMessage.parent_id = "{{parentIdOrNull}}";
976
+ }
977
+ if ("childrenIds" in nextMessage && Array.isArray(nextMessage.childrenIds)) {
978
+ nextMessage.childrenIds = [];
979
+ }
980
+ if ("children_ids" in nextMessage && Array.isArray(nextMessage.children_ids)) {
981
+ nextMessage.children_ids = [];
982
+ }
983
+ return nextMessage;
984
+ }
929
985
  function buildOpenAiConversationRequestBody(requestBody) {
930
986
  const base = requestBody ? structuredClone(requestBody) : {};
931
987
  const capturedMessage = Array.isArray(requestBody?.messages) &&
@@ -1516,6 +1572,7 @@ export const sessionPackageStoreModule = {
1516
1572
  sessionPackageMetadataSchema,
1517
1573
  installedProviderPackageSchema,
1518
1574
  createInMemorySessionPackageStore,
1575
+ buildQwenConversationRequestBody,
1519
1576
  buildSessionPackageStatus,
1520
1577
  cloneProvider,
1521
1578
  cloneConfig,
@@ -1,4 +1,13 @@
1
1
  export declare const cliModule: {
2
+ buildDetachedServerLaunchCommand: (input: {
3
+ scriptPath: string;
4
+ argv: string[];
5
+ execPath: string;
6
+ execArgv: string[];
7
+ }) => {
8
+ command: string;
9
+ args: string[];
10
+ };
2
11
  getBridgeServerCliHelpText: () => string;
3
12
  parseBridgeServerCliArgs: (input: {
4
13
  argv: string[];
@@ -1,5 +1,6 @@
1
1
  import { runBridgeServerCliModule } from "./run-bridge-server-cli.js";
2
2
  export const cliModule = {
3
+ buildDetachedServerLaunchCommand: runBridgeServerCliModule.buildDetachedServerLaunchCommand,
3
4
  getBridgeServerCliHelpText: runBridgeServerCliModule.getBridgeServerCliHelpText,
4
5
  parseBridgeServerCliArgs: runBridgeServerCliModule.parseBridgeServerCliArgs,
5
6
  runBridgeServerCli: runBridgeServerCliModule.runBridgeServerCli
@@ -121,13 +121,24 @@ type SpawnDetachedServerProcessInput = {
121
121
  type SpawnDetachedServerProcessResult = {
122
122
  pid: number;
123
123
  };
124
+ type DetachedServerLaunchCommand = {
125
+ command: string;
126
+ args: string[];
127
+ };
124
128
  declare function runBridgeServerCli(input: RunBridgeServerCliInput): Promise<number>;
125
129
  declare function parseBridgeServerCliArgs(input: {
126
130
  argv: string[];
127
131
  env?: NodeJS.ProcessEnv;
128
132
  }): BridgeServerCliCommand;
129
133
  declare function getBridgeServerCliHelpText(): string;
134
+ declare function buildDetachedServerLaunchCommand(input: {
135
+ scriptPath: string;
136
+ argv: string[];
137
+ execPath: string;
138
+ execArgv: string[];
139
+ }): DetachedServerLaunchCommand;
130
140
  export declare const runBridgeServerCliModule: {
141
+ buildDetachedServerLaunchCommand: typeof buildDetachedServerLaunchCommand;
131
142
  runBridgeServerCli: typeof runBridgeServerCli;
132
143
  parseBridgeServerCliArgs: typeof parseBridgeServerCliArgs;
133
144
  getBridgeServerCliHelpText: typeof getBridgeServerCliHelpText;
@@ -99,7 +99,8 @@ async function runBridgeServerCli(input) {
99
99
  }
100
100
  if (command.kind === "status") {
101
101
  const status = await getServerStatus(command.config, input.fetchImpl);
102
- writeJson(stdout, status);
102
+ const { logPath: _logPath, ...statusOutput } = status;
103
+ writeJson(stdout, statusOutput);
103
104
  return status.running && status.healthy !== false ? 0 : 1;
104
105
  }
105
106
  if (command.kind === "stop") {
@@ -271,13 +272,11 @@ async function runBridgeServerCli(input) {
271
272
  const statusMessage = readyState === null
272
273
  ? [
273
274
  `Bridge server started in background (pid ${daemon.pid}).`,
274
- `Logs: ${processFiles.logPath}`,
275
275
  `Startup is still pending; check status with "openbridge status".`
276
276
  ].join("\n")
277
277
  : [
278
278
  `Bridge server started in background (pid ${readyState.pid}).`,
279
- `Base URL: ${readyState.baseUrl}`,
280
- `Logs: ${readyState.logPath}`
279
+ `Base URL: ${readyState.baseUrl}`
281
280
  ].join("\n");
282
281
  stdout.write(`${statusMessage}\n`);
283
282
  return 0;
@@ -1178,7 +1177,13 @@ async function defaultSpawnDetachedServerProcess(input) {
1178
1177
  });
1179
1178
  const logHandle = await open(input.logPath, "a", 0o600);
1180
1179
  try {
1181
- const child = spawn(process.execPath, [scriptPath, ...input.argv], {
1180
+ const launch = buildDetachedServerLaunchCommand({
1181
+ scriptPath,
1182
+ argv: input.argv,
1183
+ execPath: process.execPath,
1184
+ execArgv: process.execArgv
1185
+ });
1186
+ const child = spawn(launch.command, launch.args, {
1182
1187
  cwd: input.cwd,
1183
1188
  env: input.env,
1184
1189
  detached: true,
@@ -1196,6 +1201,12 @@ async function defaultSpawnDetachedServerProcess(input) {
1196
1201
  await logHandle.close();
1197
1202
  }
1198
1203
  }
1204
+ function buildDetachedServerLaunchCommand(input) {
1205
+ return {
1206
+ command: input.execPath,
1207
+ args: [...input.execArgv, input.scriptPath, ...input.argv]
1208
+ };
1209
+ }
1199
1210
  function toForegroundStartArgv(argv) {
1200
1211
  const normalized = argv.length === 0 || argv[0]?.startsWith("--") ? ["start", ...argv] : [...argv];
1201
1212
  if (normalized[0] !== "start") {
@@ -1280,6 +1291,7 @@ function requireConfigPath(value, key) {
1280
1291
  return value;
1281
1292
  }
1282
1293
  export const runBridgeServerCliModule = {
1294
+ buildDetachedServerLaunchCommand,
1283
1295
  runBridgeServerCli,
1284
1296
  parseBridgeServerCliArgs,
1285
1297
  getBridgeServerCliHelpText