From adccd3e738ed577598ec316caa456813084b3cb3 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 28 Apr 2026 22:04:34 +0000 Subject: [PATCH] fix(appkit): allow undefined values in IGenieConfig.spaces The literal `genie({ spaces: { wanderbricks: process.env.X } })` does not typecheck under tsc strict: `process.env.X` is `string | undefined`, but `spaces` was typed `Record`. Callers had to hoist into a local with a runtime check, or use a non-null assertion, even though the plugin's own resolveSpaceId already coalesces missing entries to null. Loosen the type to `Record` so env-driven configs work without ceremony. Resolution behaviour is unchanged: aliases mapped to undefined values fall through to "Unknown space alias" the same way as missing keys, since `?? null` already handled both cases. New test locks this in. Signed-off-by: James Broadhead --- packages/appkit/src/plugins/genie/genie.ts | 2 +- .../src/plugins/genie/tests/genie.test.ts | 33 +++++++++++++++++++ packages/appkit/src/plugins/genie/types.ts | 11 +++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/appkit/src/plugins/genie/genie.ts b/packages/appkit/src/plugins/genie/genie.ts index 712aadbf5..350048387 100644 --- a/packages/appkit/src/plugins/genie/genie.ts +++ b/packages/appkit/src/plugins/genie/genie.ts @@ -38,7 +38,7 @@ export class GeniePlugin extends Plugin { }); } - private defaultSpaces(): Record { + private defaultSpaces(): Record { const spaceId = process.env.DATABRICKS_GENIE_SPACE_ID; return spaceId ? { default: spaceId } : {}; } diff --git a/packages/appkit/src/plugins/genie/tests/genie.test.ts b/packages/appkit/src/plugins/genie/tests/genie.test.ts index 3cf0784d6..9cf3d6810 100644 --- a/packages/appkit/src/plugins/genie/tests/genie.test.ts +++ b/packages/appkit/src/plugins/genie/tests/genie.test.ts @@ -1108,6 +1108,39 @@ describe("Genie Plugin", () => { error: "Unknown space alias: default", }); }); + + test("should 404 when an alias maps to undefined (e.g. unset process.env.X)", async () => { + // The IGenieConfig.spaces type accepts `string | undefined` so callers + // can pass process.env values directly. Resolution still falls through + // to "Unknown space alias", same as a missing key. + delete process.env.DATABRICKS_GENIE_SPACE_ID; + + const plugin = new GeniePlugin({ + timeout: 5000, + spaces: { wanderbricks: process.env.DATABRICKS_GENIE_SPACE_ID }, + }); + const { router, getHandler } = createMockRouter(); + + plugin.injectRoutes(router); + + const handler = getHandler("POST", "/:alias/messages"); + const mockReq = createMockRequest({ + params: { alias: "wanderbricks" }, + body: { content: "test question" }, + headers: { + "x-forwarded-access-token": "user-token", + "x-forwarded-user": "user-1", + }, + }); + const mockRes = createMockResponse(); + + await handler(mockReq, mockRes); + + expect(mockRes.status).toHaveBeenCalledWith(404); + expect(mockRes.json).toHaveBeenCalledWith({ + error: "Unknown space alias: wanderbricks", + }); + }); }); describe("getConversation with pageToken", () => { diff --git a/packages/appkit/src/plugins/genie/types.ts b/packages/appkit/src/plugins/genie/types.ts index 3d73a7b2a..7f8d22a4b 100644 --- a/packages/appkit/src/plugins/genie/types.ts +++ b/packages/appkit/src/plugins/genie/types.ts @@ -5,8 +5,15 @@ export type { GenieStreamEvent } from "shared"; export type { GenieConversationHistoryResponse } from "../../connectors/genie"; export interface IGenieConfig extends BasePluginConfig { - /** Map of alias → Genie Space ID. Defaults to { default: DATABRICKS_GENIE_SPACE_ID } if omitted. */ - spaces?: Record; + /** + * Map of alias → Genie Space ID. Defaults to { default: DATABRICKS_GENIE_SPACE_ID } if omitted. + * + * Values may be `undefined` so callers can pass `process.env.X` directly + * (which is `string | undefined`) without a hoist-and-narrow dance. Aliases + * with undefined values resolve to "unknown alias" at request time, same + * as missing keys. + */ + spaces?: Record; /** Genie polling timeout in ms. Set to 0 for indefinite. Default: 120000 (2 min) */ timeout?: number; }