@llumiverse/core 1.3.0 → 1.4.0-dev.20260629.090753Z

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.
Files changed (145) hide show
  1. package/lib/CompletionStream.d.ts +48 -0
  2. package/lib/CompletionStream.d.ts.map +1 -0
  3. package/lib/{esm/CompletionStream.js → CompletionStream.js} +142 -67
  4. package/lib/CompletionStream.js.map +1 -0
  5. package/lib/{types/Driver.d.ts → Driver.d.ts} +38 -5
  6. package/lib/Driver.d.ts.map +1 -0
  7. package/lib/{esm/Driver.js → Driver.js} +151 -106
  8. package/lib/Driver.js.map +1 -0
  9. package/lib/{types/async.d.ts → async.d.ts} +3 -3
  10. package/lib/async.d.ts.map +1 -0
  11. package/lib/{esm/async.js → async.js} +4 -4
  12. package/lib/async.js.map +1 -0
  13. package/lib/{types/conversation-utils.d.ts → conversation-utils.d.ts} +9 -0
  14. package/lib/conversation-utils.d.ts.map +1 -0
  15. package/lib/{esm/conversation-utils.js → conversation-utils.js} +78 -15
  16. package/lib/conversation-utils.js.map +1 -0
  17. package/lib/embeddings.d.ts +58 -0
  18. package/lib/embeddings.d.ts.map +1 -0
  19. package/lib/embeddings.js +131 -0
  20. package/lib/embeddings.js.map +1 -0
  21. package/lib/formatters/commons.d.ts +3 -0
  22. package/lib/formatters/commons.d.ts.map +1 -0
  23. package/lib/formatters/commons.js +4 -0
  24. package/lib/formatters/commons.js.map +1 -0
  25. package/lib/{types/formatters → formatters}/generic.d.ts +2 -2
  26. package/lib/formatters/generic.d.ts.map +1 -0
  27. package/lib/{esm/formatters → formatters}/generic.js +8 -8
  28. package/lib/formatters/generic.js.map +1 -0
  29. package/lib/formatters/index.d.ts +4 -0
  30. package/lib/formatters/index.d.ts.map +1 -0
  31. package/lib/formatters/index.js +4 -0
  32. package/lib/formatters/index.js.map +1 -0
  33. package/lib/{types/formatters → formatters}/nova.d.ts +4 -4
  34. package/lib/formatters/nova.d.ts.map +1 -0
  35. package/lib/{esm/formatters → formatters}/nova.js +19 -17
  36. package/lib/formatters/nova.js.map +1 -0
  37. package/lib/http-agent.d.ts +53 -0
  38. package/lib/http-agent.d.ts.map +1 -0
  39. package/lib/http-agent.js +141 -0
  40. package/lib/http-agent.js.map +1 -0
  41. package/lib/index.d.ts +8 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +7 -0
  44. package/lib/index.js.map +1 -0
  45. package/lib/{types/json.d.ts → json.d.ts} +1 -1
  46. package/lib/json.d.ts.map +1 -0
  47. package/lib/{esm/json.js → json.js} +4 -3
  48. package/lib/json.js.map +1 -0
  49. package/lib/logger.d.ts +3 -0
  50. package/lib/logger.d.ts.map +1 -0
  51. package/lib/logger.js +42 -0
  52. package/lib/logger.js.map +1 -0
  53. package/lib/resolver.d.ts +2 -0
  54. package/lib/resolver.d.ts.map +1 -0
  55. package/lib/{esm/resolver.js → resolver.js} +7 -4
  56. package/lib/resolver.js.map +1 -0
  57. package/lib/stream.d.ts.map +1 -0
  58. package/lib/stream.js.map +1 -0
  59. package/lib/{types/validation.d.ts → validation.d.ts} +1 -1
  60. package/lib/validation.d.ts.map +1 -0
  61. package/lib/validation.js +100 -0
  62. package/lib/validation.js.map +1 -0
  63. package/package.json +28 -38
  64. package/src/CompletionStream.ts +208 -115
  65. package/src/Driver.error.test.ts +101 -72
  66. package/src/Driver.ts +208 -152
  67. package/src/async.ts +30 -30
  68. package/src/conversation-utils.ts +92 -16
  69. package/src/embeddings.ts +157 -0
  70. package/src/formatters/commons.ts +2 -2
  71. package/src/formatters/generic.ts +20 -17
  72. package/src/formatters/index.ts +3 -3
  73. package/src/formatters/nova.ts +53 -51
  74. package/src/http-agent.test.ts +281 -0
  75. package/src/http-agent.ts +177 -0
  76. package/src/index.ts +7 -5
  77. package/src/json.ts +6 -5
  78. package/src/logger.ts +42 -0
  79. package/src/resolver.ts +12 -9
  80. package/src/stream.ts +4 -5
  81. package/src/validation.ts +42 -29
  82. package/lib/cjs/CompletionStream.js +0 -293
  83. package/lib/cjs/CompletionStream.js.map +0 -1
  84. package/lib/cjs/Driver.js +0 -300
  85. package/lib/cjs/Driver.js.map +0 -1
  86. package/lib/cjs/async.js +0 -131
  87. package/lib/cjs/async.js.map +0 -1
  88. package/lib/cjs/conversation-utils.js +0 -579
  89. package/lib/cjs/conversation-utils.js.map +0 -1
  90. package/lib/cjs/formatters/commons.js +0 -7
  91. package/lib/cjs/formatters/commons.js.map +0 -1
  92. package/lib/cjs/formatters/generic.js +0 -59
  93. package/lib/cjs/formatters/generic.js.map +0 -1
  94. package/lib/cjs/formatters/index.js +0 -20
  95. package/lib/cjs/formatters/index.js.map +0 -1
  96. package/lib/cjs/formatters/nova.js +0 -99
  97. package/lib/cjs/formatters/nova.js.map +0 -1
  98. package/lib/cjs/index.js +0 -22
  99. package/lib/cjs/index.js.map +0 -1
  100. package/lib/cjs/json.js +0 -29
  101. package/lib/cjs/json.js.map +0 -1
  102. package/lib/cjs/package.json +0 -3
  103. package/lib/cjs/resolver.js +0 -45
  104. package/lib/cjs/resolver.js.map +0 -1
  105. package/lib/cjs/stream.js +0 -30
  106. package/lib/cjs/stream.js.map +0 -1
  107. package/lib/cjs/validation.js +0 -96
  108. package/lib/cjs/validation.js.map +0 -1
  109. package/lib/esm/CompletionStream.js.map +0 -1
  110. package/lib/esm/Driver.js.map +0 -1
  111. package/lib/esm/async.js.map +0 -1
  112. package/lib/esm/conversation-utils.js.map +0 -1
  113. package/lib/esm/formatters/commons.js +0 -4
  114. package/lib/esm/formatters/commons.js.map +0 -1
  115. package/lib/esm/formatters/generic.js.map +0 -1
  116. package/lib/esm/formatters/index.js +0 -4
  117. package/lib/esm/formatters/index.js.map +0 -1
  118. package/lib/esm/formatters/nova.js.map +0 -1
  119. package/lib/esm/index.js +0 -6
  120. package/lib/esm/index.js.map +0 -1
  121. package/lib/esm/json.js.map +0 -1
  122. package/lib/esm/resolver.js.map +0 -1
  123. package/lib/esm/stream.js.map +0 -1
  124. package/lib/esm/validation.js +0 -88
  125. package/lib/esm/validation.js.map +0 -1
  126. package/lib/types/CompletionStream.d.ts +0 -20
  127. package/lib/types/CompletionStream.d.ts.map +0 -1
  128. package/lib/types/Driver.d.ts.map +0 -1
  129. package/lib/types/async.d.ts.map +0 -1
  130. package/lib/types/conversation-utils.d.ts.map +0 -1
  131. package/lib/types/formatters/commons.d.ts +0 -3
  132. package/lib/types/formatters/commons.d.ts.map +0 -1
  133. package/lib/types/formatters/generic.d.ts.map +0 -1
  134. package/lib/types/formatters/index.d.ts +0 -4
  135. package/lib/types/formatters/index.d.ts.map +0 -1
  136. package/lib/types/formatters/nova.d.ts.map +0 -1
  137. package/lib/types/index.d.ts +0 -6
  138. package/lib/types/index.d.ts.map +0 -1
  139. package/lib/types/json.d.ts.map +0 -1
  140. package/lib/types/resolver.d.ts +0 -2
  141. package/lib/types/resolver.d.ts.map +0 -1
  142. package/lib/types/stream.d.ts.map +0 -1
  143. package/lib/types/validation.d.ts.map +0 -1
  144. /package/lib/{types/stream.d.ts → stream.d.ts} +0 -0
  145. /package/lib/{esm/stream.js → stream.js} +0 -0
@@ -49,6 +49,15 @@ export interface StripOptions {
49
49
  * - N > 0: Truncate text to approximately N tokens (~4 chars/token)
50
50
  */
51
51
  textMaxTokens?: number;
52
+ /**
53
+ * Number of most-recent messages to leave untouched when truncating large
54
+ * text (the active working set). Only messages OLDER than the last
55
+ * `keepRecentMessages` entries get their large strings truncated to
56
+ * `textMaxTokens`. Requires a conversation with a `messages` array.
57
+ * - undefined/0: No turn awareness — truncate every large string (legacy).
58
+ * - N > 0: Keep the last N messages full; truncate older ones.
59
+ */
60
+ keepRecentMessages?: number;
52
61
  }
53
62
 
54
63
  /**
@@ -147,9 +156,7 @@ function isOpenAIBase64ImageBlock(obj: unknown): boolean {
147
156
  if (o.type !== 'image_url') return false;
148
157
  if (!o.image_url || typeof o.image_url !== 'object') return false;
149
158
  const imgUrl = o.image_url as Record<string, unknown>;
150
- return typeof imgUrl.url === 'string' &&
151
- imgUrl.url.startsWith('data:image/') &&
152
- imgUrl.url.includes(';base64,');
159
+ return typeof imgUrl.url === 'string' && imgUrl.url.startsWith('data:image/') && imgUrl.url.includes(';base64,');
153
160
  }
154
161
 
155
162
  /**
@@ -236,7 +243,7 @@ export function setConversationMeta(conversation: unknown, meta: ConversationMet
236
243
  return { [ARRAY_WRAPPER_KEY]: conversation, [META_KEY]: meta };
237
244
  }
238
245
  if (typeof conversation === 'object' && conversation !== null) {
239
- return { ...conversation as object, [META_KEY]: meta };
246
+ return { ...(conversation as object), [META_KEY]: meta };
240
247
  }
241
248
  return conversation;
242
249
  }
@@ -305,7 +312,7 @@ function serializeBinaryForStorage(obj: unknown): unknown {
305
312
  }
306
313
 
307
314
  if (Array.isArray(obj)) {
308
- return obj.map(item => serializeBinaryForStorage(item));
315
+ return obj.map((item) => serializeBinaryForStorage(item));
309
316
  }
310
317
 
311
318
  if (typeof obj === 'object') {
@@ -335,7 +342,7 @@ export function deserializeBinaryFromStorage(obj: unknown): unknown {
335
342
  }
336
343
 
337
344
  if (Array.isArray(obj)) {
338
- return obj.map(item => deserializeBinaryFromStorage(item));
345
+ return obj.map((item) => deserializeBinaryFromStorage(item));
339
346
  }
340
347
 
341
348
  if (typeof obj === 'object') {
@@ -366,7 +373,7 @@ function stripBinaryFromConversationInternal(obj: unknown): unknown {
366
373
  }
367
374
 
368
375
  if (Array.isArray(obj)) {
369
- return obj.map(item => {
376
+ return obj.map((item) => {
370
377
  // Replace entire Bedrock image/document/video blocks with text blocks
371
378
  if (isBedrockImageBlock(item) || isSerializedBedrockImageBlock(item)) {
372
379
  return { text: IMAGE_PLACEHOLDER };
@@ -432,7 +439,7 @@ function stripBase64ImagesFromConversationInternal(obj: unknown): unknown {
432
439
  }
433
440
 
434
441
  if (Array.isArray(obj)) {
435
- return obj.map(item => {
442
+ return obj.map((item) => {
436
443
  // Replace entire OpenAI image_url blocks with text blocks
437
444
  if (isOpenAIBase64ImageBlock(item)) {
438
445
  return { type: 'text', text: IMAGE_PLACEHOLDER };
@@ -496,18 +503,72 @@ export function truncateLargeTextInConversation(obj: unknown, options?: StripOpt
496
503
  }
497
504
 
498
505
  const maxChars = maxTokens * CHARS_PER_TOKEN;
506
+ const keepRecent = options?.keepRecentMessages;
507
+
508
+ // Turn-aware path: keep the most recent `keepRecent` messages untouched (the
509
+ // agent's active working set — the file it just read/wrote, latest
510
+ // diagnostics) and truncate large strings only in older messages. This lets
511
+ // callers use an aggressive cap without blinding the model to current work.
512
+ if (keepRecent && keepRecent > 0) {
513
+ const messages = getConversationMessages(obj);
514
+ if (messages && messages.length > keepRecent) {
515
+ const cutoff = messages.length - keepRecent;
516
+ const truncated = messages.map((m, i) => (i < cutoff ? truncateLargeTextInternal(m, maxChars) : m));
517
+ return setConversationMessages(obj, truncated);
518
+ }
519
+ // No identifiable messages array, or short conversation: leave it full.
520
+ return obj;
521
+ }
522
+
523
+ // Legacy path: truncate every large string in the conversation.
499
524
  return truncateLargeTextInternal(obj, maxChars);
500
525
  }
501
526
 
527
+ /**
528
+ * Extract the message history array from a conversation object, handling the
529
+ * `{ messages: [...] }` shape (Claude/OpenAI), the wrapped-array shape, and a
530
+ * plain array. Returns undefined when no message array is identifiable.
531
+ */
532
+ function getConversationMessages(obj: unknown): unknown[] | undefined {
533
+ if (Array.isArray(obj)) return obj;
534
+ const wrapped = unwrapConversationArray(obj);
535
+ if (wrapped) return wrapped;
536
+ if (typeof obj === 'object' && obj !== null) {
537
+ const messages = (obj as Record<string, unknown>).messages;
538
+ if (Array.isArray(messages)) return messages;
539
+ }
540
+ return undefined;
541
+ }
542
+
543
+ /**
544
+ * Rebuild a conversation with a new message array, matching the shape returned
545
+ * by getConversationMessages.
546
+ */
547
+ function setConversationMessages(obj: unknown, messages: unknown[]): unknown {
548
+ if (Array.isArray(obj)) return messages;
549
+ if (unwrapConversationArray(obj)) {
550
+ return { ...(obj as object), [ARRAY_WRAPPER_KEY]: messages };
551
+ }
552
+ if (typeof obj === 'object' && obj !== null) {
553
+ return { ...(obj as object), messages };
554
+ }
555
+ return obj;
556
+ }
557
+
502
558
  function shouldPreserveMediaPayload(obj: unknown): boolean {
503
559
  if (typeof obj !== 'object' || obj === null) return false;
504
560
 
505
561
  const o = obj as Record<string, unknown>;
506
562
 
507
563
  // Preserved Bedrock binary blocks and their serialized storage wrapper.
508
- if (isBedrockImageBlock(obj) || isSerializedBedrockImageBlock(obj) ||
509
- isBedrockDocumentBlock(obj) || isSerializedBedrockDocumentBlock(obj) ||
510
- isBedrockVideoBlock(obj) || isSerializedBedrockVideoBlock(obj)) {
564
+ if (
565
+ isBedrockImageBlock(obj) ||
566
+ isSerializedBedrockImageBlock(obj) ||
567
+ isBedrockDocumentBlock(obj) ||
568
+ isSerializedBedrockDocumentBlock(obj) ||
569
+ isBedrockVideoBlock(obj) ||
570
+ isSerializedBedrockVideoBlock(obj)
571
+ ) {
511
572
  return true;
512
573
  }
513
574
 
@@ -516,8 +577,12 @@ function shouldPreserveMediaPayload(obj: unknown): boolean {
516
577
  }
517
578
 
518
579
  // Preserved base64 media payloads for OpenAI, Gemini, and Anthropic/Claude.
519
- if (isOpenAIBase64ImageBlock(obj) || isGeminiInlineDataBlock(obj) ||
520
- isAnthropicBase64ImageBlock(obj) || isAnthropicBase64DocumentBlock(obj)) {
580
+ if (
581
+ isOpenAIBase64ImageBlock(obj) ||
582
+ isGeminiInlineDataBlock(obj) ||
583
+ isAnthropicBase64ImageBlock(obj) ||
584
+ isAnthropicBase64DocumentBlock(obj)
585
+ ) {
521
586
  return true;
522
587
  }
523
588
 
@@ -533,13 +598,24 @@ function truncateLargeTextInternal(obj: unknown, maxChars: number): unknown {
533
598
  return obj;
534
599
  }
535
600
  if (obj.length > maxChars) {
536
- return obj.substring(0, maxChars) + TEXT_TRUNCATED_MARKER;
601
+ // Don't cut through a surrogate pair: substring() slices at a UTF-16
602
+ // code-unit index, so a boundary that lands between the two halves of
603
+ // a non-BMP character (emoji, CJK-ext, …) leaves a lone high surrogate.
604
+ // That is invalid Unicode — it can't be UTF-8 encoded losslessly and
605
+ // strict JSON parsers (e.g. Vertex/Gemini) reject the request body as
606
+ // "The input data is not valid json". Step back one unit in that case.
607
+ let end = maxChars;
608
+ const lastUnit = obj.charCodeAt(end - 1);
609
+ if (lastUnit >= 0xd800 && lastUnit <= 0xdbff) {
610
+ end -= 1;
611
+ }
612
+ return obj.substring(0, end) + TEXT_TRUNCATED_MARKER;
537
613
  }
538
614
  return obj;
539
615
  }
540
616
 
541
617
  if (Array.isArray(obj)) {
542
- return obj.map(item => truncateLargeTextInternal(item, maxChars));
618
+ return obj.map((item) => truncateLargeTextInternal(item, maxChars));
543
619
  }
544
620
 
545
621
  if (typeof obj === 'object') {
@@ -608,7 +684,7 @@ function stripHeartbeatsInternal(obj: unknown): unknown {
608
684
  }
609
685
 
610
686
  if (Array.isArray(obj)) {
611
- return obj.map(item => stripHeartbeatsInternal(item));
687
+ return obj.map((item) => stripHeartbeatsInternal(item));
612
688
  }
613
689
 
614
690
  if (typeof obj === 'object') {
@@ -0,0 +1,157 @@
1
+ import type {
2
+ DataSource,
3
+ EmbeddingInput,
4
+ EmbeddingResultItem,
5
+ EmbeddingsOptions,
6
+ EmbeddingsResult,
7
+ EmbeddingTaskType,
8
+ TextEmbeddingInput,
9
+ } from '@llumiverse/common';
10
+ import { readStreamAsBase64, readStreamAsUint8Array } from './stream.js';
11
+
12
+ /** DataSource wrapping an in-memory base64 string. */
13
+ export class Base64DataSource implements DataSource {
14
+ constructor(
15
+ public readonly name: string,
16
+ public readonly mime_type: string,
17
+ private readonly base64: string,
18
+ ) {}
19
+
20
+ async getStream(): Promise<ReadableStream<Uint8Array>> {
21
+ const bytes = Buffer.from(this.base64, 'base64');
22
+ return new ReadableStream<Uint8Array>({
23
+ start(controller) {
24
+ controller.enqueue(new Uint8Array(bytes));
25
+ controller.close();
26
+ },
27
+ });
28
+ }
29
+
30
+ async getURL(): Promise<string> {
31
+ return `data:${this.mime_type};base64,${this.base64}`;
32
+ }
33
+
34
+ async getURI(): Promise<string> {
35
+ return this.getURL();
36
+ }
37
+
38
+ /** Convenience accessor that avoids re-encoding via getStream. */
39
+ getBase64(): string {
40
+ return this.base64;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * DataSource wrapping a remote URL. The URL is exposed via getURL() so
46
+ * drivers can pass it directly to providers that support URL inputs
47
+ * (gs://, s3://, https://). getStream() fetches the URL on demand.
48
+ */
49
+ export class URLDataSource implements DataSource {
50
+ constructor(
51
+ public readonly name: string,
52
+ public readonly mime_type: string,
53
+ private readonly url: string,
54
+ ) {}
55
+
56
+ async getStream(): Promise<ReadableStream<Uint8Array>> {
57
+ const response = await fetch(this.url);
58
+ if (!response.ok) {
59
+ throw new Error(`Failed to fetch ${this.url}: ${response.status} ${response.statusText}`);
60
+ }
61
+ if (!response.body) {
62
+ throw new Error(`Response body is empty for ${this.url}`);
63
+ }
64
+ return response.body as ReadableStream<Uint8Array>;
65
+ }
66
+
67
+ async getURL(): Promise<string> {
68
+ return this.url;
69
+ }
70
+
71
+ async getURI(): Promise<string> {
72
+ return this.url;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Read a DataSource as a base64-encoded string. Equivalent to streaming the
78
+ * source and base64-encoding the bytes.
79
+ */
80
+ export async function dataSourceToBase64(ds: DataSource): Promise<string> {
81
+ if (ds instanceof Base64DataSource) {
82
+ return ds.getBase64();
83
+ }
84
+ return readStreamAsBase64(await ds.getStream());
85
+ }
86
+
87
+ /**
88
+ * Read a DataSource fully into a Uint8Array.
89
+ */
90
+ export async function dataSourceToBytes(ds: DataSource): Promise<Uint8Array> {
91
+ return readStreamAsUint8Array(await ds.getStream());
92
+ }
93
+
94
+ /** Returns the first vector from a single-input EmbeddingsResult. */
95
+ export function firstVector(result: EmbeddingsResult): number[] {
96
+ const first = result.results[0]?.outputs[0]?.values;
97
+ if (!first) {
98
+ throw new Error('EmbeddingsResult contains no vectors');
99
+ }
100
+ return first;
101
+ }
102
+
103
+ /**
104
+ * Validate an EmbeddingsOptions object and return a normalized copy where:
105
+ * - request-level task_type is propagated to text inputs that don't define their own
106
+ * - inputs is guaranteed non-empty
107
+ *
108
+ * Drivers should call this before doing any provider-specific work.
109
+ */
110
+ export function normalizeEmbeddingsOptions(options: EmbeddingsOptions): EmbeddingsOptions {
111
+ if (!options.inputs || options.inputs.length === 0) {
112
+ throw new Error('EmbeddingsOptions.inputs must contain at least one input');
113
+ }
114
+
115
+ const inputs: EmbeddingInput[] = options.inputs.map((input) => {
116
+ if (input.type === 'text') {
117
+ const text = input as TextEmbeddingInput;
118
+ return {
119
+ ...text,
120
+ task_type: text.task_type ?? options.task_type,
121
+ } satisfies TextEmbeddingInput;
122
+ }
123
+ return input;
124
+ });
125
+
126
+ return {
127
+ ...options,
128
+ inputs,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Apply a task-type prompt prefix to a text input. Used by drivers that need
134
+ * to pass task type through the prompt when the model does not accept it as
135
+ * an API parameter (e.g. Vertex AI gemini-embedding-2).
136
+ */
137
+ export function applyTaskTypePrefix(
138
+ text: string,
139
+ taskType: EmbeddingTaskType | undefined,
140
+ mapping: Partial<Record<EmbeddingTaskType, string>>,
141
+ ): string {
142
+ if (!taskType) return text;
143
+ const prefix = mapping[taskType];
144
+ return prefix ? prefix + text : text;
145
+ }
146
+
147
+ /**
148
+ * Group result items so each item lines up with the corresponding input.
149
+ * Helper for drivers that fan out multiple provider calls.
150
+ */
151
+ export function buildEmbeddingsResult(
152
+ model: string,
153
+ results: EmbeddingResultItem[],
154
+ usage?: EmbeddingsResult['usage'],
155
+ ): EmbeddingsResult {
156
+ return { model, results, usage };
157
+ }
@@ -1,5 +1,5 @@
1
- import { JSONSchema } from "@llumiverse/common";
1
+ import type { JSONSchema } from '@llumiverse/common';
2
2
 
3
3
  export function getJSONSafetyNotice(schema: JSONSchema) {
4
- return "The answer must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema, undefined, 2);
4
+ return `The answer must be a JSON object using the following JSON Schema:\n${JSON.stringify(schema, undefined, 2)}`;
5
5
  }
@@ -1,24 +1,26 @@
1
- import { JSONSchema } from "@llumiverse/common";
2
- import { PromptRole, PromptSegment } from "@llumiverse/common";
3
- import { getJSONSafetyNotice } from "./commons.js";
1
+ import type { JSONSchema } from '@llumiverse/common';
2
+ import { PromptRole, type PromptSegment } from '@llumiverse/common';
3
+ import { getJSONSafetyNotice } from './commons.js';
4
4
 
5
5
  interface Labels {
6
- user: string,
7
- system: string,
8
- assistant: string,
9
- safety: string,
10
- instruction: string
6
+ user: string;
7
+ system: string;
8
+ assistant: string;
9
+ safety: string;
10
+ instruction: string;
11
11
  }
12
12
 
13
- export function createTextPromptFormatter(labels: Labels = {
14
- user: "USER",
15
- system: "CONTEXT",
16
- assistant: "ASSISTANT",
17
- safety: "IMPORTANT",
18
- instruction: "INSTRUCTION"
19
- }) {
13
+ export function createTextPromptFormatter(
14
+ labels: Labels = {
15
+ user: 'USER',
16
+ system: 'CONTEXT',
17
+ assistant: 'ASSISTANT',
18
+ safety: 'IMPORTANT',
19
+ instruction: 'INSTRUCTION',
20
+ },
21
+ ) {
20
22
  return function genericTextPrompt(segments: PromptSegment[], schema?: JSONSchema): string {
21
- const isChat = segments.find(m => m.role === PromptRole.assistant);
23
+ const isChat = segments.find((m) => m.role === PromptRole.assistant);
22
24
  const context: string[] = [];
23
25
  const content: string[] = [];
24
26
  const safety: string[] = [];
@@ -59,8 +61,9 @@ export function createTextPromptFormatter(labels: Labels = {
59
61
  out.push(`${labels.safety}: ${safety.join('\n')}`);
60
62
  }
61
63
  return out.join('\n');
62
- }
64
+ };
63
65
  }
64
66
 
65
67
  const formatTextPrompt = createTextPromptFormatter();
68
+
66
69
  export { formatTextPrompt };
@@ -1,3 +1,3 @@
1
- export * from "./commons.js";
2
- export * from "./generic.js";
3
- export * from "./nova.js";
1
+ export * from './commons.js';
2
+ export * from './generic.js';
3
+ export * from './nova.js';
@@ -1,37 +1,37 @@
1
- import { JSONSchema } from "@llumiverse/common";
2
- import { PromptRole, PromptSegment } from "@llumiverse/common";
3
- import { readStreamAsBase64 } from "../stream.js";
4
- import { getJSONSafetyNotice } from "./commons.js";
1
+ import type { JSONSchema } from '@llumiverse/common';
2
+ import { PromptRole, type PromptSegment } from '@llumiverse/common';
3
+ import { readStreamAsBase64 } from '../stream.js';
4
+ import { getJSONSafetyNotice } from './commons.js';
5
5
 
6
6
  export interface NovaMessage {
7
- role: 'user' | 'assistant',
8
- content: NovaMessagePart[]
7
+ role: 'user' | 'assistant';
8
+ content: NovaMessagePart[];
9
9
  }
10
10
 
11
11
  export interface NovaSystemMessage {
12
- text: string
12
+ text: string;
13
13
  }
14
14
 
15
15
  interface NovaMessagePart {
16
- text?: string // only set for text messages
16
+ text?: string; // only set for text messages
17
17
  image?: {
18
- format: "jpeg" | "png" | "gif" | "webp",
18
+ format: 'jpeg' | 'png' | 'gif' | 'webp';
19
19
  source: {
20
- bytes: string //"base64",
21
- }
22
- }
20
+ bytes: string; //"base64",
21
+ };
22
+ };
23
23
  video?: {
24
- format: "mkv" | "mov" | "mp4" | "webm" | "three_gp" | "flv" | "mpeg" | "mpg" | "wmv",
24
+ format: 'mkv' | 'mov' | 'mp4' | 'webm' | 'three_gp' | 'flv' | 'mpeg' | 'mpg' | 'wmv';
25
25
  source: {
26
26
  //Option 1: sending a s3 location
27
27
  s3Location?: {
28
- uri: string, // example: s3://my-bucket/object-key
29
- bucketOwner: string // optional. example: "123456789012"
30
- }
28
+ uri: string; // example: s3://my-bucket/object-key
29
+ bucketOwner: string; // optional. example: "123456789012"
30
+ };
31
31
  //Option 2: sending a base64 encoded video
32
- bytes?: string //"base64",
33
- }
34
- }
32
+ bytes?: string; //"base64",
33
+ };
34
+ };
35
35
  }
36
36
 
37
37
  export interface NovaMessagesPrompt {
@@ -49,36 +49,36 @@ export async function formatNovaPrompt(segments: PromptSegment[], schema?: JSONS
49
49
  const system: string[] = [];
50
50
  const safety: string[] = [];
51
51
  const messages: NovaMessage[] = [];
52
- let negative: string = "";
53
- let mask: string = "";
52
+ let negative: string = '';
53
+ let mask: string = '';
54
54
 
55
55
  for (const segment of segments) {
56
-
57
56
  const parts: NovaMessagePart[] = [];
58
- if (segment.files) for (const f of segment.files) {
59
- //TODO add video support
60
- if (!f.mime_type?.startsWith('image')) {
61
- continue;
62
- }
63
-
64
- const source = await f.getStream();
65
- const data = await readStreamAsBase64(source);
66
- const format = f.mime_type?.split('/')[1] || 'png';
67
-
68
- parts.push({
69
- image: {
70
- format: format as "jpeg" | "png" | "gif" | "webp",
71
- source: {
72
- bytes: data
73
- }
57
+ if (segment.files)
58
+ for (const f of segment.files) {
59
+ //TODO add video support
60
+ if (!f.mime_type?.startsWith('image')) {
61
+ continue;
74
62
  }
75
- })
76
- }
63
+
64
+ const source = await f.getStream();
65
+ const data = await readStreamAsBase64(source);
66
+ const format = f.mime_type?.split('/')[1] || 'png';
67
+
68
+ parts.push({
69
+ image: {
70
+ format: format as 'jpeg' | 'png' | 'gif' | 'webp',
71
+ source: {
72
+ bytes: data,
73
+ },
74
+ },
75
+ });
76
+ }
77
77
 
78
78
  if (segment.content) {
79
79
  parts.push({
80
- text: segment.content
81
- })
80
+ text: segment.content,
81
+ });
82
82
  }
83
83
 
84
84
  if (segment.role === PromptRole.system) {
@@ -96,13 +96,13 @@ export async function formatNovaPrompt(segments: PromptSegment[], schema?: JSONS
96
96
  } else if (segment.role !== PromptRole.tool) {
97
97
  messages.push({
98
98
  role: segment.role,
99
- content: parts
99
+ content: parts,
100
100
  });
101
101
  }
102
102
  }
103
103
 
104
104
  if (schema) {
105
- safety.push("IMPORTANT: " + getJSONSafetyNotice(schema));
105
+ safety.push(`IMPORTANT: ${getJSONSafetyNotice(schema)}`);
106
106
  }
107
107
 
108
108
  // messages must contains at least 1 item. If the prompt does not contains a user message (but only system messages)
@@ -116,7 +116,7 @@ export async function formatNovaPrompt(segments: PromptSegment[], schema?: JSONS
116
116
  messages.push({ content: [{ text: systemMessage }], role: 'user' });
117
117
  systemMessage = safety.join('\n');
118
118
  } else if (safety.length > 0) {
119
- systemMessage = systemMessage + '\n\nIMPORTANT: ' + safety.join('\n');
119
+ systemMessage = `${systemMessage}\n\nIMPORTANT: ${safety.join('\n')}`;
120
120
  }
121
121
 
122
122
  /*start Nova's message to make sure it answers properly in JSON
@@ -124,18 +124,20 @@ export async function formatNovaPrompt(segments: PromptSegment[], schema?: JSONS
124
124
 
125
125
  if (schema) {
126
126
  messages.push({
127
- role: "assistant",
128
- content: [{
129
- text: "{"
130
- }]
127
+ role: 'assistant',
128
+ content: [
129
+ {
130
+ text: '{',
131
+ },
132
+ ],
131
133
  });
132
134
  }
133
135
 
134
136
  // put system messages first and safety last
135
137
  return {
136
- system: systemMessage ? [{ text: systemMessage }] : [{ text: "" }],
138
+ system: systemMessage ? [{ text: systemMessage }] : [{ text: '' }],
137
139
  messages: messages,
138
140
  negative: negative || undefined,
139
141
  mask: mask || undefined,
140
- }
142
+ };
141
143
  }