mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20c93e3ea6 | |||
| 4fef538d67 | |||
| 5e86797e48 | |||
| c92996a440 | |||
| 57fb9b49f9 | |||
| bf82c03f21 | |||
| 69f1c913f1 | |||
| 51e66e54df | |||
| 5fbedceb50 | |||
| b4319667dc | |||
| 3a58ac91e6 | |||
| 4a58984e44 | |||
| 61e419225d | |||
| 72047413d8 | |||
| 48e6672f7c |
@@ -1,7 +1,12 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AgentManagerRuntime } from '../AgentManagerRuntime';
|
||||
import type { IAgentService, IDiscoverService } from '../types';
|
||||
import type {
|
||||
GetAvailableModelsState,
|
||||
IAgentService,
|
||||
IDiscoverService,
|
||||
SearchAgentState,
|
||||
} from '../types';
|
||||
|
||||
// Create mock services
|
||||
const mockAgentService: IAgentService = {
|
||||
@@ -303,10 +308,11 @@ describe('AgentManagerRuntime', () => {
|
||||
} as any);
|
||||
|
||||
const result = await runtime.searchAgents({ keyword: 'test' });
|
||||
const state = result.state as SearchAgentState | undefined;
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state?.source).toBe('all');
|
||||
expect(result.state?.agents).toHaveLength(2);
|
||||
expect(state?.source).toBe('all');
|
||||
expect(state?.agents).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should return no agents found message', async () => {
|
||||
@@ -336,10 +342,11 @@ describe('AgentManagerRuntime', () => {
|
||||
|
||||
it('should filter by providerId', async () => {
|
||||
const result = await runtime.getAvailableModels({ providerId: 'openai' });
|
||||
const state = result.state as GetAvailableModelsState | undefined;
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.state?.providers).toHaveLength(1);
|
||||
expect(result.state?.providers[0].id).toBe('openai');
|
||||
expect(state?.providers).toHaveLength(1);
|
||||
expect(state?.providers[0].id).toBe('openai');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
type ExtendedHumanInterventionConfig,
|
||||
type HumanInterventionConfig,
|
||||
type HumanInterventionPolicy,
|
||||
type ToolArguments,
|
||||
} from '@lobechat/types';
|
||||
|
||||
import { createDefaultGlobalAudits, DEFAULT_SECURITY_BLACKLIST } from '../audit';
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
type AgentInstructionCompressContext,
|
||||
type AgentRuntimeContext,
|
||||
type AgentState,
|
||||
type AgentStateMetadata,
|
||||
type GeneralAgentCallingToolInstructionPayload,
|
||||
type GeneralAgentCallLLMInstructionPayload,
|
||||
type GeneralAgentCallLLMResultPayload,
|
||||
@@ -61,7 +63,7 @@ export class GeneralChatAgent implements Agent {
|
||||
// Find the specific API in the manifest
|
||||
const api = manifest.api?.find((a: any) => a.name === apiName);
|
||||
|
||||
// API-level config takes precedence over tool-level config
|
||||
// API-level config takes precedence over manifest-level config
|
||||
return api?.humanIntervention ?? manifest.humanIntervention;
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ export class GeneralChatAgent implements Agent {
|
||||
|
||||
private matchesAlwaysPolicy(
|
||||
config: HumanInterventionConfig | undefined,
|
||||
toolArgs: Record<string, any>,
|
||||
toolArgs: ToolArguments,
|
||||
): boolean {
|
||||
if (!config) return false;
|
||||
if (config === 'always') return true;
|
||||
@@ -100,8 +102,8 @@ export class GeneralChatAgent implements Agent {
|
||||
|
||||
private resolveDynamicPolicy(
|
||||
config: ExtendedHumanInterventionConfig | undefined,
|
||||
toolArgs: Record<string, any>,
|
||||
metadata?: Record<string, any>,
|
||||
toolArgs: ToolArguments,
|
||||
metadata?: AgentStateMetadata,
|
||||
): HumanInterventionPolicy | undefined {
|
||||
if (!this.isDynamicInterventionConfig(config)) {
|
||||
return undefined;
|
||||
@@ -146,7 +148,7 @@ export class GeneralChatAgent implements Agent {
|
||||
const toolKey = `${identifier}/${apiName}`;
|
||||
|
||||
// Parse arguments for intervention checking
|
||||
let toolArgs: Record<string, any> = {};
|
||||
let toolArgs: ToolArguments = {};
|
||||
try {
|
||||
toolArgs = JSON.parse(toolCalling.arguments || '{}');
|
||||
} catch {
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import { type ChatToolPayload, type GlobalInterventionAuditConfig } from '@lobechat/types';
|
||||
import type { LobeToolManifest } from '@lobechat/context-engine';
|
||||
import {
|
||||
type ChatToolPayload,
|
||||
type GlobalInterventionAuditConfig,
|
||||
type LobeChatPluginApi,
|
||||
} from '@lobechat/types';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { type AgentRuntimeContext, type AgentState } from '../../types';
|
||||
import { GeneralChatAgent } from '../GeneralChatAgent';
|
||||
|
||||
/**
|
||||
* Test helper: partial manifests are fine for unit tests.
|
||||
* We cast at the boundary so every test doesn't need explicit casts.
|
||||
*/
|
||||
|
||||
type TestOverrides = Record<string, any>;
|
||||
|
||||
describe('GeneralChatAgent', () => {
|
||||
const mockModelRuntimeConfig = {
|
||||
model: 'gpt-4o-mini',
|
||||
provider: 'openai',
|
||||
};
|
||||
|
||||
const createMockState = (overrides?: Partial<AgentState>): AgentState => ({
|
||||
const createMockState = (overrides?: TestOverrides): AgentState => ({
|
||||
operationId: 'test-session',
|
||||
status: 'running',
|
||||
messages: [],
|
||||
@@ -168,7 +180,7 @@ describe('GeneralChatAgent', () => {
|
||||
'test-plugin': {
|
||||
identifier: 'test-plugin',
|
||||
// No humanIntervention config = no intervention needed
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -217,8 +229,8 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'plugin-1': { identifier: 'plugin-1' },
|
||||
'plugin-2': { identifier: 'plugin-2' },
|
||||
'plugin-1': { identifier: 'plugin-1' } as LobeToolManifest,
|
||||
'plugin-2': { identifier: 'plugin-2' } as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -261,7 +273,7 @@ describe('GeneralChatAgent', () => {
|
||||
'test-plugin': {
|
||||
identifier: 'test-plugin',
|
||||
// No humanIntervention config
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -304,8 +316,8 @@ describe('GeneralChatAgent', () => {
|
||||
toolManifestMap: {
|
||||
'dangerous-plugin': {
|
||||
identifier: 'dangerous-plugin',
|
||||
humanIntervention: 'require', // Always require approval
|
||||
},
|
||||
humanIntervention: 'required', // Always require approval
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -354,11 +366,11 @@ describe('GeneralChatAgent', () => {
|
||||
'safe-plugin': {
|
||||
identifier: 'safe-plugin',
|
||||
// No intervention
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
'dangerous-plugin': {
|
||||
identifier: 'dangerous-plugin',
|
||||
humanIntervention: 'require',
|
||||
},
|
||||
humanIntervention: 'required',
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1385,7 +1397,7 @@ describe('GeneralChatAgent', () => {
|
||||
content:
|
||||
'All tasks above have been completed. Please summarize the results or continue with your response following user query language.',
|
||||
role: 'user',
|
||||
},
|
||||
} as any,
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -1457,7 +1469,9 @@ describe('GeneralChatAgent', () => {
|
||||
agentConfig: { maxSteps: 100 },
|
||||
dynamicInterventionAudits: {
|
||||
pathScopeAudit: (toolArgs, metadata) => {
|
||||
const workingDirectory = metadata?.workingDirectory as string | undefined;
|
||||
const workingDirectory = (metadata as Record<string, unknown>)?.workingDirectory as
|
||||
| string
|
||||
| undefined;
|
||||
if (!workingDirectory) return false;
|
||||
const path = toolArgs.path as string;
|
||||
return !path.startsWith(workingDirectory);
|
||||
@@ -1490,9 +1504,9 @@ describe('GeneralChatAgent', () => {
|
||||
type: 'pathScopeAudit',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1518,7 +1532,9 @@ describe('GeneralChatAgent', () => {
|
||||
agentConfig: { maxSteps: 100 },
|
||||
dynamicInterventionAudits: {
|
||||
pathScopeAudit: (toolArgs, metadata) => {
|
||||
const workingDirectory = metadata?.workingDirectory as string | undefined;
|
||||
const workingDirectory = (metadata as Record<string, unknown>)?.workingDirectory as
|
||||
| string
|
||||
| undefined;
|
||||
if (!workingDirectory) return false;
|
||||
const path = toolArgs.path as string;
|
||||
return !path.startsWith(workingDirectory);
|
||||
@@ -1551,9 +1567,9 @@ describe('GeneralChatAgent', () => {
|
||||
type: 'pathScopeAudit',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1601,14 +1617,14 @@ describe('GeneralChatAgent', () => {
|
||||
{
|
||||
name: 'safe-api',
|
||||
// Safe API
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
{
|
||||
name: 'dangerous-api',
|
||||
// API-level config overrides tool-level
|
||||
humanIntervention: 'require',
|
||||
},
|
||||
humanIntervention: 'required',
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1650,7 +1666,7 @@ describe('GeneralChatAgent', () => {
|
||||
'dangerous-tool': {
|
||||
identifier: 'dangerous-tool',
|
||||
humanIntervention: 'required', // Tool requires approval
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'auto-run', // But user config overrides
|
||||
@@ -1706,7 +1722,7 @@ describe('GeneralChatAgent', () => {
|
||||
bash: {
|
||||
identifier: 'bash',
|
||||
humanIntervention: 'never', // Tool doesn't require approval by default
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'allow-list',
|
||||
@@ -1767,11 +1783,11 @@ describe('GeneralChatAgent', () => {
|
||||
'web-search': {
|
||||
identifier: 'web-search',
|
||||
humanIntervention: 'never', // Safe tool
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
'bash': {
|
||||
identifier: 'bash',
|
||||
humanIntervention: 'required', // Dangerous tool
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'manual', // Use tool's own config
|
||||
@@ -1826,9 +1842,9 @@ describe('GeneralChatAgent', () => {
|
||||
{
|
||||
name: 'installPlugin',
|
||||
humanIntervention: 'always', // Always requires intervention
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'auto-run', // User has auto-run enabled
|
||||
@@ -1874,7 +1890,7 @@ describe('GeneralChatAgent', () => {
|
||||
'sensitive-plugin': {
|
||||
identifier: 'sensitive-plugin',
|
||||
humanIntervention: 'always', // Tool-level always policy
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'auto-run',
|
||||
@@ -1928,16 +1944,16 @@ describe('GeneralChatAgent', () => {
|
||||
'web-search': {
|
||||
identifier: 'web-search',
|
||||
humanIntervention: 'required', // Would be bypassed by auto-run
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
'agent-builder': {
|
||||
identifier: 'agent-builder',
|
||||
api: [
|
||||
{
|
||||
name: 'installPlugin',
|
||||
humanIntervention: 'always', // Cannot be bypassed
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'auto-run',
|
||||
@@ -1994,9 +2010,9 @@ describe('GeneralChatAgent', () => {
|
||||
name: 'writeFile',
|
||||
// Rule-based config with 'always' policy
|
||||
humanIntervention: [{ policy: 'always' }],
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'auto-run',
|
||||
@@ -2048,7 +2064,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' },
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' } as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2093,7 +2109,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' },
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' } as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: { approvalMode: 'auto-run' },
|
||||
});
|
||||
@@ -2138,7 +2154,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool' },
|
||||
'my-tool': { identifier: 'my-tool' } as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: { approvalMode: 'headless' },
|
||||
});
|
||||
@@ -2179,7 +2195,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool' },
|
||||
'my-tool': { identifier: 'my-tool' } as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: { approvalMode: 'headless' },
|
||||
});
|
||||
@@ -2225,7 +2241,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' },
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' } as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2276,7 +2292,7 @@ describe('GeneralChatAgent', () => {
|
||||
const state = createMockState({
|
||||
metadata: { workingDirectory: '/workspace' },
|
||||
toolManifestMap: {
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' },
|
||||
'my-tool': { identifier: 'my-tool', humanIntervention: 'never' } as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: { approvalMode: 'auto-run' },
|
||||
});
|
||||
@@ -2333,7 +2349,7 @@ describe('GeneralChatAgent', () => {
|
||||
};
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: { 'my-tool': { identifier: 'my-tool' } },
|
||||
toolManifestMap: { 'my-tool': { identifier: 'my-tool' } as LobeToolManifest },
|
||||
});
|
||||
|
||||
const context = createMockContext('llm_result', {
|
||||
@@ -2366,7 +2382,7 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
bash: { identifier: 'bash', humanIntervention: 'never' },
|
||||
bash: { identifier: 'bash', humanIntervention: 'never' } as LobeToolManifest,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2410,7 +2426,7 @@ describe('GeneralChatAgent', () => {
|
||||
'dangerous-tool': {
|
||||
identifier: 'dangerous-tool',
|
||||
humanIntervention: 'required', // Tool requires approval
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'headless', // Headless mode for async tasks
|
||||
@@ -2460,9 +2476,9 @@ describe('GeneralChatAgent', () => {
|
||||
{
|
||||
name: 'installPlugin',
|
||||
humanIntervention: 'always', // Always requires intervention normally
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'headless', // Headless mode bypasses even 'always'
|
||||
@@ -2509,7 +2525,7 @@ describe('GeneralChatAgent', () => {
|
||||
bash: {
|
||||
identifier: 'bash',
|
||||
humanIntervention: 'never',
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'headless',
|
||||
@@ -2565,19 +2581,19 @@ describe('GeneralChatAgent', () => {
|
||||
'web-search': {
|
||||
identifier: 'web-search',
|
||||
humanIntervention: 'required',
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
'bash': {
|
||||
identifier: 'bash',
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
'agent-builder': {
|
||||
identifier: 'agent-builder',
|
||||
api: [
|
||||
{
|
||||
name: 'installPlugin',
|
||||
humanIntervention: 'always',
|
||||
},
|
||||
} as LobeChatPluginApi,
|
||||
],
|
||||
},
|
||||
} as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'headless',
|
||||
@@ -2629,8 +2645,8 @@ describe('GeneralChatAgent', () => {
|
||||
|
||||
const state = createMockState({
|
||||
toolManifestMap: {
|
||||
search: { identifier: 'search', humanIntervention: 'required' },
|
||||
crawl: { identifier: 'crawl', humanIntervention: 'always' },
|
||||
search: { identifier: 'search', humanIntervention: 'required' } as LobeToolManifest,
|
||||
crawl: { identifier: 'crawl', humanIntervention: 'always' } as LobeToolManifest,
|
||||
},
|
||||
userInterventionConfig: {
|
||||
approvalMode: 'headless',
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('createSecurityBlacklistAudit', () => {
|
||||
const audit = createSecurityBlacklistAudit();
|
||||
// No securityBlacklist in metadata → uses default
|
||||
expect(audit({ command: 'rm -rf /' }, {})).toBe(true);
|
||||
expect(audit({ command: 'rm -rf /' }, { otherField: 'value' })).toBe(true);
|
||||
expect(audit({ command: 'rm -rf /' }, { otherField: 'value' } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should fall back to DEFAULT_SECURITY_BLACKLIST when metadata is undefined', () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
type DynamicInterventionMetadata,
|
||||
type DynamicInterventionResolver,
|
||||
type GlobalInterventionAuditConfig,
|
||||
type SecurityBlacklistConfig,
|
||||
} from '@lobechat/types';
|
||||
|
||||
import { InterventionChecker } from '../core/InterventionChecker';
|
||||
@@ -8,12 +10,18 @@ import { DEFAULT_SECURITY_BLACKLIST } from './defaultSecurityBlacklist';
|
||||
|
||||
export const SECURITY_BLACKLIST_AUDIT_TYPE = 'securityBlacklist';
|
||||
|
||||
declare module '@lobechat/types' {
|
||||
interface DynamicInterventionMetadataOverrides {
|
||||
securityBlacklist?: SecurityBlacklistConfig;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DynamicInterventionResolver that checks security blacklist rules.
|
||||
* Reads blacklist from `metadata.securityBlacklist`, falls back to DEFAULT_SECURITY_BLACKLIST.
|
||||
*/
|
||||
export const createSecurityBlacklistAudit = (): DynamicInterventionResolver => {
|
||||
return (toolArgs: Record<string, any>, metadata?: Record<string, any>): boolean => {
|
||||
return (toolArgs, metadata?: DynamicInterventionMetadata): boolean => {
|
||||
const securityBlacklist = metadata?.securityBlacklist ?? DEFAULT_SECURITY_BLACKLIST;
|
||||
const result = InterventionChecker.checkSecurityBlacklist(securityBlacklist, toolArgs);
|
||||
return result.blocked;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
import {
|
||||
type ArgumentMatcher,
|
||||
type HumanInterventionPolicy,
|
||||
type HumanInterventionRule,
|
||||
type SecurityBlacklistRule,
|
||||
type ShouldInterveneParams,
|
||||
type ToolArguments,
|
||||
} from '@lobechat/types';
|
||||
|
||||
import { DEFAULT_SECURITY_BLACKLIST } from '../audit/defaultSecurityBlacklist';
|
||||
@@ -38,7 +39,7 @@ export class InterventionChecker {
|
||||
*/
|
||||
static checkSecurityBlacklist(
|
||||
securityBlacklist: SecurityBlacklistRule[] = [],
|
||||
toolArgs: Record<string, any> = {},
|
||||
toolArgs: ToolArguments = {},
|
||||
): SecurityCheckResult {
|
||||
for (const rule of securityBlacklist) {
|
||||
if (this.matchesSecurityRule(rule, toolArgs)) {
|
||||
@@ -102,7 +103,7 @@ export class InterventionChecker {
|
||||
*/
|
||||
private static matchesSecurityRule(
|
||||
rule: SecurityBlacklistRule,
|
||||
toolArgs: Record<string, any>,
|
||||
toolArgs: ToolArguments,
|
||||
): boolean {
|
||||
// Security rules must have match criteria
|
||||
if (!rule.match) return false;
|
||||
@@ -130,7 +131,7 @@ export class InterventionChecker {
|
||||
* @param toolArgs - Tool call arguments
|
||||
* @returns true if matches
|
||||
*/
|
||||
private static matchesRule(rule: HumanInterventionRule, toolArgs: Record<string, any>): boolean {
|
||||
private static matchesRule(rule: HumanInterventionRule, toolArgs: ToolArguments): boolean {
|
||||
// No match criteria means it's a default rule
|
||||
if (!rule.match) return true;
|
||||
|
||||
@@ -230,7 +231,7 @@ export class InterventionChecker {
|
||||
* @param args - Tool call arguments
|
||||
* @returns Hash string
|
||||
*/
|
||||
static hashArguments(args: Record<string, any>): string {
|
||||
static hashArguments(args: ToolArguments): string {
|
||||
const sortedKeys = Object.keys(args).sort();
|
||||
const str = sortedKeys.map((key) => `${key}=${JSON.stringify(args[key])}`).join('&');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChatToolPayload } from '@lobechat/types';
|
||||
import type { ChatToolPayload, UIChatMessage } from '@lobechat/types';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import type {
|
||||
@@ -389,22 +389,37 @@ export class AgentRuntime {
|
||||
* @returns Complete AgentState with defaults filled in
|
||||
*/
|
||||
static createInitialState(
|
||||
partialState?: Partial<AgentState> & { operationId: string },
|
||||
partialState?: Omit<Partial<AgentState>, 'messages'> & {
|
||||
messages?: Array<Partial<UIChatMessage> & Pick<UIChatMessage, 'content' | 'role'>>;
|
||||
operationId: string;
|
||||
},
|
||||
): AgentState {
|
||||
const { messages: initialMessages, ...restState } = partialState || { operationId: '' };
|
||||
const now = new Date().toISOString();
|
||||
const nowMs = Date.now();
|
||||
const normalizedMessages =
|
||||
initialMessages?.map(
|
||||
(message, index) =>
|
||||
({
|
||||
createdAt: nowMs,
|
||||
id: `message-${index + 1}`,
|
||||
updatedAt: nowMs,
|
||||
...message,
|
||||
}) as UIChatMessage,
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
cost: AgentRuntime.createDefaultCost(),
|
||||
// Default values
|
||||
createdAt: now,
|
||||
lastModified: now,
|
||||
messages: [],
|
||||
messages: normalizedMessages,
|
||||
status: 'idle',
|
||||
stepCount: 0,
|
||||
toolManifestMap: {},
|
||||
usage: AgentRuntime.createDefaultUsage(),
|
||||
// User provided values override defaults
|
||||
...(partialState || { operationId: '' }),
|
||||
...restState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -518,11 +533,15 @@ export class AgentRuntime {
|
||||
|
||||
const args = JSON.parse(toolArgs);
|
||||
const result = await handler(args);
|
||||
const messageTimestamp = Date.now();
|
||||
|
||||
newState.messages.push({
|
||||
content: JSON.stringify(result),
|
||||
createdAt: messageTimestamp,
|
||||
id: `tool-${toolId}`,
|
||||
role: 'tool',
|
||||
tool_call_id: toolId,
|
||||
updatedAt: messageTimestamp,
|
||||
});
|
||||
|
||||
events.push({ id: toolId, result, type: 'tool_result' });
|
||||
|
||||
@@ -69,7 +69,6 @@ export interface GeneralAgentHumanAbortPayload {
|
||||
|
||||
export interface GeneralAgentConfig {
|
||||
agentConfig?: {
|
||||
[key: string]: any;
|
||||
maxSteps?: number;
|
||||
};
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface RuntimeConfig {
|
||||
/** Function to get operation context and abort controller */
|
||||
getOperation?: (operationId: string) => {
|
||||
abortController: AbortController;
|
||||
context: Record<string, any>;
|
||||
context: Record<string, unknown>;
|
||||
};
|
||||
/** Operation ID for tracking this runtime instance */
|
||||
operationId?: string;
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import type { ActivatedStepTool, OperationToolSet, ToolSource } from '@lobechat/context-engine';
|
||||
import type {
|
||||
ActivatedStepTool,
|
||||
LobeToolManifest,
|
||||
OperationToolSet,
|
||||
ToolSource,
|
||||
UniformTool,
|
||||
} from '@lobechat/context-engine';
|
||||
import type {
|
||||
ChatToolPayload,
|
||||
SecurityBlacklistConfig,
|
||||
ToolArguments,
|
||||
UIChatMessage,
|
||||
UserInterventionConfig,
|
||||
} from '@lobechat/types';
|
||||
|
||||
@@ -27,7 +35,7 @@ export interface AgentState {
|
||||
costLimit?: CostLimit;
|
||||
// --- Metadata ---
|
||||
createdAt: string;
|
||||
error?: any;
|
||||
error?: unknown;
|
||||
/**
|
||||
* When true, the agent is in force-finish mode (maxSteps exceeded).
|
||||
* Tools are allowed to complete, but the next LLM call will have tools stripped
|
||||
@@ -45,7 +53,7 @@ export interface AgentState {
|
||||
/** Timestamp when interruption occurred */
|
||||
interruptedAt: string;
|
||||
/** The instruction that was being executed when interrupted */
|
||||
interruptedInstruction?: any;
|
||||
interruptedInstruction?: unknown;
|
||||
/** Whether the interruption can be resumed */
|
||||
canResume: boolean;
|
||||
};
|
||||
@@ -58,10 +66,10 @@ export interface AgentState {
|
||||
maxSteps?: number;
|
||||
|
||||
// --- Core Context ---
|
||||
messages: any[];
|
||||
messages: UIChatMessage[];
|
||||
|
||||
// --- Extensible metadata ---
|
||||
metadata?: Record<string, any>;
|
||||
metadata?: AgentStateMetadata;
|
||||
|
||||
/**
|
||||
* Model runtime configuration
|
||||
@@ -115,9 +123,9 @@ export interface AgentState {
|
||||
stepCount: number;
|
||||
|
||||
systemRole?: string;
|
||||
toolManifestMap: Record<string, any>;
|
||||
toolManifestMap: Record<string, LobeToolManifest>;
|
||||
|
||||
tools?: any[];
|
||||
tools?: UniformTool[];
|
||||
|
||||
/** Tool source map for routing tool execution to correct handler */
|
||||
toolSourceMap?: Record<string, ToolSource>;
|
||||
@@ -147,7 +155,18 @@ export interface ToolsCalling {
|
||||
type: 'function';
|
||||
}
|
||||
|
||||
export interface AgentStateMetadataOverrides {}
|
||||
|
||||
export interface AgentStateMetadata extends AgentStateMetadataOverrides {
|
||||
activeDeviceId?: string;
|
||||
agentId?: string;
|
||||
securityBlacklist?: SecurityBlacklistConfig;
|
||||
threadId?: string;
|
||||
topicId?: string;
|
||||
workingDirectory?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry for tools, mapping tool names to their implementation.
|
||||
*/
|
||||
export type ToolRegistry = Record<string, (args: any) => Promise<any>>;
|
||||
export type ToolRegistry = Record<string, (args: ToolArguments) => Promise<unknown>>;
|
||||
|
||||
@@ -672,7 +672,7 @@ export function renderMemory(step: StepSnapshot): string {
|
||||
];
|
||||
|
||||
for (const category of sortedKeys) {
|
||||
const items = (memories as Record<string, any>)[category];
|
||||
const items = (memories as Record<string, unknown>)[category];
|
||||
|
||||
if (category === 'persona') {
|
||||
const persona = items as any;
|
||||
|
||||
@@ -34,11 +34,12 @@ export class CalculatorExecutionRuntime {
|
||||
async calculate(args: CalculateParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.calculate(args);
|
||||
const resultState = result.state as CalculateState | undefined;
|
||||
|
||||
const state: CalculateState = {
|
||||
expression: result.state?.expression,
|
||||
precision: result.state?.precision,
|
||||
result: result.state?.result as number | string | undefined,
|
||||
expression: resultState?.expression,
|
||||
precision: resultState?.precision,
|
||||
result: resultState?.result,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -59,12 +60,13 @@ export class CalculatorExecutionRuntime {
|
||||
async evaluate(args: EvaluateParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.evaluate(args);
|
||||
const resultState = result.state as EvaluateState | undefined;
|
||||
|
||||
const state: EvaluateState = {
|
||||
expression: result.state?.expression,
|
||||
precision: result.state?.precision,
|
||||
result: result.state?.result as number | string | undefined,
|
||||
variables: result.state?.variables,
|
||||
expression: resultState?.expression,
|
||||
precision: resultState?.precision,
|
||||
result: resultState?.result,
|
||||
variables: resultState?.variables,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -85,16 +87,17 @@ export class CalculatorExecutionRuntime {
|
||||
async sort(args: SortParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.sort(args);
|
||||
const resultState = result.state as SortState | undefined;
|
||||
|
||||
const state: SortState = {
|
||||
largest: result.state?.largest as number | string | undefined,
|
||||
mode: result.state?.mode,
|
||||
originalNumbers: result.state?.originalNumbers,
|
||||
precision: result.state?.precision,
|
||||
result: result.state?.result,
|
||||
reverse: result.state?.reverse,
|
||||
smallest: result.state?.smallest as number | string | undefined,
|
||||
sorted: result.state?.sorted as (string | number)[] | undefined,
|
||||
largest: resultState?.largest,
|
||||
mode: resultState?.mode,
|
||||
originalNumbers: resultState?.originalNumbers,
|
||||
precision: resultState?.precision,
|
||||
result: resultState?.result,
|
||||
reverse: resultState?.reverse,
|
||||
smallest: resultState?.smallest,
|
||||
sorted: resultState?.sorted,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -115,13 +118,14 @@ export class CalculatorExecutionRuntime {
|
||||
async base(args: BaseParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.base(args);
|
||||
const resultState = result.state as BaseState | undefined;
|
||||
|
||||
const state: BaseState = {
|
||||
convertedNumber: result.state?.convertedNumber,
|
||||
decimalValue: result.state?.decimalValue,
|
||||
originalBase: result.state?.originalBase as string | undefined,
|
||||
originalNumber: result.state?.originalNumber as string | undefined,
|
||||
targetBase: result.state?.targetBase as string | undefined,
|
||||
convertedNumber: resultState?.convertedNumber,
|
||||
decimalValue: resultState?.decimalValue,
|
||||
originalBase: resultState?.originalBase,
|
||||
originalNumber: resultState?.originalNumber,
|
||||
targetBase: resultState?.targetBase,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -142,11 +146,12 @@ export class CalculatorExecutionRuntime {
|
||||
async solve(args: SolveParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.solve(args);
|
||||
const resultState = result.state as SolveState | undefined;
|
||||
|
||||
const state: SolveState = {
|
||||
equation: result.state?.equation,
|
||||
result: result.state?.result as string | string[] | undefined,
|
||||
variable: result.state?.variable,
|
||||
equation: resultState?.equation,
|
||||
result: resultState?.result,
|
||||
variable: resultState?.variable,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -167,11 +172,12 @@ export class CalculatorExecutionRuntime {
|
||||
async differentiate(args: DifferentiateParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.differentiate(args);
|
||||
const resultState = result.state as DifferentiateState | undefined;
|
||||
|
||||
const state: DifferentiateState = {
|
||||
expression: result.state?.expression,
|
||||
result: result.state?.result as string | undefined,
|
||||
variable: result.state?.variable,
|
||||
expression: resultState?.expression,
|
||||
result: resultState?.result,
|
||||
variable: resultState?.variable,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -192,10 +198,11 @@ export class CalculatorExecutionRuntime {
|
||||
async execute(args: ExecuteParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.execute(args);
|
||||
const resultState = result.state as ExecuteState | undefined;
|
||||
|
||||
const state: ExecuteState = {
|
||||
expression: result.state?.expression,
|
||||
result: result.state?.result as string | undefined,
|
||||
expression: resultState?.expression,
|
||||
result: resultState?.result,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -216,13 +223,14 @@ export class CalculatorExecutionRuntime {
|
||||
async defintegrate(args: DefintegrateParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.defintegrate(args);
|
||||
const resultState = result.state as DefintegrateState | undefined;
|
||||
|
||||
const state: DefintegrateState = {
|
||||
expression: result.state?.expression,
|
||||
lowerBound: result.state?.lowerBound,
|
||||
result: result.state?.result as string | undefined,
|
||||
upperBound: result.state?.upperBound,
|
||||
variable: result.state?.variable,
|
||||
expression: resultState?.expression,
|
||||
lowerBound: resultState?.lowerBound,
|
||||
result: resultState?.result,
|
||||
upperBound: resultState?.upperBound,
|
||||
variable: resultState?.variable,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -243,11 +251,12 @@ export class CalculatorExecutionRuntime {
|
||||
async integrate(args: IntegrateParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.integrate(args);
|
||||
const resultState = result.state as IntegrateState | undefined;
|
||||
|
||||
const state: IntegrateState = {
|
||||
expression: result.state?.expression,
|
||||
result: result.state?.result as string | undefined,
|
||||
variable: result.state?.variable,
|
||||
expression: resultState?.expression,
|
||||
result: resultState?.result,
|
||||
variable: resultState?.variable,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -268,12 +277,13 @@ export class CalculatorExecutionRuntime {
|
||||
async limit(args: LimitParams): Promise<BuiltinServerRuntimeOutput> {
|
||||
try {
|
||||
const result = await calculatorExecutor.limit(args);
|
||||
const resultState = result.state as LimitState | undefined;
|
||||
|
||||
const state: LimitState = {
|
||||
expression: result.state?.expression,
|
||||
point: result.state?.point,
|
||||
result: result.state?.result as string | undefined,
|
||||
variable: result.state?.variable,
|
||||
expression: resultState?.expression,
|
||||
point: resultState?.point,
|
||||
result: resultState?.result,
|
||||
variable: resultState?.variable,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -360,7 +360,7 @@ export class CloudSandboxExecutionRuntime {
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log('executeCode error', error);
|
||||
console.error('executeCode error', error);
|
||||
return this.handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,10 @@ class ClientSandboxService implements ISandboxService {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
async callTool(toolName: string, params: Record<string, any>): Promise<SandboxCallToolResult> {
|
||||
async callTool(
|
||||
toolName: string,
|
||||
params: Record<string, unknown>,
|
||||
): Promise<SandboxCallToolResult> {
|
||||
return cloudSandboxService.callTool(toolName, params, {
|
||||
topicId: this.topicId,
|
||||
userId: this.userId,
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface ISandboxService {
|
||||
* @param toolName - The name of the tool to call (e.g., 'runCommand', 'writeLocalFile')
|
||||
* @param params - The parameters for the tool
|
||||
*/
|
||||
callTool: (toolName: string, params: Record<string, any>) => Promise<SandboxCallToolResult>;
|
||||
callTool: (toolName: string, params: Record<string, unknown>) => Promise<SandboxCallToolResult>;
|
||||
|
||||
/**
|
||||
* Export a file from sandbox and upload to cloud storage
|
||||
|
||||
@@ -11,8 +11,10 @@ import ExecuteTasksIntervention from './ExecuteTasks';
|
||||
* before the tool is executed.
|
||||
*/
|
||||
export const GroupManagementInterventions: Record<string, BuiltinIntervention> = {
|
||||
[GroupManagementApiName.executeAgentTask]: ExecuteTaskIntervention as BuiltinIntervention,
|
||||
[GroupManagementApiName.executeAgentTasks]: ExecuteTasksIntervention as BuiltinIntervention,
|
||||
[GroupManagementApiName.executeAgentTask]:
|
||||
ExecuteTaskIntervention as unknown as BuiltinIntervention,
|
||||
[GroupManagementApiName.executeAgentTasks]:
|
||||
ExecuteTasksIntervention as unknown as BuiltinIntervention,
|
||||
};
|
||||
|
||||
export { default as ExecuteTaskIntervention } from './ExecuteTask';
|
||||
|
||||
@@ -12,9 +12,9 @@ import CreatePlanIntervention from './CreatePlan';
|
||||
* before the tool is executed.
|
||||
*/
|
||||
export const GTDInterventions: Record<string, BuiltinIntervention> = {
|
||||
[GTDApiName.clearTodos]: ClearTodosIntervention as BuiltinIntervention,
|
||||
[GTDApiName.createPlan]: CreatePlanIntervention as BuiltinIntervention,
|
||||
[GTDApiName.createTodos]: AddTodoIntervention as BuiltinIntervention,
|
||||
[GTDApiName.clearTodos]: ClearTodosIntervention as unknown as BuiltinIntervention,
|
||||
[GTDApiName.createPlan]: CreatePlanIntervention as unknown as BuiltinIntervention,
|
||||
[GTDApiName.createTodos]: AddTodoIntervention as unknown as BuiltinIntervention,
|
||||
};
|
||||
|
||||
export { default as AddTodoIntervention } from './AddTodo';
|
||||
|
||||
@@ -114,12 +114,14 @@ TodoListUI.displayName = 'TodoListUI';
|
||||
* TodoList Render component for GTD tool
|
||||
* Read-only display of todo items matching the style of AddTodoIntervention
|
||||
*/
|
||||
const TodoListRender = memo<BuiltinRenderProps<unknown, TodoListRenderState>>(({ pluginState }) => {
|
||||
const todos = pluginState?.todos;
|
||||
const items: TodoItem[] = todos?.items || [];
|
||||
const TodoListRender = memo<BuiltinRenderProps<Record<string, never>, TodoListRenderState>>(
|
||||
({ pluginState }) => {
|
||||
const todos = pluginState?.todos;
|
||||
const items: TodoItem[] = todos?.items || [];
|
||||
|
||||
return <TodoListUI items={items} />;
|
||||
});
|
||||
return <TodoListUI items={items} />;
|
||||
},
|
||||
);
|
||||
|
||||
TodoListRender.displayName = 'TodoListRender';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import FileItem from '../../components/FileItem';
|
||||
interface SearchFilesProps {
|
||||
listResults?: LocalFileItem[];
|
||||
messageId: string;
|
||||
pluginError: ChatMessagePluginError;
|
||||
pluginError?: ChatMessagePluginError;
|
||||
}
|
||||
|
||||
const SearchFiles = memo<SearchFilesProps>(({ listResults = [], messageId }) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ const ListFiles = memo<BuiltinRenderProps<ListLocalFileParams, LocalFileListStat
|
||||
<SearchResult
|
||||
listResults={pluginState?.listResults}
|
||||
messageId={messageId}
|
||||
pluginError={pluginError}
|
||||
pluginError={pluginError || undefined}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import FileItem from '../../components/FileItem';
|
||||
|
||||
interface SearchFilesProps {
|
||||
messageId: string;
|
||||
pluginError: ChatMessagePluginError;
|
||||
pluginError?: ChatMessagePluginError;
|
||||
searchResults?: FileResult[];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const SearchFiles = memo<BuiltinRenderProps<LocalSearchFilesParams, LocalFileSea
|
||||
<SearchQuery args={args} messageId={messageId} pluginState={pluginState} />
|
||||
<SearchResult
|
||||
messageId={messageId}
|
||||
pluginError={pluginError}
|
||||
pluginError={pluginError || undefined}
|
||||
searchResults={pluginState?.searchResults}
|
||||
/>
|
||||
</Flexbox>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { type DynamicInterventionResolver } from '@lobechat/types';
|
||||
import type { DynamicInterventionMetadata, DynamicInterventionResolver } from '@lobechat/types';
|
||||
|
||||
import { normalizePathForScope, resolvePathWithScope } from './utils/path';
|
||||
|
||||
declare module '@lobechat/types' {
|
||||
interface DynamicInterventionMetadataOverrides {
|
||||
workingDirectory?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is within the working directory
|
||||
*/
|
||||
@@ -24,7 +30,7 @@ const isPathWithinWorkingDirectory = (
|
||||
* Extract all path values from tool arguments
|
||||
* Looks for common path parameter names used in local-system tools
|
||||
*/
|
||||
const extractPaths = (toolArgs: Record<string, any>): string[] => {
|
||||
const extractPaths = (toolArgs: Record<string, unknown>): string[] => {
|
||||
const paths: string[] = [];
|
||||
const pathParamNames = ['path', 'file_path', 'directory', 'oldPath', 'newPath'];
|
||||
|
||||
@@ -44,9 +50,10 @@ const extractPaths = (toolArgs: Record<string, any>): string[] => {
|
||||
// Handle 'items' array for moveLocalFiles (contains oldPath/newPath objects)
|
||||
if (Array.isArray(toolArgs.items)) {
|
||||
for (const item of toolArgs.items) {
|
||||
if (typeof item === 'object') {
|
||||
if (item.oldPath) paths.push(item.oldPath);
|
||||
if (item.newPath) paths.push(item.newPath);
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
const moveItem = item as { newPath?: string; oldPath?: string };
|
||||
if (moveItem.oldPath) paths.push(moveItem.oldPath);
|
||||
if (moveItem.newPath) paths.push(moveItem.newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,11 +66,11 @@ const extractPaths = (toolArgs: Record<string, any>): string[] => {
|
||||
* Returns true if any path is outside the working directory (requires intervention)
|
||||
*/
|
||||
export const pathScopeAudit: DynamicInterventionResolver = (
|
||||
toolArgs: Record<string, any>,
|
||||
metadata?: Record<string, any>,
|
||||
toolArgs,
|
||||
metadata?: DynamicInterventionMetadata,
|
||||
): boolean => {
|
||||
const workingDirectory = metadata?.workingDirectory as string | undefined;
|
||||
const toolScope = toolArgs.scope as string | undefined;
|
||||
const workingDirectory = metadata?.workingDirectory;
|
||||
const toolScope = typeof toolArgs.scope === 'string' ? toolArgs.scope : undefined;
|
||||
|
||||
// If no working directory is set, no intervention needed
|
||||
if (!workingDirectory) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {
|
||||
import {
|
||||
type GetCommandOutputResult,
|
||||
type GlobFilesResult,
|
||||
type GrepContentResult,
|
||||
@@ -32,7 +32,7 @@ export interface FileResult {
|
||||
isDirectory: boolean;
|
||||
lastAccessTime: Date;
|
||||
metadata?: {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
modifiedTime: Date;
|
||||
name: string;
|
||||
|
||||
@@ -50,7 +50,7 @@ export const resolveArgsWithScope = <T extends { scope?: string }>(
|
||||
fallbackScope?: string,
|
||||
): T => {
|
||||
const scope = args.scope || fallbackScope;
|
||||
const currentPath = (args as Record<string, any>)[pathField] as string | undefined;
|
||||
const currentPath = (args as Record<string, unknown>)[pathField] as string | undefined;
|
||||
const resolved = resolvePathWithScope(currentPath, scope);
|
||||
if (resolved === currentPath) return args;
|
||||
return { ...args, [pathField]: resolved };
|
||||
|
||||
@@ -9,5 +9,6 @@ import AddExperienceMemoryIntervention from './AddExperienceMemory';
|
||||
* Intervention components display when human approval is required before tool execution.
|
||||
*/
|
||||
export const MemoryInterventions: Record<string, BuiltinIntervention> = {
|
||||
[MemoryApiName.addExperienceMemory]: AddExperienceMemoryIntervention as BuiltinIntervention,
|
||||
[MemoryApiName.addExperienceMemory]:
|
||||
AddExperienceMemoryIntervention as unknown as BuiltinIntervention,
|
||||
};
|
||||
|
||||
@@ -6,5 +6,5 @@ import { CreateDocumentPlaceholder } from './CreateDocument';
|
||||
export { CreateDocumentPlaceholder } from './CreateDocument';
|
||||
|
||||
export const NotebookPlaceholders: Record<string, BuiltinPlaceholder> = {
|
||||
[NotebookApiName.createDocument]: CreateDocumentPlaceholder as BuiltinPlaceholder,
|
||||
[NotebookApiName.createDocument]: CreateDocumentPlaceholder as unknown as BuiltinPlaceholder,
|
||||
};
|
||||
|
||||
@@ -10,12 +10,18 @@ const Inspector = memo<BuiltinPortalProps>(({ arguments: args, messageId, state,
|
||||
switch (apiName) {
|
||||
// 兼容旧版数据
|
||||
case WebBrowsingApiName.search: {
|
||||
return <Search messageId={messageId} query={args as SearchQuery} response={state} />;
|
||||
return (
|
||||
<Search
|
||||
messageId={messageId}
|
||||
query={args as unknown as SearchQuery}
|
||||
response={state as unknown as any}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
case WebBrowsingApiName.crawlSinglePage: {
|
||||
const url = args.url;
|
||||
const result = (state as CrawlPluginState).results.find(
|
||||
const result = (state as unknown as CrawlPluginState).results.find(
|
||||
(result) => result.originalUrl === url,
|
||||
);
|
||||
|
||||
@@ -26,8 +32,8 @@ const Inspector = memo<BuiltinPortalProps>(({ arguments: args, messageId, state,
|
||||
return (
|
||||
<PageContents
|
||||
messageId={messageId}
|
||||
results={(state as CrawlPluginState).results}
|
||||
urls={args.urls}
|
||||
results={(state as unknown as CrawlPluginState).results}
|
||||
urls={args.urls as string[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
FileIcon,
|
||||
FlaskConicalIcon,
|
||||
ImageIcon,
|
||||
type LucideIcon,
|
||||
MapIcon,
|
||||
MusicIcon,
|
||||
NewspaperIcon,
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
VideoIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const CATEGORY_ICON_MAP: Record<string, any> = {
|
||||
export const CATEGORY_ICON_MAP: Record<string, LucideIcon> = {
|
||||
files: FileIcon,
|
||||
general: SearchIcon,
|
||||
images: ImageIcon,
|
||||
|
||||
@@ -23,7 +23,7 @@ import { type BuiltinIntervention } from '@lobechat/types';
|
||||
* Organized by toolset (identifier) -> API name
|
||||
* Only register APIs that have custom intervention UI
|
||||
*/
|
||||
export const BuiltinToolInterventions: Record<string, Record<string, any>> = {
|
||||
export const BuiltinToolInterventions: Record<string, Record<string, unknown>> = {
|
||||
[AgentBuilderManifest.identifier]: AgentBuilderInterventions,
|
||||
[CloudSandboxManifest.identifier]: CloudSandboxInterventions,
|
||||
[GroupManagementManifest.identifier]: GroupManagementInterventions,
|
||||
@@ -47,5 +47,5 @@ export const getBuiltinIntervention = (
|
||||
const toolset = BuiltinToolInterventions[identifier];
|
||||
if (!toolset) return undefined;
|
||||
|
||||
return toolset[apiName];
|
||||
return toolset[apiName] as BuiltinIntervention | undefined;
|
||||
};
|
||||
|
||||
@@ -15,13 +15,13 @@ import { type BuiltinPlaceholder } from '@lobechat/types';
|
||||
* Builtin tools placeholders registry
|
||||
* Organized by toolset (identifier) -> API name
|
||||
*/
|
||||
export const BuiltinToolPlaceholders: Record<string, Record<string, any>> = {
|
||||
export const BuiltinToolPlaceholders: Record<string, Record<string, unknown>> = {
|
||||
[LocalSystemIdentifier]: {
|
||||
[LocalSystemApiName.searchLocalFiles]: LocalSystemSearchFilesPlaceholder,
|
||||
[LocalSystemApiName.listLocalFiles]: LocalSystemListFilesPlaceholder,
|
||||
},
|
||||
[NotebookIdentifier]: NotebookPlaceholders as Record<string, any>,
|
||||
[WebBrowsingManifest.identifier]: WebBrowsingPlaceholders as Record<string, any>,
|
||||
[NotebookIdentifier]: NotebookPlaceholders as Record<string, unknown>,
|
||||
[WebBrowsingManifest.identifier]: WebBrowsingPlaceholders as Record<string, unknown>,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -38,5 +38,5 @@ export const getBuiltinPlaceholder = (
|
||||
const toolset = BuiltinToolPlaceholders[identifier];
|
||||
if (!toolset) return undefined;
|
||||
|
||||
return toolset[apiName];
|
||||
return toolset[apiName] as BuiltinPlaceholder | undefined;
|
||||
};
|
||||
|
||||
@@ -11,10 +11,7 @@ export const merge: typeof _merge = <T = object>(target: T, source: T) =>
|
||||
if (Array.isArray(obj)) return src;
|
||||
});
|
||||
|
||||
type MergeableItem = {
|
||||
[key: string]: any;
|
||||
id: string;
|
||||
};
|
||||
type MergeableItem = object & { id: string };
|
||||
|
||||
/**
|
||||
* Merge two arrays based on id, preserving metadata from default items
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ToolSchema } from '@lobechat/types';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { ToolNameResolver } from '../ToolNameResolver';
|
||||
@@ -635,11 +636,11 @@ describe('ToolNameResolver', () => {
|
||||
name: apiName,
|
||||
parameters: {
|
||||
properties: {
|
||||
prompt: { type: 'string' },
|
||||
size: { type: 'string' },
|
||||
prompt: { type: 'string' as const },
|
||||
size: { type: 'string' as const },
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
type: 'object' as const,
|
||||
} as ToolSchema,
|
||||
},
|
||||
],
|
||||
identifier,
|
||||
|
||||
@@ -33,6 +33,7 @@ export type {
|
||||
ToolsGenerationContext,
|
||||
ToolsGenerationResult,
|
||||
ToolSource,
|
||||
UniformTool,
|
||||
} from './types';
|
||||
|
||||
// Utility functions
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import type { ExtendedHumanInterventionConfig } from '@/types/index';
|
||||
import type { ExtendedHumanInterventionConfig, ToolSchema } from '@/types/index';
|
||||
|
||||
interface LobeToolManifestMeta {
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
readme?: string;
|
||||
tags?: string[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface LobeChatPluginApi {
|
||||
description: string;
|
||||
@@ -16,14 +24,15 @@ export interface LobeChatPluginApi {
|
||||
*/
|
||||
humanIntervention?: ExtendedHumanInterventionConfig;
|
||||
name: string;
|
||||
parameters: Record<string, any>;
|
||||
parameters: ToolSchema;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface LobeToolManifest {
|
||||
api: LobeChatPluginApi[];
|
||||
humanIntervention?: ExtendedHumanInterventionConfig;
|
||||
identifier: string;
|
||||
meta: any;
|
||||
meta: LobeToolManifestMeta;
|
||||
systemRole?: string;
|
||||
type?: 'default' | 'standalone' | 'markdown' | 'mcp' | 'builtin';
|
||||
}
|
||||
@@ -31,13 +40,15 @@ export interface LobeToolManifest {
|
||||
/**
|
||||
* Tools generation context
|
||||
*/
|
||||
export interface ToolsGenerationContext {
|
||||
/** Additional extension context */
|
||||
[key: string]: any;
|
||||
export interface ToolsGenerationContextOverrides {}
|
||||
|
||||
export interface ToolsGenerationContext extends ToolsGenerationContextOverrides {
|
||||
/** Whether image generation is allowed */
|
||||
allowImageGeneration?: boolean;
|
||||
/** Environment information */
|
||||
environment?: 'desktop' | 'web';
|
||||
/** Whether the tool was explicitly activated and should bypass normal filters */
|
||||
isExplicitActivation?: boolean;
|
||||
/** Whether search is enabled */
|
||||
isSearchEnabled?: boolean;
|
||||
/** Model name for context-aware plugin filtering */
|
||||
@@ -135,12 +146,10 @@ export interface UniformFunctions {
|
||||
name: string;
|
||||
/**
|
||||
* The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
|
||||
* @type {{ [key: string]: any }}
|
||||
* @type {Record<string, unknown>}
|
||||
* @memberof UniformFunctions
|
||||
*/
|
||||
parameters?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
parameters?: ToolSchema;
|
||||
}
|
||||
|
||||
export interface UniformTool {
|
||||
|
||||
@@ -144,6 +144,8 @@ export class GroupRoleTransformProcessor extends BaseProcessor {
|
||||
* Transform an assistant message from another agent to a user message
|
||||
*/
|
||||
private transformAssistantMessage(msg: Message): Message {
|
||||
if (!msg.agentId) return msg;
|
||||
|
||||
const agentInfo = this.config.agentMap[msg.agentId];
|
||||
if (!agentInfo) {
|
||||
// No agent info found, keep original
|
||||
@@ -177,6 +179,8 @@ export class GroupRoleTransformProcessor extends BaseProcessor {
|
||||
* Transform a tool result message from another agent to a user message
|
||||
*/
|
||||
private transformToolMessage(msg: Message): Message {
|
||||
if (!msg.agentId) return msg;
|
||||
|
||||
const agentInfo = this.config.agentMap[msg.agentId];
|
||||
if (!agentInfo) {
|
||||
// No agent info found, keep original
|
||||
|
||||
@@ -28,12 +28,11 @@ interface MinimalMessage {
|
||||
metadata?: {
|
||||
agentCouncil?: boolean;
|
||||
compare?: boolean;
|
||||
[key: string]: any;
|
||||
} | null;
|
||||
parentId?: string;
|
||||
role: string;
|
||||
tool_call_id?: string;
|
||||
tools?: Array<{ id: string; [key: string]: any }>;
|
||||
tools?: Array<{ id: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,7 +72,7 @@ export class ToolMessageReorder extends BaseProcessor {
|
||||
});
|
||||
|
||||
// 2. Collect all valid tool messages
|
||||
const toolMessages: Record<string, any> = {};
|
||||
const toolMessages: Record<string, unknown> = {};
|
||||
messages.forEach((message) => {
|
||||
if (
|
||||
message.role === 'tool' &&
|
||||
|
||||
@@ -67,17 +67,17 @@ describe('AgentCouncilFlattenProcessor', () => {
|
||||
expect(result.messages).toHaveLength(2);
|
||||
|
||||
// Check first assistant message
|
||||
const assistantMsg1 = result.messages[0];
|
||||
const assistantMsg1 = result.messages[0]!;
|
||||
expect(assistantMsg1.role).toBe('assistant');
|
||||
expect(assistantMsg1.id).toBe('msg-agent-backend-1');
|
||||
expect(assistantMsg1.content).toBe('Backend perspective content');
|
||||
expect(assistantMsg1.agentId).toBe('agent-backend');
|
||||
expect(assistantMsg1.model).toBe('gpt-4');
|
||||
expect(assistantMsg1.provider).toBe('openai');
|
||||
expect(assistantMsg1.meta.title).toBe('Backend Developer');
|
||||
expect(assistantMsg1.meta!.title).toBe('Backend Developer');
|
||||
|
||||
// Check second assistant message
|
||||
const assistantMsg2 = result.messages[1];
|
||||
const assistantMsg2 = result.messages[1]!;
|
||||
expect(assistantMsg2.role).toBe('assistant');
|
||||
expect(assistantMsg2.id).toBe('msg-agent-devops-1');
|
||||
expect(assistantMsg2.content).toBe('DevOps perspective content');
|
||||
@@ -140,13 +140,13 @@ describe('AgentCouncilFlattenProcessor', () => {
|
||||
expect(result.messages).toHaveLength(2);
|
||||
|
||||
// Check assistant message
|
||||
const assistantMsg = result.messages[0];
|
||||
const assistantMsg = result.messages[0]!;
|
||||
expect(assistantMsg.role).toBe('assistant');
|
||||
expect(assistantMsg.tools).toHaveLength(1);
|
||||
expect(assistantMsg.tools[0].id).toBe('tool-1');
|
||||
expect(assistantMsg.tools![0].id).toBe('tool-1');
|
||||
|
||||
// Check tool message
|
||||
const toolMsg = result.messages[1];
|
||||
const toolMsg = result.messages[1]!;
|
||||
expect(toolMsg.role).toBe('tool');
|
||||
expect(toolMsg.id).toBe('msg-tool-1');
|
||||
expect(toolMsg.content).toBe('Search result');
|
||||
@@ -585,7 +585,7 @@ describe('AgentCouncilFlattenProcessor', () => {
|
||||
expect(result.messages[0].agentId).toBe('agent-backend');
|
||||
expect(result.messages[0].model).toBe('gpt-4');
|
||||
expect(result.messages[0].provider).toBe('openai');
|
||||
expect(result.messages[0].meta.title).toBe('Backend Developer');
|
||||
expect(result.messages[0]!.meta!.title).toBe('Backend Developer');
|
||||
expect(result.messages[0].content).toContain('Backend Developer');
|
||||
|
||||
// Check second agent (DevOps Engineer)
|
||||
@@ -593,12 +593,12 @@ describe('AgentCouncilFlattenProcessor', () => {
|
||||
expect(result.messages[1].agentId).toBe('agent-devops');
|
||||
expect(result.messages[1].model).toBe('claude-3-5-sonnet-20241022');
|
||||
expect(result.messages[1].provider).toBe('anthropic');
|
||||
expect(result.messages[1].meta.title).toBe('DevOps Engineer');
|
||||
expect(result.messages[1]!.meta!.title).toBe('DevOps Engineer');
|
||||
|
||||
// Check third agent (Software Architect)
|
||||
expect(result.messages[2].id).toBe('msg-agent-architect-1');
|
||||
expect(result.messages[2].agentId).toBe('agent-architect');
|
||||
expect(result.messages[2].meta.title).toBe('Software Architect');
|
||||
expect(result.messages[2]!.meta!.title).toBe('Software Architect');
|
||||
|
||||
// Check metadata
|
||||
expect(result.metadata.agentCouncilMessagesFlattened).toBe(1);
|
||||
|
||||
@@ -56,12 +56,12 @@ describe('GroupMessageFlattenProcessor', () => {
|
||||
expect(result.messages).toHaveLength(2);
|
||||
|
||||
// Check assistant message
|
||||
const assistantMsg = result.messages[0];
|
||||
const assistantMsg = result.messages[0]!;
|
||||
expect(assistantMsg.role).toBe('assistant');
|
||||
expect(assistantMsg.id).toBe('msg-1');
|
||||
expect(assistantMsg.content).toBe('Checking weather');
|
||||
expect(assistantMsg.tools).toHaveLength(1);
|
||||
expect(assistantMsg.tools[0]).toEqual({
|
||||
expect(assistantMsg.tools![0]).toEqual({
|
||||
id: 'tool-1',
|
||||
type: 'builtin',
|
||||
apiName: 'search',
|
||||
@@ -475,7 +475,7 @@ describe('GroupMessageFlattenProcessor', () => {
|
||||
const context = createContext(input);
|
||||
const result = await processor.process(context);
|
||||
|
||||
const assistantMsg = result.messages[0];
|
||||
const assistantMsg = result.messages[0]!;
|
||||
expect(assistantMsg.parentId).toBe('parent-1');
|
||||
expect(assistantMsg.threadId).toBe('thread-1');
|
||||
expect(assistantMsg.groupId).toBe('group-1');
|
||||
@@ -669,8 +669,8 @@ describe('GroupMessageFlattenProcessor', () => {
|
||||
expect(assistantMsg.role).toBe('assistant');
|
||||
expect(assistantMsg.id).toBe('msg_LnIlOyMUnX1ylf');
|
||||
expect(assistantMsg.tools).toHaveLength(1);
|
||||
expect(assistantMsg.tools[0].identifier).toBe('lobe-web-browsing');
|
||||
expect(assistantMsg.tools[0].apiName).toBe('search');
|
||||
expect(assistantMsg.tools![0].identifier).toBe('lobe-web-browsing');
|
||||
expect(assistantMsg.tools![0].apiName).toBe('search');
|
||||
expect(assistantMsg.reasoning).toBeDefined();
|
||||
expect(assistantMsg.topicId).toBe('tpc_WQ1wRvxdDpLw');
|
||||
expect(assistantMsg.parentId).toBe('msg_ekwWzxAKueHkd6');
|
||||
|
||||
@@ -17,7 +17,7 @@ vi.mock('@lobechat/utils/imageToBase64', async (importOriginal) => {
|
||||
|
||||
const createContext = (messages: UIChatMessage[]): PipelineContext => ({
|
||||
initialState: { messages: [] } as any,
|
||||
messages,
|
||||
messages: messages as unknown as PipelineContext['messages'],
|
||||
metadata: { model: 'gpt-4', provider: 'openai', maxTokens: 100000 },
|
||||
isAborted: false,
|
||||
});
|
||||
|
||||
@@ -241,7 +241,7 @@ Result content here`,
|
||||
expect(result.messages[0].id).toBe('msg-1');
|
||||
expect(result.messages[0].createdAt).toBe(1234567890);
|
||||
expect(result.messages[0].extra).toEqual({ foo: 'bar' });
|
||||
expect(result.messages[0].metadata.customField).toBe('value');
|
||||
expect(result.messages[0]!.metadata!.customField).toBe('value');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ describe('ToolCallProcessor', () => {
|
||||
|
||||
const result = await processor.process(context);
|
||||
|
||||
expect(result.messages[0].tool_calls[0].thoughtSignature).toBeUndefined();
|
||||
expect(result.messages[0]!.tool_calls![0].thoughtSignature).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use custom genToolCallingName function', async () => {
|
||||
@@ -160,7 +160,7 @@ describe('ToolCallProcessor', () => {
|
||||
const result = await processor.process(context);
|
||||
|
||||
expect(genToolCallingName).toHaveBeenCalledWith('web', 'search', 'builtin');
|
||||
expect(result.messages[0].tool_calls[0].function.name).toBe('custom_web_search_builtin');
|
||||
expect(result.messages[0]!.tool_calls![0].function.name).toBe('custom_web_search_builtin');
|
||||
});
|
||||
|
||||
it('should handle multiple tool calls', async () => {
|
||||
@@ -188,9 +188,9 @@ describe('ToolCallProcessor', () => {
|
||||
|
||||
const result = await processor.process(context);
|
||||
|
||||
expect(result.messages[0].tool_calls).toHaveLength(2);
|
||||
expect(result.messages[0].tool_calls[0].function.name).toBe('web.search');
|
||||
expect(result.messages[0].tool_calls[1].function.name).toBe('utils.translate');
|
||||
expect(result.messages[0]!.tool_calls).toHaveLength(2);
|
||||
expect(result.messages[0]!.tool_calls![0].function.name).toBe('web.search');
|
||||
expect(result.messages[0]!.tool_calls![1].function.name).toBe('utils.translate');
|
||||
});
|
||||
|
||||
it('should remove tool_calls and tools when not supported', async () => {
|
||||
|
||||
@@ -36,11 +36,11 @@ export interface OfficialToolItem {
|
||||
export interface AgentBuilderContext {
|
||||
/** Agent configuration */
|
||||
config?: {
|
||||
chatConfig?: Record<string, any>;
|
||||
chatConfig?: Record<string, unknown>;
|
||||
model?: string;
|
||||
openingMessage?: string;
|
||||
openingQuestions?: string[];
|
||||
params?: Record<string, any>;
|
||||
params?: Record<string, unknown>;
|
||||
plugins?: string[];
|
||||
provider?: string;
|
||||
systemRole?: string;
|
||||
|
||||
+2
-2
@@ -85,13 +85,13 @@ describe('AgentManagementContextInjector', () => {
|
||||
expect(result.messages).toHaveLength(3);
|
||||
expect(result.messages[1].content).toBe('Let @Designer Agent help me');
|
||||
|
||||
const delegationMsg = result.messages[2];
|
||||
const delegationMsg = result.messages[2]!;
|
||||
expect(delegationMsg.role).toBe('user');
|
||||
expect(delegationMsg.content).toContain('<mentioned_agents>');
|
||||
expect(delegationMsg.content).toContain('agt_designer');
|
||||
expect(delegationMsg.content).toContain('Designer Agent');
|
||||
expect(delegationMsg.content).toContain('MUST use the callAgent tool');
|
||||
expect(delegationMsg.meta.injectType).toBe('agent-mention-delegation');
|
||||
expect(delegationMsg.meta!.injectType).toBe('agent-mention-delegation');
|
||||
});
|
||||
|
||||
it('should inject after the LAST user message, not the first', async () => {
|
||||
|
||||
@@ -12,11 +12,14 @@ import type { UIChatMessage } from '@lobechat/types';
|
||||
*/
|
||||
export interface PipelineContextMetadataOverrides {}
|
||||
|
||||
export interface ContextEngineAgentStateOverrides {}
|
||||
|
||||
export interface PipelineMessageOverrides {}
|
||||
|
||||
/**
|
||||
* Agent state - inferred from original project types
|
||||
*/
|
||||
export interface AgentState {
|
||||
[key: string]: any;
|
||||
export interface AgentState extends ContextEngineAgentStateOverrides {
|
||||
messages: UIChatMessage[];
|
||||
model?: string;
|
||||
provider?: string;
|
||||
@@ -45,10 +48,38 @@ export interface MessageToolCall {
|
||||
thoughtSignature?: string;
|
||||
type: 'function';
|
||||
}
|
||||
export interface Message {
|
||||
[key: string]: any;
|
||||
|
||||
export interface Message extends PipelineMessageOverrides {
|
||||
agentId?: string | 'supervisor';
|
||||
agentName?: string;
|
||||
children?: any[];
|
||||
content: string | any[];
|
||||
createdAt?: number | string;
|
||||
error?: unknown;
|
||||
extra?: any;
|
||||
groupId?: string;
|
||||
id?: string;
|
||||
imageList?: unknown[];
|
||||
members?: any[];
|
||||
meta?: Record<string, unknown>;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
model?: string | null;
|
||||
name?: string;
|
||||
parentId?: string;
|
||||
plugin?: any;
|
||||
pluginError?: unknown;
|
||||
pluginState?: any;
|
||||
provider?: string | null;
|
||||
reasoning?: unknown;
|
||||
role: string;
|
||||
targetId?: string | null;
|
||||
tasks?: any[];
|
||||
threadId?: string | null;
|
||||
tool_call_id?: string;
|
||||
tool_calls?: MessageToolCall[];
|
||||
tools?: any[];
|
||||
topicId?: string;
|
||||
updatedAt?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +143,7 @@ export interface PipelineResult {
|
||||
/** Whether aborted */
|
||||
isAborted: boolean;
|
||||
/** Final processed messages */
|
||||
messages: any[];
|
||||
messages: Message[];
|
||||
/** Metadata from processing */
|
||||
metadata: PipelineContextMetadata;
|
||||
/** Execution statistics */
|
||||
@@ -164,7 +195,7 @@ export interface FileContext {
|
||||
export interface RetrievalChunk {
|
||||
content: string;
|
||||
id: string;
|
||||
metadata?: Record<string, any>;
|
||||
metadata?: Record<string, unknown>;
|
||||
similarity: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[
|
||||
processedMessage.tools.length > 0 &&
|
||||
processedMessage.metadata
|
||||
) {
|
||||
const cleanedMetadata: Record<string, any> = {};
|
||||
const cleanedMetadata: Record<string, unknown> = {};
|
||||
Object.entries(processedMessage.metadata).forEach(([key, value]) => {
|
||||
if (usagePerformanceFields.has(key)) {
|
||||
cleanedMetadata[key] = value;
|
||||
|
||||
@@ -21,7 +21,7 @@ export class FlatListBuilder {
|
||||
private branchResolver: BranchResolver,
|
||||
private messageCollector: MessageCollector,
|
||||
private messageTransformer: MessageTransformer,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generate flatList from messages array
|
||||
@@ -793,7 +793,7 @@ export class FlatListBuilder {
|
||||
const msgPerformance = assistant.performance || metaPerformance;
|
||||
|
||||
// Extract non-usage/performance metadata fields
|
||||
const otherMetadata: Record<string, any> = {};
|
||||
const otherMetadata: Record<string, unknown> = {};
|
||||
if (assistant.metadata) {
|
||||
const usagePerformanceFields = new Set([
|
||||
'acceptedPredictionTokens',
|
||||
@@ -851,7 +851,7 @@ export class FlatListBuilder {
|
||||
const aggregated = this.messageTransformer.aggregateMetadata(children);
|
||||
|
||||
// Collect all non-usage/performance metadata from all children
|
||||
const groupMetadata: Record<string, any> = {};
|
||||
const groupMetadata: Record<string, unknown> = {};
|
||||
children.forEach((child) => {
|
||||
if ((child as any).metadata) {
|
||||
Object.assign(groupMetadata, (child as any).metadata);
|
||||
@@ -936,7 +936,7 @@ export class FlatListBuilder {
|
||||
const msgPerformance = message.performance || metaPerformance;
|
||||
|
||||
// Extract non-usage/performance metadata fields
|
||||
const otherMetadata: Record<string, any> = {};
|
||||
const otherMetadata: Record<string, unknown> = {};
|
||||
if (message.metadata) {
|
||||
const usagePerformanceFields = new Set([
|
||||
'acceptedPredictionTokens',
|
||||
|
||||
@@ -407,10 +407,10 @@ export class AgentModel {
|
||||
|
||||
// First process the params field: undefined means delete, null means disable flag
|
||||
const existingParams = agent.params ?? {};
|
||||
const updatedParams: Record<string, any> = { ...existingParams };
|
||||
const updatedParams: Record<string, unknown> = { ...existingParams };
|
||||
|
||||
if (data.params) {
|
||||
const incomingParams = data.params as Record<string, any>;
|
||||
const incomingParams = data.params as Record<string, unknown>;
|
||||
Object.keys(incomingParams).forEach((key) => {
|
||||
const incomingValue = incomingParams[key];
|
||||
|
||||
@@ -435,7 +435,7 @@ export class AgentModel {
|
||||
|
||||
// Final cleanup: ensure no undefined or null values enter the database
|
||||
if (mergedValue.params) {
|
||||
const params = mergedValue.params as Record<string, any>;
|
||||
const params = mergedValue.params as Record<string, unknown>;
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key];
|
||||
|
||||
@@ -1361,7 +1361,7 @@ export class MessageModel {
|
||||
}
|
||||
|
||||
// 2. Handle metadata merge if provided
|
||||
let mergedMetadata: Record<string, any> | undefined;
|
||||
let mergedMetadata: Record<string, unknown> | undefined;
|
||||
if (metadata) {
|
||||
const [existingMessage] = await trx
|
||||
.select({ metadata: messages.metadata })
|
||||
@@ -1389,7 +1389,7 @@ export class MessageModel {
|
||||
}
|
||||
};
|
||||
|
||||
updateMetadata = async (id: string, metadata: Record<string, any>) => {
|
||||
updateMetadata = async (id: string, metadata: Record<string, unknown>) => {
|
||||
const item = await this.db.query.messages.findFirst({
|
||||
where: and(eq(messages.id, id), eq(messages.userId, this.userId)),
|
||||
});
|
||||
@@ -1402,7 +1402,7 @@ export class MessageModel {
|
||||
.where(and(eq(messages.userId, this.userId), eq(messages.id, id)));
|
||||
};
|
||||
|
||||
updatePluginState = async (id: string, state: Record<string, any>): Promise<void> => {
|
||||
updatePluginState = async (id: string, state: Record<string, unknown>): Promise<void> => {
|
||||
const item = await this.db.query.messagePlugins.findFirst({
|
||||
where: eq(messagePlugins.id, id),
|
||||
});
|
||||
@@ -1431,9 +1431,9 @@ export class MessageModel {
|
||||
id: string,
|
||||
params: {
|
||||
content?: string;
|
||||
metadata?: Record<string, any>;
|
||||
pluginError?: any;
|
||||
pluginState?: Record<string, any>;
|
||||
metadata?: Record<string, unknown>;
|
||||
pluginError?: unknown;
|
||||
pluginState?: Record<string, unknown>;
|
||||
},
|
||||
): Promise<{ success: boolean }> => {
|
||||
const { content, metadata, pluginState, pluginError } = params;
|
||||
@@ -1442,7 +1442,7 @@ export class MessageModel {
|
||||
await this.db.transaction(async (trx) => {
|
||||
// Update messages table (content, metadata)
|
||||
if (content !== undefined || metadata !== undefined) {
|
||||
const messageUpdateData: Record<string, any> = {};
|
||||
const messageUpdateData: Record<string, unknown> = {};
|
||||
|
||||
if (content !== undefined) {
|
||||
messageUpdateData.content = content;
|
||||
@@ -1471,7 +1471,7 @@ export class MessageModel {
|
||||
});
|
||||
|
||||
if (pluginItem) {
|
||||
const pluginUpdateData: Record<string, any> = {};
|
||||
const pluginUpdateData: Record<string, unknown> = {};
|
||||
|
||||
if (pluginState !== undefined) {
|
||||
pluginUpdateData.state = merge(pluginItem.state || {}, pluginState);
|
||||
|
||||
@@ -507,10 +507,10 @@ export class SessionModel {
|
||||
|
||||
// First process the params field: undefined means delete, null means disable flag
|
||||
const existingParams = session.agent.params ?? {};
|
||||
const updatedParams: Record<string, any> = { ...existingParams };
|
||||
const updatedParams: Record<string, unknown> = { ...existingParams };
|
||||
|
||||
if (data.params) {
|
||||
const incomingParams = data.params as Record<string, any>;
|
||||
const incomingParams = data.params as Record<string, unknown>;
|
||||
Object.keys(incomingParams).forEach((key) => {
|
||||
const incomingValue = incomingParams[key];
|
||||
|
||||
@@ -535,7 +535,7 @@ export class SessionModel {
|
||||
|
||||
// Final cleanup: ensure no undefined or null values enter the database
|
||||
if (mergedValue.params) {
|
||||
const params = mergedValue.params as Record<string, any>;
|
||||
const params = mergedValue.params as Record<string, unknown>;
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === undefined) {
|
||||
delete params[key];
|
||||
|
||||
@@ -11,11 +11,11 @@ export interface KnowledgeItem {
|
||||
chunkTaskId?: string | null;
|
||||
content?: string | null;
|
||||
createdAt: Date;
|
||||
editorData?: Record<string, any> | null;
|
||||
editorData?: Record<string, unknown> | null;
|
||||
embeddingTaskId?: string | null;
|
||||
fileType: string;
|
||||
id: string;
|
||||
metadata?: Record<string, any> | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
name: string;
|
||||
size: number;
|
||||
slug?: string | null;
|
||||
|
||||
@@ -20,16 +20,16 @@ interface PreparedMessage {
|
||||
agentId: string;
|
||||
content: string;
|
||||
createdAt: Date;
|
||||
error?: Record<string, any> | null;
|
||||
error?: unknown | null;
|
||||
id: string;
|
||||
metadata?: Record<string, any> | null;
|
||||
metadata?: unknown | null;
|
||||
model?: string | null;
|
||||
parentId: string | null;
|
||||
provider?: string | null;
|
||||
reasoning?: Record<string, any> | null;
|
||||
reasoning?: unknown | null;
|
||||
role: string;
|
||||
search?: Record<string, any> | null;
|
||||
tools?: Record<string, any>[] | null;
|
||||
search?: unknown | null;
|
||||
tools?: unknown[] | null;
|
||||
topicId: string;
|
||||
traceId?: string | null;
|
||||
updatedAt: Date;
|
||||
@@ -39,10 +39,10 @@ interface PreparedMessage {
|
||||
interface PreparedMessagePlugin {
|
||||
apiName?: string | null;
|
||||
arguments?: string | null;
|
||||
error?: Record<string, any> | null;
|
||||
error?: unknown | null;
|
||||
id: string;
|
||||
identifier?: string | null;
|
||||
state?: Record<string, any> | null;
|
||||
state?: unknown | null;
|
||||
toolCallId?: string | null;
|
||||
type?: string | null;
|
||||
userId: string;
|
||||
@@ -208,7 +208,14 @@ export class TopicImporterRepo {
|
||||
|
||||
// If message has plugin data (tool messages), prepare plugin record
|
||||
if (msg.plugin || msg.pluginState || msg.pluginError || msg.tool_call_id) {
|
||||
const plugin = msg.plugin as Record<string, any> | undefined;
|
||||
const plugin = msg.plugin as
|
||||
| {
|
||||
apiName?: string;
|
||||
arguments?: string;
|
||||
identifier?: string;
|
||||
type?: string;
|
||||
}
|
||||
| undefined;
|
||||
preparedPlugins.push({
|
||||
apiName: plugin?.apiName || null,
|
||||
arguments: plugin?.arguments || null,
|
||||
|
||||
@@ -111,7 +111,7 @@ export const agentDocuments = pgTable(
|
||||
* Canonical behavior config for context/retrieval policy.
|
||||
* Keep extensible fields here.
|
||||
*/
|
||||
policy: jsonb('policy').$type<Record<string, any>>(),
|
||||
policy: jsonb('policy').$type<Record<string, unknown>>(),
|
||||
/**
|
||||
* Indexed projection of policy.context.position for fast filtering/sorting.
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ export const agentSkills = pgTable(
|
||||
|
||||
// Content and editor state
|
||||
content: text('content'),
|
||||
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
||||
editorData: jsonb('editor_data').$type<Record<string, unknown>>(),
|
||||
|
||||
// Resource mapping: Record<VirtualPath, SkillResourceMeta>
|
||||
resources: jsonb('resources').$type<Record<string, SkillResourceMeta>>().default({}),
|
||||
|
||||
@@ -33,7 +33,7 @@ export const chatGroups = pgTable(
|
||||
backgroundColor: text('background_color'),
|
||||
marketIdentifier: text('market_identifier'),
|
||||
content: text('content'),
|
||||
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
||||
editorData: jsonb('editor_data').$type<Record<string, unknown>>(),
|
||||
|
||||
config: jsonb('config').$type<ChatGroupConfig>(),
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export const documents = pgTable(
|
||||
totalLineCount: integer('total_line_count').notNull(),
|
||||
|
||||
// Metadata
|
||||
metadata: jsonb('metadata').$type<Record<string, any>>(),
|
||||
metadata: jsonb('metadata').$type<Record<string, unknown>>(),
|
||||
|
||||
// Page/chunk data
|
||||
pages: jsonb('pages').$type<LobeDocumentPage[]>(),
|
||||
@@ -95,7 +95,7 @@ export const documents = pgTable(
|
||||
.notNull(),
|
||||
clientId: text('client_id'),
|
||||
|
||||
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
||||
editorData: jsonb('editor_data').$type<Record<string, unknown>>(),
|
||||
|
||||
slug: varchar('slug', { length: 255 }).$defaultFn(() => randomSlug(3)),
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface LocalFileItem {
|
||||
lastAccessTime: Date;
|
||||
// Spotlight specific metadata
|
||||
metadata?: {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
modifiedTime: Date;
|
||||
name: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface ShortcutConfig {
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
export type ShortcutActionType = Record<string, any>;
|
||||
export type ShortcutActionType = Record<string, unknown>;
|
||||
|
||||
export interface ShortcutUpdateResult {
|
||||
errorType?:
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Papa from 'papaparse';
|
||||
import type { ParseOptions, ParseResult } from '../types';
|
||||
|
||||
export function parseCSV(content: string, options?: ParseOptions): ParseResult {
|
||||
const result = Papa.parse<Record<string, any>>(content, {
|
||||
const result = Papa.parse<Record<string, unknown>>(content, {
|
||||
delimiter: options?.csvDelimiter,
|
||||
dynamicTyping: true,
|
||||
header: true,
|
||||
|
||||
@@ -2,10 +2,7 @@ import * as XLSX from 'xlsx';
|
||||
|
||||
import type { ParseOptions, ParseResult } from '../types';
|
||||
|
||||
export function parseXLSX(
|
||||
data: Buffer | Uint8Array,
|
||||
options?: ParseOptions,
|
||||
): ParseResult {
|
||||
export function parseXLSX(data: Buffer | Uint8Array, options?: ParseOptions): ParseResult {
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
|
||||
// Select sheet
|
||||
@@ -23,7 +20,7 @@ export function parseXLSX(
|
||||
return { format: 'xlsx', headers: [], metadata: { sheetName }, rows: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
const allRows = XLSX.utils.sheet_to_json<Record<string, any>>(worksheet, {
|
||||
const allRows = XLSX.utils.sheet_to_json<Record<string, unknown>>(worksheet, {
|
||||
defval: '',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
@@ -14,6 +14,6 @@ export interface ParseResult {
|
||||
metadata?: {
|
||||
sheetName?: string;
|
||||
};
|
||||
rows: Record<string, any>[];
|
||||
rows: Record<string, unknown>[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const log = debug('file-loaders:excel');
|
||||
* Converts sheet data (array of objects) to a Markdown table string.
|
||||
* Handles empty sheets and escapes pipe characters.
|
||||
*/
|
||||
function sheetToMarkdownTable(jsonData: Record<string, any>[]): string {
|
||||
function sheetToMarkdownTable(jsonData: Record<string, unknown>[]): string {
|
||||
log('Converting sheet data to Markdown table, rows:', jsonData?.length || 0);
|
||||
if (!jsonData || jsonData.length === 0) {
|
||||
log('Sheet is empty, returning placeholder message');
|
||||
@@ -71,7 +71,7 @@ export class ExcelLoader implements FileLoaderInterface {
|
||||
log(`Processing sheet: ${sheetName}`);
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
// Use sheet_to_json to get array of objects for our custom markdown function
|
||||
const jsonData = xlsx.utils.sheet_to_json<Record<string, any>>(worksheet, {
|
||||
const jsonData = xlsx.utils.sheet_to_json<Record<string, unknown>>(worksheet, {
|
||||
// Get formatted strings, not raw values
|
||||
defval: '',
|
||||
raw: false, // Use empty string for blank cells
|
||||
|
||||
@@ -33,7 +33,7 @@ export interface FileDocument {
|
||||
/**
|
||||
* Allows adding other file-level metadata.
|
||||
*/
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
/**
|
||||
* Document author (if available).
|
||||
*/
|
||||
@@ -98,7 +98,7 @@ export interface DocumentPage {
|
||||
/**
|
||||
* Allows adding other page/chunk-specific metadata.
|
||||
*/
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
|
||||
/**
|
||||
* If the original file unit is further divided into chunks, this is the index of the current chunk.
|
||||
@@ -189,7 +189,7 @@ export interface FileLoaderInterface {
|
||||
*/
|
||||
aggregateContent: (pages: DocumentPage[]) => Promise<string>;
|
||||
|
||||
attachDocumentMetadata?: (filePath: string) => Promise<Record<string, any>>;
|
||||
attachDocumentMetadata?: (filePath: string) => Promise<Record<string, unknown>>;
|
||||
|
||||
/**
|
||||
* Loads file content based on the file path and splits it into logical pages/chunks.
|
||||
|
||||
@@ -4,7 +4,14 @@ import { join } from 'node:path';
|
||||
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
||||
|
||||
interface TracePayload {
|
||||
agentCalls?: Record<string, any>;
|
||||
agentCalls?: Record<
|
||||
string,
|
||||
{
|
||||
request?: {
|
||||
messages?: Array<{ content?: string }>;
|
||||
};
|
||||
}
|
||||
>;
|
||||
contexts?: {
|
||||
trimmed?: {
|
||||
retrievedContexts?: string[];
|
||||
|
||||
@@ -60,7 +60,7 @@ export abstract class LobeOpenAICompatibleRuntime {
|
||||
abstract generateObject(
|
||||
payload: GenerateObjectPayload,
|
||||
options?: GenerateObjectOptions,
|
||||
): Promise<Record<string, any>>;
|
||||
): Promise<Record<string, unknown>>;
|
||||
|
||||
abstract models(): Promise<AIBaseModelCard[]>;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import type { ApiType, RuntimeClass } from './apiTypes';
|
||||
|
||||
const log = debug('lobe-model-runtime:router-runtime');
|
||||
|
||||
interface ProviderIniOptions extends Record<string, any> {
|
||||
interface ProviderIniOptions extends Record<string, unknown> {
|
||||
accessKeyId?: string;
|
||||
accessKeySecret?: string;
|
||||
apiKey?: string;
|
||||
@@ -74,12 +74,13 @@ interface RouterInstance {
|
||||
runtime?: RuntimeClass;
|
||||
}
|
||||
|
||||
type ConstructorOptions<T extends Record<string, any> = any> = ClientOptions & T;
|
||||
type ConstructorOptions<T extends Record<string, unknown> = Record<string, unknown>> =
|
||||
ClientOptions & T;
|
||||
|
||||
type Routers =
|
||||
| RouterInstance[]
|
||||
| ((
|
||||
options: ClientOptions & Record<string, any>,
|
||||
options: ClientOptions & Record<string, unknown>,
|
||||
runtimeContext: {
|
||||
model?: string;
|
||||
},
|
||||
@@ -100,7 +101,9 @@ export interface RouteAttemptResult {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface CreateRouterRuntimeOptions<T extends Record<string, any> = any> {
|
||||
export interface CreateRouterRuntimeOptions<
|
||||
T extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
apiKey?: string;
|
||||
chatCompletion?: {
|
||||
excludeUsage?: boolean;
|
||||
@@ -139,7 +142,7 @@ export interface CreateRouterRuntimeOptions<T extends Record<string, any> = any>
|
||||
chatCompletion: () => boolean;
|
||||
responses?: () => boolean;
|
||||
};
|
||||
defaultHeaders?: Record<string, any>;
|
||||
defaultHeaders?: Record<string, unknown>;
|
||||
errorType?: {
|
||||
bizError: ILobeAgentRuntimeErrorType;
|
||||
invalidAPIKey: ILobeAgentRuntimeErrorType;
|
||||
@@ -172,12 +175,12 @@ export const createRouterRuntime = ({
|
||||
...params
|
||||
}: CreateRouterRuntimeOptions) => {
|
||||
return class UniformRuntime implements LobeRuntimeAI {
|
||||
public _options: ClientOptions & Record<string, any>;
|
||||
public _options: ClientOptions & Record<string, unknown>;
|
||||
private _routers: Routers;
|
||||
private _params: any;
|
||||
private _id: string;
|
||||
|
||||
constructor(options: ClientOptions & Record<string, any> = {}) {
|
||||
constructor(options: ClientOptions & Record<string, unknown> = {}) {
|
||||
this._options = {
|
||||
...options,
|
||||
apiKey: options.apiKey?.trim() || DEFAULT_API_KEY,
|
||||
@@ -262,7 +265,7 @@ export const createRouterRuntime = ({
|
||||
*/
|
||||
if (resolvedApiType === 'vertexai') {
|
||||
const { apiKey, googleAuthOptions, project, location, ...restOptions } = finalOptions;
|
||||
const credentials = safeParseJSON<Record<string, any>>(apiKey);
|
||||
const credentials = safeParseJSON<Record<string, unknown>>(apiKey);
|
||||
const vertexOptions: GoogleGenAIOptions = {
|
||||
...(restOptions as GoogleGenAIOptions),
|
||||
vertexai: true,
|
||||
@@ -328,6 +331,8 @@ export const createRouterRuntime = ({
|
||||
runtime,
|
||||
} = await this.createRuntimeFromOption(matchedRouter, optionItem);
|
||||
|
||||
const userId = typeof this._options.userId === 'string' ? this._options.userId : undefined;
|
||||
|
||||
try {
|
||||
const result = await requestHandler(runtime);
|
||||
|
||||
@@ -363,7 +368,7 @@ export const createRouterRuntime = ({
|
||||
remark,
|
||||
routerId: matchedRouter.id,
|
||||
success: true,
|
||||
userId: this._options.userId,
|
||||
userId,
|
||||
})
|
||||
.catch((e) => {
|
||||
log('onRouteAttempt callback error: %O', e);
|
||||
@@ -386,7 +391,7 @@ export const createRouterRuntime = ({
|
||||
remark,
|
||||
routerId: matchedRouter.id,
|
||||
success: false,
|
||||
userId: this._options.userId,
|
||||
userId,
|
||||
})
|
||||
.catch((e) => {
|
||||
log('onRouteAttempt callback error: %O', e);
|
||||
|
||||
@@ -37,17 +37,20 @@ import { handleAnthropicError } from './handleAnthropicError';
|
||||
import { resolveCacheTTL } from './resolveCacheTTL';
|
||||
import { resolveMaxTokens } from './resolveMaxTokens';
|
||||
|
||||
type ConstructorOptions<T extends Record<string, any> = any> = ClientOptions & T;
|
||||
type ConstructorOptions<T extends Record<string, unknown> = Record<string, unknown>> =
|
||||
ClientOptions & T;
|
||||
|
||||
type AnthropicTools = Anthropic.Tool | Anthropic.WebSearchTool20250305;
|
||||
|
||||
export const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com';
|
||||
|
||||
export interface CustomClientOptions<T extends Record<string, any> = any> {
|
||||
export interface CustomClientOptions<T extends Record<string, unknown> = Record<string, unknown>> {
|
||||
createClient?: (options: ConstructorOptions<T>) => Anthropic;
|
||||
}
|
||||
|
||||
export interface AnthropicCompatibleFactoryOptions<T extends Record<string, any> = any> {
|
||||
export interface AnthropicCompatibleFactoryOptions<
|
||||
T extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
chatCompletion?: {
|
||||
@@ -100,7 +103,9 @@ export interface AnthropicCompatibleFactoryOptions<T extends Record<string, any>
|
||||
provider: string;
|
||||
}
|
||||
|
||||
export interface AnthropicCompatibleParamsInput<T extends Record<string, any> = any> extends Omit<
|
||||
export interface AnthropicCompatibleParamsInput<
|
||||
T extends Record<string, any> = Record<string, never>,
|
||||
> extends Omit<
|
||||
AnthropicCompatibleFactoryOptions<T>,
|
||||
'chatCompletion' | 'customClient' | 'generateObject' | 'models'
|
||||
> {
|
||||
@@ -242,7 +247,7 @@ export const resolveDefaultAnthropicPricingOptions = (
|
||||
/**
|
||||
* Create Anthropic SDK client with optional beta headers.
|
||||
*/
|
||||
export const createDefaultAnthropicClient = <T extends Record<string, any> = any>(
|
||||
export const createDefaultAnthropicClient = <T extends Record<string, any> = Record<string, never>>(
|
||||
options: ConstructorOptions<T>,
|
||||
) => {
|
||||
const betaHeaders = process.env.ANTHROPIC_BETA_HEADERS;
|
||||
@@ -258,7 +263,7 @@ export const createDefaultAnthropicClient = <T extends Record<string, any> = any
|
||||
/**
|
||||
* Default Anthropic error handler with desensitized endpoint.
|
||||
*/
|
||||
export const handleDefaultAnthropicError = <T extends Record<string, any> = any>(
|
||||
export const handleDefaultAnthropicError = <T extends Record<string, any> = Record<string, never>>(
|
||||
error: any,
|
||||
options: ConstructorOptions<T>,
|
||||
): Omit<ChatCompletionErrorPayload, 'provider'> => {
|
||||
@@ -363,7 +368,9 @@ export const createDefaultAnthropicModels = async ({
|
||||
/**
|
||||
* Build provider params by merging overrides with Anthropic defaults.
|
||||
*/
|
||||
export const createAnthropicCompatibleParams = <T extends Record<string, any> = any>(
|
||||
export const createAnthropicCompatibleParams = <
|
||||
T extends Record<string, any> = Record<string, never>,
|
||||
>(
|
||||
options: AnthropicCompatibleParamsInput<T>,
|
||||
): AnthropicCompatibleFactoryOptions<T> => {
|
||||
const {
|
||||
@@ -390,7 +397,9 @@ export const createAnthropicCompatibleParams = <T extends Record<string, any> =
|
||||
} as AnthropicCompatibleFactoryOptions<T>;
|
||||
};
|
||||
|
||||
export const createAnthropicCompatibleRuntime = <T extends Record<string, any> = any>({
|
||||
export const createAnthropicCompatibleRuntime = <
|
||||
T extends Record<string, any> = Record<string, never>,
|
||||
>({
|
||||
provider,
|
||||
baseURL: DEFAULT_BASE_URL = DEFAULT_ANTHROPIC_BASE_URL,
|
||||
apiKey: DEFAULT_API_KEY,
|
||||
@@ -416,7 +425,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
|
||||
baseURL!: string;
|
||||
protected _options: ConstructorOptions<T>;
|
||||
|
||||
constructor(options: ClientOptions & Record<string, any> = {}) {
|
||||
constructor(options: ClientOptions & Record<string, unknown> = {}) {
|
||||
const apiKey = typeof options.apiKey === 'string' ? options.apiKey.trim() : options.apiKey;
|
||||
const baseURL =
|
||||
typeof options.baseURL === 'string' ? options.baseURL.trim() : options.baseURL;
|
||||
@@ -449,7 +458,7 @@ export const createAnthropicCompatibleRuntime = <T extends Record<string, any> =
|
||||
}
|
||||
|
||||
this.baseURL = baseURL || this.client.baseURL;
|
||||
this.id = options.id || provider;
|
||||
this.id = typeof options.id === 'string' ? options.id : provider;
|
||||
this.logPrefix = `lobe-model-runtime:${this.id}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type Anthropic from '@anthropic-ai/sdk';
|
||||
import { imageUrlToBase64 } from '@lobechat/utils';
|
||||
import type OpenAI from 'openai';
|
||||
|
||||
import type { OpenAIChatMessage, UserMessageContentPart } from '../../types';
|
||||
import type { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '../../types';
|
||||
import { parseDataUri } from '../../utils/uriParser';
|
||||
|
||||
const ANTHROPIC_SUPPORTED_IMAGE_TYPES = new Set([
|
||||
@@ -341,7 +340,7 @@ export const buildAnthropicMessages = async (
|
||||
};
|
||||
|
||||
export const buildAnthropicTools = (
|
||||
tools?: OpenAI.ChatCompletionTool[],
|
||||
tools?: ChatCompletionTool[],
|
||||
options: { enabledContextCaching?: boolean } = {},
|
||||
) => {
|
||||
if (!tools) return;
|
||||
|
||||
@@ -259,7 +259,7 @@ const UNSUPPORTED_SCHEMA_KEYS = new Set(['examples', 'default']);
|
||||
* Google's API doesn't support certain JSON Schema keywords like 'const'
|
||||
* This function recursively processes the schema and converts unsupported keywords
|
||||
*/
|
||||
const sanitizeSchemaForGoogle = (schema: Record<string, any>): Record<string, any> => {
|
||||
const sanitizeSchemaForGoogle = (schema: unknown): unknown => {
|
||||
if (!schema || typeof schema !== 'object') return schema;
|
||||
|
||||
// Handle arrays
|
||||
@@ -267,7 +267,7 @@ const sanitizeSchemaForGoogle = (schema: Record<string, any>): Record<string, an
|
||||
return schema.map((item) => sanitizeSchemaForGoogle(item));
|
||||
}
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(schema)) {
|
||||
// Strip unsupported JSON Schema keywords (e.g. examples, default, $schema)
|
||||
@@ -305,23 +305,34 @@ const sanitizeSchemaForGoogle = (schema: Record<string, any>): Record<string, an
|
||||
*/
|
||||
export const buildGoogleTool = (tool: ChatCompletionTool): FunctionDeclaration => {
|
||||
const functionDeclaration = tool.function;
|
||||
const parameters = functionDeclaration.parameters;
|
||||
const parameters =
|
||||
functionDeclaration.parameters && typeof functionDeclaration.parameters === 'object'
|
||||
? (functionDeclaration.parameters as Record<string, unknown>)
|
||||
: undefined;
|
||||
// refs: https://github.com/lobehub/lobe-chat/pull/5002
|
||||
const rawProperties =
|
||||
parameters?.properties && Object.keys(parameters.properties).length > 0
|
||||
parameters?.properties &&
|
||||
typeof parameters.properties === 'object' &&
|
||||
!Array.isArray(parameters.properties) &&
|
||||
Object.keys(parameters.properties).length > 0
|
||||
? parameters.properties
|
||||
: { dummy: { type: 'string' } }; // dummy property to avoid empty object
|
||||
|
||||
// Sanitize properties to remove unsupported JSON Schema keywords for Google
|
||||
const properties = sanitizeSchemaForGoogle(rawProperties);
|
||||
const properties = sanitizeSchemaForGoogle(rawProperties) as Record<string, unknown>;
|
||||
const description =
|
||||
typeof parameters?.description === 'string' ? parameters.description : undefined;
|
||||
const required = Array.isArray(parameters?.required)
|
||||
? parameters.required.filter((item): item is string => typeof item === 'string')
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
description: functionDeclaration.description,
|
||||
name: functionDeclaration.name,
|
||||
parameters: {
|
||||
description: parameters?.description,
|
||||
properties,
|
||||
required: parameters?.required,
|
||||
description,
|
||||
properties: properties as any,
|
||||
required,
|
||||
type: SchemaType.OBJECT,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ async function generateByImageMode(
|
||||
['imageUrls', 'image'],
|
||||
['imageUrl', 'image'],
|
||||
]);
|
||||
const userInput: Record<string, any> = Object.fromEntries(
|
||||
const userInput: Record<string, unknown> = Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [
|
||||
paramsMap.get(key as RuntimeImageGenParamsValue) ?? key,
|
||||
value,
|
||||
@@ -46,15 +46,20 @@ async function generateByImageMode(
|
||||
// If there are imageUrls parameters, convert them to File objects
|
||||
if (isImageEdit) {
|
||||
try {
|
||||
const imageInput = userInput.image;
|
||||
const imageUrls = Array.isArray(imageInput)
|
||||
? imageInput.filter((url): url is string => typeof url === 'string')
|
||||
: [];
|
||||
|
||||
// Convert all image URLs to File objects
|
||||
const imageFiles = await Promise.all(
|
||||
userInput.image.map((url: string) => convertImageUrlToFile(url)),
|
||||
imageUrls.map((url: string) => convertImageUrlToFile(url)),
|
||||
);
|
||||
|
||||
// According to official docs, if there are multiple images, pass an array; if only one, pass a single File
|
||||
userInput.image = imageFiles.length === 1 ? imageFiles[0] : imageFiles;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to convert image URLs to File objects: ${error}`);
|
||||
throw new Error(`Failed to convert image URLs to File objects: ${error}`, { cause: error });
|
||||
}
|
||||
} else {
|
||||
delete userInput.image;
|
||||
@@ -180,7 +185,7 @@ async function generateByChatModel(
|
||||
});
|
||||
log('Successfully processed image URL for chat input');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to process image URL: ${error}`);
|
||||
throw new Error(`Failed to process image URL: ${error}`, { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import { desensitizeUrl } from '../../utils/desensitizeUrl';
|
||||
import { getModelPropertyWithFallback } from '../../utils/getFallbackModelProperty';
|
||||
import { getModelPricing } from '../../utils/getModelPricing';
|
||||
import { handleOpenAIError } from '../../utils/handleOpenAIError';
|
||||
import { mergeHeaders, toHeadersInit } from '../../utils/headers';
|
||||
import { isExceededContextWindowError } from '../../utils/isExceededContextWindowError';
|
||||
import { isQuotaLimitError } from '../../utils/isQuotaLimitError';
|
||||
import { postProcessModelList } from '../../utils/postProcessModelList';
|
||||
@@ -67,7 +68,8 @@ export const CHAT_MODELS_BLOCK_LIST = [
|
||||
'dall-e',
|
||||
];
|
||||
|
||||
type ConstructorOptions<T extends Record<string, any> = any> = ClientOptions & T;
|
||||
type ConstructorOptions<T extends Record<string, unknown> = Record<string, unknown>> =
|
||||
ClientOptions & T;
|
||||
export type CreateImageOptions = Omit<ClientOptions, 'apiKey'> & {
|
||||
apiKey: string;
|
||||
provider: string;
|
||||
@@ -78,7 +80,7 @@ export type CreateVideoOptions = Omit<ClientOptions, 'apiKey'> & {
|
||||
provider: string;
|
||||
};
|
||||
|
||||
export interface CustomClientOptions<T extends Record<string, any> = any> {
|
||||
export interface CustomClientOptions<T extends Record<string, unknown> = Record<string, unknown>> {
|
||||
createChatCompletionStream?: (
|
||||
client: any,
|
||||
payload: ChatStreamPayload,
|
||||
@@ -87,7 +89,9 @@ export interface CustomClientOptions<T extends Record<string, any> = any> {
|
||||
createClient?: (options: ConstructorOptions<T>) => any;
|
||||
}
|
||||
|
||||
export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> = any> {
|
||||
export interface OpenAICompatibleFactoryOptions<
|
||||
T extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
chatCompletion?: {
|
||||
@@ -179,7 +183,9 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
|
||||
};
|
||||
}
|
||||
|
||||
export const createOpenAICompatibleRuntime = <T extends Record<string, any> = any>({
|
||||
export const createOpenAICompatibleRuntime = <
|
||||
T extends Record<string, unknown> = Record<string, unknown>,
|
||||
>({
|
||||
provider,
|
||||
baseURL: DEFAULT_BASE_URL,
|
||||
apiKey: DEFAULT_API_KEY,
|
||||
@@ -209,7 +215,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
baseURL!: string;
|
||||
protected _options: ConstructorOptions<T>;
|
||||
|
||||
constructor(options: ClientOptions & Record<string, any> = {}) {
|
||||
constructor(options: ClientOptions & Record<string, unknown> = {}) {
|
||||
const _options = {
|
||||
...options,
|
||||
apiKey: options.apiKey?.trim() || DEFAULT_API_KEY,
|
||||
@@ -231,7 +237,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
|
||||
this.baseURL = baseURL || this.client.baseURL;
|
||||
|
||||
this.id = options.id || provider;
|
||||
this.id = typeof options.id === 'string' ? options.id : provider;
|
||||
this.logPrefix = `lobe-model-runtime:${this.id}`;
|
||||
}
|
||||
|
||||
@@ -396,8 +402,8 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
|
||||
if (targetBaseURL !== this.baseURL) {
|
||||
const restOptions = {
|
||||
...(this._options as ConstructorOptions<T> & Record<string, any>),
|
||||
} as Record<string, any>;
|
||||
...(this._options as ConstructorOptions<T> & Record<string, unknown>),
|
||||
} as Record<string, unknown>;
|
||||
const optionApiKey = restOptions.apiKey;
|
||||
delete restOptions.apiKey;
|
||||
delete restOptions.baseURL;
|
||||
@@ -415,7 +421,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
baseURL: targetBaseURL,
|
||||
...constructorOptions,
|
||||
...restOptions,
|
||||
} as ConstructorOptions<T> & Record<string, any>;
|
||||
} as ConstructorOptions<T> & Record<string, unknown>;
|
||||
|
||||
this._options = nextOptions;
|
||||
|
||||
@@ -486,9 +492,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
|
||||
response = (await this.client.chat.completions.create(finalPayload, {
|
||||
// https://github.com/lobehub/lobe-chat/pull/318
|
||||
headers: { Accept: '*/*', ...options?.requestHeaders },
|
||||
headers: mergeHeaders({ Accept: '*/*' }, options?.requestHeaders),
|
||||
signal: options?.signal,
|
||||
})) as unknown as Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
|
||||
} as any)) as unknown as Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
|
||||
}
|
||||
|
||||
if (postPayload.stream) {
|
||||
@@ -699,10 +705,10 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
messages,
|
||||
model,
|
||||
tool_choice: { function: { name: tool.function.name }, type: 'function' },
|
||||
tools: [tool],
|
||||
tools: [tool as unknown as OpenAI.ChatCompletionTool],
|
||||
user: options?.user,
|
||||
},
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage) {
|
||||
@@ -755,7 +761,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
text: { format: { strict: true, type: 'json_schema', ...processedSchema } },
|
||||
user: options?.user,
|
||||
},
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage) {
|
||||
@@ -780,10 +786,13 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
{
|
||||
messages,
|
||||
model,
|
||||
response_format: { json_schema: processedSchema, type: 'json_schema' },
|
||||
response_format: {
|
||||
json_schema: processedSchema as any,
|
||||
type: 'json_schema',
|
||||
} as any,
|
||||
user: options?.user,
|
||||
},
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
if (res.usage) {
|
||||
await options?.onUsage?.(convertOpenAIUsage(res.usage, usagePayload));
|
||||
@@ -830,7 +839,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
try {
|
||||
const res = await this.client.embeddings.create(
|
||||
{ ...payload, encoding_format: 'float', user: options?.user },
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage && options?.onUsage) {
|
||||
@@ -860,10 +869,13 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
);
|
||||
|
||||
try {
|
||||
const mp3 = await this.client.audio.speech.create(payload as any, {
|
||||
headers: options?.headers,
|
||||
signal: options?.signal,
|
||||
});
|
||||
const mp3 = await this.client.audio.speech.create(
|
||||
payload as any,
|
||||
{
|
||||
headers: toHeadersInit(options?.headers),
|
||||
signal: options?.signal,
|
||||
} as any,
|
||||
);
|
||||
const buffer = await mp3.arrayBuffer();
|
||||
log('generated audio with size: %d bytes', buffer.byteLength);
|
||||
return buffer;
|
||||
@@ -1061,9 +1073,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
log('sending responses.create request');
|
||||
|
||||
const response = await this.client.responses.create(postPayload, {
|
||||
headers: options?.requestHeaders,
|
||||
headers: toHeadersInit(options?.requestHeaders),
|
||||
signal: options?.signal,
|
||||
});
|
||||
} as any);
|
||||
|
||||
const streamOptions: OpenAIStreamOptions = {
|
||||
bizErrorTypeTransformer: chatCompletion?.handleStreamBizErrorType,
|
||||
@@ -1170,7 +1182,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
tools: tools!.map((tool) => this.convertChatCompletionToolToResponseTool(tool)),
|
||||
user: options?.user,
|
||||
},
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage) {
|
||||
@@ -1207,10 +1219,10 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
||||
messages: msgs,
|
||||
model,
|
||||
tool_choice: 'required',
|
||||
tools,
|
||||
tools: tools as unknown as OpenAI.ChatCompletionTool[] | undefined,
|
||||
user: options?.user,
|
||||
},
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage) {
|
||||
|
||||
@@ -6,7 +6,7 @@ const log = debug('lobe-cost:computeImagePricing');
|
||||
|
||||
export interface ImageGenerationParams {
|
||||
// Other possible parameters for future extensions
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
quality?: 'standard' | 'hd';
|
||||
size?: string;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const computeImageCost = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let pricePerImageInUSD = 0;
|
||||
let pricePerImageInUSD: number;
|
||||
let lookupKey: string | undefined;
|
||||
|
||||
switch (imageGenUnit.strategy) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { FixedPricingUnit, LookupPricingUnit, Pricing } from 'model-bank';
|
||||
const log = debug('lobe-cost:computeVideoCost');
|
||||
|
||||
export interface VideoGenerationParams {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
generateAudio?: boolean;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export const computeVideoCost = (
|
||||
}
|
||||
|
||||
const currency = pricing.currency || 'USD';
|
||||
let pricePerMillionTokens = 0;
|
||||
let pricePerMillionTokens: number;
|
||||
let lookupKey: string | undefined;
|
||||
|
||||
switch (videoGenUnit.strategy) {
|
||||
@@ -76,7 +76,9 @@ export const computeVideoCost = (
|
||||
}
|
||||
|
||||
pricePerMillionTokens = lookupPrice;
|
||||
log(`Lookup pricing for key "${lookupKey}": ${pricePerMillionTokens} per million tokens (${currency})`);
|
||||
log(
|
||||
`Lookup pricing for key "${lookupKey}": ${pricePerMillionTokens} per million tokens (${currency})`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import debug from 'debug';
|
||||
|
||||
import type { ChatMethodOptions } from '../types/chat';
|
||||
import { mergeHeaders } from '../utils/headers';
|
||||
|
||||
const log = debug('model-runtime:helpers:mergeChatMethodOptions');
|
||||
|
||||
@@ -105,21 +106,11 @@ export const mergeMultipleChatMethodOptions = (options: ChatMethodOptions[]): Ch
|
||||
},
|
||||
},
|
||||
};
|
||||
completionOptions.headers = options.reduce((acc, option) => {
|
||||
if (option)
|
||||
return {
|
||||
...acc,
|
||||
...option.headers,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
completionOptions.requestHeaders = options.reduce((acc, option) => {
|
||||
if (option)
|
||||
return {
|
||||
...acc,
|
||||
...option.requestHeaders,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
const headers = mergeHeaders(...options.map((option) => option?.headers));
|
||||
const requestHeaders = mergeHeaders(...options.map((option) => option?.requestHeaders));
|
||||
|
||||
completionOptions.headers = headers;
|
||||
completionOptions.requestHeaders = requestHeaders;
|
||||
|
||||
return completionOptions;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AgentRuntimeErrorType } from '../../types/error';
|
||||
import type { CreateImagePayload, CreateImageResponse } from '../../types/image';
|
||||
import { AgentRuntimeError } from '../../utils/createError';
|
||||
import { debugStream } from '../../utils/debugStream';
|
||||
import { toHeadersInit } from '../../utils/headers';
|
||||
import { StreamingResponse } from '../../utils/response';
|
||||
import { sanitizeError } from '../../utils/sanitizeError';
|
||||
|
||||
@@ -88,10 +89,16 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
: baseParams;
|
||||
|
||||
const response = enableStreaming
|
||||
? await this.client.chat.completions.create({ ...openaiParams, stream: true })
|
||||
: await this.client.chat.completions.create({ ...openaiParams, stream: false });
|
||||
? await this.client.chat.completions.create({
|
||||
...(openaiParams as any),
|
||||
stream: true,
|
||||
} as any)
|
||||
: await this.client.chat.completions.create({
|
||||
...(openaiParams as any),
|
||||
stream: false,
|
||||
} as any);
|
||||
if (enableStreaming) {
|
||||
const stream = response as Stream<OpenAI.ChatCompletionChunk>;
|
||||
const stream = response as unknown as Stream<OpenAI.ChatCompletionChunk>;
|
||||
const [prod, debug] = stream.tee();
|
||||
if (process.env.DEBUG_AZURE_CHAT_COMPLETION === '1') {
|
||||
debugStream(debug.toReadableStream()).catch(console.error);
|
||||
@@ -117,7 +124,7 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
try {
|
||||
const res = await this.client.embeddings.create(
|
||||
{ ...payload, encoding_format: 'float', user: options?.user },
|
||||
{ headers: options?.headers, signal: options?.signal },
|
||||
{ headers: toHeadersInit(options?.headers), signal: options?.signal } as any,
|
||||
);
|
||||
|
||||
if (res.usage && options?.onUsage) {
|
||||
@@ -146,7 +153,7 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
|
||||
try {
|
||||
// Clone params and remap imageUrls/imageUrl -> image
|
||||
const userInput: Record<string, any> = { ...params };
|
||||
const userInput: Record<string, unknown> = { ...params };
|
||||
|
||||
// Convert imageUrls to 'image' for edit API
|
||||
if (Array.isArray(userInput.imageUrls) && userInput.imageUrls.length > 0) {
|
||||
@@ -157,7 +164,7 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
}
|
||||
|
||||
// Backward compatibility: single imageUrl -> image
|
||||
if (userInput.imageUrl && !userInput.image) {
|
||||
if (typeof userInput.imageUrl === 'string' && !userInput.image) {
|
||||
userInput.image = await convertImageUrlToFile(userInput.imageUrl);
|
||||
}
|
||||
|
||||
@@ -251,7 +258,7 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
}
|
||||
|
||||
protected handleError(e: any, model?: string): never {
|
||||
let error = e as { [key: string]: any; code: string; message: string };
|
||||
let error = e as { [key: string]: unknown; code: string; message: string };
|
||||
|
||||
if (error.code) {
|
||||
switch (error.code) {
|
||||
@@ -261,10 +268,10 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
||||
}
|
||||
} else {
|
||||
error = {
|
||||
cause: error.cause,
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
} as any;
|
||||
cause: (e as any).cause,
|
||||
message: (e as any).message,
|
||||
name: (e as any).name,
|
||||
} as unknown as { [key: string]: unknown; code: string; message: string };
|
||||
}
|
||||
|
||||
const errorType = error.code
|
||||
|
||||
@@ -129,7 +129,7 @@ export class LobeAzureAI implements LobeRuntimeAI {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
let error = e as { [key: string]: any; code: string; message: string };
|
||||
let error = e as { [key: string]: unknown; code: string; message: string };
|
||||
|
||||
if (error.code) {
|
||||
switch (error.code) {
|
||||
@@ -139,10 +139,10 @@ export class LobeAzureAI implements LobeRuntimeAI {
|
||||
}
|
||||
} else {
|
||||
error = {
|
||||
cause: error.cause,
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
} as any;
|
||||
cause: (e as any).cause,
|
||||
message: (e as any).message,
|
||||
name: (e as any).name,
|
||||
} as unknown as { [key: string]: unknown; code: string; message: string };
|
||||
}
|
||||
|
||||
const errorType = error.code
|
||||
|
||||
@@ -21,9 +21,9 @@ export interface BflAsyncWebhookResponse {
|
||||
}
|
||||
|
||||
export interface BflResultResponse {
|
||||
details?: Record<string, any> | null;
|
||||
details?: Record<string, unknown> | null;
|
||||
id: string;
|
||||
preview?: Record<string, any> | null;
|
||||
preview?: Record<string, unknown> | null;
|
||||
progress?: number | null;
|
||||
result?: any;
|
||||
status: BflStatusResponse;
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { ChatMethodOptions, ChatStreamPayload } from '../../types';
|
||||
import { AgentRuntimeErrorType } from '../../types/error';
|
||||
import { AgentRuntimeError } from '../../utils/createError';
|
||||
import { debugStream } from '../../utils/debugStream';
|
||||
import { toHeadersInit } from '../../utils/headers';
|
||||
import { StreamingResponse } from '../../utils/response';
|
||||
|
||||
export interface CloudflareModelCard {
|
||||
@@ -61,14 +62,17 @@ export class LobeCloudflareAI implements LobeRuntimeAI {
|
||||
|
||||
const { model, tools, apiMode: _, ...restPayload } = payload;
|
||||
const functions = tools?.map((tool) => tool.function);
|
||||
const headers = options?.headers || {};
|
||||
const headers = new Headers(toHeadersInit(options?.headers));
|
||||
if (this.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
||||
headers.set('Authorization', `Bearer ${this.apiKey}`);
|
||||
}
|
||||
const url = new URL(model, this.baseURL);
|
||||
const response = await fetch(url, {
|
||||
body: JSON.stringify({ tools: functions, ...restPayload }),
|
||||
headers: { 'Content-Type': 'application/json', ...headers },
|
||||
headers: new Headers({
|
||||
...Object.fromEntries(headers.entries()),
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
method: 'POST',
|
||||
signal: options?.signal,
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ interface LobeGoogleAIParams {
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
client?: GoogleGenAI;
|
||||
defaultHeaders?: Record<string, any>;
|
||||
defaultHeaders?: Record<string, unknown>;
|
||||
id?: string;
|
||||
isVertexAi?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ChatModelCard } from '@lobechat/types';
|
||||
import { ModelProvider } from 'model-bank';
|
||||
import type OpenAI from 'openai';
|
||||
|
||||
import type { OpenAICompatibleFactoryOptions } from '../../core/openaiCompatibleFactory';
|
||||
import { createOpenAICompatibleRuntime } from '../../core/openaiCompatibleFactory';
|
||||
@@ -33,7 +34,7 @@ export const params = {
|
||||
messages: payload.messages as any,
|
||||
model: payload.model,
|
||||
stream: true,
|
||||
...(payload.tools && { tools: payload.tools }),
|
||||
...(payload.tools && { tools: payload.tools as unknown as OpenAI.ChatCompletionTool[] }),
|
||||
};
|
||||
},
|
||||
noUserId: true,
|
||||
|
||||
@@ -44,7 +44,7 @@ export const params = {
|
||||
// Check if model uses enable_thinking parameter (without clear_thinking)
|
||||
const useEnableThinking = model && enableThinkingModels.has(model);
|
||||
|
||||
const chatTemplateKwargs: Record<string, any> = {};
|
||||
const chatTemplateKwargs: Record<string, unknown> = {};
|
||||
|
||||
if (thinkingFlag !== undefined) {
|
||||
if (usePreservedThinking) {
|
||||
|
||||
@@ -68,11 +68,11 @@ async function createQwenImageTask(
|
||||
const url = `${baseUrl}/api/v1/services/aigc/${endpoint}/image-synthesis`;
|
||||
log('Creating %s task with model: %s, endpoint: %s', endpoint, model, url);
|
||||
|
||||
const input: Record<string, any> = {
|
||||
const input: Record<string, unknown> = {
|
||||
prompt: params.prompt,
|
||||
};
|
||||
|
||||
const parameters: Record<string, any> = {
|
||||
const parameters: Record<string, unknown> = {
|
||||
n: 1,
|
||||
...(typeof params.seed === 'number' ? { seed: params.seed } : {}),
|
||||
...(params.width && params.height
|
||||
|
||||
@@ -95,7 +95,7 @@ export class LobeReplicateAI implements LobeRuntimeAI {
|
||||
this.debugLog('[Replicate createImage] Model:', model);
|
||||
this.debugLog('[Replicate createImage] Params received:', JSON.stringify(params, null, 2));
|
||||
|
||||
const input: Record<string, any> = {};
|
||||
const input: Record<string, unknown> = {};
|
||||
|
||||
// Redux models don't use prompt - they only use the input image
|
||||
if (!model.includes('redux')) {
|
||||
@@ -139,7 +139,7 @@ export class LobeReplicateAI implements LobeRuntimeAI {
|
||||
const isPrivate192Range = hostname.startsWith('192.168.');
|
||||
|
||||
// 172.16.0.0 – 172.31.255.255
|
||||
const isPrivate172Range = /^172\.(1[6-9]|2\d|3[01])\./.test(hostname);
|
||||
const isPrivate172Range = /^172\.(?:1[6-9]|2\d|3[01])\./.test(hostname);
|
||||
|
||||
const isLocalTld = hostname.endsWith('.local');
|
||||
|
||||
@@ -186,7 +186,9 @@ export class LobeReplicateAI implements LobeRuntimeAI {
|
||||
this.debugLog('[Replicate createImage] Mapped to', imageParamName, 'as Buffer');
|
||||
} catch (fetchError: any) {
|
||||
this.debugLog('[Replicate createImage] Error fetching local image:', fetchError);
|
||||
throw new Error(`Failed to fetch local image: ${fetchError.message}`);
|
||||
throw new Error(`Failed to fetch local image: ${fetchError.message}`, {
|
||||
cause: fetchError,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Public URL - use directly
|
||||
@@ -416,7 +418,7 @@ export class LobeReplicateAI implements LobeRuntimeAI {
|
||||
|
||||
if (!isReplicateDebug) return;
|
||||
|
||||
console.log(...args);
|
||||
console.info(...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function createSiliconCloudImage(
|
||||
['steps', 'num_inference_steps'],
|
||||
]);
|
||||
|
||||
const body: Record<string, any> = {
|
||||
const body: Record<string, unknown> = {
|
||||
model,
|
||||
prompt: params.prompt,
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function createVolcengineImage(
|
||||
['cfg', 'guidance_scale'],
|
||||
]);
|
||||
|
||||
const userInput: Record<string, any> = Object.fromEntries(
|
||||
const userInput: Record<string, unknown> = Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [
|
||||
paramsMap.get(key as RuntimeImageGenParamsValue) ?? key,
|
||||
value,
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function createWenxinImage(
|
||||
}
|
||||
}
|
||||
|
||||
const requestBody: Record<string, any> = {
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model,
|
||||
prompt: params.prompt,
|
||||
...(images !== undefined && { image: images }),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { ModelPerformance, ModelTokensUsage, ModelUsage } from '@lobechat/types';
|
||||
import type { ModelPerformance, ModelTokensUsage, ModelUsage, ToolSchema } from '@lobechat/types';
|
||||
|
||||
import type { MessageToolCall, MessageToolCallChunk } from './toolsCalling';
|
||||
|
||||
export type RuntimeHeaders = HeadersInit | Record<string, string | undefined>;
|
||||
|
||||
export type LLMRoleType = 'user' | 'system' | 'assistant' | 'function' | 'tool';
|
||||
|
||||
export type ChatResponseFormat =
|
||||
@@ -9,7 +11,7 @@ export type ChatResponseFormat =
|
||||
| {
|
||||
json_schema: {
|
||||
name: string;
|
||||
schema: Record<string, any>;
|
||||
schema: ToolSchema;
|
||||
strict?: boolean;
|
||||
};
|
||||
type: 'json_schema';
|
||||
@@ -173,13 +175,13 @@ export interface ChatMethodOptions {
|
||||
/**
|
||||
* response headers
|
||||
*/
|
||||
headers?: Record<string, any>;
|
||||
headers?: RuntimeHeaders;
|
||||
/** Metadata passed to hooks (billing, tracing, etc.) */
|
||||
metadata?: Record<string, unknown>;
|
||||
/**
|
||||
* send the request to the ai api endpoint
|
||||
*/
|
||||
requestHeaders?: Record<string, any>;
|
||||
requestHeaders?: RuntimeHeaders;
|
||||
signal?: AbortSignal;
|
||||
/**
|
||||
* userId for the chat completion
|
||||
@@ -205,9 +207,7 @@ export interface ChatCompletionFunctions {
|
||||
* @type {{ [key: string]: any }}
|
||||
* @memberof ChatCompletionFunctions
|
||||
*/
|
||||
parameters?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
parameters?: ToolSchema;
|
||||
}
|
||||
|
||||
export interface ChatCompletionTool {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ModelUsage } from '@lobechat/types';
|
||||
|
||||
import type { RuntimeHeaders } from './chat';
|
||||
|
||||
export interface EmbeddingsPayload {
|
||||
/**
|
||||
* The number of dimensions the resulting output embeddings should have. Only
|
||||
@@ -19,7 +21,7 @@ export interface EmbeddingsPayload {
|
||||
}
|
||||
|
||||
export interface EmbeddingsOptions {
|
||||
headers?: Record<string, any>;
|
||||
headers?: RuntimeHeaders;
|
||||
/** Metadata passed to hooks (billing, tracing, etc.) */
|
||||
metadata?: Record<string, unknown>;
|
||||
onUsage?: (usage: ModelUsage) => void | Promise<void>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ModelUsage } from '@lobechat/types';
|
||||
|
||||
import type { ChatCompletionTool } from './chat';
|
||||
import type { ChatCompletionTool, RuntimeHeaders } from './chat';
|
||||
|
||||
interface GenerateObjectMessage {
|
||||
content: string;
|
||||
@@ -13,7 +13,7 @@ export interface GenerateObjectSchema {
|
||||
name: string;
|
||||
schema: {
|
||||
additionalProperties?: boolean;
|
||||
properties: Record<string, any>;
|
||||
properties: Record<string, unknown>;
|
||||
required?: string[];
|
||||
type: 'object';
|
||||
};
|
||||
@@ -32,7 +32,7 @@ export interface GenerateObjectOptions {
|
||||
/**
|
||||
* response headers
|
||||
*/
|
||||
headers?: Record<string, any>;
|
||||
headers?: RuntimeHeaders;
|
||||
|
||||
/** Metadata passed to hooks (billing, tracing, etc.) */
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { RuntimeHeaders } from './chat';
|
||||
|
||||
export interface TextToSpeechPayload {
|
||||
input: string;
|
||||
model: string;
|
||||
@@ -5,7 +7,7 @@ export interface TextToSpeechPayload {
|
||||
}
|
||||
|
||||
export interface TextToSpeechOptions {
|
||||
headers?: Record<string, any>;
|
||||
headers?: RuntimeHeaders;
|
||||
signal?: AbortSignal;
|
||||
/**
|
||||
* userId for the embeddings
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface AgentInitErrorPayload {
|
||||
}
|
||||
|
||||
export interface ChatCompletionErrorPayload {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
endpoint?: string;
|
||||
error: object;
|
||||
errorType: ILobeAgentRuntimeErrorType;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { RuntimeHeaders } from '../types/chat';
|
||||
|
||||
export const toHeadersInit = (headers?: RuntimeHeaders): Record<string, string> | undefined => {
|
||||
if (!headers) return undefined;
|
||||
|
||||
const normalized = new Headers();
|
||||
|
||||
if (headers instanceof Headers || Array.isArray(headers)) {
|
||||
new Headers(headers).forEach((value, key) => {
|
||||
normalized.set(key, value);
|
||||
});
|
||||
|
||||
return Object.fromEntries(normalized.entries());
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (typeof value === 'string') {
|
||||
normalized.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.fromEntries(normalized.entries());
|
||||
};
|
||||
|
||||
export const mergeHeaders = (
|
||||
...headersList: Array<RuntimeHeaders | undefined>
|
||||
): Record<string, string> => {
|
||||
const merged = new Headers();
|
||||
|
||||
for (const header of headersList) {
|
||||
const normalized = toHeadersInit(header);
|
||||
if (!normalized) continue;
|
||||
|
||||
new Headers(normalized).forEach((value, key) => {
|
||||
merged.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
return Object.fromEntries(merged.entries());
|
||||
};
|
||||
@@ -459,7 +459,7 @@ const getModelLocalEnableConfig = (
|
||||
* Common logic for processing model cards
|
||||
*/
|
||||
const processModelCard = (
|
||||
model: { [key: string]: any; id: string },
|
||||
model: { [key: string]: unknown; id: string },
|
||||
config: ModelProcessorConfig,
|
||||
knownModel?: any,
|
||||
options?: { includeKnownExtendParams?: boolean; includeSearchSettings?: boolean },
|
||||
@@ -495,7 +495,13 @@ const processModelCard = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mergedSettings = mergeSettings(model.settings, knownModel?.settings, options);
|
||||
const mergedSettings = mergeSettings(
|
||||
model.settings && typeof model.settings === 'object'
|
||||
? (model.settings as AiModelSettings)
|
||||
: undefined,
|
||||
knownModel?.settings,
|
||||
options,
|
||||
);
|
||||
|
||||
const formatPricing = (pricing?: {
|
||||
cachedInput?: number;
|
||||
@@ -557,7 +563,7 @@ const processModelCard = (
|
||||
contextWindowTokens: model.contextWindowTokens ?? knownModel?.contextWindowTokens ?? undefined,
|
||||
description: model.description ?? knownModel?.description ?? '',
|
||||
displayName: processDisplayName(model.displayName ?? knownModel?.displayName ?? model.id),
|
||||
enabled: model?.enabled || false,
|
||||
enabled: typeof model.enabled === 'boolean' ? model.enabled : false,
|
||||
functionCall:
|
||||
model.functionCall ??
|
||||
knownModel?.abilities?.functionCall ??
|
||||
@@ -570,7 +576,18 @@ const processModelCard = (
|
||||
((isKeywordListMatch(model.id.toLowerCase(), imageOutputKeywords) && !isExcludedModel) ||
|
||||
false),
|
||||
maxOutput: model.maxOutput ?? knownModel?.maxOutput ?? undefined,
|
||||
pricing: formatPricing(model?.pricing) ?? undefined,
|
||||
pricing:
|
||||
formatPricing(
|
||||
model.pricing && typeof model.pricing === 'object'
|
||||
? (model.pricing as {
|
||||
cachedInput?: number;
|
||||
input?: number;
|
||||
output?: number;
|
||||
units?: any[];
|
||||
writeCacheInput?: number;
|
||||
})
|
||||
: undefined,
|
||||
) ?? undefined,
|
||||
reasoning:
|
||||
model.reasoning ??
|
||||
knownModel?.abilities?.reasoning ??
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import type { RuntimeHeaders } from '../types/chat';
|
||||
import { toHeadersInit } from './headers';
|
||||
|
||||
export const StreamingResponse = (
|
||||
stream: ReadableStream,
|
||||
options?: { headers?: Record<string, string> },
|
||||
options?: { headers?: RuntimeHeaders },
|
||||
) => {
|
||||
const headers = new Headers({
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'text/event-stream',
|
||||
// for Nginx: disable chunk buffering
|
||||
'X-Accel-Buffering': 'no',
|
||||
});
|
||||
|
||||
if (options?.headers) {
|
||||
const extraHeaders = new Headers(toHeadersInit(options.headers));
|
||||
extraHeaders.forEach((value, key) => {
|
||||
headers.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Content-Type': 'text/event-stream',
|
||||
// for Nginx: disable chunk buffering
|
||||
'X-Accel-Buffering': 'no',
|
||||
...options?.headers,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const safeParseJSON = <T = Record<string, any>>(text?: string) => {
|
||||
export const safeParseJSON = <T = Record<string, unknown>>(text?: string) => {
|
||||
if (typeof text !== 'string') return undefined;
|
||||
|
||||
let json: T;
|
||||
|
||||
@@ -85,7 +85,7 @@ export async function parseFormData(c: Context): Promise<FormData> {
|
||||
multiples: true,
|
||||
});
|
||||
|
||||
const { fields, files } = await new Promise<{ fields: Record<string, any>; files: any }>(
|
||||
const { fields, files } = await new Promise<{ fields: Record<string, unknown>; files: any }>(
|
||||
(resolve, reject) => {
|
||||
form.parse(fakeReq, (err: any, fds: any, fls: any) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
@@ -7,7 +7,9 @@ const DEFAULT_PAGE_SIZE = 20;
|
||||
* @param request Query parameter object
|
||||
* @returns { limit, offset } if pagination parameters are provided; otherwise an empty object
|
||||
*/
|
||||
export function processPaginationConditions(request: Record<string, any> & IPaginationQuery): {
|
||||
export function processPaginationConditions<T extends IPaginationQuery>(
|
||||
request: T,
|
||||
): {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
} {
|
||||
|
||||
@@ -129,7 +129,7 @@ export class ResponsesService extends BaseService {
|
||||
|
||||
// Handle tool_calls from assistant
|
||||
if (hasToolCalls) {
|
||||
for (const toolCall of msg.tool_calls) {
|
||||
for (const toolCall of msg.tool_calls ?? []) {
|
||||
output.push({
|
||||
arguments: toolCall.function?.arguments ?? '{}',
|
||||
call_id: toolCall.id ?? `call_${itemCounter}`,
|
||||
@@ -575,7 +575,7 @@ export class ResponsesService extends BaseService {
|
||||
status: ResponseObject['status'];
|
||||
usage?: ResponseUsage;
|
||||
}): ResponseObject {
|
||||
const p = opts.params as Record<string, any>;
|
||||
const p = opts.params as Record<string, unknown>;
|
||||
return {
|
||||
background: p.background ?? false,
|
||||
completed_at: opts.completedAt ?? null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { AgentItem } from '../agent';
|
||||
import type { TaskDetail, UIChatMessage } from '../message';
|
||||
import type { RichTextEditorState, TaskDetail, UIChatMessage } from '../message';
|
||||
import type { ChatTopic } from '../topic';
|
||||
|
||||
export interface LobeChatGroupMetaConfig {
|
||||
@@ -42,7 +42,7 @@ export const InsertChatGroupSchema = z.object({
|
||||
config: ChatGroupConfigSchema.optional().nullable(),
|
||||
content: z.string().optional().nullable(),
|
||||
description: z.string().optional().nullable(),
|
||||
editorData: z.record(z.string(), z.any()).optional().nullable(),
|
||||
editorData: z.record(z.string(), z.unknown()).optional().nullable(),
|
||||
groupId: z.string().optional().nullable(),
|
||||
id: z.string().optional(),
|
||||
marketIdentifier: z.string().optional().nullable(),
|
||||
@@ -104,7 +104,7 @@ export interface ChatGroupItem {
|
||||
content?: string | null;
|
||||
createdAt: Date;
|
||||
description?: string | null;
|
||||
editorData?: Record<string, any> | null;
|
||||
editorData?: RichTextEditorState | null;
|
||||
groupId?: string | null;
|
||||
id: string;
|
||||
marketIdentifier?: string | null;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user