@pdpp/local-collector 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +48 -0
  2. package/dist/local-collector/bin/pdpp-local-collector.js +347 -0
  3. package/dist/local-collector/src/errors.d.ts +12 -0
  4. package/dist/local-collector/src/errors.js +20 -0
  5. package/dist/local-collector/src/runner.d.ts +16 -0
  6. package/dist/local-collector/src/runner.js +59 -0
  7. package/dist/polyfill-connectors/connectors/claude_code/index.js +806 -0
  8. package/dist/polyfill-connectors/connectors/claude_code/parsers.js +224 -0
  9. package/dist/polyfill-connectors/connectors/claude_code/schemas.js +120 -0
  10. package/dist/polyfill-connectors/connectors/claude_code/types.js +1 -0
  11. package/dist/polyfill-connectors/connectors/codex/index.js +880 -0
  12. package/dist/polyfill-connectors/connectors/codex/parsers.js +159 -0
  13. package/dist/polyfill-connectors/connectors/codex/schemas.js +118 -0
  14. package/dist/polyfill-connectors/connectors/codex/types.js +1 -0
  15. package/dist/polyfill-connectors/src/auth.js +76 -0
  16. package/dist/polyfill-connectors/src/browser-handoff.js +197 -0
  17. package/dist/polyfill-connectors/src/collector-protocol.d.ts +2 -0
  18. package/dist/polyfill-connectors/src/collector-protocol.js +2 -0
  19. package/dist/polyfill-connectors/src/collector-runner.d.ts +139 -0
  20. package/dist/polyfill-connectors/src/collector-runner.js +1084 -0
  21. package/dist/polyfill-connectors/src/connector-runtime-protocol.d.ts +191 -0
  22. package/dist/polyfill-connectors/src/connector-runtime-protocol.js +1 -0
  23. package/dist/polyfill-connectors/src/connector-runtime.js +879 -0
  24. package/dist/polyfill-connectors/src/fixture-capture.js +237 -0
  25. package/dist/polyfill-connectors/src/is-main-module.d.ts +1 -0
  26. package/dist/polyfill-connectors/src/is-main-module.js +17 -0
  27. package/dist/polyfill-connectors/src/local-device-client.d.ts +126 -0
  28. package/dist/polyfill-connectors/src/local-device-client.js +132 -0
  29. package/dist/polyfill-connectors/src/local-device-envelope.d.ts +26 -0
  30. package/dist/polyfill-connectors/src/local-device-envelope.js +43 -0
  31. package/dist/polyfill-connectors/src/local-device-outbox.d.ts +115 -0
  32. package/dist/polyfill-connectors/src/local-device-outbox.js +509 -0
  33. package/dist/polyfill-connectors/src/local-device-queue.d.ts +34 -0
  34. package/dist/polyfill-connectors/src/local-device-queue.js +133 -0
  35. package/dist/polyfill-connectors/src/local-source-inventory.js +119 -0
  36. package/dist/polyfill-connectors/src/pdpp-safe-text.js +13 -0
  37. package/dist/polyfill-connectors/src/runner/index.d.ts +11 -0
  38. package/dist/polyfill-connectors/src/runner/index.js +10 -0
  39. package/dist/polyfill-connectors/src/runtime-capabilities.d.ts +40 -0
  40. package/dist/polyfill-connectors/src/runtime-capabilities.js +59 -0
  41. package/dist/polyfill-connectors/src/safe-emit.d.ts +3 -0
  42. package/dist/polyfill-connectors/src/safe-emit.js +30 -0
  43. package/dist/polyfill-connectors/src/safe-text-preview.js +156 -0
  44. package/dist/polyfill-connectors/src/schema-registry.js +17 -0
  45. package/dist/polyfill-connectors/src/scope-filters.d.ts +38 -0
  46. package/dist/polyfill-connectors/src/scope-filters.js +80 -0
  47. package/dist/polyfill-connectors/src/shutdown-hook.js +51 -0
  48. package/dist/polyfill-connectors/src/streaming-target-registration.js +161 -0
  49. package/package.json +63 -0
@@ -0,0 +1,133 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export class LocalDeviceQueue {
4
+ #clock;
5
+ #path;
6
+ #retryBackoffMs;
7
+ constructor(options) {
8
+ this.#clock = options.clock ?? (() => new Date());
9
+ this.#path = options.path;
10
+ this.#retryBackoffMs = options.retryBackoffMs ?? defaultRetryBackoffMs;
11
+ }
12
+ async enqueue(input) {
13
+ if (input.records.length === 0) {
14
+ throw new Error("cannot enqueue an empty local device batch");
15
+ }
16
+ const items = await this.#readItems();
17
+ if (items.some((item) => item.batch_id === input.batchId)) {
18
+ throw new Error(`local device batch already queued: ${input.batchId}`);
19
+ }
20
+ const now = this.#clock().toISOString();
21
+ const item = {
22
+ available_at: now,
23
+ batch_id: input.batchId,
24
+ batch_seq: input.batchSeq,
25
+ created_at: now,
26
+ records: input.records,
27
+ retry_count: 0,
28
+ source_instance_id: input.sourceInstanceId,
29
+ status: "pending",
30
+ updated_at: now,
31
+ };
32
+ await this.#writeItems([...items, item]);
33
+ return item;
34
+ }
35
+ async dequeueReady() {
36
+ const now = this.#clock().toISOString();
37
+ const items = await this.#readItems();
38
+ const index = items.findIndex((item) => isReadyHeadOfSource(item, items, now));
39
+ if (index < 0) {
40
+ return null;
41
+ }
42
+ const item = items[index];
43
+ if (!item) {
44
+ return null;
45
+ }
46
+ const updated = { ...item, status: "in_flight", updated_at: now };
47
+ items[index] = updated;
48
+ await this.#writeItems(items);
49
+ return updated;
50
+ }
51
+ async markSent(batchId) {
52
+ await this.#updateItem(batchId, (item, now) => ({ ...item, status: "sent", updated_at: now }));
53
+ }
54
+ async markRetry(batchId, error) {
55
+ await this.#updateItem(batchId, (item, now) => {
56
+ const retryCount = item.retry_count + 1;
57
+ return {
58
+ ...item,
59
+ available_at: new Date(this.#clock().getTime() + this.#retryBackoffMs(retryCount)).toISOString(),
60
+ last_error: error,
61
+ retry_count: retryCount,
62
+ status: "pending",
63
+ updated_at: now,
64
+ };
65
+ });
66
+ }
67
+ async markPermanentFailure(batchId, error) {
68
+ await this.#updateItem(batchId, (item, now) => ({
69
+ ...item,
70
+ last_error: error,
71
+ status: "permanent_failure",
72
+ updated_at: now,
73
+ }));
74
+ }
75
+ async list() {
76
+ return await this.#readItems();
77
+ }
78
+ async #updateItem(batchId, update) {
79
+ const items = await this.#readItems();
80
+ const index = items.findIndex((item) => item.batch_id === batchId);
81
+ const item = items[index];
82
+ if (!item) {
83
+ throw new Error(`local device batch not found: ${batchId}`);
84
+ }
85
+ items[index] = update(item, this.#clock().toISOString());
86
+ await this.#writeItems(items);
87
+ }
88
+ async #readItems() {
89
+ try {
90
+ const raw = await readFile(this.#path, "utf8");
91
+ if (!raw.trim()) {
92
+ return [];
93
+ }
94
+ const parsed = JSON.parse(raw);
95
+ return [...(parsed.items ?? [])].sort(compareQueueItems);
96
+ }
97
+ catch (error) {
98
+ if (isNotFoundError(error)) {
99
+ return [];
100
+ }
101
+ throw error;
102
+ }
103
+ }
104
+ async #writeItems(items) {
105
+ await mkdir(dirname(this.#path), { recursive: true });
106
+ const ordered = [...items].sort(compareQueueItems);
107
+ const tmpPath = `${this.#path}.tmp`;
108
+ await writeFile(tmpPath, `${JSON.stringify({ items: ordered }, null, 2)}\n`);
109
+ await rename(tmpPath, this.#path);
110
+ }
111
+ }
112
+ function compareQueueItems(a, b) {
113
+ const source = a.source_instance_id.localeCompare(b.source_instance_id);
114
+ if (source !== 0) {
115
+ return source;
116
+ }
117
+ return a.batch_seq - b.batch_seq;
118
+ }
119
+ function isReadyHeadOfSource(item, items, now) {
120
+ if (item.status !== "pending" || item.available_at > now) {
121
+ return false;
122
+ }
123
+ return !items.some((other) => other.source_instance_id === item.source_instance_id &&
124
+ other.batch_seq < item.batch_seq &&
125
+ other.status !== "sent" &&
126
+ other.status !== "permanent_failure");
127
+ }
128
+ function defaultRetryBackoffMs(retryCount) {
129
+ return Math.min(60_000, 1000 * 2 ** Math.max(0, retryCount - 1));
130
+ }
131
+ function isNotFoundError(error) {
132
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
133
+ }
@@ -0,0 +1,119 @@
1
+ import { createHash } from "node:crypto";
2
+ import { statSync } from "node:fs";
3
+ import { readdir, stat } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ function pathHash(tool, relativePath) {
6
+ return createHash("sha256").update(`${tool}:${relativePath}`).digest("hex");
7
+ }
8
+ function inventoryEntryType(st) {
9
+ if (st.isDirectory()) {
10
+ return "directory";
11
+ }
12
+ if (st.isFile()) {
13
+ return "file";
14
+ }
15
+ return "other";
16
+ }
17
+ function coverageStatus(classification, exists) {
18
+ if (!exists) {
19
+ return "missing";
20
+ }
21
+ if (classification === "collect") {
22
+ return "collected";
23
+ }
24
+ if (classification === "inventory_only") {
25
+ return "inventory_only";
26
+ }
27
+ if (classification === "exclude") {
28
+ return "excluded";
29
+ }
30
+ if (classification === "defer" || classification === "collect_redacted") {
31
+ return "deferred";
32
+ }
33
+ return "unsupported";
34
+ }
35
+ async function statKind(path) {
36
+ try {
37
+ const st = await stat(path);
38
+ return {
39
+ exists: true,
40
+ mtimeEpoch: Math.floor(st.mtimeMs / 1000),
41
+ sizeBytes: st.isFile() ? st.size : null,
42
+ type: inventoryEntryType(st),
43
+ };
44
+ }
45
+ catch {
46
+ return { exists: false, mtimeEpoch: null, sizeBytes: null, type: "missing" };
47
+ }
48
+ }
49
+ export async function buildLocalSourceInventory(tool, sourceHome, stores) {
50
+ const recordsByStream = new Map();
51
+ const coverage = [];
52
+ for (const store of stores) {
53
+ const fullPath = join(sourceHome, store.relativePath);
54
+ const pathMeta = await statKind(fullPath);
55
+ const status = coverageStatus(store.classification, pathMeta.exists);
56
+ coverage.push({
57
+ id: `${store.store}:${status}`,
58
+ store: store.store,
59
+ stream: store.stream,
60
+ status,
61
+ reason: store.reason,
62
+ });
63
+ if (!(pathMeta.exists && store.stream) ||
64
+ (store.classification !== "inventory_only" && store.classification !== "defer")) {
65
+ continue;
66
+ }
67
+ const records = recordsByStream.get(store.stream) ?? [];
68
+ records.push({
69
+ id: `${store.store}:${pathHash(tool, store.relativePath)}`,
70
+ store: store.store,
71
+ relative_path: store.relativePath,
72
+ path_hash: pathHash(tool, store.relativePath),
73
+ type: pathMeta.type,
74
+ size_bytes: pathMeta.sizeBytes,
75
+ mtime_epoch: pathMeta.mtimeEpoch,
76
+ classification: store.classification,
77
+ reason: store.reason,
78
+ });
79
+ recordsByStream.set(store.stream, records);
80
+ }
81
+ return { coverage, recordsByStream };
82
+ }
83
+ export async function listDirectoryInventory(input) {
84
+ const root = join(input.sourceHome, input.relativeRoot);
85
+ let entries;
86
+ try {
87
+ entries = await readdir(root, { withFileTypes: true });
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ const records = [];
93
+ for (const ent of entries.sort((a, b) => a.name.localeCompare(b.name))) {
94
+ if (ent.name.startsWith(".")) {
95
+ continue;
96
+ }
97
+ const rel = `${input.relativeRoot}/${ent.name}`;
98
+ const full = join(root, ent.name);
99
+ let st;
100
+ try {
101
+ st = statSync(full);
102
+ }
103
+ catch {
104
+ continue;
105
+ }
106
+ records.push({
107
+ id: `${input.store}:${pathHash(input.tool, rel)}`,
108
+ store: input.store,
109
+ relative_path: rel,
110
+ path_hash: pathHash(input.tool, rel),
111
+ type: inventoryEntryType(st),
112
+ size_bytes: st.isFile() ? st.size : null,
113
+ mtime_epoch: Math.floor(st.mtimeMs / 1000),
114
+ classification: "inventory_only",
115
+ reason: input.reason,
116
+ });
117
+ }
118
+ return records;
119
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ import { safeTextPreview } from "./safe-text-preview.js";
3
+ export const pdppSafeText = z
4
+ .string()
5
+ .refine((s) => {
6
+ const result = safeTextPreview(s);
7
+ return result.kind === "text" || result.kind === "empty";
8
+ }, {
9
+ message: "must be PDPP-safe Unicode text (no U+0000, no forbidden control characters, valid UTF-8). " +
10
+ "Binary or control-rich payloads MUST be stored in the blobs table.",
11
+ })
12
+ .brand();
13
+ export const nullablePdppSafeText = pdppSafeText.nullable();
@@ -0,0 +1,11 @@
1
+ export { COLLECTOR_PROTOCOL_HEADER, COLLECTOR_PROTOCOL_VERSION } from "../collector-protocol.js";
2
+ export { buildCollectorStartMessage, type CollectorChildContext, type CollectorConnectorSpec, type CollectorEnrollmentConfig, type CollectorRunConfig, type CollectorRunResult, CollectorStateReadError, drainCollectorQueue, enrollCollector, runCollectorConnector, transformRecordsToCollectorEnvelopes, } from "../collector-runner.js";
3
+ export type { AssistanceAttachment, AssistanceAttachmentKind, AssistanceCompletion, AssistanceCompletionStatus, AssistanceOwnerAction, AssistanceProgressPosture, AssistanceRequest, AssistanceResponseContract, AssistanceSensitivity, DetailCoverageMessage, DetailGapMessage, DetailGapRecoveredMessage, DetailGapStartEntry, EmittedMessage, InteractionKind, InteractionRequest, InteractionResponse, RecordData, StartMessage, StreamScope, ValidateRecord, } from "../connector-runtime-protocol.js";
4
+ export { isMainModule } from "../is-main-module.js";
5
+ export { type EnrollmentExchangeRequest, type EnrollmentExchangeResponse, type GetSourceInstanceStateRequest, type HeartbeatRequest, type IngestBatchRequest, LOCAL_DEVICE_ENDPOINTS, LocalDeviceClient, type LocalDeviceClientOptions, LocalDeviceHttpError, type PutSourceInstanceStateRequest, type SourceInstanceStateResponse, } from "../local-device-client.js";
6
+ export { type BuildLocalDeviceRecordEnvelopeInput, buildLocalDeviceRecordEnvelope, canonicalJson, hashCanonicalJson, type LocalDeviceRecordEnvelope, } from "../local-device-envelope.js";
7
+ export { type BuildLocalDeviceOutboxIdInput, buildLocalDeviceOutboxId, LocalDeviceOutbox, type LocalDeviceOutboxClaimInput, type LocalDeviceOutboxDeadLetterInput, type LocalDeviceOutboxEnqueueInput, type LocalDeviceOutboxFailInput, type LocalDeviceOutboxItem, type LocalDeviceOutboxKind, type LocalDeviceOutboxLeaseInput, type LocalDeviceOutboxOptions, type LocalDeviceOutboxRenewInput, type LocalDeviceOutboxStatus, type LocalDeviceOutboxSummary, } from "../local-device-outbox.js";
8
+ export { LocalDeviceQueue, type LocalDeviceQueueItem, type LocalDeviceQueueOptions, type LocalDeviceQueueStatus, } from "../local-device-queue.js";
9
+ export { assertPlacementOrThrow, COLLECTOR_RUNTIME_CAPABILITIES, type ConnectorPlacementInput, type ConnectorRuntimeRequirements, diffRequiredBindings, evaluatePlacement, type PlacementDecision, PROVIDER_RUNTIME_CAPABILITIES, RUNTIME_CAPABILITY_MISMATCH_CODE, type RuntimeBindingName, RuntimeCapabilityMismatchError, type RuntimeCapabilityProfile, } from "../runtime-capabilities.js";
10
+ export { emitToStdout, parseJsonlLine, stringifyForJsonl } from "../safe-emit.js";
11
+ export { type EmitGate, type EmitGateRecord, type EmitTombstonesArgs, emitTombstones, type MakeEmitGateOptions, makeEmitGate, passesResourceFilter, passesTimeRange, type RequireCredentialsOrAskArgs, requireCredentialsOrAsk, resourceSet, type StreamRequest, type TimeRange, } from "../scope-filters.js";
@@ -0,0 +1,10 @@
1
+ export { COLLECTOR_PROTOCOL_HEADER, COLLECTOR_PROTOCOL_VERSION } from "../collector-protocol.js";
2
+ export { buildCollectorStartMessage, CollectorStateReadError, drainCollectorQueue, enrollCollector, runCollectorConnector, transformRecordsToCollectorEnvelopes, } from "../collector-runner.js";
3
+ export { isMainModule } from "../is-main-module.js";
4
+ export { LOCAL_DEVICE_ENDPOINTS, LocalDeviceClient, LocalDeviceHttpError, } from "../local-device-client.js";
5
+ export { buildLocalDeviceRecordEnvelope, canonicalJson, hashCanonicalJson, } from "../local-device-envelope.js";
6
+ export { buildLocalDeviceOutboxId, LocalDeviceOutbox, } from "../local-device-outbox.js";
7
+ export { LocalDeviceQueue, } from "../local-device-queue.js";
8
+ export { assertPlacementOrThrow, COLLECTOR_RUNTIME_CAPABILITIES, diffRequiredBindings, evaluatePlacement, PROVIDER_RUNTIME_CAPABILITIES, RUNTIME_CAPABILITY_MISMATCH_CODE, RuntimeCapabilityMismatchError, } from "../runtime-capabilities.js";
9
+ export { emitToStdout, parseJsonlLine, stringifyForJsonl } from "../safe-emit.js";
10
+ export { emitTombstones, makeEmitGate, passesResourceFilter, passesTimeRange, requireCredentialsOrAsk, resourceSet, } from "../scope-filters.js";
@@ -0,0 +1,40 @@
1
+ export type RuntimeBindingName = "network" | "browser" | "filesystem" | "local_device";
2
+ export interface RuntimeCapabilityProfile {
3
+ readonly bindings: ReadonlySet<RuntimeBindingName>;
4
+ readonly id: string;
5
+ }
6
+ export declare const PROVIDER_RUNTIME_CAPABILITIES: RuntimeCapabilityProfile;
7
+ export declare const COLLECTOR_RUNTIME_CAPABILITIES: RuntimeCapabilityProfile;
8
+ export interface ConnectorRuntimeRequirements {
9
+ readonly bindings?: Partial<Record<RuntimeBindingName, {
10
+ readonly required?: boolean;
11
+ }>>;
12
+ }
13
+ export interface ConnectorPlacementInput {
14
+ readonly connector_id: string;
15
+ readonly runtime_requirements?: ConnectorRuntimeRequirements;
16
+ }
17
+ export type PlacementDecision = {
18
+ readonly kind: "ok";
19
+ readonly satisfied: readonly RuntimeBindingName[];
20
+ } | {
21
+ readonly kind: "missing_capability";
22
+ readonly missing: readonly RuntimeBindingName[];
23
+ readonly runtime: string;
24
+ readonly connectorId: string;
25
+ };
26
+ export declare function diffRequiredBindings(connector: ConnectorPlacementInput, runtime: RuntimeCapabilityProfile): RuntimeBindingName[];
27
+ export declare function evaluatePlacement(connector: ConnectorPlacementInput, runtime: RuntimeCapabilityProfile): PlacementDecision;
28
+ export declare const RUNTIME_CAPABILITY_MISMATCH_CODE = "runtime_capability_mismatch";
29
+ export declare class RuntimeCapabilityMismatchError extends Error {
30
+ readonly code: typeof RUNTIME_CAPABILITY_MISMATCH_CODE;
31
+ readonly missing: readonly RuntimeBindingName[];
32
+ readonly runtime: string;
33
+ readonly connectorId: string;
34
+ constructor(args: {
35
+ connectorId: string;
36
+ runtime: string;
37
+ missing: readonly RuntimeBindingName[];
38
+ });
39
+ }
40
+ export declare function assertPlacementOrThrow(connector: ConnectorPlacementInput, runtime: RuntimeCapabilityProfile): readonly RuntimeBindingName[];
@@ -0,0 +1,59 @@
1
+ export const PROVIDER_RUNTIME_CAPABILITIES = {
2
+ id: "provider",
3
+ bindings: new Set(["network", "filesystem"]),
4
+ };
5
+ export const COLLECTOR_RUNTIME_CAPABILITIES = {
6
+ id: "collector",
7
+ bindings: new Set(["network", "browser", "filesystem", "local_device"]),
8
+ };
9
+ export function diffRequiredBindings(connector, runtime) {
10
+ const declared = connector.runtime_requirements?.bindings ?? {};
11
+ const missing = [];
12
+ for (const [name, decl] of Object.entries(declared)) {
13
+ if (decl?.required && !runtime.bindings.has(name)) {
14
+ missing.push(name);
15
+ }
16
+ }
17
+ return missing;
18
+ }
19
+ export function evaluatePlacement(connector, runtime) {
20
+ const missing = diffRequiredBindings(connector, runtime);
21
+ if (missing.length === 0) {
22
+ const declared = connector.runtime_requirements?.bindings ?? {};
23
+ const satisfied = Object.keys(declared).filter((name) => declared[name]?.required && runtime.bindings.has(name));
24
+ return { kind: "ok", satisfied };
25
+ }
26
+ return {
27
+ kind: "missing_capability",
28
+ missing,
29
+ runtime: runtime.id,
30
+ connectorId: connector.connector_id,
31
+ };
32
+ }
33
+ export const RUNTIME_CAPABILITY_MISMATCH_CODE = "runtime_capability_mismatch";
34
+ export class RuntimeCapabilityMismatchError extends Error {
35
+ code;
36
+ missing;
37
+ runtime;
38
+ connectorId;
39
+ constructor(args) {
40
+ super(`Runtime '${args.runtime}' cannot satisfy connector '${args.connectorId}': missing bindings [${args.missing.join(", ")}]. ` +
41
+ "Run this connector in a runtime that advertises the required bindings (typically the local collector runtime).");
42
+ this.name = "RuntimeCapabilityMismatchError";
43
+ this.code = RUNTIME_CAPABILITY_MISMATCH_CODE;
44
+ this.missing = args.missing;
45
+ this.runtime = args.runtime;
46
+ this.connectorId = args.connectorId;
47
+ }
48
+ }
49
+ export function assertPlacementOrThrow(connector, runtime) {
50
+ const decision = evaluatePlacement(connector, runtime);
51
+ if (decision.kind === "ok") {
52
+ return decision.satisfied;
53
+ }
54
+ throw new RuntimeCapabilityMismatchError({
55
+ connectorId: decision.connectorId,
56
+ runtime: decision.runtime,
57
+ missing: decision.missing,
58
+ });
59
+ }
@@ -0,0 +1,3 @@
1
+ export declare function stringifyForJsonl(msg: unknown): string;
2
+ export declare function emitToStdout(msg: unknown): Promise<void>;
3
+ export declare function parseJsonlLine(line: string): unknown;
@@ -0,0 +1,30 @@
1
+ const JSONL_TERMINATOR = /[\u2028\u2029]/g;
2
+ const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
3
+ const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
4
+ function bigIntSafeReplacer(_key, value) {
5
+ if (typeof value === "bigint") {
6
+ return value <= MAX_SAFE && value >= MIN_SAFE ? Number(value) : value.toString();
7
+ }
8
+ return value;
9
+ }
10
+ function escapeJsonlTerminator(c) {
11
+ return c === "\u2028" ? "\\u2028" : "\\u2029";
12
+ }
13
+ export function stringifyForJsonl(msg) {
14
+ return `${JSON.stringify(msg, bigIntSafeReplacer).replace(JSONL_TERMINATOR, escapeJsonlTerminator)}\n`;
15
+ }
16
+ export function emitToStdout(msg) {
17
+ const line = stringifyForJsonl(msg);
18
+ const ok = process.stdout.write(line);
19
+ if (ok) {
20
+ return Promise.resolve();
21
+ }
22
+ return new Promise((resolve) => {
23
+ process.stdout.once("drain", () => {
24
+ resolve();
25
+ });
26
+ });
27
+ }
28
+ export function parseJsonlLine(line) {
29
+ return JSON.parse(line);
30
+ }
@@ -0,0 +1,156 @@
1
+ export const PDPP_PREVIEW_MAX_CHARS = 4000;
2
+ function isForbiddenCodePoint(codeUnit) {
3
+ if (codeUnit === 0x00_00) {
4
+ return true;
5
+ }
6
+ if (codeUnit >= 0x00_01 && codeUnit <= 0x00_08) {
7
+ return true;
8
+ }
9
+ if (codeUnit === 0x00_0b) {
10
+ return true;
11
+ }
12
+ if (codeUnit === 0x00_0c) {
13
+ return true;
14
+ }
15
+ if (codeUnit >= 0x00_0e && codeUnit <= 0x00_1f) {
16
+ return true;
17
+ }
18
+ if (codeUnit === 0x00_7f) {
19
+ return true;
20
+ }
21
+ if (codeUnit >= 0x00_80 && codeUnit <= 0x00_9f) {
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ function checkStringForForbidden(value) {
27
+ for (let i = 0; i < value.length; i++) {
28
+ const codeUnit = value.charCodeAt(i);
29
+ if (isForbiddenCodePoint(codeUnit)) {
30
+ return {
31
+ isSafe: false,
32
+ firstOffendingIndex: i,
33
+ offendingCodeUnit: codeUnit,
34
+ };
35
+ }
36
+ }
37
+ return { isSafe: true };
38
+ }
39
+ function decodeBuffer(buf) {
40
+ const bufferWithUtf8 = Buffer;
41
+ const isUtf8 = bufferWithUtf8.isUtf8;
42
+ if (typeof isUtf8 === "function" && buf instanceof Buffer) {
43
+ if (!isUtf8(buf)) {
44
+ return { success: false, reason: "invalid UTF-8 sequence in buffer" };
45
+ }
46
+ return { success: true, text: buf.toString("utf-8") };
47
+ }
48
+ try {
49
+ const decoder = new TextDecoder("utf-8", { fatal: true });
50
+ const text = decoder.decode(buf);
51
+ return { success: true, text };
52
+ }
53
+ catch {
54
+ return { success: false, reason: "invalid UTF-8 sequence in buffer" };
55
+ }
56
+ }
57
+ function truncateString(text, maxChars) {
58
+ if (text.length <= maxChars) {
59
+ return { result: text, wasTruncated: false };
60
+ }
61
+ let truncateAt = maxChars;
62
+ if (truncateAt > 0 && truncateAt < text.length) {
63
+ const codeUnitAtTruncate = text.charCodeAt(truncateAt - 1);
64
+ if (codeUnitAtTruncate >= 0xd8_00 && codeUnitAtTruncate <= 0xdb_ff) {
65
+ truncateAt--;
66
+ }
67
+ }
68
+ const truncated = `${text.slice(0, truncateAt)}…`;
69
+ return { result: truncated, wasTruncated: true };
70
+ }
71
+ export function safeTextPreview(value, maxChars = PDPP_PREVIEW_MAX_CHARS) {
72
+ let text = null;
73
+ let originalLength = 0;
74
+ if (value === null || value === undefined) {
75
+ return {
76
+ kind: "empty",
77
+ preview: null,
78
+ truncated: false,
79
+ originalLength: 0,
80
+ reason: null,
81
+ };
82
+ }
83
+ if (typeof value === "string") {
84
+ text = value;
85
+ originalLength = text.length;
86
+ }
87
+ else if (Buffer.isBuffer(value)) {
88
+ originalLength = value.length;
89
+ const decoded = decodeBuffer(value);
90
+ if (!decoded.success) {
91
+ return {
92
+ kind: "binary",
93
+ preview: null,
94
+ truncated: false,
95
+ originalLength,
96
+ reason: decoded.reason || "invalid UTF-8",
97
+ };
98
+ }
99
+ text = decoded.text;
100
+ }
101
+ else if (value instanceof Uint8Array) {
102
+ originalLength = value.length;
103
+ const decoded = decodeBuffer(value);
104
+ if (!decoded.success) {
105
+ return {
106
+ kind: "binary",
107
+ preview: null,
108
+ truncated: false,
109
+ originalLength,
110
+ reason: decoded.reason || "invalid UTF-8",
111
+ };
112
+ }
113
+ text = decoded.text;
114
+ }
115
+ else {
116
+ return {
117
+ kind: "empty",
118
+ preview: null,
119
+ truncated: false,
120
+ originalLength: 0,
121
+ reason: null,
122
+ };
123
+ }
124
+ if (text === "") {
125
+ return {
126
+ kind: "empty",
127
+ preview: null,
128
+ truncated: false,
129
+ originalLength: 0,
130
+ reason: null,
131
+ };
132
+ }
133
+ const forbidden = checkStringForForbidden(text);
134
+ if (!forbidden.isSafe) {
135
+ const offendingIndex = forbidden.firstOffendingIndex;
136
+ const codeUnit = forbidden.offendingCodeUnit;
137
+ const reason = codeUnit === 0x00_00
138
+ ? `U+0000 at offset ${offendingIndex}`
139
+ : `U+${codeUnit.toString(16).toUpperCase().padStart(4, "0")} at offset ${offendingIndex}`;
140
+ return {
141
+ kind: "binary",
142
+ preview: null,
143
+ truncated: false,
144
+ originalLength,
145
+ reason,
146
+ };
147
+ }
148
+ const truncated = truncateString(text, maxChars);
149
+ return {
150
+ kind: "text",
151
+ preview: truncated.result,
152
+ truncated: truncated.wasTruncated,
153
+ originalLength,
154
+ reason: null,
155
+ };
156
+ }
@@ -0,0 +1,17 @@
1
+ export function makeValidateRecord(schemas) {
2
+ return (stream, data) => {
3
+ const schema = schemas[stream];
4
+ if (!schema) {
5
+ return { ok: true, data };
6
+ }
7
+ const result = schema.safeParse(data);
8
+ if (result.success) {
9
+ return { ok: true, data: result.data };
10
+ }
11
+ const issues = result.error.issues.map((i) => ({
12
+ path: i.path.join("."),
13
+ message: i.message,
14
+ }));
15
+ return { ok: false, issues };
16
+ };
17
+ }
@@ -0,0 +1,38 @@
1
+ import type { Credentials } from "./auth.js";
2
+ import type { EmittedMessage, InteractionRequest, InteractionResponse } from "./connector-runtime-protocol.js";
3
+ export interface TimeRange {
4
+ since?: string;
5
+ until?: string;
6
+ }
7
+ export interface StreamRequest {
8
+ resources?: readonly unknown[];
9
+ time_range?: TimeRange;
10
+ }
11
+ export declare function resourceSet(streamRequest: StreamRequest | null | undefined): Set<string> | null;
12
+ export declare function passesResourceFilter(resSet: ReadonlySet<string> | null, primaryKey: unknown): boolean;
13
+ export declare function passesTimeRange(isoValue: string | null | undefined, timeRange: TimeRange | null | undefined): boolean;
14
+ export interface EmitGateRecord {
15
+ [field: string]: unknown;
16
+ }
17
+ export interface EmitGate {
18
+ emittedSet: () => Set<string>;
19
+ (stream: string, data: EmitGateRecord, keyField?: string): boolean;
20
+ }
21
+ export interface MakeEmitGateOptions {
22
+ consentTimeField?: string;
23
+ }
24
+ export declare function makeEmitGate(emitRecord: (stream: string, data: EmitGateRecord) => void, streamRequest: StreamRequest | null | undefined, { consentTimeField }?: MakeEmitGateOptions): EmitGate;
25
+ export interface EmitTombstonesArgs {
26
+ currentIds: ReadonlySet<string>;
27
+ emit: (msg: EmittedMessage) => unknown;
28
+ emittedAt: string;
29
+ priorIds: Iterable<string> | null | undefined;
30
+ stream: string;
31
+ }
32
+ export declare function emitTombstones({ emit, stream, priorIds, currentIds, emittedAt }: EmitTombstonesArgs): number;
33
+ export interface RequireCredentialsOrAskArgs {
34
+ connectorName: string;
35
+ required: ReadonlyArray<string | readonly string[]>;
36
+ sendInteraction: (req: InteractionRequest) => Promise<InteractionResponse>;
37
+ }
38
+ export declare function requireCredentialsOrAsk({ required, connectorName, sendInteraction, }: RequireCredentialsOrAskArgs): Promise<Credentials>;