@sveda-ai/core 0.1.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/client.d.ts +79 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +367 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +12 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +25 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/streaming.d.ts +4 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +60 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tools.d.ts +22 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +34 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/client.ts +492 -0
- package/src/context.ts +38 -0
- package/src/index.ts +5 -0
- package/src/streaming.ts +77 -0
- package/src/tools.ts +63 -0
- package/src/types.ts +58 -0
package/src/context.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface SvedaContextReadable {
|
|
2
|
+
description: string;
|
|
3
|
+
value: unknown | (() => unknown);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class SvedaContextRegistry {
|
|
7
|
+
private readables = new Map<string, SvedaContextReadable>();
|
|
8
|
+
|
|
9
|
+
register(description: string, value: unknown | (() => unknown)): () => void {
|
|
10
|
+
const readable: SvedaContextReadable = { description, value };
|
|
11
|
+
this.readables.set(description, readable);
|
|
12
|
+
|
|
13
|
+
return () => this.unregister(description);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
unregister(description: string): void {
|
|
17
|
+
this.readables.delete(description);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
snapshot(): Record<string, unknown> {
|
|
21
|
+
const context: Record<string, unknown> = {};
|
|
22
|
+
|
|
23
|
+
for (const readable of this.readables.values()) {
|
|
24
|
+
const value =
|
|
25
|
+
typeof readable.value === 'function'
|
|
26
|
+
? (readable.value as () => unknown)()
|
|
27
|
+
: readable.value;
|
|
28
|
+
|
|
29
|
+
context[readable.description] = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear(): void {
|
|
36
|
+
this.readables.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
package/src/streaming.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSvedaStreamLine,
|
|
3
|
+
vercelDataPartToSvedaEvent,
|
|
4
|
+
type SvedaStreamEvent,
|
|
5
|
+
} from '@sveda-ai/protocol';
|
|
6
|
+
|
|
7
|
+
export type SvedaStreamProtocolMode = 'sveda' | 'vercel';
|
|
8
|
+
|
|
9
|
+
async function* iterateStreamLines(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {
|
|
10
|
+
const reader = body.getReader();
|
|
11
|
+
const decoder = new TextDecoder();
|
|
12
|
+
let buffer = '';
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
while (true) {
|
|
16
|
+
const { done, value } = await reader.read();
|
|
17
|
+
if (done) {
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
buffer += decoder.decode(value, { stream: true });
|
|
22
|
+
|
|
23
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
24
|
+
while (newlineIndex >= 0) {
|
|
25
|
+
yield buffer.slice(0, newlineIndex);
|
|
26
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
27
|
+
newlineIndex = buffer.indexOf('\n');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tail = buffer.trim();
|
|
32
|
+
if (tail) {
|
|
33
|
+
yield tail;
|
|
34
|
+
}
|
|
35
|
+
} finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseVercelLine(line: string): SvedaStreamEvent | null {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed.startsWith('data:')) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const payload = trimmed.slice(5).trim();
|
|
47
|
+
if (!payload || payload === '[DONE]') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const part = JSON.parse(payload) as { type?: string };
|
|
53
|
+
if (!part || typeof part.type !== 'string') {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return vercelDataPartToSvedaEvent(part as { type: string } & Record<string, unknown>);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function* iterateSvedaStream(
|
|
64
|
+
response: Response,
|
|
65
|
+
mode: SvedaStreamProtocolMode = 'sveda'
|
|
66
|
+
): AsyncGenerator<SvedaStreamEvent> {
|
|
67
|
+
if (!response.body) {
|
|
68
|
+
throw new Error('[sveda] Stream response has no body');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for await (const line of iterateStreamLines(response.body)) {
|
|
72
|
+
const event = mode === 'vercel' ? parseVercelLine(line) : parseSvedaStreamLine(line);
|
|
73
|
+
if (event) {
|
|
74
|
+
yield event;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SvedaClientToolDefinition } from '@sveda-ai/protocol';
|
|
2
|
+
|
|
3
|
+
export interface SvedaFrontendToolContext {
|
|
4
|
+
chatId: string;
|
|
5
|
+
toolCallId: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SvedaFrontendTool {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: Record<string, unknown>;
|
|
12
|
+
handler: (
|
|
13
|
+
input: Record<string, unknown>,
|
|
14
|
+
context: SvedaFrontendToolContext
|
|
15
|
+
) => unknown | Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class SvedaToolRegistry {
|
|
19
|
+
private tools = new Map<string, SvedaFrontendTool>();
|
|
20
|
+
|
|
21
|
+
register(tool: SvedaFrontendTool): () => void {
|
|
22
|
+
this.tools.set(tool.name, tool);
|
|
23
|
+
|
|
24
|
+
return () => this.unregister(tool.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
unregister(name: string): void {
|
|
28
|
+
this.tools.delete(name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(name: string): boolean {
|
|
32
|
+
return this.tools.has(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get(name: string): SvedaFrontendTool | undefined {
|
|
36
|
+
return this.tools.get(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
list(): SvedaClientToolDefinition[] {
|
|
40
|
+
return [...this.tools.values()].map(tool => ({
|
|
41
|
+
name: tool.name,
|
|
42
|
+
description: tool.description,
|
|
43
|
+
parameters: tool.parameters,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async execute(
|
|
48
|
+
name: string,
|
|
49
|
+
input: Record<string, unknown>,
|
|
50
|
+
context: SvedaFrontendToolContext
|
|
51
|
+
): Promise<unknown> {
|
|
52
|
+
const tool = this.tools.get(name);
|
|
53
|
+
if (!tool) {
|
|
54
|
+
throw new Error(`[sveda] Unknown frontend tool: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return tool.handler(input, context);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clear(): void {
|
|
61
|
+
this.tools.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SvedaRenderHint, SvedaToolTarget } from '@sveda-ai/protocol';
|
|
2
|
+
|
|
3
|
+
export type SvedaMessageRole = 'user' | 'assistant' | 'system' | 'tool';
|
|
4
|
+
|
|
5
|
+
export type SvedaMessagePart =
|
|
6
|
+
| { type: 'text'; text: string }
|
|
7
|
+
| { type: 'reasoning'; text: string }
|
|
8
|
+
| {
|
|
9
|
+
type: 'tool-call';
|
|
10
|
+
toolCallId: string;
|
|
11
|
+
toolName: string;
|
|
12
|
+
target: SvedaToolTarget;
|
|
13
|
+
input: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
type: 'tool-result';
|
|
17
|
+
toolCallId: string;
|
|
18
|
+
toolName: string;
|
|
19
|
+
output: unknown;
|
|
20
|
+
renderHint?: SvedaRenderHint;
|
|
21
|
+
renderData?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
| { type: 'file'; name: string; mediaType?: string; url?: string };
|
|
24
|
+
|
|
25
|
+
export interface SvedaDisplayMessage {
|
|
26
|
+
id: string;
|
|
27
|
+
role: SvedaMessageRole;
|
|
28
|
+
parts: SvedaMessagePart[];
|
|
29
|
+
createdAt?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type SvedaSessionStatus = 'idle' | 'submitted' | 'streaming' | 'error';
|
|
33
|
+
|
|
34
|
+
export interface SvedaChatHistorySummary {
|
|
35
|
+
chatId: string;
|
|
36
|
+
title: string;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SvedaChatHistoryDetail extends SvedaChatHistorySummary {
|
|
42
|
+
messages: SvedaDisplayMessage[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function messageText(message: SvedaDisplayMessage): string {
|
|
46
|
+
return message.parts
|
|
47
|
+
.filter((part): part is Extract<SvedaMessagePart, { type: 'text' }> => part.type === 'text')
|
|
48
|
+
.map(part => part.text)
|
|
49
|
+
.join('');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createMessageId(): string {
|
|
53
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
54
|
+
return crypto.randomUUID();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
58
|
+
}
|