@astack-tech/integrations 0.1.1-beta.6 → 0.2.0-beta.10

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.
@@ -30,317 +30,303 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/model-provider/index.ts
31
31
  var model_provider_exports = {};
32
32
  __export(model_provider_exports, {
33
- Deepseek: () => deepseek_default
33
+ Deepseek: () => deepseek_default,
34
+ OpenAICompatible: () => openai_compatible_default
34
35
  });
35
36
  module.exports = __toCommonJS(model_provider_exports);
36
37
 
37
- // src/model-provider/deepseek/index.ts
38
+ // src/model-provider/openai-compatible/index.ts
38
39
  var import_core = require("@astack-tech/core");
39
40
  var import_openai = __toESM(require("openai"), 1);
40
- var Deepseek = class extends import_core.Component {
41
+ var DEFAULT_BASE_URL = "https://api.openai.com/v1";
42
+ var DEFAULT_NAME = "The model API";
43
+ var DEFAULT_TEMPERATURE = 0.7;
44
+ var FALLBACK_TEXT = "API call failed";
45
+ function isRecord(value) {
46
+ return typeof value === "object" && value !== null && !Array.isArray(value);
47
+ }
48
+ function toApiTool(tool) {
49
+ const raw = isRecord(tool) ? tool : {};
50
+ const definition = raw["function"];
51
+ if (raw["type"] === "function" && isRecord(definition)) {
52
+ const { name: name2, description: description2, parameters: parameters2, strict } = definition;
53
+ return {
54
+ type: "function",
55
+ function: {
56
+ name: typeof name2 === "string" ? name2 : "",
57
+ ...typeof description2 === "string" ? { description: description2 } : {},
58
+ ...isRecord(parameters2) ? { parameters: parameters2 } : {},
59
+ ...typeof strict === "boolean" ? { strict } : {}
60
+ }
61
+ };
62
+ }
63
+ const { name, description, parameters } = raw;
64
+ return {
65
+ type: "function",
66
+ function: {
67
+ name: typeof name === "string" ? name : "",
68
+ description: typeof description === "string" ? description : "",
69
+ parameters: isRecord(parameters) ? parameters : {}
70
+ }
71
+ };
72
+ }
73
+ function parseArguments(raw) {
74
+ if (raw.trim().length === 0) {
75
+ return {};
76
+ }
77
+ try {
78
+ const parsed = JSON.parse(raw);
79
+ return isRecord(parsed) ? parsed : {};
80
+ } catch {
81
+ return {};
82
+ }
83
+ }
84
+ function mapUsage(usage) {
85
+ return {
86
+ completion_tokens: usage.completion_tokens ?? 0,
87
+ prompt_tokens: usage.prompt_tokens ?? 0,
88
+ total_tokens: usage.total_tokens ?? 0,
89
+ ...usage.prompt_cache_hit_tokens === void 0 ? {} : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens },
90
+ ...usage.prompt_cache_miss_tokens === void 0 ? {} : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }
91
+ };
92
+ }
93
+ var OpenAICompatible = class extends import_core.Component {
94
+ client;
95
+ model;
96
+ temperature;
97
+ maxTokens;
98
+ topP;
99
+ systemPrompt;
100
+ tools;
101
+ verbose;
102
+ name;
41
103
  constructor(config) {
42
104
  super({});
43
105
  this.client = new import_openai.default({
44
106
  apiKey: config.apiKey,
45
- baseURL: config.baseURL || "https://api.deepseek.com/v1",
107
+ baseURL: config.baseURL ?? DEFAULT_BASE_URL,
46
108
  dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false
47
109
  });
48
- this.model = config.model || "deepseek-chat";
49
- this.temperature = config.temperature ?? 0.7;
110
+ this.model = config.model;
111
+ this.name = config.name ?? DEFAULT_NAME;
112
+ this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
50
113
  this.maxTokens = config.maxTokens;
51
114
  this.topP = config.topP;
52
115
  this.systemPrompt = config.systemPrompt;
53
- if (config.rawTools && config.rawTools.length > 0) {
54
- this.tools = config.rawTools.map((tool) => ({
55
- type: "function",
56
- function: {
57
- name: tool.name,
58
- description: tool.description,
59
- parameters: tool.parameters || {}
60
- }
61
- }));
62
- } else {
63
- this.tools = config.tools;
64
- }
116
+ this.verbose = config.verbose ?? false;
117
+ this.tools = config.rawTools !== void 0 && config.rawTools.length > 0 ? config.rawTools.map(toApiTool) : config.tools;
65
118
  import_core.Component.Port.I("prompt").attach(this);
66
119
  import_core.Component.Port.I("messages").attach(this);
67
120
  import_core.Component.Port.O("completion").attach(this);
68
121
  import_core.Component.Port.O("message").attach(this);
69
122
  }
70
123
  /**
71
- * 生成文本完成
72
- * @param prompt 提示文本
73
- * @returns 生成的文本
124
+ * Generates a completion for a single prompt.
125
+ *
126
+ * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.
74
127
  */
75
128
  async generateCompletion(prompt) {
76
- const messages = [];
77
- if (this.systemPrompt) {
78
- messages.push({
79
- role: "system",
80
- content: this.systemPrompt
81
- });
82
- }
83
- messages.push({
84
- role: "user",
85
- content: prompt
129
+ const response = await this.client.chat.completions.create({
130
+ ...this.baseRequest(),
131
+ messages: [...this.systemMessages(), { role: "user", content: prompt }],
132
+ ...this.toolsParam(this.tools)
86
133
  });
87
- const requestParams = {
88
- model: this.model,
89
- messages,
90
- temperature: this.temperature,
91
- max_tokens: this.maxTokens,
92
- top_p: this.topP
93
- };
94
- if (this.tools && this.tools.length > 0) {
95
- requestParams.tools = this.tools;
96
- }
97
- const response = await this.client.chat.completions.create(requestParams);
98
- if (response.choices[0].message.tool_calls && response.choices[0].message.tool_calls.length > 0) {
99
- return JSON.stringify(response.choices[0].message);
134
+ const message = this.firstMessage(response);
135
+ if (message.tool_calls !== void 0 && message.tool_calls.length > 0) {
136
+ return JSON.stringify(message);
100
137
  }
101
- return response.choices[0].message.content || "";
138
+ return message.content ?? "";
102
139
  }
103
140
  /**
104
- * 流式处理对话消息
105
- * @param messages 对话消息数组
106
- * @param options 可选的调用选项,包含临时工具列表
107
- * @returns 生成的流式响应消息异步生成器
141
+ * Sends a conversation and returns the assistant reply.
108
142
  */
109
- async *streamChatCompletion(messages, options) {
110
- const formattedMessages = [];
111
- if (this.systemPrompt) {
112
- formattedMessages.push({
113
- role: "system",
114
- content: this.systemPrompt
115
- });
143
+ async chatCompletion(messages, options) {
144
+ const response = await this.client.chat.completions.create({
145
+ ...this.baseRequest(),
146
+ messages: this.formatMessages(messages),
147
+ ...this.toolsParam(options?.temporaryTools ?? this.tools)
148
+ });
149
+ const message = this.firstMessage(response);
150
+ const result = { role: "assistant", content: message.content ?? "" };
151
+ if (response.usage !== void 0) {
152
+ result.usage = mapUsage(response.usage);
116
153
  }
117
- formattedMessages.push(
118
- ...messages.map((msg) => {
119
- const formattedMsg = {
120
- role: msg.role,
121
- content: msg.content
122
- };
123
- if (msg.role === "tool" && msg.tool_call_id) {
124
- formattedMsg.tool_call_id = msg.tool_call_id;
125
- }
126
- return formattedMsg;
127
- })
128
- );
129
- const requestParams = {
130
- model: this.model,
131
- messages: formattedMessages,
132
- temperature: this.temperature,
133
- max_tokens: this.maxTokens,
134
- top_p: this.topP,
135
- stream: true
136
- // 开启流式模式
137
- };
138
- const toolsToUse = options?.temporaryTools || this.tools;
139
- if (toolsToUse && toolsToUse.length > 0) {
140
- const formattedTools = toolsToUse.map((tool) => {
141
- const typedTool = tool;
142
- if (typedTool.type === "function") {
143
- return tool;
154
+ if (message.tool_calls !== void 0 && message.tool_calls.length > 0) {
155
+ result.tool_calls = message.tool_calls.map((toolCall) => {
156
+ if (toolCall.type !== "function") {
157
+ throw new Error(`Unsupported tool call type: ${toolCall.type}`);
144
158
  }
159
+ const rawArguments = toolCall.function.arguments;
145
160
  return {
161
+ id: toolCall.id,
146
162
  type: "function",
147
163
  function: {
148
- name: typedTool.name,
149
- description: typedTool.description,
150
- parameters: typedTool.parameters || {}
151
- }
164
+ name: toolCall.function.name,
165
+ arguments: rawArguments.length > 0 ? rawArguments : "{}"
166
+ },
167
+ tool_name: toolCall.function.name,
168
+ arguments: parseArguments(rawArguments)
152
169
  };
153
170
  });
154
- requestParams.tools = formattedTools;
155
171
  }
156
- const stream = await this.client.chat.completions.create(requestParams);
172
+ return result;
173
+ }
174
+ /**
175
+ * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.
176
+ */
177
+ async *streamChatCompletion(messages, options) {
178
+ const stream = await this.client.chat.completions.create({
179
+ ...this.baseRequest(),
180
+ messages: this.formatMessages(messages),
181
+ ...this.toolsParam(options?.temporaryTools ?? this.tools),
182
+ stream: true
183
+ });
157
184
  for await (const chunk of stream) {
158
185
  const delta = chunk.choices[0]?.delta;
159
- if (delta?.content) {
160
- yield {
161
- role: "assistant",
162
- content: delta.content
163
- };
186
+ const text = delta?.content ?? "";
187
+ if (text !== "") {
188
+ yield { role: "assistant", content: text };
164
189
  }
165
190
  if (delta?.tool_calls) {
166
191
  yield {
167
192
  role: "assistant",
168
193
  content: "",
169
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
- tool_calls: delta.tool_calls.map((toolCall) => ({
171
- id: toolCall.id || "",
172
- function: {
173
- name: toolCall.function?.name || "",
174
- arguments: toolCall.function?.arguments || ""
175
- },
176
- type: "function",
177
- tool_name: toolCall.function?.name || "",
178
- arguments: toolCall.function?.arguments || ""
179
- }))
194
+ tool_calls: delta.tool_calls.map((toolCall) => {
195
+ const name = toolCall.function?.name ?? "";
196
+ const args = toolCall.function?.arguments ?? "";
197
+ return {
198
+ id: toolCall.id ?? "",
199
+ type: "function",
200
+ function: { name, arguments: args },
201
+ tool_name: name,
202
+ arguments: args
203
+ };
204
+ })
180
205
  };
181
206
  }
182
207
  if (chunk.usage) {
183
- yield {
184
- role: "assistant",
185
- content: "",
186
- usage: {
187
- completion_tokens: chunk.usage.completion_tokens || 0,
188
- prompt_tokens: chunk.usage.prompt_tokens || 0,
189
- prompt_cache_hit_tokens: chunk.usage.prompt_cache_hit_tokens,
190
- prompt_cache_miss_tokens: chunk.usage.prompt_cache_miss_tokens,
191
- total_tokens: chunk.usage.total_tokens || 0
192
- }
193
- };
208
+ yield { role: "assistant", content: "", usage: mapUsage(chunk.usage) };
194
209
  }
195
210
  }
196
211
  }
197
212
  /**
198
- * 处理对话消息
199
- * @param messages 对话消息数组
200
- * @param options 可选的调用选项,包含临时工具列表
201
- * @returns 生成的响应消息
213
+ * Runs the model outside a pipeline.
214
+ *
215
+ * @param input - A prompt string or a list of messages.
202
216
  */
203
- async chatCompletion(messages, options) {
204
- const formattedMessages = [];
205
- if (this.systemPrompt) {
206
- formattedMessages.push({
207
- role: "system",
208
- content: this.systemPrompt
209
- });
210
- }
211
- formattedMessages.push(
212
- ...messages.map((msg) => {
213
- const formattedMsg = {
214
- role: msg.role,
215
- content: msg.content
216
- };
217
- if (msg.role === "tool" && msg.tool_call_id) {
218
- formattedMsg.tool_call_id = msg.tool_call_id;
217
+ run(input) {
218
+ return typeof input === "string" ? this.generateCompletion(input) : this.chatCompletion(input);
219
+ }
220
+ /**
221
+ * Connects the `prompt` and `messages` input ports to the model.
222
+ * API errors produce a fallback value so the pipeline keeps flowing.
223
+ */
224
+ _transform($i, $o) {
225
+ $i("prompt").receive((prompt) => {
226
+ this.generateCompletion(prompt).then(
227
+ (completion) => {
228
+ $o("completion").send(completion);
229
+ },
230
+ (error) => {
231
+ this.logError(error);
232
+ $o("completion").send(FALLBACK_TEXT);
219
233
  }
220
- return formattedMsg;
221
- })
222
- );
223
- const requestParams = {
234
+ );
235
+ });
236
+ $i("messages").receive((messages) => {
237
+ this.chatCompletion(messages).then(
238
+ (message) => {
239
+ $o("message").send(message);
240
+ },
241
+ (error) => {
242
+ this.logError(error);
243
+ $o("message").send({ role: "assistant", content: FALLBACK_TEXT });
244
+ }
245
+ );
246
+ });
247
+ }
248
+ baseRequest() {
249
+ return {
224
250
  model: this.model,
225
- messages: formattedMessages,
226
251
  temperature: this.temperature,
227
- max_tokens: this.maxTokens,
228
- top_p: this.topP
252
+ ...this.maxTokens === void 0 ? {} : { max_tokens: this.maxTokens },
253
+ ...this.topP === void 0 ? {} : { top_p: this.topP }
229
254
  };
230
- const toolsToUse = options?.temporaryTools || this.tools;
231
- if (toolsToUse && toolsToUse.length > 0) {
232
- const formattedTools = toolsToUse.map((tool) => {
233
- const typedTool = tool;
234
- if (typedTool.type === "function") {
235
- return tool;
236
- }
237
- return {
238
- type: "function",
239
- function: {
240
- name: typedTool.name,
241
- description: typedTool.description,
242
- parameters: typedTool.parameters || {}
243
- }
244
- };
245
- });
246
- requestParams.tools = formattedTools;
247
- }
248
- const response = await this.client.chat.completions.create(requestParams);
249
- const responseMessage = response.choices[0].message;
250
- const result = {
251
- role: "assistant",
252
- content: responseMessage.content || ""
253
- };
254
- if (response.usage) {
255
- const usage = response.usage;
256
- result.usage = {
257
- completion_tokens: usage.completion_tokens || 0,
258
- prompt_tokens: usage.prompt_tokens || 0,
259
- prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens,
260
- prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens,
261
- total_tokens: usage.total_tokens || 0
262
- };
263
- }
264
- if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
265
- result.tool_calls = responseMessage.tool_calls.map((toolCall) => {
266
- if (toolCall.type !== "function" || !("function" in toolCall)) {
267
- throw new Error(`Unsupported tool call type: ${toolCall.type}`);
268
- }
269
- let args;
270
- const rawArgs = toolCall.function.arguments;
271
- if (typeof rawArgs === "string") {
272
- const raw = rawArgs;
273
- try {
274
- args = raw && raw.trim().length > 0 ? JSON.parse(raw) : {};
275
- } catch {
276
- args = {};
277
- }
278
- } else if (rawArgs && typeof rawArgs === "object") {
279
- args = rawArgs;
280
- } else {
281
- args = {};
282
- }
283
- return {
284
- id: toolCall.id,
285
- // 满足 Agent 组件期望的格式
286
- function: {
287
- name: toolCall.function.name,
288
- arguments: toolCall.function.arguments || "{}"
289
- },
290
- type: "function",
291
- // 满足内部 ToolCall 接口
292
- tool_name: toolCall.function.name,
293
- arguments: args
294
- };
295
- });
296
- }
297
- return result;
255
+ }
256
+ toolsParam(tools) {
257
+ return tools !== void 0 && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};
258
+ }
259
+ systemMessages() {
260
+ return this.systemPrompt === void 0 || this.systemPrompt === "" ? [] : [{ role: "system", content: this.systemPrompt }];
298
261
  }
299
262
  /**
300
- * 在独立模式下运行组件
301
- * @param input 输入参数,可以是提示文本或对话消息数组
302
- * @returns 生成的文本或响应消息
263
+ * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:
264
+ * agents send tool results back as user messages, and the API rejects assistant tool
265
+ * calls that are not followed by tool messages.
303
266
  */
304
- async run(input) {
305
- if (typeof input === "string") {
306
- return this.generateCompletion(input);
307
- } else {
308
- return this.chatCompletion(input);
309
- }
267
+ formatMessages(messages) {
268
+ return [...this.systemMessages(), ...messages.map((message) => this.toApiMessage(message))];
310
269
  }
311
270
  /**
312
- * 在流水线中运行组件
313
- * @param $i 输入端口
314
- * @param $o 输出端口
271
+ * Converts one input message to its API shape.
272
+ *
273
+ * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no
274
+ * `tool_call_id`. The API would reject both.
315
275
  */
316
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
- _transform($i, $o) {
318
- $i("prompt").receive(async (prompt) => {
319
- try {
320
- const completion = await this.generateCompletion(prompt);
321
- $o("completion").send(completion);
322
- } catch (error) {
323
- console.error("Deepseek API \u8C03\u7528\u9519\u8BEF:", error);
324
- $o("completion").send("API \u8C03\u7528\u9519\u8BEF");
325
- }
326
- });
327
- $i("messages").receive(async (messages) => {
328
- try {
329
- const responseMessage = await this.chatCompletion(messages);
330
- $o("message").send(responseMessage);
331
- } catch (error) {
332
- console.error("Deepseek API \u8C03\u7528\u9519\u8BEF:", error);
333
- $o("message").send({
334
- role: "assistant",
335
- content: "API \u8C03\u7528\u9519\u8BEF"
336
- });
337
- }
276
+ toApiMessage(message) {
277
+ const content = message.content ?? "";
278
+ switch (message.role) {
279
+ case "system":
280
+ return { role: "system", content };
281
+ case "user":
282
+ return { role: "user", content };
283
+ case "assistant":
284
+ return message.content === void 0 ? { role: "assistant" } : { role: "assistant", content: message.content };
285
+ case "tool":
286
+ if (message.tool_call_id === void 0 || message.tool_call_id === "") {
287
+ throw new Error(
288
+ `${this.name}: a tool message needs the tool_call_id of the call it answers.`
289
+ );
290
+ }
291
+ return { role: "tool", content, tool_call_id: message.tool_call_id };
292
+ default:
293
+ throw new Error(
294
+ `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`
295
+ );
296
+ }
297
+ }
298
+ firstMessage(response) {
299
+ const choice = response.choices[0];
300
+ if (choice === void 0) {
301
+ throw new Error(`${this.name} returned no choices.`);
302
+ }
303
+ return choice.message;
304
+ }
305
+ logError(error) {
306
+ if (this.verbose) {
307
+ console.error(`[${this.name}] API call failed:`, error);
308
+ }
309
+ }
310
+ };
311
+ var openai_compatible_default = OpenAICompatible;
312
+
313
+ // src/model-provider/deepseek/index.ts
314
+ var DEFAULT_BASE_URL2 = "https://api.deepseek.com/v1";
315
+ var DEFAULT_MODEL = "deepseek-chat";
316
+ var Deepseek = class extends openai_compatible_default {
317
+ constructor(config) {
318
+ super({
319
+ ...config,
320
+ model: config.model ?? DEFAULT_MODEL,
321
+ baseURL: config.baseURL ?? DEFAULT_BASE_URL2,
322
+ name: "DeepSeek"
338
323
  });
339
324
  }
340
325
  };
341
326
  var deepseek_default = Deepseek;
342
327
  // Annotate the CommonJS export names for ESM import in node:
343
328
  0 && (module.exports = {
344
- Deepseek
329
+ Deepseek,
330
+ OpenAICompatible
345
331
  });
346
332
  //# sourceMappingURL=index.cjs.map