@kortyx/agent 0.20.0 → 0.20.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.20.1](https://github.com/kortyx-io/kortyx/compare/agent-v0.20.0...agent-v0.20.1) (2026-09-11)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @kortyx/providers bumped to 0.6.1
11
+ * @kortyx/runtime bumped to 0.15.2
12
+ * devDependencies
13
+ * @kortyx/hooks bumped to 0.23.1
14
+
3
15
  ## [0.20.0](https://github.com/kortyx-io/kortyx/compare/agent-v0.19.0...agent-v0.20.0) (2026-09-11)
4
16
 
5
17
 
package/dist/index.js CHANGED
@@ -432,6 +432,16 @@ var require_dist = __commonJS({
432
432
  ...typeof nodeId === "string" && nodeId.length > 0 ? { node: nodeId } : {}
433
433
  });
434
434
  };
435
+ var resolveProviderOptions = (defaults, overrides) => {
436
+ const providerOptions = defaults || overrides ? { ...defaults, ...overrides } : void 0;
437
+ for (const [key, value] of Object.entries(overrides ?? {})) {
438
+ const previous = defaults?.[key];
439
+ if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
440
+ if (providerOptions) providerOptions[key] = { ...previous, ...value };
441
+ }
442
+ }
443
+ return providerOptions;
444
+ };
435
445
  async function runReasonEngine(args) {
436
446
  let stream = args.stream ?? args.model.options?.streaming ?? true;
437
447
  const emit = args.emit ?? true;
@@ -441,14 +451,10 @@ var require_dist = __commonJS({
441
451
  const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
442
452
  const reasoning = args.reasoning ?? args.model.options?.reasoning;
443
453
  const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
444
- const defaults = args.model.options?.providerOptions;
445
- const providerOptions = defaults || args.providerOptions ? { ...defaults, ...args.providerOptions } : void 0;
446
- for (const [key, value] of Object.entries(args.providerOptions ?? {})) {
447
- const previous = defaults?.[key];
448
- if (previous && value && typeof previous === "object" && typeof value === "object" && !Array.isArray(previous) && !Array.isArray(value)) {
449
- if (providerOptions) providerOptions[key] = { ...previous, ...value };
450
- }
451
- }
454
+ const providerOptions = resolveProviderOptions(
455
+ args.model.options?.providerOptions,
456
+ args.providerOptions
457
+ );
452
458
  const tools = args.tools ?? args.model.options?.tools;
453
459
  (0, import_core22.throwIfExecutionAborted)(abortSignal);
454
460
  const model = args.model.provider.getModel(args.model.modelId, {
@@ -1861,6 +1867,7 @@ ${tryStringify2(args.response)}`
1861
1867
  const tools = useReasonArgs.tools ?? [];
1862
1868
  const toolByName = /* @__PURE__ */ new Map();
1863
1869
  let validationCompleted = false;
1870
+ let separateOutput = false;
1864
1871
  try {
1865
1872
  for (const tool of tools) {
1866
1873
  if (toolByName.has(tool.name)) {
@@ -1875,6 +1882,17 @@ ${tryStringify2(args.response)}`
1875
1882
  "useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
1876
1883
  );
1877
1884
  }
1885
+ separateOutput = Boolean(
1886
+ useReasonArgs.model.provider.getModel(useReasonArgs.model.modelId, {
1887
+ ...useReasonArgs.model.options,
1888
+ ...useReasonArgs.reasoning !== void 0 ? { reasoning: useReasonArgs.reasoning } : {},
1889
+ ...useReasonArgs.responseFormat ? { responseFormat: useReasonArgs.responseFormat } : {},
1890
+ providerOptions: resolveProviderOptions(
1891
+ useReasonArgs.model.options?.providerOptions,
1892
+ useReasonArgs.providerOptions
1893
+ ) ?? {}
1894
+ }).requiresSeparateStructuredOutput
1895
+ );
1878
1896
  validationCompleted = true;
1879
1897
  } finally {
1880
1898
  if (!validationCompleted) {
@@ -1882,6 +1900,8 @@ ${tryStringify2(args.response)}`
1882
1900
  }
1883
1901
  }
1884
1902
  const toolDefinitions = toToolDefinitions(tools);
1903
+ let finalizing = checkpoint?.finalizing ?? false;
1904
+ let completed = false;
1885
1905
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1886
1906
  const messages = checkpoint?.messages ?? createInitialMessages({
1887
1907
  system: useReasonArgs.system,
@@ -1916,6 +1936,7 @@ ${tryStringify2(args.response)}`
1916
1936
  messages,
1917
1937
  steps,
1918
1938
  approvedCalls,
1939
+ finalizing,
1919
1940
  ...pending ? { pending } : {}
1920
1941
  };
1921
1942
  ctx.stateDirty = true;
@@ -1939,11 +1960,12 @@ ${tryStringify2(args.response)}`
1939
1960
  const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
1940
1961
  const emit = useReasonArgs.emit ?? true;
1941
1962
  let textStarted = false;
1942
- const structuredChunk = useReasonArgs.outputSchema && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1963
+ const structuredChunk = useReasonArgs.outputSchema && (!separateOutput || finalizing) && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1943
1964
  const step = pending ?? await reasonEngine(
1944
1965
  {
1945
1966
  ...useReasonArgs,
1946
- tools: toolDefinitions,
1967
+ tools: finalizing ? [] : toolDefinitions,
1968
+ ...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
1947
1969
  messages,
1948
1970
  emit: false,
1949
1971
  stream,
@@ -1986,8 +2008,48 @@ ${tryStringify2(args.response)}`
1986
2008
  ...step.warnings ? { warnings: step.warnings } : {}
1987
2009
  });
1988
2010
  if (toolCalls.length === 0) {
2011
+ if (separateOutput && !finalizing) {
2012
+ if (args.allowValidatedToolOutput && useReasonArgs.outputSchema) {
2013
+ try {
2014
+ finalOutput = parseReasonOutputWithSchema({
2015
+ text: step.text,
2016
+ schema: useReasonArgs.outputSchema,
2017
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
2018
+ label: "useReason output"
2019
+ });
2020
+ aggregatedWarnings = mergeWarnings(aggregatedWarnings, [
2021
+ {
2022
+ type: "compatibility",
2023
+ feature: "responseFormat",
2024
+ details: "Reused locally validated tool-phase output without an extra native-schema request. Set responseFormat.schema explicitly to require provider schema enforcement."
2025
+ }
2026
+ ]);
2027
+ completed = true;
2028
+ break;
2029
+ } catch {
2030
+ }
2031
+ }
2032
+ messages.push({
2033
+ role: "assistant",
2034
+ content: step.text,
2035
+ ...step.continuation ? { continuation: step.continuation } : {}
2036
+ });
2037
+ messages.push({
2038
+ role: "user",
2039
+ content: "Return the final answer using the requested output schema and the tool results above."
2040
+ });
2041
+ finalizing = true;
2042
+ pending = void 0;
2043
+ save();
2044
+ continue;
2045
+ }
2046
+ completed = true;
1989
2047
  break;
1990
2048
  }
2049
+ if (finalizing)
2050
+ throw new Error(
2051
+ "Provider requested a tool during schema-only finalization."
2052
+ );
1991
2053
  if (!reused) {
1992
2054
  allToolCalls.push(...toolCalls);
1993
2055
  messages.push({
@@ -2131,26 +2193,26 @@ ${tryStringify2(args.response)}`
2131
2193
  message: result2.content
2132
2194
  });
2133
2195
  }
2134
- const completed = toolResults.at(-1);
2135
- if (completed)
2196
+ const completed2 = toolResults.at(-1);
2197
+ if (completed2)
2136
2198
  messages.push({
2137
2199
  role: "tool",
2138
- content: completed.content,
2139
- toolCallId: completed.toolCallId,
2140
- name: completed.name,
2141
- ...completed.isError !== void 0 ? { isError: completed.isError } : {}
2200
+ content: completed2.content,
2201
+ toolCallId: completed2.toolCallId,
2202
+ name: completed2.name,
2203
+ ...completed2.isError !== void 0 ? { isError: completed2.isError } : {}
2142
2204
  });
2143
2205
  save();
2144
2206
  }
2145
2207
  pending = void 0;
2146
2208
  save();
2147
2209
  }
2148
- if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
2210
+ if (!completed) {
2149
2211
  throw new Error(
2150
2212
  `useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
2151
2213
  );
2152
2214
  }
2153
- if (useReasonArgs.outputSchema) {
2215
+ if (useReasonArgs.outputSchema && finalOutput === void 0) {
2154
2216
  finalOutput = parseReasonOutputWithSchema({
2155
2217
  text: finalText,
2156
2218
  schema: useReasonArgs.outputSchema,
@@ -2232,6 +2294,9 @@ ${tryStringify2(args.response)}`
2232
2294
  args.model.options?.abortSignal
2233
2295
  );
2234
2296
  (0, import_core52.throwIfExecutionAborted)(abortSignal);
2297
+ const allowValidatedToolOutput = Boolean(
2298
+ args.outputSchema && !(args.responseFormat ?? args.model.options?.responseFormat)
2299
+ );
2235
2300
  const inferred = inferOutputFormat(
2236
2301
  args.outputSchema,
2237
2302
  args.responseFormat ?? args.model.options?.responseFormat
@@ -2284,6 +2349,7 @@ ${tryStringify2(args.response)}`
2284
2349
  if (args.tools?.length) {
2285
2350
  return runReasonToolLoop({
2286
2351
  useReasonArgs: args,
2352
+ allowValidatedToolOutput,
2287
2353
  checkpointKey,
2288
2354
  initialWarnings: inferred.warnings,
2289
2355
  ...id ? { id } : {},