runner

package
v0.9.3 Latest Latest
Warning

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

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package runner is the entire ccgate PermissionRequest hook orchestration. There is no per-target adapter layer: stdin/stdout shapes are shared across Claude Code and Codex CLI (both deliver session_id / transcript_path / cwd / hook_event_name / tool_name / tool_input on stdin and the same hookSpecificOutput.decision shape on stdout). Per-target differences are handled here directly:

  • Claude-only fields: permission_mode, permission_suggestions
  • Codex-only fields: model, turn_id

cmd/<target>/ packages stay tiny -- they only hand a config.LoadOptions (where to read the per-user config / write the per-target log+metrics) and call Run.

Index

Constants

View Source
const PermissionModePlan = "plan"

PermissionModePlan is the Claude Code permission_mode value that puts ccgate into plan-mode evaluation.

Variables

This section is empty.

Functions

func Run

func Run(stdin io.Reader, stdout io.Writer, opts config.LoadOptions, runOpts ...Option) int

Run is the entry point. cmd/<target>/ packages call it with a per-target config.LoadOptions plus any target-specific Options (settings reader, transcript reader, ...). The rest of the flow is identical.

Types

type Context

type Context struct {
	gitutil.Context
	ReferencedPaths []string `json:"referenced_paths,omitempty"`
}

Context bundles the working-directory + git information ccgate surfaces to the LLM together with the tool-derived referenced_paths. Wraps gitutil.Context with the path-extraction output so both live in one nested object the LLM can navigate by name.

type HookContentUpdate

type HookContentUpdate struct {
	OldString string `json:"old_str"`
	NewString string `json:"new_str"`
}

HookContentUpdate is the per-hunk Edit / apply_patch payload.

type HookInput

type HookInput struct {
	SessionID      string `json:"session_id"`
	TranscriptPath string `json:"transcript_path"`
	Cwd            string `json:"cwd"`
	HookEventName  string `json:"hook_event_name"`
	ToolName       string `json:"tool_name"`

	ToolInput    HookToolInput   `json:"-"`
	ToolInputRaw json.RawMessage `json:"-"`

	// Claude-only
	PermissionMode        string            `json:"permission_mode,omitempty"`
	PermissionSuggestions []json.RawMessage `json:"permission_suggestions,omitempty"`

	// Codex-only
	Model  string `json:"model,omitempty"`
	TurnID string `json:"turn_id,omitempty"`
}

HookInput is the on-the-wire JSON Claude Code and Codex CLI both deliver to the PermissionRequest hook. Fields that only one of the two surfaces stay omitempty so the user payload sent to the LLM only contains what actually arrived.

func (*HookInput) UnmarshalJSON

func (h *HookInput) UnmarshalJSON(data []byte) error

UnmarshalJSON keeps the raw tool_input bytes around so the LLM sees every field the upstream hook delivered (including tool-specific shapes ccgate doesn't yet model) while also surfacing the parsed fields metrics relies on.

type HookOutput

type HookOutput struct {
	HookSpecificOutput hookSpecificOutput `json:"hookSpecificOutput"`
}

HookOutput is the JSON response shape Claude Code and Codex both expect.

type HookToolInput

type HookToolInput struct {
	Command        string              `json:"command"`
	Description    string              `json:"description"`
	FilePath       string              `json:"file_path"`
	Path           string              `json:"path"`
	Pattern        string              `json:"pattern"`
	Content        string              `json:"content"`
	ContentUpdates []HookContentUpdate `json:"content_updates"`
}

HookToolInput is the canonical parsed view of tool_input shared by all targets. Fields are emitted unconditionally (no omitempty) so the LLM sees the full schema and can address fields by name even when the upstream tool left them empty -- the JSON shape itself documents what ccgate models. ToolInputRaw carries the verbatim upstream bytes alongside this view for tool shapes ccgate has not canonicalized yet.

type Option

type Option func(*runtimeOptions)

Option customises the runner with target-specific extra inputs the LLM payload + system prompt should carry. Pass them via Run's variadic.

func WithCacheTarget added in v0.8.0

func WithCacheTarget(name string) Option

WithCacheTarget sets the per-target subdirectory name used for the keystore cache layout ("claude" / "codex" / ...). It must match the subdir cmd/<target>/ already uses for log_path / metrics_path so a user looking at one place sees them all together. An empty string is fed straight to keystore.Options.TargetName, which means the cache file lands one level up under $XDG_CACHE_HOME/ccgate/ and is shared across targets — fine for Run callers that never configure provider.auth (env-var path only), since nothing is written there.

func WithHasRecentTranscript

func WithHasRecentTranscript(has bool) Option

WithHasRecentTranscript declares that the user payload carries a `recent_transcript` field. The decision-rule wording adjusts so the LLM is not told to consult a field that isn't there. Targets that have no equivalent (Codex today) leave this false.

func WithPromptSection

func WithPromptSection(fn func(cfg config.Config) string) Option

WithPromptSection injects target-specific guidance about which payload fields the target delivers and how the LLM should interpret them. Inserted between the decision rules and the allow/deny lists. The callback receives the loaded Config so the section can drop or alter paragraphs whose underlying payload field is suppressed (e.g. settings_permissions when include_settings_permissions_in_prompt is false). Targets that have nothing target-specific to say (Codex today) simply do not pass this option.

func WithProviderFactory added in v0.8.0

func WithProviderFactory(fn func(providerName string, p config.ProviderConfig, apiKey, baseURL string) llm.Provider) Option

WithProviderFactory replaces the LLM-client constructor used by decide(). Test-only: production callers leave it unset and the runner uses newProviderClient. The signature mirrors newProviderClient so a fake can implement llm.Provider with no awareness of the underlying SDK shape; the ProviderConfig argument lets fakes observe auth.type=profile + the profile name without re-parsing the merged config.

func WithRecentTranscript

func WithRecentTranscript(fn func(transcriptPath string) any) Option

WithRecentTranscript injects a target-specific transcript reader. Returning nil omits the payload entry. Targets whose transcript format ccgate does not yet model (Codex today) do not pass this option.

func WithStaticPermissions

func WithStaticPermissions(fn func(cwd string) any) Option

WithStaticPermissions injects a target-specific reader for the host tool's static allow/deny patterns (e.g. Claude Code's `~/.claude/settings.json` permissions). Returning nil omits the payload entry. Targets that have no equivalent (Codex today -- its `~/.codex/config.toml` prefix_rules ingestion is a separate piece of work) simply do not pass this option.

func WithTargetName

func WithTargetName(name string) Option

WithTargetName labels the host tool in the system prompt header (e.g. "Claude Code", "Codex CLI"). The default header text falls back to a generic phrasing if unset.

type PromptInput

type PromptInput struct {
	ToolName              string            `json:"tool_name"`
	ToolInput             HookToolInput     `json:"tool_input"`
	ToolInputRaw          json.RawMessage   `json:"tool_input_raw,omitempty"`
	PermissionMode        string            `json:"permission_mode,omitempty"`
	PermissionSuggestions []json.RawMessage `json:"permission_suggestions,omitempty"`
	Context               Context           `json:"context"`
	SettingsPermissions   any               `json:"settings_permissions,omitempty"`
	RecentTranscript      any               `json:"recent_transcript,omitempty"`
	Model                 string            `json:"model,omitempty"`
	TurnID                string            `json:"turn_id,omitempty"`
}

PromptInput is the user-message JSON the LLM sees. It is the shared payload shape across targets: fields that one of the two targets does not deliver carry omitempty so they disappear from the JSON instead of showing up as empty values. The prompt's TargetSection (set via WithPromptSection) tells the LLM which fields are / are not present.

Jump to

Keyboard shortcuts

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