@lobehub/chat 1.128.7 → 1.128.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.cursor/rules/debug-usage.mdc +2 -2
  2. package/.github/workflows/test.yml +1 -0
  3. package/CHANGELOG.md +25 -0
  4. package/apps/desktop/package.json +1 -1
  5. package/changelog/v1.json +9 -0
  6. package/docs/development/database-schema.dbml +2 -0
  7. package/package.json +2 -1
  8. package/packages/agent-runtime/examples/tools-calling.ts +303 -0
  9. package/packages/agent-runtime/package.json +15 -0
  10. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +1046 -0
  11. package/packages/agent-runtime/src/core/index.ts +1 -0
  12. package/packages/agent-runtime/src/core/runtime.ts +656 -0
  13. package/packages/agent-runtime/src/index.ts +2 -0
  14. package/packages/agent-runtime/src/types/event.ts +121 -0
  15. package/packages/agent-runtime/src/types/index.ts +5 -0
  16. package/packages/agent-runtime/src/types/instruction.ts +125 -0
  17. package/packages/agent-runtime/src/types/runtime.ts +18 -0
  18. package/packages/agent-runtime/src/types/state.ts +108 -0
  19. package/packages/agent-runtime/src/types/usage.ts +128 -0
  20. package/packages/agent-runtime/vitest.config.mts +12 -0
  21. package/packages/database/migrations/0031_add_agent_index.sql +2 -0
  22. package/packages/database/migrations/meta/0031_snapshot.json +6447 -0
  23. package/packages/database/migrations/meta/_journal.json +7 -0
  24. package/packages/database/src/core/migrations.json +9 -0
  25. package/packages/database/src/models/session.ts +30 -27
  26. package/packages/database/src/schemas/agent.ts +3 -0
  27. package/src/features/ChatInput/ActionBar/History/Controls.tsx +12 -2
  28. package/src/features/ChatInput/ActionBar/History/index.tsx +18 -1
  29. package/src/features/ChatInput/ActionBar/Search/index.tsx +19 -1
  30. package/src/features/ChatInput/ActionBar/components/Action.tsx +5 -1
@@ -0,0 +1,121 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import type { AgentState, ToolsCalling } from './state';
3
+
4
+ export interface AgentEventInit {
5
+ type: 'init';
6
+ }
7
+
8
+ export interface AgentEventLlmStart {
9
+ type: 'llm_start';
10
+ payload: unknown;
11
+ }
12
+
13
+ export interface AgentEventLlmStream {
14
+ type: 'llm_stream';
15
+ chunk: unknown;
16
+ }
17
+
18
+ export interface AgentEventLlmResult {
19
+ type: 'llm_result';
20
+ result: unknown;
21
+ }
22
+
23
+ export interface AgentEventToolPending {
24
+ type: 'tool_pending';
25
+ toolCalls: ToolsCalling[];
26
+ }
27
+
28
+ export interface AgentEventToolResult {
29
+ type: 'tool_result';
30
+ id: string;
31
+ result: any;
32
+ }
33
+
34
+ export interface AgentEventHumanApproveRequired {
35
+ type: 'human_approve_required';
36
+ pendingToolsCalling: ToolsCalling[];
37
+ sessionId: string;
38
+ }
39
+
40
+ export interface AgentEventHumanPromptRequired {
41
+ type: 'human_prompt_required';
42
+ metadata?: Record<string, unknown>;
43
+ prompt: string;
44
+ sessionId: string;
45
+ }
46
+
47
+ export interface AgentEventHumanSelectRequired {
48
+ type: 'human_select_required';
49
+ metadata?: Record<string, unknown>;
50
+ multi?: boolean;
51
+ options: { label: string; value: string }[];
52
+ prompt?: string;
53
+ sessionId: string;
54
+ }
55
+
56
+ /**
57
+ * Standardized finish reasons
58
+ */
59
+ export type FinishReason =
60
+ | 'completed' // Normal completion
61
+ | 'user_requested' // User requested to end
62
+ | 'max_steps_exceeded' // Reached maximum steps limit
63
+ | 'cost_limit_exceeded' // Reached cost limit
64
+ | 'timeout' // Execution timeout
65
+ | 'agent_decision' // Agent decided to finish
66
+ | 'error_recovery' // Finished due to unrecoverable error
67
+ | 'system_shutdown'; // System is shutting down
68
+
69
+ export interface AgentEventDone {
70
+ type: 'done';
71
+ finalState: AgentState;
72
+ reason: FinishReason;
73
+ reasonDetail?: string;
74
+ }
75
+
76
+ export interface AgentEventError {
77
+ type: 'error';
78
+ error: any;
79
+ }
80
+
81
+ export interface AgentEventInterrupted {
82
+ type: 'interrupted';
83
+ reason: string;
84
+ interruptedAt: string;
85
+ interruptedInstruction?: any;
86
+ canResume: boolean;
87
+ metadata?: Record<string, unknown>;
88
+ }
89
+
90
+ export interface AgentEventResumed {
91
+ type: 'resumed';
92
+ reason: string;
93
+ resumedAt: string;
94
+ resumedFromStep: number;
95
+ metadata?: Record<string, unknown>;
96
+ }
97
+
98
+ /**
99
+ * Events emitted by the AgentRuntime during execution
100
+ */
101
+ export type AgentEvent =
102
+ // Initialization
103
+ | AgentEventInit
104
+ // LLM streaming output
105
+ | AgentEventLlmStart
106
+ | AgentEventLlmStream
107
+ | AgentEventLlmResult
108
+ // Tool invocation
109
+ | AgentEventToolPending
110
+ | AgentEventToolResult
111
+ // Normal completion
112
+ | AgentEventDone
113
+ // Error thrown
114
+ | AgentEventError
115
+ // Human-in-the-loop (HIL)
116
+ | AgentEventHumanApproveRequired
117
+ | AgentEventHumanPromptRequired
118
+ | AgentEventHumanSelectRequired
119
+ // Interruption and resumption
120
+ | AgentEventInterrupted
121
+ | AgentEventResumed;
@@ -0,0 +1,5 @@
1
+ export * from './event';
2
+ export * from './instruction';
3
+ export * from './runtime';
4
+ export * from './state';
5
+ export * from './usage';
@@ -0,0 +1,125 @@
1
+ import type { FinishReason } from './event';
2
+ import { AgentState, ToolRegistry, ToolsCalling } from './state';
3
+ import type { Cost, CostCalculationContext, Usage } from './usage';
4
+
5
+ /**
6
+ * Runtime execution context passed to Agent runner
7
+ */
8
+ export interface RuntimeContext {
9
+ /** Phase-specific payload/context */
10
+ payload?: unknown;
11
+ /** Current execution phase */
12
+ phase:
13
+ | 'init'
14
+ | 'user_input'
15
+ | 'llm_result'
16
+ | 'tool_result'
17
+ | 'human_response'
18
+ | 'human_approved_tool'
19
+ | 'error';
20
+ /** Session metadata */
21
+ session: {
22
+ eventCount: number;
23
+ messageCount: number;
24
+ sessionId: string;
25
+ status: AgentState['status'];
26
+ stepCount: number;
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Represents the "Brain" of an agent.
32
+ * It contains all the decision-making logic and is completely stateless.
33
+ */
34
+ export interface Agent {
35
+ /**
36
+ * Calculate cost from usage statistics
37
+ * @param context - Cost calculation context with usage and limits
38
+ * @returns Updated cost information
39
+ */
40
+ calculateCost?(context: CostCalculationContext): Cost;
41
+
42
+ /**
43
+ * Calculate usage statistics from operation results
44
+ * @param operationType - Type of operation that was performed
45
+ * @param operationResult - Result data from the operation
46
+ * @param previousUsage - Previous usage statistics
47
+ * @returns Updated usage statistics
48
+ */
49
+ calculateUsage?(
50
+ operationType: 'llm' | 'tool' | 'human_interaction',
51
+ operationResult: any,
52
+ previousUsage: Usage,
53
+ ): Usage;
54
+
55
+ /** Optional custom executors mapping to extend runtime behaviors */
56
+ executors?: Partial<Record<AgentInstruction['type'], any>>;
57
+
58
+ /**
59
+ * Model runtime function for LLM calls - Agent owns its model integration
60
+ * @param payload - LLM call payload (messages, tools, etc.)
61
+ * @returns Async iterable of streaming response chunks
62
+ */
63
+ modelRuntime?: (payload: unknown) => AsyncIterable<any>;
64
+
65
+ /**
66
+ * The core runner method. Based on the current execution context and state,
67
+ * it decides what the next action should be.
68
+ * @param context - Current runtime context with phase and payload
69
+ * @param state - Complete agent state for reference
70
+ */
71
+ runner(context: RuntimeContext, state: AgentState): Promise<AgentInstruction>;
72
+
73
+ /** Optional tools registry held by the agent */
74
+ tools?: ToolRegistry;
75
+ }
76
+
77
+ export interface AgentInstructionCallLlm {
78
+ payload: unknown;
79
+ type: 'call_llm';
80
+ }
81
+
82
+ export interface AgentInstructionCallTool {
83
+ toolCall: ToolsCalling;
84
+ type: 'call_tool';
85
+ }
86
+
87
+ export interface AgentInstructionRequestHumanPrompt {
88
+ metadata?: Record<string, unknown>;
89
+ prompt: string;
90
+ reason?: string;
91
+ type: 'request_human_prompt';
92
+ }
93
+
94
+ export interface AgentInstructionRequestHumanSelect {
95
+ metadata?: Record<string, unknown>;
96
+ multi?: boolean;
97
+ options: Array<{ label: string; value: string }>;
98
+ prompt?: string;
99
+ reason?: string;
100
+ type: 'request_human_select';
101
+ }
102
+
103
+ export interface AgentInstructionRequestHumanApprove {
104
+ pendingToolsCalling: ToolsCalling[];
105
+ reason?: string;
106
+ type: 'request_human_approve';
107
+ }
108
+
109
+ export interface AgentInstructionFinish {
110
+ reason: FinishReason;
111
+ reasonDetail?: string;
112
+ type: 'finish';
113
+ }
114
+
115
+ /**
116
+ * A serializable instruction object that the "Agent" (Brain) returns
117
+ * to the "AgentRuntime" (Engine) to execute.
118
+ */
119
+ export type AgentInstruction =
120
+ | AgentInstructionCallLlm
121
+ | AgentInstructionCallTool
122
+ | AgentInstructionRequestHumanPrompt
123
+ | AgentInstructionRequestHumanSelect
124
+ | AgentInstructionRequestHumanApprove
125
+ | AgentInstructionFinish;
@@ -0,0 +1,18 @@
1
+ import type { AgentEvent } from './event';
2
+ import { AgentInstruction, RuntimeContext } from './instruction';
3
+ import { AgentState } from './state';
4
+
5
+ export type InstructionExecutor = (
6
+ instruction: AgentInstruction,
7
+ state: AgentState,
8
+ ) => Promise<{
9
+ events: AgentEvent[];
10
+ newState: AgentState;
11
+ /** Next context to pass to Agent runner (if execution should continue) */
12
+ nextContext?: RuntimeContext;
13
+ }>;
14
+
15
+ export interface RuntimeConfig {
16
+ /** Custom executors for specific instruction types */
17
+ executors?: Partial<Record<AgentInstruction['type'], InstructionExecutor>>;
18
+ }
@@ -0,0 +1,108 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import type { AgentEvent } from './event';
3
+ import type { Cost, CostLimit, Usage } from './usage';
4
+
5
+ /**
6
+ * Agent's serializable state.
7
+ * This is the "passport" that can be persisted and transferred.
8
+ */
9
+ export interface AgentState {
10
+ sessionId: string;
11
+ // --- State Machine ---
12
+ status: 'idle' | 'running' | 'waiting_for_human_input' | 'done' | 'error' | 'interrupted';
13
+
14
+ // --- Core Context ---
15
+ messages: any[];
16
+ systemRole?: string;
17
+
18
+ // --- Event History ---
19
+ /**
20
+ * Complete event trace for this agent session.
21
+ * Useful for debugging, auditing, and state replay.
22
+ */
23
+ events: AgentEvent[];
24
+
25
+ // --- Execution Tracking ---
26
+ /**
27
+ * Number of execution steps in this session.
28
+ * Incremented on each runtime.step() call.
29
+ */
30
+ stepCount: number;
31
+ /**
32
+ * Optional maximum number of steps allowed.
33
+ * If set, execution will stop with error when exceeded.
34
+ */
35
+ maxSteps?: number;
36
+
37
+ // --- Usage and Cost Tracking ---
38
+ /**
39
+ * Accumulated usage statistics for this session.
40
+ * Tracks tokens, API calls, tool usage, etc.
41
+ */
42
+ usage: Usage;
43
+ /**
44
+ * Current calculated cost for this session.
45
+ * Updated after each billable operation.
46
+ */
47
+ cost: Cost;
48
+ /**
49
+ * Optional cost limits configuration.
50
+ * If set, execution will stop when limits are exceeded.
51
+ */
52
+ costLimit?: CostLimit;
53
+
54
+ // --- HIL ---
55
+ /**
56
+ * When status is 'waiting_for_human_input', this stores pending requests
57
+ * for human-in-the-loop operations.
58
+ */
59
+ pendingToolsCalling?: ToolsCalling[];
60
+ pendingHumanPrompt?: { metadata?: Record<string, unknown>; prompt: string };
61
+ pendingHumanSelect?: {
62
+ metadata?: Record<string, unknown>;
63
+ multi?: boolean;
64
+ options: Array<{ label: string; value: string }>;
65
+ prompt?: string;
66
+ };
67
+
68
+ // --- Interruption Handling ---
69
+ /**
70
+ * When status is 'interrupted', this stores the interruption context
71
+ * for potential resumption or cleanup.
72
+ */
73
+ interruption?: {
74
+ /** Reason for interruption */
75
+ reason: string;
76
+ /** Timestamp when interruption occurred */
77
+ interruptedAt: string;
78
+ /** The instruction that was being executed when interrupted */
79
+ interruptedInstruction?: any;
80
+ /** Whether the interruption can be resumed */
81
+ canResume: boolean;
82
+ };
83
+
84
+ // --- Metadata ---
85
+ createdAt: string;
86
+ error?: any;
87
+ lastModified: string;
88
+
89
+ // --- Extensible metadata ---
90
+ metadata?: Record<string, any>;
91
+ }
92
+
93
+ /**
94
+ * OpenAI Tool Call
95
+ */
96
+ export interface ToolsCalling {
97
+ function: {
98
+ arguments: string;
99
+ name: string; // A JSON string of arguments
100
+ };
101
+ id: string;
102
+ type: 'function';
103
+ }
104
+
105
+ /**
106
+ * A registry for tools, mapping tool names to their implementation.
107
+ */
108
+ export type ToolRegistry = Record<string, (args: any) => Promise<any>>;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Token usage tracking for different types of operations
3
+ */
4
+ export interface TokenUsage {
5
+ /** Input tokens consumed */
6
+ input: number;
7
+ /** Output tokens generated */
8
+ output: number;
9
+ /** Total tokens (input + output) */
10
+ total: number;
11
+ }
12
+
13
+ /**
14
+ * Usage statistics for different operation types
15
+ */
16
+ export interface Usage {
17
+ /** Human interaction statistics */
18
+ humanInteraction: {
19
+ /** Number of approval requests */
20
+ approvalRequests: number;
21
+ /** Number of prompt requests */
22
+ promptRequests: number;
23
+ /** Number of selection requests */
24
+ selectRequests: number;
25
+ /** Total waiting time for human input */
26
+ totalWaitingTimeMs: number;
27
+ };
28
+
29
+ /** LLM model usage */
30
+ llm: {
31
+ /** Number of LLM API calls made */
32
+ apiCalls: number;
33
+ /** Total processing time in milliseconds */
34
+ processingTimeMs: number;
35
+ /** Total token usage across all LLM calls */
36
+ tokens: TokenUsage;
37
+ };
38
+
39
+ /** Tool usage statistics */
40
+ tools: {
41
+ /** Usage breakdown by tool name */
42
+ byTool: Record<
43
+ string,
44
+ {
45
+ calls: number;
46
+ errors: number;
47
+ totalTimeMs: number;
48
+ }
49
+ >;
50
+ /** Number of tool calls executed */
51
+ totalCalls: number;
52
+ /** Total tool execution time */
53
+ totalTimeMs: number;
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Cost calculation result
59
+ */
60
+ export interface Cost {
61
+ /** Cost calculation timestamp */
62
+ calculatedAt: string;
63
+
64
+ currency: string;
65
+
66
+ /** LLM API costs */
67
+ llm: {
68
+ /** Cost per model used */
69
+ byModel: Record<
70
+ string,
71
+ {
72
+ currency: string;
73
+ inputTokens: number;
74
+ outputTokens: number;
75
+ totalCost: number;
76
+ }
77
+ >;
78
+ currency: string;
79
+ /** Total LLM cost */
80
+ total: number;
81
+ };
82
+ /** Tool execution costs */
83
+ tools: {
84
+ /** Cost per tool (if tool has associated costs) */
85
+ byTool: Record<
86
+ string,
87
+ {
88
+ calls: number;
89
+ currency: string;
90
+ totalCost: number;
91
+ }
92
+ >;
93
+ currency: string;
94
+ /** Total tool cost */
95
+ total: number;
96
+ };
97
+
98
+ /** Total session cost */
99
+ total: number;
100
+ }
101
+
102
+ /**
103
+ * Cost limit configuration
104
+ */
105
+ export interface CostLimit {
106
+ /** Currency for cost limits */
107
+ currency: string;
108
+ /** Maximum LLM cost allowed */
109
+ maxLlmCost?: number;
110
+ /** Maximum tool cost allowed */
111
+ maxToolCost?: number;
112
+ /** Maximum total cost allowed */
113
+ maxTotalCost: number;
114
+ /** Action to take when limit is exceeded */
115
+ onExceeded: 'stop' | 'warn' | 'interrupt';
116
+ }
117
+
118
+ /**
119
+ * Cost calculation context passed to Agent
120
+ */
121
+ export interface CostCalculationContext {
122
+ /** Cost limits configuration */
123
+ costLimit?: CostLimit;
124
+ /** Previous cost calculation (if any) */
125
+ previousCost?: Cost;
126
+ /** Current usage statistics */
127
+ usage: Usage;
128
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ coverage: {
6
+ reporter: ['text', 'json', 'lcov', 'text-summary'],
7
+ },
8
+ environment: 'happy-dom',
9
+ },
10
+ });
11
+
12
+
@@ -0,0 +1,2 @@
1
+ CREATE INDEX "agents_title_idx" ON "agents" USING btree ("title");--> statement-breakpoint
2
+ CREATE INDEX "agents_description_idx" ON "agents" USING btree ("description");