@reconcrap/boss-recommend-mcp 1.3.15 → 1.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -167,6 +167,7 @@ config/screening-config.example.json
167
167
  - `openaiProject`
168
168
  - `debugPort`
169
169
  - `outputDir`
170
+ - `llmThinkingLevel`:默认 `off`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。
170
171
 
171
172
  ## 常用命令
172
173
 
@@ -175,7 +176,7 @@ npm 包安装后可直接使用可执行命令 `boss-recommend-mcp`。以下示
175
176
  ```bash
176
177
  node src/cli.js install --agent trae-cn
177
178
  node src/cli.js init-config
178
- node src/cli.js config set --base-url https://api.openai.com/v1 --api-key <your-key> --model gpt-4o-mini
179
+ node src/cli.js config set --base-url https://api.openai.com/v1 --api-key <your-key> --model gpt-4o-mini --thinking-level off
179
180
  node src/cli.js set-port --port 9222
180
181
  node src/cli.js doctor --agent trae-cn
181
182
  node src/cli.js launch-chrome --port 9222
@@ -2,6 +2,7 @@
2
2
  "baseUrl": "https://api.openai.com/v1",
3
3
  "apiKey": "replace-with-openai-api-key",
4
4
  "model": "gpt-4.1-mini",
5
+ "llmThinkingLevel": "off",
5
6
  "openaiOrganization": "optional-org-id",
6
7
  "openaiProject": "optional-project-id"
7
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.15",
3
+ "version": "1.3.17",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/adapters.js CHANGED
@@ -21,6 +21,12 @@ const screenConfigTemplateDefaults = {
21
21
  apiKey: "replace-with-openai-api-key",
22
22
  model: "gpt-4.1-mini"
23
23
  };
24
+ const LLM_THINKING_LEVEL_FIELDS = [
25
+ "llmThinkingLevel",
26
+ "thinkingLevel",
27
+ "reasoningEffort",
28
+ "reasoning_effort"
29
+ ];
24
30
  const DEFAULT_RECOMMEND_SCREEN_TIMEOUT_MS = 24 * 60 * 60 * 1000;
25
31
  const PAGE_SCOPE_TO_TAB_STATUS = {
26
32
  recommend: "0",
@@ -324,7 +330,7 @@ function readJsonFile(filePath) {
324
330
  }
325
331
  }
326
332
 
327
- function validateScreenConfig(config) {
333
+ function validateScreenConfig(config) {
328
334
  if (!config || typeof config !== "object" || Array.isArray(config)) {
329
335
  return {
330
336
  ok: false,
@@ -364,8 +370,17 @@ function validateScreenConfig(config) {
364
370
  message: "screening-config.json 仍是默认模板值,请填写 baseUrl、apiKey、model。"
365
371
  };
366
372
  }
367
- return { ok: true, reason: "OK", message: "screening-config.json 校验通过。" };
368
- }
373
+ return { ok: true, reason: "OK", message: "screening-config.json 校验通过。" };
374
+ }
375
+
376
+ function resolveLlmThinkingLevel(config = {}) {
377
+ if (!config || typeof config !== "object") return "";
378
+ for (const field of LLM_THINKING_LEVEL_FIELDS) {
379
+ const value = String(config[field] ?? "").trim();
380
+ if (value) return value;
381
+ }
382
+ return "";
383
+ }
369
384
 
370
385
  function resolveWorkspaceDebugPort(workspaceRoot) {
371
386
  const fromEnv = parsePositiveInteger(process.env.BOSS_RECOMMEND_CHROME_PORT);
@@ -2915,12 +2930,16 @@ export async function runRecommendScreenCli({
2915
2930
  if (loaded.config.openaiOrganization) {
2916
2931
  args.push("--openai-organization", loaded.config.openaiOrganization);
2917
2932
  }
2918
- if (loaded.config.openaiProject) {
2919
- args.push("--openai-project", loaded.config.openaiProject);
2920
- }
2921
- if (Number.isInteger(screenParams.target_count) && screenParams.target_count > 0) {
2922
- args.push("--targetCount", String(screenParams.target_count));
2923
- }
2933
+ if (loaded.config.openaiProject) {
2934
+ args.push("--openai-project", loaded.config.openaiProject);
2935
+ }
2936
+ const llmThinkingLevel = resolveLlmThinkingLevel(loaded.config);
2937
+ if (llmThinkingLevel) {
2938
+ args.push("--thinking-level", llmThinkingLevel);
2939
+ }
2940
+ if (Number.isInteger(screenParams.target_count) && screenParams.target_count > 0) {
2941
+ args.push("--targetCount", String(screenParams.target_count));
2942
+ }
2924
2943
  if (screenParams.post_action === "greet"
2925
2944
  && Number.isInteger(screenParams.max_greet_count)
2926
2945
  && screenParams.max_greet_count > 0) {
package/src/boss-chat.js CHANGED
@@ -16,6 +16,12 @@ const BOSS_CHAT_TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
16
16
  const CHAT_REQUIRED_FIELDS = ["job", "start_from", "target_count", "criteria"];
17
17
  export const TARGET_COUNT_ACCEPTED_EXAMPLES = ["all", -1, 20, "全部候选人"];
18
18
  const TARGET_COUNT_WRAPPER_KEYS = ["target_count", "targetCount", "value", "count", "limit"];
19
+ const LLM_THINKING_LEVEL_FIELDS = [
20
+ "llmThinkingLevel",
21
+ "thinkingLevel",
22
+ "reasoningEffort",
23
+ "reasoning_effort"
24
+ ];
19
25
 
20
26
  function normalizeText(value) {
21
27
  return String(value || "").replace(/\s+/g, " ").trim();
@@ -228,6 +234,15 @@ function validateRecommendScreenConfig(config) {
228
234
  return { ok: true };
229
235
  }
230
236
 
237
+ function resolveLlmThinkingLevel(config = {}) {
238
+ if (!config || typeof config !== "object") return "";
239
+ for (const field of LLM_THINKING_LEVEL_FIELDS) {
240
+ const value = normalizeText(config[field]);
241
+ if (value) return value;
242
+ }
243
+ return "";
244
+ }
245
+
231
246
  function resolveBossChatScreenConfig(workspaceRoot) {
232
247
  const resolution = getScreenConfigResolution(workspaceRoot);
233
248
  const configPath = resolution.resolved_path || resolution.writable_path || resolution.legacy_path || null;
@@ -274,6 +289,7 @@ function resolveBossChatScreenConfig(workspaceRoot) {
274
289
  baseUrl: normalizeText(parsed.baseUrl).replace(/\/+$/, ""),
275
290
  apiKey: normalizeText(parsed.apiKey),
276
291
  model: normalizeText(parsed.model),
292
+ llmThinkingLevel: resolveLlmThinkingLevel(parsed),
277
293
  debugPort: parsePositiveInteger(parsed.debugPort, 9222)
278
294
  },
279
295
  config_path: configPath,
@@ -385,6 +401,9 @@ function buildBossChatCliArgs(command, input, resolvedConfig) {
385
401
  args.push("--baseurl", resolvedConfig.baseUrl);
386
402
  args.push("--apikey", resolvedConfig.apiKey);
387
403
  args.push("--model", resolvedConfig.model);
404
+ if (resolvedConfig.llmThinkingLevel) {
405
+ args.push("--thinking-level", resolvedConfig.llmThinkingLevel);
406
+ }
388
407
  return args;
389
408
  }
390
409
 
@@ -402,6 +421,9 @@ function buildBossChatCliArgs(command, input, resolvedConfig) {
402
421
  args.push("--baseurl", resolvedConfig.baseUrl);
403
422
  args.push("--apikey", resolvedConfig.apiKey);
404
423
  args.push("--model", resolvedConfig.model);
424
+ if (resolvedConfig.llmThinkingLevel) {
425
+ args.push("--thinking-level", resolvedConfig.llmThinkingLevel);
426
+ }
405
427
  args.push("--port", String(normalized.port || resolvedConfig.debugPort || 9222));
406
428
  if (typeof normalized.safePacing === "boolean") {
407
429
  args.push("--safe-pacing", String(normalized.safePacing));
package/src/cli.js CHANGED
@@ -768,6 +768,11 @@ function setScreeningConfig(options = {}) {
768
768
  apiKey,
769
769
  model
770
770
  };
771
+ if (typeof options["thinking-level"] === "string" && options["thinking-level"].trim()) {
772
+ nextConfig.llmThinkingLevel = options["thinking-level"].trim();
773
+ } else if (typeof options.llmThinkingLevel === "string" && options.llmThinkingLevel.trim()) {
774
+ nextConfig.llmThinkingLevel = options.llmThinkingLevel.trim();
775
+ }
771
776
  if (typeof options["openai-organization"] === "string") {
772
777
  nextConfig.openaiOrganization = options["openai-organization"];
773
778
  }
@@ -1247,7 +1252,7 @@ function printHelp() {
1247
1252
  console.log("Run command:");
1248
1253
  console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" [--confirmation-json '{...}'] [--overrides-json '{...}'] [--follow-up-json '{...}']");
1249
1254
  console.log(" boss-recommend-mcp chat run --job \"算法工程师\" --start-from unread --criteria \"有 AI Agent 经验\" --targetCount 20 # 后台启动,不自动轮询");
1250
- console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--openai-organization <id>] [--openai-project <id>]");
1255
+ console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--thinking-level off|low|medium|high|current] [--openai-organization <id>] [--openai-project <id>]");
1251
1256
  console.log(" boss-recommend-mcp install --agent trae-cn");
1252
1257
  console.log(" boss-recommend-mcp doctor --agent trae-cn --page-scope featured");
1253
1258
  console.log(" boss-recommend-mcp calibrate --port 9222 [--timeout-ms 60000] [--output <path>]");
@@ -717,6 +717,94 @@ async function testBossChatLlmTextChunkFallbackShouldWork() {
717
717
  }
718
718
  }
719
719
 
720
+ async function testBossChatLlmShouldApplyThinkingDefaultsAndOverrides() {
721
+ const completionResponse = {
722
+ ok: true,
723
+ status: 200,
724
+ async json() {
725
+ return {
726
+ choices: [
727
+ {
728
+ message: {
729
+ content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
730
+ }
731
+ }
732
+ ]
733
+ };
734
+ }
735
+ };
736
+ const responsesResponse = {
737
+ ok: true,
738
+ status: 200,
739
+ async json() {
740
+ return {
741
+ output_text: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
742
+ };
743
+ }
744
+ };
745
+
746
+ let volcCompletionPayload = null;
747
+ const volcClient = new LlmClient({
748
+ baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
749
+ apiKey: "sk-test",
750
+ model: "doubao-seed-2-0-mini-260215",
751
+ }, {
752
+ fetchImpl: async (_url, options = {}) => {
753
+ volcCompletionPayload = JSON.parse(String(options.body || "{}"));
754
+ return completionResponse;
755
+ },
756
+ });
757
+ await volcClient.requestCompletions({ prompt: "prompt", evidenceCorpus: "resume" });
758
+ assert.deepEqual(volcCompletionPayload.thinking, { type: "disabled" });
759
+ assert.equal(volcCompletionPayload.reasoning_effort, "minimal");
760
+
761
+ let lowCompletionPayload = null;
762
+ const lowClient = new LlmClient({
763
+ baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
764
+ apiKey: "sk-test",
765
+ model: "doubao-seed-2-0-mini-260215",
766
+ thinkingLevel: "low",
767
+ }, {
768
+ fetchImpl: async (_url, options = {}) => {
769
+ lowCompletionPayload = JSON.parse(String(options.body || "{}"));
770
+ return completionResponse;
771
+ },
772
+ });
773
+ await lowClient.requestCompletions({ prompt: "prompt", evidenceCorpus: "resume" });
774
+ assert.deepEqual(lowCompletionPayload.thinking, { type: "enabled" });
775
+ assert.equal(lowCompletionPayload.reasoning_effort, "low");
776
+
777
+ let openaiCompletionPayload = null;
778
+ const openaiClient = new LlmClient({
779
+ baseUrl: "https://api.openai.com/v1",
780
+ apiKey: "sk-test",
781
+ model: "gpt-test",
782
+ }, {
783
+ fetchImpl: async (_url, options = {}) => {
784
+ openaiCompletionPayload = JSON.parse(String(options.body || "{}"));
785
+ return completionResponse;
786
+ },
787
+ });
788
+ await openaiClient.requestCompletions({ prompt: "prompt", evidenceCorpus: "resume" });
789
+ assert.equal(openaiCompletionPayload.thinking, undefined);
790
+ assert.equal(openaiCompletionPayload.reasoning_effort, "minimal");
791
+
792
+ let responsesPayload = null;
793
+ const responsesClient = new LlmClient({
794
+ baseUrl: "https://api.openai.com/v1",
795
+ apiKey: "sk-test",
796
+ model: "gpt-test",
797
+ thinkingLevel: "low",
798
+ }, {
799
+ fetchImpl: async (_url, options = {}) => {
800
+ responsesPayload = JSON.parse(String(options.body || "{}"));
801
+ return responsesResponse;
802
+ },
803
+ });
804
+ await responsesClient.requestResponses({ prompt: "prompt", evidenceCorpus: "resume" });
805
+ assert.deepEqual(responsesPayload.reasoning, { effort: "low" });
806
+ }
807
+
720
808
  async function testBossChatAppShouldPersistEvidenceArtifacts() {
721
809
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-artifacts-"));
722
810
  await mkdir(tempDir, { recursive: true });
@@ -846,6 +934,7 @@ async function main() {
846
934
  testBossChatLlmEvidenceGateShouldDemoteMissingEvidence();
847
935
  testBossChatLlmEvidenceGateShouldDemoteUnmatchedEvidence();
848
936
  await testBossChatLlmTextChunkFallbackShouldWork();
937
+ await testBossChatLlmShouldApplyThinkingDefaultsAndOverrides();
849
938
  await testBossChatAppShouldPersistEvidenceArtifacts();
850
939
  console.log("boss-chat tests passed");
851
940
  }
@@ -249,6 +249,12 @@ function parseArgs(argv) {
249
249
  case 'model':
250
250
  args.overrides.llm.model = value || '';
251
251
  break;
252
+ case 'thinking-level':
253
+ case 'thinkingLevel':
254
+ case 'llm-thinking-level':
255
+ case 'reasoning-effort':
256
+ args.overrides.llm.thinkingLevel = value || '';
257
+ break;
252
258
  case 'port':
253
259
  args.overrides.chrome.port = Number.parseInt(value, 10);
254
260
  break;
@@ -299,6 +305,7 @@ function printUsage() {
299
305
  console.log(' --baseurl <url> Override LLM base URL');
300
306
  console.log(' --apikey <key> Override LLM API key');
301
307
  console.log(' --model <name> Override LLM model');
308
+ console.log(' --thinking-level <level> LLM thinking level: off|low|medium|high|current');
302
309
  console.log(' --port <n> Override Chrome remote debugging port');
303
310
  }
304
311
 
@@ -768,6 +775,9 @@ function buildDetachedRunArgs(args, runId) {
768
775
  if (args.overrides.llm.model) {
769
776
  workerArgs.push('--model', String(args.overrides.llm.model));
770
777
  }
778
+ if (args.overrides.llm.thinkingLevel) {
779
+ workerArgs.push('--thinking-level', String(args.overrides.llm.thinkingLevel));
780
+ }
771
781
  if (Number.isFinite(args.overrides.chrome.port)) {
772
782
  workerArgs.push('--port', String(args.overrides.chrome.port));
773
783
  }
@@ -74,6 +74,7 @@ function registerTools(server) {
74
74
  baseUrl: z.string().optional().describe('覆盖 LLM baseUrl'),
75
75
  apiKey: z.string().optional().describe('覆盖 LLM apiKey'),
76
76
  model: z.string().optional().describe('覆盖 LLM 模型'),
77
+ thinkingLevel: z.string().optional().describe('覆盖 LLM thinking/reasoning 级别:off/low/medium/high/current'),
77
78
  port: z.number().int().positive().optional().describe('Chrome 调试端口'),
78
79
  safePacing: z.boolean().optional().describe('是否启用安全节奏控制'),
79
80
  batchRestEnabled: z.boolean().optional().describe('是否启用批次休息'),
@@ -146,4 +147,3 @@ main().catch((error) => {
146
147
  console.error('[boss-chat-mcp] server failed:', error?.stack || error?.message || String(error));
147
148
  process.exit(1);
148
149
  });
149
-
@@ -71,6 +71,7 @@ export function buildCliArgs(command, input = {}) {
71
71
  pushValueArg(args, 'baseurl', input.baseUrl);
72
72
  pushValueArg(args, 'apikey', input.apiKey);
73
73
  pushValueArg(args, 'model', input.model);
74
+ pushValueArg(args, 'thinking-level', input.thinkingLevel || input.llmThinkingLevel || input.reasoningEffort);
74
75
 
75
76
  const port = normalizePositiveInt(input.port);
76
77
  if (port) {
@@ -190,4 +191,3 @@ export async function runCliJsonCommand(command, input = {}) {
190
191
  });
191
192
  });
192
193
  }
193
-
@@ -4,6 +4,12 @@ const DEFAULT_TEXT_MODEL_CHUNK_SIZE_CHARS = 24000;
4
4
  const DEFAULT_TEXT_MODEL_CHUNK_OVERLAP_CHARS = 1200;
5
5
  const DEFAULT_TEXT_MODEL_MAX_CHUNKS = 12;
6
6
  const MAX_EVIDENCE_TOKENS = 12;
7
+ const LLM_THINKING_ENV_KEYS = [
8
+ 'BOSS_CHAT_LLM_THINKING_LEVEL',
9
+ 'BOSS_RECOMMEND_LLM_THINKING_LEVEL',
10
+ 'BOSS_LLM_THINKING_LEVEL',
11
+ 'LLM_THINKING_LEVEL',
12
+ ];
7
13
 
8
14
  function normalizeText(value) {
9
15
  return String(value || '').replace(/\s+/g, ' ').trim();
@@ -67,6 +73,84 @@ function normalizeBool(value, fallback = false) {
67
73
  return fallback;
68
74
  }
69
75
 
76
+ function normalizeLlmThinkingLevel(value) {
77
+ const normalized = normalizeText(value).toLowerCase().replace(/[_\s]+/g, '-');
78
+ if (!normalized) return '';
79
+ if (['off', 'disabled', 'disable', 'minimal', 'none', 'false', '0'].includes(normalized)) return 'off';
80
+ if (
81
+ ['low', 'medium', 'high', 'auto', 'current', 'default', 'provider-default', 'unchanged', 'inherit'].includes(
82
+ normalized,
83
+ )
84
+ ) {
85
+ return normalized;
86
+ }
87
+ return '';
88
+ }
89
+
90
+ function getEnvLlmThinkingLevel() {
91
+ for (const key of LLM_THINKING_ENV_KEYS) {
92
+ const normalized = normalizeLlmThinkingLevel(process.env[key]);
93
+ if (normalized) return normalized;
94
+ }
95
+ return '';
96
+ }
97
+
98
+ function resolveLlmThinkingLevel(config = {}, options = {}) {
99
+ return (
100
+ normalizeLlmThinkingLevel(options.thinkingLevel) ||
101
+ normalizeLlmThinkingLevel(options.llmThinkingLevel) ||
102
+ normalizeLlmThinkingLevel(config.llmThinkingLevel) ||
103
+ normalizeLlmThinkingLevel(config.thinkingLevel) ||
104
+ normalizeLlmThinkingLevel(config.reasoningEffort) ||
105
+ normalizeLlmThinkingLevel(config.reasoning_effort) ||
106
+ getEnvLlmThinkingLevel() ||
107
+ 'off'
108
+ );
109
+ }
110
+
111
+ function isProviderDefaultThinkingLevel(level) {
112
+ return ['current', 'default', 'provider-default', 'unchanged', 'inherit'].includes(level);
113
+ }
114
+
115
+ function isVolcengineModel(baseUrl, model) {
116
+ const combined = `${baseUrl || ''} ${model || ''}`;
117
+ return /volces\.com|volcengine|ark\.cn-|doubao|seed/i.test(combined);
118
+ }
119
+
120
+ function applyChatCompletionThinking(payload, { baseUrl = '', model = '', thinkingLevel = '' } = {}) {
121
+ const level = normalizeLlmThinkingLevel(thinkingLevel) || 'off';
122
+ if (isProviderDefaultThinkingLevel(level)) return payload;
123
+ const isVolc = isVolcengineModel(baseUrl, model);
124
+ if (isVolc) {
125
+ if (level === 'auto') {
126
+ payload.thinking = { type: 'auto' };
127
+ return payload;
128
+ }
129
+ if (level === 'off') {
130
+ payload.thinking = { type: 'disabled' };
131
+ payload.reasoning_effort = 'minimal';
132
+ return payload;
133
+ }
134
+ payload.thinking = { type: 'enabled' };
135
+ payload.reasoning_effort = level;
136
+ return payload;
137
+ }
138
+ if (level !== 'auto') {
139
+ payload.reasoning_effort = level === 'off' ? 'minimal' : level;
140
+ }
141
+ return payload;
142
+ }
143
+
144
+ function applyResponsesThinking(payload, { thinkingLevel = '' } = {}) {
145
+ const level = normalizeLlmThinkingLevel(thinkingLevel) || 'off';
146
+ if (isProviderDefaultThinkingLevel(level) || level === 'auto') return payload;
147
+ payload.reasoning = {
148
+ ...(payload.reasoning || {}),
149
+ effort: level === 'off' ? 'minimal' : level,
150
+ };
151
+ return payload;
152
+ }
153
+
70
154
  function toStringArray(value, maxItems = 8) {
71
155
  if (!Array.isArray(value)) return [];
72
156
  const normalized = [];
@@ -378,8 +462,9 @@ export class LlmClient {
378
462
  options.preferCompletions !== undefined
379
463
  ? normalizeBool(options.preferCompletions, false)
380
464
  : config.preferCompletions !== undefined
381
- ? normalizeBool(config.preferCompletions, false)
382
- : /doubao|seed/i.test(String(this.model || ''));
465
+ ? normalizeBool(config.preferCompletions, false)
466
+ : /doubao|seed/i.test(String(this.model || ''));
467
+ this.thinkingLevel = resolveLlmThinkingLevel(config, options);
383
468
  }
384
469
 
385
470
  async readImageAsDataUrl(imagePath) {
@@ -405,23 +490,26 @@ export class LlmClient {
405
490
  if (imageDataUrl) {
406
491
  content.push({ type: 'input_image', image_url: imageDataUrl });
407
492
  }
493
+ const payload = {
494
+ model: this.model,
495
+ temperature: 0.1,
496
+ max_output_tokens: this.responseMaxOutputTokens,
497
+ input: [
498
+ {
499
+ role: 'user',
500
+ content,
501
+ },
502
+ ],
503
+ };
504
+ applyResponsesThinking(payload, { thinkingLevel: this.thinkingLevel });
505
+
408
506
  const response = await this.fetchImpl(`${this.baseUrl}/responses`, {
409
507
  method: 'POST',
410
508
  headers: {
411
509
  'Content-Type': 'application/json',
412
510
  Authorization: `Bearer ${this.apiKey}`,
413
511
  },
414
- body: JSON.stringify({
415
- model: this.model,
416
- temperature: 0.1,
417
- max_output_tokens: this.responseMaxOutputTokens,
418
- input: [
419
- {
420
- role: 'user',
421
- content,
422
- },
423
- ],
424
- }),
512
+ body: JSON.stringify(payload),
425
513
  signal: AbortSignal.timeout(this.timeoutMs),
426
514
  });
427
515
 
@@ -477,23 +565,30 @@ export class LlmClient {
477
565
  if (imageDataUrl) {
478
566
  content.push({ type: 'image_url', image_url: { url: imageDataUrl } });
479
567
  }
568
+ const payload = {
569
+ model: this.model,
570
+ temperature: 0.1,
571
+ max_tokens: this.completionMaxTokens,
572
+ messages: [
573
+ {
574
+ role: 'user',
575
+ content,
576
+ },
577
+ ],
578
+ };
579
+ applyChatCompletionThinking(payload, {
580
+ baseUrl: this.baseUrl,
581
+ model: this.model,
582
+ thinkingLevel: this.thinkingLevel,
583
+ });
584
+
480
585
  const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
481
586
  method: 'POST',
482
587
  headers: {
483
588
  'Content-Type': 'application/json',
484
589
  Authorization: `Bearer ${this.apiKey}`,
485
590
  },
486
- body: JSON.stringify({
487
- model: this.model,
488
- temperature: 0.1,
489
- max_tokens: this.completionMaxTokens,
490
- messages: [
491
- {
492
- role: 'user',
493
- content,
494
- },
495
- ],
496
- }),
591
+ body: JSON.stringify(payload),
497
592
  signal: AbortSignal.timeout(this.timeoutMs),
498
593
  });
499
594
 
@@ -10,6 +10,7 @@ const DEFAULT_PROFILE = {
10
10
  baseUrl: '',
11
11
  apiKey: '',
12
12
  model: '',
13
+ thinkingLevel: '',
13
14
  },
14
15
  chrome: {
15
16
  port: 9222,
@@ -78,6 +79,7 @@ export function toPersistentProfile(profile = {}) {
78
79
  baseUrl: normalized.llm.baseUrl,
79
80
  apiKey: normalized.llm.apiKey,
80
81
  model: normalized.llm.model,
82
+ thinkingLevel: normalized.llm.thinkingLevel,
81
83
  },
82
84
  chrome: {
83
85
  port: normalized.chrome.port,
@@ -99,6 +101,9 @@ export function normalizeProfile(profile = {}) {
99
101
  merged.llm.baseUrl = String(merged.llm.baseUrl || '').trim().replace(/\/+$/, '');
100
102
  merged.llm.apiKey = String(merged.llm.apiKey || '').trim();
101
103
  merged.llm.model = String(merged.llm.model || '').trim();
104
+ merged.llm.thinkingLevel = String(
105
+ merged.llm.thinkingLevel || merged.llm.llmThinkingLevel || merged.llm.reasoningEffort || merged.llm.reasoning_effort || '',
106
+ ).trim();
102
107
  merged.runtime.batchRestEnabled = merged.runtime.batchRestEnabled !== false;
103
108
  merged.runtime.safePacing = merged.runtime.safePacing !== false;
104
109
  return merged;