From 20264751b90ebaab3175802ecafc133f2f6ac847 Mon Sep 17 00:00:00 2001 From: Andrea Debernardi Date: Thu, 29 Oct 2026 05:16:01 +0000 Subject: [PATCH 1/2] feat(tabularis): add declarative AI assistant kind preset --- deploy/tabularis/README.md | 29 ++ deploy/tabularis/ai-assistant-kind.json | 323 ++++++++++++++++++ .../test/tabularis-ai-assistant.test.ts | 30 ++ 3 files changed, 382 insertions(+) create mode 100644 deploy/tabularis/ai-assistant-kind.json create mode 100644 packages/manifest/test/tabularis-ai-assistant.test.ts diff --git a/deploy/tabularis/README.md b/deploy/tabularis/README.md index 48d87c6c..ae255439 100644 --- a/deploy/tabularis/README.md +++ b/deploy/tabularis/README.md @@ -20,3 +20,32 @@ The registry's strict authoring endpoint rejects unknown properties; lenient ing The frontend follow-up shows compact locale-aware counts, exact accessible card labels, and distinct loading/empty/unavailable/populated states. It does not change counting APIs or perform tracked downloads. It can ship independently of theme-package support. Keep the theme rollout gated: packaged platform acceptance, real runtime/tooling version assignments, old-client/profile protection, staged kind admission, and published author journeys are still separate requirements. Neither this operator delta nor the statistics UI enables themes. + +## Declarative AI assistant providers + +`ai-assistant-kind.json` is a complete operator kind preset for Tabularis. It +adds the manifest extension contract, a Runcrate example and an optional public +catalogue page. Packages contain data only; Tabularis owns HTTP, credentials, +proxies, templates and response parsing. ChatGPT account login remains built in. + +Operator rollout: + +1. Deploy the supporting Tabularium version with separate `id` and display + `name` fields, and back up the current kind configuration. +2. Create the kind through authenticated `POST /api/admin/kinds` using the preset + body, or copy it into the kind editor. For an existing kind, review the + current configuration before applying `PUT /api/admin/kinds/ai-assistant`. + Leave driver, theme and global extension settings intact. +3. Validate complete provider manifests using `/api/manifest/validate`, including + the public Runcrate and Opper examples in the Tabularis repository. Confirm + that missing required contract fields and executable extensions are rejected. +4. Publish each provider as a separate release with a universal ZIP and a + standalone `.tabularium` manifest. Keep the existing IDs `openai`, `anthropic`, + `ollama`, `openrouter`, `requesty`, `minimax`, and `custom-openai` so upgrade + migration can retain configuration and credentials. +5. Publish all seven replacements before releasing the Tabularis change that + removes their built-in adapters. Assign the actual first supporting runtime + version; deploying this preset does not prove compatibility with old clients. + +The preset is not applied automatically by deploying this repository. Registry +configuration and provider publication remain explicit rollout operations. diff --git a/deploy/tabularis/ai-assistant-kind.json b/deploy/tabularis/ai-assistant-kind.json new file mode 100644 index 00000000..14cbae16 --- /dev/null +++ b/deploy/tabularis/ai-assistant-kind.json @@ -0,0 +1,323 @@ +{ + "key": "ai-assistant", + "label": "AI assistants", + "labelTranslations": { + "it": "Assistenti AI" + }, + "description": "Declarative AI providers for Tabularis. No plugin code is executed.", + "publicPageEnabled": true, + "publicPageCopy": { + "hero": "Choose your AI assistant", + "intro": "Install the providers you use, configure credentials in Tabularis, and keep provider definitions up to date independently." + }, + "extensionsSchema": { + "ai_assistant": { + "type": "object", + "additionalProperties": false, + "required": true, + "properties": { + "api_version": { + "type": "integer", + "const": 1 + }, + "authentication": { + "type": "string", + "enum": [ + "api-key", + "none" + ] + }, + "api_key_env": { + "type": "string", + "pattern": "^[A-Z_][A-Z0-9_]*$", + "maxLength": 100 + }, + "default_models": { + "type": "array", + "maxItems": 1000, + "items": { + "type": "string", + "maxLength": 2048 + } + }, + "optional_api_key": { + "type": "boolean" + }, + "api_key_header": { + "type": "string", + "maxLength": 100 + }, + "api_key_prefix": { + "type": "string", + "maxLength": 100 + }, + "direct": { + "type": "boolean" + }, + "generate": { + "type": "object", + "additionalProperties": false, + "required": [ + "requests", + "response" + ], + "properties": { + "requests": { + "type": "array", + "minItems": 1, + "maxItems": 4, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "maxLength": 2048 + }, + "append_path": { + "type": "string", + "maxLength": 2048 + }, + "headers": { + "type": "object", + "maxProperties": 16, + "additionalProperties": { + "type": "string", + "maxLength": 2048 + } + }, + "body": { + "type": "object" + }, + "authenticated": { + "type": "boolean" + } + } + } + }, + "response": { + "type": "object", + "additionalProperties": false, + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "maxLength": 2048 + }, + "select": { + "type": "object", + "additionalProperties": false, + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "maxLength": 256 + }, + "equals": {}, + "contains": {}, + "starts_with": { + "type": "string", + "maxLength": 256 + }, + "missing_matches": { + "type": "boolean" + }, + "prefixes": { + "type": "array", + "maxItems": 16, + "items": { + "type": "string", + "maxLength": 100 + } + } + } + }, + "value_path": { + "type": "string", + "maxLength": 2048 + } + } + } + } + }, + "models": { + "type": "object", + "additionalProperties": false, + "required": [ + "sources" + ], + "properties": { + "sources": { + "type": "array", + "maxItems": 8, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "request", + "items_path", + "id_path" + ], + "properties": { + "request": { + "type": "object", + "additionalProperties": false, + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "maxLength": 2048 + }, + "append_path": { + "type": "string", + "maxLength": 2048 + }, + "headers": { + "type": "object", + "maxProperties": 16, + "additionalProperties": { + "type": "string", + "maxLength": 2048 + } + }, + "body": { + "type": "object" + }, + "authenticated": { + "type": "boolean" + } + } + }, + "items_path": { + "type": "string", + "maxLength": 2048 + }, + "id_path": { + "type": "string", + "maxLength": 2048 + }, + "filters": { + "type": "array", + "maxItems": 16, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "maxLength": 256 + }, + "equals": {}, + "contains": {}, + "starts_with": { + "type": "string", + "maxLength": 256 + }, + "missing_matches": { + "type": "boolean" + }, + "prefixes": { + "type": "array", + "maxItems": 16, + "items": { + "type": "string", + "maxLength": 100 + } + } + } + } + } + } + } + }, + "cache_seconds": { + "type": "integer", + "minimum": 0, + "maximum": 86400 + }, + "fallback": { + "type": "boolean" + } + } + }, + "auto_select_priority": { + "type": "integer", + "minimum": 0, + "maximum": 1000 + } + }, + "allOf": [ + { + "required": [ + "api_version", + "authentication", + "generate", + "models" + ] + } + ] + }, + "settings": { + "type": "array", + "maxItems": 32, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "key", + "label", + "type" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$" + }, + "label": { + "type": "string", + "maxLength": 2048 + }, + "description": { + "type": "string", + "maxLength": 2048 + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "select" + ] + }, + "default": {}, + "required": { + "type": "boolean" + }, + "options": { + "type": "array", + "items": { + "type": "string", + "maxLength": 2048 + } + } + } + } + } + }, + "customExample": { + "json": "{\n \"id\": \"runcrate\",\n \"name\": \"Runcrate\",\n \"version\": \"1.0.0\",\n \"kind\": \"ai-assistant\",\n \"description\": \"Runcrate AI assistant for Tabularis.\",\n \"settings\": [],\n \"ai_assistant\": {\n \"api_version\": 1,\n \"authentication\": \"api-key\",\n \"api_key_env\": \"RUNCRATE_API_KEY\",\n \"default_models\": [\n \"deepseek-ai/DeepSeek-V4.1-Flash\",\n \"zai-org/GLM-5.3-Flash\",\n \"zai-org/GLM-5.3\",\n \"Qwen/Qwen3.8-Flash\",\n \"openai/gpt-oss-120b\",\n \"moonshotai/Kimi-K2.6\"\n ],\n \"generate\": {\n \"requests\": [\n {\n \"url\": \"https://api.runcrate.ai/v1\",\n \"body\": {\n \"model\": \"{{model}}\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"{{system_prompt}}\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{prompt}}\"\n }\n ],\n \"temperature\": 0.0\n },\n \"append_path\": \"/chat/completions\"\n }\n ],\n \"response\": {\n \"path\": \"/choices/0/message/content\"\n }\n },\n \"models\": {\n \"sources\": [\n {\n \"request\": {\n \"url\": \"https://api.runcrate.ai/v1\",\n \"append_path\": \"/models\",\n \"authenticated\": false\n },\n \"items_path\": \"/data\",\n \"id_path\": \"/id\",\n \"filters\": [\n {\n \"path\": \"/output_modalities\",\n \"contains\": \"text\"\n }\n ]\n }\n ],\n \"cache_seconds\": 86400\n }\n },\n \"license\": \"Apache-2.0\"\n}\n" + } +} diff --git a/packages/manifest/test/tabularis-ai-assistant.test.ts b/packages/manifest/test/tabularis-ai-assistant.test.ts new file mode 100644 index 00000000..78adaa0d --- /dev/null +++ b/packages/manifest/test/tabularis-ai-assistant.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'bun:test' +import { buildSchema, ManifestSchema, validateManifest, type ExtensionsDelta } from '../src/index' +import preset from '../../../deploy/tabularis/ai-assistant-kind.json' + +const schema = buildSchema({ + coreSchema: ManifestSchema as never, + kindOverrides: { 'ai-assistant': preset.extensionsSchema as ExtensionsDelta }, + kind: 'ai-assistant', +}) + +describe('Tabularis AI assistant kind preset', () => { + it('accepts the declarative Runcrate example with a stable id and display name', () => { + const manifest = JSON.parse(preset.customExample.json) as Record + expect(validateManifest(manifest, schema).ok).toBe(true) + }) + + it('requires the provider contract and excludes executable extension fields', () => { + const manifest = JSON.parse(preset.customExample.json) as Record + for (const key of ['executable', 'interpreter', 'ui_extensions']) { + expect(validateManifest({ ...manifest, [key]: 'forbidden' }, schema).ok).toBe(false) + } + const missing = { ...manifest } + delete missing.ai_assistant + expect(validateManifest(missing, schema).ok).toBe(false) + const contract = manifest.ai_assistant as Record + const incomplete = { ...contract } + delete incomplete.generate + expect(validateManifest({ ...manifest, ai_assistant: incomplete }, schema).ok).toBe(false) + }) +}) From 18c50345a648193935a45021075d49a2b2050623 Mon Sep 17 00:00:00 2001 From: Andrea Debernardi <3198901+debba@users.noreply.github.com> Date: Thu, 12 Nov 2026 22:39:58 +0000 Subject: [PATCH 2/2] fix(ci): use Node 22 for Vercel adapter builds --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3bd2392..c0bcf31a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,11 @@ jobs: steps: - uses: actions/checkout@v4 + # The Vercel adapter used by the docs and website supports Node 22. + - uses: actions/setup-node@v4 + with: + node-version: '22' + - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.12