Skip to content

Commit 789a1dd

Browse files
committed
Bug Fix: api error
1 parent d4585ab commit 789a1dd

File tree

5 files changed

+193
-23
lines changed

5 files changed

+193
-23
lines changed

.vscodeignore

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
.vscode/**
22
.vscode-test/**
3+
.pnpm-store/**
34
out/**
45
node_modules/**
56
src/**
67
.gitignore
78
.yarnrc
8-
webpack.config.js
9+
.npmrc
10+
pnpm-lock.yaml
11+
esbuild.js
912
vsc-extension-quickstart.md
10-
/tsconfig.json
11-
/.eslintrc.json
12-
/.map
13-
**/.ts
14-
.github/
15-
examples/
16-
.devcontainer/
17-
test/**
13+
**/tsconfig.json
14+
**/.eslintrc.json
15+
**/*.map
16+
**/*.ts
17+
.github/**
18+
examples/**
19+
.devcontainer/**
20+
test/**
21+
dist/test/**
22+
.eslint*
23+
coverage/**
24+
.nyc_output/**

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
33
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## [2.1.0] - 2024-11-24
6+
### Fixed
7+
API Endpoint 400 error
8+
### Changed
9+
Update API Test Suite
510

6-
## [2.0.0] - 2024-11-24
11+
## [2.0.1] - 2024-11-24
712
### Fixed
813
Purchase link
914

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "claude-vscode-assistant",
33
"displayName": "Claude AI Assistant",
4-
"version": "2.0.1",
4+
"version": "2.1.0",
55
"description": "Claude AI assistant for Visual Studio Code - Seamlessly integrate Claude's capabilities into your development workflow",
66
"publisher": "conscious-robot",
77
"pricing": "Trial",

src/api.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import * as vscode from 'vscode';
33
import { getConfiguration } from './config';
44

5-
// Constants
5+
// Constants and type definitions
66
const SERVICE_URL = 'https://api.anthropic.com/v1/messages';
77
const VALID_MODELS = ['claude-3-opus-20240229', 'claude-3-sonnet-20240229'] as const;
88
type ValidModel = typeof VALID_MODELS[number];
99

10+
// Interface definitions
1011
export interface ClaudeMessageContent {
1112
type: 'text'; // Restrict to known types
1213
text: string;
@@ -28,7 +29,7 @@ export interface ClaudeResponse {
2829
dailyLimit?: number;
2930
}
3031

31-
// Enhanced type guard with complete validation
32+
// Type guard functions - defined before use
3233
function isClaudeMessageContent(item: unknown): item is ClaudeMessageContent {
3334
return (
3435
typeof item === 'object' &&
@@ -43,30 +44,25 @@ function isClaudeMessageContent(item: unknown): item is ClaudeMessageContent {
4344
function isClaudeResponse(data: unknown): data is ClaudeResponse {
4445
const response = data as Partial<ClaudeResponse>;
4546

46-
// Basic structure check
4747
if (typeof data !== 'object' || data === null) {
4848
return false;
4949
}
5050

51-
// Required fields check
5251
const requiredStringFields = ['id', 'type', 'role'] as const;
5352
for (const field of requiredStringFields) {
5453
if (typeof response[field] !== 'string') {
5554
return false;
5655
}
5756
}
5857

59-
// Content array check
6058
if (!Array.isArray(response.content)) {
6159
return false;
6260
}
6361

64-
// Validate each content item
6562
if (!response.content.every(isClaudeMessageContent)) {
6663
return false;
6764
}
6865

69-
// Usage object check
7066
if (
7167
typeof response.usage !== 'object' ||
7268
response.usage === null ||
@@ -76,7 +72,6 @@ function isClaudeResponse(data: unknown): data is ClaudeResponse {
7672
return false;
7773
}
7874

79-
// Optional fields check
8075
if (
8176
(response.stop_reason !== null && typeof response.stop_reason !== 'string') ||
8277
(response.stop_sequence !== null && typeof response.stop_sequence !== 'string') ||
@@ -86,14 +81,14 @@ function isClaudeResponse(data: unknown): data is ClaudeResponse {
8681
return false;
8782
}
8883

89-
// Validate model string matches expected format
9084
if (!response.model || !VALID_MODELS.includes(response.model as ValidModel)) {
9185
return false;
9286
}
9387

9488
return true;
9589
}
9690

91+
// Main API function
9792
export async function askClaude(text: string, token?: vscode.CancellationToken): Promise<ClaudeResponse> {
9893
const config = getConfiguration();
9994

@@ -114,12 +109,16 @@ export async function askClaude(text: string, token?: vscode.CancellationToken):
114109
method: 'POST',
115110
headers: {
116111
'Content-Type': 'application/json',
117-
'Cache-Control': 'no-cache',
112+
'anthropic-version': '2023-06-01',
118113
'x-api-key': config.apiKey || process.env.CLAUDE_API_KEY || ''
119114
},
120115
body: JSON.stringify({
121-
prompt: text,
122-
model: config.model
116+
messages: [{
117+
role: 'user',
118+
content: text
119+
}],
120+
model: config.model,
121+
max_tokens: 1500
123122
}),
124123
signal: abortController.signal
125124
});

test/suite/api.test.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// test/suite/api.test.ts
2+
import * as assert from 'assert';
3+
import * as sinon from 'sinon';
4+
import { askClaude, ClaudeResponse } from '../../src/api';
5+
import * as vscode from 'vscode';
6+
7+
suite('Claude API Tests', () => {
8+
let sandbox: sinon.SinonSandbox;
9+
let fetchStub: sinon.SinonStub;
10+
11+
const mockConfig = {
12+
apiKey: 'test-key',
13+
model: 'claude-3-opus-20240229'
14+
};
15+
16+
const mockSuccessResponse: ClaudeResponse = {
17+
id: 'test-id',
18+
type: 'message',
19+
role: 'assistant',
20+
content: [{ type: 'text', text: 'Test response' }],
21+
model: 'claude-3-opus-20240229',
22+
stop_reason: null,
23+
stop_sequence: null,
24+
usage: {
25+
input_tokens: 10,
26+
output_tokens: 20
27+
}
28+
};
29+
30+
setup(() => {
31+
sandbox = sinon.createSandbox();
32+
33+
// Stub global fetch
34+
fetchStub = sandbox.stub(global, 'fetch');
35+
36+
// Stub configuration
37+
sandbox.stub(vscode.workspace, 'getConfiguration').returns({
38+
get: (key: string) => {
39+
if (key === 'apiKey') return mockConfig.apiKey;
40+
if (key === 'model') return mockConfig.model;
41+
return undefined;
42+
}
43+
} as any);
44+
});
45+
46+
teardown(() => {
47+
sandbox.restore();
48+
});
49+
50+
test('successful API call', async () => {
51+
fetchStub.resolves({
52+
ok: true,
53+
json: async () => mockSuccessResponse
54+
} as Response);
55+
56+
const response = await askClaude('Test prompt');
57+
58+
assert.strictEqual(response.content[0].text, 'Test response');
59+
assert.strictEqual(response.model, 'claude-3-opus-20240229');
60+
61+
const fetchCall = fetchStub.getCall(0);
62+
const requestInit = fetchCall.args[1] as RequestInit & {
63+
headers: Record<string, string>;
64+
};
65+
const requestBody = JSON.parse(requestInit.body as string);
66+
67+
assert.deepStrictEqual(requestBody.messages, [{
68+
role: 'user',
69+
content: 'Test prompt'
70+
}]);
71+
assert.strictEqual(requestInit.headers['anthropic-version'], '2023-06-01');
72+
});
73+
74+
test('validates request headers', async () => {
75+
fetchStub.resolves({
76+
ok: true,
77+
json: async () => mockSuccessResponse
78+
} as Response);
79+
80+
await askClaude('Test prompt');
81+
82+
const requestInit = fetchStub.getCall(0).args[1] as RequestInit & {
83+
headers: Record<string, string>;
84+
};
85+
86+
assert.deepStrictEqual(requestInit.headers, {
87+
'Content-Type': 'application/json',
88+
'anthropic-version': '2023-06-01',
89+
'x-api-key': 'test-key'
90+
});
91+
});
92+
93+
test('handles request cancellation', async () => {
94+
const tokenSource = new vscode.CancellationTokenSource();
95+
96+
// Create an AbortError that matches the browser's native error
97+
const abortError = new Error();
98+
abortError.name = 'AbortError';
99+
100+
// Setup fetch to throw the abort error after a delay
101+
fetchStub.callsFake(() => new Promise((_, reject) => {
102+
setTimeout(() => {
103+
reject(abortError);
104+
}, 10);
105+
}));
106+
107+
// Start the request and immediately cancel
108+
const promise = askClaude('Test prompt', tokenSource.token);
109+
tokenSource.cancel();
110+
111+
// Use a custom validator function
112+
await assert.rejects(
113+
promise,
114+
error => {
115+
// Check if the error is an instance of vscode.CancellationError
116+
return error instanceof vscode.CancellationError;
117+
},
118+
'Expected a CancellationError'
119+
);
120+
});
121+
122+
test('handles missing API key', async () => {
123+
// Update config stub to return no API key
124+
sandbox.restore();
125+
sandbox.stub(vscode.workspace, 'getConfiguration').returns({
126+
get: (key: string) => undefined
127+
} as any);
128+
process.env.CLAUDE_API_KEY = '';
129+
130+
await assert.rejects(
131+
askClaude('Test prompt'),
132+
/No API key configured/
133+
);
134+
});
135+
136+
test('validates response format', async () => {
137+
fetchStub.resolves({
138+
ok: true,
139+
json: async () => ({
140+
id: 'test-id',
141+
// Missing required fields
142+
})
143+
} as Response);
144+
145+
await assert.rejects(
146+
askClaude('Test prompt'),
147+
/Invalid response format/
148+
);
149+
});
150+
151+
test('handles network errors', async () => {
152+
fetchStub.rejects(new Error('Network error'));
153+
154+
await assert.rejects(
155+
askClaude('Test prompt'),
156+
/Network error/
157+
);
158+
});
159+
});

0 commit comments

Comments
 (0)