@lowire/loop 0.0.2 → 0.0.4
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/README.md +1 -1
- package/index.d.ts +2 -1
- package/index.js +2 -1
- package/index.mjs +1 -0
- package/lib/cache.d.ts +7 -2
- package/lib/cache.js +9 -10
- package/lib/jsx/jsx-runtime.d.ts +17 -0
- package/lib/jsx/jsx-runtime.js +33 -0
- package/lib/loop.d.ts +9 -18
- package/lib/loop.js +55 -67
- package/lib/{providers/openaiCompletions.d.ts → mcp/index.d.ts} +17 -16
- package/lib/mcp/index.js +109 -0
- package/lib/providers/anthropic.js +37 -21
- package/lib/providers/github.d.ts +10 -10
- package/lib/providers/github.js +195 -34
- package/lib/providers/google.js +58 -40
- package/lib/providers/openai.js +35 -21
- package/lib/summary.d.ts +20 -0
- package/lib/summary.js +56 -0
- package/lib/types.d.ts +39 -33
- package/package.json +1 -34
- package/lib/providers/openaiCompletions.js +0 -174
package/lib/providers/github.js
CHANGED
|
@@ -15,47 +15,208 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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') {
|
|
35
96
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
headers: exports.kEditorHeaders
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: part.text,
|
|
39
99
|
};
|
|
40
100
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
});
|
|
50
153
|
}
|
|
51
|
-
const text = content.join(' ').trim();
|
|
52
|
-
if (text.trim())
|
|
53
|
-
message.result.content.unshift({ type: 'text', text: content.join(' ') });
|
|
54
154
|
}
|
|
55
|
-
|
|
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];
|
|
56
167
|
}
|
|
168
|
+
throw new Error(`Unsupported message role: ${message.role}`);
|
|
57
169
|
}
|
|
58
|
-
|
|
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
|
+
};
|
|
59
220
|
async function getCopilotToken() {
|
|
60
221
|
const response = await fetch('https://api.github.com/copilot_internal/v2/token', {
|
|
61
222
|
method: 'GET',
|
package/lib/providers/google.js
CHANGED
|
@@ -30,7 +30,7 @@ class Google {
|
|
|
30
30
|
contents,
|
|
31
31
|
tools: conversation.tools.length > 0 ? [{ functionDeclarations: conversation.tools.map(toGeminiTool) }] : undefined,
|
|
32
32
|
generationConfig: { temperature: options.temperature },
|
|
33
|
-
});
|
|
33
|
+
}, options);
|
|
34
34
|
const [candidate] = response.candidates ?? [];
|
|
35
35
|
if (!candidate)
|
|
36
36
|
throw new Error('No candidates in response');
|
|
@@ -43,21 +43,27 @@ class Google {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
exports.Google = Google;
|
|
46
|
-
async function create(model,
|
|
46
|
+
async function create(model, createParams, options) {
|
|
47
47
|
const apiKey = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
|
|
48
48
|
if (!apiKey)
|
|
49
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));
|
|
50
52
|
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
51
53
|
method: 'POST',
|
|
52
54
|
headers: {
|
|
53
55
|
'Content-Type': 'application/json',
|
|
54
56
|
'x-goog-api-key': apiKey,
|
|
55
57
|
},
|
|
56
|
-
body: JSON.stringify(
|
|
58
|
+
body: JSON.stringify(createParams)
|
|
57
59
|
});
|
|
58
|
-
if (!response.ok)
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
options.debug?.('lowire:google')('Response:', response.status);
|
|
59
62
|
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
60
|
-
|
|
63
|
+
}
|
|
64
|
+
const responseBody = await response.json();
|
|
65
|
+
options.debug?.('lowire:google')('Response:', JSON.stringify(responseBody, null, 2));
|
|
66
|
+
return responseBody;
|
|
61
67
|
}
|
|
62
68
|
function toGeminiTool(tool) {
|
|
63
69
|
return {
|
|
@@ -71,6 +77,7 @@ function stripUnsupportedSchemaFields(schema) {
|
|
|
71
77
|
return schema;
|
|
72
78
|
const cleaned = Array.isArray(schema) ? [...schema] : { ...schema };
|
|
73
79
|
delete cleaned.additionalProperties;
|
|
80
|
+
delete cleaned.$schema;
|
|
74
81
|
for (const key in cleaned) {
|
|
75
82
|
if (cleaned[key] && typeof cleaned[key] === 'object')
|
|
76
83
|
cleaned[key] = stripUnsupportedSchemaFields(cleaned[key]);
|
|
@@ -111,6 +118,7 @@ function toGeminiContent(message) {
|
|
|
111
118
|
}
|
|
112
119
|
if (message.role === 'assistant') {
|
|
113
120
|
const parts = [];
|
|
121
|
+
const toolResults = [];
|
|
114
122
|
for (const part of message.content) {
|
|
115
123
|
if (part.type === 'text') {
|
|
116
124
|
parts.push({
|
|
@@ -127,51 +135,61 @@ function toGeminiContent(message) {
|
|
|
127
135
|
},
|
|
128
136
|
thoughtSignature: part.googleThoughtSignature,
|
|
129
137
|
});
|
|
138
|
+
if (part.result)
|
|
139
|
+
toolResults.push(...toGeminiToolResult(part, part.result));
|
|
130
140
|
}
|
|
131
141
|
}
|
|
142
|
+
if (message.toolError) {
|
|
143
|
+
toolResults.push({
|
|
144
|
+
role: 'user',
|
|
145
|
+
parts: [{
|
|
146
|
+
text: message.toolError,
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
132
150
|
return [{
|
|
133
151
|
role: 'model',
|
|
134
152
|
parts
|
|
135
|
-
}];
|
|
153
|
+
}, ...toolResults];
|
|
136
154
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Store image data for inclusion in response
|
|
147
|
-
inlineDatas.push({
|
|
148
|
-
inline_data: {
|
|
149
|
-
mime_type: part.mimeType,
|
|
150
|
-
data: part.data
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
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);
|
|
154
164
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
response: responseContent
|
|
163
|
-
}
|
|
164
|
-
}]
|
|
165
|
-
}];
|
|
166
|
-
if (inlineDatas.length > 0) {
|
|
167
|
-
result.push({
|
|
168
|
-
role: 'user',
|
|
169
|
-
parts: inlineDatas
|
|
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
|
+
}
|
|
170
172
|
});
|
|
171
173
|
}
|
|
172
|
-
return result;
|
|
173
174
|
}
|
|
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;
|
|
175
193
|
}
|
|
176
194
|
const systemPrompt = (prompt) => `
|
|
177
195
|
### System instructions
|
package/lib/providers/openai.js
CHANGED
|
@@ -29,7 +29,7 @@ class OpenAI {
|
|
|
29
29
|
tools: tools.length > 0 ? tools : undefined,
|
|
30
30
|
tool_choice: conversation.tools.length > 0 ? 'auto' : undefined,
|
|
31
31
|
parallel_tool_calls: false,
|
|
32
|
-
});
|
|
32
|
+
}, options);
|
|
33
33
|
// Parse response output items
|
|
34
34
|
const result = { role: 'assistant', content: [] };
|
|
35
35
|
for (const item of response.output) {
|
|
@@ -58,21 +58,27 @@ class OpenAI {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
exports.OpenAI = OpenAI;
|
|
61
|
-
async function create(
|
|
61
|
+
async function create(createParams, options) {
|
|
62
62
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
63
63
|
const headers = {
|
|
64
64
|
'Content-Type': 'application/json',
|
|
65
65
|
'Authorization': `Bearer ${apiKey}`,
|
|
66
66
|
'Copilot-Vision-Request': 'true',
|
|
67
67
|
};
|
|
68
|
+
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
|
|
69
|
+
options.debug?.('lowire:openai-responses')('Request:', JSON.stringify(debugBody, null, 2));
|
|
68
70
|
const response = await fetch(`https://api.openai.com/v1/responses`, {
|
|
69
71
|
method: 'POST',
|
|
70
72
|
headers,
|
|
71
|
-
body: JSON.stringify(
|
|
73
|
+
body: JSON.stringify(createParams)
|
|
72
74
|
});
|
|
73
|
-
if (!response.ok)
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
options.debug?.('lowire:openai-responses')('Response:', response.status);
|
|
74
77
|
throw new Error(`API error: ${response.status} ${response.statusText} ${await response.text()}`);
|
|
75
|
-
|
|
78
|
+
}
|
|
79
|
+
const responseBody = await response.json();
|
|
80
|
+
options.debug?.('lowire:openai-responses')('Response:', JSON.stringify(responseBody, null, 2));
|
|
81
|
+
return responseBody;
|
|
76
82
|
}
|
|
77
83
|
function toResultContentPart(part) {
|
|
78
84
|
if (part.type === 'text') {
|
|
@@ -118,16 +124,16 @@ function toResponseInputItems(message) {
|
|
|
118
124
|
};
|
|
119
125
|
items.push(outputMessage);
|
|
120
126
|
}
|
|
121
|
-
|
|
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());
|
|
122
135
|
return items;
|
|
123
136
|
}
|
|
124
|
-
if (message.role === 'tool_result') {
|
|
125
|
-
return [{
|
|
126
|
-
type: 'function_call_output',
|
|
127
|
-
call_id: message.toolCallId,
|
|
128
|
-
output: message.result.content.map(toResultContentPart),
|
|
129
|
-
}];
|
|
130
|
-
}
|
|
131
137
|
throw new Error(`Unsupported message role: ${message.role}`);
|
|
132
138
|
}
|
|
133
139
|
function toOpenAIFunctionTool(tool) {
|
|
@@ -140,14 +146,22 @@ function toOpenAIFunctionTool(tool) {
|
|
|
140
146
|
};
|
|
141
147
|
}
|
|
142
148
|
function toFunctionToolCall(toolCall) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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;
|
|
151
165
|
}
|
|
152
166
|
function toToolCall(functionCall) {
|
|
153
167
|
return {
|
package/lib/summary.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
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 function summarizeConversation(task: string, conversation: types.Conversation, options: Pick<types.CompletionOptions, 'debug'>): {
|
|
18
|
+
summary: string;
|
|
19
|
+
lastMessage: types.AssistantMessage;
|
|
20
|
+
};
|
package/lib/summary.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.summarizeConversation = summarizeConversation;
|
|
4
|
+
const jsx_runtime_1 = require("./jsx/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* Copyright (c) Microsoft Corporation.
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* you may not use this file except in compliance with the License.
|
|
10
|
+
* You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/
|
|
20
|
+
const jsx_runtime_2 = require("./jsx/jsx-runtime");
|
|
21
|
+
function summarizeConversation(task, conversation, options) {
|
|
22
|
+
const summary = ['## Task', task];
|
|
23
|
+
const combinedState = {};
|
|
24
|
+
const assistantMessages = conversation.messages.filter(message => message.role === 'assistant');
|
|
25
|
+
for (let turn = 0; turn < assistantMessages.length - 1; ++turn) {
|
|
26
|
+
if (turn === 0) {
|
|
27
|
+
summary.push('');
|
|
28
|
+
summary.push('## History');
|
|
29
|
+
}
|
|
30
|
+
summary.push(``);
|
|
31
|
+
const text = assistantMessages[turn].content.filter(part => part.type === 'text').map(part => part.text).join('\n');
|
|
32
|
+
const toolCalls = assistantMessages[turn].content.filter(part => part.type === 'tool_call');
|
|
33
|
+
for (const toolCall of toolCalls) {
|
|
34
|
+
if (toolCall.result) {
|
|
35
|
+
for (const [name, state] of Object.entries(toolCall.result._meta?.['dev.lowire/state'] || {}))
|
|
36
|
+
combinedState[name] = state;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const message = assistantMessages[turn];
|
|
40
|
+
summary.push((0, jsx_runtime_1.jsxs)("step", { turn: turn + 1, children: [(0, jsx_runtime_1.jsx)("title", { children: text }), toolCalls.map(toolCall => (0, jsx_runtime_1.jsxs)("tool-call", { children: [(0, jsx_runtime_1.jsx)("name", { children: toolCall.name }), Object.keys(toolCall.arguments).length > 0 && (0, jsx_runtime_1.jsx)("arguments", { children: Object.entries(toolCall.arguments).map(([key, value]) => (0, jsx_runtime_2.jsx)(key, { children: [JSON.stringify(value)] })) })] })), toolCalls.map(toolCall => toolCall.result?._meta?.['dev.lowire/history'] || []).flat().map(h => (0, jsx_runtime_2.jsx)(h.category, { children: [h.content] })), message.toolError && (0, jsx_runtime_1.jsx)("error", { children: message.toolError })] }));
|
|
41
|
+
}
|
|
42
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
43
|
+
if (lastMessage) { // Remove state from combined state as it'll be a part of the last assistant message.
|
|
44
|
+
for (const part of lastMessage.content.filter(part => part.type === 'tool_call')) {
|
|
45
|
+
for (const name of Object.keys(part.result?._meta?.['dev.lowire/state'] || {}))
|
|
46
|
+
delete combinedState[name];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const [name, state] of Object.entries(combinedState)) {
|
|
50
|
+
summary.push('');
|
|
51
|
+
summary.push((0, jsx_runtime_1.jsx)("state", { name: name, children: state }));
|
|
52
|
+
}
|
|
53
|
+
options.debug?.('lowire:summary')(summary.join('\n'));
|
|
54
|
+
options.debug?.('lowire:summary')(JSON.stringify(lastMessage, null, 2));
|
|
55
|
+
return { summary: summary.join('\n'), lastMessage };
|
|
56
|
+
}
|