diff --git a/.env.example b/.env.example
index be7872c..1aacdfa 100644
--- a/.env.example
+++ b/.env.example
@@ -12,21 +12,12 @@ BETTER_AUTH_SECRET=replace-with-a-long-random-secret
BETTER_AUTH_BASE_URL=https://fiscal.b11studio.xyz
BETTER_AUTH_TRUSTED_ORIGINS=https://fiscal.b11studio.xyz
-# OpenClaw / ZeroClaw (OpenAI-compatible)
-# Leave empty to use internal `openclaw` Compose service (`http://openclaw:4000`).
-# Set this only when targeting an external OpenClaw endpoint.
-OPENCLAW_BASE_URL=
-OPENCLAW_API_KEY=
-OPENCLAW_MODEL=zeroclaw
-OPENCLAW_AUTH_MODE=none
-OPENCLAW_PORT=4000
-
-# OpenClaw container source for Docker Compose
-OPENCLAW_IMAGE=coolify-zeroclaw:local
-# If this repo is private, include credentials in the URL:
-# OPENCLAW_BUILD_CONTEXT=https://:@gitea-hs848cs8kgs840o8c8s8cwkk.b11studio.xyz/Francy51/coolify_ZeroClaw.git
-OPENCLAW_BUILD_CONTEXT=https://gitea-hs848cs8kgs840o8c8s8cwkk.b11studio.xyz/Francy51/coolify_ZeroClaw.git
-OPENCLAW_DOCKERFILE=Dockerfile
+# AI SDK (Vercel) + Zhipu provider
+# Legacy OPENCLAW_* variables are removed and no longer read by the app.
+# Coding endpoint is hardcoded in runtime: https://api.z.ai/api/coding/paas/v4
+ZHIPU_API_KEY=
+ZHIPU_MODEL=glm-4.7-flashx
+AI_TEMPERATURE=0.2
# SEC API etiquette
SEC_USER_AGENT=Fiscal Clone
diff --git a/README.md b/README.md
index fe11bbb..58432e8 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Fiscal Clone 3.0
-Turbopack-first rebuild of a fiscal.ai-style terminal with OpenClaw integration.
+Turbopack-first rebuild of a fiscal.ai-style terminal with Vercel AI SDK integration.
## Stack
@@ -14,7 +14,7 @@ Turbopack-first rebuild of a fiscal.ai-style terminal with OpenClaw integration.
- Eden Treaty for type-safe frontend API calls
- Workflow DevKit Local World for background task execution
- SQLite-backed domain storage (watchlist, holdings, filings, tasks, insights)
-- OpenClaw/ZeroClaw analysis via OpenAI-compatible chat endpoint
+- Vercel AI SDK (`ai`) + Zhipu community provider (`zhipu-ai-provider`) for analysis tasks (hardcoded to `https://api.z.ai/api/coding/paas/v4`)
## Run locally
@@ -44,10 +44,8 @@ cp .env.example .env
docker compose up --build -d
```
-For local Docker, host port mapping comes from `docker-compose.override.yml` (default `http://localhost:3000` via `APP_PORT`, and `http://localhost:4000` for OpenClaw via `OPENCLAW_PORT`).
-OpenClaw is included as a Compose service (`openclaw`) and is built by default from `OPENCLAW_BUILD_CONTEXT` (set to `Francy51/coolify_ZeroClaw` in `.env.example`).
-If that Gitea repo is private, set `OPENCLAW_BUILD_CONTEXT` with embedded credentials (`https://:@.../coolify_ZeroClaw.git`) or point `OPENCLAW_IMAGE` to a prebuilt image you can pull.
-The app container defaults to `OPENCLAW_BASE_URL=http://openclaw:4000` unless you explicitly set a different `OPENCLAW_BASE_URL`.
+For local Docker, host port mapping comes from `docker-compose.override.yml` (default `http://localhost:3000` via `APP_PORT`).
+The app calls Zhipu directly via AI SDK and always targets the Coding API endpoint (`https://api.z.ai/api/coding/paas/v4`), so no extra AI gateway container is required.
On container startup, the app applies Drizzle migrations automatically before launching Next.js.
The app stores SQLite data in Docker volume `fiscal_sqlite_data` (mounted to `/app/data`) and workflow local data in `fiscal_workflow_data` (mounted to `/app/.workflow-data`).
@@ -88,19 +86,10 @@ BETTER_AUTH_SECRET=replace-with-a-long-random-secret
BETTER_AUTH_BASE_URL=https://fiscal.b11studio.xyz
BETTER_AUTH_TRUSTED_ORIGINS=https://fiscal.b11studio.xyz
-OPENCLAW_BASE_URL=
-OPENCLAW_API_KEY=
-OPENCLAW_MODEL=zeroclaw
-OPENCLAW_AUTH_MODE=none
-OPENCLAW_PORT=4000
-OPENCLAW_IMAGE=coolify-zeroclaw:local
-OPENCLAW_BUILD_CONTEXT=https://gitea-hs848cs8kgs840o8c8s8cwkk.b11studio.xyz/Francy51/coolify_ZeroClaw.git
-OPENCLAW_DOCKERFILE=Dockerfile
-# for OPENCLAW_AUTH_MODE=basic
-# OPENCLAW_BASIC_AUTH_USERNAME=your_nginx_user
-# OPENCLAW_BASIC_AUTH_PASSWORD=your_nginx_password
-# optional: forward API key in a custom header when using basic/none auth
-# OPENCLAW_API_KEY_HEADER=X-OpenClaw-API-Key
+ZHIPU_API_KEY=
+ZHIPU_MODEL=glm-4.7-flashx
+# optional generation tuning
+AI_TEMPERATURE=0.2
SEC_USER_AGENT=Fiscal Clone
WORKFLOW_TARGET_WORLD=local
@@ -108,19 +97,27 @@ WORKFLOW_LOCAL_DATA_DIR=.workflow-data
WORKFLOW_LOCAL_QUEUE_CONCURRENCY=100
```
-If OpenClaw is unset or invalidly configured, the app uses local fallback analysis so task workflows still run.
+If `ZHIPU_API_KEY` is unset, the app uses local fallback analysis so task workflows still run.
+`ZHIPU_BASE_URL` is deprecated and ignored; runtime always uses `https://api.z.ai/api/coding/paas/v4`.
-For OpenClaw behind Nginx Basic Auth, use:
+## Migration from OPENCLAW_* to ZHIPU_*
-```env
-OPENCLAW_BASE_URL=https://your-nginx-host
-OPENCLAW_AUTH_MODE=basic
-OPENCLAW_BASIC_AUTH_USERNAME=your_nginx_user
-OPENCLAW_BASIC_AUTH_PASSWORD=your_nginx_password
-# optional if upstream still needs an API key in a non-Authorization header
-OPENCLAW_API_KEY=your_key
-OPENCLAW_API_KEY_HEADER=X-OpenClaw-API-Key
-```
+This repository now uses direct provider calls through AI SDK. The legacy gateway container path is removed.
+The AI runtime endpoint is fixed to `https://api.z.ai/api/coding/paas/v4`.
+
+| Legacy variable | Replacement |
+| --- | --- |
+| `OPENCLAW_API_KEY` | `ZHIPU_API_KEY` |
+| `OPENCLAW_MODEL` | `ZHIPU_MODEL` |
+| `OPENCLAW_BASE_URL` | Removed (runtime endpoint is hardcoded to `https://api.z.ai/api/coding/paas/v4`) |
+| `OPENCLAW_AUTH_MODE` | Removed (not needed with direct provider calls) |
+| `OPENCLAW_BASIC_AUTH_USERNAME` | Removed |
+| `OPENCLAW_BASIC_AUTH_PASSWORD` | Removed |
+| `OPENCLAW_API_KEY_HEADER` | Removed |
+| `OPENCLAW_PORT` | Removed (no gateway service) |
+| `OPENCLAW_IMAGE` | Removed |
+| `OPENCLAW_BUILD_CONTEXT` | Removed |
+| `OPENCLAW_DOCKERFILE` | Removed |
## API surface
diff --git a/app/layout.tsx b/app/layout.tsx
index 2bd258c..6c1b5fc 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -3,7 +3,7 @@ import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Fiscal Clone',
- description: 'Futuristic fiscal intelligence terminal with durable tasks and OpenClaw integration.'
+ description: 'Futuristic fiscal intelligence terminal with durable tasks and AI SDK integration.'
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
diff --git a/app/page.tsx b/app/page.tsx
index 4613689..4dec0f6 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -184,7 +184,7 @@ export default function CommandCenterPage() {
)}
-
+
{loading ? (
Loading intelligence output...
) : state.latestInsight ? (
diff --git a/bun.lock b/bun.lock
index ea05ad8..6d9f963 100644
--- a/bun.lock
+++ b/bun.lock
@@ -8,6 +8,7 @@
"@elysiajs/eden": "^1.4.8",
"@libsql/client": "^0.17.0",
"@tailwindcss/postcss": "^4.2.1",
+ "ai": "^6.0.104",
"better-auth": "^1.4.19",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@@ -19,6 +20,7 @@
"react-dom": "^19.2.4",
"recharts": "^3.7.0",
"workflow": "^4.1.0-beta.60",
+ "zhipu-ai-provider": "^0.2.2",
},
"devDependencies": {
"@types/node": "^25.3.0",
@@ -34,6 +36,12 @@
},
},
"packages": {
+ "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.58", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2e1hBCKsd+7m0hELwrakR1QDfZfFhz9PF2d4qb8TxQueEyApo7ydlEWRpXeKC+KdA2FRV21dMb1G6FxdeNDa2w=="],
+
+ "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
+
+ "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="],
+
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
@@ -348,6 +356,8 @@
"@oclif/plugin-help": ["@oclif/plugin-help@6.2.31", "", { "dependencies": { "@oclif/core": "^4" } }, "sha512-o4xR98DEFf+VqY+M9B3ZooTm2T/mlGvyBHwHcnsPJCEnvzHqEA9xUlCUK4jm7FBXHhkppziMgCC2snsueLoIpQ=="],
+ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+
"@react-router/node": ["@react-router/node@7.13.0", "", { "dependencies": { "@mjackson/node-fetch-server": "^0.2.0" }, "peerDependencies": { "react-router": "7.13.0", "typescript": "^5.1.0" }, "optionalPeers": ["typescript"] }, "sha512-Mhr3fAou19oc/S93tKMIBHwCPfqLpWyWM/m0NWd3pJh/wZin8/9KhAdjwxhYbXw1TrTBZBLDENa35uZ+Y7oh3A=="],
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" } }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="],
@@ -546,7 +556,7 @@
"@vercel/functions": ["@vercel/functions@3.4.3", "", { "dependencies": { "@vercel/oidc": "3.2.0" }, "peerDependencies": { "@aws-sdk/credential-provider-web-identity": "*" }, "optionalPeers": ["@aws-sdk/credential-provider-web-identity"] }, "sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw=="],
- "@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="],
+ "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
"@vercel/queue": ["@vercel/queue@0.0.0-alpha.38", "", { "dependencies": { "@vercel/oidc": "^3.0.5", "mixpart": "0.0.5-alpha.1" } }, "sha512-gSYpZrYy1LpzfXqDMUZX7xEIQxyhelvMDgthxijs495kHIxWC65S0C3vaAw5+3c1ujbPeJgLz8fn3SDTJspssw=="],
@@ -614,6 +624,8 @@
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+ "ai": ["ai@6.0.104", "", { "dependencies": { "@ai-sdk/gateway": "3.0.58", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-boYGxbtdsa1YX3uuN7BV0FvAL3sGq7p/RLAMonK94jyt5C7sKj6jfib3/wD12koqX53htLTI/l4tX0HqNFRMZQ=="],
+
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
@@ -864,6 +876,8 @@
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
+
"exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
@@ -1030,6 +1044,8 @@
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
+ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
@@ -1468,6 +1484,8 @@
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
+ "zhipu-ai-provider": ["zhipu-ai-provider@0.2.2", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.0" } }, "sha512-UjX1ho4DI9ICUv/mrpAnzmrRe5/LXrGkS5hF6h4WDY2aup5GketWWopFzWYCqsbArXAM5wbzzdH9QzZusgGiBg=="],
+
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.3", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-tma6D8/xHZHJEUqmr6ksZjZ0onyIUqKDQLyp50ttZJmS0IwFYzxBgp5CxFvpYAnah52V3UtgrqGA6E83gtT7NQ=="],
@@ -1680,6 +1698,8 @@
"@workflow/world-local/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
+ "@workflow/world-vercel/@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="],
+
"@workflow/world-vercel/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
"@xhmikosr/archive-type/file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
@@ -1772,6 +1792,10 @@
"wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
+ "zhipu-ai-provider/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="],
+
+ "zhipu-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="],
+
"@aws-crypto/sha256-browser/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
diff --git a/components/auth/auth-shell.tsx b/components/auth/auth-shell.tsx
index b87c12c..51d1304 100644
--- a/components/auth/auth-shell.tsx
+++ b/components/auth/auth-shell.tsx
@@ -18,7 +18,7 @@ export function AuthShell({ title, subtitle, children, footer }: AuthShellProps)
Fiscal Clone
Autonomous Analyst Desk
- Secure entry into filings intelligence, portfolio diagnostics, and async AI workflows connected to OpenClaw/ZeroClaw.
+ Secure entry into filings intelligence, portfolio diagnostics, and async AI workflows powered by AI SDK.
{displayName}
{role ? Role: {role}
: null}
- OpenClaw and market data are driven by environment configuration and live API tasks.
+ AI and market data are driven by environment configuration and live API tasks.
void signOut()} disabled={isSigningOut}>
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
index 46e1b83..5d6a1ec 100644
--- a/docker-compose.override.yml
+++ b/docker-compose.override.yml
@@ -5,6 +5,3 @@ services:
environment:
BETTER_AUTH_BASE_URL: ${BETTER_AUTH_BASE_URL:-http://localhost:3000}
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
- openclaw:
- ports:
- - '${OPENCLAW_PORT:-4000}:4000'
diff --git a/docker-compose.yml b/docker-compose.yml
index b9c4f9a..889c353 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,13 +18,9 @@ services:
BETTER_AUTH_BASE_URL: ${BETTER_AUTH_BASE_URL:-https://fiscal.b11studio.xyz}
BETTER_AUTH_ADMIN_USER_IDS: ${BETTER_AUTH_ADMIN_USER_IDS:-}
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-https://fiscal.b11studio.xyz}
- OPENCLAW_BASE_URL: ${OPENCLAW_BASE_URL:-http://openclaw:4000}
- OPENCLAW_API_KEY: ${OPENCLAW_API_KEY:-}
- OPENCLAW_MODEL: ${OPENCLAW_MODEL:-zeroclaw}
- OPENCLAW_AUTH_MODE: ${OPENCLAW_AUTH_MODE:-none}
- OPENCLAW_BASIC_AUTH_USERNAME: ${OPENCLAW_BASIC_AUTH_USERNAME:-}
- OPENCLAW_BASIC_AUTH_PASSWORD: ${OPENCLAW_BASIC_AUTH_PASSWORD:-}
- OPENCLAW_API_KEY_HEADER: ${OPENCLAW_API_KEY_HEADER:-}
+ ZHIPU_API_KEY: ${ZHIPU_API_KEY:-}
+ ZHIPU_MODEL: ${ZHIPU_MODEL:-glm-4.7-flashx}
+ AI_TEMPERATURE: ${AI_TEMPERATURE:-0.2}
SEC_USER_AGENT: ${SEC_USER_AGENT:-Fiscal Clone }
WORKFLOW_TARGET_WORLD: local
WORKFLOW_LOCAL_DATA_DIR: ${WORKFLOW_LOCAL_DATA_DIR:-/app/.workflow-data}
@@ -32,9 +28,6 @@ services:
volumes:
- fiscal_sqlite_data:/app/data
- fiscal_workflow_data:/app/.workflow-data
- depends_on:
- openclaw:
- condition: service_started
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000/api/health || exit 1"]
interval: 30s
@@ -43,15 +36,6 @@ services:
expose:
- "3000"
- openclaw:
- image: ${OPENCLAW_IMAGE:-coolify-zeroclaw:local}
- build:
- context: ${OPENCLAW_BUILD_CONTEXT:-https://gitea-hs848cs8kgs840o8c8s8cwkk.b11studio.xyz/Francy51/coolify_ZeroClaw.git}
- dockerfile: ${OPENCLAW_DOCKERFILE:-Dockerfile}
- restart: unless-stopped
- expose:
- - "4000"
-
volumes:
fiscal_sqlite_data:
fiscal_workflow_data:
diff --git a/lib/server/ai.test.ts b/lib/server/ai.test.ts
new file mode 100644
index 0000000..36ec12d
--- /dev/null
+++ b/lib/server/ai.test.ts
@@ -0,0 +1,192 @@
+import { beforeEach, describe, expect, it, mock } from 'bun:test';
+import {
+ __resetAiWarningsForTests,
+ getAiConfig,
+ runAiAnalysis
+} from './ai';
+
+type EnvSource = Record;
+const CODING_API_BASE_URL = 'https://api.z.ai/api/coding/paas/v4';
+
+describe('ai config and runtime', () => {
+ beforeEach(() => {
+ __resetAiWarningsForTests();
+ });
+
+ it('uses coding endpoint defaults when optional env values are missing', () => {
+ const config = getAiConfig({
+ env: {
+ ZHIPU_API_KEY: 'key'
+ },
+ warn: () => {}
+ });
+
+ expect(config.apiKey).toBe('key');
+ expect(config.baseUrl).toBe(CODING_API_BASE_URL);
+ expect(config.model).toBe('glm-4.7-flashx');
+ expect(config.temperature).toBe(0.2);
+ });
+
+ it('ignores ZHIPU_BASE_URL and keeps the hardcoded coding endpoint', () => {
+ const config = getAiConfig({
+ env: {
+ ZHIPU_API_KEY: 'key',
+ ZHIPU_BASE_URL: 'https://api.z.ai/api/paas/v4'
+ },
+ warn: () => {}
+ });
+
+ expect(config.baseUrl).toBe(CODING_API_BASE_URL);
+ });
+
+ it('clamps temperature into [0, 2]', () => {
+ const negative = getAiConfig({
+ env: {
+ ZHIPU_API_KEY: 'key',
+ AI_TEMPERATURE: '-2'
+ },
+ warn: () => {}
+ });
+ expect(negative.temperature).toBe(0);
+
+ const high = getAiConfig({
+ env: {
+ ZHIPU_API_KEY: 'key',
+ AI_TEMPERATURE: '9'
+ },
+ warn: () => {}
+ });
+ expect(high.temperature).toBe(2);
+
+ const invalid = getAiConfig({
+ env: {
+ ZHIPU_API_KEY: 'key',
+ AI_TEMPERATURE: 'not-a-number'
+ },
+ warn: () => {}
+ });
+ expect(invalid.temperature).toBe(0.2);
+ });
+
+ it('returns fallback output when ZHIPU_API_KEY is missing', async () => {
+ const generate = mock(async () => ({ text: 'should-not-be-used' }));
+
+ const result = await runAiAnalysis(
+ 'Prompt line one\nPrompt line two',
+ 'System prompt',
+ {
+ env: {},
+ warn: () => {},
+ generate
+ }
+ );
+
+ expect(result.provider).toBe('local-fallback');
+ expect(result.model).toBe('glm-4.7-flashx');
+ expect(result.text).toContain('AI SDK fallback mode is active');
+ expect(generate).not.toHaveBeenCalled();
+ });
+
+ it('warns once when deprecated OPENCLAW_* env vars are present', () => {
+ const warn = mock((_message: string) => {});
+
+ const env: EnvSource = {
+ OPENCLAW_API_KEY: 'legacy-key',
+ OPENCLAW_BASE_URL: 'http://legacy.local',
+ ZHIPU_API_KEY: 'new-key'
+ };
+
+ getAiConfig({ env, warn });
+ getAiConfig({ env, warn });
+
+ expect(warn).toHaveBeenCalledTimes(1);
+ });
+
+ it('warns once when ZHIPU_BASE_URL is set because coding endpoint is hardcoded', () => {
+ const warn = mock((_message: string) => {});
+
+ const env: EnvSource = {
+ ZHIPU_API_KEY: 'new-key',
+ ZHIPU_BASE_URL: 'https://api.z.ai/api/paas/v4'
+ };
+
+ getAiConfig({ env, warn });
+ getAiConfig({ env, warn });
+
+ expect(warn).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not consume OPENCLAW_* values for live generation', async () => {
+ const generate = mock(async () => ({ text: 'should-not-be-used' }));
+ const warn = mock((_message: string) => {});
+
+ const result = await runAiAnalysis('Legacy-only env prompt', undefined, {
+ env: {
+ OPENCLAW_API_KEY: 'legacy-key',
+ OPENCLAW_MODEL: 'legacy-model'
+ },
+ warn,
+ generate
+ });
+
+ expect(result.provider).toBe('local-fallback');
+ expect(result.model).toBe('glm-4.7-flashx');
+ expect(generate).not.toHaveBeenCalled();
+ expect(warn).toHaveBeenCalledTimes(1);
+ });
+
+ it('uses configured ZHIPU values and injected generator when API key exists', async () => {
+ const createModel = mock((config: {
+ apiKey?: string;
+ model: string;
+ baseUrl: string;
+ temperature: number;
+ }) => {
+ expect(config.apiKey).toBe('new-key');
+ expect(config.baseUrl).toBe(CODING_API_BASE_URL);
+ expect(config.model).toBe('glm-4-plus');
+ expect(config.temperature).toBe(0.4);
+ return { modelId: config.model };
+ });
+ const generate = mock(async (input: {
+ model: unknown;
+ system?: string;
+ prompt: string;
+ temperature: number;
+ }) => {
+ expect(input.system).toBe('Use concise style');
+ expect(input.prompt).toBe('Analyze this filing');
+ expect(input.temperature).toBe(0.4);
+ return { text: ' Generated insight ' };
+ });
+
+ const result = await runAiAnalysis('Analyze this filing', 'Use concise style', {
+ env: {
+ ZHIPU_API_KEY: 'new-key',
+ ZHIPU_MODEL: 'glm-4-plus',
+ ZHIPU_BASE_URL: 'https://api.z.ai/api/paas/v4',
+ AI_TEMPERATURE: '0.4'
+ },
+ warn: () => {},
+ createModel,
+ generate
+ });
+
+ expect(createModel).toHaveBeenCalledTimes(1);
+ expect(generate).toHaveBeenCalledTimes(1);
+ expect(result.provider).toBe('zhipu');
+ expect(result.model).toBe('glm-4-plus');
+ expect(result.text).toBe('Generated insight');
+ });
+
+ it('throws when AI generation returns an empty response', async () => {
+ await expect(
+ runAiAnalysis('Analyze this filing', undefined, {
+ env: { ZHIPU_API_KEY: 'new-key' },
+ warn: () => {},
+ createModel: () => ({}),
+ generate: async () => ({ text: ' ' })
+ })
+ ).rejects.toThrow('AI SDK returned an empty response');
+ });
+});
diff --git a/lib/server/ai.ts b/lib/server/ai.ts
new file mode 100644
index 0000000..26a2cd1
--- /dev/null
+++ b/lib/server/ai.ts
@@ -0,0 +1,190 @@
+import { generateText } from 'ai';
+import { createZhipu } from 'zhipu-ai-provider';
+
+type AiConfig = {
+ apiKey?: string;
+ baseUrl: string;
+ model: string;
+ temperature: number;
+};
+
+type EnvSource = Record;
+
+type GetAiConfigOptions = {
+ env?: EnvSource;
+ warn?: (message: string) => void;
+};
+
+type AiGenerateInput = {
+ model: unknown;
+ system?: string;
+ prompt: string;
+ temperature: number;
+};
+
+type AiGenerateOutput = {
+ text: string;
+};
+
+type RunAiAnalysisOptions = GetAiConfigOptions & {
+ createModel?: (config: AiConfig) => unknown;
+ generate?: (input: AiGenerateInput) => Promise;
+};
+
+const DEPRECATED_LEGACY_GATEWAY_ENV_KEYS = [
+ 'OPENCLAW_BASE_URL',
+ 'OPENCLAW_API_KEY',
+ 'OPENCLAW_MODEL',
+ 'OPENCLAW_AUTH_MODE',
+ 'OPENCLAW_BASIC_AUTH_USERNAME',
+ 'OPENCLAW_BASIC_AUTH_PASSWORD',
+ 'OPENCLAW_API_KEY_HEADER',
+ 'OPENCLAW_PORT',
+ 'OPENCLAW_IMAGE',
+ 'OPENCLAW_BUILD_CONTEXT',
+ 'OPENCLAW_DOCKERFILE'
+] as const;
+
+const CODING_API_BASE_URL = 'https://api.z.ai/api/coding/paas/v4';
+
+let warnedDeprecatedGatewayEnv = false;
+let warnedIgnoredZhipuBaseUrl = false;
+
+function envValue(name: string, env: EnvSource = process.env) {
+ const value = env[name];
+ if (!value) {
+ return undefined;
+ }
+
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : undefined;
+}
+
+function parseTemperature(value: string | undefined) {
+ const parsed = Number(value);
+ if (!Number.isFinite(parsed)) {
+ return 0.2;
+ }
+
+ return Math.min(Math.max(parsed, 0), 2);
+}
+
+function warnDeprecatedGatewayEnv(env: EnvSource, warn: (message: string) => void) {
+ if (warnedDeprecatedGatewayEnv) {
+ return;
+ }
+
+ const presentKeys = DEPRECATED_LEGACY_GATEWAY_ENV_KEYS.filter((key) => Boolean(envValue(key, env)));
+ if (presentKeys.length === 0) {
+ return;
+ }
+
+ warnedDeprecatedGatewayEnv = true;
+ warn(
+ `[AI SDK] Deprecated OPENCLAW_* variables are ignored after migration: ${presentKeys.join(', ')}. Use ZHIPU_API_KEY, ZHIPU_MODEL, and AI_TEMPERATURE.`
+ );
+}
+
+function warnIgnoredZhipuBaseUrl(env: EnvSource, warn: (message: string) => void) {
+ if (warnedIgnoredZhipuBaseUrl) {
+ return;
+ }
+
+ const configuredBaseUrl = envValue('ZHIPU_BASE_URL', env);
+ if (!configuredBaseUrl) {
+ return;
+ }
+
+ warnedIgnoredZhipuBaseUrl = true;
+ warn(
+ `[AI SDK] ZHIPU_BASE_URL is ignored. The Coding API endpoint is hardcoded to ${CODING_API_BASE_URL}.`
+ );
+}
+
+function fallbackResponse(prompt: string) {
+ const clipped = prompt.split('\n').slice(0, 6).join(' ').slice(0, 260);
+
+ return [
+ 'AI SDK fallback mode is active (Zhipu configuration is missing).',
+ 'Thesis: Portfolio remains analyzable with local heuristics until live model access is configured.',
+ 'Risk scan: Concentration and filing sentiment should be monitored after each sync cycle.',
+ `Context digest: ${clipped}`
+ ].join('\n\n');
+}
+
+function defaultCreateModel(config: AiConfig) {
+ const zhipu = createZhipu({
+ apiKey: config.apiKey,
+ baseURL: config.baseUrl
+ });
+
+ return zhipu(config.model);
+}
+
+async function defaultGenerate(input: AiGenerateInput): Promise {
+ const result = await generateText({
+ model: input.model as never,
+ system: input.system,
+ prompt: input.prompt,
+ temperature: input.temperature
+ });
+
+ return { text: result.text };
+}
+
+export function getAiConfig(options?: GetAiConfigOptions) {
+ const env = options?.env ?? process.env;
+ warnDeprecatedGatewayEnv(env, options?.warn ?? console.warn);
+ warnIgnoredZhipuBaseUrl(env, options?.warn ?? console.warn);
+
+ return {
+ apiKey: envValue('ZHIPU_API_KEY', env),
+ baseUrl: CODING_API_BASE_URL,
+ model: envValue('ZHIPU_MODEL', env) ?? 'glm-4.7-flashx',
+ temperature: parseTemperature(envValue('AI_TEMPERATURE', env))
+ } satisfies AiConfig;
+}
+
+export function isAiConfigured(options?: GetAiConfigOptions) {
+ const config = getAiConfig(options);
+ return Boolean(config.apiKey);
+}
+
+export async function runAiAnalysis(prompt: string, systemPrompt?: string, options?: RunAiAnalysisOptions) {
+ const config = getAiConfig(options);
+
+ if (!config.apiKey) {
+ return {
+ provider: 'local-fallback',
+ model: config.model,
+ text: fallbackResponse(prompt)
+ };
+ }
+
+ const createModel = options?.createModel ?? defaultCreateModel;
+ const generate = options?.generate ?? defaultGenerate;
+ const model = createModel(config);
+
+ const result = await generate({
+ model,
+ system: systemPrompt,
+ prompt,
+ temperature: config.temperature
+ });
+
+ const text = result.text.trim();
+ if (!text) {
+ throw new Error('AI SDK returned an empty response');
+ }
+
+ return {
+ provider: 'zhipu',
+ model: config.model,
+ text
+ };
+}
+
+export function __resetAiWarningsForTests() {
+ warnedDeprecatedGatewayEnv = false;
+ warnedIgnoredZhipuBaseUrl = false;
+}
diff --git a/lib/server/openclaw.ts b/lib/server/openclaw.ts
deleted file mode 100644
index 2750330..0000000
--- a/lib/server/openclaw.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-type ChatCompletionResponse = {
- choices?: Array<{
- message?: {
- content?: string;
- };
- }>;
-};
-
-type OpenClawAuthMode = 'bearer' | 'basic' | 'none';
-
-type OpenClawConfig = {
- baseUrl?: string;
- apiKey?: string;
- model: string;
- authMode: OpenClawAuthMode;
- basicAuthUsername?: string;
- basicAuthPassword?: string;
- apiKeyHeader?: string;
-};
-
-function envValue(name: string) {
- const value = process.env[name];
- if (!value) {
- return undefined;
- }
-
- const trimmed = value.trim();
- return trimmed.length > 0 ? trimmed : undefined;
-}
-
-const DEFAULT_MODEL = 'zeroclaw';
-const DEFAULT_AUTH_MODE: OpenClawAuthMode = 'bearer';
-
-function parseAuthMode(value: string | undefined): OpenClawAuthMode {
- const normalized = value?.trim().toLowerCase();
- if (normalized === 'basic' || normalized === 'none') {
- return normalized;
- }
-
- return DEFAULT_AUTH_MODE;
-}
-
-function hasSupportedProtocol(url: string) {
- try {
- const parsed = new URL(url);
- return parsed.protocol === 'http:' || parsed.protocol === 'https:';
- } catch {
- return false;
- }
-}
-
-function buildCompletionsUrl(baseUrl: string) {
- if (!hasSupportedProtocol(baseUrl)) {
- return undefined;
- }
-
- const withSlash = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
- return new URL('v1/chat/completions', withSlash).toString();
-}
-
-function hasRequiredAuth(config: OpenClawConfig) {
- if (config.authMode === 'none') {
- return true;
- }
-
- if (config.authMode === 'basic') {
- return Boolean(config.basicAuthUsername && config.basicAuthPassword);
- }
-
- return Boolean(config.apiKey);
-}
-
-function buildAuthHeaders(config: OpenClawConfig) {
- const headers: Record = {
- 'Content-Type': 'application/json'
- };
-
- if (config.authMode === 'basic' && config.basicAuthUsername && config.basicAuthPassword) {
- const credentials = Buffer
- .from(`${config.basicAuthUsername}:${config.basicAuthPassword}`, 'utf8')
- .toString('base64');
- headers.Authorization = `Basic ${credentials}`;
- } else if (config.authMode === 'bearer' && config.apiKey) {
- headers.Authorization = `Bearer ${config.apiKey}`;
- }
-
- if (config.apiKey && config.apiKeyHeader) {
- headers[config.apiKeyHeader] = config.apiKey;
- }
-
- return headers;
-}
-
-function fallbackResponse(prompt: string) {
- const clipped = prompt.split('\n').slice(0, 6).join(' ').slice(0, 260);
-
- return [
- 'OpenClaw fallback mode is active (configuration is missing or invalid).',
- 'Thesis: Portfolio remains analyzable with local heuristics until live model access is configured.',
- 'Risk scan: Concentration and filing sentiment should be monitored after each sync cycle.',
- `Context digest: ${clipped}`
- ].join('\n\n');
-}
-
-export function getOpenClawConfig() {
- return {
- baseUrl: envValue('OPENCLAW_BASE_URL'),
- apiKey: envValue('OPENCLAW_API_KEY'),
- model: envValue('OPENCLAW_MODEL') ?? DEFAULT_MODEL,
- authMode: parseAuthMode(envValue('OPENCLAW_AUTH_MODE')),
- basicAuthUsername: envValue('OPENCLAW_BASIC_AUTH_USERNAME'),
- basicAuthPassword: envValue('OPENCLAW_BASIC_AUTH_PASSWORD'),
- apiKeyHeader: envValue('OPENCLAW_API_KEY_HEADER')
- } satisfies OpenClawConfig;
-}
-
-export function isOpenClawConfigured() {
- const config = getOpenClawConfig();
- return Boolean(config.baseUrl && hasSupportedProtocol(config.baseUrl) && hasRequiredAuth(config));
-}
-
-export async function runOpenClawAnalysis(prompt: string, systemPrompt?: string) {
- const config = getOpenClawConfig();
- const endpoint = config.baseUrl ? buildCompletionsUrl(config.baseUrl) : undefined;
-
- if (!endpoint || !hasRequiredAuth(config)) {
- return {
- provider: 'local-fallback',
- model: config.model,
- text: fallbackResponse(prompt)
- };
- }
-
- const response = await fetch(endpoint, {
- method: 'POST',
- headers: buildAuthHeaders(config),
- body: JSON.stringify({
- model: config.model,
- temperature: 0.2,
- messages: [
- systemPrompt
- ? { role: 'system', content: systemPrompt }
- : null,
- { role: 'user', content: prompt }
- ].filter(Boolean)
- }),
- cache: 'no-store'
- });
-
- if (!response.ok) {
- const body = await response.text();
- throw new Error(`OpenClaw request failed (${response.status}): ${body.slice(0, 220)}`);
- }
-
- const payload = await response.json() as ChatCompletionResponse;
- const text = payload.choices?.[0]?.message?.content?.trim();
-
- if (!text) {
- throw new Error('OpenClaw returned an empty response');
- }
-
- return {
- provider: 'openclaw',
- model: config.model,
- text
- };
-}
diff --git a/lib/server/task-processors.ts b/lib/server/task-processors.ts
index 1fc1a62..ea55197 100644
--- a/lib/server/task-processors.ts
+++ b/lib/server/task-processors.ts
@@ -1,5 +1,5 @@
import type { Filing, Holding, Task } from '@/lib/types';
-import { runOpenClawAnalysis } from '@/lib/server/openclaw';
+import { runAiAnalysis } from '@/lib/server/ai';
import { buildPortfolioSummary } from '@/lib/server/portfolio';
import { getQuote } from '@/lib/server/prices';
import {
@@ -143,7 +143,7 @@ async function processAnalyzeFiling(task: Task) {
'Return concise sections: Thesis, Red Flags, Follow-up Questions, Portfolio Impact.'
].join('\n');
- const analysis = await runOpenClawAnalysis(prompt, 'Use concise institutional analyst language.');
+ const analysis = await runAiAnalysis(prompt, 'Use concise institutional analyst language.');
await saveFilingAnalysis(accessionNumber, {
provider: analysis.provider,
@@ -186,7 +186,7 @@ async function processPortfolioInsights(task: Task) {
'Respond with: 1) health score (0-100), 2) top 3 risks, 3) top 3 opportunities, 4) next actions in 7 days.'
].join('\n');
- const analysis = await runOpenClawAnalysis(prompt, 'Act as a risk-aware buy-side analyst.');
+ const analysis = await runAiAnalysis(prompt, 'Act as a risk-aware buy-side analyst.');
await createPortfolioInsight({
userId,
diff --git a/package.json b/package.json
index 967b292..724b5c9 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@elysiajs/eden": "^1.4.8",
"@libsql/client": "^0.17.0",
"@tailwindcss/postcss": "^4.2.1",
+ "ai": "^6.0.104",
"better-auth": "^1.4.19",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
@@ -25,7 +26,8 @@
"react": "^19.2.4",
"react-dom": "^19.2.4",
"recharts": "^3.7.0",
- "workflow": "^4.1.0-beta.60"
+ "workflow": "^4.1.0-beta.60",
+ "zhipu-ai-provider": "^0.2.2"
},
"devDependencies": {
"@types/node": "^25.3.0",