dsh-plugin-subscriptions 0.1.1 → 0.2.0
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/LICENSE +21 -0
- package/README.md +2 -0
- package/README.zh.md +3 -1
- package/lib/auth/rpc.d.ts +8 -0
- package/lib/auth/rpc.js +2 -0
- package/lib/client/SubscriptionsSection.d.ts +13 -0
- package/lib/client/SubscriptionsSection.js +111 -1
- package/lib/client/locales.d.ts +20 -0
- package/lib/client/locales.js +20 -0
- package/lib/client.js +228 -0
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +238 -22
- package/lib/providers/claude.d.ts +13 -1
- package/lib/providers/claude.js +89 -0
- package/lib/providers/codex.d.ts +13 -1
- package/lib/providers/codex.js +65 -1
- package/lib/providers/common.d.ts +20 -0
- package/lib/providers/grok.d.ts +25 -1
- package/lib/providers/grok.js +104 -1
- package/package.json +8 -6
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["SUBSCRIPTIONS_AUTH_CHANNEL","PROVIDERS: readonly { id: SubscriptionProvider; name: string }[]","callSubscriptionsAuth","result: RpcResult<unknown>","fallbackTranslate","text: string","styles: Record<string, CSSProperties>","params: Record<string, unknown>","response: StatusResponse","styles","result: RpcResult<unknown>","text: string","parsed: unknown","prompt: string | undefined","parts: string[]","images: { attachment: ImageAttachmentRef }[]","styles: Record<string, CSSProperties>","labels: MessageImageLabels","IconSparkle16","ImageGallery"],"sources":["../src/client/locales.ts","../src/client/SubscriptionsSection.tsx","../src/client/ImageGenerateToolview.tsx","../src/client/index.ts"],"sourcesContent":["/** Copy dictionaries for the Subscriptions settings section. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n nav: 'Subscriptions',\n intro: 'Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.',\n unavailable: 'Connection unavailable; subscription status cannot be loaded.',\n checking: 'Checking…',\n loginInProgress: 'Login in progress…',\n loggedIn: 'Logged in',\n loggedInAccount: 'Logged in as {account}',\n loggedInExpires: 'Logged in · expires {date}',\n loggedInAccountExpires: 'Logged in as {account} · expires {date}',\n notLoggedIn: 'Not logged in',\n login: 'Log in',\n cancel: 'Cancel',\n logout: 'Log out',\n logoutConfirm: 'Log out of {provider}?',\n manualSummary: 'Browser flow not working? Paste the callback URL or code',\n manualPlaceholder: 'Paste the callback URL or code',\n submit: 'Submit',\n loginMissingUrl: 'login answered without an authorizeUrl',\n generating: 'Generating image…',\n image: 'image',\n viewImage: 'View image',\n viewImageNamed: 'View {name}',\n imageLoading: 'Loading…',\n imageLoadFailed: 'Retry',\n imagePreview: 'Image preview',\n imageClose: 'Close',\n} satisfies Record<string, string>\n\n/** zh strings, one per {@link en} key. */\nexport const zh = {\n nav: '订阅',\n intro: '在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。',\n unavailable: '连接不可用,无法加载订阅状态。',\n checking: '查询中…',\n loginInProgress: '登录中…',\n loggedIn: '已登录',\n loggedInAccount: '已登录:{account}',\n loggedInExpires: '已登录 · 过期时间 {date}',\n loggedInAccountExpires: '已登录:{account} · 过期时间 {date}',\n notLoggedIn: '未登录',\n login: '登录',\n cancel: '取消',\n logout: '退出登录',\n logoutConfirm: '确定退出 {provider} 的登录吗?',\n manualSummary: '浏览器流程无法完成?粘贴回调 URL 或授权码',\n manualPlaceholder: '粘贴回调 URL 或授权码',\n submit: '提交',\n loginMissingUrl: 'login 响应缺少 authorizeUrl',\n generating: '正在生成图片…',\n image: '图片',\n viewImage: '查看图片',\n viewImageNamed: '查看 {name}',\n imageLoading: '加载中…',\n imageLoadFailed: '重试',\n imagePreview: '图片预览',\n imageClose: '关闭',\n} satisfies Record<keyof typeof en, string>\n\n/** The Subscriptions namespace key union (en is the key-set source of truth). */\nexport type SubscriptionsKey = keyof typeof en\n","/**\n * Subscriptions settings section: one card per subscription provider with an\n * OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC\n * channel. Login state lives server-side; the page polls `status` only while\n * a login attempt is busy, so an idle page never polls. All state is local\n * React state — the page has no store.\n *\n * Every color resolves through a `--dsw-alias-*` design token (the ui-theme\n * design-platform.css values flip under `body[data-ds-dark-theme]`), and\n * every user-visible string goes through the locale-bound `t` of the\n * 'settings.subscriptions' namespace. Buttons and inputs take the\n * ModelsSection vocabulary minus hover rules, which inline styles cannot\n * express.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { ConnectionHandle, RpcResult } from '@deepseek-ai/dsh-api-remotes/client'\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\n/** Logical RPC channel served by the node half of this plugin. */\nconst SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth'\n\n/** Poll cadence while a provider login attempt is busy. */\nconst POLL_INTERVAL_MS = 2000\n\n/** Subscription provider ids, fixed by the node half's OAuth adapters. */\nexport type SubscriptionProvider = 'codex' | 'claude' | 'grok'\n\n/** One provider's login state as answered by the `status` endpoint. */\nexport interface ProviderStatus {\n loggedIn: boolean\n busy: boolean\n expiresAt?: number\n account?: string\n detail?: string\n}\n\n/** `status` endpoint value: the node half owns this shape. */\ninterface StatusResponse {\n providers: Record<SubscriptionProvider, ProviderStatus>\n}\n\n/** `login` endpoint value: the URL the user completes OAuth at. */\ninterface LoginResponse {\n authorizeUrl: string\n}\n\n/** Injected dependencies of {@link SubscriptionsSection} (slot `inject`). */\nexport interface SubscriptionsSectionInjected {\n /** Generic logical-RPC caller over the Connection transport. */\n rpc: ConnectionHandle['rpc']\n /** Section copy: translate a 'settings.subscriptions' key with `{name}` template params. */\n t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string\n}\n\n/**\n * Props delivered by the slot outlet: the inject face spread flat (the\n * renderer erases the share boundary at the render call).\n */\nexport type SubscriptionsSectionProps = Partial<SubscriptionsSectionInjected>\n\n/** Card display metadata, in page order (names are brand names, not translated). */\nconst PROVIDERS: readonly { id: SubscriptionProvider; name: string }[] = [\n { id: 'codex', name: 'Codex (ChatGPT)' },\n { id: 'claude', name: 'Claude' },\n { id: 'grok', name: 'Grok (X Premium)' },\n]\n\n/** Business error returned by the `/subscriptions-auth` channel (error branch message). */\nclass SubscriptionsAuthError extends Error {}\n\n/**\n * Call one `/subscriptions-auth` endpoint and unwrap the business result.\n * @param rpc - Connection RPC caller.\n * @param endpoint - channel-relative endpoint.\n * @param payload - channel-owned request payload.\n * @returns the success value, cast by the caller to the endpoint's shape.\n */\nasync function callSubscriptionsAuth<T>(rpc: ConnectionHandle['rpc'], endpoint: string, payload: unknown): Promise<T> {\n let result: RpcResult<unknown>\n try {\n result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload)\n } catch (error) {\n // The transport rejected rather than answering; surface the same way.\n throw new SubscriptionsAuthError(error instanceof Error ? error.message : String(error))\n }\n if (!result.ok) throw new SubscriptionsAuthError(result.error.message)\n return result.value as T\n}\n\n/** Human text of an action failure, SubscriptionsAuthError or not. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * English-dictionary fallback for a missing inject `t` (standalone renders);\n * the slot inject always supplies the locale-bound one.\n * @param key - dictionary key.\n * @param params - `{name}` template params.\n * @returns the template with params substituted.\n */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n let text: string = en[key]\n for (const [name, value] of Object.entries(params ?? {})) {\n text = text.replaceAll(`{${name}}`, String(value))\n }\n return text\n}\n\nconst styles: Record<string, CSSProperties> = {\n section: {\n display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 560,\n color: 'var(--dsw-alias-label-primary)',\n },\n intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },\n card: {\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,\n padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,\n },\n cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },\n name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },\n statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },\n button: {\n boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',\n height: 28, padding: '0 10px', borderRadius: 14,\n border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent',\n color: 'var(--dsw-alias-label-primary)', font: 'inherit', fontSize: 12, lineHeight: '18px',\n cursor: 'pointer',\n },\n manual: { marginTop: 4, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },\n manualRow: { display: 'flex', gap: 8, marginTop: 6 },\n manualInput: {\n flex: 1, height: 32, boxSizing: 'border-box',\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,\n padding: '0 10px', font: 'inherit', fontSize: 14, lineHeight: '22px',\n background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',\n },\n}\n\n/** Status dot color for one provider state. */\nfunction dotColor(status: ProviderStatus | undefined): string {\n if (status?.busy === true) return 'var(--dsw-alias-state-warn-label)'\n if (status?.loggedIn === true) return 'var(--dsw-alias-state-success-primary)'\n return 'var(--dsw-alias-label-dimmed)'\n}\n\n/**\n * One-line status text for one provider state.\n * @param t - section translate.\n * @param status - the provider's last reported state.\n * @returns the localized status line.\n */\nfunction statusText(t: SubscriptionsSectionInjected['t'], status: ProviderStatus | undefined): string {\n if (status === undefined) return t('checking')\n if (status.busy) return t('loginInProgress')\n if (status.loggedIn) {\n const params: Record<string, unknown> = {}\n if (status.account !== undefined) params.account = status.account\n if (status.expiresAt !== undefined) params.date = new Date(status.expiresAt).toLocaleString()\n if (params.account !== undefined && params.date !== undefined) return t('loggedInAccountExpires', params)\n if (params.account !== undefined) return t('loggedInAccount', params)\n if (params.date !== undefined) return t('loggedInExpires', params)\n return t('loggedIn')\n }\n return t('notLoggedIn')\n}\n\n/**\n * The Subscriptions settings page component.\n * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).\n * @returns the section body, or a notice while the RPC face is absent.\n */\nexport function SubscriptionsSection(props: SubscriptionsSectionProps) {\n const { rpc } = props\n const t = props.t ?? fallbackTranslate\n const [statuses, setStatuses] = useState<Partial<Record<SubscriptionProvider, ProviderStatus>>>({})\n const [errors, setErrors] = useState<Partial<Record<SubscriptionProvider, string>>>({})\n const [manualDrafts, setManualDrafts] = useState<Record<SubscriptionProvider, string>>({\n codex: '', claude: '', grok: '',\n })\n const mountedRef = useRef(true)\n const pollersRef = useRef(new Map<SubscriptionProvider, ReturnType<typeof setInterval>>())\n\n const setProviderError = useCallback((provider: SubscriptionProvider, message: string | undefined): void => {\n if (!mountedRef.current) return\n setErrors((prev) => {\n const next = { ...prev }\n if (message === undefined) delete next[provider]\n else next[provider] = message\n return next\n })\n }, [])\n\n const stopPolling = useCallback((provider: SubscriptionProvider): void => {\n const poller = pollersRef.current.get(provider)\n if (poller !== undefined) {\n clearInterval(poller)\n pollersRef.current.delete(provider)\n }\n }, [])\n\n /** Refetch every provider's status; stop a provider's poller once its attempt settles. */\n const refresh = useCallback(async (): Promise<void> => {\n if (rpc === undefined) return\n let response: StatusResponse\n try {\n response = await callSubscriptionsAuth<StatusResponse>(rpc, 'status', {})\n } catch {\n // A failed poll must not kill the page; busy providers keep polling and\n // the action paths report their own errors.\n return\n }\n if (!mountedRef.current) return\n setStatuses(response.providers)\n for (const { id } of PROVIDERS) {\n const status = response.providers[id]\n if (status.loggedIn || !status.busy) stopPolling(id)\n }\n }, [rpc, stopPolling])\n\n const startPolling = useCallback((provider: SubscriptionProvider): void => {\n if (pollersRef.current.has(provider)) return\n pollersRef.current.set(provider, setInterval(() => { void refresh() }, POLL_INTERVAL_MS))\n }, [refresh])\n\n // Initial load; every busy provider (e.g. an attempt started before a page\n // reload) resumes polling. Teardown clears pollers and the mounted guard.\n useEffect(() => {\n mountedRef.current = true\n void refresh().then(() => {\n if (!mountedRef.current) return\n setStatuses((current) => {\n for (const { id } of PROVIDERS) {\n if (current[id]?.busy === true) startPolling(id)\n }\n return current\n })\n })\n return () => {\n mountedRef.current = false\n for (const poller of pollersRef.current.values()) clearInterval(poller)\n pollersRef.current.clear()\n }\n }, [refresh, startPolling])\n\n const login = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n setProviderError(provider, undefined)\n try {\n const response = await callSubscriptionsAuth<LoginResponse>(rpc, 'login', { provider })\n if (typeof response.authorizeUrl !== 'string' || response.authorizeUrl === '') {\n throw new SubscriptionsAuthError(t('loginMissingUrl'))\n }\n window.open(response.authorizeUrl, '_blank', 'noopener')\n if (!mountedRef.current) return\n // Optimistic busy so Cancel and the manual fallback appear before the first poll tick.\n setStatuses(prev => ({ ...prev, [provider]: { ...prev[provider], busy: true, loggedIn: false } }))\n startPolling(provider)\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n }, [rpc, t, setProviderError, startPolling])\n\n const cancel = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n stopPolling(provider)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'cancel', { provider })\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, stopPolling, setProviderError, refresh])\n\n const submitManual = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n const input = manualDrafts[provider].trim()\n if (input === '') return\n setProviderError(provider, undefined)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'manual', { provider, input })\n if (mountedRef.current) setManualDrafts(prev => ({ ...prev, [provider]: '' }))\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, manualDrafts, setProviderError, refresh])\n\n const logout = useCallback(async (provider: SubscriptionProvider, name: string): Promise<void> => {\n if (rpc === undefined) return\n if (!window.confirm(t('logoutConfirm', { provider: name }))) return\n setProviderError(provider, undefined)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'logout', { provider })\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, t, setProviderError, refresh])\n\n if (rpc === undefined) {\n return <p style={styles.intro}>{t('unavailable')}</p>\n }\n\n return (\n <div style={styles.section}>\n <p style={styles.intro}>{t('intro')}</p>\n {PROVIDERS.map(({ id, name }) => {\n const status = statuses[id]\n const busy = status?.busy === true\n return (\n <div key={id} style={styles.card}>\n <div style={styles.cardHeader}>\n <span style={{ ...styles.dot, background: dotColor(status) }} />\n <span style={styles.name}>{name}</span>\n </div>\n <p style={styles.statusLine}>{statusText(t, status)}</p>\n {status?.detail !== undefined && status.detail !== '' && (\n <p style={styles.statusLine}>{status.detail}</p>\n )}\n {errors[id] !== undefined && <p style={styles.errorLine}>{errors[id]}</p>}\n <div style={styles.actions}>\n {!busy && status?.loggedIn !== true && (\n <button type=\"button\" style={styles.button} onClick={() => { void login(id) }}>\n {t('login')}\n </button>\n )}\n {busy && (\n <button type=\"button\" style={styles.button} onClick={() => { void cancel(id) }}>\n {t('cancel')}\n </button>\n )}\n {status?.loggedIn === true && (\n <button type=\"button\" style={styles.button} onClick={() => { void logout(id, name) }}>\n {t('logout')}\n </button>\n )}\n </div>\n {busy && (\n <details style={styles.manual}>\n <summary>{t('manualSummary')}</summary>\n <div style={styles.manualRow}>\n <input\n style={styles.manualInput}\n value={manualDrafts[id]}\n placeholder={t('manualPlaceholder')}\n onChange={event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value }))}\n />\n <button type=\"button\" style={styles.button} onClick={() => { void submitManual(id) }}>\n {t('submit')}\n </button>\n </div>\n </details>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n","/**\n * Keyed toolview for the `image_generate` tool: renders generated images\n * inline in the conversation. The row shows the call's prompt while running\n * and after settling; a settled result with image blocks renders them through\n * the platform ImageGallery, whose bytes load through the node half's\n * `/subscriptions-auth` RPC channel (the durable ImageAttachmentRef is never\n * a fetchable URL on its own). A text-only settled result (degraded route)\n * renders its text; an error result renders the first error line.\n *\n * The 'tool.call.toolview' slot contract is owned by ui-tool\n * (packages/client/ui-tool/src/client/contract/slots.ts), which this package\n * does not resolve; the SlotMap merge and ToolCallOwnerProps below mirror it\n * structurally (same discipline as platform-modules.d.ts).\n */\nimport type { CSSProperties } from 'react'\nimport type { ConnectionHandle, RpcResult } from '@deepseek-ai/dsh-api-remotes/client'\nimport type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'\nimport { IconSparkle16 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { ImageGallery } from '@deepseek-ai/dsh-client-ui-attachment'\nimport type { ImageAttachmentRef, ImageLoader, MessageImageLabels } from '@deepseek-ai/dsh-client-ui-attachment'\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\n/** Logical RPC channel served by the node half of this plugin. */\nconst SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth'\n\n/** Title prompt truncation budget (characters). */\nconst PROMPT_MAX_LENGTH = 60\n\n/** Mirror of ui-tool's ToolCallOwnerProps (see the module header). */\ninterface ToolCallOwnerProps {\n callId: string\n toolName: string\n block: ToolCallBlock\n cwd?: string | undefined\n openFile: (path: string) => void\n inspect?: (() => void) | undefined\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /** Mirror of ui-tool's keyed atomic Tool view declaration (see the module header). */\n 'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ToolCallOwnerProps }\n }\n}\n\n/** Injected dependencies of {@link ImageGenerateToolview} (slot `inject`). */\nexport interface ImageGenerateToolviewInjected {\n /** Session-authorized image URL loader riding the `/subscriptions-auth` channel. */\n load: ImageLoader\n}\n\n/**\n * Props delivered by the toolview outlet: the owner share plus the inject\n * face and the framework locale seat, spread flat.\n */\nexport type ImageGenerateToolviewProps =\n Partial<ToolCallOwnerProps>\n & Partial<ImageGenerateToolviewInjected>\n & { t?: ((key: SubscriptionsKey, params?: Record<string, unknown>) => string) | undefined }\n\n/** `image` endpoint result: the node half owns this shape. */\ninterface ImageEndpointResult {\n mediaType: string\n dataBase64: string\n}\n\n/**\n * Call one `/subscriptions-auth` endpoint and unwrap the business result.\n * @param rpc - Connection RPC caller.\n * @param endpoint - channel-relative endpoint.\n * @param payload - channel-owned request payload.\n * @returns the success value, cast by the caller to the endpoint's shape.\n */\nasync function callSubscriptionsAuth<T>(rpc: ConnectionHandle['rpc'], endpoint: string, payload: unknown): Promise<T> {\n const result: RpcResult<unknown> = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload)\n if (!result.ok) throw new Error(result.error.message)\n return result.value as T\n}\n\n/**\n * Build the ImageGallery loader over the `image` endpoint.\n * @param rpc - Connection RPC caller.\n * @returns loader resolving an attachment ref to a data URL.\n */\nexport function createImageLoader(rpc: ConnectionHandle['rpc']): ImageLoader {\n // The host validates a full ImageAttachmentRef payload (readImage takes the\n // whole ref), so forward the attachment verbatim.\n return attachment =>\n callSubscriptionsAuth<ImageEndpointResult>(rpc, 'image', { ...attachment })\n .then(result => `data:${result.mediaType};base64,${result.dataBase64}`)\n}\n\n/**\n * English-dictionary fallback for a missing locale seat (standalone renders);\n * the framework always supplies the namespace-bound one.\n * @param key - dictionary key.\n * @param params - `{name}` template params.\n * @returns the template with params substituted.\n */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n let text: string = en[key]\n for (const [name, value] of Object.entries(params ?? {})) {\n text = text.replaceAll(`{${name}}`, String(value))\n }\n return text\n}\n\n/** Extract the prompt from the call's raw args JSON; falls back to the first string value, then the raw line. */\nfunction derivePrompt(argsRaw: string): string {\n let parsed: unknown\n try {\n parsed = JSON.parse(argsRaw)\n } catch {\n // Non-JSON args (mid-stream truncation): fall back to the raw string below.\n parsed = undefined\n }\n let prompt: string | undefined\n if (typeof parsed === 'object' && parsed !== null) {\n const args = parsed as Record<string, unknown>\n if (typeof args.prompt === 'string' && args.prompt !== '') prompt = args.prompt\n else {\n for (const value of Object.values(args)) {\n if (typeof value === 'string' && value !== '') { prompt = value; break }\n }\n }\n }\n const line = (prompt ?? argsRaw).split('\\n', 1)[0] ?? ''\n return line.length > PROMPT_MAX_LENGTH ? `${line.slice(0, PROMPT_MAX_LENGTH)}…` : line\n}\n\n/** Flatten a settled result's text blocks (the degraded text-only route and the error line). */\nfunction resultText(block: ToolCallBlock): string {\n if (!('kind' in block)) return ''\n const parts: string[] = []\n for (const part of block.content) {\n if (part.type === 'text') parts.push(part.text)\n }\n if (parts.length === 0 && block.error !== undefined) parts.push(`${block.error.name}: ${block.error.code}`)\n return parts.join('\\n')\n}\n\n/** Image attachments of a settled result; empty while running or on the text-only route. */\nfunction resultImages(block: ToolCallBlock): { attachment: ImageAttachmentRef }[] {\n if (!('kind' in block)) return []\n const images: { attachment: ImageAttachmentRef }[] = []\n for (const part of block.content) {\n if (part.type === 'image') images.push({ attachment: part.attachment as ImageAttachmentRef })\n }\n return images\n}\n\nconst styles: Record<string, CSSProperties> = {\n container: { display: 'flex', flexDirection: 'column', gap: 6, padding: '4px 0' },\n row: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 },\n icon: { display: 'inline-flex', flexShrink: 0, color: 'var(--dsw-alias-label-tertiary)' },\n title: {\n fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)',\n overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',\n },\n subtle: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n output: {\n margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',\n whiteSpace: 'pre-wrap', overflowWrap: 'anywhere',\n },\n error: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n}\n\n/**\n * The `image_generate` keyed toolview component.\n * @param props - owner share, inject face, and locale seat (spread flat).\n * @returns the call row plus, once settled, the gallery / text / error body.\n */\nexport function ImageGenerateToolview(props: ImageGenerateToolviewProps) {\n const { block, load } = props\n const t = props.t ?? fallbackTranslate\n if (block === undefined) return null\n const settled = 'kind' in block\n const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? ''\n const title = `image_generate: ${derivePrompt(argsRaw)}`\n const images = resultImages(block)\n const text = settled ? resultText(block) : ''\n const labels: MessageImageLabels = {\n image: t('image'),\n open: t('viewImage'),\n openNamed: name => t('viewImageNamed', { name }),\n loading: t('imageLoading'),\n loadFailed: t('imageLoadFailed'),\n lightbox: { dialog: t('imagePreview'), close: t('imageClose') },\n }\n return (\n <div style={styles.container}>\n <div style={styles.row}>\n <span style={styles.icon}><IconSparkle16 size={14} /></span>\n <span style={styles.title}>{title}</span>\n </div>\n {!settled && <p style={styles.subtle}>{t('generating')}</p>}\n {settled && block.isError && text !== '' && (\n <p style={styles.error}>{text.split('\\n', 1)[0]}</p>\n )}\n {settled && !block.isError && images.length > 0 && load !== undefined && (\n <ImageGallery images={images} load={load} align=\"start\" labels={labels} />\n )}\n {settled && !block.isError && images.length === 0 && text !== '' && (\n <p style={styles.output}>{text}</p>\n )}\n </div>\n )\n}\n","/**\n * Subscription OAuth login page, browser half. Registers the Subscriptions\n * settings section; every login state fact arrives through the node half's\n * `/subscriptions-auth` RPC channel — this plugin holds no credential state of its\n * own. Section copy rides the client locale service: one 'settings.subscriptions'\n * namespace with zh/en dictionaries, rebound per read so the nav label and\n * page text follow the active locale.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the shell's SlotMap merge (the 'settings.section' entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// `.js` extension: this package's tsconfig lacks the reference repo's\n// allowImportingTsExtensions/rewriteRelativeImportExtensions pair; under\n// nodenext the .js specifier resolves to the .tsx source (see README note).\nimport { SubscriptionsSection } from './SubscriptionsSection.js'\nimport type { SubscriptionsSectionInjected } from './SubscriptionsSection.js'\nimport { ImageGenerateToolview, createImageLoader } from './ImageGenerateToolview.js'\nimport type { ImageGenerateToolviewInjected } from './ImageGenerateToolview.js'\nimport { en, zh } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\nexport type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js'\nexport type { ImageGenerateToolviewInjected, ImageGenerateToolviewProps } from './ImageGenerateToolview.js'\nexport type { SubscriptionsKey } from './locales.js'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The Subscriptions settings page copy. */\n 'settings.subscriptions': SubscriptionsKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.subscriptions'\n\n/**\n * Required services (cordis fiber inject): `slots` carries the registration\n * seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy\n * dictionaries.\n */\nexport const inject = ['slots', 'connection', 'locale']\n\n/**\n * Register the Subscriptions section once the `settings.section` declaration\n * is on the ledger (the shell's apply order relative to this one is NOT\n * constrained; registration depends on the slot through `slots.inject()`).\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscriptions: copy dictionaries')\n // The client-runtime Context merge types `connection` as the host handle;\n // in the browser shell the same key holds the full client ConnectionHandle.\n const connection = ctx.get('connection') as unknown as ConnectionHandle\n const t = ctx.locale.bind(NS) as SubscriptionsSectionInjected['t']\n const injected = (): SubscriptionsSectionInjected => ({ rpc: connection.rpc, t })\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'subscriptions',\n order: 90,\n // A thunk re-evaluated per read, so the nav label follows the active locale.\n label: () => t('nav'),\n inject: injected,\n }, SubscriptionsSection))\n\n // The image_generate keyed toolview owns how image calls render inline; its\n // gallery bytes ride the same channel through the injected loader. The\n // framework synthesizes the toolview's own `t` seat from `locale: NS`.\n const toolviewInjected = (): ImageGenerateToolviewInjected => ({ load: createImageLoader(connection.rpc) })\n ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({\n name: 'tool.call.toolview',\n key: 'image_generate',\n locale: NS,\n inject: toolviewInjected,\n }, ImageGenerateToolview))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["SUBSCRIPTIONS_AUTH_CHANNEL","PROVIDERS: readonly { id: SubscriptionProvider; name: string }[]","callSubscriptionsAuth","result: RpcResult<unknown>","fallbackTranslate","text: string","styles: Record<string, CSSProperties>","params: Record<string, unknown>","window","response: StatusResponse","styles","result: RpcResult<unknown>","text: string","parsed: unknown","prompt: string | undefined","parts: string[]","images: { attachment: ImageAttachmentRef }[]","styles: Record<string, CSSProperties>","labels: MessageImageLabels","IconSparkle16","ImageGallery"],"sources":["../src/client/locales.ts","../src/client/SubscriptionsSection.tsx","../src/client/ImageGenerateToolview.tsx","../src/client/index.ts"],"sourcesContent":["/** Copy dictionaries for the Subscriptions settings section. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n nav: 'Subscriptions',\n intro: 'Log a subscription provider in or out. Login opens the provider’s authorization page in a new tab; headless setups can paste the callback URL or code instead.',\n unavailable: 'Connection unavailable; subscription status cannot be loaded.',\n checking: 'Checking…',\n loginInProgress: 'Login in progress…',\n loggedIn: 'Logged in',\n loggedInAccount: 'Logged in as {account}',\n loggedInExpires: 'Logged in · expires {date}',\n loggedInAccountExpires: 'Logged in as {account} · expires {date}',\n notLoggedIn: 'Not logged in',\n login: 'Log in',\n cancel: 'Cancel',\n logout: 'Log out',\n logoutConfirm: 'Log out of {provider}?',\n manualSummary: 'Browser flow not working? Paste the callback URL or code',\n manualPlaceholder: 'Paste the callback URL or code',\n submit: 'Submit',\n loginMissingUrl: 'login answered without an authorizeUrl',\n usageTitle: 'Usage',\n usageRefresh: 'Refresh',\n usageLoading: 'Loading usage…',\n usageEmpty: 'No usage windows reported.',\n usageError: 'Usage lookup failed: {message}',\n usageSession: '5-hour window',\n usageWeekly: 'Weekly',\n usageWindow: 'Window',\n usageResets: 'resets {date}',\n usagePlan: 'Plan: {plan}',\n generating: 'Generating image…',\n image: 'image',\n viewImage: 'View image',\n viewImageNamed: 'View {name}',\n imageLoading: 'Loading…',\n imageLoadFailed: 'Retry',\n imagePreview: 'Image preview',\n imageClose: 'Close',\n} satisfies Record<string, string>\n\n/** zh strings, one per {@link en} key. */\nexport const zh = {\n nav: '订阅',\n intro: '在此登录或退出订阅服务商。点击登录会在新标签页打开服务商的授权页面;无浏览器环境可改为粘贴回调 URL 或授权码。',\n unavailable: '连接不可用,无法加载订阅状态。',\n checking: '查询中…',\n loginInProgress: '登录中…',\n loggedIn: '已登录',\n loggedInAccount: '已登录:{account}',\n loggedInExpires: '已登录 · 过期时间 {date}',\n loggedInAccountExpires: '已登录:{account} · 过期时间 {date}',\n notLoggedIn: '未登录',\n login: '登录',\n cancel: '取消',\n logout: '退出登录',\n logoutConfirm: '确定退出 {provider} 的登录吗?',\n manualSummary: '浏览器流程无法完成?粘贴回调 URL 或授权码',\n manualPlaceholder: '粘贴回调 URL 或授权码',\n submit: '提交',\n loginMissingUrl: 'login 响应缺少 authorizeUrl',\n usageTitle: '用量',\n usageRefresh: '刷新',\n usageLoading: '用量加载中…',\n usageEmpty: '服务商未返回任何用量窗口。',\n usageError: '用量查询失败:{message}',\n usageSession: '5 小时窗口',\n usageWeekly: '每周',\n usageWindow: '窗口',\n usageResets: '{date} 重置',\n usagePlan: '计划:{plan}',\n generating: '正在生成图片…',\n image: '图片',\n viewImage: '查看图片',\n viewImageNamed: '查看 {name}',\n imageLoading: '加载中…',\n imageLoadFailed: '重试',\n imagePreview: '图片预览',\n imageClose: '关闭',\n} satisfies Record<keyof typeof en, string>\n\n/** The Subscriptions namespace key union (en is the key-set source of truth). */\nexport type SubscriptionsKey = keyof typeof en\n","/**\n * Subscriptions settings section: one card per subscription provider with an\n * OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC\n * channel. Login state lives server-side; the page polls `status` only while\n * a login attempt is busy, so an idle page never polls. All state is local\n * React state — the page has no store.\n *\n * Every color resolves through a `--dsw-alias-*` design token (the ui-theme\n * design-platform.css values flip under `body[data-ds-dark-theme]`), and\n * every user-visible string goes through the locale-bound `t` of the\n * 'settings.subscriptions' namespace. Buttons and inputs take the\n * ModelsSection vocabulary minus hover rules, which inline styles cannot\n * express.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { ConnectionHandle, RpcResult } from '@deepseek-ai/dsh-api-remotes/client'\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\n/** Logical RPC channel served by the node half of this plugin. */\nconst SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth'\n\n/** Poll cadence while a provider login attempt is busy. */\nconst POLL_INTERVAL_MS = 2000\n\n/** Subscription provider ids, fixed by the node half's OAuth adapters. */\nexport type SubscriptionProvider = 'codex' | 'claude' | 'grok'\n\n/** One provider's login state as answered by the `status` endpoint. */\nexport interface ProviderStatus {\n loggedIn: boolean\n busy: boolean\n expiresAt?: number\n account?: string\n detail?: string\n}\n\n/** `status` endpoint value: the node half owns this shape. */\ninterface StatusResponse {\n providers: Record<SubscriptionProvider, ProviderStatus>\n}\n\n/** One rate-limit window as answered by the `usage` endpoint. */\nexport interface UsageWindow {\n kind: 'session' | 'weekly' | 'other'\n scope?: string\n usedPercent: number\n resetsAt?: number\n}\n\n/** `usage` endpoint value: the node half owns this shape. */\nexport interface ProviderUsage {\n supported: boolean\n windows?: UsageWindow[]\n plan?: string\n}\n\n/** `login` endpoint value: the URL the user completes OAuth at. */\ninterface LoginResponse {\n authorizeUrl: string\n}\n\n/** Injected dependencies of {@link SubscriptionsSection} (slot `inject`). */\nexport interface SubscriptionsSectionInjected {\n /** Generic logical-RPC caller over the Connection transport. */\n rpc: ConnectionHandle['rpc']\n /** Section copy: translate a 'settings.subscriptions' key with `{name}` template params. */\n t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string\n}\n\n/**\n * Props delivered by the slot outlet: the inject face spread flat (the\n * renderer erases the share boundary at the render call).\n */\nexport type SubscriptionsSectionProps = Partial<SubscriptionsSectionInjected>\n\n/** Card display metadata, in page order (names are brand names, not translated). */\nconst PROVIDERS: readonly { id: SubscriptionProvider; name: string }[] = [\n { id: 'codex', name: 'Codex (ChatGPT)' },\n { id: 'claude', name: 'Claude' },\n { id: 'grok', name: 'Grok (X Premium)' },\n]\n\n/** Business error returned by the `/subscriptions-auth` channel (error branch message). */\nclass SubscriptionsAuthError extends Error {}\n\n/**\n * Call one `/subscriptions-auth` endpoint and unwrap the business result.\n * @param rpc - Connection RPC caller.\n * @param endpoint - channel-relative endpoint.\n * @param payload - channel-owned request payload.\n * @returns the success value, cast by the caller to the endpoint's shape.\n */\nasync function callSubscriptionsAuth<T>(rpc: ConnectionHandle['rpc'], endpoint: string, payload: unknown): Promise<T> {\n let result: RpcResult<unknown>\n try {\n result = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload)\n } catch (error) {\n // The transport rejected rather than answering; surface the same way.\n throw new SubscriptionsAuthError(error instanceof Error ? error.message : String(error))\n }\n if (!result.ok) throw new SubscriptionsAuthError(result.error.message)\n return result.value as T\n}\n\n/** Human text of an action failure, SubscriptionsAuthError or not. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/**\n * English-dictionary fallback for a missing inject `t` (standalone renders);\n * the slot inject always supplies the locale-bound one.\n * @param key - dictionary key.\n * @param params - `{name}` template params.\n * @returns the template with params substituted.\n */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n let text: string = en[key]\n for (const [name, value] of Object.entries(params ?? {})) {\n text = text.replaceAll(`{${name}}`, String(value))\n }\n return text\n}\n\nconst styles: Record<string, CSSProperties> = {\n section: {\n display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 560,\n color: 'var(--dsw-alias-label-primary)',\n },\n intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },\n card: {\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,\n padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,\n },\n cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },\n name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },\n statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },\n button: {\n boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',\n height: 28, padding: '0 10px', borderRadius: 14,\n border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent',\n color: 'var(--dsw-alias-label-primary)', font: 'inherit', fontSize: 12, lineHeight: '18px',\n cursor: 'pointer',\n },\n usage: {\n display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,\n borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8,\n },\n usageHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n usageTitle: { fontSize: 12, lineHeight: '18px', fontWeight: 500, color: 'var(--dsw-alias-label-secondary)' },\n usagePlan: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n usageRefresh: {\n boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',\n height: 22, padding: '0 8px', borderRadius: 11, marginLeft: 'auto',\n border: '1px solid var(--dsw-alias-border-l2)', background: 'transparent',\n color: 'var(--dsw-alias-label-secondary)', font: 'inherit', fontSize: 12, lineHeight: '18px',\n cursor: 'pointer',\n },\n usageRow: { display: 'flex', flexDirection: 'column', gap: 3 },\n usageMeta: {\n display: 'flex', justifyContent: 'space-between', gap: 8,\n fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',\n },\n usageTrack: {\n height: 6, borderRadius: 3, overflow: 'hidden',\n background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',\n },\n usageFill: { height: '100%', borderRadius: 3 },\n manual: { marginTop: 4, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },\n manualRow: { display: 'flex', gap: 8, marginTop: 6 },\n manualInput: {\n flex: 1, height: 32, boxSizing: 'border-box',\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,\n padding: '0 10px', font: 'inherit', fontSize: 14, lineHeight: '22px',\n background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',\n },\n}\n\n/** Status dot color for one provider state. */\nfunction dotColor(status: ProviderStatus | undefined): string {\n if (status?.busy === true) return 'var(--dsw-alias-state-warn-label)'\n if (status?.loggedIn === true) return 'var(--dsw-alias-state-success-primary)'\n return 'var(--dsw-alias-label-dimmed)'\n}\n\n/**\n * One-line status text for one provider state.\n * @param t - section translate.\n * @param status - the provider's last reported state.\n * @returns the localized status line.\n */\nfunction statusText(t: SubscriptionsSectionInjected['t'], status: ProviderStatus | undefined): string {\n if (status === undefined) return t('checking')\n if (status.busy) return t('loginInProgress')\n if (status.loggedIn) {\n const params: Record<string, unknown> = {}\n if (status.account !== undefined) params.account = status.account\n if (status.expiresAt !== undefined) params.date = new Date(status.expiresAt).toLocaleString()\n if (params.account !== undefined && params.date !== undefined) return t('loggedInAccountExpires', params)\n if (params.account !== undefined) return t('loggedInAccount', params)\n if (params.date !== undefined) return t('loggedInExpires', params)\n return t('loggedIn')\n }\n return t('notLoggedIn')\n}\n\n/**\n * Localized label of one usage window (kind, plus the model scope when named).\n * @param t - section translate.\n * @param window - the reported window.\n * @returns e.g. \"5-hour window\" or \"Weekly · Opus\".\n */\nfunction usageWindowLabel(t: SubscriptionsSectionInjected['t'], window: UsageWindow): string {\n const base = window.kind === 'session'\n ? t('usageSession')\n : window.kind === 'weekly' ? t('usageWeekly') : t('usageWindow')\n return window.scope !== undefined && window.scope !== '' ? `${base} · ${window.scope}` : base\n}\n\n/** Bar fill color: success normally, warn from 80%, error from 95%. */\nfunction usageBarColor(usedPercent: number): string {\n if (usedPercent >= 95) return 'var(--dsw-alias-state-error-primary)'\n if (usedPercent >= 80) return 'var(--dsw-alias-state-warn-label)'\n return 'var(--dsw-alias-state-success-primary)'\n}\n\n/**\n * The Subscriptions settings page component.\n * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).\n * @returns the section body, or a notice while the RPC face is absent.\n */\nexport function SubscriptionsSection(props: SubscriptionsSectionProps) {\n const { rpc } = props\n const t = props.t ?? fallbackTranslate\n const [statuses, setStatuses] = useState<Partial<Record<SubscriptionProvider, ProviderStatus>>>({})\n const [errors, setErrors] = useState<Partial<Record<SubscriptionProvider, string>>>({})\n const [manualDrafts, setManualDrafts] = useState<Record<SubscriptionProvider, string>>({\n codex: '', claude: '', grok: '',\n })\n const [usages, setUsages] = useState<Partial<Record<SubscriptionProvider, ProviderUsage>>>({})\n const [usageErrors, setUsageErrors] = useState<Partial<Record<SubscriptionProvider, string>>>({})\n const [usageLoading, setUsageLoading] = useState<Partial<Record<SubscriptionProvider, boolean>>>({})\n const mountedRef = useRef(true)\n const pollersRef = useRef(new Map<SubscriptionProvider, ReturnType<typeof setInterval>>())\n /** Providers with a `usage` call in flight; guards the auto-fetch effect against re-entry. */\n const usageInflightRef = useRef(new Set<SubscriptionProvider>())\n\n const setProviderError = useCallback((provider: SubscriptionProvider, message: string | undefined): void => {\n if (!mountedRef.current) return\n setErrors((prev) => {\n const next = { ...prev }\n if (message === undefined) delete next[provider]\n else next[provider] = message\n return next\n })\n }, [])\n\n const stopPolling = useCallback((provider: SubscriptionProvider): void => {\n const poller = pollersRef.current.get(provider)\n if (poller !== undefined) {\n clearInterval(poller)\n pollersRef.current.delete(provider)\n }\n }, [])\n\n /** Refetch every provider's status; stop a provider's poller once its attempt settles. */\n const refresh = useCallback(async (): Promise<void> => {\n if (rpc === undefined) return\n let response: StatusResponse\n try {\n response = await callSubscriptionsAuth<StatusResponse>(rpc, 'status', {})\n } catch {\n // A failed poll must not kill the page; busy providers keep polling and\n // the action paths report their own errors.\n return\n }\n if (!mountedRef.current) return\n setStatuses(response.providers)\n for (const { id } of PROVIDERS) {\n const status = response.providers[id]\n if (status.loggedIn || !status.busy) stopPolling(id)\n }\n }, [rpc, stopPolling])\n\n const startPolling = useCallback((provider: SubscriptionProvider): void => {\n if (pollersRef.current.has(provider)) return\n pollersRef.current.set(provider, setInterval(() => { void refresh() }, POLL_INTERVAL_MS))\n }, [refresh])\n\n // Initial load; every busy provider (e.g. an attempt started before a page\n // reload) resumes polling. Teardown clears pollers and the mounted guard.\n useEffect(() => {\n mountedRef.current = true\n void refresh().then(() => {\n if (!mountedRef.current) return\n setStatuses((current) => {\n for (const { id } of PROVIDERS) {\n if (current[id]?.busy === true) startPolling(id)\n }\n return current\n })\n })\n return () => {\n mountedRef.current = false\n for (const poller of pollersRef.current.values()) clearInterval(poller)\n pollersRef.current.clear()\n }\n }, [refresh, startPolling])\n\n const loadUsage = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined || usageInflightRef.current.has(provider)) return\n usageInflightRef.current.add(provider)\n setUsageLoading(prev => ({ ...prev, [provider]: true }))\n try {\n const usage = await callSubscriptionsAuth<ProviderUsage>(rpc, 'usage', { provider })\n if (!mountedRef.current) return\n setUsages(prev => ({ ...prev, [provider]: usage }))\n setUsageErrors((prev) => {\n const next = { ...prev }\n delete next[provider]\n return next\n })\n } catch (error) {\n if (mountedRef.current) setUsageErrors(prev => ({ ...prev, [provider]: messageOf(error) }))\n } finally {\n usageInflightRef.current.delete(provider)\n if (mountedRef.current) setUsageLoading(prev => ({ ...prev, [provider]: false }))\n }\n }, [rpc])\n\n // Fetch usage once a provider is logged in; drop the cached snapshot on\n // logout so a re-login refetches. A failed lookup does not auto-retry — the\n // per-card Refresh button is the retry path.\n useEffect(() => {\n for (const { id } of PROVIDERS) {\n const status = statuses[id]\n if (status === undefined) continue\n if (status.loggedIn) {\n if (usages[id] === undefined && usageErrors[id] === undefined) void loadUsage(id)\n } else if (usages[id] !== undefined || usageErrors[id] !== undefined) {\n setUsages((prev) => {\n const next = { ...prev }\n delete next[id]\n return next\n })\n setUsageErrors((prev) => {\n const next = { ...prev }\n delete next[id]\n return next\n })\n }\n }\n }, [statuses, usages, usageErrors, loadUsage])\n\n const login = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n setProviderError(provider, undefined)\n try {\n const response = await callSubscriptionsAuth<LoginResponse>(rpc, 'login', { provider })\n if (typeof response.authorizeUrl !== 'string' || response.authorizeUrl === '') {\n throw new SubscriptionsAuthError(t('loginMissingUrl'))\n }\n window.open(response.authorizeUrl, '_blank', 'noopener')\n if (!mountedRef.current) return\n // Optimistic busy so Cancel and the manual fallback appear before the first poll tick.\n setStatuses(prev => ({ ...prev, [provider]: { ...prev[provider], busy: true, loggedIn: false } }))\n startPolling(provider)\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n }, [rpc, t, setProviderError, startPolling])\n\n const cancel = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n stopPolling(provider)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'cancel', { provider })\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, stopPolling, setProviderError, refresh])\n\n const submitManual = useCallback(async (provider: SubscriptionProvider): Promise<void> => {\n if (rpc === undefined) return\n const input = manualDrafts[provider].trim()\n if (input === '') return\n setProviderError(provider, undefined)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'manual', { provider, input })\n if (mountedRef.current) setManualDrafts(prev => ({ ...prev, [provider]: '' }))\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, manualDrafts, setProviderError, refresh])\n\n const logout = useCallback(async (provider: SubscriptionProvider, name: string): Promise<void> => {\n if (rpc === undefined) return\n if (!window.confirm(t('logoutConfirm', { provider: name }))) return\n setProviderError(provider, undefined)\n try {\n await callSubscriptionsAuth<{ ok: true }>(rpc, 'logout', { provider })\n } catch (error) {\n setProviderError(provider, messageOf(error))\n }\n await refresh()\n }, [rpc, t, setProviderError, refresh])\n\n if (rpc === undefined) {\n return <p style={styles.intro}>{t('unavailable')}</p>\n }\n\n return (\n <div style={styles.section}>\n <p style={styles.intro}>{t('intro')}</p>\n {PROVIDERS.map(({ id, name }) => {\n const status = statuses[id]\n const busy = status?.busy === true\n const usage = usages[id]\n const usageError = usageErrors[id]\n // Providers without a usage endpoint answer supported:false — no block.\n const showUsage = status?.loggedIn === true && usage?.supported !== false\n && (usage !== undefined || usageError !== undefined || usageLoading[id] === true)\n return (\n <div key={id} style={styles.card}>\n <div style={styles.cardHeader}>\n <span style={{ ...styles.dot, background: dotColor(status) }} />\n <span style={styles.name}>{name}</span>\n </div>\n <p style={styles.statusLine}>{statusText(t, status)}</p>\n {status?.detail !== undefined && status.detail !== '' && (\n <p style={styles.statusLine}>{status.detail}</p>\n )}\n {errors[id] !== undefined && <p style={styles.errorLine}>{errors[id]}</p>}\n <div style={styles.actions}>\n {!busy && status?.loggedIn !== true && (\n <button type=\"button\" style={styles.button} onClick={() => { void login(id) }}>\n {t('login')}\n </button>\n )}\n {busy && (\n <button type=\"button\" style={styles.button} onClick={() => { void cancel(id) }}>\n {t('cancel')}\n </button>\n )}\n {status?.loggedIn === true && (\n <button type=\"button\" style={styles.button} onClick={() => { void logout(id, name) }}>\n {t('logout')}\n </button>\n )}\n </div>\n {showUsage && (\n <div style={styles.usage}>\n <div style={styles.usageHeader}>\n <span style={styles.usageTitle}>{t('usageTitle')}</span>\n {usage?.plan !== undefined && (\n <span style={styles.usagePlan}>{t('usagePlan', { plan: usage.plan })}</span>\n )}\n <button\n type=\"button\"\n style={{ ...styles.usageRefresh, ...usageLoading[id] === true ? { opacity: 0.5, cursor: 'default' } : {} }}\n disabled={usageLoading[id] === true}\n onClick={() => { void loadUsage(id) }}\n >\n {t('usageRefresh')}\n </button>\n </div>\n {usage === undefined && usageError === undefined && (\n <p style={styles.statusLine}>{t('usageLoading')}</p>\n )}\n {usageError !== undefined && (\n <p style={styles.errorLine}>{t('usageError', { message: usageError })}</p>\n )}\n {usage?.windows !== undefined && usage.windows.length === 0 && (\n <p style={styles.statusLine}>{t('usageEmpty')}</p>\n )}\n {(usage?.windows ?? []).map((window, index) => {\n const percent = Math.min(100, Math.max(0, window.usedPercent))\n return (\n <div key={index} style={styles.usageRow}>\n <div style={styles.usageMeta}>\n <span>{usageWindowLabel(t, window)}</span>\n <span>\n {`${String(Math.round(percent))}%`}\n {window.resetsAt !== undefined\n && ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`}\n </span>\n </div>\n <div style={styles.usageTrack}>\n <div style={{ ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) }} />\n </div>\n </div>\n )\n })}\n </div>\n )}\n {busy && (\n <details style={styles.manual}>\n <summary>{t('manualSummary')}</summary>\n <div style={styles.manualRow}>\n <input\n style={styles.manualInput}\n value={manualDrafts[id]}\n placeholder={t('manualPlaceholder')}\n onChange={event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value }))}\n />\n <button type=\"button\" style={styles.button} onClick={() => { void submitManual(id) }}>\n {t('submit')}\n </button>\n </div>\n </details>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n","/**\n * Keyed toolview for the `image_generate` tool: renders generated images\n * inline in the conversation. The row shows the call's prompt while running\n * and after settling; a settled result with image blocks renders them through\n * the platform ImageGallery, whose bytes load through the node half's\n * `/subscriptions-auth` RPC channel (the durable ImageAttachmentRef is never\n * a fetchable URL on its own). A text-only settled result (degraded route)\n * renders its text; an error result renders the first error line.\n *\n * The 'tool.call.toolview' slot contract is owned by ui-tool\n * (packages/client/ui-tool/src/client/contract/slots.ts), which this package\n * does not resolve; the SlotMap merge and ToolCallOwnerProps below mirror it\n * structurally (same discipline as platform-modules.d.ts).\n */\nimport type { CSSProperties } from 'react'\nimport type { ConnectionHandle, RpcResult } from '@deepseek-ai/dsh-api-remotes/client'\nimport type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'\nimport { IconSparkle16 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { ImageGallery } from '@deepseek-ai/dsh-client-ui-attachment'\nimport type { ImageAttachmentRef, ImageLoader, MessageImageLabels } from '@deepseek-ai/dsh-client-ui-attachment'\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\n/** Logical RPC channel served by the node half of this plugin. */\nconst SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth'\n\n/** Title prompt truncation budget (characters). */\nconst PROMPT_MAX_LENGTH = 60\n\n/** Mirror of ui-tool's ToolCallOwnerProps (see the module header). */\ninterface ToolCallOwnerProps {\n callId: string\n toolName: string\n block: ToolCallBlock\n cwd?: string | undefined\n openFile: (path: string) => void\n inspect?: (() => void) | undefined\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /** Mirror of ui-tool's keyed atomic Tool view declaration (see the module header). */\n 'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ToolCallOwnerProps }\n }\n}\n\n/** Injected dependencies of {@link ImageGenerateToolview} (slot `inject`). */\nexport interface ImageGenerateToolviewInjected {\n /** Session-authorized image URL loader riding the `/subscriptions-auth` channel. */\n load: ImageLoader\n}\n\n/**\n * Props delivered by the toolview outlet: the owner share plus the inject\n * face and the framework locale seat, spread flat.\n */\nexport type ImageGenerateToolviewProps =\n Partial<ToolCallOwnerProps>\n & Partial<ImageGenerateToolviewInjected>\n & { t?: ((key: SubscriptionsKey, params?: Record<string, unknown>) => string) | undefined }\n\n/** `image` endpoint result: the node half owns this shape. */\ninterface ImageEndpointResult {\n mediaType: string\n dataBase64: string\n}\n\n/**\n * Call one `/subscriptions-auth` endpoint and unwrap the business result.\n * @param rpc - Connection RPC caller.\n * @param endpoint - channel-relative endpoint.\n * @param payload - channel-owned request payload.\n * @returns the success value, cast by the caller to the endpoint's shape.\n */\nasync function callSubscriptionsAuth<T>(rpc: ConnectionHandle['rpc'], endpoint: string, payload: unknown): Promise<T> {\n const result: RpcResult<unknown> = await rpc.call(SUBSCRIPTIONS_AUTH_CHANNEL, endpoint, payload)\n if (!result.ok) throw new Error(result.error.message)\n return result.value as T\n}\n\n/**\n * Build the ImageGallery loader over the `image` endpoint.\n * @param rpc - Connection RPC caller.\n * @returns loader resolving an attachment ref to a data URL.\n */\nexport function createImageLoader(rpc: ConnectionHandle['rpc']): ImageLoader {\n // The host validates a full ImageAttachmentRef payload (readImage takes the\n // whole ref), so forward the attachment verbatim.\n return attachment =>\n callSubscriptionsAuth<ImageEndpointResult>(rpc, 'image', { ...attachment })\n .then(result => `data:${result.mediaType};base64,${result.dataBase64}`)\n}\n\n/**\n * English-dictionary fallback for a missing locale seat (standalone renders);\n * the framework always supplies the namespace-bound one.\n * @param key - dictionary key.\n * @param params - `{name}` template params.\n * @returns the template with params substituted.\n */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n let text: string = en[key]\n for (const [name, value] of Object.entries(params ?? {})) {\n text = text.replaceAll(`{${name}}`, String(value))\n }\n return text\n}\n\n/** Extract the prompt from the call's raw args JSON; falls back to the first string value, then the raw line. */\nfunction derivePrompt(argsRaw: string): string {\n let parsed: unknown\n try {\n parsed = JSON.parse(argsRaw)\n } catch {\n // Non-JSON args (mid-stream truncation): fall back to the raw string below.\n parsed = undefined\n }\n let prompt: string | undefined\n if (typeof parsed === 'object' && parsed !== null) {\n const args = parsed as Record<string, unknown>\n if (typeof args.prompt === 'string' && args.prompt !== '') prompt = args.prompt\n else {\n for (const value of Object.values(args)) {\n if (typeof value === 'string' && value !== '') { prompt = value; break }\n }\n }\n }\n const line = (prompt ?? argsRaw).split('\\n', 1)[0] ?? ''\n return line.length > PROMPT_MAX_LENGTH ? `${line.slice(0, PROMPT_MAX_LENGTH)}…` : line\n}\n\n/** Flatten a settled result's text blocks (the degraded text-only route and the error line). */\nfunction resultText(block: ToolCallBlock): string {\n if (!('kind' in block)) return ''\n const parts: string[] = []\n for (const part of block.content) {\n if (part.type === 'text') parts.push(part.text)\n }\n if (parts.length === 0 && block.error !== undefined) parts.push(`${block.error.name}: ${block.error.code}`)\n return parts.join('\\n')\n}\n\n/** Image attachments of a settled result; empty while running or on the text-only route. */\nfunction resultImages(block: ToolCallBlock): { attachment: ImageAttachmentRef }[] {\n if (!('kind' in block)) return []\n const images: { attachment: ImageAttachmentRef }[] = []\n for (const part of block.content) {\n if (part.type === 'image') images.push({ attachment: part.attachment as ImageAttachmentRef })\n }\n return images\n}\n\nconst styles: Record<string, CSSProperties> = {\n container: { display: 'flex', flexDirection: 'column', gap: 6, padding: '4px 0' },\n row: { display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 },\n icon: { display: 'inline-flex', flexShrink: 0, color: 'var(--dsw-alias-label-tertiary)' },\n title: {\n fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)',\n overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',\n },\n subtle: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n output: {\n margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',\n whiteSpace: 'pre-wrap', overflowWrap: 'anywhere',\n },\n error: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n}\n\n/**\n * The `image_generate` keyed toolview component.\n * @param props - owner share, inject face, and locale seat (spread flat).\n * @returns the call row plus, once settled, the gallery / text / error body.\n */\nexport function ImageGenerateToolview(props: ImageGenerateToolviewProps) {\n const { block, load } = props\n const t = props.t ?? fallbackTranslate\n if (block === undefined) return null\n const settled = 'kind' in block\n const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? ''\n const title = `image_generate: ${derivePrompt(argsRaw)}`\n const images = resultImages(block)\n const text = settled ? resultText(block) : ''\n const labels: MessageImageLabels = {\n image: t('image'),\n open: t('viewImage'),\n openNamed: name => t('viewImageNamed', { name }),\n loading: t('imageLoading'),\n loadFailed: t('imageLoadFailed'),\n lightbox: { dialog: t('imagePreview'), close: t('imageClose') },\n }\n return (\n <div style={styles.container}>\n <div style={styles.row}>\n <span style={styles.icon}><IconSparkle16 size={14} /></span>\n <span style={styles.title}>{title}</span>\n </div>\n {!settled && <p style={styles.subtle}>{t('generating')}</p>}\n {settled && block.isError && text !== '' && (\n <p style={styles.error}>{text.split('\\n', 1)[0]}</p>\n )}\n {settled && !block.isError && images.length > 0 && load !== undefined && (\n <ImageGallery images={images} load={load} align=\"start\" labels={labels} />\n )}\n {settled && !block.isError && images.length === 0 && text !== '' && (\n <p style={styles.output}>{text}</p>\n )}\n </div>\n )\n}\n","/**\n * Subscription OAuth login page, browser half. Registers the Subscriptions\n * settings section; every login state fact arrives through the node half's\n * `/subscriptions-auth` RPC channel — this plugin holds no credential state of its\n * own. Section copy rides the client locale service: one 'settings.subscriptions'\n * namespace with zh/en dictionaries, rebound per read so the nav label and\n * page text follow the active locale.\n */\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the shell's SlotMap merge (the 'settings.section' entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// `.js` extension: this package's tsconfig lacks the reference repo's\n// allowImportingTsExtensions/rewriteRelativeImportExtensions pair; under\n// nodenext the .js specifier resolves to the .tsx source (see README note).\nimport { SubscriptionsSection } from './SubscriptionsSection.js'\nimport type { SubscriptionsSectionInjected } from './SubscriptionsSection.js'\nimport { ImageGenerateToolview, createImageLoader } from './ImageGenerateToolview.js'\nimport type { ImageGenerateToolviewInjected } from './ImageGenerateToolview.js'\nimport { en, zh } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\nexport type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js'\nexport type { ImageGenerateToolviewInjected, ImageGenerateToolviewProps } from './ImageGenerateToolview.js'\nexport type { SubscriptionsKey } from './locales.js'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The Subscriptions settings page copy. */\n 'settings.subscriptions': SubscriptionsKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.subscriptions'\n\n/**\n * Required services (cordis fiber inject): `slots` carries the registration\n * seat, `connection` the `/subscriptions-auth` RPC caller, and `locale` the copy\n * dictionaries.\n */\nexport const inject = ['slots', 'connection', 'locale']\n\n/**\n * Register the Subscriptions section once the `settings.section` declaration\n * is on the ledger (the shell's apply order relative to this one is NOT\n * constrained; registration depends on the slot through `slots.inject()`).\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscriptions: copy dictionaries')\n // The client-runtime Context merge types `connection` as the host handle;\n // in the browser shell the same key holds the full client ConnectionHandle.\n const connection = ctx.get('connection') as unknown as ConnectionHandle\n const t = ctx.locale.bind(NS) as SubscriptionsSectionInjected['t']\n const injected = (): SubscriptionsSectionInjected => ({ rpc: connection.rpc, t })\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'subscriptions',\n order: 90,\n // A thunk re-evaluated per read, so the nav label follows the active locale.\n label: () => t('nav'),\n inject: injected,\n }, SubscriptionsSection))\n\n // The image_generate keyed toolview owns how image calls render inline; its\n // gallery bytes ride the same channel through the injected loader. The\n // framework synthesizes the toolview's own `t` seat from `locale: NS`.\n const toolviewInjected = (): ImageGenerateToolviewInjected => ({ load: createImageLoader(connection.rpc) })\n ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({\n name: 'tool.call.toolview',\n key: 'image_generate',\n locale: NS,\n inject: toolviewInjected,\n }, ImageGenerateToolview))\n}\n"],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
10
10
|
import z from '@deepseek-ai/schemastery';
|
|
11
11
|
import type { ProviderId } from './auth/store.js';
|
|
12
12
|
import type { ModelEntry } from './providers/common.js';
|
|
13
|
-
export type { ModelEntry } from './providers/common.js';
|
|
13
|
+
export type { ModelEntry, ProviderUsage, UsageWindow } from './providers/common.js';
|
|
14
14
|
export type { ProviderStatus } from './auth/rpc.js';
|
|
15
15
|
export type { ClaudeSession, CodexSession, GrokSession, ProviderId } from './auth/store.js';
|
|
16
16
|
export declare const name = "dsh-plugin-subscriptions";
|
package/lib/index.js
CHANGED
|
@@ -454,6 +454,7 @@ async function dispatch(controller, endpoint, payload, signal) {
|
|
|
454
454
|
case "logout":
|
|
455
455
|
await controller.logout(readProvider(payload));
|
|
456
456
|
return ok({ ok: true });
|
|
457
|
+
case "usage": return ok(await controller.usage(readProvider(payload), signal));
|
|
457
458
|
case "image": return ok(await controller.readImage(readImageRef(payload), signal));
|
|
458
459
|
default: throw new BadRequest(`unknown /subscriptions-auth endpoint "${endpoint}"`);
|
|
459
460
|
}
|
|
@@ -1323,6 +1324,55 @@ async function refreshCodex(session) {
|
|
|
1323
1324
|
function isCodexPermanentRefreshError(error) {
|
|
1324
1325
|
return error instanceof OAuthEndpointError && error.oauthCode !== void 0 && PERMANENT_REFRESH_CODES.has(error.oauthCode);
|
|
1325
1326
|
}
|
|
1327
|
+
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1328
|
+
/** Map one wham/usage window into a {@link UsageWindow}; undefined when unusable. */
|
|
1329
|
+
function codexUsageWindow(value, kind) {
|
|
1330
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1331
|
+
const window = value;
|
|
1332
|
+
if (typeof window.used_percent !== "number" || !Number.isFinite(window.used_percent)) return void 0;
|
|
1333
|
+
let resetsAt;
|
|
1334
|
+
if (typeof window.reset_at === "number" && window.reset_at > 0) resetsAt = window.reset_at * 1e3;
|
|
1335
|
+
else if (typeof window.reset_after_seconds === "number" && window.reset_after_seconds > 0) resetsAt = Date.now() + window.reset_after_seconds * 1e3;
|
|
1336
|
+
return {
|
|
1337
|
+
kind,
|
|
1338
|
+
usedPercent: window.used_percent,
|
|
1339
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Fetch the codex subscription usage from the ChatGPT backend wham/usage
|
|
1344
|
+
* endpoint (the source of the codex CLI `/status` rate-limit lines). The
|
|
1345
|
+
* primary window is the rolling session (5-hour) lane, the secondary window
|
|
1346
|
+
* the weekly lane; the lookup itself consumes no rate-limit budget.
|
|
1347
|
+
* @param session - the stored session (used as-is; never refreshed here).
|
|
1348
|
+
* @param fetchFn - fetch implementation (injectable for tests).
|
|
1349
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
1350
|
+
* @returns the mapped usage snapshot.
|
|
1351
|
+
*/
|
|
1352
|
+
async function fetchCodexUsage(session, fetchFn = fetch, signal) {
|
|
1353
|
+
const response = await fetchFn(CODEX_USAGE_URL, {
|
|
1354
|
+
headers: {
|
|
1355
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
1356
|
+
"chatgpt-account-id": session.accountId,
|
|
1357
|
+
"originator": "codex_cli_rs",
|
|
1358
|
+
"accept": "application/json",
|
|
1359
|
+
...attributionHeaders()
|
|
1360
|
+
},
|
|
1361
|
+
...signal === void 0 ? {} : { signal }
|
|
1362
|
+
});
|
|
1363
|
+
if (!response.ok) throw await oauthEndpointError(response, "codex usage");
|
|
1364
|
+
const payload = await response.json();
|
|
1365
|
+
const windows = [];
|
|
1366
|
+
const primary = codexUsageWindow(payload.rate_limit?.primary_window, "session");
|
|
1367
|
+
const secondary = codexUsageWindow(payload.rate_limit?.secondary_window, "weekly");
|
|
1368
|
+
if (primary !== void 0) windows.push(primary);
|
|
1369
|
+
if (secondary !== void 0) windows.push(secondary);
|
|
1370
|
+
return {
|
|
1371
|
+
supported: true,
|
|
1372
|
+
windows,
|
|
1373
|
+
...typeof payload.plan_type === "string" && payload.plan_type.length > 0 ? { plan: payload.plan_type } : {}
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1326
1376
|
const CODEX_MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
|
|
1327
1377
|
/**
|
|
1328
1378
|
* Client version sent on the /models catalog request. The backend gates the
|
|
@@ -1400,11 +1450,10 @@ var CodexAdapter = class extends LlmAdapter {
|
|
|
1400
1450
|
}));
|
|
1401
1451
|
}
|
|
1402
1452
|
async listModels(provider) {
|
|
1403
|
-
|
|
1404
|
-
if (session === void 0) return [];
|
|
1453
|
+
if (await this.options.tokens.peek() === void 0) return [];
|
|
1405
1454
|
if (!this.options.discovery) return this.staticModels(provider);
|
|
1406
1455
|
try {
|
|
1407
|
-
return (await this.catalog.get(() => fetchCodexModels(session, this.options.fetchFn))).map((model) => ({
|
|
1456
|
+
return (await this.catalog.get(async () => fetchCodexModels(await this.options.tokens.session(), this.options.fetchFn))).map((model) => ({
|
|
1408
1457
|
provider,
|
|
1409
1458
|
id: model.id,
|
|
1410
1459
|
name: model.name,
|
|
@@ -1412,6 +1461,7 @@ var CodexAdapter = class extends LlmAdapter {
|
|
|
1412
1461
|
inputModalities: CODEX_MODALITIES
|
|
1413
1462
|
}));
|
|
1414
1463
|
} catch (error) {
|
|
1464
|
+
if (error instanceof LlmError && (error.code === "MISSING_CREDENTIAL" || error.code === "INVALID_CREDENTIAL")) return [];
|
|
1415
1465
|
if (error instanceof OAuthEndpointError && error.status === 401) this.catalog.invalidate();
|
|
1416
1466
|
this.options.onWarn?.(`codex model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
1417
1467
|
return this.staticModels(provider);
|
|
@@ -1979,6 +2029,86 @@ async function refreshClaude(session) {
|
|
|
1979
2029
|
function isClaudePermanentRefreshError(error) {
|
|
1980
2030
|
return error instanceof OAuthEndpointError && (error.oauthCode === "invalid_grant" || error.oauthCode === "invalid_token");
|
|
1981
2031
|
}
|
|
2032
|
+
const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
2033
|
+
/** RFC3339 `resets_at` value → epoch ms, or undefined when absent/unparsable. */
|
|
2034
|
+
function claudeResetsAt(value) {
|
|
2035
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
2036
|
+
const parsed = Date.parse(value);
|
|
2037
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2038
|
+
}
|
|
2039
|
+
/** Map one legacy `{utilization, resets_at}` bucket; undefined when null or unusable. */
|
|
2040
|
+
function claudeLegacyWindow(value, kind, scope) {
|
|
2041
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2042
|
+
const bucket = value;
|
|
2043
|
+
if (typeof bucket.utilization !== "number" || !Number.isFinite(bucket.utilization)) return void 0;
|
|
2044
|
+
const resetsAt = claudeResetsAt(bucket.resets_at);
|
|
2045
|
+
return {
|
|
2046
|
+
kind,
|
|
2047
|
+
...scope === void 0 ? {} : { scope },
|
|
2048
|
+
usedPercent: bucket.utilization,
|
|
2049
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
/** Map the modern `limits` array; empty when absent or carrying nothing usable. */
|
|
2053
|
+
function claudeLimitsWindows(value) {
|
|
2054
|
+
if (!Array.isArray(value)) return [];
|
|
2055
|
+
const windows = [];
|
|
2056
|
+
for (const raw of value) {
|
|
2057
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
2058
|
+
const entry = raw;
|
|
2059
|
+
if (typeof entry.percent !== "number" || !Number.isFinite(entry.percent)) continue;
|
|
2060
|
+
const kind = entry.kind === "session" ? "session" : entry.kind === "weekly_all" || entry.kind === "weekly_scoped" ? "weekly" : "other";
|
|
2061
|
+
const scope = entry.scope?.model?.display_name;
|
|
2062
|
+
const resetsAt = claudeResetsAt(entry.resets_at);
|
|
2063
|
+
windows.push({
|
|
2064
|
+
kind,
|
|
2065
|
+
...typeof scope === "string" && scope.length > 0 ? { scope } : {},
|
|
2066
|
+
usedPercent: entry.percent,
|
|
2067
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
return windows;
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Fetch the claude subscription usage from the OAuth usage endpoint (the
|
|
2074
|
+
* source of Claude Code's `/usage` screen). Newer responses carry a
|
|
2075
|
+
* structured `limits` array; older ones the flat `five_hour`/`seven_day*`
|
|
2076
|
+
* buckets — both shapes are read, the array winning when it has entries.
|
|
2077
|
+
* @param session - the stored session (used as-is; never refreshed here).
|
|
2078
|
+
* @param fetchFn - fetch implementation (injectable for tests).
|
|
2079
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
2080
|
+
* @returns the mapped usage snapshot.
|
|
2081
|
+
*/
|
|
2082
|
+
async function fetchClaudeUsage(session, fetchFn = fetch, signal) {
|
|
2083
|
+
const response = await fetchFn(CLAUDE_USAGE_URL, {
|
|
2084
|
+
headers: {
|
|
2085
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
2086
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
2087
|
+
"user-agent": CLAUDE_CLI_USER_AGENT,
|
|
2088
|
+
"accept": "application/json"
|
|
2089
|
+
},
|
|
2090
|
+
...signal === void 0 ? {} : { signal }
|
|
2091
|
+
});
|
|
2092
|
+
if (!response.ok) throw await oauthEndpointError(response, "claude usage");
|
|
2093
|
+
const payload = await response.json();
|
|
2094
|
+
const modern = claudeLimitsWindows(payload.limits);
|
|
2095
|
+
if (modern.length > 0) return {
|
|
2096
|
+
supported: true,
|
|
2097
|
+
windows: modern
|
|
2098
|
+
};
|
|
2099
|
+
const windows = [];
|
|
2100
|
+
const legacy = [
|
|
2101
|
+
claudeLegacyWindow(payload.five_hour, "session"),
|
|
2102
|
+
claudeLegacyWindow(payload.seven_day, "weekly"),
|
|
2103
|
+
claudeLegacyWindow(payload.seven_day_opus, "weekly", "Opus"),
|
|
2104
|
+
claudeLegacyWindow(payload.seven_day_sonnet, "weekly", "Sonnet")
|
|
2105
|
+
];
|
|
2106
|
+
for (const window of legacy) if (window !== void 0) windows.push(window);
|
|
2107
|
+
return {
|
|
2108
|
+
supported: true,
|
|
2109
|
+
windows
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
1982
2112
|
/** The Claude 4.5 family accepts image input. */
|
|
1983
2113
|
const CLAUDE_MODALITIES = ["text", "image"];
|
|
1984
2114
|
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
@@ -2131,6 +2261,33 @@ async function grokFlow() {
|
|
|
2131
2261
|
}
|
|
2132
2262
|
};
|
|
2133
2263
|
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Display names for the numeric `tier` claim xAI stamps on OAuth access
|
|
2266
|
+
* tokens (the `prod_auth.SubscriptionTier` proto enum; the mapping mirrors
|
|
2267
|
+
* grok-build's `jwt_tier_claim`). Unknown values fall through to the raw
|
|
2268
|
+
* number so a future tier still shows something.
|
|
2269
|
+
*/
|
|
2270
|
+
const GROK_TIER_NAMES = {
|
|
2271
|
+
0: "Free",
|
|
2272
|
+
1: "SuperGrok",
|
|
2273
|
+
2: "X Basic",
|
|
2274
|
+
3: "X Premium",
|
|
2275
|
+
4: "X Premium+",
|
|
2276
|
+
5: "SuperGrok Heavy",
|
|
2277
|
+
6: "SuperGrok Lite",
|
|
2278
|
+
7: "SuperGrok Plus"
|
|
2279
|
+
};
|
|
2280
|
+
/**
|
|
2281
|
+
* The subscription tier encoded in a grok access token's `tier` claim (no
|
|
2282
|
+
* verification — same trust posture as the other claim reads).
|
|
2283
|
+
* @param accessToken - the stored access token.
|
|
2284
|
+
* @returns the display tier name, or undefined when the claim is absent.
|
|
2285
|
+
*/
|
|
2286
|
+
function grokTierName(accessToken) {
|
|
2287
|
+
const tier = decodeJwtPayload(accessToken)?.tier;
|
|
2288
|
+
if (typeof tier !== "number" || !Number.isInteger(tier)) return void 0;
|
|
2289
|
+
return GROK_TIER_NAMES[tier] ?? String(tier);
|
|
2290
|
+
}
|
|
2134
2291
|
/** Pick a display account from an id token's claims. */
|
|
2135
2292
|
function grokAccount(idToken) {
|
|
2136
2293
|
const payload = idToken === void 0 ? void 0 : decodeJwtPayload(idToken);
|
|
@@ -2213,6 +2370,66 @@ async function refreshGrok(session) {
|
|
|
2213
2370
|
function isGrokPermanentRefreshError(error) {
|
|
2214
2371
|
return error instanceof OAuthEndpointError && error.oauthCode === "invalid_grant";
|
|
2215
2372
|
}
|
|
2373
|
+
/**
|
|
2374
|
+
* The Grok Build CLI chat proxy's billing endpoint (the source of the CLI's
|
|
2375
|
+
* `/usage` "Usage limit" panel; see xai-org/grok-build
|
|
2376
|
+
* `extensions/billing.rs`). Forwards to the backend `GetGrokCreditsConfig`.
|
|
2377
|
+
*/
|
|
2378
|
+
const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
2379
|
+
/** RFC3339 timestamp → epoch ms, or undefined when absent/unparsable. */
|
|
2380
|
+
function grokResetsAt(value) {
|
|
2381
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
2382
|
+
const parsed = Date.parse(value);
|
|
2383
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Fetch the grok subscription usage from the Grok Build CLI chat proxy. The
|
|
2387
|
+
* newer credits config carries a ready-made percentage plus the current
|
|
2388
|
+
* (typically weekly) period; the legacy shape carries cent-valued
|
|
2389
|
+
* `monthlyLimit`/`used`, from which the percentage is derived.
|
|
2390
|
+
* @param session - the stored session (used as-is; never refreshed here).
|
|
2391
|
+
* @param fetchFn - fetch implementation (injectable for tests).
|
|
2392
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
2393
|
+
* @returns the mapped usage snapshot.
|
|
2394
|
+
*/
|
|
2395
|
+
async function fetchGrokUsage(session, fetchFn = fetch, signal) {
|
|
2396
|
+
const response = await fetchFn(GROK_BILLING_URL, {
|
|
2397
|
+
headers: {
|
|
2398
|
+
"authorization": `Bearer ${session.accessToken}`,
|
|
2399
|
+
"x-xai-token-auth": "xai-grok-cli",
|
|
2400
|
+
"accept": "application/json",
|
|
2401
|
+
...attributionHeaders()
|
|
2402
|
+
},
|
|
2403
|
+
...signal === void 0 ? {} : { signal }
|
|
2404
|
+
});
|
|
2405
|
+
if (!response.ok) throw await oauthEndpointError(response, "grok billing");
|
|
2406
|
+
const payload = await response.json();
|
|
2407
|
+
const config = typeof payload.config === "object" && payload.config !== null ? payload.config : {};
|
|
2408
|
+
const windows = [];
|
|
2409
|
+
if (typeof config.creditUsagePercent === "number" && Number.isFinite(config.creditUsagePercent)) {
|
|
2410
|
+
const kind = config.currentPeriod?.type === "USAGE_PERIOD_TYPE_WEEKLY" ? "weekly" : "other";
|
|
2411
|
+
const resetsAt = grokResetsAt(config.currentPeriod?.end);
|
|
2412
|
+
windows.push({
|
|
2413
|
+
kind,
|
|
2414
|
+
usedPercent: config.creditUsagePercent,
|
|
2415
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
2416
|
+
});
|
|
2417
|
+
} else if (typeof config.monthlyLimit?.val === "number" && config.monthlyLimit.val > 0) {
|
|
2418
|
+
const used = typeof config.used?.val === "number" ? config.used.val : 0;
|
|
2419
|
+
const resetsAt = grokResetsAt(config.billingPeriodEnd);
|
|
2420
|
+
windows.push({
|
|
2421
|
+
kind: "other",
|
|
2422
|
+
usedPercent: used / config.monthlyLimit.val * 100,
|
|
2423
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
const plan = typeof payload.subscriptionTier === "string" && payload.subscriptionTier.length > 0 ? payload.subscriptionTier : grokTierName(session.accessToken);
|
|
2427
|
+
return {
|
|
2428
|
+
supported: true,
|
|
2429
|
+
windows,
|
|
2430
|
+
...plan === void 0 ? {} : { plan }
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2216
2433
|
const GROK_MODELS_URL = "https://api.x.ai/v1/models";
|
|
2217
2434
|
/**
|
|
2218
2435
|
* Input modalities for one grok model: chat models (grok-4 family) accept
|
|
@@ -2281,17 +2498,17 @@ var GrokAdapter = class extends LlmAdapter {
|
|
|
2281
2498
|
}));
|
|
2282
2499
|
}
|
|
2283
2500
|
async listModels(provider) {
|
|
2284
|
-
|
|
2285
|
-
if (session === void 0) return [];
|
|
2501
|
+
if (await this.options.tokens.peek() === void 0) return [];
|
|
2286
2502
|
if (!this.options.discovery) return this.staticModels(provider);
|
|
2287
2503
|
try {
|
|
2288
|
-
return (await this.catalog.get(() => fetchGrokModels(session, this.options.fetchFn))).map((model) => ({
|
|
2504
|
+
return (await this.catalog.get(async () => fetchGrokModels(await this.options.tokens.session(), this.options.fetchFn))).map((model) => ({
|
|
2289
2505
|
provider,
|
|
2290
2506
|
id: model.id,
|
|
2291
2507
|
name: model.name,
|
|
2292
2508
|
inputModalities: grokModalities(model.id)
|
|
2293
2509
|
}));
|
|
2294
2510
|
} catch (error) {
|
|
2511
|
+
if (error instanceof LlmError && (error.code === "MISSING_CREDENTIAL" || error.code === "INVALID_CREDENTIAL")) return [];
|
|
2295
2512
|
if (error instanceof OAuthEndpointError && error.status === 401) this.catalog.invalidate();
|
|
2296
2513
|
this.options.onWarn?.(`grok model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
2297
2514
|
return this.staticModels(provider);
|
|
@@ -2893,29 +3110,24 @@ function accountOf(provider, session) {
|
|
|
2893
3110
|
case "grok": return session.account;
|
|
2894
3111
|
}
|
|
2895
3112
|
}
|
|
2896
|
-
/** The subscription detail of a stored session (plan type), for the status endpoint. */
|
|
2897
|
-
function planOf(provider, session) {
|
|
2898
|
-
if (session === void 0) return void 0;
|
|
2899
|
-
switch (provider) {
|
|
2900
|
-
case "codex": {
|
|
2901
|
-
const codex = session;
|
|
2902
|
-
return codex.planType ?? codexProfileClaims(codex.idToken).planType;
|
|
2903
|
-
}
|
|
2904
|
-
case "claude": return session.subscriptionType;
|
|
2905
|
-
case "grok": return;
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
3113
|
/**
|
|
2909
3114
|
* Auth operations behind the `/subscriptions-auth` RPC channel: start/complete
|
|
2910
|
-
* OAuth attempts in the background, feed pasted codes, cancel,
|
|
3115
|
+
* OAuth attempts in the background, feed pasted codes, cancel, log out, and
|
|
3116
|
+
* answer usage lookups.
|
|
2911
3117
|
*/
|
|
2912
3118
|
var SubscriptionsAuthController = class {
|
|
2913
3119
|
/** Last login failure per provider, surfaced as `detail` until the next success. */
|
|
2914
3120
|
lastError = /* @__PURE__ */ new Map();
|
|
2915
|
-
constructor(flows, onAuthChanged, resolveAttachments) {
|
|
3121
|
+
constructor(flows, onAuthChanged, resolveAttachments, usageFetchers = {}) {
|
|
2916
3122
|
this.flows = flows;
|
|
2917
3123
|
this.onAuthChanged = onAuthChanged;
|
|
2918
3124
|
this.resolveAttachments = resolveAttachments;
|
|
3125
|
+
this.usageFetchers = usageFetchers;
|
|
3126
|
+
}
|
|
3127
|
+
usage(provider, signal) {
|
|
3128
|
+
const fetcher = this.usageFetchers[provider];
|
|
3129
|
+
if (fetcher === void 0) return Promise.resolve({ supported: false });
|
|
3130
|
+
return fetcher(signal);
|
|
2919
3131
|
}
|
|
2920
3132
|
async readImage(ref, signal) {
|
|
2921
3133
|
const attachments = this.resolveAttachments();
|
|
@@ -2929,7 +3141,7 @@ var SubscriptionsAuthController = class {
|
|
|
2929
3141
|
async status(provider) {
|
|
2930
3142
|
const session = await getSession(provider);
|
|
2931
3143
|
const account = accountOf(provider, session);
|
|
2932
|
-
const detail = this.lastError.get(provider)
|
|
3144
|
+
const detail = this.lastError.get(provider);
|
|
2933
3145
|
return {
|
|
2934
3146
|
loggedIn: session !== void 0,
|
|
2935
3147
|
busy: this.flows.isBusy(provider),
|
|
@@ -3004,6 +3216,7 @@ function apply(ctx, config) {
|
|
|
3004
3216
|
};
|
|
3005
3217
|
let codexTokens;
|
|
3006
3218
|
let grokTokens;
|
|
3219
|
+
const usageFetchers = {};
|
|
3007
3220
|
for (const provider of providers) switch (provider) {
|
|
3008
3221
|
case "codex": {
|
|
3009
3222
|
const tokens = new TokenManager({
|
|
@@ -3019,6 +3232,7 @@ function apply(ctx, config) {
|
|
|
3019
3232
|
}
|
|
3020
3233
|
});
|
|
3021
3234
|
codexTokens = tokens;
|
|
3235
|
+
usageFetchers.codex = async (signal) => fetchCodexUsage(await tokens.session(), fetch, signal);
|
|
3022
3236
|
handles.set("codex", ctx.llm.registerAdapter(["codex"], new CodexAdapter({
|
|
3023
3237
|
models: catalog.codex,
|
|
3024
3238
|
streamIdleTimeoutMs,
|
|
@@ -3042,6 +3256,7 @@ function apply(ctx, config) {
|
|
|
3042
3256
|
authChanged("claude");
|
|
3043
3257
|
}
|
|
3044
3258
|
});
|
|
3259
|
+
usageFetchers.claude = async (signal) => fetchClaudeUsage(await tokens.session(), fetch, signal);
|
|
3045
3260
|
handles.set("claude", ctx.llm.registerAdapter(["claude"], new ClaudeAdapter({
|
|
3046
3261
|
models: catalog.claude,
|
|
3047
3262
|
streamIdleTimeoutMs,
|
|
@@ -3064,6 +3279,7 @@ function apply(ctx, config) {
|
|
|
3064
3279
|
}
|
|
3065
3280
|
});
|
|
3066
3281
|
grokTokens = tokens;
|
|
3282
|
+
usageFetchers.grok = async (signal) => fetchGrokUsage(await tokens.session(), fetch, signal);
|
|
3067
3283
|
handles.set("grok", ctx.llm.registerAdapter(["grok"], new GrokAdapter({
|
|
3068
3284
|
models: catalog.grok,
|
|
3069
3285
|
streamIdleTimeoutMs,
|
|
@@ -3075,7 +3291,7 @@ function apply(ctx, config) {
|
|
|
3075
3291
|
break;
|
|
3076
3292
|
}
|
|
3077
3293
|
}
|
|
3078
|
-
registerAuthRpc(ctx, new SubscriptionsAuthController(flows, authChanged, resolveAttachments));
|
|
3294
|
+
registerAuthRpc(ctx, new SubscriptionsAuthController(flows, authChanged, resolveAttachments, usageFetchers));
|
|
3079
3295
|
ctx.inject(["tools"], (toolsCtx) => {
|
|
3080
3296
|
if (grokTokens !== void 0) toolsCtx.tools.register(createXSearchTool({ tokens: grokTokens }));
|
|
3081
3297
|
if (codexTokens !== void 0) toolsCtx.tools.register(createImageGenerateTool({
|