agent-accelerator 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 +1220 -0
- package/SYSTEM_PROMPT.md +14 -0
- package/SYSTEM_PROMPT_AGENT.md +34 -0
- package/SYSTEM_PROMPT_TOOLS.md +9 -0
- package/bunfig.toml +2 -0
- package/package.json +59 -0
- package/src/agent/agent.ts +615 -0
- package/src/agent/context.ts +161 -0
- package/src/agent/delegation.ts +481 -0
- package/src/agent/loop.ts +569 -0
- package/src/agent/subagent.ts +83 -0
- package/src/ai-sdk/converters.ts +342 -0
- package/src/ai-sdk/errors.ts +122 -0
- package/src/ai-sdk/executor.ts +454 -0
- package/src/ai-sdk/index.ts +55 -0
- package/src/ai-sdk/model-provider.ts +303 -0
- package/src/ai-sdk/options.ts +306 -0
- package/src/ai-sdk/provider.ts +415 -0
- package/src/ai-sdk/registry.ts +416 -0
- package/src/data/README.md +84 -0
- package/src/index.ts +190 -0
- package/src/models/catalog-cache.ts +273 -0
- package/src/models/catalog.ts +503 -0
- package/src/streaming/event-stream.ts +211 -0
- package/src/streaming/sse-parser.ts +97 -0
- package/src/tokens/counter.ts +136 -0
- package/src/tools/executor.ts +365 -0
- package/src/tools/schema.ts +221 -0
- package/src/tools/tool.ts +101 -0
- package/src/types/agent.ts +87 -0
- package/src/types/core.ts +86 -0
- package/src/types/message.ts +115 -0
- package/src/types/model.ts +212 -0
- package/src/types/provider-payloads.ts +434 -0
- package/src/types/response.ts +158 -0
- package/src/types/tool.ts +61 -0
- package/src/utils/base64.ts +27 -0
- package/src/utils/cache.ts +146 -0
- package/src/utils/env.ts +78 -0
- package/src/utils/headers.ts +110 -0
- package/src/utils/media.ts +137 -0
- package/src/utils/serialization.ts +91 -0
- package/src/utils/session.ts +26 -0
- package/src/utils/thought-signature.ts +27 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { StreamEvent, AgentResponse } from "../types/response.ts";
|
|
2
|
+
|
|
3
|
+
/** Listener invoked for a matching stream event type or `*`. */
|
|
4
|
+
export type StreamEventListener = (event: StreamEvent) => void;
|
|
5
|
+
|
|
6
|
+
type Resolver = {
|
|
7
|
+
resolve: (value: IteratorResult<StreamEvent>) => void;
|
|
8
|
+
reject: (err: Error) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Async-iterable event stream returned by Agent.stream and streaming Agent.run. */
|
|
12
|
+
export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
|
|
13
|
+
private queue: StreamEvent[] = [];
|
|
14
|
+
private resolvers: Resolver[] = [];
|
|
15
|
+
private listeners: Map<string, Set<StreamEventListener>> = new Map();
|
|
16
|
+
private finished = false;
|
|
17
|
+
private error: Error | null = null;
|
|
18
|
+
private cancelled = false;
|
|
19
|
+
private cancelHandlers = new Set<() => void>();
|
|
20
|
+
private finalResultPromise: Promise<AgentResponse>;
|
|
21
|
+
private resolveFinalResult!: (result: AgentResponse) => void;
|
|
22
|
+
private rejectFinalResult!: (err: Error) => void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates an empty event stream. `Agent.stream()` creates and completes
|
|
26
|
+
* streams automatically; this constructor is useful for custom adapters.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const stream = new AssistantMessageEventStream();
|
|
31
|
+
* stream.on("text_delta", (event) => process.stdout.write(event.delta ?? ""));
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
constructor() {
|
|
35
|
+
this.finalResultPromise = new Promise<AgentResponse>((resolve, reject) => {
|
|
36
|
+
this.resolveFinalResult = resolve;
|
|
37
|
+
this.rejectFinalResult = reject;
|
|
38
|
+
});
|
|
39
|
+
// Prevent unhandled rejection when no one awaits result()
|
|
40
|
+
this.finalResultPromise.catch(() => {});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Publishes an event to listeners and async iterators. */
|
|
44
|
+
push(event: StreamEvent): void {
|
|
45
|
+
if (this.finished) return;
|
|
46
|
+
|
|
47
|
+
if (event.type === "error") {
|
|
48
|
+
this.error = (event.error instanceof Error ? event.error : new Error(String(event.error))) ?? new Error("Unknown error in stream");
|
|
49
|
+
// Reject final result but don't finish iterator yet — let iterators receive error event then fail
|
|
50
|
+
// Still deliver error event to listeners/consumers before marking finished? Keep consistent with agent-accel: error terminates stream
|
|
51
|
+
try { this.rejectFinalResult(this.error); } catch {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Trigger registered event listener callbacks
|
|
55
|
+
const specificListeners = this.listeners.get(event.type);
|
|
56
|
+
if (specificListeners) {
|
|
57
|
+
for (const listener of specificListeners) {
|
|
58
|
+
try { listener(event); } catch {}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const allListeners = this.listeners.get("*");
|
|
62
|
+
if (allListeners) {
|
|
63
|
+
for (const listener of allListeners) {
|
|
64
|
+
try { listener(event); } catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (event.type === "error") {
|
|
69
|
+
// Mark finished after delivering error event, reject pending resolvers
|
|
70
|
+
this.finished = true;
|
|
71
|
+
while (this.resolvers.length > 0) {
|
|
72
|
+
const r = this.resolvers.shift()!;
|
|
73
|
+
// First deliver error event if they were waiting, then reject next call
|
|
74
|
+
// For simplicity, reject immediately — consumer will see error via result() or next()
|
|
75
|
+
r.reject(this.error!);
|
|
76
|
+
}
|
|
77
|
+
// Also queue error event for later pulls if no resolver
|
|
78
|
+
if (this.queue.length === 0) this.queue.push(event);
|
|
79
|
+
else this.queue.push(event);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.resolvers.length > 0) {
|
|
84
|
+
const resolver = this.resolvers.shift()!;
|
|
85
|
+
resolver.resolve({ value: event, done: false });
|
|
86
|
+
} else {
|
|
87
|
+
this.queue.push(event);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Completes the stream and resolves result() with the final response. */
|
|
92
|
+
end(finalResponse?: AgentResponse): void {
|
|
93
|
+
if (this.finished) return;
|
|
94
|
+
this.finished = true;
|
|
95
|
+
|
|
96
|
+
if (finalResponse) {
|
|
97
|
+
this.resolveFinalResult(finalResponse);
|
|
98
|
+
} else {
|
|
99
|
+
// Resolve with empty if no response — should not happen, but prevent hanging
|
|
100
|
+
// Do not reject; just finish
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Flush any waiting resolvers as done
|
|
104
|
+
while (this.resolvers.length > 0) {
|
|
105
|
+
const resolver = this.resolvers.shift()!;
|
|
106
|
+
resolver.resolve({ value: undefined as any, done: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Fails the stream and rejects pending consumers. */
|
|
111
|
+
fail(error: Error): void {
|
|
112
|
+
if (this.finished) return;
|
|
113
|
+
// Delegate to push for unified error handling (listeners + queue + rejection)
|
|
114
|
+
this.push({ type: "error", error } as StreamEvent);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** True once cancel() has been called. Background work should stop. */
|
|
118
|
+
isCancelled(): boolean {
|
|
119
|
+
return this.cancelled;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Registers a callback invoked once on cancel(). Returns an unsubscribe fn. */
|
|
123
|
+
onCancel(handler: () => void): () => void {
|
|
124
|
+
if (this.cancelled) {
|
|
125
|
+
try { handler(); } catch {}
|
|
126
|
+
return () => {};
|
|
127
|
+
}
|
|
128
|
+
this.cancelHandlers.add(handler);
|
|
129
|
+
return () => { this.cancelHandlers.delete(handler); };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Cancels the stream: stops background work and rejects result() with AbortError.
|
|
133
|
+
* Safe to call multiple times. Breaking out of for-await calls this automatically. */
|
|
134
|
+
cancel(reason?: unknown): void {
|
|
135
|
+
if (this.finished || this.cancelled) return;
|
|
136
|
+
this.cancelled = true;
|
|
137
|
+
for (const h of [...this.cancelHandlers]) {
|
|
138
|
+
try { h(); } catch {}
|
|
139
|
+
}
|
|
140
|
+
this.cancelHandlers.clear();
|
|
141
|
+
const err = reason instanceof Error ? reason : null;
|
|
142
|
+
const abortErr: Error =
|
|
143
|
+
err && (err.name === "AbortError" || /abort|cancel/i.test(err.message))
|
|
144
|
+
? err
|
|
145
|
+
: Object.assign(new Error("Stream aborted"), { name: "AbortError" });
|
|
146
|
+
this.fail(abortErr);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Registers a listener for an event type or `*`. */
|
|
150
|
+
on(event: string, listener: StreamEventListener): this {
|
|
151
|
+
if (!this.listeners.has(event)) {
|
|
152
|
+
this.listeners.set(event, new Set());
|
|
153
|
+
}
|
|
154
|
+
this.listeners.get(event)!.add(listener);
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Removes a previously registered listener. */
|
|
159
|
+
off(event: string, listener: StreamEventListener): this {
|
|
160
|
+
this.listeners.get(event)?.delete(listener);
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Resolves when the stream finishes with the normalized AgentResponse. */
|
|
165
|
+
result(): Promise<AgentResponse> {
|
|
166
|
+
return this.finalResultPromise;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
[Symbol.asyncIterator](): AsyncIterator<StreamEvent> {
|
|
170
|
+
let iteratorDone = false;
|
|
171
|
+
return {
|
|
172
|
+
return: (): Promise<IteratorResult<StreamEvent>> => {
|
|
173
|
+
if (!iteratorDone) {
|
|
174
|
+
iteratorDone = true;
|
|
175
|
+
this.cancel();
|
|
176
|
+
}
|
|
177
|
+
return Promise.resolve({ value: undefined as any, done: true });
|
|
178
|
+
},
|
|
179
|
+
throw: (err?: unknown): Promise<IteratorResult<StreamEvent>> => {
|
|
180
|
+
if (!iteratorDone) {
|
|
181
|
+
iteratorDone = true;
|
|
182
|
+
this.cancel(err);
|
|
183
|
+
}
|
|
184
|
+
return Promise.reject(err);
|
|
185
|
+
},
|
|
186
|
+
next: (): Promise<IteratorResult<StreamEvent>> => {
|
|
187
|
+
if (this.queue.length > 0) {
|
|
188
|
+
const value = this.queue.shift()!;
|
|
189
|
+
// If queued error, next call should reject, but deliver error event first?
|
|
190
|
+
// Deliver error event then on next call reject
|
|
191
|
+
if (value.type === "error" && this.error) {
|
|
192
|
+
// Return error event as value, next next() will reject
|
|
193
|
+
return Promise.resolve({ value, done: false });
|
|
194
|
+
}
|
|
195
|
+
return Promise.resolve({ value, done: false });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.finished) {
|
|
199
|
+
if (this.error) {
|
|
200
|
+
return Promise.reject(this.error);
|
|
201
|
+
}
|
|
202
|
+
return Promise.resolve({ value: undefined as any, done: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return new Promise<IteratorResult<StreamEvent>>((resolve, reject) => {
|
|
206
|
+
this.resolvers.push({ resolve, reject });
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Parsed Server-Sent Events message. */
|
|
2
|
+
export interface SSEMessage {
|
|
3
|
+
id?: string;
|
|
4
|
+
event?: string;
|
|
5
|
+
data: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Incremental SSE parser that preserves events split across network chunks.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const parser = new SSEParser();
|
|
14
|
+
* const events = parser.feed("event: message\\ndata: hello\\n\\n");
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class SSEParser {
|
|
18
|
+
private buffer = "";
|
|
19
|
+
// Persist across feed() calls — otherwise split SSE messages lose state (S1 fix)
|
|
20
|
+
private currentEvent: string | undefined = undefined;
|
|
21
|
+
private currentData: string[] = [];
|
|
22
|
+
private currentId: string | undefined = undefined;
|
|
23
|
+
|
|
24
|
+
/** Feeds a chunk and returns all complete messages found in it. */
|
|
25
|
+
feed(chunk: string): SSEMessage[] {
|
|
26
|
+
this.buffer += chunk;
|
|
27
|
+
const messages: SSEMessage[] = [];
|
|
28
|
+
|
|
29
|
+
let start = 0;
|
|
30
|
+
while (start < this.buffer.length) {
|
|
31
|
+
const newlineIdx = this.buffer.indexOf("\n", start);
|
|
32
|
+
if (newlineIdx === -1) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let lineEnd = newlineIdx;
|
|
37
|
+
if (lineEnd > start && this.buffer.charCodeAt(lineEnd - 1) === 13) {
|
|
38
|
+
lineEnd--;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const line = this.buffer.slice(start, lineEnd);
|
|
42
|
+
start = newlineIdx + 1;
|
|
43
|
+
|
|
44
|
+
if (line.length === 0) {
|
|
45
|
+
if (this.currentData.length > 0) {
|
|
46
|
+
messages.push({
|
|
47
|
+
id: this.currentId,
|
|
48
|
+
event: this.currentEvent,
|
|
49
|
+
data: this.currentData.join("\n"),
|
|
50
|
+
});
|
|
51
|
+
this.currentEvent = undefined;
|
|
52
|
+
this.currentData = [];
|
|
53
|
+
this.currentId = undefined;
|
|
54
|
+
}
|
|
55
|
+
} else if (line.charCodeAt(0) === 58) {
|
|
56
|
+
// Comment / ping
|
|
57
|
+
continue;
|
|
58
|
+
} else if (line.startsWith("data:")) {
|
|
59
|
+
const val = line.slice(5);
|
|
60
|
+
this.currentData.push(val.startsWith(" ") ? val.slice(1) : val.trimStart());
|
|
61
|
+
} else if (line.startsWith("event:")) {
|
|
62
|
+
const val = line.slice(6);
|
|
63
|
+
this.currentEvent = (val.startsWith(" ") ? val.slice(1) : val).trim();
|
|
64
|
+
} else if (line.startsWith("id:")) {
|
|
65
|
+
const val = line.slice(3);
|
|
66
|
+
this.currentId = (val.startsWith(" ") ? val.slice(1) : val).trim();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.buffer = this.buffer.slice(start);
|
|
71
|
+
return messages;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Flushes any unterminated final event. */
|
|
75
|
+
flush(): SSEMessage[] {
|
|
76
|
+
const messages: SSEMessage[] = [];
|
|
77
|
+
// S7: flush pending currentData first, then treat remaining buffer as final lines (avoid duplication)
|
|
78
|
+
if (this.buffer.trim() !== "") {
|
|
79
|
+
// Feed remaining buffer as if terminated
|
|
80
|
+
const pending = this.feed(this.buffer + "\n\n");
|
|
81
|
+
messages.push(...pending);
|
|
82
|
+
this.buffer = "";
|
|
83
|
+
}
|
|
84
|
+
if (this.currentData.length > 0) {
|
|
85
|
+
messages.push({
|
|
86
|
+
id: this.currentId,
|
|
87
|
+
event: this.currentEvent,
|
|
88
|
+
data: this.currentData.join("\n"),
|
|
89
|
+
});
|
|
90
|
+
this.currentEvent = undefined;
|
|
91
|
+
this.currentData = [];
|
|
92
|
+
this.currentId = undefined;
|
|
93
|
+
}
|
|
94
|
+
this.buffer = "";
|
|
95
|
+
return messages;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Message, ContentPart } from "../types/message.ts";
|
|
2
|
+
import type { ProviderContext } from "../types/message.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* High-speed token estimation utility.
|
|
6
|
+
* For English text, average is ~4 characters per token or ~0.75 words per token.
|
|
7
|
+
* Code and symbols average ~2-3 characters per token.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Estimates token count from text without contacting a provider tokenizer.
|
|
11
|
+
*
|
|
12
|
+
* @example `const tokens = estimateTokensFromText(prompt);`
|
|
13
|
+
*/
|
|
14
|
+
export function estimateTokensFromText(text: string): number {
|
|
15
|
+
if (!text || text.length === 0) return 0;
|
|
16
|
+
const chars = text.length;
|
|
17
|
+
let words = 0;
|
|
18
|
+
let inWord = false;
|
|
19
|
+
let codeMarkers = 0;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < chars; i++) {
|
|
22
|
+
const code = text.charCodeAt(i);
|
|
23
|
+
// Whitespace: space(32), tab(9), LF(10), CR(13)
|
|
24
|
+
if (code === 32 || code === 9 || code === 10 || code === 13) {
|
|
25
|
+
if (inWord) {
|
|
26
|
+
words++;
|
|
27
|
+
inWord = false;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
inWord = true;
|
|
31
|
+
// Code markers: { (123), } (125), ; (59), = (61), < (60), > (62), / (47), \ (92)
|
|
32
|
+
if (code === 123 || code === 125 || code === 59 || code === 61 || code === 60 || code === 62 || code === 47 || code === 92) {
|
|
33
|
+
codeMarkers++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (inWord) words++;
|
|
38
|
+
|
|
39
|
+
const isCodeHeavy = codeMarkers > chars * 0.05;
|
|
40
|
+
const charDivisor = isCodeHeavy ? 3.0 : 3.8;
|
|
41
|
+
const wordFactor = isCodeHeavy ? 1.6 : 1.3;
|
|
42
|
+
return Math.ceil(Math.max(chars / charDivisor, words * wordFactor));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Estimates tokens for one multimodal, thinking, or tool content part.
|
|
47
|
+
*
|
|
48
|
+
* @example `const tokens = estimateTokensFromPart({ type: "text", text: prompt });`
|
|
49
|
+
*/
|
|
50
|
+
export function estimateTokensFromPart(part: ContentPart): number {
|
|
51
|
+
switch (part.type) {
|
|
52
|
+
case "text":
|
|
53
|
+
return estimateTokensFromText(part.text);
|
|
54
|
+
case "thinking":
|
|
55
|
+
return estimateTokensFromText(part.thinking);
|
|
56
|
+
case "image":
|
|
57
|
+
// Standard vision token cost ~258 tokens per low-res / 1000-1600 per high-res tile
|
|
58
|
+
return 258;
|
|
59
|
+
case "audio":
|
|
60
|
+
// ~25 tokens per second of audio
|
|
61
|
+
return 150;
|
|
62
|
+
case "video":
|
|
63
|
+
// ~250 tokens per frame / minute
|
|
64
|
+
return 500;
|
|
65
|
+
case "file":
|
|
66
|
+
return 400;
|
|
67
|
+
case "tool_call":
|
|
68
|
+
return (
|
|
69
|
+
estimateTokensFromText(part.name) +
|
|
70
|
+
estimateTokensFromText(JSON.stringify(part.arguments)) +
|
|
71
|
+
15
|
|
72
|
+
);
|
|
73
|
+
case "tool_result":
|
|
74
|
+
return (
|
|
75
|
+
estimateTokensFromText(part.name) +
|
|
76
|
+
estimateTokensFromText(
|
|
77
|
+
typeof part.result === "string" ? part.result : JSON.stringify(part.result)
|
|
78
|
+
) +
|
|
79
|
+
10
|
|
80
|
+
);
|
|
81
|
+
default:
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Estimates tokens for one normalized message including metadata overhead.
|
|
88
|
+
*
|
|
89
|
+
* @example `const tokens = estimateTokensFromMessage({ role: "user", content: "Hi" });`
|
|
90
|
+
*/
|
|
91
|
+
export function estimateTokensFromMessage(message: Message): number {
|
|
92
|
+
let count = 4; // Message metadata overhead (role, markers)
|
|
93
|
+
if (typeof message.content === "string") {
|
|
94
|
+
count += estimateTokensFromText(message.content);
|
|
95
|
+
} else if (Array.isArray(message.content)) {
|
|
96
|
+
for (const part of message.content) {
|
|
97
|
+
count += estimateTokensFromPart(part);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return count;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Estimates tokens for text, messages, or a complete ProviderContext.
|
|
105
|
+
*
|
|
106
|
+
* @example `const tokens = countTokens({ messages });`
|
|
107
|
+
*/
|
|
108
|
+
export function countTokens(
|
|
109
|
+
input: string | Message[] | ProviderContext
|
|
110
|
+
): number {
|
|
111
|
+
if (typeof input === "string") {
|
|
112
|
+
return estimateTokensFromText(input);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(input)) {
|
|
116
|
+
let total = 0;
|
|
117
|
+
for (const msg of input) {
|
|
118
|
+
total += estimateTokensFromMessage(msg);
|
|
119
|
+
}
|
|
120
|
+
return total;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let total = 0;
|
|
124
|
+
if (input.systemPrompt) {
|
|
125
|
+
total += estimateTokensFromText(input.systemPrompt) + 5;
|
|
126
|
+
}
|
|
127
|
+
if (input.messages) {
|
|
128
|
+
for (const msg of input.messages) {
|
|
129
|
+
total += estimateTokensFromMessage(msg);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (input.tools) {
|
|
133
|
+
total += estimateTokensFromText(JSON.stringify(input.tools)) + 20;
|
|
134
|
+
}
|
|
135
|
+
return total;
|
|
136
|
+
}
|