jcode

package module
v0.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 22 Imported by: 0

README

Go SDK

The Go SDK is the Go client for the jcode harness API. It speaks protocol v1 over an io.ReadWriteCloser, completes the hello handshake, correlates request replies, and delivers asynchronous events through bounded subscriptions.

Unofficial community fork: This is an independent, vibe-coded fork maintained by Ariel Frischer. It is not an official Jcode release, is not endorsed or supported by the upstream Jcode maintainers, and may be incomplete or incompatible with future Jcode versions. Use it experimentally and review the code before relying on it.

API status: The Go package provides a transport-level client, typed session helpers (CreateSession, AttachSession, Session.Send, and Session.StartTurn), typed event streams, and private-instance helpers (Launch, LaunchInstance, and LaunchOptions). Raw Request and Subscribe remain available for forward-compatible protocol additions.

Install

This fork is published as a Go module. Install the tagged release with:

go get github.com/ariel-frischer/jcode-go@v0.1.0

For a source checkout:

git clone https://github.com/ariel-frischer/jcode-go.git
cd jcode-go
go test ./...
go vet ./...

The module requires Go 1.23 or newer. The SDK has no third-party dependencies.

Choose a connection mode

There are two deployment patterns:

  1. Connect to a shared instance. Start jcode api-bridge once, then dial its owner-only Unix socket and pass the connection to NewClient. This is suitable for editor plugins, dashboards, and tools that intentionally operate on the user's live sessions.
  2. Own a private instance. jcode.Launch starts a separately configured bridge/daemon, gives it a separate home and socket, and returns a client that owns shutdown. LaunchInstance is available when the caller needs to control dialing. Typed session helpers and raw protocol requests are both available.
Safe-run ownership

Connect is always non-owning. It attaches to an existing runtime, and Client.Close closes only that client's transport. It never stops the shared daemon. Launch is different: the returned client owns the private process, daemon, instance home, and cleanup through its internal Instance handle.

When a worker connection must be allowed to close while the private session continues, transfer the private ownership explicitly:

client, err := jcode.Launch(ctx, jcode.LaunchOptions{
    JcodeHome:  home,
    WorkingDir: workingDir,
})
if err != nil { return err }
owner, ok := client.DetachInstance()
if !ok { return errors.New("launch did not return an owned instance") }
_ = client.Close() // transport only; the safe-run owner remains alive
defer func() {
    shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancelShutdown()
    _ = jcode.ShutdownInstance(shutdownCtx, owner) // explicit owner performs bounded cleanup
}()

This is intentionally opt-in. It does not make arbitrary clients immortal or change shared-runtime shutdown behavior. The owner is responsible for keeping the Instance handle alive and calling Shutdown; shutdown is idempotent. The SDK does not run an implicit infinite supervisor. Reconnect remains explicit and bounded by ReconnectPolicy.MaxAttempts, with reconnect_failed, resume_failed, and reconnected observations exposing attempts and outcomes. In-flight requests are never replayed.

A shared connection sees the user's sessions and actions are visible in their terminal. A private connection must use a distinct state directory and socket. Never point a private process at the user's live jcode home.

Shared connect: one-shot CLI-like flow

Start the bridge first:

jcode api-bridge

Then run examples/oneshot:

go run ./examples/oneshot "List the top-level files"

The example dials $JCODE_API_SOCKET when set, otherwise $XDG_RUNTIME_DIR/jcode-api.sock, creates a session, sends one message, prints text deltas, and exits at turn_done.

The important shared-connection lifecycle is still explicitly closed, while the turn owns acceptance, ordered events, server cancellation, and terminal outcome:

ctx := context.Background()
conn, err := net.Dial("unix", socketPath)
if err != nil { return err }
client, err := jcode.NewClient(ctx, conn, jcode.Options{ClientName: "my-tool/1.0"})
if err != nil { return err } // NewClient closes conn after handshake failure
defer client.Close()
session, err := client.CreateSession(ctx, jcode.CreateSessionOptions{WorkingDir: workingDir})
if err != nil { return err }
turn, err := session.StartTurn(lifecycleCtx, prompt, jcode.SendOptions{})
if err != nil { return err }

NewClient starts its reader goroutine and performs a protocol v1 handshake. Client.Close is idempotent and wakes pending requests and subscriptions.

Typed session lifecycle

The existing Session.Send and Session.Events APIs remain compatible for callers that separately manage acceptance and event consumption:

session, err := client.CreateSession(ctx, jcode.CreateSessionOptions{WorkingDir: workingDir})
if err != nil { return err }
stream := session.Events(ctx)
defer stream.Close()
if err := session.Send(ctx, prompt, jcode.SendOptions{}); err != nil { return err }
for {
    event, err := stream.Next(ctx)
    if err != nil { return err }
    switch value := event.(type) {
    case *jcode.TextDelta:
        io.WriteString(out, value.Text)
    case *jcode.PermissionRequest:
        // Apply an explicit application policy before responding.
    case *jcode.TurnDone:
        return nil
    }
}

AttachSession creates the same lightweight typed view for an existing ID. client.ForkSession(ctx, session.ID) clones the source session's persisted context and returns the new session reported by session_forked. In external wake mode, typed event streams decode wake_requested as *jcode.WakeRequested, preserving its session ID, reason, and notification so the caller can decide when to run the session. Raw Request and Subscribe access remains available, while Session.Events continues to provide typed decoding.

Session.Send subscribes before notifying the server and waits for the asynchronous message_accepted event. SendOptions.NoReply selects fire-and-forget notification semantics. Session.Send does not retry because a timeout can leave a mutation with an unknown server-side outcome.

Session profiles and per-turn safety

Select a named harness profile when creating a session, then apply optional limits to an individual send or owned turn:

session, err := client.CreateSession(ctx, jcode.CreateSessionOptions{
    WorkingDir: workingDir,
    Profile:    "review",
})
if err != nil { return err }

offset := time.FixedZone("run-offset", -8*60*60)
deadline := time.Now().Add(30 * time.Minute).In(offset).Format(time.RFC3339)
options := jcode.SendOptions{
    MaxTurns:    8,
    TokenBudget: 16_000,
    Deadline:    deadline, // future RFC3339 timestamp with an explicit -08:00 offset
}
turn, err := session.StartTurn(lifecycleCtx, prompt, options)
if err != nil {
    var optionErr *jcode.OptionError
    if errors.Is(err, jcode.ErrInvalidOptions) && errors.As(err, &optionErr) {
        return fmt.Errorf("invalid %s option: %w", optionErr.Field, err)
    }
    return err
}

Zero values omit Profile, MaxTurns, TokenBudget, and Deadline from the protocol payload, preserving legacy request shapes. A non-empty profile must contain a non-whitespace character. Limits cannot be negative, and a supplied deadline must be a future RFC3339 timestamp with Z or a numeric timezone offset. Validation errors expose only the safe field name through ErrInvalidOptions and OptionError; rejected values are not included.

The same run-safety fields work with Session.Send. Setting NoReply still selects its existing notification-only behavior. These typed controls do not configure provider credentials and require no live provider to validate. Raw Request and Notify payloads remain caller-controlled and bypass typed-option validation.

Use StartTurn when the caller must own acceptance, ordered events, cancellation, and completion as one lifecycle:

turn, err := session.StartTurn(lifecycleCtx, prompt, jcode.SendOptions{})
if err != nil { return err }
if err := turn.Accepted(waitCtx); err != nil { return err }

var cancelErr error
for {
    event, err := turn.Next(waitCtx)
    if err != nil {
        if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
            cancelCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
            cancelErr = turn.Cancel(cancelCtx)
            cancel()
        }
        break
    }
    if text, ok := event.(*jcode.TextDelta); ok {
        io.WriteString(out, text.Text)
    }
}

terminalWaitCtx, cancelTerminalWait := context.WithTimeout(context.Background(), 30*time.Second)
defer cancelTerminalWait()
result, err := turn.Wait(terminalWaitCtx)
if err != nil { return errors.Join(cancelErr, err) } // only this fresh Wait was interrupted
switch result.Kind {
case jcode.TurnResultCompleted:
    return nil
case jcode.TurnResultCanceled:
    return result.Err
default:
    return result.Err
}

When the event wait is interrupted, the example explicitly requests server-side cancellation and then waits for the terminal outcome with a fresh bounded context. The lifecycle context passed to StartTurn owns the turn. Canceling it records TurnResultLifecycleCanceled or TurnResultLifecycleDeadlineExceeded and does not send protocol cancel. Contexts passed to Accepted, Next, Cancel, and Wait bound only those calls. Turn.Cancel sends at most one shared protocol request, and a successful acknowledgement remains non-terminal until the server emits its terminal event. The first terminal signal is stored immutably, so all later Wait calls return the same TurnResult.

In particular, canceling a Wait context is local. It does not stop server work. Call Turn.Cancel for server-side cancellation, then use a fresh bounded context for Turn.Wait so the interrupted context does not immediately abort the terminal wait.

create, err := protocol.NewRawRequest("create_session", map[string]any{
    "working_dir": workingDir,
})
if err != nil { return err }
reply, err := client.Request(ctx, create)
if err != nil { return err }

var fields json.RawMessage
if raw, ok := protocol.FieldsJSON(reply.Event); ok {
    fields = raw
} else {
    return errors.New("session creation returned no fields")
}
var sessions struct {
    SessionID string `json:"session_id"`
}
if err := json.Unmarshal(fields, &sessions); err != nil {
    return err
}

Typed-event semantic handling

Every concrete owned-turn event has exactly one EventSemanticClass. Adaptors should classify before dispatching and keep the switch exhaustive:

func handleEvent(event jcode.TypedEvent) error {
    class, ok := jcode.SemanticClassOf(event)
    if !ok {
        return errors.New("unclassified jcode event")
    }
    switch class {
    case jcode.EventSemanticClassContentProgress:
        // Render text/reasoning deltas, track tool progress, or record usage.
    case jcode.EventSemanticClassAdvisoryLifecycle:
        // Turn.Next filters this class. A legacy Session.Events consumer may
        // inspect it without treating it as terminal or safety-relevant.
    case jcode.EventSemanticClassTerminal:
        // Observe the terminal event, then read the immutable Turn.Wait result.
    case jcode.EventSemanticClassPermission:
        // Apply an explicit user/application policy before responding.
    case jcode.EventSemanticClassToolEffect:
        // Handle the tool effect; do not silently discard it.
    default:
        return errors.New("unsupported jcode event class")
    }
    return nil
}

Turn.Next publishes content/progress, terminal, permission, and tool-effect events in order. It ignores only recognized advisory/lifecycle metadata and emits at most one Observer observation per turn. The observation contains bounded sanitized EventKind, EventType, and Disposition fields; it never contains event payloads. Unknown, malformed, nil, and unclassified owned-turn input ends the turn as TurnResultProtocolError with a payload-free CompatibilityError no longer than 256 UTF-8 bytes.

Session.Events is the broader typed compatibility seam. Stable protocol kinds decode to exported values, including MessageAccepted, SidePaneImages, Models, RuntimeInfo, CredentialUpdated, FileContent, Files, TextMatches, FileStatus, Compacted, and SessionRenamed. Request/reply shapes that the canonical harness marks outside owned turns are intentionally not classified by SemanticClassOf; receiving one on an owned Turn therefore still fails closed. Harness error frames remain an EventError return so callers can inspect Code and the redacted ProviderCode. A genuinely unknown future kind remains UnknownEvent{Kind, Fields} instead of being silently dropped.

The raw request path remains available when an application needs a request or event added by a newer server:

Request waits for the correlated reply or ctx.Done(). Each request also has a 30-second SDK deadline by default, preventing a bridge or daemon that accepts a connection but never replies from hanging a caller indefinitely. Set Options.RequestTimeout to a positive duration to override that bound. Cancellation removes the pending request locally, but it does not necessarily cancel work already accepted by the server. For an owned turn, use Turn.Cancel; raw protocol callers may still send cancel directly.

examples/streaming demonstrates a long-lived service. For a typed stream, use session.Events(ctx) and switch on the event values relevant to the application, such as *TextDelta, *ToolStart, *ToolExec, *SidePaneImages, *TokenUsage, *PermissionRequest, and *TurnDone. The lower-level Subscription API remains useful when a service wants raw event fields:

for {
    event, err := sub.Next(ctx)
    if err != nil {
        if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
            return nil
        }
        return err
    }
    switch event.Kind {
    case "text_delta":
        var value struct { Text string `json:"text"` }
        if err := event.Decode(&value); err != nil { return err }
        io.WriteString(out, value.Text)
    case "permission_request":
        // Apply your product's policy. Never blindly allow in an untrusted app.
    case "turn_done":
        return nil
    default:
        // Unknown event kinds are intentionally forward-compatible.
        log.Printf("jcode event kind=%s", event.Kind)
    }
}

Events are delivered to every subscription. A subscription buffer is bounded (Options.EventBuffer, default 128). If a consumer falls behind, that subscription terminates with ErrSubscriberOverflow rather than blocking all request traffic or silently dropping events. Consume promptly, increase the buffer deliberately, or fan out after a single reader. Do not call Next concurrently on one subscription.

Cancellation, shutdown, and reconnect/resume

Use contexts for local deadlines and cancellation:

ctx, cancel := context.WithTimeout(parent, 30*time.Second)
defer cancel()
reply, err := client.Request(ctx, request)

A per-method canceled context only abandons that method's local wait. A StartTurn lifecycle context instead terminates the owned turn locally without sending protocol cancellation. For server-side cancellation, call Turn.Cancel and continue observing the turn until its terminal result.

The SDK does not automatically reconnect or replay events. Use Reconnect when you configure a transport.Factory; it retries connection setup according to ReconnectPolicy, and with Resume: true it sends the remembered session ID after the new handshake. In-flight requests are never retried. Persist session IDs in your application and resubscribe after reconnect:

client, err := jcode.NewClient(ctx, conn, jcode.Options{
    SessionID: sessionID,
    Reconnect: jcode.ReconnectPolicy{
        Factory: func(ctx context.Context) (transport.Transport, error) {
            return transport.UnixSocket(socketPath)(ctx)
        },
        MaxAttempts: 5,
        Backoff: 500 * time.Millisecond,
        Resume: true,
    },
})
// After ErrDisconnected:
if err := client.Reconnect(ctx); err != nil { return err }
sub := client.Subscribe(sessionID)

A fresh subscription only receives events from attach onward. Protocol v1 cannot replay events emitted before the new subscription attaches, so use get_history/peek_session after reconnect if your application needs a consistent transcript. Never blindly repeat a mutating request after a timeout or disconnect: its server-side outcome may be unknown.

Errors

Branch on sentinel errors where available, and preserve protocol error code/message fields for diagnostics:

  • ErrClosed: client or transport has closed.
  • ErrDisconnected: the client transport disconnected.
  • ErrSubscriberOverflow: one subscription exceeded its bounded queue.
  • ErrTurnCanceled: the server terminal event completed an explicit turn cancellation.
  • ErrProtocolFailure: an owned turn ended on invalid framing, protocol data, or typed event decoding.
  • ErrBridgeExited: the attached SDK-owned private bridge exited.
  • context.Canceled and context.DeadlineExceeded: local caller cancellation/deadline.
  • protocol.ErrMalformedFrame, ErrInvalidFrame, and ErrFrameTooLarge: invalid or unsafe wire data.
  • A protocol.Error event: the harness rejected a request. Its Code is stable; its Message is diagnostic.

TurnResult.Kind is one of TurnResultCompleted, TurnResultCanceled, TurnResultLifecycleCanceled, TurnResultLifecycleDeadlineExceeded, TurnResultProviderError, TurnResultProtocolError, TurnResultSubscriberOverflow, TurnResultBridgeExited, TurnResultTransportDisconnected, or TurnResultClientClosed. Inspect TurnResult.Err with errors.Is and errors.As. Owned-turn errors preserve safe sentinels and provider codes, but omit provider messages, raw frames, transport diagnostics, prompts, response content, credentials, private paths, and session identifiers.

Ordinary disconnect closes active turn-owned subscriptions so a Turn never appears as an unexplained empty stream. Raw subscriptions retain their existing explicit-reconnect behavior. Only positive evidence from an attached SDK-launched Linux Instance produces TurnResultBridgeExited; EOF on a Connect client is TurnResultTransportDisconnected. Neither path retries, replays, reconnects, or revives the ended turn automatically.

Treat timeout, disconnect, and transport failures as unknown-outcome for mutations. Retry idempotent reads only, and refresh session state after reconnect. Keep a default branch for future protocol error codes and event kinds.

Private instance pattern

The Go SDK's Launch owns a private daemon, temporary home, credential policy, and cleanup. Use it when embedding jcode as an agent engine:

inherit := false
client, err := jcode.Launch(ctx, jcode.LaunchOptions{
    WorkingDir:    workingDir,
    InheritLogins: &inherit,
    Provider:      "openrouter",
    Model:         "openai/gpt-5.6-luna",
    Env: map[string]string{
        "OPENROUTER_API_KEY": os.Getenv("OPENROUTER_API_KEY"),
    },
    ClientOptions: jcode.Options{ClientName: "my-service/1.0"},
})
if err != nil { return err }
defer client.Close()

session, err := client.CreateSession(ctx, jcode.CreateSessionOptions{
    WorkingDir: workingDir, // same explicit absolute worktree as LaunchOptions
})
if err != nil { return err }

Launch defaults to a temporary owner-only home and removes it on shutdown. Set JcodeHome to persist sessions. LaunchInstance starts the isolated process without dialing it, and its SocketPath() can be passed to net.Dial("unix", ...) and NewClient. Provider and Model become explicit global selections for the private jcode process; leave either empty to preserve jcode's normal auto/config resolution. Set InheritLogins to a bool pointer whose value is false to avoid copying/linking the user's recognized login files.

When InheritLogins is false, provide provider credentials explicitly through LaunchOptions.Env, for example OPENROUTER_API_KEY. Explicit environment entries replace same-named ambient variables, so API-key-only authentication is deterministic rather than dependent on duplicate environment-key behavior. Startup diagnostics redact explicit values for keys, tokens, and secrets. Never print or persist the credential value yourself, and use per-request context deadlines for Session.Send.

examples/private uses jcode.Launch, the same explicit absolute cwd for launch and session creation, DetachInstance, Client.Close, and context-bounded ShutdownInstance. Instance.Shutdown and Instance.Close use finite defaults; the additive helper lets a caller shorten the cooperative grace period without skipping forced termination, bounded reap, or owned-path cleanup. If a private process must use credentials, provision a dedicated service identity instead of inheriting a developer's login files.

Redacted lifecycle observations

Set Options.Observer or LaunchOptions.Observer to receive bounded lifecycle metadata. The existing Observer contract remains synchronous, concurrency-safe for callers to implement, and backend-neutral. The SDK does not create a telemetry backend or generic event framework.

Lifecycle observations include:

  • launch_start, launch preparation/process/socket phases, launch_ready, and a classified launch_error.
  • connect_start, connect_ready, and classified connect_error events in addition to the existing connection state observations.
  • turn_start, turn_prompt_accepted, the single turn_first_event, one shared cancellation-request start and result, and exactly one turn_terminal whose Observation.Outcome is the immutable TurnResultKind.
  • shutdown_start, TERM grace start/completion, optional shutdown_force_kill, bounded reap completion, cleanup completion, and the final shutdown result for SDK-owned Linux private instances.

Observations contain classifications only. They never contain prompts, credentials or tokens, response or tool content, raw protocol frames, raw session IDs, secret-bearing environment values, or private runtime paths. Keep observer implementations equally strict and fast because lifecycle code calls them synchronously and may call them concurrently.

Linux maintainers can run the deterministic private-runtime acceptance and the explicitly gated real OpenAI OAuth smoke described in docs/private-runtime-acceptance.md.

Security guidance

  • Unix sockets and runtime directories should be owner-only. Do not change permissions to make a shared socket world-readable.
  • connect operates on the user's live sessions. Treat prompts, file contents, tool inputs, permission requests, and transcripts as sensitive.
  • Do not log raw protocol frames, authorization headers, environment variables, credential paths, prompts, tool arguments, or model output by default. Redact secrets before structured logging.
  • Credential inheritance is powerful and dangerous. A process using the user's bridge or login files can spend their quota and access their sessions. Disable inheritance for untrusted code and use a dedicated account for services.
  • Validate permission requests in application policy. Do not auto-approve tools merely to make an example convenient.
  • Bound frame sizes (Options.MaxFrameSize) and event buffers. Apply request deadlines and avoid unbounded transcript or output retention.
  • Treat the private home as sensitive state. Use a real, dedicated directory, restrict permissions, and remove temporary homes only after the child process has stopped.

Platforms and protocol compatibility

The protocol client is pure Go and compiles on platforms supported by Go. The bounded private-process supervision contract in this stability program is Linux-only. Windows supervision parity is intentionally out of scope. The transport support matrix is:

Platform transport.UnixSocket Notes
Linux, macOS, and other Unix-like targets Supported and tested by build Uses the OS Unix-domain socket transport.
Windows Explicitly unsupported Supply a named-pipe/TCP Transport until a named-pipe adapter is added.

The examples are Unix examples and are not expected to run on Windows unchanged. Cross-platform package compilation is covered by the release checks; live transport interoperability remains platform-specific.

The client negotiates protocol major version 1 (protocol.APIVersionMajor == 1). Minor, additive event fields should be decoded permissively. Unknown event kinds are represented as protocol.UnknownEvent; preserve or ignore them rather than failing the whole connection. A major-version mismatch requires upgrading the bridge and SDK together. Go SDK releases are independent package releases, so pin a compatible jcode version in deployment and test the pair.

Troubleshooting

Symptom Likely cause Action
dial unix ...: no such file Bridge is not running or socket path is wrong Run jcode api-bridge; check JCODE_API_SOCKET, XDG_RUNTIME_DIR, and permissions.
Hello handshake fails Wrong socket, incompatible protocol, or non-jcode service Confirm the endpoint is the harness API socket and upgrade both sides.
ErrClosed during a request Bridge/daemon exited or transport was closed Reconnect, then refresh sessions. Do not blindly repeat mutations.
ErrSubscriberOverflow Consumer is slower than event production Consume faster, increase EventBuffer, or fan out from one reader.
No text appears Session is not attached or events are consumed after sending Subscribe/attach before send_message; inspect all event kinds and turn_done.
Permission request blocks Application has not answered the request Apply an explicit allow/deny policy and send the corresponding response request.
Private process cannot start Invalid binary, home, socket, or credentials Capture child stderr without logging secrets; verify paths and use a dedicated home.

Migration from exec-based integrations

An exec integration commonly starts jcode, writes prompts to stdin, parses terminal text, and treats process exit as completion. Migrate in stages:

Exec integration Go SDK replacement
exec.Command plus shell/terminal parsing Start jcode api-bridge or a private process, dial its API socket, call NewClient.
Prompt text on stdin Session.StartTurn; compatible callers may retain Session.Send or raw Notify.
Scraping stdout for tokens Turn.Next; compatible callers may retain Session.Events or raw Subscribe.
Killing the child for cancellation Turn.Cancel, a fresh bounded Turn.Wait, then owned instance shutdown.
Assuming process exit means success Inspect the immutable typed TurnResult.
Re-running the whole command after a timeout Reconnect, refresh history, and retry only operations known to be safe.

Keep the old exec path as a fallback while validating parity. Do not run both paths against the same live session unless you intentionally want concurrent actors. Once the SDK path is stable, remove shell quoting and terminal scraping, add explicit deadlines and permission policy, and redact protocol diagnostics before logging.

Examples and compile checks

The three examples are ordinary Go packages and are checked by:

gofmt -d .
go test ./...
go vet ./...
go build ./examples/oneshot ./examples/streaming ./examples/private

They require a live jcode bridge only at runtime. go build and go test do not contact a daemon.

Development, validation, and releases

This repository is the sole source for the Go SDK. Changes are developed and integrated on dev; reviewed semantic-version tags are the release boundaries consumed by Go modules. There is no embedded implementation in the Jcode repository and no projection, mirror, preview/apply, or reverse-synchronization workflow.

Run the complete repository-owned quality matrix here:

test -z "$(gofmt -l .)"
go mod tidy -diff
go mod verify
go vet ./...
go build ./...
go test ./... -count=1
go test -race ./... -count=1
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./...

CI runs the supported Go 1.23.x and 1.24.x matrix. Results from another local toolchain are supplementary.

The separate Jcode repository remains the authoritative Rust protocol-v1 wire source in crates/jcode-harness-api. Validate this SDK checkout against an intended Jcode revision by running from that Jcode checkout:

scripts/validate_jcode_go_compat.sh --jcode-go-dir /absolute/path/to/jcode-go

That command sets JCODE_REPO_ROOT for this repository's protocol parity tests, uses read-only module mode, and leaves both checkouts unchanged. Tagging and publishing a release still require explicit maintainer authorization.

Documentation

Index

Constants

View Source
const (
	EventSemanticClassContentProgress   EventSemanticClass = "content_progress"
	EventSemanticClassAdvisoryLifecycle EventSemanticClass = "advisory_lifecycle"
	EventSemanticClassTerminal          EventSemanticClass = "terminal"
	EventSemanticClassPermission        EventSemanticClass = "permission"
	EventSemanticClassToolEffect        EventSemanticClass = "tool_effect"
	// Short aliases keep handler switches readable while the prefixed names
	// remain unambiguous in generated documentation and downstream code.
	SemanticClassContentProgress   = EventSemanticClassContentProgress
	SemanticClassAdvisoryLifecycle = EventSemanticClassAdvisoryLifecycle
	SemanticClassTerminal          = EventSemanticClassTerminal
	SemanticClassPermission        = EventSemanticClassPermission
	SemanticClassToolEffect        = EventSemanticClassToolEffect
)

Variables

View Source
var (
	ErrClosed             = errors.New("jcode client closed")
	ErrDisconnected       = errors.New("jcode client disconnected")
	ErrSubscriberOverflow = errors.New("jcode event subscriber fell behind")
	ErrCapability         = errors.New("jcode capability is not supported")
	ErrResume             = errors.New("jcode session resume failed")
)
View Source
var (
	// ErrTurnCanceled reports explicit server-side cancellation of an owned turn.
	ErrTurnCanceled = errors.New("jcode turn canceled")
	// ErrProtocolFailure reports that an owned turn ended because protocol input
	// could not be framed, validated, or decoded safely.
	ErrProtocolFailure = errors.New("jcode turn protocol failure")
	// ErrBridgeExited reports positive evidence that the SDK-owned bridge process
	// attached to the client exited.
	ErrBridgeExited = errors.New("jcode bridge exited")
)
View Source
var ErrInvalidOptions = errors.New("invalid options")

ErrInvalidOptions classifies validation failures in typed SDK options.

View Source
var ErrTurnNoReply = errors.New("jcode turn does not support no-reply messages")

ErrTurnNoReply reports that StartTurn was called for a notification-only message, which cannot have an owned turn lifecycle.

Functions

func InheritCredentials

func InheritCredentials(fromHome, toHome string) ([]string, error)

InheritCredentials shares rotating auth files and copies mutable config.

func RedactedID

func RedactedID(value string) string

RedactedID can be used by applications to correlate a session without placing the raw identifier in logs.

func ShutdownInstance added in v0.1.5

func ShutdownInstance(ctx context.Context, instance Instance) error

ShutdownInstance shuts down an Instance while allowing a caller context to shorten the cooperative phase of SDK-owned Linux private instances. The Instance interface remains unchanged so external implementations stay source compatible. Cancellation never skips best-effort termination and cleanup.

Types

type BackgroundProgress added in v0.1.5

type BackgroundProgress struct {
	SessionID string  `json:"session_id"`
	TaskID    string  `json:"task_id"`
	Label     string  `json:"label"`
	Percent   float64 `json:"percent,omitempty"`
	Summary   string  `json:"summary"`
	Done      bool    `json:"done,omitempty"`
}

type Client

type Client struct {
	// contains filtered or unexported fields
}

func Connect

func Connect(ctx context.Context, options ConnectOptions) (*Client, error)

Connect attaches to a running local Jcode harness. It never starts or stops a process, and closing the returned client only closes its socket.

func Launch

func Launch(ctx context.Context, options LaunchOptions) (*Client, error)

Launch starts a private bridge and connects a Client to it. Any failure after the process starts performs best-effort teardown before returning.

func NewClient

func NewClient(ctx context.Context, t transport.Transport, options Options) (*Client, error)

NewClient starts reading from t and completes the protocol hello handshake. The transport is closed if the handshake fails. Reconnect is always explicit.

func (*Client) AttachSession

func (c *Client) AttachSession(ctx context.Context, id string) (Session, error)

func (*Client) Capabilities

func (c *Client) Capabilities() []string

Capabilities returns the server-advertised capabilities in stable order. The returned slice is a copy and may be modified by the caller.

func (*Client) Close

func (c *Client) Close() error

Close is idempotent and wakes all pending requests and subscriptions.

func (*Client) CreateSession

func (c *Client) CreateSession(ctx context.Context, options CreateSessionOptions) (Session, error)

func (*Client) DetachInstance added in v0.1.2

func (c *Client) DetachInstance() (Instance, bool)

DetachInstance transfers ownership of a private runtime from the client to the caller. After a successful detach, closing the client only closes its protocol transport. The returned Instance remains responsible for the private process, daemon, and SDK-owned state until Shutdown is called.

This is the safe-run boundary for applications that let a worker client come and go while a session owner supervises the runtime independently.

func (*Client) ForkSession added in v0.1.8

func (c *Client) ForkSession(ctx context.Context, id string) (Session, error)

ForkSession clones a session's persisted context into a new session.

func (*Client) Notify added in v0.1.3

func (c *Client) Notify(req protocol.RawRequest) error

Notify writes a request without waiting for a correlated request-level reply. Use this for protocol operations whose result is delivered as an event.

func (*Client) Reconnect

func (c *Client) Reconnect(ctx context.Context) error

Reconnect explicitly reconnects and, when configured, safely reattaches the remembered session. In-flight requests are never retried.

func (*Client) Request

Request sends a raw request. It never retries, preserving caller control for non-idempotent operations such as send_message.

func (*Client) RequestCapability

func (c *Client) RequestCapability(ctx context.Context, capability string, req protocol.RawRequest) (protocol.ServerFrame, error)

RequestCapability sends req only when the server advertised capability. Unlike Request, this performs a local check and never writes an unsupported request to the wire.

func (*Client) RequireCapability

func (c *Client) RequireCapability(capability string) error

RequireCapability returns a stable error before a capability-gated request.

func (*Client) SessionID

func (c *Client) SessionID() string

SessionID returns the caller-selected identity retained across reconnects.

func (*Client) SetSessionID

func (c *Client) SetSessionID(sessionID string)

func (*Client) State

func (c *Client) State() State

func (*Client) Subscribe

func (c *Client) Subscribe(sessionID string) *Subscription

Subscribe receives asynchronous events. A full buffer terminates only that subscription, keeping the reader bounded and other callers live.

func (*Client) Supports

func (c *Client) Supports(capability string) bool

Supports reports whether the server advertised capability.

type Compacted added in v0.1.6

type Compacted struct {
	SessionID string `json:"session_id"`
	Message   string `json:"message"`
}

Compacted reports that session compaction was scheduled.

type CompatibilityError added in v0.1.5

type CompatibilityError struct {
	Kind      string
	EventType string
}

CompatibilityError is a payload-free, bounded failure for an unsupported or semantically unclassified owned-turn event.

func (*CompatibilityError) Error added in v0.1.5

func (e *CompatibilityError) Error() string

func (*CompatibilityError) Unwrap added in v0.1.5

func (*CompatibilityError) Unwrap() error

type ConnectOptions

type ConnectOptions struct {
	SocketPath    string
	ClientOptions Options
}

ConnectOptions controls attaching to an already-running local harness.

type ConnectionPhase added in v0.1.5

type ConnectionPhase struct {
	SessionID string `json:"session_id"`
	Phase     string `json:"phase"`
}

type CreateSessionOptions

type CreateSessionOptions struct {
	WorkingDir string
	Profile    string
}

type CredentialUpdated added in v0.1.6

type CredentialUpdated struct {
	Provider   string `json:"provider"`
	Configured bool   `json:"configured"`
}

CredentialUpdated reports whether a provider credential is configured.

type Event

type Event struct {
	Frame  protocol.ServerFrame
	Kind   string
	Fields json.RawMessage
}

Event is a server event with its stable kind and forward-compatible fields.

func (Event) Decode

func (e Event) Decode(value any) error

type EventError added in v0.1.5

type EventError struct {
	Code         string
	Message      string
	ProviderCode string
}

EventError reports a typed error emitted by the harness. It preserves the protocol code and safe message fields for callers that need to classify request or provider failures without inspecting raw protocol frames.

func (EventError) Error added in v0.1.5

func (e EventError) Error() string

type EventSemanticClass added in v0.1.5

type EventSemanticClass string

EventSemanticClass is the closed handling policy for an owned-turn event.

func SemanticClassOf added in v0.1.5

func SemanticClassOf(event TypedEvent) (EventSemanticClass, bool)

SemanticClassOf returns the reviewed class for a known concrete event. Unknown, nil, and unclassified values deliberately return false.

type FileContent added in v0.1.6

type FileContent struct {
	SessionID string `json:"session_id"`
	Path      string `json:"path"`
	Content   string `json:"content"`
	Size      uint64 `json:"size"`
	Truncated bool   `json:"truncated"`
}

FileContent is the result of reading a file through the harness.

type FileStatus added in v0.1.6

type FileStatus struct {
	SessionID  string  `json:"session_id"`
	Path       string  `json:"path"`
	Exists     bool    `json:"exists"`
	Kind       string  `json:"kind"`
	Size       *uint64 `json:"size,omitempty"`
	ModifiedMS *uint64 `json:"modified_ms,omitempty"`
}

FileStatus reports file existence and optional metadata.

type Files added in v0.1.6

type Files struct {
	SessionID string   `json:"session_id"`
	Paths     []string `json:"paths"`
}

Files is the result of finding files through the harness.

type Instance

type Instance interface {
	SocketPath() string
	JcodeHome() string
	Shutdown() error
	Close() error
}

LaunchInstance is the ownership handle for a private runtime.

func LaunchInstance

func LaunchInstance(options LaunchOptions) (Instance, error)

LaunchInstance starts an isolated bridge without connecting the protocol client. This is useful when the caller needs to control connection setup.

type LaunchError

type LaunchError struct {
	Code   LaunchErrorCode
	Binary string
	Stderr string
	Err    error
}

LaunchError reports a failure in private-instance startup or connection.

func (*LaunchError) Error

func (e *LaunchError) Error() string

func (*LaunchError) Unwrap

func (e *LaunchError) Unwrap() error

type LaunchErrorCode

type LaunchErrorCode string

LaunchErrorCode identifies the phase which failed while starting an instance. Callers can use errors.As to inspect a LaunchError without parsing its human-readable message.

const (
	LaunchMissingBinary   LaunchErrorCode = "missing_binary"
	LaunchStartupFailed   LaunchErrorCode = "startup_failed"
	LaunchStartupTimeout  LaunchErrorCode = "startup_timeout"
	LaunchHandshakeFailed LaunchErrorCode = "handshake_failed"
	LaunchTransportFailed LaunchErrorCode = "transport_failed"
)

type LaunchOptions

type LaunchOptions struct {
	// JcodeHome is a persistent state directory. Empty creates a temporary
	// SDK-owned directory which is removed by Shutdown.
	JcodeHome  string
	WorkingDir string
	// InheritLogins is tri-state so its nil zero value preserves the SDK
	// behavior shared by the Rust and TypeScript SDKs: inherit by default.
	InheritLogins *bool
	Binary        string
	// Provider and Model are passed as global jcode CLI selections when starting
	// the private API bridge. Empty values preserve jcode's normal auto/config
	// resolution.
	Provider       string
	Model          string
	Env            map[string]string
	StartupTimeout time.Duration
	CleanupTimeout time.Duration
	// ShutdownGracePeriod bounds cooperative SIGTERM shutdown before Linux
	// private instances escalate to SIGKILL. Non-positive values use 5 seconds.
	ShutdownGracePeriod time.Duration
	// ShutdownReapTimeout bounds waiting for the single bridge Wait result after
	// termination attempts. Non-positive values use 5 seconds.
	ShutdownReapTimeout time.Duration
	InheritStderr       bool
	// ClientOptions controls the protocol client created by Launch.
	ClientOptions Options
	// Observer receives redacted startup lifecycle metadata. If set, it is also
	// used by the protocol client created by Launch unless ClientOptions.Observer
	// is explicitly provided.
	Observer Observer
}

LaunchOptions controls a private jcode instance.

type MessageAccepted added in v0.1.5

type MessageAccepted struct {
	SessionID string `json:"session_id"`
}

type ModelInfo

type ModelInfo struct {
	SessionID       string `json:"session_id"`
	Provider        string `json:"provider,omitempty"`
	Model           string `json:"model,omitempty"`
	ReasoningEffort string `json:"reasoning_effort,omitempty"`
}

type ModelRouteInfo added in v0.1.6

type ModelRouteInfo struct {
	Model     string `json:"model"`
	Provider  string `json:"provider"`
	APIMethod string `json:"api_method"`
	Available bool   `json:"available"`
	Detail    string `json:"detail"`
}

ModelRouteInfo describes one provider route exposed by the runtime.

type Models added in v0.1.6

type Models struct {
	SessionID string   `json:"session_id"`
	Models    []string `json:"models"`
	Current   string   `json:"current,omitempty"`
}

Models reports the models available to a session and its current model.

type Observation

type Observation struct {
	Kind     string
	State    State
	Request  string
	Error    string
	Attempts int
	// EventKind, EventType, and Disposition are set only for the bounded
	// advisory compatibility observation emitted by an owned Turn.
	EventKind   string
	EventType   string
	Disposition string
	// Outcome is set only for an immutable terminal turn observation.
	Outcome TurnResultKind
}

Observation contains only bounded lifecycle metadata. It intentionally excludes request fields, prompts, credentials and environment values, server response or tool content, raw frames, private paths, and session identifiers.

type Observer

type Observer interface{ Observe(Observation) }

Observer receives redacted lifecycle metadata. Implementations must be safe for concurrent calls and should return quickly. Request events and the launch, connect, owned-turn, and Linux private-instance phases identify a stalled boundary without exposing payloads, credentials, paths, or identifiers.

type OptionError added in v0.1.6

type OptionError struct {
	Field string
}

OptionError identifies the typed option field that failed validation. It intentionally retains no rejected value.

func (*OptionError) Error added in v0.1.6

func (e *OptionError) Error() string

func (*OptionError) Unwrap added in v0.1.6

func (*OptionError) Unwrap() error

type Options

type Options struct {
	ClientName    string
	MaxFrameSize  int
	EventBuffer   int
	RequestBuffer int
	// RequestTimeout bounds each request when the caller's context has no
	// earlier deadline. Non-positive values use the 30-second default.
	RequestTimeout time.Duration
	SessionID      string
	Reconnect      ReconnectPolicy
	Observer       Observer
}

Options controls client construction and event buffering.

type PermissionRequest

type PermissionRequest struct {
	SessionID   string `json:"session_id"`
	RequestID   string `json:"request_id"`
	ToolName    string `json:"tool_name"`
	Description string `json:"description"`
}

type ReasoningDelta

type ReasoningDelta struct {
	SessionID string `json:"session_id"`
	Text      string `json:"text"`
}

type ReasoningDone added in v0.1.5

type ReasoningDone struct {
	SessionID    string  `json:"session_id"`
	DurationSecs float64 `json:"duration_secs,omitempty"`
}

type ReconnectPolicy

type ReconnectPolicy struct {
	Factory     transport.Factory
	MaxAttempts int
	Backoff     time.Duration
	MaxBackoff  time.Duration
	Resume      bool
}

type RenderedImage added in v0.1.6

type RenderedImage struct {
	MediaType string               `json:"media_type"`
	Data      string               `json:"data"`
	Label     *string              `json:"label,omitempty"`
	Source    RenderedImageSource  `json:"source"`
	Anchor    *RenderedImageAnchor `json:"anchor,omitempty"`
}

RenderedImage is an image and its optional transcript placement metadata.

type RenderedImageAnchor added in v0.1.6

type RenderedImageAnchor struct {
	Kind    string `json:"kind"`
	ID      string `json:"id,omitempty"`
	Ordinal uint64 `json:"ordinal,omitempty"`
}

RenderedImageAnchor identifies where a rendered image belongs in a transcript.

type RenderedImageSource added in v0.1.6

type RenderedImageSource struct {
	Kind     string `json:"kind"`
	ToolName string `json:"tool_name,omitempty"`
	Role     string `json:"role,omitempty"`
}

RenderedImageSource identifies where a rendered image originated.

type RuntimeInfo added in v0.1.6

type RuntimeInfo struct {
	SessionID       string           `json:"session_id"`
	Provider        string           `json:"provider,omitempty"`
	Model           string           `json:"model,omitempty"`
	ReasoningEffort string           `json:"reasoning_effort,omitempty"`
	Routes          []ModelRouteInfo `json:"routes"`
}

RuntimeInfo reports provider identity and every route exposed by the runtime.

type SendOptions

type SendOptions struct {
	Images      [][2]string
	NoReply     bool
	MaxTurns    int
	TokenBudget int
	Deadline    string
}

type Session

type Session struct {
	Info SessionInfo
	ID   string
	// contains filtered or unexported fields
}

func (Session) Events

func (s Session) Events(ctx context.Context) *TypedEventStream

func (Session) Send

func (s Session) Send(ctx context.Context, content string, options SendOptions) error

func (Session) StartTurn added in v0.1.5

func (s Session) StartTurn(lifecycleCtx context.Context, content string, options SendOptions) (*Turn, error)

StartTurn starts one owned turn. Its subscription and dispatcher are ready before send_message is written, so fast acceptance and terminal events cannot be missed. The method returns after the write succeeds, before acceptance.

type SessionForked added in v0.1.8

type SessionForked struct {
	Session SessionInfo `json:"session"`
}

SessionForked reports the new session returned by ForkSession.

type SessionInfo

type SessionInfo struct {
	ID              string `json:"session_id"`
	WorkingDir      string `json:"working_dir,omitempty"`
	Title           string `json:"title,omitempty"`
	Status          string `json:"status"`
	TranscriptBytes uint64 `json:"transcript_bytes,omitempty"`
	Archived        bool   `json:"archived,omitempty"`
	ArchivedAtMS    uint64 `json:"archived_at_ms,omitempty"`
}

SessionInfo is the stable session metadata returned by the harness API.

type SessionRenamed added in v0.1.6

type SessionRenamed struct {
	SessionID    string  `json:"session_id"`
	Title        *string `json:"title,omitempty"`
	DisplayTitle string  `json:"display_title"`
}

SessionRenamed reports a session title change. Title is nil when cleared.

type SessionStatus added in v0.1.5

type SessionStatus struct {
	SessionID string `json:"session_id"`
	Status    string `json:"status"`
}

type SidePaneImages added in v0.1.6

type SidePaneImages struct {
	SessionID string          `json:"session_id"`
	Images    []RenderedImage `json:"images"`
}

SidePaneImages reports images produced for the attached session.

type State

type State string
const (
	StateConnecting   State = "connecting"
	StateConnected    State = "connected"
	StateDisconnected State = "disconnected"
	StateReconnecting State = "reconnecting"
	StateClosing      State = "closing"
	StateClosed       State = "closed"
)

type Subscription

type Subscription struct {
	// contains filtered or unexported fields
}

func (*Subscription) Close

func (s *Subscription) Close()

func (*Subscription) Next

func (s *Subscription) Next(ctx context.Context) (Event, error)

type TextDelta

type TextDelta struct {
	SessionID string `json:"session_id"`
	Text      string `json:"text"`
}

type TextMatch added in v0.1.6

type TextMatch struct {
	Path    string `json:"path"`
	Line    uint32 `json:"line"`
	Column  uint32 `json:"column"`
	Preview string `json:"preview"`
}

TextMatch identifies one text search result.

type TextMatches added in v0.1.6

type TextMatches struct {
	SessionID string      `json:"session_id"`
	Matches   []TextMatch `json:"matches"`
}

TextMatches is the result of searching text through the harness.

type TokenUsage

type TokenUsage struct {
	SessionID      string `json:"session_id"`
	Input          int64  `json:"input"`
	Output         int64  `json:"output"`
	CacheReadInput int64  `json:"cache_read_input,omitempty"`
}

type ToolDone

type ToolDone struct {
	SessionID string `json:"session_id"`
	CallID    string `json:"call_id"`
	Name      string `json:"name"`
	Output    string `json:"output"`
	Error     string `json:"error,omitempty"`
}

type ToolExec added in v0.1.5

type ToolExec struct {
	SessionID string `json:"session_id"`
	CallID    string `json:"call_id"`
	Name      string `json:"name"`
}

type ToolInputDelta

type ToolInputDelta struct {
	SessionID string `json:"session_id"`
	CallID    string `json:"call_id"`
	Delta     string `json:"delta"`
}

type ToolStart

type ToolStart struct {
	SessionID string `json:"session_id"`
	CallID    string `json:"call_id"`
	Name      string `json:"name"`
}

type Turn added in v0.1.5

type Turn struct {
	// contains filtered or unexported fields
}

Turn owns one prompt lifecycle, including its single underlying event subscription, acceptance, ordered typed events, explicit cancellation, and immutable terminal result.

func (*Turn) Accepted added in v0.1.5

func (t *Turn) Accepted(ctx context.Context) error

Accepted waits only for server acceptance. Canceling ctx interrupts this wait without changing the turn lifecycle or sending protocol cancellation.

func (*Turn) Cancel added in v0.1.5

func (t *Turn) Cancel(ctx context.Context) error

Cancel starts the protocol cancel request at most once. The request runs under the Client request timeout, while each caller's ctx bounds only that caller's wait for the shared attempt result. A successful request is not terminal.

func (*Turn) Next added in v0.1.5

func (t *Turn) Next(ctx context.Context) (TypedEvent, error)

Next returns the turn's typed events in server order. message_accepted is exposed through Accepted rather than duplicated in this stream. Only one goroutine may call Next at a time. Canceling ctx interrupts only this read.

func (*Turn) Wait added in v0.1.5

func (t *Turn) Wait(ctx context.Context) (TurnResult, error)

Wait waits for the immutable first terminal result. Its error return only reports interruption of this particular wait before the turn is terminal.

type TurnDone

type TurnDone struct {
	SessionID string `json:"session_id"`
}

type TurnResult added in v0.1.5

type TurnResult struct {
	Kind TurnResultKind
	Err  error
}

TurnResult is the immutable outcome stored by a Turn. Err preserves the underlying cause for errors.Is and errors.As inspection.

type TurnResultKind added in v0.1.5

type TurnResultKind string

TurnResultKind identifies the stable semantic terminal class of a turn.

const (
	// TurnResultCompleted reports successful turn completion.
	TurnResultCompleted TurnResultKind = "completed"
	// TurnResultCanceled reports explicit server-side turn cancellation.
	TurnResultCanceled TurnResultKind = "canceled"
	// TurnResultLifecycleCanceled reports local lifecycle context cancellation.
	TurnResultLifecycleCanceled TurnResultKind = "lifecycle_canceled"
	// TurnResultLifecycleDeadlineExceeded reports local lifecycle deadline expiry.
	TurnResultLifecycleDeadlineExceeded TurnResultKind = "lifecycle_deadline_exceeded"
	// TurnResultProviderError reports a provider failure event.
	TurnResultProviderError TurnResultKind = "provider_error"
	// TurnResultProtocolError reports invalid framing, protocol data, or typed event data.
	TurnResultProtocolError TurnResultKind = "protocol_error"
	// TurnResultSubscriberOverflow reports a bounded event queue overflow.
	TurnResultSubscriberOverflow TurnResultKind = "subscriber_overflow"
	// TurnResultBridgeExited reports exit of the attached SDK-owned bridge.
	TurnResultBridgeExited TurnResultKind = "bridge_exited"
	// TurnResultTransportDisconnected reports loss of the client transport.
	TurnResultTransportDisconnected TurnResultKind = "transport_disconnected"
	// TurnResultClientClosed reports local Client.Close termination.
	TurnResultClientClosed TurnResultKind = "client_closed"
)

type TypedEvent

type TypedEvent interface {
	// contains filtered or unexported methods
}

type TypedEventStream

type TypedEventStream struct {
	// contains filtered or unexported fields
}

func (*TypedEventStream) Close

func (s *TypedEventStream) Close()

func (*TypedEventStream) Next

type UnknownEvent

type UnknownEvent struct {
	Kind   string
	Fields json.RawMessage
}

type WakeRequested added in v0.1.8

type WakeRequested struct {
	SessionID    string `json:"session_id"`
	Reason       string `json:"reason"`
	Notification string `json:"notification"`
}

WakeRequested asks an external operator to decide when to run a session.

Directories

Path Synopsis
examples
oneshot command
private command
streaming command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL