Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- chore: update json-as and remove hack [#857](https://github.com/hypermodeinc/modus/pull/857)
- chore: rename agent lifecycle methods and APIs [#858](https://github.com/hypermodeinc/modus/pull/858)
- feat: enforce WASI reactor mode [#859](https://github.com/hypermodeinc/modus/pull/859)
- feat: return user and chat errors in API response [#863](https://github.com/hypermodeinc/modus/pull/863)
- feat: return user and chat errors in API response [#863](https://github.com/hypermodeinc/modus/pull/863) [#864](https://github.com/hypermodeinc/modus/pull/864)
- feat: list agents on health endpoint [#865](https://github.com/hypermodeinc/modus/pull/865)
- feat: add agent management APIs [#866](https://github.com/hypermodeinc/modus/pull/866)

Expand Down
16 changes: 15 additions & 1 deletion sdk/assemblyscript/src/assembly/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,18 @@ export interface ModelFactory {
getModel<T extends Model>(modelName: string): T;
}


@json
export abstract class ModelError {
abstract toString(): string;
}

export abstract class Model<TInput = unknown, TOutput = unknown> {
static invoker: ModelInvoker | null = null;
protected constructor(public info: ModelInfo) {}

debug: boolean = false;
validator: ((data: string) => ModelError | null) | null = null;

/**
* Invokes the model with the given input.
Expand All @@ -81,14 +88,21 @@ export abstract class Model<TInput = unknown, TOutput = unknown> {
}

const outputJson = Model.invoker(modelName, inputJson);
if (!outputJson) {
if (outputJson === null) {
throw new Error(`Failed to invoke ${modelName} model.`);
}

if (this.debug) {
console.debug(`Received output: ${outputJson}`);
}

if (this.validator) {
const err = this.validator(outputJson);
if (err !== null) {
utils.throwUserError(`The chat model returned an error: ${err}`);
}
}

return JSON.parse<TOutput>(outputJson);
}
}
Expand Down
13 changes: 13 additions & 0 deletions sdk/assemblyscript/src/assembly/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Checks if the result is invalid, by determining if it is a null pointer.
* This will work for any managed type, regardless of whether it is nullable or not.
*/
export function resultIsInvalid<T>(result: T): bool {
return changetype<usize>(result) == 0;
}

/**
* Logs an error intended for the user, and exits.
* The message will be displayed in the API response, and in the console logs.
*/
export function throwUserError(message: string): void {
console.error(message);
process.exit(1);
}
49 changes: 48 additions & 1 deletion sdk/assemblyscript/src/models/openai/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Model } from "../../assembly/models";
import { Model, ModelError } from "../../assembly/models";
import { JSON } from "json-as";
import * as base64 from "as-base64/assembly";

Expand All @@ -24,6 +24,7 @@ export class OpenAIChatModel extends Model<OpenAIChatInput, OpenAIChatOutput> {
* @returns An input object that can be passed to the `invoke` method.
*/
createInput(messages: RequestMessage[]): OpenAIChatInput {
this.validator = validateChatModelResponse;
const model = this.info.fullName.toLowerCase();
return <OpenAIChatInput>{ model, messages };
}
Expand Down Expand Up @@ -1587,3 +1588,49 @@ export function parseMessages(data: string): RequestMessage[] {
(msg) => new RawMessage(msg.toString()),
);
}

// Validates the response from the chat model output.
function validateChatModelResponse(data: string): ModelError | null {
if (data.length == 0) {
throw new Error("No response received from model invocation");
}

const obj = JSON.parse<JSON.Obj>(data);
const error = obj.get("error");
if (error) {
return JSON.parse<ChatModelError>(error.toString());
// return error.get<ChatModelError>(); // todo: this should work instead of the above line, but fails as of json-as v1.1.14
}

return null;
}

/**
* Represents an error returned from the OpenAI Chat API.
*/
@json
class ChatModelError extends ModelError {
/**
* The error type.
*/
type: string = "";

/**
* A human-readable description of the error.
*/
message: string = "";

/**
* The parameter related to the error, if any.
*/
param: string | null = null;

/**
* The error code, if any.
*/
code: string | null = null;

toString(): string {
return this.message;
}
}