@llmops/app 0.1.2-beta.3 → 0.1.3-beta.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/dist/index.cjs CHANGED
@@ -13388,8 +13388,22 @@ const requestHeadersSchema = object({
13388
13388
  return baseContentType === CONTENT_TYPES.APPLICATION_JSON || baseContentType === CONTENT_TYPES.MULTIPART_FORM_DATA || baseContentType.startsWith(CONTENT_TYPES.GENERIC_AUDIO_PATTERN);
13389
13389
  }, { message: "Invalid content type. Must be application/json, multipart/form-data, or audio/*" }),
13390
13390
  "x-llmops-config": string$1({ error: "LLMOps Config ID was not provided." }),
13391
- "x-llmops-environment": string$1().optional()
13391
+ authorization: string$1({ error: "Authorization header with environment secret is required." })
13392
13392
  });
13393
+ /**
13394
+ * Extracts environment secret from Authorization header.
13395
+ * The user passes it in the OpenAI apiKey option which comes as "Bearer <envSecret>"
13396
+ * @param authHeader - The Authorization header value (e.g., "Bearer sk_xxxxx")
13397
+ * @returns The environment secret
13398
+ * @throws Error if the header is missing or invalid
13399
+ */
13400
+ function extractEnvSecretFromAuth(authHeader) {
13401
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
13402
+ if (!match) throw new Error("Invalid Authorization header format. Expected: Bearer <environment-secret>");
13403
+ const token = match[1].trim();
13404
+ if (!token) throw new Error("Environment secret cannot be empty");
13405
+ return token;
13406
+ }
13393
13407
  const requestValidator = zv("header", requestHeadersSchema);
13394
13408
 
13395
13409
  //#endregion
@@ -13397,43 +13411,37 @@ const requestValidator = zv("header", requestHeadersSchema);
13397
13411
  const createRequestGuardMiddleware = () => {
13398
13412
  return async (c, next) => {
13399
13413
  const headers = await requestHeadersSchema.safeParseAsync(c.req.header());
13400
- if (headers.success) if (!headers.data["x-llmops-environment"]) {
13401
- const origin = c.req.header("origin");
13402
- const host = c.req.header("host");
13403
- if (origin) {
13404
- const originUrl = new URL(origin);
13405
- const hostWithoutPort = host?.split(":")[0];
13406
- const originHost = originUrl.hostname;
13407
- if (originHost !== hostWithoutPort && originHost !== host) return c.json({ message: "Cross-origin requests require an environment ID" }, 403);
13408
- }
13409
- c.set("configId", headers["data"]["x-llmops-config"]);
13410
- c.set("envSec", headers["data"]["x-llmops-environment"]);
13411
- await next();
13412
- } else {
13413
- c.set("configId", headers["data"]["x-llmops-config"]);
13414
- c.set("envSec", headers["data"]["x-llmops-environment"]);
13415
- await (0, hono_cors.cors)({
13416
- origin: "*",
13417
- allowMethods: [
13418
- "GET",
13419
- "POST",
13420
- "PUT",
13421
- "DELETE",
13422
- "OPTIONS"
13423
- ],
13424
- allowHeaders: [
13425
- "Content-Type",
13426
- "Authorization",
13427
- "x-llmops-config",
13428
- "x-llmops-environment"
13429
- ]
13430
- })(c, next);
13431
- }
13432
- else
13414
+ if (!headers.success)
13433
13415
  /**
13434
13416
  * @todo Refactor this to give OpenAI specific response.
13435
13417
  */
13436
- return c.json({ message: "Invalid request headers" }, 400);
13418
+ return c.json({
13419
+ message: "Invalid request headers",
13420
+ errors: headers.error.flatten().fieldErrors
13421
+ }, 400);
13422
+ let envSec;
13423
+ try {
13424
+ envSec = extractEnvSecretFromAuth(headers.data["authorization"]);
13425
+ } catch (error$45) {
13426
+ return c.json({ message: error$45 instanceof Error ? error$45.message : "Invalid authorization" }, 401);
13427
+ }
13428
+ c.set("configId", headers.data["x-llmops-config"]);
13429
+ c.set("envSec", envSec);
13430
+ await (0, hono_cors.cors)({
13431
+ origin: "*",
13432
+ allowMethods: [
13433
+ "GET",
13434
+ "POST",
13435
+ "PUT",
13436
+ "DELETE",
13437
+ "OPTIONS"
13438
+ ],
13439
+ allowHeaders: [
13440
+ "Content-Type",
13441
+ "Authorization",
13442
+ "x-llmops-config"
13443
+ ]
13444
+ })(c, next);
13437
13445
  };
13438
13446
  };
13439
13447
 
package/dist/index.mjs CHANGED
@@ -13362,8 +13362,22 @@ const requestHeadersSchema = object({
13362
13362
  return baseContentType === CONTENT_TYPES.APPLICATION_JSON || baseContentType === CONTENT_TYPES.MULTIPART_FORM_DATA || baseContentType.startsWith(CONTENT_TYPES.GENERIC_AUDIO_PATTERN);
13363
13363
  }, { message: "Invalid content type. Must be application/json, multipart/form-data, or audio/*" }),
13364
13364
  "x-llmops-config": string$1({ error: "LLMOps Config ID was not provided." }),
13365
- "x-llmops-environment": string$1().optional()
13365
+ authorization: string$1({ error: "Authorization header with environment secret is required." })
13366
13366
  });
13367
+ /**
13368
+ * Extracts environment secret from Authorization header.
13369
+ * The user passes it in the OpenAI apiKey option which comes as "Bearer <envSecret>"
13370
+ * @param authHeader - The Authorization header value (e.g., "Bearer sk_xxxxx")
13371
+ * @returns The environment secret
13372
+ * @throws Error if the header is missing or invalid
13373
+ */
13374
+ function extractEnvSecretFromAuth(authHeader) {
13375
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
13376
+ if (!match) throw new Error("Invalid Authorization header format. Expected: Bearer <environment-secret>");
13377
+ const token = match[1].trim();
13378
+ if (!token) throw new Error("Environment secret cannot be empty");
13379
+ return token;
13380
+ }
13367
13381
  const requestValidator = zv("header", requestHeadersSchema);
13368
13382
 
13369
13383
  //#endregion
@@ -13371,43 +13385,37 @@ const requestValidator = zv("header", requestHeadersSchema);
13371
13385
  const createRequestGuardMiddleware = () => {
13372
13386
  return async (c, next) => {
13373
13387
  const headers = await requestHeadersSchema.safeParseAsync(c.req.header());
13374
- if (headers.success) if (!headers.data["x-llmops-environment"]) {
13375
- const origin = c.req.header("origin");
13376
- const host = c.req.header("host");
13377
- if (origin) {
13378
- const originUrl = new URL(origin);
13379
- const hostWithoutPort = host?.split(":")[0];
13380
- const originHost = originUrl.hostname;
13381
- if (originHost !== hostWithoutPort && originHost !== host) return c.json({ message: "Cross-origin requests require an environment ID" }, 403);
13382
- }
13383
- c.set("configId", headers["data"]["x-llmops-config"]);
13384
- c.set("envSec", headers["data"]["x-llmops-environment"]);
13385
- await next();
13386
- } else {
13387
- c.set("configId", headers["data"]["x-llmops-config"]);
13388
- c.set("envSec", headers["data"]["x-llmops-environment"]);
13389
- await cors({
13390
- origin: "*",
13391
- allowMethods: [
13392
- "GET",
13393
- "POST",
13394
- "PUT",
13395
- "DELETE",
13396
- "OPTIONS"
13397
- ],
13398
- allowHeaders: [
13399
- "Content-Type",
13400
- "Authorization",
13401
- "x-llmops-config",
13402
- "x-llmops-environment"
13403
- ]
13404
- })(c, next);
13405
- }
13406
- else
13388
+ if (!headers.success)
13407
13389
  /**
13408
13390
  * @todo Refactor this to give OpenAI specific response.
13409
13391
  */
13410
- return c.json({ message: "Invalid request headers" }, 400);
13392
+ return c.json({
13393
+ message: "Invalid request headers",
13394
+ errors: headers.error.flatten().fieldErrors
13395
+ }, 400);
13396
+ let envSec;
13397
+ try {
13398
+ envSec = extractEnvSecretFromAuth(headers.data["authorization"]);
13399
+ } catch (error$45) {
13400
+ return c.json({ message: error$45 instanceof Error ? error$45.message : "Invalid authorization" }, 401);
13401
+ }
13402
+ c.set("configId", headers.data["x-llmops-config"]);
13403
+ c.set("envSec", envSec);
13404
+ await cors({
13405
+ origin: "*",
13406
+ allowMethods: [
13407
+ "GET",
13408
+ "POST",
13409
+ "PUT",
13410
+ "DELETE",
13411
+ "OPTIONS"
13412
+ ],
13413
+ allowHeaders: [
13414
+ "Content-Type",
13415
+ "Authorization",
13416
+ "x-llmops-config"
13417
+ ]
13418
+ })(c, next);
13411
13419
  };
13412
13420
  };
13413
13421
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmops/app",
3
- "version": "0.1.2-beta.3",
3
+ "version": "0.1.3-beta.1",
4
4
  "description": "LLMOps application with server and client",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -64,8 +64,8 @@
64
64
  "motion": "^12.23.25",
65
65
  "react-aria-components": "^1.13.0",
66
66
  "react-hook-form": "^7.68.0",
67
- "@llmops/core": "^0.1.2-beta.3",
68
- "@llmops/gateway": "^0.1.2-beta.3"
67
+ "@llmops/core": "^0.1.3-beta.1",
68
+ "@llmops/gateway": "^0.1.3-beta.1"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "react": "^19.2.1",