Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
aae763f
docs: add DeepWiki badge to README
marc-romu Jan 3, 2026
84ece3e
feat: new context limit management features in conversation session a…
marc-romu Jan 5, 2026
9ae46a4
feat: change conversation summaries to use AIAgent.Summary role with …
marc-romu Jan 5, 2026
79b572d
feat: add Optimized serialization context to exclude persistent/volat…
marc-romu Jan 5, 2026
c672782
feat: new LastEffectiveTotalTokens field in AIMetrics to properly cal…
marc-romu Jan 10, 2026
c76076b
feat: add ContextLimit property to all AI model capabilities across p…
marc-romu Jan 10, 2026
bedc59b
feat: add debug Update button to refresh chat view from conversation …
marc-romu Jan 10, 2026
17be784
chore: remove trailing whitespace in AIBody.cs
marc-romu Jan 10, 2026
49de681
chore: changelog update
marc-romu Jan 10, 2026
a65c821
feat(chat): add context limit management and debug improvements (#368)
marc-romu Jan 10, 2026
ae1c16f
docs: update version badge for dev
actions-user Jan 10, 2026
d3868e3
docs: update version badge for dev to 1.2.3-dev.260110 (#369)
marc-romu Jan 10, 2026
82c8604
chore: prepare release 1.2.3-alpha with version update and code style…
actions-user Jan 11, 2026
6606b6e
chore: prepare release 1.2.3-alpha with version update and code style…
marc-romu Jan 11, 2026
6ef693b
docs: reorder README badges and update DeconstructMetrics component GUID
marc-romu Jan 11, 2026
b3718dc
ci: allow release branch names as valid PR titles in validation workflow
marc-romu Jan 11, 2026
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
12 changes: 8 additions & 4 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ jobs:
# Valid types based on conventional commits
VALID_TYPES="feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert|security|release"

# Check if PR title follows conventional commits format
if [[ ! $PR_TITLE =~ ^($VALID_TYPES)(\([a-z0-9-]+\))?:\ .+ ]]; then
echo "::error Pull request title must follow conventional commits format"
# Check if PR title follows conventional commits format OR is a release branch name
if [[ ! $PR_TITLE =~ ^($VALID_TYPES)(\([a-z0-9-]+\))?:\ .+ ]] && [[ ! $PR_TITLE =~ ^release/[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?(\+[a-zA-Z0-9]+)?$ ]]; then
echo "::error Pull request title must follow conventional commits format OR be a release branch name"
echo ""
echo "Required format: <type>(<optional-scope>): <description>"
echo "Required format 1: <type>(<optional-scope>): <description>"
echo "Required format 2: release/<version>"
echo ""
echo "Where <type> is one of:"
echo " feat: A new feature"
Expand All @@ -146,6 +147,9 @@ jobs:
echo " fix(component): resolve issue with parameter handling"
echo " docs(readme): update installation instructions"
echo " security: update dependencies to address vulnerabilities"
echo " release/1.2.3-alpha"
echo " release/2.0.0"
echo " release/1.0.0+build.123"
exit 1
else
echo "PR title format is valid! ✅"
Expand Down
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.3-alpha] - 2026-01-11

### Added

- Context Management:
- Added `ContextLimit` property to `AIModelCapabilities` for storing model context window sizes across all providers (Anthropic, DeepSeek, OpenAI, OpenRouter, MistralAI).
- Added `SummarizeSpecialTurn` factory for creating conversation summarization special turns.
- Added automatic context tracking in `ConversationSession` with percentage calculation.
- Added pre-emptive summarization when context usage exceeds 80% of model limit.
- Added context exceeded error detection and automatic summarization with retry.
- Added graceful error handling when summarization fails to reduce context size.
- Added context limits for all MistralAI models (128K for most, 40K for Magistral, 32K for Voxtral).
- Metrics:
- Added `LastEffectiveTotalTokens` field to `AIMetrics` for accurate context usage percentage calculation.
- WebChat Debug:
- Added debug Update button to refresh chat view from conversation history.
- Added DOM synchronization functionality to compare cached HTML hashes and update only changed messages.
- GhJSON canvas tools:
- Added `gh_get_start` / `gh_get_start_with_data` tools to retrieve start nodes (components with no incoming connections) with optional runtime data.
- Added `gh_get_end` / `gh_get_end_with_data` tools to retrieve end nodes (components with no outgoing connections) with optional runtime data, providing a wide view of definition outputs.

### Changed

- Context Management:
- Conversation summaries now use `AIAgent.Summary` instead of `AIAgent.Assistant`, preventing extra assistant messages in chat UI.
- Providers automatically merge `Summary` interactions with the system prompt using format: `System prompt\n---\nThis is a summary of the previous conversation:\n\nSummary`.
- WebChat UI renders `Summary` interactions as collapsible elements with distinct blue styling, similar to tool/system messages.
- GhJSON Serialization:
- `PersistentData` and `VolatileData` properties are now excluded from serialization at the `GhJsonSerializer` level to prevent encrypted/binary strings from being included in GhJSON output, ensuring LLM-safe and token-efficient results by default.
- Runtime data remains available via the separate `ExtractRuntimeData` method for `_with_data` tools (e.g., `gh_get_selected_with_data`).
- Debug Logging:
- Updated `ConversationSession` debug history file to preserve previous conversations when summarization occurs.
- Added `SUMMARIZED` marker in debug logs to clearly separate pre-summary and post-summary history.

### Fixed

- Context Management:
- Fixed context usage percentage not appearing consistently in debug logs and WebChat UI metrics by caching it at the `ConversationSession` level and applying it to aggregated metrics.
- GhJSON canvas helpers and chat:
- Standardized node classification terminology from `input/output/processing/isolated` to `startnodes/endnodes/middlenodes/isolatednodes` across `gh_get` filters and the `GhGetComponents` Grasshopper component UI.
- Updated `instruction_get` canvas guidance to recommend `gh_get_start`/`gh_get_end` (and their `_with_data` variants) for obtaining a wide view of data sources and outputs.
- Strengthened the `CanvasButton` default system prompt to always call `instruction_get` for canvas queries, improving tool selection for providers like `mistral-small`.
- GhJSON Serialization:
- Fixed `gh_get` tool incorrectly using `Optimized` serialization context (which excludes `PersistentData`) instead of `Standard` context, breaking `gh_put` restoration of internalized parameter values. Now uses `Standard` format by default to preserve all data needed for restoration, only switching to `Optimized` when runtime data is explicitly included (where persistent values are redundant).

## [1.2.2-alpha] - 2025-12-27

### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# SmartHopper - AI-Powered Tools and Assistant for Grasshopper3D

[![Version](https://img.shields.io/badge/version-1.2.2--alpha-orange?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Version](https://img.shields.io/badge/version-1.2.3--alpha-orange?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![Status](https://img.shields.io/badge/status-Alpha-orange?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/releases)
[![.NET CI](https://img.shields.io/github/actions/workflow/status/architects-toolkit/SmartHopper/.github/workflows/ci-dotnet-tests.yml?label=tests&logo=dotnet&style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/actions/workflows/ci-dotnet-tests.yml)
[![Ready to use](https://img.shields.io/badge/ready_to_use-YES-brightgreen?style=for-the-badge)](https://smarthopper.xyz/#installation)
[![License](https://img.shields.io/badge/license-LGPL%20v3-white?style=for-the-badge)](https://github.com/architects-toolkit/SmartHopper/blob/main/LICENSE)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/architects-toolkit/SmartHopper)

<div align="center">

Expand Down
2 changes: 1 addition & 1 deletion Solution.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<SolutionVersion>1.2.2-alpha</SolutionVersion>
<SolutionVersion>1.2.3-alpha</SolutionVersion>
</PropertyGroup>
</Project>
45 changes: 45 additions & 0 deletions docs/Providers/AICall/SpecialTurns.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,49 @@ var greeting = await session.ExecuteSpecialTurnAsync(
- 30 second timeout
- `PersistResult` strategy (only greeting appears in history)

### Summarize Turn

Factory for conversation summarization when context limits are approached:

```csharp
var summarizeConfig = SummarizeSpecialTurn.Create(
providerName: "openai",
conversationModel: session.Request.Model,
conversationHistory: session.GetHistoryInteractionList(),
lastUserMessage: lastUserInteraction // Optional, preserved after summary
);

var summary = await session.ExecuteSpecialTurnAsync(
summarizeConfig,
preferStreaming: false,
cancellationToken);
```

**Configuration:**

- Uses the conversation's current model (so the summarizer has the same context limit)
- Disables all tools (`-*`)
- 60 second timeout
- `ReplaceAbove` strategy with `PreserveSystemContext` filter
- Generates a concise summary preserving key topics, decisions, and context

**Automatic Usage:**

The `ConversationSession` automatically triggers summarization when:

1. Context usage exceeds 80% of the model's context limit (pre-emptive)
2. A context exceeded error is received from the provider (reactive)

**Summarization behavior:**

- Finds the **last user message** in history
- Summarizes everything **before** that user message
- Uses `ReplaceAbove` to replace old history with summary (preserves system/context messages)
- Manually appends the last user message after the summary
- **Drops all interactions after the last user message** (assistant responses, tool calls, tool results)
- This ensures a clean conversation state and prevents token bloat from incomplete turns
- The next provider call will be a fresh assistant response to the preserved user message

## Execution Flow

1. **Snapshot**: Current request state is captured for final persistence
Expand Down Expand Up @@ -253,4 +296,6 @@ Possible future special turn types:
- `src/SmartHopper.Infrastructure/AICall/Sessions/SpecialTurns/HistoryPersistenceStrategy.cs`
- `src/SmartHopper.Infrastructure/AICall/Sessions/SpecialTurns/InteractionFilter.cs`
- `src/SmartHopper.Infrastructure/AICall/Sessions/ConversationSession.SpecialTurns.cs`
- `src/SmartHopper.Infrastructure/AICall/Sessions/ConversationSession.ContextManagement.cs`
- `src/SmartHopper.Infrastructure/AICall/Sessions/SpecialTurns/BuiltIn/GreetingSpecialTurn.cs`
- `src/SmartHopper.Infrastructure/AICall/Sessions/SpecialTurns/BuiltIn/SummarizeSpecialTurn.cs`
5 changes: 3 additions & 2 deletions src/SmartHopper.Components/Grasshopper/GhGetComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace SmartHopper.Components.Grasshopper
{
/// <summary>
/// Component that converts selected or all Grasshopper components to GhJSON format.
/// Supports optional filtering by runtime messages (errors, warnings, and remarks), component states (selected, enabled, disabled), preview capability (previewcapable, notpreviewcapable), preview state (previewon, previewoff), and classification by object type via Type filter (params, components, input, output, processing, isolated).
/// Supports optional filtering by runtime messages (errors, warnings, and remarks), component states (selected, enabled, disabled), preview capability (previewcapable, notpreviewcapable), preview state (previewon, previewoff), and classification by object type via Type filter (params, components, startnodes, endnodes, middlenodes, isolatednodes).
/// Optionally includes document metadata (timestamps, Rhino/Grasshopper versions, plugin dependencies).
/// </summary>
public class GhGetComponents : SelectingComponentBase
Expand All @@ -47,7 +47,7 @@ public GhGetComponents()

protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddTextParameter("Type filter", "T", "Optional list of classification tokens with include/exclude syntax: 'params', 'components', 'inputcomponents', 'outputcomponents', 'processingcomponents', 'isolatedcomponents'. Prefix '+' to include, '-' to exclude.", GH_ParamAccess.list, "");
pManager.AddTextParameter("Type filter", "T", "Optional list of classification tokens with include/exclude syntax: 'params', 'components', 'startnodes', 'endnodes', 'middlenodes', 'isolatednodes'. Prefix '+' to include, '-' to exclude.", GH_ParamAccess.list, "");
pManager.AddTextParameter("Category Filter", "C", "Optional list of category filters by Grasshopper category or subcategory (e.g. 'Maths', 'Params', 'Script'). Use '+name' to include and '-name' to exclude.", GH_ParamAccess.list, "");
pManager.AddTextParameter("Attribute Filter", "F", "Optional list of filters by tags: 'error', 'warning', 'remark', 'selected', 'unselected', 'enabled', 'disabled', 'previewon', 'previewoff'. Prefix '+' to include, '-' to exclude.", GH_ParamAccess.list, "");
pManager.AddIntegerParameter("Connection Depth", "D", "Optional depth of connections to include: 0 = only matching components; 1 = direct connections; higher = further hops.", GH_ParamAccess.item, 0);
Expand Down Expand Up @@ -117,6 +117,7 @@ protected override void SolveInstance(IGH_DataAccess DA)
["connectionDepth"] = connectionDepth,
["includeMetadata"] = includeMetadata,
["guidFilter"] = JArray.FromObject(this.SelectedObjects.Select(o => o.InstanceGuid.ToString())),
["includeRuntimeData"] = true,
};

// Create AIToolCall and execute
Expand Down
14 changes: 11 additions & 3 deletions src/SmartHopper.Components/Misc/DeconstructMetricsComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public DeconstructMetricsComponent()
{
}

public override Guid ComponentGuid => new("250D14BA-D96A-4DC0-8703-87468CE2A18D");
public override Guid ComponentGuid => new("B8FE17D7-F146-4C94-9673-D2FA04BF7B9F");

/// <summary>
/// Gets the component's icon.
Expand All @@ -44,6 +44,7 @@ protected override void RegisterOutputParams(GH_OutputParamManager pManager)
pManager.AddIntegerParameter("Output Tokens", "O", "Number of output tokens", GH_ParamAccess.item);
pManager.AddTextParameter("Finish Reason", "F", "Reason for finishing", GH_ParamAccess.item);
pManager.AddNumberParameter("Completion Time", "T", "Time taken for completion, in seconds", GH_ParamAccess.item);
pManager.AddNumberParameter("Context Usage", "CU", "Context usage percentage (0-100), representing how much of the model's context limit is being used. Empty if context limit is unknown.", GH_ParamAccess.item);
pManager.AddIntegerParameter("Data Count", "DC", "The number of data items that were processed by the component. This may not match the total number of items in your input lists. If the component is configured to process data in batches, this value indicates how many batches (or groups) of results the component needs to process.", GH_ParamAccess.item);
pManager.AddIntegerParameter("Iterations Count", "IC", "The number of times the component ran its calculation. If the component was set to recognize and group identical combinations of input items, it only processed each unique combination once and applied the results to all matching outputs. As a result, the iteration count may be less than the total data count.", GH_ParamAccess.item);
}
Expand All @@ -66,6 +67,7 @@ protected override void SolveInstance(IGH_DataAccess DA)
int outputTokens = metricsObject["tokens_output"]?.Value<int>() ?? 0;
string finishReason = metricsObject["finish_reason"]?.Value<string>() ?? "Unknown";
double completionTime = metricsObject["completion_time"]?.Value<double>() ?? 0.0;
double? contextUsagePercent = metricsObject["context_usage_percent"]?.Value<double?>();
int inputDataCount = metricsObject["data_count"]?.Value<int>() ?? 0;
int iterationsCount = metricsObject["iterations_count"]?.Value<int>() ?? 0;

Expand All @@ -76,6 +78,7 @@ protected override void SolveInstance(IGH_DataAccess DA)
bool hasOutputTokens = metricsObject["tokens_output"] != null;
bool hasFinishReason = metricsObject["finish_reason"] != null;
bool hasCompletionTime = metricsObject["completion_time"] != null;
bool hasContextUsage = metricsObject["context_usage_percent"] != null;

// Set the data, potentially with warnings if values were missing
DA.SetData(0, aiProvider);
Expand Down Expand Up @@ -114,9 +117,14 @@ protected override void SolveInstance(IGH_DataAccess DA)
this.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Completion time not found in JSON");
}

DA.SetData(6, inputDataCount);
if (hasContextUsage && contextUsagePercent.HasValue)
{
DA.SetData(6, contextUsagePercent.Value * 100.0); // Convert 0-1 to 0-100
}

DA.SetData(7, inputDataCount);

DA.SetData(7, iterationsCount);
DA.SetData(8, iterationsCount);
}
catch (Exception ex)
{
Expand Down
Loading
Loading