import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Types from "effect/Types";import * as ChildProcess from "effect/unstable/process/ChildProcess";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import * as CodexClient from "effect-codex-app-server/client";import * as CodexSchema from "effect-codex-app-server/schema";import * as CodexErrors from "effect-codex-app-server/errors"; import type { CodexSettings, ServerProvider, ServerProviderState, ModelCapabilities, ProviderOptionDescriptor, ServerProviderModel, ServerProviderSkill,} from "@t3tools/contracts";import { PREFERRED_DEFAULT_CODEX_MODELS, ServerSettingsError } from "@t3tools/contracts"; import { createModelCapabilities } from "@t3tools/shared/model";import { resolveSpawnCommand } from "@t3tools/shared/shell";import { codexAppServerArgs, resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";import { AUTH_PROBE_TIMEOUT_MS, buildServerProvider, type ServerProviderDraft,} from "../providerSnapshot.ts";import { expandHomePath } from "../../pathExpansion.ts";import packageJson from "../../../package.json" with { type: "json" };const isCodexAppServerSpawnError = Schema.is(CodexErrors.CodexAppServerSpawnError); const CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER = "2 seconds" as const; const CODEX_PRESENTATION = { displayName: "Codex", showInteractionModeToggle: true,} as const; export interface CodexAppServerProviderSnapshot { readonly account: CodexSchema.V2GetAccountResponse; readonly version: string | undefined; readonly models: ReadonlyArray<ServerProviderModel>; readonly skills: ReadonlyArray<ServerProviderSkill>;} const REASONING_EFFORT_LABELS: Readonly<Record<string, string>> = { none: "None", minimal: "Minimal", low: "Low", medium: "Medium", high: "High", xhigh: "Extra High", max: "Max", ultra: "Ultra",}; const DEFAULT_SERVICE_TIER_ID = "default"; function reasoningEffortLabel(reasoningEffort: string): string { return REASONING_EFFORT_LABELS[reasoningEffort] ?? reasoningEffort;} function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["account"]) { if (!account) return undefined; if (account.type === "apiKey") return "OpenAI API Key"; if (account.type === "amazonBedrock") return "Amazon Bedrock"; if (account.type !== "chatgpt") return undefined; switch (account.planType) { case "free": return "ChatGPT Free Subscription"; case "go": return "ChatGPT Go Subscription"; case "plus": return "ChatGPT Plus Subscription"; case "pro": return "ChatGPT Pro 20x Subscription"; case "prolite": return "ChatGPT Pro 5x Subscription"; case "team": return "ChatGPT Team Subscription"; case "self_serve_business_usage_based": case "business": return "ChatGPT Business Subscription"; case "enterprise_cbp_usage_based": case "enterprise": return "ChatGPT Enterprise Subscription"; case "edu": return "ChatGPT Edu Subscription"; case "unknown": return "ChatGPT Subscription"; default: account.planType satisfies never; return undefined; }} function codexAccountEmail(account: CodexSchema.V2GetAccountResponse["account"]) { if (!account || account.type !== "chatgpt") return undefined; return account.email;} export function mapCodexModelCapabilities( model: CodexSchema.V2ModelListResponse__Model,): ModelCapabilities { const reasoningOptions = model.supportedReasoningEfforts.map(({ reasoningEffort }) => reasoningEffort === model.defaultReasoningEffort ? { id: reasoningEffort, label: reasoningEffortLabel(reasoningEffort), isDefault: true, } : { id: reasoningEffort, label: reasoningEffortLabel(reasoningEffort), }, ); const defaultReasoning = reasoningOptions.find((option) => option.isDefault)?.id; const serviceTiers = model.serviceTiers && model.serviceTiers.length > 0 ? model.serviceTiers : (model.additionalSpeedTiers ?? []).map((id) => ({ id, name: id === "fast" ? "Fast" : id, description: "", })); const catalogDefaultServiceTier = serviceTiers.some( (tier) => tier.id === model.defaultServiceTier, ) ? model.defaultServiceTier : null; const defaultServiceTier = catalogDefaultServiceTier ?? DEFAULT_SERVICE_TIER_ID; const optionDescriptors: ProviderOptionDescriptor[] = []; if (reasoningOptions.length > 0) { optionDescriptors.push({ id: "reasoningEffort", label: "Reasoning", type: "select", options: reasoningOptions, ...(defaultReasoning ? { currentValue: defaultReasoning } : {}), }); } if (serviceTiers.length > 0) { optionDescriptors.push({ id: "serviceTier", label: "Service Tier", type: "select", options: [ { id: DEFAULT_SERVICE_TIER_ID, label: "Standard", ...(defaultServiceTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}), }, ...serviceTiers.map((tier) => ({ id: tier.id, label: tier.name, ...(tier.description ? { description: tier.description } : {}), ...(defaultServiceTier === tier.id ? { isDefault: true } : {}), })), ], currentValue: defaultServiceTier, }); } return createModelCapabilities({ optionDescriptors, });} const toDisplayName = (model: CodexSchema.V2ModelListResponse__Model): string => { // Capitalize 'gpt' to 'GPT-' and capitalize any letter following a dash return model.displayName .replace(/^gpt/i, "GPT") // Handle start with 'gpt' or 'GPT' .replace(/-([a-z])/g, (_, c) => "-" + c.toUpperCase());}; function parseCodexModelListResponse( response: CodexSchema.V2ModelListResponse,): ReadonlyArray<ServerProviderModel> { return response.data.map((model) => ({ slug: model.model, name: toDisplayName(model), isCustom: false, ...(model.isDefault ? { isDefault: true } : {}), capabilities: mapCodexModelCapabilities(model), }));} /** * Prefer our own default-model ranking when one of the preferred slugs is in * the live catalog; otherwise keep whatever Codex itself flagged as default. */export function applyPreferredCodexDefaultModel( models: ReadonlyArray<ServerProviderModel>,): ReadonlyArray<ServerProviderModel> { const preferredSlug = PREFERRED_DEFAULT_CODEX_MODELS.find((slug) => models.some((model) => model.slug === slug && !model.isCustom), ); if (!preferredSlug) { return models; } return models.map((model) => { if (model.slug === preferredSlug) { return model.isDefault ? model : { ...model, isDefault: true }; } if (!model.isDefault) { return model; } const { isDefault: _isDefault, ...rest } = model; return rest; });} function appendCustomCodexModels( models: ReadonlyArray<ServerProviderModel>, customModels: ReadonlyArray<string>,): ReadonlyArray<ServerProviderModel> { if (customModels.length === 0) { return models; } const seen = new Set(models.map((model) => model.slug)); const fallbackCapabilities = models.find((model) => model.capabilities)?.capabilities ?? null; const customEntries: ServerProviderModel[] = []; for (const rawModel of customModels) { const slug = rawModel.trim(); if (!slug || seen.has(slug)) { continue; } seen.add(slug); customEntries.push({ slug, name: slug, isCustom: true, capabilities: fallbackCapabilities, }); } return customEntries.length === 0 ? models : [...models, ...customEntries];} function parseCodexSkillsListResponse( response: CodexSchema.V2SkillsListResponse, cwd: string,): ReadonlyArray<ServerProviderSkill> { const matchingEntry = response.data.find((entry) => entry.cwd === cwd); const skills = matchingEntry ? matchingEntry.skills : response.data.flatMap((entry) => entry.skills); return skills.map((skill) => { const shortDescription = skill.shortDescription ?? skill.interface?.shortDescription ?? undefined; const parsedSkill: Types.Mutable<ServerProviderSkill> = { name: skill.name, path: skill.path, enabled: skill.enabled, }; if (skill.description) { parsedSkill.description = skill.description; } if (skill.scope) { parsedSkill.scope = skill.scope; } if (skill.interface?.displayName) { parsedSkill.displayName = skill.interface.displayName; } if (shortDescription) { parsedSkill.shortDescription = shortDescription; } return parsedSkill; });} const requestAllCodexModels = Effect.fn("requestAllCodexModels")(function* ( client: CodexClient.CodexAppServerClient["Service"],) { const models: ServerProviderModel[] = []; let cursor: string | null | undefined = undefined; do { const response: CodexSchema.V2ModelListResponse = yield* client.request( "model/list", cursor ? { cursor } : {}, ); models.push(...parseCodexModelListResponse(response)); cursor = response.nextCursor; } while (cursor); return models;}); export function buildCodexInitializeParams(): CodexSchema.V1InitializeParams { return { clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: packageJson.version, }, capabilities: { experimentalApi: true, }, };} const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(function* (input: { readonly binaryPath: string; readonly homePath?: string; readonly launchArgs?: string; readonly cwd: string; readonly customModels?: ReadonlyArray<string>; readonly environment?: NodeJS.ProcessEnv;}) { // `~` is not shell-expanded when env vars are set via `child_process.spawn`, // so `CODEX_HOME=~/.codex_work` would reach codex verbatim and trip // "CODEX_HOME points to '~/.codex_work', but that path does not exist". // Expand here for parity with `CodexTextGeneration`/`CodexSessionRuntime`. const resolvedHomePath = input.homePath ? expandHomePath(input.homePath) : undefined; const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const environment = { ...input.environment, ...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}), }; const spawnCommand = yield* resolveSpawnCommand( input.binaryPath, codexAppServerArgs(input.launchArgs), { env: environment, extendEnv: true, }, ); const child = yield* spawner .spawn( ChildProcess.make(spawnCommand.command, spawnCommand.args, { cwd: input.cwd, env: environment, extendEnv: true, forceKillAfter: CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER, shell: spawnCommand.shell, }), ) .pipe( Effect.mapError( (cause) => new CodexErrors.CodexAppServerSpawnError({ command: `${input.binaryPath} app-server`, cause, }), ), ); const clientContext = yield* Layer.build(CodexClient.layerChildProcess(child)); const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe( Effect.provide(clientContext), ); const initialize = yield* client.request("initialize", { clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: "0.1.0", }, capabilities: { experimentalApi: true, }, }); yield* client.notify("initialized", undefined); // Extract the version string after the first '/' in userAgent, up to the next space or the end const versionMatch = initialize.userAgent.match(/\/([^\s]+)/); const version = versionMatch ? versionMatch[1] : undefined; const accountResponse = yield* client.request("account/read", {}); if (!accountResponse.account && accountResponse.requiresOpenaiAuth) { return { account: accountResponse, version, models: appendCustomCodexModels([], input.customModels ?? []), skills: [], } satisfies CodexAppServerProviderSnapshot; } const [skillsResponse, models] = yield* Effect.all( [ client.request("skills/list", { cwds: [input.cwd], }), requestAllCodexModels(client), ], { concurrency: "unbounded" }, ); return { account: accountResponse, version, models: applyPreferredCodexDefaultModel( appendCustomCodexModels(models, input.customModels ?? []), ), skills: parseCodexSkillsListResponse(skillsResponse, input.cwd), } satisfies CodexAppServerProviderSnapshot;}); const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => { const models = new Set<string>(); for (const model of codexSettings.customModels) { const trimmed = model.trim(); if (trimmed.length > 0) { models.add(trimmed); } } return Array.from(models, (model) => ({ slug: model, name: model, isCustom: true, capabilities: null, }));}; const makePendingCodexProvider = ( codexSettings: CodexSettings,): Effect.Effect<ServerProviderDraft> => Effect.gen(function* () { const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); const models = emptyCodexModelsFromSettings(codexSettings); if (!codexSettings.enabled) { return buildServerProvider({ presentation: CODEX_PRESENTATION, enabled: false, checkedAt, models, skills: [], probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: "Codex is disabled in T3 Code settings.", }, }); } return buildServerProvider({ presentation: CODEX_PRESENTATION, enabled: true, checkedAt, models, skills: [], probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: "Codex provider status has not been checked in this session yet.", }, }); }); function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): { readonly status: Exclude<ServerProviderState, "disabled">; readonly auth: ServerProvider["auth"]; readonly message?: string;} { const authLabel = codexAccountAuthLabel(account.account); const authEmail = codexAccountEmail(account.account); const auth = { status: account.account ? ("authenticated" as const) : ("unknown" as const), ...(account.account?.type ? { type: account.account?.type } : {}), ...(authLabel ? { label: authLabel } : {}), ...(authEmail ? { email: authEmail } : {}), } satisfies ServerProvider["auth"]; if (account.account) { return { status: "ready", auth }; } if (account.requiresOpenaiAuth) { return { status: "error", auth: { status: "unauthenticated" }, message: "Codex CLI is not authenticated. Run `codex login` and try again.", }; } return { status: "ready", auth };} export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(function* ( codexSettings: CodexSettings, probe: (input: { readonly binaryPath: string;Lines 1–500 of 615. Load more
import * as DateTime from "effect/DateTime"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Types from "effect/Types"; import * as ChildProcess from "effect/unstable/process/ChildProcess"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as CodexClient from "effect-codex-app-server/client"; import * as CodexSchema from "effect-codex-app-server/schema"; import * as CodexErrors from "effect-codex-app-server/errors"; import type { CodexSettings, ServerProvider, ServerProviderState, ModelCapabilities, ProviderOptionDescriptor, ServerProviderModel, ServerProviderSkill, } from "@t3tools/contracts"; import { PREFERRED_DEFAULT_CODEX_MODELS, ServerSettingsError } from "@t3tools/contracts"; import { createModelCapabilities } from "@t3tools/shared/model"; import { resolveSpawnCommand } from "@t3tools/shared/shell"; import { codexAppServerArgs, resolveCodexLaunchArgs } from "./codexLaunchArgs.ts"; import { AUTH_PROBE_TIMEOUT_MS, buildServerProvider, type ServerProviderDraft, } from "../providerSnapshot.ts"; import { expandHomePath } from "../../pathExpansion.ts"; import packageJson from "../../../package.json" with { type: "json" }; const isCodexAppServerSpawnError = Schema.is(CodexErrors.CodexAppServerSpawnError); const CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER = "2 seconds" as const; const CODEX_PRESENTATION = { displayName: "Codex", showInteractionModeToggle: true, } as const; export interface CodexAppServerProviderSnapshot { readonly account: CodexSchema.V2GetAccountResponse; readonly version: string | undefined; readonly models: ReadonlyArray<ServerProviderModel>; readonly skills: ReadonlyArray<ServerProviderSkill>; } const REASONING_EFFORT_LABELS: Readonly<Record<string, string>> = { none: "None", minimal: "Minimal", low: "Low", medium: "Medium", high: "High", xhigh: "Extra High", max: "Max", ultra: "Ultra", }; const DEFAULT_SERVICE_TIER_ID = "default"; function reasoningEffortLabel(reasoningEffort: string): string { return REASONING_EFFORT_LABELS[reasoningEffort] ?? reasoningEffort; } function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["account"]) { if (!account) return undefined; if (account.type === "apiKey") return "OpenAI API Key"; if (account.type === "amazonBedrock") return "Amazon Bedrock"; if (account.type !== "chatgpt") return undefined; switch (account.planType) { case "free": return "ChatGPT Free Subscription"; case "go": return "ChatGPT Go Subscription"; case "plus": return "ChatGPT Plus Subscription"; case "pro": return "ChatGPT Pro 20x Subscription"; case "prolite": return "ChatGPT Pro 5x Subscription"; case "team": return "ChatGPT Team Subscription"; case "self_serve_business_usage_based": case "business": return "ChatGPT Business Subscription"; case "enterprise_cbp_usage_based": case "enterprise": return "ChatGPT Enterprise Subscription"; case "edu": return "ChatGPT Edu Subscription"; case "unknown": return "ChatGPT Subscription"; default: account.planType satisfies never; return undefined; } } function codexAccountEmail(account: CodexSchema.V2GetAccountResponse["account"]) { if (!account || account.type !== "chatgpt") return undefined; return account.email; } export function mapCodexModelCapabilities( model: CodexSchema.V2ModelListResponse__Model, ): ModelCapabilities { const reasoningOptions = model.supportedReasoningEfforts.map(({ reasoningEffort }) => reasoningEffort === model.defaultReasoningEffort ? { id: reasoningEffort, label: reasoningEffortLabel(reasoningEffort), isDefault: true, } : { id: reasoningEffort, label: reasoningEffortLabel(reasoningEffort), }, ); const defaultReasoning = reasoningOptions.find((option) => option.isDefault)?.id; const serviceTiers = model.serviceTiers && model.serviceTiers.length > 0 ? model.serviceTiers : (model.additionalSpeedTiers ?? []).map((id) => ({ id, name: id === "fast" ? "Fast" : id, description: "", })); const catalogDefaultServiceTier = serviceTiers.some( (tier) => tier.id === model.defaultServiceTier, ) ? model.defaultServiceTier : null; const defaultServiceTier = catalogDefaultServiceTier ?? DEFAULT_SERVICE_TIER_ID; const optionDescriptors: ProviderOptionDescriptor[] = []; if (reasoningOptions.length > 0) { optionDescriptors.push({ id: "reasoningEffort", label: "Reasoning", type: "select", options: reasoningOptions, ...(defaultReasoning ? { currentValue: defaultReasoning } : {}), }); } if (serviceTiers.length > 0) { optionDescriptors.push({ id: "serviceTier", label: "Service Tier", type: "select", options: [ { id: DEFAULT_SERVICE_TIER_ID, label: "Standard", ...(defaultServiceTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}), }, ...serviceTiers.map((tier) => ({ id: tier.id, label: tier.name, ...(tier.description ? { description: tier.description } : {}), ...(defaultServiceTier === tier.id ? { isDefault: true } : {}), })), ], currentValue: defaultServiceTier, }); } return createModelCapabilities({ optionDescriptors, }); } const toDisplayName = (model: CodexSchema.V2ModelListResponse__Model): string => { // Capitalize 'gpt' to 'GPT-' and capitalize any letter following a dash return model.displayName .replace(/^gpt/i, "GPT") // Handle start with 'gpt' or 'GPT' .replace(/-([a-z])/g, (_, c) => "-" + c.toUpperCase()); }; function parseCodexModelListResponse( response: CodexSchema.V2ModelListResponse, ): ReadonlyArray<ServerProviderModel> { return response.data.map((model) => ({ slug: model.model, name: toDisplayName(model), isCustom: false, ...(model.isDefault ? { isDefault: true } : {}), capabilities: mapCodexModelCapabilities(model), })); } /** * Prefer our own default-model ranking when one of the preferred slugs is in * the live catalog; otherwise keep whatever Codex itself flagged as default. */ export function applyPreferredCodexDefaultModel( models: ReadonlyArray<ServerProviderModel>, ): ReadonlyArray<ServerProviderModel> { const preferredSlug = PREFERRED_DEFAULT_CODEX_MODELS.find((slug) => models.some((model) => model.slug === slug && !model.isCustom), ); if (!preferredSlug) { return models; } return models.map((model) => { if (model.slug === preferredSlug) { return model.isDefault ? model : { ...model, isDefault: true }; } if (!model.isDefault) { return model; } const { isDefault: _isDefault, ...rest } = model; return rest; }); } function appendCustomCodexModels( models: ReadonlyArray<ServerProviderModel>, customModels: ReadonlyArray<string>, ): ReadonlyArray<ServerProviderModel> { if (customModels.length === 0) { return models; } const seen = new Set(models.map((model) => model.slug)); const fallbackCapabilities = models.find((model) => model.capabilities)?.capabilities ?? null; const customEntries: ServerProviderModel[] = []; for (const rawModel of customModels) { const slug = rawModel.trim(); if (!slug || seen.has(slug)) { continue; } seen.add(slug); customEntries.push({ slug, name: slug, isCustom: true, capabilities: fallbackCapabilities, }); } return customEntries.length === 0 ? models : [...models, ...customEntries]; } function parseCodexSkillsListResponse( response: CodexSchema.V2SkillsListResponse, cwd: string, ): ReadonlyArray<ServerProviderSkill> { const matchingEntry = response.data.find((entry) => entry.cwd === cwd); const skills = matchingEntry ? matchingEntry.skills : response.data.flatMap((entry) => entry.skills); return skills.map((skill) => { const shortDescription = skill.shortDescription ?? skill.interface?.shortDescription ?? undefined; const parsedSkill: Types.Mutable<ServerProviderSkill> = { name: skill.name, path: skill.path, enabled: skill.enabled, }; if (skill.description) { parsedSkill.description = skill.description; } if (skill.scope) { parsedSkill.scope = skill.scope; } if (skill.interface?.displayName) { parsedSkill.displayName = skill.interface.displayName; } if (shortDescription) { parsedSkill.shortDescription = shortDescription; } return parsedSkill; }); } const requestAllCodexModels = Effect.fn("requestAllCodexModels")(function* ( client: CodexClient.CodexAppServerClient["Service"], ) { const models: ServerProviderModel[] = []; let cursor: string | null | undefined = undefined; do { const response: CodexSchema.V2ModelListResponse = yield* client.request( "model/list", cursor ? { cursor } : {}, ); models.push(...parseCodexModelListResponse(response)); cursor = response.nextCursor; } while (cursor); return models; }); export function buildCodexInitializeParams(): CodexSchema.V1InitializeParams { return { clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: packageJson.version, }, capabilities: { experimentalApi: true, }, }; } const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(function* (input: { readonly binaryPath: string; readonly homePath?: string; readonly launchArgs?: string; readonly cwd: string; readonly customModels?: ReadonlyArray<string>; readonly environment?: NodeJS.ProcessEnv; }) { // `~` is not shell-expanded when env vars are set via `child_process.spawn`, // so `CODEX_HOME=~/.codex_work` would reach codex verbatim and trip // "CODEX_HOME points to '~/.codex_work', but that path does not exist". // Expand here for parity with `CodexTextGeneration`/`CodexSessionRuntime`. const resolvedHomePath = input.homePath ? expandHomePath(input.homePath) : undefined; const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const environment = { ...input.environment, ...(resolvedHomePath ? { CODEX_HOME: resolvedHomePath } : {}), }; const spawnCommand = yield* resolveSpawnCommand( input.binaryPath, codexAppServerArgs(input.launchArgs), { env: environment, extendEnv: true, }, ); const child = yield* spawner .spawn( ChildProcess.make(spawnCommand.command, spawnCommand.args, { cwd: input.cwd, env: environment, extendEnv: true, forceKillAfter: CODEX_APP_SERVER_PROBE_FORCE_KILL_AFTER, shell: spawnCommand.shell, }), ) .pipe( Effect.mapError( (cause) => new CodexErrors.CodexAppServerSpawnError({ command: `${input.binaryPath} app-server`, cause, }), ), ); const clientContext = yield* Layer.build(CodexClient.layerChildProcess(child)); const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe( Effect.provide(clientContext), ); const initialize = yield* client.request("initialize", { clientInfo: { name: "t3code_desktop", title: "T3 Code Desktop", version: "0.1.0", }, capabilities: { experimentalApi: true, }, }); yield* client.notify("initialized", undefined); // Extract the version string after the first '/' in userAgent, up to the next space or the end const versionMatch = initialize.userAgent.match(/\/([^\s]+)/); const version = versionMatch ? versionMatch[1] : undefined; const accountResponse = yield* client.request("account/read", {}); if (!accountResponse.account && accountResponse.requiresOpenaiAuth) { return { account: accountResponse, version, models: appendCustomCodexModels([], input.customModels ?? []), skills: [], } satisfies CodexAppServerProviderSnapshot; } const [skillsResponse, models] = yield* Effect.all( [ client.request("skills/list", { cwds: [input.cwd], }), requestAllCodexModels(client), ], { concurrency: "unbounded" }, ); return { account: accountResponse, version, models: applyPreferredCodexDefaultModel( appendCustomCodexModels(models, input.customModels ?? []), ), skills: parseCodexSkillsListResponse(skillsResponse, input.cwd), } satisfies CodexAppServerProviderSnapshot; }); const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvider["models"] => { const models = new Set<string>(); for (const model of codexSettings.customModels) { const trimmed = model.trim(); if (trimmed.length > 0) { models.add(trimmed); } } return Array.from(models, (model) => ({ slug: model, name: model, isCustom: true, capabilities: null, })); }; const makePendingCodexProvider = ( codexSettings: CodexSettings, ): Effect.Effect<ServerProviderDraft> => Effect.gen(function* () { const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); const models = emptyCodexModelsFromSettings(codexSettings); if (!codexSettings.enabled) { return buildServerProvider({ presentation: CODEX_PRESENTATION, enabled: false, checkedAt, models, skills: [], probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: "Codex is disabled in T3 Code settings.", }, }); } return buildServerProvider({ presentation: CODEX_PRESENTATION, enabled: true, checkedAt, models, skills: [], probe: { installed: false, version: null, status: "warning", auth: { status: "unknown" }, message: "Codex provider status has not been checked in this session yet.", }, }); }); function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): { readonly status: Exclude<ServerProviderState, "disabled">; readonly auth: ServerProvider["auth"]; readonly message?: string; } { const authLabel = codexAccountAuthLabel(account.account); const authEmail = codexAccountEmail(account.account); const auth = { status: account.account ? ("authenticated" as const) : ("unknown" as const), ...(account.account?.type ? { type: account.account?.type } : {}), ...(authLabel ? { label: authLabel } : {}), ...(authEmail ? { email: authEmail } : {}), } satisfies ServerProvider["auth"]; if (account.account) { return { status: "ready", auth }; } if (account.requiresOpenaiAuth) { return { status: "error", auth: { status: "unauthenticated" }, message: "Codex CLI is not authenticated. Run `codex login` and try again.", }; } return { status: "ready", auth }; } export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(function* ( codexSettings: CodexSettings, probe: (input: { readonly binaryPath: string;