ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
123
services/codex-bridge/rpc.go
Normal file
123
services/codex-bridge/rpc.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type object = map[string]any
|
||||
|
||||
type rpcError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type event struct {
|
||||
ID json.RawMessage `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *rpcError `json:"error"`
|
||||
}
|
||||
|
||||
type client struct {
|
||||
cmd *exec.Cmd
|
||||
input *json.Encoder
|
||||
output *json.Decoder
|
||||
id int
|
||||
queued []event
|
||||
}
|
||||
|
||||
func (b *bridge) start(ctx context.Context) (*client, error) {
|
||||
args := []string{"app-server"}
|
||||
if b.mcpURL != "" {
|
||||
url, err := json.Marshal(b.mcpURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, "-c", "mcp_servers.gateway.url="+string(url), "-c", "mcp_servers.gateway.required=true")
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, b.binary, args...)
|
||||
cmd.Dir = b.dir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd.Cancel = func() error { return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) }
|
||||
in, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
_ = in.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
_ = in.Close()
|
||||
_ = out.Close()
|
||||
return nil, err
|
||||
}
|
||||
c := &client{cmd: cmd, input: json.NewEncoder(in), output: json.NewDecoder(out)}
|
||||
if _, err := c.call("initialize", object{"clientInfo": object{"name": "codex_bridge", "version": "0.1.0"}, "capabilities": object{"experimentalApi": true}}); err != nil {
|
||||
c.close()
|
||||
return nil, err
|
||||
}
|
||||
if err := c.input.Encode(object{"method": "initialized", "params": object{}}); err != nil {
|
||||
c.close()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *client) close() {
|
||||
_ = syscall.Kill(-c.cmd.Process.Pid, syscall.SIGKILL)
|
||||
_ = c.cmd.Wait()
|
||||
}
|
||||
|
||||
func (c *client) read() (event, error) {
|
||||
var e event
|
||||
if err := c.output.Decode(&e); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return e, errors.New("codex app-server closed its output")
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
if len(e.ID) > 0 && e.Method != "" {
|
||||
return e, fmt.Errorf("unsupported Codex client request: %s", e.Method)
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (c *client) call(method string, params object) (json.RawMessage, error) {
|
||||
c.id++
|
||||
id := fmt.Sprint(c.id)
|
||||
if err := c.input.Encode(object{"id": c.id, "method": method, "params": params}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
e, err := c.read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(e.ID) != id {
|
||||
c.queued = append(c.queued, e)
|
||||
continue
|
||||
}
|
||||
if e.Error != nil {
|
||||
return nil, fmt.Errorf("%s: %s", method, e.Error.Message)
|
||||
}
|
||||
return e.Result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) next() (event, error) {
|
||||
if len(c.queued) == 0 {
|
||||
return c.read()
|
||||
}
|
||||
e := c.queued[0]
|
||||
c.queued = c.queued[1:]
|
||||
return e, nil
|
||||
}
|
||||
Loading…
Reference in a new issue