@geminixiang/mikan 0.4.3 → 0.4.4

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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,12 @@ any release.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.4.4] - 2026-06-12
13
+
14
+ ### Fixed
15
+
16
+ - Require exact provider model ID matches when grouping verified admin model availability.
17
+
12
18
  ## [0.4.3] - 2026-06-12
13
19
 
14
20
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"provider-models.d.ts","sourceRoot":"","sources":["../../../src/web/admin/provider-models.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,KAAK,gBAAgB,GAAG,WAAW,GAAG,YAAY,CAAC;AAEnD,UAAU,sBAAsB;IAC9B,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAYD,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAgB9C;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7D","sourcesContent":["import { createHash } from \"crypto\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { ModelRegistry } from \"@earendil-works/pi-coding-agent\";\n\ntype AdminModelStatus = \"available\" | \"unverified\";\n\ninterface AdminModelAccessStatus {\n status: AdminModelStatus;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 3000;\n\ninterface ProviderModelsCacheEntry {\n expiresAt: number;\n modelIds: Set<string>;\n}\n\nconst providerModelsCache = new Map<string, ProviderModelsCacheEntry>();\n\nexport async function resolveAdminModelAccessStatuses(\n registry: ModelRegistry,\n models: Model<Api>[],\n): Promise<Map<string, AdminModelAccessStatus>> {\n const result = new Map<string, AdminModelAccessStatus>();\n const byProvider = groupModelsByProvider(models);\n\n await Promise.all(\n [...byProvider.entries()].map(async ([provider, providerModels]) => {\n const providerModelIds = await fetchProviderModelIds(registry, provider);\n for (const model of providerModels) {\n result.set(modelKey(model.provider, model.id), {\n status: modelIsListed(providerModelIds, model.id) ? \"available\" : \"unverified\",\n });\n }\n }),\n );\n\n return result;\n}\n\nexport function modelKey(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nfunction groupModelsByProvider(models: Model<Api>[]): Map<string, Model<Api>[]> {\n const groups = new Map<string, Model<Api>[]>();\n for (const model of models) {\n const group = groups.get(model.provider) ?? [];\n group.push(model);\n groups.set(model.provider, group);\n }\n return groups;\n}\n\nasync function fetchProviderModelIds(\n registry: ModelRegistry,\n provider: string,\n): Promise<Set<string>> {\n if (provider !== \"openai\" && provider !== \"anthropic\") return new Set<string>();\n\n const apiKey = await registry.getApiKeyForProvider(provider);\n if (!apiKey) return new Set<string>();\n\n const cacheKey = `${provider}:${hashApiKey(apiKey)}`;\n const cached = providerModelsCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.modelIds;\n\n try {\n const modelIds =\n provider === \"openai\"\n ? await fetchOpenAiModelIds(apiKey)\n : await fetchAnthropicModelIds(apiKey);\n providerModelsCache.set(cacheKey, {\n expiresAt: Date.now() + CACHE_TTL_MS,\n modelIds,\n });\n return modelIds;\n } catch {\n return new Set<string>();\n }\n}\n\nasync function fetchOpenAiModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (!response.ok) throw new Error(`OpenAI models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchAnthropicModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.anthropic.com/v1/models\", {\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"x-api-key\": apiKey,\n },\n });\n if (!response.ok) throw new Error(`Anthropic models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction modelIsListed(providerModelIds: Set<string>, registryModelId: string): boolean {\n if (providerModelIds.size === 0) return true;\n if (providerModelIds.has(registryModelId)) return true;\n\n for (const providerModelId of providerModelIds) {\n if (providerModelId.startsWith(`${registryModelId}-`)) return true;\n if (registryModelId.startsWith(`${providerModelId}-`)) return true;\n }\n return false;\n}\n\nfunction hashApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n"]}
1
+ {"version":3,"file":"provider-models.d.ts","sourceRoot":"","sources":["../../../src/web/admin/provider-models.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,KAAK,gBAAgB,GAAG,WAAW,GAAG,YAAY,CAAC;AAEnD,UAAU,sBAAsB;IAC9B,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAYD,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAgB9C;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE7D","sourcesContent":["import { createHash } from \"crypto\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { ModelRegistry } from \"@earendil-works/pi-coding-agent\";\n\ntype AdminModelStatus = \"available\" | \"unverified\";\n\ninterface AdminModelAccessStatus {\n status: AdminModelStatus;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 3000;\n\ninterface ProviderModelsCacheEntry {\n expiresAt: number;\n modelIds: Set<string>;\n}\n\nconst providerModelsCache = new Map<string, ProviderModelsCacheEntry>();\n\nexport async function resolveAdminModelAccessStatuses(\n registry: ModelRegistry,\n models: Model<Api>[],\n): Promise<Map<string, AdminModelAccessStatus>> {\n const result = new Map<string, AdminModelAccessStatus>();\n const byProvider = groupModelsByProvider(models);\n\n await Promise.all(\n [...byProvider.entries()].map(async ([provider, providerModels]) => {\n const providerModelIds = await fetchProviderModelIds(registry, provider);\n for (const model of providerModels) {\n result.set(modelKey(model.provider, model.id), {\n status: modelIsListed(providerModelIds, model.id) ? \"available\" : \"unverified\",\n });\n }\n }),\n );\n\n return result;\n}\n\nexport function modelKey(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nfunction groupModelsByProvider(models: Model<Api>[]): Map<string, Model<Api>[]> {\n const groups = new Map<string, Model<Api>[]>();\n for (const model of models) {\n const group = groups.get(model.provider) ?? [];\n group.push(model);\n groups.set(model.provider, group);\n }\n return groups;\n}\n\nasync function fetchProviderModelIds(\n registry: ModelRegistry,\n provider: string,\n): Promise<Set<string>> {\n if (provider !== \"openai\" && provider !== \"anthropic\") return new Set<string>();\n\n const apiKey = await registry.getApiKeyForProvider(provider);\n if (!apiKey) return new Set<string>();\n\n const cacheKey = `${provider}:${hashApiKey(apiKey)}`;\n const cached = providerModelsCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.modelIds;\n\n try {\n const modelIds =\n provider === \"openai\"\n ? await fetchOpenAiModelIds(apiKey)\n : await fetchAnthropicModelIds(apiKey);\n providerModelsCache.set(cacheKey, {\n expiresAt: Date.now() + CACHE_TTL_MS,\n modelIds,\n });\n return modelIds;\n } catch {\n return new Set<string>();\n }\n}\n\nasync function fetchOpenAiModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (!response.ok) throw new Error(`OpenAI models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchAnthropicModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.anthropic.com/v1/models\", {\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"x-api-key\": apiKey,\n },\n });\n if (!response.ok) throw new Error(`Anthropic models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction modelIsListed(providerModelIds: Set<string>, registryModelId: string): boolean {\n return providerModelIds.size === 0 || providerModelIds.has(registryModelId);\n}\n\nfunction hashApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n"]}
@@ -83,17 +83,7 @@ async function fetchWithTimeout(url, init) {
83
83
  }
84
84
  }
85
85
  function modelIsListed(providerModelIds, registryModelId) {
86
- if (providerModelIds.size === 0)
87
- return true;
88
- if (providerModelIds.has(registryModelId))
89
- return true;
90
- for (const providerModelId of providerModelIds) {
91
- if (providerModelId.startsWith(`${registryModelId}-`))
92
- return true;
93
- if (registryModelId.startsWith(`${providerModelId}-`))
94
- return true;
95
- }
96
- return false;
86
+ return providerModelIds.size === 0 || providerModelIds.has(registryModelId);
97
87
  }
98
88
  function hashApiKey(apiKey) {
99
89
  return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
@@ -1 +1 @@
1
- {"version":3,"file":"provider-models.js","sourceRoot":"","sources":["../../../src/web/admin/provider-models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAUpC,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAO9B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAoC,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,QAAuB,EACvB,MAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkC,CAAC;IACzD,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAEjD,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,EAAE;QACjE,MAAM,gBAAgB,GAAG,MAAM,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACzE,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE;gBAC7C,MAAM,EAAE,aAAa,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY;aAC/E,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,QAAgB,EAAE,EAAU;IACnD,OAAO,GAAG,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAoB;IACjD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,QAAuB,EACvB,QAAgB;IAEhB,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,GAAG,EAAU,CAAC;IAEhF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC7D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,GAAG,EAAU,CAAC;IAEtC,MAAM,QAAQ,GAAG,GAAG,QAAQ,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IACrD,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IAEpE,IAAI,CAAC;QACH,MAAM,QAAQ,GACZ,QAAQ,KAAK,QAAQ;YACnB,CAAC,CAAC,MAAM,mBAAmB,CAAC,MAAM,CAAC;YACnC,CAAC,CAAC,MAAM,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC3C,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE;YAChC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;YACpC,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAU,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,MAAc;IAC/C,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,kCAAkC,EAAE;QAC1E,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;KAC/C,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuC,CAAC;IAC3E,OAAO,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,MAAc;IAClD,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,qCAAqC,EAAE;QAC7E,OAAO,EAAE;YACP,mBAAmB,EAAE,YAAY;YACjC,WAAW,EAAE,MAAM;SACpB;KACF,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuC,CAAC;IAC3E,OAAO,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,GAAW,EAAE,IAAiB;IAC5D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;IACvE,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,gBAA6B,EAAE,eAAuB;IAC3E,IAAI,gBAAgB,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,gBAAgB,CAAC,GAAG,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE,CAAC;QAC/C,IAAI,eAAe,CAAC,UAAU,CAAC,GAAG,eAAe,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACnE,IAAI,eAAe,CAAC,UAAU,CAAC,GAAG,eAAe,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;IACrE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC","sourcesContent":["import { createHash } from \"crypto\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { ModelRegistry } from \"@earendil-works/pi-coding-agent\";\n\ntype AdminModelStatus = \"available\" | \"unverified\";\n\ninterface AdminModelAccessStatus {\n status: AdminModelStatus;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 3000;\n\ninterface ProviderModelsCacheEntry {\n expiresAt: number;\n modelIds: Set<string>;\n}\n\nconst providerModelsCache = new Map<string, ProviderModelsCacheEntry>();\n\nexport async function resolveAdminModelAccessStatuses(\n registry: ModelRegistry,\n models: Model<Api>[],\n): Promise<Map<string, AdminModelAccessStatus>> {\n const result = new Map<string, AdminModelAccessStatus>();\n const byProvider = groupModelsByProvider(models);\n\n await Promise.all(\n [...byProvider.entries()].map(async ([provider, providerModels]) => {\n const providerModelIds = await fetchProviderModelIds(registry, provider);\n for (const model of providerModels) {\n result.set(modelKey(model.provider, model.id), {\n status: modelIsListed(providerModelIds, model.id) ? \"available\" : \"unverified\",\n });\n }\n }),\n );\n\n return result;\n}\n\nexport function modelKey(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nfunction groupModelsByProvider(models: Model<Api>[]): Map<string, Model<Api>[]> {\n const groups = new Map<string, Model<Api>[]>();\n for (const model of models) {\n const group = groups.get(model.provider) ?? [];\n group.push(model);\n groups.set(model.provider, group);\n }\n return groups;\n}\n\nasync function fetchProviderModelIds(\n registry: ModelRegistry,\n provider: string,\n): Promise<Set<string>> {\n if (provider !== \"openai\" && provider !== \"anthropic\") return new Set<string>();\n\n const apiKey = await registry.getApiKeyForProvider(provider);\n if (!apiKey) return new Set<string>();\n\n const cacheKey = `${provider}:${hashApiKey(apiKey)}`;\n const cached = providerModelsCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.modelIds;\n\n try {\n const modelIds =\n provider === \"openai\"\n ? await fetchOpenAiModelIds(apiKey)\n : await fetchAnthropicModelIds(apiKey);\n providerModelsCache.set(cacheKey, {\n expiresAt: Date.now() + CACHE_TTL_MS,\n modelIds,\n });\n return modelIds;\n } catch {\n return new Set<string>();\n }\n}\n\nasync function fetchOpenAiModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (!response.ok) throw new Error(`OpenAI models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchAnthropicModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.anthropic.com/v1/models\", {\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"x-api-key\": apiKey,\n },\n });\n if (!response.ok) throw new Error(`Anthropic models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction modelIsListed(providerModelIds: Set<string>, registryModelId: string): boolean {\n if (providerModelIds.size === 0) return true;\n if (providerModelIds.has(registryModelId)) return true;\n\n for (const providerModelId of providerModelIds) {\n if (providerModelId.startsWith(`${registryModelId}-`)) return true;\n if (registryModelId.startsWith(`${providerModelId}-`)) return true;\n }\n return false;\n}\n\nfunction hashApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n"]}
1
+ {"version":3,"file":"provider-models.js","sourceRoot":"","sources":["../../../src/web/admin/provider-models.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"crypto\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { ModelRegistry } from \"@earendil-works/pi-coding-agent\";\n\ntype AdminModelStatus = \"available\" | \"unverified\";\n\ninterface AdminModelAccessStatus {\n status: AdminModelStatus;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\nconst PROBE_TIMEOUT_MS = 3000;\n\ninterface ProviderModelsCacheEntry {\n expiresAt: number;\n modelIds: Set<string>;\n}\n\nconst providerModelsCache = new Map<string, ProviderModelsCacheEntry>();\n\nexport async function resolveAdminModelAccessStatuses(\n registry: ModelRegistry,\n models: Model<Api>[],\n): Promise<Map<string, AdminModelAccessStatus>> {\n const result = new Map<string, AdminModelAccessStatus>();\n const byProvider = groupModelsByProvider(models);\n\n await Promise.all(\n [...byProvider.entries()].map(async ([provider, providerModels]) => {\n const providerModelIds = await fetchProviderModelIds(registry, provider);\n for (const model of providerModels) {\n result.set(modelKey(model.provider, model.id), {\n status: modelIsListed(providerModelIds, model.id) ? \"available\" : \"unverified\",\n });\n }\n }),\n );\n\n return result;\n}\n\nexport function modelKey(provider: string, id: string): string {\n return `${provider}/${id}`;\n}\n\nfunction groupModelsByProvider(models: Model<Api>[]): Map<string, Model<Api>[]> {\n const groups = new Map<string, Model<Api>[]>();\n for (const model of models) {\n const group = groups.get(model.provider) ?? [];\n group.push(model);\n groups.set(model.provider, group);\n }\n return groups;\n}\n\nasync function fetchProviderModelIds(\n registry: ModelRegistry,\n provider: string,\n): Promise<Set<string>> {\n if (provider !== \"openai\" && provider !== \"anthropic\") return new Set<string>();\n\n const apiKey = await registry.getApiKeyForProvider(provider);\n if (!apiKey) return new Set<string>();\n\n const cacheKey = `${provider}:${hashApiKey(apiKey)}`;\n const cached = providerModelsCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.modelIds;\n\n try {\n const modelIds =\n provider === \"openai\"\n ? await fetchOpenAiModelIds(apiKey)\n : await fetchAnthropicModelIds(apiKey);\n providerModelsCache.set(cacheKey, {\n expiresAt: Date.now() + CACHE_TTL_MS,\n modelIds,\n });\n return modelIds;\n } catch {\n return new Set<string>();\n }\n}\n\nasync function fetchOpenAiModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n if (!response.ok) throw new Error(`OpenAI models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchAnthropicModelIds(apiKey: string): Promise<Set<string>> {\n const response = await fetchWithTimeout(\"https://api.anthropic.com/v1/models\", {\n headers: {\n \"anthropic-version\": \"2023-06-01\",\n \"x-api-key\": apiKey,\n },\n });\n if (!response.ok) throw new Error(`Anthropic models request failed: ${response.status}`);\n const body = (await response.json()) as { data?: Array<{ id?: unknown }> };\n return new Set((body.data ?? []).map((model) => model.id).filter(isString));\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction modelIsListed(providerModelIds: Set<string>, registryModelId: string): boolean {\n return providerModelIds.size === 0 || providerModelIds.has(registryModelId);\n}\n\nfunction hashApiKey(apiKey: string): string {\n return createHash(\"sha256\").update(apiKey).digest(\"hex\").slice(0, 16);\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminixiang/mikan",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Multi-platform AI coding agent for Slack, Telegram, and Discord",
5
5
  "keywords": [
6
6
  "agent",