Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions deploy/tabularis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,40 @@ 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 Gemini 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 Gemini's complete standalone manifest against the merged core and
kind schema using `/api/manifest/validate`, including the HTTPS icon, native
`generateContent` recipe, model-resource URL and discovery filter. Confirm
that missing contract fields and executable extensions are rejected.
4. Keep the existing providers embedded in Tabularis. OpenAI, Anthropic, Ollama,
OpenRouter, Requesty, MiniMax, OpenAI Compatible and API Route need no external
replacement packages. ChatGPT account login remains built in too.
5. Assign the actual first supporting Tabularis runtime version when the
Comment thread
debba marked this conversation as resolved.
Resolved
application release is determined. Publish a successor Gemini release with
`min_runtime_version` before enabling its production listing. Version 1.0.1
is a development prerelease without that gate, not evidence of compatibility.
6. Register `TabularisDB/tabularis-gemini-plugin` as slug `gemini`, kind
`ai-assistant`. Ingest its standalone `.tabularium` and `universal` ZIP,
checking release version, SHA-256, size and release-integrity metadata.
7. Verify kind filtering, icons, install/select, model discovery, generation,
update/remove/reinstall and offline availability of embedded providers in
both transports. Source deployment alone does not apply the kind or publish
the provider.

The preset is not applied automatically by deploying this repository. Registry
configuration and provider publication remain explicit rollout operations.
292 changes: 292 additions & 0 deletions deploy/tabularis/ai-assistant-kind.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
{
"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": "Use the providers built into Tabularis, or install additional providers such as Gemini. Credentials and settings stay in the application; external packages can be updated 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
},
"require_explicit_model": {
"type": "boolean",
"default": false,
"description": "Require a saved or explicitly selected model instead of choosing the first discovered model."
}
},
"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\": \"gemini\",\n \"name\": \"Google Gemini\",\n \"version\": \"1.0.1\",\n \"kind\": \"ai-assistant\",\n \"description\": \"Google Gemini assistant using the native generateContent API.\",\n \"ai_assistant\": {\n \"api_version\": 1,\n \"authentication\": \"api-key\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"api_key_header\": \"x-goog-api-key\",\n \"api_key_prefix\": \"\",\n \"default_models\": [\n \"models/gemini-flash-latest\"\n ],\n \"generate\": {\n \"requests\": [\n {\n \"url\": \"https://generativelanguage.googleapis.com/v1beta/{{model}}:generateContent\",\n \"body\": {\n \"systemInstruction\": {\n \"parts\": [\n {\n \"text\": \"{{system_prompt}}\"\n }\n ]\n },\n \"contents\": [\n {\n \"role\": \"user\",\n \"parts\": [\n {\n \"text\": \"{{prompt}}\"\n }\n ]\n }\n ]\n }\n }\n ],\n \"response\": {\n \"path\": \"/candidates/0/content/parts\",\n \"select\": {\n \"path\": \"/thought\",\n \"equals\": false,\n \"missing_matches\": true\n },\n \"value_path\": \"/text\"\n }\n },\n \"models\": {\n \"sources\": [\n {\n \"request\": {\n \"url\": \"https://generativelanguage.googleapis.com/v1beta/models\"\n },\n \"items_path\": \"/models\",\n \"id_path\": \"/name\",\n \"filters\": [\n {\n \"path\": \"/supportedGenerationMethods\",\n \"contains\": \"generateContent\"\n }\n ]\n }\n ],\n \"cache_seconds\": 86400\n }\n },\n \"settings\": [],\n \"license\": \"Apache-2.0\",\n \"icon\": \"https://raw.githubusercontent.com/TabularisDB/tabularis-gemini-plugin/main/icon.svg\"\n}"
}
}
32 changes: 32 additions & 0 deletions packages/manifest/test/tabularis-ai-assistant.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 native Gemini example with a stable id and display name', () => {
const manifest = JSON.parse(preset.customExample.json) as Record<string, unknown>
expect(validateManifest(manifest, schema).ok).toBe(true)
expect(manifest.id).toBe('gemini')
expect((manifest.ai_assistant as Record<string, unknown>).api_key_header).toBe('x-goog-api-key')
})

it('requires the provider contract and excludes executable extension fields', () => {
const manifest = JSON.parse(preset.customExample.json) as Record<string, unknown>
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<string, unknown>
const incomplete = { ...contract }
delete incomplete.generate
expect(validateManifest({ ...manifest, ai_assistant: incomplete }, schema).ok).toBe(false)
})
})
Loading