@animalabs/agent-framework 0.22.4 → 0.23.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 (234) hide show
  1. package/README.md +105 -3
  2. package/dist/src/agent.d.ts +36 -10
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +76 -32
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/code-execution/index.d.ts +1 -1
  7. package/dist/src/code-execution/index.d.ts.map +1 -1
  8. package/dist/src/code-execution/index.js.map +1 -1
  9. package/dist/src/code-execution/py-runner.d.ts +34 -2
  10. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  11. package/dist/src/code-execution/py-runner.js +72 -16
  12. package/dist/src/code-execution/py-runner.js.map +1 -1
  13. package/dist/src/code-execution/runtime-py.d.ts +3 -2
  14. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  15. package/dist/src/code-execution/runtime-py.js +47 -7
  16. package/dist/src/code-execution/runtime-py.js.map +1 -1
  17. package/dist/src/code-execution/tool-definition.d.ts +12 -7
  18. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  19. package/dist/src/code-execution/tool-definition.js +59 -11
  20. package/dist/src/code-execution/tool-definition.js.map +1 -1
  21. package/dist/src/config-checks.d.ts +2 -2
  22. package/dist/src/config-checks.d.ts.map +1 -1
  23. package/dist/src/config-checks.js +6 -2
  24. package/dist/src/config-checks.js.map +1 -1
  25. package/dist/src/framework.d.ts +744 -120
  26. package/dist/src/framework.d.ts.map +1 -1
  27. package/dist/src/framework.js +3546 -906
  28. package/dist/src/framework.js.map +1 -1
  29. package/dist/src/gate/event-gate.d.ts.map +1 -1
  30. package/dist/src/gate/event-gate.js +13 -6
  31. package/dist/src/gate/event-gate.js.map +1 -1
  32. package/dist/src/held-writes.d.ts +16 -0
  33. package/dist/src/held-writes.d.ts.map +1 -0
  34. package/dist/src/held-writes.js +56 -0
  35. package/dist/src/held-writes.js.map +1 -0
  36. package/dist/src/index.d.ts +7 -3
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +8 -4
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/mcpl/capability-grant.d.ts +4 -0
  41. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  42. package/dist/src/mcpl/capability-grant.js +1 -1
  43. package/dist/src/mcpl/capability-grant.js.map +1 -1
  44. package/dist/src/mcpl/channel-registry.d.ts +95 -18
  45. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  46. package/dist/src/mcpl/channel-registry.js +578 -150
  47. package/dist/src/mcpl/channel-registry.js.map +1 -1
  48. package/dist/src/mcpl/convert-block.d.ts +2 -2
  49. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  50. package/dist/src/mcpl/convert-block.js +6 -7
  51. package/dist/src/mcpl/convert-block.js.map +1 -1
  52. package/dist/src/mcpl/index.d.ts +5 -1
  53. package/dist/src/mcpl/index.d.ts.map +1 -1
  54. package/dist/src/mcpl/index.js +3 -1
  55. package/dist/src/mcpl/index.js.map +1 -1
  56. package/dist/src/mcpl/inference-router.d.ts +4 -2
  57. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  58. package/dist/src/mcpl/inference-router.js +6 -7
  59. package/dist/src/mcpl/inference-router.js.map +1 -1
  60. package/dist/src/mcpl/log-value.d.ts +7 -0
  61. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  62. package/dist/src/mcpl/log-value.js +9 -0
  63. package/dist/src/mcpl/log-value.js.map +1 -1
  64. package/dist/src/mcpl/modern-connection.d.ts +227 -0
  65. package/dist/src/mcpl/modern-connection.d.ts.map +1 -0
  66. package/dist/src/mcpl/modern-connection.js +1088 -0
  67. package/dist/src/mcpl/modern-connection.js.map +1 -0
  68. package/dist/src/mcpl/prefix-rule.d.ts +18 -0
  69. package/dist/src/mcpl/prefix-rule.d.ts.map +1 -0
  70. package/dist/src/mcpl/prefix-rule.js +25 -0
  71. package/dist/src/mcpl/prefix-rule.js.map +1 -0
  72. package/dist/src/mcpl/protocol-family.d.ts +179 -0
  73. package/dist/src/mcpl/protocol-family.d.ts.map +1 -0
  74. package/dist/src/mcpl/protocol-family.js +697 -0
  75. package/dist/src/mcpl/protocol-family.js.map +1 -0
  76. package/dist/src/mcpl/push-coalescer.d.ts +38 -19
  77. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  78. package/dist/src/mcpl/push-coalescer.js +52 -23
  79. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  80. package/dist/src/mcpl/reference-fetcher.d.ts +66 -15
  81. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  82. package/dist/src/mcpl/reference-fetcher.js +243 -76
  83. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  84. package/dist/src/mcpl/references.d.ts +63 -18
  85. package/dist/src/mcpl/references.d.ts.map +1 -1
  86. package/dist/src/mcpl/references.js +117 -32
  87. package/dist/src/mcpl/references.js.map +1 -1
  88. package/dist/src/mcpl/server-connection.d.ts +326 -30
  89. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  90. package/dist/src/mcpl/server-connection.js +995 -212
  91. package/dist/src/mcpl/server-connection.js.map +1 -1
  92. package/dist/src/mcpl/server-registry.d.ts +25 -5
  93. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  94. package/dist/src/mcpl/server-registry.js +51 -12
  95. package/dist/src/mcpl/server-registry.js.map +1 -1
  96. package/dist/src/mcpl/tool-result-normalize.d.ts +54 -0
  97. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -0
  98. package/dist/src/mcpl/tool-result-normalize.js +232 -0
  99. package/dist/src/mcpl/tool-result-normalize.js.map +1 -0
  100. package/dist/src/mcpl/transport.d.ts +56 -13
  101. package/dist/src/mcpl/transport.d.ts.map +1 -1
  102. package/dist/src/mcpl/transport.js +206 -70
  103. package/dist/src/mcpl/transport.js.map +1 -1
  104. package/dist/src/mcpl/types.d.ts +66 -20
  105. package/dist/src/mcpl/types.d.ts.map +1 -1
  106. package/dist/src/mcpl/types.js.map +1 -1
  107. package/dist/src/mcpl/visible-content.d.ts +2 -1
  108. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  109. package/dist/src/mcpl/visible-content.js +2 -1
  110. package/dist/src/mcpl/visible-content.js.map +1 -1
  111. package/dist/src/module-registry.d.ts +16 -6
  112. package/dist/src/module-registry.d.ts.map +1 -1
  113. package/dist/src/module-registry.js +65 -6
  114. package/dist/src/module-registry.js.map +1 -1
  115. package/dist/src/modules/api/index.d.ts +1 -1
  116. package/dist/src/modules/api/index.d.ts.map +1 -1
  117. package/dist/src/modules/api/index.js +3 -2
  118. package/dist/src/modules/api/index.js.map +1 -1
  119. package/dist/src/modules/discord/discord-js-client.d.ts +42 -2
  120. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  121. package/dist/src/modules/discord/discord-js-client.js +268 -65
  122. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  123. package/dist/src/modules/discord/index.d.ts +27 -5
  124. package/dist/src/modules/discord/index.d.ts.map +1 -1
  125. package/dist/src/modules/discord/index.js +366 -76
  126. package/dist/src/modules/discord/index.js.map +1 -1
  127. package/dist/src/modules/discord/refusal.d.ts +14 -0
  128. package/dist/src/modules/discord/refusal.d.ts.map +1 -0
  129. package/dist/src/modules/discord/refusal.js +14 -0
  130. package/dist/src/modules/discord/refusal.js.map +1 -0
  131. package/dist/src/modules/discord/types.d.ts +17 -7
  132. package/dist/src/modules/discord/types.d.ts.map +1 -1
  133. package/dist/src/modules/health/index.d.ts.map +1 -1
  134. package/dist/src/modules/health/index.js +6 -2
  135. package/dist/src/modules/health/index.js.map +1 -1
  136. package/dist/src/modules/index.d.ts +1 -1
  137. package/dist/src/modules/index.d.ts.map +1 -1
  138. package/dist/src/modules/index.js +1 -1
  139. package/dist/src/modules/index.js.map +1 -1
  140. package/dist/src/modules/workspace/disk-agreement.d.ts +25 -2
  141. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  142. package/dist/src/modules/workspace/disk-agreement.js +45 -5
  143. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  144. package/dist/src/modules/workspace/effects.d.ts +2 -1
  145. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  146. package/dist/src/modules/workspace/effects.js +19 -9
  147. package/dist/src/modules/workspace/effects.js.map +1 -1
  148. package/dist/src/modules/workspace/index.d.ts +214 -42
  149. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  150. package/dist/src/modules/workspace/index.js +667 -113
  151. package/dist/src/modules/workspace/index.js.map +1 -1
  152. package/dist/src/modules/workspace/observe.d.ts +240 -8
  153. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  154. package/dist/src/modules/workspace/observe.js +554 -24
  155. package/dist/src/modules/workspace/observe.js.map +1 -1
  156. package/dist/src/modules/workspace/types.d.ts +10 -1
  157. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  158. package/dist/src/modules/workspace/types.js.map +1 -1
  159. package/dist/src/modules/workspace/watcher.d.ts +42 -0
  160. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  161. package/dist/src/modules/workspace/watcher.js +130 -25
  162. package/dist/src/modules/workspace/watcher.js.map +1 -1
  163. package/dist/src/silent-heartbeat.d.ts +112 -39
  164. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  165. package/dist/src/silent-heartbeat.js +100 -29
  166. package/dist/src/silent-heartbeat.js.map +1 -1
  167. package/dist/src/tool-args.d.ts +40 -0
  168. package/dist/src/tool-args.d.ts.map +1 -0
  169. package/dist/src/tool-args.js +207 -0
  170. package/dist/src/tool-args.js.map +1 -0
  171. package/dist/src/tool-presentation.d.ts.map +1 -1
  172. package/dist/src/tool-presentation.js +9 -2
  173. package/dist/src/tool-presentation.js.map +1 -1
  174. package/dist/src/types/agent.d.ts +27 -17
  175. package/dist/src/types/agent.d.ts.map +1 -1
  176. package/dist/src/types/events.d.ts +8 -0
  177. package/dist/src/types/events.d.ts.map +1 -1
  178. package/dist/src/types/framework.d.ts +17 -2
  179. package/dist/src/types/framework.d.ts.map +1 -1
  180. package/dist/src/types/module.d.ts +54 -10
  181. package/dist/src/types/module.d.ts.map +1 -1
  182. package/dist/src/types/trace.d.ts +2 -2
  183. package/dist/src/types/trace.d.ts.map +1 -1
  184. package/package.json +3 -4
  185. package/src/agent.ts +99 -36
  186. package/src/code-execution/index.ts +1 -0
  187. package/src/code-execution/py-runner.ts +104 -17
  188. package/src/code-execution/runtime-py.ts +47 -7
  189. package/src/code-execution/tool-definition.ts +65 -11
  190. package/src/config-checks.ts +4 -2
  191. package/src/framework.ts +3633 -956
  192. package/src/gate/event-gate.ts +14 -6
  193. package/src/held-writes.ts +59 -0
  194. package/src/index.ts +19 -4
  195. package/src/mcpl/capability-grant.ts +1 -1
  196. package/src/mcpl/channel-registry.ts +613 -160
  197. package/src/mcpl/convert-block.ts +15 -7
  198. package/src/mcpl/index.ts +14 -1
  199. package/src/mcpl/inference-router.ts +8 -7
  200. package/src/mcpl/log-value.ts +10 -0
  201. package/src/mcpl/modern-connection.ts +1182 -0
  202. package/src/mcpl/prefix-rule.ts +32 -0
  203. package/src/mcpl/protocol-family.ts +691 -0
  204. package/src/mcpl/push-coalescer.ts +67 -29
  205. package/src/mcpl/reference-fetcher.ts +288 -82
  206. package/src/mcpl/references.ts +123 -33
  207. package/src/mcpl/server-connection.ts +1050 -221
  208. package/src/mcpl/server-registry.ts +50 -12
  209. package/src/mcpl/tool-result-normalize.ts +294 -0
  210. package/src/mcpl/transport.ts +204 -62
  211. package/src/mcpl/types.ts +67 -20
  212. package/src/mcpl/visible-content.ts +2 -1
  213. package/src/module-registry.ts +68 -13
  214. package/src/modules/api/index.ts +3 -2
  215. package/src/modules/discord/discord-js-client.ts +288 -72
  216. package/src/modules/discord/index.ts +380 -77
  217. package/src/modules/discord/refusal.ts +13 -0
  218. package/src/modules/discord/types.ts +17 -9
  219. package/src/modules/health/index.ts +6 -2
  220. package/src/modules/index.ts +1 -1
  221. package/src/modules/workspace/disk-agreement.ts +46 -5
  222. package/src/modules/workspace/effects.ts +18 -7
  223. package/src/modules/workspace/index.ts +722 -124
  224. package/src/modules/workspace/observe.ts +591 -22
  225. package/src/modules/workspace/types.ts +10 -1
  226. package/src/modules/workspace/watcher.ts +126 -25
  227. package/src/silent-heartbeat.ts +184 -59
  228. package/src/tool-args.ts +213 -0
  229. package/src/tool-presentation.ts +9 -2
  230. package/src/types/agent.ts +27 -17
  231. package/src/types/events.ts +8 -0
  232. package/src/types/framework.ts +17 -2
  233. package/src/types/module.ts +55 -10
  234. package/src/types/trace.ts +2 -2
@@ -26,6 +26,8 @@
26
26
  * code-execution materialization hook possible.
27
27
  */
28
28
 
29
+ import { NON_STRING_TEXT_PLACEHOLDER } from './visible-content.js';
30
+
29
31
  export const REFERENCE_LIMITS = { uri: 4096, mimeType: 255, name: 255, expiresAt: 64 } as const;
30
32
  export const DIGEST_PATTERN = /^sha256:[A-Za-z0-9_-]{43}$/;
31
33
  const MAX_SAFE = 9007199254740991; // 2^53 - 1
@@ -53,7 +55,8 @@ export type BlockClassification =
53
55
  | { kind: 'text' }
54
56
  | { kind: 'inline'; contradiction: boolean }
55
57
  | { kind: 'reference'; testimony: ReferenceTestimony }
56
- | { kind: 'invalid'; reason: string };
58
+ /** `shown` is what the model-visible lanes put in the block's place. */
59
+ | { kind: 'invalid'; shown: string };
57
60
 
58
61
  function isRecord(x: unknown): x is Record<string, unknown> {
59
62
  return typeof x === 'object' && x !== null && !Array.isArray(x);
@@ -80,13 +83,13 @@ function takeString(
80
83
 
81
84
  export function classifyBlock(block: unknown): BlockClassification {
82
85
  if (!isRecord(block) || typeof block.type !== 'string') {
83
- return { kind: 'invalid', reason: 'not a content block' };
86
+ return { kind: 'invalid', shown: unknownBlockText(block) };
84
87
  }
85
88
  switch (block.type) {
86
89
  case 'text':
87
90
  return typeof block.text === 'string'
88
91
  ? { kind: 'text' }
89
- : { kind: 'invalid', reason: 'text block without string text' };
92
+ : { kind: 'invalid', shown: NON_STRING_TEXT_PLACEHOLDER };
90
93
  case 'image':
91
94
  case 'audio': {
92
95
  const hasData = typeof block.data === 'string';
@@ -94,32 +97,30 @@ export function classifyBlock(block: unknown): BlockClassification {
94
97
  if (hasData) {
95
98
  // Inline form claiming bulk disposition (or smuggling a uri beside
96
99
  // the data) is the vector-2 contradiction: fail closed, withhold.
100
+ // A uri of '' counts here too, though it's no link: until the
101
+ // tool-result lane gives every inline shape a marker of its own,
102
+ // showing that data would put some of it into history as raw JSON.
103
+ // Once that lane does, an empty link beside data reads as no link.
97
104
  return { kind: 'inline', contradiction: block.disposition !== undefined || hasUri };
98
105
  }
99
- if (hasUri) return parseReferenceFields(block);
100
- return { kind: 'invalid', reason: `${block.type} block ${mediaWithoutStringReason(block)}` };
106
+ // With no data, an empty uri is no link, as the push lane's converter
107
+ // reads it.
108
+ if (hasUri && block.uri !== '') return parseReferenceFields(block);
109
+ return { kind: 'invalid', shown: block.type === 'image' ? IMAGE_NO_DATA_TEXT : AUDIO_NO_DATA_TEXT };
101
110
  }
102
111
  case 'resource':
103
112
  return parseReferenceFields(block);
104
113
  default:
105
- return { kind: 'invalid', reason: `unknown block type: ${block.type}` };
114
+ return { kind: 'invalid', shown: unknownBlockText(block) };
106
115
  }
107
116
  }
108
117
 
109
- /** What a media block with neither string data nor a string uri has instead:
110
- * nothing, or a field that isn't a string. */
111
- function mediaWithoutStringReason(block: Record<string, unknown>): string {
112
- const notString = (['data', 'uri'] as const).filter((f) => block[f] !== undefined && block[f] !== null);
113
- if (notString.length === 0) return 'with neither data nor uri';
114
- return notString.length === 2 ? "whose data and uri aren't strings" : `whose ${notString[0]} isn't a string`;
115
- }
116
-
117
118
  function parseReferenceFields(block: Record<string, unknown>): BlockClassification {
118
119
  if (typeof block.uri !== 'string' || block.uri.length === 0) {
119
- return { kind: 'invalid', reason: 'reference without string uri' };
120
+ return { kind: 'invalid', shown: REFERENCE_NO_LINK_TEXT };
120
121
  }
121
122
  if (block.uri.length > REFERENCE_LIMITS.uri) {
122
- return { kind: 'invalid', reason: 'uri exceeds schema maximum' };
123
+ return { kind: 'invalid', shown: REFERENCE_LINK_TOO_LONG_TEXT };
123
124
  }
124
125
  const out = { rejected: [] as string[], truncated: [] as string[] };
125
126
  const t: ReferenceTestimony = { uri: block.uri, rejectedFields: out.rejected, truncatedFields: out.truncated };
@@ -152,7 +153,38 @@ export function isInlineContradiction(block: unknown): boolean {
152
153
  }
153
154
 
154
155
  export const INLINE_WITHHELD_TEXT =
155
- '[inline content withheld: inline data that also claims a uri or a bulk disposition]';
156
+ '[Content not shown: its inline data arrived marked as a reference too, so this host withheld it.]';
157
+
158
+ /*
159
+ * What the model-visible lanes (push, channel, context injection and tool
160
+ * results) put in place of a block they can't show as itself. Each says what
161
+ * kind of thing was there and why it can't be seen, in the host's words, so
162
+ * the agent doesn't read the content as complete; there's nothing for the
163
+ * agent to do about any of them. The text marker is NON_STRING_TEXT_PLACEHOLDER
164
+ * (visible-content.ts).
165
+ */
166
+ export const IMAGE_NO_MEDIA_TYPE_TEXT = '[Image not shown: its data arrived without a media type.]';
167
+ export const IMAGE_NO_DATA_TEXT = '[Image not shown: it arrived with no usable data or link.]';
168
+ export const AUDIO_NO_MEDIA_TYPE_TEXT = '[Audio not shown: its data arrived without a media type.]';
169
+ /** On the push, channel and context-injection lanes, audio shows only as
170
+ * inline data, so a plain link isn't enough either. (A tool result's audio
171
+ * with a link is a reference, as any block with one is there.) */
172
+ export const AUDIO_NO_DATA_TEXT = '[Audio not shown: it arrived with no usable inline data.]';
173
+ export const REFERENCE_NO_LINK_TEXT = '[Reference not shown: it arrived without a link.]';
174
+ export const REFERENCE_LINK_TOO_LONG_TEXT = '[Reference not shown: its link is longer than this host accepts.]';
175
+ const UNREADABLE_BLOCK_TEXT = "[Content not shown: a part arrived in a form this host can't read.]";
176
+
177
+ /** A block of a type the host doesn't show, of no type at all, or no block.
178
+ * The type is the sender's word, so it's shown as a label is: bounded, with
179
+ * its controls removed. One that isn't a string is no type, and is never
180
+ * converted to one. */
181
+ export function unknownBlockText(block: unknown): string {
182
+ if (!isRecord(block)) return UNREADABLE_BLOCK_TEXT;
183
+ const label = typeof block.type === 'string' ? sanitizeLabel(block.type, STUB_FIELD_CHARS) : '';
184
+ return label
185
+ ? `[Content not shown: this host can't display "${label}" content.]`
186
+ : '[Content not shown: a part arrived with no type.]';
187
+ }
156
188
 
157
189
  /**
158
190
  * Why a reference counts as expired, or null when it doesn't: its expiresAt
@@ -177,17 +209,28 @@ export function isReferenceExpired(t: Pick<ReferenceTestimony, 'expiresAt' | 're
177
209
  return referenceExpiry(t, nowMs) !== null;
178
210
  }
179
211
 
180
- /** Each expiry case as the stub and the fetcher's refusal both say it. */
212
+ /** Each expiry case in the agent's words, as the stub, the fetcher's refusal
213
+ * and save_recent_image's refusal all say it. */
181
214
  export const EXPIRY_REASON = {
182
- passed: 'expired: its expiresAt has passed',
183
- unparseable: 'treated as expired: its expiresAt does not parse (fail closed)',
184
- rejected: `treated as expired: its expiresAt was rejected as a field, over ${REFERENCE_LIMITS.expiresAt} characters or not a string (fail closed)`,
215
+ passed: 'expired',
216
+ unparseable: "treated as expired, since its expiry time can't be read",
217
+ rejected: "treated as expired, since its expiry time isn't in a form this host accepts",
185
218
  } as const;
186
219
 
187
- /** A reference registered with no server, as the stub, the fetcher's refusal
188
- * and save_recent_image's all say it: the fetcher dials only through a
189
- * server that delivered the reference. */
190
- export const UNBOUND_REASON = 'unbound: no server is recorded to fetch it from';
220
+ /** A reference registered with no server, in the agent's words, as the stub,
221
+ * the fetcher's refusal and save_recent_image's all say it: the fetcher dials
222
+ * only through a server that delivered the reference. */
223
+ export const UNBOUND_REASON = "not tied to any server, so there's nowhere to fetch it from";
224
+
225
+ /** The fetcher's refusal for a server it has no address to dial, one the
226
+ * host doesn't connect to over a network, such as a stdio server, as a
227
+ * clause that completes "couldn't fetch <ref>: " and says so of the server;
228
+ * the fetcher dials only through a server's address. save_recent_image gives
229
+ * it too. A stub can't know it, since only the framework holds its servers'
230
+ * configs. */
231
+ export function noAddressReason(serverId: string): string {
232
+ return `this host doesn't connect to the "${serverId}" server over a network, so there's nowhere to fetch it from`;
233
+ }
191
234
 
192
235
  /** Strip control characters (C0, DEL, C1) and every bidi control (marks,
193
236
  * embeddings, overrides, isolates, and the Arabic letter mark), trim the
@@ -371,13 +414,15 @@ export function buildReferenceStub(t: ReferenceTestimony, provenance?: string, s
371
414
  }
372
415
 
373
416
  /**
374
- * Why the fetcher would refuse a reference, as far as its record shows: no
375
- * server registered for it, which the fetcher checks first, or its expiry.
376
- * Null for a saved copy, which the fetcher serves before any check, and when
377
- * the record shows neither. The fetcher's other refusals rest on what a record
378
- * doesn't hold: whether any of its servers is still known to the host and on
379
- * the uri's origin, a claimed size over the ceiling a fetch is given, or the
380
- * dialed server's fetch budget spent, and it gives those in its own words.
417
+ * Why the fetcher would refuse a reference, as far as its record alone shows,
418
+ * for its stub: no server registered for it, which the fetcher checks first,
419
+ * or its expiry. Null for a saved copy, which the fetcher serves before any
420
+ * check, and when the record shows neither. The fetcher's other refusals rest
421
+ * on what a record doesn't hold: whether any of its servers is known to the
422
+ * host, has an address and is on the uri's origin, the dialed server's fetch
423
+ * budget spent, or a claimed size over the ceiling a fetch is given. The
424
+ * fetcher gives those in its own words, and save_recent_image, in the
425
+ * framework where it can ask the fetcher, says them too.
381
426
  */
382
427
  export function unfetchableReason(record: ReferenceRecord, nowMs = Date.now()): string | null {
383
428
  if (record.fetchedPath) return null;
@@ -398,6 +443,51 @@ export function referenceStubOrNull(block: unknown, provenance?: string, serverI
398
443
  case 'inline':
399
444
  return c.contradiction ? INLINE_WITHHELD_TEXT : null;
400
445
  case 'reference': return buildReferenceStub(c.testimony, provenance, serverId);
401
- case 'invalid': return `[unrecognized content block: ${sanitizeLabel(c.reason, STUB_FIELD_CHARS)}]`;
446
+ case 'invalid': return c.shown;
447
+ }
448
+ }
449
+
450
+ /**
451
+ * RFC-005 treatment for one MCPL wire block, for a script's structured
452
+ * result, which carries blocks rather than text. It makes the decision
453
+ * referenceStubOrNull makes for the model's lanes, since what a script prints
454
+ * comes back to the model. A reference becomes the stub's facts as data,
455
+ * without its URI: the host's reference id, what the server said about it in
456
+ * this block (its labels stripped and bounded as in the stub), and `savedTo`,
457
+ * the workspace path its payload was fetched to, or null when it wasn't
458
+ * (`fetch_reference` takes the id as `ref_id`). An unsaved reference that
459
+ * counts as expired also carries `expired`, the reason the stub gives in
460
+ * place of advising fetch_reference. The reference is registered bound to
461
+ * `serverId`, the server whose tool returned it, as the model's lanes bind
462
+ * a tool result's references, so the fetch its id offers can be made. A
463
+ * block the classifier
464
+ * rejects becomes the notice the model's lanes show, and inline data that
465
+ * also claims a bulk disposition the withheld notice, each as a text block.
466
+ * A text block, or inline data that claims nothing, is returned as sent.
467
+ */
468
+ export function referenceBlockForScript(block: unknown, serverId: string): unknown {
469
+ const c = classifyBlock(block);
470
+ switch (c.kind) {
471
+ case 'text': return block;
472
+ case 'inline':
473
+ return c.contradiction ? { type: 'text', text: INLINE_WITHHELD_TEXT } : block;
474
+ case 'invalid': return { type: 'text', text: c.shown };
475
+ case 'reference': {
476
+ const t = c.testimony;
477
+ const record = referenceRegistry.register(t, serverId);
478
+ // As the stub judges it: a saved copy is still served.
479
+ const expiry = record.fetchedPath ? null : referenceExpiry(record.testimony);
480
+ return {
481
+ type: (block as { type: string }).type,
482
+ refId: record.refId,
483
+ ...(t.name !== undefined ? { name: sanitizeLabel(t.name, STUB_FIELD_CHARS) } : {}),
484
+ ...(t.mimeType !== undefined ? { mimeType: sanitizeLabel(t.mimeType, STUB_FIELD_CHARS) } : {}),
485
+ ...(t.sizeBytes !== undefined ? { sizeBytes: t.sizeBytes } : {}),
486
+ ...(t.digest !== undefined ? { digest: t.digest } : {}),
487
+ ...(t.expiresAt !== undefined ? { expiresAt: sanitizeLabel(t.expiresAt, STUB_FIELD_CHARS) } : {}),
488
+ savedTo: record.fetchedPath ?? null,
489
+ ...(expiry ? { expired: EXPIRY_REASON[expiry] } : {}),
490
+ };
491
+ }
402
492
  }
403
493
  }