@kortyx/hooks 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -129,7 +129,7 @@ var toTokenUsageDelta = (usage) => {
129
129
  if (!usage) return void 0;
130
130
  const input = usage.input ?? 0;
131
131
  const output = usage.output ?? 0;
132
- const total = usage.total ?? input + output + (usage.reasoning ?? 0);
132
+ const total = usage.total ?? input + output + (usage.outputIncludesReasoning ? 0 : usage.reasoning ?? 0);
133
133
  if (input === 0 && output === 0 && total === 0) {
134
134
  return void 0;
135
135
  }
@@ -427,7 +427,7 @@ var emitNodeEvent = (emitEvent, nodeId, event, payload) => {
427
427
  });
428
428
  };
429
429
  async function runReasonEngine(args) {
430
- const stream = args.stream ?? args.model.options?.streaming ?? true;
430
+ let stream = args.stream ?? args.model.options?.streaming ?? true;
431
431
  const emit = args.emit ?? true;
432
432
  const temperature = args.temperature ?? args.model.options?.temperature ?? args.defaultTemperature;
433
433
  const maxOutputTokens = args.maxOutputTokens ?? args.model.options?.maxOutputTokens;
@@ -435,7 +435,14 @@ async function runReasonEngine(args) {
435
435
  const abortSignal = args.abortSignal ?? args.model.options?.abortSignal;
436
436
  const reasoning = args.reasoning ?? args.model.options?.reasoning;
437
437
  const responseFormat = args.responseFormat ?? args.model.options?.responseFormat;
438
- const providerOptions = args.providerOptions ?? args.model.options?.providerOptions;
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
+ }
439
446
  const tools = args.tools ?? args.model.options?.tools;
440
447
  (0, import_core2.throwIfExecutionAborted)(abortSignal);
441
448
  const model = args.model.provider.getModel(args.model.modelId, {
@@ -449,6 +456,7 @@ async function runReasonEngine(args) {
449
456
  ...tools !== void 0 ? { tools } : {},
450
457
  ...providerOptions !== void 0 ? { providerOptions } : {}
451
458
  });
459
+ if (tools?.length && !model.supportsToolStreaming) stream = false;
452
460
  const messages = args.messages !== void 0 ? args.messages : [
453
461
  ...typeof args.system === "string" && args.system.length > 0 ? [{ role: "system", content: args.system }] : [],
454
462
  { role: "user", content: String(args.input ?? "") }
@@ -476,7 +484,9 @@ async function runReasonEngine(args) {
476
484
  },
477
485
  telemetry: {
478
486
  ...args.telemetry ?? {},
479
- input: args.telemetry?.input ?? messages
487
+ input: args.telemetry?.input ?? messages.map(
488
+ ({ continuation: _continuation, raw: _raw, ...message }) => message
489
+ )
480
490
  }
481
491
  });
482
492
  const modelRequestStartedAt = Date.now();
@@ -507,7 +517,11 @@ async function runReasonEngine(args) {
507
517
  });
508
518
  };
509
519
  const failTrace = (span, error) => {
520
+ const detail = error;
510
521
  span?.fail?.(error, {
522
+ ...detail?.usage ? { usage: detail.usage } : {},
523
+ ...detail?.finishReason ? { finishReason: detail.finishReason } : {},
524
+ ...detail?.providerMetadata ? { providerMetadata: detail.providerMetadata } : {},
511
525
  attributes: {
512
526
  providerId: args.model.provider.id,
513
527
  modelId: args.model.modelId
@@ -516,6 +530,7 @@ async function runReasonEngine(args) {
516
530
  };
517
531
  try {
518
532
  if (stream) {
533
+ let continuation;
519
534
  let final = "";
520
535
  let raw;
521
536
  let usage;
@@ -554,6 +569,7 @@ async function runReasonEngine(args) {
554
569
  break;
555
570
  }
556
571
  case "finish": {
572
+ continuation = chunk.continuation;
557
573
  if (chunk.raw !== void 0) {
558
574
  raw = chunk.raw;
559
575
  }
@@ -602,6 +618,7 @@ async function runReasonEngine(args) {
602
618
  }
603
619
  const result2 = {
604
620
  text: final,
621
+ ...continuation ? { continuation } : {},
605
622
  ...raw !== void 0 ? { raw } : {},
606
623
  ...usage !== void 0 ? { usage } : {},
607
624
  ...finishReason !== void 0 ? { finishReason } : {},
@@ -615,6 +632,7 @@ async function runReasonEngine(args) {
615
632
  }
616
633
  const response = await model.invoke(messages);
617
634
  (0, import_core2.throwIfExecutionAborted)(abortSignal);
635
+ if (response.content) args.onTextChunk?.(response.content);
618
636
  if (emit) {
619
637
  emitNodeEvent(args.emitEvent, args.nodeId, "text-start", {
620
638
  ...commonMeta
@@ -631,6 +649,7 @@ async function runReasonEngine(args) {
631
649
  }
632
650
  const result = {
633
651
  text: response.content,
652
+ ...response.continuation ? { continuation: response.continuation } : {},
634
653
  ...response.raw !== void 0 ? { raw: response.raw } : {},
635
654
  ...response.usage !== void 0 ? { usage: response.usage } : {},
636
655
  ...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
@@ -664,480 +683,188 @@ async function reasonEngine(args, meta, inputOverride) {
664
683
  );
665
684
  (0, import_core3.throwIfExecutionAborted)(abortSignal);
666
685
  ctx.node.consumeExecution?.("maxModelPasses");
667
- return runReasonEngine({
668
- model: args.model,
669
- input: inputOverride ?? args.input,
670
- system: args.system,
671
- temperature: args.temperature,
672
- maxOutputTokens: args.maxOutputTokens,
673
- stopSequences: args.stopSequences,
674
- abortSignal,
675
- reasoning: args.reasoning,
676
- responseFormat: args.responseFormat,
677
- providerOptions: args.providerOptions,
678
- tools: args.tools,
679
- messages: args.messages,
680
- defaultTemperature: ctx.node.config?.model?.temperature,
681
- stream: args.stream,
682
- emit: args.emit,
683
- onTextChunk: args.onTextChunk,
684
- telemetry: args.telemetry,
685
- nodeId: ctx.node.graph.node,
686
- emitEvent: ctx.node.emit,
687
- reasonTrace: ctx.reasonTrace,
688
- id: meta.id,
689
- opId: meta.opId
690
- });
686
+ try {
687
+ return await runReasonEngine({
688
+ model: args.model,
689
+ input: inputOverride ?? args.input,
690
+ system: args.system,
691
+ temperature: args.temperature,
692
+ maxOutputTokens: args.maxOutputTokens,
693
+ stopSequences: args.stopSequences,
694
+ abortSignal,
695
+ reasoning: args.reasoning,
696
+ responseFormat: args.responseFormat,
697
+ providerOptions: args.providerOptions,
698
+ tools: args.tools,
699
+ messages: args.messages,
700
+ defaultTemperature: ctx.node.config?.model?.temperature,
701
+ stream: args.stream,
702
+ emit: args.emit,
703
+ onTextChunk: args.onTextChunk,
704
+ telemetry: args.telemetry,
705
+ nodeId: ctx.node.graph.node,
706
+ emitEvent: ctx.node.emit,
707
+ reasonTrace: ctx.reasonTrace,
708
+ id: meta.id,
709
+ opId: meta.opId
710
+ });
711
+ } catch (error) {
712
+ const usage = error?.usage;
713
+ if (usage) accumulateTokenUsage(usage);
714
+ throw error;
715
+ }
691
716
  }
692
717
 
693
- // src/reason/parsing.ts
694
- var tryJsonParse = (value) => {
718
+ // src/reason/output-format.ts
719
+ var import_zod2 = require("zod");
720
+ function inferOutputFormat(schema, explicit) {
721
+ if (explicit) return { responseFormat: explicit };
722
+ if (!schema) return {};
695
723
  try {
696
- return { ok: true, value: JSON.parse(value) };
697
- } catch (error) {
724
+ const json = (0, import_zod2.toJSONSchema)(schema);
725
+ const clean = (value) => {
726
+ if (Array.isArray(value)) return value.map(clean);
727
+ if (!value || typeof value !== "object") return value;
728
+ const object = value;
729
+ if ([
730
+ "allOf",
731
+ "not",
732
+ "dependentRequired",
733
+ "dependentSchemas",
734
+ "if",
735
+ "then",
736
+ "else",
737
+ "prefixItems"
738
+ ].some((key) => key in object))
739
+ throw new Error(
740
+ "Schema composition requires an explicit provider schema"
741
+ );
742
+ if (object.type === "object") {
743
+ if (object.additionalProperties !== false)
744
+ throw new Error(
745
+ "Dynamic properties require an explicit provider schema"
746
+ );
747
+ const properties = object.properties;
748
+ const required = object.required;
749
+ if (properties && Object.keys(properties).some((key) => !required?.includes(key))) {
750
+ throw new Error(
751
+ "Optional properties require an explicit provider schema"
752
+ );
753
+ }
754
+ }
755
+ return Object.fromEntries(
756
+ Object.entries(object).filter(([key]) => key !== "$schema" && key !== "default").map(([key, child]) => {
757
+ if ((key === "properties" || key === "$defs" || key === "definitions") && child && typeof child === "object")
758
+ return [
759
+ key,
760
+ Object.fromEntries(
761
+ Object.entries(child).map(([name, definition]) => [
762
+ name,
763
+ clean(definition)
764
+ ])
765
+ )
766
+ ];
767
+ return [
768
+ key,
769
+ key === "enum" || key === "const" ? child : clean(child)
770
+ ];
771
+ })
772
+ );
773
+ };
774
+ if (json.type !== "object")
775
+ throw new Error("The provider schema must have an object root");
776
+ return { responseFormat: { type: "json", schema: clean(json) } };
777
+ } catch {
698
778
  return {
699
- ok: false,
700
- error: error instanceof Error ? error.message : String(error)
779
+ responseFormat: { type: "json" },
780
+ warnings: [
781
+ {
782
+ type: "compatibility",
783
+ feature: "outputSchema",
784
+ 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."
785
+ }
786
+ ]
701
787
  };
702
788
  }
703
- };
704
- var extractJsonCodeBlock = (value) => {
705
- const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
706
- if (!match || typeof match[1] !== "string") return null;
707
- const inner = match[1].trim();
708
- return inner.length > 0 ? inner : null;
709
- };
710
- var tryStringify = (value) => {
711
- if (typeof value === "string") return value;
712
- try {
713
- return JSON.stringify(value, null, 2);
714
- } catch {
715
- return String(value);
789
+ }
790
+
791
+ // src/reason/structured-stream.ts
792
+ var resolveAppendFieldPaths = (cfg) => {
793
+ const fields = cfg?.fields;
794
+ if (!fields) return [];
795
+ const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
796
+ for (const path of appendPaths) {
797
+ assertStructuredPath(path, "useReason structured append");
716
798
  }
799
+ return appendPaths;
717
800
  };
718
- var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
719
- var resolveOutputCandidate = (text) => {
720
- const trimmed = text.trim();
721
- if (!trimmed) return text;
722
- const direct = tryJsonParse(trimmed);
723
- if (direct.ok) return direct.value;
724
- const block = extractJsonCodeBlock(trimmed);
725
- if (block) {
726
- const parsedBlock = tryJsonParse(block);
727
- if (parsedBlock.ok) return parsedBlock.value;
801
+ var resolveSetFieldPaths = (cfg) => {
802
+ const fields = cfg?.fields;
803
+ if (!fields) return [];
804
+ const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
805
+ for (const path of setPaths) {
806
+ assertStructuredPath(path, "useReason structured set");
728
807
  }
729
- return text;
730
- };
731
- var looksLikeStructuredJsonAttempt = (value) => {
732
- const trimmed = value.trim();
733
- if (!trimmed) return false;
734
- return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
735
- };
736
- var isLikelyTruncatedStructuredOutput = (text) => {
737
- const trimmed = text.trim();
738
- if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
739
- const direct = tryJsonParse(trimmed);
740
- if (!direct.ok && direct.error) return true;
741
- const block = extractJsonCodeBlock(trimmed);
742
- if (!block) return false;
743
- const parsedBlock = tryJsonParse(block);
744
- return !parsedBlock.ok && Boolean(parsedBlock.error);
808
+ return setPaths;
745
809
  };
746
- var createTruncatedStructuredOutputError = (label) => new Error(
747
- `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
748
- );
749
- var createInvalidStructuredOutputError = (label) => new Error(
750
- `${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.`
751
- );
752
- var parseReasonOutputWithSchema = (args) => {
753
- const candidate = resolveOutputCandidate(args.text);
754
- try {
755
- return parseWithSchema(args.schema, candidate, args.label);
756
- } catch (error) {
757
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
758
- throw createTruncatedStructuredOutputError(args.label);
759
- }
760
- if (typeof candidate === "string" && candidate.trim().length > 0) {
761
- throw createInvalidStructuredOutputError(args.label);
762
- }
763
- throw error;
810
+ var resolveTextDeltaFieldPaths = (cfg) => {
811
+ const fields = cfg?.fields;
812
+ if (!fields) return [];
813
+ const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
814
+ for (const path of textPaths) {
815
+ assertStructuredPath(path, "useReason structured text-delta");
764
816
  }
817
+ return textPaths;
765
818
  };
766
- var parseInterruptFirstPassResult = (args) => {
767
- const candidate = resolveOutputCandidate(args.text);
768
- if (!isRecord2(candidate)) {
769
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
770
- throw createTruncatedStructuredOutputError("useReason output");
819
+ var extractCompletedStringValueAt = (args) => {
820
+ let value = "";
821
+ for (let i = args.start; i < args.text.length; i += 1) {
822
+ const ch = args.text[i];
823
+ if (ch === void 0) break;
824
+ if (ch === '"') {
825
+ return {
826
+ complete: true,
827
+ value
828
+ };
771
829
  }
772
- if (typeof candidate === "string" && candidate.trim().length > 0) {
773
- throw createInvalidStructuredOutputError("useReason output");
830
+ if (ch !== "\\") {
831
+ value += ch;
832
+ continue;
774
833
  }
775
- throw new Error(
776
- "useReason first pass with interrupt must return a JSON object."
777
- );
778
- }
779
- const mode = args.mode ?? "required";
780
- let interruptRequired = true;
781
- if (mode === "optional") {
782
- if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
783
- throw new Error(
784
- 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
785
- );
834
+ const next = args.text[i + 1];
835
+ if (!next) {
836
+ return {
837
+ complete: false
838
+ };
786
839
  }
787
- interruptRequired = candidate.decision === "interrupt";
788
- if (!interruptRequired) {
789
- const hasUnexpectedRequest = [
790
- candidate.interruptRequest,
791
- candidate.request,
792
- candidate.interrupt
793
- ].some((value) => value !== null && value !== void 0);
794
- if (hasUnexpectedRequest) {
795
- throw new Error(
796
- 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
797
- );
798
- }
840
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
841
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
842
+ i += 1;
843
+ continue;
799
844
  }
800
- }
801
- let request;
802
- if (interruptRequired) {
803
- const requestCandidates = [
804
- candidate.interruptRequest,
805
- candidate.request,
806
- candidate.interrupt,
807
- mode === "required" ? candidate : void 0
808
- ];
809
- const requestPayload = requestCandidates.find(
810
- (value) => value !== null && value !== void 0
811
- );
812
- request = parseWithSchema(
813
- args.requestSchema,
814
- requestPayload,
815
- "useReason interrupt.request"
816
- );
817
- }
818
- let output;
819
- if (args.outputSchema) {
820
- const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
821
- try {
822
- output = parseWithSchema(
823
- args.outputSchema,
824
- outputPayload,
825
- "useReason output"
826
- );
827
- } catch (error) {
828
- if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
829
- throw createTruncatedStructuredOutputError("useReason output");
830
- }
831
- if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
832
- throw createInvalidStructuredOutputError("useReason output");
845
+ if (next === "u") {
846
+ const hex = args.text.slice(i + 2, i + 6);
847
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
848
+ return {
849
+ complete: false
850
+ };
833
851
  }
834
- throw error;
852
+ value += String.fromCharCode(Number.parseInt(hex, 16));
853
+ i += 5;
854
+ continue;
835
855
  }
836
- }
837
- const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
838
- if (!args.outputSchema && !explicitDraft) {
839
- throw new Error(
840
- "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
841
- );
842
- }
843
- const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
844
- if (!interruptRequired) {
845
856
  return {
846
- draftText,
847
- interruptRequired: false,
848
- ...output !== void 0 ? { output } : {}
857
+ complete: false
849
858
  };
850
859
  }
851
860
  return {
852
- draftText,
853
- interruptRequired: true,
854
- request,
855
- ...output !== void 0 ? { output } : {}
861
+ complete: false
856
862
  };
857
863
  };
858
-
859
- // src/reason/prompting.ts
860
- var import_zod2 = 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_zod2.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
-
1005
- // src/reason/result.ts
1006
- var mergeUsage = (left, right) => {
1007
- if (!left) return right;
1008
- if (!right) return left;
1009
- const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1010
- const raw = left.raw || right.raw ? {
1011
- ...left.raw ?? {},
1012
- ...right.raw ?? {}
1013
- } : void 0;
1014
- const input = sum(left.input, right.input);
1015
- const output = sum(left.output, right.output);
1016
- const total = sum(left.total, right.total);
1017
- const reasoning = sum(left.reasoning, right.reasoning);
1018
- const cacheRead = sum(left.cacheRead, right.cacheRead);
1019
- const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1020
- const merged = {};
1021
- if (input !== void 0) merged.input = input;
1022
- if (output !== void 0) merged.output = output;
1023
- if (total !== void 0) merged.total = total;
1024
- if (reasoning !== void 0) merged.reasoning = reasoning;
1025
- if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1026
- if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1027
- if (raw) merged.raw = raw;
1028
- return merged;
1029
- };
1030
- var mergeWarnings = (left, right) => {
1031
- if (!left?.length) return right;
1032
- if (!right?.length) return left;
1033
- const seen = /* @__PURE__ */ new Set();
1034
- const merged = [];
1035
- for (const warning of [...left, ...right]) {
1036
- const key = JSON.stringify(warning);
1037
- if (seen.has(key)) continue;
1038
- seen.add(key);
1039
- merged.push(warning);
1040
- }
1041
- return merged;
1042
- };
1043
- var mergeProviderMetadata = (left, right) => {
1044
- if (!left) return right;
1045
- if (!right) return left;
1046
- return {
1047
- ...left,
1048
- ...right
1049
- };
1050
- };
1051
- var emitReasonStructuredOutput = (args) => {
1052
- if (!args.emit || !shouldEmitStructured(args.structured)) return;
1053
- emitStructuredData({
1054
- dataType: args.structured?.dataType ?? "reason-output",
1055
- data: args.output,
1056
- kind: "final",
1057
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1058
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1059
- ...args.id ? { id: args.id } : {},
1060
- streamId: args.opId
1061
- });
1062
- };
1063
-
1064
- // src/reason/structured-stream.ts
1065
- var resolveAppendFieldPaths = (cfg) => {
1066
- const fields = cfg?.fields;
1067
- if (!fields) return [];
1068
- const appendPaths = Object.entries(fields).filter(([, mode]) => mode === "append").map(([path]) => path);
1069
- for (const path of appendPaths) {
1070
- assertStructuredPath(path, "useReason structured append");
1071
- }
1072
- return appendPaths;
1073
- };
1074
- var resolveSetFieldPaths = (cfg) => {
1075
- const fields = cfg?.fields;
1076
- if (!fields) return [];
1077
- const setPaths = Object.entries(fields).filter(([, mode]) => mode === "set").map(([path]) => path);
1078
- for (const path of setPaths) {
1079
- assertStructuredPath(path, "useReason structured set");
1080
- }
1081
- return setPaths;
1082
- };
1083
- var resolveTextDeltaFieldPaths = (cfg) => {
1084
- const fields = cfg?.fields;
1085
- if (!fields) return [];
1086
- const textPaths = Object.entries(fields).filter(([, mode]) => mode === "text-delta").map(([path]) => path);
1087
- for (const path of textPaths) {
1088
- assertStructuredPath(path, "useReason structured text-delta");
1089
- }
1090
- return textPaths;
1091
- };
1092
- var extractCompletedStringValueAt = (args) => {
1093
- let value = "";
1094
- for (let i = args.start; i < args.text.length; i += 1) {
1095
- const ch = args.text[i];
1096
- if (ch === void 0) break;
1097
- if (ch === '"') {
1098
- return {
1099
- complete: true,
1100
- value
1101
- };
1102
- }
1103
- if (ch !== "\\") {
1104
- value += ch;
1105
- continue;
1106
- }
1107
- const next = args.text[i + 1];
1108
- if (!next) {
1109
- return {
1110
- complete: false
1111
- };
1112
- }
1113
- if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1114
- value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1115
- i += 1;
1116
- continue;
1117
- }
1118
- if (next === "u") {
1119
- const hex = args.text.slice(i + 2, i + 6);
1120
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1121
- return {
1122
- complete: false
1123
- };
1124
- }
1125
- value += String.fromCharCode(Number.parseInt(hex, 16));
1126
- i += 5;
1127
- continue;
1128
- }
1129
- return {
1130
- complete: false
1131
- };
1132
- }
1133
- return {
1134
- complete: false
1135
- };
1136
- };
1137
- var skipWhitespace = (text, start) => {
1138
- let i = start;
1139
- while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
1140
- return i;
864
+ var skipWhitespace = (text, start) => {
865
+ let i = start;
866
+ while (i < text.length && /\s/.test(text[i] ?? "")) i += 1;
867
+ return i;
1141
868
  };
1142
869
  var pathSegmentMatches = (patternPart, actualPart) => patternPart === "*" || patternPart === actualPart;
1143
870
  var pathMatchesPattern = (actualParts, patternParts) => actualParts.length === patternParts.length && actualParts.every(
@@ -1521,103 +1248,560 @@ var extractCompletedArrayItems = (args) => {
1521
1248
  }
1522
1249
  return items;
1523
1250
  };
1524
- var extractCompletedArrayItemGroups = (args) => {
1525
- return locateValueStarts(args).flatMap((match) => {
1526
- const items = extractCompletedArrayItems({
1527
- text: args.text,
1528
- path: match.path
1529
- });
1530
- return items.length > 0 ? [
1531
- {
1532
- path: match.path,
1533
- items
1534
- }
1535
- ] : [];
1536
- });
1251
+ var extractCompletedArrayItemGroups = (args) => {
1252
+ return locateValueStarts(args).flatMap((match) => {
1253
+ const items = extractCompletedArrayItems({
1254
+ text: args.text,
1255
+ path: match.path
1256
+ });
1257
+ return items.length > 0 ? [
1258
+ {
1259
+ path: match.path,
1260
+ items
1261
+ }
1262
+ ] : [];
1263
+ });
1264
+ };
1265
+ var extractStreamingStringValue = (args) => {
1266
+ const located = locateValueStart(args);
1267
+ if (!located.found || located.start === void 0) {
1268
+ return {
1269
+ found: located.found,
1270
+ complete: false,
1271
+ value: ""
1272
+ };
1273
+ }
1274
+ const stringStart = skipWhitespace(args.text, located.start);
1275
+ if (args.text[stringStart] !== '"') {
1276
+ return {
1277
+ found: true,
1278
+ complete: false,
1279
+ value: ""
1280
+ };
1281
+ }
1282
+ let value = "";
1283
+ for (let i = stringStart + 1; i < args.text.length; i += 1) {
1284
+ const ch = args.text[i];
1285
+ if (ch === void 0) break;
1286
+ if (ch === '"') {
1287
+ return {
1288
+ found: true,
1289
+ complete: true,
1290
+ value
1291
+ };
1292
+ }
1293
+ if (ch !== "\\") {
1294
+ value += ch;
1295
+ continue;
1296
+ }
1297
+ const next = args.text[i + 1];
1298
+ if (!next) {
1299
+ return {
1300
+ found: true,
1301
+ complete: false,
1302
+ value
1303
+ };
1304
+ }
1305
+ if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1306
+ value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1307
+ i += 1;
1308
+ continue;
1309
+ }
1310
+ if (next === "u") {
1311
+ const hex = args.text.slice(i + 2, i + 6);
1312
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1313
+ return {
1314
+ found: true,
1315
+ complete: false,
1316
+ value
1317
+ };
1318
+ }
1319
+ value += String.fromCharCode(Number.parseInt(hex, 16));
1320
+ i += 5;
1321
+ continue;
1322
+ }
1323
+ return {
1324
+ found: true,
1325
+ complete: false,
1326
+ value
1327
+ };
1328
+ }
1329
+ return {
1330
+ found: true,
1331
+ complete: false,
1332
+ value
1333
+ };
1334
+ };
1335
+ var extractStreamingStringValues = (args) => {
1336
+ return locateValueStarts(args).flatMap((match) => {
1337
+ const current = extractStreamingStringValue({
1338
+ text: args.text,
1339
+ path: match.path
1340
+ });
1341
+ return current.found ? [
1342
+ {
1343
+ path: match.path,
1344
+ complete: current.complete,
1345
+ value: current.value
1346
+ }
1347
+ ] : [];
1348
+ });
1349
+ };
1350
+
1351
+ // src/reason/output-stream.ts
1352
+ function createStructuredOutputStreamer(args, id, opId) {
1353
+ let firstText = "";
1354
+ const setFieldPaths = resolveSetFieldPaths(args.structured);
1355
+ const appendFieldPaths = resolveAppendFieldPaths(args.structured);
1356
+ const textDeltaFieldPaths = resolveTextDeltaFieldPaths(args.structured);
1357
+ const emittedSetValues = /* @__PURE__ */ new Map();
1358
+ const emittedAppendCounts = /* @__PURE__ */ new Map();
1359
+ const emittedTextValues = /* @__PURE__ */ new Map();
1360
+ return (delta) => {
1361
+ firstText += delta;
1362
+ for (const fieldPath of setFieldPaths) {
1363
+ const values = extractCompletedFieldValues({
1364
+ text: firstText,
1365
+ path: fieldPath
1366
+ });
1367
+ for (const { path, value } of values) {
1368
+ const nextSerialized = JSON.stringify(value);
1369
+ if (emittedSetValues.get(path) === nextSerialized) continue;
1370
+ emittedSetValues.set(path, nextSerialized);
1371
+ emitStructuredData({
1372
+ kind: "set",
1373
+ path,
1374
+ value,
1375
+ dataType: args.structured?.dataType ?? "reason-output",
1376
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1377
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1378
+ ...id ? { id } : {},
1379
+ streamId: opId
1380
+ });
1381
+ }
1382
+ }
1383
+ for (const fieldPath of appendFieldPaths) {
1384
+ const groups = extractCompletedArrayItemGroups({
1385
+ text: firstText,
1386
+ path: fieldPath
1387
+ });
1388
+ for (const { path, items } of groups) {
1389
+ const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
1390
+ if (items.length <= emittedAppendCount) continue;
1391
+ const nextItems = items.slice(emittedAppendCount);
1392
+ emittedAppendCounts.set(path, items.length);
1393
+ emitStructuredData({
1394
+ kind: "append",
1395
+ path,
1396
+ items: nextItems,
1397
+ dataType: args.structured?.dataType ?? "reason-output",
1398
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1399
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1400
+ ...id ? { id } : {},
1401
+ streamId: opId
1402
+ });
1403
+ }
1404
+ }
1405
+ for (const fieldPath of textDeltaFieldPaths) {
1406
+ const values = extractStreamingStringValues({
1407
+ text: firstText,
1408
+ path: fieldPath
1409
+ });
1410
+ for (const { path, value } of values) {
1411
+ const emittedTextValue = emittedTextValues.get(path) ?? "";
1412
+ if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
1413
+ continue;
1414
+ }
1415
+ const nextDelta = value.slice(emittedTextValue.length);
1416
+ emittedTextValues.set(path, value);
1417
+ if (nextDelta.length === 0) continue;
1418
+ emitStructuredData({
1419
+ kind: "text-delta",
1420
+ path,
1421
+ delta: nextDelta,
1422
+ dataType: args.structured?.dataType ?? "reason-output",
1423
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1424
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1425
+ ...id ? { id } : {},
1426
+ streamId: opId
1427
+ });
1428
+ }
1429
+ }
1430
+ };
1431
+ }
1432
+
1433
+ // src/reason/parsing.ts
1434
+ var tryJsonParse = (value) => {
1435
+ try {
1436
+ return { ok: true, value: JSON.parse(value) };
1437
+ } catch (error) {
1438
+ return {
1439
+ ok: false,
1440
+ error: error instanceof Error ? error.message : String(error)
1441
+ };
1442
+ }
1443
+ };
1444
+ var extractJsonCodeBlock = (value) => {
1445
+ const match = value.match(/```(?:json)?\s*([\s\S]*?)```/i);
1446
+ if (!match || typeof match[1] !== "string") return null;
1447
+ const inner = match[1].trim();
1448
+ return inner.length > 0 ? inner : null;
1449
+ };
1450
+ var tryStringify = (value) => {
1451
+ if (typeof value === "string") return value;
1452
+ try {
1453
+ return JSON.stringify(value, null, 2);
1454
+ } catch {
1455
+ return String(value);
1456
+ }
1457
+ };
1458
+ var isRecord2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1459
+ var resolveOutputCandidate = (text) => {
1460
+ const trimmed = text.trim();
1461
+ if (!trimmed) return text;
1462
+ const direct = tryJsonParse(trimmed);
1463
+ if (direct.ok) return direct.value;
1464
+ const block = extractJsonCodeBlock(trimmed);
1465
+ if (block) {
1466
+ const parsedBlock = tryJsonParse(block);
1467
+ if (parsedBlock.ok) return parsedBlock.value;
1468
+ }
1469
+ return text;
1470
+ };
1471
+ var looksLikeStructuredJsonAttempt = (value) => {
1472
+ const trimmed = value.trim();
1473
+ if (!trimmed) return false;
1474
+ return trimmed.startsWith("{") || trimmed.startsWith("[") || /^```(?:json)?/i.test(trimmed);
1475
+ };
1476
+ var isLikelyTruncatedStructuredOutput = (text) => {
1477
+ const trimmed = text.trim();
1478
+ if (!looksLikeStructuredJsonAttempt(trimmed)) return false;
1479
+ const direct = tryJsonParse(trimmed);
1480
+ if (!direct.ok && direct.error) return true;
1481
+ const block = extractJsonCodeBlock(trimmed);
1482
+ if (!block) return false;
1483
+ const parsedBlock = tryJsonParse(block);
1484
+ return !parsedBlock.ok && Boolean(parsedBlock.error);
1485
+ };
1486
+ var createTruncatedStructuredOutputError = (label) => new Error(
1487
+ `${label} was truncated before producing valid structured output. The model stopped due to output length. Increase maxOutputTokens or simplify the requested output.`
1488
+ );
1489
+ var createInvalidStructuredOutputError = (label) => new Error(
1490
+ `${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.`
1491
+ );
1492
+ var parseReasonOutputWithSchema = (args) => {
1493
+ const candidate = resolveOutputCandidate(args.text);
1494
+ try {
1495
+ return parseWithSchema(args.schema, candidate, args.label);
1496
+ } catch (error) {
1497
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1498
+ throw createTruncatedStructuredOutputError(args.label);
1499
+ }
1500
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1501
+ throw createInvalidStructuredOutputError(args.label);
1502
+ }
1503
+ throw error;
1504
+ }
1505
+ };
1506
+ var parseInterruptFirstPassResult = (args) => {
1507
+ const candidate = resolveOutputCandidate(args.text);
1508
+ if (!isRecord2(candidate)) {
1509
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1510
+ throw createTruncatedStructuredOutputError("useReason output");
1511
+ }
1512
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
1513
+ throw createInvalidStructuredOutputError("useReason output");
1514
+ }
1515
+ throw new Error(
1516
+ "useReason first pass with interrupt must return a JSON object."
1517
+ );
1518
+ }
1519
+ const mode = args.mode ?? "required";
1520
+ let interruptRequired = true;
1521
+ if (mode === "optional") {
1522
+ if (candidate.decision !== "continue" && candidate.decision !== "interrupt") {
1523
+ throw new Error(
1524
+ 'useReason optional interrupt first pass must include decision "continue" or "interrupt".'
1525
+ );
1526
+ }
1527
+ interruptRequired = candidate.decision === "interrupt";
1528
+ if (!interruptRequired) {
1529
+ const hasUnexpectedRequest = [
1530
+ candidate.interruptRequest,
1531
+ candidate.request,
1532
+ candidate.interrupt
1533
+ ].some((value) => value !== null && value !== void 0);
1534
+ if (hasUnexpectedRequest) {
1535
+ throw new Error(
1536
+ 'useReason optional interrupt first pass with decision "continue" must not include an interrupt request.'
1537
+ );
1538
+ }
1539
+ }
1540
+ }
1541
+ let request;
1542
+ if (interruptRequired) {
1543
+ const requestCandidates = [
1544
+ candidate.interruptRequest,
1545
+ candidate.request,
1546
+ candidate.interrupt,
1547
+ mode === "required" ? candidate : void 0
1548
+ ];
1549
+ const requestPayload = requestCandidates.find(
1550
+ (value) => value !== null && value !== void 0
1551
+ );
1552
+ request = parseWithSchema(
1553
+ args.requestSchema,
1554
+ requestPayload,
1555
+ "useReason interrupt.request"
1556
+ );
1557
+ }
1558
+ let output;
1559
+ if (args.outputSchema) {
1560
+ const outputPayload = Object.hasOwn(candidate, "output") ? candidate.output : candidate;
1561
+ try {
1562
+ output = parseWithSchema(
1563
+ args.outputSchema,
1564
+ outputPayload,
1565
+ "useReason output"
1566
+ );
1567
+ } catch (error) {
1568
+ if (args.finishReason?.unified === "length" || isLikelyTruncatedStructuredOutput(args.text)) {
1569
+ throw createTruncatedStructuredOutputError("useReason output");
1570
+ }
1571
+ if (typeof outputPayload === "string" && outputPayload.trim().length > 0) {
1572
+ throw createInvalidStructuredOutputError("useReason output");
1573
+ }
1574
+ throw error;
1575
+ }
1576
+ }
1577
+ const explicitDraft = typeof candidate.draftText === "string" && candidate.draftText.length > 0 ? candidate.draftText : typeof candidate.text === "string" && candidate.text.length > 0 ? candidate.text : void 0;
1578
+ if (!args.outputSchema && !explicitDraft) {
1579
+ throw new Error(
1580
+ "useReason first pass with interrupt requires `draftText` when outputSchema is not provided."
1581
+ );
1582
+ }
1583
+ const draftText = explicitDraft ?? (output !== void 0 ? tryStringify(output) : String(args.text ?? ""));
1584
+ if (!interruptRequired) {
1585
+ return {
1586
+ draftText,
1587
+ interruptRequired: false,
1588
+ ...output !== void 0 ? { output } : {}
1589
+ };
1590
+ }
1591
+ return {
1592
+ draftText,
1593
+ interruptRequired: true,
1594
+ request,
1595
+ ...output !== void 0 ? { output } : {}
1596
+ };
1597
+ };
1598
+
1599
+ // src/reason/prompting.ts
1600
+ var import_zod3 = 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_zod3.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);
1537
1712
  };
1538
- var extractStreamingStringValue = (args) => {
1539
- const located = locateValueStart(args);
1540
- if (!located.found || located.start === void 0) {
1541
- return {
1542
- found: located.found,
1543
- complete: false,
1544
- value: ""
1545
- };
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
+ );
1546
1719
  }
1547
- const stringStart = skipWhitespace(args.text, located.start);
1548
- if (args.text[stringStart] !== '"') {
1549
- return {
1550
- found: true,
1551
- complete: false,
1552
- value: ""
1553
- };
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
+ );
1554
1727
  }
1555
- let value = "";
1556
- for (let i = stringStart + 1; i < args.text.length; i += 1) {
1557
- const ch = args.text[i];
1558
- if (ch === void 0) break;
1559
- if (ch === '"') {
1560
- return {
1561
- found: true,
1562
- complete: true,
1563
- value
1564
- };
1565
- }
1566
- if (ch !== "\\") {
1567
- value += ch;
1568
- continue;
1569
- }
1570
- const next = args.text[i + 1];
1571
- if (!next) {
1572
- return {
1573
- found: true,
1574
- complete: false,
1575
- value
1576
- };
1577
- }
1578
- if (next === '"' || next === "\\" || next === "/" || next === "b" || next === "f" || next === "n" || next === "r" || next === "t") {
1579
- value += next === "b" ? "\b" : next === "f" ? "\f" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : next;
1580
- i += 1;
1581
- continue;
1582
- }
1583
- if (next === "u") {
1584
- const hex = args.text.slice(i + 2, i + 6);
1585
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1586
- return {
1587
- found: true,
1588
- complete: false,
1589
- value
1590
- };
1591
- }
1592
- value += String.fromCharCode(Number.parseInt(hex, 16));
1593
- i += 5;
1594
- continue;
1595
- }
1596
- return {
1597
- found: true,
1598
- complete: false,
1599
- value
1600
- };
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
+
1745
+ // src/reason/result.ts
1746
+ var mergeUsage = (left, right) => {
1747
+ if (!left) return right;
1748
+ if (!right) return left;
1749
+ const sum = (a, b) => a === void 0 && b === void 0 ? void 0 : (a ?? 0) + (b ?? 0);
1750
+ const raw = left.raw || right.raw ? {
1751
+ ...left.raw ?? {},
1752
+ ...right.raw ?? {}
1753
+ } : void 0;
1754
+ const input = sum(left.input, right.input);
1755
+ const output = sum(left.output, right.output);
1756
+ const total = sum(left.total, right.total);
1757
+ const reasoning = sum(left.reasoning, right.reasoning);
1758
+ const cacheRead = sum(left.cacheRead, right.cacheRead);
1759
+ const cacheWrite = sum(left.cacheWrite, right.cacheWrite);
1760
+ const merged = {
1761
+ ...right.outputIncludesReasoning !== void 0 ? { outputIncludesReasoning: right.outputIncludesReasoning } : {},
1762
+ ...right.inputIncludesCacheWrite !== void 0 ? { inputIncludesCacheWrite: right.inputIncludesCacheWrite } : {},
1763
+ ...right.inputIncludesCacheRead !== void 0 ? { inputIncludesCacheRead: right.inputIncludesCacheRead } : {}
1764
+ };
1765
+ if (input !== void 0) merged.input = input;
1766
+ if (output !== void 0) merged.output = output;
1767
+ if (total !== void 0) merged.total = total;
1768
+ if (reasoning !== void 0) merged.reasoning = reasoning;
1769
+ if (cacheRead !== void 0) merged.cacheRead = cacheRead;
1770
+ if (cacheWrite !== void 0) merged.cacheWrite = cacheWrite;
1771
+ if (raw) merged.raw = raw;
1772
+ return merged;
1773
+ };
1774
+ var mergeWarnings = (left, right) => {
1775
+ if (!left?.length) return right;
1776
+ if (!right?.length) return left;
1777
+ const seen = /* @__PURE__ */ new Set();
1778
+ const merged = [];
1779
+ for (const warning of [...left, ...right]) {
1780
+ const key = JSON.stringify(warning);
1781
+ if (seen.has(key)) continue;
1782
+ seen.add(key);
1783
+ merged.push(warning);
1601
1784
  }
1785
+ return merged;
1786
+ };
1787
+ var mergeProviderMetadata = (left, right) => {
1788
+ if (!left) return right;
1789
+ if (!right) return left;
1602
1790
  return {
1603
- found: true,
1604
- complete: false,
1605
- value
1791
+ ...left,
1792
+ ...right
1606
1793
  };
1607
1794
  };
1608
- var extractStreamingStringValues = (args) => {
1609
- return locateValueStarts(args).flatMap((match) => {
1610
- const current = extractStreamingStringValue({
1611
- text: args.text,
1612
- path: match.path
1613
- });
1614
- return current.found ? [
1615
- {
1616
- path: match.path,
1617
- complete: current.complete,
1618
- value: current.value
1619
- }
1620
- ] : [];
1795
+ var emitReasonStructuredOutput = (args) => {
1796
+ if (!args.emit || !shouldEmitStructured(args.structured)) return;
1797
+ emitStructuredData({
1798
+ dataType: args.structured?.dataType ?? "reason-output",
1799
+ data: args.output,
1800
+ kind: "final",
1801
+ ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
1802
+ ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
1803
+ ...args.id ? { id: args.id } : {},
1804
+ streamId: args.opId
1621
1805
  });
1622
1806
  };
1623
1807
 
@@ -1674,13 +1858,16 @@ var createInitialMessages = (args) => [
1674
1858
  { role: "user", content: String(args.input ?? "") }
1675
1859
  ];
1676
1860
  var runReasonToolLoop = async (args) => {
1677
- const { useReasonArgs, id, opId, traceSpan } = args;
1861
+ const { useReasonArgs, id, traceSpan, checkpointKey } = args;
1678
1862
  const ctx = getHookContext();
1679
1863
  const abortSignal = (0, import_core4.combineAbortSignals)(
1680
1864
  ctx.node.abortSignal,
1681
1865
  useReasonArgs.abortSignal,
1682
1866
  useReasonArgs.model.options?.abortSignal
1683
1867
  );
1868
+ const saved = ctx.currentNodeState.byKey[checkpointKey];
1869
+ const checkpoint = saved?.status === "tool_loop" ? saved : void 0;
1870
+ const opId = checkpoint?.opId ?? args.opId;
1684
1871
  const tools = useReasonArgs.tools ?? [];
1685
1872
  const toolByName = /* @__PURE__ */ new Map();
1686
1873
  let validationCompleted = false;
@@ -1706,20 +1893,43 @@ var runReasonToolLoop = async (args) => {
1706
1893
  }
1707
1894
  const toolDefinitions = toToolDefinitions(tools);
1708
1895
  const maxSteps = Math.max(1, useReasonArgs.toolExecution?.maxSteps ?? 3);
1709
- const messages = createInitialMessages({
1896
+ const messages = checkpoint?.messages ?? createInitialMessages({
1710
1897
  system: useReasonArgs.system,
1711
- input: useReasonArgs.input
1898
+ input: useReasonArgs.outputSchema ? withOutputGuardrails(useReasonArgs.input, useReasonArgs.outputSchema) : useReasonArgs.input
1712
1899
  });
1713
- const steps = [];
1714
- const allToolCalls = [];
1715
- const allToolResults = [];
1900
+ const steps = checkpoint?.steps ?? [];
1901
+ const allToolCalls = steps.flatMap(
1902
+ (step) => step.toolCalls
1903
+ );
1904
+ const allToolResults = steps.flatMap(
1905
+ (step) => step.toolResults
1906
+ );
1716
1907
  let finalText = "";
1717
1908
  let finalRaw;
1718
1909
  let finalOutput;
1719
- let aggregatedUsage;
1910
+ let aggregatedUsage = steps.reduce(
1911
+ (usage, step) => mergeUsage(usage, step.usage),
1912
+ void 0
1913
+ );
1720
1914
  let finalFinishReason;
1721
1915
  let aggregatedProviderMetadata;
1722
- let aggregatedWarnings;
1916
+ let aggregatedWarnings = steps.reduce(
1917
+ (warnings, step) => mergeWarnings(warnings, step.warnings),
1918
+ args.initialWarnings
1919
+ );
1920
+ let pending = checkpoint?.pending;
1921
+ const approvedCalls = checkpoint?.approvedCalls ?? [];
1922
+ const save = () => {
1923
+ ctx.currentNodeState.byKey[checkpointKey] = {
1924
+ status: "tool_loop",
1925
+ opId,
1926
+ messages,
1927
+ steps,
1928
+ approvedCalls,
1929
+ ...pending ? { pending } : {}
1930
+ };
1931
+ ctx.stateDirty = true;
1932
+ };
1723
1933
  const emitToolEvent = (event, payload) => {
1724
1934
  ctx.node.emit(event, {
1725
1935
  ...payload,
@@ -1729,26 +1939,43 @@ var runReasonToolLoop = async (args) => {
1729
1939
  });
1730
1940
  };
1731
1941
  try {
1732
- for (let stepIndex = 0; stepIndex < maxSteps; stepIndex += 1) {
1942
+ for (let stepIndex = pending ? steps.length - 1 : steps.length; stepIndex < maxSteps; stepIndex += 1) {
1733
1943
  (0, import_core4.throwIfExecutionAborted)(abortSignal);
1734
1944
  traceSpan?.addEvent?.("useReason.tool-step.start", {
1735
1945
  stepIndex,
1736
1946
  toolCount: toolDefinitions.length
1737
1947
  });
1738
- const step = await reasonEngine(
1948
+ const reused = Boolean(pending);
1949
+ const stream = useReasonArgs.stream ?? useReasonArgs.model.options?.streaming ?? true;
1950
+ const emit = useReasonArgs.emit ?? true;
1951
+ let textStarted = false;
1952
+ const structuredChunk = useReasonArgs.outputSchema && stream && emit && shouldStreamStructured(useReasonArgs.structured) ? createStructuredOutputStreamer(useReasonArgs, id, opId) : void 0;
1953
+ const step = pending ?? await reasonEngine(
1739
1954
  {
1740
1955
  ...useReasonArgs,
1741
1956
  tools: toolDefinitions,
1742
1957
  messages,
1743
1958
  emit: false,
1744
- stream: false
1959
+ stream,
1960
+ onTextChunk: (delta) => {
1961
+ if (structuredChunk) structuredChunk(delta);
1962
+ if (!emit || useReasonArgs.outputSchema || !delta) return;
1963
+ if (!textStarted) {
1964
+ emitToolEvent("text-start", {});
1965
+ textStarted = true;
1966
+ }
1967
+ emitToolEvent("text-delta", { delta });
1968
+ }
1745
1969
  },
1746
1970
  { ...id ? { id } : {}, opId }
1747
1971
  );
1972
+ if (textStarted) emitToolEvent("text-end", {});
1748
1973
  finalText = step.text;
1749
1974
  finalRaw = step.raw;
1750
- aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1751
- accumulateTokenUsage(step.usage);
1975
+ if (!reused) {
1976
+ aggregatedUsage = mergeUsage(aggregatedUsage, step.usage);
1977
+ accumulateTokenUsage(step.usage);
1978
+ }
1752
1979
  finalFinishReason = step.finishReason;
1753
1980
  aggregatedProviderMetadata = mergeProviderMetadata(
1754
1981
  aggregatedProviderMetadata,
@@ -1756,32 +1983,35 @@ var runReasonToolLoop = async (args) => {
1756
1983
  );
1757
1984
  aggregatedWarnings = mergeWarnings(aggregatedWarnings, step.warnings);
1758
1985
  const toolCalls = step.toolCalls ?? [];
1759
- const toolResults = [];
1760
- steps.push({
1761
- stepIndex,
1762
- text: step.text,
1763
- toolCalls,
1764
- toolResults,
1765
- ...step.usage ? { usage: step.usage } : {},
1766
- ...step.finishReason ? { finishReason: step.finishReason } : {},
1767
- ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1768
- ...step.warnings ? { warnings: step.warnings } : {}
1769
- });
1986
+ const toolResults = reused ? steps[stepIndex]?.toolResults ?? [] : [];
1987
+ if (!reused)
1988
+ steps.push({
1989
+ stepIndex,
1990
+ text: step.text,
1991
+ toolCalls,
1992
+ toolResults,
1993
+ ...step.usage ? { usage: step.usage } : {},
1994
+ ...step.finishReason ? { finishReason: step.finishReason } : {},
1995
+ ...step.providerMetadata ? { providerMetadata: step.providerMetadata } : {},
1996
+ ...step.warnings ? { warnings: step.warnings } : {}
1997
+ });
1770
1998
  if (toolCalls.length === 0) {
1771
- if ((useReasonArgs.emit ?? true) && step.text.length > 0) {
1772
- emitToolEvent("text-start", {});
1773
- emitToolEvent("text-delta", { delta: step.text });
1774
- emitToolEvent("text-end", {});
1775
- }
1776
1999
  break;
1777
2000
  }
1778
- allToolCalls.push(...toolCalls);
1779
- messages.push({
1780
- role: "assistant",
1781
- content: step.text,
1782
- toolCalls
1783
- });
2001
+ if (!reused) {
2002
+ allToolCalls.push(...toolCalls);
2003
+ messages.push({
2004
+ role: "assistant",
2005
+ content: step.text,
2006
+ toolCalls,
2007
+ ...step.continuation ? { continuation: step.continuation } : {}
2008
+ });
2009
+ }
2010
+ pending = step;
2011
+ save();
1784
2012
  for (const toolCall of toolCalls) {
2013
+ if (toolResults.some((result2) => result2.toolCallId === toolCall.id))
2014
+ continue;
1785
2015
  (0, import_core4.throwIfExecutionAborted)(abortSignal);
1786
2016
  const tool = toolByName.get(toolCall.name);
1787
2017
  if (!tool) {
@@ -1811,7 +2041,7 @@ var runReasonToolLoop = async (args) => {
1811
2041
  input: toolCall.input
1812
2042
  });
1813
2043
  }
1814
- if (shouldApproveTool) {
2044
+ if (shouldApproveTool && !approvedCalls.includes(toolCall.id)) {
1815
2045
  const approval = await awaitInterruptInternal({
1816
2046
  id: `tool:${toolCall.id}`,
1817
2047
  request: {
@@ -1846,8 +2076,18 @@ var runReasonToolLoop = async (args) => {
1846
2076
  isError: true
1847
2077
  });
1848
2078
  }
2079
+ messages.push({
2080
+ role: "tool",
2081
+ content: denied.content,
2082
+ toolCallId: denied.toolCallId,
2083
+ name: denied.name,
2084
+ isError: true
2085
+ });
2086
+ save();
1849
2087
  continue;
1850
2088
  }
2089
+ approvedCalls.push(toolCall.id);
2090
+ save();
1851
2091
  }
1852
2092
  try {
1853
2093
  (0, import_core4.throwIfExecutionAborted)(abortSignal);
@@ -1901,25 +2141,19 @@ var runReasonToolLoop = async (args) => {
1901
2141
  message: result2.content
1902
2142
  });
1903
2143
  }
2144
+ const completed = toolResults.at(-1);
2145
+ if (completed)
2146
+ messages.push({
2147
+ role: "tool",
2148
+ content: completed.content,
2149
+ toolCallId: completed.toolCallId,
2150
+ name: completed.name,
2151
+ ...completed.isError !== void 0 ? { isError: completed.isError } : {}
2152
+ });
2153
+ save();
1904
2154
  }
1905
- const currentStep = steps[steps.length - 1];
1906
- if (currentStep) {
1907
- steps[steps.length - 1] = {
1908
- ...currentStep,
1909
- toolResults
1910
- };
1911
- }
1912
- for (const result2 of toolResults) {
1913
- messages.push({
1914
- role: "tool",
1915
- content: result2.content,
1916
- toolCallId: result2.toolCallId,
1917
- name: result2.name,
1918
- ...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {},
1919
- ...result2.isError !== void 0 ? { isError: result2.isError } : {},
1920
- ...result2.raw !== void 0 ? { raw: result2.raw } : {}
1921
- });
1922
- }
2155
+ pending = void 0;
2156
+ save();
1923
2157
  }
1924
2158
  if (steps.length >= maxSteps && steps.at(-1)?.toolCalls.length) {
1925
2159
  throw new Error(
@@ -1973,6 +2207,8 @@ var runReasonToolLoop = async (args) => {
1973
2207
  output: useReasonArgs.telemetry?.output ?? finalOutput ?? finalText
1974
2208
  }
1975
2209
  });
2210
+ ctx.currentNodeState.byKey[checkpointKey] = { status: "completed", result };
2211
+ ctx.stateDirty = true;
1976
2212
  return result;
1977
2213
  } catch (error) {
1978
2214
  traceSpan?.fail?.(error, {
@@ -2008,7 +2244,15 @@ async function useReason(args) {
2008
2244
  args.model.options?.abortSignal
2009
2245
  );
2010
2246
  (0, import_core5.throwIfExecutionAborted)(abortSignal);
2011
- args = { ...args, abortSignal };
2247
+ const inferred = inferOutputFormat(
2248
+ args.outputSchema,
2249
+ args.responseFormat ?? args.model.options?.responseFormat
2250
+ );
2251
+ args = {
2252
+ ...args,
2253
+ abortSignal,
2254
+ ...!args.interrupt && inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {}
2255
+ };
2012
2256
  const id = typeof args.id === "string" && args.id.length > 0 ? args.id : void 0;
2013
2257
  const opId = createRuntimeId2();
2014
2258
  const checkpointKey = resolveReasonCheckpointKey({
@@ -2052,6 +2296,8 @@ async function useReason(args) {
2052
2296
  if (args.tools?.length) {
2053
2297
  return runReasonToolLoop({
2054
2298
  useReasonArgs: args,
2299
+ checkpointKey,
2300
+ initialWarnings: inferred.warnings,
2055
2301
  ...id ? { id } : {},
2056
2302
  opId,
2057
2303
  ...traceSpan ? { traceSpan } : {}
@@ -2075,10 +2321,7 @@ async function useReason(args) {
2075
2321
  let aggregatedUsage;
2076
2322
  let finalFinishReason;
2077
2323
  let aggregatedProviderMetadata;
2078
- let aggregatedWarnings;
2079
- const emittedSetValues = /* @__PURE__ */ new Map();
2080
- const emittedAppendCounts = /* @__PURE__ */ new Map();
2081
- const emittedTextValues = /* @__PURE__ */ new Map();
2324
+ let aggregatedWarnings = inferred.warnings;
2082
2325
  if (existingCheckpoint) {
2083
2326
  firstText = existingCheckpoint.firstText;
2084
2327
  firstRaw = existingCheckpoint.firstRaw;
@@ -2100,78 +2343,7 @@ async function useReason(args) {
2100
2343
  emit: suppressTextStream ? false : args.emit,
2101
2344
  stream: useStructuredIncrementalStreaming ? true : suppressTextStream ? false : args.stream,
2102
2345
  ...useStructuredIncrementalStreaming ? {
2103
- onTextChunk: (delta) => {
2104
- firstText += delta;
2105
- finalText = firstText;
2106
- for (const fieldPath of setFieldPaths) {
2107
- const values = extractCompletedFieldValues({
2108
- text: firstText,
2109
- path: fieldPath
2110
- });
2111
- for (const { path, value } of values) {
2112
- const nextSerialized = JSON.stringify(value);
2113
- if (emittedSetValues.get(path) === nextSerialized) continue;
2114
- emittedSetValues.set(path, nextSerialized);
2115
- emitStructuredData({
2116
- kind: "set",
2117
- path,
2118
- value,
2119
- dataType: args.structured?.dataType ?? "reason-output",
2120
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2121
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2122
- ...id ? { id } : {},
2123
- streamId: opId
2124
- });
2125
- }
2126
- }
2127
- for (const fieldPath of appendFieldPaths) {
2128
- const groups = extractCompletedArrayItemGroups({
2129
- text: firstText,
2130
- path: fieldPath
2131
- });
2132
- for (const { path, items } of groups) {
2133
- const emittedAppendCount = emittedAppendCounts.get(path) ?? 0;
2134
- if (items.length <= emittedAppendCount) continue;
2135
- const nextItems = items.slice(emittedAppendCount);
2136
- emittedAppendCounts.set(path, items.length);
2137
- emitStructuredData({
2138
- kind: "append",
2139
- path,
2140
- items: nextItems,
2141
- dataType: args.structured?.dataType ?? "reason-output",
2142
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2143
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2144
- ...id ? { id } : {},
2145
- streamId: opId
2146
- });
2147
- }
2148
- }
2149
- for (const fieldPath of textDeltaFieldPaths) {
2150
- const values = extractStreamingStringValues({
2151
- text: firstText,
2152
- path: fieldPath
2153
- });
2154
- for (const { path, value } of values) {
2155
- const emittedTextValue = emittedTextValues.get(path) ?? "";
2156
- if (value.length <= emittedTextValue.length || !value.startsWith(emittedTextValue)) {
2157
- continue;
2158
- }
2159
- const nextDelta = value.slice(emittedTextValue.length);
2160
- emittedTextValues.set(path, value);
2161
- if (nextDelta.length === 0) continue;
2162
- emitStructuredData({
2163
- kind: "text-delta",
2164
- path,
2165
- delta: nextDelta,
2166
- dataType: args.structured?.dataType ?? "reason-output",
2167
- ...args.structured?.schemaId ? { schemaId: args.structured.schemaId } : {},
2168
- ...args.structured?.schemaVersion ? { schemaVersion: args.structured.schemaVersion } : {},
2169
- ...id ? { id } : {},
2170
- streamId: opId
2171
- });
2172
- }
2173
- }
2174
- }
2346
+ onTextChunk: createStructuredOutputStreamer(args, id, opId)
2175
2347
  } : {}
2176
2348
  },
2177
2349
  reasonMeta,
@@ -2291,6 +2463,7 @@ async function useReason(args) {
2291
2463
  const second = await reasonEngine(
2292
2464
  {
2293
2465
  ...args,
2466
+ ...inferred.responseFormat ? { responseFormat: inferred.responseFormat } : {},
2294
2467
  emit: suppressTextStream ? false : args.emit,
2295
2468
  stream: suppressTextStream ? false : args.stream
2296
2469
  },