Files
zclaw_openfang/docs/archive/v1-viking-dead-code/lib/viking-client.ts
iven 1cf3f585d3 refactor(store): split gatewayStore into specialized domain stores
Major restructuring:
- Split monolithic gatewayStore into 5 focused stores:
  - connectionStore: WebSocket connection and gateway lifecycle
  - configStore: quickConfig, workspaceInfo, MCP services
  - agentStore: clones, usage stats, agent management
  - handStore: hands, approvals, triggers, hand runs
  - workflowStore: workflows, workflow runs, execution

- Update all components to use new stores with selector pattern
- Remove
2026-03-20 22:14:13 +08:00

354 lines
8.3 KiB
TypeScript

/**
* OpenViking HTTP API Client
*
* TypeScript client for communicating with the OpenViking Server.
* OpenViking is an open-source context database for AI agents by Volcengine.
*
* API Reference: https://github.com/volcengine/OpenViking
* Default server port: 1933
*/
// === Types ===
export interface VikingStatus {
status: 'ok' | 'error';
version?: string;
uptime?: number;
workspace?: string;
}
export interface VikingEntry {
uri: string;
name: string;
type: 'file' | 'directory';
size?: number;
modifiedAt?: string;
abstract?: string;
}
export interface VikingTreeNode {
uri: string;
name: string;
type: 'file' | 'directory';
children?: VikingTreeNode[];
}
export type ContextLevel = 'L0' | 'L1' | 'L2';
export interface FindOptions {
scope?: string;
level?: ContextLevel;
limit?: number;
minScore?: number;
}
export interface FindResult {
uri: string;
score: number;
content: string;
level: ContextLevel;
abstract?: string;
overview?: string;
metadata?: Record<string, unknown>;
}
export interface GrepOptions {
uri?: string;
caseSensitive?: boolean;
limit?: number;
}
export interface GrepResult {
uri: string;
line: number;
content: string;
matchStart: number;
matchEnd: number;
}
export interface AddResourceOptions {
metadata?: Record<string, string>;
wait?: boolean;
}
export interface ExtractedMemory {
category: 'user_preference' | 'user_fact' | 'agent_lesson' | 'agent_pattern' | 'task';
content: string;
tags: string[];
importance: number;
suggestedUri: string;
}
export interface SessionExtractionResult {
memories: ExtractedMemory[];
summary: string;
tokensSaved?: number;
}
export interface RetrievalTraceStep {
uri: string;
score: number;
action: 'entered' | 'skipped' | 'matched';
level: ContextLevel;
childrenExplored?: number;
}
export interface RetrievalTrace {
query: string;
steps: RetrievalTraceStep[];
totalTokensUsed: number;
tokensByLevel: { L0: number; L1: number; L2: number };
duration: number;
}
// === Client Implementation ===
export class VikingHttpClient {
private baseUrl: string;
private timeout: number;
constructor(baseUrl: string = 'http://localhost:1933', timeout: number = 30000) {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeout = timeout;
}
// === Health & Status ===
async status(): Promise<VikingStatus> {
return this.get<VikingStatus>('/api/status');
}
async isAvailable(): Promise<boolean> {
try {
const result = await this.status();
return result.status === 'ok';
} catch {
return false;
}
}
// === Resource Management ===
async addResource(
uri: string,
content: string,
options?: AddResourceOptions
): Promise<{ uri: string; status: string }> {
return this.post('/api/resources', {
uri,
content,
metadata: options?.metadata,
wait: options?.wait ?? false,
});
}
async removeResource(uri: string): Promise<void> {
await this.delete(`/api/resources`, { uri });
}
async ls(path: string): Promise<VikingEntry[]> {
const result = await this.get<{ entries: VikingEntry[] }>('/api/ls', { path });
return result.entries || [];
}
async tree(path: string, depth: number = 2): Promise<VikingTreeNode> {
return this.get<VikingTreeNode>('/api/tree', { path, depth: String(depth) });
}
// === Retrieval ===
async find(query: string, options?: FindOptions): Promise<FindResult[]> {
const result = await this.post<{ results: FindResult[]; trace?: RetrievalTrace }>(
'/api/find',
{
query,
scope: options?.scope,
level: options?.level || 'L1',
limit: options?.limit || 10,
min_score: options?.minScore,
}
);
return result.results || [];
}
async findWithTrace(
query: string,
options?: FindOptions
): Promise<{ results: FindResult[]; trace: RetrievalTrace }> {
return this.post('/api/find', {
query,
scope: options?.scope,
level: options?.level || 'L1',
limit: options?.limit || 10,
min_score: options?.minScore,
include_trace: true,
});
}
async grep(
pattern: string,
options?: GrepOptions
): Promise<GrepResult[]> {
const result = await this.post<{ results: GrepResult[] }>('/api/grep', {
pattern,
uri: options?.uri,
case_sensitive: options?.caseSensitive ?? false,
limit: options?.limit || 20,
});
return result.results || [];
}
// === Memory Operations ===
async readContent(uri: string, level: ContextLevel = 'L1'): Promise<string> {
const result = await this.get<{ content: string }>('/api/read', { uri, level });
return result.content || '';
}
// === Session Management ===
async extractMemories(
sessionContent: string,
agentId?: string
): Promise<SessionExtractionResult> {
return this.post<SessionExtractionResult>('/api/session/extract', {
content: sessionContent,
agent_id: agentId,
});
}
async compactSession(
messages: Array<{ role: string; content: string }>,
): Promise<string> {
const result = await this.post<{ summary: string }>('/api/session/compact', {
messages,
});
return result.summary;
}
// === Internal HTTP Methods ===
private async get<T>(path: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
url.searchParams.set(key, value);
}
}
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url.toString(), {
method: 'GET',
headers: { 'Accept': 'application/json' },
signal: controller.signal,
});
if (!response.ok) {
throw new VikingError(
`Viking API error: ${response.status} ${response.statusText}`,
response.status
);
}
return await response.json() as T;
} finally {
clearTimeout(timeoutId);
}
}
private async post<T>(path: string, body: unknown): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(body),
signal: controller.signal,
});
if (!response.ok) {
const errorBody = await response.text().catch(() => '');
throw new VikingError(
`Viking API error: ${response.status} ${response.statusText} - ${errorBody}`,
response.status
);
}
return await response.json() as T;
} finally {
clearTimeout(timeoutId);
}
}
private async delete(path: string, body?: unknown): Promise<void> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${path}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
if (!response.ok) {
throw new VikingError(
`Viking API error: ${response.status} ${response.statusText}`,
response.status
);
}
} finally {
clearTimeout(timeoutId);
}
}
}
// === Error Class ===
export class VikingError extends Error {
constructor(
message: string,
public readonly statusCode?: number
) {
super(message);
this.name = 'VikingError';
}
}
// === Singleton ===
let _instance: VikingHttpClient | null = null;
/**
* Get the singleton VikingHttpClient instance.
* Uses default configuration (localhost:1933).
*/
export function getVikingClient(baseUrl?: string): VikingHttpClient {
if (!_instance) {
_instance = new VikingHttpClient(baseUrl);
}
return _instance;
}
/**
* Reset the singleton instance.
* Useful for testing or reconfiguration.
*/
export function resetVikingClient(): void {
_instance = null;
}