@kortyx/agent 0.19.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/dist/index.js CHANGED
@@ -157,7 +157,7 @@ var require_dist = __commonJS({
157
157
  if (!usage) return void 0;
158
158
  const input = usage.input ?? 0;
159
159
  const output = usage.output ?? 0;
160
- const total = usage.total ?? input + output + (usage.reasoning ?? 0);
160
+ const total = usage.total ?? input + output + (usage.outputIncludesReasoning ? 0 : usage.reasoning ?? 0);
161
161
  if (input === 0 && output === 0 && total === 0) {
162
162
  return void 0;
163
163
  }
@@ -432,8 +432,18 @@ 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
- const stream = args.stream ?? args.model.options?.streaming ?? true;
446
+ let stream = args.stream ?? args.model.options?.streaming ?? true;
437
447
  const emit = args.emit ?? true;
438
448
  const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
439
449
  const maxOutputTokens = args.maxOutputTokens ?? args.model.options?.maxOutputTokens;
@@ -441,7 +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 providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
454
+ const providerOptions = resolveProviderOptions(
455
+ args.model.options?.providerOptions,
456
+ args.providerOptions
457
+ );
445
458
  const tools = args.tools ?? args.model.options?.tools;
446
459
  (0, import_core22.throwIfExecutionAborted)(abortSignal);
447
460
  const model = args.model.provider.getModel(args.model.modelId, {
@@ -455,6 +468,7 @@ var require_dist = __commonJS({
455
468
  ...tools !== void 0 ? { tools } : {},
456
469
  ...providerOptions !== void 0 ? { providerOptions } : {}
457
470
  });
471
+ if (tools?.length && !model.supportsToolStreaming) stream = false;
458
472
  const messages = args.messages !== void 0 ? args.messages : [
459
473
  ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
460
474
  { role: "user", content: String(args.input ?? "") }
@@ -482,7 +496,9 @@ var require_dist = __commonJS({
482
496
  },
483
497
  telemetry: {
484
498
  ...args.telemetry ?? {},
485
- input: args.telemetry?.input ?? messages
499
+ input: args.telemetry?.input ?? messages.map(
500
+ ({ continuation: _continuation, raw: _raw, ...message }) => message
501
+ )
486
502
  }
487
503
  });
488
504
  const modelRequestStartedAt = Date.now();
@@ -513,7 +529,11 @@ var require_dist = __commonJS({
513
529
  });
514
530
  };
515
531
  const failTrace = (span, error) => {
532
+ const detail = error;
516
533
  span?.fail?.(error, {
534
+ ...detail?.usage ? { usage: detail.usage } : {},
535
+ ...detail?.finishReason ? { finishReason: detail.finishReason } : {},
536
+ ...detail?.providerMetadata ? { providerMetadata: detail.providerMetadata } : {},
517
537
  attributes: {
518
538
  providerId: args.model.provider.id,
519
539
  modelId: args.model.modelId
@@ -522,6 +542,7 @@ var require_dist = __commonJS({
522
542
  };
523
543
  try {
524
544
  if (stream) {
545
+ let continuation;
525
546
  let final = "";
526
547
  let raw;
527
548
  let usage;
@@ -560,6 +581,7 @@ var require_dist = __commonJS({
560
581
  break;
561
582
  }
562
583
  case "finish": {
584
+ continuation = chunk.continuation;
563
585
  if (chunk.raw !== void 0) {
564
586
  raw = chunk.raw;
565
587
  }
@@ -608,6 +630,7 @@ var require_dist = __commonJS({
608
630
  }
609
631
  const result2 = {
610
632
  text: final,
633
+ ...continuation ? { continuation } : {},
611
634
  ...raw !== void 0 ? { raw } : {},
612
635
  ...usage !== void 0 ? { usage } : {},
613
636
  ...finishReason !== void 0 ? { finishReason } : {},
@@ -621,6 +644,7 @@ var require_dist = __commonJS({
621
644
  }
622
645
  const response = await model.invoke(messages);
623
646
  (0, import_core22.throwIfExecutionAborted)(abortSignal);
647
+ if (response.content) args.onTextChunk?.(response.content);
624
648
  if (emit) {
625
649
  emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
626
650
  ...commonMeta
@@ -637,6 +661,7 @@ var require_dist = __commonJS({
637
661
  }
638
662
  const result = {
639
663
  text: response.content,
664
+ ...response.continuation ? { continuation: response.continuation } : {},
640
665
  ...response.raw !== void 0 ? { raw: response.raw } : {},
641
666
  ...response.usage !== void 0 ? { usage: response.usage } : {},
642
667
  ...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
@@ -668,472 +693,184 @@ var require_dist = __commonJS({
668
693
  );
669
694
  (0, import_core32.throwIfExecutionAborted)(abortSignal);
670
695
  ctx.node.consumeExecution?.("maxModelPasses");
671
- return runReasonEngine({
672
- model: args.model,
673
- input: inputOverride ?? args.input,
674
- system: args.system,
675
- temperature: args.temperature,
676
- maxOutputTokens: args.maxOutputTokens,
677
- stopSequences: args.stopSequences,
678
- abortSignal,
679
- reasoning: args.reasoning,
680
- responseFormat: args.responseFormat,
681
- providerOptions: args.providerOptions,
682
- tools: args.tools,
683
- messages: args.messages,
684
- defaultTemperature: ctx.node.config?.model?.temperature,
685
- stream: args.stream,
686
- emit: args.emit,
687
- onTextChunk: args.onTextChunk,
688
- telemetry: args.telemetry,
689
- nodeId: ctx.node.graph.node,
690
- emitEvent: ctx.node.emit,
691
- reasonTrace: ctx.reasonTrace,
692
- id: meta.id,
693
- opId: meta.opId
694
- });
695
- }
696
- var tryJsonParse = (value) => {
697
696
  try {
698
- return { ok: true, value: JSON.parse(value) };
697
+ return await runReasonEngine({
698
+ model: args.model,
699
+ input: inputOverride ?? args.input,
700
+ system: args.system,
701
+ temperature: args.temperature,
702
+ maxOutputTokens: args.maxOutputTokens,
703
+ stopSequences: args.stopSequences,
704
+ abortSignal,
705
+ reasoning: args.reasoning,
706
+ responseFormat: args.responseFormat,
707
+ providerOptions: args.providerOptions,
708
+ tools: args.tools,
709
+ messages: args.messages,
710
+ defaultTemperature: ctx.node.config?.model?.temperature,
711
+ stream: args.stream,
712
+ emit: args.emit,
713
+ onTextChunk: args.onTextChunk,
714
+ telemetry: args.telemetry,
715
+ nodeId: ctx.node.graph.node,
716
+ emitEvent: ctx.node.emit,
717
+ reasonTrace: ctx.reasonTrace,
718
+ id: meta.id,
719
+ opId: meta.opId
720
+ });
699
721
  } catch (error) {
700
- return {
701
- ok: false,
702
- error: error instanceof Error ? error.message : String(error)
703
- };
722
+ const usage = error?.usage;
723
+ if (usage) accumulateTokenUsage(usage);
724
+ throw error;
704
725
  }
705
- };
706
- var extractJsonCodeBlock = (value) => {
707
- const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
708
- if (!match || typeof match[1] !== "string") return null;
709
- const inner = match[1].trim();
710
- return inner.length > 0 ? inner : null;
711
- };
712
- var tryStringify = (value) => {
713
- if (typeof value === "string") return value;
726
+ }
727
+ var import_zod22 = require("zod");
728
+ function inferOutputFormat(schema, explicit) {
729
+ if (explicit) return { responseFormat: explicit };
730
+ if (!schema) return {};
714
731
  try {
715
- return JSON.stringify(value, null, 2);
732
+ const json = (0, import_zod22.toJSONSchema)(schema);
733
+ const clean = (value) => {
734
+ if (Array.isArray(value)) return value.map(clean);
735
+ if (!value || typeof value !== "object") return value;
736
+ const object = value;
737
+ if ([
738
+ "allOf",
739
+ "not",
740
+ "dependentRequired",
741
+ "dependentSchemas",
742
+ "if",
743
+ "then",
744
+ "else",
745
+ "prefixItems"
746
+ ].some((key) => key in object))
747
+ throw new Error(
748
+ "Schema composition requires an explicit provider schema"
749
+ );
750
+ if (object.type === "object") {
751
+ if (object.additionalProperties !== false)
752
+ throw new Error(
753
+ "Dynamic properties require an explicit provider schema"
754
+ );
755
+ const properties = object.properties;
756
+ const required = object.required;
757
+ if (properties && Object.keys(properties).some((key) => !required?.includes(key))) {
758
+ throw new Error(
759
+ "Optional properties require an explicit provider schema"
760
+ );
761
+ }
762
+ }
763
+ return Object.fromEntries(
764
+ Object.entries(object).filter(([key]) => key !== "$schema" && key !== "default").map(([key, child]) => {
765
+ if ((key === "properties" || key === "$defs" || key === "definitions") && child && typeof child === "object")
766
+ return [
767
+ key,
768
+ Object.fromEntries(
769
+ Object.entries(child).map(([name, definition]) => [
770
+ name,
771
+ clean(definition)
772
+ ])
773
+ )
774
+ ];
775
+ return [
776
+ key,
777
+ key === "enum" || key === "const" ? child : clean(child)
778
+ ];
779
+ })
780
+ );
781
+ };
782
+ if (json.type !== "object")
783
+ throw new Error("The provider schema must have an object root");
784
+ return { responseFormat: { type: "json", schema: clean(json) } };
716
785
  } catch {
717
- return String(value);
786
+ return {
787
+ responseFormat: { type: "json" },
788
+ warnings: [
789
+ {
790
+ type: "compatibility",
791
+ feature: "outputSchema",
792
+ details: "This validator cannot be inferred as a strict provider JSON schema. Kortyx still validates the final output locally. Supply responseFormat.schema to enforce a wire schema."
793
+ }
794
+ ]
795
+ };
718
796
  }
719
- };
720
- var isRecord22 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
721
- var resolveOutputCandidate = (text) => {
722
- const trimmed = text.trim();
723
- if (!trimmed) return text;
724
- const direct = tryJsonParse(trimmed);
725
- if (direct.ok) return direct.value;
726
- const block = extractJsonCodeBlock(trimmed);
727
- if (block) {
728
- const parsedBlock = tryJsonParse(block);
729
- if (parsedBlock.ok) return parsedBlock.value;
797
+ }
798
+ var resolveAppendFieldPaths = (cfg) => {
799
+ const fields = cfg?.fields;
800
+ if (!fields) return [];
801
+ const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
802
+ for (const path of appendPaths) {
803
+ assertStructuredPath(path, "useReason structured append");
730
804
  }
731
- return text;
732
- };
733
- var looksLikeStructuredJsonAttempt = (value) => {
734
- const trimmed = value.trim();
735
- if (!trimmed) return false;
736
- return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
805
+ return appendPaths;
737
806
  };
738
- var isLikelyTruncatedStructuredOutput = (text) => {
739
- const trimmed = text.trim();
740
- if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
741
- const direct = tryJsonParse(trimmed);
742
- if (!direct.ok && direct.error) return true;
743
- const block = extractJsonCodeBlock(trimmed);
744
- if (!block) return false;
745
- const parsedBlock = tryJsonParse(block);
746
- return !parsedBlock.ok && Boolean(parsedBlock.error);
807
+ var resolveSetFieldPaths = (cfg) => {
808
+ const fields = cfg?.fields;
809
+ if (!fields) return [];
810
+ const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
811
+ for (const path of setPaths) {
812
+ assertStructuredPath(path, "useReason structured set");
813
+ }
814
+ return setPaths;
747
815
  };
748
- var createTruncatedStructuredOutputError = (label) => new Error(
749
- `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
750
- );
751
- var createInvalidStructuredOutputError = (label) => new Error(
752
- `${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. This often happens when output is truncated or the provider does not follow JSON mode. Increase maxOutputTokens or simplify the requested output.`
753
- );
754
- var parseReasonOutputWithSchema = (args) => {
755
- const candidate = resolveOutputCandidate(args.text);
756
- try {
757
- return parseWithSchema(args.schema, candidate, args.label);
758
- } catch (error) {
759
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
760
- throw createTruncatedStructuredOutputError(args.label);
761
- }
762
- if (typeof candidate === "string" && candidate.trim().length > 0) {
763
- throw createInvalidStructuredOutputError(args.label);
764
- }
765
- throw error;
816
+ var resolveTextDeltaFieldPaths = (cfg) => {
817
+ const fields = cfg?.fields;
818
+ if (!fields) return [];
819
+ const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
820
+ for (const path of textPaths) {
821
+ assertStructuredPath(path, "useReason structured text-delta");
766
822
  }
823
+ return textPaths;
767
824
  };
768
- var parseInterruptFirstPassResult = (args) => {
769
- const candidate = resolveOutputCandidate(args.text);
770
- if (!isRecord22(candidate)) {
771
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
772
- throw createTruncatedStructuredOutputError("useReason output");
825
+ var extractCompletedStringValueAt = (args) => {
826
+ let value = "";
827
+ for (let i = args.start; i < args.text.length; i += 1) {
828
+ const ch = args.text[i];
829
+ if (ch === void 0) break;
830
+ if (ch === '"') {
831
+ return {
832
+ complete: true,
833
+ value
834
+ };
773
835
  }
774
- if (typeof candidate === "string" && candidate.trim().length > 0) {
775
- throw createInvalidStructuredOutputError("useReason output");
836
+ if (ch !== "\\") {
837
+ value += ch;
838
+ continue;
776
839
  }
777
- throw new Error(
778
- "useReason first pass with interrupt must return a JSON object."
779
- );
780
- }
781
- const mode = args.mode ?? "required";
782
- let interruptRequired = true;
783
- if (mode === "optional") {
784
- if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
785
- throw new Error(
786
- 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
787
- );
840
+ const next = args.text[i + 1];
841
+ if (!next) {
842
+ return {
843
+ complete: false
844
+ };
788
845
  }
789
- interruptRequired = candidate.decision === "interrupt";
790
- if (!interruptRequired) {
791
- const hasUnexpectedRequest = [
792
- candidate.interruptRequest,
793
- candidate.request,
794
- candidate.interrupt
795
- ].some((value) => value !== null && value !== void 0);
796
- if (hasUnexpectedRequest) {
797
- throw new Error(
798
- 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
799
- );
800
- }
846
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
847
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
848
+ i += 1;
849
+ continue;
801
850
  }
802
- }
803
- let request;
804
- if (interruptRequired) {
805
- const requestCandidates = [
806
- candidate.interruptRequest,
807
- candidate.request,
808
- candidate.interrupt,
809
- mode === "required" ? candidate : void 0
810
- ];
811
- const requestPayload = requestCandidates.find(
812
- (value) => value !== null && value !== void 0
813
- );
814
- request = parseWithSchema(
815
- args.requestSchema,
816
- requestPayload,
817
- "useReason interrupt.request"
818
- );
819
- }
820
- let output;
821
- if (args.outputSchema) {
822
- const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
823
- try {
824
- output = parseWithSchema(
825
- args.outputSchema,
826
- outputPayload,
827
- "useReason output"
828
- );
829
- } catch (error) {
830
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
831
- throw createTruncatedStructuredOutputError("useReason output");
832
- }
833
- if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
834
- throw createInvalidStructuredOutputError("useReason output");
851
+ if (next === "u") {
852
+ const hex = args.text.slice(i + 2, i + 6);
853
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
854
+ return {
855
+ complete: false
856
+ };
835
857
  }
836
- throw error;
858
+ value += String.fromCharCode(Number.parseInt(hex, 16));
859
+ i += 5;
860
+ continue;
837
861
  }
838
- }
839
- const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
840
- if (!args.outputSchema && !explicitDraft) {
841
- throw new Error(
842
- "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
843
- );
844
- }
845
- const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
846
- if (!interruptRequired) {
847
862
  return {
848
- draftText,
849
- interruptRequired: false,
850
- ...output !== void 0 ? { output } : {}
863
+ complete: false
851
864
  };
852
865
  }
853
866
  return {
854
- draftText,
855
- interruptRequired: true,
856
- request,
857
- ...output !== void 0 ? { output } : {}
867
+ complete: false
858
868
  };
859
869
  };
860
- var import_zod22 = require("zod");
861
- var tryStringify2 = (value) => {
862
- if (typeof value === "string") return value;
863
- try {
864
- return JSON.stringify(value, null, 2);
865
- } catch {
866
- return String(value);
867
- }
868
- };
869
- var tryGetSchemaHint = (schema) => {
870
- try {
871
- const hint = (0, import_zod22.toJSONSchema)(schema);
872
- return JSON.stringify(hint, null, 2);
873
- } catch {
874
- return void 0;
875
- }
876
- };
877
- var withSchemaHint = (input, label, schema) => {
878
- const hint = tryGetSchemaHint(schema);
879
- if (!hint) return input;
880
- return `${input}
881
-
882
- ${label}:
883
- ${hint}`;
884
- };
885
- var defaultInterruptFirstPassInput = (args) => {
886
- const mode = args.mode ?? "required";
887
- const base = mode === "optional" ? args.outputSchema ? [
888
- args.input,
889
- "Output rules:",
890
- "- Return JSON only. No markdown fences.",
891
- "- Return an object with keys: decision, output, interruptRequest, draftText.",
892
- '- decision must be either "continue" or "interrupt".',
893
- '- Use decision "continue" when you can produce the final output without asking the user.',
894
- '- Use decision "interrupt" only when user input is needed before finalizing.',
895
- "- output must match the expected output JSON schema.",
896
- '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
897
- '- When decision is "continue", interruptRequest must be null.',
898
- "- draftText should be a concise plain-language summary of output."
899
- ].join("\n\n") : [
900
- args.input,
901
- "Output rules:",
902
- "- Return JSON only. No markdown fences.",
903
- "- Return an object with keys: decision, draftText, interruptRequest.",
904
- '- decision must be either "continue" or "interrupt".',
905
- '- Use decision "continue" when you can produce the final answer without asking the user.',
906
- '- Use decision "interrupt" only when user input is needed before finalizing.',
907
- "- draftText should be the current answer text.",
908
- '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
909
- '- When decision is "continue", interruptRequest must be null.'
910
- ].join("\n\n") : args.outputSchema ? [
911
- args.input,
912
- "Output rules:",
913
- "- Return JSON only. No markdown fences.",
914
- "- Return an object with keys: output, interruptRequest, draftText.",
915
- "- output must match the expected output JSON schema.",
916
- "- interruptRequest must match the expected interrupt request JSON schema.",
917
- "- interruptRequest must be a non-null object.",
918
- "- draftText should be a concise plain-language summary of output.",
919
- "- If uncertain, still return a valid interruptRequest with safe default options."
920
- ].join("\n\n") : [
921
- args.input,
922
- "Output rules:",
923
- "- Return JSON only. No markdown fences.",
924
- "- Return an object with keys: draftText, interruptRequest.",
925
- "- draftText should be the current draft answer text.",
926
- "- interruptRequest must match the expected interrupt request JSON schema.",
927
- "- interruptRequest must be a non-null object.",
928
- "- If uncertain, still return a valid interruptRequest with safe default options."
929
- ].join("\n\n");
930
- const withInterruptSchema = withSchemaHint(
931
- base,
932
- "Expected interrupt request JSON schema (for `interruptRequest`)",
933
- args.requestSchema
934
- );
935
- if (!args.outputSchema) return withInterruptSchema;
936
- const withOutputSchema = withSchemaHint(
937
- withInterruptSchema,
938
- "Expected output JSON schema (for `output`)",
939
- args.outputSchema
940
- );
941
- return [
942
- withOutputSchema,
943
- "JSON template:",
944
- mode === "optional" ? '{ "decision": "continue", "output": { ... }, "interruptRequest": null, "draftText": "..." }' : '{ "output": { ... }, "interruptRequest": { ... }, "draftText": "..." }'
945
- ].join("\n\n");
946
- };
947
- var defaultContinuationInput = (args) => [
948
- "You are continuing a paused reasoning task after user input.",
949
- "Update the previous result using the user response and return the final answer only.",
950
- "If the previous output was JSON, return JSON in the same schema.",
951
- "",
952
- `Original request:
953
- ${args.input}`,
954
- `Draft answer text:
955
- ${args.draftText}`,
956
- `Draft output object:
957
- ${tryStringify2(args.draftOutput ?? args.draftText)}`,
958
- `Interrupt request shown to user:
959
- ${tryStringify2(args.request)}`,
960
- `User response:
961
- ${tryStringify2(args.response)}`
962
- ].join("\n\n");
963
- var withOutputGuardrails = (input, schema) => {
964
- const base = [
965
- input,
966
- "Output rules:",
967
- "- Return only JSON, no markdown fences.",
968
- "- Ensure the response conforms to the expected output schema.",
969
- "- Do not include explanatory text outside the JSON object."
970
- ].join("\n\n");
971
- return withSchemaHint(base, "Expected output JSON schema", schema);
972
- };
973
- var withStructuredStreamHints = (input, args) => {
974
- const rules = [];
975
- for (const path of args.setFieldPaths ?? []) {
976
- rules.push(
977
- `- For the field path pattern \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
978
- );
979
- }
980
- for (const path of args.appendFieldPaths ?? []) {
981
- rules.push(
982
- `- For the array field path pattern \`${path}\`, write items in final order and finish each item before starting the next.`
983
- );
984
- rules.push(
985
- `- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
986
- );
987
- }
988
- for (const path of args.textDeltaFieldPaths ?? []) {
989
- rules.push(
990
- `- For the string field path pattern \`${path}\`, write the value progressively by appending text to the end.`
991
- );
992
- rules.push(
993
- `- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
994
- );
995
- }
996
- if (rules.length === 0) return input;
997
- return [
998
- input,
999
- "Streaming rules:",
1000
- ...rules,
1001
- "- Keep the overall response as valid JSON."
1002
- ].join("\n\n");
1003
- };
1004
- var mergeUsage = (left, right) => {
1005
- if (!left) return right;
1006
- if (!right) return left;
1007
- const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1008
- const raw = left.raw || right.raw ? {
1009
- ...left.raw ?? {},
1010
- ...right.raw ?? {}
1011
- } : void 0;
1012
- const input = sum(left.input, right.input);
1013
- const output = sum(left.output, right.output);
1014
- const total = sum(left.total, right.total);
1015
- const reasoning = sum(left.reasoning, right.reasoning);
1016
- const cacheRead = sum(left.cacheRead, right.cacheRead);
1017
- const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1018
- const merged = {};
1019
- if (input !== void 0) merged.input = input;
1020
- if (output !== void 0) merged.output = output;
1021
- if (total !== void 0) merged.total = total;
1022
- if (reasoning !== void 0) merged.reasoning = reasoning;
1023
- if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1024
- if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1025
- if (raw) merged.raw = raw;
1026
- return merged;
1027
- };
1028
- var mergeWarnings = (left, right) => {
1029
- if (!left?.length) return right;
1030
- if (!right?.length) return left;
1031
- const seen = /* @__PURE__ */ new Set();
1032
- const merged = [];
1033
- for (const warning of [...left, ...right]) {
1034
- const key = JSON.stringify(warning);
1035
- if (seen.has(key)) continue;
1036
- seen.add(key);
1037
- merged.push(warning);
1038
- }
1039
- return merged;
1040
- };
1041
- var mergeProviderMetadata = (left, right) => {
1042
- if (!left) return right;
1043
- if (!right) return left;
1044
- return {
1045
- ...left,
1046
- ...right
1047
- };
1048
- };
1049
- var emitReasonStructuredOutput = (args) => {
1050
- if (!args.emit || !shouldEmitStructured(args.structured)) return;
1051
- emitStructuredData({
1052
- dataType: args.structured?.dataType ?? "reason-output",
1053
- data: args.output,
1054
- kind: "final",
1055
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1056
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1057
- ...args.id ? { id: args.id } : {},
1058
- streamId: args.opId
1059
- });
1060
- };
1061
- var resolveAppendFieldPaths = (cfg) => {
1062
- const fields = cfg?.fields;
1063
- if (!fields) return [];
1064
- const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
1065
- for (const path of appendPaths) {
1066
- assertStructuredPath(path, "useReason structured append");
1067
- }
1068
- return appendPaths;
1069
- };
1070
- var resolveSetFieldPaths = (cfg) => {
1071
- const fields = cfg?.fields;
1072
- if (!fields) return [];
1073
- const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
1074
- for (const path of setPaths) {
1075
- assertStructuredPath(path, "useReason structured set");
1076
- }
1077
- return setPaths;
1078
- };
1079
- var resolveTextDeltaFieldPaths = (cfg) => {
1080
- const fields = cfg?.fields;
1081
- if (!fields) return [];
1082
- const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
1083
- for (const path of textPaths) {
1084
- assertStructuredPath(path, "useReason structured text-delta");
1085
- }
1086
- return textPaths;
1087
- };
1088
- var extractCompletedStringValueAt = (args) => {
1089
- let value = "";
1090
- for (let i = args.start; i < args.text.length; i += 1) {
1091
- const ch = args.text[i];
1092
- if (ch === void 0) break;
1093
- if (ch === '"') {
1094
- return {
1095
- complete: true,
1096
- value
1097
- };
1098
- }
1099
- if (ch !== "\\") {
1100
- value += ch;
1101
- continue;
1102
- }
1103
- const next = args.text[i + 1];
1104
- if (!next) {
1105
- return {
1106
- complete: false
1107
- };
1108
- }
1109
- if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1110
- value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1111
- i += 1;
1112
- continue;
1113
- }
1114
- if (next === "u") {
1115
- const hex = args.text.slice(i + 2, i + 6);
1116
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1117
- return {
1118
- complete: false
1119
- };
1120
- }
1121
- value += String.fromCharCode(Number.parseInt(hex, 16));
1122
- i += 5;
1123
- continue;
1124
- }
1125
- return {
1126
- complete: false
1127
- };
1128
- }
1129
- return {
1130
- complete: false
1131
- };
1132
- };
1133
- var skipWhitespace = (text, start) => {
1134
- let i = start;
1135
- while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
1136
- return i;
870
+ var skipWhitespace = (text, start) => {
871
+ let i = start;
872
+ while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
873
+ return i;
1137
874
  };
1138
875
  var pathSegmentMatches = (patternPart, actualPart) => patternPart === "*" || patternPart === actualPart;
1139
876
  var pathMatchesPattern = (actualParts, patternParts) => actualParts.length === patternParts.length && actualParts.every(
@@ -1517,103 +1254,552 @@ ${tryStringify2(args.response)}`
1517
1254
  }
1518
1255
  return items;
1519
1256
  };
1520
- var extractCompletedArrayItemGroups = (args) => {
1521
- return locateValueStarts(args).flatMap((match) => {
1522
- const items = extractCompletedArrayItems({
1523
- text: args.text,
1524
- path: match.path
1525
- });
1526
- return items.length > 0 ? [
1527
- {
1528
- path: match.path,
1529
- items
1530
- }
1531
- ] : [];
1532
- });
1257
+ var extractCompletedArrayItemGroups = (args) => {
1258
+ return locateValueStarts(args).flatMap((match) => {
1259
+ const items = extractCompletedArrayItems({
1260
+ text: args.text,
1261
+ path: match.path
1262
+ });
1263
+ return items.length > 0 ? [
1264
+ {
1265
+ path: match.path,
1266
+ items
1267
+ }
1268
+ ] : [];
1269
+ });
1270
+ };
1271
+ var extractStreamingStringValue = (args) => {
1272
+ const located = locateValueStart(args);
1273
+ if (!located.found || located.start === void 0) {
1274
+ return {
1275
+ found: located.found,
1276
+ complete: false,
1277
+ value: ""
1278
+ };
1279
+ }
1280
+ const stringStart = skipWhitespace(args.text, located.start);
1281
+ if (args.text[stringStart] !== '"') {
1282
+ return {
1283
+ found: true,
1284
+ complete: false,
1285
+ value: ""
1286
+ };
1287
+ }
1288
+ let value = "";
1289
+ for (let i = stringStart + 1; i < args.text.length; i += 1) {
1290
+ const ch = args.text[i];
1291
+ if (ch === void 0) break;
1292
+ if (ch === '"') {
1293
+ return {
1294
+ found: true,
1295
+ complete: true,
1296
+ value
1297
+ };
1298
+ }
1299
+ if (ch !== "\\") {
1300
+ value += ch;
1301
+ continue;
1302
+ }
1303
+ const next = args.text[i + 1];
1304
+ if (!next) {
1305
+ return {
1306
+ found: true,
1307
+ complete: false,
1308
+ value
1309
+ };
1310
+ }
1311
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1312
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1313
+ i += 1;
1314
+ continue;
1315
+ }
1316
+ if (next === "u") {
1317
+ const hex = args.text.slice(i + 2, i + 6);
1318
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1319
+ return {
1320
+ found: true,
1321
+ complete: false,
1322
+ value
1323
+ };
1324
+ }
1325
+ value += String.fromCharCode(Number.parseInt(hex, 16));
1326
+ i += 5;
1327
+ continue;
1328
+ }
1329
+ return {
1330
+ found: true,
1331
+ complete: false,
1332
+ value
1333
+ };
1334
+ }
1335
+ return {
1336
+ found: true,
1337
+ complete: false,
1338
+ value
1339
+ };
1340
+ };
1341
+ var extractStreamingStringValues = (args) => {
1342
+ return locateValueStarts(args).flatMap((match) => {
1343
+ const current = extractStreamingStringValue({
1344
+ text: args.text,
1345
+ path: match.path
1346
+ });
1347
+ return current.found ? [
1348
+ {
1349
+ path: match.path,
1350
+ complete: current.complete,
1351
+ value: current.value
1352
+ }
1353
+ ] : [];
1354
+ });
1355
+ };
1356
+ function createStructuredOutputStreamer(args, id, opId) {
1357
+ let firstText = "";
1358
+ const setFieldPaths = resolveSetFieldPaths(args.structured);
1359
+ const appendFieldPaths = resolveAppendFieldPaths(args.structured);
1360
+ const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
1361
+ const emittedSetValues = /* @__PURE__ */ new Map();
1362
+ const emittedAppendCounts = /* @__PURE__ */ new Map();
1363
+ const emittedTextValues = /* @__PURE__ */ new Map();
1364
+ return (delta) => {
1365
+ firstText += delta;
1366
+ for (const fieldPath of setFieldPaths) {
1367
+ const values = extractCompletedFieldValues({
1368
+ text: firstText,
1369
+ path: fieldPath
1370
+ });
1371
+ for (const { path, value } of values) {
1372
+ const nextSerialized = JSON.stringify(value);
1373
+ if (emittedSetValues.get(path) === nextSerialized) continue;
1374
+ emittedSetValues.set(path, nextSerialized);
1375
+ emitStructuredData({
1376
+ kind: "set",
1377
+ path,
1378
+ value,
1379
+ dataType: args.structured?.dataType ?? "reason-output",
1380
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1381
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1382
+ ...id ? { id } : {},
1383
+ streamId: opId
1384
+ });
1385
+ }
1386
+ }
1387
+ for (const fieldPath of appendFieldPaths) {
1388
+ const groups = extractCompletedArrayItemGroups({
1389
+ text: firstText,
1390
+ path: fieldPath
1391
+ });
1392
+ for (const { path, items } of groups) {
1393
+ const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
1394
+ if (items.length <= emittedAppendCount) continue;
1395
+ const nextItems = items.slice(emittedAppendCount);
1396
+ emittedAppendCounts.set(path, items.length);
1397
+ emitStructuredData({
1398
+ kind: "append",
1399
+ path,
1400
+ items: nextItems,
1401
+ dataType: args.structured?.dataType ?? "reason-output",
1402
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1403
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1404
+ ...id ? { id } : {},
1405
+ streamId: opId
1406
+ });
1407
+ }
1408
+ }
1409
+ for (const fieldPath of textDeltaFieldPaths) {
1410
+ const values = extractStreamingStringValues({
1411
+ text: firstText,
1412
+ path: fieldPath
1413
+ });
1414
+ for (const { path, value } of values) {
1415
+ const emittedTextValue = emittedTextValues.get(path) ?? "";
1416
+ if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
1417
+ continue;
1418
+ }
1419
+ const nextDelta = value.slice(emittedTextValue.length);
1420
+ emittedTextValues.set(path, value);
1421
+ if (nextDelta.length === 0) continue;
1422
+ emitStructuredData({
1423
+ kind: "text-delta",
1424
+ path,
1425
+ delta: nextDelta,
1426
+ dataType: args.structured?.dataType ?? "reason-output",
1427
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1428
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1429
+ ...id ? { id } : {},
1430
+ streamId: opId
1431
+ });
1432
+ }
1433
+ }
1434
+ };
1435
+ }
1436
+ var tryJsonParse = (value) => {
1437
+ try {
1438
+ return { ok: true, value: JSON.parse(value) };
1439
+ } catch (error) {
1440
+ return {
1441
+ ok: false,
1442
+ error: error instanceof Error ? error.message : String(error)
1443
+ };
1444
+ }
1445
+ };
1446
+ var extractJsonCodeBlock = (value) => {
1447
+ const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
1448
+ if (!match || typeof match[1] !== "string") return null;
1449
+ const inner = match[1].trim();
1450
+ return inner.length > 0 ? inner : null;
1451
+ };
1452
+ var tryStringify = (value) => {
1453
+ if (typeof value === "string") return value;
1454
+ try {
1455
+ return JSON.stringify(value, null, 2);
1456
+ } catch {
1457
+ return String(value);
1458
+ }
1459
+ };
1460
+ var isRecord22 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1461
+ var resolveOutputCandidate = (text) => {
1462
+ const trimmed = text.trim();
1463
+ if (!trimmed) return text;
1464
+ const direct = tryJsonParse(trimmed);
1465
+ if (direct.ok) return direct.value;
1466
+ const block = extractJsonCodeBlock(trimmed);
1467
+ if (block) {
1468
+ const parsedBlock = tryJsonParse(block);
1469
+ if (parsedBlock.ok) return parsedBlock.value;
1470
+ }
1471
+ return text;
1472
+ };
1473
+ var looksLikeStructuredJsonAttempt = (value) => {
1474
+ const trimmed = value.trim();
1475
+ if (!trimmed) return false;
1476
+ return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
1477
+ };
1478
+ var isLikelyTruncatedStructuredOutput = (text) => {
1479
+ const trimmed = text.trim();
1480
+ if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
1481
+ const direct = tryJsonParse(trimmed);
1482
+ if (!direct.ok && direct.error) return true;
1483
+ const block = extractJsonCodeBlock(trimmed);
1484
+ if (!block) return false;
1485
+ const parsedBlock = tryJsonParse(block);
1486
+ return !parsedBlock.ok && Boolean(parsedBlock.error);
1487
+ };
1488
+ var createTruncatedStructuredOutputError = (label) => new Error(
1489
+ `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
1490
+ );
1491
+ var createInvalidStructuredOutputError = (label) => new Error(
1492
+ `${label} did not produce valid structured output. The model returned text instead of the expected JSON payload. This often happens when output is truncated or the provider does not follow JSON mode. Increase maxOutputTokens or simplify the requested output.`
1493
+ );
1494
+ var parseReasonOutputWithSchema = (args) => {
1495
+ const candidate = resolveOutputCandidate(args.text);
1496
+ try {
1497
+ return parseWithSchema(args.schema, candidate, args.label);
1498
+ } catch (error) {
1499
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1500
+ throw createTruncatedStructuredOutputError(args.label);
1501
+ }
1502
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1503
+ throw createInvalidStructuredOutputError(args.label);
1504
+ }
1505
+ throw error;
1506
+ }
1507
+ };
1508
+ var parseInterruptFirstPassResult = (args) => {
1509
+ const candidate = resolveOutputCandidate(args.text);
1510
+ if (!isRecord22(candidate)) {
1511
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1512
+ throw createTruncatedStructuredOutputError("useReason output");
1513
+ }
1514
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1515
+ throw createInvalidStructuredOutputError("useReason output");
1516
+ }
1517
+ throw new Error(
1518
+ "useReason first pass with interrupt must return a JSON object."
1519
+ );
1520
+ }
1521
+ const mode = args.mode ?? "required";
1522
+ let interruptRequired = true;
1523
+ if (mode === "optional") {
1524
+ if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
1525
+ throw new Error(
1526
+ 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
1527
+ );
1528
+ }
1529
+ interruptRequired = candidate.decision === "interrupt";
1530
+ if (!interruptRequired) {
1531
+ const hasUnexpectedRequest = [
1532
+ candidate.interruptRequest,
1533
+ candidate.request,
1534
+ candidate.interrupt
1535
+ ].some((value) => value !== null && value !== void 0);
1536
+ if (hasUnexpectedRequest) {
1537
+ throw new Error(
1538
+ 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
1539
+ );
1540
+ }
1541
+ }
1542
+ }
1543
+ let request;
1544
+ if (interruptRequired) {
1545
+ const requestCandidates = [
1546
+ candidate.interruptRequest,
1547
+ candidate.request,
1548
+ candidate.interrupt,
1549
+ mode === "required" ? candidate : void 0
1550
+ ];
1551
+ const requestPayload = requestCandidates.find(
1552
+ (value) => value !== null && value !== void 0
1553
+ );
1554
+ request = parseWithSchema(
1555
+ args.requestSchema,
1556
+ requestPayload,
1557
+ "useReason interrupt.request"
1558
+ );
1559
+ }
1560
+ let output;
1561
+ if (args.outputSchema) {
1562
+ const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
1563
+ try {
1564
+ output = parseWithSchema(
1565
+ args.outputSchema,
1566
+ outputPayload,
1567
+ "useReason output"
1568
+ );
1569
+ } catch (error) {
1570
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1571
+ throw createTruncatedStructuredOutputError("useReason output");
1572
+ }
1573
+ if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
1574
+ throw createInvalidStructuredOutputError("useReason output");
1575
+ }
1576
+ throw error;
1577
+ }
1578
+ }
1579
+ const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
1580
+ if (!args.outputSchema && !explicitDraft) {
1581
+ throw new Error(
1582
+ "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
1583
+ );
1584
+ }
1585
+ const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
1586
+ if (!interruptRequired) {
1587
+ return {
1588
+ draftText,
1589
+ interruptRequired: false,
1590
+ ...output !== void 0 ? { output } : {}
1591
+ };
1592
+ }
1593
+ return {
1594
+ draftText,
1595
+ interruptRequired: true,
1596
+ request,
1597
+ ...output !== void 0 ? { output } : {}
1598
+ };
1599
+ };
1600
+ var import_zod32 = require("zod");
1601
+ var tryStringify2 = (value) => {
1602
+ if (typeof value === "string") return value;
1603
+ try {
1604
+ return JSON.stringify(value, null, 2);
1605
+ } catch {
1606
+ return String(value);
1607
+ }
1608
+ };
1609
+ var tryGetSchemaHint = (schema) => {
1610
+ try {
1611
+ const hint = (0, import_zod32.toJSONSchema)(schema);
1612
+ return JSON.stringify(hint, null, 2);
1613
+ } catch {
1614
+ return void 0;
1615
+ }
1616
+ };
1617
+ var withSchemaHint = (input, label, schema) => {
1618
+ const hint = tryGetSchemaHint(schema);
1619
+ if (!hint) return input;
1620
+ return `${input}
1621
+
1622
+ ${label}:
1623
+ ${hint}`;
1624
+ };
1625
+ var defaultInterruptFirstPassInput = (args) => {
1626
+ const mode = args.mode ?? "required";
1627
+ const base = mode === "optional" ? args.outputSchema ? [
1628
+ args.input,
1629
+ "Output rules:",
1630
+ "- Return JSON only. No markdown fences.",
1631
+ "- Return an object with keys: decision, output, interruptRequest, draftText.",
1632
+ '- decision must be either "continue" or "interrupt".',
1633
+ '- Use decision "continue" when you can produce the final output without asking the user.',
1634
+ '- Use decision "interrupt" only when user input is needed before finalizing.',
1635
+ "- output must match the expected output JSON schema.",
1636
+ '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
1637
+ '- When decision is "continue", interruptRequest must be null.',
1638
+ "- draftText should be a concise plain-language summary of output."
1639
+ ].join("\n\n") : [
1640
+ args.input,
1641
+ "Output rules:",
1642
+ "- Return JSON only. No markdown fences.",
1643
+ "- Return an object with keys: decision, draftText, interruptRequest.",
1644
+ '- decision must be either "continue" or "interrupt".',
1645
+ '- Use decision "continue" when you can produce the final answer without asking the user.',
1646
+ '- Use decision "interrupt" only when user input is needed before finalizing.',
1647
+ "- draftText should be the current answer text.",
1648
+ '- When decision is "interrupt", interruptRequest must match the expected interrupt request JSON schema and must be a non-null object.',
1649
+ '- When decision is "continue", interruptRequest must be null.'
1650
+ ].join("\n\n") : args.outputSchema ? [
1651
+ args.input,
1652
+ "Output rules:",
1653
+ "- Return JSON only. No markdown fences.",
1654
+ "- Return an object with keys: output, interruptRequest, draftText.",
1655
+ "- output must match the expected output JSON schema.",
1656
+ "- interruptRequest must match the expected interrupt request JSON schema.",
1657
+ "- interruptRequest must be a non-null object.",
1658
+ "- draftText should be a concise plain-language summary of output.",
1659
+ "- If uncertain, still return a valid interruptRequest with safe default options."
1660
+ ].join("\n\n") : [
1661
+ args.input,
1662
+ "Output rules:",
1663
+ "- Return JSON only. No markdown fences.",
1664
+ "- Return an object with keys: draftText, interruptRequest.",
1665
+ "- draftText should be the current draft answer text.",
1666
+ "- interruptRequest must match the expected interrupt request JSON schema.",
1667
+ "- interruptRequest must be a non-null object.",
1668
+ "- If uncertain, still return a valid interruptRequest with safe default options."
1669
+ ].join("\n\n");
1670
+ const withInterruptSchema = withSchemaHint(
1671
+ base,
1672
+ "Expected interrupt request JSON schema (for `interruptRequest`)",
1673
+ args.requestSchema
1674
+ );
1675
+ if (!args.outputSchema) return withInterruptSchema;
1676
+ const withOutputSchema = withSchemaHint(
1677
+ withInterruptSchema,
1678
+ "Expected output JSON schema (for `output`)",
1679
+ args.outputSchema
1680
+ );
1681
+ return [
1682
+ withOutputSchema,
1683
+ "JSON template:",
1684
+ mode === "optional" ? '{ "decision": "continue", "output": { ... }, "interruptRequest": null, "draftText": "..." }' : '{ "output": { ... }, "interruptRequest": { ... }, "draftText": "..." }'
1685
+ ].join("\n\n");
1686
+ };
1687
+ var defaultContinuationInput = (args) => [
1688
+ "You are continuing a paused reasoning task after user input.",
1689
+ "Update the previous result using the user response and return the final answer only.",
1690
+ "If the previous output was JSON, return JSON in the same schema.",
1691
+ "",
1692
+ `Original request:
1693
+ ${args.input}`,
1694
+ `Draft answer text:
1695
+ ${args.draftText}`,
1696
+ `Draft output object:
1697
+ ${tryStringify2(args.draftOutput ?? args.draftText)}`,
1698
+ `Interrupt request shown to user:
1699
+ ${tryStringify2(args.request)}`,
1700
+ `User response:
1701
+ ${tryStringify2(args.response)}`
1702
+ ].join("\n\n");
1703
+ var withOutputGuardrails = (input, schema) => {
1704
+ const base = [
1705
+ input,
1706
+ "Output rules:",
1707
+ "- Return only JSON, no markdown fences.",
1708
+ "- Ensure the response conforms to the expected output schema.",
1709
+ "- Do not include explanatory text outside the JSON object."
1710
+ ].join("\n\n");
1711
+ return withSchemaHint(base, "Expected output JSON schema", schema);
1533
1712
  };
1534
- var extractStreamingStringValue = (args) => {
1535
- const located = locateValueStart(args);
1536
- if (!located.found || located.start === void 0) {
1537
- return {
1538
- found: located.found,
1539
- complete: false,
1540
- value: ""
1541
- };
1713
+ var withStructuredStreamHints = (input, args) => {
1714
+ const rules = [];
1715
+ for (const path of args.setFieldPaths ?? []) {
1716
+ rules.push(
1717
+ `- For the field path pattern \`${path}\`, write one stable final value and do not rewrite it after it is complete.`
1718
+ );
1542
1719
  }
1543
- const stringStart = skipWhitespace(args.text, located.start);
1544
- if (args.text[stringStart] !== '"') {
1545
- return {
1546
- found: true,
1547
- complete: false,
1548
- value: ""
1549
- };
1720
+ for (const path of args.appendFieldPaths ?? []) {
1721
+ rules.push(
1722
+ `- For the array field path pattern \`${path}\`, write items in final order and finish each item before starting the next.`
1723
+ );
1724
+ rules.push(
1725
+ `- Once an item in \`${path}\` is complete, do not rewrite or reorder earlier items.`
1726
+ );
1550
1727
  }
1551
- let value = "";
1552
- for (let i = stringStart + 1; i < args.text.length; i += 1) {
1553
- const ch = args.text[i];
1554
- if (ch === void 0) break;
1555
- if (ch === '"') {
1556
- return {
1557
- found: true,
1558
- complete: true,
1559
- value
1560
- };
1561
- }
1562
- if (ch !== "\\") {
1563
- value += ch;
1564
- continue;
1565
- }
1566
- const next = args.text[i + 1];
1567
- if (!next) {
1568
- return {
1569
- found: true,
1570
- complete: false,
1571
- value
1572
- };
1573
- }
1574
- if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1575
- value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1576
- i += 1;
1577
- continue;
1578
- }
1579
- if (next === "u") {
1580
- const hex = args.text.slice(i + 2, i + 6);
1581
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1582
- return {
1583
- found: true,
1584
- complete: false,
1585
- value
1586
- };
1587
- }
1588
- value += String.fromCharCode(Number.parseInt(hex, 16));
1589
- i += 5;
1590
- continue;
1591
- }
1592
- return {
1593
- found: true,
1594
- complete: false,
1595
- value
1596
- };
1728
+ for (const path of args.textDeltaFieldPaths ?? []) {
1729
+ rules.push(
1730
+ `- For the string field path pattern \`${path}\`, write the value progressively by appending text to the end.`
1731
+ );
1732
+ rules.push(
1733
+ `- Do not rewrite or replace the earlier prefix of \`${path}\` once emitted.`
1734
+ );
1735
+ }
1736
+ if (rules.length === 0) return input;
1737
+ return [
1738
+ input,
1739
+ "Streaming rules:",
1740
+ ...rules,
1741
+ "- Keep the overall response as valid JSON."
1742
+ ].join("\n\n");
1743
+ };
1744
+ var mergeUsage = (left, right) => {
1745
+ if (!left) return right;
1746
+ if (!right) return left;
1747
+ const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1748
+ const raw = left.raw || right.raw ? {
1749
+ ...left.raw ?? {},
1750
+ ...right.raw ?? {}
1751
+ } : void 0;
1752
+ const input = sum(left.input, right.input);
1753
+ const output = sum(left.output, right.output);
1754
+ const total = sum(left.total, right.total);
1755
+ const reasoning = sum(left.reasoning, right.reasoning);
1756
+ const cacheRead = sum(left.cacheRead, right.cacheRead);
1757
+ const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1758
+ const merged = {
1759
+ ...right.outputIncludesReasoning !== void 0 ? { outputIncludesReasoning: right.outputIncludesReasoning } : {},
1760
+ ...right.inputIncludesCacheWrite !== void 0 ? { inputIncludesCacheWrite: right.inputIncludesCacheWrite } : {},
1761
+ ...right.inputIncludesCacheRead !== void 0 ? { inputIncludesCacheRead: right.inputIncludesCacheRead } : {}
1762
+ };
1763
+ if (input !== void 0) merged.input = input;
1764
+ if (output !== void 0) merged.output = output;
1765
+ if (total !== void 0) merged.total = total;
1766
+ if (reasoning !== void 0) merged.reasoning = reasoning;
1767
+ if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1768
+ if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1769
+ if (raw) merged.raw = raw;
1770
+ return merged;
1771
+ };
1772
+ var mergeWarnings = (left, right) => {
1773
+ if (!left?.length) return right;
1774
+ if (!right?.length) return left;
1775
+ const seen = /* @__PURE__ */ new Set();
1776
+ const merged = [];
1777
+ for (const warning of [...left, ...right]) {
1778
+ const key = JSON.stringify(warning);
1779
+ if (seen.has(key)) continue;
1780
+ seen.add(key);
1781
+ merged.push(warning);
1597
1782
  }
1783
+ return merged;
1784
+ };
1785
+ var mergeProviderMetadata = (left, right) => {
1786
+ if (!left) return right;
1787
+ if (!right) return left;
1598
1788
  return {
1599
- found: true,
1600
- complete: false,
1601
- value
1789
+ ...left,
1790
+ ...right
1602
1791
  };
1603
1792
  };
1604
- var extractStreamingStringValues = (args) => {
1605
- return locateValueStarts(args).flatMap((match) => {
1606
- const current = extractStreamingStringValue({
1607
- text: args.text,
1608
- path: match.path
1609
- });
1610
- return current.found ? [
1611
- {
1612
- path: match.path,
1613
- complete: current.complete,
1614
- value: current.value
1615
- }
1616
- ] : [];
1793
+ var emitReasonStructuredOutput = (args) => {
1794
+ if (!args.emit || !shouldEmitStructured(args.structured)) return;
1795
+ emitStructuredData({
1796
+ dataType: args.structured?.dataType ?? "reason-output",
1797
+ data: args.output,
1798
+ kind: "final",
1799
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1800
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1801
+ ...args.id ? { id: args.id } : {},
1802
+ streamId: args.opId
1617
1803
  });
1618
1804
  };
1619
1805
  var import_core42 = require("@kortyx/core");
@@ -1668,16 +1854,20 @@ ${tryStringify2(args.response)}`
1668
1854
  { role: "user", content: String(args.input ?? "") }
1669
1855
  ];
1670
1856
  var runReasonToolLoop = async (args) => {
1671
- const { useReasonArgs, id, opId, traceSpan } = args;
1857
+ const { useReasonArgs, id, traceSpan, checkpointKey } = args;
1672
1858
  const ctx = getHookContext();
1673
1859
  const abortSignal = (0, import_core42.combineAbortSignals)(
1674
1860
  ctx.node.abortSignal,
1675
1861
  useReasonArgs.abortSignal,
1676
1862
  useReasonArgs.model.options?.abortSignal
1677
1863
  );
1864
+ const saved = ctx.currentNodeState.byKey[checkpointKey];
1865
+ const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
1866
+ const opId = checkpoint?.opId ?? args.opId;
1678
1867
  const tools = useReasonArgs.tools ?? [];
1679
1868
  const toolByName = /* @__PURE__ */ new Map();
1680
1869
  let validationCompleted = false;
1870
+ let separateOutput = false;
1681
1871
  try {
1682
1872
  for (const tool of tools) {
1683
1873
  if (toolByName.has(tool.name)) {
@@ -1692,6 +1882,17 @@ ${tryStringify2(args.response)}`
1692
1882
  "useReason tools cannot be combined with useReason interrupt mode yet. Use toolExecution.approval for tool approval."
1693
1883
  );
1694
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
+ );
1695
1896
  validationCompleted = true;
1696
1897
  } finally {
1697
1898
  if (!validationCompleted) {
@@ -1699,21 +1900,47 @@ ${tryStringify2(args.response)}`
1699
1900
  }
1700
1901
  }
1701
1902
  const toolDefinitions = toToolDefinitions(tools);
1903
+ let finalizing = checkpoint?.finalizing ?? false;
1904
+ let completed = false;
1702
1905
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1703
- const messages = createInitialMessages({
1906
+ const messages = checkpoint?.messages ?? createInitialMessages({
1704
1907
  system: useReasonArgs.system,
1705
- input: useReasonArgs.input
1908
+ input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input
1706
1909
  });
1707
- const steps = [];
1708
- const allToolCalls = [];
1709
- const allToolResults = [];
1910
+ const steps = checkpoint?.steps ?? [];
1911
+ const allToolCalls = steps.flatMap(
1912
+ (step) => step.toolCalls
1913
+ );
1914
+ const allToolResults = steps.flatMap(
1915
+ (step) => step.toolResults
1916
+ );
1710
1917
  let finalText = "";
1711
1918
  let finalRaw;
1712
1919
  let finalOutput;
1713
- let aggregatedUsage;
1920
+ let aggregatedUsage = steps.reduce(
1921
+ (usage, step) => mergeUsage(usage, step.usage),
1922
+ void 0
1923
+ );
1714
1924
  let finalFinishReason;
1715
1925
  let aggregatedProviderMetadata;
1716
- let aggregatedWarnings;
1926
+ let aggregatedWarnings = steps.reduce(
1927
+ (warnings, step) => mergeWarnings(warnings, step.warnings),
1928
+ args.initialWarnings
1929
+ );
1930
+ let pending = checkpoint?.pending;
1931
+ const approvedCalls = checkpoint?.approvedCalls ?? [];
1932
+ const save = () => {
1933
+ ctx.currentNodeState.byKey[checkpointKey] = {
1934
+ status: "tool_loop",
1935
+ opId,
1936
+ messages,
1937
+ steps,
1938
+ approvedCalls,
1939
+ finalizing,
1940
+ ...pending ? { pending } : {}
1941
+ };
1942
+ ctx.stateDirty = true;
1943
+ };
1717
1944
  const emitToolEvent = (event, payload) => {
1718
1945
  ctx.node.emit(event, {
1719
1946
  ...payload,
@@ -1723,26 +1950,44 @@ ${tryStringify2(args.response)}`
1723
1950
  });
1724
1951
  };
1725
1952
  try {
1726
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
1953
+ for (let stepIndex = pending ? steps.length - 1 : steps.length; stepIndex < maxSteps; stepIndex += 1) {
1727
1954
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
1728
1955
  traceSpan?.addEvent?.("useReason.tool-step.start", {
1729
1956
  stepIndex,
1730
1957
  toolCount: toolDefinitions.length
1731
1958
  });
1732
- const step = await reasonEngine(
1959
+ const reused = Boolean(pending);
1960
+ const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
1961
+ const emit = useReasonArgs.emit ?? true;
1962
+ let textStarted = false;
1963
+ const structuredChunk = useReasonArgs.outputSchema && (!separateOutput || finalizing) && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1964
+ const step = pending ?? await reasonEngine(
1733
1965
  {
1734
1966
  ...useReasonArgs,
1735
- tools: toolDefinitions,
1967
+ tools: finalizing ? [] : toolDefinitions,
1968
+ ...separateOutput && !finalizing ? { responseFormat: { type: "text" } } : {},
1736
1969
  messages,
1737
1970
  emit: false,
1738
- stream: false
1971
+ stream,
1972
+ onTextChunk: (delta) => {
1973
+ if (structuredChunk) structuredChunk(delta);
1974
+ if (!emit || useReasonArgs.outputSchema || !delta) return;
1975
+ if (!textStarted) {
1976
+ emitToolEvent("text-start", {});
1977
+ textStarted = true;
1978
+ }
1979
+ emitToolEvent("text-delta", { delta });
1980
+ }
1739
1981
  },
1740
1982
  { ...id ? { id } : {}, opId }
1741
1983
  );
1984
+ if (textStarted) emitToolEvent("text-end", {});
1742
1985
  finalText = step.text;
1743
1986
  finalRaw = step.raw;
1744
- aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1745
- accumulateTokenUsage(step.usage);
1987
+ if (!reused) {
1988
+ aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1989
+ accumulateTokenUsage(step.usage);
1990
+ }
1746
1991
  finalFinishReason = step.finishReason;
1747
1992
  aggregatedProviderMetadata = mergeProviderMetadata(
1748
1993
  aggregatedProviderMetadata,
@@ -1750,32 +1995,75 @@ ${tryStringify2(args.response)}`
1750
1995
  );
1751
1996
  aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
1752
1997
  const toolCalls = step.toolCalls ?? [];
1753
- const toolResults = [];
1754
- steps.push({
1755
- stepIndex,
1756
- text: step.text,
1757
- toolCalls,
1758
- toolResults,
1759
- ...step.usage ? { usage: step.usage } : {},
1760
- ...step.finishReason ? { finishReason: step.finishReason } : {},
1761
- ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1762
- ...step.warnings ? { warnings: step.warnings } : {}
1763
- });
1998
+ const toolResults = reused ? steps[stepIndex]?.toolResults ?? [] : [];
1999
+ if (!reused)
2000
+ steps.push({
2001
+ stepIndex,
2002
+ text: step.text,
2003
+ toolCalls,
2004
+ toolResults,
2005
+ ...step.usage ? { usage: step.usage } : {},
2006
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
2007
+ ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
2008
+ ...step.warnings ? { warnings: step.warnings } : {}
2009
+ });
1764
2010
  if (toolCalls.length === 0) {
1765
- if ((useReasonArgs.emit ?? true) && step.text.length > 0) {
1766
- emitToolEvent("text-start", {});
1767
- emitToolEvent("text-delta", { delta: step.text });
1768
- emitToolEvent("text-end", {});
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;
1769
2045
  }
2046
+ completed = true;
1770
2047
  break;
1771
2048
  }
1772
- allToolCalls.push(...toolCalls);
1773
- messages.push({
1774
- role: "assistant",
1775
- content: step.text,
1776
- toolCalls
1777
- });
2049
+ if (finalizing)
2050
+ throw new Error(
2051
+ "Provider requested a tool during schema-only finalization."
2052
+ );
2053
+ if (!reused) {
2054
+ allToolCalls.push(...toolCalls);
2055
+ messages.push({
2056
+ role: "assistant",
2057
+ content: step.text,
2058
+ toolCalls,
2059
+ ...step.continuation ? { continuation: step.continuation } : {}
2060
+ });
2061
+ }
2062
+ pending = step;
2063
+ save();
1778
2064
  for (const toolCall of toolCalls) {
2065
+ if (toolResults.some((result2) => result2.toolCallId === toolCall.id))
2066
+ continue;
1779
2067
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
1780
2068
  const tool = toolByName.get(toolCall.name);
1781
2069
  if (!tool) {
@@ -1805,7 +2093,7 @@ ${tryStringify2(args.response)}`
1805
2093
  input: toolCall.input
1806
2094
  });
1807
2095
  }
1808
- if (shouldApproveTool) {
2096
+ if (shouldApproveTool && !approvedCalls.includes(toolCall.id)) {
1809
2097
  const approval = await awaitInterruptInternal({
1810
2098
  id: `tool:${toolCall.id}`,
1811
2099
  request: {
@@ -1840,8 +2128,18 @@ ${tryStringify2(args.response)}`
1840
2128
  isError: true
1841
2129
  });
1842
2130
  }
2131
+ messages.push({
2132
+ role: "tool",
2133
+ content: denied.content,
2134
+ toolCallId: denied.toolCallId,
2135
+ name: denied.name,
2136
+ isError: true
2137
+ });
2138
+ save();
1843
2139
  continue;
1844
2140
  }
2141
+ approvedCalls.push(toolCall.id);
2142
+ save();
1845
2143
  }
1846
2144
  try {
1847
2145
  (0, import_core42.throwIfExecutionAborted)(abortSignal);
@@ -1895,32 +2193,26 @@ ${tryStringify2(args.response)}`
1895
2193
  message: result2.content
1896
2194
  });
1897
2195
  }
2196
+ const completed2 = toolResults.at(-1);
2197
+ if (completed2)
2198
+ messages.push({
2199
+ role: "tool",
2200
+ content: completed2.content,
2201
+ toolCallId: completed2.toolCallId,
2202
+ name: completed2.name,
2203
+ ...completed2.isError !== void 0 ? { isError: completed2.isError } : {}
2204
+ });
2205
+ save();
1898
2206
  }
1899
- const currentStep = steps[steps.length - 1];
1900
- if (currentStep) {
1901
- steps[steps.length - 1] = {
1902
- ...currentStep,
1903
- toolResults
1904
- };
1905
- }
1906
- for (const result2 of toolResults) {
1907
- messages.push({
1908
- role: "tool",
1909
- content: result2.content,
1910
- toolCallId: result2.toolCallId,
1911
- name: result2.name,
1912
- ...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
1913
- ...result2.isError !== void 0 ? { isError: result2.isError } : {},
1914
- ...result2.raw !== void 0 ? { raw: result2.raw } : {}
1915
- });
1916
- }
2207
+ pending = void 0;
2208
+ save();
1917
2209
  }
1918
- if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
2210
+ if (!completed) {
1919
2211
  throw new Error(
1920
2212
  `useReason tool loop reached maxSteps (${maxSteps}) before producing a final response.`
1921
2213
  );
1922
2214
  }
1923
- if (useReasonArgs.outputSchema) {
2215
+ if (useReasonArgs.outputSchema && finalOutput === void 0) {
1924
2216
  finalOutput = parseReasonOutputWithSchema({
1925
2217
  text: finalText,
1926
2218
  schema: useReasonArgs.outputSchema,
@@ -1967,6 +2259,8 @@ ${tryStringify2(args.response)}`
1967
2259
  output: useReasonArgs.telemetry?.output ?? finalOutput ?? finalText
1968
2260
  }
1969
2261
  });
2262
+ ctx.currentNodeState.byKey[checkpointKey] = { status: "completed", result };
2263
+ ctx.stateDirty = true;
1970
2264
  return result;
1971
2265
  } catch (error) {
1972
2266
  traceSpan?.fail?.(error, {
@@ -2000,7 +2294,18 @@ ${tryStringify2(args.response)}`
2000
2294
  args.model.options?.abortSignal
2001
2295
  );
2002
2296
  (0, import_core52.throwIfExecutionAborted)(abortSignal);
2003
- args = { ...args, abortSignal };
2297
+ const allowValidatedToolOutput = Boolean(
2298
+ args.outputSchema && !(args.responseFormat ?? args.model.options?.responseFormat)
2299
+ );
2300
+ const inferred = inferOutputFormat(
2301
+ args.outputSchema,
2302
+ args.responseFormat ?? args.model.options?.responseFormat
2303
+ );
2304
+ args = {
2305
+ ...args,
2306
+ abortSignal,
2307
+ ...!args.interrupt && inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {}
2308
+ };
2004
2309
  const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
2005
2310
  const opId = createRuntimeId2();
2006
2311
  const checkpointKey = resolveReasonCheckpointKey({
@@ -2044,6 +2349,9 @@ ${tryStringify2(args.response)}`
2044
2349
  if (args.tools?.length) {
2045
2350
  return runReasonToolLoop({
2046
2351
  useReasonArgs: args,
2352
+ allowValidatedToolOutput,
2353
+ checkpointKey,
2354
+ initialWarnings: inferred.warnings,
2047
2355
  ...id ? { id } : {},
2048
2356
  opId,
2049
2357
  ...traceSpan ? { traceSpan } : {}
@@ -2067,10 +2375,7 @@ ${tryStringify2(args.response)}`
2067
2375
  let aggregatedUsage;
2068
2376
  let finalFinishReason;
2069
2377
  let aggregatedProviderMetadata;
2070
- let aggregatedWarnings;
2071
- const emittedSetValues = /* @__PURE__ */ new Map();
2072
- const emittedAppendCounts = /* @__PURE__ */ new Map();
2073
- const emittedTextValues = /* @__PURE__ */ new Map();
2378
+ let aggregatedWarnings = inferred.warnings;
2074
2379
  if (existingCheckpoint) {
2075
2380
  firstText = existingCheckpoint.firstText;
2076
2381
  firstRaw = existingCheckpoint.firstRaw;
@@ -2092,78 +2397,7 @@ ${tryStringify2(args.response)}`
2092
2397
  emit: suppressTextStream ? false : args.emit,
2093
2398
  stream: useStructuredIncrementalStreaming ? true : suppressTextStream ? false : args.stream,
2094
2399
  ...useStructuredIncrementalStreaming ? {
2095
- onTextChunk: (delta) => {
2096
- firstText += delta;
2097
- finalText = firstText;
2098
- for (const fieldPath of setFieldPaths) {
2099
- const values = extractCompletedFieldValues({
2100
- text: firstText,
2101
- path: fieldPath
2102
- });
2103
- for (const { path, value } of values) {
2104
- const nextSerialized = JSON.stringify(value);
2105
- if (emittedSetValues.get(path) === nextSerialized) continue;
2106
- emittedSetValues.set(path, nextSerialized);
2107
- emitStructuredData({
2108
- kind: "set",
2109
- path,
2110
- value,
2111
- dataType: args.structured?.dataType ?? "reason-output",
2112
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2113
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2114
- ...id ? { id } : {},
2115
- streamId: opId
2116
- });
2117
- }
2118
- }
2119
- for (const fieldPath of appendFieldPaths) {
2120
- const groups = extractCompletedArrayItemGroups({
2121
- text: firstText,
2122
- path: fieldPath
2123
- });
2124
- for (const { path, items } of groups) {
2125
- const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
2126
- if (items.length <= emittedAppendCount) continue;
2127
- const nextItems = items.slice(emittedAppendCount);
2128
- emittedAppendCounts.set(path, items.length);
2129
- emitStructuredData({
2130
- kind: "append",
2131
- path,
2132
- items: nextItems,
2133
- dataType: args.structured?.dataType ?? "reason-output",
2134
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2135
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2136
- ...id ? { id } : {},
2137
- streamId: opId
2138
- });
2139
- }
2140
- }
2141
- for (const fieldPath of textDeltaFieldPaths) {
2142
- const values = extractStreamingStringValues({
2143
- text: firstText,
2144
- path: fieldPath
2145
- });
2146
- for (const { path, value } of values) {
2147
- const emittedTextValue = emittedTextValues.get(path) ?? "";
2148
- if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
2149
- continue;
2150
- }
2151
- const nextDelta = value.slice(emittedTextValue.length);
2152
- emittedTextValues.set(path, value);
2153
- if (nextDelta.length === 0) continue;
2154
- emitStructuredData({
2155
- kind: "text-delta",
2156
- path,
2157
- delta: nextDelta,
2158
- dataType: args.structured?.dataType ?? "reason-output",
2159
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2160
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2161
- ...id ? { id } : {},
2162
- streamId: opId
2163
- });
2164
- }
2165
- }
2166
- }
2400
+ onTextChunk: createStructuredOutputStreamer(args, id, opId)
2167
2401
  } : {}
2168
2402
  },
2169
2403
  reasonMeta,
@@ -2283,6 +2517,7 @@ ${tryStringify2(args.response)}`
2283
2517
  const second = await reasonEngine(
2284
2518
  {
2285
2519
  ...args,
2520
+ ...inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {},
2286
2521
  emit: suppressTextStream ? false : args.emit,
2287
2522
  stream: suppressTextStream ? false : args.stream
2288
2523
  },
@@ -4629,7 +4864,7 @@ var responseSchema = import_zod3.z.discriminatedUnion("type", [
4629
4864
  import_zod3.z.object({ type: import_zod3.z.literal("cancel") }).strict()
4630
4865
  ]);
4631
4866
  function resultFromOutcome(outcome, runId, sessionId) {
4632
- const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
4867
+ const usage = outcome.pending?.schema.meta?.__kortyxResumeStatePatch?.tokenUsage ?? outcome.error?.__kortyxHookStatePatch?.tokenUsage ?? outcome.state.runtime?.tokenUsage;
4633
4868
  const info = {
4634
4869
  runId,
4635
4870
  sessionId,