Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,7 @@ const session = await client.createSession({

### Customize the System Message

Control the AI's behavior and personality:
Control the AI's behavior and personality by appending instructions:

```typescript
const session = await client.createSession({
Expand All @@ -1261,6 +1261,28 @@ const session = await client.createSession({
});
```

For more fine-grained control, use `mode: "customize"` to override individual sections of the system prompt while preserving the rest:

```typescript
const session = await client.createSession({
systemMessage: {
mode: "customize",
sections: {
tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." },
code_change_rules: { action: "remove" },
guidelines: { action: "append", content: "\n* Always cite data sources" },
},
content: "Focus on financial analysis and reporting.",
},
});
```

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`.

Each override supports four actions: `replace`, `remove`, `append`, and `prepend`. Unknown section IDs are handled gracefully — content is appended to additional instructions and a warning is emitted; `remove` on unknown sections is silently ignored.

See the language-specific SDK READMEs for examples in [TypeScript](../nodejs/README.md), [Python](../python/README.md), [Go](../go/README.md), and [C#](../dotnet/README.md).

---

## Connecting to an External CLI Server
Expand Down
28 changes: 28 additions & 0 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,34 @@ var session = await client.CreateSessionAsync(new SessionConfig
});
```

#### Customize Mode

Use `Mode = SystemMessageMode.Customize` to selectively override individual sections of the prompt while preserving the rest:

```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5",
SystemMessage = new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = new Dictionary<string, SectionOverride>
{
[SystemPromptSections.Tone] = new() { Action = SectionOverrideAction.Replace, Content = "Respond in a warm, professional tone. Be thorough in explanations." },
[SystemPromptSections.CodeChangeRules] = new() { Action = SectionOverrideAction.Remove },
[SystemPromptSections.Guidelines] = new() { Action = SectionOverrideAction.Append, Content = "\n* Always cite data sources" },
},
Content = "Focus on financial analysis and reporting."
}
});
```

Available section IDs are defined as constants on `SystemPromptSections`: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`.

Each section override supports four actions: `Replace`, `Remove`, `Append`, and `Prepend`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.

#### Replace Mode

For full control (removes all guardrails), use `Mode = SystemMessageMode.Replace`:

```csharp
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,7 @@ private static LogLevel MapLevel(TraceEventType eventType)
[JsonSerializable(typeof(ProviderConfig))]
[JsonSerializable(typeof(ResumeSessionRequest))]
[JsonSerializable(typeof(ResumeSessionResponse))]
[JsonSerializable(typeof(SectionOverride))]
[JsonSerializable(typeof(SessionMetadata))]
[JsonSerializable(typeof(SystemMessageConfig))]
[JsonSerializable(typeof(ToolCallResponseV2))]
Expand Down
5 changes: 1 addition & 4 deletions dotnet/src/SdkProtocolVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,5 @@ internal static class SdkProtocolVersion
/// <summary>
/// Gets the SDK protocol version.
/// </summary>
public static int GetVersion()
{
return Version;
}
public static int GetVersion() => Version;
}
79 changes: 76 additions & 3 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,71 @@ public enum SystemMessageMode
Append,
/// <summary>Replace the default system message entirely.</summary>
[JsonStringEnumMemberName("replace")]
Replace
Replace,
/// <summary>Override individual sections of the system prompt.</summary>
[JsonStringEnumMemberName("customize")]
Customize
}

/// <summary>
/// Specifies the operation to perform on a system prompt section.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<SectionOverrideAction>))]
public enum SectionOverrideAction
{
/// <summary>Replace the section content entirely.</summary>
[JsonStringEnumMemberName("replace")]
Replace,
/// <summary>Remove the section from the prompt.</summary>
[JsonStringEnumMemberName("remove")]
Remove,
/// <summary>Append content after the existing section.</summary>
[JsonStringEnumMemberName("append")]
Append,
/// <summary>Prepend content before the existing section.</summary>
[JsonStringEnumMemberName("prepend")]
Prepend
}

/// <summary>
/// Override operation for a single system prompt section.
/// </summary>
public class SectionOverride
{
/// <summary>
/// The operation to perform on this section.
/// </summary>
public SectionOverrideAction Action { get; set; }

/// <summary>
/// Content for the override. Optional for all actions. Ignored for remove.
/// </summary>
public string? Content { get; set; }
}

/// <summary>
/// Known system prompt section identifiers for the "customize" mode.
/// </summary>
public static class SystemPromptSections
{
/// <summary>Agent identity preamble and mode statement.</summary>
public const string Identity = "identity";
/// <summary>Response style, conciseness rules, output formatting preferences.</summary>
public const string Tone = "tone";
/// <summary>Tool usage patterns, parallel calling, batching guidelines.</summary>
public const string ToolEfficiency = "tool_efficiency";
/// <summary>CWD, OS, git root, directory listing, available tools.</summary>
public const string EnvironmentContext = "environment_context";
/// <summary>Coding rules, linting/testing, ecosystem tools, style.</summary>
public const string CodeChangeRules = "code_change_rules";
/// <summary>Tips, behavioral best practices, behavioral guidelines.</summary>
public const string Guidelines = "guidelines";
/// <summary>Environment limitations, prohibited actions, security policies.</summary>
public const string Safety = "safety";
/// <summary>Per-tool usage instructions.</summary>
public const string ToolInstructions = "tool_instructions";
/// <summary>Repository and organization custom instructions.</summary>
public const string CustomInstructions = "custom_instructions";
}

/// <summary>
Expand All @@ -977,13 +1041,21 @@ public enum SystemMessageMode
public class SystemMessageConfig
{
/// <summary>
/// How the system message is applied (append or replace).
/// How the system message is applied (append, replace, or customize).
/// </summary>
public SystemMessageMode? Mode { get; set; }

/// <summary>
/// Content of the system message.
/// Content of the system message. Used by append and replace modes.
/// In customize mode, additional content appended after all sections.
/// </summary>
public string? Content { get; set; }

/// <summary>
/// Section-level overrides for customize mode.
/// Keys are section identifiers (see <see cref="SystemPromptSections"/>).
/// </summary>
public Dictionary<string, SectionOverride>? Sections { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -2061,6 +2133,7 @@ public class SetForegroundSessionResponse
[JsonSerializable(typeof(SessionLifecycleEvent))]
[JsonSerializable(typeof(SessionLifecycleEventMetadata))]
[JsonSerializable(typeof(SessionListFilter))]
[JsonSerializable(typeof(SectionOverride))]
[JsonSerializable(typeof(SessionMetadata))]
[JsonSerializable(typeof(SetForegroundSessionResponse))]
[JsonSerializable(typeof(SystemMessageConfig))]
Expand Down
31 changes: 31 additions & 0 deletions dotnet/test/SessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@ public async Task Should_Create_A_Session_With_Replaced_SystemMessage_Config()
Assert.Equal(testSystemMessage, GetSystemMessage(traffic[0]));
}

[Fact]
public async Task Should_Create_A_Session_With_Customized_SystemMessage_Config()
{
var customTone = "Respond in a warm, professional tone. Be thorough in explanations.";
var appendedContent = "Always mention quarterly earnings.";
var session = await CreateSessionAsync(new SessionConfig
{
SystemMessage = new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = new Dictionary<string, SectionOverride>
{
[SystemPromptSections.Tone] = new() { Action = SectionOverrideAction.Replace, Content = customTone },
[SystemPromptSections.CodeChangeRules] = new() { Action = SectionOverrideAction.Remove },
},
Content = appendedContent
}
});

await session.SendAsync(new MessageOptions { Prompt = "Who are you?" });
var assistantMessage = await TestHelper.GetFinalAssistantMessageAsync(session);
Assert.NotNull(assistantMessage);

var traffic = await Ctx.GetExchangesAsync();
Assert.NotEmpty(traffic);
var systemMessage = GetSystemMessage(traffic[0]);
Assert.Contains(customTone, systemMessage);
Assert.Contains(appendedContent, systemMessage);
Assert.DoesNotContain("<code_change_instructions>", systemMessage);
}

[Fact]
public async Task Should_Create_A_Session_With_AvailableTools()
{
Expand Down
51 changes: 50 additions & 1 deletion go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
- `ReasoningEffort` (string): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `ListModels()` to check which models support this option.
- `SessionID` (string): Custom session ID
- `Tools` ([]Tool): Custom tools exposed to the CLI
- `SystemMessage` (\*SystemMessageConfig): System message configuration
- `SystemMessage` (\*SystemMessageConfig): System message configuration. Supports three modes:
- **append** (default): Appends `Content` after the SDK-managed prompt
- **replace**: Replaces the entire prompt with `Content`
- **customize**: Selectively override individual sections via `Sections` map (keys: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`; values: `SectionOverride` with `Action` and optional `Content`)
- `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
Expand All @@ -176,6 +179,52 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec

- `Bool(v bool) *bool` - Helper to create bool pointers for `AutoStart` option

### System Message Customization

Control the system prompt using `SystemMessage` in session config:

```go
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
SystemMessage: &copilot.SystemMessageConfig{
Content: "Always check for security vulnerabilities before suggesting changes.",
},
})
```

The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `Content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `Mode: "replace"` or `Mode: "customize"`.

#### Customize Mode

Use `Mode: "customize"` to selectively override individual sections of the prompt while preserving the rest:

```go
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
SystemMessage: &copilot.SystemMessageConfig{
Mode: "customize",
Sections: map[string]copilot.SectionOverride{
// Replace the tone/style section
copilot.SectionTone: {Action: "replace", Content: "Respond in a warm, professional tone. Be thorough in explanations."},
// Remove coding-specific rules
copilot.SectionCodeChangeRules: {Action: "remove"},
// Append to existing guidelines
copilot.SectionGuidelines: {Action: "append", Content: "\n* Always cite data sources"},
},
// Additional instructions appended after all sections
Content: "Focus on financial analysis and reporting.",
},
})
```

Available section constants: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`.

Each section override supports four actions:
- **`replace`** — Replace the section content entirely
- **`remove`** — Remove the section from the prompt
- **`append`** — Add content after the existing section
- **`prepend`** — Add content before the existing section

Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.

## Image Support

The SDK supports image attachments via the `Attachments` field in `MessageOptions`. You can attach images by providing their file path:
Expand Down
45 changes: 45 additions & 0 deletions go/internal/e2e/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,51 @@ func TestSession(t *testing.T) {
}
})

t.Run("should create a session with customized systemMessage config", func(t *testing.T) {
ctx.ConfigureForTest(t)

customTone := "Respond in a warm, professional tone. Be thorough in explanations."
appendedContent := "Always mention quarterly earnings."
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SystemMessage: &copilot.SystemMessageConfig{
Mode: "customize",
Sections: map[string]copilot.SectionOverride{
copilot.SectionTone: {Action: "replace", Content: customTone},
copilot.SectionCodeChangeRules: {Action: "remove"},
},
Content: appendedContent,
},
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}

_, err = session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Who are you?"})
if err != nil {
t.Fatalf("Failed to send message: %v", err)
}

// Validate the system message sent to the model
traffic, err := ctx.GetExchanges()
if err != nil {
t.Fatalf("Failed to get exchanges: %v", err)
}
if len(traffic) == 0 {
t.Fatal("Expected at least one exchange")
}
systemMessage := getSystemMessage(traffic[0])
if !strings.Contains(systemMessage, customTone) {
t.Errorf("Expected system message to contain custom tone, got %q", systemMessage)
}
if !strings.Contains(systemMessage, appendedContent) {
t.Errorf("Expected system message to contain appended content, got %q", systemMessage)
}
if strings.Contains(systemMessage, "<code_change_instructions>") {
t.Error("Expected system message to NOT contain code_change_instructions (it was removed)")
}
})

t.Run("should create a session with availableTools", func(t *testing.T) {
ctx.ConfigureForTest(t)

Expand Down
33 changes: 29 additions & 4 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ func Float64(v float64) *float64 {
return &v
}

// Known system prompt section identifiers for the "customize" mode.
const (
SectionIdentity = "identity"
SectionTone = "tone"
SectionToolEfficiency = "tool_efficiency"
SectionEnvironmentContext = "environment_context"
SectionCodeChangeRules = "code_change_rules"
SectionGuidelines = "guidelines"
SectionSafety = "safety"
SectionToolInstructions = "tool_instructions"
SectionCustomInstructions = "custom_instructions"
)

// SectionOverride defines an override operation for a single system prompt section.
type SectionOverride struct {
// Action is the operation to perform: "replace", "remove", "append", or "prepend".
Action string `json:"action"`
// Content for the override. Optional for all actions. Ignored for "remove".
Content string `json:"content,omitempty"`
}

// SystemMessageAppendConfig is append mode: use CLI foundation with optional appended content.
type SystemMessageAppendConfig struct {
// Mode is optional, defaults to "append"
Expand All @@ -129,11 +150,15 @@ type SystemMessageReplaceConfig struct {
}

// SystemMessageConfig represents system message configuration for session creation.
// Use SystemMessageAppendConfig for default behavior, SystemMessageReplaceConfig for full control.
// In Go, use one struct or the other based on your needs.
// - Append mode (default): SDK foundation + optional custom content
// - Replace mode: Full control, caller provides entire system message
// - Customize mode: Section-level overrides with graceful fallback
//
// In Go, use one struct and set fields appropriate for the desired mode.
type SystemMessageConfig struct {
Mode string `json:"mode,omitempty"`
Content string `json:"content,omitempty"`
Mode string `json:"mode,omitempty"`
Content string `json:"content,omitempty"`
Sections map[string]SectionOverride `json:"sections,omitempty"`
}

// PermissionRequestResultKind represents the kind of a permission request result.
Expand Down
Loading
Loading