diff --git a/dotnet/README.md b/dotnet/README.md index c63bb120..f7184607 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -68,6 +68,8 @@ new CopilotClient(CopilotClientOptions? options = null) - `Cwd` - Working directory for the CLI process - `Environment` - Environment variables to pass to the CLI process - `Logger` - `ILogger` instance for SDK logging +- `GithubToken` - GitHub token for authentication. When provided, takes priority over other auth methods. +- `UseLoggedInUser` - Whether to use logged-in user for authentication (default: true, but false when `GithubToken` is provided). Cannot be used with `CliUrl`. #### Methods @@ -98,6 +100,8 @@ Create a new conversation session. - `Provider` - Custom API provider configuration (BYOK) - `Streaming` - Enable streaming of response chunks (default: false) - `InfiniteSessions` - Configure automatic context compaction (see below) +- `OnUserInputRequest` - Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. +- `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. ##### `ResumeSessionAsync(string sessionId, ResumeSessionConfig? config = null): Task` @@ -444,6 +448,118 @@ var session = await client.CreateSessionAsync(new SessionConfig }); ``` +## User Input Requests + +Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler: + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + OnUserInputRequest = async (request, invocation) => + { + // request.Question - The question to ask + // request.Choices - Optional list of choices for multiple choice + // request.AllowFreeform - Whether freeform input is allowed (default: true) + + Console.WriteLine($"Agent asks: {request.Question}"); + if (request.Choices?.Count > 0) + { + Console.WriteLine($"Choices: {string.Join(", ", request.Choices)}"); + } + + // Return the user's response + return new UserInputResponse + { + Answer = "User's answer here", + WasFreeform = true // Whether the answer was freeform (not from choices) + }; + } +}); +``` + +## Session Hooks + +Hook into session lifecycle events by providing handlers in the `Hooks` configuration: + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + Hooks = new SessionHooks + { + // Called before each tool execution + OnPreToolUse = async (input, invocation) => + { + Console.WriteLine($"About to run tool: {input.ToolName}"); + // Return permission decision and optionally modify args + return new PreToolUseHookOutput + { + PermissionDecision = "allow", // "allow", "deny", or "ask" + ModifiedArgs = input.ToolArgs, // Optionally modify tool arguments + AdditionalContext = "Extra context for the model" + }; + }, + + // Called after each tool execution + OnPostToolUse = async (input, invocation) => + { + Console.WriteLine($"Tool {input.ToolName} completed"); + return new PostToolUseHookOutput + { + AdditionalContext = "Post-execution notes" + }; + }, + + // Called when user submits a prompt + OnUserPromptSubmitted = async (input, invocation) => + { + Console.WriteLine($"User prompt: {input.Prompt}"); + return new UserPromptSubmittedHookOutput + { + ModifiedPrompt = input.Prompt // Optionally modify the prompt + }; + }, + + // Called when session starts + OnSessionStart = async (input, invocation) => + { + Console.WriteLine($"Session started from: {input.Source}"); // "startup", "resume", "new" + return new SessionStartHookOutput + { + AdditionalContext = "Session initialization context" + }; + }, + + // Called when session ends + OnSessionEnd = async (input, invocation) => + { + Console.WriteLine($"Session ended: {input.Reason}"); + return null; + }, + + // Called when an error occurs + OnErrorOccurred = async (input, invocation) => + { + Console.WriteLine($"Error in {input.ErrorContext}: {input.Error}"); + return new ErrorOccurredHookOutput + { + ErrorHandling = "retry" // "retry", "skip", or "abort" + }; + } + } +}); +``` + +**Available hooks:** + +- `OnPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments. +- `OnPostToolUse` - Process tool results after execution. Can modify results or add context. +- `OnUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing. +- `OnSessionStart` - Run logic when a session starts or resumes. +- `OnSessionEnd` - Cleanup or logging when session ends. +- `OnErrorOccurred` - Handle errors with retry/skip/abort strategies. + ## Error Handling ```csharp diff --git a/go/README.md b/go/README.md index 883bf74e..9ea16c74 100644 --- a/go/README.md +++ b/go/README.md @@ -96,6 +96,8 @@ func main() { - `AutoStart` (\*bool): Auto-start server on first use (default: true). Use `Bool(false)` to disable. - `AutoRestart` (\*bool): Auto-restart on crash (default: true). Use `Bool(false)` to disable. - `Env` ([]string): Environment variables for CLI process (default: inherits from current process) +- `GithubToken` (string): GitHub token for authentication. When provided, takes priority over other auth methods. +- `UseLoggedInUser` (\*bool): Whether to use logged-in user for authentication (default: true, but false when `GithubToken` is provided). Cannot be used with `CLIUrl`. **SessionConfig:** @@ -106,6 +108,8 @@ func main() { - `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `Streaming` (bool): Enable streaming delta events - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration +- `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. +- `Hooks` (\*SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. **ResumeSessionConfig:** @@ -397,6 +401,101 @@ session, err := client.CreateSession(&copilot.SessionConfig{ > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `Type: "azure"`, not `Type: "openai"`. > - The `BaseURL` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically. +## User Input Requests + +Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler: + +```go +session, err := client.CreateSession(&copilot.SessionConfig{ + Model: "gpt-5", + OnUserInputRequest: func(request copilot.UserInputRequest, invocation copilot.UserInputInvocation) (copilot.UserInputResponse, error) { + // request.Question - The question to ask + // request.Choices - Optional slice of choices for multiple choice + // request.AllowFreeform - Whether freeform input is allowed (default: true) + + fmt.Printf("Agent asks: %s\n", request.Question) + if len(request.Choices) > 0 { + fmt.Printf("Choices: %v\n", request.Choices) + } + + // Return the user's response + return copilot.UserInputResponse{ + Answer: "User's answer here", + WasFreeform: true, // Whether the answer was freeform (not from choices) + }, nil + }, +}) +``` + +## Session Hooks + +Hook into session lifecycle events by providing handlers in the `Hooks` configuration: + +```go +session, err := client.CreateSession(&copilot.SessionConfig{ + Model: "gpt-5", + Hooks: &copilot.SessionHooks{ + // Called before each tool execution + OnPreToolUse: func(input copilot.PreToolUseHookInput, invocation copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { + fmt.Printf("About to run tool: %s\n", input.ToolName) + // Return permission decision and optionally modify args + return &copilot.PreToolUseHookOutput{ + PermissionDecision: "allow", // "allow", "deny", or "ask" + ModifiedArgs: input.ToolArgs, // Optionally modify tool arguments + AdditionalContext: "Extra context for the model", + }, nil + }, + + // Called after each tool execution + OnPostToolUse: func(input copilot.PostToolUseHookInput, invocation copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { + fmt.Printf("Tool %s completed\n", input.ToolName) + return &copilot.PostToolUseHookOutput{ + AdditionalContext: "Post-execution notes", + }, nil + }, + + // Called when user submits a prompt + OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, invocation copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) { + fmt.Printf("User prompt: %s\n", input.Prompt) + return &copilot.UserPromptSubmittedHookOutput{ + ModifiedPrompt: input.Prompt, // Optionally modify the prompt + }, nil + }, + + // Called when session starts + OnSessionStart: func(input copilot.SessionStartHookInput, invocation copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) { + fmt.Printf("Session started from: %s\n", input.Source) // "startup", "resume", "new" + return &copilot.SessionStartHookOutput{ + AdditionalContext: "Session initialization context", + }, nil + }, + + // Called when session ends + OnSessionEnd: func(input copilot.SessionEndHookInput, invocation copilot.HookInvocation) (*copilot.SessionEndHookOutput, error) { + fmt.Printf("Session ended: %s\n", input.Reason) + return nil, nil + }, + + // Called when an error occurs + OnErrorOccurred: func(input copilot.ErrorOccurredHookInput, invocation copilot.HookInvocation) (*copilot.ErrorOccurredHookOutput, error) { + fmt.Printf("Error in %s: %s\n", input.ErrorContext, input.Error) + return &copilot.ErrorOccurredHookOutput{ + ErrorHandling: "retry", // "retry", "skip", or "abort" + }, nil + }, + }, +}) +``` + +**Available hooks:** + +- `OnPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments. +- `OnPostToolUse` - Process tool results after execution. Can modify results or add context. +- `OnUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing. +- `OnSessionStart` - Run logic when a session starts or resumes. +- `OnSessionEnd` - Cleanup or logging when session ends. +- `OnErrorOccurred` - Handle errors with retry/skip/abort strategies. + ## Transport Modes ### stdio (Default) diff --git a/nodejs/README.md b/nodejs/README.md index fbd730e5..b6e7aa47 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -64,6 +64,8 @@ new CopilotClient(options?: CopilotClientOptions) - `logLevel?: string` - Log level (default: "info") - `autoStart?: boolean` - Auto-start server (default: true) - `autoRestart?: boolean` - Auto-restart on crash (default: true) +- `githubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods. +- `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `githubToken` is provided). Cannot be used with `cliUrl`. #### Methods @@ -91,6 +93,8 @@ Create a new conversation session. - `systemMessage?: SystemMessageConfig` - System message customization (see below) - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below) - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section. +- `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section. +- `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise` @@ -470,6 +474,101 @@ const session = await client.createSession({ > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`. > - The `baseUrl` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically. +## User Input Requests + +Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler: + +```typescript +const session = await client.createSession({ + model: "gpt-5", + onUserInputRequest: async (request, invocation) => { + // request.question - The question to ask + // request.choices - Optional array of choices for multiple choice + // request.allowFreeform - Whether freeform input is allowed (default: true) + + console.log(`Agent asks: ${request.question}`); + if (request.choices) { + console.log(`Choices: ${request.choices.join(", ")}`); + } + + // Return the user's response + return { + answer: "User's answer here", + wasFreeform: true, // Whether the answer was freeform (not from choices) + }; + }, +}); +``` + +## Session Hooks + +Hook into session lifecycle events by providing handlers in the `hooks` configuration: + +```typescript +const session = await client.createSession({ + model: "gpt-5", + hooks: { + // Called before each tool execution + onPreToolUse: async (input, invocation) => { + console.log(`About to run tool: ${input.toolName}`); + // Return permission decision and optionally modify args + return { + permissionDecision: "allow", // "allow", "deny", or "ask" + modifiedArgs: input.toolArgs, // Optionally modify tool arguments + additionalContext: "Extra context for the model", + }; + }, + + // Called after each tool execution + onPostToolUse: async (input, invocation) => { + console.log(`Tool ${input.toolName} completed`); + // Optionally modify the result or add context + return { + additionalContext: "Post-execution notes", + }; + }, + + // Called when user submits a prompt + onUserPromptSubmitted: async (input, invocation) => { + console.log(`User prompt: ${input.prompt}`); + return { + modifiedPrompt: input.prompt, // Optionally modify the prompt + }; + }, + + // Called when session starts + onSessionStart: async (input, invocation) => { + console.log(`Session started from: ${input.source}`); // "startup", "resume", "new" + return { + additionalContext: "Session initialization context", + }; + }, + + // Called when session ends + onSessionEnd: async (input, invocation) => { + console.log(`Session ended: ${input.reason}`); + }, + + // Called when an error occurs + onErrorOccurred: async (input, invocation) => { + console.error(`Error in ${input.errorContext}: ${input.error}`); + return { + errorHandling: "retry", // "retry", "skip", or "abort" + }; + }, + }, +}); +``` + +**Available hooks:** + +- `onPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments. +- `onPostToolUse` - Process tool results after execution. Can modify results or add context. +- `onUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing. +- `onSessionStart` - Run logic when a session starts or resumes. +- `onSessionEnd` - Cleanup or logging when session ends. +- `onErrorOccurred` - Handle errors with retry/skip/abort strategies. + ## Error Handling ```typescript diff --git a/python/README.md b/python/README.md index ab324508..e5a14149 100644 --- a/python/README.md +++ b/python/README.md @@ -95,6 +95,8 @@ await client.stop() - `log_level` (str): Log level (default: "info") - `auto_start` (bool): Auto-start server on first use (default: True) - `auto_restart` (bool): Auto-restart on crash (default: True) +- `github_token` (str): GitHub token for authentication. When provided, takes priority over other auth methods. +- `use_logged_in_user` (bool): Whether to use logged-in user for authentication (default: True, but False when `github_token` is provided). Cannot be used with `cli_url`. **SessionConfig Options (for `create_session`):** @@ -105,6 +107,8 @@ await client.stop() - `streaming` (bool): Enable streaming delta events - `provider` (dict): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `infinite_sessions` (dict): Automatic context compaction configuration +- `on_user_input_request` (callable): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. +- `hooks` (dict): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. ### Tools @@ -349,6 +353,95 @@ session = await client.create_session({ > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`. > - The `base_url` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically. +## User Input Requests + +Enable the agent to ask questions to the user using the `ask_user` tool by providing an `on_user_input_request` handler: + +```python +async def handle_user_input(request, invocation): + # request["question"] - The question to ask + # request.get("choices") - Optional list of choices for multiple choice + # request.get("allowFreeform", True) - Whether freeform input is allowed + + print(f"Agent asks: {request['question']}") + if request.get("choices"): + print(f"Choices: {', '.join(request['choices'])}") + + # Return the user's response + return { + "answer": "User's answer here", + "wasFreeform": True, # Whether the answer was freeform (not from choices) + } + +session = await client.create_session({ + "model": "gpt-5", + "on_user_input_request": handle_user_input, +}) +``` + +## Session Hooks + +Hook into session lifecycle events by providing handlers in the `hooks` configuration: + +```python +async def on_pre_tool_use(input, invocation): + print(f"About to run tool: {input['toolName']}") + # Return permission decision and optionally modify args + return { + "permissionDecision": "allow", # "allow", "deny", or "ask" + "modifiedArgs": input.get("toolArgs"), # Optionally modify tool arguments + "additionalContext": "Extra context for the model", + } + +async def on_post_tool_use(input, invocation): + print(f"Tool {input['toolName']} completed") + return { + "additionalContext": "Post-execution notes", + } + +async def on_user_prompt_submitted(input, invocation): + print(f"User prompt: {input['prompt']}") + return { + "modifiedPrompt": input["prompt"], # Optionally modify the prompt + } + +async def on_session_start(input, invocation): + print(f"Session started from: {input['source']}") # "startup", "resume", "new" + return { + "additionalContext": "Session initialization context", + } + +async def on_session_end(input, invocation): + print(f"Session ended: {input['reason']}") + +async def on_error_occurred(input, invocation): + print(f"Error in {input['errorContext']}: {input['error']}") + return { + "errorHandling": "retry", # "retry", "skip", or "abort" + } + +session = await client.create_session({ + "model": "gpt-5", + "hooks": { + "on_pre_tool_use": on_pre_tool_use, + "on_post_tool_use": on_post_tool_use, + "on_user_prompt_submitted": on_user_prompt_submitted, + "on_session_start": on_session_start, + "on_session_end": on_session_end, + "on_error_occurred": on_error_occurred, + }, +}) +``` + +**Available hooks:** + +- `on_pre_tool_use` - Intercept tool calls before execution. Can allow/deny or modify arguments. +- `on_post_tool_use` - Process tool results after execution. Can modify results or add context. +- `on_user_prompt_submitted` - Intercept user prompts. Can modify the prompt before processing. +- `on_session_start` - Run logic when a session starts or resumes. +- `on_session_end` - Cleanup or logging when session ends. +- `on_error_occurred` - Handle errors with retry/skip/abort strategies. + ## Requirements - Python 3.9+