Skip to main content

Shared infrastructure for the Omnia AI-assisted features suite in Plone 6.

This package provides settings management, a shared tabbed control panel, an “AI assistant” content menu, HTTP client services wrapping the Omnia APIs, extensibility interfaces, and branding assets. Other imio.omnia.* packages (imio.omnia.tinymce, imio.omnia.classification, etc.) build on top of it.

Installation

Add the egg to your buildout:

[buildout]

...

eggs =
    imio.omnia.core

Then run bin/buildout.

The package is auto-included in Plone via z3c.autoinclude.plugin, so no ZCML slug is needed.

Sub-packages that depend on imio.omnia.core should declare a GenericSetup dependency in their profiles/default/metadata.xml:

<?xml version="1.0"?>
<metadata>
  <version>1000</version>
  <dependencies>
    <dependency>profile-imio.omnia.core:default</dependency>
  </dependencies>
</metadata>

Configuration

Registry settings

Stored under the prefix imio.omnia.IOmniaCoreSettings:

Field

Purpose

core_api_url

Omnia Core API base URL

openai_api_url

Omnia OpenAI-compatible gateway base URL

openai_extra_headers

Additional HTTP headers for the OpenAI-compatible API (dict)

application_id

Application identifier (sent as x-imio-application header)

organization_id

Default organization / municipality ID (x-imio-municipality)

enable_proxy

Enable @@omnia-api proxy endpoint (default: False)

enable_openai_proxy

Enable @@omnia-openai-api streaming proxy (default: False)

api_timeout

Timeout for outbound HTTP requests, in seconds (default: 30)

core_auth_type

Core API authentication: none / oauth2 (default)

openai_auth_type

Gateway authentication: none / api_key / oauth2 (default)

openai_api_key

Bearer token used when openai_auth_type is api_key

oauth_grant_type

password (ROPC, default) or client_credentials

oauth_client_id

Keycloak client ID

oauth_client_secret

Keycloak client secret

oauth_token_url

Token endpoint of the SSO-Apps realm

oauth_scope

Optional scope, usually empty

oauth_client_auth_method

client_secret_basic (default) or client_secret_post

oauth_username

Service-account username (password grant only)

oauth_password

Service-account password (password grant only)

These settings are editable via the Omnia control panel at @@omnia-ai-settings (Site Setup > Omnia).

Authentication

Each service picks its own scheme, so the two APIs can move independently:

  • core_auth_typeoauth2 (default) or none.

  • openai_auth_typeoauth2 (default), api_key (sends the static openai_api_key as a Bearer token), or none.

With oauth2, outbound requests carry a Bearer token obtained from the Keycloak SSO-Apps realm. The token is fetched and refreshed by a process-level shared client, so it is acquired once per instance rather than per request. Both grant types are supported: password (ROPC, using oauth_username / oauth_password) and client_credentials.

As a safeguard, setting openai_auth_type to oauth2 while openai_api_url points outside imio.be raises ValueError instead of sending an iMio SSO-Apps token to a third-party provider.

Environment variables

Settings can also be driven by environment variables. They are synced to the Plone registry on Zope startup (requires SITE_ID to locate the Plone site):

Variable

Registry field

SITE_ID

Plone site ID in the ZODB (not stored in registry)

OMNIA_CORE_API_URL

core_api_url

OMNIA_OPENAI_API_URL

openai_api_url

OMNIA_OPENAI_API_KEY

openai_api_key

OMNIA_APPLICATION_ID

application_id

OMNIA_ORGANIZATION_ID

organization_id

SSO_APPS_CLIENT_ID

oauth_client_id

SSO_APPS_CLIENT_SECRET

oauth_client_secret

SSO_APPS_URL

oauth_token_url

SSO_APPS_USER_USERNAME

oauth_username

SSO_APPS_USER_PASSWORD

oauth_password

Set them in buildout.cfg under [instance] environment-vars or export them in your shell before starting Plone.

The SSO_APPS_* names are shared across iMio applications, so one set of variables configures every Omnia-enabled instance of a deployment.

Only free-text settings are configurable this way. The fields backed by a vocabulary — core_auth_type, openai_auth_type, oauth_grant_type, oauth_client_auth_method — keep their defaults (oauth2, password, client_secret_basic) and are changed in the control panel, so a typo in a deployment environment can never persist a value the form would reject.

Extending imio.omnia.core

Adding a control panel tab

Each Omnia sub-package can contribute a tab to the shared control panel. The tabbed layout is rendered by OmniaCoreControlPanelFormWrapper, which reads tabs from portal_actions in the omnia_controlpanel_tabs category.

Step 1 — Define a settings schema and form (browser/controlpanel.py):

from imio.omnia.core.browser.controlpanel import OmniaCoreControlPanelFormWrapper
from plone.app.registry.browser.controlpanel import RegistryEditForm
from plone.z3cform import layout
from zope import schema
from zope.interface import Interface

from my.package import _


class IMySettings(Interface):
    my_option = schema.TextLine(
        title=_("My option"),
        required=False,
    )


class MyControlPanelForm(RegistryEditForm):
    label = _("My add-on settings")
    schema = IMySettings


MyControlPanelView = layout.wrap_form(
    MyControlPanelForm, OmniaCoreControlPanelFormWrapper
)

Step 2 — Register the view (browser/configure.zcml):

<browser:page
  name="my-addon-settings"
  for="Products.CMFPlone.interfaces.IPloneSiteRoot"
  class=".controlpanel.MyControlPanelView"
  permission="cmf.ManagePortal"
  layer="my.package.interfaces.IMyBrowserLayer"
/>

Step 3 — Register the tab (profiles/default/actions.xml):

<?xml version="1.0"?>
<object name="portal_actions" meta_type="Plone Actions Tool"
        xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <object name="omnia_controlpanel_tabs" meta_type="CMF Action Category">
    <object name="my.package" meta_type="CMF Action">
      <property name="title" i18n:translate="">My add-on</property>
      <property name="url_expr">string:${portal_url}/@@my-addon-settings</property>
      <property name="icon_expr">string:gear</property>
    </object>
  </object>
</object>

Step 4 — Register the settings in the Plone registry (profiles/default/registry/main.xml):

<?xml version="1.0"?>
<registry>
  <records interface="my.package.browser.controlpanel.IMySettings"
           prefix="my.package.IMySettings" />
</registry>

Adding menu actions (AI assistant menu)

The “AI assistant” icon in the Plone toolbar opens a submenu populated from two sources:

  1. Static portal actions in the omnia_actions category.

  2. Dynamic IOmniaActionsProvider utilities.

Approach A — Static actions via actions.xml

Register a CMF Action in the omnia_actions category (profiles/default/actions.xml):

<?xml version="1.0"?>
<object name="portal_actions" meta_type="Plone Actions Tool"
        xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <object name="omnia_actions" meta_type="CMF Action Category">
    <object name="my_action" meta_type="CMF Action" i18n:domain="my.package">
      <property name="title" i18n:translate="">My AI action</property>
      <property name="description" i18n:translate="">Run my custom AI action.</property>
      <property name="url_expr">string:$object_url/@@my-ai-action-view</property>
      <property name="icon_expr">string:cpu</property>
      <property name="available_expr">object/@@my-ai-action-view/available|nothing</property>
      <property name="visible">True</property>
    </object>
  </object>
</object>

Approach B — Dynamic actions via IOmniaActionsProvider

Implement a utility that returns actions based on runtime conditions:

from imio.omnia.core.interfaces import IOmniaActionsProvider
from plone.protect.utils import addTokenToUrl
from zope.interface import implementer


@implementer(IOmniaActionsProvider)
class MyActionsProvider:

    def __call__(self, context, request):
        # Return an empty list to hide the actions, or a list of dicts.
        return [
            {
                "title": "My dynamic action",
                "description": "Does something smart.",
                "action": addTokenToUrl(
                    f"{context.absolute_url()}/@@my-action-view", request
                ),
                "selected": False,
                "icon": "cpu",
                "extra": {
                    "id": "plone-contentmenu-actions-my-action",
                    "separator": None,
                    "class": "actionicon-object_buttons-my-action",
                    "modal": "",  # empty string to open in a modal
                },
                "submenu": None,
            },
        ]

Register the utility in configure.zcml:

<utility
  provides="imio.omnia.core.interfaces.IOmniaActionsProvider"
  factory=".action.MyActionsProvider"
  name="my-actions"
/>

Using the API services

Two HTTP client services wrap the upstream Omnia APIs. Both are multi-adapters on (context, request) and automatically send x-imio-application and x-imio-municipality headers resolved from the registry and the IOrganizationIDProvider adapter.

IOmniaCoreAPIService

Wraps the Omnia Core AI agents API (/imio/omnia/core/v1/agents/).

Available methods:

  • expand_text(input, expansion_target=50)

  • improve_text(input)

  • reduce_text(input, reduction_target=30)

  • correct_text(input)

  • make_accessible(input)

  • translate_text(input, target_language)

  • suggest_titles(input)

  • convert_meeting_notes_to_minutes(meeting_name, meeting_notes)

  • categorize_content(input, vocabulary, unique=False)

  • deduce_metadata(input=None, image_url=None, image_file=None)

  • send(method, path, **kwargs) / post_json(path, payload) for raw calls

Usage:

from zope.component import getMultiAdapter
from imio.omnia.core.interfaces import IOmniaCoreAPIService

service = getMultiAdapter((context, request), IOmniaCoreAPIService)
result = service.improve_text("Le projet va bien.")
# result is a parsed JSON dict from the upstream API

IOmniaOpenAIService

Wraps the Omnia OpenAI-compatible gateway (/imio/omnia/openai/v1/).

Available methods:

  • list_models()

  • chat_completions(model, messages, stream=False, temperature=None, max_tokens=None, tools=None, tool_choice=None)

Usage:

from zope.component import getMultiAdapter
from imio.omnia.core.interfaces import IOmniaOpenAIService

service = getMultiAdapter((context, request), IOmniaOpenAIService)
result = service.chat_completions(
    model="Mistral Large",
    messages=[{"role": "user", "content": "Hello"}],
)

Streaming:

for chunk in service.chat_completions(
    model="Mistral Large",
    messages=[{"role": "user", "content": "Hello"}],
    stream=True,
):
    # Each chunk is a parsed JSON dict (SSE data frame)
    print(chunk)

Overriding organization ID resolution

The IOrganizationIDProvider adapter resolves which organization / municipality ID is sent with every API request. The default implementation reads from the registry (the organization_id setting).

To override the resolution for specific content types, register a more specific adapter:

from zope.component import adapter
from zope.interface import implementer
from imio.omnia.core.interfaces import IOrganizationIDProvider
from my.package.interfaces import IMyContentType


@adapter(IMyContentType)
@implementer(IOrganizationIDProvider)
class MyOrganizationIDProvider:

    def __init__(self, context):
        self.context = context

    def __call__(self):
        # Resolve the org ID from the content hierarchy
        return self.context.municipality_code

Register in configure.zcml:

<adapter factory=".adapters.MyOrganizationIDProvider" />

Zope’s adapter specificity ensures your adapter is used for IMyContentType objects while the default adapter handles everything else.

Using the proxy view (@@omnia-api)

The @@omnia-api view forwards browser JavaScript requests to the Omnia Core API, adding authentication headers server-side. This avoids exposing API credentials to the browser.

Requirements:

  • The enable_proxy setting must be True (disabled by default).

  • The caller must have the imio.omnia.core: Access Omnia API proxy permission (granted to Authenticated by default).

  • Requests must be POST with a JSON body.

URL pattern: <context_url>/@@omnia-api/<path> where <path> maps to the upstream API path (e.g. /v1/agents/improve-text).

JavaScript example:

const response = await fetch(
  `${portalUrl}/@@omnia-api/v1/agents/improve-text`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ input: selectedText }),
  }
);
const data = await response.json();

Using the streaming proxy (@@omnia-openai-api)

The @@omnia-openai-api view proxies browser requests to the OpenAI-compatible gateway and streams the response back as Server-Sent Events (SSE). This lets browser-side chat widgets stream completions without exposing API credentials or the upstream URL to the client.

Requirements:

  • The enable_openai_proxy setting must be True (disabled by default).

  • The caller must have the imio.omnia.core: Access Omnia OpenAI proxy permission (granted to Authenticated by default).

  • Requests must carry an HMAC Bearer token generated by imio.omnia.core.tokens.generate_token().

Projects that need anonymous access to @@omnia-openai-api can override the default role mapping in their own GenericSetup rolemap.xml by granting the imio.omnia.core: Access Omnia OpenAI proxy permission to Anonymous.

Token generation (server-side, e.g. in a viewlet):

from imio.omnia.core.tokens import generate_token

token = generate_token(context.portal_url())
# Pass this token to the browser as a JS variable

Tokens are HMAC-SHA256 signed using the Plone site keyring and expire after 2 hours. The proxy validates them on every request.

URL pattern: <context_url>/@@omnia-openai-api/<path> where <path> maps to the upstream API path (e.g. chat/completions).

JavaScript example:

const response = await fetch(
  `${portalUrl}/@@omnia-openai-api/chat/completions`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
    },
    body: JSON.stringify({
      model: "Mistral Large",
      messages: [{ role: "user", content: "Hello" }],
      stream: true,
    }),
  }
);

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("\n")) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6);
    if (data === "[DONE]") break;
    const chunk = JSON.parse(data);
    process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
  }
}

Available icons

The following icon names are registered in the Plone icon registry and can be used in icon_expr properties of portal actions:

Icon name

Description

omnia.ia.dark

Omnia IA (dark)

omnia.ia.light

Omnia IA (light)

omnia.monochrome.dark

Monochrome (dark)

omnia.monochrome.light

Monochrome (light)

omnia.picto.dark

Pictogram (dark)

omnia.picto.light

Pictogram (light)

omnia.logotype.dark

Logotype (dark)

omnia.logotype.light

Logotype (light)

omnia.imio.logotype.dark

iMio logotype (dark)

omnia.imio.logotype.light

iMio logotype (light)

The SVG files are served from ++plone++imio.omnia.core/.

Authors

License

The project is licensed under the GPLv2.

Contributors

Changelog

1.2 (2026-08-11)

  • Set Plone as the default SITE_ID when not defined. [boulch]

1.1 (2026-08-03)

  • Fix an instance startup crash loop: when every mapped environment variable already matched the registry, sync_env_to_registry left the connection joined to a transaction (silencing the fingerpointing audit log writes to the registry unconditionally) and ConnectionStateError aborted Zope startup. [duchenean]

1.0 (2026-08-03)

  • DELIBE-289: Add configurable API timeout. [duchenean]

  • OIA-241: Authenticate outbound Omnia API calls with Keycloak SSO-Apps OAuth 2.0 (ROPC / client credentials via authlib), configurable in the control panel, with credentials supplied by the cross-application SSO_APPS_* environment variables. OAuth 2.0 is the default scheme for both the Omnia Core API and the OpenAI gateway. [duchenean]

  • DELIBE-322: Replace httpx with httpx2 (maintained pydantic fork). [duchenean]

  • Removed the unused Vite/React scaffold from browser/resources. [duchenean]

1.0a2 (2026-04-03)

  • Updated package description to better reflect its role as shared infrastructure. [duchenean]

1.0a1 (2026-04-03)

  • Initial release. [duchenean]

  • Added OmniaCoreAPIService multi-adapter wrapping the Omnia Core API (/imio/omnia/core/v1/agents/): expand, improve, reduce, correct, translate, make accessible, suggest titles, convert meeting notes, categorize content, and extract metadata. [duchenean]

  • Added OmniaOpenAIService multi-adapter wrapping the OpenAI-compatible Omnia gateway (/imio/omnia/openai/v1/): model listing and chat completions with streaming SSE support. [duchenean]

  • Added shared Omnia control panel (@@omnia-ai-settings) with tabbed navigation extensible via omnia_controlpanel_tabs portal actions. [duchenean]

  • Added “AI assistant” content menu with dynamic action collection via IOmniaActionsProvider utilities. [duchenean]

  • Added IOrganizationIDProvider adapter interface for context-aware organization ID resolution. [duchenean]

  • Added environment variable to registry sync on Zope startup (OMNIA_CORE_API_URL, OMNIA_OPENAI_API_URL, OMNIA_APPLICATION_ID, OMNIA_ORGANIZATION_ID). [duchenean]

  • Added HMAC-signed token generation for securing browser-to-proxy communication. [duchenean]

  • Added IImioOmniaControlPanelFieldProvider interface for extending the control panel schema from downstream packages. [duchenean]

  • Added Omnia SVG branding icons. [duchenean]

  • Added i18n support (en, fr). [duchenean]

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

imio_omnia_core-1.2.tar.gz (102.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

imio_omnia_core-1.2-py3-none-any.whl (115.0 kB view details)

Uploaded Python 3

File details

Details for the file imio_omnia_core-1.2.tar.gz.

File metadata

  • Download URL: imio_omnia_core-1.2.tar.gz
  • Upload date:
  • Size: 102.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.12

File hashes

Hashes for imio_omnia_core-1.2.tar.gz
Algorithm Hash digest
SHA256 67c3a7be8aedbdb512603e4d79794e1cb30908231203ab86755e5636cd688e0c
MD5 3ddf7bef57d171d25fd7eff35bd1c899
BLAKE2b-256 ed2f6c54402721ba1488f930b3552e1872685b38d85c46a0d8c84a40a2913830

See more details on using hashes here.

File details

Details for the file imio_omnia_core-1.2-py3-none-any.whl.

File metadata

  • Download URL: imio_omnia_core-1.2-py3-none-any.whl
  • Upload date:
  • Size: 115.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.12

File hashes

Hashes for imio_omnia_core-1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4eb4f9e6a26a0fed9997c8ad16e0b6c3dab0311ca763e8a804f92b16c12097c5
MD5 56816baef708f09aa6bb1eaa6c3afe88
BLAKE2b-256 a5fe43c56005ceabcddc44b5832d87b1f51ed02743cddc489d5e669e845568c8

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.2 This release

2 files

1.1

2 files

1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page