@jxsuite/server 4.5.0 → 4.5.1

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": "@jxsuite/server",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Jx development server with live reload, server-side proxy, and studio integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,9 +44,9 @@
44
44
  "@jxsuite/ai": "^0.39.3",
45
45
  "@jxsuite/catalog": "^0.1.0",
46
46
  "@jxsuite/collab": "^0.11.1",
47
- "@jxsuite/compiler": "^5.1.2",
47
+ "@jxsuite/compiler": "^5.1.3",
48
48
  "@jxsuite/create": "^1.3.14",
49
- "@jxsuite/import": "^0.40.9",
49
+ "@jxsuite/import": "^0.40.10",
50
50
  "@jxsuite/protocol": "^2.6.1",
51
51
  "@jxsuite/runtime": "^4.1.0",
52
52
  "@jxsuite/schema": "^3.0.0",
@@ -54,8 +54,8 @@
54
54
  "@jxsuite/starters": "^2.0.1",
55
55
  "chokidar": "^5.0.0",
56
56
  "kysely": "^0.29.6",
57
- "oxfmt": "^0.72.0",
58
- "zod": "^4.6.5"
57
+ "oxfmt": "^0.73.0",
58
+ "zod": "^4.6.7"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@jxsuite/auth": "^0.6.2",
package/src/ai-api.ts CHANGED
@@ -39,6 +39,7 @@ import {
39
39
  AI_SESSION_HEADER,
40
40
  affinityKey,
41
41
  createChatHandler,
42
+ DEFAULT_UPSTREAM_BASE_URL,
42
43
  extractUpstreamErrorMessage,
43
44
  isAiSessionId,
44
45
  modelsResponse,
@@ -46,12 +47,11 @@ import {
46
47
  } from "@jxsuite/ai/gateway";
47
48
  import type { GatewayRefusal, Upstream } from "@jxsuite/ai/gateway";
48
49
  import { problemDetails } from "@jxsuite/protocol";
50
+ import { errorMessage } from "@jxsuite/schema/parse";
49
51
  import { problemTypeForStatus } from "./problem.ts";
50
52
 
51
53
  // ─── Configuration ───────────────────────────────────────────────────────────
52
54
 
53
- const DEFAULT_BASE_URL = "https://api.openai.com/v1";
54
-
55
55
  /** The model a chat request that names none is forwarded with. */
56
56
  const DEFAULT_MODEL = "gpt-4o";
57
57
 
@@ -120,7 +120,7 @@ function getConfig(req: Request): AiConfig {
120
120
  }
121
121
 
122
122
  const baseFromHeader = Boolean(baseUrlHeader);
123
- const baseUrl = baseUrlHeader || process.env.OPENAI_BASE_URL || DEFAULT_BASE_URL;
123
+ const baseUrl = baseUrlHeader || process.env.OPENAI_BASE_URL || DEFAULT_UPSTREAM_BASE_URL;
124
124
 
125
125
  if (isBlockedHost(baseUrl)) {
126
126
  return {
@@ -261,6 +261,10 @@ export async function handleModels(req: Request): Promise<Response> {
261
261
  });
262
262
  }
263
263
 
264
+ /* SAFETY: an OpenAI-compatible `/models` answers either the `{ object: "list", data }`
265
+ envelope or a bare array of model entries. Both arrays are confirmed with Array.isArray
266
+ below before use, and a body outside the contract (e.g. `null`) throws into the catch below,
267
+ which answers with the default models. */
264
268
  const data = (await upstreamResp.json()) as { data?: ModelEntry[] } | ModelEntry[];
265
269
  // OpenAI /models returns { object: "list", data: [{ id, ... }] }
266
270
  // Map to our simpler format.
@@ -285,7 +289,7 @@ export async function handleModels(req: Request): Promise<Response> {
285
289
  configured: true,
286
290
  managed: false,
287
291
  upstreamError: "network",
288
- upstreamMessage: (error as Error).message,
292
+ upstreamMessage: errorMessage(error),
289
293
  });
290
294
  }
291
295
  }
package/src/build.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /** Build.js — Configurable Bun.build pipeline */
2
2
 
3
+ import { isFunction } from "@jxsuite/schema/guards";
3
4
  import type { BuildConfig } from "bun";
4
5
 
5
6
  /**
@@ -66,12 +67,11 @@ export async function rebuild(builds: BuildEntry[], changedFile: string) {
66
67
  if (!entry.match) {
67
68
  continue;
68
69
  }
69
- const matches =
70
- typeof entry.match === "function"
71
- ? entry.match(changedFile)
72
- : entry.match instanceof RegExp
73
- ? entry.match.test(changedFile)
74
- : false;
70
+ const matches = isFunction(entry.match)
71
+ ? entry.match(changedFile)
72
+ : entry.match instanceof RegExp
73
+ ? entry.match.test(changedFile)
74
+ : false;
75
75
  if (!matches) {
76
76
  continue;
77
77
  }
package/src/code-api.ts CHANGED
@@ -7,20 +7,29 @@
7
7
 
8
8
  import { tmpdir } from "node:os";
9
9
  import { errorMessage } from "@jxsuite/schema/parse";
10
+ import type { JsonValue } from "@jxsuite/schema/types";
10
11
  import { dirname, join } from "node:path";
11
12
  import { existsSync } from "node:fs";
12
13
  import { unlink } from "node:fs/promises";
13
14
  import { format } from "oxfmt";
14
15
  import { problem } from "./problem.ts";
15
16
 
17
+ /** A label's source span; oxlint's other span members pass through to the client unchanged. */
18
+ interface OxcSpan {
19
+ offset: number;
20
+ line: number;
21
+ [key: string]: JsonValue;
22
+ }
23
+
16
24
  interface OxcLabel {
17
- span: { offset: number; line: number; [key: string]: unknown };
18
- [key: string]: unknown;
25
+ span: OxcSpan;
26
+ [key: string]: JsonValue;
19
27
  }
20
28
 
29
+ /** One entry of `oxlint --format=json` output; members other than `labels` pass through. */
21
30
  interface OxcDiagnostic {
22
31
  labels?: OxcLabel[];
23
- [key: string]: unknown;
32
+ [key: string]: JsonValue | undefined;
24
33
  }
25
34
 
26
35
  interface CodeApiBody {
@@ -87,19 +96,19 @@ function unwrapFormatted(formatted: string) {
87
96
  * @param {number} headerLen
88
97
  */
89
98
  function adjustDiagnostics(diagnostics: OxcDiagnostic[], headerLen: number) {
90
- return diagnostics
91
- .filter((d) => {
92
- const line = d.labels?.[0]?.span?.line;
93
- return line == null || line > 1;
94
- })
95
- .map((d) => {
96
- // Diagnostics are freshly parsed and serialized right after; mutate in place
97
- for (const label of d.labels ?? []) {
98
- label.span.line -= 1;
99
- label.span.offset -= headerLen;
100
- }
101
- return d;
102
- });
99
+ return diagnostics.flatMap((d) => {
100
+ const line = d.labels?.[0]?.span.line;
101
+ // A diagnostic on line 1 points at the synthetic wrapper header, not the user's code
102
+ if (line != null && line <= 1) {
103
+ return [];
104
+ }
105
+ // Diagnostics are freshly parsed and serialized right after; mutate in place
106
+ for (const label of d.labels ?? []) {
107
+ label.span.line -= 1;
108
+ label.span.offset -= headerLen;
109
+ }
110
+ return [d];
111
+ });
103
112
  }
104
113
 
105
114
  // ─── Reusable transpiler ─────────────────────────────────────────────────────
@@ -120,6 +129,8 @@ export async function handleCodeApi(req: Request, url: URL) {
120
129
 
121
130
  let body: CodeApiBody;
122
131
  try {
132
+ /* SAFETY: the Studio function editor is this route's only client, and it posts `{ code, args }`
133
+ with a string body and string parameter names; malformed JSON is refused just below. */
123
134
  body = (await req.json()) as CodeApiBody;
124
135
  } catch {
125
136
  return problem("invalidRequest", "Invalid JSON");
@@ -196,6 +207,8 @@ export async function handleCodeApi(req: Request, url: URL) {
196
207
  const output = await new Response(proc.stdout).text();
197
208
  await proc.exited;
198
209
 
210
+ /* SAFETY: `oxlint --format=json` prints `{ diagnostics: [...] }` whose labels each carry a
211
+ numeric `span.line`/`span.offset`; output that is not JSON throws into the catch below. */
199
212
  const parsed = JSON.parse(output) as { diagnostics?: OxcDiagnostic[] };
200
213
  const adjusted = adjustDiagnostics(parsed.diagnostics ?? [], headerLen);
201
214
  return Response.json({ diagnostics: adjusted });
package/src/collab.ts CHANGED
@@ -17,6 +17,7 @@ import { resolve } from "node:path";
17
17
  import { readFile, writeFile } from "node:fs/promises";
18
18
  import { colorForKey } from "@jxsuite/collab/awareness-types";
19
19
  import { createCollabHost } from "@jxsuite/collab/room";
20
+ import { isString } from "@jxsuite/schema/guards";
20
21
  import {
21
22
  COLLAB_SUBPROTOCOL,
22
23
  offeredSubprotocols,
@@ -215,10 +216,13 @@ export function createCollabRegistry(opts: {
215
216
  if (answer.reject !== null) {
216
217
  return problem("invalidRequest", answer.reject);
217
218
  }
218
- const upgraded = server.upgrade(req, {
219
- data: { connection: null },
220
- ...(answer.echo === null ? {} : { headers: { "Sec-WebSocket-Protocol": answer.echo } }),
221
- });
219
+ const upgraded =
220
+ answer.echo === null
221
+ ? server.upgrade(req, { data: { connection: null } })
222
+ : server.upgrade(req, {
223
+ data: { connection: null },
224
+ headers: { "Sec-WebSocket-Protocol": answer.echo },
225
+ });
222
226
  return upgraded ? undefined : problem("invalidRequest", "Upgrade failed");
223
227
  }
224
228
  /*
@@ -254,7 +258,7 @@ export function createCollabRegistry(opts: {
254
258
  ws.data.connection = null;
255
259
  },
256
260
  message(ws: ServerWebSocket<CollabSocketData>, message: string | Buffer) {
257
- if (typeof message === "string") {
261
+ if (isString(message)) {
258
262
  return;
259
263
  }
260
264
  ws.data.connection?.handleMessage(new Uint8Array(message));