@makefinks/daemon 0.1.0
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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/cli.js +22 -0
- package/package.json +79 -0
- package/src/ai/agent-turn-runner.ts +130 -0
- package/src/ai/daemon-ai.ts +403 -0
- package/src/ai/exa-client.ts +21 -0
- package/src/ai/exa-fetch-cache.ts +104 -0
- package/src/ai/model-config.ts +99 -0
- package/src/ai/sanitize-messages.ts +83 -0
- package/src/ai/system-prompt.ts +363 -0
- package/src/ai/tools/fetch-urls.ts +187 -0
- package/src/ai/tools/grounding-manager.ts +94 -0
- package/src/ai/tools/index.ts +52 -0
- package/src/ai/tools/read-file.ts +100 -0
- package/src/ai/tools/render-url.ts +275 -0
- package/src/ai/tools/run-bash.ts +224 -0
- package/src/ai/tools/subagents.ts +195 -0
- package/src/ai/tools/todo-manager.ts +150 -0
- package/src/ai/tools/web-search.ts +91 -0
- package/src/app/App.tsx +711 -0
- package/src/app/components/AppOverlays.tsx +131 -0
- package/src/app/components/AvatarLayer.tsx +51 -0
- package/src/app/components/ConversationPane.tsx +476 -0
- package/src/avatar/DaemonAvatarRenderable.ts +343 -0
- package/src/avatar/daemon-avatar-rig.ts +1165 -0
- package/src/avatar-preview.ts +186 -0
- package/src/cli.ts +26 -0
- package/src/components/ApiKeyInput.tsx +99 -0
- package/src/components/ApiKeyStep.tsx +95 -0
- package/src/components/ApprovalPicker.tsx +109 -0
- package/src/components/ContentBlockView.tsx +141 -0
- package/src/components/DaemonText.tsx +34 -0
- package/src/components/DeviceMenu.tsx +166 -0
- package/src/components/GroundingBadge.tsx +21 -0
- package/src/components/GroundingMenu.tsx +310 -0
- package/src/components/HotkeysPane.tsx +115 -0
- package/src/components/InlineStatusIndicator.tsx +106 -0
- package/src/components/ModelMenu.tsx +411 -0
- package/src/components/OnboardingOverlay.tsx +446 -0
- package/src/components/ProviderMenu.tsx +177 -0
- package/src/components/SessionMenu.tsx +297 -0
- package/src/components/SettingsMenu.tsx +291 -0
- package/src/components/StatusBar.tsx +126 -0
- package/src/components/TokenUsageDisplay.tsx +92 -0
- package/src/components/ToolCallView.tsx +113 -0
- package/src/components/TypingInputBar.tsx +131 -0
- package/src/components/tool-layouts/components.tsx +120 -0
- package/src/components/tool-layouts/defaults.ts +9 -0
- package/src/components/tool-layouts/index.ts +22 -0
- package/src/components/tool-layouts/layouts/bash.ts +110 -0
- package/src/components/tool-layouts/layouts/grounding.tsx +98 -0
- package/src/components/tool-layouts/layouts/index.ts +8 -0
- package/src/components/tool-layouts/layouts/read-file.ts +59 -0
- package/src/components/tool-layouts/layouts/subagent.tsx +118 -0
- package/src/components/tool-layouts/layouts/system-info.ts +8 -0
- package/src/components/tool-layouts/layouts/todo.tsx +139 -0
- package/src/components/tool-layouts/layouts/url-tools.ts +220 -0
- package/src/components/tool-layouts/layouts/web-search.ts +110 -0
- package/src/components/tool-layouts/registry.ts +17 -0
- package/src/components/tool-layouts/types.ts +94 -0
- package/src/hooks/daemon-event-handlers.ts +944 -0
- package/src/hooks/keyboard-handlers.ts +399 -0
- package/src/hooks/menu-navigation.ts +147 -0
- package/src/hooks/use-app-audio-devices-loader.ts +71 -0
- package/src/hooks/use-app-callbacks.ts +202 -0
- package/src/hooks/use-app-context-builder.ts +159 -0
- package/src/hooks/use-app-display-state.ts +162 -0
- package/src/hooks/use-app-menus.ts +51 -0
- package/src/hooks/use-app-model-pricing-loader.ts +45 -0
- package/src/hooks/use-app-model.ts +123 -0
- package/src/hooks/use-app-openrouter-models-loader.ts +44 -0
- package/src/hooks/use-app-openrouter-provider-loader.ts +35 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +212 -0
- package/src/hooks/use-app-sessions.ts +105 -0
- package/src/hooks/use-app-settings.ts +62 -0
- package/src/hooks/use-conversation-manager.ts +163 -0
- package/src/hooks/use-copy-on-select.ts +50 -0
- package/src/hooks/use-daemon-events.ts +396 -0
- package/src/hooks/use-daemon-keyboard.ts +397 -0
- package/src/hooks/use-grounding.ts +46 -0
- package/src/hooks/use-input-history.ts +92 -0
- package/src/hooks/use-menu-keyboard.ts +93 -0
- package/src/hooks/use-playwright-notification.ts +23 -0
- package/src/hooks/use-reasoning-animation.ts +97 -0
- package/src/hooks/use-response-timer.ts +55 -0
- package/src/hooks/use-tool-approval.tsx +202 -0
- package/src/hooks/use-typing-mode.ts +137 -0
- package/src/hooks/use-voice-dependencies-notification.ts +37 -0
- package/src/index.tsx +48 -0
- package/src/scripts/setup-browsers.ts +42 -0
- package/src/state/app-context.tsx +160 -0
- package/src/state/daemon-events.ts +67 -0
- package/src/state/daemon-state.ts +493 -0
- package/src/state/migrations/001-init.ts +33 -0
- package/src/state/migrations/index.ts +8 -0
- package/src/state/model-history-store.ts +45 -0
- package/src/state/runtime-context.ts +21 -0
- package/src/state/session-store.ts +359 -0
- package/src/types/index.ts +405 -0
- package/src/types/theme.ts +52 -0
- package/src/ui/constants.ts +157 -0
- package/src/utils/clipboard.ts +89 -0
- package/src/utils/debug-logger.ts +69 -0
- package/src/utils/formatters.ts +242 -0
- package/src/utils/js-rendering.ts +77 -0
- package/src/utils/markdown-tables.ts +234 -0
- package/src/utils/model-metadata.ts +191 -0
- package/src/utils/openrouter-endpoints.ts +212 -0
- package/src/utils/openrouter-models.ts +205 -0
- package/src/utils/openrouter-pricing.ts +59 -0
- package/src/utils/openrouter-reported-cost.ts +16 -0
- package/src/utils/paste.ts +33 -0
- package/src/utils/preferences.ts +289 -0
- package/src/utils/text-fragment.ts +39 -0
- package/src/utils/tool-output-preview.ts +250 -0
- package/src/utils/voice-dependencies.ts +107 -0
- package/src/utils/workspace-manager.ts +85 -0
- package/src/voice/audio-recorder.ts +579 -0
- package/src/voice/mic-level.ts +35 -0
- package/src/voice/tts/openai-tts-stream.ts +222 -0
- package/src/voice/tts/speech-controller.ts +64 -0
- package/src/voice/tts/tts-player.ts +257 -0
- package/src/voice/voice-input-controller.ts +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 makefinks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# DAEMON
|
|
2
|
+
**DAEMON** (pronounced "day-mon") is an opinionated **terminal-based AI agent** with distinct sci-fi theming,
|
|
3
|
+
delivered through a well-thought-out TUI powered by [OpenTUI](https://github.com/anomalyco/opentui).
|
|
4
|
+
It supports **text and voice interaction** and can be fully controlled through **hotkeys**.
|
|
5
|
+
|
|
6
|
+
DAEMON is focused on **information-gathering workflows** that benefit from **grounded responses**
|
|
7
|
+
but can also interact with and **control** your system through the terminal with scoped permissions.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
### 👤 Interactive Avatar
|
|
14
|
+
At the core of the TUI is DAEMON's **animated avatar**, reacting to what it's doing in real time:
|
|
15
|
+
listening to audio input, reasoning about questions, calling tools, and generating an answer.
|
|
16
|
+
|
|
17
|
+
The avatar was deliberately designed to feel slightly ominous and alien-like playing into
|
|
18
|
+
|
|
19
|
+
### 🧠 LLMs
|
|
20
|
+
DAEMON can be powered by **any** model available on [OpenRouter](https://openrouter.ai/models).
|
|
21
|
+
The TUI includes a model picker that can fetch and select models as they become available on OpenRouter.
|
|
22
|
+
Models are sorted by average provider pricing and the picker includes information about their context window and caching support.
|
|
23
|
+
Once a model is selected, the **selection of the inference provider** is possible. Open-source models can vary strongly between providers.
|
|
24
|
+
> While all models on OpenRouter are supported, a curated list is included for the best experience.
|
|
25
|
+
It is generally recommended to use Models that support caching due to being significantly cheaper in DAEMON
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### 🎙️ Voice capabilities
|
|
31
|
+
SOTA transcription accuracy is achieved by using OpenAI's latest transcription model `gpt-4o-mini-transcribe-2025-03-20`.
|
|
32
|
+
It features a large vocabulary and can transcribe multilingual inputs with complex terminology.
|
|
33
|
+
|
|
34
|
+
OpenAI's TTS model `gpt-4o-mini-tts-2025-03-20` is used to generate voice output with as little latency as possible.
|
|
35
|
+
|
|
36
|
+
### 🔎 Web Search
|
|
37
|
+
DAEMON uses the [Exa](https://exa.ai/) search and fetch API for retrieving accurate and up-to-date information.
|
|
38
|
+
|
|
39
|
+
### 💾 Session Persistence
|
|
40
|
+
DAEMON stores chat sessions locally (SQLite) and lets you resume past conversations.
|
|
41
|
+
|
|
42
|
+
## ✨ Feature List
|
|
43
|
+
|
|
44
|
+
| Feature | Description |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Terminal TUI | OpenTUI-powered interface with sci-fi styling and hotkey controls. |
|
|
47
|
+
| Text + Voice | Supports text input and voice interaction with transcription and TTS. |
|
|
48
|
+
| Animated Avatar | Sci-fi avatar reacts to listening, tool use, and response generation. |
|
|
49
|
+
| Multi-Model Support | Works with all OpenRouter models and includes a curated default list. |
|
|
50
|
+
| Session Persistence | Preferences and chat sessions stored locally on disk. |
|
|
51
|
+
| Workspaces | On-disk workspaces for the agent to work in. |
|
|
52
|
+
| Web Search | Exa-based search and fetch for grounded, up-to-date info. |
|
|
53
|
+
| Grounding | Text-fragment grounding with a dedicated UI. |
|
|
54
|
+
| Bash Execution | Bash integration with approval scoping for potentially dangerous commands. |
|
|
55
|
+
| JS Page Rendering | Optional Playwright renderer for SPA content. |
|
|
56
|
+
|
|
57
|
+
## 📦 Install (npm/bun)
|
|
58
|
+
|
|
59
|
+
DAEMON is published as a CLI package. It **requires Bun** at runtime, even if you install via npm.
|
|
60
|
+
|
|
61
|
+
To install bun on macOs/Linux
|
|
62
|
+
```bash
|
|
63
|
+
curl -fsSL https://bun.com/install | bash
|
|
64
|
+
```
|
|
65
|
+
Then install DAEMON:
|
|
66
|
+
```bash
|
|
67
|
+
# Global npm install
|
|
68
|
+
npm i -g @makefinks/daemon
|
|
69
|
+
# or via bun
|
|
70
|
+
bun add -g @makefinks/daemon
|
|
71
|
+
|
|
72
|
+
# Then run
|
|
73
|
+
daemon
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Configuration is done via environment variables (or the onboarding UI):
|
|
77
|
+
|
|
78
|
+
- `OPENROUTER_API_KEY` (required) - response generation via OpenRouter models
|
|
79
|
+
- `EXA_API_KEY` (optional) - enables web search + fetch grounding via Exa
|
|
80
|
+
- `OPENAI_API_KEY` (optional) - enables voice transcription + TTS
|
|
81
|
+
|
|
82
|
+
> Keys entered via the onboarding UI are stored locally in `~/.config/daemon/credentials.json` with restricted permissions (`0600`). For maximum security, use environment variables instead.
|
|
83
|
+
|
|
84
|
+
## 🛠️ System dependencies
|
|
85
|
+
|
|
86
|
+
Voice input requires `sox` and platform-specific audio libraries:
|
|
87
|
+
|
|
88
|
+
### macOS
|
|
89
|
+
```bash
|
|
90
|
+
brew install sox
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Linux (Debian/Ubuntu)
|
|
94
|
+
```bash
|
|
95
|
+
sudo apt install sox libsox-fmt-pulse
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Linux (Fedora)
|
|
99
|
+
```bash
|
|
100
|
+
sudo dnf install sox sox-plugins-freeworld
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Linux (Arch)
|
|
104
|
+
```bash
|
|
105
|
+
sudo pacman -S sox
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 🧩 Optional: JS-rendered page support (`renderUrl`)
|
|
109
|
+
|
|
110
|
+
DAEMON defaults to Exa-based `fetchUrls` for retrieving web page text. For JavaScript-heavy sites (SPAs) where `fetchUrls` returns "shell-only" content, DAEMON can optionally use a local Playwright Chromium renderer via the `renderUrl` tool.
|
|
111
|
+
|
|
112
|
+
This feature is **optional** and intentionally not installed by default (browser downloads are large). The render tool is not available to DAEMON without the installation below.
|
|
113
|
+
|
|
114
|
+
### For global installs (`npm i -g` / `bun add -g`)
|
|
115
|
+
|
|
116
|
+
Playwright must also be installed globally:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# 1) Install Playwright globally
|
|
120
|
+
npm i -g playwright
|
|
121
|
+
# or
|
|
122
|
+
bun add -g playwright
|
|
123
|
+
|
|
124
|
+
# 2) Install Chromium browser binaries
|
|
125
|
+
npx playwright install chromium
|
|
126
|
+
```
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
var args = process.argv.slice(2);
|
|
8
|
+
var bunCmd = process.platform === "win32" ? "bun.exe" : "bun";
|
|
9
|
+
var bunCheck = spawnSync(bunCmd, ["--version"], { stdio: "ignore" });
|
|
10
|
+
if (bunCheck.error || bunCheck.status !== 0) {
|
|
11
|
+
console.error("DAEMON requires Bun. Install it from https://bun.sh and try again.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
var packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
var entry = path.join(packageRoot, "src", "index.tsx");
|
|
16
|
+
var result = spawnSync(bunCmd, [entry, ...args], { stdio: "inherit" });
|
|
17
|
+
if (result.error) {
|
|
18
|
+
const error = result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
19
|
+
console.error(error.message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
process.exit(result.status ?? 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@makefinks/daemon",
|
|
3
|
+
"description": "Terminal-first AI agent with an interactive avatar, vim-style controls, and advanced voice capabilities.",
|
|
4
|
+
"author": "makefinks",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent",
|
|
8
|
+
"llm-agent",
|
|
9
|
+
"terminal-ui",
|
|
10
|
+
"tui",
|
|
11
|
+
"cli",
|
|
12
|
+
"ai",
|
|
13
|
+
"terminal",
|
|
14
|
+
"llm",
|
|
15
|
+
"daemon",
|
|
16
|
+
"makefinks",
|
|
17
|
+
"openrouter",
|
|
18
|
+
"bun",
|
|
19
|
+
"voice",
|
|
20
|
+
"ai-assistant"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/makefinks/daemon.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/makefinks/daemon/issues"
|
|
28
|
+
},
|
|
29
|
+
"module": "src/index.tsx",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"version": "0.1.0",
|
|
32
|
+
"bin": {
|
|
33
|
+
"daemon": "dist/cli.js"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"files": ["dist", "src", "LICENSE", "README.md", "package.json"],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build:cli": "bun build src/cli.ts --outdir dist --target node",
|
|
41
|
+
"dev": "bun run --watch src/index.tsx",
|
|
42
|
+
"check": "bun run typecheck && bun run lint && bun run format:check",
|
|
43
|
+
"typecheck": "bunx tsc -p tsconfig.json --noEmit",
|
|
44
|
+
"lint": "biome lint src __tests__",
|
|
45
|
+
"lint:fix": "biome lint --write src __tests__",
|
|
46
|
+
"format": "biome format --write src __tests__",
|
|
47
|
+
"format:check": "biome format src __tests__",
|
|
48
|
+
"preview:avatar": "bun run src/avatar-preview.ts",
|
|
49
|
+
"preview:avatar:mp4": "bun run src/avatar-preview.ts --mp4 tmp/avatar-preview.mp4",
|
|
50
|
+
"setup:browsers": "bun run src/scripts/setup-browsers.ts",
|
|
51
|
+
"test": "bun test",
|
|
52
|
+
"test:watch": "bun test --watch",
|
|
53
|
+
"prepublishOnly": "bun run build:cli",
|
|
54
|
+
"release:patch": "npm version patch && git push && git push --tags && npm publish",
|
|
55
|
+
"release:minor": "npm version minor && git push && git push --tags && npm publish",
|
|
56
|
+
"release:major": "npm version major && git push && git push --tags && npm publish"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@biomejs/biome": "^1.9.4",
|
|
60
|
+
"@types/bun": "latest",
|
|
61
|
+
"@types/react": "^19.2.7",
|
|
62
|
+
"playwright": "^1.57.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"typescript": "^5.9.2"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@ai-sdk/openai": "^3.0.0",
|
|
69
|
+
"@openrouter/ai-sdk-provider": "1.5.4",
|
|
70
|
+
"@opentui-ui/toast": "^0.0.3",
|
|
71
|
+
"@opentui/core": "^0.1.63",
|
|
72
|
+
"@opentui/react": "^0.1.63",
|
|
73
|
+
"ai": "^6.0.0",
|
|
74
|
+
"exa-js": "^2.0.12",
|
|
75
|
+
"openai": "^6.16.0",
|
|
76
|
+
"opentui-spinner": "^0.0.6",
|
|
77
|
+
"react": "^19.2.3"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { generateResponse } from "./daemon-ai";
|
|
2
|
+
import type {
|
|
3
|
+
InteractionMode,
|
|
4
|
+
ModelMessage,
|
|
5
|
+
ReasoningEffort,
|
|
6
|
+
StreamCallbacks,
|
|
7
|
+
TokenUsage,
|
|
8
|
+
ToolApprovalRequest,
|
|
9
|
+
ToolApprovalResponse,
|
|
10
|
+
} from "../types";
|
|
11
|
+
|
|
12
|
+
export interface AgentTurnParams {
|
|
13
|
+
userText: string;
|
|
14
|
+
conversationHistory: ModelMessage[];
|
|
15
|
+
interactionMode: InteractionMode;
|
|
16
|
+
reasoningEffort: ReasoningEffort;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AgentTurnResult {
|
|
20
|
+
fullText: string;
|
|
21
|
+
responseMessages: ModelMessage[];
|
|
22
|
+
usage?: TokenUsage;
|
|
23
|
+
finalText?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AgentTurnRunner {
|
|
27
|
+
private abortController: AbortController | null = null;
|
|
28
|
+
private activeRunId = 0;
|
|
29
|
+
|
|
30
|
+
cancel(): void {
|
|
31
|
+
if (this.abortController) {
|
|
32
|
+
this.abortController.abort();
|
|
33
|
+
this.abortController = null;
|
|
34
|
+
}
|
|
35
|
+
this.activeRunId++;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async run(params: AgentTurnParams, callbacks: StreamCallbacks): Promise<AgentTurnResult | null> {
|
|
39
|
+
const runId = ++this.activeRunId;
|
|
40
|
+
this.abortController = new AbortController();
|
|
41
|
+
|
|
42
|
+
const isActive = () => runId === this.activeRunId && this.abortController !== null;
|
|
43
|
+
|
|
44
|
+
let result: AgentTurnResult | null = null;
|
|
45
|
+
let error: Error | null = null;
|
|
46
|
+
|
|
47
|
+
const wrapped: StreamCallbacks = {
|
|
48
|
+
onReasoningToken: (token) => {
|
|
49
|
+
if (!isActive()) return;
|
|
50
|
+
callbacks.onReasoningToken?.(token);
|
|
51
|
+
},
|
|
52
|
+
onToolCallStart: (toolName, toolCallId) => {
|
|
53
|
+
if (!isActive()) return;
|
|
54
|
+
callbacks.onToolCallStart?.(toolName, toolCallId);
|
|
55
|
+
},
|
|
56
|
+
onToolCall: (toolName, args, toolCallId) => {
|
|
57
|
+
if (!isActive()) return;
|
|
58
|
+
callbacks.onToolCall?.(toolName, args, toolCallId);
|
|
59
|
+
},
|
|
60
|
+
onToolResult: (toolName, resultValue, toolCallId) => {
|
|
61
|
+
if (!isActive()) return;
|
|
62
|
+
callbacks.onToolResult?.(toolName, resultValue, toolCallId);
|
|
63
|
+
},
|
|
64
|
+
onToolApprovalRequest: (request) => {
|
|
65
|
+
if (!isActive()) return;
|
|
66
|
+
callbacks.onToolApprovalRequest?.(request);
|
|
67
|
+
},
|
|
68
|
+
onAwaitingApprovals: (pendingApprovals, respondToApprovals) => {
|
|
69
|
+
if (!isActive()) return;
|
|
70
|
+
callbacks.onAwaitingApprovals?.(pendingApprovals, respondToApprovals);
|
|
71
|
+
},
|
|
72
|
+
onSubagentToolCall: (toolCallId, toolName, input) => {
|
|
73
|
+
if (!isActive()) return;
|
|
74
|
+
callbacks.onSubagentToolCall?.(toolCallId, toolName, input);
|
|
75
|
+
},
|
|
76
|
+
onSubagentUsage: (usage) => {
|
|
77
|
+
if (!isActive()) return;
|
|
78
|
+
callbacks.onSubagentUsage?.(usage);
|
|
79
|
+
},
|
|
80
|
+
onSubagentToolResult: (toolCallId, toolName, success) => {
|
|
81
|
+
if (!isActive()) return;
|
|
82
|
+
callbacks.onSubagentToolResult?.(toolCallId, toolName, success);
|
|
83
|
+
},
|
|
84
|
+
onSubagentComplete: (toolCallId, success) => {
|
|
85
|
+
if (!isActive()) return;
|
|
86
|
+
callbacks.onSubagentComplete?.(toolCallId, success);
|
|
87
|
+
},
|
|
88
|
+
onToken: (token) => {
|
|
89
|
+
if (!isActive()) return;
|
|
90
|
+
callbacks.onToken?.(token);
|
|
91
|
+
},
|
|
92
|
+
onStepUsage: (usage) => {
|
|
93
|
+
if (!isActive()) return;
|
|
94
|
+
callbacks.onStepUsage?.(usage);
|
|
95
|
+
},
|
|
96
|
+
onComplete: (fullText, responseMessages, usage, finalText) => {
|
|
97
|
+
if (!isActive()) return;
|
|
98
|
+
result = { fullText, responseMessages, usage, finalText };
|
|
99
|
+
callbacks.onComplete?.(fullText, responseMessages, usage, finalText);
|
|
100
|
+
},
|
|
101
|
+
onError: (err) => {
|
|
102
|
+
if (!isActive()) return;
|
|
103
|
+
error = err;
|
|
104
|
+
callbacks.onError?.(err);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await generateResponse(
|
|
110
|
+
params.userText,
|
|
111
|
+
wrapped,
|
|
112
|
+
params.conversationHistory,
|
|
113
|
+
params.interactionMode,
|
|
114
|
+
this.abortController.signal,
|
|
115
|
+
params.reasoningEffort
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
119
|
+
error = e;
|
|
120
|
+
wrapped.onError?.(e);
|
|
121
|
+
} finally {
|
|
122
|
+
if (isActive()) {
|
|
123
|
+
this.abortController = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (error) throw error;
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
}
|