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
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ teammapper-frontend/LICENSE
teammapper-frontend/node_modules
teammapper-frontend/npm-debug.log
teammapper-frontend/README.md
README.md
README.md
teammapper-backend/benchmark
42 changes: 24 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,41 @@ on:
branches:
- main

permissions:
contents: read

jobs:
teammapper-backend-build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4

- name: Login to GitHub Container Registry
uses: docker/login-action@v4
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

- run: corepack enable
- run: corepack prepare pnpm@9 --activate

- name: Build and export to Docker
uses: docker/build-push-action@v7
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
tags: |
Expand All @@ -46,9 +52,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand All @@ -62,9 +68,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand All @@ -78,9 +84,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand All @@ -95,9 +101,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand All @@ -124,9 +130,9 @@ jobs:
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand All @@ -153,8 +159,8 @@ jobs:
teammapper-frontend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'

Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Playwright Tests
permissions:
contents: read
actions: write
on:
pull_request:
branches: [main]
Expand All @@ -25,8 +26,8 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '25'
- run: corepack enable
Expand All @@ -36,7 +37,7 @@ jobs:
- name: Build packages
run: pnpm --filter @teammapper/mermaid-mindmap-parser run build
- name: Cache Playwright browsers
uses: actions/cache@v4
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
Expand All @@ -59,7 +60,7 @@ jobs:
POSTGRES_QUERY_TIMEOUT: 100000
POSTGRES_STATEMENT_TIMEOUT: 100000
BINDING: localhost
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
if: ${{ !cancelled() }}
with:
name: playwright
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,26 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
platforms: linux/amd64
target: production
Expand Down
10 changes: 10 additions & 0 deletions .secretlintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules/
**/node_modules/
.*cache*/
.**cache*/
.pnpm-store/
**/pnpm-store/
.env
.env.*
!.env.example
!.env.sample
7 changes: 7 additions & 0 deletions .secretlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"rules": [
{
"id": "@secretlint/secretlint-rule-preset-recommend"
}
]
}
10 changes: 0 additions & 10 deletions docker-compose.dev.yml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-24
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Context

The AI provider layer in `aiProvider.ts` uses a binary check: if `AI_LLM_PROVIDER === 'stackit'`, it creates an OpenAI-compatible provider; otherwise it creates a standard OpenAI provider. The STACKIT-specific code is identical to what any OpenAI API-compatible service would need — a base URL, a bearer token, and a provider name. The coupling to "stackit" is purely nominal.

The `LLMProps` interface in `config.service.ts` already carries `url`, `token`, `provider`, and `model` — everything needed for a generic OpenAI-compatible provider.

## Goals / Non-Goals

**Goals:**
- Replace the `stackit`-specific provider path with a generic `openai-compatible` path
- Use the existing `AI_LLM_PROVIDER` value as the provider name passed to the SDK
- Keep the default provider as `openai` (no behavioral change for unconfigured deployments)

**Non-Goals:**
- Adding new AI capabilities or endpoints
- Supporting non-OpenAI-compatible providers (e.g. Anthropic, Google) — those would need different SDK integrations
- Changing rate limiting, prompt logic, or frontend behavior

## Decisions

### 1. Provider selector value: `openai-compatible` replaces `stackit`

**Choice**: Use the string `'openai-compatible'` as the provider discriminator.

**Why**: It accurately describes the capability (any service implementing the OpenAI API contract) rather than naming a single vendor. Operators immediately understand what it means.

**Alternatives considered**:
- `custom` — too vague, doesn't convey the OpenAI-compatible requirement
- `compatible` — ambiguous about compatible with what
- Keep `stackit` and add aliases — accumulates tech debt

### 2. Use `AI_LLM_PROVIDER` value as SDK provider name

**Choice**: Pass `llmConfig.provider` (i.e. `'openai-compatible'`) as the `name` parameter to `createOpenAICompatible()`.

**Why**: The `createOpenAICompatible()` call accepts a `name` parameter used internally by the AI SDK for logging. `AI_LLM_PROVIDER` already identifies the provider — no new env var needed.

### 3. No backward-compatibility alias for `stackit`

**Choice**: Clean break — `stackit` is no longer recognized as a valid provider value.

**Why**: The change is a simple config update (`stackit` → `openai-compatible`) with no code-level migration needed. Adding aliases creates maintenance burden for minimal benefit. The breaking change is documented in the release notes.

### 4. Require `url` for `openai-compatible` provider

**Choice**: Keep the existing guard — `openai-compatible` provider returns `undefined` if `url` or `token` is missing, same as the current STACKIT path.

**Why**: Unlike the default OpenAI provider (which has a known base URL), a generic compatible provider must have an explicit endpoint. This validation already exists and is correct.

## Risks / Trade-offs

- **Breaking change for STACKIT users** → Mitigated by clear documentation and a single env var change. The `AI_LLM_PROVIDER` value is the only thing that changes.
- **Provider name defaults to `openai-compatible`** → The SDK `name` parameter reflects the `AI_LLM_PROVIDER` value directly.
- **No runtime validation of provider values** → If an operator sets `AI_LLM_PROVIDER=typo`, they get the default OpenAI path (existing behavior). This is acceptable — invalid config is an operator error, and the existing fallback-to-default pattern is preserved.

## Migration Plan

1. Update `aiProvider.ts`: rename function, change provider constant
2. Update `.env.default` and README: document new provider value
4. Update existing tests to use `openai-compatible` instead of `stackit`
5. **Rollback**: Revert the provider constant back to `stackit` — single-line change
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Why

The AI provider layer currently hardcodes a `stackit` provider name check to route between the standard OpenAI SDK and the `@ai-sdk/openai-compatible` wrapper. This is unnecessarily specific — any OpenAI-compatible API (e.g. Azure OpenAI, Ollama, vLLM, LiteLLM, Groq) would use the exact same `createOpenAICompatible` code path. Generalizing this removes the STACKIT-specific coupling and lets operators connect any OpenAI API-compatible service without code changes.

## What Changes

- **BREAKING**: The `AI_LLM_PROVIDER` value `stackit` is replaced by `openai-compatible` as the provider selector. Existing deployments using `stackit` will need to update their configuration.
- The `createStackitProvider` function is renamed/generalized to `createOpenAICompatibleProvider`, with no STACKIT-specific logic.
- The hardcoded provider name `'stackit'` passed to `createOpenAICompatible({ name })` is replaced by the `AI_LLM_PROVIDER` value (i.e. `'openai-compatible'`).
- Documentation and environment defaults are updated to reflect the generic provider option.

## Capabilities

### New Capabilities

_None — this is a generalization of existing capability, not a new feature._

### Modified Capabilities

- `ai-mindmap-generation`: The provider configuration requirement changes — the system accepts `openai-compatible` as a provider value instead of `stackit`.

## Impact

- **Backend code**: `teammapper-backend/src/map/utils/aiProvider.ts` — primary change target
- **Environment files**: `.env.default`, README documentation
- **Breaking for operators**: Anyone with `AI_LLM_PROVIDER=stackit` must change to `AI_LLM_PROVIDER=openai-compatible`
- **No frontend impact**: The frontend is unaware of provider details
- **No API contract change**: The `/api/mermaid/create` endpoint behavior is unchanged
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## MODIFIED Requirements

### Requirement: The operator SHALL be able to configure the AI provider
The operator SHALL be able to choose between the default OpenAI provider and any OpenAI API-compatible provider by setting environment variables. The system SHALL support configuring the model, endpoint, and credentials. The `AI_LLM_PROVIDER` value SHALL be used as the SDK provider name.

#### Scenario: Default provider
- **WHEN** no provider is explicitly configured
- **THEN** the system SHALL use OpenAI as the default provider

#### Scenario: OpenAI-compatible provider
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** provides `AI_LLM_URL` and `AI_LLM_TOKEN`
- **THEN** the system SHALL create an OpenAI-compatible provider using the configured URL, token, and `AI_LLM_PROVIDER` as the provider name

#### Scenario: OpenAI-compatible provider missing URL
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** does not provide `AI_LLM_URL`
- **THEN** the system SHALL return an empty result (provider not created)

#### Scenario: OpenAI-compatible provider missing token
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** does not provide `AI_LLM_TOKEN`
- **THEN** the system SHALL return an empty result (provider not created)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 1. Generalize provider factory

- [x] 1.1 Rename `PROVIDER_STACKIT` constant to `PROVIDER_OPENAI_COMPATIBLE` with value `'openai-compatible'` in `aiProvider.ts`
- [x] 1.2 Rename `createStackitProvider` to `createOpenAICompatibleProvider` in `aiProvider.ts`
- [x] 1.3 Replace hardcoded `name: 'stackit'` with `llmConfig.provider` in the `createOpenAICompatible()` call
- [x] 1.4 Update the `createProvider` branch to check against `PROVIDER_OPENAI_COMPATIBLE`

## 2. Update documentation and defaults

- [x] 2.1 Update `.env.default` with `AI_LLM_PROVIDER` documentation referencing `openai-compatible`
- [x] 2.2 Update README AI configuration section to reference `openai-compatible` instead of `stackit`
- [x] 2.3 Add migration note about `AI_LLM_PROVIDER=stackit` → `AI_LLM_PROVIDER=openai-compatible`

## 3. Update tests

- [x] 3.1 Update existing `aiProvider` unit tests to use `'openai-compatible'` instead of `'stackit'`
- [x] 3.2 Add test case verifying the provider name passed to the SDK matches `AI_LLM_PROVIDER`
- [x] 3.3 Verify test cases for missing URL and missing token still pass
19 changes: 15 additions & 4 deletions openspec/specs/ai-mindmap-generation/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,26 @@ The system SHALL enforce operator-configurable limits on tokens per minute, requ
- **THEN** the system SHALL allow all generation requests

### Requirement: The operator SHALL be able to configure the AI provider
The operator SHALL be able to choose between supported AI providers and configure the model, endpoint, and credentials.
The operator SHALL be able to choose between the default OpenAI provider and any OpenAI API-compatible provider by setting environment variables. The system SHALL support configuring the model, endpoint, and credentials. The `AI_LLM_PROVIDER` value SHALL be used as the SDK provider name.

#### Scenario: Default provider
- **WHEN** no provider is explicitly configured
- **THEN** the system SHALL use OpenAI as the default provider

#### Scenario: Alternative provider
- **WHEN** the operator configures an alternative provider (e.g. Stackit)
- **THEN** the system SHALL use the configured provider
#### Scenario: OpenAI-compatible provider
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** provides `AI_LLM_URL` and `AI_LLM_TOKEN`
- **THEN** the system SHALL create an OpenAI-compatible provider using the configured URL, token, and `AI_LLM_PROVIDER` as the provider name

#### Scenario: OpenAI-compatible provider missing URL
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** does not provide `AI_LLM_URL`
- **THEN** the system SHALL return an empty result (provider not created)

#### Scenario: OpenAI-compatible provider missing token
- **WHEN** the operator sets `AI_LLM_PROVIDER` to `openai-compatible`
- **AND** does not provide `AI_LLM_TOKEN`
- **THEN** the system SHALL return an empty result (provider not created)

### Requirement: Generated content SHALL be in the requested language and contain only mindmap syntax
The system SHALL instruct the AI to generate mindmap syntax in the user's requested language, without explanatory text, and to return an empty mindmap for inappropriate topics.
Expand Down
Loading
Loading