@agentionai/agents 1.0.1 → 1.1.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/README.md CHANGED
@@ -418,6 +418,10 @@ Check out the [examples](https://github.com/laurentzuijdwijk/agention-lib/tree/m
418
418
  - RAG applications with vector search
419
419
  - Document ingestion and chunking
420
420
 
421
+ ## Built with Agention
422
+
423
+ - **[Marshall](https://marshall.agention.ai/)** — a coding agent for open weights. Runs a planner/coder/reviewer loop entirely on local hardware via llama.cpp or Ollama, no API key, account, or cloud required — with approval-gated file writes and shell commands, and support for mixing local and paid models across roles.
424
+
421
425
  ## Contributing
422
426
 
423
427
  Contributions are welcome! Please open an issue or submit a pull request.
@@ -35,7 +35,7 @@ type LlamaCppAgentConfig = Omit<BaseAgentConfig, "vendor" | "model"> & {
35
35
  };
36
36
  type AgentConfig = ClaudeAgentConfig | OpenAIAgentConfig | GeminiAgentConfig | MistralAgentConfig | OllamaAgentConfig | LlamaCppAgentConfig;
37
37
  export declare class Agent {
38
- static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | OpenAiAgent | MistralAgent | OllamaAgent | LlamaCppAgent;
38
+ static create(config: AgentConfig, history?: History): ClaudeAgent | GeminiAgent | MistralAgent | OllamaAgent | LlamaCppAgent | OpenAiAgent<(string & {}) | "gpt-5-pro" | "gpt-5.2-pro" | "gpt-5.4-pro" | "gpt-5.5-pro" | "o1" | "o1-pro" | "o3" | "o3-mini" | "o4-mini" | "gpt-5" | "gpt-5-mini" | "gpt-5-nano" | "gpt-5.1" | "gpt-5.2" | "gpt-5.4" | "gpt-5.4-mini" | "gpt-5.4-nano" | "gpt-5.5" | "gpt-5.6" | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna" | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini">;
39
39
  }
40
40
  export {};
41
41
  //# sourceMappingURL=Agent.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { Tool } from "../tools/Tool";
2
2
  import { BuiltInTool } from "../tools/BuiltInTool";
3
3
  import { BaseAgent } from "./BaseAgent";
4
+ import type { ReasoningEffort } from "./model-types";
4
5
  /** Supported LLM vendors */
5
6
  export type AgentVendor = "openai" | "anthropic" | "mistral" | "gemini" | "ollama" | "llamacpp";
6
7
  /**
@@ -84,13 +85,28 @@ export interface ClaudeSpecificConfig {
84
85
  */
85
86
  thinkingBudgetTokens?: number;
86
87
  }
88
+ /**
89
+ * How much the model should think before answering, for OpenAI reasoning models.
90
+ *
91
+ * Which values a given model accepts is **model-dependent**; see
92
+ * {@link ReasoningEffortFor} for the per-model set and
93
+ * {@link OPENAI_REASONING_SUPPORT} for the verified matrix.
94
+ */
95
+ export type { ReasoningEffort, ReasoningEffortFor } from "./model-types";
87
96
  /**
88
97
  * Vendor-specific configuration for OpenAI
89
98
  */
90
99
  export interface OpenAISpecificConfig {
91
100
  disableParallelToolUse?: boolean;
101
+ /**
102
+ * Ask for the least reasoning the configured model supports.
103
+ *
104
+ * Resolved per model family — there is no single "off" value. Has no effect on
105
+ * models that do not support `reasoning.effort` at all. Takes precedence over
106
+ * {@link OpenAISpecificConfig.reasoningEffort}.
107
+ */
92
108
  disableReasoning?: boolean;
93
- reasoningEffort?: "low" | "medium" | "high";
109
+ reasoningEffort?: ReasoningEffort;
94
110
  seed?: number;
95
111
  user?: string;
96
112
  }
@@ -39,5 +39,81 @@ export type LlamaCppModel = "default" | "gpt-oss-20b" | "gpt-oss-120b" | "llama-
39
39
  * You can also provide any custom string for newer models not yet listed.
40
40
  * @see https://platform.openai.com/docs/models
41
41
  */
42
- export type OpenAIModel = "gpt-5.2" | "gpt-5" | "gpt-4.1" | "gpt-5-mini" | "gpt-5-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1" | "o1-preview" | "o1-mini" | "o3-mini" | (string & {});
42
+ export type OpenAIModel = OpenAIReasoningModel | "gpt-4.1" | "gpt-4.1-mini" | "gpt-4.1-nano" | "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" | "gpt-4o-2024-05-13" | "gpt-4o-mini-2024-07-18" | "gpt-4-turbo" | "gpt-4-turbo-2024-04-09" | "gpt-4-turbo-preview" | "gpt-4-0125-preview" | "gpt-4-1106-preview" | "gpt-4" | "gpt-4-0613" | "gpt-3.5-turbo" | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-1106" | "o1-preview" | "o1-mini" | (string & {});
43
+ /**
44
+ * Every value the Responses API's `reasoning.effort` parameter defines.
45
+ *
46
+ * Which subset a given model accepts is model-dependent — see
47
+ * {@link OPENAI_REASONING_SUPPORT} and {@link ReasoningEffortFor}.
48
+ */
49
+ export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
50
+ /**
51
+ * Which reasoning efforts each OpenAI model accepts.
52
+ *
53
+ * There is no universal set and no universal "off" value: the families reject
54
+ * each other's minimum (`none` is rejected before `gpt-5.1`, `minimal` is
55
+ * rejected from `gpt-5.1` on, o-series takes neither), and `pro` variants drop
56
+ * the lower end. `effort: null` is not an off switch either — it means *unset*,
57
+ * so the model applies its own default.
58
+ *
59
+ * Each group's `efforts` are ordered lowest-first, so `efforts[0]` is the least
60
+ * reasoning that family will do.
61
+ *
62
+ * Every row was verified against the live Responses API on 2026-08-05. Models not
63
+ * listed here — non-reasoning models, and families released after this table was
64
+ * written — accept no `reasoning.effort` guess, so callers fall back to the full
65
+ * {@link ReasoningEffort} union and the runtime helper omits the parameter.
66
+ */
67
+ export declare const OPENAI_REASONING_SUPPORT: readonly [{
68
+ readonly models: readonly ["gpt-5-pro"];
69
+ readonly efforts: readonly ["high"];
70
+ }, {
71
+ readonly models: readonly ["gpt-5.2-pro", "gpt-5.4-pro", "gpt-5.5-pro"];
72
+ readonly efforts: readonly ["medium", "high", "xhigh"];
73
+ }, {
74
+ readonly models: readonly ["o1", "o1-pro", "o3", "o3-mini", "o4-mini"];
75
+ readonly efforts: readonly ["low", "medium", "high"];
76
+ }, {
77
+ readonly models: readonly ["gpt-5", "gpt-5-mini", "gpt-5-nano"];
78
+ readonly efforts: readonly ["minimal", "low", "medium", "high"];
79
+ }, {
80
+ readonly models: readonly ["gpt-5.1"];
81
+ readonly efforts: readonly ["none", "low", "medium", "high"];
82
+ }, {
83
+ readonly models: readonly ["gpt-5.2", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.5"];
84
+ readonly efforts: readonly ["none", "low", "medium", "high", "xhigh"];
85
+ }, {
86
+ readonly models: readonly ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
87
+ readonly efforts: readonly ["none", "low", "medium", "high", "xhigh", "max"];
88
+ }];
89
+ type ReasoningGroup = (typeof OPENAI_REASONING_SUPPORT)[number];
90
+ /** Every OpenAI model known to accept `reasoning.effort`. */
91
+ export type OpenAIReasoningModel = ReasoningGroup["models"][number];
92
+ /**
93
+ * Strip a dated snapshot suffix (`gpt-5-nano-2025-08-07` → `gpt-5-nano`) so
94
+ * pinned model ids resolve to the same support set as their alias. Snapshots
95
+ * always start `-20`, which keeps `gpt-5-mini` from looking like a snapshot of
96
+ * `gpt-5`.
97
+ */
98
+ type BaseModel<M extends string> = M extends `${infer Base}-20${string}` ? Base : M;
99
+ type EffortsOf<M extends string, G = ReasoningGroup> = G extends {
100
+ models: readonly (infer Models)[];
101
+ efforts: readonly (infer Efforts)[];
102
+ } ? BaseModel<M> extends Models ? Efforts : never : never;
103
+ /**
104
+ * The reasoning efforts a given model accepts.
105
+ *
106
+ * Resolves to the exact set for every model in {@link OPENAI_REASONING_SUPPORT},
107
+ * and to the full {@link ReasoningEffort} union for anything else — an unknown or
108
+ * newer model should not be blocked by a table that has gone stale.
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * type A = ReasoningEffortFor<"gpt-5-nano">; // "minimal" | "low" | "medium" | "high"
113
+ * type B = ReasoningEffortFor<"gpt-5.6-sol">; // adds "none", "xhigh", "max"; no "minimal"
114
+ * type C = ReasoningEffortFor<"gpt-5-pro">; // "high"
115
+ * ```
116
+ */
117
+ export type ReasoningEffortFor<M extends string> = [EffortsOf<M>] extends [never] ? ReasoningEffort : EffortsOf<M>;
118
+ export {};
43
119
  //# sourceMappingURL=model-types.d.ts.map
@@ -5,4 +5,37 @@
5
5
  * All types also accept custom string values for new/unlisted models.
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.OPENAI_REASONING_SUPPORT = void 0;
9
+ /**
10
+ * Which reasoning efforts each OpenAI model accepts.
11
+ *
12
+ * There is no universal set and no universal "off" value: the families reject
13
+ * each other's minimum (`none` is rejected before `gpt-5.1`, `minimal` is
14
+ * rejected from `gpt-5.1` on, o-series takes neither), and `pro` variants drop
15
+ * the lower end. `effort: null` is not an off switch either — it means *unset*,
16
+ * so the model applies its own default.
17
+ *
18
+ * Each group's `efforts` are ordered lowest-first, so `efforts[0]` is the least
19
+ * reasoning that family will do.
20
+ *
21
+ * Every row was verified against the live Responses API on 2026-08-05. Models not
22
+ * listed here — non-reasoning models, and families released after this table was
23
+ * written — accept no `reasoning.effort` guess, so callers fall back to the full
24
+ * {@link ReasoningEffort} union and the runtime helper omits the parameter.
25
+ */
26
+ exports.OPENAI_REASONING_SUPPORT = [
27
+ { models: ["gpt-5-pro"], efforts: ["high"] },
28
+ { models: ["gpt-5.2-pro", "gpt-5.4-pro", "gpt-5.5-pro"], efforts: ["medium", "high", "xhigh"] },
29
+ { models: ["o1", "o1-pro", "o3", "o3-mini", "o4-mini"], efforts: ["low", "medium", "high"] },
30
+ { models: ["gpt-5", "gpt-5-mini", "gpt-5-nano"], efforts: ["minimal", "low", "medium", "high"] },
31
+ { models: ["gpt-5.1"], efforts: ["none", "low", "medium", "high"] },
32
+ {
33
+ models: ["gpt-5.2", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.5"],
34
+ efforts: ["none", "low", "medium", "high", "xhigh"],
35
+ },
36
+ {
37
+ models: ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
38
+ efforts: ["none", "low", "medium", "high", "xhigh", "max"],
39
+ },
40
+ ];
8
41
  //# sourceMappingURL=model-types.js.map
@@ -1,18 +1,43 @@
1
1
  import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
2
2
  import { History, MessageContent } from "../../history/History";
3
3
  import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
4
- import { OpenAIModel } from "../model-types";
4
+ import { OpenAIModel, ReasoningEffort, ReasoningEffortFor } from "../model-types";
5
5
  import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
6
- type AgentConfig = BaseAgentConfig & {
6
+ type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
7
7
  apiKey: string;
8
- model?: OpenAIModel;
8
+ model?: M;
9
9
  maxTokens?: number;
10
10
  disableParallelToolUse?: boolean;
11
- /** Disable extended thinking/reasoning for models that support it (like gpt-5-nano) */
11
+ /**
12
+ * Ask for the least reasoning the configured model supports (e.g. `minimal` on
13
+ * `gpt-5-nano`, `none` on `gpt-5.6`). Takes precedence over `reasoningEffort`.
14
+ * No effect on models without reasoning support.
15
+ */
12
16
  disableReasoning?: boolean;
13
- reasoningEffort?: "low" | "medium" | "high";
17
+ /**
18
+ * How hard the model should think. Narrowed to the values the configured
19
+ * `model` actually accepts — `reasoningEffort: "none"` is a type error on
20
+ * `gpt-5-nano`, which takes `minimal` instead.
21
+ */
22
+ reasoningEffort?: ReasoningEffortFor<M>;
14
23
  user?: string;
15
24
  };
25
+ /**
26
+ * Lowest `reasoning.effort` the given model accepts, used to resolve
27
+ * `disableReasoning`. Returns `undefined` when the model has no reasoning to turn
28
+ * off, in which case the caller omits `reasoning` entirely rather than risk a 400
29
+ * — non-reasoning models such as `gpt-4.1-mini` reject the parameter outright.
30
+ *
31
+ * There is no single "off" value, and `effort: null` is not one either: it means
32
+ * *unset*, so the model falls back to its own default (`medium` on every family
33
+ * released before `gpt-5.1`).
34
+ *
35
+ * Reads {@link OPENAI_REASONING_SUPPORT}, the same table {@link ReasoningEffortFor}
36
+ * is derived from, so the compile-time and runtime views cannot disagree. Models
37
+ * missing from it — including newer families — return `undefined`; set
38
+ * `reasoningEffort` explicitly to override.
39
+ */
40
+ export declare function lowestReasoningEffort(model: string | undefined): ReasoningEffort | undefined;
16
41
  /**
17
42
  * Agent for OpenAI models using the Responses API.
18
43
  *
@@ -28,8 +53,14 @@ type AgentConfig = BaseAgentConfig & {
28
53
  * const response = await agent.execute("Hello!");
29
54
  * ```
30
55
  */
31
- export declare class OpenAiAgent extends BaseAgent {
56
+ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends BaseAgent {
32
57
  private client;
58
+ /**
59
+ * Resolved runtime config. Deliberately not narrowed by `M` — the constructor
60
+ * fills in defaults and merges `vendorConfig`, whose values are not
61
+ * model-scoped. Narrowing happens on the constructor's parameter, where the
62
+ * caller's model is known.
63
+ */
33
64
  protected config: Partial<AgentConfig>;
34
65
  /** Token usage from the last execution (for metrics tracking) */
35
66
  lastTokenUsage?: TokenUsage;
@@ -37,8 +68,25 @@ export declare class OpenAiAgent extends BaseAgent {
37
68
  private vizEventId?;
38
69
  /** Count of tool calls in current execution */
39
70
  private currentToolCallCount;
40
- constructor(config: Omit<AgentConfig, "vendor">, history?: History);
71
+ constructor(config: Omit<AgentConfig<M>, "vendor">, history?: History);
41
72
  protected getToolDefinitions(): Tool[];
73
+ /**
74
+ * Build the `reasoning` field for a Responses API request, as an object to
75
+ * spread into the request params.
76
+ *
77
+ * `disableReasoning` takes precedence over `reasoningEffort` and resolves to the
78
+ * lowest effort the configured model accepts (see {@link lowestReasoningEffort}).
79
+ * The field is omitted entirely when neither option applies — `reasoning: {}` is
80
+ * not the same as omitting it, and non-reasoning models reject the parameter.
81
+ *
82
+ * All three request sites go through here: they were copies of the same
83
+ * expression, and one drifted into overwriting the disable case with an
84
+ * unconditional `reasoning` key.
85
+ *
86
+ * @param summary Pass `"auto"` for streaming requests — the Responses API only
87
+ * emits `response.reasoning_summary_text.delta` events when it is set.
88
+ */
89
+ private buildReasoningParams;
42
90
  protected process(_input: string): Promise<string>;
43
91
  execute(input: string | MessageContent[]): Promise<string>;
44
92
  protected handleResponse(response: Response): Promise<string>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.OpenAiAgent = void 0;
7
+ exports.lowestReasoningEffort = lowestReasoningEffort;
7
8
  const openai_1 = __importDefault(require("openai"));
8
9
  const BaseAgent_1 = require("../BaseAgent");
9
10
  const AgentEvent_1 = require("../AgentEvent");
@@ -11,6 +12,30 @@ const AgentError_1 = require("../errors/AgentError");
11
12
  const transformers_1 = require("../../history/transformers");
12
13
  const VizReporter_1 = require("../../viz/VizReporter");
13
14
  const VizConfig_1 = require("../../viz/VizConfig");
15
+ const model_types_1 = require("../model-types");
16
+ /**
17
+ * Lowest `reasoning.effort` the given model accepts, used to resolve
18
+ * `disableReasoning`. Returns `undefined` when the model has no reasoning to turn
19
+ * off, in which case the caller omits `reasoning` entirely rather than risk a 400
20
+ * — non-reasoning models such as `gpt-4.1-mini` reject the parameter outright.
21
+ *
22
+ * There is no single "off" value, and `effort: null` is not one either: it means
23
+ * *unset*, so the model falls back to its own default (`medium` on every family
24
+ * released before `gpt-5.1`).
25
+ *
26
+ * Reads {@link OPENAI_REASONING_SUPPORT}, the same table {@link ReasoningEffortFor}
27
+ * is derived from, so the compile-time and runtime views cannot disagree. Models
28
+ * missing from it — including newer families — return `undefined`; set
29
+ * `reasoningEffort` explicitly to override.
30
+ */
31
+ function lowestReasoningEffort(model) {
32
+ if (!model)
33
+ return undefined;
34
+ // Snapshot ids (`gpt-5-nano-2025-08-07`) share their alias's support set.
35
+ const base = model.replace(/-20\d{2}-\d{2}-\d{2}$/, "");
36
+ const group = model_types_1.OPENAI_REASONING_SUPPORT.find((entry) => entry.models.includes(base));
37
+ return group?.efforts[0];
38
+ }
14
39
  /**
15
40
  * Agent for OpenAI models using the Responses API.
16
41
  *
@@ -78,6 +103,37 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
78
103
  };
79
104
  });
80
105
  }
106
+ /**
107
+ * Build the `reasoning` field for a Responses API request, as an object to
108
+ * spread into the request params.
109
+ *
110
+ * `disableReasoning` takes precedence over `reasoningEffort` and resolves to the
111
+ * lowest effort the configured model accepts (see {@link lowestReasoningEffort}).
112
+ * The field is omitted entirely when neither option applies — `reasoning: {}` is
113
+ * not the same as omitting it, and non-reasoning models reject the parameter.
114
+ *
115
+ * All three request sites go through here: they were copies of the same
116
+ * expression, and one drifted into overwriting the disable case with an
117
+ * unconditional `reasoning` key.
118
+ *
119
+ * @param summary Pass `"auto"` for streaming requests — the Responses API only
120
+ * emits `response.reasoning_summary_text.delta` events when it is set.
121
+ */
122
+ buildReasoningParams(summary) {
123
+ const effort = this.config.disableReasoning
124
+ ? lowestReasoningEffort(this.config.model)
125
+ : this.config.reasoningEffort;
126
+ if (!effort)
127
+ return {};
128
+ return {
129
+ reasoning: {
130
+ // The Responses API accepts "max" (verified on gpt-5.6), but the installed
131
+ // SDK's ReasoningEffort union predates it — cast at this one boundary.
132
+ effort: effort,
133
+ ...(summary ? { summary } : {}),
134
+ },
135
+ };
136
+ }
81
137
  async process(_input) {
82
138
  return "";
83
139
  }
@@ -120,8 +176,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
120
176
  top_p: this.config.topP,
121
177
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
122
178
  user: this.config.user,
123
- ...(this.config.disableReasoning && { reasoning: { effort: null } }),
124
- reasoning: { effort: this.config.reasoningEffort },
179
+ ...this.buildReasoningParams(),
125
180
  });
126
181
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, response);
127
182
  return await this.handleResponse(response);
@@ -242,13 +297,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
242
297
  top_p: this.config.topP,
243
298
  // Note: Responses API doesn't support seed, presence_penalty, frequency_penalty, stop
244
299
  user: this.config.user,
245
- ...(this.config.disableReasoning && {
246
- reasoning: { effort: null },
247
- }),
248
- ...(this.config.reasoningEffort &&
249
- !this.config.disableReasoning && {
250
- reasoning: { effort: this.config.reasoningEffort },
251
- }),
300
+ ...this.buildReasoningParams(),
252
301
  });
253
302
  this.emit(AgentEvent_1.AgentEvent.AFTER_EXECUTE, newResponse);
254
303
  return this.handleResponse(newResponse);
@@ -419,12 +468,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
419
468
  temperature: this.config.temperature,
420
469
  top_p: this.config.topP,
421
470
  user: this.config.user,
422
- ...(this.config.disableReasoning && { reasoning: { effort: null } }),
423
- ...(this.config.reasoningEffort && !this.config.disableReasoning && {
424
- // `summary: "auto"` is required for the Responses API to stream
425
- // `response.reasoning_summary_text.delta` events.
426
- reasoning: { effort: this.config.reasoningEffort, summary: "auto" },
427
- }),
471
+ ...this.buildReasoningParams("auto"),
428
472
  });
429
473
  let completedEvent = null;
430
474
  for await (const event of stream) {
@@ -323,6 +323,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
323
323
  ...this.buildExtraRequestParams(),
324
324
  });
325
325
  let textContent = "";
326
+ let reasoningContent = "";
326
327
  const toolCallAcc = new Map();
327
328
  let finishReason = null;
328
329
  for await (const chunk of stream) {
@@ -347,6 +348,10 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
347
348
  const deltaExtras = delta;
348
349
  const reasoningDelta = (deltaExtras.reasoning ?? deltaExtras.reasoning_content);
349
350
  if (reasoningDelta) {
351
+ // Accumulated as well as yielded: DeepSeek's thinking mode requires the
352
+ // assistant turn's reasoning to be replayed on the next request, so it
353
+ // has to reach history rather than only the caller.
354
+ reasoningContent += reasoningDelta;
350
355
  this.emit(AgentEvent_1.AgentEvent.REASONING_CHUNK, reasoningDelta);
351
356
  yield { type: "reasoning", content: reasoningDelta };
352
357
  }
@@ -389,6 +394,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
389
394
  role: "assistant",
390
395
  content: textContent || null,
391
396
  tool_calls: toolCalls,
397
+ reasoning_content: reasoningContent || null,
392
398
  });
393
399
  this.addToHistory(assistantEntry);
394
400
  const toolResults = await this.handleToolCalls(toolCalls);
@@ -401,6 +407,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
401
407
  const assistantEntry = transformers_1.chatCompletionsTransformer.fromProviderMessage({
402
408
  role: "assistant",
403
409
  content: textContent || null,
410
+ reasoning_content: reasoningContent || null,
404
411
  });
405
412
  this.addToHistory(assistantEntry);
406
413
  this.emit(AgentEvent_1.AgentEvent.DONE, { content: textContent }, this.lastTokenUsage);
@@ -182,6 +182,12 @@ type ChatCompletionMessage = {
182
182
  role: "assistant";
183
183
  content: string | null;
184
184
  tool_calls?: ChatCompletionToolCallParam[];
185
+ /**
186
+ * Reasoning replayed from a previous turn. Required by DeepSeek's thinking
187
+ * mode; accepted by OpenRouter as an alias for `reasoning`. Omitted
188
+ * entirely when the turn carried no reasoning.
189
+ */
190
+ reasoning_content?: string;
185
191
  } | {
186
192
  role: "tool";
187
193
  tool_call_id: string;
@@ -197,6 +203,10 @@ type ChatCompletionResponseMessage = {
197
203
  arguments: string;
198
204
  };
199
205
  }>;
206
+ /** Reasoning tokens as sent by OpenRouter. Not part of the OpenAI schema. */
207
+ reasoning?: string | null;
208
+ /** Reasoning tokens as sent by DeepSeek and llama.cpp. */
209
+ reasoning_content?: string | null;
200
210
  };
201
211
  export {};
202
212
  //# sourceMappingURL=transformers.d.ts.map
@@ -614,6 +614,7 @@ exports.chatCompletionsTransformer = {
614
614
  const textBlocks = entry.content.filter(types_1.isTextContent);
615
615
  const toolUseBlocks = entry.content.filter(types_1.isToolUseContent);
616
616
  const toolResultBlocks = entry.content.filter(types_1.isToolResultContent);
617
+ const thinkingBlocks = entry.content.filter(types_1.isThinkingContent);
617
618
  const imageUrlBlocks = entry.content.filter(types_1.isImageUrlContent);
618
619
  const imageBase64Blocks = entry.content.filter(types_1.isImageBase64Content);
619
620
  const hasImages = imageUrlBlocks.length > 0 || imageBase64Blocks.length > 0;
@@ -626,6 +627,20 @@ exports.chatCompletionsTransformer = {
626
627
  role: "assistant",
627
628
  content: textBlocks.map((c) => c.text).join("\n") || null,
628
629
  };
630
+ // DeepSeek's thinking mode rejects a conversation whose assistant turns
631
+ // dropped their reasoning ("The reasoning_content in the thinking mode
632
+ // must be passed back to the API"), so it has to survive the round trip.
633
+ // `reasoning_content` is DeepSeek's field name and an accepted alias for
634
+ // `reasoning` on OpenRouter. Only set it when there is something to send:
635
+ // servers that reject unknown fields must not start seeing it, and
636
+ // redacted-only blocks (Anthropic) carry no text to replay.
637
+ const reasoning = thinkingBlocks
638
+ .map((block) => block.thinking)
639
+ .filter((thought) => thought.length > 0)
640
+ .join("\n");
641
+ if (reasoning) {
642
+ msg.reasoning_content = reasoning;
643
+ }
629
644
  if (toolUseBlocks.length > 0) {
630
645
  msg.tool_calls = toolUseBlocks.map((block) => ({
631
646
  id: block.id,
@@ -684,6 +699,15 @@ exports.chatCompletionsTransformer = {
684
699
  */
685
700
  fromProviderMessage(message) {
686
701
  const content = [];
702
+ // Reasoning first, matching the order the model produced it in. Servers
703
+ // disagree on the field name — OpenRouter sends `reasoning`, DeepSeek and
704
+ // llama.cpp send `reasoning_content` — so accept either, preferring
705
+ // `reasoning` as the streaming path does. Stored as the neutral thinking
706
+ // block the history layer already round-trips for Anthropic.
707
+ const reasoning = message.reasoning ?? message.reasoning_content;
708
+ if (typeof reasoning === "string" && reasoning) {
709
+ content.push((0, types_1.thinking)(reasoning));
710
+ }
687
711
  if (typeof message.content === "string" && message.content) {
688
712
  content.push((0, types_1.text)(message.content));
689
713
  }