Skip to content
Open
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
6 changes: 4 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -1911,9 +1911,11 @@ const session = await client.createSession({
});
```

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`.
Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_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.
`identity` and `tool_instructions` are section *groups*: they target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.

Each override supports five actions: `replace`, `remove`, `append`, `prepend`, and `preserve`. The `preserve` action is a no-op that opts an individually-addressable section out of a group-level `remove` (for example, keep `tone` when removing the `identity` group). Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored.

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

Expand Down
4 changes: 2 additions & 2 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -676,9 +676,9 @@ var session = await client.CreateSessionAsync(new SessionConfig
});
```

Available section IDs are defined as static properties on the `SystemMessageSection` struct: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`, `RuntimeInstructions`, `LastInstructions`.
Available section IDs are defined as static properties on the `SystemMessageSection` struct: `Preamble`, `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`, `RuntimeInstructions`, `LastInstructions`. `Identity` and `ToolInstructions` are section groups that target a collection of related sub-sections as a unit; use `Preamble` to target just the identity preamble.

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.
Each section override supports five actions: `Replace`, `Remove`, `Append`, `Prepend`, and `Preserve` (a no-op that opts an individually-addressable section out of a group-level `Remove`). Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.

#### Replace Mode

Expand Down
9 changes: 9 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,13 @@ public enum SectionOverrideAction
/// <summary>Prepend content before the existing section.</summary>
[JsonStringEnumMemberName("prepend")]
Prepend,
/// <summary>
/// No-op marker that opts an individually-addressable section out of a group-level
/// remove (e.g. keep <see cref="SystemMessageSection.Tone"/> when removing the
/// <see cref="SystemMessageSection.Identity"/> group).
/// </summary>
[JsonStringEnumMemberName("preserve")]
Preserve,
/// <summary>Transform the section content via a callback.</summary>
[JsonStringEnumMemberName("transform")]
Transform
Expand Down Expand Up @@ -1866,6 +1873,8 @@ public sealed class SectionOverride
public readonly struct SystemMessageSection : IEquatable<SystemMessageSection>
{
/// <summary>Agent identity preamble and mode statement.</summary>
public static SystemMessageSection Preamble { get; } = new("preamble");
/// <summary>Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.).</summary>
public static SystemMessageSection Identity { get; } = new("identity");
/// <summary>Response style, conciseness rules, output formatting preferences.</summary>
public static SystemMessageSection Tone { get; } = new("tone");
Expand Down
30 changes: 30 additions & 0 deletions dotnet/test/E2E/SystemMessageSectionsE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,34 @@ public async Task Should_Use_Replaced_Identity_Section_In_Response()
content.Contains("botanica") || content.Contains("garden") || content.Contains("plant"),
$"Expected response to reflect the replaced identity section, but got: {response.Data.Content}");
}

[Fact]
public async Task Should_Use_Replaced_Preamble_Section_In_Response()
{
var session = await CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
SystemMessage = new SystemMessageConfig
{
Mode = SystemMessageMode.Customize,
Sections = new Dictionary<SystemMessageSection, SectionOverride>
{
[SystemMessageSection.Preamble] = new SectionOverride
{
Action = SectionOverrideAction.Replace,
Content = "You are a helpful gardening assistant called Botanica. You only answer questions about plants and gardening."
}
}
}
});

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

Assert.NotNull(response);
var content = response.Data.Content.ToLowerInvariant();
Assert.True(
content.Contains("botanica") || content.Contains("garden") || content.Contains("plant"),
$"Expected response to reflect the replaced preamble section, but got: {response.Data.Content}");
}
}
9 changes: 6 additions & 3 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
- `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`, `SectionRuntimeInstructions`, `SectionLastInstructions`; values: `SectionOverride` with `Action` and optional `Content`)
- **customize**: Selectively override individual sections via `Sections` map (keys: `SectionPreamble`, `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionRuntimeInstructions`, `SectionLastInstructions`; 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 (nil = runtime default)
- `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration
Expand Down Expand Up @@ -238,14 +238,17 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{
})
```

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

Each section override supports four actions:
`SectionIdentity` and `SectionToolInstructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `SectionPreamble` to target just the identity preamble without affecting its sibling sub-sections.

Each section override supports five 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
- **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`

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

Expand Down
39 changes: 39 additions & 0 deletions go/internal/e2e/system_message_sections_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,43 @@ func TestSystemMessageSectionsE2E(t *testing.T) {
t.Errorf("Expected response to reflect the replaced identity section, but got: %s", ad.Content)
}
})

t.Run("should_use_replaced_preamble_section_in_response", func(t *testing.T) {
ctx.ConfigureForTest(t)

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SystemMessage: &copilot.SystemMessageConfig{
Mode: "customize",
Sections: map[string]copilot.SectionOverride{
copilot.SectionPreamble: {
Action: copilot.SectionActionReplace,
Content: "You are a helpful gardening assistant called Botanica. You only answer questions about plants and gardening.",
},
},
},
})
if err != nil {
t.Fatalf("Failed to create session: %v", err)
}

response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{
Prompt: "Who are you?",
})
if err != nil {
t.Fatalf("Failed to send message: %v", err)
}
if response == nil {
t.Fatal("Expected a response from the assistant")
}

ad, ok := response.Data.(*copilot.AssistantMessageData)
if !ok {
t.Fatalf("Expected AssistantMessageData, got %T", response.Data)
}
content := strings.ToLower(ad.Content)
if !strings.Contains(content, "botanica") && !strings.Contains(content, "garden") && !strings.Contains(content, "plant") {
t.Errorf("Expected response to reflect the replaced preamble section, but got: %s", ad.Content)
}
})
}
9 changes: 8 additions & 1 deletion go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ func Int(v int) *int {

// Known system message section identifiers for the "customize" mode.
const (
// SectionIdentity is the agent identity preamble and mode statement.
// SectionPreamble is the agent identity preamble and mode statement.
SectionPreamble = "preamble"
// SectionIdentity is the section group covering the identity preamble and its
// sibling sub-sections (tone, tool efficiency, etc.).
SectionIdentity = "identity"
// SectionTone covers response style, conciseness rules, and output formatting preferences.
SectionTone = "tone"
Expand Down Expand Up @@ -248,6 +251,10 @@ const (
SectionActionAppend SectionOverrideAction = "append"
// SectionActionPrepend prepends to existing section content.
SectionActionPrepend SectionOverrideAction = "prepend"
// SectionActionPreserve is a no-op marker that opts an individually-addressable
// section out of a group-level "remove" (e.g. keep "tone" when removing the
// "identity" group).
SectionActionPreserve SectionOverrideAction = "preserve"
)

// SectionTransformFn is a callback that receives the current content of a system message section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ public enum SectionOverrideAction {
/** Prepend content before the existing section. */
PREPEND("prepend"),

/**
* No-op marker that opts an individually-addressable section out of a
* group-level {@link #REMOVE} (e.g. keep {@link SystemMessageSections#TONE}
* when removing the {@link SystemMessageSections#IDENTITY} group).
*/
PRESERVE("preserve"),

/**
* Transform the section content via a callback.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
public abstract sealed class SystemMessageSections permits SystemPromptSections {

/** Agent identity preamble and mode statement. */
public static final String PREAMBLE = "preamble";

/**
* Section group covering the identity preamble and its sibling sub-sections
* (tone, tool efficiency, etc.).
*/
public static final String IDENTITY = "identity";

/** Response style, conciseness rules, output formatting preferences. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void transformOnIdentitySectionReceivesNonEmptyContent() throws Exception {
*/
@Test
void deprecatedSystemPromptSectionsMatchesSystemMessageSections() {
assertEquals(SystemMessageSections.PREAMBLE, SystemPromptSections.PREAMBLE);
assertEquals(SystemMessageSections.IDENTITY, SystemPromptSections.IDENTITY);
assertEquals(SystemMessageSections.TONE, SystemPromptSections.TONE);
assertEquals(SystemMessageSections.TOOL_EFFICIENCY, SystemPromptSections.TOOL_EFFICIENCY);
Expand All @@ -143,7 +144,7 @@ void allConstantsInheritedByDeprecatedClass() throws Exception {
&& Modifier.isFinal(f.getModifiers()) && f.getType() == String.class)
.map(Field::getName).collect(Collectors.toSet());

assertEquals(11, parentConstants.size(), "Expected 11 section constants in SystemMessageSections");
assertEquals(12, parentConstants.size(), "Expected 12 section constants in SystemMessageSections");

for (String constantName : parentConstants) {
Field parentField = SystemMessageSections.class.getDeclaredField(constantName);
Expand Down Expand Up @@ -189,4 +190,41 @@ void shouldUseReplacedIdentitySectionInResponse() throws Exception {
}
}
}

/**
* Verifies that replacing the {@link SystemMessageSections#PREAMBLE} section
* via {@link SectionOverrideAction#REPLACE} causes the assistant to adopt the
* custom identity in its response without affecting sibling sections.
*
* @see Snapshot:
* system_message_sections/should_use_replaced_preamble_section_in_response
*/
@Test
void shouldUseReplacedPreambleSectionInResponse() throws Exception {
ctx.configureForTest("system_message_sections", "should_use_replaced_preamble_section_in_response");

var systemMessage = new SystemMessageConfig().setMode(SystemMessageMode.CUSTOMIZE)
.setSections(Map.of(SystemMessageSections.PREAMBLE,
new SectionOverride().setAction(SectionOverrideAction.REPLACE)
.setContent("You are a helpful gardening assistant called Botanica. "
+ "You only answer questions about plants and gardening.")));

try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(new SessionConfig().setSystemMessage(systemMessage)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(30, TimeUnit.SECONDS);

try {
AssistantMessageEvent response = session
.sendAndWait(new MessageOptions().setPrompt("Who are you?"), 60_000).get(90, TimeUnit.SECONDS);

assertNotNull(response, "Expected a response from the assistant");
String content = response.getData().content().toLowerCase();
assertTrue(content.contains("botanica") || content.contains("garden") || content.contains("plant"),
"Expected response to reflect the replaced preamble section, but got: "
+ response.getData().content());
} finally {
session.close();
}
}
}
}
7 changes: 5 additions & 2 deletions nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -618,14 +618,17 @@ const session = await client.createSession({
});
```

Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.
Available section IDs: `preamble`, `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `runtime_instructions`, `last_instructions`. Use the `SYSTEM_MESSAGE_SECTIONS` constant for descriptions of each section.

Each section override supports four actions:
`identity` and `tool_instructions` are section _groups_ that target a collection of related sub-sections as a unit. Use `preamble` to target just the identity preamble without affecting its sibling sub-sections.

Each section override supports five 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
- **`preserve`** — No-op that opts an individually-addressable section out of a group-level `remove`

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

Expand Down
10 changes: 9 additions & 1 deletion nodejs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ export interface ToolCallResponsePayload {
* Each section corresponds to a distinct part of the system prompt.
*/
export type SystemMessageSection =
| "preamble"
| "identity"
| "tone"
| "tool_efficiency"
Expand All @@ -816,7 +817,11 @@ export type SystemMessageSection =

/** Section metadata for documentation and tooling. */
export const SYSTEM_MESSAGE_SECTIONS: Record<SystemMessageSection, { description: string }> = {
identity: { description: "Agent identity preamble and mode statement" },
preamble: { description: "Agent identity preamble and mode statement" },
identity: {
description:
"Section group covering the identity preamble and its sibling sub-sections (tone, tool efficiency, etc.)",
},
tone: { description: "Response style, conciseness rules, output formatting preferences" },
tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" },
environment_context: { description: "CWD, OS, git root, directory listing, available tools" },
Expand Down Expand Up @@ -847,13 +852,16 @@ export type SectionTransformFn = (currentContent: string) => string | Promise<st
* - `"remove"`: Remove the section
* - `"append"`: Append to existing section content
* - `"prepend"`: Prepend to existing section content
* - `"preserve"`: No-op marker that opts an individually-addressable section out of a
* group-level `"remove"` (e.g. keep `tone` when removing the `identity` group)
* - `function`: Transform callback — receives current section content, returns new content
*/
export type SectionOverrideAction =
| "replace"
| "remove"
| "append"
| "prepend"
| "preserve"
| SectionTransformFn;

/**
Expand Down
Loading
Loading