enigma-memory 0.1.18 → 0.1.22

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 (276) hide show
  1. package/README.md +76 -24
  2. package/apps/cli/bin/enigma-desktop.mjs +140 -0
  3. package/apps/cli/bin/enigma-terminal.mjs +78 -0
  4. package/apps/cli/bin/enigma.mjs +1923 -285
  5. package/apps/desktop/electron-main.cjs +217 -0
  6. package/apps/desktop/package.json +12 -0
  7. package/apps/desktop/src/app.js +264 -7
  8. package/apps/desktop/src/index.html +3514 -1373
  9. package/apps/desktop/src/launch-electron.mjs +51 -0
  10. package/apps/desktop/src/server.mjs +2914 -0
  11. package/apps/desktop/src/styles.css +2972 -260
  12. package/apps/desktop/src/zk-browser-prove.mjs +53 -0
  13. package/apps/desktop/src/zk-state.mjs +1789 -0
  14. package/apps/gateway/bin/enigma-gateway.mjs +102 -5
  15. package/apps/gateway/src/server.mjs +271 -8
  16. package/apps/ios/EnigmaCore/Package.swift +12 -0
  17. package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
  18. package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
  19. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
  20. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
  21. package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
  22. package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
  23. package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
  24. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
  25. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
  26. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
  27. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
  28. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
  29. package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
  30. package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
  31. package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
  32. package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
  33. package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
  34. package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
  35. package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
  36. package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
  37. package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
  38. package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
  39. package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
  40. package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
  41. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
  42. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
  43. package/apps/native-host/README.md +19 -8
  44. package/apps/native-host/bin/enigma-native-host.mjs +229 -13
  45. package/apps/relay/bin/enigma-relay.mjs +103 -5
  46. package/apps/relay/src/federation-runtime.mjs +618 -0
  47. package/apps/relay/src/server.mjs +310 -9
  48. package/apps/verifier/bin/enigma-verify.mjs +327 -11
  49. package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
  50. package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
  51. package/cortex-v3/circuits/build/vk_bytes.json +32 -0
  52. package/cortex-v3/proving-assets.json +64 -0
  53. package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
  54. package/cortex-v3/zk/action-transition-vk.json +119 -0
  55. package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
  56. package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
  57. package/cortex-v3/zk/intent-witness.mjs +365 -0
  58. package/cortex-v3/zk/intent-witness.test.mjs +485 -0
  59. package/cortex-v3/zk/proving-assets.mjs +203 -0
  60. package/cortex-v3/zk/sale-witness.mjs +783 -0
  61. package/cortex-v3/zk/sale-witness.test.mjs +784 -0
  62. package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
  63. package/cortex-v3/zk/settlement-evidence.mjs +722 -0
  64. package/cortex-v3/zk/setup-intent.mjs +688 -0
  65. package/cortex-v3/zk/setup-sale.mjs +666 -0
  66. package/cortex-v3/zk/setup.mjs +594 -0
  67. package/cortex-v3/zk/witness.mjs +184 -0
  68. package/cortex-v3/zk/zk-codec.mjs +232 -0
  69. package/cortex-v3/zk/zk-codec.test.mjs +293 -0
  70. package/cortex-v3/zk/zk-settle.mjs +370 -0
  71. package/cortex-v3/zk/zk-tree.mjs +256 -0
  72. package/cortex-v3/zk/zk-tree.test.mjs +419 -0
  73. package/deploy/docker-compose.local-production-simulation.yml +36 -0
  74. package/docs/browser-extension-install.md +8 -6
  75. package/docs/client-connectors.md +15 -11
  76. package/docs/developer-ecosystem.md +15 -13
  77. package/docs/enigma-memory-ready-conformance.md +11 -9
  78. package/docs/install-anywhere.md +61 -28
  79. package/docs/installers-and-desktop.md +8 -7
  80. package/docs/novelty-invention-candidates.md +161 -161
  81. package/docs/proof-network-claim-boundaries.md +320 -318
  82. package/examples/01-quickstart-agent/index.mjs +49 -0
  83. package/examples/01_agent_memory_quickstart.mjs +57 -0
  84. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  85. package/examples/02_cross_model_passport.mjs +64 -0
  86. package/examples/03-langchain-memory/index.mjs +41 -0
  87. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  88. package/examples/04-python-trading-agent/trader.py +49 -0
  89. package/examples/README.md +27 -0
  90. package/examples/ci/github-actions.yml +7 -2
  91. package/package.json +142 -11
  92. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  93. package/packages/connectors/src/index.js +196 -4
  94. package/packages/connectors/swarm-router.mjs +168 -0
  95. package/packages/core/src/index.js +248 -1
  96. package/packages/core/src/version.mjs +7 -0
  97. package/packages/dev-tools/package.json +19 -0
  98. package/packages/dev-tools/src/index.js +4 -0
  99. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  100. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  101. package/packages/dev-tools/src/vault-inspector.js +114 -0
  102. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  103. package/packages/developer-platform/src/access-credentials.js +341 -0
  104. package/packages/developer-platform/src/http.js +132 -0
  105. package/packages/developer-platform/src/index.js +4 -0
  106. package/packages/developer-platform/src/usage-http.js +60 -0
  107. package/packages/developer-platform/src/usage.js +295 -0
  108. package/packages/enclave-runtime/attestation.mjs +159 -0
  109. package/packages/enclave-runtime/index.mjs +47 -0
  110. package/packages/enclave-runtime/session-manager.mjs +253 -0
  111. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  112. package/packages/enigma-reflex/package.json +14 -0
  113. package/packages/enigma-reflex/src/index.js +204 -0
  114. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  115. package/packages/enigma-reflex/training/requirements.txt +8 -0
  116. package/packages/enigma-reflex/training/train.py +314 -0
  117. package/packages/enigma-weave/LICENSE +22 -0
  118. package/packages/enigma-weave/UPSTREAM.json +21 -0
  119. package/packages/enigma-weave/package.json +14 -0
  120. package/packages/enigma-weave/src/index.js +286 -0
  121. package/packages/hosted-cloud/src/index.js +80 -5
  122. package/packages/importers/src/index.js +432 -0
  123. package/packages/inference-runtime/src/browser.js +401 -0
  124. package/packages/inference-runtime/src/chat.js +265 -0
  125. package/packages/inference-runtime/src/code.js +407 -0
  126. package/packages/inference-runtime/src/contracts.js +162 -0
  127. package/packages/inference-runtime/src/http.js +232 -0
  128. package/packages/inference-runtime/src/image.js +186 -0
  129. package/packages/inference-runtime/src/index.js +10 -0
  130. package/packages/inference-runtime/src/model-router.js +320 -0
  131. package/packages/inference-runtime/src/platform.js +125 -0
  132. package/packages/inference-runtime/src/privacy.js +400 -0
  133. package/packages/inference-runtime/src/video.js +253 -0
  134. package/packages/mcp-server/README.md +22 -6
  135. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  136. package/packages/mcp-server/src/index.js +1418 -105
  137. package/packages/mcp-server/src/oauth.js +561 -0
  138. package/packages/mcp-server/src/private-handoff.js +84 -0
  139. package/packages/mcp-server/src/remote-http.js +273 -0
  140. package/packages/mcp-server/src/remote-policy.js +72 -0
  141. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  142. package/packages/mesh/index.d.ts +283 -0
  143. package/packages/mesh/package.json +23 -0
  144. package/packages/mesh/src/crypto.js +189 -0
  145. package/packages/mesh/src/federation-packets.js +353 -0
  146. package/packages/mesh/src/gossip.js +311 -0
  147. package/packages/mesh/src/index.js +6 -0
  148. package/packages/mesh/src/protocol.js +255 -0
  149. package/packages/mesh/src/router.js +279 -0
  150. package/packages/mesh/src/transport.js +306 -0
  151. package/packages/passport/src/index.js +426 -1
  152. package/packages/private-economy/src/credits-http.js +100 -0
  153. package/packages/private-economy/src/credits.js +447 -0
  154. package/packages/private-economy/src/index.js +5 -0
  155. package/packages/private-economy/src/payments-http.js +120 -0
  156. package/packages/private-economy/src/payments.js +509 -0
  157. package/packages/private-economy/src/x402.js +346 -0
  158. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  159. package/packages/rag/index.d.ts +182 -0
  160. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  161. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  162. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  163. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  164. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  165. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  166. package/packages/rag/package.json +27 -0
  167. package/packages/rag/src/blinded-search.js +109 -0
  168. package/packages/rag/src/bm25.js +169 -0
  169. package/packages/rag/src/embeddings.js +459 -0
  170. package/packages/rag/src/hybrid.js +76 -0
  171. package/packages/rag/src/index.js +38 -0
  172. package/packages/rag/src/reranker.js +61 -0
  173. package/packages/rag/src/research.js +107 -0
  174. package/packages/rag/src/vector-store.js +430 -0
  175. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  176. package/packages/sdk/index.d.ts +760 -0
  177. package/packages/sdk/package.json +33 -0
  178. package/packages/sdk/python/README.md +24 -0
  179. package/packages/sdk/python/enigma_sdk.py +250 -0
  180. package/packages/sdk/python/pyproject.toml +34 -0
  181. package/packages/sdk/python/requirements.txt +1 -0
  182. package/packages/sdk/python/setup.py +20 -0
  183. package/packages/sdk/src/federation/capability-grant.js +389 -0
  184. package/packages/sdk/src/federation/federation-router.js +360 -0
  185. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  186. package/packages/sdk/src/federation/index.js +3 -0
  187. package/packages/sdk/src/index.js +1796 -0
  188. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  189. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  190. package/packages/sdk/src/intelligence/index.js +4 -0
  191. package/packages/sdk/src/intelligence/ontology.js +122 -0
  192. package/packages/sdk/src/intelligence/temporal.js +123 -0
  193. package/packages/sdk/src/market-client.js +142 -0
  194. package/packages/sdk/src/mesh-client.js +110 -0
  195. package/packages/sdk/src/middleware/index.js +3 -0
  196. package/packages/sdk/src/middleware/langchain.js +159 -0
  197. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  198. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  199. package/packages/sdk/src/rag-client.js +85 -0
  200. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  201. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  202. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  203. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  204. package/packages/snapcompact/index.d.ts +284 -0
  205. package/packages/snapcompact/package.json +25 -0
  206. package/packages/snapcompact/src/index.js +716 -0
  207. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  208. package/packages/terminal-console/animations.mjs +240 -0
  209. package/packages/terminal-console/auto-anchor.mjs +220 -0
  210. package/packages/terminal-console/banner.mjs +91 -0
  211. package/packages/terminal-console/commands.mjs +459 -0
  212. package/packages/terminal-console/delegation.mjs +152 -0
  213. package/packages/terminal-console/index.mjs +5 -0
  214. package/packages/terminal-console/outbox.mjs +143 -0
  215. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  216. package/packages/terminal-console/repl.mjs +136 -0
  217. package/packages/terminal-console/signer-store.mjs +130 -0
  218. package/packages/terminal-console/solana-rpc.mjs +214 -0
  219. package/packages/terminal-console/solana-transport.mjs +189 -0
  220. package/packages/terminal-tui/dashboard.mjs +214 -0
  221. package/packages/terminal-tui/index.mjs +28 -0
  222. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  223. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  224. package/packages/vault/index.d.ts +449 -0
  225. package/packages/vault/package.json +27 -0
  226. package/packages/vault/src/e2ee.mjs +393 -0
  227. package/packages/vault/src/enclave.js +481 -0
  228. package/packages/vault/src/erasure.js +207 -0
  229. package/packages/vault/src/index.js +1018 -155
  230. package/packages/vault/src/persistence.js +307 -0
  231. package/packages/vault/src/poseidon.js +354 -0
  232. package/packages/vault/src/receipt.js +459 -0
  233. package/scripts/benchmark-optical-context.mjs +166 -0
  234. package/scripts/bootstrap-enigma.mjs +502 -0
  235. package/scripts/build-edge-backend-workers.mjs +20 -5
  236. package/scripts/build-goal-completion-audit.mjs +72 -25
  237. package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
  238. package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
  239. package/scripts/build-hosted-probe-worker.mjs +19 -4
  240. package/scripts/build-installer-assets.mjs +41 -21
  241. package/scripts/build-operator-evidence-starter.mjs +59 -1
  242. package/scripts/build-production-backend-env-kit.mjs +2 -0
  243. package/scripts/build-production-unblocker.mjs +3 -0
  244. package/scripts/check.mjs +17 -3
  245. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  246. package/scripts/install-enigma-local.mjs +18 -5
  247. package/scripts/release-audit.mjs +65 -109
  248. package/scripts/release-provenance.mjs +6 -0
  249. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  250. package/scripts/scan-secrets.mjs +1 -0
  251. package/scripts/simulate-production-env.mjs +7 -2
  252. package/scripts/validate-hosted-backend-live.mjs +112 -1
  253. package/specs/antibody-pack-v1.schema.json +95 -0
  254. package/specs/antigen-envelope-v1.schema.json +81 -0
  255. package/specs/boundary-manifest-v1.schema.json +35 -35
  256. package/specs/capsule-v1.schema.json +55 -55
  257. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  258. package/specs/claim-ledger-v1.schema.json +291 -0
  259. package/specs/context-passport-v1.schema.json +59 -0
  260. package/specs/deletion-tombstone-v1.schema.json +26 -26
  261. package/specs/evidence-packet-v1.schema.json +177 -0
  262. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  263. package/specs/immune-scan-report-v1.schema.json +112 -0
  264. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  265. package/specs/memory-atom-v1.schema.json +59 -0
  266. package/specs/memory-event-v1.schema.json +42 -42
  267. package/specs/passport-v1.schema.json +50 -50
  268. package/specs/proof-of-non-use-v1.schema.json +65 -0
  269. package/specs/quarantine-record-v1.schema.json +126 -0
  270. package/specs/receipt-v1.schema.json +61 -61
  271. package/specs/state-checkpoint-v1.schema.json +37 -37
  272. package/specs/trust-bundle-v1.schema.json +56 -56
  273. package/specs/trust-card-v1.schema.json +119 -0
  274. package/docs/proof-network-launch-plan.md +0 -421
  275. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  276. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,716 @@
1
+ // Dense bitmap context transport inspired by the strategy in
2
+ // @oh-my-pi/snapcompact (MIT): https://github.com/can1357/oh-my-pi/tree/main/packages/snapcompact
3
+ // This is an independent Node implementation: it imports no upstream code or native package.
4
+ import { createHash } from 'node:crypto';
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { deflateSync, constants as zlibConstants } from 'node:zlib';
8
+
9
+ export const SNAPCOMPACT_VERSION = '1.0.0';
10
+
11
+ export const DEFAULT_LIMITS = deepFreeze({
12
+ maxCharacters: 2_000_000,
13
+ maxFrames: 64,
14
+ maxPixelsPerFrame: 4_194_304,
15
+ maxTotalPixels: 67_108_864,
16
+ maxPngBytes: 64 * 1024 * 1024,
17
+ });
18
+
19
+ const PROFILE_DEFINITIONS = {
20
+ anthropic: {
21
+ id: 'anthropic-8x13-11x16-1568',
22
+ provider: 'anthropic',
23
+ width: 1568,
24
+ maxHeight: 1568,
25
+ glyphWidth: 8,
26
+ glyphHeight: 13,
27
+ cellWidth: 11,
28
+ cellHeight: 16,
29
+ colorType: 0,
30
+ imageDetail: null,
31
+ modelFamilies: [
32
+ '^claude-3-(?:5|7)-sonnet(?:-[0-9]{8})?$',
33
+ '^claude-(?:sonnet|opus|haiku)-4(?:-[0-9]+)*(?:-[0-9]{8})?$',
34
+ ],
35
+ },
36
+ google: {
37
+ id: 'google-8x13-8x22-2048',
38
+ provider: 'google',
39
+ width: 2048,
40
+ maxHeight: 2048,
41
+ glyphWidth: 8,
42
+ glyphHeight: 13,
43
+ cellWidth: 8,
44
+ cellHeight: 22,
45
+ colorType: 0,
46
+ imageDetail: null,
47
+ modelFamilies: [
48
+ '^gemini-1\\.5-(?:pro|flash)(?:-[a-z0-9-]+)?$',
49
+ '^gemini-2\\.(?:0|5)-(?:pro|flash)(?:-[a-z0-9-]+)?$',
50
+ '^gemini-3(?:\\.[0-9]+)?-(?:pro|flash)(?:-[a-z0-9-]+)?$',
51
+ ],
52
+ },
53
+ openai: {
54
+ id: 'openai-8x13-8x22-1568-original',
55
+ provider: 'openai',
56
+ width: 1568,
57
+ maxHeight: 1568,
58
+ glyphWidth: 8,
59
+ glyphHeight: 13,
60
+ cellWidth: 8,
61
+ cellHeight: 22,
62
+ colorType: 0,
63
+ imageDetail: 'original',
64
+ modelFamilies: [
65
+ '^gpt-4o(?:-mini)?(?:-[0-9]{4}-[0-9]{2}-[0-9]{2})?$',
66
+ '^gpt-4\\.1(?:-mini|-nano)?(?:-[0-9]{4}-[0-9]{2}-[0-9]{2})?$',
67
+ '^gpt-5(?:\\.[0-9]+)?(?:-mini|-nano|-pro)?(?:-[0-9]{4}-[0-9]{2}-[0-9]{2})?$',
68
+ ],
69
+ },
70
+ };
71
+
72
+ /**
73
+ * Provider billing changes over time and varies by model. These estimates are
74
+ * deliberately labelled planning heuristics, not billing, latency, quality, or
75
+ * recall claims. Callers supply prices and can override both token estimates.
76
+ */
77
+ export const TOKEN_ESTIMATE_ASSUMPTIONS = deepFreeze({
78
+ textCharactersPerToken: 4,
79
+ imagePixelsPerToken: 750,
80
+ basis: 'planning-only heuristic; verify current provider documentation and pricing before use',
81
+ });
82
+
83
+ export const PROVIDER_PROFILES = deepFreeze(Object.fromEntries(
84
+ Object.entries(PROFILE_DEFINITIONS).map(([provider, definition]) => [
85
+ provider,
86
+ {
87
+ ...definition,
88
+ columns: Math.floor(definition.width / definition.cellWidth),
89
+ rowsPerFrame: Math.floor(definition.maxHeight / definition.cellHeight),
90
+ tokenEstimate: TOKEN_ESTIMATE_ASSUMPTIONS,
91
+ },
92
+ ]),
93
+ ));
94
+
95
+ const BDF_PATH = fileURLToPath(new URL('../assets/8x13-latin1.bdf', import.meta.url));
96
+ let bundledFont;
97
+
98
+ function deepFreeze(value) {
99
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
100
+ for (const item of Object.values(value)) deepFreeze(item);
101
+ return Object.freeze(value);
102
+ }
103
+
104
+ function sha256(value) {
105
+ return createHash('sha256').update(value).digest('hex');
106
+ }
107
+
108
+ function positiveInteger(value, fallback, name) {
109
+ const result = value === undefined ? fallback : value;
110
+ if (!Number.isSafeInteger(result) || result <= 0) throw new RangeError(`${name} must be a positive safe integer`);
111
+ return result;
112
+ }
113
+
114
+ function finiteNonNegative(value, fallback, name) {
115
+ const result = value === undefined ? fallback : value;
116
+ if (!Number.isFinite(result) || result < 0) throw new RangeError(`${name} must be a finite non-negative number`);
117
+ return result;
118
+ }
119
+
120
+ function mergeLimits(limits = {}) {
121
+ return {
122
+ maxCharacters: positiveInteger(limits.maxCharacters, DEFAULT_LIMITS.maxCharacters, 'maxCharacters'),
123
+ maxFrames: positiveInteger(limits.maxFrames, DEFAULT_LIMITS.maxFrames, 'maxFrames'),
124
+ maxPixelsPerFrame: positiveInteger(limits.maxPixelsPerFrame, DEFAULT_LIMITS.maxPixelsPerFrame, 'maxPixelsPerFrame'),
125
+ maxTotalPixels: positiveInteger(limits.maxTotalPixels, DEFAULT_LIMITS.maxTotalPixels, 'maxTotalPixels'),
126
+ maxPngBytes: positiveInteger(limits.maxPngBytes, DEFAULT_LIMITS.maxPngBytes, 'maxPngBytes'),
127
+ };
128
+ }
129
+
130
+ function codePointLabel(codePoint) {
131
+ return `[U+${codePoint.toString(16).toUpperCase().padStart(4, '0')}]`;
132
+ }
133
+
134
+ function elideDataUrls(input) {
135
+ return input.replace(
136
+ /data:([a-z0-9.+-]+\/[a-z0-9.+-]+)?((?:;[a-z0-9.+-]+=[^;,\s]*)*)(;base64)?,([^\s"'<>)]*)/giu,
137
+ (whole, mediaType = 'application/octet-stream', parameters = '', base64 = '', payload = '') => {
138
+ const payloadBytes = Buffer.byteLength(payload, 'utf8');
139
+ return `data:${mediaType}${parameters}${base64},[payload elided bytes=${payloadBytes} sha256=${sha256(payload)}]`;
140
+ },
141
+ );
142
+ }
143
+
144
+ /** Deterministically removes terminal styling and makes every unsupported code point visible. */
145
+ export function normalizeText(input, options = {}) {
146
+ if (typeof input !== 'string') throw new TypeError('input must be a string');
147
+ const maxCharacters = positiveInteger(options.maxCharacters, DEFAULT_LIMITS.maxCharacters, 'maxCharacters');
148
+ if (input.length > maxCharacters) throw new RangeError(`input exceeds maxCharacters (${maxCharacters})`);
149
+ const tabWidth = positiveInteger(options.tabWidth, 4, 'tabWidth');
150
+
151
+ let text = elideDataUrls(input);
152
+ text = text
153
+ .replace(/\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)/gu, '')
154
+ .replace(/\u001B\[[0-?]*[ -/]*[@-~]/gu, '')
155
+ .replace(/\u001B[@-_]/gu, '')
156
+ .replace(/\r\n?/gu, '\n')
157
+ .replace(/[\u2028\u2029]/gu, '\n')
158
+ .normalize('NFC');
159
+
160
+ const output = [];
161
+ let outputLength = 0;
162
+ let column = 0;
163
+ const append = (part) => {
164
+ outputLength += part.length;
165
+ if (outputLength > maxCharacters) throw new RangeError(`normalized text exceeds maxCharacters (${maxCharacters})`);
166
+ output.push(part);
167
+ };
168
+
169
+ for (const character of text) {
170
+ const codePoint = character.codePointAt(0);
171
+ if (character === '\n') {
172
+ append('\n');
173
+ column = 0;
174
+ } else if (character === '\t') {
175
+ const count = tabWidth - (column % tabWidth);
176
+ append(' '.repeat(count));
177
+ column += count;
178
+ } else if (codePoint >= 32 && codePoint <= 126 || codePoint >= 160 && codePoint <= 255) {
179
+ append(character);
180
+ column += 1;
181
+ } else {
182
+ const fallback = codePointLabel(codePoint);
183
+ append(fallback);
184
+ column += fallback.length;
185
+ }
186
+ }
187
+ return output.join('');
188
+ }
189
+
190
+ function summarizeBinary(value, mediaType = 'application/octet-stream') {
191
+ const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value);
192
+ return `[binary mediaType=${mediaType} bytes=${buffer.byteLength} sha256=${sha256(buffer)}]`;
193
+ }
194
+
195
+ function stableJson(value, seen = new WeakSet(), depth = 0) {
196
+ if (depth > 32) return '"[depth-limit]"';
197
+ if (value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
198
+ return JSON.stringify(value);
199
+ }
200
+ if (typeof value === 'bigint') return JSON.stringify(`${value}n`);
201
+ if (value === undefined) return 'null';
202
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return JSON.stringify(summarizeBinary(value));
203
+ if (value instanceof ArrayBuffer) return JSON.stringify(summarizeBinary(new Uint8Array(value)));
204
+ if (typeof value !== 'object') return JSON.stringify(String(value));
205
+ if (seen.has(value)) return '"[circular]"';
206
+ seen.add(value);
207
+ let result;
208
+ if (Array.isArray(value)) {
209
+ result = `[${value.map((item) => stableJson(item, seen, depth + 1)).join(',')}]`;
210
+ } else {
211
+ result = `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key], seen, depth + 1)}`).join(',')}}`;
212
+ }
213
+ seen.delete(value);
214
+ return result;
215
+ }
216
+
217
+ function capHeadTail(text, maxCharacters, label) {
218
+ if (text.length <= maxCharacters) return text;
219
+ // Binary summaries are already bounded, content-minimized atoms. Splitting
220
+ // one would destroy its media type, size, or digest and make the archive
221
+ // less useful than keeping the complete fixed-size placeholder.
222
+ if (/^"?\[binary mediaType=[^\]\r\n]+ bytes=\d+ sha256=[a-f0-9]{64}\]"?$/u.test(text)) {
223
+ return text;
224
+ }
225
+ const markerBudget = Math.min(160, Math.max(48, Math.floor(maxCharacters / 3)));
226
+ const available = maxCharacters - markerBudget;
227
+ if (available < 2) throw new RangeError(`${label} cap is too small`);
228
+ const headLength = Math.ceil(available / 2);
229
+ const tailLength = Math.floor(available / 2);
230
+ const omitted = text.slice(headLength, text.length - tailLength);
231
+ let marker = `\n[${label}: ${omitted.length} chars omitted sha256=${sha256(omitted)}]\n`;
232
+ if (marker.length > markerBudget) marker = marker.slice(0, markerBudget - 2) + ']\n';
233
+ return text.slice(0, headLength) + marker + text.slice(text.length - tailLength);
234
+ }
235
+
236
+ function blockToText(block) {
237
+ if (typeof block === 'string') return block;
238
+ if (!block || typeof block !== 'object') return stableJson(block);
239
+ const type = String(block.type ?? 'object').toLowerCase();
240
+ if (type === 'text' || type === 'input_text' || type === 'output_text') return String(block.text ?? block.content ?? '');
241
+ if (type === 'image' || type === 'input_image' || type === 'image_url') {
242
+ const source = block.source ?? block.image_url ?? block.imageUrl ?? block.data;
243
+ if (Buffer.isBuffer(source) || ArrayBuffer.isView(source) || source instanceof ArrayBuffer) {
244
+ return summarizeBinary(source, block.mediaType ?? block.mimeType ?? block.source?.media_type ?? 'image/unknown');
245
+ }
246
+ return `[image ${stableJson(source)}]`;
247
+ }
248
+ if (type === 'tool_use' || type === 'tool-call' || type === 'tool_call' || type === 'function') {
249
+ return `[tool-call name=${String(block.name ?? block.function?.name ?? 'unknown')} id=${String(block.id ?? '')}]\n${stableJson(block.input ?? block.arguments ?? block.function?.arguments ?? null)}`;
250
+ }
251
+ if (type === 'tool_result' || type === 'tool-result') {
252
+ return `[tool-result id=${String(block.tool_use_id ?? block.toolCallId ?? block.id ?? '')}]\n${contentToText(block.content ?? block.output ?? block.result ?? '')}`;
253
+ }
254
+ return `[${type}] ${stableJson(block)}`;
255
+ }
256
+
257
+ function contentToText(content) {
258
+ if (typeof content === 'string') return content;
259
+ if (Array.isArray(content)) return content.map(blockToText).join('\n');
260
+ if (content === undefined || content === null) return '';
261
+ return stableJson(content);
262
+ }
263
+
264
+ /** Serializes provider-neutral user/assistant/tool messages with deterministic head+tail caps. */
265
+ export function serializeConversation(messages, options = {}) {
266
+ if (!Array.isArray(messages)) throw new TypeError('messages must be an array');
267
+ const maxMessageCharacters = positiveInteger(options.maxMessageCharacters, 24_000, 'maxMessageCharacters');
268
+ const maxCharacters = positiveInteger(options.maxCharacters, 500_000, 'maxCharacters');
269
+ const sections = messages.map((message, index) => {
270
+ if (!message || typeof message !== 'object') throw new TypeError(`message ${index} must be an object`);
271
+ const role = String(message.role ?? 'unknown').toLowerCase();
272
+ const metadata = [];
273
+ const toolName = message.name ?? message.toolName;
274
+ const toolCallId = message.toolCallId ?? message.tool_call_id;
275
+ if (toolName !== undefined) metadata.push(`name=${String(toolName)}`);
276
+ if (toolCallId !== undefined) metadata.push(`toolCallId=${String(toolCallId)}`);
277
+ const header = `--- message ${index + 1} role=${role}${metadata.length ? ` ${metadata.join(' ')}` : ''} ---`;
278
+ const normalized = normalizeText(contentToText(message.content ?? message.output ?? message.result ?? ''), {
279
+ maxCharacters: Math.max(maxCharacters, maxMessageCharacters, DEFAULT_LIMITS.maxCharacters),
280
+ tabWidth: options.tabWidth,
281
+ });
282
+ return `${header}\n${capHeadTail(normalized, maxMessageCharacters, 'message content')}`;
283
+ });
284
+ return capHeadTail(sections.join('\n\n'), maxCharacters, 'conversation');
285
+ }
286
+
287
+ /** Lossily run-length-compacts identical lines only when explicitly requested. */
288
+ export function compactRepeatedLines(text, options = {}) {
289
+ if (typeof text !== 'string') throw new TypeError('text must be a string');
290
+ const minimumRun = positiveInteger(options.minimumRun, 3, 'minimumRun');
291
+ const lines = text.split('\n');
292
+ const output = [];
293
+ for (let index = 0; index < lines.length;) {
294
+ let end = index + 1;
295
+ while (end < lines.length && lines[end] === lines[index]) end += 1;
296
+ const count = end - index;
297
+ if (count >= minimumRun) {
298
+ output.push(lines[index], `[previous line repeated ${count - 1} more times]`);
299
+ } else {
300
+ for (let cursor = index; cursor < end; cursor += 1) output.push(lines[cursor]);
301
+ }
302
+ index = end;
303
+ }
304
+ return output.join('\n');
305
+ }
306
+
307
+ /** Parses an X11 BDF font into bitmap rows. No system-font APIs are used. */
308
+ export function parseBdf(source) {
309
+ if (Buffer.isBuffer(source)) source = source.toString('utf8');
310
+ if (typeof source !== 'string') throw new TypeError('BDF source must be a string or Buffer');
311
+ const lines = source.replace(/\r\n?/gu, '\n').split('\n');
312
+ let fontBox;
313
+ const glyphs = new Map();
314
+ for (let index = 0; index < lines.length; index += 1) {
315
+ const line = lines[index];
316
+ if (line.startsWith('FONTBOUNDINGBOX ')) {
317
+ const values = line.slice(16).trim().split(/\s+/u).map(Number);
318
+ if (values.length !== 4 || values.some((value) => !Number.isInteger(value))) throw new Error('Invalid FONTBOUNDINGBOX');
319
+ fontBox = { width: values[0], height: values[1], xOffset: values[2], yOffset: values[3] };
320
+ }
321
+ if (!line.startsWith('STARTCHAR ')) continue;
322
+ const name = line.slice(10).trim();
323
+ let encoding;
324
+ let advance;
325
+ let box;
326
+ let bitmap;
327
+ for (index += 1; index < lines.length && lines[index] !== 'ENDCHAR'; index += 1) {
328
+ const glyphLine = lines[index];
329
+ if (glyphLine.startsWith('ENCODING ')) encoding = Number(glyphLine.slice(9).trim().split(/\s+/u)[0]);
330
+ else if (glyphLine.startsWith('DWIDTH ')) advance = Number(glyphLine.slice(7).trim().split(/\s+/u)[0]);
331
+ else if (glyphLine.startsWith('BBX ')) {
332
+ const values = glyphLine.slice(4).trim().split(/\s+/u).map(Number);
333
+ if (values.length !== 4 || values.some((value) => !Number.isInteger(value))) throw new Error(`Invalid BBX for ${name}`);
334
+ box = { width: values[0], height: values[1], xOffset: values[2], yOffset: values[3] };
335
+ } else if (glyphLine === 'BITMAP') {
336
+ if (!box) throw new Error(`BITMAP precedes BBX for ${name}`);
337
+ bitmap = [];
338
+ for (let row = 0; row < box.height; row += 1) {
339
+ const hex = lines[++index]?.trim();
340
+ if (!hex || !/^[0-9A-F]+$/iu.test(hex)) throw new Error(`Invalid bitmap row for ${name}`);
341
+ bitmap.push(BigInt(`0x${hex}`));
342
+ }
343
+ }
344
+ }
345
+ if (Number.isInteger(encoding) && encoding >= 0) {
346
+ if (!box || !bitmap || bitmap.length !== box.height) throw new Error(`Incomplete glyph ${name}`);
347
+ glyphs.set(encoding, Object.freeze({ name, encoding, advance: advance ?? box.width, ...box, bitmap: Object.freeze(bitmap) }));
348
+ }
349
+ }
350
+ if (!fontBox || fontBox.width <= 0 || fontBox.height <= 0) throw new Error('BDF is missing a valid FONTBOUNDINGBOX');
351
+ if (glyphs.size === 0) throw new Error('BDF contains no encoded glyphs');
352
+ return Object.freeze({ ...fontBox, glyphs });
353
+ }
354
+
355
+ function getBundledFont() {
356
+ if (!bundledFont) bundledFont = parseBdf(readFileSync(BDF_PATH));
357
+ return bundledFont;
358
+ }
359
+
360
+ function canonicalProvider(provider) {
361
+ const value = String(provider ?? '').trim().toLowerCase();
362
+ if (value === 'gemini' || value === 'google-ai' || value === 'google-generative-ai') return 'google';
363
+ return value;
364
+ }
365
+
366
+ /** Returns a frozen descriptor only for an explicitly allowlisted provider/model family. */
367
+ export function resolveProviderProfile(provider, model) {
368
+ const profile = PROVIDER_PROFILES[canonicalProvider(provider)];
369
+ const modelId = String(model ?? '').trim().toLowerCase();
370
+ if (!profile || !modelId) return null;
371
+ return profile.modelFamilies.some((pattern) => new RegExp(pattern, 'iu').test(modelId)) ? profile : null;
372
+ }
373
+
374
+ function resolveProfileRequest(profileOrRequest) {
375
+ if (profileOrRequest && typeof profileOrRequest === 'object' && typeof profileOrRequest.id === 'string') {
376
+ const known = Object.values(PROVIDER_PROFILES).find((candidate) => candidate === profileOrRequest || candidate.id === profileOrRequest.id);
377
+ if (!known) throw new Error('profile must be one of PROVIDER_PROFILES');
378
+ return known;
379
+ }
380
+ const profile = resolveProviderProfile(profileOrRequest?.provider, profileOrRequest?.model);
381
+ if (!profile) throw new Error('provider/model does not have a validated bitmap profile');
382
+ return profile;
383
+ }
384
+
385
+ function planLayout(source, profile, options = {}) {
386
+ const limits = mergeLimits(options.limits);
387
+ const normalized = options.normalized === true ? source : normalizeText(source, {
388
+ maxCharacters: limits.maxCharacters,
389
+ tabWidth: options.tabWidth,
390
+ });
391
+ if (normalized.length > limits.maxCharacters) throw new RangeError(`source exceeds maxCharacters (${limits.maxCharacters})`);
392
+ const renderedText = options.collapseRepeatedLines ? compactRepeatedLines(normalized, options.repetition) : normalized;
393
+ const columns = Math.floor(profile.width / profile.cellWidth);
394
+ const rowsPerFrame = Math.floor(profile.maxHeight / profile.cellHeight);
395
+ if (columns < 1 || rowsPerFrame < 1) throw new RangeError('profile cannot fit one glyph cell');
396
+
397
+ const rows = [];
398
+ for (const logicalLine of renderedText.split('\n')) {
399
+ const characters = Array.from(logicalLine);
400
+ if (characters.length === 0) rows.push('');
401
+ else for (let offset = 0; offset < characters.length; offset += columns) rows.push(characters.slice(offset, offset + columns).join(''));
402
+ }
403
+ const frameCount = Math.ceil(rows.length / rowsPerFrame);
404
+ if (frameCount > limits.maxFrames) throw new RangeError(`render requires ${frameCount} frames, exceeding maxFrames (${limits.maxFrames})`);
405
+
406
+ const pages = [];
407
+ let totalPixels = 0;
408
+ for (let frame = 0; frame < frameCount; frame += 1) {
409
+ const startRow = frame * rowsPerFrame;
410
+ const pageRows = rows.slice(startRow, startRow + rowsPerFrame);
411
+ const width = profile.width;
412
+ const height = pageRows.length * profile.cellHeight;
413
+ const pixels = width * height;
414
+ if (!Number.isSafeInteger(pixels) || pixels > limits.maxPixelsPerFrame) {
415
+ throw new RangeError(`frame ${frame} exceeds maxPixelsPerFrame (${limits.maxPixelsPerFrame})`);
416
+ }
417
+ totalPixels += pixels;
418
+ if (!Number.isSafeInteger(totalPixels) || totalPixels > limits.maxTotalPixels) {
419
+ throw new RangeError(`render exceeds maxTotalPixels (${limits.maxTotalPixels})`);
420
+ }
421
+ pages.push({ index: frame, width, height, rows: pageRows, rowCount: pageRows.length, startRow, endRow: startRow + pageRows.length - 1, pixels });
422
+ }
423
+ return { normalized, renderedText, columns, rowsPerFrame, frameCount, pages, totalPixels, limits };
424
+ }
425
+
426
+ /** Counts frames using the exact same wrapping and page-height algorithm as rendering. */
427
+ export function countFrames(source, profileOrRequest, options = {}) {
428
+ if (typeof source !== 'string') throw new TypeError('source must be a string');
429
+ const profile = resolveProfileRequest(profileOrRequest);
430
+ const plan = planLayout(source, profile, { ...options, normalized: false });
431
+ return Object.freeze({
432
+ profileId: profile.id,
433
+ frameCount: plan.frameCount,
434
+ sourceCharacters: plan.normalized.length,
435
+ renderedCharacters: plan.renderedText.length,
436
+ columns: plan.columns,
437
+ rowsPerFrame: plan.rowsPerFrame,
438
+ rowCount: plan.pages.reduce((sum, page) => sum + page.rowCount, 0),
439
+ pixelCount: plan.totalPixels,
440
+ pages: Object.freeze(plan.pages.map((page) => Object.freeze({
441
+ index: page.index,
442
+ width: page.width,
443
+ height: page.height,
444
+ rows: page.rowCount,
445
+ startRow: page.startRow,
446
+ endRow: page.endRow,
447
+ }))),
448
+ });
449
+ }
450
+
451
+ const CRC_TABLE = (() => {
452
+ const table = new Uint32Array(256);
453
+ for (let value = 0; value < 256; value += 1) {
454
+ let crc = value;
455
+ for (let bit = 0; bit < 8; bit += 1) crc = crc & 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
456
+ table[value] = crc >>> 0;
457
+ }
458
+ return table;
459
+ })();
460
+
461
+ function crc32(buffers) {
462
+ let crc = 0xffffffff;
463
+ for (const buffer of buffers) for (const byte of buffer) crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
464
+ return (crc ^ 0xffffffff) >>> 0;
465
+ }
466
+
467
+ function pngChunk(type, data) {
468
+ const typeBuffer = Buffer.from(type, 'ascii');
469
+ const chunk = Buffer.allocUnsafe(12 + data.length);
470
+ chunk.writeUInt32BE(data.length, 0);
471
+ typeBuffer.copy(chunk, 4);
472
+ data.copy(chunk, 8);
473
+ chunk.writeUInt32BE(crc32([typeBuffer, data]), 8 + data.length);
474
+ return chunk;
475
+ }
476
+
477
+ function encodeGrayscalePng(pixels, width, height, maxPngBytes) {
478
+ const stride = width;
479
+ const rawBytes = (stride + 1) * height;
480
+ if (!Number.isSafeInteger(rawBytes) || rawBytes > maxPngBytes) throw new RangeError(`raw PNG exceeds maxPngBytes (${maxPngBytes})`);
481
+ const scanlines = Buffer.allocUnsafe(rawBytes);
482
+ for (let row = 0; row < height; row += 1) {
483
+ const destination = row * (stride + 1);
484
+ scanlines[destination] = 0;
485
+ pixels.copy(scanlines, destination + 1, row * stride, (row + 1) * stride);
486
+ }
487
+ const compressed = deflateSync(scanlines, { level: 9, strategy: zlibConstants.Z_DEFAULT_STRATEGY });
488
+ const header = Buffer.alloc(13);
489
+ header.writeUInt32BE(width, 0);
490
+ header.writeUInt32BE(height, 4);
491
+ header[8] = 8;
492
+ header[9] = 0;
493
+ header[10] = 0;
494
+ header[11] = 0;
495
+ header[12] = 0;
496
+ const png = Buffer.concat([
497
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
498
+ pngChunk('IHDR', header),
499
+ pngChunk('IDAT', compressed),
500
+ pngChunk('IEND', Buffer.alloc(0)),
501
+ ]);
502
+ if (png.length > maxPngBytes) throw new RangeError(`encoded PNG exceeds maxPngBytes (${maxPngBytes})`);
503
+ return png;
504
+ }
505
+
506
+ function rasterizePage(page, profile, font, maxPngBytes) {
507
+ const pixels = Buffer.alloc(page.pixels, 255);
508
+ const cellXOffset = Math.floor((profile.cellWidth - font.width) / 2);
509
+ const cellYOffset = Math.floor((profile.cellHeight - font.height) / 2);
510
+ for (let rowIndex = 0; rowIndex < page.rows.length; rowIndex += 1) {
511
+ const characters = Array.from(page.rows[rowIndex]);
512
+ for (let column = 0; column < characters.length; column += 1) {
513
+ const codePoint = characters[column].codePointAt(0);
514
+ const glyph = font.glyphs.get(codePoint);
515
+ if (!glyph) throw new Error(`normalized source contains missing glyph ${codePointLabel(codePoint)}`);
516
+ const rowOffset = font.height + font.yOffset - glyph.height - glyph.yOffset;
517
+ const bytesPerGlyphRow = Math.ceil(glyph.width / 8);
518
+ const encodedBits = bytesPerGlyphRow * 8;
519
+ for (let glyphRow = 0; glyphRow < glyph.height; glyphRow += 1) {
520
+ const y = rowIndex * profile.cellHeight + cellYOffset + rowOffset + glyphRow;
521
+ if (y < 0 || y >= page.height) continue;
522
+ for (let glyphColumn = 0; glyphColumn < glyph.width; glyphColumn += 1) {
523
+ const mask = 1n << BigInt(encodedBits - glyphColumn - 1);
524
+ if ((glyph.bitmap[glyphRow] & mask) === 0n) continue;
525
+ const x = column * profile.cellWidth + cellXOffset + glyph.xOffset + glyphColumn;
526
+ if (x >= 0 && x < page.width) pixels[y * page.width + x] = 0;
527
+ }
528
+ }
529
+ }
530
+ }
531
+ return encodeGrayscalePng(pixels, page.width, page.height, maxPngBytes);
532
+ }
533
+
534
+ function providerBlock(profile, png) {
535
+ const data = png.toString('base64');
536
+ if (profile.provider === 'anthropic') {
537
+ return { type: 'image', source: { type: 'base64', media_type: 'image/png', data } };
538
+ }
539
+ if (profile.provider === 'google') {
540
+ return { inlineData: { mimeType: 'image/png', data } };
541
+ }
542
+ return { type: 'input_image', image_url: `data:image/png;base64,${data}`, detail: 'original' };
543
+ }
544
+
545
+ /** Renders real, black-on-white, 8-bit grayscale PNG pages entirely in memory. */
546
+ export function renderBitmapContext(source, request = {}) {
547
+ if (typeof source !== 'string') throw new TypeError('source must be a string');
548
+ const profile = request.profile ? resolveProfileRequest(request.profile) : resolveProfileRequest(request);
549
+ const plan = planLayout(source, profile, { ...request, normalized: false });
550
+ const font = getBundledFont();
551
+ if (font.width !== profile.glyphWidth || font.height !== profile.glyphHeight) throw new Error('bundled font does not match profile glyph dimensions');
552
+
553
+ const pages = plan.pages.map((page) => {
554
+ const png = rasterizePage(page, profile, font, plan.limits.maxPngBytes);
555
+ const pageSourceSha256 = sha256(`${profile.id}\0${page.rows.join('\n')}`);
556
+ return Object.freeze({
557
+ index: page.index,
558
+ width: page.width,
559
+ height: page.height,
560
+ png,
561
+ bytes: png.byteLength,
562
+ sha256: sha256(png),
563
+ sourceSha256: pageSourceSha256,
564
+ });
565
+ });
566
+ const sourceSha256 = sha256(plan.normalized);
567
+ const contextDigest = sha256(`${SNAPCOMPACT_VERSION}\0${profile.id}\0${plan.normalized}`);
568
+ const payloadDigest = sha256(Buffer.concat(pages.map((page) => page.png)));
569
+ const receipt = deepFreeze({
570
+ version: SNAPCOMPACT_VERSION,
571
+ profile: profile.id,
572
+ contextDigest,
573
+ sourceSha256,
574
+ payloadDigest,
575
+ pageSha256s: pages.map((page) => page.sha256),
576
+ pageSourceSha256s: pages.map((page) => page.sourceSha256),
577
+ sourceCharacters: plan.normalized.length,
578
+ renderedCharacters: plan.renderedText.length,
579
+ lineCount: plan.renderedText.split('\n').length,
580
+ frameCount: pages.length,
581
+ pixelCount: plan.totalPixels,
582
+ byteCount: pages.reduce((sum, page) => sum + page.bytes, 0),
583
+ });
584
+ return Object.freeze({
585
+ version: SNAPCOMPACT_VERSION,
586
+ profile,
587
+ textFallback: plan.normalized,
588
+ pages: Object.freeze(pages),
589
+ blocks: Object.freeze(pages.map((page) => deepFreeze(providerBlock(profile, page.png)))),
590
+ receipt,
591
+ });
592
+ }
593
+
594
+ function estimateTokens(plan, profile, overrides = {}) {
595
+ const textCharactersPerToken = finiteNonNegative(overrides.textCharactersPerToken, TOKEN_ESTIMATE_ASSUMPTIONS.textCharactersPerToken, 'textCharactersPerToken');
596
+ const imagePixelsPerToken = finiteNonNegative(overrides.imagePixelsPerToken, TOKEN_ESTIMATE_ASSUMPTIONS.imagePixelsPerToken, 'imagePixelsPerToken');
597
+ if (textCharactersPerToken === 0 || imagePixelsPerToken === 0) throw new RangeError('token estimate divisors must be greater than zero');
598
+ return {
599
+ text: Math.ceil(plan.normalized.length / textCharactersPerToken),
600
+ image: Math.ceil(plan.totalPixels / imagePixelsPerToken),
601
+ assumptions: deepFreeze({ textCharactersPerToken, imagePixelsPerToken, basis: profile.tokenEstimate.basis }),
602
+ };
603
+ }
604
+
605
+ /** Fail-closed eligibility and caller-priced transport comparison. */
606
+ export function decideContextTransport(request = {}) {
607
+ const source = request.source;
608
+ if (typeof source !== 'string') throw new TypeError('source must be a string');
609
+ const profile = resolveProviderProfile(request.provider, request.model);
610
+ const textFallback = normalizeText(source, { maxCharacters: request.limits?.maxCharacters, tabWidth: request.tabWidth });
611
+ const fallback = (reason, extra = {}) => deepFreeze({
612
+ selected: 'text',
613
+ eligible: false,
614
+ reason,
615
+ profile: profile?.id ?? null,
616
+ textFallback,
617
+ ...extra,
618
+ });
619
+ if (request.vision !== true) return fallback('vision-capability-required');
620
+ if (!profile) return fallback('unvalidated-model-profile');
621
+ if (!request.costs || typeof request.costs !== 'object') return fallback('cost-input-required');
622
+
623
+ const plan = planLayout(textFallback, profile, { ...request, normalized: true });
624
+ const tokens = estimateTokens(plan, profile, request.tokenEstimates);
625
+ if (request.costs.textInputUsdPerMillion === undefined || request.costs.imageInputUsdPerMillion === undefined) {
626
+ return fallback('cost-input-required');
627
+ }
628
+ const expectedReuse = positiveInteger(request.expectedReuse, 1, 'expectedReuse');
629
+ const textInputUsdPerMillion = finiteNonNegative(request.costs.textInputUsdPerMillion, 0, 'textInputUsdPerMillion');
630
+ const imageInputUsdPerMillion = finiteNonNegative(request.costs.imageInputUsdPerMillion, 0, 'imageInputUsdPerMillion');
631
+ const outputUsdPerMillion = finiteNonNegative(request.costs.outputUsdPerMillion, 0, 'outputUsdPerMillion');
632
+ const decodeOutputTokens = finiteNonNegative(request.costs.decodeOutputTokens, 0, 'decodeOutputTokens');
633
+ const decodeFixedUsd = finiteNonNegative(request.costs.decodeFixedUsd, 0, 'decodeFixedUsd');
634
+ const textUsd = tokens.text * textInputUsdPerMillion / 1_000_000 * expectedReuse;
635
+ const imageInputUsd = tokens.image * imageInputUsdPerMillion / 1_000_000 * expectedReuse;
636
+ const oneTimeDecodeUsd = decodeOutputTokens * outputUsdPerMillion / 1_000_000 + decodeFixedUsd;
637
+ const imageUsd = imageInputUsd + oneTimeDecodeUsd;
638
+ const selected = imageUsd < textUsd ? 'bitmap-png' : 'text';
639
+ return deepFreeze({
640
+ selected,
641
+ eligible: true,
642
+ reason: selected === 'bitmap-png' ? 'lower-estimated-input-cost' : 'text-is-lossless-or-not-more-expensive',
643
+ profile: profile.id,
644
+ textFallback,
645
+ expectedReuse,
646
+ frameCount: plan.frameCount,
647
+ estimates: {
648
+ textTokens: tokens.text,
649
+ imageTokens: tokens.image,
650
+ textUsd,
651
+ imageInputUsd,
652
+ oneTimeDecodeUsd,
653
+ imageUsd,
654
+ assumptions: tokens.assumptions,
655
+ },
656
+ });
657
+ }
658
+
659
+ function textDescriptor(text) {
660
+ const bytes = Buffer.byteLength(text, 'utf8');
661
+ const digest = sha256(text);
662
+ return deepFreeze({
663
+ codec: 'utf8',
664
+ version: SNAPCOMPACT_VERSION,
665
+ profile: null,
666
+ contextDigest: digest,
667
+ payloadDigest: digest,
668
+ dimensions: [],
669
+ bytes,
670
+ counts: { characters: text.length, frames: 0, pixels: 0 },
671
+ });
672
+ }
673
+
674
+ /** SDK-facing context pack: always includes recoverable normalized text fallback. */
675
+ export function createContextCarrier(request = {}) {
676
+ if ((request.source === undefined) === (request.messages === undefined)) {
677
+ throw new TypeError('provide exactly one of source or messages');
678
+ }
679
+ const source = request.messages === undefined
680
+ ? normalizeText(request.source, { maxCharacters: request.limits?.maxCharacters, tabWidth: request.tabWidth })
681
+ : serializeConversation(request.messages, request.serialization);
682
+ const decision = decideContextTransport({ ...request, source });
683
+ if (decision.selected !== 'bitmap-png') {
684
+ return Object.freeze({
685
+ selected: 'text',
686
+ textFallback: decision.textFallback,
687
+ blocks: Object.freeze([]),
688
+ pages: Object.freeze([]),
689
+ descriptor: textDescriptor(decision.textFallback),
690
+ decision,
691
+ });
692
+ }
693
+ const rendered = renderBitmapContext(decision.textFallback, request);
694
+ const descriptor = deepFreeze({
695
+ codec: 'bitmap-png',
696
+ version: SNAPCOMPACT_VERSION,
697
+ profile: rendered.profile.id,
698
+ contextDigest: rendered.receipt.contextDigest,
699
+ payloadDigest: rendered.receipt.payloadDigest,
700
+ dimensions: rendered.pages.map((page) => ({ width: page.width, height: page.height })),
701
+ bytes: rendered.receipt.byteCount,
702
+ counts: {
703
+ characters: rendered.receipt.sourceCharacters,
704
+ frames: rendered.receipt.frameCount,
705
+ pixels: rendered.receipt.pixelCount,
706
+ },
707
+ });
708
+ return Object.freeze({
709
+ selected: 'bitmap-png',
710
+ textFallback: rendered.textFallback,
711
+ blocks: rendered.blocks,
712
+ pages: rendered.pages,
713
+ descriptor,
714
+ decision,
715
+ });
716
+ }