@haikeilabs/agentware 0.1.0 → 0.1.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.
Files changed (54) hide show
  1. package/dist/executor/agent_loop.d.ts +41 -0
  2. package/dist/executor/agent_loop.d.ts.map +1 -0
  3. package/dist/executor/agent_loop.js +206 -0
  4. package/dist/executor/index.d.ts +2 -1
  5. package/dist/executor/index.d.ts.map +1 -1
  6. package/dist/executor/index.js +2 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/llm/backend.d.ts +7 -0
  11. package/dist/llm/backend.d.ts.map +1 -1
  12. package/dist/llm/index.d.ts +2 -1
  13. package/dist/llm/index.d.ts.map +1 -1
  14. package/dist/llm/index.js +1 -0
  15. package/dist/llm/openai_backend.d.ts +51 -0
  16. package/dist/llm/openai_backend.d.ts.map +1 -0
  17. package/dist/llm/openai_backend.js +203 -0
  18. package/dist/llm/response.d.ts +5 -0
  19. package/dist/llm/response.d.ts.map +1 -1
  20. package/dist/middleware/guardrails/response_validator.d.ts +1 -1
  21. package/dist/middleware/guardrails/response_validator.d.ts.map +1 -1
  22. package/dist/middleware/guardrails/response_validator.js +1 -1
  23. package/dist/middleware/index.d.ts +5 -0
  24. package/dist/middleware/index.d.ts.map +1 -1
  25. package/dist/middleware/index.js +5 -0
  26. package/dist/middleware/inference.d.ts +8 -0
  27. package/dist/middleware/inference.d.ts.map +1 -1
  28. package/dist/middleware/inference.js +73 -31
  29. package/dist/reasoning/adapter.d.ts +33 -0
  30. package/dist/reasoning/adapter.d.ts.map +1 -0
  31. package/dist/reasoning/adapter.js +365 -0
  32. package/dist/reasoning/errors.d.ts +28 -0
  33. package/dist/reasoning/errors.d.ts.map +1 -0
  34. package/dist/reasoning/errors.js +57 -0
  35. package/dist/reasoning/index.d.ts +6 -0
  36. package/dist/reasoning/index.d.ts.map +1 -0
  37. package/dist/reasoning/index.js +4 -0
  38. package/dist/reasoning/options.d.ts +18 -0
  39. package/dist/reasoning/options.d.ts.map +1 -0
  40. package/dist/reasoning/options.js +18 -0
  41. package/dist/reasoning/types.d.ts +44 -0
  42. package/dist/reasoning/types.d.ts.map +1 -0
  43. package/dist/reasoning/types.js +32 -0
  44. package/dist/toolformat/formatter.d.ts +3 -3
  45. package/dist/toolformat/formatter.d.ts.map +1 -1
  46. package/dist/tools/index.d.ts +2 -2
  47. package/dist/tools/index.d.ts.map +1 -1
  48. package/dist/tools/index.js +2 -2
  49. package/dist/tools/registry.d.ts +4 -4
  50. package/dist/tools/registry.d.ts.map +1 -1
  51. package/dist/tools/tool.d.ts +8 -1
  52. package/dist/tools/tool.d.ts.map +1 -1
  53. package/dist/tools/tool.js +7 -0
  54. package/package.json +10 -1
@@ -5,6 +5,7 @@ import type { ContextWindowManager } from "../llmcontext/context_window.js";
5
5
  import type { ResponseValidator } from "./guardrails/response_validator.js";
6
6
  import { ErrorTracker } from "./guardrails/error_tracker.js";
7
7
  import { StepEnforcer } from "./guardrails/step_enforcer.js";
8
+ import { ReasoningAdapter, type ContextTree } from "../reasoning/index.js";
8
9
  export declare class RetriesExhaustedError extends Error {
9
10
  constructor(message: string);
10
11
  }
@@ -13,6 +14,10 @@ export interface InferenceResult {
13
14
  newMessages: Message[];
14
15
  toolCallCounter: number;
15
16
  attempts: number;
17
+ /** Normalized context tree for the final turn's reasoning (native
18
+ * reasoning_content and/or thinking tags). Null when the turn carried no
19
+ * reasoning. Holds bounded summaries only; raw reasoning is never attached. */
20
+ reasoningTree: ContextTree | null;
16
21
  }
17
22
  export interface InferenceConfig {
18
23
  client: Backend;
@@ -23,6 +28,9 @@ export interface InferenceConfig {
23
28
  toolSpecs: ToolDefinition[];
24
29
  maxAttempts: number;
25
30
  stepIndex?: number;
31
+ /** Optional reasoning adapter override for limits and registered model
32
+ * fields. */
33
+ reasoning?: ReasoningAdapter;
26
34
  }
27
35
  export declare function runInference(messages: Message[], cfg: InferenceConfig, sessionId?: string): Promise<InferenceResult | null>;
28
36
  //# sourceMappingURL=inference.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../../src/middleware/inference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,OAAO,KAAK,EAAE,iBAAiB,EAA8B,MAAM,oCAAoC,CAAC;AACxG,OAAO,EAAE,YAAY,EAAiB,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7D,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,OAAO,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,eAAe,EACpB,SAAS,GAAE,MAAW,GACrB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAwJjC"}
1
+ {"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../../src/middleware/inference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAG5E,OAAO,KAAK,EAAE,iBAAiB,EAA8B,MAAM,oCAAoC,CAAC;AACxG,OAAO,EAAE,YAAY,EAAiB,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,EAAE,gBAAgB,EAAkB,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAE3F,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,OAAO,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB;;mFAE+E;IAC/E,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;iBACa;IACb,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAKD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,eAAe,EACpB,SAAS,GAAE,MAAW,GACrB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA8LjC"}
@@ -2,12 +2,15 @@ import { Role } from "../llm/request.js";
2
2
  import { MessageType } from "./types.js";
3
3
  import { ErrorCategory } from "./guardrails/error_tracker.js";
4
4
  import { stepNudge } from "./guardrails/nudge.js";
5
+ import { ReasoningAdapter, ReasoningError } from "../reasoning/index.js";
5
6
  export class RetriesExhaustedError extends Error {
6
7
  constructor(message) {
7
8
  super(message);
8
9
  this.name = "RetriesExhaustedError";
9
10
  }
10
11
  }
12
+ // Default adapter strips and normalizes reasoning in the inference loop.
13
+ const defaultAdapter = new ReasoningAdapter();
11
14
  export async function runInference(messages, cfg, sessionId = "") {
12
15
  let maxAttempts = cfg.maxAttempts;
13
16
  if (maxAttempts <= 0) {
@@ -49,43 +52,81 @@ export async function runInference(messages, cfg, sessionId = "") {
49
52
  cfg.contextManager.updateTokenCount(resp.usage_tokens.total_tokens);
50
53
  }
51
54
  let validationResult = null;
52
- if (resp.tool_calls && resp.tool_calls.length > 0) {
53
- const guardrailsToolCalls = resp.tool_calls.map((tc) => ({
54
- tool: tc.name,
55
- args: tc.arguments,
56
- }));
57
- if (cfg.validator) {
58
- validationResult = cfg.validator.validateToolCalls(guardrailsToolCalls);
55
+ // AR-1: normalize reasoning and strip it before ordinary tool-call
56
+ // parsing. Reasoning can arrive as a native reasoning_content field or as
57
+ // embedded thinking tags in content; both are combined into a synthetic
58
+ // structured input so the adapter sees the full picture. Malformed or
59
+ // unbounded reasoning fails closed: this turn is treated as invalid and
60
+ // retried rather than parsed partially.
61
+ let reasoningTree = null;
62
+ let cleanContent = resp.content;
63
+ let reasoningFailed = false;
64
+ if (resp.content || resp.reasoning) {
65
+ const reasoningInput = { content: resp.content };
66
+ if (resp.reasoning) {
67
+ reasoningInput["reasoning_content"] = resp.reasoning;
59
68
  }
60
- else {
61
- validationResult = {
62
- toolCalls: guardrailsToolCalls,
63
- nudge: null,
64
- needsRetry: false,
65
- };
66
- }
67
- }
68
- else if (resp.content) {
69
- if (cfg.validator) {
70
- validationResult = cfg.validator.validateTextResponse(resp.content);
69
+ const adapter = cfg.reasoning ?? defaultAdapter;
70
+ try {
71
+ reasoningTree = adapter.extract(reasoningInput, cfg.client.modelName(), "llm-backend");
72
+ cleanContent = adapter.strip(reasoningInput);
71
73
  }
72
- else {
73
- validationResult = { toolCalls: [], nudge: null, needsRetry: false };
74
+ catch (e) {
75
+ if (e instanceof ReasoningError) {
76
+ reasoningFailed = true;
77
+ // Fail closed: never echo content that may carry reasoning we could
78
+ // not parse.
79
+ cleanContent = "";
80
+ validationResult = {
81
+ toolCalls: [],
82
+ nudge: null,
83
+ needsRetry: true,
84
+ };
85
+ }
86
+ else {
87
+ throw e;
88
+ }
74
89
  }
75
- if (!validationResult.needsRetry && validationResult.toolCalls.length > 0) {
76
- resp.tool_calls = validationResult.toolCalls.map((tc) => ({
77
- id: "",
78
- name: tc.tool,
79
- arguments: tc.args,
90
+ }
91
+ if (!reasoningFailed) {
92
+ if (resp.tool_calls && resp.tool_calls.length > 0) {
93
+ const guardrailsToolCalls = resp.tool_calls.map((tc) => ({
94
+ tool: tc.name,
95
+ args: tc.arguments,
80
96
  }));
97
+ if (cfg.validator) {
98
+ validationResult = cfg.validator.validateToolCalls(guardrailsToolCalls);
99
+ }
100
+ else {
101
+ validationResult = {
102
+ toolCalls: guardrailsToolCalls,
103
+ nudge: null,
104
+ needsRetry: false,
105
+ };
106
+ }
81
107
  }
82
- }
83
- else {
84
- if (cfg.validator) {
85
- validationResult = cfg.validator.validateTextResponse("");
108
+ else if (cleanContent) {
109
+ if (cfg.validator) {
110
+ validationResult = cfg.validator.validateTextResponse(cleanContent);
111
+ }
112
+ else {
113
+ validationResult = { toolCalls: [], nudge: null, needsRetry: false };
114
+ }
115
+ if (!validationResult.needsRetry && validationResult.toolCalls.length > 0) {
116
+ resp.tool_calls = validationResult.toolCalls.map((tc) => ({
117
+ id: "",
118
+ name: tc.tool,
119
+ arguments: tc.args,
120
+ }));
121
+ }
86
122
  }
87
123
  else {
88
- validationResult = { toolCalls: [], nudge: null, needsRetry: true };
124
+ if (cfg.validator) {
125
+ validationResult = cfg.validator.validateTextResponse("");
126
+ }
127
+ else {
128
+ validationResult = { toolCalls: [], nudge: null, needsRetry: true };
129
+ }
89
130
  }
90
131
  }
91
132
  lastResponse = resp;
@@ -114,6 +155,7 @@ export async function runInference(messages, cfg, sessionId = "") {
114
155
  newMessages: currentMessages,
115
156
  toolCallCounter,
116
157
  attempts,
158
+ reasoningTree,
117
159
  };
118
160
  }
119
161
  if (cfg.errorTracker) {
@@ -132,7 +174,7 @@ export async function runInference(messages, cfg, sessionId = "") {
132
174
  }
133
175
  const failedMsg = {
134
176
  role: Role.ASSISTANT,
135
- content: resp.content,
177
+ content: cleanContent,
136
178
  meta: { type: MessageType.TEXT_RESPONSE },
137
179
  };
138
180
  currentMessages.push(failedMsg);
@@ -0,0 +1,33 @@
1
+ import { ReasoningError } from "./errors.js";
2
+ import { ReasoningOptions } from "./options.js";
3
+ import { ContextTree } from "./types.js";
4
+ export { ReasoningError };
5
+ export declare class ReasoningAdapter {
6
+ private readonly opts;
7
+ private readonly modelFields;
8
+ constructor(options?: ReasoningOptions);
9
+ get options(): Required<ReasoningOptions>;
10
+ /** Map a model name to a model-specific reasoning field name. Fields
11
+ * registered here are consulted before the known field set. */
12
+ registerModelField(model: string, fieldName: string): void;
13
+ /** Remove reasoning from raw and return the clean content that ordinary
14
+ * tool-call parsers should see. Fails closed. */
15
+ strip(raw: unknown): string;
16
+ /** Normalize reasoning found in raw into a versioned context tree. Raw
17
+ * reasoning is never stored on the tree: nodes carry bounded summaries. */
18
+ extract(raw: unknown, model?: string, backend?: string): ContextTree;
19
+ /** Strip and extract in one call: return the clean content and tree. */
20
+ parse(raw: unknown, model?: string, backend?: string): {
21
+ clean: string;
22
+ tree: ContextTree;
23
+ };
24
+ private analyze;
25
+ private analyzeString;
26
+ private analyzeBytes;
27
+ private analyzeStructured;
28
+ private extractReasoningField;
29
+ private analyzeText;
30
+ private hasRecognizedKey;
31
+ private parseNodes;
32
+ }
33
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/reasoning/adapter.ts"],"names":[],"mappings":"AAcA,OAAO,EAKL,cAAc,EAKf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAoB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAoB,WAAW,EAAsC,MAAM,YAAY,CAAC;AAE/F,OAAO,EAAE,cAAc,EAAE,CAAC;AAmE1B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;gBAEtC,OAAO,CAAC,EAAE,gBAAgB;IAKtC,IAAI,OAAO,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAExC;IAED;mEAC+D;IAC/D,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAM1D;qDACiD;IACjD,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM;IAI3B;+EAC2E;IAC3E,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,SAAK,EAAE,OAAO,SAAK,GAAG,WAAW;IA0B5D,wEAAwE;IACxE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,SAAK,EAAE,OAAO,SAAK,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE;IAKnF,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,qBAAqB;IAqC7B,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,UAAU;CA2DnB"}
@@ -0,0 +1,365 @@
1
+ // Model-agnostic reasoning adapter (mirrors go/reasoning/adapter.go).
2
+ //
3
+ // The adapter recognizes three shapes of model output:
4
+ // - native reasoning_content fields (DeepSeek/Qwen/Kimi/GLM-style JSON)
5
+ // - generic thinking tags (<thinking>...</thinking>, [THINK]...[/THINK])
6
+ // - model-specific reasoning fields (registered per model name)
7
+ //
8
+ // Reasoning is stripped before ordinary tool-call parsing, and the extracted
9
+ // reasoning text is normalized into a versioned context tree. Raw reasoning is
10
+ // never exposed in normal replies or default audits: tree nodes carry only
11
+ // bounded summaries. The adapter fails closed on malformed or unbounded input.
12
+ import { createHash } from "node:crypto";
13
+ import { MalformedContentError, MalformedInputError, MalformedJSONError, MalformedReasoningFieldError, ReasoningError, ReasoningTooLargeError, TooDeepError, TooManyNodesError, UnbalancedThinkingError, } from "./errors.js";
14
+ import { normalizeOptions } from "./options.js";
15
+ import { CONTRACT_VERSION, Format, NodeKind, NodeStatus } from "./types.js";
16
+ export { ReasoningError };
17
+ // Known reasoning field names on a structured response, in priority order.
18
+ // reasoning_content is the de-facto native field (DeepSeek, Qwen/QwQ, Kimi,
19
+ // GLM); the others cover common model-specific names.
20
+ const KNOWN_REASONING_FIELDS = ["reasoning_content", "thinking", "thinking_content", "reasoning"];
21
+ // Keys that make a structured response worth entering the structured path. A
22
+ // mapping without any of these is treated as opaque text and returned
23
+ // unchanged.
24
+ const RECOGNIZED_KEYS = new Set([
25
+ "message",
26
+ "content",
27
+ "reasoning_content",
28
+ "thinking",
29
+ "thinking_content",
30
+ "reasoning",
31
+ ]);
32
+ const THINKING_OPEN = /<thinking>|\[THINK\]/i;
33
+ const THINKING_CLOSE = /<\/thinking>|\[\/THINK\]/i;
34
+ const PARAGRAPH_SEP = /\n[ \t]*\n/;
35
+ const LIST_ORNAMENT = /^\s*(?:[-*+•]|\d+[.)])\s+/;
36
+ const KIND_PREFIX = /^\s*(goal|observation|decision|tool-call|tool_call|tool-result|tool_result|tool|conclusion|blocker)\s*[:=]\s*/i;
37
+ const STATUS_MARKER = /\s*\[(blocked|pending|active|superseded|complete)\]\s*$/i;
38
+ const EVIDENCE_PATTERNS = [
39
+ /\[E\d+\]/g,
40
+ /\bref\s*[=:]\s*(\S+)/g,
41
+ /\bevidence\s*[=:]\s*(\S+)/g,
42
+ ];
43
+ const TOOL_REF = /\b(?:tool|call)\s*[=:]\s*([a-zA-Z_][a-zA-Z0-9_.-]*)/;
44
+ const LEADING_IDENTIFIER = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\s*[:=]/;
45
+ const BARE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_.-]*$/;
46
+ const KIND_BY_LABEL = {
47
+ goal: NodeKind.GOAL,
48
+ observation: NodeKind.OBSERVATION,
49
+ decision: NodeKind.DECISION,
50
+ "tool-call": NodeKind.TOOL_CALL,
51
+ tool_call: NodeKind.TOOL_CALL,
52
+ tool: NodeKind.TOOL_CALL,
53
+ "tool-result": NodeKind.TOOL_RESULT,
54
+ tool_result: NodeKind.TOOL_RESULT,
55
+ conclusion: NodeKind.CONCLUSION,
56
+ blocker: NodeKind.BLOCKER,
57
+ };
58
+ const STATUS_BY_LABEL = {
59
+ blocked: NodeStatus.BLOCKED,
60
+ pending: NodeStatus.ACTIVE,
61
+ active: NodeStatus.ACTIVE,
62
+ superseded: NodeStatus.SUPERSEDED,
63
+ complete: NodeStatus.COMPLETE,
64
+ };
65
+ function isRecord(v) {
66
+ return typeof v === "object" && v !== null && !Array.isArray(v);
67
+ }
68
+ export class ReasoningAdapter {
69
+ opts;
70
+ modelFields;
71
+ constructor(options) {
72
+ this.opts = normalizeOptions(options);
73
+ this.modelFields = new Map();
74
+ }
75
+ get options() {
76
+ return this.opts;
77
+ }
78
+ /** Map a model name to a model-specific reasoning field name. Fields
79
+ * registered here are consulted before the known field set. */
80
+ registerModelField(model, fieldName) {
81
+ if (model && fieldName) {
82
+ this.modelFields.set(model.toLowerCase(), fieldName);
83
+ }
84
+ }
85
+ /** Remove reasoning from raw and return the clean content that ordinary
86
+ * tool-call parsers should see. Fails closed. */
87
+ strip(raw) {
88
+ return this.analyze(raw, "").clean;
89
+ }
90
+ /** Normalize reasoning found in raw into a versioned context tree. Raw
91
+ * reasoning is never stored on the tree: nodes carry bounded summaries. */
92
+ extract(raw, model = "", backend = "") {
93
+ const analysis = this.analyze(raw, model);
94
+ const now = this.opts.clock();
95
+ const tree = {
96
+ version: CONTRACT_VERSION,
97
+ format: analysis.format,
98
+ model,
99
+ backend,
100
+ created_at: now,
101
+ root_id: "",
102
+ nodes: [],
103
+ };
104
+ if (analysis.reasoning.trim() === "") {
105
+ return tree;
106
+ }
107
+ const nodes = this.parseNodes(analysis.reasoning, now);
108
+ tree.nodes = nodes;
109
+ if (nodes.length > 0) {
110
+ tree.root_id = nodes[0].id;
111
+ }
112
+ return tree;
113
+ }
114
+ /** Strip and extract in one call: return the clean content and tree. */
115
+ parse(raw, model = "", backend = "") {
116
+ const analysis = this.analyze(raw, model);
117
+ return { clean: analysis.clean, tree: this.extract(raw, model, backend) };
118
+ }
119
+ analyze(raw, model) {
120
+ if (typeof raw === "string") {
121
+ return this.analyzeString(raw, model);
122
+ }
123
+ if (raw instanceof Uint8Array) {
124
+ return this.analyzeBytes(raw, model);
125
+ }
126
+ if (isRecord(raw)) {
127
+ return this.analyzeStructured(raw, model);
128
+ }
129
+ throw new MalformedInputError(`unsupported raw type ${typeof raw}`);
130
+ }
131
+ analyzeString(raw, model) {
132
+ const trimmed = raw.trim();
133
+ if (trimmed.startsWith("{")) {
134
+ let parsed;
135
+ try {
136
+ parsed = JSON.parse(trimmed);
137
+ }
138
+ catch (e) {
139
+ throw new MalformedJSONError(e instanceof Error ? e.message : String(e));
140
+ }
141
+ if (isRecord(parsed) && this.hasRecognizedKey(parsed)) {
142
+ return this.analyzeStructured(parsed, model);
143
+ }
144
+ }
145
+ return this.analyzeText(raw);
146
+ }
147
+ analyzeBytes(raw, model) {
148
+ const trimmed = raw.length > 0 ? new TextDecoder().decode(raw).trim() : "";
149
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
150
+ let parsed;
151
+ try {
152
+ parsed = JSON.parse(trimmed);
153
+ }
154
+ catch (e) {
155
+ throw new MalformedJSONError(e instanceof Error ? e.message : String(e));
156
+ }
157
+ if (isRecord(parsed) && this.hasRecognizedKey(parsed)) {
158
+ return this.analyzeStructured(parsed, model);
159
+ }
160
+ }
161
+ return this.analyzeText(trimmed);
162
+ }
163
+ analyzeStructured(m, model) {
164
+ const message = m["message"];
165
+ if (isRecord(message)) {
166
+ return this.analyzeStructured(message, model);
167
+ }
168
+ let content = "";
169
+ if ("content" in m) {
170
+ const value = m["content"];
171
+ if (value === null || value === undefined) {
172
+ content = "";
173
+ }
174
+ else if (typeof value === "string") {
175
+ content = value;
176
+ }
177
+ else {
178
+ throw new MalformedContentError(`content field is ${typeof value}, not a string`);
179
+ }
180
+ }
181
+ const [reasoning, format] = this.extractReasoningField(m, model);
182
+ if (reasoning !== "") {
183
+ return { reasoning, clean: splitThinkingTags(content).clean, format };
184
+ }
185
+ const result = splitThinkingTags(content);
186
+ if (result.reasoning !== "") {
187
+ return { reasoning: result.reasoning, clean: result.clean, format: Format.THINKING };
188
+ }
189
+ return { reasoning: "", clean: content, format: Format.NONE };
190
+ }
191
+ extractReasoningField(m, model) {
192
+ if (model) {
193
+ const fieldName = this.modelFields.get(model.toLowerCase());
194
+ if (fieldName) {
195
+ const value = m[fieldName];
196
+ if (value !== null && value !== undefined) {
197
+ if (typeof value !== "string") {
198
+ throw new MalformedReasoningFieldError(`field "${fieldName}" is ${typeof value}, not a string`);
199
+ }
200
+ if (value !== "") {
201
+ return [value, Format.MODEL_SPECIFIC];
202
+ }
203
+ }
204
+ }
205
+ }
206
+ for (const fieldName of KNOWN_REASONING_FIELDS) {
207
+ const value = m[fieldName];
208
+ if (value === null || value === undefined) {
209
+ continue;
210
+ }
211
+ if (typeof value !== "string") {
212
+ throw new MalformedReasoningFieldError(`field "${fieldName}" is ${typeof value}, not a string`);
213
+ }
214
+ if (value !== "") {
215
+ return [value, Format.NATIVE];
216
+ }
217
+ }
218
+ return ["", Format.NONE];
219
+ }
220
+ analyzeText(raw) {
221
+ const result = splitThinkingTags(raw);
222
+ if (result.reasoning !== "") {
223
+ return { reasoning: result.reasoning, clean: result.clean, format: Format.THINKING };
224
+ }
225
+ return { reasoning: "", clean: raw, format: Format.NONE };
226
+ }
227
+ hasRecognizedKey(m) {
228
+ return Object.keys(m).some((k) => RECOGNIZED_KEYS.has(k));
229
+ }
230
+ parseNodes(reasoning, now) {
231
+ if (Buffer.byteLength(reasoning, "utf8") > this.opts.max_reasoning_bytes) {
232
+ throw new ReasoningTooLargeError();
233
+ }
234
+ const paragraphs = splitParagraphs(reasoning);
235
+ if (paragraphs.length > this.opts.max_nodes) {
236
+ throw new TooManyNodesError();
237
+ }
238
+ const nodes = [];
239
+ let parentId = "";
240
+ let depth = 0;
241
+ for (const para of paragraphs) {
242
+ depth += 1;
243
+ if (depth > this.opts.max_depth) {
244
+ throw new TooDeepError();
245
+ }
246
+ let kind = NodeKind.OBSERVATION;
247
+ let summary = para.replace(LIST_ORNAMENT, "");
248
+ const kindMatch = KIND_PREFIX.exec(summary);
249
+ if (kindMatch) {
250
+ kind = KIND_BY_LABEL[kindMatch[1].toLowerCase()] ?? NodeKind.OBSERVATION;
251
+ summary = summary.slice(kindMatch[0].length).trim();
252
+ }
253
+ let status = NodeStatus.COMPLETE;
254
+ const statusMatch = STATUS_MARKER.exec(summary);
255
+ if (statusMatch) {
256
+ status = STATUS_BY_LABEL[statusMatch[1].toLowerCase()] ?? NodeStatus.COMPLETE;
257
+ summary = summary.slice(0, statusMatch.index).trim();
258
+ }
259
+ else if (kind === NodeKind.BLOCKER) {
260
+ status = NodeStatus.BLOCKED;
261
+ }
262
+ summary = truncateCodePoints(summary, this.opts.max_summary_chars);
263
+ if (summary === "") {
264
+ depth -= 1;
265
+ continue;
266
+ }
267
+ const id = nodeId(kind, parentId, summary);
268
+ nodes.push({
269
+ id,
270
+ parent_id: parentId,
271
+ kind,
272
+ status,
273
+ summary,
274
+ evidence_refs: extractEvidence(summary),
275
+ tool_call_refs: extractToolRefs(kind, summary),
276
+ created_at: now,
277
+ updated_at: now,
278
+ });
279
+ parentId = id;
280
+ }
281
+ return nodes;
282
+ }
283
+ }
284
+ function splitThinkingTags(text) {
285
+ if (!THINKING_OPEN.test(text) && !THINKING_CLOSE.test(text)) {
286
+ return { clean: text, reasoning: "" };
287
+ }
288
+ const parts = [];
289
+ const reasons = [];
290
+ let rest = text;
291
+ for (;;) {
292
+ const open = THINKING_OPEN.exec(rest);
293
+ if (open === null) {
294
+ if (THINKING_CLOSE.test(rest)) {
295
+ throw new UnbalancedThinkingError();
296
+ }
297
+ parts.push(rest);
298
+ break;
299
+ }
300
+ parts.push(rest.slice(0, open.index));
301
+ const inner = rest.slice(open.index + open[0].length);
302
+ const close = THINKING_CLOSE.exec(inner);
303
+ if (close === null) {
304
+ throw new UnbalancedThinkingError();
305
+ }
306
+ reasons.push(inner.slice(0, close.index));
307
+ rest = inner.slice(close.index + close[0].length);
308
+ }
309
+ return { clean: parts.join(""), reasoning: reasons.join("\n").trim() };
310
+ }
311
+ function splitParagraphs(text) {
312
+ return text
313
+ .split(PARAGRAPH_SEP)
314
+ .map((p) => p.trim())
315
+ .filter((p) => p !== "");
316
+ }
317
+ function extractEvidence(summary) {
318
+ const out = [];
319
+ const seen = new Set();
320
+ for (const pattern of EVIDENCE_PATTERNS) {
321
+ pattern.lastIndex = 0;
322
+ let match;
323
+ while ((match = pattern.exec(summary)) !== null) {
324
+ const ref = (match[1] ?? match[0]).replace(/^\[|\]$/g, "");
325
+ if (ref !== "" && !seen.has(ref)) {
326
+ seen.add(ref);
327
+ out.push(ref);
328
+ }
329
+ }
330
+ }
331
+ return out;
332
+ }
333
+ function extractToolRefs(kind, summary) {
334
+ if (kind !== NodeKind.TOOL_CALL && kind !== NodeKind.TOOL_RESULT) {
335
+ return [];
336
+ }
337
+ const leading = LEADING_IDENTIFIER.exec(summary);
338
+ if (leading) {
339
+ return [leading[1]];
340
+ }
341
+ const ref = TOOL_REF.exec(summary);
342
+ if (ref) {
343
+ return [ref[1]];
344
+ }
345
+ if (BARE_IDENTIFIER.test(summary)) {
346
+ return [summary];
347
+ }
348
+ return [];
349
+ }
350
+ function truncateCodePoints(s, n) {
351
+ if (n <= 0) {
352
+ return "";
353
+ }
354
+ const points = Array.from(s);
355
+ if (points.length <= n) {
356
+ return s;
357
+ }
358
+ return points.slice(0, n).join("");
359
+ }
360
+ function nodeId(kind, parentId, summary) {
361
+ return createHash("sha256")
362
+ .update(`${kind}\0${parentId}\0${summary}`)
363
+ .digest("hex")
364
+ .slice(0, 16);
365
+ }
@@ -0,0 +1,28 @@
1
+ export declare class ReasoningError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class MalformedInputError extends ReasoningError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class MalformedJSONError extends ReasoningError {
8
+ constructor(message: string);
9
+ }
10
+ export declare class MalformedReasoningFieldError extends ReasoningError {
11
+ constructor(message: string);
12
+ }
13
+ export declare class MalformedContentError extends ReasoningError {
14
+ constructor(message: string);
15
+ }
16
+ export declare class UnbalancedThinkingError extends ReasoningError {
17
+ constructor(message?: string);
18
+ }
19
+ export declare class ReasoningTooLargeError extends ReasoningError {
20
+ constructor(message?: string);
21
+ }
22
+ export declare class TooManyNodesError extends ReasoningError {
23
+ constructor(message?: string);
24
+ }
25
+ export declare class TooDeepError extends ReasoningError {
26
+ constructor(message?: string);
27
+ }
28
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/reasoning/errors.ts"],"names":[],"mappings":"AAIA,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,cAAc;gBACzC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,kBAAmB,SAAQ,cAAc;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,4BAA6B,SAAQ,cAAc;gBAClD,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,qBAAsB,SAAQ,cAAc;gBAC3C,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,uBAAwB,SAAQ,cAAc;gBAC7C,OAAO,SAAuC;CAI3D;AAED,qBAAa,sBAAuB,SAAQ,cAAc;gBAC5C,OAAO,SAA8C;CAIlE;AAED,qBAAa,iBAAkB,SAAQ,cAAc;gBACvC,OAAO,SAAwC;CAI5D;AAED,qBAAa,YAAa,SAAQ,cAAc;gBAClC,OAAO,SAAuC;CAI3D"}
@@ -0,0 +1,57 @@
1
+ // Fail-closed errors raised by the reasoning adapter.
2
+ // Every error represents a fail-closed outcome: malformed or unbounded input
3
+ // never produces a partial tree or partially-stripped content.
4
+ export class ReasoningError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "ReasoningError";
8
+ }
9
+ }
10
+ export class MalformedInputError extends ReasoningError {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "MalformedInputError";
14
+ }
15
+ }
16
+ export class MalformedJSONError extends ReasoningError {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "MalformedJSONError";
20
+ }
21
+ }
22
+ export class MalformedReasoningFieldError extends ReasoningError {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "MalformedReasoningFieldError";
26
+ }
27
+ }
28
+ export class MalformedContentError extends ReasoningError {
29
+ constructor(message) {
30
+ super(message);
31
+ this.name = "MalformedContentError";
32
+ }
33
+ }
34
+ export class UnbalancedThinkingError extends ReasoningError {
35
+ constructor(message = "reasoning: unbalanced thinking tag") {
36
+ super(message);
37
+ this.name = "UnbalancedThinkingError";
38
+ }
39
+ }
40
+ export class ReasoningTooLargeError extends ReasoningError {
41
+ constructor(message = "reasoning: reasoning exceeds maximum size") {
42
+ super(message);
43
+ this.name = "ReasoningTooLargeError";
44
+ }
45
+ }
46
+ export class TooManyNodesError extends ReasoningError {
47
+ constructor(message = "reasoning: too many reasoning nodes") {
48
+ super(message);
49
+ this.name = "TooManyNodesError";
50
+ }
51
+ }
52
+ export class TooDeepError extends ReasoningError {
53
+ constructor(message = "reasoning: reasoning tree too deep") {
54
+ super(message);
55
+ this.name = "TooDeepError";
56
+ }
57
+ }