Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on stability and extensibility.
## Project Overview

Runt is a TypeScript/Deno library for building runtime agents that connect to
[Anode notebooks](https://github.com/rgbkrk/anode). It uses LiveStore for
[Anode notebooks](https://github.com/runtimed/anode). It uses LiveStore for
event-sourcing and real-time sync between multiple users.

**Current Status**: Working system with 58 passing tests. Core functionality is
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Runt

Runtime agents for connecting to
[Anode notebooks](https://github.com/rgbkrk/anode) with real-time collaboration,
rich output support, and AI integration.
[Anode notebooks](https://github.com/runtimed/anode) with real-time
collaboration, rich output support, and AI integration.

## Features

Expand Down Expand Up @@ -50,7 +50,7 @@ deno task ci
# Run tests only
deno task test

# Run example agent
# Run example agent
deno task dev
```

Expand Down
6 changes: 3 additions & 3 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
},
"lock": false,
"tasks": {
"test": "DENO_TESTING=true RUNT_LOG_LEVEL=ERROR RUNT_DISABLE_CONSOLE_LOGS=true deno test --allow-all --reporter=dot",
"test:watch": "DENO_TESTING=true deno test --allow-all --watch",
"test:coverage": "DENO_TESTING=true deno test --allow-all --coverage=cov/ --reporter=dot && deno coverage --lcov cov/ > cov.lcov",
"test": "DENO_TESTING=true RUNT_LOG_LEVEL=ERROR RUNT_DISABLE_CONSOLE_LOGS=true deno test --allow-all --unstable-broadcast-channel --reporter=dot",
"test:watch": "DENO_TESTING=true deno test --allow-all --unstable-broadcast-channel --watch",
"test:coverage": "DENO_TESTING=true deno test --allow-all --unstable-broadcast-channel --coverage=cov/ --reporter=dot && deno coverage --lcov cov/ > cov.lcov",
"check": "deno check $(find packages -name '*.ts' -not -path '*/tui/*' | tr '\n' ' ')",
"check:tui": "deno check $(find packages/tui -name '*.ts' -o -name '*.tsx' | tr '\n' ' ') --config packages/tui/deno.json",
"fmt": "deno fmt",
Expand Down
12 changes: 6 additions & 6 deletions packages/lib/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
"@std/encoding": "jsr:@std/encoding@^1.0.0",
"@std/fs": "jsr:@std/fs@^1.0.0",
"@std/path": "jsr:@std/path@^1.0.0",
"npm:@livestore/adapter-node": "npm:@livestore/adapter-node@^0.3.1",
"npm:@livestore/adapter-web": "npm:@livestore/adapter-web@^0.3.1",
"npm:@livestore/livestore": "npm:@livestore/livestore@^0.3.1",
"npm:@livestore/sync-cf": "npm:@livestore/sync-cf@^0.3.1",
"npm:@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0"
},
"tasks": {
"dev": "deno run --allow-net --allow-env examples/echo-agent.ts",
"test": "deno test --allow-net --allow-env --allow-read --allow-sys",
"test:unit": "deno test --allow-net --allow-env --allow-read --allow-sys src/",
"test:integration": "deno test --allow-net --allow-env --allow-read --allow-sys test/",
"test:watch": "deno test --allow-net --allow-env --allow-read --allow-sys --watch",
"test:summary": "echo '🧪 Running @runt/lib test suite...' && deno test --allow-net --allow-env --allow-read --allow-sys --reporter=pretty && echo '✅ All tests completed!'",
"test": "deno test --allow-net --allow-env --allow-read --allow-sys --unstable-broadcast-channel",
"test:unit": "deno test --allow-net --allow-env --allow-read --allow-sys --unstable-broadcast-channel src/",
"test:integration": "deno test --allow-net --allow-env --allow-read --allow-sys --unstable-broadcast-channel test/",
"test:watch": "deno test --allow-net --allow-env --allow-read --allow-sys --unstable-broadcast-channel --watch",
"test:summary": "echo '🧪 Running @runt/lib test suite...' && deno test --allow-net --allow-env --allow-read --allow-sys --unstable-broadcast-channel --reporter=pretty && echo '✅ All tests completed!'",
"check": "deno check mod.ts src/*.ts examples/*.ts",
"fmt": "deno fmt",
"lint": "deno lint"
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ArtifactClient } from "./artifact-client.ts";
* Default configuration values
*/
export const DEFAULT_CONFIG = {
syncUrl: "wss://anode-docworker.rgbkrk.workers.dev",
syncUrl: "wss://app.runt.run",
imageArtifactThresholdBytes: 6 * 1024, // 6KB threshold for uploading images as artifacts
} as const;

Expand Down
25 changes: 8 additions & 17 deletions packages/lib/src/runtime-agent.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// RuntimeAgent - Base class for building Anode runtime agents

import { makeAdapter } from "npm:@livestore/adapter-node";
import {
createStorePromise,
queryDb,
type Store,
} from "npm:@livestore/livestore";
import { makeCfSync } from "npm:@livestore/sync-cf";

import {
events,
type ImageMimeType,
Expand Down Expand Up @@ -81,8 +80,13 @@ export class RuntimeAgent {
const clientId = this.config.clientId;
logger.info("Using clientId", { clientId });

// Create store with appropriate adapter and sync payload
const adapter = this.config.adapter || this.createDefaultAdapter();
// Create store with required adapter
const adapter = this.config.adapter;
if (!adapter) {
throw new Error(
"RuntimeAgent requires an adapter to be provided via config.adapter",
);
}

this.#store = await createStorePromise({
adapter,
Expand Down Expand Up @@ -165,19 +169,6 @@ export class RuntimeAgent {
}
}

/**
* Create the default adapter with Cloudflare sync
*/
private createDefaultAdapter() {
return makeAdapter({
storage: { type: "in-memory" },
sync: {
backend: makeCfSync({ url: this.config.syncUrl }),
onSyncError: "ignore",
},
});
}

/**
* Shutdown the runtime agent and clean up resources
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export interface RuntimeAgentOptions {
readonly imageArtifactThresholdBytes?: number;
/** Artifact client for dependency injection (optional) */
readonly artifactClient?: IArtifactClient;
/** Custom LiveStore adapter to use instead of default */
readonly adapter?: Adapter;
/** LiveStore adapter (required) */
readonly adapter: Adapter;
/** Client ID for sync payload (must be provided) */
readonly clientId: string;
}
Expand Down
30 changes: 8 additions & 22 deletions packages/lib/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// mocked dependencies to test the core integration points.

import { assertEquals, assertExists } from "jsr:@std/assert";
import { makeAdapter } from "npm:@livestore/adapter-node";
import { makeInMemoryAdapter } from "npm:@livestore/adapter-web";

import { RuntimeAgent } from "../src/runtime-agent.ts";
import { RuntimeConfig } from "../src/config.ts";
Expand Down Expand Up @@ -52,9 +52,7 @@ Deno.test("RuntimeAgent Integration Tests", async (t) => {
onExecutionError: createMockFunction(),
};

const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

config = new RuntimeConfig({
runtimeId: "integration-test-runtime",
Expand Down Expand Up @@ -147,9 +145,7 @@ Deno.test("RuntimeAgent Integration Tests", async (t) => {
await t.step("configuration validation", async (t) => {
setup();
await t.step("should accept valid configuration", () => {
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const validConfig = new RuntimeConfig({
runtimeId: "valid-runtime",
Expand All @@ -172,9 +168,7 @@ Deno.test("RuntimeAgent Integration Tests", async (t) => {
let error: Error | null = null;

try {
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = new RuntimeConfig({
runtimeId: "", // Invalid empty runtime ID
Expand Down Expand Up @@ -249,9 +243,7 @@ Deno.test("RuntimeAgent Integration Tests", async (t) => {

Deno.test("RuntimeConfig", async (t) => {
await t.step("should create valid config with all required fields", () => {
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = new RuntimeConfig({
runtimeId: "test-runtime-3",
Expand All @@ -278,9 +270,7 @@ Deno.test("RuntimeConfig", async (t) => {
});

await t.step("should generate unique session IDs", () => {
const adapter1 = makeAdapter({
storage: { type: "in-memory" },
});
const adapter1 = makeInMemoryAdapter({});

const config1 = new RuntimeConfig({
runtimeId: "runtime1",
Expand All @@ -297,9 +287,7 @@ Deno.test("RuntimeConfig", async (t) => {
},
});

const adapter2 = makeAdapter({
storage: { type: "in-memory" },
});
const adapter2 = makeInMemoryAdapter({});

const config2 = new RuntimeConfig({
runtimeId: "runtime2",
Expand All @@ -321,9 +309,7 @@ Deno.test("RuntimeConfig", async (t) => {
});

await t.step("should allow custom heartbeat interval", () => {
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const _config = new RuntimeConfig({
runtimeId: "test-ai-runtime",
Expand Down
5 changes: 4 additions & 1 deletion packages/lib/test/mod.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert";
import { DEFAULT_CONFIG, RuntimeAgent, RuntimeConfig } from "@runt/lib";
import { makeInMemoryAdapter } from "npm:@livestore/adapter-web";

Deno.test("Library exports are available", () => {
// Test that main exports are defined
Expand All @@ -13,7 +14,7 @@ Deno.test("Library exports are available", () => {
Deno.test("DEFAULT_CONFIG has expected values", () => {
assertEquals(
DEFAULT_CONFIG.syncUrl,
"wss://anode-docworker.rgbkrk.workers.dev",
"wss://app.runt.run",
);
});

Expand All @@ -27,6 +28,7 @@ Deno.test("RuntimeConfig validation works", () => {
authToken: "", // Missing
notebookId: "", // Missing
clientId: "test-client",
adapter: makeInMemoryAdapter({}),
capabilities: {
canExecuteCode: true,
canExecuteSql: false,
Expand All @@ -50,6 +52,7 @@ Deno.test("RuntimeConfig validation works", () => {
authToken: "test-token",
notebookId: "test-notebook",
clientId: "test-client",
adapter: makeInMemoryAdapter({}),
capabilities: {
canExecuteCode: true,
canExecuteSql: false,
Expand Down
24 changes: 10 additions & 14 deletions packages/lib/test/runtime-agent-adapter-injection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import {
type RuntimeCapabilities,
RuntimeConfig,
} from "@runt/lib";
import { makeInMemoryAdapter } from "npm:@livestore/adapter-web";
import { makeAdapter } from "npm:@livestore/adapter-node";

// Helper function for creating test configs since createBaseRuntimeConfig moved to pyodide package
function createTestRuntimeConfig(
_args: string[],
defaults: Partial<RuntimeAgentOptions> = {},
): RuntimeConfig {
// Create default in-memory adapter for testing
const defaultAdapter = makeInMemoryAdapter({});

const config: RuntimeAgentOptions = {
runtimeId: "test-runtime-id",
runtimeType: "test-runtime",
Expand All @@ -33,6 +37,7 @@ function createTestRuntimeConfig(
canExecuteAi: false,
},
clientId: "test-client",
adapter: defaultAdapter,
...defaults,
};
return new RuntimeConfig(config);
Expand Down Expand Up @@ -64,8 +69,7 @@ Deno.test("RuntimeAgent adapter injection", async (t) => {

await t.step("should accept custom in-memory adapter", async () => {
// Create custom in-memory adapter
const adapter = makeAdapter({
storage: { type: "in-memory" },
const adapter = makeInMemoryAdapter({
// No sync backend needed for pure in-memory testing
});

Expand Down Expand Up @@ -100,9 +104,7 @@ Deno.test("RuntimeAgent adapter injection", async (t) => {
"should accept custom adapter without explicit clientId",
async () => {
// Create custom in-memory adapter
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = createTestRuntimeConfig([], {
adapter,
Expand Down Expand Up @@ -131,9 +133,7 @@ Deno.test("RuntimeAgent adapter injection", async (t) => {

await t.step("should generate clientId for custom adapter", async () => {
const runtimeId = `runtime-${crypto.randomUUID()}`;
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = createTestRuntimeConfig([], {
adapter,
Expand Down Expand Up @@ -161,9 +161,7 @@ Deno.test("RuntimeAgent adapter injection", async (t) => {
});

await t.step("should use explicit clientId when provided", async () => {
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const explicitClientId = "my-custom-client-id";

Expand Down Expand Up @@ -192,9 +190,7 @@ Deno.test("RuntimeAgent adapter injection", async (t) => {

await t.step("should handle multiple agents with same adapter", async () => {
// Create shared adapter
const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

// Create two agents using the same adapter
const config1 = createTestRuntimeConfig([], {
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/test/runtime-agent-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { RuntimeAgent } from "../src/runtime-agent.ts";
import { RuntimeConfig } from "../src/config.ts";
import type { RuntimeAgentOptions } from "../src/types.ts";
import type { ImageMimeType, MediaContainer } from "@runt/schema";
import { makeInMemoryAdapter } from "npm:@livestore/adapter-web";

// Testing interface to access private methods
interface RuntimeAgentWithTestMethods {
Expand Down Expand Up @@ -73,6 +74,7 @@ const mockRuntimeOptions: RuntimeAgentOptions = {
notebookId: "test-notebook",
clientId: "test-client",
imageArtifactThresholdBytes: 6 * 1024, // 6KB threshold
adapter: makeInMemoryAdapter({}),
};

// Mock fetch for testing
Expand Down
14 changes: 4 additions & 10 deletions packages/lib/test/runtime-agent-text-representations.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "@std/assert";
import { makeAdapter } from "npm:@livestore/adapter-node";
import { makeInMemoryAdapter } from "npm:@livestore/adapter-web";
import { RuntimeAgent } from "../src/runtime-agent.ts";
import { RuntimeConfig } from "../src/config.ts";
import {
Expand Down Expand Up @@ -36,9 +36,7 @@ Deno.test("RuntimeAgent Text Representations for Artifacts", async (t) => {
canExecuteAi: false,
};

const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = new RuntimeConfig({
runtimeId: "test-runtime",
Expand Down Expand Up @@ -151,9 +149,7 @@ Deno.test("RuntimeAgent Text Representations for Artifacts", async (t) => {
canExecuteAi: false,
};

const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = new RuntimeConfig({
runtimeId: "test-runtime",
Expand Down Expand Up @@ -268,9 +264,7 @@ Deno.test("RuntimeAgent Text Representations for Artifacts", async (t) => {
canExecuteAi: false,
};

const adapter = makeAdapter({
storage: { type: "in-memory" },
});
const adapter = makeInMemoryAdapter({});

const config = new RuntimeConfig({
runtimeId: "test-runtime",
Expand Down
Loading