188 lines · 7.3 KB · TypeScript
RawDownload
import { type GrokSettings, ProviderDriverKind, type RuntimeMode } from "@t3tools/contracts";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Scope from "effect/Scope";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import * as EffectAcpErrors from "effect-acp/errors";import type * as EffectAcpSchema from "effect-acp/schema";import { normalizeModelSlug } from "@t3tools/shared/model"; import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";import { makeXAiPromptCompletionRuntime } from "./XAiAcpExtension.ts"; const GROK_API_KEY_ENV = "XAI_API_KEY";const GROK_OAUTH2_REFERRER_ENV = "GROK_OAUTH2_REFERRER";const T3_CODE_OAUTH_REFERRER = "t3code";const GROK_AUTH_METHOD_API_KEY = "xai.api_key";const GROK_AUTH_METHOD_CACHED_TOKEN = "cached_token";const GROK_DRIVER_KIND = ProviderDriverKind.make("grok"); type GrokAcpRuntimeGrokSettings = Pick<GrokSettings, "binaryPath">; interface GrokAcpRuntimeInput extends Omit<  AcpSessionRuntime.AcpSessionRuntimeOptions,  "authMethodId" | "clientCapabilities" | "spawn"> {  readonly childProcessSpawner: ChildProcessSpawner.ChildProcessSpawner["Service"];  readonly grokSettings: GrokAcpRuntimeGrokSettings | null | undefined;  readonly environment?: NodeJS.ProcessEnv;  readonly runtimeMode?: RuntimeMode;} export function grokAcpSpawnArgs(runtimeMode?: RuntimeMode): ReadonlyArray<string> {  switch (runtimeMode) {    case "approval-required":      return ["--permission-mode", "default", "agent", "stdio"];    case "auto-accept-edits":      return ["--permission-mode", "acceptEdits", "agent", "stdio"];    case "auto":      return ["--permission-mode", "auto", "agent", "stdio"];    case "full-access":      return ["agent", "--always-approve", "stdio"];    default:      return ["agent", "stdio"];  }} export function buildGrokAcpSpawnInput(  grokSettings: GrokAcpRuntimeGrokSettings | null | undefined,  cwd: string,  environment?: NodeJS.ProcessEnv,  runtimeMode?: RuntimeMode,): AcpSessionRuntime.AcpSpawnInput {  return {    command: grokSettings?.binaryPath || "grok",    args: [...grokAcpSpawnArgs(runtimeMode)],    cwd,    env: {      ...environment,      [GROK_OAUTH2_REFERRER_ENV]: T3_CODE_OAUTH_REFERRER,    },  };} function resolveGrokAuthMethodId(environment: NodeJS.ProcessEnv | undefined): string {  return environment?.[GROK_API_KEY_ENV]?.trim()    ? GROK_AUTH_METHOD_API_KEY    : GROK_AUTH_METHOD_CACHED_TOKEN;} export const makeGrokAcpRuntime = (  input: GrokAcpRuntimeInput,): Effect.Effect<  AcpSessionRuntime.AcpSessionRuntime["Service"],  EffectAcpErrors.AcpError,  Crypto.Crypto | Scope.Scope> =>  Effect.gen(function* () {    const acpContext = yield* Layer.build(      AcpSessionRuntime.layer({        ...input,        spawn: buildGrokAcpSpawnInput(          input.grokSettings,          input.cwd,          input.environment,          input.runtimeMode,        ),        authMethodId: resolveGrokAuthMethodId(input.environment),      }).pipe(        Layer.provide(          Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, input.childProcessSpawner),        ),      ),    );    const runtime = yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe(      Effect.provide(acpContext),    );    return yield* makeXAiPromptCompletionRuntime(runtime);  }); /** * T3's built-in Grok slug. It is the CLI's product name, not a model id the ACP accepts, * so selecting it means "use whatever model the Grok session currently runs on". */export const GROK_DEFAULT_MODEL_SLUG = "grok-build"; export function resolveGrokAcpBaseModelId(model: string | null | undefined): string {  const trimmed = model?.trim();  const base = trimmed && trimmed.length > 0 ? trimmed : GROK_DEFAULT_MODEL_SLUG;  return normalizeModelSlug(base, GROK_DRIVER_KIND) ?? GROK_DEFAULT_MODEL_SLUG;} const GROK_REASONING_EFFORT_TOKEN = /^[a-z0-9][a-z0-9._-]{0,31}$/i; export function isValidGrokReasoningEffortToken(value: string): boolean {  return GROK_REASONING_EFFORT_TOKEN.test(value);} export function normalizeGrokReasoningEffort(value: string | undefined): string | undefined {  const effort = value?.trim();  return effort && isValidGrokReasoningEffortToken(effort) ? effort : undefined;} export function currentGrokModelIdFromSessionSetup(  sessionSetupResult:    | EffectAcpSchema.LoadSessionResponse    | EffectAcpSchema.NewSessionResponse    | EffectAcpSchema.ResumeSessionResponse,): string | undefined {  return sessionSetupResult.models?.currentModelId?.trim() || undefined;} export function currentGrokReasoningEffortFromSessionSetup(  sessionSetupResult:    | EffectAcpSchema.LoadSessionResponse    | EffectAcpSchema.NewSessionResponse    | EffectAcpSchema.ResumeSessionResponse,): string | undefined {  const modelState = sessionSetupResult.models;  if (!modelState) {    return undefined;  }  const currentModelId = modelState.currentModelId.trim();  if (currentModelId.length === 0) {    return undefined;  }  const currentModel = modelState.availableModels.find(    (model) => model.modelId.trim() === currentModelId,  );  const reasoningEffort = currentModel?._meta?.reasoningEffort;  return typeof reasoningEffort === "string"    ? normalizeGrokReasoningEffort(reasoningEffort)    : undefined;} export function applyGrokAcpModelSelection<E>(input: {  readonly runtime: Pick<AcpSessionRuntime.AcpSessionRuntime["Service"], "setSessionModel">;  readonly currentModelId: string | undefined;  readonly currentReasoningEffort?: string | undefined;  readonly requestedModelId: string | undefined;  readonly requestedReasoningEffort?: string | undefined;  readonly mapError: (cause: EffectAcpErrors.AcpError) => E;}): Effect.Effect<string | undefined, E> {  // The product slug is never sent over the wire; it keeps the session's current model.  const requestedModelId =    input.requestedModelId === GROK_DEFAULT_MODEL_SLUG ? undefined : input.requestedModelId;  const modelChanged = requestedModelId !== undefined && requestedModelId !== input.currentModelId;  const reasoningProvided = input.requestedReasoningEffort !== undefined;  const reasoningEffort = reasoningProvided    ? normalizeGrokReasoningEffort(input.requestedReasoningEffort)    : undefined;  const reasoningEffortChanged =    reasoningProvided && reasoningEffort !== input.currentReasoningEffort;  const targetModelId = requestedModelId ?? input.currentModelId;  if ((!modelChanged && !reasoningEffortChanged) || targetModelId === undefined) {    return Effect.succeed(input.currentModelId);  }  const reasoningMeta =    reasoningProvided && reasoningEffort !== undefined ? { reasoningEffort } : undefined;  // When reasoning was explicitly provided but invalid (normalize => undefined), we deliberately  // send no meta so the invalid value is dropped rather than forwarded. When reasoning was not  // provided at all, we also send no meta, but we only reach this call when the model itself  // changed - an omitted reasoning preference must not be treated as an explicit clear of the  // CLI-advertised default (e.g. Extra High) on same-model reselections.  return input.runtime    .setSessionModel(targetModelId, reasoningMeta)    .pipe(Effect.mapError(input.mapError), Effect.as(targetModelId));}