@renxqoo/renx-code 0.0.3 → 0.0.4
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.
- package/README.md +58 -223
- package/bin/renx.cjs +34 -0
- package/package.json +27 -83
- package/src/App.tsx +297 -0
- package/src/agent/runtime/event-format.ts +258 -0
- package/src/agent/runtime/model-types.ts +13 -0
- package/src/agent/runtime/runtime.context-usage.test.ts +193 -0
- package/src/agent/runtime/runtime.error-handling.test.ts +236 -0
- package/src/agent/runtime/runtime.simple.test.ts +16 -0
- package/src/agent/runtime/runtime.test.ts +293 -0
- package/src/agent/runtime/runtime.ts +881 -0
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +229 -0
- package/src/agent/runtime/source-modules.test.ts +57 -0
- package/src/agent/runtime/source-modules.ts +353 -0
- package/src/agent/runtime/tool-call-buffer.test.ts +65 -0
- package/src/agent/runtime/tool-call-buffer.ts +60 -0
- package/src/agent/runtime/tool-confirmation.test.ts +56 -0
- package/src/agent/runtime/tool-confirmation.ts +15 -0
- package/src/agent/runtime/types.ts +99 -0
- package/src/commands/slash-commands.test.ts +216 -0
- package/src/commands/slash-commands.ts +64 -0
- package/src/components/chat/assistant-reply.test.tsx +47 -0
- package/src/components/chat/assistant-reply.tsx +136 -0
- package/src/components/chat/assistant-segment.test.ts +99 -0
- package/src/components/chat/assistant-segment.tsx +125 -0
- package/src/components/chat/assistant-tool-group.tsx +900 -0
- package/src/components/chat/code-block.test.tsx +206 -0
- package/src/components/chat/code-block.tsx +313 -0
- package/src/components/chat/prompt-card.tsx +81 -0
- package/src/components/chat/segment-groups.test.ts +52 -0
- package/src/components/chat/segment-groups.ts +106 -0
- package/src/components/chat/turn-item.tsx +39 -0
- package/src/components/conversation-panel.tsx +43 -0
- package/src/components/file-mention-menu.tsx +77 -0
- package/src/components/file-picker-dialog.tsx +206 -0
- package/src/components/footer-hints.tsx +75 -0
- package/src/components/model-picker-dialog.tsx +248 -0
- package/src/components/prompt.tsx +233 -0
- package/src/components/slash-command-menu.tsx +65 -0
- package/src/components/tool-confirm-dialog-content.test.ts +103 -0
- package/src/components/tool-confirm-dialog-content.ts +186 -0
- package/src/components/tool-confirm-dialog.tsx +187 -0
- package/src/components/tool-display-config.ts +119 -0
- package/src/context-usage-regressions.test.ts +26 -0
- package/src/files/attachment-capabilities.test.ts +30 -0
- package/src/files/attachment-capabilities.ts +50 -0
- package/src/files/attachment-content.ts +153 -0
- package/src/files/file-mention-query.test.ts +34 -0
- package/src/files/file-mention-query.ts +32 -0
- package/src/files/prompt-display.ts +13 -0
- package/src/files/types.ts +5 -0
- package/src/files/workspace-files.ts +63 -0
- package/src/hooks/agent-event-handlers.test.ts +207 -0
- package/src/hooks/agent-event-handlers.ts +196 -0
- package/src/hooks/chat-local-replies.fixed.test.ts +119 -0
- package/src/hooks/chat-local-replies.test.ts +153 -0
- package/src/hooks/chat-local-replies.ts +63 -0
- package/src/hooks/turn-updater.test.ts +70 -0
- package/src/hooks/turn-updater.ts +166 -0
- package/src/hooks/use-agent-chat.context.test.ts +10 -0
- package/src/hooks/use-agent-chat.status.test.ts +14 -0
- package/src/hooks/use-agent-chat.test.ts +80 -0
- package/src/hooks/use-agent-chat.ts +621 -0
- package/src/hooks/use-file-mention-menu.ts +196 -0
- package/src/hooks/use-file-picker.ts +185 -0
- package/src/hooks/use-model-picker.ts +196 -0
- package/src/hooks/use-slash-command-menu.ts +154 -0
- package/src/index.tsx +55 -0
- package/src/runtime/clipboard.test.ts +43 -0
- package/src/runtime/clipboard.ts +89 -0
- package/src/runtime/exit.test.ts +177 -0
- package/src/runtime/exit.ts +98 -0
- package/src/runtime/runtime-support.test.ts +31 -0
- package/src/runtime/terminal-theme.test.ts +55 -0
- package/src/runtime/terminal-theme.ts +196 -0
- package/src/types/chat.ts +32 -0
- package/src/types/message-content.ts +48 -0
- package/src/ui/open-code-theme.ts +176 -0
- package/src/ui/opencode-markdown.ts +211 -0
- package/src/ui/theme.simple.test.ts +52 -0
- package/src/ui/theme.test.ts +151 -0
- package/src/ui/theme.ts +152 -0
- package/src/utils/time.test.ts +144 -0
- package/src/utils/time.ts +7 -0
- package/tsconfig.json +30 -0
- package/LICENSE +0 -21
- package/dist/App.d.ts +0 -2
- package/dist/App.d.ts.map +0 -1
- package/dist/App.js +0 -170
- package/dist/App.js.map +0 -1
- package/dist/agent/prompts/system.d.ts +0 -24
- package/dist/agent/prompts/system.d.ts.map +0 -1
- package/dist/agent/prompts/system.js +0 -222
- package/dist/agent/prompts/system.js.map +0 -1
- package/dist/agent/runtime/event-format.d.ts +0 -17
- package/dist/agent/runtime/event-format.d.ts.map +0 -1
- package/dist/agent/runtime/event-format.js +0 -194
- package/dist/agent/runtime/event-format.js.map +0 -1
- package/dist/agent/runtime/model-types.d.ts +0 -13
- package/dist/agent/runtime/model-types.d.ts.map +0 -1
- package/dist/agent/runtime/model-types.js +0 -1
- package/dist/agent/runtime/model-types.js.map +0 -1
- package/dist/agent/runtime/runtime.d.ts +0 -16
- package/dist/agent/runtime/runtime.d.ts.map +0 -1
- package/dist/agent/runtime/runtime.js +0 -691
- package/dist/agent/runtime/runtime.js.map +0 -1
- package/dist/agent/runtime/source-modules.d.ts +0 -176
- package/dist/agent/runtime/source-modules.d.ts.map +0 -1
- package/dist/agent/runtime/source-modules.js +0 -110
- package/dist/agent/runtime/source-modules.js.map +0 -1
- package/dist/agent/runtime/tool-call-buffer.d.ts +0 -12
- package/dist/agent/runtime/tool-call-buffer.d.ts.map +0 -1
- package/dist/agent/runtime/tool-call-buffer.js +0 -48
- package/dist/agent/runtime/tool-call-buffer.js.map +0 -1
- package/dist/agent/runtime/tool-confirmation.d.ts +0 -3
- package/dist/agent/runtime/tool-confirmation.d.ts.map +0 -1
- package/dist/agent/runtime/tool-confirmation.js +0 -9
- package/dist/agent/runtime/tool-confirmation.js.map +0 -1
- package/dist/agent/runtime/types.d.ts +0 -86
- package/dist/agent/runtime/types.d.ts.map +0 -1
- package/dist/agent/runtime/types.js +0 -1
- package/dist/agent/runtime/types.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -12
- package/dist/cli.js.map +0 -1
- package/dist/commands/slash-commands.d.ts +0 -11
- package/dist/commands/slash-commands.d.ts.map +0 -1
- package/dist/commands/slash-commands.js +0 -48
- package/dist/commands/slash-commands.js.map +0 -1
- package/dist/components/chat/assistant-reply.d.ts +0 -13
- package/dist/components/chat/assistant-reply.d.ts.map +0 -1
- package/dist/components/chat/assistant-reply.js +0 -78
- package/dist/components/chat/assistant-reply.js.map +0 -1
- package/dist/components/chat/assistant-segment.d.ts +0 -8
- package/dist/components/chat/assistant-segment.d.ts.map +0 -1
- package/dist/components/chat/assistant-segment.js +0 -54
- package/dist/components/chat/assistant-segment.js.map +0 -1
- package/dist/components/chat/assistant-tool-group.d.ts +0 -7
- package/dist/components/chat/assistant-tool-group.d.ts.map +0 -1
- package/dist/components/chat/assistant-tool-group.js +0 -695
- package/dist/components/chat/assistant-tool-group.js.map +0 -1
- package/dist/components/chat/code-block.d.ts +0 -16
- package/dist/components/chat/code-block.d.ts.map +0 -1
- package/dist/components/chat/code-block.js +0 -194
- package/dist/components/chat/code-block.js.map +0 -1
- package/dist/components/chat/prompt-card.d.ts +0 -9
- package/dist/components/chat/prompt-card.d.ts.map +0 -1
- package/dist/components/chat/prompt-card.js +0 -18
- package/dist/components/chat/prompt-card.js.map +0 -1
- package/dist/components/chat/segment-groups.d.ts +0 -24
- package/dist/components/chat/segment-groups.d.ts.map +0 -1
- package/dist/components/chat/segment-groups.js +0 -69
- package/dist/components/chat/segment-groups.js.map +0 -1
- package/dist/components/chat/turn-item.d.ts +0 -9
- package/dist/components/chat/turn-item.d.ts.map +0 -1
- package/dist/components/chat/turn-item.js +0 -11
- package/dist/components/chat/turn-item.js.map +0 -1
- package/dist/components/conversation-panel.d.ts +0 -8
- package/dist/components/conversation-panel.d.ts.map +0 -1
- package/dist/components/conversation-panel.js +0 -8
- package/dist/components/conversation-panel.js.map +0 -1
- package/dist/components/file-mention-menu.d.ts +0 -11
- package/dist/components/file-mention-menu.d.ts.map +0 -1
- package/dist/components/file-mention-menu.js +0 -15
- package/dist/components/file-mention-menu.js.map +0 -1
- package/dist/components/file-picker-dialog.d.ts +0 -21
- package/dist/components/file-picker-dialog.d.ts.map +0 -1
- package/dist/components/file-picker-dialog.js +0 -48
- package/dist/components/file-picker-dialog.js.map +0 -1
- package/dist/components/footer-hints.d.ts +0 -7
- package/dist/components/footer-hints.d.ts.map +0 -1
- package/dist/components/footer-hints.js +0 -29
- package/dist/components/footer-hints.js.map +0 -1
- package/dist/components/model-picker-dialog.d.ts +0 -20
- package/dist/components/model-picker-dialog.d.ts.map +0 -1
- package/dist/components/model-picker-dialog.js +0 -72
- package/dist/components/model-picker-dialog.js.map +0 -1
- package/dist/components/prompt.d.ts +0 -18
- package/dist/components/prompt.d.ts.map +0 -1
- package/dist/components/prompt.js +0 -96
- package/dist/components/prompt.js.map +0 -1
- package/dist/components/slash-command-menu.d.ts +0 -9
- package/dist/components/slash-command-menu.d.ts.map +0 -1
- package/dist/components/slash-command-menu.js +0 -20
- package/dist/components/slash-command-menu.js.map +0 -1
- package/dist/components/tool-confirm-dialog-content.d.ts +0 -15
- package/dist/components/tool-confirm-dialog-content.d.ts.map +0 -1
- package/dist/components/tool-confirm-dialog-content.js +0 -143
- package/dist/components/tool-confirm-dialog-content.js.map +0 -1
- package/dist/components/tool-confirm-dialog.d.ts +0 -12
- package/dist/components/tool-confirm-dialog.d.ts.map +0 -1
- package/dist/components/tool-confirm-dialog.js +0 -21
- package/dist/components/tool-confirm-dialog.js.map +0 -1
- package/dist/components/tool-display-config.d.ts +0 -11
- package/dist/components/tool-display-config.d.ts.map +0 -1
- package/dist/components/tool-display-config.js +0 -94
- package/dist/components/tool-display-config.js.map +0 -1
- package/dist/config/paths.d.ts +0 -7
- package/dist/config/paths.d.ts.map +0 -1
- package/dist/config/paths.js +0 -24
- package/dist/config/paths.js.map +0 -1
- package/dist/files/attachment-capabilities.d.ts +0 -19
- package/dist/files/attachment-capabilities.d.ts.map +0 -1
- package/dist/files/attachment-capabilities.js +0 -26
- package/dist/files/attachment-capabilities.js.map +0 -1
- package/dist/files/attachment-content.d.ts +0 -5
- package/dist/files/attachment-content.d.ts.map +0 -1
- package/dist/files/attachment-content.js +0 -117
- package/dist/files/attachment-content.js.map +0 -1
- package/dist/files/file-mention-query.d.ts +0 -9
- package/dist/files/file-mention-query.d.ts.map +0 -1
- package/dist/files/file-mention-query.js +0 -23
- package/dist/files/file-mention-query.js.map +0 -1
- package/dist/files/prompt-display.d.ts +0 -3
- package/dist/files/prompt-display.d.ts.map +0 -1
- package/dist/files/prompt-display.js +0 -11
- package/dist/files/prompt-display.js.map +0 -1
- package/dist/files/types.d.ts +0 -6
- package/dist/files/types.d.ts.map +0 -1
- package/dist/files/types.js +0 -1
- package/dist/files/types.js.map +0 -1
- package/dist/files/workspace-files.d.ts +0 -3
- package/dist/files/workspace-files.d.ts.map +0 -1
- package/dist/files/workspace-files.js +0 -48
- package/dist/files/workspace-files.js.map +0 -1
- package/dist/hooks/agent-event-handlers.d.ts +0 -11
- package/dist/hooks/agent-event-handlers.d.ts.map +0 -1
- package/dist/hooks/agent-event-handlers.js +0 -137
- package/dist/hooks/agent-event-handlers.js.map +0 -1
- package/dist/hooks/chat-local-replies.d.ts +0 -9
- package/dist/hooks/chat-local-replies.d.ts.map +0 -1
- package/dist/hooks/chat-local-replies.js +0 -54
- package/dist/hooks/chat-local-replies.js.map +0 -1
- package/dist/hooks/turn-updater.d.ts +0 -9
- package/dist/hooks/turn-updater.d.ts.map +0 -1
- package/dist/hooks/turn-updater.js +0 -103
- package/dist/hooks/turn-updater.js.map +0 -1
- package/dist/hooks/use-agent-chat.d.ts +0 -29
- package/dist/hooks/use-agent-chat.d.ts.map +0 -1
- package/dist/hooks/use-agent-chat.js +0 -455
- package/dist/hooks/use-agent-chat.js.map +0 -1
- package/dist/hooks/use-file-mention-menu.d.ts +0 -22
- package/dist/hooks/use-file-mention-menu.d.ts.map +0 -1
- package/dist/hooks/use-file-mention-menu.js +0 -137
- package/dist/hooks/use-file-mention-menu.js.map +0 -1
- package/dist/hooks/use-file-picker.d.ts +0 -21
- package/dist/hooks/use-file-picker.d.ts.map +0 -1
- package/dist/hooks/use-file-picker.js +0 -145
- package/dist/hooks/use-file-picker.js.map +0 -1
- package/dist/hooks/use-model-picker.d.ts +0 -23
- package/dist/hooks/use-model-picker.d.ts.map +0 -1
- package/dist/hooks/use-model-picker.js +0 -151
- package/dist/hooks/use-model-picker.js.map +0 -1
- package/dist/hooks/use-slash-command-menu.d.ts +0 -19
- package/dist/hooks/use-slash-command-menu.d.ts.map +0 -1
- package/dist/hooks/use-slash-command-menu.js +0 -101
- package/dist/hooks/use-slash-command-menu.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -6
- package/dist/index.js.map +0 -1
- package/dist/run-cli-app.d.ts +0 -2
- package/dist/run-cli-app.d.ts.map +0 -1
- package/dist/run-cli-app.js +0 -41
- package/dist/run-cli-app.js.map +0 -1
- package/dist/runtime/clipboard.d.ts +0 -10
- package/dist/runtime/clipboard.d.ts.map +0 -1
- package/dist/runtime/clipboard.js +0 -64
- package/dist/runtime/clipboard.js.map +0 -1
- package/dist/runtime/exit.d.ts +0 -7
- package/dist/runtime/exit.d.ts.map +0 -1
- package/dist/runtime/exit.js +0 -85
- package/dist/runtime/exit.js.map +0 -1
- package/dist/runtime/runtime-support.d.ts +0 -4
- package/dist/runtime/runtime-support.d.ts.map +0 -1
- package/dist/runtime/runtime-support.js +0 -19
- package/dist/runtime/runtime-support.js.map +0 -1
- package/dist/runtime/terminal-theme.d.ts +0 -25
- package/dist/runtime/terminal-theme.d.ts.map +0 -1
- package/dist/runtime/terminal-theme.js +0 -148
- package/dist/runtime/terminal-theme.js.map +0 -1
- package/dist/types/chat.d.ts +0 -29
- package/dist/types/chat.d.ts.map +0 -1
- package/dist/types/chat.js +0 -1
- package/dist/types/chat.js.map +0 -1
- package/dist/types/message-content.d.ts +0 -38
- package/dist/types/message-content.d.ts.map +0 -1
- package/dist/types/message-content.js +0 -1
- package/dist/types/message-content.js.map +0 -1
- package/dist/ui/open-code-theme.d.ts +0 -58
- package/dist/ui/open-code-theme.d.ts.map +0 -1
- package/dist/ui/open-code-theme.js +0 -113
- package/dist/ui/open-code-theme.js.map +0 -1
- package/dist/ui/opencode-markdown.d.ts +0 -7
- package/dist/ui/opencode-markdown.d.ts.map +0 -1
- package/dist/ui/opencode-markdown.js +0 -169
- package/dist/ui/opencode-markdown.js.map +0 -1
- package/dist/ui/theme.d.ts +0 -68
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js +0 -80
- package/dist/ui/theme.js.map +0 -1
- package/dist/utils/time.d.ts +0 -2
- package/dist/utils/time.d.ts.map +0 -1
- package/dist/utils/time.js +0 -7
- package/dist/utils/time.js.map +0 -1
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
import { resolve as resolvePath } from 'node:path';
|
|
2
|
+
import type {
|
|
3
|
+
AgentContextUsageEvent,
|
|
4
|
+
AgentEventHandlers,
|
|
5
|
+
AgentLoopEvent,
|
|
6
|
+
AgentRunResult,
|
|
7
|
+
AgentStepEvent,
|
|
8
|
+
AgentStopEvent,
|
|
9
|
+
AgentTextDeltaEvent,
|
|
10
|
+
AgentToolConfirmEvent,
|
|
11
|
+
AgentToolResultEvent,
|
|
12
|
+
AgentToolStreamEvent,
|
|
13
|
+
AgentToolUseEvent,
|
|
14
|
+
AgentUsageEvent,
|
|
15
|
+
} from './types';
|
|
16
|
+
import type { AgentModelOption, AgentModelSwitchResult } from './model-types';
|
|
17
|
+
import { resolveToolConfirmDecision } from './tool-confirmation';
|
|
18
|
+
import {
|
|
19
|
+
type AgentAppContextUsageLike,
|
|
20
|
+
type AgentAppUsageLike,
|
|
21
|
+
type AgentAppRunResultLike,
|
|
22
|
+
type AgentAppServiceLike,
|
|
23
|
+
type AgentAppStoreLike,
|
|
24
|
+
type AgentV4MessageLike,
|
|
25
|
+
type CliEventEnvelopeLike,
|
|
26
|
+
getSourceModules,
|
|
27
|
+
resolveWorkspaceRoot,
|
|
28
|
+
type SourceModules,
|
|
29
|
+
type StatelessAgentLike,
|
|
30
|
+
type ToolConfirmEventLike,
|
|
31
|
+
} from './source-modules';
|
|
32
|
+
import { ToolCallBuffer } from './tool-call-buffer';
|
|
33
|
+
import { buildSystemPrompt } from '../../../../src/agent/prompts/system';
|
|
34
|
+
import { resolveRenxDatabasePath, resolveRenxTaskDir } from '../../../../src/config/paths';
|
|
35
|
+
import type { AttachmentModelCapabilities } from '../../files/attachment-capabilities';
|
|
36
|
+
import { resolveAttachmentModelCapabilities } from '../../files/attachment-capabilities';
|
|
37
|
+
import type { MessageContent } from '../../types/message-content';
|
|
38
|
+
|
|
39
|
+
type RuntimeCore = {
|
|
40
|
+
modelId: string;
|
|
41
|
+
modelLabel: string;
|
|
42
|
+
maxSteps: number;
|
|
43
|
+
conversationId: string;
|
|
44
|
+
workspaceRoot: string;
|
|
45
|
+
parentTools: Array<{ type: string; function: { name?: string } }>;
|
|
46
|
+
agent: StatelessAgentLike;
|
|
47
|
+
appService: AgentAppServiceLike;
|
|
48
|
+
appStore: AgentAppStoreLike;
|
|
49
|
+
logger?: {
|
|
50
|
+
close?: () => void | Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
modules: SourceModules;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type RunAgentPromptOptions = {
|
|
56
|
+
abortSignal?: AbortSignal;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let runtimePromise: Promise<RuntimeCore> | null = null;
|
|
60
|
+
let initializing = false;
|
|
61
|
+
const readPreferredModelIdFromEnv = (): string | undefined => {
|
|
62
|
+
return process.env.AGENT_MODEL?.trim() || undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let preferredModelId = readPreferredModelIdFromEnv();
|
|
66
|
+
|
|
67
|
+
const DEFAULT_MODEL = 'qwen3.5-plus';
|
|
68
|
+
const DEFAULT_MAX_STEPS = 10000;
|
|
69
|
+
const DEFAULT_MAX_RETRY_COUNT = 10;
|
|
70
|
+
const PARENT_HIDDEN_TOOL_NAMES = new Set(['file_history_list', 'file_history_restore']);
|
|
71
|
+
|
|
72
|
+
const parsePositiveInt = (raw: string | undefined, fallback: number): number => {
|
|
73
|
+
if (!raw || raw.trim().length === 0) {
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
const value = Number.parseInt(raw, 10);
|
|
77
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const resolvePromptCacheConfig = (
|
|
84
|
+
conversationId: string
|
|
85
|
+
): Record<string, unknown> | undefined => {
|
|
86
|
+
const rawPromptCacheKey = process.env.AGENT_PROMPT_CACHE_KEY?.trim();
|
|
87
|
+
const promptCacheRetention = process.env.AGENT_PROMPT_CACHE_RETENTION?.trim();
|
|
88
|
+
const promptCacheKey = rawPromptCacheKey?.replace(/\{conversationId\}/g, conversationId);
|
|
89
|
+
|
|
90
|
+
const config: Record<string, unknown> = {};
|
|
91
|
+
if (promptCacheKey) {
|
|
92
|
+
config.prompt_cache_key = promptCacheKey;
|
|
93
|
+
}
|
|
94
|
+
if (promptCacheRetention) {
|
|
95
|
+
config.prompt_cache_retention = promptCacheRetention;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const resolveModelId = (modules: SourceModules, requested?: string): string => {
|
|
102
|
+
const ids = modules.ProviderRegistry.getModelIds();
|
|
103
|
+
const normalized = requested?.trim();
|
|
104
|
+
if (normalized && ids.includes(normalized)) {
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
if (ids.includes(DEFAULT_MODEL)) {
|
|
108
|
+
return DEFAULT_MODEL;
|
|
109
|
+
}
|
|
110
|
+
const fallback = ids[0];
|
|
111
|
+
if (!fallback) {
|
|
112
|
+
throw new Error('No models are registered in ProviderRegistry.');
|
|
113
|
+
}
|
|
114
|
+
return fallback;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const requireModelApiKey = (modules: SourceModules, modelId: string) => {
|
|
118
|
+
const modelConfig = modules.ProviderRegistry.getModelConfig(modelId);
|
|
119
|
+
if (!process.env[modelConfig.envApiKey]) {
|
|
120
|
+
throw new Error(`Missing env ${modelConfig.envApiKey} for model ${modelId}.`);
|
|
121
|
+
}
|
|
122
|
+
return modelConfig;
|
|
123
|
+
};
|
|
124
|
+
const resolveConversationId = () => {
|
|
125
|
+
const fromEnv = process.env.AGENT_CONVERSATION_ID?.trim() || process.env.AGENT_SESSION_ID?.trim();
|
|
126
|
+
if (fromEnv) {
|
|
127
|
+
return fromEnv;
|
|
128
|
+
}
|
|
129
|
+
return `opentui-${Date.now()}`;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const resolveDbPath = (): string => {
|
|
133
|
+
return resolveRenxDatabasePath(process.env);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const buildCliLoggerEnv = (env: NodeJS.ProcessEnv): NodeJS.ProcessEnv => ({
|
|
137
|
+
...env,
|
|
138
|
+
AGENT_LOG_CONSOLE: 'false',
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const safeInvoke = (fn: (() => void) | undefined): void => {
|
|
142
|
+
if (!fn) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
fn();
|
|
147
|
+
} catch {
|
|
148
|
+
// Intentionally empty
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const asRecord = (value: unknown): Record<string, unknown> => {
|
|
153
|
+
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const readString = (value: unknown): string | undefined => {
|
|
157
|
+
return typeof value === 'string' ? value : undefined;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const readNumber = (value: unknown): number | undefined => {
|
|
161
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const readBoolean = (value: unknown): boolean | undefined => {
|
|
165
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const toUsageEventFromApp = (usage: AgentAppUsageLike): AgentUsageEvent => {
|
|
169
|
+
return {
|
|
170
|
+
promptTokens: usage.usage.prompt_tokens,
|
|
171
|
+
completionTokens: usage.usage.completion_tokens,
|
|
172
|
+
totalTokens: usage.usage.total_tokens,
|
|
173
|
+
cumulativePromptTokens: usage.cumulativeUsage.prompt_tokens,
|
|
174
|
+
cumulativeCompletionTokens: usage.cumulativeUsage.completion_tokens,
|
|
175
|
+
cumulativeTotalTokens: usage.cumulativeUsage.total_tokens,
|
|
176
|
+
contextTokens:
|
|
177
|
+
typeof usage.contextTokens === 'number' && Number.isFinite(usage.contextTokens)
|
|
178
|
+
? Math.max(0, usage.contextTokens)
|
|
179
|
+
: undefined,
|
|
180
|
+
contextLimit: usage.contextLimitTokens,
|
|
181
|
+
contextUsagePercent:
|
|
182
|
+
typeof usage.contextUsagePercent === 'number' && Number.isFinite(usage.contextUsagePercent)
|
|
183
|
+
? Math.max(0, usage.contextUsagePercent)
|
|
184
|
+
: undefined,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const toContextUsageEventFromApp = (
|
|
189
|
+
usage: AgentAppContextUsageLike
|
|
190
|
+
): AgentContextUsageEvent | null => {
|
|
191
|
+
if (
|
|
192
|
+
typeof usage.stepIndex !== 'number' ||
|
|
193
|
+
!Number.isFinite(usage.stepIndex) ||
|
|
194
|
+
typeof usage.messageCount !== 'number' ||
|
|
195
|
+
!Number.isFinite(usage.messageCount) ||
|
|
196
|
+
typeof usage.contextTokens !== 'number' ||
|
|
197
|
+
!Number.isFinite(usage.contextTokens) ||
|
|
198
|
+
typeof usage.contextLimitTokens !== 'number' ||
|
|
199
|
+
!Number.isFinite(usage.contextLimitTokens) ||
|
|
200
|
+
typeof usage.contextUsagePercent !== 'number' ||
|
|
201
|
+
!Number.isFinite(usage.contextUsagePercent)
|
|
202
|
+
) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
stepIndex: Math.max(0, usage.stepIndex),
|
|
208
|
+
messageCount: Math.max(0, usage.messageCount),
|
|
209
|
+
contextTokens: Math.max(0, usage.contextTokens),
|
|
210
|
+
contextLimit: Math.max(0, usage.contextLimitTokens),
|
|
211
|
+
contextUsagePercent: Math.max(0, usage.contextUsagePercent),
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const toJsonString = (value: unknown): string => {
|
|
216
|
+
if (typeof value === 'string') {
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
return JSON.stringify(value);
|
|
221
|
+
} catch {
|
|
222
|
+
return String(value);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const parseJsonObject = (raw: string): Record<string, unknown> => {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(raw);
|
|
229
|
+
if (parsed && typeof parsed === 'object') {
|
|
230
|
+
return parsed as Record<string, unknown>;
|
|
231
|
+
}
|
|
232
|
+
return { value: parsed };
|
|
233
|
+
} catch {
|
|
234
|
+
return { raw };
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const toTextDeltaEvent = (
|
|
239
|
+
payload: Record<string, unknown>,
|
|
240
|
+
isReasoning: boolean
|
|
241
|
+
): AgentTextDeltaEvent => {
|
|
242
|
+
return {
|
|
243
|
+
text: readString(payload.content) ?? readString(payload.reasoningContent) ?? '',
|
|
244
|
+
isReasoning,
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const toStepEvent = (
|
|
249
|
+
payload: Record<string, unknown>,
|
|
250
|
+
finishReason: string,
|
|
251
|
+
toolCallsCount = 0
|
|
252
|
+
): AgentStepEvent => {
|
|
253
|
+
return {
|
|
254
|
+
stepIndex: readNumber(payload.stepIndex) ?? 0,
|
|
255
|
+
finishReason,
|
|
256
|
+
toolCallsCount,
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const toLoopEvent = (stepIndex: number): AgentLoopEvent => {
|
|
261
|
+
return {
|
|
262
|
+
loopIndex: stepIndex,
|
|
263
|
+
steps: stepIndex,
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const toToolStreamEvent = (
|
|
268
|
+
envelope: CliEventEnvelopeLike,
|
|
269
|
+
sequenceByToolCallId: Map<string, number>
|
|
270
|
+
): AgentToolStreamEvent => {
|
|
271
|
+
const payload = asRecord(envelope.data);
|
|
272
|
+
const toolCallId = readString(payload.toolCallId) ?? 'unknown';
|
|
273
|
+
const previousSequence = sequenceByToolCallId.get(toolCallId) ?? 0;
|
|
274
|
+
const sequence = previousSequence + 1;
|
|
275
|
+
sequenceByToolCallId.set(toolCallId, sequence);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
toolCallId,
|
|
279
|
+
toolName: readString(payload.toolName) ?? 'tool',
|
|
280
|
+
type: readString(payload.chunkType) ?? readString(payload.type) ?? 'stdout',
|
|
281
|
+
sequence,
|
|
282
|
+
timestamp: envelope.createdAt,
|
|
283
|
+
content: readString(payload.chunk) ?? readString(payload.content),
|
|
284
|
+
data: payload,
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const toToolResultEvent = (
|
|
289
|
+
payload: Record<string, unknown>,
|
|
290
|
+
toolCallsById: Map<string, AgentToolUseEvent>
|
|
291
|
+
): AgentToolResultEvent => {
|
|
292
|
+
const toolCallId =
|
|
293
|
+
readString(payload.tool_call_id) ?? readString(payload.toolCallId) ?? 'unknown';
|
|
294
|
+
const metadata = asRecord(payload.metadata);
|
|
295
|
+
const toolResult = asRecord(metadata.toolResult);
|
|
296
|
+
const error = asRecord(toolResult.error);
|
|
297
|
+
const summary = readString(toolResult.summary);
|
|
298
|
+
const explicitOutput = readString(toolResult.output);
|
|
299
|
+
const content = readString(payload.content);
|
|
300
|
+
const output =
|
|
301
|
+
explicitOutput !== undefined ? explicitOutput : content !== summary ? content : undefined;
|
|
302
|
+
const toolCall =
|
|
303
|
+
toolCallsById.get(toolCallId) ??
|
|
304
|
+
({
|
|
305
|
+
id: toolCallId,
|
|
306
|
+
function: { name: 'tool', arguments: '{}' },
|
|
307
|
+
} as AgentToolUseEvent);
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
toolCall,
|
|
311
|
+
result: {
|
|
312
|
+
success: readBoolean(toolResult.success) ?? true,
|
|
313
|
+
error: readString(error.message),
|
|
314
|
+
data: {
|
|
315
|
+
...(summary !== undefined ? { summary } : {}),
|
|
316
|
+
...(output !== undefined ? { output } : {}),
|
|
317
|
+
...(toolResult.payload !== undefined ? { payload: toolResult.payload } : {}),
|
|
318
|
+
...(toolResult.metadata !== undefined ? { metadata: toolResult.metadata } : {}),
|
|
319
|
+
},
|
|
320
|
+
raw: payload,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const extractAssistantText = (result: AgentAppRunResultLike): string => {
|
|
326
|
+
for (let i = result.messages.length - 1; i >= 0; i -= 1) {
|
|
327
|
+
const message = result.messages[i];
|
|
328
|
+
if (!message) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (message.role !== 'assistant') {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (typeof message.content === 'string') {
|
|
335
|
+
return message.content;
|
|
336
|
+
}
|
|
337
|
+
return toJsonString(message.content);
|
|
338
|
+
}
|
|
339
|
+
return '';
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const createRuntime = async (): Promise<RuntimeCore> => {
|
|
343
|
+
const modules = await getSourceModules();
|
|
344
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
345
|
+
await modules.loadEnvFiles(workspaceRoot);
|
|
346
|
+
modules.loadConfigToEnv({ projectRoot: workspaceRoot });
|
|
347
|
+
const conversationId = resolveConversationId();
|
|
348
|
+
|
|
349
|
+
const modelId = resolveModelId(modules, preferredModelId);
|
|
350
|
+
const modelConfig = requireModelApiKey(modules, modelId);
|
|
351
|
+
const maxSteps = parsePositiveInt(process.env.AGENT_MAX_STEPS, DEFAULT_MAX_STEPS);
|
|
352
|
+
const coreLogger = modules.createLoggerFromEnv(buildCliLoggerEnv(process.env), workspaceRoot);
|
|
353
|
+
const agentLogger = modules.createAgentLoggerAdapter(asRecord(coreLogger), {
|
|
354
|
+
runtime: 'opentui-agent-cli',
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const provider = modules.ProviderRegistry.createFromEnv(modelId, {
|
|
358
|
+
logger: asRecord(coreLogger),
|
|
359
|
+
});
|
|
360
|
+
const toolManager = new modules.DefaultToolManager();
|
|
361
|
+
const taskStore = new modules.TaskStore({
|
|
362
|
+
baseDir: resolveRenxTaskDir(process.env),
|
|
363
|
+
});
|
|
364
|
+
toolManager.registerTool(new modules.BashTool());
|
|
365
|
+
toolManager.registerTool(
|
|
366
|
+
new modules.WriteFileTool({
|
|
367
|
+
allowedDirectories: [workspaceRoot],
|
|
368
|
+
})
|
|
369
|
+
);
|
|
370
|
+
toolManager.registerTool(
|
|
371
|
+
new modules.FileReadTool({
|
|
372
|
+
allowedDirectories: [workspaceRoot],
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
toolManager.registerTool(
|
|
376
|
+
new modules.FileEditTool({
|
|
377
|
+
allowedDirectories: [workspaceRoot],
|
|
378
|
+
})
|
|
379
|
+
);
|
|
380
|
+
toolManager.registerTool(
|
|
381
|
+
new modules.FileHistoryListTool({
|
|
382
|
+
allowedDirectories: [workspaceRoot],
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
toolManager.registerTool(
|
|
386
|
+
new modules.FileHistoryRestoreTool({
|
|
387
|
+
allowedDirectories: [workspaceRoot],
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
toolManager.registerTool(
|
|
391
|
+
new modules.GlobTool({
|
|
392
|
+
allowedDirectories: [workspaceRoot],
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
toolManager.registerTool(
|
|
396
|
+
new modules.GrepTool({
|
|
397
|
+
allowedDirectories: [workspaceRoot],
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
toolManager.registerTool(
|
|
401
|
+
new modules.SkillTool({
|
|
402
|
+
loaderOptions: {
|
|
403
|
+
workingDir: workspaceRoot,
|
|
404
|
+
},
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const agent = new modules.StatelessAgent(provider, toolManager, {
|
|
409
|
+
maxRetryCount: parsePositiveInt(process.env.AGENT_MAX_RETRY_COUNT, DEFAULT_MAX_RETRY_COUNT),
|
|
410
|
+
enableCompaction: true,
|
|
411
|
+
logger: agentLogger,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const appStore = modules.createSqliteAgentAppStore(resolveDbPath());
|
|
415
|
+
const preparableStore = appStore as AgentAppStoreLike & {
|
|
416
|
+
prepare?: () => Promise<void>;
|
|
417
|
+
};
|
|
418
|
+
if (typeof preparableStore.prepare === 'function') {
|
|
419
|
+
await preparableStore.prepare();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const appService = new modules.AgentAppService({
|
|
423
|
+
agent,
|
|
424
|
+
executionStore: appStore,
|
|
425
|
+
eventStore: appStore,
|
|
426
|
+
messageStore: appStore,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const collectToolSchemas = () =>
|
|
430
|
+
toolManager
|
|
431
|
+
.getTools()
|
|
432
|
+
.map(tool => {
|
|
433
|
+
const schema = tool?.toToolSchema?.();
|
|
434
|
+
if (!schema || typeof schema !== 'object') {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return schema;
|
|
438
|
+
})
|
|
439
|
+
.filter((schema): schema is { type: string; function: { name?: string } } => Boolean(schema));
|
|
440
|
+
|
|
441
|
+
const resolveToolSchemas = (allowedTools?: string[], hiddenToolNames?: Set<string>) => {
|
|
442
|
+
const allSchemas = toolManager
|
|
443
|
+
? collectToolSchemas().filter(schema => {
|
|
444
|
+
const name = schema.function?.name;
|
|
445
|
+
if (typeof name !== 'string') {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return !hiddenToolNames?.has(name);
|
|
449
|
+
})
|
|
450
|
+
: [];
|
|
451
|
+
|
|
452
|
+
if (!allowedTools || allowedTools.length === 0) {
|
|
453
|
+
return allSchemas;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const allowed = new Set(allowedTools);
|
|
457
|
+
return allSchemas.filter(schema => {
|
|
458
|
+
const name = schema.function?.name;
|
|
459
|
+
return typeof name === 'string' && allowed.has(name);
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const resolveSubagentToolSchemas = (allowedTools?: string[]) =>
|
|
464
|
+
resolveToolSchemas(allowedTools, undefined);
|
|
465
|
+
|
|
466
|
+
const taskRunner = new modules.RealSubagentRunnerAdapter({
|
|
467
|
+
store: taskStore,
|
|
468
|
+
appService,
|
|
469
|
+
resolveTools: resolveSubagentToolSchemas,
|
|
470
|
+
// Use provider-level model name (e.g. MiniMax-M2.5), not registry id (e.g. minimax-2.5),
|
|
471
|
+
// so subagent requests match the same backend model as parent agent.
|
|
472
|
+
resolveModelId: () => modelConfig.model || modelId,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
toolManager.registerTool(
|
|
476
|
+
new modules.TaskCreateTool({
|
|
477
|
+
store: taskStore,
|
|
478
|
+
defaultNamespace: conversationId,
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
toolManager.registerTool(
|
|
482
|
+
new modules.TaskGetTool({
|
|
483
|
+
store: taskStore,
|
|
484
|
+
defaultNamespace: conversationId,
|
|
485
|
+
})
|
|
486
|
+
);
|
|
487
|
+
toolManager.registerTool(
|
|
488
|
+
new modules.TaskListTool({
|
|
489
|
+
store: taskStore,
|
|
490
|
+
defaultNamespace: conversationId,
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
toolManager.registerTool(
|
|
494
|
+
new modules.TaskUpdateTool({
|
|
495
|
+
store: taskStore,
|
|
496
|
+
defaultNamespace: conversationId,
|
|
497
|
+
})
|
|
498
|
+
);
|
|
499
|
+
toolManager.registerTool(
|
|
500
|
+
new modules.TaskTool({
|
|
501
|
+
store: taskStore,
|
|
502
|
+
runner: taskRunner,
|
|
503
|
+
defaultNamespace: conversationId,
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
toolManager.registerTool(
|
|
507
|
+
new modules.TaskStopTool({
|
|
508
|
+
store: taskStore,
|
|
509
|
+
runner: taskRunner,
|
|
510
|
+
defaultNamespace: conversationId,
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
toolManager.registerTool(
|
|
514
|
+
new modules.TaskOutputTool({
|
|
515
|
+
store: taskStore,
|
|
516
|
+
runner: taskRunner,
|
|
517
|
+
defaultNamespace: conversationId,
|
|
518
|
+
})
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const parentTools = resolveToolSchemas(undefined, PARENT_HIDDEN_TOOL_NAMES);
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
modules,
|
|
525
|
+
modelId,
|
|
526
|
+
modelLabel: modelConfig.name,
|
|
527
|
+
maxSteps,
|
|
528
|
+
conversationId,
|
|
529
|
+
workspaceRoot,
|
|
530
|
+
parentTools,
|
|
531
|
+
agent,
|
|
532
|
+
appService,
|
|
533
|
+
appStore,
|
|
534
|
+
logger:
|
|
535
|
+
coreLogger && typeof coreLogger === 'object'
|
|
536
|
+
? (coreLogger as { close?: () => void | Promise<void> })
|
|
537
|
+
: undefined,
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const getRuntime = async (): Promise<RuntimeCore> => {
|
|
542
|
+
// 双重检查锁定模式
|
|
543
|
+
if (runtimePromise) {
|
|
544
|
+
return runtimePromise;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (initializing) {
|
|
548
|
+
// 等待初始化完成
|
|
549
|
+
while (initializing) {
|
|
550
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
551
|
+
}
|
|
552
|
+
// 初始化完成后,runtimePromise应该已经设置
|
|
553
|
+
if (runtimePromise) {
|
|
554
|
+
return runtimePromise;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 开始初始化
|
|
559
|
+
initializing = true;
|
|
560
|
+
try {
|
|
561
|
+
const promise = createRuntime();
|
|
562
|
+
runtimePromise = promise;
|
|
563
|
+
|
|
564
|
+
// 如果初始化失败,允许重新尝试
|
|
565
|
+
promise.catch(() => {
|
|
566
|
+
runtimePromise = null;
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
return promise;
|
|
570
|
+
} finally {
|
|
571
|
+
initializing = false;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const disposeRuntimeInstance = async () => {
|
|
576
|
+
const runtime = await runtimePromise;
|
|
577
|
+
runtimePromise = null;
|
|
578
|
+
if (!runtime) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
await runtime.logger?.close?.();
|
|
582
|
+
await runtime.appStore.close();
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
export const runAgentPrompt = async (
|
|
586
|
+
prompt: MessageContent,
|
|
587
|
+
handlers: AgentEventHandlers,
|
|
588
|
+
options: RunAgentPromptOptions = {}
|
|
589
|
+
): Promise<AgentRunResult> => {
|
|
590
|
+
const runtime = await getRuntime();
|
|
591
|
+
const startedAt = Date.now();
|
|
592
|
+
const streamedState = {
|
|
593
|
+
text: '',
|
|
594
|
+
latestErrorMessage: undefined as string | undefined,
|
|
595
|
+
stopEmitted: false,
|
|
596
|
+
lastLoopStep: 0,
|
|
597
|
+
};
|
|
598
|
+
const toolStreamSequenceById = new Map<string, number>();
|
|
599
|
+
const toolCallsById = new Map<string, AgentToolUseEvent>();
|
|
600
|
+
const toolCallBuffer = new ToolCallBuffer();
|
|
601
|
+
let latestUsageEvent: AgentUsageEvent | undefined;
|
|
602
|
+
let currentAction = 'llm';
|
|
603
|
+
|
|
604
|
+
const onToolConfirm = (event: ToolConfirmEventLike): void => {
|
|
605
|
+
const rawArgs = parseJsonObject(event.arguments);
|
|
606
|
+
const toolConfirmEvent: AgentToolConfirmEvent = {
|
|
607
|
+
toolCallId: event.toolCallId,
|
|
608
|
+
toolName: event.toolName,
|
|
609
|
+
args: rawArgs,
|
|
610
|
+
rawArgs,
|
|
611
|
+
reason: readString(event.reason),
|
|
612
|
+
metadata: event.metadata,
|
|
613
|
+
};
|
|
614
|
+
safeInvoke(() => handlers.onToolConfirm?.(toolConfirmEvent));
|
|
615
|
+
|
|
616
|
+
void resolveToolConfirmDecision(toolConfirmEvent, handlers)
|
|
617
|
+
.then(decision => {
|
|
618
|
+
event.resolve(decision);
|
|
619
|
+
})
|
|
620
|
+
.catch(error => {
|
|
621
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
622
|
+
event.resolve({
|
|
623
|
+
approved: false,
|
|
624
|
+
message: message || 'Tool confirmation failed.',
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
runtime.agent.on('tool_confirm', onToolConfirm);
|
|
630
|
+
|
|
631
|
+
let result: AgentAppRunResultLike;
|
|
632
|
+
try {
|
|
633
|
+
const historyMessages = await runtime.appService.listContextMessages(runtime.conversationId);
|
|
634
|
+
result = await runtime.appService.runForeground(
|
|
635
|
+
{
|
|
636
|
+
conversationId: runtime.conversationId,
|
|
637
|
+
userInput: prompt,
|
|
638
|
+
historyMessages: historyMessages as AgentV4MessageLike[],
|
|
639
|
+
systemPrompt: buildSystemPrompt({ directory: runtime.workspaceRoot }),
|
|
640
|
+
tools: runtime.parentTools,
|
|
641
|
+
config: resolvePromptCacheConfig(runtime.conversationId),
|
|
642
|
+
maxSteps: runtime.maxSteps,
|
|
643
|
+
abortSignal: options.abortSignal,
|
|
644
|
+
modelLabel: runtime.modelLabel,
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
onError: (error: unknown) => {
|
|
648
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
649
|
+
if (message) {
|
|
650
|
+
streamedState.latestErrorMessage = message;
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
onContextUsage: usage => {
|
|
654
|
+
const contextUsageEvent = toContextUsageEventFromApp(usage);
|
|
655
|
+
if (!contextUsageEvent) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
safeInvoke(() => handlers.onContextUsage?.(contextUsageEvent));
|
|
659
|
+
},
|
|
660
|
+
onUsage: usage => {
|
|
661
|
+
const usageEvent = toUsageEventFromApp(usage);
|
|
662
|
+
latestUsageEvent = usageEvent;
|
|
663
|
+
safeInvoke(() => handlers.onUsage?.(usageEvent));
|
|
664
|
+
},
|
|
665
|
+
onEvent: async envelope => {
|
|
666
|
+
const payload = asRecord(envelope.data);
|
|
667
|
+
switch (envelope.eventType) {
|
|
668
|
+
case 'chunk': {
|
|
669
|
+
const event = toTextDeltaEvent(payload, false);
|
|
670
|
+
if (event.text.length > 0) {
|
|
671
|
+
streamedState.text += event.text;
|
|
672
|
+
safeInvoke(() => handlers.onTextDelta?.(event));
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
case 'reasoning_chunk': {
|
|
677
|
+
const event = toTextDeltaEvent(payload, true);
|
|
678
|
+
if (event.text.length > 0) {
|
|
679
|
+
safeInvoke(() => handlers.onTextDelta?.(event));
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case 'tool_stream': {
|
|
684
|
+
const toolStreamEvent = toToolStreamEvent(envelope, toolStreamSequenceById);
|
|
685
|
+
toolCallBuffer.ensureEmitted(toolStreamEvent.toolCallId, toolCall => {
|
|
686
|
+
safeInvoke(() => handlers.onToolUse?.(toolCall));
|
|
687
|
+
});
|
|
688
|
+
safeInvoke(() => handlers.onToolStream?.(toolStreamEvent));
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
case 'tool_call': {
|
|
692
|
+
const rawToolCalls = payload.toolCalls;
|
|
693
|
+
if (Array.isArray(rawToolCalls) && rawToolCalls.length > 0) {
|
|
694
|
+
for (const item of rawToolCalls) {
|
|
695
|
+
const toolCall = asRecord(item) as AgentToolUseEvent;
|
|
696
|
+
const toolCallId = readString(asRecord(toolCall).id);
|
|
697
|
+
if (toolCallId) {
|
|
698
|
+
toolCallsById.set(toolCallId, toolCall);
|
|
699
|
+
}
|
|
700
|
+
toolCallBuffer.register(
|
|
701
|
+
toolCall,
|
|
702
|
+
event => {
|
|
703
|
+
safeInvoke(() => handlers.onToolUse?.(event));
|
|
704
|
+
},
|
|
705
|
+
currentAction === 'tool'
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
const toolCall = payload as AgentToolUseEvent;
|
|
710
|
+
const toolCallId = readString(asRecord(toolCall).id);
|
|
711
|
+
if (toolCallId) {
|
|
712
|
+
toolCallsById.set(toolCallId, toolCall);
|
|
713
|
+
}
|
|
714
|
+
toolCallBuffer.register(
|
|
715
|
+
toolCall,
|
|
716
|
+
event => {
|
|
717
|
+
safeInvoke(() => handlers.onToolUse?.(event));
|
|
718
|
+
},
|
|
719
|
+
currentAction === 'tool'
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
case 'tool_result': {
|
|
725
|
+
const toolCallId = readString(payload.tool_call_id) ?? readString(payload.toolCallId);
|
|
726
|
+
toolCallBuffer.ensureEmitted(toolCallId, toolCall => {
|
|
727
|
+
safeInvoke(() => handlers.onToolUse?.(toolCall));
|
|
728
|
+
});
|
|
729
|
+
const toolResultEvent = toToolResultEvent(payload, toolCallsById);
|
|
730
|
+
safeInvoke(() => handlers.onToolResult?.(toolResultEvent));
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
case 'progress': {
|
|
734
|
+
const nextAction = readString(payload.currentAction) ?? 'progress';
|
|
735
|
+
currentAction = nextAction;
|
|
736
|
+
const stepEvent = toStepEvent(payload, nextAction, 0);
|
|
737
|
+
safeInvoke(() => handlers.onStep?.(stepEvent));
|
|
738
|
+
|
|
739
|
+
if (nextAction === 'tool') {
|
|
740
|
+
toolCallBuffer.flush(toolCall => {
|
|
741
|
+
safeInvoke(() => handlers.onToolUse?.(toolCall));
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (nextAction === 'llm' && stepEvent.stepIndex > streamedState.lastLoopStep) {
|
|
746
|
+
streamedState.lastLoopStep = stepEvent.stepIndex;
|
|
747
|
+
const loopEvent = toLoopEvent(stepEvent.stepIndex);
|
|
748
|
+
safeInvoke(() => handlers.onLoop?.(loopEvent));
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
case 'checkpoint': {
|
|
753
|
+
const stepEvent = toStepEvent(payload, 'checkpoint', 0);
|
|
754
|
+
safeInvoke(() => handlers.onStep?.(stepEvent));
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
case 'done': {
|
|
758
|
+
safeInvoke(() => handlers.onTextComplete?.(streamedState.text));
|
|
759
|
+
const stopEvent: AgentStopEvent = {
|
|
760
|
+
reason: readString(payload.finishReason) ?? 'stop',
|
|
761
|
+
};
|
|
762
|
+
safeInvoke(() => handlers.onStop?.(stopEvent));
|
|
763
|
+
streamedState.stopEmitted = true;
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
case 'error': {
|
|
767
|
+
const message = readString(payload.message);
|
|
768
|
+
streamedState.latestErrorMessage = message;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
default:
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
}
|
|
776
|
+
);
|
|
777
|
+
} finally {
|
|
778
|
+
runtime.agent.off('tool_confirm', onToolConfirm);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (!streamedState.stopEmitted) {
|
|
782
|
+
safeInvoke(() => handlers.onTextComplete?.(streamedState.text));
|
|
783
|
+
safeInvoke(() =>
|
|
784
|
+
handlers.onStop?.({
|
|
785
|
+
reason: result.finishReason,
|
|
786
|
+
message: result.finishReason === 'error' ? result.run.errorMessage : undefined,
|
|
787
|
+
})
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const finalText = streamedState.text || extractAssistantText(result);
|
|
792
|
+
const completionMessage =
|
|
793
|
+
result.finishReason === 'error'
|
|
794
|
+
? (streamedState.latestErrorMessage ?? result.run.errorMessage)
|
|
795
|
+
: undefined;
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
text: finalText,
|
|
799
|
+
completionReason: result.finishReason,
|
|
800
|
+
completionMessage,
|
|
801
|
+
durationSeconds: (Date.now() - startedAt) / 1000,
|
|
802
|
+
modelLabel: runtime.modelLabel,
|
|
803
|
+
usage: latestUsageEvent,
|
|
804
|
+
};
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
export const getAgentModelLabel = async (): Promise<string> => {
|
|
808
|
+
const runtime = await getRuntime();
|
|
809
|
+
return runtime.modelLabel;
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
export const getAgentModelAttachmentCapabilities =
|
|
813
|
+
async (): Promise<AttachmentModelCapabilities> => {
|
|
814
|
+
const modules = await getSourceModules();
|
|
815
|
+
const modelId = await getAgentModelId();
|
|
816
|
+
const config = modules.ProviderRegistry.getModelConfig(modelId);
|
|
817
|
+
return resolveAttachmentModelCapabilities(config);
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
export const getAgentModelId = async (): Promise<string> => {
|
|
821
|
+
if (runtimePromise) {
|
|
822
|
+
const runtime = await runtimePromise;
|
|
823
|
+
if (runtime) {
|
|
824
|
+
return runtime.modelId;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const modules = await getSourceModules();
|
|
828
|
+
return resolveModelId(modules, preferredModelId);
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
export const listAgentModels = async (): Promise<AgentModelOption[]> => {
|
|
832
|
+
const modules = await getSourceModules();
|
|
833
|
+
const currentModelId = await getAgentModelId();
|
|
834
|
+
|
|
835
|
+
return modules.ProviderRegistry.getModelIds()
|
|
836
|
+
.map(id => {
|
|
837
|
+
const config = modules.ProviderRegistry.getModelConfig(id);
|
|
838
|
+
return {
|
|
839
|
+
id,
|
|
840
|
+
name: config.name,
|
|
841
|
+
provider: config.provider ?? 'other',
|
|
842
|
+
apiKeyEnv: config.envApiKey,
|
|
843
|
+
configured: Boolean(process.env[config.envApiKey]),
|
|
844
|
+
current: id === currentModelId,
|
|
845
|
+
};
|
|
846
|
+
})
|
|
847
|
+
.sort((a, b) => {
|
|
848
|
+
if (a.provider !== b.provider) {
|
|
849
|
+
return a.provider.localeCompare(b.provider);
|
|
850
|
+
}
|
|
851
|
+
if (a.current !== b.current) {
|
|
852
|
+
return a.current ? -1 : 1;
|
|
853
|
+
}
|
|
854
|
+
return a.name.localeCompare(b.name);
|
|
855
|
+
});
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
export const switchAgentModel = async (modelId: string): Promise<AgentModelSwitchResult> => {
|
|
859
|
+
const modules = await getSourceModules();
|
|
860
|
+
const available = modules.ProviderRegistry.getModelIds();
|
|
861
|
+
if (!available.includes(modelId)) {
|
|
862
|
+
throw new Error(`Unknown model: ${modelId}`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const config = modules.ProviderRegistry.getModelConfig(modelId);
|
|
866
|
+
if (!process.env[config.envApiKey]) {
|
|
867
|
+
throw new Error(`Missing env ${config.envApiKey} for model ${modelId}.`);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
preferredModelId = modelId;
|
|
871
|
+
await disposeRuntimeInstance();
|
|
872
|
+
return {
|
|
873
|
+
modelId,
|
|
874
|
+
modelLabel: config.name,
|
|
875
|
+
};
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
export const disposeAgentRuntime = async (): Promise<void> => {
|
|
879
|
+
await disposeRuntimeInstance();
|
|
880
|
+
preferredModelId = readPreferredModelIdFromEnv();
|
|
881
|
+
};
|