@neutrome/open-ai-router 0.4.2 → 0.5.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 (62) hide show
  1. package/.dev.vars.example +2 -0
  2. package/README.md +5 -1
  3. package/package.json +5 -4
  4. package/src/admission/access.ts +0 -1
  5. package/src/admission/types.ts +0 -1
  6. package/src/app/factory.test.ts +40 -34
  7. package/src/app/factory.ts +120 -173
  8. package/src/app/google-genai-route.ts +48 -0
  9. package/src/app/http-utils.ts +14 -0
  10. package/src/app/model-listing.ts +29 -0
  11. package/src/app/response-lifecycle.ts +66 -0
  12. package/src/app/types.ts +38 -0
  13. package/src/index.ts +11 -43
  14. package/src/otlp.ts +148 -0
  15. package/src/router/audit.ts +5 -12
  16. package/src/router/config.ts +4 -4
  17. package/src/router/error-codec.ts +170 -0
  18. package/src/router/errors.ts +38 -1
  19. package/src/router/execute.test.ts +177 -107
  20. package/src/router/execute.ts +68 -924
  21. package/src/router/execution-events.ts +39 -0
  22. package/src/router/execution-invocation.ts +144 -0
  23. package/src/router/execution-observation.ts +161 -0
  24. package/src/router/execution-resolution.ts +120 -0
  25. package/src/router/execution-runtime.test.ts +156 -83
  26. package/src/router/execution-runtime.ts +144 -542
  27. package/src/router/execution-transforms.ts +82 -0
  28. package/src/router/execution-types.ts +8 -19
  29. package/src/router/index.ts +6 -5
  30. package/src/router/mcp.ts +12 -6
  31. package/src/router/provider-invoker.ts +134 -0
  32. package/src/router/provider-stream.ts +192 -0
  33. package/src/router/provider-telemetry.ts +59 -0
  34. package/src/router/remote-mcp-execution.ts +61 -0
  35. package/src/router/request-program.ts +16 -0
  36. package/src/router/request-target.ts +56 -0
  37. package/src/router/resolve.test.ts +43 -10
  38. package/src/router/resolve.ts +50 -28
  39. package/src/router/response-delivery.ts +141 -0
  40. package/src/router/runtime.test.ts +56 -0
  41. package/src/router/runtime.ts +79 -9
  42. package/src/router/targets.ts +3 -1
  43. package/src/router/upstream-client.ts +137 -0
  44. package/src/telemetry.test.ts +50 -0
  45. package/src/telemetry.ts +316 -0
  46. package/src/trace-labels.ts +29 -0
  47. package/src/upstream-auth/env.ts +15 -3
  48. package/src/upstream-auth/proxy.ts +6 -1
  49. package/src/upstream-auth/registry.ts +3 -1
  50. package/src/util/glob.ts +1 -1
  51. package/src/util/model-syntax.ts +3 -3
  52. package/src/worker.ts +12 -6
  53. package/worker-configuration.d.ts +195 -164
  54. package/pnpm-workspace.yaml +0 -4
  55. package/src/admission/jwt.test.ts +0 -96
  56. package/src/admission/jwt.ts +0 -221
  57. package/src/app/handlers.ts +0 -198
  58. package/src/example.test.ts +0 -377
  59. package/src/example.ts +0 -73
  60. package/src/observer.test.ts +0 -54
  61. package/src/observer.ts +0 -315
  62. package/src/timing.ts +0 -36
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable */
2
- // Generated by Wrangler by running `wrangler types` (hash: 4a6da9ffb595e182f0a025599f450adb)
3
- // Runtime types generated with workerd@1.20260609.1 2026-05-15 nodejs_compat
2
+ // Generated by Wrangler by running `wrangler types` (hash: 9b122df5ee98160341dc862b002d2fc4)
3
+ // Runtime types generated with workerd@1.20260617.1 2026-05-15 nodejs_compat
4
4
  interface __BaseEnv_Env {
5
+ OPENROUTER_API_KEY: string;
5
6
  }
6
7
  declare namespace Cloudflare {
7
8
  interface GlobalProps {
@@ -10,6 +11,12 @@ declare namespace Cloudflare {
10
11
  interface Env extends __BaseEnv_Env {}
11
12
  }
12
13
  interface Env extends __BaseEnv_Env {}
14
+ type StringifyValues<EnvType extends Record<string, unknown>> = {
15
+ [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
16
+ };
17
+ declare namespace NodeJS {
18
+ interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "OPENROUTER_API_KEY">> {}
19
+ }
13
20
  declare module "*.wasm" {
14
21
  const value: WebAssembly.Module;
15
22
  export default value;
@@ -11077,78 +11084,6 @@ declare abstract class BrowserRun {
11077
11084
  */
11078
11085
  quickAction(action: 'markdown', options: BrowserRunMarkdownOptions): Promise<Response>;
11079
11086
  }
11080
- interface BasicImageTransformations {
11081
- /**
11082
- * Maximum width in image pixels. The value must be an integer.
11083
- */
11084
- width?: number;
11085
- /**
11086
- * Maximum height in image pixels. The value must be an integer.
11087
- */
11088
- height?: number;
11089
- /**
11090
- * Resizing mode as a string. It affects interpretation of width and height
11091
- * options:
11092
- * - scale-down: Similar to contain, but the image is never enlarged. If
11093
- * the image is larger than given width or height, it will be resized.
11094
- * Otherwise its original size will be kept.
11095
- * - contain: Resizes to maximum size that fits within the given width and
11096
- * height. If only a single dimension is given (e.g. only width), the
11097
- * image will be shrunk or enlarged to exactly match that dimension.
11098
- * Aspect ratio is always preserved.
11099
- * - cover: Resizes (shrinks or enlarges) to fill the entire area of width
11100
- * and height. If the image has an aspect ratio different from the ratio
11101
- * of width and height, it will be cropped to fit.
11102
- * - crop: The image will be shrunk and cropped to fit within the area
11103
- * specified by width and height. The image will not be enlarged. For images
11104
- * smaller than the given dimensions it's the same as scale-down. For
11105
- * images larger than the given dimensions, it's the same as cover.
11106
- * See also trim.
11107
- * - pad: Resizes to the maximum size that fits within the given width and
11108
- * height, and then fills the remaining area with a background color
11109
- * (white by default). Use of this mode is not recommended, as the same
11110
- * effect can be more efficiently achieved with the contain mode and the
11111
- * CSS object-fit: contain property.
11112
- * - squeeze: Stretches and deforms to the width and height given, even if it
11113
- * breaks aspect ratio
11114
- */
11115
- fit?: "scale-down" | "contain" | "cover" | "crop" | "pad" | "squeeze";
11116
- /**
11117
- * Image segmentation using artificial intelligence models. Sets pixels not
11118
- * within selected segment area to transparent e.g "foreground" sets every
11119
- * background pixel as transparent.
11120
- */
11121
- segment?: "foreground";
11122
- /**
11123
- * When cropping with fit: "cover", this defines the side or point that should
11124
- * be left uncropped. The value is either a string
11125
- * "left", "right", "top", "bottom", "auto", or "center" (the default),
11126
- * or an object {x, y} containing focal point coordinates in the original
11127
- * image expressed as fractions ranging from 0.0 (top or left) to 1.0
11128
- * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will
11129
- * crop bottom or left and right sides as necessary, but won’t crop anything
11130
- * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to
11131
- * preserve as much as possible around a point at 20% of the height of the
11132
- * source image.
11133
- */
11134
- gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates;
11135
- /**
11136
- * Background color to add underneath the image. Applies only to images with
11137
- * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),
11138
- * hsl(…), etc.)
11139
- */
11140
- background?: string;
11141
- /**
11142
- * Number of degrees (90, 180, 270) to rotate the image by. width and height
11143
- * options refer to axes after rotation.
11144
- */
11145
- rotate?: 0 | 90 | 180 | 270 | 360;
11146
- }
11147
- interface BasicImageTransformationsGravityCoordinates {
11148
- x?: number;
11149
- y?: number;
11150
- mode?: 'remainder' | 'box-center';
11151
- }
11152
11087
  /**
11153
11088
  * In addition to the properties you can set in the RequestInit dict
11154
11089
  * that you pass as an argument to the Request constructor, you can
@@ -11244,6 +11179,187 @@ interface RequestInitCfProperties extends Record<string, unknown> {
11244
11179
  */
11245
11180
  resolveOverride?: string;
11246
11181
  }
11182
+ interface BasicImageTransformations {
11183
+ /**
11184
+ * Maximum width in image pixels. The value must be an integer.
11185
+ */
11186
+ width?: number;
11187
+ /**
11188
+ * Maximum height in image pixels. The value must be an integer.
11189
+ */
11190
+ height?: number;
11191
+ /**
11192
+ * When cropping with fit: "cover", this defines the side or point that should
11193
+ * be left uncropped. The value is either a string
11194
+ * "left", "right", "top", "bottom", "auto", or "center" (the default),
11195
+ * or an object {x, y} containing focal point coordinates in the original
11196
+ * image expressed as fractions ranging from 0.0 (top or left) to 1.0
11197
+ * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will
11198
+ * crop bottom or left and right sides as necessary, but won’t crop anything
11199
+ * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to
11200
+ * preserve as much as possible around a point at 20% of the height of the
11201
+ * source image.
11202
+ */
11203
+ gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates;
11204
+ /**
11205
+ * Specifies how closely the image is cropped toward detected faces when combined
11206
+ * with the gravity=face option. Accepts a valid range between 0.0 (includes as much
11207
+ * of the background as possible) and 1.0 (crops the image as closely to the face as
11208
+ * possible). The default is 0.
11209
+ */
11210
+ zoom?: number;
11211
+ /**
11212
+ * Resizing mode as a string. It affects interpretation of width and height
11213
+ * options:
11214
+ * - scale-down: Similar to contain, but the image is never enlarged. If
11215
+ * the image is larger than given width or height, it will be resized.
11216
+ * Otherwise its original size will be kept.
11217
+ * - scale-up: Similar to contain, but the image is never shrunk. If the
11218
+ * image is smaller than the given width or height, it will be resized.
11219
+ * Otherwise its original size will be kept.
11220
+ * - contain: Resizes to maximum size that fits within the given width and
11221
+ * height. If only a single dimension is given (e.g. only width), the
11222
+ * image will be shrunk or enlarged to exactly match that dimension.
11223
+ * Aspect ratio is always preserved.
11224
+ * - cover: Resizes (shrinks or enlarges) to fill the entire area of width
11225
+ * and height. If the image has an aspect ratio different from the ratio
11226
+ * of width and height, it will be cropped to fit.
11227
+ * - crop: The image will be shrunk and cropped to fit within the area
11228
+ * specified by width and height. The image will not be enlarged. For images
11229
+ * smaller than the given dimensions it's the same as scale-down. For
11230
+ * images larger than the given dimensions, it's the same as cover.
11231
+ * See also trim.
11232
+ * - pad: Resizes to the maximum size that fits within the given width and
11233
+ * height, and then fills the remaining area with a background color
11234
+ * (white by default). Use of this mode is not recommended, as the same
11235
+ * effect can be more efficiently achieved with the contain mode and the
11236
+ * CSS object-fit: contain property.
11237
+ * - squeeze: Stretches and deforms to the width and height given, even if it
11238
+ * breaks aspect ratio
11239
+ */
11240
+ fit?: "scale-down" | "scale-up" | "contain" | "cover" | "crop" | "pad" | "squeeze";
11241
+ /**
11242
+ * Allows you to trim your image. Takes dpr into account and is performed before
11243
+ * resizing or rotation.
11244
+ *
11245
+ * It can be used as:
11246
+ * - left, top, right, bottom - it will specify the number of pixels to cut
11247
+ * off each side
11248
+ * - width, height - the width/height you'd like to end up with - can be used
11249
+ * in combination with the properties above
11250
+ * - border - this will automatically trim the surroundings of an image based on
11251
+ * it's color. It consists of three properties:
11252
+ * - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)
11253
+ * - tolerance: difference from color to treat as color
11254
+ * - keep: the number of pixels of border to keep
11255
+ */
11256
+ trim?: "border" | {
11257
+ top?: number;
11258
+ bottom?: number;
11259
+ left?: number;
11260
+ right?: number;
11261
+ width?: number;
11262
+ height?: number;
11263
+ border?: boolean | {
11264
+ color?: string;
11265
+ tolerance?: number;
11266
+ keep?: number;
11267
+ };
11268
+ };
11269
+ /**
11270
+ * Background color to add underneath the image. Applies only to images with
11271
+ * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…),
11272
+ * hsl(…), etc.)
11273
+ */
11274
+ background?: string;
11275
+ /**
11276
+ * Flips the images horizontally, vertically, or both. Flipping is applied before
11277
+ * rotation, so if you apply flip=h,rotate=90 then the image will be flipped
11278
+ * horizontally, then rotated by 90 degrees.
11279
+ */
11280
+ flip?: 'h' | 'v' | 'hv';
11281
+ /**
11282
+ * Number of degrees (90, 180, 270) to rotate the image by. width and height
11283
+ * options refer to axes after rotation.
11284
+ */
11285
+ rotate?: 0 | 90 | 180 | 270 | 360;
11286
+ /**
11287
+ * Strength of sharpening filter to apply to the image. Floating-point
11288
+ * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a
11289
+ * recommended value for downscaled images.
11290
+ */
11291
+ sharpen?: number;
11292
+ /**
11293
+ * Radius of a blur filter (approximate gaussian). Maximum supported radius
11294
+ * is 250.
11295
+ */
11296
+ blur?: number;
11297
+ /**
11298
+ * Increase contrast by a factor. A value of 1.0 equals no change, a value of
11299
+ * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is
11300
+ * ignored.
11301
+ */
11302
+ contrast?: number;
11303
+ /**
11304
+ * Increase brightness by a factor. A value of 1.0 equals no change, a value
11305
+ * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.
11306
+ * 0 is ignored.
11307
+ */
11308
+ brightness?: number;
11309
+ /**
11310
+ * Increase exposure by a factor. A value of 1.0 equals no change, a value of
11311
+ * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.
11312
+ */
11313
+ gamma?: number;
11314
+ /**
11315
+ * Increase contrast by a factor. A value of 1.0 equals no change, a value of
11316
+ * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is
11317
+ * ignored.
11318
+ */
11319
+ saturation?: number;
11320
+ /**
11321
+ * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it
11322
+ * easier to specify higher-DPI sizes in <img srcset>.
11323
+ */
11324
+ dpr?: number;
11325
+ /**
11326
+ * Adds a border around the image. The border is added after resizing. Border
11327
+ * width takes dpr into account, and can be specified either using a single
11328
+ * width property, or individually for each side.
11329
+ */
11330
+ border?: {
11331
+ color: string;
11332
+ width: number;
11333
+ } | {
11334
+ color: string;
11335
+ top: number;
11336
+ right: number;
11337
+ bottom: number;
11338
+ left: number;
11339
+ };
11340
+ /**
11341
+ * Image segmentation using artificial intelligence models. Sets pixels not
11342
+ * within selected segment area to transparent e.g "foreground" sets every
11343
+ * background pixel as transparent.
11344
+ */
11345
+ segment?: "foreground";
11346
+ /**
11347
+ * Controls the algorithm used when an image needs to be enlarged. This
11348
+ * parameter works with any fit mode that upscales, such as `contain`,
11349
+ * `cover`, and `scale-up`. It has no effect when `fit=scale-down` or when
11350
+ * the target dimensions are smaller than the source.
11351
+ * - interpolate: Uses bicubic interpolation, which may reduce image quality.
11352
+ * This is the default behavior when `upscale` is not specified.
11353
+ * - generate: Uses AI upscaling to produce sharper, more detailed results
11354
+ * when enlarging images.
11355
+ */
11356
+ upscale?: "interpolate" | "generate";
11357
+ }
11358
+ interface BasicImageTransformationsGravityCoordinates {
11359
+ x?: number;
11360
+ y?: number;
11361
+ mode?: 'remainder' | 'box-center';
11362
+ }
11247
11363
  interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {
11248
11364
  /**
11249
11365
  * Absolute URL of the image file to use for the drawing. It can be any of
@@ -11298,39 +11414,6 @@ interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations {
11298
11414
  right?: number;
11299
11415
  }
11300
11416
  interface RequestInitCfPropertiesImage extends BasicImageTransformations {
11301
- /**
11302
- * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it
11303
- * easier to specify higher-DPI sizes in <img srcset>.
11304
- */
11305
- dpr?: number;
11306
- /**
11307
- * Allows you to trim your image. Takes dpr into account and is performed before
11308
- * resizing or rotation.
11309
- *
11310
- * It can be used as:
11311
- * - left, top, right, bottom - it will specify the number of pixels to cut
11312
- * off each side
11313
- * - width, height - the width/height you'd like to end up with - can be used
11314
- * in combination with the properties above
11315
- * - border - this will automatically trim the surroundings of an image based on
11316
- * it's color. It consists of three properties:
11317
- * - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit)
11318
- * - tolerance: difference from color to treat as color
11319
- * - keep: the number of pixels of border to keep
11320
- */
11321
- trim?: "border" | {
11322
- top?: number;
11323
- bottom?: number;
11324
- left?: number;
11325
- right?: number;
11326
- width?: number;
11327
- height?: number;
11328
- border?: boolean | {
11329
- color?: string;
11330
- tolerance?: number;
11331
- keep?: number;
11332
- };
11333
- };
11334
11417
  /**
11335
11418
  * Quality setting from 1-100 (useful values are in 60-90 range). Lower values
11336
11419
  * make images look worse, but load faster. The default is 85. It applies only
@@ -11372,17 +11455,6 @@ interface RequestInitCfPropertiesImage extends BasicImageTransformations {
11372
11455
  * output formats always discard metadata.
11373
11456
  */
11374
11457
  metadata?: "keep" | "copyright" | "none";
11375
- /**
11376
- * Strength of sharpening filter to apply to the image. Floating-point
11377
- * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a
11378
- * recommended value for downscaled images.
11379
- */
11380
- sharpen?: number;
11381
- /**
11382
- * Radius of a blur filter (approximate gaussian). Maximum supported radius
11383
- * is 250.
11384
- */
11385
- blur?: number;
11386
11458
  /**
11387
11459
  * Overlays are drawn in the order they appear in the array (last array
11388
11460
  * entry is the topmost layer).
@@ -11394,50 +11466,6 @@ interface RequestInitCfPropertiesImage extends BasicImageTransformations {
11394
11466
  * the origin.
11395
11467
  */
11396
11468
  "origin-auth"?: "share-publicly";
11397
- /**
11398
- * Adds a border around the image. The border is added after resizing. Border
11399
- * width takes dpr into account, and can be specified either using a single
11400
- * width property, or individually for each side.
11401
- */
11402
- border?: {
11403
- color: string;
11404
- width: number;
11405
- } | {
11406
- color: string;
11407
- top: number;
11408
- right: number;
11409
- bottom: number;
11410
- left: number;
11411
- };
11412
- /**
11413
- * Increase brightness by a factor. A value of 1.0 equals no change, a value
11414
- * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright.
11415
- * 0 is ignored.
11416
- */
11417
- brightness?: number;
11418
- /**
11419
- * Increase contrast by a factor. A value of 1.0 equals no change, a value of
11420
- * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is
11421
- * ignored.
11422
- */
11423
- contrast?: number;
11424
- /**
11425
- * Increase exposure by a factor. A value of 1.0 equals no change, a value of
11426
- * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored.
11427
- */
11428
- gamma?: number;
11429
- /**
11430
- * Increase contrast by a factor. A value of 1.0 equals no change, a value of
11431
- * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is
11432
- * ignored.
11433
- */
11434
- saturation?: number;
11435
- /**
11436
- * Flips the images horizontally, vertically, or both. Flipping is applied before
11437
- * rotation, so if you apply flip=h,rotate=90 then the image will be flipped
11438
- * horizontally, then rotated by 90 degrees.
11439
- */
11440
- flip?: 'h' | 'v' | 'hv';
11441
11469
  /**
11442
11470
  * Slightly reduces latency on a cache miss by selecting a
11443
11471
  * quickest-to-compress file format, at a cost of increased file size and
@@ -12926,6 +12954,7 @@ declare namespace CloudflareWorkersModule {
12926
12954
  timeout?: WorkflowTimeoutDuration | number;
12927
12955
  sensitive?: WorkflowStepSensitivity;
12928
12956
  };
12957
+ export type WorkflowStepRollbackConfig = Pick<WorkflowStepConfig, 'retries' | 'timeout'>;
12929
12958
  export type WorkflowCronSchedule = {
12930
12959
  /** Cron expression that triggered this event. */
12931
12960
  cron: string;
@@ -12954,14 +12983,16 @@ declare namespace CloudflareWorkersModule {
12954
12983
  config: WorkflowStepConfig;
12955
12984
  };
12956
12985
  export type WorkflowRollbackContext<T = unknown> = {
12986
+ ctx: WorkflowStepContext;
12957
12987
  error: Error;
12958
12988
  output: T | undefined;
12989
+ /** @deprecated Use `ctx.step.name` and `ctx.step.count` instead. */
12959
12990
  stepName: string;
12960
12991
  };
12961
12992
  export type WorkflowRollbackHandler<T = unknown> = (ctx: WorkflowRollbackContext<T>) => Promise<void>;
12962
12993
  export type WorkflowStepRollbackOptions<T = unknown> = {
12963
- rollback?: WorkflowRollbackHandler<T>;
12964
- rollbackConfig?: WorkflowStepConfig;
12994
+ rollback: WorkflowRollbackHandler<T>;
12995
+ rollbackConfig?: WorkflowStepRollbackConfig;
12965
12996
  };
12966
12997
  export abstract class WorkflowStep {
12967
12998
  do<T extends Rpc.Serializable<T>>(name: string, callback: (ctx: WorkflowStepContext) => Promise<T>, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>;
@@ -1,4 +0,0 @@
1
- allowBuilds:
2
- esbuild: true
3
- sharp: true
4
- workerd: true
@@ -1,96 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createRuntimeJwtAuthenticator } from "./jwt.ts";
3
-
4
- describe("runtime JWT admission", () => {
5
- it("accepts a valid ES256 runtime token", async () => {
6
- const auth = await testAuth();
7
- const principal = await createRuntimeJwtAuthenticator({
8
- workspaceId: "workspace_1",
9
- }).authenticate(request(auth.token), env(auth.jwksJson));
10
-
11
- expect(principal).toEqual({
12
- workspaceId: "workspace_1",
13
- keyId: "key_1",
14
- allowedModels: ["support/router"],
15
- allowedNamespaces: null,
16
- });
17
- });
18
-
19
- it("rejects revoked key ids", async () => {
20
- const auth = await testAuth();
21
- const principal = await createRuntimeJwtAuthenticator({
22
- workspaceId: "workspace_1",
23
- }).authenticate(
24
- request(auth.token),
25
- env(auth.jwksJson, JSON.stringify(["key_1"])),
26
- );
27
-
28
- expect(principal).toBeNull();
29
- });
30
-
31
- it("rejects wrong workspace tokens", async () => {
32
- const auth = await testAuth({ workspaceId: "workspace_2" });
33
- const principal = await createRuntimeJwtAuthenticator({
34
- workspaceId: "workspace_1",
35
- }).authenticate(request(auth.token), env(auth.jwksJson));
36
-
37
- expect(principal).toBeNull();
38
- });
39
- });
40
-
41
- async function testAuth(
42
- options: { workspaceId?: string } = {},
43
- ): Promise<{ token: string; jwksJson: string }> {
44
- const keyPair = await crypto.subtle.generateKey(
45
- { name: "ECDSA", namedCurve: "P-256" },
46
- true,
47
- ["sign", "verify"],
48
- );
49
- const publicJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
50
- const header = { alg: "ES256", typ: "JWT", kid: "test-key" };
51
- const claims = {
52
- iss: "neutrome-platform",
53
- aud: "workspace-runtime",
54
- sub: options.workspaceId ?? "workspace_1",
55
- jti: "key_1",
56
- models: ["support/router"],
57
- iat: Math.floor(Date.now() / 1000),
58
- exp: Math.floor(Date.now() / 1000) + 3600,
59
- };
60
- const signingInput = `${base64UrlJson(header)}.${base64UrlJson(claims)}`;
61
- const signature = new Uint8Array(await crypto.subtle.sign(
62
- { name: "ECDSA", hash: "SHA-256" },
63
- keyPair.privateKey,
64
- new TextEncoder().encode(signingInput),
65
- ));
66
- return {
67
- token: `${signingInput}.${base64UrlBytes(signature)}`,
68
- jwksJson: JSON.stringify({ keys: [{ ...publicJwk, kid: "test-key", alg: "ES256", use: "sig" }] }),
69
- };
70
- }
71
-
72
- function request(token: string): Request {
73
- return new Request("http://runtime.test/v1/models", {
74
- headers: { authorization: `Bearer ${token}` },
75
- });
76
- }
77
-
78
- function env(jwksJson: string, revoked = "[]"): Record<string, unknown> {
79
- return {
80
- LIL_RUNTIME_AUTH_JWKS_JSON: jwksJson,
81
- LIL_RUNTIME_AUTH_ISSUER: "neutrome-platform",
82
- LIL_RUNTIME_AUTH_AUDIENCE: "workspace-runtime",
83
- LIL_RUNTIME_REVOKED_KEY_IDS_JSON: revoked,
84
- };
85
- }
86
-
87
- function base64UrlJson(value: unknown): string {
88
- return base64UrlBytes(new TextEncoder().encode(JSON.stringify(value)));
89
- }
90
-
91
- function base64UrlBytes(value: Uint8Array): string {
92
- let binary = "";
93
- for (const byte of value) binary += String.fromCharCode(byte);
94
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
95
- }
96
-