@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/shared",
3
- "version": "0.4.8",
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.145",
28
+ "ai": "^6.0.167",
29
29
  "zod": "^4.3.6"
30
30
  }
31
31
  }
@@ -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
+ }
@@ -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)).optional().default([]),
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(),
@@ -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.constructor.name
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.constructor.name
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: z.string().trim().min(1).max(500),
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: z.string(),
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: z.string(),
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(z.string(), z.string()).default({}),
610
+ map: z.record(SafePathSchema, safePathOrRootSchema).default({}),
578
611
  })
579
612
  .strict()
580
613
  export type PlanEdgeSpec = z.infer<typeof PlanEdgeSpecSchema>
@@ -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).optional(),
292
- includeArtifacts: z.boolean().default(true).optional(),
293
- includeApprovals: z.boolean().default(true).optional(),
294
- includeCheckpoints: z.boolean().default(false).optional(),
295
- includeValidationIssues: z.boolean().default(true).optional(),
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 = Omit<z.infer<typeof ConsultTeamResponseSchema>, 'message'> & {
415
- message?: AnyChatMessage
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>
@@ -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
- return new Date(millis).toISOString()
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 && typeof value === 'object') {
19
- const maybeToIso = (value as { toISOString?: unknown }).toISOString
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)