@lota-sdk/shared 0.1.5
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 +50 -0
- package/src/constants/attachments.ts +50 -0
- package/src/constants/workstream.ts +20 -0
- package/src/index.ts +15 -0
- package/src/runtime/chat-message-metadata.ts +47 -0
- package/src/schemas/chat-api.ts +26 -0
- package/src/schemas/chat-message.ts +55 -0
- package/src/schemas/common.ts +71 -0
- package/src/schemas/document.ts +51 -0
- package/src/schemas/error.ts +11 -0
- package/src/schemas/execution-plan.ts +183 -0
- package/src/schemas/recent-activity.ts +102 -0
- package/src/schemas/repository-structure.ts +122 -0
- package/src/schemas/tools.ts +163 -0
- package/src/utils/assistant-text.ts +129 -0
- package/src/utils/markdown-normalization.ts +39 -0
- package/src/utils/string.ts +18 -0
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lota-sdk/shared",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"bun": "./src/index.ts",
|
|
13
|
+
"import": "./src/index.ts",
|
|
14
|
+
"types": "./src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"./constants/*": {
|
|
17
|
+
"bun": "./src/constants/*.ts",
|
|
18
|
+
"import": "./src/constants/*.ts",
|
|
19
|
+
"types": "./src/constants/*.ts"
|
|
20
|
+
},
|
|
21
|
+
"./runtime/*": {
|
|
22
|
+
"bun": "./src/runtime/*.ts",
|
|
23
|
+
"import": "./src/runtime/*.ts",
|
|
24
|
+
"types": "./src/runtime/*.ts"
|
|
25
|
+
},
|
|
26
|
+
"./schemas/*": {
|
|
27
|
+
"bun": "./src/schemas/*.ts",
|
|
28
|
+
"import": "./src/schemas/*.ts",
|
|
29
|
+
"types": "./src/schemas/*.ts"
|
|
30
|
+
},
|
|
31
|
+
"./utils/*": {
|
|
32
|
+
"bun": "./src/utils/*.ts",
|
|
33
|
+
"import": "./src/utils/*.ts",
|
|
34
|
+
"types": "./src/utils/*.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"lint": "node ../node_modules/oxlint/bin/oxlint --fix -c ../oxlint.config.ts src",
|
|
39
|
+
"format": "bunx oxfmt src",
|
|
40
|
+
"typecheck": "bunx tsgo --noEmit"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"registry": "https://registry.npmjs.org/"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"ai": "^6.0.116",
|
|
48
|
+
"zod": "^4.3.6"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const ATTACHMENT_MAX_FILE_SIZE_MB = 100
|
|
2
|
+
export const ATTACHMENT_MAX_PER_MESSAGE = 5
|
|
3
|
+
export const ATTACHMENT_MAX_TOTAL_SIZE_MB = 1024
|
|
4
|
+
|
|
5
|
+
export const ATTACHMENT_MAX_FILE_SIZE_BYTES = ATTACHMENT_MAX_FILE_SIZE_MB * 1024 * 1024
|
|
6
|
+
export const ATTACHMENT_MAX_TOTAL_SIZE_BYTES = ATTACHMENT_MAX_TOTAL_SIZE_MB * 1024 * 1024
|
|
7
|
+
|
|
8
|
+
export const SUPPORTED_ATTACHMENT_MIME_TYPES = new Set([
|
|
9
|
+
'application/pdf',
|
|
10
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
11
|
+
'text/plain',
|
|
12
|
+
'text/markdown',
|
|
13
|
+
'text/x-markdown',
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
export const SUPPORTED_ATTACHMENT_EXTENSIONS = new Set(['pdf', 'docx', 'txt', 'md', 'markdown'])
|
|
17
|
+
|
|
18
|
+
export const ATTACHMENT_ACCEPT =
|
|
19
|
+
'application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/plain,text/markdown,text/x-markdown,.pdf,.docx,.txt,.md,.markdown'
|
|
20
|
+
|
|
21
|
+
export interface AttachmentDescriptor {
|
|
22
|
+
name: string
|
|
23
|
+
type?: string | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getAttachmentExtension(name: string): string | null {
|
|
27
|
+
const trimmed = name.trim()
|
|
28
|
+
const lastDot = trimmed.lastIndexOf('.')
|
|
29
|
+
if (lastDot <= 0 || lastDot === trimmed.length - 1) return null
|
|
30
|
+
return trimmed.slice(lastDot + 1).toLowerCase()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isSupportedAttachment(file: AttachmentDescriptor): boolean {
|
|
34
|
+
const mimeType = file.type?.toLowerCase() ?? ''
|
|
35
|
+
if (SUPPORTED_ATTACHMENT_MIME_TYPES.has(mimeType)) return true
|
|
36
|
+
const extension = getAttachmentExtension(file.name)
|
|
37
|
+
return extension ? SUPPORTED_ATTACHMENT_EXTENSIONS.has(extension) : false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function inferContentType(file: AttachmentDescriptor | string): string {
|
|
41
|
+
if (typeof file !== 'string' && file.type?.trim()) return file.type
|
|
42
|
+
const extension = getAttachmentExtension(typeof file === 'string' ? file : file.name)
|
|
43
|
+
if (extension === 'pdf') return 'application/pdf'
|
|
44
|
+
if (extension === 'docx') {
|
|
45
|
+
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
46
|
+
}
|
|
47
|
+
if (extension === 'md' || extension === 'markdown') return 'text/markdown'
|
|
48
|
+
if (extension === 'txt') return 'text/plain'
|
|
49
|
+
return 'application/octet-stream'
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const WORKSTREAM = {
|
|
2
|
+
DEFAULT_TITLE: 'New Workstream',
|
|
3
|
+
DEFAULT_PAGE_LIMIT: 15,
|
|
4
|
+
DEFAULT_MESSAGE_PAGE_LIMIT: 6,
|
|
5
|
+
MAX_MESSAGES_PER_PAGE: 100,
|
|
6
|
+
STREAMING_THROTTLE_MS: 50,
|
|
7
|
+
STREAM_RENDER_THROTTLE_MS: 16,
|
|
8
|
+
STREAM_REVEAL_PROFILE: 'natural-fast',
|
|
9
|
+
STREAM_REVEAL_BASE_MS: 18,
|
|
10
|
+
STREAM_REVEAL_CHAR_DELAY_FACTOR: 0.6,
|
|
11
|
+
STREAM_REVEAL_MAX_CHAR_DELAY_MS: 22,
|
|
12
|
+
STREAM_REVEAL_PUNCTUATION_PAUSE_MS: 18,
|
|
13
|
+
STREAM_REVEAL_MIN_CHARS: 8,
|
|
14
|
+
STREAM_REVEAL_MAX_CHARS: 24,
|
|
15
|
+
STREAM_REVEAL_MAX_TAIL_LATENCY_MS: 900,
|
|
16
|
+
STREAM_EMIT_MIN_CHARS: 36,
|
|
17
|
+
STREAM_EMIT_MAX_CHARS: 72,
|
|
18
|
+
MESSAGE_SEND_THROTTLE_MS: 250,
|
|
19
|
+
LLM_TIMEOUT_MS: 30_000,
|
|
20
|
+
} as const
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './constants/attachments'
|
|
2
|
+
export * from './constants/workstream'
|
|
3
|
+
export * from './runtime/chat-message-metadata'
|
|
4
|
+
export * from './schemas/chat-api'
|
|
5
|
+
export * from './schemas/chat-message'
|
|
6
|
+
export * from './schemas/common'
|
|
7
|
+
export * from './schemas/document'
|
|
8
|
+
export * from './schemas/error'
|
|
9
|
+
export * from './schemas/execution-plan'
|
|
10
|
+
export * from './schemas/recent-activity'
|
|
11
|
+
export * from './schemas/repository-structure'
|
|
12
|
+
export * from './schemas/tools'
|
|
13
|
+
export * from './utils/assistant-text'
|
|
14
|
+
export * from './utils/markdown-normalization'
|
|
15
|
+
export * from './utils/string'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface MessageMetadataLike {
|
|
2
|
+
createdAt?: unknown
|
|
3
|
+
[key: string]: unknown
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function toTimestamp(value: unknown): number {
|
|
7
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
8
|
+
if (typeof value === 'string') {
|
|
9
|
+
const parsed = Date.parse(value)
|
|
10
|
+
if (Number.isFinite(parsed)) return parsed
|
|
11
|
+
}
|
|
12
|
+
if (value instanceof Date) return value.getTime()
|
|
13
|
+
return Date.now()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getMessageCreatedAt(
|
|
17
|
+
message: { metadata?: MessageMetadataLike },
|
|
18
|
+
options?: { fallback?: number },
|
|
19
|
+
): number {
|
|
20
|
+
const createdAt = toTimestamp(message.metadata?.createdAt)
|
|
21
|
+
if (Number.isFinite(createdAt)) return createdAt
|
|
22
|
+
return options?.fallback ?? Date.now()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function withCreatedAtMetadata<TMetadata extends MessageMetadataLike | undefined>(
|
|
26
|
+
metadata: TMetadata,
|
|
27
|
+
fallback = Date.now(),
|
|
28
|
+
): NonNullable<TMetadata> & { createdAt: number } {
|
|
29
|
+
const next =
|
|
30
|
+
metadata && typeof metadata === 'object'
|
|
31
|
+
? ({ ...(metadata as Record<string, unknown>) } as NonNullable<TMetadata>)
|
|
32
|
+
: ({} as NonNullable<TMetadata>)
|
|
33
|
+
|
|
34
|
+
if (typeof next.createdAt !== 'number' || !Number.isFinite(next.createdAt)) {
|
|
35
|
+
next.createdAt = fallback
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return next as NonNullable<TMetadata> & { createdAt: number }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function withMessageCreatedAt<TMessage extends { metadata?: MessageMetadataLike }>(
|
|
42
|
+
message: TMessage,
|
|
43
|
+
fallback = Date.now(),
|
|
44
|
+
): TMessage {
|
|
45
|
+
const metadata = withCreatedAtMetadata(message.metadata, fallback)
|
|
46
|
+
return { ...message, metadata }
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { messageMetadataSchema } from './chat-message'
|
|
4
|
+
|
|
5
|
+
const textChatMessagePartSchema = z.object({ type: z.literal('text'), text: z.string() }).passthrough()
|
|
6
|
+
const reasoningChatMessagePartSchema = z
|
|
7
|
+
.object({ type: z.literal('reasoning'), text: z.string(), state: z.enum(['streaming', 'done']).optional() })
|
|
8
|
+
.passthrough()
|
|
9
|
+
const genericChatMessagePartSchema = z
|
|
10
|
+
.object({ type: z.string().refine((value) => value !== 'text' && value !== 'reasoning', 'Unsupported message part') })
|
|
11
|
+
.passthrough()
|
|
12
|
+
|
|
13
|
+
export const baseChatMessagePartSchema = z.union([
|
|
14
|
+
textChatMessagePartSchema,
|
|
15
|
+
reasoningChatMessagePartSchema,
|
|
16
|
+
genericChatMessagePartSchema,
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
export const baseChatMessageSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
id: z.string(),
|
|
22
|
+
role: z.enum(['system', 'user', 'assistant']),
|
|
23
|
+
parts: z.array(baseChatMessagePartSchema),
|
|
24
|
+
metadata: messageMetadataSchema,
|
|
25
|
+
})
|
|
26
|
+
.passthrough()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { UIDataTypes, UIMessage, UITools } from 'ai'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import type { CoreChatTools } from './tools'
|
|
5
|
+
|
|
6
|
+
const publicWorkstreamStateSchema = z.record(z.string(), z.unknown())
|
|
7
|
+
|
|
8
|
+
export const messageMetadataSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
agentId: z.string().optional(),
|
|
11
|
+
agentName: z.string().optional(),
|
|
12
|
+
reasoningProfile: z.enum(['fast', 'standard', 'deep']).optional(),
|
|
13
|
+
highImpactClasses: z.array(z.string()).optional(),
|
|
14
|
+
policyClasses: z.array(z.string()).optional(),
|
|
15
|
+
semanticTerminationReason: z.enum(['none', 'conflict', 'cycle-detected']).optional(),
|
|
16
|
+
inputTokens: z.number().optional(),
|
|
17
|
+
outputTokens: z.number().optional(),
|
|
18
|
+
totalTokens: z.number().optional(),
|
|
19
|
+
cost: z.number().optional(),
|
|
20
|
+
upstreamInferenceCost: z.number().optional(),
|
|
21
|
+
finishReason: z.string().optional(),
|
|
22
|
+
isCompacted: z.boolean().optional(),
|
|
23
|
+
createdAt: z.number().optional(),
|
|
24
|
+
})
|
|
25
|
+
.optional()
|
|
26
|
+
|
|
27
|
+
export type MessageMetadata = z.infer<typeof messageMetadataSchema>
|
|
28
|
+
|
|
29
|
+
export function parseRowMetadata(raw: unknown): MessageMetadata {
|
|
30
|
+
if (!raw || typeof raw !== 'object') return undefined
|
|
31
|
+
const result = messageMetadataSchema.safeParse(raw)
|
|
32
|
+
return result.success ? result.data : undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const dataPartsSchema = { workstreamTracker: publicWorkstreamStateSchema }
|
|
36
|
+
|
|
37
|
+
export type CustomDataParts = { workstreamTracker: z.infer<typeof publicWorkstreamStateSchema> }
|
|
38
|
+
|
|
39
|
+
export type AnyChatTools = Record<string, { input: unknown; output: unknown }>
|
|
40
|
+
export type HostExtensionChatTools = AnyChatTools
|
|
41
|
+
export type ChatUITools = CoreChatTools & HostExtensionChatTools
|
|
42
|
+
|
|
43
|
+
export type BaseChatMessage<
|
|
44
|
+
TDataParts extends UIDataTypes = CustomDataParts,
|
|
45
|
+
TTools extends UITools = AnyChatTools,
|
|
46
|
+
> = UIMessage<MessageMetadata, TDataParts, TTools>
|
|
47
|
+
|
|
48
|
+
export type BaseChatMessagePart<
|
|
49
|
+
TDataParts extends UIDataTypes = CustomDataParts,
|
|
50
|
+
TTools extends UITools = AnyChatTools,
|
|
51
|
+
> = BaseChatMessage<TDataParts, TTools>['parts'][number]
|
|
52
|
+
|
|
53
|
+
export type AnyChatMessage = BaseChatMessage
|
|
54
|
+
export type ChatMessage = BaseChatMessage<CustomDataParts, ChatUITools>
|
|
55
|
+
export type ChatMessagePart = BaseChatMessagePart<CustomDataParts, ChatUITools>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { WORKSTREAM } from '../constants/workstream'
|
|
4
|
+
|
|
5
|
+
export const unixTimestampSchema = z.coerce.date()
|
|
6
|
+
|
|
7
|
+
const SURREALDB_RECORD_ID_CLASSES = new Set(['RecordId', 'StringRecordId'])
|
|
8
|
+
|
|
9
|
+
function coerceRecordIdToString(val: unknown): unknown {
|
|
10
|
+
if (val === null || val === undefined) return val
|
|
11
|
+
if (typeof val === 'string') return val
|
|
12
|
+
if (typeof val === 'object') {
|
|
13
|
+
const constructorName = val.constructor.name
|
|
14
|
+
if (constructorName && SURREALDB_RECORD_ID_CLASSES.has(constructorName)) {
|
|
15
|
+
return (val as { toString(): string }).toString()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return val
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Record ID schema that accepts:
|
|
23
|
+
* - String format: "table:id" or just "id"
|
|
24
|
+
* - Object format: { tb: string, id: string | number }
|
|
25
|
+
* - SurrealDB RecordId/StringRecordId objects (coerced via toString)
|
|
26
|
+
*/
|
|
27
|
+
export const recordIdSchema = z.preprocess(
|
|
28
|
+
(val) => {
|
|
29
|
+
if (val === null || val === undefined) return val
|
|
30
|
+
if (typeof val === 'string') return val
|
|
31
|
+
if (typeof val === 'object') {
|
|
32
|
+
const constructorName = val.constructor.name
|
|
33
|
+
// Only coerce known SurrealDB RecordId classes
|
|
34
|
+
if (constructorName && SURREALDB_RECORD_ID_CLASSES.has(constructorName)) {
|
|
35
|
+
return (val as { toString(): string }).toString()
|
|
36
|
+
}
|
|
37
|
+
// Handle plain { tb, id } objects
|
|
38
|
+
const record = val as { tb?: unknown; id?: unknown }
|
|
39
|
+
if (typeof record.tb === 'string' && (typeof record.id === 'string' || typeof record.id === 'number')) {
|
|
40
|
+
return val
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return val
|
|
44
|
+
},
|
|
45
|
+
z.union([z.string(), z.object({ tb: z.string(), id: z.union([z.string(), z.number()]) })]),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export const recordIdStringSchema = z.preprocess(coerceRecordIdToString, z.string())
|
|
49
|
+
|
|
50
|
+
export const paginationTakeSchema = z.coerce.number().int().min(1).max(WORKSTREAM.MAX_MESSAGES_PER_PAGE)
|
|
51
|
+
export const paginationPageSchema = z.coerce.number().int().min(1)
|
|
52
|
+
export const paginationCursorSchema = z.preprocess((value) => value, recordIdSchema)
|
|
53
|
+
|
|
54
|
+
export const paginationSchemaBase = z.object({
|
|
55
|
+
page: paginationPageSchema.optional(),
|
|
56
|
+
cursor: paginationCursorSchema.optional(),
|
|
57
|
+
take: paginationTakeSchema.default(WORKSTREAM.DEFAULT_PAGE_LIMIT),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export const paginationSchema = paginationSchemaBase.refine(
|
|
61
|
+
(value) => !(value.page !== undefined && value.cursor !== undefined),
|
|
62
|
+
{ message: 'Provide either page or cursor, not both', path: ['cursor'] },
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
export const installUrlResponseSchema = z.object({ url: z.string().url() }).meta({ id: 'InstallUrlResponseSchema' })
|
|
66
|
+
|
|
67
|
+
export type InstallUrlResponse = z.infer<typeof installUrlResponseSchema>
|
|
68
|
+
|
|
69
|
+
export const successResponseSchema = z.object({ success: z.boolean() }).meta({ id: 'SuccessResponseSchema' })
|
|
70
|
+
|
|
71
|
+
export type SuccessResponse = z.infer<typeof successResponseSchema>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const DocumentRenderModeSchema = z.enum(['markdown', 'text', 'pdf']).meta({ id: 'DocumentRenderModeSchema' })
|
|
4
|
+
|
|
5
|
+
export const DocumentKnowledgeStatusSchema = z
|
|
6
|
+
.enum(['processing', 'ready', 'error'])
|
|
7
|
+
.meta({ id: 'DocumentKnowledgeStatusSchema' })
|
|
8
|
+
|
|
9
|
+
export const DocumentRecordSchema = z
|
|
10
|
+
.object({
|
|
11
|
+
id: z.string().optional(),
|
|
12
|
+
title: z.string(),
|
|
13
|
+
relativePath: z.string(),
|
|
14
|
+
mediaType: z.string(),
|
|
15
|
+
renderMode: DocumentRenderModeSchema,
|
|
16
|
+
knowledgeStatus: DocumentKnowledgeStatusSchema,
|
|
17
|
+
storageKey: z.string(),
|
|
18
|
+
sizeBytes: z.number().int().nonnegative().optional(),
|
|
19
|
+
createdAt: z.iso.datetime(),
|
|
20
|
+
updatedAt: z.iso.datetime(),
|
|
21
|
+
})
|
|
22
|
+
.meta({ id: 'DocumentRecordSchema' })
|
|
23
|
+
|
|
24
|
+
export const DocumentChunkRecordSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
id: z.string().optional(),
|
|
27
|
+
documentId: z.string(),
|
|
28
|
+
sourceCanonicalKey: z.string(),
|
|
29
|
+
sourceVersionKey: z.string(),
|
|
30
|
+
chunkKey: z.string(),
|
|
31
|
+
chunkIndex: z.number().int(),
|
|
32
|
+
sectionPath: z.string().optional(),
|
|
33
|
+
pageStart: z.number().int().positive().optional(),
|
|
34
|
+
pageEnd: z.number().int().positive().optional(),
|
|
35
|
+
content: z.string(),
|
|
36
|
+
embedding: z.array(z.number()).optional(),
|
|
37
|
+
contentHash: z.string(),
|
|
38
|
+
tokenEstimate: z.number().int(),
|
|
39
|
+
summary: z.string().optional(),
|
|
40
|
+
keywords: z.array(z.string()).optional(),
|
|
41
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
42
|
+
archivedAt: z.iso.datetime().optional(),
|
|
43
|
+
createdAt: z.iso.datetime(),
|
|
44
|
+
updatedAt: z.iso.datetime(),
|
|
45
|
+
})
|
|
46
|
+
.meta({ id: 'DocumentChunkRecordSchema' })
|
|
47
|
+
|
|
48
|
+
export type DocumentRenderMode = z.infer<typeof DocumentRenderModeSchema>
|
|
49
|
+
export type DocumentKnowledgeStatus = z.infer<typeof DocumentKnowledgeStatusSchema>
|
|
50
|
+
export type DocumentRecord = z.infer<typeof DocumentRecordSchema>
|
|
51
|
+
export type DocumentChunkRecord = z.infer<typeof DocumentChunkRecordSchema>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const ErrorResponseSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
error: z.object({
|
|
6
|
+
code: z.string(),
|
|
7
|
+
message: z.string(),
|
|
8
|
+
issues: z.array(z.object({ path: z.string(), message: z.string() })).optional(),
|
|
9
|
+
}),
|
|
10
|
+
})
|
|
11
|
+
.meta({ id: 'ErrorResponseSchema' })
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { recordIdSchema, unixTimestampSchema } from './common'
|
|
4
|
+
|
|
5
|
+
export const ExecutionPlanStatusSchema = z
|
|
6
|
+
.enum(['draft', 'executing', 'blocked', 'completed', 'aborted'])
|
|
7
|
+
.meta({ id: 'ExecutionPlanStatusSchema' })
|
|
8
|
+
export type ExecutionPlanStatus = z.infer<typeof ExecutionPlanStatusSchema>
|
|
9
|
+
|
|
10
|
+
export const ExecutionPlanTaskStatusSchema = z
|
|
11
|
+
.enum(['pending', 'in-progress', 'completed', 'blocked', 'skipped', 'failed'])
|
|
12
|
+
.meta({ id: 'ExecutionPlanTaskStatusSchema' })
|
|
13
|
+
export type ExecutionPlanTaskStatus = z.infer<typeof ExecutionPlanTaskStatusSchema>
|
|
14
|
+
|
|
15
|
+
export const ExecutionPlanTaskResultStatusSchema = z
|
|
16
|
+
.enum(['not-started', 'success', 'partial', 'failed'])
|
|
17
|
+
.meta({ id: 'ExecutionPlanTaskResultStatusSchema' })
|
|
18
|
+
export type ExecutionPlanTaskResultStatus = z.infer<typeof ExecutionPlanTaskResultStatusSchema>
|
|
19
|
+
|
|
20
|
+
export const ExecutionPlanTaskKindSchema = z
|
|
21
|
+
.enum(['agent', 'user-input', 'plugin'])
|
|
22
|
+
.meta({ id: 'ExecutionPlanTaskKindSchema' })
|
|
23
|
+
export type ExecutionPlanTaskKind = z.infer<typeof ExecutionPlanTaskKindSchema>
|
|
24
|
+
|
|
25
|
+
export const ExecutionPlanTaskOwnerTypeSchema = z
|
|
26
|
+
.enum(['agent', 'plugin', 'user'])
|
|
27
|
+
.meta({ id: 'ExecutionPlanTaskOwnerTypeSchema' })
|
|
28
|
+
export type ExecutionPlanTaskOwnerType = z.infer<typeof ExecutionPlanTaskOwnerTypeSchema>
|
|
29
|
+
|
|
30
|
+
export const ExecutionPlanEventTypeSchema = z
|
|
31
|
+
.enum([
|
|
32
|
+
'plan-created',
|
|
33
|
+
'plan-replaced',
|
|
34
|
+
'plan-status-changed',
|
|
35
|
+
'task-started',
|
|
36
|
+
'task-progress',
|
|
37
|
+
'task-completed',
|
|
38
|
+
'task-failed',
|
|
39
|
+
'task-blocked',
|
|
40
|
+
'task-skipped',
|
|
41
|
+
'task-restarted',
|
|
42
|
+
'replan',
|
|
43
|
+
])
|
|
44
|
+
.meta({ id: 'ExecutionPlanEventTypeSchema' })
|
|
45
|
+
export type ExecutionPlanEventType = z.infer<typeof ExecutionPlanEventTypeSchema>
|
|
46
|
+
|
|
47
|
+
export const ExecutionPlanTaskInputSchema = z.union([z.string(), z.record(z.string(), z.unknown())])
|
|
48
|
+
export type ExecutionPlanTaskInput = z.infer<typeof ExecutionPlanTaskInputSchema>
|
|
49
|
+
|
|
50
|
+
export const ExecutionPlanSchema = z.object({
|
|
51
|
+
id: recordIdSchema,
|
|
52
|
+
organizationId: recordIdSchema,
|
|
53
|
+
workstreamId: recordIdSchema,
|
|
54
|
+
title: z.string(),
|
|
55
|
+
objective: z.string(),
|
|
56
|
+
status: ExecutionPlanStatusSchema,
|
|
57
|
+
leadAgentId: z.string(),
|
|
58
|
+
currentTaskId: recordIdSchema.optional(),
|
|
59
|
+
restartFromTaskId: recordIdSchema.optional(),
|
|
60
|
+
replacedPlanId: recordIdSchema.optional(),
|
|
61
|
+
failureCount: z.number().int().nonnegative(),
|
|
62
|
+
createdAt: unixTimestampSchema,
|
|
63
|
+
updatedAt: unixTimestampSchema.optional(),
|
|
64
|
+
startedAt: unixTimestampSchema.optional(),
|
|
65
|
+
completedAt: unixTimestampSchema.optional(),
|
|
66
|
+
})
|
|
67
|
+
export type ExecutionPlanRecord = z.infer<typeof ExecutionPlanSchema>
|
|
68
|
+
|
|
69
|
+
export const ExecutionPlanTaskSchema = z.object({
|
|
70
|
+
id: recordIdSchema,
|
|
71
|
+
planId: recordIdSchema,
|
|
72
|
+
organizationId: recordIdSchema,
|
|
73
|
+
workstreamId: recordIdSchema,
|
|
74
|
+
position: z.number().int().nonnegative(),
|
|
75
|
+
title: z.string(),
|
|
76
|
+
rationale: z.string(),
|
|
77
|
+
kind: ExecutionPlanTaskKindSchema,
|
|
78
|
+
ownerType: ExecutionPlanTaskOwnerTypeSchema,
|
|
79
|
+
ownerRef: z.string(),
|
|
80
|
+
status: ExecutionPlanTaskStatusSchema,
|
|
81
|
+
resultStatus: ExecutionPlanTaskResultStatusSchema,
|
|
82
|
+
input: ExecutionPlanTaskInputSchema.optional(),
|
|
83
|
+
outputSummary: z.string().optional(),
|
|
84
|
+
blockedReason: z.string().optional(),
|
|
85
|
+
externalRef: z.string().optional(),
|
|
86
|
+
retryCount: z.number().int().nonnegative(),
|
|
87
|
+
idempotencyKey: z.string(),
|
|
88
|
+
createdAt: unixTimestampSchema,
|
|
89
|
+
updatedAt: unixTimestampSchema.optional(),
|
|
90
|
+
startedAt: unixTimestampSchema.optional(),
|
|
91
|
+
completedAt: unixTimestampSchema.optional(),
|
|
92
|
+
})
|
|
93
|
+
export type ExecutionPlanTaskRecord = z.infer<typeof ExecutionPlanTaskSchema>
|
|
94
|
+
|
|
95
|
+
export const ExecutionPlanEventSchema = z.object({
|
|
96
|
+
id: recordIdSchema,
|
|
97
|
+
planId: recordIdSchema,
|
|
98
|
+
taskId: recordIdSchema.optional(),
|
|
99
|
+
eventType: ExecutionPlanEventTypeSchema,
|
|
100
|
+
fromStatus: z.string().optional(),
|
|
101
|
+
toStatus: z.string().optional(),
|
|
102
|
+
message: z.string(),
|
|
103
|
+
detail: z.record(z.string(), z.unknown()).optional(),
|
|
104
|
+
emittedBy: z.string(),
|
|
105
|
+
createdAt: unixTimestampSchema,
|
|
106
|
+
})
|
|
107
|
+
export type ExecutionPlanEventRecord = z.infer<typeof ExecutionPlanEventSchema>
|
|
108
|
+
|
|
109
|
+
export const SerializableExecutionPlanTaskSchema = z.object({
|
|
110
|
+
id: z.string(),
|
|
111
|
+
position: z.number().int().nonnegative(),
|
|
112
|
+
title: z.string(),
|
|
113
|
+
rationale: z.string(),
|
|
114
|
+
kind: ExecutionPlanTaskKindSchema,
|
|
115
|
+
ownerType: ExecutionPlanTaskOwnerTypeSchema,
|
|
116
|
+
ownerRef: z.string(),
|
|
117
|
+
status: ExecutionPlanTaskStatusSchema,
|
|
118
|
+
resultStatus: ExecutionPlanTaskResultStatusSchema,
|
|
119
|
+
input: ExecutionPlanTaskInputSchema.optional(),
|
|
120
|
+
outputSummary: z.string().nullish(),
|
|
121
|
+
blockedReason: z.string().nullish(),
|
|
122
|
+
externalRef: z.string().nullish(),
|
|
123
|
+
retryCount: z.number().int().nonnegative(),
|
|
124
|
+
idempotencyKey: z.string(),
|
|
125
|
+
startedAt: z.iso.datetime().nullish(),
|
|
126
|
+
completedAt: z.iso.datetime().nullish(),
|
|
127
|
+
})
|
|
128
|
+
export type SerializableExecutionPlanTask = z.infer<typeof SerializableExecutionPlanTaskSchema>
|
|
129
|
+
|
|
130
|
+
export const SerializableExecutionPlanEventSchema = z.object({
|
|
131
|
+
id: z.string(),
|
|
132
|
+
taskId: z.string().nullish(),
|
|
133
|
+
eventType: ExecutionPlanEventTypeSchema,
|
|
134
|
+
fromStatus: z.string().nullish(),
|
|
135
|
+
toStatus: z.string().nullish(),
|
|
136
|
+
message: z.string(),
|
|
137
|
+
detail: z.record(z.string(), z.unknown()).nullish(),
|
|
138
|
+
emittedBy: z.string(),
|
|
139
|
+
createdAt: z.iso.datetime(),
|
|
140
|
+
})
|
|
141
|
+
export type SerializableExecutionPlanEvent = z.infer<typeof SerializableExecutionPlanEventSchema>
|
|
142
|
+
|
|
143
|
+
export const SerializableExecutionPlanCarriedTaskSchema = z.object({
|
|
144
|
+
planId: z.string(),
|
|
145
|
+
taskId: z.string(),
|
|
146
|
+
title: z.string(),
|
|
147
|
+
resultStatus: ExecutionPlanTaskResultStatusSchema,
|
|
148
|
+
outputSummary: z.string(),
|
|
149
|
+
})
|
|
150
|
+
export type SerializableExecutionPlanCarriedTask = z.infer<typeof SerializableExecutionPlanCarriedTaskSchema>
|
|
151
|
+
|
|
152
|
+
export const SerializableExecutionPlanProgressSchema = z.object({
|
|
153
|
+
total: z.number().int().nonnegative(),
|
|
154
|
+
pending: z.number().int().nonnegative(),
|
|
155
|
+
inProgress: z.number().int().nonnegative(),
|
|
156
|
+
completed: z.number().int().nonnegative(),
|
|
157
|
+
blocked: z.number().int().nonnegative(),
|
|
158
|
+
failed: z.number().int().nonnegative(),
|
|
159
|
+
skipped: z.number().int().nonnegative(),
|
|
160
|
+
completionRatio: z.number().min(0).max(1).nullish(),
|
|
161
|
+
})
|
|
162
|
+
export type SerializableExecutionPlanProgress = z.infer<typeof SerializableExecutionPlanProgressSchema>
|
|
163
|
+
|
|
164
|
+
export const SerializableExecutionPlanSchema = z.object({
|
|
165
|
+
planId: z.string(),
|
|
166
|
+
workstreamId: z.string(),
|
|
167
|
+
organizationId: z.string(),
|
|
168
|
+
title: z.string(),
|
|
169
|
+
objective: z.string(),
|
|
170
|
+
status: ExecutionPlanStatusSchema,
|
|
171
|
+
leadAgentId: z.string(),
|
|
172
|
+
currentTaskId: z.string().nullish(),
|
|
173
|
+
restartFromTaskId: z.string().nullish(),
|
|
174
|
+
replacedPlanId: z.string().nullish(),
|
|
175
|
+
failureCount: z.number().int().nonnegative(),
|
|
176
|
+
startedAt: z.iso.datetime().nullish(),
|
|
177
|
+
completedAt: z.iso.datetime().nullish(),
|
|
178
|
+
progress: SerializableExecutionPlanProgressSchema,
|
|
179
|
+
tasks: z.array(SerializableExecutionPlanTaskSchema),
|
|
180
|
+
recentEvents: z.array(SerializableExecutionPlanEventSchema),
|
|
181
|
+
carriedTasks: z.array(SerializableExecutionPlanCarriedTaskSchema),
|
|
182
|
+
})
|
|
183
|
+
export type SerializableExecutionPlan = z.infer<typeof SerializableExecutionPlanSchema>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const RecentActivityEventKindSchema = z.enum([
|
|
4
|
+
'workstream.opened',
|
|
5
|
+
'document.opened',
|
|
6
|
+
'website-intelligence.viewed',
|
|
7
|
+
'chat.turn.completed',
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
export const RecentActivityTargetKindSchema = z.enum(['workstream', 'document', 'website-intelligence', 'chat-turn'])
|
|
11
|
+
|
|
12
|
+
export const RecentActivityTitleSourceSchema = z.enum(['system', 'chief'])
|
|
13
|
+
|
|
14
|
+
export const RecentActivitySourceSchema = z.enum(['client', 'system'])
|
|
15
|
+
|
|
16
|
+
export const RecentActivityDeepLinkSearchSchema = z.object({
|
|
17
|
+
chat: z.string().optional(),
|
|
18
|
+
agent: z.string().optional(),
|
|
19
|
+
tab: z.string().optional(),
|
|
20
|
+
docId: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const RecentActivityDeepLinkSchema = z.object({
|
|
24
|
+
route: z.literal('/chat'),
|
|
25
|
+
search: RecentActivityDeepLinkSearchSchema.default({}),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const RecentActivityMetadataSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
agentId: z.string().optional(),
|
|
31
|
+
agentName: z.string().optional(),
|
|
32
|
+
workstreamId: z.string().optional(),
|
|
33
|
+
workstreamTitle: z.string().optional(),
|
|
34
|
+
workstreamMode: z.string().optional(),
|
|
35
|
+
coreType: z.string().optional(),
|
|
36
|
+
documentTitle: z.string().optional(),
|
|
37
|
+
documentFolder: z.string().optional(),
|
|
38
|
+
documentSource: z.string().optional(),
|
|
39
|
+
normalizedWebsiteUrl: z.string().optional(),
|
|
40
|
+
userMessageText: z.string().optional(),
|
|
41
|
+
assistantSummary: z.string().optional(),
|
|
42
|
+
messageId: z.string().optional(),
|
|
43
|
+
})
|
|
44
|
+
.partial()
|
|
45
|
+
|
|
46
|
+
export const RecentActivityEventInputSchema = z.object({
|
|
47
|
+
sourceEventId: z.string().trim().min(1).max(200),
|
|
48
|
+
kind: RecentActivityEventKindSchema,
|
|
49
|
+
targetKind: RecentActivityTargetKindSchema,
|
|
50
|
+
targetId: z.string().trim().min(1).max(200).optional(),
|
|
51
|
+
mergeKey: z.string().trim().min(1).max(200),
|
|
52
|
+
title: z.string().trim().min(1).max(140),
|
|
53
|
+
sourceLabel: z.string().trim().min(1).max(40),
|
|
54
|
+
deepLink: RecentActivityDeepLinkSchema,
|
|
55
|
+
metadata: RecentActivityMetadataSchema.optional(),
|
|
56
|
+
occurredAt: z.string().optional(),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export const RecentActivitySchema = z.object({
|
|
60
|
+
id: z.string(),
|
|
61
|
+
kind: RecentActivityEventKindSchema,
|
|
62
|
+
targetKind: RecentActivityTargetKindSchema,
|
|
63
|
+
targetId: z.string().optional(),
|
|
64
|
+
mergeKey: z.string(),
|
|
65
|
+
title: z.string(),
|
|
66
|
+
systemTitle: z.string(),
|
|
67
|
+
titleSource: RecentActivityTitleSourceSchema,
|
|
68
|
+
sourceLabel: z.string(),
|
|
69
|
+
deepLink: RecentActivityDeepLinkSchema,
|
|
70
|
+
metadata: RecentActivityMetadataSchema.optional(),
|
|
71
|
+
latestEventAt: z.string(),
|
|
72
|
+
titleRefinedAt: z.string().optional(),
|
|
73
|
+
createdAt: z.string(),
|
|
74
|
+
updatedAt: z.string(),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export const RecentActivityEventSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
sourceEventId: z.string(),
|
|
80
|
+
source: RecentActivitySourceSchema,
|
|
81
|
+
kind: RecentActivityEventKindSchema,
|
|
82
|
+
targetKind: RecentActivityTargetKindSchema,
|
|
83
|
+
targetId: z.string().optional(),
|
|
84
|
+
mergeKey: z.string(),
|
|
85
|
+
title: z.string(),
|
|
86
|
+
sourceLabel: z.string(),
|
|
87
|
+
deepLink: RecentActivityDeepLinkSchema,
|
|
88
|
+
metadata: RecentActivityMetadataSchema.optional(),
|
|
89
|
+
occurredAt: z.string(),
|
|
90
|
+
createdAt: z.string(),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export type RecentActivityEventKind = z.infer<typeof RecentActivityEventKindSchema>
|
|
94
|
+
export type RecentActivityTargetKind = z.infer<typeof RecentActivityTargetKindSchema>
|
|
95
|
+
export type RecentActivityTitleSource = z.infer<typeof RecentActivityTitleSourceSchema>
|
|
96
|
+
export type RecentActivitySource = z.infer<typeof RecentActivitySourceSchema>
|
|
97
|
+
export type RecentActivityDeepLinkSearch = z.infer<typeof RecentActivityDeepLinkSearchSchema>
|
|
98
|
+
export type RecentActivityDeepLink = z.infer<typeof RecentActivityDeepLinkSchema>
|
|
99
|
+
export type RecentActivityMetadata = z.infer<typeof RecentActivityMetadataSchema>
|
|
100
|
+
export type RecentActivityEventInput = z.infer<typeof RecentActivityEventInputSchema>
|
|
101
|
+
export type RecentActivity = z.infer<typeof RecentActivitySchema>
|
|
102
|
+
export type RecentActivityEvent = z.infer<typeof RecentActivityEventSchema>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const RepositoryStructureShapeSchema = z
|
|
4
|
+
.enum(['single-project', 'monorepo', 'library', 'docs-only', 'uncertain'])
|
|
5
|
+
.meta({ id: 'RepositoryStructureShapeSchema' })
|
|
6
|
+
|
|
7
|
+
export const RepositoryStructureSignalSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
key: z.string().trim().min(1).max(120),
|
|
10
|
+
label: z.string().trim().min(1).max(200),
|
|
11
|
+
reason: z.string().trim().min(1).max(1_000),
|
|
12
|
+
evidencePaths: z.array(z.string().trim().min(1).max(500)).max(20).default([]),
|
|
13
|
+
})
|
|
14
|
+
.meta({ id: 'RepositoryStructureSignalSchema' })
|
|
15
|
+
|
|
16
|
+
export const RepositoryStructureComponentKindSchema = z
|
|
17
|
+
.enum(['app', 'service', 'package', 'library', 'docs', 'unknown'])
|
|
18
|
+
.meta({ id: 'RepositoryStructureComponentKindSchema' })
|
|
19
|
+
|
|
20
|
+
export const RepositoryStructureComponentSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
kind: RepositoryStructureComponentKindSchema,
|
|
23
|
+
name: z.string().trim().min(1).max(200),
|
|
24
|
+
path: z.string().trim().min(1).max(500),
|
|
25
|
+
packageManager: z.string().trim().min(1).max(80).optional(),
|
|
26
|
+
languages: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
|
|
27
|
+
frameworks: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
|
|
28
|
+
reason: z.string().trim().min(1).max(1_000),
|
|
29
|
+
evidencePaths: z.array(z.string().trim().min(1).max(500)).max(20).default([]),
|
|
30
|
+
})
|
|
31
|
+
.meta({ id: 'RepositoryStructureComponentSchema' })
|
|
32
|
+
|
|
33
|
+
export const RepositoryStructureRepositorySchema = z
|
|
34
|
+
.object({
|
|
35
|
+
repositoryKey: z.string().trim().min(1).max(200),
|
|
36
|
+
repositoryFullName: z.string().trim().min(1).max(200),
|
|
37
|
+
defaultBranch: z.string().trim().min(1).max(120),
|
|
38
|
+
indexBranch: z.string().trim().min(1).max(120),
|
|
39
|
+
commitSha: z.string().trim().min(1).max(120).optional(),
|
|
40
|
+
})
|
|
41
|
+
.meta({ id: 'RepositoryStructureRepositorySchema' })
|
|
42
|
+
|
|
43
|
+
export const RepositoryStructureArtifactSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
schemaVersion: z.literal('repository-structure.v1'),
|
|
46
|
+
extractorVersion: z.string().trim().min(1).max(120),
|
|
47
|
+
generatedAt: z.string().datetime().optional(),
|
|
48
|
+
repository: RepositoryStructureRepositorySchema,
|
|
49
|
+
topLevelEntries: z.array(z.string().trim().min(1).max(200)).max(200).default([]),
|
|
50
|
+
repoShape: z.object({
|
|
51
|
+
kind: RepositoryStructureShapeSchema,
|
|
52
|
+
reason: z.string().trim().min(1).max(1_000),
|
|
53
|
+
evidencePaths: z.array(z.string().trim().min(1).max(500)).max(20).default([]),
|
|
54
|
+
}),
|
|
55
|
+
packageManagers: z.array(RepositoryStructureSignalSchema).max(40).default([]),
|
|
56
|
+
languages: z.array(RepositoryStructureSignalSchema).max(40).default([]),
|
|
57
|
+
frameworks: z.array(RepositoryStructureSignalSchema).max(60).default([]),
|
|
58
|
+
components: z.array(RepositoryStructureComponentSchema).max(200).default([]),
|
|
59
|
+
entryPoints: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
60
|
+
routeSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
61
|
+
apiSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
62
|
+
databaseSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
63
|
+
authSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
64
|
+
jobSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
65
|
+
envFiles: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
66
|
+
dockerFiles: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
67
|
+
ciCdFiles: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
68
|
+
deploymentFiles: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
69
|
+
testSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
70
|
+
lintSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
71
|
+
formatSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
72
|
+
typecheckSignals: z.array(RepositoryStructureSignalSchema).max(80).default([]),
|
|
73
|
+
unknowns: z.array(z.string().trim().min(1).max(500)).max(40).default([]),
|
|
74
|
+
})
|
|
75
|
+
.meta({ id: 'RepositoryStructureArtifactSchema' })
|
|
76
|
+
|
|
77
|
+
export const RepositoryStructureSummarySchema = z
|
|
78
|
+
.object({
|
|
79
|
+
schemaVersion: z.literal('repository-structure-summary.v1'),
|
|
80
|
+
extractorVersion: z.string().trim().min(1).max(120),
|
|
81
|
+
generatedAt: z.string().datetime().optional(),
|
|
82
|
+
repository: RepositoryStructureRepositorySchema,
|
|
83
|
+
repoShape: RepositoryStructureShapeSchema,
|
|
84
|
+
topLevelEntries: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
85
|
+
packageManagers: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
|
|
86
|
+
languages: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
|
|
87
|
+
frameworks: z.array(z.string().trim().min(1).max(80)).max(20).default([]),
|
|
88
|
+
components: z
|
|
89
|
+
.array(
|
|
90
|
+
z.object({
|
|
91
|
+
kind: RepositoryStructureComponentKindSchema,
|
|
92
|
+
name: z.string().trim().min(1).max(200),
|
|
93
|
+
path: z.string().trim().min(1).max(500),
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
.max(40)
|
|
97
|
+
.default([]),
|
|
98
|
+
entryPoints: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
99
|
+
routeSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
100
|
+
apiSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
101
|
+
databaseSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
102
|
+
authSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
103
|
+
jobSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
104
|
+
envFiles: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
105
|
+
dockerFiles: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
106
|
+
ciCdFiles: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
107
|
+
testSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
108
|
+
lintSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
109
|
+
formatSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
110
|
+
typecheckSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
111
|
+
deploymentSignals: z.array(z.string().trim().min(1).max(200)).max(20).default([]),
|
|
112
|
+
unknowns: z.array(z.string().trim().min(1).max(500)).max(20).default([]),
|
|
113
|
+
})
|
|
114
|
+
.meta({ id: 'RepositoryStructureSummarySchema' })
|
|
115
|
+
|
|
116
|
+
export type RepositoryStructureShape = z.infer<typeof RepositoryStructureShapeSchema>
|
|
117
|
+
export type RepositoryStructureSignal = z.infer<typeof RepositoryStructureSignalSchema>
|
|
118
|
+
export type RepositoryStructureComponentKind = z.infer<typeof RepositoryStructureComponentKindSchema>
|
|
119
|
+
export type RepositoryStructureComponent = z.infer<typeof RepositoryStructureComponentSchema>
|
|
120
|
+
export type RepositoryStructureRepository = z.infer<typeof RepositoryStructureRepositorySchema>
|
|
121
|
+
export type RepositoryStructureArtifact = z.infer<typeof RepositoryStructureArtifactSchema>
|
|
122
|
+
export type RepositoryStructureSummary = z.infer<typeof RepositoryStructureSummarySchema>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { baseChatMessageSchema } from './chat-api'
|
|
4
|
+
import type { AnyChatMessage } from './chat-message'
|
|
5
|
+
import {
|
|
6
|
+
ExecutionPlanStatusSchema,
|
|
7
|
+
ExecutionPlanTaskInputSchema,
|
|
8
|
+
ExecutionPlanTaskResultStatusSchema,
|
|
9
|
+
ExecutionPlanTaskStatusSchema,
|
|
10
|
+
SerializableExecutionPlanSchema,
|
|
11
|
+
} from './execution-plan'
|
|
12
|
+
|
|
13
|
+
export const USER_QUESTIONS_TOOL_NAME = 'userQuestions' as const
|
|
14
|
+
export const CONSULT_SPECIALIST_TOOL_NAME = 'consultSpecialist' as const
|
|
15
|
+
export const CONSULT_TEAM_TOOL_NAME = 'consultTeam' as const
|
|
16
|
+
export const CREATE_EXECUTION_PLAN_TOOL_NAME = 'createExecutionPlan' as const
|
|
17
|
+
export const REPLACE_EXECUTION_PLAN_TOOL_NAME = 'replaceExecutionPlan' as const
|
|
18
|
+
export const SET_EXECUTION_TASK_STATUS_TOOL_NAME = 'setExecutionTaskStatus' as const
|
|
19
|
+
export const GET_ACTIVE_EXECUTION_PLAN_TOOL_NAME = 'getActiveExecutionPlan' as const
|
|
20
|
+
export const RESTART_EXECUTION_TASK_TOOL_NAME = 'restartExecutionTask' as const
|
|
21
|
+
|
|
22
|
+
const UserQuestionItemSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
question: z.string(),
|
|
25
|
+
type: z.enum(['single-select', 'multi-select', 'input']),
|
|
26
|
+
options: z.array(z.string()).optional(),
|
|
27
|
+
allowCustomAnswer: z.boolean().optional(),
|
|
28
|
+
})
|
|
29
|
+
.strict()
|
|
30
|
+
|
|
31
|
+
export const UserQuestionsArgsSchema = z.object({ questions: z.array(UserQuestionItemSchema).min(1) }).strict()
|
|
32
|
+
|
|
33
|
+
export const ConsultSpecialistArgsSchema = z
|
|
34
|
+
.object({ agentId: z.string(), task: z.string().trim().min(1).max(4000) })
|
|
35
|
+
.strict()
|
|
36
|
+
|
|
37
|
+
export const ConsultSpecialistResultDataSchema = baseChatMessageSchema
|
|
38
|
+
|
|
39
|
+
export const ConsultTeamResponseSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
agentId: z.string(),
|
|
42
|
+
agentName: z.string().trim().min(1),
|
|
43
|
+
status: z.enum(['running', 'complete', 'error']),
|
|
44
|
+
summary: z.string().trim().optional(),
|
|
45
|
+
error: z.string().trim().optional(),
|
|
46
|
+
message: baseChatMessageSchema.optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict()
|
|
49
|
+
|
|
50
|
+
export const ConsultTeamArgsSchema = z.object({ task: z.string().trim().min(1).max(6000) }).strict()
|
|
51
|
+
|
|
52
|
+
const ExecutionPlanCreateTaskKindSchema = z.enum(['agent', 'user-input'])
|
|
53
|
+
const ExecutionPlanCreateTaskOwnerTypeSchema = z.enum(['agent', 'user'])
|
|
54
|
+
|
|
55
|
+
const ExecutionPlanTaskDefinitionSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
title: z.string().trim().min(1).max(300),
|
|
58
|
+
rationale: z.string().trim().min(1).max(500),
|
|
59
|
+
kind: ExecutionPlanCreateTaskKindSchema,
|
|
60
|
+
ownerType: ExecutionPlanCreateTaskOwnerTypeSchema,
|
|
61
|
+
ownerRef: z.string().trim().min(1).max(200),
|
|
62
|
+
input: ExecutionPlanTaskInputSchema.optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict()
|
|
65
|
+
.refine(
|
|
66
|
+
(task) =>
|
|
67
|
+
(task.kind === 'agent' && task.ownerType === 'agent') ||
|
|
68
|
+
(task.kind === 'user-input' && task.ownerType === 'user'),
|
|
69
|
+
{
|
|
70
|
+
message: 'agent tasks must be owned by an agent and user-input tasks must be owned by a user.',
|
|
71
|
+
path: ['ownerType'],
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const ExecutionPlanToolActionSchema = z.enum([
|
|
76
|
+
'created',
|
|
77
|
+
'replaced',
|
|
78
|
+
'loaded',
|
|
79
|
+
'task-status-updated',
|
|
80
|
+
'task-restarted',
|
|
81
|
+
'none',
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
export const CreateExecutionPlanArgsSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
title: z.string().trim().min(1).max(200),
|
|
87
|
+
objective: z.string().trim().min(1).max(1500),
|
|
88
|
+
tasks: z.array(ExecutionPlanTaskDefinitionSchema).min(1).max(10),
|
|
89
|
+
})
|
|
90
|
+
.strict()
|
|
91
|
+
|
|
92
|
+
export const ReplaceExecutionPlanArgsSchema = z
|
|
93
|
+
.object({
|
|
94
|
+
planId: z.string().trim().min(1),
|
|
95
|
+
reason: z.string().trim().min(1).max(1000),
|
|
96
|
+
title: z.string().trim().min(1).max(200),
|
|
97
|
+
objective: z.string().trim().min(1).max(1500),
|
|
98
|
+
tasks: CreateExecutionPlanArgsSchema.shape.tasks,
|
|
99
|
+
})
|
|
100
|
+
.strict()
|
|
101
|
+
|
|
102
|
+
export const SetExecutionTaskStatusArgsSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
planId: z.string().trim().min(1),
|
|
105
|
+
taskId: z.string().trim().min(1),
|
|
106
|
+
status: ExecutionPlanTaskStatusSchema,
|
|
107
|
+
resultStatus: ExecutionPlanTaskResultStatusSchema.optional(),
|
|
108
|
+
outputSummary: z.string().trim().max(1500).optional(),
|
|
109
|
+
blockedReason: z.string().trim().max(1000).optional(),
|
|
110
|
+
externalRef: z.string().trim().max(300).optional(),
|
|
111
|
+
})
|
|
112
|
+
.strict()
|
|
113
|
+
|
|
114
|
+
export const GetActiveExecutionPlanArgsSchema = z
|
|
115
|
+
.object({ includeEvents: z.boolean().optional().default(false) })
|
|
116
|
+
.strict()
|
|
117
|
+
|
|
118
|
+
export const RestartExecutionTaskArgsSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
planId: z.string().trim().min(1),
|
|
121
|
+
taskId: z.string().trim().min(1),
|
|
122
|
+
resetDownstream: z.boolean().optional().default(true),
|
|
123
|
+
})
|
|
124
|
+
.strict()
|
|
125
|
+
|
|
126
|
+
export const ExecutionPlanToolResultDataSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
action: ExecutionPlanToolActionSchema,
|
|
129
|
+
message: z.string().trim().min(1).optional(),
|
|
130
|
+
changedTaskId: z.string().nullish(),
|
|
131
|
+
plan: SerializableExecutionPlanSchema.nullish(),
|
|
132
|
+
hasPlan: z.boolean(),
|
|
133
|
+
status: ExecutionPlanStatusSchema.nullish(),
|
|
134
|
+
})
|
|
135
|
+
.strict()
|
|
136
|
+
|
|
137
|
+
export const ConsultTeamResultDataSchema = z.object({ responses: z.array(ConsultTeamResponseSchema) }).strict()
|
|
138
|
+
|
|
139
|
+
export type UserQuestionItem = z.infer<typeof UserQuestionItemSchema>
|
|
140
|
+
export type UserQuestionsArgs = z.infer<typeof UserQuestionsArgsSchema>
|
|
141
|
+
export type ConsultSpecialistArgs = z.infer<typeof ConsultSpecialistArgsSchema>
|
|
142
|
+
export type ConsultSpecialistResultData = AnyChatMessage
|
|
143
|
+
export type ConsultTeamArgs = z.infer<typeof ConsultTeamArgsSchema>
|
|
144
|
+
export type ConsultTeamResponseData = Omit<z.infer<typeof ConsultTeamResponseSchema>, 'message'> & {
|
|
145
|
+
message?: AnyChatMessage
|
|
146
|
+
}
|
|
147
|
+
export type ConsultTeamResultData = { responses: ConsultTeamResponseData[] }
|
|
148
|
+
export type CreateExecutionPlanArgs = z.infer<typeof CreateExecutionPlanArgsSchema>
|
|
149
|
+
export type ReplaceExecutionPlanArgs = z.infer<typeof ReplaceExecutionPlanArgsSchema>
|
|
150
|
+
export type SetExecutionTaskStatusArgs = z.infer<typeof SetExecutionTaskStatusArgsSchema>
|
|
151
|
+
export type GetActiveExecutionPlanArgs = z.infer<typeof GetActiveExecutionPlanArgsSchema>
|
|
152
|
+
export type RestartExecutionTaskArgs = z.infer<typeof RestartExecutionTaskArgsSchema>
|
|
153
|
+
export type ExecutionPlanToolResultData = z.infer<typeof ExecutionPlanToolResultDataSchema>
|
|
154
|
+
export type CoreChatTools = {
|
|
155
|
+
userQuestions: { input: UserQuestionsArgs; output: unknown }
|
|
156
|
+
consultSpecialist: { input: ConsultSpecialistArgs; output: ConsultSpecialistResultData }
|
|
157
|
+
consultTeam: { input: ConsultTeamArgs; output: ConsultTeamResultData }
|
|
158
|
+
createExecutionPlan: { input: CreateExecutionPlanArgs; output: ExecutionPlanToolResultData }
|
|
159
|
+
replaceExecutionPlan: { input: ReplaceExecutionPlanArgs; output: ExecutionPlanToolResultData }
|
|
160
|
+
setExecutionTaskStatus: { input: SetExecutionTaskStatusArgs; output: ExecutionPlanToolResultData }
|
|
161
|
+
getActiveExecutionPlan: { input: GetActiveExecutionPlanArgs; output: ExecutionPlanToolResultData }
|
|
162
|
+
restartExecutionTask: { input: RestartExecutionTaskArgs; output: ExecutionPlanToolResultData }
|
|
163
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const INTERNAL_BLOCK_PATTERNS = [
|
|
2
|
+
/<compacted-context[\s\S]*?<\/compacted-context>/gi,
|
|
3
|
+
/<assistant-message[\s\S]*?<\/assistant-message>/gi,
|
|
4
|
+
/<system-message[\s\S]*?<\/system-message>/gi,
|
|
5
|
+
/<run-context[\s\S]*?<\/run-context>/gi,
|
|
6
|
+
/<response-instructions[\s\S]*?<\/response-instructions>/gi,
|
|
7
|
+
/<tool-outcome[\s\S]*?<\/tool-outcome>/gi,
|
|
8
|
+
/<part\b[^>]*type\s*=\s*["']tool[_-](?:call|result|outcome)["'][^>]*>[\s\S]*?<\/part>/gi,
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const DANGLING_INTERNAL_BLOCK_PATTERNS = [
|
|
12
|
+
/<compacted-context[\s\S]*$/gi,
|
|
13
|
+
/<assistant-message[\s\S]*$/gi,
|
|
14
|
+
/<system-message[\s\S]*$/gi,
|
|
15
|
+
/<run-context[\s\S]*$/gi,
|
|
16
|
+
/<response-instructions[\s\S]*$/gi,
|
|
17
|
+
/<tool-outcome[\s\S]*$/gi,
|
|
18
|
+
/<part\b[^>]*type\s*=\s*["']tool[_-](?:call|result|outcome)["'][^>]*[\s\S]*$/gi,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const RAW_TOOL_OBJECT_START_PATTERN = /\{\s*"type"\s*:\s*"tool(?:_|-)(?:call|result|outcome)"/gi
|
|
22
|
+
|
|
23
|
+
function findJsonObjectEnd(input: string, startIndex: number): number | null {
|
|
24
|
+
if (startIndex < 0 || startIndex >= input.length) return null
|
|
25
|
+
if (input[startIndex] !== '{') return null
|
|
26
|
+
|
|
27
|
+
let depth = 0
|
|
28
|
+
let inString = false
|
|
29
|
+
let isEscaped = false
|
|
30
|
+
|
|
31
|
+
for (let index = startIndex; index < input.length; index += 1) {
|
|
32
|
+
const character = input[index]
|
|
33
|
+
if (inString) {
|
|
34
|
+
if (isEscaped) {
|
|
35
|
+
isEscaped = false
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (character === '\\') {
|
|
40
|
+
isEscaped = true
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (character === '"') {
|
|
45
|
+
inString = false
|
|
46
|
+
}
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (character === '"') {
|
|
51
|
+
inString = true
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (character === '{') {
|
|
56
|
+
depth += 1
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (character === '}') {
|
|
61
|
+
depth -= 1
|
|
62
|
+
if (depth === 0) return index
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stripRawToolPayloadObjects(value: string): string {
|
|
71
|
+
if (!value) return value
|
|
72
|
+
|
|
73
|
+
let cursor = 0
|
|
74
|
+
let result = ''
|
|
75
|
+
|
|
76
|
+
while (cursor < value.length) {
|
|
77
|
+
RAW_TOOL_OBJECT_START_PATTERN.lastIndex = cursor
|
|
78
|
+
const match = RAW_TOOL_OBJECT_START_PATTERN.exec(value)
|
|
79
|
+
if (!match) {
|
|
80
|
+
result += value.slice(cursor)
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const start = match.index
|
|
85
|
+
result += value.slice(cursor, start)
|
|
86
|
+
|
|
87
|
+
const end = findJsonObjectEnd(value, start)
|
|
88
|
+
if (end === null) {
|
|
89
|
+
cursor = value.length
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const candidate = value.slice(start, end + 1)
|
|
94
|
+
const looksInternalPayload =
|
|
95
|
+
(/"toolCallId"\s*:/.test(candidate) && /"toolName"\s*:/.test(candidate)) || /"toolResults"\s*:/.test(candidate)
|
|
96
|
+
if (!looksInternalPayload) {
|
|
97
|
+
result += candidate
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cursor = end + 1
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function sanitizeAssistantVisibleText(value: string): string {
|
|
107
|
+
let next = value
|
|
108
|
+
|
|
109
|
+
next = next.replace(/\[REDACTED\]/gi, '')
|
|
110
|
+
|
|
111
|
+
for (const pattern of INTERNAL_BLOCK_PATTERNS) {
|
|
112
|
+
next = next.replace(pattern, '')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const pattern of DANGLING_INTERNAL_BLOCK_PATTERNS) {
|
|
116
|
+
next = next.replace(pattern, '')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
next = stripRawToolPayloadObjects(next)
|
|
120
|
+
|
|
121
|
+
next = next
|
|
122
|
+
.split('\n')
|
|
123
|
+
.filter((line) => line.trim().toLowerCase() !== '[redacted]')
|
|
124
|
+
.join('\n')
|
|
125
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
126
|
+
.trim()
|
|
127
|
+
|
|
128
|
+
return next
|
|
129
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type EvidenceGapsPayload = Partial<Record<'evidence' | 'gaps', string>>
|
|
2
|
+
|
|
3
|
+
const COMBINED_EVIDENCE_GAPS_LANGUAGE = 'evidence-gaps'
|
|
4
|
+
const ADJACENT_EVIDENCE_GAPS_PATTERN = /```(evidence|gaps)\s*\n([\s\S]*?)```\s*```(evidence|gaps)\s*\n([\s\S]*?)```/gi
|
|
5
|
+
const EVIDENCE_GAPS_FENCE_PATTERN = /```(?:evidence-gaps|evidence|gaps)\s*\n[\s\S]*?(?:```|$)/gi
|
|
6
|
+
|
|
7
|
+
function buildCombinedEvidenceGapsFence(
|
|
8
|
+
firstLanguage: string,
|
|
9
|
+
firstCode: string,
|
|
10
|
+
secondLanguage: string,
|
|
11
|
+
secondCode: string,
|
|
12
|
+
): string {
|
|
13
|
+
const payload: EvidenceGapsPayload = {
|
|
14
|
+
evidence: firstLanguage === 'evidence' ? firstCode.trim() : secondLanguage === 'evidence' ? secondCode.trim() : '',
|
|
15
|
+
gaps: firstLanguage === 'gaps' ? firstCode.trim() : secondLanguage === 'gaps' ? secondCode.trim() : '',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `\`\`\`${COMBINED_EVIDENCE_GAPS_LANGUAGE}\n${JSON.stringify(payload)}\n\`\`\``
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeAssistantMarkdown(markdown: string): string {
|
|
22
|
+
return markdown.replace(
|
|
23
|
+
ADJACENT_EVIDENCE_GAPS_PATTERN,
|
|
24
|
+
(match, firstLanguage: string, firstCode: string, secondLanguage: string, secondCode: string) => {
|
|
25
|
+
if (firstLanguage === secondLanguage) {
|
|
26
|
+
return match
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return buildCombinedEvidenceGapsFence(firstLanguage, firstCode, secondLanguage, secondCode)
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function stripEvidenceGapsMarkdown(markdown: string): string {
|
|
35
|
+
return normalizeAssistantMarkdown(markdown)
|
|
36
|
+
.replace(EVIDENCE_GAPS_FENCE_PATTERN, '')
|
|
37
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
38
|
+
.trim()
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert camelCase, PascalCase, kebab-case, or snake_case to human-readable title case.
|
|
3
|
+
*
|
|
4
|
+
* Examples:
|
|
5
|
+
* memoryRemember → Memory Remember
|
|
6
|
+
* consultSpecialist → Consult Specialist
|
|
7
|
+
* researchTopic → Research Topic
|
|
8
|
+
* memory-block-append → Memory Block Append
|
|
9
|
+
* user_questions → User Questions
|
|
10
|
+
*/
|
|
11
|
+
export function toHumanCase(str: string): string {
|
|
12
|
+
return str
|
|
13
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
14
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
15
|
+
.replace(/[-_]/g, ' ')
|
|
16
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
17
|
+
.trim()
|
|
18
|
+
}
|