Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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",
...
}, ...
```
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.


Expand Down
6 changes: 3 additions & 3 deletions src/conductor/host/BasicHostPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
10 changes: 5 additions & 5 deletions src/conductor/host/types/IHostPlugin.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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<string>;
// requestOutput(): Promise<ChannelValue>;

// /**
// * 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.
Expand Down
10 changes: 5 additions & 5 deletions src/conductor/runner/RunnerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,21 +52,21 @@ export class RunnerPlugin implements IRunnerPlugin {
return (await this.__chunkQueue.receive()).chunk;
}

async requestInput(): Promise<string> {
async requestInput(): Promise<ChannelValue> {
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 });
}

Expand Down
10 changes: 5 additions & 5 deletions src/conductor/runner/types/IRunnerPlugin.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand All @@ -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<string>;
requestInput(): Promise<ChannelValue>;

/**
* 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.
Expand Down
11 changes: 11 additions & 0 deletions src/conductor/types/ChannelValue.ts
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 3 additions & 2 deletions src/conductor/types/IIOMessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ChannelValue } from "./ChannelValue";

export interface IIOMessage {
// stream: number;
message: string;
message: ChannelValue;
}
4 changes: 3 additions & 1 deletion src/conductor/types/IResultMessage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ChannelValue } from "./ChannelValue";

export interface IResultMessage {
result: any;
result: ChannelValue;
}
8 changes: 8 additions & 0 deletions src/conductor/types/ResultPayload.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions src/conductor/types/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The export for ResultPayload is currently out of alphabetical order. To maintain consistency and improve readability, it should be moved after IStatusMessage.

export type { IServiceMessage } from "./IServiceMessage";
export type { IStatusMessage } from "./IStatusMessage";
export { RunnerStatus } from "./RunnerStatus";
Expand Down