@agimon-ai/po-web-dashboard 0.0.0-alpha.113 → 0.0.0-alpha.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"auth.cjs","names":[],"sources":["../../src/types/auth.ts"],"sourcesContent":["/**\n * Wire vocabulary for provider authentication, shared by the hub routes and\n * the settings page.\n *\n * The hub keeps one Pi ModelRuntime over the same auth.json the sessions\n * read, so a login here is a login for every session on this machine. Prompt\n * and event shapes restate pi-ai's AuthPrompt and AuthEvent so neither the\n * flow service nor the page needs a pi import; the abort signal never leaves\n * the hub.\n */\n\n/** REST endpoint listing providers with their auth state; DELETE /:providerId signs out. */\nexport const AUTH_PROVIDERS_API_ROUTE = '/api/plugins/po/providers';\n/** REST endpoint for login flows: POST starts one, GET /:flowId polls it, POST /:flowId/answer answers its prompt, DELETE /:flowId cancels. */\nexport const AUTH_LOGINS_API_ROUTE = '/api/plugins/po/logins';\n\nexport type AuthMethodType = 'api_key' | 'oauth';\n\nexport interface ProviderAuthMethod {\n type: AuthMethodType;\n /** What the provider calls this method: \"Anthropic API key\", \"Anthropic (Claude Pro/Max)\". */\n label: string;\n}\n\nexport interface ProviderAuthSummary {\n id: string;\n name: string;\n /** Login methods the provider offers; ambient-only providers list none. */\n methods: ProviderAuthMethod[];\n /**\n * Present once the provider has working auth: how, and where the credential\n * comes from (\"stored\", or the environment variable that holds the key).\n */\n authenticated?: { type: AuthMethodType; source?: string };\n /** Set for Po's own shared sign-in, which serves Po features rather than chat models. */\n kind?: 'po-feature';\n}\n\nexport interface LoginSelectOption {\n id: string;\n label: string;\n description?: string;\n}\n\n/** pi-ai's AuthPrompt, restated. `signal` cancels this one question, not the flow. */\nexport type LoginPrompt = { signal?: AbortSignal } & (\n | { type: 'text'; message: string; placeholder?: string }\n | { type: 'secret'; message: string; placeholder?: string }\n | { type: 'select'; message: string; options: readonly LoginSelectOption[] }\n | { type: 'manual_code'; message: string; placeholder?: string }\n);\n\n/** The question the page answers: a prompt with an id and without its signal. */\nexport interface LoginPromptView {\n id: string;\n type: LoginPrompt['type'];\n message: string;\n placeholder?: string;\n /** Present for select prompts. */\n options?: LoginSelectOption[];\n}\n\n/** pi-ai's AuthEvent, restated. */\nexport type LoginEvent =\n | { type: 'info'; message: string; links?: readonly { url: string; label?: string }[] }\n | { type: 'auth_url'; url: string; instructions?: string }\n | {\n type: 'device_code';\n userCode: string;\n verificationUri: string;\n intervalSeconds?: number;\n expiresInSeconds?: number;\n }\n | { type: 'progress'; message: string };\n\nexport type LoginFlowStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';\n\nexport interface LoginFlowSnapshot {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n status: LoginFlowStatus;\n events: LoginEvent[];\n /** The question the flow is waiting on, while there is one. */\n prompt?: LoginPromptView;\n /**\n * The login was started from the tunnel. The page uses this to explain that\n * the provider's redirect lands on a loopback address this browser cannot\n * reach, so the code has to be pasted back.\n */\n remote?: boolean;\n /** Why the flow failed, once it has. */\n error?: string;\n}\n\n/** pi-ai's AuthInteraction: what a provider's login needs from its host. */\nexport interface LoginInteraction {\n signal: AbortSignal;\n prompt(prompt: LoginPrompt): Promise<string>;\n notify(event: LoginEvent): void;\n}\n\nexport interface AuthRuntimeProvider {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n /** Absent `login` means ambient-only (env vars, AWS profiles): nothing to sign in to. */\n apiKey?: { name: string; login?: unknown };\n oauth?: { name: string; loginLabel?: string };\n };\n}\n\n/**\n * The slice of Pi's ModelRuntime the hub relies on, so a test can stand in a\n * fake and the adapter never types against the whole agent.\n */\nexport interface AuthRuntime {\n getProviders(): readonly AuthRuntimeProvider[];\n getProviderAuthStatus(providerId: string): { configured: boolean; source?: string; label?: string };\n /** Models the runtime already knows about, without touching the network. */\n getAvailableSnapshot(): readonly { provider: string; id: string }[];\n hasConfiguredAuth(providerId: string): boolean;\n isUsingOAuth(providerId: string): boolean;\n /** Re-reads models.json and every provider's auth state without touching the network. */\n refresh(options: { allowNetwork: boolean }): Promise<unknown>;\n login(providerId: string, type: AuthMethodType, interaction: LoginInteraction): Promise<unknown>;\n logout(providerId: string, options?: { signal?: AbortSignal }): Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,2BAA2B;;AAExC,MAAa,wBAAwB"}
1
+ {"version":3,"file":"auth.cjs","names":[],"sources":["../../src/types/auth.ts"],"sourcesContent":["/**\n * Wire vocabulary for provider authentication, shared by the hub routes and\n * the settings page.\n *\n * The hub keeps one Pi ModelRuntime over the same auth.json the sessions\n * read, so a login here is a login for every session on this machine. Prompt\n * and event shapes restate pi-ai's AuthPrompt and AuthEvent so neither the\n * flow service nor the page needs a pi import; the abort signal never leaves\n * the hub.\n */\n\n/** REST endpoint listing providers with their auth state; DELETE /:providerId signs out. */\nexport const AUTH_PROVIDERS_API_ROUTE = '/api/plugins/po/providers';\n/** REST endpoint for login flows: POST starts one, GET /:flowId polls it, POST /:flowId/answer answers its prompt, DELETE /:flowId cancels. */\nexport const AUTH_LOGINS_API_ROUTE = '/api/plugins/po/logins';\n\nexport type AuthMethodType = 'api_key' | 'oauth';\n\nexport interface ProviderAuthMethod {\n type: AuthMethodType;\n /** What the provider calls this method: \"Anthropic API key\", \"Anthropic (Claude Pro/Max)\". */\n label: string;\n}\n\nexport interface ProviderAuthSummary {\n id: string;\n name: string;\n /** Login methods the provider offers; ambient-only providers list none. */\n methods: ProviderAuthMethod[];\n /**\n * Present once the provider has working auth: how, and where the credential\n * comes from (\"stored\", or the environment variable that holds the key).\n */\n authenticated?: { type: AuthMethodType; source?: string };\n /** Set for Po's own shared sign-in, which serves Po features rather than chat models. */\n kind?: 'po-feature';\n}\n\nexport interface LoginSelectOption {\n id: string;\n label: string;\n description?: string;\n}\n\n/** pi-ai's AuthPrompt, restated. `signal` cancels this one question, not the flow. */\nexport type LoginPrompt = { signal?: AbortSignal } & (\n | { type: 'text'; message: string; placeholder?: string }\n | { type: 'secret'; message: string; placeholder?: string }\n | { type: 'select'; message: string; options: readonly LoginSelectOption[] }\n | { type: 'manual_code'; message: string; placeholder?: string }\n);\n\n/** The question the page answers: a prompt with an id and without its signal. */\nexport interface LoginPromptView {\n id: string;\n type: LoginPrompt['type'];\n message: string;\n placeholder?: string;\n /** Present for select prompts. */\n options?: LoginSelectOption[];\n}\n\n/** pi-ai's AuthEvent, restated. */\nexport type LoginEvent =\n | { type: 'info'; message: string; links?: readonly { url: string; label?: string }[] }\n | { type: 'auth_url'; url: string; instructions?: string }\n | {\n type: 'device_code';\n userCode: string;\n verificationUri: string;\n intervalSeconds?: number;\n expiresInSeconds?: number;\n }\n | { type: 'progress'; message: string };\n\nexport type LoginFlowStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';\n\nexport interface LoginFlowSnapshot {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n status: LoginFlowStatus;\n events: LoginEvent[];\n /** The question the flow is waiting on, while there is one. */\n prompt?: LoginPromptView;\n /**\n * The login was started from the tunnel. The page uses this to explain that\n * the provider's redirect lands on a loopback address this browser cannot\n * reach, so the code has to be pasted back.\n */\n remote?: boolean;\n /** HTTP-only hosts request periodic reads; omitted when the hub pushes provider revisions. */\n pollIntervalMs?: number;\n /** Why the flow failed, once it has. */\n error?: string;\n}\n\n/** pi-ai's AuthInteraction: what a provider's login needs from its host. */\nexport interface LoginInteraction {\n signal: AbortSignal;\n prompt(prompt: LoginPrompt): Promise<string>;\n notify(event: LoginEvent): void;\n}\n\nexport interface AuthRuntimeProvider {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n /** Absent `login` means ambient-only (env vars, AWS profiles): nothing to sign in to. */\n apiKey?: { name: string; login?: unknown };\n oauth?: { name: string; loginLabel?: string };\n };\n}\n\n/**\n * The slice of Pi's ModelRuntime the hub relies on, so a test can stand in a\n * fake and the adapter never types against the whole agent.\n */\nexport interface AuthRuntime {\n getProviders(): readonly AuthRuntimeProvider[];\n getProviderAuthStatus(providerId: string): { configured: boolean; source?: string; label?: string };\n /** Models the runtime already knows about, without touching the network. */\n getAvailableSnapshot(): readonly { provider: string; id: string }[];\n hasConfiguredAuth(providerId: string): boolean;\n isUsingOAuth(providerId: string): boolean;\n /** Re-reads models.json and every provider's auth state without touching the network. */\n refresh(options: { allowNetwork: boolean }): Promise<unknown>;\n login(providerId: string, type: AuthMethodType, interaction: LoginInteraction): Promise<unknown>;\n logout(providerId: string, options?: { signal?: AbortSignal }): Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,2BAA2B;;AAExC,MAAa,wBAAwB"}
@@ -107,6 +107,8 @@ export interface LoginFlowSnapshot {
107
107
  * reach, so the code has to be pasted back.
108
108
  */
109
109
  remote?: boolean;
110
+ /** HTTP-only hosts request periodic reads; omitted when the hub pushes provider revisions. */
111
+ pollIntervalMs?: number;
110
112
  /** Why the flow failed, once it has. */
111
113
  error?: string;
112
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.mts","names":[],"sources":["../../src/types/auth.ts"],"mappings":";;;;;;;;;;;;qBAYa;;qBAEA;YAED;iBAEK;EACf,MAAM;;EAEN;;iBAGe;EACf;EACA;;EAEA,SAAS;;;;;EAKT;IAAkB,MAAM;IAAgB;;;EAExC;;iBAGe;EACf;EACA;EACA;;;YAIU;EAAgB,SAAS;;EAC/B;EAAc;EAAiB;;EAC/B;EAAgB;EAAiB;;EACjC;EAAgB;EAAiB,kBAAkB;;EACnD;EAAqB;EAAiB;;;iBAI3B;EACf;EACA,MAAM;EACN;EACA;;EAEA,UAAU;;;YAIA;EACN;EAAc;EAAiB;IAAmB;IAAa;;;EAC/D;EAAkB;EAAa;;EAE/B;EACA;EACA;EACA;EACA;;EAEA;EAAkB;;YAEZ;iBAEK;EACf;EACA;EACA;EACA,MAAM;EACN,QAAQ;EACR,QAAQ;;EAER,SAAS;;;;;;EAMT;;EAEA;;;iBAIe;EACf,QAAQ;EACR,OAAO,QAAQ,cAAc;EAC7B,OAAO,OAAO;;iBAGC;WACN;WACA;WACA;;IAEP;MAAW;MAAc;;IACzB;MAAU;MAAc;;;;;;;;iBAQX;EACf,yBAAyB;EACzB,sBAAsB;IAAuB;IAAqB;IAAiB;;;EAEnF;IAAmC;IAAkB;;EACrD,kBAAkB;EAClB,aAAa;;EAEb,QAAQ;IAAW;MAA0B;EAC7C,MAAM,oBAAoB,MAAM,gBAAgB,aAAa,mBAAmB;EAChF,OAAO,oBAAoB;IAAY,SAAS;MAAgB"}
1
+ {"version":3,"file":"auth.d.mts","names":[],"sources":["../../src/types/auth.ts"],"mappings":";;;;;;;;;;;;qBAYa;;qBAEA;YAED;iBAEK;EACf,MAAM;;EAEN;;iBAGe;EACf;EACA;;EAEA,SAAS;;;;;EAKT;IAAkB,MAAM;IAAgB;;;EAExC;;iBAGe;EACf;EACA;EACA;;;YAIU;EAAgB,SAAS;;EAC/B;EAAc;EAAiB;;EAC/B;EAAgB;EAAiB;;EACjC;EAAgB;EAAiB,kBAAkB;;EACnD;EAAqB;EAAiB;;;iBAI3B;EACf;EACA,MAAM;EACN;EACA;;EAEA,UAAU;;;YAIA;EACN;EAAc;EAAiB;IAAmB;IAAa;;;EAC/D;EAAkB;EAAa;;EAE/B;EACA;EACA;EACA;EACA;;EAEA;EAAkB;;YAEZ;iBAEK;EACf;EACA;EACA;EACA,MAAM;EACN,QAAQ;EACR,QAAQ;;EAER,SAAS;;;;;;EAMT;;EAEA;;EAEA;;;iBAIe;EACf,QAAQ;EACR,OAAO,QAAQ,cAAc;EAC7B,OAAO,OAAO;;iBAGC;WACN;WACA;WACA;;IAEP;MAAW;MAAc;;IACzB;MAAU;MAAc;;;;;;;;iBAQX;EACf,yBAAyB;EACzB,sBAAsB;IAAuB;IAAqB;IAAiB;;;EAEnF;IAAmC;IAAkB;;EACrD,kBAAkB;EAClB,aAAa;;EAEb,QAAQ;IAAW;MAA0B;EAC7C,MAAM,oBAAoB,MAAM,gBAAgB,aAAa,mBAAmB;EAChF,OAAO,oBAAoB;IAAY,SAAS;MAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":[],"sources":["../../src/types/auth.ts"],"sourcesContent":["/**\n * Wire vocabulary for provider authentication, shared by the hub routes and\n * the settings page.\n *\n * The hub keeps one Pi ModelRuntime over the same auth.json the sessions\n * read, so a login here is a login for every session on this machine. Prompt\n * and event shapes restate pi-ai's AuthPrompt and AuthEvent so neither the\n * flow service nor the page needs a pi import; the abort signal never leaves\n * the hub.\n */\n\n/** REST endpoint listing providers with their auth state; DELETE /:providerId signs out. */\nexport const AUTH_PROVIDERS_API_ROUTE = '/api/plugins/po/providers';\n/** REST endpoint for login flows: POST starts one, GET /:flowId polls it, POST /:flowId/answer answers its prompt, DELETE /:flowId cancels. */\nexport const AUTH_LOGINS_API_ROUTE = '/api/plugins/po/logins';\n\nexport type AuthMethodType = 'api_key' | 'oauth';\n\nexport interface ProviderAuthMethod {\n type: AuthMethodType;\n /** What the provider calls this method: \"Anthropic API key\", \"Anthropic (Claude Pro/Max)\". */\n label: string;\n}\n\nexport interface ProviderAuthSummary {\n id: string;\n name: string;\n /** Login methods the provider offers; ambient-only providers list none. */\n methods: ProviderAuthMethod[];\n /**\n * Present once the provider has working auth: how, and where the credential\n * comes from (\"stored\", or the environment variable that holds the key).\n */\n authenticated?: { type: AuthMethodType; source?: string };\n /** Set for Po's own shared sign-in, which serves Po features rather than chat models. */\n kind?: 'po-feature';\n}\n\nexport interface LoginSelectOption {\n id: string;\n label: string;\n description?: string;\n}\n\n/** pi-ai's AuthPrompt, restated. `signal` cancels this one question, not the flow. */\nexport type LoginPrompt = { signal?: AbortSignal } & (\n | { type: 'text'; message: string; placeholder?: string }\n | { type: 'secret'; message: string; placeholder?: string }\n | { type: 'select'; message: string; options: readonly LoginSelectOption[] }\n | { type: 'manual_code'; message: string; placeholder?: string }\n);\n\n/** The question the page answers: a prompt with an id and without its signal. */\nexport interface LoginPromptView {\n id: string;\n type: LoginPrompt['type'];\n message: string;\n placeholder?: string;\n /** Present for select prompts. */\n options?: LoginSelectOption[];\n}\n\n/** pi-ai's AuthEvent, restated. */\nexport type LoginEvent =\n | { type: 'info'; message: string; links?: readonly { url: string; label?: string }[] }\n | { type: 'auth_url'; url: string; instructions?: string }\n | {\n type: 'device_code';\n userCode: string;\n verificationUri: string;\n intervalSeconds?: number;\n expiresInSeconds?: number;\n }\n | { type: 'progress'; message: string };\n\nexport type LoginFlowStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';\n\nexport interface LoginFlowSnapshot {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n status: LoginFlowStatus;\n events: LoginEvent[];\n /** The question the flow is waiting on, while there is one. */\n prompt?: LoginPromptView;\n /**\n * The login was started from the tunnel. The page uses this to explain that\n * the provider's redirect lands on a loopback address this browser cannot\n * reach, so the code has to be pasted back.\n */\n remote?: boolean;\n /** Why the flow failed, once it has. */\n error?: string;\n}\n\n/** pi-ai's AuthInteraction: what a provider's login needs from its host. */\nexport interface LoginInteraction {\n signal: AbortSignal;\n prompt(prompt: LoginPrompt): Promise<string>;\n notify(event: LoginEvent): void;\n}\n\nexport interface AuthRuntimeProvider {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n /** Absent `login` means ambient-only (env vars, AWS profiles): nothing to sign in to. */\n apiKey?: { name: string; login?: unknown };\n oauth?: { name: string; loginLabel?: string };\n };\n}\n\n/**\n * The slice of Pi's ModelRuntime the hub relies on, so a test can stand in a\n * fake and the adapter never types against the whole agent.\n */\nexport interface AuthRuntime {\n getProviders(): readonly AuthRuntimeProvider[];\n getProviderAuthStatus(providerId: string): { configured: boolean; source?: string; label?: string };\n /** Models the runtime already knows about, without touching the network. */\n getAvailableSnapshot(): readonly { provider: string; id: string }[];\n hasConfiguredAuth(providerId: string): boolean;\n isUsingOAuth(providerId: string): boolean;\n /** Re-reads models.json and every provider's auth state without touching the network. */\n refresh(options: { allowNetwork: boolean }): Promise<unknown>;\n login(providerId: string, type: AuthMethodType, interaction: LoginInteraction): Promise<unknown>;\n logout(providerId: string, options?: { signal?: AbortSignal }): Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,2BAA2B;;AAExC,MAAa,wBAAwB"}
1
+ {"version":3,"file":"auth.mjs","names":[],"sources":["../../src/types/auth.ts"],"sourcesContent":["/**\n * Wire vocabulary for provider authentication, shared by the hub routes and\n * the settings page.\n *\n * The hub keeps one Pi ModelRuntime over the same auth.json the sessions\n * read, so a login here is a login for every session on this machine. Prompt\n * and event shapes restate pi-ai's AuthPrompt and AuthEvent so neither the\n * flow service nor the page needs a pi import; the abort signal never leaves\n * the hub.\n */\n\n/** REST endpoint listing providers with their auth state; DELETE /:providerId signs out. */\nexport const AUTH_PROVIDERS_API_ROUTE = '/api/plugins/po/providers';\n/** REST endpoint for login flows: POST starts one, GET /:flowId polls it, POST /:flowId/answer answers its prompt, DELETE /:flowId cancels. */\nexport const AUTH_LOGINS_API_ROUTE = '/api/plugins/po/logins';\n\nexport type AuthMethodType = 'api_key' | 'oauth';\n\nexport interface ProviderAuthMethod {\n type: AuthMethodType;\n /** What the provider calls this method: \"Anthropic API key\", \"Anthropic (Claude Pro/Max)\". */\n label: string;\n}\n\nexport interface ProviderAuthSummary {\n id: string;\n name: string;\n /** Login methods the provider offers; ambient-only providers list none. */\n methods: ProviderAuthMethod[];\n /**\n * Present once the provider has working auth: how, and where the credential\n * comes from (\"stored\", or the environment variable that holds the key).\n */\n authenticated?: { type: AuthMethodType; source?: string };\n /** Set for Po's own shared sign-in, which serves Po features rather than chat models. */\n kind?: 'po-feature';\n}\n\nexport interface LoginSelectOption {\n id: string;\n label: string;\n description?: string;\n}\n\n/** pi-ai's AuthPrompt, restated. `signal` cancels this one question, not the flow. */\nexport type LoginPrompt = { signal?: AbortSignal } & (\n | { type: 'text'; message: string; placeholder?: string }\n | { type: 'secret'; message: string; placeholder?: string }\n | { type: 'select'; message: string; options: readonly LoginSelectOption[] }\n | { type: 'manual_code'; message: string; placeholder?: string }\n);\n\n/** The question the page answers: a prompt with an id and without its signal. */\nexport interface LoginPromptView {\n id: string;\n type: LoginPrompt['type'];\n message: string;\n placeholder?: string;\n /** Present for select prompts. */\n options?: LoginSelectOption[];\n}\n\n/** pi-ai's AuthEvent, restated. */\nexport type LoginEvent =\n | { type: 'info'; message: string; links?: readonly { url: string; label?: string }[] }\n | { type: 'auth_url'; url: string; instructions?: string }\n | {\n type: 'device_code';\n userCode: string;\n verificationUri: string;\n intervalSeconds?: number;\n expiresInSeconds?: number;\n }\n | { type: 'progress'; message: string };\n\nexport type LoginFlowStatus = 'running' | 'succeeded' | 'failed' | 'cancelled';\n\nexport interface LoginFlowSnapshot {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n status: LoginFlowStatus;\n events: LoginEvent[];\n /** The question the flow is waiting on, while there is one. */\n prompt?: LoginPromptView;\n /**\n * The login was started from the tunnel. The page uses this to explain that\n * the provider's redirect lands on a loopback address this browser cannot\n * reach, so the code has to be pasted back.\n */\n remote?: boolean;\n /** HTTP-only hosts request periodic reads; omitted when the hub pushes provider revisions. */\n pollIntervalMs?: number;\n /** Why the flow failed, once it has. */\n error?: string;\n}\n\n/** pi-ai's AuthInteraction: what a provider's login needs from its host. */\nexport interface LoginInteraction {\n signal: AbortSignal;\n prompt(prompt: LoginPrompt): Promise<string>;\n notify(event: LoginEvent): void;\n}\n\nexport interface AuthRuntimeProvider {\n readonly id: string;\n readonly name: string;\n readonly auth: {\n /** Absent `login` means ambient-only (env vars, AWS profiles): nothing to sign in to. */\n apiKey?: { name: string; login?: unknown };\n oauth?: { name: string; loginLabel?: string };\n };\n}\n\n/**\n * The slice of Pi's ModelRuntime the hub relies on, so a test can stand in a\n * fake and the adapter never types against the whole agent.\n */\nexport interface AuthRuntime {\n getProviders(): readonly AuthRuntimeProvider[];\n getProviderAuthStatus(providerId: string): { configured: boolean; source?: string; label?: string };\n /** Models the runtime already knows about, without touching the network. */\n getAvailableSnapshot(): readonly { provider: string; id: string }[];\n hasConfiguredAuth(providerId: string): boolean;\n isUsingOAuth(providerId: string): boolean;\n /** Re-reads models.json and every provider's auth state without touching the network. */\n refresh(options: { allowNetwork: boolean }): Promise<unknown>;\n login(providerId: string, type: AuthMethodType, interaction: LoginInteraction): Promise<unknown>;\n logout(providerId: string, options?: { signal?: AbortSignal }): Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,2BAA2B;;AAExC,MAAa,wBAAwB"}
@@ -158,12 +158,14 @@ function ProviderSettings() {
158
158
  }, [reload, revision]);
159
159
  const flowId = flow?.id;
160
160
  const running = flow?.status === "running";
161
+ const pollIntervalMs = flow?.pollIntervalMs;
161
162
  (0, react.useEffect)(() => {
162
163
  if (flowId === void 0 || !running) return;
163
164
  let stopped = false;
165
+ let timer;
164
166
  const fetchFlow = async () => {
165
167
  const result = await require_authApi.readLogin(flowId);
166
- if (stopped) return;
168
+ if (stopped || flowState.state?.id !== flowId || flowState.state.status !== "running") return;
167
169
  if ("error" in result) {
168
170
  setFlow((current) => current && current.id === flowId ? {
169
171
  ...current,
@@ -177,14 +179,18 @@ function ProviderSettings() {
177
179
  reload();
178
180
  return;
179
181
  }
182
+ if (pollIntervalMs !== void 0 && pollIntervalMs > 0) timer = setTimeout(() => void fetchFlow(), pollIntervalMs);
180
183
  };
181
184
  fetchFlow();
182
185
  return () => {
183
186
  stopped = true;
187
+ if (timer !== void 0) clearTimeout(timer);
184
188
  };
185
189
  }, [
186
190
  flowId,
187
191
  running,
192
+ pollIntervalMs,
193
+ flowState,
188
194
  reload,
189
195
  setFlow,
190
196
  revision
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderSettings.cjs","names":["SettingsRow","ProviderLogo","Badge","Dot","Button","globalRevisionTopic","useSyncExternalStore","subscribeHubRevision","hubRevision","useDashboardState","useCallback","listProviders","readLogin","startLogin","answerLogin","cancelLogin","logoutProvider","SettingsPage","SettingsHeader","SettingsTitle","SettingsDescription","SettingsCard","Skeleton","SettingsRowText","SettingsRowTitle","SettingsRowDescription","EmptyState","SettingsSection","SettingsSectionHeader","SettingsSectionTitle","SettingsSectionDescription","SearchIcon","Input","LoginFlowDialog"],"sources":["../../../../src/web/features/settings/ProviderSettings.tsx"],"sourcesContent":["import { globalRevisionTopic, hubRevision, subscribeHubRevision } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Dot,\n EmptyState,\n Input,\n SearchIcon,\n SettingsCard,\n SettingsDescription,\n SettingsHeader,\n SettingsPage,\n SettingsRow,\n SettingsRowDescription,\n SettingsRowText,\n SettingsRowTitle,\n SettingsSection,\n SettingsSectionDescription,\n SettingsSectionHeader,\n SettingsSectionTitle,\n SettingsTitle,\n Skeleton,\n} from '@agimon-ai/po-web-components';\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { AuthMethodType, LoginFlowSnapshot, ProviderAuthSummary } from '../../../types/auth';\nimport { answerLogin, cancelLogin, listProviders, logoutProvider, readLogin, startLogin } from '../../lib/authApi';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { LoginFlowDialog } from './LoginFlowDialog';\nimport { ProviderLogo } from './ProviderLogo';\n\n/** The auth source Pi reports for a credential its own /login stored; the only kind /logout removes. */\nconst STORED_SOURCE = 'stored';\n\nconst ACTION_LABEL: Readonly<Record<AuthMethodType, string>> = { oauth: 'Sign in', api_key: 'Add API key' };\nconst METHOD_NAME: Readonly<Record<AuthMethodType, string>> = { oauth: 'OAuth', api_key: 'an API key' };\n\n/** Where the credential came from, in words: Pi's own store, or the variable that holds the key. */\nfunction ConnectedStatus({ type, source }: NonNullable<ProviderAuthSummary['authenticated']>) {\n if (source === STORED_SOURCE) return <>Signed in with {METHOD_NAME[type]}, saved on this machine</>;\n if (!source) return <>Signed in with {METHOD_NAME[type]}</>;\n return (\n <>\n Using the key in <code className=\"font-mono text-po-hi\">{source}</code>\n </>\n );\n}\n\nfunction ConnectedRow({\n provider,\n busy,\n onLogout,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogout: () => void;\n}) {\n const auth = provider.authenticated;\n if (!auth) return null;\n return (\n <SettingsRow asChild className=\"gap-x-4 py-4\">\n <li data-testid={`provider-${provider.id}`} data-authenticated>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-56 flex-col gap-1.5\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n <Badge tone=\"green\" className=\"rounded-full border-po-edge-green bg-po-tint-green px-2.5 font-medium\">\n <Dot tone=\"green\" />\n Connected\n </Badge>\n <span aria-hidden className=\"hidden h-4 w-px bg-po-border sm:block\" />\n <span data-testid={`provider-status-${provider.id}`} className=\"min-w-0 break-words text-sm text-po-dim\">\n <ConnectedStatus {...auth} />\n </span>\n </div>\n </div>\n {/* Only a credential Pi stored can be removed here; an environment key is the shell's to unset. */}\n {auth.source === STORED_SOURCE ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-logout-${provider.id}`}\n disabled={busy}\n onClick={onLogout}\n >\n Sign out\n </Button>\n ) : auth.source ? (\n <span className=\"shrink-0 text-sm text-po-dim\">Set by your environment</span>\n ) : null}\n </li>\n </SettingsRow>\n );\n}\n\nfunction AvailableRow({\n provider,\n busy,\n onLogin,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogin: (type: AuthMethodType) => void;\n}) {\n return (\n <SettingsRow asChild className=\"gap-x-4 py-3\">\n <li data-testid={`provider-${provider.id}`} data-authenticated={false}>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-40 flex-col\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <span className=\"truncate font-mono text-sm text-po-dim\">{provider.id}</span>\n <span data-testid={`provider-status-${provider.id}`} className=\"sr-only\">\n Not connected\n </span>\n </div>\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {provider.methods.length === 0 ? (\n <span className=\"text-sm text-po-dim\">Reads credentials from your environment</span>\n ) : (\n provider.methods.map((method) => (\n <Button\n key={method.type}\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-login-${method.type}-${provider.id}`}\n title={method.label}\n disabled={busy}\n onClick={() => onLogin(method.type)}\n >\n {ACTION_LABEL[method.type]}\n </Button>\n ))\n )}\n </div>\n </li>\n </SettingsRow>\n );\n}\n\n/**\n * The providers page: what Pi can sign in to on the hub's machine, and\n * whether it has. A login runs on the hub; this page reads its flow on changes and\n * relays the one question it may ask.\n */\nexport function ProviderSettings() {\n const revisionTopic = globalRevisionTopic('providers');\n const revision = useSyncExternalStore(\n (listener) => subscribeHubRevision(revisionTopic, listener),\n () => hubRevision(revisionTopic),\n );\n const [providers, setProviders] = useDashboardState<ProviderAuthSummary[] | null>('providers/list', null);\n const [error, setError] = useDashboardState('providers/error', '');\n const [flow, setFlow, flowState] = useDashboardState<LoginFlowSnapshot | null>('providers/flow', null);\n const [busyId, setBusyId, busyState] = useDashboardState<string | null>('providers/busy', null);\n const [filter, setFilter] = useDashboardState('providers/filter', '');\n\n const reload = useCallback(async (): Promise<void> => {\n const result = await listProviders();\n if ('providers' in result) {\n setProviders(result.providers);\n setError('');\n } else {\n setError(result.error);\n }\n }, [setProviders, setError]);\n\n useEffect(() => {\n // eslint-disable-next-line react/set-state-in-effect -- reads the hub's provider auth list over HTTP; the state is the response.\n void reload();\n }, [reload, revision]);\n\n // Read the running flow on open and pushed changes; once it settles, the list\n // is re-read so the provider's chip reflects the outcome.\n const flowId = flow?.id;\n const running = flow?.status === 'running';\n useEffect(() => {\n if (flowId === undefined || !running) return;\n let stopped = false;\n const fetchFlow = async (): Promise<void> => {\n const result = await readLogin(flowId);\n if (stopped) return;\n if ('error' in result) {\n setFlow((current) =>\n current && current.id === flowId ? { ...current, status: 'failed', error: result.error } : current,\n );\n return;\n }\n setFlow(result.flow);\n if (result.flow.status !== 'running') {\n void reload();\n return;\n }\n };\n void fetchFlow();\n return () => {\n stopped = true;\n };\n }, [flowId, running, reload, setFlow, revision]);\n\n const begin = async (providerId: string, type: AuthMethodType): Promise<void> => {\n if (busyState.state !== null || flowState.state?.status === 'running') return;\n setBusyId(providerId);\n const result = await startLogin(providerId, type);\n setBusyId(null);\n if ('flow' in result) setFlow(result.flow);\n else setError(result.error);\n };\n\n const answer = async (promptId: string, value: string): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await answerLogin(flow.id, promptId, value);\n setBusyId(null);\n if ('flow' in result) {\n const latest = await readLogin(flow.id);\n setFlow('flow' in latest ? latest.flow : result.flow);\n void reload();\n } else setError(result.error);\n };\n\n const cancel = async (): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await cancelLogin(flow.id);\n setBusyId(null);\n setFlow('flow' in result ? result.flow : null);\n void reload();\n };\n\n const signOut = async (providerId: string): Promise<void> => {\n if (busyState.state !== null) return;\n setBusyId(providerId);\n const result = await logoutProvider(providerId);\n if ('error' in result) setError(result.error);\n else await reload();\n setBusyId(null);\n };\n\n // Po's own shared sign-in serves its features, not chat models, so it gets its own\n // section. Connected providers are what a reader came to check, so they get their\n // own group; the long tail is a searchable list rather than a wall of cards.\n const needle = filter.trim().toLowerCase();\n const poFeatures = (providers ?? []).filter((provider) => provider.kind === 'po-feature');\n const models = (providers ?? []).filter((provider) => provider.kind !== 'po-feature');\n const connected = models.filter((provider) => provider.authenticated !== undefined);\n const available = models\n .filter((provider) => provider.authenticated === undefined)\n .filter((provider) => `${provider.name} ${provider.id}`.toLowerCase().includes(needle));\n const busy = busyId !== null || running;\n\n return (\n <SettingsPage data-testid=\"provider-settings\">\n <SettingsHeader>\n <SettingsTitle>Providers</SettingsTitle>\n <SettingsDescription>\n Connect the model providers Po can work with. Sign in once and every session on this machine can use it.\n Credentials stay on this machine, in Pi&apos;s <code className=\"font-mono text-po-hi\">auth.json</code>.\n </SettingsDescription>\n </SettingsHeader>\n {providers === null && !error ? (\n <SettingsCard role=\"status\" aria-busy className=\"gap-4 p-5\">\n <span className=\"sr-only\">Reading providers</span>\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-64\" />\n <Skeleton className=\"h-4 w-52\" />\n </SettingsCard>\n ) : null}\n {providers === null && error ? (\n <SettingsCard>\n <SettingsRow data-testid=\"provider-settings-error\" title={error}>\n <SettingsRowText role=\"alert\">\n <SettingsRowTitle>Po couldn&apos;t reach your providers just now</SettingsRowTitle>\n <SettingsRowDescription>\n Nothing has changed. Your sign-ins are where you left them.\n </SettingsRowDescription>\n </SettingsRowText>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void reload()}>\n Try again\n </Button>\n </SettingsRow>\n </SettingsCard>\n ) : null}\n {providers && error ? (\n <p data-testid=\"provider-settings-error\" role=\"alert\" className=\"text-sm leading-relaxed text-po-red\">\n {error}\n </p>\n ) : null}\n {providers?.length === 0 ? (\n <EmptyState\n data-testid=\"provider-list-empty\"\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"No providers to connect\"\n description=\"Pi hasn't offered any model providers on this machine yet.\"\n />\n ) : null}\n {providers && providers.length > 0 ? (\n <>\n {poFeatures.length > 0 ? (\n <SettingsSection data-testid=\"provider-po-features\">\n <SettingsSectionHeader>\n <SettingsSectionTitle>Po features</SettingsSectionTitle>\n <SettingsSectionDescription>\n Image generation, Web search, and Voice share one ChatGPT sign-in. It is separate from the OpenAI\n Codex sign-in your chat models use.\n </SettingsSectionDescription>\n </SettingsSectionHeader>\n <SettingsCard asChild>\n <ul>\n {poFeatures.map((provider) =>\n provider.authenticated ? (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ) : (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ),\n )}\n </ul>\n </SettingsCard>\n </SettingsSection>\n ) : null}\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Connected</SettingsSectionTitle>\n </SettingsSectionHeader>\n {connected.length > 0 ? (\n <SettingsCard asChild>\n <ul>\n {connected.map((provider) => (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : (\n <EmptyState\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"Nothing connected yet\"\n description=\"Pick a provider below. Po takes it from there.\"\n />\n )}\n </SettingsSection>\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Available</SettingsSectionTitle>\n <span className=\"text-sm text-po-dim\">\n {connected.length} of {models.length} connected\n </span>\n </SettingsSectionHeader>\n <label className=\"flex min-h-11 items-center gap-2.5 rounded-lg border border-po-border bg-po-panel px-3.5 transition-colors focus-within:border-po-green/60\">\n <SearchIcon className=\"size-4 shrink-0 text-po-faint\" />\n <Input\n variant=\"bare\"\n data-testid=\"provider-filter\"\n value={filter}\n placeholder=\"Search providers\"\n aria-label=\"Search providers\"\n onChange={(event) => setFilter(event.target.value)}\n className=\"flex-1\"\n />\n </label>\n {available.length > 0 ? (\n <SettingsCard asChild>\n <ul data-testid=\"provider-list\">\n {available.map((provider) => (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : needle ? (\n <EmptyState\n data-testid=\"provider-no-match\"\n className=\"rounded-lg border border-po-border bg-po-panel py-8\"\n title={`No provider called “${filter.trim()}”`}\n description=\"Try the company name, like Google or Mistral.\"\n />\n ) : (\n <p className=\"text-sm text-po-dim\">Every provider Po knows about is connected.</p>\n )}\n </SettingsSection>\n </>\n ) : null}\n {flow ? (\n <LoginFlowDialog\n flow={flow}\n onAnswer={(promptId, value) => void answer(promptId, value)}\n onCancel={() => void cancel()}\n onClose={() => setFlow(null)}\n />\n ) : null}\n </SettingsPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProviderSettings.cjs","names":["SettingsRow","ProviderLogo","Badge","Dot","Button","globalRevisionTopic","useSyncExternalStore","subscribeHubRevision","hubRevision","useDashboardState","useCallback","listProviders","readLogin","startLogin","answerLogin","cancelLogin","logoutProvider","SettingsPage","SettingsHeader","SettingsTitle","SettingsDescription","SettingsCard","Skeleton","SettingsRowText","SettingsRowTitle","SettingsRowDescription","EmptyState","SettingsSection","SettingsSectionHeader","SettingsSectionTitle","SettingsSectionDescription","SearchIcon","Input","LoginFlowDialog"],"sources":["../../../../src/web/features/settings/ProviderSettings.tsx"],"sourcesContent":["import { globalRevisionTopic, hubRevision, subscribeHubRevision } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Dot,\n EmptyState,\n Input,\n SearchIcon,\n SettingsCard,\n SettingsDescription,\n SettingsHeader,\n SettingsPage,\n SettingsRow,\n SettingsRowDescription,\n SettingsRowText,\n SettingsRowTitle,\n SettingsSection,\n SettingsSectionDescription,\n SettingsSectionHeader,\n SettingsSectionTitle,\n SettingsTitle,\n Skeleton,\n} from '@agimon-ai/po-web-components';\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { AuthMethodType, LoginFlowSnapshot, ProviderAuthSummary } from '../../../types/auth';\nimport { answerLogin, cancelLogin, listProviders, logoutProvider, readLogin, startLogin } from '../../lib/authApi';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { LoginFlowDialog } from './LoginFlowDialog';\nimport { ProviderLogo } from './ProviderLogo';\n\n/** The auth source Pi reports for a credential its own /login stored; the only kind /logout removes. */\nconst STORED_SOURCE = 'stored';\n\nconst ACTION_LABEL: Readonly<Record<AuthMethodType, string>> = { oauth: 'Sign in', api_key: 'Add API key' };\nconst METHOD_NAME: Readonly<Record<AuthMethodType, string>> = { oauth: 'OAuth', api_key: 'an API key' };\n\n/** Where the credential came from, in words: Pi's own store, or the variable that holds the key. */\nfunction ConnectedStatus({ type, source }: NonNullable<ProviderAuthSummary['authenticated']>) {\n if (source === STORED_SOURCE) return <>Signed in with {METHOD_NAME[type]}, saved on this machine</>;\n if (!source) return <>Signed in with {METHOD_NAME[type]}</>;\n return (\n <>\n Using the key in <code className=\"font-mono text-po-hi\">{source}</code>\n </>\n );\n}\n\nfunction ConnectedRow({\n provider,\n busy,\n onLogout,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogout: () => void;\n}) {\n const auth = provider.authenticated;\n if (!auth) return null;\n return (\n <SettingsRow asChild className=\"gap-x-4 py-4\">\n <li data-testid={`provider-${provider.id}`} data-authenticated>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-56 flex-col gap-1.5\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n <Badge tone=\"green\" className=\"rounded-full border-po-edge-green bg-po-tint-green px-2.5 font-medium\">\n <Dot tone=\"green\" />\n Connected\n </Badge>\n <span aria-hidden className=\"hidden h-4 w-px bg-po-border sm:block\" />\n <span data-testid={`provider-status-${provider.id}`} className=\"min-w-0 break-words text-sm text-po-dim\">\n <ConnectedStatus {...auth} />\n </span>\n </div>\n </div>\n {/* Only a credential Pi stored can be removed here; an environment key is the shell's to unset. */}\n {auth.source === STORED_SOURCE ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-logout-${provider.id}`}\n disabled={busy}\n onClick={onLogout}\n >\n Sign out\n </Button>\n ) : auth.source ? (\n <span className=\"shrink-0 text-sm text-po-dim\">Set by your environment</span>\n ) : null}\n </li>\n </SettingsRow>\n );\n}\n\nfunction AvailableRow({\n provider,\n busy,\n onLogin,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogin: (type: AuthMethodType) => void;\n}) {\n return (\n <SettingsRow asChild className=\"gap-x-4 py-3\">\n <li data-testid={`provider-${provider.id}`} data-authenticated={false}>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-40 flex-col\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <span className=\"truncate font-mono text-sm text-po-dim\">{provider.id}</span>\n <span data-testid={`provider-status-${provider.id}`} className=\"sr-only\">\n Not connected\n </span>\n </div>\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {provider.methods.length === 0 ? (\n <span className=\"text-sm text-po-dim\">Reads credentials from your environment</span>\n ) : (\n provider.methods.map((method) => (\n <Button\n key={method.type}\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-login-${method.type}-${provider.id}`}\n title={method.label}\n disabled={busy}\n onClick={() => onLogin(method.type)}\n >\n {ACTION_LABEL[method.type]}\n </Button>\n ))\n )}\n </div>\n </li>\n </SettingsRow>\n );\n}\n\n/**\n * The providers page: what Pi can sign in to on the hub's machine, and\n * whether it has. A login runs on the hub; this page reads its flow on changes and\n * relays the one question it may ask.\n */\nexport function ProviderSettings() {\n const revisionTopic = globalRevisionTopic('providers');\n const revision = useSyncExternalStore(\n (listener) => subscribeHubRevision(revisionTopic, listener),\n () => hubRevision(revisionTopic),\n );\n const [providers, setProviders] = useDashboardState<ProviderAuthSummary[] | null>('providers/list', null);\n const [error, setError] = useDashboardState('providers/error', '');\n const [flow, setFlow, flowState] = useDashboardState<LoginFlowSnapshot | null>('providers/flow', null);\n const [busyId, setBusyId, busyState] = useDashboardState<string | null>('providers/busy', null);\n const [filter, setFilter] = useDashboardState('providers/filter', '');\n\n const reload = useCallback(async (): Promise<void> => {\n const result = await listProviders();\n if ('providers' in result) {\n setProviders(result.providers);\n setError('');\n } else {\n setError(result.error);\n }\n }, [setProviders, setError]);\n\n useEffect(() => {\n // eslint-disable-next-line react/set-state-in-effect -- reads the hub's provider auth list over HTTP; the state is the response.\n void reload();\n }, [reload, revision]);\n\n // Push-capable hubs refresh on revisions; HTTP-only hosts admit polling for the active flow.\n // Once it settles, re-read the provider list so its chip reflects the outcome.\n const flowId = flow?.id;\n const running = flow?.status === 'running';\n const pollIntervalMs = flow?.pollIntervalMs;\n useEffect(() => {\n if (flowId === undefined || !running) return;\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const fetchFlow = async (): Promise<void> => {\n const result = await readLogin(flowId);\n if (stopped || flowState.state?.id !== flowId || flowState.state.status !== 'running') return;\n if ('error' in result) {\n setFlow((current) =>\n current && current.id === flowId ? { ...current, status: 'failed', error: result.error } : current,\n );\n return;\n }\n setFlow(result.flow);\n if (result.flow.status !== 'running') {\n void reload();\n return;\n }\n if (pollIntervalMs !== undefined && pollIntervalMs > 0)\n timer = setTimeout(() => void fetchFlow(), pollIntervalMs);\n };\n void fetchFlow();\n return () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [flowId, running, pollIntervalMs, flowState, reload, setFlow, revision]);\n\n const begin = async (providerId: string, type: AuthMethodType): Promise<void> => {\n if (busyState.state !== null || flowState.state?.status === 'running') return;\n setBusyId(providerId);\n const result = await startLogin(providerId, type);\n setBusyId(null);\n if ('flow' in result) setFlow(result.flow);\n else setError(result.error);\n };\n\n const answer = async (promptId: string, value: string): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await answerLogin(flow.id, promptId, value);\n setBusyId(null);\n if ('flow' in result) {\n const latest = await readLogin(flow.id);\n setFlow('flow' in latest ? latest.flow : result.flow);\n void reload();\n } else setError(result.error);\n };\n\n const cancel = async (): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await cancelLogin(flow.id);\n setBusyId(null);\n setFlow('flow' in result ? result.flow : null);\n void reload();\n };\n\n const signOut = async (providerId: string): Promise<void> => {\n if (busyState.state !== null) return;\n setBusyId(providerId);\n const result = await logoutProvider(providerId);\n if ('error' in result) setError(result.error);\n else await reload();\n setBusyId(null);\n };\n\n // Po's own shared sign-in serves its features, not chat models, so it gets its own\n // section. Connected providers are what a reader came to check, so they get their\n // own group; the long tail is a searchable list rather than a wall of cards.\n const needle = filter.trim().toLowerCase();\n const poFeatures = (providers ?? []).filter((provider) => provider.kind === 'po-feature');\n const models = (providers ?? []).filter((provider) => provider.kind !== 'po-feature');\n const connected = models.filter((provider) => provider.authenticated !== undefined);\n const available = models\n .filter((provider) => provider.authenticated === undefined)\n .filter((provider) => `${provider.name} ${provider.id}`.toLowerCase().includes(needle));\n const busy = busyId !== null || running;\n\n return (\n <SettingsPage data-testid=\"provider-settings\">\n <SettingsHeader>\n <SettingsTitle>Providers</SettingsTitle>\n <SettingsDescription>\n Connect the model providers Po can work with. Sign in once and every session on this machine can use it.\n Credentials stay on this machine, in Pi&apos;s <code className=\"font-mono text-po-hi\">auth.json</code>.\n </SettingsDescription>\n </SettingsHeader>\n {providers === null && !error ? (\n <SettingsCard role=\"status\" aria-busy className=\"gap-4 p-5\">\n <span className=\"sr-only\">Reading providers</span>\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-64\" />\n <Skeleton className=\"h-4 w-52\" />\n </SettingsCard>\n ) : null}\n {providers === null && error ? (\n <SettingsCard>\n <SettingsRow data-testid=\"provider-settings-error\" title={error}>\n <SettingsRowText role=\"alert\">\n <SettingsRowTitle>Po couldn&apos;t reach your providers just now</SettingsRowTitle>\n <SettingsRowDescription>\n Nothing has changed. Your sign-ins are where you left them.\n </SettingsRowDescription>\n </SettingsRowText>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void reload()}>\n Try again\n </Button>\n </SettingsRow>\n </SettingsCard>\n ) : null}\n {providers && error ? (\n <p data-testid=\"provider-settings-error\" role=\"alert\" className=\"text-sm leading-relaxed text-po-red\">\n {error}\n </p>\n ) : null}\n {providers?.length === 0 ? (\n <EmptyState\n data-testid=\"provider-list-empty\"\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"No providers to connect\"\n description=\"Pi hasn't offered any model providers on this machine yet.\"\n />\n ) : null}\n {providers && providers.length > 0 ? (\n <>\n {poFeatures.length > 0 ? (\n <SettingsSection data-testid=\"provider-po-features\">\n <SettingsSectionHeader>\n <SettingsSectionTitle>Po features</SettingsSectionTitle>\n <SettingsSectionDescription>\n Image generation, Web search, and Voice share one ChatGPT sign-in. It is separate from the OpenAI\n Codex sign-in your chat models use.\n </SettingsSectionDescription>\n </SettingsSectionHeader>\n <SettingsCard asChild>\n <ul>\n {poFeatures.map((provider) =>\n provider.authenticated ? (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ) : (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ),\n )}\n </ul>\n </SettingsCard>\n </SettingsSection>\n ) : null}\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Connected</SettingsSectionTitle>\n </SettingsSectionHeader>\n {connected.length > 0 ? (\n <SettingsCard asChild>\n <ul>\n {connected.map((provider) => (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : (\n <EmptyState\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"Nothing connected yet\"\n description=\"Pick a provider below. Po takes it from there.\"\n />\n )}\n </SettingsSection>\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Available</SettingsSectionTitle>\n <span className=\"text-sm text-po-dim\">\n {connected.length} of {models.length} connected\n </span>\n </SettingsSectionHeader>\n <label className=\"flex min-h-11 items-center gap-2.5 rounded-lg border border-po-border bg-po-panel px-3.5 transition-colors focus-within:border-po-green/60\">\n <SearchIcon className=\"size-4 shrink-0 text-po-faint\" />\n <Input\n variant=\"bare\"\n data-testid=\"provider-filter\"\n value={filter}\n placeholder=\"Search providers\"\n aria-label=\"Search providers\"\n onChange={(event) => setFilter(event.target.value)}\n className=\"flex-1\"\n />\n </label>\n {available.length > 0 ? (\n <SettingsCard asChild>\n <ul data-testid=\"provider-list\">\n {available.map((provider) => (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : needle ? (\n <EmptyState\n data-testid=\"provider-no-match\"\n className=\"rounded-lg border border-po-border bg-po-panel py-8\"\n title={`No provider called “${filter.trim()}”`}\n description=\"Try the company name, like Google or Mistral.\"\n />\n ) : (\n <p className=\"text-sm text-po-dim\">Every provider Po knows about is connected.</p>\n )}\n </SettingsSection>\n </>\n ) : null}\n {flow ? (\n <LoginFlowDialog\n flow={flow}\n onAnswer={(promptId, value) => void answer(promptId, value)}\n onCancel={() => void cancel()}\n onClose={() => setFlow(null)}\n />\n ) : null}\n </SettingsPage>\n );\n}\n"],"mappings":"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"}
@@ -158,12 +158,14 @@ function ProviderSettings() {
158
158
  }, [reload, revision]);
159
159
  const flowId = flow?.id;
160
160
  const running = flow?.status === "running";
161
+ const pollIntervalMs = flow?.pollIntervalMs;
161
162
  useEffect(() => {
162
163
  if (flowId === void 0 || !running) return;
163
164
  let stopped = false;
165
+ let timer;
164
166
  const fetchFlow = async () => {
165
167
  const result = await readLogin(flowId);
166
- if (stopped) return;
168
+ if (stopped || flowState.state?.id !== flowId || flowState.state.status !== "running") return;
167
169
  if ("error" in result) {
168
170
  setFlow((current) => current && current.id === flowId ? {
169
171
  ...current,
@@ -177,14 +179,18 @@ function ProviderSettings() {
177
179
  reload();
178
180
  return;
179
181
  }
182
+ if (pollIntervalMs !== void 0 && pollIntervalMs > 0) timer = setTimeout(() => void fetchFlow(), pollIntervalMs);
180
183
  };
181
184
  fetchFlow();
182
185
  return () => {
183
186
  stopped = true;
187
+ if (timer !== void 0) clearTimeout(timer);
184
188
  };
185
189
  }, [
186
190
  flowId,
187
191
  running,
192
+ pollIntervalMs,
193
+ flowState,
188
194
  reload,
189
195
  setFlow,
190
196
  revision
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderSettings.mjs","names":[],"sources":["../../../../src/web/features/settings/ProviderSettings.tsx"],"sourcesContent":["import { globalRevisionTopic, hubRevision, subscribeHubRevision } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Dot,\n EmptyState,\n Input,\n SearchIcon,\n SettingsCard,\n SettingsDescription,\n SettingsHeader,\n SettingsPage,\n SettingsRow,\n SettingsRowDescription,\n SettingsRowText,\n SettingsRowTitle,\n SettingsSection,\n SettingsSectionDescription,\n SettingsSectionHeader,\n SettingsSectionTitle,\n SettingsTitle,\n Skeleton,\n} from '@agimon-ai/po-web-components';\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { AuthMethodType, LoginFlowSnapshot, ProviderAuthSummary } from '../../../types/auth';\nimport { answerLogin, cancelLogin, listProviders, logoutProvider, readLogin, startLogin } from '../../lib/authApi';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { LoginFlowDialog } from './LoginFlowDialog';\nimport { ProviderLogo } from './ProviderLogo';\n\n/** The auth source Pi reports for a credential its own /login stored; the only kind /logout removes. */\nconst STORED_SOURCE = 'stored';\n\nconst ACTION_LABEL: Readonly<Record<AuthMethodType, string>> = { oauth: 'Sign in', api_key: 'Add API key' };\nconst METHOD_NAME: Readonly<Record<AuthMethodType, string>> = { oauth: 'OAuth', api_key: 'an API key' };\n\n/** Where the credential came from, in words: Pi's own store, or the variable that holds the key. */\nfunction ConnectedStatus({ type, source }: NonNullable<ProviderAuthSummary['authenticated']>) {\n if (source === STORED_SOURCE) return <>Signed in with {METHOD_NAME[type]}, saved on this machine</>;\n if (!source) return <>Signed in with {METHOD_NAME[type]}</>;\n return (\n <>\n Using the key in <code className=\"font-mono text-po-hi\">{source}</code>\n </>\n );\n}\n\nfunction ConnectedRow({\n provider,\n busy,\n onLogout,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogout: () => void;\n}) {\n const auth = provider.authenticated;\n if (!auth) return null;\n return (\n <SettingsRow asChild className=\"gap-x-4 py-4\">\n <li data-testid={`provider-${provider.id}`} data-authenticated>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-56 flex-col gap-1.5\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n <Badge tone=\"green\" className=\"rounded-full border-po-edge-green bg-po-tint-green px-2.5 font-medium\">\n <Dot tone=\"green\" />\n Connected\n </Badge>\n <span aria-hidden className=\"hidden h-4 w-px bg-po-border sm:block\" />\n <span data-testid={`provider-status-${provider.id}`} className=\"min-w-0 break-words text-sm text-po-dim\">\n <ConnectedStatus {...auth} />\n </span>\n </div>\n </div>\n {/* Only a credential Pi stored can be removed here; an environment key is the shell's to unset. */}\n {auth.source === STORED_SOURCE ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-logout-${provider.id}`}\n disabled={busy}\n onClick={onLogout}\n >\n Sign out\n </Button>\n ) : auth.source ? (\n <span className=\"shrink-0 text-sm text-po-dim\">Set by your environment</span>\n ) : null}\n </li>\n </SettingsRow>\n );\n}\n\nfunction AvailableRow({\n provider,\n busy,\n onLogin,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogin: (type: AuthMethodType) => void;\n}) {\n return (\n <SettingsRow asChild className=\"gap-x-4 py-3\">\n <li data-testid={`provider-${provider.id}`} data-authenticated={false}>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-40 flex-col\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <span className=\"truncate font-mono text-sm text-po-dim\">{provider.id}</span>\n <span data-testid={`provider-status-${provider.id}`} className=\"sr-only\">\n Not connected\n </span>\n </div>\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {provider.methods.length === 0 ? (\n <span className=\"text-sm text-po-dim\">Reads credentials from your environment</span>\n ) : (\n provider.methods.map((method) => (\n <Button\n key={method.type}\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-login-${method.type}-${provider.id}`}\n title={method.label}\n disabled={busy}\n onClick={() => onLogin(method.type)}\n >\n {ACTION_LABEL[method.type]}\n </Button>\n ))\n )}\n </div>\n </li>\n </SettingsRow>\n );\n}\n\n/**\n * The providers page: what Pi can sign in to on the hub's machine, and\n * whether it has. A login runs on the hub; this page reads its flow on changes and\n * relays the one question it may ask.\n */\nexport function ProviderSettings() {\n const revisionTopic = globalRevisionTopic('providers');\n const revision = useSyncExternalStore(\n (listener) => subscribeHubRevision(revisionTopic, listener),\n () => hubRevision(revisionTopic),\n );\n const [providers, setProviders] = useDashboardState<ProviderAuthSummary[] | null>('providers/list', null);\n const [error, setError] = useDashboardState('providers/error', '');\n const [flow, setFlow, flowState] = useDashboardState<LoginFlowSnapshot | null>('providers/flow', null);\n const [busyId, setBusyId, busyState] = useDashboardState<string | null>('providers/busy', null);\n const [filter, setFilter] = useDashboardState('providers/filter', '');\n\n const reload = useCallback(async (): Promise<void> => {\n const result = await listProviders();\n if ('providers' in result) {\n setProviders(result.providers);\n setError('');\n } else {\n setError(result.error);\n }\n }, [setProviders, setError]);\n\n useEffect(() => {\n // eslint-disable-next-line react/set-state-in-effect -- reads the hub's provider auth list over HTTP; the state is the response.\n void reload();\n }, [reload, revision]);\n\n // Read the running flow on open and pushed changes; once it settles, the list\n // is re-read so the provider's chip reflects the outcome.\n const flowId = flow?.id;\n const running = flow?.status === 'running';\n useEffect(() => {\n if (flowId === undefined || !running) return;\n let stopped = false;\n const fetchFlow = async (): Promise<void> => {\n const result = await readLogin(flowId);\n if (stopped) return;\n if ('error' in result) {\n setFlow((current) =>\n current && current.id === flowId ? { ...current, status: 'failed', error: result.error } : current,\n );\n return;\n }\n setFlow(result.flow);\n if (result.flow.status !== 'running') {\n void reload();\n return;\n }\n };\n void fetchFlow();\n return () => {\n stopped = true;\n };\n }, [flowId, running, reload, setFlow, revision]);\n\n const begin = async (providerId: string, type: AuthMethodType): Promise<void> => {\n if (busyState.state !== null || flowState.state?.status === 'running') return;\n setBusyId(providerId);\n const result = await startLogin(providerId, type);\n setBusyId(null);\n if ('flow' in result) setFlow(result.flow);\n else setError(result.error);\n };\n\n const answer = async (promptId: string, value: string): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await answerLogin(flow.id, promptId, value);\n setBusyId(null);\n if ('flow' in result) {\n const latest = await readLogin(flow.id);\n setFlow('flow' in latest ? latest.flow : result.flow);\n void reload();\n } else setError(result.error);\n };\n\n const cancel = async (): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await cancelLogin(flow.id);\n setBusyId(null);\n setFlow('flow' in result ? result.flow : null);\n void reload();\n };\n\n const signOut = async (providerId: string): Promise<void> => {\n if (busyState.state !== null) return;\n setBusyId(providerId);\n const result = await logoutProvider(providerId);\n if ('error' in result) setError(result.error);\n else await reload();\n setBusyId(null);\n };\n\n // Po's own shared sign-in serves its features, not chat models, so it gets its own\n // section. Connected providers are what a reader came to check, so they get their\n // own group; the long tail is a searchable list rather than a wall of cards.\n const needle = filter.trim().toLowerCase();\n const poFeatures = (providers ?? []).filter((provider) => provider.kind === 'po-feature');\n const models = (providers ?? []).filter((provider) => provider.kind !== 'po-feature');\n const connected = models.filter((provider) => provider.authenticated !== undefined);\n const available = models\n .filter((provider) => provider.authenticated === undefined)\n .filter((provider) => `${provider.name} ${provider.id}`.toLowerCase().includes(needle));\n const busy = busyId !== null || running;\n\n return (\n <SettingsPage data-testid=\"provider-settings\">\n <SettingsHeader>\n <SettingsTitle>Providers</SettingsTitle>\n <SettingsDescription>\n Connect the model providers Po can work with. Sign in once and every session on this machine can use it.\n Credentials stay on this machine, in Pi&apos;s <code className=\"font-mono text-po-hi\">auth.json</code>.\n </SettingsDescription>\n </SettingsHeader>\n {providers === null && !error ? (\n <SettingsCard role=\"status\" aria-busy className=\"gap-4 p-5\">\n <span className=\"sr-only\">Reading providers</span>\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-64\" />\n <Skeleton className=\"h-4 w-52\" />\n </SettingsCard>\n ) : null}\n {providers === null && error ? (\n <SettingsCard>\n <SettingsRow data-testid=\"provider-settings-error\" title={error}>\n <SettingsRowText role=\"alert\">\n <SettingsRowTitle>Po couldn&apos;t reach your providers just now</SettingsRowTitle>\n <SettingsRowDescription>\n Nothing has changed. Your sign-ins are where you left them.\n </SettingsRowDescription>\n </SettingsRowText>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void reload()}>\n Try again\n </Button>\n </SettingsRow>\n </SettingsCard>\n ) : null}\n {providers && error ? (\n <p data-testid=\"provider-settings-error\" role=\"alert\" className=\"text-sm leading-relaxed text-po-red\">\n {error}\n </p>\n ) : null}\n {providers?.length === 0 ? (\n <EmptyState\n data-testid=\"provider-list-empty\"\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"No providers to connect\"\n description=\"Pi hasn't offered any model providers on this machine yet.\"\n />\n ) : null}\n {providers && providers.length > 0 ? (\n <>\n {poFeatures.length > 0 ? (\n <SettingsSection data-testid=\"provider-po-features\">\n <SettingsSectionHeader>\n <SettingsSectionTitle>Po features</SettingsSectionTitle>\n <SettingsSectionDescription>\n Image generation, Web search, and Voice share one ChatGPT sign-in. It is separate from the OpenAI\n Codex sign-in your chat models use.\n </SettingsSectionDescription>\n </SettingsSectionHeader>\n <SettingsCard asChild>\n <ul>\n {poFeatures.map((provider) =>\n provider.authenticated ? (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ) : (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ),\n )}\n </ul>\n </SettingsCard>\n </SettingsSection>\n ) : null}\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Connected</SettingsSectionTitle>\n </SettingsSectionHeader>\n {connected.length > 0 ? (\n <SettingsCard asChild>\n <ul>\n {connected.map((provider) => (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : (\n <EmptyState\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"Nothing connected yet\"\n description=\"Pick a provider below. Po takes it from there.\"\n />\n )}\n </SettingsSection>\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Available</SettingsSectionTitle>\n <span className=\"text-sm text-po-dim\">\n {connected.length} of {models.length} connected\n </span>\n </SettingsSectionHeader>\n <label className=\"flex min-h-11 items-center gap-2.5 rounded-lg border border-po-border bg-po-panel px-3.5 transition-colors focus-within:border-po-green/60\">\n <SearchIcon className=\"size-4 shrink-0 text-po-faint\" />\n <Input\n variant=\"bare\"\n data-testid=\"provider-filter\"\n value={filter}\n placeholder=\"Search providers\"\n aria-label=\"Search providers\"\n onChange={(event) => setFilter(event.target.value)}\n className=\"flex-1\"\n />\n </label>\n {available.length > 0 ? (\n <SettingsCard asChild>\n <ul data-testid=\"provider-list\">\n {available.map((provider) => (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : needle ? (\n <EmptyState\n data-testid=\"provider-no-match\"\n className=\"rounded-lg border border-po-border bg-po-panel py-8\"\n title={`No provider called “${filter.trim()}”`}\n description=\"Try the company name, like Google or Mistral.\"\n />\n ) : (\n <p className=\"text-sm text-po-dim\">Every provider Po knows about is connected.</p>\n )}\n </SettingsSection>\n </>\n ) : null}\n {flow ? (\n <LoginFlowDialog\n flow={flow}\n onAnswer={(promptId, value) => void answer(promptId, value)}\n onCancel={() => void cancel()}\n onClose={() => setFlow(null)}\n />\n ) : null}\n </SettingsPage>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProviderSettings.mjs","names":[],"sources":["../../../../src/web/features/settings/ProviderSettings.tsx"],"sourcesContent":["import { globalRevisionTopic, hubRevision, subscribeHubRevision } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Dot,\n EmptyState,\n Input,\n SearchIcon,\n SettingsCard,\n SettingsDescription,\n SettingsHeader,\n SettingsPage,\n SettingsRow,\n SettingsRowDescription,\n SettingsRowText,\n SettingsRowTitle,\n SettingsSection,\n SettingsSectionDescription,\n SettingsSectionHeader,\n SettingsSectionTitle,\n SettingsTitle,\n Skeleton,\n} from '@agimon-ai/po-web-components';\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\n\nimport type { AuthMethodType, LoginFlowSnapshot, ProviderAuthSummary } from '../../../types/auth';\nimport { answerLogin, cancelLogin, listProviders, logoutProvider, readLogin, startLogin } from '../../lib/authApi';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { LoginFlowDialog } from './LoginFlowDialog';\nimport { ProviderLogo } from './ProviderLogo';\n\n/** The auth source Pi reports for a credential its own /login stored; the only kind /logout removes. */\nconst STORED_SOURCE = 'stored';\n\nconst ACTION_LABEL: Readonly<Record<AuthMethodType, string>> = { oauth: 'Sign in', api_key: 'Add API key' };\nconst METHOD_NAME: Readonly<Record<AuthMethodType, string>> = { oauth: 'OAuth', api_key: 'an API key' };\n\n/** Where the credential came from, in words: Pi's own store, or the variable that holds the key. */\nfunction ConnectedStatus({ type, source }: NonNullable<ProviderAuthSummary['authenticated']>) {\n if (source === STORED_SOURCE) return <>Signed in with {METHOD_NAME[type]}, saved on this machine</>;\n if (!source) return <>Signed in with {METHOD_NAME[type]}</>;\n return (\n <>\n Using the key in <code className=\"font-mono text-po-hi\">{source}</code>\n </>\n );\n}\n\nfunction ConnectedRow({\n provider,\n busy,\n onLogout,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogout: () => void;\n}) {\n const auth = provider.authenticated;\n if (!auth) return null;\n return (\n <SettingsRow asChild className=\"gap-x-4 py-4\">\n <li data-testid={`provider-${provider.id}`} data-authenticated>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-56 flex-col gap-1.5\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n <Badge tone=\"green\" className=\"rounded-full border-po-edge-green bg-po-tint-green px-2.5 font-medium\">\n <Dot tone=\"green\" />\n Connected\n </Badge>\n <span aria-hidden className=\"hidden h-4 w-px bg-po-border sm:block\" />\n <span data-testid={`provider-status-${provider.id}`} className=\"min-w-0 break-words text-sm text-po-dim\">\n <ConnectedStatus {...auth} />\n </span>\n </div>\n </div>\n {/* Only a credential Pi stored can be removed here; an environment key is the shell's to unset. */}\n {auth.source === STORED_SOURCE ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-logout-${provider.id}`}\n disabled={busy}\n onClick={onLogout}\n >\n Sign out\n </Button>\n ) : auth.source ? (\n <span className=\"shrink-0 text-sm text-po-dim\">Set by your environment</span>\n ) : null}\n </li>\n </SettingsRow>\n );\n}\n\nfunction AvailableRow({\n provider,\n busy,\n onLogin,\n}: {\n provider: ProviderAuthSummary;\n busy: boolean;\n onLogin: (type: AuthMethodType) => void;\n}) {\n return (\n <SettingsRow asChild className=\"gap-x-4 py-3\">\n <li data-testid={`provider-${provider.id}`} data-authenticated={false}>\n <ProviderLogo id={provider.id} name={provider.name} />\n <div className=\"flex min-w-0 flex-1 basis-40 flex-col\">\n <span className=\"truncate text-base font-semibold text-po-hi\">{provider.name}</span>\n <span className=\"truncate font-mono text-sm text-po-dim\">{provider.id}</span>\n <span data-testid={`provider-status-${provider.id}`} className=\"sr-only\">\n Not connected\n </span>\n </div>\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {provider.methods.length === 0 ? (\n <span className=\"text-sm text-po-dim\">Reads credentials from your environment</span>\n ) : (\n provider.methods.map((method) => (\n <Button\n key={method.type}\n variant=\"outline\"\n size=\"sm\"\n data-testid={`provider-login-${method.type}-${provider.id}`}\n title={method.label}\n disabled={busy}\n onClick={() => onLogin(method.type)}\n >\n {ACTION_LABEL[method.type]}\n </Button>\n ))\n )}\n </div>\n </li>\n </SettingsRow>\n );\n}\n\n/**\n * The providers page: what Pi can sign in to on the hub's machine, and\n * whether it has. A login runs on the hub; this page reads its flow on changes and\n * relays the one question it may ask.\n */\nexport function ProviderSettings() {\n const revisionTopic = globalRevisionTopic('providers');\n const revision = useSyncExternalStore(\n (listener) => subscribeHubRevision(revisionTopic, listener),\n () => hubRevision(revisionTopic),\n );\n const [providers, setProviders] = useDashboardState<ProviderAuthSummary[] | null>('providers/list', null);\n const [error, setError] = useDashboardState('providers/error', '');\n const [flow, setFlow, flowState] = useDashboardState<LoginFlowSnapshot | null>('providers/flow', null);\n const [busyId, setBusyId, busyState] = useDashboardState<string | null>('providers/busy', null);\n const [filter, setFilter] = useDashboardState('providers/filter', '');\n\n const reload = useCallback(async (): Promise<void> => {\n const result = await listProviders();\n if ('providers' in result) {\n setProviders(result.providers);\n setError('');\n } else {\n setError(result.error);\n }\n }, [setProviders, setError]);\n\n useEffect(() => {\n // eslint-disable-next-line react/set-state-in-effect -- reads the hub's provider auth list over HTTP; the state is the response.\n void reload();\n }, [reload, revision]);\n\n // Push-capable hubs refresh on revisions; HTTP-only hosts admit polling for the active flow.\n // Once it settles, re-read the provider list so its chip reflects the outcome.\n const flowId = flow?.id;\n const running = flow?.status === 'running';\n const pollIntervalMs = flow?.pollIntervalMs;\n useEffect(() => {\n if (flowId === undefined || !running) return;\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const fetchFlow = async (): Promise<void> => {\n const result = await readLogin(flowId);\n if (stopped || flowState.state?.id !== flowId || flowState.state.status !== 'running') return;\n if ('error' in result) {\n setFlow((current) =>\n current && current.id === flowId ? { ...current, status: 'failed', error: result.error } : current,\n );\n return;\n }\n setFlow(result.flow);\n if (result.flow.status !== 'running') {\n void reload();\n return;\n }\n if (pollIntervalMs !== undefined && pollIntervalMs > 0)\n timer = setTimeout(() => void fetchFlow(), pollIntervalMs);\n };\n void fetchFlow();\n return () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n };\n }, [flowId, running, pollIntervalMs, flowState, reload, setFlow, revision]);\n\n const begin = async (providerId: string, type: AuthMethodType): Promise<void> => {\n if (busyState.state !== null || flowState.state?.status === 'running') return;\n setBusyId(providerId);\n const result = await startLogin(providerId, type);\n setBusyId(null);\n if ('flow' in result) setFlow(result.flow);\n else setError(result.error);\n };\n\n const answer = async (promptId: string, value: string): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await answerLogin(flow.id, promptId, value);\n setBusyId(null);\n if ('flow' in result) {\n const latest = await readLogin(flow.id);\n setFlow('flow' in latest ? latest.flow : result.flow);\n void reload();\n } else setError(result.error);\n };\n\n const cancel = async (): Promise<void> => {\n if (!flow || busyState.state !== null) return;\n setBusyId(flow.providerId);\n const result = await cancelLogin(flow.id);\n setBusyId(null);\n setFlow('flow' in result ? result.flow : null);\n void reload();\n };\n\n const signOut = async (providerId: string): Promise<void> => {\n if (busyState.state !== null) return;\n setBusyId(providerId);\n const result = await logoutProvider(providerId);\n if ('error' in result) setError(result.error);\n else await reload();\n setBusyId(null);\n };\n\n // Po's own shared sign-in serves its features, not chat models, so it gets its own\n // section. Connected providers are what a reader came to check, so they get their\n // own group; the long tail is a searchable list rather than a wall of cards.\n const needle = filter.trim().toLowerCase();\n const poFeatures = (providers ?? []).filter((provider) => provider.kind === 'po-feature');\n const models = (providers ?? []).filter((provider) => provider.kind !== 'po-feature');\n const connected = models.filter((provider) => provider.authenticated !== undefined);\n const available = models\n .filter((provider) => provider.authenticated === undefined)\n .filter((provider) => `${provider.name} ${provider.id}`.toLowerCase().includes(needle));\n const busy = busyId !== null || running;\n\n return (\n <SettingsPage data-testid=\"provider-settings\">\n <SettingsHeader>\n <SettingsTitle>Providers</SettingsTitle>\n <SettingsDescription>\n Connect the model providers Po can work with. Sign in once and every session on this machine can use it.\n Credentials stay on this machine, in Pi&apos;s <code className=\"font-mono text-po-hi\">auth.json</code>.\n </SettingsDescription>\n </SettingsHeader>\n {providers === null && !error ? (\n <SettingsCard role=\"status\" aria-busy className=\"gap-4 p-5\">\n <span className=\"sr-only\">Reading providers</span>\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"h-4 w-64\" />\n <Skeleton className=\"h-4 w-52\" />\n </SettingsCard>\n ) : null}\n {providers === null && error ? (\n <SettingsCard>\n <SettingsRow data-testid=\"provider-settings-error\" title={error}>\n <SettingsRowText role=\"alert\">\n <SettingsRowTitle>Po couldn&apos;t reach your providers just now</SettingsRowTitle>\n <SettingsRowDescription>\n Nothing has changed. Your sign-ins are where you left them.\n </SettingsRowDescription>\n </SettingsRowText>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void reload()}>\n Try again\n </Button>\n </SettingsRow>\n </SettingsCard>\n ) : null}\n {providers && error ? (\n <p data-testid=\"provider-settings-error\" role=\"alert\" className=\"text-sm leading-relaxed text-po-red\">\n {error}\n </p>\n ) : null}\n {providers?.length === 0 ? (\n <EmptyState\n data-testid=\"provider-list-empty\"\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"No providers to connect\"\n description=\"Pi hasn't offered any model providers on this machine yet.\"\n />\n ) : null}\n {providers && providers.length > 0 ? (\n <>\n {poFeatures.length > 0 ? (\n <SettingsSection data-testid=\"provider-po-features\">\n <SettingsSectionHeader>\n <SettingsSectionTitle>Po features</SettingsSectionTitle>\n <SettingsSectionDescription>\n Image generation, Web search, and Voice share one ChatGPT sign-in. It is separate from the OpenAI\n Codex sign-in your chat models use.\n </SettingsSectionDescription>\n </SettingsSectionHeader>\n <SettingsCard asChild>\n <ul>\n {poFeatures.map((provider) =>\n provider.authenticated ? (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ) : (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ),\n )}\n </ul>\n </SettingsCard>\n </SettingsSection>\n ) : null}\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Connected</SettingsSectionTitle>\n </SettingsSectionHeader>\n {connected.length > 0 ? (\n <SettingsCard asChild>\n <ul>\n {connected.map((provider) => (\n <ConnectedRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogout={() => void signOut(provider.id)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : (\n <EmptyState\n className=\"rounded-lg border border-dashed border-po-border py-8\"\n title=\"Nothing connected yet\"\n description=\"Pick a provider below. Po takes it from there.\"\n />\n )}\n </SettingsSection>\n <SettingsSection>\n <SettingsSectionHeader>\n <SettingsSectionTitle>Available</SettingsSectionTitle>\n <span className=\"text-sm text-po-dim\">\n {connected.length} of {models.length} connected\n </span>\n </SettingsSectionHeader>\n <label className=\"flex min-h-11 items-center gap-2.5 rounded-lg border border-po-border bg-po-panel px-3.5 transition-colors focus-within:border-po-green/60\">\n <SearchIcon className=\"size-4 shrink-0 text-po-faint\" />\n <Input\n variant=\"bare\"\n data-testid=\"provider-filter\"\n value={filter}\n placeholder=\"Search providers\"\n aria-label=\"Search providers\"\n onChange={(event) => setFilter(event.target.value)}\n className=\"flex-1\"\n />\n </label>\n {available.length > 0 ? (\n <SettingsCard asChild>\n <ul data-testid=\"provider-list\">\n {available.map((provider) => (\n <AvailableRow\n key={provider.id}\n provider={provider}\n busy={busy}\n onLogin={(type) => void begin(provider.id, type)}\n />\n ))}\n </ul>\n </SettingsCard>\n ) : needle ? (\n <EmptyState\n data-testid=\"provider-no-match\"\n className=\"rounded-lg border border-po-border bg-po-panel py-8\"\n title={`No provider called “${filter.trim()}”`}\n description=\"Try the company name, like Google or Mistral.\"\n />\n ) : (\n <p className=\"text-sm text-po-dim\">Every provider Po knows about is connected.</p>\n )}\n </SettingsSection>\n </>\n ) : null}\n {flow ? (\n <LoginFlowDialog\n flow={flow}\n onAnswer={(promptId, value) => void answer(promptId, value)}\n onCancel={() => void cancel()}\n onClose={() => setFlow(null)}\n />\n ) : null}\n </SettingsPage>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-web-dashboard",
3
- "version": "0.0.0-alpha.113",
3
+ "version": "0.0.0-alpha.115",
4
4
  "description": "Shared browser state, actions and optional views for Po dashboard templates.",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,9 +61,9 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@agimon-ai/po-core": "0.0.0-alpha.113",
65
- "@agimon-ai/po-web-components": "0.0.0-alpha.113",
66
- "@agimon-ai/po-web-security": "0.0.0-alpha.113",
64
+ "@agimon-ai/po-core": "0.0.0-alpha.115",
65
+ "@agimon-ai/po-web-components": "0.0.0-alpha.115",
66
+ "@agimon-ai/po-web-security": "0.0.0-alpha.115",
67
67
  "@earendil-works/chord": "1.5.0",
68
68
  "@simplewebauthn/browser": "^14.0.0",
69
69
  "@tanstack/react-router": "1.170.45",
@@ -73,7 +73,7 @@
73
73
  "qrcode-generator": "^2.0.4"
74
74
  },
75
75
  "devDependencies": {
76
- "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.113",
76
+ "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.115",
77
77
  "@types/node": "26.6.5",
78
78
  "@types/react": "19.3.0",
79
79
  "@types/react-dom": "19.3.0",