Skip to content

Commit 0f2b971

Browse files
committed
2 parents 6a29df2 + d65ea9b commit 0f2b971

6 files changed

Lines changed: 121 additions & 49 deletions

File tree

agent/simpleAgent.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,6 @@ export const contextSchema = z.object({
2525
emitToolCallEvent: z.custom<ToolCallEventSink>(),
2626
});
2727

28-
type AgentReasoning =
29-
| "none"
30-
| "minimal"
31-
| "low"
32-
| "medium"
33-
| "high"
34-
| "xhigh";
35-
3628
type OpenAIBackedCompletionAdapter = CompletionAdapter & {
3729
options?: {
3830
openAiApiKey?: string;
@@ -170,21 +162,9 @@ function createAgentLlmMetricsLogger() {
170162
return new AgentLlmMetricsLogger();
171163
}
172164

173-
function normalizeReasoning(reasoning: AgentReasoning) {
174-
if (reasoning === "none") {
175-
return undefined;
176-
}
177-
178-
return {
179-
effort: reasoning as "minimal" | "low" | "medium" | "high" | "xhigh",
180-
summary: "auto" as const,
181-
};
182-
}
183-
184165
export function createAgentChatModel(params: {
185166
adapter: CompletionAdapter;
186167
maxTokens: number;
187-
reasoning: AgentReasoning;
188168
modelName?: string;
189169
}) {
190170
const adapter = params.adapter as OpenAIBackedCompletionAdapter;
@@ -198,7 +178,7 @@ export function createAgentChatModel(params: {
198178

199179
const model = params.modelName ?? options.model ?? "gpt-5-nano";
200180
const baseURL = options.baseURL ?? options.baseUrl;
201-
const reasoning = normalizeReasoning(params.reasoning);
181+
const reasoning = options.extraRequestBodyParameters?.reasoning;
202182

203183
// @ts-ignore
204184
return new ChatOpenAI({
@@ -285,7 +265,7 @@ export async function callAgent(params: {
285265

286266
return await agent.stream({ messages } as any, {
287267
streamMode: "messages",
288-
recursionLimit: 50,
268+
recursionLimit: 100,
289269
callbacks: [createAgentLlmMetricsLogger()],
290270
configurable: {
291271
thread_id: sessionId,

custom/ChatSurface.vue

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,49 @@
7878
v-model="agentStore.userMessageInput"
7979
ref="textInput"
8080
@input="autoResize"
81-
class="min-h-12 p-4 pr-12 w-full resize-none overflow-hidden border text-lightInputText dark:text-darkInputText rounded-md bg-transparent text-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 focus:outline-none"
81+
:class="[
82+
'min-h-12 w-full resize-none overflow-hidden border text-lightInputText dark:text-darkInputText rounded-md bg-transparent text-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 focus:outline-none',
83+
agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
84+
]"
8285
placeholder="Type a message..."
83-
@keydown.enter.exact.prevent="async () => {await agentStore.sendMessage(); autoResize();}"
86+
@keydown.enter.exact.prevent="sendMessage"
8487
/>
88+
<div
89+
v-if="agentStore.availableModes.length > 1"
90+
ref="modeMenu"
91+
class="absolute bottom-2 left-4"
92+
>
93+
<button
94+
aria-label="Select mode"
95+
class="flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-lightNavbarIcons transition-colors duration-200 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-darkNavbarIcons dark:hover:bg-gray-700"
96+
:class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
97+
:disabled="agentStore.isResponseInProgress"
98+
title="Select mode"
99+
type="button"
100+
@click="toggleModeMenu"
101+
>
102+
<IconBrainOutline class="h-4 w-4" />
103+
</button>
104+
105+
<div
106+
v-if="isModeMenuOpen"
107+
class="absolute bottom-full left-0 mb-2 min-w-40 overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg dark:border-gray-600 dark:bg-gray-800"
108+
>
109+
<button
110+
v-for="mode in agentStore.availableModes"
111+
:key="mode.name"
112+
class="block w-full px-3 py-2 text-left text-sm text-lightInputText transition-colors duration-150 hover:bg-gray-100 dark:text-darkInputText dark:hover:bg-gray-700"
113+
:class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
114+
type="button"
115+
@click="selectMode(mode.name)"
116+
>
117+
{{ mode.name }}
118+
</button>
119+
</div>
120+
</div>
85121
<Button
86122
class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
87-
@click="async () => {await agentStore.sendMessage(); autoResize();}"
123+
@click="sendMessage"
88124
:disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
89125
>
90126
<IconArrowUpOutline
@@ -101,8 +137,8 @@
101137

102138
<script setup lang="ts">
103139
import { IconChatBubbleLeft20Solid, IconSparklesSolid } from '@iconify-prerendered/vue-heroicons';
104-
import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid } from '@iconify-prerendered/vue-flowbite';
105-
import { useTemplateRef, onMounted, ref, computed } from 'vue';
140+
import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid, IconBrainOutline } from '@iconify-prerendered/vue-flowbite';
141+
import { useTemplateRef, onMounted, ref } from 'vue';
106142
import { onClickOutside } from '@vueuse/core'
107143
import ConversationArea from './ConversationArea.vue';
108144
import { useAgentStore } from './useAgentStore';
@@ -112,13 +148,19 @@ import { useCoreStore } from '@/stores/core';
112148
const props = defineProps<{
113149
meta: {
114150
pluginInstanceId: string;
151+
modes: Array<{
152+
name: string;
153+
}>;
154+
defaultModeName: string | null;
115155
}
116156
}>();
117157
118158
const chatSurface = useTemplateRef('chatSurface');
119159
const textInput = useTemplateRef('textInput');
160+
const modeMenu = useTemplateRef('modeMenu');
120161
const agentStore = useAgentStore();
121162
const coreStore = useCoreStore();
163+
const isModeMenuOpen = ref(false);
122164
123165
const MAX_WIDTH = 800;
124166
const MIN_WIDTH = 382; //w-96
@@ -158,8 +200,10 @@ const stopResize = () => {
158200
}
159201
160202
onClickOutside(chatSurface, () => {if (!agentStore.isTeleportedToBody) agentStore.setIsChatOpen(false);});
203+
onClickOutside(modeMenu, () => { isModeMenuOpen.value = false; });
161204
162205
onMounted(async () => {
206+
agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
163207
agentStore.regisrerTextInput(textInput.value);
164208
textInput.value?.focus();
165209
await agentStore.fetchSessionsList();
@@ -181,4 +225,19 @@ function autoResize() {
181225
}
182226
}
183227
184-
</script>
228+
function toggleModeMenu() {
229+
isModeMenuOpen.value = !isModeMenuOpen.value;
230+
}
231+
232+
function selectMode(modeName: string) {
233+
agentStore.setActiveMode(modeName);
234+
isModeMenuOpen.value = false;
235+
}
236+
237+
async function sendMessage() {
238+
isModeMenuOpen.value = false;
239+
await agentStore.sendMessage();
240+
autoResize();
241+
}
242+
243+
</script>

custom/ToolsGroup.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
</template>
2222

2323
<script setup lang="ts">
24-
import { Tool } from 'langchain';
2524
import ToolRenderer from './ToolRenderer.vue';
2625
import type { IPart } from './types';
2726
import { ref } from 'vue';

custom/useAgentStore.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { Chat } from './chat';
77
import { cosineSimilarity, DefaultChatTransport } from 'ai';
88
import { useCoreStore } from '@/stores/core';
99

10+
type AgentMode = {
11+
name: string;
12+
};
13+
1014
export const useAgentStore = defineStore('agent', () => {
1115
const activeSessionId = ref<string | null>(null);
1216
const currentSession = ref<IAgentSession | null>(null);
@@ -28,6 +32,8 @@ export const useAgentStore = defineStore('agent', () => {
2832
const header = ref<HTMLElement | null>(null);
2933
const lastSessionId = ref<string | null>(null);
3034
const chatWidth = ref(600);
35+
const availableModes = ref<AgentMode[]>([]);
36+
const activeModeName = ref<string | null>(null);
3137
function setLocalStorageItem(key: string, value: string) {
3238
window.localStorage.setItem(`${coreStore.config.brandName || 'adminforth'}-${key}`, value);
3339
}
@@ -105,6 +111,24 @@ export const useAgentStore = defineStore('agent', () => {
105111
})
106112
const chats = new Map<string, Chat<any>>();
107113
const currentChat = shallowRef<Chat<any>>();
114+
115+
function setAvailableModes(modes: AgentMode[], defaultModeName?: string | null) {
116+
availableModes.value = modes;
117+
activeModeName.value =
118+
modes.find((mode) => mode.name === activeModeName.value)?.name
119+
?? defaultModeName
120+
?? modes[0]?.name
121+
?? null;
122+
}
123+
124+
function setActiveMode(modeName: string) {
125+
if (!availableModes.value.some((mode) => mode.name === modeName)) {
126+
return;
127+
}
128+
129+
activeModeName.value = modeName;
130+
}
131+
108132
function setCurrentChat(sessionId: string) {
109133
if (chats.has(sessionId)) {
110134
currentChat.value = chats.get(sessionId) || null;
@@ -119,6 +143,7 @@ export const useAgentStore = defineStore('agent', () => {
119143
message,
120144
sessionId,
121145
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
146+
mode: activeModeName.value,
122147
};
123148

124149
return {
@@ -384,6 +409,10 @@ export const useAgentStore = defineStore('agent', () => {
384409
setChatWidth,
385410
focusTextInput,
386411
setFullScreen,
387-
isFullScreen
412+
isFullScreen,
413+
availableModes,
414+
activeModeName,
415+
setAvailableModes,
416+
setActiveMode,
388417
}
389-
})
418+
})

index.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type {
22
AdminForthResource,
3-
AdminUser,
43
IAdminForth,
54
IHttpServer
65
} from "adminforth";
@@ -115,19 +114,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
115114

116115
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
117116
super.modifyResourceConfig(adminforth, resourceConfig);
117+
if (!this.options.modes?.length) {
118+
throw new Error("modes is required for AdminForthAgentPlugin");
119+
}
118120
if (!this.adminforth.config.customization.globalInjections.header) {
119121
this.adminforth.config.customization.globalInjections.header = [];
120122
}
121123
this.adminforth.config.customization.globalInjections.header.push({
122124
file: this.componentPath("ChatSurface.vue"),
123125
meta: {
124126
pluginInstanceId: this.pluginInstanceId,
127+
modes: this.options.modes.map((mode) => ({ name: mode.name })),
128+
defaultModeName: this.options.modes[0].name,
125129
}
126130
});
127-
128-
if (!this.pluginOptions.completionAdapter) {
129-
throw new Error("CompletionAdapter is required for AdminForthAgentPlugin");
130-
}
131131
if (!this.pluginOptions.sessionResource) {
132132
throw new Error("sessionResource is required for AdminForthAgentPlugin");
133133
}
@@ -244,19 +244,15 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
244244
});
245245

246246
const maxTokens = this.options.maxTokens ?? 10000;
247-
const reasoning = this.options.reasoning ?? 'low';
248-
const summaryReasoning = 'low';
249-
247+
const selectedMode = this.options.modes.find((mode) => mode.name === body.mode) ?? this.options.modes[0];
250248
const model = createAgentChatModel({
251-
adapter: this.options.completionAdapter,
249+
adapter: selectedMode.completionAdapter,
252250
maxTokens,
253-
reasoning,
254251
});
255252

256253
const summaryModel = createAgentChatModel({
257-
adapter: this.options.completionAdapter,
254+
adapter: selectedMode.completionAdapter,
258255
maxTokens,
259-
reasoning: summaryReasoning,
260256
});
261257
const systemPrompt = await this.agentSystemPromptPromise;
262258
const stream = await callAgent({

types.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import {type PluginsCommonOptions } from "adminforth";
2-
import { type CompletionAdapter } from "adminforth";
1+
import { type PluginsCommonOptions, type CompletionAdapter } from "adminforth";
32

43
interface ISessionResource {
54
resourceId: string;
@@ -22,11 +21,14 @@ interface ITurnResource {
2221

2322
export interface PluginOptions extends PluginsCommonOptions {
2423
/**
25-
* Adapter instance that will be used to generate responses.
26-
* You can use any adapter that implements the CompletionAdapter interface, for example the OpenAIAdapter included in adminforth,
27-
* or create your own that fetches responses from your custom backend.
24+
* Modes for the plugin.
25+
* Each mode can have its own configuration.
26+
* Each mode uses its own completion adapter instance.
2827
*/
29-
completionAdapter: CompletionAdapter;
28+
modes: {
29+
name: string;
30+
completionAdapter: CompletionAdapter;
31+
}[];
3032

3133
/**
3234
* Max tokens for the generation.
@@ -40,6 +42,13 @@ export interface PluginOptions extends PluginsCommonOptions {
4042
*/
4143
reasoning?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
4244

45+
/**
46+
* Resource configuration for sessions.
47+
*/
4348
sessionResource: ISessionResource;
49+
50+
/**
51+
* Resource configuration for turns.
52+
*/
4453
turnResource: ITurnResource;
4554
}

0 commit comments

Comments
 (0)