toolnexus 0.12.0 → 0.14.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/a2a.js CHANGED
@@ -234,6 +234,14 @@ function skillTool(agentName, endpoint, skill, ag) {
234
234
  };
235
235
  }
236
236
  catch (e) {
237
+ // An abort that lands WHILE an RPC is in flight surfaces as a transport error
238
+ // (AbortError), not through the two checks in the poll loop. Report it as the
239
+ // cancel it is, so a cancelled call is `canceled` regardless of whether the
240
+ // signal fired between polls or mid-request.
241
+ if (ctx?.signal?.aborted) {
242
+ state = "canceled";
243
+ return { output: `A2A task ${taskId} canceled`, isError: true, metadata: meta() };
244
+ }
237
245
  return { output: e instanceof Error ? e.message : String(e), isError: true, metadata: meta() };
238
246
  }
239
247
  },
@@ -71,25 +71,41 @@ export function compactor(opts) {
71
71
  };
72
72
  }
73
73
  /**
74
- * Locate the index where the retained tail begins. Tool-pair safety requires the tail
75
- * to start at a `user` turn: the largest user-boundary tail that still fits `keepTail`.
76
- * If no user boundary fits within `keepTail`, fall back to the most recent user turn
74
+ * Does this message carry a tool result? Under the anthropic dialect a tool result is a
75
+ * `user` message whose content holds `tool_result` blocks, so such a message is a member
76
+ * of a tool group rather than a conversational boundary.
77
+ */
78
+ function carriesToolResult(m) {
79
+ const content = m.content;
80
+ if (!Array.isArray(content))
81
+ return false;
82
+ return content.some((b) => typeof b === "object" && b !== null && b.type === "tool_result");
83
+ }
84
+ /** A valid tail boundary: a `user` turn that is not carrying a tool result. */
85
+ function isBoundary(m) {
86
+ return m.role === "user" && !carriesToolResult(m);
87
+ }
88
+ /**
89
+ * Locate the index where the retained tail begins. Tool-pair safety requires the tail to
90
+ * start at a `user` turn that is not a tool-result carrier: the largest such tail that
91
+ * still fits `keepTail`. If none fits, fall back to the most recent valid boundary
77
92
  * (favoring safety over size). Returns `msgs.length` (no tail) only when there is no
78
- * user turn after the head at all.
93
+ * valid boundary after the head at all — the whole body is then summarized, which is
94
+ * bounded and cannot orphan anything since nothing is retained.
79
95
  */
80
96
  function findTailStart(msgs, headEnd, keepTail, count) {
81
97
  let split = msgs.length;
82
98
  for (let i = msgs.length - 1; i > headEnd; i--) {
83
99
  if (count(msgs.slice(i)) > keepTail)
84
100
  break;
85
- if (msgs[i].role === "user")
101
+ if (isBoundary(msgs[i]))
86
102
  split = i;
87
103
  }
88
104
  if (split !== msgs.length)
89
105
  return split;
90
- // No user boundary fit within keepTail — extend back to the most recent user turn.
106
+ // No boundary fit within keepTail — extend back to the most recent valid one.
91
107
  for (let i = msgs.length - 1; i > headEnd; i--) {
92
- if (msgs[i].role === "user")
108
+ if (isBoundary(msgs[i]))
93
109
  return i;
94
110
  }
95
111
  return msgs.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolnexus",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Dynamic MCP servers + agent skills as uniform tools for any LLM (opencode-style).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/a2a.ts CHANGED
@@ -344,6 +344,14 @@ function skillTool(agentName: string, endpoint: string, skill: A2ASkill, ag: Age
344
344
  metadata: meta(),
345
345
  }
346
346
  } catch (e) {
347
+ // An abort that lands WHILE an RPC is in flight surfaces as a transport error
348
+ // (AbortError), not through the two checks in the poll loop. Report it as the
349
+ // cancel it is, so a cancelled call is `canceled` regardless of whether the
350
+ // signal fired between polls or mid-request.
351
+ if (ctx?.signal?.aborted) {
352
+ state = "canceled"
353
+ return { output: `A2A task ${taskId} canceled`, isError: true, metadata: meta() }
354
+ }
347
355
  return { output: e instanceof Error ? e.message : String(e), isError: true, metadata: meta() }
348
356
  }
349
357
  },
@@ -121,11 +121,30 @@ export function compactor(
121
121
  }
122
122
 
123
123
  /**
124
- * Locate the index where the retained tail begins. Tool-pair safety requires the tail
125
- * to start at a `user` turn: the largest user-boundary tail that still fits `keepTail`.
126
- * If no user boundary fits within `keepTail`, fall back to the most recent user turn
124
+ * Does this message carry a tool result? Under the anthropic dialect a tool result is a
125
+ * `user` message whose content holds `tool_result` blocks, so such a message is a member
126
+ * of a tool group rather than a conversational boundary.
127
+ */
128
+ function carriesToolResult(m: Message): boolean {
129
+ const content = (m as { content?: unknown }).content
130
+ if (!Array.isArray(content)) return false
131
+ return content.some(
132
+ (b) => typeof b === "object" && b !== null && (b as { type?: unknown }).type === "tool_result",
133
+ )
134
+ }
135
+
136
+ /** A valid tail boundary: a `user` turn that is not carrying a tool result. */
137
+ function isBoundary(m: Message): boolean {
138
+ return m.role === "user" && !carriesToolResult(m)
139
+ }
140
+
141
+ /**
142
+ * Locate the index where the retained tail begins. Tool-pair safety requires the tail to
143
+ * start at a `user` turn that is not a tool-result carrier: the largest such tail that
144
+ * still fits `keepTail`. If none fits, fall back to the most recent valid boundary
127
145
  * (favoring safety over size). Returns `msgs.length` (no tail) only when there is no
128
- * user turn after the head at all.
146
+ * valid boundary after the head at all — the whole body is then summarized, which is
147
+ * bounded and cannot orphan anything since nothing is retained.
129
148
  */
130
149
  function findTailStart(
131
150
  msgs: Message[],
@@ -136,13 +155,13 @@ function findTailStart(
136
155
  let split = msgs.length
137
156
  for (let i = msgs.length - 1; i > headEnd; i--) {
138
157
  if (count(msgs.slice(i)) > keepTail) break
139
- if (msgs[i].role === "user") split = i
158
+ if (isBoundary(msgs[i])) split = i
140
159
  }
141
160
  if (split !== msgs.length) return split
142
161
 
143
- // No user boundary fit within keepTail — extend back to the most recent user turn.
162
+ // No boundary fit within keepTail — extend back to the most recent valid one.
144
163
  for (let i = msgs.length - 1; i > headEnd; i--) {
145
- if (msgs[i].role === "user") return i
164
+ if (isBoundary(msgs[i])) return i
146
165
  }
147
166
  return msgs.length
148
167
  }