Skip to content

Commit ae6f45b

Browse files
committed
feat(sdk): add search ts sdk
1 parent 3078880 commit ae6f45b

5 files changed

Lines changed: 277 additions & 4 deletions

File tree

src/client/acontext-ts/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,80 @@ await client.blocks.create(space.id, {
7878
});
7979
```
8080

81+
## Semantic search within spaces
82+
83+
The SDK provides three powerful semantic search APIs for finding content within your spaces:
84+
85+
### 1. Experience Search (Advanced AI-powered search)
86+
87+
The most sophisticated search that can operate in two modes: **fast** (quick semantic search) or **agentic** (AI-powered iterative refinement).
88+
89+
```typescript
90+
import { AcontextClient } from '@acontext/acontext';
91+
92+
const client = new AcontextClient({ apiKey: 'sk_project_token' });
93+
94+
// Fast mode - quick semantic search
95+
const result = await client.spaces.experienceSearch('space-uuid', {
96+
query: 'How to implement authentication?',
97+
limit: 10,
98+
mode: 'fast',
99+
});
100+
101+
// Agentic mode - AI-powered iterative search
102+
const agenticResult = await client.spaces.experienceSearch('space-uuid', {
103+
query: 'What are the best practices for API security?',
104+
limit: 10,
105+
mode: 'agentic',
106+
semanticThreshold: 0.8,
107+
maxIterations: 20,
108+
});
109+
110+
// Access results
111+
for (const block of result.cited_blocks) {
112+
console.log(`${block.title} (distance: ${block.distance})`);
113+
}
114+
115+
if (result.final_answer) {
116+
console.log(`AI Answer: ${result.final_answer}`);
117+
}
118+
```
119+
120+
### 2. Semantic Global (Search page/folder titles)
121+
122+
Search for pages and folders by their titles using semantic similarity (like a semantic version of `glob`):
123+
124+
```typescript
125+
// Find pages about authentication
126+
const results = await client.spaces.semanticGlobal('space-uuid', {
127+
query: 'authentication and authorization pages',
128+
limit: 10,
129+
threshold: 1.0, // Only show results with distance < 1.0
130+
});
131+
132+
for (const block of results) {
133+
console.log(`${block.title} - ${block.type}`);
134+
}
135+
```
136+
137+
### 3. Semantic Grep (Search content blocks)
138+
139+
Search through actual content blocks using semantic similarity (like a semantic version of `grep`):
140+
141+
```typescript
142+
// Find code examples for JWT validation
143+
const results = await client.spaces.semanticGrep('space-uuid', {
144+
query: 'JWT token validation code examples',
145+
limit: 15,
146+
threshold: 0.7,
147+
});
148+
149+
for (const block of results) {
150+
console.log(`${block.title} - distance: ${block.distance}`);
151+
const content = block.props.text || block.props.content;
152+
if (content) {
153+
console.log(`Content: ${String(content).substring(0, 100)}...`);
154+
}
155+
}
156+
```
157+

src/client/acontext-ts/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/acontext-ts/src/resources/spaces.ts

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@
44

55
import { RequesterProtocol } from '../client-types';
66
import { buildParams } from '../utils';
7-
import { ListSpacesOutput, ListSpacesOutputSchema, Space, SpaceSchema } from '../types';
7+
import {
8+
ListSpacesOutput,
9+
ListSpacesOutputSchema,
10+
SearchResultBlockItem,
11+
SearchResultBlockItemSchema,
12+
Space,
13+
SpaceSchema,
14+
SpaceSearchResult,
15+
SpaceSearchResultSchema,
16+
} from '../types';
817

918
export class SpacesAPI {
10-
constructor(private requester: RequesterProtocol) {}
19+
constructor(private requester: RequesterProtocol) { }
1120

1221
async list(options?: {
1322
limit?: number | null;
@@ -58,5 +67,107 @@ export class SpacesAPI {
5867
const data = await this.requester.request('GET', `/space/${spaceId}/configs`);
5968
return SpaceSchema.parse(data);
6069
}
70+
71+
/**
72+
* Perform experience search within a space.
73+
*
74+
* This is the most advanced search option that can operate in two modes:
75+
* - fast: Quick semantic search (default)
76+
* - agentic: Iterative search with AI-powered refinement
77+
*
78+
* @param spaceId - The UUID of the space
79+
* @param options - Search options
80+
* @returns SpaceSearchResult containing cited blocks and optional final answer
81+
*/
82+
async experienceSearch(
83+
spaceId: string,
84+
options: {
85+
query: string;
86+
limit?: number | null;
87+
mode?: 'fast' | 'agentic' | null;
88+
semanticThreshold?: number | null;
89+
maxIterations?: number | null;
90+
}
91+
): Promise<SpaceSearchResult> {
92+
const params = buildParams({
93+
query: options.query,
94+
limit: options.limit ?? null,
95+
mode: options.mode ?? null,
96+
semantic_threshold: options.semanticThreshold ?? null,
97+
max_iterations: options.maxIterations ?? null,
98+
});
99+
const data = await this.requester.request(
100+
'GET',
101+
`/space/${spaceId}/experience_search`,
102+
{ params: Object.keys(params).length > 0 ? params : undefined }
103+
);
104+
return SpaceSearchResultSchema.parse(data);
105+
}
106+
107+
/**
108+
* Perform semantic global (glob) search for page/folder titles.
109+
*
110+
* Searches specifically for page/folder titles using semantic similarity,
111+
* similar to a semantic version of the glob command.
112+
*
113+
* @param spaceId - The UUID of the space
114+
* @param options - Search options
115+
* @returns List of SearchResultBlockItem objects matching the query
116+
*/
117+
async semanticGlobal(
118+
spaceId: string,
119+
options: {
120+
query: string;
121+
limit?: number | null;
122+
threshold?: number | null;
123+
}
124+
): Promise<SearchResultBlockItem[]> {
125+
const params = buildParams({
126+
query: options.query,
127+
limit: options.limit ?? null,
128+
threshold: options.threshold ?? null,
129+
});
130+
const data = await this.requester.request(
131+
'GET',
132+
`/space/${spaceId}/semantic_global`,
133+
{ params: Object.keys(params).length > 0 ? params : undefined }
134+
);
135+
return (data as unknown[]).map((item) =>
136+
SearchResultBlockItemSchema.parse(item)
137+
);
138+
}
139+
140+
/**
141+
* Perform semantic grep search for content blocks.
142+
*
143+
* Searches through content blocks (actual text content) using semantic similarity,
144+
* similar to a semantic version of the grep command.
145+
*
146+
* @param spaceId - The UUID of the space
147+
* @param options - Search options
148+
* @returns List of SearchResultBlockItem objects matching the query
149+
*/
150+
async semanticGrep(
151+
spaceId: string,
152+
options: {
153+
query: string;
154+
limit?: number | null;
155+
threshold?: number | null;
156+
}
157+
): Promise<SearchResultBlockItem[]> {
158+
const params = buildParams({
159+
query: options.query,
160+
limit: options.limit ?? null,
161+
threshold: options.threshold ?? null,
162+
});
163+
const data = await this.requester.request(
164+
'GET',
165+
`/space/${spaceId}/semantic_grep`,
166+
{ params: Object.keys(params).length > 0 ? params : undefined }
167+
);
168+
return (data as unknown[]).map((item) =>
169+
SearchResultBlockItemSchema.parse(item)
170+
);
171+
}
61172
}
62173

src/client/acontext-ts/src/types/space.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,20 @@ export const ListSpacesOutputSchema = z.object({
2222

2323
export type ListSpacesOutput = z.infer<typeof ListSpacesOutputSchema>;
2424

25+
export const SearchResultBlockItemSchema = z.object({
26+
block_id: z.string(),
27+
title: z.string(),
28+
type: z.string(),
29+
props: z.record(z.string(), z.unknown()),
30+
distance: z.number().nullable(),
31+
});
32+
33+
export type SearchResultBlockItem = z.infer<typeof SearchResultBlockItemSchema>;
34+
35+
export const SpaceSearchResultSchema = z.object({
36+
cited_blocks: z.array(SearchResultBlockItemSchema),
37+
final_answer: z.string().nullable(),
38+
});
39+
40+
export type SpaceSearchResult = z.infer<typeof SpaceSearchResultSchema>;
41+

src/client/acontext-ts/tests/client.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,74 @@ describe('AcontextClient Integration Tests', () => {
9292
const space = await client.spaces.getConfigs(createdSpaceId);
9393
expect(space.configs).toMatchObject({ name: 'Updated Test Space', test: true });
9494
});
95+
96+
// TODO: Uncomment these tests when the search APIs are ready to test
97+
// test('should perform experience search with fast mode', async () => {
98+
// if (!createdSpaceId) {
99+
// throw new Error('Space not created');
100+
// }
101+
// const result = await client.spaces.experienceSearch(createdSpaceId, {
102+
// query: 'test query',
103+
// limit: 5,
104+
// mode: 'fast',
105+
// });
106+
// expect(result).toBeDefined();
107+
// expect(result.cited_blocks).toBeInstanceOf(Array);
108+
// expect(result.final_answer === null || typeof result.final_answer === 'string').toBe(true);
109+
// });
110+
111+
// test('should perform experience search with agentic mode', async () => {
112+
// if (!createdSpaceId) {
113+
// throw new Error('Space not created');
114+
// }
115+
// const result = await client.spaces.experienceSearch(createdSpaceId, {
116+
// query: 'API best practices',
117+
// limit: 10,
118+
// mode: 'agentic',
119+
// semanticThreshold: 0.8,
120+
// maxIterations: 20,
121+
// });
122+
// expect(result).toBeDefined();
123+
// expect(result.cited_blocks).toBeInstanceOf(Array);
124+
// });
125+
126+
// test('should perform semantic global search', async () => {
127+
// if (!createdSpaceId) {
128+
// throw new Error('Space not created');
129+
// }
130+
// const results = await client.spaces.semanticGlobal(createdSpaceId, {
131+
// query: 'test pages',
132+
// limit: 10,
133+
// threshold: 1.0,
134+
// });
135+
// expect(results).toBeDefined();
136+
// expect(Array.isArray(results)).toBe(true);
137+
// results.forEach((item) => {
138+
// expect(item.block_id).toBeDefined();
139+
// expect(item.title).toBeDefined();
140+
// expect(item.type).toBeDefined();
141+
// expect(item.props).toBeDefined();
142+
// });
143+
// });
144+
145+
// test('should perform semantic grep search', async () => {
146+
// if (!createdSpaceId) {
147+
// throw new Error('Space not created');
148+
// }
149+
// const results = await client.spaces.semanticGrep(createdSpaceId, {
150+
// query: 'test content',
151+
// limit: 15,
152+
// threshold: 0.7,
153+
// });
154+
// expect(results).toBeDefined();
155+
// expect(Array.isArray(results)).toBe(true);
156+
// results.forEach((item) => {
157+
// expect(item.block_id).toBeDefined();
158+
// expect(item.title).toBeDefined();
159+
// expect(item.type).toBeDefined();
160+
// expect(item.props).toBeDefined();
161+
// });
162+
// });
95163
});
96164

97165
describe('Sessions API', () => {

0 commit comments

Comments
 (0)