Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8b5dee4
Add C# SDK improvement plan for team review
manan164 Feb 25, 2026
00e3275
Fix 5 critical bugs in SDK
manan164 Feb 25, 2026
cf91fd3
Add high-level client layer with 9 interfaces and Orkes implementations
manan164 Feb 25, 2026
2a1464e
Add 13 missing task types to DSL: HttpPoll, KafkaPublish, StartWorkfl…
manan164 Feb 25, 2026
d4d9452
Add worker framework improvements: exponential backoff, health checks…
manan164 Feb 25, 2026
8e7cc97
Add metrics and telemetry infrastructure using System.Diagnostics.Met…
manan164 Feb 25, 2026
9df4b26
Add event system with listener interfaces and thread-safe dispatcher
manan164 Feb 25, 2026
fc7817c
Add missing AI/LLM providers and vector DB integrations
manan164 Feb 25, 2026
c8db2ea
Add comprehensive unit tests for all SDK improvements (106 tests)
manan164 Feb 25, 2026
f7ae5e8
Complete Phase 4-6 gaps: auto-restart, 3-tier config, WorkerMetrics, …
manan164 Feb 25, 2026
a85f3f6
Add 16 comprehensive examples mirroring Python SDK coverage
manan164 Feb 25, 2026
943db71
Add comprehensive test suite: 195 unit tests across 12 test files
manan164 Feb 25, 2026
d2305e8
Rewrite documentation: README, workers, workflows, config, metrics
manan164 Feb 25, 2026
c6400b7
Add task-update-v2, interceptors, OSS auth auto-disable, and schema a…
manan164 Mar 18, 2026
6688e7c
Fix: Sticky fallback from task-update-v2 to v1 on HTTP 404/405
manan164 Mar 19, 2026
bfdca7e
Fix: Wire metrics, EventDispatcher, NonRetryableException, and TaskEx…
manan164 Mar 19, 2026
861f188
Fix dead code and duplicate Content-Type header
manan164 Mar 23, 2026
dff7c2d
Revert .NET version changes to preserve backward compatibility
manan164 Mar 23, 2026
9235955
Add OSS integration tests via Testcontainers, clean up CI
manan164 Mar 23, 2026
0e96cd7
Fix build errors in integration tests after rebase
manan164 Apr 28, 2026
98f3c33
Fix CI failures: reclassify cloud integration tests, fix metrics list…
manan164 Apr 28, 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
46 changes: 3 additions & 43 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Run unit tests
- name: Test and collect coverage
run: >
dotnet test Tests/conductor-csharp.test.csproj
-p:DefineConstants=EXCLUDE_EXAMPLE_WORKERS
Expand All @@ -55,59 +51,23 @@ jobs:
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/**/coverage.cobertura.xml
files: coverage/out/coverage.cobertura.xml
flags: unittests
name: ${{ github.workflow }}-${{ github.job }}-${{ github.run_number }}

integration_tests:
needs: lint
runs-on: ubuntu-latest
env:
CONDUCTOR_SERVER_URL: ${{ secrets.CONDUCTOR_SERVER_URL }}
CONDUCTOR_AUTH_KEY: ${{ secrets.CONDUCTOR_AUTH_KEY }}
CONDUCTOR_AUTH_SECRET: ${{ secrets.CONDUCTOR_AUTH_SECRET }}
GITHUB_RUN_ID: ${{ github.run_id }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Show server endpoint and auth key prefix
run: |
echo "CONDUCTOR_SERVER_URL=${CONDUCTOR_SERVER_URL#*://}"
echo "CONDUCTOR_AUTH_KEY prefix=${CONDUCTOR_AUTH_KEY:0:8}..."
- name: Run integration tests (v5)
- name: Run integration tests
run: >
dotnet test Tests/conductor-csharp.test.csproj
-p:DefineConstants=EXCLUDE_EXAMPLE_WORKERS
--filter "Category=Integration"
-l "console;verbosity=normal"

legacy_integration_tests:
needs: lint
runs-on: ubuntu-latest
env:
CONDUCTOR_SERVER_URL: ${{ secrets.CONDUCTOR_SERVER_URL }}
CONDUCTOR_AUTH_KEY: ${{ secrets.CONDUCTOR_AUTH_KEY }}
CONDUCTOR_AUTH_SECRET: ${{ secrets.CONDUCTOR_AUTH_SECRET }}
GITHUB_RUN_ID: ${{ github.run_id }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Show server endpoint and auth key prefix
run: |
echo "CONDUCTOR_SERVER_URL=${CONDUCTOR_SERVER_URL#*://}"
echo "CONDUCTOR_AUTH_KEY prefix=${CONDUCTOR_AUTH_KEY:0:8}..."
- name: Run legacy integration tests
run: >
dotnet test Tests/conductor-csharp.test.csproj
-p:DefineConstants=EXCLUDE_EXAMPLE_WORKERS
--filter "Category=CloudIntegration"
-l "console;verbosity=normal"

14 changes: 14 additions & 0 deletions Conductor/Api/ITaskResourceApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,15 @@ public interface ITaskResourceApi : IApiAccessor
Workflow UpdateTaskSync(Dictionary<string, Object> output, string workflowId, string taskRefName, TaskResult.StatusEnum status, string workerid = null);


/// <summary>
/// Update a task (v2) — updates the result and returns the next available task in one call.
/// Supported on newer Conductor servers. Falls back gracefully on older servers.
/// </summary>
/// <param name="body">Task result to submit</param>
/// <param name="workerid">Worker identifier (optional)</param>
/// <returns>Next task to process, or null if none available or server does not support v2</returns>
Task UpdateTaskV2(TaskResult body, string workerid = null);

#endregion Synchronous Operations

#region Asynchronous Operations
Expand Down Expand Up @@ -459,6 +468,11 @@ public interface ITaskResourceApi : IApiAccessor
/// <returns>Workflow</returns>
ThreadTask.Task<Workflow> UpdateTaskSyncAsync(Dictionary<string, Object> output, string workflowId, string taskRefName, TaskResult.StatusEnum status, string workerid = null);

/// <summary>
/// Asynchronous update a task (v2) — updates the result and returns the next available task.
/// </summary>
ThreadTask.Task<Task> UpdateTaskV2Async(TaskResult body, string workerid = null);

#endregion Asynchronous Operations
}
}
96 changes: 95 additions & 1 deletion Conductor/Api/TaskResourceApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,101 @@
}

/// <summary>
/// Update a task By Ref Name
/// Update a task (v2) — submits the result and returns the next available task in one call.
/// Uses PUT /tasks/{taskId} which is supported on newer Conductor servers.
/// Returns null if no next task is available or the server returns a non-task response.
/// Throws ApiException with ErrorCode 404/405 on older servers (caller should fall back to v1).
/// </summary>
public Task UpdateTaskV2(TaskResult body, string workerid = null)
{
if (body == null)
throw new ApiException(400, "Missing required parameter 'body' when calling TaskResourceApi->UpdateTaskV2");

var localVarPath = "/tasks/{taskId}";
var localVarPathParams = new Dictionary<String, String>();
var localVarQueryParams = new List<KeyValuePair<String, String>>();
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
var localVarFormParams = new Dictionary<String, String>();
var localVarFileParams = new Dictionary<String, FileParameter>();

localVarPathParams["taskId"] = this.Configuration.ApiClient.ParameterToString(body.TaskId);
if (workerid != null) localVarQueryParams.AddRange(this.Configuration.ApiClient.ParameterToKeyValuePairs("", "workerid", workerid));

String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(new[] { "application/json" });
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(new[] { "application/json" });
if (localVarHttpHeaderAccept != null)
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);

Object localVarPostBody = this.Configuration.ApiClient.Serialize(body);

if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
localVarHeaderParams["X-Authorization"] = this.Configuration.AccessToken;

RestResponse localVarResponse = (RestResponse)this.Configuration.ApiClient.CallApi(
localVarPath, Method.Put, localVarQueryParams, localVarPostBody,
localVarHeaderParams, localVarFormParams, localVarFileParams,
localVarPathParams, localVarHttpContentType, this.Configuration);

int localVarStatusCode = (int)localVarResponse.StatusCode;

if (ExceptionFactory != null)
{
Exception exception = ExceptionFactory("UpdateTaskV2", localVarResponse);
if (exception != null) throw exception;
}

if (string.IsNullOrWhiteSpace(localVarResponse.Content) || localVarResponse.Content == "null")
return null;

return (Task)this.Configuration.ApiClient.Deserialize(localVarResponse, typeof(Task));
}

public async ThreadTask.Task<Task> UpdateTaskV2Async(TaskResult body, string workerid = null)
{
if (body == null)
throw new ApiException(400, "Missing required parameter 'body' when calling TaskResourceApi->UpdateTaskV2Async");

var localVarPath = "/tasks/{taskId}";
var localVarPathParams = new Dictionary<String, String>();
var localVarQueryParams = new List<KeyValuePair<String, String>>();
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
var localVarFormParams = new Dictionary<String, String>();
var localVarFileParams = new Dictionary<String, FileParameter>();

localVarPathParams["taskId"] = this.Configuration.ApiClient.ParameterToString(body.TaskId);
if (workerid != null) localVarQueryParams.AddRange(this.Configuration.ApiClient.ParameterToKeyValuePairs("", "workerid", workerid));

String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(new[] { "application/json" });
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(new[] { "application/json" });
if (localVarHttpHeaderAccept != null)
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);

Object localVarPostBody = this.Configuration.ApiClient.Serialize(body);

if (!String.IsNullOrEmpty(this.Configuration.AccessToken))
localVarHeaderParams["X-Authorization"] = this.Configuration.AccessToken;

RestResponse localVarResponse = (RestResponse)await this.Configuration.ApiClient.CallApiAsync(
localVarPath, Method.Put, localVarQueryParams, localVarPostBody,
localVarHeaderParams, localVarFormParams, localVarFileParams,
localVarPathParams, localVarHttpContentType);

int localVarStatusCode = (int)localVarResponse.StatusCode;

if (ExceptionFactory != null)
{
Exception exception = ExceptionFactory("UpdateTaskV2Async", localVarResponse);
if (exception != null) throw exception;
}

if (string.IsNullOrWhiteSpace(localVarResponse.Content) || localVarResponse.Content == "null")
return null;

return (Task)this.Configuration.ApiClient.Deserialize(localVarResponse, typeof(Task));
}

/// <summary>
/// Update a task By Ref Name
/// </summary>
/// <exception cref="Conductor.Client.ApiException">Thrown when fails to make API call</exception>
/// <param name="body"></param>
Expand Down Expand Up @@ -1605,7 +1699,7 @@
if (taskRefName == null)
throw new ApiException(400, "Missing required parameter 'taskRefName' when calling TaskResourceApi->UpdateTask");
// verify the required parameter 'status' is set
if (status == null)

Check warning on line 1702 in Conductor/Api/TaskResourceApi.cs

View workflow job for this annotation

GitHub Actions / integration_tests

The result of the expression is always 'false' since a value of type 'TaskResult.StatusEnum' is never equal to 'null' of type 'TaskResult.StatusEnum?'
throw new ApiException(400, "Missing required parameter 'status' when calling TaskResourceApi->UpdateTask");

var localVarPath = "/tasks/{workflowId}/{taskRefName}/{status}/sync";
Expand Down Expand Up @@ -1637,7 +1731,7 @@

if (workflowId != null) localVarPathParams.Add("workflowId", this.Configuration.ApiClient.ParameterToString(workflowId)); // path parameter
if (taskRefName != null) localVarPathParams.Add("taskRefName", this.Configuration.ApiClient.ParameterToString(taskRefName)); // path parameter
if (status != null) localVarPathParams.Add("status", this.Configuration.ApiClient.ParameterToString(status)); // path parameter

Check warning on line 1734 in Conductor/Api/TaskResourceApi.cs

View workflow job for this annotation

GitHub Actions / integration_tests

The result of the expression is always 'true' since a value of type 'TaskResult.StatusEnum' is never equal to 'null' of type 'TaskResult.StatusEnum?'
if (workerid != null) localVarQueryParams.AddRange(this.Configuration.ApiClient.ParameterToKeyValuePairs("", "workerid", workerid)); // query parameter
if (output != null && output.GetType() != typeof(byte[]))
{
Expand Down
54 changes: 54 additions & 0 deletions Conductor/Client/Ai/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,48 @@ public enum LLMProviderEnum
/// </summary>
[EnumMember(Value = "huggingface")]
HUGGING_FACE = 4,

/// <summary>
/// Enum ANTHROPIC for value: anthropic
/// </summary>
[EnumMember(Value = "anthropic")]
ANTHROPIC = 5,

/// <summary>
/// Enum AWS_BEDROCK for value: aws_bedrock
/// </summary>
[EnumMember(Value = "aws_bedrock")]
AWS_BEDROCK = 6,

/// <summary>
/// Enum COHERE for value: cohere
/// </summary>
[EnumMember(Value = "cohere")]
COHERE = 7,

/// <summary>
/// Enum GROK for value: grok
/// </summary>
[EnumMember(Value = "grok")]
GROK = 8,

/// <summary>
/// Enum MISTRAL for value: mistral
/// </summary>
[EnumMember(Value = "mistral")]
MISTRAL = 9,

/// <summary>
/// Enum OLLAMA for value: ollama
/// </summary>
[EnumMember(Value = "ollama")]
OLLAMA = 10,

/// <summary>
/// Enum PERPLEXITY for value: perplexity
/// </summary>
[EnumMember(Value = "perplexity")]
PERPLEXITY = 11,
}

/// <summary>
Expand All @@ -66,6 +108,18 @@ public enum VectorDBEnum
/// </summary>
[EnumMember(Value = "weaviatedb")]
WEAVIATE_DB = 2,

/// <summary>
/// Enum POSTGRES_DB for value: postgresdb
/// </summary>
[EnumMember(Value = "postgresdb")]
POSTGRES_DB = 3,

/// <summary>
/// Enum MONGO_DB for value: mongodb
/// </summary>
[EnumMember(Value = "mongodb")]
MONGO_DB = 4,
}
}
}
Loading
Loading