From f047d1a7240f1f8af27f673e977b390caadc9324 Mon Sep 17 00:00:00 2001 From: Colby Williams Date: Wed, 22 Apr 2026 01:04:34 -0500 Subject: [PATCH 1/2] Add Go client generation Add a Go code generator (scripts/generate-go.ts) that mirrors the existing Swift generator to produce an idiomatic Go module from TypeScript type definitions via ts-morph. Generated output: examples/go/ahp/ (package ahp) - state_generated.go: enums, structs, discriminated unions (StringOrMarkdown, SnapshotState, ResponsePart, ToolCallState, etc.) - actions_generated.go: ActionType enum, action structs, StateAction union with unknown/raw fallback for forward compatibility - commands_generated.go: command param/result structs, ReconnectResult union - notifications_generated.go: notification types, ProtocolNotification union - errors_generated.go: JSON-RPC and AHP error code constants - messages_generated.go: generic JSON-RPC types (Go 1.21 generics) and typed command/notification helper constructors Key design choices: - json.RawMessage for unknown types, map[string]json.RawMessage for object - Pointer types (*T) with omitempty for optional fields - Wrapper structs with custom MarshalJSON/UnmarshalJSON for discriminated unions - Go acronym conventions (ID, URL, URI, JSON, RPC, API, HTTP, IP) - Exhaustiveness check against types/version/v1.ts (same as Swift generator) - go.mod preserved across regeneration (only created if missing) Wiring: - generate.ts: --go flag and generateGoModule() integration - package.json: generate:go script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/go/ahp/actions_generated.go | 1047 ++++++++++++ examples/go/ahp/commands_generated.go | 431 +++++ examples/go/ahp/errors_generated.go | 43 + examples/go/ahp/go.mod | 3 + examples/go/ahp/messages_generated.go | 156 ++ examples/go/ahp/notifications_generated.go | 147 ++ examples/go/ahp/state_generated.go | 1741 ++++++++++++++++++++ package.json | 1 + scripts/generate-go.ts | 1419 ++++++++++++++++ scripts/generate.ts | 11 +- 10 files changed, 4998 insertions(+), 1 deletion(-) create mode 100644 examples/go/ahp/actions_generated.go create mode 100644 examples/go/ahp/commands_generated.go create mode 100644 examples/go/ahp/errors_generated.go create mode 100644 examples/go/ahp/go.mod create mode 100644 examples/go/ahp/messages_generated.go create mode 100644 examples/go/ahp/notifications_generated.go create mode 100644 examples/go/ahp/state_generated.go create mode 100644 scripts/generate-go.ts diff --git a/examples/go/ahp/actions_generated.go b/examples/go/ahp/actions_generated.go new file mode 100644 index 00000000..f428421b --- /dev/null +++ b/examples/go/ahp/actions_generated.go @@ -0,0 +1,1047 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +import ( + "encoding/json" + "fmt" +) + +// ── ActionType ──────────────────────────────────────────────────────────────── + +// Discriminant values for all state actions. +type ActionType string + +const ( + ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" + ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" + ActionTypeSessionReady ActionType = "session/ready" + ActionTypeSessionCreationFailed ActionType = "session/creationFailed" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" + ActionTypeSessionTitleChanged ActionType = "session/titleChanged" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" + ActionTypeSessionModelChanged ActionType = "session/modelChanged" + ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" + ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" + ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" + ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" + ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" + ActionTypeSessionTruncated ActionType = "session/truncated" + ActionTypeSessionIsReadChanged ActionType = "session/isReadChanged" + ActionTypeSessionIsDoneChanged ActionType = "session/isDoneChanged" + ActionTypeSessionDiffsChanged ActionType = "session/diffsChanged" + ActionTypeSessionConfigChanged ActionType = "session/configChanged" + ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" + ActionTypeRootConfigChanged ActionType = "root/configChanged" + ActionTypeTerminalData ActionType = "terminal/data" + ActionTypeTerminalInput ActionType = "terminal/input" + ActionTypeTerminalResized ActionType = "terminal/resized" + ActionTypeTerminalClaimed ActionType = "terminal/claimed" + ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" + ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" + ActionTypeTerminalExited ActionType = "terminal/exited" + ActionTypeTerminalCleared ActionType = "terminal/cleared" + ActionTypeTerminalCommandDetectionAvailable ActionType = "terminal/commandDetectionAvailable" + ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" + ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" +) + +// ── Action Infrastructure ───────────────────────────────────────────────────── + +// ActionOrigin Identifies the client that originally dispatched an action. +type ActionOrigin struct { + ClientID string `json:"clientId"` + ClientSeq int `json:"clientSeq"` +} + +// ActionEnvelope Every action is wrapped in an `ActionEnvelope`. +type ActionEnvelope struct { + Action StateAction `json:"action"` + ServerSeq int `json:"serverSeq"` + Origin *ActionOrigin `json:"origin,omitempty"` + RejectionReason *string `json:"rejectionReason,omitempty"` +} + +// ── Action Types ───────────────────────────────────────────────────────────── + +// RootAgentsChangedAction Fired when available agent backends or their models change. +type RootAgentsChangedAction struct { + Type ActionType `json:"type"` + // Updated agent list + Agents []AgentInfo `json:"agents"` +} + +// RootActiveSessionsChangedAction Fired when the number of active sessions changes. +type RootActiveSessionsChangedAction struct { + Type ActionType `json:"type"` + // Current count of active sessions + ActiveSessions int `json:"activeSessions"` +} + +// SessionReadyAction Session backend initialized successfully. +type SessionReadyAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` +} + +// SessionCreationFailedAction Session backend failed to initialize. +type SessionCreationFailedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Error details + Error ErrorInfo `json:"error"` +} + +// SessionTurnStartedAction User sent a message; server starts agent processing. +type SessionTurnStartedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // User's message + UserMessage UserMessage `json:"userMessage"` + // If this turn was auto-started from a queued message, the ID of that message + QueuedMessageID *string `json:"queuedMessageId,omitempty"` +} + +// SessionDeltaAction Streaming text chunk from the assistant, appended to a specific response part. +type SessionDeltaAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Identifier of the response part to append to + PartID string `json:"partId"` + // Text chunk + Content string `json:"content"` +} + +// SessionResponsePartAction Structured content appended to the response. +type SessionResponsePartAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Response part (markdown or content ref) + Part ResponsePart `json:"part"` +} + +// SessionToolCallStartAction A tool call begins — parameters are streaming from the LM. +type SessionToolCallStartAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + ToolClientID *string `json:"toolClientId,omitempty"` +} + +// SessionToolCallDeltaAction Streaming partial parameters for a tool call. +type SessionToolCallDeltaAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // Partial parameter content to append + Content string `json:"content"` + // Updated progress message + InvocationMessage *StringOrMarkdown `json:"invocationMessage,omitempty"` +} + +// SessionToolCallReadyAction Tool call parameters are complete, or a running tool requires re-confirmation. +type SessionToolCallReadyAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // Message describing what the tool will do or what confirmation is needed + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) + ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` + // File edits that this tool call will perform, for preview before confirmation + Edits json.RawMessage `json:"edits,omitempty"` + // Whether the agent host allows the client to edit the tool's input parameters before confirming + Editable *bool `json:"editable,omitempty"` + // If set, the tool was auto-confirmed and transitions directly to `running` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + // Options the server offers for this confirmation. When present, the client + // SHOULD render these instead of a plain approve/deny UI. Each option + // belongs to a {@link ConfirmationOptionGroup} so the client can still + // categorise the choices. + Options []ConfirmationOption `json:"options,omitempty"` +} + +// SessionToolCallConfirmedAction represents a client approving or denying a pending tool call. +type SessionToolCallConfirmedAction struct { + Type string `json:"type"` + Session string `json:"session"` + TurnID string `json:"turnId"` + ToolCallID string `json:"toolCallId"` + Approved bool `json:"approved"` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + Reason *ToolCallCancellationReason `json:"reason,omitempty"` + UserSuggestion *UserMessage `json:"userSuggestion,omitempty"` + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` +} + +// SessionToolCallCompleteAction Tool execution finished. Transitions to `completed` or `pending-result-confirmation` +type SessionToolCallCompleteAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // Execution result + Result ToolCallResult `json:"result"` + // If true, the result requires client approval before finalizing + RequiresResultConfirmation *bool `json:"requiresResultConfirmation,omitempty"` +} + +// SessionToolCallResultConfirmedAction Client approves or denies a tool's result. +type SessionToolCallResultConfirmedAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // Whether the result was approved + Approved bool `json:"approved"` +} + +// SessionTurnCompleteAction Turn finished — the assistant is idle. +type SessionTurnCompleteAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` +} + +// SessionTurnCancelledAction Turn was aborted; server stops processing. +type SessionTurnCancelledAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` +} + +// SessionErrorAction Error during turn processing. +type SessionErrorAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Error details + Error ErrorInfo `json:"error"` +} + +// SessionTitleChangedAction Session title updated. Fired by the server when the title is auto-generated +type SessionTitleChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // New title + Title string `json:"title"` +} + +// SessionUsageAction Token usage report for a turn. +type SessionUsageAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Token usage data + Usage UsageInfo `json:"usage"` +} + +// SessionReasoningAction Reasoning/thinking text from the model, appended to a specific reasoning response part. +type SessionReasoningAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Identifier of the reasoning response part to append to + PartID string `json:"partId"` + // Reasoning text chunk + Content string `json:"content"` +} + +// SessionModelChangedAction Model changed for this session. +type SessionModelChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // New model selection + Model ModelSelection `json:"model"` +} + +// SessionIsReadChangedAction The read state of the session changed. +type SessionIsReadChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Whether the session has been read + IsRead bool `json:"isRead"` +} + +// SessionIsDoneChangedAction The done state of the session changed. +type SessionIsDoneChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Whether the session is done + IsDone bool `json:"isDone"` +} + +// SessionServerToolsChangedAction Server tools for this session have changed. +type SessionServerToolsChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Updated server tools list (full replacement) + Tools []ToolDefinition `json:"tools"` +} + +// SessionActiveClientChangedAction The active client for this session has changed. +type SessionActiveClientChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // The new active client, or `null` to unset + ActiveClient *SessionActiveClient `json:"activeClient"` +} + +// SessionActiveClientToolsChangedAction The active client's tool list has changed. +type SessionActiveClientToolsChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Updated client tools list (full replacement) + Tools []ToolDefinition `json:"tools"` +} + +// SessionPendingMessageSetAction A pending message was set (upsert semantics: creates or replaces). +type SessionPendingMessageSetAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Whether this is a steering or queued message + Kind PendingMessageKind `json:"kind"` + // Unique identifier for this pending message + ID string `json:"id"` + // The message content + UserMessage UserMessage `json:"userMessage"` +} + +// SessionPendingMessageRemovedAction A pending message was removed (steering or queued). +type SessionPendingMessageRemovedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Whether this is a steering or queued message + Kind PendingMessageKind `json:"kind"` + // Identifier of the pending message to remove + ID string `json:"id"` +} + +// SessionQueuedMessagesReorderedAction Reorder the queued messages. +type SessionQueuedMessagesReorderedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Queued message IDs in the desired order + Order []string `json:"order"` +} + +// SessionInputRequestedAction A session requested input from the user. +type SessionInputRequestedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Input request to create or replace + Request SessionInputRequest `json:"request"` +} + +// SessionInputAnswerChangedAction A client updated, submitted, skipped, or removed a single in-progress answer. +type SessionInputAnswerChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Input request identifier + RequestID string `json:"requestId"` + // Question identifier within the input request + QuestionID string `json:"questionId"` + // Updated answer, or `undefined` to clear an answer draft + Answer *SessionInputAnswer `json:"answer,omitempty"` +} + +// SessionInputCompletedAction A client accepted, declined, or cancelled a session input request. +type SessionInputCompletedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Input request identifier + RequestID string `json:"requestId"` + // Completion outcome + Response SessionInputResponseKind `json:"response"` + // Optional final answer replacement, keyed by question ID + Answers map[string]SessionInputAnswer `json:"answers,omitempty"` +} + +// SessionCustomizationsChangedAction The session's customizations have changed. +type SessionCustomizationsChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Updated customization list (full replacement) + Customizations []SessionCustomization `json:"customizations"` +} + +// SessionCustomizationToggledAction A client toggled a customization on or off. +type SessionCustomizationToggledAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // The URI of the customization to toggle + URI string `json:"uri"` + // Whether to enable or disable the customization + Enabled bool `json:"enabled"` +} + +// SessionTruncatedAction Truncates a session's history. If `turnId` is provided, all turns after that +type SessionTruncatedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Keep turns up to and including this turn. Omit to clear all turns. + TurnID *string `json:"turnId,omitempty"` +} + +// SessionDiffsChangedAction The file diffs for the session changed. +type SessionDiffsChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Updated file diffs for the session + Diffs []FileEdit `json:"diffs"` +} + +// SessionConfigChangedAction Client changed a mutable config value mid-session. +type SessionConfigChangedAction struct { + Type ActionType `json:"type"` + // Session URI + Session string `json:"session"` + // Updated config values + Config map[string]json.RawMessage `json:"config"` + // When `true`, replaces all config values instead of merging + Replace *bool `json:"replace,omitempty"` +} + +// SessionToolCallContentChangedAction Partial content produced while a tool is still executing. +type SessionToolCallContentChangedAction struct { + // Session URI + Session string `json:"session"` + // Turn identifier + TurnID string `json:"turnId"` + // Tool call identifier + ToolCallID string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // The current partial content for the running tool call + Content []ToolResultContent `json:"content"` +} + +// RootTerminalsChangedAction Fired when the list of known terminals changes. +type RootTerminalsChangedAction struct { + Type ActionType `json:"type"` + // Updated terminal list (full replacement) + Terminals []TerminalInfo `json:"terminals"` +} + +// RootConfigChangedAction Fired when agent-host configuration values change. +type RootConfigChangedAction struct { + Type ActionType `json:"type"` + // Updated config values + Config map[string]json.RawMessage `json:"config"` + // When `true`, replaces all config values instead of merging + Replace *bool `json:"replace,omitempty"` +} + +// TerminalDataAction Terminal output data (pty → client direction). +type TerminalDataAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Output data (may contain ANSI escape sequences) + Data string `json:"data"` +} + +// TerminalInputAction Keyboard input sent to the terminal process (client → pty direction). +type TerminalInputAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Input data to send to the pty + Data string `json:"data"` +} + +// TerminalResizedAction Terminal dimensions changed. +type TerminalResizedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Terminal width in columns + Cols int `json:"cols"` + // Terminal height in rows + Rows int `json:"rows"` +} + +// TerminalClaimedAction Terminal claim changed. A client or session transfers ownership of the terminal. +type TerminalClaimedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // The new claim + Claim TerminalClaim `json:"claim"` +} + +// TerminalTitleChangedAction Terminal title changed. +type TerminalTitleChangedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // New terminal title + Title string `json:"title"` +} + +// TerminalCwdChangedAction Terminal working directory changed. +type TerminalCwdChangedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // New working directory + Cwd string `json:"cwd"` +} + +// TerminalExitedAction Terminal process exited. +type TerminalExitedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Process exit code. `undefined` if the process was killed without an exit code. + ExitCode *int `json:"exitCode,omitempty"` +} + +// TerminalClearedAction Terminal scrollback buffer cleared. +type TerminalClearedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` +} + +// TerminalCommandDetectionAvailableAction Shell integration has loaded and the terminal now supports command +type TerminalCommandDetectionAvailableAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` +} + +// TerminalCommandExecutedAction A command has been submitted to the shell and is now executing. +type TerminalCommandExecutedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Stable identifier for this command, scoped to the terminal URI. + // Allows correlating `commandExecuted` → `commandFinished` pairs. + CommandID string `json:"commandId"` + // The command line text that was submitted + CommandLine string `json:"commandLine"` + // Unix timestamp (ms) of when the command started executing, as measured + // on the server. + Timestamp int `json:"timestamp"` +} + +// TerminalCommandFinishedAction A command has finished executing. +type TerminalCommandFinishedAction struct { + Type ActionType `json:"type"` + // Terminal URI + Terminal string `json:"terminal"` + // Matches the `commandId` from the corresponding `commandExecuted` + CommandID string `json:"commandId"` + // Shell exit code. `undefined` if the shell did not report one. + ExitCode *int `json:"exitCode,omitempty"` + // Wall-clock duration of the command in milliseconds, as measured by the + // shell integration script on the server side. + DurationMs *int `json:"durationMs,omitempty"` +} + +// ── StateAction Union ───────────────────────────────────────────────────────── + +// StateAction is a discriminated union of all state actions. +type StateAction struct { + RootAgentsChanged *RootAgentsChangedAction + RootActiveSessionsChanged *RootActiveSessionsChangedAction + SessionReady *SessionReadyAction + SessionCreationFailed *SessionCreationFailedAction + SessionTurnStarted *SessionTurnStartedAction + SessionDelta *SessionDeltaAction + SessionResponsePart *SessionResponsePartAction + SessionToolCallStart *SessionToolCallStartAction + SessionToolCallDelta *SessionToolCallDeltaAction + SessionToolCallReady *SessionToolCallReadyAction + SessionToolCallConfirmed *SessionToolCallConfirmedAction + SessionToolCallComplete *SessionToolCallCompleteAction + SessionToolCallResultConfirmed *SessionToolCallResultConfirmedAction + SessionTurnComplete *SessionTurnCompleteAction + SessionTurnCancelled *SessionTurnCancelledAction + SessionError *SessionErrorAction + SessionTitleChanged *SessionTitleChangedAction + SessionUsage *SessionUsageAction + SessionReasoning *SessionReasoningAction + SessionModelChanged *SessionModelChangedAction + SessionIsReadChanged *SessionIsReadChangedAction + SessionIsDoneChanged *SessionIsDoneChangedAction + SessionServerToolsChanged *SessionServerToolsChangedAction + SessionActiveClientChanged *SessionActiveClientChangedAction + SessionActiveClientToolsChanged *SessionActiveClientToolsChangedAction + SessionPendingMessageSet *SessionPendingMessageSetAction + SessionPendingMessageRemoved *SessionPendingMessageRemovedAction + SessionQueuedMessagesReordered *SessionQueuedMessagesReorderedAction + SessionInputRequested *SessionInputRequestedAction + SessionInputAnswerChanged *SessionInputAnswerChangedAction + SessionInputCompleted *SessionInputCompletedAction + SessionCustomizationsChanged *SessionCustomizationsChangedAction + SessionCustomizationToggled *SessionCustomizationToggledAction + SessionTruncated *SessionTruncatedAction + SessionDiffsChanged *SessionDiffsChangedAction + SessionConfigChanged *SessionConfigChangedAction + SessionToolCallContentChanged *SessionToolCallContentChangedAction + RootTerminalsChanged *RootTerminalsChangedAction + RootConfigChanged *RootConfigChangedAction + TerminalData *TerminalDataAction + TerminalInput *TerminalInputAction + TerminalResized *TerminalResizedAction + TerminalClaimed *TerminalClaimedAction + TerminalTitleChanged *TerminalTitleChangedAction + TerminalCwdChanged *TerminalCwdChangedAction + TerminalExited *TerminalExitedAction + TerminalCleared *TerminalClearedAction + TerminalCommandDetectionAvailable *TerminalCommandDetectionAvailableAction + TerminalCommandExecuted *TerminalCommandExecutedAction + TerminalCommandFinished *TerminalCommandFinishedAction + UnknownType string + UnknownRaw json.RawMessage +} + +func (u *StateAction) UnmarshalJSON(data []byte) error { + var disc struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.Type { + case "root/agentsChanged": + u.RootAgentsChanged = new(RootAgentsChangedAction) + return json.Unmarshal(data, u.RootAgentsChanged) + case "root/activeSessionsChanged": + u.RootActiveSessionsChanged = new(RootActiveSessionsChangedAction) + return json.Unmarshal(data, u.RootActiveSessionsChanged) + case "session/ready": + u.SessionReady = new(SessionReadyAction) + return json.Unmarshal(data, u.SessionReady) + case "session/creationFailed": + u.SessionCreationFailed = new(SessionCreationFailedAction) + return json.Unmarshal(data, u.SessionCreationFailed) + case "session/turnStarted": + u.SessionTurnStarted = new(SessionTurnStartedAction) + return json.Unmarshal(data, u.SessionTurnStarted) + case "session/delta": + u.SessionDelta = new(SessionDeltaAction) + return json.Unmarshal(data, u.SessionDelta) + case "session/responsePart": + u.SessionResponsePart = new(SessionResponsePartAction) + return json.Unmarshal(data, u.SessionResponsePart) + case "session/toolCallStart": + u.SessionToolCallStart = new(SessionToolCallStartAction) + return json.Unmarshal(data, u.SessionToolCallStart) + case "session/toolCallDelta": + u.SessionToolCallDelta = new(SessionToolCallDeltaAction) + return json.Unmarshal(data, u.SessionToolCallDelta) + case "session/toolCallReady": + u.SessionToolCallReady = new(SessionToolCallReadyAction) + return json.Unmarshal(data, u.SessionToolCallReady) + case "session/toolCallConfirmed": + u.SessionToolCallConfirmed = new(SessionToolCallConfirmedAction) + return json.Unmarshal(data, u.SessionToolCallConfirmed) + case "session/toolCallComplete": + u.SessionToolCallComplete = new(SessionToolCallCompleteAction) + return json.Unmarshal(data, u.SessionToolCallComplete) + case "session/toolCallResultConfirmed": + u.SessionToolCallResultConfirmed = new(SessionToolCallResultConfirmedAction) + return json.Unmarshal(data, u.SessionToolCallResultConfirmed) + case "session/turnComplete": + u.SessionTurnComplete = new(SessionTurnCompleteAction) + return json.Unmarshal(data, u.SessionTurnComplete) + case "session/turnCancelled": + u.SessionTurnCancelled = new(SessionTurnCancelledAction) + return json.Unmarshal(data, u.SessionTurnCancelled) + case "session/error": + u.SessionError = new(SessionErrorAction) + return json.Unmarshal(data, u.SessionError) + case "session/titleChanged": + u.SessionTitleChanged = new(SessionTitleChangedAction) + return json.Unmarshal(data, u.SessionTitleChanged) + case "session/usage": + u.SessionUsage = new(SessionUsageAction) + return json.Unmarshal(data, u.SessionUsage) + case "session/reasoning": + u.SessionReasoning = new(SessionReasoningAction) + return json.Unmarshal(data, u.SessionReasoning) + case "session/modelChanged": + u.SessionModelChanged = new(SessionModelChangedAction) + return json.Unmarshal(data, u.SessionModelChanged) + case "session/isReadChanged": + u.SessionIsReadChanged = new(SessionIsReadChangedAction) + return json.Unmarshal(data, u.SessionIsReadChanged) + case "session/isDoneChanged": + u.SessionIsDoneChanged = new(SessionIsDoneChangedAction) + return json.Unmarshal(data, u.SessionIsDoneChanged) + case "session/serverToolsChanged": + u.SessionServerToolsChanged = new(SessionServerToolsChangedAction) + return json.Unmarshal(data, u.SessionServerToolsChanged) + case "session/activeClientChanged": + u.SessionActiveClientChanged = new(SessionActiveClientChangedAction) + return json.Unmarshal(data, u.SessionActiveClientChanged) + case "session/activeClientToolsChanged": + u.SessionActiveClientToolsChanged = new(SessionActiveClientToolsChangedAction) + return json.Unmarshal(data, u.SessionActiveClientToolsChanged) + case "session/pendingMessageSet": + u.SessionPendingMessageSet = new(SessionPendingMessageSetAction) + return json.Unmarshal(data, u.SessionPendingMessageSet) + case "session/pendingMessageRemoved": + u.SessionPendingMessageRemoved = new(SessionPendingMessageRemovedAction) + return json.Unmarshal(data, u.SessionPendingMessageRemoved) + case "session/queuedMessagesReordered": + u.SessionQueuedMessagesReordered = new(SessionQueuedMessagesReorderedAction) + return json.Unmarshal(data, u.SessionQueuedMessagesReordered) + case "session/inputRequested": + u.SessionInputRequested = new(SessionInputRequestedAction) + return json.Unmarshal(data, u.SessionInputRequested) + case "session/inputAnswerChanged": + u.SessionInputAnswerChanged = new(SessionInputAnswerChangedAction) + return json.Unmarshal(data, u.SessionInputAnswerChanged) + case "session/inputCompleted": + u.SessionInputCompleted = new(SessionInputCompletedAction) + return json.Unmarshal(data, u.SessionInputCompleted) + case "session/customizationsChanged": + u.SessionCustomizationsChanged = new(SessionCustomizationsChangedAction) + return json.Unmarshal(data, u.SessionCustomizationsChanged) + case "session/customizationToggled": + u.SessionCustomizationToggled = new(SessionCustomizationToggledAction) + return json.Unmarshal(data, u.SessionCustomizationToggled) + case "session/truncated": + u.SessionTruncated = new(SessionTruncatedAction) + return json.Unmarshal(data, u.SessionTruncated) + case "session/diffsChanged": + u.SessionDiffsChanged = new(SessionDiffsChangedAction) + return json.Unmarshal(data, u.SessionDiffsChanged) + case "session/configChanged": + u.SessionConfigChanged = new(SessionConfigChangedAction) + return json.Unmarshal(data, u.SessionConfigChanged) + case "session/toolCallContentChanged": + u.SessionToolCallContentChanged = new(SessionToolCallContentChangedAction) + return json.Unmarshal(data, u.SessionToolCallContentChanged) + case "root/terminalsChanged": + u.RootTerminalsChanged = new(RootTerminalsChangedAction) + return json.Unmarshal(data, u.RootTerminalsChanged) + case "root/configChanged": + u.RootConfigChanged = new(RootConfigChangedAction) + return json.Unmarshal(data, u.RootConfigChanged) + case "terminal/data": + u.TerminalData = new(TerminalDataAction) + return json.Unmarshal(data, u.TerminalData) + case "terminal/input": + u.TerminalInput = new(TerminalInputAction) + return json.Unmarshal(data, u.TerminalInput) + case "terminal/resized": + u.TerminalResized = new(TerminalResizedAction) + return json.Unmarshal(data, u.TerminalResized) + case "terminal/claimed": + u.TerminalClaimed = new(TerminalClaimedAction) + return json.Unmarshal(data, u.TerminalClaimed) + case "terminal/titleChanged": + u.TerminalTitleChanged = new(TerminalTitleChangedAction) + return json.Unmarshal(data, u.TerminalTitleChanged) + case "terminal/cwdChanged": + u.TerminalCwdChanged = new(TerminalCwdChangedAction) + return json.Unmarshal(data, u.TerminalCwdChanged) + case "terminal/exited": + u.TerminalExited = new(TerminalExitedAction) + return json.Unmarshal(data, u.TerminalExited) + case "terminal/cleared": + u.TerminalCleared = new(TerminalClearedAction) + return json.Unmarshal(data, u.TerminalCleared) + case "terminal/commandDetectionAvailable": + u.TerminalCommandDetectionAvailable = new(TerminalCommandDetectionAvailableAction) + return json.Unmarshal(data, u.TerminalCommandDetectionAvailable) + case "terminal/commandExecuted": + u.TerminalCommandExecuted = new(TerminalCommandExecutedAction) + return json.Unmarshal(data, u.TerminalCommandExecuted) + case "terminal/commandFinished": + u.TerminalCommandFinished = new(TerminalCommandFinishedAction) + return json.Unmarshal(data, u.TerminalCommandFinished) + default: + u.UnknownType = disc.Type + u.UnknownRaw = make(json.RawMessage, len(data)) + copy(u.UnknownRaw, data) + return nil + } +} + +func (u StateAction) MarshalJSON() ([]byte, error) { + if u.RootAgentsChanged != nil { + return json.Marshal(u.RootAgentsChanged) + } + if u.RootActiveSessionsChanged != nil { + return json.Marshal(u.RootActiveSessionsChanged) + } + if u.SessionReady != nil { + return json.Marshal(u.SessionReady) + } + if u.SessionCreationFailed != nil { + return json.Marshal(u.SessionCreationFailed) + } + if u.SessionTurnStarted != nil { + return json.Marshal(u.SessionTurnStarted) + } + if u.SessionDelta != nil { + return json.Marshal(u.SessionDelta) + } + if u.SessionResponsePart != nil { + return json.Marshal(u.SessionResponsePart) + } + if u.SessionToolCallStart != nil { + return json.Marshal(u.SessionToolCallStart) + } + if u.SessionToolCallDelta != nil { + return json.Marshal(u.SessionToolCallDelta) + } + if u.SessionToolCallReady != nil { + return json.Marshal(u.SessionToolCallReady) + } + if u.SessionToolCallConfirmed != nil { + return json.Marshal(u.SessionToolCallConfirmed) + } + if u.SessionToolCallComplete != nil { + return json.Marshal(u.SessionToolCallComplete) + } + if u.SessionToolCallResultConfirmed != nil { + return json.Marshal(u.SessionToolCallResultConfirmed) + } + if u.SessionTurnComplete != nil { + return json.Marshal(u.SessionTurnComplete) + } + if u.SessionTurnCancelled != nil { + return json.Marshal(u.SessionTurnCancelled) + } + if u.SessionError != nil { + return json.Marshal(u.SessionError) + } + if u.SessionTitleChanged != nil { + return json.Marshal(u.SessionTitleChanged) + } + if u.SessionUsage != nil { + return json.Marshal(u.SessionUsage) + } + if u.SessionReasoning != nil { + return json.Marshal(u.SessionReasoning) + } + if u.SessionModelChanged != nil { + return json.Marshal(u.SessionModelChanged) + } + if u.SessionIsReadChanged != nil { + return json.Marshal(u.SessionIsReadChanged) + } + if u.SessionIsDoneChanged != nil { + return json.Marshal(u.SessionIsDoneChanged) + } + if u.SessionServerToolsChanged != nil { + return json.Marshal(u.SessionServerToolsChanged) + } + if u.SessionActiveClientChanged != nil { + return json.Marshal(u.SessionActiveClientChanged) + } + if u.SessionActiveClientToolsChanged != nil { + return json.Marshal(u.SessionActiveClientToolsChanged) + } + if u.SessionPendingMessageSet != nil { + return json.Marshal(u.SessionPendingMessageSet) + } + if u.SessionPendingMessageRemoved != nil { + return json.Marshal(u.SessionPendingMessageRemoved) + } + if u.SessionQueuedMessagesReordered != nil { + return json.Marshal(u.SessionQueuedMessagesReordered) + } + if u.SessionInputRequested != nil { + return json.Marshal(u.SessionInputRequested) + } + if u.SessionInputAnswerChanged != nil { + return json.Marshal(u.SessionInputAnswerChanged) + } + if u.SessionInputCompleted != nil { + return json.Marshal(u.SessionInputCompleted) + } + if u.SessionCustomizationsChanged != nil { + return json.Marshal(u.SessionCustomizationsChanged) + } + if u.SessionCustomizationToggled != nil { + return json.Marshal(u.SessionCustomizationToggled) + } + if u.SessionTruncated != nil { + return json.Marshal(u.SessionTruncated) + } + if u.SessionDiffsChanged != nil { + return json.Marshal(u.SessionDiffsChanged) + } + if u.SessionConfigChanged != nil { + return json.Marshal(u.SessionConfigChanged) + } + if u.SessionToolCallContentChanged != nil { + return json.Marshal(u.SessionToolCallContentChanged) + } + if u.RootTerminalsChanged != nil { + return json.Marshal(u.RootTerminalsChanged) + } + if u.RootConfigChanged != nil { + return json.Marshal(u.RootConfigChanged) + } + if u.TerminalData != nil { + return json.Marshal(u.TerminalData) + } + if u.TerminalInput != nil { + return json.Marshal(u.TerminalInput) + } + if u.TerminalResized != nil { + return json.Marshal(u.TerminalResized) + } + if u.TerminalClaimed != nil { + return json.Marshal(u.TerminalClaimed) + } + if u.TerminalTitleChanged != nil { + return json.Marshal(u.TerminalTitleChanged) + } + if u.TerminalCwdChanged != nil { + return json.Marshal(u.TerminalCwdChanged) + } + if u.TerminalExited != nil { + return json.Marshal(u.TerminalExited) + } + if u.TerminalCleared != nil { + return json.Marshal(u.TerminalCleared) + } + if u.TerminalCommandDetectionAvailable != nil { + return json.Marshal(u.TerminalCommandDetectionAvailable) + } + if u.TerminalCommandExecuted != nil { + return json.Marshal(u.TerminalCommandExecuted) + } + if u.TerminalCommandFinished != nil { + return json.Marshal(u.TerminalCommandFinished) + } + if u.UnknownRaw != nil { + return u.UnknownRaw, nil + } + return nil, fmt.Errorf("empty StateAction: no variant set") +} diff --git a/examples/go/ahp/commands_generated.go b/examples/go/ahp/commands_generated.go new file mode 100644 index 00000000..c71e8e32 --- /dev/null +++ b/examples/go/ahp/commands_generated.go @@ -0,0 +1,431 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +import ( + "encoding/json" + "fmt" +) + +// ── Command Enums ───────────────────────────────────────────────────────────── + +// Discriminant for reconnect result types. +type ReconnectResultType string + +const ( + ReconnectResultTypeReplay ReconnectResultType = "replay" + ReconnectResultTypeSnapshot ReconnectResultType = "snapshot" +) + +// Encoding of fetched content data. +type ContentEncoding string + +const ( + ContentEncodingBase64 ContentEncoding = "base64" + ContentEncodingUtf8 ContentEncoding = "utf-8" +) + +// ── Command Types ───────────────────────────────────────────────────────────── + +// InitializeParams Establishes a new connection and negotiates the protocol version. +type InitializeParams struct { + // Protocol version the client speaks + ProtocolVersion int `json:"protocolVersion"` + // Unique client identifier + ClientID string `json:"clientId"` + // URIs to subscribe to during handshake + InitialSubscriptions []string `json:"initialSubscriptions,omitempty"` + // IETF BCP 47 language tag indicating the client's preferred locale + // (e.g. `"en-US"`, `"ja"`). The server SHOULD use this to localise + // user-facing strings such as confirmation option labels. + Locale *string `json:"locale,omitempty"` +} + +// InitializeResult Result of the `initialize` command. +type InitializeResult struct { + // Protocol version the server speaks + ProtocolVersion int `json:"protocolVersion"` + // Current server sequence number + ServerSeq int `json:"serverSeq"` + // Snapshots for each `initialSubscriptions` URI + Snapshots []Snapshot `json:"snapshots"` + // Suggested default directory for remote filesystem browsing + DefaultDirectory *string `json:"defaultDirectory,omitempty"` +} + +// ReconnectParams Re-establishes a dropped connection. The server replays missed actions or +type ReconnectParams struct { + // Client identifier from the original connection + ClientID string `json:"clientId"` + // Last `serverSeq` the client received + LastSeenServerSeq int `json:"lastSeenServerSeq"` + // URIs the client was subscribed to + Subscriptions []string `json:"subscriptions"` +} + +// ReconnectReplayResult Reconnect result when the server can replay from the requested sequence. +type ReconnectReplayResult struct { + // Discriminant + Type ReconnectResultType `json:"type"` + // Missed action envelopes since `lastSeenServerSeq` + Actions []ActionEnvelope `json:"actions"` +} + +// ReconnectSnapshotResult Reconnect result when the gap exceeds the replay buffer. +type ReconnectSnapshotResult struct { + // Discriminant + Type ReconnectResultType `json:"type"` + // Fresh snapshots for each subscription + Snapshots []Snapshot `json:"snapshots"` +} + +// SubscribeParams Subscribe to a URI-identified state resource. +type SubscribeParams struct { + // URI to subscribe to + Resource string `json:"resource"` +} + +// SubscribeResult Result of the `subscribe` command. +type SubscribeResult struct { + // Snapshot of the subscribed resource + Snapshot Snapshot `json:"snapshot"` +} + +// SessionForkSource Creates a new session with the specified agent provider. +type SessionForkSource struct { + // URI of the existing session to fork from + Session string `json:"session"` + // Turn ID in the source session; content up to and including this turn's response is copied + TurnID string `json:"turnId"` +} + +type CreateSessionParams struct { + // Session URI (client-chosen, e.g. `copilot:/`) + Session string `json:"session"` + // Agent provider ID + Provider *string `json:"provider,omitempty"` + // Model selection (ID and optional model-specific configuration) + Model *ModelSelection `json:"model,omitempty"` + // Working directory for the session + WorkingDirectory *string `json:"workingDirectory,omitempty"` + // Fork from an existing session. The new session is populated with content + // from the source session up to and including the specified turn's response. + Fork *SessionForkSource `json:"fork,omitempty"` + // Agent-specific configuration values collected via `resolveSessionConfig`. + // Keys and values correspond to the schema returned by the server. + Config map[string]json.RawMessage `json:"config,omitempty"` + // Eagerly claim the active client role for the new session. + // + // When provided, the server initializes the session with this client as the + // active client, equivalent to dispatching a `session/activeClientChanged` + // action immediately after creation. The `clientId` MUST match the + // `clientId` the creating client supplied in `initialize`. + ActiveClient *SessionActiveClient `json:"activeClient,omitempty"` +} + +// DisposeSessionParams Disposes a session and cleans up server-side resources. +type DisposeSessionParams struct { + // Session URI to dispose + Session string `json:"session"` +} + +// ListSessionsParams Returns a list of session summaries. Used to populate session lists and sidebars. +type ListSessionsParams struct { + // Optional filter criteria + Filter map[string]json.RawMessage `json:"filter,omitempty"` +} + +// ListSessionsResult Result of the `listSessions` command. +type ListSessionsResult struct { + // The list of session summaries. + Items []SessionSummary `json:"items"` +} + +// ResourceReadParams Reads the content of a resource by URI. +type ResourceReadParams struct { + // Content URI from a `ContentRef` + URI string `json:"uri"` + // Preferred encoding for the returned data (default: server-chosen) + Encoding *ContentEncoding `json:"encoding,omitempty"` +} + +// ResourceReadResult Result of the `resourceRead` command. +type ResourceReadResult struct { + // Content encoded as a string + Data string `json:"data"` + // How `data` is encoded + Encoding ContentEncoding `json:"encoding"` + // Content type (e.g. `"image/png"`, `"text/plain"`) + ContentType *string `json:"contentType,omitempty"` +} + +// ResourceWriteParams Writes content to a file on the server's filesystem. +type ResourceWriteParams struct { + // Target file URI on the server filesystem + URI string `json:"uri"` + // Content encoded as a string + Data string `json:"data"` + // How `data` is encoded + Encoding ContentEncoding `json:"encoding"` + // Content type (e.g. `"text/plain"`, `"image/png"`) + ContentType *string `json:"contentType,omitempty"` + // If `true`, the server MUST fail if the file already exists instead of + // overwriting it. Useful for safe creation of new files. + CreateOnly *bool `json:"createOnly,omitempty"` +} + +// ResourceWriteResult Result of the `resourceWrite` command. +type ResourceWriteResult struct { +} + +// ResourceListParams Lists directory entries at a file URI on the server's filesystem. +type ResourceListParams struct { + // Directory URI on the server filesystem + URI string `json:"uri"` +} + +// ResourceListResult Result of the `resourceList` command. +type ResourceListResult struct { + // Entries directly contained in the requested directory + Entries []DirectoryEntry `json:"entries"` +} + +// DirectoryEntry Directory entry returned by `resourceList`. +type DirectoryEntry struct { + // Base name of the entry + Name string `json:"name"` + // Whether the entry is a file or directory + Type string `json:"type"` +} + +// ResourceCopyParams Copies a resource from one URI to another on the server's filesystem. +type ResourceCopyParams struct { + // Source URI to copy from + Source string `json:"source"` + // Destination URI to copy to + Destination string `json:"destination"` + // If `true`, the server MUST fail if the destination already exists instead + // of overwriting it. + FailIfExists *bool `json:"failIfExists,omitempty"` +} + +// ResourceCopyResult Result of the `resourceCopy` command. +type ResourceCopyResult struct { +} + +// ResourceDeleteParams Deletes a resource at a URI on the server's filesystem. +type ResourceDeleteParams struct { + // URI of the resource to delete + URI string `json:"uri"` + // If `true` and the target is a directory, delete it and all its contents + // recursively. If `false` (default), deleting a non-empty directory MUST fail. + Recursive *bool `json:"recursive,omitempty"` +} + +// ResourceDeleteResult Result of the `resourceDelete` command. +type ResourceDeleteResult struct { +} + +// ResourceMoveParams Moves (renames) a resource from one URI to another on the server's filesystem. +type ResourceMoveParams struct { + // Source URI to move from + Source string `json:"source"` + // Destination URI to move to + Destination string `json:"destination"` + // If `true`, the server MUST fail if the destination already exists instead + // of overwriting it. + FailIfExists *bool `json:"failIfExists,omitempty"` +} + +// ResourceMoveResult Result of the `resourceMove` command. +type ResourceMoveResult struct { +} + +// FetchTurnsParams Fetches historical turns for a session. Used for lazy loading of conversation +type FetchTurnsParams struct { + // Session URI + Session string `json:"session"` + // Turn ID to fetch before (exclusive). Omit to fetch from the most recent turn. + Before *string `json:"before,omitempty"` + // Maximum number of turns to return. Server MAY impose its own upper bound. + Limit *int `json:"limit,omitempty"` +} + +// FetchTurnsResult Result of the `fetchTurns` command. +type FetchTurnsResult struct { + // The requested turns, ordered oldest-first + Turns []Turn `json:"turns"` + // Whether more turns exist before the returned range + HasMore bool `json:"hasMore"` +} + +// UnsubscribeParams Stop receiving updates for a URI. +type UnsubscribeParams struct { + // URI to unsubscribe from + Resource string `json:"resource"` +} + +// DispatchActionParams Fire-and-forget action dispatch (write-ahead). The client applies actions +type DispatchActionParams struct { + // Client sequence number + ClientSeq int `json:"clientSeq"` + // The action to dispatch + Action StateAction `json:"action"` +} + +// AuthenticateParams Pushes a Bearer token for a protected resource. The `resource` field MUST +type AuthenticateParams struct { + // The protected resource identifier. MUST match a `resource` value from + // `IProtectedResourceMetadata` declared in `IAgentInfo.protectedResources`. + Resource string `json:"resource"` + // Bearer token obtained from the resource's authorization server + Token string `json:"token"` +} + +// AuthenticateResult Result of the `authenticate` command. +type AuthenticateResult struct { +} + +// CreateTerminalParams Creates a new terminal on the server. +type CreateTerminalParams struct { + // Terminal URI (client-chosen) + Terminal string `json:"terminal"` + // Initial owner of the terminal + Claim TerminalClaim `json:"claim"` + // Human-readable terminal name + Name *string `json:"name,omitempty"` + // Initial working directory URI + Cwd *string `json:"cwd,omitempty"` + // Initial terminal width in columns + Cols *int `json:"cols,omitempty"` + // Initial terminal height in rows + Rows *int `json:"rows,omitempty"` +} + +// DisposeTerminalParams Disposes a terminal and kills its process if still running. +type DisposeTerminalParams struct { + // Terminal URI to dispose + Terminal string `json:"terminal"` +} + +// ResolveSessionConfigParams Iteratively resolves the session configuration schema. The client sends the +type ResolveSessionConfigParams struct { + // Agent provider ID + Provider *string `json:"provider,omitempty"` + // Working directory for the session + WorkingDirectory *string `json:"workingDirectory,omitempty"` + // Current user-filled configuration values + Config map[string]json.RawMessage `json:"config,omitempty"` +} + +// ResolveSessionConfigResult Result of the `resolveSessionConfig` command. +type ResolveSessionConfigResult struct { + // JSON Schema describing available configuration properties given the current context + Schema SessionConfigSchema `json:"schema"` + // Current configuration values (echoed back with server-resolved defaults applied) + Values map[string]json.RawMessage `json:"values"` +} + +// SessionConfigPropertySchema A session configuration property descriptor. +type SessionConfigPropertySchema struct { + // JSON Schema: property type. Only string enum properties are currently supported. + Type string `json:"type"` + // JSON Schema: human-readable label for the property + Title string `json:"title"` + // JSON Schema: description / tooltip + Description *string `json:"description,omitempty"` + // JSON Schema: default value + Default *string `json:"default,omitempty"` + // JSON Schema: allowed values + Enum []string `json:"enum"` + // Display extension: human-readable label per enum value (parallel array) + EnumLabels []string `json:"enumLabels,omitempty"` + // Display extension: description per enum value (parallel array) + EnumDescriptions []string `json:"enumDescriptions,omitempty"` + // JSON Schema: when `true`, the property is displayed but cannot be modified by the user + ReadOnly *bool `json:"readOnly,omitempty"` + // Display extension: when `true`, the full set of allowed values is too large + // to enumerate statically. The client SHOULD use `sessionConfigCompletions` + // to fetch matching values based on user input. Any values in `enum` are + // seed/recent values for initial display. + EnumDynamic *bool `json:"enumDynamic,omitempty"` + // When `true`, the user may change this property after session creation + SessionMutable *bool `json:"sessionMutable,omitempty"` +} + +// SessionConfigSchema A JSON Schema object describing available session configuration metadata. +type SessionConfigSchema struct { + // JSON Schema: always `'object'` + Type string `json:"type"` + // JSON Schema: property descriptors keyed by property id + Properties map[string]SessionConfigPropertySchema `json:"properties"` + // JSON Schema: list of required property ids + Required []string `json:"required,omitempty"` +} + +// SessionConfigCompletionsParams Queries the server for allowed values of a dynamic session config property. +type SessionConfigCompletionsParams struct { + // Agent provider ID + Provider *string `json:"provider,omitempty"` + // Working directory for the session + WorkingDirectory *string `json:"workingDirectory,omitempty"` + // Current user-filled configuration values (provides context for the query) + Config map[string]json.RawMessage `json:"config,omitempty"` + // Property id from the schema to query values for + Property string `json:"property"` + // Search filter text (empty or omitted returns default/recent values) + Query *string `json:"query,omitempty"` +} + +// SessionConfigCompletionsResult Result of the `sessionConfigCompletions` command. +type SessionConfigCompletionsResult struct { + // Matching value items + Items []SessionConfigValueItem `json:"items"` +} + +// SessionConfigValueItem A single value item returned by `sessionConfigCompletions`. +type SessionConfigValueItem struct { + // The value to store in config + Value string `json:"value"` + // Human-readable display label + Label string `json:"label"` + // Optional secondary description + Description *string `json:"description,omitempty"` +} + +// ── ReconnectResult Union ───────────────────────────────────────────────────── + +// ReconnectResult is a discriminated union keyed on "type". +type ReconnectResult struct { + Replay *ReconnectReplayResult + Snapshot *ReconnectSnapshotResult +} + +func (u *ReconnectResult) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"type"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "replay": + u.Replay = new(ReconnectReplayResult) + return json.Unmarshal(data, u.Replay) + case "snapshot": + u.Snapshot = new(ReconnectSnapshotResult) + return json.Unmarshal(data, u.Snapshot) + default: + return fmt.Errorf("unknown ReconnectResult type: %q", disc.D) + } +} + +func (u ReconnectResult) MarshalJSON() ([]byte, error) { + if u.Replay != nil { + return json.Marshal(u.Replay) + } + if u.Snapshot != nil { + return json.Marshal(u.Snapshot) + } + return nil, fmt.Errorf("empty ReconnectResult: no variant set") +} diff --git a/examples/go/ahp/errors_generated.go b/examples/go/ahp/errors_generated.go new file mode 100644 index 00000000..f0afe85e --- /dev/null +++ b/examples/go/ahp/errors_generated.go @@ -0,0 +1,43 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +// ── Standard JSON-RPC Error Codes ───────────────────────────────────────────── + +const ( + // JSONRPCParseError indicates invalid JSON. + JSONRPCParseError = -32700 + // JSONRPCInvalidRequest indicates a malformed JSON-RPC request. + JSONRPCInvalidRequest = -32600 + // JSONRPCMethodNotFound indicates an unknown method name. + JSONRPCMethodNotFound = -32601 + // JSONRPCInvalidParams indicates invalid method parameters. + JSONRPCInvalidParams = -32602 + // JSONRPCInternalError indicates an unspecified server error. + JSONRPCInternalError = -32603 +) + +// ── AHP Application Error Codes ─────────────────────────────────────────────── + +const ( + // AHPSessionNotFound indicates the referenced session URI does not exist. + AHPSessionNotFound = -32001 + // AHPProviderNotFound indicates the requested agent provider is not registered. + AHPProviderNotFound = -32002 + // AHPSessionAlreadyExists indicates a session with the given URI already exists. + AHPSessionAlreadyExists = -32003 + // AHPTurnInProgress indicates the operation requires no active turn, but one is in progress. + AHPTurnInProgress = -32004 + // AHPUnsupportedProtocolVersion indicates the client's protocol version is not supported. + AHPUnsupportedProtocolVersion = -32005 + // AHPContentNotFound indicates the requested content URI does not exist. + AHPContentNotFound = -32006 + // AHPAuthRequired indicates authentication is required for a protected resource. + AHPAuthRequired = -32007 + // AHPNotFound indicates the requested file, folder, or URI does not exist. + AHPNotFound = -32008 + // AHPPermissionDenied indicates the client is not permitted to access the requested resource. + AHPPermissionDenied = -32009 + // AHPAlreadyExists indicates the target resource already exists and overwriting is not allowed. + AHPAlreadyExists = -32010 +) diff --git a/examples/go/ahp/go.mod b/examples/go/ahp/go.mod new file mode 100644 index 00000000..62d6629d --- /dev/null +++ b/examples/go/ahp/go.mod @@ -0,0 +1,3 @@ +module github.com/microsoft/agent-host-protocol/examples/go/ahp + +go 1.21 diff --git a/examples/go/ahp/messages_generated.go b/examples/go/ahp/messages_generated.go new file mode 100644 index 00000000..ed1003a2 --- /dev/null +++ b/examples/go/ahp/messages_generated.go @@ -0,0 +1,156 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +import "encoding/json" + +// ── JSON-RPC Base Types ────────────────────────────────────────────────────── + +// JSONRPCRequest is a JSON-RPC 2.0 request with typed params. +type JSONRPCRequest[T any] struct { + JSONRPC string `json:"jsonrpc"` + ID int `json:"id"` + Method string `json:"method"` + Params T `json:"params"` +} + +// NewJSONRPCRequest creates a new JSON-RPC 2.0 request. +func NewJSONRPCRequest[T any](id int, method string, params T) JSONRPCRequest[T] { + return JSONRPCRequest[T]{ + JSONRPC: "2.0", + ID: id, + Method: method, + Params: params, + } +} + +// JSONRPCError is a JSON-RPC 2.0 error object. +type JSONRPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data *json.RawMessage `json:"data,omitempty"` +} + +// JSONRPCSuccessResponse is a JSON-RPC 2.0 success response with typed result. +type JSONRPCSuccessResponse[T any] struct { + JSONRPC string `json:"jsonrpc"` + ID int `json:"id"` + Result T `json:"result"` +} + +// JSONRPCErrorResponse is a JSON-RPC 2.0 error response. +type JSONRPCErrorResponse struct { + JSONRPC string `json:"jsonrpc"` + ID int `json:"id"` + Error JSONRPCError `json:"error"` +} + +// JSONRPCNotification is a JSON-RPC 2.0 notification (no id) with typed params. +type JSONRPCNotification[T any] struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params T `json:"params"` +} + +// NewJSONRPCNotification creates a new JSON-RPC 2.0 notification. +func NewJSONRPCNotification[T any](method string, params T) JSONRPCNotification[T] { + return JSONRPCNotification[T]{ + JSONRPC: "2.0", + Method: method, + Params: params, + } +} + +// ── Server → Client Notification Params ────────────────────────────────────── + +// ActionNotificationParams is the params for the server → client action notification. +type ActionNotificationParams = ActionEnvelope + +// NotificationMethodParams is the params for the server → client notification method. +type NotificationMethodParams struct { + Notification ProtocolNotification `json:"notification"` +} + +// ── AHP Command Helpers ────────────────────────────────────────────────────── + +// NewInitializeRequest creates an initialize JSON-RPC request. +func NewInitializeRequest(id int, params InitializeParams) JSONRPCRequest[InitializeParams] { + return NewJSONRPCRequest(id, "initialize", params) +} + +// NewReconnectRequest creates a reconnect JSON-RPC request. +func NewReconnectRequest(id int, params ReconnectParams) JSONRPCRequest[ReconnectParams] { + return NewJSONRPCRequest(id, "reconnect", params) +} + +// NewSubscribeRequest creates a subscribe JSON-RPC request. +func NewSubscribeRequest(id int, params SubscribeParams) JSONRPCRequest[SubscribeParams] { + return NewJSONRPCRequest(id, "subscribe", params) +} + +// NewCreateSessionRequest creates a createSession JSON-RPC request. +func NewCreateSessionRequest(id int, params CreateSessionParams) JSONRPCRequest[CreateSessionParams] { + return NewJSONRPCRequest(id, "createSession", params) +} + +// NewDisposeSessionRequest creates a disposeSession JSON-RPC request. +func NewDisposeSessionRequest(id int, params DisposeSessionParams) JSONRPCRequest[DisposeSessionParams] { + return NewJSONRPCRequest(id, "disposeSession", params) +} + +// NewListSessionsRequest creates a listSessions JSON-RPC request. +func NewListSessionsRequest(id int, params ListSessionsParams) JSONRPCRequest[ListSessionsParams] { + return NewJSONRPCRequest(id, "listSessions", params) +} + +// NewResourceReadRequest creates a resourceRead JSON-RPC request. +func NewResourceReadRequest(id int, params ResourceReadParams) JSONRPCRequest[ResourceReadParams] { + return NewJSONRPCRequest(id, "resourceRead", params) +} + +// NewResourceWriteRequest creates a resourceWrite JSON-RPC request. +func NewResourceWriteRequest(id int, params ResourceWriteParams) JSONRPCRequest[ResourceWriteParams] { + return NewJSONRPCRequest(id, "resourceWrite", params) +} + +// NewResourceListRequest creates a resourceList JSON-RPC request. +func NewResourceListRequest(id int, params ResourceListParams) JSONRPCRequest[ResourceListParams] { + return NewJSONRPCRequest(id, "resourceList", params) +} + +// NewResourceCopyRequest creates a resourceCopy JSON-RPC request. +func NewResourceCopyRequest(id int, params ResourceCopyParams) JSONRPCRequest[ResourceCopyParams] { + return NewJSONRPCRequest(id, "resourceCopy", params) +} + +// NewResourceDeleteRequest creates a resourceDelete JSON-RPC request. +func NewResourceDeleteRequest(id int, params ResourceDeleteParams) JSONRPCRequest[ResourceDeleteParams] { + return NewJSONRPCRequest(id, "resourceDelete", params) +} + +// NewResourceMoveRequest creates a resourceMove JSON-RPC request. +func NewResourceMoveRequest(id int, params ResourceMoveParams) JSONRPCRequest[ResourceMoveParams] { + return NewJSONRPCRequest(id, "resourceMove", params) +} + +// NewFetchTurnsRequest creates a fetchTurns JSON-RPC request. +func NewFetchTurnsRequest(id int, params FetchTurnsParams) JSONRPCRequest[FetchTurnsParams] { + return NewJSONRPCRequest(id, "fetchTurns", params) +} + +// NewAuthenticateRequest creates an authenticate JSON-RPC request. +func NewAuthenticateRequest(id int, params AuthenticateParams) JSONRPCRequest[AuthenticateParams] { + return NewJSONRPCRequest(id, "authenticate", params) +} + +// ── AHP Client Notification Helpers ────────────────────────────────────────── + +// NewUnsubscribeNotification creates an unsubscribe JSON-RPC notification. +func NewUnsubscribeNotification(params UnsubscribeParams) JSONRPCNotification[UnsubscribeParams] { + return NewJSONRPCNotification("unsubscribe", params) +} + +// NewDispatchActionNotification creates a dispatchAction JSON-RPC notification. +func NewDispatchActionNotification(params DispatchActionParams) JSONRPCNotification[DispatchActionParams] { + return NewJSONRPCNotification("dispatchAction", params) +} diff --git a/examples/go/ahp/notifications_generated.go b/examples/go/ahp/notifications_generated.go new file mode 100644 index 00000000..f66e064e --- /dev/null +++ b/examples/go/ahp/notifications_generated.go @@ -0,0 +1,147 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +import ( + "encoding/json" + "fmt" +) + +// ── Notification Enums ──────────────────────────────────────────────────────── + +// Reason why authentication is required. +type AuthRequiredReason string + +const ( + // The client has not yet authenticated for the resource + AuthRequiredReasonRequired AuthRequiredReason = "required" + // A previously valid token has expired or been revoked + AuthRequiredReasonExpired AuthRequiredReason = "expired" +) + +// Discriminant values for all protocol notifications. +type NotificationType string + +const ( + NotificationTypeSessionAdded NotificationType = "notify/sessionAdded" + NotificationTypeSessionRemoved NotificationType = "notify/sessionRemoved" + NotificationTypeSessionSummaryChanged NotificationType = "notify/sessionSummaryChanged" + NotificationTypeAuthRequired NotificationType = "notify/authRequired" +) + +// ── Notification Types ──────────────────────────────────────────────────────── + +// SessionAddedNotification Broadcast to all connected clients when a new session is created. +type SessionAddedNotification struct { + Type NotificationType `json:"type"` + // Summary of the new session + Summary SessionSummary `json:"summary"` +} + +// SessionRemovedNotification Broadcast to all connected clients when a session is disposed. +type SessionRemovedNotification struct { + Type NotificationType `json:"type"` + // URI of the removed session + Session string `json:"session"` +} + +// SessionSummaryChangedNotification Broadcast to all connected clients when an existing session's summary +type SessionSummaryChangedNotification struct { + Type NotificationType `json:"type"` + // URI of the session whose summary changed + Session string `json:"session"` + // Mutable summary fields that changed; omitted fields are unchanged. + // + // Identity fields (`resource`, `provider`, `createdAt`) never change and + // MUST be omitted by senders; receivers SHOULD ignore them if present. + Changes PartialSessionSummary `json:"changes"` +} + +// AuthRequiredNotification Sent by the server when a protected resource requires (re-)authentication. +type AuthRequiredNotification struct { + Type NotificationType `json:"type"` + // The protected resource identifier that requires authentication + Resource string `json:"resource"` + // Why authentication is required + Reason *AuthRequiredReason `json:"reason,omitempty"` +} + +// ── Partial Summary Types ───────────────────────────────────────────────────── + +type PartialSessionSummary struct { + // Session URI + Resource *string `json:"resource,omitempty"` + // Agent provider ID + Provider *string `json:"provider,omitempty"` + // Session title + Title *string `json:"title,omitempty"` + // Current session status + Status *SessionStatus `json:"status,omitempty"` + // Creation timestamp + CreatedAt *int `json:"createdAt,omitempty"` + // Last modification timestamp + ModifiedAt *int `json:"modifiedAt,omitempty"` + // Server-owned project for this session + Project *ProjectInfo `json:"project,omitempty"` + // Currently selected model + Model *ModelSelection `json:"model,omitempty"` + // The working directory URI for this session + WorkingDirectory *string `json:"workingDirectory,omitempty"` + // Whether the client has viewed this session since its last modification + IsRead *bool `json:"isRead,omitempty"` + // Whether the session has been marked as done by the client + IsDone *bool `json:"isDone,omitempty"` + // Files changed during this session with diff statistics + Diffs *[]FileEdit `json:"diffs,omitempty"` +} + +// ── ProtocolNotification Union ──────────────────────────────────────────────── + +// ProtocolNotification is a discriminated union keyed on "type". +type ProtocolNotification struct { + SessionAdded *SessionAddedNotification + SessionRemoved *SessionRemovedNotification + SessionSummaryChanged *SessionSummaryChangedNotification + AuthRequired *AuthRequiredNotification +} + +func (u *ProtocolNotification) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"type"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "notify/sessionAdded": + u.SessionAdded = new(SessionAddedNotification) + return json.Unmarshal(data, u.SessionAdded) + case "notify/sessionRemoved": + u.SessionRemoved = new(SessionRemovedNotification) + return json.Unmarshal(data, u.SessionRemoved) + case "notify/sessionSummaryChanged": + u.SessionSummaryChanged = new(SessionSummaryChangedNotification) + return json.Unmarshal(data, u.SessionSummaryChanged) + case "notify/authRequired": + u.AuthRequired = new(AuthRequiredNotification) + return json.Unmarshal(data, u.AuthRequired) + default: + return fmt.Errorf("unknown ProtocolNotification type: %q", disc.D) + } +} + +func (u ProtocolNotification) MarshalJSON() ([]byte, error) { + if u.SessionAdded != nil { + return json.Marshal(u.SessionAdded) + } + if u.SessionRemoved != nil { + return json.Marshal(u.SessionRemoved) + } + if u.SessionSummaryChanged != nil { + return json.Marshal(u.SessionSummaryChanged) + } + if u.AuthRequired != nil { + return json.Marshal(u.AuthRequired) + } + return nil, fmt.Errorf("empty ProtocolNotification: no variant set") +} diff --git a/examples/go/ahp/state_generated.go b/examples/go/ahp/state_generated.go new file mode 100644 index 00000000..59c2509c --- /dev/null +++ b/examples/go/ahp/state_generated.go @@ -0,0 +1,1741 @@ +// Code generated from types/*.ts — DO NOT EDIT. + +package ahp + +import ( + "encoding/json" + "fmt" +) + +// ── Type Aliases ────────────────────────────────────────────────────────────── + +// URI is a string alias for URI values (e.g. "agenthost:/root"). +type URI = string + +// ── StringOrMarkdown ────────────────────────────────────────────────────────── + +// StringOrMarkdown represents a value that is either a plain string or +// a markdown-formatted string. +type StringOrMarkdown struct { + Text *string // non-nil when the value is a plain string + Markdown *string // non-nil when the value is markdown +} + +func (s *StringOrMarkdown) UnmarshalJSON(data []byte) error { + // Try plain string first + var str string + if err := json.Unmarshal(data, &str); err == nil { + s.Text = &str + return nil + } + // Try markdown object + var obj struct { + Markdown string `json:"markdown"` + } + if err := json.Unmarshal(data, &obj); err == nil { + s.Markdown = &obj.Markdown + return nil + } + return fmt.Errorf("StringOrMarkdown: cannot decode %s", string(data)) +} + +func (s StringOrMarkdown) MarshalJSON() ([]byte, error) { + if s.Markdown != nil { + return json.Marshal(struct { + Markdown string `json:"markdown"` + }{Markdown: *s.Markdown}) + } + if s.Text != nil { + return json.Marshal(*s.Text) + } + return json.Marshal(nil) +} + +// ── Enums ──────────────────────────────────────────────────────────────────── + +// Policy configuration state for a model. +type PolicyState string + +const ( + PolicyStateEnabled PolicyState = "enabled" + PolicyStateDisabled PolicyState = "disabled" + PolicyStateUnconfigured PolicyState = "unconfigured" +) + +// Discriminant for pending message kinds. +type PendingMessageKind string + +const ( + // Injected into the current turn at a convenient point + PendingMessageKindSteering PendingMessageKind = "steering" + // Sent automatically as a new turn after the current turn finishes + PendingMessageKindQueued PendingMessageKind = "queued" +) + +// Session initialization state. +type SessionLifecycle string + +const ( + SessionLifecycleCreating SessionLifecycle = "creating" + SessionLifecycleReady SessionLifecycle = "ready" + SessionLifecycleCreationFailed SessionLifecycle = "creationFailed" +) + +// Bitset of summary-level session status flags. +// +// Use bitwise checks instead of equality for non-terminal activity. For example, +// `status & SessionStatus.InProgress` matches both ordinary in-progress turns +// and turns that are paused waiting for input. +type SessionStatus int + +const ( + SessionStatusIdle SessionStatus = 1 + SessionStatusError SessionStatus = 2 + SessionStatusInProgress SessionStatus = 8 + SessionStatusInputNeeded SessionStatus = 24 +) + +// Answer lifecycle state. +type SessionInputAnswerState string + +const ( + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" +) + +// Answer value kind. +type SessionInputAnswerValueKind string + +const ( + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" +) + +// Question/input control kind. +type SessionInputQuestionKind string + +const ( + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" +) + +// How a client completed an input request. +type SessionInputResponseKind string + +const ( + SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindDecline SessionInputResponseKind = "decline" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" +) + +// How a turn ended. +type TurnState string + +const ( + TurnStateComplete TurnState = "complete" + TurnStateCancelled TurnState = "cancelled" + TurnStateError TurnState = "error" +) + +// Type of a message attachment. +type AttachmentType string + +const ( + AttachmentTypeFile AttachmentType = "file" + AttachmentTypeDirectory AttachmentType = "directory" + AttachmentTypeSelection AttachmentType = "selection" +) + +// Discriminant for response part types. +type ResponsePartKind string + +const ( + ResponsePartKindMarkdown ResponsePartKind = "markdown" + ResponsePartKindContentRef ResponsePartKind = "contentRef" + ResponsePartKindToolCall ResponsePartKind = "toolCall" + ResponsePartKindReasoning ResponsePartKind = "reasoning" +) + +// Status of a tool call in the lifecycle state machine. +type ToolCallStatus string + +const ( + ToolCallStatusStreaming ToolCallStatus = "streaming" + ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" + ToolCallStatusRunning ToolCallStatus = "running" + ToolCallStatusPendingResultConfirmation ToolCallStatus = "pending-result-confirmation" + ToolCallStatusCompleted ToolCallStatus = "completed" + ToolCallStatusCancelled ToolCallStatus = "cancelled" +) + +// How a tool call was confirmed for execution. +// +// - `NotNeeded` — No confirmation required (auto-approved) +// - `UserAction` — User explicitly approved +// - `Setting` — Approved by a persistent user setting +type ToolCallConfirmationReason string + +const ( + ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" + ToolCallConfirmationReasonUserAction ToolCallConfirmationReason = "user-action" + ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" +) + +// Why a tool call was cancelled. +type ToolCallCancellationReason string + +const ( + ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" + ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" + ToolCallCancellationReasonResultDenied ToolCallCancellationReason = "result-denied" +) + +// Whether a confirmation option represents an approval or denial action. +type ConfirmationOptionKind string + +const ( + ConfirmationOptionKindApprove ConfirmationOptionKind = "approve" + ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" +) + +// Discriminant for tool result content types. +type ToolResultContentType string + +const ( + ToolResultContentTypeText ToolResultContentType = "text" + ToolResultContentTypeEmbeddedResource ToolResultContentType = "embeddedResource" + ToolResultContentTypeResource ToolResultContentType = "resource" + ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" + ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeSubagent ToolResultContentType = "subagent" +) + +// Loading status for a server-managed customization. +type CustomizationStatus string + +const ( + // Plugin is being loaded + CustomizationStatusLoading CustomizationStatus = "loading" + // Plugin is fully operational + CustomizationStatusLoaded CustomizationStatus = "loaded" + // Plugin partially loaded but has warnings + CustomizationStatusDegraded CustomizationStatus = "degraded" + // Plugin was unable to load + CustomizationStatusError CustomizationStatus = "error" +) + +// Discriminant for terminal claim kinds. +type TerminalClaimKind string + +const ( + TerminalClaimKindClient TerminalClaimKind = "client" + TerminalClaimKindSession TerminalClaimKind = "session" +) + +// ── State Types ────────────────────────────────────────────────────────────── + +// Icon An optionally-sized icon that can be displayed in a user interface. +type Icon struct { + // A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + // `data:` URI with Base64-encoded image data. + // + // Consumers SHOULD take steps to ensure URLs serving icons are from the + // same domain as the client/server or a trusted domain. + // + // Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + // executable JavaScript. + Src string `json:"src"` + // Optional MIME type override if the source MIME type is missing or generic. + // For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + ContentType *string `json:"contentType,omitempty"` + // Optional array of strings that specify sizes at which the icon can be used. + // Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + // + // If not provided, the client should assume that the icon can be used at any size. + Sizes []string `json:"sizes,omitempty"` + // Optional specifier for the theme this icon is designed for. `"light"` indicates + // the icon is designed to be used with a light background, and `"dark"` indicates + // the icon is designed to be used with a dark background. + // + // If not provided, the client should assume the icon can be used with any theme. + Theme *string `json:"theme,omitempty"` +} + +// ProtectedResourceMetadata Describes a protected resource's authentication requirements using +type ProtectedResourceMetadata struct { + // REQUIRED. The protected resource's resource identifier, a URL using the + // `https` scheme with no fragment component (e.g. `"https://api.github.com"`). + Resource string `json:"resource"` + // OPTIONAL. Human-readable name of the protected resource. + ResourceName *string `json:"resource_name,omitempty"` + // OPTIONAL. JSON array of OAuth authorization server identifier URLs. + AuthorizationServers []string `json:"authorization_servers,omitempty"` + // OPTIONAL. URL of the protected resource's JWK Set document. + JwksURI *string `json:"jwks_uri,omitempty"` + // RECOMMENDED. JSON array of OAuth 2.0 scope values used in authorization requests. + ScopesSupported []string `json:"scopes_supported,omitempty"` + // OPTIONAL. JSON array of Bearer Token presentation methods supported. + BearerMethodsSupported []string `json:"bearer_methods_supported,omitempty"` + // OPTIONAL. JSON array of JWS signing algorithms supported. + ResourceSigningAlgValuesSupported []string `json:"resource_signing_alg_values_supported,omitempty"` + // OPTIONAL. JSON array of JWE encryption algorithms (alg) supported. + ResourceEncryptionAlgValuesSupported []string `json:"resource_encryption_alg_values_supported,omitempty"` + // OPTIONAL. JSON array of JWE encryption algorithms (enc) supported. + ResourceEncryptionEncValuesSupported []string `json:"resource_encryption_enc_values_supported,omitempty"` + // OPTIONAL. URL of human-readable documentation for the resource. + ResourceDocumentation *string `json:"resource_documentation,omitempty"` + // OPTIONAL. URL of the resource's data-usage policy. + ResourcePolicyURI *string `json:"resource_policy_uri,omitempty"` + // OPTIONAL. URL of the resource's terms of service. + ResourceTosURI *string `json:"resource_tos_uri,omitempty"` + // AHP extension. Whether authentication is required for this resource. + // + // - `true` (default) — the agent cannot be used without a valid token. + // The server SHOULD return `AuthRequired` (`-32007`) if the client + // attempts to use the agent without authenticating. + // - `false` — the agent works without authentication but MAY offer + // enhanced capabilities when a token is provided. + // + // Clients SHOULD treat an absent field the same as `true`. + Required *bool `json:"required,omitempty"` +} + +// RootState Global state shared with every client subscribed to `agenthost:/root`. +type RootState struct { + // Available agent backends and their models + Agents []AgentInfo `json:"agents"` + // Number of active (non-disposed) sessions on the server + ActiveSessions *int `json:"activeSessions,omitempty"` + // Known terminals on the server. Subscribe to individual terminal URIs for full state. + Terminals []TerminalInfo `json:"terminals,omitempty"` + // Agent host configuration schema and current values + Config *RootConfigState `json:"config,omitempty"` +} + +// RootConfigState Live agent-host configuration metadata. +type RootConfigState struct { + // JSON Schema describing available configuration properties + Schema ConfigSchema `json:"schema"` + // Current configuration values + Values map[string]json.RawMessage `json:"values"` +} + +type AgentInfo struct { + // Agent provider ID (e.g. `'copilot'`) + Provider string `json:"provider"` + // Human-readable name + DisplayName string `json:"displayName"` + // Description string + Description string `json:"description"` + // Available models for this agent + Models []SessionModelInfo `json:"models"` + // Protected resources this agent requires authentication for. + // + // Each entry describes an OAuth 2.0 protected resource using + // [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) semantics. + // Clients should obtain tokens from the declared `authorization_servers` + // and push them via the `authenticate` command before creating sessions + // with this agent. + ProtectedResources []ProtectedResourceMetadata `json:"protectedResources,omitempty"` + // Customizations (Open Plugins) associated with this agent. + // + // Each entry is a reference to an [Open Plugins](https://open-plugins.com/) + // plugin that the agent host can activate for sessions using this agent. + Customizations []CustomizationRef `json:"customizations,omitempty"` +} + +type SessionModelInfo struct { + // Model identifier + ID string `json:"id"` + // Provider this model belongs to + Provider string `json:"provider"` + // Human-readable model name + Name string `json:"name"` + // Maximum context window size + MaxContextWindow *int `json:"maxContextWindow,omitempty"` + // Whether the model supports vision + SupportsVision *bool `json:"supportsVision,omitempty"` + // Policy configuration state + PolicyState *PolicyState `json:"policyState,omitempty"` + // Configuration schema describing model-specific options (e.g. thinking + // level). Clients present this as a form and pass the resolved values in + // {@link IModelSelection.config} when creating or changing sessions. + ConfigSchema *ConfigSchema `json:"configSchema,omitempty"` +} + +// ModelSelection A model selection: the chosen model ID together with any model-specific +type ModelSelection struct { + // Model identifier + ID string `json:"id"` + // Model-specific configuration values + Config map[string]string `json:"config,omitempty"` +} + +// ConfigPropertySchema A JSON Schema-compatible string enum property descriptor with display extensions. +type ConfigPropertySchema struct { + // JSON Schema: property type. Only string enum properties are currently supported. + Type string `json:"type"` + // JSON Schema: human-readable label for the property + Title string `json:"title"` + // JSON Schema: description / tooltip + Description *string `json:"description,omitempty"` + // JSON Schema: default value + Default *string `json:"default,omitempty"` + // JSON Schema: allowed values + Enum []string `json:"enum"` + // Display extension: human-readable label per enum value (parallel array) + EnumLabels []string `json:"enumLabels,omitempty"` + // Display extension: description per enum value (parallel array) + EnumDescriptions []string `json:"enumDescriptions,omitempty"` + // JSON Schema: when `true`, the property is displayed but cannot be modified by the user + ReadOnly *bool `json:"readOnly,omitempty"` +} + +// ConfigSchema A JSON Schema object describing available configuration properties. +type ConfigSchema struct { + // JSON Schema: always `'object'` + Type string `json:"type"` + // JSON Schema: property descriptors keyed by property id + Properties map[string]ConfigPropertySchema `json:"properties"` + // JSON Schema: list of required property ids + Required []string `json:"required,omitempty"` +} + +// PendingMessage A message queued for future delivery to the agent. +type PendingMessage struct { + // Unique identifier for this pending message + ID string `json:"id"` + // The message content + UserMessage UserMessage `json:"userMessage"` +} + +// SessionState Full state for a single session, loaded when a client subscribes to the session's URI. +type SessionState struct { + // Lightweight session metadata + Summary SessionSummary `json:"summary"` + // Session initialization state + Lifecycle SessionLifecycle `json:"lifecycle"` + // Error details if creation failed + CreationError *ErrorInfo `json:"creationError,omitempty"` + // Tools provided by the server (agent host) for this session + ServerTools []ToolDefinition `json:"serverTools,omitempty"` + // The client currently providing tools and interactive capabilities to this session + ActiveClient *SessionActiveClient `json:"activeClient,omitempty"` + // Completed turns + Turns []Turn `json:"turns"` + // Currently in-progress turn + ActiveTurn *ActiveTurn `json:"activeTurn,omitempty"` + // Message to inject into the current turn at a convenient point + SteeringMessage *PendingMessage `json:"steeringMessage,omitempty"` + // Messages to send automatically as new turns after the current turn finishes + QueuedMessages []PendingMessage `json:"queuedMessages,omitempty"` + // Requests for user input that are currently blocking or informing session progress + InputRequests []SessionInputRequest `json:"inputRequests,omitempty"` + // Session configuration schema and current values + Config *SessionConfigState `json:"config,omitempty"` + // Server-provided customizations active in this session. + // + // Client-provided customizations are available on + // {@link ISessionActiveClient.customizations | activeClient.customizations}. + Customizations []SessionCustomization `json:"customizations,omitempty"` +} + +// SessionActiveClient The client currently providing tools and interactive capabilities to a session. +type SessionActiveClient struct { + // Client identifier (matches `clientId` from `initialize`) + ClientID string `json:"clientId"` + // Human-readable client name (e.g. `"VS Code"`) + DisplayName *string `json:"displayName,omitempty"` + // Tools this client provides to the session + Tools []ToolDefinition `json:"tools"` + // Customizations this client contributes to the session + Customizations []CustomizationRef `json:"customizations,omitempty"` +} + +type SessionSummary struct { + // Session URI + Resource string `json:"resource"` + // Agent provider ID + Provider string `json:"provider"` + // Session title + Title string `json:"title"` + // Current session status + Status SessionStatus `json:"status"` + // Creation timestamp + CreatedAt int `json:"createdAt"` + // Last modification timestamp + ModifiedAt int `json:"modifiedAt"` + // Server-owned project for this session + Project *ProjectInfo `json:"project,omitempty"` + // Currently selected model + Model *ModelSelection `json:"model,omitempty"` + // The working directory URI for this session + WorkingDirectory *string `json:"workingDirectory,omitempty"` + // Whether the client has viewed this session since its last modification + IsRead *bool `json:"isRead,omitempty"` + // Whether the session has been marked as done by the client + IsDone *bool `json:"isDone,omitempty"` + // Files changed during this session with diff statistics + Diffs []FileEdit `json:"diffs,omitempty"` +} + +// ProjectInfo Server-owned project metadata for a session. +type ProjectInfo struct { + // Project URI + URI string `json:"uri"` + // Human-readable project name + DisplayName string `json:"displayName"` +} + +// SessionConfigState Live session configuration metadata. +type SessionConfigState struct { + // JSON Schema describing available configuration properties + Schema SessionConfigSchema `json:"schema"` + // Current configuration values + Values map[string]json.RawMessage `json:"values"` +} + +// Turn A completed request/response cycle. +type Turn struct { + // Turn identifier + ID string `json:"id"` + // The user's input + UserMessage UserMessage `json:"userMessage"` + // All response content in stream order: text, tool calls, reasoning, and content refs. + // + // Consumers should derive display text by concatenating markdown parts, + // and find tool calls by filtering for `ToolCall` parts. + ResponseParts []ResponsePart `json:"responseParts"` + // Token usage info + Usage *UsageInfo `json:"usage,omitempty"` + // How the turn ended + State TurnState `json:"state"` + // Error details if state is `'error'` + Error *ErrorInfo `json:"error,omitempty"` +} + +// ActiveTurn An in-progress turn — the assistant is actively streaming. +type ActiveTurn struct { + // Turn identifier + ID string `json:"id"` + // The user's input + UserMessage UserMessage `json:"userMessage"` + // All response content in stream order: text, tool calls, reasoning, and content refs. + // + // Tool call parts include `pendingPermissions` when permissions are awaiting user approval. + ResponseParts []ResponsePart `json:"responseParts"` + // Token usage info + Usage *UsageInfo `json:"usage,omitempty"` +} + +type UserMessage struct { + // Message text + Text string `json:"text"` + // File/selection attachments + Attachments []MessageAttachment `json:"attachments,omitempty"` +} + +// SessionInputOption A choice in a select-style question. +type SessionInputOption struct { + // Stable option identifier; for MCP enum values this is the enum string + ID string `json:"id"` + // Display label + Label string `json:"label"` + // Optional secondary text + Description *string `json:"description,omitempty"` + // Whether this option is the recommended/default choice + Recommended *bool `json:"recommended,omitempty"` +} + +// SessionInputTextAnswerValue Value captured for one answer. +type SessionInputTextAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` +} + +type SessionInputNumberAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` +} + +type SessionInputBooleanAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` +} + +type SessionInputSelectedAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` + // Free-form text entered instead of selecting an option + FreeformValues []string `json:"freeformValues,omitempty"` +} + +type SessionInputSelectedManyAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` + // Free-form text entered in addition to selected options + FreeformValues []string `json:"freeformValues,omitempty"` +} + +type SessionInputAnswered struct { + // Answer state + State SessionInputAnswerState `json:"state"` + // Answer value + Value SessionInputAnswerValue `json:"value"` +} + +type SessionInputSkipped struct { + // Answer state + State SessionInputAnswerState `json:"state"` + // Free-form reason or value captured while skipping, if any + FreeformValues []string `json:"freeformValues,omitempty"` +} + +// SessionInputTextQuestion Text question within a session input request. +type SessionInputTextQuestion struct { + // Stable question identifier used as the key in `answers` + ID string `json:"id"` + // Short display title + Title *string `json:"title,omitempty"` + // Prompt shown to the user + Message string `json:"message"` + // Whether the user must answer this question to accept the request + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` + // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` + Format *string `json:"format,omitempty"` + // Minimum string length + Min *int `json:"min,omitempty"` + // Maximum string length + Max *int `json:"max,omitempty"` + // Default text + DefaultValue *string `json:"defaultValue,omitempty"` +} + +// SessionInputNumberQuestion Numeric question within a session input request. +type SessionInputNumberQuestion struct { + // Stable question identifier used as the key in `answers` + ID string `json:"id"` + // Short display title + Title *string `json:"title,omitempty"` + // Prompt shown to the user + Message string `json:"message"` + // Whether the user must answer this question to accept the request + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` + // Minimum value + Min *float64 `json:"min,omitempty"` + // Maximum value + Max *float64 `json:"max,omitempty"` + // Default numeric value + DefaultValue *float64 `json:"defaultValue,omitempty"` +} + +// SessionInputBooleanQuestion Boolean question within a session input request. +type SessionInputBooleanQuestion struct { + // Stable question identifier used as the key in `answers` + ID string `json:"id"` + // Short display title + Title *string `json:"title,omitempty"` + // Prompt shown to the user + Message string `json:"message"` + // Whether the user must answer this question to accept the request + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` + // Default boolean value + DefaultValue *bool `json:"defaultValue,omitempty"` +} + +// SessionInputSingleSelectQuestion Single-select question within a session input request. +type SessionInputSingleSelectQuestion struct { + // Stable question identifier used as the key in `answers` + ID string `json:"id"` + // Short display title + Title *string `json:"title,omitempty"` + // Prompt shown to the user + Message string `json:"message"` + // Whether the user must answer this question to accept the request + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` + // Options the user may select from + Options []SessionInputOption `json:"options"` + // Whether the user may enter text instead of selecting an option + AllowFreeformInput *bool `json:"allowFreeformInput,omitempty"` +} + +// SessionInputMultiSelectQuestion Multi-select question within a session input request. +type SessionInputMultiSelectQuestion struct { + // Stable question identifier used as the key in `answers` + ID string `json:"id"` + // Short display title + Title *string `json:"title,omitempty"` + // Prompt shown to the user + Message string `json:"message"` + // Whether the user must answer this question to accept the request + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` + // Options the user may select from + Options []SessionInputOption `json:"options"` + // Whether the user may enter text in addition to selecting options + AllowFreeformInput *bool `json:"allowFreeformInput,omitempty"` + // Minimum selected item count + Min *int `json:"min,omitempty"` + // Maximum selected item count + Max *int `json:"max,omitempty"` +} + +// SessionInputRequest A live request for user input. +type SessionInputRequest struct { + // Stable request identifier + ID string `json:"id"` + // Display message for the request as a whole + Message string `json:"message"` + // URL the user should review or open, for URL-style elicitations + URL *string `json:"url,omitempty"` + // Ordered questions to ask the user + Questions []SessionInputQuestion `json:"questions,omitempty"` + // Current draft or submitted answers, keyed by question ID + Answers map[string]SessionInputAnswer `json:"answers,omitempty"` +} + +type MessageAttachment struct { + // Attachment type + Type AttachmentType `json:"type"` + // File/directory path + Path string `json:"path"` + // Display name + DisplayName *string `json:"displayName,omitempty"` +} + +type MarkdownResponsePart struct { + // Discriminant + Kind ResponsePartKind `json:"kind"` + // Part identifier, used by `session/delta` to target this part for content appends + ID string `json:"id"` + // Markdown content + Content string `json:"content"` +} + +// ContentRef A reference to large content stored outside the state tree. +type ContentRef struct { + // Content URI + URI string `json:"uri"` + // Approximate size in bytes + SizeHint *int `json:"sizeHint,omitempty"` + // Content MIME type + ContentType *string `json:"contentType,omitempty"` +} + +// ResourceReponsePart A content part that's a reference to large content stored outside the state tree. +type ResourceReponsePart struct { + // Content URI + URI string `json:"uri"` + // Approximate size in bytes + SizeHint *int `json:"sizeHint,omitempty"` + // Content MIME type + ContentType *string `json:"contentType,omitempty"` + // Discriminant + Kind ResponsePartKind `json:"kind"` +} + +// ToolCallResponsePart A tool call represented as a response part. +type ToolCallResponsePart struct { + // Discriminant + Kind ResponsePartKind `json:"kind"` + // Full tool call lifecycle state + ToolCall ToolCallState `json:"toolCall"` +} + +// ReasoningResponsePart Reasoning/thinking content from the model. +type ReasoningResponsePart struct { + // Discriminant + Kind ResponsePartKind `json:"kind"` + // Part identifier, used by `session/reasoning` to target this part for content appends + ID string `json:"id"` + // Accumulated reasoning text + Content string `json:"content"` +} + +// ToolCallResult Tool execution result details, available after execution completes. +type ToolCallResult struct { + // Whether the tool succeeded + Success bool `json:"success"` + // Past-tense description of what the tool did + PastTenseMessage StringOrMarkdown `json:"pastTenseMessage"` + // Unstructured result content blocks. + // + // This mirrors the `content` field of MCP `CallToolResult`. + Content []ToolResultContent `json:"content,omitempty"` + // Optional structured result object. + // + // This mirrors the `structuredContent` field of MCP `CallToolResult`. + StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` + // Error details if the tool failed + Error json.RawMessage `json:"error,omitempty"` +} + +// ToolCallStreamingState LM is streaming the tool call parameters. +type ToolCallStreamingState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Status ToolCallStatus `json:"status"` + // Partial parameters accumulated so far + PartialInput *string `json:"partialInput,omitempty"` + // Progress message shown while parameters are streaming + InvocationMessage *StringOrMarkdown `json:"invocationMessage,omitempty"` +} + +// ToolCallPendingConfirmationState Parameters are complete, or a running tool requires re-confirmation +type ToolCallPendingConfirmationState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Message describing what the tool will do + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` + // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) + ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` + // File edits that this tool call will perform, for preview before confirmation + Edits json.RawMessage `json:"edits,omitempty"` + // Whether the agent host allows the client to edit the tool's input parameters before confirming + Editable *bool `json:"editable,omitempty"` + // Options the server offers for this confirmation. When present, the client + // SHOULD render these instead of a plain approve/deny UI. Each option + // belongs to a {@link ConfirmationOptionGroup} so the client can still + // categorise the choices. + Options []ConfirmationOption `json:"options,omitempty"` +} + +// ToolCallRunningState Tool is actively executing. +type ToolCallRunningState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Message describing what the tool will do + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` + // How the tool was confirmed for execution + Confirmed ToolCallConfirmationReason `json:"confirmed"` + // The confirmation option the user selected, if confirmation options were provided + SelectedOption *ConfirmationOption `json:"selectedOption,omitempty"` + // Partial content produced while the tool is still executing. + // + // For example, a terminal content block lets clients subscribe to live + // output before the tool completes. + Content []ToolResultContent `json:"content,omitempty"` +} + +// ToolCallPendingResultConfirmationState Tool finished executing, waiting for client to approve the result. +type ToolCallPendingResultConfirmationState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Message describing what the tool will do + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + // Whether the tool succeeded + Success bool `json:"success"` + // Past-tense description of what the tool did + PastTenseMessage StringOrMarkdown `json:"pastTenseMessage"` + // Unstructured result content blocks. + // + // This mirrors the `content` field of MCP `CallToolResult`. + Content []ToolResultContent `json:"content,omitempty"` + // Optional structured result object. + // + // This mirrors the `structuredContent` field of MCP `CallToolResult`. + StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` + // Error details if the tool failed + Error json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` + // How the tool was confirmed for execution + Confirmed ToolCallConfirmationReason `json:"confirmed"` + // The confirmation option the user selected, if confirmation options were provided + SelectedOption *ConfirmationOption `json:"selectedOption,omitempty"` +} + +// ToolCallCompletedState Tool completed successfully or with an error. +type ToolCallCompletedState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Message describing what the tool will do + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + // Whether the tool succeeded + Success bool `json:"success"` + // Past-tense description of what the tool did + PastTenseMessage StringOrMarkdown `json:"pastTenseMessage"` + // Unstructured result content blocks. + // + // This mirrors the `content` field of MCP `CallToolResult`. + Content []ToolResultContent `json:"content,omitempty"` + // Optional structured result object. + // + // This mirrors the `structuredContent` field of MCP `CallToolResult`. + StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` + // Error details if the tool failed + Error json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` + // How the tool was confirmed for execution + Confirmed ToolCallConfirmationReason `json:"confirmed"` + // The confirmation option the user selected, if confirmation options were provided + SelectedOption *ConfirmationOption `json:"selectedOption,omitempty"` +} + +// ToolCallCancelledState Tool call was cancelled before execution. +type ToolCallCancelledState struct { + // Unique tool call identifier + ToolCallID string `json:"toolCallId"` + // Internal tool name (for debugging/logging) + ToolName string `json:"toolName"` + // Human-readable tool name + DisplayName string `json:"displayName"` + // If this tool is provided by a client, the `clientId` of the owning client. + // Absent for server-side tools. + // + // When set, the identified client is responsible for executing the tool and + // dispatching `session/toolCallComplete` with the result. + ToolClientID *string `json:"toolClientId,omitempty"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Message describing what the tool will do + InvocationMessage StringOrMarkdown `json:"invocationMessage"` + // Raw tool input + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` + // Why the tool was cancelled + Reason ToolCallCancellationReason `json:"reason"` + // Optional message explaining the cancellation + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + // What the user suggested doing instead + UserSuggestion *UserMessage `json:"userSuggestion,omitempty"` + // The confirmation option the user selected, if confirmation options were provided + SelectedOption *ConfirmationOption `json:"selectedOption,omitempty"` +} + +// ConfirmationOption A confirmation option that the server offers for a tool call awaiting +type ConfirmationOption struct { + // Unique identifier for the option, returned in the confirmed action + ID string `json:"id"` + // Human-readable label displayed to the user + Label string `json:"label"` + // Whether this option represents an approval or denial + Kind ConfirmationOptionKind `json:"kind"` + // Logical group number for visual categorisation. + // + // Clients SHOULD display options in the order they are defined and MAY + // use differing group numbers to insert dividers between logical clusters + // of options. + Group *int `json:"group,omitempty"` +} + +// ToolDefinition Describes a tool available in a session, provided by either the server or the active client. +type ToolDefinition struct { + // Unique tool identifier + Name string `json:"name"` + // Human-readable display name + Title *string `json:"title,omitempty"` + // Description of what the tool does + Description *string `json:"description,omitempty"` + // JSON Schema defining the expected input parameters. + // + // Optional because client-provided tools may not have formal schemas. + // Mirrors MCP `Tool.inputSchema`. + InputSchema json.RawMessage `json:"inputSchema,omitempty"` + // JSON Schema defining the structure of the tool's output. + // + // Mirrors MCP `Tool.outputSchema`. + OutputSchema json.RawMessage `json:"outputSchema,omitempty"` + // Behavioral hints about the tool. All properties are advisory. + Annotations *ToolAnnotations `json:"annotations,omitempty"` + // Additional provider-specific metadata. + // + // Mirrors the MCP `_meta` convention. + Meta map[string]json.RawMessage `json:"_meta,omitempty"` +} + +// ToolAnnotations Behavioral hints about a tool. All properties are advisory and not +type ToolAnnotations struct { + // Alternate human-readable title + Title *string `json:"title,omitempty"` + // Tool does not modify its environment (default: false) + ReadOnlyHint *bool `json:"readOnlyHint,omitempty"` + // Tool may perform destructive updates (default: true) + DestructiveHint *bool `json:"destructiveHint,omitempty"` + // Repeated calls with the same arguments have no additional effect (default: false) + IdempotentHint *bool `json:"idempotentHint,omitempty"` + // Tool may interact with external entities (default: true) + OpenWorldHint *bool `json:"openWorldHint,omitempty"` +} + +// ToolResultTextContent Text content in a tool result. +type ToolResultTextContent struct { + Type ToolResultContentType `json:"type"` + // The text content + Text string `json:"text"` +} + +// ToolResultEmbeddedResourceContent Base64-encoded binary content embedded in a tool result. +type ToolResultEmbeddedResourceContent struct { + Type ToolResultContentType `json:"type"` + // Base64-encoded data + Data string `json:"data"` + // Content type (e.g. `"image/png"`, `"application/pdf"`) + ContentType string `json:"contentType"` +} + +// ToolResultResourceContent A reference to a resource stored outside the tool result. +type ToolResultResourceContent struct { + // Content URI + URI string `json:"uri"` + // Approximate size in bytes + SizeHint *int `json:"sizeHint,omitempty"` + // Content MIME type + ContentType *string `json:"contentType,omitempty"` + Type ToolResultContentType `json:"type"` +} + +// ToolResultFileEditContent Describes a file modification performed by a tool. +type ToolResultFileEditContent struct { + // The file state before the edit. Absent for file creations or for in-place file edits. + Before json.RawMessage `json:"before,omitempty"` + // The file state after the edit. Absent for file deletions. + After json.RawMessage `json:"after,omitempty"` + // Optional diff display metadata + Diff json.RawMessage `json:"diff,omitempty"` + Type ToolResultContentType `json:"type"` +} + +// ToolResultTerminalContent A reference to a terminal whose output is relevant to this tool result. +type ToolResultTerminalContent struct { + Type ToolResultContentType `json:"type"` + // Terminal URI (subscribable for full terminal state) + Resource string `json:"resource"` + // Display title for the terminal content + Title string `json:"title"` +} + +// ToolResultSubagentContent A reference to a subagent session spawned by a tool. +type ToolResultSubagentContent struct { + Type ToolResultContentType `json:"type"` + // Subagent session URI (subscribable for full session state) + Resource string `json:"resource"` + // Display title for the subagent + Title string `json:"title"` + // Internal agent name + AgentName *string `json:"agentName,omitempty"` + // Human-readable description of the subagent's task + Description *string `json:"description,omitempty"` +} + +// CustomizationRef A reference to an [Open Plugins](https://open-plugins.com/) plugin. +type CustomizationRef struct { + // Plugin URI (e.g. an HTTPS URL or marketplace identifier) + URI string `json:"uri"` + // Human-readable name + DisplayName string `json:"displayName"` + // Description of what the plugin provides + Description *string `json:"description,omitempty"` + // Icons for the plugin + Icons []Icon `json:"icons,omitempty"` + // Opaque version token for this customization. + // + // Clients SHOULD include a nonce with every customization they provide. + // Consumers can compare nonces to detect whether a customization has + // changed since it was last seen, avoiding redundant reloads or copies. + Nonce *string `json:"nonce,omitempty"` +} + +// SessionCustomization A customization active in a session. +type SessionCustomization struct { + // The plugin this customization refers to + Customization CustomizationRef `json:"customization"` + // Whether this customization is currently enabled + Enabled bool `json:"enabled"` + // Server-reported loading status + Status *CustomizationStatus `json:"status,omitempty"` + // Human-readable status detail (e.g. error message or degradation warning). + StatusMessage *string `json:"statusMessage,omitempty"` +} + +// FileEdit Describes a file modification with before/after state and diff metadata. +type FileEdit struct { + // The file state before the edit. Absent for file creations or for in-place file edits. + Before json.RawMessage `json:"before,omitempty"` + // The file state after the edit. Absent for file deletions. + After json.RawMessage `json:"after,omitempty"` + // Optional diff display metadata + Diff json.RawMessage `json:"diff,omitempty"` +} + +// TerminalInfo Lightweight terminal metadata exposed on the root state. +type TerminalInfo struct { + // Terminal URI (subscribable for full terminal state) + Resource string `json:"resource"` + // Human-readable terminal title + Title string `json:"title"` + // Who currently holds this terminal + Claim TerminalClaim `json:"claim"` + // Process exit code, if the terminal process has exited + ExitCode *int `json:"exitCode,omitempty"` +} + +// TerminalClientClaim A terminal claimed by a connected client. +type TerminalClientClaim struct { + // Discriminant + Kind TerminalClaimKind `json:"kind"` + // The `clientId` of the claiming client + ClientID string `json:"clientId"` +} + +// TerminalSessionClaim A terminal claimed by a session, optionally scoped to a specific turn or tool call. +type TerminalSessionClaim struct { + // Discriminant + Kind TerminalClaimKind `json:"kind"` + // Session URI that claimed the terminal + Session string `json:"session"` + // Optional turn identifier within the session + TurnID *string `json:"turnId,omitempty"` + // Optional tool call identifier within the turn + ToolCallID *string `json:"toolCallId,omitempty"` +} + +// TerminalState Full state for a single terminal, loaded when a client subscribes to the terminal's URI. +type TerminalState struct { + // Human-readable terminal title + Title string `json:"title"` + // Current working directory of the terminal process + Cwd *string `json:"cwd,omitempty"` + // Terminal width in columns + Cols *int `json:"cols,omitempty"` + // Terminal height in rows + Rows *int `json:"rows,omitempty"` + // Typed content parts, replacing the flat `content: string`. + // + // Naive consumers that only need the raw VT stream can reconstruct it with: + // `content.map(p => p.type === 'command' ? p.output : p.value).join('')` + // + // Consumers that need command boundaries can filter by part type. + Content []TerminalContentPart `json:"content"` + // Process exit code, set when the terminal process exits + ExitCode *int `json:"exitCode,omitempty"` + // Who currently holds this terminal + Claim TerminalClaim `json:"claim"` + // Whether this terminal emits `terminal/commandExecuted` and + // `terminal/commandFinished` actions and populates `command`-typed parts. + // + // Clients MUST check this flag before relying on command detection. + // Do NOT use the presence of a `command` part as a feature flag — parts + // are absent in the normal idle state. + SupportsCommandDetection *bool `json:"supportsCommandDetection,omitempty"` +} + +// TerminalUnclassifiedPart Unstructured terminal output — content before, between, or after commands, +type TerminalUnclassifiedPart struct { + Type string `json:"type"` + // Accumulated VT output. Appended to by `terminal/data` when no command is executing. + Value string `json:"value"` +} + +// TerminalCommandPart A single command: its command line and the output it produced. +type TerminalCommandPart struct { + Type string `json:"type"` + // Stable id matching the `commandId` on the corresponding + // `terminal/commandExecuted` and `terminal/commandFinished` actions. + CommandID string `json:"commandId"` + // The command line submitted to the shell. + CommandLine string `json:"commandLine"` + // Accumulated VT output. Appended to by `terminal/data` while `isComplete` + // is false. Shell integration escape sequences are stripped by the server. + Output string `json:"output"` + // Unix timestamp (ms) when execution started, as reported by the server. + Timestamp int `json:"timestamp"` + // Whether the command has finished. + IsComplete bool `json:"isComplete"` + // Shell exit code. Set at completion. `undefined` if unknown. + ExitCode *int `json:"exitCode,omitempty"` + // Wall-clock duration in milliseconds. Set at completion. + DurationMs *int `json:"durationMs,omitempty"` +} + +type UsageInfo struct { + // Input tokens consumed + InputTokens *int `json:"inputTokens,omitempty"` + // Output tokens generated + OutputTokens *int `json:"outputTokens,omitempty"` + // Model used + Model *string `json:"model,omitempty"` + // Tokens read from cache + CacheReadTokens *int `json:"cacheReadTokens,omitempty"` +} + +type ErrorInfo struct { + // Error type identifier + ErrorType string `json:"errorType"` + // Human-readable error message + Message string `json:"message"` + // Stack trace + Stack *string `json:"stack,omitempty"` +} + +// Snapshot A point-in-time snapshot of a subscribed resource's state, returned by +type Snapshot struct { + // The subscribed resource URI (e.g. `agenthost:/root` or `copilot:/`) + Resource string `json:"resource"` + // The current state of the resource + State SnapshotState `json:"state"` + // The `serverSeq` at which this snapshot was taken. Subsequent actions will have `serverSeq > fromSeq`. + FromSeq int `json:"fromSeq"` +} + +// ── Discriminated Unions ────────────────────────────────────────────────────── + +// ResponsePart is a discriminated union keyed on "kind". +type ResponsePart struct { + Markdown *MarkdownResponsePart + ContentRef *ResourceReponsePart + ToolCall *ToolCallResponsePart + Reasoning *ReasoningResponsePart +} + +func (u *ResponsePart) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"kind"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "markdown": + u.Markdown = new(MarkdownResponsePart) + return json.Unmarshal(data, u.Markdown) + case "contentRef": + u.ContentRef = new(ResourceReponsePart) + return json.Unmarshal(data, u.ContentRef) + case "toolCall": + u.ToolCall = new(ToolCallResponsePart) + return json.Unmarshal(data, u.ToolCall) + case "reasoning": + u.Reasoning = new(ReasoningResponsePart) + return json.Unmarshal(data, u.Reasoning) + default: + return fmt.Errorf("unknown ResponsePart kind: %q", disc.D) + } +} + +func (u ResponsePart) MarshalJSON() ([]byte, error) { + if u.Markdown != nil { + return json.Marshal(u.Markdown) + } + if u.ContentRef != nil { + return json.Marshal(u.ContentRef) + } + if u.ToolCall != nil { + return json.Marshal(u.ToolCall) + } + if u.Reasoning != nil { + return json.Marshal(u.Reasoning) + } + return nil, fmt.Errorf("empty ResponsePart: no variant set") +} + +// ToolCallState is a discriminated union keyed on "status". +type ToolCallState struct { + Streaming *ToolCallStreamingState + PendingConfirmation *ToolCallPendingConfirmationState + Running *ToolCallRunningState + PendingResultConfirmation *ToolCallPendingResultConfirmationState + Completed *ToolCallCompletedState + Cancelled *ToolCallCancelledState +} + +func (u *ToolCallState) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"status"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "streaming": + u.Streaming = new(ToolCallStreamingState) + return json.Unmarshal(data, u.Streaming) + case "pending-confirmation": + u.PendingConfirmation = new(ToolCallPendingConfirmationState) + return json.Unmarshal(data, u.PendingConfirmation) + case "running": + u.Running = new(ToolCallRunningState) + return json.Unmarshal(data, u.Running) + case "pending-result-confirmation": + u.PendingResultConfirmation = new(ToolCallPendingResultConfirmationState) + return json.Unmarshal(data, u.PendingResultConfirmation) + case "completed": + u.Completed = new(ToolCallCompletedState) + return json.Unmarshal(data, u.Completed) + case "cancelled": + u.Cancelled = new(ToolCallCancelledState) + return json.Unmarshal(data, u.Cancelled) + default: + return fmt.Errorf("unknown ToolCallState status: %q", disc.D) + } +} + +func (u ToolCallState) MarshalJSON() ([]byte, error) { + if u.Streaming != nil { + return json.Marshal(u.Streaming) + } + if u.PendingConfirmation != nil { + return json.Marshal(u.PendingConfirmation) + } + if u.Running != nil { + return json.Marshal(u.Running) + } + if u.PendingResultConfirmation != nil { + return json.Marshal(u.PendingResultConfirmation) + } + if u.Completed != nil { + return json.Marshal(u.Completed) + } + if u.Cancelled != nil { + return json.Marshal(u.Cancelled) + } + return nil, fmt.Errorf("empty ToolCallState: no variant set") +} + +// TerminalClaim is a discriminated union keyed on "kind". +type TerminalClaim struct { + Client *TerminalClientClaim + Session *TerminalSessionClaim +} + +func (u *TerminalClaim) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"kind"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "client": + u.Client = new(TerminalClientClaim) + return json.Unmarshal(data, u.Client) + case "session": + u.Session = new(TerminalSessionClaim) + return json.Unmarshal(data, u.Session) + default: + return fmt.Errorf("unknown TerminalClaim kind: %q", disc.D) + } +} + +func (u TerminalClaim) MarshalJSON() ([]byte, error) { + if u.Client != nil { + return json.Marshal(u.Client) + } + if u.Session != nil { + return json.Marshal(u.Session) + } + return nil, fmt.Errorf("empty TerminalClaim: no variant set") +} + +// TerminalContentPart is a discriminated union keyed on "type". +type TerminalContentPart struct { + Unclassified *TerminalUnclassifiedPart + Command *TerminalCommandPart +} + +func (u *TerminalContentPart) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"type"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "unclassified": + u.Unclassified = new(TerminalUnclassifiedPart) + return json.Unmarshal(data, u.Unclassified) + case "command": + u.Command = new(TerminalCommandPart) + return json.Unmarshal(data, u.Command) + default: + return fmt.Errorf("unknown TerminalContentPart type: %q", disc.D) + } +} + +func (u TerminalContentPart) MarshalJSON() ([]byte, error) { + if u.Unclassified != nil { + return json.Marshal(u.Unclassified) + } + if u.Command != nil { + return json.Marshal(u.Command) + } + return nil, fmt.Errorf("empty TerminalContentPart: no variant set") +} + +// SessionInputQuestion is a discriminated union keyed on "kind". +type SessionInputQuestion struct { + Text *SessionInputTextQuestion + Number *SessionInputNumberQuestion + Integer *SessionInputNumberQuestion + Boolean *SessionInputBooleanQuestion + SingleSelect *SessionInputSingleSelectQuestion + MultiSelect *SessionInputMultiSelectQuestion +} + +func (u *SessionInputQuestion) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"kind"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "text": + u.Text = new(SessionInputTextQuestion) + return json.Unmarshal(data, u.Text) + case "number": + u.Number = new(SessionInputNumberQuestion) + return json.Unmarshal(data, u.Number) + case "integer": + u.Integer = new(SessionInputNumberQuestion) + return json.Unmarshal(data, u.Integer) + case "boolean": + u.Boolean = new(SessionInputBooleanQuestion) + return json.Unmarshal(data, u.Boolean) + case "single-select": + u.SingleSelect = new(SessionInputSingleSelectQuestion) + return json.Unmarshal(data, u.SingleSelect) + case "multi-select": + u.MultiSelect = new(SessionInputMultiSelectQuestion) + return json.Unmarshal(data, u.MultiSelect) + default: + return fmt.Errorf("unknown SessionInputQuestion kind: %q", disc.D) + } +} + +func (u SessionInputQuestion) MarshalJSON() ([]byte, error) { + if u.Text != nil { + return json.Marshal(u.Text) + } + if u.Number != nil { + return json.Marshal(u.Number) + } + if u.Integer != nil { + return json.Marshal(u.Integer) + } + if u.Boolean != nil { + return json.Marshal(u.Boolean) + } + if u.SingleSelect != nil { + return json.Marshal(u.SingleSelect) + } + if u.MultiSelect != nil { + return json.Marshal(u.MultiSelect) + } + return nil, fmt.Errorf("empty SessionInputQuestion: no variant set") +} + +// SessionInputAnswerValue is a discriminated union keyed on "kind". +type SessionInputAnswerValue struct { + Text *SessionInputTextAnswerValue + Number *SessionInputNumberAnswerValue + Boolean *SessionInputBooleanAnswerValue + Selected *SessionInputSelectedAnswerValue + SelectedMany *SessionInputSelectedManyAnswerValue +} + +func (u *SessionInputAnswerValue) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"kind"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "text": + u.Text = new(SessionInputTextAnswerValue) + return json.Unmarshal(data, u.Text) + case "number": + u.Number = new(SessionInputNumberAnswerValue) + return json.Unmarshal(data, u.Number) + case "boolean": + u.Boolean = new(SessionInputBooleanAnswerValue) + return json.Unmarshal(data, u.Boolean) + case "selected": + u.Selected = new(SessionInputSelectedAnswerValue) + return json.Unmarshal(data, u.Selected) + case "selected-many": + u.SelectedMany = new(SessionInputSelectedManyAnswerValue) + return json.Unmarshal(data, u.SelectedMany) + default: + return fmt.Errorf("unknown SessionInputAnswerValue kind: %q", disc.D) + } +} + +func (u SessionInputAnswerValue) MarshalJSON() ([]byte, error) { + if u.Text != nil { + return json.Marshal(u.Text) + } + if u.Number != nil { + return json.Marshal(u.Number) + } + if u.Boolean != nil { + return json.Marshal(u.Boolean) + } + if u.Selected != nil { + return json.Marshal(u.Selected) + } + if u.SelectedMany != nil { + return json.Marshal(u.SelectedMany) + } + return nil, fmt.Errorf("empty SessionInputAnswerValue: no variant set") +} + +// SessionInputAnswer is a discriminated union keyed on "state". +type SessionInputAnswer struct { + Draft *SessionInputAnswered + Submitted *SessionInputAnswered + Skipped *SessionInputSkipped +} + +func (u *SessionInputAnswer) UnmarshalJSON(data []byte) error { + var disc struct { + D string `json:"state"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.D { + case "draft": + u.Draft = new(SessionInputAnswered) + return json.Unmarshal(data, u.Draft) + case "submitted": + u.Submitted = new(SessionInputAnswered) + return json.Unmarshal(data, u.Submitted) + case "skipped": + u.Skipped = new(SessionInputSkipped) + return json.Unmarshal(data, u.Skipped) + default: + return fmt.Errorf("unknown SessionInputAnswer state: %q", disc.D) + } +} + +func (u SessionInputAnswer) MarshalJSON() ([]byte, error) { + if u.Draft != nil { + return json.Marshal(u.Draft) + } + if u.Submitted != nil { + return json.Marshal(u.Submitted) + } + if u.Skipped != nil { + return json.Marshal(u.Skipped) + } + return nil, fmt.Errorf("empty SessionInputAnswer: no variant set") +} + +// ToolResultContent is a discriminated union keyed on "type". +type ToolResultContent struct { + Text *ToolResultTextContent + EmbeddedResource *ToolResultEmbeddedResourceContent + Resource *ToolResultResourceContent + FileEdit *ToolResultFileEditContent + Terminal *ToolResultTerminalContent + Subagent *ToolResultSubagentContent +} + +func (u *ToolResultContent) UnmarshalJSON(data []byte) error { + var disc struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &disc); err != nil { + return err + } + switch disc.Type { + case "text": + u.Text = new(ToolResultTextContent) + return json.Unmarshal(data, u.Text) + case "embeddedResource": + u.EmbeddedResource = new(ToolResultEmbeddedResourceContent) + return json.Unmarshal(data, u.EmbeddedResource) + case "resource": + u.Resource = new(ToolResultResourceContent) + return json.Unmarshal(data, u.Resource) + case "fileEdit": + u.FileEdit = new(ToolResultFileEditContent) + return json.Unmarshal(data, u.FileEdit) + case "terminal": + u.Terminal = new(ToolResultTerminalContent) + return json.Unmarshal(data, u.Terminal) + case "subagent": + u.Subagent = new(ToolResultSubagentContent) + return json.Unmarshal(data, u.Subagent) + default: + return fmt.Errorf("unknown ToolResultContent type: %q", disc.Type) + } +} + +func (u ToolResultContent) MarshalJSON() ([]byte, error) { + if u.Text != nil { + return json.Marshal(u.Text) + } + if u.EmbeddedResource != nil { + return json.Marshal(u.EmbeddedResource) + } + if u.Resource != nil { + return json.Marshal(u.Resource) + } + if u.FileEdit != nil { + return json.Marshal(u.FileEdit) + } + if u.Terminal != nil { + return json.Marshal(u.Terminal) + } + if u.Subagent != nil { + return json.Marshal(u.Subagent) + } + return nil, fmt.Errorf("empty ToolResultContent: no variant set") +} + +// SnapshotState is the state payload of a snapshot — root, session, or terminal state. +type SnapshotState struct { + Root *RootState + Session *SessionState + Terminal *TerminalState +} + +func (s *SnapshotState) UnmarshalJSON(data []byte) error { + // Peek at top-level fields to determine variant type + var peek map[string]json.RawMessage + if err := json.Unmarshal(data, &peek); err != nil { + return err + } + // SessionState has a required "summary" field + if _, ok := peek["summary"]; ok { + s.Session = new(SessionState) + return json.Unmarshal(data, s.Session) + } + // TerminalState has "content" but not "agents" + if _, hasContent := peek["content"]; hasContent { + if _, hasAgents := peek["agents"]; !hasAgents { + s.Terminal = new(TerminalState) + return json.Unmarshal(data, s.Terminal) + } + } + // Fall back to RootState + s.Root = new(RootState) + return json.Unmarshal(data, s.Root) +} + +func (s SnapshotState) MarshalJSON() ([]byte, error) { + if s.Session != nil { + return json.Marshal(s.Session) + } + if s.Terminal != nil { + return json.Marshal(s.Terminal) + } + if s.Root != nil { + return json.Marshal(s.Root) + } + return nil, fmt.Errorf("empty SnapshotState: no variant set") +} diff --git a/package.json b/package.json index bd50af7a..8d28abac 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "generate:docs": "tsx scripts/generate.ts --docs", "generate:schema": "tsx scripts/generate.ts --schema", "generate:swift": "tsx scripts/generate.ts --swift", + "generate:go": "tsx scripts/generate.ts --go", "docs:dev": "vitepress dev docs", "docs:build": "npm run generate && vitepress build docs", "docs:preview": "vitepress preview docs", diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts new file mode 100644 index 00000000..618f25dd --- /dev/null +++ b/scripts/generate-go.ts @@ -0,0 +1,1419 @@ +/** + * Go Module Generator — Generates a Go module from TypeScript type + * definitions parsed via ts-morph. + * + * Output: examples/go/ahp/ with go.mod and generated .go files. + */ + +import { + Project, + InterfaceDeclaration, + EnumDeclaration, + PropertySignature, +} from 'ts-morph'; +import fs from 'fs'; +import path from 'path'; + +const GENERATED_HEADER = '// Code generated from types/*.ts — DO NOT EDIT.\n\npackage ahp\n'; + +// ─── Name Mapping ──────────────────────────────────────────────────────────── + +/** Strips the I prefix from interface names: IRootState → RootState */ +function goTypeName(tsName: string): string { + if ( + tsName.length > 1 && + tsName[0] === 'I' && + tsName[1] === tsName[1].toUpperCase() && + tsName[1] !== tsName[1].toLowerCase() + ) { + return tsName.substring(1); + } + return tsName; +} + +/** camelCase or PascalCase → Go exported PascalCase with acronym handling */ +function goFieldName(name: string): string { + // Strip leading underscore: _meta → meta + if (name.startsWith('_')) name = name.substring(1); + + // Convert snake_case to camelCase + if (name.includes('_')) { + name = name.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase()); + } + + // Capitalize first letter + name = name[0].toUpperCase() + name.slice(1); + + // Apply Go acronym conventions at word boundaries + name = name + .replace(/Id$/, 'ID') + .replace(/Id([A-Z])/g, 'ID$1') + .replace(/Url$/, 'URL') + .replace(/Url([A-Z])/g, 'URL$1') + .replace(/Uri$/, 'URI') + .replace(/Uri([A-Z])/g, 'URI$1') + .replace(/Rpc$/, 'RPC') + .replace(/Rpc([A-Z])/g, 'RPC$1') + .replace(/Json$/, 'JSON') + .replace(/Json([A-Z])/g, 'JSON$1') + .replace(/Http$/, 'HTTP') + .replace(/Http([A-Z])/g, 'HTTP$1') + .replace(/Api$/, 'API') + .replace(/Api([A-Z])/g, 'API$1') + .replace(/Ip$/, 'IP') + .replace(/Ip([A-Z])/g, 'IP$1'); + + return name; +} + +/** PascalCase → camelCase (for Go enum const names from TS member names) */ +function toCamelCase(name: string): string { + return name[0].toLowerCase() + name.slice(1); +} + +/** Snake_case → camelCase (for RFC 9728 properties) */ +function snakeToCamel(s: string): string { + return s.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase()); +} + +// ─── Type Mapping ──────────────────────────────────────────────────────────── + +/** + * Synthetic Go structs required for `Partial` references. + * Go has no structural `Partial`, so we emit a sibling struct with every + * property forced to a pointer. Populated by `mapType`. + */ +const requiredPartialStructs = new Set(); + +/** Go name for `Partial` → `PartialSessionSummary`. */ +function partialGoName(tsInterfaceName: string): string { + return `Partial${goTypeName(tsInterfaceName)}`; +} + +/** Map a TypeScript type string to a Go type string */ +function mapType(tsType: string, propName?: string, containerName?: string): string { + tsType = tsType.replace(/import\([^)]+\)\./g, '').trim(); + + // Remove outer parens + while (tsType.startsWith('(') && tsType.endsWith(')')) { + tsType = tsType.slice(1, -1).trim(); + } + + // Primitives + if (tsType === 'string') return 'string'; + if (tsType === 'number') { + const isFloat = + (containerName === 'ISessionInputNumberAnswerValue' && propName === 'value') || + (containerName === 'ISessionInputNumberQuestion' && (propName === 'defaultValue' || propName === 'min' || propName === 'max')) || + propName === 'numberValue'; + return isFloat ? 'float64' : 'int'; + } + if (tsType === 'boolean') return 'bool'; + if (tsType === 'unknown') return 'json.RawMessage'; + if (tsType === 'object') return 'map[string]json.RawMessage'; + if (tsType === 'true' || tsType === 'false') return 'bool'; + + // Type aliases + if (tsType === 'URI') return 'string'; + if (tsType === 'StringOrMarkdown') return 'StringOrMarkdown'; + + // Known unions + if (tsType === 'IRootState | ISessionState' || tsType === 'IRootState | ISessionState | ITerminalState') return 'SnapshotState'; + + // T | null → *T + const nullMatch = tsType.match(/^(.+?)\s*\|\s*null$/); + if (nullMatch) { + const inner = mapType(nullMatch[1], propName, containerName); + return inner.startsWith('*') ? inner : `*${inner}`; + } + + // T | undefined → T (optionality handled by ? token) + const undefMatch = tsType.match(/^(.+?)\s*\|\s*undefined$/); + if (undefMatch) return mapType(undefMatch[1], propName, containerName); + + // Array: T[] + const arrayMatch = tsType.match(/^(.+)\[\]$/); + if (arrayMatch) return `[]${mapType(arrayMatch[1], propName, containerName)}`; + + // Array + const arrayGenericMatch = tsType.match(/^Array<(.+)>$/); + if (arrayGenericMatch) return `[]${mapType(arrayGenericMatch[1], propName, containerName)}`; + + // Record + const recordMatch = tsType.match(/^Record$/); + if (recordMatch) return `map[string]${mapType(recordMatch[1], propName, containerName)}`; + + // Partial + const partialMatch = tsType.match(/^Partial<(\w+)>$/); + if (partialMatch) { + requiredPartialStructs.add(partialMatch[1]); + return partialGoName(partialMatch[1]); + } + + // Enum member union: EnumName.A | EnumName.B | ... + const enumUnionMatch = tsType.match(/^(\w+)\.\w+(\s*\|\s*\1\.\w+)*$/); + if (enumUnionMatch) return enumUnionMatch[1]; + + // Single enum member: EnumName.Value + const enumMemberMatch = tsType.match(/^(\w+)\.(\w+)$/); + if (enumMemberMatch) return enumMemberMatch[1]; + + // String literal: 'value' + if (tsType.startsWith("'") && tsType.endsWith("'")) return 'string'; + + // String literal union: 'a' | 'b' | ... + if (/^'[^']*'(\s*\|\s*'[^']*')+$/.test(tsType)) return 'string'; + + // Inline object type → json.RawMessage fallback + if (tsType.startsWith('{')) return 'json.RawMessage'; + + // Named type — strip I prefix + return goTypeName(tsType); +} + +// ─── Property Extraction ───────────────────────────────────────────────────── + +interface GoProp { + name: string; // Go field name (PascalCase) + wireName: string; // JSON key (original TS name) + type: string; // Go type + optional: boolean; + doc: string; +} + +function getPropertyType(prop: PropertySignature): string { + const typeNode = prop.getTypeNode(); + if (typeNode) return typeNode.getText(); + return prop.getType().getText(prop); +} + +function getPropertyDoc(prop: PropertySignature): string { + const jsDocs = prop.getJsDocs(); + if (jsDocs.length === 0) return ''; + return jsDocs[0].getDescription().trim(); +} + +/** + * Recursively collect all properties from an interface, flattening extends. + */ +function getAllProperties(iface: InterfaceDeclaration, project: Project): PropertySignature[] { + const props: PropertySignature[] = []; + + for (const ext of iface.getExtends()) { + const baseName = ext.getExpression().getText(); + const baseIface = findInterface(project, baseName); + if (baseIface) { + props.push(...getAllProperties(baseIface, project)); + } + } + + props.push(...iface.getProperties()); + return props; +} + +function findInterface(project: Project, name: string): InterfaceDeclaration | undefined { + for (const sf of project.getSourceFiles()) { + const iface = sf.getInterface(name); + if (iface) return iface; + } + return undefined; +} + +/** Check whether a Go type is inherently nullable (pointer, slice, map) */ +function isNullableGoType(goType: string): boolean { + return goType.startsWith('*') || goType.startsWith('[]') || goType.startsWith('map[') || goType === 'json.RawMessage'; +} + +/** Extract Go properties from a TypeScript interface */ +function extractProps(iface: InterfaceDeclaration, project: Project): GoProp[] { + const allProps = getAllProperties(iface, project); + const seen = new Set(); + + return allProps + .filter(p => { + const name = p.getName(); + if (seen.has(name)) return false; + seen.add(name); + return true; + }) + .map(p => { + const tsName = p.getName(); + const tsType = getPropertyType(p); + const goType = mapType(tsType, tsName, iface.getName()); + const hasUnionUndefined = /\|\s*undefined/.test(tsType); + const isOptional = p.hasQuestionToken() || hasUnionUndefined; + + // If optional and not already a nullable Go type, wrap in pointer + const needsPointer = isOptional && !isNullableGoType(goType); + const finalType = needsPointer ? `*${goType}` : goType; + + // Build Go field name + let fieldName: string; + if (tsName.startsWith('_')) { + fieldName = goFieldName(tsName); + } else if (tsName.includes('_')) { + fieldName = goFieldName(snakeToCamel(tsName)); + } else { + fieldName = goFieldName(tsName); + } + + return { + name: fieldName, + wireName: tsName, + type: finalType, + optional: isOptional, + doc: getPropertyDoc(p), + }; + }); +} + +// ─── Go Enum Generation ────────────────────────────────────────────────────── + +function generateGoEnum(enumDecl: EnumDeclaration): string { + const name = enumDecl.getName(); + const lines: string[] = []; + const desc = enumDecl.getJsDocs()[0]?.getDescription().trim(); + const values = enumDecl.getMembers().map(member => member.getValue()); + const rawType = values.every(value => typeof value === 'number') ? 'int' : 'string'; + + if (desc) { + for (const docLine of desc.split('\n')) { + lines.push(`// ${docLine.trim()}`); + } + } + lines.push(`type ${name} ${rawType}`); + lines.push(''); + lines.push('const ('); + + for (const member of enumDecl.getMembers()) { + const memberName = name + member.getName(); + const value = member.getValue(); + const memberDoc = member.getJsDocs()[0]?.getDescription().trim(); + if (memberDoc) { + for (const docLine of memberDoc.split('\n')) { + lines.push(`\t// ${docLine.trim()}`); + } + } + if (rawType === 'int') { + lines.push(`\t${memberName} ${name} = ${value}`); + } else { + lines.push(`\t${memberName} ${name} = ${JSON.stringify(value)}`); + } + } + + lines.push(')'); + return lines.join('\n'); +} + +// ─── Go Struct Generation ──────────────────────────────────────────────────── + +function generateGoStruct( + goName: string, + props: GoProp[], + doc?: string, +): string { + const lines: string[] = []; + + if (doc) { + lines.push(`// ${goName} ${doc}`); + } + lines.push(`type ${goName} struct {`); + + for (const p of props) { + if (p.doc) { + for (const docLine of p.doc.split('\n')) { + lines.push(`\t// ${docLine.trim()}`); + } + } + const omit = p.optional ? ',omitempty' : ''; + lines.push(`\t${p.name} ${p.type} \`json:"${p.wireName}${omit}"\``); + } + + lines.push('}'); + return lines.join('\n'); +} + +// ─── Discriminated Union Generation ────────────────────────────────────────── + +interface UnionVariant { + caseName: string; // Go field name on wrapper struct (PascalCase) + structName: string; // Go type name of the variant struct + discriminantValue: string; // Wire value for the discriminant +} + +interface UnionConfig { + name: string; + discriminantField: string; + variants: UnionVariant[]; + unknownFallback?: boolean; // If true, add raw JSON fallback for unknown discriminants +} + +function generateDiscriminatedUnion(config: UnionConfig): string { + const lines: string[] = []; + + // Struct definition with one pointer per variant + lines.push(`// ${config.name} is a discriminated union keyed on "${config.discriminantField}".`); + lines.push(`type ${config.name} struct {`); + for (const v of config.variants) { + lines.push(`\t${v.caseName} *${v.structName}`); + } + if (config.unknownFallback) { + lines.push(`\tUnknownType string`); + lines.push(`\tUnknownRaw json.RawMessage`); + } + lines.push('}'); + lines.push(''); + + // UnmarshalJSON + lines.push(`func (u *${config.name}) UnmarshalJSON(data []byte) error {`); + lines.push(`\tvar disc struct {`); + lines.push(`\t\tD string \`json:"${config.discriminantField}"\``); + lines.push(`\t}`); + lines.push(`\tif err := json.Unmarshal(data, &disc); err != nil {`); + lines.push(`\t\treturn err`); + lines.push(`\t}`); + lines.push(`\tswitch disc.D {`); + for (const v of config.variants) { + lines.push(`\tcase ${JSON.stringify(v.discriminantValue)}:`); + lines.push(`\t\tu.${v.caseName} = new(${v.structName})`); + lines.push(`\t\treturn json.Unmarshal(data, u.${v.caseName})`); + } + lines.push(`\tdefault:`); + if (config.unknownFallback) { + lines.push(`\t\tu.UnknownType = disc.D`); + lines.push(`\t\tu.UnknownRaw = make(json.RawMessage, len(data))`); + lines.push(`\t\tcopy(u.UnknownRaw, data)`); + lines.push(`\t\treturn nil`); + } else { + lines.push(`\t\treturn fmt.Errorf("unknown ${config.name} ${config.discriminantField}: %q", disc.D)`); + } + lines.push(`\t}`); + lines.push('}'); + lines.push(''); + + // MarshalJSON + lines.push(`func (u ${config.name}) MarshalJSON() ([]byte, error) {`); + for (const v of config.variants) { + lines.push(`\tif u.${v.caseName} != nil {`); + lines.push(`\t\treturn json.Marshal(u.${v.caseName})`); + lines.push(`\t}`); + } + if (config.unknownFallback) { + lines.push(`\tif u.UnknownRaw != nil {`); + lines.push(`\t\treturn u.UnknownRaw, nil`); + lines.push(`\t}`); + } + lines.push(`\treturn nil, fmt.Errorf("empty ${config.name}: no variant set")`); + lines.push('}'); + + return lines.join('\n'); +} + +// ─── Interface → Go Struct (auto from project) ────────────────────────────── + +function generateStructFromInterface( + project: Project, + tsInterfaceName: string, + goNameOverride?: string, +): string { + const iface = findInterface(project, tsInterfaceName); + if (!iface) throw new Error(`Interface ${tsInterfaceName} not found`); + const name = goNameOverride ?? goTypeName(tsInterfaceName); + const props = extractProps(iface, project); + const desc = iface.getJsDocs()[0]?.getDescription().trim(); + const docLine = desc ? desc.split('\n')[0].trim() : undefined; + return generateGoStruct(name, props, docLine); +} + +/** + * Emit a Go counterpart for `Partial`: same fields as `T` but with + * every field forced to a pointer type. + */ +function generatePartialStructFromInterface( + project: Project, + tsInterfaceName: string, +): string { + const iface = findInterface(project, tsInterfaceName); + if (!iface) throw new Error(`Interface ${tsInterfaceName} not found`); + const props = extractProps(iface, project).map(p => ({ + ...p, + optional: true, + type: p.type.startsWith('*') ? p.type : `*${p.type}`, + })); + return generateGoStruct(partialGoName(tsInterfaceName), props); +} + +// ─── State File Generator ──────────────────────────────────────────────────── + +const STATE_ENUMS = [ + 'PolicyState', 'PendingMessageKind', 'SessionLifecycle', 'SessionStatus', + 'SessionInputAnswerState', 'SessionInputAnswerValueKind', 'SessionInputQuestionKind', + 'SessionInputResponseKind', + 'TurnState', 'AttachmentType', 'ResponsePartKind', 'ToolCallStatus', + 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', + 'ToolResultContentType', 'CustomizationStatus', 'TerminalClaimKind', +]; + +const STATE_STRUCTS = [ + 'Icon', 'IProtectedResourceMetadata', 'IRootState', 'IRootConfigState', 'IAgentInfo', + 'ISessionModelInfo', 'IModelSelection', 'IConfigPropertySchema', 'IConfigSchema', + 'IPendingMessage', 'ISessionState', 'ISessionActiveClient', + 'ISessionSummary', 'IProjectInfo', 'ISessionConfigState', 'ITurn', 'IActiveTurn', 'IUserMessage', + 'ISessionInputOption', + 'ISessionInputTextAnswerValue', 'ISessionInputNumberAnswerValue', + 'ISessionInputBooleanAnswerValue', 'ISessionInputSelectedAnswerValue', + 'ISessionInputSelectedManyAnswerValue', 'ISessionInputAnswered', + 'ISessionInputSkipped', + 'ISessionInputTextQuestion', + 'ISessionInputNumberQuestion', 'ISessionInputBooleanQuestion', + 'ISessionInputSingleSelectQuestion', 'ISessionInputMultiSelectQuestion', + 'ISessionInputRequest', + 'IMessageAttachment', 'IMarkdownResponsePart', 'IContentRef', + 'IResourceReponsePart', 'IToolCallResponsePart', 'IReasoningResponsePart', + 'IToolCallResult', 'IToolCallStreamingState', + 'IToolCallPendingConfirmationState', 'IToolCallRunningState', + 'IToolCallPendingResultConfirmationState', 'IToolCallCompletedState', + 'IToolCallCancelledState', 'IConfirmationOption', 'IToolDefinition', 'IToolAnnotations', + 'IToolResultTextContent', 'IToolResultEmbeddedResourceContent', + 'IToolResultResourceContent', 'IToolResultFileEditContent', + 'IToolResultTerminalContent', 'IToolResultSubagentContent', 'ICustomizationRef', + 'ISessionCustomization', 'IFileEdit', 'ITerminalInfo', + 'ITerminalClientClaim', 'ITerminalSessionClaim', 'ITerminalState', + 'ITerminalUnclassifiedPart', 'ITerminalCommandPart', + 'IUsageInfo', 'IErrorInfo', 'ISnapshot', +]; + +const RESPONSE_PART_UNION: UnionConfig = { + name: 'ResponsePart', + discriminantField: 'kind', + variants: [ + { caseName: 'Markdown', structName: 'MarkdownResponsePart', discriminantValue: 'markdown' }, + { caseName: 'ContentRef', structName: 'ResourceReponsePart', discriminantValue: 'contentRef' }, + { caseName: 'ToolCall', structName: 'ToolCallResponsePart', discriminantValue: 'toolCall' }, + { caseName: 'Reasoning', structName: 'ReasoningResponsePart', discriminantValue: 'reasoning' }, + ], +}; + +const TOOL_CALL_STATE_UNION: UnionConfig = { + name: 'ToolCallState', + discriminantField: 'status', + variants: [ + { caseName: 'Streaming', structName: 'ToolCallStreamingState', discriminantValue: 'streaming' }, + { caseName: 'PendingConfirmation', structName: 'ToolCallPendingConfirmationState', discriminantValue: 'pending-confirmation' }, + { caseName: 'Running', structName: 'ToolCallRunningState', discriminantValue: 'running' }, + { caseName: 'PendingResultConfirmation', structName: 'ToolCallPendingResultConfirmationState', discriminantValue: 'pending-result-confirmation' }, + { caseName: 'Completed', structName: 'ToolCallCompletedState', discriminantValue: 'completed' }, + { caseName: 'Cancelled', structName: 'ToolCallCancelledState', discriminantValue: 'cancelled' }, + ], +}; + +const TERMINAL_CLAIM_UNION: UnionConfig = { + name: 'TerminalClaim', + discriminantField: 'kind', + variants: [ + { caseName: 'Client', structName: 'TerminalClientClaim', discriminantValue: 'client' }, + { caseName: 'Session', structName: 'TerminalSessionClaim', discriminantValue: 'session' }, + ], +}; + +const TERMINAL_CONTENT_PART_UNION: UnionConfig = { + name: 'TerminalContentPart', + discriminantField: 'type', + variants: [ + { caseName: 'Unclassified', structName: 'TerminalUnclassifiedPart', discriminantValue: 'unclassified' }, + { caseName: 'Command', structName: 'TerminalCommandPart', discriminantValue: 'command' }, + ], +}; + +const SESSION_INPUT_QUESTION_UNION: UnionConfig = { + name: 'SessionInputQuestion', + discriminantField: 'kind', + variants: [ + { caseName: 'Text', structName: 'SessionInputTextQuestion', discriminantValue: 'text' }, + { caseName: 'Number', structName: 'SessionInputNumberQuestion', discriminantValue: 'number' }, + { caseName: 'Integer', structName: 'SessionInputNumberQuestion', discriminantValue: 'integer' }, + { caseName: 'Boolean', structName: 'SessionInputBooleanQuestion', discriminantValue: 'boolean' }, + { caseName: 'SingleSelect', structName: 'SessionInputSingleSelectQuestion', discriminantValue: 'single-select' }, + { caseName: 'MultiSelect', structName: 'SessionInputMultiSelectQuestion', discriminantValue: 'multi-select' }, + ], +}; + +const SESSION_INPUT_ANSWER_VALUE_UNION: UnionConfig = { + name: 'SessionInputAnswerValue', + discriminantField: 'kind', + variants: [ + { caseName: 'Text', structName: 'SessionInputTextAnswerValue', discriminantValue: 'text' }, + { caseName: 'Number', structName: 'SessionInputNumberAnswerValue', discriminantValue: 'number' }, + { caseName: 'Boolean', structName: 'SessionInputBooleanAnswerValue', discriminantValue: 'boolean' }, + { caseName: 'Selected', structName: 'SessionInputSelectedAnswerValue', discriminantValue: 'selected' }, + { caseName: 'SelectedMany', structName: 'SessionInputSelectedManyAnswerValue', discriminantValue: 'selected-many' }, + ], +}; + +const SESSION_INPUT_ANSWER_UNION: UnionConfig = { + name: 'SessionInputAnswer', + discriminantField: 'state', + variants: [ + { caseName: 'Draft', structName: 'SessionInputAnswered', discriminantValue: 'draft' }, + { caseName: 'Submitted', structName: 'SessionInputAnswered', discriminantValue: 'submitted' }, + { caseName: 'Skipped', structName: 'SessionInputSkipped', discriminantValue: 'skipped' }, + ], +}; + +function generateToolResultContentUnion(): string { + return `// ToolResultContent is a discriminated union keyed on "type". +type ToolResultContent struct { +\tText *ToolResultTextContent +\tEmbeddedResource *ToolResultEmbeddedResourceContent +\tResource *ToolResultResourceContent +\tFileEdit *ToolResultFileEditContent +\tTerminal *ToolResultTerminalContent +\tSubagent *ToolResultSubagentContent +} + +func (u *ToolResultContent) UnmarshalJSON(data []byte) error { +\tvar disc struct { +\t\tType string \`json:"type"\` +\t} +\tif err := json.Unmarshal(data, &disc); err != nil { +\t\treturn err +\t} +\tswitch disc.Type { +\tcase "text": +\t\tu.Text = new(ToolResultTextContent) +\t\treturn json.Unmarshal(data, u.Text) +\tcase "embeddedResource": +\t\tu.EmbeddedResource = new(ToolResultEmbeddedResourceContent) +\t\treturn json.Unmarshal(data, u.EmbeddedResource) +\tcase "resource": +\t\tu.Resource = new(ToolResultResourceContent) +\t\treturn json.Unmarshal(data, u.Resource) +\tcase "fileEdit": +\t\tu.FileEdit = new(ToolResultFileEditContent) +\t\treturn json.Unmarshal(data, u.FileEdit) +\tcase "terminal": +\t\tu.Terminal = new(ToolResultTerminalContent) +\t\treturn json.Unmarshal(data, u.Terminal) +\tcase "subagent": +\t\tu.Subagent = new(ToolResultSubagentContent) +\t\treturn json.Unmarshal(data, u.Subagent) +\tdefault: +\t\treturn fmt.Errorf("unknown ToolResultContent type: %q", disc.Type) +\t} +} + +func (u ToolResultContent) MarshalJSON() ([]byte, error) { +\tif u.Text != nil { +\t\treturn json.Marshal(u.Text) +\t} +\tif u.EmbeddedResource != nil { +\t\treturn json.Marshal(u.EmbeddedResource) +\t} +\tif u.Resource != nil { +\t\treturn json.Marshal(u.Resource) +\t} +\tif u.FileEdit != nil { +\t\treturn json.Marshal(u.FileEdit) +\t} +\tif u.Terminal != nil { +\t\treturn json.Marshal(u.Terminal) +\t} +\tif u.Subagent != nil { +\t\treturn json.Marshal(u.Subagent) +\t} +\treturn nil, fmt.Errorf("empty ToolResultContent: no variant set") +}`; +} + +function generateStringOrMarkdown(): string { + return `// StringOrMarkdown represents a value that is either a plain string or +// a markdown-formatted string. +type StringOrMarkdown struct { +\tText *string // non-nil when the value is a plain string +\tMarkdown *string // non-nil when the value is markdown +} + +func (s *StringOrMarkdown) UnmarshalJSON(data []byte) error { +\t// Try plain string first +\tvar str string +\tif err := json.Unmarshal(data, &str); err == nil { +\t\ts.Text = &str +\t\treturn nil +\t} +\t// Try markdown object +\tvar obj struct { +\t\tMarkdown string \`json:"markdown"\` +\t} +\tif err := json.Unmarshal(data, &obj); err == nil { +\t\ts.Markdown = &obj.Markdown +\t\treturn nil +\t} +\treturn fmt.Errorf("StringOrMarkdown: cannot decode %s", string(data)) +} + +func (s StringOrMarkdown) MarshalJSON() ([]byte, error) { +\tif s.Markdown != nil { +\t\treturn json.Marshal(struct { +\t\t\tMarkdown string \`json:"markdown"\` +\t\t}{Markdown: *s.Markdown}) +\t} +\tif s.Text != nil { +\t\treturn json.Marshal(*s.Text) +\t} +\treturn json.Marshal(nil) +}`; +} + +function generateSnapshotState(): string { + return `// SnapshotState is the state payload of a snapshot — root, session, or terminal state. +type SnapshotState struct { +\tRoot *RootState +\tSession *SessionState +\tTerminal *TerminalState +} + +func (s *SnapshotState) UnmarshalJSON(data []byte) error { +\t// Peek at top-level fields to determine variant type +\tvar peek map[string]json.RawMessage +\tif err := json.Unmarshal(data, &peek); err != nil { +\t\treturn err +\t} +\t// SessionState has a required "summary" field +\tif _, ok := peek["summary"]; ok { +\t\ts.Session = new(SessionState) +\t\treturn json.Unmarshal(data, s.Session) +\t} +\t// TerminalState has "content" but not "agents" +\tif _, hasContent := peek["content"]; hasContent { +\t\tif _, hasAgents := peek["agents"]; !hasAgents { +\t\t\ts.Terminal = new(TerminalState) +\t\t\treturn json.Unmarshal(data, s.Terminal) +\t\t} +\t} +\t// Fall back to RootState +\ts.Root = new(RootState) +\treturn json.Unmarshal(data, s.Root) +} + +func (s SnapshotState) MarshalJSON() ([]byte, error) { +\tif s.Session != nil { +\t\treturn json.Marshal(s.Session) +\t} +\tif s.Terminal != nil { +\t\treturn json.Marshal(s.Terminal) +\t} +\tif s.Root != nil { +\t\treturn json.Marshal(s.Root) +\t} +\treturn nil, fmt.Errorf("empty SnapshotState: no variant set") +}`; +} + +function generateStateFile(project: Project): string { + const sf = project.getSourceFiles().find(f => f.getBaseName() === 'state.ts')!; + const lines: string[] = [ + GENERATED_HEADER, + 'import (', + '\t"encoding/json"', + '\t"fmt"', + ')', + '', + ]; + + lines.push('// ── Type Aliases ──────────────────────────────────────────────────────────────'); + lines.push(''); + lines.push('// URI is a string alias for URI values (e.g. "agenthost:/root").'); + lines.push('type URI = string'); + lines.push(''); + + lines.push('// ── StringOrMarkdown ──────────────────────────────────────────────────────────'); + lines.push(''); + lines.push(generateStringOrMarkdown()); + lines.push(''); + + lines.push('// ── Enums ────────────────────────────────────────────────────────────────────'); + lines.push(''); + for (const enumName of STATE_ENUMS) { + const decl = sf.getEnum(enumName); + if (decl) { + lines.push(generateGoEnum(decl)); + lines.push(''); + } + } + + lines.push('// ── State Types ──────────────────────────────────────────────────────────────'); + lines.push(''); + for (const ifaceName of STATE_STRUCTS) { + try { + lines.push(generateStructFromInterface(project, ifaceName)); + lines.push(''); + } catch (e) { + lines.push(`// TODO: Could not generate ${ifaceName}: ${e}`); + lines.push(''); + } + } + + lines.push('// ── Discriminated Unions ──────────────────────────────────────────────────────'); + lines.push(''); + lines.push(generateDiscriminatedUnion(RESPONSE_PART_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(TOOL_CALL_STATE_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(TERMINAL_CLAIM_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(TERMINAL_CONTENT_PART_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_QUESTION_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_VALUE_UNION)); + lines.push(''); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_UNION)); + lines.push(''); + lines.push(generateToolResultContentUnion()); + lines.push(''); + lines.push(generateSnapshotState()); + lines.push(''); + + return lines.join('\n'); +} + +// ─── Actions File Generator ────────────────────────────────────────────────── + +/** Action type discriminant values mapped to struct names */ +const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] = [ + { type: 'root/agentsChanged', caseName: 'RootAgentsChanged', tsInterface: 'IRootAgentsChangedAction' }, + { type: 'root/activeSessionsChanged', caseName: 'RootActiveSessionsChanged', tsInterface: 'IRootActiveSessionsChangedAction' }, + { type: 'session/ready', caseName: 'SessionReady', tsInterface: 'ISessionReadyAction' }, + { type: 'session/creationFailed', caseName: 'SessionCreationFailed', tsInterface: 'ISessionCreationFailedAction' }, + { type: 'session/turnStarted', caseName: 'SessionTurnStarted', tsInterface: 'ISessionTurnStartedAction' }, + { type: 'session/delta', caseName: 'SessionDelta', tsInterface: 'ISessionDeltaAction' }, + { type: 'session/responsePart', caseName: 'SessionResponsePart', tsInterface: 'ISessionResponsePartAction' }, + { type: 'session/toolCallStart', caseName: 'SessionToolCallStart', tsInterface: 'ISessionToolCallStartAction' }, + { type: 'session/toolCallDelta', caseName: 'SessionToolCallDelta', tsInterface: 'ISessionToolCallDeltaAction' }, + { type: 'session/toolCallReady', caseName: 'SessionToolCallReady', tsInterface: 'ISessionToolCallReadyAction' }, + { type: 'session/toolCallConfirmed', caseName: 'SessionToolCallConfirmed', tsInterface: '_merged_' }, + { type: 'session/toolCallComplete', caseName: 'SessionToolCallComplete', tsInterface: 'ISessionToolCallCompleteAction' }, + { type: 'session/toolCallResultConfirmed', caseName: 'SessionToolCallResultConfirmed', tsInterface: 'ISessionToolCallResultConfirmedAction' }, + { type: 'session/turnComplete', caseName: 'SessionTurnComplete', tsInterface: 'ISessionTurnCompleteAction' }, + { type: 'session/turnCancelled', caseName: 'SessionTurnCancelled', tsInterface: 'ISessionTurnCancelledAction' }, + { type: 'session/error', caseName: 'SessionError', tsInterface: 'ISessionErrorAction' }, + { type: 'session/titleChanged', caseName: 'SessionTitleChanged', tsInterface: 'ISessionTitleChangedAction' }, + { type: 'session/usage', caseName: 'SessionUsage', tsInterface: 'ISessionUsageAction' }, + { type: 'session/reasoning', caseName: 'SessionReasoning', tsInterface: 'ISessionReasoningAction' }, + { type: 'session/modelChanged', caseName: 'SessionModelChanged', tsInterface: 'ISessionModelChangedAction' }, + { type: 'session/isReadChanged', caseName: 'SessionIsReadChanged', tsInterface: 'ISessionIsReadChangedAction' }, + { type: 'session/isDoneChanged', caseName: 'SessionIsDoneChanged', tsInterface: 'ISessionIsDoneChangedAction' }, + { type: 'session/serverToolsChanged', caseName: 'SessionServerToolsChanged', tsInterface: 'ISessionServerToolsChangedAction' }, + { type: 'session/activeClientChanged', caseName: 'SessionActiveClientChanged', tsInterface: 'ISessionActiveClientChangedAction' }, + { type: 'session/activeClientToolsChanged', caseName: 'SessionActiveClientToolsChanged', tsInterface: 'ISessionActiveClientToolsChangedAction' }, + { type: 'session/pendingMessageSet', caseName: 'SessionPendingMessageSet', tsInterface: 'ISessionPendingMessageSetAction' }, + { type: 'session/pendingMessageRemoved', caseName: 'SessionPendingMessageRemoved', tsInterface: 'ISessionPendingMessageRemovedAction' }, + { type: 'session/queuedMessagesReordered', caseName: 'SessionQueuedMessagesReordered', tsInterface: 'ISessionQueuedMessagesReorderedAction' }, + { type: 'session/inputRequested', caseName: 'SessionInputRequested', tsInterface: 'ISessionInputRequestedAction' }, + { type: 'session/inputAnswerChanged', caseName: 'SessionInputAnswerChanged', tsInterface: 'ISessionInputAnswerChangedAction' }, + { type: 'session/inputCompleted', caseName: 'SessionInputCompleted', tsInterface: 'ISessionInputCompletedAction' }, + { type: 'session/customizationsChanged', caseName: 'SessionCustomizationsChanged', tsInterface: 'ISessionCustomizationsChangedAction' }, + { type: 'session/customizationToggled', caseName: 'SessionCustomizationToggled', tsInterface: 'ISessionCustomizationToggledAction' }, + { type: 'session/truncated', caseName: 'SessionTruncated', tsInterface: 'ISessionTruncatedAction' }, + { type: 'session/diffsChanged', caseName: 'SessionDiffsChanged', tsInterface: 'ISessionDiffsChangedAction' }, + { type: 'session/configChanged', caseName: 'SessionConfigChanged', tsInterface: 'ISessionConfigChangedAction' }, + { type: 'session/toolCallContentChanged', caseName: 'SessionToolCallContentChanged', tsInterface: 'ISessionToolCallContentChangedAction' }, + { type: 'root/terminalsChanged', caseName: 'RootTerminalsChanged', tsInterface: 'IRootTerminalsChangedAction' }, + { type: 'root/configChanged', caseName: 'RootConfigChanged', tsInterface: 'IRootConfigChangedAction' }, + { type: 'terminal/data', caseName: 'TerminalData', tsInterface: 'ITerminalDataAction' }, + { type: 'terminal/input', caseName: 'TerminalInput', tsInterface: 'ITerminalInputAction' }, + { type: 'terminal/resized', caseName: 'TerminalResized', tsInterface: 'ITerminalResizedAction' }, + { type: 'terminal/claimed', caseName: 'TerminalClaimed', tsInterface: 'ITerminalClaimedAction' }, + { type: 'terminal/titleChanged', caseName: 'TerminalTitleChanged', tsInterface: 'ITerminalTitleChangedAction' }, + { type: 'terminal/cwdChanged', caseName: 'TerminalCwdChanged', tsInterface: 'ITerminalCwdChangedAction' }, + { type: 'terminal/exited', caseName: 'TerminalExited', tsInterface: 'ITerminalExitedAction' }, + { type: 'terminal/cleared', caseName: 'TerminalCleared', tsInterface: 'ITerminalClearedAction' }, + { type: 'terminal/commandDetectionAvailable', caseName: 'TerminalCommandDetectionAvailable', tsInterface: 'ITerminalCommandDetectionAvailableAction' }, + { type: 'terminal/commandExecuted', caseName: 'TerminalCommandExecuted', tsInterface: 'ITerminalCommandExecutedAction' }, + { type: 'terminal/commandFinished', caseName: 'TerminalCommandFinished', tsInterface: 'ITerminalCommandFinishedAction' }, +]; + +/** Merged struct for the approved/denied tool call confirmed action */ +function generateMergedToolCallConfirmedStruct(): string { + return `// SessionToolCallConfirmedAction represents a client approving or denying a pending tool call. +type SessionToolCallConfirmedAction struct { +\tType string \`json:"type"\` +\tSession string \`json:"session"\` +\tTurnID string \`json:"turnId"\` +\tToolCallID string \`json:"toolCallId"\` +\tApproved bool \`json:"approved"\` +\tConfirmed *ToolCallConfirmationReason \`json:"confirmed,omitempty"\` +\tReason *ToolCallCancellationReason \`json:"reason,omitempty"\` +\tUserSuggestion *UserMessage \`json:"userSuggestion,omitempty"\` +\tReasonMessage *StringOrMarkdown \`json:"reasonMessage,omitempty"\` +\tMeta map[string]json.RawMessage \`json:"_meta,omitempty"\` +}`; +} + +function generateActionsFile(project: Project): string { + const sf = project.getSourceFiles().find(f => f.getBaseName() === 'actions.ts')!; + const lines: string[] = [ + GENERATED_HEADER, + 'import (', + '\t"encoding/json"', + '\t"fmt"', + ')', + '', + ]; + + // ActionType enum + lines.push('// ── ActionType ────────────────────────────────────────────────────────────────'); + lines.push(''); + const actionTypeEnum = sf.getEnum('ActionType'); + if (actionTypeEnum) { + lines.push(generateGoEnum(actionTypeEnum)); + lines.push(''); + } + + // ActionEnvelope and ActionOrigin + lines.push('// ── Action Infrastructure ─────────────────────────────────────────────────────'); + lines.push(''); + lines.push(generateStructFromInterface(project, 'IActionOrigin')); + lines.push(''); + lines.push(generateStructFromInterface(project, 'IActionEnvelope')); + lines.push(''); + + // Individual action structs + lines.push('// ── Action Types ─────────────────────────────────────────────────────────────'); + lines.push(''); + for (const variant of ACTION_VARIANTS) { + if (variant.tsInterface === '_merged_') { + lines.push(generateMergedToolCallConfirmedStruct()); + lines.push(''); + continue; + } + try { + lines.push(generateStructFromInterface(project, variant.tsInterface)); + lines.push(''); + } catch (e) { + lines.push(`// TODO: Could not generate ${variant.tsInterface}: ${e}`); + lines.push(''); + } + } + + // StateAction discriminated union + lines.push('// ── StateAction Union ─────────────────────────────────────────────────────────'); + lines.push(''); + lines.push('// StateAction is a discriminated union of all state actions.'); + lines.push('type StateAction struct {'); + for (const v of ACTION_VARIANTS) { + const structName = v.tsInterface === '_merged_' + ? 'SessionToolCallConfirmedAction' + : goTypeName(v.tsInterface); + lines.push(`\t${v.caseName} *${structName}`); + } + lines.push(`\tUnknownType string`); + lines.push(`\tUnknownRaw json.RawMessage`); + lines.push('}'); + lines.push(''); + + // StateAction UnmarshalJSON + lines.push('func (u *StateAction) UnmarshalJSON(data []byte) error {'); + lines.push('\tvar disc struct {'); + lines.push('\t\tType string `json:"type"`'); + lines.push('\t}'); + lines.push('\tif err := json.Unmarshal(data, &disc); err != nil {'); + lines.push('\t\treturn err'); + lines.push('\t}'); + lines.push('\tswitch disc.Type {'); + for (const v of ACTION_VARIANTS) { + const structName = v.tsInterface === '_merged_' + ? 'SessionToolCallConfirmedAction' + : goTypeName(v.tsInterface); + lines.push(`\tcase ${JSON.stringify(v.type)}:`); + lines.push(`\t\tu.${v.caseName} = new(${structName})`); + lines.push(`\t\treturn json.Unmarshal(data, u.${v.caseName})`); + } + lines.push('\tdefault:'); + lines.push('\t\tu.UnknownType = disc.Type'); + lines.push('\t\tu.UnknownRaw = make(json.RawMessage, len(data))'); + lines.push('\t\tcopy(u.UnknownRaw, data)'); + lines.push('\t\treturn nil'); + lines.push('\t}'); + lines.push('}'); + lines.push(''); + + // StateAction MarshalJSON + lines.push('func (u StateAction) MarshalJSON() ([]byte, error) {'); + for (const v of ACTION_VARIANTS) { + lines.push(`\tif u.${v.caseName} != nil {`); + lines.push(`\t\treturn json.Marshal(u.${v.caseName})`); + lines.push(`\t}`); + } + lines.push('\tif u.UnknownRaw != nil {'); + lines.push('\t\treturn u.UnknownRaw, nil'); + lines.push('\t}'); + lines.push('\treturn nil, fmt.Errorf("empty StateAction: no variant set")'); + lines.push('}'); + lines.push(''); + + return lines.join('\n'); +} + +// ─── Commands File Generator ───────────────────────────────────────────────── + +const COMMAND_ENUMS = ['ReconnectResultType', 'ContentEncoding']; + +const COMMAND_STRUCTS = [ + 'IInitializeParams', 'IInitializeResult', + 'IReconnectParams', 'IReconnectReplayResult', 'IReconnectSnapshotResult', + 'ISubscribeParams', 'ISubscribeResult', + 'ISessionForkSource', 'ICreateSessionParams', 'IDisposeSessionParams', + 'IListSessionsParams', 'IListSessionsResult', + 'IResourceReadParams', 'IResourceReadResult', + 'IResourceWriteParams', 'IResourceWriteResult', + 'IResourceListParams', 'IResourceListResult', 'IDirectoryEntry', + 'IResourceCopyParams', 'IResourceCopyResult', + 'IResourceDeleteParams', 'IResourceDeleteResult', + 'IResourceMoveParams', 'IResourceMoveResult', + 'IFetchTurnsParams', 'IFetchTurnsResult', + 'IUnsubscribeParams', 'IDispatchActionParams', + 'IAuthenticateParams', 'IAuthenticateResult', + 'ICreateTerminalParams', 'IDisposeTerminalParams', + 'IResolveSessionConfigParams', 'IResolveSessionConfigResult', + 'ISessionConfigPropertySchema', 'ISessionConfigSchema', + 'ISessionConfigCompletionsParams', 'ISessionConfigCompletionsResult', + 'ISessionConfigValueItem', +]; + +const RECONNECT_RESULT_UNION: UnionConfig = { + name: 'ReconnectResult', + discriminantField: 'type', + variants: [ + { caseName: 'Replay', structName: 'ReconnectReplayResult', discriminantValue: 'replay' }, + { caseName: 'Snapshot', structName: 'ReconnectSnapshotResult', discriminantValue: 'snapshot' }, + ], +}; + +function generateCommandsFile(project: Project): string { + const sf = project.getSourceFiles().find(f => f.getBaseName() === 'commands.ts')!; + const lines: string[] = [ + GENERATED_HEADER, + 'import (', + '\t"encoding/json"', + '\t"fmt"', + ')', + '', + ]; + + lines.push('// ── Command Enums ─────────────────────────────────────────────────────────────'); + lines.push(''); + for (const enumName of COMMAND_ENUMS) { + const decl = sf.getEnum(enumName); + if (decl) { + lines.push(generateGoEnum(decl)); + lines.push(''); + } + } + + lines.push('// ── Command Types ─────────────────────────────────────────────────────────────'); + lines.push(''); + const generated = new Set(); + for (const ifaceName of COMMAND_STRUCTS) { + if (generated.has(ifaceName)) continue; + generated.add(ifaceName); + try { + lines.push(generateStructFromInterface(project, ifaceName)); + lines.push(''); + } catch (e) { + lines.push(`// TODO: Could not generate ${ifaceName}: ${e}`); + lines.push(''); + } + } + + lines.push('// ── ReconnectResult Union ─────────────────────────────────────────────────────'); + lines.push(''); + lines.push(generateDiscriminatedUnion(RECONNECT_RESULT_UNION)); + lines.push(''); + + return lines.join('\n'); +} + +// ─── Notifications File Generator ──────────────────────────────────────────── + +const NOTIFICATION_ENUMS = ['AuthRequiredReason', 'NotificationType']; + +const NOTIFICATION_STRUCTS = [ + 'ISessionAddedNotification', 'ISessionRemovedNotification', + 'ISessionSummaryChangedNotification', 'IAuthRequiredNotification', +]; + +const PROTOCOL_NOTIFICATION_UNION: UnionConfig = { + name: 'ProtocolNotification', + discriminantField: 'type', + variants: [ + { caseName: 'SessionAdded', structName: 'SessionAddedNotification', discriminantValue: 'notify/sessionAdded' }, + { caseName: 'SessionRemoved', structName: 'SessionRemovedNotification', discriminantValue: 'notify/sessionRemoved' }, + { caseName: 'SessionSummaryChanged', structName: 'SessionSummaryChangedNotification', discriminantValue: 'notify/sessionSummaryChanged' }, + { caseName: 'AuthRequired', structName: 'AuthRequiredNotification', discriminantValue: 'notify/authRequired' }, + ], +}; + +function generateNotificationsFile(project: Project): string { + const sf = project.getSourceFiles().find(f => f.getBaseName() === 'notifications.ts')!; + const lines: string[] = [ + GENERATED_HEADER, + 'import (', + '\t"encoding/json"', + '\t"fmt"', + ')', + '', + ]; + + lines.push('// ── Notification Enums ────────────────────────────────────────────────────────'); + lines.push(''); + for (const enumName of NOTIFICATION_ENUMS) { + const decl = sf.getEnum(enumName); + if (decl) { + lines.push(generateGoEnum(decl)); + lines.push(''); + } + } + + // Track partials introduced by notification structs + const priorPartials = new Set(requiredPartialStructs); + + lines.push('// ── Notification Types ────────────────────────────────────────────────────────'); + lines.push(''); + for (const ifaceName of NOTIFICATION_STRUCTS) { + try { + lines.push(generateStructFromInterface(project, ifaceName)); + lines.push(''); + } catch (e) { + lines.push(`// TODO: Could not generate ${ifaceName}: ${e}`); + lines.push(''); + } + } + + const newPartials = [...requiredPartialStructs].filter(n => !priorPartials.has(n)); + if (newPartials.length > 0) { + lines.push('// ── Partial Summary Types ─────────────────────────────────────────────────────'); + lines.push(''); + for (const tsName of newPartials) { + try { + lines.push(generatePartialStructFromInterface(project, tsName)); + lines.push(''); + } catch (e) { + lines.push(`// TODO: Could not generate Partial<${tsName}>: ${e}`); + lines.push(''); + } + } + } + + lines.push('// ── ProtocolNotification Union ────────────────────────────────────────────────'); + lines.push(''); + lines.push(generateDiscriminatedUnion(PROTOCOL_NOTIFICATION_UNION)); + lines.push(''); + + return lines.join('\n'); +} + +// ─── Errors File Generator ─────────────────────────────────────────────────── + +function generateErrorsFile(): string { + const lines: string[] = [ + GENERATED_HEADER, + ]; + + lines.push('// ── Standard JSON-RPC Error Codes ─────────────────────────────────────────────'); + lines.push(''); + lines.push('const ('); + lines.push('\t// JSONRPCParseError indicates invalid JSON.'); + lines.push('\tJSONRPCParseError = -32700'); + lines.push('\t// JSONRPCInvalidRequest indicates a malformed JSON-RPC request.'); + lines.push('\tJSONRPCInvalidRequest = -32600'); + lines.push('\t// JSONRPCMethodNotFound indicates an unknown method name.'); + lines.push('\tJSONRPCMethodNotFound = -32601'); + lines.push('\t// JSONRPCInvalidParams indicates invalid method parameters.'); + lines.push('\tJSONRPCInvalidParams = -32602'); + lines.push('\t// JSONRPCInternalError indicates an unspecified server error.'); + lines.push('\tJSONRPCInternalError = -32603'); + lines.push(')'); + lines.push(''); + + lines.push('// ── AHP Application Error Codes ───────────────────────────────────────────────'); + lines.push(''); + lines.push('const ('); + lines.push('\t// AHPSessionNotFound indicates the referenced session URI does not exist.'); + lines.push('\tAHPSessionNotFound = -32001'); + lines.push('\t// AHPProviderNotFound indicates the requested agent provider is not registered.'); + lines.push('\tAHPProviderNotFound = -32002'); + lines.push('\t// AHPSessionAlreadyExists indicates a session with the given URI already exists.'); + lines.push('\tAHPSessionAlreadyExists = -32003'); + lines.push('\t// AHPTurnInProgress indicates the operation requires no active turn, but one is in progress.'); + lines.push('\tAHPTurnInProgress = -32004'); + lines.push('\t// AHPUnsupportedProtocolVersion indicates the client\'s protocol version is not supported.'); + lines.push('\tAHPUnsupportedProtocolVersion = -32005'); + lines.push('\t// AHPContentNotFound indicates the requested content URI does not exist.'); + lines.push('\tAHPContentNotFound = -32006'); + lines.push('\t// AHPAuthRequired indicates authentication is required for a protected resource.'); + lines.push('\tAHPAuthRequired = -32007'); + lines.push('\t// AHPNotFound indicates the requested file, folder, or URI does not exist.'); + lines.push('\tAHPNotFound = -32008'); + lines.push('\t// AHPPermissionDenied indicates the client is not permitted to access the requested resource.'); + lines.push('\tAHPPermissionDenied = -32009'); + lines.push('\t// AHPAlreadyExists indicates the target resource already exists and overwriting is not allowed.'); + lines.push('\tAHPAlreadyExists = -32010'); + lines.push(')'); + lines.push(''); + + return lines.join('\n'); +} + +// ─── Messages File Generator ───────────────────────────────────────────────── + +function generateMessagesFile(): string { + return `${GENERATED_HEADER} +import "encoding/json" + +// ── JSON-RPC Base Types ────────────────────────────────────────────────────── + +// JSONRPCRequest is a JSON-RPC 2.0 request with typed params. +type JSONRPCRequest[T any] struct { +\tJSONRPC string \`json:"jsonrpc"\` +\tID int \`json:"id"\` +\tMethod string \`json:"method"\` +\tParams T \`json:"params"\` +} + +// NewJSONRPCRequest creates a new JSON-RPC 2.0 request. +func NewJSONRPCRequest[T any](id int, method string, params T) JSONRPCRequest[T] { +\treturn JSONRPCRequest[T]{ +\t\tJSONRPC: "2.0", +\t\tID: id, +\t\tMethod: method, +\t\tParams: params, +\t} +} + +// JSONRPCError is a JSON-RPC 2.0 error object. +type JSONRPCError struct { +\tCode int \`json:"code"\` +\tMessage string \`json:"message"\` +\tData *json.RawMessage \`json:"data,omitempty"\` +} + +// JSONRPCSuccessResponse is a JSON-RPC 2.0 success response with typed result. +type JSONRPCSuccessResponse[T any] struct { +\tJSONRPC string \`json:"jsonrpc"\` +\tID int \`json:"id"\` +\tResult T \`json:"result"\` +} + +// JSONRPCErrorResponse is a JSON-RPC 2.0 error response. +type JSONRPCErrorResponse struct { +\tJSONRPC string \`json:"jsonrpc"\` +\tID int \`json:"id"\` +\tError JSONRPCError \`json:"error"\` +} + +// JSONRPCNotification is a JSON-RPC 2.0 notification (no id) with typed params. +type JSONRPCNotification[T any] struct { +\tJSONRPC string \`json:"jsonrpc"\` +\tMethod string \`json:"method"\` +\tParams T \`json:"params"\` +} + +// NewJSONRPCNotification creates a new JSON-RPC 2.0 notification. +func NewJSONRPCNotification[T any](method string, params T) JSONRPCNotification[T] { +\treturn JSONRPCNotification[T]{ +\t\tJSONRPC: "2.0", +\t\tMethod: method, +\t\tParams: params, +\t} +} + +// ── Server → Client Notification Params ────────────────────────────────────── + +// ActionNotificationParams is the params for the server → client action notification. +type ActionNotificationParams = ActionEnvelope + +// NotificationMethodParams is the params for the server → client notification method. +type NotificationMethodParams struct { +\tNotification ProtocolNotification \`json:"notification"\` +} + +// ── AHP Command Helpers ────────────────────────────────────────────────────── + +// NewInitializeRequest creates an initialize JSON-RPC request. +func NewInitializeRequest(id int, params InitializeParams) JSONRPCRequest[InitializeParams] { +\treturn NewJSONRPCRequest(id, "initialize", params) +} + +// NewReconnectRequest creates a reconnect JSON-RPC request. +func NewReconnectRequest(id int, params ReconnectParams) JSONRPCRequest[ReconnectParams] { +\treturn NewJSONRPCRequest(id, "reconnect", params) +} + +// NewSubscribeRequest creates a subscribe JSON-RPC request. +func NewSubscribeRequest(id int, params SubscribeParams) JSONRPCRequest[SubscribeParams] { +\treturn NewJSONRPCRequest(id, "subscribe", params) +} + +// NewCreateSessionRequest creates a createSession JSON-RPC request. +func NewCreateSessionRequest(id int, params CreateSessionParams) JSONRPCRequest[CreateSessionParams] { +\treturn NewJSONRPCRequest(id, "createSession", params) +} + +// NewDisposeSessionRequest creates a disposeSession JSON-RPC request. +func NewDisposeSessionRequest(id int, params DisposeSessionParams) JSONRPCRequest[DisposeSessionParams] { +\treturn NewJSONRPCRequest(id, "disposeSession", params) +} + +// NewListSessionsRequest creates a listSessions JSON-RPC request. +func NewListSessionsRequest(id int, params ListSessionsParams) JSONRPCRequest[ListSessionsParams] { +\treturn NewJSONRPCRequest(id, "listSessions", params) +} + +// NewResourceReadRequest creates a resourceRead JSON-RPC request. +func NewResourceReadRequest(id int, params ResourceReadParams) JSONRPCRequest[ResourceReadParams] { +\treturn NewJSONRPCRequest(id, "resourceRead", params) +} + +// NewResourceWriteRequest creates a resourceWrite JSON-RPC request. +func NewResourceWriteRequest(id int, params ResourceWriteParams) JSONRPCRequest[ResourceWriteParams] { +\treturn NewJSONRPCRequest(id, "resourceWrite", params) +} + +// NewResourceListRequest creates a resourceList JSON-RPC request. +func NewResourceListRequest(id int, params ResourceListParams) JSONRPCRequest[ResourceListParams] { +\treturn NewJSONRPCRequest(id, "resourceList", params) +} + +// NewResourceCopyRequest creates a resourceCopy JSON-RPC request. +func NewResourceCopyRequest(id int, params ResourceCopyParams) JSONRPCRequest[ResourceCopyParams] { +\treturn NewJSONRPCRequest(id, "resourceCopy", params) +} + +// NewResourceDeleteRequest creates a resourceDelete JSON-RPC request. +func NewResourceDeleteRequest(id int, params ResourceDeleteParams) JSONRPCRequest[ResourceDeleteParams] { +\treturn NewJSONRPCRequest(id, "resourceDelete", params) +} + +// NewResourceMoveRequest creates a resourceMove JSON-RPC request. +func NewResourceMoveRequest(id int, params ResourceMoveParams) JSONRPCRequest[ResourceMoveParams] { +\treturn NewJSONRPCRequest(id, "resourceMove", params) +} + +// NewFetchTurnsRequest creates a fetchTurns JSON-RPC request. +func NewFetchTurnsRequest(id int, params FetchTurnsParams) JSONRPCRequest[FetchTurnsParams] { +\treturn NewJSONRPCRequest(id, "fetchTurns", params) +} + +// NewAuthenticateRequest creates an authenticate JSON-RPC request. +func NewAuthenticateRequest(id int, params AuthenticateParams) JSONRPCRequest[AuthenticateParams] { +\treturn NewJSONRPCRequest(id, "authenticate", params) +} + +// ── AHP Client Notification Helpers ────────────────────────────────────────── + +// NewUnsubscribeNotification creates an unsubscribe JSON-RPC notification. +func NewUnsubscribeNotification(params UnsubscribeParams) JSONRPCNotification[UnsubscribeParams] { +\treturn NewJSONRPCNotification("unsubscribe", params) +} + +// NewDispatchActionNotification creates a dispatchAction JSON-RPC notification. +func NewDispatchActionNotification(params DispatchActionParams) JSONRPCNotification[DispatchActionParams] { +\treturn NewJSONRPCNotification("dispatchAction", params) +} +`; +} + +// ─── go.mod ────────────────────────────────────────────────────────────────── + +function goModContent(): string { + return `module github.com/microsoft/agent-host-protocol/examples/go/ahp + +go 1.21 +`; +} + +// ─── Exhaustiveness Check ──────────────────────────────────────────────────── + +/** + * Verifies that every type imported in types/version/v1.ts from the protocol + * source modules is covered by one of the generator lists or a known + * special-cased code path. + */ +function checkExhaustiveness(project: Project): void { + const v1 = project.getSourceFiles().find(f => f.getBaseName() === 'v1.ts'); + if (!v1) throw new Error('Could not find types/version/v1.ts in the project'); + + const protocolModules = new Set(['state', 'actions', 'commands', 'notifications', 'errors']); + const imported = new Set(); + for (const decl of v1.getImportDeclarations()) { + const mod = decl.getModuleSpecifierValue().replace(/^\.\.\//, '').replace(/\.js$/, ''); + if (!protocolModules.has(mod)) continue; + for (const named of decl.getNamedImports()) { + imported.add(named.getName()); + } + } + + const coveredByLists = new Set([ + ...STATE_STRUCTS, + ...STATE_ENUMS, + ...COMMAND_STRUCTS, + ...COMMAND_ENUMS, + ...NOTIFICATION_STRUCTS, + ...NOTIFICATION_ENUMS, + ...ACTION_VARIANTS + .filter(v => v.tsInterface !== '_merged_') + .map(v => v.tsInterface), + ]); + + const knownSpecial = new Set([ + 'StringOrMarkdown', // generateStringOrMarkdown() + 'IToolCallState', // TOOL_CALL_STATE_UNION discriminated union + 'IStateAction', // StateAction in generateActionsFile() + 'IActionEnvelope', // generateStructFromInterface() in generateActionsFile() + 'IActionOrigin', // generateStructFromInterface() in generateActionsFile() + 'ISessionToolCallApprovedAction', // merged into SessionToolCallConfirmedAction + 'ISessionToolCallDeniedAction', // merged into SessionToolCallConfirmedAction + 'IProtocolNotification', // PROTOCOL_NOTIFICATION_UNION discriminated union + 'ITerminalClaim', // TERMINAL_CLAIM_UNION discriminated union + 'ITerminalContentPart', // TERMINAL_CONTENT_PART_UNION discriminated union + 'ISessionInputQuestion', // SESSION_INPUT_QUESTION_UNION discriminated union + 'ISessionInputAnswerValue', // SESSION_INPUT_ANSWER_VALUE_UNION discriminated union + 'ISessionInputAnswer', // SESSION_INPUT_ANSWER_UNION discriminated union + ]); + + const missing = [...imported].filter(n => !coveredByLists.has(n) && !knownSpecial.has(n)); + if (missing.length > 0) { + throw new Error( + `generate-go.ts exhaustiveness check failed.\n` + + `The following types are declared in types/version/v1.ts but are not covered by the Go generator:\n` + + missing.map(n => ` - ${n}`).join('\n') + '\n\n' + + `Add them to the appropriate list in scripts/generate-go.ts:\n` + + ` STATE_STRUCTS / STATE_ENUMS, COMMAND_STRUCTS / COMMAND_ENUMS,\n` + + ` NOTIFICATION_STRUCTS / NOTIFICATION_ENUMS, ACTION_VARIANTS,\n` + + ` or knownSpecial if they are generated via a non-list code path.` + ); + } +} + +// ─── Main Entry Point ──────────────────────────────────────────────────────── + +export function generateGoModule(project: Project, outputDir: string): void { + checkExhaustiveness(project); + + fs.mkdirSync(outputDir, { recursive: true }); + + // go.mod is only written if it doesn't exist yet, so hand-edits are preserved. + const modPath = path.join(outputDir, 'go.mod'); + if (!fs.existsSync(modPath)) { + fs.writeFileSync(modPath, goModContent()); + } + + // Generated files (always overwritten) + fs.writeFileSync(path.join(outputDir, 'state_generated.go'), generateStateFile(project)); + fs.writeFileSync(path.join(outputDir, 'actions_generated.go'), generateActionsFile(project)); + fs.writeFileSync(path.join(outputDir, 'commands_generated.go'), generateCommandsFile(project)); + fs.writeFileSync(path.join(outputDir, 'notifications_generated.go'), generateNotificationsFile(project)); + fs.writeFileSync(path.join(outputDir, 'errors_generated.go'), generateErrorsFile()); + fs.writeFileSync(path.join(outputDir, 'messages_generated.go'), generateMessagesFile()); +} diff --git a/scripts/generate.ts b/scripts/generate.ts index 4333c3c2..0701fcae 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -11,6 +11,7 @@ import { generateMarkdownDocs } from './generate-markdown.js'; import { generateJsonSchemas } from './generate-json-schema.js'; import { generateActionOrigin } from './generate-action-origin.js'; import { generateSwiftPackage } from './generate-swift.js'; +import { generateGoModule } from './generate-go.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..'); @@ -19,13 +20,15 @@ const DOCS_DIR = path.join(ROOT, 'docs', 'reference'); const SCHEMA_DIR = path.join(ROOT, 'schema'); const SCHEMA_PUBLIC_DIR = path.join(ROOT, 'docs', 'public', 'schema'); const SWIFT_DIR = path.join(ROOT, 'examples', 'swift', 'AgentHostProtocol'); +const GO_DIR = path.join(ROOT, 'examples', 'go', 'ahp'); const args = process.argv.slice(2); const docsOnly = args.includes('--docs'); const schemaOnly = args.includes('--schema'); const actionOriginOnly = args.includes('--action-origin'); const swiftOnly = args.includes('--swift'); -const generateAll = !docsOnly && !schemaOnly && !actionOriginOnly && !swiftOnly; +const goOnly = args.includes('--go'); +const generateAll = !docsOnly && !schemaOnly && !actionOriginOnly && !swiftOnly && !goOnly; // Load the TypeScript project const project = new Project({ @@ -63,4 +66,10 @@ if (generateAll || swiftOnly) { console.log(` → Swift package written to ${path.relative(ROOT, SWIFT_DIR)}/`); } +if (generateAll || goOnly) { + console.log('Generating Go module...'); + generateGoModule(project, GO_DIR); + console.log(` → Go module written to ${path.relative(ROOT, GO_DIR)}/`); +} + console.log('Done.'); From a4e57f52a909aec37afce9b9a499d211d2dffaa8 Mon Sep 17 00:00:00 2001 From: Colby Williams Date: Wed, 22 Apr 2026 10:41:05 -0500 Subject: [PATCH 2/2] Regenerate Go output after main merge Picks up IConfigPropertySchema changes from main: expanded type union, optional enum, new items/properties/required fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/go/ahp/commands_generated.go | 14 ++++++++++---- examples/go/ahp/state_generated.go | 16 +++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/examples/go/ahp/commands_generated.go b/examples/go/ahp/commands_generated.go index c71e8e32..f3a81125 100644 --- a/examples/go/ahp/commands_generated.go +++ b/examples/go/ahp/commands_generated.go @@ -328,22 +328,28 @@ type ResolveSessionConfigResult struct { // SessionConfigPropertySchema A session configuration property descriptor. type SessionConfigPropertySchema struct { - // JSON Schema: property type. Only string enum properties are currently supported. + // JSON Schema: property type Type string `json:"type"` // JSON Schema: human-readable label for the property Title string `json:"title"` // JSON Schema: description / tooltip Description *string `json:"description,omitempty"` // JSON Schema: default value - Default *string `json:"default,omitempty"` - // JSON Schema: allowed values - Enum []string `json:"enum"` + Default json.RawMessage `json:"default,omitempty"` + // JSON Schema: allowed values (typically used with `string` type) + Enum []string `json:"enum,omitempty"` // Display extension: human-readable label per enum value (parallel array) EnumLabels []string `json:"enumLabels,omitempty"` // Display extension: description per enum value (parallel array) EnumDescriptions []string `json:"enumDescriptions,omitempty"` // JSON Schema: when `true`, the property is displayed but cannot be modified by the user ReadOnly *bool `json:"readOnly,omitempty"` + // JSON Schema: schema for array items (used when `type` is `'array'`) + Items *ConfigPropertySchema `json:"items,omitempty"` + // JSON Schema: property descriptors for object properties (used when `type` is `'object'`) + Properties map[string]ConfigPropertySchema `json:"properties,omitempty"` + // JSON Schema: list of required property ids (used when `type` is `'object'`) + Required []string `json:"required,omitempty"` // Display extension: when `true`, the full set of allowed values is too large // to enumerate statically. The client SHOULD use `sessionConfigCompletions` // to fetch matching values based on user input. Any values in `enum` are diff --git a/examples/go/ahp/state_generated.go b/examples/go/ahp/state_generated.go index 59c2509c..e7f2e8a0 100644 --- a/examples/go/ahp/state_generated.go +++ b/examples/go/ahp/state_generated.go @@ -379,24 +379,30 @@ type ModelSelection struct { Config map[string]string `json:"config,omitempty"` } -// ConfigPropertySchema A JSON Schema-compatible string enum property descriptor with display extensions. +// ConfigPropertySchema A JSON Schema-compatible property descriptor with display extensions. type ConfigPropertySchema struct { - // JSON Schema: property type. Only string enum properties are currently supported. + // JSON Schema: property type Type string `json:"type"` // JSON Schema: human-readable label for the property Title string `json:"title"` // JSON Schema: description / tooltip Description *string `json:"description,omitempty"` // JSON Schema: default value - Default *string `json:"default,omitempty"` - // JSON Schema: allowed values - Enum []string `json:"enum"` + Default json.RawMessage `json:"default,omitempty"` + // JSON Schema: allowed values (typically used with `string` type) + Enum []string `json:"enum,omitempty"` // Display extension: human-readable label per enum value (parallel array) EnumLabels []string `json:"enumLabels,omitempty"` // Display extension: description per enum value (parallel array) EnumDescriptions []string `json:"enumDescriptions,omitempty"` // JSON Schema: when `true`, the property is displayed but cannot be modified by the user ReadOnly *bool `json:"readOnly,omitempty"` + // JSON Schema: schema for array items (used when `type` is `'array'`) + Items *ConfigPropertySchema `json:"items,omitempty"` + // JSON Schema: property descriptors for object properties (used when `type` is `'object'`) + Properties map[string]ConfigPropertySchema `json:"properties,omitempty"` + // JSON Schema: list of required property ids (used when `type` is `'object'`) + Required []string `json:"required,omitempty"` } // ConfigSchema A JSON Schema object describing available configuration properties.