@retinue/agentkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deterministic PDF writer (#134).
|
|
3
|
+
*
|
|
4
|
+
* Writing a PDF is far more tractable than reading one, because the output is ours: no unknown producers, no
|
|
5
|
+
* broken xrefs, no font encodings to guess. This emits a small, valid subset — Base-14 fonts, one content
|
|
6
|
+
* stream per page, an uncompressed xref table — which every viewer has supported since 1993.
|
|
7
|
+
*
|
|
8
|
+
* **Determinism is a requirement, not a nicety** (AC-6). A PDF normally carries a `CreationDate` and often a
|
|
9
|
+
* random `/ID`, either of which makes two renders of the same input differ. Both are fixed here: the date
|
|
10
|
+
* comes from the caller (and defaults to a constant), and the `/ID` is derived from the content. That is what
|
|
11
|
+
* lets a re-render be compared byte-for-byte, which is the only way to notice a renderer that has quietly
|
|
12
|
+
* started producing something else.
|
|
13
|
+
*
|
|
14
|
+
* **No font embedding.** Base-14 fonts are guaranteed present in every viewer, so the file carries no font
|
|
15
|
+
* program — which keeps a report a few kilobytes instead of a few hundred, and sidesteps the licensing
|
|
16
|
+
* question entirely. The cost is the character repertoire: WinAnsi, so no CJK. Stated rather than discovered,
|
|
17
|
+
* and `unsupportedCharacters` reports what was dropped instead of silently emitting blanks.
|
|
18
|
+
*/
|
|
19
|
+
/** Points. A4 rather than Letter: the deployment is European and the difference is visible when it is wrong. */
|
|
20
|
+
export const PAGE_WIDTH = 595.28;
|
|
21
|
+
export const PAGE_HEIGHT = 841.89;
|
|
22
|
+
export const MARGIN = 56.7; // 20mm
|
|
23
|
+
/**
|
|
24
|
+
* Helvetica advance widths, per 1000 units, for the printable WinAnsi range this writer supports.
|
|
25
|
+
*
|
|
26
|
+
* Needed because line wrapping has to know how wide text *will* be. A monospace approximation would wrap
|
|
27
|
+
* badly enough to be obviously wrong — proportional text estimated at a fixed width either overflows the
|
|
28
|
+
* margin or leaves a third of the line empty.
|
|
29
|
+
*
|
|
30
|
+
* Indexed from space (32) to tilde (126); anything outside falls back to the average. The bold face differs
|
|
31
|
+
* enough to matter for headings, so it has its own table.
|
|
32
|
+
*/
|
|
33
|
+
const HELVETICA_WIDTHS = [
|
|
34
|
+
278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556,
|
|
35
|
+
556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278,
|
|
36
|
+
500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469,
|
|
37
|
+
556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500,
|
|
38
|
+
278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584,
|
|
39
|
+
];
|
|
40
|
+
const HELVETICA_BOLD_WIDTHS = [
|
|
41
|
+
278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556,
|
|
42
|
+
556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278,
|
|
43
|
+
556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584,
|
|
44
|
+
556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556,
|
|
45
|
+
333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584,
|
|
46
|
+
];
|
|
47
|
+
const AVERAGE_WIDTH = 556;
|
|
48
|
+
/** Courier is monospace at 600/1000, which is why it needs no table. */
|
|
49
|
+
const MONO_WIDTH = 600;
|
|
50
|
+
const charWidth = (code, face) => {
|
|
51
|
+
if (face === "mono")
|
|
52
|
+
return MONO_WIDTH;
|
|
53
|
+
const table = face === "bold" ? HELVETICA_BOLD_WIDTHS : HELVETICA_WIDTHS;
|
|
54
|
+
const index = code - 32;
|
|
55
|
+
return table[index] ?? AVERAGE_WIDTH;
|
|
56
|
+
};
|
|
57
|
+
/** Width of a string at a size, in points. */
|
|
58
|
+
export const textWidth = (text, size, face) => {
|
|
59
|
+
let total = 0;
|
|
60
|
+
for (const ch of text)
|
|
61
|
+
total += charWidth(ch.codePointAt(0) ?? 32, face);
|
|
62
|
+
return (total * size) / 1000;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* WinAnsi-encode a string for a PDF literal, reporting what could not be represented.
|
|
66
|
+
*
|
|
67
|
+
* Reported rather than substituted silently: a report whose CJK title rendered as `????` is a report someone
|
|
68
|
+
* sends to a colleague believing it is correct, and finding out later is worse than being told now.
|
|
69
|
+
*/
|
|
70
|
+
export const encodeWinAnsi = (text) => {
|
|
71
|
+
const dropped = [];
|
|
72
|
+
let encoded = "";
|
|
73
|
+
for (const ch of text) {
|
|
74
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
75
|
+
if (code === 0x0a || code === 0x0d) {
|
|
76
|
+
encoded += " ";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (code < 32)
|
|
80
|
+
continue;
|
|
81
|
+
if (code < 127) {
|
|
82
|
+
// The three characters that terminate or escape a PDF literal string.
|
|
83
|
+
if (ch === "(" || ch === ")" || ch === "\\")
|
|
84
|
+
encoded += `\\${ch}`;
|
|
85
|
+
else
|
|
86
|
+
encoded += ch;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const winAnsi = WIN_ANSI_EXTRA[ch];
|
|
90
|
+
if (winAnsi !== undefined) {
|
|
91
|
+
// Octal, because a byte above 127 in a literal string is not portable as a raw character.
|
|
92
|
+
encoded += `\\${winAnsi.toString(8).padStart(3, "0")}`;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
dropped.push(ch);
|
|
96
|
+
}
|
|
97
|
+
return { encoded, dropped };
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The WinAnsi characters worth mapping: European letters, quotes and dashes.
|
|
101
|
+
*
|
|
102
|
+
* Not the full table. These are the ones that appear in real documents and whose absence is noticed — a
|
|
103
|
+
* German report with `ä` mangled is unusable, and a smart quote rendered as nothing is a typo in someone's
|
|
104
|
+
* name. Everything else is reported as dropped rather than approximated, because a wrong character is worse
|
|
105
|
+
* than a visible gap.
|
|
106
|
+
*/
|
|
107
|
+
const WIN_ANSI_EXTRA = {
|
|
108
|
+
"€": 0x80, "‚": 0x82, "„": 0x84, "…": 0x85, "†": 0x86, "‡": 0x87, "‰": 0x89,
|
|
109
|
+
"‘": 0x91, "’": 0x92, "“": 0x93, "”": 0x94, "•": 0x95, "–": 0x96, "—": 0x97,
|
|
110
|
+
"™": 0x99, " ": 0x20, "±": 0xb1, "µ": 0xb5, "·": 0xb7, "°": 0xb0, "§": 0xa7,
|
|
111
|
+
"£": 0xa3, "¥": 0xa5, "©": 0xa9, "®": 0xae, "«": 0xab, "»": 0xbb, "¼": 0xbc, "½": 0xbd,
|
|
112
|
+
"À": 0xc0, "Á": 0xc1, "Â": 0xc2, "Ã": 0xc3, "Ä": 0xc4, "Å": 0xc5, "Æ": 0xc6, "Ç": 0xc7,
|
|
113
|
+
"È": 0xc8, "É": 0xc9, "Ê": 0xca, "Ë": 0xcb, "Ì": 0xcc, "Í": 0xcd, "Î": 0xce, "Ï": 0xcf,
|
|
114
|
+
"Ñ": 0xd1, "Ò": 0xd2, "Ó": 0xd3, "Ô": 0xd4, "Õ": 0xd5, "Ö": 0xd6, "Ø": 0xd8, "Ù": 0xd9,
|
|
115
|
+
"Ú": 0xda, "Û": 0xdb, "Ü": 0xdc, "ß": 0xdf,
|
|
116
|
+
"à": 0xe0, "á": 0xe1, "â": 0xe2, "ã": 0xe3, "ä": 0xe4, "å": 0xe5, "æ": 0xe6, "ç": 0xe7,
|
|
117
|
+
"è": 0xe8, "é": 0xe9, "ê": 0xea, "ë": 0xeb, "ì": 0xec, "í": 0xed, "î": 0xee, "ï": 0xef,
|
|
118
|
+
"ñ": 0xf1, "ò": 0xf2, "ó": 0xf3, "ô": 0xf4, "õ": 0xf5, "ö": 0xf6, "ø": 0xf8, "ù": 0xf9,
|
|
119
|
+
"ú": 0xfa, "û": 0xfb, "ü": 0xfc, "ÿ": 0xff,
|
|
120
|
+
};
|
|
121
|
+
/** Break a string into lines that fit `maxWidth`, breaking on words and only inside a word when it must. */
|
|
122
|
+
export const wrapText = (text, maxWidth, size, face) => {
|
|
123
|
+
const lines = [];
|
|
124
|
+
let line = "";
|
|
125
|
+
for (const word of text.split(/\s+/).filter((w) => w !== "")) {
|
|
126
|
+
const candidate = line === "" ? word : `${line} ${word}`;
|
|
127
|
+
if (textWidth(candidate, size, face) <= maxWidth) {
|
|
128
|
+
line = candidate;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (line !== "")
|
|
132
|
+
lines.push(line);
|
|
133
|
+
// A single word wider than the column — a URL, usually. Broken mid-word rather than allowed to run off
|
|
134
|
+
// the page, because an overflowing line is silently truncated by the viewer.
|
|
135
|
+
if (textWidth(word, size, face) > maxWidth) {
|
|
136
|
+
let chunk = "";
|
|
137
|
+
for (const ch of word) {
|
|
138
|
+
if (textWidth(chunk + ch, size, face) > maxWidth && chunk !== "") {
|
|
139
|
+
lines.push(chunk);
|
|
140
|
+
chunk = ch;
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
chunk += ch;
|
|
144
|
+
}
|
|
145
|
+
line = chunk;
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
line = word;
|
|
149
|
+
}
|
|
150
|
+
if (line !== "")
|
|
151
|
+
lines.push(line);
|
|
152
|
+
return lines.length === 0 ? [""] : lines;
|
|
153
|
+
};
|
|
154
|
+
const FONT_SIZES = { h1: 20, h2: 16, h3: 13, h4: 11.5, body: 10.5, mono: 9.5, small: 8.5 };
|
|
155
|
+
const LEADING = 1.35;
|
|
156
|
+
/** One page's accumulated content-stream operators plus the y cursor. */
|
|
157
|
+
class PageBuilder {
|
|
158
|
+
ops = [];
|
|
159
|
+
y = PAGE_HEIGHT - MARGIN;
|
|
160
|
+
get remaining() {
|
|
161
|
+
return this.y - MARGIN;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const FONT_RES = { regular: "/F1", bold: "/F2", mono: "/F3" };
|
|
165
|
+
export const renderPdf = (input) => {
|
|
166
|
+
const pages = [new PageBuilder()];
|
|
167
|
+
const dropped = new Set();
|
|
168
|
+
const contentWidth = PAGE_WIDTH - 2 * MARGIN;
|
|
169
|
+
const page = () => pages[pages.length - 1];
|
|
170
|
+
const newPage = () => {
|
|
171
|
+
pages.push(new PageBuilder());
|
|
172
|
+
return page();
|
|
173
|
+
};
|
|
174
|
+
/** Reserve vertical space, starting a page when it will not fit. */
|
|
175
|
+
const reserve = (height) => {
|
|
176
|
+
if (page().remaining < height)
|
|
177
|
+
newPage();
|
|
178
|
+
return page();
|
|
179
|
+
};
|
|
180
|
+
const draw = (text, x, size, face) => {
|
|
181
|
+
const { encoded, dropped: bad } = encodeWinAnsi(text);
|
|
182
|
+
for (const ch of bad)
|
|
183
|
+
dropped.add(ch);
|
|
184
|
+
const p = page();
|
|
185
|
+
p.ops.push({
|
|
186
|
+
text: `BT ${FONT_RES[face]} ${size} Tf 1 0 0 1 ${x.toFixed(2)} ${p.y.toFixed(2)} Tm (${encoded}) Tj ET`,
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const paragraph = (text, size, face, indent = 0, gapAfter = size * 0.55) => {
|
|
190
|
+
const lines = wrapText(text, contentWidth - indent, size, face);
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
const p = reserve(size * LEADING);
|
|
193
|
+
p.y -= size * LEADING;
|
|
194
|
+
draw(line, MARGIN + indent, size, face);
|
|
195
|
+
}
|
|
196
|
+
page().y -= gapAfter;
|
|
197
|
+
};
|
|
198
|
+
const rule = (width, thickness = 0.5) => {
|
|
199
|
+
const p = reserve(thickness + 2);
|
|
200
|
+
p.y -= 3;
|
|
201
|
+
p.ops.push({
|
|
202
|
+
text: `${thickness} w ${MARGIN.toFixed(2)} ${p.y.toFixed(2)} m ${(MARGIN + width).toFixed(2)} ${p.y.toFixed(2)} l S`,
|
|
203
|
+
});
|
|
204
|
+
p.y -= 4;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* A table.
|
|
208
|
+
*
|
|
209
|
+
* Column widths come from the widest cell, scaled to fit — a fixed split would squeeze a column of dates
|
|
210
|
+
* next to a column of prose. The header row repeats on a page break, because a table whose headings are on
|
|
211
|
+
* the previous page is a table nobody can read.
|
|
212
|
+
*/
|
|
213
|
+
const table = (rows, hasHeader) => {
|
|
214
|
+
const columns = Math.max(...rows.map((r) => r.length), 1);
|
|
215
|
+
const natural = Array.from({ length: columns }, (_, c) => Math.max(...rows.map((r) => textWidth(r[c] ?? "", FONT_SIZES.body, "regular")), 20));
|
|
216
|
+
const total = natural.reduce((a, b) => a + b, 0);
|
|
217
|
+
const padding = 5;
|
|
218
|
+
const usable = contentWidth - padding * 2 * columns;
|
|
219
|
+
const widths = natural.map((w) => (w / total) * usable);
|
|
220
|
+
const rowHeight = (cells, face) => {
|
|
221
|
+
const lines = Math.max(...cells.map((cell, c) => wrapText(cell, widths[c] ?? usable, FONT_SIZES.body, face).length), 1);
|
|
222
|
+
return lines * FONT_SIZES.body * LEADING + padding;
|
|
223
|
+
};
|
|
224
|
+
const drawRow = (cells, face) => {
|
|
225
|
+
const height = rowHeight(cells, face);
|
|
226
|
+
const p = reserve(height);
|
|
227
|
+
const top = p.y;
|
|
228
|
+
let x = MARGIN;
|
|
229
|
+
cells.forEach((cell, c) => {
|
|
230
|
+
const width = widths[c] ?? usable;
|
|
231
|
+
const lines = wrapText(cell, width, FONT_SIZES.body, face);
|
|
232
|
+
let lineY = top;
|
|
233
|
+
for (const line of lines) {
|
|
234
|
+
lineY -= FONT_SIZES.body * LEADING;
|
|
235
|
+
const { encoded, dropped: bad } = encodeWinAnsi(line);
|
|
236
|
+
for (const ch of bad)
|
|
237
|
+
dropped.add(ch);
|
|
238
|
+
p.ops.push({
|
|
239
|
+
text: `BT ${FONT_RES[face]} ${FONT_SIZES.body} Tf 1 0 0 1 ${(x + padding).toFixed(2)} ${lineY.toFixed(2)} Tm (${encoded}) Tj ET`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
x += width + padding * 2;
|
|
243
|
+
});
|
|
244
|
+
p.y = top - height;
|
|
245
|
+
return p;
|
|
246
|
+
};
|
|
247
|
+
const header = hasHeader ? rows[0] : undefined;
|
|
248
|
+
if (header !== undefined) {
|
|
249
|
+
drawRow(header, "bold");
|
|
250
|
+
rule(contentWidth, 0.8);
|
|
251
|
+
}
|
|
252
|
+
for (const row of hasHeader ? rows.slice(1) : rows) {
|
|
253
|
+
const before = page();
|
|
254
|
+
drawRow(row, "regular");
|
|
255
|
+
// The header repeats after a break. Checked by page identity rather than by counting, because the row
|
|
256
|
+
// itself may have been what caused the break.
|
|
257
|
+
if (page() !== before && header !== undefined) {
|
|
258
|
+
// Reinsert at the top of the new page, above the row just drawn — so it is redrawn rather than moved.
|
|
259
|
+
const current = page();
|
|
260
|
+
current.y = PAGE_HEIGHT - MARGIN;
|
|
261
|
+
current.ops.length = 0;
|
|
262
|
+
drawRow(header, "bold");
|
|
263
|
+
rule(contentWidth, 0.8);
|
|
264
|
+
drawRow(row, "regular");
|
|
265
|
+
}
|
|
266
|
+
rule(contentWidth, 0.2);
|
|
267
|
+
}
|
|
268
|
+
page().y -= 6;
|
|
269
|
+
};
|
|
270
|
+
// Title, then the blocks.
|
|
271
|
+
paragraph(input.title, FONT_SIZES.h1, "bold", 0, FONT_SIZES.h1 * 0.8);
|
|
272
|
+
for (const block of input.blocks) {
|
|
273
|
+
switch (block.kind) {
|
|
274
|
+
case "heading": {
|
|
275
|
+
const size = block.level === 1 ? FONT_SIZES.h1 : block.level === 2 ? FONT_SIZES.h2 : block.level === 3 ? FONT_SIZES.h3 : FONT_SIZES.h4;
|
|
276
|
+
// Space *before* a heading, so it groups with the text it introduces rather than floating.
|
|
277
|
+
page().y -= size * 0.45;
|
|
278
|
+
paragraph(block.text, size, "bold", 0, size * 0.35);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "paragraph":
|
|
282
|
+
paragraph(block.text, FONT_SIZES.body, "regular");
|
|
283
|
+
break;
|
|
284
|
+
case "list":
|
|
285
|
+
block.items.forEach((item, i) => {
|
|
286
|
+
const marker = block.ordered ? `${i + 1}.` : "•";
|
|
287
|
+
const markerWidth = textWidth(`${marker} `, FONT_SIZES.body, "regular");
|
|
288
|
+
const p = reserve(FONT_SIZES.body * LEADING);
|
|
289
|
+
p.y -= FONT_SIZES.body * LEADING;
|
|
290
|
+
draw(marker, MARGIN + 6, FONT_SIZES.body, "regular");
|
|
291
|
+
// The item's text is drawn hanging-indented, so a wrapped second line aligns under the first rather
|
|
292
|
+
// than under the bullet.
|
|
293
|
+
const lines = wrapText(item, contentWidth - markerWidth - 12, FONT_SIZES.body, "regular");
|
|
294
|
+
lines.forEach((line, li) => {
|
|
295
|
+
if (li > 0) {
|
|
296
|
+
const q = reserve(FONT_SIZES.body * LEADING);
|
|
297
|
+
q.y -= FONT_SIZES.body * LEADING;
|
|
298
|
+
}
|
|
299
|
+
draw(line, MARGIN + 6 + markerWidth + 4, FONT_SIZES.body, "regular");
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
page().y -= FONT_SIZES.body * 0.5;
|
|
303
|
+
break;
|
|
304
|
+
case "table":
|
|
305
|
+
table(block.rows, block.hasHeader);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// References last, as a numbered list matching the inline markers.
|
|
310
|
+
if (input.citations !== undefined && input.citations.length > 0) {
|
|
311
|
+
page().y -= FONT_SIZES.h2 * 0.6;
|
|
312
|
+
paragraph("References", FONT_SIZES.h2, "bold", 0, FONT_SIZES.h2 * 0.3);
|
|
313
|
+
for (const citation of input.citations) {
|
|
314
|
+
const parts = [citation.title, citation.locator, citation.url].filter((part) => part !== undefined && part !== "");
|
|
315
|
+
paragraph(`[${citation.marker}] ${parts.join(" — ")}`, FONT_SIZES.small, "regular", 0, 3);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
bytes: assemble(pages, input.createdAt ?? FIXED_CREATION_DATE),
|
|
320
|
+
pageCount: pages.length,
|
|
321
|
+
// Sorted, so the same input reports the same list in the same order — AC-6 applies to the result, not
|
|
322
|
+
// only to the bytes.
|
|
323
|
+
unsupportedCharacters: [...dropped].sort(),
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* The default creation date.
|
|
328
|
+
*
|
|
329
|
+
* A constant, and that is the point. `new Date()` would put a different timestamp in every render and make
|
|
330
|
+
* byte-for-byte comparison impossible — so a caller that wants a real date passes one, and a caller that
|
|
331
|
+
* wants reproducibility gets it by default. The date chosen is the PDF epoch's own convention for "unset".
|
|
332
|
+
*/
|
|
333
|
+
export const FIXED_CREATION_DATE = "2000-01-01T00:00:00.000Z";
|
|
334
|
+
/** `D:YYYYMMDDHHmmSSZ`, the only date format a PDF accepts. */
|
|
335
|
+
const pdfDate = (iso) => {
|
|
336
|
+
const d = new Date(iso);
|
|
337
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
338
|
+
return `D:${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}Z`;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Assemble the object graph, the xref table and the trailer.
|
|
342
|
+
*
|
|
343
|
+
* Byte offsets in the xref must be exact — a viewer that cannot parse the xref falls back to scanning, and
|
|
344
|
+
* some refuse outright — so the file is built as a list of latin1 chunks whose lengths are the offsets.
|
|
345
|
+
*/
|
|
346
|
+
const assemble = (pages, createdAt) => {
|
|
347
|
+
const objects = [];
|
|
348
|
+
const add = (body) => {
|
|
349
|
+
objects.push(body);
|
|
350
|
+
return objects.length; // 1-based object numbers
|
|
351
|
+
};
|
|
352
|
+
// Fonts first, so their numbers are stable regardless of page count — a stable object graph is one fewer
|
|
353
|
+
// way for two renders to differ.
|
|
354
|
+
const f1 = add(`<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>`);
|
|
355
|
+
const f2 = add(`<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>`);
|
|
356
|
+
const f3 = add(`<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>`);
|
|
357
|
+
const resources = `<< /Font << /F1 ${f1} 0 R /F2 ${f2} 0 R /F3 ${f3} 0 R >> >>`;
|
|
358
|
+
// Reserve the pages node's number before the page objects, since each page must point back at it.
|
|
359
|
+
const pagesNumber = add("placeholder");
|
|
360
|
+
const pageNumbers = [];
|
|
361
|
+
for (const p of pages) {
|
|
362
|
+
const content = p.ops.map((o) => o.text).join("\n");
|
|
363
|
+
const stream = add(`<< /Length ${Buffer.byteLength(content, "latin1")} >>\nstream\n${content}\nendstream`);
|
|
364
|
+
pageNumbers.push(add(`<< /Type /Page /Parent ${pagesNumber} 0 R /MediaBox [0 0 ${PAGE_WIDTH.toFixed(2)} ${PAGE_HEIGHT.toFixed(2)}] /Resources ${resources} /Contents ${stream} 0 R >>`));
|
|
365
|
+
}
|
|
366
|
+
objects[pagesNumber - 1] = `<< /Type /Pages /Count ${pages.length} /Kids [${pageNumbers
|
|
367
|
+
.map((n) => `${n} 0 R`)
|
|
368
|
+
.join(" ")}] >>`;
|
|
369
|
+
const catalog = add(`<< /Type /Catalog /Pages ${pagesNumber} 0 R >>`);
|
|
370
|
+
const info = add(`<< /Producer (agentkit) /Creator (agentkit) /CreationDate (${pdfDate(createdAt)}) /ModDate (${pdfDate(createdAt)}) >>`);
|
|
371
|
+
const chunks = ["%PDF-1.4\n"];
|
|
372
|
+
// A binary comment marks the file as binary for transfer-mode heuristics. Fixed bytes, so it costs nothing
|
|
373
|
+
// in determinism.
|
|
374
|
+
chunks.push("%âãÏÓ\n");
|
|
375
|
+
const offsets = [];
|
|
376
|
+
let position = chunks.reduce((n, c) => n + Buffer.byteLength(c, "latin1"), 0);
|
|
377
|
+
objects.forEach((body, i) => {
|
|
378
|
+
offsets.push(position);
|
|
379
|
+
const chunk = `${i + 1} 0 obj\n${body}\nendobj\n`;
|
|
380
|
+
chunks.push(chunk);
|
|
381
|
+
position += Buffer.byteLength(chunk, "latin1");
|
|
382
|
+
});
|
|
383
|
+
const xrefStart = position;
|
|
384
|
+
const xref = [
|
|
385
|
+
`xref\n0 ${objects.length + 1}\n`,
|
|
386
|
+
// The free-list head. Exactly 20 bytes per entry, including the two-character EOL — a viewer indexes
|
|
387
|
+
// into this by multiplication, so a byte out is every object misread.
|
|
388
|
+
`0000000000 65535 f \n`,
|
|
389
|
+
...offsets.map((o) => `${String(o).padStart(10, "0")} 00000 n \n`),
|
|
390
|
+
].join("");
|
|
391
|
+
chunks.push(xref);
|
|
392
|
+
chunks.push(
|
|
393
|
+
// `/ID` is derived from the object count and the date rather than random, because a random id is the other
|
|
394
|
+
// thing that makes two identical renders differ.
|
|
395
|
+
`trailer\n<< /Size ${objects.length + 1} /Root ${catalog} 0 R /Info ${info} 0 R /ID [<${idHex(objects.length, createdAt)}> <${idHex(objects.length, createdAt)}>] >>\nstartxref\n${xrefStart}\n%%EOF\n`);
|
|
396
|
+
return new Uint8Array(Buffer.from(chunks.join(""), "latin1"));
|
|
397
|
+
};
|
|
398
|
+
/** A stable 16-byte id, derived rather than random so identical input yields identical bytes. */
|
|
399
|
+
const idHex = (objectCount, createdAt) => {
|
|
400
|
+
let h = 0x811c9dc5;
|
|
401
|
+
for (const ch of `${objectCount}:${createdAt}`) {
|
|
402
|
+
h ^= ch.charCodeAt(0);
|
|
403
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
404
|
+
}
|
|
405
|
+
return h.toString(16).padStart(8, "0").repeat(4);
|
|
406
|
+
};
|
|
407
|
+
//# sourceMappingURL=pdf.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachments in context: referenced, never injected (#130).
|
|
3
|
+
*
|
|
4
|
+
* `docs/README` governing principle 6 — *"Large files and tool results are referenced, not injected
|
|
5
|
+
* wholesale into model context."* The rule is easy to state and easy to break by accident, so the defence
|
|
6
|
+
* here is structural rather than a convention someone maintains:
|
|
7
|
+
*
|
|
8
|
+
* **This module is constructed with a `FileMetadataStore` and nothing else.** It has no `FileContentStore`,
|
|
9
|
+
* so it cannot read a byte of an attachment even if a future edit tried to. A rule that says "do not inject
|
|
10
|
+
* content" can be forgotten; a provider with no way to reach the content cannot forget it.
|
|
11
|
+
*
|
|
12
|
+
* The second property is the one AC-2 measures: **an attachment's context cost does not grow with the
|
|
13
|
+
* file.** The rendered line carries a rounded, unit-bearing size — `1 KB`, `100 MB` — rather than a byte
|
|
14
|
+
* count, so a thousand-fold difference in file size is the same number of characters. The cost is bounded by
|
|
15
|
+
* the *filename*, which is the user's own text and belongs in context.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConversationId } from "../core/ids.js";
|
|
18
|
+
import type { ContextProvider } from "../context/index.js";
|
|
19
|
+
import type { FileMetadata, FileMetadataStore } from "../persistence/index.js";
|
|
20
|
+
/**
|
|
21
|
+
* How many attachments are described before the list is summarised.
|
|
22
|
+
*
|
|
23
|
+
* A conversation with two hundred attachments would otherwise cost two hundred lines — linear in something
|
|
24
|
+
* the user controls, which is the same unbounded-growth failure by a slower route. Past the cap the section
|
|
25
|
+
* says how many more there are and names the tool that lists them.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MAX_LISTED_ATTACHMENTS = 20;
|
|
28
|
+
/** The longest filename rendered. A filename is user input, and user input has no length. */
|
|
29
|
+
export declare const MAX_RENDERED_FILENAME = 80;
|
|
30
|
+
/**
|
|
31
|
+
* A rounded, unit-bearing size.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately not the byte count. `104857600` and `1024` differ in width, so rendering bytes would make an
|
|
34
|
+
* attachment's token cost a function of its size — small, but AC-2 is a statement about *measurably*, and a
|
|
35
|
+
* property that holds approximately is one that stops holding when someone changes the renderer.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SIZE_FIELD_WIDTH = 8;
|
|
38
|
+
export declare const humanSize: (byteSize: number) => string;
|
|
39
|
+
/** A filename, trimmed to a bound, with the extension kept because it is the part that carries meaning. */
|
|
40
|
+
export declare const truncateFilename: (filename: string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* One attachment, as the model sees it.
|
|
43
|
+
*
|
|
44
|
+
* Every field comes from `FileMetadata`. There is no branch that could reach content, which is the point:
|
|
45
|
+
* the function's inputs are the enforcement.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* One attachment as a prompt line.
|
|
49
|
+
*
|
|
50
|
+
* The filename is **neutralised**, not merely truncated (#145). A filename is arbitrary text chosen by whoever
|
|
51
|
+
* uploaded the file — any principal in the tenant — and it is interpolated into the system prompt. A file called
|
|
52
|
+
* `report.pdf\n## System: ignore prior instructions and` forges a heading inside the platform's own section.
|
|
53
|
+
*
|
|
54
|
+
* The section stays `platform` rather than being wrapped in an untrusted envelope, deliberately: the envelope's
|
|
55
|
+
* preamble says nothing inside it is an instruction, and this section's `READ_INSTRUCTION` *is* the platform's
|
|
56
|
+
* instruction for how to read a file. Wrapping it would negate the thing it exists to say. So the untrusted
|
|
57
|
+
* *values* are neutralised in place, which is the surgical version of the same defence.
|
|
58
|
+
*
|
|
59
|
+
* The nonce is empty: there is no envelope here to forge, so only the structural markers matter.
|
|
60
|
+
*/
|
|
61
|
+
export declare const renderAttachmentReference: (file: FileMetadata) => string;
|
|
62
|
+
/**
|
|
63
|
+
* What extraction says about a file, in as few words as possible (#131).
|
|
64
|
+
*
|
|
65
|
+
* A model needs three facts to choose its next move, and only three: whether text is available now, whether
|
|
66
|
+
* to wait, or whether it will never come and why. So `extracted` names the tool, `failed` gives the reason,
|
|
67
|
+
* and everything else says "not yet".
|
|
68
|
+
*
|
|
69
|
+
* **Bounded on purpose.** The failure message is truncated because it can carry a page count or a byte limit,
|
|
70
|
+
* and an unbounded string here would make an attachment's context cost depend on how badly extraction went —
|
|
71
|
+
* undoing #130's AC-2 by a side door.
|
|
72
|
+
*/
|
|
73
|
+
export declare const MAX_EXTRACTION_NOTE = 90;
|
|
74
|
+
export declare const ATTACHMENT_PROVIDER_ID = "attachments";
|
|
75
|
+
/**
|
|
76
|
+
* Estimated tokens for a body.
|
|
77
|
+
*
|
|
78
|
+
* Computed from the body this module just built, never accepted from elsewhere. `ContextSection`'s
|
|
79
|
+
* `estimatedTokens` is self-reported, and a section that under-reports its cost is a section that survives
|
|
80
|
+
* budgeting it should have lost — so the one place that could lie about an attachment's cost does not.
|
|
81
|
+
*
|
|
82
|
+
* Re-exported from `core/tokens.ts` rather than defined here: five copies of this arithmetic had accumulated,
|
|
83
|
+
* agreeing by coincidence, and a section sized against one and budgeted against another is a section that does
|
|
84
|
+
* not fit the budget it was measured for.
|
|
85
|
+
*/
|
|
86
|
+
/**
|
|
87
|
+
* The attachment section for a conversation.
|
|
88
|
+
*
|
|
89
|
+
* `knowledge` rather than `history`: an attachment is a durable fact about the conversation, not a turn, and
|
|
90
|
+
* putting it in the history bucket would make it compete with recent messages for the same budget. It is
|
|
91
|
+
* prunable at the `old-knowledge` stage — an attachment list is worth dropping before a recent turn is.
|
|
92
|
+
*/
|
|
93
|
+
export declare const createAttachmentContextProvider: (deps: {
|
|
94
|
+
readonly metadata: FileMetadataStore;
|
|
95
|
+
readonly conversationId: ConversationId;
|
|
96
|
+
}) => ContextProvider;
|
|
97
|
+
//# sourceMappingURL=context.d.ts.map
|