@ivotoby/postgram-cli 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/pgm.js +99 -19
- package/dist/search-output.js +170 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,16 @@ pgm store "decided to use pgvector" --type memory --tags "decisions,architecture
|
|
|
30
30
|
# Search
|
|
31
31
|
pgm search "pgvector decisions" --limit 5
|
|
32
32
|
|
|
33
|
+
# Agent-friendly output formats
|
|
34
|
+
pgm search "pgvector decisions" --json # compact JSON by default
|
|
35
|
+
pgm search "pgvector decisions" --json --full-response # full API-shaped JSON
|
|
36
|
+
pgm search "pgvector decisions" --toon # compact TOON output
|
|
37
|
+
pgm list --json # compact JSON rows
|
|
38
|
+
pgm list --json --full-response # full API-shaped rows
|
|
39
|
+
pgm list --toon # compact TOON rows
|
|
40
|
+
pgm expand <entity-id> --json # compact graph JSON
|
|
41
|
+
pgm expand <entity-id> --toon # compact TOON graph
|
|
42
|
+
|
|
33
43
|
# Recall by ID
|
|
34
44
|
pgm recall <entity-id>
|
|
35
45
|
|
|
@@ -63,6 +73,14 @@ pgm queue
|
|
|
63
73
|
pgm store "hello" --json
|
|
64
74
|
```
|
|
65
75
|
|
|
76
|
+
Agent-facing `--json` output is compact by default for search, list, task list,
|
|
77
|
+
graph expansion, write acknowledgements, and link acknowledgements. It omits
|
|
78
|
+
token-heavy fields such as timestamps, metadata, nested `entity` objects, and
|
|
79
|
+
raw similarity unless you pass `--full-response`. Use `--toon` on list-like
|
|
80
|
+
commands (`search`, `list`, `task list`, `expand`) when an agent needs the
|
|
81
|
+
smallest readable output. TOON and compacting are CLI-layer formats; the
|
|
82
|
+
Postgram API remains JSON.
|
|
83
|
+
|
|
66
84
|
## Memory Roles
|
|
67
85
|
|
|
68
86
|
Store durable memory:
|
package/dist/pgm.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createPgmClient } from './client.js';
|
|
|
9
9
|
import { handleCliFailure, isJsonMode, parseCommaList, parseJsonObject, printHuman, printJson, readStdinText, resolvePgmConfig, shortId } from './shared.js';
|
|
10
10
|
import { AppError, ErrorCode } from './errors.js';
|
|
11
11
|
import { buildSyncManifest } from './sync-walk.js';
|
|
12
|
+
import { compactEdgeResponse, compactEntityListResponse, compactGraphResponse, compactSearchResponse, compactStoredEntityResponse, entityListResponseToToon, graphResponseToToon, searchResponseToToon } from './search-output.js';
|
|
12
13
|
function formatStoredEntity(entity) {
|
|
13
14
|
return [
|
|
14
15
|
`${entity.type} ${shortId(entity.id)}${entity.status ? ` [${entity.status}]` : ''}`,
|
|
@@ -26,7 +27,8 @@ function formatSearchResults(results) {
|
|
|
26
27
|
for (const result of results) {
|
|
27
28
|
lines.push(`${result.entity.type} ${shortId(result.entity.id)} score=${result.score.toFixed(3)}`);
|
|
28
29
|
lines.push(` ${result.chunk_content}`);
|
|
29
|
-
if (result.entity.content &&
|
|
30
|
+
if (result.entity.content &&
|
|
31
|
+
result.entity.content !== result.chunk_content) {
|
|
30
32
|
lines.push(` entity: ${result.entity.content}`);
|
|
31
33
|
}
|
|
32
34
|
if (result.related && result.related.length > 0) {
|
|
@@ -57,6 +59,11 @@ function formatTaskList(items) {
|
|
|
57
59
|
];
|
|
58
60
|
});
|
|
59
61
|
}
|
|
62
|
+
function validateToonOptions(options, json) {
|
|
63
|
+
if (options.toon === true && (json || options.fullResponse === true)) {
|
|
64
|
+
throw new AppError(ErrorCode.VALIDATION, '--toon cannot be combined with --json or --full-response');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
60
67
|
async function resolveStoreContent(content) {
|
|
61
68
|
if (content !== undefined) {
|
|
62
69
|
return content;
|
|
@@ -209,6 +216,7 @@ program
|
|
|
209
216
|
.option('--source <source>', 'entity source')
|
|
210
217
|
.option('--metadata <json>', 'JSON metadata object')
|
|
211
218
|
.option('--skip-extraction', 'store without ever queueing graph extraction')
|
|
219
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
212
220
|
.action(async (content, options, command) => {
|
|
213
221
|
await runWithClient(command, async (client, json) => {
|
|
214
222
|
const body = await client.storeEntity({
|
|
@@ -223,7 +231,9 @@ program
|
|
|
223
231
|
skip_extraction: options.skipExtraction === true ? true : undefined
|
|
224
232
|
});
|
|
225
233
|
return json
|
|
226
|
-
?
|
|
234
|
+
? options.fullResponse === true
|
|
235
|
+
? body
|
|
236
|
+
: compactStoredEntityResponse(body)
|
|
227
237
|
: formatStoredEntity({
|
|
228
238
|
id: body.entity.id,
|
|
229
239
|
type: body.entity.type,
|
|
@@ -250,8 +260,13 @@ program
|
|
|
250
260
|
.option('--include-archived', 'include archived entities in results')
|
|
251
261
|
.option('--memory-role <role>', 'memory role filter: durable_memory or session_context')
|
|
252
262
|
.option('--include-other-clients-session-context', 'include session context from other clients')
|
|
263
|
+
.option('--full-response', 'emit the full API response instead of compact default output when used with --json')
|
|
264
|
+
.option('--toon', 'emit compact TOON output for lower agent token use; formatting is applied in the CLI, not the API')
|
|
253
265
|
.action(async (query, options, command) => {
|
|
254
266
|
await runWithClient(command, async (client, json) => {
|
|
267
|
+
if (options.toon === true && (json || options.fullResponse === true)) {
|
|
268
|
+
throw new AppError(ErrorCode.VALIDATION, '--toon cannot be combined with --json or --full-response');
|
|
269
|
+
}
|
|
255
270
|
if (options.memoryRole !== undefined &&
|
|
256
271
|
!['durable_memory', 'session_context'].includes(options.memoryRole)) {
|
|
257
272
|
throw new AppError(ErrorCode.VALIDATION, '--memory-role must be durable_memory or session_context');
|
|
@@ -270,7 +285,15 @@ program
|
|
|
270
285
|
memory_role: options.memoryRole,
|
|
271
286
|
include_other_clients_session_context: options.includeOtherClientsSessionContext === true ? true : undefined
|
|
272
287
|
});
|
|
273
|
-
|
|
288
|
+
if (options.toon === true) {
|
|
289
|
+
return searchResponseToToon(compactSearchResponse(body));
|
|
290
|
+
}
|
|
291
|
+
if (json) {
|
|
292
|
+
return options.fullResponse === true
|
|
293
|
+
? body
|
|
294
|
+
: compactSearchResponse(body);
|
|
295
|
+
}
|
|
296
|
+
return formatSearchResults(body.results);
|
|
274
297
|
});
|
|
275
298
|
});
|
|
276
299
|
const memoryCommand = program
|
|
@@ -290,6 +313,7 @@ memoryCommand
|
|
|
290
313
|
.option('--promotable', 'mark this session context as promotable')
|
|
291
314
|
.option('--groom-after <groomAfter>', 'ISO timestamp after which grooming may archive/promote it')
|
|
292
315
|
.option('--expires-at <expiresAt>', 'ISO timestamp after which the context is stale')
|
|
316
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
293
317
|
.action(async (content, options, command) => {
|
|
294
318
|
await runWithClient(command, async (client, json) => {
|
|
295
319
|
const body = await client.storeSessionContext({
|
|
@@ -305,7 +329,9 @@ memoryCommand
|
|
|
305
329
|
expires_at: options.expiresAt
|
|
306
330
|
});
|
|
307
331
|
return json
|
|
308
|
-
?
|
|
332
|
+
? options.fullResponse === true
|
|
333
|
+
? body
|
|
334
|
+
: compactStoredEntityResponse(body)
|
|
309
335
|
: formatStoredEntity({
|
|
310
336
|
id: body.entity.id,
|
|
311
337
|
type: body.entity.type,
|
|
@@ -341,8 +367,11 @@ program
|
|
|
341
367
|
.option('--limit <limit>', 'result limit', '50')
|
|
342
368
|
.option('--offset <offset>', 'result offset', '0')
|
|
343
369
|
.option('--include-archived', 'include archived entities')
|
|
370
|
+
.option('--full-response', 'emit the full API response instead of compact default output when used with --json')
|
|
371
|
+
.option('--toon', 'emit compact TOON output for lower agent token use; formatting is applied in the CLI, not the API')
|
|
344
372
|
.action(async (options, command) => {
|
|
345
373
|
await runWithClient(command, async (client, json) => {
|
|
374
|
+
validateToonOptions(options, json);
|
|
346
375
|
const body = await client.listEntities({
|
|
347
376
|
type: options.type,
|
|
348
377
|
status: options.status,
|
|
@@ -353,8 +382,13 @@ program
|
|
|
353
382
|
offset: Number(options.offset),
|
|
354
383
|
include_archived: options.includeArchived === true ? true : undefined
|
|
355
384
|
});
|
|
385
|
+
if (options.toon === true) {
|
|
386
|
+
return entityListResponseToToon(compactEntityListResponse(body));
|
|
387
|
+
}
|
|
356
388
|
if (json) {
|
|
357
|
-
return
|
|
389
|
+
return options.fullResponse === true
|
|
390
|
+
? body
|
|
391
|
+
: compactEntityListResponse(body);
|
|
358
392
|
}
|
|
359
393
|
if (body.items.length === 0) {
|
|
360
394
|
return ['No entities'];
|
|
@@ -387,6 +421,7 @@ program
|
|
|
387
421
|
.option('--metadata <json>', 'JSON metadata object')
|
|
388
422
|
.option('--version <version>', 'expected version')
|
|
389
423
|
.option('--force', 'retry using the latest version on conflict')
|
|
424
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
390
425
|
.action(async (id, options, command) => {
|
|
391
426
|
await runWithClient(command, async (client, json) => {
|
|
392
427
|
const payload = {
|
|
@@ -412,7 +447,11 @@ program
|
|
|
412
447
|
else {
|
|
413
448
|
throw new Error('--version is required unless --force is set');
|
|
414
449
|
}
|
|
415
|
-
return json
|
|
450
|
+
return json
|
|
451
|
+
? options.fullResponse === true
|
|
452
|
+
? body
|
|
453
|
+
: compactStoredEntityResponse(body)
|
|
454
|
+
: formatStoredEntity(body.entity);
|
|
416
455
|
});
|
|
417
456
|
});
|
|
418
457
|
program
|
|
@@ -436,6 +475,7 @@ taskCommand
|
|
|
436
475
|
.option('--tags <tags>', 'comma-separated tags')
|
|
437
476
|
.option('--visibility <visibility>', 'task visibility', 'shared')
|
|
438
477
|
.option('--metadata <json>', 'JSON metadata object')
|
|
478
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
439
479
|
.action(async (content, options, command) => {
|
|
440
480
|
await runWithClient(command, async (client, json) => {
|
|
441
481
|
const taskContent = await resolveStoreContent(content);
|
|
@@ -449,7 +489,9 @@ taskCommand
|
|
|
449
489
|
metadata: parseJsonObject(options.metadata)
|
|
450
490
|
});
|
|
451
491
|
return json
|
|
452
|
-
?
|
|
492
|
+
? options.fullResponse === true
|
|
493
|
+
? body
|
|
494
|
+
: compactStoredEntityResponse(body)
|
|
453
495
|
: formatStoredEntity({
|
|
454
496
|
id: body.entity.id,
|
|
455
497
|
type: body.entity.type,
|
|
@@ -469,8 +511,11 @@ taskCommand
|
|
|
469
511
|
.option('--limit <limit>', 'result limit', '50')
|
|
470
512
|
.option('--offset <offset>', 'result offset', '0')
|
|
471
513
|
.option('--include-archived', 'include archived tasks')
|
|
514
|
+
.option('--full-response', 'emit the full API response instead of compact default output when used with --json')
|
|
515
|
+
.option('--toon', 'emit compact TOON output for lower agent token use; formatting is applied in the CLI, not the API')
|
|
472
516
|
.action(async (options, command) => {
|
|
473
517
|
await runWithClient(command, async (client, json) => {
|
|
518
|
+
validateToonOptions(options, json);
|
|
474
519
|
const body = await client.listTasks({
|
|
475
520
|
status: options.status,
|
|
476
521
|
context: options.context,
|
|
@@ -478,7 +523,14 @@ taskCommand
|
|
|
478
523
|
offset: Number(options.offset),
|
|
479
524
|
include_archived: options.includeArchived === true ? true : undefined
|
|
480
525
|
});
|
|
481
|
-
|
|
526
|
+
if (options.toon === true) {
|
|
527
|
+
return entityListResponseToToon(compactEntityListResponse(body));
|
|
528
|
+
}
|
|
529
|
+
return json
|
|
530
|
+
? options.fullResponse === true
|
|
531
|
+
? body
|
|
532
|
+
: compactEntityListResponse(body)
|
|
533
|
+
: formatTaskList(body.items);
|
|
482
534
|
});
|
|
483
535
|
});
|
|
484
536
|
taskCommand
|
|
@@ -493,6 +545,7 @@ taskCommand
|
|
|
493
545
|
.option('--visibility <visibility>', 'updated task visibility')
|
|
494
546
|
.option('--metadata <json>', 'JSON metadata object')
|
|
495
547
|
.option('--version <version>', 'expected version', '')
|
|
548
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
496
549
|
.action(async (id, options, command) => {
|
|
497
550
|
await runWithClient(command, async (client, json) => {
|
|
498
551
|
if (!options.version) {
|
|
@@ -509,7 +562,9 @@ taskCommand
|
|
|
509
562
|
metadata: parseJsonObject(options.metadata)
|
|
510
563
|
});
|
|
511
564
|
return json
|
|
512
|
-
?
|
|
565
|
+
? options.fullResponse === true
|
|
566
|
+
? body
|
|
567
|
+
: compactStoredEntityResponse(body)
|
|
513
568
|
: formatStoredEntity({
|
|
514
569
|
id: body.entity.id,
|
|
515
570
|
type: body.entity.type,
|
|
@@ -526,6 +581,7 @@ taskCommand
|
|
|
526
581
|
.description('Mark a task complete')
|
|
527
582
|
.argument('id', 'task ID')
|
|
528
583
|
.option('--version <version>', 'expected version')
|
|
584
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
529
585
|
.action(async (id, options, command) => {
|
|
530
586
|
await runWithClient(command, async (client, json) => {
|
|
531
587
|
if (options.version === undefined) {
|
|
@@ -533,7 +589,9 @@ taskCommand
|
|
|
533
589
|
}
|
|
534
590
|
const body = await client.completeTask(id, Number(options.version));
|
|
535
591
|
return json
|
|
536
|
-
?
|
|
592
|
+
? options.fullResponse === true
|
|
593
|
+
? body
|
|
594
|
+
: compactStoredEntityResponse(body)
|
|
537
595
|
: formatStoredEntity({
|
|
538
596
|
id: body.entity.id,
|
|
539
597
|
type: body.entity.type,
|
|
@@ -554,7 +612,9 @@ program
|
|
|
554
612
|
if (json)
|
|
555
613
|
return body;
|
|
556
614
|
const e = body.embedding;
|
|
557
|
-
const age = e.oldest_pending_secs !== null
|
|
615
|
+
const age = e.oldest_pending_secs !== null
|
|
616
|
+
? ` oldest_pending=${e.oldest_pending_secs}s`
|
|
617
|
+
: '';
|
|
558
618
|
const lines = [
|
|
559
619
|
`embedding: pending=${e.pending} completed=${e.completed} failed=${e.failed} retry_eligible=${e.retry_eligible}${age}`
|
|
560
620
|
];
|
|
@@ -603,8 +663,14 @@ program
|
|
|
603
663
|
const manifest = await buildSyncManifest(resolvedDir);
|
|
604
664
|
const config = await resolvePgmConfig();
|
|
605
665
|
const client = createPgmClient(config);
|
|
606
|
-
const manifestForServer = manifest.map(({ path: p, sha }) => ({
|
|
607
|
-
|
|
666
|
+
const manifestForServer = manifest.map(({ path: p, sha }) => ({
|
|
667
|
+
path: p,
|
|
668
|
+
sha
|
|
669
|
+
}));
|
|
670
|
+
const diff = await client.diffSync({
|
|
671
|
+
repo: repoName,
|
|
672
|
+
files: manifestForServer
|
|
673
|
+
});
|
|
608
674
|
if (options.dryRun) {
|
|
609
675
|
const newCount = diff.toUpload.filter((f) => f.reason === 'new').length;
|
|
610
676
|
const changedCount = diff.toUpload.filter((f) => f.reason === 'changed').length;
|
|
@@ -650,7 +716,8 @@ program
|
|
|
650
716
|
}
|
|
651
717
|
const content = await fsReadFile(entry.fullPath, 'utf8');
|
|
652
718
|
const size = Buffer.byteLength(content, 'utf8');
|
|
653
|
-
if (batch.length > 0 &&
|
|
719
|
+
if (batch.length > 0 &&
|
|
720
|
+
(batch.length >= BATCH_FILES || batchBytes + size > BATCH_BYTES)) {
|
|
654
721
|
await flushBatch();
|
|
655
722
|
}
|
|
656
723
|
batch.push({ path: toUpload.path, sha: toUpload.sha, content });
|
|
@@ -687,6 +754,7 @@ program
|
|
|
687
754
|
.argument('<target-id>', 'target entity ID')
|
|
688
755
|
.requiredOption('--relation <relation>', 'relationship type')
|
|
689
756
|
.option('--confidence <n>', 'confidence score 0-1', '1.0')
|
|
757
|
+
.option('--full-response', 'emit the full API response when used with --json')
|
|
690
758
|
.action(async (sourceId, targetId, options, command) => {
|
|
691
759
|
await runWithClient(command, async (client, json) => {
|
|
692
760
|
const body = await client.createEdge({
|
|
@@ -695,9 +763,12 @@ program
|
|
|
695
763
|
relation: options.relation,
|
|
696
764
|
confidence: Number(options.confidence)
|
|
697
765
|
});
|
|
698
|
-
if (json)
|
|
699
|
-
return body;
|
|
700
|
-
|
|
766
|
+
if (json) {
|
|
767
|
+
return options.fullResponse === true ? body : compactEdgeResponse(body);
|
|
768
|
+
}
|
|
769
|
+
return [
|
|
770
|
+
`Linked ${shortId(sourceId)} → ${shortId(targetId)} (${options.relation})`
|
|
771
|
+
];
|
|
701
772
|
});
|
|
702
773
|
});
|
|
703
774
|
program
|
|
@@ -717,16 +788,25 @@ program
|
|
|
717
788
|
.option('--depth <n>', 'traversal depth (1-3)', '1')
|
|
718
789
|
.option('--relation <types>', 'comma-separated relation types')
|
|
719
790
|
.option('--owner <owner>', 'owner filter')
|
|
791
|
+
.option('--full-response', 'emit the full API response instead of compact default output when used with --json')
|
|
792
|
+
.option('--toon', 'emit compact TOON output for lower agent token use; formatting is applied in the CLI, not the API')
|
|
720
793
|
.action(async (entityId, options, command) => {
|
|
721
794
|
await runWithClient(command, async (client, json) => {
|
|
795
|
+
validateToonOptions(options, json);
|
|
722
796
|
const relationTypes = parseCommaList(options.relation);
|
|
723
797
|
const body = await client.expandGraph(entityId, {
|
|
724
798
|
depth: Number(options.depth),
|
|
725
799
|
...(relationTypes !== undefined ? { relationTypes } : {}),
|
|
726
800
|
...(options.owner !== undefined ? { owner: options.owner } : {})
|
|
727
801
|
});
|
|
728
|
-
if (
|
|
729
|
-
return body;
|
|
802
|
+
if (options.toon === true) {
|
|
803
|
+
return graphResponseToToon(compactGraphResponse(body));
|
|
804
|
+
}
|
|
805
|
+
if (json) {
|
|
806
|
+
return options.fullResponse === true
|
|
807
|
+
? body
|
|
808
|
+
: compactGraphResponse(body);
|
|
809
|
+
}
|
|
730
810
|
const lines = [];
|
|
731
811
|
lines.push(`Graph for ${shortId(entityId)}:`);
|
|
732
812
|
lines.push(` ${body.entities.length} entities, ${body.edges.length} edges`);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export function compactStoredEntity(entity, options = {}) {
|
|
2
|
+
return {
|
|
3
|
+
id: entity.id,
|
|
4
|
+
type: entity.type,
|
|
5
|
+
...(entity.version !== undefined ? { version: entity.version } : {}),
|
|
6
|
+
...(options.includeContent ? { content: entity.content } : {}),
|
|
7
|
+
...(entity.status ? { status: entity.status } : {}),
|
|
8
|
+
...(entity.visibility ? { visibility: entity.visibility } : {}),
|
|
9
|
+
...(entity.owner ? { owner: entity.owner } : {}),
|
|
10
|
+
...(entity.enrichment_status
|
|
11
|
+
? { enrichment_status: entity.enrichment_status }
|
|
12
|
+
: {}),
|
|
13
|
+
...(entity.tags?.length ? { tags: entity.tags } : {}),
|
|
14
|
+
...(entity.source ? { source: entity.source } : {})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function compactStoredEntityResponse(response) {
|
|
18
|
+
return {
|
|
19
|
+
entity: compactStoredEntity(response.entity)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function compactEntityListResponse(response) {
|
|
23
|
+
return {
|
|
24
|
+
items: response.items.map((entity) => compactStoredEntity(entity, { includeContent: true })),
|
|
25
|
+
total: response.total,
|
|
26
|
+
limit: response.limit,
|
|
27
|
+
offset: response.offset
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function compactEdge(edge) {
|
|
31
|
+
return {
|
|
32
|
+
id: edge.id,
|
|
33
|
+
source_id: edge.source_id,
|
|
34
|
+
target_id: edge.target_id,
|
|
35
|
+
relation: edge.relation,
|
|
36
|
+
...(edge.confidence !== undefined ? { confidence: edge.confidence } : {})
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function compactEdgeResponse(response) {
|
|
40
|
+
return {
|
|
41
|
+
edge: compactEdge(response.edge)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function compactGraphResponse(response) {
|
|
45
|
+
return {
|
|
46
|
+
entities: response.entities.map((entity) => ({
|
|
47
|
+
id: entity.id,
|
|
48
|
+
type: entity.type,
|
|
49
|
+
content: entity.content
|
|
50
|
+
})),
|
|
51
|
+
edges: response.edges.map(compactEdge)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function compactSearchResponse(response) {
|
|
55
|
+
return {
|
|
56
|
+
results: response.results.map((entry) => ({
|
|
57
|
+
id: entry.entity.id,
|
|
58
|
+
type: entry.entity.type,
|
|
59
|
+
score: entry.score,
|
|
60
|
+
content: entry.entity.content,
|
|
61
|
+
chunk: entry.chunk_content,
|
|
62
|
+
...(entry.entity.tags?.length ? { tags: entry.entity.tags } : {}),
|
|
63
|
+
...(entry.related?.length
|
|
64
|
+
? {
|
|
65
|
+
related: entry.related.map((related) => ({
|
|
66
|
+
id: related.entity.id,
|
|
67
|
+
type: related.entity.type,
|
|
68
|
+
relation: related.relation,
|
|
69
|
+
direction: related.direction,
|
|
70
|
+
content: related.entity.content
|
|
71
|
+
}))
|
|
72
|
+
}
|
|
73
|
+
: {})
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function toonScalar(value) {
|
|
78
|
+
if (value === null || value === undefined) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
let scalar;
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
scalar = value.join('|');
|
|
84
|
+
}
|
|
85
|
+
else if (typeof value === 'object') {
|
|
86
|
+
scalar = JSON.stringify(value);
|
|
87
|
+
}
|
|
88
|
+
else if (typeof value === 'string') {
|
|
89
|
+
scalar = value;
|
|
90
|
+
}
|
|
91
|
+
else if (typeof value === 'number' ||
|
|
92
|
+
typeof value === 'boolean' ||
|
|
93
|
+
typeof value === 'bigint') {
|
|
94
|
+
scalar = value.toString();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
scalar = JSON.stringify(value);
|
|
98
|
+
}
|
|
99
|
+
return /[,\n\r"]/u.test(scalar) ? JSON.stringify(scalar) : scalar;
|
|
100
|
+
}
|
|
101
|
+
export function searchResponseToToon(response) {
|
|
102
|
+
const lines = [
|
|
103
|
+
`results[${response.results.length}]{id,type,score,content,chunk,tags,related}:`
|
|
104
|
+
];
|
|
105
|
+
for (const result of response.results) {
|
|
106
|
+
lines.push([
|
|
107
|
+
result.id,
|
|
108
|
+
result.type,
|
|
109
|
+
Number.isFinite(result.score)
|
|
110
|
+
? Number(result.score.toFixed(6))
|
|
111
|
+
: result.score,
|
|
112
|
+
result.content,
|
|
113
|
+
result.chunk,
|
|
114
|
+
result.tags,
|
|
115
|
+
result.related?.length ? `${result.related.length} related` : ''
|
|
116
|
+
]
|
|
117
|
+
.map(toonScalar)
|
|
118
|
+
.join(','));
|
|
119
|
+
if (result.related?.length) {
|
|
120
|
+
lines.push(` related[${result.related.length}]{id,type,relation,direction,content}:`);
|
|
121
|
+
for (const related of result.related) {
|
|
122
|
+
lines.push(` ${[
|
|
123
|
+
related.id,
|
|
124
|
+
related.type,
|
|
125
|
+
related.relation,
|
|
126
|
+
related.direction,
|
|
127
|
+
related.content
|
|
128
|
+
]
|
|
129
|
+
.map(toonScalar)
|
|
130
|
+
.join(',')}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
export function entityListResponseToToon(response) {
|
|
137
|
+
const lines = [
|
|
138
|
+
`items[${response.items.length}]{id,type,status,version,content,tags,owner}:`
|
|
139
|
+
];
|
|
140
|
+
for (const item of response.items) {
|
|
141
|
+
lines.push([
|
|
142
|
+
item.id,
|
|
143
|
+
item.type,
|
|
144
|
+
item.status,
|
|
145
|
+
item.version,
|
|
146
|
+
item.content,
|
|
147
|
+
item.tags,
|
|
148
|
+
item.owner
|
|
149
|
+
]
|
|
150
|
+
.map(toonScalar)
|
|
151
|
+
.join(','));
|
|
152
|
+
}
|
|
153
|
+
lines.push(`total,${response.total}`);
|
|
154
|
+
lines.push(`limit,${response.limit}`);
|
|
155
|
+
lines.push(`offset,${response.offset}`);
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
export function graphResponseToToon(response) {
|
|
159
|
+
const lines = [`entities[${response.entities.length}]{id,type,content}:`];
|
|
160
|
+
for (const entity of response.entities) {
|
|
161
|
+
lines.push([entity.id, entity.type, entity.content].map(toonScalar).join(','));
|
|
162
|
+
}
|
|
163
|
+
lines.push(`edges[${response.edges.length}]{id,source_id,target_id,relation,confidence}:`);
|
|
164
|
+
for (const edge of response.edges) {
|
|
165
|
+
lines.push([edge.id, edge.source_id, edge.target_id, edge.relation, edge.confidence]
|
|
166
|
+
.map(toonScalar)
|
|
167
|
+
.join(','));
|
|
168
|
+
}
|
|
169
|
+
return lines.join('\n');
|
|
170
|
+
}
|