Extensions provide a powerful way to augment the capabilities of the system by exporting blocks to the block manager. These blocks are loaded on startup, enabling you to create custom components and patches. In this guide, we'll outline how to use extensions to create and export components.
Follow Creating Components with the Composition API , you can use the composition API from the mercs_rete
library to create components. First, make sure to import the necessary modules:
Then, you can create components and patches using methods provided by OAIBaseComponent
.
Example:
Follow Creating Components with the Composition API , you can also define and add inputs/outpus to the component:
To prevent any errors from being thrown and to avoid bundling mercs_shared
, which the server already has, you'll need to externalize mercs_rete
. You can do this by adding --external:mercs_rete
to the ESBuild in your package.json
. This step ensures a smooth building process (because it already is loaded in server memory, no need to bundle it).
Add the supports: ["blocks:v2"]
property to the extension.yaml
to tell the server to try to load the blocks:
An extension must export an object containing hooks and a createComponents()
function. Once you have defined your components and patches, combine them into an array and return them within the createComponents()
function as described. The system will automatically load these during startup, incorporating them into the available set of components.
After making all the necessary updates to your extension code, run the following command to build the extension:
This command compiles your code and generates the extension.js
file, which contains the entire extension ready for use. If you are using TypeScript for your extension development, make sure to update the tsconfig.json
file with the appropriate configuration for your project.
By following these steps, you can create complex and customizable components and integrate them into the system via extensions. Make sure to follow the guidelines for each type of component or patch and consult the specific documentation related to each class or method for more details.
Import the necessary components
Instantiate the ComponentComposer and initialize your component by defining its namespace and operationId using the .create(displayNamespace, displayOperationId) method.
Set the namespace to const NS_OMNI
Further define the characteristics of your component using provided methods. You can set the title, description, method, and category.
Instead of using the fromScratch()
method, you can also utilize the fromJSON()
method with a valid Partial<OmniComponentFormat>
JSON. This must include both the displayNamespace/operationID
and apiNamespace/operationId
.
For inputs and outputs, you'll first create an IOComposer
using the .createInput(name, type, customSocket)
or .createOutput(name, type, customSocket)
methods. You can further define their properties, including setting up controls which can be automatically selected or overridden.
Inputs always have controls associated with them, and they can be defined directly within the input creation process. Here's an example of defining an input with a control:
Note:
When using .allowMultiple()
in conjunction with { array: true }
, the input array is flattened into a single array:
You can define which characters are used for joining (or separating on input) an array (default is \n) via a custom setting (specific to the Text Socket):
Alternatively, you can also define inputs and outputs in an array:
You also have the option to directly write the JSON if that's your preference.
Controls are created using the ControlComposer
and are added similarly to inputs/outputs.
Or
You can define the behavior of your component using a macro. In this case, the OmniComponentMacroTypes.EXEC
macro type is used.
Finally, convert the component to JSON and export it.
Note: extensions don’t need to call addBlock
. they just need to export the createComponent
function.
To compose a patch with a valid OmniComponentPatch
, the same format applies:
Use .dependsOn(string[])
to specify dependencies, indicating if a block relies on other blocks (for instance, when using runblock
internally). For example:
Locate the API Specification: Determine if the API specification exists at a known URL. For example, OpenAI's specification can be found at https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml
.
Validate the Specification: Use https://editor.swagger.io/
to validate the OpenAPI spec.
Add to System:
Navigate to packages/omni-server/extensions/omni-core-blocks/server/apis/[NAMESPACE]/[NAMESPACE].yaml
.
Replace [NAMESPACE]
with your desired namespace (e.g., openai
).
Update the api
section as below.
Generate Draft Spec: If the specification doesn't exist, use tools like "OpenAPI Spec Generator" or request ChatGPT 4 to draft an initial spec.
Validate the Specification: Use https://editor.swagger.io/
to validate the OpenAPI spec.
Add to System:
Go to packages/omni-server/extensions/omni-core-blocks/server/apis/[NAMESPACE]/api/[NAMESPACE].yaml
.
Replace [NAMESPACE]
with your desired namespace (e.g., getimg
).
Update the api
section as below.
We support the following authentication type:
If auth is not defined globally in the original OpenAPI spec, you can patch it in the API yaml /omni-core-blocks/server/apis/[NAMESPACE].yaml
type:
customSocket:
Base64 image socket option:
Array:
Allow multiple connect
how to patch when response doesn't have property?
When properties have mutually exclusive or dependencies
Property name is Case-sensitive
When request content type is multipart/form-data, need file type to carry mimetype