skrypt-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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/autofix/index.d.ts +46 -0
- package/dist/autofix/index.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/autofix.d.ts +2 -0
- package/dist/commands/autofix.js +143 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/review-pr.d.ts +2 -0
- package/dist/commands/review-pr.js +117 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +142 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +34 -0
- package/dist/generator/generator.d.ts +15 -0
- package/dist/generator/generator.js +144 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +4 -0
- package/dist/generator/organizer.d.ts +29 -0
- package/dist/generator/organizer.js +222 -0
- package/dist/generator/types.d.ts +83 -0
- package/dist/generator/types.js +1 -0
- package/dist/generator/writer.d.ts +28 -0
- package/dist/generator/writer.js +320 -0
- package/dist/github/pr-comments.d.ts +40 -0
- package/dist/github/pr-comments.js +308 -0
- package/dist/llm/anthropic-client.d.ts +16 -0
- package/dist/llm/anthropic-client.js +92 -0
- package/dist/llm/index.d.ts +53 -0
- package/dist/llm/index.js +400 -0
- package/dist/llm/llm.manual-test.d.ts +1 -0
- package/dist/llm/llm.manual-test.js +112 -0
- package/dist/llm/llm.mock-test.d.ts +4 -0
- package/dist/llm/llm.mock-test.js +79 -0
- package/dist/llm/openai-client.d.ts +17 -0
- package/dist/llm/openai-client.js +90 -0
- package/dist/llm/types.d.ts +60 -0
- package/dist/llm/types.js +20 -0
- package/dist/scanner/content-type.d.ts +39 -0
- package/dist/scanner/content-type.js +194 -0
- package/dist/scanner/content-type.test.d.ts +1 -0
- package/dist/scanner/content-type.test.js +231 -0
- package/dist/scanner/go.d.ts +20 -0
- package/dist/scanner/go.js +269 -0
- package/dist/scanner/index.d.ts +21 -0
- package/dist/scanner/index.js +137 -0
- package/dist/scanner/python.d.ts +6 -0
- package/dist/scanner/python.js +57 -0
- package/dist/scanner/python_parser.py +230 -0
- package/dist/scanner/rust.d.ts +23 -0
- package/dist/scanner/rust.js +304 -0
- package/dist/scanner/scanner.test.d.ts +1 -0
- package/dist/scanner/scanner.test.js +210 -0
- package/dist/scanner/types.d.ts +50 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typescript.d.ts +34 -0
- package/dist/scanner/typescript.js +327 -0
- package/dist/scanner/typescript.manual-test.d.ts +1 -0
- package/dist/scanner/typescript.manual-test.js +112 -0
- package/dist/template/docs.json +32 -0
- package/dist/template/mdx-components.tsx +62 -0
- package/dist/template/next-env.d.ts +6 -0
- package/dist/template/next.config.mjs +17 -0
- package/dist/template/package.json +39 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/search-index.json +1 -0
- package/dist/template/scripts/build-search-index.mjs +120 -0
- package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
- package/dist/template/src/app/api/openapi/route.ts +48 -0
- package/dist/template/src/app/api/rate-limit/route.ts +84 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
- package/dist/template/src/app/docs/layout.tsx +9 -0
- package/dist/template/src/app/docs/page.mdx +67 -0
- package/dist/template/src/app/error.tsx +63 -0
- package/dist/template/src/app/layout.tsx +71 -0
- package/dist/template/src/app/page.tsx +18 -0
- package/dist/template/src/app/reference/route.ts +36 -0
- package/dist/template/src/app/robots.ts +14 -0
- package/dist/template/src/app/sitemap.ts +64 -0
- package/dist/template/src/components/breadcrumbs.tsx +41 -0
- package/dist/template/src/components/copy-button.tsx +29 -0
- package/dist/template/src/components/docs-layout.tsx +35 -0
- package/dist/template/src/components/edit-link.tsx +39 -0
- package/dist/template/src/components/feedback.tsx +52 -0
- package/dist/template/src/components/header.tsx +66 -0
- package/dist/template/src/components/mdx/accordion.tsx +48 -0
- package/dist/template/src/components/mdx/api-badge.tsx +57 -0
- package/dist/template/src/components/mdx/callout.tsx +111 -0
- package/dist/template/src/components/mdx/card.tsx +62 -0
- package/dist/template/src/components/mdx/changelog.tsx +57 -0
- package/dist/template/src/components/mdx/code-block.tsx +42 -0
- package/dist/template/src/components/mdx/code-group.tsx +125 -0
- package/dist/template/src/components/mdx/code-playground.tsx +322 -0
- package/dist/template/src/components/mdx/go-playground.tsx +235 -0
- package/dist/template/src/components/mdx/heading.tsx +37 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
- package/dist/template/src/components/mdx/index.tsx +15 -0
- package/dist/template/src/components/mdx/param-table.tsx +71 -0
- package/dist/template/src/components/mdx/python-playground.tsx +293 -0
- package/dist/template/src/components/mdx/steps.tsx +43 -0
- package/dist/template/src/components/mdx/tabs.tsx +81 -0
- package/dist/template/src/components/rate-limit-display.tsx +183 -0
- package/dist/template/src/components/search-dialog.tsx +178 -0
- package/dist/template/src/components/sidebar.tsx +129 -0
- package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
- package/dist/template/src/components/table-of-contents.tsx +84 -0
- package/dist/template/src/components/theme-toggle.tsx +46 -0
- package/dist/template/src/components/version-selector.tsx +61 -0
- package/dist/template/src/contexts/syntax-theme.tsx +52 -0
- package/dist/template/src/lib/highlight.ts +83 -0
- package/dist/template/src/lib/search-types.ts +37 -0
- package/dist/template/src/lib/search.ts +125 -0
- package/dist/template/src/lib/utils.ts +6 -0
- package/dist/template/src/styles/globals.css +152 -0
- package/dist/template/tsconfig.json +25 -0
- package/dist/template/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic Claude client
|
|
4
|
+
*/
|
|
5
|
+
export class AnthropicClient {
|
|
6
|
+
provider = 'anthropic';
|
|
7
|
+
client;
|
|
8
|
+
model;
|
|
9
|
+
maxRetries;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.model = config.model;
|
|
12
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
13
|
+
this.client = new Anthropic({
|
|
14
|
+
apiKey: config.apiKey,
|
|
15
|
+
timeout: config.timeout ?? 60000,
|
|
16
|
+
maxRetries: this.maxRetries
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
isConfigured() {
|
|
20
|
+
return true; // Anthropic SDK handles validation
|
|
21
|
+
}
|
|
22
|
+
async complete(request) {
|
|
23
|
+
const model = request.model || this.model;
|
|
24
|
+
// Separate system message from conversation
|
|
25
|
+
const systemMessage = request.messages.find(m => m.role === 'system');
|
|
26
|
+
const conversationMessages = request.messages.filter(m => m.role !== 'system');
|
|
27
|
+
try {
|
|
28
|
+
const response = await this.client.messages.create({
|
|
29
|
+
model,
|
|
30
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
31
|
+
system: systemMessage?.content,
|
|
32
|
+
messages: conversationMessages.map(m => ({
|
|
33
|
+
role: m.role,
|
|
34
|
+
content: m.content
|
|
35
|
+
})),
|
|
36
|
+
temperature: request.temperature ?? 0
|
|
37
|
+
});
|
|
38
|
+
// Extract text content from response
|
|
39
|
+
const content = response.content
|
|
40
|
+
.filter(block => block.type === 'text')
|
|
41
|
+
.map(block => block.text)
|
|
42
|
+
.join('');
|
|
43
|
+
return {
|
|
44
|
+
content,
|
|
45
|
+
model: response.model,
|
|
46
|
+
usage: {
|
|
47
|
+
inputTokens: response.usage.input_tokens,
|
|
48
|
+
outputTokens: response.usage.output_tokens,
|
|
49
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens
|
|
50
|
+
},
|
|
51
|
+
finishReason: this.mapStopReason(response.stop_reason)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw this.handleError(error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
mapStopReason(reason) {
|
|
59
|
+
switch (reason) {
|
|
60
|
+
case 'end_turn':
|
|
61
|
+
case 'stop_sequence':
|
|
62
|
+
return 'stop';
|
|
63
|
+
case 'max_tokens':
|
|
64
|
+
return 'length';
|
|
65
|
+
default:
|
|
66
|
+
return 'error';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
handleError(error) {
|
|
70
|
+
if (error instanceof Anthropic.APIError) {
|
|
71
|
+
const status = error.status;
|
|
72
|
+
const message = error.message;
|
|
73
|
+
if (status === 401) {
|
|
74
|
+
return new Error('Authentication failed for Anthropic: Invalid API key');
|
|
75
|
+
}
|
|
76
|
+
if (status === 429) {
|
|
77
|
+
return new Error(`Rate limited by Anthropic: ${message}`);
|
|
78
|
+
}
|
|
79
|
+
if (status === 500 || status === 502 || status === 503) {
|
|
80
|
+
return new Error(`Anthropic service error: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
if (status === 400 && message.includes('credit')) {
|
|
83
|
+
return new Error('Anthropic API: Insufficient credits');
|
|
84
|
+
}
|
|
85
|
+
return new Error(`Anthropic API error (${status}): ${message}`);
|
|
86
|
+
}
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
return new Error(`Unknown error from Anthropic: ${String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { LLMProvider } from '../config/types.js';
|
|
2
|
+
import { LLMClient } from './types.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export type { LLMClient };
|
|
5
|
+
/**
|
|
6
|
+
* Create an LLM client for the specified provider
|
|
7
|
+
*/
|
|
8
|
+
export declare function createLLMClient(config: {
|
|
9
|
+
provider: LLMProvider;
|
|
10
|
+
model: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
}): LLMClient;
|
|
15
|
+
/**
|
|
16
|
+
* Extended element info for better doc generation
|
|
17
|
+
*/
|
|
18
|
+
export interface ElementContext {
|
|
19
|
+
kind: string;
|
|
20
|
+
name: string;
|
|
21
|
+
signature: string;
|
|
22
|
+
parameters: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
default?: string;
|
|
26
|
+
}>;
|
|
27
|
+
returnType?: string;
|
|
28
|
+
docstring?: string;
|
|
29
|
+
parentClass?: string;
|
|
30
|
+
imports?: string[];
|
|
31
|
+
packageName?: string;
|
|
32
|
+
sourceContext?: string;
|
|
33
|
+
filePath?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generated documentation result
|
|
37
|
+
*/
|
|
38
|
+
export interface GeneratedDocResult {
|
|
39
|
+
markdown: string;
|
|
40
|
+
codeExample: string;
|
|
41
|
+
pythonExample?: string;
|
|
42
|
+
typescriptExample?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Helper to generate documentation for a code element
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateDocumentation(client: LLMClient, element: ElementContext, options?: {
|
|
48
|
+
multiLanguage?: boolean;
|
|
49
|
+
}): Promise<GeneratedDocResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to fix a broken code sample with smart strategies
|
|
52
|
+
*/
|
|
53
|
+
export declare function fixCodeSample(client: LLMClient, code: string, error: string, context: string, iteration?: number): Promise<string>;
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { PROVIDER_ENV_KEYS } from '../config/types.js';
|
|
2
|
+
import { OpenAICompatibleClient } from './openai-client.js';
|
|
3
|
+
import { AnthropicClient } from './anthropic-client.js';
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create an LLM client for the specified provider
|
|
7
|
+
*/
|
|
8
|
+
export function createLLMClient(config) {
|
|
9
|
+
// Get API key from environment
|
|
10
|
+
const envKey = PROVIDER_ENV_KEYS[config.provider];
|
|
11
|
+
const apiKey = envKey ? process.env[envKey] || '' : '';
|
|
12
|
+
const clientConfig = {
|
|
13
|
+
provider: config.provider,
|
|
14
|
+
model: config.model,
|
|
15
|
+
apiKey,
|
|
16
|
+
baseUrl: config.baseUrl,
|
|
17
|
+
timeout: config.timeout,
|
|
18
|
+
maxRetries: config.maxRetries
|
|
19
|
+
};
|
|
20
|
+
// Use Anthropic client for Anthropic, OpenAI-compatible for everything else
|
|
21
|
+
if (config.provider === 'anthropic') {
|
|
22
|
+
return new AnthropicClient(clientConfig);
|
|
23
|
+
}
|
|
24
|
+
return new OpenAICompatibleClient(clientConfig);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Helper to generate documentation for a code element
|
|
28
|
+
*/
|
|
29
|
+
export async function generateDocumentation(client, element, options) {
|
|
30
|
+
const useMultiLang = options?.multiLanguage ?? true;
|
|
31
|
+
const prompt = buildDocPrompt(element, useMultiLang);
|
|
32
|
+
const response = await client.complete({
|
|
33
|
+
messages: [
|
|
34
|
+
{
|
|
35
|
+
role: 'system',
|
|
36
|
+
content: useMultiLang ? SYSTEM_PROMPT_MULTI_LANG : SYSTEM_PROMPT
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
role: 'user',
|
|
40
|
+
content: prompt
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
temperature: 0,
|
|
44
|
+
maxTokens: useMultiLang ? 4096 : 2048
|
|
45
|
+
});
|
|
46
|
+
return useMultiLang
|
|
47
|
+
? parseMultiLangResponse(response.content)
|
|
48
|
+
: parseDocResponse(response.content);
|
|
49
|
+
}
|
|
50
|
+
// Phase 2: Multi-language system prompt
|
|
51
|
+
const SYSTEM_PROMPT_MULTI_LANG = `You are an expert technical writer creating OUTCOMES-FOCUSED documentation with DUAL-LANGUAGE examples.
|
|
52
|
+
|
|
53
|
+
Your goal: Help developers achieve their task FAST. Lead with "Use this to..." not "This function...".
|
|
54
|
+
|
|
55
|
+
## Documentation Rules
|
|
56
|
+
1. Do NOT include a heading with the function/class name - start directly with the use case
|
|
57
|
+
2. Start with the PRIMARY USE CASE - what problem does this solve?
|
|
58
|
+
3. Use a markdown table for parameters (Name | Type | Required | Description)
|
|
59
|
+
4. Clearly document what is returned and when
|
|
60
|
+
|
|
61
|
+
## Code Example Rules (CRITICAL)
|
|
62
|
+
Generate TWO self-contained examples: TypeScript AND Python.
|
|
63
|
+
|
|
64
|
+
Both examples MUST be:
|
|
65
|
+
1. COMPLETELY SELF-CONTAINED - no external imports from the library
|
|
66
|
+
2. EXECUTABLE standalone - inline all types and mock all functions
|
|
67
|
+
3. Using realistic data (API keys, user IDs, etc.)
|
|
68
|
+
4. Wrapped in try/except (Python) or try/catch (TypeScript)
|
|
69
|
+
5. Showing environment variable usage
|
|
70
|
+
6. Ending with print/console.log showing expected output
|
|
71
|
+
|
|
72
|
+
## TypeScript Example Pattern
|
|
73
|
+
\`\`\`typescript
|
|
74
|
+
// Inline types
|
|
75
|
+
type Config = { apiKey: string }
|
|
76
|
+
|
|
77
|
+
// Mock implementation
|
|
78
|
+
function createTool(config: Config) {
|
|
79
|
+
return { execute: async (q: string) => ({ results: [q] }) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Usage
|
|
83
|
+
const tool = createTool({ apiKey: process.env.API_KEY || 'your-key' })
|
|
84
|
+
|
|
85
|
+
async function main() {
|
|
86
|
+
try {
|
|
87
|
+
const result = await tool.execute('query')
|
|
88
|
+
console.log('Result:', result)
|
|
89
|
+
// Output: { results: ['query'] }
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Failed:', error)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
main()
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
## Python Example Pattern
|
|
98
|
+
\`\`\`python
|
|
99
|
+
import os
|
|
100
|
+
from dataclasses import dataclass
|
|
101
|
+
from typing import Optional, List, Dict, Any
|
|
102
|
+
|
|
103
|
+
# Inline types
|
|
104
|
+
@dataclass
|
|
105
|
+
class Config:
|
|
106
|
+
api_key: str
|
|
107
|
+
timeout: int = 30
|
|
108
|
+
|
|
109
|
+
# Mock implementation
|
|
110
|
+
class Tool:
|
|
111
|
+
def __init__(self, config: Config):
|
|
112
|
+
self.config = config
|
|
113
|
+
|
|
114
|
+
async def execute(self, query: str) -> Dict[str, Any]:
|
|
115
|
+
return {"results": [query]}
|
|
116
|
+
|
|
117
|
+
# Usage
|
|
118
|
+
import asyncio
|
|
119
|
+
|
|
120
|
+
async def main():
|
|
121
|
+
try:
|
|
122
|
+
tool = Tool(Config(api_key=os.getenv("API_KEY", "your-key")))
|
|
123
|
+
result = await tool.execute("query")
|
|
124
|
+
print(f"Result: {result}")
|
|
125
|
+
# Output: {'results': ['query']}
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"Failed: {e}")
|
|
128
|
+
|
|
129
|
+
asyncio.run(main())
|
|
130
|
+
\`\`\`
|
|
131
|
+
|
|
132
|
+
## Output Format
|
|
133
|
+
---MARKDOWN---
|
|
134
|
+
[Your markdown documentation with parameter table]
|
|
135
|
+
---TYPESCRIPT---
|
|
136
|
+
[Self-contained TypeScript example]
|
|
137
|
+
---PYTHON---
|
|
138
|
+
[Self-contained Python example]
|
|
139
|
+
---END---`;
|
|
140
|
+
const SYSTEM_PROMPT = `You are an expert technical writer creating OUTCOMES-FOCUSED documentation.
|
|
141
|
+
|
|
142
|
+
Your goal: Help developers achieve their task FAST. Lead with "Use this to..." not "This function...".
|
|
143
|
+
|
|
144
|
+
## Documentation Rules
|
|
145
|
+
1. Do NOT include a heading with the function/class name - start directly with the use case
|
|
146
|
+
2. Start with the PRIMARY USE CASE - what problem does this solve?
|
|
147
|
+
3. Use a markdown table for parameters (Name | Type | Required | Description)
|
|
148
|
+
4. Clearly document what is returned and when
|
|
149
|
+
|
|
150
|
+
## Code Example Rules (CRITICAL)
|
|
151
|
+
Your code examples MUST be COMPLETELY SELF-CONTAINED and EXECUTABLE:
|
|
152
|
+
|
|
153
|
+
1. NEVER import from the library being documented (e.g., "@supermemory/tools")
|
|
154
|
+
2. Instead, INLINE any types or simple implementations needed
|
|
155
|
+
3. Use realistic but simple data (real-looking API keys, user IDs, etc.)
|
|
156
|
+
4. ALWAYS wrap async code in try/catch with meaningful error handling
|
|
157
|
+
5. ALWAYS show environment variable usage: process.env.API_KEY || 'your-api-key'
|
|
158
|
+
6. End with a console.log showing expected output
|
|
159
|
+
7. Include a brief comment showing what output to expect
|
|
160
|
+
|
|
161
|
+
## Self-Contained Example Pattern
|
|
162
|
+
BAD (imports from library - will fail):
|
|
163
|
+
\`\`\`typescript
|
|
164
|
+
import { createTool } from '@mylib/tools'
|
|
165
|
+
const tool = createTool('key')
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
GOOD (self-contained - will run):
|
|
169
|
+
\`\`\`typescript
|
|
170
|
+
// Define the types inline
|
|
171
|
+
type ToolConfig = { apiKey: string; timeout?: number }
|
|
172
|
+
|
|
173
|
+
// Simulate the function behavior
|
|
174
|
+
function createTool(config: ToolConfig) {
|
|
175
|
+
return {
|
|
176
|
+
execute: async (query: string) => {
|
|
177
|
+
console.log(\`Searching for: \${query}\`)
|
|
178
|
+
return { results: ['result1', 'result2'] }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Usage example
|
|
184
|
+
const tool = createTool({
|
|
185
|
+
apiKey: process.env.MY_API_KEY || 'your-api-key-here'
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
async function main() {
|
|
189
|
+
try {
|
|
190
|
+
const result = await tool.execute('my query')
|
|
191
|
+
console.log('Results:', result)
|
|
192
|
+
// Output: { results: ['result1', 'result2'] }
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Failed:', error)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main()
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
Output format:
|
|
202
|
+
---MARKDOWN---
|
|
203
|
+
[Your markdown documentation with parameter table]
|
|
204
|
+
---CODE---
|
|
205
|
+
[Your SELF-CONTAINED, EXECUTABLE code example]
|
|
206
|
+
---END---`;
|
|
207
|
+
function buildDocPrompt(element, multiLanguage = false) {
|
|
208
|
+
let prompt = `Generate documentation for this ${element.kind}:\n\n`;
|
|
209
|
+
prompt += `**Name:** ${element.name}\n`;
|
|
210
|
+
prompt += `**Signature:** ${element.signature}\n`;
|
|
211
|
+
if (element.parentClass) {
|
|
212
|
+
prompt += `**Class:** ${element.parentClass}\n`;
|
|
213
|
+
}
|
|
214
|
+
if (element.packageName && element.packageName !== 'unknown') {
|
|
215
|
+
prompt += `**Package:** ${element.packageName} (DO NOT import from this - make example self-contained)\n`;
|
|
216
|
+
}
|
|
217
|
+
if (element.parameters.length > 0) {
|
|
218
|
+
prompt += `\n**Parameters:**\n`;
|
|
219
|
+
for (const param of element.parameters) {
|
|
220
|
+
prompt += `- ${param.name}`;
|
|
221
|
+
if (param.type)
|
|
222
|
+
prompt += `: ${param.type}`;
|
|
223
|
+
if (param.default)
|
|
224
|
+
prompt += ` = ${param.default}`;
|
|
225
|
+
prompt += '\n';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (element.returnType) {
|
|
229
|
+
prompt += `\n**Returns:** ${element.returnType}\n`;
|
|
230
|
+
}
|
|
231
|
+
if (element.docstring) {
|
|
232
|
+
prompt += `\n**Existing docstring:**\n${element.docstring}\n`;
|
|
233
|
+
}
|
|
234
|
+
if (element.sourceContext) {
|
|
235
|
+
prompt += `\n**Source context (for understanding, not for importing):**\n\`\`\`\n${element.sourceContext}\n\`\`\`\n`;
|
|
236
|
+
}
|
|
237
|
+
if (element.imports && element.imports.length > 0) {
|
|
238
|
+
prompt += `\n**File imports (shows dependencies - your example should NOT use these):**\n`;
|
|
239
|
+
prompt += element.imports.slice(0, 5).join('\n') + '\n';
|
|
240
|
+
}
|
|
241
|
+
if (multiLanguage) {
|
|
242
|
+
prompt += `\n**IMPORTANT:** Generate BOTH TypeScript AND Python self-contained examples.`;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
prompt += `\n**IMPORTANT:** Generate a SELF-CONTAINED example that runs without any external imports.`;
|
|
246
|
+
}
|
|
247
|
+
return prompt;
|
|
248
|
+
}
|
|
249
|
+
function parseDocResponse(content) {
|
|
250
|
+
// Extract markdown section
|
|
251
|
+
const markdownMatch = content.match(/---MARKDOWN---\s*([\s\S]*?)\s*---CODE---/);
|
|
252
|
+
const markdown = markdownMatch?.[1]?.trim() || content;
|
|
253
|
+
// Extract code section
|
|
254
|
+
const codeMatch = content.match(/---CODE---\s*([\s\S]*?)\s*---END---/);
|
|
255
|
+
let codeExample = codeMatch?.[1]?.trim() || '';
|
|
256
|
+
// Clean up code fences if present
|
|
257
|
+
codeExample = codeExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
|
|
258
|
+
return { markdown, codeExample, typescriptExample: codeExample };
|
|
259
|
+
}
|
|
260
|
+
function parseMultiLangResponse(content) {
|
|
261
|
+
// Extract markdown section
|
|
262
|
+
const markdownMatch = content.match(/---MARKDOWN---\s*([\s\S]*?)\s*---TYPESCRIPT---/);
|
|
263
|
+
const markdown = markdownMatch?.[1]?.trim() || '';
|
|
264
|
+
// Extract TypeScript section
|
|
265
|
+
const tsMatch = content.match(/---TYPESCRIPT---\s*([\s\S]*?)\s*---PYTHON---/);
|
|
266
|
+
let typescriptExample = tsMatch?.[1]?.trim() || '';
|
|
267
|
+
typescriptExample = typescriptExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
|
|
268
|
+
// Extract Python section
|
|
269
|
+
const pyMatch = content.match(/---PYTHON---\s*([\s\S]*?)\s*---END---/);
|
|
270
|
+
let pythonExample = pyMatch?.[1]?.trim() || '';
|
|
271
|
+
pythonExample = pythonExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
|
|
272
|
+
// Primary code example is TypeScript (for backwards compat)
|
|
273
|
+
return {
|
|
274
|
+
markdown,
|
|
275
|
+
codeExample: typescriptExample,
|
|
276
|
+
typescriptExample,
|
|
277
|
+
pythonExample
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Helper to fix a broken code sample with smart strategies
|
|
282
|
+
*/
|
|
283
|
+
export async function fixCodeSample(client, code, error, context, iteration = 1) {
|
|
284
|
+
// Analyze error type to provide better fix guidance
|
|
285
|
+
const fixStrategy = analyzeErrorAndGetStrategy(error, code);
|
|
286
|
+
const response = await client.complete({
|
|
287
|
+
messages: [
|
|
288
|
+
{
|
|
289
|
+
role: 'system',
|
|
290
|
+
content: FIX_SYSTEM_PROMPT
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
role: 'user',
|
|
294
|
+
content: `Fix this code example. It must be COMPLETELY SELF-CONTAINED and runnable.
|
|
295
|
+
|
|
296
|
+
**Current code:**
|
|
297
|
+
\`\`\`
|
|
298
|
+
${code}
|
|
299
|
+
\`\`\`
|
|
300
|
+
|
|
301
|
+
**Error:**
|
|
302
|
+
${error}
|
|
303
|
+
|
|
304
|
+
**Fix strategy:** ${fixStrategy}
|
|
305
|
+
|
|
306
|
+
**Context:**
|
|
307
|
+
${context}
|
|
308
|
+
|
|
309
|
+
**Iteration ${iteration}:** ${iteration > 2 ? 'SIMPLIFY the example drastically - focus on demonstrating the concept, not real functionality.' : 'Make it self-contained.'}
|
|
310
|
+
|
|
311
|
+
Return ONLY the fixed code, no explanations or markdown fences.`
|
|
312
|
+
}
|
|
313
|
+
],
|
|
314
|
+
temperature: iteration > 3 ? 0.3 : 0, // Add creativity on later iterations
|
|
315
|
+
maxTokens: 1024
|
|
316
|
+
});
|
|
317
|
+
// Clean up any markdown fences
|
|
318
|
+
return response.content
|
|
319
|
+
.replace(/^```\w*\n?/, '')
|
|
320
|
+
.replace(/\n?```$/, '')
|
|
321
|
+
.trim();
|
|
322
|
+
}
|
|
323
|
+
const FIX_SYSTEM_PROMPT = `You are fixing a documentation code example to make it SELF-CONTAINED and RUNNABLE.
|
|
324
|
+
|
|
325
|
+
## Fix Strategies by Error Type
|
|
326
|
+
|
|
327
|
+
**Import/Module errors ("Cannot find module", "is not defined"):**
|
|
328
|
+
- DO NOT try to fix the import
|
|
329
|
+
- INLINE the type definitions
|
|
330
|
+
- MOCK the function behavior with a simple implementation
|
|
331
|
+
- Show what the function WOULD do conceptually
|
|
332
|
+
|
|
333
|
+
**Type errors ("Type X is not assignable"):**
|
|
334
|
+
- Define the type inline with \`type X = {...}\`
|
|
335
|
+
- Use simpler types if complex ones cause issues
|
|
336
|
+
|
|
337
|
+
**Runtime errors ("undefined is not a function", "Cannot read property"):**
|
|
338
|
+
- Initialize all variables
|
|
339
|
+
- Add null checks
|
|
340
|
+
- Use optional chaining (?.)
|
|
341
|
+
|
|
342
|
+
**Async errors ("await is only valid in async"):**
|
|
343
|
+
- Wrap in async function
|
|
344
|
+
- Add proper try/catch
|
|
345
|
+
- Call the async function at the end
|
|
346
|
+
|
|
347
|
+
## Example Fix Pattern
|
|
348
|
+
If the error is "Cannot find module '@mylib/tools'":
|
|
349
|
+
|
|
350
|
+
WRONG (trying to fix import):
|
|
351
|
+
\`\`\`
|
|
352
|
+
import { tool } from '@mylib/tools-v2' // Still fails
|
|
353
|
+
\`\`\`
|
|
354
|
+
|
|
355
|
+
RIGHT (inline everything):
|
|
356
|
+
\`\`\`
|
|
357
|
+
// Define types inline
|
|
358
|
+
type ToolResult = { data: string[] }
|
|
359
|
+
|
|
360
|
+
// Mock the tool behavior
|
|
361
|
+
const tool = {
|
|
362
|
+
search: async (query: string): Promise<ToolResult> => {
|
|
363
|
+
// Simulated behavior
|
|
364
|
+
return { data: ['result1', 'result2'] }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Usage
|
|
369
|
+
async function main() {
|
|
370
|
+
const result = await tool.search('test')
|
|
371
|
+
console.log(result) // { data: ['result1', 'result2'] }
|
|
372
|
+
}
|
|
373
|
+
main()
|
|
374
|
+
\`\`\`
|
|
375
|
+
|
|
376
|
+
Return ONLY the fixed code.`;
|
|
377
|
+
function analyzeErrorAndGetStrategy(error, _code) {
|
|
378
|
+
const errorLower = error.toLowerCase();
|
|
379
|
+
if (errorLower.includes('cannot find module') || errorLower.includes('module not found')) {
|
|
380
|
+
const importMatch = error.match(/['"]([^'"]+)['"]/)?.[1] || 'the library';
|
|
381
|
+
return `IMPORT ERROR: Cannot import from "${importMatch}". Remove ALL imports and inline types/mock functions instead.`;
|
|
382
|
+
}
|
|
383
|
+
if (errorLower.includes('is not defined') || errorLower.includes('is not a function')) {
|
|
384
|
+
const undefinedMatch = error.match(/(\w+) is not (defined|a function)/)?.[1];
|
|
385
|
+
return `UNDEFINED: "${undefinedMatch}" is not available. Define it inline or mock it.`;
|
|
386
|
+
}
|
|
387
|
+
if (errorLower.includes('type') && (errorLower.includes('assignable') || errorLower.includes('missing'))) {
|
|
388
|
+
return `TYPE ERROR: Type mismatch. Simplify types or define them inline with correct structure.`;
|
|
389
|
+
}
|
|
390
|
+
if (errorLower.includes('await') && errorLower.includes('async')) {
|
|
391
|
+
return `ASYNC ERROR: Wrap code in async function and call it: async function main() {...} main()`;
|
|
392
|
+
}
|
|
393
|
+
if (errorLower.includes('timeout') || errorLower.includes('timed out')) {
|
|
394
|
+
return `TIMEOUT: Code took too long. Remove any actual API calls or network requests. Use mock data.`;
|
|
395
|
+
}
|
|
396
|
+
if (errorLower.includes('syntaxerror') || errorLower.includes('unexpected token')) {
|
|
397
|
+
return `SYNTAX ERROR: Fix syntax issues. Check for missing brackets, quotes, or semicolons.`;
|
|
398
|
+
}
|
|
399
|
+
return `GENERAL: Make the code self-contained. Define all types inline, mock all external calls, use try/catch.`;
|
|
400
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createLLMClient, generateDocumentation, fixCodeSample } from './index.js';
|
|
2
|
+
import { PROVIDER_ENV_KEYS } from '../config/types.js';
|
|
3
|
+
async function runTests() {
|
|
4
|
+
console.log('=== LLM Client Tests ===\n');
|
|
5
|
+
// Find an available provider
|
|
6
|
+
const providers = ['deepseek', 'openai', 'anthropic', 'google', 'openrouter', 'ollama'];
|
|
7
|
+
let availableProvider = null;
|
|
8
|
+
let availableModel = null;
|
|
9
|
+
for (const provider of providers) {
|
|
10
|
+
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
11
|
+
if (!envKey || process.env[envKey]) {
|
|
12
|
+
availableProvider = provider;
|
|
13
|
+
availableModel = getTestModel(provider);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!availableProvider) {
|
|
18
|
+
console.log('No LLM provider available. Set one of these env vars:');
|
|
19
|
+
console.log(' DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY,');
|
|
20
|
+
console.log(' GOOGLE_API_KEY, OPENROUTER_API_KEY, or run Ollama locally');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
console.log(`Using provider: ${availableProvider} (${availableModel})`);
|
|
24
|
+
console.log('');
|
|
25
|
+
// Test 1: Create client
|
|
26
|
+
console.log('Test 1: Create LLM client');
|
|
27
|
+
const client = createLLMClient({
|
|
28
|
+
provider: availableProvider,
|
|
29
|
+
model: availableModel
|
|
30
|
+
});
|
|
31
|
+
console.log(` provider: ${client.provider}`);
|
|
32
|
+
console.log(' ✓ client created\n');
|
|
33
|
+
// Test 2: Simple completion
|
|
34
|
+
console.log('Test 2: Simple completion');
|
|
35
|
+
try {
|
|
36
|
+
const response = await client.complete({
|
|
37
|
+
messages: [
|
|
38
|
+
{ role: 'user', content: 'Say "hello" and nothing else.' }
|
|
39
|
+
],
|
|
40
|
+
maxTokens: 10
|
|
41
|
+
});
|
|
42
|
+
console.log(` response: "${response.content.trim()}"`);
|
|
43
|
+
console.log(` model: ${response.model}`);
|
|
44
|
+
console.log(` tokens: ${response.usage.totalTokens}`);
|
|
45
|
+
console.log(' ✓ completion works\n');
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.log(` ✗ completion failed: ${err}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Test 3: Generate documentation
|
|
52
|
+
console.log('Test 3: Generate documentation');
|
|
53
|
+
try {
|
|
54
|
+
const result = await generateDocumentation(client, {
|
|
55
|
+
kind: 'function',
|
|
56
|
+
name: 'add',
|
|
57
|
+
signature: 'def add(a: int, b: int) -> int',
|
|
58
|
+
parameters: [
|
|
59
|
+
{ name: 'a', type: 'int' },
|
|
60
|
+
{ name: 'b', type: 'int' }
|
|
61
|
+
],
|
|
62
|
+
returnType: 'int',
|
|
63
|
+
docstring: 'Add two numbers together.'
|
|
64
|
+
});
|
|
65
|
+
console.log(` markdown length: ${result.markdown.length} chars`);
|
|
66
|
+
console.log(` code example length: ${result.codeExample.length} chars`);
|
|
67
|
+
if (result.codeExample.includes('add(')) {
|
|
68
|
+
console.log(' ✓ code example calls the function\n');
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(` code example:\n${result.codeExample}`);
|
|
72
|
+
console.log(' ⚠ code example might not call the function\n');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.log(` ✗ doc generation failed: ${err}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
// Test 4: Fix broken code
|
|
80
|
+
console.log('Test 4: Fix broken code');
|
|
81
|
+
try {
|
|
82
|
+
const fixed = await fixCodeSample(client, 'print(add(1, 2)', // Missing closing paren
|
|
83
|
+
'SyntaxError: unexpected EOF while parsing', 'This is a code example for the add function');
|
|
84
|
+
console.log(` fixed code: ${fixed}`);
|
|
85
|
+
if (fixed.includes(')')) {
|
|
86
|
+
console.log(' ✓ code was fixed\n');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(' ⚠ fix might be incomplete\n');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.log(` ✗ code fix failed: ${err}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
console.log('✓ All LLM client tests passed');
|
|
97
|
+
}
|
|
98
|
+
function getTestModel(provider) {
|
|
99
|
+
switch (provider) {
|
|
100
|
+
case 'deepseek': return 'deepseek-chat';
|
|
101
|
+
case 'openai': return 'gpt-4o-mini';
|
|
102
|
+
case 'anthropic': return 'claude-sonnet-4-5-20241022';
|
|
103
|
+
case 'google': return 'gemini-2.0-flash';
|
|
104
|
+
case 'ollama': return 'llama3.2:1b'; // Small model for testing
|
|
105
|
+
case 'openrouter': return 'openai/gpt-4o-mini';
|
|
106
|
+
default: return 'gpt-4o-mini';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
runTests().catch(err => {
|
|
110
|
+
console.error('Test failed:', err);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|