Skip to content

feat(dgw): add an AI client crate for session action descriptions - #2005

Draft
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 2 commits into
masterfrom
feat/ai-crate
Draft

irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 2 commits into
masterfrom
feat/ai-crate

Conversation

@irvingoujAtDevolution

@irvingoujAtDevolution irvingouj@Devolutions (irvingoujAtDevolution) commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds devolutions-gateway-ai, a small crate Gateway uses to ask an AI provider what a user did in a session.

let ai = AiClient::builder().provider(Provider::Anthropic).model("…").api_key(key).http_client(client).build()?;
let actions = ai.describe_session_actions(text).send().await?;

It calls the providers' HTTP APIs directly with the caller's reqwest client (so Gateway's proxy and TLS policy apply), no AI framework and no new crate in Cargo.lock:

  • OpenAI chat completions: OpenAI, Mistral, any OpenAI-compatible endpoint
  • Anthropic Messages

The prompt lives in the crate (PROMPT_VERSION), the key is a SecretString and gets redacted from errors, error reasons never quote the answer. Nothing calls it yet; the first caller is #2008.

Tested: crate tests 12/12, mock-provider HTTP tests (gateway_ai) 11/11 (paths, auth headers, token fields, error redaction).

Stacked on #2003.

🤖 Generated with Claude Code

@@ -0,0 +1,699 @@
//! Purpose-level AI helpers for Devolutions Gateway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is the meaning of "purpose-level" in this context?

Comment on lines +48 to +49
pub enum Provider {
/// OpenAI chat completions; the default base URL is `https://api.openai.com/v1/`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I appreciate the fact that our vendored implementation is small and self-contained, but could you also elaborate why not using a crate such as llm, which could support much more providers?

Base automatically changed from feat/jrec-manifest-logs to master September 30, 2026 20:33
`devolutions-gateway-ai` asks a model which actions a user performed in a
session transcript and parses its JSON Lines answer into typed actions.

It talks to the providers' HTTP APIs directly, with the workspace reqwest
client passed by the caller (so Gateway's proxy and TLS policy apply):
OpenAI chat completions (OpenAI, Mistral, any OpenAI-compatible endpoint)
and Anthropic Messages. Only the fields of a single text completion are
modeled, so there is no AI framework dependency and no new crate in the
lockfile. The API key is redacted from every error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The crate had one purpose, and its shared parts were shaped by it:
one prompt version for the whole crate, a purpose-specific error next
to the transport ones, and retry rules left to every caller. Upcoming
AI tasks need the same client with other prompts.

Each purpose is now a module owning its prompt, prompt version and
parser, and adding a method to AiClient; session_actions is the first.
The provider formats live under wire/. Every purpose returns a
Response with the output, the model reported by the provider and the
token usage, which later budget and audit work needs. Error tells
whether a request is worth retrying, reports answers cut at the output
token limit as Truncated, and treats unusable model text as
InvalidOutput. The builder rejects API keys that are not valid header
values and base URLs that are not HTTP, so sending never fails on
settings.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants