Providers and profiles
A provider holds the credentials and configuration for an external service (a source-control host, an inference API, a data source). Attached to a sandbox, it makes those credentials available to the workload under the gateway's control. A provider profile describes a provider type: its credentials, how they are injected, the endpoints and binaries they are for, and how they are refreshed.
| Client | Area |
|---|---|
client.Providers (IProviderClient) |
Create, get, list, update, delete, and ensure providers; export runtime credentials. |
client.SandboxProviders (ISandboxProviderClient) |
Attach and detach providers, and wait until a sandbox has installed them. |
client.Providers.Profiles (IProviderProfileClient) |
List, get, import, update, lint, and delete profiles. |
client.Providers.Refresh (IProviderRefreshClient) |
Gateway-owned credential refresh: status, configure, rotate, delete. |
Create and list providers
// Creates the provider, or updates it in place when one with the same name exists.
var provider = await client.Providers.EnsureAsync(new Provider
{
Name = "github",
Type = "github",
Credentials = new Dictionary<string, string>
{
["GITHUB_TOKEN"] = Environment.GetEnvironmentVariable("GITHUB_TOKEN")!,
},
Labels = new Dictionary<string, string> { ["team"] = "platform" },
});
// Provider.ToString() never prints credential values.
Console.WriteLine(provider);
await foreach (var p in client.Providers.List(new ListOptions { LabelSelector = "team=platform" }))
{
Console.WriteLine($"{p.Name} ({p.Type})");
}
EnsureAsync creates the provider or updates it in place; CreateAsync fails with AlreadyExists when it exists. Provider, ProviderCredentialValue, and RefreshConfig override ToString so secret values never print.
Attach to a sandbox
Attaching returns a ProviderMutationReceipt that names the exact authority the sandbox should install. WaitAsync waits, within a timeout of at most one hour, until the sandbox supervisor reports it:
var attached = await client.SandboxProviders.AttachAsync("agent-1", "github");
if (attached.Receipt is { } receipt)
{
// Wait until the sandbox supervisor has installed exactly this authority.
var wait = await client.SandboxProviders.WaitAsync(receipt, TimeSpan.FromMinutes(1));
switch (wait.Outcome)
{
case ProviderWaitOutcome.Complete:
break;
case ProviderWaitOutcome.TimedOut:
Console.WriteLine($"still {wait.Status.State}: {wait.Status.Reason}");
break;
case ProviderWaitOutcome.Terminal:
throw new InvalidOperationException($"provider not installed: {wait.Status.Reason}");
}
}
await foreach (var p in client.SandboxProviders.List("agent-1"))
{
Console.WriteLine(p.Name);
}
| ProviderWaitOutcome | Meaning |
|---|---|
Complete |
The authority was installed (attach, update) or revoked (detach). |
TimedOut |
The deadline passed; Status is the last state observed. |
Terminal |
Installation failed or was withheld, or a later mutation superseded this one. It never turns into success. |
Detaching returns a receipt too; revocation is complete only when it reports Revoked. Providers can also be attached at creation through SandboxSpec.Providers.
Update credentials
UpdateAsync returns one receipt per sandbox the provider is attached to; WaitAllAsync waits for all of them under one deadline.
var current = await client.Providers.GetAsync("github");
var update = await client.Providers.UpdateAsync(current with
{
Credentials = new Dictionary<string, string>
{
["GITHUB_TOKEN"] = Environment.GetEnvironmentVariable("GITHUB_TOKEN_ROTATED")!,
},
});
// One receipt per sandbox the provider is attached to, awaited under one deadline.
var results = await client.SandboxProviders.WaitAllAsync(update.TargetReceipts, TimeSpan.FromMinutes(2));
Export runtime credentials
GetCredentialsAsync exports exactly the credential keys you name, or fails without a result. It requires a client authenticated with a trusted operator client certificate (OU=operator) and no bearer or edge token, and an explicit workspace. The gateway refreshes eligible missing or short-lived values first. Keep the values out of logs.
// Requires a client authenticated with an operator client certificate (OU=operator) and no bearer token.
var credentials = await operatorClient.Providers.GetCredentialsAsync(
"my-graph",
["MS_GRAPH_ACCESS_TOKEN"],
new ProviderCredentialsOptions
{
Workspace = "production",
MinimumRemainingLifetime = TimeSpan.FromMinutes(5),
});
var token = credentials["MS_GRAPH_ACCESS_TOKEN"];
Console.WriteLine($"expires {token.ExpirationTime}"); // never log token.Value
Profiles
Profiles live in a workspace; Profiles.Platform is a view of the platform (global) profiles. LintAsync reports diagnostics without changing anything, ImportAsync adds profiles, and UpdateAsync replaces one by id, with a compare-and-swap on its resource version.
var profile = new ProviderProfile
{
Id = "internal-api",
DisplayName = "Internal API",
Category = ProviderProfileCategory.Other,
Credentials =
[
new ProfileCredential
{
Name = "api_key",
EnvVars = ["INTERNAL_API_KEY"],
Required = true,
StablePlaceholder = true, // one placeholder value across value-only revisions
},
],
};
// Lint reports diagnostics without importing anything.
var lint = await client.Providers.Profiles.LintAsync([new ProfileImportItem(profile)]);
foreach (var d in lint.Diagnostics)
{
Console.WriteLine($"{d.Severity} {d.ProfileId}.{d.Field}: {d.Message}");
}
if (lint.Valid)
{
var imported = await client.Providers.Profiles.ImportAsync([new ProfileImportItem(profile)]);
Console.WriteLine($"imported: {imported.Imported}");
}
// Platform (global) profiles instead of the ones in this workspace.
await foreach (var builtin in client.Providers.Profiles.Platform.List())
{
Console.WriteLine($"{builtin.Id}: {builtin.DisplayName}");
}
Stable placeholders
Provider credentials reach the sandbox environment as opaque placeholders. By default each provider revision gets a new placeholder. A profile credential with StablePlaceholder = true keeps one identity-bound placeholder across value-only revisions, so the sandbox environment does not change when only the secret value does.
A stable placeholder needs at least one environment variable (EnvVars), and cannot be combined with gateway-managed refresh or a token grant. Because older gateways drop unknown fields silently, ImportAsync, UpdateAsync, and LintAsync first check that the gateway supports it and fail with OpenShellErrorKind.Unimplemented when it does not; LintProfilesResult.SupportsStablePlaceholder reports support.
Credential refresh
For credentials the gateway refreshes itself (for example OAuth2 tokens), Refresh reads the state of each credential, configures the strategy, forces a rotation, and removes the configuration.
foreach (var status in await client.Providers.Refresh.GetStatusAsync("my-graph"))
{
Console.WriteLine(status);
}
// Rotate a gateway-refreshed credential now.
await client.Providers.Refresh.RotateAsync("my-graph", "MS_GRAPH_ACCESS_TOKEN");