@frockbot/kernel-contracts 0.0.0 → 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/package.json CHANGED
@@ -1,14 +1,29 @@
1
1
  {
2
2
  "name": "@frockbot/kernel-contracts",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving this name for trusted publishing. Superseded by the first release.",
5
- "license": "UNLICENSED",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "scripts": {
10
+ "test": "bun test src",
11
+ "typecheck": "tsc --noEmit -p tsconfig.json"
12
+ },
13
+ "dependencies": {
14
+ "cordis": "4.0.0-rc.8"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "1.4.0",
18
+ "@types/node": "26.2.0",
19
+ "typescript": "^7.0.2"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
6
24
  "repository": {
7
25
  "type": "git",
8
26
  "url": "git+https://github.com/timoconnellaus/frockbot.git",
9
27
  "directory": "packages/kernel-contracts"
10
- },
11
- "publishConfig": {
12
- "access": "public"
13
28
  }
14
29
  }
@@ -0,0 +1,143 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ decodePackageBundleArtifactV1,
4
+ decodePackageBundleResultV1,
5
+ PACKAGE_BUNDLE_MAX_SOURCE_BYTES,
6
+ } from "./authoring.js";
7
+ import { decodeSessionEvent } from "./types.js";
8
+
9
+ const ARTIFACT = {
10
+ contentHash: "a".repeat(64),
11
+ size: 128,
12
+ mediaType: "application/javascript",
13
+ bundlerVersion: "@cloudflare/worker-bundler@0.2.3",
14
+ };
15
+
16
+ describe("the kernel-declared bundler seam", () => {
17
+ test("bounds Package source at the D7 quota", () => {
18
+ expect(PACKAGE_BUNDLE_MAX_SOURCE_BYTES).toBe(256 * 1024);
19
+ });
20
+
21
+ test("decodes a bundled result exactly", () => {
22
+ const result = decodePackageBundleResultV1({
23
+ schemaVersion: 1,
24
+ effectId: "author-1",
25
+ status: "bundled",
26
+ artifact: ARTIFACT,
27
+ module: "export const tools = [];",
28
+ diagnostics: [],
29
+ });
30
+ expect(result.status).toBe("bundled");
31
+ expect(decodePackageBundleArtifactV1(ARTIFACT)).toEqual(ARTIFACT as never);
32
+ });
33
+
34
+ test("decodes a failed result exactly", () => {
35
+ expect(
36
+ decodePackageBundleResultV1({
37
+ schemaVersion: 1,
38
+ effectId: "author-1",
39
+ status: "failed",
40
+ failure: "unresolved-import",
41
+ diagnostics: ['still imports "zod"'],
42
+ }).status,
43
+ ).toBe("failed");
44
+ });
45
+
46
+ test.each([
47
+ [
48
+ "an unsupported schema version",
49
+ { schemaVersion: 2, effectId: "a", status: "bundled" },
50
+ ],
51
+ [
52
+ "an unknown status",
53
+ { schemaVersion: 1, effectId: "a", status: "queued" },
54
+ ],
55
+ [
56
+ "a non-hex content hash",
57
+ {
58
+ schemaVersion: 1,
59
+ effectId: "a",
60
+ status: "bundled",
61
+ artifact: { ...ARTIFACT, contentHash: "not-a-hash" },
62
+ module: "x",
63
+ diagnostics: [],
64
+ },
65
+ ],
66
+ [
67
+ "an unexpected media type",
68
+ {
69
+ schemaVersion: 1,
70
+ effectId: "a",
71
+ status: "bundled",
72
+ artifact: { ...ARTIFACT, mediaType: "text/html" },
73
+ module: "x",
74
+ diagnostics: [],
75
+ },
76
+ ],
77
+ [
78
+ "an empty module",
79
+ {
80
+ schemaVersion: 1,
81
+ effectId: "a",
82
+ status: "bundled",
83
+ artifact: ARTIFACT,
84
+ module: "",
85
+ diagnostics: [],
86
+ },
87
+ ],
88
+ [
89
+ "an extra field",
90
+ {
91
+ schemaVersion: 1,
92
+ effectId: "a",
93
+ status: "bundled",
94
+ artifact: ARTIFACT,
95
+ module: "x",
96
+ diagnostics: [],
97
+ r2Key: "packages/a.mjs",
98
+ },
99
+ ],
100
+ ])("rejects %s", (_label, input) => {
101
+ expect(() => decodePackageBundleResultV1(input)).toThrow();
102
+ });
103
+ });
104
+
105
+ describe("the authoring session events", () => {
106
+ const intent = {
107
+ type: "package/author-intent",
108
+ seq: 0,
109
+ timestamp: "2026-08-31T00:00:00.000Z",
110
+ turn: 1,
111
+ step: 1,
112
+ effectId: "author-1",
113
+ packageId: "weather-lookup",
114
+ sourceHash: "a".repeat(64),
115
+ };
116
+ const authored = {
117
+ type: "package/authored",
118
+ seq: 1,
119
+ timestamp: "2026-08-31T00:00:00.000Z",
120
+ turn: 1,
121
+ step: 1,
122
+ effectId: "author-1",
123
+ packageId: "weather-lookup",
124
+ version: "0.0.1",
125
+ contentHash: "b".repeat(64),
126
+ generationId: "2026-08-31T00:00:00.000Z:0123456789abcdef",
127
+ };
128
+
129
+ test("decode exactly", () => {
130
+ expect(decodeSessionEvent(intent)).toEqual(intent as never);
131
+ expect(decodeSessionEvent(authored)).toEqual(authored as never);
132
+ });
133
+
134
+ test.each([
135
+ ["an intent with no step", { ...intent, step: undefined }],
136
+ ["an intent with an extra field", { ...intent, source: "x" }],
137
+ ["an authored event with no generation", { ...authored, generationId: "" }],
138
+ ["an authored event with no version", { ...authored, version: undefined }],
139
+ ["a turn of zero", { ...intent, turn: 0 }],
140
+ ])("reject %s", (_label, input) => {
141
+ expect(() => decodeSessionEvent(input)).toThrow();
142
+ });
143
+ });
@@ -0,0 +1,189 @@
1
+ // The kernel-declared Package bundler seam.
2
+ //
3
+ // "Compilation and bundling happen outside Durable Objects. Composition
4
+ // consumes immutable, content-addressed artifacts and never builds them."
5
+ // The kernel therefore declares *what* a bundler is — a narrow, versioned
6
+ // binding that turns Package source text into a content-addressed artifact —
7
+ // and owns no implementation of one. `apps/cloudflare-bundler` is the
8
+ // production implementation behind the `PACKAGE_BUNDLER` service binding.
9
+ //
10
+ // A Package that authors code receives this binding from the Durable Object
11
+ // host that owns it; it never reaches a Worker `env` itself.
12
+ import type {} from "cordis";
13
+
14
+ /** D7: 256 KB of source per Package. */
15
+ export const PACKAGE_BUNDLE_MAX_SOURCE_BYTES = 256 * 1024;
16
+ /** The only entry a Bot-authored Package may declare in this slice. */
17
+ export const PACKAGE_BUNDLE_ENTRY = "package.ts";
18
+
19
+ export interface PackageBundleSourceV1 {
20
+ path: string;
21
+ text: string;
22
+ }
23
+
24
+ export interface PackageBundleRequestV1 {
25
+ schemaVersion: 1;
26
+ /** Idempotency key: the Durable-Object-recorded authorship intent id. */
27
+ effectId: string;
28
+ target: "bot-isolate";
29
+ compatibilityDate: string;
30
+ entry: "package.ts";
31
+ sources: PackageBundleSourceV1[];
32
+ }
33
+
34
+ export interface PackageBundleArtifactV1 {
35
+ /** sha-256 hex of the bundled module bytes. */
36
+ contentHash: string;
37
+ size: number;
38
+ mediaType: "application/javascript";
39
+ bundlerVersion: string;
40
+ }
41
+
42
+ export type PackageBundleResultV1 =
43
+ | {
44
+ schemaVersion: 1;
45
+ effectId: string;
46
+ status: "bundled";
47
+ artifact: PackageBundleArtifactV1;
48
+ /** The module bytes as text; the Durable Object owns the artifact write. */
49
+ module: string;
50
+ diagnostics: string[];
51
+ }
52
+ | {
53
+ schemaVersion: 1;
54
+ effectId: string;
55
+ status: "failed";
56
+ failure: string;
57
+ diagnostics: string[];
58
+ };
59
+
60
+ /** The `PACKAGE_BUNDLER` binding, declared structurally so the kernel stays platform-free. */
61
+ export interface PackageBundlerBinding {
62
+ bundle(request: PackageBundleRequestV1): Promise<PackageBundleResultV1>;
63
+ }
64
+
65
+ const SHA256_HEX = /^[0-9a-f]{64}$/;
66
+
67
+ function record(value: unknown, label: string): Record<string, unknown> {
68
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
69
+ throw new Error(`${label} must be an object`);
70
+ }
71
+ return value as Record<string, unknown>;
72
+ }
73
+
74
+ function exactKeys(
75
+ value: Record<string, unknown>,
76
+ keys: readonly string[],
77
+ label: string,
78
+ ): void {
79
+ if (
80
+ Object.keys(value).length !== keys.length ||
81
+ !keys.every((key) => Object.hasOwn(value, key))
82
+ ) {
83
+ throw new Error(`${label} has invalid fields`);
84
+ }
85
+ }
86
+
87
+ function boundedString(value: unknown, label: string, maximum: number): string {
88
+ if (typeof value !== "string" || value.length === 0 || value.length > maximum)
89
+ throw new Error(`${label} must be a bounded string`);
90
+ return value;
91
+ }
92
+
93
+ function diagnostics(value: unknown, label: string): string[] {
94
+ if (!Array.isArray(value) || value.length > 64) {
95
+ throw new Error(`${label} must be a bounded array`);
96
+ }
97
+ return value.map((entry, index) => {
98
+ if (typeof entry !== "string" || entry.length > 8_192) {
99
+ throw new Error(`${label}[${index}] must be a bounded string`);
100
+ }
101
+ return entry;
102
+ });
103
+ }
104
+
105
+ export function decodePackageBundleArtifactV1(
106
+ input: unknown,
107
+ label = "package bundle artifact",
108
+ ): PackageBundleArtifactV1 {
109
+ const value = record(input, label);
110
+ exactKeys(
111
+ value,
112
+ ["contentHash", "size", "mediaType", "bundlerVersion"],
113
+ label,
114
+ );
115
+ if (
116
+ typeof value.contentHash !== "string" ||
117
+ !SHA256_HEX.test(value.contentHash)
118
+ )
119
+ throw new Error(`${label}.contentHash must be a sha-256 hex digest`);
120
+ if (!Number.isSafeInteger(value.size) || (value.size as number) < 0)
121
+ throw new Error(`${label}.size must be a non-negative integer`);
122
+ if (value.mediaType !== "application/javascript")
123
+ throw new Error(`${label}.mediaType is invalid`);
124
+ boundedString(value.bundlerVersion, `${label}.bundlerVersion`, 128);
125
+ return {
126
+ contentHash: value.contentHash,
127
+ size: value.size as number,
128
+ mediaType: "application/javascript",
129
+ bundlerVersion: value.bundlerVersion as string,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * The exact v1 decoder for what comes back across the bundler binding. The
135
+ * bundler is a separate Worker, so its answer is an inbound value at a
136
+ * cross-runtime seam and is decoded before a byte of it becomes an artifact.
137
+ */
138
+ export function decodePackageBundleResultV1(
139
+ input: unknown,
140
+ label = "package bundle result",
141
+ ): PackageBundleResultV1 {
142
+ const value = record(input, label);
143
+ if (value.schemaVersion !== 1)
144
+ throw new Error(`${label}.schemaVersion is unsupported`);
145
+ const effectId = boundedString(value.effectId, `${label}.effectId`, 200);
146
+ if (value.status === "bundled") {
147
+ exactKeys(
148
+ value,
149
+ [
150
+ "schemaVersion",
151
+ "effectId",
152
+ "status",
153
+ "artifact",
154
+ "module",
155
+ "diagnostics",
156
+ ],
157
+ label,
158
+ );
159
+ const artifact = decodePackageBundleArtifactV1(
160
+ value.artifact,
161
+ `${label}.artifact`,
162
+ );
163
+ if (typeof value.module !== "string" || value.module.length === 0)
164
+ throw new Error(`${label}.module must be a non-empty string`);
165
+ return {
166
+ schemaVersion: 1,
167
+ effectId,
168
+ status: "bundled",
169
+ artifact,
170
+ module: value.module,
171
+ diagnostics: diagnostics(value.diagnostics, `${label}.diagnostics`),
172
+ };
173
+ }
174
+ if (value.status === "failed") {
175
+ exactKeys(
176
+ value,
177
+ ["schemaVersion", "effectId", "status", "failure", "diagnostics"],
178
+ label,
179
+ );
180
+ return {
181
+ schemaVersion: 1,
182
+ effectId,
183
+ status: "failed",
184
+ failure: boundedString(value.failure, `${label}.failure`, 256),
185
+ diagnostics: diagnostics(value.diagnostics, `${label}.diagnostics`),
186
+ };
187
+ }
188
+ throw new Error(`${label}.status is invalid`);
189
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from "./authoring.js";
2
+ export * from "./isolate.js";
3
+ export * from "./model-invocation.js";
4
+ export * from "./prompt-assembly.js";
5
+ export * from "./send-to-user.js";
6
+ export * from "./session.js";
7
+ export * from "./skills.js";
8
+ export * from "./tool-execution.js";
9
+ export * from "./turn-history.js";
10
+ export * from "./types.js";
11
+ export * from "./workspace.js";