| ... | ... | @@ -14,6 +14,7 @@ import { decodeJsonResult } from "@t3tools/shared/schemaJson"; |
| 14 | 14 | import { |
| 15 | 15 | query as claudeQuery, |
| 16 | 16 | type SlashCommand as ClaudeSlashCommand, |
| 17 | type SDKUserMessage, |
| 17 | 18 | } from "@anthropic-ai/claude-agent-sdk"; |
| 18 | 19 | |
| 19 | 20 | import { |
| ... | ... | @@ -480,13 +481,24 @@ function dedupeSlashCommands( |
| 480 | 481 | return [...commandsByName.values()]; |
| 481 | 482 | } |
| 482 | 483 | |
| 484 | function waitForAbortSignal(signal: AbortSignal): Promise<void> { |
| 485 | if (signal.aborted) { |
| 486 | return Promise.resolve(); |
| 487 | } |
| 488 | return new Promise((resolve) => { |
| 489 | signal.addEventListener("abort", () => resolve(), { once: true }); |
| 490 | }); |
| 491 | } |
| 492 | |
| 483 | 493 | /** |
| 484 | 494 | * Probe account information by spawning a lightweight Claude Agent SDK |
| 485 | 495 | * session and reading the initialization result. |
| 486 | 496 | * |
| 487 | | * The prompt is never sent to the Anthropic API — we abort immediately |
| 488 | | * after the local initialization phase completes. This gives us the |
| 489 | | * user's subscription type without incurring any token cost. |
| 497 | * We pass a never-yielding AsyncIterable as the prompt so that no user |
| 498 | * message is ever written to the subprocess stdin. This means the Claude |
| 499 | * Code subprocess completes its local initialization IPC (returning |
| 500 | * account info and slash commands) but never starts an API request to |
| 501 | * Anthropic. We read the init data and then abort the subprocess. |
| 490 | 502 | * |
| 491 | 503 | * This is used as a fallback when `claude auth status` does not include |
| 492 | 504 | * subscription type information. |
| ... | ... | @@ -495,12 +507,16 @@ const probeClaudeCapabilities = (binaryPath: string) => { |
| 495 | 507 | const abort = new AbortController(); |
| 496 | 508 | return Effect.tryPromise(async () => { |
| 497 | 509 | const q = claudeQuery({ |
| 498 | | prompt: ".", |
| 510 | // Never yield — we only need initialization data, not a conversation. |
| 511 | // This prevents any prompt from reaching the Anthropic API. |
| 512 | // oxlint-disable-next-line require-yield |
| 513 | prompt: (async function* (): AsyncGenerator<SDKUserMessage> { |
| 514 | await waitForAbortSignal(abort.signal); |
| 515 | })(), |
| 499 | 516 | options: { |
| 500 | 517 | persistSession: false, |
| 501 | 518 | pathToClaudeCodeExecutable: binaryPath, |
| 502 | 519 | abortController: abort, |
| 503 | | maxTurns: 0, |
| 504 | 520 | settingSources: ["user", "project", "local"], |
| 505 | 521 | allowedTools: [], |
| 506 | 522 | stderr: () => {}, |