Skip to content
Open
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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"node": ">=24 <25"
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"build": "tsc -p tsconfig.build.json && node scripts/copy-schemas.mjs && node scripts/copy-build-assets.mjs",
"check": "npm run typecheck && npm test && npm run check:python",
"check:python": "python3 -m unittest discover -s python/tests -v && python3 -m py_compile python/modeling_agent/*.py python/tests/*.py",
"cli": "tsx src/cli/main.ts",
Expand Down
20 changes: 20 additions & 0 deletions scripts/copy-build-assets.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cp, mkdir, rm } from "node:fs/promises";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

const root = fileURLToPath(new URL("../", import.meta.url));
const dist = resolve(root, "dist");
const pythonSource = resolve(root, "python");
const pythonDestination = resolve(dist, "python");
const pythonPackageDestination = resolve(pythonDestination, "modeling_agent");
const pythonModules = ["__init__.py", "forecasting.py", "io.py", "metrics.py", "runner.py"];

// Compiled workers and report generation resolve these assets relative to dist/src.
await rm(pythonDestination, { recursive: true, force: true });
await mkdir(pythonPackageDestination, { recursive: true });
await Promise.all([
...pythonModules.map((filename) => cp(resolve(pythonSource, "modeling_agent", filename), resolve(pythonPackageDestination, filename))),
cp(resolve(pythonSource, "requirements.lock"), resolve(pythonDestination, "requirements.lock")),
cp(resolve(pythonSource, "standalone.Dockerfile"), resolve(pythonDestination, "standalone.Dockerfile")),
cp(resolve(pythonSource, "standalone_reproduce.py"), resolve(pythonDestination, "standalone_reproduce.py"))
]);
10 changes: 10 additions & 0 deletions scripts/copy-schemas.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { cp, rm } from "node:fs/promises";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

const root = fileURLToPath(new URL("../", import.meta.url));
const source = resolve(root, "schemas");
const destination = resolve(root, "dist", "schemas");

await rm(destination, { recursive: true, force: true });
await cp(source, destination, { recursive: true });
10 changes: 8 additions & 2 deletions src/contracts/schema-registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { Ajv2020 } from "ajv/dist/2020.js";
import type { ErrorObject, ValidateFunction } from "ajv";
Expand All @@ -22,6 +23,11 @@ const schemaFiles: Record<SchemaName, string> = {
"evidence-graph": "evidence-graph.v1.json"
};

function defaultSchemaDirectory(): string {
// This resolves to schemas/ for tsx and dist/schemas/ after compilation.
return fileURLToPath(new URL("../../schemas/", import.meta.url));
}

export class ContractValidationError extends Error {
readonly schemaName: SchemaName;
readonly validationErrors: ErrorObject[];
Expand All @@ -43,15 +49,15 @@ export class SchemaRegistry {
readonly #ajv: InstanceType<typeof Ajv2020>;
readonly #validators = new Map<SchemaName, ValidateFunction>();

constructor(schemaDirectory = fileURLToPath(new URL("../../schemas/", import.meta.url))) {
constructor(schemaDirectory = defaultSchemaDirectory()) {
this.#ajv = new Ajv2020({ allErrors: true, strict: true, allowUnionTypes: true });
this.#ajv.addFormat("date-time", {
type: "string",
validate: (value: string) => !Number.isNaN(Date.parse(value))
});

for (const [name, filename] of Object.entries(schemaFiles) as Array<[SchemaName, string]>) {
const schema = JSON.parse(readFileSync(new URL(filename, `file://${schemaDirectory}/`), "utf8")) as object;
const schema = JSON.parse(readFileSync(resolve(schemaDirectory, filename), "utf8")) as object;
this.#validators.set(name, this.#ajv.compile(schema));
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/runtime/factory.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
import { SchemaRegistry } from "../contracts/schema-registry.js";
import { FakeRuntimeAdapter } from "./fake-runtime.js";
import { PiRuntimeAdapter } from "./pi-runtime.js";
import type { AgentRuntime } from "./types.js";

type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";

export interface RuntimeFactoryOptions {
kind: "fake" | "pi";
provider?: string;
model?: string;
thinkingLevel?: ThinkingLevel;
}

export function createRuntime(options: RuntimeFactoryOptions): AgentRuntime {
if (options.kind === "fake") return new FakeRuntimeAdapter();
export function createRuntime(options: RuntimeFactoryOptions, schemas = new SchemaRegistry()): AgentRuntime {
if (options.kind === "fake") return new FakeRuntimeAdapter(schemas);
return new PiRuntimeAdapter({
...(options.provider ? { provider: options.provider } : {}),
...(options.model ? { model: options.model } : {}),
...(options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {})
});
}, schemas);
}
22 changes: 22 additions & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const DEFAULT_SERVER_HOST = "127.0.0.1";
export const DEFAULT_SERVER_PORT = 4317;

export interface ServerConfig {
host: string;
port: number;
runsRoot: string;
}

export function readServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {
const host = env.MODELING_AGENT_HOST ?? DEFAULT_SERVER_HOST;
const rawPort = env.MODELING_AGENT_PORT;
const port = rawPort === undefined ? DEFAULT_SERVER_PORT : Number(rawPort);
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
throw new Error("MODELING_AGENT_PORT must be an integer between 1 and 65535.");
}
return {
host,
port,
runsRoot: env.MODELING_AGENT_RUNS_ROOT ?? "runs"
};
}
Loading