fix: remove runtime helper as-any casts
This commit is contained in:
parent
ba36ca454d
commit
ac807aeb08
|
|
@ -10,7 +10,7 @@ import { getGlobalConfig } from '../utils/config.js'
|
||||||
import { getClaudeAIOAuthTokens } from '../utils/auth.js'
|
import { getClaudeAIOAuthTokens } from '../utils/auth.js'
|
||||||
import { getOauthConfig } from '../constants/oauth.js'
|
import { getOauthConfig } from '../constants/oauth.js'
|
||||||
import { getUserAgent } from '../utils/http.js'
|
import { getUserAgent } from '../utils/http.js'
|
||||||
import type { Message } from '../types/message.js'
|
import type { ContentItem, Message } from '../types/message.js'
|
||||||
|
|
||||||
// ─── Rate limiting ──────────────────────────────────
|
// ─── Rate limiting ──────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -64,6 +64,15 @@ export function triggerCompanionReaction(
|
||||||
|
|
||||||
// ─── Helpers ────────────────────────────────────────
|
// ─── Helpers ────────────────────────────────────────
|
||||||
|
|
||||||
|
function getMessageContent(
|
||||||
|
message: Message,
|
||||||
|
): string | ContentItem[] | undefined {
|
||||||
|
const content = message.message?.content
|
||||||
|
return typeof content === 'string' || Array.isArray(content)
|
||||||
|
? content
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
function isAddressed(messages: Message[], name: string): boolean {
|
function isAddressed(messages: Message[], name: string): boolean {
|
||||||
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
|
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
|
||||||
for (
|
for (
|
||||||
|
|
@ -73,7 +82,7 @@ function isAddressed(messages: Message[], name: string): boolean {
|
||||||
) {
|
) {
|
||||||
const m = messages[i]
|
const m = messages[i]
|
||||||
if (m?.type !== 'user') continue
|
if (m?.type !== 'user') continue
|
||||||
const content = (m as any).message?.content
|
const content = getMessageContent(m)
|
||||||
if (typeof content === 'string' && pattern.test(content)) return true
|
if (typeof content === 'string' && pattern.test(content)) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -89,14 +98,17 @@ function buildTranscript(messages: Message[]): string {
|
||||||
.filter(m => m.type === 'user' || m.type === 'assistant')
|
.filter(m => m.type === 'user' || m.type === 'assistant')
|
||||||
.map(m => {
|
.map(m => {
|
||||||
const role = m.type === 'user' ? 'user' : 'claude'
|
const role = m.type === 'user' ? 'user' : 'claude'
|
||||||
const content = (m as any).message?.content
|
const content = getMessageContent(m)
|
||||||
const text =
|
const text =
|
||||||
typeof content === 'string'
|
typeof content === 'string'
|
||||||
? content.slice(0, 300)
|
? content.slice(0, 300)
|
||||||
: Array.isArray(content)
|
: Array.isArray(content)
|
||||||
? content
|
? content
|
||||||
.filter((b: any) => b?.type === 'text')
|
.filter(
|
||||||
.map((b: any) => b.text)
|
(b): b is Extract<ContentItem, { type: 'text' }> =>
|
||||||
|
b.type === 'text',
|
||||||
|
)
|
||||||
|
.map(b => b.text)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.slice(0, 300)
|
.slice(0, 300)
|
||||||
: ''
|
: ''
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,8 @@ export async function getOutputStyleConfig(): Promise<OutputStyleConfig | null>
|
||||||
const forcedStyles = Object.values(allStyles).filter(
|
const forcedStyles = Object.values(allStyles).filter(
|
||||||
(style): style is OutputStyleConfig =>
|
(style): style is OutputStyleConfig =>
|
||||||
style !== null &&
|
style !== null &&
|
||||||
(style as any).source === 'plugin' &&
|
style.source === 'plugin' &&
|
||||||
(style as any).forceForPlugin === true,
|
style.forceForPlugin === true,
|
||||||
)
|
)
|
||||||
|
|
||||||
const firstForcedStyle = forcedStyles[0]
|
const firstForcedStyle = forcedStyles[0]
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
import { describe, test, expect, mock, beforeEach } from 'bun:test'
|
import { describe, test, expect, mock, beforeEach } from 'bun:test'
|
||||||
|
|
||||||
// --- MACRO 全局注入 (编译时 define 在测试中不可用) ---
|
// --- MACRO 全局注入 (编译时 define 在测试中不可用) ---
|
||||||
;(globalThis as any).MACRO = {
|
globalThis.MACRO = {
|
||||||
VERSION: '2.1.888',
|
VERSION: '2.1.888',
|
||||||
BUILD_TIME: '2026-04-22T00:00:00Z',
|
BUILD_TIME: '2026-04-22T00:00:00Z',
|
||||||
FEEDBACK_CHANNEL: '',
|
FEEDBACK_CHANNEL: '',
|
||||||
|
|
@ -214,7 +214,7 @@ const standardTools: Tools = [
|
||||||
{ name: 'Agent' },
|
{ name: 'Agent' },
|
||||||
{ name: 'AskUserQuestion' },
|
{ name: 'AskUserQuestion' },
|
||||||
{ name: 'TaskCreate' },
|
{ name: 'TaskCreate' },
|
||||||
] as any
|
] as unknown as Tools
|
||||||
|
|
||||||
async function getFullPrompt(
|
async function getFullPrompt(
|
||||||
tools: Tools = standardTools,
|
tools: Tools = standardTools,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { isEnvTruthy } from '../utils/envUtils.js';
|
||||||
// Runtime fallback for MACRO.* when not injected by build/dev defines.
|
// Runtime fallback for MACRO.* when not injected by build/dev defines.
|
||||||
// This happens when running cli.tsx directly (not via `bun run dev` or built dist/).
|
// This happens when running cli.tsx directly (not via `bun run dev` or built dist/).
|
||||||
if (typeof globalThis.MACRO === 'undefined') {
|
if (typeof globalThis.MACRO === 'undefined') {
|
||||||
(globalThis as any).MACRO = {
|
globalThis.MACRO = {
|
||||||
VERSION: process.env.CLAUDE_CODE_VERSION || '2.1.888',
|
VERSION: process.env.CLAUDE_CODE_VERSION || '2.1.888',
|
||||||
BUILD_TIME: new Date().toISOString(),
|
BUILD_TIME: new Date().toISOString(),
|
||||||
FEEDBACK_CHANNEL: '',
|
FEEDBACK_CHANNEL: '',
|
||||||
|
|
|
||||||
|
|
@ -142,9 +142,9 @@ export async function startMCPServer(
|
||||||
(args as never) ?? {},
|
(args as never) ?? {},
|
||||||
toolUseContext,
|
toolUseContext,
|
||||||
)
|
)
|
||||||
if (validationResult && !validationResult.result) {
|
if (validationResult?.result === false) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Tool ${name} input is invalid: ${(validationResult as any).message}`,
|
`Tool ${name} input is invalid: ${validationResult.message}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const finalResult = await tool.call(
|
const finalResult = await tool.call(
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ async function isAppleTerminalBellDisabled(): Promise<boolean> {
|
||||||
// Lazy-load plist (~280KB with xmlbuilder+@xmldom) — only hit on
|
// Lazy-load plist (~280KB with xmlbuilder+@xmldom) — only hit on
|
||||||
// Apple_Terminal with auto-channel, which is a small fraction of users.
|
// Apple_Terminal with auto-channel, which is a small fraction of users.
|
||||||
const plist = await import('plist')
|
const plist = await import('plist')
|
||||||
const parsed: Record<string, unknown> = plist.parse(defaultsOutput.stdout) as any
|
const parsed = plist.parse(defaultsOutput.stdout) as Record<string, unknown>
|
||||||
const windowSettings = parsed?.['Window Settings'] as
|
const windowSettings = parsed?.['Window Settings'] as
|
||||||
| Record<string, unknown>
|
| Record<string, unknown>
|
||||||
| undefined
|
| undefined
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ export async function startSkillDiscoveryPrefetch(
|
||||||
durationMs: Date.now() - startedAt,
|
durationMs: Date.now() - startedAt,
|
||||||
indexSize: index.length,
|
indexSize: index.length,
|
||||||
method: 'tfidf',
|
method: 'tfidf',
|
||||||
} as any
|
}
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[skill-search] prefetch found ${newResults.length} skills in ${signal.durationMs}ms`,
|
`[skill-search] prefetch found ${newResults.length} skills in ${signal.durationMs}ms`,
|
||||||
|
|
@ -306,7 +306,7 @@ export async function getTurnZeroSkillDiscovery(
|
||||||
durationMs: Date.now() - startedAt,
|
durationMs: Date.now() - startedAt,
|
||||||
indexSize: index.length,
|
indexSize: index.length,
|
||||||
method: 'tfidf',
|
method: 'tfidf',
|
||||||
} as any
|
}
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[skill-search] turn-zero found ${results.length} skills in ${signal.durationMs}ms`,
|
`[skill-search] turn-zero found ${results.length} skills in ${signal.durationMs}ms`,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// STUB: 待补全 — 见 docs/devlog/02-tsc-stubs.md
|
|
||||||
// Skill search signal types — used by skill search prefetch and attachment utilities.
|
|
||||||
// DiscoverySignal represents the signal emitted when a skill is discovered during prefetch.
|
|
||||||
|
|
||||||
export type DiscoverySignal = {
|
export type DiscoverySignal = {
|
||||||
type: 'skill_discovery'
|
trigger: 'assistant_turn' | 'user_input'
|
||||||
skillId: string
|
queryText: string
|
||||||
[key: string]: unknown
|
startedAt: number
|
||||||
|
durationMs: number
|
||||||
|
indexSize: number
|
||||||
|
method: 'tfidf'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -479,7 +479,13 @@ function roughTokenCountEstimationForBlock(
|
||||||
return 2000
|
return 2000
|
||||||
}
|
}
|
||||||
if (block.type === 'tool_result') {
|
if (block.type === 'tool_result') {
|
||||||
return roughTokenCountEstimationForContent(block.content as any)
|
return roughTokenCountEstimationForContent(
|
||||||
|
block.content as
|
||||||
|
| string
|
||||||
|
| Array<Anthropic.ContentBlock>
|
||||||
|
| Array<Anthropic.ContentBlockParam>
|
||||||
|
| undefined,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (block.type === 'tool_use') {
|
if (block.type === 'tool_use') {
|
||||||
// input is the JSON the model generated — arbitrarily large (bash
|
// input is the JSON the model generated — arbitrarily large (bash
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import type { BetaContentBlock, BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
import type {
|
||||||
|
BetaContentBlock,
|
||||||
|
BetaUsage,
|
||||||
|
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
||||||
import { createHash, randomUUID, type UUID } from 'crypto'
|
import { createHash, randomUUID, type UUID } from 'crypto'
|
||||||
import { mkdir, readFile, writeFile } from 'fs/promises'
|
import { mkdir, readFile, writeFile } from 'fs/promises'
|
||||||
import isPlainObject from 'lodash-es/isPlainObject.js'
|
import isPlainObject from 'lodash-es/isPlainObject.js'
|
||||||
|
|
@ -9,6 +12,7 @@ import { calculateUSDCost } from 'src/utils/modelCost.js'
|
||||||
import type {
|
import type {
|
||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
Message,
|
Message,
|
||||||
|
MessageContent,
|
||||||
StreamEvent,
|
StreamEvent,
|
||||||
SystemAPIErrorMessage,
|
SystemAPIErrorMessage,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
|
|
@ -252,24 +256,27 @@ function mapAssistantMessage(
|
||||||
message: {
|
message: {
|
||||||
...message.message,
|
...message.message,
|
||||||
content: (message.message.content as BetaContentBlock[])
|
content: (message.message.content as BetaContentBlock[])
|
||||||
.map(_ => {
|
.map(block => {
|
||||||
switch (_.type) {
|
switch (block.type) {
|
||||||
case 'text':
|
case 'text':
|
||||||
return {
|
return {
|
||||||
..._,
|
...block,
|
||||||
text: f(_.text) as string,
|
text: f(block.text) as string,
|
||||||
citations: _.citations || [],
|
citations: block.citations || [],
|
||||||
} // Ensure citations
|
} // Ensure citations
|
||||||
case 'tool_use':
|
case 'tool_use':
|
||||||
return {
|
return {
|
||||||
..._,
|
...block,
|
||||||
input: mapValuesDeep(_.input as Record<string, unknown>, f),
|
input: mapValuesDeep(block.input as Record<string, unknown>, f),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return _ // Handle other block types unchanged
|
return block // Handle other block types unchanged
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean) as any,
|
.filter(
|
||||||
|
(block): block is NonNullable<typeof block> =>
|
||||||
|
block !== null && block !== undefined,
|
||||||
|
) as unknown as MessageContent,
|
||||||
},
|
},
|
||||||
type: 'assistant',
|
type: 'assistant',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type SetAppStateFn = (updater: (prev: AppState) => AppState) => void
|
||||||
|
|
||||||
export function killTask(taskId: string, setAppState: SetAppStateFn): void {
|
export function killTask(taskId: string, setAppState: SetAppStateFn): void {
|
||||||
updateTaskState(taskId, setAppState, task => {
|
updateTaskState(taskId, setAppState, task => {
|
||||||
if ((task as any).status !== 'running' || !isLocalShellTask(task)) {
|
if (!isLocalShellTask(task) || task.status !== 'running') {
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
39
src/types/global.d.ts
vendored
39
src/types/global.d.ts
vendored
|
|
@ -16,12 +16,28 @@ declare namespace MACRO {
|
||||||
export const VERSION_CHANGELOG: string
|
export const VERSION_CHANGELOG: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GlobalThis {
|
||||||
|
MACRO:
|
||||||
|
| {
|
||||||
|
VERSION: string
|
||||||
|
BUILD_TIME: string
|
||||||
|
FEEDBACK_CHANNEL: string
|
||||||
|
ISSUES_EXPLAINER: string
|
||||||
|
NATIVE_PACKAGE_URL: string
|
||||||
|
PACKAGE_URL: string
|
||||||
|
VERSION_CHANGELOG: string
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Internal Anthropic-only identifiers (dead-code eliminated in open-source)
|
// Internal Anthropic-only identifiers (dead-code eliminated in open-source)
|
||||||
// These are referenced inside `MACRO(() => ...)` or `false && ...` blocks.
|
// These are referenced inside `MACRO(() => ...)` or `false && ...` blocks.
|
||||||
|
|
||||||
// Model resolution (internal)
|
// Model resolution (internal)
|
||||||
declare function resolveAntModel(model: string): import('../utils/model/antModels.js').AntModel | undefined
|
declare function resolveAntModel(
|
||||||
|
model: string,
|
||||||
|
): import('../utils/model/antModels.js').AntModel | undefined
|
||||||
declare function getAntModels(): import('../utils/model/antModels.js').AntModel[]
|
declare function getAntModels(): import('../utils/model/antModels.js').AntModel[]
|
||||||
declare function getAntModelOverrideConfig(): {
|
declare function getAntModelOverrideConfig(): {
|
||||||
defaultSystemPromptSuffix?: string
|
defaultSystemPromptSuffix?: string
|
||||||
|
|
@ -31,7 +47,13 @@ declare function getAntModelOverrideConfig(): {
|
||||||
// Companion reactions handled by src/buddy/companionReact.ts (direct import)
|
// Companion reactions handled by src/buddy/companionReact.ts (direct import)
|
||||||
|
|
||||||
// Metrics (internal)
|
// Metrics (internal)
|
||||||
type ApiMetricEntry = { ttftMs: number; firstTokenTime: number; lastTokenTime: number; responseLengthBaseline: number; endResponseLength: number }
|
type ApiMetricEntry = {
|
||||||
|
ttftMs: number
|
||||||
|
firstTokenTime: number
|
||||||
|
lastTokenTime: number
|
||||||
|
responseLengthBaseline: number
|
||||||
|
endResponseLength: number
|
||||||
|
}
|
||||||
declare const apiMetricsRef: React.RefObject<ApiMetricEntry[]> | null
|
declare const apiMetricsRef: React.RefObject<ApiMetricEntry[]> | null
|
||||||
declare function computeTtftText(metrics: ApiMetricEntry[]): string
|
declare function computeTtftText(metrics: ApiMetricEntry[]): string
|
||||||
|
|
||||||
|
|
@ -44,8 +66,12 @@ declare function ExperimentEnrollmentNotice(): JSX.Element | null
|
||||||
declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number
|
declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number
|
||||||
|
|
||||||
// Ultraplan (internal)
|
// Ultraplan (internal)
|
||||||
declare function UltraplanChoiceDialog(props: Record<string, unknown>): JSX.Element | null
|
declare function UltraplanChoiceDialog(
|
||||||
declare function UltraplanLaunchDialog(props: Record<string, unknown>): JSX.Element | null
|
props: Record<string, unknown>,
|
||||||
|
): JSX.Element | null
|
||||||
|
declare function UltraplanLaunchDialog(
|
||||||
|
props: Record<string, unknown>,
|
||||||
|
): JSX.Element | null
|
||||||
declare function launchUltraplan(...args: unknown[]): Promise<string>
|
declare function launchUltraplan(...args: unknown[]): Promise<string>
|
||||||
|
|
||||||
// T — Generic type parameter leaked from React compiler output
|
// T — Generic type parameter leaked from React compiler output
|
||||||
|
|
@ -53,7 +79,10 @@ declare function launchUltraplan(...args: unknown[]): Promise<string>
|
||||||
declare type T = unknown
|
declare type T = unknown
|
||||||
|
|
||||||
// Tungsten (internal)
|
// Tungsten (internal)
|
||||||
declare function TungstenPill(props?: { key?: string; selected?: boolean }): JSX.Element | null
|
declare function TungstenPill(props?: {
|
||||||
|
key?: string
|
||||||
|
selected?: boolean
|
||||||
|
}): JSX.Element | null
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage)
|
// Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage)
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ export function isAnthropicAuthEnabled(): boolean {
|
||||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
|
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
|
||||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
|
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
|
||||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
||||||
(settings as any).modelType === 'openai' ||
|
settings.modelType === 'openai' ||
|
||||||
(settings as any).modelType === 'gemini' ||
|
settings.modelType === 'gemini' ||
|
||||||
!!process.env.OPENAI_BASE_URL ||
|
!!process.env.OPENAI_BASE_URL ||
|
||||||
!!process.env.GEMINI_BASE_URL
|
!!process.env.GEMINI_BASE_URL
|
||||||
const apiKeyHelper = settings.apiKeyHelper
|
const apiKeyHelper = settings.apiKeyHelper
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ import { TASK_LIST_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskL
|
||||||
import { TASK_UPDATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskUpdateTool/constants.js'
|
import { TASK_UPDATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskUpdateTool/constants.js'
|
||||||
import { TEAM_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamCreateTool/constants.js'
|
import { TEAM_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamCreateTool/constants.js'
|
||||||
import { TEAM_DELETE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamDeleteTool/constants.js'
|
import { TEAM_DELETE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TeamDeleteTool/constants.js'
|
||||||
import type { Message } from '../../types/message.js'
|
import type { ContentItem, Message } from '../../types/message.js'
|
||||||
import type { PermissionDecision } from '../../types/permissions.js'
|
import type { PermissionDecision } from '../../types/permissions.js'
|
||||||
import {
|
import {
|
||||||
createAssistantAPIErrorMessage,
|
createAssistantAPIErrorMessage,
|
||||||
|
|
@ -1449,13 +1449,16 @@ export async function runInProcessTeammate(
|
||||||
for (let i = allMessages.length - 1; i >= 0; i--) {
|
for (let i = allMessages.length - 1; i >= 0; i--) {
|
||||||
const m = allMessages[i]!
|
const m = allMessages[i]!
|
||||||
if (m.type === 'assistant') {
|
if (m.type === 'assistant') {
|
||||||
const blocks = (m.message?.content ?? []) as any[]
|
const content = m.message?.content
|
||||||
|
const blocks: ContentItem[] = Array.isArray(content) ? content : []
|
||||||
for (const b of blocks) {
|
for (const b of blocks) {
|
||||||
if (b?.type === 'tool_use') completionToolUseCount++
|
if (b.type === 'tool_use') completionToolUseCount++
|
||||||
}
|
}
|
||||||
const textBlocks = blocks.filter((b: any) => b?.type === 'text')
|
const textBlocks = blocks.filter(
|
||||||
|
(b): b is Extract<ContentItem, { type: 'text' }> => b.type === 'text',
|
||||||
|
)
|
||||||
if (textBlocks.length > 0 && lastAssistantContent.length === 0) {
|
if (textBlocks.length > 0 && lastAssistantContent.length === 0) {
|
||||||
lastAssistantContent = textBlocks.map((b: any) => ({
|
lastAssistantContent = textBlocks.map(b => ({
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
text: b.text,
|
text: b.text,
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user