@hebo-ai/gateway 0.4.1 → 0.5.0-beta.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/README.md +2 -3
  2. package/dist/endpoints/chat-completions/converters.d.ts +3 -1
  3. package/dist/endpoints/chat-completions/converters.js +121 -90
  4. package/dist/endpoints/chat-completions/handler.js +2 -4
  5. package/dist/endpoints/chat-completions/otel.js +7 -0
  6. package/dist/endpoints/chat-completions/schema.d.ts +400 -76
  7. package/dist/endpoints/chat-completions/schema.js +80 -36
  8. package/dist/endpoints/embeddings/handler.js +2 -4
  9. package/dist/endpoints/embeddings/schema.d.ts +1 -1
  10. package/dist/endpoints/embeddings/schema.js +1 -1
  11. package/dist/errors/gateway.js +1 -0
  12. package/dist/lifecycle.js +7 -12
  13. package/dist/logger/default.d.ts +0 -1
  14. package/dist/logger/default.js +30 -6
  15. package/dist/middleware/utils.js +1 -0
  16. package/dist/models/amazon/middleware.js +1 -0
  17. package/dist/models/anthropic/middleware.d.ts +2 -0
  18. package/dist/models/anthropic/middleware.js +77 -16
  19. package/dist/models/google/middleware.js +17 -0
  20. package/dist/models/google/presets.d.ts +387 -0
  21. package/dist/models/google/presets.js +9 -2
  22. package/dist/models/openai/middleware.js +1 -0
  23. package/dist/models/types.d.ts +1 -1
  24. package/dist/models/types.js +1 -0
  25. package/dist/providers/bedrock/index.d.ts +1 -0
  26. package/dist/providers/bedrock/index.js +1 -0
  27. package/dist/providers/bedrock/middleware.d.ts +2 -0
  28. package/dist/providers/bedrock/middleware.js +35 -0
  29. package/dist/telemetry/http.js +0 -3
  30. package/dist/types.d.ts +10 -20
  31. package/dist/utils/request.d.ts +1 -3
  32. package/dist/utils/request.js +3 -26
  33. package/dist/utils/response.d.ts +1 -1
  34. package/dist/utils/response.js +3 -3
  35. package/package.json +19 -21
  36. package/src/endpoints/chat-completions/converters.test.ts +219 -0
  37. package/src/endpoints/chat-completions/converters.ts +144 -104
  38. package/src/endpoints/chat-completions/handler.test.ts +87 -0
  39. package/src/endpoints/chat-completions/handler.ts +2 -5
  40. package/src/endpoints/chat-completions/otel.ts +6 -0
  41. package/src/endpoints/chat-completions/schema.ts +85 -43
  42. package/src/endpoints/embeddings/handler.ts +5 -5
  43. package/src/endpoints/embeddings/schema.ts +1 -1
  44. package/src/errors/gateway.ts +2 -0
  45. package/src/lifecycle.ts +7 -11
  46. package/src/logger/default.ts +34 -8
  47. package/src/middleware/utils.ts +1 -0
  48. package/src/models/amazon/middleware.ts +1 -0
  49. package/src/models/anthropic/middleware.test.ts +332 -1
  50. package/src/models/anthropic/middleware.ts +83 -19
  51. package/src/models/google/middleware.test.ts +31 -0
  52. package/src/models/google/middleware.ts +18 -0
  53. package/src/models/google/presets.ts +13 -2
  54. package/src/models/openai/middleware.ts +1 -0
  55. package/src/models/types.ts +1 -0
  56. package/src/providers/bedrock/index.ts +1 -0
  57. package/src/providers/bedrock/middleware.test.ts +73 -0
  58. package/src/providers/bedrock/middleware.ts +43 -0
  59. package/src/telemetry/http.ts +0 -3
  60. package/src/types.ts +19 -23
  61. package/src/utils/request.ts +5 -33
  62. package/src/utils/response.ts +3 -3
@@ -4,12 +4,13 @@ export const ChatCompletionsContentPartTextSchema = z.object({
4
4
  type: z.literal("text"),
5
5
  text: z.string(),
6
6
  });
7
+ export type ChatCompletionsContentPartText = z.infer<typeof ChatCompletionsContentPartTextSchema>;
7
8
 
8
9
  export const ChatCompletionsContentPartImageSchema = z.object({
9
10
  type: z.literal("image_url"),
10
11
  image_url: z.object({
11
12
  url: z.string(),
12
- detail: z.union([z.literal("low"), z.literal("high"), z.literal("auto")]).optional(),
13
+ detail: z.enum(["low", "high", "auto"]).optional(),
13
14
  }),
14
15
  });
15
16
 
@@ -22,11 +23,35 @@ export const ChatCompletionsContentPartFileSchema = z.object({
22
23
  }),
23
24
  });
24
25
 
25
- // FUTURE: missing ContentPartAudio
26
- export type ChatCompletionsContentPart =
27
- | z.infer<typeof ChatCompletionsContentPartTextSchema>
28
- | z.infer<typeof ChatCompletionsContentPartImageSchema>
29
- | z.infer<typeof ChatCompletionsContentPartFileSchema>;
26
+ export const ChatCompletionsContentPartAudioSchema = z.object({
27
+ type: z.literal("input_audio"),
28
+ input_audio: z.object({
29
+ data: z.string(),
30
+ // only wav and mp3 are official by OpenAI, rest is taken from Gemini support:
31
+ // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding
32
+ format: z.enum([
33
+ "x-aac",
34
+ "flac",
35
+ "mp3",
36
+ "m4a",
37
+ "mpeg",
38
+ "mpga",
39
+ "mp4",
40
+ "ogg",
41
+ "pcm",
42
+ "wav",
43
+ "webm",
44
+ ]),
45
+ }),
46
+ });
47
+
48
+ export const ChatCompletionsContentPartSchema = z.discriminatedUnion("type", [
49
+ ChatCompletionsContentPartTextSchema,
50
+ ChatCompletionsContentPartImageSchema,
51
+ ChatCompletionsContentPartFileSchema,
52
+ ChatCompletionsContentPartAudioSchema,
53
+ ]);
54
+ export type ChatCompletionsContentPart = z.infer<typeof ChatCompletionsContentPartSchema>;
30
55
 
31
56
  export const ChatCompletionsToolCallSchema = z.object({
32
57
  type: z.literal("function"),
@@ -35,7 +60,10 @@ export const ChatCompletionsToolCallSchema = z.object({
35
60
  arguments: z.string(),
36
61
  name: z.string(),
37
62
  }),
38
- extra_content: z.record(z.string(), z.any()).optional().meta({ extension: true }),
63
+ extra_content: z
64
+ .record(z.string(), z.record(z.string(), z.unknown()))
65
+ .optional()
66
+ .meta({ extension: true }),
39
67
  });
40
68
  export type ChatCompletionsToolCall = z.infer<typeof ChatCompletionsToolCallSchema>;
41
69
 
@@ -48,16 +76,7 @@ export type ChatCompletionsSystemMessage = z.infer<typeof ChatCompletionsSystemM
48
76
 
49
77
  export const ChatCompletionsUserMessageSchema = z.object({
50
78
  role: z.literal("user"),
51
- content: z.union([
52
- z.string(),
53
- z.array(
54
- z.union([
55
- ChatCompletionsContentPartTextSchema,
56
- ChatCompletionsContentPartImageSchema,
57
- ChatCompletionsContentPartFileSchema,
58
- ]),
59
- ),
60
- ]),
79
+ content: z.union([z.string(), z.array(ChatCompletionsContentPartSchema)]),
61
80
  name: z.string().optional(),
62
81
  });
63
82
  export type ChatCompletionsUserMessage = z.infer<typeof ChatCompletionsUserMessageSchema>;
@@ -76,8 +95,9 @@ export type ChatCompletionsReasoningDetail = z.infer<typeof ChatCompletionsReaso
76
95
 
77
96
  export const ChatCompletionsAssistantMessageSchema = z.object({
78
97
  role: z.literal("assistant"),
79
- // FUTURE: this should support arrays of TextContentPart and RefusalContentPart
80
- content: z.union([z.string(), z.null()]).optional(),
98
+ content: z
99
+ .union([z.string(), z.null(), z.array(ChatCompletionsContentPartTextSchema)])
100
+ .optional(),
81
101
  name: z.string().optional(),
82
102
  // FUTURE: This should also support Custom Tool Calls
83
103
  tool_calls: z.array(ChatCompletionsToolCallSchema).optional(),
@@ -87,19 +107,21 @@ export const ChatCompletionsAssistantMessageSchema = z.object({
87
107
  .array(ChatCompletionsReasoningDetailSchema)
88
108
  .optional()
89
109
  .meta({ extension: true }),
90
- extra_content: z.record(z.string(), z.any()).optional().meta({ extension: true }),
110
+ extra_content: z
111
+ .record(z.string(), z.record(z.string(), z.unknown()))
112
+ .optional()
113
+ .meta({ extension: true }),
91
114
  });
92
115
  export type ChatCompletionsAssistantMessage = z.infer<typeof ChatCompletionsAssistantMessageSchema>;
93
116
 
94
117
  export const ChatCompletionsToolMessageSchema = z.object({
95
118
  role: z.literal("tool"),
96
- // FUTURE: this should also support arrays of TextContentParts
97
- content: z.string(),
119
+ content: z.union([z.string(), z.array(ChatCompletionsContentPartTextSchema)]),
98
120
  tool_call_id: z.string(),
99
121
  });
100
122
  export type ChatCompletionsToolMessage = z.infer<typeof ChatCompletionsToolMessageSchema>;
101
123
 
102
- export const ChatCompletionsMessageSchema = z.union([
124
+ export const ChatCompletionsMessageSchema = z.discriminatedUnion("role", [
103
125
  ChatCompletionsSystemMessageSchema,
104
126
  ChatCompletionsUserMessageSchema,
105
127
  ChatCompletionsAssistantMessageSchema,
@@ -112,16 +134,14 @@ export const ChatCompletionsToolSchema = z.object({
112
134
  function: z.object({
113
135
  name: z.string(),
114
136
  description: z.string().optional(),
115
- parameters: z.record(z.string(), z.any()),
137
+ parameters: z.record(z.string(), z.unknown()),
116
138
  // Missing strict parameter
117
139
  }),
118
140
  });
119
141
  export type ChatCompletionsTool = z.infer<typeof ChatCompletionsToolSchema>;
120
142
 
121
143
  export const ChatCompletionsToolChoiceSchema = z.union([
122
- z.literal("none"),
123
- z.literal("auto"),
124
- z.literal("required"),
144
+ z.enum(["none", "auto", "required", "validated"]),
125
145
  // FUTURE: missing AllowedTools and CustomToolChoice
126
146
  z.object({
127
147
  type: z.literal("function"),
@@ -132,13 +152,14 @@ export const ChatCompletionsToolChoiceSchema = z.union([
132
152
  ]);
133
153
  export type ChatCompletionsToolChoice = z.infer<typeof ChatCompletionsToolChoiceSchema>;
134
154
 
135
- export const ChatCompletionsReasoningEffortSchema = z.union([
136
- z.literal("none"),
137
- z.literal("minimal"),
138
- z.literal("low"),
139
- z.literal("medium"),
140
- z.literal("high"),
141
- z.literal("xhigh"),
155
+ export const ChatCompletionsReasoningEffortSchema = z.enum([
156
+ "none",
157
+ "minimal",
158
+ "low",
159
+ "medium",
160
+ "high",
161
+ "xhigh",
162
+ "max",
142
163
  ]);
143
164
  export type ChatCompletionsReasoningEffort = z.infer<typeof ChatCompletionsReasoningEffortSchema>;
144
165
 
@@ -150,6 +171,26 @@ export const ChatCompletionsReasoningConfigSchema = z.object({
150
171
  });
151
172
  export type ChatCompletionsReasoningConfig = z.infer<typeof ChatCompletionsReasoningConfigSchema>;
152
173
 
174
+ export const ChatCompletionsResponseFormatJsonSchema = z.object({
175
+ // FUTURE: consider support for legacy json_object (if demand)
176
+ type: z.literal("json_schema"),
177
+ json_schema: z.object({
178
+ name: z.string(),
179
+ description: z.string().optional(),
180
+ schema: z.record(z.string(), z.unknown()),
181
+ // FUTURE: consider support for non-strict mode (for providers that support it)
182
+ strict: z.boolean().optional(),
183
+ }),
184
+ });
185
+ export const ChatCompletionsResponseFormatTextSchema = z.object({
186
+ type: z.literal("text"),
187
+ });
188
+ export const ChatCompletionsResponseFormatSchema = z.discriminatedUnion("type", [
189
+ ChatCompletionsResponseFormatJsonSchema,
190
+ ChatCompletionsResponseFormatTextSchema,
191
+ ]);
192
+ export type ChatCompletionsResponseFormat = z.infer<typeof ChatCompletionsResponseFormatSchema>;
193
+
153
194
  const ChatCompletionsInputsSchema = z.object({
154
195
  messages: z.array(ChatCompletionsMessageSchema),
155
196
  tools: z
@@ -167,6 +208,7 @@ const ChatCompletionsInputsSchema = z.object({
167
208
  seed: z.int().optional(),
168
209
  stop: z.union([z.string(), z.array(z.string())]).optional(),
169
210
  top_p: z.number().min(0).max(1.0).optional(),
211
+ response_format: ChatCompletionsResponseFormatSchema.optional(),
170
212
  reasoning_effort: ChatCompletionsReasoningEffortSchema.optional(),
171
213
  // Extensions
172
214
  reasoning: ChatCompletionsReasoningConfigSchema.optional().meta({ extension: true }),
@@ -180,11 +222,11 @@ export const ChatCompletionsBodySchema = z.looseObject({
180
222
  });
181
223
  export type ChatCompletionsBody = z.infer<typeof ChatCompletionsBodySchema>;
182
224
 
183
- export const ChatCompletionsFinishReasonSchema = z.union([
184
- z.literal("stop"),
185
- z.literal("length"),
186
- z.literal("content_filter"),
187
- z.literal("tool_calls"),
225
+ export const ChatCompletionsFinishReasonSchema = z.enum([
226
+ "stop",
227
+ "length",
228
+ "content_filter",
229
+ "tool_calls",
188
230
  ]);
189
231
  export type ChatCompletionsFinishReason = z.infer<typeof ChatCompletionsFinishReasonSchema>;
190
232
 
@@ -193,7 +235,7 @@ export const ChatCompletionsChoiceSchema = z.object({
193
235
  message: ChatCompletionsAssistantMessageSchema,
194
236
  finish_reason: ChatCompletionsFinishReasonSchema,
195
237
  // FUTURE: model this out
196
- logprobs: z.any().optional(),
238
+ logprobs: z.unknown().optional(),
197
239
  });
198
240
  export type ChatCompletionsChoice = z.infer<typeof ChatCompletionsChoiceSchema>;
199
241
 
@@ -224,7 +266,7 @@ export const ChatCompletionsSchema = z.object({
224
266
  choices: z.array(ChatCompletionsChoiceSchema),
225
267
  usage: ChatCompletionsUsageSchema.nullable(),
226
268
  // Extensions
227
- provider_metadata: z.any().optional().meta({ extension: true }),
269
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
228
270
  });
229
271
  export type ChatCompletions = z.infer<typeof ChatCompletionsSchema>;
230
272
 
@@ -246,7 +288,7 @@ export const ChatCompletionsChoiceDeltaSchema = z.object({
246
288
  delta: ChatCompletionsAssistantMessageDeltaSchema,
247
289
  finish_reason: ChatCompletionsFinishReasonSchema.nullable(),
248
290
  // FUTURE: model this out
249
- logprobs: z.any().optional(),
291
+ logprobs: z.unknown().optional(),
250
292
  });
251
293
  export type ChatCompletionsChoiceDelta = z.infer<typeof ChatCompletionsChoiceDeltaSchema>;
252
294
 
@@ -258,6 +300,6 @@ export const ChatCompletionsChunkSchema = z.object({
258
300
  choices: z.array(ChatCompletionsChoiceDeltaSchema),
259
301
  usage: ChatCompletionsUsageSchema.nullable(),
260
302
  // Extensions
261
- provider_metadata: z.any().optional().meta({ extension: true }),
303
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
262
304
  });
263
305
  export type ChatCompletionsChunk = z.infer<typeof ChatCompletionsChunkSchema>;
@@ -22,7 +22,6 @@ import {
22
22
  recordTokenUsage,
23
23
  } from "../../telemetry/gen-ai";
24
24
  import { addSpanEvent, setSpanAttributes } from "../../telemetry/span";
25
- import { resolveRequestId } from "../../utils/headers";
26
25
  import { prepareForwardHeaders } from "../../utils/request";
27
26
  import { convertToEmbedCallOptions, toEmbeddings } from "./converters";
28
27
  import {
@@ -45,8 +44,6 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
45
44
  throw new GatewayError("Method Not Allowed", 405);
46
45
  }
47
46
 
48
- const requestId = resolveRequestId(ctx.request);
49
-
50
47
  // Parse + validate input.
51
48
  try {
52
49
  ctx.body = await ctx.request.json();
@@ -98,7 +95,10 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
98
95
 
99
96
  // Convert inputs to AI SDK call options.
100
97
  const embedOptions = convertToEmbedCallOptions(inputs);
101
- logger.trace({ requestId, options: embedOptions }, "[embeddings] AI SDK options");
98
+ logger.trace(
99
+ { requestId: ctx.requestId, options: embedOptions },
100
+ "[embeddings] AI SDK options",
101
+ );
102
102
  addSpanEvent("hebo.options.prepared");
103
103
  setSpanAttributes(getEmbeddingsRequestAttributes(inputs, genAiSignalLevel));
104
104
 
@@ -116,7 +116,7 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
116
116
  abortSignal: ctx.request.signal,
117
117
  ...embedOptions,
118
118
  });
119
- logger.trace({ requestId, result }, "[embeddings] AI SDK result");
119
+ logger.trace({ requestId: ctx.requestId, result }, "[embeddings] AI SDK result");
120
120
  addSpanEvent("hebo.ai-sdk.completed");
121
121
 
122
122
  // Transform result.
@@ -31,6 +31,6 @@ export const EmbeddingsSchema = z.object({
31
31
  model: z.string(),
32
32
  usage: EmbeddingsUsageSchema.nullable(),
33
33
  // Extensions
34
- provider_metadata: z.optional(z.any()).meta({ extension: true }),
34
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
35
35
  });
36
36
  export type Embeddings = z.infer<typeof EmbeddingsSchema>;
@@ -7,6 +7,8 @@ export class GatewayError extends Error {
7
7
  constructor(error: unknown, status: number, code?: string, cause?: unknown) {
8
8
  const isError = error instanceof Error;
9
9
  super(isError ? error.message : String(error));
10
+
11
+ this.name = "GatewayError";
10
12
  this.cause = cause ?? (isError ? error : undefined);
11
13
 
12
14
  this.status = status;
package/src/lifecycle.ts CHANGED
@@ -15,8 +15,7 @@ import { getRequestAttributes, getResponseAttributes } from "./telemetry/http";
15
15
  import { recordV8jsMemory } from "./telemetry/memory";
16
16
  import { addSpanEvent, setSpanEventsEnabled, setSpanTracer, startSpan } from "./telemetry/span";
17
17
  import { wrapStream } from "./telemetry/stream";
18
- import { resolveRequestId } from "./utils/headers";
19
- import { maybeApplyRequestPatch, prepareRequestHeaders } from "./utils/request";
18
+ import { resolveOrCreateRequestId } from "./utils/request";
20
19
  import { prepareResponseInit, toResponse } from "./utils/response";
21
20
 
22
21
  export const winterCgHandler = (
@@ -37,15 +36,14 @@ export const winterCgHandler = (
37
36
  state: state ?? {},
38
37
  providers: parsedConfig.providers,
39
38
  models: parsedConfig.models,
39
+ requestId: resolveOrCreateRequestId(request),
40
40
  };
41
41
 
42
- const headers = prepareRequestHeaders(ctx.request);
43
- if (headers) ctx.request = new Request(ctx.request, { headers });
44
-
45
42
  const span = startSpan(ctx.request.url);
46
43
  span.setAttributes(getBaggageAttributes(ctx.request));
47
44
  if (!span.isExisting) {
48
45
  span.setAttributes(getRequestAttributes(ctx.request, parsedConfig.telemetry?.signals?.http));
46
+ span.setAttributes({ "http.request.id": ctx.requestId });
49
47
  }
50
48
 
51
49
  const finalize = (status: number, reason?: unknown) => {
@@ -65,8 +63,8 @@ export const winterCgHandler = (
65
63
  else if (status === 200 && ctx.response?.status) realStatus = ctx.response.status;
66
64
 
67
65
  if (realStatus !== 200) {
68
- (realStatus >= 500 ? logger.error : logger.warn)({
69
- requestId: resolveRequestId(ctx.request),
66
+ logger[realStatus >= 500 ? "error" : "warn"]({
67
+ requestId: ctx.requestId,
70
68
  err: reason ?? ctx.request.signal.reason,
71
69
  });
72
70
 
@@ -86,8 +84,6 @@ export const winterCgHandler = (
86
84
 
87
85
  if (onRequest instanceof Response) {
88
86
  ctx.response = onRequest;
89
- } else if (onRequest) {
90
- ctx.request = maybeApplyRequestPatch(ctx.request, onRequest);
91
87
  }
92
88
  }
93
89
 
@@ -98,7 +94,7 @@ export const winterCgHandler = (
98
94
  ctx.result = wrapStream(ctx.result, { onDone: finalize });
99
95
  }
100
96
 
101
- ctx.response = toResponse(ctx.result!, prepareResponseInit(ctx.request));
97
+ ctx.response = toResponse(ctx.result!, prepareResponseInit(ctx.requestId));
102
98
  }
103
99
 
104
100
  if (parsedConfig.hooks?.onResponse) {
@@ -118,7 +114,7 @@ export const winterCgHandler = (
118
114
  ctx.request.signal.aborted
119
115
  ? new GatewayError(error ?? ctx.request.signal.reason, 499)
120
116
  : error,
121
- prepareResponseInit(ctx.request),
117
+ prepareResponseInit(ctx.requestId),
122
118
  );
123
119
  finalize(ctx.response.status, error);
124
120
  }
@@ -1,10 +1,8 @@
1
- import { serializeError } from "serialize-error";
2
-
3
1
  import type { LogFn, LogLevel, Logger } from "./index";
4
2
 
5
3
  import { isProduction, isTest } from "../utils/env";
6
4
 
7
- export const getDefaultLogLevel = (): LogLevel =>
5
+ const getDefaultLogLevel = (): LogLevel =>
8
6
  isTest() ? "silent" : isProduction() ? "info" : "debug";
9
7
 
10
8
  const noop: LogFn = () => {};
@@ -22,20 +20,48 @@ const LEVELS = Object.keys(LEVEL) as (keyof typeof LEVEL)[];
22
20
  const isRecord = (value: unknown): value is Record<string, unknown> =>
23
21
  typeof value === "object" && value !== null && !(value instanceof Error);
24
22
 
23
+ function serializeError(err: unknown, _seen?: WeakSet<object>): Record<string, unknown> {
24
+ if (!(err instanceof Error)) return { message: String(err) };
25
+
26
+ const seen = _seen ?? new WeakSet();
27
+ if (seen.has(err)) return { name: err.name, message: err.message, circular: true };
28
+ seen.add(err);
29
+
30
+ const out: Record<string, unknown> = {};
31
+
32
+ for (const k of Object.getOwnPropertyNames(err)) {
33
+ if (k.startsWith("_")) continue;
34
+
35
+ let val: unknown;
36
+ try {
37
+ val = (err as any)[k];
38
+ } catch {
39
+ val = "[Unreadable]";
40
+ }
41
+
42
+ if (typeof val === "bigint") val = `${val}n`;
43
+
44
+ // FUTURE: check for circular references within val
45
+ out[String(k)] = val instanceof Error ? serializeError(val, seen) : val;
46
+ }
47
+
48
+ return out;
49
+ }
50
+
25
51
  const buildLogObject = (level: LogLevel, args: unknown[]): Record<string, unknown> => {
26
52
  if (args.length === 0) return {};
27
53
 
28
54
  const [first, second] = args;
29
55
 
30
56
  let obj: Record<string, unknown> | undefined;
31
- let err: unknown;
57
+ let err: Record<string, unknown> | undefined;
32
58
  let msg: string | undefined;
33
59
 
34
60
  if (first instanceof Error) {
35
- err = first;
61
+ err = serializeError(first);
36
62
  } else if (isRecord(first)) {
37
63
  if (first["err"] !== undefined) {
38
- err = first["err"];
64
+ err = serializeError(first["err"]);
39
65
  delete first["err"];
40
66
  }
41
67
  obj = first;
@@ -48,14 +74,14 @@ const buildLogObject = (level: LogLevel, args: unknown[]): Record<string, unknow
48
74
  }
49
75
 
50
76
  if (err && msg === undefined) {
51
- msg = err instanceof Error ? err.message : String(err);
77
+ msg = err["message"] as string;
52
78
  }
53
79
 
54
80
  return {
55
81
  level,
56
82
  time: Date.now(),
57
83
  ...(msg ? { msg } : {}),
58
- ...(err ? { err: err instanceof Error ? serializeError(err) : err } : {}),
84
+ ...(err ? { err } : {}),
59
85
  ...obj,
60
86
  };
61
87
  };
@@ -23,6 +23,7 @@ export function calculateReasoningBudgetFromEffort(
23
23
  percentage = 0.8;
24
24
  break;
25
25
  case "xhigh":
26
+ case "max":
26
27
  percentage = 0.95;
27
28
  break;
28
29
  default:
@@ -34,6 +34,7 @@ function mapNovaEffort(effort: ChatCompletionsReasoningEffort) {
34
34
  return "medium";
35
35
  case "high":
36
36
  case "xhigh":
37
+ case "max":
37
38
  return "high";
38
39
  }
39
40
  }