@lota-sdk/shared 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/constants/model.ts +1 -1
- package/src/index.ts +1 -0
- package/src/runtime/message-batch.ts +47 -0
- package/src/schemas/artifact.ts +1 -1
- package/src/schemas/common.ts +12 -2
- package/src/schemas/execution-plan.ts +37 -4
- package/src/schemas/tools.ts +8 -18
- package/src/utils/date-time.ts +12 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/shared",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"registry": "https://registry.npmjs.org/"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"ai": "^6.0.
|
|
28
|
+
"ai": "^6.0.167",
|
|
29
29
|
"zod": "^4.3.6"
|
|
30
30
|
}
|
|
31
31
|
}
|
package/src/constants/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OPENAI_REASONING_MODEL_ID = 'openai/gpt-5.4' as const
|
|
1
|
+
export const OPENAI_REASONING_MODEL_ID = 'openrouter/openai/gpt-5.4' as const
|
|
2
2
|
|
|
3
3
|
export const OPENROUTER_GEMINI_FLASH_MODEL_ID = 'openrouter/google/gemini-3-flash-preview' as const
|
|
4
4
|
export const OPENROUTER_TEAM_AGENT_MODEL_ID = 'openrouter/google/gemini-3-flash-preview' as const
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './constants/thread'
|
|
|
4
4
|
export * from './runtime/agent-types'
|
|
5
5
|
export * from './runtime/chat-message-metadata'
|
|
6
6
|
export * from './runtime/execution-plan-result'
|
|
7
|
+
export * from './runtime/message-batch'
|
|
7
8
|
export * from './schemas/agent-plan-draft'
|
|
8
9
|
export * from './schemas/artifact'
|
|
9
10
|
export * from './schemas/chat-api'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface NormalizeMessageListOptions<TMessage> {
|
|
2
|
+
getMessageId: (message: TMessage) => string
|
|
3
|
+
normalizeMessage?: (message: TMessage) => TMessage
|
|
4
|
+
compareMessages?: (left: TMessage, right: TMessage) => number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface NormalizeMessageBatchOptions<TMessage> extends NormalizeMessageListOptions<TMessage> {
|
|
8
|
+
parseMessage: (value: unknown) => TMessage | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeMessageList<TMessage>(
|
|
12
|
+
messages: readonly TMessage[],
|
|
13
|
+
options: NormalizeMessageListOptions<TMessage>,
|
|
14
|
+
): TMessage[] {
|
|
15
|
+
const messagesById = new Map<string, TMessage>()
|
|
16
|
+
|
|
17
|
+
for (const message of messages) {
|
|
18
|
+
const normalizedMessage = options.normalizeMessage ? options.normalizeMessage(message) : message
|
|
19
|
+
messagesById.set(options.getMessageId(normalizedMessage), normalizedMessage)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const dedupedMessages = Array.from(messagesById.values())
|
|
23
|
+
if (!options.compareMessages) {
|
|
24
|
+
return dedupedMessages
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [...dedupedMessages].sort(options.compareMessages)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeMessageBatch<TMessage>(
|
|
31
|
+
raw: unknown,
|
|
32
|
+
options: NormalizeMessageBatchOptions<TMessage>,
|
|
33
|
+
): TMessage[] {
|
|
34
|
+
if (!Array.isArray(raw)) {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parsedMessages: TMessage[] = []
|
|
39
|
+
for (const entry of raw) {
|
|
40
|
+
const parsedMessage = options.parseMessage(entry)
|
|
41
|
+
if (parsedMessage !== null) {
|
|
42
|
+
parsedMessages.push(parsedMessage)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return normalizeMessageList(parsedMessages, options)
|
|
47
|
+
}
|
package/src/schemas/artifact.ts
CHANGED
|
@@ -78,7 +78,7 @@ export const PublishArtifactArgsSchema = z
|
|
|
78
78
|
.refine((value) => value.trim().length > 0, 'Content is required.'),
|
|
79
79
|
canonicalKey: z.string().trim().min(1).max(255).optional(),
|
|
80
80
|
description: z.string().trim().min(1).max(1000).optional(),
|
|
81
|
-
tags: z.array(z.string().trim().min(1).max(100)).
|
|
81
|
+
tags: z.array(z.string().trim().min(1).max(100)).default([]),
|
|
82
82
|
sourceThreadId: z.string().trim().min(1).max(255).optional(),
|
|
83
83
|
sourcePlanRunId: z.string().trim().min(1).max(255).optional(),
|
|
84
84
|
sourcePlanNodeId: z.string().trim().min(1).max(255).optional(),
|
package/src/schemas/common.ts
CHANGED
|
@@ -4,6 +4,16 @@ import { THREAD } from '../constants/thread'
|
|
|
4
4
|
|
|
5
5
|
const SURREALDB_RECORD_ID_CLASSES = new Set(['RecordId', 'StringRecordId'])
|
|
6
6
|
|
|
7
|
+
function getConstructorName(value: object): string | undefined {
|
|
8
|
+
const prototype = Object.getPrototypeOf(value) as { constructor?: unknown } | null
|
|
9
|
+
if (prototype === null) {
|
|
10
|
+
return undefined
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const constructor = prototype.constructor
|
|
14
|
+
return typeof constructor === 'function' && typeof constructor.name === 'string' ? constructor.name : undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export const unixTimestampSchema = z.coerce.date()
|
|
8
18
|
export const isoDateTimeSchema = z.iso.datetime({ offset: true })
|
|
9
19
|
export const transportIdSchema = z.string().trim().min(1)
|
|
@@ -12,7 +22,7 @@ function coerceRecordIdToString(val: unknown): unknown {
|
|
|
12
22
|
if (val === null || val === undefined) return val
|
|
13
23
|
if (typeof val === 'string') return val
|
|
14
24
|
if (typeof val === 'object') {
|
|
15
|
-
const constructorName = val
|
|
25
|
+
const constructorName = getConstructorName(val)
|
|
16
26
|
if (constructorName && SURREALDB_RECORD_ID_CLASSES.has(constructorName)) {
|
|
17
27
|
return (val as { toString(): string }).toString()
|
|
18
28
|
}
|
|
@@ -31,7 +41,7 @@ export const recordIdSchema = z.preprocess(
|
|
|
31
41
|
if (val === null || val === undefined) return val
|
|
32
42
|
if (typeof val === 'string') return val
|
|
33
43
|
if (typeof val === 'object') {
|
|
34
|
-
const constructorName = val
|
|
44
|
+
const constructorName = getConstructorName(val)
|
|
35
45
|
// Only coerce known SurrealDB RecordId classes
|
|
36
46
|
if (constructorName && SURREALDB_RECORD_ID_CLASSES.has(constructorName)) {
|
|
37
47
|
return (val as { toString(): string }).toString()
|
|
@@ -10,6 +10,39 @@ const dbDateTimeSchema = z.coerce.date()
|
|
|
10
10
|
const apiDateTimeSchema = z.string().datetime({ offset: true })
|
|
11
11
|
|
|
12
12
|
const planSchemaLiteralSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
|
|
13
|
+
const PATH_MAX_LENGTH = 500
|
|
14
|
+
const unsafePathSegments = new Set(['__proto__', 'constructor', 'prototype'])
|
|
15
|
+
|
|
16
|
+
// Reject dot-path segments that could mutate Object.prototype when used for nested writes.
|
|
17
|
+
export function isSafePath(path: string): boolean {
|
|
18
|
+
const normalized = path.trim()
|
|
19
|
+
if (normalized.length === 0) return false
|
|
20
|
+
|
|
21
|
+
const segments = normalized.split('.')
|
|
22
|
+
if (segments.some((segment) => segment.trim().length === 0)) return false
|
|
23
|
+
|
|
24
|
+
return segments.every((segment) => {
|
|
25
|
+
const normalizedSegment = segment.trim()
|
|
26
|
+
return normalizedSegment.length > 0 && !unsafePathSegments.has(normalizedSegment)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isSafePathOrRoot(path: string): boolean {
|
|
31
|
+
return path.trim().length === 0 || isSafePath(path)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const SafePathSchema = z
|
|
35
|
+
.string()
|
|
36
|
+
.trim()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(PATH_MAX_LENGTH)
|
|
39
|
+
.refine(isSafePath, { error: 'Path contains unsafe dot-path segments.' })
|
|
40
|
+
|
|
41
|
+
const safePathOrRootSchema = z
|
|
42
|
+
.string()
|
|
43
|
+
.trim()
|
|
44
|
+
.max(PATH_MAX_LENGTH)
|
|
45
|
+
.refine(isSafePathOrRoot, { error: 'Path contains unsafe dot-path segments.' })
|
|
13
46
|
|
|
14
47
|
/** JSON Schema enum literal type */
|
|
15
48
|
export type PlanSchemaLiteral = string | number | boolean | null
|
|
@@ -381,7 +414,7 @@ export type WriteIntentAction = z.infer<typeof WriteIntentActionSchema>
|
|
|
381
414
|
|
|
382
415
|
export const WriteIntentSchema = z
|
|
383
416
|
.object({
|
|
384
|
-
targetPath:
|
|
417
|
+
targetPath: SafePathSchema,
|
|
385
418
|
action: WriteIntentActionSchema,
|
|
386
419
|
payload: z.union([z.record(z.string(), z.unknown()), z.array(z.unknown()), z.string()]),
|
|
387
420
|
})
|
|
@@ -390,7 +423,7 @@ export type WriteIntent = z.infer<typeof WriteIntentSchema>
|
|
|
390
423
|
|
|
391
424
|
export const WriteValidationErrorSchema = z
|
|
392
425
|
.object({
|
|
393
|
-
targetPath:
|
|
426
|
+
targetPath: SafePathSchema,
|
|
394
427
|
action: WriteIntentActionSchema,
|
|
395
428
|
status: z.enum(['pass', 'fail']),
|
|
396
429
|
issues: z.array(
|
|
@@ -407,7 +440,7 @@ export type WriteValidationError = z.infer<typeof WriteValidationErrorSchema>
|
|
|
407
440
|
|
|
408
441
|
export const NodeWorkspaceEntrySchema = z
|
|
409
442
|
.object({
|
|
410
|
-
targetPath:
|
|
443
|
+
targetPath: SafePathSchema,
|
|
411
444
|
action: WriteIntentActionSchema,
|
|
412
445
|
payload: z.unknown(),
|
|
413
446
|
validatedAt: apiDateTimeSchema,
|
|
@@ -574,7 +607,7 @@ export const PlanEdgeSpecSchema = z
|
|
|
574
607
|
source: z.string().trim().min(1).max(200),
|
|
575
608
|
target: z.string().trim().min(1).max(200),
|
|
576
609
|
when: z.string().trim().min(1).max(500).optional(),
|
|
577
|
-
map: z.record(
|
|
610
|
+
map: z.record(SafePathSchema, safePathOrRootSchema).default({}),
|
|
578
611
|
})
|
|
579
612
|
.strict()
|
|
580
613
|
export type PlanEdgeSpec = z.infer<typeof PlanEdgeSpecSchema>
|
package/src/schemas/tools.ts
CHANGED
|
@@ -247,15 +247,7 @@ export const ExecutionPlanArgsSchema = ExecutionPlanBaseArgsSchema.superRefine((
|
|
|
247
247
|
if (value.node === undefined) {
|
|
248
248
|
addMissingExecutionPlanFieldIssue(ctx, 'node', 'node is required for update-node.')
|
|
249
249
|
}
|
|
250
|
-
for (const path of [
|
|
251
|
-
'title',
|
|
252
|
-
'objective',
|
|
253
|
-
'nodes',
|
|
254
|
-
'edges',
|
|
255
|
-
'projectTitle',
|
|
256
|
-
'targetThreadId',
|
|
257
|
-
'reason',
|
|
258
|
-
] as const) {
|
|
250
|
+
for (const path of ['title', 'objective', 'nodes', 'edges', 'projectTitle', 'targetThreadId', 'reason'] as const) {
|
|
259
251
|
if (value[path] !== undefined) {
|
|
260
252
|
addUnexpectedExecutionPlanFieldIssue(
|
|
261
253
|
ctx,
|
|
@@ -288,11 +280,11 @@ export type ExecutionPlanArgs =
|
|
|
288
280
|
export const ExecutionPlanQueryArgsSchema = z
|
|
289
281
|
.object({
|
|
290
282
|
runId: z.string().trim().min(1).optional().describe('Specific plan run to load. Omit to list all active plans.'),
|
|
291
|
-
includeEvents: z.boolean().default(true)
|
|
292
|
-
includeArtifacts: z.boolean().default(true)
|
|
293
|
-
includeApprovals: z.boolean().default(true)
|
|
294
|
-
includeCheckpoints: z.boolean().default(false)
|
|
295
|
-
includeValidationIssues: z.boolean().default(true)
|
|
283
|
+
includeEvents: z.boolean().default(true),
|
|
284
|
+
includeArtifacts: z.boolean().default(true),
|
|
285
|
+
includeApprovals: z.boolean().default(true),
|
|
286
|
+
includeCheckpoints: z.boolean().default(false),
|
|
287
|
+
includeValidationIssues: z.boolean().default(true),
|
|
296
288
|
})
|
|
297
289
|
.strict()
|
|
298
290
|
export type ExecutionPlanQueryArgs = z.infer<typeof ExecutionPlanQueryArgsSchema>
|
|
@@ -411,10 +403,8 @@ export type UserQuestionsArgs = z.infer<typeof UserQuestionsArgsSchema>
|
|
|
411
403
|
export type ConsultSpecialistArgs = z.infer<typeof ConsultSpecialistArgsSchema>
|
|
412
404
|
export type ConsultSpecialistResultData = AnyChatMessage
|
|
413
405
|
export type ConsultTeamArgs = z.infer<typeof ConsultTeamArgsSchema>
|
|
414
|
-
export type ConsultTeamResponseData =
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
export type ConsultTeamResultData = { responses: ConsultTeamResponseData[] }
|
|
406
|
+
export type ConsultTeamResponseData = z.infer<typeof ConsultTeamResponseSchema>
|
|
407
|
+
export type ConsultTeamResultData = z.infer<typeof ConsultTeamResultDataSchema>
|
|
418
408
|
export type SubmitExecutionNodeResultArgs = z.infer<typeof SubmitExecutionNodeResultArgsSchema>
|
|
419
409
|
export type SubmitPlanTurnResultArgs = z.infer<typeof SubmitPlanTurnResultArgsSchema>
|
|
420
410
|
export type ListExecutionPlansSummary = z.infer<typeof ListExecutionPlansSummarySchema>
|
package/src/utils/date-time.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
function hasToIsoString(value: unknown): value is { toISOString: () => string } {
|
|
2
|
+
if (!value || typeof value !== 'object') {
|
|
3
|
+
return false
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return typeof Reflect.get(value, 'toISOString') === 'function'
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export function toIsoDateTimeString(value: unknown): string {
|
|
2
10
|
if (value instanceof Date) {
|
|
3
11
|
return value.toISOString()
|
|
@@ -11,15 +19,13 @@ export function toIsoDateTimeString(value: unknown): string {
|
|
|
11
19
|
// Support unix timestamps (seconds or milliseconds).
|
|
12
20
|
if (typeof value === 'number') {
|
|
13
21
|
const millis = value < 1_000_000_000_000 ? value * 1000 : value
|
|
14
|
-
|
|
22
|
+
// @effect-diagnostics-next-line globalDate:off
|
|
23
|
+
return new globalThis.Date(millis).toISOString()
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// Support objects that expose toISOString (e.g., SurrealDB temporal types).
|
|
18
|
-
if (value
|
|
19
|
-
|
|
20
|
-
if (typeof maybeToIso === 'function') {
|
|
21
|
-
return (value as { toISOString: () => string }).toISOString()
|
|
22
|
-
}
|
|
27
|
+
if (hasToIsoString(value)) {
|
|
28
|
+
return value.toISOString()
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
return String(value)
|