@kortyx/hooks 0.23.0 → 0.23.1

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.23.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.23.0...hooks-v0.23.1) (2026-09-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **providers:** preserve reasoning across tool workflows ([#169](https://github.com/kortyx-io/kortyx/issues/169)) ([6e4e403](https://github.com/kortyx-io/kortyx/commit/6e4e403d104d83586e05aab799510bc4c35b0f49))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @kortyx/providers bumped to 0.6.1
16
+
3
17
  ## [0.23.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.22.0...hooks-v0.23.0) (2026-09-11)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -426,6 +426,16 @@ var emitNodeEvent = (emitEvent, nodeId, event, payload) => {
426
426
  ...typeof nodeId === "string" && nodeId.length > 0 ? { node: nodeId } : {}
427
427
  });
428
428
  };
429
+ var resolveProviderOptions = (defaults, overrides) => {
430
+ const providerOptions = defaults || overrides ? { ...defaults, ...overrides } : void 0;
431
+ for (const [key, value] of Object.entries(overrides ?? {})) {
432
+ const previous = defaults?.[key];
433
+ if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
434
+ if (providerOptions) providerOptions[key] = { ...previous, ...value };
435
+ }
436
+ }
437
+ return providerOptions;
438
+ };
429
439
  async function runReasonEngine(args) {
430
440
  let stream = args.stream ?? args.model.options?.streaming ?? true;
431
441
  const emit = args.emit ?? true;
@@ -435,14 +445,10 @@ async function runReasonEngine(args) {
435
445
  const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
436
446
  const reasoning = args.reasoning ?? args.model.options?.reasoning;
437
447
  const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
438
- const defaults = args.model.options?.providerOptions;
439
- const providerOptions = defaults || args.providerOptions ? { ...defaults, ...args.providerOptions } : void 0;
440
- for (const [key, value] of Object.entries(args.providerOptions ?? {})) {
441
- const previous = defaults?.[key];
442
- if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
443
- if (providerOptions) providerOptions[key] = { ...previous, ...value };
444
- }
445
- }
448
+ const providerOptions = resolveProviderOptions(
449
+ args.model.options?.providerOptions,
450
+ args.providerOptions
451
+ );
446
452
  const tools = args.tools ?? args.model.options?.tools;
447
453
  (0, import_core2.throwIfExecutionAborted)(abortSignal);
448
454
  const model = args.model.provider.getModel(args.model.modelId, {
@@ -1871,6 +1877,7 @@ var runReasonToolLoop = async (args) => {
1871
1877
  const tools = useReasonArgs.tools ?? [];
1872
1878
  const toolByName = /* @__PURE__ */ new Map();
1873
1879
  let validationCompleted = false;
1880
+ let separateOutput = false;
1874
1881
  try {
1875
1882
  for (const tool of tools) {
1876
1883
  if (toolByName.has(tool.name)) {
@@ -1885,6 +1892,17 @@ var runReasonToolLoop = async (args) => {
1885
1892
  "useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
1886
1893
  );
1887
1894
  }
1895
+ separateOutput = Boolean(
1896
+ useReasonArgs.model.provider.getModel(useReasonArgs.model.modelId, {
1897
+ ...useReasonArgs.model.options,
1898
+ ...useReasonArgs.reasoning !== void 0 ? { reasoning: useReasonArgs.reasoning } : {},
1899
+ ...useReasonArgs.responseFormat ? { responseFormat: useReasonArgs.responseFormat } : {},
1900
+ providerOptions: resolveProviderOptions(
1901
+ useReasonArgs.model.options?.providerOptions,
1902
+ useReasonArgs.providerOptions
1903
+ ) ?? {}
1904
+ }).requiresSeparateStructuredOutput
1905
+ );
1888
1906
  validationCompleted = true;
1889
1907
  } finally {
1890
1908
  if (!validationCompleted) {
@@ -1892,6 +1910,8 @@ var runReasonToolLoop = async (args) => {
1892
1910
  }
1893
1911
  }
1894
1912
  const toolDefinitions = toToolDefinitions(tools);
1913
+ let finalizing = checkpoint?.finalizing ?? false;
1914
+ let completed = false;
1895
1915
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1896
1916
  const messages = checkpoint?.messages ?? createInitialMessages({
1897
1917
  system: useReasonArgs.system,
@@ -1926,6 +1946,7 @@ var runReasonToolLoop = async (args) => {
1926
1946
  messages,
1927
1947
  steps,
1928
1948
  approvedCalls,
1949
+ finalizing,
1929
1950
  ...pending ? { pending } : {}
1930
1951
  };
1931
1952
  ctx.stateDirty = true;
@@ -1949,11 +1970,12 @@ var runReasonToolLoop = async (args) => {
1949
1970
  const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
1950
1971
  const emit = useReasonArgs.emit ?? true;
1951
1972
  let textStarted = false;
1952
- const structuredChunk = useReasonArgs.outputSchema && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1973
+ const structuredChunk = useReasonArgs.outputSchema && (!separateOutput || finalizing) && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1953
1974
  const step = pending ?? await reasonEngine(
1954
1975
  {
1955
1976
  ...useReasonArgs,
1956
- tools: toolDefinitions,
1977
+ tools: finalizing ? [] : toolDefinitions,
1978
+ ...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
1957
1979
  messages,
1958
1980
  emit: false,
1959
1981
  stream,
@@ -1996,8 +2018,48 @@ var runReasonToolLoop = async (args) => {
1996
2018
  ...step.warnings ? { warnings: step.warnings } : {}
1997
2019
  });
1998
2020
  if (toolCalls.length === 0) {
2021
+ if (separateOutput && !finalizing) {
2022
+ if (args.allowValidatedToolOutput && useReasonArgs.outputSchema) {
2023
+ try {
2024
+ finalOutput = parseReasonOutputWithSchema({
2025
+ text: step.text,
2026
+ schema: useReasonArgs.outputSchema,
2027
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
2028
+ label: "useReason output"
2029
+ });
2030
+ aggregatedWarnings = mergeWarnings(aggregatedWarnings, [
2031
+ {
2032
+ type: "compatibility",
2033
+ feature: "responseFormat",
2034
+ details: "Reused locally validated tool-phase output without an extra native-schema request. Set responseFormat.schema explicitly to require provider schema enforcement."
2035
+ }
2036
+ ]);
2037
+ completed = true;
2038
+ break;
2039
+ } catch {
2040
+ }
2041
+ }
2042
+ messages.push({
2043
+ role: "assistant",
2044
+ content: step.text,
2045
+ ...step.continuation ? { continuation: step.continuation } : {}
2046
+ });
2047
+ messages.push({
2048
+ role: "user",
2049
+ content: "Return the final answer using the requested output schema and the tool results above."
2050
+ });
2051
+ finalizing = true;
2052
+ pending = void 0;
2053
+ save();
2054
+ continue;
2055
+ }
2056
+ completed = true;
1999
2057
  break;
2000
2058
  }
2059
+ if (finalizing)
2060
+ throw new Error(
2061
+ "Provider requested a tool during schema-only finalization."
2062
+ );
2001
2063
  if (!reused) {
2002
2064
  allToolCalls.push(...toolCalls);
2003
2065
  messages.push({
@@ -2141,26 +2203,26 @@ var runReasonToolLoop = async (args) => {
2141
2203
  message: result2.content
2142
2204
  });
2143
2205
  }
2144
- const completed = toolResults.at(-1);
2145
- if (completed)
2206
+ const completed2 = toolResults.at(-1);
2207
+ if (completed2)
2146
2208
  messages.push({
2147
2209
  role: "tool",
2148
- content: completed.content,
2149
- toolCallId: completed.toolCallId,
2150
- name: completed.name,
2151
- ...completed.isError !== void 0 ? { isError: completed.isError } : {}
2210
+ content: completed2.content,
2211
+ toolCallId: completed2.toolCallId,
2212
+ name: completed2.name,
2213
+ ...completed2.isError !== void 0 ? { isError: completed2.isError } : {}
2152
2214
  });
2153
2215
  save();
2154
2216
  }
2155
2217
  pending = void 0;
2156
2218
  save();
2157
2219
  }
2158
- if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
2220
+ if (!completed) {
2159
2221
  throw new Error(
2160
2222
  `useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
2161
2223
  );
2162
2224
  }
2163
- if (useReasonArgs.outputSchema) {
2225
+ if (useReasonArgs.outputSchema && finalOutput === void 0) {
2164
2226
  finalOutput = parseReasonOutputWithSchema({
2165
2227
  text: finalText,
2166
2228
  schema: useReasonArgs.outputSchema,
@@ -2244,6 +2306,9 @@ async function useReason(args) {
2244
2306
  args.model.options?.abortSignal
2245
2307
  );
2246
2308
  (0, import_core5.throwIfExecutionAborted)(abortSignal);
2309
+ const allowValidatedToolOutput = Boolean(
2310
+ args.outputSchema && !(args.responseFormat ?? args.model.options?.responseFormat)
2311
+ );
2247
2312
  const inferred = inferOutputFormat(
2248
2313
  args.outputSchema,
2249
2314
  args.responseFormat ?? args.model.options?.responseFormat
@@ -2296,6 +2361,7 @@ async function useReason(args) {
2296
2361
  if (args.tools?.length) {
2297
2362
  return runReasonToolLoop({
2298
2363
  useReasonArgs: args,
2364
+ allowValidatedToolOutput,
2299
2365
  checkpointKey,
2300
2366
  initialWarnings: inferred.warnings,
2301
2367
  ...id ? { id } : {},