582 lines · 16.5 KB · TypeScript
RawDownload
import * as Clock from "effect/Clock";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import type * as EffectAcpSchema from "effect-acp/schema";import { deriveToolActivityPresentation } from "@t3tools/shared/toolActivity";import type { ToolLifecycleItemType } from "@t3tools/contracts"; function isRecord(value: unknown): value is Record<string, unknown> {  return typeof value === "object" && value !== null && !Array.isArray(value);} function isSessionModelState(value: unknown): value is EffectAcpSchema.SessionModelState {  if (!isRecord(value) || typeof value.currentModelId !== "string") {    return false;  }  if (!Array.isArray(value.availableModels)) {    return false;  }  return value.availableModels.every(    (model) =>      isRecord(model) &&      typeof model.modelId === "string" &&      typeof model.name === "string" &&      (model.description === undefined ||        model.description === null ||        typeof model.description === "string"),  );} function isSessionModeState(value: unknown): value is EffectAcpSchema.SessionModeState {  if (!isRecord(value) || typeof value.currentModeId !== "string") {    return false;  }  if (!Array.isArray(value.availableModes)) {    return false;  }  return value.availableModes.every(    (mode) =>      isRecord(mode) &&      typeof mode.id === "string" &&      typeof mode.name === "string" &&      (mode.description === undefined || typeof mode.description === "string"),  );} export interface AcpSessionMode {  readonly id: string;  readonly name: string;  readonly description?: string;} export interface AcpSessionModeState {  readonly currentModeId: string;  readonly availableModes: ReadonlyArray<AcpSessionMode>;} export interface AcpToolCallState {  readonly toolCallId: string;  readonly kind?: string;  readonly title?: string;  readonly status?: "pending" | "inProgress" | "completed" | "failed";  readonly command?: string;  readonly detail?: string;  readonly data: Record<string, unknown>;} export interface AcpPlanUpdate {  readonly explanation?: string | null;  readonly plan: ReadonlyArray<{    readonly step: string;    readonly status: "pending" | "inProgress" | "completed";  }>;} export interface AcpPermissionRequest {  readonly kind: string | "unknown";  readonly detail?: string;  readonly toolCall?: AcpToolCallState;} export type AcpParsedSessionEvent =  | {      readonly _tag: "ModeChanged";      readonly modeId: string;    }  | {      readonly _tag: "AssistantItemStarted";      readonly itemId: string;    }  | {      readonly _tag: "AssistantItemCompleted";      readonly itemId: string;    }  | {      readonly _tag: "PlanUpdated";      readonly payload: AcpPlanUpdate;      readonly rawPayload: unknown;    }  | {      readonly _tag: "ToolCallUpdated";      readonly toolCall: AcpToolCallState;      readonly rawPayload: unknown;    }  | {      readonly _tag: "ContentDelta";      readonly itemId?: string;      readonly text: string;      readonly rawPayload: unknown;    }; type AcpSessionSetupResponse =  | EffectAcpSchema.LoadSessionResponse  | EffectAcpSchema.NewSessionResponse  | EffectAcpSchema.ResumeSessionResponse; type AcpToolCallUpdate = Extract<  EffectAcpSchema.SessionNotification["update"],  { readonly sessionUpdate: "tool_call" | "tool_call_update" }>; export function extractModelConfigId(sessionResponse: AcpSessionSetupResponse): string | undefined {  const configOptions = sessionResponse.configOptions;  if (!configOptions) return undefined;  for (const opt of configOptions) {    if (opt.category === "model" && opt.id.trim().length > 0) {      return opt.id.trim();    }  }  return undefined;} export function findSessionConfigOption(  configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption> | null | undefined,  configId: string,): EffectAcpSchema.SessionConfigOption | undefined {  if (!configOptions) {    return undefined;  }  const normalizedConfigId = configId.trim();  if (!normalizedConfigId) {    return undefined;  }  return configOptions.find((option) => option.id.trim() === normalizedConfigId);} export function collectSessionConfigOptionValues(  configOption: EffectAcpSchema.SessionConfigOption,): ReadonlyArray<string> {  if (configOption.type !== "select") {    return [];  }  return configOption.options.flatMap((entry) =>    "value" in entry ? [entry.value] : entry.options.map((option) => option.value),  );} export function parseSessionModeState(  sessionResponse: AcpSessionSetupResponse,): AcpSessionModeState | undefined {  const modes = sessionResponse.modes;  if (!modes) return undefined;  const currentModeId = modes.currentModeId.trim();  if (!currentModeId) {    return undefined;  }  const availableModes: Array<AcpSessionMode> = [];  for (const mode of modes.availableModes) {    const id = mode.id.trim();    const name = mode.name.trim();    if (!id || !name) {      continue;    }    const description = mode.description?.trim() || undefined;    availableModes.push(      description !== undefined        ? ({ id, name, description } satisfies AcpSessionMode)        : ({ id, name } satisfies AcpSessionMode),    );  }  if (availableModes.length === 0) {    return undefined;  }  return {    currentModeId,    availableModes,  };} function normalizePlanStepStatus(raw: unknown): "pending" | "inProgress" | "completed" {  switch (raw) {    case "completed":      return "completed";    case "in_progress":    case "inProgress":      return "inProgress";    default:      return "pending";  }} function normalizeToolCallStatus(  raw: unknown,  fallback?: "pending" | "inProgress" | "completed" | "failed",): "pending" | "inProgress" | "completed" | "failed" | undefined {  switch (raw) {    case "pending":      return "pending";    case "in_progress":    case "inProgress":      return "inProgress";    case "completed":      return "completed";    case "failed":      return "failed";    default:      return fallback;  }} function normalizeCommandValue(value: unknown): string | undefined {  if (typeof value === "string" && value.trim().length > 0) {    return value.trim();  }  if (!Array.isArray(value)) {    return undefined;  }  const parts: Array<string> = [];  for (const entry of value) {    if (typeof entry === "string") {      const part = entry.trim();      if (part.length > 0) {        parts.push(part);      }    }  }  return parts.length > 0 ? parts.join(" ") : undefined;} function extractCommandFromTitle(title: string | undefined): string | undefined {  if (!title) {    return undefined;  }  const match = /`([^`]+)`/.exec(title);  return match?.[1]?.trim() || undefined;} function extractToolCallCommand(rawInput: unknown, title: string | undefined): string | undefined {  if (isRecord(rawInput)) {    const directCommand = normalizeCommandValue(rawInput.command);    if (directCommand) {      return directCommand;    }    const executable = typeof rawInput.executable === "string" ? rawInput.executable.trim() : "";    const args = normalizeCommandValue(rawInput.args);    if (executable && args) {      return `${executable} ${args}`;    }    if (executable) {      return executable;    }  }  return extractCommandFromTitle(title);} function extractTextContentFromToolCallContent(  content: ReadonlyArray<EffectAcpSchema.ToolCallContent> | null | undefined,): string | undefined {  if (!content) return undefined;  const chunks: Array<string> = [];  for (const entry of content) {    if (entry.type !== "content") {      continue;    }    const nestedContent = entry.content;    if (nestedContent.type !== "text") {      continue;    }    const text = nestedContent.text.trim();    if (text.length > 0) {      chunks.push(text);    }  }  return chunks.length > 0 ? chunks.join("\n") : undefined;} function normalizeToolKind(kind: unknown): string | undefined {  return typeof kind === "string" && kind.trim().length > 0 ? kind.trim() : undefined;} function canonicalItemTypeFromAcpToolKind(kind: string | undefined): ToolLifecycleItemType {  switch (kind) {    case "execute":      return "command_execution";    case "edit":    case "delete":    case "move":      return "file_change";    case "search":    case "fetch":      return "web_search";    default:      return "dynamic_tool_call";  }} function makeToolCallState(  input: {    readonly toolCallId: string;    readonly title?: string | null | undefined;    readonly kind?: EffectAcpSchema.ToolKind | null | undefined;    readonly status?: EffectAcpSchema.ToolCallStatus | null | undefined;    readonly rawInput?: unknown;    readonly rawOutput?: unknown;    readonly content?: ReadonlyArray<EffectAcpSchema.ToolCallContent> | null | undefined;    readonly locations?: ReadonlyArray<EffectAcpSchema.ToolCallLocation> | null | undefined;  },  options?: {    readonly fallbackStatus?: "pending" | "inProgress" | "completed" | "failed";  },): AcpToolCallState | undefined {  const toolCallId = input.toolCallId.trim();  if (!toolCallId) {    return undefined;  }  const title = input.title?.trim() || undefined;  const command = extractToolCallCommand(input.rawInput, title);  const textContent = extractTextContentFromToolCallContent(input.content);  const normalizedTitle =    title && title.toLowerCase() !== "terminal" && title.toLowerCase() !== "tool call"      ? title      : undefined;  const data: Record<string, unknown> = { toolCallId };  const kind = normalizeToolKind(input.kind);  if (kind) {    data.kind = kind;  }  if (command) {    data.command = command;  }  if (input.rawInput !== undefined) {    data.rawInput = input.rawInput;  }  if (input.rawOutput !== undefined) {    data.rawOutput = input.rawOutput;  }  if (input.content !== undefined) {    data.content = input.content;  }  if (input.locations !== undefined) {    data.locations = input.locations;  }  const fallbackDetail = command ?? normalizedTitle ?? textContent;  const hasPresentationSeed =    title !== undefined ||    kind !== undefined ||    command !== undefined ||    normalizedTitle !== undefined ||    textContent !== undefined;  const presentation = hasPresentationSeed    ? deriveToolActivityPresentation({        itemType: canonicalItemTypeFromAcpToolKind(kind),        title,        detail: fallbackDetail,        data,        fallbackSummary: title ?? "Tool",      })    : undefined;  const status = normalizeToolCallStatus(input.status, options?.fallbackStatus);  return {    toolCallId,    ...(kind ? { kind } : {}),    ...(presentation?.summary ? { title: presentation.summary } : {}),    ...(status ? { status } : {}),    ...(command ? { command } : {}),    ...(presentation?.detail ? { detail: presentation.detail } : {}),    data,  };} function parseTypedToolCallState(  event: AcpToolCallUpdate,  options?: {    readonly fallbackStatus?: "pending" | "inProgress" | "completed" | "failed";  },): AcpToolCallState | undefined {  return makeToolCallState(    {      toolCallId: event.toolCallId,      title: event.title,      kind: event.kind,      status: event.status,      rawInput: event.rawInput,      rawOutput: event.rawOutput,      content: event.content,      locations: event.locations,    },    options,  );} export function mergeToolCallState(  previous: AcpToolCallState | undefined,  next: AcpToolCallState,): AcpToolCallState {  const nextKind = typeof next.data.kind === "string" ? next.data.kind : undefined;  const kind = nextKind ?? previous?.kind;  const title = next.title ?? previous?.title;  const status = next.status ?? previous?.status;  const command = next.command ?? previous?.command;  const detail = next.detail ?? previous?.detail;  return {    toolCallId: next.toolCallId,    ...(kind ? { kind } : {}),    ...(title ? { title } : {}),    ...(status ? { status } : {}),    ...(command ? { command } : {}),    ...(detail ? { detail } : {}),    data: {      ...previous?.data,      ...next.data,    },  };} export function parsePermissionRequest(  params: EffectAcpSchema.RequestPermissionRequest,): AcpPermissionRequest {  const toolCall = makeToolCallState(    {      toolCallId: params.toolCall.toolCallId,      title: params.toolCall.title,      kind: params.toolCall.kind,      status: params.toolCall.status,      rawInput: params.toolCall.rawInput,      rawOutput: params.toolCall.rawOutput,      content: params.toolCall.content,      locations: params.toolCall.locations,    },    { fallbackStatus: "pending" },  );  const kind = normalizeToolKind(params.toolCall.kind) ?? "unknown";  const detail =    toolCall?.command ??    toolCall?.title ??    toolCall?.detail ??    (typeof params.sessionId === "string" ? `Session ${params.sessionId}` : undefined);  return {    kind,    ...(detail ? { detail } : {}),    ...(toolCall ? { toolCall } : {}),  };} export function sessionUpdateIsReplay(params: EffectAcpSchema.SessionNotification): boolean {  const meta = params._meta;  return isRecord(meta) && meta.isReplay === true;} export interface SessionLoadGate {  readonly active: boolean;  readonly lastActivityAtMillis: number | undefined;  readonly idleGap: Duration.Duration;  readonly initializeResult: EffectAcpSchema.InitializeResponse;} export const waitForSessionLoadReplayIdle = (input: {  readonly gateRef: Ref.Ref<Option.Option<SessionLoadGate>>;}): Effect.Effect<EffectAcpSchema.LoadSessionResponse, never> =>  Effect.gen(function* () {    const pollInterval = Duration.millis(25);    while (true) {      const gate = yield* Ref.get(input.gateRef);      if (        Option.isSome(gate) &&        gate.value.active &&        gate.value.lastActivityAtMillis !== undefined      ) {        const idleGapMillis = Duration.toMillis(gate.value.idleGap);        const nowMillis = yield* Clock.currentTimeMillis;        if (nowMillis - gate.value.lastActivityAtMillis >= idleGapMillis) {          return syntheticLoadSessionResponseFromInitialize(gate.value.initializeResult);        }      }      yield* Effect.sleep(pollInterval);    }  }); export function syntheticLoadSessionResponseFromInitialize(  initializeResult: EffectAcpSchema.InitializeResponse,): EffectAcpSchema.LoadSessionResponse {  const meta = initializeResult._meta;  const modelState = isRecord(meta) ? meta.modelState : undefined;  const modeState = isRecord(meta) ? meta.modeState : undefined;  const models = isSessionModelState(modelState) ? modelState : undefined;  const modes = isSessionModeState(modeState) ? modeState : undefined;   return {    ...(models ? { models } : {}),

Lines 1–500 of 582.