Skip to content
← Public packages

@kody/codex

Create and manage OpenAI Agents API (Codex harness) cloud agent sessions.

src/environments/self-hosted.ts

108 lines · 3.6 KB · TypeScript
import { any, boolean, object, optional, parse, string } from 'remix/data-schema'
import { DOCS_DEFAULT_MODEL, DOCS_OVERVIEW } from '../client.ts'

const selfHostedInput = object(
	{
		workspaceDirectory: optional(string()),
		capabilityDirectories: optional(any()),
		/** Extra environment fields from docs */
		environment: optional(any()),
		/** Optional agent snippet for a create payload preview */
		agent: optional(any()),
		model: optional(string()),
		instructions: optional(string()),
		input: optional(any()),
		/** When true, return a sessions/create-shaped body preview only */
		asCreateBody: optional(boolean()),
		dryRun: optional(boolean()),
	},
	{ unknownKeys: 'error' },
)

/**
 * Self-hosted environment recipe metadata for Agents API
 * (`environment.type: self_hosted`). Not a fake Puppeteer/browser runner —
 * documents workspace_directory / capability_directories and returns a
 * create-body preview you pass to `./sessions/create` from your own runtime.
 *
 * @param raw.workspaceDirectory - Default `/workspace`
 * @param raw.capabilityDirectories - Skills/capability paths (string[])
 * @param raw.environment - Extra self_hosted fields
 * @param raw.agent / model / instructions / input - Optional create preview fields
 * @param raw.asCreateBody - Include a ready sessions/create body preview
 * @returns Recipe + optional create body (never calls OpenAI by itself)
 *
 * @example
 * import selfHosted from 'kody:@kody/codex/environments/self-hosted'
 * const recipe = await selfHosted({
 *   workspaceDirectory: '/workspace',
 *   capabilityDirectories: ['/workspace/capabilities/skills'],
 *   asCreateBody: true,
 *   input: 'Research MCP setup and summarize.',
 * })
 */
export default async function selfHosted(raw: unknown = {}) {
	const input = parse(selfHostedInput, raw ?? {})
	const workspaceDirectory = (input.workspaceDirectory || '/workspace').trim()
	let capabilityDirectories: string[] = ['/workspace/capabilities/skills']
	if (Array.isArray(input.capabilityDirectories)) {
		capabilityDirectories = input.capabilityDirectories.map((d) => String(d))
	}

	const envExtras =
		input.environment &&
		typeof input.environment === 'object' &&
		!Array.isArray(input.environment)
			? (input.environment as Record<string, unknown>)
			: {}

	const environment = {
		...envExtras,
		type: 'self_hosted',
		workspace_directory: workspaceDirectory,
		capability_directories: capabilityDirectories,
	}

	const agentExtras =
		input.agent && typeof input.agent === 'object' && !Array.isArray(input.agent)
			? (input.agent as Record<string, unknown>)
			: {}
	const agent = {
		model: input.model || DOCS_DEFAULT_MODEL,
		instructions:
			input.instructions ||
			'Use available tools and MCP servers. Delegate independent research to subagents when useful.',
		...agentExtras,
	}

	const recipe = {
		mode: 'recipe' as const,
		docs: DOCS_OVERVIEW,
		environment,
		notes: [
			'Your application (or OpenAI connector) must host/connect the environment — this export only shapes metadata.',
			'Does not implement Puppeteer, CDP, or ChatGPT task scraping.',
			'Pass the returned createBody to kody:@kody/codex/sessions/create when ready.',
			'Self-hosted does not make the Agents API ZDR-eligible (OpenAI data controls).',
		],
		nextStep:
			'import createSession from "kody:@kody/codex/sessions/create"; await createSession(createBody)',
	}

	const wantBody = input.asCreateBody === true || input.dryRun === true
	if (!wantBody) {
		return recipe
	}

	const createBody: Record<string, unknown> = {
		agent,
		environment,
	}
	if (input.input !== undefined) createBody.input = input.input

	return {
		...recipe,
		createBody,
		dryRun: true as const,
	}
}