mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* fix(runtimes): rename Qoder Cloud Agent to Qoder Cloud Agents Qoder's product is called Qoder Cloud Agents, matching how Cursor Cloud Agents is already spelled in the runtime lists. Update the display name, default runtime name, error messages, translations, landing copy, docs and tests. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(runtimes): use Qoder's current logo Replace the old green Qoder mark with the current monochrome mark from qoder.com/favIcon.svg. The background is removed, the paths are minified, and both paths use currentColor so the mark stays visible in dark mode. It applies to the Qoder, Qoder CN and Qoder Cloud Agents runtimes. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: 安辰 <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
// qoder-runtime runs a server-hosted Multica to Qoder Cloud Agents bridge.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/multica-ai/multica/server/internal/qoderruntime"
|
|
)
|
|
|
|
func main() {
|
|
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
|
configPath := flag.String("config", "", "JSON configuration file; supplied fields override environment variables")
|
|
checkOnly := flag.Bool("check", false, "Verify API access without registering a runtime or running tasks")
|
|
flag.Parse()
|
|
cfg, err := loadConfig(*configPath, os.Getenv)
|
|
if err != nil {
|
|
log.Error("read bridge configuration", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
b, err := qoderruntime.New(cfg, log)
|
|
if err != nil {
|
|
log.Error("invalid bridge configuration", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer cancel()
|
|
if *checkOnly {
|
|
if err := b.Check(ctx); err != nil {
|
|
log.ErrorContext(ctx, "bridge check failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
log.InfoContext(ctx, "bridge API access verified")
|
|
return
|
|
}
|
|
if err := b.Run(ctx); err != nil {
|
|
log.ErrorContext(ctx, "Qoder bridge stopped", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func loadConfig(path string, getenv func(string) string) (qoderruntime.Config, error) {
|
|
cfg := qoderruntime.Config{
|
|
MulticaURL: getenv("MULTICA_SERVER_URL"), MulticaToken: getenv("MULTICA_TOKEN"), WorkspaceID: getenv("MULTICA_WORKSPACE_ID"),
|
|
QoderURL: getenv("QODER_CLOUD_BASE_URL"), QoderToken: getenv("QODER_CLOUD_TOKEN"), EnvironmentID: getenv("QODER_CLOUD_ENVIRONMENT_ID"),
|
|
StateDir: getenv("QODER_BRIDGE_STATE_DIR"), Name: getenv("QODER_BRIDGE_NAME"),
|
|
}
|
|
if path == "" {
|
|
return cfg, nil
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("cannot open configuration file")
|
|
}
|
|
defer f.Close()
|
|
raw, err := io.ReadAll(io.LimitReader(f, (1<<20)+1))
|
|
if err != nil || len(raw) > 1<<20 {
|
|
return cfg, fmt.Errorf("cannot read configuration or file exceeds 1 MiB")
|
|
}
|
|
raw = bytes.TrimSpace(raw)
|
|
if len(raw) == 0 || raw[0] != '{' {
|
|
return cfg, fmt.Errorf("configuration must be a JSON object")
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&cfg); err != nil {
|
|
return cfg, fmt.Errorf("invalid configuration JSON or unknown field")
|
|
}
|
|
var extra any
|
|
if err := decoder.Decode(&extra); err != io.EOF {
|
|
return cfg, fmt.Errorf("configuration must contain one JSON object")
|
|
}
|
|
return cfg, nil
|
|
}
|