Skip to content
← Public packages

@kody/codex

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

src/sessions/create.ts

140 lines · 4.5 KB · TypeScript
import { any, boolean, number, object, optional, parse, string } from 'remix/data-schema'
import {
	DOCS_DEFAULT_MODEL,
	DOCS_QUICKSTART,
	agentsFetch,
	normalizeUserInput,
	pickAuth,
	sessionIdFromCreateResult,
} from '../client.ts'

const createInput = object(
	{
		/** Agent config: model, instructions, tools, multi_agent, … */
		agent: optional(any()),
		/** Environment: { type: 'openai_hosted' | 'self_hosted' | 'none', … } */
		environment: optional(any()),
		/** Initial task: string or message array */
		input: optional(any()),
		/** When true, request SSE from create (bounded collect). Default false for isolate-safe JSON. */
		stream: optional(boolean()),
		/** Max SSE events to collect when stream=true (default 100). */
		maxEvents: optional(number()),
		apiKeySecret: optional(string()),
		dryRun: optional(boolean()),
		/**
		 * Extra top-level create body fields from OpenAI docs (e.g. webhook config).
		 * Merged after known keys; unknownKeys on this object are still rejected —
		 * put extras here explicitly.
		 */
		body: optional(any()),
	},
	{ unknownKeys: 'error' },
)

/**
 * Create an OpenAI Agents API session (`POST /v1/agents/sessions`).
 * Main path: pass agent + environment + input. Defaults to non-streaming JSON;
 * set `stream: true` to collect a bounded first-turn SSE event list.
 *
 * @param raw.agent - Agent config (model, instructions, tools, …)
 * @param raw.environment - Environment object (`openai_hosted` | `self_hosted` | `none`)
 * @param raw.input - Initial user task (string or message array)
 * @param raw.stream - Collect first-turn SSE events (bounded)
 * @param raw.maxEvents - Cap on SSE events when streaming
 * @param raw.dryRun - Preview request without calling OpenAI
 * @param raw.apiKeySecret - Optional alternate secret **name**
 * @param raw.body - Extra documented create fields merged into the POST body
 * @returns Session JSON and/or collected events; includes `sessionId` when found
 *
 * @example
 * import createSession from 'kody:@kody/codex/sessions/create'
 * const result = await createSession({
 *   agent: {
 *     model: 'gpt-6-astra',
 *     instructions: 'Write clean code, run it, and report the actual output.',
 *   },
 *   environment: { type: 'openai_hosted' },
 *   input: 'Create tree.py that prints a directory tree, run it, show output.',
 * })
 */
export default async function createSession(raw: unknown = {}) {
	const input = parse(createInput, raw ?? {})
	const auth = pickAuth(input)
	const stream = input.stream === true

	const agent =
		input.agent && typeof input.agent === 'object' && !Array.isArray(input.agent)
			? (input.agent as Record<string, unknown>)
			: undefined
	const environment =
		input.environment &&
		typeof input.environment === 'object' &&
		!Array.isArray(input.environment)
			? (input.environment as Record<string, unknown>)
			: undefined
	const normalizedInput = normalizeUserInput(input.input)

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

	const body: Record<string, unknown> = { ...extras }
	if (agent) body.agent = agent
	if (environment) body.environment = environment
	if (normalizedInput !== undefined) body.input = normalizedInput
	if (stream) body.stream = true

	if (input.dryRun === true) {
		return {
			dryRun: true as const,
			method: 'POST' as const,
			path: '/v1/agents/sessions',
			headers: {
				'OpenAI-Beta': 'agents=v1',
				Authorization: 'Bearer {{secret:…}}',
			},
			body,
			docs: DOCS_QUICKSTART,
			modelPlaceholder: DOCS_DEFAULT_MODEL,
			note: 'Would create an Agents API session. Prefer openai_hosted for the quickstart sandbox.',
		}
	}

	if (!agent) {
		throw new Error(
			'agent is required (at least { model, instructions }). See ' + DOCS_QUICKSTART,
		)
	}
	if (!environment) {
		throw new Error(
			'environment is required ({ type: "openai_hosted" | "self_hosted" | "none" }).',
		)
	}
	if (environment.type === 'none' && normalizedInput === undefined) {
		throw new Error(
			'Sessions with environment.type "none" require initial input (OpenAI docs).',
		)
	}

	const response = await agentsFetch('/agents/sessions', {
		...auth,
		method: 'POST',
		body,
		expectSse: stream,
		maxSseEvents: input.maxEvents ?? 100,
	})

	const sessionId = sessionIdFromCreateResult(response.body, response.events)
	return {
		ok: true,
		sessionId,
		streamed: stream,
		status: response.status,
		kind: response.kind,
		session: stream ? null : response.body,
		events: response.events ?? null,
		docs: DOCS_QUICKSTART,
	}
}