@@ -20,6 +20,7 @@ import { createCompactFunction } from "agents/experimental/memory/utils";
2020import { generateText , type LanguageModel , type ToolSet } from "ai" ;
2121
2222import type { AIInspectorSnapshot } from "#/features/workspaces/ai/ai-inspector" ;
23+ import type { AIThreadContext } from "#/features/workspaces/ai/ai-thread-metadata" ;
2324import { AIThreadTelemetryRecorder } from "#/features/workspaces/ai/ai-thread-telemetry-recorder" ;
2425import {
2526 createAIThreadTools ,
@@ -34,8 +35,13 @@ import {
3435 DEFAULT_WORKSPACE_AI_CHAT_MODEL_ID ,
3536 getWorkspaceAiChatModel ,
3637 resolveWorkspaceAiChatModelId ,
38+ type WorkspaceAiChatModelId ,
3739} from "#/features/workspaces/ai/models" ;
3840import type { UserAIStore } from "#/features/workspaces/ai/user-ai-agents" ;
41+ import {
42+ checkWorkspaceAiMessageAccess ,
43+ trackWorkspaceAiMessageUsage ,
44+ } from "#/integrations/autumn/workspace-ai-usage" ;
3945
4046const AI_THREAD_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS = 90_000 ;
4147const AI_THREAD_CHAT_RECOVERY_TERMINAL_MESSAGE =
@@ -53,6 +59,11 @@ type AIThreadRunSettlement =
5359 kind : "failed" ;
5460 } ;
5561
62+ interface AIThreadUsageContext {
63+ modelId : WorkspaceAiChatModelId ;
64+ thread : AIThreadContext ;
65+ }
66+
5667export function createAIThreadClass ( getUserAIStore : ( ) => typeof UserAIStore ) {
5768 return class AIThread extends Think < Cloudflare . Env > {
5869 override chatRecovery = {
@@ -75,6 +86,7 @@ export function createAIThreadClass(getUserAIStore: () => typeof UserAIStore) {
7586 override sendReasoning = false ;
7687 private shouldRefreshSessionPrompt = false ;
7788 private activeRunStartedAt : number | undefined ;
89+ private activeUsageContext : AIThreadUsageContext | undefined ;
7890 private readonly telemetry = new AIThreadTelemetryRecorder ( {
7991 env : this . env ,
8092 host : this ,
@@ -153,6 +165,24 @@ export function createAIThreadClass(getUserAIStore: () => typeof UserAIStore) {
153165 }
154166
155167 const modelId = resolveWorkspaceAiChatModelId ( ctx . body ?. modelId ) ;
168+
169+ if ( ! ctx . continuation ) {
170+ const access = await checkWorkspaceAiMessageAccess ( {
171+ env : this . env ,
172+ modelId,
173+ userId : thread . userId ,
174+ } ) ;
175+
176+ if ( ! access . allowed ) {
177+ throw new Error ( "Usage limit reached" ) ;
178+ }
179+
180+ this . activeUsageContext = {
181+ modelId,
182+ thread,
183+ } ;
184+ }
185+
156186 const system = getAIThreadSystemPromptForWorkspace ( ctx . system , thread . promptScope , {
157187 timeZone : getBodyString ( ctx . body , "timeZone" ) ,
158188 workspaceAiContext : ctx . body ?. workspaceAiContext ,
@@ -204,6 +234,7 @@ export function createAIThreadClass(getUserAIStore: () => typeof UserAIStore) {
204234
205235 override async onChatResponse ( result : ChatResponseResult ) {
206236 this . telemetry . recordTurnFinished ( result ) ;
237+ this . _trackCompletedMessageUsage ( result ) ;
207238 if ( ! this . _shouldSettleRunAfterResponse ( result ) ) {
208239 await this . _refreshSessionPromptIfNeeded ( ) ;
209240 return ;
@@ -307,13 +338,35 @@ export function createAIThreadClass(getUserAIStore: () => typeof UserAIStore) {
307338 }
308339
309340 this . activeRunStartedAt = undefined ;
341+ this . activeUsageContext = undefined ;
310342 } catch ( error ) {
311343 onError ( error ) ;
312344 } finally {
313345 await this . _refreshSessionPromptIfNeeded ( ) ;
314346 }
315347 }
316348
349+ private _trackCompletedMessageUsage ( result : ChatResponseResult ) {
350+ if ( result . continuation || result . status !== "completed" ) {
351+ return ;
352+ }
353+
354+ const usageContext = this . activeUsageContext ;
355+ if ( ! usageContext ) {
356+ return ;
357+ }
358+
359+ void this . keepAliveWhile ( ( ) =>
360+ trackWorkspaceAiMessageUsage ( {
361+ env : this . env ,
362+ modelId : usageContext . modelId ,
363+ threadId : usageContext . thread . id ,
364+ userId : usageContext . thread . userId ,
365+ workspaceId : usageContext . thread . workspaceId ,
366+ } ) ,
367+ ) ;
368+ }
369+
317370 private async _handleChatRecoveryExhausted ( ctx : ChatRecoveryExhaustedContext ) {
318371 await this . _recordAuxiliaryError ( {
319372 error : new Error ( AI_THREAD_CHAT_RECOVERY_TERMINAL_MESSAGE ) ,
0 commit comments