@corgelabs/corge 0.0.1 → 1.0.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/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/schema.d.ts +83 -0
- package/dist/ai/schema.d.ts.map +1 -0
- package/dist/ai/schema.js +19 -0
- package/dist/ai/schema.js.map +1 -0
- package/dist/ai/service.d.ts +14 -0
- package/dist/ai/service.d.ts.map +1 -0
- package/dist/ai/service.js +266 -0
- package/dist/ai/service.js.map +1 -0
- package/dist/ai/utils.d.ts +10 -0
- package/dist/ai/utils.d.ts.map +1 -0
- package/dist/ai/utils.js +27 -0
- package/dist/ai/utils.js.map +1 -0
- package/dist/attachments/index.d.ts +4 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +4 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/attachments/routes.d.ts +75 -0
- package/dist/attachments/routes.d.ts.map +1 -0
- package/dist/attachments/routes.js +18 -0
- package/dist/attachments/routes.js.map +1 -0
- package/dist/attachments/schema.d.ts +61 -0
- package/dist/attachments/schema.d.ts.map +1 -0
- package/dist/attachments/schema.js +30 -0
- package/dist/attachments/schema.js.map +1 -0
- package/dist/attachments/service.d.ts +27 -0
- package/dist/attachments/service.d.ts.map +1 -0
- package/dist/attachments/service.js +224 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/attachments/utils.d.ts +9 -0
- package/dist/attachments/utils.d.ts.map +1 -0
- package/dist/attachments/utils.js +80 -0
- package/dist/attachments/utils.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oidc.d.ts +25 -0
- package/dist/auth/oidc.d.ts.map +1 -0
- package/dist/auth/oidc.js +298 -0
- package/dist/auth/oidc.js.map +1 -0
- package/dist/auth/recovery.d.ts +41 -0
- package/dist/auth/recovery.d.ts.map +1 -0
- package/dist/auth/recovery.js +250 -0
- package/dist/auth/recovery.js.map +1 -0
- package/dist/auth/routes.d.ts +226 -0
- package/dist/auth/routes.d.ts.map +1 -0
- package/dist/auth/routes.js +96 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/schema.d.ts +158 -0
- package/dist/auth/schema.d.ts.map +1 -0
- package/dist/auth/schema.js +82 -0
- package/dist/auth/schema.js.map +1 -0
- package/dist/auth/service.d.ts +96 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +129 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/auth/session.d.ts +51 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +344 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/utils.d.ts +17 -0
- package/dist/auth/utils.d.ts.map +1 -0
- package/dist/auth/utils.js +96 -0
- package/dist/auth/utils.js.map +1 -0
- package/dist/bin/corge.js +547 -0
- package/dist/chat/context.d.ts +10 -0
- package/dist/chat/context.d.ts.map +1 -0
- package/dist/chat/context.js +221 -0
- package/dist/chat/context.js.map +1 -0
- package/dist/chat/index.d.ts +4 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +4 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/routes.d.ts +160 -0
- package/dist/chat/routes.d.ts.map +1 -0
- package/dist/chat/routes.js +210 -0
- package/dist/chat/routes.js.map +1 -0
- package/dist/chat/schema.d.ts +285 -0
- package/dist/chat/schema.d.ts.map +1 -0
- package/dist/chat/schema.js +74 -0
- package/dist/chat/schema.js.map +1 -0
- package/dist/chat/service.d.ts +64 -0
- package/dist/chat/service.d.ts.map +1 -0
- package/dist/chat/service.js +279 -0
- package/dist/chat/service.js.map +1 -0
- package/dist/chat/stream.d.ts +53 -0
- package/dist/chat/stream.d.ts.map +1 -0
- package/dist/chat/stream.js +339 -0
- package/dist/chat/stream.js.map +1 -0
- package/dist/chat/utils.d.ts +61 -0
- package/dist/chat/utils.d.ts.map +1 -0
- package/dist/chat/utils.js +285 -0
- package/dist/chat/utils.js.map +1 -0
- package/dist/cli/builder.d.ts +6 -0
- package/dist/cli/builder.d.ts.map +1 -0
- package/dist/cli/builder.js +91 -0
- package/dist/cli/builder.js.map +1 -0
- package/dist/cli/deployer.d.ts +2 -0
- package/dist/cli/deployer.d.ts.map +1 -0
- package/dist/cli/deployer.js +152 -0
- package/dist/cli/deployer.js.map +1 -0
- package/dist/cli/dev-server.d.ts +2 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +70 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/env.d.ts +4 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +68 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +103 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config.d.ts +262 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +212 -0
- package/dist/config.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +410 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +294 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/service.d.ts +42 -0
- package/dist/db/service.d.ts.map +1 -0
- package/dist/db/service.js +372 -0
- package/dist/db/service.js.map +1 -0
- package/dist/db/utils.d.ts +25 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +188 -0
- package/dist/db/utils.js.map +1 -0
- package/dist/emails/index.d.ts +4 -0
- package/dist/emails/index.d.ts.map +1 -0
- package/dist/emails/index.js +4 -0
- package/dist/emails/index.js.map +1 -0
- package/dist/emails/routes.d.ts +191 -0
- package/dist/emails/routes.d.ts.map +1 -0
- package/dist/emails/routes.js +39 -0
- package/dist/emails/routes.js.map +1 -0
- package/dist/emails/schema.d.ts +237 -0
- package/dist/emails/schema.d.ts.map +1 -0
- package/dist/emails/schema.js +98 -0
- package/dist/emails/schema.js.map +1 -0
- package/dist/emails/service.d.ts +22 -0
- package/dist/emails/service.d.ts.map +1 -0
- package/dist/emails/service.js +337 -0
- package/dist/emails/service.js.map +1 -0
- package/dist/emails/utils.d.ts +49 -0
- package/dist/emails/utils.d.ts.map +1 -0
- package/dist/emails/utils.js +341 -0
- package/dist/emails/utils.js.map +1 -0
- package/dist/files/index.d.ts +4 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +4 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/routes.d.ts +72 -0
- package/dist/files/routes.d.ts.map +1 -0
- package/dist/files/routes.js +38 -0
- package/dist/files/routes.js.map +1 -0
- package/dist/files/schema.d.ts +57 -0
- package/dist/files/schema.d.ts.map +1 -0
- package/dist/files/schema.js +39 -0
- package/dist/files/schema.js.map +1 -0
- package/dist/files/service.d.ts +27 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +175 -0
- package/dist/files/service.js.map +1 -0
- package/dist/files/utils.d.ts +3 -0
- package/dist/files/utils.d.ts.map +1 -0
- package/dist/files/utils.js +63 -0
- package/dist/files/utils.js.map +1 -0
- package/dist/index.d.ts +875 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9919 -0
- package/dist/index.js.map +7 -0
- package/dist/payments/billing.d.ts +29 -0
- package/dist/payments/billing.d.ts.map +1 -0
- package/dist/payments/billing.js +378 -0
- package/dist/payments/billing.js.map +1 -0
- package/dist/payments/index.d.ts +4 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +4 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/payments/routes.d.ts +340 -0
- package/dist/payments/routes.d.ts.map +1 -0
- package/dist/payments/routes.js +56 -0
- package/dist/payments/routes.js.map +1 -0
- package/dist/payments/schema.d.ts +412 -0
- package/dist/payments/schema.d.ts.map +1 -0
- package/dist/payments/schema.js +260 -0
- package/dist/payments/schema.js.map +1 -0
- package/dist/payments/service.d.ts +31 -0
- package/dist/payments/service.d.ts.map +1 -0
- package/dist/payments/service.js +322 -0
- package/dist/payments/service.js.map +1 -0
- package/dist/payments/utils.d.ts +61 -0
- package/dist/payments/utils.d.ts.map +1 -0
- package/dist/payments/utils.js +369 -0
- package/dist/payments/utils.js.map +1 -0
- package/dist/router/guard.d.ts +19 -0
- package/dist/router/guard.d.ts.map +1 -0
- package/dist/router/guard.js +243 -0
- package/dist/router/guard.js.map +1 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +3 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/schema.d.ts +103 -0
- package/dist/router/schema.d.ts.map +1 -0
- package/dist/router/schema.js +282 -0
- package/dist/router/schema.js.map +1 -0
- package/dist/router/service.d.ts +25 -0
- package/dist/router/service.d.ts.map +1 -0
- package/dist/router/service.js +339 -0
- package/dist/router/service.js.map +1 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/routes.d.ts +54 -0
- package/dist/tasks/routes.d.ts.map +1 -0
- package/dist/tasks/routes.js +195 -0
- package/dist/tasks/routes.js.map +1 -0
- package/dist/tasks/schema.d.ts +261 -0
- package/dist/tasks/schema.d.ts.map +1 -0
- package/dist/tasks/schema.js +35 -0
- package/dist/tasks/schema.js.map +1 -0
- package/dist/tasks/service.d.ts +32 -0
- package/dist/tasks/service.d.ts.map +1 -0
- package/dist/tasks/service.js +201 -0
- package/dist/tasks/service.js.map +1 -0
- package/dist/tasks/steps.d.ts +18 -0
- package/dist/tasks/steps.d.ts.map +1 -0
- package/dist/tasks/steps.js +365 -0
- package/dist/tasks/steps.js.map +1 -0
- package/dist/tasks/utils.d.ts +75 -0
- package/dist/tasks/utils.d.ts.map +1 -0
- package/dist/tasks/utils.js +311 -0
- package/dist/tasks/utils.js.map +1 -0
- package/dist/tasks/watcher.d.ts +13 -0
- package/dist/tasks/watcher.d.ts.map +1 -0
- package/dist/tasks/watcher.js +255 -0
- package/dist/tasks/watcher.js.map +1 -0
- package/dist/tasks/worker.d.ts +28 -0
- package/dist/tasks/worker.d.ts.map +1 -0
- package/dist/tasks/worker.js +326 -0
- package/dist/tasks/worker.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/schema.d.ts +79 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +41 -0
- package/dist/utils/schema.js.map +1 -0
- package/dist/utils/service.d.ts +32 -0
- package/dist/utils/service.d.ts.map +1 -0
- package/dist/utils/service.js +384 -0
- package/dist/utils/service.js.map +1 -0
- package/docs/SDK_SPEC.md +1645 -0
- package/infra/firestore.base.indexes.json +138 -0
- package/infra/firestore.base.rules +152 -0
- package/infra/storage.base.rules +8 -0
- package/package.json +59 -13
- package/src/ai/index.ts +2 -0
- package/src/ai/schema.ts +64 -0
- package/src/ai/service.ts +317 -0
- package/src/ai/utils.ts +38 -0
- package/src/attachments/index.ts +3 -0
- package/src/attachments/routes.ts +28 -0
- package/src/attachments/schema.ts +45 -0
- package/src/attachments/service.ts +315 -0
- package/src/attachments/utils.ts +115 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/oidc.ts +419 -0
- package/src/auth/recovery.ts +365 -0
- package/src/auth/routes.ts +142 -0
- package/src/auth/schema.ts +181 -0
- package/src/auth/service.ts +183 -0
- package/src/auth/session.ts +414 -0
- package/src/auth/utils.ts +106 -0
- package/src/chat/context.ts +310 -0
- package/src/chat/index.ts +3 -0
- package/src/chat/routes.ts +264 -0
- package/src/chat/schema.ts +295 -0
- package/src/chat/service.ts +360 -0
- package/src/chat/stream.ts +481 -0
- package/src/chat/utils.ts +370 -0
- package/src/cli/builder.ts +93 -0
- package/src/cli/deployer.ts +184 -0
- package/src/cli/dev-server.ts +77 -0
- package/src/cli/env.ts +89 -0
- package/src/cli/init.ts +125 -0
- package/src/config.ts +447 -0
- package/src/db/index.ts +2 -0
- package/src/db/schema.ts +409 -0
- package/src/db/service.ts +477 -0
- package/src/db/utils.ts +250 -0
- package/src/emails/index.ts +3 -0
- package/src/emails/routes.ts +64 -0
- package/src/emails/schema.ts +239 -0
- package/src/emails/service.ts +488 -0
- package/src/emails/utils.ts +449 -0
- package/src/files/index.ts +3 -0
- package/src/files/routes.ts +44 -0
- package/src/files/schema.ts +76 -0
- package/src/files/service.ts +238 -0
- package/src/files/utils.ts +77 -0
- package/src/index.ts +425 -0
- package/src/payments/billing.ts +488 -0
- package/src/payments/index.ts +3 -0
- package/src/payments/routes.ts +92 -0
- package/src/payments/schema.ts +401 -0
- package/src/payments/service.ts +445 -0
- package/src/payments/utils.ts +472 -0
- package/src/router/guard.ts +319 -0
- package/src/router/index.ts +2 -0
- package/src/router/schema.ts +458 -0
- package/src/router/service.ts +453 -0
- package/src/tasks/index.ts +11 -0
- package/src/tasks/routes.ts +230 -0
- package/src/tasks/schema.ts +310 -0
- package/src/tasks/service.ts +254 -0
- package/src/tasks/steps.ts +472 -0
- package/src/tasks/utils.ts +445 -0
- package/src/tasks/watcher.ts +307 -0
- package/src/tasks/worker.ts +461 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/schema.ts +69 -0
- package/src/utils/service.ts +461 -0
- package/tests/e2e.test.ts +2531 -0
- package/tsconfig.base.json +12 -0
- package/README.md +0 -2
- package/index.d.ts +0 -1
- package/index.js +0 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { FieldValue, Timestamp } from 'firebase-admin/firestore'
|
|
2
|
+
import type { ModelMessage, streamText } from 'ai'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import type { AttachmentTypesMap } from '../attachments'
|
|
5
|
+
import type { AppBindings, DbRole, RateLimitConfig } from '../config'
|
|
6
|
+
import { entryId, fileRef, type EntryId, type FileRef } from '../db'
|
|
7
|
+
import type { Core } from '../index'
|
|
8
|
+
import type { AuthLevel } from '../router'
|
|
9
|
+
|
|
10
|
+
export type VectorValue = ReturnType<typeof FieldValue.vector>
|
|
11
|
+
|
|
12
|
+
export type SystemPromptBuilder = (ctx: {
|
|
13
|
+
today: string
|
|
14
|
+
userId: EntryId
|
|
15
|
+
env: AppBindings
|
|
16
|
+
}) => string | Promise<string>
|
|
17
|
+
|
|
18
|
+
export interface AgentToolPresets {
|
|
19
|
+
discovery?: string[]
|
|
20
|
+
workflow?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Per-user data injected into a chat turn by `AgentConfig.resolveUserContext`. */
|
|
24
|
+
export interface UserContext {
|
|
25
|
+
/** Compact flags (≤ ~1 KB). Sent to the InfoBase classifier and to the agent. */
|
|
26
|
+
state: Record<string, unknown>
|
|
27
|
+
/** Capped records (≤ ~8 KB). Sent to the agent only, never to the classifier. */
|
|
28
|
+
details?: Record<string, unknown>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type UserContextResolver = (ctx: {
|
|
32
|
+
userId: EntryId
|
|
33
|
+
core: Core
|
|
34
|
+
signal: AbortSignal
|
|
35
|
+
}) => Promise<UserContext | undefined>
|
|
36
|
+
|
|
37
|
+
export interface AgentConfig {
|
|
38
|
+
name?: string
|
|
39
|
+
description?: string
|
|
40
|
+
model?: string
|
|
41
|
+
systemPrompt: string | SystemPromptBuilder
|
|
42
|
+
tools?: string[]
|
|
43
|
+
toolPresets?: AgentToolPresets
|
|
44
|
+
maxSteps?: number
|
|
45
|
+
useContext?: boolean
|
|
46
|
+
/** Restricts InfoBase items to those tagged with any of these audiences. Omit for no filtering. */
|
|
47
|
+
contextAudiences?: readonly string[]
|
|
48
|
+
/** Loads the caller's own data for each turn. Must honour `signal`; failures degrade to no user context. */
|
|
49
|
+
resolveUserContext?: UserContextResolver
|
|
50
|
+
attachmentTypes?: AttachmentTypesMap
|
|
51
|
+
auth?: AuthLevel
|
|
52
|
+
rateLimit?: RateLimitConfig
|
|
53
|
+
mcp?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const defineAgent = <C extends AgentConfig>(config: C): C => config
|
|
57
|
+
export const defineAgents = <T extends Record<string, AgentConfig>>(agents: T): T => agents
|
|
58
|
+
|
|
59
|
+
export interface ChatMetadataDoc {
|
|
60
|
+
id: string
|
|
61
|
+
userId: string
|
|
62
|
+
agentId: string
|
|
63
|
+
title: string
|
|
64
|
+
model: string
|
|
65
|
+
messageCount: number
|
|
66
|
+
summary?: string | null
|
|
67
|
+
activeGenerationId?: string | null
|
|
68
|
+
deleted: boolean
|
|
69
|
+
createdAt: Timestamp | Date
|
|
70
|
+
updatedAt: Timestamp | Date
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ChatMessageRole = 'user' | 'assistant' | 'system' | 'tool'
|
|
74
|
+
|
|
75
|
+
export interface ChatMessageDoc {
|
|
76
|
+
id: string
|
|
77
|
+
chatId: string
|
|
78
|
+
userId: string
|
|
79
|
+
role: ChatMessageRole
|
|
80
|
+
content: string
|
|
81
|
+
parts: unknown[]
|
|
82
|
+
toolInvocations?: unknown[]
|
|
83
|
+
attachments?: string[]
|
|
84
|
+
tokenCount?: number
|
|
85
|
+
cancelled?: boolean
|
|
86
|
+
createdAt: Timestamp | Date
|
|
87
|
+
embedding?: VectorValue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ChatMessagePublic = Omit<ChatMessageDoc, 'embedding' | 'createdAt'> & {
|
|
91
|
+
createdAt: Date
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RecalledMemorySnippet {
|
|
95
|
+
id: string
|
|
96
|
+
chatId: string
|
|
97
|
+
role: ChatMessageRole
|
|
98
|
+
content: string
|
|
99
|
+
scope: 'in_chat' | 'cross_chat'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ContextItemSpec {
|
|
103
|
+
path: string
|
|
104
|
+
description: string
|
|
105
|
+
/** Audiences allowed to receive this item (matched against `AgentConfig.contextAudiences`). Omit to expose to every agent. */
|
|
106
|
+
audiences?: readonly string[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const defineContext = <const T extends readonly ContextItemSpec[]>(items: T): T => items
|
|
110
|
+
|
|
111
|
+
export interface ContextEntry {
|
|
112
|
+
path: string
|
|
113
|
+
description: string
|
|
114
|
+
markdown: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface GetContextOptions {
|
|
118
|
+
maxSources?: number
|
|
119
|
+
/** Only items tagged with any of these audiences (or untagged items) are considered. */
|
|
120
|
+
audiences?: readonly string[]
|
|
121
|
+
/** Compact caller state forwarded to the classifier to judge relevance. */
|
|
122
|
+
userState?: Record<string, unknown>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface DynamicContextBlockInput {
|
|
126
|
+
currentTimeIso?: string
|
|
127
|
+
recalledMemories?: readonly RecalledMemorySnippet[]
|
|
128
|
+
infobaseContextEntries?: readonly ContextEntry[]
|
|
129
|
+
stagedAttachmentSummaries?: readonly string[]
|
|
130
|
+
userContext?: UserContext
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface HybridRetrievalOptions {
|
|
134
|
+
userId?: EntryId
|
|
135
|
+
chatId?: string
|
|
136
|
+
messageCount?: number
|
|
137
|
+
prompt: string
|
|
138
|
+
useContext?: boolean
|
|
139
|
+
slidingWindowSize?: number
|
|
140
|
+
/** Agent whose `contextAudiences` and `resolveUserContext` scope this turn. */
|
|
141
|
+
agent?: Pick<AgentConfig, 'contextAudiences' | 'resolveUserContext'>
|
|
142
|
+
/** Verified caller role (defaults to `resolveUserRole(userId)`). */
|
|
143
|
+
role?: DbRole
|
|
144
|
+
abortSignal?: AbortSignal
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface HybridRetrievalResult {
|
|
148
|
+
slidingWindowDocs: ChatMessagePublic[]
|
|
149
|
+
slidingWindowMessages: ModelMessage[]
|
|
150
|
+
recalledMemories: RecalledMemorySnippet[]
|
|
151
|
+
infobaseContextEntries: ContextEntry[]
|
|
152
|
+
userContext?: UserContext
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ListChatMessagesOptions {
|
|
156
|
+
limit?: number
|
|
157
|
+
before?: EntryId
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface WorkflowToolSpec {
|
|
161
|
+
description: string
|
|
162
|
+
input: z.ZodType<any>
|
|
163
|
+
mcp?: boolean
|
|
164
|
+
tool?: boolean
|
|
165
|
+
execute: (input: any, userId: EntryId) => Promise<unknown>
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface VerifiedAgentToolContext {
|
|
169
|
+
core: Core
|
|
170
|
+
chatId?: string
|
|
171
|
+
abortSignal?: AbortSignal
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface RunAgentOptions {
|
|
175
|
+
modelOverride?: string
|
|
176
|
+
chatId?: string
|
|
177
|
+
abortSignal?: AbortSignal
|
|
178
|
+
dynamicContext?: DynamicContextBlockInput
|
|
179
|
+
core?: Core
|
|
180
|
+
onFinish?: Parameters<typeof streamText>[0]['onFinish']
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface AskAgentOptions {
|
|
184
|
+
userId?: EntryId
|
|
185
|
+
includeContext?: boolean
|
|
186
|
+
abortSignal?: AbortSignal
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const ReasoningExpertInputDto = z.object({
|
|
190
|
+
problemStatement: z.string().describe('Self-contained specification of the complex reasoning task.'),
|
|
191
|
+
constraintsAndContext: z.string().describe('Compact summary of relevant data, constraints, or document fields.')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
export const ReasoningExpertOutputDto = z.object({
|
|
195
|
+
analysisSummary: z.string(),
|
|
196
|
+
recommendedSolution: z.string(),
|
|
197
|
+
edgeCasesToWatch: z.array(z.string())
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
export interface RawAttachmentPart {
|
|
201
|
+
dataUrl?: string
|
|
202
|
+
ref?: FileRef
|
|
203
|
+
filename: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface PersistTurnCompletionOpts {
|
|
207
|
+
turn: ReturnType<typeof import('ai').streamText>
|
|
208
|
+
getAccumulatedText: () => string
|
|
209
|
+
signal: AbortSignal
|
|
210
|
+
userVecPromise: Promise<number[] | null>
|
|
211
|
+
chatDocRef: import('firebase-admin/firestore').DocumentReference
|
|
212
|
+
chatId: string
|
|
213
|
+
userId: EntryId
|
|
214
|
+
resolvedAgentId: string
|
|
215
|
+
title: string
|
|
216
|
+
selectedModel: string
|
|
217
|
+
userMsgId: string
|
|
218
|
+
assistantMsgId: string
|
|
219
|
+
userCreatedAtMs: number
|
|
220
|
+
finalUserText: string
|
|
221
|
+
userPartsForStorage: unknown[]
|
|
222
|
+
stagedRefs: FileRef[]
|
|
223
|
+
unsubCancelWatch: (() => void) | undefined
|
|
224
|
+
key: string
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const chatIdParam = z.object({ chatId: entryId })
|
|
228
|
+
|
|
229
|
+
export const listChatMessagesQuery = z.object({
|
|
230
|
+
limit: z.coerce.number().int().min(1).max(200).optional(),
|
|
231
|
+
before: entryId.optional()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
export const saveChatBody = z
|
|
235
|
+
.object({
|
|
236
|
+
id: entryId.optional(),
|
|
237
|
+
chatId: entryId.optional(),
|
|
238
|
+
title: z.string().min(1).max(256).optional(),
|
|
239
|
+
agentId: z.string().min(1).max(128).optional(),
|
|
240
|
+
model: z.string().min(1).max(128).optional(),
|
|
241
|
+
summary: z.string().max(4000).nullable().optional()
|
|
242
|
+
})
|
|
243
|
+
.strict()
|
|
244
|
+
|
|
245
|
+
export const streamChatMessageInput = z.object({
|
|
246
|
+
id: entryId.optional(),
|
|
247
|
+
role: z.literal('user').default('user'),
|
|
248
|
+
content: z.string().optional(),
|
|
249
|
+
parts: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
250
|
+
attachments: z.array(fileRef).max(20).optional()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
export type StreamChatMessageInput = z.infer<typeof streamChatMessageInput>
|
|
254
|
+
|
|
255
|
+
export const streamChatBody = z
|
|
256
|
+
.object({
|
|
257
|
+
id: entryId.optional(),
|
|
258
|
+
chatId: entryId.optional(),
|
|
259
|
+
agentId: z.string().min(1).max(128).optional(),
|
|
260
|
+
title: z.string().max(256).optional(),
|
|
261
|
+
model: z.string().max(128).optional(),
|
|
262
|
+
message: streamChatMessageInput
|
|
263
|
+
})
|
|
264
|
+
.strict()
|
|
265
|
+
|
|
266
|
+
export const openaiMessagePartSchema = z
|
|
267
|
+
.object({
|
|
268
|
+
type: z.string(),
|
|
269
|
+
text: z.string().optional(),
|
|
270
|
+
image_url: z.object({ url: z.string().optional() }).optional(),
|
|
271
|
+
file: z.object({ file_data: z.string().optional(), filename: z.string().optional() }).optional()
|
|
272
|
+
})
|
|
273
|
+
.passthrough()
|
|
274
|
+
|
|
275
|
+
export const openaiMessageSchema = z
|
|
276
|
+
.object({
|
|
277
|
+
role: z.string(),
|
|
278
|
+
content: z.union([z.string(), z.array(openaiMessagePartSchema), z.null()]).optional()
|
|
279
|
+
})
|
|
280
|
+
.passthrough()
|
|
281
|
+
|
|
282
|
+
export const openaiCompletionsBody = z
|
|
283
|
+
.object({
|
|
284
|
+
model: z.string(),
|
|
285
|
+
messages: z.array(openaiMessageSchema).min(1).max(500),
|
|
286
|
+
stream: z.boolean().optional(),
|
|
287
|
+
stream_options: z.object({ include_usage: z.boolean().optional() }).optional()
|
|
288
|
+
})
|
|
289
|
+
.extend({ model: z.string().optional() })
|
|
290
|
+
.passthrough()
|
|
291
|
+
|
|
292
|
+
export const webSearchInput = z.object({
|
|
293
|
+
query: z.string().min(1).optional(),
|
|
294
|
+
url: z.string().optional().describe('Full URL to open and read — wins over query')
|
|
295
|
+
})
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stepCountIs,
|
|
3
|
+
streamText,
|
|
4
|
+
tool as aiTool,
|
|
5
|
+
type ModelMessage,
|
|
6
|
+
type SystemModelMessage,
|
|
7
|
+
type ToolSet
|
|
8
|
+
} from 'ai'
|
|
9
|
+
import { registerAttachmentTypes } from '../attachments'
|
|
10
|
+
import { CORE_CONFIG } from '../config'
|
|
11
|
+
import type { EntryId } from '../db'
|
|
12
|
+
import { Core } from '../index'
|
|
13
|
+
import { ChatContextEngine } from './context'
|
|
14
|
+
import {
|
|
15
|
+
ReasoningExpertInputDto,
|
|
16
|
+
ReasoningExpertOutputDto,
|
|
17
|
+
webSearchInput,
|
|
18
|
+
type AgentConfig,
|
|
19
|
+
type AskAgentOptions,
|
|
20
|
+
type ChatMessagePublic,
|
|
21
|
+
type ContextEntry,
|
|
22
|
+
type ContextItemSpec,
|
|
23
|
+
type GetContextOptions,
|
|
24
|
+
type HybridRetrievalOptions,
|
|
25
|
+
type HybridRetrievalResult,
|
|
26
|
+
type ListChatMessagesOptions,
|
|
27
|
+
type RunAgentOptions,
|
|
28
|
+
type VerifiedAgentToolContext,
|
|
29
|
+
type WorkflowToolSpec
|
|
30
|
+
} from './schema'
|
|
31
|
+
import { ChatStreamEngine } from './stream'
|
|
32
|
+
import {
|
|
33
|
+
buildDynamicContextBlock,
|
|
34
|
+
extractLatestUserPrompt,
|
|
35
|
+
FORBIDDEN_TOOL_SCHEMA_KEYS,
|
|
36
|
+
isSafePublicUrl,
|
|
37
|
+
sanitizeToolInputSchema
|
|
38
|
+
} from './utils'
|
|
39
|
+
|
|
40
|
+
const toolRegistry = new Map<string, WorkflowToolSpec>()
|
|
41
|
+
const agentRegistry = new Map<string, AgentConfig>()
|
|
42
|
+
|
|
43
|
+
export class ChatService {
|
|
44
|
+
readonly #core: Core
|
|
45
|
+
readonly #contextEngine: ChatContextEngine
|
|
46
|
+
readonly #streamEngine: ChatStreamEngine
|
|
47
|
+
|
|
48
|
+
constructor(coreClient: Core) {
|
|
49
|
+
this.#core = coreClient
|
|
50
|
+
this.#contextEngine = new ChatContextEngine(coreClient)
|
|
51
|
+
this.#streamEngine = new ChatStreamEngine(coreClient, agentRegistry)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#registerWorkflowTool = (name: string, spec: WorkflowToolSpec): void => {
|
|
55
|
+
toolRegistry.set(name, {
|
|
56
|
+
...spec,
|
|
57
|
+
mcp: spec.mcp === true,
|
|
58
|
+
tool: spec.tool !== false,
|
|
59
|
+
input: sanitizeToolInputSchema(spec.input)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#clearWorkflowTools = (): void => {
|
|
64
|
+
toolRegistry.clear()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#getMcpTools = (): Map<string, WorkflowToolSpec> =>
|
|
68
|
+
new Map([...toolRegistry.entries()].filter(([, spec]) => spec.mcp === true))
|
|
69
|
+
|
|
70
|
+
#registerAgents = (defs: Record<string, AgentConfig>, reset = false): void => {
|
|
71
|
+
if (reset) {
|
|
72
|
+
agentRegistry.clear()
|
|
73
|
+
registerAttachmentTypes(undefined, true)
|
|
74
|
+
}
|
|
75
|
+
for (const [id, cfg] of Object.entries(defs)) {
|
|
76
|
+
agentRegistry.set(id, { ...cfg, name: cfg.name ?? id })
|
|
77
|
+
if (cfg.attachmentTypes) registerAttachmentTypes(cfg.attachmentTypes)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#getRegisteredAgents = (): Record<string, AgentConfig> => Object.fromEntries(agentRegistry)
|
|
82
|
+
|
|
83
|
+
#getAgentRegistry = (): Map<string, AgentConfig> => agentRegistry
|
|
84
|
+
|
|
85
|
+
getContext = (
|
|
86
|
+
prompt: string,
|
|
87
|
+
catalog?: readonly ContextItemSpec[],
|
|
88
|
+
options?: GetContextOptions
|
|
89
|
+
): Promise<ContextEntry[]> => this.#contextEngine.getContext(prompt, catalog, options)
|
|
90
|
+
|
|
91
|
+
retrieveContext = (opts: HybridRetrievalOptions): Promise<HybridRetrievalResult> =>
|
|
92
|
+
this.#contextEngine.retrieveContext(opts)
|
|
93
|
+
|
|
94
|
+
messages = (chatId: EntryId, opts?: ListChatMessagesOptions): Promise<ChatMessagePublic[]> =>
|
|
95
|
+
this.#streamEngine.messages(chatId, opts)
|
|
96
|
+
|
|
97
|
+
cancel = (chatId: EntryId): Promise<{ chatId: string; cancelled: true }> =>
|
|
98
|
+
this.#streamEngine.cancel(chatId)
|
|
99
|
+
|
|
100
|
+
#webSearchTool = (abortSignal?: AbortSignal) =>
|
|
101
|
+
aiTool({
|
|
102
|
+
description: 'Search the web (DuckDuckGo) or open a URL and read it as markdown. Use for current information.',
|
|
103
|
+
inputSchema: webSearchInput,
|
|
104
|
+
execute: async ({ query, url }) => {
|
|
105
|
+
const useUrl = isSafePublicUrl(url)
|
|
106
|
+
const target = useUrl ? url! : `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query ?? url ?? '')}`
|
|
107
|
+
const maxChars = useUrl ? 6000 : 4000
|
|
108
|
+
try {
|
|
109
|
+
const timeoutSignal = AbortSignal.timeout(20_000)
|
|
110
|
+
const signal = abortSignal ? AbortSignal.any([abortSignal, timeoutSignal]) : timeoutSignal
|
|
111
|
+
const jinaKey = this.#core.env.JINA_API_KEY
|
|
112
|
+
const res = await fetch(`https://r.jina.ai/${target}`, {
|
|
113
|
+
headers: {
|
|
114
|
+
Accept: 'text/plain, text/markdown;q=0.9, */*;q=0.8',
|
|
115
|
+
...(jinaKey ? { Authorization: `Bearer ${jinaKey}` } : {})
|
|
116
|
+
},
|
|
117
|
+
signal
|
|
118
|
+
})
|
|
119
|
+
if (!res.ok) return { text: `Web ${useUrl ? 'page' : 'search'} failed (HTTP ${res.status}).` }
|
|
120
|
+
const reader = res.body?.getReader()
|
|
121
|
+
if (!reader) return { text: '' }
|
|
122
|
+
const decoder = new TextDecoder()
|
|
123
|
+
let text = ''
|
|
124
|
+
while (text.length < maxChars + 256) {
|
|
125
|
+
const { done, value } = await reader.read()
|
|
126
|
+
if (done) break
|
|
127
|
+
text += decoder.decode(value, { stream: true })
|
|
128
|
+
}
|
|
129
|
+
await reader.cancel().catch(() => {})
|
|
130
|
+
return { text: text.slice(0, maxChars).replace(/\s+\S*$/, '') }
|
|
131
|
+
} catch {
|
|
132
|
+
return { text: 'Web search failed.' }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
#createReasoningExpertTool = (ctx: VerifiedAgentToolContext) =>
|
|
138
|
+
aiTool({
|
|
139
|
+
description:
|
|
140
|
+
'Delegate complex architectural, legal, financial, or multi-constraint reasoning problems to a frontier reasoning model. Use only when standard analysis is insufficient.',
|
|
141
|
+
inputSchema: ReasoningExpertInputDto,
|
|
142
|
+
execute: async ({ problemStatement, constraintsAndContext }) => {
|
|
143
|
+
const result = await ctx.core.ai.generate(
|
|
144
|
+
`Problem:\n${problemStatement}\n\nConstraints & Context:\n${constraintsAndContext}`,
|
|
145
|
+
{
|
|
146
|
+
schema: ReasoningExpertOutputDto,
|
|
147
|
+
model: CORE_CONFIG.models.vertexExpert ?? 'claude-sonnet-4-5',
|
|
148
|
+
signal: ctx.abortSignal
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
return result.output
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
#buildAgentToolsForTurn = (toolKeys: string[] = [], ctx: VerifiedAgentToolContext): ToolSet => {
|
|
156
|
+
const tools: ToolSet = {}
|
|
157
|
+
const { core: turnCore, abortSignal } = ctx
|
|
158
|
+
const coreState = {
|
|
159
|
+
env: turnCore.env,
|
|
160
|
+
userId: turnCore.userId,
|
|
161
|
+
role: turnCore.role,
|
|
162
|
+
step: turnCore.step
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const name of toolKeys) {
|
|
166
|
+
if (name === 'web_search') {
|
|
167
|
+
tools[name] = this.#webSearchTool(abortSignal)
|
|
168
|
+
} else if (name === 'consult_reasoning_expert') {
|
|
169
|
+
tools[name] = this.#createReasoningExpertTool(ctx)
|
|
170
|
+
} else {
|
|
171
|
+
const wf = toolRegistry.get(name)
|
|
172
|
+
if (wf && wf.tool !== false) {
|
|
173
|
+
tools[name] = aiTool({
|
|
174
|
+
description: wf.description,
|
|
175
|
+
inputSchema: sanitizeToolInputSchema(wf.input),
|
|
176
|
+
execute: (rawInput) =>
|
|
177
|
+
this.#core.internals.context.run(coreState, async () => {
|
|
178
|
+
const safeInput =
|
|
179
|
+
rawInput && typeof rawInput === 'object'
|
|
180
|
+
? Object.fromEntries(
|
|
181
|
+
Object.entries(rawInput as Record<string, unknown>).filter(
|
|
182
|
+
([k]) => !FORBIDDEN_TOOL_SCHEMA_KEYS.has(k)
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
: rawInput
|
|
186
|
+
return wf.execute(safeInput, turnCore.userId)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return tools
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#resolveStaticSystemPrompt = async (agent: AgentConfig): Promise<string> => {
|
|
196
|
+
const raw =
|
|
197
|
+
typeof agent.systemPrompt === 'function'
|
|
198
|
+
? await agent.systemPrompt({
|
|
199
|
+
today: 'see dynamic context',
|
|
200
|
+
userId: 'authenticated-user',
|
|
201
|
+
env: this.#core.env
|
|
202
|
+
})
|
|
203
|
+
: agent.systemPrompt
|
|
204
|
+
return raw
|
|
205
|
+
.replace(/\s*Today is \{\{today\}\}\.?/gi, '')
|
|
206
|
+
.replace(/\s*Current userId is \{\{userId\}\}\.?/gi, '')
|
|
207
|
+
.replaceAll('{{today}}', 'see dynamic context')
|
|
208
|
+
.replaceAll('{{userId}}', 'authenticated-user')
|
|
209
|
+
.trim()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#resolveStepActiveTools = (agent: AgentConfig, allToolNames: string[], stepNumber: number): string[] => {
|
|
213
|
+
if (allToolNames.length === 0) return []
|
|
214
|
+
const presets = agent.toolPresets
|
|
215
|
+
if (!presets) return allToolNames
|
|
216
|
+
if (stepNumber === 0 && presets.discovery?.length && presets.workflow?.length) {
|
|
217
|
+
return [...new Set([...presets.discovery, ...presets.workflow])].filter((k) => allToolNames.includes(k))
|
|
218
|
+
}
|
|
219
|
+
if (stepNumber > 0 && presets.workflow?.length) {
|
|
220
|
+
return presets.workflow.filter((k) => allToolNames.includes(k))
|
|
221
|
+
}
|
|
222
|
+
return allToolNames
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
stream = async (
|
|
226
|
+
agent: AgentConfig,
|
|
227
|
+
messages: ModelMessage[],
|
|
228
|
+
userId: EntryId = this.#core.userId,
|
|
229
|
+
opts?: RunAgentOptions
|
|
230
|
+
): Promise<ReturnType<typeof streamText>> => {
|
|
231
|
+
await this.#core.internals.payments.enforceAiQuota(userId)
|
|
232
|
+
const [, staticCoreAgentInstructions] = await Promise.all([
|
|
233
|
+
this.#core.internals.resilience.acquireLimit('ai'),
|
|
234
|
+
this.#resolveStaticSystemPrompt(agent)
|
|
235
|
+
])
|
|
236
|
+
|
|
237
|
+
const role = opts?.core?.role ?? this.#core.internals.auth.resolveUserRole(userId, this.#core.role)
|
|
238
|
+
const verifiedCore = opts?.core ?? new Core({ env: this.#core.env, userId, role })
|
|
239
|
+
const tools = this.#buildAgentToolsForTurn(agent.tools ?? [], {
|
|
240
|
+
core: verifiedCore,
|
|
241
|
+
chatId: opts?.chatId,
|
|
242
|
+
abortSignal: opts?.abortSignal
|
|
243
|
+
})
|
|
244
|
+
const allToolNames = Object.keys(tools)
|
|
245
|
+
|
|
246
|
+
const dynamicBlock = buildDynamicContextBlock({
|
|
247
|
+
currentTimeIso: opts?.dynamicContext?.currentTimeIso ?? new Date().toISOString(),
|
|
248
|
+
recalledMemories: opts?.dynamicContext?.recalledMemories,
|
|
249
|
+
infobaseContextEntries: opts?.dynamicContext?.infobaseContextEntries,
|
|
250
|
+
stagedAttachmentSummaries: opts?.dynamicContext?.stagedAttachmentSummaries,
|
|
251
|
+
userContext: opts?.dynamicContext?.userContext
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const systemInstructions: SystemModelMessage[] = [
|
|
255
|
+
{
|
|
256
|
+
role: 'system',
|
|
257
|
+
content: staticCoreAgentInstructions,
|
|
258
|
+
providerOptions: {
|
|
259
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
260
|
+
vertex: { cacheControl: { type: 'ephemeral' } }
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
role: 'system',
|
|
265
|
+
content: dynamicBlock
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
const messagesForLlm: ModelMessage[] = messages.filter((m) => m.role !== 'system')
|
|
270
|
+
const selectedModel = opts?.modelOverride || agent.model || CORE_CONFIG.models.vertex
|
|
271
|
+
|
|
272
|
+
return streamText({
|
|
273
|
+
model: this.#core.ai.languageModel(selectedModel),
|
|
274
|
+
system: systemInstructions,
|
|
275
|
+
messages: messagesForLlm,
|
|
276
|
+
tools,
|
|
277
|
+
...(allToolNames.length > 0
|
|
278
|
+
? {
|
|
279
|
+
activeTools: allToolNames,
|
|
280
|
+
prepareStep: ({ stepNumber, messages: stepMessages }) => ({
|
|
281
|
+
activeTools: this.#resolveStepActiveTools(agent, allToolNames, stepNumber),
|
|
282
|
+
...(stepNumber > 2 && stepMessages.length > CORE_CONFIG.limits.chatMessagesLimit
|
|
283
|
+
? { messages: stepMessages.slice(-CORE_CONFIG.limits.chatMessagesLimit) }
|
|
284
|
+
: {})
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
: {}),
|
|
288
|
+
stopWhen: stepCountIs(agent.maxSteps ?? CORE_CONFIG.models.defaultAgentMaxSteps),
|
|
289
|
+
...(opts?.abortSignal ? { abortSignal: opts.abortSignal } : {}),
|
|
290
|
+
onFinish: async (event) => {
|
|
291
|
+
const usageObj = (event as { totalUsage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number }; usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } }).totalUsage ?? event.usage
|
|
292
|
+
await this.#core.internals.payments.trackAiUsage({
|
|
293
|
+
userId,
|
|
294
|
+
model: selectedModel,
|
|
295
|
+
inputTokens: usageObj?.inputTokens ?? 0,
|
|
296
|
+
outputTokens: usageObj?.outputTokens ?? 0,
|
|
297
|
+
...(typeof usageObj?.totalTokens === 'number' ? { totalTokens: usageObj.totalTokens } : {}),
|
|
298
|
+
source: 'chat'
|
|
299
|
+
})
|
|
300
|
+
await opts?.onFinish?.(event)
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
ask = async (
|
|
306
|
+
agentOrId: string | AgentConfig,
|
|
307
|
+
prompt: string | ModelMessage[],
|
|
308
|
+
options?: AskAgentOptions
|
|
309
|
+
): Promise<{ text: string; messages: ModelMessage[] }> => {
|
|
310
|
+
const agent = typeof agentOrId === 'string' ? agentRegistry.get(agentOrId) : agentOrId
|
|
311
|
+
if (!agent) return this.#core.errors.httpError(404, `Unknown agent: ${String(agentOrId)}`, 'NOT_FOUND')
|
|
312
|
+
|
|
313
|
+
const userId = options?.userId ?? this.#core.userId ?? CORE_CONFIG.auth.adminUserId
|
|
314
|
+
const rawMessages: ModelMessage[] =
|
|
315
|
+
typeof prompt === 'string' ? [{ role: 'user', content: [{ type: 'text', text: prompt }] }] : [...prompt]
|
|
316
|
+
const latestPrompt = typeof prompt === 'string' ? prompt : extractLatestUserPrompt(rawMessages)
|
|
317
|
+
|
|
318
|
+
const hybrid =
|
|
319
|
+
options?.includeContext !== false
|
|
320
|
+
? await this.retrieveContext({
|
|
321
|
+
userId,
|
|
322
|
+
prompt: latestPrompt,
|
|
323
|
+
useContext: agent.useContext !== false,
|
|
324
|
+
agent,
|
|
325
|
+
abortSignal: options?.abortSignal
|
|
326
|
+
})
|
|
327
|
+
: { recalledMemories: [], infobaseContextEntries: [], userContext: undefined }
|
|
328
|
+
|
|
329
|
+
const turn = await this.stream(agent, rawMessages.slice(-15), userId, {
|
|
330
|
+
abortSignal: options?.abortSignal,
|
|
331
|
+
dynamicContext: {
|
|
332
|
+
recalledMemories: hybrid.recalledMemories,
|
|
333
|
+
infobaseContextEntries: hybrid.infobaseContextEntries,
|
|
334
|
+
userContext: hybrid.userContext
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
const [text, res] = await Promise.all([turn.text, turn.response])
|
|
338
|
+
return {
|
|
339
|
+
text,
|
|
340
|
+
messages: [...rawMessages, ...(res.messages as unknown as ModelMessage[])]
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
get internals() {
|
|
345
|
+
return {
|
|
346
|
+
registerWorkflowTool: this.#registerWorkflowTool,
|
|
347
|
+
clearWorkflowTools: this.#clearWorkflowTools,
|
|
348
|
+
getMcpTools: this.#getMcpTools,
|
|
349
|
+
registerAgents: this.#registerAgents,
|
|
350
|
+
getRegisteredAgents: this.#getRegisteredAgents,
|
|
351
|
+
getAgentRegistry: this.#getAgentRegistry,
|
|
352
|
+
registerContextCatalog: this.#contextEngine.registerContextCatalog,
|
|
353
|
+
enforceAgentAccess: this.#streamEngine.enforceAgentAccess,
|
|
354
|
+
stageAndScanAttachments: this.#streamEngine.stageAndScanAttachments,
|
|
355
|
+
startChatStreamTurn: this.#streamEngine.startChatStreamTurn,
|
|
356
|
+
saveChat: this.#streamEngine.saveChat,
|
|
357
|
+
streamChatSse: this.#streamEngine.streamChatSse
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|