@lowire/loop 0.0.1 → 0.0.3
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 +201 -0
- package/README.md +1 -0
- package/githubAuth.js +17 -0
- package/index.d.ts +19 -0
- package/index.js +18 -0
- package/index.mjs +18 -0
- package/lib/auth/githubAuth.d.ts +16 -0
- package/lib/auth/githubAuth.js +81 -0
- package/lib/cache.d.ts +23 -0
- package/lib/cache.js +57 -0
- package/lib/jsx/jsx-runtime.d.ts +17 -0
- package/lib/jsx/jsx-runtime.js +33 -0
- package/lib/loop.d.ts +38 -0
- package/lib/loop.js +138 -0
- package/lib/mcp/index.d.ts +34 -0
- package/lib/mcp/index.js +109 -0
- package/lib/providers/anthropic.d.ts +23 -0
- package/lib/providers/anthropic.js +195 -0
- package/lib/providers/github.d.ts +33 -0
- package/lib/providers/github.js +229 -0
- package/lib/providers/google.d.ts +23 -0
- package/lib/providers/google.js +204 -0
- package/lib/providers/openai.d.ts +23 -0
- package/lib/providers/openai.js +186 -0
- package/lib/providers/registry.d.ts +17 -0
- package/lib/providers/registry.js +33 -0
- package/lib/summary.d.ts +20 -0
- package/lib/summary.js +56 -0
- package/lib/types.d.ts +114 -0
- package/lib/types.js +17 -0
- package/package.json +21 -7
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
const message = response.choices[0].message;
|
|
56
|
+
if (message.content)
|
|
57
|
+
result.content.push({ type: 'text', text: message.content });
|
|
58
|
+
for (const entry of message.tool_calls || []) {
|
|
59
|
+
if (entry.type !== 'function')
|
|
60
|
+
continue;
|
|
61
|
+
const { toolCall, intent } = toToolCall(entry);
|
|
62
|
+
if (intent)
|
|
63
|
+
result.content.push({ type: 'text', text: intent, copilotToolCallId: toolCall.id });
|
|
64
|
+
result.content.push(toolCall);
|
|
65
|
+
}
|
|
66
|
+
const usage = {
|
|
67
|
+
input: response.usage?.prompt_tokens ?? 0,
|
|
68
|
+
output: response.usage?.completion_tokens ?? 0,
|
|
69
|
+
};
|
|
70
|
+
return { result, usage };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.Github = Github;
|
|
74
|
+
async function create(createParams, bearer, options) {
|
|
75
|
+
const headers = {
|
|
76
|
+
'Authorization': `Bearer ${bearer}`,
|
|
77
|
+
...exports.kEditorHeaders,
|
|
78
|
+
};
|
|
79
|
+
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
80
|
+
options.debug?.('lowire:github')('Request:', JSON.stringify(debugBody, null, 2));
|
|
81
|
+
const response = await fetch(`https://api.githubcopilot.com/chat/completions`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers,
|
|
84
|
+
body: JSON.stringify(createParams),
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
options.debug?.('lowire:github')('Response:', response.status);
|
|
88
|
+
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
89
|
+
}
|
|
90
|
+
const responseBody = await response.json();
|
|
91
|
+
options.debug?.('lowire:github')('Response:', JSON.stringify(responseBody, null, 2));
|
|
92
|
+
return responseBody;
|
|
93
|
+
}
|
|
94
|
+
function toCopilotResultContentPart(part) {
|
|
95
|
+
if (part.type === 'text') {
|
|
96
|
+
return {
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: part.text,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (part.type === 'image') {
|
|
102
|
+
return {
|
|
103
|
+
type: 'image_url',
|
|
104
|
+
image_url: {
|
|
105
|
+
url: `data:${part.mimeType};base64,${part.data}`,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Cannot convert content part of type ${part.type} to text content part`);
|
|
110
|
+
}
|
|
111
|
+
function toCopilotMessages(message) {
|
|
112
|
+
if (message.role === 'user') {
|
|
113
|
+
return [{
|
|
114
|
+
role: 'user',
|
|
115
|
+
content: message.content
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
if (message.role === 'assistant') {
|
|
119
|
+
const assistantMessage = {
|
|
120
|
+
role: 'assistant'
|
|
121
|
+
};
|
|
122
|
+
const toolIntents = new Map();
|
|
123
|
+
for (const part of message.content) {
|
|
124
|
+
if (part.type === 'text' && part.copilotToolCallId)
|
|
125
|
+
toolIntents.set(part.copilotToolCallId, part.text);
|
|
126
|
+
}
|
|
127
|
+
const textParts = message.content.filter(part => part.type === 'text' && !part.copilotToolCallId);
|
|
128
|
+
const toolCallParts = message.content.filter(part => part.type === 'tool_call');
|
|
129
|
+
if (textParts.length === 1)
|
|
130
|
+
assistantMessage.content = textParts[0].text;
|
|
131
|
+
else
|
|
132
|
+
assistantMessage.content = textParts;
|
|
133
|
+
const toolCalls = [];
|
|
134
|
+
const toolResultMessages = [];
|
|
135
|
+
for (const toolCall of toolCallParts) {
|
|
136
|
+
const args = { ...toolCall.arguments };
|
|
137
|
+
if (toolIntents.has(toolCall.id))
|
|
138
|
+
args['_intent'] = toolIntents.get(toolCall.id);
|
|
139
|
+
toolCalls.push({
|
|
140
|
+
id: toolCall.id,
|
|
141
|
+
type: 'function',
|
|
142
|
+
function: {
|
|
143
|
+
name: toolCall.name,
|
|
144
|
+
arguments: JSON.stringify(args)
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
if (toolCall.result) {
|
|
148
|
+
toolResultMessages.push({
|
|
149
|
+
role: 'tool',
|
|
150
|
+
tool_call_id: toolCall.id,
|
|
151
|
+
content: toolCall.result.content.map(toCopilotResultContentPart),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (toolCalls.length > 0)
|
|
156
|
+
assistantMessage.tool_calls = toolCalls;
|
|
157
|
+
if (message.toolError) {
|
|
158
|
+
toolResultMessages.push({
|
|
159
|
+
role: 'user',
|
|
160
|
+
content: [{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: message.toolError,
|
|
163
|
+
}]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return [assistantMessage, ...toolResultMessages];
|
|
167
|
+
}
|
|
168
|
+
throw new Error(`Unsupported message role: ${message.role}`);
|
|
169
|
+
}
|
|
170
|
+
// Copilot endpoint does not reply with content+tool_call, it instead
|
|
171
|
+
// replies with the content and expects continuation. I.e. instead of navigating
|
|
172
|
+
// to a page it will reply with "Navigating to <url>" w/o tool call. Mitigate it
|
|
173
|
+
// via injecting a tool call intent and then converting it into the assistant
|
|
174
|
+
// message content.
|
|
175
|
+
function toCopilotTool(tool) {
|
|
176
|
+
const parameters = { ...tool.inputSchema };
|
|
177
|
+
parameters.properties = {
|
|
178
|
+
_intent: { type: 'string', description: 'Describe the intent of this tool call' },
|
|
179
|
+
...parameters.properties || {},
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
type: 'function',
|
|
183
|
+
function: {
|
|
184
|
+
name: tool.name,
|
|
185
|
+
description: tool.description,
|
|
186
|
+
parameters,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function toToolCall(entry) {
|
|
191
|
+
const toolCall = {
|
|
192
|
+
type: 'tool_call',
|
|
193
|
+
name: entry.type === 'function' ? entry.function.name : entry.custom.name,
|
|
194
|
+
arguments: JSON.parse(entry.type === 'function' ? entry.function.arguments : entry.custom.input),
|
|
195
|
+
id: entry.id,
|
|
196
|
+
};
|
|
197
|
+
const intent = toolCall.arguments['_intent'];
|
|
198
|
+
delete toolCall.arguments['_intent'];
|
|
199
|
+
return { toolCall, intent };
|
|
200
|
+
}
|
|
201
|
+
const systemPrompt = (prompt) => `
|
|
202
|
+
### System instructions
|
|
203
|
+
|
|
204
|
+
${prompt}
|
|
205
|
+
|
|
206
|
+
### Tool calling instructions
|
|
207
|
+
- Your reply MUST be a tool call and nothing but the tool call.
|
|
208
|
+
- NEVER respond with text content, only tool calls.
|
|
209
|
+
- Do NOT describe your plan, do NOT explain what you are doing, do NOT describe what you see, call tools.
|
|
210
|
+
- Provide thoughts in the '_intent' property of the tool calls instead.
|
|
211
|
+
`;
|
|
212
|
+
exports.kEditorHeaders = {
|
|
213
|
+
'Editor-Version': 'vscode/1.96.0',
|
|
214
|
+
'Editor-Plugin-Version': 'copilot-chat/0.24.0',
|
|
215
|
+
'User-Agent': 'GitHubCopilotChat/0.24.0',
|
|
216
|
+
'Accept': 'application/json',
|
|
217
|
+
'Content-Type': 'application/json',
|
|
218
|
+
'Copilot-Vision-Request': 'true',
|
|
219
|
+
};
|
|
220
|
+
async function getCopilotToken() {
|
|
221
|
+
const response = await fetch('https://api.github.com/copilot_internal/v2/token', {
|
|
222
|
+
method: 'GET',
|
|
223
|
+
headers: { 'Authorization': `token ${process.env.COPILOT_API_KEY}`, ...exports.kEditorHeaders }
|
|
224
|
+
});
|
|
225
|
+
const data = await response.json();
|
|
226
|
+
if (data.token)
|
|
227
|
+
return data.token;
|
|
228
|
+
throw new Error('Failed to get Copilot token');
|
|
229
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type * as types from '../types';
|
|
17
|
+
export declare class Google implements types.Provider {
|
|
18
|
+
readonly name = "google";
|
|
19
|
+
complete(conversation: types.Conversation, options: types.CompletionOptions): Promise<{
|
|
20
|
+
result: types.AssistantMessage;
|
|
21
|
+
usage: types.Usage;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
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.Google = void 0;
|
|
19
|
+
class Google {
|
|
20
|
+
name = 'google';
|
|
21
|
+
async complete(conversation, options) {
|
|
22
|
+
const contents = conversation.messages.map(toGeminiContent).flat();
|
|
23
|
+
const response = await create(options.model ?? 'gemini-2.5-pro', {
|
|
24
|
+
systemInstruction: {
|
|
25
|
+
role: 'system',
|
|
26
|
+
parts: [
|
|
27
|
+
{ text: systemPrompt(conversation.systemPrompt) }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
contents,
|
|
31
|
+
tools: conversation.tools.length > 0 ? [{ functionDeclarations: conversation.tools.map(toGeminiTool) }] : undefined,
|
|
32
|
+
generationConfig: { temperature: options.temperature },
|
|
33
|
+
}, options);
|
|
34
|
+
const [candidate] = response.candidates ?? [];
|
|
35
|
+
if (!candidate)
|
|
36
|
+
throw new Error('No candidates in response');
|
|
37
|
+
const usage = {
|
|
38
|
+
input: response.usageMetadata?.promptTokenCount ?? 0,
|
|
39
|
+
output: response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
40
|
+
};
|
|
41
|
+
const result = toAssistantMessage(candidate);
|
|
42
|
+
return { result, usage };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.Google = Google;
|
|
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
|
+
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
51
|
+
options.debug?.('lowire:google')('Request:', JSON.stringify(debugBody, null, 2));
|
|
52
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'x-goog-api-key': apiKey,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(createParams)
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
options.debug?.('lowire:google')('Response:', response.status);
|
|
62
|
+
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
63
|
+
}
|
|
64
|
+
const responseBody = await response.json();
|
|
65
|
+
options.debug?.('lowire:google')('Response:', JSON.stringify(responseBody, null, 2));
|
|
66
|
+
return responseBody;
|
|
67
|
+
}
|
|
68
|
+
function toGeminiTool(tool) {
|
|
69
|
+
return {
|
|
70
|
+
name: tool.name,
|
|
71
|
+
description: tool.description,
|
|
72
|
+
parameters: stripUnsupportedSchemaFields(tool.inputSchema),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function stripUnsupportedSchemaFields(schema) {
|
|
76
|
+
if (!schema || typeof schema !== 'object')
|
|
77
|
+
return schema;
|
|
78
|
+
const cleaned = Array.isArray(schema) ? [...schema] : { ...schema };
|
|
79
|
+
delete cleaned.additionalProperties;
|
|
80
|
+
delete cleaned.$schema;
|
|
81
|
+
for (const key in cleaned) {
|
|
82
|
+
if (cleaned[key] && typeof cleaned[key] === 'object')
|
|
83
|
+
cleaned[key] = stripUnsupportedSchemaFields(cleaned[key]);
|
|
84
|
+
}
|
|
85
|
+
return cleaned;
|
|
86
|
+
}
|
|
87
|
+
function toAssistantMessage(candidate) {
|
|
88
|
+
return {
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: candidate.content.parts.map(toContentPart).filter(Boolean),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function toContentPart(part) {
|
|
94
|
+
if (part.text) {
|
|
95
|
+
return {
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: part.text,
|
|
98
|
+
googleThoughtSignature: part.thoughtSignature,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (part.functionCall) {
|
|
102
|
+
return {
|
|
103
|
+
type: 'tool_call',
|
|
104
|
+
name: part.functionCall.name,
|
|
105
|
+
arguments: part.functionCall.args,
|
|
106
|
+
id: `call_${Math.random().toString(36).substring(2, 15)}`,
|
|
107
|
+
googleThoughtSignature: part.thoughtSignature,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function toGeminiContent(message) {
|
|
113
|
+
if (message.role === 'user') {
|
|
114
|
+
return [{
|
|
115
|
+
role: 'user',
|
|
116
|
+
parts: [{ text: message.content }]
|
|
117
|
+
}];
|
|
118
|
+
}
|
|
119
|
+
if (message.role === 'assistant') {
|
|
120
|
+
const parts = [];
|
|
121
|
+
const toolResults = [];
|
|
122
|
+
for (const part of message.content) {
|
|
123
|
+
if (part.type === 'text') {
|
|
124
|
+
parts.push({
|
|
125
|
+
text: part.text,
|
|
126
|
+
thoughtSignature: part.googleThoughtSignature,
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (part.type === 'tool_call') {
|
|
131
|
+
parts.push({
|
|
132
|
+
functionCall: {
|
|
133
|
+
name: part.name,
|
|
134
|
+
args: part.arguments
|
|
135
|
+
},
|
|
136
|
+
thoughtSignature: part.googleThoughtSignature,
|
|
137
|
+
});
|
|
138
|
+
if (part.result)
|
|
139
|
+
toolResults.push(...toGeminiToolResult(part, part.result));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (message.toolError) {
|
|
143
|
+
toolResults.push({
|
|
144
|
+
role: 'user',
|
|
145
|
+
parts: [{
|
|
146
|
+
text: message.toolError,
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return [{
|
|
151
|
+
role: 'model',
|
|
152
|
+
parts
|
|
153
|
+
}, ...toolResults];
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unsupported message role: ${message.role}`);
|
|
156
|
+
}
|
|
157
|
+
function toGeminiToolResult(call, toolResult) {
|
|
158
|
+
const responseContent = {};
|
|
159
|
+
const textParts = [];
|
|
160
|
+
const inlineDatas = [];
|
|
161
|
+
for (const part of toolResult.content) {
|
|
162
|
+
if (part.type === 'text') {
|
|
163
|
+
textParts.push(part.text);
|
|
164
|
+
}
|
|
165
|
+
else if (part.type === 'image') {
|
|
166
|
+
// Store image data for inclusion in response
|
|
167
|
+
inlineDatas.push({
|
|
168
|
+
inline_data: {
|
|
169
|
+
mime_type: part.mimeType,
|
|
170
|
+
data: part.data
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (textParts.length > 0)
|
|
176
|
+
responseContent.result = textParts.join('\n');
|
|
177
|
+
const result = [{
|
|
178
|
+
role: 'function',
|
|
179
|
+
parts: [{
|
|
180
|
+
functionResponse: {
|
|
181
|
+
name: call.name,
|
|
182
|
+
response: responseContent
|
|
183
|
+
}
|
|
184
|
+
}]
|
|
185
|
+
}];
|
|
186
|
+
if (inlineDatas.length > 0) {
|
|
187
|
+
result.push({
|
|
188
|
+
role: 'user',
|
|
189
|
+
parts: inlineDatas
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
const systemPrompt = (prompt) => `
|
|
195
|
+
### System instructions
|
|
196
|
+
|
|
197
|
+
${prompt}
|
|
198
|
+
|
|
199
|
+
### Tool calling instructions
|
|
200
|
+
- Make sure every message contains a tool call.
|
|
201
|
+
- When you use a tool, you may provide a brief thought or explanation in the content field
|
|
202
|
+
immediately before the tool_call. Do not split this into separate messages.
|
|
203
|
+
- Every reply must include a tool call.
|
|
204
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type * as types from '../types';
|
|
17
|
+
export declare class OpenAI implements types.Provider {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
complete(conversation: types.Conversation, options: types.CompletionOptions): Promise<{
|
|
20
|
+
result: types.AssistantMessage;
|
|
21
|
+
usage: types.Usage;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
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.OpenAI = void 0;
|
|
19
|
+
class OpenAI {
|
|
20
|
+
name = 'openai';
|
|
21
|
+
async complete(conversation, options) {
|
|
22
|
+
const inputItems = conversation.messages.map(toResponseInputItems).flat();
|
|
23
|
+
const tools = conversation.tools.map(toOpenAIFunctionTool);
|
|
24
|
+
const response = await create({
|
|
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 };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
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
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import * as types from '../types';
|
|
17
|
+
export declare function getProvider(loopName: 'openai' | 'github' | 'anthropic' | 'google'): types.Provider;
|