zidane 6.3.5 → 6.3.7
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/dist/{acp-IgXPptYr.js → acp-CAX3tOEY.js} +4 -4
- package/dist/{acp-IgXPptYr.js.map → acp-CAX3tOEY.js.map} +1 -1
- package/dist/acp-cli.js +6 -6
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{agent-BJuiohV_.js → agent-Emjeo5Ad.js} +236 -23
- package/dist/{agent-BJuiohV_.js.map → agent-Emjeo5Ad.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-Drd3842n.js → anthropic-BN0iPMox.js} +20 -5
- package/dist/anthropic-BN0iPMox.js.map +1 -0
- package/dist/{auth-WkdClN2Y.js → auth-Dptb8U5J.js} +10 -5
- package/dist/auth-Dptb8U5J.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat.d.ts +50 -6
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +4 -4
- package/dist/{context-transport-DAjjzK_x.js → context-transport-3b0qpM2m.js} +3 -3
- package/dist/{context-transport-DAjjzK_x.js.map → context-transport-3b0qpM2m.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts-DWVt7MvF.js.map +1 -1
- package/dist/contexts.d.ts +3 -3
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +3 -3
- package/dist/{index-B9jSo84a.d.ts → index-C8TwH34B.d.ts} +2 -2
- package/dist/index-C8TwH34B.d.ts.map +1 -0
- package/dist/{index-D-DPqXxU.d.ts → index-DTIe4e1R.d.ts} +268 -10
- package/dist/index-DTIe4e1R.d.ts.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -9
- package/dist/{login-ClSY8pN6.js → login-C41DcRK-.js} +2 -2
- package/dist/{login-ClSY8pN6.js.map → login-C41DcRK-.js.map} +1 -1
- package/dist/{mcp-UIBc81Zm.js → mcp-8bEHU4WB.js} +3 -3
- package/dist/{mcp-UIBc81Zm.js.map → mcp-8bEHU4WB.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/{messages-B-Zt-_Oz.js → messages-DoHRwMw-.js} +2 -2
- package/dist/{messages-B-Zt-_Oz.js.map → messages-DoHRwMw-.js.map} +1 -1
- package/dist/{openai-compat-BF1pSB_F.js → openai-compat-Bf9uMTMa.js} +60 -3
- package/dist/openai-compat-Bf9uMTMa.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +4 -2
- package/dist/providers/baseten.js.map +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +2 -2
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +3 -1
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-C-lh3sVD.js → providers-BlaXBUwd.js} +3 -3
- package/dist/{providers-C-lh3sVD.js.map → providers-BlaXBUwd.js.map} +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +5 -5
- package/dist/restate.d.ts +2 -2
- package/dist/{sandbox-DqwpKX18.d.ts → sandbox-DLY6uah7.d.ts} +2 -2
- package/dist/{sandbox-DqwpKX18.d.ts.map → sandbox-DLY6uah7.d.ts.map} +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-BE9_p0sN.js → session-Bdk31kA5.js} +2 -2
- package/dist/{session-BE9_p0sN.js.map → session-Bdk31kA5.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +1 -1
- package/dist/{stdio-e45MhlYy.js → stdio-BKFpPNvX.js} +2 -2
- package/dist/{stdio-e45MhlYy.js.map → stdio-BKFpPNvX.js.map} +1 -1
- package/dist/{stdio-loader-Bkrs9e8t.js → stdio-loader-DFDpBwwI.js} +2 -2
- package/dist/{stdio-loader-Bkrs9e8t.js.map → stdio-loader-DFDpBwwI.js.map} +1 -1
- package/dist/{tool-formatters-BX9TaWAr.d.ts → tool-formatters-B_dGfYqT.d.ts} +2 -2
- package/dist/{tool-formatters-BX9TaWAr.d.ts.map → tool-formatters-B_dGfYqT.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-BpCQElT6.d.ts → transcript-anchors-CqxIlrhv.d.ts} +15 -6
- package/dist/transcript-anchors-CqxIlrhv.d.ts.map +1 -0
- package/dist/{transcript-anchors-9LKsM0OZ.js → transcript-anchors-DhBTqTyc.js} +146 -32
- package/dist/transcript-anchors-DhBTqTyc.js.map +1 -0
- package/dist/tui.d.ts +3 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +350 -132
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-C_bn9UmE.d.ts → turn-operations-D7JhuW_X.d.ts} +3 -3
- package/dist/{turn-operations-C_bn9UmE.d.ts.map → turn-operations-D7JhuW_X.d.ts.map} +1 -1
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/{types-DdZMQxfT.d.ts → types-M2DauK2D.d.ts} +2 -2
- package/dist/{types-DdZMQxfT.d.ts.map → types-M2DauK2D.d.ts.map} +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/{xai-CLqZQdcj.js → xai-BI4KgCGS.js} +2 -2
- package/dist/{xai-CLqZQdcj.js.map → xai-BI4KgCGS.js.map} +1 -1
- package/docs/ARCHITECTURE.md +29 -1
- package/docs/CHAT.md +4 -2
- package/docs/RUN_IN_BACKGROUND.md +2 -2
- package/docs/SKILL.md +1 -1
- package/docs/TUI.md +3 -3
- package/package.json +1 -1
- package/dist/anthropic-Drd3842n.js.map +0 -1
- package/dist/auth-WkdClN2Y.js.map +0 -1
- package/dist/index-B9jSo84a.d.ts.map +0 -1
- package/dist/index-D-DPqXxU.d.ts.map +0 -1
- package/dist/openai-compat-BF1pSB_F.js.map +0 -1
- package/dist/transcript-anchors-9LKsM0OZ.js.map +0 -1
- package/dist/transcript-anchors-BpCQElT6.d.ts.map +0 -1
|
@@ -317,7 +317,7 @@ interface ExecutionContext {
|
|
|
317
317
|
/**
|
|
318
318
|
* Absolute directory the context appends `<task-id>.log` to. The
|
|
319
319
|
* agent owns this path because it carries session-shaped knowledge
|
|
320
|
-
* (`<userDir
|
|
320
|
+
* (`<userDir>/tasks/<sessionId>/`) the context can't synthesize.
|
|
321
321
|
* Must already exist OR be creatable by the context — the
|
|
322
322
|
* implementation handles `mkdir -p` defensively.
|
|
323
323
|
*/
|
|
@@ -675,4 +675,4 @@ interface TaskStallInfo {
|
|
|
675
675
|
}
|
|
676
676
|
//#endregion
|
|
677
677
|
export { TaskHandle as _, ContextType as a, ExecutionContext as c, ListFilesEntry as d, ListFilesOptions as f, TaskExitInfo as g, TaskEntry as h, ContextMount as i, ExecutionHandle as l, StreamHandle as m, ContextCapabilities as n, DetachedTasksCapability as o, SpawnConfig as p, ContextHardening as r, ExecResult as s, BackgroundTaskStatus as t, ListFileMetadata as u, TaskStallInfo as v, resolveDetachedTasksCapability as y };
|
|
678
|
-
//# sourceMappingURL=types-
|
|
678
|
+
//# sourceMappingURL=types-M2DauK2D.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-M2DauK2D.d.ts","names":[],"sources":["../src/contexts/types.ts"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as AgentProviderError, c as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, f as matchesContextExceeded, i as AgentContextExceededError, l as ClassifiedError, n as AgentAbortedError, o as AgentToolNotAllowedError, r as AgentBudgetExceededError, u as ClassifiedErrorKind } from "./timeout-8vSIRjzl.js";
|
|
2
|
-
import { _ as TaskHandle, a as ContextType, c as ExecutionContext, g as TaskExitInfo, h as TaskEntry, l as ExecutionHandle, n as ContextCapabilities, o as DetachedTasksCapability, p as SpawnConfig, r as ContextHardening, s as ExecResult, t as BackgroundTaskStatus, v as TaskStallInfo } from "./types-
|
|
3
|
-
import { t as SandboxProvider } from "./sandbox-
|
|
4
|
-
import {
|
|
2
|
+
import { _ as TaskHandle, a as ContextType, c as ExecutionContext, g as TaskExitInfo, h as TaskEntry, l as ExecutionHandle, n as ContextCapabilities, o as DetachedTasksCapability, p as SpawnConfig, r as ContextHardening, s as ExecResult, t as BackgroundTaskStatus, v as TaskStallInfo } from "./types-M2DauK2D.js";
|
|
3
|
+
import { t as SandboxProvider } from "./sandbox-DLY6uah7.js";
|
|
4
|
+
import { Am as ToolOutcome, As as SpawnToolOptions, Bm as TurnFinishReason, C as SuccessfulMutationOutcome, Dm as ThinkingLevel, Fm as ToolResultImageContent, Fp as AgentClock, Gi as AgentHookMap, Gm as ToolContext, Hm as toolOutputBudgetByteLength, Im as ToolResultImageRefContent, Ip as AgentRunOptions, Ki as AgentHooks, Km as ToolDef, Kp as DEFAULT_AGENT_CLOCK, Lm as ToolResultMetadata, Lp as AgentStats, Mm as ToolResultContent, Nm as ToolResultDocumentContent, Pm as ToolResultDocumentRefContent, Pp as AgentBehavior, Qf as CerebrasParams, Qp as McpToolHookContext, Rm as ToolResultTextContent, Rr as InteractionToolOptions, S as MutationValidationResult, Sm as SpawnHookContext, Th as RemoteStoreOptions, Tm as StreamHookContext, Ts as ChildAgent, Uf as OpenAIParams, Um as toolOutputByteLength, Vm as TurnUsage, Vp as ChildRunStats, Wi as Agent, Wm as toolResultToText, Xm as ReadStateMap, Ym as ReadStateEntry, Zp as McpServerConfig, _ as statsByModel, _m as SessionContentBlock, af as StreamCallbacks, am as PromptAudioPart, b as MutationValidationHooksOptions, bm as SessionMessage, cf as ToolCall, ch as SkillsConfig, cm as PromptPart, df as TurnResult, dh as Session, dm as RepeatGuardConfig, fh as SessionData, fm as RepeatGuardToolMatcher, gm as RunHookMap, h as flattenTurns, hh as SessionStore, hm as RetryConfig, if as ProviderCapabilities, ig as McpConnection, ih as SkillConfig, im as ProgressNudgeMessage, jm as ToolResultAudioContent, js as SpawnToolState, km as ToolHookContext, lf as ToolResult, lh as CreateSessionOptions, lm as PromptTextPart, mh as SessionRun, mm as ResolveContentRefBlock, nm as OAuthRefreshHookContext, of as StreamOptions, oh as SkillResource, om as PromptDocumentPart, op as AnthropicParams, p as ModelUsage, pm as ResolveContentRef, qi as AgentOptions, qm as ToolMap, rf as Provider, rm as ProgressNudgeKind, sm as PromptImagePart, t as Preset, uf as ToolSpec, um as PromptVideoPart, v as MutationValidationBatch, vm as SessionEndStatus, vr as ValidationResult, x as MutationValidationMutation, xm as SessionTurn, y as MutationValidationFailure, yf as OpenRouterParams, ym as SessionHookContext, zm as ToolResultVideoContent } from "./index-DTIe4e1R.js";
|
|
5
5
|
export { type Agent, AgentAbortedError, type AgentBehavior, AgentBudgetExceededError, type AgentClock, AgentContextExceededError, type AgentHookMap, type AgentHooks, type AgentOptions, AgentProviderError, type AgentRunOptions, type AgentStats, AgentToolNotAllowedError, type AnthropicParams, type BackgroundTaskStatus, CONTEXT_EXCEEDED_MESSAGE_PATTERNS, type CerebrasParams, type ChildAgent, type ChildRunStats, type ClassifiedError, type ClassifiedErrorKind, type ContextCapabilities, type ContextHardening, type ContextType, type CreateSessionOptions, DEFAULT_AGENT_CLOCK, type DetachedTasksCapability, type ExecResult, type ExecutionContext, type ExecutionHandle, type InteractionToolOptions, type McpConnection, type McpServerConfig, type McpToolHookContext, type ModelUsage, type MutationValidationBatch, type MutationValidationFailure, type MutationValidationHooksOptions, type MutationValidationMutation, type MutationValidationResult, type OAuthRefreshHookContext, type OpenAIParams, type OpenRouterParams, type Preset, type ProgressNudgeKind, type ProgressNudgeMessage, type PromptAudioPart, type PromptDocumentPart, type PromptImagePart, type PromptPart, type PromptTextPart, type PromptVideoPart, type Provider, type ProviderCapabilities, type ReadStateEntry, type ReadStateMap, type RemoteStoreOptions, type RepeatGuardConfig, type RepeatGuardToolMatcher, type ResolveContentRef, type ResolveContentRefBlock, type RetryConfig, type RunHookMap, type SandboxProvider, type Session, type SessionContentBlock, type SessionData, type SessionEndStatus, type SessionHookContext, type SessionMessage, type SessionRun, type SessionStore, type SessionTurn, type SkillConfig, type SkillResource, type SkillsConfig, type SpawnConfig, type SpawnHookContext, type SpawnToolOptions, type SpawnToolState, type StreamCallbacks, type StreamHookContext, type StreamOptions, type SuccessfulMutationOutcome, type TaskEntry, type TaskExitInfo, type TaskHandle, type TaskStallInfo, type ThinkingLevel, type ToolCall, type ToolContext, type ToolDef, type ToolHookContext, type ToolMap, type ToolOutcome, type ToolResult, type ToolResultAudioContent, type ToolResultContent, type ToolResultDocumentContent, type ToolResultDocumentRefContent, type ToolResultImageContent, type ToolResultImageRefContent, type ToolResultMetadata, type ToolResultTextContent, type ToolResultVideoContent, type ToolSpec, type TurnFinishReason, type TurnResult, type TurnUsage, type ValidationResult, flattenTurns, matchesContextExceeded, statsByModel, toolOutputBudgetByteLength, toolOutputByteLength, toolResultToText };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as openaiCompat } from "./openai-compat-
|
|
1
|
+
import { m as openaiCompat } from "./openai-compat-Bf9uMTMa.js";
|
|
2
2
|
import { i as isOAuthAccessToken, r as extractRuntimeCredentials, s as resolveOAuthApiKey } from "./oauth-CuFPhsHY.js";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
//#region src/chat/oauth-page/pkce.ts
|
|
@@ -376,4 +376,4 @@ function xai(params) {
|
|
|
376
376
|
//#endregion
|
|
377
377
|
export { createXaiOAuthProvider as n, generatePkce as r, xai as t };
|
|
378
378
|
|
|
379
|
-
//# sourceMappingURL=xai-
|
|
379
|
+
//# sourceMappingURL=xai-BI4KgCGS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xai-CLqZQdcj.js","names":[],"sources":["../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/xai.ts","../src/providers/xai.ts"],"sourcesContent":["/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * xAI Grok OAuth flow (SuperGrok / X Premium+).\n *\n * xAI is **not** built into pi-ai, so — like Cursor — we implement the flow\n * here and register it with pi-ai's OAuth registry (`registerXaiOAuthProvider`)\n * so `getOAuthApiKey('xai-oauth', …)` resolves to it during lazy token refresh.\n *\n * Auth shape: OAuth 2.0 + PKCE with a loopback callback server, against xAI's\n * OIDC issuer (`https://auth.x.ai`). Differs from the Anthropic / Codex flows\n * (which use the shared `startCallbackServer`) on three points, so it gets its\n * own login impl rather than reusing that primitive:\n *\n * 1. The `redirect_uri` must be `http://127.0.0.1:56121/callback` verbatim\n * (xAI matches the registered loopback IP, not `localhost`).\n * 2. Endpoints come from OIDC discovery, not hard-coded constants.\n * 3. Token exchange + refresh are `application/x-www-form-urlencoded`\n * (the Anthropic flow posts JSON).\n *\n * The callback page still routes through {@link OAuthCallbackPageRenderer} so\n * the post-redirect HTML matches the rest of zidane's themed flows.\n *\n * Flow + constants adapted from the public reference implementations\n * (`stnly/pi-grok`, `BlockedPath/pi-xai-oauth`) and the Hermes Agent docs.\n * xAI's OAuth surface is unofficial/undocumented — re-verify the client id,\n * scopes, and callback port if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n} from '@earendil-works/pi-ai/oauth'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OAuthProviderInterface } from '../../providers/oauth-registry'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const XAI_OAUTH_PROVIDER_ID = 'xai-oauth'\n\nconst DEFAULT_BASE_URL = 'https://api.x.ai/v1'\nconst ISSUER = 'https://auth.x.ai'\nconst DISCOVERY_URL = `${ISSUER}/.well-known/openid-configuration`\n/** Public client id used by the Grok CLI OAuth surface. */\nconst DEFAULT_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'\nconst DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access'\n/** xAI matches the registered loopback IP exactly — `localhost` is rejected. */\nconst DEFAULT_CALLBACK_HOST = '127.0.0.1'\nconst DEFAULT_CALLBACK_PORT = 56121\nconst CALLBACK_PATH = '/callback'\nconst PROVIDER_NAME = 'xAI Grok'\n/** Refresh slightly early so requests don't race expiry. */\nconst REFRESH_SKEW_MS = 120_000\nconst CALLBACK_TIMEOUT_MS = 180_000\n\nfunction xaiOAuthClientId(): string {\n return process.env.PI_XAI_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID\n}\n\nfunction xaiOAuthScope(): string {\n return process.env.PI_XAI_OAUTH_SCOPE || DEFAULT_SCOPE\n}\n\nfunction xaiOAuthCallbackHost(): string {\n return process.env.PI_XAI_OAUTH_CALLBACK_HOST || DEFAULT_CALLBACK_HOST\n}\n\nfunction xaiOAuthCallbackPort(): number {\n const parsed = Number.parseInt(process.env.PI_XAI_OAUTH_CALLBACK_PORT || String(DEFAULT_CALLBACK_PORT), 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CALLBACK_PORT\n}\n\n/** Resolve the xAI API base URL (env override → default). */\nexport function xaiBaseUrl(): string {\n return (process.env.PI_XAI_BASE_URL || process.env.XAI_BASE_URL || DEFAULT_BASE_URL)\n .replace(/\\/+$/, '')\n}\n\ninterface XaiDiscovery {\n authorization_endpoint: string\n token_endpoint: string\n}\n\n/**\n * xAI OAuth credentials. Extends the base `{ access, refresh, expires }` with\n * the resolved `token_endpoint` so refresh doesn't need a second discovery\n * round-trip, plus the cached discovery doc for validation.\n */\nexport interface XaiOAuthCredentials extends OAuthCredentials {\n tokenEndpoint?: string\n discovery?: XaiDiscovery\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = ''\n for (const b of bytes)\n binary += String.fromCharCode(b)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(byteLength = 16): string {\n return base64Url(crypto.getRandomValues(new Uint8Array(byteLength)))\n}\n\n/**\n * Refuse any OIDC endpoint that isn't HTTPS on an xAI origin.\n *\n * The discovery doc is cached long-term in `credentials.json`. A single MITM\n * during initial login could otherwise pin a malicious `token_endpoint` that\n * receives every subsequent refresh token. Validating scheme + host keeps the\n * endpoint on `x.ai` / `*.x.ai`.\n */\nfunction validateEndpoint(value: string, field: string): string {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`xAI OAuth discovery returned an invalid ${field}: ${value}`)\n }\n if (url.protocol !== 'https:')\n throw new Error(`xAI OAuth ${field} must use HTTPS: ${value}`)\n const host = url.hostname.toLowerCase()\n if (host !== 'x.ai' && !host.endsWith('.x.ai'))\n throw new Error(`Refusing non-xAI OAuth ${field}: ${value}`)\n return url.toString()\n}\n\nasync function discover(): Promise<XaiDiscovery> {\n let response: Response\n try {\n response = await fetch(DISCOVERY_URL, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n })\n }\n catch (cause) {\n throw new Error(`xAI OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`)\n }\n if (!response.ok)\n throw new Error(`xAI OIDC discovery returned ${response.status}`)\n\n const payload = await response.json() as Record<string, unknown>\n return {\n authorization_endpoint: validateEndpoint(String(payload.authorization_endpoint ?? ''), 'authorization_endpoint'),\n token_endpoint: validateEndpoint(String(payload.token_endpoint ?? ''), 'token_endpoint'),\n }\n}\n\ninterface CallbackResult {\n code?: string\n state?: string\n error?: string\n errorDescription?: string\n}\n\ninterface XaiCallbackServer {\n redirectUri: string\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>\n close: () => void\n}\n\n/**\n * Start the loopback callback server on `127.0.0.1:56121`. Falls back to an\n * OS-assigned port if 56121 is taken — xAI accepts any loopback port as long\n * as the `redirect_uri` we send on the authorize URL matches the listener.\n */\nasync function startXaiCallbackServer(renderPage: OAuthCallbackPageRenderer): Promise<XaiCallbackServer> {\n const callbackHost = xaiOAuthCallbackHost()\n const callbackPort = xaiOAuthCallbackPort()\n let settle: ((value: CallbackResult) => void) | undefined\n let settled = false\n const callbackPromise = new Promise<CallbackResult>((resolve) => {\n settle = (value) => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n })\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n try {\n // accounts.x.ai may issue a CORS preflight against the loopback listener.\n const origin = req.headers.origin\n if (origin === 'https://accounts.x.ai' || origin === 'https://auth.x.ai') {\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n res.setHeader('Vary', 'Origin')\n }\n if (req.method === 'OPTIONS') {\n res.statusCode = 204\n res.end()\n return\n }\n\n const url = new URL(req.url ?? '/', `http://${callbackHost}`)\n if (url.pathname !== CALLBACK_PATH) {\n res.statusCode = 404\n res.end('Not found')\n return\n }\n\n const result: CallbackResult = {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n error: url.searchParams.get('error') ?? undefined,\n errorDescription: url.searchParams.get('error_description') ?? undefined,\n }\n\n res.statusCode = result.error ? 400 : 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(renderPage(result.error\n ? {\n kind: 'error',\n provider: PROVIDER_NAME,\n message: `${PROVIDER_NAME} authentication did not complete.`,\n details: result.errorDescription ?? result.error,\n }\n : {\n kind: 'success',\n provider: PROVIDER_NAME,\n message: 'xAI authentication completed. You can close this window.',\n }))\n settle?.(result)\n }\n catch {\n res.statusCode = 500\n res.end('Internal error')\n }\n })\n\n const listen = (port: number) => new Promise<number>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, callbackHost, () => {\n server.removeListener('error', reject)\n const addr = server.address()\n resolve(typeof addr === 'object' && addr ? addr.port : port)\n })\n })\n\n let actualPort: number\n try {\n actualPort = await listen(callbackPort)\n }\n catch {\n actualPort = await listen(0)\n }\n\n return {\n redirectUri: `http://${callbackHost}:${actualPort}${CALLBACK_PATH}`,\n waitForCallback: timeoutMs => Promise.race([\n callbackPromise,\n new Promise<CallbackResult>(resolve =>\n setTimeout(\n resolve,\n timeoutMs,\n { error: 'timeout', errorDescription: 'Timed out waiting for the xAI OAuth callback.' },\n )),\n ]),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n }\n}\n\nfunction expiryFromPayload(payload: Record<string, unknown>): number {\n const expiresIn = typeof payload.expires_in === 'number'\n ? payload.expires_in\n : Number(payload.expires_in ?? 3600)\n return Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS\n}\n\nasync function exchangeCode(\n tokenEndpoint: string,\n code: string,\n redirectUri: string,\n verifier: string,\n discovery: XaiDiscovery,\n): Promise<XaiOAuthCredentials> {\n const clientId = xaiOAuthClientId()\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token exchange failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n const refresh = String(payload.refresh_token ?? '')\n if (!access)\n throw new Error('xAI token exchange did not return an access_token.')\n if (!refresh)\n throw new Error('xAI token exchange did not return a refresh_token.')\n\n return { access, refresh, expires: expiryFromPayload(payload), tokenEndpoint, discovery }\n}\n\nexport async function loginXai(\n renderPage: OAuthCallbackPageRenderer,\n callbacks: OAuthLoginCallbacks,\n): Promise<XaiOAuthCredentials> {\n const discovery = await discover()\n const { verifier, challenge } = await generatePkce()\n const state = randomToken()\n const nonce = randomToken()\n const server = await startXaiCallbackServer(renderPage)\n\n try {\n const authUrl = new URL(discovery.authorization_endpoint)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', xaiOAuthClientId())\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', xaiOAuthScope())\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n // Opt into xAI's generic OAuth plan tier (SuperGrok / X Premium+).\n authUrl.searchParams.set('plan', 'generic')\n authUrl.searchParams.set('referrer', 'zidane')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: `Sign in to xAI and approve access. If the browser is on another machine, the callback listener is ${server.redirectUri}.`,\n })\n\n const result = await server.waitForCallback(CALLBACK_TIMEOUT_MS)\n if (result.error)\n throw new Error(result.errorDescription ?? result.error)\n if (result.state !== state)\n throw new Error('xAI OAuth state mismatch — possible CSRF. Please retry.')\n if (!result.code)\n throw new Error('xAI OAuth callback did not include an authorization code.')\n\n callbacks.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeCode(discovery.token_endpoint, result.code, server.redirectUri, verifier, discovery)\n }\n finally {\n server.close()\n }\n}\n\nexport async function refreshXaiToken(credentials: OAuthCredentials): Promise<XaiOAuthCredentials> {\n const xai = credentials as XaiOAuthCredentials\n const clientId = xaiOAuthClientId()\n if (!credentials.refresh)\n throw new Error('xAI credentials are missing a refresh token — re-login required.')\n\n const tokenEndpoint = xai.tokenEndpoint\n ?? xai.discovery?.token_endpoint\n ?? (await discover()).token_endpoint\n validateEndpoint(tokenEndpoint, 'token_endpoint')\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: credentials.refresh,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token refresh failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n if (!access)\n throw new Error('xAI token refresh did not return an access_token.')\n\n return {\n ...xai,\n access,\n // xAI may omit a rotated refresh token — keep the existing one.\n refresh: String(payload.refresh_token ?? credentials.refresh),\n expires: expiryFromPayload(payload),\n tokenEndpoint,\n }\n}\n\n/**\n * Build an xAI `OAuthProviderInterface`. Shape matches Anthropic / Codex /\n * Cursor so it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * `usesCallbackServer: true` — the loopback flow has a real callback server,\n * so the TUI doesn't need to prompt for a manual code paste.\n */\nexport function createXaiOAuthProvider(renderPage: OAuthCallbackPageRenderer): OAuthProviderInterface {\n return {\n id: XAI_OAUTH_PROVIDER_ID,\n name: 'xAI Grok (SuperGrok / X Premium+)',\n usesCallbackServer: true,\n login: callbacks => loginXai(renderPage, callbacks),\n refreshToken: refreshXaiToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, TurnResult } from '.'\nimport type { OAuthParams } from './oauth'\nimport { XAI_OAUTH_PROVIDER_ID, xaiBaseUrl } from '../chat/oauth-page/xai'\nimport { extractRuntimeCredentials, isOAuthAccessToken, resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\nconst DEFAULT_MODEL = 'grok-4.3'\n\n/**\n * xAI Grok provider — supports both a static API key (`XAI_API_KEY`) and the\n * SuperGrok / X Premium+ OAuth subscription.\n *\n * Both auth modes hit the same OpenAI-compatible endpoint (`https://api.x.ai/v1`),\n * so the wire handling is entirely {@link openaiCompat}. The only difference is\n * the bearer:\n *\n * - **API key**: `XAI_API_KEY` (or `params.apiKey`) → used verbatim.\n * - **OAuth**: no static key → {@link resolveOAuthApiKey} resolves + lazily\n * refreshes the stored `xai-oauth` token on every turn.\n *\n * `resolveOAuthApiKey` unifies the two: a real API key in `XAI_API_KEY` is\n * returned as-is, while an OAuth access token (or no env value at all) falls\n * through to the refreshable stored credentials. So this single factory serves\n * both paths without branching at construction time.\n */\nexport interface XaiParams extends OAuthParams {\n defaultModel?: string\n /**\n * Provider capability flags. Grok models are vision-capable; default\n * `{ vision: true, imageInToolResult: false }`. Override for text-only\n * deployments.\n */\n capabilities?: ProviderCapabilities\n /** Extra HTTP headers sent on every request. */\n extraHeaders?: Record<string, string>\n}\n\nexport function xai(params?: XaiParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n const baseURL = xaiBaseUrl()\n const capabilities: ProviderCapabilities = params?.capabilities ?? {\n vision: true,\n imageInToolResult: false,\n }\n const baseCredentials = extractRuntimeCredentials(params)\n let runtimeCredentials = baseCredentials\n\n // Factory-time snapshot for the static `meta.isOAuth` display flag only (the\n // terminal header's `oauth`/`key` badge). The real bearer resolves per turn\n // in `stream`, so this is best-effort, mirroring anthropic's pattern. It's\n // OAuth unless a genuine static API key is present: a `xai-…` key → key mode;\n // runtime OAuth creds, a JWT access token, or no static key (stored creds\n // used) → oauth mode.\n const staticKey = params?.apiKey ?? process.env.XAI_API_KEY\n const isOAuth = baseCredentials !== undefined || !staticKey || isOAuthAccessToken(staticKey)\n\n /**\n * Build the openai-compat delegate for a resolved bearer. Cheap — the factory\n * only wires closures — so we re-create it per turn with the freshly resolved\n * (possibly refreshed) OAuth token rather than baking a stale key in once.\n */\n function delegateFor(apiKey: string): Provider {\n return openaiCompat({\n name: 'xai',\n apiKey,\n baseURL,\n defaultModel,\n capabilities,\n extraHeaders: params?.extraHeaders,\n })\n }\n\n // A delegate built with a placeholder key supplies the non-stream methods\n // (formatTools, userMessage, message builders, classifyError). Only `stream`\n // resolves the real bearer, so the placeholder never reaches the wire.\n const skeleton = delegateFor('placeholder-resolved-at-stream')\n\n return {\n ...skeleton,\n name: 'xai',\n // Reuse the delegate's normalized capability flags (openaiCompat fills the\n // audio/video/documents defaults) so the advertised shape matches the wire.\n meta: { ...skeleton.meta, defaultModel, isOAuth },\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'xai',\n providerId: XAI_OAUTH_PROVIDER_ID,\n params: runtimeCredentials ? { ...params, ...runtimeCredentials } : params,\n envKey: 'XAI_API_KEY',\n missingError: 'No xAI credentials found. Set XAI_API_KEY or run `bun run auth --xai` first.',\n refreshError: reason => `xAI OAuth token refresh failed. Run \\`bun run auth --xai\\` again. ${reason}`,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\n // Keep param-sourced credentials current so a long-lived provider\n // instance reuses the rotated token instead of re-refreshing.\n if (ctx.source === 'params') {\n runtimeCredentials = {\n access: ctx.credentials.access,\n refresh: ctx.credentials.refresh,\n expires: ctx.credentials.expires,\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n\n return delegateFor(apiKey).stream(options, callbacks)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAUA,SAAS,gBAAgB,OAA2B;CAClD,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OACjB,UAAU,OAAO,aAAa,IAAI;CACpC,OAAO,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,MAAM,EAAE;AAC9E;AASA,eAAsB,eAAkC;CACtD,MAAM,gCAAgB,IAAI,WAAW,EAAE;CACvC,OAAO,gBAAgB,aAAa;CACpC,MAAM,WAAW,gBAAgB,aAAa;CAE9C,MAAM,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,QAAQ;CAC9C,MAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;CAG7D,OAAO;EAAE;EAAU,WAFD,gBAAgB,IAAI,WAAW,UAAU,CAEhC;CAAE;AAC/B;;;;ACIA,MAAa,wBAAwB;AAErC,MAAM,mBAAmB;AAEzB,MAAM,gBAAgB;;AAEtB,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;;AAEtB,MAAM,wBAAwB;AAC9B,MAAM,wBAAwB;AAC9B,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;;AAEtB,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAE5B,SAAS,mBAA2B;CAClC,OAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,SAAS,gBAAwB;CAC/B,OAAO,QAAQ,IAAI,sBAAsB;AAC3C;AAEA,SAAS,uBAA+B;CACtC,OAAO,QAAQ,IAAI,8BAA8B;AACnD;AAEA,SAAS,uBAA+B;CACtC,MAAM,SAAS,OAAO,SAAS,QAAQ,IAAI,8BAA8B,OAAO,qBAAqB,GAAG,EAAE;CAC1G,OAAO,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS;AAC1D;;AAGA,SAAgB,aAAqB;CACnC,QAAQ,QAAQ,IAAI,mBAAmB,QAAQ,IAAI,gBAAgB,iBAAA,CAChE,QAAQ,QAAQ,EAAE;AACvB;AAiBA,SAAS,UAAU,OAA2B;CAC5C,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,OACd,UAAU,OAAO,aAAa,CAAC;CACjC,OAAO,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,EAAE;AAC/E;AAEA,SAAS,YAAY,aAAa,IAAY;CAC5C,OAAO,UAAU,OAAO,gBAAgB,IAAI,WAAW,UAAU,CAAC,CAAC;AACrE;;;;;;;;;AAUA,SAAS,iBAAiB,OAAe,OAAuB;CAC9D,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,KAAK;CACrB,QACM;EACJ,MAAM,IAAI,MAAM,2CAA2C,MAAM,IAAI,OAAO;CAC9E;CACA,IAAI,IAAI,aAAa,UACnB,MAAM,IAAI,MAAM,aAAa,MAAM,mBAAmB,OAAO;CAC/D,MAAM,OAAO,IAAI,SAAS,YAAY;CACtC,IAAI,SAAS,UAAU,CAAC,KAAK,SAAS,OAAO,GAC3C,MAAM,IAAI,MAAM,0BAA0B,MAAM,IAAI,OAAO;CAC7D,OAAO,IAAI,SAAS;AACtB;AAEA,eAAe,WAAkC;CAC/C,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,MAAM,eAAe;GACpC,SAAS,EAAE,QAAQ,mBAAmB;GACtC,QAAQ,YAAY,QAAQ,IAAM;EACpC,CAAC;CACH,SACO,OAAO;EACZ,MAAM,IAAI,MAAM,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CACxG;CACA,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,+BAA+B,SAAS,QAAQ;CAElE,MAAM,UAAU,MAAM,SAAS,KAAK;CACpC,OAAO;EACL,wBAAwB,iBAAiB,OAAO,QAAQ,0BAA0B,EAAE,GAAG,wBAAwB;EAC/G,gBAAgB,iBAAiB,OAAO,QAAQ,kBAAkB,EAAE,GAAG,gBAAgB;CACzF;AACF;;;;;;AAoBA,eAAe,uBAAuB,YAAmE;CACvG,MAAM,eAAe,qBAAqB;CAC1C,MAAM,eAAe,qBAAqB;CAC1C,IAAI;CACJ,IAAI,UAAU;CACd,MAAM,kBAAkB,IAAI,SAAyB,YAAY;EAC/D,UAAU,UAAU;GAClB,IAAI,SACF;GACF,UAAU;GACV,QAAQ,KAAK;EACf;CACF,CAAC;CAED,MAAM,SAAS,cAAc,KAAsB,QAAwB;EACzE,IAAI;GAEF,MAAM,SAAS,IAAI,QAAQ;GAC3B,IAAI,WAAW,2BAA2B,WAAW,qBAAqB;IACxE,IAAI,UAAU,+BAA+B,MAAM;IACnD,IAAI,UAAU,gCAAgC,cAAc;IAC5D,IAAI,UAAU,gCAAgC,cAAc;IAC5D,IAAI,UAAU,wCAAwC,MAAM;IAC5D,IAAI,UAAU,QAAQ,QAAQ;GAChC;GACA,IAAI,IAAI,WAAW,WAAW;IAC5B,IAAI,aAAa;IACjB,IAAI,IAAI;IACR;GACF;GAEA,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,cAAc;GAC5D,IAAI,IAAI,aAAa,eAAe;IAClC,IAAI,aAAa;IACjB,IAAI,IAAI,WAAW;IACnB;GACF;GAEA,MAAM,SAAyB;IAC7B,MAAM,IAAI,aAAa,IAAI,MAAM,KAAK,KAAA;IACtC,OAAO,IAAI,aAAa,IAAI,OAAO,KAAK,KAAA;IACxC,OAAO,IAAI,aAAa,IAAI,OAAO,KAAK,KAAA;IACxC,kBAAkB,IAAI,aAAa,IAAI,mBAAmB,KAAK,KAAA;GACjE;GAEA,IAAI,aAAa,OAAO,QAAQ,MAAM;GACtC,IAAI,UAAU,gBAAgB,0BAA0B;GACxD,IAAI,IAAI,WAAW,OAAO,QACtB;IACE,MAAM;IACN,UAAU;IACV,SAAS,GAAG,cAAc;IAC1B,SAAS,OAAO,oBAAoB,OAAO;GAC7C,IACA;IACE,MAAM;IACN,UAAU;IACV,SAAS;GACX,CAAC,CAAC;GACN,SAAS,MAAM;EACjB,QACM;GACJ,IAAI,aAAa;GACjB,IAAI,IAAI,gBAAgB;EAC1B;CACF,CAAC;CAED,MAAM,UAAU,SAAiB,IAAI,SAAiB,SAAS,WAAW;EACxE,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,MAAM,oBAAoB;GACtC,OAAO,eAAe,SAAS,MAAM;GACrC,MAAM,OAAO,OAAO,QAAQ;GAC5B,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,OAAO,IAAI;EAC7D,CAAC;CACH,CAAC;CAED,IAAI;CACJ,IAAI;EACF,aAAa,MAAM,OAAO,YAAY;CACxC,QACM;EACJ,aAAa,MAAM,OAAO,CAAC;CAC7B;CAEA,OAAO;EACL,aAAa,UAAU,aAAa,GAAG,aAAa;EACpD,kBAAiB,cAAa,QAAQ,KAAK,CACzC,iBACA,IAAI,SAAwB,YAC1B,WACE,SACA,WACA;GAAE,OAAO;GAAW,kBAAkB;EAAgD,CACxF,CAAC,CACL,CAAC;EACD,aAAa;GACX,IAAI;IAAE,OAAO,MAAM;GAAE,QACf,CAAuB;EAC/B;CACF;AACF;AAEA,SAAS,kBAAkB,SAA0C;CACnE,MAAM,YAAY,OAAO,QAAQ,eAAe,WAC5C,QAAQ,aACR,OAAO,QAAQ,cAAc,IAAI;CACrC,OAAO,KAAK,IAAI,IAAI,YAAY,MAAO;AACzC;AAEA,eAAe,aACb,eACA,MACA,aACA,UACA,WAC8B;CAC9B,MAAM,WAAW,iBAAiB;CAClC,MAAM,WAAW,MAAM,MAAM,eAAe;EAC1C,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAqC,UAAU;EAAmB;EAC7F,MAAM,IAAI,gBAAgB;GACxB,YAAY;GACZ,WAAW;GACX;GACA,cAAc;GACd,eAAe;EACjB,CAAC;EACD,QAAQ,YAAY,QAAQ,GAAM;CACpC,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,8BAA8B,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE,GAAG;CAE1G,MAAM,UAAU,MAAM,SAAS,KAAK;CACpC,MAAM,SAAS,OAAO,QAAQ,gBAAgB,EAAE;CAChD,MAAM,UAAU,OAAO,QAAQ,iBAAiB,EAAE;CAClD,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,oDAAoD;CACtE,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,oDAAoD;CAEtE,OAAO;EAAE;EAAQ;EAAS,SAAS,kBAAkB,OAAO;EAAG;EAAe;CAAU;AAC1F;AAEA,eAAsB,SACpB,YACA,WAC8B;CAC9B,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,EAAE,UAAU,cAAc,MAAM,aAAa;CACnD,MAAM,QAAQ,YAAY;CAC1B,MAAM,QAAQ,YAAY;CAC1B,MAAM,SAAS,MAAM,uBAAuB,UAAU;CAEtD,IAAI;EACF,MAAM,UAAU,IAAI,IAAI,UAAU,sBAAsB;EACxD,QAAQ,aAAa,IAAI,iBAAiB,MAAM;EAChD,QAAQ,aAAa,IAAI,aAAa,iBAAiB,CAAC;EACxD,QAAQ,aAAa,IAAI,gBAAgB,OAAO,WAAW;EAC3D,QAAQ,aAAa,IAAI,SAAS,cAAc,CAAC;EACjD,QAAQ,aAAa,IAAI,kBAAkB,SAAS;EACpD,QAAQ,aAAa,IAAI,yBAAyB,MAAM;EACxD,QAAQ,aAAa,IAAI,SAAS,KAAK;EACvC,QAAQ,aAAa,IAAI,SAAS,KAAK;EAEvC,QAAQ,aAAa,IAAI,QAAQ,SAAS;EAC1C,QAAQ,aAAa,IAAI,YAAY,QAAQ;EAE7C,UAAU,OAAO;GACf,KAAK,QAAQ,SAAS;GACtB,cAAc,qGAAqG,OAAO,YAAY;EACxI,CAAC;EAED,MAAM,SAAS,MAAM,OAAO,gBAAgB,mBAAmB;EAC/D,IAAI,OAAO,OACT,MAAM,IAAI,MAAM,OAAO,oBAAoB,OAAO,KAAK;EACzD,IAAI,OAAO,UAAU,OACnB,MAAM,IAAI,MAAM,yDAAyD;EAC3E,IAAI,CAAC,OAAO,MACV,MAAM,IAAI,MAAM,2DAA2D;EAE7E,UAAU,aAAa,6CAA6C;EACpE,OAAO,MAAM,aAAa,UAAU,gBAAgB,OAAO,MAAM,OAAO,aAAa,UAAU,SAAS;CAC1G,UACQ;EACN,OAAO,MAAM;CACf;AACF;AAEA,eAAsB,gBAAgB,aAA6D;CACjG,MAAM,MAAM;CACZ,MAAM,WAAW,iBAAiB;CAClC,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,kEAAkE;CAEpF,MAAM,gBAAgB,IAAI,iBACrB,IAAI,WAAW,mBACd,MAAM,SAAS,EAAA,CAAG;CACxB,iBAAiB,eAAe,gBAAgB;CAEhD,MAAM,WAAW,MAAM,MAAM,eAAe;EAC1C,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAqC,UAAU;EAAmB;EAC7F,MAAM,IAAI,gBAAgB;GACxB,YAAY;GACZ,WAAW;GACX,eAAe,YAAY;EAC7B,CAAC;EACD,QAAQ,YAAY,QAAQ,GAAM;CACpC,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,6BAA6B,SAAS,OAAO,GAAG,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE,GAAG;CAEzG,MAAM,UAAU,MAAM,SAAS,KAAK;CACpC,MAAM,SAAS,OAAO,QAAQ,gBAAgB,EAAE;CAChD,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,mDAAmD;CAErE,OAAO;EACL,GAAG;EACH;EAEA,SAAS,OAAO,QAAQ,iBAAiB,YAAY,OAAO;EAC5D,SAAS,kBAAkB,OAAO;EAClC;CACF;AACF;;;;;;;;AASA,SAAgB,uBAAuB,YAA+D;CACpG,OAAO;EACL,IAAI;EACJ,MAAM;EACN,oBAAoB;EACpB,QAAO,cAAa,SAAS,YAAY,SAAS;EAClD,cAAc;EACd,YAAW,gBAAe,YAAY;CACxC;AACF;;;ACpZA,MAAM,gBAAgB;AA+BtB,SAAgB,IAAI,QAA8B;CAChD,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,UAAU,WAAW;CAC3B,MAAM,eAAqC,QAAQ,gBAAgB;EACjE,QAAQ;EACR,mBAAmB;CACrB;CACA,MAAM,kBAAkB,0BAA0B,MAAM;CACxD,IAAI,qBAAqB;CAQzB,MAAM,YAAY,QAAQ,UAAU,QAAQ,IAAI;CAChD,MAAM,UAAU,oBAAoB,KAAA,KAAa,CAAC,aAAa,mBAAmB,SAAS;;;;;;CAO3F,SAAS,YAAY,QAA0B;EAC7C,OAAO,aAAa;GAClB,MAAM;GACN;GACA;GACA;GACA;GACA,cAAc,QAAQ;EACxB,CAAC;CACH;CAKA,MAAM,WAAW,YAAY,gCAAgC;CAE7D,OAAO;EACL,GAAG;EACH,MAAM;EAGN,MAAM;GAAE,GAAG,SAAS;GAAM;GAAc;EAAQ;EAEhD,MAAM,OAAO,SAAwB,WAAiD;GA2BpF,OAAO,YAAY,MA1BE,mBACnB;IACE,UAAU;IACV,YAAY;IACZ,QAAQ,qBAAqB;KAAE,GAAG;KAAQ,GAAG;IAAmB,IAAI;IACpE,QAAQ;IACR,cAAc;IACd,eAAc,WAAU,qEAAqE;GAC/F,GACA;IACE,GAAG;IACH,MAAM,eAAe,KAAK;KAGxB,IAAI,IAAI,WAAW,UACjB,qBAAqB;MACnB,QAAQ,IAAI,YAAY;MACxB,SAAS,IAAI,YAAY;MACzB,SAAS,IAAI,YAAY;KAC3B;KAEF,MAAM,UAAU,iBAAiB,GAAG;IACtC;GACF,CACF,CAEyB,CAAC,CAAC,OAAO,SAAS,SAAS;EACtD;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"xai-BI4KgCGS.js","names":[],"sources":["../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/xai.ts","../src/providers/xai.ts"],"sourcesContent":["/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * xAI Grok OAuth flow (SuperGrok / X Premium+).\n *\n * xAI is **not** built into pi-ai, so — like Cursor — we implement the flow\n * here and register it with pi-ai's OAuth registry (`registerXaiOAuthProvider`)\n * so `getOAuthApiKey('xai-oauth', …)` resolves to it during lazy token refresh.\n *\n * Auth shape: OAuth 2.0 + PKCE with a loopback callback server, against xAI's\n * OIDC issuer (`https://auth.x.ai`). Differs from the Anthropic / Codex flows\n * (which use the shared `startCallbackServer`) on three points, so it gets its\n * own login impl rather than reusing that primitive:\n *\n * 1. The `redirect_uri` must be `http://127.0.0.1:56121/callback` verbatim\n * (xAI matches the registered loopback IP, not `localhost`).\n * 2. Endpoints come from OIDC discovery, not hard-coded constants.\n * 3. Token exchange + refresh are `application/x-www-form-urlencoded`\n * (the Anthropic flow posts JSON).\n *\n * The callback page still routes through {@link OAuthCallbackPageRenderer} so\n * the post-redirect HTML matches the rest of zidane's themed flows.\n *\n * Flow + constants adapted from the public reference implementations\n * (`stnly/pi-grok`, `BlockedPath/pi-xai-oauth`) and the Hermes Agent docs.\n * xAI's OAuth surface is unofficial/undocumented — re-verify the client id,\n * scopes, and callback port if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n} from '@earendil-works/pi-ai/oauth'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { OAuthProviderInterface } from '../../providers/oauth-registry'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const XAI_OAUTH_PROVIDER_ID = 'xai-oauth'\n\nconst DEFAULT_BASE_URL = 'https://api.x.ai/v1'\nconst ISSUER = 'https://auth.x.ai'\nconst DISCOVERY_URL = `${ISSUER}/.well-known/openid-configuration`\n/** Public client id used by the Grok CLI OAuth surface. */\nconst DEFAULT_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828'\nconst DEFAULT_SCOPE = 'openid profile email offline_access grok-cli:access api:access'\n/** xAI matches the registered loopback IP exactly — `localhost` is rejected. */\nconst DEFAULT_CALLBACK_HOST = '127.0.0.1'\nconst DEFAULT_CALLBACK_PORT = 56121\nconst CALLBACK_PATH = '/callback'\nconst PROVIDER_NAME = 'xAI Grok'\n/** Refresh slightly early so requests don't race expiry. */\nconst REFRESH_SKEW_MS = 120_000\nconst CALLBACK_TIMEOUT_MS = 180_000\n\nfunction xaiOAuthClientId(): string {\n return process.env.PI_XAI_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID\n}\n\nfunction xaiOAuthScope(): string {\n return process.env.PI_XAI_OAUTH_SCOPE || DEFAULT_SCOPE\n}\n\nfunction xaiOAuthCallbackHost(): string {\n return process.env.PI_XAI_OAUTH_CALLBACK_HOST || DEFAULT_CALLBACK_HOST\n}\n\nfunction xaiOAuthCallbackPort(): number {\n const parsed = Number.parseInt(process.env.PI_XAI_OAUTH_CALLBACK_PORT || String(DEFAULT_CALLBACK_PORT), 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CALLBACK_PORT\n}\n\n/** Resolve the xAI API base URL (env override → default). */\nexport function xaiBaseUrl(): string {\n return (process.env.PI_XAI_BASE_URL || process.env.XAI_BASE_URL || DEFAULT_BASE_URL)\n .replace(/\\/+$/, '')\n}\n\ninterface XaiDiscovery {\n authorization_endpoint: string\n token_endpoint: string\n}\n\n/**\n * xAI OAuth credentials. Extends the base `{ access, refresh, expires }` with\n * the resolved `token_endpoint` so refresh doesn't need a second discovery\n * round-trip, plus the cached discovery doc for validation.\n */\nexport interface XaiOAuthCredentials extends OAuthCredentials {\n tokenEndpoint?: string\n discovery?: XaiDiscovery\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = ''\n for (const b of bytes)\n binary += String.fromCharCode(b)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction randomToken(byteLength = 16): string {\n return base64Url(crypto.getRandomValues(new Uint8Array(byteLength)))\n}\n\n/**\n * Refuse any OIDC endpoint that isn't HTTPS on an xAI origin.\n *\n * The discovery doc is cached long-term in `credentials.json`. A single MITM\n * during initial login could otherwise pin a malicious `token_endpoint` that\n * receives every subsequent refresh token. Validating scheme + host keeps the\n * endpoint on `x.ai` / `*.x.ai`.\n */\nfunction validateEndpoint(value: string, field: string): string {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`xAI OAuth discovery returned an invalid ${field}: ${value}`)\n }\n if (url.protocol !== 'https:')\n throw new Error(`xAI OAuth ${field} must use HTTPS: ${value}`)\n const host = url.hostname.toLowerCase()\n if (host !== 'x.ai' && !host.endsWith('.x.ai'))\n throw new Error(`Refusing non-xAI OAuth ${field}: ${value}`)\n return url.toString()\n}\n\nasync function discover(): Promise<XaiDiscovery> {\n let response: Response\n try {\n response = await fetch(DISCOVERY_URL, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n })\n }\n catch (cause) {\n throw new Error(`xAI OIDC discovery failed: ${cause instanceof Error ? cause.message : String(cause)}`)\n }\n if (!response.ok)\n throw new Error(`xAI OIDC discovery returned ${response.status}`)\n\n const payload = await response.json() as Record<string, unknown>\n return {\n authorization_endpoint: validateEndpoint(String(payload.authorization_endpoint ?? ''), 'authorization_endpoint'),\n token_endpoint: validateEndpoint(String(payload.token_endpoint ?? ''), 'token_endpoint'),\n }\n}\n\ninterface CallbackResult {\n code?: string\n state?: string\n error?: string\n errorDescription?: string\n}\n\ninterface XaiCallbackServer {\n redirectUri: string\n waitForCallback: (timeoutMs: number) => Promise<CallbackResult>\n close: () => void\n}\n\n/**\n * Start the loopback callback server on `127.0.0.1:56121`. Falls back to an\n * OS-assigned port if 56121 is taken — xAI accepts any loopback port as long\n * as the `redirect_uri` we send on the authorize URL matches the listener.\n */\nasync function startXaiCallbackServer(renderPage: OAuthCallbackPageRenderer): Promise<XaiCallbackServer> {\n const callbackHost = xaiOAuthCallbackHost()\n const callbackPort = xaiOAuthCallbackPort()\n let settle: ((value: CallbackResult) => void) | undefined\n let settled = false\n const callbackPromise = new Promise<CallbackResult>((resolve) => {\n settle = (value) => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n })\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n try {\n // accounts.x.ai may issue a CORS preflight against the loopback listener.\n const origin = req.headers.origin\n if (origin === 'https://accounts.x.ai' || origin === 'https://auth.x.ai') {\n res.setHeader('Access-Control-Allow-Origin', origin)\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n res.setHeader('Access-Control-Allow-Private-Network', 'true')\n res.setHeader('Vary', 'Origin')\n }\n if (req.method === 'OPTIONS') {\n res.statusCode = 204\n res.end()\n return\n }\n\n const url = new URL(req.url ?? '/', `http://${callbackHost}`)\n if (url.pathname !== CALLBACK_PATH) {\n res.statusCode = 404\n res.end('Not found')\n return\n }\n\n const result: CallbackResult = {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n error: url.searchParams.get('error') ?? undefined,\n errorDescription: url.searchParams.get('error_description') ?? undefined,\n }\n\n res.statusCode = result.error ? 400 : 200\n res.setHeader('Content-Type', 'text/html; charset=utf-8')\n res.end(renderPage(result.error\n ? {\n kind: 'error',\n provider: PROVIDER_NAME,\n message: `${PROVIDER_NAME} authentication did not complete.`,\n details: result.errorDescription ?? result.error,\n }\n : {\n kind: 'success',\n provider: PROVIDER_NAME,\n message: 'xAI authentication completed. You can close this window.',\n }))\n settle?.(result)\n }\n catch {\n res.statusCode = 500\n res.end('Internal error')\n }\n })\n\n const listen = (port: number) => new Promise<number>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, callbackHost, () => {\n server.removeListener('error', reject)\n const addr = server.address()\n resolve(typeof addr === 'object' && addr ? addr.port : port)\n })\n })\n\n let actualPort: number\n try {\n actualPort = await listen(callbackPort)\n }\n catch {\n actualPort = await listen(0)\n }\n\n return {\n redirectUri: `http://${callbackHost}:${actualPort}${CALLBACK_PATH}`,\n waitForCallback: timeoutMs => Promise.race([\n callbackPromise,\n new Promise<CallbackResult>(resolve =>\n setTimeout(\n resolve,\n timeoutMs,\n { error: 'timeout', errorDescription: 'Timed out waiting for the xAI OAuth callback.' },\n )),\n ]),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n }\n}\n\nfunction expiryFromPayload(payload: Record<string, unknown>): number {\n const expiresIn = typeof payload.expires_in === 'number'\n ? payload.expires_in\n : Number(payload.expires_in ?? 3600)\n return Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS\n}\n\nasync function exchangeCode(\n tokenEndpoint: string,\n code: string,\n redirectUri: string,\n verifier: string,\n discovery: XaiDiscovery,\n): Promise<XaiOAuthCredentials> {\n const clientId = xaiOAuthClientId()\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token exchange failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n const refresh = String(payload.refresh_token ?? '')\n if (!access)\n throw new Error('xAI token exchange did not return an access_token.')\n if (!refresh)\n throw new Error('xAI token exchange did not return a refresh_token.')\n\n return { access, refresh, expires: expiryFromPayload(payload), tokenEndpoint, discovery }\n}\n\nexport async function loginXai(\n renderPage: OAuthCallbackPageRenderer,\n callbacks: OAuthLoginCallbacks,\n): Promise<XaiOAuthCredentials> {\n const discovery = await discover()\n const { verifier, challenge } = await generatePkce()\n const state = randomToken()\n const nonce = randomToken()\n const server = await startXaiCallbackServer(renderPage)\n\n try {\n const authUrl = new URL(discovery.authorization_endpoint)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', xaiOAuthClientId())\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', xaiOAuthScope())\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('nonce', nonce)\n // Opt into xAI's generic OAuth plan tier (SuperGrok / X Premium+).\n authUrl.searchParams.set('plan', 'generic')\n authUrl.searchParams.set('referrer', 'zidane')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: `Sign in to xAI and approve access. If the browser is on another machine, the callback listener is ${server.redirectUri}.`,\n })\n\n const result = await server.waitForCallback(CALLBACK_TIMEOUT_MS)\n if (result.error)\n throw new Error(result.errorDescription ?? result.error)\n if (result.state !== state)\n throw new Error('xAI OAuth state mismatch — possible CSRF. Please retry.')\n if (!result.code)\n throw new Error('xAI OAuth callback did not include an authorization code.')\n\n callbacks.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeCode(discovery.token_endpoint, result.code, server.redirectUri, verifier, discovery)\n }\n finally {\n server.close()\n }\n}\n\nexport async function refreshXaiToken(credentials: OAuthCredentials): Promise<XaiOAuthCredentials> {\n const xai = credentials as XaiOAuthCredentials\n const clientId = xaiOAuthClientId()\n if (!credentials.refresh)\n throw new Error('xAI credentials are missing a refresh token — re-login required.')\n\n const tokenEndpoint = xai.tokenEndpoint\n ?? xai.discovery?.token_endpoint\n ?? (await discover()).token_endpoint\n validateEndpoint(tokenEndpoint, 'token_endpoint')\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: credentials.refresh,\n }),\n signal: AbortSignal.timeout(30_000),\n })\n if (!response.ok)\n throw new Error(`xAI token refresh failed: ${response.status} ${await response.text().catch(() => '')}`)\n\n const payload = await response.json() as Record<string, unknown>\n const access = String(payload.access_token ?? '')\n if (!access)\n throw new Error('xAI token refresh did not return an access_token.')\n\n return {\n ...xai,\n access,\n // xAI may omit a rotated refresh token — keep the existing one.\n refresh: String(payload.refresh_token ?? credentials.refresh),\n expires: expiryFromPayload(payload),\n tokenEndpoint,\n }\n}\n\n/**\n * Build an xAI `OAuthProviderInterface`. Shape matches Anthropic / Codex /\n * Cursor so it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * `usesCallbackServer: true` — the loopback flow has a real callback server,\n * so the TUI doesn't need to prompt for a manual code paste.\n */\nexport function createXaiOAuthProvider(renderPage: OAuthCallbackPageRenderer): OAuthProviderInterface {\n return {\n id: XAI_OAUTH_PROVIDER_ID,\n name: 'xAI Grok (SuperGrok / X Premium+)',\n usesCallbackServer: true,\n login: callbacks => loginXai(renderPage, callbacks),\n refreshToken: refreshXaiToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, ProviderCapabilities, StreamCallbacks, StreamOptions, TurnResult } from '.'\nimport type { OAuthParams } from './oauth'\nimport { XAI_OAUTH_PROVIDER_ID, xaiBaseUrl } from '../chat/oauth-page/xai'\nimport { extractRuntimeCredentials, isOAuthAccessToken, resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\nconst DEFAULT_MODEL = 'grok-4.3'\n\n/**\n * xAI Grok provider — supports both a static API key (`XAI_API_KEY`) and the\n * SuperGrok / X Premium+ OAuth subscription.\n *\n * Both auth modes hit the same OpenAI-compatible endpoint (`https://api.x.ai/v1`),\n * so the wire handling is entirely {@link openaiCompat}. The only difference is\n * the bearer:\n *\n * - **API key**: `XAI_API_KEY` (or `params.apiKey`) → used verbatim.\n * - **OAuth**: no static key → {@link resolveOAuthApiKey} resolves + lazily\n * refreshes the stored `xai-oauth` token on every turn.\n *\n * `resolveOAuthApiKey` unifies the two: a real API key in `XAI_API_KEY` is\n * returned as-is, while an OAuth access token (or no env value at all) falls\n * through to the refreshable stored credentials. So this single factory serves\n * both paths without branching at construction time.\n */\nexport interface XaiParams extends OAuthParams {\n defaultModel?: string\n /**\n * Provider capability flags. Grok models are vision-capable; default\n * `{ vision: true, imageInToolResult: false }`. Override for text-only\n * deployments.\n */\n capabilities?: ProviderCapabilities\n /** Extra HTTP headers sent on every request. */\n extraHeaders?: Record<string, string>\n}\n\nexport function xai(params?: XaiParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n const baseURL = xaiBaseUrl()\n const capabilities: ProviderCapabilities = params?.capabilities ?? {\n vision: true,\n imageInToolResult: false,\n }\n const baseCredentials = extractRuntimeCredentials(params)\n let runtimeCredentials = baseCredentials\n\n // Factory-time snapshot for the static `meta.isOAuth` display flag only (the\n // terminal header's `oauth`/`key` badge). The real bearer resolves per turn\n // in `stream`, so this is best-effort, mirroring anthropic's pattern. It's\n // OAuth unless a genuine static API key is present: a `xai-…` key → key mode;\n // runtime OAuth creds, a JWT access token, or no static key (stored creds\n // used) → oauth mode.\n const staticKey = params?.apiKey ?? process.env.XAI_API_KEY\n const isOAuth = baseCredentials !== undefined || !staticKey || isOAuthAccessToken(staticKey)\n\n /**\n * Build the openai-compat delegate for a resolved bearer. Cheap — the factory\n * only wires closures — so we re-create it per turn with the freshly resolved\n * (possibly refreshed) OAuth token rather than baking a stale key in once.\n */\n function delegateFor(apiKey: string): Provider {\n return openaiCompat({\n name: 'xai',\n apiKey,\n baseURL,\n defaultModel,\n capabilities,\n extraHeaders: params?.extraHeaders,\n })\n }\n\n // A delegate built with a placeholder key supplies the non-stream methods\n // (formatTools, userMessage, message builders, classifyError). Only `stream`\n // resolves the real bearer, so the placeholder never reaches the wire.\n const skeleton = delegateFor('placeholder-resolved-at-stream')\n\n return {\n ...skeleton,\n name: 'xai',\n // Reuse the delegate's normalized capability flags (openaiCompat fills the\n // audio/video/documents defaults) so the advertised shape matches the wire.\n meta: { ...skeleton.meta, defaultModel, isOAuth },\n\n async stream(options: StreamOptions, callbacks: StreamCallbacks): Promise<TurnResult> {\n const apiKey = await resolveOAuthApiKey(\n {\n provider: 'xai',\n providerId: XAI_OAUTH_PROVIDER_ID,\n params: runtimeCredentials ? { ...params, ...runtimeCredentials } : params,\n envKey: 'XAI_API_KEY',\n missingError: 'No xAI credentials found. Set XAI_API_KEY or run `bun run auth --xai` first.',\n refreshError: reason => `xAI OAuth token refresh failed. Run \\`bun run auth --xai\\` again. ${reason}`,\n },\n {\n ...callbacks,\n async onOAuthRefresh(ctx) {\n // Keep param-sourced credentials current so a long-lived provider\n // instance reuses the rotated token instead of re-refreshing.\n if (ctx.source === 'params') {\n runtimeCredentials = {\n access: ctx.credentials.access,\n refresh: ctx.credentials.refresh,\n expires: ctx.credentials.expires,\n }\n }\n await callbacks.onOAuthRefresh?.(ctx)\n },\n },\n )\n\n return delegateFor(apiKey).stream(options, callbacks)\n },\n }\n}\n"],"mappings":"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"}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -384,6 +384,34 @@ The built-in chat profiles (`BUILD_AGENT` / `PLAN_AGENT`, consumed by the TUI, G
|
|
|
384
384
|
|
|
385
385
|
The end-to-end proof lives in the live suite's *golden configuration* case: a real model driving the real file tools through the loop under the settings above, asserted on **perfect prefix carry-forward** — every turn's `cacheRead` equals the previous turn's `cacheRead + cacheCreation`, i.e. writes only ever cover new transcript. Carry-forward is the metric to reach for over hit *rate*: a short run with large tool outputs can carry its prefix perfectly while scoring a mediocre ratio, because the ratio measures transcript proportions, not cache health.
|
|
386
386
|
|
|
387
|
+
### Affinity routing (`promptCacheKey`)
|
|
388
|
+
|
|
389
|
+
Everything above assumes the second request reaches the machine that cached the first. That is not guaranteed: providers pick a replica by hashing the head of the prompt (OpenAI documents ~the first 256 tokens), so under load a session's traffic can spill onto a cold replica and the prefix is re-billed for reasons the wire cannot explain. `promptCacheKey` is the opt-in hint against that — a stable per-session value the server combines with its own prefix hash to make routing sticky. It is a routing hint, never a cache handle: it cannot make a provider keep or fetch an entry.
|
|
390
|
+
|
|
391
|
+
Supported on both provider families, off by default (when unset, requests are byte-identical to before the option existed):
|
|
392
|
+
|
|
393
|
+
| Provider | Wire shape |
|
|
394
|
+
|---|---|
|
|
395
|
+
| `openaiCompat` (and `openrouter` / `baseten`) | `prompt_cache_key` body field; `promptCacheKeyField: 'user'` for legacy routes |
|
|
396
|
+
| `anthropic` | `metadata.user_id`, merged onto any caller-supplied `metadata` |
|
|
397
|
+
| either | `promptCacheKeyHeader: 'x-…'` additionally mirrors it into a header, for gateways that route on one |
|
|
398
|
+
|
|
399
|
+
`promptCacheKey: true` derives the key as `sha256(system + first user message)`, 32 hex chars. Deriving beats threading a session id through `StreamOptions`: the derived value is stable for a session's lifetime yet distinct between sessions, needs no plumbing in the loop, and — because the GUI/daemon shares one provider instance across sessions — a construction-time constant would bucket unrelated sessions onto a single replica. It moves exactly when the routable prefix moves (new session, different profile, a compaction that rewrites the head), which are the cases where the old replica's cache is worthless anyway. A hash also keeps prompt text and user identifiers out of a field that leaves the process, which is what Anthropic asks for on `metadata.user_id`. Pass a string or a `(ctx) => string | undefined` resolver instead when the host has a real session id.
|
|
400
|
+
|
|
401
|
+
What it actually buys, per route — worth knowing before enabling it:
|
|
402
|
+
|
|
403
|
+
| Route | Effect |
|
|
404
|
+
|---|---|
|
|
405
|
+
| OpenAI / Azure | Real. Combined with the prefix hash; newer model families require it for the more reliable matching path. |
|
|
406
|
+
| OpenRouter | Real. Read as its sticky-routing key, complementing the stickiness it already applies after a cached request. |
|
|
407
|
+
| Baseten Model APIs | ~None. Their load balancer is already KV-cache-aware (routes on replica cache state) and caching is unconditional. |
|
|
408
|
+
| 1P Anthropic | ~None. Cache entries are scoped per organization/workspace and matched purely by prefix; there is no client-side routing knob. |
|
|
409
|
+
| Proxy / AI gateway in front of either (`baseURL`) | Real — this is the case that motivates plumbing it on Anthropic at all. |
|
|
410
|
+
|
|
411
|
+
A note on reading the live suite's Baseten case: when Baseten reports no `cached_tokens` at all (cold replica, eviction under load) the cache assertions cannot run, so the case records an explicit `UNVERIFIED` entry and warns rather than passing quietly. `INFERENCE_STRICT=1` promotes that to a failure — use it for a release gate, where "could not verify" must not read as green.
|
|
412
|
+
|
|
413
|
+
Two operational notes. OpenAI documents a ceiling of roughly **15 requests/minute per key** before traffic spills anyway; a per-session key sits comfortably under it, a shared constant would not. And a gateway that sets the field itself always wins: a value already present via `extraBodyParams` (or the header via `extraHeaders`) is never overwritten, so "we route" and "the gateway routes" compose instead of fighting. `test/prompt-cache-key.test.ts` pins the derivation, the per-turn stability, and every no-clobber path.
|
|
414
|
+
|
|
387
415
|
### Cache-break diagnosis
|
|
388
416
|
|
|
389
417
|
`installCacheBreakLogger(hooks, options?)` hashes per-request dimensions (`system`, `tools`, `model`, `thinking`, `thinkingBudget`, `messagePrefix`) at `turn:before` and compares against the previous turn on `turn:after`. When a break is detected on turn 2+ and dimensions differ, it logs the responsible dimension(s) — turning "cache broke" into "tools array changed at turn 7 because MCP server X loaded".
|
|
@@ -608,7 +636,7 @@ session:end ← run finished (completed | aborted | err
|
|
|
608
636
|
|
|
609
637
|
### Background task lifecycle
|
|
610
638
|
|
|
611
|
-
Fires when the model invokes `shell({ run_in_background: true })`. The shell tool dispatches via `ExecutionContext.execBackground`, which spawns a process group, opens a `WriteStream` to `<userDir
|
|
639
|
+
Fires when the model invokes `shell({ run_in_background: true })`. The shell tool dispatches via `ExecutionContext.execBackground`, which spawns a process group, opens a `WriteStream` to `<userDir>/sessions/<sessionId>/tasks/<task-id>.<context-timestamp>.log` (the timestamp segment is `YYYYMMDD-HHMMSS-mmm` UTC, shared by every task in the same `ExecutionContext` so two contexts on the same session never collide), and returns immediately with a `TaskHandle`. The agent listens to `background:exit` and queues a `<task-notification>` block for injection into the next user turn (see "Notification queue" below).
|
|
612
640
|
|
|
613
641
|
**Host opt-in / opt-out.** Background mode is auto-disabled at the schema level (the `run_in_background` field is dropped from the `shell` tool's input schema and the related description paragraphs are stripped) when either `behavior.tasksDir` is unset or `behavior.disableBackgroundTasks: true` is set. `createAgent` performs the rewrite once per run as it indexes tools by spec name — only for the identity-equal framework `shell` constant, so host-customized shell-named tools are left untouched. The runtime check in `runBackground` stays as defense-in-depth (forged inputs that smuggle the flag in past the schema fall through to a clean error, not a silent fallthrough to foreground). Hosts that want explicit control should call `createShellTool({ allowBackground })` and register the tailored variant directly.
|
|
614
642
|
|
package/docs/CHAT.md
CHANGED
|
@@ -148,7 +148,7 @@ The table below indexes every named export; sections further down dive into the
|
|
|
148
148
|
| `model-catalog` | Cross-provider unified model picker assembly — `buildModelCatalog`, `filterModelCatalog`, `indexOfEntry`, `CatalogEntry`. |
|
|
149
149
|
| `model-chain` | Ordered model fallback chains (pure helpers behind the picker's multi-select + the host's run-option assembly) — `ModelChainEntry`, `toggleChainEntry`, `chainPositionOf`, `chainsEqual`, `filterChainFallbacks`, `restoreChainFallbacks`, `sanitizeModelChain`, `providerKeySetChanged`, `buildRunModel`. |
|
|
150
150
|
| `oauth` | `runOAuthLogin(descriptor, { onUrl, onPrompt, onProgress, signal })` + `supportsOAuth(descriptor)` + `oauthUsesManualCodePaste(descriptor)` — AI-provider OAuth (distinct from MCP OAuth). |
|
|
151
|
-
| `oauth-redirect` | `fetchOAuthRedirect(pastedUrl)` — SSH escape hatch. Validates `pasted` is a loopback URL, fires a GET so the in-process callback server (pi-ai's or `startOAuthCallback`'s) receives the request through its normal handler. Used by the TUI to accept a redirect-URL paste when the browser couldn't reach the callback server directly. |
|
|
151
|
+
| `oauth-redirect` | `fetchOAuthRedirect(pastedUrl, { authUrl? })` — SSH escape hatch. Validates `pasted` is a loopback URL, fires a GET so the in-process callback server (pi-ai's or `startOAuthCallback`'s) receives the request through its normal handler. Used by the TUI to accept a redirect-URL paste when the browser couldn't reach the callback server directly. `normalizeRedirectPaste(pasted, authUrl)` widens what counts as a paste (bare code / query / path); `redirectUriFromAuthUrl(authUrl)` reads back the loopback callback the request was minted with, which the TUI also displays. |
|
|
152
152
|
| `path-display` | `formatPathForCwd(projectRel, projectRoot, cwd)` — rewrites a project-root-relative path (as emitted by `listProjectFiles`) into the form the agent's CWD-resolving tools actually find. Wired into `createFilesCompletionProvider({ formatPath })` by the TUI shell. |
|
|
153
153
|
| `project-root` | `findGitRoot(cwd)` — walks upward looking for `.git`, returns absolute path or `null`. Used for session scope tagging + export anchors. |
|
|
154
154
|
| `prompt-segments` | `splitPromptSegments(text, refs)` → `PromptSegment[]`. GUI maps the same segments to inline-block chip pills. |
|
|
@@ -1362,6 +1362,8 @@ if (supportsOAuth(descriptor)) {
|
|
|
1362
1362
|
|
|
1363
1363
|
**SSH paste-back.** When the user runs zidane over SSH (or behind a firewall that blocks loopback), the browser-side redirect to `http://127.0.0.1:<port>/callback?...` can't reach the remote callback server. `fetchOAuthRedirect(pastedUrl)` is the escape hatch: hand it the URL the browser ended up at and it fires a GET from inside the agent process. The request hits pi-ai's still-running server through the same handler a real browser would have, `waitForCode` resolves, and `runOAuthLogin` continues uninterrupted — no `onPrompt` call needed. The TUI wires this into a paste input next to the auth URL on every OAuth surface (AI providers + MCP).
|
|
1364
1364
|
|
|
1365
|
+
Pass the authorization URL along (`fetchOAuthRedirect(pasted, { authUrl })`) and partial pastes work too: `normalizeRedirectPaste(pasted, authUrl)` completes a bare `code`, a `?code=…&state=…` query, or a `/callback?…` path against the `redirect_uri` the request was minted with (`redirectUriFromAuthUrl`), reusing the `state` from the authorization URL when the paste has none. Absolute URLs are passed through untouched so a mistaken non-loopback paste still hits the SSRF guard instead of being rewritten into a request against our own callback server.
|
|
1366
|
+
|
|
1365
1367
|
### MCP-server OAuth
|
|
1366
1368
|
|
|
1367
1369
|
Streamable-HTTP and SSE MCP servers can require OAuth 2.1 (Linear, Sentry, the Anthropic-hosted catalog). Wired through the SDK's `OAuthClientProvider` interface — `zidane/chat` provides the persistence half (`McpCredentialStore` + `createFileMcpCredentialStore`), `zidane/mcp` provides the SDK-protocol half (`McpOAuthProvider`).
|
|
@@ -1449,7 +1451,7 @@ const eff = effectiveContextWindow(raw) // 180_000 = raw - OUTPUT_RE
|
|
|
1449
1451
|
|
|
1450
1452
|
The agent loop can replace oversize `tool_result` outputs with a `<persisted-output tool="…" bytes="…" path="…">` stub carrying a 2 KiB head preview. Imports from `zidane`:
|
|
1451
1453
|
|
|
1452
|
-
- `resolvePersistDir({ userDir, sessionId })` → `<userDir>/tool-results
|
|
1454
|
+
- `resolvePersistDir({ userDir, sessionId })` → `<userDir>/sessions/<sessionId>/tool-results/`. Always pin to the user dir.
|
|
1453
1455
|
- `buildPersistedStub({ toolName, originalBytes, persistedPath, output })` → the stub string.
|
|
1454
1456
|
- `maybePersistToolResult(input)` → returns a tagged outcome: `persisted`, `skip`, or a bounded `error` stub when persistence was required but the blob write failed.
|
|
1455
1457
|
- `PERSISTED_STUB_PREFIX` (`'<persisted-output tool="'`) — recognize a stub for tail compaction / future stale-output elision.
|
|
@@ -300,7 +300,7 @@ interface TaskState {
|
|
|
300
300
|
}
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
-
**File layout**: per-session under `<userDir
|
|
303
|
+
**File layout**: per-session under `<userDir>/sessions/<sessionId>/tasks/<task-id>.<context-timestamp>.log`. Off the project tree, scoped to session lifetime, easy to clean up. The `<context-timestamp>` segment is `YYYYMMDD-HHMMSS-mmm` (UTC, millisecond precision) shared by every task spawned within the same `ExecutionContext` instance — so a directory listing groups cleanly by "which run produced these" and two contexts on the same session never write into the same file. On `agent.destroy()` we close the streams; on session delete (a TUI affordance) we remove the directory.
|
|
304
304
|
|
|
305
305
|
**No ring buffer, no cursors**: the model uses `read_file({ offset, limit })` for incremental reads. The file is a normal log file; the existing read tool already does grep / range / pagination.
|
|
306
306
|
|
|
@@ -391,7 +391,7 @@ When a background task exits, the framework prepends a `<task-notification>` blo
|
|
|
391
391
|
4. Banner shape: one line, theme-accented by status (`COLOR.dim` for exited-0, `COLOR.warn` for non-zero, `COLOR.error` for killed). Format: `⌁ bash_1 exited (0) · 4.2s · /Users/.../tasks/bash_1.20260523-024147-123.log`. OSC 8 hyperlink on the path so terminal-emulators that support it open the log file. Multi-task drains render as N banners in completion order.
|
|
392
392
|
5. The renderer must DEDUPE — if the raw text block is also visible in the transcript via the generic text-block path, we'd double-render. The detection in step 3 strips the text block from generic rendering when a structured event covers it. (Same dedupe pattern the compact-summary code already uses.)
|
|
393
393
|
|
|
394
|
-
### §2. File location — `<userDir
|
|
394
|
+
### §2. File location — `<userDir>/sessions/<sessionId>/tasks/<task-id>.<context-timestamp>.log`
|
|
395
395
|
|
|
396
396
|
Under zidane's user data dir, scoped per session. Same rationale as the existing session-persistence layout. Cleanup hooks into the session-delete path — when a session is removed, its `tasks/` subdirectory goes with it. The user's project tree stays clean.
|
|
397
397
|
|
package/docs/SKILL.md
CHANGED
|
@@ -492,7 +492,7 @@ const agent = createAgent({
|
|
|
492
492
|
- `behavior.disableBackgroundTasks: 'non-durable'` is set and the execution context's `capabilities.detachedTasks` is below `'durable'` — the capability-aware form durable-execution hosts use (`restateBehavior()` defaults to it), so remote contexts whose tasks survive worker crashes keep background mode while host-local processes don't.
|
|
493
493
|
- the execution context's `capabilities.detachedTasks` is `'none'` — the context can't background at all, so the schema would advertise a dead flag.
|
|
494
494
|
|
|
495
|
-
The path is per-session by convention (`<userDir
|
|
495
|
+
The path is per-session by convention (`<userDir>/sessions/<sessionId>/tasks/`) so a session delete cleans up log files alongside other session state.
|
|
496
496
|
|
|
497
497
|
The auto-rewrite only fires for the identity-equal framework `shell` tool — hosts who register a custom shell-named tool keep ownership of their spec. For explicit control, hosts can import `createShellTool({ allowBackground })` from `zidane` and register the tailored variant directly.
|
|
498
498
|
|
package/docs/TUI.md
CHANGED
|
@@ -479,7 +479,7 @@ The `'compact-summary'` `StreamEvent` (carrying `compact: { replacedCount, model
|
|
|
479
479
|
|
|
480
480
|
- `behavior.persistThreshold` defaults to 8 KiB on the built-in profiles; the TUI overrides to 0 when the user toggles persistence off.
|
|
481
481
|
- `behavior.persistExcludeTools` = `DEFAULT_PERSIST_EXCLUDE_TOOLS` from `zidane/chat` (`read_file`, `tool_search`, `skills_use`, `skills_read`, `present_plan`, `ask_user`, `spawn`).
|
|
482
|
-
- `behavior.persistDir` is `<userDir>/tool-results
|
|
482
|
+
- `behavior.persistDir` is `<userDir>/sessions/<sessionId>/tool-results/` (always pinned to user dir, never the project dir) — `resolvePersistDir` from `zidane`.
|
|
483
483
|
- Flipping the toggle takes effect on the **next** session activation; in-flight streams aren't torn down. Persisted blobs already on disk stay readable via `read_file` on the stub's `path` attribute.
|
|
484
484
|
|
|
485
485
|
## MCP servers + OAuth login
|
|
@@ -503,7 +503,7 @@ Interactive login flow (triggered by `ctrl+L` on a needs-auth row in `SettingsMo
|
|
|
503
503
|
1. App calls `loginMcpServer(config, { store, hooks, signal })` from `zidane/mcp`.
|
|
504
504
|
2. `startOAuthCallback` spawns a loopback `127.0.0.1` HTTP listener on a random port.
|
|
505
505
|
3. `McpOAuthProvider` is built with `redirectUri` pointing at the loopback; `client.connect()` triggers `redirectToAuthorization` which fires `mcp:auth:url` (the modal renders the URL via `<McpAuthorizingPanel>` — `tryOpenBrowser` opens it automatically).
|
|
506
|
-
4. SDK throws `UnauthorizedError`; we wait on the callback for the `?code=...` redirect. The same `<McpAuthorizingPanel>` includes a paste-back input
|
|
506
|
+
4. SDK throws `UnauthorizedError`; we wait on the callback for the `?code=...` redirect. The same `<McpAuthorizingPanel>` includes a paste-back input (step 2 of the panel) — submitting pipes the value through `fetchOAuthRedirect`, which `fetch()`s the loopback callback URL from inside the agent process and lands a `?code=...` on the listener exactly like a real browser would. This unblocks SSH sessions where the loopback redirect can't reach the remote server directly. The panel prints the exact `redirect_uri` the request was minted with (`redirectUriFromAuthUrl`) so there's no guessing about which address to copy out of the browser's URL bar, and it force-focuses the input on mount — `focused` as a prop alone leaves keys with whichever field owned focus before the takeover mounted.
|
|
507
507
|
5. `transport.finishAuth(code)` exchanges the code for tokens; `provider.saveTokens` persists via `createFileMcpCredentialStore(userDir)`.
|
|
508
508
|
6. Reconnect with the new tokens; `client.listTools()` returns the catalog.
|
|
509
509
|
7. App rebuilds the active agent so the now-authenticated server connects on the next run.
|
|
@@ -516,7 +516,7 @@ Interactive login flow (triggered by `ctrl+L` on a needs-auth row in `SettingsMo
|
|
|
516
516
|
|
|
517
517
|
1. **Pick provider** — every entry in `config.providers`. Methods column derives from `supportsOAuth(descriptor)`.
|
|
518
518
|
2. **Pick method** — API key or OAuth (gated on `descriptor.oauthProvider`).
|
|
519
|
-
3. **Enter API key** → writes via `setProviderCredential(dataDir, descriptor, { kind: 'apikey', value })` and seeds `process.env[descriptor.envKey]`, OR **OAuth running** → `runOAuthLogin(descriptor, { onUrl, onPrompt, onProgress, signal })`. The URL is surfaced via `<OAuthAuthBlock>`
|
|
519
|
+
3. **Enter API key** → writes via `setProviderCredential(dataDir, descriptor, { kind: 'apikey', value })` and seeds `process.env[descriptor.envKey]`, OR **OAuth running** → `runOAuthLogin(descriptor, { onUrl, onPrompt, onProgress, signal })`. The URL is surfaced via `<OAuthAuthBlock>` — two clickable buttons (`ctrl+b` open in browser, `ctrl+y` copy the URL), the full URL in a framed panel with its own `[copy]` button and chunked into per-row hyperlinks, and a paste-back input. Submitting the input either resolves a pending pi-ai `onPrompt` (providers with `usesCallbackServer: false`, e.g. Anthropic Claude Pro/Max) or, when no prompt is pending, fires the pasted URL through `fetchOAuthRedirect` so the loopback callback server completes through its normal path — the SSH escape hatch. The screen pre-flips its spinner copy via `oauthUsesManualCodePaste(descriptor)` so the manual-paste providers say "paste the code below" before pi-ai actually prompts.
|
|
520
520
|
|
|
521
521
|
A pre-existing user picker (when at least one provider is already authed) lists current credentials plus a `+ add or re-configure` entry that opens the wizard with a `← back` affordance. Esc on the wizard bails to the picker (or exits the TUI on a true first launch).
|
|
522
522
|
|