Skip to content

Commit 760c4c3

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/roo-cline-select-api-config
2 parents 2611964 + e0e2d2d commit 760c4c3

File tree

25 files changed

+1753
-43
lines changed

25 files changed

+1753
-43
lines changed

.changeset/eleven-papayas-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Experimental support for VS Code Language Models (thanks @RaySinner / @julesmons!)

.github/workflows/changeset-release.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ name: Changeset Release
22
run-name: Changeset Release ${{ github.actor != 'R00-B0T' && '- Create PR' || '- Update Changelog' }}
33

44
on:
5+
workflow_dispatch:
56
pull_request:
67
types: [closed, opened, labeled]
78

89
env:
910
REPO_PATH: ${{ github.repository }}
11+
GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
1012

1113
jobs:
1214
# Job 1: Create version bump PR when changesets are merged to main
1315
changeset-pr-version-bump:
1416
if: >
15-
github.event_name == 'pull_request' &&
17+
( github.event_name == 'pull_request' &&
1618
github.event.pull_request.merged == true &&
1719
github.event.pull_request.base.ref == 'main' &&
18-
github.actor != 'R00-B0T'
20+
github.actor != 'R00-B0T' ) ||
21+
github.event_name == 'workflow_dispatch'
1922
runs-on: ubuntu-latest
2023
permissions:
2124
contents: write
@@ -25,7 +28,7 @@ jobs:
2528
uses: actions/checkout@v4
2629
with:
2730
fetch-depth: 0
28-
ref: ${{ github.event.pull_request.head.sha }}
31+
ref: ${{ env.GIT_REF }}
2932

3033
- name: Setup Node.js
3134
uses: actions/setup-node@v4

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Give it a try and let us know what you think in the reddit: https://www.reddit.c
6060
- Support for Glama
6161
- Support for listing models from OpenAI-compatible providers
6262
- Support for adding OpenAI-compatible models with or without streaming
63+
- Experimental support for VS Code Language Models (e.g. Copilot)
6364
- Per-tool MCP auto-approval
6465
- Enable/disable individual MCP servers
6566
- Enable/disable the MCP feature overall
@@ -143,7 +144,7 @@ Once your merge is successful:
143144
<table>
144145
<tbody>
145146
<td align="center">
146-
<a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev" target="_blank"><strong>Download on VS Marketplace</strong></a>
147+
<a href="https://marketplace.visualstudio.com/items?itemName=rooveterinaryinc.roo-cline" target="_blank"><strong>Download on VS Marketplace</strong></a>
147148
</td>
148149
<td align="center">
149150
<a href="https://discord.gg/cline" target="_blank"><strong>Join the Discord</strong></a>

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@
4242
"ai",
4343
"llama"
4444
],
45-
"activationEvents": [],
45+
"activationEvents": [
46+
"onLanguage",
47+
"onStartupFinished"
48+
],
4649
"main": "./dist/extension.js",
4750
"contributes": {
4851
"viewsContainers": {
@@ -151,6 +154,20 @@
151154
"git show"
152155
],
153156
"description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled"
157+
},
158+
"roo-cline.vsCodeLmModelSelector": {
159+
"type": "object",
160+
"properties": {
161+
"vendor": {
162+
"type": "string",
163+
"description": "The vendor of the language model (e.g. copilot)"
164+
},
165+
"family": {
166+
"type": "string",
167+
"description": "The family of the language model (e.g. gpt-4)"
168+
}
169+
},
170+
"description": "Settings for VSCode Language Model API"
154171
}
155172
}
156173
}
@@ -227,7 +244,7 @@
227244
"isbinaryfile": "^5.0.2",
228245
"mammoth": "^1.8.0",
229246
"monaco-vscode-textmate-theme-converter": "^0.1.7",
230-
"openai": "^4.73.1",
247+
"openai": "^4.78.1",
231248
"os-name": "^6.0.0",
232249
"p-wait-for": "^5.0.2",
233250
"pdf-parse": "^1.1.1",

src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { LmStudioHandler } from "./providers/lmstudio"
1111
import { GeminiHandler } from "./providers/gemini"
1212
import { OpenAiNativeHandler } from "./providers/openai-native"
1313
import { DeepSeekHandler } from "./providers/deepseek"
14+
import { VsCodeLmHandler } from "./providers/vscode-lm"
1415
import { ApiStream } from "./transform/stream"
1516

1617
export interface SingleCompletionHandler {

src/api/providers/__tests__/openai-native.test.ts

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ jest.mock('openai', () => {
6060
describe('OpenAiNativeHandler', () => {
6161
let handler: OpenAiNativeHandler;
6262
let mockOptions: ApiHandlerOptions;
63+
const systemPrompt = 'You are a helpful assistant.';
64+
const messages: Anthropic.Messages.MessageParam[] = [
65+
{
66+
role: 'user',
67+
content: 'Hello!'
68+
}
69+
];
6370

6471
beforeEach(() => {
6572
mockOptions = {
@@ -86,14 +93,6 @@ describe('OpenAiNativeHandler', () => {
8693
});
8794

8895
describe('createMessage', () => {
89-
const systemPrompt = 'You are a helpful assistant.';
90-
const messages: Anthropic.Messages.MessageParam[] = [
91-
{
92-
role: 'user',
93-
content: 'Hello!'
94-
}
95-
];
96-
9796
it('should handle streaming responses', async () => {
9897
const stream = handler.createMessage(systemPrompt, messages);
9998
const chunks: any[] = [];
@@ -109,15 +108,126 @@ describe('OpenAiNativeHandler', () => {
109108

110109
it('should handle API errors', async () => {
111110
mockCreate.mockRejectedValueOnce(new Error('API Error'));
112-
113111
const stream = handler.createMessage(systemPrompt, messages);
114-
115112
await expect(async () => {
116113
for await (const chunk of stream) {
117114
// Should not reach here
118115
}
119116
}).rejects.toThrow('API Error');
120117
});
118+
119+
it('should handle missing content in response for o1 model', async () => {
120+
// Use o1 model which supports developer role
121+
handler = new OpenAiNativeHandler({
122+
...mockOptions,
123+
apiModelId: 'o1'
124+
});
125+
126+
mockCreate.mockResolvedValueOnce({
127+
choices: [{ message: { content: null } }],
128+
usage: {
129+
prompt_tokens: 0,
130+
completion_tokens: 0,
131+
total_tokens: 0
132+
}
133+
});
134+
135+
const generator = handler.createMessage(systemPrompt, messages);
136+
const results = [];
137+
for await (const result of generator) {
138+
results.push(result);
139+
}
140+
141+
expect(results).toEqual([
142+
{ type: 'text', text: '' },
143+
{ type: 'usage', inputTokens: 0, outputTokens: 0 }
144+
]);
145+
146+
// Verify developer role is used for system prompt with o1 model
147+
expect(mockCreate).toHaveBeenCalledWith({
148+
model: 'o1',
149+
messages: [
150+
{ role: 'developer', content: systemPrompt },
151+
{ role: 'user', content: 'Hello!' }
152+
]
153+
});
154+
});
155+
});
156+
157+
describe('streaming models', () => {
158+
beforeEach(() => {
159+
handler = new OpenAiNativeHandler({
160+
...mockOptions,
161+
apiModelId: 'gpt-4o',
162+
});
163+
});
164+
165+
it('should handle streaming response', async () => {
166+
const mockStream = [
167+
{ choices: [{ delta: { content: 'Hello' } }], usage: null },
168+
{ choices: [{ delta: { content: ' there' } }], usage: null },
169+
{ choices: [{ delta: { content: '!' } }], usage: { prompt_tokens: 10, completion_tokens: 5 } },
170+
];
171+
172+
mockCreate.mockResolvedValueOnce(
173+
(async function* () {
174+
for (const chunk of mockStream) {
175+
yield chunk;
176+
}
177+
})()
178+
);
179+
180+
const generator = handler.createMessage(systemPrompt, messages);
181+
const results = [];
182+
for await (const result of generator) {
183+
results.push(result);
184+
}
185+
186+
expect(results).toEqual([
187+
{ type: 'text', text: 'Hello' },
188+
{ type: 'text', text: ' there' },
189+
{ type: 'text', text: '!' },
190+
{ type: 'usage', inputTokens: 10, outputTokens: 5 },
191+
]);
192+
193+
expect(mockCreate).toHaveBeenCalledWith({
194+
model: 'gpt-4o',
195+
temperature: 0,
196+
messages: [
197+
{ role: 'system', content: systemPrompt },
198+
{ role: 'user', content: 'Hello!' },
199+
],
200+
stream: true,
201+
stream_options: { include_usage: true },
202+
});
203+
});
204+
205+
it('should handle empty delta content', async () => {
206+
const mockStream = [
207+
{ choices: [{ delta: {} }], usage: null },
208+
{ choices: [{ delta: { content: null } }], usage: null },
209+
{ choices: [{ delta: { content: 'Hello' } }], usage: { prompt_tokens: 10, completion_tokens: 5 } },
210+
];
211+
212+
mockCreate.mockResolvedValueOnce(
213+
(async function* () {
214+
for (const chunk of mockStream) {
215+
yield chunk;
216+
}
217+
})()
218+
);
219+
220+
const generator = handler.createMessage(systemPrompt, messages);
221+
const results = [];
222+
for await (const result of generator) {
223+
results.push(result);
224+
}
225+
226+
expect(results).toEqual([
227+
{ type: 'text', text: 'Hello' },
228+
{ type: 'usage', inputTokens: 10, outputTokens: 5 },
229+
]);
230+
});
121231
});
122232

123233
describe('completePrompt', () => {
@@ -206,4 +316,4 @@ describe('OpenAiNativeHandler', () => {
206316
expect(modelInfo.info).toBeDefined();
207317
});
208318
});
209-
});
319+
});

0 commit comments

Comments
 (0)