Providers
A studio reaches the platform API through a provider. There are two, and they serve one contract: an OpenAPI document that generates the Go, Python and JavaScript clients, so an operation cannot exist in one language and not another.
Remote: over HTTP
helmstudio's daemon serves the API on the machine's loopback address, and so does helm dev. Either one starts the studio's processes with the address and a token in the environment:
| Variable | Holds |
|---|---|
HELM_API |
The API's address. |
HELM_TOKEN |
A token for the capabilities the manifest declares. Absent for a studio that declares none. |
HELM_STUDIO_ID |
The studio's id. |
HELM_STAGE_DIR |
A directory for this run's outputs, which the studio adopts from. |
from_env() in Python, fromEnv() in JavaScript and helm.FromEnv() in Go read them and return a client. The Python and JavaScript clients are remote only: without HELM_API they refuse, naming helmstudio and helm dev, so a Python or JavaScript studio developed on its own runs under helm dev.
A studio's page never holds the token. Its server mounts the runtime SDK's proxy at /helm/, which adds the token to what the page sends — see theming, which uses it.
Embedded: in the studio's own process
In Go, a studio can import the embedded provider and run the platform API in its own process, with no daemon and no helm dev. It keeps its data in .helm, in the directory the studio runs from, unless HELM_DIR names another:
- import
github.com/janishar/helmstudio/packages/helm-runtime-sdk/go/embeddedfor its side effect, andhelm.FromEnv()uses it wheneverHELM_APIis not set; - it is a module of its own, so a studio that only ever runs under helmstudio never pulls SQLite into its build;
- it reads the studio's capabilities from its manifest and enforces them as the daemon does;
- only one process may open a
.helmat a time, so a studio with more than one process runs underhelm devinstead.
Run in a directory that holds the studio's helmstudio.yaml, this records an output with neither helmstudio nor helm dev running:
// A Go studio that records an output with no daemon and no helm dev: the
// embedded provider runs the platform API in this process, keeping its data in
// ./.helm, with the capabilities ./helmstudio.yaml declares.
package main
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
helm "github.com/janishar/helmstudio/packages/helm-runtime-sdk/go"
// Imported for its side effect: helm.FromEnv uses it when HELM_API is not set.
_ "github.com/janishar/helmstudio/packages/helm-runtime-sdk/go/embedded"
)
// A one-pixel PNG, standing in for what a model would make.
var pixel = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89" +
"\x00\x00\x00\rIDATx\xdac\xf8\xcf\xc0\xf0\x1f\x00\x05\x00\x02\x01\xdd\xb1\xc6\x1c\x00\x00\x00\x00IEND\xaeB`\x82")
func main() {
ctx := context.Background()
// The remote client under helmstudio or helm dev, where HELM_API is set,
// and the embedded provider everywhere else. The calls below are the same
// either way.
c, err := helm.FromEnv()
if err != nil {
log.Fatal(err)
}
// Where this studio may adopt from, as the provider running it says.
me, err := c.Me.Get(ctx)
if err != nil {
log.Fatal(err)
}
out := filepath.Join(me.Paths.Stage, "frame.png")
if err := os.WriteFile(out, pixel, 0o644); err != nil {
log.Fatal(err)
}
asset, err := c.Assets.Adopt(ctx, helm.AdoptRequest{Path: out, Kind: helm.AssetKindImage})
if err != nil {
log.Fatal(err)
}
item, err := c.Gallery.Add(ctx, helm.ItemCreate{
Kind: helm.AssetKindImage,
AssetID: asset.ID,
Params: map[string]any{"prompt": "a lighthouse at dusk", "seed": 7},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("recorded %s through the %s provider\n", item.ID, me.Provider)
}
Where a studio may adopt from comes from /me, so the same code works under every provider. There is no embedded provider in Python or JavaScript.
The same answers
A provider that answered differently would make a studio's tests worthless. The conformance suite in test/conformance runs the same cases against the daemon over HTTP and against the embedded provider in process.
What is deliberately different is presentation. POST /timeline/{id}:open asks for a window to show a sequence in, and today every provider answers 501, because the launcher's timeline screen ships with the Mac app. The sequence is still made, edited and exported, and a studio hides its Open button. See the timeline.
Not built yet
helm adopt, which is to bring what a studio recorded under helm dev or the embedded provider into helmstudio's library, is not built. Neither are helm dev --fixtures, for a gallery of sample media, and helm dev --fail, for injected errors.