feat: migrate AI to Vercel AI SDK, add OpenAI provider, fix worklist

- Replace raw @anthropic-ai/sdk with Vercel AI SDK (generateText, tool, generateObject)
- Add provider abstraction (ai-provider.ts) — swap OpenAI/Anthropic via env var
- AI chat controller: dynamic KB from platform (clinics, packages, insurance), zero hardcoding
- AI enrichment service: use generateObject with Zod schema instead of manual JSON parsing
- Worklist: resolve agent name from platform currentUser API instead of JWT decode
- Worklist: fix GraphQL field names to match platform remapping (source, status, direction, etc.)
- Config: add AI_PROVIDER, AI_MODEL, OPENAI_API_KEY env vars

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 16:45:05 +05:30
parent 6f7d408724
commit 9688d5144e
9 changed files with 1150 additions and 409 deletions

View File

@@ -1,11 +1,15 @@
import { Controller, Get, Headers, HttpException, Logger } from '@nestjs/common';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { WorklistService } from './worklist.service';
@Controller('api/worklist')
export class WorklistController {
private readonly logger = new Logger(WorklistController.name);
constructor(private readonly worklist: WorklistService) {}
constructor(
private readonly worklist: WorklistService,
private readonly platform: PlatformGraphqlService,
) {}
@Get()
async getWorklist(@Headers('authorization') authHeader: string) {
@@ -13,33 +17,25 @@ export class WorklistController {
throw new HttpException('Authorization required', 401);
}
// Decode the JWT to extract the agent name
// The platform JWT payload contains user info — we extract the name
const agentName = this.extractAgentName(authHeader);
if (!agentName) {
throw new HttpException('Could not determine agent identity from token', 400);
}
const agentName = await this.resolveAgentName(authHeader);
this.logger.log(`Fetching worklist for agent: ${agentName}`);
return this.worklist.getWorklist(agentName, authHeader);
}
private extractAgentName(authHeader: string): string | null {
private async resolveAgentName(authHeader: string): Promise<string> {
try {
const token = authHeader.replace(/^Bearer\s+/i, '');
// JWT payload is the second segment, base64url-encoded
const payload = JSON.parse(
Buffer.from(token.split('.')[1], 'base64url').toString('utf8'),
const data = await this.platform.queryWithAuth<any>(
`{ currentUser { workspaceMember { name { firstName lastName } } } }`,
undefined,
authHeader,
);
// The platform JWT includes sub (userId) and workspace info
// The agent name comes from firstName + lastName in the token
const firstName = payload.firstName ?? payload.given_name ?? '';
const lastName = payload.lastName ?? payload.family_name ?? '';
const fullName = `${firstName} ${lastName}`.trim();
return fullName || payload.email || payload.sub || null;
} catch {
return null;
const name = data.currentUser?.workspaceMember?.name;
const full = `${name?.firstName ?? ''} ${name?.lastName ?? ''}`.trim();
if (full) return full;
} catch (err) {
this.logger.warn(`Failed to resolve agent name: ${err}`);
}
throw new HttpException('Could not determine agent identity', 400);
}
}