@volter/world-core 2.0.37 → 3.0.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 (205) hide show
  1. package/README.md +4 -5
  2. package/app-route.cjs +12 -6
  3. package/app-route.d.cts +1 -1
  4. package/dist/app-route.cjs +12 -6
  5. package/dist/app-route.d.cts +1 -1
  6. package/dist/generated/pack-facts.json +1410 -3069
  7. package/dist/inject.cjs +64 -9
  8. package/dist/pack-facts.cjs +44 -0
  9. package/dist/src/actions.d.ts +3 -3
  10. package/dist/src/actions.js +22 -16
  11. package/dist/src/ancestry.d.ts +14 -2
  12. package/dist/src/ancestry.js +92 -2
  13. package/dist/src/anthropic-wire.d.ts +39 -0
  14. package/dist/src/anthropic-wire.js +136 -0
  15. package/dist/src/bytes.d.ts +7 -0
  16. package/dist/src/bytes.js +35 -0
  17. package/dist/src/changeset.d.ts +1 -1
  18. package/dist/src/changeset.js +0 -0
  19. package/dist/src/clickhouse/index.d.ts +3 -0
  20. package/dist/src/clickhouse/index.js +6 -0
  21. package/dist/src/clickhouse/sql.d.ts +233 -0
  22. package/dist/src/clickhouse/sql.js +4329 -0
  23. package/dist/src/clickhouse/types.d.ts +18 -0
  24. package/dist/src/clickhouse/types.js +47 -0
  25. package/dist/src/clickhouse/values.d.ts +146 -0
  26. package/dist/src/clickhouse/values.js +858 -0
  27. package/dist/src/client-bundle.js +2 -3
  28. package/dist/src/cors.d.ts +15 -0
  29. package/dist/src/cors.js +31 -0
  30. package/dist/src/derived-core.d.ts +487 -24
  31. package/dist/src/derived-core.js +788 -144
  32. package/dist/src/derived-real.d.ts +13 -0
  33. package/dist/src/derived-real.js +518 -0
  34. package/dist/src/derived.d.ts +35 -1
  35. package/dist/src/derived.js +61 -9
  36. package/dist/src/emit.js +1 -2
  37. package/dist/src/events.d.ts +206 -0
  38. package/dist/src/events.js +341 -0
  39. package/dist/src/executor.d.ts +3 -0
  40. package/dist/src/executor.js +19 -2
  41. package/dist/src/file-response.d.ts +6 -0
  42. package/dist/src/file-response.js +30 -0
  43. package/dist/src/fork.js +3 -2
  44. package/dist/src/git/history.d.ts +7 -0
  45. package/dist/src/git/history.js +24 -0
  46. package/dist/src/git/index.d.ts +1 -0
  47. package/dist/src/git/index.js +1 -0
  48. package/dist/src/git/lfs.d.ts +28 -0
  49. package/dist/src/git/lfs.js +66 -0
  50. package/dist/src/git/objects.js +3 -8
  51. package/dist/src/git/smart-http.d.ts +3 -1
  52. package/dist/src/git/smart-http.js +67 -6
  53. package/dist/src/graphql-wire.d.ts +29 -0
  54. package/dist/src/graphql-wire.js +101 -0
  55. package/dist/src/grpc-wire.d.ts +67 -0
  56. package/dist/src/grpc-wire.js +170 -0
  57. package/dist/src/h2.d.ts +40 -0
  58. package/dist/src/h2.js +656 -0
  59. package/dist/src/head.d.ts +32 -3
  60. package/dist/src/head.js +161 -40
  61. package/dist/src/history.d.ts +1 -1
  62. package/dist/src/history.js +6 -6
  63. package/dist/src/hpack.json +1 -0
  64. package/dist/src/index.d.ts +64 -75
  65. package/dist/src/index.js +58 -101
  66. package/dist/src/log.js +28 -19
  67. package/dist/src/machines.d.ts +50 -0
  68. package/dist/src/machines.js +151 -0
  69. package/dist/src/managed-database.d.ts +86 -0
  70. package/dist/src/managed-database.js +283 -0
  71. package/dist/src/multipart.d.ts +11 -0
  72. package/dist/src/multipart.js +51 -0
  73. package/dist/src/observe.d.ts +15 -5
  74. package/dist/src/observe.js +23 -9
  75. package/dist/src/openai-wire.d.ts +108 -0
  76. package/dist/src/openai-wire.js +337 -0
  77. package/dist/src/pack-assets.d.ts +3 -4
  78. package/dist/src/pack-assets.js +15 -10
  79. package/dist/src/pack-fetch.d.ts +77 -0
  80. package/dist/src/pack-fetch.js +449 -0
  81. package/dist/src/pack-paths.d.ts +12 -0
  82. package/dist/src/pack-paths.js +86 -0
  83. package/dist/src/packRegistry.d.ts +69 -162
  84. package/dist/src/packRegistry.js +55 -20
  85. package/dist/src/people.d.ts +13 -0
  86. package/dist/src/people.js +18 -0
  87. package/dist/src/placeholder-image.d.ts +5 -0
  88. package/dist/src/placeholder-image.js +114 -0
  89. package/dist/src/protobuf.d.ts +28 -0
  90. package/dist/src/protobuf.js +332 -0
  91. package/dist/src/redis/engine.js +1 -1
  92. package/dist/src/request-scope.d.ts +1 -1
  93. package/dist/src/request-scope.js +6 -4
  94. package/dist/src/resource-blob.d.ts +5 -0
  95. package/dist/src/resource-blob.js +11 -0
  96. package/dist/src/runtime.d.ts +85 -0
  97. package/dist/src/runtime.js +104 -0
  98. package/dist/src/s3/wire.d.ts +60 -0
  99. package/dist/src/s3/wire.js +157 -0
  100. package/dist/src/scenario.d.ts +3 -0
  101. package/dist/src/scenario.js +2 -0
  102. package/dist/src/schema-sample.d.ts +1 -0
  103. package/dist/src/schema-sample.js +21 -0
  104. package/dist/src/sealed-box.d.ts +14 -0
  105. package/dist/src/sealed-box.js +225 -0
  106. package/dist/src/serve-http.d.ts +14 -0
  107. package/dist/src/serve-http.js +27 -3
  108. package/dist/src/serve.d.ts +6 -0
  109. package/dist/src/serve.js +69 -14
  110. package/dist/src/signing.d.ts +135 -0
  111. package/dist/src/signing.js +222 -0
  112. package/dist/src/sigv4.d.ts +48 -0
  113. package/dist/src/sigv4.js +167 -0
  114. package/dist/src/smtp.d.ts +16 -0
  115. package/dist/src/smtp.js +72 -0
  116. package/dist/src/sockets.d.ts +51 -0
  117. package/dist/src/sockets.js +90 -0
  118. package/dist/src/state-system.d.ts +1 -0
  119. package/dist/src/state-system.js +1 -1
  120. package/dist/src/storage.d.ts +1 -1
  121. package/dist/src/storage.js +3 -3
  122. package/dist/src/trace-context.js +1 -1
  123. package/dist/src/twin-fetch.d.ts +0 -7
  124. package/dist/src/twin-fetch.js +0 -14
  125. package/dist/src/vendor-call.d.ts +6 -0
  126. package/dist/src/vendor-call.js +41 -0
  127. package/dist/src/world-store.js +1 -1
  128. package/dist/vendor-hosts.cjs +36 -125
  129. package/dist/vendor-hosts.d.cts +8 -0
  130. package/generated/pack-facts.json +1410 -3069
  131. package/inject.cjs +64 -9
  132. package/pack-facts.cjs +44 -0
  133. package/package.json +17 -3
  134. package/src/actions.ts +23 -16
  135. package/src/ancestry.ts +74 -2
  136. package/src/anthropic-wire.ts +137 -0
  137. package/src/bytes.ts +42 -0
  138. package/src/changeset.ts +5 -5
  139. package/src/clickhouse/index.ts +6 -0
  140. package/src/clickhouse/sql.ts +3059 -0
  141. package/src/clickhouse/types.ts +44 -0
  142. package/src/clickhouse/values.ts +697 -0
  143. package/src/client-bundle.ts +2 -3
  144. package/src/cors.ts +34 -0
  145. package/src/derived-core.ts +1013 -146
  146. package/src/derived-real.ts +434 -0
  147. package/src/derived.ts +73 -3
  148. package/src/emit.ts +1 -2
  149. package/src/events.ts +449 -0
  150. package/src/executor.ts +24 -2
  151. package/src/file-response.ts +27 -0
  152. package/src/fork.ts +3 -2
  153. package/src/git/history.ts +19 -0
  154. package/src/git/index.ts +1 -0
  155. package/src/git/lfs.ts +67 -0
  156. package/src/git/objects.ts +3 -5
  157. package/src/git/smart-http.ts +56 -6
  158. package/src/graphql-wire.ts +106 -0
  159. package/src/grpc-wire.ts +159 -0
  160. package/src/h2.ts +627 -0
  161. package/src/head.ts +132 -41
  162. package/src/history.ts +6 -6
  163. package/src/hpack.json +1 -0
  164. package/src/index.ts +82 -329
  165. package/src/log.ts +27 -18
  166. package/src/machines.ts +151 -0
  167. package/src/managed-database.ts +299 -0
  168. package/src/multipart.ts +51 -0
  169. package/src/observe.ts +31 -15
  170. package/src/openai-wire.ts +371 -0
  171. package/src/pack-assets.ts +15 -11
  172. package/src/pack-fetch.ts +458 -0
  173. package/src/pack-paths.ts +72 -0
  174. package/src/packRegistry.ts +79 -167
  175. package/src/people.ts +31 -0
  176. package/src/placeholder-image.ts +88 -0
  177. package/src/protobuf.ts +251 -0
  178. package/src/redis/engine.ts +1 -1
  179. package/src/request-scope.ts +8 -4
  180. package/src/resource-blob.ts +13 -0
  181. package/src/runtime.ts +344 -0
  182. package/src/s3/wire.ts +172 -0
  183. package/src/scenario.ts +4 -0
  184. package/src/schema-sample.ts +24 -0
  185. package/src/sealed-box.ts +182 -0
  186. package/src/serve-http.ts +31 -3
  187. package/src/serve.ts +58 -14
  188. package/src/signing.ts +231 -0
  189. package/src/sigv4.ts +158 -0
  190. package/src/smtp.ts +76 -0
  191. package/src/sockets.ts +140 -0
  192. package/src/state-system.ts +2 -2
  193. package/src/storage.ts +3 -3
  194. package/src/trace-context.ts +1 -1
  195. package/src/twin-fetch.ts +0 -20
  196. package/src/vendor-call.ts +41 -0
  197. package/src/world-store.ts +1 -1
  198. package/vendor-hosts.cjs +36 -125
  199. package/vendor-hosts.d.cts +8 -0
  200. package/dist/src/mirror-shell.d.ts +0 -2
  201. package/dist/src/mirror-shell.js +0 -13
  202. package/dist/src/v1-removed.d.ts +0 -159
  203. package/dist/src/v1-removed.js +0 -124
  204. package/src/mirror-shell.ts +0 -15
  205. package/src/v1-removed.ts +0 -172
package/inject.cjs CHANGED
@@ -13,8 +13,7 @@
13
13
  // e.g. STRIPE_TWIN_URL=http://127.0.0.1:12111 or a hosted skin URL
14
14
  // {twinsUrl}/{org}/{world}/{vendor} with VOLTER_TWINS_KEY set (R7c). Host claims come
15
15
  // from pack-facts at runtime — this file carries no per-vendor roster to rot.
16
- // AWS service-areas (DYNAMODB/TIMESTREAM/SESV2/SECRETSMANAGER/BEDROCK_TWIN_URL) point at
17
- // the consolidated `aws` twin, same as S3_TWIN_URL.
16
+ // The aws pack's service keys (S3_TWIN_URL, SECRETSMANAGER_TWIN_URL) both point at its one twin.
18
17
  // …or combined: TWIN_INJECT="stripe=http://127.0.0.1:12111,github=http://127.0.0.1:12120"
19
18
  //
20
19
  // It patches http/https `request`/`get` and global `fetch` at the process
@@ -44,6 +43,7 @@ const { createPublicKey, createVerify } = require('crypto');
44
43
  // serves it) has its own home, shared with the application route
45
44
  const { VENDOR_HOSTS, twinEnvStem, twinOrigins } = require('./vendor-hosts.cjs');
46
45
  const dns = require('dns');
46
+ const http2 = require('http2');
47
47
 
48
48
  function decodeBase64UrlJson(part) {
49
49
  try {
@@ -130,7 +130,7 @@ installClerkFastifyTwinShim();
130
130
  // twin that is attached AND whose URL variable is set; the first such pack wins.
131
131
  function installPrismaAdapterShim() {
132
132
  if (Module.__volterPrismaAdapterShimInstalled) return;
133
- const { packs } = require('./generated/pack-facts.json');
133
+ const { packs } = require('./pack-facts.cjs').packFacts();
134
134
  const candidates = Object.keys(packs).filter((vendor) => packs[vendor].prismaAdapter).sort();
135
135
  if (candidates.length === 0) return;
136
136
  Module.__volterPrismaAdapterShimInstalled = true;
@@ -225,7 +225,7 @@ function resolveTwin(hostname, map, pathname) {
225
225
  const base = worldBase();
226
226
  // a name a vendor's host rule gives a twin running here is that twin's, never an application's (a vendor with no
227
227
  // twin here can be one of the World's applications: the product itself)
228
- const app = appRouteFor(map[APP_INSTANCE], bare, map);
228
+ const app = appRouteFor(map[APP_INSTANCE], bare, map, claimedAppHosts(map));
229
229
  if (app && !isTwinOriginHost(bare) && (!base || new URL(base.origin).hostname !== bare)) {
230
230
  return { vendor: 'app', origin: app.origin };
231
231
  }
@@ -245,17 +245,18 @@ const { appRouteFor, appForwardHeaders, appRequestOptions, sessionCa } = require
245
245
  // to a vendor, so each claiming twin lists its own at its door, read here every five seconds from the moment the
246
246
  // injector installs (or, in a process that only resolves, the proxy, from its first resolve). A fetch that would be
247
247
  // refused while a first read is still in flight waits for it; a name connected moments ago is refused until the next
248
- // read, as a new DNS record takes a moment.
248
+ // read, as a new DNS record takes a moment. The door's `appHosts` are the names a hosting twin serves as the
249
+ // customer's own application (a domain added to a Vercel project): those route to the application, not the twin.
249
250
  const CLAIMERS = {};
250
251
  {
251
- const { packs } = require('./generated/pack-facts.json');
252
+ const { packs } = require('./pack-facts.cjs').packFacts();
252
253
  for (const vendor of Object.keys(packs)) if (packs[vendor].hostsClaimed) CLAIMERS[vendor] = packs[vendor].hostsClaimed.door;
253
254
  }
254
255
  const CLAIMS = new Map(); // cache: origin -> the hosts its twin last listed; refreshed by the poll below
255
256
  function claimsAt(origin, door) {
256
257
  const held = CLAIMS.get(origin);
257
258
  if (held) return held;
258
- const entry = { hosts: new Set(), loaded: false, ready: null };
259
+ const entry = { hosts: new Set(), appHosts: new Set(), loaded: false, ready: null };
259
260
  let settle;
260
261
  entry.ready = new Promise((resolve) => { settle = resolve; });
261
262
  CLAIMS.set(origin, entry);
@@ -266,7 +267,10 @@ function claimsAt(origin, door) {
266
267
  Promise.resolve()
267
268
  .then(() => f(`${origin}${door}`, { headers: attachTwinsKey({}) }))
268
269
  .then((r) => (r.ok ? r.json() : null))
269
- .then((body) => { if (body && Array.isArray(body.hosts)) entry.hosts = new Set(body.hosts.map((h) => String(h).toLowerCase())); })
270
+ .then((body) => {
271
+ if (body && Array.isArray(body.hosts)) entry.hosts = new Set(body.hosts.map((h) => String(h).toLowerCase()));
272
+ if (body) entry.appHosts = new Set(Array.isArray(body.appHosts) ? body.appHosts.map((h) => String(h).toLowerCase()) : []);
273
+ })
270
274
  .catch(() => {})
271
275
  .then(() => { entry.loaded = true; settle(); });
272
276
  };
@@ -277,6 +281,15 @@ function claimsAt(origin, door) {
277
281
  if (timer.unref) timer.unref();
278
282
  return entry;
279
283
  }
284
+ /** The names the claiming twins of this map serve as the application (their doors' `appHosts`), or null for none. */
285
+ function claimedAppHosts(map) {
286
+ let out = null;
287
+ for (const vendor of Object.keys(CLAIMERS)) {
288
+ if (!map[vendor]) continue;
289
+ for (const h of claimsAt(map[vendor], CLAIMERS[vendor]).appHosts) (out ??= new Set()).add(h);
290
+ }
291
+ return out;
292
+ }
280
293
  /** The first read of a claiming twin's door still in flight for this map, or null when every one has landed. */
281
294
  function claimsPending(map) {
282
295
  const pending = Object.keys(CLAIMERS).filter((v) => map[v]).map((v) => claimsAt(map[v], CLAIMERS[v])).filter((e) => !e.loaded);
@@ -737,7 +750,7 @@ function socketTargetHosts(args, isTls) {
737
750
 
738
751
  function endpointEnvFor(vendor) {
739
752
  try {
740
- const facts = require('./generated/pack-facts.json').packs[vendor];
753
+ const facts = require('./pack-facts.cjs').packFacts().packs[vendor];
741
754
  const endpoint = facts && facts.endpointEnv;
742
755
  if (!endpoint) return null;
743
756
  const templates = Object.keys(endpoint.templates || {});
@@ -868,6 +881,33 @@ function patchedConnect(originalConnect, isTls) {
868
881
  let MAP = {};
869
882
  let installed = false;
870
883
 
884
+ /** A request asking to switch protocols (a WebSocket's `Upgrade: websocket`): a vendor's socket a twin serves (Slack's
885
+ * Socket Mode link, Discord's Gateway), which the fetch-backed stand-in cannot carry. */
886
+ function isUpgrade(headers) {
887
+ for (const [k, v] of Object.entries(headers || {})) if (k.toLowerCase() === 'upgrade' && v) return true;
888
+ return false;
889
+ }
890
+
891
+ /** An upgrade to a twinned host, sent as a real request to the twin's own listener (its origin's scheme, host and port,
892
+ * the vendor's path under it), the vendor's host kept in `Host`: the client's upgrade, its socket and its frames are the
893
+ * twin's. The caller's own connection options (a TLS `createConnection`, an agent, a servername) name the vendor and
894
+ * are left behind. Where the evidence stops: a hosted World's front upgrades nothing yet, so this carries a local
895
+ * World's twins only. */
896
+ function upgradeToTwin(origin, url, method, headers, args, callback) {
897
+ const target = new URL(origin);
898
+ const given = args.find((a) => a && typeof a === 'object' && !(a instanceof URL)) || {};
899
+ const options = {};
900
+ for (const [k, v] of Object.entries(given)) {
901
+ if (!['protocol', 'host', 'hostname', 'port', 'path', 'agent', 'createConnection', 'servername', 'defaultPort', 'socketPath', 'headers', 'method', 'href', 'origin', 'search', 'pathname'].includes(k)) options[k] = v;
902
+ }
903
+ Object.assign(options, {
904
+ protocol: target.protocol, hostname: target.hostname, port: target.port || (target.protocol === 'https:' ? 443 : 80),
905
+ path: `${target.pathname.replace(/\/+$/, '')}${url.pathname}${url.search}`, method, headers,
906
+ });
907
+ const request = target.protocol === 'https:' ? ORIGINALS.httpsRequest : ORIGINALS.httpRequest;
908
+ return callback ? request(options, callback) : request(options);
909
+ }
910
+
871
911
  function patchedFactory(originalRequest, defaultProtocol) {
872
912
  return function patchedRequest(...args) {
873
913
  const { url, method, headers, callback } = describe(args, defaultProtocol);
@@ -879,6 +919,7 @@ function patchedFactory(originalRequest, defaultProtocol) {
879
919
  if (twin) {
880
920
  const fwdHeaders = Object.assign({}, headers);
881
921
  if (!fwdHeaders.host && !fwdHeaders.Host) fwdHeaders.host = url.host;
922
+ if (isUpgrade(headers)) return upgradeToTwin(twin.origin, url, method, fwdHeaders, args, callback);
882
923
  return fakeClientRequest(twin.origin, method, url.pathname + url.search, fwdHeaders, callback);
883
924
  }
884
925
  // No twin claims this path — but if configured twins claim the HOST, the path is an unclaimed
@@ -926,6 +967,7 @@ function install(mapOverride) {
926
967
  tlsConnect: tls.connect,
927
968
  dnsLookup: dns.lookup,
928
969
  dnsPromisesLookup: dns.promises.lookup,
970
+ http2Connect: http2.connect,
929
971
  };
930
972
 
931
973
  http.request = patchedFactory(ORIGINALS.httpRequest, 'http:');
@@ -937,6 +979,18 @@ function install(mapOverride) {
937
979
  tls.connect = patchedConnect(ORIGINALS.tlsConnect, true);
938
980
  dns.lookup = patchedLookup(ORIGINALS.dnsLookup);
939
981
  dns.promises.lookup = patchedPromisesLookup(ORIGINALS.dnsPromisesLookup);
982
+ // gRPC (architecture, "Other wires": gRPC): an HTTP/2 session to a host a twin serves (grpc-js on google-gax: TLS to
983
+ // webrisk.googleapis.com) connects to that twin's origin in plain text, where the kernel's HTTP/2 server answers it;
984
+ // the client carries on unchanged. Any other authority is the real one's.
985
+ http2.connect = function patchedHttp2Connect(authority, options, listener) {
986
+ let url = null;
987
+ try { url = new URL(typeof authority === 'string' ? authority : authority && authority.href ? authority.href : `https://${authority.hostname || authority.host}`); } catch { url = null; }
988
+ const twin = url && resolveTwin(url.hostname, MAP, '/');
989
+ if (!twin) return ORIGINALS.http2Connect.apply(this, arguments);
990
+ const cb = typeof options === 'function' ? options : listener;
991
+ const opts = typeof options === 'function' || !options ? {} : Object.fromEntries(Object.entries(options).filter(([k]) => !['secureContext', 'servername', 'ca', 'cert', 'key', 'checkServerIdentity', 'ALPNProtocols', 'createConnection', 'rejectUnauthorized'].includes(k)));
992
+ return ORIGINALS.http2Connect.call(this, new URL(twin.origin).origin, opts, cb);
993
+ };
940
994
 
941
995
  if (ORIGINALS.fetch) {
942
996
  const originalFetch = ORIGINALS.fetch;
@@ -1084,6 +1138,7 @@ function restore() {
1084
1138
  tls.connect = ORIGINALS.tlsConnect;
1085
1139
  dns.lookup = ORIGINALS.dnsLookup;
1086
1140
  dns.promises.lookup = ORIGINALS.dnsPromisesLookup;
1141
+ http2.connect = ORIGINALS.http2Connect;
1087
1142
  installed = false;
1088
1143
  MAP = {};
1089
1144
  }
package/pack-facts.cjs ADDED
@@ -0,0 +1,44 @@
1
+ // THE PACK FACTS a process routes by (docs/contributing/architecture.md, "The catalog: where twins come from"): the facts
2
+ // this kernel was built with (generated/pack-facts.json, compiled from the packs of its checkout), overlaid by the facts
3
+ // of the packs the World resolved from installed packages. Each released pack carries its own
4
+ // (`generated/pack-facts.json`), and the World's instance file (VOLTER_WORLD_INSTANCE, which every World process already
5
+ // has) names them as `packFacts`. A pack's own facts replace the built-in ones for its vendor. Plain CommonJS, no
6
+ // dependency: the injector preloads it. Read once per process.
7
+ 'use strict';
8
+ const fs = require('fs');
9
+
10
+ let cached;
11
+ function packFacts() {
12
+ if (cached) return cached;
13
+ const base = require('./generated/pack-facts.json');
14
+ const packs = Object.assign({}, base.packs);
15
+ // the vendors whose facts came from an installed pack (the host rules let those replace a hand entry)
16
+ const installed = [];
17
+ const instance = process.env.VOLTER_WORLD_INSTANCE;
18
+ if (instance) {
19
+ try {
20
+ const files = JSON.parse(fs.readFileSync(instance, 'utf8')).packFacts || [];
21
+ for (const file of files) {
22
+ try { const own = JSON.parse(fs.readFileSync(file, 'utf8')).packs || {}; Object.assign(packs, own); installed.push(...Object.keys(own)); } catch (_) { /* a pack without readable facts keeps the built-in ones */ }
23
+ }
24
+ } catch (_) { /* no instance yet: the built-in facts */ }
25
+ }
26
+ cached = Object.assign({}, base, { packs, installed });
27
+ return cached;
28
+ }
29
+
30
+ /** Facts files a process knows before any World instance names them (the runtime booting a World it has not written
31
+ * the instance of): merged as the instance's are, and into the host rules already built. */
32
+ function extendPackFacts(files) {
33
+ const facts = packFacts();
34
+ const added = {};
35
+ for (const file of files || []) {
36
+ try { Object.assign(added, JSON.parse(fs.readFileSync(file, 'utf8')).packs || {}); } catch (_) { /* the facts it has stand */ }
37
+ }
38
+ Object.assign(facts.packs, added);
39
+ // the host rules already built from the facts learn these packs too
40
+ require('./vendor-hosts.cjs').addPackHosts(added);
41
+ return facts;
42
+ }
43
+
44
+ module.exports = { packFacts, extendPackFacts };
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "2.0.37",
3
+ "version": "3.0.1",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
7
7
  "local",
8
8
  "mock",
9
- "mirror",
10
9
  "simulator",
11
10
  "fixtures",
12
11
  "testing",
@@ -29,6 +28,8 @@
29
28
  "network-policy.d.cts",
30
29
  "app-route.cjs",
31
30
  "vendor-hosts.cjs",
31
+ "pack-facts.cjs",
32
+ "vendor-hosts.d.cts",
32
33
  "world-clock.cjs",
33
34
  "world-clock.d.cts",
34
35
  "app-route.d.cts",
@@ -45,7 +46,7 @@
45
46
  ],
46
47
  "repository": {
47
48
  "type": "git",
48
- "url": "git+https://github.com/volter-ai/twin.git",
49
+ "url": "git+https://github.com/volter-ai/twin-world.git",
49
50
  "directory": "packages/world-core"
50
51
  },
51
52
  "homepage": "https://world.volter.ai/docs/README",
@@ -55,10 +56,18 @@
55
56
  "types": "./dist/src/index.d.ts",
56
57
  "default": "./dist/src/index.js"
57
58
  },
59
+ "./runtime": {
60
+ "types": "./dist/src/runtime.d.ts",
61
+ "default": "./dist/src/runtime.js"
62
+ },
58
63
  "./args": {
59
64
  "types": "./dist/src/args.d.ts",
60
65
  "default": "./dist/src/args.js"
61
66
  },
67
+ "./s3": {
68
+ "types": "./dist/src/s3/wire.d.ts",
69
+ "default": "./dist/src/s3/wire.js"
70
+ },
62
71
  "./schemas": {
63
72
  "types": "./dist/src/schemas.d.ts",
64
73
  "default": "./dist/src/schemas.js"
@@ -75,6 +84,7 @@
75
84
  "default": "./app-route.cjs"
76
85
  },
77
86
  "./vendor-hosts": "./vendor-hosts.cjs",
87
+ "./pack-facts": "./pack-facts.cjs",
78
88
  "./world-clock": {
79
89
  "types": "./world-clock.d.cts",
80
90
  "default": "./world-clock.cjs"
@@ -87,6 +97,10 @@
87
97
  "./redis": {
88
98
  "types": "./dist/src/redis/index.d.ts",
89
99
  "default": "./dist/src/redis/index.js"
100
+ },
101
+ "./clickhouse": {
102
+ "types": "./dist/src/clickhouse/index.d.ts",
103
+ "default": "./dist/src/clickhouse/index.js"
90
104
  }
91
105
  },
92
106
  "scripts": {
package/src/actions.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  //
12
12
  // Projection = observed mirror, then apply each `set` transaction in order,
13
13
  // skipping any transaction that was reverted or confirmed.
14
- import { withAncestryLock } from './ancestry.ts';
14
+ import { withHistoryLock } from './ancestry.ts';
15
15
  import { AsyncLocalStorage } from 'node:async_hooks';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { dirname, join } from 'node:path';
@@ -183,13 +183,19 @@ function actionIds(service: string, root?: string): ReadonlySet<string> {
183
183
  return ids;
184
184
  }
185
185
 
186
+ /** A service's projection lock, under its history lock: one directory's locks are taken history, projection,
187
+ * actions, events, in that order (architecture, the history lock). */
188
+ function withProjection<T>(service: string, root: string | undefined, fn: () => T): T {
189
+ return withHistoryLock(worldPaths(service, root).dir, () => withFileLock(projectionLock(service, root), fn));
190
+ }
191
+
186
192
  function appendActionRaw(action: TwinAction, root?: string): void {
187
193
  const path = actionsPath(action.service, root);
188
194
  getActiveWorldStore().mkdir(dirname(path));
189
195
  const memos = idMemoSlot(path);
190
196
  const held = memos.get(path);
191
197
  const current = held !== undefined && held.key === logKey(path);
192
- withAncestryLock(() => appendDurable(path, `${JSON.stringify(action)}\n`));
198
+ withHistoryLock(worldPaths(action.service, root).dir, () => appendDurable(path, `${JSON.stringify(action)}\n`));
193
199
  if (current) { held.ids.add(action.id); held.key = logKey(path); } else memos.delete(path);
194
200
  twinLog('action.append', { service: action.service, id: action.id, op: action.op, correlationId: action.correlationId });
195
201
  appendObserver?.(action, root);
@@ -234,9 +240,10 @@ function existingExactAction(action: TwinAction, root?: string): TwinAction | un
234
240
  return existing;
235
241
  }
236
242
 
237
- /** The request-scoped correlation id (D3): set by the kernel fetch adapter for the duration of one
238
- * handler call from the wire's `x-twins-request-id`; read by appendAction as the default. */
239
- // created on first use, never at import: a browser bundle of a mirror client carries this module and has
243
+ /** The request-scoped correlation id (D3): for the duration of one handler call, the id a pack fetch mints for the
244
+ * request (a caller's `x-twins-request-id` is recorded with each write as provenance, never as the group), or, under
245
+ * the kernel fetch adapter, the wire's `x-twins-request-id`; read by appendAction as the default. */
246
+ // created on first use, never at import: a browser bundle carrying the kernel carries this module and has
240
247
  // no AsyncLocalStorage (see serve.ts identitySlot)
241
248
  let correlationStore: AsyncLocalStorage<string> | undefined;
242
249
  const correlationScope = (): AsyncLocalStorage<string> => (correlationStore ??= new AsyncLocalStorage<string>());
@@ -249,7 +256,7 @@ export function appendAction(action: TwinAction, root?: string): TwinAction {
249
256
  // compare-and-set, not an advisory validation: checking outside either lock would let another
250
257
  // writer invalidate it before this action lands. The shadow basis is stamped under the same
251
258
  // lock so the recorded merge base is the mirror the preconditions were checked against.
252
- return withFileLock(projectionLock(action.service, root), () => withFileLock(actionsLock(action.service, root), () => {
259
+ return withProjection(action.service, root, () => withFileLock(actionsLock(action.service, root), () => {
253
260
  const stamped = withCorrelationId(action);
254
261
  assertPreconditions(stamped, root);
255
262
  const placeholder = landIfPlaceholderPull(stamped, root);
@@ -265,7 +272,7 @@ export function appendAction(action: TwinAction, root?: string): TwinAction {
265
272
  * writes both land). A reused id with different content fails loudly. */
266
273
  export function appendActionIfAbsent(action: TwinAction, root?: string): { action: TwinAction; appended: boolean; placeholder?: true } {
267
274
  refuseReadOnlyWrite(action.service);
268
- return withFileLock(projectionLock(action.service, root), () => withFileLock(actionsLock(action.service, root), () => {
275
+ return withProjection(action.service, root, () => withFileLock(actionsLock(action.service, root), () => {
269
276
  const identified = withCorrelationId(action);
270
277
  // An exact retry is already committed. Resolve it before re-evaluating author-time
271
278
  // preconditions against the state that first commit intentionally changed — and
@@ -285,7 +292,7 @@ export function appendActionIfAbsent(action: TwinAction, root?: string): { actio
285
292
  * Append `build(n)` as an OCCURRENCE: the nth time this exact write has been made.
286
293
  *
287
294
  * A local vendor write is an occurrence, not a replay — two calls are two actions, even
288
- * byte-identical in the same instant (docs/contributing/adding-a-twin.md#5-build-on-the-shared-kernel--dont-reinvent,
295
+ * byte-identical in the same instant (docs/contributing/architecture.md, "The state kernel",
289
296
  * "A local write is an occurrence"). `appendActionIfAbsent` cannot express that: its identity is content, and
290
297
  * content provably cannot separate "the same request delivered twice" from "the same change made
291
298
  * twice". So a caller wanting at-most-once passes an explicit key and uses that function; a caller
@@ -299,7 +306,7 @@ export function appendActionIfAbsent(action: TwinAction, root?: string): { actio
299
306
  */
300
307
  export function appendActionOccurrence(base: TwinAction, root?: string): { action: TwinAction; appended: boolean; placeholder?: true } {
301
308
  refuseReadOnlyWrite(base.service);
302
- return withFileLock(projectionLock(base.service, root), () => withFileLock(actionsLock(base.service, root), () => {
309
+ return withProjection(base.service, root, () => withFileLock(actionsLock(base.service, root), () => {
303
310
  const stamped = occurrenceOf(base, actionIds(base.service, root), root);
304
311
  assertPreconditions(stamped, root);
305
312
  const placeholder = landIfPlaceholderPull(stamped, root);
@@ -355,7 +362,7 @@ export function decideAndAppendAction<T>(
355
362
  decide: (resources: TwinResource[]) => AtomicActionDecision<T>,
356
363
  root?: string,
357
364
  ): { value: T; action?: TwinAction; appended: boolean; placeholder?: true } {
358
- return withFileLock(projectionLock(service, root), () => withFileLock(actionsLock(service, root), () => {
365
+ return withProjection(service, root, () => withFileLock(actionsLock(service, root), () => {
359
366
  const resources = projectResources(service, root);
360
367
  const decision = decide(resources);
361
368
  if (decision.kind === 'skip') return { value: decision.value, appended: false };
@@ -481,8 +488,8 @@ function ownerIndex(owner: string, root: string): OwnerIndex {
481
488
  }
482
489
 
483
490
  /**
484
- * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, xidentity's tokens
485
- * read by x, googleoauth's by googlecalendar; scripts/architecture.test.ts holds the declared reader-owner pairs): the
491
+ * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, googleoauth's tokens
492
+ * read by webrisk; the reader's manifest declares the pair, `ownerReads`): the
486
493
  * owner's tree, from wherever the World keeps the owner's store (`ownerStoreRoots`). Read only: the rows are frozen and
487
494
  * shared. It claims no journal identity (the twin answering is the reader).
488
495
  *
@@ -551,11 +558,11 @@ export function confirmAction(opts: {
551
558
  ...(opts.additionalObservations ?? []).map((o) => landedCopy(base, receipt, { subject: o.subject, fields: o.fields })),
552
559
  landedCopy(base, receipt, { subject: opts.subject, fields: opts.fields, ...(opts.vendorSubjectId ? { vendorSubjectId: opts.vendorSubjectId } : {}) }),
553
560
  ];
554
- withFileLock(projectionLock(opts.service, opts.root), () => {
555
- withAncestryLock(() => withFileLock(eventsLockPath(paths), () => {
561
+ withProjection(opts.service, opts.root, () => {
562
+ withFileLock(eventsLockPath(paths), () => {
556
563
  const held = landedIds(parentEntries(opts.service, opts.root));
557
564
  for (const copy of copies) if (!held.has(copy.id)) appendDurable(paths.events, `${JSON.stringify(copy)}\n`);
558
- }));
565
+ });
559
566
  dropCheckpoint(opts.service, opts.root);
560
567
  });
561
568
  const observedEventIds = copies.map((c) => c.id);
@@ -578,7 +585,7 @@ export type RevertOutcome =
578
585
  */
579
586
  export function revertAction(opts: { service: string; actionId: string; occurredAt: string; root?: string }): RevertOutcome {
580
587
  refuseReadOnlyWrite(opts.service);
581
- return withFileLock(projectionLock(opts.service, opts.root), () => withFileLock(actionsLock(opts.service, opts.root), (): RevertOutcome => {
588
+ return withProjection(opts.service, opts.root, () => withFileLock(actionsLock(opts.service, opts.root), (): RevertOutcome => {
582
589
  const all = listActions(opts.service, opts.root);
583
590
  const target = all.find((a) => a.id === opts.actionId);
584
591
  if (target === undefined) return { status: 'not-found' };
package/src/ancestry.ts CHANGED
@@ -14,7 +14,10 @@ const metaPath = (dir: string) => join(dir, 'ancestry.json');
14
14
  const inside = (base: string, path: string) => { const rel = relative(canonicalStatePath(base), canonicalStatePath(path)); return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !rel.startsWith(sep)); };
15
15
 
16
16
  /** Synchronous only; use exclusive acquisition without age-stealing a live owner.
17
- * The coordinator lives outside service, instance and hosted World deletion trees. */
17
+ * The coordinator lives outside service, instance and hosted World deletion trees. It is held only for ancestry
18
+ * bookkeeping (generations, pins, branch pointers, removal receipts) and the not-being-removed check: every World
19
+ * sharing the VOLTER_HOME waits on it, so no append, fold or caller's critical section runs inside it, and no state lock
20
+ * is taken inside it (`withStateLock` takes its lock first). */
18
21
  export function withAncestryLock<T>(fn: () => T): T {
19
22
  const store = getActiveWorldStore();
20
23
  const active = entered ??= new WeakSet<WorldStore>();
@@ -27,6 +30,61 @@ export function withAncestryLock<T>(fn: () => T): T {
27
30
  }, { retainLiveOwner: true });
28
31
  }
29
32
 
33
+ /** The state locks this process holds, per store: taken again inside one, a lock is already ours. */
34
+ let heldLocks: WeakMap<WorldStore, Set<string>> | undefined;
35
+ const heldBy = (store: WorldStore): Set<string> => { const map = heldLocks ??= new WeakMap(); let set = map.get(store); if (!set) { set = new Set(); map.set(store, set); } return set; };
36
+
37
+ /** Run `fn` holding the cross-process lock at `lockPath`, a lock of the directory it sits in: taken first, then the
38
+ * coordinator only to check that the directory is not being removed. Re-entrant within a process. */
39
+ export function withStateLock<T>(lockPath: string, fn: () => T): T {
40
+ const store = getActiveWorldStore();
41
+ const held = heldBy(store);
42
+ if (held.has(lockPath)) return fn();
43
+ if (entered?.has(store)) throw new Error(`State lock ${lockPath} taken inside the ancestry coordinator: a state lock is taken before it, never inside it`);
44
+ return store.withLock(lockPath, () => {
45
+ withAncestryLock(() => assertNotBeingRemoved(dirname(lockPath)));
46
+ held.add(lockPath);
47
+ try { return fn(); } finally { held.delete(lockPath); }
48
+ });
49
+ }
50
+
51
+ /** A state directory's history lock: its appends, captures, observation batches and folds. A branch's is taken before
52
+ * its ancestors'. */
53
+ export function withHistoryLock<T>(directory: string, fn: () => T): T {
54
+ return withStateLock(join(canonicalStatePath(directory), 'history.lock'), fn);
55
+ }
56
+
57
+ /** A lock file a holder has recorded itself in (`{pid, hostname}`, written before the holder runs anything): a file
58
+ * that only ends in `.lock` (a yarn.lock) is not one. */
59
+ function isHeldLock(text: string | null): boolean {
60
+ try { const holder = JSON.parse(text ?? '') as { pid?: unknown }; return Number.isInteger(holder.pid); } catch { return false; }
61
+ }
62
+
63
+ /** Several sibling directories' history locks at once (a World's services, a changeset's targets), in one order. */
64
+ export function withHistoryLocks<T>(directories: string[], fn: () => T): T {
65
+ const sorted = [...new Set(directories.map((d) => canonicalStatePath(d)))].sort();
66
+ const take = (i: number): T => (i === sorted.length ? fn() : withHistoryLock(sorted[i]!, () => take(i + 1)));
67
+ return take(0);
68
+ }
69
+
70
+ /** The locks inside a tree a removal waits on before deleting it. */
71
+ function locksUnder(path: string): string[] {
72
+ const store = getActiveWorldStore();
73
+ const out: string[] = [];
74
+ const walk = (dir: string): void => {
75
+ for (const name of store.list(dir)) {
76
+ if (name === 'node_modules' || name === '.git') continue;
77
+ const child = join(dir, name);
78
+ const stat = store.stat(child);
79
+ if (!stat || stat.isSymbolicLink) continue;
80
+ if (stat.isDirectory) walk(child);
81
+ else if (name.endsWith('.lock') && isHeldLock(store.read(child))) out.push(child);
82
+ }
83
+ };
84
+ if (store.stat(path)?.isDirectory) walk(path);
85
+ return out.sort();
86
+ }
87
+
30
88
  function metadata(dir: string): Ancestry | null {
31
89
  const raw = getActiveWorldStore().read(metaPath(dir));
32
90
  if (raw === null) return null;
@@ -186,7 +244,8 @@ export function assertNotBeingRemoved(path: string): void {
186
244
  /** force on scrub bypasses shape checking only, never another branch's ownership. The coordinator is held to decide
187
245
  * and record the removal (`check`, the caller's own precondition, runs there too) and to release it, not through the
188
246
  * deletion: every World on the machine shares the coordinator, and a large tree's deletion outlasts their writes'
189
- * patience. The receipt, written first, keeps the path from being pinned or written while it is deleted. */
247
+ * patience. The receipt, written first, keeps the path from being pinned or locked while it is deleted; the deletion
248
+ * then waits on each lock inside the tree in turn, so a holder that checked before the receipt finishes first. */
190
249
  export function withStateRemoval<T>(path: string, remove: () => T, check?: () => void): T {
191
250
  const store = getActiveWorldStore();
192
251
  const { removal, receipt } = withAncestryLock(() => {
@@ -202,6 +261,19 @@ export function withStateRemoval<T>(path: string, remove: () => T, check?: () =>
202
261
  beingRemoved = [...beingRemoved, removal.path];
203
262
  return { removal, receipt };
204
263
  });
264
+ // Each lock is waited on alone, taken and let go: a holder that checked before the receipt finishes, and a taker
265
+ // after it refuses once it holds the lock (withStateLock), so the removal never holds one lock while waiting on
266
+ // another and no lock order can deadlock it. A wait that fails leaves nothing deleted: the receipt is released.
267
+ const held = heldBy(store);
268
+ try {
269
+ for (const lock of locksUnder(path).filter((l) => !held.has(l))) store.withLock(lock, () => undefined);
270
+ } catch (error) {
271
+ withAncestryLock(() => {
272
+ if (store.exists(receipt)) store.remove(receipt);
273
+ beingRemoved = beingRemoved.filter((p) => p !== removal.path);
274
+ });
275
+ throw error;
276
+ }
205
277
  const result = remove();
206
278
  withAncestryLock(() => {
207
279
  if (store.exists(path)) throw new Error(`Branch state removal incomplete: ${path}; receipt retained`);
@@ -0,0 +1,137 @@
1
+ // Anthropic's Messages wire (architecture, "The kernel owns shared mechanics"): the Messages shapes a vendor serves as
2
+ // Anthropic defines them (https://platform.claude.com/docs/en/api/messages and /build-with-claude/streaming), for
3
+ // generative packs that run no model, beside the OpenAI wire (./openai-wire.ts) whose turn vocabulary it answers. A
4
+ // pack decides its turn (the World's scenario's, else the stub) and its own facts (its ids, its usage fields); this
5
+ // module reads a request as the scenario reads it and shapes the answer and its Server-Sent Events. Importing it does
6
+ // nothing.
7
+ import { estimateTokens, forbidsTools, type WireRequest, type WireTurn } from './openai-wire.ts';
8
+ import { sampleFromSchema } from './schema-sample.ts';
9
+
10
+ type Row = Record<string, unknown>;
11
+ const obj = (v: unknown): Row => (v && typeof v === 'object' && !Array.isArray(v) ? v as Row : {});
12
+ const arr = (v: unknown): Row[] => (Array.isArray(v) ? v as Row[] : []);
13
+
14
+ /** A message's content as text: a string, or its text blocks (and the text of its tool results) joined. */
15
+ export const messagesText = (content: unknown): string =>
16
+ typeof content === 'string' ? content : arr(content).map((c) => (c.type === 'text' ? String(c.text ?? '') : c.type === 'tool_result' ? messagesText(c.content) : '')).filter(Boolean).join('\n');
17
+
18
+ /** The last user message's text. */
19
+ export function messagesLastUserText(messages: Row[]): string {
20
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i]!.role === 'user') { const t = messagesText(messages[i]!.content); if (t) return t; }
21
+ return '';
22
+ }
23
+
24
+ /** The tool results the last message carries, by the names of the tool_use blocks they answer. */
25
+ export function messagesToolResults(messages: Row[]): string[] {
26
+ const ids = arr(messages.at(-1)?.content).filter((c) => c.type === 'tool_result').map((c) => c.tool_use_id);
27
+ if (!ids.length) return [];
28
+ return messages.flatMap((m) => arr(m.content)).filter((c) => c.type === 'tool_use' && ids.includes(c.id)).map((c) => String(c.name));
29
+ }
30
+
31
+ /** A Messages request as a scenario reads it (the OpenAI wire's `WireRequest`): its model, its system and messages'
32
+ * text, its last user text, the tools it offers and the tool results it ends with. */
33
+ export function messagesWireRequest(body: Row): WireRequest {
34
+ const messages = arr(body.messages);
35
+ const system = typeof body.system === 'string' ? body.system : messagesText(body.system);
36
+ return {
37
+ model: String(body.model ?? ''), text: [system, ...messages.map((m) => messagesText(m.content))].join('\n'), lastUser: messagesLastUserText(messages),
38
+ tools: arr(body.tools).map((t) => String(t.name ?? '')).filter(Boolean), results: messagesToolResults(messages),
39
+ ...(forbidsTools(body.tool_choice) ? { toolsForbidden: true } : {}),
40
+ };
41
+ }
42
+
43
+ /** The labeled stub for a Messages request: with tools offered (`tool_choice` not `none`) and the last message not a
44
+ * tool's result, it calls the tool the choice names, else the first, with input its schema admits; with a JSON schema
45
+ * for the output, it answers a value the schema admits; otherwise it echoes the last user message. `label` names the
46
+ * twin. */
47
+ export function messagesStubTurn(body: Row, label: string): WireTurn {
48
+ const model = String(body.model);
49
+ const messages = arr(body.messages);
50
+ const tools = arr(body.tools).filter((t) => t.input_schema !== undefined);
51
+ const choice = obj(body.tool_choice ?? { type: 'auto' });
52
+ if (tools.length && choice.type !== 'none' && (messagesToolResults(messages).length === 0 || choice.type === 'any' || choice.type === 'tool')) {
53
+ const tool = (choice.type === 'tool' ? tools.find((t) => t.name === choice.name) : undefined) ?? tools[0]!;
54
+ return { toolCalls: [{ name: String(tool.name), arguments: sampleFromSchema(tool.input_schema, String(tool.name)) as Row }] };
55
+ }
56
+ const format = obj(obj(body.output_config).format ?? body.output_format);
57
+ if (format.type === 'json_schema') return { text: JSON.stringify(sampleFromSchema(format.schema)) };
58
+ const said = messagesLastUserText(messages).trim();
59
+ return { text: `[twin-stub:${model}] This is a deterministic stub from the ${label} (no model runs). Your last message: ${said.length > 200 ? `${said.slice(0, 200)}…` : said || '(empty)'}` };
60
+ }
61
+
62
+ /** The OpenAI wire's finish reasons as the Messages stop reasons they mean. */
63
+ const STOP: Record<string, string> = { stop: 'end_turn', length: 'max_tokens', tool_calls: 'tool_use', content_filter: 'refusal' };
64
+
65
+ export type MessageShape = {
66
+ id: string; model: string; turn: WireTurn; body: Row;
67
+ /** each tool_use block's id, and the thinking block's signature */
68
+ toolId: (i: number) => string;
69
+ signature?: (thinking: string) => string;
70
+ /** usage fields the vendor adds (service_tier, the prompt cache's counts) */
71
+ usage?: Row;
72
+ };
73
+
74
+ /** A message (`type: message`): a thinking block when the request turns thinking on, then the
75
+ * text, then the tool_use blocks; its stop reason, and its usage estimated from the request and the answer. A text
76
+ * longer than `max_tokens` is cut there and stops `max_tokens`. */
77
+ export function message(s: MessageShape): Row {
78
+ const b = s.body;
79
+ // thinking blocks come only when the request turns thinking on; a turn's reasoning is then their text
80
+ const thinkingOn = ['enabled', 'adaptive'].includes(String(obj(b.thinking).type));
81
+ const max = typeof b.max_tokens === 'number' ? b.max_tokens : undefined;
82
+ let text = s.turn.text ?? undefined;
83
+ let cut = false;
84
+ if (text !== undefined && max !== undefined && estimateTokens(text) > max) { text = text.slice(0, Math.max(1, max) * 4); cut = true; }
85
+ const content: Row[] = [];
86
+ if (thinkingOn) {
87
+ const thinking = s.turn.reasoning ?? '[twin-stub] No model runs here, so there is no reasoning to show.';
88
+ content.push({ type: 'thinking', thinking, signature: s.signature ? s.signature(thinking) : '' });
89
+ }
90
+ if (text !== undefined) content.push({ type: 'text', text });
91
+ (s.turn.toolCalls ?? []).forEach((c, i) => content.push({ type: 'tool_use', id: s.toolId(i), name: c.name, input: c.arguments ?? {} }));
92
+ const stop = cut ? 'max_tokens' : s.turn.finishReason ? (STOP[s.turn.finishReason] ?? s.turn.finishReason) : s.turn.toolCalls?.length ? 'tool_use' : 'end_turn';
93
+ const request = messagesWireRequest(b);
94
+ const output = estimateTokens(content.map((c) => (c.type === 'tool_use' ? JSON.stringify(c.input) : String(c.text ?? c.thinking ?? ''))).join(''));
95
+ return {
96
+ id: s.id, type: 'message', role: 'assistant', model: s.model, content, stop_reason: stop, stop_sequence: null,
97
+ usage: { input_tokens: estimateTokens(request.text), cache_creation_input_tokens: 0, cache_read_input_tokens: 0, output_tokens: output, ...(s.usage ?? {}) },
98
+ };
99
+ }
100
+
101
+ /** Text in the pieces a stream sends it in: a few words at a time. */
102
+ function pieces(text: string): string[] {
103
+ const words = text.match(/\S+\s*|\s+/g) ?? [text];
104
+ const out: string[] = [];
105
+ for (let i = 0; i < words.length; i += 4) out.push(words.slice(i, i + 4).join(''));
106
+ return out.length ? out : [''];
107
+ }
108
+
109
+ /** A message as the stream's events: message_start (its content empty), per block its start, deltas and stop (a ping
110
+ * after the first start), message_delta with the stop reason and the output usage, message_stop. `extra` adds fields
111
+ * a vendor puts on the two closing events (Merge's `routing`). */
112
+ export function messageSSE(m: Row, extra: Row = {}, options: { ping?: boolean; completeToolInput?: boolean } = {}): string {
113
+ const events: string[] = [];
114
+ const send = (type: string, data: Row): void => { events.push(`event: ${type}\ndata: ${JSON.stringify({ type, ...data })}\n\n`); };
115
+ const usage = obj(m.usage);
116
+ send('message_start', { message: { ...m, content: [], stop_reason: null, stop_sequence: null, usage: { ...usage, output_tokens: 1 } } });
117
+ arr(m.content).forEach((block, index) => {
118
+ if (block.type === 'text') {
119
+ send('content_block_start', { index, content_block: { type: 'text', text: '' } });
120
+ if (index === 0 && options.ping !== false) send('ping', {});
121
+ for (const piece of pieces(String(block.text))) send('content_block_delta', { index, delta: { type: 'text_delta', text: piece } });
122
+ } else if (block.type === 'thinking') {
123
+ send('content_block_start', { index, content_block: { type: 'thinking', thinking: '', signature: '' } });
124
+ if (index === 0 && options.ping !== false) send('ping', {});
125
+ for (const piece of pieces(String(block.thinking))) send('content_block_delta', { index, delta: { type: 'thinking_delta', thinking: piece } });
126
+ send('content_block_delta', { index, delta: { type: 'signature_delta', signature: block.signature } });
127
+ } else {
128
+ send('content_block_start', { index, content_block: { ...block, input: {} } });
129
+ if (index === 0 && options.ping !== false) send('ping', {});
130
+ for (const piece of options.completeToolInput ? [JSON.stringify(block.input)] : pieces(JSON.stringify(block.input))) send('content_block_delta', { index, delta: { type: 'input_json_delta', partial_json: piece } });
131
+ }
132
+ send('content_block_stop', { index });
133
+ });
134
+ send('message_delta', { delta: { stop_reason: m.stop_reason, stop_sequence: null }, usage: { output_tokens: usage.output_tokens }, ...extra });
135
+ send('message_stop', { ...extra });
136
+ return events.join('');
137
+ }