functionalscript 0.46.0 → 0.47.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.
Files changed (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +120 -0
  157. package/fjs/types/rtti/validate/module.f.mjs +351 -0
  158. package/fjs/types/rtti/validate/proof.f.d.mts +154 -0
  159. package/fjs/types/rtti/validate/proof.f.mjs +781 -0
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. package/package.json +2 -3
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * @import { Unknown } from '../../media/json/types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../../types/ts/types.ts'
3
5
  * @import { Operation } from '../../effects/types.ts'
4
6
  * @import { Effect, NotImplemented } from '../../effects/types.ts'
5
7
  * @import { MemOperationMap } from '../../effects/mock/types.ts'
@@ -7,6 +9,7 @@
7
9
  * @import {
8
10
  * ToolsListParams,
9
11
  * ToolsCallParams,
12
+ * ToolsCallResult,
10
13
  * McpHandlers,
11
14
  * McpConfig,
12
15
  * McpSessionState,
@@ -48,6 +51,11 @@ export declare const proof: {
48
51
  methodDuringInitializingReturnsNotInitialized: () => void;
49
52
  invalidEnvelopeReturnsInvalidRequest: () => void;
50
53
  };
54
+ versionNegotiation: {
55
+ supportedRequestIsEchoed: () => void;
56
+ unsupportedRequestGetsLatestSupported: () => void;
57
+ oneVersionCounterProposesItsOnlyVersion: () => void;
58
+ };
51
59
  tools: {
52
60
  toolsListSucceeds: () => void;
53
61
  toolsListPassesCursorToHandler: () => void;
@@ -62,5 +70,10 @@ export declare const proof: {
62
70
  };
63
71
  registry: {
64
72
  toolsCallAbsentArgumentsDefaultsToEmptyObject: () => void;
73
+ inlineSchemaLiteralKeepsItsLiteralMembers: () => void;
74
+ };
75
+ toolResultStep: {
76
+ okIsRendered: () => void;
77
+ errorIsRenderedInBand: () => void;
65
78
  };
66
79
  };
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * @import { Unknown } from '../../media/json/types.ts'
3
+ * @import { Assert } from '../../asserts/types.ts'
4
+ * @import { Equal } from '../../types/ts/types.ts'
3
5
  * @import { Operation } from '../../effects/types.ts'
4
6
  * @import { Effect, NotImplemented } from '../../effects/types.ts'
5
7
  * @import { MemOperationMap } from '../../effects/mock/types.ts'
@@ -7,6 +9,7 @@
7
9
  * @import {
8
10
  * ToolsListParams,
9
11
  * ToolsCallParams,
12
+ * ToolsCallResult,
10
13
  * McpHandlers,
11
14
  * McpConfig,
12
15
  * McpSessionState,
@@ -15,13 +18,15 @@
15
18
 
16
19
  import { assert, assertEq } from '../../asserts/module.f.mjs'
17
20
  import { runPure } from '../../effects/module.f.mjs'
18
- import { history, historyStep, mapStep, pureOk, step } from '../../effects/module.f.mjs'
21
+ import { history, historyStep, mapStep, pureError, pureOk, step } from '../../effects/module.f.mjs'
19
22
  import { error, ok, unwrap as unwrapResult } from '../../types/result/module.f.mjs'
20
23
  import { run } from '../../effects/mock/module.f.mjs'
21
24
  import { internalError } from '../json_rpc/module.f.mjs'
25
+ import { string } from '../../types/rtti/module.f.mjs'
22
26
  import { asBase, asNominal, create, read } from '../../effects/memory/module.f.mjs'
23
27
  import {
24
28
  uninitializedState, mcpStep, notInitialized, fromRegistry, toolEntry, okResult,
29
+ toolResultStep,
25
30
  } from './module.f.mjs'
26
31
 
27
32
  // ── Memory mock ────────────────────────────────────────────────────────────────
@@ -55,11 +60,16 @@ const mock = {
55
60
  const config = {
56
61
  serverInfo: { name: 'test-server', version: '0.1.0' },
57
62
  capabilities: { tools: {} },
58
- protocolVersion: '2024-11-05',
63
+ protocolVersions: ['2024-11-05'],
59
64
  }
60
65
 
61
66
  const configNoTools = { ...config, capabilities: {} }
62
67
 
68
+ // A server speaking two revisions, latest first — the case a single
69
+ // `protocolVersion` string could not describe.
70
+ /** @type {McpConfig} */
71
+ const configTwoVersions = { ...config, protocolVersions: ['2025-06-18', '2024-11-05'] }
72
+
63
73
  /** @typedef {never} _Op */
64
74
  /** @type {McpHandlers<_Op>} */
65
75
  const handlers = {
@@ -208,16 +218,39 @@ const firstText = resp => {
208
218
  return text
209
219
  }
210
220
 
221
+ // ── `toolResultStep` renderers ────────────────────────────────────────────────
222
+
223
+ // Shared by both branches so each renderer is written once and reached once:
224
+ // the `ok` test exercises `dashes`, the `error` test `errorText`.
225
+
226
+ /** @type {(xs: readonly string[]) => string} */
227
+ const dashes = xs => xs.join('-')
228
+
229
+ /** @type {(e: string) => string} */
230
+ const errorText = e => `failed: ${e}`
231
+
232
+ /** The `text` of the first content item of a `ToolsCallResult`. */
233
+ /** @type {(r: ToolsCallResult) => string} */
234
+ const textOf = r => {
235
+ const [item] = r.content
236
+ assert(item.type === 'text', item)
237
+ return item.text
238
+ }
239
+
211
240
  // ── Test messages ─────────────────────────────────────────────────────────────
212
241
 
213
- const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
214
- params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'client', version: '0.0.1' } } }
242
+ /** An `initialize` request asking for `protocolVersion`. */
243
+ /** @type {(protocolVersion: string) => Unknown} */
244
+ const initMsgFor = protocolVersion => ({ jsonrpc: '2.0', method: 'initialize', id: 1,
245
+ params: { protocolVersion, capabilities: {}, clientInfo: { name: 'client', version: '0.0.1' } } })
246
+
247
+ const initMsg = initMsgFor('2024-11-05')
215
248
 
216
249
  const initNotif = { jsonrpc: '2.0', method: 'notifications/initialized' }
217
250
 
218
251
  /** A memory handler that answers as a runner with no such operation. */
219
252
  const memNotImplemented = () => (/** @type {_MemoryState} */ state) =>
220
- /** @type {const} */ ([state, error(/** @type {const} */ (['notImplemented', 'memRead']))])
253
+ /** @type {const} */ ([state, error(['notImplemented', 'memRead'])])
221
254
 
222
255
  // Runs one step against a memory mock with `overrides` applied, from a session
223
256
  // slot created before them so the slot itself always exists.
@@ -376,6 +409,35 @@ export const proof = {
376
409
  },
377
410
  },
378
411
 
412
+ // Version negotiation, per the lifecycle spec: answer with the requested
413
+ // revision when the server speaks it, and with the latest one it does speak
414
+ // otherwise. The counter-proposal is a success frame, so what distinguishes
415
+ // the two branches is the answered version, never the session state.
416
+ versionNegotiation: {
417
+ supportedRequestIsEchoed: () => {
418
+ // `2024-11-05` is supported but not the latest — the case a
419
+ // single-string config had to counter-propose out of.
420
+ const [resp] = step1(configTwoVersions)(initMsgFor('2024-11-05'))
421
+ assertEq(protocolVersion(resp), '2024-11-05')
422
+ },
423
+
424
+ unsupportedRequestGetsLatestSupported: () => {
425
+ const [resp, newState] = step1(configTwoVersions)(initMsgFor('2030-01-01'))
426
+ assertEq(protocolVersion(resp), '2025-06-18')
427
+ assert(newState[0] === 'initializing')
428
+ },
429
+
430
+ // A one-element list answers exactly as the `protocolVersion` string
431
+ // field did: its only revision, whichever branch is taken. The echo
432
+ // half is `lifecycle.initializeReturnsResult`, which asks `config` for
433
+ // the one version it has; this is the counter-proposal half.
434
+ oneVersionCounterProposesItsOnlyVersion: () => {
435
+ const [resp, newState] = step1(config)(initMsgFor('2030-01-01'))
436
+ assertEq(protocolVersion(resp), '2024-11-05')
437
+ assert(newState[0] === 'initializing')
438
+ },
439
+ },
440
+
379
441
  tools: {
380
442
  toolsListSucceeds: () => {
381
443
  const msg = { jsonrpc: '2.0', method: 'tools/list', id: 5 }
@@ -453,8 +515,7 @@ export const proof = {
453
515
  // validating it against the empty-object schema below would fail and
454
516
  // this would observe an error result instead of `ok`.
455
517
  toolsCallAbsentArgumentsDefaultsToEmptyObject: () => {
456
- const echoArgs = /** @type {const} */ ({})
457
- const entry = toolEntry('echo', 'echoes', echoArgs,
518
+ const entry = toolEntry('echo', 'echoes', {},
458
519
  () => pureOk(okResult('ok')))
459
520
  const handlers = fromRegistry([entry])
460
521
  const [r] = runPure(handlers.toolsCall({ name: 'echo' }))
@@ -463,5 +524,44 @@ export const proof = {
463
524
  assert(item.type === 'text', item)
464
525
  assertEq(item.text, 'ok')
465
526
  },
527
+ // The type guarantee `./README.md` documents: an inline schema literal
528
+ // reaches the handler as `Ts<T>`, with no cast on either side. That
529
+ // rests on `toolEntry`'s `const` type parameter — without it `kind`
530
+ // widens to `string` and the assertion below fails, which is the only
531
+ // place that would notice.
532
+ inlineSchemaLiteralKeepsItsLiteralMembers: () => {
533
+ const entry = toolEntry('tag', 'tags', { kind: 'add', name: string },
534
+ ({ kind, name }) => {
535
+ /** @typedef {Assert<Equal<typeof kind, 'add'>>} _ConstParameter */
536
+ return pureOk(okResult(`${kind}:${name}`))
537
+ })
538
+ const handlers = fromRegistry([entry])
539
+ const [r] = runPure(handlers.toolsCall(
540
+ { name: 'tag', arguments: { kind: 'add', name: 'x' } }))
541
+ assert(r !== undefined && r[0] === 'ok', r)
542
+ const [item] = r[1].content
543
+ assert(item.type === 'text', item)
544
+ assertEq(item.text, 'add:x')
545
+ },
546
+ },
547
+
548
+ // `toolResultStep` picks between `okResult` and `errorResult`, so both
549
+ // branches are the whole of it — and the error branch is the one that
550
+ // proves the failure stays in-band (`isError`) instead of escaping as a
551
+ // transport error, which the `never` error channel is the type-level claim
552
+ // of.
553
+ toolResultStep: {
554
+ okIsRendered: () => {
555
+ const [r] = runPure(toolResultStep(pureOk(['a', 'b']), dashes, errorText))
556
+ assert(r !== undefined && r[0] === 'ok', r)
557
+ assertEq(r[1].isError, undefined)
558
+ assertEq(textOf(r[1]), 'a-b')
559
+ },
560
+ errorIsRenderedInBand: () => {
561
+ const [r] = runPure(toolResultStep(pureError('gone'), dashes, errorText))
562
+ assert(r !== undefined && r[0] === 'ok', r)
563
+ assertEq(r[1].isError, true)
564
+ assertEq(textOf(r[1]), 'failed: gone')
565
+ },
466
566
  },
467
567
  }
@@ -41,19 +41,18 @@ import { ioError, readLine, write } from '../../../effects/node/module.f.mjs'
41
41
  import { tryUtf8 } from '../../../text/module.f.mjs'
42
42
  import { parse, stringify } from '../../../media/json/module.f.mjs'
43
43
  import { sort } from '../../../types/object/module.f.mjs'
44
- import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.mjs'
44
+ import { errorResponseOf, internalError, parseError } from '../../json_rpc/module.f.mjs'
45
45
  import { error } from '../../../types/result/module.f.mjs'
46
46
 
47
47
  const stringifyJson = stringify(sort)
48
48
 
49
49
  /** The parse-error response (`-32700`, `id: null`) for a malformed input line. */
50
- /** @type {Response} */
51
- const parseErrorResponse = { jsonrpc, error: parseError, id: null }
50
+ const parseErrorResponse = errorResponseOf(null)(parseError)
52
51
 
53
52
  /** An internal-error response (`-32603`) carrying `id`.
54
53
  * @type {(id: Response['id']) => Response}
55
54
  */
56
- const internalErrorResponse = id => ({ jsonrpc, error: internalError, id })
55
+ const internalErrorResponse = id => errorResponseOf(id)(internalError)
57
56
 
58
57
  /** Encodes a response as a newline-terminated UTF-8 line and writes it to `stdout`.
59
58
  * @type {(resp: Response) => Effect<Write, void, IoChannel>}
@@ -79,7 +79,7 @@ export const proof = {
79
79
  // `eofImmediately` above pins it as the clean shutdown it should be.
80
80
  readFailurePropagates: () => {
81
81
  const runner = mockRun(/** @type {Parameters<typeof mockRun<Read | Write, undefined>>[0]} */ ({
82
- read: () => (/** @type {undefined} */ s) => [s, error(/** @type {const} */ (['notImplemented', 'read']))],
82
+ read: () => (/** @type {undefined} */ s) => [s, error(['notImplemented', 'read'])],
83
83
  write: () => (/** @type {undefined} */ s) => [s, ok(undefined)],
84
84
  }))
85
85
  const [, result] = runner(undefined)(stdioTransport(echoStep))
@@ -95,7 +95,7 @@ export const proof = {
95
95
  const runner = mockRun(/** @type {Parameters<typeof mockRun<Read | Write, readonly number[]>>[0]} */ ({
96
96
  read: () => (/** @type {readonly number[]} */ s) =>
97
97
  s.length === 0
98
- ? [s, error(/** @type {const} */ (['notImplemented', 'read']))]
98
+ ? [s, error(['notImplemented', 'read'])]
99
99
  : [s.slice(1), ok(s[0])],
100
100
  write: () => (/** @type {readonly number[]} */ s) => [s, ok(undefined)],
101
101
  }))
@@ -48,7 +48,7 @@ export type ToolEntry<O extends Operation> = {
48
48
  * **`never` is a claim, not an absence.** The handler behind this does perform
49
49
  * effects and they can fail — a session-state read is dispatched by a runner
50
50
  * that may decline it. It says `never` because it has *absorbed* those: a
51
- * request's failure becomes `_errResponse(id)(internalError)` and a
51
+ * request's failure becomes `errorResponseOf(id)(internalError)` and a
52
52
  * notification's is dropped, there being no frame to put it in. Spelling that
53
53
  * as `Effect<…, never>` puts the decision in the type where a reader can
54
54
  * disagree with it, which an opaque payload could not.
@@ -62,9 +62,21 @@ export type Initializing = readonly ['initializing'];
62
62
  export type InitializedState = true;
63
63
  /** The three phases of an MCP session. */
64
64
  export type McpSessionState = Uninitialized | Initializing | readonly ['initialized', InitializedState];
65
+ /**
66
+ * The protocol revisions a server supports, **latest first** and non-empty by
67
+ * construction — the tuple's head is what an unsupported request is answered
68
+ * with, so "the server supports nothing" is not a state that can be reached.
69
+ */
70
+ export type ProtocolVersions = readonly [string, ...readonly string[]];
65
71
  /** Static configuration supplied by the server implementer. */
66
72
  export type McpConfig = {
67
73
  readonly serverInfo: Implementation;
68
74
  readonly capabilities: ServerCapabilities;
69
- readonly protocolVersion: string;
75
+ /**
76
+ * Every revision this server speaks, latest first. `initialize` answers
77
+ * with the client's requested version when it is in this list and with the
78
+ * head — the latest supported one — otherwise, which is the counter-proposal
79
+ * the lifecycle spec prescribes.
80
+ */
81
+ readonly protocolVersions: ProtocolVersions;
70
82
  };
@@ -19,6 +19,7 @@ import {
19
19
  isValidCodePoint,
20
20
  } from '../code_point/module.f.mjs'
21
21
  import { msb, u8List, length } from '../../types/bit_vec/module.f.mjs'
22
+ import { contains } from '../../types/range/module.f.mjs'
22
23
  import { codePointListToString } from '../utf16/module.f.mjs'
23
24
 
24
25
  /**
@@ -43,6 +44,31 @@ const lead3Mask = 0b0000_1111
43
44
  const lead4Tag = 0b1111_0000
44
45
  const lead4Mask = 0b0000_0111
45
46
 
47
+ const isInU8Range = contains(0x00, 0xff)
48
+
49
+ /**
50
+ * Whether `i` is a byte this decoder can be handed.
51
+ *
52
+ * `U8` is just `number`, so neither half is redundant. The dispatch below
53
+ * partitions only the *integers* in `0x00`–`0xff` — below `contTag`, a
54
+ * continuation, or one of the leads, with no gap — so a fraction falls between
55
+ * two of those and would be misclassified rather than rejected: emitted as a
56
+ * code point when it is below `contTag`, tagged with a fractional payload when
57
+ * it is not. Worse where the payload arithmetic reaches it, since the bitwise
58
+ * operators truncate silently: a fractional continuation byte would decode to
59
+ * the very code point its integer part spells, reporting nothing.
60
+ *
61
+ * The range half is not carrying `NaN` — `contains` is written positively, so
62
+ * `isInU8Range(NaN)` is already `false`. `Number.isInteger`'s own job here is
63
+ * the *in-range* fraction, which no bound can reach.
64
+ *
65
+ * This mirrors `u16` in `../utf16/module.f.mjs`, which carries the same check
66
+ * for the same reason; the two differ only in their bounds.
67
+ *
68
+ * @type {(i: number) => boolean}
69
+ */
70
+ const u8 = i => Number.isInteger(i) && isInU8Range(i)
71
+
46
72
  /**
47
73
  * Encodes the low six bits of `x` as a UTF-8 continuation byte.
48
74
  *
@@ -199,7 +225,7 @@ export const utf8StateToError = state => {
199
225
  * @type {StateScan<number, Utf8State, readonly I32[]>}
200
226
  */
201
227
  export const utf8ByteToCodePointOp = (byte, state) => {
202
- if (byte < 0x00 || byte > 0xff) {
228
+ if (!u8(byte)) {
203
229
  return [[errorMask], state]
204
230
  }
205
231
  if (state === null) return restart([])(byte)
@@ -82,6 +82,43 @@ export const proof = {
82
82
  () => {
83
83
  const result = stringify(utf8ByteToCodePointOp(0x80, [0xf8, 0x80]))
84
84
  assertEq(result, '[[-2147483136,-2147483520],null]')
85
+ },
86
+ // `U8` is just `number`, so a non-integer in [0x00, 0xff] is a
87
+ // possible (if malformed) input. The dispatch partitions only the
88
+ // integers in that range, so a fraction has to be rejected here rather
89
+ // than misclassified: below `contTag` it would otherwise be emitted as
90
+ // a code point, and above it tagged with a fractional payload.
91
+ () => {
92
+ const result = stringify(toArray(toCodePointList([1.5])))
93
+ assertEq(result, '[2147483648]')
94
+ },
95
+ () => {
96
+ const result = stringify(toArray(toCodePointList([200.5])))
97
+ assertEq(result, '[2147483648]')
98
+ },
99
+ // `NaN` is flagged either way — the old guard let it past and the
100
+ // payload arithmetic tagged it anyway, as `errorMask | 0`. Pinned for
101
+ // the spelling: rejecting it up front gives the canonical `errorMask`
102
+ // rather than its `| 0` form.
103
+ () => {
104
+ const result = stringify(toArray(toCodePointList([NaN])))
105
+ assertEq(result, '[2147483648]')
106
+ },
107
+ // A fractional continuation byte is the case that hides: the bitwise
108
+ // payload arithmetic truncates it, so `C3 A9.5` decoded as U+00E9 —
109
+ // the same character `C3 A9` spells — with nothing reported. It is now
110
+ // invalid on its own, and the pending lead is flagged incomplete.
111
+ () => {
112
+ const result = stringify(toArray(toCodePointList([0xc3, 0xa9])))
113
+ assertEq(result, '[233]')
114
+ },
115
+ () => {
116
+ const result = stringify(toArray(toCodePointList([0xc3, 169.5])))
117
+ assertEq(result, '[2147483648,-2147483453]')
118
+ },
119
+ () => {
120
+ const result = stringify(toArray(toCodePointList([0xf0, 0x90, 128.5])))
121
+ assertEq(result, '[2147483648,-2147483120]')
85
122
  }
86
123
  ],
87
124
  fromCodePointList: [
@@ -6,7 +6,7 @@
6
6
  * @import { Tuple } from './types.ts'
7
7
  */
8
8
 
9
- import { fromUndefined, map } from '../nullable/module.f.mjs'
9
+ import { fromUndefined } from '../nullable/module.f.mjs'
10
10
 
11
11
  /**
12
12
  * @type {(value: unknown) => value is readonly unknown[]}
@@ -61,6 +61,11 @@ export const last = a => at(a.length - 1)(a)
61
61
  * through `fromUndefined`, so it reads a *stored* `null` or `undefined`
62
62
  * element as absence. `[null]` is not empty, but `first` says it is.
63
63
  *
64
+ * For the same reason `splitFirst`/`splitLast` take their element by index
65
+ * rather than projecting `first`/`last` through `nullable`'s `map`: their
66
+ * result tells "present and nullish" apart from "empty" — `[null, []]` is not
67
+ * `null` — where `at`/`first`/`last`, answering `T | null`, cannot.
68
+ *
64
69
  * @type {<T, R>(f: (a: readonly T[]) => R) => (a: readonly T[]) => R | null}
65
70
  */
66
71
  const onNonEmpty = f => a => a.length === 0 ? null : f(a)
@@ -70,24 +75,26 @@ const onNonEmpty = f => a => a.length === 0 ? null : f(a)
70
75
  */
71
76
  export const tail = onNonEmpty(uncheckTail)
72
77
 
78
+ /**
79
+ * @type {<T>(a: readonly T[]) => readonly [T, readonly T[]]}
80
+ */
81
+ const uncheckSplitFirst = a => [a[0], uncheckTail(a)]
82
+
73
83
  /**
74
84
  * @type {<T>(a: readonly T[]) => readonly [T, readonly T[]] | null}
75
85
  */
76
- export const splitFirst = a => {
77
- /** @typedef {(typeof a)[0]} T */
78
- const split = (/** @type {T} */first) =>
79
- /** @type {const} */([first, uncheckTail(a)])
80
- return map(split)(first(a))
81
- }
86
+ export const splitFirst = onNonEmpty(uncheckSplitFirst)
82
87
 
83
88
  /** @type {<T>(a: readonly T[]) => readonly T[] | null} */
84
89
  export const head = onNonEmpty(uncheckHead)
85
90
 
91
+ /**
92
+ * @type {<T>(a: readonly T[]) => readonly [readonly T[], T]}
93
+ */
94
+ const uncheckSplitLast = a => [uncheckHead(a), a[a.length - 1]]
95
+
86
96
  /** @type {<T>(a: readonly T[]) => readonly [readonly T[], T] | null} */
87
- export const splitLast = a => {
88
- const lastA = last(a)
89
- return lastA === null ? null : [uncheckHead(a), lastA]
90
- }
97
+ export const splitLast = onNonEmpty(uncheckSplitLast)
91
98
 
92
99
  /**
93
100
  * An empty immutable array.
@@ -1,7 +1,7 @@
1
1
  import { at, first, last, head, tail, splitFirst, splitLast, empty } from './module.f.mjs'
2
2
  import { stringify as jsonStringify } from '../../media/json/module.f.mjs'
3
3
  import { sort } from '../object/module.f.mjs'
4
- import { assertEq, assertNotNullish } from '../../asserts/module.f.mjs'
4
+ import { assertEq, assertNotNullish, assertStructurallySame } from '../../asserts/module.f.mjs'
5
5
 
6
6
  const stringify = jsonStringify(sort)
7
7
 
@@ -74,6 +74,20 @@ export const proof = {
74
74
  const result = splitFirst([])
75
75
  assertEq(result, null)
76
76
  },
77
+ // A stored nullish element is an element: `[null]` has a first one,
78
+ // even though `first([null])` cannot say so.
79
+ () => {
80
+ const result = splitFirst([null])
81
+ assertStructurallySame(result, [null, []])
82
+ },
83
+ () => {
84
+ const result = splitFirst([undefined])
85
+ assertStructurallySame(result, [undefined, []])
86
+ },
87
+ () => {
88
+ const result = splitFirst([undefined, 20, 300])
89
+ assertStructurallySame(result, [undefined, [20, 300]])
90
+ },
77
91
  ],
78
92
  splitLast: [
79
93
  () => {
@@ -84,6 +98,18 @@ export const proof = {
84
98
  () => {
85
99
  const result = splitLast([])
86
100
  assertEq(result, null)
101
+ },
102
+ () => {
103
+ const result = splitLast([null])
104
+ assertStructurallySame(result, [[], null])
105
+ },
106
+ () => {
107
+ const result = splitLast([undefined])
108
+ assertStructurallySame(result, [[], undefined])
109
+ },
110
+ () => {
111
+ const result = splitLast([1, 20, undefined])
112
+ assertStructurallySame(result, [[1, 20], undefined])
87
113
  }
88
114
  ],
89
115
  empty: () => {
@@ -3,12 +3,28 @@
3
3
  *
4
4
  * @module
5
5
  *
6
- * @import { BigFloat } from './types.ts'
6
+ * @import { BigFloat, Format } from './types.ts'
7
+ * @import { Nullable } from '../nullable/types.ts'
7
8
  */
8
- import type { BigFloat } from './types.ts';
9
+ import type { BigFloat, Format } from './types.ts';
10
+ import type { Nullable } from '../nullable/types.ts';
9
11
  export type _BigFloatWithRemainder = readonly [BigFloat, bigint];
10
12
  /** @type {(_: BigFloat) => (mul: bigint) => BigFloat} */
11
13
  export declare const multiply: (_: BigFloat) => (mul: bigint) => BigFloat;
14
+ /**
15
+ * IEEE-754 binary64 — the format of a JavaScript `number`.
16
+ *
17
+ * Both exponents are *ulp* exponents, the same units a `BigFloat`'s own
18
+ * exponent is in: `minExp` is the exponent of the smallest subnormal
19
+ * (`2^-1074`), not the smallest normal's `-1022`, and `maxExp` is the exponent
20
+ * the largest finite value carries once its mantissa fills `precision` bits
21
+ * (`(2^53 - 1) * 2^971`). The two conventions differ by the precision, and a
22
+ * `Format` that mixes them is off by exactly that much, so this one never
23
+ * leaves `BigFloat`'s units.
24
+ *
25
+ * @type {Format}
26
+ */
27
+ export declare const binary64: Format;
12
28
  /**
13
29
  * Converts a decimal big-float `m * 10^e` into the nearest binary big-float,
14
30
  * rounding ties to even.
@@ -18,17 +34,40 @@ export declare const multiply: (_: BigFloat) => (mul: bigint) => BigFloat;
18
34
  * even when rounding carries out of the top bit.
19
35
  *
20
36
  * The **exponent is unbounded**: this is the correctly-rounded 53-bit value,
21
- * not a `number`. Nothing here knows binary64's exponent range, so a result
22
- * is neither turned into an infinity when it is too large for a `double` nor
23
- * cut down to the fewer-than-53 bits a subnormal actually carries. A consumer
24
- * that rounds one of these results onto the subnormal grid therefore rounds
25
- * twice and can land an ulp away from the correctly-rounded `double`: reaching
26
- * the subnormal range needs a single rounding to a precision chosen from the
27
- * target exponent, which this function does not offer yet.
37
+ * not a `number`. A result too large for a `double` is not turned into an
38
+ * infinity, and one below the normal range keeps all 53 bits instead of the
39
+ * fewer a subnormal carries. That is the honest answer when the target is not
40
+ * a `double` and the wrong starting point when it is, because rounding one
41
+ * of these results onto the subnormal grid rounds twice and can land an ulp
42
+ * away from the correctly-rounded `double`. Use {@link tryDecToFormat} with
43
+ * {@link binary64} for that; it rounds once.
28
44
  *
29
45
  * @type {(dec: BigFloat) => BigFloat}
30
46
  */
31
47
  export declare const decToBin: (dec: BigFloat) => BigFloat;
48
+ /**
49
+ * Converts a decimal big-float `m * 10^e` into the nearest value of `format`,
50
+ * rounding ties to even, or `null` when the magnitude is too large for the
51
+ * format to hold — the caller decides what an overflow becomes, since
52
+ * `BigFloat` has no encoding for an infinity.
53
+ *
54
+ * The result is a multiple of `2^format.minExp` carrying **at most**
55
+ * `format.precision` significant bits: exactly that many above the format's
56
+ * normal range, fewer below it, and `[0n, 0]` when the value rounds away
57
+ * entirely. Underflow to zero is not a separate signal, because `[0n, 0]` is
58
+ * the correctly-rounded answer rather than a failure to produce one.
59
+ *
60
+ * The rounding happens **once**, on the exact decimal, which is the whole
61
+ * point of taking the format rather than post-processing {@link decToBin}. The
62
+ * grid it rounds onto is `2^max(minExp, e + 1)`, where `e + 1` is the exponent
63
+ * a full-precision result would carry: full precision above the normal range's
64
+ * floor, shrinking by a bit per binade below it, down to none at all. Rounding
65
+ * to `precision` bits first and onto that grid afterwards is two roundings,
66
+ * and the first can manufacture a midpoint the true value only approached.
67
+ *
68
+ * @type {(format: Format) => (dec: BigFloat) => Nullable<BigFloat>}
69
+ */
70
+ export declare const tryDecToFormat: (format: Format) => (dec: BigFloat) => Nullable<BigFloat>;
32
71
  export declare const proof: {
33
72
  normalizeMantissaZero: () => void;
34
73
  };