@agent-native/creative-context 0.5.3 → 0.5.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.
Files changed (49) hide show
  1. package/dist/client/CreativeContextChip.js +1 -1
  2. package/dist/client/CreativeContextChip.js.map +1 -1
  3. package/dist/client/CreativeContextPanel.d.ts +1 -1
  4. package/dist/client/CreativeContextPanel.d.ts.map +1 -1
  5. package/dist/client/CreativeContextPanel.js +2 -1
  6. package/dist/client/CreativeContextPanel.js.map +1 -1
  7. package/dist/client/CreativeContextSettingsLink.js +1 -1
  8. package/dist/client/CreativeContextSettingsLink.js.map +1 -1
  9. package/dist/client/actions.d.ts.map +1 -1
  10. package/dist/client/actions.js +1 -1
  11. package/dist/client/actions.js.map +1 -1
  12. package/dist/client/agent-tab.d.ts +2 -1
  13. package/dist/client/agent-tab.d.ts.map +1 -1
  14. package/dist/client/agent-tab.js.map +1 -1
  15. package/dist/client/application-state.js +1 -1
  16. package/dist/client/application-state.js.map +1 -1
  17. package/dist/embeddings/providers.d.ts +1 -6
  18. package/dist/embeddings/providers.d.ts.map +1 -1
  19. package/dist/embeddings/providers.js +1 -175
  20. package/dist/embeddings/providers.js.map +1 -1
  21. package/dist/embeddings/types.d.ts +1 -18
  22. package/dist/embeddings/types.d.ts.map +1 -1
  23. package/dist/search/fusion.d.ts +1 -14
  24. package/dist/search/fusion.d.ts.map +1 -1
  25. package/dist/search/fusion.js +1 -24
  26. package/dist/search/fusion.js.map +1 -1
  27. package/dist/search/postgres-fts.d.ts +1 -21
  28. package/dist/search/postgres-fts.d.ts.map +1 -1
  29. package/dist/search/postgres-fts.js +1 -82
  30. package/dist/search/postgres-fts.js.map +1 -1
  31. package/dist/vector/pgvector.d.ts +1 -27
  32. package/dist/vector/pgvector.d.ts.map +1 -1
  33. package/dist/vector/pgvector.js +1 -116
  34. package/dist/vector/pgvector.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/client/CreativeContextChip.tsx +1 -1
  37. package/src/client/CreativeContextPanel.tsx +3 -6
  38. package/src/client/CreativeContextSettingsLink.tsx +1 -1
  39. package/src/client/actions.ts +4 -1
  40. package/src/client/agent-tab.tsx +2 -4
  41. package/src/client/application-state.ts +1 -1
  42. package/src/embeddings/providers.ts +1 -228
  43. package/src/embeddings/types.ts +6 -24
  44. package/src/eval/library-flow.acceptance.spec.tsx +4 -1
  45. package/src/search/fusion.ts +1 -39
  46. package/src/search/postgres-fts.spec.ts +10 -18
  47. package/src/search/postgres-fts.ts +1 -112
  48. package/src/vector/pgvector.spec.ts +19 -2
  49. package/src/vector/pgvector.ts +1 -164
@@ -1,112 +1 @@
1
- import type { DbExec } from "@agent-native/core/db";
2
- import { isPostgres } from "@agent-native/core/db";
3
-
4
- const FTS_TABLE = "creative_context_search_documents";
5
-
6
- export interface PostgresFtsHit {
7
- chunkId: string;
8
- itemVersionId: string;
9
- score: number;
10
- }
11
-
12
- export async function ensurePostgresFts(db: DbExec): Promise<boolean> {
13
- if (!isPostgres()) return false;
14
- await db.execute(`
15
- CREATE TABLE IF NOT EXISTS ${FTS_TABLE} (
16
- chunk_id TEXT PRIMARY KEY,
17
- item_version_id TEXT NOT NULL,
18
- document TSVECTOR NOT NULL,
19
- updated_at TEXT NOT NULL
20
- )
21
- `);
22
- await db.execute(
23
- `CREATE INDEX IF NOT EXISTS creative_context_search_documents_gin ON ${FTS_TABLE} USING GIN (document)`,
24
- );
25
- return true;
26
- }
27
-
28
- export async function upsertPostgresFtsDocument(
29
- db: DbExec,
30
- input: {
31
- chunkId: string;
32
- itemVersionId: string;
33
- title: string;
34
- summary?: string | null;
35
- body: string;
36
- updatedAt?: string;
37
- },
38
- ): Promise<boolean> {
39
- if (!(await ensurePostgresFts(db))) return false;
40
- await db.execute({
41
- sql: `
42
- INSERT INTO ${FTS_TABLE} (chunk_id, item_version_id, document, updated_at)
43
- VALUES (
44
- ?,
45
- ?,
46
- setweight(to_tsvector('simple', ?), 'A') ||
47
- setweight(to_tsvector('simple', ?), 'B') ||
48
- setweight(to_tsvector('simple', ?), 'C'),
49
- ?
50
- )
51
- ON CONFLICT (chunk_id) DO UPDATE SET
52
- item_version_id = EXCLUDED.item_version_id,
53
- document = EXCLUDED.document,
54
- updated_at = EXCLUDED.updated_at
55
- `,
56
- args: [
57
- input.chunkId,
58
- input.itemVersionId,
59
- input.title,
60
- input.summary ?? "",
61
- input.body,
62
- input.updatedAt ?? new Date().toISOString(),
63
- ],
64
- });
65
- return true;
66
- }
67
-
68
- export async function queryPostgresFts(
69
- db: DbExec,
70
- input: {
71
- query: string;
72
- allowedChunkIds?: readonly string[];
73
- limit?: number;
74
- },
75
- ): Promise<PostgresFtsHit[]> {
76
- if (!isPostgres() || input.allowedChunkIds?.length === 0) return [];
77
- await ensurePostgresFts(db);
78
- const placeholders = input.allowedChunkIds?.map(() => "?").join(", ");
79
- const accessClause = placeholders ? `chunk_id IN (${placeholders}) AND` : "";
80
- const limit = Math.max(1, Math.min(200, Math.floor(input.limit ?? 40)));
81
- const result = await db.execute({
82
- sql: `
83
- SELECT chunk_id, item_version_id,
84
- ts_rank_cd(document, websearch_to_tsquery('simple', ?)) AS score
85
- FROM ${FTS_TABLE}
86
- WHERE ${accessClause}
87
- document @@ websearch_to_tsquery('simple', ?)
88
- ORDER BY score DESC, chunk_id ASC
89
- LIMIT ?
90
- `,
91
- args: [input.query, ...(input.allowedChunkIds ?? []), input.query, limit],
92
- });
93
- return result.rows.map((row) => ({
94
- chunkId: String(row.chunk_id),
95
- itemVersionId: String(row.item_version_id),
96
- score: Number(row.score),
97
- }));
98
- }
99
-
100
- export async function deletePostgresFtsDocuments(
101
- db: DbExec,
102
- chunkIds: readonly string[],
103
- ): Promise<number> {
104
- if (!isPostgres() || chunkIds.length === 0) return 0;
105
- await ensurePostgresFts(db);
106
- const placeholders = chunkIds.map(() => "?").join(", ");
107
- const result = await db.execute({
108
- sql: `DELETE FROM ${FTS_TABLE} WHERE chunk_id IN (${placeholders})`,
109
- args: [...chunkIds],
110
- });
111
- return result.rowsAffected;
112
- }
1
+ export * from "@agent-native/core/search";
@@ -29,7 +29,7 @@ describe("pgvector creative-context lane", () => {
29
29
  expect(String(db.execute.mock.calls[1]?.[0])).toContain(
30
30
  "creative_context_vectors_1024",
31
31
  );
32
- expect(String(db.execute.mock.calls[2]?.[0])).toContain("USING hnsw");
32
+ expect(String(db.execute.mock.calls[3]?.[0])).toContain("USING hnsw");
33
33
  });
34
34
 
35
35
  it("binds vector values and validates the selected dimensions", async () => {
@@ -44,7 +44,7 @@ describe("pgvector creative-context lane", () => {
44
44
  },
45
45
  true,
46
46
  );
47
- const write = db.execute.mock.calls[3]?.[0] as {
47
+ const write = db.execute.mock.calls[5]?.[0] as {
48
48
  sql: string;
49
49
  args: unknown[];
50
50
  };
@@ -68,6 +68,23 @@ describe("pgvector creative-context lane", () => {
68
68
  ).rejects.toThrow("expected 2");
69
69
  });
70
70
 
71
+ it("can write a batch after its namespace and dimensions are provisioned", async () => {
72
+ const db = mockDb();
73
+ await ensurePgVectorIndex(db, 3, true);
74
+ await upsertPgVector(
75
+ db,
76
+ {
77
+ vectorKey: "chunk:1",
78
+ embeddingSetId: "set:1",
79
+ dimensions: 3,
80
+ vector: [0.25, 0.5, 0.75],
81
+ },
82
+ { postgres: true, indexInitialized: true },
83
+ );
84
+ expect(db.execute).toHaveBeenCalledTimes(6);
85
+ expect(String(db.execute.mock.calls[5]?.[0].sql)).toContain("INSERT INTO");
86
+ });
87
+
71
88
  it("limits vector results to access-filtered vector keys", async () => {
72
89
  const db = mockDb([
73
90
  { vector_key: "media:1", embedding_set_id: "set:1", score: 0.91 },
@@ -1,164 +1 @@
1
- import type { DbExec } from "@agent-native/core/db";
2
- import { isPostgres } from "@agent-native/core/db";
3
-
4
- export const PGVECTOR_REQUIRED_MESSAGE =
5
- "Visual search requires Postgres with the pgvector extension in the configured DATABASE_URL database.";
6
-
7
- export interface PgVectorHit {
8
- vectorKey: string;
9
- embeddingSetId: string;
10
- score: number;
11
- }
12
-
13
- function checkedDimensions(value: number): number {
14
- if (!Number.isInteger(value) || value < 1 || value > 16_000) {
15
- throw new Error("Embedding dimensions must be an integer from 1 to 16000.");
16
- }
17
- return value;
18
- }
19
-
20
- function vectorTable(dimensions: number): string {
21
- return `creative_context_vectors_${checkedDimensions(dimensions)}`;
22
- }
23
-
24
- function vectorIndex(dimensions: number): string {
25
- return `creative_context_vectors_${checkedDimensions(dimensions)}_hnsw`;
26
- }
27
-
28
- function vectorLiteral(vector: readonly number[], dimensions: number): string {
29
- if (vector.length !== dimensions) {
30
- throw new Error(
31
- `Embedding has ${vector.length} values; expected ${dimensions}.`,
32
- );
33
- }
34
- for (const value of vector) {
35
- if (!Number.isFinite(value)) {
36
- throw new Error("Embedding vectors may contain only finite numbers.");
37
- }
38
- }
39
- return `[${vector.join(",")}]`;
40
- }
41
-
42
- export function assertPgVectorAvailable(postgres = isPostgres()): void {
43
- if (!postgres) throw new Error(PGVECTOR_REQUIRED_MESSAGE);
44
- }
45
-
46
- export async function ensurePgVectorIndex(
47
- db: DbExec,
48
- dimensions: number,
49
- postgres = isPostgres(),
50
- ): Promise<void> {
51
- assertPgVectorAvailable(postgres);
52
- const table = vectorTable(dimensions);
53
- const index = vectorIndex(dimensions);
54
- try {
55
- await db.execute("CREATE EXTENSION IF NOT EXISTS vector");
56
- await db.execute(`
57
- CREATE TABLE IF NOT EXISTS ${table} (
58
- vector_key TEXT PRIMARY KEY,
59
- embedding_set_id TEXT NOT NULL,
60
- embedding vector(${checkedDimensions(dimensions)}) NOT NULL,
61
- updated_at TEXT NOT NULL
62
- )
63
- `);
64
- await db.execute(
65
- `CREATE INDEX IF NOT EXISTS ${index} ON ${table} USING hnsw (embedding vector_cosine_ops)`,
66
- );
67
- } catch (error) {
68
- throw new Error(PGVECTOR_REQUIRED_MESSAGE, { cause: error });
69
- }
70
- }
71
-
72
- export async function upsertPgVector(
73
- db: DbExec,
74
- input: {
75
- vectorKey: string;
76
- embeddingSetId: string;
77
- dimensions: number;
78
- vector: readonly number[];
79
- updatedAt?: string;
80
- },
81
- postgres = isPostgres(),
82
- ): Promise<void> {
83
- assertPgVectorAvailable(postgres);
84
- await ensurePgVectorIndex(db, input.dimensions, postgres);
85
- const table = vectorTable(input.dimensions);
86
- await db.execute({
87
- sql: `
88
- INSERT INTO ${table} (vector_key, embedding_set_id, embedding, updated_at)
89
- VALUES (?, ?, ?::vector, ?)
90
- ON CONFLICT (vector_key) DO UPDATE SET
91
- embedding_set_id = EXCLUDED.embedding_set_id,
92
- embedding = EXCLUDED.embedding,
93
- updated_at = EXCLUDED.updated_at
94
- `,
95
- args: [
96
- input.vectorKey,
97
- input.embeddingSetId,
98
- vectorLiteral(input.vector, input.dimensions),
99
- input.updatedAt ?? new Date().toISOString(),
100
- ],
101
- });
102
- }
103
-
104
- export async function deletePgVectors(
105
- db: DbExec,
106
- input: {
107
- dimensions: number;
108
- vectorKeys: readonly string[];
109
- },
110
- postgres = isPostgres(),
111
- ): Promise<number> {
112
- assertPgVectorAvailable(postgres);
113
- if (input.vectorKeys.length === 0) return 0;
114
- const table = vectorTable(input.dimensions);
115
- const placeholders = input.vectorKeys.map(() => "?").join(", ");
116
- const result = await db.execute({
117
- sql: `DELETE FROM ${table} WHERE vector_key IN (${placeholders})`,
118
- args: [...input.vectorKeys],
119
- });
120
- return result.rowsAffected;
121
- }
122
-
123
- export async function queryPgVectorIndex(
124
- db: DbExec,
125
- input: {
126
- embeddingSetId: string;
127
- dimensions: number;
128
- vector: readonly number[];
129
- limit?: number;
130
- allowedVectorKeys: readonly string[];
131
- },
132
- postgres = isPostgres(),
133
- ): Promise<PgVectorHit[]> {
134
- assertPgVectorAvailable(postgres);
135
- const limit = Math.max(1, Math.min(200, Math.floor(input.limit ?? 40)));
136
- const allowed = input.allowedVectorKeys;
137
- if (allowed.length === 0) return [];
138
- const table = vectorTable(input.dimensions);
139
- const literal = vectorLiteral(input.vector, input.dimensions);
140
- const allowedSql = allowed.length
141
- ? ` AND vector_key IN (${allowed.map(() => "?").join(", ")})`
142
- : "";
143
- try {
144
- await db.execute("SET hnsw.iterative_scan = strict_order");
145
- } catch {
146
- // pgvector before iterative scans still supports the filtered exact query.
147
- }
148
- const result = await db.execute({
149
- sql: `
150
- SELECT vector_key, embedding_set_id,
151
- 1 - (embedding <=> ?::vector) AS score
152
- FROM ${table}
153
- WHERE embedding_set_id = ?${allowedSql}
154
- ORDER BY embedding <=> ?::vector
155
- LIMIT ?
156
- `,
157
- args: [literal, input.embeddingSetId, ...allowed, literal, limit],
158
- });
159
- return result.rows.map((row) => ({
160
- vectorKey: String(row.vector_key),
161
- embeddingSetId: String(row.embedding_set_id),
162
- score: Number(row.score),
163
- }));
164
- }
1
+ export * from "@agent-native/core/search";