@agimon-ai/po-core 0.0.0-alpha.116 → 0.0.0-alpha.117

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.
@@ -80,20 +80,21 @@ function createProviderAuthOwner(options) {
80
80
  pending = pending.then(recompute, recompute);
81
81
  return pending;
82
82
  };
83
- const models = (0, _earendil_works_pi_ai.createModels)({
84
- credentials: {
85
- read: (providerId, operation) => options.credentials.read(providerId, operation),
86
- list: (operation) => options.credentials.list(operation),
87
- modify: async (providerId, fn, operation) => {
88
- const result = await options.credentials.modify(providerId, fn, operation);
89
- changed().catch(report);
90
- return result;
91
- },
92
- delete: async (providerId, operation) => {
93
- await options.credentials.delete(providerId, operation);
94
- changed().catch(report);
95
- }
83
+ const credentials = {
84
+ read: (providerId, operation) => options.credentials.read(providerId, operation),
85
+ list: (operation) => options.credentials.list(operation),
86
+ modify: async (providerId, fn, operation) => {
87
+ const result = await options.credentials.modify(providerId, fn, operation);
88
+ changed().catch(report);
89
+ return result;
96
90
  },
91
+ delete: async (providerId, operation) => {
92
+ await options.credentials.delete(providerId, operation);
93
+ changed().catch(report);
94
+ }
95
+ };
96
+ const models = (0, _earendil_works_pi_ai.createModels)({
97
+ credentials,
97
98
  modelsStore: options.modelsStore,
98
99
  authContext: NO_AMBIENT_AUTH
99
100
  });
@@ -110,6 +111,7 @@ function createProviderAuthOwner(options) {
110
111
  let current;
111
112
  async function recompute() {
112
113
  const providers = models.getProviders();
114
+ const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));
113
115
  const checks = await Promise.all(providers.map(async (provider) => ({
114
116
  provider,
115
117
  check: await models.checkAuth(provider.id).catch(() => void 0)
@@ -124,7 +126,7 @@ function createProviderAuthOwner(options) {
124
126
  providers: checks.flatMap(({ provider, check }) => check === void 0 ? [] : [{
125
127
  id: provider.id,
126
128
  type: check.type,
127
- ...check.source === void 0 ? {} : { source: check.source },
129
+ source: stored.get(provider.id) === check.type ? "stored" : check.source,
128
130
  subscription: check.type === "oauth" && provider.auth.oauth?.isSubscription === true
129
131
  }]),
130
132
  available: (await models.getAllAvailable()).map((model) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["openAICompletionsApi","openAIResponsesApi","anthropicMessagesApi","createProvider","envApiKeyAuth","createModels","builtinProviders","getModelType","ModelsError"],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n ...(check.source === undefined ? {} : { source: check.source }),\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["openAICompletionsApi","openAIResponsesApi","anthropicMessagesApi","createProvider","envApiKeyAuth","createModels","builtinProviders","getModelType","ModelsError"],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n source: stored.get(provider.id) === check.type ? 'stored' : check.source,\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"mappings":";;;;;;wBAsCgB,eAAe,YAAY,6BAA6B;;wBAuCxD,oBAAoB,OAAO,mBAAmB;;;;;;wBAiB9C,wBAAwB,SAAS,2BAA2B;;;;;;wBA4JtD,8BAA8B,SAAS,iCAAiC,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"mappings":";;;;;;wBAsCgB,eAAe,YAAY,6BAA6B;;wBAuCxD,oBAAoB,OAAO,mBAAmB;;;;;;wBAiB9C,wBAAwB,SAAS,2BAA2B;;;;;;wBA6JtD,8BAA8B,SAAS,iCAAiC,QAAQ"}
@@ -80,20 +80,21 @@ function createProviderAuthOwner(options) {
80
80
  pending = pending.then(recompute, recompute);
81
81
  return pending;
82
82
  };
83
- const models = createModels({
84
- credentials: {
85
- read: (providerId, operation) => options.credentials.read(providerId, operation),
86
- list: (operation) => options.credentials.list(operation),
87
- modify: async (providerId, fn, operation) => {
88
- const result = await options.credentials.modify(providerId, fn, operation);
89
- changed().catch(report);
90
- return result;
91
- },
92
- delete: async (providerId, operation) => {
93
- await options.credentials.delete(providerId, operation);
94
- changed().catch(report);
95
- }
83
+ const credentials = {
84
+ read: (providerId, operation) => options.credentials.read(providerId, operation),
85
+ list: (operation) => options.credentials.list(operation),
86
+ modify: async (providerId, fn, operation) => {
87
+ const result = await options.credentials.modify(providerId, fn, operation);
88
+ changed().catch(report);
89
+ return result;
96
90
  },
91
+ delete: async (providerId, operation) => {
92
+ await options.credentials.delete(providerId, operation);
93
+ changed().catch(report);
94
+ }
95
+ };
96
+ const models = createModels({
97
+ credentials,
97
98
  modelsStore: options.modelsStore,
98
99
  authContext: NO_AMBIENT_AUTH
99
100
  });
@@ -110,6 +111,7 @@ function createProviderAuthOwner(options) {
110
111
  let current;
111
112
  async function recompute() {
112
113
  const providers = models.getProviders();
114
+ const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));
113
115
  const checks = await Promise.all(providers.map(async (provider) => ({
114
116
  provider,
115
117
  check: await models.checkAuth(provider.id).catch(() => void 0)
@@ -124,7 +126,7 @@ function createProviderAuthOwner(options) {
124
126
  providers: checks.flatMap(({ provider, check }) => check === void 0 ? [] : [{
125
127
  id: provider.id,
126
128
  type: check.type,
127
- ...check.source === void 0 ? {} : { source: check.source },
129
+ source: stored.get(provider.id) === check.type ? "stored" : check.source,
128
130
  subscription: check.type === "oauth" && provider.auth.oauth?.isSubscription === true
129
131
  }]),
130
132
  available: (await models.getAllAvailable()).map((model) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n ...(check.source === undefined ? {} : { source: check.source }),\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":";;;;;;AA0BA,MAAM,oBAAoB;;AAG1B,MAAM,kBAA+B;CAAE,KAAK,YAAY,KAAA;CAAW,YAAY,YAAY;AAAM;AAEjG,MAAM,aAAa;CACjB,sBAAsB;CACtB,oBAAoB;CACpB,sBAAsB;AACxB;;AAGA,SAAgB,eAAe,YAAkD;CAC/E,OAAO,eAAe;EACpB,IAAI,WAAW;EACf,MAAM,WAAW;EACjB,SAAS,WAAW;EACpB,MAAM,EAAE,QAAQ,cAAc,GAAG,WAAW,KAAK,WAAW,CAAC,CAAC,EAAE;EAChE,QAAQ,WAAW,OAAO,KAAK,WAAW;GACxC,IAAI,MAAM;GACV,MAAM,MAAM,QAAQ,MAAM;GAC1B,KAAK,WAAW;GAChB,UAAU,WAAW;GACrB,SAAS,WAAW;GACpB,WAAW,MAAM,aAAa;GAC9B,OAAO,CAAC,GAAI,MAAM,SAAS,CAAC,MAAM,CAAE;GACpC,MAAM;IAAE,OAAO;IAAG,QAAQ;IAAG,WAAW;IAAG,YAAY;GAAE;GACzD,eAAe,MAAM,iBAAiB;GACtC,WAAW,MAAM,aAAa;EAChC,EAAE;EACF,KAAK,WAAW,WAAW,IAAI,CAAC;CAClC,CAAC;AACH;;AAGA,SAAS,UAAU,OAAuB;CACxC,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,KAAK;CACrB,QAAQ;EACN,OAAO;CACT;CACA,IAAI,IAAI,aAAa,MAAM,IAAI,aAAa,MAAM,IAAI,WAAW,MAAM,IAAI,SAAS,IAAI,OAAO;CAC/F,IAAI,WAAW;CACf,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO,IAAI;AACb;;AAGA,SAAgB,oBAAoB,OAA2C;CAC7E,OAAO,KAAK,MACV,KAAK,UAAU;EACb,GAAG;EACH,QAAQ,MAAM,OAAO,KAAK,EAAE,SAAS,UAAU,GAAG,aAAa;GAAE,GAAG;GAAO,SAAS,UAAU,MAAM,OAAO;EAAE,EAAE;CACjH,CAAC,CACH;AACF;AAEA,MAAM,gBAAgB,UACpB,GAAG,MAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,IAAI,MAAM;;;;;;AAOvD,SAAgB,wBAAwB,SAAsD;CAE5F,MAAM,SAAS,QAAQ,kBAAwB,KAAA;CAC/C,IAAI,UAAyB,QAAQ,QAAQ;CAC7C,MAAM,gBAA+B;EACnC,UAAU,QAAQ,KAAK,WAAW,SAAS;EAC3C,OAAO;CACT;CAcA,MAAM,SAAS,aAAa;EAAE,aAAA;GAZ5B,OAAO,YAAY,cAAc,QAAQ,YAAY,KAAK,YAAY,SAAS;GAC/E,OAAO,cAAc,QAAQ,YAAY,KAAK,SAAS;GACvD,QAAQ,OAAO,YAAY,IAAI,cAAc;IAC3C,MAAM,SAAS,MAAM,QAAQ,YAAY,OAAO,YAAY,IAAI,SAAS;IACzE,QAAa,CAAC,CAAC,MAAM,MAAM;IAC3B,OAAO;GACT;GACA,QAAQ,OAAO,YAAY,cAAc;IACvC,MAAM,QAAQ,YAAY,OAAO,YAAY,SAAS;IACtD,QAAa,CAAC,CAAC,MAAM,MAAM;GAC7B;EAEsC;EAAG,aAAa,QAAQ;EAAa,aAAa;CAAgB,CAAC;CAC3G,KAAK,MAAM,aAAa,QAAQ,aAAa,iBAAA,CAAkB,GAAG,OAAO,YAAY,QAAQ;CAE7F,IAAI,kBAAyD,CAAC;CAC9D,MAAM,wBAAwB,gBAA6D;EACzF,KAAK,MAAM,YAAY,iBACrB,IAAI,CAAC,YAAY,MAAM,eAAe,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,eAAe,SAAS,EAAE;EACzG,KAAK,MAAM,cAAc,aAAa,OAAO,YAAY,eAAe,UAAU,CAAC;EACnF,kBAAkB;CACpB;CACA,qBAAqB,QAAQ,mBAAmB,CAAC,CAAC;CAGlD,IAAI,UAAU,KAAK,IAAI;CACvB,IAAI,WAAW;CACf,IAAI;CACJ,eAAe,YAA2B;EACxC,MAAM,YAAY,OAAO,aAAa;EACtC,MAAM,SAAS,MAAM,QAAQ,IAC3B,UAAU,IAAI,OAAO,cAAc;GACjC;GACA,OAAO,MAAM,OAAO,UAAU,SAAS,EAAE,CAAC,CAAC,YAAY,KAAA,CAAS;EAClE,EAAE,CACJ;EACA,MAAM,WAA6C,CAAC;EACpD,KAAK,MAAM,YAAY,WAAW;GAChC,IAAI,SAAS,kBAAkB,KAAA,GAAW;GAC1C,MAAM,QAAQ,MAAM,QAAQ,YAAY,KAAK,SAAS,EAAE;GACxD,IAAI,UAAU,KAAA,GAAW,SAAS,SAAS,MAAM,oBAAoB,KAAK;EAC5E;EACA,MAAM,OAAO;GACX,WAAW,OAAO,SAAS,EAAE,UAAU,YACrC,UAAU,KAAA,IACN,CAAC,IACD,CACE;IACE,IAAI,SAAS;IACb,MAAM,MAAM;IACZ,GAAI,MAAM,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,OAAO;IAC7D,cAAc,MAAM,SAAS,WAAW,SAAS,KAAK,OAAO,mBAAmB;GAClF,CACF,CACN;GACA,YAAY,MAAM,OAAO,gBAAgB,EAAA,CAAG,KAAK,WAAW;IAC1D,UAAU,MAAM;IAChB,IAAI,MAAM;IACV,MAAM,aAAa,KAAK;GAC1B,EAAE;GACF,QAAQ;GACR;EACF;EACA,MAAM,aAAa,KAAK,UAAU,IAAI;EACtC,IAAI,eAAe,YAAY,YAAY,KAAA,GAAW;EACtD,WAAW;EACX,WAAW;EACX,UAAU;GAAE;GAAS,GAAI,KAAK,MAAM,UAAU;EAA8C;CAC9F;CACA,MAAM,UAAU,YAA6C;EAC3D,MAAM;EACN,IAAI,YAAY,KAAA,GAAW,MAAM,QAAQ;EACzC,OAAO;CACT;CACA,QAAa,CAAC,CAAC,MAAM,MAAM;CAE3B,MAAM,YAAY,eAAuB,SAAS,UAAU,MAAM,UAAU,MAAM,OAAO,UAAU;CACnG,MAAM,UAAqC,OAAO,mBAAmB;EACnE,MAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,cAAc,gBAAgB,gBAAgB,KAAK,CAAC;EAC1F,MAAM,QAAQ;EAEd,OAAO;GACL,UAAS,MAFY,QAAQ,EAAA,CAEX;GAClB,QAAQ,OAAO,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,WAAW,CAAC,YAAY,MAAM,OAAO,CAAC,CAAC;EACzG;CACF;CAmCA,OAAO;EACL,SAAA;GAjCA,YAAY;GACZ,aAAa,OAAO,eAAe;IACjC,MAAM,OAAO,MAAM,OAAO,QAAQ,UAAU;IAC5C,OAAO;KAAE,UAAU,MAAM,QAAQ,EAAA,CAAG;KAAS,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;IAAG;GACvF;GACA,UAAU,OAAO,iBAAiB;IAChC,MAAM,WAAW,MAAM,QAAQ;IAC/B,OAAO,iBAAiB,SAAS,UAAU,EAAE,SAAS,SAAS,QAAQ,IAAI;GAC7E;GACA;EAwBM;EACN,aAAA;GArBA,oBAAoB,OAAO,aAAa;GACxC,wBAAwB,eAAe;IACrC,MAAM,SAAS,SAAS,UAAU;IAClC,OAAO,WAAW,KAAA,IAAY,EAAE,YAAY,MAAM,IAAI;KAAE,YAAY;KAAM,QAAQ,OAAO;IAAO;GAClG;GACA,6BAA6B,SAAS,aAAa,CAAC,EAAA,CAAG,QAAQ,UAAU,MAAM,SAAS,MAAM;GAC9F,oBAAoB,eAAe,SAAS,UAAU,MAAM,KAAA;GAC5D,eAAe,eAAe,SAAS,UAAU,CAAC,EAAE,SAAS;GAC7D,UAAU,mBAAmB,QAAQ,cAAc;GACnD,OAAO,OAAO,YAAY,MAAM,gBAAgB;IAC9C,MAAM,OAAO,MAAM,YAAY,MAAM,WAA8B;IACnE,MAAM,QAAQ;GAChB;GACA,QAAQ,OAAO,YAAY,kBAAkB;IAC3C,MAAM,OAAO,OAAO,YAAY,aAAa;IAC7C,MAAM,QAAQ;GAChB;EAKU;EACV,oBAAoB,OAAO,gBAAgB;GACzC,qBAAqB,WAAW;GAChC,MAAM,QAAQ;EAChB;CACF;AACF;;AAGA,MAAM,yBAA0C;CAC9C,MAAM,YAAY,KAAA;CAClB,MAAM,YAAY,CAAC;CACnB,QAAQ,YAAY;EAClB,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;CACA,QAAQ,YAAY;EAClB,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;AACF;;;;;;AAOA,eAAsB,8BAA8B,SAAkE;CACpH,MAAM,EAAE,UAAU;CAClB,IAAI;CACJ,IAAI,gCAAgB,IAAI,IAAY;CACpC,MAAM,YAAY,eAAuB,UAAU,UAAU,MAAM,UAAU,MAAM,OAAO,UAAU;CACpG,MAAM,eAAe,UACnB,cAAc,IAAI,aAAa;EAAE,UAAU,MAAM;EAAU,IAAI,MAAM;EAAI,MAAM,aAAa,KAAK;CAAE,CAAC,CAAC;CAQvG,MAAM,SAAS,aAAa;EAC1B,aAAa;EACb,aAAA;GAPA,MAAM,OAAO,eAAe,UAAU,OAAO;GAE7C,OAAO,YAAY,KAAA;GACnB,QAAQ,YAAY,KAAA;EAIV;EACV,aAAa;CACf,CAAC;CAGD,MAAM,SAAS,QAAQ,kBAAwB,KAAA;CAC/C,IAAI,UAAyB,QAAQ,QAAQ;CAC7C,MAAM,OAAO,YAA2B;EACtC,MAAM,OAAO,MAAM,MAAM,SAAS,UAAU,OAAO;EACnD,IAAI,EAAE,eAAe,OAAO;EAC5B,MAAM,iBAAiB,UAAU,mBAAmB,CAAC;EACrD,WAAW;EACX,gBAAgB,IAAI,IAAI,KAAK,UAAU,IAAI,YAAY,CAAC;EACxD,KAAK,MAAM,YAAY,gBACrB,IAAI,CAAC,KAAK,gBAAgB,MAAM,eAAe,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,eAAe,SAAS,EAAE;EAClH,KAAK,MAAM,cAAc,KAAK,iBAAiB,OAAO,YAAY,YAAY,eAAe,UAAU,CAAC,CAAC;EACzG,MAAM,OAAO,QAAQ,EAAE,cAAc,MAAM,CAAC;CAC9C;CACA,MAAM,aAA4B;EAChC,UAAU,QAAQ,KAAK,MAAM,IAAI;EACjC,OAAO;CACT;CACA,MAAM,WAAW,YAA0B;EACzC,IAAI,YAAY,UAAU,SAAS,KAAU,CAAC,CAAC,MAAM,MAAM;CAC7D;CAEA,MAAM,eAAe,aAAiC;EACpD,MAAM,SAAS,SAAS,KAAK;EAC7B,OAAO;GACL,GAAG;GACH,MAAM;IACJ,GAAG,SAAS;IACZ,QAAQ;KACN,MAAM,QAAQ,QAAQ,SAAS;KAC/B,OAAO,YAAY;MACjB,MAAM,SAAS,SAAS,SAAS,EAAE;MACnC,OAAO,WAAW,KAAA,IACd,KAAA,IACA;OAAE,MAAM,OAAO;OAAM,GAAI,OAAO,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,OAAO,OAAO;MAAG;KAC7F;KACA,SAAS,OAAO,EAAE,YAAY,aAAa;MAEzC,IAAI,YAAY,QAAQ,KAAA,KAAa,WAAW,KAAA,GAC9C,OAAO,OAAO,QAAQ;OAAE,KAAK;OAAiB;OAAY;MAAO,CAAC;MACpE,MAAM,SAAS,MAAM,MAAM,YAAY,SAAS,EAAE;MAClD,QAAQ,OAAO,OAAO;MACtB,OAAO,OAAO;KAChB;IACF;GACF;GACA,eAAe,SAAS,KAAK,OAAO,WAAW;GAC/C,kBAAkB,SAAS,KAAK,OAAO,WAAW;EACpD;CACF;CACA,KAAK,MAAM,aAAa,QAAQ,aAAa,iBAAA,CAAkB,GAAG,OAAO,YAAY,YAAY,QAAQ,CAAC;CAC1G,MAAM,KAAK;CAEX,MAAM,kBAAkB,YAA4B;EAClD,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;CACA,MAAM,gBAAuB;EAC3B,MAAM,IAAI,YAAY,YAAY,iBAAiB;CACrD;CACA,OAAO;EACL,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,aAAa,OAAO,YAAY,KAAK,MAAM;EAC3C,WAAW,OAAO,UAAU,KAAK,MAAM;EACvC,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,iBAAiB,OAAO,gBAAgB,KAAK,MAAM;EACnD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,WAAW,OAAO,UAAU,KAAK,MAAM;EACvC,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,oBAAoB,OAAO,mBAAmB,KAAK,MAAM;EACzD,iBAAiB,OAAO,gBAAgB,KAAK,MAAM;EACnD,SAAS,OAAO,QAAQ,KAAK,MAAM;EACnC,QAAQ,OAAO,OAAO,KAAK,MAAM;EACjC,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,eAAe,OAAO,cAAc,KAAK,MAAM;EAC/C,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,SAAS,OAAO,mBAAgD;GAC9D,MAAM,SAAS,MAAM,MAAM,QAAQ,EAAE,cAAc,gBAAgB,gBAAgB,KAAK,CAAC;GACzF,IAAI,OAAO,YAAY,UAAU,SAAS,MAAM,KAAK;GACrD,OAAO;IACL,SAAS;IACT,QAAQ,IAAI,IACV,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,aAAa,CAC3D,YACA,IAAI,YAAY,gBAAgB,OAAO,CACzC,CAAC,CACH;GACF;EACF;EACA,4BAA4B,OAAO,UAAU,CAAC,CAAC,OAAO,WAAW;EACjE,gBAAgB,KAAA;EAChB,oBAAoB,eAAuB,SAAS,UAAU,MAAM,KAAA;EACpE,eAAe,eAAuB,SAAS,UAAU,CAAC,EAAE,SAAS;EACrE,sBAAsB,eAAuB,SAAS,UAAU,CAAC,EAAE,iBAAiB;EACpF,wBAAwB,eAAuB;GAC7C,MAAM,SAAS,SAAS,UAAU;GAClC,OAAO,WAAW,KAAA,IAAY,EAAE,YAAY,MAAM,IAAI;IAAE,YAAY;IAAM,QAAQ,OAAO;GAAO;EAClG;EACA,OAAO;EACP,QAAQ;EACR,kBAAkB;EAClB,qBAAqB;EACrB,iBAAiB,YAAY,CAAC;EAC9B,mCAAmC,KAAA;EACnC,mCAAmC,KAAA;EACnC,gCAAgC,CAAC;EACjC,+BAA+B;EAC/B,kBAAkB;EAClB,wBAAwB;EACxB,oBAAoB;EACpB,sBAAsB;EACtB,wBAAwB;CAC1B;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n source: stored.get(provider.id) === check.type ? 'stored' : check.source,\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":";;;;;;AA0BA,MAAM,oBAAoB;;AAG1B,MAAM,kBAA+B;CAAE,KAAK,YAAY,KAAA;CAAW,YAAY,YAAY;AAAM;AAEjG,MAAM,aAAa;CACjB,sBAAsB;CACtB,oBAAoB;CACpB,sBAAsB;AACxB;;AAGA,SAAgB,eAAe,YAAkD;CAC/E,OAAO,eAAe;EACpB,IAAI,WAAW;EACf,MAAM,WAAW;EACjB,SAAS,WAAW;EACpB,MAAM,EAAE,QAAQ,cAAc,GAAG,WAAW,KAAK,WAAW,CAAC,CAAC,EAAE;EAChE,QAAQ,WAAW,OAAO,KAAK,WAAW;GACxC,IAAI,MAAM;GACV,MAAM,MAAM,QAAQ,MAAM;GAC1B,KAAK,WAAW;GAChB,UAAU,WAAW;GACrB,SAAS,WAAW;GACpB,WAAW,MAAM,aAAa;GAC9B,OAAO,CAAC,GAAI,MAAM,SAAS,CAAC,MAAM,CAAE;GACpC,MAAM;IAAE,OAAO;IAAG,QAAQ;IAAG,WAAW;IAAG,YAAY;GAAE;GACzD,eAAe,MAAM,iBAAiB;GACtC,WAAW,MAAM,aAAa;EAChC,EAAE;EACF,KAAK,WAAW,WAAW,IAAI,CAAC;CAClC,CAAC;AACH;;AAGA,SAAS,UAAU,OAAuB;CACxC,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,KAAK;CACrB,QAAQ;EACN,OAAO;CACT;CACA,IAAI,IAAI,aAAa,MAAM,IAAI,aAAa,MAAM,IAAI,WAAW,MAAM,IAAI,SAAS,IAAI,OAAO;CAC/F,IAAI,WAAW;CACf,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO,IAAI;AACb;;AAGA,SAAgB,oBAAoB,OAA2C;CAC7E,OAAO,KAAK,MACV,KAAK,UAAU;EACb,GAAG;EACH,QAAQ,MAAM,OAAO,KAAK,EAAE,SAAS,UAAU,GAAG,aAAa;GAAE,GAAG;GAAO,SAAS,UAAU,MAAM,OAAO;EAAE,EAAE;CACjH,CAAC,CACH;AACF;AAEA,MAAM,gBAAgB,UACpB,GAAG,MAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,IAAI,MAAM;;;;;;AAOvD,SAAgB,wBAAwB,SAAsD;CAE5F,MAAM,SAAS,QAAQ,kBAAwB,KAAA;CAC/C,IAAI,UAAyB,QAAQ,QAAQ;CAC7C,MAAM,gBAA+B;EACnC,UAAU,QAAQ,KAAK,WAAW,SAAS;EAC3C,OAAO;CACT;CACA,MAAM,cAA+B;EACnC,OAAO,YAAY,cAAc,QAAQ,YAAY,KAAK,YAAY,SAAS;EAC/E,OAAO,cAAc,QAAQ,YAAY,KAAK,SAAS;EACvD,QAAQ,OAAO,YAAY,IAAI,cAAc;GAC3C,MAAM,SAAS,MAAM,QAAQ,YAAY,OAAO,YAAY,IAAI,SAAS;GACzE,QAAa,CAAC,CAAC,MAAM,MAAM;GAC3B,OAAO;EACT;EACA,QAAQ,OAAO,YAAY,cAAc;GACvC,MAAM,QAAQ,YAAY,OAAO,YAAY,SAAS;GACtD,QAAa,CAAC,CAAC,MAAM,MAAM;EAC7B;CACF;CACA,MAAM,SAAS,aAAa;EAAE;EAAa,aAAa,QAAQ;EAAa,aAAa;CAAgB,CAAC;CAC3G,KAAK,MAAM,aAAa,QAAQ,aAAa,iBAAA,CAAkB,GAAG,OAAO,YAAY,QAAQ;CAE7F,IAAI,kBAAyD,CAAC;CAC9D,MAAM,wBAAwB,gBAA6D;EACzF,KAAK,MAAM,YAAY,iBACrB,IAAI,CAAC,YAAY,MAAM,eAAe,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,eAAe,SAAS,EAAE;EACzG,KAAK,MAAM,cAAc,aAAa,OAAO,YAAY,eAAe,UAAU,CAAC;EACnF,kBAAkB;CACpB;CACA,qBAAqB,QAAQ,mBAAmB,CAAC,CAAC;CAGlD,IAAI,UAAU,KAAK,IAAI;CACvB,IAAI,WAAW;CACf,IAAI;CACJ,eAAe,YAA2B;EACxC,MAAM,YAAY,OAAO,aAAa;EACtC,MAAM,SAAS,IAAI,KAAK,MAAM,YAAY,KAAK,EAAA,CAAG,KAAK,EAAE,YAAY,WAAW,CAAC,YAAY,IAAI,CAAC,CAAC;EACnG,MAAM,SAAS,MAAM,QAAQ,IAC3B,UAAU,IAAI,OAAO,cAAc;GACjC;GACA,OAAO,MAAM,OAAO,UAAU,SAAS,EAAE,CAAC,CAAC,YAAY,KAAA,CAAS;EAClE,EAAE,CACJ;EACA,MAAM,WAA6C,CAAC;EACpD,KAAK,MAAM,YAAY,WAAW;GAChC,IAAI,SAAS,kBAAkB,KAAA,GAAW;GAC1C,MAAM,QAAQ,MAAM,QAAQ,YAAY,KAAK,SAAS,EAAE;GACxD,IAAI,UAAU,KAAA,GAAW,SAAS,SAAS,MAAM,oBAAoB,KAAK;EAC5E;EACA,MAAM,OAAO;GACX,WAAW,OAAO,SAAS,EAAE,UAAU,YACrC,UAAU,KAAA,IACN,CAAC,IACD,CACE;IACE,IAAI,SAAS;IACb,MAAM,MAAM;IACZ,QAAQ,OAAO,IAAI,SAAS,EAAE,MAAM,MAAM,OAAO,WAAW,MAAM;IAClE,cAAc,MAAM,SAAS,WAAW,SAAS,KAAK,OAAO,mBAAmB;GAClF,CACF,CACN;GACA,YAAY,MAAM,OAAO,gBAAgB,EAAA,CAAG,KAAK,WAAW;IAC1D,UAAU,MAAM;IAChB,IAAI,MAAM;IACV,MAAM,aAAa,KAAK;GAC1B,EAAE;GACF,QAAQ;GACR;EACF;EACA,MAAM,aAAa,KAAK,UAAU,IAAI;EACtC,IAAI,eAAe,YAAY,YAAY,KAAA,GAAW;EACtD,WAAW;EACX,WAAW;EACX,UAAU;GAAE;GAAS,GAAI,KAAK,MAAM,UAAU;EAA8C;CAC9F;CACA,MAAM,UAAU,YAA6C;EAC3D,MAAM;EACN,IAAI,YAAY,KAAA,GAAW,MAAM,QAAQ;EACzC,OAAO;CACT;CACA,QAAa,CAAC,CAAC,MAAM,MAAM;CAE3B,MAAM,YAAY,eAAuB,SAAS,UAAU,MAAM,UAAU,MAAM,OAAO,UAAU;CACnG,MAAM,UAAqC,OAAO,mBAAmB;EACnE,MAAM,SAAS,MAAM,OAAO,QAAQ,EAAE,cAAc,gBAAgB,gBAAgB,KAAK,CAAC;EAC1F,MAAM,QAAQ;EAEd,OAAO;GACL,UAAS,MAFY,QAAQ,EAAA,CAEX;GAClB,QAAQ,OAAO,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,WAAW,CAAC,YAAY,MAAM,OAAO,CAAC,CAAC;EACzG;CACF;CAmCA,OAAO;EACL,SAAA;GAjCA,YAAY;GACZ,aAAa,OAAO,eAAe;IACjC,MAAM,OAAO,MAAM,OAAO,QAAQ,UAAU;IAC5C,OAAO;KAAE,UAAU,MAAM,QAAQ,EAAA,CAAG;KAAS,GAAI,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK;IAAG;GACvF;GACA,UAAU,OAAO,iBAAiB;IAChC,MAAM,WAAW,MAAM,QAAQ;IAC/B,OAAO,iBAAiB,SAAS,UAAU,EAAE,SAAS,SAAS,QAAQ,IAAI;GAC7E;GACA;EAwBM;EACN,aAAA;GArBA,oBAAoB,OAAO,aAAa;GACxC,wBAAwB,eAAe;IACrC,MAAM,SAAS,SAAS,UAAU;IAClC,OAAO,WAAW,KAAA,IAAY,EAAE,YAAY,MAAM,IAAI;KAAE,YAAY;KAAM,QAAQ,OAAO;IAAO;GAClG;GACA,6BAA6B,SAAS,aAAa,CAAC,EAAA,CAAG,QAAQ,UAAU,MAAM,SAAS,MAAM;GAC9F,oBAAoB,eAAe,SAAS,UAAU,MAAM,KAAA;GAC5D,eAAe,eAAe,SAAS,UAAU,CAAC,EAAE,SAAS;GAC7D,UAAU,mBAAmB,QAAQ,cAAc;GACnD,OAAO,OAAO,YAAY,MAAM,gBAAgB;IAC9C,MAAM,OAAO,MAAM,YAAY,MAAM,WAA8B;IACnE,MAAM,QAAQ;GAChB;GACA,QAAQ,OAAO,YAAY,kBAAkB;IAC3C,MAAM,OAAO,OAAO,YAAY,aAAa;IAC7C,MAAM,QAAQ;GAChB;EAKU;EACV,oBAAoB,OAAO,gBAAgB;GACzC,qBAAqB,WAAW;GAChC,MAAM,QAAQ;EAChB;CACF;AACF;;AAGA,MAAM,yBAA0C;CAC9C,MAAM,YAAY,KAAA;CAClB,MAAM,YAAY,CAAC;CACnB,QAAQ,YAAY;EAClB,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;CACA,QAAQ,YAAY;EAClB,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;AACF;;;;;;AAOA,eAAsB,8BAA8B,SAAkE;CACpH,MAAM,EAAE,UAAU;CAClB,IAAI;CACJ,IAAI,gCAAgB,IAAI,IAAY;CACpC,MAAM,YAAY,eAAuB,UAAU,UAAU,MAAM,UAAU,MAAM,OAAO,UAAU;CACpG,MAAM,eAAe,UACnB,cAAc,IAAI,aAAa;EAAE,UAAU,MAAM;EAAU,IAAI,MAAM;EAAI,MAAM,aAAa,KAAK;CAAE,CAAC,CAAC;CAQvG,MAAM,SAAS,aAAa;EAC1B,aAAa;EACb,aAAA;GAPA,MAAM,OAAO,eAAe,UAAU,OAAO;GAE7C,OAAO,YAAY,KAAA;GACnB,QAAQ,YAAY,KAAA;EAIV;EACV,aAAa;CACf,CAAC;CAGD,MAAM,SAAS,QAAQ,kBAAwB,KAAA;CAC/C,IAAI,UAAyB,QAAQ,QAAQ;CAC7C,MAAM,OAAO,YAA2B;EACtC,MAAM,OAAO,MAAM,MAAM,SAAS,UAAU,OAAO;EACnD,IAAI,EAAE,eAAe,OAAO;EAC5B,MAAM,iBAAiB,UAAU,mBAAmB,CAAC;EACrD,WAAW;EACX,gBAAgB,IAAI,IAAI,KAAK,UAAU,IAAI,YAAY,CAAC;EACxD,KAAK,MAAM,YAAY,gBACrB,IAAI,CAAC,KAAK,gBAAgB,MAAM,eAAe,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,eAAe,SAAS,EAAE;EAClH,KAAK,MAAM,cAAc,KAAK,iBAAiB,OAAO,YAAY,YAAY,eAAe,UAAU,CAAC,CAAC;EACzG,MAAM,OAAO,QAAQ,EAAE,cAAc,MAAM,CAAC;CAC9C;CACA,MAAM,aAA4B;EAChC,UAAU,QAAQ,KAAK,MAAM,IAAI;EACjC,OAAO;CACT;CACA,MAAM,WAAW,YAA0B;EACzC,IAAI,YAAY,UAAU,SAAS,KAAU,CAAC,CAAC,MAAM,MAAM;CAC7D;CAEA,MAAM,eAAe,aAAiC;EACpD,MAAM,SAAS,SAAS,KAAK;EAC7B,OAAO;GACL,GAAG;GACH,MAAM;IACJ,GAAG,SAAS;IACZ,QAAQ;KACN,MAAM,QAAQ,QAAQ,SAAS;KAC/B,OAAO,YAAY;MACjB,MAAM,SAAS,SAAS,SAAS,EAAE;MACnC,OAAO,WAAW,KAAA,IACd,KAAA,IACA;OAAE,MAAM,OAAO;OAAM,GAAI,OAAO,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,OAAO,OAAO;MAAG;KAC7F;KACA,SAAS,OAAO,EAAE,YAAY,aAAa;MAEzC,IAAI,YAAY,QAAQ,KAAA,KAAa,WAAW,KAAA,GAC9C,OAAO,OAAO,QAAQ;OAAE,KAAK;OAAiB;OAAY;MAAO,CAAC;MACpE,MAAM,SAAS,MAAM,MAAM,YAAY,SAAS,EAAE;MAClD,QAAQ,OAAO,OAAO;MACtB,OAAO,OAAO;KAChB;IACF;GACF;GACA,eAAe,SAAS,KAAK,OAAO,WAAW;GAC/C,kBAAkB,SAAS,KAAK,OAAO,WAAW;EACpD;CACF;CACA,KAAK,MAAM,aAAa,QAAQ,aAAa,iBAAA,CAAkB,GAAG,OAAO,YAAY,YAAY,QAAQ,CAAC;CAC1G,MAAM,KAAK;CAEX,MAAM,kBAAkB,YAA4B;EAClD,MAAM,IAAI,YAAY,QAAQ,iBAAiB;CACjD;CACA,MAAM,gBAAuB;EAC3B,MAAM,IAAI,YAAY,YAAY,iBAAiB;CACrD;CACA,OAAO;EACL,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,aAAa,OAAO,YAAY,KAAK,MAAM;EAC3C,WAAW,OAAO,UAAU,KAAK,MAAM;EACvC,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,iBAAiB,OAAO,gBAAgB,KAAK,MAAM;EACnD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,WAAW,OAAO,UAAU,KAAK,MAAM;EACvC,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,oBAAoB,OAAO,mBAAmB,KAAK,MAAM;EACzD,iBAAiB,OAAO,gBAAgB,KAAK,MAAM;EACnD,SAAS,OAAO,QAAQ,KAAK,MAAM;EACnC,QAAQ,OAAO,OAAO,KAAK,MAAM;EACjC,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,cAAc,OAAO,aAAa,KAAK,MAAM;EAC7C,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,eAAe,OAAO,cAAc,KAAK,MAAM;EAC/C,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,gBAAgB,OAAO,eAAe,KAAK,MAAM;EACjD,UAAU,OAAO,SAAS,KAAK,MAAM;EACrC,SAAS,OAAO,mBAAgD;GAC9D,MAAM,SAAS,MAAM,MAAM,QAAQ,EAAE,cAAc,gBAAgB,gBAAgB,KAAK,CAAC;GACzF,IAAI,OAAO,YAAY,UAAU,SAAS,MAAM,KAAK;GACrD,OAAO;IACL,SAAS;IACT,QAAQ,IAAI,IACV,OAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,KAAK,CAAC,YAAY,aAAa,CAC3D,YACA,IAAI,YAAY,gBAAgB,OAAO,CACzC,CAAC,CACH;GACF;EACF;EACA,4BAA4B,OAAO,UAAU,CAAC,CAAC,OAAO,WAAW;EACjE,gBAAgB,KAAA;EAChB,oBAAoB,eAAuB,SAAS,UAAU,MAAM,KAAA;EACpE,eAAe,eAAuB,SAAS,UAAU,CAAC,EAAE,SAAS;EACrE,sBAAsB,eAAuB,SAAS,UAAU,CAAC,EAAE,iBAAiB;EACpF,wBAAwB,eAAuB;GAC7C,MAAM,SAAS,SAAS,UAAU;GAClC,OAAO,WAAW,KAAA,IAAY,EAAE,YAAY,MAAM,IAAI;IAAE,YAAY;IAAM,QAAQ,OAAO;GAAO;EAClG;EACA,OAAO;EACP,QAAQ;EACR,kBAAkB;EAClB,qBAAqB;EACrB,iBAAiB,YAAY,CAAC;EAC9B,mCAAmC,KAAA;EACnC,mCAAmC,KAAA;EACnC,gCAAgC,CAAC;EACjC,+BAA+B;EAC/B,kBAAkB;EAClB,wBAAwB;EACxB,oBAAoB;EACpB,sBAAsB;EACtB,wBAAwB;CAC1B;AACF"}
@@ -31,7 +31,7 @@ function summarize(runtime, provider) {
31
31
  const status = runtime.getProviderAuthStatus(provider.id);
32
32
  if (status.configured) summary.authenticated = {
33
33
  type: runtime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
34
- source: status.label ?? status.source
34
+ source: status.source === "stored" ? "stored" : status.label ?? status.source
35
35
  };
36
36
  return summary;
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["createLoginFlow"],"sources":["../../../../src/services/providerAuth/index.ts"],"sourcesContent":["import type {\n AuthMethodType,\n AuthRuntime,\n AuthRuntimeProvider,\n LoginFlowSnapshot,\n ProviderAuthMethod,\n ProviderAuthSummary,\n} from '../../types/server/auth';\nimport { CHATGPT_AUTH_PROVIDER_ID } from '../chatgptAuth';\nimport { createLoginFlow, type LoginFlow } from '../loginFlow';\ninterface SettingsModel {\n value: string;\n label: string;\n group: string;\n}\n\n/** Matches the timeout Pi's own /logout gives the credential store. */\nconst LOGOUT_TIMEOUT_MS = 15_000;\n\nexport type StartLoginOutcome =\n | { ok: true; flow: LoginFlowSnapshot }\n | { ok: false; code: 'unknown_provider' | 'unsupported_method' | 'busy'; error: string };\n\nexport type LogoutOutcome = { ok: true } | { ok: false; code: 'unknown_provider' | 'runtime'; error: string };\n\nexport type AnswerOutcome = 'answered' | 'unknown_flow' | 'not_waiting';\n\n/** Provider credentials as the settings page sees them, backed by one Pi runtime. */\nexport interface ProviderAuth {\n /** Every provider Pi knows, with its current auth state; re-read from disk each call. */\n listProviders(): Promise<ProviderAuthSummary[]>;\n /**\n * Starts a login. `remote` means the browser driving it is on another\n * machine, which decides whether a redirect-free method is chosen for the\n * user; it comes from the accepted socket, never from the request body.\n */\n startLogin(providerId: string, type: AuthMethodType, remote?: boolean): Promise<StartLoginOutcome>;\n getLogin(flowId: string): LoginFlowSnapshot | undefined;\n answerLogin(flowId: string, promptId: string, value: string): AnswerOutcome;\n /** Cancels the flow and returns how it stands, or undefined for an unknown flow. */\n cancelLogin(flowId: string): LoginFlowSnapshot | undefined;\n logout(providerId: string): Promise<LogoutOutcome>;\n /**\n * Every model the machine can actually use, grouped by provider. Read from\n * the same runtime the providers page uses, so the settings model pickers\n * work with no session running.\n */\n listModels(): Promise<readonly SettingsModel[]>;\n /** Abandons every running flow; the hub is shutting down. */\n close(): void;\n}\n\nexport interface ProviderAuthOptions {\n runtime: () => Promise<AuthRuntime>;\n onNotice?: (message: string) => void;\n onChange?: () => void;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The methods a page can start, in the order the TUI's selector lists them. */\nfunction methodsOf(provider: AuthRuntimeProvider): ProviderAuthMethod[] {\n const methods: ProviderAuthMethod[] = [];\n const { oauth, apiKey } = provider.auth;\n if (oauth) methods.push({ type: 'oauth', label: oauth.loginLabel ?? oauth.name });\n if (apiKey?.login) methods.push({ type: 'api_key', label: apiKey.name });\n return methods;\n}\n\n/** Mirrors the status the TUI's login selector shows beside each provider. */\nfunction summarize(runtime: AuthRuntime, provider: AuthRuntimeProvider): ProviderAuthSummary {\n const summary: ProviderAuthSummary = { id: provider.id, name: provider.name, methods: methodsOf(provider) };\n if (provider.id === CHATGPT_AUTH_PROVIDER_ID) summary.kind = 'po-feature';\n const status = runtime.getProviderAuthStatus(provider.id);\n if (status.configured) {\n summary.authenticated = {\n type: runtime.isUsingOAuth(provider.id) ? 'oauth' : 'api_key',\n source: status.label ?? status.source,\n };\n }\n return summary;\n}\n\nexport function createProviderAuth(options: ProviderAuthOptions): ProviderAuth {\n const notice = options.onNotice ?? ((): void => {});\n const load = options.runtime;\n const flows = new Map<string, LoginFlow>();\n let loading: Promise<AuthRuntime> | undefined;\n\n // A failed load is forgotten so the next request tries again rather than\n // pinning the page on a startup error.\n const runtime = (): Promise<AuthRuntime> => {\n loading ??= load().catch((error: unknown) => {\n loading = undefined;\n throw error;\n });\n return loading;\n };\n\n const findProvider = (current: AuthRuntime, providerId: string): AuthRuntimeProvider | undefined =>\n current.getProviders().find((provider) => provider.id === providerId);\n\n const runningFor = (providerId: string): LoginFlow | undefined =>\n [...flows.values()].find((flow) => flow.providerId === providerId && flow.snapshot().status === 'running');\n\n // Another client may have stored a credential since the last look (a TUI\n // /login, a hand-edited auth.json), and a provider registered by a Pi\n // extension after the runtime was created is missing from the cached auth\n // state until a refresh. The stale snapshot still lists on a failed refresh,\n // which the notice explains.\n const refreshed = async (): Promise<AuthRuntime> => {\n const current = await runtime();\n try {\n await current.refresh({ allowNetwork: false });\n } catch (error) {\n notice(`provider auth refresh failed: ${describe(error)}`);\n }\n return current;\n };\n\n return {\n async listProviders() {\n const current = await refreshed();\n return current\n .getProviders()\n .map((provider) => summarize(current, provider))\n .sort((left, right) => left.name.localeCompare(right.name));\n },\n\n async startLogin(providerId, type, remote) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n if (!methodsOf(provider).some((method) => method.type === type)) {\n return { ok: false, code: 'unsupported_method', error: `${provider.name} has no ${type} login.` };\n }\n if (runningFor(providerId)) {\n return { ok: false, code: 'busy', error: `A login for ${provider.name} is already in progress.` };\n }\n // Finished flows live until the next start: long enough for the page to\n // read how they ended, short enough that the map never grows.\n for (const [id, flow] of flows) if (flow.snapshot().status !== 'running') flows.delete(id);\n\n const flow = createLoginFlow({\n id: crypto.randomUUID(),\n providerId,\n providerName: provider.name,\n type,\n ...(options.onChange ? { onChange: options.onChange } : {}),\n ...(remote === true ? { remote: true } : {}),\n });\n flows.set(flow.id, flow);\n options.onChange?.();\n current.login(providerId, type, flow.interaction).then(\n () => {\n flow.settle({ ok: true });\n notice(`signed in to ${provider.name} (${type})`);\n },\n (error: unknown) => {\n flow.settle({ ok: false, error: describe(error) });\n },\n );\n return { ok: true, flow: flow.snapshot() };\n },\n\n getLogin(flowId) {\n return flows.get(flowId)?.snapshot();\n },\n\n answerLogin(flowId, promptId, value) {\n const flow = flows.get(flowId);\n if (!flow) return 'unknown_flow';\n return flow.answer(promptId, value) ? 'answered' : 'not_waiting';\n },\n\n cancelLogin(flowId) {\n const flow = flows.get(flowId);\n if (!flow) return undefined;\n flow.cancel();\n return flow.snapshot();\n },\n\n async logout(providerId) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n try {\n await current.logout(providerId, { signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS) });\n } catch (error) {\n return { ok: false, code: 'runtime', error: describe(error) };\n }\n options.onChange?.();\n notice(`signed out of ${provider.name}`);\n return { ok: true };\n },\n\n async listModels() {\n const current = await refreshed();\n const providers = new Map(current.getProviders().map((provider) => [provider.id, provider.name]));\n return current\n .getAvailableSnapshot()\n .filter((model) => current.hasConfiguredAuth(model.provider))\n .map((model) => ({\n value: `${model.provider}/${model.id}`,\n label: model.id,\n group: providers.get(model.provider) ?? model.provider,\n }));\n },\n\n close() {\n for (const flow of flows.values()) flow.cancel();\n flows.clear();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["createLoginFlow"],"sources":["../../../../src/services/providerAuth/index.ts"],"sourcesContent":["import type {\n AuthMethodType,\n AuthRuntime,\n AuthRuntimeProvider,\n LoginFlowSnapshot,\n ProviderAuthMethod,\n ProviderAuthSummary,\n} from '../../types/server/auth';\nimport { CHATGPT_AUTH_PROVIDER_ID } from '../chatgptAuth';\nimport { createLoginFlow, type LoginFlow } from '../loginFlow';\ninterface SettingsModel {\n value: string;\n label: string;\n group: string;\n}\n\n/** Matches the timeout Pi's own /logout gives the credential store. */\nconst LOGOUT_TIMEOUT_MS = 15_000;\n\nexport type StartLoginOutcome =\n | { ok: true; flow: LoginFlowSnapshot }\n | { ok: false; code: 'unknown_provider' | 'unsupported_method' | 'busy'; error: string };\n\nexport type LogoutOutcome = { ok: true } | { ok: false; code: 'unknown_provider' | 'runtime'; error: string };\n\nexport type AnswerOutcome = 'answered' | 'unknown_flow' | 'not_waiting';\n\n/** Provider credentials as the settings page sees them, backed by one Pi runtime. */\nexport interface ProviderAuth {\n /** Every provider Pi knows, with its current auth state; re-read from disk each call. */\n listProviders(): Promise<ProviderAuthSummary[]>;\n /**\n * Starts a login. `remote` means the browser driving it is on another\n * machine, which decides whether a redirect-free method is chosen for the\n * user; it comes from the accepted socket, never from the request body.\n */\n startLogin(providerId: string, type: AuthMethodType, remote?: boolean): Promise<StartLoginOutcome>;\n getLogin(flowId: string): LoginFlowSnapshot | undefined;\n answerLogin(flowId: string, promptId: string, value: string): AnswerOutcome;\n /** Cancels the flow and returns how it stands, or undefined for an unknown flow. */\n cancelLogin(flowId: string): LoginFlowSnapshot | undefined;\n logout(providerId: string): Promise<LogoutOutcome>;\n /**\n * Every model the machine can actually use, grouped by provider. Read from\n * the same runtime the providers page uses, so the settings model pickers\n * work with no session running.\n */\n listModels(): Promise<readonly SettingsModel[]>;\n /** Abandons every running flow; the hub is shutting down. */\n close(): void;\n}\n\nexport interface ProviderAuthOptions {\n runtime: () => Promise<AuthRuntime>;\n onNotice?: (message: string) => void;\n onChange?: () => void;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The methods a page can start, in the order the TUI's selector lists them. */\nfunction methodsOf(provider: AuthRuntimeProvider): ProviderAuthMethod[] {\n const methods: ProviderAuthMethod[] = [];\n const { oauth, apiKey } = provider.auth;\n if (oauth) methods.push({ type: 'oauth', label: oauth.loginLabel ?? oauth.name });\n if (apiKey?.login) methods.push({ type: 'api_key', label: apiKey.name });\n return methods;\n}\n\n/** Mirrors the status the TUI's login selector shows beside each provider. */\nfunction summarize(runtime: AuthRuntime, provider: AuthRuntimeProvider): ProviderAuthSummary {\n const summary: ProviderAuthSummary = { id: provider.id, name: provider.name, methods: methodsOf(provider) };\n if (provider.id === CHATGPT_AUTH_PROVIDER_ID) summary.kind = 'po-feature';\n const status = runtime.getProviderAuthStatus(provider.id);\n if (status.configured) {\n summary.authenticated = {\n type: runtime.isUsingOAuth(provider.id) ? 'oauth' : 'api_key',\n source: status.source === 'stored' ? 'stored' : (status.label ?? status.source),\n };\n }\n return summary;\n}\n\nexport function createProviderAuth(options: ProviderAuthOptions): ProviderAuth {\n const notice = options.onNotice ?? ((): void => {});\n const load = options.runtime;\n const flows = new Map<string, LoginFlow>();\n let loading: Promise<AuthRuntime> | undefined;\n\n // A failed load is forgotten so the next request tries again rather than\n // pinning the page on a startup error.\n const runtime = (): Promise<AuthRuntime> => {\n loading ??= load().catch((error: unknown) => {\n loading = undefined;\n throw error;\n });\n return loading;\n };\n\n const findProvider = (current: AuthRuntime, providerId: string): AuthRuntimeProvider | undefined =>\n current.getProviders().find((provider) => provider.id === providerId);\n\n const runningFor = (providerId: string): LoginFlow | undefined =>\n [...flows.values()].find((flow) => flow.providerId === providerId && flow.snapshot().status === 'running');\n\n // Another client may have stored a credential since the last look (a TUI\n // /login, a hand-edited auth.json), and a provider registered by a Pi\n // extension after the runtime was created is missing from the cached auth\n // state until a refresh. The stale snapshot still lists on a failed refresh,\n // which the notice explains.\n const refreshed = async (): Promise<AuthRuntime> => {\n const current = await runtime();\n try {\n await current.refresh({ allowNetwork: false });\n } catch (error) {\n notice(`provider auth refresh failed: ${describe(error)}`);\n }\n return current;\n };\n\n return {\n async listProviders() {\n const current = await refreshed();\n return current\n .getProviders()\n .map((provider) => summarize(current, provider))\n .sort((left, right) => left.name.localeCompare(right.name));\n },\n\n async startLogin(providerId, type, remote) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n if (!methodsOf(provider).some((method) => method.type === type)) {\n return { ok: false, code: 'unsupported_method', error: `${provider.name} has no ${type} login.` };\n }\n if (runningFor(providerId)) {\n return { ok: false, code: 'busy', error: `A login for ${provider.name} is already in progress.` };\n }\n // Finished flows live until the next start: long enough for the page to\n // read how they ended, short enough that the map never grows.\n for (const [id, flow] of flows) if (flow.snapshot().status !== 'running') flows.delete(id);\n\n const flow = createLoginFlow({\n id: crypto.randomUUID(),\n providerId,\n providerName: provider.name,\n type,\n ...(options.onChange ? { onChange: options.onChange } : {}),\n ...(remote === true ? { remote: true } : {}),\n });\n flows.set(flow.id, flow);\n options.onChange?.();\n current.login(providerId, type, flow.interaction).then(\n () => {\n flow.settle({ ok: true });\n notice(`signed in to ${provider.name} (${type})`);\n },\n (error: unknown) => {\n flow.settle({ ok: false, error: describe(error) });\n },\n );\n return { ok: true, flow: flow.snapshot() };\n },\n\n getLogin(flowId) {\n return flows.get(flowId)?.snapshot();\n },\n\n answerLogin(flowId, promptId, value) {\n const flow = flows.get(flowId);\n if (!flow) return 'unknown_flow';\n return flow.answer(promptId, value) ? 'answered' : 'not_waiting';\n },\n\n cancelLogin(flowId) {\n const flow = flows.get(flowId);\n if (!flow) return undefined;\n flow.cancel();\n return flow.snapshot();\n },\n\n async logout(providerId) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n try {\n await current.logout(providerId, { signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS) });\n } catch (error) {\n return { ok: false, code: 'runtime', error: describe(error) };\n }\n options.onChange?.();\n notice(`signed out of ${provider.name}`);\n return { ok: true };\n },\n\n async listModels() {\n const current = await refreshed();\n const providers = new Map(current.getProviders().map((provider) => [provider.id, provider.name]));\n return current\n .getAvailableSnapshot()\n .filter((model) => current.hasConfiguredAuth(model.provider))\n .map((model) => ({\n value: `${model.provider}/${model.id}`,\n label: model.id,\n group: providers.get(model.provider) ?? model.provider,\n }));\n },\n\n close() {\n for (const flow of flows.values()) flow.cancel();\n flows.clear();\n },\n };\n}\n"],"mappings":"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"}
@@ -31,7 +31,7 @@ function summarize(runtime, provider) {
31
31
  const status = runtime.getProviderAuthStatus(provider.id);
32
32
  if (status.configured) summary.authenticated = {
33
33
  type: runtime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
34
- source: status.label ?? status.source
34
+ source: status.source === "stored" ? "stored" : status.label ?? status.source
35
35
  };
36
36
  return summary;
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/providerAuth/index.ts"],"sourcesContent":["import type {\n AuthMethodType,\n AuthRuntime,\n AuthRuntimeProvider,\n LoginFlowSnapshot,\n ProviderAuthMethod,\n ProviderAuthSummary,\n} from '../../types/server/auth';\nimport { CHATGPT_AUTH_PROVIDER_ID } from '../chatgptAuth';\nimport { createLoginFlow, type LoginFlow } from '../loginFlow';\ninterface SettingsModel {\n value: string;\n label: string;\n group: string;\n}\n\n/** Matches the timeout Pi's own /logout gives the credential store. */\nconst LOGOUT_TIMEOUT_MS = 15_000;\n\nexport type StartLoginOutcome =\n | { ok: true; flow: LoginFlowSnapshot }\n | { ok: false; code: 'unknown_provider' | 'unsupported_method' | 'busy'; error: string };\n\nexport type LogoutOutcome = { ok: true } | { ok: false; code: 'unknown_provider' | 'runtime'; error: string };\n\nexport type AnswerOutcome = 'answered' | 'unknown_flow' | 'not_waiting';\n\n/** Provider credentials as the settings page sees them, backed by one Pi runtime. */\nexport interface ProviderAuth {\n /** Every provider Pi knows, with its current auth state; re-read from disk each call. */\n listProviders(): Promise<ProviderAuthSummary[]>;\n /**\n * Starts a login. `remote` means the browser driving it is on another\n * machine, which decides whether a redirect-free method is chosen for the\n * user; it comes from the accepted socket, never from the request body.\n */\n startLogin(providerId: string, type: AuthMethodType, remote?: boolean): Promise<StartLoginOutcome>;\n getLogin(flowId: string): LoginFlowSnapshot | undefined;\n answerLogin(flowId: string, promptId: string, value: string): AnswerOutcome;\n /** Cancels the flow and returns how it stands, or undefined for an unknown flow. */\n cancelLogin(flowId: string): LoginFlowSnapshot | undefined;\n logout(providerId: string): Promise<LogoutOutcome>;\n /**\n * Every model the machine can actually use, grouped by provider. Read from\n * the same runtime the providers page uses, so the settings model pickers\n * work with no session running.\n */\n listModels(): Promise<readonly SettingsModel[]>;\n /** Abandons every running flow; the hub is shutting down. */\n close(): void;\n}\n\nexport interface ProviderAuthOptions {\n runtime: () => Promise<AuthRuntime>;\n onNotice?: (message: string) => void;\n onChange?: () => void;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The methods a page can start, in the order the TUI's selector lists them. */\nfunction methodsOf(provider: AuthRuntimeProvider): ProviderAuthMethod[] {\n const methods: ProviderAuthMethod[] = [];\n const { oauth, apiKey } = provider.auth;\n if (oauth) methods.push({ type: 'oauth', label: oauth.loginLabel ?? oauth.name });\n if (apiKey?.login) methods.push({ type: 'api_key', label: apiKey.name });\n return methods;\n}\n\n/** Mirrors the status the TUI's login selector shows beside each provider. */\nfunction summarize(runtime: AuthRuntime, provider: AuthRuntimeProvider): ProviderAuthSummary {\n const summary: ProviderAuthSummary = { id: provider.id, name: provider.name, methods: methodsOf(provider) };\n if (provider.id === CHATGPT_AUTH_PROVIDER_ID) summary.kind = 'po-feature';\n const status = runtime.getProviderAuthStatus(provider.id);\n if (status.configured) {\n summary.authenticated = {\n type: runtime.isUsingOAuth(provider.id) ? 'oauth' : 'api_key',\n source: status.label ?? status.source,\n };\n }\n return summary;\n}\n\nexport function createProviderAuth(options: ProviderAuthOptions): ProviderAuth {\n const notice = options.onNotice ?? ((): void => {});\n const load = options.runtime;\n const flows = new Map<string, LoginFlow>();\n let loading: Promise<AuthRuntime> | undefined;\n\n // A failed load is forgotten so the next request tries again rather than\n // pinning the page on a startup error.\n const runtime = (): Promise<AuthRuntime> => {\n loading ??= load().catch((error: unknown) => {\n loading = undefined;\n throw error;\n });\n return loading;\n };\n\n const findProvider = (current: AuthRuntime, providerId: string): AuthRuntimeProvider | undefined =>\n current.getProviders().find((provider) => provider.id === providerId);\n\n const runningFor = (providerId: string): LoginFlow | undefined =>\n [...flows.values()].find((flow) => flow.providerId === providerId && flow.snapshot().status === 'running');\n\n // Another client may have stored a credential since the last look (a TUI\n // /login, a hand-edited auth.json), and a provider registered by a Pi\n // extension after the runtime was created is missing from the cached auth\n // state until a refresh. The stale snapshot still lists on a failed refresh,\n // which the notice explains.\n const refreshed = async (): Promise<AuthRuntime> => {\n const current = await runtime();\n try {\n await current.refresh({ allowNetwork: false });\n } catch (error) {\n notice(`provider auth refresh failed: ${describe(error)}`);\n }\n return current;\n };\n\n return {\n async listProviders() {\n const current = await refreshed();\n return current\n .getProviders()\n .map((provider) => summarize(current, provider))\n .sort((left, right) => left.name.localeCompare(right.name));\n },\n\n async startLogin(providerId, type, remote) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n if (!methodsOf(provider).some((method) => method.type === type)) {\n return { ok: false, code: 'unsupported_method', error: `${provider.name} has no ${type} login.` };\n }\n if (runningFor(providerId)) {\n return { ok: false, code: 'busy', error: `A login for ${provider.name} is already in progress.` };\n }\n // Finished flows live until the next start: long enough for the page to\n // read how they ended, short enough that the map never grows.\n for (const [id, flow] of flows) if (flow.snapshot().status !== 'running') flows.delete(id);\n\n const flow = createLoginFlow({\n id: crypto.randomUUID(),\n providerId,\n providerName: provider.name,\n type,\n ...(options.onChange ? { onChange: options.onChange } : {}),\n ...(remote === true ? { remote: true } : {}),\n });\n flows.set(flow.id, flow);\n options.onChange?.();\n current.login(providerId, type, flow.interaction).then(\n () => {\n flow.settle({ ok: true });\n notice(`signed in to ${provider.name} (${type})`);\n },\n (error: unknown) => {\n flow.settle({ ok: false, error: describe(error) });\n },\n );\n return { ok: true, flow: flow.snapshot() };\n },\n\n getLogin(flowId) {\n return flows.get(flowId)?.snapshot();\n },\n\n answerLogin(flowId, promptId, value) {\n const flow = flows.get(flowId);\n if (!flow) return 'unknown_flow';\n return flow.answer(promptId, value) ? 'answered' : 'not_waiting';\n },\n\n cancelLogin(flowId) {\n const flow = flows.get(flowId);\n if (!flow) return undefined;\n flow.cancel();\n return flow.snapshot();\n },\n\n async logout(providerId) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n try {\n await current.logout(providerId, { signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS) });\n } catch (error) {\n return { ok: false, code: 'runtime', error: describe(error) };\n }\n options.onChange?.();\n notice(`signed out of ${provider.name}`);\n return { ok: true };\n },\n\n async listModels() {\n const current = await refreshed();\n const providers = new Map(current.getProviders().map((provider) => [provider.id, provider.name]));\n return current\n .getAvailableSnapshot()\n .filter((model) => current.hasConfiguredAuth(model.provider))\n .map((model) => ({\n value: `${model.provider}/${model.id}`,\n label: model.id,\n group: providers.get(model.provider) ?? model.provider,\n }));\n },\n\n close() {\n for (const flow of flows.values()) flow.cancel();\n flows.clear();\n },\n };\n}\n"],"mappings":";;;;AAiBA,MAAM,oBAAoB;AAyC1B,SAAS,SAAS,OAAwB;CACxC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;AAGA,SAAS,UAAU,UAAqD;CACtE,MAAM,UAAgC,CAAC;CACvC,MAAM,EAAE,OAAO,WAAW,SAAS;CACnC,IAAI,OAAO,QAAQ,KAAK;EAAE,MAAM;EAAS,OAAO,MAAM,cAAc,MAAM;CAAK,CAAC;CAChF,IAAI,QAAQ,OAAO,QAAQ,KAAK;EAAE,MAAM;EAAW,OAAO,OAAO;CAAK,CAAC;CACvE,OAAO;AACT;;AAGA,SAAS,UAAU,SAAsB,UAAoD;CAC3F,MAAM,UAA+B;EAAE,IAAI,SAAS;EAAI,MAAM,SAAS;EAAM,SAAS,UAAU,QAAQ;CAAE;CAC1G,IAAI,SAAS,OAAA,cAAiC,QAAQ,OAAO;CAC7D,MAAM,SAAS,QAAQ,sBAAsB,SAAS,EAAE;CACxD,IAAI,OAAO,YACT,QAAQ,gBAAgB;EACtB,MAAM,QAAQ,aAAa,SAAS,EAAE,IAAI,UAAU;EACpD,QAAQ,OAAO,SAAS,OAAO;CACjC;CAEF,OAAO;AACT;AAEA,SAAgB,mBAAmB,SAA4C;CAC7E,MAAM,SAAS,QAAQ,mBAAyB,CAAC;CACjD,MAAM,OAAO,QAAQ;CACrB,MAAM,wBAAQ,IAAI,IAAuB;CACzC,IAAI;CAIJ,MAAM,gBAAsC;EAC1C,YAAY,KAAK,CAAC,CAAC,OAAO,UAAmB;GAC3C,UAAU,KAAA;GACV,MAAM;EACR,CAAC;EACD,OAAO;CACT;CAEA,MAAM,gBAAgB,SAAsB,eAC1C,QAAQ,aAAa,CAAC,CAAC,MAAM,aAAa,SAAS,OAAO,UAAU;CAEtE,MAAM,cAAc,eAClB,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,MAAM,SAAS,KAAK,eAAe,cAAc,KAAK,SAAS,CAAC,CAAC,WAAW,SAAS;CAO3G,MAAM,YAAY,YAAkC;EAClD,MAAM,UAAU,MAAM,QAAQ;EAC9B,IAAI;GACF,MAAM,QAAQ,QAAQ,EAAE,cAAc,MAAM,CAAC;EAC/C,SAAS,OAAO;GACd,OAAO,iCAAiC,SAAS,KAAK,GAAG;EAC3D;EACA,OAAO;CACT;CAEA,OAAO;EACL,MAAM,gBAAgB;GACpB,MAAM,UAAU,MAAM,UAAU;GAChC,OAAO,QACJ,aAAa,CAAC,CACd,KAAK,aAAa,UAAU,SAAS,QAAQ,CAAC,CAAC,CAC/C,MAAM,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;EAC9D;EAEA,MAAM,WAAW,YAAY,MAAM,QAAQ;GACzC,MAAM,UAAU,MAAM,QAAQ;GAC9B,MAAM,WAAW,aAAa,SAAS,UAAU;GACjD,IAAI,CAAC,UAAU,OAAO;IAAE,IAAI;IAAO,MAAM;IAAoB,OAAO,qBAAqB;GAAa;GACtG,IAAI,CAAC,UAAU,QAAQ,CAAC,CAAC,MAAM,WAAW,OAAO,SAAS,IAAI,GAC5D,OAAO;IAAE,IAAI;IAAO,MAAM;IAAsB,OAAO,GAAG,SAAS,KAAK,UAAU,KAAK;GAAS;GAElG,IAAI,WAAW,UAAU,GACvB,OAAO;IAAE,IAAI;IAAO,MAAM;IAAQ,OAAO,eAAe,SAAS,KAAK;GAA0B;GAIlG,KAAK,MAAM,CAAC,IAAI,SAAS,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,WAAW,WAAW,MAAM,OAAO,EAAE;GAEzF,MAAM,OAAO,gBAAgB;IAC3B,IAAI,OAAO,WAAW;IACtB;IACA,cAAc,SAAS;IACvB;IACA,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;IACzD,GAAI,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;GAC5C,CAAC;GACD,MAAM,IAAI,KAAK,IAAI,IAAI;GACvB,QAAQ,WAAW;GACnB,QAAQ,MAAM,YAAY,MAAM,KAAK,WAAW,CAAC,CAAC,WAC1C;IACJ,KAAK,OAAO,EAAE,IAAI,KAAK,CAAC;IACxB,OAAO,gBAAgB,SAAS,KAAK,IAAI,KAAK,EAAE;GAClD,IACC,UAAmB;IAClB,KAAK,OAAO;KAAE,IAAI;KAAO,OAAO,SAAS,KAAK;IAAE,CAAC;GACnD,CACF;GACA,OAAO;IAAE,IAAI;IAAM,MAAM,KAAK,SAAS;GAAE;EAC3C;EAEA,SAAS,QAAQ;GACf,OAAO,MAAM,IAAI,MAAM,CAAC,EAAE,SAAS;EACrC;EAEA,YAAY,QAAQ,UAAU,OAAO;GACnC,MAAM,OAAO,MAAM,IAAI,MAAM;GAC7B,IAAI,CAAC,MAAM,OAAO;GAClB,OAAO,KAAK,OAAO,UAAU,KAAK,IAAI,aAAa;EACrD;EAEA,YAAY,QAAQ;GAClB,MAAM,OAAO,MAAM,IAAI,MAAM;GAC7B,IAAI,CAAC,MAAM,OAAO,KAAA;GAClB,KAAK,OAAO;GACZ,OAAO,KAAK,SAAS;EACvB;EAEA,MAAM,OAAO,YAAY;GACvB,MAAM,UAAU,MAAM,QAAQ;GAC9B,MAAM,WAAW,aAAa,SAAS,UAAU;GACjD,IAAI,CAAC,UAAU,OAAO;IAAE,IAAI;IAAO,MAAM;IAAoB,OAAO,qBAAqB;GAAa;GACtG,IAAI;IACF,MAAM,QAAQ,OAAO,YAAY,EAAE,QAAQ,YAAY,QAAQ,iBAAiB,EAAE,CAAC;GACrF,SAAS,OAAO;IACd,OAAO;KAAE,IAAI;KAAO,MAAM;KAAW,OAAO,SAAS,KAAK;IAAE;GAC9D;GACA,QAAQ,WAAW;GACnB,OAAO,iBAAiB,SAAS,MAAM;GACvC,OAAO,EAAE,IAAI,KAAK;EACpB;EAEA,MAAM,aAAa;GACjB,MAAM,UAAU,MAAM,UAAU;GAChC,MAAM,YAAY,IAAI,IAAI,QAAQ,aAAa,CAAC,CAAC,KAAK,aAAa,CAAC,SAAS,IAAI,SAAS,IAAI,CAAC,CAAC;GAChG,OAAO,QACJ,qBAAqB,CAAC,CACtB,QAAQ,UAAU,QAAQ,kBAAkB,MAAM,QAAQ,CAAC,CAAC,CAC5D,KAAK,WAAW;IACf,OAAO,GAAG,MAAM,SAAS,GAAG,MAAM;IAClC,OAAO,MAAM;IACb,OAAO,UAAU,IAAI,MAAM,QAAQ,KAAK,MAAM;GAChD,EAAE;EACN;EAEA,QAAQ;GACN,KAAK,MAAM,QAAQ,MAAM,OAAO,GAAG,KAAK,OAAO;GAC/C,MAAM,MAAM;EACd;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/providerAuth/index.ts"],"sourcesContent":["import type {\n AuthMethodType,\n AuthRuntime,\n AuthRuntimeProvider,\n LoginFlowSnapshot,\n ProviderAuthMethod,\n ProviderAuthSummary,\n} from '../../types/server/auth';\nimport { CHATGPT_AUTH_PROVIDER_ID } from '../chatgptAuth';\nimport { createLoginFlow, type LoginFlow } from '../loginFlow';\ninterface SettingsModel {\n value: string;\n label: string;\n group: string;\n}\n\n/** Matches the timeout Pi's own /logout gives the credential store. */\nconst LOGOUT_TIMEOUT_MS = 15_000;\n\nexport type StartLoginOutcome =\n | { ok: true; flow: LoginFlowSnapshot }\n | { ok: false; code: 'unknown_provider' | 'unsupported_method' | 'busy'; error: string };\n\nexport type LogoutOutcome = { ok: true } | { ok: false; code: 'unknown_provider' | 'runtime'; error: string };\n\nexport type AnswerOutcome = 'answered' | 'unknown_flow' | 'not_waiting';\n\n/** Provider credentials as the settings page sees them, backed by one Pi runtime. */\nexport interface ProviderAuth {\n /** Every provider Pi knows, with its current auth state; re-read from disk each call. */\n listProviders(): Promise<ProviderAuthSummary[]>;\n /**\n * Starts a login. `remote` means the browser driving it is on another\n * machine, which decides whether a redirect-free method is chosen for the\n * user; it comes from the accepted socket, never from the request body.\n */\n startLogin(providerId: string, type: AuthMethodType, remote?: boolean): Promise<StartLoginOutcome>;\n getLogin(flowId: string): LoginFlowSnapshot | undefined;\n answerLogin(flowId: string, promptId: string, value: string): AnswerOutcome;\n /** Cancels the flow and returns how it stands, or undefined for an unknown flow. */\n cancelLogin(flowId: string): LoginFlowSnapshot | undefined;\n logout(providerId: string): Promise<LogoutOutcome>;\n /**\n * Every model the machine can actually use, grouped by provider. Read from\n * the same runtime the providers page uses, so the settings model pickers\n * work with no session running.\n */\n listModels(): Promise<readonly SettingsModel[]>;\n /** Abandons every running flow; the hub is shutting down. */\n close(): void;\n}\n\nexport interface ProviderAuthOptions {\n runtime: () => Promise<AuthRuntime>;\n onNotice?: (message: string) => void;\n onChange?: () => void;\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The methods a page can start, in the order the TUI's selector lists them. */\nfunction methodsOf(provider: AuthRuntimeProvider): ProviderAuthMethod[] {\n const methods: ProviderAuthMethod[] = [];\n const { oauth, apiKey } = provider.auth;\n if (oauth) methods.push({ type: 'oauth', label: oauth.loginLabel ?? oauth.name });\n if (apiKey?.login) methods.push({ type: 'api_key', label: apiKey.name });\n return methods;\n}\n\n/** Mirrors the status the TUI's login selector shows beside each provider. */\nfunction summarize(runtime: AuthRuntime, provider: AuthRuntimeProvider): ProviderAuthSummary {\n const summary: ProviderAuthSummary = { id: provider.id, name: provider.name, methods: methodsOf(provider) };\n if (provider.id === CHATGPT_AUTH_PROVIDER_ID) summary.kind = 'po-feature';\n const status = runtime.getProviderAuthStatus(provider.id);\n if (status.configured) {\n summary.authenticated = {\n type: runtime.isUsingOAuth(provider.id) ? 'oauth' : 'api_key',\n source: status.source === 'stored' ? 'stored' : (status.label ?? status.source),\n };\n }\n return summary;\n}\n\nexport function createProviderAuth(options: ProviderAuthOptions): ProviderAuth {\n const notice = options.onNotice ?? ((): void => {});\n const load = options.runtime;\n const flows = new Map<string, LoginFlow>();\n let loading: Promise<AuthRuntime> | undefined;\n\n // A failed load is forgotten so the next request tries again rather than\n // pinning the page on a startup error.\n const runtime = (): Promise<AuthRuntime> => {\n loading ??= load().catch((error: unknown) => {\n loading = undefined;\n throw error;\n });\n return loading;\n };\n\n const findProvider = (current: AuthRuntime, providerId: string): AuthRuntimeProvider | undefined =>\n current.getProviders().find((provider) => provider.id === providerId);\n\n const runningFor = (providerId: string): LoginFlow | undefined =>\n [...flows.values()].find((flow) => flow.providerId === providerId && flow.snapshot().status === 'running');\n\n // Another client may have stored a credential since the last look (a TUI\n // /login, a hand-edited auth.json), and a provider registered by a Pi\n // extension after the runtime was created is missing from the cached auth\n // state until a refresh. The stale snapshot still lists on a failed refresh,\n // which the notice explains.\n const refreshed = async (): Promise<AuthRuntime> => {\n const current = await runtime();\n try {\n await current.refresh({ allowNetwork: false });\n } catch (error) {\n notice(`provider auth refresh failed: ${describe(error)}`);\n }\n return current;\n };\n\n return {\n async listProviders() {\n const current = await refreshed();\n return current\n .getProviders()\n .map((provider) => summarize(current, provider))\n .sort((left, right) => left.name.localeCompare(right.name));\n },\n\n async startLogin(providerId, type, remote) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n if (!methodsOf(provider).some((method) => method.type === type)) {\n return { ok: false, code: 'unsupported_method', error: `${provider.name} has no ${type} login.` };\n }\n if (runningFor(providerId)) {\n return { ok: false, code: 'busy', error: `A login for ${provider.name} is already in progress.` };\n }\n // Finished flows live until the next start: long enough for the page to\n // read how they ended, short enough that the map never grows.\n for (const [id, flow] of flows) if (flow.snapshot().status !== 'running') flows.delete(id);\n\n const flow = createLoginFlow({\n id: crypto.randomUUID(),\n providerId,\n providerName: provider.name,\n type,\n ...(options.onChange ? { onChange: options.onChange } : {}),\n ...(remote === true ? { remote: true } : {}),\n });\n flows.set(flow.id, flow);\n options.onChange?.();\n current.login(providerId, type, flow.interaction).then(\n () => {\n flow.settle({ ok: true });\n notice(`signed in to ${provider.name} (${type})`);\n },\n (error: unknown) => {\n flow.settle({ ok: false, error: describe(error) });\n },\n );\n return { ok: true, flow: flow.snapshot() };\n },\n\n getLogin(flowId) {\n return flows.get(flowId)?.snapshot();\n },\n\n answerLogin(flowId, promptId, value) {\n const flow = flows.get(flowId);\n if (!flow) return 'unknown_flow';\n return flow.answer(promptId, value) ? 'answered' : 'not_waiting';\n },\n\n cancelLogin(flowId) {\n const flow = flows.get(flowId);\n if (!flow) return undefined;\n flow.cancel();\n return flow.snapshot();\n },\n\n async logout(providerId) {\n const current = await runtime();\n const provider = findProvider(current, providerId);\n if (!provider) return { ok: false, code: 'unknown_provider', error: `Unknown provider: ${providerId}` };\n try {\n await current.logout(providerId, { signal: AbortSignal.timeout(LOGOUT_TIMEOUT_MS) });\n } catch (error) {\n return { ok: false, code: 'runtime', error: describe(error) };\n }\n options.onChange?.();\n notice(`signed out of ${provider.name}`);\n return { ok: true };\n },\n\n async listModels() {\n const current = await refreshed();\n const providers = new Map(current.getProviders().map((provider) => [provider.id, provider.name]));\n return current\n .getAvailableSnapshot()\n .filter((model) => current.hasConfiguredAuth(model.provider))\n .map((model) => ({\n value: `${model.provider}/${model.id}`,\n label: model.id,\n group: providers.get(model.provider) ?? model.provider,\n }));\n },\n\n close() {\n for (const flow of flows.values()) flow.cancel();\n flows.clear();\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-core",
3
- "version": "0.0.0-alpha.116",
3
+ "version": "0.0.0-alpha.117",
4
4
  "description": "Po runtime systems and reusable core services.",
5
5
  "keywords": [
6
6
  "ai",
@@ -609,8 +609,8 @@
609
609
  "registry": "https://registry.npmjs.org/"
610
610
  },
611
611
  "dependencies": {
612
- "@agimon-ai/po-logsink": "0.0.1-alpha.98",
613
- "@agimon-ai/po-web-security": "0.0.0-alpha.116",
612
+ "@agimon-ai/po-logsink": "0.0.1-alpha.99",
613
+ "@agimon-ai/po-web-security": "0.0.0-alpha.117",
614
614
  "@deepseek-ai/cordis": "4.0.5",
615
615
  "@earendil-works/chord": "1.5.0",
616
616
  "@earendil-works/pi-agent-core": "1.5.0",
@@ -636,7 +636,7 @@
636
636
  "yaml": "2.9.1"
637
637
  },
638
638
  "devDependencies": {
639
- "@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.116",
639
+ "@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.117",
640
640
  "@apidevtools/swagger-parser": "^13.1.0",
641
641
  "@asyncapi/parser": "^3.6.4",
642
642
  "@earendil-works/pi-ai": "1.5.0",