@theokit/sdk 2.11.0 → 2.11.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.11.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8b411c5: Add `SendOptions.toolChoice` (`"auto" | "none" | "required"`), forwarded to the OpenAI/OpenRouter `tool_choice` request field. `"none"` forces a text answer even when the agent has tools registered — this lets an agent loop force a closing summary at its step ceiling (a cached agent's tools cannot be un-registered, so the gate must be applied per-send, not at agent creation). `tool_choice` is emitted only alongside a non-empty `tools` array. Additive and backward-compatible (absent ⇒ provider default).
8
+
9
+ ## 2.11.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 6893812: Forward `ModelSelection.params` reasoning to OpenRouter / OpenAI-compat providers (issue #47). The `thinking` param was silently dropped (model resolution kept only `model.id`; the request body had no `reasoning` field), so `Agent.send` never requested or surfaced reasoning. Now a `thinking` param maps to the reasoning request the target provider accepts — OpenRouter (and OpenAI-compatible passthroughs) use the unified `reasoning: { effort }` object, while native OpenAI Chat Completions uses the top-level `reasoning_effort` string (so opting into reasoning never 400s on api.openai.com). The streamed reasoning (`delta.reasoning`, or `delta.reasoning_content` on DeepSeek-direct / vLLM / LMStudio compat endpoints) is surfaced as `thinking-delta` `InteractionUpdate`s (live via `onDelta`) plus a `thinking` `SDKMessage` (replayed by `Run.stream`), on a separate channel from the visible answer. Validated end-to-end against `deepseek/deepseek-r1` via OpenRouter.
14
+
3
15
  ## 2.11.0
4
16
 
5
17
  ### Minor Changes
@@ -6889,6 +6889,14 @@ function buildAssistantEvent(inputs, text) {
6889
6889
  message: { role: "assistant", content: [{ type: "text", text }] }
6890
6890
  };
6891
6891
  }
6892
+ function buildThinkingEvent(inputs, text) {
6893
+ return {
6894
+ type: "thinking",
6895
+ agent_id: inputs.agentId,
6896
+ run_id: inputs.runId,
6897
+ text
6898
+ };
6899
+ }
6892
6900
  function buildAssistantTurn(text, toolCalls) {
6893
6901
  const content = [];
6894
6902
  if (text.length > 0) content.push({ type: "text", text });
@@ -7074,6 +7082,10 @@ ${additions}`;
7074
7082
  function toLlmTool(tool) {
7075
7083
  return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema };
7076
7084
  }
7085
+ function reasoningEffortFromParams(params) {
7086
+ const thinking = params?.find((p) => p.id === "thinking");
7087
+ return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
7088
+ }
7077
7089
  async function streamLlmTurn(inputs, ctx) {
7078
7090
  const llmSpan = inputs.telemetry?.startSpan("llm.call", {
7079
7091
  "model.id": inputs.model.id ?? "auto",
@@ -7090,6 +7102,15 @@ async function streamLlmTurn(inputs, ctx) {
7090
7102
  );
7091
7103
  return effective !== void 0 ? { system: effective } : {};
7092
7104
  })(),
7105
+ // issue #47: forward the reasoning effort from ModelSelection.params (the `thinking` param)
7106
+ // so the provider produces reasoning. Absent param ⇒ no `reasoning` field (bare request).
7107
+ ...(() => {
7108
+ const effort = reasoningEffortFromParams(inputs.model.params);
7109
+ return effort !== void 0 ? { reasoning: { effort } } : {};
7110
+ })(),
7111
+ // Step-cap force-close: forward the per-run tool gate (`"none"` on a ceiling round forces a
7112
+ // text close even though tools are advertised). Absent ⇒ provider default (auto).
7113
+ ...inputs.toolChoice !== void 0 ? { toolChoice: inputs.toolChoice } : {},
7093
7114
  messages: ctx.messages,
7094
7115
  tools: ctx.tools.map(toLlmTool)
7095
7116
  },
@@ -7158,6 +7179,7 @@ function registerLoopError(ctx, cause) {
7158
7179
  }
7159
7180
  async function runCollectorLoop(generator, inputs, ctx) {
7160
7181
  let accumulatedText = "";
7182
+ let reasoningText = "";
7161
7183
  let errored = false;
7162
7184
  let finishValue;
7163
7185
  while (true) {
@@ -7170,6 +7192,10 @@ async function runCollectorLoop(generator, inputs, ctx) {
7170
7192
  accumulatedText += next.value.text;
7171
7193
  await emitTextDeltaCallback(inputs, next.value.text);
7172
7194
  }
7195
+ if (next.value.type === "reasoning_delta") {
7196
+ reasoningText += next.value.text;
7197
+ await emitReasoningDeltaCallback(inputs, next.value.text);
7198
+ }
7173
7199
  if (next.value.type === "error") {
7174
7200
  registerLoopError(ctx, next.value);
7175
7201
  ctx.finalText = "";
@@ -7177,6 +7203,9 @@ async function runCollectorLoop(generator, inputs, ctx) {
7177
7203
  break;
7178
7204
  }
7179
7205
  }
7206
+ if (reasoningText.length > 0) {
7207
+ ctx.events.push(buildThinkingEvent(inputs, reasoningText));
7208
+ }
7180
7209
  return { accumulatedText, errored, finishValue };
7181
7210
  }
7182
7211
  async function emitTextDeltaCallback(inputs, text) {
@@ -7188,6 +7217,15 @@ async function emitTextDeltaCallback(inputs, text) {
7188
7217
  "SendOptions.onDelta"
7189
7218
  );
7190
7219
  }
7220
+ async function emitReasoningDeltaCallback(inputs, text) {
7221
+ if (inputs.onDelta === void 0) return;
7222
+ const cb = inputs.onDelta;
7223
+ await safeCall(
7224
+ () => cb({ update: { type: "thinking-delta", text } }),
7225
+ void 0,
7226
+ "SendOptions.onDelta"
7227
+ );
7228
+ }
7191
7229
  var init_loop_llm_stream = __esm({
7192
7230
  "src/internal/agent-loop/loop-llm-stream.ts"() {
7193
7231
  init_safe_call();
@@ -10072,7 +10110,22 @@ function mapOpenAIFinish(reason) {
10072
10110
  return "end_turn";
10073
10111
  }
10074
10112
  }
10075
- function buildOpenAIBody(request) {
10113
+ function applyReasoningRequest(body, effort, providerName) {
10114
+ if (providerName === "openai") {
10115
+ body.reasoning_effort = effort;
10116
+ return;
10117
+ }
10118
+ body.reasoning = { effort };
10119
+ }
10120
+ function applyToolsRequest(body, request) {
10121
+ if (request.tools === void 0 || request.tools.length === 0) return;
10122
+ body.tools = request.tools.map((tool) => ({
10123
+ type: "function",
10124
+ function: { name: tool.name, description: tool.description, parameters: tool.inputSchema }
10125
+ }));
10126
+ if (request.toolChoice !== void 0) body.tool_choice = request.toolChoice;
10127
+ }
10128
+ function buildOpenAIBody(request, providerName) {
10076
10129
  const messages = [];
10077
10130
  const systemText = openAISystemText(request.system);
10078
10131
  if (systemText.length > 0) {
@@ -10091,12 +10144,9 @@ function buildOpenAIBody(request) {
10091
10144
  };
10092
10145
  if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
10093
10146
  if (request.temperature !== void 0) body.temperature = request.temperature;
10094
- if (request.tools !== void 0 && request.tools.length > 0) {
10095
- body.tools = request.tools.map((tool) => ({
10096
- type: "function",
10097
- function: { name: tool.name, description: tool.description, parameters: tool.inputSchema }
10098
- }));
10099
- }
10147
+ if (request.reasoning?.effort !== void 0)
10148
+ applyReasoningRequest(body, request.reasoning.effort, providerName);
10149
+ applyToolsRequest(body, request);
10100
10150
  const responseFormat = encodeOpenAIResponseFormat(request.responseFormat);
10101
10151
  if (responseFormat !== void 0) body.response_format = responseFormat;
10102
10152
  return body;
@@ -10186,7 +10236,7 @@ var init_openai2 = __esm({
10186
10236
  method: "POST",
10187
10237
  signal,
10188
10238
  headers,
10189
- body: JSON.stringify(buildOpenAIBody(request))
10239
+ body: JSON.stringify(buildOpenAIBody(request, providerId))
10190
10240
  });
10191
10241
  } catch (fetchErr) {
10192
10242
  const mapped = mapOllamaTransportError({
@@ -10248,6 +10298,10 @@ var init_openai2 = __esm({
10248
10298
  const events = [];
10249
10299
  this.applyUsage(chunk.usage);
10250
10300
  for (const choice of chunk.choices ?? []) {
10301
+ const reasoningEvent = this.applyReasoningDelta(
10302
+ choice.delta?.reasoning ?? choice.delta?.reasoning_content
10303
+ );
10304
+ if (reasoningEvent !== void 0) events.push(reasoningEvent);
10251
10305
  const textEvent = this.applyContentDelta(choice.delta?.content);
10252
10306
  if (textEvent !== void 0) events.push(textEvent);
10253
10307
  this.mergeToolCallDeltas(choice.delta?.tool_calls);
@@ -10255,6 +10309,10 @@ var init_openai2 = __esm({
10255
10309
  }
10256
10310
  return events;
10257
10311
  }
10312
+ applyReasoningDelta(reasoning) {
10313
+ if (typeof reasoning !== "string" || reasoning.length === 0) return void 0;
10314
+ return { type: "reasoning_delta", text: reasoning };
10315
+ }
10258
10316
  applyUsage(usage) {
10259
10317
  if (usage?.prompt_tokens !== void 0) this.inputTokens = usage.prompt_tokens;
10260
10318
  if (usage?.completion_tokens !== void 0) this.outputTokens = usage.completion_tokens;
@@ -11144,7 +11202,12 @@ function buildLoopInputs(options, runId, userText) {
11144
11202
  return {
11145
11203
  agentId: options.agentId,
11146
11204
  runId,
11147
- model: { id: effectiveModelId },
11205
+ // issue #47: carry ModelSelection.params (the reasoning `thinking` param) into the loop so the
11206
+ // request can forward reasoning. The id is stripped/normalized above; params pass through as-is.
11207
+ model: {
11208
+ id: effectiveModelId,
11209
+ ...options.model?.params !== void 0 ? { params: options.model.params } : {}
11210
+ },
11148
11211
  userMessage: userText,
11149
11212
  llm,
11150
11213
  mcp: buildMcpMap(options),
@@ -11154,6 +11217,7 @@ function buildLoopInputs(options, runId, userText) {
11154
11217
  ...options.systemPrompt !== void 0 ? { systemPrompt: options.systemPrompt } : {},
11155
11218
  ...options.onStep !== void 0 ? { onStep: options.onStep } : {},
11156
11219
  ...options.onDelta !== void 0 ? { onDelta: options.onDelta } : {},
11220
+ ...options.sendOptions.toolChoice !== void 0 ? { toolChoice: options.sendOptions.toolChoice } : {},
11157
11221
  ...options.priorMessages !== void 0 ? { priorMessages: options.priorMessages } : {},
11158
11222
  ...options.memoryTools !== void 0 && options.memoryTools.length > 0 ? { memoryTools: options.memoryTools } : {},
11159
11223
  ...buildCustomToolsInput(