@codeany/open-agent-sdk 0.2.1 → 0.2.3
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/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +2 -0
- package/dist/engine.js.map +1 -1
- package/package.json +5 -6
- package/src/agent.ts +0 -516
- package/src/engine.ts +0 -630
- package/src/hooks.ts +0 -261
- package/src/index.ts +0 -426
- package/src/mcp/client.ts +0 -150
- package/src/providers/anthropic.ts +0 -60
- package/src/providers/index.ts +0 -34
- package/src/providers/openai.ts +0 -315
- package/src/providers/types.ts +0 -85
- package/src/sdk-mcp-server.ts +0 -78
- package/src/session.ts +0 -227
- package/src/skills/bundled/commit.ts +0 -38
- package/src/skills/bundled/debug.ts +0 -48
- package/src/skills/bundled/index.ts +0 -28
- package/src/skills/bundled/review.ts +0 -41
- package/src/skills/bundled/simplify.ts +0 -51
- package/src/skills/bundled/test.ts +0 -43
- package/src/skills/index.ts +0 -25
- package/src/skills/registry.ts +0 -133
- package/src/skills/types.ts +0 -99
- package/src/tool-helper.ts +0 -127
- package/src/tools/agent-tool.ts +0 -164
- package/src/tools/ask-user.ts +0 -79
- package/src/tools/bash.ts +0 -75
- package/src/tools/config-tool.ts +0 -89
- package/src/tools/cron-tools.ts +0 -153
- package/src/tools/edit.ts +0 -74
- package/src/tools/glob.ts +0 -77
- package/src/tools/grep.ts +0 -168
- package/src/tools/index.ts +0 -240
- package/src/tools/lsp-tool.ts +0 -163
- package/src/tools/mcp-resource-tools.ts +0 -125
- package/src/tools/notebook-edit.ts +0 -93
- package/src/tools/plan-tools.ts +0 -88
- package/src/tools/read.ts +0 -73
- package/src/tools/send-message.ts +0 -96
- package/src/tools/skill-tool.ts +0 -133
- package/src/tools/task-tools.ts +0 -290
- package/src/tools/team-tools.ts +0 -128
- package/src/tools/todo-tool.ts +0 -112
- package/src/tools/tool-search.ts +0 -87
- package/src/tools/types.ts +0 -66
- package/src/tools/web-fetch.ts +0 -66
- package/src/tools/web-search.ts +0 -86
- package/src/tools/worktree-tools.ts +0 -140
- package/src/tools/write.ts +0 -42
- package/src/types.ts +0 -486
- package/src/utils/compact.ts +0 -207
- package/src/utils/context.ts +0 -191
- package/src/utils/fileCache.ts +0 -148
- package/src/utils/messages.ts +0 -195
- package/src/utils/retry.ts +0 -140
- package/src/utils/tokens.ts +0 -145
package/src/utils/messages.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Utilities
|
|
3
|
-
*
|
|
4
|
-
* Message creation factories, normalization for API,
|
|
5
|
-
* synthetic placeholders, and content processing.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Message, UserMessage, AssistantMessage, TokenUsage } from '../types.js'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Create a user message.
|
|
12
|
-
*/
|
|
13
|
-
export function createUserMessage(
|
|
14
|
-
content: string | any[],
|
|
15
|
-
options?: {
|
|
16
|
-
uuid?: string
|
|
17
|
-
isMeta?: boolean
|
|
18
|
-
toolUseResult?: unknown
|
|
19
|
-
},
|
|
20
|
-
): UserMessage {
|
|
21
|
-
return {
|
|
22
|
-
type: 'user',
|
|
23
|
-
message: {
|
|
24
|
-
role: 'user',
|
|
25
|
-
content,
|
|
26
|
-
},
|
|
27
|
-
uuid: options?.uuid || crypto.randomUUID(),
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create an assistant message.
|
|
34
|
-
*/
|
|
35
|
-
export function createAssistantMessage(
|
|
36
|
-
content: any[],
|
|
37
|
-
usage?: TokenUsage,
|
|
38
|
-
): AssistantMessage {
|
|
39
|
-
return {
|
|
40
|
-
type: 'assistant',
|
|
41
|
-
message: {
|
|
42
|
-
role: 'assistant',
|
|
43
|
-
content,
|
|
44
|
-
},
|
|
45
|
-
uuid: crypto.randomUUID(),
|
|
46
|
-
timestamp: new Date().toISOString(),
|
|
47
|
-
usage,
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Normalize messages for the LLM API.
|
|
53
|
-
* Ensures proper message format, strips internal metadata,
|
|
54
|
-
* and fixes tool result pairing.
|
|
55
|
-
*/
|
|
56
|
-
export function normalizeMessagesForAPI(
|
|
57
|
-
messages: Array<{ role: string; content: any }>,
|
|
58
|
-
): Array<{ role: string; content: any }> {
|
|
59
|
-
const normalized: Array<{ role: string; content: any }> = []
|
|
60
|
-
|
|
61
|
-
for (let i = 0; i < messages.length; i++) {
|
|
62
|
-
const msg = messages[i]
|
|
63
|
-
|
|
64
|
-
// Ensure alternating user/assistant messages
|
|
65
|
-
if (normalized.length > 0) {
|
|
66
|
-
const last = normalized[normalized.length - 1]
|
|
67
|
-
if (last.role === msg.role) {
|
|
68
|
-
// Merge same-role messages
|
|
69
|
-
if (msg.role === 'user') {
|
|
70
|
-
// Combine content
|
|
71
|
-
const lastContent = typeof last.content === 'string'
|
|
72
|
-
? [{ type: 'text' as const, text: last.content }]
|
|
73
|
-
: last.content as any[]
|
|
74
|
-
const newContent = typeof msg.content === 'string'
|
|
75
|
-
? [{ type: 'text' as const, text: msg.content }]
|
|
76
|
-
: msg.content as any[]
|
|
77
|
-
normalized[normalized.length - 1] = {
|
|
78
|
-
role: 'user',
|
|
79
|
-
content: [...lastContent, ...newContent],
|
|
80
|
-
}
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
normalized.push({ ...msg })
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Ensure tool results are properly paired with tool_use
|
|
90
|
-
return fixToolResultPairing(normalized)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Fix tool result pairing: ensure every tool_result has a
|
|
95
|
-
* matching tool_use in the previous assistant message.
|
|
96
|
-
*/
|
|
97
|
-
function fixToolResultPairing(
|
|
98
|
-
messages: Array<{ role: string; content: any }>,
|
|
99
|
-
): Array<{ role: string; content: any }> {
|
|
100
|
-
const result: Array<{ role: string; content: any }> = []
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < messages.length; i++) {
|
|
103
|
-
const msg = messages[i]
|
|
104
|
-
|
|
105
|
-
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
106
|
-
// Check for tool_result blocks
|
|
107
|
-
const toolResults = (msg.content as any[]).filter(
|
|
108
|
-
(block: any) => block.type === 'tool_result',
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
if (toolResults.length > 0 && result.length > 0) {
|
|
112
|
-
// Find the previous assistant message
|
|
113
|
-
const prevAssistant = result[result.length - 1]
|
|
114
|
-
if (prevAssistant.role === 'assistant' && Array.isArray(prevAssistant.content)) {
|
|
115
|
-
const toolUseIds = new Set(
|
|
116
|
-
(prevAssistant.content as any[])
|
|
117
|
-
.filter((b: any) => b.type === 'tool_use')
|
|
118
|
-
.map((b: any) => b.id),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
// Filter out orphaned tool results
|
|
122
|
-
const validContent = (msg.content as any[]).filter((block: any) => {
|
|
123
|
-
if (block.type === 'tool_result') {
|
|
124
|
-
return toolUseIds.has(block.tool_use_id)
|
|
125
|
-
}
|
|
126
|
-
return true
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
if (validContent.length > 0) {
|
|
130
|
-
result.push({ ...msg, content: validContent })
|
|
131
|
-
}
|
|
132
|
-
continue
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
result.push(msg)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return result
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Strip images from messages (for compaction).
|
|
145
|
-
*/
|
|
146
|
-
export function stripImagesFromMessages(
|
|
147
|
-
messages: Array<{ role: string; content: any }>,
|
|
148
|
-
): Array<{ role: string; content: any }> {
|
|
149
|
-
return messages.map((msg) => {
|
|
150
|
-
if (typeof msg.content === 'string') return msg
|
|
151
|
-
if (!Array.isArray(msg.content)) return msg
|
|
152
|
-
|
|
153
|
-
const filtered = (msg.content as any[]).filter(
|
|
154
|
-
(block: any) => block.type !== 'image',
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
...msg,
|
|
159
|
-
content: filtered.length > 0 ? filtered : '[content removed]',
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Extract text from message content blocks.
|
|
166
|
-
*/
|
|
167
|
-
export function extractTextFromContent(
|
|
168
|
-
content: any[] | string,
|
|
169
|
-
): string {
|
|
170
|
-
if (typeof content === 'string') return content
|
|
171
|
-
|
|
172
|
-
return content
|
|
173
|
-
.filter((b: any) => b.type === 'text')
|
|
174
|
-
.map((b: any) => b.text)
|
|
175
|
-
.join('')
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Create a system message for compact boundary.
|
|
180
|
-
*/
|
|
181
|
-
export function createCompactBoundaryMessage(): { role: string; content: string } {
|
|
182
|
-
return {
|
|
183
|
-
role: 'user',
|
|
184
|
-
content: '[Previous context has been summarized above. Continuing conversation.]',
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Truncate text to max length with ellipsis.
|
|
190
|
-
*/
|
|
191
|
-
export function truncateText(text: string, maxLength: number): string {
|
|
192
|
-
if (text.length <= maxLength) return text
|
|
193
|
-
const half = Math.floor(maxLength / 2)
|
|
194
|
-
return text.slice(0, half) + '\n...(truncated)...\n' + text.slice(-half)
|
|
195
|
-
}
|
package/src/utils/retry.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retry Logic with Exponential Backoff
|
|
3
|
-
*
|
|
4
|
-
* Handles API retries for rate limits, overloaded servers,
|
|
5
|
-
* and transient failures.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Retry configuration.
|
|
10
|
-
*/
|
|
11
|
-
export interface RetryConfig {
|
|
12
|
-
maxRetries: number
|
|
13
|
-
baseDelayMs: number
|
|
14
|
-
maxDelayMs: number
|
|
15
|
-
retryableStatusCodes: number[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Default retry configuration.
|
|
20
|
-
*/
|
|
21
|
-
export const DEFAULT_RETRY_CONFIG: RetryConfig = {
|
|
22
|
-
maxRetries: 3,
|
|
23
|
-
baseDelayMs: 2000,
|
|
24
|
-
maxDelayMs: 30000,
|
|
25
|
-
retryableStatusCodes: [429, 500, 502, 503, 529],
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Check if an error is retryable.
|
|
30
|
-
*/
|
|
31
|
-
export function isRetryableError(err: any, config: RetryConfig = DEFAULT_RETRY_CONFIG): boolean {
|
|
32
|
-
if (err?.status && config.retryableStatusCodes.includes(err.status)) {
|
|
33
|
-
return true
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Network errors
|
|
37
|
-
if (err?.code === 'ECONNRESET' || err?.code === 'ETIMEDOUT' || err?.code === 'ECONNREFUSED') {
|
|
38
|
-
return true
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// API overloaded
|
|
42
|
-
if (err?.error?.type === 'overloaded_error') {
|
|
43
|
-
return true
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Calculate delay for exponential backoff.
|
|
51
|
-
*/
|
|
52
|
-
export function getRetryDelay(attempt: number, config: RetryConfig = DEFAULT_RETRY_CONFIG): number {
|
|
53
|
-
const delay = config.baseDelayMs * Math.pow(2, attempt)
|
|
54
|
-
// Add jitter (±25%)
|
|
55
|
-
const jitter = delay * 0.25 * (Math.random() * 2 - 1)
|
|
56
|
-
return Math.min(delay + jitter, config.maxDelayMs)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Execute a function with retries.
|
|
61
|
-
*/
|
|
62
|
-
export async function withRetry<T>(
|
|
63
|
-
fn: () => Promise<T>,
|
|
64
|
-
config: RetryConfig = DEFAULT_RETRY_CONFIG,
|
|
65
|
-
abortSignal?: AbortSignal,
|
|
66
|
-
): Promise<T> {
|
|
67
|
-
let lastError: any
|
|
68
|
-
|
|
69
|
-
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
70
|
-
if (abortSignal?.aborted) {
|
|
71
|
-
throw new Error('Aborted')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
return await fn()
|
|
76
|
-
} catch (err: any) {
|
|
77
|
-
lastError = err
|
|
78
|
-
|
|
79
|
-
if (!isRetryableError(err, config)) {
|
|
80
|
-
throw err
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (attempt === config.maxRetries) {
|
|
84
|
-
throw err
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Wait before retry
|
|
88
|
-
const delay = getRetryDelay(attempt, config)
|
|
89
|
-
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
throw lastError
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if an error is a "prompt too long" error.
|
|
98
|
-
*/
|
|
99
|
-
export function isPromptTooLongError(err: any): boolean {
|
|
100
|
-
if (err?.status === 400) {
|
|
101
|
-
const message = err?.error?.error?.message || err?.message || ''
|
|
102
|
-
return message.includes('prompt is too long') ||
|
|
103
|
-
message.includes('max_tokens') ||
|
|
104
|
-
message.includes('context length')
|
|
105
|
-
}
|
|
106
|
-
return false
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Check if error is an auth error.
|
|
111
|
-
*/
|
|
112
|
-
export function isAuthError(err: any): boolean {
|
|
113
|
-
return err?.status === 401 || err?.status === 403
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check if error is a rate limit error.
|
|
118
|
-
*/
|
|
119
|
-
export function isRateLimitError(err: any): boolean {
|
|
120
|
-
return err?.status === 429
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Format an API error for display.
|
|
125
|
-
*/
|
|
126
|
-
export function formatApiError(err: any): string {
|
|
127
|
-
if (isAuthError(err)) {
|
|
128
|
-
return 'Authentication failed. Check your CODEANY_API_KEY.'
|
|
129
|
-
}
|
|
130
|
-
if (isRateLimitError(err)) {
|
|
131
|
-
return 'Rate limit exceeded. Please retry after a short wait.'
|
|
132
|
-
}
|
|
133
|
-
if (err?.status === 529) {
|
|
134
|
-
return 'API overloaded. Please retry later.'
|
|
135
|
-
}
|
|
136
|
-
if (isPromptTooLongError(err)) {
|
|
137
|
-
return 'Prompt too long. Auto-compacting conversation...'
|
|
138
|
-
}
|
|
139
|
-
return `API error: ${err.message || err}`
|
|
140
|
-
}
|
package/src/utils/tokens.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token Estimation & Counting
|
|
3
|
-
*
|
|
4
|
-
* Provides rough token estimation (character-based) and
|
|
5
|
-
* API-based exact counting when available.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Rough token estimation: ~4 chars per token (conservative).
|
|
10
|
-
*/
|
|
11
|
-
export function estimateTokens(text: string): number {
|
|
12
|
-
return Math.ceil(text.length / 4)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Estimate tokens for a message array.
|
|
17
|
-
*/
|
|
18
|
-
export function estimateMessagesTokens(
|
|
19
|
-
messages: Array<{ role: string; content: any }>,
|
|
20
|
-
): number {
|
|
21
|
-
let total = 0
|
|
22
|
-
for (const msg of messages) {
|
|
23
|
-
if (typeof msg.content === 'string') {
|
|
24
|
-
total += estimateTokens(msg.content)
|
|
25
|
-
} else if (Array.isArray(msg.content)) {
|
|
26
|
-
for (const block of msg.content) {
|
|
27
|
-
if ('text' in block && typeof block.text === 'string') {
|
|
28
|
-
total += estimateTokens(block.text)
|
|
29
|
-
} else if ('content' in block && typeof block.content === 'string') {
|
|
30
|
-
total += estimateTokens(block.content)
|
|
31
|
-
} else {
|
|
32
|
-
// tool_use, image, etc - rough estimate
|
|
33
|
-
total += estimateTokens(JSON.stringify(block))
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return total
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Estimate tokens for a system prompt.
|
|
43
|
-
*/
|
|
44
|
-
export function estimateSystemPromptTokens(systemPrompt: string): number {
|
|
45
|
-
return estimateTokens(systemPrompt)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Count tokens from API usage response.
|
|
50
|
-
*/
|
|
51
|
-
export function getTokenCountFromUsage(usage: {
|
|
52
|
-
input_tokens: number
|
|
53
|
-
output_tokens: number
|
|
54
|
-
cache_creation_input_tokens?: number
|
|
55
|
-
cache_read_input_tokens?: number
|
|
56
|
-
}): number {
|
|
57
|
-
return (
|
|
58
|
-
usage.input_tokens +
|
|
59
|
-
usage.output_tokens +
|
|
60
|
-
(usage.cache_creation_input_tokens || 0) +
|
|
61
|
-
(usage.cache_read_input_tokens || 0)
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get the context window size for a model.
|
|
67
|
-
*/
|
|
68
|
-
export function getContextWindowSize(model: string): number {
|
|
69
|
-
// Anthropic model context windows
|
|
70
|
-
if (model.includes('opus-4') && model.includes('1m')) return 1_000_000
|
|
71
|
-
if (model.includes('opus-4')) return 200_000
|
|
72
|
-
if (model.includes('sonnet-4')) return 200_000
|
|
73
|
-
if (model.includes('haiku-4')) return 200_000
|
|
74
|
-
if (model.includes('claude-3')) return 200_000
|
|
75
|
-
|
|
76
|
-
// OpenAI model context windows
|
|
77
|
-
if (model.includes('gpt-4o')) return 128_000
|
|
78
|
-
if (model.includes('gpt-4-turbo')) return 128_000
|
|
79
|
-
if (model.includes('gpt-4-1')) return 1_000_000
|
|
80
|
-
if (model.includes('gpt-4')) return 128_000
|
|
81
|
-
if (model.includes('gpt-3.5')) return 16_385
|
|
82
|
-
if (model.includes('o1')) return 200_000
|
|
83
|
-
if (model.includes('o3')) return 200_000
|
|
84
|
-
if (model.includes('o4')) return 200_000
|
|
85
|
-
|
|
86
|
-
// DeepSeek models
|
|
87
|
-
if (model.includes('deepseek')) return 128_000
|
|
88
|
-
|
|
89
|
-
// Default
|
|
90
|
-
return 200_000
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Auto-compact buffer: trigger compaction when within this many tokens of the limit.
|
|
95
|
-
*/
|
|
96
|
-
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Get the auto-compact threshold for a model.
|
|
100
|
-
*/
|
|
101
|
-
export function getAutoCompactThreshold(model: string): number {
|
|
102
|
-
return getContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Model pricing (USD per token).
|
|
107
|
-
*/
|
|
108
|
-
export const MODEL_PRICING: Record<string, { input: number; output: number }> = {
|
|
109
|
-
// Anthropic models
|
|
110
|
-
'claude-opus-4-6': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
|
|
111
|
-
'claude-opus-4-5': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
|
|
112
|
-
'claude-sonnet-4-6': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
|
|
113
|
-
'claude-sonnet-4-5': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
|
|
114
|
-
'claude-haiku-4-5': { input: 0.8 / 1_000_000, output: 4 / 1_000_000 },
|
|
115
|
-
'claude-3-5-sonnet': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
|
|
116
|
-
'claude-3-5-haiku': { input: 0.8 / 1_000_000, output: 4 / 1_000_000 },
|
|
117
|
-
'claude-3-opus': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
|
|
118
|
-
|
|
119
|
-
// OpenAI models
|
|
120
|
-
'gpt-4o': { input: 2.5 / 1_000_000, output: 10 / 1_000_000 },
|
|
121
|
-
'gpt-4o-mini': { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
|
|
122
|
-
'gpt-4-turbo': { input: 10 / 1_000_000, output: 30 / 1_000_000 },
|
|
123
|
-
'gpt-4-1': { input: 2 / 1_000_000, output: 8 / 1_000_000 },
|
|
124
|
-
'o1': { input: 15 / 1_000_000, output: 60 / 1_000_000 },
|
|
125
|
-
'o3': { input: 10 / 1_000_000, output: 40 / 1_000_000 },
|
|
126
|
-
'o4-mini': { input: 1.1 / 1_000_000, output: 4.4 / 1_000_000 },
|
|
127
|
-
|
|
128
|
-
// DeepSeek models
|
|
129
|
-
'deepseek-chat': { input: 0.27 / 1_000_000, output: 1.1 / 1_000_000 },
|
|
130
|
-
'deepseek-reasoner': { input: 0.55 / 1_000_000, output: 2.19 / 1_000_000 },
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Estimate cost from usage and model.
|
|
135
|
-
*/
|
|
136
|
-
export function estimateCost(
|
|
137
|
-
model: string,
|
|
138
|
-
usage: { input_tokens: number; output_tokens: number },
|
|
139
|
-
): number {
|
|
140
|
-
const pricing = Object.entries(MODEL_PRICING).find(([key]) =>
|
|
141
|
-
model.includes(key),
|
|
142
|
-
)?.[1] ?? { input: 3 / 1_000_000, output: 15 / 1_000_000 }
|
|
143
|
-
|
|
144
|
-
return usage.input_tokens * pricing.input + usage.output_tokens * pricing.output
|
|
145
|
-
}
|