@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.
@@ -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;