Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bb39536
Add C# SDK improvement plan for team review
manan164 Feb 25, 2026
a933737
Fix 5 critical bugs in SDK
manan164 Feb 25, 2026
dcab7d2
Add high-level client layer with 9 interfaces and Orkes implementations
manan164 Feb 25, 2026
46e4403
Add 13 missing task types to DSL: HttpPoll, KafkaPublish, StartWorkfl…
manan164 Feb 25, 2026
aff0676
Add worker framework improvements: exponential backoff, health checks…
manan164 Feb 25, 2026
0f9e4c7
Add metrics and telemetry infrastructure using System.Diagnostics.Met…
manan164 Feb 25, 2026
b981888
Add event system with listener interfaces and thread-safe dispatcher
manan164 Feb 25, 2026
751d856
Add missing AI/LLM providers and vector DB integrations
manan164 Feb 25, 2026
e86d262
Add comprehensive unit tests for all SDK improvements (106 tests)
manan164 Feb 25, 2026
44f0fc2
Complete Phase 4-6 gaps: auto-restart, 3-tier config, WorkerMetrics, …
manan164 Feb 25, 2026
494907e
Add 16 comprehensive examples mirroring Python SDK coverage
manan164 Feb 25, 2026
5ca2ab1
Add comprehensive test suite: 195 unit tests across 12 test files
manan164 Feb 25, 2026
b6da58b
Rewrite documentation: README, workers, workflows, config, metrics
manan164 Feb 25, 2026
e2f0561
Add task-update-v2, interceptors, OSS auth auto-disable, and schema a…
manan164 Mar 18, 2026
d2d36e2
Fix: Sticky fallback from task-update-v2 to v1 on HTTP 404/405
manan164 Mar 19, 2026
21b4c17
Fix: Wire metrics, EventDispatcher, NonRetryableException, and TaskEx…
manan164 Mar 19, 2026
fd284c6
Fix dead code and duplicate Content-Type header
manan164 Mar 23, 2026
c2c570f
Revert .NET version changes to preserve backward compatibility
manan164 Mar 23, 2026
fb54e5f
Add OSS integration tests via Testcontainers, clean up CI
manan164 Mar 23, 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
22 changes: 19 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Test and collect coverage
continue-on-error: true
run: >
DOCKER_BUILDKIT=1 docker build
--target=coverage_export
Expand Down Expand Up @@ -61,7 +60,24 @@ jobs:
if: ${{ !env.ACT }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/out/coverage.cobertura.xml
flags: unittests
name: ${{ github.workflow }}-${{ github.job }}-${{ github.run_number }}
name: ${{ github.workflow }}-${{ github.job }}-${{ github.run_number }}

integration_tests:
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Run integration tests
run: >
dotnet test Tests/conductor-csharp.test.csproj
-p:DefineConstants=EXCLUDE_EXAMPLE_WORKERS
--filter "Category=Integration"
-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