@boostecom/provider 0.0.1
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/README.md +90 -0
- package/dist/index.cjs +2522 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +848 -0
- package/dist/index.d.ts +848 -0
- package/dist/index.js +2484 -0
- package/dist/index.js.map +1 -0
- package/docs/content/README.md +337 -0
- package/docs/content/agent-teams.mdx +324 -0
- package/docs/content/api.mdx +757 -0
- package/docs/content/best-practices.mdx +624 -0
- package/docs/content/examples.mdx +675 -0
- package/docs/content/guide.mdx +516 -0
- package/docs/content/index.mdx +99 -0
- package/docs/content/installation.mdx +246 -0
- package/docs/content/skills.mdx +548 -0
- package/docs/content/troubleshooting.mdx +588 -0
- package/docs/examples/README.md +499 -0
- package/docs/examples/abort-signal.ts +125 -0
- package/docs/examples/agent-teams.ts +122 -0
- package/docs/examples/basic-usage.ts +73 -0
- package/docs/examples/check-cli.ts +51 -0
- package/docs/examples/conversation-history.ts +69 -0
- package/docs/examples/custom-config.ts +90 -0
- package/docs/examples/generate-object-constraints.ts +209 -0
- package/docs/examples/generate-object.ts +211 -0
- package/docs/examples/hooks-callbacks.ts +63 -0
- package/docs/examples/images.ts +76 -0
- package/docs/examples/integration-test.ts +241 -0
- package/docs/examples/limitations.ts +150 -0
- package/docs/examples/logging-custom-logger.ts +99 -0
- package/docs/examples/logging-default.ts +55 -0
- package/docs/examples/logging-disabled.ts +74 -0
- package/docs/examples/logging-verbose.ts +64 -0
- package/docs/examples/long-running-tasks.ts +179 -0
- package/docs/examples/message-injection.ts +210 -0
- package/docs/examples/mid-stream-injection.ts +126 -0
- package/docs/examples/run-all-examples.sh +48 -0
- package/docs/examples/sdk-tools-callbacks.ts +49 -0
- package/docs/examples/skills-discovery.ts +144 -0
- package/docs/examples/skills-management.ts +140 -0
- package/docs/examples/stream-object.ts +80 -0
- package/docs/examples/streaming.ts +52 -0
- package/docs/examples/structured-output-repro.ts +227 -0
- package/docs/examples/tool-management.ts +215 -0
- package/docs/examples/tool-streaming.ts +132 -0
- package/docs/examples/zod4-compatibility-test.ts +290 -0
- package/docs/src/claude-code-language-model.test.ts +3883 -0
- package/docs/src/claude-code-language-model.ts +2586 -0
- package/docs/src/claude-code-provider.test.ts +97 -0
- package/docs/src/claude-code-provider.ts +179 -0
- package/docs/src/convert-to-claude-code-messages.images.test.ts +104 -0
- package/docs/src/convert-to-claude-code-messages.test.ts +193 -0
- package/docs/src/convert-to-claude-code-messages.ts +419 -0
- package/docs/src/errors.test.ts +213 -0
- package/docs/src/errors.ts +216 -0
- package/docs/src/index.test.ts +49 -0
- package/docs/src/index.ts +98 -0
- package/docs/src/logger.integration.test.ts +164 -0
- package/docs/src/logger.test.ts +184 -0
- package/docs/src/logger.ts +65 -0
- package/docs/src/map-claude-code-finish-reason.test.ts +120 -0
- package/docs/src/map-claude-code-finish-reason.ts +60 -0
- package/docs/src/mcp-helpers.test.ts +71 -0
- package/docs/src/mcp-helpers.ts +123 -0
- package/docs/src/message-injection.test.ts +460 -0
- package/docs/src/types.ts +447 -0
- package/docs/src/validation.test.ts +558 -0
- package/docs/src/validation.ts +360 -0
- package/package.json +124 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Object Generation Examples
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates core generateObject patterns with the Claude Code provider,
|
|
7
|
+
* progressing from simple to complex.
|
|
8
|
+
*
|
|
9
|
+
* Topics covered:
|
|
10
|
+
* - Simple objects with primitives
|
|
11
|
+
* - Arrays and optional fields
|
|
12
|
+
* - Enums and nested objects
|
|
13
|
+
* - Arrays of objects
|
|
14
|
+
* - Deep nesting (3+ levels)
|
|
15
|
+
*
|
|
16
|
+
* NOTE: Avoid Zod methods that produce unsupported JSON Schema annotations:
|
|
17
|
+
* .email(), .url(), .uuid(), .datetime() all emit `format` constraints that
|
|
18
|
+
* cause the Claude Code CLI to silently fall back to prose. Use .describe()
|
|
19
|
+
* with format hints instead, then validate client-side if strict format
|
|
20
|
+
* compliance is needed. See JSON_FIX.md for details.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createClaudeCode } from '../dist/index.js';
|
|
24
|
+
import { generateObject } from 'ai';
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
|
|
27
|
+
const claudeCode = createClaudeCode();
|
|
28
|
+
|
|
29
|
+
console.log('=== Claude Code: Object Generation Examples ===\n');
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Example 1: Simple object with primitives
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
async function example1_simpleObject() {
|
|
35
|
+
console.log('1. Simple Object with Primitives\n');
|
|
36
|
+
|
|
37
|
+
const { object } = await generateObject({
|
|
38
|
+
model: claudeCode('opus'),
|
|
39
|
+
schema: z.object({
|
|
40
|
+
name: z.string().describe('Full name of the person'),
|
|
41
|
+
age: z.number().describe('Age in years'),
|
|
42
|
+
email: z.string().describe('Email address (e.g. user@example.com)'),
|
|
43
|
+
isActive: z.boolean().describe('Whether the account is active'),
|
|
44
|
+
}),
|
|
45
|
+
prompt: 'Generate a profile for a software developer named Sarah.',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log('Generated profile:');
|
|
49
|
+
console.log(JSON.stringify(object, null, 2));
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Example 2: Arrays and optional fields
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
async function example2_arraysAndOptional() {
|
|
57
|
+
console.log('2. Arrays and Optional Fields\n');
|
|
58
|
+
|
|
59
|
+
const { object } = await generateObject({
|
|
60
|
+
model: claudeCode('opus'),
|
|
61
|
+
schema: z.object({
|
|
62
|
+
productName: z.string().describe('Name of the product'),
|
|
63
|
+
price: z.number().describe('Price in USD'),
|
|
64
|
+
description: z.string().describe('Product description'),
|
|
65
|
+
discount: z.number().optional().describe('Discount percentage if applicable'),
|
|
66
|
+
tags: z.array(z.string()).optional().describe('Product tags for categorization'),
|
|
67
|
+
features: z.array(z.string()).describe('Key product features'),
|
|
68
|
+
inStock: z.boolean().describe('Whether the product is in stock'),
|
|
69
|
+
}),
|
|
70
|
+
prompt: 'Generate a product listing for a wireless mechanical keyboard.',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('Generated product:');
|
|
74
|
+
console.log(JSON.stringify(object, null, 2));
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Example 3: Enums and nested objects
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
async function example3_enumsAndNested() {
|
|
82
|
+
console.log('3. Enums and Nested Objects\n');
|
|
83
|
+
|
|
84
|
+
const { object } = await generateObject({
|
|
85
|
+
model: claudeCode('opus'),
|
|
86
|
+
schema: z.object({
|
|
87
|
+
id: z.string().describe('Unique user ID'),
|
|
88
|
+
username: z.string(),
|
|
89
|
+
profile: z.object({
|
|
90
|
+
firstName: z.string(),
|
|
91
|
+
lastName: z.string(),
|
|
92
|
+
age: z.number().min(0).max(150),
|
|
93
|
+
email: z.string().describe('Email address (e.g. alex@example.com)'),
|
|
94
|
+
bio: z.string().describe('Short biography'),
|
|
95
|
+
interests: z.array(z.string()).describe('List of interests'),
|
|
96
|
+
location: z.object({
|
|
97
|
+
city: z.string(),
|
|
98
|
+
country: z.string(),
|
|
99
|
+
timezone: z.string(),
|
|
100
|
+
}),
|
|
101
|
+
}),
|
|
102
|
+
settings: z.object({
|
|
103
|
+
theme: z.enum(['light', 'dark', 'auto']),
|
|
104
|
+
notifications: z.boolean(),
|
|
105
|
+
language: z.string(),
|
|
106
|
+
}),
|
|
107
|
+
}),
|
|
108
|
+
prompt:
|
|
109
|
+
'Generate a complete user profile for a software developer named Alex who loves open source.',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
console.log('Generated user profile:');
|
|
113
|
+
console.log(JSON.stringify(object, null, 2));
|
|
114
|
+
console.log();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Example 4: Arrays of objects
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
async function example4_arraysOfObjects() {
|
|
121
|
+
console.log('4. Arrays of Objects\n');
|
|
122
|
+
|
|
123
|
+
const { object } = await generateObject({
|
|
124
|
+
model: claudeCode('opus'),
|
|
125
|
+
schema: z.object({
|
|
126
|
+
name: z.string().describe('Name of the recipe'),
|
|
127
|
+
ingredients: z
|
|
128
|
+
.array(
|
|
129
|
+
z.object({
|
|
130
|
+
item: z.string(),
|
|
131
|
+
amount: z.string(),
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
.describe('List of ingredients with amounts'),
|
|
135
|
+
instructions: z.array(z.string()).describe('Step-by-step cooking instructions'),
|
|
136
|
+
prepTime: z.number().describe('Preparation time in minutes'),
|
|
137
|
+
cookTime: z.number().describe('Cooking time in minutes'),
|
|
138
|
+
servings: z.number().describe('Number of servings'),
|
|
139
|
+
}),
|
|
140
|
+
prompt: 'Generate a detailed recipe for chocolate chip cookies.',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
console.log('Generated recipe:');
|
|
144
|
+
console.log(JSON.stringify(object, null, 2));
|
|
145
|
+
console.log();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Example 5: Deep nesting (3+ levels)
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
async function example5_deepNesting() {
|
|
152
|
+
console.log('5. Deep Nesting (3+ Levels)\n');
|
|
153
|
+
|
|
154
|
+
const { object } = await generateObject({
|
|
155
|
+
model: claudeCode('opus'),
|
|
156
|
+
schema: z.object({
|
|
157
|
+
company: z.object({
|
|
158
|
+
name: z.string().describe('Company name'),
|
|
159
|
+
founded: z.number().describe('Year founded'),
|
|
160
|
+
headquarters: z.object({
|
|
161
|
+
city: z.string(),
|
|
162
|
+
country: z.string(),
|
|
163
|
+
timezone: z.string(),
|
|
164
|
+
}),
|
|
165
|
+
departments: z
|
|
166
|
+
.array(
|
|
167
|
+
z.object({
|
|
168
|
+
name: z.string().describe('Department name'),
|
|
169
|
+
budget: z.number().describe('Annual budget in USD'),
|
|
170
|
+
headCount: z.number().describe('Number of employees'),
|
|
171
|
+
teams: z.array(
|
|
172
|
+
z.object({
|
|
173
|
+
name: z.string(),
|
|
174
|
+
lead: z.string().describe('Team lead name'),
|
|
175
|
+
members: z.number().describe('Team size'),
|
|
176
|
+
projects: z.array(z.string()).describe('Active project names'),
|
|
177
|
+
})
|
|
178
|
+
),
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
.describe('Company departments'),
|
|
182
|
+
}),
|
|
183
|
+
}),
|
|
184
|
+
prompt:
|
|
185
|
+
'Generate a structure for a mid-sized software company with 3 departments: Engineering, Product, and Marketing. Each should have 2-3 teams.',
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log('Generated company structure:');
|
|
189
|
+
console.log(JSON.stringify(object, null, 2));
|
|
190
|
+
console.log();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Main
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
async function main() {
|
|
197
|
+
try {
|
|
198
|
+
await example1_simpleObject();
|
|
199
|
+
await example2_arraysAndOptional();
|
|
200
|
+
await example3_enumsAndNested();
|
|
201
|
+
await example4_arraysOfObjects();
|
|
202
|
+
await example5_deepNesting();
|
|
203
|
+
|
|
204
|
+
console.log('All examples completed!');
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Error:', error);
|
|
207
|
+
console.log('\nTip: Make sure the Claude Code SDK is authenticated with: claude login');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Hooks and canUseTool
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates lifecycle hooks and dynamic permission callback.
|
|
5
|
+
* Requires Claude Code CLI authentication and environment setup.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { streamText } from 'ai';
|
|
9
|
+
import { createClaudeCode } from '../dist/index.js';
|
|
10
|
+
// NOTE: Migrating to Claude Agent SDK:
|
|
11
|
+
// - System prompt is not applied by default
|
|
12
|
+
// - Filesystem settings (CLAUDE.md, settings.json) are not loaded by default
|
|
13
|
+
// To restore old behavior, set when creating model instances, e.g.:
|
|
14
|
+
// systemPrompt: { type: 'preset', preset: 'claude_code' }
|
|
15
|
+
// settingSources: ['user', 'project', 'local']
|
|
16
|
+
|
|
17
|
+
// PreToolUse hook: log and allow
|
|
18
|
+
const preToolHook = async (input: any) => {
|
|
19
|
+
if (input.hook_event_name === 'PreToolUse') {
|
|
20
|
+
console.log(`🔧 About to run tool: ${input.tool_name}`);
|
|
21
|
+
return {
|
|
22
|
+
continue: true,
|
|
23
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return { continue: true };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// PostToolUse hook: log after tool completes
|
|
30
|
+
const postToolHook = async (input: any) => {
|
|
31
|
+
if (input.hook_event_name === 'PostToolUse') {
|
|
32
|
+
console.log(`✅ Tool completed: ${input.tool_name}`);
|
|
33
|
+
}
|
|
34
|
+
return { continue: true };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
const provider = createClaudeCode({
|
|
39
|
+
defaultSettings: {
|
|
40
|
+
hooks: {
|
|
41
|
+
PreToolUse: [{ hooks: [preToolHook] }],
|
|
42
|
+
PostToolUse: [{ hooks: [postToolHook] }],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Use a prompt that triggers tool use so the hooks actually fire
|
|
48
|
+
const result = streamText({
|
|
49
|
+
model: provider('sonnet'),
|
|
50
|
+
prompt: 'List the files in the current directory using the Bash tool.',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let text = '';
|
|
54
|
+
for await (const chunk of result.textStream) {
|
|
55
|
+
text += chunk;
|
|
56
|
+
}
|
|
57
|
+
console.log('Response:', text.trim());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().catch((err) => {
|
|
61
|
+
console.error('Example failed:', err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { extname, basename, join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { streamText } from 'ai';
|
|
5
|
+
import { claudeCode } from '../dist/index.js';
|
|
6
|
+
|
|
7
|
+
const SUPPORTED_EXTENSIONS: Record<string, string> = {
|
|
8
|
+
'.png': 'image/png',
|
|
9
|
+
'.jpg': 'image/jpeg',
|
|
10
|
+
'.jpeg': 'image/jpeg',
|
|
11
|
+
'.gif': 'image/gif',
|
|
12
|
+
'.webp': 'image/webp',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function toDataUrl(filePath: string): string {
|
|
16
|
+
const ext = extname(filePath).toLowerCase();
|
|
17
|
+
const mediaType = SUPPORTED_EXTENSIONS[ext];
|
|
18
|
+
if (!mediaType) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Unsupported image extension "${ext}". Supported: ${Object.keys(SUPPORTED_EXTENSIONS).join(', ')}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const contents = readFileSync(filePath);
|
|
25
|
+
const base64 = contents.toString('base64');
|
|
26
|
+
return `data:${mediaType};base64,${base64}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
// Use bundled bull.webp as default if no path provided
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const defaultImagePath = join(__dirname, 'bull.webp');
|
|
33
|
+
|
|
34
|
+
const filePath = process.argv[2] || defaultImagePath;
|
|
35
|
+
if (!process.argv[2]) {
|
|
36
|
+
console.log(`Using default image: ${defaultImagePath}`);
|
|
37
|
+
console.log(
|
|
38
|
+
'Tip: Pass a custom image path as argument: npx tsx examples/images.ts /path/to/image.png\n'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dataUrl = toDataUrl(filePath);
|
|
43
|
+
|
|
44
|
+
const result = streamText({
|
|
45
|
+
model: claudeCode('opus', { streamingInput: 'always' }),
|
|
46
|
+
messages: [
|
|
47
|
+
{
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: `Describe the mood conveyed by "${basename(filePath)}" in one sentence.`,
|
|
53
|
+
},
|
|
54
|
+
{ type: 'image', image: dataUrl },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
process.stdout.write('Assistant: ');
|
|
61
|
+
for await (const chunk of result.textStream) {
|
|
62
|
+
process.stdout.write(chunk);
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((error) => {
|
|
68
|
+
console.error('Error while streaming image prompt:', error);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
});
|
|
71
|
+
// NOTE: Migrating to Claude Agent SDK:
|
|
72
|
+
// - System prompt is not applied by default
|
|
73
|
+
// - Filesystem settings (CLAUDE.md, settings.json) are not loaded by default
|
|
74
|
+
// To restore old behavior, set:
|
|
75
|
+
// systemPrompt: { type: 'preset', preset: 'claude_code' }
|
|
76
|
+
// settingSources: ['user', 'project', 'local']
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Claude Code AI SDK Provider
|
|
3
|
+
*
|
|
4
|
+
* These tests verify core functionality of the provider
|
|
5
|
+
* including text generation, conversations, and error handling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { generateText } from 'ai';
|
|
9
|
+
import { claudeCode, isAuthenticationError } from '../dist/index.js';
|
|
10
|
+
// NOTE: Migrating to Claude Agent SDK:
|
|
11
|
+
// - System prompt is not applied by default
|
|
12
|
+
// - Filesystem settings (CLAUDE.md, settings.json) are not loaded by default
|
|
13
|
+
// To restore old behavior, set:
|
|
14
|
+
// systemPrompt: { type: 'preset', preset: 'claude_code' }
|
|
15
|
+
// settingSources: ['user', 'project', 'local']
|
|
16
|
+
|
|
17
|
+
async function testBasicGeneration() {
|
|
18
|
+
console.log('🧪 Test 1: Basic text generation with Haiku...');
|
|
19
|
+
try {
|
|
20
|
+
const { text } = await generateText({
|
|
21
|
+
model: claudeCode('opus'),
|
|
22
|
+
prompt: 'Say "Hello from Claude Code Provider!" and nothing else.',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (text.includes('Hello from Claude Code Provider')) {
|
|
26
|
+
console.log('✅ Success:', text);
|
|
27
|
+
} else {
|
|
28
|
+
console.error('❌ Unexpected response:', text);
|
|
29
|
+
throw new Error('Basic generation test failed');
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('❌ Failed:', error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function testWithSystemMessage() {
|
|
38
|
+
console.log('\n🧪 Test 2: Generation with system message...');
|
|
39
|
+
try {
|
|
40
|
+
const { text } = await generateText({
|
|
41
|
+
model: claudeCode('opus'),
|
|
42
|
+
messages: [
|
|
43
|
+
{
|
|
44
|
+
role: 'system',
|
|
45
|
+
content: 'You are a helpful assistant. Answer with just the number, no explanation.',
|
|
46
|
+
},
|
|
47
|
+
{ role: 'user', content: 'What is 2+2?' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const cleanText = text.trim();
|
|
52
|
+
if (cleanText === '4' || cleanText.includes('4')) {
|
|
53
|
+
console.log('✅ Success:', text);
|
|
54
|
+
} else {
|
|
55
|
+
console.error('❌ Unexpected response:', text);
|
|
56
|
+
throw new Error('System message test failed');
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('❌ Failed:', error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function testConversation() {
|
|
65
|
+
console.log('\n🧪 Test 3: Multi-turn conversation with message history...');
|
|
66
|
+
try {
|
|
67
|
+
// First turn: establish context
|
|
68
|
+
const { text: response1 } = await generateText({
|
|
69
|
+
model: claudeCode('opus'),
|
|
70
|
+
messages: [
|
|
71
|
+
{
|
|
72
|
+
role: 'user',
|
|
73
|
+
content:
|
|
74
|
+
'My favorite color is purple and I live in Seattle. Just acknowledge this information.',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
console.log('✅ First turn:', response1);
|
|
79
|
+
|
|
80
|
+
// Second turn: test memory with full history
|
|
81
|
+
const { text: response2 } = await generateText({
|
|
82
|
+
model: claudeCode('opus'),
|
|
83
|
+
messages: [
|
|
84
|
+
{
|
|
85
|
+
role: 'user',
|
|
86
|
+
content:
|
|
87
|
+
'My favorite color is purple and I live in Seattle. Just acknowledge this information.',
|
|
88
|
+
},
|
|
89
|
+
{ role: 'assistant', content: response1 },
|
|
90
|
+
{ role: 'user', content: 'What is my favorite color?' },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (response2.toLowerCase().includes('purple')) {
|
|
95
|
+
console.log('✅ Second turn (remembered color):', response2);
|
|
96
|
+
} else {
|
|
97
|
+
console.error('❌ Failed to remember color:', response2);
|
|
98
|
+
throw new Error('Conversation memory test failed');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Third turn: test deeper context
|
|
102
|
+
const { text: response3 } = await generateText({
|
|
103
|
+
model: claudeCode('opus'),
|
|
104
|
+
messages: [
|
|
105
|
+
{
|
|
106
|
+
role: 'user',
|
|
107
|
+
content:
|
|
108
|
+
'My favorite color is purple and I live in Seattle. Just acknowledge this information.',
|
|
109
|
+
},
|
|
110
|
+
{ role: 'assistant', content: response1 },
|
|
111
|
+
{ role: 'user', content: 'What is my favorite color?' },
|
|
112
|
+
{ role: 'assistant', content: response2 },
|
|
113
|
+
{ role: 'user', content: 'Where do I live?' },
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (response3.toLowerCase().includes('seattle')) {
|
|
118
|
+
console.log('✅ Third turn (remembered location):', response3);
|
|
119
|
+
console.log('✅ Conversation context maintained successfully!');
|
|
120
|
+
} else {
|
|
121
|
+
console.error('❌ Failed to remember location:', response3);
|
|
122
|
+
throw new Error('Conversation memory test failed');
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('❌ Failed:', error);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function testErrorHandling() {
|
|
131
|
+
console.log('\n🧪 Test 4: Error handling with invalid executable path...');
|
|
132
|
+
try {
|
|
133
|
+
const badClaude = claudeCode('opus', {
|
|
134
|
+
pathToClaudeCodeExecutable: 'claude-nonexistent-binary-12345',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await generateText({
|
|
138
|
+
model: badClaude,
|
|
139
|
+
prompt: 'This should fail',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
console.error('❌ Expected error but got success');
|
|
143
|
+
throw new Error('Error handling test failed - should have thrown an error');
|
|
144
|
+
} catch (error: any) {
|
|
145
|
+
if (error.message?.includes('not found') || error.message?.includes('ENOENT')) {
|
|
146
|
+
console.log('✅ Error handled correctly:', error.message);
|
|
147
|
+
} else if (error.message?.includes('Error handling test failed')) {
|
|
148
|
+
throw error; // Re-throw our test failure
|
|
149
|
+
} else {
|
|
150
|
+
console.log('✅ Got error (different than expected):', error.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function testStreaming() {
|
|
156
|
+
console.log('\n🧪 Test 5: Basic streaming...');
|
|
157
|
+
try {
|
|
158
|
+
const { textStream } = streamText({
|
|
159
|
+
model: claudeCode('opus'),
|
|
160
|
+
prompt: 'Count from 1 to 5, one number per line.',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
let fullText = '';
|
|
164
|
+
process.stdout.write('Streaming: ');
|
|
165
|
+
for await (const chunk of textStream) {
|
|
166
|
+
process.stdout.write(chunk);
|
|
167
|
+
fullText += chunk;
|
|
168
|
+
}
|
|
169
|
+
console.log('\n✅ Streaming completed');
|
|
170
|
+
|
|
171
|
+
// Verify we got numbers
|
|
172
|
+
const hasNumbers = ['1', '2', '3', '4', '5'].every((num) => fullText.includes(num));
|
|
173
|
+
if (!hasNumbers) {
|
|
174
|
+
throw new Error('Streaming test failed - missing expected numbers');
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('\n❌ Streaming failed:', error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Import streamText for the streaming test
|
|
183
|
+
import { streamText } from 'ai';
|
|
184
|
+
|
|
185
|
+
async function runAllTests() {
|
|
186
|
+
console.log('🚀 Running Claude Code AI SDK Provider Integration Tests\n');
|
|
187
|
+
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
let testsRun = 0;
|
|
190
|
+
let testsPassed = 0;
|
|
191
|
+
|
|
192
|
+
const tests = [
|
|
193
|
+
{ name: 'Basic Generation', fn: testBasicGeneration },
|
|
194
|
+
{ name: 'System Message', fn: testWithSystemMessage },
|
|
195
|
+
{ name: 'Conversation', fn: testConversation },
|
|
196
|
+
{ name: 'Error Handling', fn: testErrorHandling },
|
|
197
|
+
{ name: 'Streaming', fn: testStreaming },
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const test of tests) {
|
|
201
|
+
testsRun++;
|
|
202
|
+
try {
|
|
203
|
+
await test.fn();
|
|
204
|
+
testsPassed++;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`\n❌ ${test.name} test failed`);
|
|
207
|
+
if (isAuthenticationError(error)) {
|
|
208
|
+
console.log('\n⚠️ Authentication required. Please run: claude login');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const duration = Date.now() - startTime;
|
|
215
|
+
console.log('\n' + '='.repeat(50));
|
|
216
|
+
console.log(`📊 Test Results: ${testsPassed}/${testsRun} passed (${duration}ms)`);
|
|
217
|
+
|
|
218
|
+
if (testsPassed === testsRun) {
|
|
219
|
+
console.log('✅ All tests passed!');
|
|
220
|
+
process.exit(0);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(`❌ ${testsRun - testsPassed} tests failed`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Add configurable global timeout (default 3 minutes)
|
|
228
|
+
const TIMEOUT_MS = Number(process.env.CLAUDE_IT_TIMEOUT_MS ?? '180000');
|
|
229
|
+
const timeoutId = setTimeout(() => {
|
|
230
|
+
console.log(`\n⏱️ Tests timed out after ${TIMEOUT_MS / 1000} seconds`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}, TIMEOUT_MS);
|
|
233
|
+
|
|
234
|
+
runAllTests()
|
|
235
|
+
.then(() => {
|
|
236
|
+
clearTimeout(timeoutId);
|
|
237
|
+
})
|
|
238
|
+
.catch((error) => {
|
|
239
|
+
console.error('Fatal error:', error);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
});
|