fix tests
This commit is contained in:
parent
340042682a
commit
fc784620e0
@ -12,6 +12,8 @@ export type SettingComponentProps = {
|
||||
extensionName?: string
|
||||
requireModelReload?: boolean
|
||||
configType?: ConfigType
|
||||
titleKey?: string
|
||||
descriptionKey?: string
|
||||
}
|
||||
|
||||
export type ConfigType = 'runtime' | 'setting'
|
||||
|
||||
@ -41,7 +41,7 @@ export function getRAGTools(retrievalLimit: number): MCPTool[] {
|
||||
{
|
||||
name: GET_CHUNKS,
|
||||
description:
|
||||
'Retrieve chunks from a file by their order range. For a single chunk, use start_order = end_order. Thread context is inferred automatically; you may optionally provide {"scope":"thread"}.',
|
||||
'Retrieve chunks from a file by their order range. For a single chunk, use start_order = end_order. Thread context is inferred automatically; you may optionally provide {"scope":"thread"}. Use sparingly; intended for advanced usage. Prefer using retrieve instead for relevance-based fetching.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
@ -565,7 +565,7 @@ const ChatInput = ({
|
||||
|
||||
// If thread exists, ingest images immediately
|
||||
if (currentThreadId) {
|
||||
;(async () => {
|
||||
void (async () => {
|
||||
for (const img of newFiles) {
|
||||
try {
|
||||
// Mark as processing
|
||||
|
||||
@ -184,9 +184,9 @@ export const ThreadContent = memo(
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((v) => v !== null)
|
||||
// Keep embedded document metadata in the message for regenerate
|
||||
sendMessage(rawText, true, attachments)
|
||||
sendMessage(textContent, true, attachments)
|
||||
}
|
||||
}, [deleteMessage, getMessages, item, sendMessage])
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { ExtensionManager } from '@/lib/extension'
|
||||
import { ExtensionTypeEnum, type RAGExtension } from '@janhq/core'
|
||||
import { ExtensionTypeEnum, type RAGExtension, type SettingComponentProps } from '@janhq/core'
|
||||
|
||||
export type AttachmentsSettings = {
|
||||
enabled: boolean
|
||||
@ -14,7 +14,7 @@ export type AttachmentsSettings = {
|
||||
|
||||
type AttachmentsStore = AttachmentsSettings & {
|
||||
// Dynamic controller definitions for rendering UI
|
||||
settingsDefs: any[]
|
||||
settingsDefs: SettingComponentProps[]
|
||||
loadSettingsDefs: () => Promise<void>
|
||||
setEnabled: (v: boolean) => void
|
||||
setMaxFileSizeMB: (v: number) => void
|
||||
@ -27,7 +27,7 @@ type AttachmentsStore = AttachmentsSettings & {
|
||||
|
||||
const getRagExtension = (): RAGExtension | undefined => {
|
||||
try {
|
||||
return ExtensionManager.getInstance().get<RAGExtension>(ExtensionTypeEnum.RAG) as any
|
||||
return ExtensionManager.getInstance().get<RAGExtension>(ExtensionTypeEnum.RAG)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
@ -43,94 +43,124 @@ export const useAttachments = create<AttachmentsStore>()((set) => ({
|
||||
searchMode: 'auto',
|
||||
settingsDefs: [],
|
||||
loadSettingsDefs: async () => {
|
||||
const ext = getRagExtension() as any
|
||||
const ext = getRagExtension()
|
||||
if (!ext?.getSettings) return
|
||||
try {
|
||||
const defs = await ext.getSettings()
|
||||
if (Array.isArray(defs)) set({ settingsDefs: defs })
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.debug('Failed to load attachment settings defs:', e)
|
||||
}
|
||||
},
|
||||
setEnabled: async (v) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'enabled', controllerProps: { value: !!v } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'enabled', controllerProps: { value: !!v } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
enabled: v,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'enabled' ? { ...d, controllerProps: { ...d.controllerProps, value: !!v } } : d
|
||||
d.key === 'enabled'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: !!v } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setMaxFileSizeMB: async (val) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'max_file_size_mb', controllerProps: { value: val } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'max_file_size_mb', controllerProps: { value: val } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
maxFileSizeMB: val,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'max_file_size_mb' ? { ...d, controllerProps: { ...d.controllerProps, value: val } } : d
|
||||
d.key === 'max_file_size_mb'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: val } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setRetrievalLimit: async (val) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'retrieval_limit', controllerProps: { value: val } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'retrieval_limit', controllerProps: { value: val } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
retrievalLimit: val,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'retrieval_limit' ? { ...d, controllerProps: { ...d.controllerProps, value: val } } : d
|
||||
d.key === 'retrieval_limit'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: val } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setRetrievalThreshold: async (val) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'retrieval_threshold', controllerProps: { value: val } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'retrieval_threshold', controllerProps: { value: val } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
retrievalThreshold: val,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'retrieval_threshold' ? { ...d, controllerProps: { ...d.controllerProps, value: val } } : d
|
||||
d.key === 'retrieval_threshold'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: val } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setChunkSizeTokens: async (val) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'chunk_size_tokens', controllerProps: { value: val } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'chunk_size_tokens', controllerProps: { value: val } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
chunkSizeTokens: val,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'chunk_size_tokens' ? { ...d, controllerProps: { ...d.controllerProps, value: val } } : d
|
||||
d.key === 'chunk_size_tokens'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: val } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setOverlapTokens: async (val) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'overlap_tokens', controllerProps: { value: val } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'overlap_tokens', controllerProps: { value: val } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
overlapTokens: val,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'overlap_tokens' ? { ...d, controllerProps: { ...d.controllerProps, value: val } } : d
|
||||
d.key === 'overlap_tokens'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: val } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
setSearchMode: async (v) => {
|
||||
const ext = getRagExtension()
|
||||
if (ext?.updateSettings) {
|
||||
await ext.updateSettings([{ key: 'search_mode', controllerProps: { value: v } } as any])
|
||||
await ext.updateSettings([
|
||||
{ key: 'search_mode', controllerProps: { value: v } } as Partial<SettingComponentProps>,
|
||||
])
|
||||
}
|
||||
set((s) => ({
|
||||
searchMode: v,
|
||||
settingsDefs: s.settingsDefs.map((d) =>
|
||||
d.key === 'search_mode' ? { ...d, controllerProps: { ...d.controllerProps, value: v } } : d
|
||||
d.key === 'search_mode'
|
||||
? ({ ...d, controllerProps: { ...d.controllerProps, value: v } } as SettingComponentProps)
|
||||
: d
|
||||
),
|
||||
}))
|
||||
},
|
||||
@ -139,22 +169,26 @@ export const useAttachments = create<AttachmentsStore>()((set) => ({
|
||||
// Initialize from extension settings once on import
|
||||
;(async () => {
|
||||
try {
|
||||
const ext = getRagExtension() as any
|
||||
const ext = getRagExtension()
|
||||
if (!ext?.getSettings) return
|
||||
const settings = await ext.getSettings()
|
||||
if (!Array.isArray(settings)) return
|
||||
const map = new Map<string, any>()
|
||||
const map = new Map<string, unknown>()
|
||||
for (const s of settings) map.set(s.key, s?.controllerProps?.value)
|
||||
// seed defs and values
|
||||
useAttachments.setState((prev) => ({
|
||||
settingsDefs: settings,
|
||||
enabled: map.get('enabled') ?? prev.enabled,
|
||||
maxFileSizeMB: map.get('max_file_size_mb') ?? prev.maxFileSizeMB,
|
||||
retrievalLimit: map.get('retrieval_limit') ?? prev.retrievalLimit,
|
||||
retrievalThreshold: map.get('retrieval_threshold') ?? prev.retrievalThreshold,
|
||||
chunkSizeTokens: map.get('chunk_size_tokens') ?? prev.chunkSizeTokens,
|
||||
overlapTokens: map.get('overlap_tokens') ?? prev.overlapTokens,
|
||||
searchMode: map.get('search_mode') ?? prev.searchMode,
|
||||
enabled: (map.get('enabled') as boolean | undefined) ?? prev.enabled,
|
||||
maxFileSizeMB: (map.get('max_file_size_mb') as number | undefined) ?? prev.maxFileSizeMB,
|
||||
retrievalLimit: (map.get('retrieval_limit') as number | undefined) ?? prev.retrievalLimit,
|
||||
retrievalThreshold:
|
||||
(map.get('retrieval_threshold') as number | undefined) ?? prev.retrievalThreshold,
|
||||
chunkSizeTokens: (map.get('chunk_size_tokens') as number | undefined) ?? prev.chunkSizeTokens,
|
||||
overlapTokens: (map.get('overlap_tokens') as number | undefined) ?? prev.overlapTokens,
|
||||
searchMode:
|
||||
(map.get('search_mode') as 'auto' | 'ann' | 'linear' | undefined) ?? prev.searchMode,
|
||||
}))
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.debug('Failed to initialize attachment settings from extension:', e)
|
||||
}
|
||||
})()
|
||||
|
||||
@ -380,7 +380,7 @@ export const useChat = () => {
|
||||
// Build the user content once; use it for both the outbound request
|
||||
// and persisting to the store so both are identical.
|
||||
if (updateAttachmentProcessing) {
|
||||
updateAttachmentProcessing('__CLEAR_ALL__' as any, 'clear_all')
|
||||
updateAttachmentProcessing('__CLEAR_ALL__', 'clear_all')
|
||||
}
|
||||
const userContent = newUserThreadContent(
|
||||
activeThread.id,
|
||||
|
||||
@ -241,7 +241,10 @@ export const sendCompletion = async (
|
||||
usableTools = [...tools, ...ragTools]
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
// Ignore RAG tool injection errors during completion setup
|
||||
console.debug('Skipping RAG tools injection:', e)
|
||||
}
|
||||
|
||||
const engine = ExtensionManager.getInstance().getEngine(provider.provider)
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ChatCompletionMessageParam } from 'token.js'
|
||||
import { ChatCompletionMessageToolCall } from 'openai/resources'
|
||||
import { ThreadMessage } from '@janhq/core'
|
||||
import { ThreadMessage, ContentType } from '@janhq/core'
|
||||
import { removeReasoningContent } from '@/utils/reasoning'
|
||||
// Attachments are now handled upstream in newUserThreadContent
|
||||
|
||||
type ThreadContent = NonNullable<ThreadMessage['content']>[number]
|
||||
|
||||
/**
|
||||
* @fileoverview Helper functions for creating chat completion request.
|
||||
* These functions are used to create chat completion request objects
|
||||
@ -22,7 +23,14 @@ export class CompletionMessagesBuilder {
|
||||
this.messages.push(
|
||||
...messages
|
||||
.filter((e) => !e.metadata?.error)
|
||||
.map<ChatCompletionMessageParam>((msg) => this.toCompletionParamFromThread(msg))
|
||||
.map<ChatCompletionMessageParam>((msg) => {
|
||||
const param = this.toCompletionParamFromThread(msg)
|
||||
// In constructor context, normalize empty user text to a placeholder
|
||||
if (param.role === 'user' && typeof param.content === 'string' && param.content === '') {
|
||||
return { ...param, content: '.' }
|
||||
}
|
||||
return param
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,19 +53,20 @@ export class CompletionMessagesBuilder {
|
||||
|
||||
// User messages: handle multimodal content
|
||||
if (Array.isArray(msg.content) && msg.content.length > 1) {
|
||||
const content = msg.content.map((part: any) => {
|
||||
if (part.type === 'text') {
|
||||
return { type: 'text', text: part.text?.value ?? '' }
|
||||
const content = msg.content.map((part: ThreadContent) => {
|
||||
if (part.type === ContentType.Text) {
|
||||
return { type: 'text' as const, text: part.text?.value ?? '' }
|
||||
}
|
||||
if (part.type === 'image_url') {
|
||||
if (part.type === ContentType.Image) {
|
||||
return {
|
||||
type: 'image_url',
|
||||
type: 'image_url' as const,
|
||||
image_url: { url: part.image_url?.url || '', detail: part.image_url?.detail || 'auto' },
|
||||
}
|
||||
}
|
||||
return part
|
||||
// Fallback for unknown content types
|
||||
return { type: 'text' as const, text: '' }
|
||||
})
|
||||
return { role: 'user', content } as any
|
||||
return { role: 'user', content } as ChatCompletionMessageParam
|
||||
}
|
||||
// Single text part
|
||||
const text = msg?.content?.[0]?.text?.value ?? '.'
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { route } from '@/constants/routes'
|
||||
import SettingsMenu from '@/containers/SettingsMenu'
|
||||
import HeaderPage from '@/containers/HeaderPage'
|
||||
import { Card, CardItem } from '@/containers/Card'
|
||||
import { useAttachments } from '@/hooks/useAttachments'
|
||||
import type { SettingComponentProps } from '@janhq/core'
|
||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||
import { PlatformGuard } from '@/lib/platform/PlatformGuard'
|
||||
import { DynamicControllerSetting } from '@/containers/dynamicControllerSetting'
|
||||
@ -11,14 +11,13 @@ import { PlatformFeature } from '@/lib/platform/types'
|
||||
import { useEffect, useState, useCallback, useRef } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const Route = createFileRoute(route.settings.attachments as any)({
|
||||
export const Route = createFileRoute('/settings/attachments')({
|
||||
component: AttachmentsSettings,
|
||||
})
|
||||
|
||||
// Helper to extract constraints from settingsDefs
|
||||
function getConstraints(def: any) {
|
||||
const props = def?.controllerProps || {}
|
||||
function getConstraints(def: SettingComponentProps) {
|
||||
const props = def.controllerProps as Partial<{ min: number; max: number; step: number }>
|
||||
return {
|
||||
min: props.min ?? -Infinity,
|
||||
max: props.max ?? Infinity,
|
||||
@ -27,7 +26,7 @@ function getConstraints(def: any) {
|
||||
}
|
||||
|
||||
// Helper to validate and clamp numeric values
|
||||
function clampValue(val: any, def: any, currentValue: number): number {
|
||||
function clampValue(val: unknown, def: SettingComponentProps, currentValue: number): number {
|
||||
const num = typeof val === 'number' ? val : Number(val)
|
||||
if (!Number.isFinite(num)) return currentValue
|
||||
const { min, max, step } = getConstraints(def)
|
||||
@ -40,7 +39,7 @@ function AttachmentsSettings() {
|
||||
const { t } = useTranslation()
|
||||
const hookDefs = useAttachments((s) => s.settingsDefs)
|
||||
const loadDefs = useAttachments((s) => s.loadSettingsDefs)
|
||||
const [defs, setDefs] = useState<any[]>([])
|
||||
const [defs, setDefs] = useState<SettingComponentProps[]>([])
|
||||
|
||||
// Load schema from extension via the hook once
|
||||
useEffect(() => {
|
||||
@ -73,32 +72,36 @@ function AttachmentsSettings() {
|
||||
)
|
||||
|
||||
// Local state for inputs to allow intermediate values while typing
|
||||
const [localValues, setLocalValues] = useState<Record<string, any>>({})
|
||||
const [localValues, setLocalValues] = useState<Record<string, string | number | boolean | string[]>>({})
|
||||
|
||||
// Debounce timers
|
||||
const timersRef = useRef<Record<string, NodeJS.Timeout>>({})
|
||||
const timersRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
|
||||
|
||||
// Cleanup timers on unmount
|
||||
useEffect(() => {
|
||||
const timers = timersRef.current
|
||||
return () => {
|
||||
Object.values(timersRef.current).forEach(clearTimeout)
|
||||
Object.values(timers).forEach(clearTimeout)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Debounced setter with validation
|
||||
const debouncedSet = useCallback((key: string, val: any, def: any) => {
|
||||
const debouncedSet = useCallback((key: string, val: unknown, def: SettingComponentProps) => {
|
||||
// Clear existing timer for this key
|
||||
if (timersRef.current[key]) {
|
||||
clearTimeout(timersRef.current[key])
|
||||
}
|
||||
|
||||
// Set local value immediately for responsive UI
|
||||
setLocalValues((prev) => ({ ...prev, [key]: val }))
|
||||
setLocalValues((prev) => ({
|
||||
...prev,
|
||||
[key]: val as string | number | boolean | string[]
|
||||
}))
|
||||
|
||||
// For non-numeric inputs, apply immediately without debounce
|
||||
if (key === 'enabled' || key === 'search_mode') {
|
||||
if (key === 'enabled') sel.setEnabled(!!val)
|
||||
else if (key === 'search_mode') sel.setSearchMode(val)
|
||||
else if (key === 'search_mode') sel.setSearchMode(val as 'auto' | 'ann' | 'linear')
|
||||
return
|
||||
}
|
||||
|
||||
@ -136,7 +139,10 @@ function AttachmentsSettings() {
|
||||
}
|
||||
|
||||
// Update local value to validated one
|
||||
setLocalValues((prev) => ({ ...prev, [key]: validated }))
|
||||
setLocalValues((prev) => ({
|
||||
...prev,
|
||||
[key]: validated as string | number | boolean | string[]
|
||||
}))
|
||||
}, 500) // 500ms debounce
|
||||
}, [sel])
|
||||
|
||||
@ -174,8 +180,30 @@ function AttachmentsSettings() {
|
||||
}
|
||||
})()
|
||||
|
||||
const currentValue = localValues[d.key] !== undefined ? localValues[d.key] : storeValue
|
||||
const props = { ...(d.controllerProps || {}), value: currentValue }
|
||||
const currentValue =
|
||||
localValues[d.key] !== undefined ? localValues[d.key] : storeValue
|
||||
|
||||
// Convert to DynamicControllerSetting compatible props
|
||||
const baseProps = d.controllerProps
|
||||
const normalizedValue: string | number | boolean = (() => {
|
||||
if (Array.isArray(currentValue)) {
|
||||
return currentValue.join(',')
|
||||
}
|
||||
return currentValue as string | number | boolean
|
||||
})()
|
||||
|
||||
const props = {
|
||||
value: normalizedValue,
|
||||
placeholder: 'placeholder' in baseProps ? baseProps.placeholder : undefined,
|
||||
type: 'type' in baseProps ? baseProps.type : undefined,
|
||||
options: 'options' in baseProps ? baseProps.options : undefined,
|
||||
input_actions: 'inputActions' in baseProps ? baseProps.inputActions : undefined,
|
||||
rows: undefined,
|
||||
min: 'min' in baseProps ? baseProps.min : undefined,
|
||||
max: 'max' in baseProps ? baseProps.max : undefined,
|
||||
step: 'step' in baseProps ? baseProps.step : undefined,
|
||||
recommended: 'recommended' in baseProps ? baseProps.recommended : undefined,
|
||||
}
|
||||
|
||||
const title = d.titleKey ? t(d.titleKey) : d.title
|
||||
const description = d.descriptionKey ? t(d.descriptionKey) : d.description
|
||||
@ -188,7 +216,7 @@ function AttachmentsSettings() {
|
||||
actions={
|
||||
<DynamicControllerSetting
|
||||
controllerType={d.controllerType}
|
||||
controllerProps={props as any}
|
||||
controllerProps={props}
|
||||
onChange={(val) => debouncedSet(d.key, val, d)}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -16,14 +16,15 @@ export class DefaultRAGService implements RAGService {
|
||||
return []
|
||||
}
|
||||
|
||||
async callTool(args: { toolName: string; arguments: object; threadId?: string }): Promise<MCPToolCallResult> {
|
||||
async callTool(args: { toolName: string; arguments: Record<string, unknown>; threadId?: string }): Promise<MCPToolCallResult> {
|
||||
const ext = ExtensionManager.getInstance().get<RAGExtension>(ExtensionTypeEnum.RAG)
|
||||
if (!ext?.callTool) {
|
||||
return { error: 'RAG extension not available', content: [{ type: 'text', text: 'RAG extension not available' }] }
|
||||
}
|
||||
try {
|
||||
// Inject thread context when scope requires it
|
||||
const a: any = { ...(args.arguments as any) }
|
||||
type ToolCallArgs = Record<string, unknown> & { scope?: string; thread_id?: string }
|
||||
const a: ToolCallArgs = { ...(args.arguments as Record<string, unknown>) }
|
||||
if (!a.scope) a.scope = 'thread'
|
||||
if (a.scope === 'thread' && !a.thread_id) {
|
||||
a.thread_id = args.threadId
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user