@tuturuuu/ai 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/package.json +106 -0
- package/src/api-key-hash.ts +28 -0
- package/src/calendar/events.ts +34 -0
- package/src/calendar/route.ts +114 -0
- package/src/chat/credit-source.ts +1 -0
- package/src/chat/google/chat-request-schema.ts +150 -0
- package/src/chat/google/default-system-instruction.ts +198 -0
- package/src/chat/google/message-file-processing.ts +212 -0
- package/src/chat/google/mira-step-preparation.ts +221 -0
- package/src/chat/google/new/route.ts +368 -0
- package/src/chat/google/route-auth.ts +81 -0
- package/src/chat/google/route-chat-resolution.ts +98 -0
- package/src/chat/google/route-credits.ts +61 -0
- package/src/chat/google/route-message-preparation.ts +331 -0
- package/src/chat/google/route-mira-runtime.ts +206 -0
- package/src/chat/google/route.ts +632 -0
- package/src/chat/google/stream-finish-persistence.ts +722 -0
- package/src/chat/google/summary/route.ts +153 -0
- package/src/chat/mira-render-ui-policy.ts +540 -0
- package/src/chat/mira-system-instruction.ts +484 -0
- package/src/chat-sdk/adapters.ts +389 -0
- package/src/chat-sdk/registry.ts +197 -0
- package/src/chat-sdk.ts +33 -0
- package/src/core.ts +3 -0
- package/src/credits/cap-output-tokens.ts +90 -0
- package/src/credits/check-credits.ts +232 -0
- package/src/credits/constants.ts +30 -0
- package/src/credits/index.ts +46 -0
- package/src/credits/model-mapping.ts +92 -0
- package/src/credits/reservations.ts +514 -0
- package/src/credits/resolve-plan-model.ts +219 -0
- package/src/credits/sync-gateway-models.ts +351 -0
- package/src/credits/types.ts +109 -0
- package/src/credits/use-ai-credits.ts +3 -0
- package/src/embeddings/metered.ts +283 -0
- package/src/executions/route.ts +137 -0
- package/src/generate/route.ts +411 -0
- package/src/hooks.ts +7 -0
- package/src/meetings/summary/route.ts +7 -0
- package/src/meetings/transcription/route.ts +134 -0
- package/src/memory/client.ts +158 -0
- package/src/memory/config.ts +38 -0
- package/src/memory/index.ts +32 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/middleware.ts +35 -0
- package/src/memory/operations.ts +480 -0
- package/src/memory/scope.ts +102 -0
- package/src/memory/settings.ts +121 -0
- package/src/memory/types.ts +101 -0
- package/src/memory/workspace.ts +36 -0
- package/src/memory.ts +1 -0
- package/src/mind/patch.ts +146 -0
- package/src/mind/route.ts +687 -0
- package/src/mind/tools.ts +1500 -0
- package/src/mind/types.ts +20 -0
- package/src/object/core.ts +3 -0
- package/src/object/flashcards/route.ts +140 -0
- package/src/object/quizzes/explanation/route.ts +145 -0
- package/src/object/quizzes/route.ts +142 -0
- package/src/object/types.ts +187 -0
- package/src/object/year-plan/route.ts +196 -0
- package/src/react.ts +1 -0
- package/src/scheduling/algorithm.ts +791 -0
- package/src/scheduling/default.ts +36 -0
- package/src/scheduling/duration-optimizer.ts +689 -0
- package/src/scheduling/index.ts +79 -0
- package/src/scheduling/priority-calculator.ts +187 -0
- package/src/scheduling/recurrence-calculator.ts +621 -0
- package/src/scheduling/templates.ts +892 -0
- package/src/scheduling/types.ts +136 -0
- package/src/scheduling/web-adapter.ts +308 -0
- package/src/scheduling.ts +6 -0
- package/src/supported-actions.ts +1 -0
- package/src/supported-providers.ts +6 -0
- package/src/tools/context-builder.ts +372 -0
- package/src/tools/core.ts +1 -0
- package/src/tools/definitions/calendar.ts +106 -0
- package/src/tools/definitions/finance.ts +197 -0
- package/src/tools/definitions/image.ts +74 -0
- package/src/tools/definitions/memory.ts +83 -0
- package/src/tools/definitions/meta.ts +154 -0
- package/src/tools/definitions/render-ui.ts +81 -0
- package/src/tools/definitions/tasks.ts +343 -0
- package/src/tools/definitions/time-tracking.ts +381 -0
- package/src/tools/definitions/workspace-context.ts +45 -0
- package/src/tools/definitions/workspace-user-chat.ts +111 -0
- package/src/tools/executors/calendar.ts +371 -0
- package/src/tools/executors/chat.ts +15 -0
- package/src/tools/executors/finance.ts +638 -0
- package/src/tools/executors/helpers/encryption.ts +107 -0
- package/src/tools/executors/image.ts +247 -0
- package/src/tools/executors/markitdown.ts +684 -0
- package/src/tools/executors/memory.ts +277 -0
- package/src/tools/executors/parallel-checks.ts +176 -0
- package/src/tools/executors/qr.ts +170 -0
- package/src/tools/executors/scope-helpers.ts +192 -0
- package/src/tools/executors/search.ts +149 -0
- package/src/tools/executors/settings.ts +40 -0
- package/src/tools/executors/tasks.ts +1087 -0
- package/src/tools/executors/theme.ts +23 -0
- package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
- package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
- package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
- package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
- package/src/tools/executors/timer/timer-helpers.ts +372 -0
- package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
- package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
- package/src/tools/executors/timer/timer-mutations.ts +19 -0
- package/src/tools/executors/timer/timer-queries.ts +18 -0
- package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
- package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
- package/src/tools/executors/timer/timer-session-queries.ts +153 -0
- package/src/tools/executors/timer/timer-session-updates.ts +200 -0
- package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
- package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
- package/src/tools/executors/timer.ts +22 -0
- package/src/tools/executors/user.ts +60 -0
- package/src/tools/executors/workspace.ts +135 -0
- package/src/tools/json-render-catalog.ts +875 -0
- package/src/tools/mira-tool-definitions.ts +55 -0
- package/src/tools/mira-tool-dispatcher.ts +265 -0
- package/src/tools/mira-tool-metadata.ts +164 -0
- package/src/tools/mira-tool-names.ts +95 -0
- package/src/tools/mira-tool-render-ui.ts +54 -0
- package/src/tools/mira-tool-types.ts +17 -0
- package/src/tools/mira-tools.ts +167 -0
- package/src/tools/normalize-render-ui-input.ts +321 -0
- package/src/tools/workspace-context.ts +233 -0
- package/src/types.ts +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @tuturuuu/ai
|
|
2
|
+
|
|
3
|
+
AI utilities and helpers for Tuturuuu Platform.
|
|
4
|
+
|
|
5
|
+
## Chat SDK
|
|
6
|
+
|
|
7
|
+
`@tuturuuu/ai/chat-sdk` exposes the Vercel Chat SDK core, `chat/ai`
|
|
8
|
+
tool helpers, and a Tuturuuu adapter registry for omni-channel agentic
|
|
9
|
+
presence workflows.
|
|
10
|
+
|
|
11
|
+
The registry covers every adapter currently listed at
|
|
12
|
+
https://chat-sdk.dev/adapters:
|
|
13
|
+
|
|
14
|
+
- Official platform adapters: Slack, Microsoft Teams, Google Chat, Discord,
|
|
15
|
+
GitHub, Linear, Telegram, WhatsApp Business Cloud, Messenger, and Web.
|
|
16
|
+
- Vendor-official platform adapters: Beeper Matrix, Photon iMessage, Resend,
|
|
17
|
+
Zernio, and Liveblocks.
|
|
18
|
+
- Community platform adapters: Webex, Baileys WhatsApp, Sendblue, Blooio, Zalo,
|
|
19
|
+
and Mattermost.
|
|
20
|
+
- State adapters: Memory, Redis, ioredis, PostgreSQL, Cloudflare Durable
|
|
21
|
+
Objects, and MySQL.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
createChatSdkRuntime,
|
|
26
|
+
createChatTools,
|
|
27
|
+
} from '@tuturuuu/ai/chat-sdk';
|
|
28
|
+
|
|
29
|
+
const chat = await createChatSdkRuntime({
|
|
30
|
+
userName: 'mira',
|
|
31
|
+
adapters: {
|
|
32
|
+
slack: true,
|
|
33
|
+
zalo: {
|
|
34
|
+
botToken: process.env.ZALO_BOT_TOKEN,
|
|
35
|
+
webhookSecret: process.env.ZALO_WEBHOOK_SECRET,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
state: {
|
|
39
|
+
id: 'redis',
|
|
40
|
+
config: { url: process.env.REDIS_URL },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const tools = createChatTools({
|
|
45
|
+
chat,
|
|
46
|
+
preset: 'messenger',
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use a production state adapter for deployed bots so subscriptions,
|
|
51
|
+
deduplication, and distributed webhook locks survive restarts. The memory state
|
|
52
|
+
adapter is only for local development and tests.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @tuturuuu/ai
|
|
58
|
+
# or
|
|
59
|
+
yarn add @tuturuuu/ai
|
|
60
|
+
# or
|
|
61
|
+
bun add @tuturuuu/ai
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Install dependencies
|
|
68
|
+
bun install
|
|
69
|
+
|
|
70
|
+
# Run tests
|
|
71
|
+
bun run test
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT © [Tuturuuu](https://github.com/tutur3u)
|
package/package.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuturuuu/ai",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "0.0.10",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": ["src", "!src/**/*.test.ts", "!src/**/__tests__", "README.md"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"type-check": "tsgo --project tsconfig.typecheck.json"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ai-sdk/amazon-bedrock": "^4.0.111",
|
|
16
|
+
"@ai-sdk/anthropic": "^3.0.81",
|
|
17
|
+
"@ai-sdk/azure": "^3.0.68",
|
|
18
|
+
"@ai-sdk/cerebras": "^2.0.53",
|
|
19
|
+
"@ai-sdk/cohere": "^3.0.36",
|
|
20
|
+
"@ai-sdk/deepgram": "^2.0.33",
|
|
21
|
+
"@ai-sdk/deepinfra": "^2.0.51",
|
|
22
|
+
"@ai-sdk/deepseek": "^2.0.34",
|
|
23
|
+
"@ai-sdk/elevenlabs": "^2.0.33",
|
|
24
|
+
"@ai-sdk/fal": "^2.0.34",
|
|
25
|
+
"@ai-sdk/fireworks": "^2.0.52",
|
|
26
|
+
"@ai-sdk/gateway": "^3.0.121",
|
|
27
|
+
"@ai-sdk/gladia": "^2.0.33",
|
|
28
|
+
"@ai-sdk/google": "^3.0.80",
|
|
29
|
+
"@ai-sdk/google-vertex": "^4.0.140",
|
|
30
|
+
"@ai-sdk/groq": "^3.0.39",
|
|
31
|
+
"@ai-sdk/hume": "^2.0.33",
|
|
32
|
+
"@ai-sdk/lmnt": "^2.0.33",
|
|
33
|
+
"@ai-sdk/luma": "^2.0.33",
|
|
34
|
+
"@ai-sdk/mistral": "^3.0.37",
|
|
35
|
+
"@ai-sdk/openai": "^3.0.67",
|
|
36
|
+
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
37
|
+
"@ai-sdk/perplexity": "^3.0.33",
|
|
38
|
+
"@ai-sdk/react": "^3.0.195",
|
|
39
|
+
"@ai-sdk/replicate": "^2.0.33",
|
|
40
|
+
"@ai-sdk/revai": "^2.0.33",
|
|
41
|
+
"@ai-sdk/togetherai": "^2.0.52",
|
|
42
|
+
"@ai-sdk/xai": "^3.0.93",
|
|
43
|
+
"@beeper/chat-adapter-matrix": "^0.2.0",
|
|
44
|
+
"@bitbasti/chat-adapter-webex": "^0.1.0",
|
|
45
|
+
"@chat-adapter/discord": "^4.29.0",
|
|
46
|
+
"@chat-adapter/gchat": "^4.29.0",
|
|
47
|
+
"@chat-adapter/github": "^4.29.0",
|
|
48
|
+
"@chat-adapter/linear": "^4.29.0",
|
|
49
|
+
"@chat-adapter/messenger": "^4.29.0",
|
|
50
|
+
"@chat-adapter/slack": "^4.29.0",
|
|
51
|
+
"@chat-adapter/state-ioredis": "^4.29.0",
|
|
52
|
+
"@chat-adapter/state-memory": "^4.29.0",
|
|
53
|
+
"@chat-adapter/state-pg": "^4.29.0",
|
|
54
|
+
"@chat-adapter/state-redis": "^4.29.0",
|
|
55
|
+
"@chat-adapter/teams": "^4.29.0",
|
|
56
|
+
"@chat-adapter/telegram": "^4.29.0",
|
|
57
|
+
"@chat-adapter/web": "^4.29.0",
|
|
58
|
+
"@chat-adapter/whatsapp": "^4.29.0",
|
|
59
|
+
"@json-render/core": "^0.19.0",
|
|
60
|
+
"@json-render/react": "^0.19.0",
|
|
61
|
+
"@liveblocks/chat-sdk-adapter": "^3.19.3",
|
|
62
|
+
"@octokit/rest": "^22.0.1",
|
|
63
|
+
"@resend/chat-sdk-adapter": "^0.2.2",
|
|
64
|
+
"@streamdown/cjk": "^1.0.3",
|
|
65
|
+
"@streamdown/code": "^1.1.1",
|
|
66
|
+
"@streamdown/math": "^1.0.2",
|
|
67
|
+
"@streamdown/mermaid": "^1.0.2",
|
|
68
|
+
"@tuturuuu/google": "0.0.1",
|
|
69
|
+
"@tuturuuu/internal-api": "0.0.2",
|
|
70
|
+
"@tuturuuu/supabase": "0.2.3",
|
|
71
|
+
"@tuturuuu/utils": "0.0.1",
|
|
72
|
+
"@vercel/sandbox": "^2.0.2",
|
|
73
|
+
"@zernio/chat-sdk-adapter": "^0.3.0",
|
|
74
|
+
"ai": "^6.0.193",
|
|
75
|
+
"bash-tool": "^1.3.17",
|
|
76
|
+
"chat": "^4.29.0",
|
|
77
|
+
"chat-adapter-baileys": "^2.0.2",
|
|
78
|
+
"chat-adapter-blooio": "^0.1.0",
|
|
79
|
+
"chat-adapter-imessage": "^0.1.1",
|
|
80
|
+
"chat-adapter-mattermost": "^1.1.3",
|
|
81
|
+
"chat-adapter-sendblue": "^0.2.0",
|
|
82
|
+
"chat-adapter-zalo": "^0.1.0",
|
|
83
|
+
"chat-state-cloudflare-do": "^0.2.0",
|
|
84
|
+
"chat-state-mysql": "^0.1.0",
|
|
85
|
+
"dayjs": "^1.11.20",
|
|
86
|
+
"next": "^16.2.6",
|
|
87
|
+
"qrcode": "^1.5.4",
|
|
88
|
+
"react": "^19.2.6",
|
|
89
|
+
"react-dom": "^19.2.6",
|
|
90
|
+
"streamdown": "^2.5.0",
|
|
91
|
+
"uuid": "^14.0.0",
|
|
92
|
+
"zod": "^4.4.3"
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@tuturuuu/types": "0.2.4",
|
|
96
|
+
"@tuturuuu/typescript-config": "0.1.0",
|
|
97
|
+
"@types/node": "^25.9.1",
|
|
98
|
+
"@types/qrcode": "^1.5.6",
|
|
99
|
+
"@types/react": "^19.2.15",
|
|
100
|
+
"typescript": "^6.0.3"
|
|
101
|
+
},
|
|
102
|
+
"exports": {
|
|
103
|
+
"./*": "./src/*.ts"
|
|
104
|
+
},
|
|
105
|
+
"packageManager": "bun@1.3.14"
|
|
106
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { scrypt, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
|
|
4
|
+
const scryptAsync = promisify(scrypt);
|
|
5
|
+
const KEY_DERIVATION_LENGTH = 64;
|
|
6
|
+
|
|
7
|
+
export async function validateApiKeyHash(
|
|
8
|
+
key: string,
|
|
9
|
+
storedHash: string
|
|
10
|
+
): Promise<boolean> {
|
|
11
|
+
try {
|
|
12
|
+
const [salt, hash] = storedHash.split(':');
|
|
13
|
+
|
|
14
|
+
if (!salt || !hash) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const derivedKey = (await scryptAsync(
|
|
19
|
+
key,
|
|
20
|
+
salt,
|
|
21
|
+
KEY_DERIVATION_LENGTH
|
|
22
|
+
)) as Buffer;
|
|
23
|
+
|
|
24
|
+
return timingSafeEqual(derivedKey, Buffer.from(hash, 'hex'));
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const calendarEventSchema = z.object({
|
|
4
|
+
title: z.string().min(1, 'Title is required').describe('Title of the event'),
|
|
5
|
+
description: z.string().optional().describe('Description of the event'),
|
|
6
|
+
start_at: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe('Start date and time of the event in ISO format'),
|
|
9
|
+
end_at: z.string().describe('End date and time of the event in ISO format'),
|
|
10
|
+
color: z
|
|
11
|
+
.enum([
|
|
12
|
+
'BLUE',
|
|
13
|
+
'RED',
|
|
14
|
+
'GREEN',
|
|
15
|
+
'YELLOW',
|
|
16
|
+
'PURPLE',
|
|
17
|
+
'PINK',
|
|
18
|
+
'ORANGE',
|
|
19
|
+
'INDIGO',
|
|
20
|
+
'CYAN',
|
|
21
|
+
'GRAY',
|
|
22
|
+
])
|
|
23
|
+
.default('BLUE')
|
|
24
|
+
.describe(
|
|
25
|
+
"Color of the event for display purposes. Should match one of the user's defined categories if applicable."
|
|
26
|
+
),
|
|
27
|
+
location: z.string().optional().describe('Location of the event'),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const calendarEventsSchema = z.object({
|
|
31
|
+
events: z.array(calendarEventSchema),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type CalendarEvent = z.infer<typeof calendarEventSchema>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { google } from '@ai-sdk/google';
|
|
2
|
+
import { createClient } from '@tuturuuu/supabase/next/server';
|
|
3
|
+
import { ROOT_WORKSPACE_ID } from '@tuturuuu/utils/constants';
|
|
4
|
+
import { Output, streamText } from 'ai';
|
|
5
|
+
import { withAiMemory } from '../memory';
|
|
6
|
+
import { calendarEventsSchema } from './events';
|
|
7
|
+
|
|
8
|
+
export async function POST(req: Request) {
|
|
9
|
+
const context = (await req.json()) as {
|
|
10
|
+
prompt: string;
|
|
11
|
+
current_time: string;
|
|
12
|
+
existing_events?: Array<{
|
|
13
|
+
start_at: string;
|
|
14
|
+
end_at: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
priority?: string;
|
|
17
|
+
}>;
|
|
18
|
+
categories?: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
color: string;
|
|
21
|
+
}>;
|
|
22
|
+
wsId?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const supabase = await createClient();
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
data: { user },
|
|
29
|
+
} = await supabase.auth.getUser();
|
|
30
|
+
|
|
31
|
+
if (!user?.email?.endsWith('@tuturuuu.com'))
|
|
32
|
+
return new Response('Unauthorized', { status: 401 });
|
|
33
|
+
|
|
34
|
+
// Extract timezone information from context if available
|
|
35
|
+
const userTimezone = extractTimezone(context.prompt);
|
|
36
|
+
|
|
37
|
+
// Extract priority information from the prompt
|
|
38
|
+
const priority = extractPriority(context.prompt) || 'medium';
|
|
39
|
+
|
|
40
|
+
// Build the prompt based on whether smart scheduling is enabled
|
|
41
|
+
let promptText =
|
|
42
|
+
`Current time: ${context.current_time}. ` +
|
|
43
|
+
`Generate a calendar event with the provided information with proper capitalization. Create start_at and end_at in ISO format that works with JavaScript's new Date(). ` +
|
|
44
|
+
`Use local time in the user's timezone (${userTimezone}). ` +
|
|
45
|
+
`Set the priority field to "${priority}" for this event. `;
|
|
46
|
+
|
|
47
|
+
// Add category color information if available
|
|
48
|
+
if (context.categories && context.categories.length > 0) {
|
|
49
|
+
promptText += `\nIMPORTANT: The user has defined the following event categories and associated colors:\n`;
|
|
50
|
+
context.categories.forEach((category, index) => {
|
|
51
|
+
promptText += `${index + 1}. ${category.name}: ${category.color}\n`;
|
|
52
|
+
});
|
|
53
|
+
promptText += `\nIt is CRITICAL that you analyze the event content and assign the MOST RELEVANT color based on the event category. DO NOT default to blue unless it truly matches.
|
|
54
|
+
|
|
55
|
+
For example:
|
|
56
|
+
- If creating a "Team Meeting" event and "Work" category is blue, use "blue"
|
|
57
|
+
- If creating a "Doctor Appointment" event and "Health" category is red, use "red"
|
|
58
|
+
- If creating a "Family Dinner" event and "Family" category is purple, use "purple"
|
|
59
|
+
- If creating a "Gym" event and "Personal" category is green, use "green"
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
promptText += `\nContext: ${context.prompt}`;
|
|
64
|
+
|
|
65
|
+
const result = streamText({
|
|
66
|
+
model: await withAiMemory({
|
|
67
|
+
addMemory: 'never',
|
|
68
|
+
customId: `calendar-event-${Date.now()}`,
|
|
69
|
+
model: google('gemini-3.1-flash-lite'),
|
|
70
|
+
product: 'calendar',
|
|
71
|
+
source: 'calendar_event_generation',
|
|
72
|
+
surface: 'calendar_event_generation',
|
|
73
|
+
userId: user.id,
|
|
74
|
+
wsId: context.wsId ?? ROOT_WORKSPACE_ID,
|
|
75
|
+
}),
|
|
76
|
+
output: Output.object({ schema: calendarEventsSchema }),
|
|
77
|
+
prompt: promptText,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return result.toTextStreamResponse();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Helper function to extract timezone information from the context
|
|
84
|
+
function extractTimezone(context: string): string {
|
|
85
|
+
// Default to UTC if no timezone found
|
|
86
|
+
let timezone = 'UTC';
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Look for the timezone pattern in the context
|
|
90
|
+
const timezoneMatch = context.match(/User timezone:\s*([^)]+)/i);
|
|
91
|
+
if (timezoneMatch?.[1]) {
|
|
92
|
+
timezone = timezoneMatch[1].trim();
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Error extracting timezone:', error);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return timezone;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Helper function to extract priority information from the context
|
|
102
|
+
function extractPriority(context: string): string | null {
|
|
103
|
+
try {
|
|
104
|
+
// Look for the priority pattern in the context
|
|
105
|
+
const priorityMatch = context.match(/Priority:\s*(low|medium|high)/i);
|
|
106
|
+
if (priorityMatch?.[1]) {
|
|
107
|
+
return priorityMatch[1].toLowerCase().trim();
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Error extracting priority:', error);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type CreditSource = 'workspace' | 'personal';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MAX_ID_LENGTH } from '@tuturuuu/utils/constants';
|
|
2
|
+
import { generateRandomUUID } from '@tuturuuu/utils/uuid-helper';
|
|
3
|
+
import type { UIMessage } from 'ai';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
const ChatRoleSchema = z
|
|
7
|
+
.string()
|
|
8
|
+
.trim()
|
|
9
|
+
.min(1)
|
|
10
|
+
.transform((value) => value.toLowerCase())
|
|
11
|
+
.pipe(z.enum(['assistant', 'system', 'user']));
|
|
12
|
+
|
|
13
|
+
const UIMessagePartSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
type: z.string().trim().min(1),
|
|
16
|
+
})
|
|
17
|
+
.catchall(z.unknown());
|
|
18
|
+
|
|
19
|
+
const UIMessageSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
id: z.string().optional(),
|
|
22
|
+
role: ChatRoleSchema,
|
|
23
|
+
parts: z.array(UIMessagePartSchema),
|
|
24
|
+
name: z.string().optional(),
|
|
25
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
26
|
+
})
|
|
27
|
+
.passthrough();
|
|
28
|
+
|
|
29
|
+
const TaskBoardContextListSchema = z.object({
|
|
30
|
+
id: z.string().trim().min(1).max(MAX_ID_LENGTH),
|
|
31
|
+
name: z.string().trim().min(1).max(120).nullable().optional(),
|
|
32
|
+
status: z.string().trim().min(1).max(80).nullable().optional(),
|
|
33
|
+
position: z.number().nullable().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const TaskBoardContextSchema = z.object({
|
|
37
|
+
workspaceId: z.string().trim().min(1).max(MAX_ID_LENGTH).optional(),
|
|
38
|
+
workspaceName: z.string().trim().min(1).max(160).optional(),
|
|
39
|
+
boardId: z.string().trim().min(1).max(MAX_ID_LENGTH),
|
|
40
|
+
boardName: z.string().trim().min(1).max(160).optional(),
|
|
41
|
+
selectedList: TaskBoardContextListSchema.nullable().optional(),
|
|
42
|
+
lists: z.array(TaskBoardContextListSchema).max(80).default([]),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const ChatRequestBodySchema = z.object({
|
|
46
|
+
id: z.string().optional(),
|
|
47
|
+
model: z.string().optional(),
|
|
48
|
+
messages: z.array(UIMessageSchema).optional(),
|
|
49
|
+
wsId: z.string().optional(),
|
|
50
|
+
workspaceContextId: z.string().optional(),
|
|
51
|
+
isMiraMode: z.boolean().optional(),
|
|
52
|
+
timezone: z.string().optional(),
|
|
53
|
+
thinkingMode: z.enum(['thinking', 'fast']).optional(),
|
|
54
|
+
creditSource: z.enum(['personal', 'workspace']).optional(),
|
|
55
|
+
creditWsId: z.string().trim().min(1).max(MAX_ID_LENGTH).optional(),
|
|
56
|
+
observabilityContext: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
57
|
+
taskBoardContext: TaskBoardContextSchema.optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type ChatRequestBody = z.infer<typeof ChatRequestBodySchema>;
|
|
61
|
+
export type ChatRequestTaskBoardContext = z.infer<
|
|
62
|
+
typeof TaskBoardContextSchema
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
function isValidHttpUrl(value: string): boolean {
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(value);
|
|
68
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function mapToUIMessageParts(
|
|
75
|
+
parts: z.infer<typeof UIMessageSchema>['parts']
|
|
76
|
+
): UIMessage['parts'] {
|
|
77
|
+
const mappedParts: UIMessage['parts'] = [];
|
|
78
|
+
|
|
79
|
+
for (const rawPart of parts) {
|
|
80
|
+
const part = UIMessagePartSchema.parse(rawPart);
|
|
81
|
+
|
|
82
|
+
if (part.type === 'text' && typeof part.text === 'string') {
|
|
83
|
+
mappedParts.push({ type: 'text', text: part.text });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
part.type === 'dynamic-tool' &&
|
|
89
|
+
typeof part.toolName === 'string' &&
|
|
90
|
+
typeof part.toolCallId === 'string'
|
|
91
|
+
) {
|
|
92
|
+
mappedParts.push({
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: serializeToolPartForModelContext(part),
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
part.type === 'source-url' &&
|
|
101
|
+
typeof part.sourceId === 'string' &&
|
|
102
|
+
typeof part.url === 'string' &&
|
|
103
|
+
isValidHttpUrl(part.url)
|
|
104
|
+
) {
|
|
105
|
+
mappedParts.push({
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: `Source: ${
|
|
108
|
+
typeof part.title === 'string' ? `${part.title} ` : ''
|
|
109
|
+
}${part.url}`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return mappedParts;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function serializeToolPartForModelContext(part: Record<string, unknown>) {
|
|
118
|
+
const sections = [
|
|
119
|
+
`Tool result: ${part.toolName}`,
|
|
120
|
+
part.input === undefined ? null : `Input: ${safeStringify(part.input)}`,
|
|
121
|
+
part.output === undefined ? null : `Output: ${safeStringify(part.output)}`,
|
|
122
|
+
typeof part.errorText === 'string' ? `Error: ${part.errorText}` : null,
|
|
123
|
+
].filter(Boolean);
|
|
124
|
+
|
|
125
|
+
return sections.join('\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function safeStringify(value: unknown) {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.stringify(value);
|
|
131
|
+
} catch {
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function mapToUIMessages(
|
|
137
|
+
messages: ChatRequestBody['messages']
|
|
138
|
+
): UIMessage[] {
|
|
139
|
+
if (!messages) return [];
|
|
140
|
+
|
|
141
|
+
return messages.map(
|
|
142
|
+
(message): UIMessage => ({
|
|
143
|
+
id: message.id ?? generateRandomUUID(),
|
|
144
|
+
role: message.role,
|
|
145
|
+
parts: mapToUIMessageParts(message.parts),
|
|
146
|
+
...(message.name ? { name: message.name } : {}),
|
|
147
|
+
...(message.metadata ? { metadata: message.metadata } : {}),
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
}
|