@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@tuturuuu/supabase';
|
|
2
|
+
import { resolveGatewayModelId } from './model-mapping';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Query the gateway model's output price per token from the database.
|
|
6
|
+
* Returns the flat output_price_per_token. For tiered pricing, returns
|
|
7
|
+
* the lowest tier cost as a conservative estimate.
|
|
8
|
+
*/
|
|
9
|
+
async function getOutputPricePerToken(
|
|
10
|
+
sbAdmin: SupabaseClient,
|
|
11
|
+
modelId: string
|
|
12
|
+
): Promise<number | null> {
|
|
13
|
+
const gatewayId = resolveGatewayModelId(modelId);
|
|
14
|
+
const privateDb = sbAdmin.schema('private');
|
|
15
|
+
|
|
16
|
+
const { data, error } = await privateDb
|
|
17
|
+
.from('ai_gateway_models')
|
|
18
|
+
.select('output_price_per_token, output_tiers')
|
|
19
|
+
.or(`id.eq.${gatewayId},id.eq.google/${modelId}`)
|
|
20
|
+
.eq('is_enabled', true)
|
|
21
|
+
.limit(1)
|
|
22
|
+
.maybeSingle();
|
|
23
|
+
|
|
24
|
+
if (error || !data) return null;
|
|
25
|
+
|
|
26
|
+
// If tiered pricing exists, use the first (lowest) tier cost
|
|
27
|
+
if (
|
|
28
|
+
data.output_tiers &&
|
|
29
|
+
Array.isArray(data.output_tiers) &&
|
|
30
|
+
data.output_tiers.length > 0
|
|
31
|
+
) {
|
|
32
|
+
const firstTier = data.output_tiers[0] as { cost?: string };
|
|
33
|
+
if (firstTier?.cost) {
|
|
34
|
+
return parseFloat(firstTier.cost);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return data.output_price_per_token ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Application-level safety cap: ensure maxOutputTokens won't exceed
|
|
43
|
+
* what the user can afford based on remaining credits and real gateway pricing.
|
|
44
|
+
*
|
|
45
|
+
* This is a defense-in-depth layer — the database function
|
|
46
|
+
* `check_ai_credit_allowance` applies the same cap, but this catches
|
|
47
|
+
* cases where the DB function is stale or fails open.
|
|
48
|
+
*
|
|
49
|
+
* @returns capped maxOutputTokens, or `null` if no output can be afforded
|
|
50
|
+
*/
|
|
51
|
+
export async function capMaxOutputTokensByCredits(
|
|
52
|
+
sbAdmin: SupabaseClient,
|
|
53
|
+
modelId: string,
|
|
54
|
+
maxOutputTokens: number | null,
|
|
55
|
+
remainingCredits: number,
|
|
56
|
+
markupMultiplier = 1.0
|
|
57
|
+
): Promise<number | null> {
|
|
58
|
+
if (remainingCredits <= 0) return null;
|
|
59
|
+
|
|
60
|
+
const outputPricePerToken = await getOutputPricePerToken(sbAdmin, modelId);
|
|
61
|
+
if (!outputPricePerToken || outputPricePerToken <= 0) {
|
|
62
|
+
// Can't determine pricing — fall through to DB-layer cap
|
|
63
|
+
return maxOutputTokens;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// credits = (tokens * pricePerToken / 0.0001) * markup
|
|
67
|
+
// → tokens = (credits * 0.0001 / markup) / pricePerToken
|
|
68
|
+
const affordableTokens = Math.floor(
|
|
69
|
+
(remainingCredits * 0.0001) / markupMultiplier / outputPricePerToken
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (affordableTokens < 1) return null;
|
|
73
|
+
if (!maxOutputTokens) return affordableTokens;
|
|
74
|
+
return Math.min(maxOutputTokens, affordableTokens);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Pure computation version for unit testing (no DB dependency).
|
|
79
|
+
* Same formula as the async version, but takes price directly.
|
|
80
|
+
*/
|
|
81
|
+
export function computeAffordableTokens(
|
|
82
|
+
remainingCredits: number,
|
|
83
|
+
outputPricePerToken: number,
|
|
84
|
+
markupMultiplier = 1.0
|
|
85
|
+
): number {
|
|
86
|
+
if (remainingCredits <= 0 || outputPricePerToken <= 0) return 0;
|
|
87
|
+
return Math.floor(
|
|
88
|
+
(remainingCredits * 0.0001) / markupMultiplier / outputPricePerToken
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiFeature,
|
|
3
|
+
CreditErrorCode,
|
|
4
|
+
} from '@tuturuuu/ai/credits/constants';
|
|
5
|
+
import {
|
|
6
|
+
matchesAllowedModel,
|
|
7
|
+
resolveGatewayModelId,
|
|
8
|
+
} from '@tuturuuu/ai/credits/model-mapping';
|
|
9
|
+
import type {
|
|
10
|
+
CreditCheckResult,
|
|
11
|
+
CreditDeductionResult,
|
|
12
|
+
DeductCreditsParams,
|
|
13
|
+
} from '@tuturuuu/ai/credits/types';
|
|
14
|
+
import { createAdminClient } from '@tuturuuu/supabase/next/server';
|
|
15
|
+
import {
|
|
16
|
+
decrementAiCreditChargeInFlight,
|
|
17
|
+
hasAiCreditChargeInFlight,
|
|
18
|
+
incrementAiCreditChargeInFlight,
|
|
19
|
+
invalidateAiCreditSnapshot,
|
|
20
|
+
isAiCreditSnapshotUsable,
|
|
21
|
+
readAiCreditSnapshot,
|
|
22
|
+
} from '@tuturuuu/utils/ai-temp-auth';
|
|
23
|
+
|
|
24
|
+
type DeductAiCreditsRpcRow = {
|
|
25
|
+
success?: boolean;
|
|
26
|
+
credits_deducted?: number | string;
|
|
27
|
+
remaining_credits?: number | string;
|
|
28
|
+
error_code?: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type RpcError = { message?: string } | null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pre-flight check: can this workspace use AI credits for the given model/feature?
|
|
35
|
+
* Returns allowance info including remaining credits and effective maxOutputTokens.
|
|
36
|
+
*/
|
|
37
|
+
export async function checkAiCredits(
|
|
38
|
+
wsId: string | undefined,
|
|
39
|
+
modelId: string,
|
|
40
|
+
feature: AiFeature,
|
|
41
|
+
opts?: { userId?: string; estimatedInputTokens?: number }
|
|
42
|
+
): Promise<CreditCheckResult> {
|
|
43
|
+
if (!wsId) {
|
|
44
|
+
return {
|
|
45
|
+
allowed: false,
|
|
46
|
+
remainingCredits: 0,
|
|
47
|
+
tier: 'FREE',
|
|
48
|
+
maxOutputTokens: null,
|
|
49
|
+
errorCode: 'CREDIT_CHECK_FAILED',
|
|
50
|
+
errorMessage: 'Workspace ID is missing.',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const gatewayModelId = resolveGatewayModelId(modelId);
|
|
55
|
+
|
|
56
|
+
if (opts?.userId) {
|
|
57
|
+
const snapshot = await readAiCreditSnapshot({
|
|
58
|
+
wsId,
|
|
59
|
+
userId: opts.userId,
|
|
60
|
+
});
|
|
61
|
+
const inFlight = await hasAiCreditChargeInFlight({
|
|
62
|
+
wsId,
|
|
63
|
+
userId: opts.userId,
|
|
64
|
+
});
|
|
65
|
+
if (
|
|
66
|
+
isAiCreditSnapshotUsable(snapshot, { inFlight }) &&
|
|
67
|
+
(snapshot.allowedFeatures.length === 0 ||
|
|
68
|
+
snapshot.allowedFeatures.includes(feature)) &&
|
|
69
|
+
matchesAllowedModel(gatewayModelId, snapshot.allowedModels)
|
|
70
|
+
) {
|
|
71
|
+
return {
|
|
72
|
+
allowed: snapshot.remainingCredits > 0,
|
|
73
|
+
remainingCredits: snapshot.remainingCredits,
|
|
74
|
+
tier: snapshot.tier,
|
|
75
|
+
maxOutputTokens: snapshot.maxOutputTokens,
|
|
76
|
+
errorCode: snapshot.remainingCredits > 0 ? null : 'CREDITS_EXHAUSTED',
|
|
77
|
+
errorMessage:
|
|
78
|
+
snapshot.remainingCredits > 0
|
|
79
|
+
? null
|
|
80
|
+
: 'AI credits exhausted. Please upgrade your plan or purchase more credits.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sbAdmin = await createAdminClient();
|
|
86
|
+
const { data, error } = await sbAdmin.rpc('check_ai_credit_allowance', {
|
|
87
|
+
p_ws_id: wsId,
|
|
88
|
+
p_model_id: gatewayModelId,
|
|
89
|
+
p_feature: feature,
|
|
90
|
+
...(opts?.estimatedInputTokens != null
|
|
91
|
+
? { p_estimated_input_tokens: opts.estimatedInputTokens }
|
|
92
|
+
: {}),
|
|
93
|
+
...(opts?.userId ? { p_user_id: opts.userId } : {}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (error) {
|
|
97
|
+
console.error('Error checking AI credits:', error);
|
|
98
|
+
return {
|
|
99
|
+
allowed: false,
|
|
100
|
+
remainingCredits: 0,
|
|
101
|
+
tier: 'FREE',
|
|
102
|
+
maxOutputTokens: null,
|
|
103
|
+
errorCode: 'CREDIT_CHECK_FAILED',
|
|
104
|
+
errorMessage: 'AI credit check failed. Please try again.',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
109
|
+
if (!row) {
|
|
110
|
+
return {
|
|
111
|
+
allowed: false,
|
|
112
|
+
remainingCredits: 0,
|
|
113
|
+
tier: 'FREE',
|
|
114
|
+
maxOutputTokens: null,
|
|
115
|
+
errorCode: 'CREDIT_CHECK_FAILED',
|
|
116
|
+
errorMessage: 'AI credit check returned no result. Please try again.',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
allowed: row.allowed ?? true,
|
|
122
|
+
remainingCredits: Number(row.remaining_credits ?? 0),
|
|
123
|
+
tier: row.tier ?? 'FREE',
|
|
124
|
+
maxOutputTokens: row.max_output_tokens ?? null,
|
|
125
|
+
errorCode: (row.error_code as CreditErrorCode) ?? null,
|
|
126
|
+
errorMessage: row.error_message ?? null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Deduct credits after a successful AI execution.
|
|
132
|
+
* Should be called in the `onFinish` callback or after `generateObject` completes.
|
|
133
|
+
*/
|
|
134
|
+
export async function deductAiCredits(
|
|
135
|
+
params: DeductCreditsParams
|
|
136
|
+
): Promise<CreditDeductionResult> {
|
|
137
|
+
if (!params.wsId) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
creditsDeducted: 0,
|
|
141
|
+
remainingCredits: 0,
|
|
142
|
+
errorCode: 'DEDUCTION_FAILED',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const wsId = params.wsId;
|
|
147
|
+
const sbAdmin = await createAdminClient();
|
|
148
|
+
const gatewayModelId = resolveGatewayModelId(params.modelId);
|
|
149
|
+
let inFlightMarked = false;
|
|
150
|
+
|
|
151
|
+
if (params.userId) {
|
|
152
|
+
inFlightMarked = await incrementAiCreditChargeInFlight({
|
|
153
|
+
wsId,
|
|
154
|
+
userId: params.userId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let data: DeductAiCreditsRpcRow[] | DeductAiCreditsRpcRow | null = null;
|
|
159
|
+
let error: RpcError = null;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const response = await sbAdmin.rpc('deduct_ai_credits', {
|
|
163
|
+
p_ws_id: wsId,
|
|
164
|
+
p_model_id: gatewayModelId,
|
|
165
|
+
p_input_tokens: params.inputTokens,
|
|
166
|
+
p_output_tokens: params.outputTokens,
|
|
167
|
+
p_reasoning_tokens: params.reasoningTokens ?? 0,
|
|
168
|
+
p_feature: params.feature,
|
|
169
|
+
...(params.executionId ? { p_execution_id: params.executionId } : {}),
|
|
170
|
+
...(params.chatMessageId
|
|
171
|
+
? { p_chat_message_id: params.chatMessageId }
|
|
172
|
+
: {}),
|
|
173
|
+
...(params.metadata
|
|
174
|
+
? {
|
|
175
|
+
p_metadata:
|
|
176
|
+
params.metadata as unknown as import('@tuturuuu/types').Json,
|
|
177
|
+
}
|
|
178
|
+
: {}),
|
|
179
|
+
...(params.userId ? { p_user_id: params.userId } : {}),
|
|
180
|
+
...(params.imageCount ? { p_image_count: params.imageCount } : {}),
|
|
181
|
+
...(params.searchCount ? { p_search_count: params.searchCount } : {}),
|
|
182
|
+
});
|
|
183
|
+
data = response.data as
|
|
184
|
+
| DeductAiCreditsRpcRow[]
|
|
185
|
+
| DeductAiCreditsRpcRow
|
|
186
|
+
| null;
|
|
187
|
+
error = response.error as RpcError;
|
|
188
|
+
} finally {
|
|
189
|
+
if (inFlightMarked && params.userId) {
|
|
190
|
+
await decrementAiCreditChargeInFlight({
|
|
191
|
+
wsId,
|
|
192
|
+
userId: params.userId,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (error) {
|
|
198
|
+
console.error('Error deducting AI credits:', error);
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
creditsDeducted: 0,
|
|
202
|
+
remainingCredits: 0,
|
|
203
|
+
errorCode: 'DEDUCTION_FAILED',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
208
|
+
if (!row) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
creditsDeducted: 0,
|
|
212
|
+
remainingCredits: 0,
|
|
213
|
+
errorCode: 'NO_RESULT',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const result = {
|
|
218
|
+
success: row.success ?? false,
|
|
219
|
+
creditsDeducted: Number(row.credits_deducted ?? 0),
|
|
220
|
+
remainingCredits: Number(row.remaining_credits ?? 0),
|
|
221
|
+
errorCode: row.error_code ?? null,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (result.success && params.wsId && params.userId) {
|
|
225
|
+
await invalidateAiCreditSnapshot({
|
|
226
|
+
wsId,
|
|
227
|
+
userId: params.userId,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** 1 credit = this many USD */
|
|
2
|
+
export const CREDIT_UNIT_USD = 0.0001;
|
|
3
|
+
|
|
4
|
+
/** AI feature slugs used throughout the credit system */
|
|
5
|
+
export const AI_FEATURES = [
|
|
6
|
+
'chat',
|
|
7
|
+
'generate',
|
|
8
|
+
'task_journal',
|
|
9
|
+
'email_draft',
|
|
10
|
+
'image_generation',
|
|
11
|
+
'embeddings',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type AiFeature = (typeof AI_FEATURES)[number];
|
|
15
|
+
|
|
16
|
+
/** Error codes returned by credit checks */
|
|
17
|
+
export const CREDIT_ERROR_CODES = {
|
|
18
|
+
CREDITS_EXHAUSTED: 'CREDITS_EXHAUSTED',
|
|
19
|
+
MODEL_NOT_ALLOWED: 'MODEL_NOT_ALLOWED',
|
|
20
|
+
MODEL_DISABLED: 'MODEL_DISABLED',
|
|
21
|
+
MODEL_PRICING_UNAVAILABLE: 'MODEL_PRICING_UNAVAILABLE',
|
|
22
|
+
FEATURE_NOT_ALLOWED: 'FEATURE_NOT_ALLOWED',
|
|
23
|
+
DAILY_LIMIT_REACHED: 'DAILY_LIMIT_REACHED',
|
|
24
|
+
NO_ALLOCATION: 'NO_ALLOCATION',
|
|
25
|
+
NO_BALANCE: 'NO_BALANCE',
|
|
26
|
+
CREDIT_CHECK_FAILED: 'CREDIT_CHECK_FAILED',
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export type CreditErrorCode =
|
|
30
|
+
(typeof CREDIT_ERROR_CODES)[keyof typeof CREDIT_ERROR_CODES];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export {
|
|
2
|
+
capMaxOutputTokensByCredits,
|
|
3
|
+
computeAffordableTokens,
|
|
4
|
+
} from './cap-output-tokens';
|
|
5
|
+
export { checkAiCredits, deductAiCredits } from './check-credits';
|
|
6
|
+
export {
|
|
7
|
+
AI_FEATURES,
|
|
8
|
+
type AiFeature,
|
|
9
|
+
CREDIT_ERROR_CODES,
|
|
10
|
+
CREDIT_UNIT_USD,
|
|
11
|
+
type CreditErrorCode,
|
|
12
|
+
} from './constants';
|
|
13
|
+
export {
|
|
14
|
+
matchesAllowedModel,
|
|
15
|
+
resolveGatewayModelId,
|
|
16
|
+
toBareModelName,
|
|
17
|
+
toGatewayModelId,
|
|
18
|
+
} from './model-mapping';
|
|
19
|
+
export {
|
|
20
|
+
commitFixedAiCreditReservation,
|
|
21
|
+
commitMeteredEmbeddingCredits,
|
|
22
|
+
releaseFixedAiCreditReservation,
|
|
23
|
+
releaseMeteredEmbeddingCredits,
|
|
24
|
+
reserveFixedAiCredits,
|
|
25
|
+
reserveMeteredEmbeddingCredits,
|
|
26
|
+
} from './reservations';
|
|
27
|
+
export {
|
|
28
|
+
type EffectivePlanModel,
|
|
29
|
+
type PlanModelCapability,
|
|
30
|
+
PlanModelResolutionError,
|
|
31
|
+
resolvePlanModel,
|
|
32
|
+
selectEffectivePlanModel,
|
|
33
|
+
} from './resolve-plan-model';
|
|
34
|
+
export { syncGatewayModels } from './sync-gateway-models';
|
|
35
|
+
export type {
|
|
36
|
+
AiCreditStatus,
|
|
37
|
+
CreditAllocation,
|
|
38
|
+
CreditCheckResult,
|
|
39
|
+
CreditDeductionResult,
|
|
40
|
+
CreditReservationCommitResult,
|
|
41
|
+
CreditReservationReleaseResult,
|
|
42
|
+
CreditReservationResult,
|
|
43
|
+
DeductCreditsParams,
|
|
44
|
+
FeatureAccess,
|
|
45
|
+
MeteredEmbeddingReservationResult,
|
|
46
|
+
} from './types';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps bare model names used in existing code to gateway model IDs.
|
|
3
|
+
* The gateway uses `provider/model-name` format (e.g., `google/gemini-2.5-flash`),
|
|
4
|
+
* while existing code uses bare names (e.g., `gemini-2.5-flash`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const GEMINI_31_FLASH_LITE_MODEL = 'gemini-3.1-flash-lite';
|
|
8
|
+
export const GEMINI_31_FLASH_LITE_GATEWAY_MODEL =
|
|
9
|
+
'google/gemini-3.1-flash-lite';
|
|
10
|
+
export const GEMINI_31_FLASH_LITE_PREVIEW_MODEL =
|
|
11
|
+
'gemini-3.1-flash-lite-preview';
|
|
12
|
+
export const GEMINI_31_FLASH_LITE_PREVIEW_GATEWAY_MODEL =
|
|
13
|
+
'google/gemini-3.1-flash-lite-preview';
|
|
14
|
+
export const GEMINI_3_FLASH_MODEL = 'gemini-3-flash';
|
|
15
|
+
export const GEMINI_3_FLASH_GATEWAY_MODEL = 'google/gemini-3-flash';
|
|
16
|
+
|
|
17
|
+
/** Normalize retired model aliases to the current stable model ids. */
|
|
18
|
+
export function normalizeStableModelId(modelId: string): string {
|
|
19
|
+
const slashIndex = modelId.indexOf('/');
|
|
20
|
+
const providerPrefix =
|
|
21
|
+
slashIndex === -1 ? '' : `${modelId.slice(0, slashIndex + 1)}`;
|
|
22
|
+
const bareModelId =
|
|
23
|
+
slashIndex === -1 ? modelId : modelId.slice(slashIndex + 1);
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
bareModelId === GEMINI_31_FLASH_LITE_PREVIEW_MODEL ||
|
|
27
|
+
bareModelId === GEMINI_3_FLASH_MODEL
|
|
28
|
+
) {
|
|
29
|
+
return `${providerPrefix}${GEMINI_31_FLASH_LITE_MODEL}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return modelId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Convert a bare model name + provider to a gateway model ID */
|
|
36
|
+
export function toGatewayModelId(provider: string, modelName: string): string {
|
|
37
|
+
return `${provider}/${normalizeStableModelId(modelName)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a model name to a gateway model ID.
|
|
42
|
+
* If the name already contains '/', it's assumed to be in gateway format.
|
|
43
|
+
* Otherwise, prepend the provider (defaults to 'google').
|
|
44
|
+
*/
|
|
45
|
+
export function resolveGatewayModelId(
|
|
46
|
+
modelName: string,
|
|
47
|
+
provider?: string
|
|
48
|
+
): string {
|
|
49
|
+
const stableModelName = normalizeStableModelId(modelName);
|
|
50
|
+
if (stableModelName.includes('/')) return stableModelName;
|
|
51
|
+
return `${provider || 'google'}/${stableModelName}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Checks whether a model is allowed by an allocation list.
|
|
56
|
+
* Empty lists mean "all models".
|
|
57
|
+
*/
|
|
58
|
+
export function matchesAllowedModel(
|
|
59
|
+
modelName: string,
|
|
60
|
+
allowedModels: string[]
|
|
61
|
+
): boolean {
|
|
62
|
+
if (allowedModels.length === 0) return true;
|
|
63
|
+
|
|
64
|
+
const gatewayModelId = resolveGatewayModelId(modelName);
|
|
65
|
+
const bareModelName = toBareModelName(gatewayModelId);
|
|
66
|
+
|
|
67
|
+
return allowedModels.some((allowedModel) => {
|
|
68
|
+
const normalizedAllowedModel = resolveGatewayModelId(allowedModel);
|
|
69
|
+
return (
|
|
70
|
+
normalizedAllowedModel === gatewayModelId ||
|
|
71
|
+
toBareModelName(normalizedAllowedModel) === bareModelName
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isGoogleModelId(modelId: string): boolean {
|
|
77
|
+
const slashIndex = modelId.indexOf('/');
|
|
78
|
+
if (slashIndex === -1) return true;
|
|
79
|
+
|
|
80
|
+
const provider = modelId.slice(0, slashIndex);
|
|
81
|
+
return provider === 'google' || provider === 'google-vertex';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extract the bare model name from a gateway model ID.
|
|
86
|
+
* e.g., 'google/gemini-2.5-flash' → 'gemini-2.5-flash'
|
|
87
|
+
*/
|
|
88
|
+
export function toBareModelName(gatewayModelId: string): string {
|
|
89
|
+
const slashIndex = gatewayModelId.indexOf('/');
|
|
90
|
+
if (slashIndex === -1) return gatewayModelId;
|
|
91
|
+
return gatewayModelId.slice(slashIndex + 1);
|
|
92
|
+
}
|