diff --git a/devolutions-gateway/openapi/doc/index.adoc b/devolutions-gateway/openapi/doc/index.adoc index 04e0934d0..77b56ff30 100644 --- a/devolutions-gateway/openapi/doc/index.adoc +++ b/devolutions-gateway/openapi/doc/index.adoc @@ -1119,6 +1119,118 @@ ifdef::internal-generation[] endif::internal-generation[] +[.getAiAnalysis] +==== getAiAnalysis + +`GET /jet/jrec/{session_id}/ai-analysis/{task_id}` + +Gets an AI analysis of a recorded session + +===== Description + +Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + + +// markup not found, no include::{specDir}jet/jrec/\{session_id\}/ai-analysis/\{task_id\}/GET/spec.adoc[opts=optional] + + + +===== Security + +[cols="2,1,1"] +|=== +| Name | Type | Scheme + +| `scope_token` +| http +| bearer +|=== + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| session_id +| Recorded session ID +| X +| null +| + +| task_id +| Task ID of the AI analysis +| X +| null +| + +|=== + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| The AI analysis Task +| <> + + +| 400 +| Bad request +| <<>> + + +| 401 +| Invalid or missing authorization token +| <<>> + + +| 403 +| Insufficient permissions +| <<>> + + +| 404 +| The session has no AI analysis with this Task ID +| <<>> + + +| 500 +| Unexpected server error +| <<>> + +|=== + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}jet/jrec/\{session_id\}/ai-analysis/\{task_id\}/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.listRecordings] ==== listRecordings @@ -1426,6 +1538,135 @@ ifdef::internal-generation[] endif::internal-generation[] +[.startAiAnalysis] +==== startAiAnalysis + +`POST /jet/jrec/{session_id}/ai-analysis` + +Starts an AI analysis of a recorded session + +===== Description + +Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + + +// markup not found, no include::{specDir}jet/jrec/\{session_id\}/ai-analysis/POST/spec.adoc[opts=optional] + + + +===== Security + +[cols="2,1,1"] +|=== +| Name | Type | Scheme + +| `scope_token` +| http +| bearer +|=== + +===== Parameters + +====== Path Parameters + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| session_id +| Recorded session ID +| X +| null +| + +|=== + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| AiAnalysisRequest +| AI analysis settings <> +| X +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| The same request came in before; nothing new starts +| <> + + +| 202 +| The AI analysis Task is created and runs in the background +| <> + + +| 400 +| Invalid request body or AI settings +| <<>> + + +| 401 +| Invalid or missing authorization token +| <<>> + + +| 403 +| Insufficient permissions +| <<>> + + +| 404 +| The session has no recording +| <<>> + + +| 409 +| The AI analysis cannot start now +| <> + + +| 500 +| Unexpected server error +| <<>> + +|=== + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}jet/jrec/\{session_id\}/ai-analysis/POST/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.Jrl] === Jrl @@ -3371,6 +3612,8 @@ endif::internal-generation[] | gateway.net.monitor.drain | gateway.agent.delete | gateway.agent.read +| gateway.tasks.recording-ai-analysis.start +| gateway.tasks.recording-ai-analysis.read |=== @@ -3544,6 +3787,278 @@ endif::internal-generation[] |=== +[#AiAnalysisConflict] +=== _AiAnalysisConflict_ + +Why an AI analysis cannot start now + + +[.fields-AiAnalysisConflict] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| code +| X +| +| <> +| +| task_id_conflict, recording_active, analysis_in_progress, + +| taskId +| +| X +| UUID +| The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. +| uuid + +|=== + + + +[#AiAnalysisConflictCode] +=== _AiAnalysisConflictCode_ + + + + + + +[.fields-AiAnalysisConflictCode] +[cols="1"] +|=== +| Enum Values + +| task_id_conflict +| recording_active +| analysis_in_progress + +|=== + + +[#AiAnalysisFailedPayload] +=== _AiAnalysisFailedPayload_ + +Payload of a `failed` AI analysis Task: why it failed + + +[.fields-AiAnalysisFailedPayload] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| attempts +| X +| +| Integer +| Number of attempts started. +| int32 + +| details +| X +| X +| oas_any_type_not_mapped +| What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": }`. +| + +| reason +| X +| +| String +| Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. +| + +|=== + + + +[#AiAnalysisRequest] +=== _AiAnalysisRequest_ + +Settings of an AI analysis + + +[.fields-AiAnalysisRequest] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| apiKey +| X +| +| String +| API key of the provider, kept in memory only until the Task ends. +| password + +| baseUrl +| +| X +| String +| Endpoint of the provider API, kept in memory only; required for `openai-compatible`. +| + +| maxOutputTokens +| +| X +| Integer +| Upper bound of tokens in each AI answer. +| int32 + +| model +| X +| +| String +| Model identifier, passed to the provider as is. +| + +| provider +| X +| +| <> +| +| openai, anthropic, mistral, gemini, openai-compatible, + +| taskId +| X +| +| UUID +| Task ID, chosen by the caller so it can send the same request again safely. +| uuid + +|=== + + + +[#AiAnalysisRunningPayload] +=== _AiAnalysisRunningPayload_ + +Payload of a `running` AI analysis Task: its progress + + +[.fields-AiAnalysisRunningPayload] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| done +| +| X +| Long +| Work done in the step: bytes read while `reading`, parts described while `describing`. +| int64 + +| step +| X +| +| String +| Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). +| + +| total +| +| X +| Long +| Work the step has in all, in the same unit as `done`. +| int64 + +|=== + + + +[#AiAnalysisSucceededPayload] +=== _AiAnalysisSucceededPayload_ + +Payload of a `succeeded` AI analysis Task: the log it added to the session + + +[.fields-AiAnalysisSucceededPayload] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| fileName +| X +| +| String +| Name of the new log in the session manifest, such as `ai-analysis-0.slog`. +| + +| model +| X +| +| String +| Model that wrote the log, as reported by the AI provider; the requested model when it reports none. +| + +| promptVersion +| X +| +| String +| Version of the instructions sent to the AI provider. +| + +| usage +| +| X +| <> +| +| + +|=== + + + +[#AiAnalysisUsage] +=== _AiAnalysisUsage_ + +Tokens counted by an AI provider + + +[.fields-AiAnalysisUsage] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| inputTokens +| X +| +| Long +| +| int64 + +| outputTokens +| X +| +| Long +| +| int64 + +|=== + + + +[#AiProvider] +=== _AiProvider_ + +AI provider to ask; `openai-compatible` needs a base URL + + + + +[.fields-AiProvider] +[cols="1"] +|=== +| Enum Values + +| openai +| anthropic +| mistral +| gemini +| openai-compatible + +|=== + + [#AppCredential] === _AppCredential_ @@ -5564,6 +6079,124 @@ Subscriber configuration +[#TaskInfo] +=== _TaskInfo_ + +A Task: the permanent record of one piece of work the provisioner asked Gateway to do + +The work runs in the background; `state` and `payload` tell how it goes. +The shape of `params` and `payload` belongs to the kind. +For `recording-ai-analysis`, `payload` is an `AiAnalysisRunningPayload` while `running`, an +`AiAnalysisSucceededPayload` once `succeeded`, and an `AiAnalysisFailedPayload` once `failed`. + + +[.fields-TaskInfo] +[cols="2,1,1,2,4,1"] +|=== +| Field Name| Required| Nullable | Type| Description | Format + +| attempts +| X +| +| Integer +| Number of attempts started so far. +| int32 + +| createdAt +| X +| +| Date +| When the Task was created. +| date-time + +| deadlineAt +| X +| +| Date +| Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. +| date-time + +| finishedAt +| +| X +| Date +| When it succeeded or failed. +| date-time + +| id +| X +| +| UUID +| Task ID, chosen by the provisioner. +| uuid + +| kind +| X +| +| String +| Kind of work, such as `recording-ai-analysis`. +| + +| params +| X +| +| Object +| What was asked, without secrets. +| + +| payload +| +| X +| Object +| Details of the current state; null while `queued`. +| + +| startedAt +| +| X +| Date +| When its first attempt started. +| date-time + +| state +| X +| +| <> +| +| queued, running, succeeded, failed, + +| target +| X +| +| String +| What the work is about, such as a session ID. +| + +|=== + + + +[#TaskState] +=== _TaskState_ + + + + + + +[.fields-TaskState] +[cols="1"] +|=== +| Enum Values + +| queued +| running +| succeeded +| failed + +|=== + + [#TrafficEventResponse] === _TrafficEventResponse_ diff --git a/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES b/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES index b49a09154..9766c02c6 100644 --- a/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES +++ b/devolutions-gateway/openapi/dotnet-client/.openapi-generator/FILES @@ -9,6 +9,14 @@ docs/AgentApi.md docs/AgentDomainAdvertisement.md docs/AgentInfo.md docs/AgentStatus.md +docs/AiAnalysisConflict.md +docs/AiAnalysisConflictCode.md +docs/AiAnalysisFailedPayload.md +docs/AiAnalysisRequest.md +docs/AiAnalysisRunningPayload.md +docs/AiAnalysisSucceededPayload.md +docs/AiAnalysisUsage.md +docs/AiProvider.md docs/AppCredential.md docs/AppCredentialKind.md docs/AppTokenContentType.md @@ -70,6 +78,8 @@ docs/SetUpdateScheduleRequest.md docs/SubProvisionerKey.md docs/Subscriber.md docs/TargetConnectionOptions.md +docs/TaskInfo.md +docs/TaskState.md docs/TrafficApi.md docs/TrafficEventResponse.md docs/TransportProtocolResponse.md @@ -117,6 +127,14 @@ src/Devolutions.Gateway.Client/Model/AddressFamily.cs src/Devolutions.Gateway.Client/Model/AgentDomainAdvertisement.cs src/Devolutions.Gateway.Client/Model/AgentInfo.cs src/Devolutions.Gateway.Client/Model/AgentStatus.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisConflict.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisConflictCode.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisFailedPayload.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisRequest.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisRunningPayload.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisSucceededPayload.cs +src/Devolutions.Gateway.Client/Model/AiAnalysisUsage.cs +src/Devolutions.Gateway.Client/Model/AiProvider.cs src/Devolutions.Gateway.Client/Model/AppCredential.cs src/Devolutions.Gateway.Client/Model/AppCredentialKind.cs src/Devolutions.Gateway.Client/Model/AppTokenContentType.cs @@ -168,6 +186,8 @@ src/Devolutions.Gateway.Client/Model/SetUpdateScheduleRequest.cs src/Devolutions.Gateway.Client/Model/SubProvisionerKey.cs src/Devolutions.Gateway.Client/Model/Subscriber.cs src/Devolutions.Gateway.Client/Model/TargetConnectionOptions.cs +src/Devolutions.Gateway.Client/Model/TaskInfo.cs +src/Devolutions.Gateway.Client/Model/TaskState.cs src/Devolutions.Gateway.Client/Model/TrafficEventResponse.cs src/Devolutions.Gateway.Client/Model/TransportProtocolResponse.cs src/Devolutions.Gateway.Client/Model/UpdateProductInfo.cs diff --git a/devolutions-gateway/openapi/dotnet-client/README.md b/devolutions-gateway/openapi/dotnet-client/README.md index c60cd47e2..50ec69200 100644 --- a/devolutions-gateway/openapi/dotnet-client/README.md +++ b/devolutions-gateway/openapi/dotnet-client/README.md @@ -153,9 +153,11 @@ Class | Method | HTTP request | Description *HeartbeatApi* | [**GetHeartbeat**](docs/HeartbeatApi.md#getheartbeat) | **GET** /jet/heartbeat | Performs a heartbeat check *JrecApi* | [**DeleteManyRecordings**](docs/JrecApi.md#deletemanyrecordings) | **DELETE** /jet/jrec/delete | Mass-deletes recordings stored on this instance *JrecApi* | [**DeleteRecording**](docs/JrecApi.md#deleterecording) | **DELETE** /jet/jrec/delete/{id} | Deletes a recording stored on this instance +*JrecApi* | [**GetAiAnalysis**](docs/JrecApi.md#getaianalysis) | **GET** /jet/jrec/{session_id}/ai-analysis/{task_id} | Gets an AI analysis of a recorded session *JrecApi* | [**ListRecordings**](docs/JrecApi.md#listrecordings) | **GET** /jet/jrec/list | Lists all recordings stored on this instance *JrecApi* | [**PullRecordingFile**](docs/JrecApi.md#pullrecordingfile) | **GET** /jet/jrec/pull/{id}/{filename} | Retrieves a recording file for a given session *JrecApi* | [**PullRecordingSession**](docs/JrecApi.md#pullrecordingsession) | **GET** /jet/jrec/pull/{id} | Downloads an entire recorded session as a ZIP archive +*JrecApi* | [**StartAiAnalysis**](docs/JrecApi.md#startaianalysis) | **POST** /jet/jrec/{session_id}/ai-analysis | Starts an AI analysis of a recorded session *JrlApi* | [**GetJrlInfo**](docs/JrlApi.md#getjrlinfo) | **GET** /jet/jrl/info | Retrieves current JRL (Json Revocation List) info *JrlApi* | [**UpdateJrl**](docs/JrlApi.md#updatejrl) | **POST** /jet/jrl | Updates JRL (Json Revocation List) using a JRL token *NetApi* | [**GetNetConfig**](docs/NetApi.md#getnetconfig) | **GET** /jet/net/config | Lists network interfaces @@ -186,6 +188,14 @@ Class | Method | HTTP request | Description - [Model.AgentDomainAdvertisement](docs/AgentDomainAdvertisement.md) - [Model.AgentInfo](docs/AgentInfo.md) - [Model.AgentStatus](docs/AgentStatus.md) + - [Model.AiAnalysisConflict](docs/AiAnalysisConflict.md) + - [Model.AiAnalysisConflictCode](docs/AiAnalysisConflictCode.md) + - [Model.AiAnalysisFailedPayload](docs/AiAnalysisFailedPayload.md) + - [Model.AiAnalysisRequest](docs/AiAnalysisRequest.md) + - [Model.AiAnalysisRunningPayload](docs/AiAnalysisRunningPayload.md) + - [Model.AiAnalysisSucceededPayload](docs/AiAnalysisSucceededPayload.md) + - [Model.AiAnalysisUsage](docs/AiAnalysisUsage.md) + - [Model.AiProvider](docs/AiProvider.md) - [Model.AppCredential](docs/AppCredential.md) - [Model.AppCredentialKind](docs/AppCredentialKind.md) - [Model.AppTokenContentType](docs/AppTokenContentType.md) @@ -237,6 +247,8 @@ Class | Method | HTTP request | Description - [Model.SubProvisionerKey](docs/SubProvisionerKey.md) - [Model.Subscriber](docs/Subscriber.md) - [Model.TargetConnectionOptions](docs/TargetConnectionOptions.md) + - [Model.TaskInfo](docs/TaskInfo.md) + - [Model.TaskState](docs/TaskState.md) - [Model.TrafficEventResponse](docs/TrafficEventResponse.md) - [Model.TransportProtocolResponse](docs/TransportProtocolResponse.md) - [Model.UpdateProductInfo](docs/UpdateProductInfo.md) diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflict.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflict.md new file mode 100644 index 000000000..af1ed1fa7 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflict.md @@ -0,0 +1,12 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisConflict +Why an AI analysis cannot start now + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Code** | **AiAnalysisConflictCode** | | +**TaskId** | **Guid?** | The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflictCode.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflictCode.md new file mode 100644 index 000000000..f67809bcc --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisConflictCode.md @@ -0,0 +1,9 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisConflictCode + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisFailedPayload.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisFailedPayload.md new file mode 100644 index 000000000..14ab9a82b --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisFailedPayload.md @@ -0,0 +1,13 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisFailedPayload +Payload of a `failed` AI analysis Task: why it failed + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Attempts** | **int** | Number of attempts started. | +**Details** | **Object** | What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": <payload before the timeout> }`. | +**Reason** | **string** | Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRequest.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRequest.md new file mode 100644 index 000000000..7fb6f7d0e --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRequest.md @@ -0,0 +1,16 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisRequest +Settings of an AI analysis + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**ApiKey** | **string** | API key of the provider, kept in memory only until the Task ends. | +**BaseUrl** | **string** | Endpoint of the provider API, kept in memory only; required for `openai-compatible`. | [optional] +**MaxOutputTokens** | **int?** | Upper bound of tokens in each AI answer. | [optional] +**Model** | **string** | Model identifier, passed to the provider as is. | +**Provider** | **AiProvider** | | +**TaskId** | **Guid** | Task ID, chosen by the caller so it can send the same request again safely. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRunningPayload.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRunningPayload.md new file mode 100644 index 000000000..fd2a14d23 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisRunningPayload.md @@ -0,0 +1,13 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisRunningPayload +Payload of a `running` AI analysis Task: its progress + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Done** | **long?** | Work done in the step: bytes read while `reading`, parts described while `describing`. | [optional] +**Step** | **string** | Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). | +**Total** | **long?** | Work the step has in all, in the same unit as `done`. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisSucceededPayload.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisSucceededPayload.md new file mode 100644 index 000000000..6afc9bcea --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisSucceededPayload.md @@ -0,0 +1,14 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisSucceededPayload +Payload of a `succeeded` AI analysis Task: the log it added to the session + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**FileName** | **string** | Name of the new log in the session manifest, such as `ai-analysis-0.slog`. | +**Model** | **string** | Model that wrote the log, as reported by the AI provider; the requested model when it reports none. | +**PromptVersion** | **string** | Version of the instructions sent to the AI provider. | +**Usage** | [**AiAnalysisUsage**](AiAnalysisUsage.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisUsage.md b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisUsage.md new file mode 100644 index 000000000..28e94dc22 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiAnalysisUsage.md @@ -0,0 +1,12 @@ +# Devolutions.Gateway.Client.Model.AiAnalysisUsage +Tokens counted by an AI provider + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**InputTokens** | **long** | | +**OutputTokens** | **long** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/AiProvider.md b/devolutions-gateway/openapi/dotnet-client/docs/AiProvider.md new file mode 100644 index 000000000..a553e6c22 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/AiProvider.md @@ -0,0 +1,10 @@ +# Devolutions.Gateway.Client.Model.AiProvider +AI provider to ask; `openai-compatible` needs a base URL + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md b/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md index 5777700ac..ee5bc68a6 100644 --- a/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md +++ b/devolutions-gateway/openapi/dotnet-client/docs/JrecApi.md @@ -6,9 +6,11 @@ All URIs are relative to *http://localhost* |--------|--------------|-------------| | [**DeleteManyRecordings**](JrecApi.md#deletemanyrecordings) | **DELETE** /jet/jrec/delete | Mass-deletes recordings stored on this instance | | [**DeleteRecording**](JrecApi.md#deleterecording) | **DELETE** /jet/jrec/delete/{id} | Deletes a recording stored on this instance | +| [**GetAiAnalysis**](JrecApi.md#getaianalysis) | **GET** /jet/jrec/{session_id}/ai-analysis/{task_id} | Gets an AI analysis of a recorded session | | [**ListRecordings**](JrecApi.md#listrecordings) | **GET** /jet/jrec/list | Lists all recordings stored on this instance | | [**PullRecordingFile**](JrecApi.md#pullrecordingfile) | **GET** /jet/jrec/pull/{id}/{filename} | Retrieves a recording file for a given session | | [**PullRecordingSession**](JrecApi.md#pullrecordingsession) | **GET** /jet/jrec/pull/{id} | Downloads an entire recorded session as a ZIP archive | +| [**StartAiAnalysis**](JrecApi.md#startaianalysis) | **POST** /jet/jrec/{session_id}/ai-analysis | Starts an AI analysis of a recorded session | # **DeleteManyRecordings** @@ -210,6 +212,111 @@ void (empty response body) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **GetAiAnalysis** +> TaskInfo GetAiAnalysis (Guid sessionId, Guid taskId) + +Gets an AI analysis of a recorded session + +Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + +### Example +```csharp +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using Devolutions.Gateway.Client.Api; +using Devolutions.Gateway.Client.Client; +using Devolutions.Gateway.Client.Model; + +namespace Example +{ + public class GetAiAnalysisExample + { + public static void Main() + { + Configuration config = new Configuration(); + config.BasePath = "http://localhost"; + // Configure Bearer token for authorization: scope_token + config.AccessToken = "YOUR_BEARER_TOKEN"; + + // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes + HttpClient httpClient = new HttpClient(); + HttpClientHandler httpClientHandler = new HttpClientHandler(); + var apiInstance = new JrecApi(httpClient, config, httpClientHandler); + var sessionId = "sessionId_example"; // Guid | Recorded session ID + var taskId = "taskId_example"; // Guid | Task ID of the AI analysis + + try + { + // Gets an AI analysis of a recorded session + TaskInfo result = apiInstance.GetAiAnalysis(sessionId, taskId); + Debug.WriteLine(result); + } + catch (ApiException e) + { + Debug.Print("Exception when calling JrecApi.GetAiAnalysis: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); + } + } + } +} +``` + +#### Using the GetAiAnalysisWithHttpInfo variant +This returns an ApiResponse object which contains the response data, status code and headers. + +```csharp +try +{ + // Gets an AI analysis of a recorded session + ApiResponse response = apiInstance.GetAiAnalysisWithHttpInfo(sessionId, taskId); + Debug.Write("Status Code: " + response.StatusCode); + Debug.Write("Response Headers: " + response.Headers); + Debug.Write("Response Body: " + response.Data); +} +catch (ApiException e) +{ + Debug.Print("Exception when calling JrecApi.GetAiAnalysisWithHttpInfo: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); +} +``` + +### Parameters + +| Name | Type | Description | Notes | +|------|------|-------------|-------| +| **sessionId** | **Guid** | Recorded session ID | | +| **taskId** | **Guid** | Task ID of the AI analysis | | + +### Return type + +[**TaskInfo**](TaskInfo.md) + +### Authorization + +[scope_token](../README.md#scope_token) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | The AI analysis Task | - | +| **400** | Bad request | - | +| **401** | Invalid or missing authorization token | - | +| **403** | Insufficient permissions | - | +| **404** | The session has no AI analysis with this Task ID | - | +| **500** | Unexpected server error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **ListRecordings** > List<Guid> ListRecordings (bool active) @@ -513,3 +620,110 @@ catch (ApiException e) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **StartAiAnalysis** +> TaskInfo StartAiAnalysis (Guid sessionId, AiAnalysisRequest aiAnalysisRequest) + +Starts an AI analysis of a recorded session + +Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + +### Example +```csharp +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using Devolutions.Gateway.Client.Api; +using Devolutions.Gateway.Client.Client; +using Devolutions.Gateway.Client.Model; + +namespace Example +{ + public class StartAiAnalysisExample + { + public static void Main() + { + Configuration config = new Configuration(); + config.BasePath = "http://localhost"; + // Configure Bearer token for authorization: scope_token + config.AccessToken = "YOUR_BEARER_TOKEN"; + + // create instances of HttpClient, HttpClientHandler to be reused later with different Api classes + HttpClient httpClient = new HttpClient(); + HttpClientHandler httpClientHandler = new HttpClientHandler(); + var apiInstance = new JrecApi(httpClient, config, httpClientHandler); + var sessionId = "sessionId_example"; // Guid | Recorded session ID + var aiAnalysisRequest = new AiAnalysisRequest(); // AiAnalysisRequest | AI analysis settings + + try + { + // Starts an AI analysis of a recorded session + TaskInfo result = apiInstance.StartAiAnalysis(sessionId, aiAnalysisRequest); + Debug.WriteLine(result); + } + catch (ApiException e) + { + Debug.Print("Exception when calling JrecApi.StartAiAnalysis: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); + } + } + } +} +``` + +#### Using the StartAiAnalysisWithHttpInfo variant +This returns an ApiResponse object which contains the response data, status code and headers. + +```csharp +try +{ + // Starts an AI analysis of a recorded session + ApiResponse response = apiInstance.StartAiAnalysisWithHttpInfo(sessionId, aiAnalysisRequest); + Debug.Write("Status Code: " + response.StatusCode); + Debug.Write("Response Headers: " + response.Headers); + Debug.Write("Response Body: " + response.Data); +} +catch (ApiException e) +{ + Debug.Print("Exception when calling JrecApi.StartAiAnalysisWithHttpInfo: " + e.Message); + Debug.Print("Status Code: " + e.ErrorCode); + Debug.Print(e.StackTrace); +} +``` + +### Parameters + +| Name | Type | Description | Notes | +|------|------|-------------|-------| +| **sessionId** | **Guid** | Recorded session ID | | +| **aiAnalysisRequest** | [**AiAnalysisRequest**](AiAnalysisRequest.md) | AI analysis settings | | + +### Return type + +[**TaskInfo**](TaskInfo.md) + +### Authorization + +[scope_token](../README.md#scope_token) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +| **200** | The same request came in before; nothing new starts | - | +| **202** | The AI analysis Task is created and runs in the background | * Location - Path of the new Task, `/jet/jrec/{session_id}/ai-analysis/{task_id}`
| +| **400** | Invalid request body or AI settings | - | +| **401** | Invalid or missing authorization token | - | +| **403** | Insufficient permissions | - | +| **404** | The session has no recording | - | +| **409** | The AI analysis cannot start now | - | +| **500** | Unexpected server error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/TaskInfo.md b/devolutions-gateway/openapi/dotnet-client/docs/TaskInfo.md new file mode 100644 index 000000000..af5bf17e1 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/TaskInfo.md @@ -0,0 +1,21 @@ +# Devolutions.Gateway.Client.Model.TaskInfo +A Task: the permanent record of one piece of work the provisioner asked Gateway to do The work runs in the background; `state` and `payload` tell how it goes. The shape of `params` and `payload` belongs to the kind. For `recording-ai-analysis`, `payload` is an `AiAnalysisRunningPayload` while `running`, an `AiAnalysisSucceededPayload` once `succeeded`, and an `AiAnalysisFailedPayload` once `failed`. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Attempts** | **int** | Number of attempts started so far. | +**CreatedAt** | **DateTime** | When the Task was created. | +**DeadlineAt** | **DateTime** | Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. | +**FinishedAt** | **DateTime?** | When it succeeded or failed. | [optional] +**Id** | **Guid** | Task ID, chosen by the provisioner. | +**Kind** | **string** | Kind of work, such as `recording-ai-analysis`. | +**Params** | **Object** | What was asked, without secrets. | +**Payload** | **Object** | Details of the current state; null while `queued`. | [optional] +**StartedAt** | **DateTime?** | When its first attempt started. | [optional] +**State** | **TaskState** | | +**Target** | **string** | What the work is about, such as a session ID. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/docs/TaskState.md b/devolutions-gateway/openapi/dotnet-client/docs/TaskState.md new file mode 100644 index 000000000..40328b8ec --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/docs/TaskState.md @@ -0,0 +1,9 @@ +# Devolutions.Gateway.Client.Model.TaskState + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs index 17a031fcd..0628ae821 100644 --- a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Api/JrecApi.cs @@ -68,6 +68,29 @@ public interface IJrecApiSync : IApiAccessor /// ApiResponse of Object(void) ApiResponse DeleteRecordingWithHttpInfo(Guid id); /// + /// Gets an AI analysis of a recorded session + /// + /// + /// Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// TaskInfo + TaskInfo GetAiAnalysis(Guid sessionId, Guid taskId); + + /// + /// Gets an AI analysis of a recorded session + /// + /// + /// Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// ApiResponse of TaskInfo + ApiResponse GetAiAnalysisWithHttpInfo(Guid sessionId, Guid taskId); + /// /// Lists all recordings stored on this instance /// /// Thrown when fails to make API call @@ -126,6 +149,29 @@ public interface IJrecApiSync : IApiAccessor /// Recorded session ID /// ApiResponse of FileParameter ApiResponse PullRecordingSessionWithHttpInfo(Guid id); + /// + /// Starts an AI analysis of a recorded session + /// + /// + /// Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// TaskInfo + TaskInfo StartAiAnalysis(Guid sessionId, AiAnalysisRequest aiAnalysisRequest); + + /// + /// Starts an AI analysis of a recorded session + /// + /// + /// Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// ApiResponse of TaskInfo + ApiResponse StartAiAnalysisWithHttpInfo(Guid sessionId, AiAnalysisRequest aiAnalysisRequest); #endregion Synchronous Operations } @@ -182,6 +228,31 @@ public interface IJrecApiAsync : IApiAccessor /// Task of ApiResponse System.Threading.Tasks.Task> DeleteRecordingWithHttpInfoAsync(Guid id, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); /// + /// Gets an AI analysis of a recorded session + /// + /// + /// Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// Cancellation Token to cancel the request. + /// Task of TaskInfo + System.Threading.Tasks.Task GetAiAnalysisAsync(Guid sessionId, Guid taskId, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + + /// + /// Gets an AI analysis of a recorded session + /// + /// + /// Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (TaskInfo) + System.Threading.Tasks.Task> GetAiAnalysisWithHttpInfoAsync(Guid sessionId, Guid taskId, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + /// /// Lists all recordings stored on this instance /// /// @@ -252,6 +323,31 @@ public interface IJrecApiAsync : IApiAccessor /// Cancellation Token to cancel the request. /// Task of ApiResponse (FileParameter) System.Threading.Tasks.Task> PullRecordingSessionWithHttpInfoAsync(Guid id, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + /// + /// Starts an AI analysis of a recorded session + /// + /// + /// Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// Cancellation Token to cancel the request. + /// Task of TaskInfo + System.Threading.Tasks.Task StartAiAnalysisAsync(Guid sessionId, AiAnalysisRequest aiAnalysisRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); + + /// + /// Starts an AI analysis of a recorded session + /// + /// + /// Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (TaskInfo) + System.Threading.Tasks.Task> StartAiAnalysisWithHttpInfoAsync(Guid sessionId, AiAnalysisRequest aiAnalysisRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)); #endregion Asynchronous Operations } @@ -709,6 +805,131 @@ public Devolutions.Gateway.Client.Client.ApiResponse DeleteRecordingWith return localVarResponse; } + /// + /// Gets an AI analysis of a recorded session Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// TaskInfo + public TaskInfo GetAiAnalysis(Guid sessionId, Guid taskId) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = GetAiAnalysisWithHttpInfo(sessionId, taskId); + return localVarResponse.Data; + } + + /// + /// Gets an AI analysis of a recorded session Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// ApiResponse of TaskInfo + public Devolutions.Gateway.Client.Client.ApiResponse GetAiAnalysisWithHttpInfo(Guid sessionId, Guid taskId) + { + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.PathParameters.Add("session_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(sessionId)); // path parameter + localVarRequestOptions.PathParameters.Add("task_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(taskId)); // path parameter + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + var localVarResponse = this.Client.Get("/jet/jrec/{session_id}/ai-analysis/{task_id}", localVarRequestOptions, this.Configuration); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("GetAiAnalysis", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + + /// + /// Gets an AI analysis of a recorded session Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// Cancellation Token to cancel the request. + /// Task of TaskInfo + public async System.Threading.Tasks.Task GetAiAnalysisAsync(Guid sessionId, Guid taskId, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = await GetAiAnalysisWithHttpInfoAsync(sessionId, taskId, cancellationToken).ConfigureAwait(false); + return localVarResponse.Data; + } + + /// + /// Gets an AI analysis of a recorded session Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// Task ID of the AI analysis + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (TaskInfo) + public async System.Threading.Tasks.Task> GetAiAnalysisWithHttpInfoAsync(Guid sessionId, Guid taskId, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.PathParameters.Add("session_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(sessionId)); // path parameter + localVarRequestOptions.PathParameters.Add("task_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(taskId)); // path parameter + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + + var localVarResponse = await this.AsynchronousClient.GetAsync("/jet/jrec/{session_id}/ai-analysis/{task_id}", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("GetAiAnalysis", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + /// /// Lists all recordings stored on this instance /// @@ -1080,5 +1301,140 @@ public Devolutions.Gateway.Client.Client.ApiResponse PullRecordin return localVarResponse; } + /// + /// Starts an AI analysis of a recorded session Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// TaskInfo + public TaskInfo StartAiAnalysis(Guid sessionId, AiAnalysisRequest aiAnalysisRequest) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = StartAiAnalysisWithHttpInfo(sessionId, aiAnalysisRequest); + return localVarResponse.Data; + } + + /// + /// Starts an AI analysis of a recorded session Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// ApiResponse of TaskInfo + public Devolutions.Gateway.Client.Client.ApiResponse StartAiAnalysisWithHttpInfo(Guid sessionId, AiAnalysisRequest aiAnalysisRequest) + { + // verify the required parameter 'aiAnalysisRequest' is set + if (aiAnalysisRequest == null) + throw new Devolutions.Gateway.Client.Client.ApiException(400, "Missing required parameter 'aiAnalysisRequest' when calling JrecApi->StartAiAnalysis"); + + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + "application/json" + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.PathParameters.Add("session_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(sessionId)); // path parameter + localVarRequestOptions.Data = aiAnalysisRequest; + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + var localVarResponse = this.Client.Post("/jet/jrec/{session_id}/ai-analysis", localVarRequestOptions, this.Configuration); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("StartAiAnalysis", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + + /// + /// Starts an AI analysis of a recorded session Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// Cancellation Token to cancel the request. + /// Task of TaskInfo + public async System.Threading.Tasks.Task StartAiAnalysisAsync(Guid sessionId, AiAnalysisRequest aiAnalysisRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + Devolutions.Gateway.Client.Client.ApiResponse localVarResponse = await StartAiAnalysisWithHttpInfoAsync(sessionId, aiAnalysisRequest, cancellationToken).ConfigureAwait(false); + return localVarResponse.Data; + } + + /// + /// Starts an AI analysis of a recorded session Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + /// + /// Thrown when fails to make API call + /// Recorded session ID + /// AI analysis settings + /// Cancellation Token to cancel the request. + /// Task of ApiResponse (TaskInfo) + public async System.Threading.Tasks.Task> StartAiAnalysisWithHttpInfoAsync(Guid sessionId, AiAnalysisRequest aiAnalysisRequest, System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + { + // verify the required parameter 'aiAnalysisRequest' is set + if (aiAnalysisRequest == null) + throw new Devolutions.Gateway.Client.Client.ApiException(400, "Missing required parameter 'aiAnalysisRequest' when calling JrecApi->StartAiAnalysis"); + + + Devolutions.Gateway.Client.Client.RequestOptions localVarRequestOptions = new Devolutions.Gateway.Client.Client.RequestOptions(); + + string[] _contentTypes = new string[] { + "application/json" + }; + + // to determine the Accept header + string[] _accepts = new string[] { + "application/json" + }; + + + var localVarContentType = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderContentType(_contentTypes); + if (localVarContentType != null) localVarRequestOptions.HeaderParameters.Add("Content-Type", localVarContentType); + + var localVarAccept = Devolutions.Gateway.Client.Client.ClientUtils.SelectHeaderAccept(_accepts); + if (localVarAccept != null) localVarRequestOptions.HeaderParameters.Add("Accept", localVarAccept); + + localVarRequestOptions.PathParameters.Add("session_id", Devolutions.Gateway.Client.Client.ClientUtils.ParameterToString(sessionId)); // path parameter + localVarRequestOptions.Data = aiAnalysisRequest; + + // authentication (scope_token) required + // bearer authentication required + if (!string.IsNullOrEmpty(this.Configuration.AccessToken) && !localVarRequestOptions.HeaderParameters.ContainsKey("Authorization")) + { + localVarRequestOptions.HeaderParameters.Add("Authorization", "Bearer " + this.Configuration.AccessToken); + } + + // make the HTTP request + + var localVarResponse = await this.AsynchronousClient.PostAsync("/jet/jrec/{session_id}/ai-analysis", localVarRequestOptions, this.Configuration, cancellationToken).ConfigureAwait(false); + + if (this.ExceptionFactory != null) + { + Exception _exception = this.ExceptionFactory("StartAiAnalysis", localVarResponse); + if (_exception != null) throw _exception; + } + + return localVarResponse; + } + } } diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs index 98c7ac937..f39128442 100644 --- a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AccessScope.cs @@ -145,7 +145,19 @@ public enum AccessScope /// Enum GatewayAgentRead for value: gateway.agent.read /// [EnumMember(Value = "gateway.agent.read")] - GatewayAgentRead = 19 + GatewayAgentRead = 19, + + /// + /// Enum GatewayTasksRecordingAiAnalysisStart for value: gateway.tasks.recording-ai-analysis.start + /// + [EnumMember(Value = "gateway.tasks.recording-ai-analysis.start")] + GatewayTasksRecordingAiAnalysisStart = 20, + + /// + /// Enum GatewayTasksRecordingAiAnalysisRead for value: gateway.tasks.recording-ai-analysis.read + /// + [EnumMember(Value = "gateway.tasks.recording-ai-analysis.read")] + GatewayTasksRecordingAiAnalysisRead = 21 } public static class AccessScopeExtensions @@ -195,6 +207,10 @@ public static string ToValue(this AccessScope variant) return "gateway.agent.delete"; case AccessScope.GatewayAgentRead: return "gateway.agent.read"; + case AccessScope.GatewayTasksRecordingAiAnalysisStart: + return "gateway.tasks.recording-ai-analysis.start"; + case AccessScope.GatewayTasksRecordingAiAnalysisRead: + return "gateway.tasks.recording-ai-analysis.read"; default: throw new ArgumentOutOfRangeException(nameof(variant), $"Unexpected variant: {variant}"); } diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflict.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflict.cs new file mode 100644 index 000000000..7fc19bc47 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflict.cs @@ -0,0 +1,99 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Why an AI analysis cannot start now + /// + [DataContract(Name = "AiAnalysisConflict")] + public partial class AiAnalysisConflict : IValidatableObject + { + + /// + /// Gets or Sets Code + /// + [DataMember(Name = "code", IsRequired = true, EmitDefaultValue = true)] + public AiAnalysisConflictCode Code { get; set; } + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisConflict() { } + /// + /// Initializes a new instance of the class. + /// + /// code (required). + /// The AI analysis of this session that is not finished yet; set for `analysis_in_progress`.. + public AiAnalysisConflict(AiAnalysisConflictCode code = default(AiAnalysisConflictCode), Guid? taskId = default(Guid?)) + { + this.Code = code; + this.TaskId = taskId; + } + + /// + /// The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. + /// + /// The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. + [DataMember(Name = "taskId", EmitDefaultValue = true)] + public Guid? TaskId { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisConflict {\n"); + sb.Append(" Code: ").Append(Code).Append("\n"); + sb.Append(" TaskId: ").Append(TaskId).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflictCode.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflictCode.cs new file mode 100644 index 000000000..52284a5d8 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisConflictCode.cs @@ -0,0 +1,76 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Defines AiAnalysisConflictCode + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum AiAnalysisConflictCode + { + /// + /// Enum TaskIdConflict for value: task_id_conflict + /// + [EnumMember(Value = "task_id_conflict")] + TaskIdConflict = 1, + + /// + /// Enum RecordingActive for value: recording_active + /// + [EnumMember(Value = "recording_active")] + RecordingActive = 2, + + /// + /// Enum AnalysisInProgress for value: analysis_in_progress + /// + [EnumMember(Value = "analysis_in_progress")] + AnalysisInProgress = 3 + } + + public static class AiAnalysisConflictCodeExtensions + { + /// + /// Returns the value as string for a given variant + /// + public static string ToValue(this AiAnalysisConflictCode variant) + { + switch (variant) + { + case AiAnalysisConflictCode.TaskIdConflict: + return "task_id_conflict"; + case AiAnalysisConflictCode.RecordingActive: + return "recording_active"; + case AiAnalysisConflictCode.AnalysisInProgress: + return "analysis_in_progress"; + default: + throw new ArgumentOutOfRangeException(nameof(variant), $"Unexpected variant: {variant}"); + } + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisFailedPayload.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisFailedPayload.cs new file mode 100644 index 000000000..228d4fe94 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisFailedPayload.cs @@ -0,0 +1,126 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Payload of a `failed` AI analysis Task: why it failed + /// + [DataContract(Name = "AiAnalysisFailedPayload")] + public partial class AiAnalysisFailedPayload : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisFailedPayload() { } + /// + /// Initializes a new instance of the class. + /// + /// Number of attempts started. (required). + /// What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": <payload before the timeout> }`. (required). + /// Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. (required). + public AiAnalysisFailedPayload(int attempts = default(int), Object details = default(Object), string reason = default(string)) + { + this.Attempts = attempts; + // to ensure "details" is required (not null) + if (details == null) + { + throw new ArgumentNullException("details is a required property for AiAnalysisFailedPayload and cannot be null"); + } + this.Details = details; + // to ensure "reason" is required (not null) + if (reason == null) + { + throw new ArgumentNullException("reason is a required property for AiAnalysisFailedPayload and cannot be null"); + } + this.Reason = reason; + } + + /// + /// Number of attempts started. + /// + /// Number of attempts started. + [DataMember(Name = "attempts", IsRequired = true, EmitDefaultValue = true)] + public int Attempts { get; set; } + + /// + /// What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": <payload before the timeout> }`. + /// + /// What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": <payload before the timeout> }`. + [DataMember(Name = "details", IsRequired = true, EmitDefaultValue = true)] + public Object Details { get; set; } + + /// + /// Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. + /// + /// Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. + [DataMember(Name = "reason", IsRequired = true, EmitDefaultValue = true)] + public string Reason { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisFailedPayload {\n"); + sb.Append(" Attempts: ").Append(Attempts).Append("\n"); + sb.Append(" Details: ").Append(Details).Append("\n"); + sb.Append(" Reason: ").Append(Reason).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // Attempts (int) minimum + if (this.Attempts < (int)0) + { + yield return new ValidationResult("Invalid value for Attempts, must be a value greater than or equal to 0.", new [] { "Attempts" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRequest.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRequest.cs new file mode 100644 index 000000000..e08f5e153 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRequest.cs @@ -0,0 +1,155 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Settings of an AI analysis + /// + [DataContract(Name = "AiAnalysisRequest")] + public partial class AiAnalysisRequest : IValidatableObject + { + + /// + /// Gets or Sets Provider + /// + [DataMember(Name = "provider", IsRequired = true, EmitDefaultValue = true)] + public AiProvider Provider { get; set; } + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisRequest() { } + /// + /// Initializes a new instance of the class. + /// + /// API key of the provider, kept in memory only until the Task ends. (required). + /// Endpoint of the provider API, kept in memory only; required for `openai-compatible`.. + /// Upper bound of tokens in each AI answer.. + /// Model identifier, passed to the provider as is. (required). + /// provider (required). + /// Task ID, chosen by the caller so it can send the same request again safely. (required). + public AiAnalysisRequest(string apiKey = default(string), string baseUrl = default(string), int? maxOutputTokens = default(int?), string model = default(string), AiProvider provider = default(AiProvider), Guid taskId = default(Guid)) + { + // to ensure "apiKey" is required (not null) + if (apiKey == null) + { + throw new ArgumentNullException("apiKey is a required property for AiAnalysisRequest and cannot be null"); + } + this.ApiKey = apiKey; + // to ensure "model" is required (not null) + if (model == null) + { + throw new ArgumentNullException("model is a required property for AiAnalysisRequest and cannot be null"); + } + this.Model = model; + this.Provider = provider; + this.TaskId = taskId; + this.BaseUrl = baseUrl; + this.MaxOutputTokens = maxOutputTokens; + } + + /// + /// API key of the provider, kept in memory only until the Task ends. + /// + /// API key of the provider, kept in memory only until the Task ends. + [DataMember(Name = "apiKey", IsRequired = true, EmitDefaultValue = true)] + public string ApiKey { get; set; } + + /// + /// Endpoint of the provider API, kept in memory only; required for `openai-compatible`. + /// + /// Endpoint of the provider API, kept in memory only; required for `openai-compatible`. + [DataMember(Name = "baseUrl", EmitDefaultValue = true)] + public string BaseUrl { get; set; } + + /// + /// Upper bound of tokens in each AI answer. + /// + /// Upper bound of tokens in each AI answer. + [DataMember(Name = "maxOutputTokens", EmitDefaultValue = true)] + public int? MaxOutputTokens { get; set; } + + /// + /// Model identifier, passed to the provider as is. + /// + /// Model identifier, passed to the provider as is. + [DataMember(Name = "model", IsRequired = true, EmitDefaultValue = true)] + public string Model { get; set; } + + /// + /// Task ID, chosen by the caller so it can send the same request again safely. + /// + /// Task ID, chosen by the caller so it can send the same request again safely. + [DataMember(Name = "taskId", IsRequired = true, EmitDefaultValue = true)] + public Guid TaskId { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisRequest {\n"); + sb.Append(" ApiKey: ").Append(ApiKey).Append("\n"); + sb.Append(" BaseUrl: ").Append(BaseUrl).Append("\n"); + sb.Append(" MaxOutputTokens: ").Append(MaxOutputTokens).Append("\n"); + sb.Append(" Model: ").Append(Model).Append("\n"); + sb.Append(" Provider: ").Append(Provider).Append("\n"); + sb.Append(" TaskId: ").Append(TaskId).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // MaxOutputTokens (int?) minimum + if (this.MaxOutputTokens < (int?)0) + { + yield return new ValidationResult("Invalid value for MaxOutputTokens, must be a value greater than or equal to 0.", new [] { "MaxOutputTokens" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRunningPayload.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRunningPayload.cs new file mode 100644 index 000000000..eb0fa2640 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisRunningPayload.cs @@ -0,0 +1,127 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Payload of a `running` AI analysis Task: its progress + /// + [DataContract(Name = "AiAnalysisRunningPayload")] + public partial class AiAnalysisRunningPayload : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisRunningPayload() { } + /// + /// Initializes a new instance of the class. + /// + /// Work done in the step: bytes read while `reading`, parts described while `describing`.. + /// Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). (required). + /// Work the step has in all, in the same unit as `done`.. + public AiAnalysisRunningPayload(long? done = default(long?), string step = default(string), long? total = default(long?)) + { + // to ensure "step" is required (not null) + if (step == null) + { + throw new ArgumentNullException("step is a required property for AiAnalysisRunningPayload and cannot be null"); + } + this.Step = step; + this.Done = done; + this.Total = total; + } + + /// + /// Work done in the step: bytes read while `reading`, parts described while `describing`. + /// + /// Work done in the step: bytes read while `reading`, parts described while `describing`. + [DataMember(Name = "done", EmitDefaultValue = true)] + public long? Done { get; set; } + + /// + /// Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). + /// + /// Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). + [DataMember(Name = "step", IsRequired = true, EmitDefaultValue = true)] + public string Step { get; set; } + + /// + /// Work the step has in all, in the same unit as `done`. + /// + /// Work the step has in all, in the same unit as `done`. + [DataMember(Name = "total", EmitDefaultValue = true)] + public long? Total { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisRunningPayload {\n"); + sb.Append(" Done: ").Append(Done).Append("\n"); + sb.Append(" Step: ").Append(Step).Append("\n"); + sb.Append(" Total: ").Append(Total).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // Done (long?) minimum + if (this.Done < (long?)0) + { + yield return new ValidationResult("Invalid value for Done, must be a value greater than or equal to 0.", new [] { "Done" }); + } + + // Total (long?) minimum + if (this.Total < (long?)0) + { + yield return new ValidationResult("Invalid value for Total, must be a value greater than or equal to 0.", new [] { "Total" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisSucceededPayload.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisSucceededPayload.cs new file mode 100644 index 000000000..ff1339300 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisSucceededPayload.cs @@ -0,0 +1,134 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Payload of a `succeeded` AI analysis Task: the log it added to the session + /// + [DataContract(Name = "AiAnalysisSucceededPayload")] + public partial class AiAnalysisSucceededPayload : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisSucceededPayload() { } + /// + /// Initializes a new instance of the class. + /// + /// Name of the new log in the session manifest, such as `ai-analysis-0.slog`. (required). + /// Model that wrote the log, as reported by the AI provider; the requested model when it reports none. (required). + /// Version of the instructions sent to the AI provider. (required). + /// usage. + public AiAnalysisSucceededPayload(string fileName = default(string), string model = default(string), string promptVersion = default(string), AiAnalysisUsage usage = default(AiAnalysisUsage)) + { + // to ensure "fileName" is required (not null) + if (fileName == null) + { + throw new ArgumentNullException("fileName is a required property for AiAnalysisSucceededPayload and cannot be null"); + } + this.FileName = fileName; + // to ensure "model" is required (not null) + if (model == null) + { + throw new ArgumentNullException("model is a required property for AiAnalysisSucceededPayload and cannot be null"); + } + this.Model = model; + // to ensure "promptVersion" is required (not null) + if (promptVersion == null) + { + throw new ArgumentNullException("promptVersion is a required property for AiAnalysisSucceededPayload and cannot be null"); + } + this.PromptVersion = promptVersion; + this.Usage = usage; + } + + /// + /// Name of the new log in the session manifest, such as `ai-analysis-0.slog`. + /// + /// Name of the new log in the session manifest, such as `ai-analysis-0.slog`. + [DataMember(Name = "fileName", IsRequired = true, EmitDefaultValue = true)] + public string FileName { get; set; } + + /// + /// Model that wrote the log, as reported by the AI provider; the requested model when it reports none. + /// + /// Model that wrote the log, as reported by the AI provider; the requested model when it reports none. + [DataMember(Name = "model", IsRequired = true, EmitDefaultValue = true)] + public string Model { get; set; } + + /// + /// Version of the instructions sent to the AI provider. + /// + /// Version of the instructions sent to the AI provider. + [DataMember(Name = "promptVersion", IsRequired = true, EmitDefaultValue = true)] + public string PromptVersion { get; set; } + + /// + /// Gets or Sets Usage + /// + [DataMember(Name = "usage", EmitDefaultValue = true)] + public AiAnalysisUsage Usage { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisSucceededPayload {\n"); + sb.Append(" FileName: ").Append(FileName).Append("\n"); + sb.Append(" Model: ").Append(Model).Append("\n"); + sb.Append(" PromptVersion: ").Append(PromptVersion).Append("\n"); + sb.Append(" Usage: ").Append(Usage).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisUsage.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisUsage.cs new file mode 100644 index 000000000..812d5255d --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiAnalysisUsage.cs @@ -0,0 +1,110 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Tokens counted by an AI provider + /// + [DataContract(Name = "AiAnalysisUsage")] + public partial class AiAnalysisUsage : IValidatableObject + { + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected AiAnalysisUsage() { } + /// + /// Initializes a new instance of the class. + /// + /// inputTokens (required). + /// outputTokens (required). + public AiAnalysisUsage(long inputTokens = default(long), long outputTokens = default(long)) + { + this.InputTokens = inputTokens; + this.OutputTokens = outputTokens; + } + + /// + /// Gets or Sets InputTokens + /// + [DataMember(Name = "inputTokens", IsRequired = true, EmitDefaultValue = true)] + public long InputTokens { get; set; } + + /// + /// Gets or Sets OutputTokens + /// + [DataMember(Name = "outputTokens", IsRequired = true, EmitDefaultValue = true)] + public long OutputTokens { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class AiAnalysisUsage {\n"); + sb.Append(" InputTokens: ").Append(InputTokens).Append("\n"); + sb.Append(" OutputTokens: ").Append(OutputTokens).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // InputTokens (long) minimum + if (this.InputTokens < (long)0) + { + yield return new ValidationResult("Invalid value for InputTokens, must be a value greater than or equal to 0.", new [] { "InputTokens" }); + } + + // OutputTokens (long) minimum + if (this.OutputTokens < (long)0) + { + yield return new ValidationResult("Invalid value for OutputTokens, must be a value greater than or equal to 0.", new [] { "OutputTokens" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiProvider.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiProvider.cs new file mode 100644 index 000000000..da7b2dbe9 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/AiProvider.cs @@ -0,0 +1,93 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// AI provider to ask; `openai-compatible` needs a base URL + /// + /// AI provider to ask; `openai-compatible` needs a base URL + [JsonConverter(typeof(StringEnumConverter))] + public enum AiProvider + { + /// + /// Enum Openai for value: openai + /// + [EnumMember(Value = "openai")] + Openai = 1, + + /// + /// Enum Anthropic for value: anthropic + /// + [EnumMember(Value = "anthropic")] + Anthropic = 2, + + /// + /// Enum Mistral for value: mistral + /// + [EnumMember(Value = "mistral")] + Mistral = 3, + + /// + /// Enum Gemini for value: gemini + /// + [EnumMember(Value = "gemini")] + Gemini = 4, + + /// + /// Enum OpenaiCompatible for value: openai-compatible + /// + [EnumMember(Value = "openai-compatible")] + OpenaiCompatible = 5 + } + + public static class AiProviderExtensions + { + /// + /// Returns the value as string for a given variant + /// + public static string ToValue(this AiProvider variant) + { + switch (variant) + { + case AiProvider.Openai: + return "openai"; + case AiProvider.Anthropic: + return "anthropic"; + case AiProvider.Mistral: + return "mistral"; + case AiProvider.Gemini: + return "gemini"; + case AiProvider.OpenaiCompatible: + return "openai-compatible"; + default: + throw new ArgumentOutOfRangeException(nameof(variant), $"Unexpected variant: {variant}"); + } + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskInfo.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskInfo.cs new file mode 100644 index 000000000..3bc67d861 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskInfo.cs @@ -0,0 +1,210 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// A Task: the permanent record of one piece of work the provisioner asked Gateway to do The work runs in the background; `state` and `payload` tell how it goes. The shape of `params` and `payload` belongs to the kind. For `recording-ai-analysis`, `payload` is an `AiAnalysisRunningPayload` while `running`, an `AiAnalysisSucceededPayload` once `succeeded`, and an `AiAnalysisFailedPayload` once `failed`. + /// + [DataContract(Name = "TaskInfo")] + public partial class TaskInfo : IValidatableObject + { + + /// + /// Gets or Sets State + /// + [DataMember(Name = "state", IsRequired = true, EmitDefaultValue = true)] + public TaskState State { get; set; } + /// + /// Initializes a new instance of the class. + /// + [JsonConstructorAttribute] + protected TaskInfo() { } + /// + /// Initializes a new instance of the class. + /// + /// Number of attempts started so far. (required). + /// When the Task was created. (required). + /// Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. (required). + /// When it succeeded or failed.. + /// Task ID, chosen by the provisioner. (required). + /// Kind of work, such as `recording-ai-analysis`. (required). + /// What was asked, without secrets. (required). + /// Details of the current state; null while `queued`.. + /// When its first attempt started.. + /// state (required). + /// What the work is about, such as a session ID. (required). + public TaskInfo(int attempts = default(int), DateTime createdAt = default(DateTime), DateTime deadlineAt = default(DateTime), DateTime? finishedAt = default(DateTime?), Guid id = default(Guid), string kind = default(string), Object varParams = default(Object), Object payload = default(Object), DateTime? startedAt = default(DateTime?), TaskState state = default(TaskState), string target = default(string)) + { + this.Attempts = attempts; + this.CreatedAt = createdAt; + this.DeadlineAt = deadlineAt; + this.Id = id; + // to ensure "kind" is required (not null) + if (kind == null) + { + throw new ArgumentNullException("kind is a required property for TaskInfo and cannot be null"); + } + this.Kind = kind; + // to ensure "varParams" is required (not null) + if (varParams == null) + { + throw new ArgumentNullException("varParams is a required property for TaskInfo and cannot be null"); + } + this.Params = varParams; + this.State = state; + // to ensure "target" is required (not null) + if (target == null) + { + throw new ArgumentNullException("target is a required property for TaskInfo and cannot be null"); + } + this.Target = target; + this.FinishedAt = finishedAt; + this.Payload = payload; + this.StartedAt = startedAt; + } + + /// + /// Number of attempts started so far. + /// + /// Number of attempts started so far. + [DataMember(Name = "attempts", IsRequired = true, EmitDefaultValue = true)] + public int Attempts { get; set; } + + /// + /// When the Task was created. + /// + /// When the Task was created. + [DataMember(Name = "createdAt", IsRequired = true, EmitDefaultValue = true)] + public DateTime CreatedAt { get; set; } + + /// + /// Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. + /// + /// Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. + [DataMember(Name = "deadlineAt", IsRequired = true, EmitDefaultValue = true)] + public DateTime DeadlineAt { get; set; } + + /// + /// When it succeeded or failed. + /// + /// When it succeeded or failed. + [DataMember(Name = "finishedAt", EmitDefaultValue = true)] + public DateTime? FinishedAt { get; set; } + + /// + /// Task ID, chosen by the provisioner. + /// + /// Task ID, chosen by the provisioner. + [DataMember(Name = "id", IsRequired = true, EmitDefaultValue = true)] + public Guid Id { get; set; } + + /// + /// Kind of work, such as `recording-ai-analysis`. + /// + /// Kind of work, such as `recording-ai-analysis`. + [DataMember(Name = "kind", IsRequired = true, EmitDefaultValue = true)] + public string Kind { get; set; } + + /// + /// What was asked, without secrets. + /// + /// What was asked, without secrets. + [DataMember(Name = "params", IsRequired = true, EmitDefaultValue = true)] + public Object Params { get; set; } + + /// + /// Details of the current state; null while `queued`. + /// + /// Details of the current state; null while `queued`. + [DataMember(Name = "payload", EmitDefaultValue = true)] + public Object Payload { get; set; } + + /// + /// When its first attempt started. + /// + /// When its first attempt started. + [DataMember(Name = "startedAt", EmitDefaultValue = true)] + public DateTime? StartedAt { get; set; } + + /// + /// What the work is about, such as a session ID. + /// + /// What the work is about, such as a session ID. + [DataMember(Name = "target", IsRequired = true, EmitDefaultValue = true)] + public string Target { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("class TaskInfo {\n"); + sb.Append(" Attempts: ").Append(Attempts).Append("\n"); + sb.Append(" CreatedAt: ").Append(CreatedAt).Append("\n"); + sb.Append(" DeadlineAt: ").Append(DeadlineAt).Append("\n"); + sb.Append(" FinishedAt: ").Append(FinishedAt).Append("\n"); + sb.Append(" Id: ").Append(Id).Append("\n"); + sb.Append(" Kind: ").Append(Kind).Append("\n"); + sb.Append(" Params: ").Append(Params).Append("\n"); + sb.Append(" Payload: ").Append(Payload).Append("\n"); + sb.Append(" StartedAt: ").Append(StartedAt).Append("\n"); + sb.Append(" State: ").Append(State).Append("\n"); + sb.Append(" Target: ").Append(Target).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public virtual string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + } + + /// + /// To validate all properties of the instance + /// + /// Validation context + /// Validation Result + IEnumerable IValidatableObject.Validate(ValidationContext validationContext) + { + // Attempts (int) minimum + if (this.Attempts < (int)0) + { + yield return new ValidationResult("Invalid value for Attempts, must be a value greater than or equal to 0.", new [] { "Attempts" }); + } + + yield break; + } + } + +} diff --git a/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskState.cs b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskState.cs new file mode 100644 index 000000000..425797788 --- /dev/null +++ b/devolutions-gateway/openapi/dotnet-client/src/Devolutions.Gateway.Client/Model/TaskState.cs @@ -0,0 +1,84 @@ +/* + * devolutions-gateway + * + * Protocol-aware fine-grained relay server + * + * The version of the OpenAPI document: 2026.2.4 + * Contact: infos@devolutions.net + * Generated by: https://github.com/openapitools/openapi-generator.git + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using System.ComponentModel.DataAnnotations; +using FileParameter = Devolutions.Gateway.Client.Client.FileParameter; +using OpenAPIDateConverter = Devolutions.Gateway.Client.Client.OpenAPIDateConverter; + +namespace Devolutions.Gateway.Client.Model +{ + /// + /// Defines TaskState + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum TaskState + { + /// + /// Enum Queued for value: queued + /// + [EnumMember(Value = "queued")] + Queued = 1, + + /// + /// Enum Running for value: running + /// + [EnumMember(Value = "running")] + Running = 2, + + /// + /// Enum Succeeded for value: succeeded + /// + [EnumMember(Value = "succeeded")] + Succeeded = 3, + + /// + /// Enum Failed for value: failed + /// + [EnumMember(Value = "failed")] + Failed = 4 + } + + public static class TaskStateExtensions + { + /// + /// Returns the value as string for a given variant + /// + public static string ToValue(this TaskState variant) + { + switch (variant) + { + case TaskState.Queued: + return "queued"; + case TaskState.Running: + return "running"; + case TaskState.Succeeded: + return "succeeded"; + case TaskState.Failed: + return "failed"; + default: + throw new ArgumentOutOfRangeException(nameof(variant), $"Unexpected variant: {variant}"); + } + } + } + +} diff --git a/devolutions-gateway/openapi/gateway-api.yaml b/devolutions-gateway/openapi/gateway-api.yaml index a2c1b1b2b..1dc556157 100644 --- a/devolutions-gateway/openapi/gateway-api.yaml +++ b/devolutions-gateway/openapi/gateway-api.yaml @@ -346,6 +346,118 @@ paths: security: - jrec_token: - pull + /jet/jrec/{session_id}/ai-analysis: + post: + tags: + - Jrec + summary: Starts an AI analysis of a recorded session + description: |- + Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a + new log, listed under `artifacts.ai-analysis` in its manifest. + The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, + which the `Location` header of a 202 answer names. + + Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from + scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. + The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished + Tasks. + operationId: StartAiAnalysis + parameters: + - name: session_id + in: path + description: Recorded session ID + required: true + schema: + type: string + format: uuid + requestBody: + description: AI analysis settings + content: + application/json: + schema: + $ref: '#/components/schemas/AiAnalysisRequest' + required: true + responses: + '200': + description: The same request came in before; nothing new starts + content: + application/json: + schema: + $ref: '#/components/schemas/TaskInfo' + '202': + description: The AI analysis Task is created and runs in the background + headers: + Location: + schema: + type: string + description: Path of the new Task, `/jet/jrec/{session_id}/ai-analysis/{task_id}` + content: + application/json: + schema: + $ref: '#/components/schemas/TaskInfo' + '400': + description: Invalid request body or AI settings + '401': + description: Invalid or missing authorization token + '403': + description: Insufficient permissions + '404': + description: The session has no recording + '409': + description: The AI analysis cannot start now + content: + application/json: + schema: + $ref: '#/components/schemas/AiAnalysisConflict' + '500': + description: Unexpected server error + security: + - scope_token: + - gateway.tasks.recording-ai-analysis.start + /jet/jrec/{session_id}/ai-analysis/{task_id}: + get: + tags: + - Jrec + summary: Gets an AI analysis of a recorded session + description: |- + Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. + Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + operationId: GetAiAnalysis + parameters: + - name: session_id + in: path + description: Recorded session ID + required: true + schema: + type: string + format: uuid + - name: task_id + in: path + description: Task ID of the AI analysis + required: true + schema: + type: string + format: uuid + responses: + '200': + description: The AI analysis Task + content: + application/json: + schema: + $ref: '#/components/schemas/TaskInfo' + '400': + description: Bad request + '401': + description: Invalid or missing authorization token + '403': + description: Insufficient permissions + '404': + description: The session has no AI analysis with this Task ID + '500': + description: Unexpected server error + security: + - scope_token: + - gateway.tasks.recording-ai-analysis.read /jet/jrl: post: tags: @@ -1326,6 +1438,8 @@ components: - gateway.net.monitor.drain - gateway.agent.delete - gateway.agent.read + - gateway.tasks.recording-ai-analysis.start + - gateway.tasks.recording-ai-analysis.read AckRequest: type: object required: @@ -1402,6 +1516,143 @@ components: - offline - online - unresponsive + AiAnalysisConflict: + type: object + description: Why an AI analysis cannot start now + required: + - code + properties: + code: + $ref: '#/components/schemas/AiAnalysisConflictCode' + taskId: + type: string + format: uuid + description: The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. + nullable: true + AiAnalysisConflictCode: + type: string + enum: + - task_id_conflict + - recording_active + - analysis_in_progress + AiAnalysisFailedPayload: + type: object + description: 'Payload of a `failed` AI analysis Task: why it failed' + required: + - reason + - details + - attempts + properties: + attempts: + type: integer + format: int32 + description: Number of attempts started. + minimum: 0 + details: + description: 'What went wrong: a text, except for `timed out`, where it is `{ "lastPayload": }`.' + reason: + type: string + description: 'Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`.' + AiAnalysisRequest: + type: object + description: Settings of an AI analysis + required: + - taskId + - provider + - model + - apiKey + properties: + apiKey: + type: string + format: password + description: API key of the provider, kept in memory only until the Task ends. + baseUrl: + type: string + description: Endpoint of the provider API, kept in memory only; required for `openai-compatible`. + nullable: true + maxOutputTokens: + type: integer + format: int32 + description: Upper bound of tokens in each AI answer. + nullable: true + minimum: 0 + model: + type: string + description: Model identifier, passed to the provider as is. + provider: + $ref: '#/components/schemas/AiProvider' + taskId: + type: string + format: uuid + description: Task ID, chosen by the caller so it can send the same request again safely. + AiAnalysisRunningPayload: + type: object + description: 'Payload of a `running` AI analysis Task: its progress' + required: + - step + properties: + done: + type: integer + format: int64 + description: 'Work done in the step: bytes read while `reading`, parts described while `describing`.' + nullable: true + minimum: 0 + step: + type: string + description: |- + Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is + asked about the recording, part by part), then `writing` (the log is written). + total: + type: integer + format: int64 + description: Work the step has in all, in the same unit as `done`. + nullable: true + minimum: 0 + AiAnalysisSucceededPayload: + type: object + description: 'Payload of a `succeeded` AI analysis Task: the log it added to the session' + required: + - fileName + - model + - promptVersion + properties: + fileName: + type: string + description: Name of the new log in the session manifest, such as `ai-analysis-0.slog`. + model: + type: string + description: Model that wrote the log, as reported by the AI provider; the requested model when it reports none. + promptVersion: + type: string + description: Version of the instructions sent to the AI provider. + usage: + allOf: + - $ref: '#/components/schemas/AiAnalysisUsage' + nullable: true + AiAnalysisUsage: + type: object + description: Tokens counted by an AI provider + required: + - inputTokens + - outputTokens + properties: + inputTokens: + type: integer + format: int64 + minimum: 0 + outputTokens: + type: integer + format: int64 + minimum: 0 + AiProvider: + type: string + description: AI provider to ask; `openai-compatible` needs a base URL + enum: + - openai + - anthropic + - mistral + - gemini + - openai-compatible AppCredential: type: object required: @@ -2392,6 +2643,76 @@ components: Format: `://:` (port is required). Supported schemes are `tcp` and `udp`. nullable: true + TaskInfo: + type: object + description: |- + A Task: the permanent record of one piece of work the provisioner asked Gateway to do + + The work runs in the background; `state` and `payload` tell how it goes. + The shape of `params` and `payload` belongs to the kind. + For `recording-ai-analysis`, `payload` is an `AiAnalysisRunningPayload` while `running`, an + `AiAnalysisSucceededPayload` once `succeeded`, and an `AiAnalysisFailedPayload` once `failed`. + required: + - id + - kind + - target + - params + - state + - attempts + - createdAt + - deadlineAt + properties: + attempts: + type: integer + format: int32 + description: Number of attempts started so far. + minimum: 0 + createdAt: + type: string + format: date-time + description: When the Task was created. + deadlineAt: + type: string + format: date-time + description: |- + Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier + once too little time is left for another attempt. + finishedAt: + type: string + format: date-time + description: When it succeeded or failed. + nullable: true + id: + type: string + format: uuid + description: Task ID, chosen by the provisioner. + kind: + type: string + description: Kind of work, such as `recording-ai-analysis`. + params: + type: object + description: What was asked, without secrets. + payload: + type: object + description: Details of the current state; null while `queued`. + nullable: true + startedAt: + type: string + format: date-time + description: When its first attempt started. + nullable: true + state: + $ref: '#/components/schemas/TaskState' + target: + type: string + description: What the work is about, such as a session ID. + TaskState: + type: string + enum: + - queued + - running + - succeeded + - failed TrafficEventResponse: type: object required: diff --git a/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES b/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES index 84eba61ed..0db9467fa 100644 --- a/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES +++ b/devolutions-gateway/openapi/ts-angular-client/.openapi-generator/FILES @@ -26,6 +26,14 @@ model/addressFamily.ts model/agentDomainAdvertisement.ts model/agentInfo.ts model/agentStatus.ts +model/aiAnalysisConflict.ts +model/aiAnalysisConflictCode.ts +model/aiAnalysisFailedPayload.ts +model/aiAnalysisRequest.ts +model/aiAnalysisRunningPayload.ts +model/aiAnalysisSucceededPayload.ts +model/aiAnalysisUsage.ts +model/aiProvider.ts model/appCredential.ts model/appCredentialKind.ts model/appTokenContentType.ts @@ -78,6 +86,8 @@ model/setUpdateScheduleRequest.ts model/subProvisionerKey.ts model/subscriber.ts model/targetConnectionOptions.ts +model/taskInfo.ts +model/taskState.ts model/trafficEventResponse.ts model/transportProtocolResponse.ts model/updateProductInfo.ts diff --git a/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts b/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts index b1e8e0a18..537d3611a 100644 --- a/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts +++ b/devolutions-gateway/openapi/ts-angular-client/api/jrec.service.ts @@ -16,8 +16,14 @@ import { HttpClient, HttpHeaders, HttpParams, import { CustomHttpParameterCodec } from '../encoder'; import { Observable } from 'rxjs'; +// @ts-ignore +import { AiAnalysisConflict } from '../model/aiAnalysisConflict'; +// @ts-ignore +import { AiAnalysisRequest } from '../model/aiAnalysisRequest'; // @ts-ignore import { DeleteManyResult } from '../model/deleteManyResult'; +// @ts-ignore +import { TaskInfo } from '../model/taskInfo'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; @@ -242,6 +248,82 @@ export class JrecService { ); } + /** + * Gets an AI analysis of a recorded session + * Returns the Task of the analysis: its state, its progress while it runs, and its result once it is over. Tasks are kept forever, across Gateway restarts; an unfinished Task past its deadline is reported as failed. + * @param sessionId Recorded session ID + * @param taskId Task ID of the AI analysis + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getAiAnalysis(sessionId: string, taskId: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getAiAnalysis(sessionId: string, taskId: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getAiAnalysis(sessionId: string, taskId: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getAiAnalysis(sessionId: string, taskId: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (sessionId === null || sessionId === undefined) { + throw new Error('Required parameter sessionId was null or undefined when calling getAiAnalysis.'); + } + if (taskId === null || taskId === undefined) { + throw new Error('Required parameter taskId was null or undefined when calling getAiAnalysis.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (scope_token) required + localVarCredential = this.configuration.lookupCredential('scope_token'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/jet/jrec/${this.configuration.encodeParam({name: "sessionId", value: sessionId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: "uuid"})}/ai-analysis/${this.configuration.encodeParam({name: "taskId", value: taskId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: "uuid"})}`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + /** * Lists all recordings stored on this instance * @param active When true, only the active recordings are returned @@ -445,4 +527,90 @@ export class JrecService { ); } + /** + * Starts an AI analysis of a recorded session + * Gateway asks the AI provider what the user did in the session recording, and adds the answer to the session as a new log, listed under `artifacts.ai-analysis` in its manifest. The work runs in the background as a Task; follow it with `GET /jet/jrec/{session_id}/ai-analysis/{task_id}`, which the `Location` header of a 202 answer names. Sending the same request again returns the same Task and starts nothing new. A new Task for the session starts from scratch: it removes the earlier AI analysis of the session, even a succeeded one, so a session holds at most one. The API key and the base URL are kept in memory only, until the Task ends; a Gateway restart fails the unfinished Tasks. + * @param sessionId Recorded session ID + * @param aiAnalysisRequest AI analysis settings + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public startAiAnalysis(sessionId: string, aiAnalysisRequest: AiAnalysisRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public startAiAnalysis(sessionId: string, aiAnalysisRequest: AiAnalysisRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public startAiAnalysis(sessionId: string, aiAnalysisRequest: AiAnalysisRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public startAiAnalysis(sessionId: string, aiAnalysisRequest: AiAnalysisRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (sessionId === null || sessionId === undefined) { + throw new Error('Required parameter sessionId was null or undefined when calling startAiAnalysis.'); + } + if (aiAnalysisRequest === null || aiAnalysisRequest === undefined) { + throw new Error('Required parameter aiAnalysisRequest was null or undefined when calling startAiAnalysis.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (scope_token) required + localVarCredential = this.configuration.lookupCredential('scope_token'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/jet/jrec/${this.configuration.encodeParam({name: "sessionId", value: sessionId, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: "uuid"})}/ai-analysis`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: aiAnalysisRequest, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + } diff --git a/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts b/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts index 0827c35b7..4b0350033 100644 --- a/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts +++ b/devolutions-gateway/openapi/ts-angular-client/model/accessScope.ts @@ -9,7 +9,7 @@ */ -export type AccessScope = '*' | 'gateway.sessions.read' | 'gateway.session.terminate' | 'gateway.associations.read' | 'gateway.diagnostics.read' | 'gateway.jrl.read' | 'gateway.config.write' | 'gateway.heartbeat.read' | 'gateway.recording.delete' | 'gateway.recordings.read' | 'gateway.update' | 'gateway.update.read' | 'gateway.preflight' | 'gateway.traffic.claim' | 'gateway.traffic.ack' | 'gateway.net.monitor.config' | 'gateway.net.monitor.drain' | 'gateway.agent.delete' | 'gateway.agent.read'; +export type AccessScope = '*' | 'gateway.sessions.read' | 'gateway.session.terminate' | 'gateway.associations.read' | 'gateway.diagnostics.read' | 'gateway.jrl.read' | 'gateway.config.write' | 'gateway.heartbeat.read' | 'gateway.recording.delete' | 'gateway.recordings.read' | 'gateway.update' | 'gateway.update.read' | 'gateway.preflight' | 'gateway.traffic.claim' | 'gateway.traffic.ack' | 'gateway.net.monitor.config' | 'gateway.net.monitor.drain' | 'gateway.agent.delete' | 'gateway.agent.read' | 'gateway.tasks.recording-ai-analysis.start' | 'gateway.tasks.recording-ai-analysis.read'; export const AccessScope = { Star: '*' as AccessScope, @@ -30,6 +30,8 @@ export const AccessScope = { GatewayNetMonitorConfig: 'gateway.net.monitor.config' as AccessScope, GatewayNetMonitorDrain: 'gateway.net.monitor.drain' as AccessScope, GatewayAgentDelete: 'gateway.agent.delete' as AccessScope, - GatewayAgentRead: 'gateway.agent.read' as AccessScope + GatewayAgentRead: 'gateway.agent.read' as AccessScope, + GatewayTasksRecordingAiAnalysisStart: 'gateway.tasks.recording-ai-analysis.start' as AccessScope, + GatewayTasksRecordingAiAnalysisRead: 'gateway.tasks.recording-ai-analysis.read' as AccessScope }; diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflict.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflict.ts new file mode 100644 index 000000000..322bc7147 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflict.ts @@ -0,0 +1,26 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { AiAnalysisConflictCode } from './aiAnalysisConflictCode'; + + +/** + * Why an AI analysis cannot start now + */ +export interface AiAnalysisConflict { + code: AiAnalysisConflictCode; + /** + * The AI analysis of this session that is not finished yet; set for `analysis_in_progress`. + */ + taskId?: string | null; +} +export namespace AiAnalysisConflict { +} + + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflictCode.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflictCode.ts new file mode 100644 index 000000000..ac7e69575 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisConflictCode.ts @@ -0,0 +1,19 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type AiAnalysisConflictCode = 'task_id_conflict' | 'recording_active' | 'analysis_in_progress'; + +export const AiAnalysisConflictCode = { + TaskIdConflict: 'task_id_conflict' as AiAnalysisConflictCode, + RecordingActive: 'recording_active' as AiAnalysisConflictCode, + AnalysisInProgress: 'analysis_in_progress' as AiAnalysisConflictCode +}; + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisFailedPayload.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisFailedPayload.ts new file mode 100644 index 000000000..b7af9a440 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisFailedPayload.ts @@ -0,0 +1,29 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * Payload of a `failed` AI analysis Task: why it failed + */ +export interface AiAnalysisFailedPayload { + /** + * Number of attempts started. + */ + attempts: number; + /** + * What went wrong: a text, except for `timed out`, where it is `{ \"lastPayload\": }`. + */ + details: any | null; + /** + * Kind of failure: `permanent error`, `attempts exhausted`, `key lost`, `timed out`, `invalid task` or `not queued`. + */ + reason: string; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRequest.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRequest.ts new file mode 100644 index 000000000..691788436 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRequest.ts @@ -0,0 +1,42 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { AiProvider } from './aiProvider'; + + +/** + * Settings of an AI analysis + */ +export interface AiAnalysisRequest { + /** + * API key of the provider, kept in memory only until the Task ends. + */ + apiKey: string; + /** + * Endpoint of the provider API, kept in memory only; required for `openai-compatible`. + */ + baseUrl?: string | null; + /** + * Upper bound of tokens in each AI answer. + */ + maxOutputTokens?: number | null; + /** + * Model identifier, passed to the provider as is. + */ + model: string; + provider: AiProvider; + /** + * Task ID, chosen by the caller so it can send the same request again safely. + */ + taskId: string; +} +export namespace AiAnalysisRequest { +} + + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRunningPayload.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRunningPayload.ts new file mode 100644 index 000000000..d0fd35ed3 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisRunningPayload.ts @@ -0,0 +1,29 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * Payload of a `running` AI analysis Task: its progress + */ +export interface AiAnalysisRunningPayload { + /** + * Work done in the step: bytes read while `reading`, parts described while `describing`. + */ + done?: number | null; + /** + * Current step: `preparing` (the attempt starts), `reading` (the recordings are read), `describing` (the AI is asked about the recording, part by part), then `writing` (the log is written). + */ + step: string; + /** + * Work the step has in all, in the same unit as `done`. + */ + total?: number | null; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisSucceededPayload.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisSucceededPayload.ts new file mode 100644 index 000000000..60cfbdf4e --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisSucceededPayload.ts @@ -0,0 +1,31 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { AiAnalysisUsage } from './aiAnalysisUsage'; + + +/** + * Payload of a `succeeded` AI analysis Task: the log it added to the session + */ +export interface AiAnalysisSucceededPayload { + /** + * Name of the new log in the session manifest, such as `ai-analysis-0.slog`. + */ + fileName: string; + /** + * Model that wrote the log, as reported by the AI provider; the requested model when it reports none. + */ + model: string; + /** + * Version of the instructions sent to the AI provider. + */ + promptVersion: string; + usage?: AiAnalysisUsage | null; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisUsage.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisUsage.ts new file mode 100644 index 000000000..a7c7a16ba --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiAnalysisUsage.ts @@ -0,0 +1,19 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * Tokens counted by an AI provider + */ +export interface AiAnalysisUsage { + inputTokens: number; + outputTokens: number; +} + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/aiProvider.ts b/devolutions-gateway/openapi/ts-angular-client/model/aiProvider.ts new file mode 100644 index 000000000..01385edbb --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/aiProvider.ts @@ -0,0 +1,24 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * AI provider to ask; `openai-compatible` needs a base URL + */ +export type AiProvider = 'openai' | 'anthropic' | 'mistral' | 'gemini' | 'openai-compatible'; + +export const AiProvider = { + Openai: 'openai' as AiProvider, + Anthropic: 'anthropic' as AiProvider, + Mistral: 'mistral' as AiProvider, + Gemini: 'gemini' as AiProvider, + OpenaiCompatible: 'openai-compatible' as AiProvider +}; + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/models.ts b/devolutions-gateway/openapi/ts-angular-client/model/models.ts index 7d7eb4f0f..114f5b41d 100644 --- a/devolutions-gateway/openapi/ts-angular-client/model/models.ts +++ b/devolutions-gateway/openapi/ts-angular-client/model/models.ts @@ -5,6 +5,14 @@ export * from './addressFamily'; export * from './agentDomainAdvertisement'; export * from './agentInfo'; export * from './agentStatus'; +export * from './aiAnalysisConflict'; +export * from './aiAnalysisConflictCode'; +export * from './aiAnalysisFailedPayload'; +export * from './aiAnalysisRequest'; +export * from './aiAnalysisRunningPayload'; +export * from './aiAnalysisSucceededPayload'; +export * from './aiAnalysisUsage'; +export * from './aiProvider'; export * from './appCredential'; export * from './appCredentialKind'; export * from './appTokenContentType'; @@ -56,6 +64,8 @@ export * from './setUpdateScheduleRequest'; export * from './subProvisionerKey'; export * from './subscriber'; export * from './targetConnectionOptions'; +export * from './taskInfo'; +export * from './taskState'; export * from './trafficEventResponse'; export * from './transportProtocolResponse'; export * from './updateProductInfo'; diff --git a/devolutions-gateway/openapi/ts-angular-client/model/taskInfo.ts b/devolutions-gateway/openapi/ts-angular-client/model/taskInfo.ts new file mode 100644 index 000000000..867035d4e --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/taskInfo.ts @@ -0,0 +1,62 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { TaskState } from './taskState'; + + +/** + * A Task: the permanent record of one piece of work the provisioner asked Gateway to do The work runs in the background; `state` and `payload` tell how it goes. The shape of `params` and `payload` belongs to the kind. For `recording-ai-analysis`, `payload` is an `AiAnalysisRunningPayload` while `running`, an `AiAnalysisSucceededPayload` once `succeeded`, and an `AiAnalysisFailedPayload` once `failed`. + */ +export interface TaskInfo { + /** + * Number of attempts started so far. + */ + attempts: number; + /** + * When the Task was created. + */ + createdAt: string; + /** + * Latest time an unfinished Task becomes failed, waiting and retries included; it fails as `timed out` earlier once too little time is left for another attempt. + */ + deadlineAt: string; + /** + * When it succeeded or failed. + */ + finishedAt?: string | null; + /** + * Task ID, chosen by the provisioner. + */ + id: string; + /** + * Kind of work, such as `recording-ai-analysis`. + */ + kind: string; + /** + * What was asked, without secrets. + */ + params: object; + /** + * Details of the current state; null while `queued`. + */ + payload?: object | null; + /** + * When its first attempt started. + */ + startedAt?: string | null; + state: TaskState; + /** + * What the work is about, such as a session ID. + */ + target: string; +} +export namespace TaskInfo { +} + + diff --git a/devolutions-gateway/openapi/ts-angular-client/model/taskState.ts b/devolutions-gateway/openapi/ts-angular-client/model/taskState.ts new file mode 100644 index 000000000..568b0e257 --- /dev/null +++ b/devolutions-gateway/openapi/ts-angular-client/model/taskState.ts @@ -0,0 +1,20 @@ +/** + * devolutions-gateway + * + * Contact: infos@devolutions.net + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export type TaskState = 'queued' | 'running' | 'succeeded' | 'failed'; + +export const TaskState = { + Queued: 'queued' as TaskState, + Running: 'running' as TaskState, + Succeeded: 'succeeded' as TaskState, + Failed: 'failed' as TaskState +}; + diff --git a/utils/dotnet/Devolutions.Gateway.Utils.Tests/JsonSerializationTests.cs b/utils/dotnet/Devolutions.Gateway.Utils.Tests/JsonSerializationTests.cs index eeb30c306..f6962e949 100644 --- a/utils/dotnet/Devolutions.Gateway.Utils.Tests/JsonSerializationTests.cs +++ b/utils/dotnet/Devolutions.Gateway.Utils.Tests/JsonSerializationTests.cs @@ -219,6 +219,26 @@ public void ScopeClaimsAgentRead() Assert.Equal(EXPECTED, result); } + [Fact] + public void ScopeClaimsRecordingAiAnalysisStart() + { + const string EXPECTED = """{"scope":"gateway.tasks.recording-ai-analysis.start","jet_gw_id":"ccbaad3f-4627-4666-8bb5-cb6a1a7db815"}"""; + + var claims = new ScopeClaims(gatewayId, AccessScope.GatewayTasksRecordingAiAnalysisStart); + string result = JsonSerializer.Serialize(claims); + Assert.Equal(EXPECTED, result); + } + + [Fact] + public void ScopeClaimsRecordingAiAnalysisRead() + { + const string EXPECTED = """{"scope":"gateway.tasks.recording-ai-analysis.read","jet_gw_id":"ccbaad3f-4627-4666-8bb5-cb6a1a7db815"}"""; + + var claims = new ScopeClaims(gatewayId, AccessScope.GatewayTasksRecordingAiAnalysisRead); + string result = JsonSerializer.Serialize(claims); + Assert.Equal(EXPECTED, result); + } + [Fact] public void EnrollmentClaimsAllFields() { diff --git a/utils/dotnet/Devolutions.Gateway.Utils/src/AccessScope.cs b/utils/dotnet/Devolutions.Gateway.Utils/src/AccessScope.cs index bc953a2b1..a88f6f1ae 100644 --- a/utils/dotnet/Devolutions.Gateway.Utils/src/AccessScope.cs +++ b/utils/dotnet/Devolutions.Gateway.Utils/src/AccessScope.cs @@ -30,6 +30,8 @@ internal AccessScope(string value) public static AccessScope GatewayNetMonitorDrain = new AccessScope("gateway.net.monitor.drain"); public static AccessScope GatewayAgentDelete = new AccessScope("gateway.agent.delete"); public static AccessScope GatewayAgentRead = new AccessScope("gateway.agent.read"); + public static AccessScope GatewayTasksRecordingAiAnalysisStart = new AccessScope("gateway.tasks.recording-ai-analysis.start"); + public static AccessScope GatewayTasksRecordingAiAnalysisRead = new AccessScope("gateway.tasks.recording-ai-analysis.read"); public override string? ToString() {