├── tools ├── header.txt ├── vendor.go └── devmode-kubeconfig ├── docs ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── obot-mcp-mgmt.png │ │ ├── high-level-arch.png │ │ ├── okta-group-claims.png │ │ ├── webhook-overview.png │ │ ├── entra-client-secret.png │ │ ├── entra-app-registration.png │ │ ├── gateway-architecture.webp │ │ ├── token-exchange-flow.webp │ │ ├── okta-client-credentials.png │ │ ├── add-mcp-server-type-selector.png │ │ ├── entra-configured-permissions.png │ │ ├── entra-new-configured-permissions.png │ │ └── tutorials │ │ ├── slack-agent │ │ ├── agent-config.png │ │ ├── chat-example.png │ │ └── slack-tools.png │ │ ├── github-agent │ │ ├── github-tools.png │ │ ├── github-agent-config.png │ │ └── github-chat-example.png │ │ └── knowledge-agent │ │ ├── agent-config.png │ │ ├── chat-example.png │ │ └── knowledge-config.png ├── docs │ ├── concepts │ │ └── _category_.json │ └── enterprise │ │ └── overview.md ├── versions.json ├── versioned_docs │ ├── version-v0.13.0 │ │ ├── concepts │ │ │ └── _category_.json │ │ ├── tutorials │ │ │ └── _category_.json │ │ └── enterprise │ │ │ └── overview.md │ ├── version-v0.14.0 │ │ ├── concepts │ │ │ └── _category_.json │ │ ├── tutorials │ │ │ └── _category_.json │ │ └── enterprise │ │ │ └── overview.md │ └── version-v0.15.0 │ │ ├── concepts │ │ └── _category_.json │ │ └── enterprise │ │ └── overview.md ├── .gitignore └── VERSIONING_INSTRUCTIONS.md ├── ui └── user │ ├── .npmrc │ ├── src │ ├── lib │ │ ├── actions │ │ │ ├── index.ts │ │ │ ├── size.svelte.ts │ │ │ └── overflow.ts │ │ ├── components │ │ │ ├── messages │ │ │ │ ├── preset │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── composed │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── keymap.ts │ │ │ │ │ │ ├── inputrules.ts │ │ │ │ │ │ ├── plugins.ts │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ └── commands.ts │ │ │ │ │ └── plaintext.ts │ │ │ │ └── MessageIcon.svelte │ │ │ ├── ui │ │ │ │ ├── render │ │ │ │ │ ├── index.ts │ │ │ │ │ └── render.svelte │ │ │ │ ├── multi-value-input │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── virtual-page │ │ │ │ │ └── index.ts │ │ │ ├── admin │ │ │ │ ├── usage │ │ │ │ │ └── types.ts │ │ │ │ └── filters-drawer │ │ │ │ │ └── types.ts │ │ │ ├── primitives │ │ │ │ └── draggable │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── contextItem.ts │ │ │ │ │ └── contextRoot.ts │ │ │ ├── edit │ │ │ │ ├── Memories.svelte │ │ │ │ └── SystemPrompt.svelte │ │ │ ├── Spinner.svelte │ │ │ ├── Error.svelte │ │ │ ├── editor │ │ │ │ ├── Pdf.svelte │ │ │ │ └── Image.svelte │ │ │ ├── Logo.svelte │ │ │ ├── SuccessNotifications.svelte │ │ │ ├── Success.svelte │ │ │ ├── OverflowContainer.svelte │ │ │ └── ReLoginDialog.svelte │ │ ├── services │ │ │ ├── admin │ │ │ │ ├── index.ts │ │ │ │ └── constants.ts │ │ │ ├── chat │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── format.ts │ │ ├── tools.ts │ │ ├── icons │ │ │ └── Loading.svelte │ │ ├── stores │ │ │ ├── responsive.svelte.ts │ │ │ ├── version.svelte.ts │ │ │ ├── index.ts │ │ │ ├── errors.svelte.ts │ │ │ ├── success.ts │ │ │ └── profile.svelte.ts │ │ ├── image.ts │ │ └── context │ │ │ ├── layout.svelte.ts │ │ │ ├── admin │ │ │ └── models.svelte.ts │ │ │ └── projectTools.svelte.ts │ ├── hooks.server.ts │ ├── routes │ │ ├── auth │ │ │ └── mcp │ │ │ │ └── composite │ │ │ │ └── [composite_mcp_id] │ │ │ │ ├── +page.ts │ │ │ │ └── +page.svelte │ │ ├── usage │ │ │ ├── +page.ts │ │ │ └── +page.svelte │ │ ├── audit-logs │ │ │ ├── +page.ts │ │ │ └── +page.svelte │ │ ├── login_complete │ │ │ └── +page.svelte │ │ ├── admin │ │ │ ├── filters │ │ │ │ ├── [id] │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── +page.svelte │ │ │ │ └── +page.ts │ │ │ ├── chat-threads │ │ │ │ └── [id] │ │ │ │ │ └── +page.ts │ │ │ ├── users │ │ │ │ └── +page.ts │ │ │ ├── user-roles │ │ │ │ └── +page.ts │ │ │ ├── chat-configuration │ │ │ │ └── +page.ts │ │ │ ├── server-scheduling │ │ │ │ └── +page.ts │ │ │ ├── model-providers │ │ │ │ └── +page.ts │ │ │ ├── mcp-registries │ │ │ │ ├── [id] │ │ │ │ │ └── +page.ts │ │ │ │ ├── w │ │ │ │ │ └── [wid] │ │ │ │ │ │ └── r │ │ │ │ │ │ └── [id] │ │ │ │ │ │ └── +page.ts │ │ │ │ └── +page.ts │ │ │ ├── mcp-servers │ │ │ │ ├── w │ │ │ │ │ └── [wid] │ │ │ │ │ │ ├── c │ │ │ │ │ │ └── [id] │ │ │ │ │ │ │ ├── +page.ts │ │ │ │ │ │ │ └── instance │ │ │ │ │ │ │ └── [ms_id] │ │ │ │ │ │ │ ├── details │ │ │ │ │ │ │ └── +page.ts │ │ │ │ │ │ │ └── +page.ts │ │ │ │ │ │ └── s │ │ │ │ │ │ └── [id] │ │ │ │ │ │ ├── +page.ts │ │ │ │ │ │ └── details │ │ │ │ │ │ └── +page.ts │ │ │ │ ├── c │ │ │ │ │ └── [id] │ │ │ │ │ │ ├── +page.ts │ │ │ │ │ │ └── instance │ │ │ │ │ │ └── [ms_id] │ │ │ │ │ │ ├── details │ │ │ │ │ │ └── +page.ts │ │ │ │ │ │ └── +page.ts │ │ │ │ └── s │ │ │ │ │ └── [id] │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── details │ │ │ │ │ └── +page.ts │ │ │ ├── usage │ │ │ │ └── +page.svelte │ │ │ ├── tasks │ │ │ │ └── [id] │ │ │ │ │ └── +page.ts │ │ │ ├── auth-providers │ │ │ │ └── +page.ts │ │ │ ├── task-runs │ │ │ │ └── [id] │ │ │ │ │ └── +page.ts │ │ │ ├── +page.ts │ │ │ └── audit-logs │ │ │ │ └── +page.svelte │ │ ├── t │ │ │ └── [id] │ │ │ │ └── +page.ts │ │ ├── i │ │ │ └── [code] │ │ │ │ └── +page.ts │ │ ├── mcp-servers │ │ │ ├── +page.ts │ │ │ ├── c │ │ │ │ └── [id] │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── instance │ │ │ │ │ └── [ms_id] │ │ │ │ │ └── details │ │ │ │ │ └── +page.ts │ │ │ └── s │ │ │ │ └── [id] │ │ │ │ ├── +page.ts │ │ │ │ └── details │ │ │ │ └── +page.ts │ │ ├── mcp-registries │ │ │ ├── [id] │ │ │ │ └── +page.ts │ │ │ └── +page.ts │ │ ├── [agent] │ │ │ ├── +page.svelte │ │ │ └── +page.ts │ │ └── chat │ │ │ └── +page.ts │ └── app.d.ts │ ├── .prettierignore │ ├── .build │ ├── static │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── agent │ │ └── images │ │ │ ├── survey.webp │ │ │ ├── hubspot.webp │ │ │ ├── jobhunt.webp │ │ │ ├── mork-borg.webp │ │ │ ├── postgres.webp │ │ │ ├── ci-failure.webp │ │ │ ├── smartthings.webp │ │ │ ├── create-a-chat.webp │ │ │ ├── slack2github.webp │ │ │ ├── create-from-mcp.webp │ │ │ ├── obot_placeholder.webp │ │ │ ├── create-a-template.webp │ │ │ ├── create-from-scratch.webp │ │ │ ├── workout-assistant.webp │ │ │ ├── wordpress-blog-assistant.webp │ │ │ └── zoom-meeting-action-image.webp │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── landing │ │ └── images │ │ │ ├── landing.webp │ │ │ └── landing_dark.webp │ ├── admin │ │ └── assets │ │ │ ├── amazon-bedrock.png │ │ │ ├── google_icon_small.png │ │ │ ├── obot_openai_antenna_icon.png │ │ │ ├── anthropic_icon.svg │ │ │ ├── xai-logo.svg │ │ │ └── azure_openai_icon.svg │ └── user │ │ └── images │ │ ├── sharing-agent.webp │ │ ├── under-construction.webp │ │ ├── share-project-delivery.webp │ │ ├── share-project-snapshot.webp │ │ ├── sharing-agent-expired.webp │ │ ├── social │ │ ├── facebook-mark.svg │ │ ├── twitter-mark.svg │ │ ├── youtube-mark.svg │ │ └── linkedin-mark.svg │ │ ├── assistant │ │ ├── highlightai-mark.svg │ │ └── vscode-mark.svg │ │ └── github-mark │ │ ├── github-mark.svg │ │ └── github-mark-white.svg │ ├── postcss.config.js │ ├── .gitignore │ ├── .prettierrc │ ├── vite.config.ts │ ├── tsconfig.json │ └── svelte.config.js ├── .dockerignore ├── apiclient ├── types │ ├── doc.go │ ├── userdefaultrole.go │ ├── activity.go │ ├── file.go │ ├── eula.go │ ├── poweruserworkspace.go │ ├── memory.go │ ├── authprovider.go │ ├── workflowexecution.go │ ├── mcpcatalog.go │ ├── filescannerprovider.go │ ├── types.go │ ├── emailreceiver.go │ ├── k8ssettings.go │ ├── credential.go │ ├── cronjob.go │ ├── projectshare.go │ ├── webhook.go │ ├── defaultmodelalias.go │ ├── tool.go │ ├── mcpserverdetails.go │ ├── usage.go │ └── template.go ├── go.mod └── files.go ├── pkg ├── storage │ ├── apis │ │ └── obot.obot.ai │ │ │ ├── group.go │ │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── generationed.go │ │ │ ├── prompts.go │ │ │ ├── time.go │ │ │ ├── refs.go │ │ │ ├── userdelete.go │ │ │ ├── userrolechange.go │ │ │ ├── usergroupchange.go │ │ │ ├── grouprolechange.go │ │ │ ├── template.go │ │ │ ├── userdefaultrolesetting.go │ │ │ ├── mcpsession.go │ │ │ └── apppreferences.go │ ├── selectors │ │ └── selectors.go │ ├── scheme │ │ └── scheme.go │ ├── authz │ │ └── authz.go │ └── tables │ │ └── table │ │ └── fmt.go ├── cli │ ├── templates │ │ ├── fs.go │ │ ├── emailreceiver.yaml │ │ └── webhook.yaml │ ├── version.go │ ├── server.go │ ├── textio │ │ └── input.go │ ├── events │ │ └── events.go │ ├── run_debug.go │ ├── token.go │ ├── output.go │ ├── tools_update.go │ ├── table.go │ ├── run_print.go │ ├── tools_unregister.go │ ├── tools_register.go │ ├── invokeclient │ │ └── input.go │ └── threadprint.go ├── gateway │ ├── types │ │ ├── migration.go │ │ ├── property.go │ │ ├── virusscanner.go │ │ ├── images.go │ │ ├── runstate.go │ │ ├── tempsetupuser.go │ │ └── grouproleassignment.go │ ├── server │ │ └── response.go │ ├── db │ │ ├── creds.go │ │ ├── drop_session_cookies.go │ │ └── remove_github_groups.go │ ├── client │ │ ├── token.go │ │ ├── error.go │ │ ├── image.go │ │ └── property.go │ └── context │ │ └── context.go ├── system │ ├── mcp.go │ ├── bin.go │ └── currentbin.go ├── version │ └── version.go ├── api │ ├── authz │ │ ├── user.go │ │ ├── template.go │ │ ├── projectmcpserver.go │ │ ├── mcpserverinstance.go │ │ ├── pathmatcher.go │ │ ├── poweruserworkspace.go │ │ ├── run.go │ │ ├── workflow.go │ │ └── tool.go │ ├── server │ │ └── audit │ │ │ └── store │ │ │ └── store.go │ ├── handlers │ │ ├── prompt.go │ │ └── providers │ │ │ ├── filescannerproviders.go │ │ │ ├── authproviders.go │ │ │ └── modelproviders.go │ ├── authn │ │ ├── anonymous.go │ │ ├── authn.go │ │ └── token.go │ └── errors.go ├── controller │ ├── data │ │ ├── everything-access-control-rule.yaml │ │ └── default-model-aliases.yaml │ ├── handlers │ │ ├── workflow │ │ │ ├── workflow.go │ │ │ └── ids.go │ │ ├── cleanup │ │ │ └── projectmcp.go │ │ ├── oauthclients │ │ │ └── oauthclients.go │ │ └── projectmcpserver │ │ │ └── projectmcpserver.go │ ├── generationed │ │ └── generationed.go │ └── migrate.go ├── mcp │ ├── ping.go │ ├── capabilities.go │ ├── resources.go │ └── prompts.go ├── accesstoken │ └── accesstoken.go ├── hash │ └── hash.go ├── services │ └── queuesplitters.go ├── oauth │ └── redirect.go ├── logutil │ └── dsn.go ├── utils │ └── utils.go └── create │ └── create.go ├── tests └── integration │ ├── tools │ ├── index.yaml │ ├── tool.gpt │ └── mock-model-provider │ │ └── tool.gpt │ └── integration_test.go ├── chart ├── Chart.yaml └── templates │ ├── serviceaccount.yaml │ ├── NOTES.txt │ ├── pvc.yaml │ └── service.yaml ├── .envrc.dev ├── logger └── go.mod ├── .github ├── workflows │ ├── helm.yml │ ├── go.yml │ ├── test.yaml │ ├── docs.yml │ ├── test-docker-build.yml │ ├── update-prod-env.yml │ ├── update-conference-env.yml │ ├── user-ui.yml │ ├── update-chat-env.yml │ ├── update-demo-env.yml │ └── weekly-snapshot.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── azure-encryption.yaml ├── gcp-encryption.yaml ├── aws-encryption.yaml ├── .golangci.yaml ├── generate.go ├── workflow.yaml ├── main.go ├── .goreleaser.yaml ├── .gitignore └── run.sh /tools/header.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/user/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | .local/ 3 | obot-tools/ 4 | -------------------------------------------------------------------------------- /docs/docs/concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Concepts" 3 | } 4 | -------------------------------------------------------------------------------- /docs/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "v0.15.0", 3 | "v0.14.0", 4 | "v0.13.0" 5 | ] 6 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.13.0/concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Concepts" 3 | } 4 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.13.0/tutorials/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorials" 3 | } -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.14.0/concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Concepts" 3 | } 4 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.14.0/tutorials/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorials" 3 | } -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.15.0/concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Concepts" 3 | } 4 | -------------------------------------------------------------------------------- /ui/user/src/lib/actions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as popover } from './popover.svelte.js'; 2 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/index.ts: -------------------------------------------------------------------------------- 1 | export { plaintext } from './plaintext'; 2 | -------------------------------------------------------------------------------- /ui/user/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ui/render/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Render } from './render.svelte'; 2 | -------------------------------------------------------------------------------- /apiclient/types/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:openapi-gen=true 2 | // +k8s:deepcopy-gen=package 3 | 4 | package types 5 | -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/group.go: -------------------------------------------------------------------------------- 1 | package obotobotplatformai 2 | 3 | var Group = "obot.obot.ai" 4 | -------------------------------------------------------------------------------- /ui/user/.build: -------------------------------------------------------------------------------- 1 | # DO NOT REMOVE: This is used to make the go embedded FS work when ./build doesn't exist 2 | -------------------------------------------------------------------------------- /ui/user/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/favicon.ico -------------------------------------------------------------------------------- /ui/user/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /pkg/cli/templates/fs.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import "embed" 4 | 5 | //go:embed *.yaml 6 | var FS embed.FS 7 | -------------------------------------------------------------------------------- /ui/user/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/favicon-16x16.png -------------------------------------------------------------------------------- /ui/user/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/favicon-32x32.png -------------------------------------------------------------------------------- /docs/static/img/obot-mcp-mgmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/obot-mcp-mgmt.png -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:openapi-gen=true 2 | // +k8s:deepcopy-gen=package 3 | 4 | package v1 5 | -------------------------------------------------------------------------------- /tests/integration/tools/index.yaml: -------------------------------------------------------------------------------- 1 | modelProviders: 2 | mock-model-provider: 3 | reference: ./mock-model-provider 4 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ui/multi-value-input/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MultiValueInput } from './Input.svelte'; 2 | -------------------------------------------------------------------------------- /docs/static/img/high-level-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/high-level-arch.png -------------------------------------------------------------------------------- /docs/static/img/okta-group-claims.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/okta-group-claims.png -------------------------------------------------------------------------------- /docs/static/img/webhook-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/webhook-overview.png -------------------------------------------------------------------------------- /ui/user/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/apple-touch-icon.png -------------------------------------------------------------------------------- /apiclient/types/userdefaultrole.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type UserDefaultRoleSetting struct { 4 | Role Role `json:"role"` 5 | } 6 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: obot 3 | description: A Helm chart for Obot 4 | version: 0.1.0 5 | appVersion: 1.0.0 6 | -------------------------------------------------------------------------------- /docs/static/img/entra-client-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/entra-client-secret.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/survey.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/survey.webp -------------------------------------------------------------------------------- /docs/static/img/entra-app-registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/entra-app-registration.png -------------------------------------------------------------------------------- /docs/static/img/gateway-architecture.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/gateway-architecture.webp -------------------------------------------------------------------------------- /docs/static/img/token-exchange-flow.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/token-exchange-flow.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/hubspot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/hubspot.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/jobhunt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/jobhunt.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/mork-borg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/mork-borg.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/postgres.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/postgres.webp -------------------------------------------------------------------------------- /ui/user/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /ui/user/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /ui/user/static/landing/images/landing.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/landing/images/landing.webp -------------------------------------------------------------------------------- /docs/static/img/okta-client-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/okta-client-credentials.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/ci-failure.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/ci-failure.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/smartthings.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/smartthings.webp -------------------------------------------------------------------------------- /pkg/gateway/types/migration.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | type Migration struct { 5 | Name string `gorm:"primaryKey"` 6 | } 7 | -------------------------------------------------------------------------------- /ui/user/static/admin/assets/amazon-bedrock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/admin/assets/amazon-bedrock.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/create-a-chat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/create-a-chat.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/slack2github.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/slack2github.webp -------------------------------------------------------------------------------- /ui/user/static/landing/images/landing_dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/landing/images/landing_dark.webp -------------------------------------------------------------------------------- /ui/user/static/user/images/sharing-agent.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/user/images/sharing-agent.webp -------------------------------------------------------------------------------- /docs/static/img/add-mcp-server-type-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/add-mcp-server-type-selector.png -------------------------------------------------------------------------------- /docs/static/img/entra-configured-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/entra-configured-permissions.png -------------------------------------------------------------------------------- /tests/integration/tools/tool.gpt: -------------------------------------------------------------------------------- 1 | name: Tool Index 2 | description: The upstream Obot tool registry 3 | 4 | outputfilter: index.yaml 5 | #!sys.echo 6 | -------------------------------------------------------------------------------- /ui/user/static/admin/assets/google_icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/admin/assets/google_icon_small.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/create-from-mcp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/create-from-mcp.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/obot_placeholder.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/obot_placeholder.webp -------------------------------------------------------------------------------- /.envrc.dev: -------------------------------------------------------------------------------- 1 | export KUBECONFIG=$(pwd)/tools/devmode-kubeconfig 2 | export OBOT_DEV_MODE=true 3 | export WORKSPACE_PROVIDER_IGNORE_WORKSPACE_NOT_FOUND=true 4 | -------------------------------------------------------------------------------- /docs/static/img/entra-new-configured-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/entra-new-configured-permissions.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/create-a-template.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/create-a-template.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/create-from-scratch.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/create-from-scratch.webp -------------------------------------------------------------------------------- /ui/user/static/agent/images/workout-assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/workout-assistant.webp -------------------------------------------------------------------------------- /ui/user/static/user/images/under-construction.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/user/images/under-construction.webp -------------------------------------------------------------------------------- /docs/static/img/tutorials/slack-agent/agent-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/slack-agent/agent-config.png -------------------------------------------------------------------------------- /docs/static/img/tutorials/slack-agent/chat-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/slack-agent/chat-example.png -------------------------------------------------------------------------------- /docs/static/img/tutorials/slack-agent/slack-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/slack-agent/slack-tools.png -------------------------------------------------------------------------------- /ui/user/static/user/images/share-project-delivery.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/user/images/share-project-delivery.webp -------------------------------------------------------------------------------- /ui/user/static/user/images/share-project-snapshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/user/images/share-project-snapshot.webp -------------------------------------------------------------------------------- /ui/user/static/user/images/sharing-agent-expired.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/user/images/sharing-agent-expired.webp -------------------------------------------------------------------------------- /docs/static/img/tutorials/github-agent/github-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/github-agent/github-tools.png -------------------------------------------------------------------------------- /ui/user/static/admin/assets/obot_openai_antenna_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/admin/assets/obot_openai_antenna_icon.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/wordpress-blog-assistant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/wordpress-blog-assistant.webp -------------------------------------------------------------------------------- /docs/static/img/tutorials/knowledge-agent/agent-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/knowledge-agent/agent-config.png -------------------------------------------------------------------------------- /docs/static/img/tutorials/knowledge-agent/chat-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/knowledge-agent/chat-example.png -------------------------------------------------------------------------------- /ui/user/static/agent/images/zoom-meeting-action-image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/ui/user/static/agent/images/zoom-meeting-action-image.webp -------------------------------------------------------------------------------- /docs/static/img/tutorials/github-agent/github-agent-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/github-agent/github-agent-config.png -------------------------------------------------------------------------------- /docs/static/img/tutorials/github-agent/github-chat-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/github-agent/github-chat-example.png -------------------------------------------------------------------------------- /docs/static/img/tutorials/knowledge-agent/knowledge-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obot-platform/obot/HEAD/docs/static/img/tutorials/knowledge-agent/knowledge-config.png -------------------------------------------------------------------------------- /ui/user/src/lib/components/admin/usage/types.ts: -------------------------------------------------------------------------------- 1 | import type { UsageStatsFilters } from '$lib/services'; 2 | 3 | export type SupportedStateFilter = keyof UsageStatsFilters; 4 | -------------------------------------------------------------------------------- /ui/user/src/lib/services/admin/index.ts: -------------------------------------------------------------------------------- 1 | import { baseURL } from '../http'; 2 | import * as Operations from './operations'; 3 | 4 | export default { 5 | baseURL, 6 | ...Operations 7 | }; 8 | -------------------------------------------------------------------------------- /apiclient/types/activity.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type APIActivity struct { 4 | UserID string `json:"userID"` 5 | Date Time `json:"date"` 6 | } 7 | 8 | type APIActivityList List[APIActivity] 9 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/admin/filters-drawer/types.ts: -------------------------------------------------------------------------------- 1 | import type { AdminService } from '$lib/services'; 2 | 3 | export type FilterOptionsEndpoint = typeof AdminService.listAuditLogFilterOptions; 4 | -------------------------------------------------------------------------------- /pkg/gateway/server/response.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (s *Server) authCompleteURL() string { 8 | return fmt.Sprintf("%s/login_complete", s.uiURL) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/system/mcp.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func MCPConnectURL(serverURL, id string) string { 8 | return fmt.Sprintf("%s/mcp-connect/%s", serverURL, id) 9 | } 10 | -------------------------------------------------------------------------------- /logger/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/obot-platform/obot/logger 2 | 3 | go 1.23.5 4 | 5 | require github.com/sirupsen/logrus v1.9.3 6 | 7 | require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 8 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/generationed.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // +k8s:deepcopy-gen=false 4 | 5 | type Generationed interface { 6 | GetObservedGeneration() int64 7 | SetObservedGeneration(int64) 8 | } 9 | -------------------------------------------------------------------------------- /tools/vendor.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | // Needed for go generate 5 | _ "github.com/obot-platform/nah/pkg/deepcopy" 6 | _ "k8s.io/gengo/v2" 7 | _ "k8s.io/kube-openapi/cmd/openapi-gen/args" 8 | ) 9 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schema'; 2 | export * from './inputrules'; 3 | export * from './commands'; 4 | export * from './keymap'; 5 | export * from './plugins'; 6 | -------------------------------------------------------------------------------- /apiclient/types/file.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type File struct { 4 | Name string `json:"name,omitempty"` 5 | } 6 | 7 | type FileList List[File] 8 | 9 | type FolderSet map[string]Item 10 | 11 | type Item struct{} 12 | -------------------------------------------------------------------------------- /apiclient/types/eula.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // EulaStatus represents the user's EULA acceptance status 4 | type EulaStatus struct { 5 | // Accepted indicates whether the user has accepted the EULA 6 | Accepted bool `json:"accepted"` 7 | } 8 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/prompts.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | const ( 4 | DefaultAgentPrompt = "You are a helpful assistant." 5 | DefaultWorkflowAgentPrompt = "You are a helpful assistant, precisely follow the given instructions." 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/version" 5 | ) 6 | 7 | var ( 8 | Tag = "v0.0.0-dev" 9 | ) 10 | 11 | func Get() version.Version { 12 | return version.NewVersion(Tag) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/storage/selectors/selectors.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | func RemoveEmpty(selector map[string]string) map[string]string { 4 | for k, v := range selector { 5 | if v == "" { 6 | delete(selector, k) 7 | } 8 | } 9 | return selector 10 | } 11 | -------------------------------------------------------------------------------- /ui/user/src/lib/format.ts: -------------------------------------------------------------------------------- 1 | export function formatNumber(num: number): string { 2 | if (num >= 1000) { 3 | const thousands = num / 1000; 4 | return thousands % 1 === 0 ? `${thousands}k` : `${thousands.toFixed(1)}k`; 5 | } 6 | return num.toString(); 7 | } 8 | -------------------------------------------------------------------------------- /ui/user/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import type { Handle } from '@sveltejs/kit'; 2 | 3 | export const handle: Handle = async ({ event, resolve }) => { 4 | return resolve(event, { 5 | filterSerializedResponseHeaders: (name) => name === 'content-type' 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /pkg/gateway/db/creds.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "time" 4 | 5 | type GptscriptCredential struct { 6 | ID uint `gorm:"primary_key"` 7 | CreatedAt time.Time 8 | ServerURL string `gorm:"unique"` 9 | Username string 10 | Secret string 11 | } 12 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/primitives/draggable/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DraggableList } from './DraggableList.svelte'; 2 | export { default as DraggableItem } from './DraggableItem.svelte'; 3 | export { default as DraggableHandle } from './DraggableHandle.svelte'; 4 | -------------------------------------------------------------------------------- /apiclient/types/poweruserworkspace.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type PowerUserWorkspace struct { 4 | Metadata 5 | UserID string `json:"userID,omitempty"` 6 | Role Role `json:"role,omitempty"` 7 | } 8 | 9 | type PowerUserWorkspaceList List[PowerUserWorkspace] 10 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | VirtualPageViewport, 3 | VirtualPageTable, 4 | getVirtualPageContext, 5 | setVirtualPageContext 6 | } from './virtual-page'; 7 | 8 | export { Render } from './render'; 9 | export { MultiValueInput } from './multi-value-input'; 10 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ui/virtual-page/index.ts: -------------------------------------------------------------------------------- 1 | export { default as VirtualPageViewport } from './virtual-page-viewport.svelte'; 2 | export { default as VirtualPageTable } from './virtual-page-table.svelte'; 3 | 4 | export { getVirtualPageContext, setVirtualPageContext } from './context'; 5 | -------------------------------------------------------------------------------- /ui/user/src/lib/tools.ts: -------------------------------------------------------------------------------- 1 | import type { AssistantTool } from '$lib/services'; 2 | 3 | export function hasTool(tools: AssistantTool[], tool: string): boolean { 4 | for (const t of tools) { 5 | if (t.id === tool) { 6 | return t.enabled || t.builtin || false; 7 | } 8 | } 9 | return false; 10 | } 11 | -------------------------------------------------------------------------------- /ui/user/src/routes/auth/mcp/composite/[composite_mcp_id]/+page.ts: -------------------------------------------------------------------------------- 1 | export const load = ({ params, url }: { params: { composite_mcp_id: string }; url: URL }) => { 2 | return { 3 | compositeMcpId: params.composite_mcp_id, 4 | oauthAuthRequestId: url.searchParams.get('oauth_auth_request') || undefined 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /pkg/gateway/types/property.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import "time" 5 | 6 | type Property struct { 7 | CreatedAt time.Time `json:"createdAt"` 8 | UpdatedAt time.Time `json:"updatedAt"` 9 | Key string `json:"key" gorm:"primaryKey"` 10 | Value string `json:"value"` 11 | } 12 | -------------------------------------------------------------------------------- /pkg/cli/version.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/obot-platform/obot/pkg/version" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type Version struct{} 11 | 12 | func (l *Version) Run(*cobra.Command, []string) error { 13 | fmt.Println("Version: ", version.Get()) 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/time.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | func NewTime(t *metav1.Time) *types.Time { 9 | if t == nil { 10 | return nil 11 | } 12 | return types.NewTime(t.Time) 13 | } 14 | -------------------------------------------------------------------------------- /ui/user/src/routes/usage/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from '../$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { profile } = await parent(); 6 | if (profile?.hasAdminAccess?.()) { 7 | throw redirect(302, '/admin/usage'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /ui/user/src/routes/audit-logs/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from '../$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { profile } = await parent(); 6 | if (profile?.hasAdminAccess?.()) { 7 | throw redirect(302, '/admin/audit-logs'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /tests/integration/integration_test.go: -------------------------------------------------------------------------------- 1 | // revive:disable:dot-imports 2 | 3 | package integration_test 4 | 5 | import ( 6 | "testing" 7 | 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | func TestIntegration(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Integration Suite") 15 | } 16 | -------------------------------------------------------------------------------- /tests/integration/tools/mock-model-provider/tool.gpt: -------------------------------------------------------------------------------- 1 | Name: MockModelProvider 2 | Description: Mock Model Provider 3 | Model Provider: true 4 | 5 | #!sys.daemon ${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool 6 | --- 7 | Name: validate 8 | Description: Validate the OpenAI API key 9 | 10 | #!${GPTSCRIPT_TOOL_DIR}/bin/gptscript-go-tool validate -------------------------------------------------------------------------------- /ui/user/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /pkg/system/bin.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import "os" 4 | 5 | const BinEnvVar = "OBOT_BIN" 6 | 7 | func SetBinToSelf() { 8 | if err := os.Setenv(BinEnvVar, Bin()); err != nil { 9 | panic(err) 10 | } 11 | } 12 | 13 | func Bin() string { 14 | bin := os.Getenv(BinEnvVar) 15 | if bin != "" { 16 | return bin 17 | } 18 | return currentBin() 19 | } 20 | -------------------------------------------------------------------------------- /ui/user/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # IDE 4 | .vscode 5 | .idea 6 | 7 | # Output 8 | .output 9 | .vercel 10 | /.svelte-kit 11 | /build 12 | /build-node 13 | 14 | # OS 15 | .DS_Store 16 | Thumbs.db 17 | 18 | # Env 19 | .env 20 | .env.* 21 | !.env.example 22 | !.env.test 23 | 24 | # Vite 25 | vite.config.js.timestamp-* 26 | vite.config.ts.timestamp-* 27 | -------------------------------------------------------------------------------- /pkg/cli/server.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/obot-platform/obot/pkg/server" 5 | "github.com/obot-platform/obot/pkg/services" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type Server struct { 10 | services.Config 11 | } 12 | 13 | func (s *Server) Run(cmd *cobra.Command, _ []string) error { 14 | return server.Run(cmd.Context(), s.Config) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/gateway/types/virusscanner.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import "time" 5 | 6 | type FileScannerConfig struct { 7 | ID uint `json:"id" gorm:"primaryKey"` 8 | UpdatedAt time.Time `json:"updatedAt"` 9 | ProviderName string `json:"providerName"` 10 | ProviderNamespace string `json:"providerNamespace"` 11 | } 12 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/plaintext.ts: -------------------------------------------------------------------------------- 1 | import { commands, inputRules, keymap, markInputRules, plugins, schema } from './composed'; 2 | import type { MilkdownPlugin } from '@milkdown/ctx'; 3 | 4 | export const plaintext: MilkdownPlugin[] = [ 5 | schema, 6 | inputRules, 7 | markInputRules, 8 | commands, 9 | keymap, 10 | plugins 11 | ].flat(); 12 | -------------------------------------------------------------------------------- /ui/user/src/lib/icons/Loading.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pkg/gateway/client/token.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/obot-platform/obot/pkg/gateway/types" 7 | ) 8 | 9 | // CreateTokenRequest creates a new token request in the database. 10 | func (c *Client) CreateTokenRequest(ctx context.Context, tr *types.TokenRequest) error { 11 | return c.db.WithContext(ctx).Create(tr).Error 12 | } 13 | -------------------------------------------------------------------------------- /ui/user/src/lib/services/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { baseURL } from '../http'; 2 | import { buildMessagesFromProgress } from './messages'; 3 | import * as Operations from './operations'; 4 | import * as MessageSource from './thread.svelte'; 5 | 6 | export default { 7 | progressToMessages: buildMessagesFromProgress, 8 | baseURL, 9 | ...Operations, 10 | ...MessageSource 11 | }; 12 | -------------------------------------------------------------------------------- /ui/user/static/admin/assets/anthropic_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pkg/api/authz/user.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "slices" 5 | 6 | "github.com/obot-platform/obot/apiclient/types" 7 | "k8s.io/apiserver/pkg/authentication/user" 8 | ) 9 | 10 | func (a *Authorizer) checkUser(user user.Info, userID string) bool { 11 | return userID == "" || 12 | userID == user.GetUID() || 13 | slices.Contains(user.GetGroups(), types.GroupAdmin) 14 | } 15 | -------------------------------------------------------------------------------- /ui/user/src/lib/stores/responsive.svelte.ts: -------------------------------------------------------------------------------- 1 | const store = $state({ 2 | isMobile: false 3 | }); 4 | 5 | if (typeof window !== 'undefined') { 6 | const mediaQuery = window.matchMedia('(max-width: 768px)'); 7 | store.isMobile = mediaQuery.matches; 8 | 9 | mediaQuery.addEventListener('change', (e) => { 10 | store.isMobile = e.matches; 11 | }); 12 | } 13 | 14 | export default store; 15 | -------------------------------------------------------------------------------- /ui/user/src/routes/auth/mcp/composite/[composite_mcp_id]/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /pkg/controller/data/everything-access-control-rule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: obot.obot.ai/v1 2 | kind: AccessControlRule 3 | metadata: 4 | name: acr1-everything 5 | namespace: default 6 | spec: 7 | mcpCatalogID: default 8 | manifest: 9 | displayName: Everything 10 | resources: 11 | - id: '*' 12 | type: selector 13 | subjects: 14 | - id: '*' 15 | type: selector 16 | -------------------------------------------------------------------------------- /pkg/mcp/ping.go: -------------------------------------------------------------------------------- 1 | package mcp 2 | 3 | import ( 4 | "context" 5 | 6 | nmcp "github.com/nanobot-ai/nanobot/pkg/mcp" 7 | ) 8 | 9 | func (sm *SessionManager) PingServer(ctx context.Context, serverConfig ServerConfig) (*nmcp.PingResult, error) { 10 | client, err := sm.clientForMCPServer(ctx, serverConfig) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return client.Ping(ctx) 16 | } 17 | -------------------------------------------------------------------------------- /apiclient/types/memory.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // Memory represents a single memory item 4 | type Memory struct { 5 | ID string `json:"id,omitempty"` 6 | Content string `json:"content,omitempty"` 7 | CreatedAt Time `json:"createdAt,omitempty"` 8 | } 9 | 10 | // MemoryList represents a list of memories 11 | type MemoryList struct { 12 | Metadata 13 | Items []Memory `json:"items"` 14 | } 15 | -------------------------------------------------------------------------------- /ui/user/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": [ 7 | "prettier-plugin-svelte", 8 | "prettier-plugin-tailwindcss", 9 | "@trivago/prettier-plugin-sort-imports" 10 | ], 11 | "overrides": [ 12 | { 13 | "files": "*.svelte", 14 | "options": { 15 | "parser": "svelte" 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /pkg/cli/templates/emailreceiver.yaml: -------------------------------------------------------------------------------- 1 | # Required to be set to webhook 2 | type: emailreceiver 3 | 4 | # A display name for you webhook 5 | name: "%NAME%" 6 | 7 | # A description of your webhook 8 | description: "" 9 | 10 | # Required the user portion of the user@example.com email address that will be created 11 | user: "" 12 | 13 | # The workflow ID or workflow alias that this email receiver will trigger 14 | workflow: "" -------------------------------------------------------------------------------- /ui/user/src/lib/stores/version.svelte.ts: -------------------------------------------------------------------------------- 1 | import { type Version } from '$lib/services/chat/types'; 2 | 3 | const store = $state<{ current: Version; initialize: (version?: Version) => void }>({ 4 | current: {}, 5 | initialize 6 | }); 7 | 8 | function initialize(version?: Version) { 9 | if (version) { 10 | store.current = version; 11 | } else { 12 | store.current = {}; 13 | } 14 | } 15 | 16 | export default store; 17 | -------------------------------------------------------------------------------- /pkg/api/server/audit/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | type Store interface { 10 | Persist([]byte) error 11 | } 12 | 13 | func filename(host string, compress bool) string { 14 | suffix := ".log" 15 | if compress { 16 | suffix += ".gz" 17 | } 18 | return fmt.Sprintf("%s-%s%s", strings.ReplaceAll(host, ".", "_"), time.Now().Format(time.RFC3339), suffix) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/accesstoken/accesstoken.go: -------------------------------------------------------------------------------- 1 | package accesstoken 2 | 3 | import "context" 4 | 5 | type accessTokenKey struct{} 6 | 7 | func ContextWithAccessToken(ctx context.Context, accessToken string) context.Context { 8 | return context.WithValue(ctx, accessTokenKey{}, accessToken) 9 | } 10 | 11 | func GetAccessToken(ctx context.Context) string { 12 | accessToken, _ := ctx.Value(accessTokenKey{}).(string) 13 | return accessToken 14 | } 15 | -------------------------------------------------------------------------------- /pkg/gateway/db/drop_session_cookies.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | func dropSessionCookiesTable(tx *gorm.DB) error { 10 | if !tx.Migrator().HasTable("session_cookies") { 11 | return nil 12 | } 13 | 14 | if err := tx.Migrator().DropTable("session_cookies"); err != nil { 15 | return fmt.Errorf("failed to drop session_cookies table: %w", err) 16 | } 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/hash/hash.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package hash 3 | 4 | import ( 5 | "crypto/sha256" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | func String(obj any) string { 11 | switch v := obj.(type) { 12 | case []byte: 13 | return fmt.Sprintf("%x", sha256.Sum256(v)) 14 | case string: 15 | return fmt.Sprintf("%x", sha256.Sum256([]byte(v))) 16 | default: 17 | data, _ := json.Marshal(obj) 18 | return String(data) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tools/devmode-kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | insecure-skip-tls-verify: true 6 | name: local-cluster 7 | contexts: 8 | - context: 9 | cluster: local-cluster 10 | user: local-user 11 | name: local-context 12 | current-context: local-context 13 | kind: Config 14 | preferences: {} 15 | users: 16 | - name: local-user 17 | user: 18 | token: adminpass 19 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/keymap.ts: -------------------------------------------------------------------------------- 1 | import type { MilkdownPlugin } from '@milkdown/ctx'; 2 | import { inlineCodeKeymap } from '@milkdown/kit/preset/commonmark'; 3 | import { codeBlockKeymap, hardbreakKeymap, paragraphKeymap } from '@milkdown/kit/preset/commonmark'; 4 | 5 | export const keymap: MilkdownPlugin[] = [ 6 | codeBlockKeymap, 7 | hardbreakKeymap, 8 | paragraphKeymap, 9 | inlineCodeKeymap 10 | ].flat(); 11 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/refs.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import kclient "sigs.k8s.io/controller-runtime/pkg/client" 4 | 5 | // +k8s:deepcopy-gen=false 6 | 7 | type Ref struct { 8 | ObjType kclient.Object 9 | Namespace string 10 | Name string 11 | Alias string 12 | Kind string 13 | } 14 | 15 | // +k8s:deepcopy-gen=false 16 | 17 | type DeleteRefs interface { 18 | DeleteRefs() []Ref 19 | } 20 | 21 | type EmptyStatus struct{} 22 | -------------------------------------------------------------------------------- /chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if or .Values.serviceAccount.create (eq .Values.config.OBOT_SERVER_MCPRUNTIME_BACKEND "kubernetes") }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "obot.serviceAccountName" . }} 6 | {{- with .Values.serviceAccount.annotations }} 7 | annotations: 8 | {{- toYaml . | nindent 4 }} 9 | {{- end }} 10 | labels: 11 | {{- include "obot.labels" . | nindent 4 }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /ui/user/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | server: { 6 | port: 5174, 7 | proxy: { 8 | '/api': 'http://localhost:8080', 9 | '/oauth2': 'http://localhost:8080' 10 | } 11 | }, 12 | optimizeDeps: { 13 | // currently incompatible with dep optimizer 14 | exclude: ['layerchart', 'layercake'] 15 | }, 16 | plugins: [sveltekit()] 17 | }); 18 | -------------------------------------------------------------------------------- /apiclient/types/authprovider.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type AuthProvider struct { 4 | Metadata 5 | AuthProviderManifest 6 | AuthProviderStatus 7 | } 8 | 9 | type AuthProviderManifest struct { 10 | Name string `json:"name"` 11 | Namespace string `json:"namespace"` 12 | ToolReference string `json:"toolReference"` 13 | } 14 | 15 | type AuthProviderStatus struct { 16 | CommonProviderStatus 17 | } 18 | 19 | type AuthProviderList List[AuthProvider] 20 | -------------------------------------------------------------------------------- /docs/docs/enterprise/overview.md: -------------------------------------------------------------------------------- 1 | # Enterprise Release 2 | 3 | We offer an enterprise-grade version of the Obot Platform that adds support for the following: 4 | - Additional **auth providers** such as Okta and Microsoft Entra 5 | - Additional **model providers** such as OpenAI on Azure and Anthropic on Amazon Bedrock 6 | 7 | You can use this version free for non-commercial and proof-of-concept purposes by reaching out to us on our [Discord](https://discord.gg/9sSf4UyAMC). 8 | -------------------------------------------------------------------------------- /pkg/services/queuesplitters.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/runtime" 5 | "github.com/obot-platform/obot/pkg/system" 6 | ) 7 | 8 | type runQueueSplitter struct{} 9 | 10 | func (*runQueueSplitter) Split(key string) int { 11 | _, name := runtime.KeyParse(key) 12 | if system.IsChatRunID(name) { 13 | return 1 14 | } 15 | 16 | return 0 17 | } 18 | 19 | func (*runQueueSplitter) Queues() int { 20 | return 2 21 | } 22 | -------------------------------------------------------------------------------- /pkg/storage/scheme/scheme.go: -------------------------------------------------------------------------------- 1 | package scheme 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/restconfig" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | coordinationv1 "k8s.io/api/coordination/v1" 7 | corev1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | //nolint:revive 11 | var Scheme, Codecs, Parameter, AddToScheme = restconfig.MustBuildScheme( 12 | v1.AddToScheme, 13 | coordinationv1.AddToScheme, 14 | corev1.AddToScheme, 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/mcp/capabilities.go: -------------------------------------------------------------------------------- 1 | package mcp 2 | 3 | import ( 4 | "context" 5 | 6 | nmcp "github.com/nanobot-ai/nanobot/pkg/mcp" 7 | ) 8 | 9 | func (sm *SessionManager) ServerCapabilities(ctx context.Context, serverConfig ServerConfig) (nmcp.ServerCapabilities, error) { 10 | client, err := sm.clientForMCPServer(ctx, serverConfig) 11 | if err != nil { 12 | return nmcp.ServerCapabilities{}, err 13 | } 14 | 15 | return client.Session.InitializeResult.Capabilities, nil 16 | } 17 | -------------------------------------------------------------------------------- /ui/user/src/lib/stores/index.ts: -------------------------------------------------------------------------------- 1 | export { default as darkMode } from './darkmode.svelte'; 2 | export { default as profile } from './profile.svelte'; 3 | export { default as errors } from './errors.svelte'; 4 | export { default as responsive } from './responsive.svelte'; 5 | export { default as version } from './version.svelte'; 6 | export { default as appPreferences } from './appPreferences.svelte'; 7 | export { default as mcpServersAndEntries } from './mcpServersAndEntries.svelte'; 8 | -------------------------------------------------------------------------------- /pkg/cli/templates/webhook.yaml: -------------------------------------------------------------------------------- 1 | # Required to be set to webhook 2 | type: webhook 3 | 4 | # A display name for you webhook 5 | name: "%NAME%" 6 | 7 | # A description of your webhook 8 | description: "" 9 | 10 | # A shared secret used for HMAC SHA256 hashing of the payload 11 | secret: "" 12 | 13 | # The HTTP header that contains the HMAC SHA256 hash of the payload 14 | validationHeader: "" 15 | 16 | # The workflow ID or workflow alias that this webhook will trigger 17 | workflow: "" -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.13.0/enterprise/overview.md: -------------------------------------------------------------------------------- 1 | # Enterprise Release 2 | 3 | We offer an enterprise-grade version of the Obot Platform that adds support for the following: 4 | - Additional **auth providers** such as Okta and Microsoft Entra 5 | - Additional **model providers** such as OpenAI on Azure and Anthropic on Amazon Bedrock 6 | 7 | You can use this version free for non-commercial and proof-of-concept purposes by reaching out to us on our [Discord](https://discord.gg/9sSf4UyAMC). 8 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.14.0/enterprise/overview.md: -------------------------------------------------------------------------------- 1 | # Enterprise Release 2 | 3 | We offer an enterprise-grade version of the Obot Platform that adds support for the following: 4 | - Additional **auth providers** such as Okta and Microsoft Entra 5 | - Additional **model providers** such as OpenAI on Azure and Anthropic on Amazon Bedrock 6 | 7 | You can use this version free for non-commercial and proof-of-concept purposes by reaching out to us on our [Discord](https://discord.gg/9sSf4UyAMC). 8 | -------------------------------------------------------------------------------- /docs/versioned_docs/version-v0.15.0/enterprise/overview.md: -------------------------------------------------------------------------------- 1 | # Enterprise Release 2 | 3 | We offer an enterprise-grade version of the Obot Platform that adds support for the following: 4 | - Additional **auth providers** such as Okta and Microsoft Entra 5 | - Additional **model providers** such as OpenAI on Azure and Anthropic on Amazon Bedrock 6 | 7 | You can use this version free for non-commercial and proof-of-concept purposes by reaching out to us on our [Discord](https://discord.gg/9sSf4UyAMC). 8 | -------------------------------------------------------------------------------- /apiclient/types/workflowexecution.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type WorkflowExecution struct { 4 | Metadata 5 | Workflow WorkflowManifest `json:"workflow,omitempty"` 6 | StartTime Time `json:"startTime"` 7 | EndTime *Time `json:"endTime"` 8 | Input string `json:"input"` 9 | Error string `json:"error,omitempty"` 10 | Warning string `json:"warning,omitempty"` 11 | } 12 | 13 | type WorkflowExecutionList List[WorkflowExecution] 14 | -------------------------------------------------------------------------------- /apiclient/types/mcpcatalog.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type MCPCatalog struct { 4 | Metadata 5 | MCPCatalogManifest 6 | LastSynced Time `json:"lastSynced,omitzero"` 7 | SyncErrors map[string]string `json:"syncErrors,omitempty"` 8 | IsSyncing bool `json:"isSyncing,omitempty"` 9 | } 10 | 11 | type MCPCatalogManifest struct { 12 | DisplayName string `json:"displayName"` 13 | SourceURLs []string `json:"sourceURLs"` 14 | } 15 | 16 | type MCPCatalogList List[MCPCatalog] 17 | -------------------------------------------------------------------------------- /pkg/cli/textio/input.go: -------------------------------------------------------------------------------- 1 | package textio 2 | 3 | import "github.com/pterm/pterm" 4 | 5 | func Info(text string) { 6 | pterm.Info.Println(text) 7 | } 8 | 9 | func Print(text string) { 10 | pterm.DefaultParagraph.WithMaxWidth(80).Println(text) 11 | } 12 | 13 | func Ask(text, def string) (string, error) { 14 | return pterm.DefaultInteractiveTextInput. 15 | WithDefaultValue(def).Show(text) 16 | } 17 | 18 | func Select(string, map[string]string, string) (string, error) { 19 | return "", nil 20 | } 21 | -------------------------------------------------------------------------------- /ui/user/src/routes/login_complete/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | 8 |

All Set!

9 |

10 | You can close this window and return to the application 11 |

12 |
13 |
14 | -------------------------------------------------------------------------------- /ui/user/static/user/images/social/facebook-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/inputrules.ts: -------------------------------------------------------------------------------- 1 | import type { MilkdownPlugin } from '@milkdown/ctx'; 2 | import { inlineCodeInputRule } from '@milkdown/kit/preset/commonmark'; 3 | import { createCodeBlockInputRule, insertHrInputRule } from '@milkdown/kit/preset/commonmark'; 4 | 5 | /// @internal 6 | export const inputRules: MilkdownPlugin[] = [createCodeBlockInputRule, insertHrInputRule].flat(); 7 | 8 | /// @internal 9 | export const markInputRules: MilkdownPlugin[] = [inlineCodeInputRule].flat(); 10 | -------------------------------------------------------------------------------- /apiclient/types/filescannerprovider.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type FileScannerProvider struct { 4 | Metadata 5 | FileScannerProviderManifest 6 | FileScannerProviderStatus 7 | } 8 | 9 | type FileScannerProviderManifest struct { 10 | Name string `json:"name"` 11 | Namespace string `json:"namespace"` 12 | ToolReference string `json:"toolReference"` 13 | } 14 | 15 | type FileScannerProviderStatus struct { 16 | CommonProviderStatus 17 | } 18 | 19 | type FileScannerProviderList List[FileScannerProvider] 20 | -------------------------------------------------------------------------------- /docs/VERSIONING_INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | # Versioning Instructions 2 | 3 | ### Snapshotting a new version 4 | 5 | To snapshot a new version of the docs, run `make gen-docs-release version= prev_version=`. 6 | This will accomplish everything needed. Then, just commit the changes and create a pull request. 7 | 8 | ### Removing an old version 9 | 10 | To remove an old version of the docs, run `make remove-docs-version version=`. 11 | Then commit your changes and create a pull request. 12 | -------------------------------------------------------------------------------- /.github/workflows/helm.yml: -------------------------------------------------------------------------------- 1 | name: Helm Chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - chart/** 9 | pull_request: 10 | branches: 11 | - main 12 | paths: 13 | - chart/** 14 | 15 | jobs: 16 | lint: 17 | runs-on: depot-ubuntu-22.04 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Helm 24 | uses: azure/setup-helm@v4 25 | 26 | - name: Lint Helm chart 27 | run: helm lint chart -------------------------------------------------------------------------------- /chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for installing {{ .Chart.Name }}. 2 | 3 | Your release is named {{ .Release.Name }}. 4 | 5 | To learn more about the release, try: 6 | 7 | $ helm status {{ .Release.Name }} 8 | $ helm get all {{ .Release.Name }} 9 | 10 | {{ if eq .Values.config.OBOT_SERVER_ENABLE_AUTHENTICATION true }} 11 | You can retrieve the bootstrap token by running: 12 | $ kubectl get secret -n {{ .Release.Namespace }} {{ include "obot.config.secretName" . }} -ojson | jq -r .data.OBOT_BOOTSTRAP_TOKEN | base64 -d; echo 13 | {{ end }} -------------------------------------------------------------------------------- /pkg/api/handlers/prompt.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gptscript-ai/go-gptscript" 5 | "github.com/obot-platform/obot/pkg/api" 6 | ) 7 | 8 | type PromptHandler struct{} 9 | 10 | func NewPromptHandler() *PromptHandler { 11 | return &PromptHandler{} 12 | } 13 | 14 | func (p *PromptHandler) Prompt(req api.Context) error { 15 | var promptResponse gptscript.PromptResponse 16 | if err := req.Read(&promptResponse); err != nil { 17 | return err 18 | } 19 | return req.GPTClient.PromptResponse(req.Context(), promptResponse) 20 | } 21 | -------------------------------------------------------------------------------- /apiclient/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // +k8s:deepcopy-gen=false 4 | 5 | // +k8s:openapi-gen=false 6 | type List[T any] struct { 7 | Items []T `json:"items"` 8 | } 9 | 10 | type Metadata struct { 11 | ID string `json:"id,omitempty"` 12 | Created Time `json:"created,omitzero"` 13 | Deleted *Time `json:"deleted,omitempty"` 14 | Links map[string]string `json:"links,omitempty"` 15 | Metadata map[string]string `json:"metadata,omitempty"` 16 | Type string `json:"type,omitempty"` 17 | } 18 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/edit/Memories.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |

Memories

15 | 16 |
17 | -------------------------------------------------------------------------------- /apiclient/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/obot-platform/obot/apiclient 2 | 3 | go 1.25.5 4 | 5 | require ( 6 | github.com/gptscript-ai/go-gptscript v0.9.8 7 | github.com/modelcontextprotocol/go-sdk v0.2.0 8 | github.com/obot-platform/obot/logger v0.0.0-20241217130503-4004a5c69f32 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 13 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 14 | github.com/sirupsen/logrus v1.9.3 // indirect 15 | golang.org/x/sys v0.33.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /pkg/cli/events/events.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/obot-platform/obot/apiclient" 7 | "github.com/obot-platform/obot/apiclient/types" 8 | ) 9 | 10 | type Printer interface { 11 | Print(events <-chan types.Progress) error 12 | } 13 | 14 | func NewPrinter(ctx context.Context, c *apiclient.Client, quiet, details bool) Printer { 15 | if quiet { 16 | return &Quiet{ 17 | Client: c, 18 | Ctx: ctx, 19 | } 20 | } 21 | return &Verbose{ 22 | Details: details, 23 | Client: c, 24 | Ctx: ctx, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ui/user/src/lib/image.ts: -------------------------------------------------------------------------------- 1 | import type { Project, ProjectShare, ThreadManifest } from './services'; 2 | 3 | export function isImage(filename: string): boolean { 4 | return /\.(jpe?g|png|gif|bmp|webp)$/i.test(filename); 5 | } 6 | 7 | export function getProjectImage( 8 | project: Project | ProjectShare | ThreadManifest, 9 | isDarkMode: boolean 10 | ) { 11 | const imageUrl = isDarkMode 12 | ? project.icons?.iconDark || project.icons?.icon 13 | : project.icons?.icon; 14 | 15 | return imageUrl ?? '/agent/images/obot_placeholder.webp'; // need placeholder image 16 | } 17 | -------------------------------------------------------------------------------- /pkg/api/handlers/providers/filescannerproviders.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | ) 7 | 8 | func ConvertFileScannerProviderToolRef(toolRef v1.ToolReference, cred map[string]string) (*types.FileScannerProviderStatus, error) { 9 | providerStatus, err := ConvertProviderToolRef(toolRef, cred) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | return &types.FileScannerProviderStatus{CommonProviderStatus: *providerStatus}, nil 15 | } 16 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/filters/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let filter; 10 | try { 11 | filter = await AdminService.getMCPFilter(id, { fetch }); 12 | } catch (err) { 13 | handleRouteError(err, `/admin/filters/${id}`, profile.current); 14 | } 15 | 16 | return { 17 | filter 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/user/src/lib/context/layout.svelte.ts: -------------------------------------------------------------------------------- 1 | import { getContext, hasContext, setContext } from 'svelte'; 2 | 3 | export const LAYOUT_CONTEXT = 'layout'; 4 | 5 | export interface Layout { 6 | sidebarOpen?: boolean; 7 | } 8 | 9 | export function initLayout() { 10 | const data = $state({ 11 | sidebarOpen: true 12 | }); 13 | setContext(LAYOUT_CONTEXT, data); 14 | } 15 | 16 | export function getLayout(): Layout { 17 | if (!hasContext(LAYOUT_CONTEXT)) { 18 | throw new Error('layout context not initialized'); 19 | } 20 | return getContext(LAYOUT_CONTEXT); 21 | } 22 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/chat-threads/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let thread; 10 | try { 11 | thread = await AdminService.getThread(id, { fetch }); 12 | } catch (err) { 13 | handleRouteError(err, `/admin/chat-threads/${id}`, profile.current); 14 | } 15 | 16 | return { 17 | thread 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/users/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import type { OrgUser } from '$lib/services/admin/types'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ fetch }) => { 8 | let users: OrgUser[] = []; 9 | try { 10 | users = await AdminService.listUsers({ fetch }); 11 | } catch (err) { 12 | handleRouteError(err, `/users`, profile.current); 13 | } 14 | 15 | return { 16 | users 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /ui/user/src/routes/t/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService, type ProjectTemplate } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | let template: ProjectTemplate | undefined; 8 | 9 | try { 10 | template = await ChatService.getTemplate(params.id, { fetch }); 11 | } catch (e) { 12 | handleRouteError(e, `/t/${params.id}`, profile.current); 13 | } 14 | 15 | return { 16 | template 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /pkg/api/authn/anonymous.go: -------------------------------------------------------------------------------- 1 | package authn 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/obot-platform/obot/pkg/api/authz" 7 | "k8s.io/apiserver/pkg/authentication/authenticator" 8 | "k8s.io/apiserver/pkg/authentication/user" 9 | ) 10 | 11 | type Anonymous struct{} 12 | 13 | func (Anonymous) AuthenticateRequest(*http.Request) (*authenticator.Response, bool, error) { 14 | return &authenticator.Response{ 15 | User: &user.DefaultInfo{ 16 | UID: "anonymous", 17 | Name: "anonymous", 18 | Groups: []string{authz.UnauthenticatedGroup}, 19 | }, 20 | }, true, nil 21 | } 22 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/user-roles/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch }) => { 7 | let defaultUsersRole: number | undefined; 8 | try { 9 | defaultUsersRole = await AdminService.getDefaultUsersRoleSettings({ fetch }); 10 | } catch (err) { 11 | handleRouteError(err, `/user-configuration`, profile.current); 12 | } 13 | 14 | return { 15 | defaultUsersRole 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/chat-configuration/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, type BaseAgent } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch }) => { 7 | let baseAgent: BaseAgent | undefined; 8 | try { 9 | baseAgent = await AdminService.getDefaultBaseAgent({ fetch }); 10 | } catch (err) { 11 | handleRouteError(err, '/admin/chat-configuration', profile.current); 12 | } 13 | 14 | return { 15 | baseAgent 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /pkg/controller/handlers/workflow/workflow.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/router" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | "k8s.io/apimachinery/pkg/api/equality" 7 | ) 8 | 9 | func EnsureIDs(req router.Request, _ router.Response) error { 10 | wf := req.Object.(*v1.Workflow) 11 | manifestWithIDs := PopulateIDs(wf.Spec.Manifest) 12 | if !equality.Semantic.DeepEqual(wf.Spec.Manifest, manifestWithIDs) { 13 | wf.Spec.Manifest = manifestWithIDs 14 | return req.Client.Update(req.Ctx, wf) 15 | } 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/server-scheduling/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, type K8sSettings } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch }) => { 7 | let k8sSettings: K8sSettings | undefined; 8 | try { 9 | k8sSettings = await AdminService.listK8sSettings({ fetch }); 10 | } catch (err) { 11 | handleRouteError(err, '/admin/chat-configuration', profile.current); 12 | } 13 | 14 | return { 15 | k8sSettings 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/model-providers/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, type ModelProvider } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch }) => { 7 | let modelProviders: ModelProvider[] = []; 8 | try { 9 | modelProviders = await AdminService.listModelProviders({ fetch }); 10 | } catch (err) { 11 | handleRouteError(err, '/admin/model-providers', profile.current); 12 | } 13 | 14 | return { 15 | modelProviders 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /pkg/gateway/types/images.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type Image struct { 12 | ID string `json:"id" gorm:"primaryKey;autoIncrement:false"` 13 | CreatedAt time.Time `json:"createdAt"` 14 | Data []byte `json:"-" gorm:"type:bytea;not null"` 15 | MIMEType string `json:"mimeType" gorm:"type:varchar(100);not null"` 16 | } 17 | 18 | // BeforeCreate will set the ID to a UUID v4. 19 | func (i *Image) BeforeCreate(_ *gorm.DB) error { 20 | i.ID = uuid.NewString() 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /pkg/oauth/redirect.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func IsOAuthCallbackResponse(r *http.Request) bool { 8 | return r.URL.Path == "/" && 9 | (r.URL.Query().Get("code") != "" || 10 | r.URL.Query().Get("error") != "" || 11 | r.URL.Query().Get("state") != "") 12 | } 13 | 14 | func HandleOAuthRedirect(w http.ResponseWriter, r *http.Request) bool { 15 | if !IsOAuthCallbackResponse(r) { 16 | return false 17 | } 18 | redirectURL := r.URL 19 | redirectURL.Path = "/oauth2/callback" 20 | http.Redirect(w, r, redirectURL.String(), http.StatusFound) 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/filters/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import type { MCPFilter } from '$lib/services/admin/types'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ fetch }) => { 8 | let filters: MCPFilter[] = []; 9 | 10 | try { 11 | filters = await AdminService.listMCPFilters({ fetch }); 12 | } catch (err) { 13 | handleRouteError(err, '/admin/filters', profile.current); 14 | } 15 | 16 | return { 17 | filters 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /apiclient/types/emailreceiver.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type EmailReceiver struct { 4 | Metadata 5 | EmailReceiverManifest 6 | AliasAssigned *bool `json:"aliasAssigned,omitempty"` 7 | EmailAddress string `json:"emailAddress,omitempty"` 8 | } 9 | 10 | type EmailReceiverManifest struct { 11 | Name string `json:"name"` 12 | Description string `json:"description"` 13 | Alias string `json:"alias,omitempty"` 14 | WorkflowName string `json:"workflowName"` 15 | AllowedSenders []string `json:"allowedSenders,omitempty"` 16 | } 17 | 18 | type EmailReceiverList List[EmailReceiver] 19 | -------------------------------------------------------------------------------- /azure-encryption.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiserver.config.k8s.io/v1 2 | kind: EncryptionConfiguration 3 | resources: 4 | - resources: 5 | - credentials 6 | - runstates.obot.obot.ai 7 | - users.obot.obot.ai 8 | - identities.obot.obot.ai 9 | - mcpoauthtokens.obot.obot.ai 10 | - mcpauditlogs.obot.obot.ai 11 | providers: 12 | - kms: 13 | apiVersion: v2 14 | name: azure-kms 15 | endpoint: unix:///tmp/azure-cred-socket.sock 16 | - identity: {} # this fallback allows reading unencrypted secrets; 17 | # for example, during initial migration 18 | -------------------------------------------------------------------------------- /pkg/cli/run_debug.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type Debug struct { 11 | root *Obot 12 | } 13 | 14 | func (l *Debug) Customize(cmd *cobra.Command) { 15 | cmd.Use = "debug [flags] [RUN_ID]" 16 | cmd.Args = cobra.ExactArgs(1) 17 | } 18 | 19 | func (l *Debug) Run(cmd *cobra.Command, args []string) error { 20 | debug, err := l.root.Client.DebugRun(cmd.Context(), args[0]) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | enc := json.NewEncoder(os.Stdout) 26 | enc.SetIndent("", " ") 27 | return enc.Encode(debug) 28 | } 29 | -------------------------------------------------------------------------------- /ui/user/src/routes/i/[code]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | // Disable server-side rendering for this route 7 | export const ssr = false; 8 | 9 | export const load = (async ({ params, fetch }) => { 10 | try { 11 | const invitation = await ChatService.getProjectInvitation(params.code, { fetch }); 12 | return { invitation }; 13 | } catch (e) { 14 | handleRouteError(e, `/i/${params.code}`, profile.current); 15 | } 16 | }) satisfies PageLoad; 17 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: go 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths-ignore: 11 | - ui/user/** 12 | 13 | jobs: 14 | lint: 15 | runs-on: depot-ubuntu-22.04 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version: '1.23' 25 | 26 | - name: Build 27 | run: make build 28 | 29 | - name: Validate Go Code 30 | run: make validate-go-code 31 | -------------------------------------------------------------------------------- /gcp-encryption.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiserver.config.k8s.io/v1 2 | kind: EncryptionConfiguration 3 | resources: 4 | - resources: 5 | - credentials 6 | - runstates.obot.obot.ai 7 | - users.obot.obot.ai 8 | - identities.obot.obot.ai 9 | - mcpoauthtokens.obot.obot.ai 10 | - mcpauditlogs.obot.obot.ai 11 | providers: 12 | - kms: 13 | apiVersion: v2 14 | name: google-cloud-kms 15 | endpoint: unix:///tmp/gcp-cred-socket.sock 16 | - identity: {} # this fallback allows reading unencrypted secrets; 17 | # for example, during initial migration 18 | -------------------------------------------------------------------------------- /ui/user/src/lib/context/admin/models.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from '$lib/services'; 2 | import { getContext, hasContext, setContext } from 'svelte'; 3 | 4 | const Key = Symbol('admin-models'); 5 | 6 | export interface AdminModelsContext { 7 | items: Model[]; 8 | } 9 | 10 | export function getAdminModels() { 11 | if (!hasContext(Key)) { 12 | throw new Error('Admin models not initialized'); 13 | } 14 | return getContext(Key); 15 | } 16 | 17 | export function initModels(models: Model[]) { 18 | const data = $state({ items: models }); 19 | setContext(Key, data); 20 | } 21 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/Spinner.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {#if spinning} 23 | {#if children} 24 | {@render children()} 25 | {:else} 26 | 27 | {/if} 28 | {/if} 29 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-registries/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let accessControlRule; 10 | try { 11 | accessControlRule = await AdminService.getAccessControlRule(id, { fetch }); 12 | } catch (err) { 13 | handleRouteError(err, `/admin/mcp-registries/${id}`, profile.current); 14 | } 15 | 16 | return { 17 | accessControlRule 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /aws-encryption.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiserver.config.k8s.io/v1 2 | kind: EncryptionConfiguration 3 | resources: 4 | - resources: 5 | - credentials 6 | - runstates.obot.obot.ai 7 | - users.obot.obot.ai 8 | - identities.obot.obot.ai 9 | - mcpoauthtokens.obot.obot.ai 10 | - mcpauditlogs.obot.obot.ai 11 | providers: 12 | - kms: 13 | apiVersion: v2 14 | name: aws-kms 15 | endpoint: unix:///tmp/aws-cred-socket.sock 16 | timeout: 3s 17 | - identity: {} # this fallback allows reading unencrypted secrets; 18 | # for example, during initial migration 19 | -------------------------------------------------------------------------------- /chart/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} 2 | kind: PersistentVolumeClaim 3 | apiVersion: v1 4 | metadata: 5 | name: {{ .Release.Name }}-pvc 6 | labels: 7 | {{- include "obot.labels" . | nindent 4 }} 8 | spec: 9 | {{- if .Values.persistence.storageClass }} 10 | storageClassName: {{ .Values.persistence.storageClass }} 11 | {{- end }} 12 | accessModes: 13 | {{- range .Values.persistence.accessModes }} 14 | - {{ . | quote }} 15 | {{- end }} 16 | resources: 17 | requests: 18 | storage: {{ .Values.persistence.size | quote }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /pkg/gateway/client/error.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type LastAdminError struct{} 4 | 5 | func (e *LastAdminError) Error() string { 6 | return "last admin" 7 | } 8 | 9 | type LastOwnerError struct{} 10 | 11 | func (e *LastOwnerError) Error() string { 12 | return "last owner" 13 | } 14 | 15 | type AlreadyExistsError struct { 16 | name string 17 | } 18 | 19 | func (e *AlreadyExistsError) Error() string { 20 | return e.name + " already exists" 21 | } 22 | 23 | type ExplicitRoleError struct { 24 | email string 25 | } 26 | 27 | func (e *ExplicitRoleError) Error() string { 28 | return e.email + " has a role that was explicitly set" 29 | } 30 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/plugins.ts: -------------------------------------------------------------------------------- 1 | import type { MilkdownPlugin } from '@milkdown/ctx'; 2 | import { 3 | hardbreakClearMarkPlugin, 4 | hardbreakFilterNodes, 5 | hardbreakFilterPlugin, 6 | inlineNodesCursorPlugin, 7 | remarkHtmlTransformer, 8 | remarkLineBreak, 9 | remarkMarker 10 | } from '@milkdown/kit/preset/commonmark'; 11 | 12 | /// @internal 13 | export const plugins: MilkdownPlugin[] = [ 14 | hardbreakClearMarkPlugin, 15 | hardbreakFilterNodes, 16 | hardbreakFilterPlugin, 17 | 18 | inlineNodesCursorPlugin, 19 | 20 | remarkLineBreak, 21 | remarkHtmlTransformer, 22 | remarkMarker 23 | ].flat(); 24 | -------------------------------------------------------------------------------- /ui/user/static/user/images/social/twitter-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | -------------------------------------------------------------------------------- /ui/user/static/user/images/social/youtube-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /pkg/cli/token.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type Token struct { 10 | NoExpiration bool `usage:"Set the token to never expire"` 11 | ForceRefresh bool `usage:"Force refresh the token"` 12 | root *Obot 13 | } 14 | 15 | func (t *Token) Customize(cmd *cobra.Command) { 16 | cmd.Use = "token" 17 | cmd.Args = cobra.NoArgs 18 | } 19 | 20 | func (t *Token) Run(cmd *cobra.Command, _ []string) error { 21 | token, err := t.root.Client.GetToken(cmd.Context(), t.NoExpiration, t.ForceRefresh) 22 | if err != nil { 23 | return err 24 | } 25 | fmt.Println(token) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests on Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.25" 20 | 21 | - name: Install Dependencies 22 | run: | 23 | go mod download 24 | 25 | - name: Run Tests 26 | run: | 27 | make test 28 | env: 29 | GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths: 8 | - "docs/**" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: "22" 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | working-directory: docs 26 | 27 | - name: Build documentation 28 | run: npm run build 29 | working-directory: docs -------------------------------------------------------------------------------- /apiclient/types/k8ssettings.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // K8sSettings represents global Kubernetes configuration for MCP server deployments 4 | type K8sSettings struct { 5 | // Affinity rules (JSON/YAML blob) 6 | Affinity string `json:"affinity,omitempty"` 7 | 8 | // Tolerations (JSON/YAML blob) 9 | Tolerations string `json:"tolerations,omitempty"` 10 | 11 | // Resources configuration (JSON/YAML blob) 12 | Resources string `json:"resources,omitempty"` 13 | 14 | // SetViaHelm indicates settings are from Helm (cannot be updated via API) 15 | SetViaHelm bool `json:"setViaHelm,omitempty"` 16 | 17 | Metadata Metadata `json:"metadata,omitempty"` 18 | } 19 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/w/[wid]/c/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id, wid } = params; 8 | let catalogEntry; 9 | try { 10 | catalogEntry = await ChatService.getWorkspaceMCPCatalogEntry(wid, id, { fetch }); 11 | } catch (err) { 12 | handleRouteError(err, `/admin/mcp-servers/w/${wid}/c/${id}`, profile.current); 13 | } 14 | return { 15 | workspaceId: wid, 16 | catalogEntry 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /ui/user/static/user/images/social/linkedin-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /apiclient/types/credential.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Credential struct { 4 | ContextID string `json:"contextID,omitempty"` 5 | Name string `json:"name,omitempty"` 6 | EnvVars []string `json:"envVars,omitempty"` 7 | ExpiresAt *Time `json:"expiresAt,omitempty"` 8 | } 9 | 10 | type CredentialList List[Credential] 11 | 12 | type ProjectCredential struct { 13 | ToolID string `json:"toolID,omitempty"` 14 | ToolName string `json:"toolName,omitempty"` 15 | Icon string `json:"icon,omitempty"` 16 | Exists bool `json:"exists"` 17 | BaseAgent bool `json:"baseAgent"` 18 | } 19 | 20 | type ProjectCredentialList List[ProjectCredential] 21 | -------------------------------------------------------------------------------- /ui/user/src/routes/usage/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 |
12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/Error.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 |
14 |
15 |

Error

16 |

{error.message}

17 | {#if onClick} 18 | 19 | {/if} 20 |
21 |
22 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/w/[wid]/s/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id, wid } = params; 8 | 9 | let mcpServer; 10 | try { 11 | mcpServer = await ChatService.getWorkspaceMCPCatalogServer(wid, id, { 12 | fetch 13 | }); 14 | } catch (err) { 15 | handleRouteError(err, `/admin/mcp-servers/w/${wid}/s/${id}`, profile.current); 16 | } 17 | 18 | return { 19 | mcpServer, 20 | workspaceId: wid 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/usage/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 |
16 | 17 |
18 |
19 | 20 | 21 | Obot | Usage 22 | 23 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/w/[wid]/s/[id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { wid, id } = params; 8 | 9 | let mcpServer; 10 | try { 11 | mcpServer = await ChatService.getWorkspaceMCPCatalogServer(wid, id, { fetch }); 12 | } catch (err) { 13 | handleRouteError(err, `/admin/mcp-servers/w/${wid}/s/${id}/details`, profile.current); 14 | } 15 | 16 | return { 17 | mcpServer, 18 | workspaceId: wid 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "obot.fullname" . }} 5 | labels: 6 | {{- include "obot.labels" . | nindent 4 }} 7 | {{- if .Values.service.annotations }} 8 | annotations: 9 | {{ .Values.service.annotations | toYaml | nindent 4 }} 10 | {{- end }} 11 | spec: 12 | type: {{ .Values.service.type }} 13 | ports: 14 | - port: {{ .Values.service.port }} 15 | targetPort: 8080 16 | protocol: TCP 17 | name: http 18 | selector: 19 | {{- include "obot.selectorLabels" . | nindent 4 }} 20 | {{- if .Values.service.spec }} 21 | {{ .Values.service.spec | toYaml | indent 2 }} 22 | {{- end }} -------------------------------------------------------------------------------- /pkg/cli/output.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "sigs.k8s.io/yaml" 9 | ) 10 | 11 | func output(format string, obj any) (bool, error) { 12 | if format == "" || format == "table" { 13 | return false, nil 14 | } 15 | switch format { 16 | case "json": 17 | enc := json.NewEncoder(os.Stdout) 18 | enc.SetIndent("", " ") 19 | return true, enc.Encode(obj) 20 | case "yaml": 21 | data, err := yaml.Marshal(obj) 22 | if err != nil { 23 | return false, err 24 | } 25 | _, err = os.Stdout.Write(data) 26 | return true, err 27 | default: 28 | return false, fmt.Errorf("unsupported output format: %s", format) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-registries/w/[wid]/r/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id, wid } = params; 8 | let accessControlRule; 9 | try { 10 | accessControlRule = await ChatService.getWorkspaceAccessControlRule(wid, id, { fetch }); 11 | } catch (err) { 12 | handleRouteError(err, `/admin/mcp-registries/w/${wid}/r/${id}`, profile.current); 13 | } 14 | 15 | return { 16 | accessControlRule, 17 | workspaceId: wid 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/user/src/lib/stores/errors.svelte.ts: -------------------------------------------------------------------------------- 1 | const store = $state({ 2 | items: [] as Error[], 3 | append 4 | }); 5 | 6 | export function isError(value: unknown): value is Error { 7 | return ( 8 | value instanceof Error || 9 | (typeof value === 'object' && 10 | value !== null && 11 | 'message' in value && 12 | typeof value.message === 'string') 13 | ); 14 | } 15 | 16 | function append(e: unknown, duration?: number) { 17 | const err = isError(e) ? e : new Error(String(e)); 18 | store.items.push(err); 19 | 20 | if (duration) { 21 | setTimeout(() => { 22 | store.items = store.items.filter((x) => x !== err); 23 | }, duration); 24 | } 25 | } 26 | 27 | export default store; 28 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/c/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MCP_CATALOG_ID } from '$lib/constants'; 2 | import { handleRouteError } from '$lib/errors'; 3 | import { AdminService } from '$lib/services'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ params, fetch }) => { 8 | const { id } = params; 9 | 10 | let catalogEntry; 11 | try { 12 | catalogEntry = await AdminService.getMCPCatalogEntry(DEFAULT_MCP_CATALOG_ID, id, { fetch }); 13 | } catch (err) { 14 | handleRouteError(err, `/admin/mcp-servers/c/${id}`, profile.current); 15 | } 16 | 17 | return { 18 | catalogEntry 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/schema.ts: -------------------------------------------------------------------------------- 1 | import type { MilkdownPlugin } from '@milkdown/ctx'; 2 | import { 3 | inlineCodeAttr, 4 | inlineCodeSchema, 5 | codeBlockAttr, 6 | codeBlockSchema, 7 | docSchema, 8 | paragraphAttr, 9 | paragraphSchema, 10 | textSchema, 11 | hardbreakSchema, 12 | hardbreakAttr 13 | } from '@milkdown/kit/preset/commonmark'; 14 | 15 | /// @internal 16 | export const schema: MilkdownPlugin[] = [ 17 | docSchema, 18 | 19 | paragraphAttr, 20 | paragraphSchema, 21 | 22 | hardbreakAttr, 23 | hardbreakSchema, 24 | 25 | codeBlockAttr, 26 | codeBlockSchema, 27 | 28 | inlineCodeAttr, 29 | inlineCodeSchema, 30 | 31 | textSchema 32 | ].flat(); 33 | -------------------------------------------------------------------------------- /ui/user/src/lib/stores/success.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export interface SuccessMessage { 4 | message: string; 5 | id: number; 6 | } 7 | 8 | export const success = (() => { 9 | const { subscribe, update } = writable([]); 10 | let nextId = 0; 11 | 12 | return { 13 | subscribe, 14 | add: (message: string) => { 15 | const id = nextId++; 16 | update((messages) => [...messages, { message, id }]); 17 | setTimeout(() => { 18 | update((messages) => messages.filter((m) => m.id !== id)); 19 | }, 5000); 20 | }, 21 | remove: (id: number) => { 22 | update((messages) => messages.filter((m) => m.id !== id)); 23 | } 24 | }; 25 | })(); 26 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/s/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MCP_CATALOG_ID } from '$lib/constants'; 2 | import { handleRouteError } from '$lib/errors'; 3 | import { AdminService } from '$lib/services'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ params, fetch }) => { 8 | const { id } = params; 9 | 10 | let mcpServer; 11 | try { 12 | mcpServer = await AdminService.getMCPCatalogServer(DEFAULT_MCP_CATALOG_ID, id, { 13 | fetch 14 | }); 15 | } catch (err) { 16 | handleRouteError(err, `/admin/mcp-servers/s/${id}`, profile.current); 17 | } 18 | 19 | return { 20 | mcpServer 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | timeout: 5m 4 | linters: 5 | default: none 6 | enable: 7 | - errcheck 8 | - govet 9 | - ineffassign 10 | - revive 11 | - staticcheck 12 | - thelper 13 | - unused 14 | - whitespace 15 | exclusions: 16 | generated: lax 17 | presets: 18 | - comments 19 | - common-false-positives 20 | - legacy 21 | - std-error-handling 22 | paths: 23 | - third_party$ 24 | - builtin$ 25 | - examples$ 26 | formatters: 27 | enable: 28 | - gofmt 29 | - goimports 30 | exclusions: 31 | generated: lax 32 | paths: 33 | - third_party$ 34 | - builtin$ 35 | - examples$ 36 | -------------------------------------------------------------------------------- /apiclient/types/cronjob.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type CronJob struct { 4 | Metadata 5 | CronJobManifest 6 | LastRunStartedAt *Time `json:"lastRunStartedAt,omitempty"` 7 | LastSuccessfulRunCompleted *Time `json:"lastSuccessfulRunCompleted,omitempty"` 8 | NextRunAt *Time `json:"nextRunAt,omitempty"` 9 | } 10 | 11 | type CronJobManifest struct { 12 | Description string `json:"description,omitempty"` 13 | Schedule string `json:"schedule,omitempty"` 14 | WorkflowName string `json:"workflowName,omitempty"` 15 | Input string `json:"input,omitempty"` 16 | TaskSchedule *Schedule `json:"taskSchedule,omitempty"` 17 | } 18 | 19 | type CronJobList List[CronJob] 20 | -------------------------------------------------------------------------------- /pkg/api/handlers/providers/authproviders.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | ) 7 | 8 | const ( 9 | CookieSecretEnvVar = "OBOT_AUTH_PROVIDER_COOKIE_SECRET" 10 | PostgresConnectionEnvVar = "OBOT_AUTH_PROVIDER_POSTGRES_CONNECTION_DSN" 11 | ) 12 | 13 | func ConvertAuthProviderToolRef(toolRef v1.ToolReference, cred map[string]string) (*types.AuthProviderStatus, error) { 14 | providerStatus, err := ConvertProviderToolRef(toolRef, cred) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &types.AuthProviderStatus{CommonProviderStatus: *providerStatus}, nil 20 | } 21 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/editor/Pdf.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | {#if blobUrl} 25 | 26 | {/if} 27 |
28 | -------------------------------------------------------------------------------- /.github/workflows/test-docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Test Docker Build 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - docs/** 8 | - chart/** 9 | 10 | jobs: 11 | build: 12 | runs-on: depot-ubuntu-22.04 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Depot 18 | uses: depot/setup-action@v1 19 | 20 | - name: Build Docker Image 21 | uses: depot/build-push-action@v1 22 | with: 23 | project: bbqjs4tj1g 24 | context: . 25 | platforms: linux/amd64,linux/arm64 26 | build-args: | 27 | BASE_IMAGE=ghcr.io/obot-platform/obot/base:latest 28 | -------------------------------------------------------------------------------- /pkg/api/authz/template.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 7 | "github.com/obot-platform/obot/pkg/system" 8 | kclient "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func (a *Authorizer) checkTemplate(req *http.Request, resources *Resources) (bool, error) { 12 | if resources.TemplateID == "" { 13 | return true, nil 14 | } 15 | 16 | var templateShareList v1.ThreadShareList 17 | err := a.cache.List(req.Context(), &templateShareList, kclient.InNamespace(system.DefaultNamespace), kclient.MatchingFields{ 18 | "spec.publicID": resources.TemplateID, 19 | }) 20 | 21 | return len(templateShareList.Items) > 0, err 22 | } 23 | -------------------------------------------------------------------------------- /ui/user/src/lib/stores/profile.svelte.ts: -------------------------------------------------------------------------------- 1 | import { type Profile } from '$lib/services/chat/types'; 2 | 3 | const store = $state({ 4 | current: { 5 | id: '', 6 | username: '', 7 | email: '', 8 | iconURL: '', 9 | role: 0, 10 | groups: [] 11 | } as Profile, 12 | initialize 13 | }); 14 | 15 | function initialize(profile?: Profile) { 16 | if (profile) { 17 | store.current = profile; 18 | if (profile.isBootstrapUser?.()) { 19 | store.current.displayName = 'Bootstrap'; 20 | } 21 | } else { 22 | store.current = { 23 | id: '', 24 | email: '', 25 | iconURL: '', 26 | role: 0, 27 | groups: [], 28 | unauthorized: true, 29 | username: '' 30 | }; 31 | } 32 | } 33 | 34 | export default store; 35 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/s/[id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MCP_CATALOG_ID } from '$lib/constants'; 2 | import { handleRouteError } from '$lib/errors'; 3 | import { AdminService } from '$lib/services'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ params, fetch }) => { 8 | const { id } = params; 9 | 10 | let mcpServer; 11 | try { 12 | mcpServer = await AdminService.getMCPCatalogServer(DEFAULT_MCP_CATALOG_ID, id, { 13 | fetch 14 | }); 15 | } catch (err) { 16 | handleRouteError(err, `/admin/mcp-servers/s/${id}/details`, profile.current); 17 | } 18 | 19 | return { 20 | mcpServer, 21 | id 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /.github/workflows/update-prod-env.yml: -------------------------------------------------------------------------------- 1 | name: Update Prod Env 2 | 3 | permissions: 4 | id-token: write 5 | contents: read 6 | packages: write 7 | 8 | on: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | copy-tag: 13 | runs-on: depot-ubuntu-22.04 14 | 15 | steps: 16 | - name: Log in to GitHub Container Registry 17 | uses: docker/login-action@v3 18 | with: 19 | registry: ghcr.io 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Setup crane 24 | uses: imjasonh/setup-crane@v0.4 25 | 26 | - name: Copy to prod tag 27 | run: | 28 | crane tag ghcr.io/${{ github.repository }}-enterprise:main prod 29 | -------------------------------------------------------------------------------- /ui/user/static/user/images/assistant/highlightai-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/cli/tools_update.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type ToolUpdate struct { 10 | root *Obot 11 | Quiet bool `usage:"Only print IDs of updated step template" short:"q"` 12 | } 13 | 14 | func (l *ToolUpdate) Customize(cmd *cobra.Command) { 15 | cmd.Use = "update [flags] NAME REFERENCE" 16 | cmd.Args = cobra.ExactArgs(2) 17 | } 18 | 19 | func (l *ToolUpdate) Run(cmd *cobra.Command, args []string) error { 20 | tr, err := l.root.Client.UpdateToolReference(cmd.Context(), args[0], args[1]) 21 | if err != nil { 22 | return err 23 | } 24 | if l.Quiet { 25 | fmt.Println(tr.ID) 26 | } else { 27 | fmt.Println("Step template updated:", tr.ID) 28 | } 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/controller/generationed/generationed.go: -------------------------------------------------------------------------------- 1 | package generationed 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/router" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | ) 7 | 8 | // UpdateObservedGeneration should be the last handler that runs on such an object to ensure 9 | // that updating of the observed generation only happens if no error occurs. 10 | func UpdateObservedGeneration(req router.Request, resp router.Response) error { 11 | if req.Object == nil { 12 | return nil 13 | } 14 | 15 | if errored, ok := resp.Attributes()["generation:errored"]; !ok || errored != true { 16 | req.Object.(v1.Generationed).SetObservedGeneration(req.Object.GetGeneration()) 17 | } 18 | 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /ui/user/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /pkg/api/authn/authn.go: -------------------------------------------------------------------------------- 1 | package authn 2 | 3 | import ( 4 | "net/http" 5 | 6 | "k8s.io/apiserver/pkg/authentication/authenticator" 7 | "k8s.io/apiserver/pkg/authentication/user" 8 | ) 9 | 10 | type Authenticator struct { 11 | authenticator authenticator.Request 12 | } 13 | 14 | func NewAuthenticator(authenticator authenticator.Request) *Authenticator { 15 | return &Authenticator{ 16 | authenticator: authenticator, 17 | } 18 | } 19 | 20 | func (a *Authenticator) Authenticate(req *http.Request) (user.Info, error) { 21 | resp, ok, err := a.authenticator.AuthenticateRequest(req) 22 | if err != nil { 23 | return nil, err 24 | } 25 | if !ok { 26 | panic("authentication should always succeed") 27 | } 28 | return resp.User, nil 29 | } 30 | -------------------------------------------------------------------------------- /ui/user/src/lib/context/projectTools.svelte.ts: -------------------------------------------------------------------------------- 1 | import type { AssistantTool } from '$lib/services'; 2 | import { getContext, hasContext, setContext } from 'svelte'; 3 | 4 | const PROJECT_TOOLS_CONTEXT_NAME = 'project-tools'; 5 | export interface ProjectTools { 6 | tools: AssistantTool[]; 7 | maxTools: number; 8 | } 9 | 10 | export function getProjectTools(): ProjectTools { 11 | if (!hasContext(PROJECT_TOOLS_CONTEXT_NAME)) { 12 | throw new Error('layout context not initialized'); 13 | } 14 | return getContext(PROJECT_TOOLS_CONTEXT_NAME); 15 | } 16 | 17 | export function initProjectTools(init: ProjectTools) { 18 | const projectTools = $state(init); 19 | setContext(PROJECT_TOOLS_CONTEXT_NAME, projectTools); 20 | } 21 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/Logo.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | Obot {variant} logo icon 22 | -------------------------------------------------------------------------------- /.github/workflows/update-conference-env.yml: -------------------------------------------------------------------------------- 1 | name: Update Conference Env 2 | 3 | permissions: 4 | id-token: write 5 | contents: read 6 | packages: write 7 | 8 | on: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | copy-tag: 13 | runs-on: depot-ubuntu-22.04 14 | 15 | steps: 16 | - name: Log in to GitHub Container Registry 17 | uses: docker/login-action@v3 18 | with: 19 | registry: ghcr.io 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Setup crane 24 | uses: imjasonh/setup-crane@v0.4 25 | 26 | - name: Copy to chat tag 27 | run: | 28 | crane tag ghcr.io/${{ github.repository }}-enterprise:main conference 29 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | //go:generate go run github.com/obot-platform/nah/cmd/deepcopy ./pkg/storage/apis/obot.obot.ai/v1/ 2 | //go:generate go run github.com/obot-platform/nah/cmd/deepcopy ./apiclient/types/ 3 | //go:generate go run k8s.io/kube-openapi/cmd/openapi-gen --go-header-file tools/header.txt --output-file openapi_generated.go --output-dir ./pkg/storage/openapi/generated/ --output-pkg github.com/obot-platform/obot/pkg/storage/openapi/generated github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1 k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/runtime k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/util/intstr k8s.io/api/coordination/v1 github.com/obot-platform/obot/apiclient/types 4 | 5 | package main 6 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-servers/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import type { PageLoad } from './$types'; 4 | import { redirect } from '@sveltejs/kit'; 5 | 6 | export const load: PageLoad = async ({ fetch, parent }) => { 7 | const { profile } = await parent(); 8 | let workspace; 9 | 10 | if (profile?.hasAdminAccess?.()) { 11 | throw redirect(302, '/admin/mcp-servers'); 12 | } 13 | 14 | try { 15 | const workspaces = await ChatService.listWorkspaces({ fetch }); 16 | workspace = workspaces.find((w) => w.userID === profile?.id) ?? null; 17 | } catch (err) { 18 | handleRouteError(err, `/mcp-servers`, profile); 19 | } 20 | 21 | return { 22 | workspace 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /apiclient/types/projectshare.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ProjectShare struct { 4 | Metadata 5 | ProjectShareManifest 6 | PublicID string `json:"publicID,omitempty"` 7 | ProjectID string `json:"projectID,omitempty"` 8 | Name string `json:"name,omitempty"` 9 | Description string `json:"description,omitempty"` 10 | Icons *AgentIcons `json:"icons"` 11 | Featured bool `json:"featured,omitempty"` 12 | Tools []string `json:"tools,omitempty"` 13 | Editor bool `json:"editor,omitempty"` 14 | } 15 | 16 | type ProjectShareManifest struct { 17 | Public bool `json:"public,omitempty"` 18 | Users []string `json:"users,omitempty"` 19 | } 20 | 21 | type ProjectShareList List[ProjectShare] 22 | -------------------------------------------------------------------------------- /pkg/gateway/types/runstate.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | type RunState struct { 9 | UserID string `json:"userID"` 10 | Name string `json:"name" gorm:"primaryKey"` 11 | Namespace string `json:"namespace" gorm:"primaryKey"` 12 | CreatedAt time.Time `json:"createdAt"` 13 | UpdatedAt time.Time `json:"updatedAt"` 14 | ThreadName string `json:"threadName,omitempty"` 15 | Program []byte `json:"program,omitempty"` 16 | ChatState []byte `json:"chatState,omitempty"` 17 | CallFrame []byte `json:"callFrame,omitempty"` 18 | Output []byte `json:"output,omitempty"` 19 | Done bool `json:"done,omitempty"` 20 | Error string `json:"error,omitempty"` 21 | } 22 | -------------------------------------------------------------------------------- /pkg/gateway/types/tempsetupuser.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import ( 5 | "time" 6 | 7 | types2 "github.com/obot-platform/obot/apiclient/types" 8 | ) 9 | 10 | type TempSetupUser struct { 11 | ID uint `json:"id" gorm:"primaryKey"` 12 | UserID uint `json:"userID" gorm:"index"` 13 | Username string `json:"username"` 14 | Email string `json:"email"` 15 | Role types2.Role `json:"role"` 16 | IconURL string `json:"iconURL"` 17 | AuthProviderName string `json:"authProviderName"` 18 | AuthProviderNamespace string `json:"authProviderNamespace"` 19 | CreatedAt time.Time `json:"createdAt"` 20 | } 21 | -------------------------------------------------------------------------------- /apiclient/types/webhook.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Webhook struct { 4 | Metadata 5 | WebhookManifest 6 | AliasAssigned *bool `json:"aliasAssigned,omitempty"` 7 | LastSuccessfulRunCompleted *Time `json:"lastSuccessfulRunCompleted,omitempty"` 8 | HasToken bool `json:"hasToken,omitempty"` 9 | } 10 | 11 | type WebhookManifest struct { 12 | Name string `json:"name"` 13 | Description string `json:"description"` 14 | Alias string `json:"alias"` 15 | WorkflowName string `json:"workflowName"` 16 | Headers []string `json:"headers"` 17 | Secret string `json:"secret"` 18 | ValidationHeader string `json:"validationHeader"` 19 | } 20 | 21 | type WebhookList List[Webhook] 22 | -------------------------------------------------------------------------------- /pkg/system/currentbin.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "path/filepath" 7 | ) 8 | 9 | // currentBin returns the executable of yourself to allow forking a new version of yourself 10 | // Copied from github.com/moby/moby/pkg/reexec d25b0bd7ea6ce17ca085c54d5965eeeb66417e52 11 | func currentBin() string { 12 | name := os.Args[0] 13 | if filepath.Base(name) == name { 14 | if lp, err := exec.LookPath(name); err == nil { 15 | return lp 16 | } 17 | } 18 | // handle conversion of relative paths to absolute 19 | if absName, err := filepath.Abs(name); err == nil { 20 | return absName 21 | } 22 | // if we couldn't get absolute name, return original 23 | // (NOTE: Go only errors on Abs() if os.Getwd fails) 24 | return name 25 | } 26 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/tasks/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, type Project, type Task } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch, params }) => { 7 | const { id } = params; 8 | 9 | let task: Task | null = null; 10 | let project: Project | null = null; 11 | try { 12 | task = await AdminService.getTask(id, { fetch }); 13 | if (task?.projectID) { 14 | project = await AdminService.getProject(task.projectID, { fetch }); 15 | } 16 | } catch (err) { 17 | handleRouteError(err, `/tasks/${id}`, profile.current); 18 | } 19 | 20 | return { 21 | task, 22 | project 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /apiclient/types/defaultmodelalias.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type DefaultModelAliasType string 4 | 5 | const ( 6 | DefaultModelAliasTypeTextEmbedding DefaultModelAliasType = "text-embedding" 7 | DefaultModelAliasTypeLLM DefaultModelAliasType = "llm" 8 | DefaultModelAliasTypeLLMMini DefaultModelAliasType = "llm-mini" 9 | DefaultModelAliasTypeImageGeneration DefaultModelAliasType = "image-generation" 10 | DefaultModelAliasTypeVision DefaultModelAliasType = "vision" 11 | ) 12 | 13 | type DefaultModelAlias struct { 14 | DefaultModelAliasManifest 15 | } 16 | 17 | type DefaultModelAliasManifest struct { 18 | Alias string `json:"alias"` 19 | Model string `json:"model"` 20 | } 21 | 22 | type DefaultModelAliasList List[DefaultModelAlias] 23 | -------------------------------------------------------------------------------- /pkg/storage/authz/authz.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "context" 5 | "slices" 6 | 7 | "k8s.io/apiserver/pkg/authorization/authorizer" 8 | ) 9 | 10 | const ( 11 | AdminName = "admin" 12 | AdminGroup = "system:admin" 13 | AuthenticatedGroup = "system:authenticated" 14 | ) 15 | 16 | type Authorizer struct { 17 | } 18 | 19 | func (*Authorizer) Authorize(_ context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { 20 | if slices.Contains(a.GetUser().GetGroups(), AdminGroup) { 21 | return authorizer.DecisionAllow, "", nil 22 | } 23 | if a.GetUser().GetName() == "system:apiserver" { 24 | return authorizer.DecisionAllow, "", nil 25 | } 26 | return authorizer.DecisionNoOpinion, "", nil 27 | } 28 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ui/render/render.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | {#if component} 19 | 20 | {@render children?.()} 21 | 22 | {:else} 23 | 24 | {@render children?.()} 25 | 26 | {/if} 27 | -------------------------------------------------------------------------------- /ui/user/static/admin/assets/xai-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-registries/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let accessControlRule; 10 | let workspaceId; 11 | try { 12 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile.current?.id, { fetch }); 13 | accessControlRule = await ChatService.getWorkspaceAccessControlRule(workspaceId, id, { fetch }); 14 | } catch (err) { 15 | handleRouteError(err, `/mcp-registries/${id}`, profile.current); 16 | } 17 | 18 | return { 19 | accessControlRule, 20 | workspaceId 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/SuccessNotifications.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/userdelete.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 4 | 5 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 6 | 7 | type UserDelete struct { 8 | metav1.TypeMeta `json:",inline"` 9 | metav1.ObjectMeta `json:"metadata,omitempty"` 10 | 11 | Spec UserDeleteSpec `json:"spec,omitempty"` 12 | Status EmptyStatus `json:"status,omitempty"` 13 | } 14 | 15 | type UserDeleteSpec struct { 16 | UserID uint `json:"userID,omitempty"` 17 | } 18 | 19 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 20 | 21 | type UserDeleteList struct { 22 | metav1.TypeMeta `json:",inline"` 23 | metav1.ListMeta `json:"metadata,omitempty"` 24 | Items []UserDelete `json:"items"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/api/authz/projectmcpserver.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/obot-platform/nah/pkg/router" 7 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 8 | "github.com/obot-platform/obot/pkg/system" 9 | "k8s.io/apiserver/pkg/authentication/user" 10 | ) 11 | 12 | func (a *Authorizer) checkProjectMCPServer(req *http.Request, resources *Resources, u user.Info) (bool, error) { 13 | if resources.ProjectMCPServerID == "" { 14 | return true, nil 15 | } 16 | 17 | var projectMCPServer v1.ProjectMCPServer 18 | if err := a.get(req.Context(), router.Key(system.DefaultNamespace, resources.ProjectMCPServerID), &projectMCPServer); err != nil { 19 | return false, err 20 | } 21 | 22 | return projectMCPServer.Spec.UserID == u.GetUID(), nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/cli/table.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/liggitt/tabwriter" 8 | ) 9 | 10 | type table struct { 11 | w *tabwriter.Writer 12 | err error 13 | } 14 | 15 | func newTable(cols ...string) *table { 16 | w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', tabwriter.RememberWidths) 17 | _, err := w.Write([]byte(strings.Join(cols, "\t") + "\n")) 18 | return &table{w: w, err: err} 19 | } 20 | 21 | func (t *table) Flush() { 22 | _ = t.w.Flush() 23 | } 24 | 25 | func (t *table) Err() error { 26 | if t.err != nil { 27 | return t.err 28 | } 29 | return t.w.Flush() 30 | } 31 | 32 | func (t *table) WriteRow(cols ...string) { 33 | if t.err != nil { 34 | return 35 | } 36 | _, t.err = t.w.Write([]byte(strings.Join(cols, "\t") + "\n")) 37 | } 38 | -------------------------------------------------------------------------------- /workflow.yaml: -------------------------------------------------------------------------------- 1 | alias: testamig 2 | output: The final number and the chose fruit 3 | workflows: 4 | - subflow 5 | steps: 6 | - step: Pick a number 7 | - step: Now add 1 to it 8 | - step: Now multiply by two 9 | - if: 10 | condition: The number is less than 100 11 | steps: 12 | - step: Subtract 2 13 | - while: 14 | maxLoops: 4 15 | condition: Number is less than 100 16 | steps: 17 | - step: Multiply times two 18 | - step: now invoke the workflow called subflow 19 | 20 | --- 21 | name: subflow 22 | steps: 23 | - step: What's the current number 24 | - step: Mod that by 3 and then pick apple, orange, or banana, treating that list as an array and pick the index according to the modulus. 25 | output: Print the chose fruit 26 | -------------------------------------------------------------------------------- /pkg/cli/run_print.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/obot-platform/obot/pkg/cli/events" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | type RunPrint struct { 9 | root *Obot 10 | Quiet bool `usage:"Only print the response content of the runs" short:"q"` 11 | Verbose bool `usage:"Print more information" short:"v"` 12 | } 13 | 14 | func (l *RunPrint) Customize(cmd *cobra.Command) { 15 | cmd.Use = "print [flags] [RUN_ID]" 16 | cmd.Args = cobra.ExactArgs(1) 17 | } 18 | 19 | func (l *RunPrint) Run(cmd *cobra.Command, args []string) error { 20 | debug, err := l.root.Client.RunEvents(cmd.Context(), args[0]) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | printer := events.NewPrinter(cmd.Context(), l.root.Client, l.Quiet, l.Verbose) 26 | return printer.Print(debug) 27 | } 28 | -------------------------------------------------------------------------------- /ui/user/src/routes/audit-logs/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 |
10 | 11 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /pkg/api/authz/mcpserverinstance.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/obot-platform/nah/pkg/router" 7 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 8 | "github.com/obot-platform/obot/pkg/system" 9 | "k8s.io/apiserver/pkg/authentication/user" 10 | ) 11 | 12 | func (a *Authorizer) checkMCPServerInstance(req *http.Request, resources *Resources, u user.Info) (bool, error) { 13 | if resources.MCPServerInstanceID == "" { 14 | return true, nil 15 | } 16 | 17 | var mcpServerInstance v1.MCPServerInstance 18 | if err := a.get(req.Context(), router.Key(system.DefaultNamespace, resources.MCPServerInstanceID), &mcpServerInstance); err != nil { 19 | return false, err 20 | } 21 | 22 | return mcpServerInstance.Spec.UserID == u.GetUID(), nil 23 | } 24 | -------------------------------------------------------------------------------- /ui/user/src/lib/actions/size.svelte.ts: -------------------------------------------------------------------------------- 1 | export function transitionParentHeight(node: HTMLElement, fn: () => unknown) { 2 | const onsizechange = () => { 3 | // Debounce the resize calculation 4 | requestAnimationFrame(() => { 5 | if (!node.parentElement) { 6 | return; 7 | } 8 | node.parentElement!.style.height = node.scrollHeight + 'px'; 9 | }); 10 | }; 11 | 12 | onsizechange(); 13 | 14 | const observer = new ResizeObserver(onsizechange); 15 | 16 | $effect(() => { 17 | // Recalculate the parent height programmatically 18 | fn(); 19 | 20 | onsizechange(); 21 | 22 | observer.observe(node); 23 | 24 | return () => { 25 | observer.disconnect(); 26 | 27 | if (node.parentElement) { 28 | node.parentElement!.style.height = 'auto'; 29 | } 30 | }; 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/primitives/draggable/contextItem.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext } from 'svelte'; 2 | 3 | const CONTEXT_KEY = '@obot/context/dragable-root/item'; 4 | 5 | export type DraggableItem = { 6 | id: string; 7 | data: T; 8 | }; 9 | 10 | export type DraggableItemContext = { 11 | readonly state: { 12 | id: string; 13 | data?: T; 14 | onPointerDown?: (ev: PointerEvent) => void; 15 | onPointerEnter?: (ev: PointerEvent) => void; 16 | onPointerLeave?: (ev: PointerEvent) => void; 17 | }; 18 | }; 19 | export function getDraggableItemContext(): DraggableItemContext { 20 | return getContext(CONTEXT_KEY); 21 | } 22 | 23 | export function setDraggableItemContext(context: DraggableItemContext) { 24 | return setContext(CONTEXT_KEY, context); 25 | } 26 | -------------------------------------------------------------------------------- /ui/user/src/routes/[agent]/+page.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | {#if title} 26 | {title} 27 | {/if} 28 | 29 | -------------------------------------------------------------------------------- /apiclient/types/tool.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ToolType string 4 | 5 | const ( 6 | ToolTypeContainer = ToolType("container") 7 | ToolTypeJavaScript = ToolType("javascript") 8 | ToolTypePython = ToolType("python") 9 | ToolTypeScript = ToolType("script") 10 | ) 11 | 12 | type ToolManifest struct { 13 | Name string `json:"name,omitempty"` 14 | Description string `json:"description,omitempty"` 15 | Icon string `json:"icon,omitempty"` 16 | ToolType ToolType `json:"toolType,omitempty"` 17 | Image string `json:"image,omitempty"` 18 | Context string `json:"context,omitempty"` 19 | Instructions string `json:"instructions,omitempty"` 20 | Params map[string]string `json:"params,omitempty"` 21 | } 22 | -------------------------------------------------------------------------------- /ui/user/static/admin/assets/azure_openai_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | _ "time/tzdata" 7 | 8 | "github.com/gptscript-ai/cmd" 9 | "github.com/gptscript-ai/gptscript/pkg/embedded" 10 | "github.com/nanobot-ai/nanobot/pkg/supervise" 11 | "github.com/obot-platform/obot/pkg/cli" 12 | ) 13 | 14 | func main() { 15 | if os.Getenv("GPTSCRIPT_EMBEDDED") != "false" { 16 | if embedded.Run(embedded.Options{}) { 17 | return 18 | } 19 | } 20 | if len(os.Args) > 1 && os.Args[1] == "_exec" { 21 | if err := supervise.Daemon(); err != nil { 22 | fmt.Printf("failed to run nanobot daemon: %v\n", err) 23 | os.Exit(1) 24 | } 25 | os.Exit(0) 26 | } 27 | // Don't shutdown on SIGTERM, only on SIGINT. SIGTERM is handled by the controller leader election 28 | cmd.ShutdownSignals = []os.Signal{os.Interrupt} 29 | cmd.Main(cli.New()) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/api/errors.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package api 3 | 4 | import ( 5 | "errors" 6 | "net/http" 7 | 8 | "github.com/obot-platform/obot/apiclient/types" 9 | apierrors "k8s.io/apimachinery/pkg/api/errors" 10 | ) 11 | 12 | func IsHTTPCode(err error, code int) bool { 13 | if err == nil { 14 | return false 15 | } 16 | if errHTTP := (*types.ErrHTTP)(nil); errors.As(err, &errHTTP) { 17 | return errHTTP.Code == code 18 | } else if errMeta := (*apierrors.StatusError)(nil); errors.As(err, &errMeta) { 19 | return errMeta.ErrStatus.Code == int32(code) 20 | } 21 | return false 22 | } 23 | 24 | func IsConflict(err error) bool { 25 | return IsHTTPCode(err, http.StatusConflict) 26 | } 27 | 28 | var ErrMustAuth = &types.ErrHTTP{ 29 | Code: http.StatusUnauthorized, 30 | Message: "unauthorized request, must authenticate", 31 | } 32 | -------------------------------------------------------------------------------- /pkg/gateway/context/context.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package context 3 | 4 | import ( 5 | "context" 6 | "log/slog" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | type reqIDKey struct{} 12 | 13 | func WithNewRequestID(ctx context.Context) context.Context { 14 | return context.WithValue(ctx, reqIDKey{}, uuid.NewString()) 15 | } 16 | 17 | func GetRequestID(ctx context.Context) string { 18 | s, _ := ctx.Value(reqIDKey{}).(string) 19 | return s 20 | } 21 | 22 | type loggerKey struct{} 23 | 24 | func WithLogger(ctx context.Context, log *slog.Logger) context.Context { 25 | return context.WithValue(ctx, loggerKey{}, log) 26 | } 27 | 28 | func GetLogger(ctx context.Context) *slog.Logger { 29 | l, ok := ctx.Value(loggerKey{}).(*slog.Logger) 30 | if !ok || l == nil { 31 | return slog.Default() 32 | } 33 | 34 | return l 35 | } 36 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/userrolechange.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 8 | 9 | type UserRoleChange struct { 10 | metav1.TypeMeta `json:",inline"` 11 | metav1.ObjectMeta `json:"metadata,omitempty"` 12 | 13 | Spec UserRoleChangeSpec `json:"spec,omitempty"` 14 | Status EmptyStatus `json:"status,omitempty"` 15 | } 16 | 17 | type UserRoleChangeSpec struct { 18 | UserID uint `json:"userID,omitempty"` 19 | } 20 | 21 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 22 | 23 | type UserRoleChangeList struct { 24 | metav1.TypeMeta `json:",inline"` 25 | metav1.ListMeta `json:"metadata,omitempty"` 26 | Items []UserRoleChange `json:"items"` 27 | } 28 | -------------------------------------------------------------------------------- /pkg/api/authz/pathmatcher.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import "net/http" 4 | 5 | type pathMatcher struct { 6 | m *http.ServeMux 7 | } 8 | 9 | func newPathMatcher(paths ...string) *pathMatcher { 10 | m := http.NewServeMux() 11 | for _, path := range paths { 12 | m.Handle(path, (*fake)(nil)) 13 | } 14 | return &pathMatcher{m: m} 15 | } 16 | 17 | type GetVar func(string) string 18 | 19 | func (p *pathMatcher) Match(req *http.Request) (GetVar, bool) { 20 | if p == nil { 21 | return nil, false 22 | } 23 | 24 | r := req.Clone(req.Context()) 25 | _, pattern := p.m.Handler(r) 26 | if pattern == "" { 27 | return nil, false 28 | } 29 | 30 | // Note: This will reset the path values for this request, otherwise they won't match what we have set in the authorize router. 31 | p.m.ServeHTTP(nil, r) 32 | return r.PathValue, true 33 | } 34 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/usergroupchange.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 8 | 9 | type UserGroupChange struct { 10 | metav1.TypeMeta `json:",inline"` 11 | metav1.ObjectMeta `json:"metadata,omitempty"` 12 | 13 | Spec UserGroupChangeSpec `json:"spec,omitempty"` 14 | Status EmptyStatus `json:"status,omitempty"` 15 | } 16 | 17 | type UserGroupChangeSpec struct { 18 | UserID uint `json:"userID,omitempty"` 19 | } 20 | 21 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 22 | 23 | type UserGroupChangeList struct { 24 | metav1.TypeMeta `json:",inline"` 25 | metav1.ListMeta `json:"metadata,omitempty"` 26 | Items []UserGroupChange `json:"items"` 27 | } 28 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/Success.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
18 |
19 | 20 |
21 |
22 | {message} 23 |
24 | {#if onClose} 25 | 28 | {/if} 29 |
30 | -------------------------------------------------------------------------------- /ui/user/src/lib/services/admin/constants.ts: -------------------------------------------------------------------------------- 1 | import { Role } from './types'; 2 | 3 | export const userRoleOptions = [ 4 | { 5 | id: Role.BASIC, 6 | label: 'Basic User', 7 | description: 'Connect to MCP servers made available through registries and use Chat.' 8 | }, 9 | { 10 | id: Role.POWERUSER, 11 | label: 'Power User', 12 | description: 13 | 'In addition to basic user features, users can publish custom MCP servers for their own personal use.' 14 | }, 15 | { 16 | id: Role.POWERUSER_PLUS, 17 | label: 'Power User Plus', 18 | description: 19 | 'In addition to power user features, users can share their custom MCP servers through their own registries.' 20 | }, 21 | { 22 | id: Role.ADMIN, 23 | label: 'Admin', 24 | description: 'Every user is a full admin. Use caution when selecting this option.' 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /pkg/storage/tables/table/fmt.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | ) 7 | 8 | func SimpleFormat(values [][]string) (string, string) { 9 | headerBuffer := bytes.Buffer{} 10 | valueBuffer := bytes.Buffer{} 11 | for _, v := range values { 12 | appendTabDelim(&headerBuffer, strings.ToUpper(v[0])) 13 | if strings.Contains(v[1], "{{") { 14 | appendTabDelim(&valueBuffer, v[1]) 15 | } else { 16 | appendTabDelim(&valueBuffer, "{{."+v[1]+"}}") 17 | } 18 | } 19 | 20 | headerBuffer.WriteString("\n") 21 | valueBuffer.WriteString("\n") 22 | 23 | return headerBuffer.String(), valueBuffer.String() 24 | } 25 | 26 | func appendTabDelim(buf *bytes.Buffer, value string) { 27 | if buf.Len() == 0 { 28 | buf.WriteString(value) 29 | } else { 30 | buf.WriteString("\t") 31 | buf.WriteString(value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/grouprolechange.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 8 | 9 | type GroupRoleChange struct { 10 | metav1.TypeMeta `json:",inline"` 11 | metav1.ObjectMeta `json:"metadata,omitempty"` 12 | 13 | Spec GroupRoleChangeSpec `json:"spec,omitempty"` 14 | Status EmptyStatus `json:"status,omitempty"` 15 | } 16 | 17 | type GroupRoleChangeSpec struct { 18 | GroupName string `json:"groupName,omitempty"` 19 | } 20 | 21 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 22 | 23 | type GroupRoleChangeList struct { 24 | metav1.TypeMeta `json:",inline"` 25 | metav1.ListMeta `json:"metadata,omitempty"` 26 | Items []GroupRoleChange `json:"items"` 27 | } 28 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/auth-providers/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, ChatService } from '$lib/services'; 3 | import type { AuthProvider } from '$lib/services/admin/types'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | import { redirect } from '@sveltejs/kit'; 7 | 8 | export const load: PageLoad = async ({ fetch }) => { 9 | const version = await ChatService.getVersion({ fetch }); 10 | if (!version.authEnabled) { 11 | throw redirect(302, '/admin'); 12 | } 13 | 14 | let authProviders: AuthProvider[] = []; 15 | try { 16 | authProviders = await AdminService.listAuthProviders({ fetch }); 17 | } catch (err) { 18 | handleRouteError(err, '/admin/auth-providers', profile.current); 19 | } 20 | 21 | return { 22 | authProviders 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /.github/workflows/user-ui.yml: -------------------------------------------------------------------------------- 1 | name: user 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths: 11 | - ui/user/** 12 | 13 | jobs: 14 | lint: 15 | runs-on: depot-ubuntu-22.04 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: "22.12.0" 25 | 26 | - name: Set up pnpm 27 | uses: pnpm/action-setup@v4 28 | with: 29 | version: 9.12.3 30 | 31 | - name: Run linter 32 | run: | 33 | cd ui/user 34 | pnpm install 35 | pnpm run build 36 | pnpm run ci 37 | 38 | - name: Verify no changes 39 | run: make no-changes 40 | -------------------------------------------------------------------------------- /pkg/controller/handlers/cleanup/projectmcp.go: -------------------------------------------------------------------------------- 1 | package cleanup 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/obot-platform/nah/pkg/router" 7 | "github.com/obot-platform/obot/pkg/mcp" 8 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 9 | ) 10 | 11 | func (c *Credentials) ShutdownProjectMCP(req router.Request, _ router.Response) error { 12 | projectServer := req.Object.(*v1.ProjectMCPServer) 13 | 14 | config, err := mcp.ProjectServerToConfig(*projectServer, c.serverURL, c.internalServerURL, projectServer.Spec.UserID) 15 | if err != nil { 16 | return fmt.Errorf("failed to convert project server to config: %w", err) 17 | } 18 | 19 | if err = c.mcpSessionManager.CloseClient(req.Ctx, config, "default"); err != nil { 20 | return fmt.Errorf("failed to shutdown project server: %w", err) 21 | } 22 | 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-servers/c/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | let workspaceId; 9 | let catalogEntry; 10 | try { 11 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile.current?.id, { fetch }); 12 | } catch (_err) { 13 | // may not have a workspace id if basic user atm 14 | workspaceId = undefined; 15 | } 16 | 17 | try { 18 | catalogEntry = await ChatService.getMCP(id, { fetch }); 19 | } catch (err) { 20 | handleRouteError(err, `/mcp-servers/c/${id}`, profile.current); 21 | } 22 | 23 | return { 24 | workspaceId, 25 | catalogEntry 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-servers/s/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let mcpServer; 10 | let workspaceId; 11 | try { 12 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile.current?.id, { fetch }); 13 | } catch (_err) { 14 | // may not have a workspace id if basic user atm 15 | workspaceId = undefined; 16 | } 17 | 18 | try { 19 | mcpServer = await ChatService.getMcpCatalogServer(id, { fetch }); 20 | } catch (err) { 21 | handleRouteError(err, `/mcp-servers/s/${id}`, profile.current); 22 | } 23 | 24 | return { 25 | mcpServer, 26 | workspaceId 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-servers/s/[id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const { id } = params; 8 | 9 | let workspaceId; 10 | let mcpServer; 11 | try { 12 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile.current?.id, { fetch }); 13 | } catch (_err) { 14 | // can happen if basic user atm 15 | workspaceId = undefined; 16 | } 17 | 18 | try { 19 | mcpServer = await ChatService.getMcpCatalogServer(id, { fetch }); 20 | } catch (err) { 21 | handleRouteError(err, `/mcp-servers/s/${id}/details`, profile.current); 22 | } 23 | return { 24 | workspaceId, 25 | mcpServer, 26 | id 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /ui/user/static/user/images/assistant/vscode-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/update-chat-env.yml: -------------------------------------------------------------------------------- 1 | name: Update Chat Env 2 | 3 | permissions: 4 | id-token: write 5 | contents: read 6 | packages: write 7 | 8 | on: 9 | schedule: 10 | - cron: "0 03 * * *" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | copy-tag: 15 | if: github.event_name != 'schedule' || vars.DISABLE_CHAT_AUTO_UPDATE != 'true' 16 | runs-on: depot-ubuntu-22.04 17 | 18 | steps: 19 | - name: Log in to GitHub Container Registry 20 | uses: docker/login-action@v3 21 | with: 22 | registry: ghcr.io 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Setup crane 27 | uses: imjasonh/setup-crane@v0.4 28 | 29 | - name: Copy to chat tag 30 | run: | 31 | crane tag ghcr.io/${{ github.repository }}-enterprise:main chat 32 | -------------------------------------------------------------------------------- /.github/workflows/update-demo-env.yml: -------------------------------------------------------------------------------- 1 | name: Update Demo Env 2 | 3 | permissions: 4 | id-token: write 5 | contents: read 6 | packages: write 7 | 8 | on: 9 | schedule: 10 | - cron: "0 03 * * *" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | copy-tag: 15 | if: github.event_name != 'schedule' || vars.DISABLE_DEMO_AUTO_UPDATE != 'true' 16 | runs-on: depot-ubuntu-22.04 17 | 18 | steps: 19 | - name: Log in to GitHub Container Registry 20 | uses: docker/login-action@v3 21 | with: 22 | registry: ghcr.io 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Setup crane 27 | uses: imjasonh/setup-crane@v0.4 28 | 29 | - name: Copy to demo tag 30 | run: | 31 | crane tag ghcr.io/${{ github.repository }}-enterprise:main demo 32 | -------------------------------------------------------------------------------- /pkg/api/authn/token.go: -------------------------------------------------------------------------------- 1 | package authn 2 | 3 | import ( 4 | "net/http" 5 | 6 | "k8s.io/apiserver/pkg/authentication/authenticator" 7 | "k8s.io/apiserver/pkg/authentication/user" 8 | ) 9 | 10 | type Token struct { 11 | Token, Username string 12 | Groups []string 13 | } 14 | 15 | func NewToken(token, username string, groups ...string) *Token { 16 | return &Token{ 17 | Token: token, 18 | Username: username, 19 | Groups: groups, 20 | } 21 | } 22 | 23 | func (t *Token) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { 24 | if req.Header.Get("Authorization") == "Bearer "+t.Token { 25 | return &authenticator.Response{ 26 | User: &user.DefaultInfo{ 27 | UID: t.Username, 28 | Name: t.Username, 29 | Groups: t.Groups, 30 | }, 31 | }, true, nil 32 | } 33 | 34 | return nil, false, nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/logutil/dsn.go: -------------------------------------------------------------------------------- 1 | package logutil 2 | 3 | import "strings" 4 | 5 | // SanitizeDSN removes credentials from a database DSN for safe logging 6 | func SanitizeDSN(dsn string) string { 7 | if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { 8 | // Find the @ symbol that separates credentials from host 9 | atIndex := strings.Index(dsn, "@") 10 | if atIndex == -1 { 11 | return dsn 12 | } 13 | 14 | schemeEnd := strings.Index(dsn, "://") 15 | if schemeEnd == -1 { 16 | return dsn 17 | } 18 | 19 | // Extract scheme and host+path parts 20 | schemePrefix := dsn[:schemeEnd+3] 21 | hostAndPath := dsn[atIndex+1:] 22 | 23 | // Return sanitized version: scheme + [REDACTED] + @ + host+path 24 | return schemePrefix + "[REDACTED]@" + hostAndPath 25 | } 26 | 27 | // For SQLite, just return as-is 28 | return dsn 29 | } 30 | -------------------------------------------------------------------------------- /ui/user/src/routes/[agent]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const agentID = params.agent; 8 | try { 9 | const agent = await ChatService.getAssistant(agentID, { fetch }); 10 | const projects = await ChatService.listProjects({ fetch }); 11 | let project = projects.items.find((p) => p.assistantID === agent.id); 12 | if (!project) { 13 | project = await ChatService.createProject(agent.id, { 14 | name: agent.name, 15 | description: agent.description, 16 | fetch 17 | }); 18 | } 19 | return { 20 | project 21 | }; 22 | } catch (e) { 23 | handleRouteError(e, `/${agentID}`, profile.current); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /pkg/cli/tools_unregister.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/obot-platform/obot/apiclient/types" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type ToolUnregister struct { 11 | root *Obot 12 | Quiet bool `usage:"Only print IDs of unregistered tool references" short:"q"` 13 | } 14 | 15 | func (l *ToolUnregister) Customize(cmd *cobra.Command) { 16 | cmd.Use = "unregister [flags] [ID...]" 17 | cmd.Aliases = []string{"rm", "del", "d"} 18 | } 19 | 20 | func (l *ToolUnregister) Run(cmd *cobra.Command, args []string) error { 21 | for _, arg := range args { 22 | if err := l.root.Client.DeleteToolReference(cmd.Context(), arg, types.ToolReferenceTypeTool); err != nil { 23 | return err 24 | } 25 | if l.Quiet { 26 | fmt.Println(arg) 27 | } else { 28 | fmt.Println("Tool reference deleted:", arg) 29 | } 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/controller/migrate.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | 6 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 7 | "github.com/obot-platform/obot/pkg/system" 8 | kclient "sigs.k8s.io/controller-runtime/pkg/client" 9 | ) 10 | 11 | func addCatalogIDToAccessControlRules(ctx context.Context, client kclient.Client) error { 12 | var acRules v1.AccessControlRuleList 13 | if err := client.List(ctx, &acRules); err != nil { 14 | return err 15 | } 16 | 17 | // Iterate over each AccessControlRule and add CatalogID 18 | for _, acRule := range acRules.Items { 19 | if acRule.Spec.MCPCatalogID == "" && acRule.Spec.PowerUserWorkspaceID == "" { 20 | acRule.Spec.MCPCatalogID = system.DefaultCatalog 21 | if err := client.Update(ctx, &acRule); err != nil { 22 | return err 23 | } 24 | } 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /apiclient/types/mcpserverdetails.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type MCPServerEvent struct { 4 | Time Time `json:"time"` 5 | Reason string `json:"reason"` 6 | Message string `json:"message"` 7 | EventType string `json:"eventType"` 8 | Action string `json:"action"` 9 | Count int32 `json:"count"` 10 | ResourceName string `json:"resourceName"` 11 | ResourceKind string `json:"resourceKind"` 12 | } 13 | 14 | type MCPServerDetails struct { 15 | DeploymentName string `json:"deploymentName"` 16 | Namespace string `json:"namespace"` 17 | LastRestart Time `json:"lastRestart"` 18 | ReadyReplicas int32 `json:"readyReplicas"` 19 | Replicas int32 `json:"replicas"` 20 | IsAvailable bool `json:"isAvailable"` 21 | Events []MCPServerEvent `json:"events"` 22 | } 23 | -------------------------------------------------------------------------------- /pkg/gateway/db/remove_github_groups.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/obot-platform/obot/pkg/gateway/types" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func removeGitHubGroups(tx *gorm.DB) error { 11 | // Check if tables exist 12 | if !tx.Migrator().HasTable(&types.GroupMemberships{}) || !tx.Migrator().HasTable(&types.Group{}) { 13 | return nil 14 | } 15 | 16 | // Delete from group_memberships first (foreign key constraint order) 17 | if err := tx.Where("group_id LIKE ?", "github/%").Delete(&types.GroupMemberships{}).Error; err != nil { 18 | return fmt.Errorf("failed to delete GitHub group memberships: %w", err) 19 | } 20 | 21 | // Delete from groups 22 | if err := tx.Where("id LIKE ?", "github/%").Delete(&types.Group{}).Error; err != nil { 23 | return fmt.Errorf("failed to delete GitHub groups: %w", err) 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/template.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | 3 | // TODO: MAKE SURE YOU ADD NEW TYPES TO scheme.go 4 | 5 | package v1 6 | 7 | import ( 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 12 | 13 | type Template struct { 14 | metav1.TypeMeta `json:",inline"` 15 | metav1.ObjectMeta `json:"metadata,omitempty"` 16 | 17 | Spec TemplateSpec `json:"spec,omitempty"` 18 | Status TemplateStatus `json:"status,omitempty"` 19 | } 20 | 21 | type TemplateSpec struct { 22 | } 23 | 24 | type TemplateStatus struct { 25 | } 26 | 27 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 28 | 29 | type TemplateList struct { 30 | metav1.TypeMeta `json:",inline"` 31 | metav1.ListMeta `json:"metadata,omitempty"` 32 | 33 | Items []Template `json:"items"` 34 | } 35 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/c/[id]/instance/[ms_id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MCP_CATALOG_ID } from '$lib/constants'; 2 | import { handleRouteError } from '$lib/errors'; 3 | import { AdminService } from '$lib/services'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ params, fetch }) => { 8 | const catalogEntryId = params.id; 9 | const mcpServerId = params.ms_id; 10 | 11 | let catalogEntry; 12 | try { 13 | catalogEntry = await AdminService.getMCPCatalogEntry(DEFAULT_MCP_CATALOG_ID, catalogEntryId, { 14 | fetch 15 | }); 16 | } catch (err) { 17 | handleRouteError( 18 | err, 19 | `/admin/mcp-servers/c/${catalogEntryId}/instance/${mcpServerId}`, 20 | profile.current 21 | ); 22 | } 23 | 24 | return { 25 | catalogEntry, 26 | mcpServerId 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /pkg/controller/handlers/oauthclients/oauthclients.go: -------------------------------------------------------------------------------- 1 | package oauthclients 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gptscript-ai/go-gptscript" 7 | "github.com/obot-platform/nah/pkg/router" 8 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 9 | ) 10 | 11 | type Handler struct { 12 | gptClient *gptscript.GPTScript 13 | } 14 | 15 | func NewHandler(gptClient *gptscript.GPTScript) *Handler { 16 | return &Handler{ 17 | gptClient: gptClient, 18 | } 19 | } 20 | 21 | func (h *Handler) CleanupOAuthClientCred(req router.Request, _ router.Response) error { 22 | o := req.Object.(*v1.OAuthClient) 23 | 24 | if o.Spec.MCPServerName == "" { 25 | return nil 26 | } 27 | 28 | if err := h.gptClient.DeleteCredential(req.Ctx, o.Spec.MCPServerName, o.Spec.MCPServerName); !errors.As(err, &gptscript.ErrNotFound{}) { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/w/[wid]/c/[id]/instance/[ms_id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const workspaceId = params.wid; 8 | const catalogEntryId = params.id; 9 | const mcpServerId = params.ms_id; 10 | 11 | let catalogEntry; 12 | try { 13 | catalogEntry = await ChatService.getWorkspaceMCPCatalogEntry(workspaceId, catalogEntryId, { 14 | fetch 15 | }); 16 | } catch (err) { 17 | handleRouteError( 18 | err, 19 | `/admin/mcp-servers/w/${workspaceId}/c/${catalogEntryId}/instance/${mcpServerId}`, 20 | profile.current 21 | ); 22 | } 23 | 24 | return { 25 | workspaceId, 26 | catalogEntry, 27 | mcpServerId 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-registries/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService } from '$lib/services'; 3 | import type { AccessControlRule } from '$lib/services/admin/types'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ fetch }) => { 8 | let accessControlRules: AccessControlRule[] = []; 9 | 10 | try { 11 | const adminAccessControlRules = await AdminService.listAccessControlRules({ fetch }); 12 | const userWorkspacesAccessControlRules = 13 | await AdminService.listAllUserWorkspaceAccessControlRules({ fetch }); 14 | accessControlRules = [...adminAccessControlRules, ...userWorkspacesAccessControlRules]; 15 | } catch (err) { 16 | handleRouteError(err, '/admin/mcp-registries', profile.current); 17 | } 18 | 19 | return { 20 | accessControlRules 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/userdefaultrolesetting.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 9 | 10 | type UserDefaultRoleSetting struct { 11 | metav1.TypeMeta `json:",inline"` 12 | metav1.ObjectMeta `json:"metadata,omitempty"` 13 | 14 | Spec UserDefaultRoleSettingSpec `json:"spec,omitempty"` 15 | } 16 | 17 | type UserDefaultRoleSettingSpec struct { 18 | // Role is the default role for new users 19 | Role types.Role `json:"role,omitempty"` 20 | } 21 | 22 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 23 | 24 | type UserDefaultRoleSettingList struct { 25 | metav1.TypeMeta `json:",inline"` 26 | metav1.ListMeta `json:"metadata,omitempty"` 27 | 28 | Items []UserDefaultRoleSetting `json:"items"` 29 | } 30 | -------------------------------------------------------------------------------- /pkg/gateway/client/image.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/obot-platform/obot/pkg/gateway/types" 7 | ) 8 | 9 | // CreateImage stores a new image in the database 10 | func (c *Client) CreateImage(ctx context.Context, data []byte, mimeType string) (*types.Image, error) { 11 | img := &types.Image{ 12 | Data: data, 13 | MIMEType: mimeType, 14 | } 15 | 16 | return img, c.db.WithContext(ctx).Create(img).Error 17 | } 18 | 19 | // GetImage retrieves an image by its ID 20 | func (c *Client) GetImage(ctx context.Context, id string) (*types.Image, error) { 21 | img := new(types.Image) 22 | return img, c.db.WithContext(ctx).Where("id = ?", id).First(img).Error 23 | } 24 | 25 | // DeleteImage removes an image from the database 26 | func (c *Client) DeleteImage(ctx context.Context, id string) error { 27 | return c.db.WithContext(ctx).Where("id = ?", id).Delete(&types.Image{}).Error 28 | } 29 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/editor/Image.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#await src then src} 26 |
27 | AI generated, content unknown 28 |
29 | {/await} 30 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/OverflowContainer.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
28 | {@render children?.({ 29 | x: x, 30 | y: y 31 | })} 32 |
33 | -------------------------------------------------------------------------------- /apiclient/types/usage.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type TokenUsage struct { 4 | UserID string `json:"userID,omitempty"` 5 | RunName string `json:"runName,omitempty"` 6 | PromptTokens int `json:"promptTokens"` 7 | CompletionTokens int `json:"completionTokens"` 8 | TotalTokens int `json:"totalTokens"` 9 | Date Time `json:"date,omitzero"` 10 | PersonalToken bool `json:"personalToken"` 11 | } 12 | 13 | type TokenUsageList List[TokenUsage] 14 | 15 | type RemainingTokenUsage struct { 16 | UserID string `json:"userID,omitempty"` 17 | PromptTokens int `json:"promptTokens"` 18 | CompletionTokens int `json:"completionTokens"` 19 | UnlimitedPromptTokens bool `json:"unlimitedPromptTokens"` 20 | UnlimitedCompletionTokens bool `json:"unlimitedCompletionTokens"` 21 | } 22 | 23 | type RemainingTokenUsageList List[RemainingTokenUsage] 24 | -------------------------------------------------------------------------------- /apiclient/types/template.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type ProjectTemplate struct { 4 | Metadata 5 | ProjectSnapshot ThreadManifest `json:"projectSnapshot,omitempty"` 6 | ProjectSnapshotLastUpgraded *Time `json:"projectSnapshotLastUpgraded,omitempty"` 7 | ProjectSnapshotStale bool `json:"projectSnapshotStale,omitempty"` 8 | ProjectSnapshotUpgradeInProgress bool `json:"projectSnapshotUpgradeInProgress,omitempty"` 9 | MCPServers []string `json:"mcpServers,omitempty"` 10 | AssistantID string `json:"assistantID,omitempty"` 11 | ProjectID string `json:"projectID,omitempty"` 12 | PublicID string `json:"publicID,omitempty"` 13 | Ready bool `json:"ready,omitempty"` 14 | } 15 | 16 | type ProjectTemplateList List[ProjectTemplate] 17 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/mcpsession.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 8 | 9 | type MCPSession struct { 10 | v1.TypeMeta `json:",inline"` 11 | v1.ObjectMeta `json:"metadata,omitempty"` 12 | Spec MCPSessionSpec `json:"spec"` 13 | Status MCPSessionStatus `json:"status"` 14 | } 15 | 16 | type MCPSessionSpec struct { 17 | MCPID string `json:"mcpID"` 18 | UserID string `json:"userID"` 19 | State []byte `json:"state"` 20 | } 21 | 22 | type MCPSessionStatus struct { 23 | LastUsedTime v1.Time `json:"lastUsedTime,omitempty"` 24 | } 25 | 26 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 27 | 28 | type MCPSessionList struct { 29 | v1.TypeMeta `json:",inline"` 30 | v1.ListMeta `json:"metadata,omitempty"` 31 | Items []MCPSession `json:"items"` 32 | } 33 | -------------------------------------------------------------------------------- /pkg/api/authz/poweruserworkspace.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 7 | "github.com/obot-platform/obot/pkg/system" 8 | "k8s.io/apiserver/pkg/authentication/user" 9 | kclient "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | func (a *Authorizer) checkPowerUserWorkspace(req *http.Request, resources *Resources, user user.Info) (bool, error) { 13 | if resources.WorkspaceID == "" { 14 | return true, nil 15 | } 16 | 17 | var workspace v1.PowerUserWorkspace 18 | if err := a.get(req.Context(), kclient.ObjectKey{ 19 | Namespace: system.DefaultNamespace, 20 | Name: resources.WorkspaceID, 21 | }, &workspace); err != nil { 22 | return false, err 23 | } 24 | 25 | if workspace.Spec.UserID == user.GetUID() { 26 | resources.Authorizated.PowerUserWorkspace = &workspace 27 | return true, nil 28 | } 29 | 30 | return false, nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/gateway/types/grouproleassignment.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package types 3 | 4 | import ( 5 | "time" 6 | 7 | types2 "github.com/obot-platform/obot/apiclient/types" 8 | ) 9 | 10 | // GroupRoleAssignment assigns a role to all members of an auth provider group. 11 | type GroupRoleAssignment struct { 12 | // GroupName is the name of the auth provider group (used as primary key) 13 | GroupName string `json:"groupName" gorm:"primaryKey"` 14 | 15 | // CreatedAt is when the assignment was created 16 | CreatedAt time.Time `json:"createdAt" gorm:"autoCreateTime"` 17 | 18 | // UpdatedAt is when the assignment was last modified 19 | UpdatedAt time.Time `json:"updatedAt" gorm:"autoUpdateTime"` 20 | 21 | // Role is the role to assign to all members of the group 22 | Role types2.Role `json:"role" gorm:"not null"` 23 | 24 | // Description is an optional description of why this assignment exists 25 | Description string `json:"description"` 26 | } 27 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | //nolint:revive 2 | package utils 3 | 4 | // SlicesEqualIgnoreOrder returns true if slices a and b contain the same elements 5 | // with the same multiplicities, regardless of their order. 6 | func SlicesEqualIgnoreOrder[T comparable](a, b []T) bool { 7 | // If lengths differ, they cannot be equal 8 | if len(a) != len(b) { 9 | return false 10 | } 11 | 12 | // Count occurrences of each element in slice a 13 | counts := make(map[T]int, len(a)) 14 | for _, v := range a { 15 | counts[v]++ 16 | } 17 | 18 | // Subtract counts based on slice b 19 | for _, v := range b { 20 | if c, ok := counts[v]; !ok || c == 0 { 21 | // Either v was not in a, or b has more occurrences than a 22 | return false 23 | } 24 | counts[v]-- 25 | } 26 | 27 | // Ensure all counts are zero (every a element matched by b) 28 | for _, c := range counts { 29 | if c != 0 { 30 | return false 31 | } 32 | } 33 | 34 | return true 35 | } 36 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/task-runs/[id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { AdminService, type Project, type Task, type Thread } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ fetch, params }) => { 7 | const { id } = params; 8 | 9 | let taskRun: Thread | null = null; 10 | let task: Task | null = null; 11 | let project: Project | null = null; 12 | try { 13 | taskRun = await AdminService.getThread(id, { fetch }); 14 | if (taskRun?.taskID) { 15 | task = await AdminService.getTask(taskRun.taskID, { fetch }); 16 | } 17 | if (task?.projectID) { 18 | project = await AdminService.getProject(task.projectID, { fetch }); 19 | } 20 | } catch (err) { 21 | handleRouteError(err, `/tasks/${id}`, profile.current); 22 | } 23 | 24 | return { 25 | taskRun, 26 | task, 27 | project 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /ui/user/src/lib/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './chat/operations'; 2 | export * from './chat/types'; 3 | export * from './admin/types'; 4 | export { default as AdminService } from './admin'; 5 | export { default as ChatService } from './chat'; 6 | export { default as EditorService } from './editor/index.svelte'; 7 | export type { Fetcher } from './http'; 8 | 9 | export async function updateMemory( 10 | assistantId: string, 11 | projectId: string, 12 | memoryId: string, 13 | content: string 14 | ) { 15 | const response = await fetch( 16 | `/api/assistants/${assistantId}/projects/${projectId}/memories/${memoryId}`, 17 | { 18 | method: 'PUT', 19 | headers: { 20 | 'Content-Type': 'application/json' 21 | }, 22 | body: JSON.stringify({ content }) 23 | } 24 | ); 25 | 26 | if (!response.ok) { 27 | throw new Error(`Failed to update memory: ${response.status} ${response.statusText}`); 28 | } 29 | 30 | return response.json(); 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/weekly-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Update Snapshot Tag 2 | 3 | permissions: 4 | id-token: write 5 | contents: read 6 | packages: write 7 | 8 | on: 9 | workflow_dispatch: 10 | schedule: 11 | - cron: '0 21 * * 5' 12 | 13 | jobs: 14 | copy-tag: 15 | runs-on: depot-ubuntu-22.04 16 | 17 | steps: 18 | - name: Log in to GitHub Container Registry 19 | uses: docker/login-action@v3 20 | with: 21 | registry: ghcr.io 22 | username: ${{ github.actor }} 23 | password: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Setup crane 26 | uses: imjasonh/setup-crane@v0.4 27 | 28 | - name: Copy to oss snapshot tag 29 | run: | 30 | crane tag ghcr.io/${{ github.repository }}:main main-$(date +%Y%m%d) 31 | 32 | - name: Copy to enterprise snapshot tag 33 | run: | 34 | crane tag ghcr.io/${{ github.repository }}-enterprise:main main-$(date +%Y%m%d) 35 | -------------------------------------------------------------------------------- /pkg/api/authz/run.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/obot-platform/nah/pkg/router" 7 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 8 | "github.com/obot-platform/obot/pkg/system" 9 | "k8s.io/apiserver/pkg/authentication/user" 10 | ) 11 | 12 | func (a *Authorizer) checkRun(req *http.Request, resources *Resources, _ user.Info) (bool, error) { 13 | if resources.RunID == "" || resources.RunID == "editor" { 14 | return true, nil 15 | } 16 | 17 | if resources.Authorizated.Task == nil { 18 | return false, nil 19 | } 20 | 21 | var ( 22 | wfe v1.WorkflowExecution 23 | ) 24 | 25 | if err := a.get(req.Context(), router.Key(system.DefaultNamespace, resources.RunID), &wfe); err != nil { 26 | return false, err 27 | } 28 | 29 | if resources.Authorizated.Task.Name != wfe.Spec.WorkflowName { 30 | return false, nil 31 | } 32 | 33 | resources.Authorizated.Run = &wfe 34 | return true, nil 35 | } 36 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-registries/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import type { AccessControlRule } from '$lib/services/admin/types'; 4 | import type { PageLoad } from './$types'; 5 | import { redirect } from '@sveltejs/kit'; 6 | 7 | export const load: PageLoad = async ({ fetch, parent }) => { 8 | let accessControlRules: AccessControlRule[] = []; 9 | let workspaceId; 10 | 11 | const { profile } = await parent(); 12 | if (profile?.hasAdminAccess?.()) { 13 | throw redirect(302, '/admin/mcp-registries'); 14 | } 15 | 16 | try { 17 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile?.id, { fetch }); 18 | accessControlRules = await ChatService.listWorkspaceAccessControlRules(workspaceId, { fetch }); 19 | } catch (err) { 20 | handleRouteError(err, '/mcp-registries', profile); 21 | } 22 | 23 | return { 24 | accessControlRules, 25 | workspaceId 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /apiclient/files.go: -------------------------------------------------------------------------------- 1 | package apiclient 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/obot-platform/obot/apiclient/types" 9 | ) 10 | 11 | type ListFileOptions struct { 12 | AgentID string 13 | WorkflowID string 14 | ThreadID string 15 | } 16 | 17 | func (c *Client) ListFiles(ctx context.Context, opts ListFileOptions) (result types.FileList, err error) { 18 | path := "/files" 19 | if opts.AgentID != "" { 20 | path = "/agents/" + opts.AgentID + path 21 | } else if opts.WorkflowID != "" { 22 | path = "/workflows/" + opts.WorkflowID + path 23 | } else if opts.ThreadID != "" { 24 | path = "/threads/" + opts.ThreadID + path 25 | } else { 26 | return result, fmt.Errorf("missing agentID, workflowID, or threadID") 27 | } 28 | 29 | _, resp, err := c.doRequest(ctx, http.MethodGet, path, nil) 30 | if err != nil { 31 | return 32 | } 33 | defer resp.Body.Close() 34 | 35 | _, err = toObject(resp, &result) 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /pkg/api/authz/workflow.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/obot-platform/nah/pkg/router" 7 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 8 | "github.com/obot-platform/obot/pkg/system" 9 | "k8s.io/apiserver/pkg/authentication/user" 10 | ) 11 | 12 | func (a *Authorizer) checkWorkflow(req *http.Request, resources *Resources, _ user.Info) (bool, error) { 13 | if resources.WorkflowID == "" { 14 | return true, nil 15 | } 16 | 17 | if resources.Authorizated.Thread == nil { 18 | return false, nil 19 | } 20 | 21 | var ( 22 | workflow v1.Workflow 23 | ) 24 | 25 | if err := a.get(req.Context(), router.Key(system.DefaultNamespace, resources.WorkflowID), &workflow); err != nil { 26 | return false, err 27 | } 28 | 29 | if resources.Authorizated.Thread.Name != workflow.Spec.ThreadName { 30 | return false, nil 31 | } 32 | 33 | resources.Authorizated.Workflow = &workflow 34 | return true, nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/cli/tools_register.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/obot-platform/obot/apiclient/types" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | type ToolRegister struct { 11 | root *Obot 12 | Quiet bool `usage:"Only print IDs of created tool references:" short:"q"` 13 | } 14 | 15 | func (l *ToolRegister) Customize(cmd *cobra.Command) { 16 | cmd.Use = "register [flags] NAME REFERENCE" 17 | cmd.Args = cobra.ExactArgs(2) 18 | cmd.Aliases = []string{"add", "create", "new"} 19 | } 20 | 21 | func (l *ToolRegister) Run(cmd *cobra.Command, args []string) error { 22 | tr, err := l.root.Client.CreateToolReference(cmd.Context(), types.ToolReferenceManifest{ 23 | Name: args[0], 24 | ToolType: types.ToolReferenceTypeTool, 25 | Reference: args[1], 26 | }) 27 | if err != nil { 28 | return err 29 | } 30 | if l.Quiet { 31 | fmt.Println(tr.ID) 32 | } else { 33 | fmt.Println("Tool reference created:", tr.ID) 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/primitives/draggable/contextRoot.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext } from 'svelte'; 2 | 3 | const CONTEXT_KEY = '@obot/context/dragable-root'; 4 | 5 | export type DraggableItem = { 6 | id: string; 7 | data: T; 8 | }; 9 | 10 | export type DraggableContext = { 11 | readonly state: { 12 | items: DraggableItem[]; 13 | sourceItemId?: string; 14 | targetItemId?: string; 15 | disabled?: boolean; 16 | }; 17 | methods: { 18 | reorder: () => void; 19 | mount: (id: string, item: { id: string; data: T }) => () => void; 20 | unmount: (id: string) => void; 21 | setSourceItem: (id?: string) => void; 22 | setTargetItem: (id?: string) => void; 23 | }; 24 | }; 25 | export function getDraggableContext(): DraggableContext { 26 | return getContext(CONTEXT_KEY); 27 | } 28 | 29 | export function setDraggableContext(context: DraggableContext) { 30 | return setContext(CONTEXT_KEY, context); 31 | } 32 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/+page.ts: -------------------------------------------------------------------------------- 1 | import { ChatService, getProfile, type AuthProvider } from '$lib/services'; 2 | import { Group } from '$lib/services/admin/types'; 3 | import type { PageLoad } from './$types'; 4 | import { redirect } from '@sveltejs/kit'; 5 | 6 | export const load: PageLoad = async ({ fetch, url }) => { 7 | let authProviders: AuthProvider[] = []; 8 | let profile; 9 | 10 | try { 11 | profile = await getProfile({ fetch }); 12 | } catch (_err) { 13 | authProviders = await ChatService.listAuthProviders({ fetch }); 14 | } 15 | 16 | const showSetupHandoff = url.searchParams.get('setup') === 'complete'; 17 | const hasAccess = 18 | profile?.groups.includes(Group.ADMIN) || profile?.groups.includes(Group.AUDITOR); 19 | if (hasAccess && !showSetupHandoff) { 20 | throw redirect(307, '/admin/mcp-servers'); 21 | } 22 | 23 | return { 24 | loggedIn: profile?.loaded ?? false, 25 | hasAccess, 26 | authProviders, 27 | showSetupHandoff 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /ui/user/static/user/images/github-mark/github-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/user/svelte.config.js: -------------------------------------------------------------------------------- 1 | import nodeAdapter from '@sveltejs/adapter-node'; 2 | import adapter from '@sveltejs/adapter-static'; 3 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 4 | 5 | /** @type {import('@sveltejs/kit').Config}*/ 6 | const config = { 7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 8 | // for more information about preprocessors 9 | preprocess: [vitePreprocess()], 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter({ 15 | fallback: 'fallback.html' 16 | }) 17 | } 18 | }; 19 | 20 | if (process.env.BUILD === 'node') { 21 | config.kit.adapter = nodeAdapter({ 22 | out: 'build-node' 23 | }); 24 | } 25 | 26 | export default config; 27 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/MessageIcon.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#if !msg.icon} 16 | 17 | {:else if msg.icon === 'Pencil' || msg.icon === 'Assistant'} 18 | 19 | {:else} 20 |
21 | {#if msg.icon === 'Profile'} 22 | 23 | {:else if msg.icon === 'Error'} 24 | 25 | {:else} 26 | message icon 31 | {/if} 32 |
33 | {/if} 34 | -------------------------------------------------------------------------------- /ui/user/static/user/images/github-mark/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/user/src/lib/actions/overflow.ts: -------------------------------------------------------------------------------- 1 | import popover from './popover.svelte.js'; 2 | 3 | function hasOverflow(element: HTMLElement) { 4 | return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; 5 | } 6 | 7 | export function overflowToolTip(node: HTMLElement) { 8 | const { ref, tooltip } = popover({ 9 | placement: 'top-end', 10 | offset: 10 11 | }); 12 | 13 | node.classList.add('truncate'); 14 | 15 | const p = document.createElement('p') as HTMLParagraphElement; 16 | p.classList.add('tooltip'); 17 | p.textContent = node.textContent; 18 | 19 | node.insertAdjacentElement('afterend', p); 20 | node.addEventListener('mouseenter', (e) => { 21 | if (!hasOverflow(node)) { 22 | e.stopImmediatePropagation(); 23 | } 24 | // Update content if changed 25 | p.textContent = node.textContent; 26 | }); 27 | 28 | // Register after the above event listener to ensure we can stop propagation 29 | tooltip(p, { hover: true }); 30 | ref(node); 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cli/invokeclient/input.go: -------------------------------------------------------------------------------- 1 | package invokeclient 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient" 5 | "github.com/obot-platform/obot/apiclient/types" 6 | "github.com/obot-platform/obot/pkg/cli/textio" 7 | ) 8 | 9 | type QuietInputter struct { 10 | } 11 | 12 | func (d QuietInputter) Next(previous string, resp *types.InvokeResponse) (string, bool, error) { 13 | if resp == nil { 14 | return previous, true, nil 15 | } 16 | return "", false, nil 17 | } 18 | 19 | type VerboseInputter struct { 20 | client *apiclient.Client 21 | } 22 | 23 | func nextInput() (string, bool, error) { 24 | x, err := textio.Ask("Input", "") 25 | if err != nil { 26 | return "", false, err 27 | } 28 | return x, true, nil 29 | } 30 | 31 | func (d VerboseInputter) Next(previous string, resp *types.InvokeResponse) (string, bool, error) { 32 | if resp == nil { 33 | if previous == "" { 34 | return nextInput() 35 | } 36 | return previous, true, nil 37 | } 38 | 39 | return nextInput() 40 | } 41 | -------------------------------------------------------------------------------- /pkg/storage/apis/obot.obot.ai/v1/apppreferences.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 9 | 10 | type AppPreferences struct { 11 | metav1.TypeMeta `json:",inline"` 12 | metav1.ObjectMeta `json:"metadata,omitempty"` 13 | 14 | Spec AppPreferencesSpec `json:"spec,omitempty"` 15 | Status AppPreferencesStatus `json:"status,omitempty"` 16 | } 17 | 18 | type AppPreferencesSpec struct { 19 | Logos types.LogoPreferences `json:"logos,omitempty"` 20 | Theme types.ThemePreferences `json:"theme,omitempty"` 21 | } 22 | 23 | type AppPreferencesStatus struct{} 24 | 25 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 26 | 27 | type AppPreferencesList struct { 28 | metav1.TypeMeta `json:",inline"` 29 | metav1.ListMeta `json:"metadata,omitempty"` 30 | 31 | Items []AppPreferences `json:"items"` 32 | } 33 | -------------------------------------------------------------------------------- /pkg/controller/handlers/workflow/ids.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/randomtoken" 5 | "github.com/obot-platform/obot/apiclient/types" 6 | ) 7 | 8 | func PopulateIDs(manifest types.WorkflowManifest) types.WorkflowManifest { 9 | manifest = *manifest.DeepCopy() 10 | ids := map[string]struct{}{} 11 | for i, step := range manifest.Steps { 12 | manifest.Steps[i] = populateStepID(ids, step) 13 | } 14 | return manifest 15 | } 16 | 17 | func nextID(seen map[string]struct{}) string { 18 | for { 19 | next, err := randomtoken.Generate() 20 | if err != nil { 21 | panic(err) 22 | } 23 | id := "si1" + next[:5] 24 | if _, ok := seen[id]; !ok { 25 | seen[id] = struct{}{} 26 | return id 27 | } 28 | } 29 | } 30 | 31 | func populateStepID(seen map[string]struct{}, step types.Step) types.Step { 32 | if step.ID == "" { 33 | step.ID = nextID(seen) 34 | } else if _, ok := seen[step.ID]; ok { 35 | step.ID = nextID(seen) 36 | } 37 | return step 38 | } 39 | -------------------------------------------------------------------------------- /pkg/mcp/resources.go: -------------------------------------------------------------------------------- 1 | package mcp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/nanobot-ai/nanobot/pkg/mcp" 8 | ) 9 | 10 | func (sm *SessionManager) ListResources(ctx context.Context, serverConfig ServerConfig) ([]mcp.Resource, error) { 11 | client, err := sm.clientForMCPServer(ctx, serverConfig) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | resp, err := client.ListResources(ctx) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to list MCP resources: %w", err) 19 | } 20 | 21 | return resp.Resources, nil 22 | } 23 | 24 | func (sm *SessionManager) ReadResource(ctx context.Context, serverConfig ServerConfig, uri string) ([]mcp.ResourceContent, error) { 25 | client, err := sm.clientForMCPServer(ctx, serverConfig) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | resp, err := client.ReadResource(ctx, uri) 31 | if err != nil { 32 | return nil, fmt.Errorf("failed to get MCP resource: %w", err) 33 | } 34 | 35 | return resp.Contents, nil 36 | } 37 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/c/[id]/instance/[ms_id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MCP_CATALOG_ID } from '$lib/constants'; 2 | import { handleRouteError } from '$lib/errors'; 3 | import { AdminService, ChatService } from '$lib/services'; 4 | import { profile } from '$lib/stores'; 5 | import type { PageLoad } from './$types'; 6 | 7 | export const load: PageLoad = async ({ params, fetch }) => { 8 | const catalogEntryId = params.id; 9 | const mcpServerId = params.ms_id; 10 | 11 | let catalogEntry; 12 | let mcpServer; 13 | try { 14 | catalogEntry = await AdminService.getMCPCatalogEntry(DEFAULT_MCP_CATALOG_ID, catalogEntryId, { 15 | fetch 16 | }); 17 | mcpServer = await ChatService.getSingleOrRemoteMcpServer(mcpServerId, { fetch }); 18 | } catch (err) { 19 | handleRouteError( 20 | err, 21 | `/admin/mcp-servers/c/${catalogEntryId}/instance/${mcpServerId}`, 22 | profile.current 23 | ); 24 | } 25 | 26 | return { 27 | catalogEntry, 28 | mcpServerId, 29 | mcpServer 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/ReLoginDialog.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | { 21 | // Prevent closing by clicking outside 22 | dialog.showModal(); 23 | }} 24 | > 25 |
26 |

Session Expired

27 |

28 | Your session has expired. Please log in again to continue. 29 |

30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/mcp-servers/w/[wid]/c/[id]/instance/[ms_id]/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const workspaceId = params.wid; 8 | const catalogEntryId = params.id; 9 | const mcpServerId = params.ms_id; 10 | let mcpServer; 11 | 12 | let catalogEntry; 13 | try { 14 | catalogEntry = await ChatService.getWorkspaceMCPCatalogEntry(workspaceId, catalogEntryId, { 15 | fetch 16 | }); 17 | mcpServer = await ChatService.getSingleOrRemoteMcpServer(mcpServerId, { fetch }); 18 | } catch (err) { 19 | handleRouteError( 20 | err, 21 | `/admin/mcp-servers/w/${workspaceId}/c/${catalogEntryId}/instance/${mcpServerId}`, 22 | profile.current 23 | ); 24 | } 25 | 26 | return { 27 | workspaceId, 28 | catalogEntry, 29 | mcpServerId, 30 | mcpServer 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /pkg/cli/threadprint.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient" 5 | "github.com/obot-platform/obot/pkg/cli/events" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type ThreadPrint struct { 10 | root *Obot 11 | Quiet bool `usage:"Only print response content of threads" short:"q"` 12 | Follow bool `usage:"Follow the thread and print new events" short:"f"` 13 | Verbose bool `usage:"Print more information" short:"v"` 14 | } 15 | 16 | func (l *ThreadPrint) Customize(cmd *cobra.Command) { 17 | cmd.Use = "print [flags] [THREAD_ID]" 18 | cmd.Args = cobra.ExactArgs(1) 19 | } 20 | 21 | func (l *ThreadPrint) Run(cmd *cobra.Command, args []string) error { 22 | var ( 23 | printer = events.NewPrinter(cmd.Context(), l.root.Client, l.Quiet, l.Verbose) 24 | ) 25 | 26 | events, err := l.root.Client.ThreadEvents(cmd.Context(), args[0], apiclient.ThreadEventsOptions{ 27 | Follow: l.Follow, 28 | }) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return printer.Print(events) 34 | } 35 | -------------------------------------------------------------------------------- /ui/user/src/routes/mcp-servers/c/[id]/instance/[ms_id]/details/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | 6 | export const load: PageLoad = async ({ params, fetch }) => { 7 | const catalogEntryId = params.id; 8 | const mcpServerId = params.ms_id; 9 | let workspaceId; 10 | let catalogEntry; 11 | try { 12 | workspaceId = await ChatService.fetchWorkspaceIDForProfile(profile.current?.id, { fetch }); 13 | } catch (_err) { 14 | // can happen if basic user atm 15 | workspaceId = undefined; 16 | } 17 | 18 | try { 19 | catalogEntry = await ChatService.getMCP(catalogEntryId, { 20 | fetch 21 | }); 22 | } catch (err) { 23 | handleRouteError( 24 | err, 25 | `/mcp-servers/c/${catalogEntryId}/instance/${mcpServerId}`, 26 | profile.current 27 | ); 28 | } 29 | 30 | return { 31 | workspaceId, 32 | catalogEntry, 33 | mcpServerId 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: "obot" 4 | 5 | builds: 6 | - id: build 7 | main: . 8 | goos: 9 | - linux 10 | - darwin 11 | - windows 12 | goarch: 13 | - amd64 14 | - arm64 15 | ldflags: 16 | - -X "github.com/obot-platform/obot/pkg/version.Tag=v{{ .Version }}" 17 | 18 | archives: 19 | - format: tar.gz 20 | name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" 21 | format_overrides: 22 | - goos: windows 23 | format: zip 24 | 25 | release: 26 | github: 27 | owner: "obot-platform" 28 | name: "obot" 29 | make_latest: false 30 | prerelease: auto 31 | 32 | brews: 33 | - name: obot 34 | description: "Obot CLI" 35 | install: | 36 | bin.install "obot" 37 | homepage: "https://github.com/obot-platform/obot" 38 | skip_upload: false 39 | directory: "Formula" 40 | repository: 41 | owner: obot-platform 42 | name: homebrew-tap 43 | token: "{{ .Env.TAP_GITHUB_TOKEN }}" 44 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/edit/SystemPrompt.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore binary files 2 | bin/ 3 | 4 | # Random TLS files 5 | apiserver.local.config/ 6 | # Ignore the kubeconfig file 7 | kubeconfig 8 | 9 | # Ignore envrc 10 | .envrc 11 | 12 | # Ignore any compiled Go binaries 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Ignore Go build artifacts 20 | *.log 21 | *.test 22 | *.out 23 | 24 | # Ignore packed helm chart 25 | .packaged/ 26 | 27 | # Ignore dependency directories 28 | vendor/ 29 | 30 | # Ignore temporary files 31 | *.tmp 32 | *.swp 33 | *.swo 34 | *.bak 35 | *.old 36 | 37 | # Ignore IDE specific files 38 | .vscode/ 39 | .idea/ 40 | *.iml 41 | 42 | # Ignore OS specific files 43 | .DS_Store 44 | Thumbs.db 45 | 46 | # Ignore local DB files 47 | obot*.db* 48 | 49 | # Ignore http dir for http client files 50 | http/ 51 | 52 | # Ignore the catalog.json file downloaded from the MCP registry 53 | catalog.json 54 | 55 | # Ignore Kiro files 56 | .kiro/ 57 | 58 | # Ignore zed files 59 | .zed/ 60 | __debug_* 61 | 62 | .claude/ 63 | thoughts/ 64 | thoughts 65 | -------------------------------------------------------------------------------- /pkg/mcp/prompts.go: -------------------------------------------------------------------------------- 1 | package mcp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/nanobot-ai/nanobot/pkg/mcp" 8 | ) 9 | 10 | func (sm *SessionManager) ListPrompts(ctx context.Context, serverConfig ServerConfig) ([]mcp.Prompt, error) { 11 | client, err := sm.clientForMCPServer(ctx, serverConfig) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | resp, err := client.ListPrompts(ctx) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to list MCP prompts: %w", err) 19 | } 20 | 21 | return resp.Prompts, nil 22 | } 23 | 24 | func (sm *SessionManager) GetPrompt(ctx context.Context, serverConfig ServerConfig, name string, args map[string]string) ([]mcp.PromptMessage, string, error) { 25 | client, err := sm.clientForMCPServer(ctx, serverConfig) 26 | if err != nil { 27 | return nil, "", err 28 | } 29 | 30 | resp, err := client.GetPrompt(ctx, name, args) 31 | if err != nil { 32 | return nil, "", fmt.Errorf("failed to get MCP prompt: %w", err) 33 | } 34 | 35 | return resp.Messages, resp.Description, nil 36 | } 37 | -------------------------------------------------------------------------------- /ui/user/src/lib/components/messages/preset/composed/commands.ts: -------------------------------------------------------------------------------- 1 | import type { MilkdownPlugin } from '@milkdown/ctx'; 2 | import { 3 | addBlockTypeCommand, 4 | clearTextInCurrentBlockCommand, 5 | insertHardbreakCommand, 6 | isMarkSelectedCommand, 7 | isNodeSelectedCommand, 8 | selectTextNearPosCommand, 9 | setBlockTypeCommand, 10 | wrapInBlockTypeCommand 11 | } from '@milkdown/kit/preset/commonmark'; 12 | import { toggleInlineCodeCommand } from '@milkdown/kit/preset/commonmark'; 13 | import { 14 | createCodeBlockCommand, 15 | insertHrCommand, 16 | turnIntoTextCommand 17 | } from '@milkdown/kit/preset/commonmark'; 18 | 19 | /// @internal 20 | export const commands: MilkdownPlugin[] = [ 21 | turnIntoTextCommand, 22 | createCodeBlockCommand, 23 | insertHardbreakCommand, 24 | insertHrCommand, 25 | toggleInlineCodeCommand, 26 | 27 | isMarkSelectedCommand, 28 | isNodeSelectedCommand, 29 | 30 | clearTextInCurrentBlockCommand, 31 | setBlockTypeCommand, 32 | wrapInBlockTypeCommand, 33 | addBlockTypeCommand, 34 | selectTextNearPosCommand 35 | ]; 36 | -------------------------------------------------------------------------------- /ui/user/src/routes/chat/+page.ts: -------------------------------------------------------------------------------- 1 | import { handleRouteError } from '$lib/errors'; 2 | import { ChatService, EditorService } from '$lib/services'; 3 | import { profile } from '$lib/stores'; 4 | import type { PageLoad } from './$types'; 5 | import { redirect } from '@sveltejs/kit'; 6 | 7 | export const load: PageLoad = async ({ fetch }) => { 8 | let lastProjectId; 9 | try { 10 | const projects = (await ChatService.listProjects({ fetch })).items.sort( 11 | (a, b) => new Date(b.created).getTime() - new Date(a.created).getTime() 12 | ); 13 | 14 | if (projects.length !== 0) { 15 | lastProjectId = projects[0].id; 16 | } else { 17 | // Create new project and redirect to it 18 | const newProject = await EditorService.createObot({ fetch }); 19 | lastProjectId = newProject.id; 20 | } 21 | } catch (err) { 22 | // Handle redirecting to login, showing unauthorized error, etc. 23 | handleRouteError(err, '/chat', profile.current); 24 | return; 25 | } 26 | 27 | // Redirect to project 28 | throw redirect(302, `/o/${lastProjectId}`); 29 | }; 30 | -------------------------------------------------------------------------------- /pkg/api/handlers/providers/modelproviders.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "github.com/obot-platform/obot/apiclient/types" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | ) 7 | 8 | type ProviderMeta struct { 9 | types.CommonProviderMetadata 10 | EnvVars []types.ProviderConfigurationParameter `json:"envVars"` 11 | OptionalEnvVars []types.ProviderConfigurationParameter `json:"optionalEnvVars"` 12 | } 13 | 14 | func ConvertModelProviderToolRef(toolRef v1.ToolReference, cred map[string]string) (*types.ModelProviderStatus, error) { 15 | commonProviderStatus, err := ConvertProviderToolRef(toolRef, cred) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | var modelsPopulated *bool 21 | if commonProviderStatus.Configured { 22 | modelsPopulated = new(bool) 23 | *modelsPopulated = toolRef.Status.ObservedGeneration == toolRef.Generation 24 | } 25 | 26 | return &types.ModelProviderStatus{ 27 | CommonProviderStatus: *commonProviderStatus, 28 | ModelsBackPopulated: modelsPopulated, 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/api/authz/tool.go: -------------------------------------------------------------------------------- 1 | package authz 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/obot-platform/nah/pkg/router" 8 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 9 | "github.com/obot-platform/obot/pkg/system" 10 | "k8s.io/apiserver/pkg/authentication/user" 11 | ) 12 | 13 | func (a *Authorizer) checkTools(req *http.Request, resources *Resources, _ user.Info) (bool, error) { 14 | // Skip this auth check if the request isn't for a custom tool 15 | if resources.ToolID == "" || !strings.HasPrefix(resources.ToolID, system.ToolPrefix) { 16 | return true, nil 17 | } 18 | 19 | if resources.Authorizated.Project == nil { 20 | return false, nil 21 | } 22 | 23 | var tool v1.Tool 24 | if err := a.get(req.Context(), router.Key(resources.Authorizated.Project.Namespace, 25 | resources.ToolID), &tool); err != nil { 26 | return false, err 27 | } 28 | 29 | if tool.Spec.ThreadName != resources.Authorizated.Project.Name { 30 | return false, nil 31 | } 32 | 33 | resources.Authorizated.Tool = &tool 34 | return true, nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/controller/data/default-model-aliases.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - apiVersion: obot.obot.ai/v1 3 | kind: DefaultModelAlias 4 | metadata: 5 | name: llm 6 | namespace: default 7 | spec: 8 | manifest: 9 | alias: llm 10 | - apiVersion: obot.obot.ai/v1 11 | kind: DefaultModelAlias 12 | metadata: 13 | name: llm-mini 14 | namespace: default 15 | spec: 16 | manifest: 17 | alias: llm-mini 18 | - apiVersion: obot.obot.ai/v1 19 | kind: DefaultModelAlias 20 | metadata: 21 | name: text-embedding 22 | namespace: default 23 | spec: 24 | manifest: 25 | alias: text-embedding 26 | - apiVersion: obot.obot.ai/v1 27 | kind: DefaultModelAlias 28 | metadata: 29 | name: image-generation 30 | namespace: default 31 | spec: 32 | manifest: 33 | alias: image-generation 34 | - apiVersion: obot.obot.ai/v1 35 | kind: DefaultModelAlias 36 | metadata: 37 | name: vision 38 | namespace: default 39 | spec: 40 | manifest: 41 | alias: vision -------------------------------------------------------------------------------- /pkg/controller/handlers/projectmcpserver/projectmcpserver.go: -------------------------------------------------------------------------------- 1 | package projectmcpserver 2 | 3 | import ( 4 | "github.com/obot-platform/nah/pkg/router" 5 | v1 "github.com/obot-platform/obot/pkg/storage/apis/obot.obot.ai/v1" 6 | "github.com/obot-platform/obot/pkg/system" 7 | ) 8 | 9 | type Handler struct{} 10 | 11 | func NewHandler() *Handler { 12 | return &Handler{} 13 | } 14 | 15 | func (h *Handler) EnsureMCPServerName(req router.Request, _ router.Response) error { 16 | server := req.Object.(*v1.ProjectMCPServer) 17 | if server.Spec.MCPServerName != "" { 18 | return nil 19 | } 20 | 21 | if !system.IsMCPServerInstanceID(server.Spec.Manifest.MCPID) { 22 | server.Spec.MCPServerName = server.Spec.Manifest.MCPID 23 | return req.Client.Update(req.Ctx, server) 24 | } 25 | 26 | var mcpServerInstance v1.MCPServerInstance 27 | if err := req.Get(&mcpServerInstance, server.Namespace, server.Spec.Manifest.MCPID); err != nil { 28 | return err 29 | } 30 | 31 | server.Spec.MCPServerName = mcpServerInstance.Spec.MCPServerName 32 | return req.Client.Update(req.Ctx, server) 33 | } 34 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/audit-logs/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | Obot | Audit Logs 14 | 15 | 16 | 24 |
25 |
26 | 27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /ui/user/src/routes/admin/filters/[id]/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 |
19 | { 22 | goto('/admin/filters'); 23 | }} 24 | readonly={profile.current.isAdminReadonly?.()} 25 | /> 26 |
27 |
28 | 29 | 30 | Obot | {title} 31 | 32 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | check_postgres_active() { 5 | for i in {1..30}; do 6 | if pg_isready -q; then 7 | echo "PostgreSQL is active and ready!" 8 | return 0 9 | fi 10 | echo "Waiting for PostgreSQL to become active... ($i/10)" 11 | sleep 2 12 | done 13 | echo "PostgreSQL did not become active in time." 14 | exit 1 15 | } 16 | 17 | source /obot-tools/.envrc.tools 18 | export PATH=$TOOLS_VENV_BIN:$PATH 19 | 20 | # double echo to remove trailing whitespace 21 | export OBOT_SERVER_VERSIONS="$(cat <