@llm-refract/sdk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @llm-refract/sdk
2
+
3
+ Node.js SDK for Refract — a portable execution recording, replay and diff engine for AI systems.
4
+ Targets server-side Node.js apps and agents (uses `AsyncLocalStorage`, filesystem and crypto APIs;
5
+ not a browser SDK).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @llm-refract/sdk
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { refract, unpack } from "@llm-refract/sdk";
17
+ import { readFile } from "node:fs/promises";
18
+
19
+ const answer = await refract.run(
20
+ "agent",
21
+ async () => {
22
+ const output = { text: "Hello" };
23
+ refract.event({
24
+ type: "generation",
25
+ name: "answer",
26
+ output,
27
+ attributes: { provider: "local", model: "demo" },
28
+ });
29
+ return output;
30
+ },
31
+ { path: "agent.rfr", endpoint: "http://localhost:8000" },
32
+ );
33
+ const recorded = unpack(await readFile("agent.rfr"));
34
+ ```
35
+
36
+ Omit `endpoint` for offline recording, or `path` for API-only submission. `pack`/`unpack` read and
37
+ write the text artifact profile and verify checksums on read.
38
+
39
+ ## Links
40
+
41
+ - [Documentation](https://github.com/khaleddeissa/llm-refract/tree/main/docs)
42
+ - [Source](https://github.com/khaleddeissa/llm-refract/tree/main/packages/typescript)
43
+ - [Issues](https://github.com/khaleddeissa/llm-refract/issues)
44
+
45
+ License: Apache-2.0
@@ -0,0 +1,47 @@
1
+ export type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ export type EventType = "generation" | "tool.call" | "retrieval" | "decision" | "state.change" | "checkpoint" | "handoff" | "human" | "artifact" | "error";
5
+ export type ReplayPolicy = "READ_ONLY" | "MOCK" | "RECORDED" | "LIVE" | "REQUIRES_APPROVAL" | "BLOCKED";
6
+ export interface EventInput {
7
+ type: EventType;
8
+ name: string;
9
+ input?: Json;
10
+ output?: Json;
11
+ parent_id?: string;
12
+ duration_ms?: number;
13
+ attributes?: Record<string, Json>;
14
+ replay_policy?: ReplayPolicy;
15
+ status?: "running" | "completed" | "failed";
16
+ }
17
+ export interface ExecutionEvent extends Omit<EventInput, "parent_id"> {
18
+ id: string;
19
+ run_id: string;
20
+ parent_id: string | null;
21
+ timestamp: string;
22
+ }
23
+ export interface Execution {
24
+ spec_version: "refract.execution.v1";
25
+ id: string;
26
+ name: string;
27
+ status: "running" | "completed" | "failed";
28
+ started_at: string;
29
+ ended_at: string | null;
30
+ metadata: Record<string, Json>;
31
+ events: ExecutionEvent[];
32
+ }
33
+ export declare function redact(value: Json): Json;
34
+ export declare function event(input: EventInput): string;
35
+ /** UTF-8 checksum header + formatted execution JSON. */
36
+ export declare function pack(execution: Execution): Uint8Array;
37
+ export declare function unpack(bytes: Uint8Array): Execution;
38
+ export declare function run<T>(name: string, fn: () => T | Promise<T>, options?: {
39
+ path?: string;
40
+ endpoint?: string;
41
+ metadata?: Record<string, Json>;
42
+ onComplete?: (execution: Execution) => void;
43
+ }): Promise<T>;
44
+ export declare const refract: {
45
+ run: typeof run;
46
+ event: typeof event;
47
+ };
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { writeFile } from "node:fs/promises";
4
+ export function redact(value) {
5
+ if (Array.isArray(value))
6
+ return value.map(redact);
7
+ if (value !== null && typeof value === "object")
8
+ return Object.fromEntries(Object.entries(value).map(([key, v]) => [
9
+ key,
10
+ /password|secret|token|api_key|authorization|cookie|email/.test(key.toLowerCase().replaceAll("-", "_"))
11
+ ? "[REDACTED]"
12
+ : redact(v),
13
+ ]));
14
+ if (typeof value === "number" && !Number.isFinite(value))
15
+ throw new Error("non-finite JSON number");
16
+ return value;
17
+ }
18
+ const context = new AsyncLocalStorage();
19
+ export function event(input) {
20
+ const state = context.getStore();
21
+ if (!state?.active)
22
+ throw new Error("event requires an active refract.run");
23
+ if (!input.name.trim() || (input.duration_ms ?? 0) < 0)
24
+ throw new Error("invalid event name/duration");
25
+ if (input.parent_id &&
26
+ !state.execution.events.some((e) => e.id === input.parent_id))
27
+ throw new Error("parent must precede child");
28
+ const e = redact({
29
+ ...input,
30
+ id: `evt_${randomUUID()}`,
31
+ run_id: state.execution.id,
32
+ parent_id: input.parent_id ?? null,
33
+ timestamp: new Date().toISOString(),
34
+ status: input.status ?? "completed",
35
+ duration_ms: input.duration_ms ?? 0,
36
+ input: input.input ?? null,
37
+ output: input.output ?? null,
38
+ attributes: input.attributes ?? {},
39
+ replay_policy: input.replay_policy ?? "RECORDED",
40
+ });
41
+ state.execution.events.push(e);
42
+ return e.id;
43
+ }
44
+ /** UTF-8 checksum header + formatted execution JSON. */
45
+ export function pack(execution) {
46
+ const safe = redact(execution);
47
+ const payload = Buffer.from(JSON.stringify(safe, null, 2) + "\n");
48
+ if (payload.length > 16 * 1024 * 1024)
49
+ throw new Error("artifact exceeds size limit");
50
+ const header = {
51
+ format: "refract.artifact.v1",
52
+ encoding: "json",
53
+ sha256: createHash("sha256").update(payload).digest("hex"),
54
+ };
55
+ return Buffer.concat([Buffer.from(JSON.stringify(header) + "\n"), payload]);
56
+ }
57
+ export function unpack(bytes) {
58
+ const data = Buffer.from(bytes);
59
+ if (data.length > 16 * 1024 * 1024 + 4097)
60
+ throw new Error("artifact exceeds size limit");
61
+ const split = data.indexOf(10);
62
+ if (split < 0 || split > 4096)
63
+ throw new Error("invalid artifact header");
64
+ const header = JSON.parse(data.subarray(0, split).toString("utf8"));
65
+ const payload = data.subarray(split + 1);
66
+ if (payload.length > 16 * 1024 * 1024)
67
+ throw new Error("payload exceeds size limit");
68
+ if (header.format !== "refract.artifact.v1" || header.encoding !== "json")
69
+ throw new Error("unsupported profile; use Rust CLI for legacy ZIP");
70
+ if (createHash("sha256").update(payload).digest("hex") !== header.sha256)
71
+ throw new Error("checksum mismatch");
72
+ const run = JSON.parse(payload.toString("utf8"));
73
+ if (run?.spec_version !== "refract.execution.v1" ||
74
+ !Array.isArray(run.events))
75
+ throw new Error("invalid execution");
76
+ return run;
77
+ }
78
+ export async function run(name, fn, options = {}) {
79
+ if (!name.trim())
80
+ throw new Error("run name cannot be empty");
81
+ const execution = {
82
+ spec_version: "refract.execution.v1",
83
+ id: `run_${randomUUID()}`,
84
+ name,
85
+ status: "running",
86
+ started_at: new Date().toISOString(),
87
+ ended_at: null,
88
+ metadata: redact(options.metadata ?? {}),
89
+ events: [],
90
+ };
91
+ const state = { execution, active: true };
92
+ return context.run(state, async () => {
93
+ let failed = false;
94
+ try {
95
+ const result = await fn();
96
+ execution.status = "completed";
97
+ return result;
98
+ }
99
+ catch (error) {
100
+ failed = true;
101
+ execution.status = "failed";
102
+ event({ type: "error", name: "Execution failed", status: "failed" });
103
+ throw error;
104
+ }
105
+ finally {
106
+ state.active = false;
107
+ execution.ended_at = new Date().toISOString();
108
+ try {
109
+ if (options.path)
110
+ await writeFile(options.path, options.path.endsWith(".rfr")
111
+ ? pack(execution)
112
+ : JSON.stringify(execution, null, 2), { flag: "wx" });
113
+ if (options.endpoint) {
114
+ const response = await fetch(`${options.endpoint.replace(/\/$/, "")}/v1/runs`, {
115
+ method: "POST",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify(execution),
118
+ signal: AbortSignal.timeout(10_000),
119
+ });
120
+ if (!response.ok)
121
+ throw new Error(`Refract ingestion failed: ${response.status}`);
122
+ }
123
+ options.onComplete?.(structuredClone(execution));
124
+ }
125
+ catch (recordingError) {
126
+ if (!failed)
127
+ throw recordingError;
128
+ }
129
+ }
130
+ });
131
+ }
132
+ export const refract = { run, event };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@llm-refract/sdk",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/khaleddeissa/llm-refract",
22
+ "directory": "packages/typescript"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.build.json",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run"
29
+ }
30
+ }