← Public packages
@kody/codex
Create and manage OpenAI Agents API (Codex harness) cloud agent sessions.
src/sessions/input.ts
105 lines · 3.1 KB · TypeScriptimport { any, boolean, object, optional, parse, string } from 'remix/data-schema'
import {
DOCS_SESSIONS,
agentsFetch,
pickAuth,
requireSessionId,
userTextMessage,
} from '../client.ts'
const inputSchema = object(
{
sessionId: string(),
/** Plain text follow-up — wrapped as agent.session.input.message */
text: optional(string()),
/** When true, send agent.session.input.cancel instead of a message */
cancel: optional(boolean()),
/**
* Full `events` array for the POST body when you need function results
* or custom event shapes. Mutually exclusive with text/cancel shortcuts.
*/
events: optional(any()),
apiKeySecret: optional(string()),
dryRun: optional(boolean()),
},
{ unknownKeys: 'error' },
)
/**
* Send input or cancel on an existing session
* (`POST /v1/agents/sessions/{session_id}/events`).
* Prefer opening `./events/stream` before follow-up so early events are not
* missed. `text` steers an active turn or starts a new one when idle.
*
* @param raw.sessionId - Session id
* @param raw.text - User follow-up text (shortcut for input.message)
* @param raw.cancel - Send turn cancel instead of a message
* @param raw.events - Full events array when not using text/cancel
* @param raw.dryRun - Preview without calling OpenAI
* @param raw.apiKeySecret - Optional alternate secret name
* @returns API result or dry-run preview
*
* @example
* import sendInput from 'kody:@kody/codex/sessions/input'
* await sendInput({
* sessionId: 'sess_123',
* text: 'Add a max-depth option to tree.py, run it, show output.',
* })
*/
export default async function sendInput(raw: unknown = {}) {
const input = parse(inputSchema, raw ?? {})
const auth = pickAuth(input)
const sessionId = requireSessionId(input.sessionId)
const path = `/agents/sessions/${encodeURIComponent(sessionId)}/events`
let events: unknown[]
if (input.events !== undefined) {
if (!Array.isArray(input.events)) {
throw new Error('events must be an array of Agents API input events.')
}
if (input.text !== undefined || input.cancel === true) {
throw new Error('Pass either events or text/cancel, not both.')
}
events = input.events
} else if (input.cancel === true) {
events = [{ type: 'agent.session.input.cancel' }]
} else if (input.text !== undefined) {
const text = String(input.text).trim()
if (!text) throw new Error('text must be a non-empty string.')
events = [
{
type: 'agent.session.input.message',
input: userTextMessage(text),
},
]
} else {
throw new Error('Provide text, cancel: true, or an events array.')
}
const body = { events }
if (input.dryRun === true) {
return {
dryRun: true as const,
method: 'POST' as const,
path: `/v1${path}`,
sessionId,
body,
docs: DOCS_SESSIONS,
note: 'Open events/stream on this session before sending live follow-up when you need early events.',
}
}
const response = await agentsFetch(path, {
...auth,
method: 'POST',
body,
})
return {
ok: true,
sessionId,
result: response.body,
status: response.status,
hint: 'If you need live progress, call events/stream before the next input.',
}
}