@asterai/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @asterai/cli
20
20
  $ asterai COMMAND
21
21
  running command...
22
22
  $ asterai (--version)
23
- @asterai/cli/0.4.0 linux-x64 node-v20.12.2
23
+ @asterai/cli/0.5.0 linux-x64 node-v20.12.2
24
24
  $ asterai --help [COMMAND]
25
25
  USAGE
26
26
  $ asterai COMMAND
@@ -32,11 +32,10 @@ USAGE
32
32
 
33
33
  <!-- commands -->
34
34
  * [`asterai auth KEY`](#asterai-auth-key)
35
- * [`asterai build [INPUT]`](#asterai-build-input)
36
- * [`asterai codegen`](#asterai-codegen)
37
- * [`asterai deploy [INPUT]`](#asterai-deploy-input)
35
+ * [`asterai deploy`](#asterai-deploy)
38
36
  * [`asterai help [COMMAND]`](#asterai-help-command)
39
37
  * [`asterai init [OUTDIR]`](#asterai-init-outdir)
38
+ * [`asterai pkg [INPUT]`](#asterai-pkg-input)
40
39
  * [`asterai query`](#asterai-query)
41
40
 
42
41
  ## `asterai auth KEY`
@@ -54,74 +53,31 @@ EXAMPLES
54
53
  $ asterai auth
55
54
  ```
56
55
 
57
- _See code: [src/commands/auth.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/auth.ts)_
56
+ _See code: [src/commands/auth.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.5.0/src/commands/auth.ts)_
58
57
 
59
- ## `asterai build [INPUT]`
58
+ ## `asterai deploy`
60
59
 
61
- compiles the plugin
60
+ uploads a plugin to asterai
62
61
 
63
62
  ```
64
63
  USAGE
65
- $ asterai build [INPUT] [-m <value>]
64
+ $ asterai deploy [-a <value>] [-e <value>] [-s] [--plugin <value>] [--pkg <value>]
66
65
 
67
66
  FLAGS
68
- -m, --manifest=<value> [default: plugin.asterai.proto] manifest path
69
-
70
- DESCRIPTION
71
- compiles the plugin
72
-
73
- EXAMPLES
74
- $ asterai build
75
- ```
76
-
77
- _See code: [src/commands/build.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/build.ts)_
78
-
79
- ## `asterai codegen`
80
-
81
- Generate code from the plugin manifest
82
-
83
- ```
84
- USAGE
85
- $ asterai codegen [-m <value>] [-o <value>] [-a <value>] [-l <value>] [-s]
86
-
87
- FLAGS
88
- -a, --appId=<value> app id
89
- -l, --language=<value> [default: js] language of generated typings
90
- -m, --manifest=<value> [default: plugin.asterai.proto] manifest path
91
- -o, --outputDir=<value> [default: generated] output directory
92
- -s, --staging use staging endpoint
93
-
94
- DESCRIPTION
95
- Generate code from the plugin manifest
96
-
97
- EXAMPLES
98
- $ asterai codegen
99
- ```
100
-
101
- _See code: [src/commands/codegen.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/codegen.ts)_
102
-
103
- ## `asterai deploy [INPUT]`
104
-
105
- compiles and uploads the plugin to asterai
106
-
107
- ```
108
- USAGE
109
- $ asterai deploy [INPUT] -a <value> [-m <value>] [-e <value>] [-s]
110
-
111
- FLAGS
112
- -a, --app=<value> (required) app ID to immediately configure this plugin with
113
- -e, --endpoint=<value> [default: https://api.asterai.io/app/plugin]
114
- -m, --manifest=<value> [default: plugin.asterai.proto] manifest path
67
+ -a, --agent=<value> agent ID to immediately activate this plugin for
68
+ -e, --endpoint=<value> [default: https://api.asterai.io]
115
69
  -s, --staging
70
+ --pkg=<value> [default: package.wasm] package WASM path
71
+ --plugin=<value> [default: plugin.wasm] plugin WASM path
116
72
 
117
73
  DESCRIPTION
118
- compiles and uploads the plugin to asterai
74
+ uploads a plugin to asterai
119
75
 
120
76
  EXAMPLES
121
77
  $ asterai deploy --app 66a46b12-b1a7-4b72-a64a-0e4fe21902b6
122
78
  ```
123
79
 
124
- _See code: [src/commands/deploy.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/deploy.ts)_
80
+ _See code: [src/commands/deploy.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.5.0/src/commands/deploy.ts)_
125
81
 
126
82
  ## `asterai help [COMMAND]`
127
83
 
@@ -158,7 +114,32 @@ EXAMPLES
158
114
  $ asterai init project-name
159
115
  ```
160
116
 
161
- _See code: [src/commands/init.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/init.ts)_
117
+ _See code: [src/commands/init.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.5.0/src/commands/init.ts)_
118
+
119
+ ## `asterai pkg [INPUT]`
120
+
121
+ bundles the WIT into a binary WASM package
122
+
123
+ ```
124
+ USAGE
125
+ $ asterai pkg [INPUT] [-o <value>] [-w <value>] [-e <value>]
126
+
127
+ ARGUMENTS
128
+ INPUT [default: plugin.wit] path to the plugin's WIT file
129
+
130
+ FLAGS
131
+ -e, --endpoint=<value> [default: https://api.asterai.io]
132
+ -o, --output=<value> [default: package.wasm] output file name for the binary WASM package
133
+ -w, --wit=<value> [default: package.wit] output package converted to the WIT format
134
+
135
+ DESCRIPTION
136
+ bundles the WIT into a binary WASM package
137
+
138
+ EXAMPLES
139
+ $ asterai pkg
140
+ ```
141
+
142
+ _See code: [src/commands/pkg.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.5.0/src/commands/pkg.ts)_
162
143
 
163
144
  ## `asterai query`
164
145
 
@@ -181,5 +162,5 @@ EXAMPLES
181
162
  $ asterai query
182
163
  ```
183
164
 
184
- _See code: [src/commands/query.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.4.0/src/commands/query.ts)_
165
+ _See code: [src/commands/query.ts](https://github.com/asterai-io/asterai-sdk/blob/v0.5.0/src/commands/query.ts)_
185
166
  <!-- commandsstop -->
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "plugin",
3
- "version": "1.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
- "author": "asterai",
6
- "license": "MIT",
5
+ "author": "",
6
+ "license": "UNLICENSED",
7
+ "type": "module",
7
8
  "scripts": {
8
9
  "auth": "asterai auth",
9
- "codegen": "asterai codegen",
10
- "build": "asterai build",
10
+ "gen:pkg": "mkdirp build && asterai pkg -o build/package.wasm -w build/package.wit",
11
+ "gen:types": "jco guest-types build/package.wit -n plugin -o generated/",
12
+ "compile": "tsc",
13
+ "componentize": "jco componentize build/plugin.js -w build/package.wit -n plugin -o build/plugin.wasm",
14
+ "build": "run-s gen:pkg gen:types compile componentize",
11
15
  "deploy": "asterai deploy"
12
16
  },
13
- "type": "module",
14
17
  "devDependencies": {
15
18
  "@asterai/cli": "latest",
16
19
  "@asterai/sdk": "latest",
17
- "@asterai/as-proto": "latest",
18
- "@asterai/as-proto-gen": "latest"
19
- },
20
- "peerDependencies": {
21
- "assemblyscript": "0.27.27"
20
+ "@bytecodealliance/jco": "^1.9.1",
21
+ "@types/node": "^22.13.4",
22
+ "mkdirp": "^3.0.1",
23
+ "npm-run-all": "^4.1.5",
24
+ "typescript": "^5.7.3"
22
25
  }
23
26
  }
@@ -1,35 +1,11 @@
1
- import { BinaryOperationInput } from "./generated/BinaryOperationInput";
2
- import { CalculationOutput } from "./generated/CalculationOutput";
3
- import { PluginContext } from "./generated/PluginContext";
4
- import { ProcessQueryOutput } from "./generated/ProcessQueryOutput";
5
- import { Log } from "@asterai/sdk";
1
+ import { BinaryOperationInput } from "./generated/plugin";
2
+ import * as asterai from "asterai:host/api@0.1.0";
6
3
 
7
- export function processQuery(input: PluginContext): ProcessQueryOutput {
8
- Log.info(`math plugin received a query: ${input.query.content}`);
9
- // This doesn't return any data, but protobuf requires functions
10
- // to always have one input and one output exactly to ensure
11
- // backward compatibility.
12
- return new ProcessQueryOutput();
13
- }
14
-
15
- export function add(input: BinaryOperationInput): CalculationOutput {
4
+ export const add = (input: BinaryOperationInput): number => {
16
5
  const result = input.a + input.b;
17
- // CalculationOutput returns a system message.
18
- // The `system_message` field is sent to the LLM.
19
- return new CalculationOutput(`the result is ${result}`);
20
- }
21
-
22
- export function mul(input: BinaryOperationInput): CalculationOutput {
23
- const result = input.a * input.b;
24
- return new CalculationOutput(`the result is ${result}`);
25
- }
26
-
27
- export function div(input: BinaryOperationInput): CalculationOutput {
28
- const result = input.a / input.b;
29
- return new CalculationOutput(`the result is ${result}`);
30
- }
31
-
32
- export function pow(input: BinaryOperationInput): CalculationOutput {
33
- const result = input.a ** input.b;
34
- return new CalculationOutput(`the result is ${result}`);
35
- }
6
+ // Send the calculation result to the agent.
7
+ asterai.sendResponseToAgent(`the result is ${result}`);
8
+ // This result is not seen by the agent, but it can be consumed by
9
+ // other plugins calling this function.
10
+ return result;
11
+ };
@@ -0,0 +1,12 @@
1
+ package example:math@0.1.0;
2
+
3
+ world plugin {
4
+ import asterai:host/api@0.1.0;
5
+
6
+ export add: func(input: binary-operation-input) -> s32;
7
+
8
+ record binary-operation-input {
9
+ a: f64,
10
+ b: f64,
11
+ }
12
+ }
@@ -1,4 +1,11 @@
1
1
  {
2
- "extends": "assemblyscript/std/assembly.json",
3
- "include": ["./**/*.ts"]
2
+ "include": ["./**/*.ts"],
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "paths": {
8
+ "asterai:host/api@0.1.0": ["./generated/interfaces/asterai-host-api.d.ts"]
9
+ }
10
+ }
4
11
  }
@@ -1,16 +1,14 @@
1
1
  import { Command } from "@oclif/core";
2
2
  export default class Deploy extends Command {
3
- static args: {
4
- input: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
5
- };
3
+ static args: {};
6
4
  static description: string;
7
5
  static examples: string[];
8
6
  static flags: {
9
- app: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
- manifest: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ agent: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
8
  endpoint: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
9
  staging: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
+ plugin: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
+ pkg: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
12
  };
14
13
  run(): Promise<void>;
15
14
  }
16
- export declare const mergeProtoImports: (proto: string, protoPath: string, excludeSdk?: boolean, excludeSyntaxDefinition?: boolean, n?: number) => string;
@@ -1,59 +1,57 @@
1
- import { Args, Command, Flags } from "@oclif/core";
1
+ import { Command, Flags } from "@oclif/core";
2
2
  import fs from "fs";
3
- import path from "path";
4
3
  import FormData from "form-data";
5
4
  import axios from "axios";
6
5
  import { getConfigValue } from "../config.js";
7
- import { build } from "./build.js";
8
- const PRODUCTION_ENDPOINT = "https://api.asterai.io/app/plugin";
9
- const STAGING_ENDPOINT = "https://staging.api.asterai.io/app/plugin";
6
+ import { BASE_API_URL, BASE_API_URL_STAGING } from "../const.js";
7
+ import path from "path";
8
+ // If the input file doesn't exist, try looking into this dir.
9
+ const RETRY_FIND_FILE_DIR = "build/";
10
10
  export default class Deploy extends Command {
11
- static args = {
12
- input: Args.string({
13
- default: "plugin.ts",
14
- }),
15
- };
16
- static description = "compiles and uploads the plugin to asterai";
11
+ static args = {};
12
+ static description = "uploads a plugin to asterai";
17
13
  static examples = [
18
14
  `<%= config.bin %> <%= command.id %> --app 66a46b12-b1a7-4b72-a64a-0e4fe21902b6`,
19
15
  ];
20
16
  static flags = {
21
- app: Flags.string({
17
+ agent: Flags.string({
22
18
  char: "a",
23
- description: "app ID to immediately configure this plugin with",
24
- required: true,
25
- }),
26
- manifest: Flags.string({
27
- char: "m",
28
- description: "manifest path",
29
- default: "plugin.asterai.proto",
19
+ description: "agent ID to immediately activate this plugin for",
20
+ required: false,
30
21
  }),
31
22
  endpoint: Flags.string({
32
23
  char: "e",
33
- default: PRODUCTION_ENDPOINT,
24
+ default: BASE_API_URL,
34
25
  }),
35
26
  staging: Flags.boolean({
36
27
  char: "s",
37
28
  }),
29
+ plugin: Flags.string({
30
+ description: "plugin WASM path",
31
+ default: "plugin.wasm",
32
+ }),
33
+ pkg: Flags.string({
34
+ description: "package WASM path",
35
+ default: "package.wasm",
36
+ }),
38
37
  };
39
38
  async run() {
40
- const { args, flags } = await this.parse(Deploy);
41
- const output = await build(args, flags);
42
- await deploy(output, flags);
39
+ const { flags } = await this.parse(Deploy);
40
+ await deploy(flags);
43
41
  }
44
42
  }
45
- const deploy = async (args, flags) => {
43
+ const deploy = async (flags) => {
46
44
  const form = new FormData();
47
- form.append("app_id", flags.app);
48
- form.append("module", fs.readFileSync(args.outputFile));
49
- const manifestString = fs.readFileSync(args.manifestPath, {
50
- encoding: "utf8",
51
- });
52
- const mergedManifestString = mergeProtoImports(manifestString, args.manifestPath);
53
- form.append("manifest", mergedManifestString);
54
- const url = flags.staging ? STAGING_ENDPOINT : flags.endpoint;
45
+ if (flags.agent) {
46
+ form.append("agent_id", flags.agent);
47
+ }
48
+ const plugin = readFile(flags.plugin);
49
+ const pkg = readFile(flags.pkg);
50
+ form.append("plugin.wasm", plugin);
51
+ form.append("package.wasm", pkg);
52
+ const baseApiUrl = flags.staging ? BASE_API_URL_STAGING : flags.endpoint;
55
53
  await axios({
56
- url,
54
+ url: `${baseApiUrl}/v1/plugin`,
57
55
  method: "put",
58
56
  data: form,
59
57
  headers: {
@@ -64,38 +62,17 @@ const deploy = async (args, flags) => {
64
62
  .then(() => console.log("done"))
65
63
  .catch(logRequestError);
66
64
  };
67
- export const mergeProtoImports = (proto, protoPath, excludeSdk = true, excludeSyntaxDefinition = true, n = 0) => {
68
- let mergedManifestString = "";
69
- const lines = proto.split("\n");
70
- for (let line of lines) {
71
- line = line.trim();
72
- const isSyntaxLine = line.startsWith("syntax");
73
- if (isSyntaxLine && (excludeSyntaxDefinition || n > 1)) {
74
- continue;
75
- }
76
- const isImportLine = line.startsWith("import");
77
- if (!isImportLine) {
78
- mergedManifestString = `${mergedManifestString}\n${line}`;
79
- continue;
80
- }
81
- const importLine = line.replaceAll("'", '"');
82
- const pathStart = importLine.indexOf('"') + 1;
83
- const pathEnd = importLine.lastIndexOf('"');
84
- const pathRelative = importLine.substring(pathStart, pathEnd);
85
- const isSdkImport = pathRelative.startsWith("node_modules/@asterai/sdk");
86
- if (isSdkImport && excludeSdk) {
87
- // Asterai protobuf definitions should not be uploaded
88
- // as part of the plugin manifest.
89
- continue;
90
- }
91
- const pathAbsolute = path.join(path.dirname(protoPath), pathRelative);
92
- const importProto = fs.readFileSync(pathAbsolute, { encoding: "utf8" });
93
- const importProtoMerged = mergeProtoImports(importProto, pathAbsolute, excludeSdk, excludeSyntaxDefinition, n + 1);
94
- mergedManifestString = `${mergedManifestString}\n${importProtoMerged}`;
95
- }
96
- return mergedManifestString.trim();
97
- };
98
65
  const logRequestError = (e) => {
99
66
  const info = e.response?.data ?? e;
100
67
  console.log("request error:", info);
101
68
  };
69
+ const readFile = (relativePath) => {
70
+ if (fs.existsSync(relativePath)) {
71
+ return fs.readFileSync(relativePath);
72
+ }
73
+ const retryPath = path.join(RETRY_FIND_FILE_DIR, relativePath);
74
+ if (fs.existsSync(retryPath)) {
75
+ return fs.readFileSync(retryPath);
76
+ }
77
+ throw new Error(`file not found: ${relativePath}`);
78
+ };
@@ -0,0 +1,27 @@
1
+ import { Command } from "@oclif/core";
2
+ export type PkgArgs = {
3
+ input: string;
4
+ };
5
+ export type PkgFlags = {
6
+ endpoint: string;
7
+ output: string;
8
+ wit?: string;
9
+ };
10
+ export type PkgOutput = {
11
+ outputFile: string;
12
+ witPath: string;
13
+ };
14
+ export default class Pkg extends Command {
15
+ static args: {
16
+ input: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
17
+ };
18
+ static flags: {
19
+ output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
20
+ wit: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
21
+ endpoint: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
22
+ };
23
+ static description: string;
24
+ static examples: string[];
25
+ run(): Promise<void>;
26
+ }
27
+ export declare const pkg: (args: PkgArgs, flags: PkgFlags) => Promise<PkgOutput>;
@@ -0,0 +1,98 @@
1
+ import { Args, Command, Flags } from "@oclif/core";
2
+ import path from "path";
3
+ import fs from "fs/promises";
4
+ import fsSync from "fs";
5
+ import { BASE_API_URL } from "../const.js";
6
+ import axios from "axios";
7
+ import FormData from "form-data";
8
+ import { getConfigValue } from "../config.js";
9
+ export default class Pkg extends Command {
10
+ static args = {
11
+ input: Args.string({
12
+ default: "plugin.wit",
13
+ description: "path to the plugin's WIT file",
14
+ }),
15
+ };
16
+ static flags = {
17
+ output: Flags.string({
18
+ char: "o",
19
+ default: "package.wasm",
20
+ description: "output file name for the binary WASM package",
21
+ }),
22
+ wit: Flags.string({
23
+ char: "w",
24
+ default: "package.wit",
25
+ description: "output package converted to the WIT format",
26
+ }),
27
+ endpoint: Flags.string({
28
+ char: "e",
29
+ default: BASE_API_URL,
30
+ }),
31
+ };
32
+ static description = "bundles the WIT into a binary WASM package";
33
+ static examples = [`<%= config.bin %> <%= command.id %>`];
34
+ async run() {
35
+ const { args, flags } = await this.parse(Pkg);
36
+ await pkg(args, flags);
37
+ }
38
+ }
39
+ export const pkg = async (args, flags) => {
40
+ const witPath = path.resolve(args.input);
41
+ if (!fsSync.existsSync(witPath)) {
42
+ throw new Error(`WIT file not found at ${witPath}`);
43
+ }
44
+ const baseDir = path.dirname(witPath);
45
+ const outputFile = path.join(baseDir, flags.output);
46
+ const form = new FormData();
47
+ form.append("package.wit", await fs.readFile(witPath));
48
+ const response = await axios({
49
+ url: `${flags.endpoint}/v1/pkg`,
50
+ method: "post",
51
+ data: form,
52
+ headers: {
53
+ Authorization: getConfigValue("key"),
54
+ ...form.getHeaders(),
55
+ },
56
+ responseType: "arraybuffer",
57
+ }).catch(catchAxiosError);
58
+ validateResponseStatus(response.status);
59
+ await fs.writeFile(outputFile, Buffer.from(response.data), {
60
+ encoding: "binary",
61
+ });
62
+ if (flags.wit) {
63
+ await wasm2wit(flags.endpoint, outputFile, path.join(baseDir, flags.wit));
64
+ }
65
+ return {
66
+ outputFile,
67
+ witPath,
68
+ };
69
+ };
70
+ const wasm2wit = async (endpoint, inputFilePath, outputFilePath) => {
71
+ const form = new FormData();
72
+ form.append("package.wasm", await fs.readFile(inputFilePath));
73
+ const response = await axios({
74
+ url: `${endpoint}/v1/wasm2wit`,
75
+ method: "post",
76
+ data: form,
77
+ headers: {
78
+ Authorization: getConfigValue("key"),
79
+ ...form.getHeaders(),
80
+ },
81
+ responseType: "text",
82
+ });
83
+ validateResponseStatus(response.status);
84
+ await fs.writeFile(outputFilePath, response.data, { encoding: "utf8" });
85
+ };
86
+ const validateResponseStatus = (status) => {
87
+ if (status < 200 || status >= 300) {
88
+ throw new Error("request failed");
89
+ }
90
+ };
91
+ const catchAxiosError = (error) => {
92
+ const data = error.response?.data?.toString() ?? "";
93
+ if (axios.isAxiosError(error) && data.length > 0) {
94
+ const errorMessage = data.replace(/\\n/g, "\n");
95
+ throw new Error(`Request failed: ${errorMessage}`);
96
+ }
97
+ throw new Error("Request failed");
98
+ };
@@ -0,0 +1,2 @@
1
+ export declare const BASE_API_URL = "https://api.asterai.io";
2
+ export declare const BASE_API_URL_STAGING = "https://staging.api.asterai.io";
package/dist/const.js ADDED
@@ -0,0 +1,2 @@
1
+ export const BASE_API_URL = "https://api.asterai.io";
2
+ export const BASE_API_URL_STAGING = "https://staging.api.asterai.io";
@@ -28,24 +28,49 @@
28
28
  "auth.js"
29
29
  ]
30
30
  },
31
- "build": {
31
+ "deploy": {
32
32
  "aliases": [],
33
- "args": {
34
- "input": {
35
- "default": "plugin.ts",
36
- "name": "input"
37
- }
38
- },
39
- "description": "compiles the plugin",
33
+ "args": {},
34
+ "description": "uploads a plugin to asterai",
40
35
  "examples": [
41
- "<%= config.bin %> <%= command.id %>"
36
+ "<%= config.bin %> <%= command.id %> --app 66a46b12-b1a7-4b72-a64a-0e4fe21902b6"
42
37
  ],
43
38
  "flags": {
44
- "manifest": {
45
- "char": "m",
46
- "description": "manifest path",
47
- "name": "manifest",
48
- "default": "plugin.asterai.proto",
39
+ "agent": {
40
+ "char": "a",
41
+ "description": "agent ID to immediately activate this plugin for",
42
+ "name": "agent",
43
+ "required": false,
44
+ "hasDynamicHelp": false,
45
+ "multiple": false,
46
+ "type": "option"
47
+ },
48
+ "endpoint": {
49
+ "char": "e",
50
+ "name": "endpoint",
51
+ "default": "https://api.asterai.io",
52
+ "hasDynamicHelp": false,
53
+ "multiple": false,
54
+ "type": "option"
55
+ },
56
+ "staging": {
57
+ "char": "s",
58
+ "name": "staging",
59
+ "allowNo": false,
60
+ "type": "boolean"
61
+ },
62
+ "plugin": {
63
+ "description": "plugin WASM path",
64
+ "name": "plugin",
65
+ "default": "plugin.wasm",
66
+ "hasDynamicHelp": false,
67
+ "multiple": false,
68
+ "type": "option"
69
+ },
70
+ "pkg": {
71
+ "description": "package WASM path",
72
+ "name": "pkg",
73
+ "default": "package.wasm",
49
74
  "hasDynamicHelp": false,
50
75
  "multiple": false,
51
76
  "type": "option"
@@ -53,7 +78,7 @@
53
78
  },
54
79
  "hasDynamicHelp": false,
55
80
  "hiddenAliases": [],
56
- "id": "build",
81
+ "id": "deploy",
57
82
  "pluginAlias": "@asterai/cli",
58
83
  "pluginName": "@asterai/cli",
59
84
  "pluginType": "core",
@@ -63,66 +88,25 @@
63
88
  "relativePath": [
64
89
  "dist",
65
90
  "commands",
66
- "build.js"
91
+ "deploy.js"
67
92
  ]
68
93
  },
69
- "codegen": {
94
+ "init": {
70
95
  "aliases": [],
71
- "args": {},
72
- "description": "Generate code from the plugin manifest",
73
- "examples": [
74
- "<%= config.bin %> <%= command.id %>"
75
- ],
76
- "flags": {
77
- "manifest": {
78
- "char": "m",
79
- "description": "manifest path",
80
- "name": "manifest",
81
- "default": "plugin.asterai.proto",
82
- "hasDynamicHelp": false,
83
- "multiple": false,
84
- "type": "option"
85
- },
86
- "outputDir": {
87
- "char": "o",
88
- "description": "output directory",
89
- "name": "outputDir",
90
- "default": "generated",
91
- "hasDynamicHelp": false,
92
- "multiple": false,
93
- "type": "option"
94
- },
95
- "appId": {
96
- "char": "a",
97
- "description": "app id",
98
- "name": "appId",
99
- "required": false,
100
- "hasDynamicHelp": false,
101
- "multiple": false,
102
- "type": "option"
103
- },
104
- "language": {
105
- "char": "l",
106
- "description": "language of generated typings",
107
- "name": "language",
108
- "required": false,
109
- "default": "js",
110
- "hasDynamicHelp": false,
111
- "multiple": false,
112
- "type": "option"
113
- },
114
- "staging": {
115
- "char": "s",
116
- "description": "use staging endpoint",
117
- "name": "staging",
118
- "required": false,
119
- "allowNo": false,
120
- "type": "boolean"
96
+ "args": {
97
+ "outDir": {
98
+ "default": "plugin",
99
+ "name": "outDir"
121
100
  }
122
101
  },
102
+ "description": "Initialise a new plugin project",
103
+ "examples": [
104
+ "<%= config.bin %> <%= command.id %> project-name"
105
+ ],
106
+ "flags": {},
123
107
  "hasDynamicHelp": false,
124
108
  "hiddenAliases": [],
125
- "id": "codegen",
109
+ "id": "init",
126
110
  "pluginAlias": "@asterai/cli",
127
111
  "pluginName": "@asterai/cli",
128
112
  "pluginType": "core",
@@ -132,36 +116,37 @@
132
116
  "relativePath": [
133
117
  "dist",
134
118
  "commands",
135
- "codegen.js"
119
+ "init.js"
136
120
  ]
137
121
  },
138
- "deploy": {
122
+ "pkg": {
139
123
  "aliases": [],
140
124
  "args": {
141
125
  "input": {
142
- "default": "plugin.ts",
126
+ "default": "plugin.wit",
127
+ "description": "path to the plugin's WIT file",
143
128
  "name": "input"
144
129
  }
145
130
  },
146
- "description": "compiles and uploads the plugin to asterai",
131
+ "description": "bundles the WIT into a binary WASM package",
147
132
  "examples": [
148
- "<%= config.bin %> <%= command.id %> --app 66a46b12-b1a7-4b72-a64a-0e4fe21902b6"
133
+ "<%= config.bin %> <%= command.id %>"
149
134
  ],
150
135
  "flags": {
151
- "app": {
152
- "char": "a",
153
- "description": "app ID to immediately configure this plugin with",
154
- "name": "app",
155
- "required": true,
136
+ "output": {
137
+ "char": "o",
138
+ "description": "output file name for the binary WASM package",
139
+ "name": "output",
140
+ "default": "package.wasm",
156
141
  "hasDynamicHelp": false,
157
142
  "multiple": false,
158
143
  "type": "option"
159
144
  },
160
- "manifest": {
161
- "char": "m",
162
- "description": "manifest path",
163
- "name": "manifest",
164
- "default": "plugin.asterai.proto",
145
+ "wit": {
146
+ "char": "w",
147
+ "description": "output package converted to the WIT format",
148
+ "name": "wit",
149
+ "default": "package.wit",
165
150
  "hasDynamicHelp": false,
166
151
  "multiple": false,
167
152
  "type": "option"
@@ -169,49 +154,15 @@
169
154
  "endpoint": {
170
155
  "char": "e",
171
156
  "name": "endpoint",
172
- "default": "https://api.asterai.io/app/plugin",
157
+ "default": "https://api.asterai.io",
173
158
  "hasDynamicHelp": false,
174
159
  "multiple": false,
175
160
  "type": "option"
176
- },
177
- "staging": {
178
- "char": "s",
179
- "name": "staging",
180
- "allowNo": false,
181
- "type": "boolean"
182
161
  }
183
162
  },
184
163
  "hasDynamicHelp": false,
185
164
  "hiddenAliases": [],
186
- "id": "deploy",
187
- "pluginAlias": "@asterai/cli",
188
- "pluginName": "@asterai/cli",
189
- "pluginType": "core",
190
- "strict": true,
191
- "enableJsonFlag": false,
192
- "isESM": true,
193
- "relativePath": [
194
- "dist",
195
- "commands",
196
- "deploy.js"
197
- ]
198
- },
199
- "init": {
200
- "aliases": [],
201
- "args": {
202
- "outDir": {
203
- "default": "plugin",
204
- "name": "outDir"
205
- }
206
- },
207
- "description": "Initialise a new plugin project",
208
- "examples": [
209
- "<%= config.bin %> <%= command.id %> project-name"
210
- ],
211
- "flags": {},
212
- "hasDynamicHelp": false,
213
- "hiddenAliases": [],
214
- "id": "init",
165
+ "id": "pkg",
215
166
  "pluginAlias": "@asterai/cli",
216
167
  "pluginName": "@asterai/cli",
217
168
  "pluginType": "core",
@@ -221,7 +172,7 @@
221
172
  "relativePath": [
222
173
  "dist",
223
174
  "commands",
224
- "init.js"
175
+ "pkg.js"
225
176
  ]
226
177
  },
227
178
  "query": {
@@ -280,5 +231,5 @@
280
231
  ]
281
232
  }
282
233
  },
283
- "version": "0.4.0"
234
+ "version": "0.5.0"
284
235
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@asterai/cli",
3
3
  "description": "CLI for building and deploying asterai plugins",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "author": "asterai <support@asterai.io>",
6
6
  "repository": "asterai-io/asterai-sdk",
7
7
  "homepage": "https://github.com/asterai-io/asterai-sdk",
@@ -1,22 +0,0 @@
1
- syntax = "proto3";
2
- import "node_modules/@asterai/sdk/protobuf/asterai.proto";
3
-
4
- service Math {
5
- rpc processQuery(PluginContext) returns (ProcessQueryOutput);
6
- rpc add(BinaryOperationInput) returns (CalculationOutput);
7
- rpc mul(BinaryOperationInput) returns (CalculationOutput);
8
- rpc div(BinaryOperationInput) returns (CalculationOutput);
9
- rpc pow(BinaryOperationInput) returns (CalculationOutput);
10
- }
11
-
12
- message ProcessQueryOutput {}
13
-
14
- message BinaryOperationInput {
15
- PluginContext context = 1;
16
- double a = 2;
17
- double b = 3;
18
- }
19
-
20
- message CalculationOutput {
21
- string system_message = 1;
22
- }
@@ -1,23 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- export type BuildArgs = {
3
- input: string;
4
- };
5
- export type BuildFlags = {
6
- manifest: string;
7
- };
8
- export type BuildOutput = {
9
- outputFile: string;
10
- manifestPath: string;
11
- };
12
- export default class Build extends Command {
13
- static args: {
14
- input: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
15
- };
16
- static description: string;
17
- static examples: string[];
18
- static flags: {
19
- manifest: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
20
- };
21
- run(): Promise<void>;
22
- }
23
- export declare const build: (args: BuildArgs, flags: BuildFlags) => Promise<BuildOutput>;
@@ -1,139 +0,0 @@
1
- import { Args, Command, Flags } from "@oclif/core";
2
- import path from "path";
3
- import fs from "fs";
4
- import { compile } from "../compile.js";
5
- import Mustache from "mustache";
6
- import protobuf from "protobufjs";
7
- import { mergeProtoImports } from "./deploy.js";
8
- export default class Build extends Command {
9
- static args = {
10
- input: Args.string({
11
- default: "plugin.ts",
12
- }),
13
- };
14
- static description = "compiles the plugin";
15
- static examples = [`<%= config.bin %> <%= command.id %>`];
16
- static flags = {
17
- manifest: Flags.string({
18
- char: "m",
19
- description: "manifest path",
20
- default: "plugin.asterai.proto",
21
- }),
22
- };
23
- async run() {
24
- const { args, flags } = await this.parse(Build);
25
- await build(args, flags);
26
- }
27
- }
28
- export const build = async (args, flags) => {
29
- const manifestPath = path.resolve(flags.manifest);
30
- const inputFile = path.resolve(args.input);
31
- if (!fs.existsSync(inputFile)) {
32
- throw new Error(`input file not found (${args.input})`);
33
- }
34
- const inputFileName = path.parse(inputFile).name;
35
- const baseDir = path.dirname(manifestPath);
36
- const outDir = path.join(baseDir, "build");
37
- const outputFile = path.join(outDir, `${inputFileName}.wasm`);
38
- const libsDir = path.join(baseDir, "node_modules");
39
- if (!fs.existsSync(libsDir)) {
40
- throw new Error("no node_modules found in the plugin directory");
41
- }
42
- const proto = fs.readFileSync(manifestPath, { encoding: "utf8" });
43
- const functionDescriptors = getPluginFunctionDescriptors(proto, manifestPath);
44
- const inputFileContent = fs.readFileSync(inputFile, { encoding: "utf8" });
45
- assertPluginCodeHasAllFunctionsFromManifest(inputFileContent, functionDescriptors);
46
- const entryPointCode = generateEntryPointCode(functionDescriptors);
47
- const mergedPluginCode = mergeInputPluginCodeWithEntrypoint(inputFileContent, entryPointCode);
48
- const mergedTempFilePath = writeMergedPluginCodeTempFile(mergedPluginCode, path.parse(inputFile).dir, inputFileName);
49
- const globalFile = path.join(libsDir, "@asterai/sdk/global.ts");
50
- const options = {
51
- inputFiles: [mergedTempFilePath, globalFile],
52
- baseDir,
53
- outputFile,
54
- libs: libsDir,
55
- };
56
- try {
57
- await compile(options);
58
- }
59
- finally {
60
- fs.unlinkSync(mergedTempFilePath);
61
- }
62
- return {
63
- manifestPath,
64
- outputFile,
65
- };
66
- };
67
- const getPluginFunctionDescriptors = (proto, protoPath) => {
68
- const protoMerged = mergeProtoImports(proto, protoPath, false, false);
69
- const result = protobuf.parse(protoMerged);
70
- const namespace = result.root.resolveAll();
71
- const objects = namespace.nestedArray;
72
- const serviceObject = objects.find(o => o.methods !== undefined);
73
- if (!serviceObject) {
74
- throw new Error("no service found in plugin manifest");
75
- }
76
- const service = serviceObject;
77
- return service.methodsArray.map(m => ({
78
- functionName: m.name,
79
- inputType: m.requestType,
80
- outputType: m.responseType,
81
- }));
82
- };
83
- const generateEntryPointCode = (functionDescriptors) => {
84
- const importsCode = `
85
- import { Protobuf } from "@asterai/as-proto/assembly";
86
- import { readBufferFromPtr, writeBufferToPr } from "@asterai/sdk/buffer";
87
- `;
88
- let code = "\n// generated plugin entry points\n\n";
89
- for (const functionDescriptor of functionDescriptors) {
90
- const template = `
91
- export function {{func}}_entry_point(ptr: u32): u32 {
92
- const inputBuffer = readBufferFromPtr(ptr);
93
- const input = Protobuf.decode<{{inpt}}>(
94
- inputBuffer,
95
- {{inpt}}.decode,
96
- );
97
- const output = {{func}}(input);
98
- const outputBuffer = Protobuf.encode<{{outp}}>(
99
- output,
100
- {{outp}}.encode,
101
- );
102
- return writeBufferToPr(outputBuffer);
103
- }
104
- `;
105
- const view = {
106
- func: functionDescriptor.functionName,
107
- inpt: functionDescriptor.inputType,
108
- outp: functionDescriptor.outputType,
109
- };
110
- const functionEntryPoint = Mustache.render(template, view);
111
- code = `${code}\n${functionEntryPoint}`;
112
- }
113
- return {
114
- importsCode,
115
- entryPointsCode: code,
116
- };
117
- };
118
- const mergeInputPluginCodeWithEntrypoint = (pluginCode, entryPoint) => `${entryPoint.importsCode}\n${pluginCode}\n${entryPoint.entryPointsCode}`;
119
- const writeMergedPluginCodeTempFile = (source, inputFileDir, inputFileName) => {
120
- const tempFileName = `.entrypoint.${inputFileName}.ts`;
121
- const tempFilePath = path.join(inputFileDir, tempFileName);
122
- fs.writeFileSync(tempFilePath, source, { encoding: "utf8" });
123
- return tempFilePath;
124
- };
125
- /**
126
- * Throw an error if the plugin WASM source is missing a function from
127
- * the manifest.
128
- * The only purpose of this function is to let the user know about
129
- * the issue in a direct way.
130
- */
131
- const assertPluginCodeHasAllFunctionsFromManifest = (source, functionDescriptors) => {
132
- for (const functionDescriptor of functionDescriptors) {
133
- const includesFunction = source.includes(`function ${functionDescriptor.functionName}`);
134
- if (!includesFunction) {
135
- throw new Error(`function "${functionDescriptor.functionName}" was defined in plugin ` +
136
- "manifest (.proto file) but is missing from plugin code");
137
- }
138
- }
139
- };
@@ -1,22 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- export type CodegenFlags = {
3
- manifest: string;
4
- outputDir: string;
5
- appId?: string;
6
- language?: string;
7
- staging?: boolean;
8
- };
9
- export default class Codegen extends Command {
10
- static args: {};
11
- static description: string;
12
- static examples: string[];
13
- static flags: {
14
- manifest: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
15
- outputDir: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
- appId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
17
- language: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
18
- staging: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
19
- };
20
- run(): Promise<void>;
21
- }
22
- export declare const codegen: (flags: CodegenFlags) => Promise<void>;
@@ -1,149 +0,0 @@
1
- import { Command, Flags } from "@oclif/core";
2
- import path from "path";
3
- import fs from "fs";
4
- import { execSync } from "node:child_process";
5
- import axios from "axios";
6
- import { getConfigValue } from "../config.js";
7
- import os from "os";
8
- // Relative path from the plugin root directory.
9
- const AS_PROTO_GEN_PATH = "./node_modules/.bin/as-proto-gen";
10
- const PRODUCTION_ENDPOINT_BASE_URL = "https://api.asterai.io";
11
- const STAGING_ENDPOINT_BASE_URL = "https://staging.api.asterai.io";
12
- export default class Codegen extends Command {
13
- static args = {};
14
- static description = "Generate code from the plugin manifest";
15
- static examples = [`<%= config.bin %> <%= command.id %>`];
16
- static flags = {
17
- manifest: Flags.string({
18
- char: "m",
19
- description: "manifest path",
20
- default: "plugin.asterai.proto",
21
- }),
22
- outputDir: Flags.string({
23
- char: "o",
24
- description: "output directory",
25
- default: "generated",
26
- }),
27
- appId: Flags.string({
28
- char: "a",
29
- description: "app id",
30
- required: false,
31
- }),
32
- language: Flags.string({
33
- char: "l",
34
- description: "language of generated typings",
35
- required: false,
36
- default: "js",
37
- }),
38
- staging: Flags.boolean({
39
- char: "s",
40
- description: "use staging endpoint",
41
- required: false,
42
- default: false,
43
- }),
44
- };
45
- async run() {
46
- const { flags } = await this.parse(Codegen);
47
- codegen(flags);
48
- }
49
- }
50
- export const codegen = (flags) => {
51
- const manifestPath = path.resolve(flags.manifest);
52
- const baseDir = path.dirname(manifestPath);
53
- const outDir = path.join(baseDir, flags.outputDir);
54
- if (!fs.existsSync(outDir)) {
55
- fs.mkdirSync(outDir, { recursive: true });
56
- }
57
- else {
58
- deleteOldGeneratedFiles(outDir);
59
- }
60
- if (flags.appId && flags.language) {
61
- return generateClientTypings(outDir, flags.language, flags.appId, flags.staging ?? false);
62
- }
63
- return generateAssemblyScriptPluginTypings(flags, baseDir);
64
- };
65
- const generateAssemblyScriptPluginTypings = async (flags, baseDir) => {
66
- const absoluteAsProtoGenPath = path.join(baseDir, AS_PROTO_GEN_PATH);
67
- try {
68
- execSync("protoc --version");
69
- }
70
- catch (e) {
71
- console.error("No protoc binary found. " +
72
- "Is protocol buffers installed on the system? " +
73
- "Download protocol buffers here: https://protobuf.dev/downloads");
74
- return;
75
- }
76
- try {
77
- execSync("protoc " +
78
- `--plugin='protoc-gen-as=${absoluteAsProtoGenPath}' ` +
79
- `--experimental_allow_proto3_optional ` +
80
- `--as_out='./${flags.outputDir}' ./${flags.manifest}`);
81
- }
82
- catch (e) {
83
- console.error("Failed to generate protobuf types:", e);
84
- }
85
- };
86
- const deleteOldGeneratedFiles = (outDir) => {
87
- const oldFiles = fs.readdirSync(outDir);
88
- for (const oldFile of oldFiles) {
89
- const file = path.parse(oldFile);
90
- if (file.ext !== ".ts") {
91
- continue;
92
- }
93
- const deletePath = path.join(outDir, oldFile);
94
- fs.unlinkSync(deletePath);
95
- }
96
- };
97
- const generateClientTypings = async (outDir, language, appId, shouldUseStaging) => {
98
- const manifestsResponse = await downloadEnabledPluginsManifests(appId, shouldUseStaging);
99
- const asteraiProto = await fetchAsteraiProto();
100
- const aggregatedManifest = aggregateManifests(manifestsResponse.manifests, asteraiProto);
101
- const appPrefix = `app.${appId}`;
102
- fs.writeFileSync(path.join(outDir, `${appPrefix}.proto`), aggregatedManifest.content);
103
- if (language === "ts") {
104
- console.log("generating TypeScript typings for plugin manifest...");
105
- const jsOutput = `${appPrefix}.js`;
106
- const dTsOutput = `${appPrefix}.d.ts`;
107
- execSync(`
108
- npx -p protobufjs-cli pbjs -t static --no-service ${aggregatedManifest.filePath} -o ${path.join(outDir, jsOutput)}
109
- `);
110
- execSync(`
111
- npx -p protobufjs-cli pbts -o ${path.join(outDir, dTsOutput)} ${path.join(outDir, jsOutput)}
112
- `);
113
- fs.unlinkSync(aggregatedManifest.filePath);
114
- console.log("Typings generated successfully.");
115
- }
116
- console.log("done.");
117
- };
118
- const fetchAsteraiProto = () => {
119
- // TODO: fetch this from the local file system instead.
120
- return axios
121
- .get("https://unpkg.com/@asterai/sdk@latest/protobuf/asterai.proto")
122
- .then(r => r.data);
123
- };
124
- const aggregateManifests = (manifests, asteraiProto) => {
125
- let aggregatedManifest = `${asteraiProto}\n`;
126
- const osTmpDir = os.tmpdir();
127
- const aggregatedManifestPath = path.join(osTmpDir, "plugins.asterai.proto");
128
- for (const manifest of manifests) {
129
- aggregatedManifest += `${manifest.proto}\n`;
130
- }
131
- fs.writeFileSync(aggregatedManifestPath, aggregatedManifest);
132
- return {
133
- content: aggregatedManifest,
134
- filePath: aggregatedManifestPath,
135
- };
136
- };
137
- const downloadEnabledPluginsManifests = async (appId, shouldUseStaging) => {
138
- const baseUrl = shouldUseStaging
139
- ? STAGING_ENDPOINT_BASE_URL
140
- : PRODUCTION_ENDPOINT_BASE_URL;
141
- const response = await axios({
142
- url: `${baseUrl}/app/${appId}/plugin/manifests`,
143
- method: "GET",
144
- headers: {
145
- Authorization: getConfigValue("key"),
146
- },
147
- });
148
- return response.data;
149
- };
package/dist/compile.d.ts DELETED
@@ -1,7 +0,0 @@
1
- export type CompileOptions = {
2
- inputFiles: string[];
3
- baseDir: string;
4
- libs: string;
5
- outputFile: string;
6
- };
7
- export declare const compile: (options: CompileOptions) => Promise<void>;
package/dist/compile.js DELETED
@@ -1,25 +0,0 @@
1
- import * as asc from "assemblyscript/asc";
2
- const COMPILER_OPTIONS = {
3
- stdout: process.stdout,
4
- stderr: process.stderr,
5
- };
6
- export const compile = async (options) => {
7
- const args = [
8
- "--exportRuntime",
9
- "--runtime",
10
- "stub",
11
- ...options.inputFiles,
12
- "--baseDir",
13
- options.baseDir,
14
- "--lib",
15
- options.libs,
16
- "--outFile",
17
- options.outputFile,
18
- "--optimize",
19
- "--debug",
20
- ];
21
- const result = await asc.main(args, COMPILER_OPTIONS);
22
- if (result.error) {
23
- throw result.error;
24
- }
25
- };