-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_utils.ts
More file actions
273 lines (241 loc) · 7.69 KB
/
Copy path_utils.ts
File metadata and controls
273 lines (241 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/**
* Shared utilities for examples
*
* Provides common setup, validation, and helper functions.
*/
import { existsSync, readFileSync } from "node:fs"
import { join } from "node:path"
import { AnthropicProvider, OpenAIProvider, setDefaultProvider } from "../src"
import type { LLMProvider } from "../src"
/**
* Load .env file if it exists (for examples that don't use --env-file flag)
* Local .env file takes precedence over shell environment variables
*/
function loadEnvFile(): void {
const envPath = join(process.cwd(), ".env")
if (existsSync(envPath)) {
const content = readFileSync(envPath, "utf-8")
for (const line of content.split("\n")) {
const trimmed = line.trim()
// Skip comments and empty lines
if (!trimmed || trimmed.startsWith("#")) continue
const eqIndex = trimmed.indexOf("=")
if (eqIndex > 0) {
const key = trimmed.slice(0, eqIndex).trim()
let value = trimmed.slice(eqIndex + 1).trim()
// Remove surrounding quotes if present
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1)
}
// Always set - local .env takes precedence over shell env
process.env[key] = value
}
}
}
}
// Auto-load .env file on module import
loadEnvFile()
// Debug: show if env was loaded (only when DEBUG_ENV=1 or DEBUG_ENV=true)
const isDebug = process.env.DEBUG_ENV === "1" || process.env.DEBUG_ENV === "true"
if (isDebug) {
const key = process.env.ANTHROPIC_API_KEY
console.log(`[Debug] ANTHROPIC_API_KEY loaded: ${key ? `${key.slice(0, 15)}...` : "NOT SET"}`)
}
/**
* Environment variable names for API keys
*/
export const ENV_KEYS = {
ANTHROPIC: "ANTHROPIC_API_KEY",
OPENAI: "OPENAI_API_KEY",
} as const
/**
* Check if an API key is configured
*/
export function hasApiKey(provider: "anthropic" | "openai"): boolean {
const envKey = provider === "anthropic" ? ENV_KEYS.ANTHROPIC : ENV_KEYS.OPENAI
return !!process.env[envKey]
}
/**
* Get API key or throw with helpful message
*/
export function requireApiKey(provider: "anthropic" | "openai"): string {
const envKey = provider === "anthropic" ? ENV_KEYS.ANTHROPIC : ENV_KEYS.OPENAI
let apiKey = process.env[envKey]
if (!apiKey) {
console.error(`\n[Error] ${envKey} environment variable is not set.`)
console.error(`\nTo run this example, set your API key:`)
console.error(` export ${envKey}=your-api-key-here`)
console.error(`\nOr create a .env file with:`)
console.error(` ${envKey}=your-api-key-here\n`)
process.exit(1)
}
// Clean up common issues: trim whitespace and newlines
const cleanedKey = apiKey.trim()
// Check if key was modified (had whitespace)
if (cleanedKey !== apiKey) {
console.warn(`[Warning] API key had leading/trailing whitespace - cleaned automatically.`)
}
// Validate API key format (warning only)
if (provider === "anthropic") {
if (!cleanedKey.startsWith("sk-ant-")) {
console.warn(`[Warning] Anthropic API key may have unusual format.`)
console.warn(` Expected: sk-ant-...`)
console.warn(` Got: ${cleanedKey.substring(0, 10)}...`)
}
}
// Update the env var with cleaned value for downstream use
process.env[envKey] = cleanedKey
return cleanedKey
}
/**
* Setup the default Anthropic provider
* Validates API key before creating provider
*/
export function setupAnthropic(): AnthropicProvider {
const apiKey = requireApiKey("anthropic")
const provider = new AnthropicProvider({ apiKey })
setDefaultProvider(provider)
return provider
}
/**
* Setup the default OpenAI provider
* Validates API key before creating provider
*/
export function setupOpenAI(): OpenAIProvider {
const apiKey = requireApiKey("openai")
const provider = new OpenAIProvider({ apiKey })
setDefaultProvider(provider)
return provider
}
/**
* Setup any available provider (prefers Anthropic)
*/
export function setupAnyProvider(): LLMProvider {
if (hasApiKey("anthropic")) {
return setupAnthropic()
}
if (hasApiKey("openai")) {
return setupOpenAI()
}
console.error(`\n[Error] No API key found.`)
console.error(`\nTo run this example, set one of the following:`)
console.error(` export ${ENV_KEYS.ANTHROPIC}=your-api-key`)
console.error(` export ${ENV_KEYS.OPENAI}=your-api-key\n`)
process.exit(1)
}
/**
* Print a section header
*/
export function printHeader(title: string): void {
console.log(`\n${"=".repeat(50)}`)
console.log(` ${title}`)
console.log(`${"=".repeat(50)}\n`)
}
/**
* Print a subsection header
*/
export function printSubHeader(title: string): void {
console.log(`\n--- ${title} ---\n`)
}
/**
* Run an example with error handling
*/
export async function runExample(
name: string,
fn: () => Promise<void>
): Promise<void> {
printHeader(name)
try {
await fn()
} catch (error) {
console.error(`\n[Error in ${name}]`)
if (error instanceof Error) {
// Always show full error message for debugging
console.error(` Full error: ${error.message}`)
// Check for common API errors
if (error.message.includes("401") || error.message.includes("Invalid API key")) {
console.error(" API Key Error: Your API key may be invalid or expired.")
console.error(" Please check your API key and try again.")
} else if (error.message.includes("429") || error.message.includes("rate limit")) {
console.error(" Rate Limit Error: Too many requests.")
console.error(" Please wait a moment and try again.")
} else if (error.message.includes("500") || error.message.includes("503")) {
console.error(" Server Error: The API service is temporarily unavailable.")
console.error(" Please try again later.")
}
} else {
console.error(` ${String(error)}`)
}
throw error
}
}
/**
* Process streaming events and print output
*/
export async function processStream(
receiver: AsyncGenerator<any, void, unknown>,
options?: {
showToolCalls?: boolean
showUsage?: boolean
}
): Promise<void> {
const { showToolCalls = true, showUsage = true } = options ?? {}
for await (const event of receiver) {
switch (event.type) {
case "text":
process.stdout.write(event.text)
break
case "tool_use":
if (showToolCalls) {
console.log(`\n[Tool Call] ${event.name}`)
console.log(` Input: ${JSON.stringify(event.input, null, 2)}`)
}
break
case "tool_result":
if (showToolCalls) {
const preview = String(event.content).slice(0, 100)
console.log(` Result: ${preview}${event.content.length > 100 ? "..." : ""}`)
if (event.is_error) {
console.log(` [Error]`)
}
}
break
case "stop":
if (showUsage) {
console.log(`\n[Usage] ${event.usage.input_tokens} in, ${event.usage.output_tokens} out`)
}
break
case "error":
console.error(`\n[Error] ${event.error.message}`)
throw event.error
}
}
console.log()
}
/**
* Wait for user to press Enter (for interactive examples)
*/
export function waitForEnter(message = "Press Enter to continue..."): Promise<void> {
return new Promise((resolve) => {
process.stdout.write(message)
process.stdin.once("data", () => {
resolve()
})
})
}
/**
* Sleep for a specified duration
*/
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* Main wrapper with global error handling
*/
export function main(fn: () => Promise<void>): void {
fn().catch((error) => {
// Error already logged in runExample, just exit with error code
process.exit(1)
})
}