@lowire/loop 0.0.12 → 0.0.13
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/lib/loop.d.ts +2 -2
- package/lib/loop.js +2 -2
- package/lib/providers/anthropic.js +3 -3
- package/lib/providers/google.js +2 -5
- package/lib/providers/openai.js +5 -162
- package/lib/{auth/githubAuth.d.ts → providers/openaiCompletions.d.ts} +5 -1
- package/lib/providers/openaiCompletions.js +182 -0
- package/lib/providers/{github.d.ts → openaiResponses.d.ts} +4 -17
- package/lib/providers/openaiResponses.js +191 -0
- package/lib/providers/registry.d.ts +2 -2
- package/lib/providers/registry.js +6 -9
- package/lib/types.d.ts +5 -2
- package/package.json +1 -1
- package/lib/auth/githubAuth.js +0 -81
- package/lib/providers/github.js +0 -231
package/lib/loop.d.ts
CHANGED
|
@@ -55,8 +55,8 @@ export declare class Loop {
|
|
|
55
55
|
private _provider;
|
|
56
56
|
private _loopOptions;
|
|
57
57
|
private _cacheOutput;
|
|
58
|
-
constructor(
|
|
59
|
-
run(task: string, runOptions?: Omit<LoopOptions, 'model'> & {
|
|
58
|
+
constructor(options: LoopOptions);
|
|
59
|
+
run(task: string, runOptions?: Omit<LoopOptions, 'model' | 'api' | 'apiKey'> & {
|
|
60
60
|
model?: string;
|
|
61
61
|
}): Promise<{
|
|
62
62
|
result?: types.ToolResult;
|
package/lib/loop.js
CHANGED
|
@@ -23,8 +23,8 @@ class Loop {
|
|
|
23
23
|
_provider;
|
|
24
24
|
_loopOptions;
|
|
25
25
|
_cacheOutput = {};
|
|
26
|
-
constructor(
|
|
27
|
-
this._provider = (0, registry_1.getProvider)(
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this._provider = (0, registry_1.getProvider)(options.api);
|
|
28
28
|
this._loopOptions = options;
|
|
29
29
|
}
|
|
30
30
|
async run(task, runOptions = {}) {
|
|
@@ -27,7 +27,7 @@ class Anthropic {
|
|
|
27
27
|
system: systemPrompt(conversation.systemPrompt),
|
|
28
28
|
messages: conversation.messages.map(toAnthropicMessageParts).flat(),
|
|
29
29
|
tools: conversation.tools.map(toAnthropicTool),
|
|
30
|
-
thinking: options.reasoning ? {
|
|
30
|
+
thinking: options.reasoning !== 'none' ? {
|
|
31
31
|
type: 'enabled',
|
|
32
32
|
budget_tokens: options.maxTokens ? Math.round(maxTokens / 10) : 1024,
|
|
33
33
|
} : undefined,
|
|
@@ -44,12 +44,12 @@ exports.Anthropic = Anthropic;
|
|
|
44
44
|
async function create(createParams, options) {
|
|
45
45
|
const headers = {
|
|
46
46
|
'Content-Type': 'application/json',
|
|
47
|
-
'x-api-key':
|
|
47
|
+
'x-api-key': options.apiKey,
|
|
48
48
|
'anthropic-version': '2023-06-01',
|
|
49
49
|
};
|
|
50
50
|
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
51
51
|
options.debug?.('lowire:anthropic')('Request:', JSON.stringify(debugBody, null, 2));
|
|
52
|
-
const response = await fetch(`https://api.anthropic.com/v1/messages`, {
|
|
52
|
+
const response = await fetch(options.apiEndpoint ?? `https://api.anthropic.com/v1/messages`, {
|
|
53
53
|
method: 'POST',
|
|
54
54
|
headers,
|
|
55
55
|
body: JSON.stringify(createParams)
|
package/lib/providers/google.js
CHANGED
|
@@ -44,16 +44,13 @@ class Google {
|
|
|
44
44
|
}
|
|
45
45
|
exports.Google = Google;
|
|
46
46
|
async function create(model, createParams, options) {
|
|
47
|
-
const apiKey = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
48
|
-
if (!apiKey)
|
|
49
|
-
throw new Error('GEMINI_API_KEY or GOOGLE_API_KEY environment variable is required');
|
|
50
47
|
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
51
48
|
options.debug?.('lowire:google')('Request:', JSON.stringify(debugBody, null, 2));
|
|
52
|
-
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
49
|
+
const response = await fetch(options.apiEndpoint ?? `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
53
50
|
method: 'POST',
|
|
54
51
|
headers: {
|
|
55
52
|
'Content-Type': 'application/json',
|
|
56
|
-
'x-goog-api-key': apiKey,
|
|
53
|
+
'x-goog-api-key': options.apiKey,
|
|
57
54
|
},
|
|
58
55
|
body: JSON.stringify(createParams)
|
|
59
56
|
});
|
package/lib/providers/openai.js
CHANGED
|
@@ -16,171 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.OpenAI = void 0;
|
|
19
|
+
const openaiCompletions_1 = require("./openaiCompletions");
|
|
20
|
+
const openaiResponses_1 = require("./openaiResponses");
|
|
19
21
|
class OpenAI {
|
|
20
22
|
name = 'openai';
|
|
21
23
|
async complete(conversation, options) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
model: options.model,
|
|
26
|
-
temperature: options.temperature,
|
|
27
|
-
input: inputItems,
|
|
28
|
-
instructions: systemPrompt(conversation.systemPrompt),
|
|
29
|
-
tools: tools.length > 0 ? tools : undefined,
|
|
30
|
-
tool_choice: conversation.tools.length > 0 ? 'auto' : undefined,
|
|
31
|
-
parallel_tool_calls: false,
|
|
32
|
-
}, options);
|
|
33
|
-
// Parse response output items
|
|
34
|
-
const result = { role: 'assistant', content: [] };
|
|
35
|
-
for (const item of response.output) {
|
|
36
|
-
if (item.type === 'message' && item.role === 'assistant') {
|
|
37
|
-
result.openaiId = item.id;
|
|
38
|
-
result.openaiStatus = item.status;
|
|
39
|
-
for (const contentPart of item.content) {
|
|
40
|
-
if (contentPart.type === 'output_text') {
|
|
41
|
-
result.content.push({
|
|
42
|
-
type: 'text',
|
|
43
|
-
text: contentPart.text,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
else if (item.type === 'function_call') {
|
|
49
|
-
// Add tool call
|
|
50
|
-
result.content.push(toToolCall(item));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const usage = {
|
|
54
|
-
input: response.usage?.input_tokens ?? 0,
|
|
55
|
-
output: response.usage?.output_tokens ?? 0,
|
|
56
|
-
};
|
|
57
|
-
return { result, usage };
|
|
24
|
+
if (options.apiVersion === 'v1/chat/completions')
|
|
25
|
+
return (0, openaiCompletions_1.complete)(conversation, options);
|
|
26
|
+
return (0, openaiResponses_1.complete)(conversation, options);
|
|
58
27
|
}
|
|
59
28
|
}
|
|
60
29
|
exports.OpenAI = OpenAI;
|
|
61
|
-
async function create(createParams, options) {
|
|
62
|
-
const apiKey = process.env.OPENAI_API_KEY;
|
|
63
|
-
const headers = {
|
|
64
|
-
'Content-Type': 'application/json',
|
|
65
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
66
|
-
'Copilot-Vision-Request': 'true',
|
|
67
|
-
};
|
|
68
|
-
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
69
|
-
options.debug?.('lowire:openai-responses')('Request:', JSON.stringify(debugBody, null, 2));
|
|
70
|
-
const response = await fetch(`https://api.openai.com/v1/responses`, {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers,
|
|
73
|
-
body: JSON.stringify(createParams)
|
|
74
|
-
});
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
options.debug?.('lowire:openai-responses')('Response:', response.status);
|
|
77
|
-
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
78
|
-
}
|
|
79
|
-
const responseBody = await response.json();
|
|
80
|
-
options.debug?.('lowire:openai-responses')('Response:', JSON.stringify(responseBody, null, 2));
|
|
81
|
-
return responseBody;
|
|
82
|
-
}
|
|
83
|
-
function toResultContentPart(part) {
|
|
84
|
-
if (part.type === 'text') {
|
|
85
|
-
return {
|
|
86
|
-
type: 'input_text',
|
|
87
|
-
text: part.text,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (part.type === 'image') {
|
|
91
|
-
return {
|
|
92
|
-
type: 'input_image',
|
|
93
|
-
image_url: `data:${part.mimeType};base64,${part.data}`,
|
|
94
|
-
detail: 'auto',
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
throw new Error(`Cannot convert content part of type ${part.type} to response content part`);
|
|
98
|
-
}
|
|
99
|
-
function toResponseInputItems(message) {
|
|
100
|
-
if (message.role === 'user') {
|
|
101
|
-
return [{
|
|
102
|
-
type: 'message',
|
|
103
|
-
role: 'user',
|
|
104
|
-
content: message.content
|
|
105
|
-
}];
|
|
106
|
-
}
|
|
107
|
-
if (message.role === 'assistant') {
|
|
108
|
-
const textParts = message.content.filter(part => part.type === 'text');
|
|
109
|
-
const toolCallParts = message.content.filter(part => part.type === 'tool_call');
|
|
110
|
-
const items = [];
|
|
111
|
-
// Add assistant message with text content
|
|
112
|
-
if (textParts.length > 0) {
|
|
113
|
-
const outputMessage = {
|
|
114
|
-
id: message.openaiId,
|
|
115
|
-
status: message.openaiStatus,
|
|
116
|
-
type: 'message',
|
|
117
|
-
role: 'assistant',
|
|
118
|
-
content: textParts.map(part => ({
|
|
119
|
-
type: 'output_text',
|
|
120
|
-
text: part.text,
|
|
121
|
-
annotations: [],
|
|
122
|
-
logprobs: []
|
|
123
|
-
}))
|
|
124
|
-
};
|
|
125
|
-
items.push(outputMessage);
|
|
126
|
-
}
|
|
127
|
-
if (message.toolError) {
|
|
128
|
-
items.push({
|
|
129
|
-
type: 'message',
|
|
130
|
-
role: 'user',
|
|
131
|
-
content: message.toolError
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
items.push(...toolCallParts.map(toFunctionToolCall).flat());
|
|
135
|
-
return items;
|
|
136
|
-
}
|
|
137
|
-
throw new Error(`Unsupported message role: ${message.role}`);
|
|
138
|
-
}
|
|
139
|
-
function toOpenAIFunctionTool(tool) {
|
|
140
|
-
return {
|
|
141
|
-
type: 'function',
|
|
142
|
-
name: tool.name,
|
|
143
|
-
description: tool.description ?? null,
|
|
144
|
-
parameters: tool.inputSchema,
|
|
145
|
-
strict: null,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function toFunctionToolCall(toolCall) {
|
|
149
|
-
const result = [{
|
|
150
|
-
type: 'function_call',
|
|
151
|
-
call_id: toolCall.id,
|
|
152
|
-
name: toolCall.name,
|
|
153
|
-
arguments: JSON.stringify(toolCall.arguments),
|
|
154
|
-
id: toolCall.openaiId,
|
|
155
|
-
status: toolCall.openaiStatus,
|
|
156
|
-
}];
|
|
157
|
-
if (toolCall.result) {
|
|
158
|
-
result.push({
|
|
159
|
-
type: 'function_call_output',
|
|
160
|
-
call_id: toolCall.id,
|
|
161
|
-
output: toolCall.result.content.map(toResultContentPart),
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
function toToolCall(functionCall) {
|
|
167
|
-
return {
|
|
168
|
-
type: 'tool_call',
|
|
169
|
-
name: functionCall.name,
|
|
170
|
-
arguments: JSON.parse(functionCall.arguments),
|
|
171
|
-
id: functionCall.call_id,
|
|
172
|
-
openaiId: functionCall.id,
|
|
173
|
-
openaiStatus: functionCall.status,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const systemPrompt = (prompt) => `
|
|
177
|
-
### System instructions
|
|
178
|
-
|
|
179
|
-
${prompt}
|
|
180
|
-
|
|
181
|
-
### Tool calling instructions
|
|
182
|
-
- Make sure every message contains a tool call.
|
|
183
|
-
- When you use a tool, you may provide a brief thought or explanation in the content field
|
|
184
|
-
immediately before the tool_call. Do not split this into separate messages.
|
|
185
|
-
- Every reply must include a tool call.
|
|
186
|
-
`;
|
|
@@ -13,4 +13,8 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
import type * as types from '../types';
|
|
17
|
+
export declare function complete(conversation: types.Conversation, options: types.CompletionOptions): Promise<{
|
|
18
|
+
result: types.AssistantMessage;
|
|
19
|
+
usage: types.Usage;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.complete = complete;
|
|
19
|
+
async function complete(conversation, options) {
|
|
20
|
+
// Convert generic messages to OpenAI format
|
|
21
|
+
const systemMessage = {
|
|
22
|
+
role: 'system',
|
|
23
|
+
content: systemPrompt(conversation.systemPrompt)
|
|
24
|
+
};
|
|
25
|
+
const openaiMessages = [systemMessage, ...conversation.messages.map(toCompletionsMessages).flat()];
|
|
26
|
+
const openaiTools = conversation.tools.map(t => toCompletionsTool(t));
|
|
27
|
+
const response = await create({
|
|
28
|
+
model: options.model,
|
|
29
|
+
max_tokens: options.maxTokens,
|
|
30
|
+
temperature: options.temperature,
|
|
31
|
+
messages: openaiMessages,
|
|
32
|
+
tools: openaiTools,
|
|
33
|
+
tool_choice: conversation.tools.length > 0 ? 'auto' : undefined,
|
|
34
|
+
reasoning_effort: toCompletionsReasoning(options.reasoning),
|
|
35
|
+
parallel_tool_calls: false,
|
|
36
|
+
}, options);
|
|
37
|
+
if (!response || !response.choices.length)
|
|
38
|
+
throw new Error('Failed to get response from OpenAI completions');
|
|
39
|
+
const result = { role: 'assistant', content: [] };
|
|
40
|
+
for (const choice of response.choices) {
|
|
41
|
+
const message = choice.message;
|
|
42
|
+
if (message.content)
|
|
43
|
+
result.content.push({ type: 'text', text: message.content });
|
|
44
|
+
for (const entry of message.tool_calls || []) {
|
|
45
|
+
if (entry.type !== 'function')
|
|
46
|
+
continue;
|
|
47
|
+
result.content.push(toToolCall(entry));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const usage = {
|
|
51
|
+
input: response.usage?.prompt_tokens ?? 0,
|
|
52
|
+
output: response.usage?.completion_tokens ?? 0,
|
|
53
|
+
};
|
|
54
|
+
return { result, usage };
|
|
55
|
+
}
|
|
56
|
+
async function create(createParams, options) {
|
|
57
|
+
const headers = {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'Authorization': `Bearer ${options.apiKey}`,
|
|
60
|
+
};
|
|
61
|
+
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
62
|
+
options.debug?.('lowire:openai')('Request:', JSON.stringify(debugBody, null, 2));
|
|
63
|
+
const response = await fetch(options.apiEndpoint ?? `https://api.openai.com/v1/chat/completions`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers,
|
|
66
|
+
body: JSON.stringify(createParams),
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
options.debug?.('lowire:openai')('Response:', response.status);
|
|
70
|
+
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
71
|
+
}
|
|
72
|
+
const responseBody = await response.json();
|
|
73
|
+
options.debug?.('lowire:openai')('Response:', JSON.stringify(responseBody, null, 2));
|
|
74
|
+
return responseBody;
|
|
75
|
+
}
|
|
76
|
+
function toCopilotResultContentPart(part) {
|
|
77
|
+
if (part.type === 'text') {
|
|
78
|
+
return {
|
|
79
|
+
type: 'text',
|
|
80
|
+
text: part.text,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (part.type === 'image') {
|
|
84
|
+
return {
|
|
85
|
+
type: 'image_url',
|
|
86
|
+
image_url: {
|
|
87
|
+
url: `data:${part.mimeType};base64,${part.data}`,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Cannot convert content part of type ${part.type} to text content part`);
|
|
92
|
+
}
|
|
93
|
+
function toCompletionsMessages(message) {
|
|
94
|
+
if (message.role === 'user') {
|
|
95
|
+
return [{
|
|
96
|
+
role: 'user',
|
|
97
|
+
content: message.content
|
|
98
|
+
}];
|
|
99
|
+
}
|
|
100
|
+
if (message.role === 'assistant') {
|
|
101
|
+
const assistantMessage = {
|
|
102
|
+
role: 'assistant'
|
|
103
|
+
};
|
|
104
|
+
const textParts = message.content.filter(part => part.type === 'text');
|
|
105
|
+
const toolCallParts = message.content.filter(part => part.type === 'tool_call');
|
|
106
|
+
if (textParts.length === 1)
|
|
107
|
+
assistantMessage.content = textParts[0].text;
|
|
108
|
+
else
|
|
109
|
+
assistantMessage.content = textParts;
|
|
110
|
+
const toolCalls = [];
|
|
111
|
+
const toolResultMessages = [];
|
|
112
|
+
for (const toolCall of toolCallParts) {
|
|
113
|
+
toolCalls.push({
|
|
114
|
+
id: toolCall.id,
|
|
115
|
+
type: 'function',
|
|
116
|
+
function: {
|
|
117
|
+
name: toolCall.name,
|
|
118
|
+
arguments: JSON.stringify(toolCall.arguments)
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
if (toolCall.result) {
|
|
122
|
+
toolResultMessages.push({
|
|
123
|
+
role: 'tool',
|
|
124
|
+
tool_call_id: toolCall.id,
|
|
125
|
+
content: toolCall.result.content.map(toCopilotResultContentPart),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (toolCalls.length > 0)
|
|
130
|
+
assistantMessage.tool_calls = toolCalls;
|
|
131
|
+
if (message.toolError) {
|
|
132
|
+
toolResultMessages.push({
|
|
133
|
+
role: 'user',
|
|
134
|
+
content: [{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: message.toolError,
|
|
137
|
+
}]
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return [assistantMessage, ...toolResultMessages];
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Unsupported message role: ${message.role}`);
|
|
143
|
+
}
|
|
144
|
+
function toCompletionsTool(tool) {
|
|
145
|
+
return {
|
|
146
|
+
type: 'function',
|
|
147
|
+
function: {
|
|
148
|
+
name: tool.name,
|
|
149
|
+
description: tool.description,
|
|
150
|
+
parameters: tool.inputSchema,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function toToolCall(entry) {
|
|
155
|
+
return {
|
|
156
|
+
type: 'tool_call',
|
|
157
|
+
name: entry.type === 'function' ? entry.function.name : entry.custom.name,
|
|
158
|
+
arguments: JSON.parse(entry.type === 'function' ? entry.function.arguments : entry.custom.input),
|
|
159
|
+
id: entry.id,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function toCompletionsReasoning(reasoning) {
|
|
163
|
+
switch (reasoning) {
|
|
164
|
+
case 'none':
|
|
165
|
+
return 'none';
|
|
166
|
+
case 'medium':
|
|
167
|
+
return 'medium';
|
|
168
|
+
case 'high':
|
|
169
|
+
return 'high';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const systemPrompt = (prompt) => `
|
|
173
|
+
### System instructions
|
|
174
|
+
|
|
175
|
+
${prompt}
|
|
176
|
+
|
|
177
|
+
### Tool calling instructions
|
|
178
|
+
- Make sure every message contains a tool call.
|
|
179
|
+
- When you use a tool, you may provide a brief thought or explanation in the content field
|
|
180
|
+
immediately before the tool_call. Do not split this into separate messages.
|
|
181
|
+
- Every reply must include a tool call.
|
|
182
|
+
`;
|
|
@@ -14,20 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import type * as types from '../types';
|
|
17
|
-
export declare
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
complete(conversation: types.Conversation, options: types.CompletionOptions): Promise<{
|
|
22
|
-
result: types.AssistantMessage;
|
|
23
|
-
usage: types.Usage;
|
|
24
|
-
}>;
|
|
25
|
-
}
|
|
26
|
-
export declare const kEditorHeaders: {
|
|
27
|
-
'Editor-Version': string;
|
|
28
|
-
'Editor-Plugin-Version': string;
|
|
29
|
-
'User-Agent': string;
|
|
30
|
-
Accept: string;
|
|
31
|
-
'Content-Type': string;
|
|
32
|
-
'Copilot-Vision-Request': string;
|
|
33
|
-
};
|
|
17
|
+
export declare function complete(conversation: types.Conversation, options: types.CompletionOptions): Promise<{
|
|
18
|
+
result: types.AssistantMessage;
|
|
19
|
+
usage: types.Usage;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.complete = complete;
|
|
19
|
+
async function complete(conversation, options) {
|
|
20
|
+
const inputItems = conversation.messages.map(toResponseInputItems).flat();
|
|
21
|
+
const tools = conversation.tools.map(toOpenAIFunctionTool);
|
|
22
|
+
const response = await create({
|
|
23
|
+
model: options.model,
|
|
24
|
+
temperature: options.temperature,
|
|
25
|
+
input: inputItems,
|
|
26
|
+
instructions: systemPrompt(conversation.systemPrompt),
|
|
27
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
28
|
+
tool_choice: conversation.tools.length > 0 ? 'auto' : undefined,
|
|
29
|
+
parallel_tool_calls: false,
|
|
30
|
+
reasoning: toOpenAIReasoning(options.reasoning),
|
|
31
|
+
}, options);
|
|
32
|
+
// Parse response output items
|
|
33
|
+
const result = { role: 'assistant', content: [] };
|
|
34
|
+
for (const item of response.output) {
|
|
35
|
+
if (item.type === 'message' && item.role === 'assistant') {
|
|
36
|
+
result.openaiId = item.id;
|
|
37
|
+
result.openaiStatus = item.status;
|
|
38
|
+
for (const contentPart of item.content) {
|
|
39
|
+
if (contentPart.type === 'output_text') {
|
|
40
|
+
result.content.push({
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: contentPart.text,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (item.type === 'function_call') {
|
|
48
|
+
// Add tool call
|
|
49
|
+
result.content.push(toToolCall(item));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const usage = {
|
|
53
|
+
input: response.usage?.input_tokens ?? 0,
|
|
54
|
+
output: response.usage?.output_tokens ?? 0,
|
|
55
|
+
};
|
|
56
|
+
return { result, usage };
|
|
57
|
+
}
|
|
58
|
+
async function create(createParams, options) {
|
|
59
|
+
const headers = {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'Authorization': `Bearer ${options.apiKey}`,
|
|
62
|
+
};
|
|
63
|
+
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
64
|
+
options.debug?.('lowire:openai-responses')('Request:', JSON.stringify(debugBody, null, 2));
|
|
65
|
+
const response = await fetch(options.apiEndpoint ?? `https://api.openai.com/v1/responses`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers,
|
|
68
|
+
body: JSON.stringify(createParams)
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
options.debug?.('lowire:openai-responses')('Response:', response.status);
|
|
72
|
+
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
73
|
+
}
|
|
74
|
+
const responseBody = await response.json();
|
|
75
|
+
options.debug?.('lowire:openai-responses')('Response:', JSON.stringify(responseBody, null, 2));
|
|
76
|
+
return responseBody;
|
|
77
|
+
}
|
|
78
|
+
function toResultContentPart(part) {
|
|
79
|
+
if (part.type === 'text') {
|
|
80
|
+
return {
|
|
81
|
+
type: 'input_text',
|
|
82
|
+
text: part.text,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (part.type === 'image') {
|
|
86
|
+
return {
|
|
87
|
+
type: 'input_image',
|
|
88
|
+
image_url: `data:${part.mimeType};base64,${part.data}`,
|
|
89
|
+
detail: 'auto',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Cannot convert content part of type ${part.type} to response content part`);
|
|
93
|
+
}
|
|
94
|
+
function toResponseInputItems(message) {
|
|
95
|
+
if (message.role === 'user') {
|
|
96
|
+
return [{
|
|
97
|
+
type: 'message',
|
|
98
|
+
role: 'user',
|
|
99
|
+
content: message.content
|
|
100
|
+
}];
|
|
101
|
+
}
|
|
102
|
+
if (message.role === 'assistant') {
|
|
103
|
+
const textParts = message.content.filter(part => part.type === 'text');
|
|
104
|
+
const toolCallParts = message.content.filter(part => part.type === 'tool_call');
|
|
105
|
+
const items = [];
|
|
106
|
+
// Add assistant message with text content
|
|
107
|
+
if (textParts.length > 0) {
|
|
108
|
+
const outputMessage = {
|
|
109
|
+
id: message.openaiId,
|
|
110
|
+
status: message.openaiStatus,
|
|
111
|
+
type: 'message',
|
|
112
|
+
role: 'assistant',
|
|
113
|
+
content: textParts.map(part => ({
|
|
114
|
+
type: 'output_text',
|
|
115
|
+
text: part.text,
|
|
116
|
+
annotations: [],
|
|
117
|
+
logprobs: []
|
|
118
|
+
}))
|
|
119
|
+
};
|
|
120
|
+
items.push(outputMessage);
|
|
121
|
+
}
|
|
122
|
+
if (message.toolError) {
|
|
123
|
+
items.push({
|
|
124
|
+
type: 'message',
|
|
125
|
+
role: 'user',
|
|
126
|
+
content: message.toolError
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
items.push(...toolCallParts.map(toFunctionToolCall).flat());
|
|
130
|
+
return items;
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Unsupported message role: ${message.role}`);
|
|
133
|
+
}
|
|
134
|
+
function toOpenAIFunctionTool(tool) {
|
|
135
|
+
return {
|
|
136
|
+
type: 'function',
|
|
137
|
+
name: tool.name,
|
|
138
|
+
description: tool.description ?? null,
|
|
139
|
+
parameters: tool.inputSchema,
|
|
140
|
+
strict: null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function toFunctionToolCall(toolCall) {
|
|
144
|
+
const result = [{
|
|
145
|
+
type: 'function_call',
|
|
146
|
+
call_id: toolCall.id,
|
|
147
|
+
name: toolCall.name,
|
|
148
|
+
arguments: JSON.stringify(toolCall.arguments),
|
|
149
|
+
id: toolCall.openaiId,
|
|
150
|
+
status: toolCall.openaiStatus,
|
|
151
|
+
}];
|
|
152
|
+
if (toolCall.result) {
|
|
153
|
+
result.push({
|
|
154
|
+
type: 'function_call_output',
|
|
155
|
+
call_id: toolCall.id,
|
|
156
|
+
output: toolCall.result.content.map(toResultContentPart),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function toToolCall(functionCall) {
|
|
162
|
+
return {
|
|
163
|
+
type: 'tool_call',
|
|
164
|
+
name: functionCall.name,
|
|
165
|
+
arguments: JSON.parse(functionCall.arguments),
|
|
166
|
+
id: functionCall.call_id,
|
|
167
|
+
openaiId: functionCall.id,
|
|
168
|
+
openaiStatus: functionCall.status,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function toOpenAIReasoning(reasoning) {
|
|
172
|
+
switch (reasoning) {
|
|
173
|
+
case 'none':
|
|
174
|
+
return { effort: 'none' };
|
|
175
|
+
case 'medium':
|
|
176
|
+
return { effort: 'medium' };
|
|
177
|
+
case 'high':
|
|
178
|
+
return { effort: 'high' };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const systemPrompt = (prompt) => `
|
|
182
|
+
### System instructions
|
|
183
|
+
|
|
184
|
+
${prompt}
|
|
185
|
+
|
|
186
|
+
### Tool calling instructions
|
|
187
|
+
- Make sure every message contains a tool call.
|
|
188
|
+
- When you use a tool, you may provide a brief thought or explanation in the content field
|
|
189
|
+
immediately before the tool_call. Do not split this into separate messages.
|
|
190
|
+
- Every reply must include a tool call.
|
|
191
|
+
`;
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import * as types from '../types';
|
|
17
|
-
export declare function getProvider(
|
|
16
|
+
import type * as types from '../types';
|
|
17
|
+
export declare function getProvider(api: 'openai' | 'anthropic' | 'google'): types.Provider;
|
|
@@ -16,18 +16,15 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.getProvider = getProvider;
|
|
19
|
-
const openai_1 = require("./openai");
|
|
20
|
-
const github_1 = require("./github");
|
|
21
19
|
const anthropic_1 = require("./anthropic");
|
|
22
20
|
const google_1 = require("./google");
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const openai_1 = require("./openai");
|
|
22
|
+
function getProvider(api) {
|
|
23
|
+
if (api === 'openai')
|
|
25
24
|
return new openai_1.OpenAI();
|
|
26
|
-
if (
|
|
27
|
-
return new github_1.Github();
|
|
28
|
-
if (loopName === 'anthropic')
|
|
25
|
+
if (api === 'anthropic')
|
|
29
26
|
return new anthropic_1.Anthropic();
|
|
30
|
-
if (
|
|
27
|
+
if (api === 'google')
|
|
31
28
|
return new google_1.Google();
|
|
32
|
-
throw new Error(`Unknown loop LLM: ${
|
|
29
|
+
throw new Error(`Unknown loop LLM: ${api}`);
|
|
33
30
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -46,7 +46,6 @@ export type TextContentPart = {
|
|
|
46
46
|
type: 'text';
|
|
47
47
|
text: string;
|
|
48
48
|
googleThoughtSignature?: string;
|
|
49
|
-
copilotToolCallId?: string;
|
|
50
49
|
};
|
|
51
50
|
export type ThinkingContentPart = {
|
|
52
51
|
type: 'thinking';
|
|
@@ -91,9 +90,13 @@ export type Conversation = {
|
|
|
91
90
|
};
|
|
92
91
|
export type Debug = (category: string) => (...args: any[]) => void;
|
|
93
92
|
export type CompletionOptions = {
|
|
93
|
+
api: 'openai' | 'anthropic' | 'google';
|
|
94
|
+
apiEndpoint?: string;
|
|
95
|
+
apiKey: string;
|
|
96
|
+
apiVersion?: string;
|
|
94
97
|
model: string;
|
|
95
98
|
maxTokens?: number;
|
|
96
|
-
reasoning?:
|
|
99
|
+
reasoning?: 'none' | 'medium' | 'high';
|
|
97
100
|
temperature?: number;
|
|
98
101
|
debug?: Debug;
|
|
99
102
|
};
|
package/package.json
CHANGED
package/lib/auth/githubAuth.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) Microsoft Corporation.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
const github_1 = require("../providers/github");
|
|
19
|
-
/* eslint-disable no-console */
|
|
20
|
-
// The Client ID for VS Code. This is public knowledge but technically "internal" to VS Code.
|
|
21
|
-
// Using this ID allows the script to impersonate VS Code to get the correct scopes.
|
|
22
|
-
const CLIENT_ID = 'Iv1.b507a08c87ecfe98';
|
|
23
|
-
const SCOPE = 'read:user share:copilot';
|
|
24
|
-
async function initiateDeviceFlow() {
|
|
25
|
-
const response = await fetch('https://github.com/login/device/code', {
|
|
26
|
-
method: 'POST',
|
|
27
|
-
headers: github_1.kEditorHeaders,
|
|
28
|
-
body: JSON.stringify({
|
|
29
|
-
client_id: CLIENT_ID,
|
|
30
|
-
scope: SCOPE
|
|
31
|
-
})
|
|
32
|
-
});
|
|
33
|
-
return await response.json();
|
|
34
|
-
}
|
|
35
|
-
async function pollForToken(deviceCode, interval) {
|
|
36
|
-
console.log('Waiting for user authorization...');
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
const pollInterval = setInterval(async () => {
|
|
39
|
-
try {
|
|
40
|
-
const response = await fetch('https://github.com/login/oauth/access_token', {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: github_1.kEditorHeaders,
|
|
43
|
-
body: JSON.stringify({
|
|
44
|
-
client_id: CLIENT_ID,
|
|
45
|
-
device_code: deviceCode,
|
|
46
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
47
|
-
})
|
|
48
|
-
});
|
|
49
|
-
const data = await response.json();
|
|
50
|
-
if ('access_token' in data) {
|
|
51
|
-
clearInterval(pollInterval);
|
|
52
|
-
resolve(data.access_token);
|
|
53
|
-
}
|
|
54
|
-
else if (data.error === 'authorization_pending') {
|
|
55
|
-
process.stdout.write('.');
|
|
56
|
-
}
|
|
57
|
-
else if (data.error === 'slow_down') {
|
|
58
|
-
console.log('(slow down)');
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
clearInterval(pollInterval);
|
|
62
|
-
reject(new Error(data.error_description || data.error));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
clearInterval(pollInterval);
|
|
67
|
-
reject(error);
|
|
68
|
-
}
|
|
69
|
-
}, (interval + 1) * 1000);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
void (async () => {
|
|
73
|
-
const deviceData = await initiateDeviceFlow();
|
|
74
|
-
console.log('\n**************************************************');
|
|
75
|
-
console.log(`Please go to: ${deviceData.verification_uri}`);
|
|
76
|
-
console.log(`And enter code: ${deviceData.user_code}`);
|
|
77
|
-
console.log('**************************************************\n');
|
|
78
|
-
const oauthToken = await pollForToken(deviceData.device_code, deviceData.interval);
|
|
79
|
-
console.log('\n✔ Authentication successful!');
|
|
80
|
-
console.log(`COPILOT_API_KEY=${oauthToken}`);
|
|
81
|
-
})();
|
package/lib/providers/github.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) Microsoft Corporation.
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.kEditorHeaders = exports.Github = void 0;
|
|
19
|
-
class Github {
|
|
20
|
-
name = 'github';
|
|
21
|
-
_apiKey;
|
|
22
|
-
async _bearer() {
|
|
23
|
-
if (!this._apiKey)
|
|
24
|
-
this._apiKey = await getCopilotToken();
|
|
25
|
-
return this._apiKey;
|
|
26
|
-
}
|
|
27
|
-
async complete(conversation, options) {
|
|
28
|
-
// Convert generic messages to OpenAI format
|
|
29
|
-
const systemMessage = {
|
|
30
|
-
role: 'system',
|
|
31
|
-
content: systemPrompt(conversation.systemPrompt)
|
|
32
|
-
};
|
|
33
|
-
const openaiMessages = [systemMessage, ...conversation.messages.map(toCopilotMessages).flat()];
|
|
34
|
-
const openaiTools = conversation.tools.map(t => toCopilotTool(t));
|
|
35
|
-
const bearer = await this._bearer();
|
|
36
|
-
let response;
|
|
37
|
-
// Github provider is unreliable, retry up to 3 times.
|
|
38
|
-
for (let i = 0; i < 3; ++i) {
|
|
39
|
-
response = await create({
|
|
40
|
-
model: options.model,
|
|
41
|
-
max_tokens: options.maxTokens,
|
|
42
|
-
temperature: options.temperature,
|
|
43
|
-
messages: openaiMessages,
|
|
44
|
-
tools: openaiTools,
|
|
45
|
-
tool_choice: conversation.tools.length > 0 ? 'auto' : undefined,
|
|
46
|
-
reasoning_effort: options.reasoning ? 'medium' : undefined,
|
|
47
|
-
parallel_tool_calls: false,
|
|
48
|
-
}, bearer, options);
|
|
49
|
-
if (response.choices.length)
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
if (!response || !response.choices.length)
|
|
53
|
-
throw new Error('Failed to get response from GitHub Copilot');
|
|
54
|
-
const result = { role: 'assistant', content: [] };
|
|
55
|
-
for (const choice of response.choices) {
|
|
56
|
-
const message = choice.message;
|
|
57
|
-
if (message.content)
|
|
58
|
-
result.content.push({ type: 'text', text: message.content });
|
|
59
|
-
for (const entry of message.tool_calls || []) {
|
|
60
|
-
if (entry.type !== 'function')
|
|
61
|
-
continue;
|
|
62
|
-
const { toolCall, intent } = toToolCall(entry);
|
|
63
|
-
if (intent)
|
|
64
|
-
result.content.push({ type: 'text', text: intent, copilotToolCallId: toolCall.id });
|
|
65
|
-
result.content.push(toolCall);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const usage = {
|
|
69
|
-
input: response.usage?.prompt_tokens ?? 0,
|
|
70
|
-
output: response.usage?.completion_tokens ?? 0,
|
|
71
|
-
};
|
|
72
|
-
return { result, usage };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
exports.Github = Github;
|
|
76
|
-
async function create(createParams, bearer, options) {
|
|
77
|
-
const headers = {
|
|
78
|
-
'Authorization': `Bearer ${bearer}`,
|
|
79
|
-
...exports.kEditorHeaders,
|
|
80
|
-
};
|
|
81
|
-
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
82
|
-
options.debug?.('lowire:github')('Request:', JSON.stringify(debugBody, null, 2));
|
|
83
|
-
const response = await fetch(`https://api.githubcopilot.com/chat/completions`, {
|
|
84
|
-
method: 'POST',
|
|
85
|
-
headers,
|
|
86
|
-
body: JSON.stringify(createParams),
|
|
87
|
-
});
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
options.debug?.('lowire:github')('Response:', response.status);
|
|
90
|
-
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
91
|
-
}
|
|
92
|
-
const responseBody = await response.json();
|
|
93
|
-
options.debug?.('lowire:github')('Response:', JSON.stringify(responseBody, null, 2));
|
|
94
|
-
return responseBody;
|
|
95
|
-
}
|
|
96
|
-
function toCopilotResultContentPart(part) {
|
|
97
|
-
if (part.type === 'text') {
|
|
98
|
-
return {
|
|
99
|
-
type: 'text',
|
|
100
|
-
text: part.text,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if (part.type === 'image') {
|
|
104
|
-
return {
|
|
105
|
-
type: 'image_url',
|
|
106
|
-
image_url: {
|
|
107
|
-
url: `data:${part.mimeType};base64,${part.data}`,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
throw new Error(`Cannot convert content part of type ${part.type} to text content part`);
|
|
112
|
-
}
|
|
113
|
-
function toCopilotMessages(message) {
|
|
114
|
-
if (message.role === 'user') {
|
|
115
|
-
return [{
|
|
116
|
-
role: 'user',
|
|
117
|
-
content: message.content
|
|
118
|
-
}];
|
|
119
|
-
}
|
|
120
|
-
if (message.role === 'assistant') {
|
|
121
|
-
const assistantMessage = {
|
|
122
|
-
role: 'assistant'
|
|
123
|
-
};
|
|
124
|
-
const toolIntents = new Map();
|
|
125
|
-
for (const part of message.content) {
|
|
126
|
-
if (part.type === 'text' && part.copilotToolCallId)
|
|
127
|
-
toolIntents.set(part.copilotToolCallId, part.text);
|
|
128
|
-
}
|
|
129
|
-
const textParts = message.content.filter(part => part.type === 'text' && !part.copilotToolCallId);
|
|
130
|
-
const toolCallParts = message.content.filter(part => part.type === 'tool_call');
|
|
131
|
-
if (textParts.length === 1)
|
|
132
|
-
assistantMessage.content = textParts[0].text;
|
|
133
|
-
else
|
|
134
|
-
assistantMessage.content = textParts;
|
|
135
|
-
const toolCalls = [];
|
|
136
|
-
const toolResultMessages = [];
|
|
137
|
-
for (const toolCall of toolCallParts) {
|
|
138
|
-
const args = { ...toolCall.arguments };
|
|
139
|
-
if (toolIntents.has(toolCall.id))
|
|
140
|
-
args['_intent'] = toolIntents.get(toolCall.id);
|
|
141
|
-
toolCalls.push({
|
|
142
|
-
id: toolCall.id,
|
|
143
|
-
type: 'function',
|
|
144
|
-
function: {
|
|
145
|
-
name: toolCall.name,
|
|
146
|
-
arguments: JSON.stringify(args)
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
if (toolCall.result) {
|
|
150
|
-
toolResultMessages.push({
|
|
151
|
-
role: 'tool',
|
|
152
|
-
tool_call_id: toolCall.id,
|
|
153
|
-
content: toolCall.result.content.map(toCopilotResultContentPart),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (toolCalls.length > 0)
|
|
158
|
-
assistantMessage.tool_calls = toolCalls;
|
|
159
|
-
if (message.toolError) {
|
|
160
|
-
toolResultMessages.push({
|
|
161
|
-
role: 'user',
|
|
162
|
-
content: [{
|
|
163
|
-
type: 'text',
|
|
164
|
-
text: message.toolError,
|
|
165
|
-
}]
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return [assistantMessage, ...toolResultMessages];
|
|
169
|
-
}
|
|
170
|
-
throw new Error(`Unsupported message role: ${message.role}`);
|
|
171
|
-
}
|
|
172
|
-
// Copilot endpoint does not reply with content+tool_call, it instead
|
|
173
|
-
// replies with the content and expects continuation. I.e. instead of navigating
|
|
174
|
-
// to a page it will reply with "Navigating to <url>" w/o tool call. Mitigate it
|
|
175
|
-
// via injecting a tool call intent and then converting it into the assistant
|
|
176
|
-
// message content.
|
|
177
|
-
function toCopilotTool(tool) {
|
|
178
|
-
const parameters = { ...tool.inputSchema };
|
|
179
|
-
parameters.properties = {
|
|
180
|
-
_intent: { type: 'string', description: 'Describe the intent of this tool call' },
|
|
181
|
-
...parameters.properties || {},
|
|
182
|
-
};
|
|
183
|
-
return {
|
|
184
|
-
type: 'function',
|
|
185
|
-
function: {
|
|
186
|
-
name: tool.name,
|
|
187
|
-
description: tool.description,
|
|
188
|
-
parameters,
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function toToolCall(entry) {
|
|
193
|
-
const toolCall = {
|
|
194
|
-
type: 'tool_call',
|
|
195
|
-
name: entry.type === 'function' ? entry.function.name : entry.custom.name,
|
|
196
|
-
arguments: JSON.parse(entry.type === 'function' ? entry.function.arguments : entry.custom.input),
|
|
197
|
-
id: entry.id,
|
|
198
|
-
};
|
|
199
|
-
const intent = toolCall.arguments['_intent'];
|
|
200
|
-
delete toolCall.arguments['_intent'];
|
|
201
|
-
return { toolCall, intent };
|
|
202
|
-
}
|
|
203
|
-
const systemPrompt = (prompt) => `
|
|
204
|
-
### System instructions
|
|
205
|
-
|
|
206
|
-
${prompt}
|
|
207
|
-
|
|
208
|
-
### Tool calling instructions
|
|
209
|
-
- Your reply MUST be a tool call and nothing but the tool call.
|
|
210
|
-
- NEVER respond with text content, only tool calls.
|
|
211
|
-
- Do NOT describe your plan, do NOT explain what you are doing, do NOT describe what you see, call tools.
|
|
212
|
-
- Provide thoughts in the '_intent' property of the tool calls instead.
|
|
213
|
-
`;
|
|
214
|
-
exports.kEditorHeaders = {
|
|
215
|
-
'Editor-Version': 'vscode/1.96.0',
|
|
216
|
-
'Editor-Plugin-Version': 'copilot-chat/0.24.0',
|
|
217
|
-
'User-Agent': 'GitHubCopilotChat/0.24.0',
|
|
218
|
-
'Accept': 'application/json',
|
|
219
|
-
'Content-Type': 'application/json',
|
|
220
|
-
'Copilot-Vision-Request': 'true',
|
|
221
|
-
};
|
|
222
|
-
async function getCopilotToken() {
|
|
223
|
-
const response = await fetch('https://api.github.com/copilot_internal/v2/token', {
|
|
224
|
-
method: 'GET',
|
|
225
|
-
headers: { 'Authorization': `token ${process.env.COPILOT_API_KEY}`, ...exports.kEditorHeaders }
|
|
226
|
-
});
|
|
227
|
-
const data = await response.json();
|
|
228
|
-
if (data.token)
|
|
229
|
-
return data.token;
|
|
230
|
-
throw new Error('Failed to get Copilot token');
|
|
231
|
-
}
|