@hebo-ai/gateway 0.6.2-rc0 → 0.6.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.
Files changed (134) hide show
  1. package/README.md +3 -3
  2. package/dist/endpoints/chat-completions/converters.js +26 -21
  3. package/dist/endpoints/chat-completions/handler.js +2 -0
  4. package/dist/endpoints/chat-completions/otel.js +1 -1
  5. package/dist/endpoints/chat-completions/schema.d.ts +4 -18
  6. package/dist/endpoints/chat-completions/schema.js +14 -17
  7. package/dist/endpoints/embeddings/handler.js +2 -0
  8. package/dist/endpoints/embeddings/otel.js +5 -0
  9. package/dist/endpoints/embeddings/schema.d.ts +6 -0
  10. package/dist/endpoints/embeddings/schema.js +4 -1
  11. package/dist/endpoints/models/converters.js +3 -3
  12. package/dist/lifecycle.js +2 -2
  13. package/dist/logger/default.js +3 -3
  14. package/dist/logger/index.d.ts +2 -5
  15. package/dist/middleware/common.js +1 -0
  16. package/dist/middleware/utils.js +0 -3
  17. package/dist/models/amazon/middleware.js +8 -5
  18. package/dist/models/anthropic/middleware.js +13 -13
  19. package/dist/models/catalog.js +5 -1
  20. package/dist/models/cohere/middleware.js +7 -5
  21. package/dist/models/google/middleware.d.ts +1 -1
  22. package/dist/models/google/middleware.js +29 -25
  23. package/dist/models/openai/middleware.js +13 -9
  24. package/dist/models/voyage/middleware.js +2 -1
  25. package/dist/providers/bedrock/middleware.js +21 -23
  26. package/dist/providers/registry.js +3 -0
  27. package/dist/telemetry/fetch.js +7 -2
  28. package/dist/telemetry/gen-ai.js +15 -12
  29. package/dist/telemetry/memory.d.ts +1 -1
  30. package/dist/telemetry/memory.js +30 -14
  31. package/dist/telemetry/span.js +1 -1
  32. package/dist/telemetry/stream.js +30 -23
  33. package/dist/utils/env.js +4 -2
  34. package/dist/utils/preset.js +1 -0
  35. package/dist/utils/response.js +3 -1
  36. package/package.json +36 -50
  37. package/src/config.ts +0 -98
  38. package/src/endpoints/chat-completions/converters.test.ts +0 -631
  39. package/src/endpoints/chat-completions/converters.ts +0 -899
  40. package/src/endpoints/chat-completions/handler.test.ts +0 -391
  41. package/src/endpoints/chat-completions/handler.ts +0 -201
  42. package/src/endpoints/chat-completions/index.ts +0 -4
  43. package/src/endpoints/chat-completions/otel.test.ts +0 -315
  44. package/src/endpoints/chat-completions/otel.ts +0 -214
  45. package/src/endpoints/chat-completions/schema.ts +0 -364
  46. package/src/endpoints/embeddings/converters.ts +0 -51
  47. package/src/endpoints/embeddings/handler.test.ts +0 -133
  48. package/src/endpoints/embeddings/handler.ts +0 -137
  49. package/src/endpoints/embeddings/index.ts +0 -4
  50. package/src/endpoints/embeddings/otel.ts +0 -40
  51. package/src/endpoints/embeddings/schema.ts +0 -36
  52. package/src/endpoints/models/converters.ts +0 -56
  53. package/src/endpoints/models/handler.test.ts +0 -122
  54. package/src/endpoints/models/handler.ts +0 -37
  55. package/src/endpoints/models/index.ts +0 -3
  56. package/src/endpoints/models/schema.ts +0 -37
  57. package/src/errors/ai-sdk.ts +0 -99
  58. package/src/errors/gateway.ts +0 -17
  59. package/src/errors/openai.ts +0 -57
  60. package/src/errors/utils.ts +0 -47
  61. package/src/gateway.ts +0 -50
  62. package/src/index.ts +0 -19
  63. package/src/lifecycle.ts +0 -135
  64. package/src/logger/default.ts +0 -105
  65. package/src/logger/index.ts +0 -42
  66. package/src/middleware/common.test.ts +0 -215
  67. package/src/middleware/common.ts +0 -163
  68. package/src/middleware/debug.ts +0 -37
  69. package/src/middleware/matcher.ts +0 -161
  70. package/src/middleware/utils.ts +0 -34
  71. package/src/models/amazon/index.ts +0 -2
  72. package/src/models/amazon/middleware.test.ts +0 -133
  73. package/src/models/amazon/middleware.ts +0 -79
  74. package/src/models/amazon/presets.ts +0 -104
  75. package/src/models/anthropic/index.ts +0 -2
  76. package/src/models/anthropic/middleware.test.ts +0 -643
  77. package/src/models/anthropic/middleware.ts +0 -148
  78. package/src/models/anthropic/presets.ts +0 -191
  79. package/src/models/catalog.ts +0 -13
  80. package/src/models/cohere/index.ts +0 -2
  81. package/src/models/cohere/middleware.test.ts +0 -138
  82. package/src/models/cohere/middleware.ts +0 -76
  83. package/src/models/cohere/presets.ts +0 -186
  84. package/src/models/google/index.ts +0 -2
  85. package/src/models/google/middleware.test.ts +0 -298
  86. package/src/models/google/middleware.ts +0 -137
  87. package/src/models/google/presets.ts +0 -118
  88. package/src/models/meta/index.ts +0 -1
  89. package/src/models/meta/presets.ts +0 -143
  90. package/src/models/openai/index.ts +0 -2
  91. package/src/models/openai/middleware.test.ts +0 -189
  92. package/src/models/openai/middleware.ts +0 -103
  93. package/src/models/openai/presets.ts +0 -280
  94. package/src/models/types.ts +0 -114
  95. package/src/models/voyage/index.ts +0 -2
  96. package/src/models/voyage/middleware.test.ts +0 -28
  97. package/src/models/voyage/middleware.ts +0 -23
  98. package/src/models/voyage/presets.ts +0 -126
  99. package/src/providers/anthropic/canonical.ts +0 -17
  100. package/src/providers/anthropic/index.ts +0 -1
  101. package/src/providers/bedrock/canonical.ts +0 -87
  102. package/src/providers/bedrock/index.ts +0 -2
  103. package/src/providers/bedrock/middleware.test.ts +0 -303
  104. package/src/providers/bedrock/middleware.ts +0 -128
  105. package/src/providers/cohere/canonical.ts +0 -26
  106. package/src/providers/cohere/index.ts +0 -1
  107. package/src/providers/groq/canonical.ts +0 -21
  108. package/src/providers/groq/index.ts +0 -1
  109. package/src/providers/openai/canonical.ts +0 -16
  110. package/src/providers/openai/index.ts +0 -1
  111. package/src/providers/registry.test.ts +0 -44
  112. package/src/providers/registry.ts +0 -165
  113. package/src/providers/types.ts +0 -20
  114. package/src/providers/vertex/canonical.ts +0 -17
  115. package/src/providers/vertex/index.ts +0 -1
  116. package/src/providers/voyage/canonical.ts +0 -16
  117. package/src/providers/voyage/index.ts +0 -1
  118. package/src/telemetry/ai-sdk.ts +0 -46
  119. package/src/telemetry/baggage.ts +0 -27
  120. package/src/telemetry/fetch.ts +0 -62
  121. package/src/telemetry/gen-ai.ts +0 -113
  122. package/src/telemetry/http.ts +0 -62
  123. package/src/telemetry/index.ts +0 -1
  124. package/src/telemetry/memory.ts +0 -36
  125. package/src/telemetry/span.ts +0 -85
  126. package/src/telemetry/stream.ts +0 -64
  127. package/src/types.ts +0 -223
  128. package/src/utils/env.ts +0 -7
  129. package/src/utils/headers.ts +0 -27
  130. package/src/utils/preset.ts +0 -65
  131. package/src/utils/request.test.ts +0 -75
  132. package/src/utils/request.ts +0 -52
  133. package/src/utils/response.ts +0 -84
  134. package/src/utils/url.ts +0 -26
@@ -1,64 +0,0 @@
1
- import { toOpenAIError } from "../errors/openai";
2
-
3
- const isErrorChunk = (v: unknown) => v instanceof Error || !!(v as any)?.error;
4
-
5
- export const wrapStream = (
6
- src: ReadableStream,
7
- hooks: { onDone?: (status: number, reason: unknown) => void },
8
- ): ReadableStream => {
9
- let finished = false;
10
-
11
- const done = (
12
- reader: ReadableStreamDefaultReader,
13
- controller: ReadableStreamDefaultController,
14
- status: number,
15
- reason?: unknown,
16
- ) => {
17
- if (!finished) {
18
- finished = true;
19
- hooks.onDone?.(status, reason);
20
- }
21
- reader.cancel(reason).catch(() => {});
22
- controller.close();
23
- };
24
-
25
- return new ReadableStream({
26
- async start(controller) {
27
- const reader = src.getReader();
28
-
29
- try {
30
- for (;;) {
31
- // oxlint-disable-next-line no-await-in-loop
32
- const { value, done: eof } = await reader.read();
33
- if (eof) break;
34
-
35
- const out = isErrorChunk(value) ? toOpenAIError(value) : value;
36
- controller.enqueue(out);
37
-
38
- if (out !== value) {
39
- const status = out.error?.type === "invalid_request_error" ? 422 : 502;
40
- done(reader, controller, status, value);
41
- return;
42
- }
43
- }
44
-
45
- done(reader, controller, 200);
46
- } catch (err) {
47
- controller.enqueue(toOpenAIError(err));
48
- done(reader, controller, 502, err);
49
- } finally {
50
- try {
51
- reader.releaseLock();
52
- } catch {}
53
- }
54
- },
55
-
56
- cancel(reason) {
57
- if (!finished) {
58
- finished = true;
59
- hooks.onDone?.(499, reason);
60
- }
61
- src.cancel(reason).catch(() => {});
62
- },
63
- });
64
- };
package/src/types.ts DELETED
@@ -1,223 +0,0 @@
1
- import type { ProviderV3 } from "@ai-sdk/provider";
2
- import type { Tracer } from "@opentelemetry/api";
3
-
4
- import type {
5
- ChatCompletions,
6
- ChatCompletionsBody,
7
- ChatCompletionsChunk,
8
- } from "./endpoints/chat-completions/schema";
9
- import type { Embeddings, EmbeddingsBody } from "./endpoints/embeddings/schema";
10
- import type { Model, ModelList } from "./endpoints/models";
11
- import type { Logger, LoggerConfig } from "./logger";
12
- import type { ModelCatalog, ModelId } from "./models/types";
13
- import type { ProviderId, ProviderRegistry } from "./providers/types";
14
-
15
- /**
16
- * Per-request context shared across handlers and hooks.
17
- */
18
- export type GatewayContext = {
19
- /**
20
- * Mutable bag for passing data between hooks.
21
- */
22
- state: Record<string, unknown>;
23
- /**
24
- * Provider registry from config.
25
- */
26
- providers: ProviderRegistry;
27
- /**
28
- * Model catalog from config.
29
- */
30
- models: ModelCatalog;
31
- /**
32
- * Incoming request for the handler.
33
- */
34
- request: Request;
35
- /**
36
- * Resolved request ID for logging and telemetry.
37
- */
38
- requestId: string;
39
- /**
40
- * Parsed body from the request.
41
- */
42
- body?: ChatCompletionsBody | EmbeddingsBody;
43
- /**
44
- * Incoming model ID.
45
- */
46
- modelId?: ModelId;
47
- /**
48
- * Resolved model ID.
49
- */
50
- resolvedModelId?: ModelId;
51
- /**
52
- * Operation type.
53
- */
54
- operation?: "chat" | "embeddings" | "models";
55
- /**
56
- * Resolved provider instance.
57
- */
58
- provider?: ProviderV3;
59
- /**
60
- * Resolved provider ID.
61
- */
62
- resolvedProviderId?: ProviderId;
63
- /**
64
- * Result returned by the handler (pre-response).
65
- */
66
- result?:
67
- | ChatCompletions
68
- | ReadableStream<ChatCompletionsChunk | Error>
69
- | Embeddings
70
- | Model
71
- | ModelList;
72
- /**
73
- * Response object returned by the handler.
74
- */
75
- response?: Response;
76
- };
77
-
78
- /**
79
- * Hook context: all fields readonly except `state`.
80
- */
81
- export type HookContext = Omit<Readonly<GatewayContext>, "state"> & {
82
- state: GatewayContext["state"];
83
- };
84
-
85
- type RequiredHookContext<K extends keyof GatewayContext> = Omit<HookContext, K> &
86
- Required<Pick<HookContext, K>>;
87
- export type OnRequestHookContext = RequiredHookContext<"request">;
88
- export type BeforeHookContext = RequiredHookContext<"request" | "operation" | "body">;
89
- export type ResolveModelHookContext = RequiredHookContext<
90
- "request" | "operation" | "body" | "modelId"
91
- >;
92
- export type ResolveProviderHookContext = RequiredHookContext<
93
- "request" | "operation" | "body" | "modelId" | "resolvedModelId"
94
- >;
95
- export type AfterHookContext = RequiredHookContext<
96
- | "request"
97
- | "operation"
98
- | "body"
99
- | "modelId"
100
- | "resolvedModelId"
101
- | "provider"
102
- | "resolvedProviderId"
103
- | "result"
104
- >;
105
- export type OnResponseHookContext = RequiredHookContext<"request" | "response">;
106
-
107
- /**
108
- * Hooks to plugin to the gateway lifecycle.
109
- */
110
- export type GatewayHooks = {
111
- /**
112
- * Runs before any endpoint handler logic.
113
- * @returns Optional Response to short-circuit the request.
114
- */
115
- onRequest?: (ctx: OnRequestHookContext) => void | Response | Promise<void | Response>;
116
- /**
117
- * Runs after request JSON is parsed and validated for chat completions / embeddings.
118
- * @returns Replacement parsed body, or undefined to keep original.
119
- */
120
- before?: (
121
- ctx: BeforeHookContext,
122
- ) =>
123
- | void
124
- | ChatCompletionsBody
125
- | EmbeddingsBody
126
- | Promise<void | ChatCompletionsBody | EmbeddingsBody>;
127
- /**
128
- * Maps a user-provided model ID or alias to a canonical ID.
129
- * @returns Canonical model ID or undefined to keep original.
130
- */
131
- resolveModelId?: (ctx: ResolveModelHookContext) => ModelId | void | Promise<ModelId | void>;
132
- /**
133
- * Picks a provider instance for the request.
134
- * @returns ProviderV3 to override, or undefined to use default.
135
- */
136
- resolveProvider?: (
137
- ctx: ResolveProviderHookContext,
138
- ) => ProviderV3 | void | Promise<ProviderV3 | void>;
139
- /**
140
- * Runs after the endpoint handler.
141
- * @returns Result to replace, or undefined to keep original.
142
- */
143
- after?: (
144
- ctx: AfterHookContext,
145
- ) =>
146
- | void
147
- | ChatCompletions
148
- | ReadableStream<ChatCompletionsChunk | Error>
149
- | Embeddings
150
- | Promise<void | ChatCompletions | ReadableStream<ChatCompletionsChunk | Error> | Embeddings>;
151
- /**
152
- * Runs after the lifecycle has produced the final Response.
153
- * @returns Replacement Response, or undefined to keep original.
154
- */
155
- onResponse?: (ctx: OnResponseHookContext) => void | Response | Promise<void | Response>;
156
- };
157
-
158
- export type TelemetrySignalLevel = "off" | "required" | "recommended" | "full";
159
-
160
- /**
161
- * Main configuration object for the gateway.
162
- */
163
- export type GatewayConfig = {
164
- /**
165
- * Optional base path the gateway is mounted under (e.g. "/v1/gateway").
166
- */
167
- basePath?: string;
168
- /**
169
- * Provider registry keyed by canonical provider IDs.
170
- */
171
- providers: ProviderRegistry;
172
- /**
173
- * Model catalog keyed by canonical model IDs.
174
- */
175
- models: ModelCatalog;
176
- /**
177
- * Optional lifecycle hooks for routing, auth, and response shaping.
178
- */
179
- hooks?: GatewayHooks;
180
- /**
181
- * Preferred logger configuration: custom logger or default logger settings.
182
- */
183
- logger?: Logger | LoggerConfig | null;
184
- /**
185
- * Optional AI SDK telemetry configuration.
186
- */
187
- telemetry?: {
188
- /**
189
- * Enable AI SDK OpenTelemetry instrumentation.
190
- * Disabled by default.
191
- */
192
- enabled?: boolean;
193
- /**
194
- * Optional custom OpenTelemetry tracer passed to AI SDK telemetry.
195
- */
196
- tracer?: Tracer;
197
- /**
198
- * Telemetry signal levels by namespace.
199
- * - off: disable the namespace
200
- * - required: minimal baseline
201
- * - recommended: practical defaults
202
- * - full: include all available details
203
- */
204
- signals?: {
205
- gen_ai?: TelemetrySignalLevel;
206
- http?: TelemetrySignalLevel;
207
- hebo?: TelemetrySignalLevel;
208
- };
209
- };
210
- };
211
-
212
- export const kParsed = Symbol("hebo.gateway.parsed");
213
- export type GatewayConfigParsed = GatewayConfig & {
214
- [kParsed]: true;
215
- };
216
-
217
- export interface Endpoint {
218
- handler: (request: Request, state?: Record<string, unknown>) => Promise<Response>;
219
- }
220
-
221
- export interface HeboGateway<Routes extends Record<string, Endpoint>> extends Endpoint {
222
- routes: Routes;
223
- }
package/src/utils/env.ts DELETED
@@ -1,7 +0,0 @@
1
- const NODE_ENV =
2
- typeof process === "undefined"
3
- ? ((globalThis as any).NODE_ENV ?? (globalThis as any).ENV?.NODE_ENV)
4
- : process.env?.NODE_ENV;
5
-
6
- export const isProduction = () => NODE_ENV === "production";
7
- export const isTest = () => NODE_ENV === "test";
@@ -1,27 +0,0 @@
1
- export const REQUEST_ID_HEADER = "x-request-id";
2
-
3
- type HeaderSource = Request | ResponseInit | undefined;
4
-
5
- export const resolveRequestId = (source: HeaderSource): string | undefined => {
6
- if (!source) return undefined;
7
-
8
- if (source instanceof Request) {
9
- return source.headers.get(REQUEST_ID_HEADER) ?? undefined;
10
- }
11
-
12
- const headers = source.headers;
13
- if (!headers) return undefined;
14
-
15
- if (headers instanceof Headers) {
16
- return headers.get(REQUEST_ID_HEADER) ?? undefined;
17
- }
18
-
19
- if (Array.isArray(headers)) {
20
- for (const [key, value] of headers) {
21
- if (key.toLowerCase() === REQUEST_ID_HEADER) return value;
22
- }
23
- return undefined;
24
- }
25
-
26
- return headers[REQUEST_ID_HEADER];
27
- };
@@ -1,65 +0,0 @@
1
- function isPlainObject(v: unknown): v is Record<string, unknown> {
2
- if (!v || typeof v !== "object" || Array.isArray(v)) return false;
3
- const proto = Object.getPrototypeOf(v);
4
- return proto === Object.prototype || proto === null;
5
- }
6
-
7
- export type DeepPartial<T> = T extends (...args: unknown[]) => unknown
8
- ? T
9
- : T extends readonly (infer U)[]
10
- ? readonly DeepPartial<U>[]
11
- : T extends object
12
- ? { [K in keyof T]?: DeepPartial<T[K]> }
13
- : T;
14
-
15
- /**
16
- * Deep merge where overrides win.
17
- * Arrays are replaced.
18
- */
19
- export function deepMerge<A extends object, B extends object>(base: A, override?: B): A & B {
20
- if (override === null || override === undefined) return base as A & B;
21
-
22
- if (!isPlainObject(base) || !isPlainObject(override)) {
23
- return override as unknown as A & B;
24
- }
25
-
26
- const out: Record<string, unknown> = { ...(base as Record<string, unknown>) };
27
-
28
- for (const [key, ov] of Object.entries(override as Record<string, unknown>)) {
29
- if (ov === undefined) continue;
30
-
31
- const bv = out[key];
32
-
33
- if (Array.isArray(ov)) {
34
- out[key] = ov;
35
- continue;
36
- }
37
-
38
- if (isPlainObject(bv) && isPlainObject(ov)) {
39
- out[key] = deepMerge(bv as object, ov as object);
40
- continue;
41
- }
42
-
43
- out[key] = ov;
44
- }
45
-
46
- return out as unknown as A & B;
47
- }
48
-
49
- export type Preset<
50
- Id extends string,
51
- T extends Record<string, unknown>,
52
- Base extends DeepPartial<T>,
53
- > = <O extends DeepPartial<T>>(override?: O) => Record<Id, Base & O>;
54
-
55
- export function presetFor<Ids extends string, T extends Record<string, unknown>>() {
56
- return function preset<const Id extends Ids, const Base extends DeepPartial<T>>(
57
- id: Id,
58
- base: Base,
59
- ): Preset<Id, T, Base> {
60
- return <const O extends DeepPartial<T>>(override?: O) => {
61
- const merged = deepMerge(base, override ?? ({} as O));
62
- return { [id]: merged } as Record<Id, Base & O>;
63
- };
64
- };
65
- }
@@ -1,75 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { prepareForwardHeaders } from "./request";
4
-
5
- describe("prepareForwardHeaders", () => {
6
- test("always appends gateway user-agent suffix", () => {
7
- const request = new Request("https://example.com", {
8
- headers: { "user-agent": "client/1.0" },
9
- });
10
-
11
- const headers = prepareForwardHeaders(request);
12
-
13
- expect(headers["user-agent"].startsWith("client/1.0 @hebo-ai/gateway/")).toBe(true);
14
- });
15
-
16
- test("falls back to gateway user-agent when incoming user-agent is missing", () => {
17
- const request = new Request("https://example.com");
18
-
19
- const headers = prepareForwardHeaders(request);
20
-
21
- expect(headers["user-agent"].startsWith("@hebo-ai/gateway/")).toBe(true);
22
- });
23
-
24
- test("forwards allowlisted provider headers without provider context", () => {
25
- const request = new Request("https://example.com", {
26
- headers: {
27
- "openai-beta": "responses=v1",
28
- "openai-organization": "org_123",
29
- "openai-project": "proj_123",
30
- "anthropic-beta": "interleaved-thinking-2025-05-14",
31
- "x-amzn-bedrock-performanceconfig-latency": "optimized",
32
- "x-amzn-bedrock-trace": "ENABLED_FULL",
33
- "x-client-name": "hebo-gateway-test",
34
- "x-vertex-ai-endpoint-id": "projects/1/locations/us-central1/endpoints/123",
35
- "x-vertex-ai-llm-request-type": "dedicated",
36
- "x-vertex-ai-llm-shared-request-type": "provisioned-throughput",
37
- "x-title": "Gateway App",
38
- "x-unrelated-header": "blocked",
39
- },
40
- });
41
-
42
- const headers = prepareForwardHeaders(request);
43
-
44
- expect(headers["openai-beta"]).toBe("responses=v1");
45
- expect(headers["openai-organization"]).toBe("org_123");
46
- expect(headers["openai-project"]).toBe("proj_123");
47
- expect(headers["anthropic-beta"]).toBe("interleaved-thinking-2025-05-14");
48
- expect(headers["x-amzn-bedrock-performanceconfig-latency"]).toBe("optimized");
49
- expect(headers["x-amzn-bedrock-trace"]).toBe("ENABLED_FULL");
50
- expect(headers["x-client-name"]).toBe("hebo-gateway-test");
51
- expect(headers["x-vertex-ai-endpoint-id"]).toBe(
52
- "projects/1/locations/us-central1/endpoints/123",
53
- );
54
- expect(headers["x-vertex-ai-llm-request-type"]).toBe("dedicated");
55
- expect(headers["x-vertex-ai-llm-shared-request-type"]).toBe("provisioned-throughput");
56
- expect(headers["x-title"]).toBe("Gateway App");
57
- expect(headers["x-unrelated-header"]).toBeUndefined();
58
- });
59
-
60
- test("does not forward headers outside the allowlist", () => {
61
- const request = new Request("https://example.com", {
62
- headers: {
63
- authorization: "Bearer user-token",
64
- cookie: "session=abc",
65
- "x-custom-header": "blocked",
66
- },
67
- });
68
-
69
- const headers = prepareForwardHeaders(request);
70
-
71
- expect(headers.authorization).toBeUndefined();
72
- expect(headers.cookie).toBeUndefined();
73
- expect(headers["x-custom-header"]).toBeUndefined();
74
- });
75
- });
@@ -1,52 +0,0 @@
1
- import pkg from "../../package.json" with { type: "json" };
2
- import { resolveRequestId } from "./headers";
3
-
4
- const GATEWAY_VERSION = pkg.version;
5
-
6
- const FORWARD_HEADER_ALLOWLIST = [
7
- // OpenAI + OpenAI-compatible providers (Azure, Groq, Together, Fireworks, etc.)
8
- "openai-beta",
9
- "openai-organization",
10
- "openai-project",
11
- // OpenRouter
12
- "x-openrouter-categories",
13
- "x-openrouter-title",
14
- "x-title",
15
- // Anthropic
16
- "anthropic-beta",
17
- // AWS Bedrock
18
- "x-amzn-bedrock-guardrailidentifier",
19
- "x-amzn-bedrock-guardrailversion",
20
- "x-amzn-bedrock-performanceconfig-latency",
21
- "x-amzn-bedrock-trace",
22
- // Cohere
23
- "x-client-name",
24
- // Vertex provisioned throughput / endpoint routing
25
- "x-vertex-ai-endpoint-id",
26
- "x-vertex-ai-llm-request-type",
27
- "x-vertex-ai-llm-shared-request-type",
28
- ] as const;
29
-
30
- const createRequestId = () =>
31
- "req_" + crypto.getRandomValues(new Uint32Array(2)).reduce((s, n) => s + n.toString(36), "");
32
-
33
- export const resolveOrCreateRequestId = (request: Request) =>
34
- resolveRequestId(request) ?? createRequestId();
35
-
36
- export const prepareForwardHeaders = (request: Request): Record<string, string> => {
37
- const userAgent = request.headers.get("user-agent");
38
- const appendedUserAgent = userAgent
39
- ? `${userAgent} @hebo-ai/gateway/${GATEWAY_VERSION}`
40
- : `@hebo-ai/gateway/${GATEWAY_VERSION}`;
41
-
42
- const headers: Record<string, string> = {
43
- "user-agent": appendedUserAgent,
44
- };
45
-
46
- for (const key of FORWARD_HEADER_ALLOWLIST) {
47
- const value = request.headers.get(key);
48
- if (value !== null) headers[key] = value;
49
- }
50
-
51
- return headers;
52
- };
@@ -1,84 +0,0 @@
1
- import { REQUEST_ID_HEADER } from "./headers";
2
-
3
- const TEXT_ENCODER = new TextEncoder();
4
-
5
- class JsonToSseTransformStream extends TransformStream<unknown, string> {
6
- constructor() {
7
- super({
8
- transform(part, controller) {
9
- controller.enqueue(`data: ${JSON.stringify(part)}\n\n`);
10
- },
11
- flush(controller) {
12
- controller.enqueue("data: [DONE]\n\n");
13
- },
14
- });
15
- }
16
- }
17
-
18
- export const prepareResponseInit = (requestId: string): ResponseInit => ({
19
- headers: { [REQUEST_ID_HEADER]: requestId },
20
- });
21
-
22
- export const mergeResponseInit = (
23
- defaultHeaders: HeadersInit,
24
- responseInit?: ResponseInit,
25
- ): ResponseInit => {
26
- const headers = new Headers(defaultHeaders);
27
- const override = responseInit?.headers;
28
- if (override) {
29
- new Headers(override).forEach((value, key) => headers.set(key, value));
30
- }
31
- if (!responseInit) return { headers };
32
-
33
- return {
34
- status: responseInit.status,
35
- statusText: responseInit.statusText,
36
- headers,
37
- };
38
- };
39
-
40
- export const toResponse = (
41
- result: ReadableStream | Uint8Array<ArrayBuffer> | object | string,
42
- responseInit?: ResponseInit,
43
- ): Response => {
44
- let body: BodyInit;
45
-
46
- const isStream = result instanceof ReadableStream;
47
- if (isStream) {
48
- body = result.pipeThrough(new JsonToSseTransformStream()).pipeThrough(new TextEncoderStream());
49
- } else if (result instanceof Uint8Array) {
50
- body = result;
51
- } else if (typeof result === "string") {
52
- body = TEXT_ENCODER.encode(result);
53
- } else if (result instanceof Error) {
54
- body = TEXT_ENCODER.encode(JSON.stringify({ message: result.message }));
55
- } else {
56
- body = TEXT_ENCODER.encode(JSON.stringify(result));
57
- }
58
-
59
- if (!responseInit?.statusText) {
60
- const isError = result instanceof Error;
61
-
62
- const status = responseInit?.status ?? (isError ? 500 : 200);
63
- const statusText = isError ? "REQUEST_FAILED" : "OK";
64
- const headers = responseInit?.headers;
65
-
66
- responseInit = headers ? { status, statusText, headers } : { status, statusText };
67
- }
68
-
69
- const init = mergeResponseInit(
70
- isStream
71
- ? {
72
- "content-type": "text/event-stream",
73
- "cache-control": "no-cache",
74
- connection: "keep-alive",
75
- }
76
- : {
77
- "content-type": "application/json",
78
- "content-length": String((body as Uint8Array).byteLength),
79
- },
80
- responseInit,
81
- );
82
-
83
- return new Response(body, init);
84
- };
package/src/utils/url.ts DELETED
@@ -1,26 +0,0 @@
1
- export const parseDataUrl = (url: string): { mimeType: string; dataStart: number } => {
2
- if (url.slice(0, 5).toLowerCase() !== "data:") {
3
- return { mimeType: "", dataStart: 0 };
4
- }
5
-
6
- const MAX_HEADER_LENGTH = 1024;
7
- const headerEnd = Math.min(url.length, 5 + MAX_HEADER_LENGTH);
8
-
9
- const comma = url.indexOf(",", 5);
10
- if (comma <= 5 || comma > headerEnd) {
11
- return { mimeType: "", dataStart: 0 };
12
- }
13
-
14
- const semi = url.indexOf(";", 5);
15
- const mimeEnd = semi !== -1 && semi < comma ? semi : comma;
16
-
17
- const mimeType = url.slice(5, mimeEnd).trim();
18
- if (!mimeType) {
19
- return { mimeType: "", dataStart: 0 };
20
- }
21
-
22
- return {
23
- mimeType,
24
- dataStart: comma + 1,
25
- };
26
- };