@byollm/protocol 0.1.2 → 0.1.4
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/README.md +1 -1
- package/dist/index.d.ts +60 -2
- package/dist/index.js +77 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- release-note 0.1.1 -->
|
|
2
|
-
> **`0.1.
|
|
2
|
+
> **`0.1.4` is documentation only — nothing to upgrade.** `PROTOCOL_VERSION`
|
|
3
3
|
> is still `2`, so a 0.1.0 party and a 0.1.1 party talk to each other in both
|
|
4
4
|
> directions. No dependency, schema, wire field or on-disk shape moved.
|
|
5
5
|
>
|
package/dist/index.d.ts
CHANGED
|
@@ -45,7 +45,16 @@ export { C as CONSOLE_FRAME_VERSION, a as CONSOLE_MAX_DATA_BYTES, b as ConsoleBy
|
|
|
45
45
|
* unchanged by the move.
|
|
46
46
|
*/
|
|
47
47
|
/**
|
|
48
|
-
* The hub fence, verbatim — ruled by Todd, 2026-
|
|
48
|
+
* The hub fence, verbatim — ruled by Todd, 2026-10-22 (#4058), replacing the
|
|
49
|
+
* 2026-09-04 wording.
|
|
50
|
+
*
|
|
51
|
+
* It says what the hub sees and stops there. The old sentence opened "sees
|
|
52
|
+
* only what routing and metering need", and that opening is why the sentence
|
|
53
|
+
* was ruled three times: a justification fails every new fact that serves
|
|
54
|
+
* neither — the device that did each job is kept for the team card, not for
|
|
55
|
+
* routing (which has it in Valkey) or billing (unchanged). Without it, the
|
|
56
|
+
* fence is the mapping below: a column that fits a clause is a release, and
|
|
57
|
+
* one that fits none is a ruling.
|
|
49
58
|
*
|
|
50
59
|
* Asserted whole rather than by keyword wherever it is checked: "close
|
|
51
60
|
* enough" is not a standard a page about what we can see gets to use, and a
|
|
@@ -976,6 +985,55 @@ declare const ABOUT_SHORT_LEDE = "BYOLLM \u2013 Bring Your Own LLM \u2013 lets y
|
|
|
976
985
|
declare const ABOUT_SHORT_TAIL = "Sites can charge you less because you bring your own \u2013 see why that matters \u2192. Teams can optionally share the free or metered services on their devices with people they name. Personal subscriptions are never shared.";
|
|
977
986
|
/** Both halves, for a surface that wants the paragraph entire. */
|
|
978
987
|
declare const ABOUT_SHORT = "BYOLLM \u2013 Bring Your Own LLM \u2013 lets you use your own AI on websites you authorize. A small program installed on your machine lets you use your own models and subscriptions on any BYOLLM-integrated site, including new models the moment you get access \u2013 no site updates required. BYOLLM Cloud connects sites to your devices with end-to-end encryption, so no one, including us, can see your data.\n\nSites can charge you less because you bring your own \u2013 see why that matters \u2192. Teams can optionally share the free or metered services on their devices with people they name. Personal subscriptions are never shared.";
|
|
988
|
+
/** Who a prompt is for, which is also which path it sets up. */
|
|
989
|
+
type AgentPromptAudience = "user" | "developer" | "operator";
|
|
990
|
+
/** One "paste this into your agent" prompt. */
|
|
991
|
+
interface AgentPrompt {
|
|
992
|
+
/** Stable, for anchors: `agent-<id>` on byo-llm.com. */
|
|
993
|
+
readonly id: string;
|
|
994
|
+
readonly title: string;
|
|
995
|
+
readonly audience: AgentPromptAudience;
|
|
996
|
+
/** Verbatim, line breaks included — what the copy button copies. */
|
|
997
|
+
readonly prompt: string;
|
|
998
|
+
}
|
|
999
|
+
/** The heading every surface shows above the prompts. */
|
|
1000
|
+
declare const AGENT_PROMPTS_HEADING = "You already have an AI. Hand it this.";
|
|
1001
|
+
/** The one line under it. */
|
|
1002
|
+
declare const AGENT_PROMPTS_LEDE = "Paste the prompt into whatever you use \u2014 Claude, ChatGPT, Codex, Cursor, a local model \u2014 and it does the setup, stopping where a step needs you.";
|
|
1003
|
+
/** Set up my computer, add BYOLLM to my site, run my own relay — in that order. */
|
|
1004
|
+
declare const AGENT_PROMPTS: readonly AgentPrompt[];
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* What BYOLLM does not do yet — one list, rendered on every surface.
|
|
1008
|
+
*
|
|
1009
|
+
* Todd, 2026-10-06: the sites did not call out the limits the way the
|
|
1010
|
+
* open-sourcing post did. The post's sentence is the source of the words; this
|
|
1011
|
+
* is the source of the list. The README's "Not yet", byo-llm.com's "What it
|
|
1012
|
+
* doesn't do yet" and byollm.cloud render from here, and a test on each
|
|
1013
|
+
* surface fails when its copy and this disagree.
|
|
1014
|
+
*
|
|
1015
|
+
* Hand-written beside `about.ts` rather than inside it, because that file is
|
|
1016
|
+
* generated from the ABOUT markdown and this one derives from code.
|
|
1017
|
+
*/
|
|
1018
|
+
interface NotYet {
|
|
1019
|
+
/** The missing thing, phrased so "no …" reads as a sentence. */
|
|
1020
|
+
readonly what: string;
|
|
1021
|
+
/** What that means for you, when the name alone does not say. */
|
|
1022
|
+
readonly detail?: string;
|
|
1023
|
+
}
|
|
1024
|
+
/** What BYOLLM does not do yet, in the post's order. */
|
|
1025
|
+
declare const NOT_YET: readonly NotYet[];
|
|
1026
|
+
declare const NOT_YET_LEDE = "Things we know are limiting right now:";
|
|
1027
|
+
declare const NOT_YET_TAIL = "We have plans for every one of these, and we are building in public.";
|
|
1028
|
+
/** The list as one paragraph — the post's sentence, from the list. */
|
|
1029
|
+
declare function notYetSentence(list?: readonly NotYet[]): string;
|
|
1030
|
+
/**
|
|
1031
|
+
* One entry as a list line: "Streaming — a result comes back whole".
|
|
1032
|
+
*
|
|
1033
|
+
* Every list surface renders this, so the README, byo-llm.com and
|
|
1034
|
+
* byollm.cloud cannot each phrase an entry their own way.
|
|
1035
|
+
*/
|
|
1036
|
+
declare function notYetLine({ what, detail }: NotYet): string;
|
|
979
1037
|
|
|
980
1038
|
/**
|
|
981
1039
|
* Its members, for anything that has to report what it accepts.
|
|
@@ -2984,4 +3042,4 @@ declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_C
|
|
|
2984
3042
|
/** Every MUST verified a particular way. */
|
|
2985
3043
|
declare function mustsVerifiedBy(kind: MustVerification): MustId[];
|
|
2986
3044
|
|
|
2987
|
-
export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DEFAULT_ROUTING, DeliveredResult, ENDPOINTS, ERROR_STATUS, type Endpoint, type FenceClause, FetchRequest, FetchResponse, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HUB_FENCE, HUB_FENCE_CLAUSES, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, Purpose, PurposeRouting, REFUSAL_MESSAGES, REFUSAL_TEXT, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, type StopReason, StopReasonSchema, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, UPGRADE_COMMAND, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, declaredVersion, describeBytes, effectiveOfferScope, envelopeBytes, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, kindsOf, matchAudience, mayOfferUpdate, mentionsWireField, mustsVerifiedBy, payloadTextLength, provenanceFor, resolveCost, routingOf, signGrant, signRequest, signSiteRequest, signSuccession, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, tooLargeMessage, updateOfferFor, verifyGrant, verifyLink, verifyRequest, verifySiteRequest, walkSuccession, withoutComments };
|
|
3045
|
+
export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AGENT_PROMPTS, AGENT_PROMPTS_HEADING, AGENT_PROMPTS_LEDE, AUDIENCES, type AgentPrompt, type AgentPromptAudience, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DEFAULT_ROUTING, DeliveredResult, ENDPOINTS, ERROR_STATUS, type Endpoint, type FenceClause, FetchRequest, FetchResponse, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HUB_FENCE, HUB_FENCE_CLAUSES, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, NOT_YET, NOT_YET_LEDE, NOT_YET_TAIL, type NotYet, OFFER_SCOPES, OfferScope, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, Purpose, PurposeRouting, REFUSAL_MESSAGES, REFUSAL_TEXT, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, type StopReason, StopReasonSchema, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, UPGRADE_COMMAND, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, declaredVersion, describeBytes, effectiveOfferScope, envelopeBytes, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, kindsOf, matchAudience, mayOfferUpdate, mentionsWireField, mustsVerifiedBy, notYetLine, notYetSentence, payloadTextLength, provenanceFor, resolveCost, routingOf, signGrant, signRequest, signSiteRequest, signSuccession, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, tooLargeMessage, updateOfferFor, verifyGrant, verifyLink, verifyRequest, verifySiteRequest, walkSuccession, withoutComments };
|
package/dist/index.js
CHANGED
|
@@ -22,18 +22,29 @@ import {
|
|
|
22
22
|
} from "./chunk-J3HTAGMX.js";
|
|
23
23
|
|
|
24
24
|
// src/hub-fence.ts
|
|
25
|
-
var HUB_FENCE = "byollm.cloud sees
|
|
25
|
+
var HUB_FENCE = "byollm.cloud sees who asked, which devices and sites are connected, which device did each job, and each job's kind, size, outcome and time. Never prompts, never answers, never credentials.";
|
|
26
26
|
var HUB_FENCE_CLAUSES = Object.freeze([
|
|
27
27
|
{
|
|
28
|
-
says: "
|
|
29
|
-
covers: Object.freeze(["owner_id", "
|
|
28
|
+
says: "who asked",
|
|
29
|
+
covers: Object.freeze(["owner_id", "member_id"])
|
|
30
30
|
},
|
|
31
|
+
/*
|
|
32
|
+
* The pair row's device owner — the hub's one row per (requester, device
|
|
33
|
+
* owner), #4058's shape (c). Ruled into "devices and sites"; the clause runs
|
|
34
|
+
* on through "which device did each job" because that is the phrase the
|
|
35
|
+
* column makes true, and a clause that stopped short would map the column
|
|
36
|
+
* to words that do not mention it.
|
|
37
|
+
*/
|
|
31
38
|
{
|
|
32
|
-
says: "
|
|
39
|
+
says: "which devices and sites are connected, which device did each job",
|
|
40
|
+
covers: Object.freeze(["site_id", "served_by_owner_id"])
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
says: "each job's kind, size, outcome",
|
|
33
44
|
covers: Object.freeze(["kind", "size_class", "disposition", "bytes"])
|
|
34
45
|
},
|
|
35
46
|
{
|
|
36
|
-
says: "and
|
|
47
|
+
says: "and time",
|
|
37
48
|
covers: Object.freeze(["at", "month", "updated_at"])
|
|
38
49
|
},
|
|
39
50
|
/*
|
|
@@ -2157,6 +2168,59 @@ var ABOUT_SHORT_TAIL = "Sites can charge you less because you bring your own \u2
|
|
|
2157
2168
|
var ABOUT_SHORT = `${ABOUT_SHORT_LEDE}
|
|
2158
2169
|
|
|
2159
2170
|
${ABOUT_SHORT_TAIL}`;
|
|
2171
|
+
var AGENT_PROMPTS_HEADING = "You already have an AI. Hand it this.";
|
|
2172
|
+
var AGENT_PROMPTS_LEDE = "Paste the prompt into whatever you use \u2014 Claude, ChatGPT, Codex, Cursor, a local model \u2014 and it does the setup, stopping where a step needs you.";
|
|
2173
|
+
var AGENT_PROMPTS = [
|
|
2174
|
+
{
|
|
2175
|
+
"id": "computer",
|
|
2176
|
+
"title": "Set up my computer",
|
|
2177
|
+
"audience": "user",
|
|
2178
|
+
"prompt": "Set up BYOLLM on my computer so websites I approve can use the AI I already\nhave. Some steps need me at the keyboard: tell me exactly what to type, then\nwait. First I open Terminal (Mac) or PowerShell (Windows). Run\n`node --version`; if it fails or is below 22.14, I install the LTS version\nof Node.js \u2014 the free program BYOLLM runs on \u2014 from https://nodejs.org and\nreopen the window. Then run `npm install -g byollm@latest`. Then I run\n`byollm setup` myself, because it asks questions: it finds what I have\n(Ollama, MLX or llama.cpp here, or my Claude Pro/Max or Codex sign-in \u2014\nsubscriptions stay mine, never shared) and pairs with byollm.cloud. When it\nprints a pairing code and fingerprint, STOP: I approve it at\nhttps://dashboard.byollm.cloud/devices and check the fingerprint matches.\nThen run `byollm status` and `byollm services`, fix anything they name, and\n`byollm start` if it is not running. Finish by having me press Connect at\nhttps://test.byollm.cloud. Reference:\nhttps://docs.byollm.cloud/#what-is-byollm and\nhttps://docs.byollm.cloud/#keep-daemon-running. Don't guess commands:\n`byollm --help` is the authority."
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
"id": "site",
|
|
2182
|
+
"title": "Add BYOLLM to my site",
|
|
2183
|
+
"audience": "developer",
|
|
2184
|
+
"prompt": 'Add BYOLLM to this project so my users bring their own AI. Install\n`@byollm/server`, generate site keys once with\n`npx --package @byollm/server keygen` and put them in env (never in the\nrepo), mount the handler at `/byollm` (not under `/api` \u2014 the daemon pairs\nwith an origin), and add one `enqueue` with a timeout and a no-runner path \u2014\nnever a bare await. Then register the site at https://dashboard.byollm.cloud\nand add the Connect button exactly as https://docs.byollm.cloud/#embed says:\nthe iframe gets `site`, `state` and `return`, the page posts\n`{ byollm: "hello" }` first, and the return page posts\n`{ byollm: "connected" }` and closes. Job states and why an answer stopped:\nhttps://docs.byollm.cloud/#job-states and\nhttps://docs.byollm.cloud/#stop-reason. Test against\nhttps://test.byollm.cloud\'s flow before you tell me it works. STOP and ask\nme for anything that needs my dashboard login.'
|
|
2185
|
+
},
|
|
2186
|
+
{
|
|
2187
|
+
"id": "relay",
|
|
2188
|
+
"title": "Run my own relay",
|
|
2189
|
+
"audience": "operator",
|
|
2190
|
+
"prompt": "Stand up my own BYOLLM relay instead of byollm.cloud. Use `@byollm/relay`\nand `@byollm/control-plane` from npm \u2014 one replica on the built-in memory\nstore, or give the relay a shared `RoutingStore` to run more \u2014 and prove it\nwith `@byollm/conformance`\n(`npx --package @byollm/conformance byollm-certify <target>`) before\npointing any daemon at it: a server is byollm-compatible when the kit\npasses. Then pair a daemon with `byollm connect <my relay origin>`. The\nrelay never sees prompts (https://docs.byollm.cloud/#what-we-see) and that\nmust stay true in my deployment. Reference:\nhttps://github.com/oftomorrowinc/byollm#packages. STOP and ask me before\nanything that costs money or opens a port to the internet."
|
|
2191
|
+
}
|
|
2192
|
+
];
|
|
2193
|
+
|
|
2194
|
+
// src/not-yet.ts
|
|
2195
|
+
var and = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.slice(-1).join("")}`;
|
|
2196
|
+
function notYetFor(kinds) {
|
|
2197
|
+
return Object.freeze([
|
|
2198
|
+
{ what: "tool use" },
|
|
2199
|
+
{ what: "access to your local files" },
|
|
2200
|
+
{ what: "streaming", detail: "a result comes back whole" },
|
|
2201
|
+
{
|
|
2202
|
+
what: `job kinds beyond ${and(kinds)}`,
|
|
2203
|
+
detail: "no embeddings or images yet"
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
what: "jobs while your device is offline",
|
|
2207
|
+
detail: "your device has to be online for your jobs to run"
|
|
2208
|
+
}
|
|
2209
|
+
]);
|
|
2210
|
+
}
|
|
2211
|
+
var NOT_YET = notYetFor(JobKind.options);
|
|
2212
|
+
var NOT_YET_LEDE = "Things we know are limiting right now:";
|
|
2213
|
+
var NOT_YET_TAIL = "We have plans for every one of these, and we are building in public.";
|
|
2214
|
+
function notYetSentence(list = NOT_YET) {
|
|
2215
|
+
const items = list.map(
|
|
2216
|
+
({ what, detail }) => `no ${what}${detail ? ` (${detail})` : ""}`
|
|
2217
|
+
);
|
|
2218
|
+
return `${NOT_YET_LEDE} ${and(items)}. ${NOT_YET_TAIL}`;
|
|
2219
|
+
}
|
|
2220
|
+
function notYetLine({ what, detail }) {
|
|
2221
|
+
const head = what.charAt(0).toUpperCase() + what.slice(1);
|
|
2222
|
+
return detail ? `${head} \u2014 ${detail}` : head;
|
|
2223
|
+
}
|
|
2160
2224
|
|
|
2161
2225
|
// src/signing.ts
|
|
2162
2226
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -2729,6 +2793,9 @@ export {
|
|
|
2729
2793
|
ABOUT_SHORT,
|
|
2730
2794
|
ABOUT_SHORT_LEDE,
|
|
2731
2795
|
ABOUT_SHORT_TAIL,
|
|
2796
|
+
AGENT_PROMPTS,
|
|
2797
|
+
AGENT_PROMPTS_HEADING,
|
|
2798
|
+
AGENT_PROMPTS_LEDE,
|
|
2732
2799
|
AUDIENCES,
|
|
2733
2800
|
Audience,
|
|
2734
2801
|
BACKENDS,
|
|
@@ -2795,6 +2862,9 @@ export {
|
|
|
2795
2862
|
MUST_IDS,
|
|
2796
2863
|
Manifest,
|
|
2797
2864
|
MatchRefusal,
|
|
2865
|
+
NOT_YET,
|
|
2866
|
+
NOT_YET_LEDE,
|
|
2867
|
+
NOT_YET_TAIL,
|
|
2798
2868
|
OFFER_SCOPES,
|
|
2799
2869
|
OfferScope,
|
|
2800
2870
|
PAYLOAD_LIMITS,
|
|
@@ -2874,6 +2944,8 @@ export {
|
|
|
2874
2944
|
mayOfferUpdate,
|
|
2875
2945
|
mentionsWireField,
|
|
2876
2946
|
mustsVerifiedBy,
|
|
2947
|
+
notYetLine,
|
|
2948
|
+
notYetSentence,
|
|
2877
2949
|
open,
|
|
2878
2950
|
payloadTextLength,
|
|
2879
2951
|
provenanceFor,
|