diff --git a/README.md b/README.md index 1d4e95f..4f667e0 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Load conductor from the GitHub repository rather than from npm. The GitHub sourc ```json "dependencies": { ... - "conductor": "https://github.com/source-academy/conductor.git#0.3.0", + "conductor": "https://github.com/source-academy/conductor.git#0.4.0", ... }, ... ``` @@ -72,9 +72,37 @@ Consult [`language-directory` repository](https://github.com/source-academy/lang ### Sending messages To send messages between the runner and host, use these methods: -- `sendResult(result: string)` — sends evaluation results as a string over the `__result` channel. +- `sendResult(result: ChannelValue)` — sends evaluation results over the `__result` channel. - `sendError(error: ConductorError)` — sends errors over the `__error` channel. -- `sendOutput(message: string)` — sends standard output/log messages over the `__stdio` channel. +- `sendOutput(message: ChannelValue)` — sends standard output/log messages over the `__stdio` channel. + +A `ChannelValue` carries a mandatory `replString` for display and an optional `payload` for rich rendering: + +```ts +interface ChannelValue { + replString: string; // always displayable as text + payload?: ResultPayload; // optional typed data for rich rendering +} + +interface ResultPayload { + type: string; // discriminant for frontend renderers +} +``` + +For example, an evaluator that produces a numeric result: +```ts +sendResult({ replString: "42" }); +``` + +Or a result with a rich payload that the frontend can render specially: +```ts +sendResult({ + replString: "[Rune]", + payload: { type: "rune", rune: runeObject } +}); +``` + +If the frontend does not have a renderer for the payload type, it falls back to displaying `replString`. These methods are available on the relevant runner plugin classes. Call the messaging methods on the plugin instance you receive in your evaluator or plugin code. @@ -156,6 +184,8 @@ The host (i.e., the frontend REPL) formats messages differently depending on the - Output (from `__stdio`) appear in orange. Used to send IO messages (e.g. display calls). - Results (from `__result`) appear in the default color. Used to display program return results. +The `__result` and `__stdio` channels carry `ChannelValue` objects, which always include a `replString` for text display and may include a typed `payload` for rich rendering (e.g. interactive visualizations, media controls). The `__error` channel carries `ConductorError` objects. + If you are implementing your own `RunnerPlugin` (or similar plugin), you should use the `__result`, `__error`, and `__stdio` channels for results, errors, and output respectively to ensure compatibility with the host and consistent REPL formatting. diff --git a/src/conductor/host/BasicHostPlugin.ts b/src/conductor/host/BasicHostPlugin.ts index 4dd90c8..003a1ec 100644 --- a/src/conductor/host/BasicHostPlugin.ts +++ b/src/conductor/host/BasicHostPlugin.ts @@ -3,7 +3,7 @@ import type { ConductorError } from "../../common/errors"; import { importExternalPlugin } from "../../common/util"; import { checkIsPluginClass, type PluginClass, makeRpc, type IChannel, type IConduit, type IPlugin } from "../../conduit"; import { InternalPluginId, InternalChannelName } from "../strings"; -import { type IChunkMessage, type IServiceMessage, RunnerStatus, ServiceMessageType, HelloServiceMessage, AbortServiceMessage, EntryServiceMessage, type Chunk, type IErrorMessage, type IStatusMessage, type IIOMessage } from "../types"; +import { type ChannelValue, type IChunkMessage, type IServiceMessage, RunnerStatus, ServiceMessageType, HelloServiceMessage, AbortServiceMessage, EntryServiceMessage, type Chunk, type IErrorMessage, type IStatusMessage, type IIOMessage } from "../types"; import type { IHostFileRpc, IHostPlugin, IHostPluginRpc } from "./types"; @checkIsPluginClass @@ -49,11 +49,11 @@ export abstract class BasicHostPlugin implements IHostPlugin { this.__chunkChannel.send({ id: this.__chunkCount++, chunk }); } - sendInput(message: string): void { + sendInput(message: ChannelValue): void { this.__ioChannel.send({ message }); } - receiveOutput?(message: string): void; + receiveOutput?(message: ChannelValue): void; receiveError?(message: ConductorError): void; diff --git a/src/conductor/host/types/IHostPlugin.ts b/src/conductor/host/types/IHostPlugin.ts index 2b2758a..be11335 100644 --- a/src/conductor/host/types/IHostPlugin.ts +++ b/src/conductor/host/types/IHostPlugin.ts @@ -1,6 +1,6 @@ import type { ConductorError } from "../../../common/errors"; import type { IPlugin, PluginClass } from "../../../conduit"; -import type { Chunk, RunnerStatus } from "../../types"; +import type { ChannelValue, Chunk, RunnerStatus } from "../../types"; export interface IHostPlugin extends IPlugin { /** @@ -39,25 +39,25 @@ export interface IHostPlugin extends IPlugin { * Send an input on standard-input. * @param input The input to be sent on standard-input. */ - sendInput(input: string): void; + sendInput(input: ChannelValue): void; // /** // * Request for some output on standard-output. // * @returns A promise resolving to the output received. // */ - // requestOutput(): Promise; + // requestOutput(): Promise; // /** // * Try to request for some output on standard-output. // * @returns The output received, or undefined if there is currently no output. // */ - // tryRequestOutput(): string | undefined; + // tryRequestOutput(): ChannelValue | undefined; /** * An event handler called when an output is received. * @param message The output received. */ - receiveOutput?(message: string): void; + receiveOutput?(message: ChannelValue): void; // /** // * Request for some output on standard-error. diff --git a/src/conductor/runner/RunnerPlugin.ts b/src/conductor/runner/RunnerPlugin.ts index 83f74ab..0011fda 100644 --- a/src/conductor/runner/RunnerPlugin.ts +++ b/src/conductor/runner/RunnerPlugin.ts @@ -6,7 +6,7 @@ import { type IConduit, type IChannelQueue, ChannelQueue, makeRpc, checkIsPlugin import type { IHostFileRpc, IHostPluginRpc } from "../host"; import type { IModulePlugin, ModuleClass } from "../module"; import { InternalChannelName, InternalPluginId } from "../strings"; -import { RunnerStatus, ServiceMessageType, HelloServiceMessage, AbortServiceMessage, type EntryServiceMessage, type Chunk, type IChunkMessage, type IErrorMessage, type IIOMessage, type IResultMessage, type IServiceMessage, type IStatusMessage, } from "../types"; +import { RunnerStatus, ServiceMessageType, HelloServiceMessage, AbortServiceMessage, type EntryServiceMessage, type ChannelValue, type Chunk, type IChunkMessage, type IErrorMessage, type IIOMessage, type IResultMessage, type IServiceMessage, type IStatusMessage, } from "../types"; import { type EvaluatorClass, type IEvaluator, type IInterfacableEvaluator, type IRunnerPlugin } from "./types"; @checkIsPluginClass @@ -52,21 +52,21 @@ export class RunnerPlugin implements IRunnerPlugin { return (await this.__chunkQueue.receive()).chunk; } - async requestInput(): Promise { + async requestInput(): Promise { const { message } = await this.__ioQueue.receive(); return message; } - tryRequestInput(): string | undefined { + tryRequestInput(): ChannelValue | undefined { const out = this.__ioQueue.tryReceive(); return out?.message; } - sendOutput(message: string): void { + sendOutput(message: ChannelValue): void { this.__ioQueue.send({ message }); } - sendResult(result: any): void { + sendResult(result: ChannelValue): void { this.__resultChannel.send({ result }); } diff --git a/src/conductor/runner/types/IRunnerPlugin.ts b/src/conductor/runner/types/IRunnerPlugin.ts index dd3a570..b1545d4 100644 --- a/src/conductor/runner/types/IRunnerPlugin.ts +++ b/src/conductor/runner/types/IRunnerPlugin.ts @@ -1,7 +1,7 @@ import type { ConductorError } from "../../../common/errors"; import type { IPlugin, PluginClass } from "../../../conduit"; import type { IModulePlugin, ModuleClass } from "../../module"; -import type { Chunk, RunnerStatus } from "../../types"; +import type { ChannelValue, Chunk, RunnerStatus } from "../../types"; export interface IRunnerPlugin extends IPlugin { /** @@ -21,25 +21,25 @@ export interface IRunnerPlugin extends IPlugin { * Request for some input on standard-input. * @returns A promise resolving to the input received. */ - requestInput(): Promise; + requestInput(): Promise; /** * Try to request for some input on standard-input. * @returns The input received, or undefined if there is currently no input. */ - tryRequestInput(): string | undefined; + tryRequestInput(): ChannelValue | undefined; /** * Sends a message on standard-output. * @param message The output message to send. */ - sendOutput(message: string): void; + sendOutput(message: ChannelValue): void; /** * Sends an evaluation result. * @param result The result to send. */ - sendResult(result: any): void; + sendResult(result: ChannelValue): void; /** * Sends an error. diff --git a/src/conductor/types/ChannelValue.ts b/src/conductor/types/ChannelValue.ts new file mode 100644 index 0000000..f95c4f4 --- /dev/null +++ b/src/conductor/types/ChannelValue.ts @@ -0,0 +1,11 @@ +import type { ResultPayload } from "./ResultPayload"; + +/** + * A serializable value carried on user-facing channels + * (__result, __stdio). Always includes a string representation; + * optionally includes a typed payload for rich rendering. + */ +export interface ChannelValue { + replString: string; + payload?: ResultPayload; +} diff --git a/src/conductor/types/IIOMessage.ts b/src/conductor/types/IIOMessage.ts index d09b739..fbe6c39 100644 --- a/src/conductor/types/IIOMessage.ts +++ b/src/conductor/types/IIOMessage.ts @@ -1,4 +1,5 @@ +import type { ChannelValue } from "./ChannelValue"; + export interface IIOMessage { - // stream: number; - message: string; + message: ChannelValue; } diff --git a/src/conductor/types/IResultMessage.ts b/src/conductor/types/IResultMessage.ts index 78af101..18c1dcd 100644 --- a/src/conductor/types/IResultMessage.ts +++ b/src/conductor/types/IResultMessage.ts @@ -1,3 +1,5 @@ +import type { ChannelValue } from "./ChannelValue"; + export interface IResultMessage { - result: any; + result: ChannelValue; } diff --git a/src/conductor/types/ResultPayload.ts b/src/conductor/types/ResultPayload.ts new file mode 100644 index 0000000..b1dfba3 --- /dev/null +++ b/src/conductor/types/ResultPayload.ts @@ -0,0 +1,8 @@ +/** + * Base interface for rich result payloads. + * The conductor treats this opaquely — concrete payload + * types are defined downstream (e.g. in modules). + */ +export interface ResultPayload { + type: string; +} diff --git a/src/conductor/types/index.ts b/src/conductor/types/index.ts index 516902d..4e05ed1 100644 --- a/src/conductor/types/index.ts +++ b/src/conductor/types/index.ts @@ -1,11 +1,13 @@ export * from "./moduleInterface"; export * from "./serviceMessages"; +export type { ChannelValue } from "./ChannelValue"; export type { Chunk } from "./Chunk"; export type { IChunkMessage } from "./IChunkMessage"; export type { IErrorMessage } from "./IErrorMessage"; export type { IIOMessage } from "./IIOMessage"; export type { IResultMessage } from "./IResultMessage"; +export type { ResultPayload } from "./ResultPayload"; export type { IServiceMessage } from "./IServiceMessage"; export type { IStatusMessage } from "./IStatusMessage"; export { RunnerStatus } from "./RunnerStatus";