diff --git a/nginx/nginx.tmpl b/nginx/nginx.tmpl index e359f2da9..2f7487736 100644 --- a/nginx/nginx.tmpl +++ b/nginx/nginx.tmpl @@ -35,6 +35,22 @@ http { location /v1/healthz { return 200 'OK'; } + location ~ ^/teams/([^/]+)/agents/([^/]+)/(.*)$ { + set $team_id $1; + set $agent_name $2; + set $agent_path $3; + resolver ${KUBE_DNS_SERVICE_HOST} valid=10s; + proxy_pass http://$agent_name.team-$team_id.svc.cluster.local:9099/$agent_path$is_args$args; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # Support for streaming responses + proxy_buffering off; + proxy_cache off; + chunked_transfer_encoding on; + } location ~* \.(?:css|js|eot|woff|woff2|ttf|svg|otf) { # Enable GZip for static files gzip_static on; diff --git a/nginx/run.sh b/nginx/run.sh index 538084e37..e802c172c 100755 --- a/nginx/run.sh +++ b/nginx/run.sh @@ -4,6 +4,10 @@ CONTEXT_PATH=${CONTEXT_PATH:-''} find build -type f -print0 | xargs -0 sed -i -e "s/##CONTEXT_PATH##/$CONTEXT_PATH/g" -envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" /etc/nginx/nginx.conf +# Extract DNS nameserver for nginx resolver (fallback to resolv.conf if env var not set) +DNS_SERVER=${KUBE_DNS_SERVICE_HOST:-$(awk '/^nameserver/{print $2; exit}' /etc/resolv.conf)} + +# Generate nginx.conf with DNS resolver substituted +sed "s/\${KUBE_DNS_SERVICE_HOST}/$DNS_SERVER/g" /app/nginx.tmpl > /etc/nginx/nginx.conf exec nginx -g 'daemon off;' diff --git a/public/assets/agents_icon.svg b/public/assets/agents_icon.svg new file mode 100644 index 000000000..33621541e --- /dev/null +++ b/public/assets/agents_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/i18n/en/common.json b/public/i18n/en/common.json index eb6b64476..47b616a97 100644 --- a/public/i18n/en/common.json +++ b/public/i18n/en/common.json @@ -99,6 +99,10 @@ "Knowledge-base_plural": "Knowledge Bases", "TITLE_KNOWLEDGE_BASE": "Knowledge base details", "TITLE_KNOWLEDGE_BASES": "Knowledge bases - {{scope}}", + "Agent": "Agent", + "Agent_plural": "Agents", + "TITLE_AGENT": "Agent details", + "TITLE_AGENTS": "Agents - {{scope}}", "TITLE_NETWORK_POLICY": "Network policy details", "TITLE_NETWORK_POLICIES": "Network policies - {{scope}}" } diff --git a/src/App.tsx b/src/App.tsx index c3c96b903..e0e55f6d6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,8 @@ import CodeRepositoriesCreateEditPage from 'pages/code-repositories/create-edit/ import CodeRepositoriesOverviewPage from 'pages/code-repositories/overview/CodeRepositoriesOverviewPage' import KnowledgeBasesCreateEditPage from 'pages/knowledge-bases/create-edit/KnowledgeBasesCreateEditPage' import KnowledgeBasesOverviewPage from 'pages/knowledge-bases/overview/KnowledgeBasesOverviewPage' +import AgentsCreateEditPage from 'pages/agents/create-edit/AgentsCreateEditPage' +import AgentsOverviewPage from 'pages/agents/overview/AgentsOverviewPage' import NetworkPoliciesOverviewPage from 'pages/network-policies/overview/NetworkPoliciesOverviewPage' import NetworkPoliciesIngressCreateEditPage from 'pages/network-policies/create-edit/NetworkPoliciesIngressCreateEditPage' import NetworkPoliciesEgressCreateEditPage from 'pages/network-policies/create-edit/NetworkPoliciesEgressCreateEditPage' @@ -124,6 +126,18 @@ function App() { exact /> + + + + diff --git a/src/components/AgentPlayground.tsx b/src/components/AgentPlayground.tsx new file mode 100644 index 000000000..a0895c9aa --- /dev/null +++ b/src/components/AgentPlayground.tsx @@ -0,0 +1,331 @@ +import React, { useEffect, useRef, useState } from 'react' +import { Alert, Box, IconButton, TextField, Typography, keyframes } from '@mui/material' +import SendIcon from '@mui/icons-material/Send' +import DeleteIcon from '@mui/icons-material/Delete' +import StopIcon from '@mui/icons-material/StopCircle' +import Markdown from './Markdown' +import { Paper } from './Paper' +import Iconify from './Iconify' + +const thinkingAnimation = keyframes` + 0%, 60%, 100% { + opacity: 0.3; + } + 30% { + opacity: 1; + } +` + +interface Message { + role: 'user' | 'assistant' + content: string + id: string +} + +interface AgentPlaygroundProps { + teamId: string + agentName: string +} + +export function AgentPlayground({ teamId, agentName }: AgentPlaygroundProps): React.ReactElement { + const [messages, setMessages] = useState([]) + const [input, setInput] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const messagesEndRef = useRef(null) + const messagesContainerRef = useRef(null) + const abortControllerRef = useRef(null) + + const scrollToBottom = () => { + if (messagesContainerRef.current) messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight + } + + useEffect(() => { + scrollToBottom() + }, [messages]) + + const handleStop = () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + abortControllerRef.current = null + setLoading(false) + // Remove the empty assistant message if it exists + setMessages((prev) => { + const lastMessage = prev[prev.length - 1] + if (lastMessage?.role === 'assistant' && !lastMessage.content) return prev.slice(0, -1) + + return prev + }) + } + } + + const handleSend = async () => { + if (!input.trim() || loading) return + + const userMessage: Message = { role: 'user', content: input.trim(), id: `user-${Date.now()}` } + const assistantId = `assistant-${Date.now()}` + const newMessages = [...messages, userMessage] + + // Immediately add user message and empty assistant message for thinking animation + setMessages([...newMessages, { role: 'assistant', content: '', id: assistantId }]) + setInput('') + setLoading(true) + setError(null) + + // Create new abort controller for this request + abortControllerRef.current = new AbortController() + + try { + // Call agent service through nginx proxy to handle http and mixed-content issues + // In development: use /agent proxy (port-forward to localhost:9099) + // In cluster: use /teams/{teamId}/agents/{agentName} proxy (nginx routes to internal service) + const isDev = process.env.NODE_ENV === 'development' + const agentServiceUrl = isDev + ? `/agent/v1/chat/completions` + : `/teams/${teamId}/agents/${agentName}/v1/chat/completions` + + const requestBody = { + messages: newMessages.map((msg) => ({ role: msg.role, content: msg.content })), + stream: true, + model: 'rag-pipeline', + } + + const response = await fetch(agentServiceUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + signal: abortControllerRef.current.signal, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) + const errorMessage = errorData?.error?.message || `Error: ${response.status}` + throw new Error(typeof errorMessage === 'string' ? errorMessage : 'Unknown error') + } + + // Handle streaming response + const reader = response.body?.getReader() + const decoder = new TextDecoder() + + if (reader) { + // Process streaming response + const processStream = async () => { + let accumulatedContent = '' + // eslint-disable-next-line no-constant-condition + while (true) { + // eslint-disable-next-line no-await-in-loop + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value, { stream: true }) + const lines = chunk.split('\n') + + // eslint-disable-next-line no-loop-func + lines.forEach((line) => { + if (line.startsWith('data: ')) { + const data = line.slice(6) + if (data === '[DONE]') return + + try { + const parsed = JSON.parse(data) + const content = parsed.choices?.[0]?.delta?.content || '' + if (content) { + accumulatedContent += content + const currentContent = accumulatedContent + setMessages((prev) => { + const updated = [...prev] + const lastIndex = updated.length - 1 + if (lastIndex >= 0 && updated[lastIndex].id === assistantId) + updated[lastIndex] = { ...updated[lastIndex], content: currentContent } + return updated + }) + } + } catch { + // Ignore JSON parse errors for malformed chunks + } + } + }) + } + } + + await processStream() + } else { + // Non-streaming response + const data = await response.json() + const assistantContent = data.choices?.[0]?.message?.content || 'No response' + setMessages([...newMessages, { role: 'assistant', content: assistantContent, id: assistantId }]) + } + } catch (err) { + // Don't show error if request was aborted by user + if (err instanceof Error && err.name === 'AbortError') return + + const errorMessage = err instanceof Error ? err.message : 'Failed to send message' + setError(errorMessage) + } finally { + setLoading(false) + abortControllerRef.current = null + } + } + + const handleClear = () => { + setMessages([]) + setError(null) + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSend() + } + } + + return ( + + + {/* Header */} + + Agent Playground + + + + + + {/* Messages Area */} + + {messages.length === 0 ? ( + + + + Ask your agent a question to start evaluating. + + + ) : ( + messages.map((message) => ( + + + + {message.role === 'user' ? 'You' : 'Agent'} + + {(() => { + // Show thinking animation for empty assistant message while loading + if (message.role === 'assistant' && !message.content && loading) { + return ( + + + + + + ) + } + + // Render assistant message with markdown + if (message.role === 'assistant') { + return ( + + ) + } + + return ( + + {message.content} + + ) + })()} + + + )) + )} +
+ + + {/* Error Display */} + {error && ( + setError(null)}> + {error} + + )} + + {/* Input Area */} + + setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder='Type your message...' + disabled={loading} + variant='outlined' + size='small' + /> + {loading ? ( + + + + ) : ( + + + + )} + + + + ) +} diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 0e651c5c1..345dcc4aa 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -1,4 +1,7 @@ -import { Card, styled } from '@mui/material' +import React, { useState } from 'react' +import { Box, Card, IconButton, Tooltip, styled } from '@mui/material' +import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import CheckIcon from '@mui/icons-material/Check' import MarkdownJSX from 'markdown-to-jsx' // Higher-order component to generate markdown components @@ -30,15 +33,57 @@ const Code = createMDComp('code', { backgroundColor: '#6e768164', textWrap: 'pretty', }) -const Pre = createMDComp('pre', { - ...mb, - backgroundColor: '#6e768164', - padding: '12px', - borderRadius: '6px', - '& > code': { - backgroundColor: 'transparent', - }, -}) + +// Code block with copy button +function CodeBlock({ children }: any) { + const [copied, setCopied] = useState(false) + + const handleCopy = () => { + const codeText = children?.props?.children || children + const textToCopy = typeof codeText === 'string' ? codeText : String(codeText) + navigator.clipboard.writeText(textToCopy.trim()) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( + + code': { + backgroundColor: 'transparent', + }, + }} + > + {children} + + + + {copied ? : } + + + + ) +} const Ol = createMDComp('ol', { ...mb, paddingLeft: '32px' }) const Li = createMDComp('li', { ...lh }) const Table = createMDComp('table', { ...mb, borderCollapse: 'collapse' }) @@ -77,7 +122,7 @@ export default function Markdown({ readme, sx }: Props) { component: Code, }, pre: { - component: Pre, + component: CodeBlock, }, li: { component: Li, diff --git a/src/components/NavConfig.tsx b/src/components/NavConfig.tsx index 60a50a843..fba7b25f8 100644 --- a/src/components/NavConfig.tsx +++ b/src/components/NavConfig.tsx @@ -52,6 +52,12 @@ export default function NavConfig() { path: `/catalogs/${oboTeamId}`, icon: getIcon('developer_guide_icon.svg'), }, + { + title: 'Agents', + path: `/teams/${oboTeamId}/agents`, + icon: getIcon('agents_icon.svg'), + hidden: !aiEnabled, + }, { title: 'Knowledge Bases', path: `/teams/${oboTeamId}/knowledge-bases`, diff --git a/src/components/Setting.tsx b/src/components/Setting.tsx index 1c3f158d2..ed5e4f907 100644 --- a/src/components/Setting.tsx +++ b/src/components/Setting.tsx @@ -67,6 +67,7 @@ export const getSettingUiSchema = (settings: GetSettingsInfoApiResponse, setting isPreInstalled: { 'ui:widget': 'hidden' }, adminPassword: { 'ui:widget': 'hidden' }, useORCS: { 'ui:widget': 'hidden' }, + aiEnabled: { 'ui:widget': 'hidden' }, }, kms: { sops: { diff --git a/src/components/forms/TextArea.tsx b/src/components/forms/TextArea.tsx index 7d6e41e2b..854cadbf3 100644 --- a/src/components/forms/TextArea.tsx +++ b/src/components/forms/TextArea.tsx @@ -168,6 +168,11 @@ export function AutoResizableTextarea({ adjustSize() }, [rest.value]) + // Sync internal value state with external value prop + useEffect(() => { + if (!showLock && rest.value !== value) setValue(rest.value) + }, [rest.value, showLock]) + return ( ): React.ReactElement { + const { t } = useTranslation() + + const [create, { isLoading: isLoadingCreate, isSuccess: isSuccessCreate }] = useCreateAplAgentMutation() + const [update, { isLoading: isLoadingUpdate, isSuccess: isSuccessUpdate }] = useEditAplAgentMutation() + const [del, { isLoading: isLoadingDelete, isSuccess: isSuccessDelete }] = useDeleteAplAgentMutation() + const { data, isLoading, isFetching, isError, refetch } = useGetAplAgentQuery( + { teamId, agentName }, + { skip: !agentName }, + ) + const { data: aiModels } = useGetAiModelsQuery() + const { data: knowledgeBases } = useGetAplKnowledgeBasesQuery({ teamId }) + + const isDirty = useAppSelector(({ global: { isDirty } }) => isDirty) + useEffect(() => { + if (isDirty !== false) return + if (!isFetching) refetch() + }, [isDirty]) + + type FormType = CreateAplAgentApiArg['body'] + + const defaultValues: FormType = { + kind: 'AkamaiAgent' as const, + metadata: { + name: '', + }, + spec: { + foundationModel: '', + agentInstructions: '', + tools: [], + }, + } + + const methods = useForm({ + resolver: yupResolver(agentSchema) as unknown as Resolver, + defaultValues, + }) + + const { + register, + reset, + handleSubmit, + watch, + formState: { errors }, + setValue, + } = methods + + useEffect(() => { + if (data) reset(data) + }, [data, reset]) + + const onSubmit = (formData: FormType) => { + const body = { ...formData } + + if (agentName) update({ teamId, agentName, body }) + else create({ teamId, body }) + } + + const mutating = isLoadingCreate || isLoadingUpdate || isLoadingDelete + if (!mutating && (isSuccessCreate || isSuccessUpdate || isSuccessDelete)) + return + + const loading = isLoading + + if (loading) return + + return ( + + + + +
+
+ + { + const value = e.target.value + setValue('metadata.name', value) + }} + error={!!errors.metadata?.name} + helperText={errors.metadata?.name?.message?.toString()} + disabled={!!agentName} + /> + + + + + + model.spec.modelType === 'foundation') + .map((model) => model.metadata.name) || [] + } + getOptionLabel={(option) => { + const model = aiModels?.find((m) => m.metadata.name === option) + return model?.spec.displayName || option + }} + value={watch('spec.foundationModel') || null} + onChange={(_, value) => { + setValue('spec.foundationModel', value || '') + }} + errorText={errors.spec?.foundationModel?.message?.toString()} + helperText={errors.spec?.foundationModel?.message?.toString()} + /> + + + + + + kb.metadata.name) || []} + value={watch('spec.tools')?.find((tool) => tool.type === 'knowledgeBase')?.name || ''} + onChange={(_, value) => { + const currentTools = watch('spec.tools') || [] + const nonKbTools = currentTools.filter((tool) => tool.type !== 'knowledgeBase') + const updatedTools = value ? [...nonKbTools, { type: 'knowledgeBase', name: value }] : nonKbTools + setValue('spec.tools', updatedTools) + }} + /> + + + + + + { + const value = e.target.value + setValue('spec.agentInstructions', value) + }} + error={!!errors.spec?.agentInstructions} + value={watch('spec.agentInstructions') || ''} + /> + +
+ + {agentName && ( + del({ teamId, agentName })} + resourceName={watch('metadata.name')} + resourceType='agent' + data-cy='button-delete-agent' + sx={{ float: 'right', textTransform: 'capitalize', ml: 2 }} + loading={isLoadingDelete} + disabled={isLoadingDelete || isLoadingCreate || isLoadingUpdate} + /> + )} + + {agentName ? 'Save Changes' : 'Create Agent'} + + +
+ {agentName && } +
+
+ ) +} diff --git a/src/pages/agents/create-edit/create-edit-agents.validator.ts b/src/pages/agents/create-edit/create-edit-agents.validator.ts new file mode 100644 index 000000000..44a6c15f8 --- /dev/null +++ b/src/pages/agents/create-edit/create-edit-agents.validator.ts @@ -0,0 +1,21 @@ +import * as yup from 'yup' + +// Schema for Agent form validation +export const agentSchema = yup.object({ + kind: yup.string().oneOf(['AkamaiAgent']).required(), + metadata: yup.object({ + name: yup + .string() + .required('Agent name is required') + .min(2, 'Agent name must be at least 2 characters') + .matches( + /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/, + 'Name must start and end with a lowercase letter or number, and can only contain lowercase letters, numbers, and hyphens', + ), + }), + spec: yup.object({ + foundationModel: yup.string().required('Please select a foundation model'), + knowledgeBase: yup.string().optional(), + agentInstructions: yup.string().required('Please enter agent instructions'), + }), +}) diff --git a/src/pages/agents/index.tsx b/src/pages/agents/index.tsx new file mode 100644 index 000000000..7e029d6bd --- /dev/null +++ b/src/pages/agents/index.tsx @@ -0,0 +1,7 @@ +import AgentsOverviewPage from './overview/AgentsOverviewPage' +import AgentsCreateEditPage from './create-edit/AgentsCreateEditPage' + +export default { + AgentsOverviewPage, + AgentsCreateEditPage, +} diff --git a/src/pages/agents/overview/AgentsOverviewPage.tsx b/src/pages/agents/overview/AgentsOverviewPage.tsx new file mode 100644 index 000000000..915cc62aa --- /dev/null +++ b/src/pages/agents/overview/AgentsOverviewPage.tsx @@ -0,0 +1,93 @@ +import PaperLayout from 'layouts/Paper' +import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { RouteComponentProps } from 'react-router-dom' +import { getRole } from 'utils/data' +import { useGetAplAgentsQuery } from 'redux/otomiApi' +import { useAppSelector } from 'redux/hooks' +import { HeadCell } from '../../../components/EnhancedTable' +import RLink from '../../../components/Link' +import ListTable from '../../../components/ListTable' + +const getAgentName = (): CallableFunction => + function (row: any): string | React.ReactElement { + const { teamId, name }: { teamId: string; name: string } = row + const path = `/teams/${teamId}/agents/${encodeURIComponent(name)}` + return ( + + {name} + + ) + } + +const getStatus = (): CallableFunction => + function (row: any): string { + const { status } = row + return status?.phase || 'Unknown' + } + +const getFoundationModel = (): CallableFunction => + function (row: any): string { + const { foundationModel } = row + return foundationModel || 'N/A' + } + +interface Params { + teamId: string +} + +export default function AgentsOverviewPage({ + match: { + params: { teamId }, + }, +}: RouteComponentProps): React.ReactElement { + const { t } = useTranslation() + + const { data: agents, isLoading, isFetching, refetch } = useGetAplAgentsQuery({ teamId }) + + const isDirty = useAppSelector(({ global: { isDirty } }) => isDirty) + useEffect(() => { + if (isDirty !== false) return + if (!isFetching) refetch() + }, [isDirty]) + + // Transform API response to match table format + const transformedData = + agents?.map((agent) => ({ + name: agent.metadata.name, + teamId, + status: agent.status, + foundationModel: agent.spec.foundationModel, + })) || [] + + const headCells: HeadCell[] = [ + { + id: 'name', + label: t('Name'), + renderer: getAgentName(), + }, + { + id: 'status', + label: t('Status'), + renderer: getStatus(), + }, + { + id: 'foundationModel', + label: t('Foundation Model'), + renderer: getFoundationModel(), + }, + ] + + const customButtonText = () => Create Agent + + const comp = ( + + ) + return +} diff --git a/src/pages/knowledge-bases/overview/KnowledgeBasesOverviewPage.tsx b/src/pages/knowledge-bases/overview/KnowledgeBasesOverviewPage.tsx index a642a40cd..9ec4e3d45 100644 --- a/src/pages/knowledge-bases/overview/KnowledgeBasesOverviewPage.tsx +++ b/src/pages/knowledge-bases/overview/KnowledgeBasesOverviewPage.tsx @@ -26,12 +26,6 @@ const getStatus = (): CallableFunction => return status?.phase || 'Unknown' } -const getDataSource = (): CallableFunction => - function (row: any): string | React.ReactElement { - const { sourceUrl }: { sourceUrl: string } = row - return sourceUrl || 'N/A' - } - const getEmbeddingModel = (): CallableFunction => function (row: any): string { const { modelName } = row @@ -63,7 +57,6 @@ export default function KnowledgeBasesOverviewPage({ name: kb.metadata.name, teamId, status: kb.status, - sourceUrl: kb.spec.sourceUrl, modelName: kb.spec.modelName, })) || [] @@ -78,11 +71,6 @@ export default function KnowledgeBasesOverviewPage({ label: t('Status'), renderer: getStatus(), }, - { - id: 'datasource', - label: t('Data Source'), - renderer: getDataSource(), - }, { id: 'embeddingModel', label: t('Embedding Model'), diff --git a/src/redux/otomiApi.ts b/src/redux/otomiApi.ts index 5d7817f6b..5c538b644 100644 --- a/src/redux/otomiApi.ts +++ b/src/redux/otomiApi.ts @@ -6438,9 +6438,14 @@ export type DeleteAplKnowledgeBaseApiArg = { export type GetAplAgentsApiResponse = /** status 200 Successfully obtained agents */ ({ kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { @@ -6468,9 +6473,14 @@ export type GetAplAgentsApiArg = { export type CreateAplAgentApiResponse = /** status 200 Successfully stored agent configuration */ { kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { @@ -6498,9 +6508,14 @@ export type CreateAplAgentApiArg = { body: { kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { @@ -6511,9 +6526,14 @@ export type CreateAplAgentApiArg = { export type GetAplAgentApiResponse = /** status 200 Successfully obtained agent configuration */ { kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { @@ -6543,9 +6563,14 @@ export type GetAplAgentApiArg = { export type EditAplAgentApiResponse = /** status 200 Successfully edited a team agent */ { kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { @@ -6575,9 +6600,14 @@ export type EditAplAgentApiArg = { body: { kind: 'AkamaiAgent' spec: { - knowledgeBase?: string foundationModel: string agentInstructions: string + tools?: { + type: string + name: string + description?: string + endpoint?: string + }[] } } & { metadata: { diff --git a/src/setupProxy.js b/src/setupProxy.js index aa8f95ec3..bf3d43122 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -9,6 +9,11 @@ module.exports = function (app) { logLevel: 'debug', pathRewrite: { '^/api/ws': '/ws' }, }), + proxy('/agent', { + target: 'http://localhost:9099', + pathRewrite: { '^/agent': '' }, + changeOrigin: true, + }), proxy('/api', { target: 'http://localhost:8080', pathRewrite: { '^/api/': '/' },