chub-dev 0.2.0-beta.2 → 0.2.0-beta.4

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.
@@ -1,8 +1,10 @@
1
- import { loadSourceRegistry } from './cache.js';
1
+ import { loadSourceRegistry, loadSearchIndex } from './cache.js';
2
2
  import { loadConfig } from './config.js';
3
3
  import { normalizeLanguage } from './normalize.js';
4
+ import { search as bm25Search } from './bm25.js';
4
5
 
5
6
  let _merged = null;
7
+ let _searchIndex = null;
6
8
 
7
9
  /**
8
10
  * Load and merge entries from all configured sources.
@@ -14,11 +16,16 @@ function getMerged() {
14
16
  const config = loadConfig();
15
17
  const allDocs = [];
16
18
  const allSkills = [];
19
+ const searchIndexes = [];
17
20
 
18
21
  for (const source of config.sources) {
19
22
  const registry = loadSourceRegistry(source);
20
23
  if (!registry) continue;
21
24
 
25
+ // Load BM25 search index if available
26
+ const idx = loadSearchIndex(source);
27
+ if (idx) searchIndexes.push(idx);
28
+
22
29
  // Support both new format (docs/skills) and old format (entries)
23
30
  if (registry.docs) {
24
31
  for (const doc of registry.docs) {
@@ -46,6 +53,53 @@ function getMerged() {
46
53
  }
47
54
  }
48
55
 
56
+ // Merge search indexes (combine documents and recompute IDF)
57
+ if (searchIndexes.length > 0) {
58
+ if (searchIndexes.length === 1) {
59
+ _searchIndex = searchIndexes[0];
60
+ } else {
61
+ // Merge multiple indexes: combine documents, recompute global IDF
62
+ const allDocuments = searchIndexes.flatMap((idx) => idx.documents);
63
+ const N = allDocuments.length;
64
+ const dfMap = {};
65
+ const fieldLengths = { name: [], description: [], tags: [] };
66
+
67
+ for (const doc of allDocuments) {
68
+ const allTerms = new Set([
69
+ ...(doc.tokens.name || []),
70
+ ...(doc.tokens.description || []),
71
+ ...(doc.tokens.tags || []),
72
+ ]);
73
+ for (const term of allTerms) {
74
+ dfMap[term] = (dfMap[term] || 0) + 1;
75
+ }
76
+ fieldLengths.name.push((doc.tokens.name || []).length);
77
+ fieldLengths.description.push((doc.tokens.description || []).length);
78
+ fieldLengths.tags.push((doc.tokens.tags || []).length);
79
+ }
80
+
81
+ const idf = {};
82
+ for (const [term, df] of Object.entries(dfMap)) {
83
+ idf[term] = Math.log((N - df + 0.5) / (df + 0.5) + 1);
84
+ }
85
+
86
+ const avg = (arr) => arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length;
87
+ _searchIndex = {
88
+ version: '1.0.0',
89
+ algorithm: 'bm25',
90
+ params: searchIndexes[0].params,
91
+ totalDocs: N,
92
+ avgFieldLengths: {
93
+ name: avg(fieldLengths.name),
94
+ description: avg(fieldLengths.description),
95
+ tags: avg(fieldLengths.tags),
96
+ },
97
+ idf,
98
+ documents: allDocuments,
99
+ };
100
+ }
101
+ }
102
+
49
103
  _merged = { docs: allDocs, skills: allSkills };
50
104
  return _merged;
51
105
  }
@@ -121,11 +175,10 @@ export function getDisplayId(entry) {
121
175
 
122
176
  /**
123
177
  * Search entries by query string. Searches both docs and skills.
178
+ * Uses BM25 when a search index is available, falls back to keyword matching.
124
179
  */
125
180
  export function searchEntries(query, filters = {}) {
126
181
  const entries = applySourceFilter(getAllEntries());
127
- const q = query.toLowerCase();
128
- const words = q.split(/\s+/);
129
182
 
130
183
  // Deduplicate: same id+source appearing as both doc and skill → show once
131
184
  const seen = new Set();
@@ -138,27 +191,50 @@ export function searchEntries(query, filters = {}) {
138
191
  }
139
192
  }
140
193
 
141
- let results = deduped.map((entry) => {
142
- let score = 0;
194
+ // Build entry lookup by id
195
+ const entryById = new Map();
196
+ for (const entry of deduped) {
197
+ entryById.set(entry.id, entry);
198
+ }
199
+
200
+ let results;
201
+
202
+ if (_searchIndex) {
203
+ // BM25 search
204
+ const bm25Results = bm25Search(query, _searchIndex);
205
+ results = bm25Results
206
+ .map((r) => {
207
+ const entry = entryById.get(r.id);
208
+ return entry ? { entry, score: r.score } : null;
209
+ })
210
+ .filter(Boolean);
211
+ } else {
212
+ // Fallback: keyword matching
213
+ const q = query.toLowerCase();
214
+ const words = q.split(/\s+/);
143
215
 
144
- if (entry.id === q) score += 100;
145
- else if (entry.id.includes(q)) score += 50;
216
+ results = deduped.map((entry) => {
217
+ let score = 0;
146
218
 
147
- const nameLower = entry.name.toLowerCase();
148
- if (nameLower === q) score += 80;
149
- else if (nameLower.includes(q)) score += 40;
219
+ if (entry.id === q) score += 100;
220
+ else if (entry.id.includes(q)) score += 50;
150
221
 
151
- for (const word of words) {
152
- if (entry.id.includes(word)) score += 10;
153
- if (nameLower.includes(word)) score += 10;
154
- if (entry.description?.toLowerCase().includes(word)) score += 5;
155
- if (entry.tags?.some((t) => t.toLowerCase().includes(word))) score += 15;
156
- }
222
+ const nameLower = entry.name.toLowerCase();
223
+ if (nameLower === q) score += 80;
224
+ else if (nameLower.includes(q)) score += 40;
157
225
 
158
- return { entry, score };
159
- });
226
+ for (const word of words) {
227
+ if (entry.id.includes(word)) score += 10;
228
+ if (nameLower.includes(word)) score += 10;
229
+ if (entry.description?.toLowerCase().includes(word)) score += 5;
230
+ if (entry.tags?.some((t) => t.toLowerCase().includes(word))) score += 15;
231
+ }
232
+
233
+ return { entry, score };
234
+ });
160
235
 
161
- results = results.filter((r) => r.score > 0);
236
+ results = results.filter((r) => r.score > 0);
237
+ }
162
238
 
163
239
  const filtered = applyFilters(results.map((r) => r.entry), filters);
164
240
  const filteredSet = new Set(filtered);
@@ -255,6 +331,13 @@ export function resolveDocPath(entry, language, version) {
255
331
  let verObj = null;
256
332
  if (version) {
257
333
  verObj = langObj.versions?.find((v) => v.version === version);
334
+ if (!verObj) {
335
+ return {
336
+ versionNotFound: true,
337
+ requested: version,
338
+ available: langObj.versions?.map((v) => v.version) || [],
339
+ };
340
+ }
258
341
  } else {
259
342
  const rec = langObj.recommendedVersion;
260
343
  verObj = langObj.versions?.find((v) => v.version === rec) || langObj.versions?.[0];
@@ -272,7 +355,7 @@ export function resolveDocPath(entry, language, version) {
272
355
  * Given a resolved path and a type ("doc" or "skill"), return the entry file path.
273
356
  */
274
357
  export function resolveEntryFile(resolved, type) {
275
- if (!resolved || resolved.needsLanguage) return { error: 'unresolved' };
358
+ if (!resolved || resolved.needsLanguage || resolved.versionNotFound) return { error: 'unresolved' };
276
359
 
277
360
  const fileName = type === 'skill' ? 'SKILL.md' : 'DOC.md';
278
361
 
@@ -1,499 +0,0 @@
1
- ---
2
- name: sdk
3
- description: "Claude AI assistant API for text generation, analysis, conversation, streaming, tool use, vision, and batch processing"
4
- metadata:
5
- languages: "javascript"
6
- versions: "0.67.0"
7
- updated-on: "2025-10-24"
8
- source: maintainer
9
- tags: "anthropic,sdk,llm,ai,claude"
10
- ---
11
-
12
- # Anthropic JavaScript/TypeScript SDK Coding Guidelines
13
-
14
- You are an Anthropic API coding expert. Help me with writing code using the Anthropic API calling the official libraries and SDKs.
15
-
16
- You can find the official SDK documentation and code samples here:
17
- https://docs.anthropic.com/claude/reference/
18
-
19
- ## Golden Rule: Use the Correct and Current SDK
20
-
21
- Always use the Anthropic TypeScript SDK to call the Claude models, which is the standard library for all Anthropic API interactions. Do not use legacy libraries or unofficial SDKs.
22
-
23
- - **Library Name:** Anthropic TypeScript SDK
24
- - **NPM Package:** `@anthropic-ai/sdk`
25
- - **Legacy Libraries**: Other unofficial packages are not recommended
26
-
27
- **Installation:**
28
-
29
- - **Correct:** `npm install @anthropic-ai/sdk`
30
-
31
- **APIs and Usage:**
32
-
33
- - **Correct:** `import Anthropic from '@anthropic-ai/sdk'`
34
- - **Correct:** `const client = new Anthropic({})`
35
- - **Correct:** `await client.messages.create(...)`
36
- - **Correct:** `await client.messages.stream(...)`
37
- - **Incorrect:** `AnthropicClient` or `AnthropicAPI`
38
- - **Incorrect:** `client.generate` or `client.completions`
39
- - **Incorrect:** Legacy completion endpoints
40
-
41
- ## Initialization and API key
42
-
43
- The `@anthropic-ai/sdk` library requires creating an `Anthropic` instance for all API calls.
44
-
45
- - Always use `const client = new Anthropic({})` to create an instance.
46
- - Set the `ANTHROPIC_API_KEY` environment variable, which will be picked up automatically.
47
-
48
- ```javascript
49
- import Anthropic from '@anthropic-ai/sdk';
50
-
51
- // Uses the ANTHROPIC_API_KEY environment variable if apiKey not specified
52
- const client = new Anthropic({});
53
-
54
- // Or pass the API key directly
55
- // const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
56
- ```
57
-
58
- ## Models
59
-
60
- - By default, use the following models when using `@anthropic-ai/sdk`:
61
- - **General Tasks:** `claude-sonnet-4-20250514`
62
- - **Legacy Model (if needed):** `claude-3-7-sonnet-latest`
63
-
64
- - Advanced models available:
65
- - **High-performance:** `claude-opus-4-20250514`
66
-
67
- ## Basic Inference (Text Generation)
68
-
69
- Here's how to generate a response from a text prompt.
70
-
71
- ```javascript
72
- import Anthropic from '@anthropic-ai/sdk';
73
-
74
- const client = new Anthropic({}); // Assumes ANTHROPIC_API_KEY is set
75
-
76
- async function run() {
77
- const message = await client.messages.create({
78
- max_tokens: 1024,
79
- messages: [{ role: 'user', content: 'Hello, Claude' }],
80
- model: 'claude-sonnet-4-20250514',
81
- });
82
-
83
- console.log(message.content);
84
- }
85
-
86
- run();
87
- ```
88
-
89
- Multimodal inputs are supported by passing image data in the messages array. You can include images, documents, and other file types using base64 encoding or file uploads.
90
-
91
- For file uploads, use the `client.beta.files.upload` method:
92
-
93
- ```javascript
94
- import fs from 'fs';
95
- import Anthropic, { toFile } from '@anthropic-ai/sdk';
96
-
97
- const client = new Anthropic();
98
-
99
- // File upload example
100
- await client.beta.files.upload({
101
- file: await toFile(fs.createReadStream('/path/to/file'), undefined, { type: 'application/json' }),
102
- betas: ['files-api-2025-04-14'],
103
- });
104
- ```
105
-
106
- ## Streaming Responses
107
-
108
- We provide comprehensive support for streaming responses using Server Sent Events (SSE).
109
-
110
- ### Basic Streaming
111
-
112
- ```javascript
113
- import Anthropic from '@anthropic-ai/sdk';
114
-
115
- const client = new Anthropic();
116
-
117
- const stream = await client.messages.create({
118
- max_tokens: 1024,
119
- messages: [{ role: 'user', content: 'Hello, Claude' }],
120
- model: 'claude-sonnet-4-20250514',
121
- stream: true,
122
- });
123
-
124
- for await (const messageStreamEvent of stream) {
125
- console.log(messageStreamEvent.type);
126
- }
127
- ```
128
-
129
- ### Advanced Streaming with Helpers
130
-
131
- The SDK provides powerful streaming helpers for convenience:
132
-
133
- ```javascript
134
- import Anthropic from '@anthropic-ai/sdk';
135
-
136
- const client = new Anthropic();
137
-
138
- async function main() {
139
- const stream = client.messages
140
- .stream({
141
- model: 'claude-sonnet-4-20250514',
142
- max_tokens: 1024,
143
- messages: [
144
- {
145
- role: 'user',
146
- content: 'Say hello there!',
147
- },
148
- ],
149
- })
150
- .on('text', (text) => {
151
- console.log(text);
152
- });
153
-
154
- const message = await stream.finalMessage();
155
- console.log(message);
156
- }
157
-
158
- main();
159
- ```
160
-
161
- You can cancel streams by calling `stream.controller.abort()` or breaking from loops.
162
-
163
- ## Tool Use (Function Calling)
164
-
165
- The SDK supports tool use (function calling) for extending Claude's capabilities.
166
-
167
- ### Custom Tools
168
-
169
- Define custom functions that Claude can call:
170
-
171
- ```javascript
172
- import Anthropic from '@anthropic-ai/sdk';
173
-
174
- const client = new Anthropic();
175
-
176
- async function run() {
177
- const response = await client.messages.create({
178
- model: 'claude-sonnet-4-20250514',
179
- max_tokens: 1024,
180
- messages: [{ role: 'user', content: "What's the weather like in San Francisco?" }],
181
- tools: [
182
- {
183
- name: 'get_weather',
184
- description: 'Get the current weather in a given location',
185
- input_schema: {
186
- type: 'object',
187
- properties: {
188
- location: { description: 'The city and state, e.g. San Francisco, CA', type: 'string' },
189
- unit: { description: 'Unit for the output - one of (celsius, fahrenheit)', type: 'string' },
190
- },
191
- required: ['location'],
192
- },
193
- type: 'custom',
194
- },
195
- ],
196
- tool_choice: { type: 'auto' },
197
- });
198
-
199
- // Handle tool use in the response
200
- if (response.content.some((block) => block.type === 'tool_use')) {
201
- // Process tool calls and provide results back to Claude
202
- console.log('Claude wants to use a tool!');
203
- }
204
- }
205
-
206
- run();
207
- ```
208
-
209
- ### Built-in Beta Tools
210
-
211
- The beta API provides specialized built-in tools.
212
-
213
- #### Bash Tool
214
-
215
- ```javascript
216
- const response = await client.beta.messages.create({
217
- model: 'claude-sonnet-4-20250514',
218
- max_tokens: 1024,
219
- messages: [{ role: 'user', content: 'List the files in the current directory' }],
220
- tools: [{ type: 'bash_20250124', name: 'bash' }],
221
- });
222
- ```
223
-
224
- #### Computer Use Tool
225
-
226
- ```javascript
227
- const response = await client.beta.messages.create({
228
- model: 'claude-sonnet-4-20250514',
229
- max_tokens: 1024,
230
- messages: [{ role: 'user', content: 'Take a screenshot' }],
231
- tools: [
232
- {
233
- type: 'computer_20250124',
234
- name: 'computer',
235
- display_width_px: 1920,
236
- display_height_px: 1080,
237
- },
238
- ],
239
- });
240
- ```
241
-
242
- #### Text Editor Tool
243
-
244
- ```javascript
245
- const response = await client.beta.messages.create({
246
- model: 'claude-sonnet-4-20250514',
247
- max_tokens: 1024,
248
- messages: [{ role: 'user', content: 'Create a Python script' }],
249
- tools: [{ type: 'text_editor_20250124', name: 'str_replace_editor' }],
250
- });
251
- ```
252
-
253
- ### Tool Choice Configuration
254
-
255
- Control how Claude uses tools:
256
-
257
- - `{ type: 'auto' }` - Claude decides when to use tools
258
- - `{ type: 'any' }` - Claude must use a tool
259
- - `{ type: 'tool', name: 'specific_tool' }` - Force a specific tool
260
- - `{ disable_parallel_tool_use: true }` - Disable parallel tool execution
261
-
262
- ## Message Batches
263
-
264
- The SDK supports the Message Batches API for processing multiple requests efficiently.
265
-
266
- ### Creating a Batch
267
-
268
- ```javascript
269
- await client.messages.batches.create({
270
- requests: [
271
- {
272
- custom_id: 'my-first-request',
273
- params: {
274
- model: 'claude-sonnet-4-20250514',
275
- max_tokens: 1024,
276
- messages: [{ role: 'user', content: 'Hello, world' }],
277
- },
278
- },
279
- {
280
- custom_id: 'my-second-request',
281
- params: {
282
- model: 'claude-sonnet-4-20250514',
283
- max_tokens: 1024,
284
- messages: [{ role: 'user', content: 'Hi again, friend' }],
285
- },
286
- },
287
- ],
288
- });
289
- ```
290
-
291
- ### Getting Batch Results
292
-
293
- ```javascript
294
- const results = await client.messages.batches.results(batch_id);
295
- for await (const entry of results) {
296
- if (entry.result.type === 'succeeded') {
297
- console.log(entry.result.message.content);
298
- }
299
- }
300
- ```
301
-
302
- ## Additional Capabilities
303
-
304
- ### System Instructions
305
-
306
- Guide Claude's behavior with system instructions:
307
-
308
- ```javascript
309
- const response = await client.messages.create({
310
- model: 'claude-sonnet-4-20250514',
311
- max_tokens: 1024,
312
- messages: [{ role: 'user', content: 'Hello' }],
313
- system: [{ text: 'You are a helpful assistant that responds in a pirate voice.', type: 'text' }],
314
- });
315
- ```
316
-
317
- ### Thinking (Beta Feature)
318
-
319
- Configure Claude's reasoning process:
320
-
321
- ```javascript
322
- const response = await client.messages.create({
323
- model: 'claude-sonnet-4-20250514',
324
- max_tokens: 1024,
325
- messages: [{ role: 'user', content: 'Solve this complex problem...' }],
326
- thinking: { budget_tokens: 1024, type: 'enabled' },
327
- });
328
- ```
329
-
330
- ### Temperature and Generation Parameters
331
-
332
- Control randomness and output:
333
-
334
- ```javascript
335
- const response = await client.messages.create({
336
- model: 'claude-sonnet-4-20250514',
337
- max_tokens: 1024,
338
- messages: [{ role: 'user', content: 'Write a creative story' }],
339
- temperature: 0.7,
340
- top_k: 5,
341
- top_p: 0.9,
342
- });
343
- ```
344
-
345
- ### Token Counting
346
-
347
- Count tokens before making requests:
348
-
349
- ```javascript
350
- const tokenCount = await client.messages.countTokens({
351
- messages: [{ role: 'user', content: 'Hello, Claude' }],
352
- model: 'claude-sonnet-4-20250514',
353
- });
354
- console.log(tokenCount); // { input_tokens: 25, output_tokens: 13 }
355
- ```
356
-
357
- ### Auto-pagination
358
-
359
- Handle paginated responses automatically:
360
-
361
- ```javascript
362
- async function fetchAllMessageBatches(params) {
363
- const allMessageBatches = [];
364
- // Automatically fetches more pages as needed.
365
- for await (const messageBatch of client.messages.batches.list({ limit: 20 })) {
366
- allMessageBatches.push(messageBatch);
367
- }
368
- return allMessageBatches;
369
- }
370
- ```
371
-
372
- ## Error Handling
373
-
374
- The SDK provides comprehensive error handling with specific error types:
375
-
376
- ```javascript
377
- import Anthropic from '@anthropic-ai/sdk';
378
-
379
- const client = new Anthropic();
380
-
381
- try {
382
- const message = await client.messages.create({
383
- max_tokens: 1024,
384
- messages: [{ role: 'user', content: 'Hello, Claude' }],
385
- model: 'claude-sonnet-4-20250514',
386
- });
387
- } catch (err) {
388
- if (err instanceof Anthropic.APIError) {
389
- console.log(err.status); // 400
390
- console.log(err.name); // BadRequestError
391
- console.log(err.headers); // {server: 'nginx', ...}
392
- console.log(err.requestID); // request id string
393
- } else {
394
- throw err;
395
- }
396
- }
397
- ```
398
-
399
- ### Error Types
400
-
401
- | Status Code | Error Type |
402
- | ----------- | -------------------------- |
403
- | 400 | `BadRequestError` |
404
- | 401 | `AuthenticationError` |
405
- | 403 | `PermissionDeniedError` |
406
- | 404 | `NotFoundError` |
407
- | 422 | `UnprocessableEntityError` |
408
- | 429 | `RateLimitError` |
409
- | >=500 | `InternalServerError` |
410
- | N/A | `APIConnectionError` |
411
-
412
- All errors extend from `AnthropicError` which extends the standard `Error` class.
413
-
414
- ### Request IDs
415
-
416
- All responses include a `_request_id` property for debugging:
417
-
418
- ```javascript
419
- const message = await client.messages.create({
420
- max_tokens: 1024,
421
- messages: [{ role: 'user', content: 'Hello, Claude' }],
422
- model: 'claude-sonnet-4-20250514',
423
- });
424
- console.log(message._request_id);
425
- ```
426
-
427
- ## Advanced Configuration
428
-
429
- ### Retries
430
-
431
- Configure automatic retry behavior:
432
-
433
- ```javascript
434
- // Configure default retries for all requests
435
- const client = new Anthropic({
436
- maxRetries: 3, // default is 2
437
- });
438
-
439
- // Or configure per-request
440
- await client.messages.create(
441
- { max_tokens: 1024, messages: [{ role: 'user', content: 'Hello, Claude' }], model: 'claude-sonnet-4-20250514' },
442
- { maxRetries: 5 },
443
- );
444
- ```
445
-
446
- ### Timeouts
447
-
448
- Set custom timeout values:
449
-
450
- ```javascript
451
- // Configure default timeout for all requests
452
- const client = new Anthropic({
453
- timeout: 20 * 1000, // 20 seconds (default is 10 minutes)
454
- });
455
-
456
- // Override per-request
457
- await client.messages.create(
458
- { max_tokens: 1024, messages: [{ role: 'user', content: 'Hello, Claude' }], model: 'claude-sonnet-4-20250514' },
459
- { timeout: 5 * 1000 },
460
- );
461
- ```
462
-
463
- ### Logging
464
-
465
- Enable debug logging:
466
-
467
- ```javascript
468
- import Anthropic from '@anthropic-ai/sdk';
469
-
470
- const client = new Anthropic({
471
- logLevel: 'debug', // Show all log messages
472
- });
473
-
474
- // Or use environment variable
475
- // ANTHROPIC_LOG=debug
476
- ```
477
-
478
- ### Custom Fetch Options
479
-
480
- Customize the underlying fetch behavior:
481
-
482
- ```javascript
483
- import Anthropic from '@anthropic-ai/sdk';
484
-
485
- const client = new Anthropic({
486
- fetchOptions: {
487
- // Custom RequestInit options
488
- },
489
- });
490
- ```
491
-
492
- ## Useful Links
493
-
494
- - Documentation: https://docs.anthropic.com/
495
- - API Reference: https://docs.anthropic.com/claude/reference/
496
- - Models: https://docs.anthropic.com/claude/docs/models-overview
497
- - API Pricing: https://www.anthropic.com/pricing
498
- - Rate Limits: https://docs.anthropic.com/claude/reference/rate-limits
499
-