Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.

Commit 6ffb96a

Browse files
feat: return user and chat errors in API response (AssemblyScript)
1 parent 9566a5c commit 6ffb96a

File tree

4 files changed

+80
-3
lines changed

4 files changed

+80
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- chore: update json-as and remove hack [#857](https://github.com/hypermodeinc/modus/pull/857)
88
- chore: rename agent lifecycle methods and APIs [#858](https://github.com/hypermodeinc/modus/pull/858)
99
- feat: enforce WASI reactor mode [#859](https://github.com/hypermodeinc/modus/pull/859)
10-
- feat: return user and chat errors in API response [#863](https://github.com/hypermodeinc/modus/pull/863)
10+
- 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)
1111

1212
## 2025-05-22 - Go SDK 0.18.0-alpha.3
1313

sdk/assemblyscript/src/assembly/models.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,18 @@ export interface ModelFactory {
5858
getModel<T extends Model>(modelName: string): T;
5959
}
6060

61+
62+
@json
63+
export abstract class ModelError {
64+
abstract toString(): string;
65+
}
66+
6167
export abstract class Model<TInput = unknown, TOutput = unknown> {
6268
static invoker: ModelInvoker | null = null;
6369
protected constructor(public info: ModelInfo) {}
6470

6571
debug: boolean = false;
72+
validator: ((data: string) => ModelError | null) | null = null;
6673

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

8390
const outputJson = Model.invoker(modelName, inputJson);
84-
if (!outputJson) {
91+
if (outputJson === null) {
8592
throw new Error(`Failed to invoke ${modelName} model.`);
8693
}
8794

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

99+
if (this.validator) {
100+
const err = this.validator(outputJson);
101+
if (err !== null) {
102+
utils.throwUserError(`The chat model returned an error: ${err}`);
103+
}
104+
}
105+
92106
return JSON.parse<TOutput>(outputJson);
93107
}
94108
}

sdk/assemblyscript/src/assembly/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
* SPDX-License-Identifier: Apache-2.0
88
*/
99

10+
/**
11+
* Checks if the result is invalid, by determining if it is a null pointer.
12+
* This will work for any managed type, regardless of whether it is nullable or not.
13+
*/
1014
export function resultIsInvalid<T>(result: T): bool {
1115
return changetype<usize>(result) == 0;
1216
}
17+
18+
/**
19+
* Logs an error intended for the user, and exits.
20+
* The message will be displayed in the API response, and in the console logs.
21+
*/
22+
export function throwUserError(message: string): void {
23+
console.error(message);
24+
process.exit(1);
25+
}

sdk/assemblyscript/src/models/openai/chat.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* SPDX-License-Identifier: Apache-2.0
88
*/
99

10-
import { Model } from "../../assembly/models";
10+
import { Model, ModelError } from "../../assembly/models";
1111
import { JSON } from "json-as";
1212
import * as base64 from "as-base64/assembly";
1313

@@ -24,6 +24,7 @@ export class OpenAIChatModel extends Model<OpenAIChatInput, OpenAIChatOutput> {
2424
* @returns An input object that can be passed to the `invoke` method.
2525
*/
2626
createInput(messages: RequestMessage[]): OpenAIChatInput {
27+
this.validator = validateChatModelResponse;
2728
const model = this.info.fullName.toLowerCase();
2829
return <OpenAIChatInput>{ model, messages };
2930
}
@@ -1585,3 +1586,52 @@ export function parseMessages(data: string): RequestMessage[] {
15851586
(msg) => new RawMessage(msg.toString()),
15861587
);
15871588
}
1589+
1590+
// Validates the response from the chat model output.
1591+
function validateChatModelResponse(data: string): ModelError | null {
1592+
if (data.length == 0) {
1593+
throw new Error("No response received from model invocation");
1594+
}
1595+
1596+
// hack until json-as issues are resolved
1597+
if (data.startsWith(`{\n "error": {`)) {
1598+
return JSON.parse<ChatModelError>(data.substring(13, data.length - 2));
1599+
}
1600+
1601+
// const obj = JSON.parse<JSON.Obj>(data);
1602+
// if (obj.has("error")) {
1603+
// return obj.get("error")!.get<ChatModelError>();
1604+
// }
1605+
1606+
return null;
1607+
}
1608+
1609+
/**
1610+
* Represents an error returned from the OpenAI Chat API.
1611+
*/
1612+
@json
1613+
class ChatModelError extends ModelError {
1614+
/**
1615+
* The error type.
1616+
*/
1617+
type: string = "";
1618+
1619+
/**
1620+
* A human-readable description of the error.
1621+
*/
1622+
message: string = "";
1623+
1624+
/**
1625+
* The parameter related to the error, if any.
1626+
*/
1627+
param: string | null = null;
1628+
1629+
/**
1630+
* The error code, if any.
1631+
*/
1632+
code: string | null = null;
1633+
1634+
toString(): string {
1635+
return this.message;
1636+
}
1637+
}

0 commit comments

Comments
 (0)