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
65 changes: 65 additions & 0 deletions .github/agents/openspec.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: OpenSpec
description: "Manages OpenSpec changes, specs, and workflows using the OpenSpec CLI. Use this agent for proposing changes, exploring ideas, validating artifacts, checking status, and archiving completed work."
Comment thread
Copilot marked this conversation as resolved.
tools:
- "execute"
- "read"
- "search"
- "edit"
---

<!-- Generated by OpenSpec for GitHub Copilot coding agent support. -->

# OpenSpec Agent

You are a specialized agent for managing OpenSpec workflows. Before using the `openspec` CLI, run `openspec --version`. If it is unavailable, install it with `npm install -g @fission-ai/openspec@1.14.1`.

## What is OpenSpec?

OpenSpec is a structured change management system for codebases. It organizes work into **changes** with planning artifacts (proposals, specs, designs, tasks) that guide implementation.

## Available Commands

### Agent-Compatible CLI Commands (prefer `--json` for structured output)

| Command | Purpose |
|---------|---------|
| `openspec list [--json]` | List all changes and specs |
| `openspec show <item> [--json]` | View a specific change or spec |
| `openspec validate [--all] [--json]` | Validate changes and specs for issues |
| `openspec status [--change <name>] [--json]` | Show artifact progress for a change |
| `openspec instructions [artifact] [--change <name>] [--json]` | Get next-step instructions for an artifact |
| `openspec templates [--json]` | List available templates |
| `openspec schemas [--json]` | List available workflow schemas |
| `openspec archive <change> --json [--yes]` | Archive a completed change; use `--yes` only after confirming all tasks are complete |

### Interactive CLI Commands (use when prompted by the user)

| Command | Purpose |
|---------|---------|
| `openspec init` | Initialize OpenSpec in the project |
| `openspec update` | Update OpenSpec configuration and artifacts |
| `openspec view` | Interactive dashboard |
| `openspec config` | View or modify settings |

## Workflow

Run OpenSpec commands from `lib/netstd`, where this change's OpenSpec project is located.

1. Run `openspec list --json` to see active changes and specs.
2. Run `openspec status --change <name> --json` for the selected change.
3. Run `openspec instructions [artifact] --change <name> --json` for the next-step instructions.
4. Run `openspec validate <name> --json` before completing.

## Key Directories

- `lib/netstd/openspec/` — OpenSpec directory
- `lib/netstd/openspec/changes/` — Active changes and their artifacts
- `lib/netstd/openspec/config.yaml` — Project configuration

## Best Practices

- Always use `--json` when parsing output programmatically
- Run `openspec validate` after creating or modifying artifacts
- Check `openspec status` before starting work to understand the current state
- When archiving, ensure all tasks are completed and validated first
26 changes: 26 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by OpenSpec for GitHub Copilot coding agent support.

name: "Copilot Setup Steps"
Comment thread
Copilot marked this conversation as resolved.

# Run manually to verify the setup; Copilot runs these steps when starting agent work.
on:
workflow_dispatch:

jobs:
# The job MUST be called `copilot-setup-steps` for Copilot coding agent to pick it up.
copilot-setup-steps:
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install OpenSpec CLI
run: npm install -g @fission-ai/openspec@1.14.1

- name: Verify OpenSpec CLI
run: openspec --version
33 changes: 29 additions & 4 deletions compiler/cpp/src/thrift/generate/t_netstd_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2432,13 +2432,30 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi
<< indent() << "{" << '\n';
indent_up();

out << indent() << "public Client(TProtocol protocol) : this(protocol, protocol)" << '\n'
out << indent() << "/// <summary>Initializes the client with a shared protocol.</summary>" << '\n'
<< indent() << "/// <remarks>Use this constructor when the application already owns and configures the protocol. It preserves shared-protocol behavior; use the transport and protocol-factory constructor to opt into per-call transports.</remarks>" << '\n'
<< indent() << "/// <param name=\"protocol\">The shared protocol used for input and output.</param>" << '\n'
<< indent() << "public Client(TProtocol protocol) : this(protocol, protocol)" << '\n'
<< indent() << "{" << '\n'
<< indent() << "}" << '\n'
<< '\n'
<< indent() << "/// <summary>Initializes the client with shared input and output protocols.</summary>" << '\n'
<< indent() << "/// <remarks>This constructor remains available for callers that manage protocol instances directly and preserves their shared-transport semantics. For concurrent per-call operations, use a supporting transport with the protocol-factory constructor.</remarks>" << '\n'
<< indent() << "/// <param name=\"inputProtocol\">The shared input protocol.</param>" << '\n'
<< indent() << "/// <param name=\"outputProtocol\">The shared output protocol.</param>" << '\n'
<< indent() << "public Client(TProtocol inputProtocol, TProtocol outputProtocol) : base(inputProtocol, outputProtocol)" << '\n'
<< indent() << "{" << '\n'
<< indent() << "}" << '\n'
<< '\n'
<< indent() << "/// <summary>Initializes the client with protocols created for a supplied transport.</summary>" << '\n'
<< indent() << "/// <remarks>When <paramref name=\"transport\"/> supports <see cref=\"ITPerCallTransportProvider\"/>, generated asynchronous calls use a separate transport and protocol state per call. Existing protocol constructors retain shared-protocol behavior.</remarks>" << '\n'
<< indent() << "/// <param name=\"transport\">The shared transport or per-call transport provider.</param>" << '\n'
<< indent() << "/// <param name=\"inputProtocolFactory\">The factory used to create input protocols.</param>" << '\n'
<< indent() << "/// <param name=\"outputProtocolFactory\">The factory used to create output protocols.</param>" << '\n'
<< indent() << "public Client(TTransport transport, TProtocolFactory inputProtocolFactory, TProtocolFactory outputProtocolFactory)" << '\n'
<< indent() << " : base(transport, inputProtocolFactory, outputProtocolFactory)" << '\n'
<< indent() << "{" << '\n'
<< indent() << "}" << '\n'
<< '\n';

vector<t_function*> functions = tservice->get_functions();
Expand All @@ -2448,23 +2465,31 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi
{
string raw_func_name = (*functions_iterator)->get_name();
string function_name = raw_func_name + (add_async_postfix ? "Async" : "");
string call_cancellation_token = tmp("callCancellationToken");

// async
generate_deprecation_attribute(out, (*functions_iterator)->annotations_);
out << indent() << "public async " << function_signature_async(*functions_iterator, "") << '\n'
<< indent() << "{" << '\n';
indent_up();
bool returns_value = !(*functions_iterator)->is_oneway() && !(*functions_iterator)->get_returntype()->is_void();
out << indent() << (returns_value ? "return await " : "await ")
<< "ExecutePerCallAsync(async (" << call_cancellation_token << ") =>" << '\n'
<< indent() << "{" << '\n';
indent_up();
out << indent() << "await send_" << function_name << "(";
string call_args = argument_list((*functions_iterator)->get_arglist(),false);
if(! call_args.empty()) {
out << call_args << ", ";
}
out << CANCELLATION_TOKEN_NAME << ");" << '\n';
out << call_cancellation_token << ");" << '\n';
if(! (*functions_iterator)->is_oneway()) {
out << indent() << ((*functions_iterator)->get_returntype()->is_void() ? "" : "return ")
<< "await recv_" << function_name << "(" << CANCELLATION_TOKEN_NAME << ");" << '\n';
out << indent() << (returns_value ? "return " : "")
<< "await recv_" << function_name << "(" << call_cancellation_token << ");" << '\n';
}
indent_down();
out << indent() << "}, " << CANCELLATION_TOKEN_NAME << ");" << '\n';
indent_down();
out << indent() << "}" << '\n' << '\n';

// async send
Expand Down
2 changes: 2 additions & 0 deletions lib/netstd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ dist-hook:

EXTRA_DIST = \
README.md \
per-call-transport.md \
openspec \
Directory.Build.props \
Benchmarks/Thrift.Benchmarks \
Tests/codegen/run-NetStd-Codegen-Tests.ps1 \
Expand Down
24 changes: 24 additions & 0 deletions lib/netstd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ The library ships as two packages so that non-web projects no longer pull in the
*in addition to* `ApacheThrift` only when you host Thrift over ASP.NET Core. Existing code
keeps compiling unchanged once the package reference is added.

# Per-call HTTP client calls

For concurrent asynchronous calls, construct the generated client with a transport that
implements `ITPerCallTransportProvider` and protocol factories. `THttpTransport`,
`TBufferedTransport`, and `TFramedTransport` over a supporting transport provide this capability. Each generated
high-level call then gets independent transport and protocol state while the HTTP connection
pool is shared:

```csharp
var transport = new THttpTransport(new Uri("http://localhost:9090"), new TConfiguration());
var protocolFactory = new TBinaryProtocol.Factory();
using var client = new Calculator.Client(transport, protocolFactory, protocolFactory);

var results = await Task.WhenAll(Enumerable.Range(1, 8)
.Select(value => client.add(value, value, cancellationToken)));
```

The existing constructors that accept `TProtocol` instances remain the shared-protocol path
for applications that manage protocol instances directly. Per-call behavior is opt-in; a
transport without the provider capability continues to use shared behavior. The per-call scope
covers generated high-level asynchronous methods that perform a complete RPC. Calling generated
`send_*` and `recv_*` methods separately continues to use the shared protocols and must not be
interleaved on the same client.

# Build the library

## How to build on Windows
Expand Down
Loading