Files
zopu-code/packages/primitives/src/agent-os.ts
T

123 lines
4.2 KiB
TypeScript

/* eslint-disable max-classes-per-file -- the AgentOS service and its tagged error form one adapter contract. */
import codex from "@agentos-software/codex-cli";
import { agentOS as createAgentOsActor } from "@rivet-dev/agentos";
import type { AgentOSConfigInput } from "@rivet-dev/agentos";
import { Context, Effect, Layer, Schema } from "effect";
import { getExecutableGitSoftware } from "./agent-os-git-software";
const withGitSoftware = (
config: AgentOSConfigInput<undefined>
): AgentOSConfigInput<undefined> => ({
...config,
software: [getExecutableGitSoftware(), ...(config.software ?? [])],
});
export class AgentOsError extends Schema.TaggedErrorClass<AgentOsError>()(
"AgentOsError",
{
cause: Schema.Defect(),
}
) {}
export interface AgentOsOptions {
readonly config?: AgentOSConfigInput<undefined>;
}
export class AgentOs extends Context.Service<
AgentOs,
{
readonly createActor: (
config?: AgentOSConfigInput<undefined>
) => Effect.Effect<
ReturnType<typeof createAgentOsActor<undefined>>,
AgentOsError
>;
}
>()("@code/primitives/agent-os/AgentOs") {
static readonly layer = Layer.succeed(
AgentOs,
AgentOs.of({
createActor: Effect.fn("AgentOs.createActor")(function* createActor(
config: AgentOSConfigInput<undefined> = {}
) {
return yield* Effect.try({
catch: (cause) => new AgentOsError({ cause }),
try: () => createAgentOsActor<undefined>(withGitSoftware(config)),
});
}),
})
);
}
export const makeAgentOsLayer = (options: AgentOsOptions = {}) =>
Layer.succeed(
AgentOs,
AgentOs.of({
createActor: Effect.fn("AgentOs.createActor")(function* createActor(
config: AgentOSConfigInput<undefined> = options.config ?? {}
) {
return yield* Effect.try({
catch: (cause) => new AgentOsError({ cause }),
try: () => createAgentOsActor<undefined>(withGitSoftware(config)),
});
}),
})
);
export const createAgentOsActorEffect = Effect.fn("createAgentOsActorEffect")(
function* createAgentOsActorEffect(config?: AgentOSConfigInput<undefined>) {
const agentOs = yield* AgentOs;
return yield* agentOs.createActor(config);
}
);
// ---------------------------------------------------------------------------
// Codex harness configuration
//
// The canonical execution registry runs exactly one AgentOS actor for the
// puter/zopu-code repo, booted with the Codex CLI harness + git. The model
// provider is injected as VM env (OpenAI-compatible base URL + key) so Codex
// authenticates against the configured gateway without a second credentials
// path. This is a pure config builder; the RivetKit registry/server that hosts
// the actor lives in the agents package.
// ---------------------------------------------------------------------------
/** Software bundle for a Codex-capable VM: the Codex harness plus git. */
export const codexSoftware = [codex, getExecutableGitSoftware()] as const;
/** Model-provider env that Codex reads inside the VM. Pass this to the session's
* `env` when opening a Codex session (`agent: "codex"`). */
export interface CodexModelEnv {
readonly apiKey: string;
readonly baseUrl: string;
readonly model: string;
}
/** Builds the VM env record Codex reads to authenticate against the model gateway. */
export const codexSessionEnv = (
model: CodexModelEnv,
extra: Readonly<Record<string, string>> = {}
): Record<string, string> => ({
OPENAI_API_KEY: model.apiKey,
OPENAI_BASE_URL: model.baseUrl,
...extra,
});
export interface CodexAgentOsConfigInput {
/** Extra software merged after the Codex+git bundle. */
readonly software?: NonNullable<AgentOSConfigInput<undefined>["software"]>;
}
/**
* Builds an AgentOS actor config for a Codex-backed VM. The Codex CLI and git
* are always present; software the caller passes is appended, never replacing
* the harness or git. Model-provider env is supplied per session via
* `codexSessionEnv`, not here — the actor config has no env field.
*/
export const makeCodexAgentOsConfig = (
input: CodexAgentOsConfigInput = {}
): AgentOSConfigInput<undefined> => ({
software: [...codexSoftware, ...(input.software ?? [])],
});