feat: derive provider_tokens_set from local git provider cache (#123)

This commit is contained in:
Hiep Le
2026-05-07 00:55:55 +07:00
committed by GitHub
parent 62871d3a7b
commit 3de362249f
2 changed files with 39 additions and 1 deletions
+25
View File
@@ -103,4 +103,29 @@ describe("SettingsService", () => {
fetchSpy.mockRestore();
});
it("derives provider_tokens_set from locally stored git provider tokens", async () => {
// Arrange: simulate post-save state. SecretsService.addGitProvider writes to
// this localStorage key after the server PUT /api/settings/secrets succeeds.
// The agent-server API never returns provider_tokens_set, so the GUI must
// derive it from local state for useUserProviders / git-settings to recognize
// the configured providers.
window.localStorage.setItem(
"openhands-agent-server-git-provider-tokens",
JSON.stringify({
github: { token: "ghp_test_123", host: "github.com" },
gitlab: { token: "glpat_test_456", host: null },
}),
);
// Act
const settings = await SettingsService.getSettings();
// Assert: each stored provider surfaces in provider_tokens_set with its host
// (or null), which is what consumers like useUserProviders read.
expect(settings.provider_tokens_set).toEqual({
github: "github.com",
gitlab: null,
});
});
});
@@ -1,5 +1,6 @@
import { DEFAULT_SETTINGS } from "#/services/settings";
import { Settings, SettingsSchema, SettingsValue } from "#/types/settings";
import { Provider, Settings, SettingsSchema, SettingsValue } from "#/types/settings";
import { getStoredGitProviders } from "../secrets-service";
import { createHttpClient, createSettingsClient } from "../typescript-client";
/**
@@ -115,12 +116,24 @@ const syncDerivedSettings = (settings: Partial<Settings>): Settings => {
settings.conversation_settings ?? {},
);
// The agent-server has no concept of provider_tokens_set; the GUI derives it
// from locally-stored git provider credentials so the UI knows which
// providers are configured after a save.
const storedProviders = getStoredGitProviders();
const derivedProviderTokensSet = Object.fromEntries(
Object.entries(storedProviders).map(([provider, value]) => [
provider,
value?.host ?? null,
]),
) as Partial<Record<Provider, string | null>>;
const merged = {
...deepClone(DEFAULT_SETTINGS),
...settings,
provider_tokens_set: {
...(DEFAULT_SETTINGS.provider_tokens_set ?? {}),
...(settings.provider_tokens_set ?? {}),
...derivedProviderTokensSet,
},
agent_settings: agentSettings,
conversation_settings: conversationSettings,