@hebo-ai/gateway 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 (46) hide show
  1. package/README.md +8 -6
  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/otel.js +7 -0
  5. package/dist/endpoints/chat-completions/schema.d.ts +400 -76
  6. package/dist/endpoints/chat-completions/schema.js +80 -36
  7. package/dist/endpoints/embeddings/schema.d.ts +1 -1
  8. package/dist/endpoints/embeddings/schema.js +1 -1
  9. package/dist/errors/gateway.js +1 -0
  10. package/dist/logger/default.d.ts +0 -1
  11. package/dist/logger/default.js +30 -6
  12. package/dist/middleware/utils.js +1 -0
  13. package/dist/models/amazon/middleware.js +1 -0
  14. package/dist/models/anthropic/middleware.d.ts +2 -0
  15. package/dist/models/anthropic/middleware.js +77 -16
  16. package/dist/models/google/middleware.js +17 -0
  17. package/dist/models/google/presets.d.ts +387 -0
  18. package/dist/models/google/presets.js +9 -2
  19. package/dist/models/openai/middleware.js +1 -0
  20. package/dist/models/types.d.ts +1 -1
  21. package/dist/models/types.js +1 -0
  22. package/dist/providers/bedrock/index.d.ts +1 -0
  23. package/dist/providers/bedrock/index.js +1 -0
  24. package/dist/providers/bedrock/middleware.d.ts +2 -0
  25. package/dist/providers/bedrock/middleware.js +35 -0
  26. package/package.json +19 -21
  27. package/src/endpoints/chat-completions/converters.test.ts +219 -0
  28. package/src/endpoints/chat-completions/converters.ts +144 -104
  29. package/src/endpoints/chat-completions/handler.test.ts +87 -0
  30. package/src/endpoints/chat-completions/otel.ts +6 -0
  31. package/src/endpoints/chat-completions/schema.ts +85 -43
  32. package/src/endpoints/embeddings/schema.ts +1 -1
  33. package/src/errors/gateway.ts +2 -0
  34. package/src/logger/default.ts +34 -8
  35. package/src/middleware/utils.ts +1 -0
  36. package/src/models/amazon/middleware.ts +1 -0
  37. package/src/models/anthropic/middleware.test.ts +332 -1
  38. package/src/models/anthropic/middleware.ts +83 -19
  39. package/src/models/google/middleware.test.ts +31 -0
  40. package/src/models/google/middleware.ts +18 -0
  41. package/src/models/google/presets.ts +13 -2
  42. package/src/models/openai/middleware.ts +1 -0
  43. package/src/models/types.ts +1 -0
  44. package/src/providers/bedrock/index.ts +1 -0
  45. package/src/providers/bedrock/middleware.test.ts +73 -0
  46. package/src/providers/bedrock/middleware.ts +43 -0
@@ -7,7 +7,7 @@ export const ChatCompletionsContentPartImageSchema = z.object({
7
7
  type: z.literal("image_url"),
8
8
  image_url: z.object({
9
9
  url: z.string(),
10
- detail: z.union([z.literal("low"), z.literal("high"), z.literal("auto")]).optional(),
10
+ detail: z.enum(["low", "high", "auto"]).optional(),
11
11
  }),
12
12
  });
13
13
  export const ChatCompletionsContentPartFileSchema = z.object({
@@ -18,6 +18,33 @@ export const ChatCompletionsContentPartFileSchema = z.object({
18
18
  filename: z.string().optional(),
19
19
  }),
20
20
  });
21
+ export const ChatCompletionsContentPartAudioSchema = z.object({
22
+ type: z.literal("input_audio"),
23
+ input_audio: z.object({
24
+ data: z.string(),
25
+ // only wav and mp3 are official by OpenAI, rest is taken from Gemini support:
26
+ // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding
27
+ format: z.enum([
28
+ "x-aac",
29
+ "flac",
30
+ "mp3",
31
+ "m4a",
32
+ "mpeg",
33
+ "mpga",
34
+ "mp4",
35
+ "ogg",
36
+ "pcm",
37
+ "wav",
38
+ "webm",
39
+ ]),
40
+ }),
41
+ });
42
+ export const ChatCompletionsContentPartSchema = z.discriminatedUnion("type", [
43
+ ChatCompletionsContentPartTextSchema,
44
+ ChatCompletionsContentPartImageSchema,
45
+ ChatCompletionsContentPartFileSchema,
46
+ ChatCompletionsContentPartAudioSchema,
47
+ ]);
21
48
  export const ChatCompletionsToolCallSchema = z.object({
22
49
  type: z.literal("function"),
23
50
  id: z.string(),
@@ -25,7 +52,10 @@ export const ChatCompletionsToolCallSchema = z.object({
25
52
  arguments: z.string(),
26
53
  name: z.string(),
27
54
  }),
28
- extra_content: z.record(z.string(), z.any()).optional().meta({ extension: true }),
55
+ extra_content: z
56
+ .record(z.string(), z.record(z.string(), z.unknown()))
57
+ .optional()
58
+ .meta({ extension: true }),
29
59
  });
30
60
  export const ChatCompletionsSystemMessageSchema = z.object({
31
61
  role: z.literal("system"),
@@ -34,14 +64,7 @@ export const ChatCompletionsSystemMessageSchema = z.object({
34
64
  });
35
65
  export const ChatCompletionsUserMessageSchema = z.object({
36
66
  role: z.literal("user"),
37
- content: z.union([
38
- z.string(),
39
- z.array(z.union([
40
- ChatCompletionsContentPartTextSchema,
41
- ChatCompletionsContentPartImageSchema,
42
- ChatCompletionsContentPartFileSchema,
43
- ])),
44
- ]),
67
+ content: z.union([z.string(), z.array(ChatCompletionsContentPartSchema)]),
45
68
  name: z.string().optional(),
46
69
  });
47
70
  export const ChatCompletionsReasoningDetailSchema = z.object({
@@ -56,8 +79,9 @@ export const ChatCompletionsReasoningDetailSchema = z.object({
56
79
  });
57
80
  export const ChatCompletionsAssistantMessageSchema = z.object({
58
81
  role: z.literal("assistant"),
59
- // FUTURE: this should support arrays of TextContentPart and RefusalContentPart
60
- content: z.union([z.string(), z.null()]).optional(),
82
+ content: z
83
+ .union([z.string(), z.null(), z.array(ChatCompletionsContentPartTextSchema)])
84
+ .optional(),
61
85
  name: z.string().optional(),
62
86
  // FUTURE: This should also support Custom Tool Calls
63
87
  tool_calls: z.array(ChatCompletionsToolCallSchema).optional(),
@@ -67,15 +91,17 @@ export const ChatCompletionsAssistantMessageSchema = z.object({
67
91
  .array(ChatCompletionsReasoningDetailSchema)
68
92
  .optional()
69
93
  .meta({ extension: true }),
70
- extra_content: z.record(z.string(), z.any()).optional().meta({ extension: true }),
94
+ extra_content: z
95
+ .record(z.string(), z.record(z.string(), z.unknown()))
96
+ .optional()
97
+ .meta({ extension: true }),
71
98
  });
72
99
  export const ChatCompletionsToolMessageSchema = z.object({
73
100
  role: z.literal("tool"),
74
- // FUTURE: this should also support arrays of TextContentParts
75
- content: z.string(),
101
+ content: z.union([z.string(), z.array(ChatCompletionsContentPartTextSchema)]),
76
102
  tool_call_id: z.string(),
77
103
  });
78
- export const ChatCompletionsMessageSchema = z.union([
104
+ export const ChatCompletionsMessageSchema = z.discriminatedUnion("role", [
79
105
  ChatCompletionsSystemMessageSchema,
80
106
  ChatCompletionsUserMessageSchema,
81
107
  ChatCompletionsAssistantMessageSchema,
@@ -86,14 +112,12 @@ export const ChatCompletionsToolSchema = z.object({
86
112
  function: z.object({
87
113
  name: z.string(),
88
114
  description: z.string().optional(),
89
- parameters: z.record(z.string(), z.any()),
115
+ parameters: z.record(z.string(), z.unknown()),
90
116
  // Missing strict parameter
91
117
  }),
92
118
  });
93
119
  export const ChatCompletionsToolChoiceSchema = z.union([
94
- z.literal("none"),
95
- z.literal("auto"),
96
- z.literal("required"),
120
+ z.enum(["none", "auto", "required", "validated"]),
97
121
  // FUTURE: missing AllowedTools and CustomToolChoice
98
122
  z.object({
99
123
  type: z.literal("function"),
@@ -102,13 +126,14 @@ export const ChatCompletionsToolChoiceSchema = z.union([
102
126
  }),
103
127
  }),
104
128
  ]);
105
- export const ChatCompletionsReasoningEffortSchema = z.union([
106
- z.literal("none"),
107
- z.literal("minimal"),
108
- z.literal("low"),
109
- z.literal("medium"),
110
- z.literal("high"),
111
- z.literal("xhigh"),
129
+ export const ChatCompletionsReasoningEffortSchema = z.enum([
130
+ "none",
131
+ "minimal",
132
+ "low",
133
+ "medium",
134
+ "high",
135
+ "xhigh",
136
+ "max",
112
137
  ]);
113
138
  export const ChatCompletionsReasoningConfigSchema = z.object({
114
139
  enabled: z.optional(z.boolean()),
@@ -116,6 +141,24 @@ export const ChatCompletionsReasoningConfigSchema = z.object({
116
141
  max_tokens: z.optional(z.number()),
117
142
  exclude: z.optional(z.boolean()),
118
143
  });
144
+ export const ChatCompletionsResponseFormatJsonSchema = z.object({
145
+ // FUTURE: consider support for legacy json_object (if demand)
146
+ type: z.literal("json_schema"),
147
+ json_schema: z.object({
148
+ name: z.string(),
149
+ description: z.string().optional(),
150
+ schema: z.record(z.string(), z.unknown()),
151
+ // FUTURE: consider support for non-strict mode (for providers that support it)
152
+ strict: z.boolean().optional(),
153
+ }),
154
+ });
155
+ export const ChatCompletionsResponseFormatTextSchema = z.object({
156
+ type: z.literal("text"),
157
+ });
158
+ export const ChatCompletionsResponseFormatSchema = z.discriminatedUnion("type", [
159
+ ChatCompletionsResponseFormatJsonSchema,
160
+ ChatCompletionsResponseFormatTextSchema,
161
+ ]);
119
162
  const ChatCompletionsInputsSchema = z.object({
120
163
  messages: z.array(ChatCompletionsMessageSchema),
121
164
  tools: z
@@ -132,6 +175,7 @@ const ChatCompletionsInputsSchema = z.object({
132
175
  seed: z.int().optional(),
133
176
  stop: z.union([z.string(), z.array(z.string())]).optional(),
134
177
  top_p: z.number().min(0).max(1.0).optional(),
178
+ response_format: ChatCompletionsResponseFormatSchema.optional(),
135
179
  reasoning_effort: ChatCompletionsReasoningEffortSchema.optional(),
136
180
  // Extensions
137
181
  reasoning: ChatCompletionsReasoningConfigSchema.optional().meta({ extension: true }),
@@ -141,18 +185,18 @@ export const ChatCompletionsBodySchema = z.looseObject({
141
185
  stream: z.boolean().optional(),
142
186
  ...ChatCompletionsInputsSchema.shape,
143
187
  });
144
- export const ChatCompletionsFinishReasonSchema = z.union([
145
- z.literal("stop"),
146
- z.literal("length"),
147
- z.literal("content_filter"),
148
- z.literal("tool_calls"),
188
+ export const ChatCompletionsFinishReasonSchema = z.enum([
189
+ "stop",
190
+ "length",
191
+ "content_filter",
192
+ "tool_calls",
149
193
  ]);
150
194
  export const ChatCompletionsChoiceSchema = z.object({
151
195
  index: z.int().nonnegative(),
152
196
  message: ChatCompletionsAssistantMessageSchema,
153
197
  finish_reason: ChatCompletionsFinishReasonSchema,
154
198
  // FUTURE: model this out
155
- logprobs: z.any().optional(),
199
+ logprobs: z.unknown().optional(),
156
200
  });
157
201
  export const ChatCompletionsUsageSchema = z.object({
158
202
  prompt_tokens: z.int().nonnegative().optional(),
@@ -179,7 +223,7 @@ export const ChatCompletionsSchema = z.object({
179
223
  choices: z.array(ChatCompletionsChoiceSchema),
180
224
  usage: ChatCompletionsUsageSchema.nullable(),
181
225
  // Extensions
182
- provider_metadata: z.any().optional().meta({ extension: true }),
226
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
183
227
  });
184
228
  export const ChatCompletionsToolCallDeltaSchema = ChatCompletionsToolCallSchema.partial().extend({
185
229
  index: z.int().nonnegative(),
@@ -192,7 +236,7 @@ export const ChatCompletionsChoiceDeltaSchema = z.object({
192
236
  delta: ChatCompletionsAssistantMessageDeltaSchema,
193
237
  finish_reason: ChatCompletionsFinishReasonSchema.nullable(),
194
238
  // FUTURE: model this out
195
- logprobs: z.any().optional(),
239
+ logprobs: z.unknown().optional(),
196
240
  });
197
241
  export const ChatCompletionsChunkSchema = z.object({
198
242
  id: z.string(),
@@ -202,5 +246,5 @@ export const ChatCompletionsChunkSchema = z.object({
202
246
  choices: z.array(ChatCompletionsChoiceDeltaSchema),
203
247
  usage: ChatCompletionsUsageSchema.nullable(),
204
248
  // Extensions
205
- provider_metadata: z.any().optional().meta({ extension: true }),
249
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
206
250
  });
@@ -33,6 +33,6 @@ export declare const EmbeddingsSchema: z.ZodObject<{
33
33
  prompt_tokens: z.ZodOptional<z.ZodInt>;
34
34
  total_tokens: z.ZodOptional<z.ZodInt>;
35
35
  }, z.core.$strip>>;
36
- provider_metadata: z.ZodOptional<z.ZodAny>;
36
+ provider_metadata: z.ZodOptional<z.ZodUnknown>;
37
37
  }, z.core.$strip>;
38
38
  export type Embeddings = z.infer<typeof EmbeddingsSchema>;
@@ -22,5 +22,5 @@ export const EmbeddingsSchema = z.object({
22
22
  model: z.string(),
23
23
  usage: EmbeddingsUsageSchema.nullable(),
24
24
  // Extensions
25
- provider_metadata: z.optional(z.any()).meta({ extension: true }),
25
+ provider_metadata: z.unknown().optional().meta({ extension: true }),
26
26
  });
@@ -5,6 +5,7 @@ export class GatewayError extends Error {
5
5
  constructor(error, status, code, cause) {
6
6
  const isError = error instanceof Error;
7
7
  super(isError ? error.message : String(error));
8
+ this.name = "GatewayError";
8
9
  this.cause = cause ?? (isError ? error : undefined);
9
10
  this.status = status;
10
11
  this.code = code ?? STATUS_CODE(status);
@@ -1,5 +1,4 @@
1
1
  import type { LogLevel, Logger } from "./index";
2
- export declare const getDefaultLogLevel: () => LogLevel;
3
2
  export declare const createDefaultLogger: (config: {
4
3
  level?: LogLevel;
5
4
  }) => Logger;
@@ -1,6 +1,5 @@
1
- import { serializeError } from "serialize-error";
2
1
  import { isProduction, isTest } from "../utils/env";
3
- export const getDefaultLogLevel = () => isTest() ? "silent" : isProduction() ? "info" : "debug";
2
+ const getDefaultLogLevel = () => isTest() ? "silent" : isProduction() ? "info" : "debug";
4
3
  const noop = () => { };
5
4
  const LEVEL = {
6
5
  trace: 5,
@@ -12,6 +11,31 @@ const LEVEL = {
12
11
  };
13
12
  const LEVELS = Object.keys(LEVEL);
14
13
  const isRecord = (value) => typeof value === "object" && value !== null && !(value instanceof Error);
14
+ function serializeError(err, _seen) {
15
+ if (!(err instanceof Error))
16
+ return { message: String(err) };
17
+ const seen = _seen ?? new WeakSet();
18
+ if (seen.has(err))
19
+ return { name: err.name, message: err.message, circular: true };
20
+ seen.add(err);
21
+ const out = {};
22
+ for (const k of Object.getOwnPropertyNames(err)) {
23
+ if (k.startsWith("_"))
24
+ continue;
25
+ let val;
26
+ try {
27
+ val = err[k];
28
+ }
29
+ catch {
30
+ val = "[Unreadable]";
31
+ }
32
+ if (typeof val === "bigint")
33
+ val = `${val}n`;
34
+ // FUTURE: check for circular references within val
35
+ out[String(k)] = val instanceof Error ? serializeError(val, seen) : val;
36
+ }
37
+ return out;
38
+ }
15
39
  const buildLogObject = (level, args) => {
16
40
  if (args.length === 0)
17
41
  return {};
@@ -20,11 +44,11 @@ const buildLogObject = (level, args) => {
20
44
  let err;
21
45
  let msg;
22
46
  if (first instanceof Error) {
23
- err = first;
47
+ err = serializeError(first);
24
48
  }
25
49
  else if (isRecord(first)) {
26
50
  if (first["err"] !== undefined) {
27
- err = first["err"];
51
+ err = serializeError(first["err"]);
28
52
  delete first["err"];
29
53
  }
30
54
  obj = first;
@@ -36,13 +60,13 @@ const buildLogObject = (level, args) => {
36
60
  msg = String(second);
37
61
  }
38
62
  if (err && msg === undefined) {
39
- msg = err instanceof Error ? err.message : String(err);
63
+ msg = err["message"];
40
64
  }
41
65
  return {
42
66
  level,
43
67
  time: Date.now(),
44
68
  ...(msg ? { msg } : {}),
45
- ...(err ? { err: err instanceof Error ? serializeError(err) : err } : {}),
69
+ ...(err ? { err } : {}),
46
70
  ...obj,
47
71
  };
48
72
  };
@@ -17,6 +17,7 @@ export function calculateReasoningBudgetFromEffort(effort, maxTokens, minTokens
17
17
  percentage = 0.8;
18
18
  break;
19
19
  case "xhigh":
20
+ case "max":
20
21
  percentage = 0.95;
21
22
  break;
22
23
  default:
@@ -24,6 +24,7 @@ function mapNovaEffort(effort) {
24
24
  return "medium";
25
25
  case "high":
26
26
  case "xhigh":
27
+ case "max":
27
28
  return "high";
28
29
  }
29
30
  }
@@ -1,2 +1,4 @@
1
1
  import type { LanguageModelMiddleware } from "ai";
2
+ import type { ChatCompletionsReasoningEffort } from "../../endpoints/chat-completions/schema";
3
+ export declare function mapClaudeReasoningEffort(effort: ChatCompletionsReasoningEffort, modelId: string): "low" | "high" | "medium" | "max";
2
4
  export declare const claudeReasoningMiddleware: LanguageModelMiddleware;
@@ -1,15 +1,54 @@
1
1
  import { modelMiddlewareMatcher } from "../../middleware/matcher";
2
2
  import { calculateReasoningBudgetFromEffort } from "../../middleware/utils";
3
- const CLAUDE_MAX_OUTPUT_TOKENS = 64000;
4
- const CLAUDE_OPUS_4_MAX_OUTPUT_TOKENS = 32000;
5
- function getMaxOutputTokens(modelId) {
6
- if (!modelId.includes("opus-4"))
7
- return CLAUDE_MAX_OUTPUT_TOKENS;
8
- if (modelId.includes("opus-4.5") || modelId.includes("opus-4-5")) {
9
- return CLAUDE_MAX_OUTPUT_TOKENS;
3
+ const isClaude = (family, version) => {
4
+ const dashed = version.replace(".", "-");
5
+ return (modelId) => modelId.includes(`claude-${family}-${version}`) ||
6
+ modelId.includes(`claude-${family}-${dashed}`);
7
+ };
8
+ const isOpus46 = isClaude("opus", "4.6");
9
+ const isOpus45 = isClaude("opus", "4.5");
10
+ const isOpus4 = isClaude("opus", "4");
11
+ const isSonnet46 = isClaude("sonnet", "4.6");
12
+ const isSonnet45 = isClaude("sonnet", "4.5");
13
+ export function mapClaudeReasoningEffort(effort, modelId) {
14
+ if (isOpus46(modelId)) {
15
+ switch (effort) {
16
+ case "none":
17
+ case "minimal":
18
+ case "low":
19
+ return "low";
20
+ case "medium":
21
+ return "medium";
22
+ case "high":
23
+ return "high";
24
+ case "xhigh":
25
+ case "max":
26
+ return "max";
27
+ }
10
28
  }
11
- return CLAUDE_OPUS_4_MAX_OUTPUT_TOKENS;
29
+ switch (effort) {
30
+ case "none":
31
+ case "minimal":
32
+ case "low":
33
+ return "low";
34
+ case "medium":
35
+ return "medium";
36
+ case "high":
37
+ case "xhigh":
38
+ case "max":
39
+ return "high";
40
+ }
41
+ }
42
+ function getMaxOutputTokens(modelId) {
43
+ if (isOpus46(modelId))
44
+ return 128_000;
45
+ if (isOpus45(modelId))
46
+ return 64_000;
47
+ if (isOpus4(modelId))
48
+ return 32_000;
49
+ return 64_000;
12
50
  }
51
+ // https://platform.claude.com/docs/en/build-with-claude/effort
13
52
  export const claudeReasoningMiddleware = {
14
53
  specificationVersion: "v3",
15
54
  // eslint-disable-next-line require-await
@@ -21,20 +60,42 @@ export const claudeReasoningMiddleware = {
21
60
  if (!reasoning)
22
61
  return params;
23
62
  const target = (params.providerOptions["anthropic"] ??= {});
63
+ const modelId = model.modelId;
64
+ const clampedMaxTokens = reasoning.max_tokens && Math.min(reasoning.max_tokens, getMaxOutputTokens(modelId));
24
65
  if (!reasoning.enabled) {
25
66
  target["thinking"] = { type: "disabled" };
26
67
  }
27
- else if (reasoning.max_tokens) {
28
- target["thinking"] = {
29
- type: "enabled",
30
- budgetTokens: Math.min(reasoning.max_tokens, getMaxOutputTokens(model.modelId)),
31
- };
32
- }
33
68
  else if (reasoning.effort) {
34
- // FUTURE: warn that reasoning.max_tokens was computed
69
+ if (isOpus46(modelId)) {
70
+ target["thinking"] = clampedMaxTokens
71
+ ? { type: "adaptive", budgetTokens: clampedMaxTokens }
72
+ : { type: "adaptive" };
73
+ target["effort"] = mapClaudeReasoningEffort(reasoning.effort, modelId);
74
+ }
75
+ else if (isSonnet46(modelId)) {
76
+ target["thinking"] = clampedMaxTokens
77
+ ? { type: "enabled", budgetTokens: clampedMaxTokens }
78
+ : { type: "adaptive" };
79
+ target["effort"] = mapClaudeReasoningEffort(reasoning.effort, modelId);
80
+ }
81
+ else if (isOpus45(modelId) || isSonnet45(modelId)) {
82
+ target["thinking"] = { type: "enabled" };
83
+ if (clampedMaxTokens)
84
+ target["thinking"]["budgetTokens"] = clampedMaxTokens;
85
+ target["effort"] = mapClaudeReasoningEffort(reasoning.effort, modelId);
86
+ }
87
+ else {
88
+ // FUTURE: warn that reasoning.max_tokens was computed
89
+ target["thinking"] = {
90
+ type: "enabled",
91
+ budgetTokens: calculateReasoningBudgetFromEffort(reasoning.effort, params.maxOutputTokens ?? getMaxOutputTokens(modelId), 1024),
92
+ };
93
+ }
94
+ }
95
+ else if (clampedMaxTokens) {
35
96
  target["thinking"] = {
36
97
  type: "enabled",
37
- budgetTokens: calculateReasoningBudgetFromEffort(reasoning.effort, params.maxOutputTokens ?? getMaxOutputTokens(model.modelId), 1024),
98
+ budgetTokens: clampedMaxTokens,
38
99
  };
39
100
  }
40
101
  else {
@@ -16,7 +16,22 @@ export const geminiDimensionsMiddleware = {
16
16
  return params;
17
17
  },
18
18
  };
19
+ // https://ai.google.dev/gemini-api/docs/thinking#thinking-levels
19
20
  export function mapGeminiReasoningEffort(effort, modelId) {
21
+ if (modelId.includes("gemini-3.1-pro")) {
22
+ switch (effort) {
23
+ case "none":
24
+ case "minimal":
25
+ case "low":
26
+ return "low";
27
+ case "medium":
28
+ return "medium";
29
+ case "high":
30
+ case "xhigh":
31
+ case "max":
32
+ return "high";
33
+ }
34
+ }
20
35
  if (modelId.includes("gemini-3-pro")) {
21
36
  switch (effort) {
22
37
  case "none":
@@ -26,6 +41,7 @@ export function mapGeminiReasoningEffort(effort, modelId) {
26
41
  case "medium":
27
42
  case "high":
28
43
  case "xhigh":
44
+ case "max":
29
45
  return "high";
30
46
  }
31
47
  }
@@ -40,6 +56,7 @@ export function mapGeminiReasoningEffort(effort, modelId) {
40
56
  return "medium";
41
57
  case "high":
42
58
  case "xhigh":
59
+ case "max":
43
60
  return "high";
44
61
  }
45
62
  }