Skip to main content

imio.omnia.core

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_type — oauth2 (default) or none.

  • openai_auth_type — oauth2 (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).

  • Cross-origin requests are rejected: when an Origin header is present, its host must match the portal’s.

  • Requests must carry plone.protect’s CSRF token in an X-CSRF-TOKEN header (or an _authenticator parameter); otherwise the proxy answers 403.

No Authorization header is needed: a same-origin fetch() sends the visitor’s session cookie, and Zope checks the permission.

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

from plone.protect.authenticator import createToken

token = createToken()
# Pass this token to the browser, e.g. in a JS settings object

For anonymous visitors the token is the same for everyone and stays valid until the keyring rotates (on user login, at most daily). It proves the caller loaded a page from the site; it does not replace rate limiting.

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.

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",
      "X-CSRF-TOKEN": 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.3 (2026-10-09)

  • Breaking: replace the custom HMAC Bearer token of @@omnia-openai-api by plone.protect’s CSRF token (X-CSRF-TOKEN header) and remove imio.omnia.core.tokens. [chris-adam]

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]

Metadata

Release files for imio.omnia.core 1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for imio.omnia.core 1.3
File Size Uploaded
imio_omnia_core-1.3.tar.gz 102.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for imio.omnia.core 1.3
File Interpreter ABI Platform
imio_omnia_core-1.3-py3-none-any.whl Python 3 none any Details

Total release size: 216.0 kB

Release files / imio_omnia_core-1.3.tar.gz

Download URL imio_omnia_core-1.3.tar.gz
Size 102.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d809525e9dd1cb4b96fb9ff05405fafb71d34072913e426c20eee89d50447b34
BLAKE2b-256 checksum
How to use checksums
5975612f50f7fc7bc1ae93d56dd8bc7d09af765d200b4d106db56db082f24e64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / imio_omnia_core-1.3-py3-none-any.whl

Download URL imio_omnia_core-1.3-py3-none-any.whl
Size 113.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
69e508c245eb9c99c919921e5ff2e46a01b109ab9f4d9efc51806442c6f5c116
BLAKE2b-256 checksum
How to use checksums
b4d577ef31db3f8b2b3ee59ff600af35c2230a47d9a2c2b120c3bf94bb78e1f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release history Release notifications | RSS feed

This release

1.3 This release

2 release files

1.2

2 release files

1.1

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page