mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: consolidate utility functions into shared package
Move duplicate isObject, asString, asNumber, and safeStringify functions from multiple modules into a centralized shared/src/utils.ts module and update imports across cli, server, and web packages. This eliminates code duplication and improves maintainability.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { AgentMessage, PlanItem } from '@/agent/types';
|
||||
import { asString, deriveToolName, isObject } from '@/agent/utils';
|
||||
import { asString, isObject } from '@hapi/protocol';
|
||||
import { deriveToolName } from '@/agent/utils';
|
||||
import { ACP_SESSION_UPDATE_TYPES } from './constants';
|
||||
|
||||
function normalizeStatus(status: unknown): 'pending' | 'in_progress' | 'completed' | 'failed' {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AgentBackend, AgentMessage, AgentSessionConfig, PermissionRequest, PermissionResponse, PromptContent } from '@/agent/types';
|
||||
import { asString, isObject } from '@/agent/utils';
|
||||
import { asString, isObject } from '@hapi/protocol';
|
||||
import { AcpStdioTransport, type AcpStderrError } from './AcpStdioTransport';
|
||||
import { AcpMessageHandler } from './AcpMessageHandler';
|
||||
import { logger } from '@/ui/logger';
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
export function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object';
|
||||
}
|
||||
|
||||
export function asString(value: unknown): string | null {
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
import { isObject } from '@hapi/protocol';
|
||||
|
||||
export function deriveToolName(input: {
|
||||
title?: string | null;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getToolName } from "./getToolName";
|
||||
import { EnhancedMode, PermissionMode } from "../loop";
|
||||
import { getToolDescriptor } from "./getToolDescriptor";
|
||||
import { delay } from "@/utils/time";
|
||||
import { isObject } from "@hapi/protocol";
|
||||
import {
|
||||
BasePermissionHandler,
|
||||
type PendingPermissionRequest,
|
||||
@@ -33,10 +34,6 @@ interface PermissionResponse {
|
||||
|
||||
const PLAN_EXIT_MODES: PermissionMode[] = ['default', 'acceptEdits', 'bypassPermissions'];
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object';
|
||||
}
|
||||
|
||||
function isAskUserQuestionToolName(toolName: string): boolean {
|
||||
return toolName === 'AskUserQuestion' || toolName === 'ask_user_question';
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { isObject } from '@hapi/protocol';
|
||||
import { logger } from '@/ui/logger';
|
||||
import { isProcessAlive, killProcess } from '@/utils/process';
|
||||
import type { CodexSessionConfig, CodexToolResponse } from './types';
|
||||
@@ -20,10 +21,6 @@ type ElicitRequestedSchema = {
|
||||
required?: string[];
|
||||
};
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object';
|
||||
}
|
||||
|
||||
function extractRequestedSchema(params: Record<string, unknown>): ElicitRequestedSchema | null {
|
||||
const raw = params.requestedSchema;
|
||||
if (!isObject(raw)) return null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import type { SyncEvent } from '../sync/syncEngine'
|
||||
|
||||
type EventEnvelope = {
|
||||
@@ -5,10 +6,6 @@ type EventEnvelope = {
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function extractEventEnvelope(message: unknown): EventEnvelope | null {
|
||||
if (!isObject(message)) {
|
||||
return null
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages'
|
||||
import { TodoItemSchema, TodosSchema } from '@hapi/protocol/schemas'
|
||||
import type { TodoItem } from '@hapi/protocol/types'
|
||||
@@ -5,10 +6,6 @@ import type { TodoItem } from '@hapi/protocol/types'
|
||||
export { TodoItemSchema, TodosSchema }
|
||||
export type { TodoItem }
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function extractTodosFromClaudeOutput(content: Record<string, unknown>): TodoItem[] | null {
|
||||
if (content.type !== 'output') return null
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './messages'
|
||||
export * from './modes'
|
||||
export * from './sessionSummary'
|
||||
export * from './utils'
|
||||
export type * from './types'
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { isObject } from './utils'
|
||||
|
||||
type RoleWrappedRecord = {
|
||||
role: string
|
||||
content: unknown
|
||||
meta?: unknown
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function isRoleWrappedRecord(value: unknown): value is RoleWrappedRecord {
|
||||
if (!isObject(value)) return false
|
||||
return typeof value.role === 'string' && 'content' in value
|
||||
|
||||
@@ -13,7 +13,8 @@ export function asNumber(value: unknown): number | null {
|
||||
export function safeStringify(value: unknown): string {
|
||||
if (typeof value === 'string') return value
|
||||
try {
|
||||
return JSON.stringify(value, null, 2)
|
||||
const stringified = JSON.stringify(value, null, 2)
|
||||
return typeof stringified === 'string' ? stringified : String(value)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages'
|
||||
import { safeStringify } from '@hapi/protocol'
|
||||
import type { DecryptedMessage } from '@/types/api'
|
||||
import type { NormalizedMessage } from '@/chat/types'
|
||||
import { safeStringify } from '@/chat/normalizeUtils'
|
||||
import { isCodexContent, isSkippableAgentContent, normalizeAgentRecord } from '@/chat/normalizeAgent'
|
||||
import { normalizeUserRecord } from '@/chat/normalizeUser'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AgentEvent, NormalizedAgentContent, NormalizedMessage, ToolResultPermission } from '@/chat/types'
|
||||
import { asNumber, asString, isObject } from '@/chat/normalizeUtils'
|
||||
import { asNumber, asString, isObject } from '@hapi/protocol'
|
||||
|
||||
function normalizeToolResultPermissions(value: unknown): ToolResultPermission | undefined {
|
||||
if (!isObject(value)) return undefined
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NormalizedMessage } from '@/chat/types'
|
||||
import type { AttachmentMetadata } from '@/types/api'
|
||||
import { isObject } from '@/chat/normalizeUtils'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
|
||||
function parseAttachments(raw: unknown): AttachmentMetadata[] | undefined {
|
||||
if (!Array.isArray(raw)) return undefined
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { NormalizedMessage } from '@/chat/types'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
|
||||
export type TracedMessage = NormalizedMessage & {
|
||||
sidechainId?: string
|
||||
@@ -10,10 +11,6 @@ type TracerState = {
|
||||
orphanMessages: Map<string, NormalizedMessage[]>
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function getMessageUuid(message: NormalizedMessage): string | null {
|
||||
if (message.role === 'agent' && message.content.length > 0) {
|
||||
const first = message.content[0] as unknown as Record<string, unknown>
|
||||
@@ -124,4 +121,3 @@ export function traceMessages(messages: NormalizedMessage[]): TracedMessage[] {
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ToolCallMessagePartProps } from '@assistant-ui/react'
|
||||
import type { ChatBlock } from '@/chat/types'
|
||||
import type { ToolCallBlock } from '@/chat/types'
|
||||
import { isObject, safeStringify } from '@hapi/protocol'
|
||||
import { getEventPresentation } from '@/chat/presentation'
|
||||
import { CodeBlock } from '@/components/CodeBlock'
|
||||
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
||||
@@ -10,20 +11,6 @@ import { ToolCard } from '@/components/ToolCard/ToolCard'
|
||||
import { useHappyChatContext } from '@/components/AssistantChat/context'
|
||||
import { CliOutputBlock } from '@/components/CliOutputBlock'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function safeStringify(value: unknown): string {
|
||||
if (typeof value === 'string') return value
|
||||
try {
|
||||
const stringified = JSON.stringify(value, null, 2)
|
||||
return typeof stringified === 'string' ? stringified : String(value)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
function isToolCallBlock(value: unknown): value is ToolCallBlock {
|
||||
if (!isObject(value)) return false
|
||||
if (value.kind !== 'tool-call') return false
|
||||
|
||||
@@ -2,14 +2,11 @@ import { useMemo, useState } from 'react'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import type { SessionMetadataSummary } from '@/types/api'
|
||||
import type { ChatToolCall, ToolPermission } from '@/chat/types'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { usePlatform } from '@/hooks/usePlatform'
|
||||
import { Spinner } from '@/components/Spinner'
|
||||
import { useTranslation } from '@/lib/use-translation'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function getInputStringAny(input: unknown, keys: string[]): string | null {
|
||||
if (!isObject(input)) return null
|
||||
for (const key of keys) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ToolCallBlock } from '@/chat/types'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
import type { SessionMetadataSummary } from '@/types/api'
|
||||
import { memo, useEffect, useMemo, useState, type ReactNode } from 'react'
|
||||
import { isObject, safeStringify } from '@hapi/protocol'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { CodeBlock } from '@/components/CodeBlock'
|
||||
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
||||
@@ -17,18 +18,6 @@ import { usePointerFocusRing } from '@/hooks/usePointerFocusRing'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useTranslation } from '@/lib/use-translation'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function safeStringify(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value, null, 2)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
function getInputString(input: unknown, key: string): string | null {
|
||||
if (!isObject(input)) return null
|
||||
const value = input[key]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isObject } from '@hapi/protocol'
|
||||
|
||||
export type AskUserQuestionOption = {
|
||||
label: string
|
||||
description: string | null
|
||||
@@ -15,10 +17,6 @@ export type AskUserQuestionQuestionInfo = {
|
||||
question: string | null
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function isAskUserQuestionToolName(toolName: string): boolean {
|
||||
return toolName === 'AskUserQuestion' || toolName === 'ask_user_question'
|
||||
}
|
||||
@@ -77,4 +75,3 @@ export function extractAskUserQuestionQuestionsInfo(input: unknown): AskUserQues
|
||||
}
|
||||
return questions
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { SessionMetadataSummary } from '@/types/api'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { BulbIcon, ClipboardIcon, EyeIcon, FileDiffIcon, GlobeIcon, PuzzleIcon, QuestionIcon, RocketIcon, SearchIcon, TerminalIcon, WrenchIcon } from '@/components/ToolCard/icons'
|
||||
import { basename, resolveDisplayPath } from '@/utils/path'
|
||||
|
||||
@@ -13,10 +14,6 @@ export type ToolPresentation = {
|
||||
minimal: boolean
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function getInputStringAny(input: unknown, keys: string[]): string | null {
|
||||
if (!isObject(input)) return null
|
||||
for (const key of keys) {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { DiffView } from '@/components/DiffView'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function parseUnifiedDiff(unifiedDiff: string): { oldText: string; newText: string; fileName?: string } {
|
||||
const lines = unifiedDiff.split('\n')
|
||||
const oldLines: string[] = []
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { basename, resolveDisplayPath } from '@/utils/path'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function CodexPatchView(props: ToolViewProps) {
|
||||
const input = props.block.tool.input
|
||||
if (!isObject(input) || !isObject(input.changes)) return null
|
||||
@@ -25,4 +22,3 @@ export function CodexPatchView(props: ToolViewProps) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { DiffView } from '@/components/DiffView'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function EditView(props: ToolViewProps) {
|
||||
const input = props.block.tool.input
|
||||
if (!isObject(input)) return null
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function ExitPlanModeView(props: ToolViewProps) {
|
||||
const input = props.block.tool.input
|
||||
if (!isObject(input)) return null
|
||||
@@ -12,4 +9,3 @@ export function ExitPlanModeView(props: ToolViewProps) {
|
||||
if (!plan) return null
|
||||
return <MarkdownRenderer content={plan} />
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { DiffView } from '@/components/DiffView'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
type Edit = { old_string: string; new_string: string }
|
||||
|
||||
const MAX_COMPACT_EDITS = 3
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
|
||||
type TodoItem = {
|
||||
id?: string
|
||||
@@ -7,10 +8,6 @@ type TodoItem = {
|
||||
priority?: 'high' | 'medium' | 'low'
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function extractTodos(input: unknown, result: unknown): TodoItem[] {
|
||||
const todosFromInput = isObject(input) && Array.isArray(input.todos)
|
||||
? input.todos.filter(isObject)
|
||||
@@ -63,4 +60,3 @@ export function TodoWriteView(props: ToolViewProps) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import { DiffView } from '@/components/DiffView'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
export function WriteView(props: ToolViewProps) {
|
||||
const input = props.block.tool.input
|
||||
if (!isObject(input)) return null
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import type { ToolViewComponent, ToolViewProps } from '@/components/ToolCard/views/_all'
|
||||
import { isObject, safeStringify } from '@hapi/protocol'
|
||||
import { CodeBlock } from '@/components/CodeBlock'
|
||||
import { MarkdownRenderer } from '@/components/MarkdownRenderer'
|
||||
import { basename, resolveDisplayPath } from '@/utils/path'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function safeStringify(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value, null, 2)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
function parseToolUseError(message: string): { isToolUseError: boolean; errorMessage: string | null } {
|
||||
const regex = /<tool_use_error>(.*?)<\/tool_use_error>/s
|
||||
const match = message.match(regex)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import type { SyncEvent } from '@/types/api'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
import { clearMessageWindow, ingestIncomingMessages } from '@/lib/message-window-store'
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
type SSESubscription = {
|
||||
all?: boolean
|
||||
sessionId?: string
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { AppendMessage, AttachmentAdapter, ThreadMessageLike } from '@assistant-ui/react'
|
||||
import { useExternalMessageConverter, useExternalStoreRuntime } from '@assistant-ui/react'
|
||||
import { safeStringify } from '@hapi/protocol'
|
||||
import { renderEventLabel } from '@/chat/presentation'
|
||||
import type { ChatBlock, CliOutputBlock } from '@/chat/types'
|
||||
import type { AgentEvent, ToolCallBlock } from '@/chat/types'
|
||||
import type { AttachmentMetadata, MessageStatus as HappyMessageStatus, Session } from '@/types/api'
|
||||
|
||||
function safeStringify(value: unknown): string {
|
||||
if (typeof value === 'string') return value
|
||||
try {
|
||||
const stringified = JSON.stringify(value, null, 2)
|
||||
return typeof stringified === 'string' ? stringified : String(value)
|
||||
} catch {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
export type HappyChatMessageMetadata = {
|
||||
kind: 'user' | 'assistant' | 'tool' | 'event' | 'cli-output'
|
||||
status?: HappyMessageStatus
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages'
|
||||
import { isObject } from '@hapi/protocol'
|
||||
import type { DecryptedMessage, Session } from '@/types/api'
|
||||
import { VOICE_CONFIG } from '../voiceConfig'
|
||||
|
||||
@@ -22,10 +23,6 @@ function isContentArray(content: unknown): content is ContentItem[] {
|
||||
return Array.isArray(content)
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object'
|
||||
}
|
||||
|
||||
function normalizeRole(role: string | null | undefined): NormalizedRole | null {
|
||||
if (role === 'agent' || role === 'assistant') return 'assistant'
|
||||
if (role === 'user') return 'user'
|
||||
|
||||
Reference in New Issue
Block a user