Files
mai-bot/dashboard/src/lib/memory-api.ts
A-Dawn 21b642d07d feat:同步本地非算法改动到上游基线
保留反馈纠错、WebUI 与运行时增强。\n移除不应提交的 algorithm_redesign 设计目录及其专项测试。
2026-04-16 13:57:07 +08:00

918 lines
25 KiB
TypeScript

import type { PluginConfigSchema } from '@/lib/plugin-api'
import { getApiBaseUrl } from './api-base'
import { isElectron } from './runtime'
async function getMemoryApiBase(): Promise<string> {
if (isElectron()) {
const base = await getApiBaseUrl()
return base ? `${base}/api/webui/memory` : '/api/webui/memory'
}
return import.meta.env.VITE_API_BASE_URL
? `${import.meta.env.VITE_API_BASE_URL}/memory`
: '/api/webui/memory'
}
async function requestJson<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${await getMemoryApiBase()}${path}`, init)
if (!response.ok) {
let detail = `${response.status}`
try {
const payload = await response.json()
detail = String(payload?.detail ?? payload?.error ?? detail)
} catch {
// ignore json parsing fallback
}
throw new Error(detail)
}
return response.json() as Promise<T>
}
export interface MemoryGraphNodePayload {
id: string
name: string
attributes?: Record<string, unknown>
}
export interface MemoryGraphEdgePayload {
source: string
target: string
weight: number
relation_hashes?: string[]
predicates?: string[]
relation_count?: number
evidence_count?: number
label?: string
}
export interface MemoryGraphPayload {
success: boolean
nodes: MemoryGraphNodePayload[]
edges: MemoryGraphEdgePayload[]
total_nodes: number
total_edges: number
}
export interface MemoryGraphSearchItem {
type: 'entity' | 'relation'
title: string
matched_field: string
matched_value: string
entity_name?: string
entity_hash?: string
appearance_count?: number
subject?: string
predicate?: string
object?: string
relation_hash?: string
confidence?: number
created_at?: number
}
export interface MemoryGraphSearchPayload {
success: boolean
query: string
limit: number
count: number
items: MemoryGraphSearchItem[]
error?: string
}
export interface MemoryGraphRelationDetailPayload {
hash: string
subject: string
predicate: string
object: string
text: string
confidence: number
paragraph_count: number
paragraph_hashes: string[]
source_paragraph: string
}
export interface MemoryGraphParagraphDetailPayload {
hash: string
content: string
preview: string
source: string
created_at?: number | null
updated_at?: number | null
entity_count: number
relation_count: number
entities: string[]
relations: string[]
}
export interface MemoryEvidenceGraphNodePayload {
id: string
type: 'entity' | 'relation' | 'paragraph'
content: string
metadata?: MemoryEvidenceGraphNodeMetadata
}
export interface MemoryEvidenceGraphEdgePayload {
source: string
target: string
kind: 'mentions' | 'supports' | 'subject' | 'object'
label: string
weight: number
}
export interface MemoryEvidenceGraphPayload {
nodes: MemoryEvidenceGraphNodePayload[]
edges: MemoryEvidenceGraphEdgePayload[]
focus_entities: string[]
}
export interface MemoryEvidenceEntityNodeMetadata extends Record<string, unknown> {
entity_name?: string
}
export interface MemoryEvidenceRelationNodeMetadata extends Record<string, unknown> {
hash?: string
subject?: string
predicate?: string
object?: string
confidence?: number
paragraph_count?: number
paragraph_hashes?: string[]
text?: string
}
export interface MemoryEvidenceParagraphNodeMetadata extends Record<string, unknown> {
hash?: string
source?: string
updated_at?: number | null
entity_count?: number
relation_count?: number
preview?: string
}
export type MemoryEvidenceGraphNodeMetadata =
| MemoryEvidenceEntityNodeMetadata
| MemoryEvidenceRelationNodeMetadata
| MemoryEvidenceParagraphNodeMetadata
| Record<string, unknown>
export interface MemoryGraphNodeDetailPayload {
success: boolean
node: {
id: string
type: 'entity'
content: string
hash?: string
appearance_count?: number
}
relations: MemoryGraphRelationDetailPayload[]
paragraphs: MemoryGraphParagraphDetailPayload[]
evidence_graph: MemoryEvidenceGraphPayload
}
export interface MemoryGraphEdgeDetailPayload {
success: boolean
edge: MemoryGraphEdgePayload
relations: MemoryGraphRelationDetailPayload[]
paragraphs: MemoryGraphParagraphDetailPayload[]
evidence_graph: MemoryEvidenceGraphPayload
}
export interface MemoryRuntimeConfigPayload {
success: boolean
config: Record<string, unknown>
data_dir: string
embedding_dimension: number
auto_save: boolean
relation_vectors_enabled: boolean
runtime_ready: boolean
embedding_degraded: boolean
embedding_degraded_reason: string
embedding_degraded_since?: number | null
embedding_last_check?: number | null
paragraph_vector_backfill_pending: number
paragraph_vector_backfill_running: number
paragraph_vector_backfill_failed: number
paragraph_vector_backfill_done: number
}
export interface MemoryRuntimeSelfCheckPayload {
success: boolean
report?: Record<string, unknown>
error?: string
}
export interface MemoryConfigPayload {
success: boolean
config: Record<string, unknown>
path: string
}
export interface MemoryRawConfigPayload {
success: boolean
config: string
path: string
exists?: boolean
using_default?: boolean
}
export interface MemoryConfigSchemaPayload {
success: boolean
schema: PluginConfigSchema
path: string
}
export interface MemoryImportGuidePayload {
success: boolean
content: string
source?: string
path?: string
settings?: MemoryImportSettings
}
export interface MemoryTaskPayload {
task_id?: string
status?: string
mode?: string
created_at?: number
updated_at?: number
[key: string]: unknown
}
export interface MemoryTaskListPayload {
success: boolean
items: MemoryTaskPayload[]
count?: number
settings?: Record<string, unknown>
}
export type MemoryImportInputMode = 'text' | 'json'
export type MemoryImportTaskKind =
| 'upload'
| 'paste'
| 'raw_scan'
| 'lpmm_openie'
| 'lpmm_convert'
| 'temporal_backfill'
| 'maibot_migration'
export interface MemoryImportSettings {
max_queue_size?: number
max_files_per_task?: number
max_file_size_mb?: number
max_paste_chars?: number
default_file_concurrency?: number
default_chunk_concurrency?: number
max_file_concurrency?: number
max_chunk_concurrency?: number
poll_interval_ms?: number
maibot_source_db_default?: string
maibot_target_data_dir?: string
path_aliases?: Record<string, string>
llm_retry?: Record<string, number>
convert_enable_staging_switch?: boolean
convert_keep_backup_count?: number
}
export interface MemoryImportSettingsPayload {
success: boolean
settings: MemoryImportSettings
}
export interface MemoryImportPathAliasesPayload {
success: boolean
path_aliases: Record<string, string>
}
export interface MemoryImportResolvePathPayload {
success?: boolean
alias: string
relative_path: string
resolved_path: string
exists: boolean
is_file: boolean
is_dir: boolean
error?: string
}
export interface MemoryImportChunkPayload {
chunk_id: string
index: number
chunk_type: string
status: string
step: string
failed_at: string
retryable: boolean
error: string
progress: number
content_preview: string
updated_at: number
}
export interface MemoryImportFilePayload {
file_id: string
name: string
source_kind: string
input_mode: MemoryImportInputMode
status: string
current_step: string
detected_strategy_type: string
total_chunks: number
done_chunks: number
failed_chunks: number
cancelled_chunks: number
progress: number
error: string
created_at: number
updated_at: number
source_path?: string
content_hash?: string
retry_chunk_indexes?: number[]
retry_mode?: string
chunks?: MemoryImportChunkPayload[]
}
export interface MemoryImportRetrySummary {
chunk_retry_files?: number
chunk_retry_chunks?: number
file_fallback_files?: number
skipped_files?: number
parent_task_id?: string
skipped_details?: Array<Record<string, string>>
}
export interface MemoryImportTaskPayload extends MemoryTaskPayload {
task_id: string
source: string
status: string
current_step: string
total_chunks: number
done_chunks: number
failed_chunks: number
cancelled_chunks: number
progress: number
error: string
file_count: number
created_at: number
started_at?: number | null
finished_at?: number | null
updated_at: number
task_kind?: MemoryImportTaskKind | string
schema_detected?: string
artifact_paths?: Record<string, string>
rollback_info?: Record<string, unknown>
retry_parent_task_id?: string
retry_summary?: MemoryImportRetrySummary
params?: Record<string, unknown>
files?: MemoryImportFilePayload[]
}
export interface MemoryImportTaskListPayload {
success: boolean
items: MemoryImportTaskPayload[]
count?: number
settings?: MemoryImportSettings
}
export interface MemoryImportTaskDetailPayload {
success: boolean
task?: MemoryImportTaskPayload
error?: string
}
export interface MemoryImportChunkListPayload {
success: boolean
task_id?: string
file_id?: string
offset?: number
limit?: number
total?: number
items?: MemoryImportChunkPayload[]
error?: string
}
export interface MemoryImportActionPayload {
success: boolean
task?: MemoryImportTaskPayload
error?: string
}
export interface MemoryTuningProfilePayload {
success: boolean
profile?: Record<string, unknown>
settings?: Record<string, unknown>
toml?: string
}
export interface MemoryDeleteCountsPayload {
relations?: number
paragraphs?: number
entities?: number
sources?: number
requested_sources?: number
matched_sources?: number
[key: string]: number | undefined
}
export interface MemoryDeletePreviewItemPayload {
item_type: string
item_hash: string
item_key?: string
label?: string
preview?: string
source?: string
}
export interface MemoryDeleteRequestPayload {
mode: string
selector: Record<string, unknown> | string
reason?: string
requested_by?: string
}
export interface MemoryDeletePreviewPayload {
success: boolean
mode: string
selector: Record<string, unknown> | string
counts: MemoryDeleteCountsPayload
sources: string[]
items: MemoryDeletePreviewItemPayload[]
item_count: number
dry_run?: boolean
requested_source_count?: number
matched_source_count?: number
vector_ids?: string[]
error?: string
}
export interface MemoryDeleteExecutePayload {
success: boolean
mode: string
operation_id: string
counts: MemoryDeleteCountsPayload
sources: string[]
deleted_count: number
deleted_entity_count: number
deleted_relation_count: number
deleted_paragraph_count: number
deleted_source_count: number
deleted_vector_count?: number
requested_source_count?: number
matched_source_count?: number
error?: string
deleted?: boolean | number
}
export interface MemoryDeleteOperationItemPayload {
item_type: string
item_hash: string
item_key?: string
payload?: Record<string, unknown>
created_at?: number
}
export interface MemoryDeleteOperationPayload {
operation_id: string
mode: string
selector?: Record<string, unknown> | string
reason?: string | null
requested_by?: string | null
status?: string
created_at?: number
restored_at?: number | null
summary?: Record<string, unknown>
items?: MemoryDeleteOperationItemPayload[]
}
export interface MemoryDeleteOperationListPayload {
success: boolean
items: MemoryDeleteOperationPayload[]
count?: number
error?: string
}
export interface MemoryDeleteOperationDetailPayload {
success: boolean
operation?: MemoryDeleteOperationPayload | null
error?: string
}
export interface MemoryFeedbackAffectedCountsPayload {
relations?: number
stale_paragraphs?: number
episode_sources?: number
profile_person_ids?: number
correction_paragraphs?: number
corrected_relations?: number
}
export interface MemoryFeedbackActionLogPayload {
id: number
task_id: number
query_tool_id: string
action_type: string
target_hash: string
reason?: string
before_payload?: Record<string, unknown>
after_payload?: Record<string, unknown>
created_at?: number
}
export interface MemoryFeedbackCorrectionSummaryPayload {
task_id: number
query_tool_id: string
session_id: string
query_text: string
query_timestamp?: number
task_status: string
decision: string
decision_confidence: number
feedback_message_count: number
rollback_status: string
affected_counts: MemoryFeedbackAffectedCountsPayload
created_at?: number
updated_at?: number
}
export interface MemoryFeedbackCorrectionDetailTaskPayload extends MemoryFeedbackCorrectionSummaryPayload {
query_snapshot?: Record<string, unknown>
decision_payload?: Record<string, unknown>
rollback_plan_summary?: Record<string, unknown>
rollback_result?: Record<string, unknown>
rollback_error?: string
rollback_requested_by?: string
rollback_reason?: string
rollback_requested_at?: number
rolled_back_at?: number
action_logs?: MemoryFeedbackActionLogPayload[]
}
export interface MemoryFeedbackCorrectionListPayload {
success: boolean
items: MemoryFeedbackCorrectionSummaryPayload[]
count?: number
error?: string
}
export interface MemoryFeedbackCorrectionDetailPayload {
success: boolean
task?: MemoryFeedbackCorrectionDetailTaskPayload | null
error?: string
}
export interface MemoryFeedbackCorrectionRollbackPayload {
success: boolean
already_rolled_back?: boolean
result?: Record<string, unknown>
task?: MemoryFeedbackCorrectionDetailTaskPayload | null
error?: string
}
export interface MemorySourceItemPayload {
source: string
paragraph_count?: number
relation_count?: number
episode_rebuild_blocked?: boolean
[key: string]: unknown
}
export interface MemorySourceListPayload {
success: boolean
items: MemorySourceItemPayload[]
count: number
}
export async function getMemoryGraph(limit: number = 120): Promise<MemoryGraphPayload> {
return requestJson<MemoryGraphPayload>(`/graph?limit=${limit}`)
}
export async function getMemoryGraphSearch(
query: string,
limit: number = 50,
): Promise<MemoryGraphSearchPayload> {
const params = new URLSearchParams({
query,
limit: String(limit),
})
return requestJson<MemoryGraphSearchPayload>(`/graph/search?${params.toString()}`)
}
export async function getMemoryGraphNodeDetail(
nodeId: string,
options?: {
relationLimit?: number
paragraphLimit?: number
evidenceNodeLimit?: number
},
): Promise<MemoryGraphNodeDetailPayload> {
const params = new URLSearchParams({
node_id: nodeId,
relation_limit: String(options?.relationLimit ?? 20),
paragraph_limit: String(options?.paragraphLimit ?? 20),
evidence_node_limit: String(options?.evidenceNodeLimit ?? 80),
})
return requestJson<MemoryGraphNodeDetailPayload>(`/graph/node-detail?${params.toString()}`)
}
export async function getMemoryGraphEdgeDetail(
source: string,
target: string,
options?: {
paragraphLimit?: number
evidenceNodeLimit?: number
},
): Promise<MemoryGraphEdgeDetailPayload> {
const params = new URLSearchParams({
source,
target,
paragraph_limit: String(options?.paragraphLimit ?? 20),
evidence_node_limit: String(options?.evidenceNodeLimit ?? 80),
})
return requestJson<MemoryGraphEdgeDetailPayload>(`/graph/edge-detail?${params.toString()}`)
}
export async function previewMemoryDelete(
payload: MemoryDeleteRequestPayload,
): Promise<MemoryDeletePreviewPayload> {
return requestJson<MemoryDeletePreviewPayload>('/delete/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function executeMemoryDelete(
payload: MemoryDeleteRequestPayload,
): Promise<MemoryDeleteExecutePayload> {
return requestJson<MemoryDeleteExecutePayload>('/delete/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function restoreMemoryDelete(payload: {
operation_id: string
mode?: string
selector?: Record<string, unknown> | string
reason?: string
requested_by?: string
}): Promise<Record<string, unknown>> {
return requestJson('/delete/restore', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemoryDeleteOperations(
limit: number = 20,
mode: string = '',
): Promise<MemoryDeleteOperationListPayload> {
const params = new URLSearchParams({ limit: String(limit) })
if (mode.trim()) {
params.set('mode', mode)
}
return requestJson<MemoryDeleteOperationListPayload>(`/delete/operations?${params.toString()}`)
}
export async function getMemoryDeleteOperation(
operationId: string,
): Promise<MemoryDeleteOperationDetailPayload> {
return requestJson<MemoryDeleteOperationDetailPayload>(`/delete/operations/${encodeURIComponent(operationId)}`)
}
export async function getMemoryFeedbackCorrections(
options?: {
limit?: number
status?: string
rollbackStatus?: string
query?: string
},
): Promise<MemoryFeedbackCorrectionListPayload> {
const params = new URLSearchParams({
limit: String(options?.limit ?? 50),
})
if (options?.status?.trim()) {
params.set('status', options.status.trim())
}
if (options?.rollbackStatus?.trim()) {
params.set('rollback_status', options.rollbackStatus.trim())
}
if (options?.query?.trim()) {
params.set('query', options.query.trim())
}
return requestJson<MemoryFeedbackCorrectionListPayload>(`/feedback-corrections?${params.toString()}`)
}
export async function getMemoryFeedbackCorrection(
taskId: number,
): Promise<MemoryFeedbackCorrectionDetailPayload> {
return requestJson<MemoryFeedbackCorrectionDetailPayload>(`/feedback-corrections/${taskId}`)
}
export async function rollbackMemoryFeedbackCorrection(
taskId: number,
payload: {
requested_by?: string
reason?: string
},
): Promise<MemoryFeedbackCorrectionRollbackPayload> {
return requestJson<MemoryFeedbackCorrectionRollbackPayload>(`/feedback-corrections/${taskId}/rollback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemorySources(): Promise<MemorySourceListPayload> {
return requestJson<MemorySourceListPayload>('/sources')
}
export async function getMemoryRuntimeConfig(): Promise<MemoryRuntimeConfigPayload> {
return requestJson<MemoryRuntimeConfigPayload>('/runtime/config')
}
export async function refreshMemoryRuntimeSelfCheck(): Promise<MemoryRuntimeSelfCheckPayload> {
return requestJson<MemoryRuntimeSelfCheckPayload>('/runtime/self-check/refresh', {
method: 'POST',
})
}
export async function getMemoryConfigSchema(): Promise<MemoryConfigSchemaPayload> {
return requestJson<MemoryConfigSchemaPayload>('/config/schema')
}
export async function getMemoryConfig(): Promise<MemoryConfigPayload> {
return requestJson<MemoryConfigPayload>('/config')
}
export async function updateMemoryConfig(config: Record<string, unknown>): Promise<{ success: boolean; message?: string }> {
return requestJson('/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config }),
})
}
export async function getMemoryConfigRaw(): Promise<MemoryRawConfigPayload> {
return requestJson<MemoryRawConfigPayload>('/config/raw')
}
export async function updateMemoryConfigRaw(config: string): Promise<{ success: boolean; message?: string }> {
return requestJson('/config/raw', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config }),
})
}
export async function getMemoryImportGuide(): Promise<MemoryImportGuidePayload> {
return requestJson<MemoryImportGuidePayload>('/import/guide')
}
export async function getMemoryImportSettings(): Promise<MemoryImportSettingsPayload> {
return requestJson<MemoryImportSettingsPayload>('/import/settings')
}
export async function getMemoryImportPathAliases(): Promise<MemoryImportPathAliasesPayload> {
return requestJson<MemoryImportPathAliasesPayload>('/import/path-aliases')
}
export async function resolveMemoryImportPath(payload: {
alias: string
relative_path?: string
must_exist?: boolean
}): Promise<MemoryImportResolvePathPayload> {
return requestJson<MemoryImportResolvePathPayload>('/import/resolve-path', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemoryImportTasks(limit: number = 20): Promise<MemoryImportTaskListPayload> {
return requestJson<MemoryImportTaskListPayload>(`/import/tasks?limit=${limit}`)
}
export async function getMemoryImportTask(taskId: string, includeChunks: boolean = false): Promise<MemoryImportTaskDetailPayload> {
return requestJson<MemoryImportTaskDetailPayload>(
`/import/tasks/${encodeURIComponent(taskId)}?include_chunks=${includeChunks ? 'true' : 'false'}`,
)
}
export async function getMemoryImportTaskChunks(
taskId: string,
fileId: string,
offset: number = 0,
limit: number = 50,
): Promise<MemoryImportChunkListPayload> {
return requestJson<MemoryImportChunkListPayload>(
`/import/tasks/${encodeURIComponent(taskId)}/chunks/${encodeURIComponent(fileId)}?offset=${offset}&limit=${limit}`,
)
}
export async function createMemoryUploadImport(files: File[], payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
const formData = new FormData()
files.forEach((file) => {
formData.append('files', file)
})
formData.append('payload_json', JSON.stringify(payload))
return requestJson<MemoryImportActionPayload>('/import/upload', {
method: 'POST',
body: formData,
})
}
export async function createMemoryPasteImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/paste', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function createMemoryRawScanImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/raw-scan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function createMemoryLpmmOpenieImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/lpmm-openie', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function createMemoryLpmmConvertImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/lpmm-convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function createMemoryTemporalBackfillImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/temporal-backfill', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function createMemoryMaibotMigrationImport(payload: Record<string, unknown>): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>('/import/maibot-migration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function cancelMemoryImportTask(taskId: string): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>(`/import/tasks/${encodeURIComponent(taskId)}/cancel`, {
method: 'POST',
})
}
export async function retryMemoryImportTask(
taskId: string,
payload: {
overrides?: Record<string, unknown>
} = {},
): Promise<MemoryImportActionPayload> {
return requestJson<MemoryImportActionPayload>(`/import/tasks/${encodeURIComponent(taskId)}/retry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemoryTuningProfile(): Promise<MemoryTuningProfilePayload> {
return requestJson<MemoryTuningProfilePayload>('/retrieval_tuning/profile')
}
export async function getMemoryTuningTasks(limit: number = 20): Promise<MemoryTaskListPayload> {
return requestJson<MemoryTaskListPayload>(`/retrieval_tuning/tasks?limit=${limit}`)
}
export async function createMemoryTuningTask(payload: Record<string, unknown>): Promise<{ success: boolean; task?: MemoryTaskPayload }> {
return requestJson('/retrieval_tuning/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function applyBestMemoryTuningProfile(taskId: string): Promise<{ success: boolean; error?: string }> {
return requestJson(`/retrieval_tuning/tasks/${encodeURIComponent(taskId)}/apply-best`, {
method: 'POST',
})
}
export async function getMemoryTuningReport(taskId: string, format: 'md' | 'json' = 'md'): Promise<{ success: boolean; content: string; path: string; error?: string }> {
return requestJson(`/retrieval_tuning/tasks/${encodeURIComponent(taskId)}/report?format=${format}`)
}