@tuturuuu/ai 0.16.0 → 0.17.0
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/package.json +4 -4
- package/src/chat/google/message-file-processing.ts +6 -2
- package/src/chat/google/provider-options.ts +12 -0
- package/src/chat/google/route-message-preparation.ts +11 -2
- package/src/chat/google/route-subscription-boundary.ts +17 -0
- package/src/chat/google/route.ts +109 -69
- package/src/chat/google/stream-finish-persistence.ts +7 -4
- package/src/openai.ts +1 -0
- package/src/tools/mira-tool-discovery.ts +7 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/ai",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tutur3u/platform",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
"@streamdown/math": "^1.0.2",
|
|
77
77
|
"@streamdown/mermaid": "^1.0.2",
|
|
78
78
|
"@tuturuuu/google": "0.1.1",
|
|
79
|
-
"@tuturuuu/internal-api": "0.
|
|
79
|
+
"@tuturuuu/internal-api": "0.49.0",
|
|
80
80
|
"@tuturuuu/supabase": "0.5.2",
|
|
81
|
-
"@tuturuuu/utils": "0.
|
|
81
|
+
"@tuturuuu/utils": "0.34.0",
|
|
82
82
|
"@vercel/sandbox": "^3.3.0",
|
|
83
83
|
"@zernio/chat-sdk-adapter": "^0.5.1",
|
|
84
84
|
"ai": "^7.0.107",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"zod": "^4.6.2"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@tuturuuu/types": "0.
|
|
108
|
+
"@tuturuuu/types": "0.34.0",
|
|
109
109
|
"@tuturuuu/typescript-config": "0.1.2",
|
|
110
110
|
"@types/node": "^26.5.1",
|
|
111
111
|
"@types/qrcode": "^1.5.6",
|
|
@@ -317,9 +317,13 @@ export async function processMessagesWithFiles(
|
|
|
317
317
|
messages: ModelMessage[],
|
|
318
318
|
wsId: string,
|
|
319
319
|
chatId: string,
|
|
320
|
-
request?: Pick<Request, 'headers'
|
|
320
|
+
request?: Pick<Request, 'headers'>,
|
|
321
|
+
excludeAudioVideoFiles = false
|
|
321
322
|
): Promise<ModelMessage[]> {
|
|
322
|
-
const chatFiles = await getAllChatFiles(wsId, chatId, request)
|
|
323
|
+
const chatFiles = (await getAllChatFiles(wsId, chatId, request)).filter(
|
|
324
|
+
(file) =>
|
|
325
|
+
!excludeAudioVideoFiles || !/^(audio|video)\//.test(file.mediaType)
|
|
326
|
+
);
|
|
323
327
|
if (chatFiles.length === 0) {
|
|
324
328
|
return messages;
|
|
325
329
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createGoogleChatProviderOptions(includeThoughts: boolean) {
|
|
2
|
+
const options = {
|
|
3
|
+
thinkingConfig: { includeThoughts },
|
|
4
|
+
safetySettings: [
|
|
5
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_NONE' },
|
|
6
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_NONE' },
|
|
7
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_NONE' },
|
|
8
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_NONE' },
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
return { google: options, vertex: options };
|
|
12
|
+
}
|
|
@@ -229,7 +229,10 @@ export async function prepareProcessedMessages(
|
|
|
229
229
|
wsId: string | undefined,
|
|
230
230
|
chatId: string,
|
|
231
231
|
request?: Pick<Request, 'headers'>,
|
|
232
|
-
options?: {
|
|
232
|
+
options?: {
|
|
233
|
+
attachYoutubeVideoInput?: boolean;
|
|
234
|
+
excludeAudioVideoFiles?: boolean;
|
|
235
|
+
}
|
|
233
236
|
): Promise<{ processedMessages: ModelMessage[] } | { error: Response }> {
|
|
234
237
|
const modelMessages = await convertToModelMessages(normalizedMessages);
|
|
235
238
|
const validationError = validateModelMessages(modelMessages);
|
|
@@ -239,7 +242,13 @@ export async function prepareProcessedMessages(
|
|
|
239
242
|
|
|
240
243
|
const processedMessages =
|
|
241
244
|
wsId && chatId
|
|
242
|
-
? await processMessagesWithFiles(
|
|
245
|
+
? await processMessagesWithFiles(
|
|
246
|
+
modelMessages,
|
|
247
|
+
wsId,
|
|
248
|
+
chatId,
|
|
249
|
+
request,
|
|
250
|
+
options?.excludeAudioVideoFiles
|
|
251
|
+
)
|
|
243
252
|
: modelMessages;
|
|
244
253
|
|
|
245
254
|
return {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/** A subscription model can never enter gateway allocation or credit billing. */
|
|
4
|
+
export function validateSubscriptionRoute(
|
|
5
|
+
model: string | undefined,
|
|
6
|
+
subscription: boolean
|
|
7
|
+
) {
|
|
8
|
+
if (!subscription && model?.trim().toLowerCase().startsWith('chatgpt/')) {
|
|
9
|
+
return NextResponse.json(
|
|
10
|
+
{
|
|
11
|
+
error: 'Use the ChatGPT subscription endpoint for this model',
|
|
12
|
+
code: 'CHATGPT_ROUTE_REQUIRED',
|
|
13
|
+
},
|
|
14
|
+
{ status: 400 }
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/chat/google/route.ts
CHANGED
|
@@ -4,7 +4,13 @@ import {
|
|
|
4
4
|
normalizeWorkspaceId,
|
|
5
5
|
verifyWorkspaceMembershipType,
|
|
6
6
|
} from '@tuturuuu/utils/workspace-helper';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
consumeStream,
|
|
9
|
+
type LanguageModel,
|
|
10
|
+
smoothStream,
|
|
11
|
+
stepCountIs,
|
|
12
|
+
streamText,
|
|
13
|
+
} from 'ai';
|
|
8
14
|
import { type NextRequest, NextResponse } from 'next/server';
|
|
9
15
|
import { normalizeStableModelId } from '../../credits/model-mapping';
|
|
10
16
|
import {
|
|
@@ -26,6 +32,7 @@ import { ChatRequestBodySchema, mapToUIMessages } from './chat-request-schema';
|
|
|
26
32
|
import { resolveChatTools } from './chat-tools';
|
|
27
33
|
import { systemInstruction } from './default-system-instruction';
|
|
28
34
|
import { prepareMiraToolStep } from './mira-step-preparation';
|
|
35
|
+
import { createGoogleChatProviderOptions } from './provider-options';
|
|
29
36
|
import { resolveChatReasoningSettings } from './reasoning-settings';
|
|
30
37
|
import {
|
|
31
38
|
beginAiPersistenceRequest,
|
|
@@ -52,6 +59,7 @@ import {
|
|
|
52
59
|
splitSystemMessages,
|
|
53
60
|
} from './route-message-preparation';
|
|
54
61
|
import { prepareMiraRuntime } from './route-mira-runtime';
|
|
62
|
+
import { validateSubscriptionRoute } from './route-subscription-boundary';
|
|
55
63
|
import {
|
|
56
64
|
buildAbortedStreamFinishResponse,
|
|
57
65
|
persistAssistantResponse,
|
|
@@ -60,6 +68,19 @@ import {
|
|
|
60
68
|
export function createPOST(
|
|
61
69
|
_options: {
|
|
62
70
|
serverAPIKeyFallback?: boolean;
|
|
71
|
+
/** Direct user-funded chat. It bypasses gateway memory, credits, and provider tools. */
|
|
72
|
+
subscription?: {
|
|
73
|
+
resolveModel: (
|
|
74
|
+
userId: string,
|
|
75
|
+
model: string | undefined
|
|
76
|
+
) => Promise<LanguageModel>;
|
|
77
|
+
onError: () => string;
|
|
78
|
+
errorResponse?: (error: unknown) => {
|
|
79
|
+
message: string;
|
|
80
|
+
status: number;
|
|
81
|
+
code: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
63
84
|
/** Gateway provider prefix for bare model names (e.g., 'openai', 'anthropic', 'vertex'). Defaults to 'google'. */
|
|
64
85
|
defaultProvider?: string;
|
|
65
86
|
requireWorkspaceId?: boolean;
|
|
@@ -104,7 +125,7 @@ export function createPOST(
|
|
|
104
125
|
messages,
|
|
105
126
|
wsId,
|
|
106
127
|
workspaceContextId,
|
|
107
|
-
isMiraMode,
|
|
128
|
+
isMiraMode: requestedMiraMode,
|
|
108
129
|
timezone,
|
|
109
130
|
thinkingMode: rawThinkingMode,
|
|
110
131
|
creditSource: requestedCreditSourceRaw,
|
|
@@ -113,6 +134,12 @@ export function createPOST(
|
|
|
113
134
|
persistenceRequestId,
|
|
114
135
|
taskBoardContext,
|
|
115
136
|
} = parsedBody.data;
|
|
137
|
+
const isMiraMode = _options.subscription ? false : requestedMiraMode;
|
|
138
|
+
const subscriptionError = validateSubscriptionRoute(
|
|
139
|
+
model,
|
|
140
|
+
!!_options.subscription
|
|
141
|
+
);
|
|
142
|
+
if (subscriptionError) return subscriptionError;
|
|
116
143
|
const thinkingMode = rawThinkingMode === 'thinking' ? 'thinking' : 'fast';
|
|
117
144
|
|
|
118
145
|
if (!messages) {
|
|
@@ -124,6 +151,24 @@ export function createPOST(
|
|
|
124
151
|
(await _options.resolveAuth?.(req)) ?? (await resolveAiRouteAuth(req));
|
|
125
152
|
if (!auth.ok) return auth.response;
|
|
126
153
|
const { messageInsertMode = 'rpc', supabase, user } = auth;
|
|
154
|
+
let subscriptionModel: LanguageModel | undefined;
|
|
155
|
+
if (_options.subscription) {
|
|
156
|
+
try {
|
|
157
|
+
subscriptionModel = await _options.subscription.resolveModel(
|
|
158
|
+
user.id,
|
|
159
|
+
model
|
|
160
|
+
);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const failure = _options.subscription.errorResponse?.(error);
|
|
163
|
+
return NextResponse.json(
|
|
164
|
+
{
|
|
165
|
+
error: failure?.message ?? _options.subscription.onError(),
|
|
166
|
+
code: failure?.code ?? 'CHATGPT_CONNECTION_REQUIRED',
|
|
167
|
+
},
|
|
168
|
+
{ status: failure?.status ?? 403 }
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
127
172
|
|
|
128
173
|
if (isMiraMode && !(await isInternalTuturuuuAiUser(auth))) {
|
|
129
174
|
return NextResponse.json(
|
|
@@ -174,7 +219,7 @@ export function createPOST(
|
|
|
174
219
|
requestedCreditSourceRaw ?? 'workspace';
|
|
175
220
|
let billingWsId: string | null = normalizedWsId ?? null;
|
|
176
221
|
|
|
177
|
-
if (requestedCreditSource === 'personal') {
|
|
222
|
+
if (!_options.subscription && requestedCreditSource === 'personal') {
|
|
178
223
|
const { data: personalWorkspace, error: personalWorkspaceError } =
|
|
179
224
|
await sbAdmin
|
|
180
225
|
.from('workspaces')
|
|
@@ -220,7 +265,7 @@ export function createPOST(
|
|
|
220
265
|
}
|
|
221
266
|
|
|
222
267
|
billingWsId = personalWorkspace.id;
|
|
223
|
-
} else if (requestedCreditWsId) {
|
|
268
|
+
} else if (!_options.subscription && requestedCreditWsId) {
|
|
224
269
|
if (normalizedWsId && requestedCreditWsId !== normalizedWsId) {
|
|
225
270
|
return NextResponse.json(
|
|
226
271
|
{
|
|
@@ -263,7 +308,9 @@ export function createPOST(
|
|
|
263
308
|
|
|
264
309
|
let resolvedModelId: string;
|
|
265
310
|
try {
|
|
266
|
-
if (
|
|
311
|
+
if (subscriptionModel && model) {
|
|
312
|
+
resolvedModelId = model;
|
|
313
|
+
} else if (billingWsId) {
|
|
267
314
|
const resolvedPlanModel = await resolvePlanModel({
|
|
268
315
|
capability: 'language',
|
|
269
316
|
requestedModel: model,
|
|
@@ -346,6 +393,7 @@ export function createPOST(
|
|
|
346
393
|
chatId,
|
|
347
394
|
req,
|
|
348
395
|
{
|
|
396
|
+
excludeAudioVideoFiles: !!subscriptionModel,
|
|
349
397
|
attachYoutubeVideoInput:
|
|
350
398
|
resolvedModelId.toLowerCase().startsWith('google/') ||
|
|
351
399
|
resolvedModelId.toLowerCase().startsWith('google-vertex/'),
|
|
@@ -355,6 +403,26 @@ export function createPOST(
|
|
|
355
403
|
return preparedMessages.error;
|
|
356
404
|
}
|
|
357
405
|
const { processedMessages } = preparedMessages;
|
|
406
|
+
if (
|
|
407
|
+
subscriptionModel &&
|
|
408
|
+
processedMessages.some(
|
|
409
|
+
(message) =>
|
|
410
|
+
Array.isArray(message.content) &&
|
|
411
|
+
message.content.some(
|
|
412
|
+
(part) =>
|
|
413
|
+
part.type === 'file' && /^(audio|video)\//.test(part.mediaType)
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
) {
|
|
417
|
+
return NextResponse.json(
|
|
418
|
+
{
|
|
419
|
+
error:
|
|
420
|
+
'ChatGPT plan sharing does not support audio or video inputs',
|
|
421
|
+
},
|
|
422
|
+
{ status: 400 }
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
358
426
|
const promptMessages = splitSystemMessages(processedMessages);
|
|
359
427
|
|
|
360
428
|
const persistUserMessageError = await persistLatestUserMessage({
|
|
@@ -420,12 +488,14 @@ export function createPOST(
|
|
|
420
488
|
return persistUserMessageError;
|
|
421
489
|
}
|
|
422
490
|
|
|
423
|
-
const creditPreflight =
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
491
|
+
const creditPreflight = subscriptionModel
|
|
492
|
+
? { cappedMaxOutput: null }
|
|
493
|
+
: await performCreditPreflight({
|
|
494
|
+
wsId: billingWsId ?? normalizedWsId ?? undefined,
|
|
495
|
+
model: resolvedModelId,
|
|
496
|
+
userId: user.id,
|
|
497
|
+
sbAdmin,
|
|
498
|
+
});
|
|
429
499
|
if ('error' in creditPreflight) {
|
|
430
500
|
return creditPreflight.error;
|
|
431
501
|
}
|
|
@@ -452,15 +522,17 @@ export function createPOST(
|
|
|
452
522
|
|
|
453
523
|
const effectiveSource = isMiraMode ? 'Mira' : 'Rewise';
|
|
454
524
|
|
|
455
|
-
const resolvedGatewayModel =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
525
|
+
const resolvedGatewayModel =
|
|
526
|
+
subscriptionModel ??
|
|
527
|
+
(await withAiMemory({
|
|
528
|
+
customId: chatId,
|
|
529
|
+
model: google(resolvedModelId.split('/').slice(-1)[0]!),
|
|
530
|
+
product: isMiraMode ? 'mira' : 'rewise',
|
|
531
|
+
source: effectiveSource,
|
|
532
|
+
surface: 'shared_chat',
|
|
533
|
+
userId: user.id,
|
|
534
|
+
wsId: billingWsId ?? normalizedWsId,
|
|
535
|
+
}));
|
|
464
536
|
|
|
465
537
|
const reasoningSettings = resolveChatReasoningSettings(thinkingMode);
|
|
466
538
|
const forceWorkspaceArtifact = shouldPresentWorkspaceArtifact(
|
|
@@ -540,6 +612,7 @@ export function createPOST(
|
|
|
540
612
|
effectiveSource,
|
|
541
613
|
wsId: billingWsId ?? normalizedWsId ?? undefined,
|
|
542
614
|
observabilityContext,
|
|
615
|
+
skipCreditDeduction: !!subscriptionModel,
|
|
543
616
|
persistenceRequestId,
|
|
544
617
|
}),
|
|
545
618
|
});
|
|
@@ -556,7 +629,7 @@ export function createPOST(
|
|
|
556
629
|
promptMessages.system
|
|
557
630
|
),
|
|
558
631
|
...(cappedMaxOutput ? { maxOutputTokens: cappedMaxOutput } : {}),
|
|
559
|
-
tools: resolveChatTools(miraTools),
|
|
632
|
+
tools: subscriptionModel ? undefined : resolveChatTools(miraTools),
|
|
560
633
|
...(miraTools
|
|
561
634
|
? {
|
|
562
635
|
stopWhen: stepCountIs(25),
|
|
@@ -566,54 +639,9 @@ export function createPOST(
|
|
|
566
639
|
>,
|
|
567
640
|
}
|
|
568
641
|
: {}),
|
|
569
|
-
providerOptions:
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
includeThoughts: reasoningSettings.includeThoughts,
|
|
573
|
-
},
|
|
574
|
-
safetySettings: [
|
|
575
|
-
{
|
|
576
|
-
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
|
577
|
-
threshold: 'BLOCK_NONE',
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
category: 'HARM_CATEGORY_HATE_SPEECH',
|
|
581
|
-
threshold: 'BLOCK_NONE',
|
|
582
|
-
},
|
|
583
|
-
{
|
|
584
|
-
category: 'HARM_CATEGORY_HARASSMENT',
|
|
585
|
-
threshold: 'BLOCK_NONE',
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
589
|
-
threshold: 'BLOCK_NONE',
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
},
|
|
593
|
-
vertex: {
|
|
594
|
-
thinkingConfig: {
|
|
595
|
-
includeThoughts: reasoningSettings.includeThoughts,
|
|
596
|
-
},
|
|
597
|
-
safetySettings: [
|
|
598
|
-
{
|
|
599
|
-
category: 'HARM_CATEGORY_HARASSMENT',
|
|
600
|
-
threshold: 'BLOCK_NONE',
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
category: 'HARM_CATEGORY_HATE_SPEECH',
|
|
604
|
-
threshold: 'BLOCK_NONE',
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
608
|
-
threshold: 'BLOCK_NONE',
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
|
612
|
-
threshold: 'BLOCK_NONE',
|
|
613
|
-
},
|
|
614
|
-
],
|
|
615
|
-
},
|
|
616
|
-
},
|
|
642
|
+
providerOptions: subscriptionModel
|
|
643
|
+
? undefined
|
|
644
|
+
: createGoogleChatProviderOptions(reasoningSettings.includeThoughts),
|
|
617
645
|
onAbort: async ({ steps }) =>
|
|
618
646
|
persistChatAssistantResponse(buildAbortedStreamFinishResponse(steps)),
|
|
619
647
|
onFinish: persistChatAssistantResponse,
|
|
@@ -631,6 +659,9 @@ export function createPOST(
|
|
|
631
659
|
consumeSseStream: consumeStream,
|
|
632
660
|
sendReasoning: true,
|
|
633
661
|
sendSources: true,
|
|
662
|
+
...(_options.subscription
|
|
663
|
+
? { onError: _options.subscription.onError }
|
|
664
|
+
: {}),
|
|
634
665
|
});
|
|
635
666
|
} catch (error) {
|
|
636
667
|
if (releaseClaimedPersistenceLease) {
|
|
@@ -642,6 +673,15 @@ export function createPOST(
|
|
|
642
673
|
});
|
|
643
674
|
}
|
|
644
675
|
}
|
|
676
|
+
if (_options.subscription) {
|
|
677
|
+
console.error('ChatGPT request failed', {
|
|
678
|
+
errorType: error instanceof Error ? error.name : 'UnknownError',
|
|
679
|
+
});
|
|
680
|
+
return NextResponse.json(
|
|
681
|
+
{ error: _options.subscription.onError() },
|
|
682
|
+
{ status: 502 }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
645
685
|
if (error instanceof Error) {
|
|
646
686
|
console.log(error.message);
|
|
647
687
|
return NextResponse.json(
|
|
@@ -109,6 +109,7 @@ type PersistAssistantResponseParams = {
|
|
|
109
109
|
observabilityContext?: unknown;
|
|
110
110
|
persistenceRequestId?: string;
|
|
111
111
|
wsId?: string;
|
|
112
|
+
skipCreditDeduction?: boolean;
|
|
112
113
|
};
|
|
113
114
|
|
|
114
115
|
function collectToolData(steps: StepLike[]) {
|
|
@@ -469,7 +470,9 @@ export async function persistAssistantResponse({
|
|
|
469
470
|
observabilityContext,
|
|
470
471
|
persistenceRequestId,
|
|
471
472
|
wsId,
|
|
473
|
+
skipCreditDeduction = false,
|
|
472
474
|
}: PersistAssistantResponseParams): Promise<boolean> {
|
|
475
|
+
if (skipCreditDeduction && response.finishReason !== 'stop') return false;
|
|
473
476
|
const steps = response.steps ?? [];
|
|
474
477
|
const { allToolCalls, allToolResults } = collectToolData(steps);
|
|
475
478
|
|
|
@@ -517,10 +520,9 @@ export async function persistAssistantResponse({
|
|
|
517
520
|
.map((part) => part.text)
|
|
518
521
|
.join('\n\n'),
|
|
519
522
|
role: 'ASSISTANT',
|
|
520
|
-
model:
|
|
521
|
-
? model
|
|
522
|
-
: model
|
|
523
|
-
).toLowerCase(),
|
|
523
|
+
model: skipCreditDeduction
|
|
524
|
+
? model
|
|
525
|
+
: (model.includes('/') ? model.split('/').pop()! : model).toLowerCase(),
|
|
524
526
|
finish_reason: response.finishReason,
|
|
525
527
|
prompt_tokens: inputTokens,
|
|
526
528
|
completion_tokens: outputTokens,
|
|
@@ -563,6 +565,7 @@ export async function persistAssistantResponse({
|
|
|
563
565
|
}
|
|
564
566
|
|
|
565
567
|
if (
|
|
568
|
+
!skipCreditDeduction &&
|
|
566
569
|
(wsId || userId) &&
|
|
567
570
|
(inputTokens > 0 ||
|
|
568
571
|
outputTokens > 0 ||
|
package/src/openai.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createOpenAI } from '@ai-sdk/openai';
|
|
@@ -83,6 +83,12 @@ export function searchMiraTools(
|
|
|
83
83
|
.some((word) => word === term || term === `${word}s`)
|
|
84
84
|
)
|
|
85
85
|
);
|
|
86
|
+
const creatingTasks =
|
|
87
|
+
/\b(?:add|create|make)\b.*\btasks?\b|\bnew tasks?\b/.test(query) &&
|
|
88
|
+
!/\b(?:label|assignee|project)\b|\btasks?\s+lists?\b/.test(query);
|
|
89
|
+
const taskCreationPrerequisiteScore: Record<string, number> = creatingTasks
|
|
90
|
+
? { create_task: 30, list_boards: 28, list_task_lists: 26 }
|
|
91
|
+
: {};
|
|
86
92
|
const matches = (
|
|
87
93
|
Object.entries(MIRA_TOOL_DIRECTORY) as [MiraToolName, string][]
|
|
88
94
|
)
|
|
@@ -99,6 +105,7 @@ export function searchMiraTools(
|
|
|
99
105
|
const nameWords = normalize(name).split(' ');
|
|
100
106
|
const score =
|
|
101
107
|
(normalize(name) === query ? 100 : 0) +
|
|
108
|
+
(taskCreationPrerequisiteScore[name] ?? 0) +
|
|
102
109
|
terms.reduce(
|
|
103
110
|
(total, term) =>
|
|
104
111
|
total +
|