@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
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Proprietary software. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Use of Nuxt Agentic packages and starter materials is governed by the applicable
|
|
4
|
+
commercial agreement supplied with a purchase. This repository does not grant
|
|
5
|
+
an open-source license or permission to redistribute the packages as a competing
|
|
6
|
+
template or module library.
|
|
7
|
+
|
|
8
|
+
Third-party dependencies retain their respective licenses. This notice does not
|
|
9
|
+
replace or modify those licenses.
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Saved agents run through CloudConfig's provider harness. “Test agent” runs the
|
|
4
|
+
saved version with JSON input and displays progress, approval waits and output.
|
|
5
|
+
Runs are scoped to the authenticated owner, active workspace, agent and session.
|
|
6
|
+
|
|
7
|
+
Agent code never names a platform. `server/cloudconfig/agents.ts`
|
|
8
|
+
default-exports `defineAgentHost(...)`, built from `agentRunHost`; server code
|
|
9
|
+
opens runs and schedules through `useCloudConfigAgents(event)` or
|
|
10
|
+
`getAgentPlatform(env)`. CloudConfig's Nuxt module creates the platform's agent
|
|
11
|
+
from the host: the official Cloudflare Agents SDK with SQLite checkpoints in
|
|
12
|
+
Cloudflare builds, and an in-memory runner with the local CLI adapters in
|
|
13
|
+
development.
|
|
14
|
+
|
|
15
|
+
Deployment declarations live in the host application's `cloud.config.ts`: `deploy.agents`
|
|
16
|
+
names the host entry, and `deploy.workflows` exports the automation runner. Use `cloudconfig deploy` to reconcile their bindings and
|
|
17
|
+
migrations. OpenAI uses `NUXT_OPENAI_API_KEY`; configure
|
|
18
|
+
`NUXT_ANTHROPIC_API_KEY` or `NUXT_GOOGLE_AI_API_KEY` for those providers.
|
|
19
|
+
Optional keys are pushed when supplied locally. Secrets never enter saved
|
|
20
|
+
definitions or the browser.
|
|
21
|
+
|
|
22
|
+
`@nuxtagentic/agents` provides the Agents collection and Markdown editor. The
|
|
23
|
+
framework-independent definitions, ownership rules, validation, frontmatter and
|
|
24
|
+
provider tool schemas belong to `@cloudconfig/core/agents` in CloudConfig.
|
|
25
|
+
|
|
26
|
+
Agents is a core feature, always available in every workspace. It has no install
|
|
27
|
+
or remove controls in the Modules catalog.
|
|
28
|
+
|
|
29
|
+
Open Agents in the workspace navigation, or type `agents` in the hero prompt.
|
|
30
|
+
`Create agent: Release notes writer` creates a named draft agent.
|
|
31
|
+
**New agent** inserts a draft record right away and opens it; edit its
|
|
32
|
+
instructions, choose a runtime and model, and add tool declarations. Edits
|
|
33
|
+
autosave after an 800 ms pause. **⌘/Ctrl Enter** saves immediately. Leaving the editor saves pending
|
|
34
|
+
edits first; failed saves keep your changes available to retry. Search and selection
|
|
35
|
+
live in the URL; open `/dashboard/agents` or link directly with
|
|
36
|
+
`/dashboard/agents?agent=<id>`. Older query-based links redirect to these URLs.
|
|
37
|
+
|
|
38
|
+
In local development at `localhost`, the runtime picker lists Codex CLI and
|
|
39
|
+
Claude Code when they are installed on the server's PATH (including
|
|
40
|
+
`~/.local/bin`). Install the CLI and sign in using `codex login` or
|
|
41
|
+
`claude auth login`. Model choices come from the installed runtime's catalog;
|
|
42
|
+
**Runtime default** uses the CLI default. Runtime and model have separate inline
|
|
43
|
+
menus. Search the model list by name or ID, or enter a custom ID. Claude
|
|
44
|
+
discovery follows Open Run: it includes models with effort support alongside the
|
|
45
|
+
latest model in each family, and falls back to the `opus`, `sonnet` and `haiku`
|
|
46
|
+
aliases. Catalogs are read once per installed CLI version (or Codex models cache)
|
|
47
|
+
and reused by every editor in the session; opening a menu refreshes them after a
|
|
48
|
+
minute.
|
|
49
|
+
|
|
50
|
+
Local runtime and model choices are saved separately from the production API
|
|
51
|
+
provider and model. The cloud button opens that API configuration when using a
|
|
52
|
+
local runtime. Deployed hosts always execute through OpenAI, Anthropic, or Google APIs;
|
|
53
|
+
they never attempt to spawn a local process. Configure the selected server key:
|
|
54
|
+
`NUXT_AGENTS_OPENAI_API_KEY`, `NUXT_AGENTS_ANTHROPIC_API_KEY`, or
|
|
55
|
+
`NUXT_AGENTS_GOOGLE_AI_API_KEY`. Existing `NUXT_OPENAI_API_KEY`,
|
|
56
|
+
`NUXT_ANTHROPIC_API_KEY`, `NUXT_GOOGLE_AI_API_KEY` and Automations key overrides
|
|
57
|
+
continue to work. Add your chosen key to `deploy.secrets` before deploying with
|
|
58
|
+
CloudConfig. API model lists are fetched from the provider account, cached for an
|
|
59
|
+
hour and refreshed in the background; saved and custom IDs remain editable if
|
|
60
|
+
discovery is unavailable.
|
|
61
|
+
|
|
62
|
+
`@cloudconfig/core/agents` owns the runtime fields, validation and Markdown
|
|
63
|
+
frontmatter alongside the existing agent contract. This package supplies the UI,
|
|
64
|
+
catalog discovery and local execution adapters. Both execution paths use the
|
|
65
|
+
granted integration handlers; local CLI built-in tools are disabled.
|
|
66
|
+
|
|
67
|
+
The visual editor and Markdown source edit the same definition. Source shows the
|
|
68
|
+
settings as YAML frontmatter: runtime, model, status, tool choice and the names
|
|
69
|
+
of enabled tools. The toolbar and Tools drawer stay available in both views and
|
|
70
|
+
update the frontmatter as you change them. Tool declarations are edited in the
|
|
71
|
+
drawer; frontmatter with complete JSON tool declarations is still accepted.
|
|
72
|
+
The tools drawer supports enabling tools, auto/required/none choice, parameters,
|
|
73
|
+
and copying schemas for OpenAI Responses, Anthropic and Google. Connected
|
|
74
|
+
integration tools appear as suggestions at the bottom of the drawer.
|
|
75
|
+
|
|
76
|
+
To add Agents to another host, install its local packages and register
|
|
77
|
+
`@nuxtagentic/agents` in Nuxt. It remains available outside the optional catalog. Include `agentsModels` from its
|
|
78
|
+
`schema` export in `cloud.config.ts`, then generate and apply a migration. The
|
|
79
|
+
host uses generated CloudConfig CRUD and the typed SDK; no custom agent CRUD
|
|
80
|
+
routes or database tables are needed.
|
|
81
|
+
|
|
82
|
+
Active marks a definition ready for automatic execution. Use
|
|
83
|
+
[Automations](../automations/README.md) to run it from events and webhooks, or use
|
|
84
|
+
a test run while the definition is still a draft. Configure its provider key on
|
|
85
|
+
the server. Connect accounts in [Integrations](../integrations/README.md), then pick an
|
|
86
|
+
integration tool suggestion in the Tools drawer to attach granted runtime handlers.
|
|
87
|
+
Custom tool declarations need a handler before execution. The existing
|
|
88
|
+
`@nuxtagentic/prompt` package continues to own workspace prompt planning and
|
|
89
|
+
reviewed mutations.
|
|
90
|
+
|
|
91
|
+
Deployment verification runs `pnpm build` followed by `pnpm test:worker`. The
|
|
92
|
+
Worker test loads the complete production bundle with its real D1 migrations,
|
|
93
|
+
authentication, Nuxt SSR, Agent and AutomationRunner exports. Provider responses
|
|
94
|
+
are mocked, so verification does not call a paid model API. `pnpm test:agents`
|
|
95
|
+
also checks durable execution and the generated deployment wrapper, while
|
|
96
|
+
`pnpm test:automations` checks approvals, retries and integration permissions.
|
|
97
|
+
|
|
98
|
+
The production build uses a bounded Node heap and exposes garbage collection.
|
|
99
|
+
Default code verification is `pnpm typecheck`; build and runtime verification
|
|
100
|
+
require an explicit request.
|
package/module.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { addRouteMiddleware, createResolver, defineNuxtModule } from '@nuxt/kit'
|
|
2
|
+
import { registerWorkspaceModule } from '@nuxtagentic/kit'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtModule({
|
|
5
|
+
meta: { name: '@nuxtagentic/agents' },
|
|
6
|
+
moduleDependencies: { '@nuxtagentic/workspace': {} },
|
|
7
|
+
setup(_options, nuxt) {
|
|
8
|
+
const resolver = createResolver(import.meta.url)
|
|
9
|
+
nuxt.options.runtimeConfig.agents = Object.assign(
|
|
10
|
+
{ openaiApiKey: '', anthropicApiKey: '', googleAiApiKey: '' },
|
|
11
|
+
nuxt.options.runtimeConfig.agents,
|
|
12
|
+
)
|
|
13
|
+
registerWorkspaceModule(
|
|
14
|
+
{
|
|
15
|
+
id: 'agents',
|
|
16
|
+
runtime: resolver.resolve('./runtime'),
|
|
17
|
+
feature: { from: resolver.resolve('./runtime/index.ts'), name: 'agentsFeature' },
|
|
18
|
+
metadata: resolver.resolve('./runtime/shared/agent.ts'),
|
|
19
|
+
},
|
|
20
|
+
nuxt,
|
|
21
|
+
)
|
|
22
|
+
addRouteMiddleware({
|
|
23
|
+
name: 'agents-route',
|
|
24
|
+
path: resolver.resolve('./runtime/middleware/agents-route.ts'),
|
|
25
|
+
global: true,
|
|
26
|
+
})
|
|
27
|
+
nuxt.hook('pages:extend', (pages) => {
|
|
28
|
+
pages.push({
|
|
29
|
+
name: 'dashboard-agents',
|
|
30
|
+
path: '/dashboard/agents',
|
|
31
|
+
file: resolver.resolve('./runtime/pages/agents.vue'),
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtagentic/agents",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"module.ts",
|
|
7
|
+
"runtime",
|
|
8
|
+
"schema.ts",
|
|
9
|
+
"README.md",
|
|
10
|
+
"server.ts",
|
|
11
|
+
"LICENSE.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./module.ts",
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
"./schema": "./schema.ts",
|
|
17
|
+
"./server": "./server.ts",
|
|
18
|
+
"./runtime/*.vue": "./runtime/*.vue",
|
|
19
|
+
"./runtime/*.css": "./runtime/*.css",
|
|
20
|
+
"./runtime/*.json": "./runtime/*.json",
|
|
21
|
+
"./runtime/*.svg": "./runtime/*.svg",
|
|
22
|
+
"./runtime/*": "./runtime/*.ts"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@cloudconfig/core": "0.2.0-beta.2",
|
|
26
|
+
"@codemirror/commands": "^6.11.1",
|
|
27
|
+
"@codemirror/lang-markdown": "^6.5.2",
|
|
28
|
+
"@codemirror/lang-yaml": "^6.1.3",
|
|
29
|
+
"@codemirror/language": "^6.13.1",
|
|
30
|
+
"@codemirror/state": "^6.7.6",
|
|
31
|
+
"@codemirror/view": "^6.43.14",
|
|
32
|
+
"@lezer/highlight": "^1.2.5",
|
|
33
|
+
"@nuxt/kit": "4.6.1",
|
|
34
|
+
"@nuxt/ui": "^4.11.1",
|
|
35
|
+
"h3": "1.15.11",
|
|
36
|
+
"vue-router": "^5.4.0",
|
|
37
|
+
"@nuxtagentic/prompt": "0.1.0",
|
|
38
|
+
"@nuxtagentic/data": "0.1.0",
|
|
39
|
+
"@nuxtagentic/kit": "0.1.0",
|
|
40
|
+
"@nuxtagentic/workspace": "0.1.0",
|
|
41
|
+
"@nuxtagentic/integrations": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"nuxt": "^4.6.1",
|
|
45
|
+
"vue": "^3.5.43"
|
|
46
|
+
},
|
|
47
|
+
"cloudconfig": {
|
|
48
|
+
"models": "agentsModels"
|
|
49
|
+
},
|
|
50
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
51
|
+
"description": "Saved agent definitions, editing, tools and execution for Nuxt Agentic.",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": "^22.22.3 || ^24.15.0 || >=26.0.0"
|
|
58
|
+
}
|
|
6
59
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentAction } from '@nuxtagentic/workspace/runtime/shared/agent-actions'
|
|
2
|
+
|
|
3
|
+
export const actions = {
|
|
4
|
+
agents: [
|
|
5
|
+
{
|
|
6
|
+
id: 'new',
|
|
7
|
+
name: 'New agent',
|
|
8
|
+
aliases: ['Create agent', 'Create an agent'],
|
|
9
|
+
description: 'Write the responsibilities and tools for a new agent.',
|
|
10
|
+
icon: 'i-lucide-plus',
|
|
11
|
+
run: (context) => context.navigate('/dashboard/agents', { agent: 'new' }),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'save',
|
|
15
|
+
name: 'Save agent',
|
|
16
|
+
description: 'Save this agent’s instructions and tools.',
|
|
17
|
+
icon: 'i-lucide-check',
|
|
18
|
+
available: (context) => context.canRun('agents.save'),
|
|
19
|
+
run: (context) => context.run('agents.save'),
|
|
20
|
+
},
|
|
21
|
+
] satisfies AgentAction[],
|
|
22
|
+
}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
|
|
3
|
+
import { defineShortcuts, navigateTo, useRoute } from '#imports'
|
|
4
|
+
import { onBeforeRouteLeave, onBeforeRouteUpdate, type RouteLocationNormalized } from 'vue-router'
|
|
5
|
+
import { agentName, validateAgentDefinition, type AgentDefinition } from '@cloudconfig/core/agents'
|
|
6
|
+
import { useAgentActionHandler } from '@nuxtagentic/workspace/runtime/composables/useAgentActions'
|
|
7
|
+
import { parseAgentSource, serializeAgentSource } from '../shared/source'
|
|
8
|
+
import { MENU_UI, PILL_CLASS } from '../shared/presentation'
|
|
9
|
+
import RuntimeModelPicker from './RuntimeModelPicker.vue'
|
|
10
|
+
import SourceEditor from './SourceEditor.vue'
|
|
11
|
+
import AgentToolPicker from './AgentToolPicker.vue'
|
|
12
|
+
import type { AgentRuntimeCatalog } from '../shared/runtime'
|
|
13
|
+
import type { AgentIntegrationTool } from '../shared/integration-tool'
|
|
14
|
+
|
|
15
|
+
const { agent, busy, error, active, removing, save, embedded, runtimeCatalog, integrationTools } =
|
|
16
|
+
defineProps<{
|
|
17
|
+
agent: AgentDefinition
|
|
18
|
+
busy: boolean
|
|
19
|
+
error: string
|
|
20
|
+
active: boolean
|
|
21
|
+
removing: boolean
|
|
22
|
+
save: (agent: AgentDefinition) => Promise<boolean>
|
|
23
|
+
embedded?: boolean
|
|
24
|
+
runtimeCatalog?: AgentRuntimeCatalog
|
|
25
|
+
integrationTools?: AgentIntegrationTool[]
|
|
26
|
+
}>()
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
duplicate: [agent: AgentDefinition]
|
|
29
|
+
remove: []
|
|
30
|
+
close: []
|
|
31
|
+
}>()
|
|
32
|
+
function clone(agent: AgentDefinition): AgentDefinition {
|
|
33
|
+
return JSON.parse(JSON.stringify(agent))
|
|
34
|
+
}
|
|
35
|
+
const MODES = [
|
|
36
|
+
{ value: 'visual', label: 'Visual', icon: 'i-lucide-eye' },
|
|
37
|
+
{ value: 'source', label: 'Markdown', icon: 'i-lucide-code' },
|
|
38
|
+
] as const
|
|
39
|
+
const draft = ref<AgentDefinition>(clone(agent))
|
|
40
|
+
const mode = ref<'visual' | 'source'>('visual')
|
|
41
|
+
// What the visual editor holds; it stays mounted while Markdown source is shown.
|
|
42
|
+
const visualMarkdown = ref(draft.value.markdown)
|
|
43
|
+
const source = ref('')
|
|
44
|
+
const sourceError = ref('')
|
|
45
|
+
const validationError = ref('')
|
|
46
|
+
const isToolsOpen = ref(false)
|
|
47
|
+
const isToolsMounted = ref(false)
|
|
48
|
+
const visualInput = useTemplateRef('visualInput')
|
|
49
|
+
const editorRoot = useTemplateRef('editorRoot')
|
|
50
|
+
const sourceInput = useTemplateRef('sourceInput')
|
|
51
|
+
const route = useRoute()
|
|
52
|
+
const isDiscardOpen = ref(false)
|
|
53
|
+
let nextLocation = ''
|
|
54
|
+
let canNavigate = false
|
|
55
|
+
let isParsing = false
|
|
56
|
+
const baseline = ref(JSON.stringify(agent))
|
|
57
|
+
const snapshot = computed(() => JSON.stringify(draft.value))
|
|
58
|
+
const hasChanges = computed(() => !!sourceError.value || snapshot.value !== baseline.value)
|
|
59
|
+
let savePromise: Promise<boolean> | null = null
|
|
60
|
+
const enabled = computed(() => draft.value.tools.filter((tool) => tool.enabled).length)
|
|
61
|
+
const settings = computed(() => {
|
|
62
|
+
const { provider, model, status, toolChoice, tools, runtime, localModel } = draft.value
|
|
63
|
+
return serializeAgentSource({
|
|
64
|
+
markdown: '',
|
|
65
|
+
provider,
|
|
66
|
+
model,
|
|
67
|
+
status,
|
|
68
|
+
toolChoice,
|
|
69
|
+
tools,
|
|
70
|
+
runtime,
|
|
71
|
+
localModel,
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
const moreItems = computed(() => [
|
|
75
|
+
{ label: 'Duplicate', disabled: busy, onSelect: duplicate },
|
|
76
|
+
{ label: 'Delete agent', disabled: busy, onSelect: () => emit('remove') },
|
|
77
|
+
])
|
|
78
|
+
const toolbar = [
|
|
79
|
+
[
|
|
80
|
+
{
|
|
81
|
+
kind: 'mark' as const,
|
|
82
|
+
mark: 'bold' as const,
|
|
83
|
+
icon: 'i-lucide-bold',
|
|
84
|
+
tooltip: { text: 'Bold' },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
kind: 'mark' as const,
|
|
88
|
+
mark: 'italic' as const,
|
|
89
|
+
icon: 'i-lucide-italic',
|
|
90
|
+
tooltip: { text: 'Italic' },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
kind: 'mark' as const,
|
|
94
|
+
mark: 'code' as const,
|
|
95
|
+
icon: 'i-lucide-code',
|
|
96
|
+
tooltip: { text: 'Inline code' },
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
]
|
|
100
|
+
const layer =
|
|
101
|
+
'min-w-0 [grid-area:1/1] transition-[opacity,translate,visibility] duration-200 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none'
|
|
102
|
+
const shown = 'visible opacity-100'
|
|
103
|
+
const hidden = 'invisible translate-y-1 opacity-0'
|
|
104
|
+
|
|
105
|
+
// Toolbar and drawer changes rewrite the settings; typed source is never reformatted.
|
|
106
|
+
watch(
|
|
107
|
+
settings,
|
|
108
|
+
() => {
|
|
109
|
+
if (mode.value === 'source' && !isParsing && !sourceError.value) {
|
|
110
|
+
source.value = serializeAgentSource(draft.value)
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{ flush: 'sync' },
|
|
114
|
+
)
|
|
115
|
+
function updateVisual(value: string) {
|
|
116
|
+
visualMarkdown.value = value
|
|
117
|
+
draft.value.markdown = value
|
|
118
|
+
}
|
|
119
|
+
function syncVisual() {
|
|
120
|
+
const editor = visualInput.value?.editor
|
|
121
|
+
if (!editor || draft.value.markdown === visualMarkdown.value) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
// Loading source edits must not round-trip TipTap's normalized Markdown into the draft.
|
|
125
|
+
editor.commands.setContent(draft.value.markdown, { contentType: 'markdown', emitUpdate: false })
|
|
126
|
+
visualMarkdown.value = editor.getMarkdown()
|
|
127
|
+
}
|
|
128
|
+
function updateSource(value: string) {
|
|
129
|
+
source.value = value
|
|
130
|
+
isParsing = true
|
|
131
|
+
try {
|
|
132
|
+
draft.value = parseAgentSource(value, draft.value)
|
|
133
|
+
sourceError.value = ''
|
|
134
|
+
} catch (cause) {
|
|
135
|
+
sourceError.value = cause instanceof Error ? cause.message : 'Check the agent settings.'
|
|
136
|
+
} finally {
|
|
137
|
+
isParsing = false
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function setMode(value: typeof mode.value) {
|
|
141
|
+
if (mode.value === value || (value === 'visual' && sourceError.value)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (value === 'source') {
|
|
145
|
+
source.value = serializeAgentSource(draft.value)
|
|
146
|
+
} else {
|
|
147
|
+
syncVisual()
|
|
148
|
+
}
|
|
149
|
+
mode.value = value
|
|
150
|
+
await nextTick()
|
|
151
|
+
if (value === 'visual') {
|
|
152
|
+
visualInput.value?.editor?.commands.focus()
|
|
153
|
+
} else {
|
|
154
|
+
sourceInput.value?.focus()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function toggleTools() {
|
|
158
|
+
isToolsMounted.value = true
|
|
159
|
+
isToolsOpen.value = !isToolsOpen.value
|
|
160
|
+
}
|
|
161
|
+
function validate() {
|
|
162
|
+
validationError.value =
|
|
163
|
+
sourceError.value ||
|
|
164
|
+
validateAgentDefinition({ ...draft.value })
|
|
165
|
+
.map((issue) => issue.message)
|
|
166
|
+
.join(' ')
|
|
167
|
+
return !validationError.value
|
|
168
|
+
}
|
|
169
|
+
watch([snapshot, sourceError, () => removing], (_, __, onCleanup) => {
|
|
170
|
+
validationError.value = ''
|
|
171
|
+
if (!active || removing || !hasChanges.value || sourceError.value) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const timer = setTimeout(() => void saveDraft(), 800)
|
|
175
|
+
onCleanup(() => clearTimeout(timer))
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
async function saveDraft(): Promise<boolean> {
|
|
179
|
+
if (savePromise && !(await savePromise)) {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
if (!hasChanges.value) {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
if (busy || !validate()) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
const submitted = snapshot.value
|
|
189
|
+
savePromise = save(clone(draft.value))
|
|
190
|
+
.then((saved) => {
|
|
191
|
+
if (saved) {
|
|
192
|
+
baseline.value = submitted
|
|
193
|
+
}
|
|
194
|
+
return saved
|
|
195
|
+
})
|
|
196
|
+
.finally(() => {
|
|
197
|
+
savePromise = null
|
|
198
|
+
})
|
|
199
|
+
return savePromise
|
|
200
|
+
}
|
|
201
|
+
async function duplicate() {
|
|
202
|
+
if (await saveDraft()) {
|
|
203
|
+
emit('duplicate', clone(draft.value))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function guardNavigation(to: RouteLocationNormalized) {
|
|
207
|
+
if (embedded || !active || canNavigate || removing) {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
if (to.path === route.path && to.query.agent === route.query.agent) {
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
if (await saveDraft()) {
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
nextLocation = to.fullPath
|
|
217
|
+
isDiscardOpen.value = true
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
function discard() {
|
|
221
|
+
canNavigate = true
|
|
222
|
+
isDiscardOpen.value = false
|
|
223
|
+
void navigateTo(nextLocation)
|
|
224
|
+
}
|
|
225
|
+
onBeforeRouteLeave(guardNavigation)
|
|
226
|
+
onBeforeRouteUpdate(guardNavigation)
|
|
227
|
+
useAgentActionHandler('agents.save', saveDraft, () => !embedded && active && !busy)
|
|
228
|
+
defineShortcuts({
|
|
229
|
+
meta_enter: {
|
|
230
|
+
usingInput: true,
|
|
231
|
+
handler: () => {
|
|
232
|
+
if (
|
|
233
|
+
active &&
|
|
234
|
+
(!embedded || editorRoot.value?.contains(document.activeElement)) &&
|
|
235
|
+
!document.querySelector(
|
|
236
|
+
'[data-state="open"]:is([role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"])',
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
saveDraft()
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
function onKeydown(event: KeyboardEvent) {
|
|
245
|
+
if (
|
|
246
|
+
event.key === 'Escape' &&
|
|
247
|
+
isToolsOpen.value &&
|
|
248
|
+
!event.defaultPrevented &&
|
|
249
|
+
!document.querySelector(
|
|
250
|
+
'[data-state="open"]:is([role="dialog"], [role="menu"], [role="listbox"])',
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
event.preventDefault()
|
|
254
|
+
event.stopPropagation()
|
|
255
|
+
isToolsOpen.value = false
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
defineExpose({ save: saveDraft })
|
|
260
|
+
</script>
|
|
261
|
+
|
|
262
|
+
<template>
|
|
263
|
+
<section
|
|
264
|
+
ref="editorRoot"
|
|
265
|
+
class="relative flex h-full min-h-0 min-w-0 flex-col overflow-hidden"
|
|
266
|
+
:aria-label="agentName(draft.markdown)"
|
|
267
|
+
@keydown="onKeydown"
|
|
268
|
+
>
|
|
269
|
+
<div
|
|
270
|
+
class="relative z-20 flex h-13 shrink-0 items-center gap-1.5 border-b border-default pr-3 pl-4"
|
|
271
|
+
>
|
|
272
|
+
<UButton
|
|
273
|
+
icon="i-lucide-arrow-left"
|
|
274
|
+
aria-label="Back to agents"
|
|
275
|
+
variant="ghost"
|
|
276
|
+
square
|
|
277
|
+
class="-ml-1.5 size-7.5 md:hidden"
|
|
278
|
+
@click="$emit('close')"
|
|
279
|
+
/>
|
|
280
|
+
<label
|
|
281
|
+
class="flex h-7.5 shrink-0 cursor-pointer items-center rounded-full border border-(--gray-500) pr-2.5 pl-1.5 transition-colors duration-160 hover:bg-(--gray-400) motion-reduce:transition-none"
|
|
282
|
+
:title="draft.status === 'active' ? 'Active' : 'Draft'"
|
|
283
|
+
>
|
|
284
|
+
<USwitch
|
|
285
|
+
:model-value="draft.status === 'active'"
|
|
286
|
+
size="xs"
|
|
287
|
+
color="success"
|
|
288
|
+
:ui="{ thumb: 'data-[state=checked]:bg-white' }"
|
|
289
|
+
aria-label="Active agent"
|
|
290
|
+
@update:model-value="draft.status = $event ? 'active' : 'draft'"
|
|
291
|
+
/>
|
|
292
|
+
</label>
|
|
293
|
+
<div class="flex min-w-0 items-center gap-1.5">
|
|
294
|
+
<RuntimeModelPicker
|
|
295
|
+
v-model="draft"
|
|
296
|
+
:catalog="runtimeCatalog"
|
|
297
|
+
>
|
|
298
|
+
<UButton
|
|
299
|
+
icon="i-lucide-wrench"
|
|
300
|
+
:label="draft.tools.length ? `${enabled}/${draft.tools.length}` : 'Tools'"
|
|
301
|
+
title="Tools"
|
|
302
|
+
:aria-expanded="isToolsOpen"
|
|
303
|
+
variant="ghost"
|
|
304
|
+
class="shrink-0"
|
|
305
|
+
:class="PILL_CLASS"
|
|
306
|
+
:ui="{ leadingIcon: 'size-3.5' }"
|
|
307
|
+
@click="toggleTools"
|
|
308
|
+
/>
|
|
309
|
+
</RuntimeModelPicker>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="min-w-0 flex-1" />
|
|
312
|
+
<div
|
|
313
|
+
role="group"
|
|
314
|
+
aria-label="Editor view"
|
|
315
|
+
class="relative grid h-7.5 w-17 shrink-0 grid-cols-2 rounded-lg border border-(--gray-500) p-0.5"
|
|
316
|
+
>
|
|
317
|
+
<span
|
|
318
|
+
class="pointer-events-none absolute top-0.5 bottom-0.5 left-0.5 w-[calc((100%-4px)/2)] rounded-md bg-(--gray-400) transition-transform duration-240 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none"
|
|
319
|
+
:class="mode === 'source' && 'translate-x-full'"
|
|
320
|
+
/>
|
|
321
|
+
<button
|
|
322
|
+
v-for="view in MODES"
|
|
323
|
+
:key="view.value"
|
|
324
|
+
type="button"
|
|
325
|
+
:title="view.label"
|
|
326
|
+
:aria-label="view.label"
|
|
327
|
+
:aria-pressed="mode === view.value"
|
|
328
|
+
:disabled="view.value === 'visual' && !!sourceError"
|
|
329
|
+
class="relative grid cursor-pointer place-items-center rounded-md transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-accented disabled:cursor-not-allowed motion-reduce:transition-none"
|
|
330
|
+
:class="mode === view.value ? 'text-highlighted' : 'text-muted hover:text-highlighted'"
|
|
331
|
+
@click="setMode(view.value)"
|
|
332
|
+
>
|
|
333
|
+
<UIcon
|
|
334
|
+
:name="view.icon"
|
|
335
|
+
class="size-3.5"
|
|
336
|
+
/>
|
|
337
|
+
</button>
|
|
338
|
+
</div>
|
|
339
|
+
<UDropdownMenu
|
|
340
|
+
:items="moreItems"
|
|
341
|
+
:content="{ align: 'end', sideOffset: 6 }"
|
|
342
|
+
:ui="{
|
|
343
|
+
...MENU_UI,
|
|
344
|
+
content: 'w-45 rounded-[10px] p-1.5 ring-(--gray-500)',
|
|
345
|
+
item: `${MENU_UI.item} min-h-8.5`,
|
|
346
|
+
}"
|
|
347
|
+
>
|
|
348
|
+
<UButton
|
|
349
|
+
icon="i-lucide-ellipsis"
|
|
350
|
+
aria-label="Agent actions"
|
|
351
|
+
variant="ghost"
|
|
352
|
+
square
|
|
353
|
+
class="size-7.5 shrink-0 rounded-lg hover:bg-(--gray-400) aria-expanded:bg-(--gray-400)"
|
|
354
|
+
:ui="{ leadingIcon: 'size-4' }"
|
|
355
|
+
/>
|
|
356
|
+
</UDropdownMenu>
|
|
357
|
+
</div>
|
|
358
|
+
<UAlert
|
|
359
|
+
v-if="validationError || sourceError || error"
|
|
360
|
+
:title="validationError || sourceError || error"
|
|
361
|
+
color="error"
|
|
362
|
+
variant="subtle"
|
|
363
|
+
class="shrink-0 rounded-none"
|
|
364
|
+
/>
|
|
365
|
+
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
366
|
+
<div class="mx-auto grid max-w-180 px-[clamp(16px,4vw,40px)] pt-10 pb-40">
|
|
367
|
+
<UEditor
|
|
368
|
+
ref="visualInput"
|
|
369
|
+
v-slot="{ editor }"
|
|
370
|
+
:model-value="visualMarkdown"
|
|
371
|
+
content-type="markdown"
|
|
372
|
+
:mention="false"
|
|
373
|
+
:inert="mode !== 'visual'"
|
|
374
|
+
:editor-props="{
|
|
375
|
+
attributes: {
|
|
376
|
+
'aria-label': 'Agent instructions',
|
|
377
|
+
role: 'textbox',
|
|
378
|
+
'aria-multiline': 'true',
|
|
379
|
+
},
|
|
380
|
+
}"
|
|
381
|
+
placeholder="Write this agent’s responsibilities…"
|
|
382
|
+
:class="[layer, mode === 'visual' ? shown : hidden]"
|
|
383
|
+
:ui="{
|
|
384
|
+
base: 'min-h-120 text-[15px] leading-6 text-default caret-highlighted *:my-0 sm:px-0 [&_p]:mb-3 [&_p]:leading-6 [&_:is(h1,h2,h3,h4,h5,h6)]:font-medium [&_h1]:mb-2 [&_h1]:text-[28px] [&_h1]:leading-9 [&_h1]:tracking-[-0.01em] [&_h1+p]:mb-2 [&_h1+p]:text-muted [&_h2]:mt-7 [&_h2]:mb-1.5 [&_h2]:text-[17px] [&_h2]:leading-[26px] [&_h3]:mt-6 [&_h3]:mb-1.5 [&_h3]:text-base [&_h3]:leading-6 [&_:is(ul,ol)]:mb-3 [&_:is(ul,ol)]:ps-6 [&_li]:my-0 [&_li]:mb-1 [&_li]:ps-0.5 [&_li>p]:mb-0 [&_strong]:font-semibold [&_strong]:text-highlighted [&_code]:inline [&_code]:rounded [&_code]:border-0 [&_code]:bg-(--gray-400) [&_code]:px-[5px] [&_code]:py-px [&_code]:text-[13px] [&_code]:font-normal [&_blockquote]:mb-5 [&_blockquote]:rounded-lg [&_blockquote]:border-s-0 [&_blockquote]:bg-(--gray-300) [&_blockquote]:px-4 [&_blockquote]:py-3 [&_blockquote]:not-italic [&_blockquote]:text-highlighted',
|
|
385
|
+
}"
|
|
386
|
+
@update:model-value="updateVisual"
|
|
387
|
+
>
|
|
388
|
+
<UEditorToolbar
|
|
389
|
+
:editor="editor"
|
|
390
|
+
:items="toolbar"
|
|
391
|
+
layout="bubble"
|
|
392
|
+
/>
|
|
393
|
+
</UEditor>
|
|
394
|
+
<SourceEditor
|
|
395
|
+
ref="sourceInput"
|
|
396
|
+
:model-value="source"
|
|
397
|
+
:inert="mode !== 'source'"
|
|
398
|
+
label="Agent Markdown source"
|
|
399
|
+
:class="[layer, mode === 'source' ? shown : hidden]"
|
|
400
|
+
@update:model-value="updateSource"
|
|
401
|
+
/>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
<aside
|
|
405
|
+
class="absolute top-13 right-0 bottom-0 z-10 flex w-[min(440px,100%)] flex-col border-l border-default bg-(--gray-200) pb-19 shadow-[-16px_0_48px_rgb(0_0_0/0.12)] transition-[translate,opacity] duration-260 ease-[cubic-bezier(.2,0,0,1)] motion-reduce:transition-none dark:shadow-[-16px_0_48px_rgb(0_0_0/0.35)]"
|
|
406
|
+
:class="
|
|
407
|
+
isToolsOpen
|
|
408
|
+
? 'translate-x-0 opacity-100'
|
|
409
|
+
: 'pointer-events-none translate-x-[calc(100%+24px)] opacity-0'
|
|
410
|
+
"
|
|
411
|
+
:inert="!isToolsOpen"
|
|
412
|
+
:aria-hidden="!isToolsOpen"
|
|
413
|
+
>
|
|
414
|
+
<AgentToolPicker
|
|
415
|
+
v-if="isToolsMounted"
|
|
416
|
+
v-model="draft"
|
|
417
|
+
:integration-tools="integrationTools"
|
|
418
|
+
@close="isToolsOpen = false"
|
|
419
|
+
/>
|
|
420
|
+
</aside>
|
|
421
|
+
</section>
|
|
422
|
+
<UModal
|
|
423
|
+
v-model:open="isDiscardOpen"
|
|
424
|
+
title="Discard unsaved changes?"
|
|
425
|
+
>
|
|
426
|
+
<template #body>
|
|
427
|
+
<p class="text-sm text-muted">
|
|
428
|
+
Your changes could not be saved. Keep editing to retry, or discard them.
|
|
429
|
+
</p>
|
|
430
|
+
</template>
|
|
431
|
+
<template #footer>
|
|
432
|
+
<div class="flex w-full justify-end gap-2">
|
|
433
|
+
<UButton
|
|
434
|
+
label="Keep editing"
|
|
435
|
+
variant="outline"
|
|
436
|
+
@click="isDiscardOpen = false"
|
|
437
|
+
/>
|
|
438
|
+
<UButton
|
|
439
|
+
label="Discard changes"
|
|
440
|
+
color="error"
|
|
441
|
+
@click="discard"
|
|
442
|
+
/>
|
|
443
|
+
</div>
|
|
444
|
+
</template>
|
|
445
|
+
</UModal>
|
|
446
|
+
</template>
|