@cognipeer/agent-sdk 0.12.0 → 0.14.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.
package/dist/index.mjs CHANGED
@@ -2016,27 +2016,71 @@ function convertMessageListToSections(messageList) {
2016
2016
  }
2017
2017
  return sections.length > 0 ? sections : void 0;
2018
2018
  }
2019
- function createTraceSession(opts) {
2019
+ function readTraceContinuation(value) {
2020
+ if (!value || typeof value !== "object") return void 0;
2021
+ const raw = value;
2022
+ if (raw.v !== 1 || typeof raw.sessionId !== "string" || raw.sessionId.length === 0) return void 0;
2023
+ const nextSequence = Number(raw.nextSequence);
2024
+ const startedAt = Number(raw.startedAt);
2025
+ const part = Number(raw.part);
2026
+ if (!Number.isInteger(nextSequence) || nextSequence < 1) return void 0;
2027
+ if (!Number.isFinite(startedAt) || !Number.isInteger(part) || part < 1) return void 0;
2028
+ const summary = raw.summary && typeof raw.summary === "object" ? { ...createEmptySummary(), ...raw.summary } : createEmptySummary();
2029
+ summary.eventCounts = { ...raw.summary?.eventCounts ?? {} };
2030
+ return {
2031
+ v: 1,
2032
+ sessionId: raw.sessionId,
2033
+ traceId: typeof raw.traceId === "string" ? raw.traceId : void 0,
2034
+ rootSpanId: typeof raw.rootSpanId === "string" ? raw.rootSpanId : void 0,
2035
+ threadId: typeof raw.threadId === "string" ? raw.threadId : void 0,
2036
+ startedAt,
2037
+ nextSequence,
2038
+ part,
2039
+ summary,
2040
+ errors: Array.isArray(raw.errors) ? raw.errors : []
2041
+ };
2042
+ }
2043
+ function traceContinuationOf(session) {
2044
+ return {
2045
+ v: 1,
2046
+ sessionId: session.sessionId,
2047
+ traceId: session.traceId,
2048
+ rootSpanId: session.rootSpanId,
2049
+ threadId: session.threadId,
2050
+ startedAt: session.startedAt,
2051
+ nextSequence: (session.sequenceOffset ?? 0) + session.events.length + 1,
2052
+ part: session.part ?? 1,
2053
+ summary: JSON.parse(JSON.stringify(session.summary)),
2054
+ // Sink failures belong to the part that hit them; carrying them on would
2055
+ // mark every later part `partial`.
2056
+ errors: session.errors.filter((error) => error.type !== "sink")
2057
+ };
2058
+ }
2059
+ function createTraceSession(opts, continuation) {
2020
2060
  const cfg = withDefaults(opts.tracing);
2021
2061
  if (!cfg.enabled) return void 0;
2022
2062
  const providedSessionId = opts.tracing?.sessionId?.trim();
2023
- const sessionId = providedSessionId || generateSessionId();
2024
- const traceId = generateTraceId();
2025
- const rootSpanId = generateSpanId();
2063
+ const sessionId = continuation?.sessionId || providedSessionId || generateSessionId();
2064
+ const traceId = continuation?.traceId || generateTraceId();
2065
+ const rootSpanId = continuation?.rootSpanId || generateSpanId();
2026
2066
  const runtime = {
2027
2067
  sessionId,
2028
2068
  traceId,
2029
2069
  rootSpanId,
2030
- threadId: opts.tracing?.threadId,
2070
+ threadId: continuation?.threadId ?? opts.tracing?.threadId,
2031
2071
  configAgentName: opts.tracing?.agentName?.trim() || void 0,
2032
2072
  configMetadata: opts.tracing?.metadata && Object.keys(opts.tracing.metadata).length > 0 ? opts.tracing.metadata : void 0,
2033
- startedAt: Date.now(),
2073
+ startedAt: continuation?.startedAt ?? Date.now(),
2034
2074
  resolvedConfig: cfg,
2035
2075
  events: [],
2036
- summary: createEmptySummary(),
2076
+ summary: continuation ? continuation.summary : createEmptySummary(),
2037
2077
  status: "in_progress",
2038
- errors: [],
2039
- sessionStarted: false
2078
+ errors: continuation ? [...continuation.errors] : [],
2079
+ // A continued session was announced by its first part; announcing it
2080
+ // again would reset a streaming receiver's copy.
2081
+ sessionStarted: Boolean(continuation),
2082
+ sequenceOffset: continuation ? continuation.nextSequence - 1 : 0,
2083
+ part: continuation ? continuation.part + 1 : 1
2040
2084
  };
2041
2085
  if (cfg.sink.type === "file") {
2042
2086
  const baseDir = cfg.sink.baseDir;
@@ -2096,7 +2140,7 @@ async function startStreamingSession(session, agentRuntime) {
2096
2140
  }
2097
2141
  function recordTraceEvent(session, event) {
2098
2142
  if (!session) return void 0;
2099
- const sequence = session.events.length + 1;
2143
+ const sequence = (session.sequenceOffset ?? 0) + session.events.length + 1;
2100
2144
  const id = generateEventId(sequence);
2101
2145
  const timestampIso = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
2102
2146
  const status = event.status ?? "success";
@@ -2286,7 +2330,8 @@ async function finalizeTraceSession(session, params = {}) {
2286
2330
  summary: session.summary,
2287
2331
  events: session.events,
2288
2332
  status: status2,
2289
- errors: session.errors
2333
+ errors: session.errors,
2334
+ ...(session.part ?? 1) > 1 ? { continuation: { part: session.part, firstSequence: (session.sequenceOffset ?? 0) + 1 } } : {}
2290
2335
  });
2291
2336
  const payloadForSink = buildPayload(initialStatus);
2292
2337
  let sinkFailed = false;
@@ -2343,7 +2388,17 @@ async function finalizeTraceSession(session, params = {}) {
2343
2388
  if (sink.type === "file" && session.fileSessionDir) {
2344
2389
  try {
2345
2390
  const filePath = path.join(session.fileSessionDir, "trace.session.json");
2346
- await fs.promises.writeFile(filePath, JSON.stringify(finalPayload, null, 2), "utf8");
2391
+ let filePayload = finalPayload;
2392
+ if (finalPayload.continuation) {
2393
+ try {
2394
+ const previous = JSON.parse(await fs.promises.readFile(filePath, "utf8"));
2395
+ const earlier = (previous.events ?? []).filter((event) => event.sequence < finalPayload.continuation.firstSequence);
2396
+ filePayload = { ...finalPayload, events: [...earlier, ...finalPayload.events] };
2397
+ delete filePayload.continuation;
2398
+ } catch {
2399
+ }
2400
+ }
2401
+ await fs.promises.writeFile(filePath, JSON.stringify(filePayload, null, 2), "utf8");
2347
2402
  } catch (err) {
2348
2403
  sinkFailed = true;
2349
2404
  const message = err instanceof Error ? err.message : String(err);
@@ -4782,6 +4837,207 @@ ${append}`;
4782
4837
  };
4783
4838
  }
4784
4839
 
4840
+ // src/utils/traceDecisions.ts
4841
+ var TRACED_KEY = "__outcomeTraced";
4842
+ function waitedMs(from, to) {
4843
+ if (!from || !to) return void 0;
4844
+ const ms = Date.parse(to) - Date.parse(from);
4845
+ return Number.isFinite(ms) && ms >= 0 ? ms : void 0;
4846
+ }
4847
+ function sameJson(a, b) {
4848
+ try {
4849
+ return JSON.stringify(a) === JSON.stringify(b);
4850
+ } catch {
4851
+ return false;
4852
+ }
4853
+ }
4854
+ function recordApprovalRequested(session, entry) {
4855
+ if (!session) return;
4856
+ const logData = session.resolvedConfig.logData;
4857
+ const sections = [
4858
+ {
4859
+ kind: "metadata",
4860
+ label: "Approval",
4861
+ data: {
4862
+ approvalId: entry.id,
4863
+ toolCallId: entry.toolCallId,
4864
+ status: "pending",
4865
+ requestedAt: entry.requestedAt,
4866
+ ...entry.metadata?.prompt !== void 0 ? { prompt: entry.metadata.prompt } : {}
4867
+ }
4868
+ }
4869
+ ];
4870
+ if (logData) {
4871
+ sections.push({ kind: "tool_call", label: "Arguments to approve", tool: entry.toolName, arguments: sanitizeTracePayload(entry.args) });
4872
+ }
4873
+ recordTraceEvent(session, {
4874
+ type: "tool_approval",
4875
+ label: `Approval requested \xB7 ${entry.toolName}`,
4876
+ timestamp: entry.requestedAt,
4877
+ status: "pending",
4878
+ actor: { scope: "agent", name: entry.toolName, role: "approval_request" },
4879
+ sections
4880
+ });
4881
+ }
4882
+ function recordHumanDecisions(session, state) {
4883
+ if (!session) return;
4884
+ const logData = session.resolvedConfig.logData;
4885
+ for (const entry of state.pendingApprovals ?? []) {
4886
+ if (!entry.decidedAt || entry.metadata?.[TRACED_KEY]) continue;
4887
+ if (entry.status !== "approved" && entry.status !== "rejected") continue;
4888
+ const approved = entry.status === "approved";
4889
+ const edited = approved && entry.approvedArgs !== void 0 && !sameJson(entry.approvedArgs, entry.args);
4890
+ const sections = [
4891
+ {
4892
+ kind: "metadata",
4893
+ label: "Decision",
4894
+ data: {
4895
+ approvalId: entry.id,
4896
+ toolCallId: entry.toolCallId,
4897
+ decision: entry.status,
4898
+ ...entry.decidedBy ? { decidedBy: entry.decidedBy } : {},
4899
+ ...entry.comment ? { comment: entry.comment } : {},
4900
+ requestedAt: entry.requestedAt,
4901
+ decidedAt: entry.decidedAt,
4902
+ ...waitedMs(entry.requestedAt, entry.decidedAt) !== void 0 ? { waitedMs: waitedMs(entry.requestedAt, entry.decidedAt) } : {},
4903
+ ...edited ? { argumentsEdited: true } : {}
4904
+ }
4905
+ }
4906
+ ];
4907
+ if (logData) {
4908
+ sections.push({ kind: "tool_call", label: "Requested arguments", tool: entry.toolName, arguments: sanitizeTracePayload(entry.args) });
4909
+ if (edited) {
4910
+ sections.push({ kind: "tool_call", label: "Approved arguments", tool: entry.toolName, arguments: sanitizeTracePayload(entry.approvedArgs) });
4911
+ }
4912
+ }
4913
+ recordTraceEvent(session, {
4914
+ type: "tool_approval",
4915
+ label: `${approved ? "Approved" : "Rejected"} \xB7 ${entry.toolName}`,
4916
+ timestamp: entry.decidedAt,
4917
+ durationMs: waitedMs(entry.requestedAt, entry.decidedAt),
4918
+ status: "success",
4919
+ actor: { scope: "human", name: entry.decidedBy || "reviewer", role: "approver" },
4920
+ sections
4921
+ });
4922
+ entry.metadata = { ...entry.metadata || {}, [TRACED_KEY]: true };
4923
+ }
4924
+ for (const entry of state.pendingUserQuestions ?? []) {
4925
+ if (!entry.answeredAt || entry.metadata?.[TRACED_KEY]) continue;
4926
+ if (entry.status !== "answered" && entry.status !== "cancelled") continue;
4927
+ const clientTool = readClientTool(entry);
4928
+ if (clientTool) {
4929
+ recordClientToolResult(session, entry, clientTool, state.toolHistory);
4930
+ entry.metadata = { ...entry.metadata || {}, [TRACED_KEY]: true };
4931
+ continue;
4932
+ }
4933
+ const cancelled = entry.status === "cancelled";
4934
+ const data = {
4935
+ questionId: entry.id,
4936
+ toolCallId: entry.toolCallId,
4937
+ status: entry.status,
4938
+ ...entry.answeredBy ? { answeredBy: entry.answeredBy } : {},
4939
+ ...entry.notes ? { notes: entry.notes } : {},
4940
+ requestedAt: entry.requestedAt,
4941
+ answeredAt: entry.answeredAt,
4942
+ ...waitedMs(entry.requestedAt, entry.answeredAt) !== void 0 ? { waitedMs: waitedMs(entry.requestedAt, entry.answeredAt) } : {}
4943
+ };
4944
+ const sections = [{ kind: "metadata", label: "Answer", data }];
4945
+ if (logData) {
4946
+ sections.push({
4947
+ kind: "metadata",
4948
+ label: "Questions and answers",
4949
+ data: {
4950
+ items: (entry.questions ?? []).map((item) => ({
4951
+ question: item.question,
4952
+ ...item.header ? { header: item.header } : {},
4953
+ answer: sanitizeTracePayload(entry.answers?.[item.question] ?? (item.header ? entry.answers?.[item.header] : void 0) ?? null)
4954
+ }))
4955
+ }
4956
+ });
4957
+ }
4958
+ recordTraceEvent(session, {
4959
+ type: "user_question",
4960
+ label: cancelled ? "Question cancelled" : "Answer from user",
4961
+ timestamp: entry.answeredAt,
4962
+ durationMs: waitedMs(entry.requestedAt, entry.answeredAt),
4963
+ status: cancelled ? "skipped" : "success",
4964
+ actor: { scope: "human", name: entry.answeredBy || "user", role: "user" },
4965
+ sections
4966
+ });
4967
+ entry.metadata = { ...entry.metadata || {}, [TRACED_KEY]: true };
4968
+ }
4969
+ }
4970
+ function recordClientToolRequested(session, entry) {
4971
+ if (!session) return;
4972
+ const clientTool = readClientTool(entry);
4973
+ if (!clientTool) return;
4974
+ const sections = [
4975
+ {
4976
+ kind: "metadata",
4977
+ label: "Client tool",
4978
+ data: { questionId: entry.id, toolCallId: entry.toolCallId, status: "pending", requestedAt: entry.requestedAt }
4979
+ }
4980
+ ];
4981
+ if (session.resolvedConfig.logData) {
4982
+ sections.push({ kind: "tool_call", label: "Arguments", tool: clientTool.name, arguments: sanitizeTracePayload(clientTool.args) });
4983
+ }
4984
+ recordTraceEvent(session, {
4985
+ type: "tool_call",
4986
+ label: `Tool Execution - ${clientTool.name} (client)`,
4987
+ timestamp: entry.requestedAt,
4988
+ status: "pending",
4989
+ actor: { scope: "client", name: clientTool.name, role: "tool" },
4990
+ sections
4991
+ });
4992
+ }
4993
+ function readClientTool(entry) {
4994
+ const meta = entry.metadata?.clientTool;
4995
+ if (!meta || typeof meta !== "object" || typeof meta.name !== "string") return void 0;
4996
+ return { name: meta.name, args: meta.args };
4997
+ }
4998
+ function recordClientToolResult(session, entry, clientTool, toolHistory) {
4999
+ const step = [...toolHistory ?? []].reverse().find((item) => item?.tool_call_id === entry.toolCallId);
5000
+ const failed = step?.status === "error" || entry.status === "cancelled";
5001
+ const sections = [
5002
+ {
5003
+ kind: "metadata",
5004
+ label: "Client tool",
5005
+ data: {
5006
+ questionId: entry.id,
5007
+ toolCallId: entry.toolCallId,
5008
+ status: failed ? "error" : "answered",
5009
+ ...entry.answeredBy ? { answeredBy: entry.answeredBy } : {},
5010
+ requestedAt: entry.requestedAt,
5011
+ answeredAt: entry.answeredAt,
5012
+ ...waitedMs(entry.requestedAt, entry.answeredAt) !== void 0 ? { waitedMs: waitedMs(entry.requestedAt, entry.answeredAt) } : {}
5013
+ }
5014
+ }
5015
+ ];
5016
+ if (session.resolvedConfig.logData) {
5017
+ sections.push({ kind: "tool_call", label: "Arguments", tool: clientTool.name, arguments: sanitizeTracePayload(clientTool.args) });
5018
+ if (step) {
5019
+ const output = step.rawOutput ?? step.output;
5020
+ sections.push({
5021
+ kind: "tool_result",
5022
+ label: "Result from client",
5023
+ tool: clientTool.name,
5024
+ output: sanitizeTracePayload(output),
5025
+ summary: typeof step.output === "string" ? step.output.slice(0, 2e3) : void 0
5026
+ });
5027
+ }
5028
+ }
5029
+ const ranMs = typeof step?.durationMs === "number" ? step.durationMs : void 0;
5030
+ recordTraceEvent(session, {
5031
+ type: "tool_call",
5032
+ label: `Tool Execution - ${clientTool.name} (client)`,
5033
+ timestamp: entry.answeredAt,
5034
+ durationMs: waitedMs(entry.requestedAt, entry.answeredAt) ?? ranMs,
5035
+ status: failed ? "error" : "success",
5036
+ actor: { scope: "client", name: clientTool.name, role: "tool" },
5037
+ sections
5038
+ });
5039
+ }
5040
+
4785
5041
  // src/utils/traceSections.ts
4786
5042
  init_content();
4787
5043
  function truncate(text, max = 500) {
@@ -5095,10 +5351,10 @@ function createToolsNode(initialTools, opts) {
5095
5351
  const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
5096
5352
  const validatedArgs = validateToolArgs(tool, args);
5097
5353
  if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
5098
- const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
5099
- push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
5100
- markToolFailure(errorMessage, tc.name, toolCallId);
5101
- onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
5354
+ const errorMessage2 = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
5355
+ push({ role: "tool", content: errorMessage2, tool_call_id: toolCallId, name: tc.name });
5356
+ markToolFailure(errorMessage2, tc.name, toolCallId);
5357
+ onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage2 } });
5102
5358
  toolCount += 1;
5103
5359
  return { status: "error" };
5104
5360
  }
@@ -5250,6 +5506,7 @@ function createToolsNode(initialTools, opts) {
5250
5506
  };
5251
5507
  pendingApprovals.push(approvalEntry);
5252
5508
  pendingByCallId.set(toolCallId, approvalEntry);
5509
+ recordApprovalRequested(traceSession, approvalEntry);
5253
5510
  onEvent?.({ type: "tool_approval", status: "pending", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, args });
5254
5511
  if (host?.has("notification")) {
5255
5512
  await host.runObservers("notification", {
@@ -5513,6 +5770,7 @@ function createToolsNode(initialTools, opts) {
5513
5770
  if (!pendingApproval) {
5514
5771
  throw new Error(`Tool ${toolName} requested approval but no pending approval entry was recorded.`);
5515
5772
  }
5773
+ recordApprovalRequested(traceSession, pendingApproval);
5516
5774
  awaitingApproval = true;
5517
5775
  return {
5518
5776
  status: "awaiting_approval",
@@ -5529,6 +5787,7 @@ function createToolsNode(initialTools, opts) {
5529
5787
  if (!pendingQuestion) {
5530
5788
  throw new Error(`Tool ${toolName} requested a user question but no pending question entry was recorded.`);
5531
5789
  }
5790
+ recordClientToolRequested(traceSession, pendingQuestion);
5532
5791
  awaitingUserQuestion = true;
5533
5792
  if (host?.has("notification")) {
5534
5793
  await host.runObservers("notification", {
@@ -6109,7 +6368,7 @@ var LEVEL_DEFAULTS = {
6109
6368
  };
6110
6369
  var VALID_LEVELS = ["minimal", "low", "medium", "high"];
6111
6370
  var VALID_CADENCES = ["off", "every_turn", "after_tool", "on_branch", "initial_then_after_tool"];
6112
- var VALID_EFFORTS = ["none", "minimal", "low", "medium", "high"];
6371
+ var VALID_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
6113
6372
  function validateReasoningConfig(input) {
6114
6373
  if (!input) return;
6115
6374
  if (input.level !== void 0 && !VALID_LEVELS.includes(input.level)) {
@@ -6625,16 +6884,20 @@ function createAskUserQuestionTool(stateRef, config) {
6625
6884
  });
6626
6885
  recordTraceEvent(ctx.__traceSession, {
6627
6886
  type: "user_question",
6628
- label: "Ask User",
6887
+ label: "Question to user",
6888
+ timestamp: entry.requestedAt,
6629
6889
  actor: { scope: "agent", name: "ask_user_question", role: "user_interaction" },
6630
- status: "success",
6890
+ status: "pending",
6631
6891
  sections: [
6632
6892
  {
6633
6893
  kind: "metadata",
6634
6894
  label: "Pending Question",
6635
6895
  data: {
6636
6896
  id: entry.id,
6897
+ questionId: entry.id,
6637
6898
  toolCallId: entry.toolCallId,
6899
+ status: "pending",
6900
+ requestedAt: entry.requestedAt,
6638
6901
  questions: entry.questions,
6639
6902
  allowFreeText
6640
6903
  }
@@ -7853,15 +8116,17 @@ function createAgent(opts) {
7853
8116
  }
7854
8117
  const traceSession = state.ctx?.__traceSession;
7855
8118
  if (traceSession) {
8119
+ const iterationNumber = (traceSession.summary.eventCounts.agent_iteration ?? 0) + 1;
7856
8120
  const iterEvent = recordTraceEvent(traceSession, {
7857
8121
  type: "agent_iteration",
7858
- label: `Iteration ${iterations}`,
8122
+ label: `Iteration ${iterationNumber}`,
7859
8123
  actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
7860
8124
  parentSpanId: traceSession.rootSpanId
7861
8125
  });
7862
8126
  if (iterEvent) {
7863
8127
  traceSession.currentIterationSpanId = iterEvent.spanId;
7864
8128
  }
8129
+ recordHumanDecisions(traceSession, state);
7865
8130
  }
7866
8131
  if (cancelIfRequested("loop")) break;
7867
8132
  const skippingAgent = resumeStage === "tools";
@@ -8142,7 +8407,7 @@ function createAgent(opts) {
8142
8407
  }
8143
8408
  emit({ type: "progress", ...progress });
8144
8409
  };
8145
- const traceSession = createTraceSession(opts);
8410
+ const traceSession = createTraceSession(opts, readTraceContinuation(input.ctx?.__traceContinuation));
8146
8411
  const ctx = {
8147
8412
  ...input.ctx || {},
8148
8413
  __onEvent: onEvent,
@@ -8150,6 +8415,7 @@ function createAgent(opts) {
8150
8415
  __onStream: onStream,
8151
8416
  __streaming: streamEnabled
8152
8417
  };
8418
+ delete ctx.__traceContinuation;
8153
8419
  if (traceSession) ctx.__traceSession = traceSession;
8154
8420
  delete ctx.__plugins;
8155
8421
  delete ctx.__applySystemPromptContribution;
@@ -8298,10 +8564,17 @@ function createAgent(opts) {
8298
8564
  }
8299
8565
  }
8300
8566
  }
8567
+ const pausedForHuman = statusFor(res) === "paused";
8568
+ const midTurn = pausedForHuman || Boolean(res.ctx?.__needsSummarization);
8301
8569
  await finalizeTraceSession(traceSession, {
8302
8570
  agentRuntime: res.agent || runtime,
8303
- status: "success"
8571
+ status: pausedForHuman ? "paused" : midTurn ? "in_progress" : "success"
8304
8572
  });
8573
+ if (traceSession && midTurn) {
8574
+ res.ctx = { ...res.ctx || {}, __traceContinuation: traceContinuationOf(traceSession) };
8575
+ } else if (res.ctx && "__traceContinuation" in res.ctx) {
8576
+ delete res.ctx.__traceContinuation;
8577
+ }
8305
8578
  const isTransientSummarizationExit = !!res.ctx?.__needsSummarization;
8306
8579
  const finalAssistantMsg = isTransientSummarizationExit ? void 0 : getLastAssistantMessage(res.messages);
8307
8580
  const finalIsSyntheticSummary = !isTransientSummarizationExit && finalAssistantMsg && isSyntheticSummaryMessage(finalAssistantMsg);
@@ -11205,6 +11478,14 @@ ${pluginAppend}`;
11205
11478
  } else if (lastResult.state) {
11206
11479
  lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, messages: rawMessages } };
11207
11480
  }
11481
+ if (lastResult.state) {
11482
+ const endCtx = lastResult.state.ctx;
11483
+ if (endCtx?.__traceContinuation && !(endCtx.__awaitingApproval || endCtx.__awaitingUserQuestion || endCtx.__paused)) {
11484
+ const strippedCtx = { ...endCtx };
11485
+ delete strippedCtx.__traceContinuation;
11486
+ lastResult = { ...lastResult, state: { ...lastResult.state, ctx: strippedCtx } };
11487
+ }
11488
+ }
11208
11489
  if (skillsEnabled && lastResult.state) {
11209
11490
  const persisted = skillStateFromRegistry(toolSet.skillRegistryRef);
11210
11491
  if (persisted) {
@@ -11579,6 +11860,7 @@ var BaseProvider = class {
11579
11860
  /** Applies the transport options shared by every provider config. */
11580
11861
  configureTransport(config) {
11581
11862
  this.fetchImpl = config.fetch;
11863
+ this.onModelRuleLearned = config.onModelRuleLearned;
11582
11864
  this.timeoutMs = typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : void 0;
11583
11865
  }
11584
11866
  /**
@@ -15246,32 +15528,805 @@ function languageGuard(config) {
15246
15528
  // src/index.ts
15247
15529
  init_content();
15248
15530
 
15531
+ // src/models/model-rules.json
15532
+ var model_rules_default = {
15533
+ version: 1,
15534
+ effortLadder: [
15535
+ "none",
15536
+ "minimal",
15537
+ "low",
15538
+ "medium",
15539
+ "high",
15540
+ "xhigh",
15541
+ "max"
15542
+ ],
15543
+ budgets: {
15544
+ minimal: 1024,
15545
+ low: 2048,
15546
+ medium: 8192,
15547
+ high: 16384,
15548
+ xhigh: 24576,
15549
+ max: 32768
15550
+ },
15551
+ geminiBudgets: {
15552
+ minimal: 512,
15553
+ low: 1024,
15554
+ medium: 4096,
15555
+ high: 12e3,
15556
+ xhigh: 24576,
15557
+ max: 24576
15558
+ },
15559
+ rules: [
15560
+ {
15561
+ id: "openai-chat-models",
15562
+ family: "openai",
15563
+ match: "^(?:chatgpt|gpt-[\\d.]+[a-z0-9.-]*?-chat)",
15564
+ reasoning: {
15565
+ style: "none",
15566
+ efforts: [],
15567
+ off: null
15568
+ },
15569
+ sampling: {
15570
+ temperature: "any",
15571
+ topP: true,
15572
+ topK: true,
15573
+ penalties: true,
15574
+ logprobs: true
15575
+ },
15576
+ maxTokensParam: "max_completion_tokens",
15577
+ chatTools: {
15578
+ reasoning: "any"
15579
+ },
15580
+ source: "docs",
15581
+ note: "Chat-tuned *-chat variants are not reasoning models: ordinary sampling, no reasoning field."
15582
+ },
15583
+ {
15584
+ id: "openai-gpt-6-plus",
15585
+ family: "openai",
15586
+ match: "^gpt-?(?:[6-9]|(?!35(?!\\d))[1-9]\\d+)(?:[.-]|$)",
15587
+ reasoning: {
15588
+ style: "effort",
15589
+ efforts: [
15590
+ "none",
15591
+ "low",
15592
+ "medium",
15593
+ "high",
15594
+ "xhigh"
15595
+ ],
15596
+ off: "none",
15597
+ responsesEfforts: [
15598
+ "none",
15599
+ "low",
15600
+ "medium",
15601
+ "high",
15602
+ "xhigh",
15603
+ "max"
15604
+ ]
15605
+ },
15606
+ sampling: {
15607
+ temperature: "default-only",
15608
+ topP: false,
15609
+ topK: false,
15610
+ penalties: false,
15611
+ logprobs: false
15612
+ },
15613
+ maxTokensParam: "max_completion_tokens",
15614
+ chatTools: {
15615
+ reasoning: "force-none"
15616
+ },
15617
+ source: "verified-2026-10-14",
15618
+ note: "gpt-N, N>=6. Chat Completions takes none/low/medium/high/xhigh (minimal and max are 400s); Responses also takes max. Function tools with an effort other than none are a 400 on Chat Completions and the default effort is not none, so none is sent explicitly. Rejects max_tokens, temperature != 1, top_p, penalties, logprobs."
15619
+ },
15620
+ {
15621
+ id: "openai-gpt-5x",
15622
+ family: "openai",
15623
+ match: "^gpt-?5\\.[1-9]",
15624
+ reasoning: {
15625
+ style: "effort",
15626
+ efforts: [
15627
+ "none",
15628
+ "low",
15629
+ "medium",
15630
+ "high",
15631
+ "xhigh"
15632
+ ],
15633
+ off: "none",
15634
+ responsesEfforts: [
15635
+ "none",
15636
+ "low",
15637
+ "medium",
15638
+ "high",
15639
+ "xhigh",
15640
+ "max"
15641
+ ]
15642
+ },
15643
+ sampling: {
15644
+ temperature: "default-only",
15645
+ topP: false,
15646
+ topK: false,
15647
+ penalties: false,
15648
+ logprobs: false
15649
+ },
15650
+ maxTokensParam: "max_completion_tokens",
15651
+ chatTools: {
15652
+ reasoning: "force-none"
15653
+ },
15654
+ source: "verified-2026-10-14",
15655
+ note: "gpt-5.1 and newer 5.x. Same wire rules as gpt-6: no minimal/max on Chat Completions (Responses takes max), function tools need reasoning_effort none on Chat Completions."
15656
+ },
15657
+ {
15658
+ id: "openai-gpt-5-base",
15659
+ family: "openai",
15660
+ match: "^gpt-?5(?![.\\d])",
15661
+ reasoning: {
15662
+ style: "effort",
15663
+ efforts: [
15664
+ "minimal",
15665
+ "low",
15666
+ "medium",
15667
+ "high"
15668
+ ],
15669
+ off: null
15670
+ },
15671
+ sampling: {
15672
+ temperature: "default-only",
15673
+ topP: false,
15674
+ topK: false,
15675
+ penalties: false,
15676
+ logprobs: false
15677
+ },
15678
+ maxTokensParam: "max_completion_tokens",
15679
+ chatTools: {
15680
+ reasoning: "any"
15681
+ },
15682
+ source: "docs",
15683
+ note: "gpt-5, -mini, -nano, -codex: minimal/low/medium/high, reasoning cannot be turned off (none maps to minimal)."
15684
+ },
15685
+ {
15686
+ id: "openai-o-series",
15687
+ family: "openai",
15688
+ match: "^o[1-9]\\d?(?![a-z0-9])",
15689
+ reasoning: {
15690
+ style: "effort",
15691
+ efforts: [
15692
+ "low",
15693
+ "medium",
15694
+ "high"
15695
+ ],
15696
+ off: null
15697
+ },
15698
+ sampling: {
15699
+ temperature: "default-only",
15700
+ topP: false,
15701
+ topK: false,
15702
+ penalties: false,
15703
+ logprobs: false
15704
+ },
15705
+ maxTokensParam: "max_completion_tokens",
15706
+ chatTools: {
15707
+ reasoning: "any"
15708
+ },
15709
+ source: "docs",
15710
+ note: "o1/o3/o4 family: low/medium/high, no sampling overrides."
15711
+ },
15712
+ {
15713
+ id: "openai-gpt-oss",
15714
+ family: "openai",
15715
+ match: "^gpt-?oss",
15716
+ reasoning: {
15717
+ style: "effort",
15718
+ efforts: [
15719
+ "low",
15720
+ "medium",
15721
+ "high"
15722
+ ],
15723
+ off: null
15724
+ },
15725
+ sampling: {
15726
+ temperature: "any",
15727
+ topP: true,
15728
+ topK: true,
15729
+ penalties: true,
15730
+ logprobs: true
15731
+ },
15732
+ maxTokensParam: "max_tokens",
15733
+ chatTools: {
15734
+ reasoning: "any"
15735
+ },
15736
+ source: "docs",
15737
+ note: "Open-weight gpt-oss: low/medium/high; sampling and max_tokens behave like an ordinary chat model."
15738
+ },
15739
+ {
15740
+ id: "openai-legacy-gpt",
15741
+ family: "openai",
15742
+ match: "^(?:gpt-?(?:[0-4]|35)(?![0-9])|text-|davinci|babbage)",
15743
+ reasoning: {
15744
+ style: "none",
15745
+ efforts: [],
15746
+ off: null
15747
+ },
15748
+ sampling: {
15749
+ temperature: "any",
15750
+ topP: true,
15751
+ topK: true,
15752
+ penalties: true,
15753
+ logprobs: true
15754
+ },
15755
+ maxTokensParam: "max_tokens",
15756
+ chatTools: {
15757
+ reasoning: "any"
15758
+ },
15759
+ source: "docs",
15760
+ note: "gpt-4o, gpt-4.1, gpt-3.5 and older: no reasoning, ordinary sampling."
15761
+ },
15762
+ {
15763
+ id: "openai-compatible-toggle",
15764
+ family: "openai",
15765
+ match: "^(?:deepseek-(?:r\\d|reasoner)|qwq|qwen-?3)",
15766
+ reasoning: {
15767
+ style: "toggle",
15768
+ efforts: [
15769
+ "none",
15770
+ "minimal",
15771
+ "low",
15772
+ "medium",
15773
+ "high",
15774
+ "xhigh",
15775
+ "max"
15776
+ ],
15777
+ off: "none"
15778
+ },
15779
+ sampling: {
15780
+ temperature: "any",
15781
+ topP: true,
15782
+ topK: true,
15783
+ penalties: true,
15784
+ logprobs: true
15785
+ },
15786
+ maxTokensParam: "max_tokens",
15787
+ chatTools: {
15788
+ reasoning: "any"
15789
+ },
15790
+ source: "docs",
15791
+ note: "DeepSeek R/reasoner, QwQ, Qwen3: on/off style hosts. The effort is forwarded as-is (including none, which switches thinking off on thinking-by-default models)."
15792
+ },
15793
+ {
15794
+ id: "openai-default",
15795
+ family: "openai",
15796
+ match: ".*",
15797
+ reasoning: {
15798
+ style: "effort",
15799
+ efforts: [
15800
+ "none",
15801
+ "minimal",
15802
+ "low",
15803
+ "medium",
15804
+ "high",
15805
+ "xhigh",
15806
+ "max"
15807
+ ],
15808
+ off: "none"
15809
+ },
15810
+ sampling: {
15811
+ temperature: "any",
15812
+ topP: true,
15813
+ topK: true,
15814
+ penalties: true,
15815
+ logprobs: true
15816
+ },
15817
+ maxTokensParam: "max_tokens",
15818
+ chatTools: {
15819
+ reasoning: "any"
15820
+ },
15821
+ source: "docs",
15822
+ note: "Permissive default for unknown OpenAI-wire models: pass what the caller asked; the provider learns from 400s."
15823
+ },
15824
+ {
15825
+ id: "claude-haiku-5",
15826
+ family: "anthropic",
15827
+ match: "^claude-(?:haiku-5|5(?:-\\d+)?-haiku)(?!\\d)",
15828
+ reasoning: {
15829
+ style: "adaptive",
15830
+ efforts: [
15831
+ "low",
15832
+ "medium",
15833
+ "high",
15834
+ "xhigh",
15835
+ "max"
15836
+ ],
15837
+ off: "disabled"
15838
+ },
15839
+ sampling: {
15840
+ temperature: "unsupported",
15841
+ topP: false,
15842
+ topK: false,
15843
+ penalties: false,
15844
+ logprobs: false
15845
+ },
15846
+ maxTokensParam: "max_tokens",
15847
+ chatTools: {
15848
+ reasoning: "any"
15849
+ },
15850
+ source: "verified-2026-10-14",
15851
+ note: "Claude Haiku 5.x: thinking:{type:adaptive} + output_config.effort; thinking:{type:enabled,budget_tokens} is rejected. Off = thinking:{type:disabled}. Sampling params are rejected."
15852
+ },
15853
+ {
15854
+ id: "claude-sonnet-5",
15855
+ family: "anthropic",
15856
+ match: "^claude-(?:sonnet-5|5(?:-\\d+)?-sonnet)(?!\\d)",
15857
+ reasoning: {
15858
+ style: "adaptive",
15859
+ efforts: [
15860
+ "low",
15861
+ "medium",
15862
+ "high",
15863
+ "xhigh",
15864
+ "max"
15865
+ ],
15866
+ off: "between_tools"
15867
+ },
15868
+ sampling: {
15869
+ temperature: "unsupported",
15870
+ topP: false,
15871
+ topK: false,
15872
+ penalties: false,
15873
+ logprobs: false
15874
+ },
15875
+ maxTokensParam: "max_tokens",
15876
+ chatTools: {
15877
+ reasoning: "any"
15878
+ },
15879
+ source: "verified-2026-10-14",
15880
+ note: "Claude Sonnet 5.x: adaptive + output_config.effort. thinking:{type:disabled} is rejected; off = thinking:{type:between_tools}. Sampling params are rejected."
15881
+ },
15882
+ {
15883
+ id: "claude-opus-5",
15884
+ family: "anthropic",
15885
+ match: "^claude-(?:opus-5|5(?:-\\d+)?-opus)(?!\\d)",
15886
+ reasoning: {
15887
+ style: "adaptive",
15888
+ efforts: [
15889
+ "low",
15890
+ "medium",
15891
+ "high",
15892
+ "xhigh",
15893
+ "max"
15894
+ ],
15895
+ off: null
15896
+ },
15897
+ sampling: {
15898
+ temperature: "unsupported",
15899
+ topP: false,
15900
+ topK: false,
15901
+ penalties: false,
15902
+ logprobs: false
15903
+ },
15904
+ maxTokensParam: "max_tokens",
15905
+ chatTools: {
15906
+ reasoning: "any"
15907
+ },
15908
+ source: "verified-2026-10-14",
15909
+ note: "Claude Opus 5.x: adaptive + output_config.effort; thinking cannot be turned off (none maps to the lowest effort). Sampling params are rejected."
15910
+ },
15911
+ {
15912
+ id: "claude-fable",
15913
+ family: "anthropic",
15914
+ match: "^claude-fable",
15915
+ reasoning: {
15916
+ style: "adaptive",
15917
+ efforts: [
15918
+ "low",
15919
+ "medium",
15920
+ "high",
15921
+ "xhigh",
15922
+ "max"
15923
+ ],
15924
+ off: null
15925
+ },
15926
+ sampling: {
15927
+ temperature: "unsupported",
15928
+ topP: false,
15929
+ topK: false,
15930
+ penalties: false,
15931
+ logprobs: false
15932
+ },
15933
+ maxTokensParam: "max_tokens",
15934
+ chatTools: {
15935
+ reasoning: "any"
15936
+ },
15937
+ source: "docs",
15938
+ note: "Claude Fable line: treated like the 5.x adaptive models."
15939
+ },
15940
+ {
15941
+ id: "claude-4-7",
15942
+ family: "anthropic",
15943
+ match: "^claude-(?:(?:opus|sonnet|haiku)-4[-.]7|4[-.]7-(?:opus|sonnet|haiku))(?!\\d)",
15944
+ reasoning: {
15945
+ style: "adaptive",
15946
+ efforts: [
15947
+ "low",
15948
+ "medium",
15949
+ "high",
15950
+ "xhigh",
15951
+ "max"
15952
+ ],
15953
+ off: "disabled"
15954
+ },
15955
+ sampling: {
15956
+ temperature: "thinking-requires-1",
15957
+ topP: true,
15958
+ topK: true,
15959
+ penalties: true,
15960
+ logprobs: true
15961
+ },
15962
+ maxTokensParam: "max_tokens",
15963
+ chatTools: {
15964
+ reasoning: "any"
15965
+ },
15966
+ source: "docs",
15967
+ note: "Claude 4.7: adaptive thinking, low/medium/high/xhigh/max."
15968
+ },
15969
+ {
15970
+ id: "claude-4-6",
15971
+ family: "anthropic",
15972
+ match: "^claude-(?:(?:opus|sonnet|haiku)-4[-.]6|4[-.]6-(?:opus|sonnet|haiku))(?!\\d)",
15973
+ reasoning: {
15974
+ style: "adaptive",
15975
+ efforts: [
15976
+ "low",
15977
+ "medium",
15978
+ "high",
15979
+ "max"
15980
+ ],
15981
+ off: "disabled"
15982
+ },
15983
+ sampling: {
15984
+ temperature: "thinking-requires-1",
15985
+ topP: true,
15986
+ topK: true,
15987
+ penalties: true,
15988
+ logprobs: true
15989
+ },
15990
+ maxTokensParam: "max_tokens",
15991
+ chatTools: {
15992
+ reasoning: "any"
15993
+ },
15994
+ source: "docs",
15995
+ note: "Claude 4.6: adaptive thinking, low/medium/high/max (no xhigh)."
15996
+ },
15997
+ {
15998
+ id: "claude-legacy-budget",
15999
+ family: "anthropic",
16000
+ match: "^claude-(?:3|4-|(?:opus|sonnet|haiku)-4)",
16001
+ reasoning: {
16002
+ style: "budget",
16003
+ efforts: [
16004
+ "none",
16005
+ "minimal",
16006
+ "low",
16007
+ "medium",
16008
+ "high",
16009
+ "xhigh",
16010
+ "max"
16011
+ ],
16012
+ off: null
16013
+ },
16014
+ sampling: {
16015
+ temperature: "thinking-requires-1",
16016
+ topP: true,
16017
+ topK: true,
16018
+ penalties: true,
16019
+ logprobs: true
16020
+ },
16021
+ maxTokensParam: "max_tokens",
16022
+ chatTools: {
16023
+ reasoning: "any"
16024
+ },
16025
+ source: "docs",
16026
+ note: "Claude 3.7 and 4.0-4.5: thinking:{type:enabled,budget_tokens}; every effort maps to a budget; sampling overrides are not allowed while thinking."
16027
+ },
16028
+ {
16029
+ id: "anthropic-default",
16030
+ family: "anthropic",
16031
+ match: ".*",
16032
+ reasoning: {
16033
+ style: "budget",
16034
+ efforts: [
16035
+ "none",
16036
+ "minimal",
16037
+ "low",
16038
+ "medium",
16039
+ "high",
16040
+ "xhigh",
16041
+ "max"
16042
+ ],
16043
+ off: null
16044
+ },
16045
+ sampling: {
16046
+ temperature: "thinking-requires-1",
16047
+ topP: true,
16048
+ topK: true,
16049
+ penalties: true,
16050
+ logprobs: true
16051
+ },
16052
+ maxTokensParam: "max_tokens",
16053
+ chatTools: {
16054
+ reasoning: "any"
16055
+ },
16056
+ source: "docs",
16057
+ note: "Permissive default for unknown Anthropic-wire models: budget style (the long-standing form); the provider flips the thinking form when the API says so."
16058
+ },
16059
+ {
16060
+ id: "gemini-budget",
16061
+ family: "gemini",
16062
+ match: "^gemini",
16063
+ reasoning: {
16064
+ style: "budget",
16065
+ efforts: [
16066
+ "none",
16067
+ "minimal",
16068
+ "low",
16069
+ "medium",
16070
+ "high",
16071
+ "xhigh",
16072
+ "max"
16073
+ ],
16074
+ off: "none"
16075
+ },
16076
+ sampling: {
16077
+ temperature: "any",
16078
+ topP: true,
16079
+ topK: true,
16080
+ penalties: true,
16081
+ logprobs: true
16082
+ },
16083
+ maxTokensParam: "max_tokens",
16084
+ chatTools: {
16085
+ reasoning: "any"
16086
+ },
16087
+ source: "docs",
16088
+ note: "Gemini 2.5-style thinkingBudget; effort maps to a token budget, none = budget 0."
16089
+ },
16090
+ {
16091
+ id: "gemini-default",
16092
+ family: "gemini",
16093
+ match: ".*",
16094
+ reasoning: {
16095
+ style: "budget",
16096
+ efforts: [
16097
+ "none",
16098
+ "minimal",
16099
+ "low",
16100
+ "medium",
16101
+ "high",
16102
+ "xhigh",
16103
+ "max"
16104
+ ],
16105
+ off: "none"
16106
+ },
16107
+ sampling: {
16108
+ temperature: "any",
16109
+ topP: true,
16110
+ topK: true,
16111
+ penalties: true,
16112
+ logprobs: true
16113
+ },
16114
+ maxTokensParam: "max_tokens",
16115
+ chatTools: {
16116
+ reasoning: "any"
16117
+ },
16118
+ source: "docs",
16119
+ note: "Permissive default for unknown Gemini-wire models."
16120
+ },
16121
+ {
16122
+ id: "other-default",
16123
+ family: "other",
16124
+ match: ".*",
16125
+ reasoning: {
16126
+ style: "effort",
16127
+ efforts: [
16128
+ "none",
16129
+ "minimal",
16130
+ "low",
16131
+ "medium",
16132
+ "high",
16133
+ "xhigh",
16134
+ "max"
16135
+ ],
16136
+ off: "none"
16137
+ },
16138
+ sampling: {
16139
+ temperature: "any",
16140
+ topP: true,
16141
+ topK: true,
16142
+ penalties: true,
16143
+ logprobs: true
16144
+ },
16145
+ maxTokensParam: "max_tokens",
16146
+ chatTools: {
16147
+ reasoning: "any"
16148
+ },
16149
+ source: "docs",
16150
+ note: "Permissive default for model families the SDK has no rules for."
16151
+ }
16152
+ ]
16153
+ };
16154
+
16155
+ // src/models/rules.ts
16156
+ var MODEL_RULES = model_rules_default;
16157
+ var LADDER = MODEL_RULES.effortLadder;
16158
+ var compiled = /* @__PURE__ */ new Map();
16159
+ function regexFor(rule) {
16160
+ let re = compiled.get(rule.id);
16161
+ if (!re) {
16162
+ re = new RegExp(rule.match, "i");
16163
+ compiled.set(rule.id, re);
16164
+ }
16165
+ return re;
16166
+ }
16167
+ var GATEWAY_PREFIX = /^(?:(?:global|us|eu|apac|jp|au|ca|us-gov)\.)?(?:(?:openai|anthropic|google|meta|amazon|deepseek|qwen|mistral|cohere|ai21)\.)?/;
16168
+ function modelIdCandidates(model) {
16169
+ if (!model) return [];
16170
+ const lower = String(model).trim().toLowerCase();
16171
+ const last = lower.split("/").pop() ?? lower;
16172
+ const out = [];
16173
+ for (const id of [lower, last]) {
16174
+ for (const c of [id, id.replace(GATEWAY_PREFIX, "")]) {
16175
+ if (c && !out.includes(c)) out.push(c);
16176
+ }
16177
+ }
16178
+ return out;
16179
+ }
16180
+ var OPENAI_WIRE = /* @__PURE__ */ new Set(["openai", "azure", "openai-compatible", "together", "openrouter", "groq", "fireworks", "ollama", "vllm", "litellm", "mistral", "deepseek", "xai", "jev"]);
16181
+ function familiesFor(provider) {
16182
+ const p = (provider ?? "").toLowerCase();
16183
+ if (OPENAI_WIRE.has(p)) return ["openai"];
16184
+ if (p === "anthropic") return ["anthropic"];
16185
+ if (p === "bedrock") return ["anthropic", "openai", "other"];
16186
+ if (p === "vertex" || p === "gemini" || p === "google") return ["gemini", "anthropic"];
16187
+ return ["openai", "anthropic", "gemini", "other"];
16188
+ }
16189
+ function matchRule(family, ids, allowDefault) {
16190
+ for (const rule of MODEL_RULES.rules) {
16191
+ if (rule.family !== family) continue;
16192
+ if (rule.match === ".*") {
16193
+ if (allowDefault) return rule;
16194
+ continue;
16195
+ }
16196
+ const re = regexFor(rule);
16197
+ if (ids.some((id) => re.test(id))) return rule;
16198
+ }
16199
+ return void 0;
16200
+ }
16201
+ function mergeRulePatch(base, patch) {
16202
+ if (!patch) return base;
16203
+ const out = { ...base };
16204
+ if (patch.reasoning) out.reasoning = { ...base.reasoning ?? {}, ...patch.reasoning };
16205
+ if (patch.sampling) out.sampling = { ...base.sampling ?? {}, ...patch.sampling };
16206
+ if (patch.chatTools) out.chatTools = { ...base.chatTools ?? {}, ...patch.chatTools };
16207
+ if (patch.maxTokensParam) out.maxTokensParam = patch.maxTokensParam;
16208
+ if (patch.dropParams) out.dropParams = [.../* @__PURE__ */ new Set([...base.dropParams ?? [], ...patch.dropParams])];
16209
+ return out;
16210
+ }
16211
+ var LEARNED_MAX = 500;
16212
+ var learned = /* @__PURE__ */ new Map();
16213
+ function learnedKey(provider, model, scope) {
16214
+ return `${(provider ?? "").toLowerCase()}@${scope ?? ""}::${(model ?? "").trim().toLowerCase()}`;
16215
+ }
16216
+ function rememberModelRulePatch(provider, model, patch, scope) {
16217
+ const key = learnedKey(provider, model, scope);
16218
+ const merged = mergeRulePatch(learned.get(key)?.patch ?? {}, patch);
16219
+ learned.delete(key);
16220
+ learned.set(key, { provider: provider.toLowerCase(), model: model.trim().toLowerCase(), scope, patch: merged });
16221
+ if (learned.size > LEARNED_MAX) learned.delete(learned.keys().next().value);
16222
+ }
16223
+ function getLearnedModelRulePatch(provider, model, scope) {
16224
+ return learned.get(learnedKey(provider, model, scope))?.patch;
16225
+ }
16226
+ function listLearnedModelRules() {
16227
+ return [...learned.values()].map((e) => ({ ...e }));
16228
+ }
16229
+ function clearLearnedModelRules() {
16230
+ learned.clear();
16231
+ }
16232
+ function resolveModelRule(opts) {
16233
+ const families = familiesFor(opts.provider);
16234
+ const ids = modelIdCandidates(opts.model);
16235
+ let rule;
16236
+ for (const family of families) {
16237
+ rule = matchRule(family, ids, false);
16238
+ if (rule) break;
16239
+ }
16240
+ let isDefault = false;
16241
+ if (!rule) {
16242
+ isDefault = true;
16243
+ rule = matchRule(families[0], ids, true) ?? MODEL_RULES.rules[MODEL_RULES.rules.length - 1];
16244
+ }
16245
+ let effective = rule;
16246
+ const known = opts.model ? learned.get(learnedKey(opts.provider, opts.model, opts.scope))?.patch : void 0;
16247
+ if (known) effective = mergeRulePatch(effective, known);
16248
+ if (opts.patch) effective = mergeRulePatch(effective, opts.patch);
16249
+ return { ...effective, matchedRuleId: rule.id, isDefault, learned: !!known };
16250
+ }
16251
+ function mapReasoningEffort(rule, effort, endpoint = "chat") {
16252
+ const efforts = endpoint === "responses" && rule.reasoning.responsesEfforts || rule.reasoning.efforts;
16253
+ if (rule.reasoning.style === "none" || efforts.length === 0) return { mapped: true };
16254
+ const supported = LADDER.filter((e) => efforts.includes(e));
16255
+ if (supported.includes(effort)) return { effort, mapped: false };
16256
+ if (effort === "none") {
16257
+ const off = rule.reasoning.off;
16258
+ if (off && off !== "none") return { off, mapped: true };
16259
+ const lowest = supported.find((e) => e !== "none");
16260
+ return lowest ? { effort: lowest, mapped: true } : { mapped: true };
16261
+ }
16262
+ const at = LADDER.indexOf(effort);
16263
+ if (at < 0) return { mapped: true };
16264
+ for (let i = at - 1; i >= 1; i--) if (supported.includes(LADDER[i])) return { effort: LADDER[i], mapped: true };
16265
+ for (let i = at + 1; i < LADDER.length; i++) if (supported.includes(LADDER[i])) return { effort: LADDER[i], mapped: true };
16266
+ return supported.includes("none") ? { effort: "none", mapped: true } : { mapped: true };
16267
+ }
16268
+ function allowsSampling(rule, field, value, thinkingOn = false) {
16269
+ const s = rule.sampling;
16270
+ switch (field) {
16271
+ case "temperature":
16272
+ switch (s.temperature) {
16273
+ case "any":
16274
+ return true;
16275
+ case "unsupported":
16276
+ return false;
16277
+ // The default is what the server applies anyway, so it is never sent.
16278
+ case "default-only":
16279
+ return false;
16280
+ case "thinking-requires-1":
16281
+ return !thinkingOn || value === 1;
16282
+ }
16283
+ return true;
16284
+ // Anthropic's thinking restriction covers top_p / top_k as well as temperature.
16285
+ case "topP":
16286
+ return s.topP && !(thinkingOn && s.temperature === "thinking-requires-1");
16287
+ case "topK":
16288
+ return s.topK && !(thinkingOn && s.temperature === "thinking-requires-1");
16289
+ case "presencePenalty":
16290
+ case "frequencyPenalty":
16291
+ return s.penalties;
16292
+ case "logprobs":
16293
+ return s.logprobs;
16294
+ }
16295
+ }
16296
+ function applyDropParams(body, rule) {
16297
+ for (const p of rule.dropParams ?? []) {
16298
+ const dot = p.indexOf(".");
16299
+ if (dot < 0) delete body[p];
16300
+ else if (body[p.slice(0, dot)] && typeof body[p.slice(0, dot)] === "object") delete body[p.slice(0, dot)][p.slice(dot + 1)];
16301
+ }
16302
+ }
16303
+ function effortBudget(effort, table = "anthropic") {
16304
+ if (effort === "none") return 0;
16305
+ return (table === "gemini" ? MODEL_RULES.geminiBudgets : MODEL_RULES.budgets)[effort] ?? 0;
16306
+ }
16307
+
15249
16308
  // src/providers/utils/reasoning.ts
15250
16309
  var ANTHROPIC_EFFORT_BUDGET = {
15251
16310
  // 0 = off. `applyAnthropicReasoning` bails on a falsy budget and emits no
15252
16311
  // thinking block, which on Anthropic IS off — thinking there is opt-in.
15253
16312
  none: 0,
15254
- minimal: 1024,
15255
- low: 2048,
15256
- medium: 8192,
15257
- high: 16384
16313
+ ...MODEL_RULES.budgets
15258
16314
  };
15259
16315
  var GEMINI_EFFORT_BUDGET = {
15260
16316
  // 0 is Gemini's own documented spelling for "do not think", and unlike
15261
16317
  // Anthropic it has to be SENT — hence `applyGeminiReasoning` testing for null
15262
16318
  // rather than falsiness.
15263
16319
  none: 0,
15264
- minimal: 512,
15265
- low: 1024,
15266
- medium: 4096,
15267
- high: 12e3
16320
+ ...MODEL_RULES.geminiBudgets
15268
16321
  };
15269
16322
  var THINKING_OUTPUT_RESERVE = 1024;
15270
16323
  var MIN_THINKING_BUDGET = 1024;
15271
16324
  var THINKING_MIN_MAX_TOKENS = 4096;
15272
- function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort") {
16325
+ function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort", model, rule) {
15273
16326
  if (!reasoning) return;
15274
- const effort = normalizeEffort(reasoning.effort);
16327
+ const requested = normalizeEffort(reasoning.effort);
16328
+ const r = rule ?? resolveModelRule({ provider: "openai", model: model ?? body.model });
16329
+ const effort = requested && mapReasoningEffort(r, requested, mode === "responses" ? "responses" : "chat").effort;
15275
16330
  if (mode === "responses") {
15276
16331
  const obj = {};
15277
16332
  if (effort) obj.effort = effort;
@@ -15285,48 +16340,54 @@ function applyOpenAIReasoning(body, reasoning, mode = "legacy_effort") {
15285
16340
  }
15286
16341
  function supportsAdaptiveThinking(model) {
15287
16342
  if (typeof model !== "string") return false;
15288
- const id = model.toLowerCase();
15289
- const at = id.indexOf("claude-");
15290
- if (at < 0) return false;
15291
- for (const tok of id.slice(at + 7).split("-")) {
15292
- if (tok === "fable") return true;
15293
- const m = /^(\d+)(?:@|$)/.exec(tok);
15294
- if (m) {
15295
- if (m[1].length <= 2) return Number(m[1]) >= 5;
15296
- return false;
15297
- }
15298
- }
15299
- return false;
16343
+ return resolveModelRule({ provider: "anthropic", model }).reasoning.style === "adaptive";
15300
16344
  }
15301
- function resolveThinkingForm(model, mode) {
16345
+ function resolveThinkingForm(model, mode, rule) {
15302
16346
  if (mode === "adaptive" || mode === "budget") return mode;
16347
+ if (rule) return rule.reasoning.style === "adaptive" ? "adaptive" : "budget";
15303
16348
  return supportsAdaptiveThinking(model) ? "adaptive" : "budget";
15304
16349
  }
15305
- function applyAdaptiveThinking(_body, reasoning, setThinking, setOutputConfig) {
15306
- const requested = normalizeEffort(reasoning.effort);
15307
- if (requested === "none") return false;
15308
- let effort;
15309
- if (requested) effort = requested === "minimal" ? "low" : requested;
15310
- else if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
15311
- effort = reasoning.budgetTokens <= ANTHROPIC_EFFORT_BUDGET.low ? "low" : reasoning.budgetTokens <= ANTHROPIC_EFFORT_BUDGET.medium ? "medium" : "high";
16350
+ function enforceAnthropicSampling(target, rule, thinkingOn, bedrock = false) {
16351
+ const fields = bedrock ? [["temperature", "temperature"], ["topP", "topP"]] : [["temperature", "temperature"], ["top_p", "topP"], ["top_k", "topK"]];
16352
+ for (const [key, field] of fields) {
16353
+ if (key in target && !allowsSampling(rule, field, target[key], thinkingOn)) delete target[key];
15312
16354
  }
15313
- if (!effort) return false;
16355
+ }
16356
+ function applyAdaptiveThinking(reasoning, rule, setThinking, setOutputConfig) {
16357
+ let requested = normalizeEffort(reasoning.effort);
16358
+ if (!requested && typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
16359
+ requested = reasoning.budgetTokens <= ANTHROPIC_EFFORT_BUDGET.low ? "low" : reasoning.budgetTokens <= ANTHROPIC_EFFORT_BUDGET.medium ? "medium" : "high";
16360
+ }
16361
+ if (!requested) return false;
16362
+ const mapped = mapReasoningEffort(rule, requested);
16363
+ if (mapped.off) {
16364
+ setThinking({ type: mapped.off });
16365
+ return false;
16366
+ }
16367
+ if (!mapped.effort || mapped.effort === "none") return false;
15314
16368
  const thinking = { type: "adaptive" };
15315
16369
  if (reasoning.includeThoughts !== false) thinking.display = "summarized";
15316
16370
  if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
15317
- setThinking({ thinking });
15318
- setOutputConfig({ effort });
16371
+ setThinking(thinking);
16372
+ setOutputConfig({ effort: mapped.effort });
15319
16373
  return true;
15320
16374
  }
15321
16375
  function applyAnthropicReasoning(body, reasoning, opts) {
15322
- if (!reasoning) return;
15323
- if (resolveThinkingForm(opts?.model ?? body.model, opts?.mode) === "adaptive") {
15324
- const on = applyAdaptiveThinking(body, reasoning, (extra) => body.thinking = extra.thinking, (o) => body.output_config = { ...body.output_config ?? {}, ...o });
15325
- if (on) stripSamplingParams(body, ["temperature", "top_p", "top_k"]);
16376
+ const rule = opts?.rule ?? resolveModelRule({ provider: "anthropic", model: opts?.model ?? body.model });
16377
+ if (!reasoning) {
16378
+ enforceAnthropicSampling(body, rule, false);
16379
+ return;
16380
+ }
16381
+ if (resolveThinkingForm(opts?.model ?? body.model, opts?.mode, rule) === "adaptive") {
16382
+ const on = applyAdaptiveThinking(reasoning, rule, (t) => body.thinking = t, (o) => body.output_config = { ...body.output_config ?? {}, ...o });
16383
+ enforceAnthropicSampling(body, rule, on);
16384
+ return;
16385
+ }
16386
+ let budget = resolveBudget(reasoning, ANTHROPIC_EFFORT_BUDGET, rule);
16387
+ if (!budget) {
16388
+ enforceAnthropicSampling(body, rule, false);
15326
16389
  return;
15327
16390
  }
15328
- let budget = resolveBudget(reasoning, ANTHROPIC_EFFORT_BUDGET);
15329
- if (!budget) return;
15330
16391
  const currentMax = typeof body.max_tokens === "number" ? body.max_tokens : 0;
15331
16392
  const requiredMax = Math.max(THINKING_MIN_MAX_TOKENS, budget + THINKING_OUTPUT_RESERVE);
15332
16393
  if (currentMax < requiredMax) {
@@ -15336,18 +16397,25 @@ function applyAnthropicReasoning(body, reasoning, opts) {
15336
16397
  const thinking = { type: "enabled", budget_tokens: budget };
15337
16398
  if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
15338
16399
  body.thinking = thinking;
15339
- stripSamplingParams(body, ["temperature", "top_p", "top_k"]);
16400
+ enforceAnthropicSampling(body, rule, true);
15340
16401
  }
15341
16402
  function applyBedrockReasoning(body, reasoning, opts) {
15342
- if (!reasoning) return;
15343
- if (resolveThinkingForm(opts?.model ?? body.modelId, opts?.mode) === "adaptive") {
16403
+ const rule = opts?.rule ?? resolveModelRule({ provider: "bedrock", model: opts?.model ?? body.modelId });
16404
+ if (!reasoning) {
16405
+ if (body.inferenceConfig) enforceAnthropicSampling(body.inferenceConfig, rule, false, true);
16406
+ return;
16407
+ }
16408
+ if (resolveThinkingForm(opts?.model ?? body.modelId, opts?.mode, rule) === "adaptive") {
15344
16409
  const fields = () => body.additionalModelRequestFields ?? (body.additionalModelRequestFields = {});
15345
- const on = applyAdaptiveThinking(body, reasoning, (extra2) => fields().thinking = extra2.thinking, (o) => fields().output_config = { ...fields().output_config ?? {}, ...o });
15346
- if (on && body.inferenceConfig) stripSamplingParams(body.inferenceConfig, ["temperature", "topP"]);
16410
+ const on = applyAdaptiveThinking(reasoning, rule, (t) => fields().thinking = t, (o) => fields().output_config = { ...fields().output_config ?? {}, ...o });
16411
+ if (body.inferenceConfig) enforceAnthropicSampling(body.inferenceConfig, rule, on, true);
16412
+ return;
16413
+ }
16414
+ let budget = resolveBudget(reasoning, ANTHROPIC_EFFORT_BUDGET, rule);
16415
+ if (!budget) {
16416
+ if (body.inferenceConfig) enforceAnthropicSampling(body.inferenceConfig, rule, false, true);
15347
16417
  return;
15348
16418
  }
15349
- let budget = resolveBudget(reasoning, ANTHROPIC_EFFORT_BUDGET);
15350
- if (!budget) return;
15351
16419
  const inference = body.inferenceConfig ?? (body.inferenceConfig = {});
15352
16420
  const currentMax = typeof inference.maxTokens === "number" ? inference.maxTokens : 0;
15353
16421
  const requiredMax = Math.max(THINKING_MIN_MAX_TOKENS, budget + THINKING_OUTPUT_RESERVE);
@@ -15359,7 +16427,7 @@ function applyBedrockReasoning(body, reasoning, opts) {
15359
16427
  const thinking = { type: "enabled", budget_tokens: budget };
15360
16428
  if (reasoning.providerExtras) Object.assign(thinking, reasoning.providerExtras);
15361
16429
  extra.thinking = thinking;
15362
- stripSamplingParams(inference, ["temperature", "topP"]);
16430
+ enforceAnthropicSampling(inference, rule, true, true);
15363
16431
  }
15364
16432
  function applyGeminiReasoning(generationConfig, reasoning) {
15365
16433
  if (!reasoning) return;
@@ -15371,11 +16439,6 @@ function applyGeminiReasoning(generationConfig, reasoning) {
15371
16439
  if (reasoning.providerExtras) Object.assign(thinkingConfig, reasoning.providerExtras);
15372
16440
  generationConfig.thinkingConfig = thinkingConfig;
15373
16441
  }
15374
- function stripSamplingParams(target, keys) {
15375
- for (const k of keys) {
15376
- if (k in target) delete target[k];
15377
- }
15378
- }
15379
16442
  function clampBudget(budget, maxTokens) {
15380
16443
  if (typeof maxTokens !== "number" || maxTokens <= 0) return budget;
15381
16444
  const ceiling = maxTokens - THINKING_OUTPUT_RESERVE;
@@ -15384,16 +16447,17 @@ function clampBudget(budget, maxTokens) {
15384
16447
  }
15385
16448
  function normalizeEffort(effort) {
15386
16449
  if (!effort) return void 0;
15387
- const allowed = ["none", "minimal", "low", "medium", "high"];
16450
+ const allowed = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
15388
16451
  return allowed.includes(effort) ? effort : void 0;
15389
16452
  }
15390
- function resolveBudget(reasoning, effortBudget) {
16453
+ function resolveBudget(reasoning, effortBudget2, rule) {
15391
16454
  const effort = normalizeEffort(reasoning.effort);
15392
16455
  if (effort === "none") return 0;
15393
16456
  if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
15394
16457
  return Math.floor(reasoning.budgetTokens);
15395
16458
  }
15396
- if (effort && effortBudget[effort]) return effortBudget[effort];
16459
+ const mapped = effort && rule ? mapReasoningEffort(rule, effort).effort : effort;
16460
+ if (mapped && effortBudget2[mapped]) return effortBudget2[mapped];
15397
16461
  return void 0;
15398
16462
  }
15399
16463
 
@@ -15464,6 +16528,211 @@ function parseSSEBlock(block) {
15464
16528
  return { event, data: dataLines.join("\n"), id, retry };
15465
16529
  }
15466
16530
 
16531
+ // src/models/errors.ts
16532
+ var ModelRequestError = class extends ProviderError {
16533
+ constructor(init) {
16534
+ super(init.message, init.provider, init.status ?? 400, init.responseBody);
16535
+ this.status = init.status ?? 400;
16536
+ this.code = init.code;
16537
+ this.param = init.param;
16538
+ this.value = init.value;
16539
+ this.supportedValues = init.supportedValues;
16540
+ this.model = init.model;
16541
+ }
16542
+ };
16543
+
16544
+ // src/models/adjust.ts
16545
+ var MAX_ADJUSTMENTS = 3;
16546
+ var Q = "['\"`]";
16547
+ function errorMessage(text) {
16548
+ try {
16549
+ const j = JSON.parse(text);
16550
+ const e = j?.error ?? j;
16551
+ const message = typeof e?.message === "string" ? e.message : typeof j?.message === "string" ? j.message : text;
16552
+ return { message, param: typeof e?.param === "string" ? e.param : void 0, code: typeof e?.code === "string" ? e.code : void 0 };
16553
+ } catch {
16554
+ return { message: text };
16555
+ }
16556
+ }
16557
+ function quotedList(s) {
16558
+ const quoted = [...s.matchAll(new RegExp(`${Q}([^'"\`]+)${Q}`, "g"))].map((m) => m[1]);
16559
+ if (quoted.length > 0) return quoted;
16560
+ return s.split(/[,\s]+/).map((x) => x.replace(/[.\s]+$/, "")).filter(Boolean);
16561
+ }
16562
+ function parseModelError(text) {
16563
+ const { message, param: errParam } = errorMessage(String(text ?? ""));
16564
+ const m = message.toLowerCase();
16565
+ if (m.includes("function tools with reasoning_effort") && m.includes("not supported")) {
16566
+ return { kind: "chat_tools_effort", message };
16567
+ }
16568
+ if (m.includes("between_tools") && m.includes("thinking")) return { kind: "thinking_off_between_tools", message };
16569
+ if (m.includes("thinking.type.enabled") && m.includes("not supported")) return { kind: "thinking_enabled_unsupported", message };
16570
+ if (m.includes("thinking.type.adaptive") && m.includes("not supported")) return { kind: "thinking_adaptive_unsupported", message };
16571
+ if (m.includes("thinking.type.disabled") && m.includes("not supported")) return { kind: "thinking_disabled_unsupported", message };
16572
+ const tt = new RegExp(`${Q}?(temperature|top_p|top_k)${Q}?\\s+may only be set to 1 when thinking is enabled`, "i").exec(message);
16573
+ if (tt) return { kind: "temperature_thinking", param: tt[1].toLowerCase(), message };
16574
+ const dep = new RegExp(`${Q}([\\w.]+)${Q}\\s+is deprecated for this model`, "i").exec(message);
16575
+ if (dep) return { kind: "unsupported_parameter", param: dep[1], message };
16576
+ const rename = new RegExp(`use ${Q}(max_completion_tokens|max_tokens)${Q} instead`, "i").exec(message);
16577
+ if (rename) return { kind: "max_tokens_rename", renameTo: rename[1].toLowerCase(), param: rename[1].toLowerCase() === "max_tokens" ? "max_completion_tokens" : "max_tokens", message };
16578
+ const val = new RegExp(`unsupported value:?\\s*${Q}?([\\w.]+)${Q}?\\s+does not support\\s+(${Q}[^'"\`]*${Q}|[^\\s]+)`, "i").exec(message);
16579
+ if (val) {
16580
+ const param = val[1];
16581
+ const value = val[2].replace(/^['"`]|['"`]$/g, "");
16582
+ if (m.includes("only the default") && m.includes("value is supported")) return { kind: "default_only", param, value, message };
16583
+ const sup = /supported values? (?:are|is):?\s*([^\n]*)/i.exec(message);
16584
+ return { kind: "unsupported_value", param, value, supportedValues: sup ? quotedList(sup[1]) : void 0, message };
16585
+ }
16586
+ if (m.includes("only the default") && m.includes("value is supported")) {
16587
+ return { kind: "default_only", param: errParam ?? "temperature", message };
16588
+ }
16589
+ const prm = new RegExp(`unsupported parameter:?\\s*${Q}?([\\w.]+)${Q}?`, "i").exec(message);
16590
+ if (prm) return { kind: "unsupported_parameter", param: prm[1], message };
16591
+ return void 0;
16592
+ }
16593
+ var normParam = (p) => p.toLowerCase().replace(/[-\s]/g, "_");
16594
+ var LADDER2 = new Set(MODEL_RULES.effortLadder);
16595
+ function patchFromParsedError(parsed, endpoint = "chat") {
16596
+ const param = parsed.param ? normParam(parsed.param) : "";
16597
+ switch (parsed.kind) {
16598
+ case "chat_tools_effort":
16599
+ return { patch: { chatTools: { reasoning: "force-none" } }, adjustment: "chat-tools:force-effort-none" };
16600
+ case "max_tokens_rename":
16601
+ return { patch: { maxTokensParam: parsed.renameTo }, adjustment: `${parsed.param}->${parsed.renameTo}` };
16602
+ case "thinking_enabled_unsupported":
16603
+ return { patch: { reasoning: { style: "adaptive" } }, adjustment: "thinking:adaptive" };
16604
+ case "thinking_adaptive_unsupported":
16605
+ return { patch: { reasoning: { style: "budget" } }, adjustment: "thinking:budget" };
16606
+ case "thinking_off_between_tools":
16607
+ return { patch: { reasoning: { off: "between_tools" } }, adjustment: "thinking-off:between_tools" };
16608
+ case "thinking_disabled_unsupported":
16609
+ return { patch: { reasoning: { off: null } }, adjustment: "thinking-off:none" };
16610
+ case "temperature_thinking":
16611
+ return { patch: { sampling: { temperature: "unsupported" } }, adjustment: "drop:temperature" };
16612
+ case "default_only":
16613
+ if (param === "temperature") return { patch: { sampling: { temperature: "default-only" } }, adjustment: "temperature:default-only" };
16614
+ return { patch: { dropParams: [parsed.param ?? param] }, adjustment: `drop:${parsed.param ?? param}` };
16615
+ case "unsupported_value": {
16616
+ if (/^(reasoning_effort|reasoning\.effort|effort|output_config\.effort)$/.test(param)) {
16617
+ const list = (parsed.supportedValues ?? []).map((v) => v.toLowerCase()).filter((v) => LADDER2.has(v));
16618
+ if (list.length === 0) return void 0;
16619
+ const reasoning = endpoint === "responses" ? { responsesEfforts: list } : { efforts: list };
16620
+ return { patch: { reasoning }, adjustment: `efforts:${list.join("|")}` };
16621
+ }
16622
+ return paramDropPatch(param, parsed);
16623
+ }
16624
+ case "unsupported_parameter":
16625
+ return paramDropPatch(param, parsed);
16626
+ }
16627
+ }
16628
+ function paramDropPatch(param, parsed) {
16629
+ if (!param) return void 0;
16630
+ const label = `drop:${parsed.param ?? param}`;
16631
+ switch (param) {
16632
+ case "temperature":
16633
+ return { patch: { sampling: { temperature: "unsupported" } }, adjustment: label };
16634
+ case "top_p":
16635
+ return { patch: { sampling: { topP: false } }, adjustment: label };
16636
+ case "top_k":
16637
+ return { patch: { sampling: { topK: false } }, adjustment: label };
16638
+ case "presence_penalty":
16639
+ case "frequency_penalty":
16640
+ return { patch: { sampling: { penalties: false } }, adjustment: label };
16641
+ case "logprobs":
16642
+ case "top_logprobs":
16643
+ return { patch: { sampling: { logprobs: false } }, adjustment: label };
16644
+ case "max_tokens":
16645
+ return { patch: { maxTokensParam: "max_completion_tokens" }, adjustment: "max_tokens->max_completion_tokens" };
16646
+ case "max_completion_tokens":
16647
+ return { patch: { maxTokensParam: "max_tokens" }, adjustment: "max_completion_tokens->max_tokens" };
16648
+ case "reasoning_effort":
16649
+ case "reasoning.effort":
16650
+ case "reasoning":
16651
+ return { patch: { reasoning: { style: "none", efforts: [], responsesEfforts: [] }, dropParams: [param] }, adjustment: label };
16652
+ default:
16653
+ return { patch: { dropParams: [parsed.param ?? param] }, adjustment: label };
16654
+ }
16655
+ }
16656
+ function codeFor(parsed) {
16657
+ if (!parsed) return "model_request_invalid";
16658
+ if (parsed.kind === "unsupported_value" || parsed.kind === "default_only") return "unsupported_value";
16659
+ if (parsed.kind === "unsupported_parameter" || parsed.kind === "max_tokens_rename" || parsed.kind === "temperature_thinking") return "unsupported_parameter";
16660
+ return "model_request_invalid";
16661
+ }
16662
+ function toModelRequestError(err, parsed, provider, model) {
16663
+ return new ModelRequestError({
16664
+ status: err.statusCode ?? 400,
16665
+ code: codeFor(parsed),
16666
+ param: parsed?.param,
16667
+ value: parsed?.value,
16668
+ supportedValues: parsed?.supportedValues,
16669
+ provider: err.provider || provider,
16670
+ model,
16671
+ message: err.message,
16672
+ responseBody: err.responseBody
16673
+ });
16674
+ }
16675
+ var SameRequest = class extends Error {
16676
+ };
16677
+ function withoutCallerParams(step, callerParams) {
16678
+ const drops = step.patch.dropParams;
16679
+ if (!callerParams?.length || !drops) return step;
16680
+ const mine = (p) => callerParams.includes(p.split(".")[0]);
16681
+ if (!drops.some(mine)) return step;
16682
+ const { dropParams: _ignored, ...rest } = step.patch;
16683
+ const kept = drops.filter((p) => !mine(p));
16684
+ const patch = kept.length ? { ...rest, dropParams: kept } : rest;
16685
+ return Object.keys(patch).length ? { ...step, patch } : void 0;
16686
+ }
16687
+ async function runWithModelAdjustments(opts) {
16688
+ let overlay = {};
16689
+ const steps = [];
16690
+ let previous;
16691
+ let lastError;
16692
+ let lastParsed;
16693
+ for (; ; ) {
16694
+ const guard = (body) => {
16695
+ let json;
16696
+ try {
16697
+ json = JSON.stringify(body);
16698
+ } catch {
16699
+ return;
16700
+ }
16701
+ if (previous !== void 0 && json === previous) throw new SameRequest();
16702
+ previous = json;
16703
+ };
16704
+ try {
16705
+ const result = await opts.run(overlay, guard);
16706
+ for (const step of steps) {
16707
+ const s = withoutCallerParams(step, opts.callerParams);
16708
+ if (!s) continue;
16709
+ rememberModelRulePatch(opts.provider, opts.model, s.patch, opts.scope);
16710
+ if (opts.onLearned) {
16711
+ try {
16712
+ opts.onLearned({ provider: opts.provider, model: opts.model, adjustment: s.adjustment, patch: s.patch, reason: s.reason });
16713
+ } catch {
16714
+ }
16715
+ }
16716
+ }
16717
+ return result;
16718
+ } catch (err) {
16719
+ if (err instanceof SameRequest) throw toModelRequestError(lastError, lastParsed, opts.provider, opts.model);
16720
+ if (!(err instanceof ProviderError) || err instanceof ModelRequestError || err.statusCode !== 400) throw err;
16721
+ const parsed = parseModelError(String(err.responseBody ?? err.message));
16722
+ const fix = parsed ? patchFromParsedError(parsed, opts.endpoint) : void 0;
16723
+ lastError = err;
16724
+ lastParsed = parsed;
16725
+ if (!fix || steps.length >= MAX_ADJUSTMENTS) throw toModelRequestError(err, parsed, opts.provider, opts.model);
16726
+ overlay = mergeRulePatch(overlay, fix.patch);
16727
+ steps.push({ ...fix, reason: parsed.message });
16728
+ }
16729
+ }
16730
+ }
16731
+ function asModelRequestError(err, provider, model) {
16732
+ if (!(err instanceof ProviderError) || err instanceof ModelRequestError || err.statusCode !== 400) return err;
16733
+ return toModelRequestError(err, parseModelError(String(err.responseBody ?? err.message)), provider, model);
16734
+ }
16735
+
15467
16736
  // src/providers/utils/media.ts
15468
16737
  var EXTENSION_MIME = {
15469
16738
  jpg: "image/jpeg",
@@ -15764,8 +17033,7 @@ var OpenAIProvider = class extends BaseProvider {
15764
17033
  if (this.useResponsesApi(request)) {
15765
17034
  return this.completeResponses(request);
15766
17035
  }
15767
- const body = this.buildRequestBody(request, false);
15768
- const res = await this.doFetch(body, request.signal);
17036
+ const res = await this.fetchChat(request, false);
15769
17037
  const json = await res.json();
15770
17038
  return this.parseResponse(json);
15771
17039
  }
@@ -15789,8 +17057,7 @@ var OpenAIProvider = class extends BaseProvider {
15789
17057
  };
15790
17058
  return;
15791
17059
  }
15792
- const body = this.buildRequestBody(request, true);
15793
- const res = await this.doFetch(body, request.signal);
17060
+ const res = await this.fetchChat(request, true);
15794
17061
  if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
15795
17062
  let id = "";
15796
17063
  let model = "";
@@ -15894,7 +17161,43 @@ var OpenAIProvider = class extends BaseProvider {
15894
17161
  return res;
15895
17162
  }
15896
17163
  // ─── Internals ───────────────────────────────────────────────────────────
15897
- buildRequestBody(request, stream) {
17164
+ /** Chat Completions call that corrects itself from the provider's own 400s (see models/adjust.ts). */
17165
+ fetchChat(request, stream) {
17166
+ const model = request.model || this.defaultModel;
17167
+ return runWithModelAdjustments({
17168
+ provider: this.providerName,
17169
+ model,
17170
+ endpoint: "chat",
17171
+ scope: this.baseURL,
17172
+ callerParams: request.extra ? Object.keys(request.extra) : void 0,
17173
+ onLearned: this.onModelRuleLearned,
17174
+ run: (overlay, guard) => {
17175
+ const body = this.buildRequestBody(request, stream, overlay);
17176
+ guard(body);
17177
+ return this.doFetch(body, request.signal);
17178
+ }
17179
+ });
17180
+ }
17181
+ /** Same for the Responses API. */
17182
+ fetchResponses(request, adaptExtras, stream = false) {
17183
+ const model = request.model || this.defaultModel;
17184
+ return runWithModelAdjustments({
17185
+ provider: this.providerName,
17186
+ model,
17187
+ endpoint: "responses",
17188
+ scope: this.baseURL,
17189
+ callerParams: request.extra ? Object.keys(request.extra) : void 0,
17190
+ onLearned: this.onModelRuleLearned,
17191
+ run: (overlay, guard) => {
17192
+ const body = this.buildResponsesBody(request, overlay);
17193
+ if (adaptExtras) adaptChatExtrasForResponses(body, request.extra);
17194
+ if (stream) body.stream = true;
17195
+ guard(body);
17196
+ return this.responsesFetch(body, request.signal);
17197
+ }
17198
+ });
17199
+ }
17200
+ buildRequestBody(request, stream, overlay) {
15898
17201
  const body = {
15899
17202
  model: request.model || this.defaultModel,
15900
17203
  messages: request.messages.map((m) => this.toOpenAIMessage(m)),
@@ -15904,20 +17207,20 @@ var OpenAIProvider = class extends BaseProvider {
15904
17207
  body.stream_options = { include_usage: true };
15905
17208
  }
15906
17209
  const modelId = String(body.model);
15907
- const reasoningModel = isReasoningModel(modelId) && !/chat/i.test(modelId);
15908
- if (request.temperature != null && !reasoningModel) body.temperature = request.temperature;
17210
+ const rule = resolveModelRule({ provider: this.providerName, model: modelId, patch: overlay, scope: this.baseURL });
17211
+ if (request.temperature != null && allowsSampling(rule, "temperature", request.temperature)) body.temperature = request.temperature;
15909
17212
  if (request.maxTokens != null) {
15910
- const useCompletionTokens = this.maxTokensParam === "max_completion_tokens" || this.maxTokensParam === "auto" && isReasoningModel(modelId);
15911
- body[useCompletionTokens ? "max_completion_tokens" : "max_tokens"] = request.maxTokens;
17213
+ const param = this.maxTokensParam === "auto" ? rule.maxTokensParam : this.maxTokensParam;
17214
+ body[param] = request.maxTokens;
15912
17215
  }
15913
- if (request.topP != null && !reasoningModel) body.top_p = request.topP;
17216
+ if (request.topP != null && allowsSampling(rule, "topP")) body.top_p = request.topP;
15914
17217
  if (request.stop) body.stop = request.stop;
15915
- if (request.presencePenalty != null) body.presence_penalty = request.presencePenalty;
15916
- if (request.frequencyPenalty != null) body.frequency_penalty = request.frequencyPenalty;
17218
+ if (request.presencePenalty != null && allowsSampling(rule, "presencePenalty")) body.presence_penalty = request.presencePenalty;
17219
+ if (request.frequencyPenalty != null && allowsSampling(rule, "frequencyPenalty")) body.frequency_penalty = request.frequencyPenalty;
15917
17220
  if (request.seed != null) body.seed = request.seed;
15918
17221
  if (request.n != null) body.n = request.n;
15919
- if (request.logprobs != null) body.logprobs = request.logprobs;
15920
- if (request.topLogprobs != null) body.top_logprobs = request.topLogprobs;
17222
+ if (request.logprobs != null && allowsSampling(rule, "logprobs")) body.logprobs = request.logprobs;
17223
+ if (request.topLogprobs != null && allowsSampling(rule, "logprobs")) body.top_logprobs = request.topLogprobs;
15921
17224
  if (request.tools?.length) {
15922
17225
  body.tools = request.tools.map((t) => this.toOpenAITool(t));
15923
17226
  if (request.parallelToolCalls != null) body.parallel_tool_calls = request.parallelToolCalls;
@@ -15946,14 +17249,17 @@ var OpenAIProvider = class extends BaseProvider {
15946
17249
  if (request.extra) {
15947
17250
  Object.assign(body, request.extra);
15948
17251
  }
15949
- applyOpenAIReasoning(body, request.reasoning, "legacy_effort");
17252
+ applyOpenAIReasoning(body, request.reasoning, "legacy_effort", modelId, rule);
17253
+ if (body.tools?.length && rule.chatTools.reasoning === "force-none" && rule.reasoning.style !== "none") {
17254
+ body.reasoning_effort = "none";
17255
+ }
17256
+ applyDropParams(body, rule);
15950
17257
  return body;
15951
17258
  }
15952
17259
  // ─── Responses API (reasoning models) ──────────────────────────────────────
15953
17260
  async completeResponses(request) {
15954
- const body = this.buildResponsesBody(request);
15955
- if (this.apiResponsesNative || this.isResponsesApiModel(request)) adaptChatExtrasForResponses(body, request.extra);
15956
- const res = await this.responsesFetch(body, request.signal);
17261
+ const adapt = this.apiResponsesNative || this.isResponsesApiModel(request);
17262
+ const res = await this.fetchResponses(request, adapt);
15957
17263
  const json = await res.json();
15958
17264
  return this.parseResponsesResponse(json, request.model || this.defaultModel);
15959
17265
  }
@@ -15966,10 +17272,7 @@ var OpenAIProvider = class extends BaseProvider {
15966
17272
  * reads it). */
15967
17273
  async *streamResponses(request) {
15968
17274
  const modelId = request.model || this.defaultModel;
15969
- const body = this.buildResponsesBody(request);
15970
- adaptChatExtrasForResponses(body, request.extra);
15971
- body.stream = true;
15972
- const res = await this.responsesFetch(body, request.signal);
17275
+ const res = await this.fetchResponses(request, true, true);
15973
17276
  if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
15974
17277
  let id = "";
15975
17278
  let model = modelId;
@@ -16058,13 +17361,14 @@ var OpenAIProvider = class extends BaseProvider {
16058
17361
  finishReason: final?.finishReason ?? (calls.size > 0 ? "tool_calls" : "stop")
16059
17362
  };
16060
17363
  }
16061
- buildResponsesBody(request) {
17364
+ buildResponsesBody(request, overlay) {
16062
17365
  const body = {
16063
17366
  model: request.model || this.defaultModel,
16064
17367
  input: request.messages.map((m) => this.toResponsesInput(m))
16065
17368
  };
16066
17369
  if (request.maxTokens != null) body.max_output_tokens = request.maxTokens;
16067
- if (request.topP != null) body.top_p = request.topP;
17370
+ const rule = resolveModelRule({ provider: this.providerName, model: String(body.model), patch: overlay, scope: this.baseURL });
17371
+ if (request.topP != null && allowsSampling(rule, "topP")) body.top_p = request.topP;
16068
17372
  if (request.tools?.length) {
16069
17373
  body.tools = request.tools.map((t) => ({
16070
17374
  type: "function",
@@ -16096,7 +17400,8 @@ var OpenAIProvider = class extends BaseProvider {
16096
17400
  }
16097
17401
  }
16098
17402
  if (request.extra) Object.assign(body, request.extra);
16099
- applyOpenAIReasoning(body, request.reasoning, "responses");
17403
+ applyOpenAIReasoning(body, request.reasoning, "responses", String(body.model), rule);
17404
+ applyDropParams(body, rule);
16100
17405
  return body;
16101
17406
  }
16102
17407
  /** Maps a unified message to a Responses API `input` item. */
@@ -16361,8 +17666,8 @@ function pickProviderFields(top, message, choice) {
16361
17666
  }
16362
17667
  function isReasoningModel(model) {
16363
17668
  if (!model) return false;
16364
- const m = model.toLowerCase();
16365
- return /(^|[/_-])o[0-9]/.test(m) || m.startsWith("o1") || m.startsWith("o3") || m.startsWith("o4") || m.includes("gpt-5") || m.includes("gpt5");
17669
+ const rule = resolveModelRule({ provider: "openai", model });
17670
+ return !rule.isDefault && rule.reasoning.style === "effort" && rule.sampling.temperature !== "any";
16366
17671
  }
16367
17672
  function adaptChatExtrasForResponses(body, extra) {
16368
17673
  const native = extra ?? {};
@@ -16478,12 +17783,6 @@ function normalizeExplicitThinking(body) {
16478
17783
  }
16479
17784
  for (const k of ["temperature", "top_p", "top_k"]) delete body[k];
16480
17785
  }
16481
- function thinkingFormRejected(text) {
16482
- const t = text.toLowerCase();
16483
- if (t.indexOf("thinking.type.enabled") >= 0 && t.indexOf("not supported") >= 0) return "enabled";
16484
- if (t.indexOf("thinking.type.adaptive") >= 0 && t.indexOf("not supported") >= 0 && t.indexOf("thinking.type.enabled") < 0) return "adaptive";
16485
- return void 0;
16486
- }
16487
17786
  var AnthropicProvider = class extends BaseProvider {
16488
17787
  constructor(config) {
16489
17788
  super();
@@ -16634,7 +17933,7 @@ var AnthropicProvider = class extends BaseProvider {
16634
17933
  }
16635
17934
  }
16636
17935
  // ─── Internals ───────────────────────────────────────────────────────────
16637
- buildRequestBody(request, stream, modeOverride, downgradeToolChoice = false) {
17936
+ buildRequestBody(request, stream, overlay, downgradeToolChoice = false) {
16638
17937
  const { system, messages } = this.splitSystemMessages(request.messages);
16639
17938
  const body = {
16640
17939
  model: request.model || this.defaultModel,
@@ -16649,6 +17948,7 @@ var AnthropicProvider = class extends BaseProvider {
16649
17948
  body.system = system;
16650
17949
  }
16651
17950
  }
17951
+ const rule = resolveModelRule({ provider: this.providerName, model: String(body.model), patch: overlay });
16652
17952
  if (request.temperature != null) body.temperature = request.temperature;
16653
17953
  if (request.topP != null) body.top_p = request.topP;
16654
17954
  if (request.stop) body.stop_sequences = request.stop;
@@ -16675,9 +17975,11 @@ var AnthropicProvider = class extends BaseProvider {
16675
17975
  const explicitThinking = request.extra && typeof request.extra === "object" && "thinking" in request.extra;
16676
17976
  if (explicitThinking) {
16677
17977
  normalizeExplicitThinking(body);
17978
+ enforceAnthropicSampling(body, rule, body.thinking?.type === "enabled" || body.thinking?.type === "adaptive");
16678
17979
  } else {
16679
- applyAnthropicReasoning(body, request.reasoning, { model: body.model, mode: modeOverride ?? this.thinkingMode });
17980
+ applyAnthropicReasoning(body, request.reasoning, { model: body.model, mode: this.thinkingMode, rule });
16680
17981
  }
17982
+ applyDropParams(body, rule);
16681
17983
  if (typeof body.max_tokens !== "number" || !(body.max_tokens > 0)) {
16682
17984
  body.max_tokens = DEFAULT_MAX_TOKENS;
16683
17985
  }
@@ -16813,31 +18115,28 @@ var AnthropicProvider = class extends BaseProvider {
16813
18115
  }
16814
18116
  /** Builds and sends the request; retries once with the other thinking form
16815
18117
  * when the upstream rejects the one we picked. */
16816
- async send(request, stream) {
16817
- let mode;
18118
+ send(request, stream) {
16818
18119
  let downgrade = false;
16819
- let flippedThinking = false;
16820
- for (; ; ) {
16821
- const body = this.buildRequestBody(request, stream, mode, downgrade);
16822
- try {
16823
- return await this.doFetch(body, request.signal);
16824
- } catch (err) {
16825
- if (!(err instanceof ProviderError) || err.statusCode !== 400) throw err;
16826
- const text = String(err.responseBody ?? err.message);
16827
- if (!downgrade && body.tool_choice && body.tool_choice.type !== "auto" && forcedToolChoiceRejected(text)) {
16828
- downgrade = true;
16829
- continue;
18120
+ return runWithModelAdjustments({
18121
+ provider: this.providerName,
18122
+ model: request.model || this.defaultModel,
18123
+ endpoint: "messages",
18124
+ callerParams: request.extra ? Object.keys(request.extra) : void 0,
18125
+ onLearned: this.onModelRuleLearned,
18126
+ run: async (overlay, guard) => {
18127
+ for (; ; ) {
18128
+ const body = this.buildRequestBody(request, stream, overlay, downgrade);
18129
+ guard(body);
18130
+ try {
18131
+ return await this.doFetch(body, request.signal);
18132
+ } catch (err) {
18133
+ const rejected = err instanceof ProviderError && err.statusCode === 400 && !downgrade && body.tool_choice && body.tool_choice.type !== "auto" && forcedToolChoiceRejected(String(err.responseBody ?? err.message));
18134
+ if (!rejected) throw err;
18135
+ downgrade = true;
18136
+ }
16830
18137
  }
16831
- const explicit = request.extra && typeof request.extra === "object" && "thinking" in request.extra;
16832
- if (flippedThinking || explicit || !request.reasoning) throw err;
16833
- const wrong = thinkingFormRejected(text);
16834
- if (!wrong) throw err;
16835
- const used = resolveThinkingForm(request.model ?? this.defaultModel, this.thinkingMode);
16836
- if (wrong === "enabled" && used !== "budget" || wrong === "adaptive" && used !== "adaptive") throw err;
16837
- mode = wrong === "enabled" ? "adaptive" : "budget";
16838
- flippedThinking = true;
16839
18138
  }
16840
- }
18139
+ });
16841
18140
  }
16842
18141
  async doFetch(body, signal) {
16843
18142
  const url = `${this.baseURL}/v1/messages`;
@@ -16954,6 +18253,7 @@ var AzureProvider = class extends OpenAIProvider {
16954
18253
  retry: config.retry,
16955
18254
  fetch: config.fetch,
16956
18255
  timeoutMs: config.timeoutMs,
18256
+ onModelRuleLearned: config.onModelRuleLearned,
16957
18257
  maxTokensParam: config.maxTokensParam,
16958
18258
  api: config.api,
16959
18259
  responsesApi: config.responsesApi,
@@ -17082,6 +18382,7 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
17082
18382
  retry: config.retry,
17083
18383
  fetch: config.fetch,
17084
18384
  timeoutMs: config.timeoutMs,
18385
+ onModelRuleLearned: config.onModelRuleLearned,
17085
18386
  maxTokensParam: config.maxTokensParam,
17086
18387
  api: config.api,
17087
18388
  // Unlike OpenAI / Azure, "auto" on a compatible server means Chat
@@ -17437,7 +18738,7 @@ var BedrockProvider = class extends BaseProvider {
17437
18738
  return res;
17438
18739
  }
17439
18740
  // ─── Internals ───────────────────────────────────────────────────────────
17440
- buildConverseBody(request, downgradeToolChoice = false) {
18741
+ buildConverseBody(request, downgradeToolChoice = false, overlay) {
17441
18742
  const { system, messages } = this.splitSystemMessages(request.messages);
17442
18743
  const body = {
17443
18744
  messages: this.toBedrockMessages(messages)
@@ -17483,7 +18784,12 @@ var BedrockProvider = class extends BaseProvider {
17483
18784
  Object.assign(body, topLevel);
17484
18785
  if (Object.keys(fields).length > 0) body.additionalModelRequestFields = fields;
17485
18786
  }
17486
- applyBedrockReasoning(body, request.reasoning, { model: request.model || this.defaultModel, mode: this.thinkingMode });
18787
+ const rule = resolveModelRule({ provider: this.providerName, model: request.model || this.defaultModel, patch: overlay });
18788
+ applyBedrockReasoning(body, request.reasoning, { model: request.model || this.defaultModel, mode: this.thinkingMode, rule });
18789
+ if (body.inferenceConfig && rule.dropParams?.length) {
18790
+ for (const p of rule.dropParams) delete body.inferenceConfig[p === "top_p" ? "topP" : p === "max_tokens" ? "maxTokens" : p];
18791
+ }
18792
+ applyDropParams(body, rule);
17487
18793
  return body;
17488
18794
  }
17489
18795
  splitSystemMessages(messages) {
@@ -17608,16 +18914,29 @@ var BedrockProvider = class extends BaseProvider {
17608
18914
  }
17609
18915
  /** Sends the Converse call; retries once with toolChoice auto when the
17610
18916
  * model rejects forced tool use. */
17611
- async converseFetch(modelId, request, stream) {
17612
- const body = this.buildConverseBody(request);
17613
- try {
17614
- return await this.doFetch(modelId, body, request.signal, stream);
17615
- } catch (err) {
17616
- const tc = body.toolConfig?.toolChoice;
17617
- if (!(err instanceof ProviderError) || err.statusCode !== 400 || !tc || tc.auto) throw err;
17618
- if (!forcedToolChoiceRejected(String(err.responseBody ?? err.message))) throw err;
17619
- return this.doFetch(modelId, this.buildConverseBody(request, true), request.signal, stream);
17620
- }
18917
+ converseFetch(modelId, request, stream) {
18918
+ let downgrade = false;
18919
+ return runWithModelAdjustments({
18920
+ provider: this.providerName,
18921
+ model: modelId,
18922
+ endpoint: "converse",
18923
+ callerParams: request.extra ? Object.keys(request.extra) : void 0,
18924
+ onLearned: this.onModelRuleLearned,
18925
+ run: async (overlay, guard) => {
18926
+ for (; ; ) {
18927
+ const body = this.buildConverseBody(request, downgrade, overlay);
18928
+ guard(body);
18929
+ try {
18930
+ return await this.doFetch(modelId, body, request.signal, stream);
18931
+ } catch (err) {
18932
+ const tc = body.toolConfig?.toolChoice;
18933
+ const rejected = err instanceof ProviderError && err.statusCode === 400 && !downgrade && !!tc && !tc.auto && forcedToolChoiceRejected(String(err.responseBody ?? err.message));
18934
+ if (!rejected) throw err;
18935
+ downgrade = true;
18936
+ }
18937
+ }
18938
+ }
18939
+ });
17621
18940
  }
17622
18941
  async doFetch(modelId, body, signal, stream) {
17623
18942
  const host = `bedrock-runtime.${this.region}.amazonaws.com`;
@@ -17807,7 +19126,7 @@ var VertexProvider = class extends BaseProvider {
17807
19126
  const modelId = request.model || this.defaultModel;
17808
19127
  const body = this.buildGeminiBody(request);
17809
19128
  const url = this.buildUrl(modelId, false);
17810
- const res = await this.doFetch(url, body, request.signal);
19129
+ const res = await this.doFetch(url, body, request.signal, modelId);
17811
19130
  const json = await res.json();
17812
19131
  return this.parseResponse(json, modelId);
17813
19132
  }
@@ -17815,7 +19134,7 @@ var VertexProvider = class extends BaseProvider {
17815
19134
  const modelId = request.model || this.defaultModel;
17816
19135
  const body = this.buildGeminiBody(request);
17817
19136
  const url = this.buildUrl(modelId, true);
17818
- const res = await this.doFetch(url, body, request.signal);
19137
+ const res = await this.doFetch(url, body, request.signal, modelId);
17819
19138
  if (!res.body) throw new ProviderError("No response body for stream", this.providerName);
17820
19139
  const counter = { next: 0 };
17821
19140
  const contentType = res.headers.get("content-type") ?? "";
@@ -17895,7 +19214,7 @@ var VertexProvider = class extends BaseProvider {
17895
19214
  };
17896
19215
  if (Object.keys(parameters).length > 0) body.parameters = parameters;
17897
19216
  Object.assign(body, extraRest);
17898
- const res = await this.doFetch(url, body, request.signal);
19217
+ const res = await this.doFetch(url, body, request.signal, model);
17899
19218
  const json = await res.json();
17900
19219
  raw.push(json);
17901
19220
  for (const p of json.predictions ?? []) {
@@ -18118,7 +19437,7 @@ var VertexProvider = class extends BaseProvider {
18118
19437
  const tokenJson = await tokenRes.json();
18119
19438
  return tokenJson.access_token;
18120
19439
  }
18121
- async doFetch(url, body, signal) {
19440
+ async doFetch(url, body, signal, model = "") {
18122
19441
  const token = await this.getAccessToken();
18123
19442
  const bodyStr = JSON.stringify(body);
18124
19443
  const res = await this.doFetchWithRetry(
@@ -18136,6 +19455,20 @@ var VertexProvider = class extends BaseProvider {
18136
19455
  );
18137
19456
  if (!res.ok) {
18138
19457
  const text = await res.text().catch(() => "");
19458
+ if (res.status === 400) {
19459
+ const parsed = parseModelError(text);
19460
+ throw new ModelRequestError({
19461
+ status: 400,
19462
+ code: parsed?.kind === "unsupported_value" ? "unsupported_value" : parsed?.kind === "unsupported_parameter" ? "unsupported_parameter" : "model_request_invalid",
19463
+ param: parsed?.param,
19464
+ value: parsed?.value,
19465
+ supportedValues: parsed?.supportedValues,
19466
+ provider: this.providerName,
19467
+ model,
19468
+ message: `Vertex AI API error ${res.status}: ${text}`,
19469
+ responseBody: text
19470
+ });
19471
+ }
18139
19472
  throw new ProviderError(
18140
19473
  `Vertex AI API error ${res.status}: ${text}`,
18141
19474
  this.providerName,
@@ -18294,6 +19627,6 @@ function createProvider(config) {
18294
19627
  }
18295
19628
  }
18296
19629
 
18297
- export { ASK_USER_TOOL_NAME, AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, CCRStore, CONSOLE_HOOK_MAP, CONTEXT_PILOT_JSON_MARKER_KEY, CONTROL_PLANE_TOOL_NAMES, DEFAULT_MAX_TOOL_INPUT_FIELD_CHARS, DEFAULT_PROFILE_CONFIGS, DEFAULT_SKILL_POLICY, DEFAULT_SUBAGENT_POLICY, DEFAULT_SUBAGENT_TOOL_NAMES, DEFAULT_TOOL_INPUT_DIGEST_HEAD_CHARS, DELEGATION_TOOL_NAMES, DedupTracker, GuardrailPhase, HOOK_CONTRACT_VERSION, HOOK_NAMES, HookTimeoutError, InMemoryMemoryStore, JevProvider, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, PREOPEN_TOOL_CALL_PREFIX, ProviderError, SMALL_TIER_SKILL_POLICY, StructuredOutputManager, TOOL_INPUT_DIGEST_KEY, ToolBasedStrategy, VertexProvider, agentVerdictRule, appendBoundTools, auditLog, azureContentSafety, bedrockGuardrail, budgetGuard, buildHandoffTools, buildResponseFormatSection, buildSkillHeaderBlock, buildSubagentCatalogBlock, buildSystemPrompt, buildToolDefinitionsSection, canOpenSkill, canSpawn, captureSnapshot, checkpointing, codePresenceRule, coerceToolArgs, cognipeerGuardrail, cognipeerSink, collectAttachments, collectToolRetentionDeclarations, composeToolSets, compressDiffOutput, compressJsonOutput, compressLogOutput, compressSearchOutput, compressTextOutput, compressToolOutput, contentPartToText, contentToString, conversationHistory, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createAskUserQuestionTool, createBindSkillToolsTool, createCodeGuardrail, createContextPilotRuntime, createContextSummarizeNode, createContextTools, createDelegateTool, createGetToolResponseTool, createGuardrail, createGuardrailPlugin, createJsonGuardrail, createManageTodoListAliasTool, createManageTodoTool, createOpenSkillTool, createParallelTool, createPluginHost, createProvider, createRegexGuardrail, createResolverNode, createSearchSkillsTool, createSkillRegistryRef, createSkillTools, createSmartAgent, createSpawnTool, createSubagentRegistryRef, createSubagentTools, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customGuardrail, customSink, dedupeToolsByName, defaultTokenCounter, defineHook, definePlugin, describeContentPart, detectLanguage, detectTextFormat, detectVolatileContent, digestToolInputArguments, digestToolInputValue, emptyUsage, evaluateGuardrails, extractLatestUserQuery, extractMessageText, fileCheckpointStore, fileSink, findSubagent, formatZodError, fromLangchainModel, fromLangchainTools, fromNativeProvider, generateSpanId, generateTraceId, getModelCapabilities, getNestedValue, getResolvedSmartConfig, getTokenCounter, hasMediaPart, hasToolResponseRecoveryReference, httpGuardrail, httpSink, inMemoryCheckpointStore, inMemoryConversationStore, isHookImplemented, isOpenAIHostedEndpoint, isSmartModel, isValidIban, isValidLuhn, isValidTckn, jsonSchemaRule, defaultBuildPayload as langfuseDefaultPayload, langfuseTracing, languageGuard, loadSkillsFromDisk, mapConsoleHookVerdict, mapTextParts, mcp, mediaKindOf, mediaPolicy, normalizeSmartAgentOptions, normalizeVerdicts, openAIModeration, otelTracing, otlpSink, outputGuard, parseApprovalDecision, parseSkillFrontmatter, pathSandbox, piiRedaction, pluginCapabilities, portkeyGateway, portkeyGuardrail, preopenSkills, preopenToolCallId, prepareStrictToolMenu, promptInjectionGuard, rateLimit, readMemoryFacts, redactText, regexGuardrail, regexRule, replaceTextContent, resolveAvailableSkills, resolveAvailableSubagents, resolveInputRetention, resolveMemoryStore, resolveReasoning, resolveStrategy, resolveSubagentTools, resolveSummarizationRetention, resolveToolApprovalState, resolveUserQuestionState, responseCache, restoreSnapshot, restoreToolCalls, runSmartAgentEvalHarness, scoreItemsByRelevance, searchSkills, seedChildMessages, selectTopIndicesInOrder, sessionMetrics, setTokenCounter, startStreamingSession, summarizeObject, supportsAdaptiveThinking, textFromContent, toMessageUsage, toStrictCompatible, toStrictToolSchema, tokenize, toolPolicy, traceSessionToOtlp, validateReasoningConfig, validateToolArgs, webhookApproval, withTools, withinDepth, writeSummaryFactsToMemory };
19630
+ export { ASK_USER_TOOL_NAME, AnthropicProvider, AzureProvider, BaseProvider, BedrockProvider, CCRStore, CONSOLE_HOOK_MAP, CONTEXT_PILOT_JSON_MARKER_KEY, CONTROL_PLANE_TOOL_NAMES, DEFAULT_MAX_TOOL_INPUT_FIELD_CHARS, DEFAULT_PROFILE_CONFIGS, DEFAULT_SKILL_POLICY, DEFAULT_SUBAGENT_POLICY, DEFAULT_SUBAGENT_TOOL_NAMES, DEFAULT_TOOL_INPUT_DIGEST_HEAD_CHARS, DELEGATION_TOOL_NAMES, DedupTracker, GuardrailPhase, HOOK_CONTRACT_VERSION, HOOK_NAMES, HookTimeoutError, InMemoryMemoryStore, JevProvider, MAX_ADJUSTMENTS, MODEL_RULES, ModelRequestError, NativeJsonSchemaStrategy, OpenAICompatibleProvider, OpenAIProvider, PREOPEN_TOOL_CALL_PREFIX, ProviderError, SMALL_TIER_SKILL_POLICY, StructuredOutputManager, TOOL_INPUT_DIGEST_KEY, ToolBasedStrategy, VertexProvider, agentVerdictRule, allowsSampling, appendBoundTools, applyDropParams, asModelRequestError, auditLog, azureContentSafety, bedrockGuardrail, budgetGuard, buildHandoffTools, buildResponseFormatSection, buildSkillHeaderBlock, buildSubagentCatalogBlock, buildSystemPrompt, buildToolDefinitionsSection, canOpenSkill, canSpawn, captureSnapshot, checkpointing, clearLearnedModelRules, codePresenceRule, coerceToolArgs, cognipeerGuardrail, cognipeerSink, collectAttachments, collectToolRetentionDeclarations, composeToolSets, compressDiffOutput, compressJsonOutput, compressLogOutput, compressSearchOutput, compressTextOutput, compressToolOutput, contentPartToText, contentToString, conversationHistory, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createAskUserQuestionTool, createBindSkillToolsTool, createCodeGuardrail, createContextPilotRuntime, createContextSummarizeNode, createContextTools, createDelegateTool, createGetToolResponseTool, createGuardrail, createGuardrailPlugin, createJsonGuardrail, createManageTodoListAliasTool, createManageTodoTool, createOpenSkillTool, createParallelTool, createPluginHost, createProvider, createRegexGuardrail, createResolverNode, createSearchSkillsTool, createSkillRegistryRef, createSkillTools, createSmartAgent, createSpawnTool, createSubagentRegistryRef, createSubagentTools, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customGuardrail, customSink, dedupeToolsByName, defaultTokenCounter, defineHook, definePlugin, describeContentPart, detectLanguage, detectTextFormat, detectVolatileContent, digestToolInputArguments, digestToolInputValue, effortBudget, emptyUsage, evaluateGuardrails, extractLatestUserQuery, extractMessageText, fileCheckpointStore, fileSink, findSubagent, formatZodError, fromLangchainModel, fromLangchainTools, fromNativeProvider, generateSpanId, generateTraceId, getLearnedModelRulePatch, getModelCapabilities, getNestedValue, getResolvedSmartConfig, getTokenCounter, hasMediaPart, hasToolResponseRecoveryReference, httpGuardrail, httpSink, inMemoryCheckpointStore, inMemoryConversationStore, isHookImplemented, isOpenAIHostedEndpoint, isSmartModel, isValidIban, isValidLuhn, isValidTckn, jsonSchemaRule, defaultBuildPayload as langfuseDefaultPayload, langfuseTracing, languageGuard, listLearnedModelRules, loadSkillsFromDisk, mapConsoleHookVerdict, mapReasoningEffort, mapTextParts, mcp, mediaKindOf, mediaPolicy, mergeRulePatch, modelIdCandidates, normalizeSmartAgentOptions, normalizeVerdicts, openAIModeration, otelTracing, otlpSink, outputGuard, parseApprovalDecision, parseModelError, parseSkillFrontmatter, pathSandbox, piiRedaction, pluginCapabilities, portkeyGateway, portkeyGuardrail, preopenSkills, preopenToolCallId, prepareStrictToolMenu, promptInjectionGuard, rateLimit, readMemoryFacts, readTraceContinuation, redactText, regexGuardrail, regexRule, rememberModelRulePatch, replaceTextContent, resolveAvailableSkills, resolveAvailableSubagents, resolveInputRetention, resolveMemoryStore, resolveModelRule, resolveReasoning, resolveStrategy, resolveSubagentTools, resolveSummarizationRetention, resolveToolApprovalState, resolveUserQuestionState, responseCache, restoreSnapshot, restoreToolCalls, runSmartAgentEvalHarness, runWithModelAdjustments, scoreItemsByRelevance, searchSkills, seedChildMessages, selectTopIndicesInOrder, sessionMetrics, setTokenCounter, startStreamingSession, summarizeObject, supportsAdaptiveThinking, textFromContent, toMessageUsage, toStrictCompatible, toStrictToolSchema, tokenize, toolPolicy, traceContinuationOf, traceSessionToOtlp, validateReasoningConfig, validateToolArgs, webhookApproval, withTools, withinDepth, writeSummaryFactsToMemory };
18298
19631
  //# sourceMappingURL=index.mjs.map
18299
19632
  //# sourceMappingURL=index.mjs.map