@nuxtagentic/agents 0.0.0-stage → 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.md +9 -0
- package/README.md +99 -2
- package/module.ts +35 -0
- package/package.json +56 -3
- package/runtime/actions.ts +22 -0
- package/runtime/components/AgentEditor.vue +446 -0
- package/runtime/components/AgentList.vue +110 -0
- package/runtime/components/AgentToolPicker.vue +371 -0
- package/runtime/components/AgentsWorkspace.vue +328 -0
- package/runtime/components/ModelSelect.vue +113 -0
- package/runtime/components/RunPanel.vue +114 -0
- package/runtime/components/RuntimeModelPicker.vue +276 -0
- package/runtime/components/SourceEditor.vue +78 -0
- package/runtime/composables/useAgentIntegrationTools.ts +24 -0
- package/runtime/composables/useAgentRuntimes.ts +32 -0
- package/runtime/composables/useAgents.ts +24 -0
- package/runtime/index.ts +71 -0
- package/runtime/middleware/agents-route.ts +10 -0
- package/runtime/pages/agents.vue +26 -0
- package/runtime/server/api/agents/[id]/runs/[run].get.ts +11 -0
- package/runtime/server/api/agents/[id]/runs.post.ts +33 -0
- package/runtime/server/api/agents/runtimes.get.ts +37 -0
- package/runtime/server/utils/agent-execution.ts +124 -0
- package/runtime/server/utils/agent-gateway.ts +13 -0
- package/runtime/server/utils/agent-model-catalog.ts +93 -0
- package/runtime/server/utils/agent-provider-keys.ts +46 -0
- package/runtime/server/utils/local-agent-catalog.ts +151 -0
- package/runtime/server/utils/local-agent-runtime.ts +8 -0
- package/runtime/server/utils/model-fetch.ts +66 -0
- package/runtime/server/utils/run-local-agent.ts +208 -0
- package/runtime/server/utils/tool-result.ts +22 -0
- package/runtime/shared/agent-summary.ts +9 -0
- package/runtime/shared/agent.ts +4 -0
- package/runtime/shared/definition.ts +16 -0
- package/runtime/shared/editorTheme.ts +43 -0
- package/runtime/shared/integration-tool.ts +11 -0
- package/runtime/shared/presentation.ts +50 -0
- package/runtime/shared/prompt.ts +37 -0
- package/runtime/shared/runtime.ts +40 -0
- package/runtime/shared/source.ts +81 -0
- package/schema.ts +2 -0
- package/server.ts +2 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { AgentSummary } from '../shared/agent-summary'
|
|
4
|
+
import { getAgentAvatarClass, getAgentInitials } from '../shared/presentation'
|
|
5
|
+
import { PROVIDER_LABELS } from '../shared/runtime'
|
|
6
|
+
|
|
7
|
+
const { agents, selectedId, hasSelection, pending, error } = defineProps<{
|
|
8
|
+
agents: AgentSummary[]
|
|
9
|
+
selectedId: number | null
|
|
10
|
+
hasSelection?: boolean
|
|
11
|
+
pending?: boolean
|
|
12
|
+
error?: string
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
select: [id: number]
|
|
17
|
+
refresh: []
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const search = defineModel<string>('search', { required: true })
|
|
21
|
+
|
|
22
|
+
const isHiddenOnMobile = computed(() => hasSelection ?? selectedId !== null)
|
|
23
|
+
const isLoading = computed(() => pending && !agents.length)
|
|
24
|
+
const isEmpty = computed(() => !pending && !error && !agents.length)
|
|
25
|
+
const emptyMessage = computed(() =>
|
|
26
|
+
search.value ? 'No agents match.' : 'Your agents will appear here.',
|
|
27
|
+
)
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<aside
|
|
32
|
+
class="flex w-full shrink-0 flex-col border-r border-default md:w-60"
|
|
33
|
+
:class="isHiddenOnMobile ? 'hidden md:flex' : 'flex'"
|
|
34
|
+
>
|
|
35
|
+
<div class="px-3 pt-3 pb-2">
|
|
36
|
+
<UInput
|
|
37
|
+
v-model="search"
|
|
38
|
+
icon="i-lucide-search"
|
|
39
|
+
placeholder="Search agents"
|
|
40
|
+
aria-label="Search agents"
|
|
41
|
+
class="w-full"
|
|
42
|
+
:ui="{
|
|
43
|
+
base: 'rounded-lg text-[13px] ring-(--gray-500)',
|
|
44
|
+
leadingIcon: 'size-3.5 text-dimmed',
|
|
45
|
+
}"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="min-h-0 flex-1 space-y-0.5 overflow-auto px-2 pb-30">
|
|
49
|
+
<div
|
|
50
|
+
v-if="error"
|
|
51
|
+
class="space-y-2 px-2 py-6"
|
|
52
|
+
>
|
|
53
|
+
<p
|
|
54
|
+
role="alert"
|
|
55
|
+
class="text-[13px] text-error"
|
|
56
|
+
>
|
|
57
|
+
{{ error }}
|
|
58
|
+
</p>
|
|
59
|
+
<UButton
|
|
60
|
+
label="Retry"
|
|
61
|
+
color="neutral"
|
|
62
|
+
variant="outline"
|
|
63
|
+
size="xs"
|
|
64
|
+
@click="emit('refresh')"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<p
|
|
68
|
+
v-else-if="isLoading"
|
|
69
|
+
role="status"
|
|
70
|
+
class="px-2 py-6 text-center text-[13px] text-muted"
|
|
71
|
+
>
|
|
72
|
+
Loading agents…
|
|
73
|
+
</p>
|
|
74
|
+
<button
|
|
75
|
+
v-for="agent in agents"
|
|
76
|
+
:key="agent.id"
|
|
77
|
+
type="button"
|
|
78
|
+
:aria-current="selectedId === agent.id ? 'true' : undefined"
|
|
79
|
+
class="flex h-13 w-full cursor-pointer items-center gap-2.5 rounded-lg pr-2.5 pl-2 text-left transition-[background-color,transform] duration-160 ease-[cubic-bezier(.2,0,0,1)] hover:bg-(--gray-400) focus-visible:outline-2 focus-visible:outline-accented active:scale-[0.98] motion-reduce:transition-none"
|
|
80
|
+
:class="selectedId === agent.id && 'bg-(--gray-400)'"
|
|
81
|
+
@click="emit('select', agent.id)"
|
|
82
|
+
>
|
|
83
|
+
<span
|
|
84
|
+
class="grid size-8 shrink-0 place-items-center rounded-full text-xs font-semibold text-black"
|
|
85
|
+
:class="getAgentAvatarClass(agent.id)"
|
|
86
|
+
>
|
|
87
|
+
{{ getAgentInitials(agent.name) }}
|
|
88
|
+
</span>
|
|
89
|
+
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
90
|
+
<span class="truncate text-sm leading-4.5 text-highlighted">{{ agent.name }}</span>
|
|
91
|
+
<span class="truncate text-xs leading-4 text-dimmed">
|
|
92
|
+
{{ PROVIDER_LABELS[agent.provider] }} · {{ agent.model }}
|
|
93
|
+
</span>
|
|
94
|
+
</span>
|
|
95
|
+
<span
|
|
96
|
+
class="size-1.5 shrink-0 rounded-full transition-colors duration-240"
|
|
97
|
+
:class="agent.status === 'active' ? 'bg-success' : 'bg-(--gray-600)'"
|
|
98
|
+
:aria-label="agent.status === 'active' ? 'Active' : 'Draft'"
|
|
99
|
+
/>
|
|
100
|
+
</button>
|
|
101
|
+
<p
|
|
102
|
+
v-if="isEmpty"
|
|
103
|
+
class="px-2 py-6 text-center text-[13px] text-muted"
|
|
104
|
+
>
|
|
105
|
+
{{ emptyMessage }}
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
<slot name="footer" />
|
|
109
|
+
</aside>
|
|
110
|
+
</template>
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive, ref } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
AGENT_PARAMETER_TYPES,
|
|
5
|
+
agentToolSchema,
|
|
6
|
+
type AgentDefinition,
|
|
7
|
+
type AgentTool,
|
|
8
|
+
} from '@cloudconfig/core/agents'
|
|
9
|
+
import { PROVIDER_LABELS } from '../shared/runtime'
|
|
10
|
+
import type { AgentIntegrationTool } from '../shared/integration-tool'
|
|
11
|
+
import { useAgentIntegrationTools } from '../composables/useAgentIntegrationTools'
|
|
12
|
+
|
|
13
|
+
const { integrationTools } = defineProps<{ integrationTools?: AgentIntegrationTool[] }>()
|
|
14
|
+
const agent = defineModel<AgentDefinition>({ required: true })
|
|
15
|
+
defineEmits<{ close: [] }>()
|
|
16
|
+
const CHOICES = [
|
|
17
|
+
{ value: 'auto', label: 'Auto', hint: 'The model decides when to call a tool.' },
|
|
18
|
+
{
|
|
19
|
+
value: 'required',
|
|
20
|
+
label: 'Required',
|
|
21
|
+
hint: 'The model must call at least one tool each turn.',
|
|
22
|
+
},
|
|
23
|
+
{ value: 'none', label: 'None', hint: 'Tools are described but never called.' },
|
|
24
|
+
] as const
|
|
25
|
+
const expanded = ref<string | null>(null)
|
|
26
|
+
// Forms render once a tool is first opened, so long tool lists stay cheap to mount.
|
|
27
|
+
const opened = reactive(new Set<string>())
|
|
28
|
+
const copied = ref<string | null>(null)
|
|
29
|
+
const copyError = ref('')
|
|
30
|
+
let copiedTimer: ReturnType<typeof setTimeout> | undefined
|
|
31
|
+
const integrations = integrationTools ? undefined : useAgentIntegrationTools()
|
|
32
|
+
const integrationError = computed(() => integrations?.error.value)
|
|
33
|
+
const enabled = computed(() => agent.value.tools.filter((tool) => tool.enabled).length)
|
|
34
|
+
const choice = computed(() =>
|
|
35
|
+
Math.max(
|
|
36
|
+
0,
|
|
37
|
+
CHOICES.findIndex((item) => item.value === agent.value.toolChoice),
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
const suggestions = computed(() =>
|
|
41
|
+
(integrationTools ?? integrations?.data.value ?? []).filter(
|
|
42
|
+
(item) => !agent.value.tools.some((tool) => tool.name === item.name),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
const fieldUi = {
|
|
46
|
+
base: 'h-8 rounded-lg px-2.5 text-[13px] ring-(--gray-500) focus-visible:ring-(--gray-800)',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toggle(tool: AgentTool) {
|
|
50
|
+
opened.add(tool.id)
|
|
51
|
+
expanded.value = expanded.value === tool.id ? null : tool.id
|
|
52
|
+
}
|
|
53
|
+
function addTool(tool: AgentTool) {
|
|
54
|
+
agent.value.tools.push(tool)
|
|
55
|
+
opened.add(tool.id)
|
|
56
|
+
expanded.value = tool.id
|
|
57
|
+
}
|
|
58
|
+
function addBlankTool() {
|
|
59
|
+
let name = 'new_tool'
|
|
60
|
+
for (let index = 2; agent.value.tools.some((tool) => tool.name === name); index++) {
|
|
61
|
+
name = `new_tool_${index}`
|
|
62
|
+
}
|
|
63
|
+
addTool({ id: crypto.randomUUID(), name, description: '', enabled: true, parameters: [] })
|
|
64
|
+
}
|
|
65
|
+
function addIntegrationTool(item: AgentIntegrationTool) {
|
|
66
|
+
addTool({
|
|
67
|
+
id: crypto.randomUUID(),
|
|
68
|
+
name: item.name,
|
|
69
|
+
description: item.description,
|
|
70
|
+
enabled: true,
|
|
71
|
+
parameters: Object.entries(item.parameters.properties).map(([name, field]) => ({
|
|
72
|
+
name,
|
|
73
|
+
type: field.type,
|
|
74
|
+
description: field.description ?? '',
|
|
75
|
+
required: item.parameters.required.includes(name),
|
|
76
|
+
})),
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
function addParameter(tool: AgentTool) {
|
|
80
|
+
let name = 'parameter'
|
|
81
|
+
for (let index = 2; tool.parameters.some((parameter) => parameter.name === name); index++) {
|
|
82
|
+
name = `parameter_${index}`
|
|
83
|
+
}
|
|
84
|
+
tool.parameters.push({ name, type: 'string', description: '', required: false })
|
|
85
|
+
}
|
|
86
|
+
function identifier(value: string) {
|
|
87
|
+
return value.replace(/\s+/g, '_').replace(/[^\w-]/g, '')
|
|
88
|
+
}
|
|
89
|
+
function schema(tool: AgentTool) {
|
|
90
|
+
return JSON.stringify(agentToolSchema(tool, agent.value.provider), null, 2)
|
|
91
|
+
}
|
|
92
|
+
async function copy(tool: AgentTool) {
|
|
93
|
+
copyError.value = ''
|
|
94
|
+
try {
|
|
95
|
+
await navigator.clipboard.writeText(schema(tool))
|
|
96
|
+
copied.value = tool.id
|
|
97
|
+
clearTimeout(copiedTimer)
|
|
98
|
+
copiedTimer = setTimeout(() => (copied.value = null), 1400)
|
|
99
|
+
} catch {
|
|
100
|
+
copyError.value = 'Could not copy. Select the schema text and copy it.'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<template>
|
|
106
|
+
<div class="flex h-full min-h-0 w-full flex-col">
|
|
107
|
+
<div class="flex h-13 shrink-0 items-center gap-2.5 pr-2 pl-4">
|
|
108
|
+
<span class="text-[15px] text-highlighted">Tools</span>
|
|
109
|
+
<span class="flex-1 text-xs text-dimmed">
|
|
110
|
+
{{ enabled }} of {{ agent.tools.length }} enabled
|
|
111
|
+
</span>
|
|
112
|
+
<UButton
|
|
113
|
+
icon="i-lucide-x"
|
|
114
|
+
aria-label="Close tools"
|
|
115
|
+
variant="ghost"
|
|
116
|
+
square
|
|
117
|
+
class="size-7.5 hover:bg-(--gray-400)"
|
|
118
|
+
@click="$emit('close')"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="flex shrink-0 flex-col gap-1.5 border-b border-default px-4 pb-3">
|
|
122
|
+
<div
|
|
123
|
+
class="relative grid h-7.5 grid-cols-3 rounded-lg border border-(--gray-500) p-0.5"
|
|
124
|
+
role="group"
|
|
125
|
+
aria-label="Tool choice"
|
|
126
|
+
>
|
|
127
|
+
<span
|
|
128
|
+
class="pointer-events-none absolute top-0.5 bottom-0.5 left-0.5 w-[calc((100%-4px)/3)] rounded-md bg-(--gray-400) transition-transform duration-240 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
129
|
+
:class="['translate-x-0', 'translate-x-full', 'translate-x-[200%]'][choice]"
|
|
130
|
+
/>
|
|
131
|
+
<button
|
|
132
|
+
v-for="item in CHOICES"
|
|
133
|
+
:key="item.value"
|
|
134
|
+
type="button"
|
|
135
|
+
class="relative cursor-pointer rounded-md text-xs transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-accented motion-reduce:transition-none"
|
|
136
|
+
:class="
|
|
137
|
+
agent.toolChoice === item.value
|
|
138
|
+
? 'text-highlighted'
|
|
139
|
+
: 'text-muted hover:text-highlighted'
|
|
140
|
+
"
|
|
141
|
+
:aria-pressed="agent.toolChoice === item.value"
|
|
142
|
+
@click="agent.toolChoice = item.value"
|
|
143
|
+
>
|
|
144
|
+
{{ item.label }}
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
<p class="text-xs text-dimmed">{{ CHOICES[choice]!.hint }}</p>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
150
|
+
<div
|
|
151
|
+
v-for="tool in agent.tools"
|
|
152
|
+
:key="tool.id"
|
|
153
|
+
class="border-b border-default transition-colors duration-220 motion-reduce:transition-none"
|
|
154
|
+
:class="expanded === tool.id && 'bg-(--gray-300)'"
|
|
155
|
+
>
|
|
156
|
+
<div class="flex h-14 items-center gap-2.5 pr-2 pl-4">
|
|
157
|
+
<USwitch
|
|
158
|
+
v-model="tool.enabled"
|
|
159
|
+
:aria-label="`Enable ${tool.name}`"
|
|
160
|
+
size="xs"
|
|
161
|
+
/>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="flex h-11 min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-lg px-1.5 text-left transition-colors hover:bg-(--gray-400) focus-visible:outline-2 focus-visible:outline-accented motion-reduce:transition-none"
|
|
165
|
+
:aria-expanded="expanded === tool.id"
|
|
166
|
+
@click="toggle(tool)"
|
|
167
|
+
>
|
|
168
|
+
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
169
|
+
<span
|
|
170
|
+
class="truncate font-mono text-[13px] leading-4.5 transition-colors"
|
|
171
|
+
:class="tool.enabled ? 'text-highlighted' : 'text-dimmed'"
|
|
172
|
+
>
|
|
173
|
+
{{ tool.name || 'untitled_tool' }}
|
|
174
|
+
</span>
|
|
175
|
+
<span class="truncate text-xs leading-4 text-dimmed">
|
|
176
|
+
{{ tool.description || 'No description' }}
|
|
177
|
+
</span>
|
|
178
|
+
</span>
|
|
179
|
+
<UIcon
|
|
180
|
+
name="i-lucide-chevron-down"
|
|
181
|
+
class="size-4 shrink-0 text-muted transition-transform duration-240 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
182
|
+
:class="expanded === tool.id && 'rotate-180'"
|
|
183
|
+
/>
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
<div
|
|
187
|
+
class="grid transition-[grid-template-rows] duration-260 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
188
|
+
:class="expanded === tool.id ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
|
|
189
|
+
:inert="expanded !== tool.id"
|
|
190
|
+
>
|
|
191
|
+
<div class="min-h-0 overflow-hidden">
|
|
192
|
+
<div
|
|
193
|
+
v-if="opened.has(tool.id)"
|
|
194
|
+
class="flex flex-col gap-3.5 px-4 pt-0.5 pb-4"
|
|
195
|
+
>
|
|
196
|
+
<UFormField
|
|
197
|
+
label="Name"
|
|
198
|
+
:ui="{ label: 'text-xs', container: 'mt-1.5' }"
|
|
199
|
+
>
|
|
200
|
+
<UInput
|
|
201
|
+
:model-value="tool.name"
|
|
202
|
+
placeholder="tool_name"
|
|
203
|
+
class="w-full"
|
|
204
|
+
:ui="{ base: `${fieldUi.base} font-mono text-xs` }"
|
|
205
|
+
@update:model-value="tool.name = identifier(String($event))"
|
|
206
|
+
/>
|
|
207
|
+
</UFormField>
|
|
208
|
+
<UFormField
|
|
209
|
+
label="Description"
|
|
210
|
+
:ui="{ label: 'text-xs', container: 'mt-1.5' }"
|
|
211
|
+
>
|
|
212
|
+
<UInput
|
|
213
|
+
v-model="tool.description"
|
|
214
|
+
placeholder="What the tool does and when to call it"
|
|
215
|
+
class="w-full"
|
|
216
|
+
:ui="fieldUi"
|
|
217
|
+
/>
|
|
218
|
+
</UFormField>
|
|
219
|
+
<div class="flex flex-col gap-1.5">
|
|
220
|
+
<span class="text-xs text-muted">Parameters</span>
|
|
221
|
+
<div
|
|
222
|
+
v-for="(parameter, index) in tool.parameters"
|
|
223
|
+
:key="index"
|
|
224
|
+
class="flex flex-col gap-1.5 rounded-[10px] border border-default p-1.5"
|
|
225
|
+
>
|
|
226
|
+
<div class="grid grid-cols-[minmax(0,1fr)_72px_76px_28px] items-center gap-1.5">
|
|
227
|
+
<UInput
|
|
228
|
+
:model-value="parameter.name"
|
|
229
|
+
aria-label="Parameter name"
|
|
230
|
+
placeholder="name"
|
|
231
|
+
:ui="{ base: `${fieldUi.base} font-mono text-xs` }"
|
|
232
|
+
@update:model-value="parameter.name = identifier(String($event))"
|
|
233
|
+
/>
|
|
234
|
+
<USelect
|
|
235
|
+
v-model="parameter.type"
|
|
236
|
+
:items="[...AGENT_PARAMETER_TYPES]"
|
|
237
|
+
aria-label="Parameter type"
|
|
238
|
+
:ui="{
|
|
239
|
+
base: `${fieldUi.base} px-2 font-mono text-xs`,
|
|
240
|
+
trailingIcon: 'hidden',
|
|
241
|
+
content: 'min-w-28',
|
|
242
|
+
}"
|
|
243
|
+
/>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
class="h-8 cursor-pointer rounded-lg border border-(--gray-500) text-xs transition-colors duration-180 hover:text-highlighted motion-reduce:transition-none"
|
|
247
|
+
:class="
|
|
248
|
+
parameter.required ? 'bg-(--gray-500) text-highlighted' : 'text-dimmed'
|
|
249
|
+
"
|
|
250
|
+
:aria-pressed="parameter.required"
|
|
251
|
+
@click="parameter.required = !parameter.required"
|
|
252
|
+
>
|
|
253
|
+
{{ parameter.required ? 'Required' : 'Optional' }}
|
|
254
|
+
</button>
|
|
255
|
+
<UButton
|
|
256
|
+
icon="i-lucide-x"
|
|
257
|
+
aria-label="Remove parameter"
|
|
258
|
+
variant="ghost"
|
|
259
|
+
square
|
|
260
|
+
class="hover:bg-(--gray-400)"
|
|
261
|
+
@click="tool.parameters.splice(index, 1)"
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
<UInput
|
|
265
|
+
v-model="parameter.description"
|
|
266
|
+
aria-label="Parameter description"
|
|
267
|
+
placeholder="Description"
|
|
268
|
+
variant="none"
|
|
269
|
+
:ui="{ base: 'h-7 px-1 text-[13px] text-default' }"
|
|
270
|
+
/>
|
|
271
|
+
<p
|
|
272
|
+
v-if="parameter.type === 'array'"
|
|
273
|
+
class="px-1 text-xs text-dimmed"
|
|
274
|
+
>
|
|
275
|
+
A list of strings.
|
|
276
|
+
</p>
|
|
277
|
+
</div>
|
|
278
|
+
<p
|
|
279
|
+
v-if="!tool.parameters.length"
|
|
280
|
+
class="text-xs text-dimmed"
|
|
281
|
+
>
|
|
282
|
+
No parameters. The tool is called with an empty object.
|
|
283
|
+
</p>
|
|
284
|
+
<div>
|
|
285
|
+
<UButton
|
|
286
|
+
label="Add parameter"
|
|
287
|
+
icon="i-lucide-plus"
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="xs"
|
|
290
|
+
class="-ml-2 h-7 px-2 font-normal hover:bg-(--gray-400)"
|
|
291
|
+
@click="addParameter(tool)"
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="flex flex-col gap-1.5">
|
|
296
|
+
<div class="flex items-center justify-between">
|
|
297
|
+
<span class="text-xs text-muted">
|
|
298
|
+
Schema · {{ PROVIDER_LABELS[agent.provider] }} format
|
|
299
|
+
</span>
|
|
300
|
+
<UButton
|
|
301
|
+
:label="copied === tool.id ? 'Copied' : 'Copy'"
|
|
302
|
+
variant="ghost"
|
|
303
|
+
size="xs"
|
|
304
|
+
class="font-normal hover:bg-(--gray-400)"
|
|
305
|
+
@click="copy(tool)"
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
<pre
|
|
309
|
+
class="max-h-60 overflow-auto rounded-lg border border-default bg-(--gray-100) px-3.5 py-3 font-mono text-xs leading-4.5 text-default"
|
|
310
|
+
>{{ schema(tool) }}</pre>
|
|
311
|
+
<p
|
|
312
|
+
v-if="copyError"
|
|
313
|
+
role="alert"
|
|
314
|
+
class="text-xs text-error"
|
|
315
|
+
>
|
|
316
|
+
{{ copyError }}
|
|
317
|
+
</p>
|
|
318
|
+
</div>
|
|
319
|
+
<div class="flex justify-end">
|
|
320
|
+
<UButton
|
|
321
|
+
label="Remove tool"
|
|
322
|
+
variant="outline"
|
|
323
|
+
size="xs"
|
|
324
|
+
class="h-7 rounded-lg px-2.5 font-normal text-muted ring-(--gray-500) hover:bg-(--gray-400)"
|
|
325
|
+
@click="agent.tools = agent.tools.filter((item) => item.id !== tool.id)"
|
|
326
|
+
/>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<p
|
|
333
|
+
v-if="!agent.tools.length"
|
|
334
|
+
class="px-4 py-6 text-center text-[13px] text-dimmed"
|
|
335
|
+
>
|
|
336
|
+
No tools yet. The agent can only reply with text.
|
|
337
|
+
</p>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="flex shrink-0 flex-wrap items-center gap-1.5 border-t border-default p-2">
|
|
340
|
+
<UButton
|
|
341
|
+
label="Add tool"
|
|
342
|
+
icon="i-lucide-plus"
|
|
343
|
+
variant="ghost"
|
|
344
|
+
class="h-7.5 rounded-lg px-2.5 font-normal text-highlighted hover:bg-(--gray-400)"
|
|
345
|
+
@click="addBlankTool"
|
|
346
|
+
/>
|
|
347
|
+
<button
|
|
348
|
+
v-for="item in suggestions"
|
|
349
|
+
:key="item.name"
|
|
350
|
+
type="button"
|
|
351
|
+
:title="item.description"
|
|
352
|
+
class="h-6.5 cursor-pointer rounded-full border border-(--gray-500) px-2.5 font-mono text-xs whitespace-nowrap text-muted transition-colors hover:bg-(--gray-400) hover:text-highlighted focus-visible:outline-2 focus-visible:outline-accented active:scale-[0.97] motion-reduce:transition-none"
|
|
353
|
+
@click="addIntegrationTool(item)"
|
|
354
|
+
>
|
|
355
|
+
+ {{ item.name }}
|
|
356
|
+
</button>
|
|
357
|
+
<p
|
|
358
|
+
v-if="integrationError"
|
|
359
|
+
class="flex items-center gap-2 px-2 text-xs text-error"
|
|
360
|
+
>
|
|
361
|
+
Could not load integration tools.
|
|
362
|
+
<UButton
|
|
363
|
+
label="Retry"
|
|
364
|
+
variant="link"
|
|
365
|
+
size="xs"
|
|
366
|
+
@click="integrations?.refresh()"
|
|
367
|
+
/>
|
|
368
|
+
</p>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</template>
|