@radkod/ai 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/README.md +156 -0
- package/dist/index.d.mts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +259 -0
- package/dist/index.mjs +233 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @radkod/ai
|
|
2
|
+
|
|
3
|
+
Lightweight SDK for RadKod AI API — OpenAI-compatible LLM proxy with streaming and tool calling.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @radkod/ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { RadKodAI } from '@radkod/ai'
|
|
15
|
+
|
|
16
|
+
const ai = new RadKodAI({
|
|
17
|
+
baseUrl: 'https://ai.radkod.com',
|
|
18
|
+
apiKey: 'benv-your-key-here'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Simple prompt
|
|
22
|
+
const answer = await ai.prompt('What is quantum computing?')
|
|
23
|
+
console.log(answer)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Streaming
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
await ai.promptStream('Count to 10', (token) => {
|
|
30
|
+
process.stdout.write(token)
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or with full control:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const result = await ai.chatStream(
|
|
38
|
+
{
|
|
39
|
+
model: 'z-ai/glm4.7',
|
|
40
|
+
messages: [{ role: 'user', content: 'Hello!' }]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
onToken: (token) => process.stdout.write(token),
|
|
44
|
+
onDone: (fullText) => console.log('\n\nDone:', fullText.length, 'chars'),
|
|
45
|
+
onError: (err) => console.error('Error:', err)
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Tool Calling
|
|
51
|
+
|
|
52
|
+
Define tools and handlers — the SDK runs the tool loop automatically:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const result = await ai.chatWithTools(
|
|
56
|
+
{
|
|
57
|
+
messages: [{ role: 'user', content: 'What is the weather in Istanbul?' }],
|
|
58
|
+
tools: [
|
|
59
|
+
{
|
|
60
|
+
type: 'function',
|
|
61
|
+
function: {
|
|
62
|
+
name: 'get_weather',
|
|
63
|
+
description: 'Get current weather for a city',
|
|
64
|
+
parameters: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
city: { type: 'string', description: 'City name' }
|
|
68
|
+
},
|
|
69
|
+
required: ['city']
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
get_weather: async ({ city }) => {
|
|
77
|
+
// Your tool implementation
|
|
78
|
+
return { temperature: 22, condition: 'sunny', city }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
console.log(result.finalText) // "The weather in Istanbul is 22°C and sunny."
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Full Chat API
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const response = await ai.chat({
|
|
90
|
+
model: 'meta/llama-3.3-70b-instruct',
|
|
91
|
+
messages: [
|
|
92
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
93
|
+
{ role: 'user', content: 'Explain REST vs GraphQL' }
|
|
94
|
+
],
|
|
95
|
+
temperature: 0.7,
|
|
96
|
+
max_tokens: 2000
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
console.log(response.choices[0].message.content)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## List Models
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const models = await ai.models()
|
|
106
|
+
models.forEach(m => console.log(m.id, '—', m.owned_by))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Health Check
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const health = await ai.health()
|
|
113
|
+
console.log(health.status) // "ok"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Browser Usage
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<script type="module">
|
|
120
|
+
import { RadKodAI } from 'https://cdn.jsdelivr.net/npm/@radkod/ai/dist/index.mjs'
|
|
121
|
+
|
|
122
|
+
const ai = new RadKodAI({ apiKey: 'benv-xxx' })
|
|
123
|
+
const answer = await ai.prompt('Hello!')
|
|
124
|
+
document.body.textContent = answer
|
|
125
|
+
</script>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const ai = new RadKodAI({
|
|
132
|
+
baseUrl: 'https://ai.radkod.com', // API server URL
|
|
133
|
+
apiKey: 'benv-xxx', // API key (optional in open mode)
|
|
134
|
+
defaultModel: 'z-ai/glm4.7', // Default model for all requests
|
|
135
|
+
timeout: 300000 // Request timeout in ms (default: 5min)
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Error Handling
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { RadKodAI, RadKodError } from '@radkod/ai'
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await ai.prompt('Hello')
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof RadKodError) {
|
|
148
|
+
console.log(err.status) // 401, 429, 502, etc.
|
|
149
|
+
console.log(err.body) // API error response body
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
interface RadKodConfig {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
defaultModel?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
interface ChatMessage {
|
|
8
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
9
|
+
content: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
tool_call_id?: string;
|
|
12
|
+
tool_calls?: ToolCall[];
|
|
13
|
+
}
|
|
14
|
+
interface ToolDefinition {
|
|
15
|
+
type: 'function';
|
|
16
|
+
function: {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
parameters: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface ToolCall {
|
|
23
|
+
id: string;
|
|
24
|
+
type: 'function';
|
|
25
|
+
function: {
|
|
26
|
+
name: string;
|
|
27
|
+
arguments: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface ChatOptions {
|
|
31
|
+
model?: string;
|
|
32
|
+
messages: ChatMessage[];
|
|
33
|
+
stream?: boolean;
|
|
34
|
+
tools?: ToolDefinition[];
|
|
35
|
+
tool_choice?: 'auto' | 'none' | {
|
|
36
|
+
type: 'function';
|
|
37
|
+
function: {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
max_tokens?: number;
|
|
42
|
+
temperature?: number;
|
|
43
|
+
top_p?: number;
|
|
44
|
+
seed?: number;
|
|
45
|
+
}
|
|
46
|
+
interface ChatResponse {
|
|
47
|
+
id: string;
|
|
48
|
+
object: string;
|
|
49
|
+
created: number;
|
|
50
|
+
model: string;
|
|
51
|
+
choices: {
|
|
52
|
+
index: number;
|
|
53
|
+
message: ChatMessage;
|
|
54
|
+
finish_reason: string | null;
|
|
55
|
+
}[];
|
|
56
|
+
usage?: {
|
|
57
|
+
prompt_tokens: number;
|
|
58
|
+
completion_tokens: number;
|
|
59
|
+
total_tokens: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
interface StreamChunk {
|
|
63
|
+
id: string;
|
|
64
|
+
object: string;
|
|
65
|
+
created: number;
|
|
66
|
+
model: string;
|
|
67
|
+
choices: {
|
|
68
|
+
index: number;
|
|
69
|
+
delta: {
|
|
70
|
+
role?: string;
|
|
71
|
+
content?: string;
|
|
72
|
+
tool_calls?: {
|
|
73
|
+
index: number;
|
|
74
|
+
id?: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
function?: {
|
|
77
|
+
name?: string;
|
|
78
|
+
arguments?: string;
|
|
79
|
+
};
|
|
80
|
+
}[];
|
|
81
|
+
};
|
|
82
|
+
finish_reason: string | null;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
interface Model {
|
|
86
|
+
id: string;
|
|
87
|
+
object: string;
|
|
88
|
+
created: number;
|
|
89
|
+
owned_by: string;
|
|
90
|
+
}
|
|
91
|
+
interface StreamCallbacks {
|
|
92
|
+
onToken?: (token: string) => void;
|
|
93
|
+
onToolCall?: (toolCall: ToolCall) => void;
|
|
94
|
+
onDone?: (fullText: string) => void;
|
|
95
|
+
onError?: (error: Error) => void;
|
|
96
|
+
}
|
|
97
|
+
interface ChatStreamResult {
|
|
98
|
+
text: string;
|
|
99
|
+
toolCalls: ToolCall[];
|
|
100
|
+
}
|
|
101
|
+
declare class RadKodError extends Error {
|
|
102
|
+
status: number;
|
|
103
|
+
body: unknown;
|
|
104
|
+
constructor(message: string, status: number, body?: unknown);
|
|
105
|
+
}
|
|
106
|
+
declare class RadKodAI {
|
|
107
|
+
private baseUrl;
|
|
108
|
+
private apiKey;
|
|
109
|
+
private defaultModel;
|
|
110
|
+
private timeout;
|
|
111
|
+
constructor(config?: RadKodConfig);
|
|
112
|
+
private headers;
|
|
113
|
+
private request;
|
|
114
|
+
health(): Promise<{
|
|
115
|
+
status: string;
|
|
116
|
+
}>;
|
|
117
|
+
models(): Promise<Model[]>;
|
|
118
|
+
chat(options: ChatOptions): Promise<ChatResponse>;
|
|
119
|
+
chatStream(options: ChatOptions, callbacks?: StreamCallbacks): Promise<ChatStreamResult>;
|
|
120
|
+
prompt(text: string, model?: string): Promise<string>;
|
|
121
|
+
promptStream(text: string, onToken: (token: string) => void, model?: string): Promise<string>;
|
|
122
|
+
chatWithTools(options: ChatOptions & {
|
|
123
|
+
tools: ToolDefinition[];
|
|
124
|
+
}, toolHandlers: Record<string, (args: unknown) => unknown | Promise<unknown>>, maxRounds?: number): Promise<{
|
|
125
|
+
messages: ChatMessage[];
|
|
126
|
+
finalText: string;
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { type ChatMessage, type ChatOptions, type ChatResponse, type ChatStreamResult, type Model, RadKodAI, type RadKodConfig, RadKodError, type StreamCallbacks, type StreamChunk, type ToolCall, type ToolDefinition, RadKodAI as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
interface RadKodConfig {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
defaultModel?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
interface ChatMessage {
|
|
8
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
9
|
+
content: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
tool_call_id?: string;
|
|
12
|
+
tool_calls?: ToolCall[];
|
|
13
|
+
}
|
|
14
|
+
interface ToolDefinition {
|
|
15
|
+
type: 'function';
|
|
16
|
+
function: {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
parameters: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface ToolCall {
|
|
23
|
+
id: string;
|
|
24
|
+
type: 'function';
|
|
25
|
+
function: {
|
|
26
|
+
name: string;
|
|
27
|
+
arguments: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface ChatOptions {
|
|
31
|
+
model?: string;
|
|
32
|
+
messages: ChatMessage[];
|
|
33
|
+
stream?: boolean;
|
|
34
|
+
tools?: ToolDefinition[];
|
|
35
|
+
tool_choice?: 'auto' | 'none' | {
|
|
36
|
+
type: 'function';
|
|
37
|
+
function: {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
max_tokens?: number;
|
|
42
|
+
temperature?: number;
|
|
43
|
+
top_p?: number;
|
|
44
|
+
seed?: number;
|
|
45
|
+
}
|
|
46
|
+
interface ChatResponse {
|
|
47
|
+
id: string;
|
|
48
|
+
object: string;
|
|
49
|
+
created: number;
|
|
50
|
+
model: string;
|
|
51
|
+
choices: {
|
|
52
|
+
index: number;
|
|
53
|
+
message: ChatMessage;
|
|
54
|
+
finish_reason: string | null;
|
|
55
|
+
}[];
|
|
56
|
+
usage?: {
|
|
57
|
+
prompt_tokens: number;
|
|
58
|
+
completion_tokens: number;
|
|
59
|
+
total_tokens: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
interface StreamChunk {
|
|
63
|
+
id: string;
|
|
64
|
+
object: string;
|
|
65
|
+
created: number;
|
|
66
|
+
model: string;
|
|
67
|
+
choices: {
|
|
68
|
+
index: number;
|
|
69
|
+
delta: {
|
|
70
|
+
role?: string;
|
|
71
|
+
content?: string;
|
|
72
|
+
tool_calls?: {
|
|
73
|
+
index: number;
|
|
74
|
+
id?: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
function?: {
|
|
77
|
+
name?: string;
|
|
78
|
+
arguments?: string;
|
|
79
|
+
};
|
|
80
|
+
}[];
|
|
81
|
+
};
|
|
82
|
+
finish_reason: string | null;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
interface Model {
|
|
86
|
+
id: string;
|
|
87
|
+
object: string;
|
|
88
|
+
created: number;
|
|
89
|
+
owned_by: string;
|
|
90
|
+
}
|
|
91
|
+
interface StreamCallbacks {
|
|
92
|
+
onToken?: (token: string) => void;
|
|
93
|
+
onToolCall?: (toolCall: ToolCall) => void;
|
|
94
|
+
onDone?: (fullText: string) => void;
|
|
95
|
+
onError?: (error: Error) => void;
|
|
96
|
+
}
|
|
97
|
+
interface ChatStreamResult {
|
|
98
|
+
text: string;
|
|
99
|
+
toolCalls: ToolCall[];
|
|
100
|
+
}
|
|
101
|
+
declare class RadKodError extends Error {
|
|
102
|
+
status: number;
|
|
103
|
+
body: unknown;
|
|
104
|
+
constructor(message: string, status: number, body?: unknown);
|
|
105
|
+
}
|
|
106
|
+
declare class RadKodAI {
|
|
107
|
+
private baseUrl;
|
|
108
|
+
private apiKey;
|
|
109
|
+
private defaultModel;
|
|
110
|
+
private timeout;
|
|
111
|
+
constructor(config?: RadKodConfig);
|
|
112
|
+
private headers;
|
|
113
|
+
private request;
|
|
114
|
+
health(): Promise<{
|
|
115
|
+
status: string;
|
|
116
|
+
}>;
|
|
117
|
+
models(): Promise<Model[]>;
|
|
118
|
+
chat(options: ChatOptions): Promise<ChatResponse>;
|
|
119
|
+
chatStream(options: ChatOptions, callbacks?: StreamCallbacks): Promise<ChatStreamResult>;
|
|
120
|
+
prompt(text: string, model?: string): Promise<string>;
|
|
121
|
+
promptStream(text: string, onToken: (token: string) => void, model?: string): Promise<string>;
|
|
122
|
+
chatWithTools(options: ChatOptions & {
|
|
123
|
+
tools: ToolDefinition[];
|
|
124
|
+
}, toolHandlers: Record<string, (args: unknown) => unknown | Promise<unknown>>, maxRounds?: number): Promise<{
|
|
125
|
+
messages: ChatMessage[];
|
|
126
|
+
finalText: string;
|
|
127
|
+
}>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { type ChatMessage, type ChatOptions, type ChatResponse, type ChatStreamResult, type Model, RadKodAI, type RadKodConfig, RadKodError, type StreamCallbacks, type StreamChunk, type ToolCall, type ToolDefinition, RadKodAI as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
RadKodAI: () => RadKodAI,
|
|
24
|
+
RadKodError: () => RadKodError,
|
|
25
|
+
default: () => index_default
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var RadKodError = class extends Error {
|
|
29
|
+
constructor(message, status, body) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "RadKodError";
|
|
32
|
+
this.status = status;
|
|
33
|
+
this.body = body;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var RadKodAI = class {
|
|
37
|
+
constructor(config = {}) {
|
|
38
|
+
this.baseUrl = (config.baseUrl || "https://ai.radkod.com").replace(/\/$/, "");
|
|
39
|
+
this.apiKey = config.apiKey || "";
|
|
40
|
+
this.defaultModel = config.defaultModel || "z-ai/glm4.7";
|
|
41
|
+
this.timeout = config.timeout || 3e5;
|
|
42
|
+
}
|
|
43
|
+
// ── Headers ─────────────────────────────────────────────────
|
|
44
|
+
headers() {
|
|
45
|
+
const h = { "Content-Type": "application/json" };
|
|
46
|
+
if (this.apiKey) h["Authorization"] = `Bearer ${this.apiKey}`;
|
|
47
|
+
return h;
|
|
48
|
+
}
|
|
49
|
+
async request(path, init) {
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
52
|
+
try {
|
|
53
|
+
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
54
|
+
...init,
|
|
55
|
+
headers: { ...this.headers(), ...init?.headers },
|
|
56
|
+
signal: controller.signal
|
|
57
|
+
});
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
if (!resp.ok) {
|
|
60
|
+
let body;
|
|
61
|
+
try {
|
|
62
|
+
body = await resp.json();
|
|
63
|
+
} catch {
|
|
64
|
+
body = await resp.text();
|
|
65
|
+
}
|
|
66
|
+
throw new RadKodError(
|
|
67
|
+
`API error ${resp.status}: ${typeof body === "string" ? body : JSON.stringify(body)}`,
|
|
68
|
+
resp.status,
|
|
69
|
+
body
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return await resp.json();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
if (err instanceof RadKodError) throw err;
|
|
76
|
+
throw new RadKodError(String(err), 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ── Health ──────────────────────────────────────────────────
|
|
80
|
+
async health() {
|
|
81
|
+
return this.request("/health");
|
|
82
|
+
}
|
|
83
|
+
// ── Models ──────────────────────────────────────────────────
|
|
84
|
+
async models() {
|
|
85
|
+
const data = await this.request("/v1/models");
|
|
86
|
+
return data.data;
|
|
87
|
+
}
|
|
88
|
+
// ── Chat (non-streaming) ──────────────────────────────────
|
|
89
|
+
async chat(options) {
|
|
90
|
+
return this.request("/v1/chat/completions", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
model: options.model || this.defaultModel,
|
|
94
|
+
messages: options.messages,
|
|
95
|
+
stream: false,
|
|
96
|
+
tools: options.tools,
|
|
97
|
+
tool_choice: options.tool_choice,
|
|
98
|
+
max_tokens: options.max_tokens,
|
|
99
|
+
temperature: options.temperature,
|
|
100
|
+
top_p: options.top_p,
|
|
101
|
+
seed: options.seed
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// ── Chat (streaming) ─────────────────────────────────────
|
|
106
|
+
async chatStream(options, callbacks = {}) {
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
109
|
+
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: this.headers(),
|
|
112
|
+
signal: controller.signal,
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
model: options.model || this.defaultModel,
|
|
115
|
+
messages: options.messages,
|
|
116
|
+
stream: true,
|
|
117
|
+
tools: options.tools,
|
|
118
|
+
tool_choice: options.tool_choice,
|
|
119
|
+
max_tokens: options.max_tokens,
|
|
120
|
+
temperature: options.temperature,
|
|
121
|
+
top_p: options.top_p,
|
|
122
|
+
seed: options.seed
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
if (!resp.ok) {
|
|
127
|
+
let body;
|
|
128
|
+
try {
|
|
129
|
+
body = await resp.json();
|
|
130
|
+
} catch {
|
|
131
|
+
body = await resp.text();
|
|
132
|
+
}
|
|
133
|
+
const err = new RadKodError(`API error ${resp.status}`, resp.status, body);
|
|
134
|
+
callbacks.onError?.(err);
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
const reader = resp.body.getReader();
|
|
138
|
+
const decoder = new TextDecoder();
|
|
139
|
+
let fullText = "";
|
|
140
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
141
|
+
let buffer = "";
|
|
142
|
+
try {
|
|
143
|
+
while (true) {
|
|
144
|
+
const { done, value } = await reader.read();
|
|
145
|
+
if (done) break;
|
|
146
|
+
buffer += decoder.decode(value, { stream: true });
|
|
147
|
+
const lines = buffer.split("\n");
|
|
148
|
+
buffer = lines.pop() || "";
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
if (!line.startsWith("data: ")) continue;
|
|
151
|
+
const data = line.slice(6).trim();
|
|
152
|
+
if (data === "[DONE]") continue;
|
|
153
|
+
try {
|
|
154
|
+
const chunk = JSON.parse(data);
|
|
155
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
156
|
+
if (delta?.content) {
|
|
157
|
+
fullText += delta.content;
|
|
158
|
+
callbacks.onToken?.(delta.content);
|
|
159
|
+
}
|
|
160
|
+
if (delta?.tool_calls) {
|
|
161
|
+
for (const tc of delta.tool_calls) {
|
|
162
|
+
let existing = toolCalls.get(tc.index);
|
|
163
|
+
if (!existing) {
|
|
164
|
+
existing = {
|
|
165
|
+
id: tc.id || "",
|
|
166
|
+
type: "function",
|
|
167
|
+
function: { name: "", arguments: "" }
|
|
168
|
+
};
|
|
169
|
+
toolCalls.set(tc.index, existing);
|
|
170
|
+
}
|
|
171
|
+
if (tc.id) existing.id = tc.id;
|
|
172
|
+
if (tc.function?.name) existing.function.name += tc.function.name;
|
|
173
|
+
if (tc.function?.arguments) existing.function.arguments += tc.function.arguments;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
|
|
177
|
+
for (const tc of toolCalls.values()) {
|
|
178
|
+
callbacks.onToolCall?.(tc);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
const result = {
|
|
190
|
+
text: fullText,
|
|
191
|
+
toolCalls: [...toolCalls.values()]
|
|
192
|
+
};
|
|
193
|
+
callbacks.onDone?.(fullText);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
// ── Convenience: simple prompt ───────────────────────────
|
|
197
|
+
async prompt(text, model) {
|
|
198
|
+
const resp = await this.chat({
|
|
199
|
+
model,
|
|
200
|
+
messages: [{ role: "user", content: text }]
|
|
201
|
+
});
|
|
202
|
+
return resp.choices[0]?.message?.content || "";
|
|
203
|
+
}
|
|
204
|
+
// ── Convenience: streaming prompt ────────────────────────
|
|
205
|
+
async promptStream(text, onToken, model) {
|
|
206
|
+
const result = await this.chatStream(
|
|
207
|
+
{ model, messages: [{ role: "user", content: text }] },
|
|
208
|
+
{ onToken }
|
|
209
|
+
);
|
|
210
|
+
return result.text;
|
|
211
|
+
}
|
|
212
|
+
// ── Tool calling helper ──────────────────────────────────
|
|
213
|
+
async chatWithTools(options, toolHandlers, maxRounds = 5) {
|
|
214
|
+
const messages = [...options.messages];
|
|
215
|
+
let round = 0;
|
|
216
|
+
while (round < maxRounds) {
|
|
217
|
+
const resp = await this.chat({ ...options, messages });
|
|
218
|
+
const choice = resp.choices[0];
|
|
219
|
+
messages.push(choice.message);
|
|
220
|
+
if (!choice.message.tool_calls || choice.finish_reason !== "tool_calls") {
|
|
221
|
+
return { messages, finalText: choice.message.content || "" };
|
|
222
|
+
}
|
|
223
|
+
for (const tc of choice.message.tool_calls) {
|
|
224
|
+
const handler = toolHandlers[tc.function.name];
|
|
225
|
+
if (!handler) {
|
|
226
|
+
messages.push({
|
|
227
|
+
role: "tool",
|
|
228
|
+
tool_call_id: tc.id,
|
|
229
|
+
content: JSON.stringify({ error: `Unknown tool: ${tc.function.name}` })
|
|
230
|
+
});
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const args = JSON.parse(tc.function.arguments);
|
|
235
|
+
const result = await handler(args);
|
|
236
|
+
messages.push({
|
|
237
|
+
role: "tool",
|
|
238
|
+
tool_call_id: tc.id,
|
|
239
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
messages.push({
|
|
243
|
+
role: "tool",
|
|
244
|
+
tool_call_id: tc.id,
|
|
245
|
+
content: JSON.stringify({ error: String(err) })
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
round++;
|
|
250
|
+
}
|
|
251
|
+
return { messages, finalText: messages[messages.length - 1]?.content || "" };
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var index_default = RadKodAI;
|
|
255
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
256
|
+
0 && (module.exports = {
|
|
257
|
+
RadKodAI,
|
|
258
|
+
RadKodError
|
|
259
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var RadKodError = class extends Error {
|
|
3
|
+
constructor(message, status, body) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "RadKodError";
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.body = body;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var RadKodAI = class {
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.baseUrl = (config.baseUrl || "https://ai.radkod.com").replace(/\/$/, "");
|
|
13
|
+
this.apiKey = config.apiKey || "";
|
|
14
|
+
this.defaultModel = config.defaultModel || "z-ai/glm4.7";
|
|
15
|
+
this.timeout = config.timeout || 3e5;
|
|
16
|
+
}
|
|
17
|
+
// ── Headers ─────────────────────────────────────────────────
|
|
18
|
+
headers() {
|
|
19
|
+
const h = { "Content-Type": "application/json" };
|
|
20
|
+
if (this.apiKey) h["Authorization"] = `Bearer ${this.apiKey}`;
|
|
21
|
+
return h;
|
|
22
|
+
}
|
|
23
|
+
async request(path, init) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
26
|
+
try {
|
|
27
|
+
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
28
|
+
...init,
|
|
29
|
+
headers: { ...this.headers(), ...init?.headers },
|
|
30
|
+
signal: controller.signal
|
|
31
|
+
});
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
if (!resp.ok) {
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = await resp.json();
|
|
37
|
+
} catch {
|
|
38
|
+
body = await resp.text();
|
|
39
|
+
}
|
|
40
|
+
throw new RadKodError(
|
|
41
|
+
`API error ${resp.status}: ${typeof body === "string" ? body : JSON.stringify(body)}`,
|
|
42
|
+
resp.status,
|
|
43
|
+
body
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return await resp.json();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
if (err instanceof RadKodError) throw err;
|
|
50
|
+
throw new RadKodError(String(err), 0);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ── Health ──────────────────────────────────────────────────
|
|
54
|
+
async health() {
|
|
55
|
+
return this.request("/health");
|
|
56
|
+
}
|
|
57
|
+
// ── Models ──────────────────────────────────────────────────
|
|
58
|
+
async models() {
|
|
59
|
+
const data = await this.request("/v1/models");
|
|
60
|
+
return data.data;
|
|
61
|
+
}
|
|
62
|
+
// ── Chat (non-streaming) ──────────────────────────────────
|
|
63
|
+
async chat(options) {
|
|
64
|
+
return this.request("/v1/chat/completions", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: JSON.stringify({
|
|
67
|
+
model: options.model || this.defaultModel,
|
|
68
|
+
messages: options.messages,
|
|
69
|
+
stream: false,
|
|
70
|
+
tools: options.tools,
|
|
71
|
+
tool_choice: options.tool_choice,
|
|
72
|
+
max_tokens: options.max_tokens,
|
|
73
|
+
temperature: options.temperature,
|
|
74
|
+
top_p: options.top_p,
|
|
75
|
+
seed: options.seed
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ── Chat (streaming) ─────────────────────────────────────
|
|
80
|
+
async chatStream(options, callbacks = {}) {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
83
|
+
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: this.headers(),
|
|
86
|
+
signal: controller.signal,
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
model: options.model || this.defaultModel,
|
|
89
|
+
messages: options.messages,
|
|
90
|
+
stream: true,
|
|
91
|
+
tools: options.tools,
|
|
92
|
+
tool_choice: options.tool_choice,
|
|
93
|
+
max_tokens: options.max_tokens,
|
|
94
|
+
temperature: options.temperature,
|
|
95
|
+
top_p: options.top_p,
|
|
96
|
+
seed: options.seed
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
if (!resp.ok) {
|
|
101
|
+
let body;
|
|
102
|
+
try {
|
|
103
|
+
body = await resp.json();
|
|
104
|
+
} catch {
|
|
105
|
+
body = await resp.text();
|
|
106
|
+
}
|
|
107
|
+
const err = new RadKodError(`API error ${resp.status}`, resp.status, body);
|
|
108
|
+
callbacks.onError?.(err);
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
const reader = resp.body.getReader();
|
|
112
|
+
const decoder = new TextDecoder();
|
|
113
|
+
let fullText = "";
|
|
114
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
115
|
+
let buffer = "";
|
|
116
|
+
try {
|
|
117
|
+
while (true) {
|
|
118
|
+
const { done, value } = await reader.read();
|
|
119
|
+
if (done) break;
|
|
120
|
+
buffer += decoder.decode(value, { stream: true });
|
|
121
|
+
const lines = buffer.split("\n");
|
|
122
|
+
buffer = lines.pop() || "";
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (!line.startsWith("data: ")) continue;
|
|
125
|
+
const data = line.slice(6).trim();
|
|
126
|
+
if (data === "[DONE]") continue;
|
|
127
|
+
try {
|
|
128
|
+
const chunk = JSON.parse(data);
|
|
129
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
130
|
+
if (delta?.content) {
|
|
131
|
+
fullText += delta.content;
|
|
132
|
+
callbacks.onToken?.(delta.content);
|
|
133
|
+
}
|
|
134
|
+
if (delta?.tool_calls) {
|
|
135
|
+
for (const tc of delta.tool_calls) {
|
|
136
|
+
let existing = toolCalls.get(tc.index);
|
|
137
|
+
if (!existing) {
|
|
138
|
+
existing = {
|
|
139
|
+
id: tc.id || "",
|
|
140
|
+
type: "function",
|
|
141
|
+
function: { name: "", arguments: "" }
|
|
142
|
+
};
|
|
143
|
+
toolCalls.set(tc.index, existing);
|
|
144
|
+
}
|
|
145
|
+
if (tc.id) existing.id = tc.id;
|
|
146
|
+
if (tc.function?.name) existing.function.name += tc.function.name;
|
|
147
|
+
if (tc.function?.arguments) existing.function.arguments += tc.function.arguments;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
|
|
151
|
+
for (const tc of toolCalls.values()) {
|
|
152
|
+
callbacks.onToolCall?.(tc);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
const result = {
|
|
164
|
+
text: fullText,
|
|
165
|
+
toolCalls: [...toolCalls.values()]
|
|
166
|
+
};
|
|
167
|
+
callbacks.onDone?.(fullText);
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
// ── Convenience: simple prompt ───────────────────────────
|
|
171
|
+
async prompt(text, model) {
|
|
172
|
+
const resp = await this.chat({
|
|
173
|
+
model,
|
|
174
|
+
messages: [{ role: "user", content: text }]
|
|
175
|
+
});
|
|
176
|
+
return resp.choices[0]?.message?.content || "";
|
|
177
|
+
}
|
|
178
|
+
// ── Convenience: streaming prompt ────────────────────────
|
|
179
|
+
async promptStream(text, onToken, model) {
|
|
180
|
+
const result = await this.chatStream(
|
|
181
|
+
{ model, messages: [{ role: "user", content: text }] },
|
|
182
|
+
{ onToken }
|
|
183
|
+
);
|
|
184
|
+
return result.text;
|
|
185
|
+
}
|
|
186
|
+
// ── Tool calling helper ──────────────────────────────────
|
|
187
|
+
async chatWithTools(options, toolHandlers, maxRounds = 5) {
|
|
188
|
+
const messages = [...options.messages];
|
|
189
|
+
let round = 0;
|
|
190
|
+
while (round < maxRounds) {
|
|
191
|
+
const resp = await this.chat({ ...options, messages });
|
|
192
|
+
const choice = resp.choices[0];
|
|
193
|
+
messages.push(choice.message);
|
|
194
|
+
if (!choice.message.tool_calls || choice.finish_reason !== "tool_calls") {
|
|
195
|
+
return { messages, finalText: choice.message.content || "" };
|
|
196
|
+
}
|
|
197
|
+
for (const tc of choice.message.tool_calls) {
|
|
198
|
+
const handler = toolHandlers[tc.function.name];
|
|
199
|
+
if (!handler) {
|
|
200
|
+
messages.push({
|
|
201
|
+
role: "tool",
|
|
202
|
+
tool_call_id: tc.id,
|
|
203
|
+
content: JSON.stringify({ error: `Unknown tool: ${tc.function.name}` })
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const args = JSON.parse(tc.function.arguments);
|
|
209
|
+
const result = await handler(args);
|
|
210
|
+
messages.push({
|
|
211
|
+
role: "tool",
|
|
212
|
+
tool_call_id: tc.id,
|
|
213
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
214
|
+
});
|
|
215
|
+
} catch (err) {
|
|
216
|
+
messages.push({
|
|
217
|
+
role: "tool",
|
|
218
|
+
tool_call_id: tc.id,
|
|
219
|
+
content: JSON.stringify({ error: String(err) })
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
round++;
|
|
224
|
+
}
|
|
225
|
+
return { messages, finalText: messages[messages.length - 1]?.content || "" };
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var index_default = RadKodAI;
|
|
229
|
+
export {
|
|
230
|
+
RadKodAI,
|
|
231
|
+
RadKodError,
|
|
232
|
+
index_default as default
|
|
233
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@radkod/ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight SDK for RadKod AI API — OpenAI-compatible LLM proxy with streaming and tool calling",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
18
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"tsup": "^8.0.0",
|
|
22
|
+
"typescript": "^5.9.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": ["ai", "llm", "openai", "sdk", "radkod", "streaming", "tool-calling"],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/apo-bozdag/benvidia"
|
|
29
|
+
}
|
|
30
|
+
}
|