Skip to content
← Public packages

@cameronpak/zo-computer

Typed Zo Computer API client with Result-based error handling and SSE streaming.

src/call.ts

75 lines · 2.0 KB · TypeScript
import { Result } from 'better-result'
import ask from './ask'
import listModels from './list-models'
import listPersonas from './list-personas'
import type { ZoAskInput, ZoClientOptions } from './types'

/**
 * A `Result` flattened into plain JSON.
 */
export type ZoWireResult =
	| { status: 'ok'; value: unknown }
	| { status: 'error'; error: Record<string, unknown> }

type CallParams = ZoClientOptions &
	Partial<ZoAskInput> & {
		/** Which operation to run. */
		operation: 'ask' | 'listModels' | 'listPersonas'
	}

function toWire(result: Result<unknown, { _tag: string }>): ZoWireResult {
	if (Result.isOk(result)) return { status: 'ok', value: result.value }

	const error = result.error as {
		_tag: string
		message?: string
		toJSON?: () => Record<string, unknown>
	}
	const payload =
		typeof error.toJSON === 'function'
			? error.toJSON()
			: { _tag: error._tag, message: error.message ?? '' }

	const { stack: _stack, name: _name, ...rest } = payload
	return { status: 'error', error: { ...rest, _tag: error._tag } }
}

/**
 * Run one operation and return plain JSON.
 *
 * A `Result` is a class instance, so it cannot cross a `packages.invoke`
 * boundary. Use this export from `packages.invoke`. Use the direct exports
 * when you import the package.
 *
 * @example
 * await packages.invoke({
 *   kodyId: 'zo-computer',
 *   exportName: './call',
 *   params: { operation: 'ask', input: 'Hello' },
 * })
 * // => { status: 'ok', value: { output: '...', conversationId: '...' } }
 */
export default async function call(params: CallParams): Promise<ZoWireResult> {
	const operation = params?.operation

	if (operation === 'listModels') {
		return toWire(await listModels(params))
	}

	if (operation === 'listPersonas') {
		return toWire(await listPersonas(params))
	}

	if (operation === 'ask') {
		return toWire(await ask(params as ZoAskInput & ZoClientOptions))
	}

	return {
		status: 'error',
		error: {
			_tag: 'ZoConfigError',
			field: 'operation',
			message: 'operation must be ask, listModels, or listPersonas',
		},
	}
}